diff --git a/calico-vpp-agent/cmd/calico_vpp_dataplane.go b/calico-vpp-agent/cmd/calico_vpp_dataplane.go index 6ef165a7c..efa39ae72 100644 --- a/calico-vpp-agent/cmd/calico_vpp_dataplane.go +++ b/calico-vpp-agent/cmd/calico_vpp_dataplane.go @@ -37,7 +37,6 @@ import ( "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/common" "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/felix" "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/prometheus" - "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/routing" "github.com/projectcalico/vpp-dataplane/v3/config" watchdog "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/watch_dog" @@ -139,10 +138,10 @@ func main() { peerWatcher := watchers.NewPeerWatcher(clientv3, k8sclient, log.WithFields(logrus.Fields{"subcomponent": "peer-watcher"})) bgpFilterWatcher := watchers.NewBGPFilterWatcher(clientv3, k8sclient, log.WithFields(logrus.Fields{"subcomponent": "BGPFilter-watcher"})) netWatcher := watchers.NewNetWatcher(vpp, log.WithFields(logrus.Fields{"component": "net-watcher"})) - routingServer := routing.NewRoutingServer(vpp, bgpServer, log.WithFields(logrus.Fields{"component": "routing"})) prometheusServer := prometheus.NewPrometheusServer(vpp, log.WithFields(logrus.Fields{"component": "prometheus"})) localSIDWatcher := watchers.NewLocalSIDWatcher(vpp, clientv3, log.WithFields(logrus.Fields{"subcomponent": "localsid-watcher"})) felixServer := felix.NewFelixServer(vpp, clientv3, log.WithFields(logrus.Fields{"component": "policy"})) + felixServer.SetBGPServer(bgpServer) felixWatcher := watchers.NewFelixWatcher(felixServer.GetFelixServerEventChan(), log.WithFields(logrus.Fields{"component": "felix watcher"})) cniServer := watchers.NewCNIServer(felixServer.GetFelixServerEventChan(), log.WithFields(logrus.Fields{"component": "cni"})) serviceServer := watchers.NewServiceServer(felixServer.GetFelixServerEventChan(), k8sclient, log.WithFields(logrus.Fields{"component": "services"})) @@ -159,10 +158,11 @@ func main() { log.Fatalf("cannot get default BGP config %s", err) } - routingServer.SetBGPConf(bgpConf) felixServer.SetBGPConf(bgpConf) - routingServer.SetPeerHandler(felixServer.GetPeerHandler()) + bgpWatcher := watchers.NewBGPWatcher(felixServer.GetCache(), log.WithFields(logrus.Fields{"component": "bgp-watcher"})) + bgpWatcher.SetBGPServer(bgpServer) + bgpWatcher.SetBGPHandler(felixServer.GetBGPHandler()) watchDog := watchdog.NewWatchDog(log.WithFields(logrus.Fields{"component": "watchDog"}), &t) Go(felixServer.ServeFelix) @@ -179,7 +179,8 @@ func main() { panic("ourBGPSpec is not *common.LocalNodeSpec") } prefixWatcher.SetOurBGPSpec(bgpSpec) - routingServer.SetOurBGPSpec(bgpSpec) + bgpWatcher.SetOurBGPSpec(bgpSpec) + felixServer.GetRoutingHandler().SetOurBGPSpec(bgpSpec) localSIDWatcher.SetOurBGPSpec(bgpSpec) netWatcher.SetOurBGPSpec(bgpSpec) } @@ -195,7 +196,8 @@ func main() { Go(prefixWatcher.WatchPrefix) Go(peerWatcher.WatchBGPPeers) Go(bgpFilterWatcher.WatchBGPFilters) - Go(routingServer.ServeRouting) + Go(bgpWatcher.WatchBGPPath) + Go(felixServer.GetRoutingHandler().ServeRoutingHandler) Go(serviceServer.ServeService) Go(cniServer.ServeCNI) Go(prometheusServer.ServePrometheus) diff --git a/calico-vpp-agent/felix/felix_server.go b/calico-vpp-agent/felix/felix_server.go index 47703366c..b8d1785ea 100644 --- a/calico-vpp-agent/felix/felix_server.go +++ b/calico-vpp-agent/felix/felix_server.go @@ -19,6 +19,8 @@ import ( "fmt" "net" + bgpapi "github.com/osrg/gobgp/v3/api" + bgpserver "github.com/osrg/gobgp/v3/pkg/server" "github.com/pkg/errors" calicov3cli "github.com/projectcalico/calico/libcalico-go/lib/clientv3" "github.com/sirupsen/logrus" @@ -33,8 +35,8 @@ import ( "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/felix/cni/model" "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/felix/connectivity" "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/felix/policies" + "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/felix/routing" "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/felix/services" - "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/routing" "github.com/projectcalico/vpp-dataplane/v3/config" "github.com/projectcalico/vpp-dataplane/v3/vpplink" "github.com/projectcalico/vpp-dataplane/v3/vpplink/types" @@ -61,6 +63,8 @@ type Server struct { connectivityHandler *connectivity.ConnectivityHandler serviceHandler *services.ServiceHandler peerHandler *routing.PeerHandler + routingHandler *routing.RoutingHandler + bgpHandler *routing.BGPHandler } // NewFelixServer creates a felix server @@ -80,6 +84,8 @@ func NewFelixServer(vpp *vpplink.VppLink, clientv3 calicov3cli.Interface, log *l connectivityHandler: connectivity.NewConnectivityHandler(vpp, cache, clientv3, log), serviceHandler: services.NewServiceHandler(vpp, cache, log), peerHandler: routing.NewPeerHandler(cache, log), + routingHandler: routing.NewRoutingHandler(vpp, cache, log), + bgpHandler: routing.NewBGPHandler(log), } reg := common.RegisterHandler(server.felixServerEventChan, "felix server events") @@ -101,6 +107,17 @@ func NewFelixServer(vpp *vpplink.VppLink, clientv3 calicov3cli.Interface, log *l common.SecretAdded, common.SecretChanged, common.SecretDeleted, + common.BGPPathAdded, + common.BGPPathDeleted, + common.BGPPeerAdded, + common.BGPPeerUpdated, + common.BGPPeerDeleted, + common.BGPFilterAddedOrUpdated, + common.BGPFilterDeleted, + common.BGPDefinedSetAdded, + common.BGPDefinedSetDeleted, + common.LocalPodAddressAdded, + common.LocalPodAddressDeleted, ) return server @@ -118,10 +135,22 @@ func (s *Server) GetPeerHandler() *routing.PeerHandler { return s.peerHandler } +func (s *Server) GetRoutingHandler() *routing.RoutingHandler { + return s.routingHandler +} + +func (s *Server) GetBGPHandler() *routing.BGPHandler { + return s.bgpHandler +} + func (s *Server) SetBGPConf(bgpConf *calicov3.BGPConfigurationSpec) { s.cache.BGPConf = bgpConf } +func (s *Server) SetBGPServer(bgpServer *bgpserver.BgpServer) { + s.bgpHandler.SetBGPServer(bgpServer) +} + func (s *Server) getMainInterface() *config.UplinkStatus { for _, i := range common.VppManagerInfo.UplinkStatuses { if i.IsMain { @@ -385,39 +414,27 @@ func (s *Server) handleFelixServerEvents(msg interface{}) (err error) { case common.ConnectivityAdded: new, ok := evt.New.(*common.NodeConnectivity) if !ok { - s.log.Errorf("evt.New is not a *common.NodeConnectivity %v", evt.New) - } - err := s.connectivityHandler.UpdateIPConnectivity(new, false /* isWithdraw */) - if err != nil { - s.log.Errorf("Error while adding connectivity %s", err) + return fmt.Errorf("evt.New is not a (*common.NodeConnectivity) %v", evt.New) } + err = s.connectivityHandler.UpdateIPConnectivity(new, false /* isWithdraw */) case common.ConnectivityDeleted: old, ok := evt.Old.(*common.NodeConnectivity) if !ok { - s.log.Errorf("evt.Old is not a *common.NodeConnectivity %v", evt.Old) - } - err := s.connectivityHandler.UpdateIPConnectivity(old, true /* isWithdraw */) - if err != nil { - s.log.Errorf("Error while deleting connectivity %s", err) + return fmt.Errorf("evt.Old is not a (*common.NodeConnectivity) %v", evt.Old) } + err = s.connectivityHandler.UpdateIPConnectivity(old, true /* isWithdraw */) case common.SRv6PolicyAdded: new, ok := evt.New.(*common.NodeConnectivity) if !ok { - s.log.Errorf("evt.New is not a *common.NodeConnectivity %v", evt.New) - } - err := s.connectivityHandler.UpdateSRv6Policy(new, false /* isWithdraw */) - if err != nil { - s.log.Errorf("Error while adding SRv6 Policy %s", err) + return fmt.Errorf("evt.New is not a (*common.NodeConnectivity) %v", evt.New) } + err = s.connectivityHandler.UpdateSRv6Policy(new, false /* isWithdraw */) case common.SRv6PolicyDeleted: old, ok := evt.Old.(*common.NodeConnectivity) if !ok { - s.log.Errorf("evt.Old is not a *common.NodeConnectivity %v", evt.Old) - } - err := s.connectivityHandler.UpdateSRv6Policy(old, true /* isWithdraw */) - if err != nil { - s.log.Errorf("Error while deleting SRv6 Policy %s", err) + return fmt.Errorf("evt.Old is not a (*common.NodeConnectivity) %v", evt.Old) } + err = s.connectivityHandler.UpdateSRv6Policy(old, true /* isWithdraw */) case common.PeersChanged: peersEvent, ok := evt.New.(*common.PeersChangedEvent) if !ok { @@ -472,6 +489,76 @@ func (s *Server) handleFelixServerEvents(msg interface{}) (err error) { return fmt.Errorf("evt.New is not a (*common.SecretDeletedEvent) %v", evt.New) } s.peerHandler.OnSecretDeleted(secretEvent.SecretName) + case common.BGPPathAdded: + path, ok := evt.New.(*bgpapi.Path) + if !ok { + return fmt.Errorf("evt.New is not a (*bgpapi.Path) %v", evt.New) + } + err = s.bgpHandler.HandleBGPPathAdded(path) + case common.BGPPathDeleted: + path, ok := evt.Old.(*bgpapi.Path) + if !ok { + return fmt.Errorf("evt.Old is not a (*bgpapi.Path) %v", evt.Old) + } + err = s.bgpHandler.HandleBGPPathDeleted(path) + case common.BGPPeerAdded: + peer, ok := evt.New.(*routing.LocalBGPPeer) + if !ok { + return fmt.Errorf("evt.New is not a (*routing.LocalBGPPeer) %v", evt.New) + } + err = s.bgpHandler.HandleBGPPeerAdded(peer) + case common.BGPPeerUpdated: + newPeer, ok := evt.New.(*routing.LocalBGPPeer) + if !ok { + return fmt.Errorf("evt.New is not a (*routing.LocalBGPPeer) %v", evt.New) + } + oldPeer, ok := evt.Old.(*routing.LocalBGPPeer) + if !ok { + return fmt.Errorf("evt.Old is not a (*routing.LocalBGPPeer) %v", evt.Old) + } + err = s.bgpHandler.HandleBGPPeerUpdated(newPeer, oldPeer) + case common.BGPPeerDeleted: + peerIP, ok := evt.Old.(string) + if !ok { + return fmt.Errorf("evt.Old is not a string %v", evt.Old) + } + err = s.bgpHandler.HandleBGPPeerDeleted(peerIP) + case common.BGPFilterAddedOrUpdated: + filter, ok := evt.New.(calicov3.BGPFilter) + if !ok { + return fmt.Errorf("evt.New is not a (calicov3.BGPFilter) %v", evt.New) + } + err = s.bgpHandler.HandleBGPFilterAddedOrUpdated(filter) + case common.BGPFilterDeleted: + filter, ok := evt.Old.(calicov3.BGPFilter) + if !ok { + return fmt.Errorf("evt.Old is not a (calicov3.BGPFilter) %v", evt.Old) + } + err = s.bgpHandler.HandleBGPFilterDeleted(filter) + case common.BGPDefinedSetAdded: + definedSet, ok := evt.New.(*bgpapi.DefinedSet) + if !ok { + return fmt.Errorf("evt.New is not a (*bgpapi.DefinedSet) %v", evt.New) + } + err = s.bgpHandler.HandleBGPDefinedSetAdded(definedSet) + case common.BGPDefinedSetDeleted: + definedSet, ok := evt.Old.(*bgpapi.DefinedSet) + if !ok { + return fmt.Errorf("evt.Old is not a (*bgpapi.DefinedSet) %v", evt.Old) + } + err = s.bgpHandler.HandleBGPDefinedSetDeleted(definedSet) + case common.LocalPodAddressAdded: + networkPod, ok := evt.New.(cni.NetworkPod) + if !ok { + return fmt.Errorf("evt.New is not a (cni.NetworkPod) %v", evt.New) + } + err = s.routingHandler.AnnounceLocalAddress(networkPod.ContainerIP, networkPod.NetworkVni) + case common.LocalPodAddressDeleted: + networkPod, ok := evt.Old.(cni.NetworkPod) + if !ok { + return fmt.Errorf("evt.Old is not a (cni.NetworkPod) %v", evt.Old) + } + err = s.routingHandler.WithdrawLocalAddress(networkPod.ContainerIP, networkPod.NetworkVni) default: s.log.Warnf("Unhandled CalicoVppEvent.Type: %s", evt.Type) } diff --git a/calico-vpp-agent/felix/routing/bgp_handler.go b/calico-vpp-agent/felix/routing/bgp_handler.go new file mode 100644 index 000000000..f505fc6c1 --- /dev/null +++ b/calico-vpp-agent/felix/routing/bgp_handler.go @@ -0,0 +1,749 @@ +// Copyright (C) 2020 Cisco Systems Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package routing + +import ( + "fmt" + "net" + "reflect" + "sort" + + bgpapi "github.com/osrg/gobgp/v3/api" + bgpserver "github.com/osrg/gobgp/v3/pkg/server" + "github.com/pkg/errors" + calicov3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" + "github.com/sirupsen/logrus" + "golang.org/x/net/context" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/common" + "github.com/projectcalico/vpp-dataplane/v3/vpplink/generated/bindings/ip_types" + "github.com/projectcalico/vpp-dataplane/v3/vpplink/types" +) + +// LocalBGPPeer represents a BGP peer with its configuration and policies +type LocalBGPPeer struct { + Peer *bgpapi.Peer + BGPFilterNames []string + BGPPolicies map[string]*ImpExpPol + NeighborSet *bgpapi.DefinedSet +} + +// BGPPrefixesPolicyAndAssignment contains BGP policy and prefix information +type BGPPrefixesPolicyAndAssignment struct { + PolicyAssignment *bgpapi.PolicyAssignment + Policy *bgpapi.Policy + Prefixes []*bgpapi.DefinedSet +} + +// ImpExpPol contains import and export policies +type ImpExpPol struct { + Imp *BGPPrefixesPolicyAndAssignment + Exp *BGPPrefixesPolicyAndAssignment +} + +// BGPHandler handles BGP business logic operations +type BGPHandler struct { + log *logrus.Entry + BGPServer *bgpserver.BgpServer + bgpFilters map[string]*calicov3.BGPFilter + bgpPeers map[string]*LocalBGPPeer +} + +// NewBGPHandler creates a new BGP handler instance +func NewBGPHandler(log *logrus.Entry) *BGPHandler { + return &BGPHandler{ + log: log, + bgpFilters: make(map[string]*calicov3.BGPFilter), + bgpPeers: make(map[string]*LocalBGPPeer), + } +} + +// SetBGPServer sets the BGP server instance +func (h *BGPHandler) SetBGPServer(bgpServer *bgpserver.BgpServer) { + h.BGPServer = bgpServer +} + +// getNexthop extracts the next hop from BGP path attributes +func (h *BGPHandler) getNexthop(path *bgpapi.Path) string { + for _, attr := range path.Pattrs { + nhAttr := &bgpapi.NextHopAttribute{} + mpReachAttr := &bgpapi.MpReachNLRIAttribute{} + if err := attr.UnmarshalTo(nhAttr); err == nil { + return nhAttr.NextHop + } + if err := attr.UnmarshalTo(mpReachAttr); err == nil { + if len(mpReachAttr.NextHops) != 1 { + h.log.Fatalf("Cannot process more than one Nlri in path attributes: %+v", mpReachAttr) + } + return mpReachAttr.NextHops[0] + } + } + return "" +} + +// CompareStringSlices compares two string slices for equality (order-independent) +func CompareStringSlices(slice1, slice2 []string) bool { + if len(slice1) != len(slice2) { + return false + } + + // Sort the slices in ascending order + sort.Strings(slice1) + sort.Strings(slice2) + + // Compare the sorted slices + return reflect.DeepEqual(slice1, slice2) +} + +// injectRoute is a helper function to inject BGP routes to VPP +// TODO: multipath support +func (h *BGPHandler) InjectRoute(path *bgpapi.Path) error { + var dst net.IPNet + ipAddrPrefixNlri := &bgpapi.IPAddressPrefix{} + labeledVPNIPAddressPrefixNlri := &bgpapi.LabeledVPNIPAddressPrefix{} + vpn := false + otherNodeIP := net.ParseIP(h.getNexthop(path)) + if otherNodeIP == nil { + return fmt.Errorf("cannot determine path nexthop: %+v", path) + } + + if err := path.Nlri.UnmarshalTo(ipAddrPrefixNlri); err == nil { + dst.IP = net.ParseIP(ipAddrPrefixNlri.Prefix) + if dst.IP == nil { + return fmt.Errorf("cannot parse nlri addr: %s", ipAddrPrefixNlri.Prefix) + } else if dst.IP.To4() == nil { + dst.Mask = net.CIDRMask(int(ipAddrPrefixNlri.PrefixLen), 128) + } else { + dst.Mask = net.CIDRMask(int(ipAddrPrefixNlri.PrefixLen), 32) + } + } else { + err := path.Nlri.UnmarshalTo(labeledVPNIPAddressPrefixNlri) + if err == nil { + dst.IP = net.ParseIP(labeledVPNIPAddressPrefixNlri.Prefix) + if dst.IP == nil { + return fmt.Errorf("cannot parse nlri addr: %s", labeledVPNIPAddressPrefixNlri.Prefix) + } else if dst.IP.To4() == nil { + dst.Mask = net.CIDRMask(int(labeledVPNIPAddressPrefixNlri.PrefixLen), 128) + } else { + dst.Mask = net.CIDRMask(int(labeledVPNIPAddressPrefixNlri.PrefixLen), 32) + } + vpn = true + } else { + return fmt.Errorf("cannot handle Nlri: %+v", path.Nlri) + } + } + + cn := &common.NodeConnectivity{ + Dst: dst, + NextHop: otherNodeIP, + } + + if vpn { + rd := &bgpapi.RouteDistinguisherTwoOctetASN{} + err := labeledVPNIPAddressPrefixNlri.Rd.UnmarshalTo(rd) + if err != nil { + return errors.Wrap(err, "Error Unmarshalling labeledVPNIPAddressPrefixNlri.Rd") + } + cn.Vni = rd.Assigned + } + + if path.IsWithdraw { + common.SendEvent(common.CalicoVppEvent{ + Type: common.ConnectivityDeleted, + Old: cn, + }) + } else { + common.SendEvent(common.CalicoVppEvent{ + Type: common.ConnectivityAdded, + New: cn, + }) + } + return nil +} + +func (h *BGPHandler) getSRPolicy(path *bgpapi.Path) (srv6Policy *types.SrPolicy, srv6tunnel *common.SRv6Tunnel, srnrli *bgpapi.SRPolicyNLRI, err error) { + srnrli = &bgpapi.SRPolicyNLRI{} + tun := &bgpapi.TunnelEncapAttribute{} + subTLVSegList := &bgpapi.TunnelEncapSubTLVSRSegmentList{} + segments := []*bgpapi.SegmentTypeB{} + srv6bsid := &bgpapi.SRBindingSID{} + srv6tunnel = &common.SRv6Tunnel{} + + if err := path.Nlri.UnmarshalTo(srnrli); err != nil { + return nil, nil, nil, err + } + srv6tunnel.Dst = net.IP(srnrli.Endpoint) + + for _, pattr := range path.Pattrs { + if err := pattr.UnmarshalTo(tun); err == nil { + for _, tlv := range tun.Tlvs { + // unmarshal Tlvs + for _, innerTlv := range tlv.Tlvs { + // search for TunnelEncapSubTLVSRSegmentList + if err := innerTlv.UnmarshalTo(subTLVSegList); err == nil { + for _, seglist := range subTLVSegList.Segments { + segment := &bgpapi.SegmentTypeB{} + if err = seglist.UnmarshalTo(segment); err == nil { + segments = append(segments, segment) + } + } + } + // search for TunnelEncapSubTLVSRBindingSID + srbsids := &anypb.Any{} + if err := innerTlv.UnmarshalTo(srbsids); err == nil { + h.log.Debugf("getSRPolicy TunnelEncapSubTLVSRBindingSID") + if err := srbsids.UnmarshalTo(srv6bsid); err != nil { + return nil, nil, nil, err + } + } + + // search for TunnelEncapSubTLVSRPriority + subTLVSRPriority := &bgpapi.TunnelEncapSubTLVSRPriority{} + if err := innerTlv.UnmarshalTo(subTLVSRPriority); err == nil { + h.log.Debugf("getSRPolicyPriority TunnelEncapSubTLVSRPriority") + srv6tunnel.Priority = subTLVSRPriority.Priority + } + } + } + } + } + + policySidListsids := [16]ip_types.IP6Address{} + for i, segment := range segments { + policySidListsids[i] = types.ToVppIP6Address(net.IP(segment.Sid)) + } + srv6Policy = &types.SrPolicy{ + Bsid: types.ToVppIP6Address(net.IP(srv6bsid.Sid)), + IsSpray: false, + IsEncap: true, + FibTable: 0, + SidLists: []types.Srv6SidList{{ + NumSids: uint8(len(segments)), + Weight: 1, + Sids: policySidListsids, + }}, + } + srv6tunnel.Bsid = srv6Policy.Bsid.ToIP() + srv6tunnel.Policy = srv6Policy + + srv6tunnel.Behavior = uint8(segments[len(segments)-1].GetEndpointBehaviorStructure().Behavior) + + return srv6Policy, srv6tunnel, srnrli, err +} + +func (h *BGPHandler) InjectSRv6Policy(path *bgpapi.Path) error { + _, srv6tunnel, srnrli, err := h.getSRPolicy(path) + + if err != nil { + return errors.Wrap(err, "error injectSRv6Policy") + } + + cn := &common.NodeConnectivity{ + Dst: net.IPNet{}, + NextHop: srnrli.Endpoint, + ResolvedProvider: "", + Custom: srv6tunnel, + } + + if path.IsWithdraw { + common.SendEvent(common.CalicoVppEvent{ + Type: common.SRv6PolicyDeleted, + Old: cn, + }) + } else { + common.SendEvent(common.CalicoVppEvent{ + Type: common.SRv6PolicyAdded, + New: cn, + }) + } + return nil +} + +// NewBGPPolicyV4V6 creates BGP policy for IPv4/IPv6 CIDR filtering +func (h *BGPHandler) NewBGPPolicyV4V6(CIDR string, matchOperator calicov3.BGPFilterMatchOperator, action calicov3.BGPFilterAction) (*bgpapi.DefinedSet, bgpapi.MatchSet_Type, bgpapi.RouteAction, string, error) { + routeAction := bgpapi.RouteAction_ACCEPT + if action == calicov3.Reject { + routeAction = bgpapi.RouteAction_REJECT + } else if action != calicov3.Accept { + return nil, 0, 0, "", errors.Errorf("error creating new bgp policy: action %s not supported", action) + } + + var matchSetType bgpapi.MatchSet_Type + var minMask, maxMask uint32 + if matchOperator == calicov3.In || matchOperator == calicov3.NotIn { + _, subnet, err := net.ParseCIDR(CIDR) + if err != nil { + return nil, 0, 0, "", errors.Wrap(err, "error creating new bgp policy") + } + ones, bits := subnet.Mask.Size() + minMask = uint32(ones) + maxMask = uint32(bits) + if matchOperator == calicov3.In { + matchSetType = bgpapi.MatchSet_ANY // any and all are same in our case as we have only one member of the defined set + } else { + matchSetType = bgpapi.MatchSet_INVERT + } + } else { + // mask is zero + if matchOperator == calicov3.Equal { + matchSetType = bgpapi.MatchSet_ANY + } else { + matchSetType = bgpapi.MatchSet_INVERT + } + } + + prefixName := CIDR + "prefix" + fmt.Sprint(minMask) + fmt.Sprint(maxMask) // this name should be unique + defset := &bgpapi.DefinedSet{ + DefinedType: bgpapi.DefinedType_PREFIX, + Name: prefixName, + Prefixes: []*bgpapi.Prefix{{IpPrefix: CIDR, MaskLengthMin: minMask, MaskLengthMax: maxMask}}, + } + return defset, matchSetType, routeAction, prefixName, nil +} + +// addStatementToPolicy adds a statement to a BGP policy +func (h *BGPHandler) addStatementToPolicy(pol *bgpapi.Policy, routeAction bgpapi.RouteAction, neighborName string, prefixName string, matchSetType bgpapi.MatchSet_Type) { + pol.Statements = append(pol.Statements, + &bgpapi.Statement{ + Actions: &bgpapi.Actions{ + RouteAction: routeAction, + }, + Conditions: &bgpapi.Conditions{ + NeighborSet: &bgpapi.MatchSet{ + Name: neighborName, + Type: bgpapi.MatchSet_ANY, + }, + PrefixSet: &bgpapi.MatchSet{ + Name: prefixName, + Type: matchSetType, + }, + }, + }, + ) +} + +// NewBGPPolicyAndAssignment creates BGP policy and assignment from filter rules +func (h *BGPHandler) NewBGPPolicyAndAssignment(name string, rulesv4 []calicov3.BGPFilterRuleV4, rulesv6 []calicov3.BGPFilterRuleV6, neighborName string, dir bgpapi.PolicyDirection) (*BGPPrefixesPolicyAndAssignment, error) { + pol := &bgpapi.Policy{Name: name} + prefixes := []*bgpapi.DefinedSet{} + + for _, rule := range rulesv6 { + defset, matchSetType, routeAction, prefixName, err := h.NewBGPPolicyV4V6(rule.CIDR, rule.MatchOperator, rule.Action) + if err != nil { + return nil, err + } + prefixes = append(prefixes, defset) + h.addStatementToPolicy(pol, routeAction, neighborName, prefixName, matchSetType) + } + + for _, rule := range rulesv4 { + defset, matchSetType, routeAction, prefixName, err := h.NewBGPPolicyV4V6(rule.CIDR, rule.MatchOperator, rule.Action) + if err != nil { + return nil, err + } + prefixes = append(prefixes, defset) + h.addStatementToPolicy(pol, routeAction, neighborName, prefixName, matchSetType) + } + + PA := &bgpapi.PolicyAssignment{ + Name: "global", + Direction: dir, + Policies: []*bgpapi.Policy{pol}, + DefaultAction: bgpapi.RouteAction_ACCEPT, + } + return &BGPPrefixesPolicyAndAssignment{PolicyAssignment: PA, Policy: pol, Prefixes: prefixes}, nil +} + +// filterPeer creates policies in gobgp representing bgpfilters for the peer +func (h *BGPHandler) filterPeer(peerAddress string, filterNames []string) (map[string]*ImpExpPol, error) { + BGPPolicies := make(map[string]*ImpExpPol) + if len(filterNames) != 0 { + h.log.Infof("Peer: (neighbor=%s) has filters, applying filters %s ...", peerAddress, filterNames) + for _, filterName := range filterNames { + _, ok := h.bgpFilters[filterName] + if !ok { + h.log.Warnf("peer (neighbor=%s) uses filter %s that does not exist yet", peerAddress, filterName) + // save state for late filter creation + BGPPolicies[filterName] = nil + } else { + impExpPol, err := h.createFilterPolicy(peerAddress, filterName, peerAddress+"neighbor") + if err != nil { + return nil, errors.Wrapf(err, "error creating filter policy") + } + BGPPolicies[filterName] = impExpPol + } + } + } + return BGPPolicies, nil +} + +// createFilterPolicy creates policies in gobgp using filter prefix and neighbor +func (h *BGPHandler) createFilterPolicy(peerAddress string, filterName string, neighborSet string) (*ImpExpPol, error) { + h.log.Infof("Creating policies for: (peer: %s, filter: %s, neighborSet: %s)", peerAddress, filterName, neighborSet) + filter := h.bgpFilters[filterName] + imppol, err := h.NewBGPPolicyAndAssignment("import-"+peerAddress+"-"+filterName, filter.Spec.ImportV4, filter.Spec.ImportV6, neighborSet, bgpapi.PolicyDirection_IMPORT) + if err != nil { + return nil, err + } + exppol, err := h.NewBGPPolicyAndAssignment("export-"+peerAddress+"-"+filterName, filter.Spec.ExportV4, filter.Spec.ExportV6, neighborSet, bgpapi.PolicyDirection_EXPORT) + if err != nil { + return nil, err + } + + for _, pol := range []*BGPPrefixesPolicyAndAssignment{imppol, exppol} { + for _, defset := range pol.Prefixes { + err := h.BGPServer.AddDefinedSet(context.Background(), &bgpapi.AddDefinedSetRequest{ + DefinedSet: defset, + }) + if err != nil { + return nil, err + } + } + err = h.BGPServer.AddPolicy(context.Background(), &bgpapi.AddPolicyRequest{Policy: pol.Policy}) + if err != nil { + return nil, errors.Wrapf(err, "error adding policy") + } + err = h.BGPServer.AddPolicyAssignment(context.Background(), &bgpapi.AddPolicyAssignmentRequest{Assignment: pol.PolicyAssignment}) + if err != nil { + return nil, errors.Wrapf(err, "error adding policy assignment") + } + } + return &ImpExpPol{Imp: imppol, Exp: exppol}, nil +} + +// deleteFilterPolicy deletes policies and their assignments in gobgp +func (h *BGPHandler) deleteFilterPolicy(impExpPol *ImpExpPol) error { + for _, pol := range []*BGPPrefixesPolicyAndAssignment{impExpPol.Imp, impExpPol.Exp} { + err := h.BGPServer.DeletePolicyAssignment(context.Background(), &bgpapi.DeletePolicyAssignmentRequest{Assignment: pol.PolicyAssignment}) + if err != nil { + return errors.Wrapf(err, "error deleting policy assignment") + } + err = h.BGPServer.DeletePolicy(context.Background(), &bgpapi.DeletePolicyRequest{Policy: pol.Policy, All: true}) + if err != nil { + return errors.Wrapf(err, "error deleting policy assignment") + } + for _, defset := range pol.Prefixes { + err = h.BGPServer.DeleteDefinedSet(context.Background(), &bgpapi.DeleteDefinedSetRequest{DefinedSet: defset, All: true}) + if err != nil { + return errors.Wrapf(err, "error deleting prefix set") + } + } + } + return nil +} + +// cleanUpPeerFilters cleans up policies for a particular peer, from gobgp and saved state +func (h *BGPHandler) cleanUpPeerFilters(peerAddr string) error { + polToDelete := []string{} + for name, impExpPol := range h.bgpPeers[peerAddr].BGPPolicies { + h.log.Infof("deleting filter: %s", name) + err := h.deleteFilterPolicy(impExpPol) + if err != nil { + return errors.Wrapf(err, "error deleting filter policies") + } + polToDelete = append(polToDelete, name) + } + for _, name := range polToDelete { + delete(h.bgpPeers[peerAddr].BGPPolicies, name) + } + return nil +} + +// createEmptyPrefixSet creates an empty prefix set for BGP policies +func (h *BGPHandler) createEmptyPrefixSet(name string) error { + ps := &bgpapi.DefinedSet{ + DefinedType: bgpapi.DefinedType_PREFIX, + Name: name, + } + err := h.BGPServer.AddDefinedSet( + context.Background(), + &bgpapi.AddDefinedSetRequest{DefinedSet: ps}, + ) + if err != nil { + return errors.Wrapf(err, "error creating prefix set %s", name) + } + return nil +} + +// InitialPolicySetting initializes BGP export policy. +// this creates two prefix-sets named 'aggregated' and 'host'. +// A route is allowed to be exported when it matches with 'aggregated' set, +// and not allowed when it matches with 'host' set. +func (h *BGPHandler) InitialPolicySetting(isv6 bool) error { + aggregatedPrefixSetName := common.GetAggPrefixSetName(isv6) + hostPrefixSetName := common.GetHostPrefixSetName(isv6) + err := h.createEmptyPrefixSet(aggregatedPrefixSetName) + if err != nil { + return err + } + err = h.createEmptyPrefixSet(hostPrefixSetName) + if err != nil { + return err + } + // intended to work as same as 'calico_pools' export filter of BIRD configuration + definition := &bgpapi.Policy{ + Name: common.GetPolicyName(isv6), + Statements: []*bgpapi.Statement{ + { + Conditions: &bgpapi.Conditions{ + PrefixSet: &bgpapi.MatchSet{ + Type: bgpapi.MatchSet_ANY, + Name: aggregatedPrefixSetName, + }, + }, + Actions: &bgpapi.Actions{ + RouteAction: bgpapi.RouteAction_ACCEPT, + }, + }, + { + Conditions: &bgpapi.Conditions{ + PrefixSet: &bgpapi.MatchSet{ + Type: bgpapi.MatchSet_ANY, + Name: hostPrefixSetName, + }, + }, + Actions: &bgpapi.Actions{ + RouteAction: bgpapi.RouteAction_REJECT, + }, + }, + }, + } + + err = h.BGPServer.AddPolicy( + context.Background(), + &bgpapi.AddPolicyRequest{ + Policy: definition, + ReferExistingStatements: false, + }, + ) + if err != nil { + return errors.Wrap(err, "error adding policy") + } + err = h.BGPServer.AddPolicyAssignment( + context.Background(), + &bgpapi.AddPolicyAssignmentRequest{ + Assignment: &bgpapi.PolicyAssignment{ + Name: "global", + Direction: bgpapi.PolicyDirection_EXPORT, + Policies: []*bgpapi.Policy{definition}, + DefaultAction: bgpapi.RouteAction_ACCEPT, + }, + }) + if err != nil { + return errors.Wrap(err, "cannot add policy assignment") + } + return nil +} + +// HandleBGPPeerAdded handles BGP peer addition directly +func (h *BGPHandler) HandleBGPPeerAdded(localPeer *LocalBGPPeer) error { + h.log.Debugf("BGP handler processing BGP peer added") + peer := localPeer.Peer + filters := localPeer.BGPFilterNames + + // create a neighbor set to apply filter only on specific peer using a global policy + neighborSet := &bgpapi.DefinedSet{ + Name: peer.Conf.NeighborAddress + "neighbor", + DefinedType: bgpapi.DefinedType_NEIGHBOR, + List: []string{peer.Conf.NeighborAddress + "/32"}, + } + err := h.BGPServer.AddDefinedSet(context.Background(), &bgpapi.AddDefinedSetRequest{ + DefinedSet: neighborSet, + }) + if err != nil { + return errors.Wrapf(err, "error creating neighbor set") + } + + BGPPolicies, err := h.filterPeer(peer.Conf.NeighborAddress, filters) + if err != nil { + return errors.Wrapf(err, "error filtering peer") + } + + h.log.Infof("bgp(add) new neighbor=%s AS=%d", peer.Conf.NeighborAddress, peer.Conf.PeerAsn) + err = h.BGPServer.AddPeer( + context.Background(), + &bgpapi.AddPeerRequest{Peer: peer}, + ) + if err != nil { + return err + } + + localPeer.BGPPolicies = BGPPolicies + localPeer.NeighborSet = neighborSet + h.bgpPeers[peer.Conf.NeighborAddress] = localPeer + return nil +} + +// HandleBGPPeerUpdated handles BGP peer updates directly +func (h *BGPHandler) HandleBGPPeerUpdated(localPeer *LocalBGPPeer, oldPeer *LocalBGPPeer) error { + h.log.Debugf("BGP handler processing BGP peer updated") + peer := localPeer.Peer + filters := localPeer.BGPFilterNames + h.log.Infof("bgp(upd) neighbor=%s", peer.Conf.NeighborAddress) + + var BGPPolicies map[string]*ImpExpPol + if !CompareStringSlices(localPeer.BGPFilterNames, oldPeer.BGPFilterNames) { // update filters + err := h.cleanUpPeerFilters(peer.Conf.NeighborAddress) + if err != nil { + return errors.Wrapf(err, "error cleaning peer filters up") + } + BGPPolicies, err = h.filterPeer(peer.Conf.NeighborAddress, filters) + if err != nil { + return errors.Wrapf(err, "error filtering peer") + } + } + + h.log.Infof("bgp(upd) neighbor=%s AS=%d", peer.Conf.NeighborAddress, peer.Conf.PeerAsn) + _, err := h.BGPServer.UpdatePeer( + context.Background(), + &bgpapi.UpdatePeerRequest{Peer: peer}, + ) + if err != nil { + return err + } + + localPeer.BGPPolicies = BGPPolicies + h.bgpPeers[peer.Conf.NeighborAddress] = localPeer + return nil +} + +// HandleBGPPeerDeleted handles BGP peer deletion directly +func (h *BGPHandler) HandleBGPPeerDeleted(peerIP string) error { + h.log.Debugf("BGP handler processing BGP peer deleted") + localPeer, found := h.bgpPeers[peerIP] + if !found { + h.log.Warnf("BGP peer %s not found for deletion", peerIP) + return nil + } + + err := h.BGPServer.DeletePeer(context.Background(), &bgpapi.DeletePeerRequest{ + Address: peerIP, + }) + if err != nil { + return errors.Wrapf(err, "error deleting BGP peer %s", peerIP) + } + + // Clean up filters and neighbor set + err = h.cleanUpPeerFilters(peerIP) + if err != nil { + h.log.Warnf("Error cleaning up peer filters for %s: %v", peerIP, err) + } + + if localPeer.NeighborSet != nil { + err = h.BGPServer.DeleteDefinedSet(context.Background(), &bgpapi.DeleteDefinedSetRequest{ + DefinedSet: localPeer.NeighborSet, + }) + if err != nil { + h.log.Warnf("Error deleting neighbor set for %s: %v", peerIP, err) + } + } + + delete(h.bgpPeers, peerIP) + return nil +} + +// HandleBGPFilterAddedOrUpdated handles BGP filter addition or update directly +func (h *BGPHandler) HandleBGPFilterAddedOrUpdated(filter calicov3.BGPFilter) error { + h.log.Infof("bgp(add/upd) filter: %s", filter.Name) + h.bgpFilters[filter.Name] = &filter + + // If this filter is already used in gobgp, delete old policies if any and recreate them + for peerAddress := range h.bgpPeers { + if impExpPol, ok := h.bgpPeers[peerAddress].BGPPolicies[filter.Name]; ok { + h.log.Infof("filter used in %s, updating filter", peerAddress) + if impExpPol != nil { + err := h.deleteFilterPolicy(impExpPol) + if err != nil { + return errors.Wrap(err, "error deleting filter policies") + } + } // else we received peer using a filter before receiving the filter, so just create it + + impExpPol, err := h.createFilterPolicy(peerAddress, filter.Name, peerAddress+"neighbor") + if err != nil { + return errors.Wrapf(err, "error creating filter policy") + } + h.bgpPeers[peerAddress].BGPPolicies[filter.Name] = impExpPol + + // have to update peers to apply changes + _, err2 := h.BGPServer.UpdatePeer( + context.Background(), + &bgpapi.UpdatePeerRequest{Peer: h.bgpPeers[peerAddress].Peer}, + ) + if err2 != nil { + return errors.Wrapf(err2, "error updating peer %s", peerAddress) + } + } + } + return nil +} + +// HandleBGPFilterDeleted handles BGP filter deletion directly +func (h *BGPHandler) HandleBGPFilterDeleted(filter calicov3.BGPFilter) error { + h.log.Infof("bgp(del) filter deleted: %s", filter.Name) + delete(h.bgpFilters, filter.Name) + return nil +} + +// HandleBGPDefinedSetAdded handles BGP defined set addition directly +func (h *BGPHandler) HandleBGPDefinedSetAdded(definedSet *bgpapi.DefinedSet) error { + h.log.Debugf("BGP handler processing defined set added") + err := h.BGPServer.AddDefinedSet( + context.Background(), + &bgpapi.AddDefinedSetRequest{DefinedSet: definedSet}, + ) + if err != nil { + return err + } + return nil +} + +// HandleBGPDefinedSetDeleted handles BGP defined set deletion directly +func (h *BGPHandler) HandleBGPDefinedSetDeleted(definedSet *bgpapi.DefinedSet) error { + h.log.Debugf("BGP handler processing defined set deleted") + err := h.BGPServer.DeleteDefinedSet( + context.Background(), + &bgpapi.DeleteDefinedSetRequest{DefinedSet: definedSet, All: false}, + ) + if err != nil { + return err + } + return nil +} + +// HandleBGPPathAdded handles BGP path addition +func (h *BGPHandler) HandleBGPPathAdded(path *bgpapi.Path) error { + h.log.Debugf("BGP handler processing BGP path added") + _, err := h.BGPServer.AddPath(context.Background(), &bgpapi.AddPathRequest{ + Path: path, + }) + if err != nil { + return errors.Wrap(err, "error adding BGP path") + } + return nil +} + +// HandleBGPPathDeleted handles BGP path deletion +func (h *BGPHandler) HandleBGPPathDeleted(path *bgpapi.Path) error { + h.log.Debugf("BGP handler processing BGP path deleted") + err := h.BGPServer.DeletePath(context.Background(), &bgpapi.DeletePathRequest{ + Path: path, + }) + if err != nil { + return errors.Wrap(err, "error deleting BGP path") + } + return nil +} diff --git a/calico-vpp-agent/routing/peer_handler.go b/calico-vpp-agent/felix/routing/peer_handler.go similarity index 98% rename from calico-vpp-agent/routing/peer_handler.go rename to calico-vpp-agent/felix/routing/peer_handler.go index 272cb1603..a8fba6bbe 100644 --- a/calico-vpp-agent/routing/peer_handler.go +++ b/calico-vpp-agent/felix/routing/peer_handler.go @@ -28,7 +28,6 @@ import ( "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/common" "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/felix/cache" - "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/watchers" "github.com/projectcalico/vpp-dataplane/v3/config" ) @@ -252,7 +251,7 @@ func (h *PeerHandler) addBGPPeer(ip string, asn uint32, peerSpec *calicov3.BGPPe } common.SendEvent(common.CalicoVppEvent{ Type: common.BGPPeerAdded, - New: &watchers.LocalBGPPeer{Peer: peer, BGPFilterNames: peerSpec.Filters}, + New: &LocalBGPPeer{Peer: peer, BGPFilterNames: peerSpec.Filters}, }) return nil } @@ -264,8 +263,8 @@ func (h *PeerHandler) updateBGPPeer(ip string, asn uint32, peerSpec, oldPeerSpec } common.SendEvent(common.CalicoVppEvent{ Type: common.BGPPeerUpdated, - New: &watchers.LocalBGPPeer{Peer: peer, BGPFilterNames: peerSpec.Filters}, - Old: &watchers.LocalBGPPeer{BGPFilterNames: oldPeerSpec.Filters}, + New: &LocalBGPPeer{Peer: peer, BGPFilterNames: peerSpec.Filters}, + Old: &LocalBGPPeer{BGPFilterNames: oldPeerSpec.Filters}, }) return nil } @@ -273,7 +272,7 @@ func (h *PeerHandler) updateBGPPeer(ip string, asn uint32, peerSpec, oldPeerSpec func (h *PeerHandler) deleteBGPPeer(ip string) error { common.SendEvent(common.CalicoVppEvent{ Type: common.BGPPeerDeleted, - New: ip, + Old: ip, }) return nil } diff --git a/calico-vpp-agent/felix/routing/routing_handler.go b/calico-vpp-agent/felix/routing/routing_handler.go new file mode 100644 index 000000000..a77f2e222 --- /dev/null +++ b/calico-vpp-agent/felix/routing/routing_handler.go @@ -0,0 +1,172 @@ +// Copyright (C) 2025 Cisco Systems Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package routing + +import ( + "net" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" + "gopkg.in/tomb.v2" + + "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/common" + "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/felix/cache" + "github.com/projectcalico/vpp-dataplane/v3/vpplink" +) + +const ( + NetLinkRouteProtocolGoBGP = 0x11 +) + +type localAddress struct { + ipNet *net.IPNet + vni uint32 +} + +type RoutingHandler struct { + log *logrus.Entry + vpp *vpplink.VppLink + cache *cache.Cache + + localAddressMap map[string]localAddress + + nodeBGPSpec *common.LocalNodeSpec +} + +func NewRoutingHandler(vpp *vpplink.VppLink, cache *cache.Cache, log *logrus.Entry) *RoutingHandler { + handler := &RoutingHandler{ + log: log, + vpp: vpp, + cache: cache, + localAddressMap: make(map[string]localAddress), + } + + return handler +} + +func (h *RoutingHandler) SetOurBGPSpec(nodeBGPSpec *common.LocalNodeSpec) { + h.nodeBGPSpec = nodeBGPSpec +} + +func (h *RoutingHandler) cleanUpRoutes() error { + h.log.Tracef("Clean up injected routes") + filter := &netlink.Route{ + Protocol: NetLinkRouteProtocolGoBGP, + } + list4, err := netlink.RouteListFiltered(netlink.FAMILY_V4, filter, netlink.RT_FILTER_PROTOCOL) + if err != nil { + return err + } + list6, err := netlink.RouteListFiltered(netlink.FAMILY_V6, filter, netlink.RT_FILTER_PROTOCOL) + if err != nil { + return err + } + for _, route := range append(list4, list6...) { + err = netlink.RouteDel(&route) + if err != nil { + return err + } + } + return nil +} + +// AnnounceLocalAddress announces a local address to BGP +func (h *RoutingHandler) AnnounceLocalAddress(addr *net.IPNet, vni uint32) error { + h.log.Debugf("Announcing prefix %s in BGP", addr.String()) + nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(h.nodeBGPSpec) + path, err := common.MakePath(addr.String(), false /* isWithdrawal */, nodeIP4, nodeIP6, vni, uint32(*h.cache.BGPConf.ASNumber)) + if err != nil { + return errors.Wrap(err, "error making path to announce") + } + h.localAddressMap[addr.String()] = localAddress{ipNet: addr, vni: vni} + + // Send BGP path event + common.SendEvent(common.CalicoVppEvent{ + Type: common.BGPPathAdded, + New: path, + }) + return nil +} + +// WithdrawLocalAddress withdraws a local address from BGP +func (h *RoutingHandler) WithdrawLocalAddress(addr *net.IPNet, vni uint32) error { + h.log.Debugf("Withdrawing prefix %s from BGP", addr.String()) + nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(h.nodeBGPSpec) + path, err := common.MakePath(addr.String(), true /* isWithdrawal */, nodeIP4, nodeIP6, vni, uint32(*h.cache.BGPConf.ASNumber)) + if err != nil { + return errors.Wrap(err, "error making path to withdraw") + } + delete(h.localAddressMap, addr.String()) + + // Send BGP path event + common.SendEvent(common.CalicoVppEvent{ + Type: common.BGPPathDeleted, + Old: path, + }) + return nil +} + +func (h *RoutingHandler) RestoreLocalAddresses() { + for _, localAddr := range h.localAddressMap { + err := h.AnnounceLocalAddress(localAddr.ipNet, localAddr.vni) + if err != nil { + h.log.Errorf("Local address %s restore failed : %+v", localAddr.ipNet.String(), err) + } + } +} + +// Configure SNAT prefixes so that we don't snat traffic going from a local pod to the node +func (h *RoutingHandler) configureLocalNodeSnat() error { + nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(h.nodeBGPSpec) + if nodeIP4 != nil { + err := h.vpp.CnatAddDelSnatPrefix(common.ToMaxLenCIDR(*nodeIP4), true) + if err != nil { + return errors.Wrapf(err, "error configuring snat prefix for current node (%v)", *nodeIP4) + } + } + if nodeIP6 != nil { + err := h.vpp.CnatAddDelSnatPrefix(common.ToMaxLenCIDR(*nodeIP6), true) + if err != nil { + return errors.Wrapf(err, "error configuring snat prefix for current node (%v)", *nodeIP6) + } + } + return nil +} + +func (h *RoutingHandler) ServeRoutingHandler(t *tomb.Tomb) (err error) { + h.log.Infof("Routing handler started") + + /* Clean up any routes we may have injected in previous runs */ + err = h.cleanUpRoutes() + if err != nil { + return errors.Wrap(err, "failed to clean up previously injected routes") + } + + err = h.configureLocalNodeSnat() + if err != nil { + return errors.Wrap(err, "cannot configure node snat") + } + + /* Restore the previous config in case we restarted */ + h.RestoreLocalAddresses() + + h.log.Infof("Routing handler is running") + + <-t.Dying() + h.log.Infof("Routing handler asked to stop") + return nil +} diff --git a/calico-vpp-agent/routing/bgp_watcher.go b/calico-vpp-agent/routing/bgp_watcher.go deleted file mode 100644 index 9a6d628bb..000000000 --- a/calico-vpp-agent/routing/bgp_watcher.go +++ /dev/null @@ -1,681 +0,0 @@ -// Copyright (C) 2020 Cisco Systems Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package routing - -import ( - "fmt" - "net" - - bgpapi "github.com/osrg/gobgp/v3/api" - "github.com/pkg/errors" - "golang.org/x/net/context" - "google.golang.org/protobuf/types/known/anypb" - "gopkg.in/tomb.v2" - - calicov3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/common" - "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/felix/cni" - "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/watchers" - "github.com/projectcalico/vpp-dataplane/v3/config" - "github.com/projectcalico/vpp-dataplane/v3/vpplink/generated/bindings/ip_types" - "github.com/projectcalico/vpp-dataplane/v3/vpplink/types" - - // needed for GoBGP building (in ../Makefile, gobgp target) - _ "github.com/inconshreveable/mousetrap" - _ "github.com/spf13/cobra" -) - -func (s *Server) getNexthop(path *bgpapi.Path) string { - for _, attr := range path.Pattrs { - nhAttr := &bgpapi.NextHopAttribute{} - mpReachAttr := &bgpapi.MpReachNLRIAttribute{} - if err := attr.UnmarshalTo(nhAttr); err == nil { - return nhAttr.NextHop - } - if err := attr.UnmarshalTo(mpReachAttr); err == nil { - if len(mpReachAttr.NextHops) != 1 { - s.log.Fatalf("Cannot process more than one Nlri in path attributes: %+v", mpReachAttr) - } - return mpReachAttr.NextHops[0] - } - } - return "" -} - -// injectRoute is a helper function to inject BGP routes to VPP -// TODO: multipath support -func (s *Server) injectRoute(path *bgpapi.Path) error { - var dst net.IPNet - ipAddrPrefixNlri := &bgpapi.IPAddressPrefix{} - labeledVPNIPAddressPrefixNlri := &bgpapi.LabeledVPNIPAddressPrefix{} - vpn := false - otherNodeIP := net.ParseIP(s.getNexthop(path)) - if otherNodeIP == nil { - return fmt.Errorf("cannot determine path nexthop: %+v", path) - } - - if err := path.Nlri.UnmarshalTo(ipAddrPrefixNlri); err == nil { - dst.IP = net.ParseIP(ipAddrPrefixNlri.Prefix) - if dst.IP == nil { - return fmt.Errorf("cannot parse nlri addr: %s", ipAddrPrefixNlri.Prefix) - } else if dst.IP.To4() == nil { - dst.Mask = net.CIDRMask(int(ipAddrPrefixNlri.PrefixLen), 128) - } else { - dst.Mask = net.CIDRMask(int(ipAddrPrefixNlri.PrefixLen), 32) - } - } else { - err := path.Nlri.UnmarshalTo(labeledVPNIPAddressPrefixNlri) - if err == nil { - dst.IP = net.ParseIP(labeledVPNIPAddressPrefixNlri.Prefix) - if dst.IP == nil { - return fmt.Errorf("cannot parse nlri addr: %s", labeledVPNIPAddressPrefixNlri.Prefix) - } else if dst.IP.To4() == nil { - dst.Mask = net.CIDRMask(int(labeledVPNIPAddressPrefixNlri.PrefixLen), 128) - } else { - dst.Mask = net.CIDRMask(int(labeledVPNIPAddressPrefixNlri.PrefixLen), 32) - } - vpn = true - } else { - return fmt.Errorf("cannot handle Nlri: %+v", path.Nlri) - } - } - - cn := &common.NodeConnectivity{ - Dst: dst, - NextHop: otherNodeIP, - } - if vpn { - rd := &bgpapi.RouteDistinguisherTwoOctetASN{} - err := labeledVPNIPAddressPrefixNlri.Rd.UnmarshalTo(rd) - if err != nil { - return errors.Wrap(err, "Error Unmarshalling labeledVPNIPAddressPrefixNlri.Rd") - } - cn.Vni = rd.Assigned - } - if path.IsWithdraw { - common.SendEvent(common.CalicoVppEvent{ - Type: common.ConnectivityDeleted, - Old: cn, - }) - } else { - common.SendEvent(common.CalicoVppEvent{ - Type: common.ConnectivityAdded, - New: cn, - }) - } - return nil -} - -func (s *Server) getSRPolicy(path *bgpapi.Path) (srv6Policy *types.SrPolicy, srv6tunnel *common.SRv6Tunnel, srnrli *bgpapi.SRPolicyNLRI, err error) { - srnrli = &bgpapi.SRPolicyNLRI{} - tun := &bgpapi.TunnelEncapAttribute{} - subTLVSegList := &bgpapi.TunnelEncapSubTLVSRSegmentList{} - segments := []*bgpapi.SegmentTypeB{} - srv6bsid := &bgpapi.SRBindingSID{} - srv6tunnel = &common.SRv6Tunnel{} - - if err := path.Nlri.UnmarshalTo(srnrli); err != nil { - return nil, nil, nil, err - } - srv6tunnel.Dst = net.IP(srnrli.Endpoint) - - for _, pattr := range path.Pattrs { - if err := pattr.UnmarshalTo(tun); err == nil { - for _, tlv := range tun.Tlvs { - // unmarshal Tlvs - for _, innerTlv := range tlv.Tlvs { - // search for TunnelEncapSubTLVSRSegmentList - if err := innerTlv.UnmarshalTo(subTLVSegList); err == nil { - for _, seglist := range subTLVSegList.Segments { - segment := &bgpapi.SegmentTypeB{} - if err = seglist.UnmarshalTo(segment); err == nil { - segments = append(segments, segment) - } - } - } - // search for TunnelEncapSubTLVSRBindingSID - srbsids := &anypb.Any{} - if err := innerTlv.UnmarshalTo(srbsids); err == nil { - s.log.Debugf("getSRPolicy TunnelEncapSubTLVSRBindingSID") - if err := srbsids.UnmarshalTo(srv6bsid); err != nil { - return nil, nil, nil, err - } - - } - - // search for TunnelEncapSubTLVSRPriority - subTLVSRPriority := &bgpapi.TunnelEncapSubTLVSRPriority{} - if err := innerTlv.UnmarshalTo(subTLVSRPriority); err == nil { - s.log.Debugf("getSRPolicyPriority TunnelEncapSubTLVSRPriority") - srv6tunnel.Priority = subTLVSRPriority.Priority - } - - } - } - } - - } - - policySidListsids := [16]ip_types.IP6Address{} - for i, segment := range segments { - policySidListsids[i] = types.ToVppIP6Address(net.IP(segment.Sid)) - } - srv6Policy = &types.SrPolicy{ - Bsid: types.ToVppIP6Address(net.IP(srv6bsid.Sid)), - IsSpray: false, - IsEncap: true, - FibTable: 0, - SidLists: []types.Srv6SidList{{ - NumSids: uint8(len(segments)), - Weight: 1, - Sids: policySidListsids, - }}, - } - srv6tunnel.Bsid = srv6Policy.Bsid.ToIP() - srv6tunnel.Policy = srv6Policy - - srv6tunnel.Behavior = uint8(segments[len(segments)-1].GetEndpointBehaviorStructure().Behavior) - - return srv6Policy, srv6tunnel, srnrli, err -} - -func (s *Server) injectSRv6Policy(path *bgpapi.Path) error { - _, srv6tunnel, srnrli, err := s.getSRPolicy(path) - - if err != nil { - return errors.Wrap(err, "error injectSRv6Policy") - } - - cn := &common.NodeConnectivity{ - Dst: net.IPNet{}, - NextHop: srnrli.Endpoint, - ResolvedProvider: "", - Custom: srv6tunnel, - } - if path.IsWithdraw { - common.SendEvent(common.CalicoVppEvent{ - Type: common.SRv6PolicyDeleted, - Old: cn, - }) - } else { - common.SendEvent(common.CalicoVppEvent{ - Type: common.SRv6PolicyAdded, - New: cn, - }) - } - return nil -} - -func (s *Server) startBGPMonitoring() (func(), error) { - nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(s.nodeBGPSpec) - ctx, stopFunc := context.WithCancel(context.Background()) - err := s.BGPServer.WatchEvent(ctx, - &bgpapi.WatchEventRequest{ - Table: &bgpapi.WatchEventRequest_Table{ - Filters: []*bgpapi.WatchEventRequest_Table_Filter{{ - Type: bgpapi.WatchEventRequest_Table_Filter_BEST, - }}, - }, - }, - func(r *bgpapi.WatchEventResponse) { - if table := r.GetTable(); table != nil { - for _, path := range table.GetPaths() { - if path == nil || path.GetFamily() == nil { - s.log.Warnf("nil path update, skipping") - continue - } - if nodeIP4 == nil && path.GetFamily().Afi == bgpapi.Family_AFI_IP { - s.log.Debugf("Ignoring ipv4 path with no node ip4") - continue - } - if nodeIP6 == nil && path.GetFamily().Afi == bgpapi.Family_AFI_IP6 { - s.log.Debugf("Ignoring ipv6 path with no node ip6") - continue - } - if path.GetNeighborIp() == "" || path.GetNeighborIp() == "" { // Weird GoBGP API behaviour - s.log.Debugf("Ignoring internal path") - continue - } - if *config.GetCalicoVppFeatureGates().SRv6Enabled && path.GetFamily() == &common.BgpFamilySRv6IPv6 { - s.log.Debugf("Path SRv6") - err := s.injectSRv6Policy(path) - if err != nil { - s.log.Errorf("cannot inject SRv6: %v", err) - } - continue - } - s.log.Infof("Got path update from=%s as=%d family=%s", path.GetSourceId(), path.GetSourceAsn(), path.GetFamily()) - err := s.injectRoute(path) - if err != nil { - s.log.Errorf("cannot inject route: %v", err) - } - } - } - }, - ) - return stopFunc, err -} - -func (s *Server) NewBGPPolicyV4V6(CIDR string, matchOperator calicov3.BGPFilterMatchOperator, action calicov3.BGPFilterAction) (*bgpapi.DefinedSet, bgpapi.MatchSet_Type, bgpapi.RouteAction, string, error) { - routeAction := bgpapi.RouteAction_ACCEPT - if action == calicov3.Reject { - routeAction = bgpapi.RouteAction_REJECT - } else if action != calicov3.Accept { - return nil, 0, 0, "", errors.Errorf("error creating new bgp policy: action %s not supported", action) - } - - var matchSetType bgpapi.MatchSet_Type - var minMask, maxMask uint32 - if matchOperator == calicov3.In || matchOperator == calicov3.NotIn { - _, subnet, err := net.ParseCIDR(CIDR) - if err != nil { - return nil, 0, 0, "", errors.Wrap(err, "error creating new bgp policy") - } - ones, bits := subnet.Mask.Size() - minMask = uint32(ones) - maxMask = uint32(bits) - if matchOperator == calicov3.In { - matchSetType = bgpapi.MatchSet_ANY // any and all are same in our case as we have only one member of the defined set - } else { - matchSetType = bgpapi.MatchSet_INVERT - } - } else { - // mask is zero - if matchOperator == calicov3.Equal { - matchSetType = bgpapi.MatchSet_ANY - } else { - matchSetType = bgpapi.MatchSet_INVERT - } - } - - prefixName := CIDR + "prefix" + fmt.Sprint(minMask) + fmt.Sprint(maxMask) // this name should be unique - defset := &bgpapi.DefinedSet{ - DefinedType: bgpapi.DefinedType_PREFIX, - Name: prefixName, - Prefixes: []*bgpapi.Prefix{{IpPrefix: CIDR, MaskLengthMin: minMask, MaskLengthMax: maxMask}}, - } - return defset, matchSetType, routeAction, prefixName, nil - -} -func (s *Server) addStatementToPolicy(pol *bgpapi.Policy, routeAction bgpapi.RouteAction, neighborName string, prefixName string, matchSetType bgpapi.MatchSet_Type) { - pol.Statements = append(pol.Statements, - &bgpapi.Statement{ - Actions: &bgpapi.Actions{ - RouteAction: routeAction, - }, - Conditions: &bgpapi.Conditions{ - NeighborSet: &bgpapi.MatchSet{ - Name: neighborName, - Type: bgpapi.MatchSet_ANY, - }, - PrefixSet: &bgpapi.MatchSet{ - Name: prefixName, - Type: matchSetType, - }, - }, - }, - ) -} - -func (s *Server) NewBGPPolicyAndAssignment(name string, rulesv4 []calicov3.BGPFilterRuleV4, rulesv6 []calicov3.BGPFilterRuleV6, neighborName string, dir bgpapi.PolicyDirection) (*watchers.BGPPrefixesPolicyAndAssignment, error) { - pol := &bgpapi.Policy{Name: name} - prefixes := []*bgpapi.DefinedSet{} - for _, rule := range rulesv6 { - defset, matchSetType, routeAction, prefixName, err := s.NewBGPPolicyV4V6(rule.CIDR, rule.MatchOperator, rule.Action) - if err != nil { - return nil, err - } - prefixes = append(prefixes, defset) - s.addStatementToPolicy(pol, routeAction, neighborName, prefixName, matchSetType) - } - for _, rule := range rulesv4 { - defset, matchSetType, routeAction, prefixName, err := s.NewBGPPolicyV4V6(rule.CIDR, rule.MatchOperator, rule.Action) - if err != nil { - return nil, err - } - prefixes = append(prefixes, defset) - s.addStatementToPolicy(pol, routeAction, neighborName, prefixName, matchSetType) - } - PA := &bgpapi.PolicyAssignment{ - Name: "global", - Direction: dir, - Policies: []*bgpapi.Policy{pol}, - DefaultAction: bgpapi.RouteAction_ACCEPT, - } - return &watchers.BGPPrefixesPolicyAndAssignment{PolicyAssignment: PA, Policy: pol, Prefixes: prefixes}, nil -} - -// filterPeer creates policies in gobgp representing bgpfilters for the peer -func (s *Server) filterPeer(peerAddress string, filterNames []string) (map[string]*watchers.ImpExpPol, error) { - BGPPolicies := make(map[string]*watchers.ImpExpPol) - if len(filterNames) != 0 { - s.log.Infof("Peer: (neighbor=%s) has filters, applying filters %s ...", peerAddress, filterNames) - for _, filterName := range filterNames { - _, ok := s.bgpFilters[filterName] - if !ok { - s.log.Warnf("peer (neighbor=%s) uses filter %s that does not exist yet", peerAddress, filterName) - // save state for late filter creation - BGPPolicies[filterName] = nil - } else { - impExpPol, err := s.createFilterPolicy(peerAddress, filterName, peerAddress+"neighbor") - if err != nil { - return nil, errors.Wrapf(err, "error creating filter policy") - } - BGPPolicies[filterName] = impExpPol - } - } - } - return BGPPolicies, nil -} - -// createFilterPolicy creates policies in gobgp using filter prefix and neighbor -func (s *Server) createFilterPolicy(peerAddress string, filterName string, neighborSet string) (*watchers.ImpExpPol, error) { - s.log.Infof("Creating policies for: (peer: %s, filter: %s, neighborSet: %s)", peerAddress, filterName, neighborSet) - filter := s.bgpFilters[filterName] - imppol, err := s.NewBGPPolicyAndAssignment("import-"+peerAddress+"-"+filterName, filter.Spec.ImportV4, filter.Spec.ImportV6, neighborSet, bgpapi.PolicyDirection_IMPORT) - if err != nil { - return nil, err - } - exppol, err := s.NewBGPPolicyAndAssignment("export-"+peerAddress+"-"+filterName, filter.Spec.ExportV4, filter.Spec.ExportV6, neighborSet, bgpapi.PolicyDirection_EXPORT) - if err != nil { - return nil, err - } - for _, pol := range []*watchers.BGPPrefixesPolicyAndAssignment{imppol, exppol} { - for _, defset := range pol.Prefixes { - err := s.BGPServer.AddDefinedSet(context.Background(), &bgpapi.AddDefinedSetRequest{ - DefinedSet: defset, - }) - if err != nil { - return nil, err - } - } - err = s.BGPServer.AddPolicy(context.Background(), &bgpapi.AddPolicyRequest{Policy: pol.Policy}) - if err != nil { - return nil, errors.Wrapf(err, "error adding policy") - } - err = s.BGPServer.AddPolicyAssignment(context.Background(), &bgpapi.AddPolicyAssignmentRequest{Assignment: pol.PolicyAssignment}) - if err != nil { - return nil, errors.Wrapf(err, "error adding policy assignment") - } - } - return &watchers.ImpExpPol{Imp: imppol, Exp: exppol}, nil -} - -// deleteFilterPolicy deletes policies and their assignments in gobgp -func (s *Server) deleteFilterPolicy(impExpPol *watchers.ImpExpPol) error { - for _, pol := range []*watchers.BGPPrefixesPolicyAndAssignment{impExpPol.Imp, impExpPol.Exp} { - err := s.BGPServer.DeletePolicyAssignment(context.Background(), &bgpapi.DeletePolicyAssignmentRequest{Assignment: pol.PolicyAssignment}) - if err != nil { - return errors.Wrapf(err, "error deleting policy assignment") - } - err = s.BGPServer.DeletePolicy(context.Background(), &bgpapi.DeletePolicyRequest{Policy: pol.Policy, All: true}) - if err != nil { - return errors.Wrapf(err, "error deleting policy assignment") - } - for _, defset := range pol.Prefixes { - err = s.BGPServer.DeleteDefinedSet(context.Background(), &bgpapi.DeleteDefinedSetRequest{DefinedSet: defset, All: true}) - if err != nil { - return errors.Wrapf(err, "error deleting prefix set") - } - } - } - return nil -} - -// cleanUpPeerFilters cleans up policies for a particular peer, from gobgp and saved state -func (s *Server) cleanUpPeerFilters(peerAddr string) error { - polToDelete := []string{} - for name, impExpPol := range s.bgpPeers[peerAddr].BGPPolicies { - s.log.Infof("deleting filter: %s", name) - err := s.deleteFilterPolicy(impExpPol) - if err != nil { - return errors.Wrapf(err, "error deleting filter policies") - } - polToDelete = append(polToDelete, name) - } - for _, name := range polToDelete { - delete(s.bgpPeers[peerAddr].BGPPolicies, name) - } - return nil -} - -// watchBGPPath watches BGP routes from other peers and inject them into linux kernel -// TODO: multipath support -func (s *Server) WatchBGPPath(t *tomb.Tomb) error { - stopBGPMonitoring, err := s.startBGPMonitoring() - if err != nil { - return errors.Wrap(err, "error starting BGP monitoring") - } - - for { - select { - case <-t.Dying(): - stopBGPMonitoring() - s.log.Infof("Routing Server asked to stop") - return nil - case msg := <-s.routingServerEventChan: - evt, ok := msg.(common.CalicoVppEvent) - if !ok { - continue - } - /* Note: we will only receive events we ask for when registering the chan */ - switch evt.Type { - case common.LocalPodAddressAdded: - networkPod, ok := evt.New.(cni.NetworkPod) - if !ok { - return fmt.Errorf("evt.New is not a (cni.NetworkPod) %v", evt.New) - } - err := s.announceLocalAddress(networkPod.ContainerIP, networkPod.NetworkVni) - if err != nil { - return err - } - case common.LocalPodAddressDeleted: - networkPod, ok := evt.Old.(cni.NetworkPod) - if !ok { - return fmt.Errorf("evt.Old is not a (cni.NetworkPod) %v", evt.Old) - } - err := s.withdrawLocalAddress(networkPod.ContainerIP, networkPod.NetworkVni) - if err != nil { - return err - } - case common.BGPPathAdded: - path, ok := evt.New.(*bgpapi.Path) - if !ok { - return fmt.Errorf("evt.New is not a (*bgpapi.Path) %v", evt.New) - } - _, err = s.BGPServer.AddPath(context.Background(), &bgpapi.AddPathRequest{ - TableType: bgpapi.TableType_GLOBAL, - Path: path, - }) - if err != nil { - return err - } - case common.BGPPathDeleted: - path, ok := evt.Old.(*bgpapi.Path) - if !ok { - return fmt.Errorf("evt.Old is not a (*bgpapi.Path) %v", evt.Old) - } - err = s.BGPServer.DeletePath(context.Background(), &bgpapi.DeletePathRequest{ - TableType: bgpapi.TableType_GLOBAL, - Path: path, - }) - if err != nil { - return err - } - case common.BGPDefinedSetAdded: - ps, ok := evt.New.(*bgpapi.DefinedSet) - if !ok { - return fmt.Errorf("evt.New is not a (*bgpapi.DefinedSet) %v", evt.New) - } - err := s.BGPServer.AddDefinedSet( - context.Background(), - &bgpapi.AddDefinedSetRequest{DefinedSet: ps}, - ) - if err != nil { - return err - } - case common.BGPDefinedSetDeleted: - ps, ok := evt.Old.(*bgpapi.DefinedSet) - if !ok { - return fmt.Errorf("evt.Old is not a (*bgpapi.DefinedSet) %v", evt.Old) - } - err := s.BGPServer.DeleteDefinedSet( - context.Background(), - &bgpapi.DeleteDefinedSetRequest{DefinedSet: ps, All: false}, - ) - if err != nil { - return err - } - case common.BGPPeerAdded: - localPeer, ok := evt.New.(*watchers.LocalBGPPeer) - if !ok { - return fmt.Errorf("evt.New is not a (*watchers.LocalBGPPeer) %v", evt.New) - } - peer := localPeer.Peer - filters := localPeer.BGPFilterNames - // create a neighbor set to apply filter only on specific peer using a global policy - neighborSet := &bgpapi.DefinedSet{ - Name: peer.Conf.NeighborAddress + "neighbor", - DefinedType: bgpapi.DefinedType_NEIGHBOR, - List: []string{peer.Conf.NeighborAddress + "/32"}, - } - err := s.BGPServer.AddDefinedSet(context.Background(), &bgpapi.AddDefinedSetRequest{ - DefinedSet: neighborSet, - }) - if err != nil { - return errors.Wrapf(err, "error creating neighbor set") - } - BGPPolicies, err := s.filterPeer(peer.Conf.NeighborAddress, filters) - if err != nil { - return errors.Wrapf(err, "error filetring peer") - } - s.log.Infof("bgp(add) new neighbor=%s AS=%d", - peer.Conf.NeighborAddress, peer.Conf.PeerAsn) - err = s.BGPServer.AddPeer( - context.Background(), - &bgpapi.AddPeerRequest{Peer: peer}, - ) - if err != nil { - return err - } - localPeer.BGPPolicies = BGPPolicies - localPeer.NeighborSet = neighborSet - s.bgpPeers[peer.Conf.NeighborAddress] = localPeer - case common.BGPPeerDeleted: - addr, ok := evt.New.(string) - if !ok { - return fmt.Errorf("evt.New is not a (string) %v", evt.New) - } - s.log.Infof("bgp(del) neighbor=%s", addr) - err = s.cleanUpPeerFilters(addr) - if err != nil { - return errors.Wrapf(err, "error cleaning peer filters up") - } - err = s.BGPServer.DeleteDefinedSet(context.Background(), &bgpapi.DeleteDefinedSetRequest{DefinedSet: s.bgpPeers[addr].NeighborSet, All: true}) - if err != nil { - return errors.Wrapf(err, "error deleting prefix set") - } - err := s.BGPServer.DeletePeer( - context.Background(), - &bgpapi.DeletePeerRequest{Address: addr}, - ) - if err != nil { - return err - } - delete(s.bgpPeers, addr) - case common.BGPPeerUpdated: - oldPeer, ok := evt.Old.(*watchers.LocalBGPPeer) - if !ok { - return fmt.Errorf("evt.Old is not (*watchers.LocalBGPPeer) %v", evt.Old) - } - localPeer, ok := evt.New.(*watchers.LocalBGPPeer) - if !ok { - return fmt.Errorf("evt.New is not (*watchers.LocalBGPPeer %v", evt.New) - } - peer := localPeer.Peer - filters := localPeer.BGPFilterNames - s.log.Infof("bgp(upd) neighbor=%s", peer.Conf.NeighborAddress) - var BGPPolicies map[string]*watchers.ImpExpPol - if !watchers.CompareStringSlices(localPeer.BGPFilterNames, oldPeer.BGPFilterNames) { // update filters - err = s.cleanUpPeerFilters(peer.Conf.NeighborAddress) - if err != nil { - return errors.Wrapf(err, "error cleaning peer filters up") - } - BGPPolicies, err = s.filterPeer(peer.Conf.NeighborAddress, filters) - if err != nil { - return errors.Wrapf(err, "error filetring peer") - } - } - s.log.Infof("bgp(upd) neighbor=%s AS=%d", - peer.Conf.NeighborAddress, peer.Conf.PeerAsn) - _, err = s.BGPServer.UpdatePeer( - context.Background(), - &bgpapi.UpdatePeerRequest{Peer: peer}, - ) - if err != nil { - return err - } - localPeer.BGPPolicies = BGPPolicies - s.bgpPeers[peer.Conf.NeighborAddress] = localPeer - case common.BGPFilterAddedOrUpdated: - filter, ok := evt.New.(calicov3.BGPFilter) - if !ok { - return fmt.Errorf("evt.New is not (calicov3.BGPFilter) %v", evt.New) - } - s.log.Infof("bgp(add/upd) filter: %s", filter.Name) - s.bgpFilters[filter.Name] = &filter - // If this filter is already used in gobgp, delete old policies if any and recreate them - for peerAddress := range s.bgpPeers { - if impExpPol, ok := s.bgpPeers[peerAddress].BGPPolicies[filter.Name]; ok { - s.log.Infof("filter used in %s, updating filter", peerAddress) - if impExpPol != nil { - err := s.deleteFilterPolicy(impExpPol) - if err != nil { - return errors.Wrap(err, "error deleting filter policies") - } - } // else we received peer using a filter before receiving the filter, so just create it - impExpPol, err := s.createFilterPolicy(peerAddress, filter.Name, peerAddress+"neighbor") - if err != nil { - return errors.Wrap(err, "error creating filters") - } - s.bgpPeers[peerAddress].BGPPolicies[filter.Name] = impExpPol - // have to update peers to apply changes - _, err = s.BGPServer.UpdatePeer( - context.Background(), - &bgpapi.UpdatePeerRequest{Peer: s.bgpPeers[peerAddress].Peer}, - ) - if err != nil { - return errors.Wrapf(err, "error updating peer %s", peerAddress) - } - } - } - case common.BGPFilterDeleted: // supposed to rely on user to never delete a used bgpfilter - filter, ok := evt.Old.(calicov3.BGPFilter) - if !ok { - return fmt.Errorf("evt.Old is not (calicov3.BGPFilter) %v", evt.Old) - } - s.log.Infof("bgp(del) filter deleted: %s", filter.Name) - delete(s.bgpFilters, filter.Name) - case common.PeerNodeStateChanged: - old, _ := evt.Old.(*common.LocalNodeSpec) - new, _ := evt.New.(*common.LocalNodeSpec) - if s.peerHandler != nil { - s.peerHandler.OnPeerNodeStateChanged(old, new) - } - } - } - } -} diff --git a/calico-vpp-agent/routing/routing_server.go b/calico-vpp-agent/routing/routing_server.go deleted file mode 100644 index ecfa82a3b..000000000 --- a/calico-vpp-agent/routing/routing_server.go +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (C) 2019 Cisco Systems Inc. -// Copyright (C) 2016-2017 Nippon Telegraph and Telephone Corporation. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package routing - -import ( - "fmt" - "net" - - bgpapi "github.com/osrg/gobgp/v3/api" - bgpserver "github.com/osrg/gobgp/v3/pkg/server" - "github.com/pkg/errors" - calicov3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" - "github.com/sirupsen/logrus" - "github.com/vishvananda/netlink" - "golang.org/x/net/context" - "gopkg.in/tomb.v2" - - "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/common" - "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/watchers" - "github.com/projectcalico/vpp-dataplane/v3/vpplink" -) - -const ( - NetLinkRouteProtocolGoBGP = 0x11 -) - -type localAddress struct { - ipNet *net.IPNet - vni uint32 -} - -type Server struct { - log *logrus.Entry - vpp *vpplink.VppLink - - localAddressMap map[string]localAddress - ShouldStop bool - - BGPConf *calicov3.BGPConfigurationSpec - BGPServer *bgpserver.BgpServer - bgpFilters map[string]*calicov3.BGPFilter - bgpPeers map[string]*watchers.LocalBGPPeer - - routingServerEventChan chan any - - nodeBGPSpec *common.LocalNodeSpec - peerHandler *PeerHandler -} - -func (s *Server) SetBGPConf(bgpConf *calicov3.BGPConfigurationSpec) { - s.BGPConf = bgpConf - - logLevel, err := logrus.ParseLevel(s.getLogSeverityScreen()) - if err != nil { - s.log.WithError(err).Errorf("Failed to parse loglevel: %s, defaulting to info", s.getLogSeverityScreen()) - } else { - logrus.SetLevel(logLevel) - } -} - -func (s *Server) SetOurBGPSpec(nodeBGPSpec *common.LocalNodeSpec) { - s.nodeBGPSpec = nodeBGPSpec -} - -// SetPeerHandler sets the peer handler for the routing server -func (s *Server) SetPeerHandler(peerHandler *PeerHandler) { - s.peerHandler = peerHandler -} - -func NewRoutingServer(vpp *vpplink.VppLink, bgpServer *bgpserver.BgpServer, log *logrus.Entry) *Server { - server := Server{ - log: log, - vpp: vpp, - BGPServer: bgpServer, - localAddressMap: make(map[string]localAddress), - - routingServerEventChan: make(chan any, common.ChanSize), - bgpFilters: make(map[string]*calicov3.BGPFilter), - bgpPeers: make(map[string]*watchers.LocalBGPPeer), - } - - reg := common.RegisterHandler(server.routingServerEventChan, "routing server events") - reg.ExpectEvents( - common.LocalPodAddressAdded, - common.LocalPodAddressDeleted, - common.BGPPathAdded, - common.BGPPathDeleted, - common.BGPDefinedSetAdded, - common.BGPDefinedSetDeleted, - common.BGPPeerAdded, - common.BGPPeerDeleted, - common.BGPPeerUpdated, - common.BGPFilterAddedOrUpdated, - common.BGPFilterDeleted, - common.PeerNodeStateChanged, - ) - - return &server -} - -func (s *Server) ServeRouting(t *tomb.Tomb) (err error) { - s.log.Infof("Routing server started") - - err = s.configureLocalNodeSnat() - if err != nil { - return errors.Wrap(err, "cannot configure node snat") - } - - for t.Alive() { - globalConfig, err := s.getGoBGPGlobalConfig() - if err != nil { - return fmt.Errorf("cannot get global configuration: %v", err) - } - - err = s.BGPServer.StartBgp(context.Background(), &bgpapi.StartBgpRequest{Global: globalConfig}) - if err != nil { - return errors.Wrap(err, "failed to start BGP server") - } - - nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(s.nodeBGPSpec) - if nodeIP4 != nil { - err = s.initialPolicySetting(false /* isv6 */) - if err != nil { - return errors.Wrap(err, "error configuring initial policies") - } - } - if nodeIP6 != nil { - err = s.initialPolicySetting(true /* isv6 */) - if err != nil { - return errors.Wrap(err, "error configuring initial policies") - } - } - - /* Restore the previous config in case we restarted */ - s.RestoreLocalAddresses() - - s.log.Infof("Routing server is running ") - - /* Start watching goBGP */ - err = s.WatchBGPPath(t) - if err != nil { - s.log.Error(err) - return err - } - - /* watch returned, we shall restart */ - err = s.cleanUpRoutes() - if err != nil { - return errors.Wrap(err, "also failed to clean up routes which we injected") - } - - err = s.BGPServer.StopBgp(context.Background(), &bgpapi.StopBgpRequest{}) - if err != nil { - s.log.Errorf("failed to stop BGP server: %s", err) - } - s.log.Infof("Routing server stopped") - - } - s.log.Warn("Routing Server returned") - - return nil -} - -func (s *Server) getListenPort() uint16 { - return s.BGPConf.ListenPort -} - -func (s *Server) getLogSeverityScreen() string { - return s.BGPConf.LogSeverityScreen -} - -func (s *Server) getGoBGPGlobalConfig() (*bgpapi.Global, error) { - var routerID string - listenAddresses := make([]string, 0) - asn := s.nodeBGPSpec.ASNumber - if asn == nil { - asn = s.BGPConf.ASNumber - } - - nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(s.nodeBGPSpec) - if nodeIP6 != nil { - routerID = nodeIP6.String() - listenAddresses = append(listenAddresses, routerID) - } - if nodeIP4 != nil { - routerID = nodeIP4.String() // Override v6 ID if v4 is available - listenAddresses = append(listenAddresses, routerID) - } - - if routerID == "" { - return nil, fmt.Errorf("no IPs to make a router ID") - } - return &bgpapi.Global{ - Asn: uint32(*asn), - RouterId: routerID, - ListenPort: int32(s.getListenPort()), - ListenAddresses: listenAddresses, - }, nil -} - -func (s *Server) cleanUpRoutes() error { - s.log.Tracef("Clean up injected routes") - filter := &netlink.Route{ - Protocol: NetLinkRouteProtocolGoBGP, - } - list4, err := netlink.RouteListFiltered(netlink.FAMILY_V4, filter, netlink.RT_FILTER_PROTOCOL) - if err != nil { - return err - } - list6, err := netlink.RouteListFiltered(netlink.FAMILY_V6, filter, netlink.RT_FILTER_PROTOCOL) - if err != nil { - return err - } - for _, route := range append(list4, list6...) { - err = netlink.RouteDel(&route) - if err != nil { - return err - } - } - return nil -} - -func (s *Server) announceLocalAddress(addr *net.IPNet, vni uint32) error { - s.log.Debugf("Announcing prefix %s in BGP", addr.String()) - nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(s.nodeBGPSpec) - path, err := common.MakePath(addr.String(), false /* isWithdrawal */, nodeIP4, nodeIP6, vni, uint32(*s.BGPConf.ASNumber)) - if err != nil { - return errors.Wrap(err, "error making path to announce") - } - s.localAddressMap[addr.String()] = localAddress{ipNet: addr, vni: vni} - _, err = s.BGPServer.AddPath(context.Background(), &bgpapi.AddPathRequest{ - TableType: bgpapi.TableType_GLOBAL, - Path: path, - }) - return errors.Wrap(err, "error announcing local address") -} - -func (s *Server) withdrawLocalAddress(addr *net.IPNet, vni uint32) error { - s.log.Debugf("Withdrawing prefix %s from BGP", addr.String()) - nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(s.nodeBGPSpec) - path, err := common.MakePath(addr.String(), true /* isWithdrawal */, nodeIP4, nodeIP6, vni, uint32(*s.BGPConf.ASNumber)) - if err != nil { - return errors.Wrap(err, "error making path to withdraw") - } - delete(s.localAddressMap, addr.String()) - err = s.BGPServer.DeletePath(context.Background(), &bgpapi.DeletePathRequest{ - TableType: bgpapi.TableType_GLOBAL, - Path: path, - }) - return errors.Wrap(err, "error withdrawing local address") -} - -func (s *Server) RestoreLocalAddresses() { - for _, localAddr := range s.localAddressMap { - err := s.announceLocalAddress(localAddr.ipNet, localAddr.vni) - if err != nil { - s.log.Errorf("Local address %s restore failed : %+v", localAddr.ipNet.String(), err) - } - } -} diff --git a/calico-vpp-agent/routing/routing_server_init.go b/calico-vpp-agent/routing/routing_server_init.go deleted file mode 100644 index 145440ffd..000000000 --- a/calico-vpp-agent/routing/routing_server_init.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (C) 2021 Cisco Systems Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package routing - -import ( - bgpapi "github.com/osrg/gobgp/v3/api" - "github.com/pkg/errors" - "golang.org/x/net/context" - - "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/common" -) - -func (s *Server) createEmptyPrefixSet(name string) error { - ps := &bgpapi.DefinedSet{ - DefinedType: bgpapi.DefinedType_PREFIX, - Name: name, - } - err := s.BGPServer.AddDefinedSet( - context.Background(), - &bgpapi.AddDefinedSetRequest{DefinedSet: ps}, - ) - if err != nil { - return errors.Wrapf(err, "error creating prefix set %s", name) - } - return nil -} - -// initialPolicySetting initialize BGP export policy. -// this creates two prefix-sets named 'aggregated' and 'host'. -// A route is allowed to be exported when it matches with 'aggregated' set, -// and not allowed when it matches with 'host' set. -func (s *Server) initialPolicySetting(isv6 bool) error { - aggregatedPrefixSetName := common.GetAggPrefixSetName(isv6) - hostPrefixSetName := common.GetHostPrefixSetName(isv6) - err := s.createEmptyPrefixSet(aggregatedPrefixSetName) - if err != nil { - return err - } - err = s.createEmptyPrefixSet(hostPrefixSetName) - if err != nil { - return err - } - // intended to work as same as 'calico_pools' export filter of BIRD configuration - definition := &bgpapi.Policy{ - Name: common.GetPolicyName(isv6), - Statements: []*bgpapi.Statement{ - { - Conditions: &bgpapi.Conditions{ - PrefixSet: &bgpapi.MatchSet{ - Type: bgpapi.MatchSet_ANY, - Name: aggregatedPrefixSetName, - }, - }, - Actions: &bgpapi.Actions{ - RouteAction: bgpapi.RouteAction_ACCEPT, - }, - }, - { - Conditions: &bgpapi.Conditions{ - PrefixSet: &bgpapi.MatchSet{ - Type: bgpapi.MatchSet_ANY, - Name: hostPrefixSetName, - }, - }, - Actions: &bgpapi.Actions{ - RouteAction: bgpapi.RouteAction_REJECT, - }, - }, - }, - } - - err = s.BGPServer.AddPolicy( - context.Background(), - &bgpapi.AddPolicyRequest{ - Policy: definition, - ReferExistingStatements: false, - }, - ) - if err != nil { - return errors.Wrap(err, "error adding policy") - } - err = s.BGPServer.AddPolicyAssignment( - context.Background(), - &bgpapi.AddPolicyAssignmentRequest{ - Assignment: &bgpapi.PolicyAssignment{ - Name: "global", - Direction: bgpapi.PolicyDirection_EXPORT, - Policies: []*bgpapi.Policy{definition}, - DefaultAction: bgpapi.RouteAction_ACCEPT, - }, - }) - if err != nil { - return errors.Wrap(err, "cannot add policy assignment") - } - return nil -} - -// Configure SNAT prefixes so that we don't snat traffic going from a local pod to the node -func (s *Server) configureLocalNodeSnat() error { - nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(s.nodeBGPSpec) - if nodeIP4 != nil { - err := s.vpp.CnatAddDelSnatPrefix(common.ToMaxLenCIDR(*nodeIP4), true) - if err != nil { - return errors.Wrapf(err, "error configuring snat prefix for current node (%v)", *nodeIP4) - } - } - if nodeIP6 != nil { - err := s.vpp.CnatAddDelSnatPrefix(common.ToMaxLenCIDR(*nodeIP6), true) - if err != nil { - return errors.Wrapf(err, "error configuring snat prefix for current node (%v)", *nodeIP6) - } - } - return nil -} diff --git a/calico-vpp-agent/watchers/bgp_watcher.go b/calico-vpp-agent/watchers/bgp_watcher.go new file mode 100644 index 000000000..5e15e6f6a --- /dev/null +++ b/calico-vpp-agent/watchers/bgp_watcher.go @@ -0,0 +1,213 @@ +// Copyright (C) 2019 Cisco Systems Inc. +// Copyright (C) 2016-2017 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package watchers + +import ( + "context" + "fmt" + + bgpapi "github.com/osrg/gobgp/v3/api" + bgpserver "github.com/osrg/gobgp/v3/pkg/server" + "github.com/pkg/errors" + logrus "github.com/sirupsen/logrus" + "gopkg.in/tomb.v2" + + "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/common" + "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/felix/cache" + "github.com/projectcalico/vpp-dataplane/v3/config" +) + +// BGPRouteHandler defines the interface for handling BGP routes and policies +type BGPRouteHandler interface { + InitialPolicySetting(isv6 bool) error + InjectRoute(path *bgpapi.Path) error + InjectSRv6Policy(path *bgpapi.Path) error +} + +type BGPWatcher struct { + log *logrus.Entry + cache *cache.Cache + + BGPServer *bgpserver.BgpServer + nodeBGPSpec *common.LocalNodeSpec + bgpHandler BGPRouteHandler +} + +func NewBGPWatcher(cache *cache.Cache, log *logrus.Entry) *BGPWatcher { + watcher := &BGPWatcher{ + log: log, + cache: cache, + } + + return watcher +} + +func (w *BGPWatcher) SetBGPServer(bgpServer *bgpserver.BgpServer) { + w.BGPServer = bgpServer +} + +func (w *BGPWatcher) SetBGPHandler(handler BGPRouteHandler) { + w.bgpHandler = handler +} + +func (w *BGPWatcher) injectRoute(path *bgpapi.Path) error { + if w.bgpHandler == nil { + w.log.Warnf("BGP handler not set, cannot inject route") + return nil + } + return w.bgpHandler.InjectRoute(path) +} + +func (w *BGPWatcher) injectSRv6Policy(path *bgpapi.Path) error { + if w.bgpHandler == nil { + w.log.Warnf("BGP handler not set, cannot inject SRv6 policy") + return nil + } + return w.bgpHandler.InjectSRv6Policy(path) +} + +func (w *BGPWatcher) SetOurBGPSpec(nodeBGPSpec *common.LocalNodeSpec) { + w.nodeBGPSpec = nodeBGPSpec +} + +func (w *BGPWatcher) getListenPort() uint16 { + return w.cache.BGPConf.ListenPort +} + +func (w *BGPWatcher) getGoBGPGlobalConfig() (*bgpapi.Global, error) { + var routerID string + listenAddresses := make([]string, 0) + asn := w.nodeBGPSpec.ASNumber + if asn == nil { + asn = w.cache.BGPConf.ASNumber + } + + nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(w.nodeBGPSpec) + if nodeIP6 != nil { + routerID = nodeIP6.String() + listenAddresses = append(listenAddresses, routerID) + } + if nodeIP4 != nil { + routerID = nodeIP4.String() // Override v6 ID if v4 is available + listenAddresses = append(listenAddresses, routerID) + } + + if routerID == "" { + return nil, fmt.Errorf("no IPs to make a router ID") + } + return &bgpapi.Global{ + Asn: uint32(*asn), + RouterId: routerID, + ListenPort: int32(w.getListenPort()), + ListenAddresses: listenAddresses, + }, nil +} + +func (w *BGPWatcher) startBGPMonitoring() (func(), error) { + nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(w.nodeBGPSpec) + ctx, stopFunc := context.WithCancel(context.Background()) + err := w.BGPServer.WatchEvent(ctx, + &bgpapi.WatchEventRequest{ + Table: &bgpapi.WatchEventRequest_Table{ + Filters: []*bgpapi.WatchEventRequest_Table_Filter{{ + Type: bgpapi.WatchEventRequest_Table_Filter_BEST, + }}, + }, + }, + func(r *bgpapi.WatchEventResponse) { + if table := r.GetTable(); table != nil { + for _, path := range table.GetPaths() { + if path == nil || path.GetFamily() == nil { + w.log.Warnf("nil path update, skipping") + continue + } + if nodeIP4 == nil && path.GetFamily().Afi == bgpapi.Family_AFI_IP { + w.log.Debugf("Ignoring ipv4 path with no node ip4") + continue + } + if nodeIP6 == nil && path.GetFamily().Afi == bgpapi.Family_AFI_IP6 { + w.log.Debugf("Ignoring ipv6 path with no node ip6") + continue + } + if path.GetNeighborIp() == "" || path.GetNeighborIp() == "" { // Weird GoBGP API behaviour + w.log.Debugf("Ignoring internal path") + continue + } + // Only process SRv6 if feature gate is enabled and path family matches + if config.GetCalicoVppFeatureGates().SRv6Enabled != nil && *config.GetCalicoVppFeatureGates().SRv6Enabled && path.GetFamily() == &common.BgpFamilySRv6IPv6 { + w.log.Debugf("Path SRv6") + err := w.injectSRv6Policy(path) + if err != nil { + w.log.Errorf("cannot inject SRv6: %v", err) + } + continue + } + w.log.Infof("Got path update from=%s as=%d family=%s", path.GetSourceId(), path.GetSourceAsn(), path.GetFamily()) + err := w.injectRoute(path) + if err != nil { + w.log.Errorf("cannot inject route: %v", err) + } + } + } + }, + ) + return stopFunc, err +} + +// WatchBGPPath watches BGP routes from other peers and inject them into linux kernel +// TODO: multipath support +func (w *BGPWatcher) WatchBGPPath(t *tomb.Tomb) error { + globalConfig, err := w.getGoBGPGlobalConfig() + if err != nil { + return fmt.Errorf("cannot get global configuration: %v", err) + } + + err = w.BGPServer.StartBgp(context.Background(), &bgpapi.StartBgpRequest{Global: globalConfig}) + if err != nil { + return errors.Wrap(err, "failed to start BGP server") + } + + // Set up initial BGP policies for route export + nodeIP4, nodeIP6 := common.GetBGPSpecAddresses(w.nodeBGPSpec) + if nodeIP4 != nil && w.bgpHandler != nil { + err = w.bgpHandler.InitialPolicySetting(false /* isv6 */) + if err != nil { + return errors.Wrap(err, "error configuring initial policies for IPv4") + } + } + if nodeIP6 != nil && w.bgpHandler != nil { + err = w.bgpHandler.InitialPolicySetting(true /* isv6 */) + if err != nil { + return errors.Wrap(err, "error configuring initial policies for IPv6") + } + } + + w.log.Infof("BGP Watcher is running ") + + stopBGPMonitoring, err := w.startBGPMonitoring() + if err != nil { + return errors.Wrap(err, "error starting BGP monitoring") + } + + <-t.Dying() + stopBGPMonitoring() + err = w.BGPServer.StopBgp(context.Background(), &bgpapi.StopBgpRequest{}) + if err != nil { + w.log.Errorf("failed to stop BGP server: %s", err) + } + w.log.Infof("BGP Watcher asked to stop") + return nil +} diff --git a/calico-vpp-agent/watchers/peers_watcher.go b/calico-vpp-agent/watchers/peers_watcher.go index da59677ae..e0021aa83 100644 --- a/calico-vpp-agent/watchers/peers_watcher.go +++ b/calico-vpp-agent/watchers/peers_watcher.go @@ -16,11 +16,8 @@ package watchers import ( - "reflect" - "sort" "time" - bgpapi "github.com/osrg/gobgp/v3/api" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/net/context" @@ -36,24 +33,6 @@ import ( "github.com/projectcalico/vpp-dataplane/v3/calico-vpp-agent/common" ) -type LocalBGPPeer struct { - Peer *bgpapi.Peer - BGPFilterNames []string - BGPPolicies map[string]*ImpExpPol - NeighborSet *bgpapi.DefinedSet -} - -type BGPPrefixesPolicyAndAssignment struct { - PolicyAssignment *bgpapi.PolicyAssignment - Policy *bgpapi.Policy - Prefixes []*bgpapi.DefinedSet -} - -type ImpExpPol struct { - Imp *BGPPrefixesPolicyAndAssignment - Exp *BGPPrefixesPolicyAndAssignment -} - type PeerWatcher struct { log *logrus.Entry clientv3 calicov3cli.Interface @@ -146,19 +125,6 @@ func (w *PeerWatcher) WatchBGPPeers(t *tomb.Tomb) error { return nil } -func CompareStringSlices(slice1, slice2 []string) bool { - if len(slice1) != len(slice2) { - return false - } - - // Sort the slices in ascending order - sort.Strings(slice1) - sort.Strings(slice2) - - // Compare the sorted slices - return reflect.DeepEqual(slice1, slice2) -} - func (w *PeerWatcher) resyncAndCreateWatcher() error { if w.currentWatchRevision == "" { err := w.resyncPeers() diff --git a/go.mod b/go.mod index 8dad6ebae..767dbfb22 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/containernetworking/plugins v1.6.2 github.com/google/gopacket v1.1.19 github.com/gookit/color v1.5.4 - github.com/inconshreveable/mousetrap v1.1.0 github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0 github.com/lunixbochs/struc v0.0.0-20241101090106-8d528fa2c543 // indirect github.com/onsi/ginkgo v1.16.5 @@ -19,7 +18,6 @@ require ( github.com/projectcalico/api v0.0.0-20250617202239-c3be7477438e // v3.30.1 github.com/projectcalico/calico v0.0.0-20250529224300-393b14e729a6 // v3.30.1 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.9.1 github.com/vishvananda/netlink v1.3.1-0.20250206174618-62fb240731fa github.com/vishvananda/netns v0.0.4 github.com/yookoala/realpath v1.0.0 diff --git a/go.sum b/go.sum index c4c019737..d63d3017a 100644 --- a/go.sum +++ b/go.sum @@ -28,7 +28,6 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -141,8 +140,6 @@ github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/Q github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -252,7 +249,6 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -265,8 +261,6 @@ github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=