|
@@ -3,35 +3,44 @@ package grs
|
|
|
import (
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
+ "log"
|
|
|
"net"
|
|
|
"net/url"
|
|
|
"regexp"
|
|
|
+ "strings"
|
|
|
"time"
|
|
|
|
|
|
- "gopkg.in/jmcvetta/napping.v3"
|
|
|
- "gopkg.in/mgo.v2/bson"
|
|
|
+ napping "gopkg.in/jmcvetta/napping.v3"
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
- GRS_URL = "http://rest.db.ripe.net/search.json"
|
|
|
+ // GrsURL contains the RIPE REST API URL
|
|
|
+ GrsURL = "http://rest.db.ripe.net/search.json"
|
|
|
)
|
|
|
|
|
|
+// GRS contains whois data from the ripe API
|
|
|
type GRS struct {
|
|
|
- ID bson.ObjectId `bson:"_id,omitempty" json:"id"`
|
|
|
- Source string `bson:"source" json:"source"`
|
|
|
- IpFrom string `bson:"ip_from" json:"ip_from"`
|
|
|
- IpTo string `bson:"ip_to" json:"ip_to"`
|
|
|
- IpFromRaw []byte `bson:"ip_from_raw" json:"ip_from_raw"`
|
|
|
- IpToRaw []byte `bson:"ip_to_raw" json:"ip_to_raw"`
|
|
|
- Description string `bson:"description" json:"description"`
|
|
|
- Country string `bson:"country" json:"country"`
|
|
|
- Status string `bson:"status" json:"status"`
|
|
|
- Cidr string `bson:"cidr" json:"cidr"`
|
|
|
- Name string `bson:"name" json:"name"`
|
|
|
- Org string `bson:"org" json:"org"`
|
|
|
- CreatedAt time.Time `bson:"created_at" json:"created_at"`
|
|
|
+ //ID bson.ObjectId `bson:"_id,omitempty" json:"id"`
|
|
|
+ ID string `bson:"_id" json:"_id"`
|
|
|
+ Source string `bson:"source" json:"source"`
|
|
|
+ IpFrom string `bson:"ip_from" json:"ip_from"`
|
|
|
+ IpTo string `bson:"ip_to" json:"ip_to"`
|
|
|
+ IpFromRaw []byte `bson:"ip_from_raw" json:"ip_from_raw"`
|
|
|
+ IpToRaw []byte `bson:"ip_to_raw" json:"ip_to_raw"`
|
|
|
+ Description string `bson:"description" json:"description"`
|
|
|
+ Country string `bson:"country" json:"country"`
|
|
|
+ Status string `bson:"status" json:"status"`
|
|
|
+ Cidr string `bson:"cidr" json:"cidr"`
|
|
|
+ NetMaskBits int `bson:"netmask_bits" json:"netmask_bits"`
|
|
|
+ Name string `bson:"name" json:"name"`
|
|
|
+ Org string `bson:"org" json:"org"`
|
|
|
+ ASN string `bson:"asn" json:"asn"`
|
|
|
+ CreatedAt time.Time `bson:"created_at" json:"created_at"`
|
|
|
+ ModifiedAt time.Time `bson:"modified_at" json:"modified_at"`
|
|
|
+ Organization Organization `bson:"organization" json:"organization"`
|
|
|
}
|
|
|
|
|
|
+// GRSRaw contains the raw GRS response from the ripe API server
|
|
|
type GRSRaw struct {
|
|
|
Service Name `json:"service"`
|
|
|
Parameters Parameters `json:"parameters"`
|
|
@@ -40,10 +49,12 @@ type GRSRaw struct {
|
|
|
ErrorMessages ErrorMessages `json:"errormessages"`
|
|
|
}
|
|
|
|
|
|
+// Name contains the GRS name
|
|
|
type Name struct {
|
|
|
Name string `json:"name"`
|
|
|
}
|
|
|
|
|
|
+// Parameters contains all API query parameters
|
|
|
type Parameters struct {
|
|
|
InverseLookup map[string]interface{} `json:"inverse-lookup"`
|
|
|
TypeFilters map[string]interface{} `json:"type-filters"`
|
|
@@ -52,10 +63,12 @@ type Parameters struct {
|
|
|
Sources Sources `json:"sources"`
|
|
|
}
|
|
|
|
|
|
+// QueryString contains a list of query-strings for API requests
|
|
|
type QueryString struct {
|
|
|
QueryString []Value `json:"query-string"`
|
|
|
}
|
|
|
|
|
|
+// Value contains a GRS value
|
|
|
type Value struct {
|
|
|
Value string `json:"value"`
|
|
|
}
|
|
@@ -64,6 +77,34 @@ type Sources struct {
|
|
|
Ids []Id `json:"source"`
|
|
|
}
|
|
|
|
|
|
+type AutNum struct {
|
|
|
+ Source string
|
|
|
+ ID string
|
|
|
+ Name string
|
|
|
+ Org string
|
|
|
+ AdminContact string
|
|
|
+ TechContact string
|
|
|
+ Status string
|
|
|
+ Notify []string
|
|
|
+ Maintainer []string
|
|
|
+ Created time.Time
|
|
|
+ Modified time.Time
|
|
|
+ Organization Organization
|
|
|
+}
|
|
|
+
|
|
|
+type Organization struct {
|
|
|
+ // ID string
|
|
|
+ Name string
|
|
|
+ Type string
|
|
|
+ Address []string
|
|
|
+ Phone string
|
|
|
+ Fax string
|
|
|
+ Email string
|
|
|
+ Organization string
|
|
|
+ AbuseEmail string
|
|
|
+ Source string
|
|
|
+}
|
|
|
+
|
|
|
type Id struct {
|
|
|
ID string `json:"id"`
|
|
|
}
|
|
@@ -75,7 +116,7 @@ type Objects struct {
|
|
|
type Object struct {
|
|
|
Type string `json:"type"`
|
|
|
Link Link `json:"link"`
|
|
|
- Source Id `json:source"`
|
|
|
+ Source Id `json:"source"`
|
|
|
PrimaryKey Attributes `json:"primary-key"`
|
|
|
Attributes Attributes `json:"attributes"`
|
|
|
Tags Tags `json:"tags"`
|
|
@@ -120,6 +161,143 @@ type ErrorMessage struct {
|
|
|
Text string `json:"text"`
|
|
|
}
|
|
|
|
|
|
+// LookupAutNum looks up the Autonomous System Number (ASN) using the RIPE API and returns an AutNum object
|
|
|
+func LookupAutNum(id string) (AutNum, error) {
|
|
|
+ asID := id
|
|
|
+ asID = strings.TrimPrefix(asID, "MNT-")
|
|
|
+ asID = strings.TrimPrefix(asID, "AS")
|
|
|
+ asID = fmt.Sprintf("AS%s", id)
|
|
|
+
|
|
|
+ args := url.Values{}
|
|
|
+ args.Set("query-string", id)
|
|
|
+ args.Add("type-filter", "aut-num")
|
|
|
+ args.Add("source", "ripe-grs")
|
|
|
+ args.Add("source", "arin-grs")
|
|
|
+ args.Add("source", "apnic-grs")
|
|
|
+ args.Add("source", "lacnic-grs")
|
|
|
+ args.Add("source", "afrinic-grs")
|
|
|
+
|
|
|
+ var data GRSRaw
|
|
|
+
|
|
|
+ resp, err := napping.Get(GrsURL, &args, &data, nil)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return AutNum{}, errors.New(fmt.Sprint("Failed to load data from ", resp.Url, ": ", err))
|
|
|
+ }
|
|
|
+
|
|
|
+ if resp.Status() != 200 {
|
|
|
+ return AutNum{}, errors.New(fmt.Sprint("Failed to load data from GRS server: ", resp.Status()))
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(data.ErrorMessages.ErrorMessage) > 0 {
|
|
|
+ return AutNum{}, errors.New(fmt.Sprint("No GRS data for ", id, ": ", data.ErrorMessages.ErrorMessage[0].Text))
|
|
|
+ }
|
|
|
+ if err != nil {
|
|
|
+ return AutNum{}, err
|
|
|
+ }
|
|
|
+
|
|
|
+ var aut AutNum
|
|
|
+
|
|
|
+ var autnum *Object
|
|
|
+ var org *Object
|
|
|
+
|
|
|
+ ymdRegexp := regexp.MustCompile(`^\d+$`)
|
|
|
+
|
|
|
+ for i, e := range data.Objects.Objects {
|
|
|
+ switch e.Type {
|
|
|
+ case "aut-num":
|
|
|
+ autnum = &data.Objects.Objects[i]
|
|
|
+ case "organisation":
|
|
|
+ org = &data.Objects.Objects[i]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if autnum != nil {
|
|
|
+ for _, e := range autnum.Attributes.Attributes {
|
|
|
+ switch e.Name {
|
|
|
+ case "aut-num":
|
|
|
+ aut.ID = e.Value
|
|
|
+ case "as-name":
|
|
|
+ aut.Name = e.Value
|
|
|
+ case "org":
|
|
|
+ aut.Org = e.Value
|
|
|
+ case "admin-c":
|
|
|
+ aut.AdminContact = e.Value
|
|
|
+ case "tech-c":
|
|
|
+ aut.TechContact = e.Value
|
|
|
+ case "status":
|
|
|
+ aut.Status = e.Value
|
|
|
+ case "mnt-by":
|
|
|
+ aut.Maintainer = append(aut.Maintainer, e.Value)
|
|
|
+ case "source":
|
|
|
+ aut.Source = e.Value
|
|
|
+ case "created":
|
|
|
+ if ymdRegexp.MatchString(e.Value) {
|
|
|
+ t, err := time.Parse("20060102", e.Value)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("%s: %s\n", e.Value, err)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ aut.Created = t
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ t, err := time.Parse(time.RFC3339, e.Value)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("%s: %s\n", e.Value, err)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ aut.Created = t
|
|
|
+ case "last-modified":
|
|
|
+ if ymdRegexp.MatchString(e.Value) {
|
|
|
+ t, err := time.Parse("20060102", e.Value)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("%s: %s\n", e.Value, err)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ aut.Modified = t
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ t, err := time.Parse(time.RFC3339, e.Value)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("%s: %s\n", e.Value, err)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ aut.Modified = t
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if org != nil {
|
|
|
+ o := Organization{}
|
|
|
+
|
|
|
+ for _, e := range org.Attributes.Attributes {
|
|
|
+ switch e.Name {
|
|
|
+ case "organisation":
|
|
|
+ o.Organization = e.Value
|
|
|
+ case "org-name":
|
|
|
+ o.Name = e.Value
|
|
|
+ case "org-type":
|
|
|
+ o.Type = e.Value
|
|
|
+ case "address":
|
|
|
+ o.Address = append(o.Address, e.Value)
|
|
|
+ case "phone":
|
|
|
+ o.Phone = e.Value
|
|
|
+ case "abuse-mailbox":
|
|
|
+ o.AbuseEmail = e.Value
|
|
|
+ case "e-mail":
|
|
|
+ o.Email = e.Value
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ aut.Organization = o
|
|
|
+ }
|
|
|
+
|
|
|
+ return aut, nil
|
|
|
+}
|
|
|
+
|
|
|
+// Lookup loads GRS data from the RIPE REST API and returns a GRS object
|
|
|
func Lookup(ip string) (GRS, error) {
|
|
|
// var data map[string]interface{}
|
|
|
var data GRSRaw
|
|
@@ -132,7 +310,7 @@ func Lookup(ip string) (GRS, error) {
|
|
|
args.Add("source", "lacnic-grs")
|
|
|
args.Add("source", "afrinic-grs")
|
|
|
|
|
|
- resp, err := napping.Get(GRS_URL, &args, &data, nil)
|
|
|
+ resp, err := napping.Get(GrsURL, &args, &data, nil)
|
|
|
|
|
|
if err != nil {
|
|
|
return GRS{}, errors.New(fmt.Sprint("Failed to load data from ", resp.Url, ": ", err))
|
|
@@ -149,8 +327,11 @@ func Lookup(ip string) (GRS, error) {
|
|
|
grs := GRS{}
|
|
|
var grs_inetnum *Object
|
|
|
var grs_route *Object
|
|
|
+ var grs_org *Object
|
|
|
var inetnum string
|
|
|
|
|
|
+ ymdRegexp := regexp.MustCompile(`^\d+$`)
|
|
|
+
|
|
|
for i, e := range data.Objects.Objects {
|
|
|
switch e.Type {
|
|
|
case "inetnum":
|
|
@@ -159,6 +340,8 @@ func Lookup(ip string) (GRS, error) {
|
|
|
grs_inetnum = &data.Objects.Objects[i]
|
|
|
case "route":
|
|
|
grs_route = &data.Objects.Objects[i]
|
|
|
+ case "organisation":
|
|
|
+ grs_org = &data.Objects.Objects[i]
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -181,6 +364,42 @@ func Lookup(ip string) (GRS, error) {
|
|
|
grs.Status = e.Value
|
|
|
case "org":
|
|
|
grs.Org = e.Value
|
|
|
+ case "source":
|
|
|
+ grs.Source = e.Value
|
|
|
+ case "created":
|
|
|
+ if ymdRegexp.MatchString(e.Value) {
|
|
|
+ t, err := time.Parse("20060102", e.Value)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("%s: %s\n", e.Value, err)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ grs.CreatedAt = t
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ t, err := time.Parse(time.RFC3339, e.Value)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("%s: %s\n", e.Value, err)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ grs.CreatedAt = t
|
|
|
+ case "last-modified":
|
|
|
+ if ymdRegexp.MatchString(e.Value) {
|
|
|
+ t, err := time.Parse("20060102", e.Value)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("%s: %s\n", e.Value, err)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ grs.ModifiedAt = t
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ t, err := time.Parse(time.RFC3339, e.Value)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("%s: %s\n", e.Value, err)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ grs.ModifiedAt = t
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -190,27 +409,32 @@ func Lookup(ip string) (GRS, error) {
|
|
|
if grs_route != nil {
|
|
|
for _, e := range grs_route.Attributes.Attributes {
|
|
|
switch e.Name {
|
|
|
- case "route":
|
|
|
- grs.Cidr = e.Value
|
|
|
+ //case "route":
|
|
|
+ // grs.Cidr = e.Value
|
|
|
case "descr":
|
|
|
grs.Description = e.Value
|
|
|
+ case "origin":
|
|
|
+ grs.ASN = e.Value
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- var ip_from net.IP
|
|
|
- var ip_to net.IP
|
|
|
+ var ipFrom net.IP
|
|
|
+ var ipTo net.IP
|
|
|
|
|
|
if inetnum != "" {
|
|
|
|
|
|
_, network, err := net.ParseCIDR(inetnum)
|
|
|
|
|
|
if err == nil {
|
|
|
- ip_from, ip_to = networkRange(network)
|
|
|
- grs.IpFrom = ip_from.String()
|
|
|
- grs.IpTo = ip_to.String()
|
|
|
+ ipFrom, ipTo = networkRange(network)
|
|
|
+ grs.IpFrom = ipFrom.String()
|
|
|
+ grs.IpTo = ipTo.String()
|
|
|
if grs.Cidr == "" {
|
|
|
grs.Cidr = network.String()
|
|
|
+
|
|
|
+ _, numBits := network.Mask.Size()
|
|
|
+ grs.NetMaskBits = numBits
|
|
|
}
|
|
|
} else {
|
|
|
reg := regexp.MustCompile(`^\s*(\d+\.\d+\.\d+\.\d+)\s*-\s*(\d+\.\d+\.\d+\.\d+)\s*$`)
|
|
@@ -220,15 +444,47 @@ func Lookup(ip string) (GRS, error) {
|
|
|
grs.IpFrom = matches[0][1]
|
|
|
grs.IpTo = matches[0][2]
|
|
|
|
|
|
- ip_from = net.ParseIP(grs.IpFrom)
|
|
|
- ip_to = net.ParseIP(grs.IpTo)
|
|
|
+ ipFrom = net.ParseIP(grs.IpFrom)
|
|
|
+ ipTo = net.ParseIP(grs.IpTo)
|
|
|
+
|
|
|
+ _, masklen := cidr(ipFrom, ipTo)
|
|
|
+
|
|
|
+ grs.NetMaskBits = masklen
|
|
|
+ grs.Cidr = fmt.Sprintf("%s/%d", grs.IpFrom, masklen)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
- grs.IpFromRaw = ip_from.To16()
|
|
|
- grs.IpToRaw = ip_to.To16()
|
|
|
|
|
|
+ grs.IpFromRaw = ipFrom.To16()
|
|
|
+ grs.IpToRaw = ipTo.To16()
|
|
|
+ }
|
|
|
+
|
|
|
+ if grs_org != nil {
|
|
|
+ org := Organization{}
|
|
|
+ for _, e := range grs_org.Attributes.Attributes {
|
|
|
+ switch e.Name {
|
|
|
+ case "organisation":
|
|
|
+ org.Organization = e.Value
|
|
|
+ case "org-name":
|
|
|
+ org.Name = e.Value
|
|
|
+ case "org-type":
|
|
|
+ org.Type = e.Value
|
|
|
+ case "address":
|
|
|
+ org.Address = append(org.Address, e.Value)
|
|
|
+ case "e-mail":
|
|
|
+ org.Email = e.Value
|
|
|
+ case "abuse-mailbox":
|
|
|
+ org.AbuseEmail = e.Value
|
|
|
+ case "phone":
|
|
|
+ org.Phone = e.Value
|
|
|
+ case "fax":
|
|
|
+ org.Fax = e.Value
|
|
|
+ case "source":
|
|
|
+ org.Source = e.Value
|
|
|
+ }
|
|
|
+ }
|
|
|
+ grs.Organization = org
|
|
|
}
|
|
|
|
|
|
return grs, nil
|
|
@@ -244,3 +500,27 @@ func networkRange(network *net.IPNet) (net.IP, net.IP) {
|
|
|
}
|
|
|
return firstIP, lastIP
|
|
|
}
|
|
|
+
|
|
|
+func cidr(first, last net.IP) (net.IPMask, int) {
|
|
|
+
|
|
|
+ // Is the address IPv4?
|
|
|
+ a := first.To4()
|
|
|
+ b := last.To4()
|
|
|
+ if a == nil || b == nil {
|
|
|
+ a = first
|
|
|
+ b = last
|
|
|
+ }
|
|
|
+
|
|
|
+ l := len(a)
|
|
|
+ m := make([]byte, l)
|
|
|
+
|
|
|
+ for i := 0; i < l; i++ {
|
|
|
+ msk := a[i] ^ b[i]
|
|
|
+ m[i] = ^msk
|
|
|
+ }
|
|
|
+
|
|
|
+ ipmask := net.IPMask(m)
|
|
|
+ masklen, _ := ipmask.Size()
|
|
|
+
|
|
|
+ return ipmask, masklen
|
|
|
+}
|