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