Track UDP connections

This will enable NAT to be performed on UDP packets that are sent
in response to packets sent by the stack.

This will also enable ICMP errors to be properly NAT-ed in response
to UDP packets (#5916).

Updates #5915.

PiperOrigin-RevId: 398373251
This commit is contained in:
Ghanan Gowripalan
2021-09-22 17:55:49 -07:00
committed by gVisor bot
parent 440fc07f70
commit d877254511
8 changed files with 419 additions and 92 deletions
+63 -47
View File
@@ -160,7 +160,13 @@ func (cn *conn) timedOut(now time.Time) bool {
// update the connection tracking state. // update the connection tracking state.
// //
// Precondition: cn.mu must be held. // Precondition: cn.mu must be held.
func (cn *conn) updateLocked(tcpHeader header.TCP, hook Hook) { func (cn *conn) updateLocked(pkt *PacketBuffer, hook Hook) {
if pkt.TransportProtocolNumber != header.TCPProtocolNumber {
return
}
tcpHeader := header.TCP(pkt.TransportHeader().View())
// Update the state of tcb. tcb assumes it's always initialized on the // Update the state of tcb. tcb assumes it's always initialized on the
// client. However, we only need to know whether the connection is // client. However, we only need to know whether the connection is
// established or not, so the client/server distinction isn't important. // established or not, so the client/server distinction isn't important.
@@ -209,27 +215,38 @@ type bucket struct {
tuples tupleList tuples tupleList
} }
func getTransportHeader(pkt *PacketBuffer) (header.ChecksummableTransport, bool) {
switch pkt.TransportProtocolNumber {
case header.TCPProtocolNumber:
if tcpHeader := header.TCP(pkt.TransportHeader().View()); len(tcpHeader) >= header.TCPMinimumSize {
return tcpHeader, true
}
case header.UDPProtocolNumber:
if udpHeader := header.UDP(pkt.TransportHeader().View()); len(udpHeader) >= header.UDPMinimumSize {
return udpHeader, true
}
}
return nil, false
}
// packetToTupleID converts packet to a tuple ID. It fails when pkt lacks a valid // packetToTupleID converts packet to a tuple ID. It fails when pkt lacks a valid
// TCP header. // TCP header.
// //
// Preconditions: pkt.NetworkHeader() is valid. // Preconditions: pkt.NetworkHeader() is valid.
func packetToTupleID(pkt *PacketBuffer) (tupleID, tcpip.Error) { func packetToTupleID(pkt *PacketBuffer) (tupleID, tcpip.Error) {
netHeader := pkt.Network() netHeader := pkt.Network()
if netHeader.TransportProtocol() != header.TCPProtocolNumber { transportHeader, ok := getTransportHeader(pkt)
return tupleID{}, &tcpip.ErrUnknownProtocol{} if !ok {
}
tcpHeader := header.TCP(pkt.TransportHeader().View())
if len(tcpHeader) < header.TCPMinimumSize {
return tupleID{}, &tcpip.ErrUnknownProtocol{} return tupleID{}, &tcpip.ErrUnknownProtocol{}
} }
return tupleID{ return tupleID{
srcAddr: netHeader.SourceAddress(), srcAddr: netHeader.SourceAddress(),
srcPort: tcpHeader.SourcePort(), srcPort: transportHeader.SourcePort(),
dstAddr: netHeader.DestinationAddress(), dstAddr: netHeader.DestinationAddress(),
dstPort: tcpHeader.DestinationPort(), dstPort: transportHeader.DestinationPort(),
transProto: netHeader.TransportProtocol(), transProto: pkt.TransportProtocolNumber,
netProto: pkt.NetworkProtocolNumber, netProto: pkt.NetworkProtocolNumber,
}, nil }, nil
} }
@@ -381,8 +398,8 @@ func (ct *ConnTrack) handlePacket(pkt *PacketBuffer, hook Hook, r *Route) bool {
return false return false
} }
// TODO(gvisor.dev/issue/6168): Support UDP. transportHeader, ok := getTransportHeader(pkt)
if pkt.Network().TransportProtocol() != header.TCPProtocolNumber { if !ok {
return false return false
} }
@@ -396,10 +413,6 @@ func (ct *ConnTrack) handlePacket(pkt *PacketBuffer, hook Hook, r *Route) bool {
} }
netHeader := pkt.Network() netHeader := pkt.Network()
tcpHeader := header.TCP(pkt.TransportHeader().View())
if len(tcpHeader) < header.TCPMinimumSize {
return false
}
// TODO(gvisor.dev/issue/5748): TCP checksums on inbound packets should be // TODO(gvisor.dev/issue/5748): TCP checksums on inbound packets should be
// validated if checksum offloading is off. It may require IP defrag if the // validated if checksum offloading is off. It may require IP defrag if the
@@ -412,36 +425,31 @@ func (ct *ConnTrack) handlePacket(pkt *PacketBuffer, hook Hook, r *Route) bool {
switch hook { switch hook {
case Prerouting, Output: case Prerouting, Output:
if conn.manip == manipDestination { if conn.manip == manipDestination && dir == dirOriginal {
switch dir { newPort = conn.reply.srcPort
case dirOriginal: newAddr = conn.reply.srcAddr
newPort = conn.reply.srcPort pkt.NatDone = true
newAddr = conn.reply.srcAddr } else if conn.manip == manipSource && dir == dirReply {
case dirReply: newPort = conn.original.srcPort
newPort = conn.original.dstPort newAddr = conn.original.srcAddr
newAddr = conn.original.dstAddr
updateSRCFields = true
}
pkt.NatDone = true pkt.NatDone = true
} }
case Input, Postrouting: case Input, Postrouting:
if conn.manip == manipSource { if conn.manip == manipSource && dir == dirOriginal {
switch dir { newPort = conn.reply.dstPort
case dirOriginal: newAddr = conn.reply.dstAddr
newPort = conn.reply.dstPort updateSRCFields = true
newAddr = conn.reply.dstAddr pkt.NatDone = true
} else if conn.manip == manipDestination && dir == dirReply {
updateSRCFields = true newPort = conn.original.dstPort
case dirReply: newAddr = conn.original.dstAddr
newPort = conn.original.srcPort updateSRCFields = true
newAddr = conn.original.srcAddr
}
pkt.NatDone = true pkt.NatDone = true
} }
default: default:
panic(fmt.Sprintf("unrecognized hook = %s", hook)) panic(fmt.Sprintf("unrecognized hook = %s", hook))
} }
if !pkt.NatDone { if !pkt.NatDone {
return false return false
} }
@@ -449,10 +457,15 @@ func (ct *ConnTrack) handlePacket(pkt *PacketBuffer, hook Hook, r *Route) bool {
fullChecksum := false fullChecksum := false
updatePseudoHeader := false updatePseudoHeader := false
switch hook { switch hook {
case Prerouting, Input: case Prerouting:
// Packet came from outside the stack so it must have a checksum set
// already.
fullChecksum = true
updatePseudoHeader = true
case Input:
case Output, Postrouting: case Output, Postrouting:
// Calculate the TCP checksum and set it. // Calculate the TCP checksum and set it.
if pkt.GSOOptions.Type != GSONone && pkt.GSOOptions.NeedsCsum { if pkt.TransportProtocolNumber == header.TCPProtocolNumber && pkt.GSOOptions.Type != GSONone && pkt.GSOOptions.NeedsCsum {
updatePseudoHeader = true updatePseudoHeader = true
} else if r.RequiresTXTransportChecksum() { } else if r.RequiresTXTransportChecksum() {
fullChecksum = true fullChecksum = true
@@ -464,7 +477,7 @@ func (ct *ConnTrack) handlePacket(pkt *PacketBuffer, hook Hook, r *Route) bool {
rewritePacket( rewritePacket(
netHeader, netHeader,
tcpHeader, transportHeader,
updateSRCFields, updateSRCFields,
fullChecksum, fullChecksum,
updatePseudoHeader, updatePseudoHeader,
@@ -479,7 +492,7 @@ func (ct *ConnTrack) handlePacket(pkt *PacketBuffer, hook Hook, r *Route) bool {
// Mark the connection as having been used recently so it isn't reaped. // Mark the connection as having been used recently so it isn't reaped.
conn.lastUsed = time.Now() conn.lastUsed = time.Now()
// Update connection state. // Update connection state.
conn.updateLocked(header.TCP(pkt.TransportHeader().View()), hook) conn.updateLocked(pkt, hook)
return false return false
} }
@@ -497,8 +510,11 @@ func (ct *ConnTrack) maybeInsertNoop(pkt *PacketBuffer, hook Hook) {
return return
} }
// We only track TCP connections. switch pkt.TransportProtocolNumber {
if pkt.Network().TransportProtocol() != header.TCPProtocolNumber { case header.TCPProtocolNumber, header.UDPProtocolNumber:
default:
// TODO(https://gvisor.dev/issue/5915): Track ICMP and other trackable
// connections.
return return
} }
@@ -510,7 +526,7 @@ func (ct *ConnTrack) maybeInsertNoop(pkt *PacketBuffer, hook Hook) {
return return
} }
conn := newConn(tid, tid.reply(), manipNone, hook) conn := newConn(tid, tid.reply(), manipNone, hook)
conn.updateLocked(header.TCP(pkt.TransportHeader().View()), hook) conn.updateLocked(pkt, hook)
ct.insertConn(conn) ct.insertConn(conn)
} }
@@ -632,7 +648,7 @@ func (ct *ConnTrack) reapTupleLocked(tuple *tuple, bucket int, now time.Time) bo
return true return true
} }
func (ct *ConnTrack) originalDst(epID TransportEndpointID, netProto tcpip.NetworkProtocolNumber) (tcpip.Address, uint16, tcpip.Error) { func (ct *ConnTrack) originalDst(epID TransportEndpointID, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber) (tcpip.Address, uint16, tcpip.Error) {
// Lookup the connection. The reply's original destination // Lookup the connection. The reply's original destination
// describes the original address. // describes the original address.
tid := tupleID{ tid := tupleID{
@@ -640,7 +656,7 @@ func (ct *ConnTrack) originalDst(epID TransportEndpointID, netProto tcpip.Networ
srcPort: epID.LocalPort, srcPort: epID.LocalPort,
dstAddr: epID.RemoteAddress, dstAddr: epID.RemoteAddress,
dstPort: epID.RemotePort, dstPort: epID.RemotePort,
transProto: header.TCPProtocolNumber, transProto: transProto,
netProto: netProto, netProto: netProto,
} }
conn, _ := ct.connForTID(tid) conn, _ := ct.connForTID(tid)
+2 -2
View File
@@ -482,11 +482,11 @@ func (it *IPTables) checkRule(hook Hook, pkt *PacketBuffer, table Table, ruleIdx
// OriginalDst returns the original destination of redirected connections. It // OriginalDst returns the original destination of redirected connections. It
// returns an error if the connection doesn't exist or isn't redirected. // returns an error if the connection doesn't exist or isn't redirected.
func (it *IPTables) OriginalDst(epID TransportEndpointID, netProto tcpip.NetworkProtocolNumber) (tcpip.Address, uint16, tcpip.Error) { func (it *IPTables) OriginalDst(epID TransportEndpointID, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber) (tcpip.Address, uint16, tcpip.Error) {
it.mu.RLock() it.mu.RLock()
defer it.mu.RUnlock() defer it.mu.RUnlock()
if !it.modified { if !it.modified {
return "", 0, &tcpip.ErrNotConnected{} return "", 0, &tcpip.ErrNotConnected{}
} }
return it.connections.originalDst(epID, netProto) return it.connections.originalDst(epID, netProto, transProto)
} }
+19 -25
View File
@@ -206,34 +206,28 @@ func (st *SNATTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Rou
panic(fmt.Sprintf("%s unrecognized", hook)) panic(fmt.Sprintf("%s unrecognized", hook))
} }
switch protocol := pkt.TransportProtocolNumber; protocol { port := st.Port
case header.UDPProtocolNumber:
// Only calculate the checksum if offloading isn't supported.
requiresChecksum := r.RequiresTXTransportChecksum()
rewritePacket(
pkt.Network(),
header.UDP(pkt.TransportHeader().View()),
true, /* updateSRCFields */
requiresChecksum,
requiresChecksum,
st.Port,
st.Addr,
)
pkt.NatDone = true if port == 0 {
case header.TCPProtocolNumber: switch protocol := pkt.TransportProtocolNumber; protocol {
if ct == nil { case header.UDPProtocolNumber:
return RuleAccept, 0 if port == 0 {
port = header.UDP(pkt.TransportHeader().View()).SourcePort()
}
case header.TCPProtocolNumber:
if port == 0 {
port = header.TCP(pkt.TransportHeader().View()).SourcePort()
}
} }
}
// Set up conection for matching NAT rule. Only the first // Set up conection for matching NAT rule. Only the first packet of the
// packet of the connection comes here. Other packets will be // connection comes here. Other packets will be manipulated in connection
// manipulated in connection tracking. // tracking.
if conn := ct.insertSNATConn(pkt, hook, st.Port, st.Addr); conn != nil { //
ct.handlePacket(pkt, hook, r) // Does nothing if the protocol does not support connection tracking.
} if conn := ct.insertSNATConn(pkt, hook, port, st.Addr); conn != nil {
default: ct.handlePacket(pkt, hook, r)
return RuleDrop, 0
} }
return RuleAccept, 0 return RuleAccept, 0
+14 -9
View File
@@ -335,9 +335,7 @@ func (pk *PacketBuffer) CloneToInbound() *PacketBuffer {
// tell if a noop connection should be inserted at Input hook. Once conntrack // tell if a noop connection should be inserted at Input hook. Once conntrack
// redefines the manipulation field as mutable, we won't need the special noop // redefines the manipulation field as mutable, we won't need the special noop
// connection. // connection.
if pk.NatDone { newPk.NatDone = pk.NatDone
newPk.NatDone = true
}
return newPk return newPk
} }
@@ -347,7 +345,7 @@ func (pk *PacketBuffer) CloneToInbound() *PacketBuffer {
// The returned packet buffer will have the network and transport headers // The returned packet buffer will have the network and transport headers
// set if the original packet buffer did. // set if the original packet buffer did.
func (pk *PacketBuffer) DeepCopyForForwarding(reservedHeaderBytes int) *PacketBuffer { func (pk *PacketBuffer) DeepCopyForForwarding(reservedHeaderBytes int) *PacketBuffer {
newPkt := NewPacketBuffer(PacketBufferOptions{ newPk := NewPacketBuffer(PacketBufferOptions{
ReserveHeaderBytes: reservedHeaderBytes, ReserveHeaderBytes: reservedHeaderBytes,
Data: PayloadSince(pk.NetworkHeader()).ToVectorisedView(), Data: PayloadSince(pk.NetworkHeader()).ToVectorisedView(),
IsForwardedPacket: true, IsForwardedPacket: true,
@@ -355,21 +353,28 @@ func (pk *PacketBuffer) DeepCopyForForwarding(reservedHeaderBytes int) *PacketBu
{ {
consumeBytes := pk.NetworkHeader().View().Size() consumeBytes := pk.NetworkHeader().View().Size()
if _, consumed := newPkt.NetworkHeader().Consume(consumeBytes); !consumed { if _, consumed := newPk.NetworkHeader().Consume(consumeBytes); !consumed {
panic(fmt.Sprintf("expected to consume network header %d bytes from new packet", consumeBytes)) panic(fmt.Sprintf("expected to consume network header %d bytes from new packet", consumeBytes))
} }
newPkt.NetworkProtocolNumber = pk.NetworkProtocolNumber newPk.NetworkProtocolNumber = pk.NetworkProtocolNumber
} }
{ {
consumeBytes := pk.TransportHeader().View().Size() consumeBytes := pk.TransportHeader().View().Size()
if _, consumed := newPkt.TransportHeader().Consume(consumeBytes); !consumed { if _, consumed := newPk.TransportHeader().Consume(consumeBytes); !consumed {
panic(fmt.Sprintf("expected to consume transport header %d bytes from new packet", consumeBytes)) panic(fmt.Sprintf("expected to consume transport header %d bytes from new packet", consumeBytes))
} }
newPkt.TransportProtocolNumber = pk.TransportProtocolNumber newPk.TransportProtocolNumber = pk.TransportProtocolNumber
} }
return newPkt // TODO(gvisor.dev/issue/5696): reimplement conntrack so that no need to
// maintain this flag in the packet. Currently conntrack needs this flag to
// tell if a noop connection should be inserted at Input hook. Once conntrack
// redefines the manipulation field as mutable, we won't need the special noop
// connection.
newPk.NatDone = pk.NatDone
return newPk
} }
// headerInfo stores metadata about a header in a packet. // headerInfo stores metadata about a header in a packet.
+4
View File
@@ -34,12 +34,16 @@ go_test(
"//pkg/tcpip/checker", "//pkg/tcpip/checker",
"//pkg/tcpip/header", "//pkg/tcpip/header",
"//pkg/tcpip/link/channel", "//pkg/tcpip/link/channel",
"//pkg/tcpip/network/arp",
"//pkg/tcpip/network/ipv4", "//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6", "//pkg/tcpip/network/ipv6",
"//pkg/tcpip/stack", "//pkg/tcpip/stack",
"//pkg/tcpip/tests/utils", "//pkg/tcpip/tests/utils",
"//pkg/tcpip/testutil", "//pkg/tcpip/testutil",
"//pkg/tcpip/transport/tcp",
"//pkg/tcpip/transport/udp", "//pkg/tcpip/transport/udp",
"//pkg/waiter",
"@com_github_google_go_cmp//cmp:go_default_library",
], ],
) )
@@ -15,19 +15,24 @@
package iptables_test package iptables_test
import ( import (
"bytes"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer" "gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/checker" "gvisor.dev/gvisor/pkg/tcpip/checker"
"gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel" "gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/network/arp"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4" "gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6" "gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/tests/utils" "gvisor.dev/gvisor/pkg/tcpip/tests/utils"
"gvisor.dev/gvisor/pkg/tcpip/testutil" "gvisor.dev/gvisor/pkg/tcpip/testutil"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp" "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
) )
type inputIfNameMatcher struct { type inputIfNameMatcher struct {
@@ -1156,3 +1161,286 @@ func TestInputHookWithLocalForwarding(t *testing.T) {
}) })
} }
} }
func TestSNAT(t *testing.T) {
const listenPort = 8080
type endpointAndAddresses struct {
serverEP tcpip.Endpoint
serverAddr tcpip.Address
serverReadableCH chan struct{}
clientEP tcpip.Endpoint
clientAddr tcpip.Address
clientReadableCH chan struct{}
nattedClientAddr tcpip.Address
}
newEP := func(t *testing.T, s *stack.Stack, transProto tcpip.TransportProtocolNumber, netProto tcpip.NetworkProtocolNumber) (tcpip.Endpoint, chan struct{}) {
t.Helper()
var wq waiter.Queue
we, ch := waiter.NewChannelEntry(nil)
wq.EventRegister(&we, waiter.ReadableEvents)
t.Cleanup(func() {
wq.EventUnregister(&we)
})
ep, err := s.NewEndpoint(transProto, netProto, &wq)
if err != nil {
t.Fatalf("s.NewEndpoint(%d, %d, _): %s", transProto, netProto, err)
}
t.Cleanup(ep.Close)
return ep, ch
}
tests := []struct {
name string
epAndAddrs func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses
}{
{
name: "IPv4 host1 server with host2 client",
epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
t.Helper()
ipt := routerStack.IPTables()
filter := ipt.GetTable(stack.NATID, false /* ipv6 */)
ruleIdx := filter.BuiltinChains[stack.Postrouting]
filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{OutputInterface: utils.RouterNIC1Name}
filter.Rules[ruleIdx].Target = &stack.SNATTarget{NetworkProtocol: ipv4.ProtocolNumber, Addr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address}
// Make sure the packet is not dropped by the next rule.
filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
if err := ipt.ReplaceTable(stack.NATID, filter, false /* ipv6 */); err != nil {
t.Fatalf("ipt.ReplaceTable(%d, _, %t): %s", stack.NATID, false, err)
}
ep1, ep1WECH := newEP(t, host1Stack, proto, ipv4.ProtocolNumber)
ep2, ep2WECH := newEP(t, host2Stack, proto, ipv4.ProtocolNumber)
return endpointAndAddresses{
serverEP: ep1,
serverAddr: utils.Host1IPv4Addr.AddressWithPrefix.Address,
serverReadableCH: ep1WECH,
clientEP: ep2,
clientAddr: utils.Host2IPv4Addr.AddressWithPrefix.Address,
clientReadableCH: ep2WECH,
nattedClientAddr: utils.RouterNIC1IPv4Addr.AddressWithPrefix.Address,
}
},
},
{
name: "IPv6 host1 server with host2 client",
epAndAddrs: func(t *testing.T, host1Stack, routerStack, host2Stack *stack.Stack, proto tcpip.TransportProtocolNumber) endpointAndAddresses {
t.Helper()
ipt := routerStack.IPTables()
filter := ipt.GetTable(stack.NATID, true /* ipv6 */)
ruleIdx := filter.BuiltinChains[stack.Postrouting]
filter.Rules[ruleIdx].Filter = stack.IPHeaderFilter{OutputInterface: utils.RouterNIC1Name}
filter.Rules[ruleIdx].Target = &stack.SNATTarget{NetworkProtocol: ipv6.ProtocolNumber, Addr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address}
// Make sure the packet is not dropped by the next rule.
filter.Rules[ruleIdx+1].Target = &stack.AcceptTarget{}
if err := ipt.ReplaceTable(stack.NATID, filter, true /* ipv6 */); err != nil {
t.Fatalf("ipt.ReplaceTable(%d, _, %t): %s", stack.NATID, true, err)
}
ep1, ep1WECH := newEP(t, host1Stack, proto, ipv6.ProtocolNumber)
ep2, ep2WECH := newEP(t, host2Stack, proto, ipv6.ProtocolNumber)
return endpointAndAddresses{
serverEP: ep1,
serverAddr: utils.Host1IPv6Addr.AddressWithPrefix.Address,
serverReadableCH: ep1WECH,
clientEP: ep2,
clientAddr: utils.Host2IPv6Addr.AddressWithPrefix.Address,
clientReadableCH: ep2WECH,
nattedClientAddr: utils.RouterNIC1IPv6Addr.AddressWithPrefix.Address,
}
},
},
}
subTests := []struct {
name string
proto tcpip.TransportProtocolNumber
expectedConnectErr tcpip.Error
setupServer func(t *testing.T, ep tcpip.Endpoint)
setupServerConn func(t *testing.T, ep tcpip.Endpoint, ch <-chan struct{}, clientAddr tcpip.FullAddress) (tcpip.Endpoint, chan struct{})
needRemoteAddr bool
}{
{
name: "UDP",
proto: udp.ProtocolNumber,
expectedConnectErr: nil,
setupServerConn: func(t *testing.T, ep tcpip.Endpoint, _ <-chan struct{}, clientAddr tcpip.FullAddress) (tcpip.Endpoint, chan struct{}) {
t.Helper()
if err := ep.Connect(clientAddr); err != nil {
t.Fatalf("ep.Connect(%#v): %s", clientAddr, err)
}
return nil, nil
},
needRemoteAddr: true,
},
{
name: "TCP",
proto: tcp.ProtocolNumber,
expectedConnectErr: &tcpip.ErrConnectStarted{},
setupServer: func(t *testing.T, ep tcpip.Endpoint) {
t.Helper()
if err := ep.Listen(1); err != nil {
t.Fatalf("ep.Listen(1): %s", err)
}
},
setupServerConn: func(t *testing.T, ep tcpip.Endpoint, ch <-chan struct{}, clientAddr tcpip.FullAddress) (tcpip.Endpoint, chan struct{}) {
t.Helper()
var addr tcpip.FullAddress
for {
newEP, wq, err := ep.Accept(&addr)
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
<-ch
continue
}
if err != nil {
t.Fatalf("ep.Accept(_): %s", err)
}
if diff := cmp.Diff(clientAddr, addr, checker.IgnoreCmpPath(
"NIC",
)); diff != "" {
t.Errorf("accepted address mismatch (-want +got):\n%s", diff)
}
we, newCH := waiter.NewChannelEntry(nil)
wq.EventRegister(&we, waiter.ReadableEvents)
return newEP, newCH
}
},
needRemoteAddr: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
for _, subTest := range subTests {
t.Run(subTest.name, func(t *testing.T) {
stackOpts := stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{arp.NewProtocol, ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, tcp.NewProtocol},
}
host1Stack := stack.New(stackOpts)
routerStack := stack.New(stackOpts)
host2Stack := stack.New(stackOpts)
utils.SetupRoutedStacks(t, host1Stack, routerStack, host2Stack)
epsAndAddrs := test.epAndAddrs(t, host1Stack, routerStack, host2Stack, subTest.proto)
serverAddr := tcpip.FullAddress{Addr: epsAndAddrs.serverAddr, Port: listenPort}
if err := epsAndAddrs.serverEP.Bind(serverAddr); err != nil {
t.Fatalf("epsAndAddrs.serverEP.Bind(%#v): %s", serverAddr, err)
}
clientAddr := tcpip.FullAddress{Addr: epsAndAddrs.clientAddr}
if err := epsAndAddrs.clientEP.Bind(clientAddr); err != nil {
t.Fatalf("epsAndAddrs.clientEP.Bind(%#v): %s", clientAddr, err)
}
if subTest.setupServer != nil {
subTest.setupServer(t, epsAndAddrs.serverEP)
}
{
err := epsAndAddrs.clientEP.Connect(serverAddr)
if diff := cmp.Diff(subTest.expectedConnectErr, err); diff != "" {
t.Fatalf("unexpected error from epsAndAddrs.clientEP.Connect(%#v), (-want, +got):\n%s", serverAddr, diff)
}
}
nattedClientAddr := tcpip.FullAddress{Addr: epsAndAddrs.nattedClientAddr}
if addr, err := epsAndAddrs.clientEP.GetLocalAddress(); err != nil {
t.Fatalf("epsAndAddrs.clientEP.GetLocalAddress(): %s", err)
} else {
nattedClientAddr.Port = addr.Port
}
serverEP := epsAndAddrs.serverEP
serverCH := epsAndAddrs.serverReadableCH
if ep, ch := subTest.setupServerConn(t, serverEP, serverCH, nattedClientAddr); ep != nil {
defer ep.Close()
serverEP = ep
serverCH = ch
}
write := func(ep tcpip.Endpoint, data []byte) {
t.Helper()
var r bytes.Reader
r.Reset(data)
var wOpts tcpip.WriteOptions
n, err := ep.Write(&r, wOpts)
if err != nil {
t.Fatalf("ep.Write(_, %#v): %s", wOpts, err)
}
if want := int64(len(data)); n != want {
t.Fatalf("got ep.Write(_, %#v) = (%d, _), want = (%d, _)", wOpts, n, want)
}
}
read := func(ch chan struct{}, ep tcpip.Endpoint, data []byte, expectedFrom tcpip.FullAddress) {
t.Helper()
var buf bytes.Buffer
var res tcpip.ReadResult
for {
var err tcpip.Error
opts := tcpip.ReadOptions{NeedRemoteAddr: subTest.needRemoteAddr}
res, err = ep.Read(&buf, opts)
if _, ok := err.(*tcpip.ErrWouldBlock); ok {
<-ch
continue
}
if err != nil {
t.Fatalf("ep.Read(_, %d, %#v): %s", len(data), opts, err)
}
break
}
readResult := tcpip.ReadResult{
Count: len(data),
Total: len(data),
}
if subTest.needRemoteAddr {
readResult.RemoteAddr = expectedFrom
}
if diff := cmp.Diff(readResult, res, checker.IgnoreCmpPath(
"ControlMessages",
"RemoteAddr.NIC",
)); diff != "" {
t.Errorf("ep.Read: unexpected result (-want +got):\n%s", diff)
}
if diff := cmp.Diff(buf.Bytes(), data); diff != "" {
t.Errorf("received data mismatch (-want +got):\n%s", diff)
}
if t.Failed() {
t.FailNow()
}
}
{
data := []byte{1, 2, 3, 4}
write(epsAndAddrs.clientEP, data)
read(serverCH, serverEP, data, nattedClientAddr)
}
{
data := []byte{5, 6, 7, 8, 9, 10, 11, 12}
write(serverEP, data)
read(epsAndAddrs.clientReadableCH, epsAndAddrs.clientEP, data, serverAddr)
}
})
}
})
}
}
+28 -8
View File
@@ -40,6 +40,14 @@ const (
Host2NICID = 4 Host2NICID = 4
) )
// Common NIC names used by tests.
const (
Host1NICName = "host1NIC"
RouterNIC1Name = "routerNIC1"
RouterNIC2Name = "routerNIC2"
Host2NICName = "host2NIC"
)
// Common link addresses used by tests. // Common link addresses used by tests.
const ( const (
LinkAddr1 = tcpip.LinkAddress("\x02\x03\x03\x04\x05\x06") LinkAddr1 = tcpip.LinkAddress("\x02\x03\x03\x04\x05\x06")
@@ -211,17 +219,29 @@ func SetupRoutedStacks(t *testing.T, host1Stack, routerStack, host2Stack *stack.
host1NIC, routerNIC1 := pipe.New(LinkAddr1, LinkAddr2) host1NIC, routerNIC1 := pipe.New(LinkAddr1, LinkAddr2)
routerNIC2, host2NIC := pipe.New(LinkAddr3, LinkAddr4) routerNIC2, host2NIC := pipe.New(LinkAddr3, LinkAddr4)
if err := host1Stack.CreateNIC(Host1NICID, NewEthernetEndpoint(host1NIC)); err != nil { {
t.Fatalf("host1Stack.CreateNIC(%d, _): %s", Host1NICID, err) opts := stack.NICOptions{Name: Host1NICName}
if err := host1Stack.CreateNICWithOptions(Host1NICID, NewEthernetEndpoint(host1NIC), opts); err != nil {
t.Fatalf("host1Stack.CreateNICWithOptions(%d, _, %#v): %s", Host1NICID, opts, err)
}
} }
if err := routerStack.CreateNIC(RouterNICID1, NewEthernetEndpoint(routerNIC1)); err != nil { {
t.Fatalf("routerStack.CreateNIC(%d, _): %s", RouterNICID1, err) opts := stack.NICOptions{Name: RouterNIC1Name}
if err := routerStack.CreateNICWithOptions(RouterNICID1, NewEthernetEndpoint(routerNIC1), opts); err != nil {
t.Fatalf("routerStack.CreateNICWithOptions(%d, _, %#v): %s", RouterNICID1, opts, err)
}
} }
if err := routerStack.CreateNIC(RouterNICID2, NewEthernetEndpoint(routerNIC2)); err != nil { {
t.Fatalf("routerStack.CreateNIC(%d, _): %s", RouterNICID2, err) opts := stack.NICOptions{Name: RouterNIC2Name}
if err := routerStack.CreateNICWithOptions(RouterNICID2, NewEthernetEndpoint(routerNIC2), opts); err != nil {
t.Fatalf("routerStack.CreateNICWithOptions(%d, _, %#v): %s", RouterNICID2, opts, err)
}
} }
if err := host2Stack.CreateNIC(Host2NICID, NewEthernetEndpoint(host2NIC)); err != nil { {
t.Fatalf("host2Stack.CreateNIC(%d, _): %s", Host2NICID, err) opts := stack.NICOptions{Name: Host2NICName}
if err := host2Stack.CreateNICWithOptions(Host2NICID, NewEthernetEndpoint(host2NIC), opts); err != nil {
t.Fatalf("host2Stack.CreateNICWithOptions(%d, _, %#v): %s", Host2NICID, opts, err)
}
} }
if err := routerStack.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true); err != nil { if err := routerStack.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true); err != nil {
+1 -1
View File
@@ -2066,7 +2066,7 @@ func (e *endpoint) GetSockOpt(opt tcpip.GettableSocketOption) tcpip.Error {
case *tcpip.OriginalDestinationOption: case *tcpip.OriginalDestinationOption:
e.LockUser() e.LockUser()
ipt := e.stack.IPTables() ipt := e.stack.IPTables()
addr, port, err := ipt.OriginalDst(e.TransportEndpointInfo.ID, e.NetProto) addr, port, err := ipt.OriginalDst(e.TransportEndpointInfo.ID, e.NetProto, ProtocolNumber)
e.UnlockUser() e.UnlockUser()
if err != nil { if err != nil {
return err return err