mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Validate incoming NDP Router Advertisements, as per RFC 4861 section 6.1.2
This change validates incoming NDP Router Advertisements as per RFC 4861 section 6.1.2. It also includes the skeleton to handle Router Advertiements that arrive on some NIC. Tests: Unittest to make sure only valid NDP Router Advertisements are received/ not dropped. PiperOrigin-RevId: 278891972
This commit is contained in:
committed by
gVisor bot
parent
57f6dbc4be
commit
a824b48cea
@@ -86,7 +86,8 @@ func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.V
|
||||
|
||||
// As per RFC 4861 sections 4.1 - 4.5, 6.1.1, 6.1.2, 7.1.1, 7.1.2 and
|
||||
// 8.1, nodes MUST silently drop NDP packets where the Hop Limit field
|
||||
// in the IPv6 header is not set to 255.
|
||||
// in the IPv6 header is not set to 255, or the ICMPv6 Code field is not
|
||||
// set to 0.
|
||||
switch h.Type() {
|
||||
case header.ICMPv6NeighborSolicit,
|
||||
header.ICMPv6NeighborAdvert,
|
||||
@@ -97,6 +98,11 @@ func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.V
|
||||
received.Invalid.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
if h.Code() != 0 {
|
||||
received.Invalid.Increment()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/112892170): Meaningfully handle all ICMP types.
|
||||
@@ -309,8 +315,51 @@ func (e *endpoint) handleICMP(r *stack.Route, netHeader buffer.View, vv buffer.V
|
||||
received.RouterSolicit.Increment()
|
||||
|
||||
case header.ICMPv6RouterAdvert:
|
||||
routerAddr := iph.SourceAddress()
|
||||
|
||||
//
|
||||
// Validate the RA as per RFC 4861 section 6.1.2.
|
||||
//
|
||||
|
||||
// Is the IP Source Address a link-local address?
|
||||
if !header.IsV6LinkLocalAddress(routerAddr) {
|
||||
// ...No, silently drop the packet.
|
||||
received.Invalid.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
p := h.NDPPayload()
|
||||
|
||||
// Is the NDP payload of sufficient size to hold a Router
|
||||
// Advertisement?
|
||||
if len(p) < header.NDPRAMinimumSize {
|
||||
// ...No, silently drop the packet.
|
||||
received.Invalid.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
ra := header.NDPRouterAdvert(p)
|
||||
opts := ra.Options()
|
||||
|
||||
// Are options valid as per the wire format?
|
||||
if _, err := opts.Iter(true); err != nil {
|
||||
// ...No, silently drop the packet.
|
||||
received.Invalid.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// At this point, we have a valid Router Advertisement, as far
|
||||
// as RFC 4861 section 6.1.2 is concerned.
|
||||
//
|
||||
|
||||
received.RouterAdvert.Increment()
|
||||
|
||||
// Tell the NIC to handle the RA.
|
||||
stack := r.Stack()
|
||||
rxNICID := r.NICID()
|
||||
stack.HandleNDPRA(rxNICID, routerAddr, ra)
|
||||
|
||||
case header.ICMPv6RedirectMsg:
|
||||
received.RedirectMsg.Increment()
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ func TestICMPCounts(t *testing.T) {
|
||||
{header.ICMPv6EchoRequest, header.ICMPv6EchoMinimumSize},
|
||||
{header.ICMPv6EchoReply, header.ICMPv6EchoMinimumSize},
|
||||
{header.ICMPv6RouterSolicit, header.ICMPv6MinimumSize},
|
||||
{header.ICMPv6RouterAdvert, header.ICMPv6MinimumSize},
|
||||
{header.ICMPv6RouterAdvert, header.ICMPv6HeaderSize + header.NDPRAMinimumSize},
|
||||
{header.ICMPv6NeighborSolicit, header.ICMPv6NeighborSolicitMinimumSize},
|
||||
{header.ICMPv6NeighborAdvert, header.ICMPv6NeighborAdvertSize},
|
||||
{header.ICMPv6RedirectMsg, header.ICMPv6MinimumSize},
|
||||
@@ -426,7 +426,7 @@ func TestICMPChecksumValidationSimple(t *testing.T) {
|
||||
{
|
||||
"RouterAdvert",
|
||||
header.ICMPv6RouterAdvert,
|
||||
header.ICMPv6MinimumSize,
|
||||
header.ICMPv6HeaderSize + header.NDPRAMinimumSize,
|
||||
func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
|
||||
return stats.RouterAdvert
|
||||
},
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||
)
|
||||
@@ -109,7 +110,7 @@ func TestHopLimitValidation(t *testing.T) {
|
||||
{"RouterSolicit", header.ICMPv6RouterSolicit, header.ICMPv6MinimumSize, func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
|
||||
return stats.RouterSolicit
|
||||
}},
|
||||
{"RouterAdvert", header.ICMPv6RouterAdvert, header.ICMPv6MinimumSize, func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
|
||||
{"RouterAdvert", header.ICMPv6RouterAdvert, header.ICMPv6HeaderSize + header.NDPRAMinimumSize, func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
|
||||
return stats.RouterAdvert
|
||||
}},
|
||||
{"NeighborSolicit", header.ICMPv6NeighborSolicit, header.ICMPv6NeighborSolicitMinimumSize, func(stats tcpip.ICMPv6ReceivedPacketStats) *tcpip.StatCounter {
|
||||
@@ -179,3 +180,189 @@ func TestHopLimitValidation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRouterAdvertValidation tests that when the NIC is configured to handle
|
||||
// NDP Router Advertisement packets, it validates the Router Advertisement
|
||||
// properly before handling them.
|
||||
func TestRouterAdvertValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
src tcpip.Address
|
||||
hopLimit uint8
|
||||
code uint8
|
||||
ndpPayload []byte
|
||||
expectedSuccess bool
|
||||
}{
|
||||
{
|
||||
"OK",
|
||||
lladdr0,
|
||||
255,
|
||||
0,
|
||||
[]byte{
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"NonLinkLocalSourceAddr",
|
||||
addr1,
|
||||
255,
|
||||
0,
|
||||
[]byte{
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"HopLimitNot255",
|
||||
lladdr0,
|
||||
254,
|
||||
0,
|
||||
[]byte{
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"NonZeroCode",
|
||||
lladdr0,
|
||||
255,
|
||||
1,
|
||||
[]byte{
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"NDPPayloadTooSmall",
|
||||
lladdr0,
|
||||
255,
|
||||
0,
|
||||
[]byte{
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"OKWithOptions",
|
||||
lladdr0,
|
||||
255,
|
||||
0,
|
||||
[]byte{
|
||||
// RA payload
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
|
||||
// Option #1 (TargetLinkLayerAddress)
|
||||
2, 1, 0, 0, 0, 0, 0, 0,
|
||||
|
||||
// Option #2 (unrecognized)
|
||||
255, 1, 0, 0, 0, 0, 0, 0,
|
||||
|
||||
// Option #3 (PrefixInformation)
|
||||
3, 4, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"OptionWithZeroLength",
|
||||
lladdr0,
|
||||
255,
|
||||
0,
|
||||
[]byte{
|
||||
// RA payload
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
|
||||
// Option #1 (TargetLinkLayerAddress)
|
||||
// Invalid as it has 0 length.
|
||||
2, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
||||
// Option #2 (unrecognized)
|
||||
255, 1, 0, 0, 0, 0, 0, 0,
|
||||
|
||||
// Option #3 (PrefixInformation)
|
||||
3, 4, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
e := channel.New(10, 1280, linkAddr1)
|
||||
s := stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocol{NewProtocol()},
|
||||
})
|
||||
|
||||
if err := s.CreateNIC(1, e); err != nil {
|
||||
t.Fatalf("CreateNIC(_) = %s", err)
|
||||
}
|
||||
|
||||
icmpSize := header.ICMPv6HeaderSize + len(test.ndpPayload)
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + icmpSize)
|
||||
pkt := header.ICMPv6(hdr.Prepend(icmpSize))
|
||||
pkt.SetType(header.ICMPv6RouterAdvert)
|
||||
pkt.SetCode(test.code)
|
||||
copy(pkt.NDPPayload(), test.ndpPayload)
|
||||
payloadLength := hdr.UsedLength()
|
||||
pkt.SetChecksum(header.ICMPv6Checksum(pkt, test.src, header.IPv6AllNodesMulticastAddress, buffer.VectorisedView{}))
|
||||
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
||||
ip.Encode(&header.IPv6Fields{
|
||||
PayloadLength: uint16(payloadLength),
|
||||
NextHeader: uint8(icmp.ProtocolNumber6),
|
||||
HopLimit: test.hopLimit,
|
||||
SrcAddr: test.src,
|
||||
DstAddr: header.IPv6AllNodesMulticastAddress,
|
||||
})
|
||||
|
||||
stats := s.Stats().ICMP.V6PacketsReceived
|
||||
invalid := stats.Invalid
|
||||
rxRA := stats.RouterAdvert
|
||||
|
||||
if got := invalid.Value(); got != 0 {
|
||||
t.Fatalf("got invalid = %d, want = 0", got)
|
||||
}
|
||||
if got := rxRA.Value(); got != 0 {
|
||||
t.Fatalf("got rxRA = %d, want = 0", got)
|
||||
}
|
||||
|
||||
e.Inject(header.IPv6ProtocolNumber, hdr.View().ToVectorisedView())
|
||||
|
||||
if test.expectedSuccess {
|
||||
if got := invalid.Value(); got != 0 {
|
||||
t.Fatalf("got invalid = %d, want = 0", got)
|
||||
}
|
||||
if got := rxRA.Value(); got != 1 {
|
||||
t.Fatalf("got rxRA = %d, want = 1", got)
|
||||
}
|
||||
|
||||
} else {
|
||||
if got := invalid.Value(); got != 1 {
|
||||
t.Fatalf("got invalid = %d, want = 1", got)
|
||||
}
|
||||
if got := rxRA.Value(); got != 0 {
|
||||
t.Fatalf("got rxRA = %d, want = 0", got)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,19 @@ const (
|
||||
// Default = 1s (from RFC 4861 section 10).
|
||||
defaultRetransmitTimer = time.Second
|
||||
|
||||
// defaultHandleRAs is the default configuration for whether or not to
|
||||
// handle incoming Router Advertisements as a host.
|
||||
//
|
||||
// Default = true.
|
||||
defaultHandleRAs = true
|
||||
|
||||
// defaultDiscoverDefaultRouters is the default configuration for
|
||||
// whether or not to discover default routers from incoming Router
|
||||
// Advertisements as a host.
|
||||
//
|
||||
// Default = true.
|
||||
defaultDiscoverDefaultRouters = true
|
||||
|
||||
// minimumRetransmitTimer is the minimum amount of time to wait between
|
||||
// sending NDP Neighbor solicitation messages. Note, RFC 4861 does
|
||||
// not impose a minimum Retransmit Timer, but we do here to make sure
|
||||
@@ -49,6 +62,13 @@ const (
|
||||
//
|
||||
// Min = 1ms.
|
||||
minimumRetransmitTimer = time.Millisecond
|
||||
|
||||
// MaxDiscoveredDefaultRouters is the maximum number of discovered
|
||||
// default routers. The stack should stop discovering new routers after
|
||||
// discovering MaxDiscoveredDefaultRouters routers.
|
||||
//
|
||||
// Max = 10.
|
||||
MaxDiscoveredDefaultRouters = 10
|
||||
)
|
||||
|
||||
// NDPDispatcher is the interface integrators of netstack must implement to
|
||||
@@ -80,6 +100,15 @@ type NDPConfigurations struct {
|
||||
//
|
||||
// Must be greater than 0.5s.
|
||||
RetransmitTimer time.Duration
|
||||
|
||||
// HandleRAs determines whether or not Router Advertisements will be
|
||||
// processed.
|
||||
HandleRAs bool
|
||||
|
||||
// DiscoverDefaultRouters determines whether or not default routers will
|
||||
// be discovered from Router Advertisements. This configuration is
|
||||
// ignored if HandleRAs is false.
|
||||
DiscoverDefaultRouters bool
|
||||
}
|
||||
|
||||
// DefaultNDPConfigurations returns an NDPConfigurations populated with
|
||||
@@ -88,6 +117,8 @@ func DefaultNDPConfigurations() NDPConfigurations {
|
||||
return NDPConfigurations{
|
||||
DupAddrDetectTransmits: defaultDupAddrDetectTransmits,
|
||||
RetransmitTimer: defaultRetransmitTimer,
|
||||
HandleRAs: defaultHandleRAs,
|
||||
DiscoverDefaultRouters: defaultDiscoverDefaultRouters,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +143,9 @@ type ndpState struct {
|
||||
|
||||
// The DAD state to send the next NS message, or resolve the address.
|
||||
dad map[tcpip.Address]dadState
|
||||
|
||||
// The default routers discovered through Router Advertisements.
|
||||
defaultRouters map[tcpip.Address]defaultRouterState
|
||||
}
|
||||
|
||||
// dadState holds the Duplicate Address Detection timer and channel to signal
|
||||
@@ -127,6 +161,12 @@ type dadState struct {
|
||||
done *bool
|
||||
}
|
||||
|
||||
// defaultRouterState holds data associated with a default router discovered by
|
||||
// a Router Advertisement.
|
||||
type defaultRouterState struct {
|
||||
invalidationTimer *time.Timer
|
||||
}
|
||||
|
||||
// startDuplicateAddressDetection performs Duplicate Address Detection.
|
||||
//
|
||||
// This function must only be called by IPv6 addresses that are currently
|
||||
@@ -319,3 +359,18 @@ func (ndp *ndpState) stopDuplicateAddressDetection(addr tcpip.Address) {
|
||||
go ndp.nic.stack.ndpDisp.OnDuplicateAddressDetectionStatus(ndp.nic.ID(), addr, false, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// handleRA handles a Router Advertisement message that arrived on the NIC
|
||||
// this ndp is for.
|
||||
//
|
||||
// The NIC that ndp belongs to MUST be locked.
|
||||
func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
|
||||
// Is the NIC configured to handle RAs at all?
|
||||
if !ndp.configs.HandleRAs {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(b/140882146): Do Router Discovery.
|
||||
// TODO(b/140948104): Do Prefix Discovery.
|
||||
// TODO(b/141556115): Do Parameter Discovery.
|
||||
}
|
||||
|
||||
+11
-2
@@ -115,8 +115,9 @@ func newNIC(stack *Stack, id tcpip.NICID, name string, ep LinkEndpoint, loopback
|
||||
},
|
||||
},
|
||||
ndp: ndpState{
|
||||
configs: stack.ndpConfigs,
|
||||
dad: make(map[tcpip.Address]dadState),
|
||||
configs: stack.ndpConfigs,
|
||||
dad: make(map[tcpip.Address]dadState),
|
||||
defaultRouters: make(map[tcpip.Address]defaultRouterState),
|
||||
},
|
||||
}
|
||||
nic.ndp.nic = nic
|
||||
@@ -960,6 +961,14 @@ func (n *NIC) setNDPConfigs(c NDPConfigurations) {
|
||||
n.mu.Unlock()
|
||||
}
|
||||
|
||||
// handleNDPRA handles an NDP Router Advertisement message that arrived on n.
|
||||
func (n *NIC) handleNDPRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
n.ndp.handleRA(ip, ra)
|
||||
}
|
||||
|
||||
type networkEndpointKind int32
|
||||
|
||||
const (
|
||||
|
||||
@@ -1557,6 +1557,22 @@ func (s *Stack) SetNDPConfigurations(id tcpip.NICID, c NDPConfigurations) *tcpip
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleNDPRA provides a NIC with ID id a validated NDP Router Advertisement
|
||||
// message that it needs to handle.
|
||||
func (s *Stack) HandleNDPRA(id tcpip.NICID, ip tcpip.Address, ra header.NDPRouterAdvert) *tcpip.Error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
nic, ok := s.nics[id]
|
||||
if !ok {
|
||||
return tcpip.ErrUnknownNICID
|
||||
}
|
||||
|
||||
nic.handleNDPRA(ip, ra)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PortSeed returns a 32 bit value that can be used as a seed value for port
|
||||
// picking.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user