Обновление снапшота с v0.0.0-20250811.0 на пин, которого требует sing-box после мержа 235 коммитов (upstream d620bbbf2 "Update gvisor to 20260727.0"). Прежний снапшот был взят 2026-08-04 ровно с той версии, на которой тогда стоял апстрим; разрыв возник 2026-08-05 вместе с его бампом. За год апстрим-gvisor изменил ~14 000 строк в 292 файлах. Значимое для нас — сетевой стек: tcp/connect.go (PMTU-discovery + исправление начального RTT/RTO: раньше задержка ACK внутри стека завышала стартовый таймаут на несколько RTT), tcp/snd.go, tcp/rcv.go, stack/conntrack.go, stack/packet_buffer.go. Всего 30 файлов в TCP и 37 в stack. Баг SPEC 048 апстрим НЕ исправил — проверено по коду новой версии: handleConnecting по-прежнему проверяет состояние endpoint'а, но не ep.h, а performHandshake так же зануляет h и отпускает мьютекс до Close(). Поэтому guard перенесён (12 строк) вместе со своим тестом (45 строк). Red/green проверен на новой базе: без guard'а тест падает с той же nil-паникой, что в полевом крашдампе; с ним зелёный.
441 lines
14 KiB
Go
441 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"
|
|
|
|
"github.com/sagernet/gvisor/pkg/log"
|
|
"github.com/sagernet/gvisor/pkg/tcpip"
|
|
"github.com/sagernet/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) {
|
|
panic("UserChainTarget should never be called.")
|
|
}
|
|
|
|
// 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
|
|
}
|