wgengine/netstack: deliver self-addressed packets via loopback
When a tsnet.Server dials its own Tailscale IP, TCP SYN packets are silently dropped. In inject(), outbound packets with dst=self fail the shouldSendToHost check and fall through to WireGuard, which has no peer for the node's own address. Fix this by detecting self-addressed packets in inject() using isLocalIP and delivering them back into gVisor's network stack as inbound packets via a new DeliverLoopback method on linkEndpoint. The outbound packet must be re-serialized into a new PacketBuffer because outbound packets have their headers parsed into separate views, but DeliverNetworkPacket expects raw unparsed data. Updates #18829 Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
committed by
James Tucker
parent
30e12310f1
commit
0fb207c3d0
@@ -2792,3 +2792,76 @@ func TestResolveAuthKey(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSelfDial verifies that a single tsnet.Server can Dial its own Listen
|
||||||
|
// address. This is a regression test for a bug where self-addressed TCP SYN
|
||||||
|
// packets were sent to WireGuard (which has no peer for the node's own IP)
|
||||||
|
// and silently dropped, causing Dial to hang indefinitely.
|
||||||
|
func TestSelfDial(t *testing.T) {
|
||||||
|
tstest.Shard(t)
|
||||||
|
tstest.ResourceCheck(t)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
controlURL, _ := startControl(t)
|
||||||
|
s1, s1ip, _ := startServer(t, ctx, controlURL, "s1")
|
||||||
|
|
||||||
|
ln, err := s1.Listen("tcp", ":8081")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
connc := make(chan net.Conn, 1)
|
||||||
|
go func() {
|
||||||
|
c, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
connc <- c
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Self-dial: the same server dials its own Tailscale IP.
|
||||||
|
w, err := s1.Dial(ctx, "tcp", fmt.Sprintf("%s:8081", s1ip))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("self-dial failed: %v", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
var accepted net.Conn
|
||||||
|
select {
|
||||||
|
case accepted = <-connc:
|
||||||
|
case err := <-errc:
|
||||||
|
t.Fatalf("accept failed: %v", err)
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatal("timeout waiting for accept")
|
||||||
|
}
|
||||||
|
defer accepted.Close()
|
||||||
|
|
||||||
|
// Verify bidirectional data exchange.
|
||||||
|
want := "hello self"
|
||||||
|
if _, err := io.WriteString(w, want); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got := make([]byte, len(want))
|
||||||
|
if _, err := io.ReadFull(accepted, got); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(got) != want {
|
||||||
|
t.Errorf("client->server: got %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := "hello back"
|
||||||
|
if _, err := io.WriteString(accepted, reply); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
gotReply := make([]byte, len(reply))
|
||||||
|
if _, err := io.ReadFull(w, gotReply); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(gotReply) != reply {
|
||||||
|
t.Errorf("server->client: got %q, want %q", gotReply, reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
@@ -198,6 +199,43 @@ func (ep *linkEndpoint) injectInbound(p *packet.Parsed) {
|
|||||||
pkt.DecRef()
|
pkt.DecRef()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeliverLoopback delivers pkt back into gVisor's network stack as if it
|
||||||
|
// arrived from the network, for self-addressed (loopback) packets. It takes
|
||||||
|
// ownership of one reference count on pkt. The caller must not use pkt after
|
||||||
|
// calling this method. It returns false if the dispatcher is not attached.
|
||||||
|
//
|
||||||
|
// Outbound packets from gVisor have their headers already parsed into separate
|
||||||
|
// views (NetworkHeader, TransportHeader, Data). DeliverNetworkPacket expects
|
||||||
|
// a raw unparsed packet, so we must re-serialize the packet into a new
|
||||||
|
// PacketBuffer with all bytes in the payload for gVisor to parse on inbound.
|
||||||
|
func (ep *linkEndpoint) DeliverLoopback(pkt *stack.PacketBuffer) bool {
|
||||||
|
ep.mu.RLock()
|
||||||
|
d := ep.dispatcher
|
||||||
|
ep.mu.RUnlock()
|
||||||
|
if d == nil {
|
||||||
|
pkt.DecRef()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the outbound packet back to raw bytes.
|
||||||
|
raw := stack.PayloadSince(pkt.NetworkHeader()).AsSlice()
|
||||||
|
proto := pkt.NetworkProtocolNumber
|
||||||
|
|
||||||
|
// We're done with the original outbound packet.
|
||||||
|
pkt.DecRef()
|
||||||
|
|
||||||
|
// Create a new PacketBuffer from the raw bytes for inbound delivery.
|
||||||
|
newPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||||
|
Payload: buffer.MakeWithData(raw),
|
||||||
|
})
|
||||||
|
newPkt.NetworkProtocolNumber = proto
|
||||||
|
newPkt.RXChecksumValidated = true
|
||||||
|
|
||||||
|
d.DeliverNetworkPacket(proto, newPkt)
|
||||||
|
newPkt.DecRef()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Attach saves the stack network-layer dispatcher for use later when packets
|
// Attach saves the stack network-layer dispatcher for use later when packets
|
||||||
// are injected.
|
// are injected.
|
||||||
func (ep *linkEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
func (ep *linkEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||||
|
|||||||
@@ -1037,6 +1037,16 @@ func (ns *Impl) inject() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Self-addressed packet: deliver back into gVisor directly
|
||||||
|
// via the link endpoint's dispatcher, but only if the packet is not
|
||||||
|
// earmarked for the host. Neither the inbound path (fakeTUN Write is a
|
||||||
|
// no-op) nor the outbound path (WireGuard has no peer for our own IP)
|
||||||
|
// can handle these.
|
||||||
|
if ns.isSelfDst(pkt) {
|
||||||
|
ns.linkEP.DeliverLoopback(pkt)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if err := ns.tundev.InjectOutboundPacketBuffer(pkt); err != nil {
|
if err := ns.tundev.InjectOutboundPacketBuffer(pkt); err != nil {
|
||||||
ns.logf("netstack inject outbound: %v", err)
|
ns.logf("netstack inject outbound: %v", err)
|
||||||
return
|
return
|
||||||
@@ -1116,6 +1126,20 @@ func (ns *Impl) shouldSendToHost(pkt *stack.PacketBuffer) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isSelfDst reports whether pkt's destination IP is a local Tailscale IP
|
||||||
|
// assigned to this node. This is used by inject() to detect self-addressed
|
||||||
|
// packets that need loopback delivery.
|
||||||
|
func (ns *Impl) isSelfDst(pkt *stack.PacketBuffer) bool {
|
||||||
|
hdr := pkt.Network()
|
||||||
|
switch v := hdr.(type) {
|
||||||
|
case header.IPv4:
|
||||||
|
return ns.isLocalIP(netip.AddrFrom4(v.DestinationAddress().As4()))
|
||||||
|
case header.IPv6:
|
||||||
|
return ns.isLocalIP(netip.AddrFrom16(v.DestinationAddress().As16()))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// isLocalIP reports whether ip is a Tailscale IP assigned to this
|
// isLocalIP reports whether ip is a Tailscale IP assigned to this
|
||||||
// node directly (but not a subnet-routed IP).
|
// node directly (but not a subnet-routed IP).
|
||||||
func (ns *Impl) isLocalIP(ip netip.Addr) bool {
|
func (ns *Impl) isLocalIP(ip netip.Addr) bool {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/buffer"
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
@@ -1073,3 +1074,295 @@ func makeUDP6PacketBuffer(src, dst netip.AddrPort) *stack.PacketBuffer {
|
|||||||
|
|
||||||
return pkt
|
return pkt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestIsSelfDst verifies that isSelfDst correctly identifies packets whose
|
||||||
|
// destination IP is a local Tailscale IP assigned to this node.
|
||||||
|
func TestIsSelfDst(t *testing.T) {
|
||||||
|
var (
|
||||||
|
selfIP4 = netip.MustParseAddr("100.64.1.2")
|
||||||
|
selfIP6 = netip.MustParseAddr("fd7a:115c:a1e0::123")
|
||||||
|
remoteIP4 = netip.MustParseAddr("100.64.99.88")
|
||||||
|
remoteIP6 = netip.MustParseAddr("fd7a:115c:a1e0::99")
|
||||||
|
)
|
||||||
|
|
||||||
|
ns := makeNetstack(t, func(impl *Impl) {
|
||||||
|
impl.ProcessLocalIPs = true
|
||||||
|
impl.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool {
|
||||||
|
return addr == selfIP4 || addr == selfIP6
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
src, dst netip.AddrPort
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "self_to_self_v4",
|
||||||
|
src: netip.AddrPortFrom(selfIP4, 12345),
|
||||||
|
dst: netip.AddrPortFrom(selfIP4, 8081),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "self_to_self_v6",
|
||||||
|
src: netip.AddrPortFrom(selfIP6, 12345),
|
||||||
|
dst: netip.AddrPortFrom(selfIP6, 8081),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remote_to_self_v4",
|
||||||
|
src: netip.AddrPortFrom(remoteIP4, 12345),
|
||||||
|
dst: netip.AddrPortFrom(selfIP4, 8081),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remote_to_self_v6",
|
||||||
|
src: netip.AddrPortFrom(remoteIP6, 12345),
|
||||||
|
dst: netip.AddrPortFrom(selfIP6, 8081),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "self_to_remote_v4",
|
||||||
|
src: netip.AddrPortFrom(selfIP4, 12345),
|
||||||
|
dst: netip.AddrPortFrom(remoteIP4, 8081),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "self_to_remote_v6",
|
||||||
|
src: netip.AddrPortFrom(selfIP6, 12345),
|
||||||
|
dst: netip.AddrPortFrom(remoteIP6, 8081),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remote_to_remote_v4",
|
||||||
|
src: netip.AddrPortFrom(remoteIP4, 12345),
|
||||||
|
dst: netip.MustParseAddrPort("100.64.77.66:7777"),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service_ip_to_self_v4",
|
||||||
|
src: netip.AddrPortFrom(serviceIP, 53),
|
||||||
|
dst: netip.AddrPortFrom(selfIP4, 9999),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service_ip_to_self_v6",
|
||||||
|
src: netip.AddrPortFrom(serviceIPv6, 53),
|
||||||
|
dst: netip.AddrPortFrom(selfIP6, 9999),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var pkt *stack.PacketBuffer
|
||||||
|
if tt.src.Addr().Is4() {
|
||||||
|
pkt = makeUDP4PacketBuffer(tt.src, tt.dst)
|
||||||
|
} else {
|
||||||
|
pkt = makeUDP6PacketBuffer(tt.src, tt.dst)
|
||||||
|
}
|
||||||
|
defer pkt.DecRef()
|
||||||
|
|
||||||
|
if got := ns.isSelfDst(pkt); got != tt.want {
|
||||||
|
t.Errorf("isSelfDst(%v -> %v) = %v, want %v", tt.src, tt.dst, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDeliverLoopback verifies that DeliverLoopback correctly re-serializes an
|
||||||
|
// outbound packet and delivers it back into gVisor's inbound path.
|
||||||
|
func TestDeliverLoopback(t *testing.T) {
|
||||||
|
ep := newLinkEndpoint(64, 1280, "", groNotSupported)
|
||||||
|
|
||||||
|
// Track delivered packets via a mock dispatcher.
|
||||||
|
type delivered struct {
|
||||||
|
proto tcpip.NetworkProtocolNumber
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
deliveredCh := make(chan delivered, 4)
|
||||||
|
ep.Attach(&mockDispatcher{
|
||||||
|
onDeliverNetworkPacket: func(proto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||||
|
// Capture the raw bytes from the delivered packet. At this
|
||||||
|
// point the packet is unparsed — everything is in the
|
||||||
|
// payload, no headers have been consumed yet.
|
||||||
|
buf := pkt.ToBuffer()
|
||||||
|
raw := buf.Flatten()
|
||||||
|
deliveredCh <- delivered{proto: proto, data: raw}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ipv4", func(t *testing.T) {
|
||||||
|
selfAddr := netip.MustParseAddrPort("100.64.1.2:8081")
|
||||||
|
pkt := makeUDP4PacketBuffer(selfAddr, selfAddr)
|
||||||
|
// Capture what the outbound bytes look like before loopback.
|
||||||
|
wantLen := pkt.Size()
|
||||||
|
wantProto := pkt.NetworkProtocolNumber
|
||||||
|
|
||||||
|
if !ep.DeliverLoopback(pkt) {
|
||||||
|
t.Fatal("DeliverLoopback returned false")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case got := <-deliveredCh:
|
||||||
|
if got.proto != wantProto {
|
||||||
|
t.Errorf("proto = %d, want %d", got.proto, wantProto)
|
||||||
|
}
|
||||||
|
if len(got.data) != wantLen {
|
||||||
|
t.Errorf("data length = %d, want %d", len(got.data), wantLen)
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("timeout waiting for loopback delivery")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ipv6", func(t *testing.T) {
|
||||||
|
selfAddr := netip.MustParseAddrPort("[fd7a:115c:a1e0::123]:8081")
|
||||||
|
pkt := makeUDP6PacketBuffer(selfAddr, selfAddr)
|
||||||
|
wantLen := pkt.Size()
|
||||||
|
wantProto := pkt.NetworkProtocolNumber
|
||||||
|
|
||||||
|
if !ep.DeliverLoopback(pkt) {
|
||||||
|
t.Fatal("DeliverLoopback returned false")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case got := <-deliveredCh:
|
||||||
|
if got.proto != wantProto {
|
||||||
|
t.Errorf("proto = %d, want %d", got.proto, wantProto)
|
||||||
|
}
|
||||||
|
if len(got.data) != wantLen {
|
||||||
|
t.Errorf("data length = %d, want %d", len(got.data), wantLen)
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("timeout waiting for loopback delivery")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil_dispatcher", func(t *testing.T) {
|
||||||
|
ep2 := newLinkEndpoint(64, 1280, "", groNotSupported)
|
||||||
|
// Don't attach a dispatcher.
|
||||||
|
selfAddr := netip.MustParseAddrPort("100.64.1.2:8081")
|
||||||
|
pkt := makeUDP4PacketBuffer(selfAddr, selfAddr)
|
||||||
|
if ep2.DeliverLoopback(pkt) {
|
||||||
|
t.Error("DeliverLoopback should return false with nil dispatcher")
|
||||||
|
}
|
||||||
|
// pkt refcount was consumed by DeliverLoopback, so we don't DecRef.
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// mockDispatcher implements stack.NetworkDispatcher for testing.
|
||||||
|
type mockDispatcher struct {
|
||||||
|
onDeliverNetworkPacket func(tcpip.NetworkProtocolNumber, *stack.PacketBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *mockDispatcher) DeliverNetworkPacket(proto tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||||
|
if d.onDeliverNetworkPacket != nil {
|
||||||
|
d.onDeliverNetworkPacket(proto, pkt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *mockDispatcher) DeliverLinkPacket(tcpip.NetworkProtocolNumber, *stack.PacketBuffer) {}
|
||||||
|
|
||||||
|
// udp4raw constructs a valid raw IPv4+UDP packet with proper checksums.
|
||||||
|
func udp4raw(t testing.TB, src, dst netip.Addr, sport, dport uint16, payload []byte) []byte {
|
||||||
|
t.Helper()
|
||||||
|
totalLen := header.IPv4MinimumSize + header.UDPMinimumSize + len(payload)
|
||||||
|
buf := make([]byte, totalLen)
|
||||||
|
|
||||||
|
ip := header.IPv4(buf)
|
||||||
|
ip.Encode(&header.IPv4Fields{
|
||||||
|
TotalLength: uint16(totalLen),
|
||||||
|
Protocol: uint8(header.UDPProtocolNumber),
|
||||||
|
TTL: 64,
|
||||||
|
SrcAddr: tcpip.AddrFrom4Slice(src.AsSlice()),
|
||||||
|
DstAddr: tcpip.AddrFrom4Slice(dst.AsSlice()),
|
||||||
|
})
|
||||||
|
ip.SetChecksum(^ip.CalculateChecksum())
|
||||||
|
|
||||||
|
// Build UDP header + payload.
|
||||||
|
u := header.UDP(buf[header.IPv4MinimumSize:])
|
||||||
|
u.Encode(&header.UDPFields{
|
||||||
|
SrcPort: sport,
|
||||||
|
DstPort: dport,
|
||||||
|
Length: uint16(header.UDPMinimumSize + len(payload)),
|
||||||
|
})
|
||||||
|
copy(buf[header.IPv4MinimumSize+header.UDPMinimumSize:], payload)
|
||||||
|
|
||||||
|
xsum := header.PseudoHeaderChecksum(
|
||||||
|
header.UDPProtocolNumber,
|
||||||
|
tcpip.AddrFrom4Slice(src.AsSlice()),
|
||||||
|
tcpip.AddrFrom4Slice(dst.AsSlice()),
|
||||||
|
uint16(header.UDPMinimumSize+len(payload)),
|
||||||
|
)
|
||||||
|
u.SetChecksum(^header.UDP(buf[header.IPv4MinimumSize:]).CalculateChecksum(xsum))
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInjectLoopback verifies that the inject goroutine delivers self-addressed
|
||||||
|
// packets back into gVisor (via DeliverLoopback) instead of sending them to
|
||||||
|
// WireGuard outbound. This is a regression test for a bug where self-dial
|
||||||
|
// packets were sent to WireGuard and silently dropped.
|
||||||
|
func TestInjectLoopback(t *testing.T) {
|
||||||
|
selfIP4 := netip.MustParseAddr("100.64.1.2")
|
||||||
|
|
||||||
|
ns := makeNetstack(t, func(impl *Impl) {
|
||||||
|
impl.ProcessLocalIPs = true
|
||||||
|
impl.atomicIsLocalIPFunc.Store(func(addr netip.Addr) bool {
|
||||||
|
return addr == selfIP4
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Register gVisor's NIC address so the stack accepts and routes
|
||||||
|
// packets for this IP.
|
||||||
|
protocolAddr := tcpip.ProtocolAddress{
|
||||||
|
Protocol: header.IPv4ProtocolNumber,
|
||||||
|
AddressWithPrefix: tcpip.AddrFrom4(selfIP4.As4()).WithPrefix(),
|
||||||
|
}
|
||||||
|
if err := ns.ipstack.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
|
||||||
|
t.Fatalf("AddProtocolAddress: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind a UDP socket on the gVisor stack to receive the loopback packet.
|
||||||
|
pc, err := gonet.DialUDP(ns.ipstack, &tcpip.FullAddress{
|
||||||
|
NIC: nicID,
|
||||||
|
Addr: tcpip.AddrFrom4(selfIP4.As4()),
|
||||||
|
Port: 8081,
|
||||||
|
}, nil, header.IPv4ProtocolNumber)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DialUDP: %v", err)
|
||||||
|
}
|
||||||
|
defer pc.Close()
|
||||||
|
|
||||||
|
// Build a valid self-addressed UDP packet from raw bytes and wrap it
|
||||||
|
// in a gVisor PacketBuffer with headers already pushed, as gVisor's
|
||||||
|
// outbound path produces.
|
||||||
|
payload := []byte("loopback test")
|
||||||
|
raw := udp4raw(t, selfIP4, selfIP4, 12345, 8081, payload)
|
||||||
|
|
||||||
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||||
|
ReserveHeaderBytes: header.IPv4MinimumSize + header.UDPMinimumSize,
|
||||||
|
Payload: buffer.MakeWithData(payload),
|
||||||
|
})
|
||||||
|
copy(pkt.TransportHeader().Push(header.UDPMinimumSize),
|
||||||
|
raw[header.IPv4MinimumSize:header.IPv4MinimumSize+header.UDPMinimumSize])
|
||||||
|
pkt.TransportProtocolNumber = header.UDPProtocolNumber
|
||||||
|
copy(pkt.NetworkHeader().Push(header.IPv4MinimumSize), raw[:header.IPv4MinimumSize])
|
||||||
|
pkt.NetworkProtocolNumber = header.IPv4ProtocolNumber
|
||||||
|
|
||||||
|
if err := ns.linkEP.q.Write(pkt); err != nil {
|
||||||
|
t.Fatalf("queue.Write: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The inject goroutine should detect the self-addressed packet via
|
||||||
|
// isSelfDst and deliver it back into gVisor via DeliverLoopback.
|
||||||
|
pc.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||||
|
buf := make([]byte, 256)
|
||||||
|
n, _, err := pc.ReadFrom(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadFrom: %v (self-addressed packet was not looped back)", err)
|
||||||
|
}
|
||||||
|
if got := string(buf[:n]); got != "loopback test" {
|
||||||
|
t.Errorf("got %q, want %q", got, "loopback test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user