blob: 49c4eb10dc9e25b9379b5b2f2e759ffca834a353 [file] [log] [blame]
Sergiusz Bazanski1fad2e52019-08-01 20:16:27 +02001package main
2
3import (
4 "context"
5 "encoding/hex"
6 "fmt"
7 "strings"
8 "sync"
9 "time"
10
11 "code.hackerspace.pl/hscloud/go/pki"
12 "github.com/golang/glog"
13 "google.golang.org/grpc"
14 "google.golang.org/grpc/codes"
15 "google.golang.org/grpc/status"
16
17 pb "code.hackerspace.pl/hscloud/bgpwtf/cccampix/proto"
18 "code.hackerspace.pl/hscloud/bgpwtf/cccampix/verifier/model"
19)
20
21const (
22 RS_ASN = "AS208521"
23 RS_ASSET = "AS-CCCAMP19-IX"
24)
25
26type irr struct {
27 irrc pb.IRRClient
28}
29
30func newIRR(addr string) (processor, error) {
31 conn, err := grpc.Dial(addr, pki.WithClientHSPKI())
32 if err != nil {
33 return nil, fmt.Errorf("could not connect to irr service: %v", err)
34 }
35
36 return &irr{
37 irrc: pb.NewIRRClient(conn),
38 }, nil
39}
40
41func (i *irr) Name() string {
42 return "IRR"
43}
44
45func (i *irr) NextRun(now time.Time) time.Time {
46 return now.Add(5 * time.Minute)
47}
48
49func (i *irr) RunAll(ctx context.Context, m model.Model) error {
50 peers, err := m.GetCheckablePeers(ctx)
51 if err != nil {
52 return fmt.Errorf("could not retrieve peers: %v", err)
53 }
54
55 results := make(chan *model.PeerCheckResult)
56 pcr := []*model.PeerCheckResult{}
57 pcrDone := make(chan struct{})
58
59 pgpKeys := make(chan *model.PeerPGPKey)
60 pk := []*model.PeerPGPKey{}
61 pkDone := make(chan struct{})
62
63 go func() {
64 for res := range results {
65 pcr = append(pcr, res)
66 }
67 pcrDone <- struct{}{}
68 }()
69 go func() {
70 for res := range pgpKeys {
71 pk = append(pk, res)
72 }
73 pkDone <- struct{}{}
74 }()
75
76 fail := func(p *model.Peer, hard bool, f string, args ...interface{}) {
77 status := model.PeerCheckStatus_SoftFailed
78 if hard {
79 status = model.PeerCheckStatus_Failed
80 }
81 results <- &model.PeerCheckResult{
82 PeerASN: p.ASN,
83 CheckName: "irr",
84 Time: time.Now(),
85 Status: status,
86 Message: fmt.Sprintf(f, args...),
87 }
88
89 }
90
91 var wg sync.WaitGroup
92 wg.Add(len(peers))
93
94 sem := make(chan struct{}, 10)
95
96 for _, peer := range peers {
97 go func(p *model.Peer) {
98 sem <- struct{}{}
99 defer func() {
100 <-sem
101 wg.Done()
102 }()
103
104 req := &pb.IRRQueryRequest{
105 As: fmt.Sprintf("%d", p.ASN),
106 }
107 res, err := i.irrc.Query(ctx, req)
108 if err != nil {
109 s, ok := status.FromError(err)
110 switch {
111 case ok && s.Code() == codes.NotFound:
112 fail(p, true, "ASN %d not found in IRR", p.ASN)
113 case ok && s.Code() == codes.Unimplemented:
114 fail(p, true, "ASN %d belongs to an unknown IRR/RIR", p.ASN)
115 case ok && s.Code() == codes.Unavailable:
116 fail(p, false, "could not contact IRR")
117 default:
118 glog.Errorf("IRR.Query(%d): %v", p.ASN, err)
119 fail(p, false, "unhandled IRR error")
120 }
121 return
122 }
123
124 importOkay := false
125 exportOkay := false
126 pgpKey := ""
127
128 for _, attr := range res.Attributes {
129 switch value := attr.Value.(type) {
130 case *pb.IRRAttribute_Remarks:
131 if ok, key := i.checkRemarks(value.Remarks); ok {
132 pgpKey = key
133 }
134 case *pb.IRRAttribute_Import:
135 if i.checkImport(value.Import) {
136 importOkay = true
137 }
138 case *pb.IRRAttribute_Export:
139 if i.checkExport(value.Export, p.ASN) {
140 exportOkay = true
141 }
142 }
143 }
144
145 switch {
146 case !importOkay:
147 fail(p, true, "no `import: from %s accept %s` entry", RS_ASN, RS_ASSET)
148 return
149 case !exportOkay:
150 fail(p, true, "no `export: to %s announce AS%d` entry", RS_ASN, p.ASN)
151 return
152 case pgpKey == "":
153 fail(p, true, "no `remarks: CCCAMP19-IX PGP: <...>` entry")
154 return
155 }
156
157 pgpKeys <- &model.PeerPGPKey{
158 PeerASN: p.ASN,
159 Fingerprint: pgpKey,
160 }
161
162 results <- &model.PeerCheckResult{
163 PeerASN: p.ASN,
164 CheckName: "irr",
165 Time: time.Now(),
166 Status: model.PeerCheckStatus_Okay,
167 Message: "",
168 }
169 }(peer)
170 }
171
172 wg.Wait()
173 close(results)
174 close(pgpKeys)
175 <-pcrDone
176 <-pkDone
177
178 err = m.SubmitPeerCheckResults(ctx, pcr)
179 if err != nil {
180 return err
181 }
182
183 for _, k := range pk {
184 err = m.UpdatePGPKey(ctx, k)
185 if err != nil {
186 return err
187 }
188 }
189
190 return nil
191}
192
193func (i *irr) checkRemarks(remarks string) (bool, string) {
194 label := "cccamp19-ix pgp:"
195 remarks = strings.TrimSpace(strings.ToLower(remarks))
196 if !strings.HasPrefix(remarks, label) {
197 return false, ""
198 }
199
200 data := strings.TrimSpace(strings.TrimPrefix(remarks, label))
201 data = strings.ReplaceAll(data, " ", "")
202 data = strings.ReplaceAll(data, "\t", "")
203
204 if len(data) != 40 {
205 return false, ""
206 }
207
208 if _, err := hex.DecodeString(data); err != nil {
209 return false, ""
210 }
211
212 return true, data
213}
214
215func (i *irr) checkImport(imp *pb.IRRAttribute_ImportExport) bool {
216 if imp.ProtocolFrom != "" && strings.ToLower(imp.ProtocolFrom) != "bgp" {
217 return false
218 }
219 if strings.ToUpper(imp.Filter) != RS_ASSET {
220 return false
221 }
222
223 for _, expression := range imp.Expressions {
224 if strings.ToUpper(expression.Peering) == RS_ASN {
225 return true
226 }
227 }
228
229 return false
230}
231
232func (i *irr) checkExport(exp *pb.IRRAttribute_ImportExport, asn int64) bool {
233 if exp.ProtocolInto != "" && strings.ToLower(exp.ProtocolInto) != "bgp" {
234 return false
235 }
236 if strings.ToUpper(exp.Filter) != fmt.Sprintf("AS%d", asn) {
237 return false
238 }
239
240 for _, expression := range exp.Expressions {
241 if strings.ToUpper(expression.Peering) == RS_ASN {
242 return true
243 }
244 }
245
246 return false
247}