diff --git a/net/ip.go b/net/ip.go index 2855235eae0e6c44753f351f2363ed790f2ed9e3..90628ece21c0edd8dd0df4dbb7a1c86ecdf4d7c0 100644 --- a/net/ip.go +++ b/net/ip.go @@ -41,6 +41,27 @@ func IPv6FromBlocks(b1, b2, b3, b4, b5, b6, b7, b8 uint16) IP { uint64(uint64(b5)<<48+uint64(b6)<<32+uint64(b7)<<16+uint64(b8))) } +// IPFromBytes returns an IP address for a byte slice +func IPFromBytes(b []byte) (IP, error) { + if len(b) == 4 { + return IPv4FromOctets(b[0], b[1], b[2], b[3]), nil + } + + if len(b) == 16 { + return IPv6FromBlocks( + uint16(b[0])<<8+uint16(b[1]), + uint16(b[2])<<8+uint16(b[3]), + uint16(b[4])<<8+uint16(b[5]), + uint16(b[6])<<8+uint16(b[7]), + uint16(b[8])<<8+uint16(b[9]), + uint16(b[10])<<8+uint16(b[11]), + uint16(b[12])<<8+uint16(b[13]), + uint16(b[14])<<8+uint16(b[15])), nil + } + + return IP{}, fmt.Errorf("byte slice has an invalid legth. Expected either 4 (IPv4) or 16 (IPv6) bytes but got: %d", len(b)) +} + // Equal returns true if ip is equal to other func (ip IP) Equal(other IP) bool { return ip == other diff --git a/net/ip_test.go b/net/ip_test.go index 613e342d7ffe46460007aeec692da8c39286258f..f1edd4688cb83e8277b723e45e32cacfc0b8e33e 100644 --- a/net/ip_test.go +++ b/net/ip_test.go @@ -228,6 +228,50 @@ func TestIPv6FromBlocks(t *testing.T) { } } +func TestIPFromBytes(t *testing.T) { + tests := []struct { + name string + bytes []byte + expected IP + wantFail bool + }{ + { + name: "IPV4: 172.217.16.195", + bytes: []byte{172, 217, 16, 195}, + expected: IP{ + higher: 0, + lower: 2899906755, + ipVersion: 4, + }, + }, + { + name: "IPV6: IPv6 2001:678:1E0:1234:5678:DEAD:BEEF:CAFE", + bytes: []byte{0x20, 0x01, 0x06, 0x78, 0x01, 0xE0, 0x12, 0x34, 0x56, 0x78, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE}, + expected: IP{ + higher: 2306131596687708724, + lower: 6230974922281175806, + ipVersion: 6, + }, + }, + { + name: "invalid length", + bytes: []byte{172, 217, 123, 231, 95}, + wantFail: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ip, err := IPFromBytes(test.bytes) + if err == nil && test.wantFail { + t.Fatalf("Expected test to fail, but did not") + } + + assert.Equal(t, test.expected, ip) + }) + } +} + func TestToNetIP(t *testing.T) { tests := []struct { name string diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index 3f25ddf60adea2bfdf13b6043e06ef05818a999c..e008205a2ad95e97872c0a691a479efe42888c24 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -85,17 +85,26 @@ const ( ConnectionCollisionResolution = 7 OutOfResoutces = 8 - IPv4AFI = 1 - IPv6AFI = 2 - UnicastSAFI = 1 - CapabilitiesParamType = 2 - MultiProtocolCapabilityCode = 1 - AddPathCapabilityCode = 69 - ASN4CapabilityCode = 65 - AddPathReceive = 1 - AddPathSend = 2 - AddPathSendReceive = 3 - ASTransASN = 23456 + IPv4AFI = 1 + IPv6AFI = 2 + UnicastSAFI = 1 + CapabilitiesParamType = 2 + MultiProtocolCapabilityCode = 1 + MultiProtocolReachNLRICode = 14 + MultiProtocolUnreachNLRICode = 15 + AddPathCapabilityCode = 69 + ASN4CapabilityCode = 65 + AddPathReceive = 1 + AddPathSend = 2 + AddPathSendReceive = 3 + ASTransASN = 23456 +) + +var ( + afiAddrLenBytes = map[uint16]uint8{ + 1: 4, + 2: 16, + } ) type BGPError struct { diff --git a/protocols/bgp/packet/helper.go b/protocols/bgp/packet/helper.go index 1fd55ffe033864a44fca2a09f462d3105a237d13..18176cf1ec461e4f46d788d6f6c22072f83af687 100644 --- a/protocols/bgp/packet/helper.go +++ b/protocols/bgp/packet/helper.go @@ -1,6 +1,7 @@ package packet import ( + "fmt" "math" bnet "github.com/bio-routing/bio-rd/net" @@ -11,7 +12,7 @@ func serializePrefix(pfx bnet.Prefix) []byte { return []byte{} } - numBytes := uint8(math.Ceil(float64(pfx.Pfxlen()) / float64(8))) + numBytes := uint8(math.Ceil(float64(pfx.Pfxlen()) / 8)) b := make([]byte, numBytes+1) b[0] = pfx.Pfxlen() @@ -19,3 +20,21 @@ func serializePrefix(pfx bnet.Prefix) []byte { return b } + +func deserializePrefix(b []byte, pfxLen uint8, afi uint16) (bnet.Prefix, error) { + numBytes := int(math.Ceil(float64(pfxLen) / 8)) + + if numBytes != len(b) { + return bnet.Prefix{}, fmt.Errorf("could not parse prefix of legth %d. Expected %d bytes, got %d", pfxLen, numBytes, len(b)) + } + + ipBytes := make([]byte, afiAddrLenBytes[afi]) + copy(ipBytes, b) + + ip, err := bnet.IPFromBytes(ipBytes) + if err != nil { + return bnet.Prefix{}, err + } + + return bnet.NewPfx(ip, pfxLen), nil +} diff --git a/protocols/bgp/packet/mp_reach_nlri.go b/protocols/bgp/packet/mp_reach_nlri.go index 64c76066015b503c9f5b5f0c848c267e062aefe2..e886f27c3f6da4116958759d1fcbddbcb1c65f0c 100644 --- a/protocols/bgp/packet/mp_reach_nlri.go +++ b/protocols/bgp/packet/mp_reach_nlri.go @@ -2,6 +2,7 @@ package packet import ( "bytes" + "fmt" "github.com/taktv6/tflow2/convert" @@ -31,3 +32,32 @@ func (n *MultiProtocolReachNLRI) serialize(buf *bytes.Buffer) uint8 { return uint8(tempBuf.Len()) } + +func deserializeMultiProtocolReachNLRI(b []byte) (MultiProtocolReachNLRI, error) { + n := MultiProtocolReachNLRI{} + nextHopLength := uint8(0) + variable := make([]byte, len(b)-4) + + fields := []interface{}{ + &n.AFI, + &n.SAFI, + &nextHopLength, + &variable, + } + err := decode(bytes.NewBuffer(b), fields) + if err != nil { + return MultiProtocolReachNLRI{}, err + } + + n.NextHop, err = bnet.IPFromBytes(variable[:nextHopLength]) + if err != nil { + return MultiProtocolReachNLRI{}, fmt.Errorf("Failed to decode next hop IP: %v", err) + } + + n.Prefix, err = deserializePrefix(variable[nextHopLength+2:], variable[nextHopLength+1], n.AFI) + if err != nil { + return MultiProtocolReachNLRI{}, err + } + + return n, nil +} diff --git a/protocols/bgp/packet/mp_unreach_nlri.go b/protocols/bgp/packet/mp_unreach_nlri.go index 1c0250931c03f0dd20fdfca7c006d077028c846e..e3e43822caecceda91ff72e5ff44f55c73299adb 100644 --- a/protocols/bgp/packet/mp_unreach_nlri.go +++ b/protocols/bgp/packet/mp_unreach_nlri.go @@ -24,3 +24,25 @@ func (n *MultiProtocolUnreachNLRI) serialize(buf *bytes.Buffer) uint8 { return uint8(tempBuf.Len()) } + +func deserializeMultiProtocolUnreachNLRI(b []byte) (MultiProtocolUnreachNLRI, error) { + n := MultiProtocolUnreachNLRI{} + prefix := make([]byte, len(b)-3) + + fields := []interface{}{ + &n.AFI, + &n.SAFI, + &prefix, + } + err := decode(bytes.NewBuffer(b), fields) + if err != nil { + return MultiProtocolUnreachNLRI{}, err + } + + n.Prefix, err = deserializePrefix(prefix[1:], prefix[0], n.AFI) + if err != nil { + return MultiProtocolUnreachNLRI{}, err + } + + return n, nil +} diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index f1aae242153e09da32de808fabe40efbcc719456..77025f38ef976917f088509a8481aa3e53a363a5 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -99,6 +99,14 @@ func decodePathAttr(buf *bytes.Buffer, opt *types.Options) (pa *PathAttribute, c if err := pa.decodeCommunities(buf); err != nil { return nil, consumed, fmt.Errorf("Failed to decode Community: %v", err) } + case MultiProtocolReachNLRICode: + if err := pa.decodeMultiProtocolReachNLRI(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to multi protocol reachable NLRI: %v", err) + } + case MultiProtocolUnreachNLRICode: + if err := pa.decodeMultiProtocolUnreachNLRI(buf); err != nil { + return nil, consumed, fmt.Errorf("Failed to multi protocol unreachable NLRI: %v", err) + } case AS4AggregatorAttr: if err := pa.decodeAS4Aggregator(buf); err != nil { return nil, consumed, fmt.Errorf("Failed to skip not supported AS4Aggregator: %v", err) @@ -116,18 +124,53 @@ func decodePathAttr(buf *bytes.Buffer, opt *types.Options) (pa *PathAttribute, c return pa, consumed + pa.Length, nil } +func (pa *PathAttribute) decodeMultiProtocolReachNLRI(buf *bytes.Buffer) error { + b := make([]byte, pa.Length) + n, err := buf.Read(b) + if err != nil { + return fmt.Errorf("Unable to read %d bytes from buffer: %v", pa.Length, err) + } + if n != int(pa.Length) { + return fmt.Errorf("Unable to read %d bytes from buffer, only got %d bytes", pa.Length, n) + } + + nlri, err := deserializeMultiProtocolReachNLRI(b) + if err != nil { + return fmt.Errorf("Unable to decode MP_REACH_NLRI: %v", err) + } + + pa.Value = nlri + return nil +} + +func (pa *PathAttribute) decodeMultiProtocolUnreachNLRI(buf *bytes.Buffer) error { + b := make([]byte, pa.Length) + n, err := buf.Read(b) + if err != nil { + return fmt.Errorf("Unable to read %d bytes from buffer: %v", pa.Length, err) + } + if n != int(pa.Length) { + return fmt.Errorf("Unable to read %d bytes from buffer, only got %d bytes", pa.Length, n) + } + + nlri, err := deserializeMultiProtocolUnreachNLRI(b) + if err != nil { + return fmt.Errorf("Unable to decode MP_UNREACH_NLRI: %v", err) + } + + pa.Value = nlri + return nil +} + func (pa *PathAttribute) decodeUnknown(buf *bytes.Buffer) error { u := make([]byte, pa.Length) - p := uint16(0) err := decode(buf, []interface{}{&u}) if err != nil { return fmt.Errorf("Unable to decode: %v", err) } pa.Value = u - p += pa.Length - return nil } diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index 4a0966e6b3dee2a6bbbf3bb4712247eefc7696e1..0048f9005eba42d009e90281d17a2a2745505f70 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -746,6 +746,133 @@ func TestDecodeCommunity(t *testing.T) { } } +func TestDecodeMultiProtocolReachNLRI(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "incomplete", + input: []byte{0, 0, 0, 0}, + wantFail: true, + explicitLength: 32, + }, + { + name: "valid MP_REACH_NLRI", + input: []byte{ + 0x00, 0x02, // AFI + 0x01, // SAFI + 0x10, 0x20, 0x01, 0x06, 0x78, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // NextHop + 0x00, // RESERVED + 0x30, 0x26, 0x00, 0x00, 0x06, 0xff, 0x05, // Prefix + }, + expected: &PathAttribute{ + Length: 28, + Value: MultiProtocolReachNLRI{ + AFI: IPv6AFI, + SAFI: UnicastSAFI, + NextHop: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0x2), + Prefix: bnet.NewPfx(bnet.IPv6FromBlocks(0x2600, 0x6, 0xff05, 0, 0, 0, 0, 0), 48), + }, + }, + }, + } + + t.Parallel() + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeMultiProtocolReachNLRI(bytes.NewBuffer(test.input)) + + if test.wantFail { + if err != nil { + return + } + t.Fatalf("Expected error did not happen for test %q", test.name) + } + + if err != nil { + t.Fatalf("Unexpected failure for test %q: %v", test.name, err) + } + + assert.Equal(t, test.expected, pa) + }) + } +} + +func TestDecodeMultiProtocolUnreachNLRI(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "incomplete", + input: []byte{0, 0, 0, 0}, + wantFail: true, + explicitLength: 32, + }, + { + name: "valid MP_UNREACH_NLRI", + input: []byte{ + 0x00, 0x02, // AFI + 0x01, // SAFI + 0x2c, 0x26, 0x20, 0x01, 0x10, 0x90, 0x00, // Prefix + }, + expected: &PathAttribute{ + Length: 10, + Value: MultiProtocolUnreachNLRI{ + AFI: IPv6AFI, + SAFI: UnicastSAFI, + Prefix: bnet.NewPfx(bnet.IPv6FromBlocks(0x2620, 0x110, 0x9000, 0, 0, 0, 0, 0), 44), + }, + }, + }, + } + + t.Parallel() + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeMultiProtocolUnreachNLRI(bytes.NewBuffer(test.input)) + + if test.wantFail { + if err != nil { + return + } + t.Fatalf("Expected error did not happen for test %q", test.name) + } + + if err != nil { + t.Fatalf("Unexpected failure for test %q: %v", test.name, err) + } + + assert.Equal(t, test.expected, pa) + }) + } +} + func TestSetLength(t *testing.T) { tests := []struct { name string