diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index 46be5aed41d781a12696b0811cb2865eb8c91a0c..be4f30dc5890e9abca69a4822a3e7d6187a30a0c 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -4,10 +4,11 @@ const ( OctetLen = 8 BGP4Version = 4 - MarkerLen = 16 - HeaderLen = 19 - MinLen = 19 - MaxLen = 4096 + MarkerLen = 16 + HeaderLen = 19 + MinLen = 19 + MaxLen = 4096 + NLRIMaxLen = 5 OpenMsg = 1 UpdateMsg = 2 diff --git a/protocols/bgp/packet/encoder.go b/protocols/bgp/packet/encoder.go index e7c98c6f2035c5c07e681f3f062c10807fe0787e..3cfe702d14ffb66605919fe60839d98a8308b761 100644 --- a/protocols/bgp/packet/encoder.go +++ b/protocols/bgp/packet/encoder.go @@ -2,6 +2,7 @@ package packet import ( "bytes" + "fmt" "github.com/taktv6/tflow2/convert" ) @@ -43,3 +44,62 @@ func serializeHeader(buf *bytes.Buffer, length uint16, typ uint8) { buf.Write(convert.Uint16Byte(length)) buf.WriteByte(typ) } + +func (b *BGPUpdate) SerializeUpdate() ([]byte, error) { + budget := MaxLen - MinLen + buf := bytes.NewBuffer(nil) + + withdrawBuf := bytes.NewBuffer(nil) + for withdraw := b.WithdrawnRoutes; withdraw != nil; withdraw = withdraw.Next { + nlriLen := int(withdraw.serialize(withdrawBuf)) + budget -= nlriLen + if budget < 0 { + return nil, fmt.Errorf("update too long") + } + } + + pathAttributesBuf := bytes.NewBuffer(nil) + for pa := b.PathAttributes; pa != nil; pa = pa.Next { + paLen := int(pa.serialize(pathAttributesBuf)) + budget -= paLen + if budget < 0 { + return nil, fmt.Errorf("update too long") + } + } + + nlriBuf := bytes.NewBuffer(nil) + for nlri := b.NLRI; nlri != nil; nlri = nlri.Next { + nlriLen := int(nlri.serialize(nlriBuf)) + budget -= nlriLen + if budget < 0 { + return nil, fmt.Errorf("update too long") + } + } + + withdrawnRoutesLen := withdrawBuf.Len() + if withdrawnRoutesLen > 65535 { + return nil, fmt.Errorf("Invalid Withdrawn Routes Length: %d", withdrawnRoutesLen) + } + + totalPathAttributesLen := pathAttributesBuf.Len() + if totalPathAttributesLen > 65535 { + return nil, fmt.Errorf("Invalid Total Path Attribute Length: %d", totalPathAttributesLen) + } + + totalLength := 2 + withdrawnRoutesLen + totalPathAttributesLen + 2 + nlriBuf.Len() + 19 + if totalLength > 4096 { + return nil, fmt.Errorf("Update too long: %d bytes", totalLength) + } + + serializeHeader(buf, uint16(totalLength), UpdateMsg) + + buf.Write(convert.Uint16Byte(uint16(withdrawnRoutesLen))) + buf.Write(withdrawBuf.Bytes()) + + buf.Write(convert.Uint16Byte(uint16(totalPathAttributesLen))) + buf.Write(pathAttributesBuf.Bytes()) + + buf.Write(nlriBuf.Bytes()) + + return buf.Bytes(), nil +} diff --git a/protocols/bgp/packet/nlri.go b/protocols/bgp/packet/nlri.go index 1f478fc10ffc01c26447975ece48168735bd67b8..b9ce4f2427c97c776d11cc257bb5ae2f3b5e6a1f 100644 --- a/protocols/bgp/packet/nlri.go +++ b/protocols/bgp/packet/nlri.go @@ -58,3 +58,17 @@ func decodeNLRI(buf *bytes.Buffer) (*NLRI, uint8, error) { nlri.IP = addr return nlri, toCopy + 1, nil } + +func (n *NLRI) serialize(buf *bytes.Buffer) uint8 { + addr := n.IP.([4]byte) + nBytes := bytesInAddr(n.Pfxlen) + + buf.WriteByte(n.Pfxlen) + buf.Write(addr[:nBytes]) + + return nBytes + 1 +} + +func bytesInAddr(pfxlen uint8) uint8 { + return uint8(math.Ceil(float64(pfxlen) / 8)) +} diff --git a/protocols/bgp/packet/nlri_test.go b/protocols/bgp/packet/nlri_test.go index e7251e41ef95499b470cdada02073c9cdacd046e..c9299e2c2dff1e42953f527837276cb193f19577 100644 --- a/protocols/bgp/packet/nlri_test.go +++ b/protocols/bgp/packet/nlri_test.go @@ -127,3 +127,84 @@ func TestDecodeNLRI(t *testing.T) { assert.Equal(t, test.expected, res) } } + +func TestBytesInAddr(t *testing.T) { + tests := []struct { + name string + input uint8 + expected uint8 + }{ + { + name: "Test #1", + input: 24, + expected: 3, + }, + { + name: "Test #2", + input: 25, + expected: 4, + }, + { + name: "Test #3", + input: 32, + expected: 4, + }, + { + name: "Test #4", + input: 0, + expected: 0, + }, + { + name: "Test #5", + input: 9, + expected: 2, + }, + } + + for _, test := range tests { + res := bytesInAddr(test.input) + if res != test.expected { + t.Errorf("Unexpected result for test %q: %d", test.name, res) + } + } +} + +func TestNLRISerialize(t *testing.T) { + tests := []struct { + name string + nlri *NLRI + expected []byte + }{ + { + name: "Test #1", + nlri: &NLRI{ + IP: [4]byte{1, 2, 3, 0}, + Pfxlen: 25, + }, + expected: []byte{25, 1, 2, 3, 0}, + }, + { + name: "Test #2", + nlri: &NLRI{ + IP: [4]byte{1, 2, 3, 0}, + Pfxlen: 24, + }, + expected: []byte{24, 1, 2, 3}, + }, + { + name: "Test #3", + nlri: &NLRI{ + IP: [4]byte{100, 200, 128, 0}, + Pfxlen: 17, + }, + expected: []byte{17, 100, 200, 128}, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(nil) + test.nlri.serialize(buf) + res := buf.Bytes() + assert.Equal(t, test.expected, res) + } +} diff --git a/protocols/bgp/packet/path_attribute_flags.go b/protocols/bgp/packet/path_attribute_flags.go index 8beca7ac9dd841ae3366d50ce1752b3b177f1707..8c4b09f916dff0aa619c61c33bec0b66e1398922 100644 --- a/protocols/bgp/packet/path_attribute_flags.go +++ b/protocols/bgp/packet/path_attribute_flags.go @@ -44,3 +44,19 @@ func isExtendedLength(x uint8) bool { } return false } + +func setOptional(x uint8) uint8 { + return x | 128 +} + +func setTransitive(x uint8) uint8 { + return x | 64 +} + +func setPartial(x uint8) uint8 { + return x | 32 +} + +func setExtendedLength(x uint8) uint8 { + return x | 16 +} diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index b00f1599c96ec4f29ad4df54080b98a5a36ab0f7..be40ccf8ed2efbd0503bedc9d392ed3cd4cf118d 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -3,6 +3,8 @@ package packet import ( "bytes" "fmt" + + "github.com/taktv6/tflow2/convert" ) func decodePathAttrs(buf *bytes.Buffer, tpal uint16) (*PathAttribute, error) { @@ -287,3 +289,112 @@ func dumpNBytes(buf *bytes.Buffer, n uint16) error { } return nil } + +func (pa *PathAttribute) serialize(buf *bytes.Buffer) uint8 { + pathAttrLen := uint8(0) + + switch pa.TypeCode { + case OriginAttr: + pathAttrLen = pa.serializeOrigin(buf) + case ASPathAttr: + pathAttrLen = pa.serializeASPath(buf) + case NextHopAttr: + pathAttrLen = pa.serializeNextHop(buf) + case MEDAttr: + pathAttrLen = pa.serializeMED(buf) + case LocalPrefAttr: + pathAttrLen = pa.serializeLocalpref(buf) + case AtomicAggrAttr: + pathAttrLen = pa.serializeAtomicAggregate(buf) + case AggregatorAttr: + pathAttrLen = pa.serializeAggregator(buf) + } + + return pathAttrLen +} + +func (pa *PathAttribute) serializeOrigin(buf *bytes.Buffer) uint8 { + attrFlags := uint8(0) + attrFlags = setTransitive(attrFlags) + buf.WriteByte(attrFlags) + buf.WriteByte(OriginAttr) + length := uint8(1) + buf.WriteByte(length) + buf.WriteByte(pa.Value.(uint8)) + return 4 +} + +func (pa *PathAttribute) serializeASPath(buf *bytes.Buffer) uint8 { + attrFlags := uint8(0) + attrFlags = setTransitive(attrFlags) + buf.WriteByte(attrFlags) + buf.WriteByte(ASPathAttr) + length := uint8(2) + asPath := pa.Value.(ASPath) + for _, segment := range asPath { + buf.WriteByte(segment.Type) + buf.WriteByte(uint8(len(segment.ASNs))) + for _, asn := range segment.ASNs { + buf.Write(convert.Uint16Byte(uint16(asn))) + } + length += 2 + uint8(len(segment.ASNs))*2 + } + + return length +} + +func (pa *PathAttribute) serializeNextHop(buf *bytes.Buffer) uint8 { + attrFlags := uint8(0) + attrFlags = setTransitive(attrFlags) + buf.WriteByte(attrFlags) + buf.WriteByte(NextHopAttr) + length := uint8(4) + buf.WriteByte(length) + addr := pa.Value.([4]byte) + buf.Write(addr[:]) + return 7 +} + +func (pa *PathAttribute) serializeMED(buf *bytes.Buffer) uint8 { + attrFlags := uint8(0) + attrFlags = setOptional(attrFlags) + buf.WriteByte(attrFlags) + buf.WriteByte(MEDAttr) + length := uint8(4) + buf.WriteByte(length) + buf.Write(convert.Uint32Byte(pa.Value.(uint32))) + return 7 +} + +func (pa *PathAttribute) serializeLocalpref(buf *bytes.Buffer) uint8 { + attrFlags := uint8(0) + attrFlags = setTransitive(attrFlags) + buf.WriteByte(attrFlags) + buf.WriteByte(LocalPrefAttr) + length := uint8(4) + buf.WriteByte(length) + buf.Write(convert.Uint32Byte(pa.Value.(uint32))) + return 7 +} + +func (pa *PathAttribute) serializeAtomicAggregate(buf *bytes.Buffer) uint8 { + attrFlags := uint8(0) + attrFlags = setTransitive(attrFlags) + buf.WriteByte(attrFlags) + buf.WriteByte(AtomicAggrAttr) + length := uint8(0) + buf.WriteByte(length) + return 3 +} + +func (pa *PathAttribute) serializeAggregator(buf *bytes.Buffer) uint8 { + attrFlags := uint8(0) + attrFlags = setOptional(attrFlags) + attrFlags = setTransitive(attrFlags) + buf.WriteByte(attrFlags) + buf.WriteByte(AggregatorAttr) + length := uint8(2) + buf.WriteByte(length) + buf.Write(convert.Uint16Byte(pa.Value.(uint16))) + return 5 +} diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index ee4d0fc3f760cef3f81b0dceaaf9a56d262afb66..052c40796d1b720fbba8c394dc3158a38725b369 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -757,3 +757,536 @@ func TestASPathString(t *testing.T) { assert.Equal(t, test.expected, res) } } + +func TestSetOptional(t *testing.T) { + tests := []struct { + name string + input uint8 + expected uint8 + }{ + { + name: "Test #1", + input: 0, + expected: 128, + }, + } + + for _, test := range tests { + res := setOptional(test.input) + if res != test.expected { + t.Errorf("Unexpected result for test %q: %d", test.name, res) + } + } +} + +func TestSetTransitive(t *testing.T) { + tests := []struct { + name string + input uint8 + expected uint8 + }{ + { + name: "Test #1", + input: 0, + expected: 64, + }, + } + + for _, test := range tests { + res := setTransitive(test.input) + if res != test.expected { + t.Errorf("Unexpected result for test %q: %d", test.name, res) + } + } +} + +func TestSetPartial(t *testing.T) { + tests := []struct { + name string + input uint8 + expected uint8 + }{ + { + name: "Test #1", + input: 0, + expected: 32, + }, + } + + for _, test := range tests { + res := setPartial(test.input) + if res != test.expected { + t.Errorf("Unexpected result for test %q: %d", test.name, res) + } + } +} + +func TestSetExtendedLength(t *testing.T) { + tests := []struct { + name string + input uint8 + expected uint8 + }{ + { + name: "Test #1", + input: 0, + expected: 16, + }, + } + + for _, test := range tests { + res := setExtendedLength(test.input) + if res != test.expected { + t.Errorf("Unexpected result for test %q: %d", test.name, res) + } + } +} + +func TestSerializeOrigin(t *testing.T) { + tests := []struct { + name string + input *PathAttribute + expected []byte + expectedLen uint8 + }{ + { + name: "Test #1", + input: &PathAttribute{ + TypeCode: OriginAttr, + Value: uint8(0), // IGP + }, + expectedLen: 4, + expected: []byte{64, 1, 1, 0}, + }, + { + name: "Test #2", + input: &PathAttribute{ + TypeCode: OriginAttr, + Value: uint8(2), // INCOMPLETE + }, + expectedLen: 4, + expected: []byte{64, 1, 1, 2}, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(nil) + n := test.input.serializeOrigin(buf) + if test.expectedLen != n { + t.Errorf("Unexpected length for test %q: %d", test.name, n) + continue + } + + assert.Equal(t, test.expected, buf.Bytes()) + } +} + +func TestSerializeNextHop(t *testing.T) { + tests := []struct { + name string + input *PathAttribute + expected []byte + expectedLen uint8 + }{ + { + name: "Test #1", + input: &PathAttribute{ + TypeCode: NextHopAttr, + Value: [4]byte{100, 110, 120, 130}, + }, + expected: []byte{0, 3, 4, 100, 110, 120, 130}, + expectedLen: 7, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(nil) + n := test.input.serializeNextHop(buf) + if n != test.expectedLen { + t.Errorf("Unexpected length for test %q: %d", test.name, n) + continue + } + + assert.Equal(t, test.expected, buf.Bytes()) + } +} + +func TestSerializeMED(t *testing.T) { + tests := []struct { + name string + input *PathAttribute + expected []byte + expectedLen uint8 + }{ + { + name: "Test #1", + input: &PathAttribute{ + TypeCode: MEDAttr, + Value: uint32(1000), + }, + expected: []byte{ + 128, // Attribute flags + 4, // Type + 4, // Length + 0, 0, 3, 232, // Value = 1000 + }, + expectedLen: 7, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(nil) + n := test.input.serializeMED(buf) + if n != test.expectedLen { + t.Errorf("Unexpected length for test %q: %d", test.name, n) + continue + } + + assert.Equal(t, test.expected, buf.Bytes()) + } +} + +func TestSerializeLocalPref(t *testing.T) { + tests := []struct { + name string + input *PathAttribute + expected []byte + expectedLen uint8 + }{ + { + name: "Test #1", + input: &PathAttribute{ + TypeCode: LocalPrefAttr, + Value: uint32(1000), + }, + expected: []byte{ + 64, // Attribute flags + 5, // Type + 4, // Length + 0, 0, 3, 232, // Value = 1000 + }, + expectedLen: 7, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(nil) + n := test.input.serializeLocalpref(buf) + if n != test.expectedLen { + t.Errorf("Unexpected length for test %q: %d", test.name, n) + continue + } + + assert.Equal(t, test.expected, buf.Bytes()) + } +} + +func TestSerializeAtomicAggregate(t *testing.T) { + tests := []struct { + name string + input *PathAttribute + expected []byte + expectedLen uint8 + }{ + { + name: "Test #1", + input: &PathAttribute{ + TypeCode: AtomicAggrAttr, + }, + expected: []byte{ + 64, // Attribute flags + 6, // Type + 0, // Length + }, + expectedLen: 3, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(nil) + n := test.input.serializeAtomicAggregate(buf) + if n != test.expectedLen { + t.Errorf("Unexpected length for test %q: %d", test.name, n) + continue + } + + assert.Equal(t, test.expected, buf.Bytes()) + } +} + +func TestSerializeAggregator(t *testing.T) { + tests := []struct { + name string + input *PathAttribute + expected []byte + expectedLen uint8 + }{ + { + name: "Test #1", + input: &PathAttribute{ + TypeCode: AggregatorAttr, + Value: uint16(174), + }, + expected: []byte{ + 192, // Attribute flags + 7, // Type + 2, // Length + 0, 174, // Value = 174 + }, + expectedLen: 5, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(nil) + n := test.input.serializeAggregator(buf) + if n != test.expectedLen { + t.Errorf("Unexpected length for test %q: %d", test.name, n) + continue + } + + assert.Equal(t, test.expected, buf.Bytes()) + } +} + +func TestSerializeASPath(t *testing.T) { + tests := []struct { + name string + input *PathAttribute + expected []byte + expectedLen uint8 + }{ + { + name: "Test #1", + input: &PathAttribute{ + TypeCode: ASPathAttr, + Value: ASPath{ + { + Type: 2, // Sequence + ASNs: []uint32{ + 100, 200, 210, + }, + }, + }, + }, + expected: []byte{ + 64, // Attribute flags + 2, // Type + 2, // AS_SEQUENCE + 3, // ASN count + 0, 100, // ASN 100 + 0, 200, // ASN 200 + 0, 210, // ASN 210 + }, + expectedLen: 10, + }, + } + + for _, test := range tests { + buf := bytes.NewBuffer(nil) + n := test.input.serializeASPath(buf) + if n != test.expectedLen { + t.Errorf("Unexpected length for test %q: %d", test.name, n) + continue + } + + assert.Equal(t, test.expected, buf.Bytes()) + } +} + +func TestSerialize(t *testing.T) { + tests := []struct { + name string + msg *BGPUpdate + expected []byte + wantFail bool + }{ + { + name: "Withdraw only", + msg: &BGPUpdate{ + WithdrawnRoutes: &NLRI{ + IP: [4]byte{100, 110, 120, 0}, + Pfxlen: 24, + }, + }, + expected: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0, 27, // Length + 2, // Msg Type + 0, 4, // Withdrawn Routes Length + 24, 100, 110, 120, // NLRI + 0, 0, // Total Path Attribute Length + }, + }, + { + name: "NLRI only", + msg: &BGPUpdate{ + NLRI: &NLRI{ + IP: [4]byte{100, 110, 128, 0}, + Pfxlen: 17, + }, + }, + expected: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0, 27, // Length + 2, // Msg Type + 0, 0, // Withdrawn Routes Length + 0, 0, // Total Path Attribute Length + 17, 100, 110, 128, // NLRI + }, + }, + { + name: "Path Attributes only", + msg: &BGPUpdate{ + PathAttributes: &PathAttribute{ + Optional: true, + Transitive: true, + TypeCode: OriginAttr, + Value: uint8(0), // IGP + }, + }, + expected: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0, 27, // Length + 2, // Msg Type + 0, 0, // Withdrawn Routes Length + 0, 4, // Total Path Attribute Length + 64, // Attr. Flags + 1, // Attr. Type Code + 1, // Length + 0, // Value + }, + }, + { + name: "Full test", + msg: &BGPUpdate{ + WithdrawnRoutes: &NLRI{ + IP: [4]byte{10, 0, 0, 0}, + Pfxlen: 8, + Next: &NLRI{ + IP: [4]byte{192, 168, 0, 0}, + Pfxlen: 16, + }, + }, + PathAttributes: &PathAttribute{ + TypeCode: OriginAttr, + Value: uint8(0), + Next: &PathAttribute{ + TypeCode: ASPathAttr, + Value: ASPath{ + { + Type: 2, + ASNs: []uint32{100, 155, 200}, + }, + { + Type: 1, + ASNs: []uint32{10, 20}, + }, + }, + Next: &PathAttribute{ + TypeCode: NextHopAttr, + Value: [4]byte{10, 20, 30, 40}, + Next: &PathAttribute{ + TypeCode: MEDAttr, + Value: uint32(100), + Next: &PathAttribute{ + TypeCode: LocalPrefAttr, + Value: uint32(500), + Next: &PathAttribute{ + TypeCode: AtomicAggrAttr, + Next: &PathAttribute{ + TypeCode: AggregatorAttr, + Value: uint16(200), + }, + }, + }, + }, + }, + }, + }, + NLRI: &NLRI{ + IP: [4]byte{8, 8, 8, 0}, + Pfxlen: 24, + Next: &NLRI{ + IP: [4]byte{185, 65, 240, 0}, + Pfxlen: 22, + }, + }, + }, + expected: []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0, 85, // Length + 2, // Msg Type + + // Withdraws + 0, 5, // Withdrawn Routes Length + 8, 10, // Withdraw 10/8 + 16, 192, 168, // Withdraw 192.168/16 + + 0, 49, // Total Path Attribute Length + + // ORIGIN + 64, // Attr. Flags + 1, // Attr. Type Code + 1, // Length + 0, // Value + // ASPath + 64, // Attr. Flags + 2, // Attr. Type Code + 2, // Path Segment Type = AS_SEQUENCE + 3, // Path Segment Length + 0, 100, 0, 155, 0, 200, // ASNs + 1, // Path Segment Type = AS_SET + 2, // Path Segment Type = AS_SET + 0, 10, 0, 20, // ASNs + // Next Hop + 64, // Attr. Flags + 3, // Attr. Type Code + 4, // Length + 10, 20, 30, 40, // Next Hop Address + // MED + 128, // Attr. Flags + 4, // Attr Type Code + 4, // Length + 0, 0, 0, 100, // MED = 100 + // LocalPref + 64, // Attr. Flags + 5, // Attr. Type Code + 4, // Length + 0, 0, 1, 244, // Localpref + // Atomic Aggregate + 64, // Attr. Flags + 6, // Attr. Type Code + 0, // Length + // Aggregator + 192, // Attr. Flags + 7, // Attr. Type Code + 2, // Length + 0, 200, // Aggregator ASN = 200 + + // NLRI + 24, 8, 8, 8, // 8.8.8.0/24 + 22, 185, 65, 240, // 185.65.240.0/22 + }, + }, + } + + for _, test := range tests { + res, err := test.msg.SerializeUpdate() + 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) + } +}