diff --git a/go.mod b/go.mod index 9995a54d..fd5f8320 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ require ( github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect github.com/bio-routing/tflow2 v0.0.0-20181230153523-2e308a4a3c3a + github.com/gogo/protobuf v1.3.0 // indirect github.com/golang/protobuf v1.4.0 + github.com/google/gopacket v1.1.17 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/pkg/errors v0.8.0 @@ -15,12 +17,11 @@ require ( github.com/urfave/cli v1.21.0 github.com/vishvananda/netlink v1.0.0 github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect - golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect + golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect golang.org/x/text v0.3.2 // indirect google.golang.org/genproto v0.0.0-20200413115906-b5235f65be36 // indirect google.golang.org/grpc v1.28.0 - google.golang.org/protobuf v1.21.0 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 1119a158..53f79435 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -48,12 +50,16 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY= +github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -69,7 +75,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -108,8 +113,10 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= @@ -125,6 +132,8 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998= +golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -133,6 +142,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/protocols/ospf/packetv3/common.go b/protocols/ospf/packetv3/common.go new file mode 100644 index 00000000..e00b5b95 --- /dev/null +++ b/protocols/ospf/packetv3/common.go @@ -0,0 +1,92 @@ +package packetv3 + +import ( + "bytes" + "encoding/binary" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/tflow2/convert" + "github.com/pkg/errors" +) + +// Serializable represents any packet which can be serialized +// to bytes to be on the wire +type Serializable interface { + Serialize(buf *bytes.Buffer) +} + +// ID is a common type used for 32-bit IDs in OSPF +type ID uint32 + +func DeserializeID(buf *bytes.Buffer) (ID, int, error) { + var id uint32 + if err := binary.Read(buf, binary.BigEndian, &id); err != nil { + return ID(id), 0, errors.Wrap(err, "unable to read ID from buffer") + } + return ID(id), 4, nil +} + +func (i ID) Serialize(buf *bytes.Buffer) { + buf.Write(convert.Uint32Byte(uint32(i))) +} + +// bitmasks for flags in RouterOptions +const ( + RouterOptV6 uint16 = 1 << iota + RouterOptE + _ + RouterOptN + RouterOptR + RouterOptDC + _ + _ + RouterOptAF +) + +type RouterOptions struct { + _ uint8 + Flags uint16 +} + +func OptionsFromFlags(flags ...uint16) RouterOptions { + opts := RouterOptions{Flags: 0} + for _, flag := range flags { + opts.Flags = opts.Flags | flag + } + return opts +} + +func (r RouterOptions) HasFlag(flag uint16) bool { + return r.Flags&flag != 0 +} + +func (r RouterOptions) SetFlag(flag uint16) RouterOptions { + return RouterOptions{ + Flags: r.Flags | flag, + } +} + +func (r *RouterOptions) Serialize(buf *bytes.Buffer) { + buf.WriteByte(0) + buf.Write(convert.Uint16Byte(uint16(r.Flags))) +} + +type deserializableIP struct { + Higher uint64 + Lower uint64 +} + +func (ip deserializableIP) ToNetIP() net.IP { + return net.IPv6(ip.Higher, ip.Lower) +} + +func serializeIPv6(ip net.IP, buf *bytes.Buffer) { + if ip.IsIPv4() { + for i := 0; i < 16; i++ { + buf.WriteByte(0) + } + return + } + + buf.Write(ip.Bytes()) +} diff --git a/protocols/ospf/packetv3/decode_test.go b/protocols/ospf/packetv3/decode_test.go new file mode 100644 index 00000000..30b20bb4 --- /dev/null +++ b/protocols/ospf/packetv3/decode_test.go @@ -0,0 +1,980 @@ +package packetv3_test + +import ( + "bytes" + "testing" + + "github.com/bio-routing/bio-rd/net" + ospf "github.com/bio-routing/bio-rd/protocols/ospf/packetv3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type decodeTest struct { + name string + input []byte + wantFail bool + expected *ospf.OSPFv3Message +} + +func runDecodeTest(t *testing.T, testCase decodeTest, src, dst net.IP) { + t.Run(testCase.name, func(t *testing.T) { + buf := bytes.NewBuffer(testCase.input) + msg, _, err := ospf.DeserializeOSPFv3Message(buf, src, dst) + if testCase.wantFail { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, testCase.expected, msg) + assert.Len(t, testCase.input, int(testCase.expected.PacketLength)) + }) +} + +func routerID(o1, o2, o3, o4 uint8) ospf.ID { + return ospf.ID(net.IPv4FromOctets(o1, o2, o3, o4).Ptr().ToUint32()) +} + +func TestDecodeHello(t *testing.T) { + tests := []decodeTest{ + { + name: "Default", + input: []byte{ + // Header + 3, // Version + 1, // Type: Hello + 0, 36, // Length + 3, 3, 3, 3, // Router ID + 0, 0, 0, 0, // Area ID + 0x94, 0x1c, // Checksum + 0, // Instance ID + 0, // Reserved + + // Payload (Hello) + 0, 0, 0, 6, // Interface ID + 100, // Router Prio + 0, // Reserved + 0, 0x13, // Options: R, E, V6 + 0, 30, // Hello Interval + 0, 120, // Dead Interval + 0, 0, 0, 0, // Designated Router + 0, 0, 0, 0, // Backup Designated Router + }, + expected: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeHello, + Checksum: 0x941c, + PacketLength: 36, + RouterID: routerID(3, 3, 3, 3), + AreaID: 0, + InstanceID: 0, + Body: &ospf.Hello{ + InterfaceID: 6, + RouterPriority: 100, + HelloInterval: 30, + RouterDeadInterval: 120, + Options: ospf.OptionsFromFlags(ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + }, + }, + }, + { + name: "InvalidLength", + input: []byte{ + // Header + 3, // Version + 1, // Type: Hello + 0, 38, // Length (invalid, expecting 36) + 3, 3, 3, 3, // Router ID + 0, 0, 0, 0, // Area ID + 0x94, 0x1a, // Checksum + 0, // Instance ID + 0, // Reserved + + // Payload (20 bytes) + 0, 0, 0, 6, 100, 0, 0, 0x13, 0, 30, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, + }, + wantFail: true, + }, + { + name: "InvalidChecksum", + input: []byte{ + // Header + 3, // Version + 1, // Type: Hello + 0, 36, // Length + 3, 3, 3, 3, // Router ID + 0, 0, 0, 0, // Area ID + 0x94, 0x1d, // Checksum (invalid) + 0, // Instance ID + 0, // Reserved + + // Payload (20 bytes) + 0, 0, 0, 6, 100, 0, 0, 0x13, 0, 30, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, + }, + wantFail: true, + }, + { + name: "WithNeighbors", + input: []byte{ + // Header + 3, // Version + 1, // Type: Hello + 0, 44, // Length + 3, 3, 3, 3, // Router ID + 0, 0, 0, 0, // Area ID + 0x8e, 0x06, // Checksum + 0, // Instance ID + 0, // Reserved + + // Payload (Hello) + 0, 0, 0, 6, // Interface ID + 100, // Router Prio + 0, // Reserved + 0, 0x13, // Options: R, E, V6 + 0, 30, // Hello Interval + 0, 120, // Dead Interval + 0, 0, 0, 0, // Designated Router + 0, 0, 0, 0, // Backup Designated Router + 1, 1, 1, 1, // Neighbor 1 + 2, 2, 2, 2, // Neighbor 2 + }, + expected: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeHello, + Checksum: 0x8e06, + PacketLength: 44, + RouterID: routerID(3, 3, 3, 3), + AreaID: 0, + InstanceID: 0, + Body: &ospf.Hello{ + InterfaceID: 6, + RouterPriority: 100, + HelloInterval: 30, + RouterDeadInterval: 120, + Options: ospf.OptionsFromFlags(ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + Neighbors: []ospf.ID{ + routerID(1, 1, 1, 1), + routerID(2, 2, 2, 2), + }, + }, + }, + }, + { + name: "WithDR", + input: []byte{ + // Header + 3, // Version + 1, // Type: Hello + 0, 44, // Length + 3, 3, 3, 3, // Router ID + 0, 0, 0, 0, // Area ID + 0x8c, 0x04, // Checksum + 0, // Instance ID + 0, // Reserved + + // Payload (Hello) + 0, 0, 0, 6, // Interface ID + 100, // Router Prio + 0, // Reserved + 0, 0x13, // Options: R, E, V6 + 0, 30, // Hello Interval + 0, 120, // Dead Interval + 1, 1, 1, 1, // Designated Router + 0, 0, 0, 0, // Backup Designated Router + 1, 1, 1, 1, // Neighbor 1 + 2, 2, 2, 2, // Neighbor 2 + }, + expected: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeHello, + Checksum: 0x8c04, + PacketLength: 44, + RouterID: routerID(3, 3, 3, 3), + AreaID: 0, + InstanceID: 0, + Body: &ospf.Hello{ + InterfaceID: 6, + RouterPriority: 100, + HelloInterval: 30, + RouterDeadInterval: 120, + Options: ospf.OptionsFromFlags(ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + DesignatedRouterID: routerID(1, 1, 1, 1), + Neighbors: []ospf.ID{ + routerID(1, 1, 1, 1), + routerID(2, 2, 2, 2), + }, + }, + }, + }, + { + name: "WithBDR", + input: []byte{ + // Header + 3, // Version + 1, // Type: Hello + 0, 44, // Length + 3, 3, 3, 3, // Router ID + 0, 0, 0, 0, // Area ID + 0x88, 0x00, // Checksum + 0, // Instance ID + 0, // Reserved + + // Payload (Hello) + 0, 0, 0, 6, // Interface ID + 100, // Router Prio + 0, // Reserved + 0, 0x13, // Options: R, E, V6 + 0, 30, // Hello Interval + 0, 120, // Dead Interval + 1, 1, 1, 1, // Designated Router + 2, 2, 2, 2, // Backup Designated Router + 1, 1, 1, 1, // Neighbor 1 + 2, 2, 2, 2, // Neighbor 2 + }, + expected: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeHello, + Checksum: 0x8800, + PacketLength: 44, + RouterID: routerID(3, 3, 3, 3), + AreaID: 0, + InstanceID: 0, + Body: &ospf.Hello{ + InterfaceID: 6, + RouterPriority: 100, + HelloInterval: 30, + RouterDeadInterval: 120, + Options: ospf.OptionsFromFlags(ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + DesignatedRouterID: routerID(1, 1, 1, 1), + BackupDesignatedRouterID: routerID(2, 2, 2, 2), + Neighbors: []ospf.ID{ + routerID(1, 1, 1, 1), + routerID(2, 2, 2, 2), + }, + }, + }, + }, + } + + src, err := net.IPFromString("fe80::3") + require.NoError(t, err) + dst, err := net.IPFromString("ff02::5") + require.NoError(t, err) + + for _, test := range tests { + runDecodeTest(t, test, src, dst) + } +} + +func TestDecodeDBDesc(t *testing.T) { + tests := []decodeTest{ + { + name: "Default", + input: []byte{ + // Header + 0x03, // Version + 0x02, // Type: Database Description + 0x00, 0x1c, // Length + 0x03, 0x03, 0x03, 0x03, // Router ID + 0x00, 0x00, 0x00, 0x00, // Area ID + 0xe7, 0xad, // Checksum + 0x00, // Instance ID + 0x00, // Reserved + + // Payload + 0x00, // Reserved + 0x00, 0x00, 0x13, // Options + 0x05, 0xdc, // MTU + 0x00, // Reserved + 0x07, // Description Flags + 0x00, 0x00, 0x0b, 0xbd, // Sequence Number + }, + expected: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeDatabaseDescription, + Checksum: 0xe7ad, + PacketLength: 28, + RouterID: routerID(3, 3, 3, 3), + AreaID: 0, + InstanceID: 0, + Body: &ospf.DatabaseDescription{ + Options: ospf.OptionsFromFlags(ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + InterfaceMTU: 1500, + DBFlags: ospf.DBFlagInit | ospf.DBFlagMore | ospf.DBFlagMS, + DDSequenceNumber: 3005, + }, + }, + }, + { + name: "WithLSAs", + input: []byte{ + // Header + 0x03, // Version + 0x02, // Type: Database Description + 0x00, 0xbc, // Length + 0x01, 0x01, 0x01, 0x01, // Router ID + 0x00, 0x00, 0x00, 0x00, // Area ID + 0xb6, 0xd0, // Checksum + 0x00, // Instance ID + 0x00, // Reserved + + // Payload + 0x00, // Reserved + 0x00, 0x00, 0x13, // Options + 0x05, 0xdc, // Link MTU + 0x00, // Reserved + 0x02, // Flags + 0x00, 0x00, 0x0b, 0xbd, // Seq Num + + // LSA Header + 0x00, 0x1d, + 0x20, 0x01, // Type: Router-LSA + 0x00, 0x00, 0x00, 0x00, // LS ID + 0x01, 0x01, 0x01, 0x01, // Router ID + 0x80, 0x00, 0x00, 0x12, // Seq Num + 0xb1, 0x4a, // Checksum + 0x00, 0x18, // Length + + // LSA Header + 0x01, 0xb4, + 0x20, 0x01, // Type: Router-LSA + 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x02, 0x02, + 0x80, 0x00, 0x00, 0x0f, + 0x02, 0x8e, + 0x00, 0x28, + + // LSA Header: Network-LSA + 0x01, 0xdc, 0x20, 0x02, 0x00, 0x00, 0x00, 0x06, + 0x03, 0x03, 0x03, 0x03, 0x80, 0x00, 0x00, 0x02, + 0x6d, 0x6c, 0x00, 0x24, + + // LSA-Header: Inter-Area-Prefix-LSA + 0x00, 0x1e, 0x20, 0x03, 0x00, 0x00, 0x00, 0x05, + 0x01, 0x01, 0x01, 0x01, 0x80, 0x00, 0x00, 0x01, + 0xdb, 0x0f, 0x00, 0x24, + + // LSA-Header: Inter-Area-Prefix-LSA + 0x03, 0x2a, 0x20, 0x03, 0x00, 0x00, 0x00, 0x04, + 0x02, 0x02, 0x02, 0x02, 0x80, 0x00, 0x00, 0x01, + 0xc7, 0x20, 0x00, 0x24, + + // LSA-Header: Link-LSA + 0x00, 0x1d, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, + 0x01, 0x01, 0x01, 0x01, 0x80, 0x00, 0x00, 0x01, + 0x86, 0xd0, 0x00, 0x38, + + // LSA-Header: Intra-Area-Prefix-LSA + 0x00, 0x1d, 0x20, 0x09, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x80, 0x00, 0x00, 0x01, + 0x74, 0x18, 0x00, 0x34, + + // LSA-Header: Unknown type + 0x00, 0x1d, + 0x20, 0x22, // Type: Unknown + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, + 0x80, 0x00, 0x00, 0x01, + 0x74, 0x18, 0x00, 0x34, + }, + expected: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeDatabaseDescription, + Checksum: 0xb6d0, + PacketLength: 188, + RouterID: routerID(1, 1, 1, 1), + AreaID: 0, + InstanceID: 0, + Body: &ospf.DatabaseDescription{ + Options: ospf.OptionsFromFlags(ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + InterfaceMTU: 1500, + DBFlags: ospf.DBFlagMore, + DDSequenceNumber: 3005, + LSAHeaders: []*ospf.LSA{ + { + Type: ospf.LSATypeRouter, + Age: 29, + ID: 0, + AdvertisingRouter: routerID(1, 1, 1, 1), + SequenceNumber: 0x80000012, + Checksum: 0xb14a, + Length: 0x18, + }, + { + Type: ospf.LSATypeRouter, + Age: 436, + ID: 0, + AdvertisingRouter: routerID(2, 2, 2, 2), + SequenceNumber: 0x8000000f, + Checksum: 0x028e, + Length: 0x28, + }, + { + Type: ospf.LSATypeNetwork, + Age: 476, + ID: 6, + AdvertisingRouter: routerID(3, 3, 3, 3), + SequenceNumber: 0x80000002, + Checksum: 0x6d6c, + Length: 0x24, + }, + { + Type: ospf.LSATypeInterAreaPrefix, + Age: 30, + ID: 5, + AdvertisingRouter: routerID(1, 1, 1, 1), + SequenceNumber: 0x80000001, + Checksum: 0xdb0f, + Length: 0x24, + }, + { + Type: ospf.LSATypeInterAreaPrefix, + Age: 0x032a, + ID: 4, + AdvertisingRouter: routerID(2, 2, 2, 2), + SequenceNumber: 0x80000001, + Checksum: 0xc720, + Length: 0x24, + }, + { + Type: ospf.LSATypeLink, + Age: 0x001d, + ID: 6, + AdvertisingRouter: routerID(1, 1, 1, 1), + SequenceNumber: 0x80000001, + Checksum: 0x86d0, + Length: 0x38, + }, + { + Type: ospf.LSATypeIntraAreaPrefix, + Age: 0x001d, + ID: 0, + AdvertisingRouter: routerID(1, 1, 1, 1), + SequenceNumber: 0x80000001, + Checksum: 0x7418, + Length: 0x34, + }, + { + Type: 0x2022, // Unknown + Age: 0x001d, + ID: 0, + AdvertisingRouter: routerID(1, 1, 1, 1), + SequenceNumber: 0x80000001, + Checksum: 0x7418, + Length: 0x34, + }, + }, + }, + }, + }, + } + + src, err := net.IPFromString("fe80::3") + require.NoError(t, err) + dst, err := net.IPFromString("fe80::1") + require.NoError(t, err) + + for _, test := range tests { + runDecodeTest(t, test, src, dst) + } +} + +func TestDecodeLSRequest(t *testing.T) { + tests := []decodeTest{ + { + name: "Default", + input: []byte{ + // Header + 0x03, // Version + 0x03, // Type + 0x00, 0x34, // Length + 0x03, 0x03, 0x03, 0x03, // Router ID + 0x00, 0x00, 0x00, 0x00, // Area ID + 0x8b, 0x13, // Checksum + 0x00, // Instance ID + 0x00, // Reserved + + // LS Request + 0x00, 0x00, // Reserved + 0x20, 0x01, // Type + 0x00, 0x00, 0x00, 0x00, // Link State ID + 0x01, 0x01, 0x01, 0x01, // Advertising Router + + // LS Request + 0x00, 0x00, + 0x20, 0x02, + 0x00, 0x00, 0x00, 0x06, + 0x03, 0x03, 0x03, 0x03, + + // LS Request + 0x00, 0x00, + 0x20, 0x03, + 0x00, 0x00, 0x00, 0x02, + 0x03, 0x03, 0x03, 0x03, + }, + expected: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeLinkStateRequest, + Checksum: 0x8b13, + PacketLength: 52, + RouterID: routerID(3, 3, 3, 3), + AreaID: 0, + InstanceID: 0, + Body: ospf.LinkStateRequestMsg{ + { + LSType: ospf.LSATypeRouter, + LinkStateID: 0, + AdvertisingRouter: routerID(1, 1, 1, 1), + }, + { + LSType: ospf.LSATypeNetwork, + LinkStateID: 6, + AdvertisingRouter: routerID(3, 3, 3, 3), + }, + { + LSType: ospf.LSATypeInterAreaPrefix, + LinkStateID: 2, + AdvertisingRouter: routerID(3, 3, 3, 3), + }, + }, + }, + }, + } + + src, err := net.IPFromString("fe80::3") + require.NoError(t, err) + dst, err := net.IPFromString("fe80::1") + require.NoError(t, err) + + for _, test := range tests { + runDecodeTest(t, test, src, dst) + } +} + +func mustIP(ip net.IP, err error) net.IP { + if err != nil { + panic(err) + } + return ip +} + +func TestDecodeLSUpdate(t *testing.T) { + tests := []decodeTest{ + { + name: "Default", + input: []byte{ + // Header + 0x03, // Version + 0x04, // Type: LS Update + 0x00, 0xf0, // Length + 0x01, 0x01, 0x01, 0x01, // Router ID + 0x00, 0x00, 0x00, 0x00, // Area ID + 0xa6, 0x81, // Checksum + 0x00, // Instance ID + 0x00, // Reserved + + // Payload + 0x00, 0x00, 0x00, 0x05, // Num of Updates + + // Router LSA + 0x00, 0x01, // Age + 0x20, 0x01, // Type: Router + 0x00, 0x00, 0x00, 0x00, // Link State ID + 0x01, 0x01, 0x01, 0x01, // Router ID + 0x80, 0x00, 0x00, 0x13, // Seq Num + 0x11, 0x80, // Checksum + 0x00, 0x28, // Length + 0x01, // Flags + 0x00, 0x00, 0x33, // Options + // Interface #1 + 0x01, // Type: PTP + 0x00, // Reserved + 0x00, 0x40, // Metric + 0x00, 0x00, 0x00, 0x06, // Interface ID + 0x00, 0x00, 0x00, 0x06, // Neighbor Interface ID + 0x03, 0x03, 0x03, 0x03, // Neighbor Router ID + + // Inter-Area-Prefix LSA + 0x00, 0x24, // Age + 0x20, 0x03, // Type: Inter-Area-Prefix + 0x00, 0x00, 0x00, 0x05, // LS ID + 0x03, 0x03, 0x03, 0x03, // Router ID + 0x80, 0x00, 0x00, 0x01, // Seq Num + 0x06, 0xba, // Checksum + 0x00, 0x24, // Length + 0x00, // Reserved + 0x00, 0x00, 0x0a, // Metric + 0x40, // Prefix Length + 0x00, // Prefix Options + 0x00, 0x00, //Reserved + // Address + 0x20, 0x01, 0x0d, 0xb8, + 0x00, 0x00, 0x00, 0x34, + + // Link LSA + 0x00, 0x23, // Ags + 0x00, 0x08, // Type: Link + 0x00, 0x00, 0x00, 0x06, // LS ID + 0x03, 0x03, 0x03, 0x03, // Router ID + 0x80, 0x00, 0x00, 0x01, // Seq Num + 0xa0, 0x49, // Checksum + 0x00, 0x38, // Length + 0x64, // Router Priority + 0x00, 0x00, 0x33, // Options + // Link local addr + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x01, // Num prefixes + // LSA Prefix + 0x40, 0x00, 0x00, 0x00, // Len & Opts + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, // Addr + + // Intra-Area-Prefix LSA + 0x00, 0x23, // Age + 0x20, 0x09, // Type: Intra Area Prefix + 0x00, 0x00, 0x00, 0x00, // LS ID + 0x03, 0x03, 0x03, 0x03, // Router ID + 0x80, 0x00, 0x00, 0x01, // Seq Num + 0xe0, 0x99, // Checksum + 0x00, 0x34, // Length + 0x00, 0x01, // Num prefixes + 0x20, 0x01, // Referenced type + 0x00, 0x00, 0x00, 0x00, // Referenced ID + 0x03, 0x03, 0x03, 0x03, // Referenced Router + 0x80, // Pfx Len + 0x02, // Pfx Opts + 0x00, 0x00, // Metric + // Pfx Addr + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + + // Network LSA + 0x0e, 0x10, // Age + 0x20, 0x02, // Type: Network + 0x00, 0x00, 0x00, 0x06, // LS ID + 0x03, 0x03, 0x03, 0x03, // Router ID + 0x80, 0x00, 0x00, 0x03, // Seq Num + 0x6b, 0x6d, // Checksum + 0x00, 0x24, // Length + 0x00, // Reserved + 0x00, 0x00, 0x33, // Options + // Attached Routers + 0x03, 0x03, 0x03, 0x03, + 0x02, 0x02, 0x02, 0x02, + 0x01, 0x01, 0x01, 0x01, + }, + expected: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeLinkStateUpdate, + Checksum: 0xa681, + PacketLength: 240, + RouterID: routerID(1, 1, 1, 1), + AreaID: 0, + InstanceID: 0, + Body: ospf.LinkStateUpdate{ + { + Age: 1, + Type: ospf.LSATypeRouter, + ID: 0, + AdvertisingRouter: routerID(1, 1, 1, 1), + SequenceNumber: 0x80000013, + Checksum: 0x1180, + Length: 40, + Body: &ospf.RouterLSA{ + Flags: ospf.RouterLSAFlagsFrom(ospf.RouterLSAFlagBorder), + Options: ospf.OptionsFromFlags( + ospf.RouterOptDC, ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6, + ), + LinkDescriptions: []ospf.AreaLinkDescription{ + { + Type: ospf.ALDTypePTP, + Metric: 64, + InterfaceID: 6, + NeighborInterfaceID: 6, + NeighborRouterID: routerID(3, 3, 3, 3), + }, + }, + }, + }, + { + Age: 0x24, + Type: ospf.LSATypeInterAreaPrefix, + ID: 5, + AdvertisingRouter: routerID(3, 3, 3, 3), + SequenceNumber: 0x80000001, + Checksum: 0x06ba, + Length: 0x24, + Body: &ospf.InterAreaPrefixLSA{ + Metric: 10, + Prefix: ospf.LSAPrefix{ + PrefixLength: 64, + Options: ospf.PrefixOptions{}, + Address: mustIP(net.IPFromString("2001:db8:0:34::")), + }, + }, + }, + { + Age: 0x23, + Type: ospf.LSATypeLink, + ID: 6, + AdvertisingRouter: routerID(3, 3, 3, 3), + SequenceNumber: 0x80000001, + Checksum: 0xa049, + Length: 0x38, + Body: &ospf.LinkLSA{ + RouterPriority: 100, + Options: ospf.OptionsFromFlags( + ospf.RouterOptDC, ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + LinkLocalInterfaceAddress: mustIP(net.IPFromString("fe80::3")), + PrefixNum: 1, + Prefixes: []ospf.LSAPrefix{ + { + PrefixLength: 64, + Options: ospf.PrefixOptions{}, + Address: mustIP(net.IPFromString("2001:db8::")), + }, + }, + }, + }, + { + Age: 0x23, + Type: ospf.LSATypeIntraAreaPrefix, + ID: 0, + AdvertisingRouter: routerID(3, 3, 3, 3), + SequenceNumber: 0x80000001, + Checksum: 0xe099, + Length: 0x34, + Body: &ospf.IntraAreaPrefixLSA{ + ReferencedLSType: ospf.LSATypeRouter, + ReferencedLinkStateID: 0, + ReferencedAdvertisingRouter: routerID(3, 3, 3, 3), + Prefixes: []ospf.LSAPrefix{ + { + PrefixLength: 128, + Options: ospf.PrefixOptions{ + LocalAddress: true, + }, + Special: 0, // Metric + Address: mustIP(net.IPFromString("2001:db8::3")), + }, + }, + }, + }, + { + Age: 0xe10, + Type: ospf.LSATypeNetwork, + ID: 6, + AdvertisingRouter: routerID(3, 3, 3, 3), + SequenceNumber: 0x80000003, + Checksum: 0x6b6d, + Length: 0x24, + Body: &ospf.NetworkLSA{ + Options: ospf.OptionsFromFlags( + ospf.RouterOptDC, ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + AttachedRouter: []ospf.ID{ + routerID(3, 3, 3, 3), + routerID(2, 2, 2, 2), + routerID(1, 1, 1, 1), + }, + }, + }, + }, + }, + }, + } + + src, err := net.IPFromString("fe80::1") + require.NoError(t, err) + dst, err := net.IPFromString("fe80::3") + require.NoError(t, err) + + for _, test := range tests { + runDecodeTest(t, test, src, dst) + } +} + +func TestDecodeLSAck(t *testing.T) { + tests := []decodeTest{ + { + name: "Default", + input: []byte{ + // Header + 0x03, // Version + 0x05, // Type: LS Ack + 0x00, 0xc4, // Length + 0x03, 0x03, 0x03, 0x03, // Router ID + 0x00, 0x00, 0x00, 0x00, // Area ID + 0x8c, 0x8f, // Checksum + 0x00, // Instance ID + 0x00, // Reserved + + // LSA Type 1 + 0x00, 0x1e, // Age + 0x20, 0x01, // Type: Router-LSA + 0x00, 0x00, 0x00, 0x00, // LS ID + 0x01, 0x01, 0x01, 0x01, // Router ID + 0x80, 0x00, 0x00, 0x12, // Seq Num + 0xb1, 0x4a, // Checksum + 0x00, 0x18, // Length + + // LSA Type 2 + 0x01, 0xdd, 0x20, 0x02, + 0x00, 0x00, 0x00, 0x06, + 0x03, 0x03, 0x03, 0x03, + 0x80, 0x00, 0x00, 0x02, + 0x6d, 0x6c, 0x00, 0x24, + + // LSA Type 3 + 0x02, 0x54, 0x20, 0x03, + 0x00, 0x00, 0x00, 0x02, + 0x03, 0x03, 0x03, 0x03, + 0x80, 0x00, 0x00, 0x01, + 0xfc, 0xec, 0x00, 0x24, + + // LSA Type 3 + 0x02, 0x5e, 0x20, 0x03, + 0x00, 0x00, 0x00, 0x01, + 0x03, 0x03, 0x03, 0x03, + 0x80, 0x00, 0x00, 0x01, + 0x2e, 0x96, 0x00, 0x24, + + // LSA Type 3 + 0x02, 0x5e, 0x20, 0x03, + 0x00, 0x00, 0x00, 0x00, + 0x03, 0x03, 0x03, 0x03, + 0x80, 0x00, 0x00, 0x01, + 0xc2, 0x34, 0x00, 0x24, + + // LSA Type 3 + 0x00, 0x1f, 0x20, 0x03, + 0x00, 0x00, 0x00, 0x05, + 0x01, 0x01, 0x01, 0x01, + 0x80, 0x00, 0x00, 0x01, + 0xdb, 0x0f, 0x00, 0x24, + + // LSA Type 8 + 0x00, 0x1e, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x06, + 0x01, 0x01, 0x01, 0x01, + 0x80, 0x00, 0x00, 0x01, + 0x86, 0xd0, 0x00, 0x38, + + // LSA Type 9 + 0x01, 0xdd, 0x20, 0x09, + 0x00, 0x00, 0x18, 0x00, + 0x03, 0x03, 0x03, 0x03, + 0x80, 0x00, 0x00, 0x02, + 0xbd, 0xe9, 0x00, 0x2c, + + // LSA Type 9 + 0x00, 0x1e, 0x20, 0x09, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, + 0x80, 0x00, 0x00, 0x01, + 0x74, 0x18, 0x00, 0x34, + }, + expected: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeLinkStateAcknowledgment, + PacketLength: 196, + RouterID: routerID(3, 3, 3, 3), + AreaID: 0, + Checksum: 0x8c8f, + InstanceID: 0, + Body: ospf.LinkStateAcknowledgement{ + { + Type: ospf.LSATypeRouter, + Age: 0x1e, + ID: 0, + AdvertisingRouter: routerID(1, 1, 1, 1), + SequenceNumber: 0x80000012, + Checksum: 0xb14a, + Length: 0x18, + }, + { + Type: ospf.LSATypeNetwork, + Age: 0x1dd, + ID: 6, + AdvertisingRouter: routerID(3, 3, 3, 3), + SequenceNumber: 0x80000002, + Checksum: 0x6d6c, + Length: 0x24, + }, + { + Type: ospf.LSATypeInterAreaPrefix, + Age: 0x254, + ID: 2, + AdvertisingRouter: routerID(3, 3, 3, 3), + SequenceNumber: 0x80000001, + Checksum: 0xfcec, + Length: 0x24, + }, + { + Type: ospf.LSATypeInterAreaPrefix, + Age: 0x25e, + ID: 1, + AdvertisingRouter: routerID(3, 3, 3, 3), + SequenceNumber: 0x80000001, + Checksum: 0x2e96, + Length: 0x24, + }, + { + Type: ospf.LSATypeInterAreaPrefix, + Age: 0x25e, + ID: 0, + AdvertisingRouter: routerID(3, 3, 3, 3), + SequenceNumber: 0x80000001, + Checksum: 0xc234, + Length: 0x24, + }, + { + Type: ospf.LSATypeInterAreaPrefix, + Age: 0x1f, + ID: 5, + AdvertisingRouter: routerID(1, 1, 1, 1), + SequenceNumber: 0x80000001, + Checksum: 0xdb0f, + Length: 0x24, + }, + { + Type: ospf.LSATypeLink, + Age: 0x1e, + ID: 6, + AdvertisingRouter: routerID(1, 1, 1, 1), + SequenceNumber: 0x80000001, + Checksum: 0x86d0, + Length: 0x38, + }, + { + Type: ospf.LSATypeIntraAreaPrefix, + Age: 0x1dd, + ID: 0x1800, + AdvertisingRouter: routerID(3, 3, 3, 3), + SequenceNumber: 0x80000002, + Checksum: 0xbde9, + Length: 0x2c, + }, + { + Type: ospf.LSATypeIntraAreaPrefix, + Age: 0x1e, + ID: 0, + AdvertisingRouter: routerID(1, 1, 1, 1), + SequenceNumber: 0x80000001, + Checksum: 0x7418, + Length: 0x34, + }, + }, + }, + }, + } + + src, err := net.IPFromString("fe80::3") + require.NoError(t, err) + dst, err := net.IPFromString("fe80::2") + require.NoError(t, err) + + for _, test := range tests { + runDecodeTest(t, test, src, dst) + } +} diff --git a/protocols/ospf/packetv3/encode_test.go b/protocols/ospf/packetv3/encode_test.go new file mode 100644 index 00000000..4d74edab --- /dev/null +++ b/protocols/ospf/packetv3/encode_test.go @@ -0,0 +1,165 @@ +package packetv3_test + +import ( + "bytes" + "testing" + + "github.com/bio-routing/bio-rd/net" + ospf "github.com/bio-routing/bio-rd/protocols/ospf/packetv3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type encodeTest struct { + name string + msg *ospf.OSPFv3Message + expected []byte +} + +func runEncodeTest(t *testing.T, testCase encodeTest, src, dst net.IP) { + t.Run(testCase.name, func(t *testing.T) { + out := new(bytes.Buffer) + testCase.msg.Serialize(out, src, dst) + assert.Equal(t, testCase.expected, out.Bytes()) + }) +} + +func TestEncodeHello(t *testing.T) { + tests := []encodeTest{ + { + name: "Init", + msg: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeHello, + RouterID: routerID(5, 5, 5, 5), + AreaID: 0, + InstanceID: 0, + Body: &ospf.Hello{ + InterfaceID: 4, + RouterPriority: 200, + Options: ospf.OptionsFromFlags(ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + HelloInterval: 20, + RouterDeadInterval: 60, + }, + }, + expected: []byte{ + 0x03, // Version + 0x01, // Type: Hello + 0x00, 0x24, // Length + 0x05, 0x05, 0x05, 0x05, // Router ID + 0x00, 0x00, 0x00, 0x00, // Area ID + 0xd0, 0x7a, // Checksum + 0x00, // Instance ID + 0x00, // Reserved + + // Payload + 0x00, 0x00, 0x00, 0x04, // Interface ID + 0xc8, // prio + 0x00, // reserved + 0x00, 0x13, // Options + 0x00, 0x14, // Hello Interval + 0x00, 0x3c, // Dead Interval + 0x00, 0x00, 0x00, 0x00, // DR + 0x00, 0x00, 0x00, 0x00, // BDR + }, + }, + { + name: "WithDR", + msg: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeHello, + RouterID: routerID(5, 5, 5, 5), + AreaID: 0, + InstanceID: 0, + Body: &ospf.Hello{ + InterfaceID: 4, + RouterPriority: 200, + Options: ospf.OptionsFromFlags(ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + HelloInterval: 20, + RouterDeadInterval: 60, + DesignatedRouterID: routerID(3, 3, 3, 3), + Neighbors: []ospf.ID{ + routerID(3, 3, 3, 3), + routerID(6, 6, 6, 6), + }, + }, + }, + expected: []byte{ + 0x03, // Version + 0x01, // Type: Hello + 0x00, 0x2c, // Length + 0x05, 0x05, 0x05, 0x05, // Router ID + 0x00, 0x00, 0x00, 0x00, // Area ID + 0xb8, 0x52, // Checksum + 0x00, // Instance ID + 0x00, // Reserved + + // Payload + 0x00, 0x00, 0x00, 0x04, // Interface ID + 0xc8, // prio + 0x00, // reserved + 0x00, 0x13, // Options + 0x00, 0x14, // Hello Interval + 0x00, 0x3c, // Dead Interval + 0x03, 0x03, 0x03, 0x03, // DR + 0x00, 0x00, 0x00, 0x00, // BDR + // Neighbors + 0x03, 0x03, 0x03, 0x03, + 0x06, 0x06, 0x06, 0x06, + }, + }, + { + name: "WithDRSelf", + msg: &ospf.OSPFv3Message{ + Version: 3, + Type: ospf.MsgTypeHello, + RouterID: routerID(5, 5, 5, 5), + AreaID: 0, + InstanceID: 0, + Body: &ospf.Hello{ + InterfaceID: 4, + RouterPriority: 200, + Options: ospf.OptionsFromFlags(ospf.RouterOptR, ospf.RouterOptE, ospf.RouterOptV6), + HelloInterval: 20, + RouterDeadInterval: 60, + DesignatedRouterID: routerID(5, 5, 5, 5), + BackupDesignatedRouterID: routerID(3, 3, 3, 3), + Neighbors: []ospf.ID{ + routerID(3, 3, 3, 3), + }, + }, + }, + expected: []byte{ + 0x03, // Version + 0x01, // Type: Hello + 0x00, 0x28, // Length + 0x05, 0x05, 0x05, 0x05, // Router ID + 0x00, 0x00, 0x00, 0x00, // Area ID + 0xba, 0x5c, // Checksum + 0x00, // Instance ID + 0x00, // Reserved + + // Payload + 0x00, 0x00, 0x00, 0x04, // Interface ID + 0xc8, // prio + 0x00, // reserved + 0x00, 0x13, // Options + 0x00, 0x14, // Hello Interval + 0x00, 0x3c, // Dead Interval + 0x05, 0x05, 0x05, 0x05, // DR + 0x03, 0x03, 0x03, 0x03, // BDR + // Neighbors + 0x03, 0x03, 0x03, 0x03, + }, + }, + } + + src, err := net.IPFromString("fe80::c92:6b3f:92b0:d49e") + require.NoError(t, err) + dst, err := net.IPFromString("fe80::acac:d4ff:fe15:fd8b") + require.NoError(t, err) + + for _, test := range tests { + runEncodeTest(t, test, src, dst) + } +} diff --git a/protocols/ospf/packetv3/fixtures/OSPFv3_NBMA_adjacencies.cap b/protocols/ospf/packetv3/fixtures/OSPFv3_NBMA_adjacencies.cap new file mode 100644 index 00000000..7aa8fae0 Binary files /dev/null and b/protocols/ospf/packetv3/fixtures/OSPFv3_NBMA_adjacencies.cap differ diff --git a/protocols/ospf/packetv3/fixtures/OSPFv3_broadcast_adjacency.cap b/protocols/ospf/packetv3/fixtures/OSPFv3_broadcast_adjacency.cap new file mode 100644 index 00000000..15cb2dd2 Binary files /dev/null and b/protocols/ospf/packetv3/fixtures/OSPFv3_broadcast_adjacency.cap differ diff --git a/protocols/ospf/packetv3/fixtures/OSPFv3_multipoint_adjacencies.cap b/protocols/ospf/packetv3/fixtures/OSPFv3_multipoint_adjacencies.cap new file mode 100644 index 00000000..9ba76989 Binary files /dev/null and b/protocols/ospf/packetv3/fixtures/OSPFv3_multipoint_adjacencies.cap differ diff --git a/protocols/ospf/packetv3/fixtures/utils.go b/protocols/ospf/packetv3/fixtures/utils.go new file mode 100644 index 00000000..d9fb6922 --- /dev/null +++ b/protocols/ospf/packetv3/fixtures/utils.go @@ -0,0 +1,49 @@ +package fixtures + +import ( + "os" + "testing" + + "github.com/bio-routing/bio-rd/net" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcapgo" +) + +func PacketReader(t *testing.T, path string) (*pcapgo.Reader, *os.File) { + f, err := os.Open(path) + if err != nil { + t.Error(err) + } + + r, err := pcapgo.NewReader(f) + if err != nil { + t.Error(err) + } + return r, f +} + +func Payload(raw []byte) (pl []byte, src, dst net.IP, err error) { + packet := gopacket.NewPacket(raw, layers.LayerTypeEthernet, gopacket.Default) + if perr := packet.ErrorLayer(); perr != nil { + // fallback to handling of FrameRelay (cut-off header) + packet = gopacket.NewPacket(raw[4:], layers.LayerTypeIPv6, gopacket.Default) + if perr = packet.ErrorLayer(); perr != nil { + err = perr.Error() + return + } + } + + flowSrc, flowDst := packet.NetworkLayer().NetworkFlow().Endpoints() + src, err = net.IPFromBytes(flowSrc.Raw()) + if err != nil { + return + } + dst, err = net.IPFromBytes(flowDst.Raw()) + if err != nil { + return + } + + pl = packet.NetworkLayer().LayerPayload() + return +} diff --git a/protocols/ospf/packetv3/lsa.go b/protocols/ospf/packetv3/lsa.go new file mode 100644 index 00000000..d9165320 --- /dev/null +++ b/protocols/ospf/packetv3/lsa.go @@ -0,0 +1,621 @@ +package packetv3 + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/util/decode" + "github.com/bio-routing/tflow2/convert" + "github.com/pkg/errors" +) + +type LSAType uint16 + +func (t LSAType) Serialize(buf *bytes.Buffer) { + buf.Write(convert.Uint16Byte(uint16(t))) +} + +func (t LSAType) FloodIfUnknown() bool { + return t&(1<<15) != 0 // test for top bit +} + +type FloodingScope uint8 + +const ( + FloodLinkLocal FloodingScope = iota + FloodArea + FloodAS + FloodReserved +) + +func (t LSAType) FloodingScope() FloodingScope { + return FloodingScope((t & 0b0110000000000000) >> 13) // second and third bit as int +} + +// OSPF LSA types +const ( + LSATypeUnknown LSAType = 0 + LSATypeRouter = 0x2001 + LSATypeNetwork = 0x2002 + LSATypeInterAreaPrefix = 0x2003 + LSATypeInterAreaRouter = 0x2004 + LSATypeASExternal = 0x4005 + LSATypeDeprecated = 0x2006 + LSATypeNSSA = 0x2007 + LSATypeLink = 0x0008 + LSATypeIntraAreaPrefix = 0x2009 +) + +type LSA struct { + Age uint16 + Type LSAType + ID ID + AdvertisingRouter ID + SequenceNumber uint32 + Checksum uint16 + Length uint16 + Body Serializable +} + +const LSAHeaderLength = 20 + +func (x *LSA) SerializeHeader(buf *bytes.Buffer) { + buf.Write(convert.Uint16Byte(x.Age)) + x.Type.Serialize(buf) + x.ID.Serialize(buf) + x.AdvertisingRouter.Serialize(buf) + buf.Write(convert.Uint32Byte(x.SequenceNumber)) + buf.Write(convert.Uint16Byte(x.Checksum)) + buf.Write(convert.Uint16Byte(x.Length)) +} + +func (x *LSA) Serialize(buf *bytes.Buffer) { + x.SerializeHeader(buf) + x.Body.Serialize(buf) +} + +func DeserializeLSAHeader(buf *bytes.Buffer) (*LSA, int, error) { + pdu := &LSA{} + + var readBytes int + var err error + var fields []interface{} + + fields = []interface{}{ + &pdu.Age, + &pdu.Type, + &pdu.ID, + &pdu.AdvertisingRouter, + &pdu.SequenceNumber, + &pdu.Checksum, + &pdu.Length, + } + + err = decode.Decode(buf, fields) + if err != nil { + return nil, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 20 + + return pdu, readBytes, nil +} +func DeserializeLSA(buf *bytes.Buffer) (*LSA, int, error) { + pdu, readBytes, err := DeserializeLSAHeader(buf) + if err != nil { + return nil, 0, err + } + + n, err := pdu.ReadBody(buf) + if err != nil { + return nil, readBytes, errors.Wrap(err, "unable to decode LSA body") + } + readBytes += n + + return pdu, readBytes, nil +} + +func (x *LSA) ReadBody(buf *bytes.Buffer) (int, error) { + bodyLength := x.Length - LSAHeaderLength + var body Serializable + var readBytes int + var err error + + switch x.Type { + case LSATypeRouter: + body, readBytes, err = DeserializeRouterLSA(buf, bodyLength) + case LSATypeNetwork: + body, readBytes, err = DeserializeNetworkLSA(buf, bodyLength) + case LSATypeInterAreaPrefix: + body, readBytes, err = DeserializeInterAreaPrefixLSA(buf) + case LSATypeInterAreaRouter: + body, readBytes, err = DeserializeInterAreaRouterLSA(buf) + case LSATypeASExternal: + body, readBytes, err = DeserializeASExternalLSA(buf) + case LSATypeNSSA: // NSSA-LSA special case + body, readBytes, err = DeserializeASExternalLSA(buf) + case LSATypeLink: + body, readBytes, err = DeserializeLinkLSA(buf) + case LSATypeIntraAreaPrefix: + body, readBytes, err = DeserializeIntraAreaPrefixLSA(buf) + default: + raw := make(UnknownLSA, bodyLength) + readBytes, err = buf.Read(raw) + body = raw + } + + if err != nil { + return readBytes, err + } + + x.Body = body + return readBytes, nil +} + +type UnknownLSA []byte + +func (u UnknownLSA) Serialize(buf *bytes.Buffer) { + buf.Write(u) +} + +// helper for deserializing 24-bit metric values +type interfaceMetric struct { + High uint8 + Low uint16 +} + +// Value returns the numeric value of this metric field +func (m interfaceMetric) Value() uint32 { + return uint32(m.High)<<16 + uint32(m.Low) +} + +func serializeMetric(buf *bytes.Buffer, val uint32) { + metricBytes := convert.Uint32Byte(val) + buf.Write(metricBytes[1:4]) +} + +type AreaLinkDescriptionType uint8 + +const ( + _ AreaLinkDescriptionType = iota + ALDTypePTP + ALDTypeTransit + ALDTypeReserved + ALDTypeVirtualLink +) + +type AreaLinkDescription struct { + Type AreaLinkDescriptionType + Metric uint32 // max: 24 bit + InterfaceID ID + NeighborInterfaceID ID + NeighborRouterID ID +} + +func (x *AreaLinkDescription) Serialize(buf *bytes.Buffer) { + buf.WriteByte(uint8(x.Type)) + serializeMetric(buf, x.Metric) + x.InterfaceID.Serialize(buf) + x.NeighborInterfaceID.Serialize(buf) + x.NeighborRouterID.Serialize(buf) +} + +func DeserializeAreaLinkDescription(buf *bytes.Buffer) (AreaLinkDescription, int, error) { + pdu := AreaLinkDescription{} + + var readBytes int + var err error + var fields []interface{} + var metric interfaceMetric + + fields = []interface{}{ + &pdu.Type, + &metric, + &pdu.InterfaceID, + &pdu.NeighborInterfaceID, + &pdu.NeighborRouterID, + } + + err = decode.Decode(buf, fields) + if err != nil { + return pdu, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 16 + + pdu.Metric = metric.Value() + + return pdu, readBytes, nil +} + +type RouterLSAFlags uint8 + +const ( + RouterLSAFlagBorder RouterLSAFlags = 1 << iota + RouterLSAFlagExternal + RouterLSAFlagVirtualLink + _ + RouterLSAFlagNSSATranslation +) + +func RouterLSAFlagsFrom(flags ...RouterLSAFlags) RouterLSAFlags { + var val RouterLSAFlags + for _, flag := range flags { + val = val | flag + } + return val +} + +func (f RouterLSAFlags) HasFlag(flag uint8) bool { + return uint8(f)&flag != 0 +} + +func (f RouterLSAFlags) SetFlag(flag uint8) uint8 { + return uint8(f) | flag +} + +type RouterLSA struct { + Flags RouterLSAFlags + Options RouterOptions + LinkDescriptions []AreaLinkDescription +} + +func (x *RouterLSA) Serialize(buf *bytes.Buffer) { + buf.WriteByte(byte(x.Flags)) + x.Options.Serialize(buf) + for i := range x.LinkDescriptions { + x.LinkDescriptions[i].Serialize(buf) + } +} + +func DeserializeRouterLSA(buf *bytes.Buffer, bodyLength uint16) (*RouterLSA, int, error) { + pdu := &RouterLSA{} + + var readBytes int + var err error + var fields []interface{} + + fields = []interface{}{ + &pdu.Flags, + &pdu.Options, + } + + err = decode.Decode(buf, fields) + if err != nil { + return nil, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 4 + + for i := readBytes; i < int(bodyLength); { + tlv, n, err := DeserializeAreaLinkDescription(buf) + if err != nil { + return nil, readBytes, errors.Wrap(err, "unable to decode LinkDescription") + } + pdu.LinkDescriptions = append(pdu.LinkDescriptions, tlv) + i += n + readBytes += n + } + + return pdu, readBytes, nil +} + +type NetworkLSA struct { + Options RouterOptions + AttachedRouter []ID +} + +func (x *NetworkLSA) Serialize(buf *bytes.Buffer) { + buf.WriteByte(0) // 1 byte reserved + x.Options.Serialize(buf) + for i := range x.AttachedRouter { + x.AttachedRouter[i].Serialize(buf) + } +} + +func DeserializeNetworkLSA(buf *bytes.Buffer, bodyLength uint16) (*NetworkLSA, int, error) { + pdu := &NetworkLSA{} + + var readBytes int + var err error + var fields []interface{} + + fields = []interface{}{ + new(uint8), // 1 byte reserved + &pdu.Options, + } + + err = decode.Decode(buf, fields) + if err != nil { + return nil, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 4 + + for i := readBytes; i < int(bodyLength); { + tlv, n, err := DeserializeID(buf) + if err != nil { + return nil, 0, errors.Wrap(err, "Unable to decode AttachedRouterID") + } + pdu.AttachedRouter = append(pdu.AttachedRouter, tlv) + i += n + readBytes += n + } + + return pdu, readBytes, nil +} + +type InterAreaPrefixLSA struct { + Metric uint32 // max: 24 bit + Prefix LSAPrefix +} + +func (x *InterAreaPrefixLSA) Serialize(buf *bytes.Buffer) { + buf.WriteByte(0) // Reserved + serializeMetric(buf, x.Metric) + x.Prefix.Serialize(buf) +} + +func DeserializeInterAreaPrefixLSA(buf *bytes.Buffer) (*InterAreaPrefixLSA, int, error) { + pdu := &InterAreaPrefixLSA{} + var readBytes int + + // decode metric + _, _ = buf.ReadByte() // skip reserved byte + var metric interfaceMetric + if err := binary.Read(buf, binary.BigEndian, &metric); err != nil { + return nil, readBytes, errors.Wrap(err, "failed to read metric") + } + readBytes += 3 + pdu.Metric = metric.Value() + + pfx, n, err := DeserializeLSAPrefix(buf) + readBytes += n + if err != nil { + return nil, readBytes, errors.Wrap(err, "unable to decode prefix") + } + pdu.Prefix = pfx + + return pdu, readBytes, nil +} + +type InterAreaRouterLSA struct { + Options RouterOptions + Metric uint32 // max: 24 bit + DestinationRouterID ID +} + +func (x *InterAreaRouterLSA) Serialize(buf *bytes.Buffer) { + buf.WriteByte(0) // 1 byte reserved + x.Options.Serialize(buf) + buf.WriteByte(0) // 1 byte reserved + serializeMetric(buf, x.Metric) + x.DestinationRouterID.Serialize(buf) +} + +func DeserializeInterAreaRouterLSA(buf *bytes.Buffer) (*InterAreaRouterLSA, int, error) { + pdu := &InterAreaRouterLSA{} + + var readBytes int + var err error + var metric interfaceMetric + var fields []interface{} + + fields = []interface{}{ + new(uint8), // 1 byte reserved + &pdu.Options, + new(uint8), // 1 byte reserved + &metric, + &pdu.DestinationRouterID, + } + + err = decode.Decode(buf, fields) + if err != nil { + return nil, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 12 + + pdu.Metric = metric.Value() + + return pdu, readBytes, nil +} + +// Bitmasks for flags used in ASExternalLSA +const ( + ASExtLSAFlagT uint8 = 1 << iota + ASExtLSAFlagF + ASExtLSAFlagE +) + +type ASExternalLSA struct { + Flags uint8 + Metric uint32 // max: 24 bit + Prefix LSAPrefix + + ForwardingAddress net.IP // optional + ExternalRouteTag uint32 // optional + ReferencedLinkStateID ID // optional +} + +func (a *ASExternalLSA) FlagE() bool { + return (a.Flags & ASExtLSAFlagE) != 0 +} + +func (a *ASExternalLSA) FlagF() bool { + return (a.Flags & ASExtLSAFlagF) != 0 +} + +func (a *ASExternalLSA) FlagT() bool { + return (a.Flags & ASExtLSAFlagT) != 0 +} + +func (x *ASExternalLSA) Serialize(buf *bytes.Buffer) { + buf.WriteByte(x.Flags) + serializeMetric(buf, x.Metric) + x.Prefix.Serialize(buf) + if x.FlagF() { + serializeIPv6(x.ForwardingAddress, buf) + } + if x.FlagT() { + buf.Write(convert.Uint32Byte(x.ExternalRouteTag)) + } + if x.Prefix.Special != 0 { + x.ReferencedLinkStateID.Serialize(buf) + } +} + +func DeserializeASExternalLSA(buf *bytes.Buffer) (*ASExternalLSA, int, error) { + pdu := &ASExternalLSA{} + + var readBytes int + var err error + var fields []interface{} + var metric interfaceMetric + + fields = []interface{}{ + &pdu.Flags, + &metric, + } + + err = decode.Decode(buf, fields) + if err != nil { + return nil, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 4 + + pdu.Metric = metric.Value() + + pfx, n, err := DeserializeLSAPrefix(buf) + if err != nil { + return nil, readBytes, errors.Wrap(err, "unable to decode prefix") + } + pdu.Prefix = pfx + readBytes += n + + if pdu.FlagF() { + ip := deserializableIP{} + err := binary.Read(buf, binary.BigEndian, &ip) + if err != nil { + return nil, readBytes, errors.Wrap(err, "unable to decode ForwardingAddress") + } + pdu.ForwardingAddress = ip.ToNetIP() + readBytes += 16 + } + if pdu.FlagT() { + err := binary.Read(buf, binary.BigEndian, &pdu.ExternalRouteTag) + if err != nil { + return nil, readBytes, errors.Wrap(err, "unable to decode ExternalRouteTag") + } + readBytes += 4 + } + if pdu.Prefix.Special != 0 { + id, n, err := DeserializeID(buf) + if err != nil { + return nil, readBytes, errors.Wrap(err, "unable to decode ReferencedLinkStateID") + } + pdu.ReferencedLinkStateID = id + readBytes += n + } + + return pdu, readBytes, nil +} + +type LinkLSA struct { + RouterPriority uint8 + Options RouterOptions + LinkLocalInterfaceAddress net.IP + PrefixNum uint32 + Prefixes []LSAPrefix +} + +func (x *LinkLSA) Serialize(buf *bytes.Buffer) { + buf.WriteByte(x.RouterPriority) + x.Options.Serialize(buf) + serializeIPv6(x.LinkLocalInterfaceAddress, buf) + buf.Write(convert.Uint32Byte(x.PrefixNum)) + for i := range x.Prefixes { + x.Prefixes[i].Serialize(buf) + } +} + +func DeserializeLinkLSA(buf *bytes.Buffer) (*LinkLSA, int, error) { + pdu := &LinkLSA{} + + var readBytes int + var err error + var fields []interface{} + + llintfAddr := deserializableIP{} + fields = []interface{}{ + &pdu.RouterPriority, + &pdu.Options, + &llintfAddr, + &pdu.PrefixNum, + } + + err = decode.Decode(buf, fields) + if err != nil { + return nil, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + pdu.LinkLocalInterfaceAddress = llintfAddr.ToNetIP() + readBytes += 24 + + for i := 0; i < int(pdu.PrefixNum); i++ { + tlv, n, err := DeserializeLSAPrefix(buf) + if err != nil { + return nil, 0, errors.Wrap(err, "Unable to decode") + } + pdu.Prefixes = append(pdu.Prefixes, tlv) + readBytes += n + } + + return pdu, readBytes, nil +} + +type IntraAreaPrefixLSA struct { + ReferencedLSType LSAType + ReferencedLinkStateID ID + ReferencedAdvertisingRouter ID + Prefixes []LSAPrefix +} + +func (x *IntraAreaPrefixLSA) Serialize(buf *bytes.Buffer) { + buf.Write(convert.Uint16Byte(uint16(len(x.Prefixes)))) + x.ReferencedLSType.Serialize(buf) + x.ReferencedLinkStateID.Serialize(buf) + x.ReferencedAdvertisingRouter.Serialize(buf) + for i := range x.Prefixes { + x.Prefixes[i].Serialize(buf) + } +} + +func DeserializeIntraAreaPrefixLSA(buf *bytes.Buffer) (*IntraAreaPrefixLSA, int, error) { + pdu := &IntraAreaPrefixLSA{} + var prefixNum uint16 + + var readBytes int + var err error + var fields []interface{} + + fields = []interface{}{ + &prefixNum, + &pdu.ReferencedLSType, + &pdu.ReferencedLinkStateID, + &pdu.ReferencedAdvertisingRouter, + } + + err = decode.Decode(buf, fields) + if err != nil { + return nil, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 12 + + for i := 0; i < int(prefixNum); i++ { + tlv, n, err := DeserializeLSAPrefix(buf) + if err != nil { + return nil, 0, errors.Wrap(err, "Unable to decode") + } + pdu.Prefixes = append(pdu.Prefixes, tlv) + readBytes += n + } + + return pdu, readBytes, nil +} diff --git a/protocols/ospf/packetv3/lsa_prefix.go b/protocols/ospf/packetv3/lsa_prefix.go new file mode 100644 index 00000000..0b17f25f --- /dev/null +++ b/protocols/ospf/packetv3/lsa_prefix.go @@ -0,0 +1,109 @@ +package packetv3 + +import ( + "bytes" + "fmt" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/util/decode" + "github.com/bio-routing/tflow2/convert" + "github.com/pkg/errors" +) + +// Prefix Option Bits +const ( + NUBIT = 1 + LABIT = 2 + PBIT = 8 + DNBIT = 16 +) + +type PrefixOptions struct { + NoUnicast bool // NU-bit + LocalAddress bool // LA-bit + Propagate bool // P-bit + DN bool // DN-bit +} + +func (o PrefixOptions) Serialize(buf *bytes.Buffer) { + var rawOptions uint8 + if o.NoUnicast { + rawOptions = rawOptions | NUBIT + } + if o.LocalAddress { + rawOptions = rawOptions | LABIT + } + if o.Propagate { + rawOptions = rawOptions | PBIT + } + if o.DN { + rawOptions = rawOptions | DNBIT + } + buf.WriteByte(rawOptions) +} + +type LSAPrefix struct { + PrefixLength uint8 + Options PrefixOptions + + // this may represent different things + // used for metric or referenced LSType + Special uint16 + + Address net.IP +} + +func (x *LSAPrefix) ToNetPrefix() net.Prefix { + return net.NewPfx(x.Address, x.PrefixLength) +} + +func DeserializeLSAPrefix(buf *bytes.Buffer) (LSAPrefix, int, error) { + pdu := LSAPrefix{} + + var readBytes int + var err error + + var rawOptions uint8 + + fields := []interface{}{ + &pdu.PrefixLength, + &rawOptions, + &pdu.Special, + } + + err = decode.Decode(buf, fields) + if err != nil { + return pdu, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 4 + + // read Options + pdu.Options.NoUnicast = (rawOptions & NUBIT) != 0 + pdu.Options.LocalAddress = (rawOptions & LABIT) != 0 + pdu.Options.Propagate = (rawOptions & PBIT) != 0 + pdu.Options.DN = (rawOptions & DNBIT) != 0 + + // read AddressPrefix + numBytes := int((pdu.PrefixLength+31)/32) * 4 + pfxBytes := buf.Next(numBytes) + ipBytes := make([]byte, 16) + copy(ipBytes[:len(pfxBytes)], pfxBytes) + addr, err := net.IPFromBytes(ipBytes) + if err != nil { + return pdu, readBytes, errors.Wrap(err, "unable to decode AddressPrefix") + } + pdu.Address = addr + readBytes += len(pfxBytes) + + return pdu, readBytes, nil +} + +func (x *LSAPrefix) Serialize(buf *bytes.Buffer) { + buf.WriteByte(x.PrefixLength) + x.Options.Serialize(buf) + buf.Write(convert.Uint16Byte(x.Special)) + + // serialize AddressPrefix + numBytes := int((x.PrefixLength+31)/32) * 4 + buf.Write(x.Address.Bytes()[:numBytes]) +} diff --git a/protocols/ospf/packetv3/lsa_test.go b/protocols/ospf/packetv3/lsa_test.go new file mode 100644 index 00000000..ca9bbf05 --- /dev/null +++ b/protocols/ospf/packetv3/lsa_test.go @@ -0,0 +1,74 @@ +package packetv3_test + +import ( + "testing" + + ospf "github.com/bio-routing/bio-rd/protocols/ospf/packetv3" + "github.com/stretchr/testify/assert" +) + +func TestLSATypeFlooding(t *testing.T) { + tests := []struct { + input ospf.LSAType + expectUnknownFlood bool + expectedFlooding ospf.FloodingScope + }{ + { + input: ospf.LSATypeRouter, + expectedFlooding: ospf.FloodArea, + }, + { + input: ospf.LSATypeNetwork, + expectedFlooding: ospf.FloodArea, + }, + { + input: ospf.LSATypeInterAreaPrefix, + expectedFlooding: ospf.FloodArea, + }, + { + input: ospf.LSATypeInterAreaRouter, + expectedFlooding: ospf.FloodArea, + }, + { + input: ospf.LSATypeASExternal, + expectedFlooding: ospf.FloodAS, + }, + { + input: ospf.LSATypeDeprecated, + expectedFlooding: ospf.FloodArea, + }, + { + input: ospf.LSATypeNSSA, + expectedFlooding: ospf.FloodArea, + }, + { + input: ospf.LSATypeLink, + expectedFlooding: ospf.FloodLinkLocal, + }, + { + input: ospf.LSATypeIntraAreaPrefix, + expectedFlooding: ospf.FloodArea, + }, + { + // Unknown with local scope + input: 0x0022, + expectUnknownFlood: false, + }, + { + // Unknown with flooding scope + input: 0xa022, + expectUnknownFlood: true, + expectedFlooding: ospf.FloodArea, + }, + { + // Unknown with reserved flooding scope + input: 0x6022, + expectedFlooding: ospf.FloodReserved, + }, + } + + for _, test := range tests { + assert.Equal(t, test.expectedFlooding, test.input.FloodingScope()) + assert.Equal(t, test.expectUnknownFlood, test.input.FloodIfUnknown()) + } +} diff --git a/protocols/ospf/packetv3/packet.go b/protocols/ospf/packetv3/packet.go new file mode 100644 index 00000000..f7afbdf3 --- /dev/null +++ b/protocols/ospf/packetv3/packet.go @@ -0,0 +1,389 @@ +package packetv3 + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/util/checksum" + "github.com/bio-routing/bio-rd/util/decode" + "github.com/bio-routing/tflow2/convert" + "github.com/pkg/errors" +) + +const OSPFProtocolNumber = 89 +const expectedVersion = 3 + +type OSPFMessageType uint8 + +// OSPF message types +const ( + MsgTypeUnknown OSPFMessageType = iota + MsgTypeHello + MsgTypeDatabaseDescription + MsgTypeLinkStateRequest + MsgTypeLinkStateUpdate + MsgTypeLinkStateAcknowledgment +) + +type OSPFv3Message struct { + Version uint8 + Type OSPFMessageType + PacketLength uint16 + RouterID ID + AreaID ID + Checksum uint16 + InstanceID uint8 + Body Serializable +} + +const OSPFv3MessageHeaderLength = 16 +const OSPFv3MessagePacketLengthAtByte = 2 +const OSPFv3MessageChecksumAtByte = 12 + +func (x *OSPFv3Message) Serialize(out *bytes.Buffer, src, dst net.IP) { + buf := bytes.NewBuffer(nil) + + buf.WriteByte(x.Version) + buf.WriteByte(uint8(x.Type)) + buf.Write(convert.Uint16Byte(x.PacketLength)) + x.RouterID.Serialize(buf) + x.AreaID.Serialize(buf) + buf.Write(convert.Uint16Byte(x.Checksum)) + buf.WriteByte(x.InstanceID) + buf.WriteByte(0) // 1 byte reserved + x.Body.Serialize(buf) + + data := buf.Bytes() + + length := uint16(len(data)) + putUint16(data, OSPFv3MessagePacketLengthAtByte, length) + + checksum := OSPFv3Checksum(data, src, dst) + putUint16(data, OSPFv3MessageChecksumAtByte, checksum) + + out.Write(data) +} + +func putUint16(b []byte, p int, v uint16) { + binary.BigEndian.PutUint16(b[p:p+2], v) +} + +func DeserializeOSPFv3Message(buf *bytes.Buffer, src, dst net.IP) (*OSPFv3Message, int, error) { + pdu := &OSPFv3Message{} + data := buf.Bytes() + + var readBytes int + var err error + var fields []interface{} + + fields = []interface{}{ + &pdu.Version, + &pdu.Type, + &pdu.PacketLength, + &pdu.RouterID, + &pdu.AreaID, + &pdu.Checksum, + &pdu.InstanceID, + new(uint8), // 1 byte reserved + } + + err = decode.Decode(buf, fields) + if err != nil { + return nil, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 16 + + if pdu.Version != expectedVersion { + return nil, readBytes, fmt.Errorf("Invalid OSPF version: %d", pdu.Version) + } + + expectedChecksum := OSPFv3Checksum(data, src, dst) + if pdu.Checksum != expectedChecksum { + return nil, readBytes, fmt.Errorf("Checksum mismatch. Expected %#04x, got %#04x", expectedChecksum, pdu.Checksum) + } + + n, err := pdu.ReadBody(buf) + if err != nil { + return nil, readBytes, errors.Wrap(err, "unable to decode message body") + } + readBytes += n + + return pdu, readBytes, nil +} + +func OSPFv3Checksum(data []byte, src, dst net.IP) uint16 { + data[12] = 0 + data[13] = 0 + return checksum.IPv6UpperLayerChecksum(src, dst, OSPFProtocolNumber, data) +} + +func (m *OSPFv3Message) ReadBody(buf *bytes.Buffer) (int, error) { + bodyLength := m.PacketLength - OSPFv3MessageHeaderLength + var body Serializable + var readBytes int + var err error + + switch m.Type { + case MsgTypeHello: + body, readBytes, err = DeserializeHello(buf, bodyLength) + case MsgTypeDatabaseDescription: + body, readBytes, err = DeserializeDatabaseDescription(buf, bodyLength) + case MsgTypeLinkStateRequest: + body, readBytes, err = DeserializeLinkStateRequestMsg(buf, bodyLength) + case MsgTypeLinkStateUpdate: + body, readBytes, err = DeserializeLinkStateUpdate(buf) + case MsgTypeLinkStateAcknowledgment: + body, readBytes, err = DeserializeLinkStateAcknowledgement(buf, bodyLength) + default: + return 0, fmt.Errorf("unknown message type: %d", m.Type) + } + + if err != nil { + return 0, err + } + + m.Body = body + return readBytes, nil +} + +type Hello struct { + InterfaceID ID + RouterPriority uint8 + Options RouterOptions + HelloInterval uint16 + RouterDeadInterval uint16 + DesignatedRouterID ID + BackupDesignatedRouterID ID + Neighbors []ID +} + +func (x *Hello) Serialize(buf *bytes.Buffer) { + x.InterfaceID.Serialize(buf) + buf.WriteByte(x.RouterPriority) + x.Options.Serialize(buf) + buf.Write(convert.Uint16Byte(x.HelloInterval)) + buf.Write(convert.Uint16Byte(x.RouterDeadInterval)) + x.DesignatedRouterID.Serialize(buf) + x.BackupDesignatedRouterID.Serialize(buf) + for i := range x.Neighbors { + x.Neighbors[i].Serialize(buf) + } +} + +func DeserializeHello(buf *bytes.Buffer, bodyLength uint16) (*Hello, int, error) { + pdu := &Hello{} + + var readBytes int + var err error + var fields []interface{} + + fields = []interface{}{ + &pdu.InterfaceID, + &pdu.RouterPriority, + &pdu.Options, + &pdu.HelloInterval, + &pdu.RouterDeadInterval, + &pdu.DesignatedRouterID, + &pdu.BackupDesignatedRouterID, + } + + err = decode.Decode(buf, fields) + if err != nil { + return nil, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 20 + + for i := readBytes; i < int(bodyLength); { + id, n, err := DeserializeID(buf) + if err != nil { + return nil, readBytes, errors.Wrap(err, "unable to decode neighbor id") + } + pdu.Neighbors = append(pdu.Neighbors, id) + i += n + readBytes += n + } + + return pdu, readBytes, nil +} + +type DBFlags uint8 + +// database description flags +const ( + DBFlagInit DBFlags = 1 << iota + DBFlagMore + DBFlagMS +) + +type DatabaseDescription struct { + Options RouterOptions + InterfaceMTU uint16 + DBFlags DBFlags + DDSequenceNumber uint32 + LSAHeaders []*LSA +} + +func (x *DatabaseDescription) Serialize(buf *bytes.Buffer) { + buf.WriteByte(0) // 1 byte reserved + x.Options.Serialize(buf) + buf.Write(convert.Uint16Byte(x.InterfaceMTU)) + buf.WriteByte(0) // 1 byte reserved + buf.WriteByte(uint8(x.DBFlags)) + buf.Write(convert.Uint32Byte(x.DDSequenceNumber)) + for i := range x.LSAHeaders { + x.LSAHeaders[i].SerializeHeader(buf) + } +} + +func DeserializeDatabaseDescription(buf *bytes.Buffer, bodyLength uint16) (*DatabaseDescription, int, error) { + pdu := &DatabaseDescription{} + + var readBytes int + var err error + var fields []interface{} + + fields = []interface{}{ + new(uint8), + &pdu.Options, + &pdu.InterfaceMTU, + new(uint8), + &pdu.DBFlags, + &pdu.DDSequenceNumber, + } + + err = decode.Decode(buf, fields) + if err != nil { + return nil, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 12 + + for i := readBytes; i < int(bodyLength); { + tlv, n, err := DeserializeLSAHeader(buf) + if err != nil { + return nil, 0, errors.Wrap(err, "Unable to decode") + } + pdu.LSAHeaders = append(pdu.LSAHeaders, tlv) + i += n + readBytes += n + } + + return pdu, readBytes, nil +} + +type LinkStateRequestMsg []LinkStateRequest + +func (x LinkStateRequestMsg) Serialize(buf *bytes.Buffer) { + for i := range x { + x[i].Serialize(buf) + } +} + +func DeserializeLinkStateRequestMsg(buf *bytes.Buffer, bodyLength uint16) (LinkStateRequestMsg, int, error) { + reqs := make(LinkStateRequestMsg, 0) + + var readBytes int + for readBytes < int(bodyLength) { + req, n, err := DeserializeLinkStateRequest(buf) + if err != nil { + return nil, readBytes, errors.Wrap(err, "unable to decode LinkStateRequest") + } + reqs = append(reqs, req) + readBytes += n + } + + return reqs, readBytes, nil +} + +type LinkStateRequest struct { + LSType LSAType + LinkStateID ID + AdvertisingRouter ID +} + +func (x *LinkStateRequest) Serialize(buf *bytes.Buffer) { + buf.Write([]byte{0, 0}) // 2 bytes reserved + x.LSType.Serialize(buf) + x.LinkStateID.Serialize(buf) + x.AdvertisingRouter.Serialize(buf) +} + +func DeserializeLinkStateRequest(buf *bytes.Buffer) (LinkStateRequest, int, error) { + pdu := LinkStateRequest{} + + var readBytes int + var err error + var fields []interface{} + + fields = []interface{}{ + new(uint16), // 2 bytes reserved + &pdu.LSType, + &pdu.LinkStateID, + &pdu.AdvertisingRouter, + } + + err = decode.Decode(buf, fields) + if err != nil { + return pdu, readBytes, fmt.Errorf("Unable to decode fields: %v", err) + } + readBytes += 12 + + return pdu, readBytes, nil +} + +type LinkStateUpdate []*LSA + +func (x LinkStateUpdate) Serialize(buf *bytes.Buffer) { + buf.Write(convert.Uint32Byte(uint32(len(x)))) + for i := range x { + x[i].Serialize(buf) + } +} + +func DeserializeLinkStateUpdate(buf *bytes.Buffer) (LinkStateUpdate, int, error) { + lsas := make(LinkStateUpdate, 0) + + var lsaCount uint32 + if err := binary.Read(buf, binary.BigEndian, &lsaCount); err != nil { + return nil, 0, errors.Wrap(err, "unable to decode LSA count") + } + readBytes := 4 + + for i := 0; i < int(lsaCount); i++ { + tlv, n, err := DeserializeLSA(buf) + if err != nil { + return nil, 0, errors.Wrap(err, "unable to decode LSA") + } + lsas = append(lsas, tlv) + readBytes += n + } + + return lsas, readBytes, nil +} + +type LinkStateAcknowledgement []*LSA + +func (x LinkStateAcknowledgement) Serialize(buf *bytes.Buffer) { + for i := range x { + x[i].SerializeHeader(buf) + } +} + +func DeserializeLinkStateAcknowledgement(buf *bytes.Buffer, bodyLength uint16) (LinkStateAcknowledgement, int, error) { + lsas := make(LinkStateAcknowledgement, 0) + + var readBytes int + + for i := 0; i < int(bodyLength); { + tlv, n, err := DeserializeLSAHeader(buf) + if err != nil { + return nil, 0, errors.Wrap(err, "Unable to decode") + } + lsas = append(lsas, tlv) + i += n + readBytes += n + } + + return lsas, readBytes, nil +} diff --git a/protocols/ospf/packetv3/smoke_test.go b/protocols/ospf/packetv3/smoke_test.go new file mode 100644 index 00000000..c48e57a6 --- /dev/null +++ b/protocols/ospf/packetv3/smoke_test.go @@ -0,0 +1,68 @@ +package packetv3_test + +import ( + "bytes" + "fmt" + "io" + "os" + "testing" + + ospf "github.com/bio-routing/bio-rd/protocols/ospf/packetv3" + "github.com/bio-routing/bio-rd/protocols/ospf/packetv3/fixtures" +) + +var files = []string{ + "OSPFv3_multipoint_adjacencies.cap", + "OSPFv3_broadcast_adjacency.cap", + "OSPFv3_NBMA_adjacencies.cap", +} + +var dir string + +func init() { + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + dir = cwd + "/fixtures/" +} + +func TestDecodeDumps(t *testing.T) { + for _, path := range files { + t.Run(path, func(t *testing.T) { + testDecodeFile(t, dir+path) + }) + } +} + +func testDecodeFile(t *testing.T, path string) { + fmt.Printf("Testing on file: %s\n", path) + r, f := fixtures.PacketReader(t, path) + defer f.Close() + + var packetCount int + for { + data, _, err := r.ReadPacketData() + if err == io.EOF { + break + } + if err != nil { + t.Error(err) + return + } + + t.Run(fmt.Sprintf("Packet_%03d", packetCount+1), func(t *testing.T) { + payload, src, dst, err := fixtures.Payload(data) + if err != nil { + t.Error(err) + return + } + + buf := bytes.NewBuffer(payload) + if _, _, err := ospf.DeserializeOSPFv3Message(buf, src, dst); err != nil { + t.Error(err) + } + }) + packetCount++ + } +} diff --git a/util/checksum/ipv6_upperlayer.go b/util/checksum/ipv6_upperlayer.go new file mode 100644 index 00000000..7dc802fc --- /dev/null +++ b/util/checksum/ipv6_upperlayer.go @@ -0,0 +1,72 @@ +// taken from https://go.googlesource.com/net/+/refs/changes/17/112817/2/ipv4/header.go#102 +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package checksum + +import ( + "encoding/binary" + + "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/tflow2/convert" + "golang.org/x/net/icmp" +) + +func IPv6PseudoHeader(src, dst net.IP, lenght uint32, proto uint8) []byte { + header := icmp.IPv6PseudoHeader(src.ToNetIP(), dst.ToNetIP()) + + lenBytes := convert.Uint32Byte(uint32(lenght)) + copy(header[32:36], lenBytes) + + header[len(header)-1] = proto // next header + return header +} + +// IPv6UpperLayerChecksum calculates the checksum for +// an upper layer payload in IPv6 according to RFC 2460 Section 8.1 +// +// Specify the position of the checksum using sumAt. +// Use a value lower than 0 to not skip a checksum field. +func IPv6UpperLayerChecksum(src, dst net.IP, proto uint8, pl []byte) uint16 { + header := IPv6PseudoHeader(src, dst, uint32(len(pl)), proto) + b := append(header, pl...) + + // Algorithm taken from: https://en.wikipedia.org/wiki/IPv4_header_checksum. + // "First calculate the sum of each 16 bit value within the header, + // skipping only the checksum field itself." + var chk uint32 + for i := 0; i < len(b); i += 2 { + chk += uint32(binary.BigEndian.Uint16(b[i : i+2])) + } + + // "The first 4 bits are the carry and will be added to the rest of + // the value." + carry := uint16(chk >> 16) + sum := carry + uint16(chk&0x0ffff) + + // "Next, we flip every bit in that value, to obtain the checksum." + return uint16(^sum) +}