Merge pull request #96 from jsimonetti/mdl-mpls

rtnetlink: implement MPLSNextHop encoding and decoding
This commit is contained in:
Jeroen Simonetti 2020-11-19 10:57:10 +01:00 committed by GitHub
commit 9af4a03067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 228 additions and 8 deletions

3
go.mod
View File

@ -5,5 +5,6 @@ go 1.12
require (
github.com/google/go-cmp v0.5.3
github.com/mdlayher/netlink v1.1.1
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7
)

7
go.sum
View File

@ -4,6 +4,7 @@ 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/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
@ -24,6 +25,8 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjut
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -35,8 +38,8 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7 h1:Z991aAXPjz0tLnj74pVXW3eWJ5lHMIBvbRfMq4M2jHA=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -65,6 +65,9 @@ const (
IFLA_XDP_FLAGS = linux.IFLA_XDP_FLAGS
IFLA_XDP_PROG_ID = linux.IFLA_XDP_PROG_ID
IFLA_XDP_EXPECTED_FD = linux.IFLA_XDP_EXPECTED_FD
LWTUNNEL_ENCAP_MPLS = linux.LWTUNNEL_ENCAP_MPLS
MPLS_IPTUNNEL_DST = linux.MPLS_IPTUNNEL_DST
MPLS_IPTUNNEL_TTL = linux.MPLS_IPTUNNEL_TTL
NDA_UNSPEC = linux.NDA_UNSPEC
NDA_DST = linux.NDA_DST
NDA_LLADDR = linux.NDA_LLADDR
@ -72,6 +75,8 @@ const (
NDA_IFINDEX = linux.NDA_IFINDEX
RTA_UNSPEC = linux.RTA_UNSPEC
RTA_DST = linux.RTA_DST
RTA_ENCAP = linux.RTA_ENCAP
RTA_ENCAP_TYPE = linux.RTA_ENCAP_TYPE
RTA_PREFSRC = linux.RTA_PREFSRC
RTA_GATEWAY = linux.RTA_GATEWAY
RTA_OIF = linux.RTA_OIF

View File

@ -61,6 +61,9 @@ const (
IFLA_XDP_FLAGS = 0x3
IFLA_XDP_PROG_ID = 0x4
IFLA_XDP_EXPECTED_FD = 0x8
LWTUNNEL_ENCAP_MPLS = 0x1
MPLS_IPTUNNEL_DST = 0x1
MPLS_IPTUNNEL_TTL = 0x2
NDA_UNSPEC = 0x0
NDA_DST = 0x1
NDA_LLADDR = 0x2
@ -68,6 +71,8 @@ const (
NDA_IFINDEX = 0x8
RTA_UNSPEC = 0x0
RTA_DST = 0x1
RTA_ENCAP = 0x16
RTA_ENCAP_TYPE = 0x15
RTA_PREFSRC = 0x7
RTA_GATEWAY = 0x5
RTA_OIF = 0x4

126
route.go
View File

@ -1,6 +1,7 @@
package rtnetlink
import (
"encoding/binary"
"errors"
"net"
"unsafe"
@ -348,8 +349,9 @@ type RTNextHop struct {
// NextHop wraps struct rtnexthop to provide access to nested attributes
type NextHop struct {
Hop RTNextHop // a rtnexthop struct
Gateway net.IP // that struct's nested Gateway attribute
Hop RTNextHop // a rtnexthop struct
Gateway net.IP // that struct's nested Gateway attribute
MPLS []MPLSNextHop // Any MPLS next hops for a route.
}
func (a *RouteAttributes) encodeMultipath() ([]byte, error) {
@ -359,11 +361,18 @@ func (a *RouteAttributes) encodeMultipath() ([]byte, error) {
// compute the length of each (rtnexthop, attributes) pair.
ae := netlink.NewAttributeEncoder()
if a.Gateway != nil {
if nh.Gateway != nil {
// TODO(mdlayher): more validation.
ae.Bytes(unix.RTA_GATEWAY, nh.Gateway)
}
if len(nh.MPLS) > 0 {
// TODO(mdlayher): validation over different encapsulation types,
// and ensure that only one can be set.
ae.Uint16(unix.RTA_ENCAP_TYPE, unix.LWTUNNEL_ENCAP_MPLS)
ae.Nested(unix.RTA_ENCAP, nh.encodeEncap)
}
ab, err := ae.Encode()
if err != nil {
return nil, err
@ -417,16 +426,26 @@ func (a *RouteAttributes) parseMultipath(b []byte) error {
return mpp.Err()
}
// rtnexthop payload is at least one nested attribute RTA_GATEWAY
// possibly others?
// decode decodes netlink attribute values into a NextHop.
func (nh *NextHop) decode(ad *netlink.AttributeDecoder) error {
if ad == nil {
// Invalid decoder, do nothing.
return nil
}
// If encapsulation is present, we won't know how to deal with it until we
// identify the right type and then later parse the nested attribute bytes.
var (
encapType uint16
encapBuf []byte
)
for ad.Next() {
switch ad.Type() {
case unix.RTA_ENCAP:
encapBuf = ad.Bytes()
case unix.RTA_ENCAP_TYPE:
encapType = ad.Uint16()
case unix.RTA_GATEWAY:
l := len(ad.Bytes())
if l != 4 && l != 16 {
@ -437,6 +456,103 @@ func (nh *NextHop) decode(ad *netlink.AttributeDecoder) error {
}
}
if err := ad.Err(); err != nil {
return err
}
if encapType != 0 && encapBuf != nil {
// Found encapsulation, start decoding it from the buffer.
return nh.decodeEncap(encapType, encapBuf)
}
return nil
}
// An MPLSNextHop is a route next hop using MPLS encapsulation.
type MPLSNextHop struct {
Label int
TrafficClass int
BottomOfStack bool
TTL uint8
}
// TODO(mdlayher): MPLSNextHop TTL vs MPLS_IPTUNNEL_TTL. What's the difference?
// encodeEncap encodes netlink attribute values related to encapsulation from
// a NextHop.
func (nh *NextHop) encodeEncap(ae *netlink.AttributeEncoder) error {
// TODO: this only handles MPLS encapsulation as that is all we support.
// Allocate enough space for an MPLS label stack.
var (
i int
b = make([]byte, 4*len(nh.MPLS))
)
for _, mnh := range nh.MPLS {
// Pack the following:
// - label: 20 bits
// - traffic class: 3 bits
// - bottom-of-stack: 1 bit
// - TTL: 8 bits
binary.BigEndian.PutUint32(b[i:i+4], uint32(mnh.Label)<<12)
b[i+2] |= byte(mnh.TrafficClass) << 1
if mnh.BottomOfStack {
b[i+2] |= 1
}
b[i+3] = mnh.TTL
// Advance in the buffer to begin storing the next label.
i += 4
}
// Finally store the output bytes.
ae.Bytes(unix.MPLS_IPTUNNEL_DST, b)
return nil
}
// decodeEncap decodes netlink attribute values related to encapsulation into a
// NextHop.
func (nh *NextHop) decodeEncap(typ uint16, b []byte) error {
if typ != unix.LWTUNNEL_ENCAP_MPLS {
// TODO: handle other encapsulation types as needed.
return nil
}
// MPLS labels are stored as big endian bytes.
ad, err := netlink.NewAttributeDecoder(b)
if err != nil {
return err
}
for ad.Next() {
switch ad.Type() {
case unix.MPLS_IPTUNNEL_DST:
// Every 4 bytes stores another MPLS label, so make sure the stored
// bytes are divisible by exactly 4.
b := ad.Bytes()
if len(b)%4 != 0 {
return errInvalidRouteMessageAttr
}
for i := 0; i < len(b); i += 4 {
n := binary.BigEndian.Uint32(b[i : i+4])
// For reference, see:
// https://en.wikipedia.org/wiki/Multiprotocol_Label_Switching#Operation
nh.MPLS = append(nh.MPLS, MPLSNextHop{
Label: int(n) >> 12,
TrafficClass: int(n & 0xe00 >> 9),
BottomOfStack: n&0x100 != 0,
TTL: uint8(n & 0xff),
})
}
}
}
return ad.Err()
}

View File

@ -204,6 +204,96 @@ func TestRouteMessageMarshalUnmarshalBinary(t *testing.T) {
}
}
func TestRouteMessageMarshalRoundTrip(t *testing.T) {
skipBigEndian(t)
// The above tests begin with unmarshaling raw bytes and are more
// comprehensive, but due to the complexity of nested route message
// attributes and structures, it has become rather difficult to maintain
// over time. These tests will focus on a subset of that functionality to
// ensure that marshaling and unmarshaling perform symmetrical operations
// given a proper Go type as input, rather than raw bytes.
tests := []struct {
name string
m *RouteMessage
}{
{
name: "multipath MPLS",
m: &RouteMessage{
Attributes: RouteAttributes{
Multipath: []NextHop{
{
Hop: RTNextHop{
Length: 48,
IfIndex: 1,
},
Gateway: net.IPv4(10, 0, 0, 2),
MPLS: []MPLSNextHop{{
Label: 1,
TrafficClass: 1,
BottomOfStack: true,
TTL: 1,
}},
},
{
Hop: RTNextHop{
Length: 52,
IfIndex: 2,
},
Gateway: net.IPv4(10, 0, 0, 3),
MPLS: []MPLSNextHop{
{
Label: 1,
TrafficClass: 1,
TTL: 1,
},
{
Label: 2,
TrafficClass: 2,
BottomOfStack: true,
TTL: 2,
},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// First do a marshaling and unmarshaling round trip to ensure
// the inputs and outputs are identical.
b1, err := tt.m.MarshalBinary()
if err != nil {
t.Fatalf("failed to marshal test message: %v", err)
}
var m RouteMessage
if err := m.UnmarshalBinary(b1); err != nil {
t.Fatalf("failed to unmarshal: %v", err)
}
if diff := cmp.Diff(tt.m, &m); diff != "" {
t.Fatalf("unexpected RouteMessage after round-trip (-want +got):\n%s", diff)
}
// Then compare the results of the first marshaled bytes against
// the newly marshaled bytes.
b2, err := m.MarshalBinary()
if err != nil {
t.Fatalf("failed to marshal parsed message: %v", err)
}
if diff := cmp.Diff(b1, b2); diff != "" {
t.Fatalf("unexpected final raw byte output (-want +got):\n%s", diff)
}
})
}
}
func TestRouteMessageUnmarshalBinaryErrors(t *testing.T) {
skipBigEndian(t)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.