Files
visor/pkg/tcpip/stack/iptables_targets.go
Konstantin BogomolovandgVisor bot 2e622d3dbf Fix panic in iptables UserChainTarget.Action().
Prior to this CL, we assumed that execution would never fall through to
UserChainTargets, because every chain ends with an unconditional RETURN or
policy rule. This is not true with setsockopt(IP6T_SO_SET_REPLACE), with which
a userspace program can supply an arbitrary table layout.

Linux already rejects such tables, and so now we do too. Additionally, such
failures in Linux are handled by just calling ipt_error which logs an error
message instead of panicking, so replacing our panic with a BugTracebackOnce
makes sense as well. This traceback isn't triggered, but it's here just for
future-proofing.

PiperOrigin-RevId: 959886702
2026-08-05 15:02:02 -07:00

444 lines
14 KiB
Go

// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stack
import (
"fmt"
"math"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
)
// AcceptTarget accepts packets.
//
// +stateify savable
type AcceptTarget struct {
// NetworkProtocol is the network protocol the target is used with.
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (*AcceptTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
return RuleAccept, 0
}
// DropTarget drops packets.
//
// +stateify savable
type DropTarget struct {
// NetworkProtocol is the network protocol the target is used with.
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (*DropTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
return RuleDrop, 0
}
// RejectIPv4WithHandler handles rejecting a packet.
type RejectIPv4WithHandler interface {
// SendRejectionError sends an error packet in response to the packet.
SendRejectionError(pkt *PacketBuffer, rejectWith RejectIPv4WithICMPType, inputHook bool) tcpip.Error
}
// RejectIPv4WithICMPType indicates the type of ICMP error that should be sent.
type RejectIPv4WithICMPType int
// The types of errors that may be returned when rejecting IPv4 packets.
const (
_ RejectIPv4WithICMPType = iota
RejectIPv4WithICMPNetUnreachable
RejectIPv4WithICMPHostUnreachable
RejectIPv4WithICMPPortUnreachable
RejectIPv4WithICMPProtUnreachable
RejectIPv4WithICMPEchoReply
RejectIPv4WithICMPNetProhibited
RejectIPv4WithICMPHostProhibited
RejectIPv4WithTCPReset
RejectIPv4WithICMPAdminProhibited
)
// RejectIPv4Target drops packets and sends back an error packet in response to the
// matched packet.
//
// +stateify savable
type RejectIPv4Target struct {
Handler RejectIPv4WithHandler
RejectWith RejectIPv4WithICMPType
}
// Action implements Target.Action.
func (rt *RejectIPv4Target) Action(pkt *PacketBuffer, hook Hook, _ *Route, _ AddressableEndpoint) (RuleVerdict, int) {
switch hook {
case Input, Forward, Output:
// There is nothing reasonable for us to do in response to an error here;
// we already drop the packet.
_ = rt.Handler.SendRejectionError(pkt, rt.RejectWith, hook == Input)
return RuleDrop, 0
case Prerouting, Postrouting:
panic(fmt.Sprintf("%s hook not supported for REDIRECT", hook))
default:
panic(fmt.Sprintf("unhandled hook = %s", hook))
}
}
// RejectIPv6WithHandler handles rejecting a packet.
type RejectIPv6WithHandler interface {
// SendRejectionError sends an error packet in response to the packet.
SendRejectionError(pkt *PacketBuffer, rejectWith RejectIPv6WithICMPType, forwardingHook bool) tcpip.Error
}
// RejectIPv6WithICMPType indicates the type of ICMP error that should be sent.
type RejectIPv6WithICMPType int
// The types of errors that may be returned when rejecting IPv6 packets.
const (
_ RejectIPv6WithICMPType = iota
RejectIPv6WithICMPNoRoute
RejectIPv6WithICMPAdminProhibited
RejectIPv6WithICMPNotNeighbour
RejectIPv6WithICMPAddrUnreachable
RejectIPv6WithICMPPortUnreachable
RejectIPv6WithICMPEchoReply
RejectIPv6WithTCPReset
RejectIPv6WithICMPPolicyFail
RejectIPv6WithICMPRejectRoute
)
// RejectIPv6Target drops packets and sends back an error packet in response to the
// matched packet.
//
// +stateify savable
type RejectIPv6Target struct {
Handler RejectIPv6WithHandler
RejectWith RejectIPv6WithICMPType
}
// Action implements Target.Action.
func (rt *RejectIPv6Target) Action(pkt *PacketBuffer, hook Hook, _ *Route, _ AddressableEndpoint) (RuleVerdict, int) {
switch hook {
case Input, Forward, Output:
// There is nothing reasonable for us to do in response to an error here;
// we already drop the packet.
_ = rt.Handler.SendRejectionError(pkt, rt.RejectWith, hook == Input)
return RuleDrop, 0
case Prerouting, Postrouting:
panic(fmt.Sprintf("%s hook not supported for REDIRECT", hook))
default:
panic(fmt.Sprintf("unhandled hook = %s", hook))
}
}
// ErrorTarget logs an error and drops the packet. It represents a target that
// should be unreachable.
//
// +stateify savable
type ErrorTarget struct {
// NetworkProtocol is the network protocol the target is used with.
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (*ErrorTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
log.Debugf("ErrorTarget triggered.")
return RuleDrop, 0
}
// UserChainTarget marks a rule as the beginning of a user chain.
//
// +stateify savable
type UserChainTarget struct {
// Name is the chain name.
Name string
// NetworkProtocol is the network protocol the target is used with.
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (*UserChainTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
log.BugTracebackOnce(fmt.Errorf("UserChainTarget should never be called"))
return RuleDrop, 0
}
// ReturnTarget returns from the current chain. If the chain is a built-in, the
// hook's underflow should be called.
//
// +stateify savable
type ReturnTarget struct {
// NetworkProtocol is the network protocol the target is used with.
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (*ReturnTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
return RuleReturn, 0
}
// DNATTarget modifies the destination port/IP of packets.
//
// +stateify savable
type DNATTarget struct {
// The new destination address for packets.
//
// Immutable.
Addr tcpip.Address
// The new destination port for packets.
//
// Immutable.
Port uint16
// NetworkProtocol is the network protocol the target is used with.
//
// Immutable.
NetworkProtocol tcpip.NetworkProtocolNumber
// ChangeAddress indicates whether we should check addresses.
//
// Immutable.
ChangeAddress bool
// ChangePort indicates whether we should check ports.
//
// Immutable.
ChangePort bool
}
// Action implements Target.Action.
func (rt *DNATTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if rt.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
"DNATTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
rt.NetworkProtocol, pkt.NetworkProtocolNumber))
}
switch hook {
case Prerouting, Output:
case Input, Forward, Postrouting:
panic(fmt.Sprintf("%s not supported for DNAT", hook))
default:
panic(fmt.Sprintf("%s unrecognized", hook))
}
return dnatAction(pkt, hook, r, rt.Port, rt.Addr, rt.ChangePort, rt.ChangeAddress)
}
// RedirectTarget redirects the packet to this machine by modifying the
// destination port/IP. Outgoing packets are redirected to the loopback device,
// and incoming packets are redirected to the incoming interface (rather than
// forwarded).
//
// +stateify savable
type RedirectTarget struct {
// Port indicates port used to redirect. It is immutable.
Port uint16
// NetworkProtocol is the network protocol the target is used with. It
// is immutable.
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (rt *RedirectTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if rt.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
"RedirectTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
rt.NetworkProtocol, pkt.NetworkProtocolNumber))
}
// Change the address to loopback (127.0.0.1 or ::1) in Output and to
// the primary address of the incoming interface in Prerouting.
var address tcpip.Address
switch hook {
case Output:
if pkt.NetworkProtocolNumber == header.IPv4ProtocolNumber {
address = tcpip.AddrFrom4([4]byte{127, 0, 0, 1})
} else {
address = header.IPv6Loopback
}
case Prerouting:
// addressEP is expected to be set for the prerouting hook.
address = addressEP.MainAddress().Address
default:
panic("redirect target is supported only on output and prerouting hooks")
}
return dnatAction(pkt, hook, r, rt.Port, address, true /* changePort */, true /* changeAddress */)
}
// SNATTarget modifies the source port/IP in the outgoing packets.
//
// +stateify savable
type SNATTarget struct {
Addr tcpip.Address
Port uint16
// NetworkProtocol is the network protocol the target is used with. It
// is immutable.
NetworkProtocol tcpip.NetworkProtocolNumber
// ChangeAddress indicates whether we should check addresses.
//
// Immutable.
ChangeAddress bool
// ChangePort indicates whether we should check ports.
//
// Immutable.
ChangePort bool
}
func dnatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address, changePort, changeAddress bool) (RuleVerdict, int) {
return natAction(pkt, hook, r, PortOrIdentRange{Start: port, Size: 1}, address, true /* dnat */, changePort, changeAddress)
}
func targetPortRangeForTCPAndUDP(originalSrcPort uint16) PortOrIdentRange {
// As per iptables(8),
//
// If no port range is specified, then source ports below 512 will be
// mapped to other ports below 512: those between 512 and 1023 inclusive
// will be mapped to ports below 1024, and other ports will be mapped to
// 1024 or above.
switch {
case originalSrcPort < 512:
return PortOrIdentRange{Start: 1, Size: 511}
case originalSrcPort < 1024:
return PortOrIdentRange{Start: 1, Size: 1023}
default:
return PortOrIdentRange{Start: 1024, Size: math.MaxUint16 - 1023}
}
}
func snatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcpip.Address, changePort, changeAddress bool) (RuleVerdict, int) {
portsOrIdents := PortOrIdentRange{Start: port, Size: 1}
switch pkt.TransportProtocolNumber {
case header.UDPProtocolNumber:
if port == 0 {
portsOrIdents = targetPortRangeForTCPAndUDP(header.UDP(pkt.TransportHeader().Slice()).SourcePort())
}
case header.TCPProtocolNumber:
if port == 0 {
portsOrIdents = targetPortRangeForTCPAndUDP(header.TCP(pkt.TransportHeader().Slice()).SourcePort())
}
case header.ICMPv4ProtocolNumber, header.ICMPv6ProtocolNumber:
// Allow NAT-ing to any 16-bit value for ICMP's Ident field to match Linux
// behaviour.
//
// https://github.com/torvalds/linux/blob/58e1100fdc5990b0cc0d4beaf2562a92e621ac7d/net/netfilter/nf_nat_core.c#L391
portsOrIdents = PortOrIdentRange{Start: 0, Size: math.MaxUint16 + 1}
}
return natAction(pkt, hook, r, portsOrIdents, address, false /* dnat */, changePort, changeAddress)
}
func natAction(pkt *PacketBuffer, hook Hook, r *Route, portsOrIdents PortOrIdentRange, address tcpip.Address, dnat, changePort, changeAddress bool) (RuleVerdict, int) {
// Drop the packet if network and transport header are not set.
if len(pkt.NetworkHeader().Slice()) == 0 || len(pkt.TransportHeader().Slice()) == 0 {
return RuleDrop, 0
}
if t := pkt.tuple; t != nil {
IPTPerformNAT(pkt, hook, r, portsOrIdents, address, dnat, changePort, changeAddress)
return RuleAccept, 0
}
return RuleDrop, 0
}
// Action implements Target.Action.
func (st *SNATTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, _ AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if st.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
"SNATTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
st.NetworkProtocol, pkt.NetworkProtocolNumber))
}
switch hook {
case Postrouting, Input:
case Prerouting, Output, Forward:
panic(fmt.Sprintf("%s not supported", hook))
default:
panic(fmt.Sprintf("%s unrecognized", hook))
}
return snatAction(pkt, hook, r, st.Port, st.Addr, st.ChangePort, st.ChangeAddress)
}
// MasqueradeTarget modifies the source port/IP in the outgoing packets.
//
// +stateify savable
type MasqueradeTarget struct {
// NetworkProtocol is the network protocol the target is used with. It
// is immutable.
NetworkProtocol tcpip.NetworkProtocolNumber
}
// Action implements Target.Action.
func (mt *MasqueradeTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addressEP AddressableEndpoint) (RuleVerdict, int) {
// Sanity check.
if mt.NetworkProtocol != pkt.NetworkProtocolNumber {
panic(fmt.Sprintf(
"MasqueradeTarget.Action with NetworkProtocol %d called on packet with NetworkProtocolNumber %d",
mt.NetworkProtocol, pkt.NetworkProtocolNumber))
}
switch hook {
case Postrouting:
case Prerouting, Input, Forward, Output:
panic(fmt.Sprintf("masquerade target is supported only on postrouting hook; hook = %d", hook))
default:
panic(fmt.Sprintf("%s unrecognized", hook))
}
// addressEP is expected to be set for the postrouting hook.
ep := addressEP.AcquireOutgoingPrimaryAddress(pkt.Network().DestinationAddress(), tcpip.Address{} /* srcHint */, false /* allowExpired */)
if ep == nil {
// No address exists that we can use as a source address.
return RuleDrop, 0
}
address := ep.AddressWithPrefix().Address
ep.DecRef()
return snatAction(pkt, hook, r, 0 /* port */, address, true /* changePort */, true /* changeAddress */)
}
// CTTarget is a no-op implementation of the CT (conntrack) target used in the
// raw table. In Linux, CT --zone sets conntrack zones for connection tracking
// isolation. gVisor's conntrack does not support zones, so this target simply
// accepts the packet, allowing iptables-restore to load rulesets that reference
// CT targets (e.g. Istio with DNS capture enabled).
//
// +stateify savable
type CTTarget struct {
// NetworkProtocol is the network protocol the target is used with.
NetworkProtocol tcpip.NetworkProtocolNumber
// Zone is the conntrack zone ID. Stored but not acted upon.
Zone uint16
}
// Action implements Target.Action. It is a no-op that accepts the packet.
func (*CTTarget) Action(*PacketBuffer, Hook, *Route, AddressableEndpoint) (RuleVerdict, int) {
return RuleAccept, 0
}