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)) +}