diff --git a/cmd/bio-rd/bgp.go b/cmd/bio-rd/bgp.go new file mode 100644 index 00000000..ccf49d18 --- /dev/null +++ b/cmd/bio-rd/bgp.go @@ -0,0 +1,222 @@ +package main + +import ( + "fmt" + "time" + + "github.com/bio-routing/bio-rd/cmd/bio-rd/config" + bgpserver "github.com/bio-routing/bio-rd/protocols/bgp/server" + "github.com/bio-routing/bio-rd/routingtable" + "github.com/bio-routing/bio-rd/routingtable/vrf" +) + +const ( + DefaultReconnectInterval = time.Second * 15 +) + +type bgpConfigurator struct { + srv bgpserver.BGPServer + vrfReg *vrf.VRFRegistry +} + +func (c *bgpConfigurator) configure(cfg *config.BGP) error { + for _, bg := range cfg.Groups { + for _, bn := range bg.Neighbors { + err := c.configureSession(bn, bg) + if err != nil { + return fmt.Errorf("could not configure session for neighbor %s: %w", bn.PeerAddress, err) + } + } + } + + c.deconfigureRemovedSessions(cfg) + + return nil +} + +func (c *bgpConfigurator) configureSession(bn *config.BGPNeighbor, bg *config.BGPGroup) error { + v, err := c.determineVRF(bn, bg) + if err != nil { + return fmt.Errorf("could not determine VRF: %w", err) + } + + newCfg := c.newPeerConfig(bn, bg, v) + oldCfg := c.srv.GetPeerConfig(v, bn.PeerAddressIP) + if oldCfg != nil { + return c.reconfigureModifiedSession(bn, bg, newCfg, oldCfg) + } + + err = c.srv.AddPeer(*newCfg) + if err != nil { + return fmt.Errorf("unable to add BGP peer: %w", err) + } + + return nil +} + +func (c *bgpConfigurator) deconfigureRemovedSessions(cfg *config.BGP) { + for _, p := range c.srv.GetPeers() { + if !c.peerExistsInConfig(cfg, p) { + c.srv.DisposePeer(p.VRF(), p.Addr()) + } + } +} + +func (c *bgpConfigurator) peerExistsInConfig(cfg *config.BGP, p bgpserver.PeerKey) bool { + for _, bg := range cfg.Groups { + for _, bn := range bg.Neighbors { + v, _ := c.determineVRF(bn, bg) + if bn.PeerAddressIP == p.Addr() && p.VRF() == v { + return true + } + } + } + + return false +} + +func (c *bgpConfigurator) reconfigureModifiedSession(bn *config.BGPNeighbor, bg *config.BGPGroup, newCfg, oldCfg *bgpserver.PeerConfig) error { + if oldCfg.NeedsRestart(newCfg) { + return c.replaceSession(newCfg, oldCfg) + } + + err := c.srv.ReplaceImportFilterChain( + newCfg.VRF, + bn.PeerAddressIP, + bn.ImportFilterChain) + if err != nil { + return fmt.Errorf("could not replace import filter: %w", err) + } + + err = c.srv.ReplaceExportFilterChain( + newCfg.VRF, + bn.PeerAddressIP, + bn.ExportFilterChain) + if err != nil { + return fmt.Errorf("could not replace export filter: %w", err) + } + + return nil +} + +func (c *bgpConfigurator) replaceSession(newCfg, oldCfg *bgpserver.PeerConfig) error { + c.srv.DisposePeer(oldCfg.VRF, oldCfg.PeerAddress) + err := c.srv.AddPeer(*newCfg) + if err != nil { + return fmt.Errorf("unable to reconfigure BGP peer: %w", err) + } + + return nil +} + +func (c *bgpConfigurator) determineVRF(bn *config.BGPNeighbor, bg *config.BGPGroup) (*vrf.VRF, error) { + if len(bn.RoutingInstance) > 0 { + return c.vrfByName(bn.RoutingInstance) + } + + if len(bg.RoutingInstance) > 0 { + return c.vrfByName(bg.RoutingInstance) + } + + return bgpSrv.GetDefaultVRF(), nil +} + +func (c *bgpConfigurator) vrfByName(name string) (*vrf.VRF, error) { + v := c.vrfReg.GetVRFByName(name) + if v == nil { + return nil, fmt.Errorf("could not find VRF for name %s", name) + } + + return v, nil +} + +func (c *bgpConfigurator) newPeerConfig(bn *config.BGPNeighbor, bg *config.BGPGroup, vrf *vrf.VRF) *bgpserver.PeerConfig { + p := &bgpserver.PeerConfig{ + AdminEnabled: !bn.Disabled, + AuthenticationKey: bn.AuthenticationKey, + LocalAS: bn.LocalAS, + PeerAS: bn.PeerAS, + PeerAddress: bn.PeerAddressIP, + LocalAddress: bn.LocalAddressIP, + TTL: bn.TTL, + ReconnectInterval: DefaultReconnectInterval, + HoldTime: bn.HoldTimeDuration, + KeepAlive: bn.HoldTimeDuration / 3, + RouterID: c.srv.RouterID(), + VRF: vrf, + AdvertiseIPv4MultiProtocol: bn.AdvertiseIPv4MultiProtocol, + } + + c.configureIPv4(bn, bg, p) + c.configureIPv6(bn, bg, p) + + if bn.Passive != nil { + p.Passive = *bn.Passive + } + + if bn.RouteServerClient != nil { + p.RouteServerClient = *bn.RouteServerClient + } + + if bn.RouteReflectorClient != nil { + p.RouteReflectorClient = *bn.RouteReflectorClient + } + + if bn.ClusterIDIP != nil { + p.RouteReflectorClusterID = bn.ClusterIDIP.ToUint32() + } + + return p +} + +func (c *bgpConfigurator) configureIPv4(bn *config.BGPNeighbor, bg *config.BGPGroup, p *bgpserver.PeerConfig) { + if !bn.PeerAddressIP.IsIPv4() && bn.IPv4 == nil { + return + } + + p.IPv4 = c.newAFIConfig(bn, bg) + + if bn.IPv4 != nil { + c.configureAddressFamily(bn.IPv4, p.IPv4) + } +} + +func (c *bgpConfigurator) configureIPv6(bn *config.BGPNeighbor, bg *config.BGPGroup, p *bgpserver.PeerConfig) { + if bn.PeerAddressIP.IsIPv4() && bn.IPv6 == nil { + return + } + + p.IPv6 = c.newAFIConfig(bn, bg) + + if bn.IPv6 != nil { + c.configureAddressFamily(bn.IPv6, p.IPv6) + } +} + +func (c *bgpConfigurator) newAFIConfig(bn *config.BGPNeighbor, bg *config.BGPGroup) *bgpserver.AddressFamilyConfig { + return &bgpserver.AddressFamilyConfig{ + ImportFilterChain: bn.ImportFilterChain, + ExportFilterChain: bn.ExportFilterChain, + AddPathSend: routingtable.ClientOptions{ + BestOnly: true, + }, + AddPathRecv: false, + } +} + +func (c *bgpConfigurator) configureAddressFamily(baf *config.AddressFamilyConfig, af *bgpserver.AddressFamilyConfig) { + if baf.AddPath != nil { + c.configureAddPath(baf.AddPath, af) + } +} + +func (c *bgpConfigurator) configureAddPath(bac *config.AddPathConfig, af *bgpserver.AddressFamilyConfig) { + af.AddPathRecv = bac.Receive + + if bac.Send == nil { + return + } + + af.AddPathSend.BestOnly = !bac.Send.Multipath + af.AddPathSend.MaxPaths = uint(bac.Send.PathCount) +} diff --git a/cmd/bio-rd/config/bgp.go b/cmd/bio-rd/config/bgp.go index 6fd5ca73..f28b1fa2 100644 --- a/cmd/bio-rd/config/bgp.go +++ b/cmd/bio-rd/config/bgp.go @@ -8,6 +8,10 @@ import ( "github.com/bio-routing/bio-rd/routingtable/filter" ) +const ( + DefaultHoldTimeSeconds = 90 +) + type BGP struct { Groups []*BGPGroup `yaml:"groups"` } @@ -24,21 +28,28 @@ func (b *BGP) load(localAS uint32, policyOptions *PolicyOptions) error { } type BGPGroup struct { - Name string `yaml:"name"` - LocalAddress string `yaml:"local_address"` - LocalAddressIP *bnet.IP - TTL uint8 `yaml:"ttl"` - AuthenticationKey string `yaml:"authentication_key"` - PeerAS uint32 `yaml:"peer_as"` - LocalAS uint32 `yaml:"local_as"` - HoldTime uint16 `yaml:"hold_time"` - Multipath *Multipath `yaml:"multipath"` - Import []string `yaml:"import"` - Export []string `yaml:"export"` - RouteServerClient bool `yaml:"route_server_client"` - Passive bool `yaml:"passive"` - Neighbors []*BGPNeighbor `yaml:"neighbors"` - AFIs []*AFI `yaml:"afi"` + Name string `yaml:"name"` + LocalAddress string `yaml:"local_address"` + LocalAddressIP *bnet.IP + TTL uint8 `yaml:"ttl"` + AuthenticationKey string `yaml:"authentication_key"` + PeerAS uint32 `yaml:"peer_as"` + LocalAS uint32 `yaml:"local_as"` + HoldTime uint16 `yaml:"hold_time"` + Multipath *Multipath `yaml:"multipath"` + Import []string `yaml:"import"` + ImportFilterChain filter.Chain + Export []string `yaml:"export"` + ExportFilterChain filter.Chain + RouteServerClient *bool `yaml:"route_server_client"` + RouteReflectorClient *bool `yaml:"route_reflector_client"` + ClusterID string `yaml:"cluster_id"` + ClusterIDIP *bnet.IP + Passive *bool `yaml:"passive"` + Neighbors []*BGPNeighbor `yaml:"neighbors"` + IPv4 *AddressFamilyConfig `yaml:"ipv4"` + IPv6 *AddressFamilyConfig `yaml:"ipv6"` + RoutingInstance string `yaml:"routing_instance"` } func (bg *BGPGroup) load(localAS uint32, policyOptions *PolicyOptions) error { @@ -55,52 +66,106 @@ func (bg *BGPGroup) load(localAS uint32, policyOptions *PolicyOptions) error { bg.LocalAddressIP = a.Dedup() } + if bg.ClusterID != "" { + a, err := bnet.IPFromString(bg.ClusterID) + if err != nil { + return fmt.Errorf("unable to parse BGP cluster identifier: %w", err) + } + + bg.ClusterIDIP = a.Dedup() + } + if bg.HoldTime == 0 { - bg.HoldTime = 90 + bg.HoldTime = DefaultHoldTimeSeconds + } + + for i := range bg.Import { + f := policyOptions.getPolicyStatementFilter(bg.Import[i]) + if f == nil { + return fmt.Errorf("policy statement %q undefined", bg.Import[i]) + } + + bg.ImportFilterChain = append(bg.ImportFilterChain, f) + } + + for i := range bg.Export { + f := policyOptions.getPolicyStatementFilter(bg.Export[i]) + if f == nil { + return fmt.Errorf("policy statement %q undefined", bg.Export[i]) + } + + bg.ExportFilterChain = append(bg.ExportFilterChain, f) } - for _, n := range bg.Neighbors { - if n.RouteServerClient == nil { - n.RouteServerClient = &bg.RouteServerClient + for _, bn := range bg.Neighbors { + if bn.RouteServerClient == nil { + bn.RouteServerClient = bg.RouteServerClient } - if n.Passive == nil { - n.Passive = &bg.Passive + if bn.Passive == nil { + bn.Passive = bg.Passive } - if n.LocalAddress == "" { - n.LocalAddressIP = bg.LocalAddressIP + if bn.LocalAddress == "" { + bn.LocalAddressIP = bg.LocalAddressIP } - if n.TTL == 0 { - n.TTL = bg.TTL + if bn.ClusterID == "" { + bn.ClusterIDIP = bg.ClusterIDIP } - if n.AuthenticationKey == "" { - n.AuthenticationKey = bg.AuthenticationKey + if bn.TTL == 0 { + bn.TTL = bg.TTL } - if n.LocalAS == 0 { - n.LocalAS = localAS + if bn.AuthenticationKey == "" { + bn.AuthenticationKey = bg.AuthenticationKey } - if n.LocalAS == 0 { + if bn.LocalAS == 0 { + bn.LocalAS = bg.LocalAS + } + + if bn.LocalAS == 0 { return fmt.Errorf("local_as 0 is invalid") } - if n.PeerAS == 0 { - n.PeerAS = bg.PeerAS + if bn.PeerAS == 0 { + bn.PeerAS = bg.PeerAS } - if n.PeerAS == 0 { + if bn.PeerAS == 0 { return fmt.Errorf("peer_as 0 is invalid") } - if n.HoldTime == 0 { - n.HoldTime = bg.HoldTime + if bn.HoldTime == 0 { + bn.HoldTime = bg.HoldTime + } + + if bn.Multipath == nil { + bn.Multipath = bg.Multipath + } + + if len(bn.RoutingInstance) == 0 { + bn.RoutingInstance = bg.RoutingInstance + } + + if bn.IPv4 == nil { + bn.IPv4 = bg.IPv4 } - err := n.load(policyOptions) + if bn.IPv6 == nil { + bn.IPv6 = bg.IPv6 + } + + if bn.RouteReflectorClient == nil { + bn.RouteReflectorClient = bg.RouteReflectorClient + } + + bn.ImportFilterChain = bg.ImportFilterChain + bn.ExportFilterChain = bg.ExportFilterChain + + err := bn.load(policyOptions) if err != nil { return err } @@ -115,35 +180,40 @@ type Multipath struct { } type BGPNeighbor struct { - PeerAddress string `yaml:"peer_address"` - PeerAddressIP *bnet.IP - LocalAddress string `yaml:"local_address"` - LocalAddressIP *bnet.IP - TTL uint8 `yaml:"ttl"` - AuthenticationKey string `yaml:"authentication_key"` - PeerAS uint32 `yaml:"peer_as"` - LocalAS uint32 `yaml:"local_as"` - HoldTime uint16 `yaml:"hold_time"` - HoldTimeDuration time.Duration - Multipath *Multipath `yaml:"multipath"` - Import []string `yaml:"import"` - ImportFilterChain filter.Chain - Export []string `yaml:"export"` - ExportFilterChain filter.Chain - RouteServerClient *bool `yaml:"route_server_client"` - Passive *bool `yaml:"passive"` - ClusterID string `yaml:"cluster_id"` - ClusterIDIP *bnet.IP - AFIs []*AFI `yaml:"afi"` + PeerAddress string `yaml:"peer_address"` + PeerAddressIP *bnet.IP + LocalAddress string `yaml:"local_address"` + LocalAddressIP *bnet.IP + Disabled bool `yaml:"disabled"` + TTL uint8 `yaml:"ttl"` + AuthenticationKey string `yaml:"authentication_key"` + PeerAS uint32 `yaml:"peer_as"` + LocalAS uint32 `yaml:"local_as"` + HoldTime uint16 `yaml:"hold_time"` + HoldTimeDuration time.Duration + Multipath *Multipath `yaml:"multipath"` + Import []string `yaml:"import"` + ImportFilterChain filter.Chain + Export []string `yaml:"export"` + ExportFilterChain filter.Chain + RouteServerClient *bool `yaml:"route_server_client"` + RouteReflectorClient *bool `yaml:"route_reflector_client"` + Passive *bool `yaml:"passive"` + ClusterID string `yaml:"cluster_id"` + ClusterIDIP *bnet.IP + IPv4 *AddressFamilyConfig `yaml:"ipv4"` + IPv6 *AddressFamilyConfig `yaml:"ipv6"` + AdvertiseIPv4MultiProtocol bool `yaml:"advertise_ipv4_multiprotocol"` + RoutingInstance string `yaml:"routing_instance"` } -func (bn *BGPNeighbor) load(po *PolicyOptions) error { +func (bn *BGPNeighbor) load(policyOptions *PolicyOptions) error { if bn.PeerAS == 0 { - return fmt.Errorf("Peer %q is lacking peer as number", bn.PeerAddress) + return fmt.Errorf("peer %q is lacking peer as number", bn.PeerAddress) } if bn.PeerAddress == "" { - return fmt.Errorf("Mandatory parameter BGP peer address is empty") + return fmt.Errorf("mandatory parameter BGP peer address is empty") } if bn.LocalAddress != "" { @@ -155,6 +225,15 @@ func (bn *BGPNeighbor) load(po *PolicyOptions) error { bn.LocalAddressIP = a.Dedup() } + if bn.ClusterID != "" { + a, err := bnet.IPFromString(bn.ClusterID) + if err != nil { + return fmt.Errorf("unable to parse BGP cluster identifier: %w", err) + } + + bn.ClusterIDIP = a.Dedup() + } + b, err := bnet.IPFromString(bn.PeerAddress) if err != nil { return fmt.Errorf("unable to parse BGP peer address: %w", err) @@ -163,8 +242,11 @@ func (bn *BGPNeighbor) load(po *PolicyOptions) error { bn.PeerAddressIP = b.Dedup() bn.HoldTimeDuration = time.Second * time.Duration(bn.HoldTime) + if len(bn.Import) > 0 { + bn.ImportFilterChain = filter.Chain{} + } for i := range bn.Import { - f := po.getPolicyStatementFilter(bn.Import[i]) + f := policyOptions.getPolicyStatementFilter(bn.Import[i]) if f == nil { return fmt.Errorf("policy statement %q undefined", bn.Import[i]) } @@ -172,33 +254,31 @@ func (bn *BGPNeighbor) load(po *PolicyOptions) error { bn.ImportFilterChain = append(bn.ImportFilterChain, f) } + if len(bn.Export) > 0 { + bn.ExportFilterChain = filter.Chain{} + } for i := range bn.Export { - f := po.getPolicyStatementFilter(bn.Export[i]) + f := policyOptions.getPolicyStatementFilter(bn.Export[i]) if f == nil { return fmt.Errorf("policy statement %q undefined", bn.Export[i]) } bn.ExportFilterChain = append(bn.ExportFilterChain, f) } - return nil -} -type AFI struct { - Name string `yaml:"name"` - SAFI SAFI `yaml:"safi"` + return nil } -type SAFI struct { - Name string `yaml:"name"` - AddPath *AddPath `yaml:"add_path"` +type AddressFamilyConfig struct { + AddPath *AddPathConfig `yaml:"add_path"` } -type AddPath struct { - Receive bool `yaml:"receive"` - Send *AddPathSend `yaml:"send"` +type AddPathConfig struct { + Receive bool `yaml:"receive"` + Send *AddPathSendConfig `yaml:"send"` } -type AddPathSend struct { +type AddPathSendConfig struct { Multipath bool `yaml:"multipath"` PathCount uint8 `yaml:"path_count"` } diff --git a/cmd/bio-rd/config/bgp_test.go b/cmd/bio-rd/config/bgp_test.go new file mode 100644 index 00000000..d460923e --- /dev/null +++ b/cmd/bio-rd/config/bgp_test.go @@ -0,0 +1,131 @@ +package config + +import ( + "testing" + "time" + + bnet "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/routingtable/filter" + + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" +) + +const ( + BGPGroupTestFile = ` +groups: + - name: Group1 + local_address: 100.64.0.1 + route_server_client: true + route_reflector_client: true + passive: true + ttl: 10 + local_as: 65200 + peer_as: 65300 + authentication_key: secret + hold_time: 180 + routing_instance: main + import: ["ACCEPT_ALL"] + export: ["REJECT_ALL"] + cluster_id: 100.65.1.1 + neighbors: + - peer_address: 100.64.0.2 + cluster_id: 100.64.0.0 + disabled: true + ipv4: + add_path: + receive: true + send: + path_count: 5 + - peer_address: 100.64.1.2 + local_address: 100.64.1.1 + local_as: 65400 + peer_as: 65401 + hold_time: 90 + ttl: 1 + routing_instance: test + export: ["ACCEPT_ALL"] + import: ["REJECT_ALL"] + authentication_key: top-secret + passive: false + route_reflector_client: false + route_server_client: false + ipv6: + add_path: + receive: true + send: + path_count: 2 + ipv6: + add_path: + receive: false + send: + path_count: 10 + ` +) + +func TestBGPLoad(t *testing.T) { + policyOptions := &PolicyOptions{} + accept_all := filter.NewFilter("ACCEPT_ALL", nil) + reject_all := filter.NewFilter("REJECT_ALL", nil) + policyOptions.PolicyStatementsFilter = []*filter.Filter{ + accept_all, + reject_all, + } + + b := []byte(BGPGroupTestFile) + var bgp *BGP + err := yaml.Unmarshal(b, &bgp) + if err != nil { + t.Fatalf("unexpected error while parsing: %s", err) + } + + assert.Equal(t, 1, len(bgp.Groups), "group count") + group := bgp.Groups[0] + + assert.Equal(t, 2, len(group.Neighbors), "neighbor count") + + err = bgp.load(64900, policyOptions) + if err != nil { + t.Fatalf("unexpected error while loading group config: %s", err) + } + + n1 := group.Neighbors[0] + assert.Equal(t, bnet.IPv4FromOctets(100, 64, 0, 1).Dedup(), n1.LocalAddressIP, "neighbor 1 local address") + assert.Equal(t, bnet.IPv4FromOctets(100, 64, 0, 2).Dedup(), n1.PeerAddressIP, "neighbor 1 peer address") + assert.True(t, *n1.RouteServerClient, "neighbor 1 route server client") + assert.True(t, *n1.RouteReflectorClient, "neighbor 1 route reflector client") + assert.True(t, *n1.Passive, "neighbor 1 passive") + assert.Equal(t, uint8(10), n1.TTL, "neighbor 1 TTL") + assert.Equal(t, uint32(65200), n1.LocalAS, "neighbor 1 local ASN") + assert.Equal(t, uint32(65300), n1.PeerAS, "neighbor 1 peer ASN") + assert.Equal(t, "secret", n1.AuthenticationKey, "neighbor 1 auth") + assert.Equal(t, 180*time.Second, n1.HoldTimeDuration, "neighbor 1 hold") + assert.Equal(t, "main", n1.RoutingInstance, "neighbor 1 VRF") + assert.Equal(t, filter.Chain{accept_all}, n1.ImportFilterChain, "neighbor 1 import") + assert.Equal(t, filter.Chain{reject_all}, n1.ExportFilterChain, "neighbor 1 export") + assert.Equal(t, bnet.IPv4FromOctets(100, 64, 0, 0).Dedup(), n1.ClusterIDIP, "neighbor 1 cluster ID") + assert.True(t, n1.IPv4.AddPath.Receive, "neighbor 1 IPv4 add path receive") + assert.False(t, n1.IPv6.AddPath.Receive, "neighbor 1 IPv6 add path receive") + assert.Equal(t, uint8(5), n1.IPv4.AddPath.Send.PathCount, "neighbor 1 IPv4 add path send count") + assert.Equal(t, uint8(10), n1.IPv6.AddPath.Send.PathCount, "neighbor 1 IPv6 add path send count") + assert.True(t, n1.Disabled, "neighbor 1 disabled") + + n2 := group.Neighbors[1] + assert.Equal(t, bnet.IPv4FromOctets(100, 64, 1, 1).Dedup(), n2.LocalAddressIP, "neighbor 2 local address") + assert.Equal(t, bnet.IPv4FromOctets(100, 64, 1, 2).Dedup(), n2.PeerAddressIP, "neighbor 2 peer address") + assert.False(t, *n2.RouteServerClient, "neighbor 2 route server client") + assert.False(t, *n2.RouteReflectorClient, "neighbor 2 route reflector client") + assert.False(t, *n2.Passive, "neighbor 2 passive") + assert.Equal(t, uint8(1), n2.TTL, "neighbor 2 TTL") + assert.Equal(t, uint32(65400), n2.LocalAS, "neighbor 2 local ASN") + assert.Equal(t, uint32(65401), n2.PeerAS, "neighbor 2 peer ASN") + assert.Equal(t, "top-secret", n2.AuthenticationKey, "neighbor 2 auth") + assert.Equal(t, 90*time.Second, n2.HoldTimeDuration, "neighbor 2 hold") + assert.Equal(t, "test", n2.RoutingInstance, "neighbor 2 VRF") + assert.Equal(t, filter.Chain{reject_all}, n2.ImportFilterChain, "neighbor 2 import") + assert.Equal(t, filter.Chain{accept_all}, n2.ExportFilterChain, "neighbor 2 export") + assert.Equal(t, n2.ClusterIDIP, bnet.IPv4FromOctets(100, 65, 1, 1).Dedup(), "neighbor 2 cluster ID") + assert.Nil(t, n2.IPv4, "neighbor 2 IPv4") + assert.True(t, n2.IPv6.AddPath.Receive, "neighbor 2 IPv6 add path receive") + assert.Equal(t, uint8(2), n2.IPv6.AddPath.Send.PathCount, "neighbor 2 IPv6 add path send count") +} diff --git a/cmd/bio-rd/main.go b/cmd/bio-rd/main.go index 3f209b02..79454007 100644 --- a/cmd/bio-rd/main.go +++ b/cmd/bio-rd/main.go @@ -14,7 +14,6 @@ import ( "github.com/bio-routing/bio-rd/protocols/device" isisapi "github.com/bio-routing/bio-rd/protocols/isis/api" isisserver "github.com/bio-routing/bio-rd/protocols/isis/server" - "github.com/bio-routing/bio-rd/routingtable" "github.com/bio-routing/bio-rd/routingtable/vrf" "github.com/bio-routing/bio-rd/util/log" "github.com/bio-routing/bio-rd/util/servicewrapper" @@ -23,11 +22,18 @@ import ( "google.golang.org/grpc/keepalive" ) +const ( + DefaultBGPListenAddrIPv4 = "0.0.0.0:179" + DefaultBGPListenAddrIPv6 = "[::]:179" +) + var ( configFilePath = flag.String("config.file", "bio-rd.yml", "bio-rd config file") grpcPort = flag.Uint("grpc_port", 5566, "GRPC API server port") grpcKeepaliveMinTime = flag.Uint("grpc_keepalive_min_time", 1, "Minimum time (seconds) for a client to wait between GRPC keepalive pings") metricsPort = flag.Uint("metrics_port", 55667, "Metrics HTTP server port") + bgpListenAddrIPv4 = flag.String("bgp.listen-addr-ipv4", DefaultBGPListenAddrIPv4, "BGP listen address for IPv4 AFI") + bgpListenAddrIPv6 = flag.String("bgp.listen-addr-ipv6", DefaultBGPListenAddrIPv6, "BGP listen address for IPv6 AFI") sigHUP = make(chan os.Signal) vrfReg = vrf.NewVRFRegistry() bgpSrv bgpserver.BGPServer @@ -63,8 +69,8 @@ func main() { listenAddrsByVRF := map[string][]string{ vrf.DefaultVRFName: { - "[::]:179", - "0.0.0.0:179", + fmt.Sprintf(*bgpListenAddrIPv6), + fmt.Sprintf(*bgpListenAddrIPv4), }, } @@ -142,7 +148,11 @@ func loadConfig(cfg *config.Config) error { if cfg.Protocols != nil { if cfg.Protocols.BGP != nil { - err := configureProtocolsBGP(cfg.Protocols.BGP) + bgpCfgtr := &bgpConfigurator{ + srv: bgpSrv, + vrfReg: vrfReg, + } + err := bgpCfgtr.configure(cfg.Protocols.BGP) if err != nil { return fmt.Errorf("unable to configure BGP: %w", err) } @@ -159,95 +169,6 @@ func loadConfig(cfg *config.Config) error { return nil } -func configureProtocolsBGP(bgp *config.BGP) error { - // Tear down peers that are to be removed - for _, p := range bgpSrv.GetPeers() { - found := false - for _, g := range bgp.Groups { - for _, n := range g.Neighbors { - if n.PeerAddressIP == p.Addr() && p.VRF() == bgpSrv.GetDefaultVRF() { - found = true - break - } - } - } - - if !found { - bgpSrv.DisposePeer(bgpSrv.GetDefaultVRF(), p.Addr()) - } - } - - // Tear down peers that need new sessions as they changed too significantly - for _, g := range bgp.Groups { - for _, n := range g.Neighbors { - newCfg := BGPPeerConfig(n, bgpSrv.GetDefaultVRF()) - oldCfg := bgpSrv.GetPeerConfig(bgpSrv.GetDefaultVRF(), n.PeerAddressIP) - if oldCfg == nil { - continue - } - - if !oldCfg.NeedsRestart(newCfg) { - bgpSrv.ReplaceImportFilterChain(bgpSrv.GetDefaultVRF(), n.PeerAddressIP, newCfg.IPv4.ImportFilterChain) - bgpSrv.ReplaceExportFilterChain(bgpSrv.GetDefaultVRF(), n.PeerAddressIP, newCfg.IPv4.ExportFilterChain) - continue - } - - bgpSrv.DisposePeer(bgpSrv.GetDefaultVRF(), oldCfg.PeerAddress) - } - } - - // Turn up all sessions that are missing - for _, g := range bgp.Groups { - for _, n := range g.Neighbors { - if bgpSrv.GetPeerConfig(bgpSrv.GetDefaultVRF(), n.PeerAddressIP) != nil { - continue - } - - newCfg := BGPPeerConfig(n, vrfReg.GetVRFByName(vrf.DefaultVRFName)) - err := bgpSrv.AddPeer(*newCfg) - if err != nil { - return fmt.Errorf("unable to add BGP peer: %w", err) - } - } - } - - return nil -} - -// BGPPeerConfig converts a BGPNeighbor config into a PeerConfig -func BGPPeerConfig(n *config.BGPNeighbor, vrf *vrf.VRF) *bgpserver.PeerConfig { - r := &bgpserver.PeerConfig{ - AuthenticationKey: n.AuthenticationKey, - LocalAS: n.LocalAS, - PeerAS: n.PeerAS, - PeerAddress: n.PeerAddressIP, - LocalAddress: n.LocalAddressIP, - TTL: n.TTL, - ReconnectInterval: time.Second * 15, - HoldTime: n.HoldTimeDuration, - KeepAlive: n.HoldTimeDuration / 3, - RouterID: bgpSrv.RouterID(), - IPv4: &bgpserver.AddressFamilyConfig{ - ImportFilterChain: n.ImportFilterChain, - ExportFilterChain: n.ExportFilterChain, - AddPathSend: routingtable.ClientOptions{ - MaxPaths: 10, - }, - }, - VRF: vrf, - } - - if n.Passive != nil { - r.Passive = *n.Passive - } - - if n.RouteServerClient != nil { - r.RouteServerClient = *n.RouteServerClient - } - - return r -} - func configureRoutingInstance(ri *config.RoutingInstance) error { vrf := vrfReg.GetVRFByName(ri.Name) diff --git a/docker/bio-rd/Dockerfile b/docker/bio-rd/Dockerfile new file mode 100644 index 00000000..921e2bd8 --- /dev/null +++ b/docker/bio-rd/Dockerfile @@ -0,0 +1,18 @@ +FROM golang as builder +ADD . /go/bio-rd +WORKDIR /go/bio-rd/cmd/bio-rd +RUN GOOS=linux go build -o /go/bin/bio-rd + +FROM debian:stable +WORKDIR /app +COPY --from=builder /go/bin/bio-rd . +CMD /app/bio-rd --config.file=/config/bio-rd.yml ${CMD_ARGS} +RUN apt update && \ + apt install -y libcap2-bin && \ + setcap cap_net_bind_service+eip /app/bio-rd && \ + useradd --system bio-rd +USER bio-rd +VOLUME /config +EXPOSE 179 +EXPOSE 5566 +EXPOSE 55667