blob: 1bc0c529e5e00da80b79f82385dce81cc841e0e8 [file] [log] [blame]
Sergiusz Bazanski6eaaaf92019-08-02 01:25:31 +02001package provider
2
3// IANA is not a full provider - we use it to determine the relevant IRR for a given aut-num.
4
5import (
6 "context"
7 "fmt"
8 "strconv"
9 "strings"
Sergiusz Bazanski2316ac02019-08-03 23:49:19 +020010 "sync"
Sergiusz Bazanski6eaaaf92019-08-02 01:25:31 +020011
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.
19type IRR int
20
21const (
22 IRR_UNKNOWN IRR = iota
23 IRR_ARIN
24 IRR_RIPE
25)
26
27func (i IRR) String() string {
28 return []string{
29 "UNKNOWN",
30 "ARIN",
31 "RIPE",
32 }[i]
33}
34
35// IANA access service.
36type 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 Bazanski2316ac02019-08-03 23:49:19 +020043
44 mu sync.Mutex
Sergiusz Bazanski6eaaaf92019-08-02 01:25:31 +020045}
46
Sergiusz Bazanski2316ac02019-08-03 23:49:19 +020047func NewIANA(limit int) *IANA {
Sergiusz Bazanski6eaaaf92019-08-02 01:25:31 +020048 return &IANA{
49 cache: augmentedtree.New(1),
50 }
51}
52
53func (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.
60type delegation struct {
61 to IRR
62 id uint64
63 low int64
64 high int64
65}
66
67func (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
74func (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
81func (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
93func (d *delegation) ID() uint64 {
94 return d.id
95}
96
97// Who returns the responsible IRR (or UNKNOWN) for a given AS.
98func (i *IANA) Who(ctx context.Context, asn uint64) (IRR, error) {
Sergiusz Bazanski2316ac02019-08-03 23:49:19 +020099 i.mu.Lock()
100 defer i.mu.Unlock()
101
Sergiusz Bazanski6eaaaf92019-08-02 01:25:31 +0200102 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 Bazanski2316ac02019-08-03 23:49:19 +0200114
Sergiusz Bazanski6eaaaf92019-08-02 01:25:31 +0200115 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}