| package provider |
| |
| // IANA is not a full provider - we use it to determine the relevant IRR for a given aut-num. |
| |
| import ( |
| "context" |
| "fmt" |
| "strconv" |
| "strings" |
| "sync" |
| |
| "github.com/golang-collections/go-datastructures/augmentedtree" |
| "github.com/golang/glog" |
| |
| "code.hackerspace.pl/hscloud/bgpwtf/cccampix/irr/whois" |
| ) |
| |
| // Possible IRRs that AS blocks can be delegated to. |
| type IRR int |
| |
| const ( |
| IRR_UNKNOWN IRR = iota |
| IRR_ARIN |
| IRR_RIPE |
| ) |
| |
| func (i IRR) String() string { |
| return []string{ |
| "UNKNOWN", |
| "ARIN", |
| "RIPE", |
| }[i] |
| } |
| |
| // IANA access service. |
| type IANA struct { |
| // Interval tree of AS block delegation to IRRs. We use this to not |
| // keep hitting the IANA whois unnecessariy. |
| cache augmentedtree.Tree |
| // The tree library needs intervals to have a unique ID. We use a counter |
| // for this effect. |
| id uint64 |
| |
| mu sync.Mutex |
| } |
| |
| func NewIANA(limit int) *IANA { |
| return &IANA{ |
| cache: augmentedtree.New(1), |
| } |
| } |
| |
| func (i *IANA) nextID() uint64 { |
| res := i.id |
| i.id += 1 |
| return res |
| } |
| |
| // delegation implements the Interval interface for the interval tree. |
| type delegation struct { |
| to IRR |
| id uint64 |
| low int64 |
| high int64 |
| } |
| |
| func (d *delegation) LowAtDimension(n uint64) int64 { |
| if n != 1 { |
| panic(fmt.Sprintf("dimension too high (%d)", n)) |
| } |
| return d.low |
| } |
| |
| func (d *delegation) HighAtDimension(n uint64) int64 { |
| if n != 1 { |
| panic(fmt.Sprintf("dimension too high (%d)", n)) |
| } |
| return d.high |
| } |
| |
| func (d *delegation) OverlapsAtDimension(i augmentedtree.Interval, n uint64) bool { |
| if n != 1 { |
| return false |
| } |
| |
| if i.LowAtDimension(1) <= d.HighAtDimension(1) && i.HighAtDimension(1) >= d.LowAtDimension(1) { |
| return true |
| } |
| |
| return false |
| } |
| |
| func (d *delegation) ID() uint64 { |
| return d.id |
| } |
| |
| // Who returns the responsible IRR (or UNKNOWN) for a given AS. |
| func (i *IANA) Who(ctx context.Context, asn uint64) (IRR, error) { |
| i.mu.Lock() |
| defer i.mu.Unlock() |
| |
| q := &delegation{ |
| id: i.nextID(), |
| low: int64(asn), |
| high: int64(asn), |
| } |
| |
| res := i.cache.Query(q) |
| if len(res) > 0 { |
| return res[0].(*delegation).to, nil |
| } |
| |
| // No cache entry, query whois. |
| |
| glog.Infof("Cache miss for AS%d", asn) |
| data, err := whois.Query(ctx, "whois.iana.org:43", fmt.Sprintf("AS%d", asn)) |
| if err != nil { |
| return IRR_UNKNOWN, err |
| } |
| |
| // We not only find the responsible IRR, but also the delegation bounds |
| // to feed the interval tree. |
| lines := strings.Split(data, "\n") |
| var lower int64 |
| var upper int64 |
| var irr IRR |
| for _, line := range lines { |
| parts := strings.Fields(line) |
| if len(parts) == 2 && parts[0] == "as-block:" { |
| bounds := strings.Split(parts[1], "-") |
| if len(bounds) != 2 { |
| return IRR_UNKNOWN, fmt.Errorf("unparseable as-block: %v", parts[1]) |
| } |
| lower, err = strconv.ParseInt(bounds[0], 10, 64) |
| if err != nil { |
| return IRR_UNKNOWN, fmt.Errorf("unparseable as-block: %v", parts[1]) |
| } |
| upper, err = strconv.ParseInt(bounds[1], 10, 64) |
| if err != nil { |
| return IRR_UNKNOWN, fmt.Errorf("unparseable as-block: %v", parts[1]) |
| } |
| } |
| if len(parts) > 2 && parts[0] == "organisation:" { |
| switch { |
| case strings.Contains(line, "RIPE NCC"): |
| irr = IRR_RIPE |
| case strings.Contains(line, "ARIN"): |
| irr = IRR_ARIN |
| } |
| } |
| } |
| |
| if lower == 0 || upper == 0 { |
| return IRR_UNKNOWN, nil |
| } |
| |
| glog.Infof("Saving %d-%d AS blocks delegation (%s) to cache", lower, upper, irr.String()) |
| |
| // Insert into tree. |
| q = &delegation{ |
| id: i.nextID(), |
| to: irr, |
| low: lower, |
| high: upper + 1, |
| } |
| |
| i.cache.Add(q) |
| |
| return irr, nil |
| } |