diff --git a/examples/netlink/BUILD.bazel b/examples/netlink/BUILD.bazel new file mode 100644 index 0000000000000000000000000000000000000000..c720d946780a555352121d2f6a21aa33999465b6 --- /dev/null +++ b/examples/netlink/BUILD.bazel @@ -0,0 +1,27 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "main.go", + "main_ipv4.go", + ], + importpath = "github.com/bio-routing/bio-rd/examples/netlink", + visibility = ["//visibility:private"], + deps = [ + "//config:go_default_library", + "//net:go_default_library", + "//protocols/bgp/server:go_default_library", + "//protocols/netlink:go_default_library", + "//routingtable:go_default_library", + "//routingtable/filter:go_default_library", + "//routingtable/locRIB:go_default_library", + "//vendor/github.com/sirupsen/logrus:go_default_library", + ], +) + +go_binary( + name = "netlink", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/examples/netlink/main.go b/examples/netlink/main.go new file mode 100644 index 0000000000000000000000000000000000000000..aa4e493acb4e37c1f2745f5593e39811870ecacc --- /dev/null +++ b/examples/netlink/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "net" + "os" + "time" + + "github.com/bio-routing/bio-rd/config" + "github.com/bio-routing/bio-rd/protocols/bgp/server" + "github.com/bio-routing/bio-rd/protocols/netlink" + "github.com/bio-routing/bio-rd/routingtable/locRIB" + log "github.com/sirupsen/logrus" + + bnet "github.com/bio-routing/bio-rd/net" +) + +func strAddr(s string) uint32 { + ret, _ := bnet.StrToAddr(s) + return ret +} + +func main() { + log.SetLevel(log.DebugLevel) + + f, err := os.OpenFile("/var/log/bio-rd.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatalf("error opening file: %v", err) + } + defer f.Close() + + log.SetOutput(f) + + log.Info("bio-routing started...\n") + + cfg := &config.Global{ + Listen: true, + LocalAddressList: []net.IP{ + net.IPv4(169, 254, 0, 2), + }, + } + + rib := locRIB.New() + b := server.NewBgpServer() + startBGPServer(b, rib, cfg) + + // Netlink communication + n := protocolnetlink.NewNetlink(&config.Netlink{ + HoldTime: time.Second * 15, + UpdateInterval: time.Second * 15, + RoutingTable: config.RtMain, + }, rib) + n.Start() + + go func() { + for { + log.Debugf("LocRIB count: %d", rib.Count()) + log.Debugf(rib.String()) + time.Sleep(time.Second * 10) + } + }() + + select {} +} diff --git a/examples/netlink/main_ipv4.go b/examples/netlink/main_ipv4.go new file mode 100644 index 0000000000000000000000000000000000000000..12bb67ae2305208035c00d46ed634c72a2dee454 --- /dev/null +++ b/examples/netlink/main_ipv4.go @@ -0,0 +1,49 @@ +package main + +import ( + "time" + + "github.com/bio-routing/bio-rd/routingtable" + "github.com/bio-routing/bio-rd/routingtable/locRIB" + + "github.com/bio-routing/bio-rd/config" + "github.com/bio-routing/bio-rd/protocols/bgp/server" + "github.com/bio-routing/bio-rd/routingtable/filter" + log "github.com/sirupsen/logrus" + + bnet "github.com/bio-routing/bio-rd/net" +) + +func startBGPServer(b server.BGPServer, rib *locRIB.LocRIB, cfg *config.Global) { + err := b.Start(cfg) + if err != nil { + log.Fatalf("Unable to start BGP server: %v", err) + } + + b.AddPeer(config.Peer{ + AdminEnabled: true, + LocalAS: 65200, + PeerAS: 65100, + PeerAddress: bnet.IPv4FromOctets(169, 254, 0, 1), + LocalAddress: bnet.IPv4FromOctets(169, 254, 0, 2), + ReconnectInterval: time.Second * 20, + HoldTime: time.Second * 20, + KeepAlive: time.Second * 20, + Passive: false, + RouterID: b.RouterID(), + + //AddPathSend: routingtable.ClientOptions{ + // MaxPaths: 10, + //}, + //RouteServerClient: true, + IPv4: &config.AddressFamilyConfig{ + RIB: rib, + ImportFilter: filter.NewAcceptAllFilter(), + ExportFilter: filter.NewAcceptAllFilter(), + AddPathSend: routingtable.ClientOptions{ + MaxPaths: 10, + }, + AddPathRecv: true, + }, + }) +} diff --git a/examples/netlink/main_ipv6.go b/examples/netlink/main_ipv6.go new file mode 100644 index 0000000000000000000000000000000000000000..cc26484b2e7f855eca6eeb75c6d51df436167f47 --- /dev/null +++ b/examples/netlink/main_ipv6.go @@ -0,0 +1,72 @@ +// +build ipv6 + +package main + +import ( + "net" + "time" + + "github.com/bio-routing/bio-rd/config" + "github.com/bio-routing/bio-rd/protocols/bgp/server" + "github.com/bio-routing/bio-rd/routingtable" + "github.com/bio-routing/bio-rd/routingtable/filter" + "github.com/bio-routing/bio-rd/routingtable/locRIB" + "github.com/sirupsen/logrus" + + bnet "github.com/bio-routing/bio-rd/net" +) + +func startServer(b server.BGPServer, rib *locRIB.LocRIB) { + + err := b.Start(&config.Global{ + Listen: true, + LocalAddressList: []net.IP{ + net.IP{0x20, 0x01, 0x6, 0x78, 0x1, 0xe0, 0, 0, 0, 0, 0, 0, 0, 0, 0xca, 0xfe}, + }, + }) + if err != nil { + logrus.Fatalf("Unable to start BGP server: %v", err) + } + + b.AddPeer(config.Peer{ + AdminEnabled: true, + LocalAS: 65200, + PeerAS: 202739, + PeerAddress: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 1), + LocalAddress: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0xcafe), + ReconnectInterval: time.Second * 15, + HoldTime: time.Second * 90, + KeepAlive: time.Second * 30, + Passive: true, + RouterID: b.RouterID(), + IPv6: &config.AddressFamilyConfig{ + RIB: rib, + ImportFilter: filter.NewAcceptAllFilter(), + ExportFilter: filter.NewDrainFilter(), + AddPathSend: routingtable.ClientOptions{ + BestOnly: true, + }, + }, + }) + + b.AddPeer(config.Peer{ + AdminEnabled: true, + LocalAS: 65200, + PeerAS: 65400, + PeerAddress: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0xcafe, 0, 0, 0, 5), + LocalAddress: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0xcafe), + ReconnectInterval: time.Second * 15, + HoldTime: time.Second * 90, + KeepAlive: time.Second * 30, + Passive: true, + RouterID: b.RouterID(), + IPv6: &config.AddressFamilyConfig{ + RIB: rib, + ImportFilter: filter.NewDrainFilter(), + ExportFilter: filter.NewAcceptAllFilter(), + AddPathSend: routingtable.ClientOptions{ + BestOnly: true, + }, + }, + }) +} diff --git a/route/BUILD.bazel b/route/BUILD.bazel index c249b10f33d990279a7fa5cdbc2578cbdd8c639b..cefdafaefdc1e939221f8106e8318076208e5303 100644 --- a/route/BUILD.bazel +++ b/route/BUILD.bazel @@ -15,7 +15,6 @@ go_library( deps = [ "//net:go_default_library", "//protocols/bgp/types:go_default_library", - "//vendor/github.com/sirupsen/logrus:go_default_library", "//vendor/github.com/taktv6/tflow2/convert:go_default_library", "//vendor/github.com/vishvananda/netlink:go_default_library", ], diff --git a/route/netlink_path.go b/route/netlink_path.go index c9d19b19fcfdb9621755536462c5b4a96a4e9674..cb374fe8c7672d417641b07c22187620feabea58 100644 --- a/route/netlink_path.go +++ b/route/netlink_path.go @@ -52,18 +52,18 @@ func NewPathsFromNlRoute(r netlink.Route, kernel bool) (bnet.Prefix, []*Path, er if r.Src == nil && r.Dst != nil { dst = bnet.NewPfxFromIPNet(r.Dst) if dst.Addr().IsIPv4() { - src = bnet.IPv4FromOctets(0, 0, 0, 0) + src = bnet.IPv4(0) } else { - src = bnet.IPv6FromBlocks(0, 0, 0, 0, 0, 0, 0, 0) + src = bnet.IPv6(0, 0) } } if r.Src != nil && r.Dst == nil { src, _ = bnet.IPFromBytes(r.Src) if src.IsIPv4() { - dst = bnet.NewPfx(bnet.IPv4FromOctets(0, 0, 0, 0), 0) + dst = bnet.NewPfx(bnet.IPv4(0), 0) } else { - dst = bnet.NewPfx(bnet.IPv6FromBlocks(0, 0, 0, 0, 0, 0, 0, 0), 0) + dst = bnet.NewPfx(bnet.IPv6(0, 0), 0) } } @@ -111,19 +111,19 @@ func NewPathsFromNlRoute(r netlink.Route, kernel bool) (bnet.Prefix, []*Path, er // Select compares s with t and returns negative if s < t, 0 if paths are equal, positive if s > t func (s *NetlinkPath) Select(t *NetlinkPath) int8 { - if s.NextHop.Compare(t.NextHop) > 0 { + if s.NextHop.Compare(t.NextHop) < 0 { return -1 } - if s.NextHop.Compare(t.NextHop) < 0 { + if s.NextHop.Compare(t.NextHop) > 0 { return 1 } - if s.Src.Compare(t.Src) > 0 { + if s.Src.Compare(t.Src) < 0 { return -1 } - if s.Src.Compare(t.Src) < 0 { + if s.Src.Compare(t.Src) > 0 { return 1 } @@ -156,28 +156,7 @@ func (s *NetlinkPath) Select(t *NetlinkPath) int8 { // ECMP determines if path s and t are equal in terms of ECMP func (s *NetlinkPath) ECMP(t *NetlinkPath) bool { - - if s.Src != t.Src { - return false - } - - if s.Priority != t.Priority { - return false - } - - if s.Protocol != t.Protocol { - return false - } - - if s.Type != t.Type { - return false - } - - if s.Table != t.Table { - return false - } - - return true + return s.Src == t.Src && s.Priority == t.Priority && s.Protocol == t.Protocol && s.Type == t.Type && s.Table == t.Table } // Copy duplicates the current object diff --git a/route/path_test.go b/route/path_test.go index c76cc96171591ae8af6077aac2052c97e0f52b96..84b3cde044750bcc75eb44ac89ff894c619d5cf3 100644 --- a/route/path_test.go +++ b/route/path_test.go @@ -451,90 +451,140 @@ func TestNewPathsFromNetlinkRoute(t *testing.T) { }, expectError: false, }, - // { - // name: "No source but destination", - // source: &netlink.Route{ - // Dst: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8).GetIPNet(), - // Gw: bnet.IPv4(789).Bytes(), - // Protocol: ProtoKernel, - // Priority: 1, - // Table: 254, - // Type: 1, - // }, - // expected: &NetlinkPath{ - // Src: bnet.IPv4FromOctets(0, 0, 0, 0), - // NextHop: bnet.IPv4(789), - // Protocol: ProtoKernel, - // Priority: 1, - // Table: 254, - // Type: 1, - // Kernel: true, - // }, - // expectError: false, - // }, - // { - // name: "Source but no destination", - // source: &netlink.Route{ - // Src: bnet.IPv4(456).Bytes(), - // Gw: bnet.IPv4(789).Bytes(), - // Protocol: ProtoKernel, - // Priority: 1, - // Table: 254, - // Type: 1, - // }, - // expected: &NetlinkPath{ - // Src: bnet.IPv4(456), - // NextHop: bnet.IPv4(789), - // Protocol: ProtoKernel, - // Priority: 1, - // Table: 254, - // Type: 1, - // Kernel: true, - // }, - // expectError: false, - // }, - // { - // name: "No source but destination IPv6", - // source: &netlink.Route{ - // Dst: bnet.NewPfx(bnet.IPv6(2001, 0), 48).GetIPNet(), - // Gw: bnet.IPv6(2001, 2).Bytes(), - // Protocol: ProtoKernel, - // Priority: 1, - // Table: 254, - // Type: 1, - // }, - // expected: &NetlinkPath{ - // Src: bnet.IPv6FromBlocks(0, 0, 0, 0, 0, 0, 0, 0), - // NextHop: bnet.IPv6(2001, 2), - // Protocol: ProtoKernel, - // Priority: 1, - // Table: 254, - // Type: 1, - // Kernel: true, - // }, - // expectError: false, - // }, - // { - // name: "Source but no destination IPv6", - // source: &netlink.Route{ - // Src: bnet.IPv6(2001, 0).Bytes(), - // Gw: bnet.IPv6(2001, 2).Bytes(), - // Protocol: ProtoKernel, - // Priority: 1, - // Table: 254, - // Type: 1, - // }, - // expected: &NetlinkPath{ - // Src: bnet.IPv6(2001, 0), - // NextHop: bnet.IPv6(2001, 2), - // Protocol: ProtoKernel, - // Priority: 1, - // Table: 254, - // Type: 1, - // Kernel: true, - // }, - // expectError: false, - // }, + { + name: "No source but destination", + source: netlink.Route{ + Dst: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8).GetIPNet(), + Gw: bnet.IPv4(789).Bytes(), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + }, + expectedPfx: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8), + expectedPaths: []*Path{ + { + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: bnet.IPv4(0), + NextHop: bnet.IPv4(789), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + Kernel: true, + }, + }, + }, + expectError: false, + }, + { + name: "Source but no destination", + source: netlink.Route{ + Src: bnet.IPv4(456).Bytes(), + Gw: bnet.IPv4(789).Bytes(), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + }, + expectedPfx: bnet.NewPfx(bnet.IPv4FromOctets(0, 0, 0, 0), 0), + expectedPaths: []*Path{ + { + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: bnet.IPv4(456), + NextHop: bnet.IPv4(789), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + Kernel: true, + }, + }, + }, + expectError: false, + }, + { + name: "No source but no destination", + source: netlink.Route{ + Gw: bnet.IPv4(789).Bytes(), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + }, + expectedPfx: bnet.Prefix{}, + expectedPaths: []*Path{}, + expectError: true, + }, + { + name: "No source but destination IPv6", + source: netlink.Route{ + Dst: bnet.NewPfx(bnet.IPv6(2001, 0), 48).GetIPNet(), + Gw: bnet.IPv6(2001, 123).Bytes(), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + }, + expectedPfx: bnet.NewPfx(bnet.IPv6(2001, 0), 48), + expectedPaths: []*Path{ + { + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: bnet.IPv6(0, 0), + NextHop: bnet.IPv6(2001, 123), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + Kernel: true, + }, + }, + }, + expectError: false, + }, + { + name: "Source but no destination IPv6", + source: netlink.Route{ + Src: bnet.IPv6(2001, 456).Bytes(), + Gw: bnet.IPv6(2001, 789).Bytes(), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + }, + expectedPfx: bnet.NewPfx(bnet.IPv6(0, 0), 0), + expectedPaths: []*Path{ + { + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: bnet.IPv6(2001, 456), + NextHop: bnet.IPv6(2001, 789), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + Kernel: true, + }, + }, + }, + expectError: false, + }, + { + name: "no source no destination", + source: netlink.Route{ + Gw: bnet.IPv4(123).Bytes(), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + }, + expectedPfx: bnet.NewPfx(bnet.IPv4(0), 0), + expectedPaths: []*Path{{}}, + expectError: true, + }, } for _, test := range tests { @@ -548,3 +598,272 @@ func TestNewPathsFromNetlinkRoute(t *testing.T) { } } } + +func TestECMP(t *testing.T) { + tests := []struct { + name string + left *Path + right *Path + ecmp bool + }{ + { + name: "BGP Path ecmp", + left: &Path{ + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 100, + ASPathLen: 10, + MED: 1, + Origin: 123, + }, + }, + right: &Path{ + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 100, + ASPathLen: 10, + MED: 1, + Origin: 123, + }, + }, + ecmp: true, + }, { + name: "BGP Path not ecmp", + left: &Path{ + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 100, + ASPathLen: 10, + MED: 1, + Origin: 123, + }, + }, + right: &Path{ + Type: BGPPathType, + BGPPath: &BGPPath{ + LocalPref: 100, + ASPathLen: 5, + MED: 1, + Origin: 123, + }, + }, + ecmp: false, + }, { + name: "Netlink Path ecmp", + left: &Path{ + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: bnet.IPv4(123), + Priority: 1, + Protocol: 1, + Type: 1, + Table: 1, + }, + }, + right: &Path{ + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: bnet.IPv4(123), + Priority: 1, + Protocol: 1, + Type: 1, + Table: 1, + }, + }, + ecmp: true, + }, { + name: "Netlink Path not ecmp", + left: &Path{ + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: bnet.IPv4(123), + Priority: 1, + Protocol: 1, + Type: 1, + Table: 1, + }, + }, + right: &Path{ + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: bnet.IPv4(123), + Priority: 2, + Protocol: 1, + Type: 1, + Table: 1, + }, + }, + ecmp: false, + }, { + name: "static Path ecmp", + left: &Path{ + Type: StaticPathType, + StaticPath: &StaticPath{ + NextHop: bnet.IPv4(123), + }, + }, + right: &Path{ + Type: StaticPathType, + StaticPath: &StaticPath{ + NextHop: bnet.IPv4(123), + }, + }, + ecmp: true, + }, { + name: "static Path not ecmp", + left: &Path{ + Type: StaticPathType, + StaticPath: &StaticPath{ + NextHop: bnet.IPv4(123), + }, + }, + right: &Path{ + Type: StaticPathType, + StaticPath: &StaticPath{ + NextHop: bnet.IPv4(345), + }, + }, + // ECMP is always true for staticPath + ecmp: true, + }, + } + + for _, test := range tests { + ecmp := test.left.ECMP(test.right) + assert.Equal(t, test.ecmp, ecmp, test.name) + } +} + +func TestNetlinkPathSelect(t *testing.T) { + tests := []struct { + name string + left *NetlinkPath + right *NetlinkPath + expected int8 + }{ + { + name: "equal", + left: &NetlinkPath{ + NextHop: bnet.IPv4(123), + Src: bnet.IPv4(234), + Priority: 1, + Protocol: 1, + Table: 1, + }, + right: &NetlinkPath{ + NextHop: bnet.IPv4(123), + Src: bnet.IPv4(234), + Priority: 1, + Protocol: 1, + Table: 1, + }, + expected: 0, + }, + { + name: "nextHop smaller", + left: &NetlinkPath{ + NextHop: bnet.IPv4(1), + }, + right: &NetlinkPath{ + NextHop: bnet.IPv4(2), + }, + expected: -1, + }, + { + name: "nextHop bigger", + left: &NetlinkPath{ + NextHop: bnet.IPv4(2), + }, + right: &NetlinkPath{ + NextHop: bnet.IPv4(1), + }, + expected: 1, + }, + { + name: "src smaller", + left: &NetlinkPath{ + Src: bnet.IPv4(1), + }, + right: &NetlinkPath{ + Src: bnet.IPv4(2), + }, + expected: -1, + }, + { + name: "src bigger", + left: &NetlinkPath{ + Src: bnet.IPv4(2), + }, + right: &NetlinkPath{ + Src: bnet.IPv4(1), + }, + expected: 1, + }, + { + name: "priority smaller", + left: &NetlinkPath{ + Priority: 1, + }, + right: &NetlinkPath{ + Priority: 2, + }, + expected: -1, + }, + { + name: "priority bigger", + left: &NetlinkPath{ + Priority: 2, + }, + right: &NetlinkPath{ + Priority: 1, + }, + expected: 1, + }, + { + name: "protocol smaller", + left: &NetlinkPath{ + Protocol: 1, + }, + right: &NetlinkPath{ + Protocol: 2, + }, + expected: -1, + }, + { + name: "protocol bigger", + left: &NetlinkPath{ + Protocol: 2, + }, + right: &NetlinkPath{ + Protocol: 1, + }, + expected: 1, + }, + { + name: "table smaller", + left: &NetlinkPath{ + Table: 1, + }, + right: &NetlinkPath{ + Table: 2, + }, + expected: -1, + }, + { + name: "table bigger", + left: &NetlinkPath{ + Table: 2, + }, + right: &NetlinkPath{ + Table: 1, + }, + expected: 1, + }, + } + + for _, test := range tests { + result := test.left.Select(test.right) + assert.Equal(t, test.expected, result, test.name) + } + +} diff --git a/routingtable/locRIB/loc_rib.go b/routingtable/locRIB/loc_rib.go index aa60b9243aab1846bdb6dd010f9ced6155071268..6d1f7a48d62295743280a861eb19cdcfc902e67b 100644 --- a/routingtable/locRIB/loc_rib.go +++ b/routingtable/locRIB/loc_rib.go @@ -175,6 +175,23 @@ func (a *LocRIB) ContainsPfxPath(pfx net.Prefix, p *route.Path) bool { return false } +func (a *LocRIB) String() string { + a.mu.RLock() + defer a.mu.RUnlock() + + ret := "" + routes := a.rt.Dump() + for idx, r := range routes { + if idx < len(routes)-1 { + ret += fmt.Sprintf("%s, ", r.Prefix().String()) + } else { + ret += fmt.Sprintf("%s", r.Prefix().String()) + } + } + + return ret +} + func (a *LocRIB) Print() string { a.mu.RLock() defer a.mu.RUnlock()