From 513bda0e5fede5c90b6bfa37d5250f0a7a869954 Mon Sep 17 00:00:00 2001 From: Ben Cartwright-Cox Date: Tue, 21 Feb 2023 21:55:50 +0000 Subject: [PATCH] Implement BGPsec support This imports and exports BGPsec router key data, and exports router key data out over RTR to supporting clients (any version higher than 1) Since it's obvious that at some point there will be clients that will have issues seeing a RouterKey PDU for the first time ever, I've included a -disable.bgpsec flag to prevent them from being sent. That way if someone is caught off guard during an upgrade, they can disable it and keep upgrading. Tag: https://github.com/bgp/stayrtr/issues/57 --- cmd/stayrtr/stayrtr.go | 68 ++++++++++++++++++++++++------ cmd/stayrtr/stayrtr_test.go | 2 +- lib/server.go | 83 ++++++++++++++++++++++++++++++++++--- lib/server_test.go | 47 ++++++++++++++++++++- 4 files changed, 180 insertions(+), 20 deletions(-) diff --git a/cmd/stayrtr/stayrtr.go b/cmd/stayrtr/stayrtr.go index d36ffb5..44c68d0 100644 --- a/cmd/stayrtr/stayrtr.go +++ b/cmd/stayrtr/stayrtr.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "crypto/tls" "encoding/base64" + "encoding/hex" "encoding/json" "errors" "flag" @@ -99,6 +100,13 @@ var ( }, []string{"ip_version", "filtered", "path"}, ) + NumberOfBSKs = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "rpki_bgpsec", + Help: "Number of BGPsec keys.", + }, + []string{}, + ) LastRefresh = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "rpki_refresh", @@ -153,6 +161,7 @@ var ( func initMetrics() { prometheus.MustRegister(NumberOfVRPs) + prometheus.MustRegister(NumberOfBSKs) prometheus.MustRegister(LastChange) prometheus.MustRegister(LastRefresh) prometheus.MustRegister(RefreshStatusCode) @@ -197,7 +206,7 @@ func isValidPrefixLength(prefix *net.IPNet, maxLength uint8) bool { // 2 - The ASN is a valid ASN // 3 - The MaxLength is valid // Will return a deduped slice, as well as total VRPs, IPv4 VRPs, and IPv6 VRPs -func processData(vrplistjson []prefixfile.VRPJson) ([]rtr.VRP, int, int, int) { +func processData(vrplistjson []prefixfile.VRPJson, bsklistjson []prefixfile.BgpSecKeyJson) ([]rtr.VRP, []rtr.BgpsecKey, int, int, int) { filterDuplicates := make(map[string]bool) // It may be tempting to change this to a simple time.Since() but that will @@ -207,6 +216,7 @@ func processData(vrplistjson []prefixfile.VRPJson) ([]rtr.VRP, int, int, int) { NowUnix := time.Now().UTC().Unix() var vrplist []rtr.VRP + var bsklist = make([]rtr.BgpsecKey, 0) var countv4 int var countv6 int @@ -254,7 +264,29 @@ func processData(vrplistjson []prefixfile.VRPJson) ([]rtr.VRP, int, int, int) { } vrplist = append(vrplist, vrp) } - return vrplist, countv4 + countv6, countv4, countv6 + + for _, v := range bsklistjson { + if v.Expires != nil { + // Prevent stale VRPs from being considered + // https://github.com/bgp/stayrtr/issues/15 + if int(NowUnix) > int(*v.Expires) { + continue + } + } + + SKIBytes, err := hex.DecodeString(v.Ski) + if err != nil { + continue + } + + bsklist = append(bsklist, rtr.BgpsecKey{ + ASN: v.Asn, + Pubkey: v.Pubkey, + Ski: SKIBytes, + }) + } + + return vrplist, bsklist, countv4 + countv6, countv4, countv6 } type IdenticalFile struct { @@ -295,10 +327,10 @@ func (s *state) updateFromNewState() error { vrpsjson = append(kept, asserted...) } - vrps, count, countv4, countv6 := processData(vrpsjson) + vrps, bsks, count, countv4, countv6 := processData(vrpsjson, s.lastdata.BgpSecKeys) log.Infof("New update (%v uniques, %v total prefixes).", len(vrps), count) - return s.applyUpdateFromNewState(vrps, sessid, vrpsjson, countv4, countv6) + return s.applyUpdateFromNewState(vrps, bsks, sessid, vrpsjson, s.lastdata.BgpSecKeys, countv4, countv6) } // Update the state based on the currently loaded files @@ -329,16 +361,26 @@ func (s *state) reloadFromCurrentState() error { vrpsjson = append(kept, asserted...) } - vrps, count, countv4, countv6 := processData(vrpsjson) + vrps, bsks, count, countv4, countv6 := processData(vrpsjson, s.lastdata.BgpSecKeys) if s.server.CountVRPs() != count { log.Infof("New update to old state (%v uniques, %v total prefixes). (old %v - new %v)", len(vrps), count, s.server.CountVRPs(), count) - return s.applyUpdateFromNewState(vrps, sessid, vrpsjson, countv4, countv6) + return s.applyUpdateFromNewState(vrps, bsks, sessid, vrpsjson, s.lastdata.BgpSecKeys, countv4, countv6) } return nil } -func (s *state) applyUpdateFromNewState(vrps []rtr.VRP, sessid uint16, vrpsjson []prefixfile.VRPJson, countv4 int, countv6 int) error { - s.server.AddData(vrps) +func (s *state) applyUpdateFromNewState(vrps []rtr.VRP, bsks []rtr.BgpsecKey, sessid uint16, + vrpsjson []prefixfile.VRPJson, bsksjson []prefixfile.BgpSecKeyJson, + countv4 int, countv6 int) error { + + SDs := make([]rtr.SendableData, 0) + for _, v := range vrps { + SDs = append(SDs, v.Copy()) + } + for _, v := range bsks { + SDs = append(SDs, v.Copy()) + } + s.server.AddData(SDs) serial, _ := s.server.GetCurrentSerial(sessid) log.Infof("Updated added, new serial %v", serial) @@ -353,7 +395,8 @@ func (s *state) applyUpdateFromNewState(vrps []rtr.VRP, sessid uint16, vrpsjson Counts: len(vrpsjson), Buildtime: s.lastdata.Metadata.Buildtime, }, - Data: vrpsjson, + Data: vrpsjson, + BgpSecKeys: bsksjson, } s.lockJson.Unlock() @@ -368,7 +411,7 @@ func (s *state) applyUpdateFromNewState(vrps []rtr.VRP, sessid uint16, vrpsjson countv6_dup++ } } - s.metricsEvent.UpdateMetrics(countv4, countv6, countv4_dup, countv6_dup, s.lastchange, s.lastts, *CacheBin) + s.metricsEvent.UpdateMetrics(countv4, countv6, countv4_dup, countv6_dup, s.lastchange, s.lastts, *CacheBin, len(bsks)) } return nil @@ -513,7 +556,7 @@ func (s *state) routineUpdate(file string, interval int, slurmFile string) { // to avoid routing on stale data buildTime := s.exported.Metadata.GetBuildTime() if !buildTime.IsZero() && time.Since(buildTime) > time.Hour*24 { - s.server.AddData([]rtr.VRP{}) // empty the store of VRP by giving it a empty VRP array, triggering a emptying + s.server.AddData([]rtr.SendableData{}) // empty the store of sendable stuff, triggering a emptying of the RTR server } } log.Errorf("Error updating from new state: %v", err) @@ -578,11 +621,12 @@ func (m *metricsEvent) HandlePDU(c *rtr.Client, pdu rtr.PDU) { "_", -1))).Inc() } -func (m *metricsEvent) UpdateMetrics(numIPv4 int, numIPv6 int, numIPv4filtered int, numIPv6filtered int, changed time.Time, refreshed time.Time, file string) { +func (m *metricsEvent) UpdateMetrics(numIPv4 int, numIPv6 int, numIPv4filtered int, numIPv6filtered int, changed time.Time, refreshed time.Time, file string, bskCount int) { NumberOfVRPs.WithLabelValues("ipv4", "filtered", file).Set(float64(numIPv4filtered)) NumberOfVRPs.WithLabelValues("ipv4", "unfiltered", file).Set(float64(numIPv4)) NumberOfVRPs.WithLabelValues("ipv6", "filtered", file).Set(float64(numIPv6filtered)) NumberOfVRPs.WithLabelValues("ipv6", "unfiltered", file).Set(float64(numIPv6)) + NumberOfBSKs.WithLabelValues().Set(float64(bskCount)) LastChange.WithLabelValues(file).Set(float64(changed.UnixNano() / 1e9)) } diff --git a/cmd/stayrtr/stayrtr_test.go b/cmd/stayrtr/stayrtr_test.go index 1b34cf8..dfb2fb1 100644 --- a/cmd/stayrtr/stayrtr_test.go +++ b/cmd/stayrtr/stayrtr_test.go @@ -100,7 +100,7 @@ func TestProcessData(t *testing.T) { Expires: &ExpiredTime, }, ) - got, count, v4count, v6count := processData(stuff) + got, _, count, v4count, v6count := processData(stuff, nil) want := []rtr.VRP{ { Prefix: mustParseIPNet("192.168.0.0/24"), diff --git a/lib/server.go b/lib/server.go index 9f0245e..c899202 100644 --- a/lib/server.go +++ b/lib/server.go @@ -3,6 +3,7 @@ package rtrlib import ( "bytes" "crypto/tls" + "flag" "fmt" "io" "math/rand" @@ -79,7 +80,7 @@ func (e *DefaultRTREventHandler) RequestCache(c *Client) { e.Log.Debugf("%v < Internal error requesting cache (does not exists)", c) } } else { - c.SendVRPs(sessionId, serial, vrps) + c.SendSDs(sessionId, serial, vrps) if e.Log != nil { e.Log.Debugf("%v < Sent VRPs (current serial %d, session: %d)", c, serial, sessionId) } @@ -114,7 +115,7 @@ func (e *DefaultRTREventHandler) RequestNewVersion(c *Client, sessionId uint16, e.Log.Debugf("%v < Sent cache reset", c) } } else { - c.SendVRPs(sessionId, serial, vrps) + c.SendSDs(sessionId, serial, vrps) if e.Log != nil { e.Log.Debugf("%v < Sent VRPs (current serial %d, session from client: %d)", c, serial, sessionId) } @@ -364,7 +365,7 @@ func (s *Server) CountVRPs() int { return len(s.sdCurrent) } -func (s *Server) AddData(vrps []VRP) { +func (s *Server) AddData(vrps []SendableData) { s.sdlock.RLock() // a slight hack for now, until we have BGPsec/ASPA support @@ -533,6 +534,8 @@ func (s *Server) Start(bind string) error { return s.loopTCP(tcplist, "tcp", s.acceptClientTCP) } +var DisableBGPSec = flag.Bool("disable.bgpsec", false, "Disable sending out BGPSEC Router Keys") + func (s *Server) acceptClientTCP(tcpconn net.Conn) error { client := ClientFromConn(tcpconn, s, s) client.log = s.log @@ -540,6 +543,9 @@ func (s *Server) acceptClientTCP(tcpconn net.Conn) error { client.SetVersion(s.baseVersion) } client.SetIntervals(s.pduRefreshInterval, s.pduRetryInterval, s.pduExpireInterval) + if *DisableBGPSec { + client.DisableBGPsec() + } go client.Start() return nil } @@ -725,6 +731,8 @@ type Client struct { retryInterval uint32 expireInterval uint32 + dontSendBGPsecKeys bool + log Logger } @@ -744,6 +752,10 @@ func (c *Client) GetVersion() uint8 { return c.version } +func (c *Client) DisableBGPsec() { + c.dontSendBGPsecKeys = true +} + func (c *Client) SetIntervals(refreshInterval uint32, retryInterval uint32, expireInterval uint32) { c.refreshInterval = refreshInterval c.retryInterval = retryInterval @@ -905,14 +917,62 @@ func (r1 *VRP) Copy() SendableData { } func (r1 *VRP) SetFlag(f uint8) { - r1.Flags = f // TODO convert everything to *VRP + r1.Flags = f } func (r1 *VRP) GetFlag() uint8 { return r1.Flags } -func (c *Client) SendVRPs(sessionId uint16, serialNumber uint32, data []SendableData) { +type BgpsecKey struct { + ASN uint32 + Pubkey []byte + Ski []byte + Flags uint8 +} + +func (bsk *BgpsecKey) Type() string { + return "BGPsecKey" +} + +func (bsk *BgpsecKey) String() string { + return fmt.Sprintf("BGPsec AS%v -> %x, Flags: %v", bsk.ASN, bsk.Ski, bsk.Flags) +} + +func (bsk *BgpsecKey) HashKey() string { + return fmt.Sprintf("%v-%x-%x", bsk.ASN, bsk.Ski, bsk.Pubkey) +} + +func (r1 *BgpsecKey) Equals(r2 SendableData) bool { + if r1.Type() != r2.Type() { + return false + } + + r2True := r2.(*BgpsecKey) + return r1.ASN == r2True.ASN && bytes.Equal(r1.Pubkey, r2True.Pubkey) && bytes.Equal(r1.Ski, r2True.Ski) +} + +func (bsk *BgpsecKey) Copy() SendableData { + cop := BgpsecKey{ + ASN: bsk.ASN, + Pubkey: make([]byte, len(bsk.Pubkey)), + Ski: make([]byte, len(bsk.Ski)), + Flags: bsk.Flags, + } + copy(cop.Pubkey, bsk.Pubkey) + copy(cop.Ski, bsk.Ski) + return &cop +} + +func (bsk *BgpsecKey) SetFlag(f uint8) { + bsk.Flags = f +} + +func (bsk *BgpsecKey) GetFlag() uint8 { + return bsk.Flags +} + +func (c *Client) SendSDs(sessionId uint16, serialNumber uint32, data []SendableData) { pduBegin := &PDUCacheResponse{ SessionId: sessionId, } @@ -989,6 +1049,19 @@ func (c *Client) SendData(sd SendableData) { } c.SendPDU(pdu) } + case *BgpsecKey: + if c.version == 0 || c.dontSendBGPsecKeys { + return + } + + pdu := &PDURouterKey{ + Version: c.version, // The RouterKey PDU is unchanged from rfc8210 to draft-ietf-sidrops-8210bis-10 + Flags: t.Flags, + SubjectKeyIdentifier: t.Ski, + ASN: t.ASN, + SubjectPublicKeyInfo: t.Pubkey, + } + c.SendPDU(pdu) } } diff --git a/lib/server_test.go b/lib/server_test.go index 31bbb83..ca2f4b8 100644 --- a/lib/server_test.go +++ b/lib/server_test.go @@ -197,10 +197,10 @@ func TestApplyDiff(t *testing.T) { } diffSD, prevVrpsAsSD := make([]SendableData, 0), make([]SendableData, 0) for _, v := range diff { - diffSD = append(diffSD, &v) + diffSD = append(diffSD, v.Copy()) } for _, v := range prevVrps { - prevVrpsAsSD = append(prevVrpsAsSD, &v) + prevVrpsAsSD = append(prevVrpsAsSD, v.Copy()) } vrps := ApplyDiff(diffSD, prevVrpsAsSD) @@ -219,3 +219,46 @@ func TestApplyDiff(t *testing.T) { assert.Equal(t, vrps[5].(*VRP).ASN, uint32(65007)) assert.Equal(t, vrps[5].(*VRP).GetFlag(), uint8(FLAG_ADDED)) } + +func TestComputeDiffBGPSEC(t *testing.T) { + newVrps := []BgpsecKey{ + { + ASN: 65003, + Pubkey: []byte("hurr"), + Ski: []byte("durr"), + }, + { + Pubkey: []byte("abc"), + Ski: []byte("dce"), + ASN: 65002, + }, + } + prevVrps := []BgpsecKey{ + { + Pubkey: []byte("murr"), + Ski: []byte("durr"), + ASN: 65001, + }, + { + Pubkey: []byte("abc"), + Ski: []byte("dce"), + ASN: 65002, + }, + } + + newVrpsSD, prevVrpsAsSD := make([]SendableData, 0), make([]SendableData, 0) + for _, v := range newVrps { + newVrpsSD = append(newVrpsSD, v.Copy()) + } + for _, v := range prevVrps { + prevVrpsAsSD = append(prevVrpsAsSD, v.Copy()) + } + + added, removed, unchanged := ComputeDiff(newVrpsSD, prevVrpsAsSD) + assert.Len(t, added, 1) + assert.Len(t, removed, 1) + assert.Len(t, unchanged, 1) + assert.Equal(t, added[0].(*BgpsecKey).ASN, uint32(65003)) + assert.Equal(t, removed[0].(*BgpsecKey).ASN, uint32(65001)) + assert.Equal(t, unchanged[0].(*BgpsecKey).ASN, uint32(65002)) +}