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