From 1219d029b90ed8a31c5c933fcf191b9f6d2113ff Mon Sep 17 00:00:00 2001 From: Oliver Herms <oliver.herms@exaring.de> Date: Tue, 17 Apr 2018 11:27:32 +0200 Subject: [PATCH] init --- AUTHORS | 13 + CONTRIBUTING.md | 15 + CONTRIBUTORS | 7 + main.go | 41 + net/prefix.go | 104 ++ net/prefix_test.go | 334 ++++ protocols/bgp/packet/bgp.go | 147 ++ protocols/bgp/packet/decoder.go | 273 ++++ protocols/bgp/packet/decoder_test.go | 1536 ++++++++++++++++++ protocols/bgp/packet/encoder.go | 45 + protocols/bgp/packet/encoder_test.go | 130 ++ protocols/bgp/packet/nlri.go | 60 + protocols/bgp/packet/nlri_test.go | 129 ++ protocols/bgp/packet/path_attribute_flags.go | 46 + protocols/bgp/packet/path_attributes.go | 289 ++++ protocols/bgp/packet/path_attributes_test.go | 759 +++++++++ protocols/bgp/server/fsm.go | 865 ++++++++++ protocols/bgp/server/peer.go | 36 + protocols/bgp/server/server.go | 119 ++ protocols/bgp/server/sockopt.go | 31 + protocols/bgp/server/tcplistener.go | 65 + protocols/bgp/server/util.go | 33 + 22 files changed, 5077 insertions(+) create mode 100644 AUTHORS create mode 100644 CONTRIBUTING.md create mode 100644 CONTRIBUTORS create mode 100644 main.go create mode 100644 net/prefix.go create mode 100644 net/prefix_test.go create mode 100644 protocols/bgp/packet/bgp.go create mode 100644 protocols/bgp/packet/decoder.go create mode 100644 protocols/bgp/packet/decoder_test.go create mode 100644 protocols/bgp/packet/encoder.go create mode 100644 protocols/bgp/packet/encoder_test.go create mode 100644 protocols/bgp/packet/nlri.go create mode 100644 protocols/bgp/packet/nlri_test.go create mode 100644 protocols/bgp/packet/path_attribute_flags.go create mode 100644 protocols/bgp/packet/path_attributes.go create mode 100644 protocols/bgp/packet/path_attributes_test.go create mode 100644 protocols/bgp/server/fsm.go create mode 100644 protocols/bgp/server/peer.go create mode 100644 protocols/bgp/server/server.go create mode 100644 protocols/bgp/server/sockopt.go create mode 100644 protocols/bgp/server/tcplistener.go create mode 100644 protocols/bgp/server/util.go diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..61a22aa1 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,13 @@ +# This is the official list of bio-routing‚ authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as one of +# Organization's name +# Individual's name <submission email address> +# Individual's name <submission email address> <email2> <emailN> +# See CONTRIBUTORS for the meaning of multiple email addresses. + +# Please keep the list sorted. + +Oliver Herms diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..1b082ebe --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +Want to contribute? Great! First, read this page. + +### Code guidelines +We expect all submissions to be properly formatted using gofmt. +Golint and govet shall not complain about your code. Gocyclo shall not report +complexity above 15. And your code should not lower the testcoverage of our +codebase. + +### Code reviews +All submissions, including submissions by project members, require review. We +use Github pull requests for this purpose. + +### License +By sending us your code you agree to release your contribution under the projects +chosen license: Apche License 2.0 (see LICENSE file). \ No newline at end of file diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 00000000..d9d12494 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,7 @@ +# This is the official list of people who can contribute +# (and typically have contributed) code to the bio-rd repository. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. + +Oliver Herms diff --git a/main.go b/main.go new file mode 100644 index 00000000..108c326e --- /dev/null +++ b/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "net" + "sync" + + "github.com/sirupsen/logrus" + + "github.com/taktv6/tbgp/config" + "github.com/taktv6/tbgp/server" +) + +func main() { + fmt.Printf("This is a BGP speaker\n") + + b := server.NewBgpServer() + + err := b.Start(&config.Global{ + Listen: true, + }) + if err != nil { + logrus.Fatalf("Unable to start BGP server: %v", err) + } + + b.AddPeer(config.Peer{ + AdminEnabled: true, + LocalAS: 65200, + PeerAS: 65201, + PeerAddress: net.IP([]byte{169, 254, 123, 1}), + LocalAddress: net.IP([]byte{169, 254, 123, 0}), + HoldTimer: 90, + KeepAlive: 30, + Passive: true, + RouterID: b.RouterID(), + }) + + var wg sync.WaitGroup + wg.Add(1) + wg.Wait() +} diff --git a/net/prefix.go b/net/prefix.go new file mode 100644 index 00000000..72ece5d6 --- /dev/null +++ b/net/prefix.go @@ -0,0 +1,104 @@ +package net + +import ( + "fmt" + "math" + "net" + "strconv" + "strings" + + "github.com/taktv6/tflow2/convert" +) + +// Prefix represents an IPv4 prefix +type Prefix struct { + addr uint32 + pfxlen uint8 +} + +// NewPfx creates a new Prefix +func NewPfx(addr uint32, pfxlen uint8) *Prefix { + return &Prefix{ + addr: addr, + pfxlen: pfxlen, + } +} + +// StrToAddr converts an IP address string to it's uint32 representation +func StrToAddr(x string) (uint32, error) { + parts := strings.Split(x, ".") + if len(parts) != 4 { + return 0, fmt.Errorf("Invalid format") + } + + ret := uint32(0) + for i := 0; i < 4; i++ { + y, err := strconv.Atoi(parts[i]) + if err != nil { + return 0, fmt.Errorf("Unable to convert %q to int: %v", parts[i], err) + } + + if y > 255 { + return 0, fmt.Errorf("%d is too big for a uint8", y) + } + + ret += uint32(math.Pow(256, float64(3-i))) * uint32(y) + } + + return ret, nil +} + +// Addr returns the address of the prefix +func (pfx *Prefix) Addr() uint32 { + return pfx.addr +} + +// Pfxlen returns the length of the prefix +func (pfx *Prefix) Pfxlen() uint8 { + return pfx.pfxlen +} + +// String returns a string representation of pfx +func (pfx *Prefix) String() string { + return fmt.Sprintf("%s/%d", net.IP(convert.Uint32Byte(pfx.addr)), pfx.pfxlen) +} + +// Contains checks if x is a subnet of or equal to pfx +func (pfx *Prefix) Contains(x *Prefix) bool { + if x.pfxlen <= pfx.pfxlen { + return false + } + + mask := (uint32(1) << (32 - pfx.pfxlen)) + return (pfx.addr & mask) == (x.addr & mask) +} + +// Equal checks if pfx and x are equal +func (pfx *Prefix) Equal(x *Prefix) bool { + return *pfx == *x +} + +// GetSupernet gets the next common supernet of pfx and x +func (pfx *Prefix) GetSupernet(x *Prefix) *Prefix { + maxPfxLen := min(pfx.pfxlen, x.pfxlen) - 1 + a := pfx.addr >> (32 - maxPfxLen) + b := x.addr >> (32 - maxPfxLen) + + for i := 0; a != b; i++ { + a = a >> 1 + b = b >> 1 + maxPfxLen-- + } + + return &Prefix{ + addr: a << (32 - maxPfxLen), + pfxlen: maxPfxLen, + } +} + +func min(a uint8, b uint8) uint8 { + if a < b { + return a + } + return b +} diff --git a/net/prefix_test.go b/net/prefix_test.go new file mode 100644 index 00000000..45fb9028 --- /dev/null +++ b/net/prefix_test.go @@ -0,0 +1,334 @@ +package net + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewPfx(t *testing.T) { + p := NewPfx(123, 11) + if p.addr != 123 || p.pfxlen != 11 { + t.Errorf("NewPfx() failed: Unexpected values") + } +} + +func TestAddr(t *testing.T) { + tests := []struct { + name string + pfx *Prefix + expected uint32 + }{ + { + name: "Test 1", + pfx: NewPfx(100, 5), + expected: 100, + }, + } + + for _, test := range tests { + res := test.pfx.Addr() + if res != test.expected { + t.Errorf("Unexpected result for test %s: Got %d Expected %d", test.name, res, test.expected) + } + } +} + +func TestPfxlen(t *testing.T) { + tests := []struct { + name string + pfx *Prefix + expected uint8 + }{ + { + name: "Test 1", + pfx: NewPfx(100, 5), + expected: 5, + }, + } + + for _, test := range tests { + res := test.pfx.Pfxlen() + if res != test.expected { + t.Errorf("Unexpected result for test %s: Got %d Expected %d", test.name, res, test.expected) + } + } +} + +func TestGetSupernet(t *testing.T) { + tests := []struct { + name string + a *Prefix + b *Prefix + expected *Prefix + }{ + { + name: "Test 1", + a: &Prefix{ + addr: 167772160, // 10.0.0.0/8 + pfxlen: 8, + }, + b: &Prefix{ + addr: 191134464, // 11.100.123.0/24 + pfxlen: 24, + }, + expected: &Prefix{ + addr: 167772160, // 10.0.0.0/7 + pfxlen: 7, + }, + }, + { + name: "Test 2", + a: &Prefix{ + addr: 167772160, // 10.0.0.0/8 + pfxlen: 8, + }, + b: &Prefix{ + addr: 3232235520, // 192.168.0.0/24 + pfxlen: 24, + }, + expected: &Prefix{ + addr: 0, // 0.0.0.0/0 + pfxlen: 0, + }, + }, + } + + for _, test := range tests { + s := test.a.GetSupernet(test.b) + assert.Equal(t, s, test.expected) + } +} + +func TestContains(t *testing.T) { + tests := []struct { + name string + a *Prefix + b *Prefix + expected bool + }{ + { + name: "Test 1", + a: &Prefix{ + addr: 0, + pfxlen: 0, + }, + b: &Prefix{ + addr: 100, + pfxlen: 24, + }, + expected: true, + }, + { + name: "Test 2", + a: &Prefix{ + addr: 100, + pfxlen: 24, + }, + b: &Prefix{ + addr: 0, + pfxlen: 0, + }, + expected: false, + }, + { + name: "Test 3", + a: &Prefix{ + addr: 167772160, + pfxlen: 8, + }, + b: &Prefix{ + addr: 167772160, + pfxlen: 9, + }, + expected: true, + }, + { + name: "Test 4", + a: &Prefix{ + addr: 167772160, + pfxlen: 8, + }, + b: &Prefix{ + addr: 174391040, + pfxlen: 24, + }, + expected: true, + }, + { + name: "Test 5", + a: &Prefix{ + addr: 167772160, + pfxlen: 8, + }, + b: &Prefix{ + addr: 184549377, + pfxlen: 24, + }, + expected: false, + }, + { + name: "Test 6", + a: &Prefix{ + addr: 167772160, + pfxlen: 8, + }, + b: &Prefix{ + addr: 191134464, + pfxlen: 24, + }, + expected: false, + }, + } + + for _, test := range tests { + res := test.a.Contains(test.b) + if res != test.expected { + t.Errorf("Unexpected result %v for test %s: %s contains %s\n", res, test.name, test.a.String(), test.b.String()) + } + } +} + +func TestMin(t *testing.T) { + tests := []struct { + name string + a uint8 + b uint8 + expected uint8 + }{ + { + name: "Min 100 200", + a: 100, + b: 200, + expected: 100, + }, + { + name: "Min 200 100", + a: 200, + b: 100, + expected: 100, + }, + { + name: "Min 111 112", + a: 111, + b: 112, + expected: 111, + }, + } + + for _, test := range tests { + res := min(test.a, test.b) + if res != test.expected { + t.Errorf("Unexpected result for test %s: Got %d Expected %d", test.name, res, test.expected) + } + } +} + +func TestEqual(t *testing.T) { + tests := []struct { + name string + a *Prefix + b *Prefix + expected bool + }{ + { + name: "Equal PFXs", + a: NewPfx(100, 8), + b: NewPfx(100, 8), + expected: true, + }, + { + name: "Unequal PFXs", + a: NewPfx(100, 8), + b: NewPfx(200, 8), + expected: false, + }, + } + + for _, test := range tests { + res := test.a.Equal(test.b) + if res != test.expected { + t.Errorf("Unexpected result for %q: Got %v Expected %v", test.name, res, test.expected) + } + } +} + +func TestString(t *testing.T) { + tests := []struct { + name string + pfx *Prefix + expected string + }{ + { + name: "Test 1", + pfx: NewPfx(167772160, 8), // 10.0.0.0/8 + expected: "10.0.0.0/8", + }, + { + name: "Test 2", + pfx: NewPfx(167772160, 16), // 10.0.0.0/8 + expected: "10.0.0.0/16", + }, + } + + for _, test := range tests { + res := test.pfx.String() + if res != test.expected { + t.Errorf("Unexpected result for %q: Got %q Expected %q", test.name, res, test.expected) + } + } +} + +func TestStrToAddr(t *testing.T) { + tests := []struct { + name string + input string + wantFail bool + expected uint32 + }{ + { + name: "Invalid address #1", + input: "10.10.10", + wantFail: true, + }, + { + name: "Invalid address #2", + input: "", + wantFail: true, + }, + { + name: "Invalid address #3", + input: "10.10.10.10.10", + wantFail: true, + }, + { + name: "Invalid address #4", + input: "10.256.0.0", + wantFail: true, + }, + { + name: "Valid address", + input: "10.0.0.0", + wantFail: false, + expected: 167772160, + }, + } + + for _, test := range tests { + res, err := StrToAddr(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) + } +} diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go new file mode 100644 index 00000000..46be5aed --- /dev/null +++ b/protocols/bgp/packet/bgp.go @@ -0,0 +1,147 @@ +package packet + +const ( + OctetLen = 8 + BGP4Version = 4 + + MarkerLen = 16 + HeaderLen = 19 + MinLen = 19 + MaxLen = 4096 + + OpenMsg = 1 + UpdateMsg = 2 + NotificationMsg = 3 + KeepaliveMsg = 4 + + MessageHeaderError = 1 + OpenMessageError = 2 + UpdateMessageError = 3 + HoldTimeExpired = 4 + FiniteStateMachineError = 5 + Cease = 6 + + // Msg Header Errors + ConnectionNotSync = 1 + BadMessageLength = 2 + BadMessageType = 3 + + // Open Msg Errors + UnsupportedVersionNumber = 1 + BadPeerAS = 2 + BadBGPIdentifier = 3 + UnsupportedOptionalParameter = 4 + DeprecatedOpenMsgError5 = 5 + UnacceptableHoldTime = 6 + + // Update Msg Errors + MalformedAttributeList = 1 + UnrecognizedWellKnownAttr = 2 + MissingWellKnonAttr = 3 + AttrFlagsError = 4 + AttrLengthError = 5 + InvalidOriginAttr = 6 + DeprecatedUpdateMsgError7 = 7 + InvalidNextHopAttr = 8 + OptionalAttrError = 9 + InvalidNetworkField = 10 + MalformedASPath = 11 + + // Attribute Type Codes + OriginAttr = 1 + ASPathAttr = 2 + NextHopAttr = 3 + MEDAttr = 4 + LocalPrefAttr = 5 + AtomicAggrAttr = 6 + AggregatorAttr = 7 + + // ORIGIN values + IGP = 0 + EGP = 1 + INCOMPLETE = 2 + + // ASPath Segment Types + ASSet = 1 + ASSequence = 2 + + // NOTIFICATION Cease error SubCodes (RFC4486) + MaxPrefReached = 1 + AdminShut = 2 + PeerDeconfigured = 3 + AdminReset = 4 + ConnectionRejected = 5 + OtherConfigChange = 8 + ConnectionCollisionResolution = 7 + OutOfResoutces = 8 +) + +type BGPError struct { + ErrorCode uint8 + ErrorSubCode uint8 + ErrorStr string +} + +func (b BGPError) Error() string { + return b.ErrorStr +} + +type BGPMessage struct { + Header *BGPHeader + Body interface{} +} + +type BGPHeader struct { + Length uint16 + Type uint8 +} + +type BGPOpen struct { + Version uint8 + AS uint16 + HoldTime uint16 + BGPIdentifier uint32 + OptParmLen uint8 +} + +type BGPNotification struct { + ErrorCode uint8 + ErrorSubcode uint8 +} + +type BGPUpdate struct { + WithdrawnRoutesLen uint16 + WithdrawnRoutes *NLRI + TotalPathAttrLen uint16 + PathAttributes *PathAttribute + NLRI *NLRI +} + +type PathAttribute struct { + Length uint16 + Optional bool + Transitive bool + Partial bool + ExtendedLength bool + TypeCode uint8 + Value interface{} + Next *PathAttribute +} + +type NLRI struct { + IP interface{} + Pfxlen uint8 + Next *NLRI +} + +type ASPath []ASPathSegment +type ASPathSegment struct { + Type uint8 + Count uint8 + ASNs []uint32 +} + +type Aggretator struct { + Addr [4]byte + ASN uint16 +} diff --git a/protocols/bgp/packet/decoder.go b/protocols/bgp/packet/decoder.go new file mode 100644 index 00000000..8ba824ec --- /dev/null +++ b/protocols/bgp/packet/decoder.go @@ -0,0 +1,273 @@ +package packet + +import ( + "bytes" + "encoding/binary" + "fmt" + "net" + + "github.com/taktv6/tflow2/convert" +) + +// Decode decodes a BGP message +func Decode(buf *bytes.Buffer) (*BGPMessage, error) { + hdr, err := decodeHeader(buf) + if err != nil { + return nil, fmt.Errorf("Failed to decode header: %v", err) + } + + body, err := decodeMsgBody(buf, hdr.Type, hdr.Length-MinLen) + if err != nil { + return nil, fmt.Errorf("Failed to decode message: %v", err) + } + + return &BGPMessage{ + Header: hdr, + Body: body, + }, nil +} + +func decodeMsgBody(buf *bytes.Buffer, msgType uint8, l uint16) (interface{}, error) { + switch msgType { + case OpenMsg: + return decodeOpenMsg(buf) + case UpdateMsg: + return decodeUpdateMsg(buf, l) + case KeepaliveMsg: + return nil, nil // Nothing to decode in Keepalive message + case NotificationMsg: + return decodeNotificationMsg(buf) + } + return nil, fmt.Errorf("Unknown message type: %d", msgType) +} + +func decodeUpdateMsg(buf *bytes.Buffer, l uint16) (*BGPUpdate, error) { + msg := &BGPUpdate{} + + err := decode(buf, []interface{}{&msg.WithdrawnRoutesLen}) + if err != nil { + return msg, err + } + + msg.WithdrawnRoutes, err = decodeNLRIs(buf, uint16(msg.WithdrawnRoutesLen)) + if err != nil { + return msg, err + } + + err = decode(buf, []interface{}{&msg.TotalPathAttrLen}) + if err != nil { + return msg, err + } + + msg.PathAttributes, err = decodePathAttrs(buf, msg.TotalPathAttrLen) + if err != nil { + return msg, err + } + + nlriLen := uint16(l) - 4 - uint16(msg.TotalPathAttrLen) - uint16(msg.WithdrawnRoutesLen) + if nlriLen > 0 { + msg.NLRI, err = decodeNLRIs(buf, nlriLen) + if err != nil { + return msg, err + } + } + + return msg, nil +} + +func decodeNotificationMsg(buf *bytes.Buffer) (*BGPNotification, error) { + msg := &BGPNotification{} + + fields := []interface{}{ + &msg.ErrorCode, + &msg.ErrorSubcode, + } + + err := decode(buf, fields) + if err != nil { + return msg, err + } + + if msg.ErrorCode > Cease { + return msg, fmt.Errorf("Invalid error code: %d", msg.ErrorSubcode) + } + + switch msg.ErrorCode { + case MessageHeaderError: + if msg.ErrorSubcode > BadMessageType || msg.ErrorSubcode == 0 { + return invalidErrCode(msg) + } + case OpenMessageError: + if msg.ErrorSubcode > UnacceptableHoldTime || msg.ErrorSubcode == 0 || msg.ErrorSubcode == DeprecatedOpenMsgError5 { + return invalidErrCode(msg) + } + case UpdateMessageError: + if msg.ErrorSubcode > MalformedASPath || msg.ErrorSubcode == 0 || msg.ErrorSubcode == DeprecatedUpdateMsgError7 { + return invalidErrCode(msg) + } + case HoldTimeExpired: + if msg.ErrorSubcode != 0 { + return invalidErrCode(msg) + } + case FiniteStateMachineError: + if msg.ErrorSubcode != 0 { + return invalidErrCode(msg) + } + case Cease: + if msg.ErrorSubcode != 0 { + return invalidErrCode(msg) + } + default: + return invalidErrCode(msg) + } + + return msg, nil +} + +func invalidErrCode(n *BGPNotification) (*BGPNotification, error) { + return n, fmt.Errorf("Invalid error sub code: %d/%d", n.ErrorCode, n.ErrorSubcode) +} + +func decodeOpenMsg(buf *bytes.Buffer) (*BGPOpen, error) { + msg, err := _decodeOpenMsg(buf) + return msg.(*BGPOpen), err +} + +func _decodeOpenMsg(buf *bytes.Buffer) (interface{}, error) { + msg := &BGPOpen{} + + fields := []interface{}{ + &msg.Version, + &msg.AS, + &msg.HoldTime, + &msg.BGPIdentifier, + &msg.OptParmLen, + } + + err := decode(buf, fields) + if err != nil { + return msg, err + } + + err = validateOpen(msg) + if err != nil { + return nil, err + } + + return msg, nil +} + +func validateOpen(msg *BGPOpen) error { + if msg.Version != BGP4Version { + return BGPError{ + ErrorCode: OpenMessageError, + ErrorSubCode: UnsupportedVersionNumber, + ErrorStr: fmt.Sprintf("Unsupported version number"), + } + } + if !isValidIdentifier(msg.BGPIdentifier) { + return BGPError{ + ErrorCode: OpenMessageError, + ErrorSubCode: BadBGPIdentifier, + ErrorStr: fmt.Sprintf("Invalid BGP identifier"), + } + } + + return nil +} + +func isValidIdentifier(id uint32) bool { + addr := net.IP(convert.Uint32Byte(id)) + if addr.IsLoopback() { + return false + } + + if addr.IsMulticast() { + return false + } + + if addr[0] == 0 { + return false + } + + if addr[0] == 255 && addr[1] == 255 && addr[2] == 255 && addr[3] == 255 { + return false + } + + return true +} + +func decodeHeader(buf *bytes.Buffer) (*BGPHeader, error) { + hdr := &BGPHeader{} + + marker := make([]byte, MarkerLen) + n, err := buf.Read(marker) + if err != nil { + return hdr, BGPError{ + ErrorCode: Cease, + ErrorSubCode: 0, + ErrorStr: fmt.Sprintf("Failed to read from buffer: %v", err.Error()), + } + } + + if n != MarkerLen { + return hdr, BGPError{ + ErrorCode: Cease, + ErrorSubCode: 0, + ErrorStr: fmt.Sprintf("Unable to read marker"), + } + } + + for i := range marker { + if marker[i] != 255 { + return nil, BGPError{ + ErrorCode: MessageHeaderError, + ErrorSubCode: ConnectionNotSync, + ErrorStr: fmt.Sprintf("Invalid marker: %v", marker), + } + } + } + + fields := []interface{}{ + &hdr.Length, + &hdr.Type, + } + + err = decode(buf, fields) + if err != nil { + return hdr, BGPError{ + ErrorCode: Cease, + ErrorSubCode: 0, + ErrorStr: fmt.Sprintf("%v", err.Error()), + } + } + + if hdr.Length < MinLen || hdr.Length > MaxLen { + return hdr, BGPError{ + ErrorCode: MessageHeaderError, + ErrorSubCode: BadMessageLength, + ErrorStr: fmt.Sprintf("Invalid length in BGP header: %v", hdr.Length), + } + } + + if hdr.Type > KeepaliveMsg || hdr.Type == 0 { + return hdr, BGPError{ + ErrorCode: MessageHeaderError, + ErrorSubCode: BadMessageType, + ErrorStr: fmt.Sprintf("Invalid message type: %d", hdr.Type), + } + } + + return hdr, nil +} + +func decode(buf *bytes.Buffer, fields []interface{}) error { + var err error + for _, field := range fields { + err = binary.Read(buf, binary.BigEndian, field) + if err != nil { + return fmt.Errorf("Unable to read from buffer: %v", err) + } + } + return nil +} diff --git a/protocols/bgp/packet/decoder_test.go b/protocols/bgp/packet/decoder_test.go new file mode 100644 index 00000000..ddb20a2e --- /dev/null +++ b/protocols/bgp/packet/decoder_test.go @@ -0,0 +1,1536 @@ +package packet + +import ( + "bytes" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/taktv6/tflow2/convert" +) + +type test struct { + testNum int + input []byte + wantFail bool + expected interface{} +} + +type decodeFunc func(*bytes.Buffer) (interface{}, error) + +func BenchmarkDecodeUpdateMsg(b *testing.B) { + input := []byte{0, 5, 8, 10, 16, 192, 168, + 0, 53, // Total Path Attribute Length + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + 0, // Attribute flags + 2, // Attribute Type code (AS Path) + 12, // Length + 2, // Type = AS_SEQUENCE + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + 1, // Type = AS_SET + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + + 0, // Attribute flags + 3, // Attribute Type code (Next Hop) + 4, // Length + 10, 11, 12, 13, // Next Hop + + 0, // Attribute flags + 4, // Attribute Type code (MED) + 4, // Length + 0, 0, 1, 0, // MED 256 + + 0, // Attribute flags + 5, // Attribute Type code (Local Pref) + 4, // Length + 0, 0, 1, 0, // Local Pref 256 + + 0, // Attribute flags + 6, // Attribute Type code (Atomic Aggregate) + 0, // Length + + 0, // Attribute flags + 7, // Attribute Type code (Atomic Aggregate) + 6, // Length + 1, 2, // ASN + 10, 11, 12, 13, // Address + + 8, 11, // 11.0.0.0/8 + } + + for i := 0; i < b.N; i++ { + buf := bytes.NewBuffer(input) + _, err := decodeUpdateMsg(buf, uint16(len(input))) + if err != nil { + fmt.Printf("decodeUpdateMsg failed: %v\n", err) + } + //buf.Next(1) + } +} + +func TestDecode(t *testing.T) { + tests := []test{ + { + // Proper packet + testNum: 1, + input: []byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker + 0, 19, // Length + 4, // Type = Keepalive + + }, + wantFail: false, + expected: &BGPMessage{ + Header: &BGPHeader{ + Length: 19, + Type: 4, + }, + }, + }, + { + // Invalid marker + testNum: 2, + input: []byte{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, // Marker + 0, 19, // Length + 4, // Type = Keepalive + + }, + wantFail: true, + expected: &BGPMessage{ + Header: &BGPHeader{ + Length: 19, + Type: 4, + }, + }, + }, + { + // Proper NOTIFICATION packet + testNum: 3, + input: []byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker + 0, 21, // Length + 3, // Type = Notification + 1, 1, // Message Header Error, Connection Not Synchronized. + }, + wantFail: false, + expected: &BGPMessage{ + Header: &BGPHeader{ + Length: 21, + Type: 3, + }, + Body: &BGPNotification{ + ErrorCode: 1, + ErrorSubcode: 1, + }, + }, + }, + { + // Proper OPEN packet + testNum: 4, + input: []byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker + 0, 29, // Length + 1, // Type = Open + 4, // Version + 0, 200, //ASN, + 0, 15, // Holdtime + 10, 20, 30, 40, // BGP Identifier + 0, // Opt Parm Len + }, + wantFail: false, + expected: &BGPMessage{ + Header: &BGPHeader{ + Length: 29, + Type: 1, + }, + Body: &BGPOpen{ + Version: 4, + AS: 200, + HoldTime: 15, + BGPIdentifier: uint32(169090600), + OptParmLen: 0, + }, + }, + }, + { + // Incomplete OPEN packet + testNum: 5, + input: []byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker + 0, 28, // Length + 1, // Type = Open + 4, // Version + 0, 200, //ASN, + 0, 15, // Holdtime + 0, 0, 0, 100, // BGP Identifier + }, + wantFail: true, + expected: &BGPMessage{ + Header: &BGPHeader{ + Length: 28, + Type: 1, + }, + Body: &BGPOpen{ + Version: 4, + AS: 200, + HoldTime: 15, + BGPIdentifier: uint32(100), + }, + }, + }, + { + testNum: 6, + input: []byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker + 0, 28, // Length + 2, // Type = Update + 0, 5, 8, 10, 16, 192, 168, 0, 0, // 2 withdraws + }, + wantFail: false, + expected: &BGPMessage{ + Header: &BGPHeader{ + Length: 28, + Type: 2, + }, + Body: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + }, + }, + }, + { + testNum: 7, + input: []byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // Marker + 0, 28, // Length + 5, // Type = Invalid + 0, 5, 8, 10, 16, 192, 168, 0, 0, // Some more stuff + }, + wantFail: true, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(test.input) + msg, err := Decode(buf) + + if err != nil && !test.wantFail { + t.Errorf("Unexpected error in test %d: %v", test.testNum, err) + continue + } + + if err == nil && test.wantFail { + t.Errorf("Expected error did not happen in test %d", test.testNum) + continue + } + + if err != nil && test.wantFail { + continue + } + + if msg == nil { + t.Errorf("Unexpected nil result in test %d. Expected: %v", test.testNum, test.expected) + continue + } + + assert.Equal(t, test.expected, msg) + } +} + +func TestDecodeNotificationMsg(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + expected interface{} + }{ + { + name: "Invalid error code", + input: []byte{0, 0}, + wantFail: true, + }, + { + name: "Invalid error code #2", + input: []byte{7, 0}, + wantFail: true, + }, + { + name: "Invalid ErrSubCode (Header)", + input: []byte{1, 0}, + wantFail: true, + }, + { + name: "Invalid ErrSubCode (Header) #2", + input: []byte{1, 4}, + wantFail: true, + }, + { + name: "Invalid ErrSubCode (Open)", + input: []byte{2, 0}, + wantFail: true, + }, + { + name: "Invalid ErrSubCode (Open) #2", + input: []byte{2, 7}, + wantFail: true, + }, + { + name: "Invalid ErrSubCode (Open) #3", + input: []byte{2, 5}, + wantFail: true, + }, + { + name: "Invalid ErrSubCode (Update)", + input: []byte{3, 0}, + wantFail: true, + }, + { + name: "Invalid ErrSubCode (Update) #2", + input: []byte{3, 12}, + wantFail: true, + }, + { + name: "Invalid ErrSubCode (Update) #3", + input: []byte{3, 7}, + wantFail: true, + }, + { + name: "Valid Notification", + input: []byte{2, 2}, + wantFail: false, + expected: &BGPNotification{ + ErrorCode: 2, + ErrorSubcode: 2, + }, + }, + { + name: "Empty input", + input: []byte{}, + wantFail: true, + }, + { + name: "Hold Timer Expired", + input: []byte{4, 0}, + wantFail: false, + expected: &BGPNotification{ + ErrorCode: 4, + ErrorSubcode: 0, + }, + }, + { + name: "Hold Timer Expired (invalid subcode)", + input: []byte{4, 1}, + wantFail: true, + }, + { + name: "FSM Error", + input: []byte{5, 0}, + wantFail: false, + expected: &BGPNotification{ + ErrorCode: 5, + ErrorSubcode: 0, + }, + }, + { + name: "FSM Error (invalid subcode)", + input: []byte{5, 1}, + wantFail: true, + }, + { + name: "Cease", + input: []byte{6, 0}, + wantFail: false, + expected: &BGPNotification{ + ErrorCode: 6, + ErrorSubcode: 0, + }, + }, + { + name: "Cease (invalid subcode)", + input: []byte{6, 1}, + wantFail: true, + }, + } + + for _, test := range tests { + res, err := decodeNotificationMsg(bytes.NewBuffer(test.input)) + + if test.wantFail { + if err != nil { + continue + } + t.Errorf("Expected error did not happen for test %q", test.name) + continue + } + + if err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue + } + + assert.Equal(t, test.expected, res) + } +} + +func TestDecodeUpdateMsg(t *testing.T) { + tests := []struct { + testNum int + input []byte + explicitLength uint16 + wantFail bool + expected interface{} + }{ + { + // 2 withdraws only, valid update + testNum: 1, + input: []byte{0, 5, 8, 10, 16, 192, 168, 0, 0}, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + }, + }, + { + // 2 withdraws with one path attribute (ORIGIN), valid update + testNum: 2, + input: []byte{ + 0, 5, // Withdrawn Routes Length + 8, 10, // 10.0.0.0/8 + 16, 192, 168, // 192.168.0.0/16 + 0, 5, // Total Path Attribute Length + 255, // Attribute flags + 1, // Attribute Type code + 0, 1, // Length + 2, // INCOMPLETE + }, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + TotalPathAttrLen: 5, + PathAttributes: &PathAttribute{ + Optional: true, + Transitive: true, + Partial: true, + ExtendedLength: true, + Length: 1, + TypeCode: 1, + Value: uint8(2), + }, + }, + }, + { + // 2 withdraws with two path attributes (ORIGIN + ASPath), valid update + testNum: 3, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 14, // Total Path Attribute Length + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + 0, // Attribute flags + 2, // Attribute Type code (AS Path) + 6, // Length + 2, // Type = AS_SEQUENCE + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + }, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + TotalPathAttrLen: 14, + PathAttributes: &PathAttribute{ + Optional: true, + Transitive: true, + Partial: true, + ExtendedLength: true, + Length: 1, + TypeCode: 1, + Value: uint8(2), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 6, + TypeCode: 2, + Value: ASPath{ + { + Type: 2, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + }, + }, + }, + }, + }, + { + // 2 withdraws with two path attributes (ORIGIN + ASPath), invalid AS Path segment type + testNum: 4, + 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 + 2, // Attribute Type code (AS Path) + 6, // Length + 1, // Type = AS_SET + 0, // Path Segement Length + }, + wantFail: true, + }, + { + // 2 withdraws with two path attributes (ORIGIN + ASPath), invalid AS Path segment member count + testNum: 5, + 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 + 2, // Attribute Type code (AS Path) + 6, // Length + 3, // Type = INVALID + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + }, + wantFail: true, + }, + { + // 2 withdraws with two path attributes (ORIGIN + ASPath), valid update + testNum: 6, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 20, // Total Path Attribute Length + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + 0, // Attribute flags + 2, // Attribute Type code (AS Path) + 12, // Length + 2, // Type = AS_SEQUENCE + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + 1, // Type = AS_SET + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + }, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + TotalPathAttrLen: 20, + PathAttributes: &PathAttribute{ + Optional: true, + Transitive: true, + Partial: true, + ExtendedLength: true, + Length: 1, + TypeCode: 1, + Value: uint8(2), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 12, + TypeCode: 2, + Value: ASPath{ + { + Type: 2, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + { + Type: 1, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + }, + }, + }, + }, + }, + { + // 2 withdraws with 3 path attributes (ORIGIN + ASPath, NH), valid update + testNum: 7, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 27, // Total Path Attribute Length + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + 0, // Attribute flags + 2, // Attribute Type code (AS Path) + 12, // Length + 2, // Type = AS_SEQUENCE + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + 1, // Type = AS_SET + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + + 0, // Attribute flags + 3, // Attribute Type code (Next Hop) + 4, // Length + 10, 11, 12, 13, // Next Hop + + }, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + TotalPathAttrLen: 27, + PathAttributes: &PathAttribute{ + + Optional: true, + Transitive: true, + Partial: true, + ExtendedLength: true, + Length: 1, + TypeCode: 1, + Value: uint8(2), + Next: &PathAttribute{ + + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 12, + TypeCode: 2, + Value: ASPath{ + { + Type: 2, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + { + Type: 1, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + }, + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 3, + Value: [4]byte{10, 11, 12, 13}, + }, + }, + }, + }, + }, + { + // 2 withdraws with 4 path attributes (ORIGIN + ASPath, NH, MED), valid update + testNum: 8, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 34, // Total Path Attribute Length + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + 0, // Attribute flags + 2, // Attribute Type code (AS Path) + 12, // Length + 2, // Type = AS_SEQUENCE + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + 1, // Type = AS_SET + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + + 0, // Attribute flags + 3, // Attribute Type code (Next Hop) + 4, // Length + 10, 11, 12, 13, // Next Hop + + 0, // Attribute flags + 4, // Attribute Type code (Next Hop) + 4, // Length + 0, 0, 1, 0, // MED 256 + + }, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + TotalPathAttrLen: 34, + PathAttributes: &PathAttribute{ + + Optional: true, + Transitive: true, + Partial: true, + ExtendedLength: true, + Length: 1, + TypeCode: 1, + Value: uint8(2), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 12, + TypeCode: 2, + Value: ASPath{ + { + Type: 2, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + { + Type: 1, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + }, + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 3, + Value: [4]byte{10, 11, 12, 13}, + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 4, + Value: uint32(256), + }, + }, + }, + }, + }, + }, + { + // 2 withdraws with 4 path attributes (ORIGIN + ASPath, NH, MED, Local Pref), valid update + testNum: 9, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 41, // Total Path Attribute Length + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + 0, // Attribute flags + 2, // Attribute Type code (AS Path) + 12, // Length + 2, // Type = AS_SEQUENCE + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + 1, // Type = AS_SET + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + + 0, // Attribute flags + 3, // Attribute Type code (Next Hop) + 4, // Length + 10, 11, 12, 13, // Next Hop + + 0, // Attribute flags + 4, // Attribute Type code (MED) + 4, // Length + 0, 0, 1, 0, // MED 256 + + 0, // Attribute flags + 5, // Attribute Type code (Local Pref) + 4, // Length + 0, 0, 1, 0, // Local Pref 256 + + }, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + TotalPathAttrLen: 41, + PathAttributes: &PathAttribute{ + Optional: true, + Transitive: true, + Partial: true, + ExtendedLength: true, + Length: 1, + TypeCode: 1, + Value: uint8(2), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 12, + TypeCode: 2, + Value: ASPath{ + { + Type: 2, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + { + Type: 1, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + }, + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 3, + Value: [4]byte{10, 11, 12, 13}, + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 4, + Value: uint32(256), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 5, + Value: uint32(256), + }, + }, + }, + }, + }, + }, + }, + { + // 2 withdraws with 6 path attributes (ORIGIN, ASPath, NH, MED, Local Pref, Atomi Aggregate), valid update + testNum: 9, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 44, // Total Path Attribute Length + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + 0, // Attribute flags + 2, // Attribute Type code (AS Path) + 12, // Length + 2, // Type = AS_SEQUENCE + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + 1, // Type = AS_SET + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + + 0, // Attribute flags + 3, // Attribute Type code (Next Hop) + 4, // Length + 10, 11, 12, 13, // Next Hop + + 0, // Attribute flags + 4, // Attribute Type code (MED) + 4, // Length + 0, 0, 1, 0, // MED 256 + + 0, // Attribute flags + 5, // Attribute Type code (Local Pref) + 4, // Length + 0, 0, 1, 0, // Local Pref 256 + + 0, // Attribute flags + 6, // Attribute Type code (Atomic Aggregate) + 0, // Length + }, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + TotalPathAttrLen: 44, + PathAttributes: &PathAttribute{ + Optional: true, + Transitive: true, + Partial: true, + ExtendedLength: true, + Length: 1, + TypeCode: 1, + Value: uint8(2), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 12, + TypeCode: 2, + Value: ASPath{ + { + Type: 2, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + { + Type: 1, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + }, + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 3, + Value: [4]byte{10, 11, 12, 13}, + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 4, + Value: uint32(256), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 5, + Value: uint32(256), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 0, + TypeCode: 6, + }, + }, + }, + }, + }, + }, + }, + }, + { + // 2 withdraws with 7 path attributes (ORIGIN, ASPath, NH, MED, Local Pref, Atomic Aggregate), valid update + testNum: 10, + input: []byte{0, 5, 8, 10, 16, 192, 168, + 0, 53, // Total Path Attribute Length + + 255, // Attribute flags + 1, // Attribute Type code (ORIGIN) + 0, 1, // Length + 2, // INCOMPLETE + + 0, // Attribute flags + 2, // Attribute Type code (AS Path) + 12, // Length + 2, // Type = AS_SEQUENCE + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + 1, // Type = AS_SET + 2, // Path Segement Length + 59, 65, // AS15169 + 12, 248, // AS3320 + + 0, // Attribute flags + 3, // Attribute Type code (Next Hop) + 4, // Length + 10, 11, 12, 13, // Next Hop + + 0, // Attribute flags + 4, // Attribute Type code (MED) + 4, // Length + 0, 0, 1, 0, // MED 256 + + 0, // Attribute flags + 5, // Attribute Type code (Local Pref) + 4, // Length + 0, 0, 1, 0, // Local Pref 256 + + 0, // Attribute flags + 6, // Attribute Type code (Atomic Aggregate) + 0, // Length + + 0, // Attribute flags + 7, // Attribute Type code (Atomic Aggregate) + 6, // Length + 1, 2, // ASN + 10, 11, 12, 13, // Address + + 8, 11, // 11.0.0.0/8 + }, + wantFail: false, + expected: &BGPUpdate{ + WithdrawnRoutesLen: 5, + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + TotalPathAttrLen: 53, + PathAttributes: &PathAttribute{ + Optional: true, + Transitive: true, + Partial: true, + ExtendedLength: true, + Length: 1, + TypeCode: 1, + Value: uint8(2), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 12, + TypeCode: 2, + Value: ASPath{ + { + Type: 2, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + { + Type: 1, + Count: 2, + ASNs: []uint32{ + 15169, + 3320, + }, + }, + }, + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 3, + Value: [4]byte{10, 11, 12, 13}, + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 4, + Value: uint32(256), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 4, + TypeCode: 5, + Value: uint32(256), + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 0, + TypeCode: 6, + Next: &PathAttribute{ + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + Length: 6, + TypeCode: 7, + Value: Aggretator{ + ASN: uint16(258), + Addr: [4]byte{10, 11, 12, 13}, + }, + }, + }, + }, + }, + }, + }, + }, + NLRI: &NLRI{ + Pfxlen: 8, + IP: [4]byte{11, 0, 0, 0}, + }, + }, + }, + { + testNum: 11, // Incomplete Withdraw + input: []byte{ + 0, 5, // Length + }, + wantFail: true, + }, + { + testNum: 12, // Empty buffer + input: []byte{}, + wantFail: true, + }, + { + testNum: 13, + input: []byte{ + 0, 0, // No Withdraws + 0, 5, // Total Path Attributes Length + }, + wantFail: true, + }, + { + testNum: 14, + input: []byte{ + 0, 0, // No Withdraws + 0, 0, // Total Path Attributes Length + 24, // Incomplete NLRI + }, + wantFail: true, + }, + { + testNum: 15, // Cut at Total Path Attributes Length + input: []byte{ + 0, 0, // No Withdraws + }, + explicitLength: 5, + wantFail: true, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(test.input) + l := test.explicitLength + if l == 0 { + l = uint16(len(test.input)) + } + msg, err := decodeUpdateMsg(buf, l) + + if err != nil && !test.wantFail { + t.Errorf("Unexpected error in test %d: %v", test.testNum, err) + continue + } + + if err == nil && test.wantFail { + t.Errorf("Expected error did not happen in test %d", test.testNum) + continue + } + + if err != nil && test.wantFail { + continue + } + + assert.Equal(t, test.expected, msg) + } +} + +func TestDecodeMsgBody(t *testing.T) { + tests := []struct { + name string + buffer *bytes.Buffer + msgType uint8 + length uint16 + wantFail bool + expected interface{} + }{ + { + name: "Unknown msgType", + msgType: 5, + wantFail: true, + }, + } + + for _, test := range tests { + res, err := decodeMsgBody(test.buffer, test.msgType, test.length) + if test.wantFail && err == nil { + t.Errorf("Expected error dit not happen in test %q", test.name) + } + + if !test.wantFail && err != nil { + t.Errorf("Unexpected error in test %q: %v", test.name, err) + } + + assert.Equal(t, test.expected, res) + } +} + +func TestDecodeOpenMsg(t *testing.T) { + tests := []test{ + { + // Valid message + testNum: 1, + input: []byte{4, 1, 1, 0, 15, 10, 20, 30, 40, 0}, + wantFail: false, + expected: &BGPOpen{ + Version: 4, + AS: 257, + HoldTime: 15, + BGPIdentifier: 169090600, + OptParmLen: 0, + }, + }, + { + // Invalid Version + testNum: 2, + input: []byte{3, 1, 1, 0, 15, 10, 10, 10, 11, 0}, + wantFail: true, + }, + } + + genericTest(_decodeOpenMsg, tests, t) +} + +func TestDecodeHeader(t *testing.T) { + tests := []test{ + { + // Valid header + testNum: 1, + input: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19, KeepaliveMsg}, + wantFail: false, + expected: &BGPHeader{ + Length: 19, + Type: KeepaliveMsg, + }, + }, + { + // Invalid length too short + testNum: 2, + input: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 18, KeepaliveMsg}, + wantFail: true, + expected: &BGPHeader{ + Length: 18, + Type: KeepaliveMsg, + }, + }, + { + // Invalid length too long + testNum: 3, + input: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 16, 1, KeepaliveMsg}, + wantFail: true, + expected: &BGPHeader{ + Length: 18, + Type: KeepaliveMsg, + }, + }, + { + // Invalid message type 5 + testNum: 4, + input: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19, 5}, + wantFail: true, + expected: &BGPHeader{ + Length: 19, + Type: KeepaliveMsg, + }, + }, + { + // Invalid message type 0 + testNum: 5, + input: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19, 0}, + wantFail: true, + expected: &BGPHeader{ + Length: 19, + Type: KeepaliveMsg, + }, + }, + { + // Invalid marker + testNum: 6, + input: []byte{1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 19, KeepaliveMsg}, + wantFail: true, + expected: &BGPHeader{ + Length: 19, + Type: KeepaliveMsg, + }, + }, + { + // Incomplete Marker + testNum: 7, + input: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + wantFail: true, + }, + { + // Incomplete Header + testNum: 8, + input: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19}, + wantFail: true, + }, + { + // Empty input + testNum: 9, + input: []byte{}, + wantFail: true, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(test.input) + res, err := decodeHeader(buf) + + if err != nil { + if test.wantFail { + continue + } + t.Errorf("Unexpected failure for test %d: %v", test.testNum, err) + continue + } + + if test.wantFail { + t.Errorf("Unexpected success fo test %d", test.testNum) + } + + assert.Equal(t, test.expected, res) + } +} + +func genericTest(f decodeFunc, tests []test, t *testing.T) { + for _, test := range tests { + buf := bytes.NewBuffer(test.input) + msg, err := f(buf) + + if err != nil && !test.wantFail { + t.Errorf("Unexpected error in test %d: %v", test.testNum, err) + continue + } + + if err == nil && test.wantFail { + t.Errorf("Expected error did not happen in test %d", test.testNum) + continue + } + + if err != nil && test.wantFail { + continue + } + + if msg == nil { + t.Errorf("Unexpected nil result in test %d. Expected: %v", test.testNum, test.expected) + continue + } + + assert.Equal(t, test.expected, msg) + } +} + +func TestIsValidIdentifier(t *testing.T) { + tests := []struct { + name string + input uint32 + expected bool + }{ + { + name: "Valid #1", + input: convert.Uint32b([]byte{8, 8, 8, 8}), + expected: true, + }, + { + name: "Multicast", + input: convert.Uint32b([]byte{239, 8, 8, 8}), + expected: false, + }, + { + name: "Loopback", + input: convert.Uint32b([]byte{127, 8, 8, 8}), + expected: false, + }, + { + name: "First byte 0", + input: convert.Uint32b([]byte{0, 8, 8, 8}), + expected: false, + }, + { + name: "All bytes 255", + input: convert.Uint32b([]byte{255, 255, 255, 255}), + expected: false, + }, + } + + for _, test := range tests { + res := isValidIdentifier(test.input) + assert.Equal(t, test.expected, res) + } +} + +func TestValidateOpenMessage(t *testing.T) { + tests := []struct { + name string + input *BGPOpen + wantFail bool + }{ + { + name: "Valid #1", + input: &BGPOpen{ + Version: 4, + BGPIdentifier: convert.Uint32b([]byte{8, 8, 8, 8}), + }, + wantFail: false, + }, + { + name: "Invalid Identifier", + input: &BGPOpen{ + Version: 4, + BGPIdentifier: convert.Uint32b([]byte{0, 8, 8, 8}), + }, + wantFail: true, + }, + } + + for _, test := range tests { + res := validateOpen(test.input) + + if res != nil { + if test.wantFail { + continue + } + t.Errorf("Unexpected failure for test %q: %v", test.name, res) + continue + } + + if test.wantFail { + t.Errorf("Unexpected success for test %q", test.name) + } + } +} diff --git a/protocols/bgp/packet/encoder.go b/protocols/bgp/packet/encoder.go new file mode 100644 index 00000000..e7c98c6f --- /dev/null +++ b/protocols/bgp/packet/encoder.go @@ -0,0 +1,45 @@ +package packet + +import ( + "bytes" + + "github.com/taktv6/tflow2/convert" +) + +func SerializeKeepaliveMsg() []byte { + keepaliveLen := uint16(19) + buf := bytes.NewBuffer(make([]byte, 0, keepaliveLen)) + serializeHeader(buf, keepaliveLen, KeepaliveMsg) + + return buf.Bytes() +} + +func SerializeNotificationMsg(msg *BGPNotification) []byte { + notificationLen := uint16(21) + buf := bytes.NewBuffer(make([]byte, 0, notificationLen)) + serializeHeader(buf, notificationLen, NotificationMsg) + buf.WriteByte(msg.ErrorCode) + buf.WriteByte(msg.ErrorSubcode) + + return buf.Bytes() +} + +func SerializeOpenMsg(msg *BGPOpen) []byte { + openLen := uint16(29) + buf := bytes.NewBuffer(make([]byte, 0, openLen)) + serializeHeader(buf, openLen, OpenMsg) + + buf.WriteByte(msg.Version) + buf.Write(convert.Uint16Byte(msg.AS)) + buf.Write(convert.Uint16Byte(msg.HoldTime)) + buf.Write(convert.Uint32Byte(msg.BGPIdentifier)) + buf.WriteByte(uint8(0)) + + return buf.Bytes() +} + +func serializeHeader(buf *bytes.Buffer, length uint16, typ uint8) { + buf.Write([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) + buf.Write(convert.Uint16Byte(length)) + buf.WriteByte(typ) +} diff --git a/protocols/bgp/packet/encoder_test.go b/protocols/bgp/packet/encoder_test.go new file mode 100644 index 00000000..eccfdfdc --- /dev/null +++ b/protocols/bgp/packet/encoder_test.go @@ -0,0 +1,130 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/taktv6/tflow2/convert" +) + +func TestSerializeKeepaliveMsg(t *testing.T) { + expected := []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x13, 0x04, + } + res := SerializeKeepaliveMsg() + + assert.Equal(t, expected, res) +} + +func TestSerializeNotificationMsg(t *testing.T) { + tests := []struct { + name string + input *BGPNotification + expected []byte + }{ + { + name: "Valid #1", + input: &BGPNotification{ + ErrorCode: 10, + ErrorSubcode: 5, + }, + expected: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x15, // Length + 0x03, // Type + 0x0a, // Error Code + 0x05, // Error Subcode + }, + }, + { + name: "Valid #2", + input: &BGPNotification{ + ErrorCode: 11, + ErrorSubcode: 6, + }, + expected: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x15, // Length + 0x03, // Type + 0x0b, // Error Code + 0x06, // Error Subcode + }, + }, + } + + for _, test := range tests { + res := SerializeNotificationMsg(test.input) + assert.Equal(t, test.expected, res) + } +} + +func TestSerializeOpenMsg(t *testing.T) { + tests := []struct { + name string + input *BGPOpen + expected []byte + }{ + { + name: "Valid #1", + input: &BGPOpen{ + Version: 4, + AS: 15169, + HoldTime: 120, + BGPIdentifier: convert.Uint32([]byte{100, 111, 120, 130}), + OptParmLen: 0, + }, + expected: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x1d, // Length + 0x01, // Type + 0x04, // Version + 0x3b, 0x41, // ASN + 0x00, 0x78, // Holdtime + 130, 120, 111, 100, // BGP Identifier + 0x00, // Opt. Param Length + }, + }, + } + + for _, test := range tests { + res := SerializeOpenMsg(test.input) + assert.Equal(t, test.expected, res) + } +} + +func TestSerializeHeader(t *testing.T) { + tests := []struct { + name string + length uint16 + typ uint8 + expected []byte + }{ + { + name: "Valid #1", + length: 10, + typ: 5, + expected: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0a, 0x05, + }, + }, + { + name: "Valid #12", + length: 256, + typ: 255, + expected: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x00, 0xff, + }, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer([]byte{}) + serializeHeader(buf, test.length, test.typ) + + assert.Equal(t, test.expected, buf.Bytes()) + } +} diff --git a/protocols/bgp/packet/nlri.go b/protocols/bgp/packet/nlri.go new file mode 100644 index 00000000..1f478fc1 --- /dev/null +++ b/protocols/bgp/packet/nlri.go @@ -0,0 +1,60 @@ +package packet + +import ( + "bytes" + "fmt" + "math" + "net" +) + +func decodeNLRIs(buf *bytes.Buffer, length uint16) (*NLRI, error) { + var ret *NLRI + var eol *NLRI + var nlri *NLRI + var err error + var consumed uint8 + p := uint16(0) + + for p < length { + nlri, consumed, err = decodeNLRI(buf) + if err != nil { + return nil, fmt.Errorf("Unable to decode NLRI: %v", err) + } + p += uint16(consumed) + + if ret == nil { + ret = nlri + eol = nlri + continue + } + + eol.Next = nlri + eol = nlri + } + + return ret, nil +} + +func decodeNLRI(buf *bytes.Buffer) (*NLRI, uint8, error) { + var addr [4]byte + nlri := &NLRI{} + + err := decode(buf, []interface{}{&nlri.Pfxlen}) + if err != nil { + return nil, 0, err + } + + toCopy := uint8(math.Ceil(float64(nlri.Pfxlen) / float64(OctetLen))) + for i := uint8(0); i < net.IPv4len%OctetLen; i++ { + if i < toCopy { + err := decode(buf, []interface{}{&addr[i]}) + if err != nil { + return nil, 0, err + } + } else { + addr[i] = 0 + } + } + nlri.IP = addr + return nlri, toCopy + 1, nil +} diff --git a/protocols/bgp/packet/nlri_test.go b/protocols/bgp/packet/nlri_test.go new file mode 100644 index 00000000..e7251e41 --- /dev/null +++ b/protocols/bgp/packet/nlri_test.go @@ -0,0 +1,129 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecodeNLRIs(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + expected *NLRI + }{ + { + name: "Valid NRLI #1", + input: []byte{ + 24, 192, 168, 0, + 8, 10, + 17, 172, 16, 0, + }, + wantFail: false, + expected: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 24, + Next: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{172, 16, 0, 0}, + Pfxlen: 17, + }, + }, + }, + }, + { + name: "Invalid NRLI #1", + input: []byte{ + 24, 192, 168, 0, + 8, 10, + 17, 172, 16, + }, + wantFail: true, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(test.input) + res, err := decodeNLRIs(buf, uint16(len(test.input))) + + if test.wantFail && err == nil { + t.Errorf("Expected error did not happen for test %q", test.name) + } + + if !test.wantFail && err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + } + + assert.Equal(t, test.expected, res) + } +} + +func TestDecodeNLRI(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + expected *NLRI + }{ + { + name: "Valid NRLI #1", + input: []byte{ + 24, 192, 168, 0, + }, + wantFail: false, + expected: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 24, + }, + }, + { + name: "Valid NRLI #2", + input: []byte{ + 25, 192, 168, 0, 128, + }, + wantFail: false, + expected: &NLRI{ + IP: [4]byte{192, 168, 0, 128}, + Pfxlen: 25, + }, + }, + { + name: "Incomplete NLRI #1", + input: []byte{ + 25, 192, 168, 0, + }, + wantFail: true, + }, + { + name: "Incomplete NLRI #2", + input: []byte{ + 25, + }, + wantFail: true, + }, + { + name: "Empty input", + input: []byte{}, + wantFail: true, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(test.input) + res, _, err := decodeNLRI(buf) + + if test.wantFail && err == nil { + t.Errorf("Expected error did not happen for test %q", test.name) + } + + if !test.wantFail && err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + } + + assert.Equal(t, test.expected, res) + } +} diff --git a/protocols/bgp/packet/path_attribute_flags.go b/protocols/bgp/packet/path_attribute_flags.go new file mode 100644 index 00000000..8beca7ac --- /dev/null +++ b/protocols/bgp/packet/path_attribute_flags.go @@ -0,0 +1,46 @@ +package packet + +import "bytes" + +func decodePathAttrFlags(buf *bytes.Buffer, pa *PathAttribute) error { + flags := uint8(0) + err := decode(buf, []interface{}{&flags}) + if err != nil { + return err + } + + pa.Optional = isOptional(flags) + pa.Transitive = isTransitive(flags) + pa.Partial = isPartial(flags) + pa.ExtendedLength = isExtendedLength(flags) + + return nil +} + +func isOptional(x uint8) bool { + if x&128 == 128 { + return true + } + return false +} + +func isTransitive(x uint8) bool { + if x&64 == 64 { + return true + } + return false +} + +func isPartial(x uint8) bool { + if x&32 == 32 { + return true + } + return false +} + +func isExtendedLength(x uint8) bool { + if x&16 == 16 { + return true + } + return false +} diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go new file mode 100644 index 00000000..b00f1599 --- /dev/null +++ b/protocols/bgp/packet/path_attributes.go @@ -0,0 +1,289 @@ +package packet + +import ( + "bytes" + "fmt" +) + +func decodePathAttrs(buf *bytes.Buffer, tpal uint16) (*PathAttribute, error) { + var ret *PathAttribute + var eol *PathAttribute + var pa *PathAttribute + var err error + var consumed uint16 + + p := uint16(0) + for p < tpal { + pa, consumed, err = decodePathAttr(buf) + if err != nil { + return nil, fmt.Errorf("Unable to decode path attr: %v", err) + } + p += consumed + + if ret == nil { + ret = pa + eol = pa + } else { + eol.Next = pa + eol = pa + } + } + + return ret, nil +} + +func decodePathAttr(buf *bytes.Buffer) (pa *PathAttribute, consumed uint16, err error) { + pa = &PathAttribute{} + + err = decodePathAttrFlags(buf, pa) + if err != nil { + return nil, consumed, fmt.Errorf("Unable to get path attribute flags: %v", err) + } + consumed++ + + err = decode(buf, []interface{}{&pa.TypeCode}) + if err != nil { + return nil, consumed, err + } + consumed++ + + n, err := pa.setLength(buf) + if err != nil { + return nil, consumed, err + } + consumed += uint16(n) + + switch pa.TypeCode { + case OriginAttr: + if err := pa.decodeOrigin(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode Origin: %v", err) + } + case ASPathAttr: + if err := pa.decodeASPath(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode AS Path: %v", err) + } + case NextHopAttr: + if err := pa.decodeNextHop(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode Next-Hop: %v", err) + } + case MEDAttr: + if err := pa.decodeMED(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode MED: %v", err) + } + case LocalPrefAttr: + if err := pa.decodeLocalPref(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode local pref: %v", err) + } + case AggregatorAttr: + if err := pa.decodeAggregator(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to decode Aggregator: %v", err) + } + case AtomicAggrAttr: + // Nothing to do for 0 octet long attribute + default: + return nil, consumed, fmt.Errorf("Invalid Attribute Type Code: %v", pa.TypeCode) + } + + return pa, consumed + pa.Length, nil +} + +func (pa *PathAttribute) decodeOrigin(buf *bytes.Buffer) error { + origin := uint8(0) + + p := uint16(0) + err := decode(buf, []interface{}{&origin}) + if err != nil { + return fmt.Errorf("Unable to decode: %v", err) + } + + pa.Value = origin + p++ + + return dumpNBytes(buf, pa.Length-p) +} + +func (pa *PathAttribute) decodeASPath(buf *bytes.Buffer) error { + pa.Value = make(ASPath, 0) + + p := uint16(0) + for p < pa.Length { + segment := ASPathSegment{ + ASNs: make([]uint32, 0), + } + + err := decode(buf, []interface{}{&segment.Type, &segment.Count}) + if err != nil { + return err + } + p += 2 + + if segment.Type != ASSet && segment.Type != ASSequence { + return fmt.Errorf("Invalid AS Path segment type: %d", segment.Type) + } + + if segment.Count == 0 { + return fmt.Errorf("Invalid AS Path segment length: %d", segment.Count) + } + + for i := uint8(0); i < segment.Count; i++ { + asn := uint16(0) + + err := decode(buf, []interface{}{&asn}) + if err != nil { + return err + } + p += 2 + + segment.ASNs = append(segment.ASNs, uint32(asn)) + } + pa.Value = append(pa.Value.(ASPath), segment) + } + + return nil +} + +func (pa *PathAttribute) decodeNextHop(buf *bytes.Buffer) error { + addr := [4]byte{} + + p := uint16(0) + n, err := buf.Read(addr[:]) + if err != nil { + return err + } + if n != 4 { + return fmt.Errorf("Unable to read next hop: buf.Read read %d bytes", n) + } + + pa.Value = addr + p += 4 + + return dumpNBytes(buf, pa.Length-p) +} + +func (pa *PathAttribute) decodeMED(buf *bytes.Buffer) error { + med, err := pa.decodeUint32(buf) + if err != nil { + return fmt.Errorf("Unable to recode local pref: %v", err) + } + + pa.Value = uint32(med) + return nil +} + +func (pa *PathAttribute) decodeLocalPref(buf *bytes.Buffer) error { + lpref, err := pa.decodeUint32(buf) + if err != nil { + return fmt.Errorf("Unable to recode local pref: %v", err) + } + + pa.Value = uint32(lpref) + return nil +} + +func (pa *PathAttribute) decodeAggregator(buf *bytes.Buffer) error { + aggr := Aggretator{} + + p := uint16(0) + err := decode(buf, []interface{}{&aggr.ASN}) + if err != nil { + return err + } + p += 2 + + n, err := buf.Read(aggr.Addr[:]) + if err != nil { + return err + } + if n != 4 { + return fmt.Errorf("Unable to read aggregator IP: buf.Read read %d bytes", n) + } + p += 4 + + pa.Value = aggr + return dumpNBytes(buf, pa.Length-p) +} + +func (pa *PathAttribute) setLength(buf *bytes.Buffer) (int, error) { + bytesRead := 0 + if pa.ExtendedLength { + err := decode(buf, []interface{}{&pa.Length}) + if err != nil { + return 0, err + } + bytesRead = 2 + } else { + x := uint8(0) + err := decode(buf, []interface{}{&x}) + if err != nil { + return 0, err + } + pa.Length = uint16(x) + bytesRead = 1 + } + return bytesRead, nil +} + +func (pa *PathAttribute) decodeUint32(buf *bytes.Buffer) (uint32, error) { + var v uint32 + + p := uint16(0) + err := decode(buf, []interface{}{&v}) + if err != nil { + return 0, err + } + + p += 4 + err = dumpNBytes(buf, pa.Length-p) + if err != nil { + return 0, fmt.Errorf("dumpNBytes failed: %v", err) + } + + return v, nil +} + +func (pa *PathAttribute) ASPathString() (ret string) { + for _, p := range *pa.Value.(*ASPath) { + if p.Type == ASSet { + ret += " (" + } + n := len(p.ASNs) + for i, asn := range p.ASNs { + if i < n-1 { + ret += fmt.Sprintf("%d ", asn) + continue + } + ret += fmt.Sprintf("%d", asn) + } + if p.Type == ASSet { + ret += ")" + } + } + + return +} + +func (pa *PathAttribute) ASPathLen() (ret uint16) { + for _, p := range *pa.Value.(*ASPath) { + if p.Type == ASSet { + ret++ + continue + } + ret += uint16(len(p.ASNs)) + } + + return +} + +// dumpNBytes is used to dump n bytes of buf. This is useful in case an path attributes +// length doesn't match a fixed length's attributes length (e.g. ORIGIN is always an octet) +func dumpNBytes(buf *bytes.Buffer, n uint16) error { + if n <= 0 { + return nil + } + dump := make([]byte, n) + err := decode(buf, []interface{}{&dump}) + if err != nil { + return err + } + return nil +} diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go new file mode 100644 index 00000000..ee4d0fc3 --- /dev/null +++ b/protocols/bgp/packet/path_attributes_test.go @@ -0,0 +1,759 @@ +package packet + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecodePathAttrs(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + expected *PathAttribute + }{ + { + name: "Valid attribute set", + input: []byte{ + 0, // Attr. Flags + 1, // Attr. Type Code + 1, // Attr. Length + 1, // EGP + 0, // Attr. Flags + 3, // Next Hop + 4, // Attr. Length + 10, 20, 30, 40, // IP-Address + }, + wantFail: false, + expected: &PathAttribute{ + TypeCode: 1, + Length: 1, + Value: uint8(1), + Next: &PathAttribute{ + TypeCode: 3, + Length: 4, + Value: [4]byte{10, 20, 30, 40}, + }, + }, + }, + { + name: "Incomplete data", + input: []byte{ + 0, // Attr. Flags + 1, // Attr. Type Code + 1, // Attr. Length + }, + wantFail: true, + }, + } + + for _, test := range tests { + res, err := decodePathAttrs(bytes.NewBuffer(test.input), uint16(len(test.input))) + + if test.wantFail && err == nil { + t.Errorf("Expected error did not happen for test %q", test.name) + continue + } + + if !test.wantFail && err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue + } + + assert.Equal(t, test.expected, res) + } +} + +func TestDecodePathAttr(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + expected *PathAttribute + }{ + { + name: "Valid origin", + input: []byte{ + 0, // Attr. Flags + 1, // Attr. Type Code + 1, // Attr. Length + 1, // EGP + }, + wantFail: false, + expected: &PathAttribute{ + Length: 1, + Optional: false, + Transitive: false, + Partial: false, + ExtendedLength: false, + TypeCode: OriginAttr, + Value: uint8(1), + }, + }, + { + name: "Missing TypeCode", + input: []byte{ + 0, // Attr. Flags + }, + wantFail: true, + }, + { + name: "Missing Length", + input: []byte{ + 0, // Attr. Flags + 1, // Attr. Type Code + }, + wantFail: true, + }, + { + name: "Missing Value ORIGIN", + input: []byte{ + 0, // Attr. Flags + 1, // Attr. Type Code + 1, // Attr. Length + }, + wantFail: true, + }, + { + name: "Missing value AS_PATH", + input: []byte{ + 0, // Attr. Flags + 2, // Attr. Type Code + 8, // Attr. Length + }, + wantFail: true, + }, + { + name: "Missing value NextHop", + input: []byte{ + 0, // Attr. Flags + 3, // Attr. Type Code + 4, // Attr. Length + }, + wantFail: true, + }, + { + name: "Missing value MED", + input: []byte{ + 0, // Attr. Flags + 4, // Attr. Type Code + 4, // Attr. Length + }, + wantFail: true, + }, + { + name: "Missing value LocalPref", + input: []byte{ + 0, // Attr. Flags + 5, // Attr. Type Code + 4, // Attr. Length + }, + wantFail: true, + }, + { + name: "Missing value AGGREGATOR", + input: []byte{ + 0, // Attr. Flags + 7, // Attr. Type Code + 4, // Attr. Length + }, + wantFail: true, + }, + { + name: "Not supported attribute", + input: []byte{ + 0, // Attr. Flags + 111, // Attr. Type Code + 4, // Attr. Length + }, + wantFail: true, + }, + } + + for _, test := range tests { + res, _, err := decodePathAttr(bytes.NewBuffer(test.input)) + + if test.wantFail && err == nil { + t.Errorf("Expected error did not happen for test %q", test.name) + continue + } + + if !test.wantFail && err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue + } + + assert.Equal(t, test.expected, res) + } +} + +func TestDecodeOrigin(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + expected *PathAttribute + }{ + { + name: "Test #1", + input: []byte{ + 0, // Origin: IGP + }, + wantFail: false, + expected: &PathAttribute{ + Value: uint8(IGP), + Length: 1, + }, + }, + { + name: "Test #2", + input: []byte{ + 1, // Origin: EGP + }, + wantFail: false, + expected: &PathAttribute{ + Value: uint8(EGP), + Length: 1, + }, + }, + { + name: "Test #3", + input: []byte{ + 2, // Origin: INCOMPLETE + }, + wantFail: false, + expected: &PathAttribute{ + Value: uint8(INCOMPLETE), + Length: 1, + }, + }, + { + name: "Test #4", + input: []byte{}, + wantFail: true, + }, + } + + for _, test := range tests { + pa := &PathAttribute{ + Length: uint16(len(test.input)), + } + err := pa.decodeOrigin(bytes.NewBuffer(test.input)) + + if test.wantFail && err == nil { + t.Errorf("Expected error did not happen for test %q", test.name) + } + + if !test.wantFail && err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + } + + if err != nil { + continue + } + + assert.Equal(t, test.expected, pa) + } +} + +func TestDecodeASPath(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "Test #1", + input: []byte{ + 2, // AS_SEQUENCE + 4, // Path Length + 0, 100, 0, 200, 0, 222, 0, 240, + }, + wantFail: false, + expected: &PathAttribute{ + Length: 10, + Value: ASPath{ + ASPathSegment{ + Type: 2, + Count: 4, + ASNs: []uint32{ + 100, 200, 222, 240, + }, + }, + }, + }, + }, + { + name: "Test #2", + input: []byte{ + 1, // AS_SEQUENCE + 3, // Path Length + 0, 100, 0, 222, 0, 240, + }, + wantFail: false, + expected: &PathAttribute{ + Length: 8, + Value: ASPath{ + ASPathSegment{ + Type: 1, + Count: 3, + ASNs: []uint32{ + 100, 222, 240, + }, + }, + }, + }, + }, + { + name: "Empty input", + input: []byte{}, + explicitLength: 5, + wantFail: true, + }, + { + name: "Incomplete AS_PATH", + input: []byte{ + 1, // AS_SEQUENCE + 3, // Path Length + 0, 100, 0, 222, + }, + wantFail: true, + }, + } + + for _, test := range tests { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeASPath(bytes.NewBuffer(test.input)) + + if test.wantFail && err == nil { + t.Errorf("Expected error did not happen for test %q", test.name) + } + + if !test.wantFail && err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + } + + if err != nil { + continue + } + + assert.Equal(t, test.expected, pa) + } +} + +func TestDecodeNextHop(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "Test #1", + input: []byte{ + 10, 20, 30, 40, + }, + wantFail: false, + expected: &PathAttribute{ + Length: 4, + Value: [4]byte{ + 10, 20, 30, 40, + }, + }, + }, + { + name: "Test #2", + input: []byte{}, + explicitLength: 5, + wantFail: true, + }, + { + name: "Incomplete IP-Address", + input: []byte{10, 20, 30}, + explicitLength: 5, + wantFail: true, + }, + } + + for _, test := range tests { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeNextHop(bytes.NewBuffer(test.input)) + + if test.wantFail && err == nil { + t.Errorf("Expected error did not happen for test %q", test.name) + } + + if !test.wantFail && err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + } + + if err != nil { + continue + } + + assert.Equal(t, test.expected, pa) + } +} + +func TestDecodeMED(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "Test #1", + input: []byte{ + 0, 0, 3, 232, + }, + wantFail: false, + expected: &PathAttribute{ + Length: 4, + Value: uint32(1000), + }, + }, + { + name: "Test #2", + input: []byte{}, + explicitLength: 5, + wantFail: true, + }, + } + + for _, test := range tests { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeMED(bytes.NewBuffer(test.input)) + + if test.wantFail && err == nil { + t.Errorf("Expected error did not happen for test %q", test.name) + } + + if !test.wantFail && err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + } + + if err != nil { + continue + } + + assert.Equal(t, test.expected, pa) + } +} + +func TestDecodeLocalPref(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "Test #1", + input: []byte{ + 0, 0, 3, 232, + }, + wantFail: false, + expected: &PathAttribute{ + Length: 4, + Value: uint32(1000), + }, + }, + { + name: "Test #2", + input: []byte{}, + explicitLength: 5, + wantFail: true, + }, + } + + for _, test := range tests { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeLocalPref(bytes.NewBuffer(test.input)) + + if test.wantFail { + if err != nil { + continue + } + t.Errorf("Expected error did not happen for test %q", test.name) + continue + } + + if err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue + } + + assert.Equal(t, test.expected, pa) + } +} + +func TestDecodeAggregator(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "Valid aggregator", + input: []byte{ + 0, 222, // ASN + 10, 20, 30, 40, // Aggregator IP + }, + wantFail: false, + expected: &PathAttribute{ + Length: 6, + Value: Aggretator{ + ASN: 222, + Addr: [4]byte{10, 20, 30, 40}, + }, + }, + }, + { + name: "Incomplete Address", + input: []byte{ + 0, 222, // ASN + 10, 20, // Aggregator IP + }, + wantFail: true, + }, + { + name: "Missing Address", + input: []byte{ + 0, 222, // ASN + }, + wantFail: true, + }, + { + name: "Empty input", + input: []byte{}, + wantFail: true, + }, + } + + for _, test := range tests { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeAggregator(bytes.NewBuffer(test.input)) + + if test.wantFail { + if err != nil { + continue + } + t.Errorf("Expected error did not happen for test %q", test.name) + continue + } + + if err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue + } + + assert.Equal(t, test.expected, pa) + } +} + +func TestSetLength(t *testing.T) { + tests := []struct { + name string + input []byte + ExtendedLength bool + wantFail bool + expected *PathAttribute + expectedConsumed int + }{ + { + name: "Valid input", + ExtendedLength: false, + input: []byte{22}, + expected: &PathAttribute{ + ExtendedLength: false, + Length: 22, + }, + expectedConsumed: 1, + }, + { + name: "Valid input (extended)", + ExtendedLength: true, + input: []byte{1, 1}, + expected: &PathAttribute{ + ExtendedLength: true, + Length: 257, + }, + expectedConsumed: 2, + }, + { + name: "Invalid input", + ExtendedLength: true, + input: []byte{}, + wantFail: true, + }, + { + name: "Invalid input (extended)", + ExtendedLength: true, + input: []byte{1}, + wantFail: true, + }, + } + + for _, test := range tests { + pa := &PathAttribute{ + ExtendedLength: test.ExtendedLength, + } + consumed, err := pa.setLength(bytes.NewBuffer(test.input)) + + if test.wantFail { + if err != nil { + continue + } + t.Errorf("Expected error did not happen for test %q", test.name) + continue + } + + if err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue + } + + assert.Equal(t, test.expected, pa) + assert.Equal(t, test.expectedConsumed, consumed) + } +} + +func TestDecodeUint32(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected uint32 + }{ + { + name: "Valid input", + input: []byte{0, 0, 1, 1}, + expected: 257, + }, + { + name: "Valid input with additional crap", + input: []byte{0, 0, 1, 1, 200}, + expected: 257, + }, + { + name: "Valid input with additional crap and invalid length", + input: []byte{0, 0, 1, 1, 200}, + explicitLength: 8, + wantFail: true, + }, + { + name: "Invalid input", + input: []byte{0, 0, 1}, + wantFail: true, + }, + } + + for _, test := range tests { + l := uint16(len(test.input)) + if test.explicitLength > 0 { + l = test.explicitLength + } + pa := &PathAttribute{ + Length: l, + } + res, err := pa.decodeUint32(bytes.NewBuffer(test.input)) + + if test.wantFail { + if err != nil { + continue + } + t.Errorf("Expected error did not happen for test %q", test.name) + continue + } + + if err != nil { + t.Errorf("Unexpected failure for test %q: %v", test.name, err) + continue + } + + assert.Equal(t, test.expected, res) + } +} + +func TestASPathString(t *testing.T) { + tests := []struct { + name string + pa *PathAttribute + expected string + }{ + { + name: "Test #1", + pa: &PathAttribute{ + Value: &ASPath{ + { + Type: ASSequence, + ASNs: []uint32{10, 20, 30}, + }, + }, + }, + expected: "10 20 30", + }, + { + name: "Test #2", + pa: &PathAttribute{ + Value: &ASPath{ + { + Type: ASSequence, + ASNs: []uint32{10, 20, 30}, + }, + { + Type: ASSet, + ASNs: []uint32{200, 300}, + }, + }, + }, + expected: "10 20 30 (200 300)", + }, + } + + for _, test := range tests { + res := test.pa.ASPathString() + assert.Equal(t, test.expected, res) + } +} diff --git a/protocols/bgp/server/fsm.go b/protocols/bgp/server/fsm.go new file mode 100644 index 00000000..6b49d604 --- /dev/null +++ b/protocols/bgp/server/fsm.go @@ -0,0 +1,865 @@ +package server + +import ( + "bytes" + "fmt" + "math" + "net" + "time" + + "github.com/taktv6/tbgp/rt" + + log "github.com/sirupsen/logrus" + "github.com/taktv6/tbgp/config" + tnet "github.com/taktv6/tbgp/net" + "github.com/taktv6/tbgp/packet" + "github.com/taktv6/tflow2/convert" + tomb "gopkg.in/tomb.v2" +) + +const ( + // Administrative events + ManualStart = 1 + ManualStop = 2 + AutomaticStart = 3 + ManualStartWithPassiveTcpEstablishment = 4 + AutomaticStartWithPassiveTcpEstablishment = 5 + AutomaticStop = 8 + + // Timer events + ConnectRetryTimerExpires = 9 + HoldTimerExpires = 10 + KeepaliveTimerExpires = 11 +) + +const ( + Cease = 0 + Idle = 1 + Connect = 2 + Active = 3 + OpenSent = 4 + OpenConfirm = 5 + Established = 6 +) + +type FSM struct { + t tomb.Tomb + stateReason string + state int + lastState int + eventCh chan int + + con *net.TCPConn + con2 *net.TCPConn + conCh chan *net.TCPConn + conErrCh chan error + initiateCon chan struct{} + passive bool + + local net.IP + remote net.IP + + localASN uint16 + remoteASN uint16 + + neighborID uint32 + routerID uint32 + + delayOpen bool + delayOpenTime time.Duration + delayOpenTimer *time.Timer + + connectRetryTime time.Duration + connectRetryTimer *time.Timer + connectRetryCounter int + + holdTimeConfigured time.Duration + holdTime time.Duration + holdTimer *time.Timer + + keepaliveTime time.Duration + keepaliveTimer *time.Timer + + msgRecvCh chan msgRecvMsg + msgRecvFailCh chan msgRecvErr + stopMsgRecvCh chan struct{} + + adjRibIn *rt.LPM + adjRibOut *rt.LPM +} + +type msgRecvMsg struct { + msg []byte + con *net.TCPConn +} + +type msgRecvErr struct { + err error + con *net.TCPConn +} + +func NewFSM(c config.Peer) *FSM { + fsm := &FSM{ + state: Idle, + passive: true, + connectRetryTime: 5, + connectRetryTimer: time.NewTimer(time.Second * time.Duration(20)), + + msgRecvCh: make(chan msgRecvMsg), + msgRecvFailCh: make(chan msgRecvErr), + stopMsgRecvCh: make(chan struct{}), + + holdTimeConfigured: time.Duration(c.HoldTimer), + holdTimer: time.NewTimer(0), + + keepaliveTime: time.Duration(c.KeepAlive), + keepaliveTimer: time.NewTimer(0), + + routerID: c.RouterID, + remote: c.PeerAddress, + local: c.LocalAddress, + localASN: uint16(c.LocalAS), + eventCh: make(chan int), + conCh: make(chan *net.TCPConn), + conErrCh: make(chan error), initiateCon: make(chan struct{}), + } + return fsm +} + +func (fsm *FSM) disconnect() { + if fsm.con != nil { + fsm.con.Close() + fsm.con = nil + } + if fsm.con2 != nil { + fsm.con2.Close() + fsm.con2 = nil + } +} + +func (fsm *FSM) changeState(new int, reason string) int { + state := map[int]string{ + Cease: "Cease", + Idle: "Idle", + Connect: "Connect", + Active: "Active", + OpenSent: "OpenSent", + OpenConfirm: "OpenConfirm", + Established: "Established", + } + + log.WithFields(log.Fields{ + "peer": fsm.remote.String(), + "last_state": state[fsm.state], + "new_state": state[new], + "reason": reason, + }).Info("FSM: Neighbor state change") + + fsm.lastState = fsm.state + fsm.state = new + fsm.stateReason = reason + + return fsm.state +} + +func (fsm *FSM) activate() { + fsm.eventCh <- ManualStart +} + +func (fsm *FSM) Stop() error { + fsm.eventCh <- ManualStop + fsm.t.Kill(nil) + return fsm.t.Wait() +} + +func (fsm *FSM) start() { + fsm.t.Go(fsm.main) + fsm.t.Go(fsm.tcpConnector) + return +} + +func (fsm *FSM) main() error { + next := fsm.idle() + for { + switch next { + case Cease: + fsm.t.Kill(fmt.Errorf("FSM is being stopped")) + return nil + case Idle: + next = fsm.idle() + case Connect: + next = fsm.connect() + case Active: + next = fsm.active() + case OpenSent: + next = fsm.openSent() + case OpenConfirm: + next = fsm.openConfirm() + case Established: + next = fsm.established() + } + } +} + +func (fsm *FSM) idle() int { + fsm.adjRibIn = nil + fsm.adjRibOut = nil + for { + select { + case c := <-fsm.conCh: + c.Close() + continue + case e := <-fsm.eventCh: + reason := "" + switch e { + case ManualStart: + reason = "Received ManualStart event %d for %s peer" + case AutomaticStart: + reason = "Received AutomaticStart event %d for %s peer" + default: + continue + } + + fsm.connectRetryCounter = 0 + fsm.startConnectRetryTimer() + if fsm.passive { + return fsm.changeState(Active, fmt.Sprintf(reason, "passive")) + } + fsm.tcpConnect() + return fsm.changeState(Connect, fmt.Sprintf(reason, "active")) + } + + } +} + +func (fsm *FSM) tcpConnector() error { + for { + select { + case <-fsm.initiateCon: + c, err := net.DialTCP("tcp", &net.TCPAddr{IP: fsm.local}, &net.TCPAddr{IP: fsm.remote, Port: BGPPORT}) + if err != nil { + select { + case fsm.conErrCh <- err: + continue + case <-time.NewTimer(time.Second * 30).C: + continue + } + } + + select { + case fsm.conCh <- c: + continue + case <-time.NewTimer(time.Second * 30).C: + c.Close() + continue + } + case <-fsm.t.Dying(): + return nil + } + } +} + +func (fsm *FSM) tcpConnect() { + fsm.initiateCon <- struct{}{} +} + +// connect state waits for a TCP connection to be fully established. Either the active or passive one. +func (fsm *FSM) connect() int { + for { + select { + case e := <-fsm.eventCh: + if e == ManualStop { + fsm.connectRetryCounter = 0 + stopTimer(fsm.connectRetryTimer) + return fsm.changeState(Idle, "Manual stop event") + } + continue + case <-fsm.connectRetryTimer.C: + fsm.resetConnectRetryTimer() + fsm.tcpConnect() + continue + case c := <-fsm.conCh: + fsm.con = c + stopTimer(fsm.connectRetryTimer) + return fsm.connectSendOpen() + } + } +} + +func (fsm *FSM) connectSendOpen() int { + err := fsm.sendOpen(fsm.con) + if err != nil { + stopTimer(fsm.connectRetryTimer) + return fsm.changeState(Idle, fmt.Sprintf("Sending OPEN message failed: %v", err)) + } + fsm.holdTimer = time.NewTimer(time.Minute * 4) + return fsm.changeState(OpenSent, "Sent OPEN message") +} + +// in the active state we wait for a passive TCP connection to be established +func (fsm *FSM) active() int { + for { + select { + case e := <-fsm.eventCh: + if e == ManualStop { + fsm.disconnect() + fsm.connectRetryCounter = 0 + stopTimer(fsm.connectRetryTimer) + return fsm.changeState(Active, "Manual stop event") + } + continue + case <-fsm.connectRetryTimer.C: + fsm.resetConnectRetryTimer() + fsm.tcpConnect() + return fsm.changeState(Connect, "Connect retry timer expired") + case c := <-fsm.conCh: + fsm.con = c + stopTimer(fsm.connectRetryTimer) + return fsm.activeSendOpen() + } + } +} + +func (fsm *FSM) activeSendOpen() int { + err := fsm.sendOpen(fsm.con) + if err != nil { + fsm.resetConnectRetryTimer() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, fmt.Sprintf("Sending OPEN message failed: %v", err)) + } + fsm.holdTimer = time.NewTimer(time.Minute * 4) + return fsm.changeState(OpenSent, "Sent OPEN message") +} + +func (fsm *FSM) msgReceiver(c *net.TCPConn) error { + for { + msg, err := recvMsg(c) + if err != nil { + fsm.msgRecvFailCh <- msgRecvErr{err: err, con: c} + return nil + + /*select { + case fsm.msgRecvFailCh <- msgRecvErr{err: err, con: c}: + continue + case <-time.NewTimer(time.Second * 60).C: + return nil + }*/ + } + fsm.msgRecvCh <- msgRecvMsg{msg: msg, con: c} + + select { + case <-fsm.stopMsgRecvCh: + return nil + default: + continue + } + } +} + +func (fsm *FSM) openSent() int { + go fsm.msgReceiver(fsm.con) + + for { + select { + case e := <-fsm.eventCh: + if e == ManualStop { + sendNotification(fsm.con, packet.Cease, 0) + stopTimer(fsm.connectRetryTimer) + fsm.disconnect() + fsm.connectRetryCounter = 0 + return fsm.changeState(Idle, "Manual stop event") + } + continue + case <-fsm.holdTimer.C: + sendNotification(fsm.con, packet.HoldTimeExpired, 0) + stopTimer(fsm.connectRetryTimer) + fsm.disconnect() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "Holdtimer expired") + case c := <-fsm.conCh: // 2nd connection coming in. Collision! + if fsm.con2 != nil { + log.WithFields(log.Fields{ + "peer": fsm.remote.String(), + "local": fsm.local.String(), + }).Warningf("Received third connection from peer. Dropping new connection") + c.Close() + continue + } + + err := fsm.sendOpen(c) // FIXME: Not sure if this is standard compliant + if err != nil { + c.Close() + continue + } + fsm.con2 = c + go fsm.msgReceiver(c) + continue + case recvMsg := <-fsm.msgRecvCh: + msg, err := packet.Decode(bytes.NewBuffer(recvMsg.msg)) + if err != nil { + switch bgperr := err.(type) { + case packet.BGPError: + sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode) + sendNotification(fsm.con2, bgperr.ErrorCode, bgperr.ErrorSubCode) + } + stopTimer(fsm.connectRetryTimer) + fsm.disconnect() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, fmt.Sprintf("Failed to decode BGP message: %v", err)) + } + switch msg.Header.Type { + case packet.NotificationMsg: + nMsg := msg.Body.(*packet.BGPNotification) + if nMsg.ErrorCode == packet.UnsupportedVersionNumber { + stopTimer(fsm.connectRetryTimer) + fsm.disconnect() + return fsm.changeState(Idle, "Received NOTIFICATION") + } + + if nMsg.ErrorCode == packet.Cease { + // Was this connection to be closed anyways? + if fsm.dumpCon(recvMsg.con) { + continue + } + } + stopTimer(fsm.connectRetryTimer) + fsm.disconnect() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "Received NOTIFICATION") + case packet.OpenMsg: + openMsg := msg.Body.(*packet.BGPOpen) + fsm.neighborID = openMsg.BGPIdentifier + fsm.resolveCollision() + stopTimer(fsm.connectRetryTimer) + err := fsm.sendKeepalive() + if err != nil { + return fsm.openSentTCPFail(err) + } + fsm.holdTime = time.Duration(math.Min(float64(fsm.holdTimeConfigured), float64(openMsg.HoldTime))) + if fsm.holdTime != 0 { + fsm.holdTimer.Reset(time.Second * fsm.holdTime) + fsm.keepaliveTime = fsm.holdTime / 3 + fsm.keepaliveTimer.Reset(time.Second * fsm.keepaliveTime) + } + return fsm.changeState(OpenConfirm, "Received OPEN message") + default: + sendNotification(fsm.con, packet.FiniteStateMachineError, 0) + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "FSM Error") + } + case err := <-fsm.msgRecvFailCh: + if err.con == fsm.con && fsm.con2 != nil { + fsm.con.Close() + fsm.con = fsm.con2 + fsm.con2 = nil + continue + } + + if err.con == fsm.con2 { + fsm.con2.Close() + fsm.con2 = nil + continue + } + return fsm.openSentTCPFail(err.err) + } + } +} + +func (fsm *FSM) openSentTCPFail(err error) int { + fsm.con.Close() + fsm.resetConnectRetryTimer() + return fsm.changeState(Active, fmt.Sprintf("TCP failure: %v", err)) +} + +func (fsm *FSM) dumpCon(c *net.TCPConn) bool { + p := fsm.isPassive(c) + if fsm.routerID > fsm.neighborID { + return p + } + return !p +} + +func (fsm *FSM) resolveCollision() { + if fsm.con2 == nil { + return + } + + if fsm.routerID > fsm.neighborID { + // Terminate passive connection + if fsm.isPassive(fsm.con) { + dumpCon(fsm.con) + fsm.con = fsm.con2 + return + } + if fsm.isPassive(fsm.con2) { + dumpCon(fsm.con2) + return + } + return + } + + // Terminate active connection + if !fsm.isPassive(fsm.con) { + dumpCon(fsm.con) + fsm.con = fsm.con2 + return + } + if !fsm.isPassive(fsm.con2) { + dumpCon(fsm.con2) + fsm.con2.Close() + fsm.con2 = nil + return + } +} + +func dumpCon(c *net.TCPConn) { + sendNotification(c, packet.Cease, packet.ConnectionCollisionResolution) + c.Close() +} + +func (fsm *FSM) isPassive(c *net.TCPConn) bool { + if c.LocalAddr().String() == fmt.Sprintf("%s:179", fsm.local.String()) { + return true + } + return false +} + +func (fsm *FSM) openConfirm() int { + for { + select { + case e := <-fsm.eventCh: + if e == ManualStop { // Event 2 + sendNotification(fsm.con, packet.Cease, 0) + stopTimer(fsm.connectRetryTimer) + fsm.disconnect() + fsm.connectRetryCounter = 0 + return fsm.changeState(Idle, "Manual stop event") + } + continue + case <-fsm.holdTimer.C: + sendNotification(fsm.con, packet.HoldTimeExpired, 0) + stopTimer(fsm.connectRetryTimer) + fsm.disconnect() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "Holdtimer expired") + case <-fsm.keepaliveTimer.C: + err := fsm.sendKeepalive() + if err != nil { + stopTimer(fsm.connectRetryTimer) + fsm.disconnect() + 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 { + log.WithFields(log.Fields{ + "peer": fsm.remote.String(), + "local": fsm.local.String(), + }).Warningf("Received third connection from peer. Dropping new connection") + c.Close() + continue + } + + err := fsm.sendOpen(c) // FIXME: Not sure if this is standard compliant + if err != nil { + c.Close() + continue + } + fsm.con2 = c + go fsm.msgReceiver(c) + continue + 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) + switch bgperr := err.(type) { + case packet.BGPError: + sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode) + sendNotification(fsm.con2, bgperr.ErrorCode, bgperr.ErrorSubCode) + } + stopTimer(fsm.connectRetryTimer) + fsm.disconnect() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "Failed to decode BGP message") + } + + switch msg.Header.Type { + case packet.NotificationMsg: + nMsg := msg.Body.(packet.BGPNotification) + if nMsg.ErrorCode == packet.UnsupportedVersionNumber { + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + return fsm.changeState(Idle, "Received NOTIFICATION") + } + + if nMsg.ErrorCode == packet.Cease { + // Was this connection to be closed anyways? + if fsm.dumpCon(recvMsg.con) { + continue + } + } + + return fsm.openConfirmTCPFail(fmt.Errorf("NOTIFICATION received")) + case packet.KeepaliveMsg: + fsm.holdTimer.Reset(time.Second * fsm.holdTime) + return fsm.changeState(Established, "Received KEEPALIVE") + case packet.OpenMsg: + openMsg := msg.Body.(*packet.BGPOpen) + fsm.neighborID = openMsg.BGPIdentifier + fsm.resolveCollision() + default: + sendNotification(fsm.con, packet.FiniteStateMachineError, 0) + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "FSM Error") + } + case err := <-fsm.msgRecvFailCh: + if err.con == fsm.con && fsm.con2 != nil { + fsm.con.Close() + fsm.con = fsm.con2 + fsm.con2 = nil + continue + } + + if err.con == fsm.con2 { + fsm.con2.Close() + fsm.con2 = nil + continue + } + return fsm.openConfirmTCPFail(err.err) + } + } +} + +func (fsm *FSM) openConfirmTCPFail(err error) int { + fsm.con.Close() + fsm.resetConnectRetryTimer() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, fmt.Sprintf("Failure: %v", err)) +} + +func (fsm *FSM) established() int { + fsm.adjRibIn = rt.New() + go func() { + for { + time.Sleep(time.Second * 10) + fmt.Printf("Dumping AdjRibIn\n") + routes := fsm.adjRibIn.Dump() + for _, route := range routes { + fmt.Printf("LPM: %s\n", route.Prefix().String()) + } + } + }() + + for { + select { + case e := <-fsm.eventCh: + if e == ManualStop { // Event 2 + sendNotification(fsm.con, packet.Cease, 0) + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + fsm.connectRetryCounter = 0 + return fsm.changeState(Idle, "Manual stop event") + } + if e == AutomaticStop { // Event 8 + sendNotification(fsm.con, packet.Cease, 0) + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "Automatic stop event") + } + continue + case <-fsm.holdTimer.C: + sendNotification(fsm.con, packet.HoldTimeExpired, 0) + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "Holdtimer expired") + case <-fsm.keepaliveTimer.C: + err := fsm.sendKeepalive() + if err != nil { + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + 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() + continue + case recvMsg := <-fsm.msgRecvCh: + msg, err := packet.Decode(bytes.NewBuffer(recvMsg.msg)) + if err != nil { + switch bgperr := err.(type) { + case packet.BGPError: + sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode) + } + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "Failed to decode BGP message") + } + switch msg.Header.Type { + case packet.NotificationMsg: + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "Received NOTIFICATION") + case packet.UpdateMsg: + if fsm.holdTime != 0 { + fsm.holdTimer.Reset(time.Second * fsm.holdTime) + } + + 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) + fmt.Printf("LPM: Removing prefix %s\n", pfx.String()) + fsm.adjRibIn.RemovePfx(pfx) + } + + for r := u.NLRI; r != nil; r = r.Next { + x := r.IP.([4]byte) + pfx := tnet.NewPfx(convert.Uint32b(x[:]), r.Pfxlen) + fmt.Printf("LPM: Adding prefix %s\n", pfx.String()) + + path := &rt.Path{ + Type: rt.BGPPathType, + BGPPath: &rt.BGPPath{}, + } + + for pa := u.PathAttributes; pa.Next != nil; pa = pa.Next { + switch pa.TypeCode { + case packet.OriginAttr: + path.BGPPath.Origin = pa.Value.(uint8) + case packet.LocalPrefAttr: + path.BGPPath.LocalPref = pa.Value.(uint32) + case packet.MEDAttr: + path.BGPPath.MED = pa.Value.(uint32) + case packet.NextHopAttr: + path.BGPPath.NextHop = pa.Value.(uint32) + case packet.ASPathAttr: + path.BGPPath.ASPath = pa.ASPathString() + path.BGPPath.ASPathLen = pa.ASPathLen() + } + } + fsm.adjRibIn.Insert(rt.NewRoute(pfx, []*rt.Path{path})) + } + + continue + case packet.KeepaliveMsg: + if fsm.holdTime != 0 { + fsm.holdTimer.Reset(time.Second * fsm.holdTime) + } + continue + case packet.OpenMsg: + if fsm.con2 != nil { + sendNotification(fsm.con2, packet.Cease, packet.ConnectionCollisionResolution) + fsm.con2.Close() + fsm.con2 = nil + continue + } + sendNotification(fsm.con, packet.FiniteStateMachineError, 0) + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "FSM Error") + default: + sendNotification(fsm.con, packet.FiniteStateMachineError, 0) + stopTimer(fsm.connectRetryTimer) + fsm.con.Close() + fsm.connectRetryCounter++ + return fsm.changeState(Idle, "FSM Error") + } + case err := <-fsm.msgRecvFailCh: + if err.con == fsm.con && fsm.con2 != nil { + fsm.con.Close() + fsm.con = fsm.con2 + fsm.con2 = nil + continue + } + + if err.con == fsm.con2 { + fsm.con2.Close() + fsm.con2 = nil + continue + } + return fsm.openConfirmTCPFail(err.err) + } + } +} + +func stopTimer(t *time.Timer) { + if !t.Stop() { + select { + case <-t.C: + default: + } + } +} + +func (fsm *FSM) startConnectRetryTimer() { + fsm.connectRetryTimer = time.NewTimer(time.Second * fsm.connectRetryTime) +} + +func (fsm *FSM) resetConnectRetryTimer() { + if !fsm.connectRetryTimer.Reset(time.Second * fsm.connectRetryTime) { + <-fsm.connectRetryTimer.C + } +} + +func (fsm *FSM) resetDelayOpenTimer() { + if !fsm.delayOpenTimer.Reset(time.Second * fsm.delayOpenTime) { + <-fsm.delayOpenTimer.C + } +} + +func (fsm *FSM) sendKeepalive() error { + msg := packet.SerializeKeepaliveMsg() + + _, err := fsm.con.Write(msg) + if err != nil { + return fmt.Errorf("Unable to send KEEPALIVE message: %v", err) + } + + return nil +} + +func (fsm *FSM) sendOpen(c *net.TCPConn) error { + msg := packet.SerializeOpenMsg(&packet.BGPOpen{ + Version: BGPVersion, + AS: fsm.localASN, + HoldTime: uint16(fsm.holdTimeConfigured), + BGPIdentifier: fsm.routerID, + OptParmLen: 0, + }) + + _, err := c.Write(msg) + if err != nil { + return fmt.Errorf("Unable to send OPEN message: %v", err) + } + + return nil +} + +func sendNotification(c *net.TCPConn, errorCode uint8, errorSubCode uint8) error { + if c == nil { + return fmt.Errorf("connection is nil") + } + + msg := packet.SerializeNotificationMsg(&packet.BGPNotification{}) + + _, err := c.Write(msg) + if err != nil { + return fmt.Errorf("Unable to send NOTIFICATION message: %v", err) + } + + return nil +} diff --git a/protocols/bgp/server/peer.go b/protocols/bgp/server/peer.go new file mode 100644 index 00000000..d6ac7855 --- /dev/null +++ b/protocols/bgp/server/peer.go @@ -0,0 +1,36 @@ +package server + +import ( + "net" + + "github.com/taktv6/tbgp/config" +) + +type Peer struct { + addr net.IP + asn uint32 + fsm *FSM + routerID uint32 +} + +func NewPeer(c config.Peer) (*Peer, error) { + p := &Peer{ + addr: c.PeerAddress, + asn: c.PeerAS, + fsm: NewFSM(c), + } + return p, nil +} + +func (p *Peer) GetAddr() net.IP { + return p.addr +} + +func (p *Peer) GetASN() uint32 { + return p.asn +} + +func (p *Peer) Start() { + p.fsm.start() + p.fsm.activate() +} diff --git a/protocols/bgp/server/server.go b/protocols/bgp/server/server.go new file mode 100644 index 00000000..3bc9b193 --- /dev/null +++ b/protocols/bgp/server/server.go @@ -0,0 +1,119 @@ +package server + +import ( + "fmt" + "io" + "net" + "strings" + + log "github.com/sirupsen/logrus" + "github.com/taktv6/tbgp/config" + "github.com/taktv6/tbgp/packet" +) + +const ( + uint16max = 65535 + BGPVersion = 4 +) + +type BGPServer struct { + listeners []*TCPListener + acceptCh chan *net.TCPConn + peers map[string]*Peer + routerID uint32 +} + +func NewBgpServer() *BGPServer { + return &BGPServer{ + peers: make(map[string]*Peer), + } +} + +func (b *BGPServer) RouterID() uint32 { + return b.routerID +} + +func (b *BGPServer) Start(c *config.Global) error { + if err := c.SetDefaultGlobalConfigValues(); err != nil { + return fmt.Errorf("Failed to load defaults: %v", err) + } + + fmt.Printf("ROUTER ID: %d\n", c.RouterID) + b.routerID = c.RouterID + + if c.Listen { + acceptCh := make(chan *net.TCPConn, 4096) + for _, addr := range c.LocalAddressList { + l, err := NewTCPListener(addr, c.Port, acceptCh) + if err != nil { + return fmt.Errorf("Failed to start TCPListener for %s: %v", addr.String(), err) + } + b.listeners = append(b.listeners, l) + } + b.acceptCh = acceptCh + + go b.incomingConnectionWorker() + } + + return nil +} + +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 { + c.Close() + log.WithFields(log.Fields{ + "source": c.RemoteAddr(), + }).Warning("TCP connection from unknown source") + continue + } + + log.WithFields(log.Fields{ + "source": c.RemoteAddr(), + }).Info("Incoming TCP connection") + + fmt.Printf("DEBUG: Sending incoming TCP connection to fsm for peer %s\n", peerAddr) + b.peers[peerAddr].fsm.conCh <- c + fmt.Printf("DEBUG: Sending done\n") + } +} + +func (b *BGPServer) AddPeer(c config.Peer) error { + if c.LocalAS > uint16max || c.PeerAS > uint16max { + return fmt.Errorf("32bit ASNs are not supported yet") + } + + peer, err := NewPeer(c) + if err != nil { + return err + } + + peer.routerID = c.RouterID + peerAddr := peer.GetAddr().String() + b.peers[peerAddr] = peer + b.peers[peerAddr].Start() + + return nil +} + +func recvMsg(c *net.TCPConn) (msg []byte, err error) { + buffer := make([]byte, packet.MaxLen) + _, err = io.ReadFull(c, buffer[0:packet.MinLen]) + if err != nil { + return nil, fmt.Errorf("Read failed: %v", err) + } + + l := int(buffer[16])*256 + int(buffer[17]) + toRead := l + _, err = io.ReadFull(c, buffer[packet.MinLen:toRead]) + if err != nil { + return nil, fmt.Errorf("Read failed: %v", err) + } + + return buffer, nil +} diff --git a/protocols/bgp/server/sockopt.go b/protocols/bgp/server/sockopt.go new file mode 100644 index 00000000..2984216a --- /dev/null +++ b/protocols/bgp/server/sockopt.go @@ -0,0 +1,31 @@ +package server + +import ( + "net" + "os" + "syscall" +) + +const ( + TCP_MD5SIG = 14 // TCP MD5 Signature (RFC2385) + IPV6_MINHOPCOUNT = 73 // Generalized TTL Security Mechanism (RFC5082) +) + +func SetListenTCPTTLSockopt(l *net.TCPListener, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPListener(l) + defer fi.Close() + if err != nil { + return err + } + return setsockoptIPTTL(int(fi.Fd()), family, ttl) +} + +func setsockoptIPTTL(fd int, family int, value int) error { + level := syscall.IPPROTO_IP + name := syscall.IP_TTL + if family == syscall.AF_INET6 { + level = syscall.IPPROTO_IPV6 + name = syscall.IPV6_UNICAST_HOPS + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, level, name, value)) +} diff --git a/protocols/bgp/server/tcplistener.go b/protocols/bgp/server/tcplistener.go new file mode 100644 index 00000000..04607c74 --- /dev/null +++ b/protocols/bgp/server/tcplistener.go @@ -0,0 +1,65 @@ +package server + +import ( + "net" + "strconv" + + log "github.com/sirupsen/logrus" +) + +const ( + BGPPORT = 179 +) + +type TCPListener struct { + l *net.TCPListener + closeCh chan struct{} +} + +func NewTCPListener(address net.IP, port uint16, ch chan *net.TCPConn) (*TCPListener, error) { + proto := "tcp4" + if address.To4() == nil { + proto = "tcp6" + } + + addr, err := net.ResolveTCPAddr(proto, net.JoinHostPort(address.String(), strconv.Itoa(int(port)))) + if err != nil { + return nil, err + } + + l, err := net.ListenTCP(proto, addr) + if err != nil { + return nil, err + } + + // Note: Set TTL=255 for incoming connection listener in order to accept + // connection in case for the neighbor has TTL Security settings. + if err := SetListenTCPTTLSockopt(l, 255); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Warnf("cannot set TTL(=%d) for TCPLisnter: %s", 255, err) + } + + tl := &TCPListener{ + l: l, + closeCh: make(chan struct{}), + } + + go func(tl *TCPListener) error { + for { + conn, err := tl.l.AcceptTCP() + if err != nil { + close(tl.closeCh) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Error": err, + }).Warn("Failed to AcceptTCP") + return err + } + ch <- conn + } + }(tl) + + return tl, nil +} diff --git a/protocols/bgp/server/util.go b/protocols/bgp/server/util.go new file mode 100644 index 00000000..871f6a58 --- /dev/null +++ b/protocols/bgp/server/util.go @@ -0,0 +1,33 @@ +package server + +import ( + "net" + "os" + "strings" + "syscall" +) + +func extractFileAndFamilyFromTCPListener(l *net.TCPListener) (*os.File, int, error) { + // Note #1: TCPListener.File() has the unexpected side-effect of putting + // the original socket into blocking mode. See Note #2. + fi, err := l.File() + if err != nil { + return nil, 0, err + } + + // Note #2: Call net.FileListener() to put the original socket back into + // non-blocking mode. + fl, err := net.FileListener(fi) + if err != nil { + fi.Close() + return nil, 0, err + } + fl.Close() + + family := syscall.AF_INET + if strings.Contains(l.Addr().String(), "[") { + family = syscall.AF_INET6 + } + + return fi, family, nil +} -- GitLab