Sergiusz Bazanski | 1fad2e5 | 2019-08-01 20:16:27 +0200 | [diff] [blame] | 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "fmt" |
| 7 | "io/ioutil" |
| 8 | "net" |
| 9 | "net/http" |
| 10 | "strconv" |
| 11 | "strings" |
| 12 | "time" |
| 13 | |
| 14 | "code.hackerspace.pl/hscloud/bgpwtf/cccampix/verifier/model" |
| 15 | "github.com/golang/glog" |
| 16 | ) |
| 17 | |
| 18 | type rpki struct { |
| 19 | octorpki string |
| 20 | } |
| 21 | |
| 22 | func newRPKI(octorpki string) (processor, error) { |
| 23 | return &rpki{ |
| 24 | octorpki: octorpki, |
| 25 | }, nil |
| 26 | } |
| 27 | |
| 28 | func (p *rpki) Name() string { |
| 29 | return "RPKI" |
| 30 | } |
| 31 | |
Serge Bazanski | ec71cb5 | 2019-08-22 18:13:13 +0200 | [diff] [blame] | 32 | func (p *rpki) NextRun(now time.Time, lastFailed bool) time.Time { |
Sergiusz Bazanski | 1fad2e5 | 2019-08-01 20:16:27 +0200 | [diff] [blame] | 33 | return now.Add(1 * time.Minute) |
| 34 | } |
| 35 | |
| 36 | type octorpkiRes struct { |
| 37 | Metadata struct { |
| 38 | Counts int64 `json:"counts"` |
| 39 | Generated int64 `json:"counts"` |
| 40 | Valid int64 `json:"counts"` |
| 41 | } `json:"metadata"` |
| 42 | |
| 43 | ROAs []octorpkiROA `json:"roas"` |
| 44 | } |
| 45 | |
| 46 | type octorpkiROA struct { |
| 47 | Prefix string `json:"prefix"` |
| 48 | MaxLength int64 `json:"maxLength"` |
| 49 | ASN string `json:"asn"` |
| 50 | TA string `json:"ta"` |
| 51 | } |
| 52 | |
| 53 | func (p *rpki) RunAll(ctx context.Context, m model.Model) error { |
| 54 | peers, err := m.GetCheckablePeers(ctx) |
| 55 | if err != nil { |
| 56 | return err |
| 57 | } |
| 58 | |
| 59 | wantASNs := make(map[string]bool) |
| 60 | for _, peer := range peers { |
| 61 | wantASNs[fmt.Sprintf("AS%d", peer.ASN)] = true |
| 62 | } |
| 63 | |
| 64 | // Get RPKI data dump from OctoRPKI. |
| 65 | url := fmt.Sprintf("http://%s/output.json", p.octorpki) |
| 66 | req, err := http.NewRequest("GET", url, nil) |
| 67 | if err != nil { |
| 68 | return fmt.Errorf("NewRequest(GET %q): %v", url, err) |
| 69 | } |
| 70 | req = req.WithContext(ctx) |
| 71 | client := http.Client{} |
| 72 | resp, err := client.Do(req) |
| 73 | if err != nil { |
| 74 | return fmt.Errorf("GET %q: %v", url, err) |
| 75 | } |
| 76 | defer resp.Body.Close() |
| 77 | |
| 78 | data, err := ioutil.ReadAll(resp.Body) |
| 79 | if err != nil { |
| 80 | return fmt.Errorf("GET %q: %v", url, err) |
| 81 | } |
| 82 | |
| 83 | if strings.HasPrefix(string(data), "File not ready yet") { |
| 84 | return fmt.Errorf("OctoRPKI not yet ready") |
| 85 | } |
| 86 | |
| 87 | var res octorpkiRes |
| 88 | if err := json.Unmarshal(data, &res); err != nil { |
| 89 | return fmt.Errorf("Could not decode OctoRPKI output: %v", err) |
| 90 | } |
| 91 | |
| 92 | // Make list of prefixes we should honor. |
| 93 | prefixes := make(map[int64][]*model.AllowedPrefix) |
| 94 | for _, roa := range res.ROAs { |
| 95 | if !wantASNs[strings.ToUpper(roa.ASN)] { |
| 96 | continue |
| 97 | } |
| 98 | |
| 99 | asn, err := strconv.ParseInt(roa.ASN[2:], 10, 64) |
| 100 | if err != nil { |
| 101 | glog.Errorf("Invalid ASN: %s %q", roa.ASN, roa.ASN) |
| 102 | continue |
| 103 | } |
| 104 | |
| 105 | if _, ok := prefixes[asn]; !ok { |
| 106 | prefixes[asn] = []*model.AllowedPrefix{} |
| 107 | } |
| 108 | |
| 109 | _, prefix, err := net.ParseCIDR(roa.Prefix) |
| 110 | if err != nil { |
| 111 | glog.Errorf("Invalid prefix: %s %q", roa.ASN, roa.Prefix) |
| 112 | continue |
| 113 | } |
| 114 | |
| 115 | prefixes[asn] = append(prefixes[asn], &model.AllowedPrefix{ |
| 116 | Prefix: *prefix, |
| 117 | MaxLength: roa.MaxLength, |
| 118 | TA: roa.TA, |
| 119 | }) |
| 120 | } |
| 121 | |
| 122 | for asn, p := range prefixes { |
| 123 | err := m.UpdateAllowedPrefixes(ctx, asn, p) |
| 124 | if err != nil { |
| 125 | return err |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | return nil |
| 130 | } |