From 5bf9d69aef51402dc9fe4355bf57f1220a333fe6 Mon Sep 17 00:00:00 2001 From: Daniel Czerwonk <daniel@dan-nrw.de> Date: Thu, 31 May 2018 20:48:32 +0200 Subject: [PATCH] community convert methods, decoding --- protocols/bgp/packet/bgp.go | 1 + protocols/bgp/packet/community.go | 37 ++++++++ protocols/bgp/packet/community_test.go | 93 ++++++++++++++++++++ protocols/bgp/packet/large_community.go | 2 +- protocols/bgp/packet/path_attributes.go | 31 ++++--- protocols/bgp/packet/path_attributes_test.go | 59 +++++++++++++ 6 files changed, 209 insertions(+), 14 deletions(-) create mode 100644 protocols/bgp/packet/community.go create mode 100644 protocols/bgp/packet/community_test.go diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go index b32980db..4835484d 100644 --- a/protocols/bgp/packet/bgp.go +++ b/protocols/bgp/packet/bgp.go @@ -11,6 +11,7 @@ const ( MinLen = 19 MaxLen = 4096 NLRIMaxLen = 5 + CommunityLen = 4 LargeCommunityLen = 12 OpenMsg = 1 diff --git a/protocols/bgp/packet/community.go b/protocols/bgp/packet/community.go new file mode 100644 index 00000000..e97d6788 --- /dev/null +++ b/protocols/bgp/packet/community.go @@ -0,0 +1,37 @@ +package packet + +import ( + "fmt" + "strconv" + "strings" +) + +func CommunityString(v uint32) string { + e1 := v >> 16 + e2 := v - e1<<16 + + return fmt.Sprintf("(%d,%d)", e1, e2) +} + +func ParseCommunityString(s string) (uint32, error) { + s = strings.Trim(s, "()") + t := strings.Split(s, ",") + + if len(t) != 2 { + return 0, fmt.Errorf("can not parse community %s", s) + } + + v, err := strconv.ParseUint(t[0], 10, 16) + if err != nil { + return 0, err + } + e1 := uint32(v) + + v, err = strconv.ParseUint(t[1], 10, 16) + if err != nil { + return 0, err + } + e2 := uint16(v) + + return e1<<16 + uint32(e2), nil +} diff --git a/protocols/bgp/packet/community_test.go b/protocols/bgp/packet/community_test.go new file mode 100644 index 00000000..06a22689 --- /dev/null +++ b/protocols/bgp/packet/community_test.go @@ -0,0 +1,93 @@ +package packet + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCommunityString(t *testing.T) { + tests := []struct { + name string + value uint32 + expected string + }{ + { + name: "both elements", + value: 131080, + expected: "(2,8)", + }, + { + name: "right element only", + value: 250, + expected: "(0,250)", + }, + { + name: "left element only", + value: 131072, + expected: "(2,0)", + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + assert.Equal(te, test.expected, CommunityString(test.value)) + }) + } +} + +func TestParseCommunityString(t *testing.T) { + tests := []struct { + name string + value string + expected uint32 + wantFail bool + }{ + { + name: "both elements", + expected: 131080, + value: "(2,8)", + }, + { + name: "right element only", + expected: 250, + value: "(0,250)", + }, + { + name: "left element only", + expected: 131072, + value: "(2,0)", + }, + { + name: "too big", + value: "(131072,256)", + wantFail: true, + }, + { + name: "empty string", + value: "", + wantFail: true, + }, + { + name: "random string", + value: "foo-bar", + wantFail: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(te *testing.T) { + c, err := ParseCommunityString(test.value) + + if test.wantFail { + if err == nil { + te.Fatal("test was expected to fail, but did not") + } + + return + } + + assert.Equal(te, test.expected, c) + }) + } +} diff --git a/protocols/bgp/packet/large_community.go b/protocols/bgp/packet/large_community.go index 135c8639..5b3f7788 100644 --- a/protocols/bgp/packet/large_community.go +++ b/protocols/bgp/packet/large_community.go @@ -16,7 +16,7 @@ func (c LargeCommunity) String() string { return fmt.Sprintf("(%d,%d,%d)", c.GlobalAdministrator, c.DataPart1, c.DataPart2) } -func ParseCommunityString(s string) (com LargeCommunity, err error) { +func ParseLargeCommunityString(s string) (com LargeCommunity, err error) { s = strings.Trim(s, "()") t := strings.Split(s, ",") diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go index 055a85e2..8a13ec64 100644 --- a/protocols/bgp/packet/path_attributes.go +++ b/protocols/bgp/packet/path_attributes.go @@ -244,13 +244,15 @@ func (pa *PathAttribute) decodeAggregator(buf *bytes.Buffer) error { } func (pa *PathAttribute) decodeCommunities(buf *bytes.Buffer) error { - if pa.Length%4 != 0 { + if pa.Length%CommunityLen != 0 { return fmt.Errorf("Unable to read community path attribute length %d is not divisible by 4", pa.Length) } - comNumber := pa.Length / 4 - var com = make([]uint32, comNumber) - for i := uint16(0); i < comNumber; i++ { - c := [4]byte{} + + count := pa.Length / CommunityLen + coms := make([]uint32, count) + + for i := uint16(0); i < count; i++ { + c := [CommunityLen]byte{} n, err := buf.Read(c[:]) if err != nil { return err @@ -258,16 +260,21 @@ func (pa *PathAttribute) decodeCommunities(buf *bytes.Buffer) error { if n != 4 { return fmt.Errorf("Unable to read next hop: buf.Read read %d bytes", n) } - com[i] = fourBytesToUint32(c) + + v := fourBytesToUint32(c) + coms[i] = v } - pa.Value = com + + pa.Value = coms return nil } func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error { - length := pa.Length - count := length / LargeCommunityLen + if pa.Length%LargeCommunityLen != 0 { + return fmt.Errorf("Unable to read large community path attribute. Length %d is not divisible by 12", pa.Length) + } + count := pa.Length / LargeCommunityLen coms := make([]LargeCommunity, count) for i := uint16(0); i < count; i++ { @@ -295,9 +302,7 @@ func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error { } pa.Value = coms - - dump := pa.Length - (count * LargeCommunityLen) - return dumpNBytes(buf, dump) + return nil } func (pa *PathAttribute) decodeAS4Path(buf *bytes.Buffer) error { @@ -646,7 +651,7 @@ func LargeCommunityAttributeForString(s string) (*PathAttribute, error) { var err error for i, str := range strs { - coms[i], err = ParseCommunityString(str) + coms[i], err = ParseLargeCommunityString(str) if err != nil { return nil, err } diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go index 0e049760..70599584 100644 --- a/protocols/bgp/packet/path_attributes_test.go +++ b/protocols/bgp/packet/path_attributes_test.go @@ -657,6 +657,65 @@ func TestDecodeLargeCommunity(t *testing.T) { } } +func TestDecodeCommunity(t *testing.T) { + tests := []struct { + name string + input []byte + wantFail bool + explicitLength uint16 + expected *PathAttribute + }{ + { + name: "two valid communities", + input: []byte{ + 0, 2, 0, 8, 1, 0, 4, 1, // (2,8), (256,1025) + }, + wantFail: false, + expected: &PathAttribute{ + Length: 8, + Value: []uint32{ + 131080, 16777216 + 1025, + }, + }, + }, + { + name: "Empty input", + input: []byte{}, + wantFail: false, + expected: &PathAttribute{ + Length: 0, + Value: []uint32{}, + }, + }, + } + + for _, test := range tests { + l := uint16(len(test.input)) + if test.explicitLength != 0 { + l = test.explicitLength + } + pa := &PathAttribute{ + Length: l, + } + err := pa.decodeCommunities(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 -- GitLab