mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
3e5a6981d6
commit
66a79461a2
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user