diff --git a/e2etests/go.mod b/e2etests/go.mod index b01a4733..a7df8dbc 100644 --- a/e2etests/go.mod +++ b/e2etests/go.mod @@ -5,7 +5,7 @@ go 1.19 replace ( github.com/metallb/frrk8s => ../ github.com/onsi/ginkgo/v2 => github.com/onsi/ginkgo/v2 v2.11.0 - go.universe.tf/e2etest => github.com/metallb/metallb/e2etest v0.0.0-20230704151713-ca4eed32d1f6 + go.universe.tf/e2etest => github.com/metallb/metallb/e2etest v0.0.0-20230719075116-22ffd63a2942 k8s.io/api => k8s.io/api v0.26.0 k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.26.0 k8s.io/apimachinery => k8s.io/apimachinery v0.26.0 diff --git a/e2etests/go.sum b/e2etests/go.sum index 129d1ce2..0c9d4718 100644 --- a/e2etests/go.sum +++ b/e2etests/go.sum @@ -226,8 +226,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/metallb/metallb/e2etest v0.0.0-20230704151713-ca4eed32d1f6 h1:PlT2uIiyi0jsbXax1wapWg0MjGoODcx5BODqdoXP40A= -github.com/metallb/metallb/e2etest v0.0.0-20230704151713-ca4eed32d1f6/go.mod h1:pvpDmNSSuGzhAF32B9Rsf8cl+9XiAMq0fRMaWbJsWAo= +github.com/metallb/metallb/e2etest v0.0.0-20230719075116-22ffd63a2942 h1:KHKjIaQSPXxqSRMf7KwRGSigM5OdrmGsTZUjBCCYEmI= +github.com/metallb/metallb/e2etest v0.0.0-20230719075116-22ffd63a2942/go.mod h1:pvpDmNSSuGzhAF32B9Rsf8cl+9XiAMq0fRMaWbJsWAo= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= diff --git a/e2etests/go.work.sum b/e2etests/go.work.sum index 5c7da725..03a798e2 100644 --- a/e2etests/go.work.sum +++ b/e2etests/go.work.sum @@ -263,8 +263,8 @@ github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118/go.mod h1:ZFUnHI github.com/mdlayher/ndp v0.0.0-20200602162440-17ab9e3e5567/go.mod h1:32w/5dDZWVSEOxyniAgKK4d7dHTuO6TCxWmUznQe3f8= github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU= github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= -github.com/metallb/metallb/e2etest v0.0.0-20230704151713-ca4eed32d1f6 h1:PlT2uIiyi0jsbXax1wapWg0MjGoODcx5BODqdoXP40A= -github.com/metallb/metallb/e2etest v0.0.0-20230704151713-ca4eed32d1f6/go.mod h1:pvpDmNSSuGzhAF32B9Rsf8cl+9XiAMq0fRMaWbJsWAo= +github.com/metallb/metallb/e2etest v0.0.0-20230719075116-22ffd63a2942 h1:KHKjIaQSPXxqSRMf7KwRGSigM5OdrmGsTZUjBCCYEmI= +github.com/metallb/metallb/e2etest v0.0.0-20230719075116-22ffd63a2942/go.mod h1:pvpDmNSSuGzhAF32B9Rsf8cl+9XiAMq0fRMaWbJsWAo= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= diff --git a/e2etests/pkg/routes/routes.go b/e2etests/pkg/routes/routes.go index b6c1e0ef..08ce2969 100644 --- a/e2etests/pkg/routes/routes.go +++ b/e2etests/pkg/routes/routes.go @@ -11,9 +11,24 @@ import ( "go.universe.tf/e2etest/pkg/frr" frrcontainer "go.universe.tf/e2etest/pkg/frr/container" "go.universe.tf/e2etest/pkg/ipfamily" + "go.universe.tf/metallb/e2etest/pkg/executor" v1 "k8s.io/api/core/v1" + "k8s.io/kubernetes/test/e2e/framework" ) +// PodHasPrefixFromContainer tells if the given frr-k8s pod has recevied a route for +// the given prefix from the given container. +func PodHasPrefixFromContainer(pod *v1.Pod, frr frrcontainer.FRR, prefix string) bool { + _, cidr, _ := net.ParseCIDR(prefix) + ipFamily := ipfamily.ForCIDR(cidr) + nextHop := frr.Ipv4 + if ipFamily == ipfamily.IPv6 { + nextHop = frr.Ipv6 + } + vrf := frr.RouterConfig.VRF + return hasPrefix(pod, ipFamily, cidr, nextHop, vrf) +} + // CheckNeighborHasPrefix tells if the given frr container has a route toward the given prefix // via the set of node passed to this function. func CheckNeighborHasPrefix(neighbor frrcontainer.FRR, prefix string, nodes []v1.Node) (bool, error) { @@ -67,3 +82,28 @@ func routeForCIDR(cidr *net.IPNet, routesV4 map[string]frr.Route, routesV6 map[s } return frr.Route{}, RouteNotFoundError(fmt.Sprintf("route %s not found", cidr)) } + +func hasPrefix(pod *v1.Pod, pairingFamily ipfamily.Family, prefix *net.IPNet, nextHop, vrf string) bool { + found := false + podExec := executor.ForPod(pod.Namespace, pod.Name, "frr") + routes, frrRoutesV6, err := frr.RoutesForVRF(vrf, podExec) + framework.ExpectNoError(err) + + if pairingFamily == ipfamily.IPv6 { + routes = frrRoutesV6 + } + +out: + for _, route := range routes { + if !cidrsAreEqual(route.Destination, prefix) { + continue + } + for _, nh := range route.NextHops { + if nh.String() == nextHop { + found = true + break out + } + } + } + return found +} diff --git a/e2etests/tests/receiving.go b/e2etests/tests/receiving.go new file mode 100644 index 00000000..edc817cc --- /dev/null +++ b/e2etests/tests/receiving.go @@ -0,0 +1,298 @@ +// SPDX-License-Identifier:Apache-2.0 + +package tests + +import ( + "github.com/onsi/ginkgo/v2" + "go.universe.tf/e2etest/pkg/frr/container" + + frrk8sv1beta1 "github.com/metallb/frrk8s/api/v1beta1" + "github.com/metallb/frrk8stests/pkg/config" + "github.com/metallb/frrk8stests/pkg/dump" + "github.com/metallb/frrk8stests/pkg/infra" + "github.com/metallb/frrk8stests/pkg/k8s" + frrconfig "go.universe.tf/e2etest/pkg/frr/config" + "go.universe.tf/e2etest/pkg/ipfamily" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/test/e2e/framework" + admissionapi "k8s.io/pod-security-admission/api" +) + +var _ = ginkgo.Describe("Receiving routes", func() { + var cs clientset.Interface + var f *framework.Framework + + defer ginkgo.GinkgoRecover() + clientconfig, err := framework.LoadConfig() + framework.ExpectNoError(err) + updater, err := config.NewUpdater(clientconfig) + framework.ExpectNoError(err) + reporter := dump.NewK8sReporter(framework.TestContext.KubeConfig, k8s.FRRK8sNamespace) + + f = framework.NewDefaultFramework("bgpfrr") + f.NamespacePodSecurityEnforceLevel = admissionapi.LevelPrivileged + + ginkgo.AfterEach(func() { + if ginkgo.CurrentSpecReport().Failed() { + testName := ginkgo.CurrentSpecReport().LeafNodeText + dump.K8sInfo(testName, reporter) + dump.BGPInfo(testName, infra.FRRContainers, f.ClientSet, f) + } + }) + + ginkgo.BeforeEach(func() { + ginkgo.By("Clearing any previous configuration") + + for _, c := range infra.FRRContainers { + err := c.UpdateBGPConfigFile(frrconfig.Empty) + framework.ExpectNoError(err) + } + err := updater.Clean() + framework.ExpectNoError(err) + + cs = f.ClientSet + }) + + ginkgo.Context("Receiving IPs", func() { + type params struct { + vrf string + ipFamily ipfamily.Family + myAsn uint32 + toAdvertiseV4 []string + toAdvertiseV6 []string + modifyPeers func([]config.Peer, []config.Peer) + validate func([]config.Peer, []config.Peer, []*v1.Pod) + } + + ginkgo.DescribeTable("Works with external frrs", func(p params) { + frrs := config.ContainersForVRF(infra.FRRContainers, p.vrf) + peersV4, peersV6 := config.PeersForContainers(frrs, p.ipFamily) + p.modifyPeers(peersV4, peersV6) + neighbors := config.NeighborsFromPeers(peersV4, peersV6) + + config := frrk8sv1beta1.FRRConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: frrk8sv1beta1.FRRConfigurationSpec{ + BGP: frrk8sv1beta1.BGPConfig{ + Routers: []frrk8sv1beta1.Router{ + { + ASN: p.myAsn, + VRF: p.vrf, + Neighbors: neighbors, + }, + }, + }, + }, + } + + ginkgo.By("pairing with nodes") + for _, c := range frrs { + err := container.PairWithNodes(cs, c, p.ipFamily, func(frr *container.FRR) { + frr.NeighborConfig.ToAdvertiseV4 = p.toAdvertiseV4 + frr.NeighborConfig.ToAdvertiseV6 = p.toAdvertiseV6 + }) + framework.ExpectNoError(err) + } + err := updater.Update(config) + framework.ExpectNoError(err) + + nodes, err := k8s.Nodes(cs) + framework.ExpectNoError(err) + + for _, c := range frrs { + ValidateFRRPeeredWithNodes(nodes, c, p.ipFamily) + } + + pods, err := k8s.FRRK8sPods(cs) + framework.ExpectNoError(err) + + ginkgo.By("validating") + p.validate(peersV4, peersV6, pods) + }, + ginkgo.Entry("IPV4 - receive ips from all", params{ + ipFamily: ipfamily.IPv4, + vrf: "", + myAsn: infra.FRRK8sASN, + toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24"}, + modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) { + for i := range ppV4 { + ppV4[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll + } + }, + validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) { + for _, p := range ppV4 { + ValidateNodesHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...) + } + }, + }), + ginkgo.Entry("IPV4 - receive ips from some, all mode", params{ + ipFamily: ipfamily.IPv4, + vrf: "", + myAsn: infra.FRRK8sASN, + toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24"}, + modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) { + ppV4[0].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll + }, + validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) { + ValidateNodesHaveRoutes(pods, ppV4[0].FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...) + for _, p := range ppV4[1:] { + ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...) + } + }, + }), + ginkgo.Entry("IPV4 - receive ips from some, explicit mode", params{ + ipFamily: ipfamily.IPv4, + vrf: "", + myAsn: infra.FRRK8sASN, + toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24", "192.170.2.0/24"}, + modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) { + ppV4[0].Neigh.ToReceive.Allowed.Prefixes = []string{"192.168.2.0/24", "192.169.2.0/24"} + }, + validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) { + ValidateNodesHaveRoutes(pods, ppV4[0].FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...) + ValidateNodesDoNotHaveRoutes(pods, ppV4[0].FRR, []string{"192.170.2.0/24"}...) + for _, p := range ppV4[1:] { + ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24", "192.170.2.0/24"}...) + } + }, + }), + ginkgo.Entry("IPV6 - receive ips from all", params{ + ipFamily: ipfamily.IPv6, + vrf: "", + myAsn: infra.FRRK8sASN, + toAdvertiseV6: []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}, + modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) { + for i := range ppV4 { + ppV4[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll + } + for i := range ppV6 { + ppV6[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll + } + }, + validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) { + for _, p := range ppV6 { + ValidateNodesHaveRoutes(pods, p.FRR, []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}...) + } + }, + }), + ginkgo.Entry("IPV6 - receive ips from some, explicit mode", params{ + ipFamily: ipfamily.IPv6, + vrf: "", + myAsn: infra.FRRK8sASN, + toAdvertiseV6: []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}, + modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) { + ppV6[0].Neigh.ToReceive.Allowed.Prefixes = []string{"fc00:f853:ccd:e799::/64"} + }, + validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) { + ValidateNodesHaveRoutes(pods, ppV6[0].FRR, []string{"fc00:f853:ccd:e799::/64"}...) + ValidateNodesDoNotHaveRoutes(pods, ppV6[0].FRR, []string{"fc00:f853:ccd:e800::/64"}...) + for _, p := range ppV6[1:] { + ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}...) + } + }, + }), + ginkgo.Entry("IPV4 - VRF - receive ips from some, all mode", params{ + ipFamily: ipfamily.IPv4, + vrf: infra.VRFName, + myAsn: infra.FRRK8sASNVRF, + toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24"}, + modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) { + ppV4[0].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll + }, + validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) { + ValidateNodesHaveRoutes(pods, ppV4[0].FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...) + for _, p := range ppV4[1:] { + ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...) + } + }, + }), + ginkgo.Entry("IPV4 - VRF - receive ips from some, explicit mode", params{ + ipFamily: ipfamily.IPv4, + vrf: infra.VRFName, + myAsn: infra.FRRK8sASNVRF, + toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24", "192.170.2.0/24"}, + modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) { + ppV4[0].Neigh.ToReceive.Allowed.Prefixes = []string{"192.168.2.0/24", "192.169.2.0/24"} + }, + validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) { + ValidateNodesHaveRoutes(pods, ppV4[0].FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...) + ValidateNodesDoNotHaveRoutes(pods, ppV4[0].FRR, []string{"192.170.2.0/24"}...) + for _, p := range ppV4[1:] { + ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24", "192.170.2.0/24"}...) + } + }, + }), + ginkgo.Entry("IPV6 - VRF - receive ips from all", params{ + ipFamily: ipfamily.IPv6, + vrf: infra.VRFName, + myAsn: infra.FRRK8sASNVRF, + toAdvertiseV6: []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}, + modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) { + for i := range ppV4 { + ppV4[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll + } + for i := range ppV6 { + ppV6[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll + } + }, + validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) { + for _, p := range ppV6 { + ValidateNodesHaveRoutes(pods, p.FRR, []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}...) + } + }, + }), + ginkgo.Entry("DUALSTACK - receive ips from all", params{ + ipFamily: ipfamily.DualStack, + vrf: "", + myAsn: infra.FRRK8sASN, + toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24"}, + toAdvertiseV6: []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}, + modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) { + for i := range ppV4 { + ppV4[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll + } + for i := range ppV6 { + ppV6[i].Neigh.ToReceive.Allowed.Mode = frrk8sv1beta1.AllowAll + } + }, + + validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) { + for _, p := range ppV4 { + ValidateNodesHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...) + } + for _, p := range ppV6 { + ValidateNodesHaveRoutes(pods, p.FRR, []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}...) + } + }, + }), + ginkgo.Entry("DUALSTACK - receive ips from some, explicit mode", params{ + ipFamily: ipfamily.DualStack, + vrf: "", + myAsn: infra.FRRK8sASN, + toAdvertiseV4: []string{"192.168.2.0/24", "192.169.2.0/24"}, + toAdvertiseV6: []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}, + modifyPeers: func(ppV4 []config.Peer, ppV6 []config.Peer) { + ppV4[0].Neigh.ToReceive.Allowed.Prefixes = []string{"192.169.2.0/24"} + ppV6[0].Neigh.ToReceive.Allowed.Prefixes = []string{"fc00:f853:ccd:e799::/64"} + }, + validate: func(ppV4 []config.Peer, ppV6 []config.Peer, pods []*v1.Pod) { + ValidateNodesHaveRoutes(pods, ppV4[0].FRR, []string{"192.169.2.0/24"}...) + ValidateNodesDoNotHaveRoutes(pods, ppV4[0].FRR, []string{"192.168.2.0/24"}...) + for _, p := range ppV4[1:] { + ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"192.168.2.0/24", "192.169.2.0/24"}...) + } + ValidateNodesHaveRoutes(pods, ppV6[0].FRR, []string{"fc00:f853:ccd:e799::/64"}...) + ValidateNodesDoNotHaveRoutes(pods, ppV6[0].FRR, []string{"fc00:f853:ccd:e800::/64"}...) + for _, p := range ppV6[1:] { + ValidateNodesDoNotHaveRoutes(pods, p.FRR, []string{"fc00:f853:ccd:e799::/64", "fc00:f853:ccd:e800::/64"}...) + } + }, + }), + ) + }) +}) diff --git a/e2etests/tests/validate.go b/e2etests/tests/validate.go index a26fc865..4a6a29d4 100644 --- a/e2etests/tests/validate.go +++ b/e2etests/tests/validate.go @@ -97,3 +97,42 @@ func ValidateNeighborCommunityPrefixes(neigh frrcontainer.FRR, community string, return nil }, 5*time.Second, time.Second).ShouldNot(HaveOccurred()) } + +func ValidateNodesHaveRoutes(pods []*v1.Pod, neigh frrcontainer.FRR, prefixes ...string) { + ginkgo.By(fmt.Sprintf("Checking routes %v from %s", prefixes, neigh.Name)) + Eventually(func() error { + for _, prefix := range prefixes { + for _, pod := range pods { + if !routes.PodHasPrefixFromContainer(pod, neigh, prefix) { + return fmt.Errorf("pod %s does not have prefix %s from %s", pod.Name, prefix, neigh.Name) + } + } + } + return nil + }, time.Minute, time.Second).ShouldNot(HaveOccurred()) +} + +func ValidateNodesDoNotHaveRoutes(pods []*v1.Pod, neigh frrcontainer.FRR, prefixes ...string) { + ginkgo.By(fmt.Sprintf("Checking routes %v not injected from %s", prefixes, neigh.Name)) + shouldPassConsistently(func() error { + for _, prefix := range prefixes { + for _, pod := range pods { + if routes.PodHasPrefixFromContainer(pod, neigh, prefix) { + return fmt.Errorf("pod %s has prefix %s from %s", pod.Name, prefix, neigh.Name) + } + } + } + return nil + }) +} + +// shouldPassConsistently checks for the failure to happen +// and then checks it consistently. +func shouldPassConsistently(toCheck func() error) { + Eventually(func() error { + return toCheck() + }, 2*time.Minute, time.Second).ShouldNot(HaveOccurred()) + Consistently(func() error { + return toCheck() + }, 5*time.Second, time.Second).ShouldNot(HaveOccurred()) +} diff --git a/internal/controller/api_to_config.go b/internal/controller/api_to_config.go index 73efae08..75d88c09 100644 --- a/internal/controller/api_to_config.go +++ b/internal/controller/api_to_config.go @@ -54,7 +54,7 @@ func routerToFRRConfig(r v1beta1.Router) (*frr.RouterConfig, error) { for _, n := range r.Neighbors { frrNeigh, err := neighborToFRR(n, res.IPV4Prefixes, res.IPV6Prefixes) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to process neighbor %s for router %d-%s: %w", neighborName(n.ASN, n.Address), r.ASN, r.VRF, err) } res.Neighbors = append(res.Neighbors, frrNeigh) } @@ -73,90 +73,175 @@ func neighborToFRR(n v1beta1.Neighbor, ipv4Prefixes, ipv6Prefixes []string) (*fr Addr: n.Address, Port: n.Port, // Password: n.Password, TODO password as secret - Advertisements: make([]*frr.AdvertisementConfig, 0), - IPFamily: neighborFamily, - EBGPMultiHop: n.EBGPMultiHop, + IPFamily: neighborFamily, + EBGPMultiHop: n.EBGPMultiHop, } - advs := map[string]*frr.AdvertisementConfig{} - if n.ToAdvertise.Allowed.Mode == v1beta1.AllowAll { + res.Outgoing, err = toAdvertiseToFRR(n.ToAdvertise, ipv4Prefixes, ipv6Prefixes) + if err != nil { + return nil, err + } + res.Incoming = toReceiveToFRR(n.ToReceive) + return res, nil +} + +func toAdvertiseToFRR(toAdvertise v1beta1.Advertise, ipv4Prefixes, ipv6Prefixes []string) (frr.AllowedOut, error) { + advsV4, advsV6 := prefixesToMap(toAdvertise, ipv4Prefixes, ipv6Prefixes) + communities, err := communityPrefixesToMap(toAdvertise.PrefixesWithCommunity) + if err != nil { + return frr.AllowedOut{}, err + } + err = setCommunitiesToAdvertisements(advsV4, communities, ipfamily.IPv4) + if err != nil { + return frr.AllowedOut{}, err + } + err = setCommunitiesToAdvertisements(advsV6, communities, ipfamily.IPv6) + if err != nil { + return frr.AllowedOut{}, err + } + res := frr.AllowedOut{ + PrefixesV4: sortMap(advsV4), + PrefixesV6: sortMap(advsV6), + } + return res, nil +} + +// prefixesToMap returns two maps of prefix->OutgoingFIlter (ie family, advertisement, communities), one for each family. +func prefixesToMap(toAdvertise v1beta1.Advertise, ipv4Prefixes, ipv6Prefixes []string) (map[string]*frr.OutgoingFilter, map[string]*frr.OutgoingFilter) { + resV4 := map[string]*frr.OutgoingFilter{} + resV6 := map[string]*frr.OutgoingFilter{} + if toAdvertise.Allowed.Mode == v1beta1.AllowAll { for _, p := range ipv4Prefixes { - advs[p] = &frr.AdvertisementConfig{Prefix: p, IPFamily: ipfamily.IPv4} - res.HasV4Advertisements = true + resV4[p] = &frr.OutgoingFilter{Prefix: p, IPFamily: ipfamily.IPv4} } for _, p := range ipv6Prefixes { - advs[p] = &frr.AdvertisementConfig{Prefix: p, IPFamily: ipfamily.IPv6} - res.HasV6Advertisements = true + resV6[p] = &frr.OutgoingFilter{Prefix: p, IPFamily: ipfamily.IPv6} } + return resV4, resV6 } - - for _, p := range n.ToAdvertise.Allowed.Prefixes { + // TODO: add a validation somewhere that checks that the prefixes are present in the + // global per router list. + for _, p := range toAdvertise.Allowed.Prefixes { family := ipfamily.ForCIDRString(p) switch family { case ipfamily.IPv4: - res.HasV4Advertisements = true + resV4[p] = &frr.OutgoingFilter{Prefix: p, IPFamily: family} case ipfamily.IPv6: - res.HasV6Advertisements = true + resV6[p] = &frr.OutgoingFilter{Prefix: p, IPFamily: family} } - - // TODO: check that the prefix matches the passed IPv4/IPv6 prefixes - advs[p] = &frr.AdvertisementConfig{Prefix: p, IPFamily: family} } + return resV4, resV6 +} - communitiesForPrefix := map[string]sets.Set[string]{} - largeCommunitiesForPrefix := map[string]sets.Set[string]{} - for _, pfxs := range n.ToAdvertise.PrefixesWithCommunity { - c, err := community.New(pfxs.Community) - if err != nil { - return nil, fmt.Errorf("community %s invalid for neighbor %s, err: %w", pfxs.Community, n.Address, err) - } - - prefixesMap := communitiesForPrefix - if community.IsLarge(c) { - prefixesMap = largeCommunitiesForPrefix - } - - for _, p := range pfxs.Prefixes { - _, ok := advs[p] - if !ok { - return nil, fmt.Errorf("prefix %s with community %s not in allowed list for neighbor %s", p, pfxs.Community, n.Address) - } - _, ok = prefixesMap[p] - if !ok { - prefixesMap[p] = sets.New(c.String()) - continue - } - - prefixesMap[p].Insert(c.String()) - } +// setCommunitiesToAdvertisements takes the given communityPrefixes and fills the relevant fields to the advertisements contained in the advs map. +func setCommunitiesToAdvertisements(advs map[string]*frr.OutgoingFilter, communities communityPrefixes, ipFamily ipfamily.Family) error { + communitiesForPrefix := communities.communitiesForPrefixV4 + largeCommunitiesForPrefix := communities.largeCommunitiesForPrefixV4 + if ipFamily == ipfamily.IPv6 { + communitiesForPrefix = communities.communitiesForPrefixV6 + largeCommunitiesForPrefix = communities.largeCommunitiesForPrefixV6 } - for p, c := range communitiesForPrefix { adv, ok := advs[p] - if !ok { // shouldn't happen as we check in previous loop, just in case - return nil, fmt.Errorf("unexpected err - no community prefix matching %s", p) + if !ok { + return fmt.Errorf("community associated to non existing prefix %s", p) } adv.Communities = sets.List(c) } for p, c := range largeCommunitiesForPrefix { adv, ok := advs[p] - if !ok { // shouldn't happen as we check in previous loop, just in case - return nil, fmt.Errorf("unexpected err - no community prefix matching %s", p) + if !ok { + return fmt.Errorf("large community associated to non existing prefix %s", p) } adv.LargeCommunities = sets.List(c) } + return nil +} - res.Advertisements = sortMap(advs) - - return res, nil +func toReceiveToFRR(toReceive v1beta1.Receive) frr.AllowedIn { + res := frr.AllowedIn{ + PrefixesV4: make([]frr.IncomingFilter, 0), + PrefixesV6: make([]frr.IncomingFilter, 0), + } + if toReceive.Allowed.Mode == v1beta1.AllowAll { + res.All = true + return res + } + for _, p := range toReceive.Allowed.Prefixes { + family := ipfamily.ForCIDRString(p) + if family == ipfamily.IPv4 { + res.PrefixesV4 = append(res.PrefixesV4, frr.IncomingFilter{Prefix: p, IPFamily: family}) + continue + } + res.PrefixesV6 = append(res.PrefixesV6, frr.IncomingFilter{Prefix: p, IPFamily: family}) + } + sort.Slice(res.PrefixesV4, func(i, j int) bool { + return res.PrefixesV4[i].Prefix < res.PrefixesV4[j].Prefix + }) + sort.Slice(res.PrefixesV6, func(i, j int) bool { + return res.PrefixesV6[i].Prefix < res.PrefixesV6[j].Prefix + }) + return res } func neighborName(ASN uint32, peerAddr string) string { return fmt.Sprintf("%d@%s", ASN, peerAddr) } -func sortMap[T any](toSort map[string]T) []T { +type communityPrefixes struct { + communitiesForPrefixV4 map[string]sets.Set[string] + largeCommunitiesForPrefixV4 map[string]sets.Set[string] + communitiesForPrefixV6 map[string]sets.Set[string] + largeCommunitiesForPrefixV6 map[string]sets.Set[string] +} + +func (c *communityPrefixes) mapFor(family ipfamily.Family, isLarge bool) map[string]sets.Set[string] { + switch family { + case ipfamily.IPv4: + if isLarge { + return c.largeCommunitiesForPrefixV4 + } + return c.communitiesForPrefixV4 + case ipfamily.IPv6: + if isLarge { + return c.largeCommunitiesForPrefixV6 + } + return c.communitiesForPrefixV6 + } + return nil +} + +func communityPrefixesToMap(withCommunity []v1beta1.CommunityPrefixes) (communityPrefixes, error) { + res := communityPrefixes{ + communitiesForPrefixV4: map[string]sets.Set[string]{}, + largeCommunitiesForPrefixV4: map[string]sets.Set[string]{}, + communitiesForPrefixV6: map[string]sets.Set[string]{}, + largeCommunitiesForPrefixV6: map[string]sets.Set[string]{}, + } + + for _, pfxs := range withCommunity { + c, err := community.New(pfxs.Community) + if err != nil { + return communityPrefixes{}, fmt.Errorf("invalid community %s, err: %w", pfxs.Community, err) + } + isLarge := community.IsLarge(c) + for _, p := range pfxs.Prefixes { + family := ipfamily.ForCIDRString(p) + communityMap := res.mapFor(family, isLarge) + _, ok := communityMap[p] + if !ok { + communityMap[p] = sets.New(c.String()) + continue + } + + communityMap[p].Insert(c.String()) + } + } + return res, nil +} + +func sortMap[T any](toSort map[string]*T) []T { keys := make([]string, 0) for k := range toSort { keys = append(keys, k) @@ -164,7 +249,7 @@ func sortMap[T any](toSort map[string]T) []T { sort.Strings(keys) res := make([]T, 0) for _, k := range keys { - res = append(res, toSort[k]) + res = append(res, *toSort[k]) } return res } diff --git a/internal/controller/api_to_config_test.go b/internal/controller/api_to_config_test.go index 8f13e53c..e49c0202 100644 --- a/internal/controller/api_to_config_test.go +++ b/internal/controller/api_to_config_test.go @@ -52,12 +52,19 @@ func TestConversion(t *testing.T) { RouterID: "192.0.2.1", Neighbors: []*frr.NeighborConfig{ { - IPFamily: ipfamily.IPv4, - Name: "65002@192.0.2.2", - ASN: 65002, - Addr: "192.0.2.2", - Port: 179, - Advertisements: []*frr.AdvertisementConfig{}, + IPFamily: ipfamily.IPv4, + Name: "65002@192.0.2.2", + ASN: 65002, + Addr: "192.0.2.2", + Port: 179, + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{}, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, + }, }, }, VRF: "", @@ -118,20 +125,34 @@ func TestConversion(t *testing.T) { RouterID: "192.0.2.5", Neighbors: []*frr.NeighborConfig{ { - IPFamily: ipfamily.IPv4, - Name: "65011@192.0.2.6", - ASN: 65011, - Addr: "192.0.2.6", - Port: 179, - Advertisements: []*frr.AdvertisementConfig{}, + IPFamily: ipfamily.IPv4, + Name: "65011@192.0.2.6", + ASN: 65011, + Addr: "192.0.2.6", + Port: 179, + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{}, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, + }, }, { - IPFamily: ipfamily.IPv4, - Name: "65012@192.0.2.7", - ASN: 65012, - Addr: "192.0.2.7", - Port: 179, - Advertisements: []*frr.AdvertisementConfig{}, + IPFamily: ipfamily.IPv4, + Name: "65012@192.0.2.7", + ASN: 65012, + Addr: "192.0.2.7", + Port: 179, + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{}, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, + }, }, }, VRF: "", @@ -143,12 +164,19 @@ func TestConversion(t *testing.T) { RouterID: "2001:db8::3", Neighbors: []*frr.NeighborConfig{ { - IPFamily: ipfamily.IPv6, - Name: "65014@2001:db8::4", - ASN: 65014, - Addr: "2001:db8::4", - Port: 179, - Advertisements: []*frr.AdvertisementConfig{}, + IPFamily: ipfamily.IPv6, + Name: "65014@2001:db8::4", + ASN: 65014, + Addr: "2001:db8::4", + Port: 179, + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{}, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, + }, }, }, VRF: "vrf2", @@ -191,12 +219,19 @@ func TestConversion(t *testing.T) { RouterID: "192.0.2.10", Neighbors: []*frr.NeighborConfig{ { - IPFamily: ipfamily.IPv4, - Name: "65021@192.0.2.11", - ASN: 65021, - Addr: "192.0.2.11", - Port: 179, - Advertisements: []*frr.AdvertisementConfig{}, + IPFamily: ipfamily.IPv4, + Name: "65021@192.0.2.11", + ASN: 65021, + Addr: "192.0.2.11", + Port: 179, + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{}, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, + }, }, }, IPV4Prefixes: []string{"192.0.2.0/24"}, @@ -248,12 +283,19 @@ func TestConversion(t *testing.T) { RouterID: "192.0.2.15", Neighbors: []*frr.NeighborConfig{ { - IPFamily: ipfamily.IPv4, - Name: "65031@192.0.2.16", - ASN: 65031, - Addr: "192.0.2.16", - Port: 179, - Advertisements: []*frr.AdvertisementConfig{}, + IPFamily: ipfamily.IPv4, + Name: "65031@192.0.2.16", + ASN: 65031, + Addr: "192.0.2.16", + Port: 179, + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{}, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, + }, }, }, VRF: "vrf1", @@ -306,13 +348,19 @@ func TestConversion(t *testing.T) { ASN: 65041, Addr: "192.0.2.21", Port: 179, - Advertisements: []*frr.AdvertisementConfig{ - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.2.0/24", + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{ + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.2.0/24", + }, }, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, }, - HasV4Advertisements: true, }, }, IPV4Prefixes: []string{"192.0.2.0/24"}, @@ -374,17 +422,23 @@ func TestConversion(t *testing.T) { ASN: 65041, Addr: "192.0.2.21", Port: 179, - Advertisements: []*frr.AdvertisementConfig{ - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.2.0/24", - }, - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.4.0/24", + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{ + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.2.0/24", + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.4.0/24", + }, }, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, }, - HasV4Advertisements: true, }, { IPFamily: ipfamily.IPv4, @@ -392,26 +446,32 @@ func TestConversion(t *testing.T) { ASN: 65041, Addr: "192.0.2.22", Port: 179, - Advertisements: []*frr.AdvertisementConfig{ - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.2.0/24", - }, - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.3.0/24", - }, - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.4.0/24", + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{ + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.2.0/24", + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.3.0/24", + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.4.0/24", + }, }, - { - IPFamily: ipfamily.IPv6, - Prefix: "2001:db8::/64", + PrefixesV6: []frr.OutgoingFilter{ + { + IPFamily: ipfamily.IPv6, + Prefix: "2001:db8::/64", + }, }, }, - HasV4Advertisements: true, - HasV6Advertisements: true, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, + }, }, }, IPV4Prefixes: []string{"192.0.2.0/24", "192.0.3.0/24", "192.0.4.0/24"}, @@ -509,21 +569,27 @@ func TestConversion(t *testing.T) { ASN: 65041, Addr: "192.0.2.21", Port: 179, - Advertisements: []*frr.AdvertisementConfig{ - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.2.0/24", - Communities: []string{"10:100", "10:102"}, - LargeCommunities: []string{"123:456:7890"}, - }, - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.4.0/24", - Communities: []string{"10:100", "10:104"}, - LargeCommunities: []string{"123:456:7890", "123:456:7892"}, + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{ + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.2.0/24", + Communities: []string{"10:100", "10:102"}, + LargeCommunities: []string{"123:456:7890"}, + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.4.0/24", + Communities: []string{"10:100", "10:104"}, + LargeCommunities: []string{"123:456:7890", "123:456:7892"}, + }, }, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, }, - HasV4Advertisements: true, }, { IPFamily: ipfamily.IPv4, @@ -531,29 +597,35 @@ func TestConversion(t *testing.T) { ASN: 65041, Addr: "192.0.2.22", Port: 179, - Advertisements: []*frr.AdvertisementConfig{ - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.2.0/24", - Communities: []string{"10:100", "10:102", "10:108"}, - }, - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.3.0/24", - }, - { - IPFamily: ipfamily.IPv4, - Prefix: "192.0.4.0/24", - Communities: []string{"10:100"}, + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{ + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.2.0/24", + Communities: []string{"10:100", "10:102", "10:108"}, + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.3.0/24", + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.0.4.0/24", + Communities: []string{"10:100"}, + }, }, - { - IPFamily: ipfamily.IPv6, - Prefix: "2001:db8::/64", - Communities: []string{"10:108"}, + PrefixesV6: []frr.OutgoingFilter{ + { + IPFamily: ipfamily.IPv6, + Prefix: "2001:db8::/64", + Communities: []string{"10:108"}, + }, }, }, - HasV4Advertisements: true, - HasV6Advertisements: true, + Incoming: frr.AllowedIn{ + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, + }, }, }, IPV4Prefixes: []string{"192.0.2.0/24", "192.0.3.0/24", "192.0.4.0/24"}, @@ -606,6 +678,127 @@ func TestConversion(t *testing.T) { expected: nil, err: fmt.Errorf("prefix %s with community %s not in allowed list for neighbor %s", "192.0.10.10/32", "10:100", "192.0.2.21"), }, + { + name: "Neighbor with ToReceiveAll", + fromK8s: []v1beta1.FRRConfiguration{ + { + Spec: v1beta1.FRRConfigurationSpec{ + BGP: v1beta1.BGPConfig{ + Routers: []v1beta1.Router{ + { + ASN: 65040, + ID: "192.0.2.20", + Neighbors: []v1beta1.Neighbor{ + { + ASN: 65041, + Address: "192.0.2.21", + Port: 179, + ToReceive: v1beta1.Receive{ + Allowed: v1beta1.AllowedPrefixes{ + Mode: v1beta1.AllowAll, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: &frr.Config{ + Routers: []*frr.RouterConfig{ + { + MyASN: 65040, + RouterID: "192.0.2.20", + IPV4Prefixes: []string{}, + IPV6Prefixes: []string{}, + Neighbors: []*frr.NeighborConfig{ + { + IPFamily: ipfamily.IPv4, + Name: "65041@192.0.2.21", + ASN: 65041, + Addr: "192.0.2.21", + Port: 179, + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{}, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + All: true, + PrefixesV4: []frr.IncomingFilter{}, + PrefixesV6: []frr.IncomingFilter{}, + }, + }, + }, + }, + }, + }, + err: nil, + }, { + name: "Neighbor with ToReceive some ips only", + fromK8s: []v1beta1.FRRConfiguration{ + { + Spec: v1beta1.FRRConfigurationSpec{ + BGP: v1beta1.BGPConfig{ + Routers: []v1beta1.Router{ + { + ASN: 65040, + ID: "192.0.2.20", + Neighbors: []v1beta1.Neighbor{ + { + ASN: 65041, + Address: "192.0.2.21", + Port: 179, + ToReceive: v1beta1.Receive{ + Allowed: v1beta1.AllowedPrefixes{ + Prefixes: []string{"192.0.2.0/24", "192.0.3.0/24", "192.0.4.0/24", "2001:db8::/64"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: &frr.Config{ + Routers: []*frr.RouterConfig{ + { + MyASN: 65040, + RouterID: "192.0.2.20", + IPV4Prefixes: []string{}, + IPV6Prefixes: []string{}, + Neighbors: []*frr.NeighborConfig{ + { + IPFamily: ipfamily.IPv4, + Name: "65041@192.0.2.21", + ASN: 65041, + Addr: "192.0.2.21", + Port: 179, + Outgoing: frr.AllowedOut{ + PrefixesV4: []frr.OutgoingFilter{}, + PrefixesV6: []frr.OutgoingFilter{}, + }, + Incoming: frr.AllowedIn{ + All: false, + PrefixesV4: []frr.IncomingFilter{ + {IPFamily: "ipv4", Prefix: "192.0.2.0/24"}, + {IPFamily: "ipv4", Prefix: "192.0.3.0/24"}, + {IPFamily: "ipv4", Prefix: "192.0.4.0/24"}, + }, + PrefixesV6: []frr.IncomingFilter{ + {IPFamily: "ipv6", Prefix: "2001:db8::/64"}, + }, + }, + }, + }, + }, + }, + }, + err: nil, + }, } for _, test := range tests { diff --git a/internal/frr/config.go b/internal/frr/config.go index 0b9a8807..036aa2cd 100644 --- a/internal/frr/config.go +++ b/internal/frr/config.go @@ -61,21 +61,20 @@ type BFDProfile struct { } type NeighborConfig struct { - IPFamily ipfamily.Family - Name string - ASN uint32 - SrcAddr string - Addr string - Port uint16 - HoldTime uint64 - KeepaliveTime uint64 - Password string - Advertisements []*AdvertisementConfig - BFDProfile string - EBGPMultiHop bool - VRFName string - HasV4Advertisements bool - HasV6Advertisements bool + IPFamily ipfamily.Family + Name string + ASN uint32 + SrcAddr string + Addr string + Port uint16 + HoldTime uint64 + KeepaliveTime uint64 + Password string + BFDProfile string + EBGPMultiHop bool + VRFName string + Incoming AllowedIn + Outgoing AllowedOut } func (n *NeighborConfig) ID() string { @@ -85,7 +84,31 @@ func (n *NeighborConfig) ID() string { return fmt.Sprintf("%s-%s", n.Addr, n.VRFName) } -type AdvertisementConfig struct { +type AllowedIn struct { + All bool + PrefixesV4 []IncomingFilter + PrefixesV6 []IncomingFilter +} + +func (a *AllowedIn) AllPrefixes() []IncomingFilter { + return append(a.PrefixesV4, a.PrefixesV6...) +} + +type AllowedOut struct { + PrefixesV4 []OutgoingFilter + PrefixesV6 []OutgoingFilter +} + +func (a *AllowedOut) AllPrefixes() []OutgoingFilter { + return append(a.PrefixesV4, a.PrefixesV6...) +} + +type IncomingFilter struct { + IPFamily ipfamily.Family + Prefix string +} + +type OutgoingFilter struct { IPFamily ipfamily.Family Prefix string Communities []string @@ -126,6 +149,9 @@ func templateConfig(data interface{}) (string, error) { "allowedPrefixList": func(neighbor *NeighborConfig) string { return fmt.Sprintf("%s-pl-%s", neighbor.ID(), neighbor.IPFamily) }, + "allowedIncomingList": func(neighbor *NeighborConfig) string { + return fmt.Sprintf("%s-inpl-%s", neighbor.ID(), neighbor.IPFamily) + }, "mustDisableConnectedCheck": func(ipFamily ipfamily.Family, myASN, asn uint32, eBGPMultiHop bool) bool { // return true only for IPv6 eBGP sessions if ipFamily == "ipv6" && myASN != asn && !eBGPMultiHop { diff --git a/internal/frr/frr_test.go b/internal/frr/frr_test.go index 38f72f39..e1ae6d43 100644 --- a/internal/frr/frr_test.go +++ b/internal/frr/frr_test.go @@ -110,14 +110,16 @@ func TestSingleSession(t *testing.T) { ASN: 65001, Addr: "192.168.1.2", Port: 4567, - Advertisements: []*AdvertisementConfig{ - { - IPFamily: ipfamily.IPv4, - Prefix: "192.169.1.0/24", - }, - { - IPFamily: ipfamily.IPv4, - Prefix: "192.170.1.0/22", + Outgoing: AllowedOut{ + PrefixesV4: []OutgoingFilter{ + { + IPFamily: ipfamily.IPv4, + Prefix: "192.169.1.0/24", + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.170.1.0/22", + }, }, }, }, @@ -149,22 +151,25 @@ func TestTwoRoutersTwoNeighbors(t *testing.T) { ASN: 65001, Addr: "192.168.1.2", Port: 4567, - Advertisements: []*AdvertisementConfig{ - { - IPFamily: ipfamily.IPv4, - Prefix: "192.169.1.0/24", - Communities: []string{"10:169", "10:170"}, - }, - { - IPFamily: ipfamily.IPv4, - Prefix: "192.169.1.0/22", - Communities: []string{"10:170"}, - LargeCommunities: []string{"123:456:7890"}, - }, - { - IPFamily: ipfamily.IPv4, - Prefix: "192.170.1.0/22", - Communities: []string{"10:170"}, + + Outgoing: AllowedOut{ + PrefixesV4: []OutgoingFilter{ + { + IPFamily: ipfamily.IPv4, + Prefix: "192.169.1.0/24", + Communities: []string{"10:169", "10:170"}, + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.169.1.0/22", + Communities: []string{"10:170"}, + LargeCommunities: []string{"123:456:7890"}, + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.170.1.0/22", + Communities: []string{"10:170"}, + }, }, }, }, @@ -179,10 +184,164 @@ func TestTwoRoutersTwoNeighbors(t *testing.T) { IPFamily: ipfamily.IPv4, ASN: 65001, Addr: "192.168.1.3", - Advertisements: []*AdvertisementConfig{ - { - IPFamily: ipfamily.IPv4, - Prefix: "192.169.1.0/24", + Outgoing: AllowedOut{ + PrefixesV4: []OutgoingFilter{ + { + IPFamily: ipfamily.IPv4, + Prefix: "192.169.1.0/24", + }, + }, + }, + }, + }, + }, + }, + } + err := frr.ApplyConfig(&config) + if err != nil { + t.Fatalf("Failed to apply config: %s", err) + } + + testCheckConfigFile(t) +} + +func TestTwoSessionsAcceptAll(t *testing.T) { + testSetup(t) + ctx, cancel := context.WithCancel(context.Background()) + frr := NewFRR(ctx, log.NewNopLogger(), logging.LevelInfo) + defer cancel() + + config := Config{ + Routers: []*RouterConfig{ + { + MyASN: 65000, + Neighbors: []*NeighborConfig{ + { + IPFamily: ipfamily.IPv4, + ASN: 65001, + Addr: "192.168.1.2", + Port: 4567, + Incoming: AllowedIn{ + All: true, + }, + }, { + IPFamily: ipfamily.IPv4, + ASN: 65001, + Addr: "192.168.1.3", + Port: 4567, + Incoming: AllowedIn{ + All: true, + }, + }, + }, + }, + }, + } + err := frr.ApplyConfig(&config) + if err != nil { + t.Fatalf("Failed to apply config: %s", err) + } + + testCheckConfigFile(t) +} + +func TestTwoSessionsAcceptSomeV4(t *testing.T) { + testSetup(t) + ctx, cancel := context.WithCancel(context.Background()) + frr := NewFRR(ctx, log.NewNopLogger(), logging.LevelInfo) + defer cancel() + + config := Config{ + Routers: []*RouterConfig{ + { + MyASN: 65000, + Neighbors: []*NeighborConfig{ + { + IPFamily: ipfamily.IPv4, + ASN: 65001, + Addr: "192.168.1.2", + Port: 4567, + Incoming: AllowedIn{ + PrefixesV4: []IncomingFilter{ + { + IPFamily: ipfamily.IPv4, + Prefix: "192.168.1.0/24", + }, + }, + }, + }, { + IPFamily: ipfamily.IPv4, + ASN: 65001, + Addr: "192.168.1.3", + Port: 4567, + Incoming: AllowedIn{ + PrefixesV4: []IncomingFilter{ + { + IPFamily: ipfamily.IPv4, + Prefix: "192.170.1.0/24", + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.169.1.0/24", + }, + }, + }, + }, + }, + }, + }, + } + err := frr.ApplyConfig(&config) + if err != nil { + t.Fatalf("Failed to apply config: %s", err) + } + + testCheckConfigFile(t) +} + +func TestTwoSessionsAcceptV4AndV6(t *testing.T) { + testSetup(t) + ctx, cancel := context.WithCancel(context.Background()) + frr := NewFRR(ctx, log.NewNopLogger(), logging.LevelInfo) + defer cancel() + + config := Config{ + Routers: []*RouterConfig{ + { + MyASN: 65000, + Neighbors: []*NeighborConfig{ + { + IPFamily: ipfamily.IPv4, + ASN: 65001, + Addr: "192.168.1.2", + Port: 4567, + Incoming: AllowedIn{ + PrefixesV4: []IncomingFilter{ + { + IPFamily: ipfamily.IPv6, + Prefix: "fc00:f853:ccd:e800::/64", + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.168.1.0/24", + }, + }, + }, + }, { + IPFamily: ipfamily.IPv4, + ASN: 65001, + Addr: "192.168.1.3", + Port: 4567, + Incoming: AllowedIn{ + PrefixesV4: []IncomingFilter{ + { + IPFamily: ipfamily.IPv6, + Prefix: "fc00:f853:ccd:e799::/64", + }, + { + IPFamily: ipfamily.IPv4, + Prefix: "192.169.1.0/24", + }, }, }, }, diff --git a/internal/frr/templates/filters.tmpl b/internal/frr/templates/filters.tmpl index 83248e44..8f8228cb 100644 --- a/internal/frr/templates/filters.tmpl +++ b/internal/frr/templates/filters.tmpl @@ -28,8 +28,7 @@ route-map {{.neighbor.ID}}-out permit {{counter .neighbor.ID}} deny all the others.*/ -}} {{- define "neighborfilters" -}} -route-map {{.neighbor.ID}}-in deny 20 -{{- range $a := .neighbor.Advertisements }} +{{- range $a := .neighbor.Outgoing.AllPrefixes }} {{/* Advertisements for which we must enable set the local pref */}} {{- if not (eq $a.LocalPref 0)}} {{template "localpreffilter" dict "advertisement" $a "neighbor" $.neighbor}} @@ -54,10 +53,32 @@ route-map {{$.neighbor.ID}}-out permit {{counter $.neighbor.ID}} {{/* If the neighbor does not have an advertisement, we need to add a prefix to deny for when we have a prefix but a given peer is not selected for any prefixes */}} -{{- if not .neighbor.HasV4Advertisements}} +{{- if not .neighbor.Outgoing.PrefixesV4 }} ip prefix-list {{allowedPrefixList $.neighbor }} deny any {{- end }} -{{- if not .neighbor.HasV6Advertisements}} +{{- if not .neighbor.Outgoing.PrefixesV6 }} ipv6 prefix-list {{allowedPrefixList $.neighbor}} deny any {{- end -}} + +{{/* filtering incoming prefixes */}} +{{ range $i := .neighbor.Incoming.AllPrefixes }} +{{frrIPFamily $i.IPFamily}} prefix-list {{allowedIncomingList $.neighbor}} permit {{$i.Prefix}} +{{- end }} + +{{ if not .neighbor.Incoming.PrefixesV4 }} +ip prefix-list {{allowedIncomingList $.neighbor }} deny any +{{- end }} +{{ if not .neighbor.Incoming.PrefixesV6 }} +ipv6 prefix-list {{allowedIncomingList $.neighbor}} deny any +{{- end -}} + +{{ if .neighbor.Incoming.All }} +route-map {{$.neighbor.ID}}-in permit {{counter $.neighbor.ID}} +{{ else }} +route-map {{$.neighbor.ID}}-in permit {{counter $.neighbor.ID}} + match ip address prefix-list {{allowedIncomingList $.neighbor}} +route-map {{$.neighbor.ID}}-in permit {{counter $.neighbor.ID}} + match ipv6 address prefix-list {{allowedIncomingList $.neighbor}} +{{- end }} + {{- end -}} diff --git a/internal/frr/testdata/TestSingleSession b/internal/frr/testdata/TestSingleSession index 8a068dbd..e0099fb7 100644 --- a/internal/frr/testdata/TestSingleSession +++ b/internal/frr/testdata/TestSingleSession @@ -3,7 +3,7 @@ log timestamp precision 3 hostname dummyhostname ip nht resolve-via-default ipv6 nht resolve-via-default -route-map 192.168.1.2-in deny 20 + ip prefix-list 192.168.1.2-pl-ipv4 permit 192.169.1.0/24 @@ -17,9 +17,18 @@ route-map 192.168.1.2-out permit 2 match ipv6 address prefix-list 192.168.1.2-pl-ipv4 -ip prefix-list 192.168.1.2-pl-ipv4 deny any ipv6 prefix-list 192.168.1.2-pl-ipv4 deny any + + +ip prefix-list 192.168.1.2-inpl-ipv4 deny any + +ipv6 prefix-list 192.168.1.2-inpl-ipv4 deny any +route-map 192.168.1.2-in permit 3 + match ip address prefix-list 192.168.1.2-inpl-ipv4 +route-map 192.168.1.2-in permit 4 + match ipv6 address prefix-list 192.168.1.2-inpl-ipv4 + router bgp 65000 no bgp ebgp-requires-policy no bgp network import-check diff --git a/internal/frr/testdata/TestSingleSession.golden b/internal/frr/testdata/TestSingleSession.golden index 8a068dbd..e0099fb7 100644 --- a/internal/frr/testdata/TestSingleSession.golden +++ b/internal/frr/testdata/TestSingleSession.golden @@ -3,7 +3,7 @@ log timestamp precision 3 hostname dummyhostname ip nht resolve-via-default ipv6 nht resolve-via-default -route-map 192.168.1.2-in deny 20 + ip prefix-list 192.168.1.2-pl-ipv4 permit 192.169.1.0/24 @@ -17,9 +17,18 @@ route-map 192.168.1.2-out permit 2 match ipv6 address prefix-list 192.168.1.2-pl-ipv4 -ip prefix-list 192.168.1.2-pl-ipv4 deny any ipv6 prefix-list 192.168.1.2-pl-ipv4 deny any + + +ip prefix-list 192.168.1.2-inpl-ipv4 deny any + +ipv6 prefix-list 192.168.1.2-inpl-ipv4 deny any +route-map 192.168.1.2-in permit 3 + match ip address prefix-list 192.168.1.2-inpl-ipv4 +route-map 192.168.1.2-in permit 4 + match ipv6 address prefix-list 192.168.1.2-inpl-ipv4 + router bgp 65000 no bgp ebgp-requires-policy no bgp network import-check diff --git a/internal/frr/testdata/TestTwoRoutersTwoNeighbors.golden b/internal/frr/testdata/TestTwoRoutersTwoNeighbors.golden index 16185927..e43f9f39 100644 --- a/internal/frr/testdata/TestTwoRoutersTwoNeighbors.golden +++ b/internal/frr/testdata/TestTwoRoutersTwoNeighbors.golden @@ -3,7 +3,7 @@ log timestamp precision 3 hostname dummyhostname ip nht resolve-via-default ipv6 nht resolve-via-default -route-map 192.168.1.2-in deny 20 + ip prefix-list 192.168.1.2-10:169-ipv4-community-prefixes permit 192.169.1.0/24 route-map 192.168.1.2-out permit 1 @@ -45,9 +45,18 @@ route-map 192.168.1.2-out permit 7 match ipv6 address prefix-list 192.168.1.2-pl-ipv4 -ip prefix-list 192.168.1.2-pl-ipv4 deny any ipv6 prefix-list 192.168.1.2-pl-ipv4 deny any -route-map 192.168.1.3-in deny 20 + + + +ip prefix-list 192.168.1.2-inpl-ipv4 deny any + +ipv6 prefix-list 192.168.1.2-inpl-ipv4 deny any +route-map 192.168.1.2-in permit 8 + match ip address prefix-list 192.168.1.2-inpl-ipv4 +route-map 192.168.1.2-in permit 9 + match ipv6 address prefix-list 192.168.1.2-inpl-ipv4 + ip prefix-list 192.168.1.3-pl-ipv4 permit 192.169.1.0/24 @@ -58,9 +67,18 @@ route-map 192.168.1.3-out permit 2 match ipv6 address prefix-list 192.168.1.3-pl-ipv4 -ip prefix-list 192.168.1.3-pl-ipv4 deny any ipv6 prefix-list 192.168.1.3-pl-ipv4 deny any + + +ip prefix-list 192.168.1.3-inpl-ipv4 deny any + +ipv6 prefix-list 192.168.1.3-inpl-ipv4 deny any +route-map 192.168.1.3-in permit 3 + match ip address prefix-list 192.168.1.3-inpl-ipv4 +route-map 192.168.1.3-in permit 4 + match ipv6 address prefix-list 192.168.1.3-inpl-ipv4 + router bgp 65000 no bgp ebgp-requires-policy no bgp network import-check diff --git a/internal/frr/testdata/TestTwoSessionsAcceptAll.golden b/internal/frr/testdata/TestTwoSessionsAcceptAll.golden new file mode 100644 index 00000000..5f9f52bf --- /dev/null +++ b/internal/frr/testdata/TestTwoSessionsAcceptAll.golden @@ -0,0 +1,80 @@ +log file /etc/frr/frr.log informational +log timestamp precision 3 +hostname dummyhostname +ip nht resolve-via-default +ipv6 nht resolve-via-default + + +route-map 192.168.1.2-out permit 1 + match ip address prefix-list 192.168.1.2-pl-ipv4 +route-map 192.168.1.2-out permit 2 + match ipv6 address prefix-list 192.168.1.2-pl-ipv4 + + +ip prefix-list 192.168.1.2-pl-ipv4 deny any +ipv6 prefix-list 192.168.1.2-pl-ipv4 deny any + + + +ip prefix-list 192.168.1.2-inpl-ipv4 deny any + +ipv6 prefix-list 192.168.1.2-inpl-ipv4 deny any +route-map 192.168.1.2-in permit 3 + + + +route-map 192.168.1.3-out permit 1 + match ip address prefix-list 192.168.1.3-pl-ipv4 +route-map 192.168.1.3-out permit 2 + match ipv6 address prefix-list 192.168.1.3-pl-ipv4 + + +ip prefix-list 192.168.1.3-pl-ipv4 deny any +ipv6 prefix-list 192.168.1.3-pl-ipv4 deny any + + + +ip prefix-list 192.168.1.3-inpl-ipv4 deny any + +ipv6 prefix-list 192.168.1.3-inpl-ipv4 deny any +route-map 192.168.1.3-in permit 3 + + +router bgp 65000 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + + neighbor 192.168.1.2 remote-as 65001 + neighbor 192.168.1.2 port 4567 + neighbor 192.168.1.2 timers 0 0 + + + neighbor 192.168.1.3 remote-as 65001 + neighbor 192.168.1.3 port 4567 + neighbor 192.168.1.3 timers 0 0 + + + + address-family ipv4 unicast + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 route-map 192.168.1.2-in in + neighbor 192.168.1.2 route-map 192.168.1.2-out out + exit-address-family + address-family ipv6 unicast + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 route-map 192.168.1.2-in in + neighbor 192.168.1.2 route-map 192.168.1.2-out out + exit-address-family + + address-family ipv4 unicast + neighbor 192.168.1.3 activate + neighbor 192.168.1.3 route-map 192.168.1.3-in in + neighbor 192.168.1.3 route-map 192.168.1.3-out out + exit-address-family + address-family ipv6 unicast + neighbor 192.168.1.3 activate + neighbor 192.168.1.3 route-map 192.168.1.3-in in + neighbor 192.168.1.3 route-map 192.168.1.3-out out + exit-address-family + diff --git a/internal/frr/testdata/TestTwoSessionsAcceptSomeV4.golden b/internal/frr/testdata/TestTwoSessionsAcceptSomeV4.golden new file mode 100644 index 00000000..de1d8a10 --- /dev/null +++ b/internal/frr/testdata/TestTwoSessionsAcceptSomeV4.golden @@ -0,0 +1,85 @@ +log file /etc/frr/frr.log informational +log timestamp precision 3 +hostname dummyhostname +ip nht resolve-via-default +ipv6 nht resolve-via-default + + +route-map 192.168.1.2-out permit 1 + match ip address prefix-list 192.168.1.2-pl-ipv4 +route-map 192.168.1.2-out permit 2 + match ipv6 address prefix-list 192.168.1.2-pl-ipv4 + + +ip prefix-list 192.168.1.2-pl-ipv4 deny any +ipv6 prefix-list 192.168.1.2-pl-ipv4 deny any + +ip prefix-list 192.168.1.2-inpl-ipv4 permit 192.168.1.0/24 + + + +ipv6 prefix-list 192.168.1.2-inpl-ipv4 deny any +route-map 192.168.1.2-in permit 3 + match ip address prefix-list 192.168.1.2-inpl-ipv4 +route-map 192.168.1.2-in permit 4 + match ipv6 address prefix-list 192.168.1.2-inpl-ipv4 + + +route-map 192.168.1.3-out permit 1 + match ip address prefix-list 192.168.1.3-pl-ipv4 +route-map 192.168.1.3-out permit 2 + match ipv6 address prefix-list 192.168.1.3-pl-ipv4 + + +ip prefix-list 192.168.1.3-pl-ipv4 deny any +ipv6 prefix-list 192.168.1.3-pl-ipv4 deny any + +ip prefix-list 192.168.1.3-inpl-ipv4 permit 192.170.1.0/24 +ip prefix-list 192.168.1.3-inpl-ipv4 permit 192.169.1.0/24 + + + +ipv6 prefix-list 192.168.1.3-inpl-ipv4 deny any +route-map 192.168.1.3-in permit 3 + match ip address prefix-list 192.168.1.3-inpl-ipv4 +route-map 192.168.1.3-in permit 4 + match ipv6 address prefix-list 192.168.1.3-inpl-ipv4 + +router bgp 65000 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + + neighbor 192.168.1.2 remote-as 65001 + neighbor 192.168.1.2 port 4567 + neighbor 192.168.1.2 timers 0 0 + + + neighbor 192.168.1.3 remote-as 65001 + neighbor 192.168.1.3 port 4567 + neighbor 192.168.1.3 timers 0 0 + + + + address-family ipv4 unicast + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 route-map 192.168.1.2-in in + neighbor 192.168.1.2 route-map 192.168.1.2-out out + exit-address-family + address-family ipv6 unicast + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 route-map 192.168.1.2-in in + neighbor 192.168.1.2 route-map 192.168.1.2-out out + exit-address-family + + address-family ipv4 unicast + neighbor 192.168.1.3 activate + neighbor 192.168.1.3 route-map 192.168.1.3-in in + neighbor 192.168.1.3 route-map 192.168.1.3-out out + exit-address-family + address-family ipv6 unicast + neighbor 192.168.1.3 activate + neighbor 192.168.1.3 route-map 192.168.1.3-in in + neighbor 192.168.1.3 route-map 192.168.1.3-out out + exit-address-family + diff --git a/internal/frr/testdata/TestTwoSessionsAcceptV4AndV6.golden b/internal/frr/testdata/TestTwoSessionsAcceptV4AndV6.golden new file mode 100644 index 00000000..682e8055 --- /dev/null +++ b/internal/frr/testdata/TestTwoSessionsAcceptV4AndV6.golden @@ -0,0 +1,86 @@ +log file /etc/frr/frr.log informational +log timestamp precision 3 +hostname dummyhostname +ip nht resolve-via-default +ipv6 nht resolve-via-default + + +route-map 192.168.1.2-out permit 1 + match ip address prefix-list 192.168.1.2-pl-ipv4 +route-map 192.168.1.2-out permit 2 + match ipv6 address prefix-list 192.168.1.2-pl-ipv4 + + +ip prefix-list 192.168.1.2-pl-ipv4 deny any +ipv6 prefix-list 192.168.1.2-pl-ipv4 deny any + +ipv6 prefix-list 192.168.1.2-inpl-ipv4 permit fc00:f853:ccd:e800::/64 +ip prefix-list 192.168.1.2-inpl-ipv4 permit 192.168.1.0/24 + + + +ipv6 prefix-list 192.168.1.2-inpl-ipv4 deny any +route-map 192.168.1.2-in permit 3 + match ip address prefix-list 192.168.1.2-inpl-ipv4 +route-map 192.168.1.2-in permit 4 + match ipv6 address prefix-list 192.168.1.2-inpl-ipv4 + + +route-map 192.168.1.3-out permit 1 + match ip address prefix-list 192.168.1.3-pl-ipv4 +route-map 192.168.1.3-out permit 2 + match ipv6 address prefix-list 192.168.1.3-pl-ipv4 + + +ip prefix-list 192.168.1.3-pl-ipv4 deny any +ipv6 prefix-list 192.168.1.3-pl-ipv4 deny any + +ipv6 prefix-list 192.168.1.3-inpl-ipv4 permit fc00:f853:ccd:e799::/64 +ip prefix-list 192.168.1.3-inpl-ipv4 permit 192.169.1.0/24 + + + +ipv6 prefix-list 192.168.1.3-inpl-ipv4 deny any +route-map 192.168.1.3-in permit 3 + match ip address prefix-list 192.168.1.3-inpl-ipv4 +route-map 192.168.1.3-in permit 4 + match ipv6 address prefix-list 192.168.1.3-inpl-ipv4 + +router bgp 65000 + no bgp ebgp-requires-policy + no bgp network import-check + no bgp default ipv4-unicast + + neighbor 192.168.1.2 remote-as 65001 + neighbor 192.168.1.2 port 4567 + neighbor 192.168.1.2 timers 0 0 + + + neighbor 192.168.1.3 remote-as 65001 + neighbor 192.168.1.3 port 4567 + neighbor 192.168.1.3 timers 0 0 + + + + address-family ipv4 unicast + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 route-map 192.168.1.2-in in + neighbor 192.168.1.2 route-map 192.168.1.2-out out + exit-address-family + address-family ipv6 unicast + neighbor 192.168.1.2 activate + neighbor 192.168.1.2 route-map 192.168.1.2-in in + neighbor 192.168.1.2 route-map 192.168.1.2-out out + exit-address-family + + address-family ipv4 unicast + neighbor 192.168.1.3 activate + neighbor 192.168.1.3 route-map 192.168.1.3-in in + neighbor 192.168.1.3 route-map 192.168.1.3-out out + exit-address-family + address-family ipv6 unicast + neighbor 192.168.1.3 activate + neighbor 192.168.1.3 route-map 192.168.1.3-in in + neighbor 192.168.1.3 route-map 192.168.1.3-out out + exit-address-family +