1
0
mirror of https://github.com/bgp/stayrtr.git synced 2024-05-06 15:54:54 +00:00

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
This commit is contained in:
Ben Cartwright-Cox
2023-02-21 21:55:50 +00:00
parent b08f5383ac
commit 513bda0e5f
4 changed files with 180 additions and 20 deletions

View File

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

View File

@ -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"),

View File

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

View File

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