Skip to content
Snippets Groups Projects
Commit cde9a80f authored by Oliver Herms's avatar Oliver Herms
Browse files

Implemented BGP update serialization

parent e2cf0208
No related branches found
No related tags found
No related merge requests found
......@@ -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
......
......@@ -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
}
......@@ -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))
}
......@@ -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)
}
}
......@@ -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
}
......@@ -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
}
......@@ -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)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment