Sergiusz Bazanski | 6eaaaf9 | 2019-08-02 01:25:31 +0200 | [diff] [blame] | 1 | package provider |
| 2 | |
| 3 | // IANA is not a full provider - we use it to determine the relevant IRR for a given aut-num. |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "fmt" |
| 8 | "strconv" |
| 9 | "strings" |
Sergiusz Bazanski | 2316ac0 | 2019-08-03 23:49:19 +0200 | [diff] [blame] | 10 | "sync" |
Sergiusz Bazanski | 6eaaaf9 | 2019-08-02 01:25:31 +0200 | [diff] [blame] | 11 | |
| 12 | "github.com/golang-collections/go-datastructures/augmentedtree" |
| 13 | "github.com/golang/glog" |
| 14 | |
| 15 | "code.hackerspace.pl/hscloud/bgpwtf/cccampix/irr/whois" |
| 16 | ) |
| 17 | |
| 18 | // Possible IRRs that AS blocks can be delegated to. |
| 19 | type IRR int |
| 20 | |
| 21 | const ( |
| 22 | IRR_UNKNOWN IRR = iota |
| 23 | IRR_ARIN |
| 24 | IRR_RIPE |
| 25 | ) |
| 26 | |
| 27 | func (i IRR) String() string { |
| 28 | return []string{ |
| 29 | "UNKNOWN", |
| 30 | "ARIN", |
| 31 | "RIPE", |
| 32 | }[i] |
| 33 | } |
| 34 | |
| 35 | // IANA access service. |
| 36 | type IANA struct { |
| 37 | // Interval tree of AS block delegation to IRRs. We use this to not |
| 38 | // keep hitting the IANA whois unnecessariy. |
| 39 | cache augmentedtree.Tree |
| 40 | // The tree library needs intervals to have a unique ID. We use a counter |
| 41 | // for this effect. |
| 42 | id uint64 |
Sergiusz Bazanski | 2316ac0 | 2019-08-03 23:49:19 +0200 | [diff] [blame] | 43 | |
| 44 | mu sync.Mutex |
Sergiusz Bazanski | 6eaaaf9 | 2019-08-02 01:25:31 +0200 | [diff] [blame] | 45 | } |
| 46 | |
Sergiusz Bazanski | 2316ac0 | 2019-08-03 23:49:19 +0200 | [diff] [blame] | 47 | func NewIANA(limit int) *IANA { |
Sergiusz Bazanski | 6eaaaf9 | 2019-08-02 01:25:31 +0200 | [diff] [blame] | 48 | return &IANA{ |
| 49 | cache: augmentedtree.New(1), |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | func (i *IANA) nextID() uint64 { |
| 54 | res := i.id |
| 55 | i.id += 1 |
| 56 | return res |
| 57 | } |
| 58 | |
| 59 | // delegation implements the Interval interface for the interval tree. |
| 60 | type delegation struct { |
| 61 | to IRR |
| 62 | id uint64 |
| 63 | low int64 |
| 64 | high int64 |
| 65 | } |
| 66 | |
| 67 | func (d *delegation) LowAtDimension(n uint64) int64 { |
| 68 | if n != 1 { |
| 69 | panic(fmt.Sprintf("dimension too high (%d)", n)) |
| 70 | } |
| 71 | return d.low |
| 72 | } |
| 73 | |
| 74 | func (d *delegation) HighAtDimension(n uint64) int64 { |
| 75 | if n != 1 { |
| 76 | panic(fmt.Sprintf("dimension too high (%d)", n)) |
| 77 | } |
| 78 | return d.high |
| 79 | } |
| 80 | |
| 81 | func (d *delegation) OverlapsAtDimension(i augmentedtree.Interval, n uint64) bool { |
| 82 | if n != 1 { |
| 83 | return false |
| 84 | } |
| 85 | |
| 86 | if i.LowAtDimension(1) <= d.HighAtDimension(1) && i.HighAtDimension(1) >= d.LowAtDimension(1) { |
| 87 | return true |
| 88 | } |
| 89 | |
| 90 | return false |
| 91 | } |
| 92 | |
| 93 | func (d *delegation) ID() uint64 { |
| 94 | return d.id |
| 95 | } |
| 96 | |
| 97 | // Who returns the responsible IRR (or UNKNOWN) for a given AS. |
| 98 | func (i *IANA) Who(ctx context.Context, asn uint64) (IRR, error) { |
Sergiusz Bazanski | 2316ac0 | 2019-08-03 23:49:19 +0200 | [diff] [blame] | 99 | i.mu.Lock() |
| 100 | defer i.mu.Unlock() |
| 101 | |
Sergiusz Bazanski | 6eaaaf9 | 2019-08-02 01:25:31 +0200 | [diff] [blame] | 102 | q := &delegation{ |
| 103 | id: i.nextID(), |
| 104 | low: int64(asn), |
| 105 | high: int64(asn), |
| 106 | } |
| 107 | |
| 108 | res := i.cache.Query(q) |
| 109 | if len(res) > 0 { |
| 110 | return res[0].(*delegation).to, nil |
| 111 | } |
| 112 | |
| 113 | // No cache entry, query whois. |
Sergiusz Bazanski | 2316ac0 | 2019-08-03 23:49:19 +0200 | [diff] [blame] | 114 | |
Sergiusz Bazanski | 6eaaaf9 | 2019-08-02 01:25:31 +0200 | [diff] [blame] | 115 | glog.Infof("Cache miss for AS%d", asn) |
| 116 | data, err := whois.Query(ctx, "whois.iana.org:43", fmt.Sprintf("AS%d", asn)) |
| 117 | if err != nil { |
| 118 | return IRR_UNKNOWN, err |
| 119 | } |
| 120 | |
| 121 | // We not only find the responsible IRR, but also the delegation bounds |
| 122 | // to feed the interval tree. |
| 123 | lines := strings.Split(data, "\n") |
| 124 | var lower int64 |
| 125 | var upper int64 |
| 126 | var irr IRR |
| 127 | for _, line := range lines { |
| 128 | parts := strings.Fields(line) |
| 129 | if len(parts) == 2 && parts[0] == "as-block:" { |
| 130 | bounds := strings.Split(parts[1], "-") |
| 131 | if len(bounds) != 2 { |
| 132 | return IRR_UNKNOWN, fmt.Errorf("unparseable as-block: %v", parts[1]) |
| 133 | } |
| 134 | lower, err = strconv.ParseInt(bounds[0], 10, 64) |
| 135 | if err != nil { |
| 136 | return IRR_UNKNOWN, fmt.Errorf("unparseable as-block: %v", parts[1]) |
| 137 | } |
| 138 | upper, err = strconv.ParseInt(bounds[1], 10, 64) |
| 139 | if err != nil { |
| 140 | return IRR_UNKNOWN, fmt.Errorf("unparseable as-block: %v", parts[1]) |
| 141 | } |
| 142 | } |
| 143 | if len(parts) > 2 && parts[0] == "organisation:" { |
| 144 | switch { |
| 145 | case strings.Contains(line, "RIPE NCC"): |
| 146 | irr = IRR_RIPE |
| 147 | case strings.Contains(line, "ARIN"): |
| 148 | irr = IRR_ARIN |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | if lower == 0 || upper == 0 { |
| 154 | return IRR_UNKNOWN, nil |
| 155 | } |
| 156 | |
| 157 | glog.Infof("Saving %d-%d AS blocks delegation (%s) to cache", lower, upper, irr.String()) |
| 158 | |
| 159 | // Insert into tree. |
| 160 | q = &delegation{ |
| 161 | id: i.nextID(), |
| 162 | to: irr, |
| 163 | low: lower, |
| 164 | high: upper + 1, |
| 165 | } |
| 166 | |
| 167 | i.cache.Add(q) |
| 168 | |
| 169 | return irr, nil |
| 170 | } |