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