From 72a60cdf476f60d732e338038b1ac2c399c3b48c Mon Sep 17 00:00:00 2001 From: Oliver Herms <oliver.herms@exaring.de> Date: Fri, 11 May 2018 22:34:40 +0200 Subject: [PATCH] Stabilizing --- main.go | 30 +- protocols/bgp/packet/bgp.go | 7 +- protocols/bgp/packet/nlri.go | 4 +- protocols/bgp/packet/path_attributes.go | 101 ++- protocols/bgp/packet/path_attributes_test.go | 119 ++++ protocols/bgp/server/fsm.go | 65 +- protocols/bgp/server/peer.go | 11 +- protocols/bgp/server/server.go | 6 +- protocols/bgp/server/update_sender.go | 46 +- route/bgp.go | 111 ++- route/path.go | 76 +++ route/path_test.go | 163 +++++ route/route.go | 115 +++- route/route_test.go | 35 + route/static.go | 26 +- routingtable/adjRIBIn/adj_rib_in.go | 10 +- routingtable/adjRIBOut/adj_rib_out.go | 85 +++ routingtable/client_manager.go | 50 +- routingtable/client_manager_test.go | 39 ++ routingtable/locRIB/loc_rib.go | 146 ++++ rt/bgp.go | 160 ----- rt/client_manager.go | 21 - rt/filter.go | 21 - rt/lpm_test.go.dis | 673 ------------------- rt/route.go | 152 ----- rt/route_test.go | 473 ------------- rt/routing_table.go | 470 ------------- rt/routing_table_test.go | 605 ----------------- rt/static.go | 26 - 29 files changed, 1113 insertions(+), 2733 deletions(-) create mode 100644 route/path_test.go create mode 100644 routingtable/adjRIBOut/adj_rib_out.go create mode 100644 routingtable/locRIB/loc_rib.go delete mode 100644 rt/bgp.go delete mode 100644 rt/client_manager.go delete mode 100644 rt/filter.go delete mode 100644 rt/lpm_test.go.dis delete mode 100644 rt/route.go delete mode 100644 rt/route_test.go delete mode 100644 rt/routing_table.go delete mode 100644 rt/routing_table_test.go delete mode 100644 rt/static.go diff --git a/main.go b/main.go index 30aaa128..78171d7c 100644 --- a/main.go +++ b/main.go @@ -4,18 +4,19 @@ import ( "fmt" "net" "sync" + "time" "github.com/sirupsen/logrus" "github.com/bio-routing/bio-rd/config" "github.com/bio-routing/bio-rd/protocols/bgp/server" - "github.com/bio-routing/bio-rd/rt" + "github.com/bio-routing/bio-rd/routingtable/locRIB" ) func main() { fmt.Printf("This is a BGP speaker\n") - VRF := rt.New(true) + rib := locRIB.New() b := server.NewBgpServer() err := b.Start(&config.Global{ @@ -28,26 +29,35 @@ func main() { b.AddPeer(config.Peer{ AdminEnabled: true, LocalAS: 65200, - PeerAS: 65100, - PeerAddress: net.IP([]byte{169, 254, 100, 0}), - LocalAddress: net.IP([]byte{169, 254, 100, 1}), + PeerAS: 65300, + PeerAddress: net.IP([]byte{169, 254, 200, 1}), + LocalAddress: net.IP([]byte{169, 254, 200, 0}), HoldTimer: 90, KeepAlive: 30, Passive: true, RouterID: b.RouterID(), - }, VRF) + }, rib) + + time.Sleep(time.Second * 30) b.AddPeer(config.Peer{ AdminEnabled: true, LocalAS: 65200, - PeerAS: 65300, - PeerAddress: net.IP([]byte{169, 254, 200, 1}), - LocalAddress: net.IP([]byte{169, 254, 200, 0}), + PeerAS: 65100, + PeerAddress: net.IP([]byte{169, 254, 100, 0}), + LocalAddress: net.IP([]byte{169, 254, 100, 1}), HoldTimer: 90, KeepAlive: 30, Passive: true, RouterID: b.RouterID(), - }, VRF) + }, rib) + + go func() { + for { + fmt.Print(rib.Print()) + time.Sleep(time.Second * 10) + } + }() var wg sync.WaitGroup wg.Add(1) diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index be4f30dc..ba1d3d32 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -1,8 +1,9 @@ package packet const ( - OctetLen = 8 - BGP4Version = 4 + OctetLen = 8 + MaxASNsSegment = 255 + BGP4Version = 4 MarkerLen = 16 HeaderLen = 19 @@ -130,7 +131,7 @@ type PathAttribute struct { } type NLRI struct { - IP interface{} + IP uint32 Pfxlen uint8 Next *NLRI } diff --git a/protocols/bgp/packet/nlri.go b/protocols/bgp/packet/nlri.go index 42c7a65e..05fecbff 100644 --- a/protocols/bgp/packet/nlri.go +++ b/protocols/bgp/packet/nlri.go @@ -57,12 +57,12 @@ func decodeNLRI(buf *bytes.Buffer) (*NLRI, uint8, error) { addr[i] = 0 } } - nlri.IP = addr + nlri.IP = fourBytesToUint32(addr) return nlri, toCopy + 1, nil } func (n *NLRI) serialize(buf *bytes.Buffer) uint8 { - a := convert.Uint32Byte(n.IP.(uint32)) + a := convert.Uint32Byte(n.IP) addr := [4]byte{a[0], a[1], a[2], a[3]} nBytes := bytesInAddr(n.Pfxlen) diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index 50c0cdb5..c305ec27 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -3,6 +3,8 @@ package packet import ( "bytes" "fmt" + "strconv" + "strings" "github.com/taktv6/tflow2/convert" ) @@ -156,7 +158,7 @@ func (pa *PathAttribute) decodeNextHop(buf *bytes.Buffer) error { return fmt.Errorf("Unable to read next hop: buf.Read read %d bytes", n) } - pa.Value = addr + pa.Value = fourBytesToUint32(addr) p += 4 return dumpNBytes(buf, pa.Length-p) @@ -354,8 +356,8 @@ func (pa *PathAttribute) serializeNextHop(buf *bytes.Buffer) uint8 { buf.WriteByte(NextHopAttr) length := uint8(4) buf.WriteByte(length) - addr := pa.Value.([4]byte) - buf.Write(addr[:]) + addr := pa.Value.(uint32) + buf.Write(convert.Uint32Byte(addr)) return 7 } @@ -402,3 +404,96 @@ func (pa *PathAttribute) serializeAggregator(buf *bytes.Buffer) uint8 { buf.Write(convert.Uint16Byte(pa.Value.(uint16))) return 5 } + +/*func (pa *PathAttribute) PrependASPath(prepend []uint32) { + if pa.TypeCode != ASPathAttr { + return + } + + asPath := pa.Value.(ASPath) + asPathSegementCount := len(asPath) + currentSegment := asPathSegementCount - 1 + + newSegmentNeeded := false + if asPath[asPathSegementCount-1].Type == ASSequence { + newSegmentNeeded = true + } else { + if len(asPath[asPathSegementCount-1].ASNs) >= MaxASNsSegment { + newSegmentNeeded = true + } + } + + for _, asn := range prepend { + if newSegmentNeeded { + segment := ASPathSegment{ + Type: ASSequence, + ASNs: make([]uint32, 0), + }, + } + + asPath[currentSegment].ASNs = append(asPath[currentSegment].ASNs, asn) + if len(asPath[asPathSegementCount-1].ASNs) >= MaxASNsSegment { + newSegmentNeeded = true + } + } + +}*/ + +// ParseASPathStr converts an AS path from string representation info an PathAttribute object +func ParseASPathStr(asPathString string) (*PathAttribute, error) { + asPath := ASPath{} + + currentType := ASSequence + newSegmentNeeded := true + currentSegment := -1 + for _, asn := range strings.Split(asPathString, " ") { + if isBeginOfASSet(asn) { + currentType = ASSet + newSegmentNeeded = true + asn = strings.Replace(asn, "(", "", 1) + } + + if newSegmentNeeded { + seg := ASPathSegment{ + Type: uint8(currentType), + ASNs: make([]uint32, 0), + } + asPath = append(asPath, seg) + currentSegment++ + newSegmentNeeded = false + } + + if isEndOfASSset(asn) { + currentType = ASSequence + newSegmentNeeded = true + asn = strings.Replace(asn, ")", "", 1) + } + + numericASN, err := strconv.Atoi(asn) + if err != nil { + return nil, fmt.Errorf("Unable to convert ASN: %v", err) + } + asPath[currentSegment].ASNs = append(asPath[currentSegment].ASNs, uint32(numericASN)) + + if len(asPath[currentSegment].ASNs) == MaxASNsSegment { + newSegmentNeeded = true + } + } + + return &PathAttribute{ + TypeCode: ASPathAttr, + Value: asPath, + }, nil +} + +func isBeginOfASSet(asPathPart string) bool { + return strings.Contains(asPathPart, "(") +} + +func isEndOfASSset(asPathPart string) bool { + return strings.Contains(asPathPart, ")") +} + +func fourBytesToUint32(address [4]byte) uint32 { + return uint32(address[0])<<24 + uint32(address[1])<<16 + uint32(address[2])<<8 + uint32(address[3]) +} diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index 247028cb..140752e5 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -1291,3 +1291,122 @@ func TestSerialize(t *testing.T) { assert.Equal(t, test.expected, res) } } + +func TestParseASPathStr(t *testing.T) { + tests := []struct { + name string + input string + wantFail bool + expected *PathAttribute + }{ + { + name: "Simple AS_SEQUENCE", + input: "3320 15169", + wantFail: false, + expected: &PathAttribute{ + TypeCode: ASPathAttr, + Value: ASPath{ + ASPathSegment{ + Type: ASSequence, + ASNs: []uint32{3320, 15169}, + }, + }, + }, + }, + { + name: "AS_SEQUENCE with more than 255 elements", + input: "123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123", + wantFail: false, + expected: &PathAttribute{ + TypeCode: ASPathAttr, + Value: ASPath{ + ASPathSegment{ + Type: ASSequence, + ASNs: []uint32{123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123}, + }, + ASPathSegment{ + Type: ASSequence, + ASNs: []uint32{123, 123, 123, 123, 123}, + }, + }, + }, + }, + { + name: "AS_SET only", + input: "(3320 201701 15169)", + wantFail: false, + expected: &PathAttribute{ + TypeCode: ASPathAttr, + Value: ASPath{ + ASPathSegment{ + Type: ASSet, + ASNs: []uint32{3320, 201701, 15169}, + }, + }, + }, + }, + { + name: "Mixed AS Path", + input: "199714 51324 (3320 201701 15169)", + wantFail: false, + expected: &PathAttribute{ + TypeCode: ASPathAttr, + Value: ASPath{ + ASPathSegment{ + Type: ASSequence, + ASNs: []uint32{199714, 51324}, + }, + ASPathSegment{ + Type: ASSet, + ASNs: []uint32{3320, 201701, 15169}, + }, + }, + }, + }, + } + + for _, test := range tests { + res, err := ParseASPathStr(test.input) + if err != nil { + if test.wantFail { + continue + } + + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue + } + + if test.wantFail { + t.Errorf("Unexpected success for test %q", test.name) + continue + } + + assert.Equal(t, test.expected, res) + } +} + +func TestFourBytesToUint32(t *testing.T) { + tests := []struct { + name string + input [4]byte + expected uint32 + }{ + { + name: "Test #1", + input: [4]byte{0, 0, 0, 200}, + expected: 200, + }, + { + name: "Test #2", + input: [4]byte{1, 0, 0, 200}, + expected: 16777416, + }, + } + + for _, test := range tests { + res := fourBytesToUint32(test.input) + if res != test.expected { + t.Errorf("Unexpected result for test %q: Got: %d Want: %d", test.name, res, test.expected) + } + } +} diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go index a65d9570..721c7faf 100644 --- a/protocols/bgp/server/fsm.go +++ b/protocols/bgp/server/fsm.go @@ -7,12 +7,16 @@ import ( "net" "time" + "github.com/bio-routing/bio-rd/routingtable" + "github.com/bio-routing/bio-rd/config" tnet "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/protocols/bgp/packet" - "github.com/bio-routing/bio-rd/rt" + "github.com/bio-routing/bio-rd/route" + "github.com/bio-routing/bio-rd/routingtable/adjRIBIn" + "github.com/bio-routing/bio-rd/routingtable/adjRIBOut" + "github.com/bio-routing/bio-rd/routingtable/locRIB" log "github.com/sirupsen/logrus" - "github.com/taktv6/tflow2/convert" tomb "gopkg.in/tomb.v2" ) @@ -83,9 +87,9 @@ type FSM struct { msgRecvFailCh chan msgRecvErr stopMsgRecvCh chan struct{} - adjRIBIn *rt.RT - adjRIBOut *rt.RT - vrf *rt.RT + adjRIBIn *adjRIBIn.AdjRIBIn + adjRIBOut *adjRIBOut.AdjRIBOut + rib *locRIB.LocRIB updateSender *UpdateSender } @@ -99,7 +103,7 @@ type msgRecvErr struct { con *net.TCPConn } -func NewFSM(c config.Peer, vrf *rt.RT) *FSM { +func NewFSM(c config.Peer, rib *locRIB.LocRIB) *FSM { fsm := &FSM{ state: Idle, passive: true, @@ -124,7 +128,7 @@ func NewFSM(c config.Peer, vrf *rt.RT) *FSM { conCh: make(chan *net.TCPConn), conErrCh: make(chan error), initiateCon: make(chan struct{}), - vrf: vrf, + rib: rib, } fsm.updateSender = newUpdateSender(fsm) @@ -208,7 +212,7 @@ func (fsm *FSM) main() error { func (fsm *FSM) idle() int { if fsm.adjRIBOut != nil { - fsm.vrf.Unregister(fsm.adjRIBOut) + fsm.rib.Unregister(fsm.adjRIBOut) fsm.adjRIBOut.Unregister(fsm.updateSender) } fsm.adjRIBIn = nil @@ -653,23 +657,31 @@ func (fsm *FSM) openConfirmTCPFail(err error) int { } func (fsm *FSM) established() int { - fsm.adjRIBIn = rt.New(false) - fsm.adjRIBIn.Register(fsm.vrf) + fsm.adjRIBIn = adjRIBIn.New() + fsm.adjRIBIn.Register(fsm.rib) - fsm.adjRIBOut = rt.New(false) + fsm.adjRIBOut = adjRIBOut.New() fsm.adjRIBOut.Register(fsm.updateSender) - fsm.vrf.Register(fsm.adjRIBOut) + fsm.rib.RegisterWithOptions(fsm.adjRIBOut, routingtable.ClientOptions{BestOnly: true}) - go func() { + /*go func() { for { time.Sleep(time.Second * 10) fmt.Printf("Dumping AdjRibIn\n") - routes := fsm.adjRIBIn.Dump() + routes := fsm.adjRIBIn.RT().Dump() for _, route := range routes { - fmt.Printf("LPM: %s\n", route.Prefix().String()) + fmt.Print(route.Print()) } } + }()*/ + + go func() { + for { + fmt.Printf("ADJ-RIB-OUT: %s\n", fsm.remote.String()) + fmt.Print(fsm.adjRIBOut.Print()) + time.Sleep(time.Second * 11) + } }() for { @@ -712,6 +724,7 @@ func (fsm *FSM) established() int { case recvMsg := <-fsm.msgRecvCh: msg, err := packet.Decode(bytes.NewBuffer(recvMsg.msg)) if err != nil { + fmt.Printf("Failed to decode BGP message: %v\n", recvMsg.msg) switch bgperr := err.(type) { case packet.BGPError: sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode) @@ -735,23 +748,22 @@ func (fsm *FSM) established() int { u := msg.Body.(*packet.BGPUpdate) for r := u.WithdrawnRoutes; r != nil; r = r.Next { - x := r.IP.([4]byte) - pfx := tnet.NewPfx(convert.Uint32b(x[:]), r.Pfxlen) + pfx := tnet.NewPfx(r.IP, r.Pfxlen) fmt.Printf("LPM: Removing prefix %s\n", pfx.String()) - fsm.adjRIBIn.RemoveRoute(pfx) + fsm.adjRIBIn.RemovePath(pfx, nil) } for r := u.NLRI; r != nil; r = r.Next { - x := r.IP.([4]byte) - pfx := tnet.NewPfx(convert.Uint32b(x[:]), r.Pfxlen) + pfx := tnet.NewPfx(r.IP, r.Pfxlen) fmt.Printf("LPM: Adding prefix %s\n", pfx.String()) - path := &rt.Path{ - Type: rt.BGPPathType, - BGPPath: &rt.BGPPath{}, + path := &route.Path{ + Type: route.BGPPathType, + BGPPath: &route.BGPPath{}, } - for pa := u.PathAttributes; pa.Next != nil; pa = pa.Next { + for pa := u.PathAttributes; pa != nil; pa = pa.Next { + fmt.Printf("TypeCode: %d\n", pa.TypeCode) switch pa.TypeCode { case packet.OriginAttr: path.BGPPath.Origin = pa.Value.(uint8) @@ -760,15 +772,14 @@ func (fsm *FSM) established() int { case packet.MEDAttr: path.BGPPath.MED = pa.Value.(uint32) case packet.NextHopAttr: + fmt.Printf("RECEIVED NEXT_HOP: %d\n", pa.Value.(uint32)) path.BGPPath.NextHop = pa.Value.(uint32) case packet.ASPathAttr: path.BGPPath.ASPath = pa.ASPathString() path.BGPPath.ASPathLen = pa.ASPathLen() } } - // TO BE USED WITH BGP ADD PATH: - // fsm.adjRIBIn.AddPath(rt.NewRoute(pfx, []*rt.Path{path})) - fsm.adjRIBIn.ReplaceRoute(rt.NewRoute(pfx, []*rt.Path{path})) + fsm.adjRIBIn.AddPath(pfx, path) } continue diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go index 4371d5d4..5c65732b 100644 --- a/protocols/bgp/server/peer.go +++ b/protocols/bgp/server/peer.go @@ -3,24 +3,25 @@ package server import ( "net" + "github.com/bio-routing/bio-rd/routingtable/locRIB" + "github.com/bio-routing/bio-rd/config" - "github.com/bio-routing/bio-rd/rt" ) type Peer struct { addr net.IP asn uint32 fsm *FSM - vrf *rt.RT + rib *locRIB.LocRIB routerID uint32 } -func NewPeer(c config.Peer, vrf *rt.RT) (*Peer, error) { +func NewPeer(c config.Peer, rib *locRIB.LocRIB) (*Peer, error) { p := &Peer{ addr: c.PeerAddress, asn: c.PeerAS, - fsm: NewFSM(c, vrf), - vrf: vrf, + fsm: NewFSM(c, rib), + rib: rib, } return p, nil } diff --git a/protocols/bgp/server/server.go b/protocols/bgp/server/server.go index 3603c164..8a11cd3a 100644 --- a/protocols/bgp/server/server.go +++ b/protocols/bgp/server/server.go @@ -8,7 +8,7 @@ import ( "github.com/bio-routing/bio-rd/config" "github.com/bio-routing/bio-rd/protocols/bgp/packet" - "github.com/bio-routing/bio-rd/rt" + "github.com/bio-routing/bio-rd/routingtable/locRIB" log "github.com/sirupsen/logrus" ) @@ -84,12 +84,12 @@ func (b *BGPServer) incomingConnectionWorker() { } } -func (b *BGPServer) AddPeer(c config.Peer, vrf *rt.RT) error { +func (b *BGPServer) AddPeer(c config.Peer, rib *locRIB.LocRIB) error { if c.LocalAS > uint16max || c.PeerAS > uint16max { return fmt.Errorf("32bit ASNs are not supported yet") } - peer, err := NewPeer(c, vrf) + peer, err := NewPeer(c, rib) if err != nil { return err } diff --git a/protocols/bgp/server/update_sender.go b/protocols/bgp/server/update_sender.go index 275b326f..329e9713 100644 --- a/protocols/bgp/server/update_sender.go +++ b/protocols/bgp/server/update_sender.go @@ -7,7 +7,8 @@ import ( "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/protocols/bgp/packet" - "github.com/bio-routing/bio-rd/rt" + "github.com/bio-routing/bio-rd/route" + "github.com/bio-routing/bio-rd/routingtable" ) type UpdateSender struct { @@ -20,50 +21,51 @@ func newUpdateSender(fsm *FSM) *UpdateSender { } } -func (u *UpdateSender) AddPath(route *rt.Route) { - log.Warningf("BGP Update Sender: AddPath not implemented") +func (u *UpdateSender) AddPath(pfx net.Prefix, p *route.Path) error { + fmt.Printf("SENDING AN BGP UPDATE\n") + asPathPA, err := packet.ParseASPathStr(fmt.Sprintf("%d %s", u.fsm.localASN, p.BGPPath.ASPath)) + if err != nil { + return fmt.Errorf("Unable to parse AS path: %v", err) + } update := &packet.BGPUpdate{ PathAttributes: &packet.PathAttribute{ TypeCode: packet.OriginAttr, - Value: uint8(packet.IGP), + Value: p.BGPPath.Origin, Next: &packet.PathAttribute{ TypeCode: packet.ASPathAttr, - Value: packet.ASPath{ - { - Type: 2, - ASNs: []uint32{15169, 3329}, - }, - }, + Value: asPathPA.Value, Next: &packet.PathAttribute{ TypeCode: packet.NextHopAttr, - Value: [4]byte{100, 110, 120, 130}, + Value: p.BGPPath.NextHop, }, }, }, NLRI: &packet.NLRI{ - IP: route.Prefix().Addr(), - Pfxlen: route.Pfxlen(), + IP: pfx.Addr(), + Pfxlen: pfx.Pfxlen(), }, } updateBytes, err := update.SerializeUpdate() if err != nil { log.Errorf("Unable to serialize BGP Update: %v", err) - return + return nil } fmt.Printf("Sending Update: %v\n", updateBytes) - u.fsm.con.Write(updateBytes) -} - -func (u *UpdateSender) ReplaceRoute(*rt.Route) { - log.Warningf("BGP Update Sender: ReplaceRoute not implemented") + _, err = u.fsm.con.Write(updateBytes) + if err != nil { + return fmt.Errorf("Failed sending Update: %v", err) + } + return nil } -func (u *UpdateSender) RemovePath(*rt.Route) { +func (u *UpdateSender) RemovePath(pfx net.Prefix, p *route.Path) bool { log.Warningf("BGP Update Sender: RemovePath not implemented") + return false } -func (u *UpdateSender) RemoveRoute(*net.Prefix) { - log.Warningf("BGP Update Sender: RemoveRoute not implemented") +func (u *UpdateSender) UpdateNewClient(client routingtable.RouteTableClient) error { + log.Warningf("BGP Update Sender: RemovePath not implemented") + return nil } diff --git a/route/bgp.go b/route/bgp.go index 603fa3f6..47f7db4f 100644 --- a/route/bgp.go +++ b/route/bgp.go @@ -1,5 +1,11 @@ package route +import ( + "fmt" + + "github.com/taktv6/tflow2/convert" +) + // BGPPath represents a set of BGP path attributes type BGPPath struct { PathIdentifier uint32 @@ -14,38 +20,62 @@ type BGPPath struct { Source uint32 } -func (r *Route) bgpPathSelection() (best *Path, active []*Path) { - // TODO: Implement next hop lookup and compare IGP metrics - for _, p := range r.paths { - if p.Type != BGPPathType { - continue - } +// ECMP determines if routes b and c are euqal in terms of ECMP +func (b *BGPPath) ECMP(c *BGPPath) bool { + return b.LocalPref == c.LocalPref && b.ASPathLen == c.ASPathLen && b.MED == c.MED && b.Origin == c.Origin +} - if len(active) == 0 { - active = append(active, p) - best = p - continue - } +// Compare returns negative if b < c, 0 if paths are equal, positive if b > c +func (b *BGPPath) Compare(c *BGPPath) int8 { + if c.LocalPref < b.LocalPref { + return 1 + } - if active[0].BGPPath.ecmp(p.BGPPath) { - active = append(active, p) - if !r.bestPath.BGPPath.better(p.BGPPath) { - continue - } + if c.LocalPref > b.LocalPref { + return -1 + } - best = p - continue - } + if c.ASPathLen > b.ASPathLen { + return 1 + } - if !active[0].BGPPath.betterECMP(p.BGPPath) { - continue - } + if c.ASPathLen < b.ASPathLen { + return -1 + } - active = []*Path{p} - best = p + if c.Origin > b.Origin { + return 1 + } + + if c.Origin < b.Origin { + return -1 } - return best, active + if c.MED > b.MED { + return 1 + } + + if c.MED < b.MED { + return -1 + } + + if c.BGPIdentifier < b.BGPIdentifier { + return 1 + } + + if c.BGPIdentifier > b.BGPIdentifier { + return -1 + } + + if c.Source < b.Source { + return 1 + } + + if c.Source > b.Source { + return -1 + } + + return 0 } func (b *BGPPath) betterECMP(c *BGPPath) bool { @@ -103,3 +133,34 @@ func (b *BGPPath) better(c *BGPPath) bool { func (b *BGPPath) ecmp(c *BGPPath) bool { return b.LocalPref == c.LocalPref && b.ASPathLen == c.ASPathLen && b.Origin == c.Origin && b.MED == c.MED } + +func (b *BGPPath) Print() string { + origin := "" + switch b.Origin { + case 0: + origin = "Incomplete" + case 1: + origin = "EGP" + case 2: + origin = "IGP" + } + ret := fmt.Sprintf("\t\tLocal Pref: %d\n", b.LocalPref) + ret += fmt.Sprintf("\t\tOrigin: %s\n", origin) + ret += fmt.Sprintf("\t\tAS Path: %s\n", b.ASPath) + nh := uint32To4Byte(b.NextHop) + ret += fmt.Sprintf("\t\tNEXT HOP: %d.%d.%d.%d\n", nh[0], nh[1], nh[2], nh[3]) + ret += fmt.Sprintf("\t\tMED: %d\n", b.MED) + + return ret +} + +func uint32To4Byte(addr uint32) [4]byte { + slice := convert.Uint32Byte(addr) + ret := [4]byte{ + slice[0], + slice[1], + slice[2], + slice[3], + } + return ret +} diff --git a/route/path.go b/route/path.go index 23a93889..544b83b4 100644 --- a/route/path.go +++ b/route/path.go @@ -1,11 +1,44 @@ package route +import "fmt" + type Path struct { Type uint8 StaticPath *StaticPath BGPPath *BGPPath } +// Compare returns negative if p < q, 0 if paths are equal, positive if p > q +func (p *Path) Compare(q *Path) int8 { + if p.Type > q.Type { + return 1 + } + + if p.Type < q.Type { + return -1 + } + + switch p.Type { + case BGPPathType: + return p.BGPPath.Compare(q.BGPPath) + case StaticPathType: + return p.StaticPath.Compare(q.StaticPath) + } + + panic("Unknown path type") +} + +func (p *Path) ECMP(q *Path) bool { + switch p.Type { + case BGPPathType: + return p.BGPPath.ECMP(q.BGPPath) + case StaticPathType: + return p.StaticPath.ECMP(q.StaticPath) + } + + panic("Unknown path type") +} + func (p *Path) Equal(q *Path) bool { if p == nil || q == nil { return false @@ -24,3 +57,46 @@ func (p *Path) Equal(q *Path) bool { return true } + +// PathsDiff gets the list of elements contained by a but not b +func PathsDiff(a, b []*Path) []*Path { + ret := make([]*Path, 0) + + for _, pa := range a { + if !pathsContains(pa, b) { + ret = append(ret, pa) + } + } + + return ret +} + +func pathsContains(needle *Path, haystack []*Path) bool { + for _, p := range haystack { + if p == needle { + return true + } + } + + return false +} + +func (p *Path) Print() string { + protocol := "" + switch p.Type { + case StaticPathType: + protocol = "static" + case BGPPathType: + protocol = "BGP" + } + + ret := fmt.Sprintf("\tProtocol: %s\n", protocol) + switch p.Type { + case StaticPathType: + ret += "Not implemented yet" + case BGPPathType: + ret += p.BGPPath.Print() + } + + return ret +} diff --git a/route/path_test.go b/route/path_test.go new file mode 100644 index 00000000..b7545431 --- /dev/null +++ b/route/path_test.go @@ -0,0 +1,163 @@ +package route + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPathsDiff(t *testing.T) { + tests := []struct { + name string + any []*Path + a []int + b []int + expected []*Path + }{ + { + name: "Equal", + any: []*Path{ + { + Type: 10, + }, + { + Type: 20, + }, + }, + a: []int{ + 0, 1, + }, + b: []int{ + 0, 1, + }, + expected: []*Path{}, + }, + { + name: "Left empty", + any: []*Path{ + { + Type: 10, + }, + { + Type: 20, + }, + }, + a: []int{}, + b: []int{ + 0, 1, + }, + expected: []*Path{}, + }, + { + name: "Right empty", + any: []*Path{ + { + Type: 10, + }, + { + Type: 20, + }, + }, + a: []int{0, 1}, + b: []int{}, + expected: []*Path{ + { + Type: 10, + }, + { + Type: 20, + }, + }, + }, + { + name: "Disjunkt", + any: []*Path{ + { + Type: 10, + }, + { + Type: 20, + }, + { + Type: 30, + }, + { + Type: 40, + }, + }, + a: []int{0, 1}, + b: []int{2, 3}, + expected: []*Path{{ + Type: 10, + }, + { + Type: 20, + }}, + }, + } + + for _, test := range tests { + listA := make([]*Path, 0) + for _, i := range test.a { + listA = append(listA, test.any[i]) + } + + listB := make([]*Path, 0) + for _, i := range test.b { + listB = append(listB, test.any[i]) + } + + res := PathsDiff(listA, listB) + assert.Equal(t, test.expected, res) + } +} + +func TestPathsContains(t *testing.T) { + tests := []struct { + name string + needle int + haystack []*Path + expected bool + }{ + { + name: "Existent", + needle: 0, + haystack: []*Path{ + { + Type: 100, + }, + { + Type: 200, + }, + }, + expected: true, + }, + { + name: "Non existent", + needle: -1, + haystack: []*Path{ + { + Type: 100, + }, + { + Type: 200, + }, + }, + expected: false, + }, + } + + for _, test := range tests { + var needle *Path + if test.needle >= 0 { + needle = test.haystack[test.needle] + } else { + needle = &Path{} + } + + res := pathsContains(needle, test.haystack) + if res != test.expected { + t.Errorf("Unexpected result for test %q: %v", test.name, res) + } + } +} diff --git a/route/route.go b/route/route.go index 90bc3972..562f4f91 100644 --- a/route/route.go +++ b/route/route.go @@ -1,6 +1,8 @@ package route import ( + "fmt" + "sort" "sync" "github.com/bio-routing/bio-rd/net" @@ -20,15 +22,14 @@ const ISISPathType = 4 // Route links a prefix to paths type Route struct { - pfx net.Prefix - mu sync.Mutex - bestPath *Path - activePaths []*Path - paths []*Path + pfx net.Prefix + mu sync.Mutex + paths []*Path + ecmpPaths uint } -// NewRoute generates a new route -func NewRoute(pfx net.Prefix, p *Path) *Route { +// NewRoute generates a new route with paths p +func NewRoute(pfx net.Prefix, p ...*Path) *Route { r := &Route{ pfx: pfx, } @@ -38,10 +39,21 @@ func NewRoute(pfx net.Prefix, p *Path) *Route { return r } - r.paths = []*Path{p} + r.paths = append(r.paths, p...) return r } +// Copy returns a copy of route r +func (r *Route) Copy() *Route { + new := &Route{ + pfx: r.pfx, + ecmpPaths: r.ecmpPaths, + } + new.paths = make([]*Path, len(r.paths)) + copy(new.paths, r.paths) + return new +} + // Prefix gets the prefix of route `r` func (r *Route) Prefix() net.Prefix { return r.pfx @@ -60,7 +72,7 @@ func (r *Route) Pfxlen() uint8 { // Paths returns a copy of the list of paths associated with route r func (r *Route) Paths() []*Path { if r.paths == nil { - return nil + return []*Path{} } ret := make([]*Path, len(r.paths)) @@ -68,6 +80,34 @@ func (r *Route) Paths() []*Path { return ret } +// ECMPPathCount returns the count of ecmp paths for route r +func (r *Route) ECMPPathCount() uint { + return r.ecmpPaths +} + +// ECMPPaths returns a copy of the list of paths associated with route r +func (r *Route) ECMPPaths() []*Path { + r.mu.Lock() + defer r.mu.Unlock() + + if len(r.paths) == 0 { + return nil + } + + ret := make([]*Path, r.ecmpPaths) + copy(ret, r.paths[0:r.ecmpPaths]) + return ret +} + +// BestPath returns the current best path. nil if non exists +func (r *Route) BestPath() *Path { + if len(r.paths) == 0 { + return nil + } + + return r.paths[0] +} + // AddPath adds path p to route r func (r *Route) AddPath(p *Path) { if p == nil { @@ -109,3 +149,60 @@ func removePath(paths []*Path, remove *Path) []*Path { copy(paths[i:], paths[i+1:]) return paths[:len(paths)-1] } + +// PathSelection recalculates the best path + active paths +func (r *Route) PathSelection() { + r.mu.Lock() + defer r.mu.Unlock() + + sort.Slice(r.paths, func(i, j int) bool { + return r.paths[i].Compare(r.paths[j]) == -1 + }) + + r.updateEqualPathCount() +} + +func (r *Route) updateEqualPathCount() { + count := uint(1) + for i := 0; i < len(r.paths)-1; i++ { + if !r.paths[i].ECMP(r.paths[i+1]) { + break + } + count++ + } + + r.ecmpPaths = count +} + +func getBestProtocol(paths []*Path) uint8 { + best := uint8(0) + for _, p := range paths { + if best == 0 { + best = p.Type + continue + } + + if p.Type < best { + best = p.Type + } + } + + return best +} + +// Print returns a prinatble representation of route `r` +func (r *Route) Print() string { + ret := fmt.Sprintf("%s:\n", r.pfx.String()) + //ret += fmt.Sprintf("Best path:\n") + //ret += r.bestPath.Print() + /*ret += fmt.Sprintf("Active Paths:\n") + for _, p := range r.activePaths { + ret += p.Print() + }*/ + ret += fmt.Sprintf("All Paths:\n") + for _, p := range r.paths { + ret += p.Print() + } + + return ret +} diff --git a/route/route_test.go b/route/route_test.go index 598a38d7..86ff4346 100644 --- a/route/route_test.go +++ b/route/route_test.go @@ -262,6 +262,41 @@ func TestRouteRemovePath(t *testing.T) { } } +func TestCopy(t *testing.T) { + tests := []struct { + name string + route *Route + expected *Route + }{ + { + name: "", + route: &Route{ + pfx: net.NewPfx(1000, 8), + ecmpPaths: 2, + paths: []*Path{ + { + Type: 100, + }, + }, + }, + expected: &Route{ + pfx: net.NewPfx(1000, 8), + ecmpPaths: 2, + paths: []*Path{ + { + Type: 100, + }, + }, + }, + }, + } + + for _, test := range tests { + res := test.route.Copy() + assert.Equal(t, test.expected, res) + } +} + func strAddr(s string) uint32 { ret, _ := net.StrToAddr(s) return ret diff --git a/route/static.go b/route/static.go index 1ae8b302..483138f0 100644 --- a/route/static.go +++ b/route/static.go @@ -5,23 +5,21 @@ type StaticPath struct { NextHop uint32 } -func (r *Route) staticPathSelection() (best *Path, active []*Path) { - if r.paths == nil { - return nil, nil - } - +func (r *Route) staticPathSelection() { if len(r.paths) == 0 { - return nil, nil + return } - for _, p := range r.paths { - if p.Type != StaticPathType { - continue - } + r.ecmpPaths = uint(len(r.paths)) + return +} - active = append(active, p) - best = p - } +// Compare returns negative if s < t, 0 if paths are equal, positive if s > t +func (s *StaticPath) Compare(t *StaticPath) int8 { + return 0 +} - return +// ECMP determines if path s and t are equal in terms of ECMP +func (s *StaticPath) ECMP(t *StaticPath) bool { + return true } diff --git a/routingtable/adjRIBIn/adj_rib_in.go b/routingtable/adjRIBIn/adj_rib_in.go index 33366d21..1427278b 100644 --- a/routingtable/adjRIBIn/adj_rib_in.go +++ b/routingtable/adjRIBIn/adj_rib_in.go @@ -10,13 +10,13 @@ import ( // AdjRIBIn represents an Adjacency RIB In as described in RFC4271 type AdjRIBIn struct { - rt *routingtable.RoutingTable routingtable.ClientManager + rt *routingtable.RoutingTable mu sync.RWMutex } -// NewAdjRIBIn creates a new Adjacency RIB In -func NewAdjRIBIn() *AdjRIBIn { +// New creates a new Adjacency RIB In +func New() *AdjRIBIn { a := &AdjRIBIn{ rt: routingtable.NewRoutingTable(), } @@ -79,3 +79,7 @@ func (a *AdjRIBIn) removePathsFromClients(pfx net.Prefix, paths []*route.Path) { } } } + +func (a *AdjRIBIn) RT() *routingtable.RoutingTable { + return a.rt +} diff --git a/routingtable/adjRIBOut/adj_rib_out.go b/routingtable/adjRIBOut/adj_rib_out.go new file mode 100644 index 00000000..580b86b8 --- /dev/null +++ b/routingtable/adjRIBOut/adj_rib_out.go @@ -0,0 +1,85 @@ +package adjRIBOut + +import ( + "fmt" + "sync" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/bio-routing/bio-rd/routingtable" +) + +// AdjRIBOut represents an Adjacency RIB In as described in RFC4271 +type AdjRIBOut struct { + rt *routingtable.RoutingTable + routingtable.ClientManager + mu sync.RWMutex +} + +// New creates a new Adjacency RIB In +func New() *AdjRIBOut { + a := &AdjRIBOut{ + rt: routingtable.NewRoutingTable(), + } + a.ClientManager = routingtable.NewClientManager(a) + return a +} + +// UpdateNewClient sends current state to a new client +func (a *AdjRIBOut) UpdateNewClient(client routingtable.RouteTableClient) error { + return fmt.Errorf("Not supported") +} + +// AddPath replaces the path for prefix `pfx`. If the prefix doesn't exist it is added. +func (a *AdjRIBOut) AddPath(pfx net.Prefix, p *route.Path) error { + a.mu.Lock() + defer a.mu.Unlock() + + oldPaths := a.rt.ReplacePath(pfx, p) + a.removePathsFromClients(pfx, oldPaths) + + for _, client := range a.ClientManager.Clients() { + client.AddPath(pfx, p) + } + return nil +} + +// RemovePath removes the path for prefix `pfx` +func (a *AdjRIBOut) RemovePath(pfx net.Prefix, p *route.Path) bool { + a.mu.Lock() + defer a.mu.Unlock() + + r := a.rt.Get(pfx) + if r == nil { + return false + } + + oldPaths := r.Paths() + for _, path := range oldPaths { + a.rt.RemovePath(pfx, path) + } + + a.removePathsFromClients(pfx, oldPaths) + return true +} + +func (a *AdjRIBOut) removePathsFromClients(pfx net.Prefix, paths []*route.Path) { + for _, path := range paths { + for _, client := range a.ClientManager.Clients() { + client.RemovePath(pfx, path) + } + } +} + +func (a *AdjRIBOut) Print() string { + a.mu.RLock() + defer a.mu.RUnlock() + + ret := fmt.Sprintf("DUMPING ADJ-RIB-OUT:\n") + routes := a.rt.Dump() + for _, r := range routes { + ret += fmt.Sprintf("%s\n", r.Prefix().String()) + } + + return ret +} diff --git a/routingtable/client_manager.go b/routingtable/client_manager.go index bb86111d..ee0275c3 100644 --- a/routingtable/client_manager.go +++ b/routingtable/client_manager.go @@ -1,25 +1,63 @@ package routingtable +import ( + "sync" +) + +// ClientOptions represents options for a client +type ClientOptions struct { + BestOnly bool + EcmpOnly bool + MaxPaths uint +} + +// GetMaxPaths calculates the maximum amount of wanted paths given that ecmpPaths paths exist +func (c *ClientOptions) GetMaxPaths(ecmpPaths uint) uint { + if c.BestOnly { + return 1 + } + + if c.EcmpOnly { + return ecmpPaths + } + + return c.MaxPaths +} + // ClientManager manages clients of routing tables (observer pattern) type ClientManager struct { - clients map[RouteTableClient]struct{} // Ensures a client registers at most once + clients map[RouteTableClient]ClientOptions master RouteTableClient + mu sync.RWMutex } // NewClientManager creates and initializes a new client manager func NewClientManager(master RouteTableClient) ClientManager { return ClientManager{ - clients: make(map[RouteTableClient]struct{}, 0), + clients: make(map[RouteTableClient]ClientOptions, 0), master: master, } } +// GetOptions gets the options for a registred client +func (c *ClientManager) GetOptions(client RouteTableClient) ClientOptions { + c.mu.RLock() + defer c.mu.RUnlock() + + return c.clients[client] +} + // Register registers a client for updates func (c *ClientManager) Register(client RouteTableClient) { - if c.clients == nil { - c.clients = make(map[RouteTableClient]struct{}, 0) - } - c.clients[client] = struct{}{} + c.RegisterWithOptions(client, ClientOptions{BestOnly: true}) +} + +// RegisterWithOptions registers a client with options for updates +func (c *ClientManager) RegisterWithOptions(client RouteTableClient, opt ClientOptions) { + c.mu.Lock() + defer c.mu.Unlock() + + c.clients[client] = opt c.master.UpdateNewClient(client) } diff --git a/routingtable/client_manager_test.go b/routingtable/client_manager_test.go index d05370ce..674a9a4e 100644 --- a/routingtable/client_manager_test.go +++ b/routingtable/client_manager_test.go @@ -64,3 +64,42 @@ func TestClients(t *testing.T) { assert.Equal(t, test.expected, ret) } } + +func TestGetMaxPaths(t *testing.T) { + tests := []struct { + name string + clientOptions ClientOptions + ecmpPaths uint + expected uint + }{ + { + name: "Test #1", + clientOptions: ClientOptions{ + BestOnly: true, + }, + ecmpPaths: 8, + expected: 1, + }, + { + name: "Test #2", + clientOptions: ClientOptions{ + EcmpOnly: true, + }, + ecmpPaths: 8, + expected: 8, + }, + { + name: "Test #3", + clientOptions: ClientOptions{ + MaxPaths: 100, + }, + ecmpPaths: 10, + expected: 100, + }, + } + + for _, test := range tests { + res := test.clientOptions.GetMaxPaths(test.ecmpPaths) + assert.Equal(t, test.expected, res) + } +} diff --git a/routingtable/locRIB/loc_rib.go b/routingtable/locRIB/loc_rib.go new file mode 100644 index 00000000..73a6a2ff --- /dev/null +++ b/routingtable/locRIB/loc_rib.go @@ -0,0 +1,146 @@ +package locRIB + +import ( + "fmt" + "math" + "sync" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/route" + "github.com/bio-routing/bio-rd/routingtable" +) + +// LocRIB represents a routing information base +type LocRIB struct { + routingtable.ClientManager + rt *routingtable.RoutingTable + mu sync.RWMutex +} + +// New creates a new routing information base +func New() *LocRIB { + a := &LocRIB{ + rt: routingtable.NewRoutingTable(), + } + a.ClientManager = routingtable.NewClientManager(a) + return a +} + +// UpdateNewClient sends current state to a new client +func (a *LocRIB) UpdateNewClient(client routingtable.RouteTableClient) error { + a.mu.RLock() + defer a.mu.RUnlock() + + routes := a.rt.Dump() + for _, r := range routes { + a.propagateChanges(&route.Route{}, r) + } + + return nil +} + +// AddPath replaces the path for prefix `pfx`. If the prefix doesn't exist it is added. +func (a *LocRIB) AddPath(pfx net.Prefix, p *route.Path) error { + a.mu.Lock() + defer a.mu.Unlock() + + routeExisted := false + oldRoute := &route.Route{} + r := a.rt.Get(pfx) + if r != nil { + oldRoute = r.Copy() + routeExisted = true + } + + a.rt.AddPath(pfx, p) + if !routeExisted { + r = a.rt.Get(pfx) + } + + r.PathSelection() + + newRoute := r.Copy() + + fmt.Printf("NEW: %v\n", newRoute.Paths()) + fmt.Printf("OLD: %v\n", oldRoute.Paths()) + + a.propagateChanges(oldRoute, newRoute) + return nil +} + +// RemovePath removes the path for prefix `pfx` +func (a *LocRIB) RemovePath(pfx net.Prefix, p *route.Path) bool { + a.mu.Lock() + defer a.mu.Unlock() + + var oldRoute *route.Route + r := a.rt.Get(pfx) + if r != nil { + oldRoute = r.Copy() + } + + a.rt.RemovePath(pfx, p) + r.PathSelection() + + r = a.rt.Get(pfx) + newRoute := r.Copy() + + fmt.Printf("NEW: %v\n", newRoute.Paths()) + fmt.Printf("OLD: %v\n", oldRoute.Paths()) + + a.propagateChanges(oldRoute, newRoute) + return true +} + +func (a *LocRIB) propagateChanges(oldRoute *route.Route, newRoute *route.Route) { + a.removePathsFromClients(oldRoute, newRoute) + a.addPathsToClients(oldRoute, newRoute) +} + +func (a *LocRIB) addPathsToClients(oldRoute *route.Route, newRoute *route.Route) { + for _, client := range a.ClientManager.Clients() { + opts := a.ClientManager.GetOptions(client) + oldMaxPaths := opts.GetMaxPaths(oldRoute.ECMPPathCount()) + newMaxPaths := opts.GetMaxPaths(newRoute.ECMPPathCount()) + + oldPathsLimit := int(math.Min(float64(oldMaxPaths), float64(len(oldRoute.Paths())))) + newPathsLimit := int(math.Min(float64(newMaxPaths), float64(len(newRoute.Paths())))) + + advertise := route.PathsDiff(newRoute.Paths()[0:newPathsLimit], oldRoute.Paths()[0:oldPathsLimit]) + fmt.Printf("ADVERTISING PATHS %v TO CLIENTS\n", advertise) + + for _, p := range advertise { + client.AddPath(newRoute.Prefix(), p) + } + } +} + +func (a *LocRIB) removePathsFromClients(oldRoute *route.Route, newRoute *route.Route) { + for _, client := range a.ClientManager.Clients() { + opts := a.ClientManager.GetOptions(client) + oldMaxPaths := opts.GetMaxPaths(oldRoute.ECMPPathCount()) + newMaxPaths := opts.GetMaxPaths(newRoute.ECMPPathCount()) + + oldPathsLimit := int(math.Min(float64(oldMaxPaths), float64(len(oldRoute.Paths())))) + newPathsLimit := int(math.Min(float64(newMaxPaths), float64(len(newRoute.Paths())))) + + withdraw := route.PathsDiff(oldRoute.Paths()[0:oldPathsLimit], newRoute.Paths()[0:newPathsLimit]) + + for _, p := range withdraw { + client.RemovePath(newRoute.Prefix(), p) + } + } +} + +func (a *LocRIB) Print() string { + a.mu.RLock() + defer a.mu.RUnlock() + + ret := "Loc-RIB DUMP:\n" + routes := a.rt.Dump() + for _, r := range routes { + ret += fmt.Sprintf("%s\n", r.Prefix().String()) + } + + return ret +} diff --git a/rt/bgp.go b/rt/bgp.go deleted file mode 100644 index 9fd3e56c..00000000 --- a/rt/bgp.go +++ /dev/null @@ -1,160 +0,0 @@ -package rt - -import ( - "sync" - - log "github.com/sirupsen/logrus" -) - -type BGPPath struct { - PathIdentifier uint32 - NextHop uint32 - LocalPref uint32 - ASPath string - ASPathLen uint16 - Origin uint8 - MED uint32 - EBGP bool - BGPIdentifier uint32 - Source uint32 -} - -type BGPPathManager struct { - paths map[BGPPath]*BGPPathCounter - mu sync.Mutex -} - -type BGPPathCounter struct { - usageCount uint64 - path *BGPPath -} - -func NewBGPPathManager() *BGPPathManager { - m := &BGPPathManager{} - return m -} - -func (m *BGPPathManager) pathExists(p BGPPath) bool { - if _, ok := m.paths[p]; !ok { - return false - } - - return true -} - -func (m *BGPPathManager) AddPath(p BGPPath) *BGPPath { - m.mu.Lock() - defer m.mu.Unlock() - - if !m.pathExists(p) { - m.paths[p] = &BGPPathCounter{ - path: &p, - } - } - - m.paths[p].usageCount++ - return m.paths[p].path -} - -func (m *BGPPathManager) RemovePath(p BGPPath) { - m.mu.Lock() - defer m.mu.Unlock() - - if !m.pathExists(p) { - log.Fatalf("Tried to remove non-existent BGPPath: %v", p) - return - } - - m.paths[p].usageCount-- - if m.paths[p].usageCount == 0 { - delete(m.paths, p) - } -} - -func (r *Route) bgpPathSelection() (best *Path, active []*Path) { - // TODO: Implement next hop lookup and compare IGP metrics - for _, p := range r.paths { - if p.Type != BGPPathType { - continue - } - - if len(active) == 0 { - active = append(active, p) - continue - } - - if active[0].BGPPath.ecmp(p.BGPPath) { - active = append(active, p) - if !r.bestPath.BGPPath.better(p.BGPPath) { - continue - } - - best = p - continue - } - - if !active[0].BGPPath.betterECMP(p.BGPPath) { - continue - } - - active = []*Path{p} - } - - return best, active -} - -func (b *BGPPath) betterECMP(c *BGPPath) bool { - if c.LocalPref < b.LocalPref { - return false - } - - if c.LocalPref > b.LocalPref { - return true - } - - if c.ASPathLen > b.ASPathLen { - return false - } - - if c.ASPathLen < b.ASPathLen { - return true - } - - if c.Origin > b.Origin { - return false - } - - if c.Origin < b.Origin { - return true - } - - if c.MED > b.MED { - return false - } - - if c.MED < b.MED { - return true - } - - return false -} - -func (b *BGPPath) better(c *BGPPath) bool { - if b.betterECMP(c) { - return true - } - - if c.BGPIdentifier < b.BGPIdentifier { - return true - } - - if c.Source < b.Source { - return true - } - - return false -} - -func (b *BGPPath) ecmp(c *BGPPath) bool { - return b.LocalPref == c.LocalPref && b.ASPathLen == c.ASPathLen && b.Origin == c.Origin && b.MED == c.MED -} diff --git a/rt/client_manager.go b/rt/client_manager.go deleted file mode 100644 index 723b6e94..00000000 --- a/rt/client_manager.go +++ /dev/null @@ -1,21 +0,0 @@ -package rt - -type ClientManager struct { - clients map[RouteTableClient]struct{} // Ensures a client registers at most once - routingTable *RT -} - -func (c *ClientManager) Register(client RouteTableClient) { - if c.clients == nil { - c.clients = make(map[RouteTableClient]struct{}, 0) - } - c.clients[client] = struct{}{} - c.routingTable.updateNewClient(client) -} - -func (c *ClientManager) Unregister(client RouteTableClient) { - if _, ok := c.clients[client]; !ok { - return - } - delete(c.clients, client) -} diff --git a/rt/filter.go b/rt/filter.go deleted file mode 100644 index f68a6d42..00000000 --- a/rt/filter.go +++ /dev/null @@ -1,21 +0,0 @@ -package rt - -type Filter struct { - ClientManager -} - -func NewFilter() *Filter { - return &Filter{} -} - -func (f *Filter) Add(r *Route) { - for client := range f.clients { - client.AddPath(r) - } -} - -func (f *Filter) Remove(r *Route) { - for client := range f.clients { - client.RemovePath(r) - } -} diff --git a/rt/lpm_test.go.dis b/rt/lpm_test.go.dis deleted file mode 100644 index 90f9e450..00000000 --- a/rt/lpm_test.go.dis +++ /dev/null @@ -1,673 +0,0 @@ -package rt - -import ( - "testing" - - "github.com/taktv6/tbgp/net" - - "github.com/stretchr/testify/assert" -) - -func TestNew(t *testing.T) { - l := New() - if l == nil { - t.Errorf("New() returned nil") - } -} - -func TestRemove(t *testing.T) { - tests := []struct { - name string - routes []*Route - remove []*net.Prefix - expected []*Route - }{ - { - name: "Test 1", - routes: []*Route{ - NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0 - NewRoute(net.NewPfx(167772160, 9)), // 10.0.0.0 - NewRoute(net.NewPfx(176160768, 9)), // 10.128.0.0 - }, - remove: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - }, - expected: []*Route{ - NewRoute(net.NewPfx(167772160, 9)), // 10.0.0.0 - NewRoute(net.NewPfx(176160768, 9)), // 10.128.0.0 - }, - }, - { - name: "Test 2", - routes: []*Route{ - NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0/8 - NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 - NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0/12 - NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0/10 - }, - remove: []*net.Prefix{ - net.NewPfx(167772160, 7), // 10.0.0.0/7 - }, - expected: []*Route{ - NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0/8 - NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0/10 - NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0/12 - NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 - }, - }, - { - name: "Test 3", - remove: []*net.Prefix{ - NewRoute(net.NewPfx(167772160, 7)), // 10.0.0.0/7 - }, - expected: []*Route{}, - }, - { - name: "Test 4", - prefixes: []*net.Prefix{ - NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0 - NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 - NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0 - NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0 - NewRoute(net.NewPfx(191134592, 25)), // 11.100.123.128/25 - }, - remove: []*net.Prefix{ - NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 - }, - expected: []*Route{ - NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0 - NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0 - NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0 - NewRoute(net.NewPfx(191134592, 25)), // 11.100.123.128/25 - }, - }, - { - name: "Test 5", - prefixes: []*net.Prefix{ - NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0 - NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 - NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0 - NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0 - NewRoute(net.NewPfx(191134592, 25)), // 11.100.123.128/25 - }, - remove: []*net.Prefix{ - NewRoute(net.NewPfx(167772160, 12)), // 10.0.0.0/12 - }, - expected: []*Route{ - NewRoute(net.NewPfx(167772160, 8)), // 10.0.0.0 - NewRoute(net.NewPfx(167772160, 10)), // 10.0.0.0 - NewRoute(net.NewPfx(191134464, 24)), // 11.100.123.0/24 - NewRoute(net.NewPfx(191134592, 25)), // 11.100.123.128/25 - }, - }, - } - - for _, test := range tests { - lpm := New() - for _, route := range test.routes { - lpm.Insert(route) - } - - for _, pfx := range test.remove { - lpm.Remove(pfx) - } - - res := lpm.Dump() - assert.Equal(t, test.expected, res) - } -} - -func TestInsert(t *testing.T) { - tests := []struct { - name string - prefixes []*net.Prefix - expected *node - }{ - { - name: "Insert first node", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8)}, // 10.0.0.0/8 - skip: 8, - }, - }, - { - name: "Insert double node", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0/8 - net.NewPfx(167772160, 8), // 10.0.0.0/8 - net.NewPfx(167772160, 8), // 10.0.0.0/8 - net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - skip: 8, - }, - }, - { - name: "Insert triangle", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(167772160, 9), // 10.0.0.0 - net.NewPfx(176160768, 9), // 10.128.0.0 - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - skip: 8, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 9), // 10.0.0.0 - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(176160768, 9), // 10.128.0.0 - }, - }, - }, - }, - { - name: "Insert disjunct prefixes", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 7), // 10.0.0.0/7 - }, - skip: 7, - dummy: true, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(191134464, 24), // 10.0.0.0/8 - }, - skip: 16, - }, - }, - }, - { - name: "Insert disjunct prefixes plus one child low", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - net.NewPfx(167772160, 12), // 10.0.0.0 - net.NewPfx(167772160, 10), // 10.0.0.0 - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 7), // 10.0.0.0/7 - }, - skip: 7, - dummy: true, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - l: &node{ - skip: 1, - route: &Route{ - pfx: net.NewPfx(167772160, 10), // 10.0.0.0/10 - }, - l: &node{ - skip: 1, - route: &Route{ - pfx: net.NewPfx(167772160, 12), // 10.0.0.0 - }, - }, - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(191134464, 24), // 10.0.0.0/8 - }, - skip: 16, - }, - }, - }, - { - name: "Insert disjunct prefixes plus one child high", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - net.NewPfx(167772160, 12), // 10.0.0.0 - net.NewPfx(167772160, 10), // 10.0.0.0 - net.NewPfx(191134592, 25), // 11.100.123.128/25 - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 7), // 10.0.0.0/7 - }, - skip: 7, - dummy: true, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - l: &node{ - skip: 1, - route: &Route{ - pfx: net.NewPfx(167772160, 10), // 10.0.0.0/10 - }, - l: &node{ - skip: 1, - route: &Route{ - pfx: net.NewPfx(167772160, 12), // 10.0.0.0 - }, - }, - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(191134464, 24), //11.100.123.0/24 - }, - skip: 16, - h: &node{ - route: &Route{ - pfx: net.NewPfx(191134592, 25), //11.100.123.128/25 - }, - }, - }, - }, - }, - } - - for _, test := range tests { - l := New() - for _, pfx := range test.prefixes { - l.Insert(&Route{pfx: pfx}) - } - - assert.Equal(t, test.expected, l.root) - } -} - -func TestLPM(t *testing.T) { - tests := []struct { - name string - prefixes []*net.Prefix - needle *net.Prefix - expected []*net.Prefix - }{ - { - name: "Test 1", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - net.NewPfx(167772160, 12), // 10.0.0.0 - net.NewPfx(167772160, 10), // 10.0.0.0 - }, - needle: net.NewPfx(167772160, 32), // 10.0.0.0/32 - expected: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(167772160, 10), // 10.0.0.0 - net.NewPfx(167772160, 12), // 10.0.0.0 - }, - }, - { - name: "Test 2", - prefixes: []*net.Prefix{}, - needle: net.NewPfx(167772160, 32), // 10.0.0.0/32 - expected: nil, - }, - { - name: "Test 3", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - net.NewPfx(167772160, 12), // 10.0.0.0 - net.NewPfx(167772160, 10), // 10.0.0.0 - }, - needle: net.NewPfx(167772160, 10), // 10.0.0.0/10 - expected: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(167772160, 10), // 10.0.0.0 - }, - }, - } - - for _, test := range tests { - lpm := New() - for _, pfx := range test.prefixes { - lpm.Insert(&Route{pfx: pfx}) - } - assert.Equal(t, test.expected, lpm.LPM(test.needle)) - } -} - -func TestGet(t *testing.T) { - tests := []struct { - name string - moreSpecifics bool - prefixes []*net.Prefix - needle *net.Prefix - expected []*net.Prefix - }{ - { - name: "Test 1: Search pfx and dump more specifics", - moreSpecifics: true, - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0/8 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - net.NewPfx(167772160, 12), // 10.0.0.0/12 - net.NewPfx(167772160, 10), // 10.0.0.0/10 - }, - needle: net.NewPfx(167772160, 8), // 10.0.0.0/8 - expected: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0/8 - net.NewPfx(167772160, 10), // 10.0.0.0 - net.NewPfx(167772160, 12), // 10.0.0.0 - }, - }, - { - name: "Test 2: Search pfx and don't dump more specifics", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0/8 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - net.NewPfx(167772160, 12), // 10.0.0.0 - net.NewPfx(167772160, 10), // 10.0.0.0 - }, - needle: net.NewPfx(167772160, 8), // 10.0.0.0/8 - expected: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - }, - { - name: "Test 3", - prefixes: []*net.Prefix{}, - needle: net.NewPfx(167772160, 32), // 10.0.0.0/32 - expected: nil, - }, - { - name: "Test 4: Get Dummy", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - }, - needle: net.NewPfx(167772160, 7), // 10.0.0.0/7 - expected: nil, - }, - { - name: "Test 5", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - net.NewPfx(167772160, 12), // 10.0.0.0 - net.NewPfx(167772160, 10), // 10.0.0.0 - }, - needle: net.NewPfx(191134464, 24), // 10.0.0.0/8 - expected: []*net.Prefix{ - net.NewPfx(191134464, 24), // 11.100.123.0/24 - }, - }, - { - name: "Test 4: Get nonexistent #1", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - }, - needle: net.NewPfx(167772160, 10), // 10.0.0.0/10 - expected: nil, - }, - { - name: "Test 4: Get nonexistent #2", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0/8 - net.NewPfx(167772160, 12), // 10.0.0.0/12 - }, - needle: net.NewPfx(167772160, 10), // 10.0.0.0/10 - expected: nil, - }, - } - - for _, test := range tests { - lpm := New() - for _, pfx := range test.prefixes { - lpm.Insert(&Route{pfx: pfx}) - } - p := lpm.Get(test.needle, test.moreSpecifics) - - if p == nil { - if test.expected != nil { - t.Errorf("Unexpected nil result for test %q", test.name) - } - continue - } - - assert.Equal(t, test.expected, p) - } -} - -func TestNewSuperNode(t *testing.T) { - tests := []struct { - name string - a *net.Prefix - b *net.Prefix - expected *node - }{ - { - name: "Test 1", - a: net.NewPfx(167772160, 8), // 10.0.0.0/8 - b: net.NewPfx(191134464, 24), // 11.100.123.0/24 - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 7), // 10.0.0.0/7 - }, - skip: 7, - dummy: true, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(191134464, 24), //11.100.123.0/24 - }, - skip: 16, - }, - }, - }, - } - - for _, test := range tests { - n := newNode(&Route{pfx: test.a}, test.a.Pfxlen(), false) - n = n.newSuperNode(&Route{pfx: test.b}) - assert.Equal(t, test.expected, n) - } -} - -func TestDumpPfxs(t *testing.T) { - tests := []struct { - name string - prefixes []*net.Prefix - expected []*net.Prefix - }{ - - { - name: "Test 1: Empty node", - expected: nil, - }, - { - name: "Test 2: ", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0/8 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - net.NewPfx(167772160, 12), // 10.0.0.0/12 - net.NewPfx(167772160, 10), // 10.0.0.0/10 - }, - expected: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0/8 - net.NewPfx(167772160, 10), // 10.0.0.0/10 - net.NewPfx(167772160, 12), // 10.0.0.0/12 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - }, - }, - } - - for _, test := range tests { - lpm := New() - for _, pfx := range test.prefixes { - lpm.Insert(&Route{pfx: pfx}) - } - - res := make([]*Route, 0) - r := lpm.root.dumpPfxs(res) - assert.Equal(t, test.expected, r) - } -} - -func TestGetBitUint32(t *testing.T) { - tests := []struct { - name string - input uint32 - offset uint8 - expected bool - }{ - { - name: "test 1", - input: 167772160, // 10.0.0.0 - offset: 8, - expected: false, - }, - { - name: "test 2", - input: 184549376, // 11.0.0.0 - offset: 8, - expected: true, - }, - } - - for _, test := range tests { - b := getBitUint32(test.input, test.offset) - if b != test.expected { - t.Errorf("%s: Unexpected failure: Bit %d of %d is %v. Expected %v", test.name, test.offset, test.input, b, test.expected) - } - } -} - -func TestInsertChildren(t *testing.T) { - tests := []struct { - name string - base *net.Prefix - old *net.Prefix - new *net.Prefix - expected *node - }{ - { - name: "Test 1", - base: net.NewPfx(167772160, 8), //10.0.0.0/8 - old: net.NewPfx(167772160, 9), //10.0.0.0/9 - new: net.NewPfx(176160768, 9), //10.128.0.0/9 - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), - }, - skip: 8, - dummy: true, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 9), - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(176160768, 9), - }, - }, - }, - }, - { - name: "Test 2", - base: net.NewPfx(167772160, 8), //10.0.0.0/8 - old: net.NewPfx(176160768, 9), //10.128.0.0/9 - new: net.NewPfx(167772160, 9), //10.0.0.0/9 - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), - }, - skip: 8, - dummy: true, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 9), - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(176160768, 9), - }, - }, - }, - }, - } - - for _, test := range tests { - n := newNode(&Route{pfx: test.base}, test.base.Pfxlen(), true) - old := newNode(&Route{pfx: test.old}, test.old.Pfxlen(), false) - n.insertChildren(old, &Route{pfx: test.new}) - assert.Equal(t, test.expected, n) - } -} - -func TestInsertBefore(t *testing.T) { - tests := []struct { - name string - a *net.Prefix - b *net.Prefix - expected *node - }{ - { - name: "Test 1", - a: net.NewPfx(167772160, 10), // 10.0.0.0 - b: net.NewPfx(167772160, 8), // 10.0.0.0 - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0, - }, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 10), // 10.0.0.0 - }, - skip: 1, - }, - skip: 8, - }, - }, - { - name: "Test 2", - a: net.NewPfx(184549376, 8), // 11.0.0.0/8 - b: net.NewPfx(167772160, 7), // 10.0.0.0/7 - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 7), // 10.0.0.0, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(184549376, 8), // 10.0.0.0 - }, - skip: 0, - }, - skip: 7, - }, - }, - } - - for _, test := range tests { - n := newNode(&Route{pfx: test.a}, test.a.Pfxlen(), false) - n = n.insertBefore(&Route{pfx: test.b}, test.b.Pfxlen()) - assert.Equal(t, test.expected, n) - } -} diff --git a/rt/route.go b/rt/route.go deleted file mode 100644 index d271c914..00000000 --- a/rt/route.go +++ /dev/null @@ -1,152 +0,0 @@ -package rt - -import ( - net "github.com/bio-routing/bio-rd/net" -) - -// Path Types -const StaticPathType = 1 -const BGPPathType = 2 -const OSPFPathType = 3 -const ISISPathType = 4 - -type Path struct { - Type uint8 - StaticPath *StaticPath - BGPPath *BGPPath -} - -type Route struct { - pfx *net.Prefix - bestPath *Path - activePaths []*Path - paths []*Path -} - -func NewRoute(pfx *net.Prefix, paths []*Path) *Route { - r := &Route{ - pfx: pfx, - paths: paths, - } - - return r -} - -func (r *Route) Pfxlen() uint8 { - return r.pfx.Pfxlen() -} - -func (r *Route) Prefix() *net.Prefix { - return r.pfx -} - -func (r *Route) Remove(rm *Route) (final bool) { - for _, del := range rm.paths { - r.paths = removePath(r.paths, del) - } - - return len(r.paths) == 0 -} - -// returns a list of Paths that are in a but not in b -func missingPaths(a, b []*Path) []*Path { - ret := make([]*Path, 0) - for _, p := range a { - found := false - for _, q := range b { - if *p == *q { - found = true - break - } - } - if !found { - ret = append(ret, p) - } - } - - return ret -} - -func removePath(paths []*Path, remove *Path) []*Path { - i := -1 - for j := range paths { - if paths[j].Equal(remove) { - i = j - break - } - } - - if i < 0 { - return paths - } - - copy(paths[i:], paths[i+1:]) - return paths[:len(paths)-1] -} - -func (r *Route) removeAllPaths() { - r.paths = make([]*Path, 0) -} - -func (p *Path) Equal(q *Path) bool { - if p == nil || q == nil { - return false - } - - if p.Type != q.Type { - return false - } - - switch p.Type { - case BGPPathType: - if *p.BGPPath != *q.BGPPath { - return false - } - } - - return true -} - -func (r *Route) AddPath(p *Path) { - r.paths = append(r.paths, p) - r.bestPaths() -} - -func (r *Route) AddPaths(paths []*Path) { - for _, p := range paths { - r.paths = append(r.paths, p) - } - r.bestPaths() -} - -func (r *Route) bestPaths() { - var best *Path - var active []*Path - protocol := getBestProtocol(r.paths) - - switch protocol { - case StaticPathType: - best, active = r.staticPathSelection() - case BGPPathType: - best, active = r.bgpPathSelection() - } - - r.bestPath = best - r.activePaths = active -} - -func getBestProtocol(paths []*Path) uint8 { - best := uint8(0) - for _, p := range paths { - if best == 0 { - best = p.Type - continue - } - - if p.Type < best { - best = p.Type - } - } - - return best -} diff --git a/rt/route_test.go b/rt/route_test.go deleted file mode 100644 index 7b381809..00000000 --- a/rt/route_test.go +++ /dev/null @@ -1,473 +0,0 @@ -package rt - -import ( - "testing" - - net "github.com/bio-routing/bio-rd/net" - "github.com/stretchr/testify/assert" -) - -func TestNewRoute(t *testing.T) { - tests := []struct { - name string - pfx *net.Prefix - paths []*Path - expected *Route - }{ - { - name: "Test #1", - pfx: net.NewPfx(158798889, 24), - paths: []*Path{ - { - Type: 2, - StaticPath: &StaticPath{ - NextHop: 56963289, - }, - }, - }, - expected: &Route{ - pfx: net.NewPfx(158798889, 24), - paths: []*Path{ - { - Type: 2, - StaticPath: &StaticPath{ - NextHop: 56963289, - }, - }, - }, - }, - }, - } - - for _, test := range tests { - res := NewRoute(test.pfx, test.paths) - assert.Equal(t, test.expected, res) - } -} - -func TestPfxlen(t *testing.T) { - tests := []struct { - name string - pfx *net.Prefix - expected uint8 - }{ - { - name: "Test #1", - pfx: net.NewPfx(158798889, 24), - expected: 24, - }, - } - - for _, test := range tests { - r := NewRoute(test.pfx, nil) - res := r.Pfxlen() - assert.Equal(t, test.expected, res) - } -} - -func TestPrefix(t *testing.T) { - tests := []struct { - name string - pfx *net.Prefix - expected *net.Prefix - }{ - { - name: "Test #1", - pfx: net.NewPfx(158798889, 24), - expected: net.NewPfx(158798889, 24), - }, - } - - for _, test := range tests { - r := NewRoute(test.pfx, nil) - res := r.Prefix() - assert.Equal(t, test.expected, res) - } -} - -func TestRouteRemovePath(t *testing.T) { - tests := []struct { - name string - paths []*Path - remove *Path - expected []*Path - }{ - { - name: "Remove middle", - paths: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 100, - }, - }, - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 200, - }, - }, - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 300, - }, - }, - }, - remove: &Path{ - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 200, - }, - }, - expected: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 100, - }, - }, - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 300, - }, - }, - }, - }, - { - name: "Remove non-existent", - paths: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 10, - }, - }, - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 20, - }, - }, - }, - remove: &Path{ - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 50, - }, - }, - expected: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 10, - }, - }, - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 20, - }, - }, - }, - }, - } - - for _, test := range tests { - res := removePath(test.paths, test.remove) - assert.Equal(t, test.expected, res) - } -} - -func TestEqual(t *testing.T) { - tests := []struct { - name string - pathA *Path - pathB *Path - expected bool - }{ - { - name: "Unequal types", - pathA: &Path{ - Type: 1, - }, - pathB: &Path{ - Type: 2, - }, - expected: false, - }, - { - name: "Unequal attributes", - pathA: &Path{ - Type: 2, - BGPPath: &BGPPath{ - LocalPref: 100, - }, - }, - pathB: &Path{ - Type: 2, - BGPPath: &BGPPath{ - LocalPref: 200, - }, - }, - expected: false, - }, - { - name: "Equal", - pathA: &Path{ - Type: 2, - BGPPath: &BGPPath{ - LocalPref: 100, - }, - }, - pathB: &Path{ - Type: 2, - BGPPath: &BGPPath{ - LocalPref: 100, - }, - }, - expected: true, - }, - } - - for _, test := range tests { - res := test.pathA.Equal(test.pathB) - assert.Equal(t, test.expected, res) - } -} - -func TestAddPath(t *testing.T) { - tests := []struct { - name string - route *Route - new *Path - expected *Route - }{ - { - name: "Add a new best path", - route: &Route{ - paths: []*Path{ - { - Type: 2, - BGPPath: &BGPPath{ - LocalPref: 100, - }, - }, - }, - }, - new: &Path{ - Type: 2, - BGPPath: &BGPPath{ - LocalPref: 200, - }, - }, - expected: &Route{ - activePaths: []*Path{ - { - Type: 2, - BGPPath: &BGPPath{ - LocalPref: 200, - }, - }, - }, - paths: []*Path{ - { - Type: 2, - BGPPath: &BGPPath{ - LocalPref: 100, - }, - }, - { - Type: 2, - BGPPath: &BGPPath{ - LocalPref: 200, - }, - }, - }, - }, - }, - } - - for _, test := range tests { - test.route.AddPath(test.new) - assert.Equal(t, test.expected, test.route) - } -} - -func TestAddPaths(t *testing.T) { - tests := []struct { - name string - route *Route - new []*Path - expected *Route - }{ - { - name: "Add 2 new paths including a new best path", - route: &Route{ - paths: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 100, - }, - }, - }, - }, - new: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 200, - }, - }, - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 50, - }, - }, - }, - expected: &Route{ - activePaths: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 200, - }, - }, - }, - paths: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 100, - }, - }, - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 200, - }, - }, - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 50, - }, - }, - }, - }, - }, - } - - for _, test := range tests { - test.route.AddPaths(test.new) - assert.Equal(t, test.expected, test.route) - } -} - -func TestGetBestProtocol(t *testing.T) { - tests := []struct { - name string - input []*Path - expected uint8 - }{ - { - name: "Foo", - input: []*Path{ - { - Type: BGPPathType, - }, - { - Type: StaticPathType, - }, - }, - expected: StaticPathType, - }, - } - - for _, test := range tests { - res := getBestProtocol(test.input) - assert.Equal(t, test.expected, res) - } -} - -func TestMissingPaths(t *testing.T) { - tests := []struct { - name string - a []*Path - b []*Path - expected []*Path - }{ - { - name: "None missing #2", - a: []*Path{ - { - Type: 1, - }, - { - Type: 2, - }, - }, - b: []*Path{ - { - Type: 1, - }, - { - Type: 2, - }, - { - Type: 3, - }, - }, - expected: []*Path{}, - }, - { - name: "None missing", - a: []*Path{ - { - Type: 1, - }, - { - Type: 2, - }, - }, - b: []*Path{ - { - Type: 1, - }, - { - Type: 2, - }, - }, - expected: []*Path{}, - }, - { - name: "One missing", - a: []*Path{ - { - Type: 1, - }, - { - Type: 2, - }, - }, - b: []*Path{ - { - Type: 1, - }, - }, - expected: []*Path{ - { - Type: 2, - }, - }, - }, - } - - for _, test := range tests { - res := missingPaths(test.a, test.b) - assert.Equal(t, test.expected, res) - } -} diff --git a/rt/routing_table.go b/rt/routing_table.go deleted file mode 100644 index 9e03e03d..00000000 --- a/rt/routing_table.go +++ /dev/null @@ -1,470 +0,0 @@ -package rt - -import ( - "fmt" - "sync" - - "github.com/bio-routing/bio-rd/net" -) - -type RouteTableClient interface { - AddPath(*Route) - ReplaceRoute(*Route) - RemovePath(*Route) - RemoveRoute(*net.Prefix) -} - -// RT represents a routing table -type RT struct { - root *node - selectBestPaths bool - mu sync.RWMutex - ClientManager -} - -// node is a node in the compressed trie that is used to implement a routing table -type node struct { - skip uint8 - dummy bool - route *Route - l *node - h *node -} - -// New creates a new empty LPM -func New(selectBestPaths bool) *RT { - rt := &RT{ - selectBestPaths: selectBestPaths, - } - - rt.ClientManager.routingTable = rt - return rt -} - -func (rt *RT) updateNewClient(client RouteTableClient) { - rt.root.updateNewClient(client) -} - -func (n *node) updateNewClient(client RouteTableClient) { - if n == nil { - return - } - - if !n.dummy { - client.AddPath(n.route) - } - - n.l.updateNewClient(client) - n.h.updateNewClient(client) -} - -func (rt *RT) updatePathSelection(route *Route) { - fmt.Printf("updatePathSelection\n") - formerActivePaths := rt.Get(route.pfx, false)[0].activePaths - activePaths := rt.selectBestPath(route).route.activePaths - - fmt.Printf("formerActivePaths: %v\n", formerActivePaths) - fmt.Printf("activePaths: %v\n", activePaths) - x := missingPaths(activePaths, formerActivePaths) - fmt.Printf("x: %v\n", x) - - for _, advertisePath := range x { - adervtiseRoute := &Route{ - pfx: route.pfx, - paths: []*Path{advertisePath}, - } - for client := range rt.clients { - fmt.Printf("Propagating an Update via AddPath()") - client.AddPath(adervtiseRoute) - } - } - - for _, withdrawPath := range missingPaths(formerActivePaths, activePaths) { - withdrawRoute := &Route{ - pfx: route.pfx, - paths: []*Path{withdrawPath}, - } - for client := range rt.clients { - fmt.Printf("Propagating an Update via RemovePath()") - client.RemovePath(withdrawRoute) - } - } -} - -func (rt *RT) AddPath(route *Route) { - rt.addPath(route) - if rt.selectBestPaths { - rt.updatePathSelection(route) - return - } - - for client := range rt.clients { - client.AddPath(route) - } -} - -// RemovePath removes a path from the trie -func (rt *RT) RemovePath(route *Route) { - rt.root.removePath(route) - - for client := range rt.clients { - client.RemovePath(route) - } -} - -// RemoveRoute removes a prefix from the rt including all it's paths -func (rt *RT) RemoveRoute(pfx *net.Prefix) { - if rt.selectBestPaths { - return - } - - r := rt.Get(pfx, false) - if len(r) == 0 { - return - } - - for client := range rt.clients { - for _, path := range r[0].paths { - withdrawRoute := NewRoute(pfx, []*Path{path}) - client.RemovePath(withdrawRoute) - } - } - - rt.root.removeRoute(pfx) -} - -// ReplaceRoute replaces all paths of a route. Route is added if it doesn't exist yet. -func (rt *RT) ReplaceRoute(route *Route) { - fmt.Printf("Replacing a route!\n") - if rt.selectBestPaths { - fmt.Printf("Ignoring because rt.selectBestPaths is false\n") - return - } - - r := rt.Get(route.pfx, false) - if len(r) > 0 { - rt.RemoveRoute(route.pfx) - } - rt.addPath(route) - rt.updatePathSelection(route) -} - -// LPM performs a longest prefix match for pfx on lpm -func (rt *RT) LPM(pfx *net.Prefix) (res []*Route) { - if rt.root == nil { - return nil - } - - rt.root.lpm(pfx, &res) - return res -} - -// Get get's prefix pfx from the LPM -func (rt *RT) Get(pfx *net.Prefix, moreSpecifics bool) (res []*Route) { - if rt.root == nil { - return nil - } - - node := rt.root.get(pfx) - if moreSpecifics { - return node.dumpPfxs(res) - } - - if node == nil { - return nil - } - - return []*Route{ - node.route, - } -} - -func (rt *RT) addPath(route *Route) { - if rt.root == nil { - rt.root = newNode(route, route.Pfxlen(), false) - return - } - - rt.root = rt.root.insert(route) -} - -func (rt *RT) selectBestPath(route *Route) *node { - if rt.root == nil { - return nil - } - - node := rt.root.get(route.pfx) - if !rt.selectBestPaths { - // If we don't select best path(s) evey path is best path - node.route.activePaths = make([]*Path, len(node.route.paths)) - copy(node.route.activePaths, node.route.paths) - return node - } - - node.route.bestPaths() - return node -} - -func newNode(route *Route, skip uint8, dummy bool) *node { - n := &node{ - route: route, - skip: skip, - dummy: dummy, - } - return n -} - -func (n *node) removePath(route *Route) { - if n == nil { - return - } - - if *n.route.Prefix() == *route.Prefix() { - if n.dummy { - return - } - - if n.route.Remove(route) { - // FIXME: Can this node actually be removed from the trie entirely? - n.dummy = true - } - - return - } - - b := getBitUint32(route.Prefix().Addr(), n.route.Pfxlen()+1) - if !b { - n.l.removePath(route) - return - } - n.h.removePath(route) - return -} - -func (n *node) replaceRoute(route *Route) { - if n == nil { - return - } - - if *n.route.Prefix() == *route.Prefix() { - n.route = route - n.dummy = false - return - } - - b := getBitUint32(route.Prefix().Addr(), n.route.Pfxlen()+1) - if !b { - n.l.replaceRoute(route) - return - } - n.h.replaceRoute(route) - return -} - -func (n *node) removeRoute(pfx *net.Prefix) { - if n == nil { - return - } - - if *n.route.Prefix() == *pfx { - if n.dummy { - return - } - - // TODO: Remove node if possible - n.dummy = true - return - } - - b := getBitUint32(pfx.Addr(), n.route.Pfxlen()+1) - if !b { - n.l.removeRoute(pfx) - return - } - n.h.removeRoute(pfx) - return -} - -func (n *node) lpm(needle *net.Prefix, res *[]*Route) { - if n == nil { - return - } - - if *n.route.Prefix() == *needle && !n.dummy { - *res = append(*res, n.route) - return - } - - if !n.route.Prefix().Contains(needle) { - return - } - - if !n.dummy { - *res = append(*res, n.route) - } - n.l.lpm(needle, res) - n.h.lpm(needle, res) -} - -func (n *node) dumpPfxs(res []*Route) []*Route { - if n == nil { - return nil - } - - if !n.dummy { - res = append(res, n.route) - } - - if n.l != nil { - res = n.l.dumpPfxs(res) - } - - if n.h != nil { - res = n.h.dumpPfxs(res) - } - - return res -} - -func (n *node) get(pfx *net.Prefix) *node { - if n == nil { - return nil - } - - if *n.route.Prefix() == *pfx { - if n.dummy { - return nil - } - return n - } - - if n.route.Pfxlen() > pfx.Pfxlen() { - return nil - } - - b := getBitUint32(pfx.Addr(), n.route.Pfxlen()+1) - if !b { - return n.l.get(pfx) - } - return n.h.get(pfx) -} - -func (n *node) insert(route *Route) *node { - if *n.route.Prefix() == *route.Prefix() { - n.route.AddPaths(route.paths) - n.dummy = false - return n - } - - // is pfx NOT a subnet of this node? - if !n.route.Prefix().Contains(route.Prefix()) { - route.bestPaths() - if route.Prefix().Contains(n.route.Prefix()) { - return n.insertBefore(route, n.route.Pfxlen()-n.skip-1) - } - - return n.newSuperNode(route) - } - - // pfx is a subnet of this node - b := getBitUint32(route.Prefix().Addr(), n.route.Pfxlen()+1) - if !b { - return n.insertLow(route, n.route.Prefix().Pfxlen()) - } - return n.insertHigh(route, n.route.Pfxlen()) -} - -func (n *node) insertLow(route *Route, parentPfxLen uint8) *node { - if n.l == nil { - route.bestPaths() - n.l = newNode(route, route.Pfxlen()-parentPfxLen-1, false) - return n - } - n.l = n.l.insert(route) - return n -} - -func (n *node) insertHigh(route *Route, parentPfxLen uint8) *node { - if n.h == nil { - route.bestPaths() - n.h = newNode(route, route.Pfxlen()-parentPfxLen-1, false) - return n - } - n.h = n.h.insert(route) - return n -} - -func (n *node) newSuperNode(route *Route) *node { - superNet := route.Prefix().GetSupernet(n.route.Prefix()) - - pfxLenDiff := n.route.Pfxlen() - superNet.Pfxlen() - skip := n.skip - pfxLenDiff - - pseudoNode := newNode(NewRoute(superNet, nil), skip, true) - pseudoNode.insertChildren(n, route) - return pseudoNode -} - -func (n *node) insertChildren(old *node, new *Route) { - // Place the old node - b := getBitUint32(old.route.Prefix().Addr(), n.route.Pfxlen()+1) - if !b { - n.l = old - n.l.skip = old.route.Pfxlen() - n.route.Pfxlen() - 1 - } else { - n.h = old - n.h.skip = old.route.Pfxlen() - n.route.Pfxlen() - 1 - } - - // Place the new Prefix - newNode := newNode(new, new.Pfxlen()-n.route.Pfxlen()-1, false) - b = getBitUint32(new.Prefix().Addr(), n.route.Pfxlen()+1) - if !b { - n.l = newNode - } else { - n.h = newNode - } -} - -func (n *node) insertBefore(route *Route, parentPfxLen uint8) *node { - tmp := n - - pfxLenDiff := n.route.Pfxlen() - route.Pfxlen() - skip := n.skip - pfxLenDiff - new := newNode(route, skip, false) - - b := getBitUint32(route.Prefix().Addr(), parentPfxLen) - if !b { - new.l = tmp - new.l.skip = tmp.route.Pfxlen() - route.Pfxlen() - 1 - } else { - new.h = tmp - new.h.skip = tmp.route.Pfxlen() - route.Pfxlen() - 1 - } - - return new -} - -// Dump dumps all routes in table rt into a slice -func (rt *RT) Dump() []*Route { - res := make([]*Route, 0) - return rt.root.dump(res) -} - -func (n *node) dump(res []*Route) []*Route { - if n == nil { - return res - } - - if !n.dummy { - res = append(res, n.route) - } - - res = n.l.dump(res) - res = n.h.dump(res) - return res -} - -func getBitUint32(x uint32, pos uint8) bool { - return ((x) & (1 << (32 - pos))) != 0 -} diff --git a/rt/routing_table_test.go b/rt/routing_table_test.go deleted file mode 100644 index 093af87b..00000000 --- a/rt/routing_table_test.go +++ /dev/null @@ -1,605 +0,0 @@ -package rt - -import ( - "testing" - - net "github.com/bio-routing/bio-rd/net" - "github.com/stretchr/testify/assert" -) - -func TestNew(t *testing.T) { - l := New(false) - if l == nil { - t.Errorf("New() returned nil") - } -} - -func TestRemovePath(t *testing.T) { - tests := []struct { - name string - routes []*Route - remove []*Route - expected []*Route - }{ - { - name: "Remove a path that is the only one for a prefix", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - }, - remove: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - }, - expected: []*Route{ - { - pfx: net.NewPfx(strAddr("10.0.0.0"), 9), - paths: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }, - }, - { - pfx: net.NewPfx(strAddr("10.128.0.0"), 9), - paths: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }, - }, - }, - }, - /*{ - name: "Remove a path that is one of two for a prefix", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 1000, - }, - }, - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 2000, - }, - }, - }), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - }, - remove: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 1000, - }, - }, - }), - }, - expected: []*Route{ - { - pfx: net.NewPfx(strAddr("10.0.0.0"), 8), - paths: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 2000, - }, - }, - }, - activePaths: []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{ - LocalPref: 2000, - }, - }, - }, - }, - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - }, - },*/ - } - - for _, test := range tests { - rt := New(false) - for _, route := range test.routes { - rt.AddPath(route) - } - - for _, route := range test.remove { - rt.RemovePath(route) - } - - res := rt.Dump() - assert.Equal(t, test.expected, res) - } -} - -func TestRemovePfx(t *testing.T) { - tests := []struct { - name string - routes []*Route - remove []*net.Prefix - expected []*Route - }{ - { - name: "Remove non-existent prefix", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - NewRoute(net.NewPfx(strAddr("100.0.0.0"), 8), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - }, - remove: []*net.Prefix{ - net.NewPfx(0, 0), - }, - expected: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - NewRoute(net.NewPfx(strAddr("100.0.0.0"), 8), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - }, - }, - { - name: "Remove final prefix", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), []*Path{ - { - Type: BGPPathType, - BGPPath: &BGPPath{}, - }, - }), - }, - remove: []*net.Prefix{ - net.NewPfx(strAddr("10.0.0.0"), 8), - }, - expected: []*Route{}, - }, - } - - for _, test := range tests { - lpm := New(false) - for _, route := range test.routes { - lpm.AddPath(route) - } - - for _, pfx := range test.remove { - lpm.RemoveRoute(pfx) - } - - res := lpm.Dump() - assert.Equal(t, test.expected, res) - } -} - -func TestGetBitUint32(t *testing.T) { - tests := []struct { - name string - input uint32 - offset uint8 - expected bool - }{ - { - name: "test 1", - input: 167772160, // 10.0.0.0 - offset: 8, - expected: false, - }, - { - name: "test 2", - input: 184549376, // 11.0.0.0 - offset: 8, - expected: true, - }, - } - - for _, test := range tests { - b := getBitUint32(test.input, test.offset) - if b != test.expected { - t.Errorf("%s: Unexpected failure: Bit %d of %d is %v. Expected %v", test.name, test.offset, test.input, b, test.expected) - } - } -} - -func TestLPM(t *testing.T) { - tests := []struct { - name string - routes []*Route - needle *net.Prefix - expected []*Route - }{ - { - name: "LPM for non-existent route", - routes: []*Route{}, - needle: net.NewPfx(strAddr("10.0.0.0"), 32), - expected: nil, - }, - { - name: "Positive LPM test", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), - }, - needle: net.NewPfx(167772160, 32), // 10.0.0.0/32 - expected: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), - }, - }, - /*{ - name: "Exact match", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), - }, - needle: net.NewPfx(strAddr("10.0.0.0"), 10), - expected: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), - }, - },*/ - } - - for _, test := range tests { - rt := New(false) - for _, route := range test.routes { - rt.AddPath(route) - } - assert.Equal(t, test.expected, rt.LPM(test.needle)) - } -} - -func TestGet(t *testing.T) { - tests := []struct { - name string - moreSpecifics bool - routes []*Route - needle *net.Prefix - expected []*Route - }{ - { - name: "Test 1: Search pfx and dump route + more specifics", - moreSpecifics: true, - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), - }, - needle: net.NewPfx(strAddr("10.0.0.0"), 8), - expected: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), - }, - }, - { - name: "Test 2: Search pfx and don't dump more specifics", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), - }, - needle: net.NewPfx(strAddr("10.0.0.0"), 8), - expected: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - }, - }, - { - name: "Test 3: Empty table", - routes: []*Route{}, - needle: net.NewPfx(strAddr("10.0.0.0"), 32), - expected: nil, - }, - { - name: "Test 4: Get Dummy", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), - }, - needle: net.NewPfx(strAddr("10.0.0.0"), 7), - expected: nil, - }, - { - name: "Test 5", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 10), nil), - }, - needle: net.NewPfx(strAddr("11.100.123.0"), 24), - expected: []*Route{ - NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), - }, - }, - { - name: "Test 4: Get nonexistent #1", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("11.100.123.0"), 24), nil), - }, - needle: net.NewPfx(strAddr("10.0.0.0"), 10), - expected: nil, - }, - { - name: "Test 4: Get nonexistent #2", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 12), nil), - }, - needle: net.NewPfx(strAddr("10.0.0.0"), 10), - expected: nil, - }, - } - - for _, test := range tests { - rt := New(false) - for _, route := range test.routes { - rt.AddPath(route) - } - p := rt.Get(test.needle, test.moreSpecifics) - - if p == nil { - if test.expected != nil { - t.Errorf("Unexpected nil result for test %q", test.name) - } - continue - } - - assert.Equal(t, test.expected, p) - } -} - -func TestInsert(t *testing.T) { - tests := []struct { - name string - routes []*Route - expected *node - }{ - { - name: "Insert first node", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(strAddr("10.0.0.0"), 8), - }, - skip: 8, - }, - }, - { - name: "Insert duplicate node", - routes: []*Route{ - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), nil), - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(strAddr("10.0.0.0"), 8), - }, - skip: 8, - }, - }, - /*{ - name: "Insert triangle", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(167772160, 9), // 10.0.0.0 - net.NewPfx(176160768, 9), // 10.128.0.0 - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - skip: 8, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 9), // 10.0.0.0 - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(176160768, 9), // 10.128.0.0 - }, - }, - }, - }, - { - name: "Insert disjunct prefixes", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 7), // 10.0.0.0/7 - }, - skip: 7, - dummy: true, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(191134464, 24), // 10.0.0.0/8 - }, - skip: 16, - }, - }, - }, - { - name: "Insert disjunct prefixes plus one child low", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - net.NewPfx(167772160, 12), // 10.0.0.0 - net.NewPfx(167772160, 10), // 10.0.0.0 - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 7), // 10.0.0.0/7 - }, - skip: 7, - dummy: true, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - l: &node{ - skip: 1, - route: &Route{ - pfx: net.NewPfx(167772160, 10), // 10.0.0.0/10 - }, - l: &node{ - skip: 1, - route: &Route{ - pfx: net.NewPfx(167772160, 12), // 10.0.0.0 - }, - }, - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(191134464, 24), // 10.0.0.0/8 - }, - skip: 16, - }, - }, - }, - { - name: "Insert disjunct prefixes plus one child high", - prefixes: []*net.Prefix{ - net.NewPfx(167772160, 8), // 10.0.0.0 - net.NewPfx(191134464, 24), // 11.100.123.0/24 - net.NewPfx(167772160, 12), // 10.0.0.0 - net.NewPfx(167772160, 10), // 10.0.0.0 - net.NewPfx(191134592, 25), // 11.100.123.128/25 - }, - expected: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 7), // 10.0.0.0/7 - }, - skip: 7, - dummy: true, - l: &node{ - route: &Route{ - pfx: net.NewPfx(167772160, 8), // 10.0.0.0/8 - }, - l: &node{ - skip: 1, - route: &Route{ - pfx: net.NewPfx(167772160, 10), // 10.0.0.0/10 - }, - l: &node{ - skip: 1, - route: &Route{ - pfx: net.NewPfx(167772160, 12), // 10.0.0.0 - }, - }, - }, - }, - h: &node{ - route: &Route{ - pfx: net.NewPfx(191134464, 24), //11.100.123.0/24 - }, - skip: 16, - h: &node{ - route: &Route{ - pfx: net.NewPfx(191134592, 25), //11.100.123.128/25 - }, - }, - }, - }, - },*/ - } - - for _, test := range tests { - rt := New(false) - for _, route := range test.routes { - rt.AddPath(route) - } - - assert.Equal(t, test.expected, rt.root) - } -} - -func strAddr(s string) uint32 { - ret, _ := net.StrToAddr(s) - return ret -} diff --git a/rt/static.go b/rt/static.go deleted file mode 100644 index ae3e6bc3..00000000 --- a/rt/static.go +++ /dev/null @@ -1,26 +0,0 @@ -package rt - -type StaticPath struct { - NextHop uint32 -} - -func (r *Route) staticPathSelection() (best *Path, active []*Path) { - if r.paths == nil { - return nil, nil - } - - if len(r.paths) == 0 { - return nil, nil - } - - for _, p := range r.paths { - if p.Type != StaticPathType { - continue - } - - active = append(active, p) - best = p - } - - return -} -- GitLab