mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Create null entry connection on first IPTables hook
...all connections should be tracked by ConnTrack, so create a no-op connection entry on the first hook into IPTables (Prerouting or Output) and let NAT targets modify the connection entry if they need to instead of letting the NAT target create their own connection entry. This also prepares for "twice-NAT" where a packet may have both DNAT and SNAT performed on it (which requires the ability to update ConnTrack entries). Updates #5696. PiperOrigin-RevId: 401360377
This commit is contained in:
committed by
gVisor bot
parent
a259115490
commit
dd74503b8e
@@ -647,7 +647,7 @@ func (jt *JumpTarget) id() targetID {
|
||||
}
|
||||
|
||||
// Action implements stack.Target.Action.
|
||||
func (jt *JumpTarget) Action(*stack.PacketBuffer, *stack.ConnTrack, stack.Hook, *stack.Route, stack.AddressableEndpoint) (stack.RuleVerdict, int) {
|
||||
func (jt *JumpTarget) Action(*stack.PacketBuffer, stack.Hook, *stack.Route, stack.AddressableEndpoint) (stack.RuleVerdict, int) {
|
||||
return stack.RuleJump, jt.RuleNum
|
||||
}
|
||||
|
||||
|
||||
+190
-200
File diff suppressed because it is too large
Load Diff
+22
-45
@@ -277,8 +277,9 @@ func (it *IPTables) CheckPrerouting(pkt *PacketBuffer, addressEP AddressableEndp
|
||||
return true
|
||||
}
|
||||
|
||||
if conn, dir := it.connections.connFor(pkt); conn != nil {
|
||||
conn.handlePacket(pkt, hook, dir, nil /* route */)
|
||||
if t := it.connections.getConnOrMaybeInsertNoop(pkt); t != nil {
|
||||
pkt.tuple = t
|
||||
t.conn.handlePacket(pkt, hook, nil /* route */)
|
||||
}
|
||||
|
||||
return it.check(hook, pkt, nil /* route */, addressEP, inNicName, "" /* outNicName */)
|
||||
@@ -297,20 +298,16 @@ func (it *IPTables) CheckInput(pkt *PacketBuffer, inNicName string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
shouldTrack := true
|
||||
if conn, dir := it.connections.connFor(pkt); conn != nil {
|
||||
conn.handlePacket(pkt, hook, dir, nil /* route */)
|
||||
shouldTrack = false
|
||||
if t := pkt.tuple; t != nil {
|
||||
t.conn.handlePacket(pkt, hook, nil /* route */)
|
||||
}
|
||||
|
||||
if !it.check(hook, pkt, nil /* route */, nil /* addressEP */, inNicName, "" /* outNicName */) {
|
||||
return false
|
||||
ret := it.check(hook, pkt, nil /* route */, nil /* addressEP */, inNicName, "" /* outNicName */)
|
||||
if t := pkt.tuple; t != nil {
|
||||
t.conn.finalize()
|
||||
}
|
||||
|
||||
// This is the last hook a packet will perform so if the packet's
|
||||
// connection is not tracked, we may need to add a no-op entry.
|
||||
it.maybeinsertNoopConn(pkt, hook, shouldTrack)
|
||||
return true
|
||||
pkt.tuple = nil
|
||||
return ret
|
||||
}
|
||||
|
||||
// CheckForward performs the forward hook on the packet.
|
||||
@@ -323,7 +320,6 @@ func (it *IPTables) CheckForward(pkt *PacketBuffer, inNicName, outNicName string
|
||||
if it.shouldSkip(pkt.NetworkProtocolNumber) {
|
||||
return true
|
||||
}
|
||||
|
||||
return it.check(Forward, pkt, nil /* route */, nil /* addressEP */, inNicName, outNicName)
|
||||
}
|
||||
|
||||
@@ -340,8 +336,9 @@ func (it *IPTables) CheckOutput(pkt *PacketBuffer, r *Route, outNicName string)
|
||||
return true
|
||||
}
|
||||
|
||||
if conn, dir := it.connections.connFor(pkt); conn != nil {
|
||||
conn.handlePacket(pkt, hook, dir, r)
|
||||
if t := it.connections.getConnOrMaybeInsertNoop(pkt); t != nil {
|
||||
pkt.tuple = t
|
||||
t.conn.handlePacket(pkt, hook, r)
|
||||
}
|
||||
|
||||
return it.check(hook, pkt, r, nil /* addressEP */, "" /* inNicName */, outNicName)
|
||||
@@ -360,20 +357,16 @@ func (it *IPTables) CheckPostrouting(pkt *PacketBuffer, r *Route, addressEP Addr
|
||||
return true
|
||||
}
|
||||
|
||||
shouldTrack := true
|
||||
if conn, dir := it.connections.connFor(pkt); conn != nil {
|
||||
conn.handlePacket(pkt, hook, dir, r)
|
||||
shouldTrack = false
|
||||
if t := pkt.tuple; t != nil {
|
||||
t.conn.handlePacket(pkt, hook, r)
|
||||
}
|
||||
|
||||
if !it.check(hook, pkt, r, addressEP, "" /* inNicName */, outNicName) {
|
||||
return false
|
||||
ret := it.check(hook, pkt, r, addressEP, "" /* inNicName */, outNicName)
|
||||
if t := pkt.tuple; t != nil {
|
||||
t.conn.finalize()
|
||||
}
|
||||
|
||||
// This is the last hook a packet will perform so if the packet's
|
||||
// connection is not tracked, we may need to add a no-op entry.
|
||||
it.maybeinsertNoopConn(pkt, hook, shouldTrack)
|
||||
return true
|
||||
pkt.tuple = nil
|
||||
return ret
|
||||
}
|
||||
|
||||
func (it *IPTables) shouldSkip(netProto tcpip.NetworkProtocolNumber) bool {
|
||||
@@ -426,7 +419,7 @@ func (it *IPTables) check(hook Hook, pkt *PacketBuffer, r *Route, addressEP Addr
|
||||
// Any Return from a built-in chain means we have to
|
||||
// call the underflow.
|
||||
underflow := table.Rules[table.Underflows[hook]]
|
||||
switch v, _ := underflow.Target.Action(pkt, &it.connections, hook, r, addressEP); v {
|
||||
switch v, _ := underflow.Target.Action(pkt, hook, r, addressEP); v {
|
||||
case RuleAccept:
|
||||
continue
|
||||
case RuleDrop:
|
||||
@@ -445,22 +438,6 @@ func (it *IPTables) check(hook Hook, pkt *PacketBuffer, r *Route, addressEP Addr
|
||||
return true
|
||||
}
|
||||
|
||||
func (it *IPTables) maybeinsertNoopConn(pkt *PacketBuffer, hook Hook, shouldTrack bool) {
|
||||
// If this connection should be tracked, try to add an entry for it. If
|
||||
// traversing the nat table didn't end in adding an entry,
|
||||
// maybeInsertNoop will add a no-op entry for the connection. This is
|
||||
// needeed when establishing connections so that the SYN/ACK reply to an
|
||||
// outgoing SYN is delivered to the correct endpoint rather than being
|
||||
// redirected by a prerouting rule.
|
||||
//
|
||||
// From the iptables documentation: "If there is no rule, a `null'
|
||||
// binding is created: this usually does not map the packet, but exists
|
||||
// to ensure we don't map another stream over an existing one."
|
||||
if shouldTrack {
|
||||
it.connections.maybeInsertNoop(pkt)
|
||||
}
|
||||
}
|
||||
|
||||
// beforeSave is invoked by stateify.
|
||||
func (it *IPTables) beforeSave() {
|
||||
// Ensure the reaper exits cleanly.
|
||||
@@ -606,7 +583,7 @@ func (it *IPTables) checkRule(hook Hook, pkt *PacketBuffer, table Table, ruleIdx
|
||||
}
|
||||
|
||||
// All the matchers matched, so run the target.
|
||||
return rule.Target.Action(pkt, &it.connections, hook, r, addressEP)
|
||||
return rule.Target.Action(pkt, hook, r, addressEP)
|
||||
}
|
||||
|
||||
// OriginalDst returns the original destination of redirected connections. It
|
||||
|
||||
@@ -29,7 +29,7 @@ type AcceptTarget struct {
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (*AcceptTarget) Action(*PacketBuffer, *ConnTrack, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
|
||||
func (*AcceptTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
|
||||
return RuleAccept, 0
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ type DropTarget struct {
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (*DropTarget) Action(*PacketBuffer, *ConnTrack, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
|
||||
func (*DropTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
|
||||
return RuleDrop, 0
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ type ErrorTarget struct {
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (*ErrorTarget) Action(*PacketBuffer, *ConnTrack, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
|
||||
func (*ErrorTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
|
||||
log.Debugf("ErrorTarget triggered.")
|
||||
return RuleDrop, 0
|
||||
}
|
||||
@@ -67,7 +67,7 @@ type UserChainTarget struct {
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (*UserChainTarget) Action(*PacketBuffer, *ConnTrack, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
|
||||
func (*UserChainTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
|
||||
panic("UserChainTarget should never be called.")
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ type ReturnTarget struct {
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (*ReturnTarget) Action(*PacketBuffer, *ConnTrack, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
|
||||
func (*ReturnTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
|
||||
return RuleReturn, 0
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ type RedirectTarget struct {
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (rt *RedirectTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
|
||||
func (rt *RedirectTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
|
||||
// Sanity check.
|
||||
if rt.NetworkProtocol != pkt.NetworkProtocolNumber {
|
||||
panic(fmt.Sprintf(
|
||||
@@ -154,15 +154,8 @@ func (rt *RedirectTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r
|
||||
|
||||
pkt.NatDone = true
|
||||
case header.TCPProtocolNumber:
|
||||
if ct == nil {
|
||||
return RuleAccept, 0
|
||||
}
|
||||
|
||||
// Set up conection for matching NAT rule. Only the first
|
||||
// packet of the connection comes here. Other packets will be
|
||||
// manipulated in connection tracking.
|
||||
if conn := ct.insertRedirectConn(pkt, hook, rt.Port, address); conn != nil {
|
||||
conn.handlePacket(pkt, hook, dirOriginal, r)
|
||||
if t := pkt.tuple; t != nil {
|
||||
t.conn.performNAT(pkt, hook, r, rt.Port, address, true /* dnat */)
|
||||
}
|
||||
default:
|
||||
return RuleDrop, 0
|
||||
@@ -181,7 +174,7 @@ type SNATTarget struct {
|
||||
NetworkProtocol tcpip.NetworkProtocolNumber
|
||||
}
|
||||
|
||||
func snatAction(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Route, port uint16, address tcpip.Address) (RuleVerdict, int) {
|
||||
func snatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address) (RuleVerdict, int) {
|
||||
// Packet is already manipulated.
|
||||
if pkt.NatDone {
|
||||
return RuleAccept, 0
|
||||
@@ -197,30 +190,21 @@ func snatAction(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Route, port uint
|
||||
if port == 0 {
|
||||
switch protocol := pkt.TransportProtocolNumber; protocol {
|
||||
case header.UDPProtocolNumber:
|
||||
if port == 0 {
|
||||
port = header.UDP(pkt.TransportHeader().View()).SourcePort()
|
||||
}
|
||||
port = header.UDP(pkt.TransportHeader().View()).SourcePort()
|
||||
case header.TCPProtocolNumber:
|
||||
if port == 0 {
|
||||
port = header.TCP(pkt.TransportHeader().View()).SourcePort()
|
||||
}
|
||||
port = header.TCP(pkt.TransportHeader().View()).SourcePort()
|
||||
}
|
||||
}
|
||||
|
||||
// Set up conection for matching NAT rule. Only the first packet of the
|
||||
// connection comes here. Other packets will be manipulated in connection
|
||||
// tracking.
|
||||
//
|
||||
// Does nothing if the protocol does not support connection tracking.
|
||||
if conn := ct.insertSNATConn(pkt, hook, port, address); conn != nil {
|
||||
conn.handlePacket(pkt, hook, dirOriginal, r)
|
||||
if t := pkt.tuple; t != nil {
|
||||
t.conn.performNAT(pkt, hook, r, port, address, false /* dnat */)
|
||||
}
|
||||
|
||||
return RuleAccept, 0
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (st *SNATTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Route, _ AddressableEndpoint) (RuleVerdict, int) {
|
||||
func (st *SNATTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, _ AddressableEndpoint) (RuleVerdict, int) {
|
||||
// Sanity check.
|
||||
if st.NetworkProtocol != pkt.NetworkProtocolNumber {
|
||||
panic(fmt.Sprintf(
|
||||
@@ -236,7 +220,7 @@ func (st *SNATTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Rou
|
||||
panic(fmt.Sprintf("%s unrecognized", hook))
|
||||
}
|
||||
|
||||
return snatAction(pkt, ct, hook, r, st.Port, st.Addr)
|
||||
return snatAction(pkt, hook, r, st.Port, st.Addr)
|
||||
}
|
||||
|
||||
// MasqueradeTarget modifies the source port/IP in the outgoing packets.
|
||||
@@ -247,7 +231,7 @@ type MasqueradeTarget struct {
|
||||
}
|
||||
|
||||
// Action implements Target.Action.
|
||||
func (mt *MasqueradeTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
|
||||
func (mt *MasqueradeTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
|
||||
// Sanity check.
|
||||
if mt.NetworkProtocol != pkt.NetworkProtocolNumber {
|
||||
panic(fmt.Sprintf(
|
||||
@@ -272,7 +256,7 @@ func (mt *MasqueradeTarget) Action(pkt *PacketBuffer, ct *ConnTrack, hook Hook,
|
||||
|
||||
address := ep.AddressWithPrefix().Address
|
||||
ep.DecRef()
|
||||
return snatAction(pkt, ct, hook, r, 0 /* port */, address)
|
||||
return snatAction(pkt, hook, r, 0 /* port */, address)
|
||||
}
|
||||
|
||||
func rewritePacket(n header.Network, t header.ChecksummableTransport, updateSRCFields, fullChecksum, updatePseudoHeader bool, newPort uint16, newAddr tcpip.Address) {
|
||||
|
||||
@@ -356,5 +356,5 @@ type Target interface {
|
||||
// Action takes an action on the packet and returns a verdict on how
|
||||
// traversal should (or should not) continue. If the return value is
|
||||
// Jump, it also returns the index of the rule to jump to.
|
||||
Action(*PacketBuffer, *ConnTrack, Hook, *Route, AddressableEndpoint) (RuleVerdict, int)
|
||||
Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int)
|
||||
}
|
||||
|
||||
@@ -143,6 +143,8 @@ type PacketBuffer struct {
|
||||
|
||||
// NetworkPacketInfo holds an incoming packet's network-layer information.
|
||||
NetworkPacketInfo NetworkPacketInfo
|
||||
|
||||
tuple *tuple
|
||||
}
|
||||
|
||||
// NewPacketBuffer creates a new PacketBuffer with opts.
|
||||
@@ -302,6 +304,7 @@ func (pk *PacketBuffer) Clone() *PacketBuffer {
|
||||
NICID: pk.NICID,
|
||||
RXTransportChecksumValidated: pk.RXTransportChecksumValidated,
|
||||
NetworkPacketInfo: pk.NetworkPacketInfo,
|
||||
tuple: pk.tuple,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,13 +332,8 @@ func (pk *PacketBuffer) CloneToInbound() *PacketBuffer {
|
||||
buf: pk.buf.Clone(),
|
||||
// Treat unfilled header portion as reserved.
|
||||
reserved: pk.AvailableHeaderBytes(),
|
||||
tuple: pk.tuple,
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -367,12 +365,7 @@ func (pk *PacketBuffer) DeepCopyForForwarding(reservedHeaderBytes int) *PacketBu
|
||||
newPk.TransportProtocolNumber = pk.TransportProtocolNumber
|
||||
}
|
||||
|
||||
// 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
|
||||
newPk.tuple = pk.tuple
|
||||
|
||||
return newPk
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user