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:
Ghanan Gowripalan
2019-11-06 10:39:29 -08:00
committed by gVisor bot
parent 57f6dbc4be
commit a824b48cea
6 changed files with 322 additions and 6 deletions
+50 -1
View File
@@ -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()
+2 -2
View File
@@ -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
},
+188 -1
View File
@@ -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)
}
}
})
}
}
+55
View File
@@ -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
View File
@@ -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 (
+16
View File
@@ -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.
//