blob: 1bc0c529e5e00da80b79f82385dce81cc841e0e8 [file] [log] [blame]
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
}