diff --git a/.gitignore b/.gitignore index c97ea2a6069695630909b3884565f91629e3965e..740e0fd72bccc0e15acad46fc9cb87677431958c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ bio-rd examples/bgp examples/bmp +examples/netlink/netlink # bazel directories /bazel-* diff --git a/README.md b/README.md index ef6538c6580315b8814a997a61ec7f1344b22a25..0254191f9601b7c9cbc863a5fc906ff2c749c28a 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ A re-implementation of BGP, IS-IS and OSPF in go. We value respect and robustnes cd examples/bmp/ && go build +#### Netlink + + cd examples/netlink && go build + ### Run Tests go test -v -cover ./... 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/protocols/netlink/netlink_reader.go b/protocols/netlink/netlink_reader.go index 7fcfbbb2b745f335cc0ca5d4325b74c2f10666c7..dba4f45bb70b7b8d5794e5197437f8df5931e221 100644 --- a/protocols/netlink/netlink_reader.go +++ b/protocols/netlink/netlink_reader.go @@ -16,11 +16,11 @@ import ( // Constants for IP family const ( - IPFamily4 = 4 // IPv4 - IPFamily6 = 6 // IPv6 + IPFamily4 int = 4 // IPv4 + IPFamily6 int = 6 // IPv6 ) -// NetlinkReader read routes from the Linux Kernel and propagates it to the locRIB +// NetlinkReader reads routes from the Linux Kernel and propagates them to the locRIB type NetlinkReader struct { clientManager *routingtable.ClientManager options *config.Netlink @@ -49,7 +49,7 @@ func (nr *NetlinkReader) Read() { for { // Family doesn't matter. I only filter by the rt_table here - routes, err := netlink.RouteListFiltered(int(IPFamily4), &netlink.Route{Table: int(nr.options.RoutingTable)}, netlink.RT_FILTER_TABLE) + routes, err := netlink.RouteListFiltered(IPFamily4, &netlink.Route{Table: int(nr.options.RoutingTable)}, netlink.RT_FILTER_TABLE) if err != nil { log.WithError(err).Panic("Failed to read routes from kernel") } @@ -66,20 +66,6 @@ func (nr *NetlinkReader) Read() { } } -// create a path from a route -func createPathFromRoute(r *netlink.Route) (*route.Path, error) { - nlPath, err := route.NewNlPathFromRoute(r, true) - - if err != nil { - return nil, fmt.Errorf("Error while creating path object from route object: %v", err) - } - - return &route.Path{ - Type: route.NetlinkPathType, - NetlinkPath: nlPath, - }, nil -} - // propagate changes to all subscribed clients func (nr *NetlinkReader) propagateChanges(routes []netlink.Route) { nr.removePathsFromClients(routes) @@ -88,66 +74,62 @@ func (nr *NetlinkReader) propagateChanges(routes []netlink.Route) { // Add given paths to clients func (nr *NetlinkReader) addPathsToClients(routes []netlink.Route) { + // If there were no routes yet, just skip this function. There's nothing to add + if len(routes) == 0 { + return + } + // only advertise changed routes nr.mu.RLock() advertise := route.NetlinkRouteDiff(routes, nr.routes) nr.mu.RUnlock() - for _, r := range advertise { - if isBioRoute(r) { - log.WithFields(routeLogFields(r)).Debug("Skipping bio route") - continue - } - - // create pfx and path from route - pfx := bnet.NewPfxFromIPNet(r.Dst) - path, err := createPathFromRoute(&r) - if err != nil { - log.WithError(err).WithFields(log.Fields{ - "prefix": pfx.String(), - "path": path.String(), - }).Error("Unable to create path") - continue - } + for _, client := range nr.ClientManager.Clients() { + for _, r := range advertise { + if isBioRoute(r) { + log.WithFields(routeLogFields(r)).Debug("Skipping bio route") + continue + } - if nr.filter != nil { - var reject bool - // TODO: Implement filter that cann handle netlinkRoute objects - path, reject = nr.filter.ProcessTerms(pfx, path) - if reject { + // create pfx and path from route + pfx, paths, err := route.NewPathsFromNlRoute(r, true) + if err != nil { log.WithError(err).WithFields(log.Fields{ "prefix": pfx.String(), - "path": path.String(), - }).Debug("Skipping route due to filter") - + }).Error("Unable to create path") continue } - } - for _, client := range nr.clientManager.Clients() { - log.WithFields(log.Fields{ - "pfx": pfx, - "path": path, - }).Debug("NetlinkReader - client.AddPath") - client.AddPath(pfx, path) + for _, path := range paths { + var p *route.Path + if nr.filter != nil { + var reject bool + p, reject := nr.filter.ProcessTerms(pfx, path) + if reject { + log.WithError(err).WithFields(log.Fields{ + "prefix": pfx.String(), + "path": p.String(), + }).Debug("Skipping route due to filter") + continue + } + } + + log.WithFields(log.Fields{ + "pfx": pfx, + "path": p, + }).Debug("NetlinkReader - client.AddPath") + client.AddPath(pfx, p) + } } } } -// Is route a BIO-Written route? -func isBioRoute(r netlink.Route) bool { - return uint32(r.Protocol) == route.ProtoBio -} - // Remove given paths from clients func (nr *NetlinkReader) removePathsFromClients(routes []netlink.Route) { nr.mu.RLock() - // get the number of routes - routeLength := len(nr.routes) - - // If there where no routes yet, just skip this funktion. There's nothing to delete - if routeLength == 0 { + // If there were no routes yet, just skip this function. There's nothing to delete + if len(nr.routes) == 0 { nr.mu.RUnlock() return } @@ -156,39 +138,51 @@ func (nr *NetlinkReader) removePathsFromClients(routes []netlink.Route) { withdraw := route.NetlinkRouteDiff(nr.routes, routes) nr.mu.RUnlock() - for _, r := range withdraw { - // Is it a BIO-Written route? if so, skip it, dont advertise it - if r.Protocol == route.ProtoBio { - continue - } - - // create pfx and path from route - pfx := bnet.NewPfxFromIPNet(r.Dst) - path, err := createPathFromRoute(&r) - if err != nil { - log.WithError(err).Error("Unable to create path") - continue - } + for _, client := range nr.ClientManager.Clients() { + for _, r := range withdraw { + if isBioRoute(r) { + log.WithFields(routeLogFields(r)).Debug("Skipping bio route") + continue + } - if nr.filter != nil { - var reject bool - // TODO: Implement filter that cann handle netlinkRoute objects - path, reject = nr.filter.ProcessTerms(pfx, path) - if reject { + // create pfx and path from route + pfx, paths, err := route.NewPathsFromNlRoute(r, true) + if err != nil { + log.WithError(err).WithFields(log.Fields{ + "prefix": pfx.String(), + }).Error("Unable to create path") continue } - } - for _, client := range nr.clientManager.Clients() { - log.WithFields(log.Fields{ - "pfx": pfx, - "path": path, - }).Debug("NetlinkReader - client.RemovePath") - client.RemovePath(pfx, path) + for _, path := range paths { + var p *route.Path + if nr.filter != nil { + var reject bool + p, reject = nr.filter.ProcessTerms(pfx, path) + if reject { + log.WithError(err).WithFields(log.Fields{ + "prefix": pfx.String(), + "path": p.String(), + }).Debug("Skipping route due to filter") + continue + } + } + + log.WithFields(log.Fields{ + "pfx": pfx, + "path": p, + }).Debug("NetlinkReader - client.RemovePath") + client.RemovePath(pfx, p) + } } } } +// Is route a BIO-Written route? +func isBioRoute(r netlink.Route) bool { + return uint32(r.Protocol) == route.ProtoBio +} + func routeLogFields(route netlink.Route) log.Fields { return log.Fields{ "LinkIndex": route.LinkIndex, diff --git a/protocols/netlink/netlink_writer.go b/protocols/netlink/netlink_writer.go index 1ebe54205ce1b86105788401a5605276bd7be6a8..f0fb324b8f66fd0f859dab51b58ef1f4f085e2d3 100644 --- a/protocols/netlink/netlink_writer.go +++ b/protocols/netlink/netlink_writer.go @@ -66,12 +66,8 @@ func (nw *NetlinkWriter) AddPath(pfx bnet.Prefix, path *route.Path) error { // if no route exists, add that route if existingPaths == nil || !ok { - paths := make([]*route.Path, 1) - paths = append(paths, path) - nw.pathTable[pfx] = paths - - // add the route to kernel - return nw.addKernel(pfx, path) + nw.pathTable[pfx] = []*route.Path{path} + return nw.addKernel(pfx) } // if the new path is already in, don't do anything @@ -81,11 +77,20 @@ func (nw *NetlinkWriter) AddPath(pfx bnet.Prefix, path *route.Path) error { } } - existingPaths = append(existingPaths, path) - nw.pathTable[pfx] = existingPaths + // if newly added path is a ecmp path to the existing paths, add it + if path.ECMP(existingPaths[0]) { + nw.removeKernel(pfx, existingPaths) + existingPaths = append(existingPaths, path) + nw.pathTable[pfx] = existingPaths + + return nw.addKernel(pfx) + } + + // if newly added path is no ecmp path to the existing ones, remove all old and only add the new + nw.removeKernel(pfx, existingPaths) + nw.pathTable[pfx] = []*route.Path{path} + return nw.addKernel(pfx) - // now add to netlink - return nw.addKernel(pfx, path) } // RemovePath removes a path from the Kernel using netlink This function is triggered by the loc_rib, cause we are subscribed as client in the loc_rib @@ -106,7 +111,7 @@ func (nw *NetlinkWriter) RemovePath(pfx bnet.Prefix, path *route.Path) bool { removeIdx = idx remove = true - err := nw.removeKernel(pfx, path) + err := nw.removeKernel(pfx, []*route.Path{path}) if err != nil { log.WithError(err).Errorf("Error while removing path %s for prefix %s", path.String(), pfx.String()) remove = false @@ -125,11 +130,10 @@ func (nw *NetlinkWriter) RemovePath(pfx bnet.Prefix, path *route.Path) bool { } // Add pfx/path to kernel -func (nw *NetlinkWriter) addKernel(pfx bnet.Prefix, path *route.Path) error { - route, err := nw.createRoute(pfx, path) +func (nw *NetlinkWriter) addKernel(pfx bnet.Prefix) error { + route, err := nw.createRoute(pfx, nw.pathTable[pfx]) if err != nil { - log.Errorf("Error while creating route: %v", err) - return fmt.Errorf("Error while creating route: %v", err) + return fmt.Errorf("Could not create Route: %v", err.Error()) } log.WithFields(log.Fields{ @@ -147,16 +151,16 @@ func (nw *NetlinkWriter) addKernel(pfx bnet.Prefix, path *route.Path) error { } // remove pfx/path from kernel -func (nw *NetlinkWriter) removeKernel(pfx bnet.Prefix, path *route.Path) error { +func (nw *NetlinkWriter) removeKernel(pfx bnet.Prefix, paths []*route.Path) error { + route, err := nw.createRoute(pfx, nw.pathTable[pfx]) + if err != nil { + return fmt.Errorf("Could not create Route: %v", err.Error()) + } + log.WithFields(log.Fields{ "Prefix": pfx.String(), }).Debug("Remove from netlink") - route, err := nw.createRoute(pfx, path) - if err != nil { - return fmt.Errorf("Error while creating route: %v", err) - } - err = netlink.RouteDel(route) if err != nil { return fmt.Errorf("Error while removing route: %v", err) @@ -166,62 +170,29 @@ func (nw *NetlinkWriter) removeKernel(pfx bnet.Prefix, path *route.Path) error { } // create a route from a prefix and a path -func (nw *NetlinkWriter) createRoute(pfx bnet.Prefix, path *route.Path) (*netlink.Route, error) { - if path.Type != route.NetlinkPathType { +func (nw *NetlinkWriter) createRoute(pfx bnet.Prefix, paths []*route.Path) (*netlink.Route, error) { + route := &netlink.Route{ + Dst: pfx.GetIPNet(), + Table: int(nw.options.RoutingTable), // config dependent + Protocol: route.ProtoBio, } - switch path.Type { - case route.NetlinkPathType: - return nw.createRouteFromNetlink(pfx, path) + multiPath := make([]*netlink.NexthopInfo, 0) - case route.BGPPathType: - return nw.createRouteFromBGPPath(pfx, path) - - default: - return nil, fmt.Errorf("PathType %d is not supported for adding to netlink", path.Type) + for _, path := range paths { + nextHop := &netlink.NexthopInfo{ + Gw: path.NextHop().Bytes(), + } + multiPath = append(multiPath, nextHop) } -} - -func (nw *NetlinkWriter) createRouteFromNetlink(pfx bnet.Prefix, path *route.Path) (*netlink.Route, error) { - nlPath := path.NetlinkPath - log.WithFields(log.Fields{ - "Dst": nlPath.Dst, - "Src": nlPath.Src, - "NextHop": nlPath.NextHop, - "Priority": nlPath.Priority, - "Protocol": nlPath.Protocol, - "Type": nlPath.Type, - "Table": nw.options.RoutingTable, - }).Debug("created route") - - return &netlink.Route{ - Dst: nlPath.Dst.GetIPNet(), - Src: nlPath.Src.Bytes(), - Gw: nlPath.NextHop.Bytes(), - Priority: nlPath.Priority, - Type: nlPath.Type, - Table: int(nw.options.RoutingTable), // config dependent - Protocol: route.ProtoBio, // fix - }, nil -} - -func (nw *NetlinkWriter) createRouteFromBGPPath(pfx bnet.Prefix, path *route.Path) (*netlink.Route, error) { - bgpPath := path.BGPPath - - log.WithFields(log.Fields{ - "Dst": pfx, - "NextHop": bgpPath.NextHop, - "Protocol": "BGP", - "BGPIdentifier": bgpPath.BGPIdentifier, - "Table": nw.options.RoutingTable, - }).Debug("created route") - - return &netlink.Route{ - Dst: pfx.GetIPNet(), - Gw: bgpPath.NextHop.Bytes(), - Table: int(nw.options.RoutingTable), // config dependent - Protocol: route.ProtoBio, // fix - }, nil + if len(multiPath) == 1 { + route.Gw = multiPath[0].Gw + } else if len(multiPath) > 1 { + route.MultiPath = multiPath + } else { + return nil, fmt.Errorf("No destination address specified. At least one NextHop has to be specified in path") + } + return route, nil } diff --git a/route/netlink_path.go b/route/netlink_path.go index ebd721cd54e2ed9cc1d4ebeb12b5b5ae897ceae0..cb374fe8c7672d417641b07c22187620feabea58 100644 --- a/route/netlink_path.go +++ b/route/netlink_path.go @@ -21,7 +21,6 @@ const ( // NetlinkPath represents a path learned via Netlink of a route type NetlinkPath struct { - Dst bnet.Prefix Src bnet.IP NextHop bnet.IP // GW Priority int @@ -42,29 +41,29 @@ func NewNlPathFromBgpPath(p *BGPPath) *NetlinkPath { } // NewNlPathFromRoute creates a new NetlinkPath object from a netlink.Route object -func NewNlPathFromRoute(r *netlink.Route, kernel bool) (*NetlinkPath, error) { +func NewPathsFromNlRoute(r netlink.Route, kernel bool) (bnet.Prefix, []*Path, error) { var src bnet.IP var dst bnet.Prefix if r.Src == nil && r.Dst == nil { - return nil, fmt.Errorf("Cannot create NlPath, since source and destination are both nil") + return bnet.Prefix{}, nil, fmt.Errorf("Cannot create NlPath, since source and destination are both nil") } 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) } } @@ -73,39 +72,58 @@ func NewNlPathFromRoute(r *netlink.Route, kernel bool) (*NetlinkPath, error) { dst = bnet.NewPfxFromIPNet(r.Dst) } - nextHop, _ := bnet.IPFromBytes(r.Gw) - - return &NetlinkPath{ - Dst: dst, - Src: src, - NextHop: nextHop, - Priority: r.Priority, - Protocol: r.Protocol, - Type: r.Type, - Table: r.Table, - Kernel: kernel, - }, nil + paths := make([]*Path, 0) + + if len(r.MultiPath) > 0 { + for _, multiPath := range r.MultiPath { + nextHop, _ := bnet.IPFromBytes(multiPath.Gw) + paths = append(paths, &Path{ + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: src, + NextHop: nextHop, + Priority: r.Priority, + Protocol: r.Protocol, + Type: r.Type, + Table: r.Table, + Kernel: kernel, + }, + }) + } + } else { + nextHop, _ := bnet.IPFromBytes(r.Gw) + paths = append(paths, &Path{ + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: src, + NextHop: nextHop, + Priority: r.Priority, + Protocol: r.Protocol, + Type: r.Type, + Table: r.Table, + Kernel: kernel, + }, + }) + } + + return dst, paths, nil } // 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.Dst.Equal(t.Dst) { - return 1 - } - - 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 } @@ -138,7 +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 { - return false + 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 @@ -153,8 +171,7 @@ func (s *NetlinkPath) Copy() *NetlinkPath { // Print all known information about a route in logfile friendly format func (s *NetlinkPath) String() string { - ret := fmt.Sprintf("Destination: %s, ", s.Dst.String()) - ret += fmt.Sprintf("Source: %s, ", s.Src.String()) + ret := fmt.Sprintf("Source: %s, ", s.Src.String()) ret += fmt.Sprintf("NextHop: %s, ", s.NextHop.String()) ret += fmt.Sprintf("Priority: %d, ", s.Priority) ret += fmt.Sprintf("Type: %d, ", s.Type) @@ -165,8 +182,7 @@ func (s *NetlinkPath) String() string { // Print all known information about a route in human readable form func (s *NetlinkPath) Print() string { - ret := fmt.Sprintf("\t\tDestination: %s\n", s.Dst.String()) - ret += fmt.Sprintf("\t\tSource: %s\n", s.Src.String()) + ret := fmt.Sprintf("\t\tSource: %s\n", s.Src.String()) ret += fmt.Sprintf("\t\tNextHop: %s\n", s.NextHop.String()) ret += fmt.Sprintf("\t\tPriority: %d\n", s.Priority) ret += fmt.Sprintf("\t\tType: %d\n", s.Type) diff --git a/route/path_test.go b/route/path_test.go index d409cbd9b6a162e6a7ce42308df11cc94fde0569..84b3cde044750bcc75eb44ac89ff894c619d5cf3 100644 --- a/route/path_test.go +++ b/route/path_test.go @@ -360,16 +360,17 @@ func TestNewNlPath(t *testing.T) { } } -func TestNewNlPathFromNetlinkRoute(t *testing.T) { +func TestNewPathsFromNetlinkRoute(t *testing.T) { tests := []struct { - name string - source *netlink.Route - expected *NetlinkPath - expectError bool + name string + source netlink.Route + expectedPfx bnet.Prefix + expectedPaths []*Path + expectError bool }{ { name: "Simple", - source: &netlink.Route{ + source: netlink.Route{ Dst: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8).GetIPNet(), Src: bnet.IPv4(456).Bytes(), Gw: bnet.IPv4(789).Bytes(), @@ -378,33 +379,81 @@ func TestNewNlPathFromNetlinkRoute(t *testing.T) { Table: 254, Type: 1, }, - expected: &NetlinkPath{ - Dst: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8), - Src: bnet.IPv4(456), - NextHop: bnet.IPv4(789), - Protocol: ProtoKernel, - Priority: 1, - Table: 254, - Type: 1, - Kernel: true, + expectedPfx: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8), + 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, no destination", - source: &netlink.Route{ - Gw: bnet.IPv4(789).Bytes(), + name: "Multiple nexthop", + source: netlink.Route{ + Dst: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8).GetIPNet(), + Src: bnet.IPv4(456).Bytes(), + MultiPath: []*netlink.NexthopInfo{ + { + LinkIndex: 1, + Hops: 1, + Gw: bnet.IPv4(123).Bytes(), + Flags: 0, + NewDst: nil, + Encap: nil, + }, { + LinkIndex: 2, + Hops: 1, + Gw: bnet.IPv4(345).Bytes(), + Flags: 0, + NewDst: nil, + Encap: nil, + }, + }, Protocol: ProtoKernel, Priority: 1, Table: 254, Type: 1, }, - expected: &NetlinkPath{}, - expectError: true, + expectedPfx: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8), + expectedPaths: []*Path{ + { + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: bnet.IPv4(456), + NextHop: bnet.IPv4(123), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + Kernel: true, + }, + }, { + Type: NetlinkPathType, + NetlinkPath: &NetlinkPath{ + Src: bnet.IPv4(456), + NextHop: bnet.IPv4(345), + Protocol: ProtoKernel, + Priority: 1, + Table: 254, + Type: 1, + Kernel: true, + }, + }, + }, + expectError: false, }, { name: "No source but destination", - source: &netlink.Route{ + source: netlink.Route{ Dst: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8).GetIPNet(), Gw: bnet.IPv4(789).Bytes(), Protocol: ProtoKernel, @@ -412,21 +461,26 @@ func TestNewNlPathFromNetlinkRoute(t *testing.T) { Table: 254, Type: 1, }, - expected: &NetlinkPath{ - Dst: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8), - Src: bnet.IPv4FromOctets(0, 0, 0, 0), - NextHop: bnet.IPv4(789), - Protocol: ProtoKernel, - Priority: 1, - Table: 254, - Type: 1, - Kernel: true, + 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{ + source: netlink.Route{ Src: bnet.IPv4(456).Bytes(), Gw: bnet.IPv4(789).Bytes(), Protocol: ProtoKernel, @@ -434,71 +488,382 @@ func TestNewNlPathFromNetlinkRoute(t *testing.T) { Table: 254, Type: 1, }, - expected: &NetlinkPath{ - Dst: bnet.NewPfx(bnet.IPv4FromOctets(0, 0, 0, 0), 0), - Src: bnet.IPv4(456), - NextHop: bnet.IPv4(789), + 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, - Kernel: true, }, - expectError: false, + expectedPfx: bnet.Prefix{}, + expectedPaths: []*Path{}, + expectError: true, }, { name: "No source but destination IPv6", - source: &netlink.Route{ + source: netlink.Route{ Dst: bnet.NewPfx(bnet.IPv6(2001, 0), 48).GetIPNet(), - Gw: bnet.IPv6(2001, 2).Bytes(), + Gw: bnet.IPv6(2001, 123).Bytes(), Protocol: ProtoKernel, Priority: 1, Table: 254, Type: 1, }, - expected: &NetlinkPath{ - Dst: bnet.NewPfx(bnet.IPv6(2001, 0), 48), - 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, + 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, 0).Bytes(), - Gw: bnet.IPv6(2001, 2).Bytes(), + source: netlink.Route{ + Src: bnet.IPv6(2001, 456).Bytes(), + Gw: bnet.IPv6(2001, 789).Bytes(), Protocol: ProtoKernel, Priority: 1, Table: 254, Type: 1, }, - expected: &NetlinkPath{ - Dst: bnet.NewPfx(bnet.IPv6FromBlocks(0, 0, 0, 0, 0, 0, 0, 0), 0), - Src: bnet.IPv6(2001, 0), - NextHop: bnet.IPv6(2001, 2), + 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, - Kernel: true, }, - expectError: false, + expectedPfx: bnet.NewPfx(bnet.IPv4(0), 0), + expectedPaths: []*Path{{}}, + expectError: true, }, } for _, test := range tests { - converted, err := NewNlPathFromRoute(test.source, true) + pfx, paths, err := NewPathsFromNlRoute(test.source, true) if test.expectError { assert.Error(t, err) } else { assert.NoError(t, err) - assert.Equalf(t, test.expected, converted, test.name) + assert.Equalf(t, test.expectedPaths, paths, test.name) + assert.Equalf(t, test.expectedPfx, pfx, test.name) } } } + +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 1c7935e41b9d55912e8a240c1f22afaa18f1616e..b1f975f65fad28e31f3fcd327836ad4f518e0f4f 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()