Support parsing NDP Route Information option

This change prepares for a later change which supports the NDP
Route Information option to discover more-specific routes, as
per RFC 4191.

Updates #6172.

PiperOrigin-RevId: 382225812
This commit is contained in:
Ghanan Gowripalan
2021-06-29 21:32:09 -07:00
committed by gVisor bot
parent 3e5a6981d6
commit 66a79461a2
2 changed files with 364 additions and 0 deletions
+145
View File
@@ -233,6 +233,17 @@ func (i *NDPOptionIterator) Next() (NDPOption, bool, error) {
case ndpNonceOptionType:
return NDPNonceOption(body), false, nil
case ndpRouteInformationType:
if numBodyBytes > ndpRouteInformationMaxLength {
return nil, true, fmt.Errorf("got %d bytes for NDP Route Information option's body, expected at max %d bytes: %w", numBodyBytes, ndpRouteInformationMaxLength, ErrNDPOptMalformedBody)
}
opt := NDPRouteInformation(body)
if err := opt.hasError(); err != nil {
return nil, true, err
}
return opt, false, nil
case ndpPrefixInformationType:
// Make sure the length of a Prefix Information option
// body is ndpPrefixInformationLength, as per RFC 4861
@@ -930,3 +941,137 @@ func isUpperLetter(b byte) bool {
func isDigit(b byte) bool {
return b >= '0' && b <= '9'
}
// As per RFC 4191 section 2.3,
//
// 2.3. Route Information Option
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Type | Length | Prefix Length |Resvd|Prf|Resvd|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Route Lifetime |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Prefix (Variable Length) |
// . .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Fields:
//
// Type 24
//
//
// Length 8-bit unsigned integer. The length of the option
// (including the Type and Length fields) in units of 8
// octets. The Length field is 1, 2, or 3 depending on the
// Prefix Length. If Prefix Length is greater than 64, then
// Length must be 3. If Prefix Length is greater than 0,
// then Length must be 2 or 3. If Prefix Length is zero,
// then Length must be 1, 2, or 3.
const (
ndpRouteInformationType = ndpOptionIdentifier(24)
ndpRouteInformationMaxLength = 22
ndpRouteInformationPrefixLengthIdx = 0
ndpRouteInformationFlagsIdx = 1
ndpRouteInformationPrfShift = 3
ndpRouteInformationPrfMask = 3 << ndpRouteInformationPrfShift
ndpRouteInformationRouteLifetimeIdx = 2
ndpRouteInformationRoutePrefixIdx = 6
)
// NDPRouteInformation is the NDP Router Information option, as defined by
// RFC 4191 section 2.3.
type NDPRouteInformation []byte
func (NDPRouteInformation) kind() ndpOptionIdentifier {
return ndpRouteInformationType
}
func (o NDPRouteInformation) length() int {
return len(o)
}
func (o NDPRouteInformation) serializeInto(b []byte) int {
return copy(b, o)
}
// String implements fmt.Stringer.
func (o NDPRouteInformation) String() string {
return fmt.Sprintf("%T", o)
}
// PrefixLength returns the length of the prefix.
func (o NDPRouteInformation) PrefixLength() uint8 {
return o[ndpRouteInformationPrefixLengthIdx]
}
// RoutePreference returns the preference of the route over other routes to the
// same destination but through a different router.
func (o NDPRouteInformation) RoutePreference() NDPRoutePreference {
return NDPRoutePreference((o[ndpRouteInformationFlagsIdx] & ndpRouteInformationPrfMask) >> ndpRouteInformationPrfShift)
}
// RouteLifetime returns the lifetime of the route.
//
// Note, a value of 0 implies the route is now invalid and a value of
// infinity/forever is represented by NDPInfiniteLifetime.
func (o NDPRouteInformation) RouteLifetime() time.Duration {
return time.Second * time.Duration(binary.BigEndian.Uint32(o[ndpRouteInformationRouteLifetimeIdx:]))
}
// Prefix returns the prefix of the destination subnet this route is for.
func (o NDPRouteInformation) Prefix() (tcpip.Subnet, error) {
prefixLength := int(o.PrefixLength())
if max := IPv6AddressSize * 8; prefixLength > max {
return tcpip.Subnet{}, fmt.Errorf("got prefix length = %d, want <= %d", prefixLength, max)
}
prefix := o[ndpRouteInformationRoutePrefixIdx:]
var addrBytes [IPv6AddressSize]byte
if n := copy(addrBytes[:], prefix); n != len(prefix) {
panic(fmt.Sprintf("got copy(addrBytes, prefix) = %d, want = %d", n, len(prefix)))
}
return tcpip.AddressWithPrefix{
Address: tcpip.Address(addrBytes[:]),
PrefixLen: prefixLength,
}.Subnet(), nil
}
func (o NDPRouteInformation) hasError() error {
l := len(o)
if l < ndpRouteInformationRoutePrefixIdx {
return fmt.Errorf("%T too small, got = %d bytes: %w", o, l, ErrNDPOptMalformedBody)
}
prefixLength := int(o.PrefixLength())
if max := IPv6AddressSize * 8; prefixLength > max {
return fmt.Errorf("got prefix length = %d, want <= %d: %w", prefixLength, max, ErrNDPOptMalformedBody)
}
// Length 8-bit unsigned integer. The length of the option
// (including the Type and Length fields) in units of 8
// octets. The Length field is 1, 2, or 3 depending on the
// Prefix Length. If Prefix Length is greater than 64, then
// Length must be 3. If Prefix Length is greater than 0,
// then Length must be 2 or 3. If Prefix Length is zero,
// then Length must be 1, 2, or 3.
l += 2 // Add 2 bytes for the type and length bytes.
lengthField := l / lengthByteUnits
if prefixLength > 64 {
if lengthField != 3 {
return fmt.Errorf("Length field must be 3 when Prefix Length (%d) is > 64 (got = %d): %w", prefixLength, lengthField, ErrNDPOptMalformedBody)
}
} else if prefixLength > 0 {
if lengthField != 2 && lengthField != 3 {
return fmt.Errorf("Length field must be 2 or 3 when Prefix Length (%d) is between 0 and 64 (got = %d): %w", prefixLength, lengthField, ErrNDPOptMalformedBody)
}
} else if lengthField == 0 || lengthField > 3 {
return fmt.Errorf("Length field must be 1, 2, or 3 when Prefix Length is zero (got = %d): %w", lengthField, ErrNDPOptMalformedBody)
}
return nil
}
+219
View File
@@ -21,6 +21,7 @@ import (
"fmt"
"io"
"regexp"
"strings"
"testing"
"time"
@@ -58,6 +59,224 @@ func TestNDPNeighborSolicit(t *testing.T) {
}
}
func TestNDPRouteInformationOption(t *testing.T) {
tests := []struct {
name string
length uint8
prefixLength uint8
prf NDPRoutePreference
lifetimeS uint32
prefixBytes []byte
expectedPrefix tcpip.Subnet
expectedErr error
}{
{
name: "Length=1 with Prefix Length = 0",
length: 1,
prefixLength: 0,
prf: MediumRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedPrefix: IPv6EmptySubnet,
},
{
name: "Length=1 but Prefix Length > 0",
length: 1,
prefixLength: 1,
prf: MediumRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedErr: ErrNDPOptMalformedBody,
},
{
name: "Length=2 with Prefix Length = 0",
length: 2,
prefixLength: 0,
prf: MediumRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedPrefix: IPv6EmptySubnet,
},
{
name: "Length=2 with Prefix Length in [1, 64] (1)",
length: 2,
prefixLength: 1,
prf: LowRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)),
PrefixLen: 1,
}.Subnet(),
},
{
name: "Length=2 with Prefix Length in [1, 64] (64)",
length: 2,
prefixLength: 64,
prf: HighRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)),
PrefixLen: 64,
}.Subnet(),
},
{
name: "Length=2 with Prefix Length > 64",
length: 2,
prefixLength: 65,
prf: HighRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedErr: ErrNDPOptMalformedBody,
},
{
name: "Length=3 with Prefix Length = 0",
length: 3,
prefixLength: 0,
prf: MediumRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedPrefix: IPv6EmptySubnet,
},
{
name: "Length=3 with Prefix Length in [1, 64] (1)",
length: 3,
prefixLength: 1,
prf: LowRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)),
PrefixLen: 1,
}.Subnet(),
},
{
name: "Length=3 with Prefix Length in [1, 64] (64)",
length: 3,
prefixLength: 64,
prf: HighRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)),
PrefixLen: 64,
}.Subnet(),
},
{
name: "Length=3 with Prefix Length in [65, 128] (65)",
length: 3,
prefixLength: 65,
prf: HighRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)),
PrefixLen: 65,
}.Subnet(),
},
{
name: "Length=3 with Prefix Length in [65, 128] (128)",
length: 3,
prefixLength: 128,
prf: HighRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address(strings.Repeat("\x00", IPv6AddressSize)),
PrefixLen: 128,
}.Subnet(),
},
{
name: "Length=3 with (invalid) Prefix Length > 128",
length: 3,
prefixLength: 129,
prf: HighRoutePreference,
lifetimeS: 1,
prefixBytes: nil,
expectedErr: ErrNDPOptMalformedBody,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
expectedRouteInformationBytes := [...]byte{
// Type, Length
24, test.length,
// Prefix Length, Prf
uint8(test.prefixLength), uint8(test.prf) << 3,
// Route Lifetime
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
}
binary.BigEndian.PutUint32(expectedRouteInformationBytes[4:], test.lifetimeS)
_ = copy(expectedRouteInformationBytes[8:], test.prefixBytes)
opts := NDPOptions(expectedRouteInformationBytes[:test.length*lengthByteUnits])
it, err := opts.Iter(false)
if err != nil {
t.Fatalf("got Iter(false) = (_, %s), want = (_, nil)", err)
}
opt, done, err := it.Next()
if !errors.Is(err, test.expectedErr) {
t.Fatalf("got Next() = (_, _, %s), want = (_, _, %s)", err, test.expectedErr)
}
if want := test.expectedErr != nil; done != want {
t.Fatalf("got Next() = (_, %t, _), want = (_, %t, _)", done, want)
}
if test.expectedErr != nil {
return
}
if got := opt.kind(); got != ndpRouteInformationType {
t.Errorf("got kind() = %d, want = %d", got, ndpRouteInformationType)
}
ri, ok := opt.(NDPRouteInformation)
if !ok {
t.Fatalf("got opt = %T, want = NDPRouteInformation", opt)
}
if got := ri.PrefixLength(); got != test.prefixLength {
t.Errorf("got PrefixLength() = %d, want = %d", got, test.prefixLength)
}
if got := ri.RoutePreference(); got != test.prf {
t.Errorf("got RoutePreference() = %d, want = %d", got, test.prf)
}
if got, want := ri.RouteLifetime(), time.Duration(test.lifetimeS)*time.Second; got != want {
t.Errorf("got RouteLifetime() = %s, want = %s", got, want)
}
if got, err := ri.Prefix(); err != nil {
t.Errorf("Prefix(): %s", err)
} else if got != test.expectedPrefix {
t.Errorf("got Prefix() = %s, want = %s", got, test.expectedPrefix)
}
// Iterator should not return anything else.
{
next, done, err := it.Next()
if err != nil {
t.Errorf("got Next() = (_, _, %s), want = (_, _, nil)", err)
}
if !done {
t.Error("got Next() = (_, false, _), want = (_, true, _)")
}
if next != nil {
t.Errorf("got Next() = (%x, _, _), want = (nil, _, _)", next)
}
}
})
}
}
// TestNDPNeighborAdvert tests the functions of NDPNeighborAdvert.
func TestNDPNeighborAdvert(t *testing.T) {
b := []byte{