diff --git a/config/peer.go b/config/peer.go index 9e057b9da60f6a33632a55bf7a3181c4a850ba70..ded26d7bd89f5d3bde535148284c632cfef14284 100644 --- a/config/peer.go +++ b/config/peer.go @@ -3,19 +3,22 @@ package config import ( "net" + "time" + "github.com/bio-routing/bio-rd/routingtable" ) type Peer struct { - AdminEnabled bool - KeepAlive uint16 - HoldTimer uint16 - LocalAddress net.IP - PeerAddress net.IP - LocalAS uint32 - PeerAS uint32 - Passive bool - RouterID uint32 - AddPathSend routingtable.ClientOptions - AddPathRecv bool + AdminEnabled bool + KeepAlive uint16 + HoldTimer uint16 + LocalAddress net.IP + PeerAddress net.IP + LocalAS uint32 + PeerAS uint32 + Passive bool + RouterID uint32 + AddPathSend routingtable.ClientOptions + AddPathRecv bool + ReconnectInterval time.Duration } diff --git a/main.go b/main.go index 29c4b0d418f890d0778b3b9001bcfea95a690cfe..1952d771d9713831e851f7c7388b532ba1f4f7f1 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ import ( ) func main() { - fmt.Printf("This is a BGP speaker\n") + logrus.Printf("This is a BGP speaker\n") rib := locRIB.New() b := server.NewBgpServer() diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index 71c4236b18f515a2f07d65ebaa829edebb3dcba2..307c34ab118a6d56072daa282753b6c220e7d908 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -51,6 +51,10 @@ const ( InvalidNetworkField = 10 MalformedASPath = 11 + // Notification Msg Subcodes + AdministrativeShutdown = 2 + AdministrativeReset = 4 + // Attribute Type Codes OriginAttr = 1 ASPathAttr = 2 @@ -59,6 +63,9 @@ const ( LocalPrefAttr = 5 AtomicAggrAttr = 6 AggregatorAttr = 7 + CommunitiesAttr = 8 + AS4PathAttr = 17 + AS4AggregatorAttr = 18 LargeCommunityAttr = 32 // ORIGIN values diff --git a/protocols/bgp/packet/decoder.go b/protocols/bgp/packet/decoder.go index 961789b3cfdd43b0aae606e2a693ceef88a315b9..31583bf1b68257625dfaa3d8b8e40893982d6713 100644 --- a/protocols/bgp/packet/decoder.go +++ b/protocols/bgp/packet/decoder.go @@ -114,7 +114,7 @@ func decodeNotificationMsg(buf *bytes.Buffer) (*BGPNotification, error) { return invalidErrCode(msg) } case Cease: - if msg.ErrorSubcode != 0 { + if !(msg.ErrorSubcode == 0 || msg.ErrorSubcode == AdministrativeShutdown || msg.ErrorSubcode == AdministrativeReset) { return invalidErrCode(msg) } default: diff --git a/protocols/bgp/packet/decoder_test.go b/protocols/bgp/packet/decoder_test.go index 4cbc8c110b507ce475c7b7f22ac3d73ba43992f1..19a83a960c956378415d2c1792abce344dbb76dc 100644 --- a/protocols/bgp/packet/decoder_test.go +++ b/protocols/bgp/packet/decoder_test.go @@ -651,7 +651,6 @@ func TestDecodeUpdateMsg(t *testing.T) { 3, // Attribute Type code (Next Hop) 4, // Length 10, 11, 12, 13, // Next Hop - }, wantFail: false, expected: &BGPUpdate{ @@ -745,7 +744,6 @@ func TestDecodeUpdateMsg(t *testing.T) { 4, // Attribute Type code (Next Hop) 4, // Length 0, 0, 1, 0, // MED 256 - }, wantFail: false, expected: &BGPUpdate{ @@ -1242,6 +1240,191 @@ func TestDecodeUpdateMsg(t *testing.T) { explicitLength: 5, wantFail: true, }, + { + // Unknown attribute + testNum: 16, + input: []byte{ + 0, 0, // No Withdraws + 0, 7, // Total Path Attributes Length + 64, 111, 4, 1, 1, 1, 1, // Unknown attribute + }, + wantFail: false, + expected: &BGPUpdate{ + TotalPathAttrLen: 7, + PathAttributes: &PathAttribute{ + Length: 4, + Transitive: true, + TypeCode: 111, + Value: []byte{1, 1, 1, 1}, + }, + }, + }, + { + // 2 withdraws with two path attributes (Communities + Origin), valid update + testNum: 17, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 16, // Total Path Attribute Length + + 0, // Attribute flags + 8, // Attribute Type code (Community) + 8, // Length + 0, 0, 1, 0, // Arbitrary Community + 0, 0, 1, 1, // Arbitrary Community + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + }, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: strAddr("10.0.0.0"), + Pfxlen: 8, + Next: &NLRI{ + IP: strAddr("192.168.0.0"), + Pfxlen: 16, + }, + }, + TotalPathAttrLen: 16, + PathAttributes: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 8, + TypeCode: 8, + Value: []uint32{256, 257}, + Next: &PathAttribute{ + Optional: true, + Transitive: true, + Partial: true, + ExtendedLength: true, + Length: 1, + TypeCode: 1, + Value: uint8(2), + }, + }, + }, + }, + { + // 2 withdraws with two path attributes (ORIGIN + Community), invalid update (too short community) + testNum: 18, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 11, // Total Path Attribute Length + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + 0, // Attribute flags + 8, // Attribute Type code (Community) + 3, // Length + 0, 0, 1, // Arbitrary Community + }, + wantFail: true, + expected: nil, + }, + { + // 2 withdraws with two path attributes (ORIGIN + Community), invalid update (too long community) + testNum: 19, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 13, // Total Path Attribute Length + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + 0, // Attribute flags + 8, // Attribute Type code (Community) + 5, // Length + 0, 0, 1, 0, 1, // Arbitrary Community + }, + wantFail: true, + expected: nil, + }, + { + // 2 withdraws with four path attributes (Communities + AS4Path +AS4Aggregator + Origin), valid update + testNum: 19, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 30, // Total Path Attribute Length + + 0, // Attribute flags + 8, // Attribute Type code (Community) + 8, // Length + 0, 0, 1, 0, // Arbitrary Community + 0, 0, 1, 1, // Arbitrary Community + + 128, // Attribute flags + 17, // Attribute Type code (AS4Path) + 4, // Length + 0, 0, 2, 3, // Arbitrary Bytes + + 128, // Attribute flags + 18, // Attribute Type code (AS4Aggregator) + 4, // Length + 0, 0, 2, 3, // Arbitrary Bytes + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + }, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: strAddr("10.0.0.0"), + Pfxlen: 8, + Next: &NLRI{ + IP: strAddr("192.168.0.0"), + Pfxlen: 16, + }, + }, + TotalPathAttrLen: 30, + PathAttributes: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 8, + TypeCode: 8, + Value: []uint32{256, 257}, + Next: &PathAttribute{ + Optional: true, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 17, + Value: uint32(515), + Next: &PathAttribute{ + Optional: true, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 18, + Value: uint32(515), + Next: &PathAttribute{ + Optional: true, + Transitive: true, + Partial: true, + ExtendedLength: true, + Length: 1, + TypeCode: 1, + Value: uint8(2), + }, + }, + }, + }, + }, + }, } for _, test := range tests { diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index d9b5aa0cae45f4f1079536e17ddf6f4d75dd387c..055a85e244cefbd101e9b41465e095d935d35e8d 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -84,17 +84,46 @@ func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err } case AtomicAggrAttr: // Nothing to do for 0 octet long attribute + case CommunitiesAttr: + if err := pa.decodeCommunities(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode Community: %v", err) + } + case AS4PathAttr: + if err := pa.decodeAS4Path(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to skip not supported AS4Path: %v", err) + } + case AS4AggregatorAttr: + if err := pa.decodeAS4Aggregator(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to skip not supported AS4Aggregator: %v", err) + } case LargeCommunityAttr: if err := pa.decodeLargeCommunities(buf); err != nil { return nil, consumed, fmt.Errorf("Failed to decode large communities: %v", err) } default: - return nil, consumed, fmt.Errorf("Invalid Attribute Type Code: %v", pa.TypeCode) + if err := pa.decodeUnknown(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode unknown attribute: %v", err) + } } return pa, consumed + pa.Length, nil } +func (pa *PathAttribute) decodeUnknown(buf *bytes.Buffer) error { + u := make([]byte, pa.Length) + + p := uint16(0) + err := decode(buf, []interface{}{&u}) + if err != nil { + return fmt.Errorf("Unable to decode: %v", err) + } + + pa.Value = u + p += pa.Length + + return nil +} + func (pa *PathAttribute) decodeOrigin(buf *bytes.Buffer) error { origin := uint8(0) @@ -214,6 +243,27 @@ func (pa *PathAttribute) decodeAggregator(buf *bytes.Buffer) error { return dumpNBytes(buf, pa.Length-p) } +func (pa *PathAttribute) decodeCommunities(buf *bytes.Buffer) error { + if pa.Length%4 != 0 { + return fmt.Errorf("Unable to read community path attribute length %d is not divisible by 4", pa.Length) + } + comNumber := pa.Length / 4 + var com = make([]uint32, comNumber) + for i := uint16(0); i < comNumber; i++ { + c := [4]byte{} + n, err := buf.Read(c[:]) + if err != nil { + return err + } + if n != 4 { + return fmt.Errorf("Unable to read next hop: buf.Read read %d bytes", n) + } + com[i] = fourBytesToUint32(c) + } + pa.Value = com + return nil +} + func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error { length := pa.Length count := length / LargeCommunityLen @@ -250,6 +300,26 @@ func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error { return dumpNBytes(buf, dump) } +func (pa *PathAttribute) decodeAS4Path(buf *bytes.Buffer) error { + as4Path, err := pa.decodeUint32(buf) + if err != nil { + return fmt.Errorf("Unable to decode AS4Path: %v", err) + } + + pa.Value = as4Path + return nil +} + +func (pa *PathAttribute) decodeAS4Aggregator(buf *bytes.Buffer) error { + as4Aggregator, err := pa.decodeUint32(buf) + if err != nil { + return fmt.Errorf("Unable to decode AS4Aggregator: %v", err) + } + + pa.Value = as4Aggregator + return nil +} + func (pa *PathAttribute) setLength(buf *bytes.Buffer) (int, error) { bytesRead := 0 if pa.ExtendedLength { @@ -527,6 +597,10 @@ func ParseASPathStr(asPathString string) (*PathAttribute, error) { newSegmentNeeded := true currentSegment := -1 for _, asn := range strings.Split(asPathString, " ") { + if asn == "" { + continue + } + if isBeginOfASSet(asn) { currentType = ASSet newSegmentNeeded = true diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index a56fba0d237d26128af87c500e12a62bb11217b7..0e049760ae3a08b9540bef33906a072dfc29e187 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -1456,6 +1456,15 @@ func TestParseASPathStr(t *testing.T) { wantFail bool expected *PathAttribute }{ + { + name: "Empty AS Path", + input: "", + wantFail: false, + expected: &PathAttribute{ + TypeCode: ASPathAttr, + Value: ASPath{}, + }, + }, { name: "Simple AS_SEQUENCE", input: "3320 15169", diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go index d4c472066583d2c0190b64ef60b98f54a61b981e..1a20503e13c9057fa5ed454150d422dd0faa9774 100644 --- a/protocols/bgp/server/fsm.go +++ b/protocols/bgp/server/fsm.go @@ -16,7 +16,6 @@ import ( "github.com/bio-routing/bio-rd/routingtable/adjRIBIn" "github.com/bio-routing/bio-rd/routingtable/adjRIBOut" "github.com/bio-routing/bio-rd/routingtable/adjRIBOutAddPath" - "github.com/bio-routing/bio-rd/routingtable/locRIB" log "github.com/sirupsen/logrus" tomb "gopkg.in/tomb.v2" ) @@ -84,7 +83,7 @@ type FSM struct { holdTimer *time.Timer keepaliveTime time.Duration - keepaliveTimer *time.Timer + keepaliveTimer *time.Ticker msgRecvCh chan msgRecvMsg msgRecvFailCh chan msgRecvErr @@ -92,7 +91,7 @@ type FSM struct { adjRIBIn *adjRIBIn.AdjRIBIn adjRIBOut routingtable.RouteTableClient - rib *locRIB.LocRIB + rib routingtable.RouteTableClient updateSender routingtable.RouteTableClient capAddPathSend bool @@ -109,7 +108,7 @@ type msgRecvErr struct { con *net.TCPConn } -func NewFSM(peer *Peer, c config.Peer, rib *locRIB.LocRIB) *FSM { +func NewFSM(peer *Peer, c config.Peer, rib routingtable.RouteTableClient) *FSM { fsm := &FSM{ peer: peer, state: Idle, @@ -125,7 +124,7 @@ func NewFSM(peer *Peer, c config.Peer, rib *locRIB.LocRIB) *FSM { holdTimer: time.NewTimer(0), keepaliveTime: time.Duration(c.KeepAlive), - keepaliveTimer: time.NewTimer(0), + keepaliveTimer: time.NewTicker(time.Duration(c.KeepAlive)), routerID: c.RouterID, remote: c.PeerAddress, @@ -458,7 +457,10 @@ func (fsm *FSM) openSent() int { if fsm.holdTime != 0 { fsm.holdTimer.Reset(time.Second * fsm.holdTime) fsm.keepaliveTime = fsm.holdTime / 3 - fsm.keepaliveTimer.Reset(time.Second * fsm.keepaliveTime) + if fsm.keepaliveTimer != nil { + fsm.keepaliveTimer.Stop() + } + fsm.keepaliveTimer = time.NewTicker(fsm.keepaliveTime * time.Second) } fsm.processOpenOptions(openMsg.OptParams) @@ -624,7 +626,6 @@ func (fsm *FSM) openConfirm() int { fsm.connectRetryCounter++ return fsm.changeState(Idle, fmt.Sprintf("Failed to send keepalive: %v", err)) } - fsm.keepaliveTimer.Reset(time.Second * fsm.keepaliveTime) continue case c := <-fsm.conCh: if fsm.con2 != nil { @@ -647,7 +648,7 @@ func (fsm *FSM) openConfirm() int { case recvMsg := <-fsm.msgRecvCh: msg, err := packet.Decode(bytes.NewBuffer(recvMsg.msg)) if err != nil { - fmt.Printf("Failed to decode message: %v\n", recvMsg.msg) + log.WithError(err).Errorf("Failed to decode BGP message %v\n", recvMsg.msg) switch bgperr := err.(type) { case packet.BGPError: sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode) @@ -751,6 +752,7 @@ func (fsm *FSM) established() int { }()*/ for { + log.Debug("Iterate established loop.") select { case e := <-fsm.eventCh: if e == ManualStop { // Event 2 @@ -775,6 +777,7 @@ func (fsm *FSM) established() int { fsm.connectRetryCounter++ return fsm.changeState(Idle, "Holdtimer expired") case <-fsm.keepaliveTimer.C: + err := fsm.sendKeepalive() if err != nil { stopTimer(fsm.connectRetryTimer) @@ -782,7 +785,6 @@ func (fsm *FSM) established() int { fsm.connectRetryCounter++ return fsm.changeState(Idle, fmt.Sprintf("Failed to send keepalive: %v", err)) } - fsm.keepaliveTimer.Reset(time.Second * fsm.keepaliveTime) continue case c := <-fsm.conCh: c.Close() @@ -790,7 +792,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) + log.WithError(err).Errorf("Failed to decode BGP message %v\n", recvMsg.msg) switch bgperr := err.(type) { case packet.BGPError: sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode) @@ -815,14 +817,13 @@ func (fsm *FSM) established() int { for r := u.WithdrawnRoutes; r != nil; r = r.Next { pfx := tnet.NewPfx(r.IP, r.Pfxlen) - fmt.Printf("LPM: Removing prefix %s\n", pfx.String()) + log.WithField("Prefix", pfx.String()).Debug("LPM: Removing prefix") fsm.adjRIBIn.RemovePath(pfx, nil) } for r := u.NLRI; r != nil; r = r.Next { pfx := tnet.NewPfx(r.IP, r.Pfxlen) - fmt.Printf("LPM: Adding prefix %s\n", pfx.String()) - + log.WithField("Prefix", pfx.String()).Debug("LPM: Adding prefix") path := &route.Path{ Type: route.BGPPathType, BGPPath: &route.BGPPath{ @@ -831,7 +832,6 @@ func (fsm *FSM) established() int { } 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) @@ -840,7 +840,7 @@ 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)) + log.WithField("NextHop", pa.Value.(uint32)).Debug("RECEIVED NEXT_HOP") path.BGPPath.NextHop = pa.Value.(uint32) case packet.ASPathAttr: path.BGPPath.ASPath = pa.ASPathString() @@ -921,9 +921,10 @@ func (fsm *FSM) resetDelayOpenTimer() { } func (fsm *FSM) sendKeepalive() error { - msg := packet.SerializeKeepaliveMsg() + msg := packet.SerializeKeepaliveMsg() _, err := fsm.con.Write(msg) + log.WithError(err).Debug("Send keepalive") if err != nil { return fmt.Errorf("Unable to send KEEPALIVE message: %v", err) } diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go index 7bfcbc560181663fec221f76ada98d00ef29e517..5077aaeff37e7634f0e2d12bb4988d30f1f52775 100644 --- a/protocols/bgp/server/peer.go +++ b/protocols/bgp/server/peer.go @@ -3,32 +3,36 @@ package server import ( "net" + "github.com/bio-routing/bio-rd/config" "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/routingtable" - "github.com/bio-routing/bio-rd/routingtable/locRIB" - "github.com/bio-routing/bio-rd/config" + "time" ) type Peer struct { - addr net.IP - asn uint32 - fsm *FSM - rib *locRIB.LocRIB - routerID uint32 - addPathSend routingtable.ClientOptions - addPathRecv bool - optOpenParams []packet.OptParam + addr net.IP + asn uint32 + fsm *FSM + rib routingtable.RouteTableClient + routerID uint32 + addPathSend routingtable.ClientOptions + addPathRecv bool + optOpenParams []packet.OptParam + reconnectInterval time.Duration } -func NewPeer(c config.Peer, rib *locRIB.LocRIB) (*Peer, error) { +// NewPeer creates a new peer with the given config. If an connection is established, the adjRIBIN of the peer is connected +// to the given rib. To actually connect the peer, call Start() on the returned peer. +func NewPeer(c config.Peer, rib routingtable.RouteTableClient) (*Peer, error) { p := &Peer{ - addr: c.PeerAddress, - asn: c.PeerAS, - rib: rib, - addPathSend: c.AddPathSend, - addPathRecv: c.AddPathRecv, - optOpenParams: make([]packet.OptParam, 0), + addr: c.PeerAddress, + asn: c.PeerAS, + rib: rib, + addPathSend: c.AddPathSend, + addPathRecv: c.AddPathRecv, + optOpenParams: make([]packet.OptParam, 0), + reconnectInterval: c.ReconnectInterval, } p.fsm = NewFSM(p, c, rib) @@ -63,15 +67,29 @@ func NewPeer(c config.Peer, rib *locRIB.LocRIB) (*Peer, error) { return p, nil } +// GetAddr returns the IP address of the peer func (p *Peer) GetAddr() net.IP { return p.addr } +// GetASN returns the configured AS number of the peer func (p *Peer) GetASN() uint32 { return p.asn } +// Start the peers fsm. It starts from the Idle state and will get an ManualStart event. To trigger +// reconnects if the fsm falls back into the Idle state, every reconnectInterval a ManualStart event is send. +// The default value for reconnectInterval is 30 seconds. func (p *Peer) Start() { p.fsm.start() - p.fsm.activate() + if p.reconnectInterval == 0 { + p.reconnectInterval = 30 * time.Second + } + t := time.Tick(p.reconnectInterval) + go func() { + for { + <-t + p.fsm.activate() + } + }() } diff --git a/protocols/bgp/server/server.go b/protocols/bgp/server/server.go index 8a11cd3a0641c6ff926ddc4459da157d7273db44..a6025140fd62f81d8fa503aeb0c31114a7589f2b 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/routingtable/locRIB" + "github.com/bio-routing/bio-rd/routingtable" log "github.com/sirupsen/logrus" ) @@ -39,7 +39,7 @@ func (b *BGPServer) Start(c *config.Global) error { return fmt.Errorf("Failed to load defaults: %v", err) } - fmt.Printf("ROUTER ID: %d\n", c.RouterID) + log.Infof("ROUTER ID: %d\n", c.RouterID) b.routerID = c.RouterID if c.Listen { @@ -62,8 +62,6 @@ func (b *BGPServer) Start(c *config.Global) error { func (b *BGPServer) incomingConnectionWorker() { for { c := <-b.acceptCh - fmt.Printf("Incoming connection!\n") - fmt.Printf("Connection from: %v\n", c.RemoteAddr()) peerAddr := strings.Split(c.RemoteAddr().String(), ":")[0] if _, ok := b.peers[peerAddr]; !ok { @@ -78,13 +76,13 @@ func (b *BGPServer) incomingConnectionWorker() { "source": c.RemoteAddr(), }).Info("Incoming TCP connection") - fmt.Printf("DEBUG: Sending incoming TCP connection to fsm for peer %s\n", peerAddr) + log.WithField("Peer", peerAddr).Debug("Sending incoming TCP connection to fsm for peer") b.peers[peerAddr].fsm.conCh <- c - fmt.Printf("DEBUG: Sending done\n") + log.Debug("Sending done") } } -func (b *BGPServer) AddPeer(c config.Peer, rib *locRIB.LocRIB) error { +func (b *BGPServer) AddPeer(c config.Peer, rib routingtable.RouteTableClient) error { if c.LocalAS > uint16max || c.PeerAS > uint16max { return fmt.Errorf("32bit ASNs are not supported yet") } diff --git a/protocols/bgp/server/bgp_update.go b/protocols/bgp/server/update_helper.go similarity index 74% rename from protocols/bgp/server/bgp_update.go rename to protocols/bgp/server/update_helper.go index 28018bf102a420b49b513f057765a50ad7890e52..22bfeec52ad72e603583452c9bc9f70be314f4a7 100644 --- a/protocols/bgp/server/bgp_update.go +++ b/protocols/bgp/server/update_helper.go @@ -4,26 +4,10 @@ import ( "fmt" "strings" - "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/route" ) -func updateMessageForPath(pfx net.Prefix, p *route.Path, fsm *FSM) (*packet.BGPUpdate, error) { - pathAttrs, err := pathAttribues(p, fsm) - if err != nil { - return nil, err - } - - return &packet.BGPUpdate{ - PathAttributes: pathAttrs, - NLRI: &packet.NLRI{ - IP: pfx.Addr(), - Pfxlen: pfx.Pfxlen(), - }, - }, nil -} - func pathAttribues(p *route.Path, fsm *FSM) (*packet.PathAttribute, error) { asPathPA, err := packet.ParseASPathStr(strings.TrimRight(fmt.Sprintf("%d %s", fsm.localASN, p.BGPPath.ASPath), " ")) if err != nil { @@ -42,8 +26,14 @@ func pathAttribues(p *route.Path, fsm *FSM) (*packet.PathAttribute, error) { } asPathPA.Next = nextHop + localPref := &packet.PathAttribute{ + TypeCode: packet.LocalPrefAttr, + Value: p.BGPPath.LocalPref, + } + nextHop.Next = localPref + if p.BGPPath != nil { - err := addOptionalPathAttribues(p, nextHop) + err := addOptionalPathAttribues(p, localPref) if err != nil { return nil, err diff --git a/protocols/bgp/server/update_sender.go b/protocols/bgp/server/update_sender.go index 4a514ec4c5529c10846124ca2c4371a1022e40e6..54c1d1b0cfa28c9094015d50483af1fd12bfd2e6 100644 --- a/protocols/bgp/server/update_sender.go +++ b/protocols/bgp/server/update_sender.go @@ -2,10 +2,12 @@ package server import ( "fmt" + "strings" log "github.com/sirupsen/logrus" "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/route" "github.com/bio-routing/bio-rd/routingtable" ) @@ -13,23 +15,34 @@ import ( // UpdateSender converts table changes into BGP update messages type UpdateSender struct { routingtable.ClientManager - fsm *FSM + fsm *FSM + iBGP bool } func newUpdateSender(fsm *FSM) *UpdateSender { return &UpdateSender{ - fsm: fsm, + fsm: fsm, + iBGP: fsm.localASN == fsm.remoteASN, } } // AddPath serializes a new path and sends out a BGP update message func (u *UpdateSender) AddPath(pfx net.Prefix, p *route.Path) error { - update, err := updateMessageForPath(pfx, p, u.fsm) + pathAttrs, err := pathAttribues(p, u.fsm) if err != nil { log.Errorf("Unable to create BGP Update: %v", err) return nil } + update := &packet.BGPUpdateAddPath{ + PathAttributes: pathAttrs, + NLRI: &packet.NLRIAddPath{ + PathIdentifier: p.BGPPath.PathIdentifier, + IP: pfx.Addr(), + Pfxlen: pfx.Pfxlen(), + }, + } + updateBytes, err := update.SerializeUpdate() if err != nil { log.Errorf("Unable to serialize BGP Update: %v", err) @@ -54,3 +67,12 @@ func (u *UpdateSender) UpdateNewClient(client routingtable.RouteTableClient) err log.Warningf("BGP Update Sender: UpdateNewClient() not supported") return nil } + +func asPathString(iBGP bool, localASN uint16, asPath string) string { + ret := "" + if iBGP { + ret = ret + fmt.Sprintf("%d ", localASN) + } + ret = ret + asPath + return strings.TrimRight(ret, " ") +} diff --git a/protocols/bgp/server/update_sender_add_path.go b/protocols/bgp/server/update_sender_add_path.go index f7c51c569953946b7a290ce1c5facc7c2c2b48ae..84d6796046b8b3380a6afae2f7ab1257b59a699c 100644 --- a/protocols/bgp/server/update_sender_add_path.go +++ b/protocols/bgp/server/update_sender_add_path.go @@ -6,6 +6,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/protocols/bgp/packet" "github.com/bio-routing/bio-rd/route" "github.com/bio-routing/bio-rd/routingtable" ) @@ -13,23 +14,33 @@ import ( // UpdateSenderAddPath converts table changes into BGP update messages with add path type UpdateSenderAddPath struct { routingtable.ClientManager - fsm *FSM + fsm *FSM + iBGP bool } func newUpdateSenderAddPath(fsm *FSM) *UpdateSenderAddPath { return &UpdateSenderAddPath{ - fsm: fsm, + fsm: fsm, + iBGP: fsm.localASN == fsm.remoteASN, } } // AddPath serializes a new path and sends out a BGP update message func (u *UpdateSenderAddPath) AddPath(pfx net.Prefix, p *route.Path) error { - update, err := updateMessageForPath(pfx, p, u.fsm) + pathAttrs, err := pathAttribues(p, u.fsm) if err != nil { log.Errorf("Unable to create BGP Update: %v", err) return nil } + update := &packet.BGPUpdate{ + PathAttributes: pathAttrs, + NLRI: &packet.NLRI{ + IP: pfx.Addr(), + Pfxlen: pfx.Pfxlen(), + }, + } + updateBytes, err := update.SerializeUpdate() if err != nil { log.Errorf("Unable to serialize BGP Update: %v", err) diff --git a/route/bgp.go b/route/bgp.go index 1109a42d2b8ee12cd4d1b7719ee402109b33085f..a19c4cde94873f5761fd579737bacde3fd240729 100644 --- a/route/bgp.go +++ b/route/bgp.go @@ -78,6 +78,14 @@ func (b *BGPPath) Compare(c *BGPPath) int8 { return -1 } + if c.NextHop < b.NextHop { + return 1 + } + + if c.NextHop > b.NextHop { + return -1 + } + return 0 } @@ -133,10 +141,6 @@ func (b *BGPPath) better(c *BGPPath) bool { 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 -} - func (b *BGPPath) Print() string { origin := "" switch b.Origin { diff --git a/routingtable/adjRIBIn/adj_rib_in.go b/routingtable/adjRIBIn/adj_rib_in.go index 1427278bbd5aa6c465c16bf2ac4e1462fd5a34d8..cd4dde58f17e42d9bf75433850dfa39bf2498fbf 100644 --- a/routingtable/adjRIBIn/adj_rib_in.go +++ b/routingtable/adjRIBIn/adj_rib_in.go @@ -6,6 +6,7 @@ import ( "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/route" "github.com/bio-routing/bio-rd/routingtable" + log "github.com/sirupsen/logrus" ) // AdjRIBIn represents an Adjacency RIB In as described in RFC4271 @@ -33,7 +34,11 @@ func (a *AdjRIBIn) UpdateNewClient(client routingtable.RouteTableClient) error { for _, route := range routes { paths := route.Paths() for _, path := range paths { - client.AddPath(route.Prefix(), path) + + err := client.AddPath(route.Prefix(), path) + if err != nil { + log.WithField("Sender", "AdjRIBOutAddPath").WithError(err).Error("Could not send update to client") + } } } return nil diff --git a/routingtable/adjRIBIn/adj_rib_in_test.go b/routingtable/adjRIBIn/adj_rib_in_test.go index 5659ab805f73b3bf888a08fa8ff6a7dbe8185bdf..a994e0f5e82be4ccef141ce63463b762d055d111 100644 --- a/routingtable/adjRIBIn/adj_rib_in_test.go +++ b/routingtable/adjRIBIn/adj_rib_in_test.go @@ -34,6 +34,10 @@ func (m *RTMockClient) Register(routingtable.RouteTableClient) { return } +func (m *RTMockClient) RegisterWithOptions(routingtable.RouteTableClient, routingtable.ClientOptions) { + return +} + func (m *RTMockClient) Unregister(routingtable.RouteTableClient) { return } diff --git a/routingtable/adjRIBOut/adj_rib_out.go b/routingtable/adjRIBOut/adj_rib_out.go index 1ddd93c6e05b835c9952b6e86ba5fe57545ed5e4..51347119a6b360fd2f81283edc18d9225548fdf2 100644 --- a/routingtable/adjRIBOut/adj_rib_out.go +++ b/routingtable/adjRIBOut/adj_rib_out.go @@ -7,6 +7,7 @@ import ( "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/route" "github.com/bio-routing/bio-rd/routingtable" + log "github.com/sirupsen/logrus" ) // AdjRIBOut represents an Adjacency RIB In as described in RFC4271 @@ -45,7 +46,10 @@ func (a *AdjRIBOut) AddPath(pfx net.Prefix, p *route.Path) error { a.removePathsFromClients(pfx, oldPaths) for _, client := range a.ClientManager.Clients() { - client.AddPath(pfx, p) + err := client.AddPath(pfx, p) + if err != nil { + log.WithField("Sender", "AdjRIBOut").WithError(err).Error("Could not send update to client") + } } return nil } diff --git a/routingtable/adjRIBOutAddPath/adj_rib_out_add_path.go b/routingtable/adjRIBOutAddPath/adj_rib_out_add_path.go index 2b4dff95575b2df72036642f40bd69cfedb4e5ab..90f76fcc873643a6d94086b0b84f5bf940b6fa9f 100644 --- a/routingtable/adjRIBOutAddPath/adj_rib_out_add_path.go +++ b/routingtable/adjRIBOutAddPath/adj_rib_out_add_path.go @@ -7,6 +7,7 @@ import ( "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/route" "github.com/bio-routing/bio-rd/routingtable" + log "github.com/sirupsen/logrus" ) // AdjRIBOutAddPath represents an Adjacency RIB Out with BGP add path @@ -52,7 +53,10 @@ func (a *AdjRIBOutAddPath) AddPath(pfx net.Prefix, p *route.Path) error { a.rt.AddPath(pfx, p) for _, client := range a.ClientManager.Clients() { - client.AddPath(pfx, p) + err := client.AddPath(pfx, p) + if err != nil { + log.WithField("Sender", "AdjRIBOutAddPath").WithError(err).Error("Could not send update to client") + } } return nil } diff --git a/routingtable/client_interface.go b/routingtable/client_interface.go index 0800ceec8db1cb3b758b74b3a1d3c8b7aef62acb..e7190c5b6c0bb1829f59cf5faadd0623ef3b395c 100644 --- a/routingtable/client_interface.go +++ b/routingtable/client_interface.go @@ -11,5 +11,6 @@ type RouteTableClient interface { RemovePath(net.Prefix, *route.Path) bool UpdateNewClient(RouteTableClient) error Register(RouteTableClient) + RegisterWithOptions(RouteTableClient, ClientOptions) Unregister(RouteTableClient) } diff --git a/routingtable/client_manager.go b/routingtable/client_manager.go index 20d848b2c090a4c311cb4a81fc8c1c4697cf5891..f29ae2dce47dc7e4a3d3ba725ff5d57fd27f74f6 100644 --- a/routingtable/client_manager.go +++ b/routingtable/client_manager.go @@ -55,9 +55,8 @@ func (c *ClientManager) Register(client RouteTableClient) { // 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.mu.Unlock() c.master.UpdateNewClient(client) } diff --git a/routingtable/client_manager_test.go b/routingtable/client_manager_test.go index af2507855bfac8a5d09551b105d7bfc4ae93dd79..78e2e58c4030a2e25a1b9ae228ef712fc1c67550 100644 --- a/routingtable/client_manager_test.go +++ b/routingtable/client_manager_test.go @@ -25,6 +25,11 @@ func (m MockClient) UpdateNewClient(RouteTableClient) error { func (m MockClient) Register(RouteTableClient) { return } + +func (m MockClient) RegisterWithOptions(RouteTableClient, ClientOptions) { + return +} + func (m MockClient) Unregister(RouteTableClient) { return } diff --git a/routingtable/filter/term_condition.go b/routingtable/filter/term_condition.go index 946a04e85781d160dd893dab84588a965ff9190e..003b9dcbc021e75cd28d3039efbad2d00bf1d109 100644 --- a/routingtable/filter/term_condition.go +++ b/routingtable/filter/term_condition.go @@ -11,6 +11,13 @@ type TermCondition struct { largeCommunityFilters []*LargeCommunityFilter } +func NewTermCondition(prefixLists []*PrefixList, routeFilters []*RouteFilter) *TermCondition { + return &TermCondition{ + prefixLists: prefixLists, + routeFilters: routeFilters, + } +} + func (f *TermCondition) Matches(p net.Prefix, pa *route.Path) bool { return f.matchesAnyPrefixList(p) || f.machtchesAnyRouteFilter(p) || f.machtchesAnyLageCommunityFilter(pa) } diff --git a/routingtable/filter/term_condition_test.go b/routingtable/filter/term_condition_test.go index 307cd5e0485d6388661c61c4f67cf1c2a4bdb9bd..6e9b89f20eb226faebdd3856fa4bc61a97388a4e 100644 --- a/routingtable/filter/term_condition_test.go +++ b/routingtable/filter/term_condition_test.go @@ -144,11 +144,8 @@ func TestMatches(t *testing.T) { for _, test := range tests { t.Run(test.name, func(te *testing.T) { - f := &TermCondition{ - prefixLists: test.prefixLists, - routeFilters: test.routeFilters, - largeCommunityFilters: test.largeCommunityFilters, - } + f := NewTermCondition(test.prefixLists, test.routeFilters) + f.largeCommunityFilters = test.largeCommunityFilters pa := &route.Path{ BGPPath: test.bgpPath, diff --git a/routingtable/locRIB/loc_rib.go b/routingtable/locRIB/loc_rib.go index 584954a954e394bf7f859693b87ec827529a4b05..ff5750c34d226080bea079968becdec410d2704e 100644 --- a/routingtable/locRIB/loc_rib.go +++ b/routingtable/locRIB/loc_rib.go @@ -8,6 +8,7 @@ import ( "github.com/bio-routing/bio-rd/net" "github.com/bio-routing/bio-rd/route" "github.com/bio-routing/bio-rd/routingtable" + "github.com/sirupsen/logrus" ) // LocRIB represents a routing information base @@ -44,6 +45,10 @@ func (a *LocRIB) AddPath(pfx net.Prefix, p *route.Path) error { a.mu.Lock() defer a.mu.Unlock() + logrus.WithFields(map[string]interface{}{ + "Prefix": pfx, + "Route": p, + }).Debug("AddPath to locRIB") routeExisted := false oldRoute := &route.Route{} r := a.rt.Get(pfx) @@ -70,10 +75,16 @@ func (a *LocRIB) RemovePath(pfx net.Prefix, p *route.Path) bool { a.mu.Lock() defer a.mu.Unlock() + logrus.WithFields(map[string]interface{}{ + "Prefix": pfx, + "Route": p, + }).Debug("Remove from locRIB") var oldRoute *route.Route r := a.rt.Get(pfx) if r != nil { oldRoute = r.Copy() + } else { + return true } a.rt.RemovePath(pfx, p) diff --git a/routingtable/locRIB/loc_rib_test.go b/routingtable/locRIB/loc_rib_test.go index 6493991c9c30a4df8352b4496f3fd953ad60a926..ea4d9c6c30e5596f4b0685d92a20b86eb182bf16 100644 --- a/routingtable/locRIB/loc_rib_test.go +++ b/routingtable/locRIB/loc_rib_test.go @@ -84,3 +84,14 @@ func TestContainsPfxPath(t *testing.T) { assert.Equal(t, tc.expected, contains, "mismatch in testcase %v", i) } } + +func TestLocRIB_RemovePathUnknown(t *testing.T) { + rib := New() + assert.True(t, rib.RemovePath(net.NewPfx(1, 32), + &route.Path{ + Type: route.StaticPathType, + StaticPath: &route.StaticPath{ + NextHop: 2, + }, + })) +}