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