Find route before sending NA response

This change also brings back the stack.Route.ResolveWith method so that
we can immediately resolve a route when sending an NA in response to a
a NS with a source link layer address option.

Test: ipv6_test.TestNeighorSolicitationResponse
PiperOrigin-RevId: 337185461
This commit is contained in:
Ghanan Gowripalan
2020-10-14 15:29:47 -07:00
committed by gVisor bot
parent fc1e653973
commit 6e6a9d3f3d
5 changed files with 200 additions and 53 deletions
+61 -40
View File
@@ -252,26 +252,29 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
return
}
it, err := ns.Options().Iter(false /* check */)
if err != nil {
// Options are not valid as per the wire format, silently drop the packet.
received.Invalid.Increment()
return
}
var sourceLinkAddr tcpip.LinkAddress
{
it, err := ns.Options().Iter(false /* check */)
if err != nil {
// Options are not valid as per the wire format, silently drop the
// packet.
received.Invalid.Increment()
return
}
sourceLinkAddr, ok := getSourceLinkAddr(it)
if !ok {
received.Invalid.Increment()
return
sourceLinkAddr, ok = getSourceLinkAddr(it)
if !ok {
received.Invalid.Increment()
return
}
}
unspecifiedSource := r.RemoteAddress == header.IPv6Any
// As per RFC 4861 section 4.3, the Source Link-Layer Address Option MUST
// NOT be included when the source IP address is the unspecified address.
// Otherwise, on link layers that have addresses this option MUST be
// included in multicast solicitations and SHOULD be included in unicast
// solicitations.
unspecifiedSource := r.RemoteAddress == header.IPv6Any
if len(sourceLinkAddr) == 0 {
if header.IsV6MulticastAddress(r.LocalAddress) && !unspecifiedSource {
received.Invalid.Increment()
@@ -297,41 +300,51 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
return
}
// ICMPv6 Neighbor Solicit messages are always sent to
// specially crafted IPv6 multicast addresses. As a result, the
// route we end up with here has as its LocalAddress such a
// multicast address. It would be nonsense to claim that our
// source address is a multicast address, so we manually set
// the source address to the target address requested in the
// solicit message. Since that requires mutating the route, we
// must first clone it.
r := r.Clone()
defer r.Release()
r.LocalAddress = targetAddr
// As per RFC 4861 section 7.2.4, if the the source of the solicitation is
// the unspecified address, the node MUST set the Solicited flag to zero and
// multicast the advertisement to the all-nodes address.
solicited := true
// As per RFC 4861 section 7.2.4:
//
// If the source of the solicitation is the unspecified address, the node
// MUST [...] and multicast the advertisement to the all-nodes address.
//
remoteAddr := r.RemoteAddress
if unspecifiedSource {
solicited = false
r.RemoteAddress = header.IPv6AllNodesMulticastAddress
remoteAddr = header.IPv6AllNodesMulticastAddress
}
// If the NS has a source link-layer option, use the link address it
// specifies as the remote link address for the response instead of the
// source link address of the packet.
// Even if we were able to receive a packet from some remote, we may not
// have a route to it - the remote may be blocked via routing rules. We must
// always consult our routing table and find a route to the remote before
// sending any packet.
r, err := e.protocol.stack.FindRoute(e.nic.ID(), targetAddr, remoteAddr, ProtocolNumber, false /* multicastLoop */)
if err != nil {
// If we cannot find a route to the destination, silently drop the packet.
return
}
defer r.Release()
// If the NS has a source link-layer option, resolve the route immediately
// to avoid querying the neighbor table when the neighbor entry was updated
// as probing the neighbor table for a link address will transition the
// entry's state from stale to delay.
//
// Note, if the source link address is unspecified and this is a unicast
// solicitation, we may need to perform neighbor discovery to send the
// neighbor advertisement response. This is expected as per RFC 4861 section
// 7.2.4:
//
// Because unicast Neighbor Solicitations are not required to include a
// Source Link-Layer Address, it is possible that a node sending a
// solicited Neighbor Advertisement does not have a corresponding link-
// layer address for its neighbor in its Neighbor Cache. In such
// situations, a node will first have to use Neighbor Discovery to
// determine the link-layer address of its neighbor (i.e., send out a
// multicast Neighbor Solicitation).
//
// TODO(#2401): As per RFC 4861 section 7.2.4 we should consult our link
// address cache for the right destination link address instead of manually
// patching the route with the remote link address if one is specified in a
// Source Link-Layer Address option.
if len(sourceLinkAddr) != 0 {
r.RemoteLinkAddress = sourceLinkAddr
r.ResolveWith(sourceLinkAddr)
}
optsSerializer := header.NDPOptionsSerializer{
header.NDPTargetLinkLayerAddressOption(r.LocalLinkAddress),
header.NDPTargetLinkLayerAddressOption(e.nic.LinkAddress()),
}
neighborAdvertSize := header.ICMPv6NeighborAdvertMinimumSize + optsSerializer.Length()
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
@@ -341,7 +354,14 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
packet := header.ICMPv6(pkt.TransportHeader().Push(neighborAdvertSize))
packet.SetType(header.ICMPv6NeighborAdvert)
na := header.NDPNeighborAdvert(packet.NDPPayload())
na.SetSolicitedFlag(solicited)
// As per RFC 4861 section 7.2.4:
//
// If the source of the solicitation is the unspecified address, the node
// MUST set the Solicited flag to zero and [..]. Otherwise, the node MUST
// set the Solicited flag to one and [..].
//
na.SetSolicitedFlag(!unspecifiedSource)
na.SetOverrideFlag(true)
na.SetTargetAddress(targetAddr)
na.Options().Serialize(optsSerializer)
@@ -635,6 +655,7 @@ func (*protocol) LinkAddressRequest(addr, localAddr tcpip.Address, remoteLinkAdd
r := stack.Route{
LocalAddress: localAddr,
RemoteAddress: addr,
LocalLinkAddress: linkEP.LinkAddress(),
RemoteLinkAddress: remoteLinkAddr,
}
+85 -13
View File
@@ -15,6 +15,7 @@
package ipv6
import (
"context"
"strings"
"testing"
"time"
@@ -398,16 +399,17 @@ func TestNeighorSolicitationResponse(t *testing.T) {
}
tests := []struct {
name string
nsOpts header.NDPOptionsSerializer
nsSrcLinkAddr tcpip.LinkAddress
nsSrc tcpip.Address
nsDst tcpip.Address
nsInvalid bool
naDstLinkAddr tcpip.LinkAddress
naSolicited bool
naSrc tcpip.Address
naDst tcpip.Address
name string
nsOpts header.NDPOptionsSerializer
nsSrcLinkAddr tcpip.LinkAddress
nsSrc tcpip.Address
nsDst tcpip.Address
nsInvalid bool
naDstLinkAddr tcpip.LinkAddress
naSolicited bool
naSrc tcpip.Address
naDst tcpip.Address
performsLinkResolution bool
}{
{
name: "Unspecified source to solicited-node multicast destination",
@@ -416,7 +418,7 @@ func TestNeighorSolicitationResponse(t *testing.T) {
nsSrc: header.IPv6Any,
nsDst: nicAddrSNMC,
nsInvalid: false,
naDstLinkAddr: remoteLinkAddr0,
naDstLinkAddr: header.EthernetAddressFromMulticastIPv6Address(header.IPv6AllNodesMulticastAddress),
naSolicited: false,
naSrc: nicAddr,
naDst: header.IPv6AllNodesMulticastAddress,
@@ -449,7 +451,6 @@ func TestNeighorSolicitationResponse(t *testing.T) {
nsDst: nicAddr,
nsInvalid: true,
},
{
name: "Specified source with 1 source ll to multicast destination",
nsOpts: header.NDPOptionsSerializer{
@@ -509,6 +510,10 @@ func TestNeighorSolicitationResponse(t *testing.T) {
naSolicited: true,
naSrc: nicAddr,
naDst: remoteAddr,
// Since we send a unicast solicitations to a node without an entry for
// the remote, the node needs to perform neighbor discovery to get the
// remote's link address to send the advertisement response.
performsLinkResolution: true,
},
{
name: "Specified source with 1 source ll to unicast destination",
@@ -615,11 +620,78 @@ func TestNeighorSolicitationResponse(t *testing.T) {
t.Fatalf("got invalid = %d, want = 0", got)
}
p, got := e.Read()
if test.performsLinkResolution {
p, got := e.ReadContext(context.Background())
if !got {
t.Fatal("expected an NDP NS response")
}
if p.Route.LocalAddress != nicAddr {
t.Errorf("got p.Route.LocalAddress = %s, want = %s", p.Route.LocalAddress, nicAddr)
}
if p.Route.LocalLinkAddress != nicLinkAddr {
t.Errorf("p.Route.LocalLinkAddress = %s, want = %s", p.Route.LocalLinkAddress, nicLinkAddr)
}
respNSDst := header.SolicitedNodeAddr(test.nsSrc)
if p.Route.RemoteAddress != respNSDst {
t.Errorf("got p.Route.RemoteAddress = %s, want = %s", p.Route.RemoteAddress, respNSDst)
}
if want := header.EthernetAddressFromMulticastIPv6Address(respNSDst); p.Route.RemoteLinkAddress != want {
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, want)
}
checker.IPv6(t, stack.PayloadSince(p.Pkt.NetworkHeader()),
checker.SrcAddr(nicAddr),
checker.DstAddr(respNSDst),
checker.TTL(header.NDPHopLimit),
checker.NDPNS(
checker.NDPNSTargetAddress(test.nsSrc),
checker.NDPNSOptions([]header.NDPOption{
header.NDPSourceLinkLayerAddressOption(nicLinkAddr),
}),
))
ser := header.NDPOptionsSerializer{
header.NDPTargetLinkLayerAddressOption(linkAddr1),
}
ndpNASize := header.ICMPv6NeighborAdvertMinimumSize + ser.Length()
hdr := buffer.NewPrependable(header.IPv6MinimumSize + ndpNASize)
pkt := header.ICMPv6(hdr.Prepend(ndpNASize))
pkt.SetType(header.ICMPv6NeighborAdvert)
na := header.NDPNeighborAdvert(pkt.NDPPayload())
na.SetSolicitedFlag(true)
na.SetOverrideFlag(true)
na.SetTargetAddress(test.nsSrc)
na.Options().Serialize(ser)
pkt.SetChecksum(header.ICMPv6Checksum(pkt, test.nsSrc, nicAddr, buffer.VectorisedView{}))
payloadLength := hdr.UsedLength()
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(payloadLength),
NextHeader: uint8(header.ICMPv6ProtocolNumber),
HopLimit: header.NDPHopLimit,
SrcAddr: test.nsSrc,
DstAddr: nicAddr,
})
e.InjectLinkAddr(ProtocolNumber, "", stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: hdr.View().ToVectorisedView(),
}))
}
p, got := e.ReadContext(context.Background())
if !got {
t.Fatal("expected an NDP NA response")
}
if p.Route.LocalAddress != test.naSrc {
t.Errorf("got p.Route.LocalAddress = %s, want = %s", p.Route.LocalAddress, test.naSrc)
}
if p.Route.LocalLinkAddress != nicLinkAddr {
t.Errorf("p.Route.LocalLinkAddress = %s, want = %s", p.Route.LocalLinkAddress, nicLinkAddr)
}
if p.Route.RemoteAddress != test.naDst {
t.Errorf("got p.Route.RemoteAddress = %s, want = %s", p.Route.RemoteAddress, test.naDst)
}
if p.Route.RemoteLinkAddress != test.naDstLinkAddr {
t.Errorf("got p.Route.RemoteLinkAddress = %s, want = %s", p.Route.RemoteLinkAddress, test.naDstLinkAddr)
}
+1
View File
@@ -123,6 +123,7 @@ go_test(
"//pkg/tcpip/header",
"//pkg/tcpip/link/channel",
"//pkg/tcpip/link/loopback",
"//pkg/tcpip/network/arp",
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6",
"//pkg/tcpip/ports",
+6
View File
@@ -126,6 +126,12 @@ func (r *Route) GSOMaxSize() uint32 {
return 0
}
// ResolveWith immediately resolves a route with the specified remote link
// address.
func (r *Route) ResolveWith(addr tcpip.LinkAddress) {
r.RemoteLinkAddress = addr
}
// Resolve attempts to resolve the link address if necessary. Returns ErrWouldBlock in
// case address resolution requires blocking, e.g. wait for ARP reply. Waker is
// notified when address resolution is complete (success or not).
+47
View File
@@ -34,6 +34,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
"gvisor.dev/gvisor/pkg/tcpip/network/arp"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
@@ -3498,6 +3499,52 @@ func TestOutgoingSubnetBroadcast(t *testing.T) {
}
}
func TestResolveWith(t *testing.T) {
const (
unspecifiedNICID = 0
nicID = 1
)
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, arp.NewProtocol},
})
ep := channel.New(0, defaultMTU, "")
ep.LinkEPCapabilities |= stack.CapabilityResolutionRequired
if err := s.CreateNIC(nicID, ep); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
addr := tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tcpip.Address([]byte{192, 168, 1, 58}),
PrefixLen: 24,
},
}
if err := s.AddProtocolAddress(nicID, addr); err != nil {
t.Fatalf("AddProtocolAddress(%d, %#v): %s", nicID, addr, err)
}
s.SetRouteTable([]tcpip.Route{{Destination: header.IPv4EmptySubnet, NIC: nicID}})
remoteAddr := tcpip.Address([]byte{192, 168, 1, 59})
r, err := s.FindRoute(unspecifiedNICID, "" /* localAddr */, remoteAddr, header.IPv4ProtocolNumber, false /* multicastLoop */)
if err != nil {
t.Fatalf("FindRoute(%d, '', %s, %d): %s", unspecifiedNICID, remoteAddr, header.IPv4ProtocolNumber, err)
}
defer r.Release()
// Should initially require resolution.
if !r.IsResolutionRequired() {
t.Fatal("got r.IsResolutionRequired() = false, want = true")
}
// Manually resolving the route should no longer require resolution.
r.ResolveWith("\x01")
if r.IsResolutionRequired() {
t.Fatal("got r.IsResolutionRequired() = true, want = false")
}
}
// TestRouteReleaseAfterAddrRemoval tests that releasing a Route after its
// associated address is removed should not cause a panic.
func TestRouteReleaseAfterAddrRemoval(t *testing.T) {