diff --git a/pkg/packet/bgp/bgp.go b/pkg/packet/bgp/bgp.go index d9fca178..23960be4 100644 --- a/pkg/packet/bgp/bgp.go +++ b/pkg/packet/bgp/bgp.go @@ -32,8 +32,21 @@ import ( ) type MarshallingOption struct { - AddPath map[RouteFamily]BGPAddPathMode - Attributes map[BGPAttrType]bool + AddPath map[RouteFamily]BGPAddPathMode + Attributes map[BGPAttrType]bool + ImplicitPrefix AddrPrefixInterface +} + +// GetImplicitPrefix gets the implicit prefix associated with decoding/serialisation. This is used for +// the MRT representation of MP_REACH_NLRI (see RFC 6396 4.3.4). +func GetImplicitPrefix(options []*MarshallingOption) AddrPrefixInterface { + for _, opt := range options { + if opt != nil && opt.ImplicitPrefix != nil { + return opt.ImplicitPrefix + } + } + + return nil } func IsAddPathEnabled(decode bool, f RouteFamily, options []*MarshallingOption) bool { @@ -10952,19 +10965,35 @@ func (p *PathAttributeMpReachNLRI) DecodeFromBytes(data []byte, options ...*Mars if p.Length < 3 { return NewMessageError(eCode, eSubCode, value, "mpreach header length is short") } - afi := binary.BigEndian.Uint16(value[0:2]) - safi := value[2] + + var afi uint16 + var safi uint8 + + // In MRT dumps, AFI+SAFI+NLRI is implicit based on RIB Entry Header, see RFC 6396 4.3.4 + implicitPrefix := GetImplicitPrefix(options) + if implicitPrefix == nil { + afi = binary.BigEndian.Uint16(value[0:2]) + safi = value[2] + + value = value[3:] + } else { + afi = implicitPrefix.AFI() + safi = implicitPrefix.SAFI() + + p.Value = []AddrPrefixInterface{implicitPrefix} + } + p.AFI = afi p.SAFI = safi _, err = NewPrefixFromRouteFamily(afi, safi) if err != nil { return NewMessageError(eCode, BGP_ERROR_SUB_INVALID_NETWORK_FIELD, eData, err.Error()) } - nexthoplen := int(value[3]) - if len(value) < 4+nexthoplen { + nexthoplen := int(value[0]) + if len(value) < 1+nexthoplen { return NewMessageError(eCode, eSubCode, value, "mpreach nexthop length is short") } - nexthopbin := value[4 : 4+nexthoplen] + nexthopbin := value[1 : 1+nexthoplen] if nexthoplen > 0 { v4addrlen := 4 v6addrlen := 16 @@ -10984,7 +11013,13 @@ func (p *PathAttributeMpReachNLRI) DecodeFromBytes(data []byte, options ...*Mars return NewMessageError(eCode, eSubCode, value, "mpreach nexthop length is incorrect") } } - value = value[4+nexthoplen:] + + // NLRI implicit for MRT dumps + if implicitPrefix != nil { + return nil + } + + value = value[1+nexthoplen:] // skip reserved if len(value) == 0 { return NewMessageError(eCode, eSubCode, value, "no skip byte") @@ -11030,27 +11065,40 @@ func (p *PathAttributeMpReachNLRI) Serialize(options ...*MarshallingOption) ([]b if p.LinkLocalNexthop != nil && p.LinkLocalNexthop.IsLinkLocalUnicast() { nexthoplen = BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL } - buf := make([]byte, 4+nexthoplen) - binary.BigEndian.PutUint16(buf[0:], afi) - buf[2] = safi - buf[3] = uint8(nexthoplen) + var buf []byte + includeNLRI := GetImplicitPrefix(options) == nil + if includeNLRI { + family := make([]byte, 3) + binary.BigEndian.PutUint16(family[0:], afi) + family[2] = safi + + buf = append(buf, family...) + } + buf = append(buf, uint8(nexthoplen)) if nexthoplen != 0 { + nexthop := make([]byte, nexthoplen) + if p.Nexthop.To4() == nil { - copy(buf[4+offset:], p.Nexthop.To16()) + copy(nexthop[offset:], p.Nexthop.To16()) + if nexthoplen == BGP_ATTR_NHLEN_IPV6_GLOBAL_AND_LL { - copy(buf[4+offset+16:], p.LinkLocalNexthop.To16()) + copy(nexthop[offset+16:], p.LinkLocalNexthop.To16()) } } else { - copy(buf[4+offset:], p.Nexthop) + copy(nexthop[offset:], p.Nexthop) } + + buf = append(buf, nexthop...) } - buf = append(buf, 0) - for _, prefix := range p.Value { - pbuf, err := prefix.Serialize(options...) - if err != nil { - return nil, err + if includeNLRI { + buf = append(buf, 0) + for _, prefix := range p.Value { + pbuf, err := prefix.Serialize(options...) + if err != nil { + return nil, err + } + buf = append(buf, pbuf...) } - buf = append(buf, pbuf...) } return p.PathAttribute.Serialize(buf, options...) } diff --git a/pkg/packet/bgp/bgp_test.go b/pkg/packet/bgp/bgp_test.go index 441e0911..b999d099 100644 --- a/pkg/packet/bgp/bgp_test.go +++ b/pkg/packet/bgp/bgp_test.go @@ -1105,6 +1105,38 @@ func Test_MpReachNLRIWithIPv4PrefixWithIPv6Nexthop(t *testing.T) { assert.Equal(bufin, bufout) } +func Test_MpReachNLRIWithImplicitPrefix(t *testing.T) { + assert := assert.New(t) + bufin := []byte{ + 0x80, 0x0e, 0x11, // flags(1), type(1), length(1) + 0x10, // nexthoplen(1) + 0x20, 0x01, 0x0d, 0xb8, // nexthop(32) + 0x00, 0x01, 0x00, 0x00, // = "2001:db8:1::1" + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + } + prefix := NewIPAddrPrefix(24, "192.168.10.0") + // Test DecodeFromBytes() + p := &PathAttributeMpReachNLRI{} + option := &MarshallingOption{ImplicitPrefix: prefix} + err := p.DecodeFromBytes(bufin, option) + assert.Nil(err) + // Test decoded values + assert.Equal(BGPAttrFlag(0x80), p.Flags) + assert.Equal(BGPAttrType(0xe), p.Type) + assert.Equal(uint16(0x11), p.Length) + assert.Equal(prefix.AFI(), p.AFI) + assert.Equal(prefix.SAFI(), p.SAFI) + assert.Equal(net.ParseIP("2001:db8:1::1"), p.Nexthop) + value := []AddrPrefixInterface{prefix} + assert.Equal(value, p.Value) + // Test Serialize() + bufout, err := p.Serialize(option) + assert.Nil(err) + // Test serialised value + assert.Equal(bufin, bufout) +} + func Test_ParseRouteDistinguisher(t *testing.T) { assert := assert.New(t) diff --git a/pkg/packet/mrt/mrt.go b/pkg/packet/mrt/mrt.go index adea3a1d..cad57709 100644 --- a/pkg/packet/mrt/mrt.go +++ b/pkg/packet/mrt/mrt.go @@ -365,7 +365,7 @@ type RibEntry struct { var errNotAllRibEntryBytesAvailable = errors.New("not all RibEntry bytes are available") -func (e *RibEntry) DecodeFromBytes(data []byte) ([]byte, error) { +func (e *RibEntry) DecodeFromBytes(data []byte, prefix bgp.AddrPrefixInterface) ([]byte, error) { if len(data) < 8 { return nil, errNotAllRibEntryBytesAvailable } @@ -384,7 +384,7 @@ func (e *RibEntry) DecodeFromBytes(data []byte) ([]byte, error) { if err != nil { return nil, err } - err = p.DecodeFromBytes(data) + err = p.DecodeFromBytes(data, &bgp.MarshallingOption{ImplicitPrefix: prefix}) if err != nil { return nil, err } @@ -398,21 +398,11 @@ func (e *RibEntry) DecodeFromBytes(data []byte) ([]byte, error) { return data, nil } -func (e *RibEntry) Serialize() ([]byte, error) { +func (e *RibEntry) Serialize(prefix bgp.AddrPrefixInterface) ([]byte, error) { pbuf := make([]byte, 0) totalLen := 0 for _, pattr := range e.PathAttributes { - // TODO special modification is needed for MP_REACH_NLRI - // but also Quagga doesn't implement this. - // - // RFC 6396 4.3.4 - // There is one exception to the encoding of BGP attributes for the BGP - // MP_REACH_NLRI attribute (BGP Type Code 14). - // Since the AFI, SAFI, and NLRI information is already encoded - // in the RIB Entry Header or RIB_GENERIC Entry Header, - // only the Next Hop Address Length and Next Hop Address fields are included. - - pb, err := pattr.Serialize() + pb, err := pattr.Serialize(&bgp.MarshallingOption{ImplicitPrefix: prefix}) if err != nil { return nil, err } @@ -492,7 +482,7 @@ func (u *Rib) DecodeFromBytes(data []byte) error { e := &RibEntry{ isAddPath: u.isAddPath, } - data, err = e.DecodeFromBytes(data) + data, err = e.DecodeFromBytes(data, prefix) if err != nil { return err } @@ -524,7 +514,7 @@ func (u *Rib) Serialize() ([]byte, error) { } buf = append(buf, bbuf...) for _, entry := range u.Entries { - bbuf, err = entry.Serialize() + bbuf, err = entry.Serialize(u.Prefix) if err != nil { return nil, err } diff --git a/pkg/packet/mrt/mrt_test.go b/pkg/packet/mrt/mrt_test.go index 275b094e..2020ecfb 100644 --- a/pkg/packet/mrt/mrt_test.go +++ b/pkg/packet/mrt/mrt_test.go @@ -116,13 +116,13 @@ func TestMrtRibEntry(t *testing.T) { } e1 := NewRibEntry(1, uint32(time.Now().Unix()), 0, p, false) - b1, err := e1.Serialize() + b1, err := e1.Serialize(nil) if err != nil { t.Fatal(err) } e2 := &RibEntry{} - rest, err := e2.DecodeFromBytes(b1) + rest, err := e2.DecodeFromBytes(b1, nil) if err != nil { t.Fatal(err) } @@ -145,13 +145,13 @@ func TestMrtRibEntryWithAddPath(t *testing.T) { bgp.NewPathAttributeLocalPref(1 << 22), } e1 := NewRibEntry(1, uint32(time.Now().Unix()), 200, p, true) - b1, err := e1.Serialize() + b1, err := e1.Serialize(nil) if err != nil { t.Fatal(err) } e2 := &RibEntry{isAddPath: true} - rest, err := e2.DecodeFromBytes(b1) + rest, err := e2.DecodeFromBytes(b1, nil) if err != nil { t.Fatal(err) }