snapshot: sagernet/gvisor v0.0.0-20260727.0-sing-box-mod.1 + SPEC 048 guard
Обновление снапшота с 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-паникой, что в полевом крашдампе; с ним зелёный.
This commit is contained in:
parent
ffebe42860
commit
117243aa02
293 changed files with 16413 additions and 2842 deletions
|
|
@ -184,7 +184,7 @@ func (d *deadlineTimer) setDeadline(cancelCh *chan struct{}, timer **time.Timer,
|
|||
return
|
||||
}
|
||||
|
||||
timeout := t.Sub(time.Now())
|
||||
timeout := time.Until(t)
|
||||
if timeout <= 0 {
|
||||
close(*cancelCh)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ import (
|
|||
)
|
||||
|
||||
// NullClock implements a clock that never advances.
|
||||
//
|
||||
// +stateify savable
|
||||
type NullClock struct{}
|
||||
|
||||
var _ tcpip.Clock = (*NullClock)(nil)
|
||||
|
|
@ -42,8 +40,6 @@ func (*NullClock) NowMonotonic() tcpip.MonotonicTime {
|
|||
}
|
||||
|
||||
// nullTimer implements a timer that never fires.
|
||||
//
|
||||
// +stateify savable
|
||||
type nullTimer struct{}
|
||||
|
||||
var _ tcpip.Timer = (*nullTimer)(nil)
|
||||
|
|
@ -96,9 +92,8 @@ func (n *notificationChannels) wait() {
|
|||
}
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type manualClockMutex struct {
|
||||
sync.RWMutex `state:"nosave"`
|
||||
sync.RWMutex
|
||||
|
||||
// now is the current (fake) time of the clock.
|
||||
now time.Time
|
||||
|
|
@ -334,9 +329,8 @@ func (mc *ManualClock) stopTimer(mt *manualTimer) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type manualTimerMu struct {
|
||||
sync.Mutex `state:"nosave"`
|
||||
sync.Mutex
|
||||
|
||||
// firesAt is the time when the timer will fire.
|
||||
//
|
||||
|
|
@ -344,13 +338,10 @@ type manualTimerMu struct {
|
|||
firesAt time.Time
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type manualTimer struct {
|
||||
clock *ManualClock
|
||||
// TODO(b/341946753): Restore when netstack is savable.
|
||||
f func() `state:"nosave"`
|
||||
|
||||
mu manualTimerMu
|
||||
f func()
|
||||
mu manualTimerMu
|
||||
}
|
||||
|
||||
var _ tcpip.Timer = (*manualTimer)(nil)
|
||||
|
|
|
|||
|
|
@ -8,79 +8,6 @@ import (
|
|||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (n *NullClock) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.NullClock"
|
||||
}
|
||||
|
||||
func (n *NullClock) StateFields() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (n *NullClock) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (n *NullClock) StateSave(stateSinkObject state.Sink) {
|
||||
n.beforeSave()
|
||||
}
|
||||
|
||||
func (n *NullClock) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (n *NullClock) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
}
|
||||
|
||||
func (n *nullTimer) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.nullTimer"
|
||||
}
|
||||
|
||||
func (n *nullTimer) StateFields() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (n *nullTimer) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (n *nullTimer) StateSave(stateSinkObject state.Sink) {
|
||||
n.beforeSave()
|
||||
}
|
||||
|
||||
func (n *nullTimer) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (n *nullTimer) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
}
|
||||
|
||||
func (m *manualClockMutex) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.manualClockMutex"
|
||||
}
|
||||
|
||||
func (m *manualClockMutex) StateFields() []string {
|
||||
return []string{
|
||||
"now",
|
||||
"times",
|
||||
"timers",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manualClockMutex) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *manualClockMutex) StateSave(stateSinkObject state.Sink) {
|
||||
m.beforeSave()
|
||||
stateSinkObject.Save(0, &m.now)
|
||||
stateSinkObject.Save(1, &m.times)
|
||||
stateSinkObject.Save(2, &m.timers)
|
||||
}
|
||||
|
||||
func (m *manualClockMutex) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *manualClockMutex) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &m.now)
|
||||
stateSourceObject.Load(1, &m.times)
|
||||
stateSourceObject.Load(2, &m.timers)
|
||||
}
|
||||
|
||||
func (mc *ManualClock) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.ManualClock"
|
||||
}
|
||||
|
|
@ -109,64 +36,6 @@ func (mc *ManualClock) StateLoad(ctx context.Context, stateSourceObject state.So
|
|||
stateSourceObject.Load(1, &mc.mu)
|
||||
}
|
||||
|
||||
func (m *manualTimerMu) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.manualTimerMu"
|
||||
}
|
||||
|
||||
func (m *manualTimerMu) StateFields() []string {
|
||||
return []string{
|
||||
"firesAt",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manualTimerMu) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *manualTimerMu) StateSave(stateSinkObject state.Sink) {
|
||||
m.beforeSave()
|
||||
stateSinkObject.Save(0, &m.firesAt)
|
||||
}
|
||||
|
||||
func (m *manualTimerMu) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (m *manualTimerMu) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &m.firesAt)
|
||||
}
|
||||
|
||||
func (mt *manualTimer) StateTypeName() string {
|
||||
return "pkg/tcpip/faketime.manualTimer"
|
||||
}
|
||||
|
||||
func (mt *manualTimer) StateFields() []string {
|
||||
return []string{
|
||||
"clock",
|
||||
"mu",
|
||||
}
|
||||
}
|
||||
|
||||
func (mt *manualTimer) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (mt *manualTimer) StateSave(stateSinkObject state.Sink) {
|
||||
mt.beforeSave()
|
||||
stateSinkObject.Save(0, &mt.clock)
|
||||
stateSinkObject.Save(1, &mt.mu)
|
||||
}
|
||||
|
||||
func (mt *manualTimer) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (mt *manualTimer) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &mt.clock)
|
||||
stateSourceObject.Load(1, &mt.mu)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*NullClock)(nil))
|
||||
state.Register((*nullTimer)(nil))
|
||||
state.Register((*manualClockMutex)(nil))
|
||||
state.Register((*ManualClock)(nil))
|
||||
state.Register((*manualTimerMu)(nil))
|
||||
state.Register((*manualTimer)(nil))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,10 +205,80 @@ func (b IPv6) NextHeader() uint8 {
|
|||
}
|
||||
|
||||
// TransportProtocol implements Network.TransportProtocol.
|
||||
//
|
||||
// Deprecated: Use TryParseTransportProtocol instead.
|
||||
// This function does not parse extension headers and returns the next header
|
||||
// field of the IPv6 header as the transport
|
||||
// protocol which may not be the actual transport protocol.
|
||||
// Use TryParseTransportProtocol to get the transport protocol correctly.
|
||||
func (b IPv6) TransportProtocol() tcpip.TransportProtocolNumber {
|
||||
return tcpip.TransportProtocolNumber(b.NextHeader())
|
||||
}
|
||||
|
||||
// IsExtensionHeader returns true if the next header is a known extension header.
|
||||
func IsExtensionHeader(nextHdr uint8) bool {
|
||||
extType := IPv6ExtensionHeaderIdentifier(nextHdr)
|
||||
switch extType {
|
||||
case IPv6HopByHopOptionsExtHdrIdentifier, IPv6RoutingExtHdrIdentifier, IPv6FragmentExtHdrIdentifier, IPv6DestinationOptionsExtHdrIdentifier, IPv6AuthenticationExtHdrIdentifier, IPv6NoNextHeaderIdentifier:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// TryParseTransportProtocol parses the IPv6 header and extension headers to get the
|
||||
// transport protocol.
|
||||
// Reference: net/ipv6/exthdrs_core.c:ipv6_skip_exthdr.
|
||||
// Returns the transport protocol and a boolean indicating if the transport
|
||||
// protocol parsing was successful.
|
||||
func (b IPv6) TryParseTransportProtocol() (tcpip.TransportProtocolNumber, bool) {
|
||||
if len(b) < IPv6MinimumSize {
|
||||
return 0, false
|
||||
}
|
||||
data := []byte(b[IPv6MinimumSize:])
|
||||
nxtHdr := b.NextHeader()
|
||||
maybeProto := tcpip.TransportProtocolNumber(nxtHdr)
|
||||
for IsExtensionHeader(nxtHdr) {
|
||||
dataLen := len(data)
|
||||
if dataLen < 2 {
|
||||
return maybeProto, false
|
||||
}
|
||||
currHdrLen := 0
|
||||
switch IPv6ExtensionHeaderIdentifier(nxtHdr) {
|
||||
case IPv6FragmentExtHdrIdentifier:
|
||||
// Fragment extension header is always 8 bytes long.
|
||||
if dataLen < 8 {
|
||||
return maybeProto, false
|
||||
}
|
||||
// Get the fragment offset from the fragment extension header.
|
||||
fragOffset := binary.BigEndian.Uint16(data[2:4]) & ^uint16(0x7)
|
||||
if fragOffset != 0 {
|
||||
return tcpip.TransportProtocolNumber(data[0]), false
|
||||
}
|
||||
currHdrLen = 8
|
||||
case IPv6HopByHopOptionsExtHdrIdentifier, IPv6RoutingExtHdrIdentifier, IPv6DestinationOptionsExtHdrIdentifier:
|
||||
currHdrLen = int(data[1]+1) * 8
|
||||
case IPv6AuthenticationExtHdrIdentifier:
|
||||
// Authentication extension header length calculation is different from
|
||||
// other extension headers.
|
||||
currHdrLen = int(data[1]+2) * 4
|
||||
default:
|
||||
// IPv6NoNextHeaderIdentifier or any unknown extension header.
|
||||
return maybeProto, false
|
||||
}
|
||||
if currHdrLen > len(data) {
|
||||
return maybeProto, false
|
||||
}
|
||||
nxtHdr = data[0]
|
||||
maybeProto = tcpip.TransportProtocolNumber(nxtHdr)
|
||||
data = data[currHdrLen:]
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return maybeProto, false
|
||||
}
|
||||
return maybeProto, true
|
||||
}
|
||||
|
||||
// Payload implements Network.Payload.
|
||||
func (b IPv6) Payload() []byte {
|
||||
return b[IPv6MinimumSize:][:b.PayloadLength()]
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ const (
|
|||
// Destination Options extension header, as per RFC 8200 section 4.6.
|
||||
IPv6DestinationOptionsExtHdrIdentifier IPv6ExtensionHeaderIdentifier = 60
|
||||
|
||||
// IPv6AuthenticationExtHdrIdentifier is the header identifier of an
|
||||
// Authentication extension header, as per RFC 8200 section 4.1.
|
||||
// TODO: b/512233021 - Parse Authentication extension header correctly.
|
||||
IPv6AuthenticationExtHdrIdentifier IPv6ExtensionHeaderIdentifier = 51
|
||||
|
||||
// IPv6NoNextHeaderIdentifier is the header identifier used to signify the end
|
||||
// of an IPv6 payload, as per RFC 8200 section 4.7.
|
||||
IPv6NoNextHeaderIdentifier IPv6ExtensionHeaderIdentifier = 59
|
||||
|
|
|
|||
|
|
@ -158,9 +158,19 @@ traverseExtensions:
|
|||
//
|
||||
// Returns true if the header was successfully parsed.
|
||||
func UDP(pkt *stack.PacketBuffer) bool {
|
||||
_, ok := pkt.TransportHeader().Consume(header.UDPMinimumSize)
|
||||
hdr, ok := pkt.TransportHeader().Consume(header.UDPMinimumSize)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
pkt.TransportProtocolNumber = header.UDPProtocolNumber
|
||||
return ok
|
||||
// Validate the UDP payload length.
|
||||
length := int(header.UDP(hdr).Length()) - header.UDPMinimumSize
|
||||
if length < 0 || length > pkt.Data().Size() {
|
||||
return false
|
||||
}
|
||||
// Trim the payload to the length specified in the UDP header.
|
||||
pkt.Data().CapLength(length)
|
||||
return true
|
||||
}
|
||||
|
||||
// TCP parses a TCP packet found in pkt.Data and populates pkt's transport
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ package header
|
|||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/google/btree"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/seqnum"
|
||||
|
|
@ -175,11 +174,6 @@ type SACKBlock struct {
|
|||
End seqnum.Value
|
||||
}
|
||||
|
||||
// Less returns true if r.Start < b.Start.
|
||||
func (r SACKBlock) Less(b btree.Item) bool {
|
||||
return r.Start.LessThan(b.(SACKBlock).Start)
|
||||
}
|
||||
|
||||
// Contains returns true if b is completely contained in r.
|
||||
func (r SACKBlock) Contains(b SACKBlock) bool {
|
||||
return r.Start.LessThanEq(b.Start) && b.End.LessThanEq(r.End)
|
||||
|
|
@ -219,9 +213,8 @@ const (
|
|||
// TCPTotalHeaderMaximumSize is the maximum size of headers from all layers in
|
||||
// a TCP packet. It analogous to MAX_TCP_HEADER in Linux.
|
||||
//
|
||||
// TODO(b/319936470): Investigate why this needs to be at least 140 bytes. In
|
||||
// Linux this value is at least 160, but in theory we should be able to use
|
||||
// 138. In practice anything less than 140 starts to break GSO on gVNIC
|
||||
// Note: In Linux this value is at least 160, but in theory we should be able
|
||||
// to use 138. In practice anything less than 140 starts to break GSO on gVNIC
|
||||
// hardware.
|
||||
TCPTotalHeaderMaximumSize = 160
|
||||
|
||||
|
|
|
|||
|
|
@ -138,20 +138,33 @@ func (b UDP) Encode(u *UDPFields) {
|
|||
|
||||
// SetSourcePortWithChecksumUpdate implements ChecksummableTransport.
|
||||
func (b UDP) SetSourcePortWithChecksumUpdate(new uint16) {
|
||||
if b.Checksum() == 0 {
|
||||
b.SetSourcePort(new)
|
||||
return
|
||||
}
|
||||
old := b.SourcePort()
|
||||
b.SetSourcePort(new)
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
|
||||
xsum := ^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new)
|
||||
b.SetChecksum(normalizeChecksum(xsum))
|
||||
}
|
||||
|
||||
// SetDestinationPortWithChecksumUpdate implements ChecksummableTransport.
|
||||
func (b UDP) SetDestinationPortWithChecksumUpdate(new uint16) {
|
||||
if b.Checksum() == 0 {
|
||||
b.SetDestinationPort(new)
|
||||
return
|
||||
}
|
||||
old := b.DestinationPort()
|
||||
b.SetDestinationPort(new)
|
||||
b.SetChecksum(^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new))
|
||||
xsum := ^checksumUpdate2ByteAlignedUint16(^b.Checksum(), old, new)
|
||||
b.SetChecksum(normalizeChecksum(xsum))
|
||||
}
|
||||
|
||||
// UpdateChecksumPseudoHeaderAddress implements ChecksummableTransport.
|
||||
func (b UDP) UpdateChecksumPseudoHeaderAddress(old, new tcpip.Address, fullChecksum bool) {
|
||||
if fullChecksum && b.Checksum() == 0 {
|
||||
return
|
||||
}
|
||||
xsum := b.Checksum()
|
||||
if fullChecksum {
|
||||
xsum = ^xsum
|
||||
|
|
@ -159,7 +172,7 @@ func (b UDP) UpdateChecksumPseudoHeaderAddress(old, new tcpip.Address, fullCheck
|
|||
|
||||
xsum = checksumUpdate2ByteAlignedAddress(xsum, old, new)
|
||||
if fullChecksum {
|
||||
xsum = ^xsum
|
||||
xsum = normalizeChecksum(^xsum)
|
||||
}
|
||||
|
||||
b.SetChecksum(xsum)
|
||||
|
|
@ -197,3 +210,14 @@ func UDPValid(hdr UDP, payloadChecksum func() uint16, payloadSize uint16, netPro
|
|||
|
||||
return true, hdr.IsChecksumValid(srcAddr, dstAddr, payloadChecksum())
|
||||
}
|
||||
|
||||
func normalizeChecksum(xsum uint16) uint16 {
|
||||
// RFC 768:
|
||||
// If the computed UDP checksum is zero, it is transmitted as all ones.
|
||||
// An all zero transmitted checksum value means that
|
||||
// the transmitter generated no checksum.
|
||||
if xsum == 0 {
|
||||
return 0xFFFF
|
||||
}
|
||||
return xsum
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func endpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointRWMutex](), endpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func queueinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
queueinitLockNames()
|
||||
queueprefixIndex = locking.NewMutexClass(reflect.TypeOf(queueRWMutex{}), queuelockNames)
|
||||
queueprefixIndex = locking.NewMutexClass(reflect.TypeFor[queueRWMutex](), queuelockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,11 @@ func (e *Endpoint) LinkAddress() tcpip.LinkAddress {
|
|||
|
||||
// MTU implements stack.LinkEndpoint.
|
||||
func (e *Endpoint) MTU() uint32 {
|
||||
return e.Endpoint.MTU()
|
||||
// It prevents upper-layers from sending larger than expected packets.
|
||||
if mtu := e.Endpoint.MTU(); mtu > header.EthernetMinimumSize {
|
||||
return mtu - header.EthernetMinimumSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// DeliverNetworkPacket implements stack.NetworkDispatcher.
|
||||
|
|
|
|||
|
|
@ -203,10 +203,6 @@ type Options struct {
|
|||
// include CapabilitySaveRestore
|
||||
SaveRestore bool
|
||||
|
||||
// DisconnectOk if true, indicates that this NIC capability set should
|
||||
// include CapabilityDisconnectOk.
|
||||
DisconnectOk bool
|
||||
|
||||
// GSOMaxSize is the maximum GSO packet size. It is zero if GSO is
|
||||
// disabled.
|
||||
GSOMaxSize uint32
|
||||
|
|
@ -240,19 +236,21 @@ type Options struct {
|
|||
// ProcessorsPerChannel is the number of goroutines used to handle packets
|
||||
// from each FD.
|
||||
ProcessorsPerChannel int
|
||||
|
||||
// IsPacketSocket indicates whether each FD is a packet socket.
|
||||
// If nil, getsockname will be called.
|
||||
IsPacketSocket []bool
|
||||
|
||||
// PreConfigured indicates that socket setup (getsockname, setsockopt)
|
||||
// has already been performed on the host.
|
||||
PreConfigured bool
|
||||
}
|
||||
|
||||
// fanoutID is used for AF_PACKET based endpoints to enable PACKET_FANOUT
|
||||
// support in the host kernel. This allows us to use multiple FD's to receive
|
||||
// from the same underlying NIC. The fanoutID needs to be the same for a given
|
||||
// set of FD's that point to the same NIC. Trying to set the PACKET_FANOUT
|
||||
// option for an FD with a fanoutID already in use by another FD for a different
|
||||
// NIC will return an EINVAL.
|
||||
//
|
||||
// Since fanoutID must be unique within the network namespace, we start with
|
||||
// the PID to avoid collisions. The only way to be sure of avoiding collisions
|
||||
// is to run in a new network namespace.
|
||||
var fanoutID atomicbitops.Int32 = atomicbitops.FromInt32(int32(unix.Getpid()))
|
||||
// fallbackFanoutID is used only when PACKET_FANOUT_FLAG_UNIQUEID is not
|
||||
// supported by the host kernel. It preserves the PID-seeded best-effort behavior:
|
||||
// seed from unix.Getpid() and increment per endpoint. This is not
|
||||
// collision-free across sentries that share a network namespace.
|
||||
var fallbackFanoutID atomicbitops.Int32 = atomicbitops.FromInt32(int32(unix.Getpid()))
|
||||
|
||||
// New creates a new fd-based endpoint.
|
||||
//
|
||||
|
|
@ -279,10 +277,6 @@ func New(opts *Options) (stack.LinkEndpoint, error) {
|
|||
caps |= stack.CapabilitySaveRestore
|
||||
}
|
||||
|
||||
if opts.DisconnectOk {
|
||||
caps |= stack.CapabilityDisconnectOk
|
||||
}
|
||||
|
||||
if len(opts.FDs) == 0 {
|
||||
return nil, fmt.Errorf("opts.FD is empty, at least one FD must be specified")
|
||||
}
|
||||
|
|
@ -307,17 +301,18 @@ func New(opts *Options) (stack.LinkEndpoint, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Increment fanoutID to ensure that we don't re-use the same fanoutID
|
||||
// for the next endpoint.
|
||||
fid := fanoutID.Add(1)
|
||||
// Fanout id allocated by the kernel for this endpoint. All AF_PACKET FDs
|
||||
// belonging to this endpoint must use the same id. -1 means no AF_PACKET
|
||||
// FD has allocated an id yet; 0 is a valid fanout id.
|
||||
fid := int32(-1)
|
||||
|
||||
// Create per channel dispatchers.
|
||||
for _, fd := range opts.FDs {
|
||||
for i, fd := range opts.FDs {
|
||||
if err := unix.SetNonblock(fd, true); err != nil {
|
||||
return nil, fmt.Errorf("unix.SetNonblock(%v) failed: %v", fd, err)
|
||||
}
|
||||
|
||||
isSocket, err := isSocketFD(fd)
|
||||
isSocket, err := IsSocketFD(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -334,7 +329,34 @@ func New(opts *Options) (stack.LinkEndpoint, error) {
|
|||
opts.ProcessorsPerChannel = max(1, runtime.GOMAXPROCS(0)/len(opts.FDs))
|
||||
}
|
||||
|
||||
inboundDispatcher, err := createInboundDispatcher(e, fd, isSocket, fid, opts)
|
||||
var isPacket bool
|
||||
if opts.PreConfigured {
|
||||
if opts.IsPacketSocket != nil && i < len(opts.IsPacketSocket) {
|
||||
isPacket = opts.IsPacketSocket[i]
|
||||
} else {
|
||||
return nil, fmt.Errorf("PreConfigured is true but IsPacketSocket is missing or too short (index %d, len %d)", i, len(opts.IsPacketSocket))
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
isPacket, err = IsPacketSocket(fd, isSocket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if isPacket && !opts.PreConfigured {
|
||||
var err error
|
||||
if fid < 0 {
|
||||
fid, err = CreatePacketFanoutGroup(fd)
|
||||
} else {
|
||||
err = JoinPacketFanoutGroup(fd, fid)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to enable PACKET_FANOUT option: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
inboundDispatcher, err := createInboundDispatcher(e, fd, isSocket, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("createInboundDispatcher(...) = %v", err)
|
||||
}
|
||||
|
|
@ -344,7 +366,7 @@ func New(opts *Options) (stack.LinkEndpoint, error) {
|
|||
return e, nil
|
||||
}
|
||||
|
||||
func createInboundDispatcher(e *endpoint, fd int, isSocket bool, fID int32, opts *Options) (linkDispatcher, error) {
|
||||
func createInboundDispatcher(e *endpoint, fd int, isSocket bool, opts *Options) (linkDispatcher, error) {
|
||||
// By default use the readv() dispatcher as it works with all kinds of
|
||||
// FDs (tap/tun/unix domain sockets and af_packet).
|
||||
inboundDispatcher, err := newReadVDispatcher(fd, e, opts)
|
||||
|
|
@ -353,38 +375,6 @@ func createInboundDispatcher(e *endpoint, fd int, isSocket bool, fID int32, opts
|
|||
}
|
||||
|
||||
if isSocket {
|
||||
sa, err := unix.Getsockname(fd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unix.Getsockname(%d) = %v", fd, err)
|
||||
}
|
||||
switch sa.(type) {
|
||||
case *unix.SockaddrLinklayer:
|
||||
// Enable PACKET_FANOUT mode if the underlying socket is of type
|
||||
// AF_PACKET. We do not enable PACKET_FANOUT_FLAG_DEFRAG as that will
|
||||
// prevent gvisor from receiving fragmented packets and the host does the
|
||||
// reassembly on our behalf before delivering the fragments. This makes it
|
||||
// hard to test fragmentation reassembly code in Netstack.
|
||||
//
|
||||
// See: include/uapi/linux/if_packet.h (struct fanout_args).
|
||||
//
|
||||
// NOTE: We are using SetSockOptInt here even though the underlying
|
||||
// option is actually a struct. The code follows the example in the
|
||||
// kernel documentation as described at the link below:
|
||||
//
|
||||
// See: https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt
|
||||
//
|
||||
// This works out because the actual implementation for the option zero
|
||||
// initializes the structure and will initialize the max_members field
|
||||
// to a proper value if zero.
|
||||
//
|
||||
// See: https://github.com/torvalds/linux/blob/7acac4b3196caee5e21fb5ea53f8bc124e6a16fc/net/packet/af_packet.c#L3881
|
||||
const fanoutType = unix.PACKET_FANOUT_HASH
|
||||
fanoutArg := (int(fID) & 0xffff) | fanoutType<<16
|
||||
if err := unix.SetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_FANOUT, fanoutArg); err != nil {
|
||||
return nil, fmt.Errorf("failed to enable PACKET_FANOUT option: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch e.packetDispatchMode {
|
||||
case PacketMMap:
|
||||
inboundDispatcher, err = newPacketMMapDispatcher(fd, e, opts)
|
||||
|
|
@ -407,7 +397,82 @@ func createInboundDispatcher(e *endpoint, fd int, isSocket bool, fID int32, opts
|
|||
return inboundDispatcher, nil
|
||||
}
|
||||
|
||||
func isSocketFD(fd int) (bool, error) {
|
||||
// IsPacketSocket checks if the FD is an AF_PACKET socket.
|
||||
func IsPacketSocket(fd int, isSocket bool) (bool, error) {
|
||||
if !isSocket {
|
||||
return false, nil
|
||||
}
|
||||
sa, err := unix.Getsockname(fd)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("unix.Getsockname(%d) = %v", fd, err)
|
||||
}
|
||||
_, ok := sa.(*unix.SockaddrLinklayer)
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// CreatePacketFanoutGroup enables PACKET_FANOUT for the first AF_PACKET socket
|
||||
// in an endpoint and returns the fanout id the group joined.
|
||||
//
|
||||
// All AF_PACKET FDs that back the same endpoint must join the same fanout
|
||||
// group so the host kernel consistently hashes packets for a flow to one FD.
|
||||
// Fanout ids are unique within the Linux network namespace that owns the
|
||||
// sockets; reusing an id for a different NIC in that namespace fails with
|
||||
// EINVAL.
|
||||
//
|
||||
// We ask the kernel to allocate the id via PACKET_FANOUT_FLAG_UNIQUEID so the
|
||||
// id is guaranteed unique within the namespace even when multiple sentries
|
||||
// share it. If that setsockopt fails (e.g. the host kernel predates
|
||||
// PACKET_FANOUT_FLAG_UNIQUEID), we fall back to the PID-seeded
|
||||
// fallbackFanoutID, which is best-effort and not collision-free across
|
||||
// sentries that share a network namespace.
|
||||
//
|
||||
// We do not enable PACKET_FANOUT_FLAG_DEFRAG as that will prevent gvisor from
|
||||
// receiving fragmented packets and the host does the reassembly on our behalf
|
||||
// before delivering the fragments. This makes it hard to test fragmentation
|
||||
// reassembly code in Netstack.
|
||||
//
|
||||
// See: include/uapi/linux/if_packet.h (struct fanout_args).
|
||||
//
|
||||
// NOTE: We are using SetSockOptInt here even though the underlying option is
|
||||
// actually a struct. The code follows the example in the kernel documentation
|
||||
// as described at the link below:
|
||||
//
|
||||
// See: https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt
|
||||
//
|
||||
// This works out because the actual implementation for the option zero
|
||||
// initializes the structure and will initialize the max_members field to a
|
||||
// proper value if zero.
|
||||
//
|
||||
// See: https://github.com/torvalds/linux/blob/7acac4b3196caee5e21fb5ea53f8bc124e6a16fc/net/packet/af_packet.c#L3881
|
||||
func CreatePacketFanoutGroup(fd int) (int32, error) {
|
||||
const fanoutType = unix.PACKET_FANOUT_HASH
|
||||
fanoutArg := (fanoutType | unix.PACKET_FANOUT_FLAG_UNIQUEID) << 16
|
||||
if err := unix.SetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_FANOUT, fanoutArg); err != nil {
|
||||
uniqueIDErr := err
|
||||
fallbackID := fallbackFanoutID.Add(1)
|
||||
fanoutArg = (int(fallbackID) & 0xffff) | fanoutType<<16
|
||||
if err := unix.SetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_FANOUT, fanoutArg); err != nil {
|
||||
return 0, fmt.Errorf("UNIQUEID failed (%v); fallback fanout id %d also failed: %v", uniqueIDErr, fanoutArg&0xffff, err)
|
||||
}
|
||||
return int32(fanoutArg & 0xffff), nil
|
||||
}
|
||||
|
||||
fanoutArg, err := unix.GetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_FANOUT)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("getsockopt(PACKET_FANOUT) failed: %v", err)
|
||||
}
|
||||
return int32(fanoutArg & 0xffff), nil
|
||||
}
|
||||
|
||||
// JoinPacketFanoutGroup joins the FD to the specified fanout group.
|
||||
func JoinPacketFanoutGroup(fd int, fID int32) error {
|
||||
const fanoutType = unix.PACKET_FANOUT_HASH
|
||||
fanoutArg := (int(fID) & 0xffff) | fanoutType<<16
|
||||
return unix.SetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_FANOUT, fanoutArg)
|
||||
}
|
||||
|
||||
// IsSocketFD checks if the FD is a socket.
|
||||
func IsSocketFD(fd int) (bool, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Fstat(fd, &stat); err != nil {
|
||||
return false, fmt.Errorf("unix.Fstat(%v,...) failed: %v", fd, err)
|
||||
|
|
@ -892,7 +957,7 @@ func (e *InjectableEndpoint) InjectInbound(protocol tcpip.NetworkProtocolNumber,
|
|||
// NewInjectable creates a new fd-based InjectableEndpoint.
|
||||
func NewInjectable(fd int, mtu uint32, capabilities stack.LinkEndpointCapabilities) (*InjectableEndpoint, error) {
|
||||
unix.SetNonblock(fd, true)
|
||||
isSocket, err := isSocketFD(fd)
|
||||
isSocket, err := IsSocketFD(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func endpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointRWMutex](), endpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,6 @@ func (o *Options) StateFields() []string {
|
|||
"ClosedFunc",
|
||||
"Address",
|
||||
"SaveRestore",
|
||||
"DisconnectOk",
|
||||
"GSOMaxSize",
|
||||
"GVisorGSOEnabled",
|
||||
"PacketDispatchMode",
|
||||
|
|
@ -122,6 +121,8 @@ func (o *Options) StateFields() []string {
|
|||
"InterfaceIndex",
|
||||
"GRO",
|
||||
"ProcessorsPerChannel",
|
||||
"IsPacketSocket",
|
||||
"PreConfigured",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,16 +137,17 @@ func (o *Options) StateSave(stateSinkObject state.Sink) {
|
|||
stateSinkObject.Save(3, &o.ClosedFunc)
|
||||
stateSinkObject.Save(4, &o.Address)
|
||||
stateSinkObject.Save(5, &o.SaveRestore)
|
||||
stateSinkObject.Save(6, &o.DisconnectOk)
|
||||
stateSinkObject.Save(7, &o.GSOMaxSize)
|
||||
stateSinkObject.Save(8, &o.GVisorGSOEnabled)
|
||||
stateSinkObject.Save(9, &o.PacketDispatchMode)
|
||||
stateSinkObject.Save(10, &o.TXChecksumOffload)
|
||||
stateSinkObject.Save(11, &o.RXChecksumOffload)
|
||||
stateSinkObject.Save(12, &o.MaxSyscallHeaderBytes)
|
||||
stateSinkObject.Save(13, &o.InterfaceIndex)
|
||||
stateSinkObject.Save(14, &o.GRO)
|
||||
stateSinkObject.Save(15, &o.ProcessorsPerChannel)
|
||||
stateSinkObject.Save(6, &o.GSOMaxSize)
|
||||
stateSinkObject.Save(7, &o.GVisorGSOEnabled)
|
||||
stateSinkObject.Save(8, &o.PacketDispatchMode)
|
||||
stateSinkObject.Save(9, &o.TXChecksumOffload)
|
||||
stateSinkObject.Save(10, &o.RXChecksumOffload)
|
||||
stateSinkObject.Save(11, &o.MaxSyscallHeaderBytes)
|
||||
stateSinkObject.Save(12, &o.InterfaceIndex)
|
||||
stateSinkObject.Save(13, &o.GRO)
|
||||
stateSinkObject.Save(14, &o.ProcessorsPerChannel)
|
||||
stateSinkObject.Save(15, &o.IsPacketSocket)
|
||||
stateSinkObject.Save(16, &o.PreConfigured)
|
||||
}
|
||||
|
||||
func (o *Options) afterLoad(context.Context) {}
|
||||
|
|
@ -158,16 +160,17 @@ func (o *Options) StateLoad(ctx context.Context, stateSourceObject state.Source)
|
|||
stateSourceObject.Load(3, &o.ClosedFunc)
|
||||
stateSourceObject.Load(4, &o.Address)
|
||||
stateSourceObject.Load(5, &o.SaveRestore)
|
||||
stateSourceObject.Load(6, &o.DisconnectOk)
|
||||
stateSourceObject.Load(7, &o.GSOMaxSize)
|
||||
stateSourceObject.Load(8, &o.GVisorGSOEnabled)
|
||||
stateSourceObject.Load(9, &o.PacketDispatchMode)
|
||||
stateSourceObject.Load(10, &o.TXChecksumOffload)
|
||||
stateSourceObject.Load(11, &o.RXChecksumOffload)
|
||||
stateSourceObject.Load(12, &o.MaxSyscallHeaderBytes)
|
||||
stateSourceObject.Load(13, &o.InterfaceIndex)
|
||||
stateSourceObject.Load(14, &o.GRO)
|
||||
stateSourceObject.Load(15, &o.ProcessorsPerChannel)
|
||||
stateSourceObject.Load(6, &o.GSOMaxSize)
|
||||
stateSourceObject.Load(7, &o.GVisorGSOEnabled)
|
||||
stateSourceObject.Load(8, &o.PacketDispatchMode)
|
||||
stateSourceObject.Load(9, &o.TXChecksumOffload)
|
||||
stateSourceObject.Load(10, &o.RXChecksumOffload)
|
||||
stateSourceObject.Load(11, &o.MaxSyscallHeaderBytes)
|
||||
stateSourceObject.Load(12, &o.InterfaceIndex)
|
||||
stateSourceObject.Load(13, &o.GRO)
|
||||
stateSourceObject.Load(14, &o.ProcessorsPerChannel)
|
||||
stateSourceObject.Load(15, &o.IsPacketSocket)
|
||||
stateSourceObject.Load(16, &o.PreConfigured)
|
||||
}
|
||||
|
||||
func (e *InjectableEndpoint) StateTypeName() string {
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func injectableEndpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
injectableEndpointinitLockNames()
|
||||
injectableEndpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(injectableEndpointRWMutex{}), injectableEndpointlockNames)
|
||||
injectableEndpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[injectableEndpointRWMutex](), injectableEndpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,5 +60,5 @@ func processorinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
processorinitLockNames()
|
||||
processorprefixIndex = locking.NewMutexClass(reflect.TypeOf(processorMutex{}), processorlockNames)
|
||||
processorprefixIndex = locking.NewMutexClass(reflect.TypeFor[processorMutex](), processorlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,6 @@ type processor struct {
|
|||
|
||||
func (p *processor) start(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
defer p.sleeper.Done()
|
||||
for {
|
||||
switch w := p.sleeper.Fetch(true); {
|
||||
case w == &p.packetWaker:
|
||||
|
|
@ -128,9 +127,10 @@ func (m *processorManager) start() {
|
|||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (m *processorManager) afterLoad(context.Context) {
|
||||
m.wg.Add(len(m.processors))
|
||||
m.start()
|
||||
func (m *processorManager) afterLoad(ctx context.Context) {
|
||||
// Close all the old/saved processors. There are new NICs and
|
||||
// processors created during restore.
|
||||
m.close()
|
||||
}
|
||||
|
||||
func (m *processorManager) connectionHash(cid *connectionID) uint32 {
|
||||
|
|
@ -215,34 +215,47 @@ func tcpipConnectionID(pkt *stack.PacketBuffer) (connectionID, bool) {
|
|||
return cid, true
|
||||
}
|
||||
ipHdr := header.IPv6(h)
|
||||
cid.srcAddr = ipHdr.SourceAddressSlice()
|
||||
cid.dstAddr = ipHdr.DestinationAddressSlice()
|
||||
cid.proto = header.IPv6ProtocolNumber
|
||||
|
||||
var tcpHdr header.TCP
|
||||
if tcpip.TransportProtocolNumber(ipHdr.NextHeader()) == header.TCPProtocolNumber {
|
||||
tcpHdr = header.TCP(h[header.IPv6FixedHeaderSize:][:tcpSrcDstPortLen])
|
||||
if !header.IsExtensionHeader(ipHdr.NextHeader()) {
|
||||
// Known transport protocols(not just TCP) store the src and dst ports
|
||||
// in the first 4 bytes after the IPv6 fixed header.
|
||||
tcpHdr := header.TCP(h[header.IPv6FixedHeaderSize:][:tcpSrcDstPortLen])
|
||||
cid.srcPort = tcpHdr.SourcePort()
|
||||
cid.dstPort = tcpHdr.DestinationPort()
|
||||
} else {
|
||||
// Slow path for IPv6 extension headers :(.
|
||||
dataBuf := pkt.Data().ToBuffer()
|
||||
dataBuf.TrimFront(header.IPv6MinimumSize)
|
||||
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(ipHdr.NextHeader()), dataBuf)
|
||||
defer it.Release()
|
||||
// All fragment packets need to be processed by the same goroutine, so
|
||||
// only record the ports if this is not a fragment packet.
|
||||
var isFragment bool
|
||||
for {
|
||||
hdr, done, err := it.Next()
|
||||
if done || err != nil {
|
||||
break
|
||||
}
|
||||
if fh, ok := hdr.(header.IPv6FragmentExtHdr); ok && !fh.IsAtomic() {
|
||||
isFragment = true
|
||||
}
|
||||
hdr.Release()
|
||||
}
|
||||
h, ok = pkt.Data().PullUp(int(it.HeaderOffset()) + tcpSrcDstPortLen)
|
||||
if !ok {
|
||||
return cid, true
|
||||
if !isFragment {
|
||||
h, ok = pkt.Data().PullUp(int(it.HeaderOffset()) + tcpSrcDstPortLen)
|
||||
if !ok {
|
||||
return cid, true
|
||||
}
|
||||
// Known transport protocols store the src and dst ports
|
||||
// in the first 4 bytes after the IPv6 fixed header.
|
||||
tcpHdr := header.TCP(h[it.HeaderOffset():][:tcpSrcDstPortLen])
|
||||
cid.srcPort = tcpHdr.SourcePort()
|
||||
cid.dstPort = tcpHdr.DestinationPort()
|
||||
}
|
||||
tcpHdr = header.TCP(h[it.HeaderOffset():][:tcpSrcDstPortLen])
|
||||
}
|
||||
cid.srcAddr = ipHdr.SourceAddressSlice()
|
||||
cid.dstAddr = ipHdr.DestinationAddressSlice()
|
||||
cid.srcPort = tcpHdr.SourcePort()
|
||||
cid.dstPort = tcpHdr.DestinationPort()
|
||||
cid.proto = header.IPv6ProtocolNumber
|
||||
default:
|
||||
return cid, true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func endpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointRWMutex](), endpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func endpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointRWMutex](), endpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func endpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointRWMutex](), endpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,5 +60,5 @@ func queueDispatcherinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
queueDispatcherinitLockNames()
|
||||
queueDispatcherprefixIndex = locking.NewMutexClass(reflect.TypeOf(queueDispatcherMutex{}), queueDispatcherlockNames)
|
||||
queueDispatcherprefixIndex = locking.NewMutexClass(reflect.TypeFor[queueDispatcherMutex](), queueDispatcherlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/sagernet/gvisor/pkg/sleep"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/qdisc"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
|
|
@ -60,7 +61,7 @@ type queueDispatcher struct {
|
|||
|
||||
mu queueDispatcherMutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
queue packetBufferCircularList
|
||||
queue qdisc.PacketBufferCircularList
|
||||
|
||||
newPacketWaker sleep.Waker `state:"nosave"`
|
||||
closeWaker sleep.Waker `state:"nosave"`
|
||||
|
|
@ -78,7 +79,7 @@ func New(lower stack.LinkWriter, n int, queueLen int) stack.QueueingDiscipline {
|
|||
for i := range d.dispatchers {
|
||||
qd := &d.dispatchers[i]
|
||||
qd.lower = lower
|
||||
qd.queue.init(queueLen)
|
||||
qd.queue.Init(queueLen)
|
||||
|
||||
d.wg.Add(1)
|
||||
go func() {
|
||||
|
|
@ -101,19 +102,19 @@ func (qd *queueDispatcher) dispatchLoop() {
|
|||
case &qd.newPacketWaker:
|
||||
case &qd.closeWaker:
|
||||
qd.mu.Lock()
|
||||
for p := qd.queue.removeFront(); p != nil; p = qd.queue.removeFront() {
|
||||
for p := qd.queue.RemoveFront(); p != nil; p = qd.queue.RemoveFront() {
|
||||
p.DecRef()
|
||||
}
|
||||
qd.queue.decRef()
|
||||
qd.queue.DecRef()
|
||||
qd.mu.Unlock()
|
||||
return
|
||||
default:
|
||||
panic("unknown waker")
|
||||
}
|
||||
qd.mu.Lock()
|
||||
for pkt := qd.queue.removeFront(); pkt != nil; pkt = qd.queue.removeFront() {
|
||||
for pkt := qd.queue.RemoveFront(); pkt != nil; pkt = qd.queue.RemoveFront() {
|
||||
batch.PushBack(pkt)
|
||||
if batch.Len() < BatchSize && !qd.queue.isEmpty() {
|
||||
if batch.Len() < BatchSize && !qd.queue.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
qd.mu.Unlock()
|
||||
|
|
@ -137,9 +138,13 @@ func (d *discipline) WritePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
|||
}
|
||||
qd := &d.dispatchers[int(pkt.Hash)%len(d.dispatchers)]
|
||||
qd.mu.Lock()
|
||||
haveSpace := qd.queue.hasSpace()
|
||||
if d.closed.Load() == qDiscClosed {
|
||||
qd.mu.Unlock()
|
||||
return &tcpip.ErrClosedForSend{}
|
||||
}
|
||||
haveSpace := qd.queue.HasSpace()
|
||||
if haveSpace {
|
||||
qd.queue.pushBack(pkt.IncRef())
|
||||
qd.queue.PushBack(pkt.IncRef())
|
||||
}
|
||||
qd.mu.Unlock()
|
||||
if !haveSpace {
|
||||
|
|
|
|||
|
|
@ -64,39 +64,7 @@ func (qd *queueDispatcher) StateLoad(ctx context.Context, stateSourceObject stat
|
|||
stateSourceObject.Load(1, &qd.queue)
|
||||
}
|
||||
|
||||
func (pl *packetBufferCircularList) StateTypeName() string {
|
||||
return "pkg/tcpip/link/qdisc/fifo.packetBufferCircularList"
|
||||
}
|
||||
|
||||
func (pl *packetBufferCircularList) StateFields() []string {
|
||||
return []string{
|
||||
"pbs",
|
||||
"head",
|
||||
"size",
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *packetBufferCircularList) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (pl *packetBufferCircularList) StateSave(stateSinkObject state.Sink) {
|
||||
pl.beforeSave()
|
||||
stateSinkObject.Save(0, &pl.pbs)
|
||||
stateSinkObject.Save(1, &pl.head)
|
||||
stateSinkObject.Save(2, &pl.size)
|
||||
}
|
||||
|
||||
func (pl *packetBufferCircularList) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (pl *packetBufferCircularList) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &pl.pbs)
|
||||
stateSourceObject.Load(1, &pl.head)
|
||||
stateSourceObject.Load(2, &pl.size)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*discipline)(nil))
|
||||
state.Register((*queueDispatcher)(nil))
|
||||
state.Register((*packetBufferCircularList)(nil))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,93 +0,0 @@
|
|||
// Copyright 2022 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 fifo
|
||||
|
||||
import "github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
|
||||
// packetBufferCircularList is a slice-backed circular list. All operations are
|
||||
// O(1) unless otherwise noted. It only allocates once, during the call to
|
||||
// init().
|
||||
//
|
||||
// Users should call init() before using packetBufferCircularList.
|
||||
//
|
||||
// +stateify savable
|
||||
type packetBufferCircularList struct {
|
||||
pbs []*stack.PacketBuffer
|
||||
head int
|
||||
size int
|
||||
}
|
||||
|
||||
// init initializes the list with the given size.
|
||||
func (pl *packetBufferCircularList) init(size int) {
|
||||
pl.pbs = make([]*stack.PacketBuffer, size)
|
||||
}
|
||||
|
||||
// length returns the number of elements in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) length() int {
|
||||
return pl.size
|
||||
}
|
||||
|
||||
// hasSpace returns whether there is space left in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) hasSpace() bool {
|
||||
return pl.size < len(pl.pbs)
|
||||
}
|
||||
|
||||
// isEmpty returns whether the list is empty.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) isEmpty() bool {
|
||||
return pl.size == 0
|
||||
}
|
||||
|
||||
// pushBack inserts the PacketBuffer at the end of the list.
|
||||
//
|
||||
// Users must check beforehand that there is space via a call to hasSpace().
|
||||
// Failing to do so may clobber existing entries.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) pushBack(pb *stack.PacketBuffer) {
|
||||
next := (pl.head + pl.size) % len(pl.pbs)
|
||||
pl.pbs[next] = pb
|
||||
pl.size++
|
||||
}
|
||||
|
||||
// removeFront returns the first element of the list or nil.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) removeFront() *stack.PacketBuffer {
|
||||
if pl.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
ret := pl.pbs[pl.head]
|
||||
pl.pbs[pl.head] = nil
|
||||
pl.head = (pl.head + 1) % len(pl.pbs)
|
||||
pl.size--
|
||||
return ret
|
||||
}
|
||||
|
||||
// decRef decreases the reference count on each stack.PacketBuffer stored in
|
||||
// the list.
|
||||
//
|
||||
// NOTE: runs in O(n) time.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *packetBufferCircularList) decRef() {
|
||||
for i := 0; i < pl.size; i++ {
|
||||
pl.pbs[(pl.head+i)%len(pl.pbs)].DecRef()
|
||||
}
|
||||
}
|
||||
108
pkg/tcpip/link/qdisc/packet_buffer_circular_list.go
Normal file
108
pkg/tcpip/link/qdisc/packet_buffer_circular_list.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2022 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 qdisc provides shared building blocks used by queueing disciplines.
|
||||
package qdisc
|
||||
|
||||
import "github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
|
||||
// PacketBufferCircularList is a slice-backed circular list. All operations are
|
||||
// O(1) unless otherwise noted. It only allocates once, during the call to
|
||||
// Init().
|
||||
//
|
||||
// Users should call Init() before using PacketBufferCircularList.
|
||||
//
|
||||
// +stateify savable
|
||||
type PacketBufferCircularList struct {
|
||||
pbs []*stack.PacketBuffer
|
||||
head int
|
||||
size int
|
||||
}
|
||||
|
||||
// Init initializes the list with the given size.
|
||||
func (pl *PacketBufferCircularList) Init(size int) {
|
||||
pl.pbs = make([]*stack.PacketBuffer, size)
|
||||
}
|
||||
|
||||
// Length returns the number of elements in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *PacketBufferCircularList) Length() int {
|
||||
return pl.size
|
||||
}
|
||||
|
||||
// HasSpace returns whether there is space left in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *PacketBufferCircularList) HasSpace() bool {
|
||||
return pl.size < len(pl.pbs)
|
||||
}
|
||||
|
||||
// IsEmpty returns whether the list is empty.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *PacketBufferCircularList) IsEmpty() bool {
|
||||
return pl.size == 0
|
||||
}
|
||||
|
||||
// PushBack inserts the PacketBuffer at the end of the list.
|
||||
//
|
||||
// Users must check beforehand that there is space via a call to HasSpace().
|
||||
// Failing to do so may clobber existing entries.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *PacketBufferCircularList) PushBack(pb *stack.PacketBuffer) {
|
||||
next := (pl.head + pl.size) % len(pl.pbs)
|
||||
pl.pbs[next] = pb
|
||||
pl.size++
|
||||
}
|
||||
|
||||
// PeekFront returns the first element of the list without removing it, or nil
|
||||
// if empty. The list retains its reference; the caller must not DecRef. To take
|
||||
// ownership, call RemoveFront, which returns the same pointer. The returned
|
||||
// pointer is only valid until the next mutation of the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *PacketBufferCircularList) PeekFront() *stack.PacketBuffer {
|
||||
if pl.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
return pl.pbs[pl.head]
|
||||
}
|
||||
|
||||
// RemoveFront returns the first element of the list or nil.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *PacketBufferCircularList) RemoveFront() *stack.PacketBuffer {
|
||||
if pl.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
ret := pl.pbs[pl.head]
|
||||
pl.pbs[pl.head] = nil
|
||||
pl.head = (pl.head + 1) % len(pl.pbs)
|
||||
pl.size--
|
||||
return ret
|
||||
}
|
||||
|
||||
// DecRef decreases the reference count on each stack.PacketBuffer stored in
|
||||
// the list.
|
||||
//
|
||||
// NOTE: runs in O(n) time.
|
||||
//
|
||||
//go:nosplit
|
||||
func (pl *PacketBufferCircularList) DecRef() {
|
||||
for i := 0; i < pl.size; i++ {
|
||||
pl.pbs[(pl.head+i)%len(pl.pbs)].DecRef()
|
||||
}
|
||||
}
|
||||
44
pkg/tcpip/link/qdisc/qdisc_state_autogen.go
Normal file
44
pkg/tcpip/link/qdisc/qdisc_state_autogen.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package qdisc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (pl *PacketBufferCircularList) StateTypeName() string {
|
||||
return "pkg/tcpip/link/qdisc.PacketBufferCircularList"
|
||||
}
|
||||
|
||||
func (pl *PacketBufferCircularList) StateFields() []string {
|
||||
return []string{
|
||||
"pbs",
|
||||
"head",
|
||||
"size",
|
||||
}
|
||||
}
|
||||
|
||||
func (pl *PacketBufferCircularList) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (pl *PacketBufferCircularList) StateSave(stateSinkObject state.Sink) {
|
||||
pl.beforeSave()
|
||||
stateSinkObject.Save(0, &pl.pbs)
|
||||
stateSinkObject.Save(1, &pl.head)
|
||||
stateSinkObject.Save(2, &pl.size)
|
||||
}
|
||||
|
||||
func (pl *PacketBufferCircularList) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (pl *PacketBufferCircularList) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &pl.pbs)
|
||||
stateSourceObject.Load(1, &pl.head)
|
||||
stateSourceObject.Load(2, &pl.size)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*PacketBufferCircularList)(nil))
|
||||
}
|
||||
64
pkg/tcpip/link/qdisc/tbf/queue_mutex.go
Normal file
64
pkg/tcpip/link/qdisc/tbf/queue_mutex.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package tbf
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/sync/locking"
|
||||
)
|
||||
|
||||
// Mutex is sync.Mutex with the correctness validator.
|
||||
type queueMutex struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var queueprefixIndex *locking.MutexClass
|
||||
|
||||
// lockNames is a list of user-friendly lock names.
|
||||
// Populated in init.
|
||||
var queuelockNames []string
|
||||
|
||||
// lockNameIndex is used as an index passed to NestedLock and NestedUnlock,
|
||||
// referring to an index within lockNames.
|
||||
// Values are specified using the "consts" field of go_template_instance.
|
||||
type queuelockNameIndex int
|
||||
|
||||
// DO NOT REMOVE: The following function automatically replaced with lock index constants.
|
||||
// LOCK_NAME_INDEX_CONSTANTS
|
||||
const ()
|
||||
|
||||
// Lock locks m.
|
||||
// +checklocksignore
|
||||
func (m *queueMutex) Lock() {
|
||||
locking.AddGLock(queueprefixIndex, -1)
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// NestedLock locks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *queueMutex) NestedLock(i queuelockNameIndex) {
|
||||
locking.AddGLock(queueprefixIndex, int(i))
|
||||
m.mu.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks m.
|
||||
// +checklocksignore
|
||||
func (m *queueMutex) Unlock() {
|
||||
locking.DelGLock(queueprefixIndex, -1)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// NestedUnlock unlocks m knowing that another lock of the same type is held.
|
||||
// +checklocksignore
|
||||
func (m *queueMutex) NestedUnlock(i queuelockNameIndex) {
|
||||
locking.DelGLock(queueprefixIndex, int(i))
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// DO NOT REMOVE: The following function is automatically replaced.
|
||||
func queueinitLockNames() {}
|
||||
|
||||
func init() {
|
||||
queueinitLockNames()
|
||||
queueprefixIndex = locking.NewMutexClass(reflect.TypeFor[queueMutex](), queuelockNames)
|
||||
}
|
||||
239
pkg/tcpip/link/qdisc/tbf/tbf.go
Normal file
239
pkg/tcpip/link/qdisc/tbf/tbf.go
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
// Copyright 2026 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 tbf provides a simplified Token Bucket Filter queueing discipline
|
||||
// modeled on Linux's net/sched/sch_tbf.c. Only the single-rate bucket is
|
||||
// implemented; peakrate/peakburst (Linux's second bucket) is not.
|
||||
package tbf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/sleep"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/qdisc"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
const (
|
||||
// BatchSize is the number of packets to write in each syscall. It is 47
|
||||
// because when GVisorGSO is in use then a single 65KB TCP segment can get
|
||||
// split into 46 segments of 1420 bytes and a single 216 byte segment.
|
||||
BatchSize = 47
|
||||
|
||||
qDiscClosed = 1
|
||||
)
|
||||
|
||||
var _ stack.QueueingDiscipline = (*discipline)(nil)
|
||||
|
||||
// +stateify savable
|
||||
type discipline struct {
|
||||
// Immutable configuration set by New.
|
||||
lower stack.LinkWriter
|
||||
clock tcpip.Clock `state:"nosave"`
|
||||
rate uint64 // max sustained throughput, bytes/sec
|
||||
burst uint32 // largest packet this TBF will pass, bytes
|
||||
buffer int64 // nanoseconds needed to transmit burst bytes at rate
|
||||
|
||||
// Shutdown state.
|
||||
wg sync.WaitGroup `state:"nosave"`
|
||||
closed atomicbitops.Int32
|
||||
|
||||
// Wakers driving dispatchLoop.
|
||||
newPacketWaker sleep.Waker `state:"nosave"`
|
||||
tokenWaker sleep.Waker `state:"nosave"`
|
||||
closeWaker sleep.Waker `state:"nosave"`
|
||||
|
||||
mu queueMutex `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
queue qdisc.PacketBufferCircularList
|
||||
|
||||
// Dispatcher state: mutated only inside dispatchLoop and
|
||||
// thus not protected by mu.
|
||||
tokens int64 // current bucket level, ns
|
||||
timeCheckpoint tcpip.MonotonicTime
|
||||
watchdog tcpip.Timer `state:"nosave"`
|
||||
}
|
||||
|
||||
// len2TimeNS returns the number of ns to transmit len bytes at rate bytes/sec.
|
||||
// Linux's psched_l2t_ns avoids the divide via a precomputed mult/shift; see
|
||||
// psched_ratecfg_precompute__ in net/sched/sch_generic.c.
|
||||
func len2TimeNS(rate uint64, len uint32) uint64 {
|
||||
const nsecPerSec = 1000000000
|
||||
return uint64(len) * nsecPerSec / rate
|
||||
}
|
||||
|
||||
func (d *discipline) dispatchLoop() {
|
||||
s := sleep.Sleeper{}
|
||||
s.AddWaker(&d.newPacketWaker)
|
||||
s.AddWaker(&d.tokenWaker)
|
||||
s.AddWaker(&d.closeWaker)
|
||||
defer s.Done()
|
||||
|
||||
var batch stack.PacketBufferList
|
||||
for {
|
||||
switch w := s.Fetch(true); w {
|
||||
case &d.newPacketWaker, &d.tokenWaker:
|
||||
case &d.closeWaker:
|
||||
if d.watchdog != nil {
|
||||
d.watchdog.Stop()
|
||||
}
|
||||
d.mu.Lock()
|
||||
for p := d.queue.RemoveFront(); p != nil; p = d.queue.RemoveFront() {
|
||||
p.DecRef()
|
||||
}
|
||||
d.queue.DecRef()
|
||||
d.mu.Unlock()
|
||||
return
|
||||
default:
|
||||
panic("unknown waker")
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
for pkt := d.queue.PeekFront(); pkt != nil; pkt = d.queue.PeekFront() {
|
||||
pktLen := pkt.Size()
|
||||
now := d.clock.NowMonotonic()
|
||||
toks := min(now.Sub(d.timeCheckpoint).Nanoseconds(), d.buffer)
|
||||
toks += d.tokens
|
||||
if toks > d.buffer {
|
||||
toks = d.buffer
|
||||
}
|
||||
toks -= int64(len2TimeNS(d.rate, uint32(pktLen)))
|
||||
sufficientTokens := toks >= 0
|
||||
if !sufficientTokens {
|
||||
// -toks is the deficit in ns: how long until enough tokens accumulate.
|
||||
if d.watchdog != nil {
|
||||
d.watchdog.Stop()
|
||||
}
|
||||
d.watchdog = d.clock.AfterFunc(time.Duration(-toks), d.tokenWaker.Assert)
|
||||
break
|
||||
}
|
||||
d.queue.RemoveFront()
|
||||
d.timeCheckpoint = now
|
||||
d.tokens = toks
|
||||
batch.PushBack(pkt)
|
||||
|
||||
possiblyAnotherPacket := batch.Len() < BatchSize && !d.queue.IsEmpty()
|
||||
if possiblyAnotherPacket {
|
||||
continue
|
||||
}
|
||||
d.mu.Unlock()
|
||||
_, _ = d.lower.WritePackets(batch)
|
||||
batch.Reset()
|
||||
d.mu.Lock()
|
||||
}
|
||||
if batch.Len() > 0 {
|
||||
d.mu.Unlock()
|
||||
_, _ = d.lower.WritePackets(batch)
|
||||
batch.Reset()
|
||||
d.mu.Lock()
|
||||
}
|
||||
d.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new TBF queueing discipline that will rate-limit lower to
|
||||
// rate bytes/sec with bursts of up to burst bytes, queueing up to queueLen
|
||||
// packets of backlog before dropping. Note that queueLen counts packets,
|
||||
// not bytes as in Linux's sch_tbf.c, for consistency with the fifo qdisc.
|
||||
//
|
||||
// +checklocksignore: we don't have to hold locks during initialization.
|
||||
func New(lower stack.LinkEndpoint, clock tcpip.Clock, rate uint64, burst, queueLen uint32) (stack.QueueingDiscipline, error) {
|
||||
if rate == 0 {
|
||||
return nil, fmt.Errorf("qdisc=tbf requires setting qdisc-tbf-rate")
|
||||
}
|
||||
|
||||
if burst == 0 {
|
||||
return nil, fmt.Errorf("qdisc=tbf requires setting qdisc-tbf-burst")
|
||||
}
|
||||
|
||||
if gsoEP, ok := lower.(stack.GSOEndpoint); ok {
|
||||
// HostGSOSupported endpoints can hand WritePacket a single GSO
|
||||
// super-packet up to GSOMaxSize+MaxHeaderLength bytes, so the bucket
|
||||
// must be able to hold one. GVisorGSOSupported segments above the
|
||||
// qdisc and GSONotSupported never produces packets above the link
|
||||
// MTU, both covered by the next check.
|
||||
maxGSOPktLen := gsoEP.GSOMaxSize() + uint32(lower.MaxHeaderLength())
|
||||
if gsoEP.SupportedGSO() == stack.HostGSOSupported && burst < uint32(maxGSOPktLen) {
|
||||
return nil, fmt.Errorf("burst (%d bytes) is smaller than link's max GSO packet size (%d bytes); either increase burst or disable host GSO via --gso=false", burst, maxGSOPktLen)
|
||||
}
|
||||
}
|
||||
|
||||
maxPktLen := lower.MTU() + uint32(lower.MaxHeaderLength())
|
||||
if burst < maxPktLen {
|
||||
return nil, fmt.Errorf("burst (%d bytes) is smaller than max packet length (%d bytes)", burst, maxPktLen)
|
||||
}
|
||||
|
||||
buffer := int64(len2TimeNS(rate, burst))
|
||||
if buffer == 0 {
|
||||
return nil, fmt.Errorf("rate (%d bytes/sec) is too high relative to burst (%d bytes); reduce qdisc-tbf-rate or increase qdisc-tbf-burst", rate, burst)
|
||||
}
|
||||
|
||||
d := &discipline{
|
||||
lower: lower,
|
||||
clock: clock,
|
||||
rate: rate,
|
||||
burst: burst,
|
||||
buffer: buffer,
|
||||
tokens: buffer,
|
||||
timeCheckpoint: clock.NowMonotonic(),
|
||||
}
|
||||
d.queue.Init(int(queueLen))
|
||||
d.wg.Add(1)
|
||||
go func() {
|
||||
defer d.wg.Done()
|
||||
d.dispatchLoop()
|
||||
}()
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// WritePacket implements stack.QueueingDiscipline.WritePacket.
|
||||
func (d *discipline) WritePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
||||
if d.closed.Load() == qDiscClosed {
|
||||
return &tcpip.ErrClosedForSend{}
|
||||
}
|
||||
|
||||
if uint32(pkt.Size()) > d.burst {
|
||||
// if the burst parameter is not smaller than the expected packet size,
|
||||
// oversize packets should be impossible with New's GSO check
|
||||
return &tcpip.ErrMessageTooLong{}
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
if d.closed.Load() == qDiscClosed {
|
||||
d.mu.Unlock()
|
||||
return &tcpip.ErrClosedForSend{}
|
||||
}
|
||||
haveSpace := d.queue.HasSpace()
|
||||
if haveSpace {
|
||||
d.queue.PushBack(pkt.IncRef())
|
||||
}
|
||||
d.mu.Unlock()
|
||||
if !haveSpace {
|
||||
return &tcpip.ErrNoBufferSpace{}
|
||||
}
|
||||
|
||||
d.newPacketWaker.Assert()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements stack.QueueingDiscipline.Close.
|
||||
func (d *discipline) Close() {
|
||||
d.closed.Store(qDiscClosed)
|
||||
d.closeWaker.Assert()
|
||||
d.wg.Wait()
|
||||
}
|
||||
59
pkg/tcpip/link/qdisc/tbf/tbf_state_autogen.go
Normal file
59
pkg/tcpip/link/qdisc/tbf/tbf_state_autogen.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package tbf
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (d *discipline) StateTypeName() string {
|
||||
return "pkg/tcpip/link/qdisc/tbf.discipline"
|
||||
}
|
||||
|
||||
func (d *discipline) StateFields() []string {
|
||||
return []string{
|
||||
"lower",
|
||||
"rate",
|
||||
"burst",
|
||||
"buffer",
|
||||
"closed",
|
||||
"queue",
|
||||
"tokens",
|
||||
"timeCheckpoint",
|
||||
}
|
||||
}
|
||||
|
||||
func (d *discipline) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (d *discipline) StateSave(stateSinkObject state.Sink) {
|
||||
d.beforeSave()
|
||||
stateSinkObject.Save(0, &d.lower)
|
||||
stateSinkObject.Save(1, &d.rate)
|
||||
stateSinkObject.Save(2, &d.burst)
|
||||
stateSinkObject.Save(3, &d.buffer)
|
||||
stateSinkObject.Save(4, &d.closed)
|
||||
stateSinkObject.Save(5, &d.queue)
|
||||
stateSinkObject.Save(6, &d.tokens)
|
||||
stateSinkObject.Save(7, &d.timeCheckpoint)
|
||||
}
|
||||
|
||||
func (d *discipline) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (d *discipline) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &d.lower)
|
||||
stateSourceObject.Load(1, &d.rate)
|
||||
stateSourceObject.Load(2, &d.burst)
|
||||
stateSourceObject.Load(3, &d.buffer)
|
||||
stateSourceObject.Load(4, &d.closed)
|
||||
stateSourceObject.Load(5, &d.queue)
|
||||
stateSourceObject.Load(6, &d.tokens)
|
||||
stateSourceObject.Load(7, &d.timeCheckpoint)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*discipline)(nil))
|
||||
}
|
||||
|
|
@ -92,5 +92,5 @@ func endpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointRWMutex](), endpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func serverEndpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
serverEndpointinitLockNames()
|
||||
serverEndpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(serverEndpointRWMutex{}), serverEndpointlockNames)
|
||||
serverEndpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[serverEndpointRWMutex](), serverEndpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ package sharedmem
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
|
|
@ -48,12 +47,7 @@ func getBuffer(fd int) ([]byte, error) {
|
|||
return nil, fmt.Errorf("failed to map memory for buffer fd: %d, error: %s", fd, err)
|
||||
}
|
||||
|
||||
// Use unsafe to convert addr into a []byte.
|
||||
var b []byte
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
hdr.Data = addr
|
||||
hdr.Len = int(s.Size)
|
||||
hdr.Cap = int(s.Size)
|
||||
b := unsafe.Slice((*byte)(unsafe.Pointer(addr)), int(s.Size))
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -353,8 +353,13 @@ func LogPacket(prefix string, dir Direction, protocol tcpip.NetworkProtocolNumbe
|
|||
details += fmt.Sprintf("invalid packet: tcp data offset too small %d", offset)
|
||||
break
|
||||
}
|
||||
if size := clone.Data().Size() + len(tcp); offset > size && !moreFragments {
|
||||
details += fmt.Sprintf("invalid packet: tcp data offset %d larger than tcp packet length %d", offset, size)
|
||||
|
||||
if size := clone.Data().Size() + len(tcp); offset > size {
|
||||
if !moreFragments {
|
||||
details += fmt.Sprintf("invalid packet: tcp data offset %d larger than tcp packet length %d", offset, size)
|
||||
} else {
|
||||
details += fmt.Sprintf("truncated options (tcp data offset %d, tcp packet length %d)", offset, size)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ package tun
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/context"
|
||||
"github.com/sagernet/gvisor/pkg/errors/linuxerr"
|
||||
|
|
@ -258,10 +257,15 @@ func (d *Device) Write(data *buffer.View) (int64, error) {
|
|||
case d.flags.TUN:
|
||||
// TUN interface with IFF_NO_PI enabled, thus
|
||||
// we need to determine protocol from version field
|
||||
if data.Size() == 0 {
|
||||
// Ignore bad packet.
|
||||
return dataLen, nil
|
||||
}
|
||||
version := data.AsSlice()[0] >> 4
|
||||
if version == 4 {
|
||||
switch version {
|
||||
case 4:
|
||||
protocol = header.IPv4ProtocolNumber
|
||||
} else if version == 6 {
|
||||
case 6:
|
||||
protocol = header.IPv6ProtocolNumber
|
||||
}
|
||||
}
|
||||
|
|
@ -362,22 +366,26 @@ type tunEndpoint struct {
|
|||
tunEndpointRefs
|
||||
*channel.Endpoint
|
||||
|
||||
stack *stack.Stack
|
||||
nicID tcpip.NICID
|
||||
name string
|
||||
isTap bool
|
||||
persistent atomicbitops.Bool
|
||||
closed atomicbitops.Bool
|
||||
stack *stack.Stack
|
||||
nicID tcpip.NICID
|
||||
name string
|
||||
isTap bool
|
||||
|
||||
mu endpointMutex `state:"nosave"`
|
||||
onCloseAction func() `state:"nosave"`
|
||||
persistent bool
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (e *tunEndpoint) setPersistent(v bool) {
|
||||
old := e.persistent.Swap(v)
|
||||
if old == v {
|
||||
e.mu.Lock()
|
||||
if e.persistent == v || e.closed {
|
||||
e.mu.Unlock()
|
||||
return
|
||||
}
|
||||
e.persistent = v
|
||||
e.mu.Unlock()
|
||||
// Update refs without holding the lock.
|
||||
if v {
|
||||
e.IncRef()
|
||||
} else {
|
||||
|
|
@ -386,17 +394,19 @@ func (e *tunEndpoint) setPersistent(v bool) {
|
|||
}
|
||||
|
||||
func (e *tunEndpoint) Close() {
|
||||
if e.closed.Swap(true) {
|
||||
e.mu.Lock()
|
||||
if e.closed {
|
||||
e.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
if e.persistent.Load() {
|
||||
e.DecRef(context.Background())
|
||||
}
|
||||
e.mu.Lock()
|
||||
e.closed = true
|
||||
decref := e.persistent
|
||||
action := e.onCloseAction
|
||||
e.onCloseAction = nil
|
||||
e.mu.Unlock()
|
||||
if decref {
|
||||
e.DecRef(context.Background())
|
||||
}
|
||||
if action != nil {
|
||||
action()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func deviceinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
deviceinitLockNames()
|
||||
deviceprefixIndex = locking.NewMutexClass(reflect.TypeOf(deviceRWMutex{}), devicelockNames)
|
||||
deviceprefixIndex = locking.NewMutexClass(reflect.TypeFor[deviceRWMutex](), devicelockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,5 +60,5 @@ func endpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointMutex{}), endpointlockNames)
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointMutex](), endpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func endpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointRWMutex](), endpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,8 +163,7 @@ func (e *Endpoint) SetMTU(mtu uint32) {
|
|||
|
||||
// Capabilities implements stack.LinkEndpoint.Capabilities.
|
||||
func (e *Endpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
// TODO(b/352384218): Enable CapabilityTXChecksumOffload.
|
||||
return stack.CapabilityRXChecksumOffload | stack.CapabilitySaveRestore
|
||||
return stack.CapabilityRXChecksumOffload | stack.CapabilitySaveRestore | stack.CapabilityTXChecksumOffload
|
||||
}
|
||||
|
||||
// GSOMaxSize implements stack.GSOEndpoint.
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func vethinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
vethinitLockNames()
|
||||
vethprefixIndex = locking.NewMutexClass(reflect.TypeOf(vethRWMutex{}), vethlockNames)
|
||||
vethprefixIndex = locking.NewMutexClass(reflect.TypeFor[vethRWMutex](), vethlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func endpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointRWMutex](), endpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,10 +88,6 @@ type Options struct {
|
|||
// include CapabilitySaveRestore
|
||||
SaveRestore bool
|
||||
|
||||
// DisconnectOk if true, indicates that this NIC capability set should
|
||||
// include CapabilityDisconnectOk.
|
||||
DisconnectOk bool
|
||||
|
||||
// TXChecksumOffload if true, indicates that this endpoints capability
|
||||
// set should include CapabilityTXChecksumOffload.
|
||||
TXChecksumOffload bool
|
||||
|
|
@ -109,6 +105,9 @@ type Options struct {
|
|||
|
||||
// GRO enables generic receive offload.
|
||||
GRO bool
|
||||
|
||||
// QueueID is the ID of the RX queue to which the AF_XDP socket is attached.
|
||||
QueueID uint32
|
||||
}
|
||||
|
||||
// New creates a new endpoint from an AF_XDP socket.
|
||||
|
|
@ -126,10 +125,6 @@ func New(opts *Options) (stack.LinkEndpoint, error) {
|
|||
caps |= stack.CapabilitySaveRestore
|
||||
}
|
||||
|
||||
if opts.DisconnectOk {
|
||||
caps |= stack.CapabilityDisconnectOk
|
||||
}
|
||||
|
||||
if err := unix.SetNonblock(opts.FD, true); err != nil {
|
||||
return nil, fmt.Errorf("unix.SetNonblock(%v) failed: %v", opts.FD, err)
|
||||
}
|
||||
|
|
@ -164,7 +159,7 @@ func New(opts *Options) (stack.LinkEndpoint, error) {
|
|||
NDescriptors: nFrames / 2,
|
||||
Bind: opts.Bind,
|
||||
}
|
||||
ep.control, err = xdp.NewFromSocket(opts.FD, uint32(opts.InterfaceIndex), 0 /* queueID */, xdpOpts)
|
||||
ep.control, err = xdp.NewFromSocket(opts.FD, uint32(opts.InterfaceIndex), opts.QueueID, xdpOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create AF_XDP dispatcher: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func endpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointinitLockNames()
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointRWMutex{}), endpointlockNames)
|
||||
endpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointRWMutex](), endpointlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,11 @@ func (e *endpoint) MTU() uint32 {
|
|||
return lmtu - uint32(e.MaxHeaderLength())
|
||||
}
|
||||
|
||||
// EndpointHeaderSize returns the size necessary for the ARP header.
|
||||
func (e *endpoint) EndpointHeaderSize() uint32 {
|
||||
return header.ARPSize
|
||||
}
|
||||
|
||||
func (e *endpoint) MaxHeaderLength() uint16 {
|
||||
return e.nic.MaxHeaderLength() + header.ARPSize
|
||||
}
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ type PacketFragmenter struct {
|
|||
fragmentCount int
|
||||
currentFragment int
|
||||
fragmentOffset int
|
||||
mark uint32
|
||||
}
|
||||
|
||||
// MakePacketFragmenter prepares the struct needed for packet fragmentation.
|
||||
|
|
@ -332,6 +333,7 @@ func MakePacketFragmenter(pkt *stack.PacketBuffer, fragmentPayloadLen uint32, re
|
|||
reserve: reserve,
|
||||
fragmentPayloadLen: int(fragmentPayloadLen),
|
||||
fragmentCount: int(fragmentCount),
|
||||
mark: pkt.Mark,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -351,6 +353,7 @@ func (pf *PacketFragmenter) BuildNextFragment() (*stack.PacketBuffer, int, int,
|
|||
|
||||
fragPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: pf.reserve,
|
||||
Mark: pf.mark,
|
||||
})
|
||||
|
||||
// Copy data for the fragment.
|
||||
|
|
|
|||
|
|
@ -103,8 +103,21 @@ func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *s
|
|||
}
|
||||
|
||||
holeFound = true
|
||||
// IPv6: rfc8200#section-4.5
|
||||
// Changed the text to require that IPv6 nodes must not create
|
||||
// overlapping fragments. Also, when reassembling an IPv6
|
||||
// datagram, if one or more its constituent fragments is
|
||||
// determined to be an overlapping fragment, the entire datagram
|
||||
// (and any constituent fragments) must be silently discarded.
|
||||
// Includes a clarification that no ICMP error message should be
|
||||
// sent if overlapping fragments are received.
|
||||
if currentHole.filled {
|
||||
// Incoming fragment is a subset of an existing fragment.
|
||||
if first != currentHole.first || last != currentHole.last {
|
||||
return nil, 0, false, 0, ErrFragmentOverlap
|
||||
}
|
||||
// Incoming fragment is a duplicate.
|
||||
// Not dropping packet incase of duplicates.
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ type dadState struct {
|
|||
extendRequest extendRequest
|
||||
|
||||
done *bool
|
||||
timer tcpip.Timer
|
||||
timer tcpip.Timer `state:"nosave"`
|
||||
|
||||
completionHandlers []stack.DADCompletionHandler
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,13 +291,13 @@ type GenericMulticastProtocolState struct {
|
|||
robustnessVariable uint8
|
||||
queryInterval time.Duration
|
||||
mode protocolMode
|
||||
modeTimer tcpip.Timer
|
||||
modeTimer tcpip.Timer `state:"nosave"`
|
||||
|
||||
generalQueryV2Timer tcpip.Timer
|
||||
generalQueryV2Timer tcpip.Timer `state:"nosave"`
|
||||
// TODO(b/341946753): Restore when netstack is savable.
|
||||
generalQueryV2TimerFiresAt time.Time `state:"nosave"`
|
||||
|
||||
stateChangedReportV2Timer tcpip.Timer
|
||||
stateChangedReportV2Timer tcpip.Timer `state:"nosave"`
|
||||
stateChangedReportV2TimerSet bool
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ func (d *dadState) StateFields() []string {
|
|||
"nonce",
|
||||
"extendRequest",
|
||||
"done",
|
||||
"timer",
|
||||
"completionHandlers",
|
||||
}
|
||||
}
|
||||
|
|
@ -30,8 +29,7 @@ func (d *dadState) StateSave(stateSinkObject state.Sink) {
|
|||
stateSinkObject.Save(0, &d.nonce)
|
||||
stateSinkObject.Save(1, &d.extendRequest)
|
||||
stateSinkObject.Save(2, &d.done)
|
||||
stateSinkObject.Save(3, &d.timer)
|
||||
stateSinkObject.Save(4, &d.completionHandlers)
|
||||
stateSinkObject.Save(3, &d.completionHandlers)
|
||||
}
|
||||
|
||||
func (d *dadState) afterLoad(context.Context) {}
|
||||
|
|
@ -41,8 +39,7 @@ func (d *dadState) StateLoad(ctx context.Context, stateSourceObject state.Source
|
|||
stateSourceObject.Load(0, &d.nonce)
|
||||
stateSourceObject.Load(1, &d.extendRequest)
|
||||
stateSourceObject.Load(2, &d.done)
|
||||
stateSourceObject.Load(3, &d.timer)
|
||||
stateSourceObject.Load(4, &d.completionHandlers)
|
||||
stateSourceObject.Load(3, &d.completionHandlers)
|
||||
}
|
||||
|
||||
func (d *DADOptions) StateTypeName() string {
|
||||
|
|
@ -237,9 +234,6 @@ func (g *GenericMulticastProtocolState) StateFields() []string {
|
|||
"robustnessVariable",
|
||||
"queryInterval",
|
||||
"mode",
|
||||
"modeTimer",
|
||||
"generalQueryV2Timer",
|
||||
"stateChangedReportV2Timer",
|
||||
"stateChangedReportV2TimerSet",
|
||||
}
|
||||
}
|
||||
|
|
@ -254,10 +248,7 @@ func (g *GenericMulticastProtocolState) StateSave(stateSinkObject state.Sink) {
|
|||
stateSinkObject.Save(2, &g.robustnessVariable)
|
||||
stateSinkObject.Save(3, &g.queryInterval)
|
||||
stateSinkObject.Save(4, &g.mode)
|
||||
stateSinkObject.Save(5, &g.modeTimer)
|
||||
stateSinkObject.Save(6, &g.generalQueryV2Timer)
|
||||
stateSinkObject.Save(7, &g.stateChangedReportV2Timer)
|
||||
stateSinkObject.Save(8, &g.stateChangedReportV2TimerSet)
|
||||
stateSinkObject.Save(5, &g.stateChangedReportV2TimerSet)
|
||||
}
|
||||
|
||||
func (g *GenericMulticastProtocolState) afterLoad(context.Context) {}
|
||||
|
|
@ -269,10 +260,7 @@ func (g *GenericMulticastProtocolState) StateLoad(ctx context.Context, stateSour
|
|||
stateSourceObject.Load(2, &g.robustnessVariable)
|
||||
stateSourceObject.Load(3, &g.queryInterval)
|
||||
stateSourceObject.Load(4, &g.mode)
|
||||
stateSourceObject.Load(5, &g.modeTimer)
|
||||
stateSourceObject.Load(6, &g.generalQueryV2Timer)
|
||||
stateSourceObject.Load(7, &g.stateChangedReportV2Timer)
|
||||
stateSourceObject.Load(8, &g.stateChangedReportV2TimerSet)
|
||||
stateSourceObject.Load(5, &g.stateChangedReportV2TimerSet)
|
||||
}
|
||||
|
||||
func (m *MultiCounterIPForwardingStats) StateTypeName() string {
|
||||
|
|
|
|||
293
pkg/tcpip/network/internal/ip/reject_with_reset.go
Normal file
293
pkg/tcpip/network/internal/ip/reject_with_reset.go
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
// Copyright 2026 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 ip
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// ipv6FragmentOffset returns the fragment offset of the IPv6 packet
|
||||
// if present.
|
||||
func ipv6FragmentOffset(pkt *stack.PacketBuffer, ipHdr header.IPv6) (uint16, bool) {
|
||||
if !header.IsExtensionHeader(ipHdr.NextHeader()) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
netHeaderSlice := pkt.NetworkHeader().Slice()
|
||||
if len(netHeaderSlice) <= header.IPv6MinimumSize {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Make an iterator to walk the extension headers.
|
||||
buf := buffer.MakeWithData(netHeaderSlice[header.IPv6MinimumSize:])
|
||||
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(ipHdr.NextHeader()), buf)
|
||||
defer it.Release()
|
||||
|
||||
for {
|
||||
extHdr, done, err := it.Next()
|
||||
if err != nil || done {
|
||||
break
|
||||
}
|
||||
switch extHdr := extHdr.(type) {
|
||||
case header.IPv6FragmentExtHdr:
|
||||
offset := extHdr.FragmentOffset()
|
||||
extHdr.Release()
|
||||
return offset, true
|
||||
default:
|
||||
extHdr.Release()
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// buildResetPayloadV4 builds an IPv4 + TCP Reset packet in a buffer.
|
||||
func buildResetPayloadV4(ttl uint8, src, dst tcpip.Address, tcpHdr header.TCP, seq, ack uint32, flags header.TCPFlags) *buffer.View {
|
||||
totalHdrLen := header.IPv4MinimumSize + header.TCPMinimumSize
|
||||
v := buffer.NewViewSize(totalHdrLen)
|
||||
buf := v.AsSlice()
|
||||
|
||||
rstIPHdr := header.IPv4(buf[:header.IPv4MinimumSize])
|
||||
rstIPHdr.Encode(&header.IPv4Fields{
|
||||
TotalLength: uint16(totalHdrLen),
|
||||
TTL: ttl,
|
||||
Protocol: uint8(header.TCPProtocolNumber),
|
||||
TOS: stack.DefaultTOS,
|
||||
Flags: header.IPv4FlagDontFragment,
|
||||
// Flip source and destination addresses.
|
||||
SrcAddr: dst,
|
||||
DstAddr: src,
|
||||
})
|
||||
|
||||
rstTCPHdr := header.TCP(buf[header.IPv4MinimumSize:])
|
||||
rstTCPHdr.Encode(&header.TCPFields{
|
||||
SrcPort: tcpHdr.DestinationPort(),
|
||||
DstPort: tcpHdr.SourcePort(),
|
||||
SeqNum: seq,
|
||||
AckNum: ack,
|
||||
DataOffset: header.TCPMinimumSize,
|
||||
Flags: flags,
|
||||
})
|
||||
|
||||
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, dst, src, header.TCPMinimumSize)
|
||||
rstTCPHdr.SetChecksum(0)
|
||||
rstTCPHdr.SetChecksum(^rstTCPHdr.CalculateChecksum(xsum))
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// buildResetPayloadV6 builds an IPv6 + TCP Reset packet in a buffer.
|
||||
func buildResetPayloadV6(hopLimit uint8, src, dst tcpip.Address, tcpHdr header.TCP, seq, ack uint32, flags header.TCPFlags) *buffer.View {
|
||||
totalHdrLen := header.IPv6MinimumSize + header.TCPMinimumSize
|
||||
v := buffer.NewViewSize(totalHdrLen)
|
||||
buf := v.AsSlice()
|
||||
|
||||
rstIPHdr := header.IPv6(buf[:header.IPv6MinimumSize])
|
||||
rstIPHdr.Encode(&header.IPv6Fields{
|
||||
PayloadLength: uint16(header.TCPMinimumSize),
|
||||
TransportProtocol: header.TCPProtocolNumber,
|
||||
HopLimit: hopLimit,
|
||||
// Flip source and destination addresses.
|
||||
SrcAddr: dst,
|
||||
DstAddr: src,
|
||||
})
|
||||
|
||||
rstTCPHdr := header.TCP(buf[header.IPv6MinimumSize:])
|
||||
rstTCPHdr.Encode(&header.TCPFields{
|
||||
SrcPort: tcpHdr.DestinationPort(),
|
||||
DstPort: tcpHdr.SourcePort(),
|
||||
SeqNum: seq,
|
||||
AckNum: ack,
|
||||
DataOffset: header.TCPMinimumSize,
|
||||
Flags: flags,
|
||||
})
|
||||
|
||||
// Compute TCP checksum.
|
||||
xsum := header.PseudoHeaderChecksum(header.TCPProtocolNumber, dst, src, header.TCPMinimumSize)
|
||||
rstTCPHdr.SetChecksum(0)
|
||||
rstTCPHdr.SetChecksum(^rstTCPHdr.CalculateChecksum(xsum))
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// RejectWithTCPReset sends a TCP reset in response to the packet.
|
||||
//
|
||||
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_send_reset[6]()
|
||||
func RejectWithTCPReset(pkt *stack.PacketBuffer, netProto tcpip.NetworkProtocolNumber, stk *stack.Stack, deliveredLocally bool) tcpip.Error {
|
||||
var src, dst tcpip.Address
|
||||
var ttl uint8
|
||||
isFragment := false
|
||||
|
||||
switch netProto {
|
||||
case header.IPv4ProtocolNumber:
|
||||
// Ref: net/ipv4/netfilter/nf_reject_ipv4.c:nf_reject_ip_tcphdr_get
|
||||
ipHdr := header.IPv4(pkt.NetworkHeader().Slice())
|
||||
if len(ipHdr) < header.IPv4MinimumSize {
|
||||
return nil
|
||||
}
|
||||
if ipHdr.Protocol() != uint8(header.TCPProtocolNumber) {
|
||||
return nil
|
||||
}
|
||||
if ipHdr.FragmentOffset() != 0 {
|
||||
return nil
|
||||
}
|
||||
isFragment = ipHdr.More()
|
||||
src = ipHdr.SourceAddress()
|
||||
dst = ipHdr.DestinationAddress()
|
||||
|
||||
// Ref: net/ipv4/netfilter/nf_reject_ipv4.c:nf_send_reset
|
||||
if header.IsV4MulticastAddress(dst) ||
|
||||
header.IsV4MulticastAddress(src) ||
|
||||
pkt.NetworkPacketInfo.LocalAddressBroadcast ||
|
||||
pkt.PktType == tcpip.PacketBroadcast || pkt.PktType == tcpip.PacketMulticast ||
|
||||
src == header.IPv4Any || dst == header.IPv4Any {
|
||||
return nil
|
||||
}
|
||||
|
||||
case header.IPv6ProtocolNumber:
|
||||
// Ref: net/ipv6/netfilter/nf_reject_ipv6.c:nf_reject_ip6_tcphdr_get
|
||||
ipHdr := header.IPv6(pkt.NetworkHeader().Slice())
|
||||
if len(ipHdr) < header.IPv6MinimumSize {
|
||||
return nil
|
||||
}
|
||||
fragOffset, ok := ipv6FragmentOffset(pkt, ipHdr)
|
||||
if ok && fragOffset != 0 {
|
||||
return nil
|
||||
}
|
||||
isFragment = ok
|
||||
src = ipHdr.SourceAddress()
|
||||
dst = ipHdr.DestinationAddress()
|
||||
|
||||
// Ref: net/ipv6/netfilter/nf_reject_ipv6.c:nf_send_reset6
|
||||
if header.IsV6MulticastAddress(src) || header.IsV6MulticastAddress(dst) ||
|
||||
header.IsV4MappedAddress(src) || header.IsV4MappedAddress(dst) ||
|
||||
src == header.IPv6Any || dst == header.IPv6Any ||
|
||||
pkt.PktType == tcpip.PacketBroadcast || pkt.PktType == tcpip.PacketMulticast {
|
||||
return nil
|
||||
}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
tcpHdr := func(pkt *stack.PacketBuffer) header.TCP {
|
||||
// If 0 < len(transportHdr) < header.TCPMinimumSize, then the TCP header is invalid.
|
||||
// Assuming a TCP packet,
|
||||
// if the TCP header was parsed, the
|
||||
// len should be >= header.TCPMinimumSize;
|
||||
// else the TCP header was not parsed and the len should be 0.
|
||||
transportHdr := pkt.TransportHeader().Slice()
|
||||
if len(transportHdr) >= header.TCPMinimumSize {
|
||||
return header.TCP(transportHdr)
|
||||
}
|
||||
if len(transportHdr) != 0 {
|
||||
return nil
|
||||
}
|
||||
// In the case of fragmented TCP packets, the TCP header may not be parsed.
|
||||
// Pull up the TCP header from the payload.
|
||||
b, ok := pkt.Data().PullUp(header.TCPMinimumSize)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
hdr := header.TCP(b)
|
||||
hdrLen := int(hdr.DataOffset())
|
||||
if hdrLen < header.TCPMinimumSize || pkt.Data().Size() < hdrLen {
|
||||
return nil
|
||||
}
|
||||
tcpHdr, ok := pkt.Data().Consume(hdrLen)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
pkt.TransportProtocolNumber = header.TCPProtocolNumber
|
||||
return header.TCP(tcpHdr)
|
||||
}(pkt)
|
||||
if tcpHdr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_reject_ip[6]_tcphdr_get()
|
||||
// No RST for RST as this will cause a loop.
|
||||
if tcpHdr.Flags().Contains(header.TCPFlagRst) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check checksum integrity only for non-fragmented packets.
|
||||
// We don't support refragmentation(nf_defrag) before REJECT,
|
||||
// so we can't validate the checksum for fragmented packets.
|
||||
if !isFragment {
|
||||
// Check checksum integrity.
|
||||
if !pkt.RXChecksumValidated && !tcpHdr.IsChecksumValid(src, dst, pkt.Data().Checksum(), uint16(pkt.Data().Size())) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
localAddr := dst
|
||||
if !deliveredLocally {
|
||||
// If the packet wasn't delivered locally, do not use the packet's destination
|
||||
// address as the response's source address as we should not own the
|
||||
// destination address.
|
||||
localAddr = tcpip.Address{}
|
||||
}
|
||||
|
||||
route, err := stk.FindRoute(0 /*nicID*/, localAddr, src, netProto, false /* multicastLoop */)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer route.Release()
|
||||
ttl = route.DefaultTTL()
|
||||
|
||||
var seq uint32
|
||||
var ack uint32
|
||||
payloadLen := uint32(pkt.Data().Size())
|
||||
flags := header.TCPFlagRst
|
||||
|
||||
// Ref: net/ipv[4|6]/netfilter/nf_reject_ipv[4|6].c:nf_reject_ip[6]_tcphdr_put()
|
||||
if tcpHdr.Flags()&header.TCPFlagAck != 0 {
|
||||
seq = tcpHdr.AckNumber()
|
||||
} else {
|
||||
flags |= header.TCPFlagAck
|
||||
ack = tcpHdr.SequenceNumber() + payloadLen
|
||||
if tcpHdr.Flags()&header.TCPFlagSyn != 0 {
|
||||
ack++
|
||||
}
|
||||
if tcpHdr.Flags()&header.TCPFlagFin != 0 {
|
||||
ack++
|
||||
}
|
||||
}
|
||||
|
||||
var v *buffer.View
|
||||
if netProto == header.IPv4ProtocolNumber {
|
||||
v = buildResetPayloadV4(ttl, src, dst, tcpHdr, seq, ack, flags)
|
||||
} else {
|
||||
v = buildResetPayloadV6(ttl, src, dst, tcpHdr, seq, ack, flags)
|
||||
}
|
||||
|
||||
rstPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: int(route.MaxHeaderLength()),
|
||||
Payload: buffer.MakeWithView(v),
|
||||
})
|
||||
rstPkt.TransportProtocolNumber = header.TCPProtocolNumber
|
||||
defer rstPkt.DecRef()
|
||||
|
||||
// TODO: b/521536712 - Add support for mark propagation.
|
||||
if err := route.WriteHeaderIncludedPacket(rstPkt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ func (r *RouteTable) StateFields() []string {
|
|||
return []string{
|
||||
"installedRoutes",
|
||||
"pendingRoutes",
|
||||
"cleanupPendingRoutesTimer",
|
||||
"isCleanupRoutineRunning",
|
||||
"config",
|
||||
}
|
||||
|
|
@ -29,9 +28,8 @@ func (r *RouteTable) StateSave(stateSinkObject state.Sink) {
|
|||
r.beforeSave()
|
||||
stateSinkObject.Save(0, &r.installedRoutes)
|
||||
stateSinkObject.Save(1, &r.pendingRoutes)
|
||||
stateSinkObject.Save(2, &r.cleanupPendingRoutesTimer)
|
||||
stateSinkObject.Save(3, &r.isCleanupRoutineRunning)
|
||||
stateSinkObject.Save(4, &r.config)
|
||||
stateSinkObject.Save(2, &r.isCleanupRoutineRunning)
|
||||
stateSinkObject.Save(3, &r.config)
|
||||
}
|
||||
|
||||
func (r *RouteTable) afterLoad(context.Context) {}
|
||||
|
|
@ -40,9 +38,8 @@ func (r *RouteTable) afterLoad(context.Context) {}
|
|||
func (r *RouteTable) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &r.installedRoutes)
|
||||
stateSourceObject.Load(1, &r.pendingRoutes)
|
||||
stateSourceObject.Load(2, &r.cleanupPendingRoutesTimer)
|
||||
stateSourceObject.Load(3, &r.isCleanupRoutineRunning)
|
||||
stateSourceObject.Load(4, &r.config)
|
||||
stateSourceObject.Load(2, &r.isCleanupRoutineRunning)
|
||||
stateSourceObject.Load(3, &r.config)
|
||||
}
|
||||
|
||||
func (r *InstalledRoute) StateTypeName() string {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ type RouteTable struct {
|
|||
// cleanupPendingRoutesTimer is a timer that triggers a routine to remove
|
||||
// pending routes that are expired.
|
||||
// +checklocks:pendingMu
|
||||
cleanupPendingRoutesTimer tcpip.Timer
|
||||
cleanupPendingRoutesTimer tcpip.Timer `state:"nosave"`
|
||||
// +checklocks:pendingMu
|
||||
isCleanupRoutineRunning bool
|
||||
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ package ipv4
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header/parse"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
|
|
@ -343,10 +345,47 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
|
|||
switch h.Type() {
|
||||
case header.ICMPv4Echo:
|
||||
received.echoRequest.Increment()
|
||||
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
|
||||
|
||||
// DeliverTransportPacket may modify pkt so don't use it beyond
|
||||
// this point. Make a deep copy of the data before pkt gets sent as we will
|
||||
// be modifying fields. Both the ICMP header (with its type modified to
|
||||
// EchoReply) and payload are reused in the reply packet.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/4399): The copy may not be needed if there are no
|
||||
// waiting endpoints. Consider moving responsibility for doing the copy to
|
||||
// DeliverTransportPacket so that is is only done when needed.
|
||||
replyData := stack.PayloadSince(pkt.TransportHeader())
|
||||
defer replyData.Release()
|
||||
localAddressTemporary := pkt.NetworkPacketInfo.LocalAddressTemporary
|
||||
localAddressBroadcast := pkt.NetworkPacketInfo.LocalAddressBroadcast
|
||||
|
||||
// It's possible that a raw socket or per-stack default handler expects
|
||||
// to receive this packet.
|
||||
defaultHandlerHandled := false
|
||||
if dispatcher, ok := e.dispatcher.(stack.TransportDispatcherWithDefaultHandlerResult); ok {
|
||||
_, defaultHandlerHandled = dispatcher.DeliverTransportPacketWithDefaultHandlerResult(header.ICMPv4ProtocolNumber, pkt)
|
||||
} else {
|
||||
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
|
||||
}
|
||||
pkt = nil
|
||||
|
||||
// Skip the built-in ICMP echo reply if the request was consumed by a
|
||||
// per-stack default handler. Also preserve the IPv4 behavior for
|
||||
// temporary local addresses: the packet is delivered above, but the
|
||||
// stack does not synthesize an echo reply for it.
|
||||
if defaultHandlerHandled || localAddressTemporary {
|
||||
return
|
||||
}
|
||||
|
||||
e.sendICMPEchoReply(replyData, iph, newOptions, localAddressBroadcast)
|
||||
|
||||
case header.ICMPv4EchoReply:
|
||||
received.echoReply.Increment()
|
||||
|
||||
// ICMP sockets expect the ICMP header to be present, so we don't consume
|
||||
// the ICMP header.
|
||||
e.dispatcher.DeliverTransportPacket(header.ICMPv4ProtocolNumber, pkt)
|
||||
|
||||
case header.ICMPv4DstUnreachable:
|
||||
received.dstUnreachable.Increment()
|
||||
|
||||
|
|
@ -411,6 +450,98 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer) {
|
|||
}
|
||||
}
|
||||
|
||||
func (e *endpoint) sendICMPEchoReply(replyData *buffer.View, ipHdr header.IPv4, newOptions header.IPv4Options, localAddressBroadcast bool) {
|
||||
sent := e.stats.icmp.packetsSent
|
||||
if !e.protocol.allowICMPReply(header.ICMPv4EchoReply, header.ICMPv4UnusedCode) {
|
||||
sent.rateLimited.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
// As per RFC 1122 section 3.2.1.3, when a host sends any datagram, the IP
|
||||
// source address MUST be one of its own IP addresses (but not a broadcast
|
||||
// or multicast address).
|
||||
localAddr := ipHdr.DestinationAddress()
|
||||
if localAddressBroadcast || header.IsV4MulticastAddress(localAddr) {
|
||||
localAddr = tcpip.Address{}
|
||||
}
|
||||
|
||||
r, err := e.protocol.stack.FindRoute(e.nic.ID(), localAddr, ipHdr.SourceAddress(), ProtocolNumber, false /* multicastLoop */)
|
||||
if err != nil {
|
||||
// If we cannot find a route to the destination, silently drop the packet.
|
||||
return
|
||||
}
|
||||
defer r.Release()
|
||||
|
||||
outgoingEP, ok := e.protocol.getEndpointForNIC(r.NICID())
|
||||
if !ok {
|
||||
// The outgoing NIC went away.
|
||||
sent.dropped.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
// Because IP and ICMP are so closely intertwined, we need to handcraft our
|
||||
// IP header to be able to follow RFC 792. The wording on page 13 is as
|
||||
// follows:
|
||||
// IP Fields:
|
||||
// Addresses
|
||||
// The address of the source in an echo message will be the
|
||||
// destination of the echo reply message. To form an echo reply
|
||||
// message, the source and destination addresses are simply reversed,
|
||||
// the type code changed to 0, and the checksum recomputed.
|
||||
//
|
||||
// This was interpreted by early implementors to mean that all options must
|
||||
// be copied from the echo request IP header to the echo reply IP header
|
||||
// and this behaviour is still relied upon by some applications.
|
||||
//
|
||||
// Create a copy of the IP header we received, options and all, and change
|
||||
// The fields we need to alter.
|
||||
//
|
||||
// We need to produce the entire packet in the data segment in order to
|
||||
// use WriteHeaderIncludedPacket(). WriteHeaderIncludedPacket sets the
|
||||
// total length and the header checksum so we don't need to set those here.
|
||||
//
|
||||
// Take the base of the incoming request IP header but replace the options.
|
||||
replyHeaderLength := uint8(header.IPv4MinimumSize + len(newOptions))
|
||||
replyIPHdrView := buffer.NewView(int(replyHeaderLength))
|
||||
replyIPHdrView.Write(ipHdr[:header.IPv4MinimumSize])
|
||||
replyIPHdrView.Write(newOptions)
|
||||
replyIPHdr := header.IPv4(replyIPHdrView.AsSlice())
|
||||
replyIPHdr.SetHeaderLength(replyHeaderLength)
|
||||
replyIPHdr.SetSourceAddress(r.LocalAddress())
|
||||
replyIPHdr.SetDestinationAddress(r.RemoteAddress())
|
||||
replyIPHdr.SetTTL(r.DefaultTTL())
|
||||
replyIPHdr.SetTotalLength(uint16(len(replyIPHdr) + len(replyData.AsSlice())))
|
||||
replyIPHdr.SetChecksum(0)
|
||||
replyIPHdr.SetChecksum(^replyIPHdr.CalculateChecksum())
|
||||
|
||||
replyICMPHdr := header.ICMPv4(replyData.AsSlice())
|
||||
replyICMPHdr.SetType(header.ICMPv4EchoReply)
|
||||
replyICMPHdr.SetChecksum(0)
|
||||
replyICMPHdr.SetChecksum(^checksum.Checksum(replyData.AsSlice(), 0))
|
||||
|
||||
replyBuf := buffer.MakeWithView(replyIPHdrView)
|
||||
replyBuf.Append(replyData.Clone())
|
||||
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: int(r.MaxHeaderLength()),
|
||||
Payload: replyBuf,
|
||||
})
|
||||
defer replyPkt.DecRef()
|
||||
// Populate the network/transport headers in the packet buffer so the
|
||||
// ICMP packet goes through IPTables.
|
||||
if ok := parse.IPv4(replyPkt); !ok {
|
||||
panic("expected to parse IPv4 header we just created")
|
||||
}
|
||||
if ok := parse.ICMPv4(replyPkt); !ok {
|
||||
panic("expected to parse ICMPv4 header we just created")
|
||||
}
|
||||
|
||||
if err := outgoingEP.writePacket(r, replyPkt); err != nil {
|
||||
sent.dropped.Increment()
|
||||
return
|
||||
}
|
||||
sent.echoReply.Increment()
|
||||
}
|
||||
|
||||
// ======= ICMP Error packet generation =========
|
||||
|
||||
// icmpReason is a marker interface for IPv4 specific ICMP errors.
|
||||
|
|
@ -479,7 +610,12 @@ func (*icmpReasonNetworkUnreachable) isICMPReason() {}
|
|||
// icmpReasonFragmentationNeeded is an error where a packet requires
|
||||
// fragmentation while also having the Don't Fragment flag set, as per RFC 792
|
||||
// page 3, Destination Unreachable Message.
|
||||
type icmpReasonFragmentationNeeded struct{}
|
||||
type icmpReasonFragmentationNeeded struct {
|
||||
// mtu is the MTU of the next-hop link. Per RFC 1191 §4, this value
|
||||
// must be included in the ICMP Fragmentation Needed message so the
|
||||
// sender can update its path MTU cache.
|
||||
mtu uint32
|
||||
}
|
||||
|
||||
func (*icmpReasonFragmentationNeeded) isICMPReason() {}
|
||||
|
||||
|
|
@ -584,30 +720,36 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer, deliv
|
|||
}
|
||||
|
||||
sent := netEP.stats.icmp.packetsSent
|
||||
icmpType, icmpCode, counter, pointer := func() (header.ICMPv4Type, header.ICMPv4Code, tcpip.MultiCounterStat, byte) {
|
||||
icmpType, icmpCode, counter, pointer, nextHopMTU := func() (header.ICMPv4Type, header.ICMPv4Code, tcpip.MultiCounterStat, byte, uint16) {
|
||||
switch reason := reason.(type) {
|
||||
case *icmpReasonNetworkProhibited:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4NetProhibited, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4NetProhibited, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonHostProhibited:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4HostProhibited, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4HostProhibited, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonAdministrativelyProhibited:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4AdminProhibited, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4AdminProhibited, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonPortUnreachable:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4PortUnreachable, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4PortUnreachable, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonProtoUnreachable:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4ProtoUnreachable, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4ProtoUnreachable, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonNetworkUnreachable:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4NetUnreachable, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4NetUnreachable, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonHostUnreachable:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4HostUnreachable, sent.dstUnreachable, 0
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4HostUnreachable, sent.dstUnreachable, 0, 0
|
||||
case *icmpReasonFragmentationNeeded:
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, sent.dstUnreachable, 0
|
||||
// Per RFC 1191 §4, include the next-hop MTU in the ICMP message.
|
||||
// Cap at MaxUint16 since the field is 16 bits wide.
|
||||
mtu := reason.mtu
|
||||
if mtu > math.MaxUint16 {
|
||||
mtu = math.MaxUint16
|
||||
}
|
||||
return header.ICMPv4DstUnreachable, header.ICMPv4FragmentationNeeded, sent.dstUnreachable, 0, uint16(mtu)
|
||||
case *icmpReasonTTLExceeded:
|
||||
return header.ICMPv4TimeExceeded, header.ICMPv4TTLExceeded, sent.timeExceeded, 0
|
||||
return header.ICMPv4TimeExceeded, header.ICMPv4TTLExceeded, sent.timeExceeded, 0, 0
|
||||
case *icmpReasonReassemblyTimeout:
|
||||
return header.ICMPv4TimeExceeded, header.ICMPv4ReassemblyTimeout, sent.timeExceeded, 0
|
||||
return header.ICMPv4TimeExceeded, header.ICMPv4ReassemblyTimeout, sent.timeExceeded, 0, 0
|
||||
case *icmpReasonParamProblem:
|
||||
return header.ICMPv4ParamProblem, header.ICMPv4UnusedCode, sent.paramProblem, reason.pointer
|
||||
return header.ICMPv4ParamProblem, header.ICMPv4UnusedCode, sent.paramProblem, reason.pointer, 0
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
|
||||
}
|
||||
|
|
@ -676,6 +818,7 @@ func (p *protocol) returnError(reason icmpReason, pkt *stack.PacketBuffer, deliv
|
|||
icmpHdr.SetCode(icmpCode)
|
||||
icmpHdr.SetType(icmpType)
|
||||
icmpHdr.SetPointer(pointer)
|
||||
icmpHdr.SetMTU(nextHopMTU)
|
||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, icmpPkt.Data().Checksum()))
|
||||
|
||||
if err := route.WritePacket(
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/sagernet/gvisor/pkg/log"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header/parse"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/network/hash"
|
||||
|
|
@ -84,6 +85,7 @@ var (
|
|||
_ IGMPEndpoint = (*endpoint)(nil)
|
||||
)
|
||||
|
||||
// +checklocksalias:igmp.ep.mu=mu
|
||||
// +stateify savable
|
||||
type endpoint struct {
|
||||
nic stack.NetworkInterface
|
||||
|
|
@ -128,13 +130,11 @@ func (e *endpoint) GetIGMPVersion() IGMPVersion {
|
|||
}
|
||||
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.igmp.ep.mu=e.mu
|
||||
func (e *endpoint) setIGMPVersionLocked(v IGMPVersion) IGMPVersion {
|
||||
return e.igmp.setVersion(v)
|
||||
}
|
||||
|
||||
// +checklocksread:e.mu
|
||||
// +checklocksalias:e.igmp.ep.mu=e.mu
|
||||
func (e *endpoint) getIGMPVersionLocked() IGMPVersion {
|
||||
return e.igmp.getVersion()
|
||||
}
|
||||
|
|
@ -293,7 +293,6 @@ func (e *endpoint) Enable() tcpip.Error {
|
|||
}
|
||||
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.igmp.ep.mu=e.mu
|
||||
func (e *endpoint) enableLocked() tcpip.Error {
|
||||
// If the NIC is not enabled, the endpoint can't do anything meaningful so
|
||||
// don't enable the endpoint.
|
||||
|
|
@ -364,7 +363,6 @@ func (e *endpoint) Disable() {
|
|||
}
|
||||
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.igmp.ep.mu=e.mu
|
||||
func (e *endpoint) disableLocked() {
|
||||
if !e.isEnabled() {
|
||||
return
|
||||
|
|
@ -428,6 +426,11 @@ func (e *endpoint) MTU() uint32 {
|
|||
return networkMTU
|
||||
}
|
||||
|
||||
// EndpointHeaderSize returns the size necessary for the IPv4 header.
|
||||
func (e *endpoint) EndpointHeaderSize() uint32 {
|
||||
return header.IPv4MinimumSize
|
||||
}
|
||||
|
||||
// MaxHeaderLength returns the maximum length needed by ipv4 headers (and
|
||||
// underlying protocols).
|
||||
func (e *endpoint) MaxHeaderLength() uint16 {
|
||||
|
|
@ -519,6 +522,48 @@ func (e *endpoint) handleFragments(_ *stack.Route, networkMTU uint32, pkt *stack
|
|||
}
|
||||
}
|
||||
|
||||
// recalculateChecksum recalculates the checksum of a TCP packet.
|
||||
func recalculateChecksum(pkt *stack.PacketBuffer, r *stack.Route) tcpip.Error {
|
||||
// RXChecksumValidated indicates that checksum verification may be
|
||||
// safely skipped.
|
||||
if pkt.RXChecksumValidated {
|
||||
return nil
|
||||
}
|
||||
// NeedsCsum is set if the checksum offload is enabled, so no need to
|
||||
// calculate the checksum.
|
||||
if pkt.GSOOptions.Type != stack.GSONone && pkt.GSOOptions.NeedsCsum {
|
||||
return nil
|
||||
}
|
||||
transportHeader := pkt.TransportHeader().Slice()
|
||||
netHdr := header.IPv4(pkt.NetworkHeader().Slice())
|
||||
switch pkt.TransportProtocolNumber {
|
||||
case header.TCPProtocolNumber:
|
||||
if len(transportHeader) < header.TCPMinimumSize {
|
||||
return &tcpip.ErrMalformedHeader{}
|
||||
}
|
||||
tcp := header.TCP(transportHeader)
|
||||
xsum := r.PseudoHeaderChecksum(header.TCPProtocolNumber, netHdr.PayloadLength())
|
||||
xsum = checksum.Combine(xsum, pkt.Data().Checksum())
|
||||
tcp.SetChecksum(0)
|
||||
tcp.SetChecksum(^tcp.CalculateChecksum(xsum))
|
||||
case header.UDPProtocolNumber:
|
||||
if len(transportHeader) < header.UDPMinimumSize {
|
||||
return &tcpip.ErrMalformedHeader{}
|
||||
}
|
||||
udp := header.UDP(transportHeader)
|
||||
xsum := r.PseudoHeaderChecksum(header.UDPProtocolNumber, netHdr.PayloadLength())
|
||||
xsum = checksum.Combine(xsum, pkt.Data().Checksum())
|
||||
udp.SetChecksum(0)
|
||||
csum := ^udp.CalculateChecksum(xsum)
|
||||
// RFC 768: If the computed checksum is zero, it is transmitted as all ones.
|
||||
if csum == 0 {
|
||||
csum = 0xFFFF
|
||||
}
|
||||
udp.SetChecksum(csum)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WritePacket writes a packet to the given destination address and protocol.
|
||||
func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams, pkt *stack.PacketBuffer) tcpip.Error {
|
||||
if err := e.addIPHeader(r.LocalAddress(), r.RemoteAddress(), pkt, params, nil /* options */); err != nil {
|
||||
|
|
@ -531,16 +576,23 @@ func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams,
|
|||
func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer) tcpip.Error {
|
||||
netHeader := header.IPv4(pkt.NetworkHeader().Slice())
|
||||
dstAddr := netHeader.DestinationAddress()
|
||||
stk := e.protocol.stack
|
||||
|
||||
// iptables filtering. All packets that reach here are locally
|
||||
// generated.
|
||||
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
|
||||
if ok := e.protocol.stack.IPTables().CheckOutput(pkt, r, outNicName); !ok {
|
||||
// iptables filtering. All packets that reach here are locally generated.
|
||||
outNicName := stk.FindNICNameFromID(e.nic.ID())
|
||||
if ok := stk.IPTables().CheckOutput(pkt, r, outNicName); !ok {
|
||||
// iptables is telling us to drop the packet.
|
||||
e.stats.ip.IPTablesOutputDropped.Increment()
|
||||
return nil
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckOutput(pkt, r, stack.IP) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// If the packet is manipulated as per DNAT Output rules, handle packet
|
||||
// based on destination address and do not send the packet to link
|
||||
// layer.
|
||||
|
|
@ -555,6 +607,37 @@ func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer) tcpip.Er
|
|||
ep.handleLocalPacket(pkt, true /* canSkipRXChecksum */)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Similar to the `ip_route_me_harder` in the kernel,
|
||||
// we need to find a new route for the packet.
|
||||
// Implementation is similar to the func forwardUnicastPacket.
|
||||
stk := e.protocol.stack
|
||||
newRoute, err := stk.FindRoute(0 /* nic id */, netHeader.SourceAddress(), newDstAddr, header.IPv4ProtocolNumber, false /* multicastLoop */)
|
||||
if err != nil {
|
||||
return err // Drop the packet
|
||||
}
|
||||
// Release the new route on exit.
|
||||
defer newRoute.Release()
|
||||
|
||||
// Check if we need to recalculate the checksum.
|
||||
// If the original route did not require a checksum but the new one does,
|
||||
// we must calculate the full checksum; otherwise, NAT should have already
|
||||
// done it.
|
||||
if !r.RequiresTXTransportChecksum() && newRoute.RequiresTXTransportChecksum() {
|
||||
if err := recalculateChecksum(pkt, newRoute); err != nil {
|
||||
return err // Drop the packet
|
||||
}
|
||||
}
|
||||
|
||||
// Update the route to the new route.
|
||||
r = newRoute
|
||||
|
||||
// Use the new endpoint to write the packet.
|
||||
forwardToEp, ok := e.protocol.getEndpointForNIC(r.NICID())
|
||||
if !ok {
|
||||
return &tcpip.ErrUnknownNICID{}
|
||||
}
|
||||
return forwardToEp.writePacketPostRouting(r, pkt, true /* headerIncluded */)
|
||||
}
|
||||
|
||||
return e.writePacketPostRouting(r, pkt, false /* headerIncluded */)
|
||||
|
|
@ -571,15 +654,23 @@ func (e *endpoint) writePacketPostRouting(r *stack.Route, pkt *stack.PacketBuffe
|
|||
return nil
|
||||
}
|
||||
|
||||
stk := e.protocol.stack
|
||||
// Postrouting NAT can only change the source address, and does not alter the
|
||||
// route or outgoing interface of the packet.
|
||||
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
|
||||
if ok := e.protocol.stack.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
|
||||
outNicName := stk.FindNICNameFromID(e.nic.ID())
|
||||
if ok := stk.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
|
||||
// iptables is telling us to drop the packet.
|
||||
e.stats.ip.IPTablesPostroutingDropped.Increment()
|
||||
return nil
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckPostrouting(pkt, r, stack.IP) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
stats := e.stats.ip
|
||||
|
||||
networkMTU, err := calculateNetworkMTU(e.nic.MTU(), uint32(len(pkt.NetworkHeader().Slice())))
|
||||
|
|
@ -690,6 +781,13 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
|
|||
return nil
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckForward(pkt, route, stack.IP) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// We need to do a deep copy of the IP packet because
|
||||
// WriteHeaderIncludedPacket may modify the packet buffer, but we do
|
||||
// not own it.
|
||||
|
|
@ -725,6 +823,10 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
|
|||
newHdr.SetChecksum(0)
|
||||
newHdr.SetChecksum(^newHdr.CalculateChecksum())
|
||||
|
||||
if route.RequiresTXTransportChecksum() {
|
||||
newPkt.CalculateTransportChecksum()
|
||||
}
|
||||
|
||||
switch err := forwardToEp.writePacketPostRouting(route, newPkt, true /* headerIncluded */); err.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
|
|
@ -738,7 +840,9 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
|
|||
// WriteHeaderIncludedPacket checks for the presence of the Don't Fragment bit
|
||||
// while sending the packet and returns this error iff fragmentation is
|
||||
// necessary and the bit is also set.
|
||||
_ = e.protocol.returnError(&icmpReasonFragmentationNeeded{}, pkt, false /* deliveredLocally */)
|
||||
_ = e.protocol.returnError(&icmpReasonFragmentationNeeded{
|
||||
mtu: forwardToEp.nic.MTU(),
|
||||
}, pkt, false /* deliveredLocally */)
|
||||
return &ip.ErrMessageTooLong{}
|
||||
case *tcpip.ErrNoBufferSpace:
|
||||
return &ip.ErrOutgoingDeviceNoBufferSpace{}
|
||||
|
|
@ -790,6 +894,13 @@ func (e *endpoint) forwardUnicastPacket(pkt *stack.PacketBuffer) ip.ForwardingEr
|
|||
return nil
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckForward(pkt, nil /* route */, stack.IP) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// The packet originally arrived on e so provide its NIC as the input NIC.
|
||||
ep.handleValidatedPacket(h, pkt, e.nic.Name() /* inNICName */)
|
||||
return nil
|
||||
|
|
@ -857,7 +968,8 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
|
|||
}
|
||||
}
|
||||
|
||||
if e.protocol.stack.HandleLocal() {
|
||||
stk := e.protocol.stack
|
||||
if stk.HandleLocal() {
|
||||
addressEndpoint := e.AcquireAssignedAddress(header.IPv4(pkt.NetworkHeader().Slice()).SourceAddress(), e.nic.Promiscuous(), stack.CanBePrimaryEndpoint, true /* readOnly */)
|
||||
if addressEndpoint != nil {
|
||||
// The source address is one of our own, so we never should have gotten
|
||||
|
|
@ -868,13 +980,22 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
|
|||
}
|
||||
}
|
||||
|
||||
nicID := e.nic.ID()
|
||||
// Loopback traffic skips the prerouting chain.
|
||||
inNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
|
||||
if ok := e.protocol.stack.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
|
||||
inNicName := stk.FindNICNameFromID(nicID)
|
||||
pkt.InputNICID = nicID
|
||||
if ok := stk.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
|
||||
// iptables is telling us to drop the packet.
|
||||
stats.IPTablesPreroutingDropped.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckPrerouting(pkt, nil /* route */, stack.IP) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// CheckPrerouting can modify the backing storage of the packet, so refresh
|
||||
// the header.
|
||||
|
|
@ -1208,14 +1329,22 @@ func (e *endpoint) handleForwardingError(err ip.ForwardingError) {
|
|||
|
||||
func (e *endpoint) deliverPacketLocally(h header.IPv4, pkt *stack.PacketBuffer, inNICName string) {
|
||||
stats := e.stats
|
||||
stk := e.protocol.stack
|
||||
// iptables filtering. All packets that reach here are intended for
|
||||
// this machine and will not be forwarded.
|
||||
if ok := e.protocol.stack.IPTables().CheckInput(pkt, inNICName); !ok {
|
||||
if ok := stk.IPTables().CheckInput(pkt, inNICName); !ok {
|
||||
// iptables is telling us to drop the packet.
|
||||
stats.ip.IPTablesInputDropped.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckInput(pkt, nil /* route */, stack.IP) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if h.More() || h.FragmentOffset() != 0 {
|
||||
if pkt.Data().Size()+len(pkt.TransportHeader().Slice()) == 0 {
|
||||
// Drop the packet as it's marked as a fragment but has
|
||||
|
|
@ -1327,7 +1456,7 @@ func (e *endpoint) deliverPacketLocally(h header.IPv4, pkt *stack.PacketBuffer,
|
|||
}
|
||||
if p == header.IGMPProtocolNumber {
|
||||
e.mu.Lock()
|
||||
e.igmp.handleIGMP(pkt, hasRouterAlertOption) // +checklocksforce: e == e.igmp.ep.
|
||||
e.igmp.handleIGMP(pkt, hasRouterAlertOption)
|
||||
e.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
|
@ -1377,7 +1506,6 @@ func (e *endpoint) AddAndAcquirePermanentAddress(addr tcpip.AddressWithPrefix, p
|
|||
// sendQueuedReports sends queued igmp reports.
|
||||
//
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.igmp.ep.mu=e.mu
|
||||
func (e *endpoint) sendQueuedReports() {
|
||||
e.igmp.sendQueuedReports()
|
||||
}
|
||||
|
|
@ -1463,7 +1591,6 @@ func (e *endpoint) JoinGroup(addr tcpip.Address) tcpip.Error {
|
|||
// joinGroupLocked is like JoinGroup but with locking requirements.
|
||||
//
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.igmp.ep.mu=e.mu
|
||||
func (e *endpoint) joinGroupLocked(addr tcpip.Address) tcpip.Error {
|
||||
if !header.IsV4MulticastAddress(addr) {
|
||||
return &tcpip.ErrBadAddress{}
|
||||
|
|
@ -1483,7 +1610,6 @@ func (e *endpoint) LeaveGroup(addr tcpip.Address) tcpip.Error {
|
|||
// leaveGroupLocked is like LeaveGroup but with locking requirements.
|
||||
//
|
||||
// +checklocks:e.mu
|
||||
// +checklocksalias:e.igmp.ep.mu=e.mu
|
||||
func (e *endpoint) leaveGroupLocked(addr tcpip.Address) tcpip.Error {
|
||||
return e.igmp.leaveGroup(addr)
|
||||
}
|
||||
|
|
@ -1492,7 +1618,7 @@ func (e *endpoint) leaveGroupLocked(addr tcpip.Address) tcpip.Error {
|
|||
func (e *endpoint) IsInGroup(addr tcpip.Address) bool {
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
return e.igmp.isInGroup(addr) // +checklocksforce: e.mu==e.igmp.ep.mu.
|
||||
return e.igmp.isInGroup(addr)
|
||||
}
|
||||
|
||||
// Stats implements stack.NetworkEndpoint.
|
||||
|
|
@ -1860,6 +1986,8 @@ func (p *protocol) SendRejectionError(pkt *stack.PacketBuffer, rejectWith stack.
|
|||
return p.returnError(&icmpReasonHostProhibited{}, pkt, inputHook)
|
||||
case stack.RejectIPv4WithICMPAdminProhibited:
|
||||
return p.returnError(&icmpReasonAdministrativelyProhibited{}, pkt, inputHook)
|
||||
case stack.RejectIPv4WithTCPReset:
|
||||
return ip.RejectWithTCPReset(pkt, ProtocolNumber, p.stack, inputHook)
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled %[1]T = %[1]d", rejectWith))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -654,11 +654,29 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
|
|||
|
||||
case header.ICMPv6EchoRequest:
|
||||
received.echoRequest.Increment()
|
||||
if len(h) < header.ICMPv6EchoMinimumSize {
|
||||
received.invalid.Increment()
|
||||
replyPayload := pkt.Data().ToBuffer()
|
||||
replyHeader := make([]byte, header.ICMPv6EchoMinimumSize)
|
||||
copy(replyHeader, h[:header.ICMPv6EchoMinimumSize])
|
||||
|
||||
// It's possible that a raw socket or per-stack default handler expects
|
||||
// to receive this packet.
|
||||
defaultHandlerHandled := false
|
||||
if dispatcher, ok := e.dispatcher.(stack.TransportDispatcherWithDefaultHandlerResult); ok {
|
||||
_, defaultHandlerHandled = dispatcher.DeliverTransportPacketWithDefaultHandlerResult(header.ICMPv6ProtocolNumber, pkt)
|
||||
} else {
|
||||
e.dispatcher.DeliverTransportPacket(header.ICMPv6ProtocolNumber, pkt)
|
||||
}
|
||||
pkt = nil
|
||||
|
||||
// Skip the built-in ICMP echo reply if the request was consumed by a
|
||||
// per-stack default handler.
|
||||
if defaultHandlerHandled {
|
||||
replyPayload.Release()
|
||||
return
|
||||
}
|
||||
e.dispatcher.DeliverTransportPacket(header.ICMPv6ProtocolNumber, pkt)
|
||||
|
||||
e.sendICMPEchoReply(replyPayload, replyHeader, srcAddr, dstAddr, iph)
|
||||
|
||||
case header.ICMPv6EchoReply:
|
||||
received.echoReply.Increment()
|
||||
if len(h) < header.ICMPv6EchoMinimumSize {
|
||||
|
|
@ -666,6 +684,7 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
|
|||
return
|
||||
}
|
||||
e.dispatcher.DeliverTransportPacket(header.ICMPv6ProtocolNumber, pkt)
|
||||
|
||||
case header.ICMPv6TimeExceeded:
|
||||
received.timeExceeded.Increment()
|
||||
|
||||
|
|
@ -852,6 +871,62 @@ func (e *endpoint) handleICMP(pkt *stack.PacketBuffer, hasFragmentHeader bool, r
|
|||
}
|
||||
}
|
||||
|
||||
func (e *endpoint) sendICMPEchoReply(replyPayload buffer.Buffer, replyHeader []byte, srcAddr, dstAddr tcpip.Address, ipHdr header.IPv6) {
|
||||
sent := e.stats.icmp.packetsSent
|
||||
|
||||
// As per RFC 4291 section 2.7, multicast addresses must not be used as
|
||||
// source addresses in IPv6 packets.
|
||||
localAddr := dstAddr
|
||||
if header.IsV6MulticastAddress(dstAddr) {
|
||||
localAddr = tcpip.Address{}
|
||||
}
|
||||
|
||||
r, err := e.protocol.stack.FindRoute(e.nic.ID(), localAddr, srcAddr, ProtocolNumber, false /* multicastLoop */)
|
||||
if err != nil {
|
||||
// If we cannot find a route to the destination, silently drop the packet.
|
||||
replyPayload.Release()
|
||||
return
|
||||
}
|
||||
defer r.Release()
|
||||
|
||||
if !e.protocol.allowICMPReply(header.ICMPv6EchoReply) {
|
||||
sent.rateLimited.Increment()
|
||||
replyPayload.Release()
|
||||
return
|
||||
}
|
||||
|
||||
replyPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: int(r.MaxHeaderLength()) + header.ICMPv6EchoMinimumSize,
|
||||
Payload: replyPayload,
|
||||
})
|
||||
defer replyPkt.DecRef()
|
||||
icmp := header.ICMPv6(replyPkt.TransportHeader().Push(header.ICMPv6EchoMinimumSize))
|
||||
replyPkt.TransportProtocolNumber = header.ICMPv6ProtocolNumber
|
||||
copy(icmp, replyHeader)
|
||||
icmp.SetType(header.ICMPv6EchoReply)
|
||||
replyData := replyPkt.Data()
|
||||
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||
Header: icmp,
|
||||
Src: r.LocalAddress(),
|
||||
Dst: r.RemoteAddress(),
|
||||
PayloadCsum: replyData.Checksum(),
|
||||
PayloadLen: replyData.Size(),
|
||||
}))
|
||||
replyTClass, _ := ipHdr.TOS()
|
||||
if err := r.WritePacket(stack.NetworkHeaderParams{
|
||||
Protocol: header.ICMPv6ProtocolNumber,
|
||||
TTL: r.DefaultTTL(),
|
||||
// Even though RFC 4443 does not mention anything about it, Linux uses the
|
||||
// TrafficClass of the received echo request when replying.
|
||||
// https://github.com/torvalds/linux/blob/0280e3c58f9/net/ipv6/icmp.c#L797
|
||||
TOS: replyTClass,
|
||||
}, replyPkt); err != nil {
|
||||
sent.dropped.Increment()
|
||||
return
|
||||
}
|
||||
sent.echoReply.Increment()
|
||||
}
|
||||
|
||||
// LinkAddressProtocol implements stack.LinkAddressResolver.
|
||||
func (*endpoint) LinkAddressProtocol() tcpip.NetworkProtocolNumber {
|
||||
return header.IPv6ProtocolNumber
|
||||
|
|
|
|||
|
|
@ -674,7 +674,7 @@ func (e *endpoint) Disable() {
|
|||
}
|
||||
|
||||
func (e *endpoint) disableLocked() {
|
||||
if !e.Enabled() {
|
||||
if !e.isEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -730,6 +730,11 @@ func (e *endpoint) MTU() uint32 {
|
|||
return networkMTU
|
||||
}
|
||||
|
||||
// EndpointHeaderSize returns the size necessary for the IPv6 header.
|
||||
func (e *endpoint) EndpointHeaderSize() uint32 {
|
||||
return header.IPv6MinimumSize
|
||||
}
|
||||
|
||||
// MaxHeaderLength returns the maximum length needed by ipv6 headers (and
|
||||
// underlying protocols).
|
||||
func (e *endpoint) MaxHeaderLength() uint16 {
|
||||
|
|
@ -819,15 +824,23 @@ func (e *endpoint) WritePacket(r *stack.Route, params stack.NetworkHeaderParams,
|
|||
return err
|
||||
}
|
||||
|
||||
// iptables filtering. All packets that reach here are locally
|
||||
// generated.
|
||||
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
|
||||
if ok := e.protocol.stack.IPTables().CheckOutput(pkt, r, outNicName); !ok {
|
||||
stk := e.protocol.stack
|
||||
// iptables filtering. All packets that reach here are locally generated.
|
||||
outNicName := stk.FindNICNameFromID(e.nic.ID())
|
||||
if ok := stk.IPTables().CheckOutput(pkt, r, outNicName); !ok {
|
||||
// iptables is telling us to drop the packet.
|
||||
e.stats.ip.IPTablesOutputDropped.Increment()
|
||||
return nil
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
// TODO: b/486197011 - Add support for NAT re-routing in IPv6.
|
||||
if !nft.CheckOutput(pkt, r, stack.IP6) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// If the packet is manipulated as per DNAT Output rules, handle packet
|
||||
// based on destination address and do not send the packet to link
|
||||
// layer.
|
||||
|
|
@ -858,15 +871,23 @@ func (e *endpoint) writePacket(r *stack.Route, pkt *stack.PacketBuffer, protocol
|
|||
return nil
|
||||
}
|
||||
|
||||
stk := e.protocol.stack
|
||||
// Postrouting NAT can only change the source address, and does not alter the
|
||||
// route or outgoing interface of the packet.
|
||||
outNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
|
||||
if ok := e.protocol.stack.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
|
||||
outNicName := stk.FindNICNameFromID(e.nic.ID())
|
||||
if ok := stk.IPTables().CheckPostrouting(pkt, r, e, outNicName); !ok {
|
||||
// iptables is telling us to drop the packet.
|
||||
e.stats.ip.IPTablesPostroutingDropped.Increment()
|
||||
return nil
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckPostrouting(pkt, r, stack.IP6) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
stats := e.stats.ip
|
||||
networkMTU, err := calculateNetworkMTU(e.nic.MTU(), uint32(len(pkt.NetworkHeader().Slice())))
|
||||
if err != nil {
|
||||
|
|
@ -1007,6 +1028,13 @@ func (e *endpoint) forwardUnicastPacket(pkt *stack.PacketBuffer) ip.ForwardingEr
|
|||
return nil
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckForward(pkt, nil, stack.IP6) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// The packet originally arrived on e so provide its NIC as the input NIC.
|
||||
ep.handleValidatedPacket(h, pkt, e.nic.Name() /* inNICName */)
|
||||
return nil
|
||||
|
|
@ -1048,6 +1076,13 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
|
|||
return nil
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckForward(pkt, route, stack.IP6) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
hopLimit := h.HopLimit()
|
||||
|
||||
// We need to do a deep copy of the IP packet because
|
||||
|
|
@ -1063,6 +1098,10 @@ func (e *endpoint) forwardPacketWithRoute(route *stack.Route, pkt *stack.PacketB
|
|||
// each node that forwards the packet.
|
||||
newHdr.SetHopLimit(hopLimit - 1)
|
||||
|
||||
if route.RequiresTXTransportChecksum() {
|
||||
newPkt.CalculateTransportChecksum()
|
||||
}
|
||||
|
||||
forwardToEp, ok := e.protocol.getEndpointForNIC(route.NICID())
|
||||
if !ok {
|
||||
// The interface was removed after we obtained the route.
|
||||
|
|
@ -1123,7 +1162,8 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
|
|||
}
|
||||
}
|
||||
|
||||
if e.protocol.stack.HandleLocal() {
|
||||
stk := e.protocol.stack
|
||||
if stk.HandleLocal() {
|
||||
addressEndpoint := e.AcquireAssignedAddress(header.IPv6(pkt.NetworkHeader().Slice()).SourceAddress(), e.nic.Promiscuous(), stack.CanBePrimaryEndpoint, true /* readOnly */)
|
||||
if addressEndpoint != nil {
|
||||
// The source address is one of our own, so we never should have gotten
|
||||
|
|
@ -1135,12 +1175,19 @@ func (e *endpoint) HandlePacket(pkt *stack.PacketBuffer) {
|
|||
}
|
||||
|
||||
// Loopback traffic skips the prerouting chain.
|
||||
inNicName := e.protocol.stack.FindNICNameFromID(e.nic.ID())
|
||||
if ok := e.protocol.stack.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
|
||||
inNicName := stk.FindNICNameFromID(e.nic.ID())
|
||||
if ok := stk.IPTables().CheckPrerouting(pkt, e, inNicName); !ok {
|
||||
// iptables is telling us to drop the packet.
|
||||
stats.IPTablesPreroutingDropped.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckPrerouting(pkt, nil, stack.IP6) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckPrerouting can modify the backing storage of the packet, so refresh
|
||||
|
|
@ -1386,15 +1433,20 @@ func (e *endpoint) handleValidatedPacket(h header.IPv6, pkt *stack.PacketBuffer,
|
|||
|
||||
func (e *endpoint) deliverPacketLocally(h header.IPv6, pkt *stack.PacketBuffer, inNICName string) {
|
||||
stats := e.stats.ip
|
||||
|
||||
// iptables filtering. All packets that reach here are intended for
|
||||
// this machine and need not be forwarded.
|
||||
if ok := e.protocol.stack.IPTables().CheckInput(pkt, inNICName); !ok {
|
||||
stk := e.protocol.stack
|
||||
if ok := stk.IPTables().CheckInput(pkt, inNICName); !ok {
|
||||
// iptables is telling us to drop the packet.
|
||||
stats.IPTablesInputDropped.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
if nft := stk.NFTables(); nft != nil && stk.IsNFTablesConfigured() {
|
||||
if !nft.CheckInput(pkt, nil, stack.IP6) {
|
||||
// nftables is telling us to drop the packet.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Any returned error is only useful for terminating execution early, but
|
||||
// we have nothing left to do, so we can drop it.
|
||||
_ = e.processExtensionHeaders(h, pkt, false /* forwarding */)
|
||||
|
|
@ -2692,6 +2744,8 @@ func (p *protocol) SendRejectionError(pkt *stack.PacketBuffer, rejectWith stack.
|
|||
return p.returnError(&icmpReasonPortUnreachable{}, pkt, inputHook)
|
||||
case stack.RejectIPv6WithICMPAdminProhibited:
|
||||
return p.returnError(&icmpReasonAdministrativelyProhibited{}, pkt, inputHook)
|
||||
case stack.RejectIPv6WithTCPReset:
|
||||
return ip.RejectWithTCPReset(pkt, ProtocolNumber, p.stack, inputHook)
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled %[1]T = %[1]d", rejectWith))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -525,7 +525,6 @@ func (t *timer) StateTypeName() string {
|
|||
func (t *timer) StateFields() []string {
|
||||
return []string{
|
||||
"done",
|
||||
"timer",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -535,7 +534,6 @@ func (t *timer) beforeSave() {}
|
|||
func (t *timer) StateSave(stateSinkObject state.Sink) {
|
||||
t.beforeSave()
|
||||
stateSinkObject.Save(0, &t.done)
|
||||
stateSinkObject.Save(1, &t.timer)
|
||||
}
|
||||
|
||||
func (t *timer) afterLoad(context.Context) {}
|
||||
|
|
@ -543,7 +541,6 @@ func (t *timer) afterLoad(context.Context) {}
|
|||
// +checklocksignore
|
||||
func (t *timer) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &t.done)
|
||||
stateSourceObject.Load(1, &t.timer)
|
||||
}
|
||||
|
||||
func (o *offLinkRoute) StateTypeName() string {
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ type timer struct {
|
|||
// done indicates to the timer that the timer was stopped.
|
||||
done *bool
|
||||
|
||||
timer tcpip.Timer
|
||||
timer tcpip.Timer `state:"nosave"`
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
|
|
|
|||
|
|
@ -253,13 +253,6 @@ type SocketOptions struct {
|
|||
// receiveBufferSize determines the receive buffer size for this socket.
|
||||
receiveBufferSize atomicbitops.Int64
|
||||
|
||||
// mu protects the access to the below fields.
|
||||
mu sync.Mutex `state:"nosave"`
|
||||
|
||||
// linger determines the amount of time the socket should linger before
|
||||
// close. We currently implement this option for TCP socket only.
|
||||
linger LingerOption
|
||||
|
||||
// rcvlowat specifies the minimum number of bytes which should be
|
||||
// received to indicate the socket as readable.
|
||||
rcvlowat atomicbitops.Int32
|
||||
|
|
@ -267,6 +260,16 @@ type SocketOptions struct {
|
|||
// experimentOptionValue is the value set for the IP option experiment header
|
||||
// if it is not zero.
|
||||
experimentOptionValue atomicbitops.Uint32
|
||||
|
||||
// mark is the mark value set for the socket.
|
||||
mark atomicbitops.Uint32
|
||||
|
||||
// mu protects the access to the below fields.
|
||||
mu sync.Mutex `state:"nosave"`
|
||||
|
||||
// linger determines the amount of time the socket should linger before
|
||||
// close. We currently implement this option for TCP socket only.
|
||||
linger LingerOption
|
||||
}
|
||||
|
||||
// InitHandler initializes the handler. This must be called before using the
|
||||
|
|
@ -771,3 +774,13 @@ func (so *SocketOptions) SetRcvlowat(rcvlowat int32) Error {
|
|||
func (so *SocketOptions) GetAcceptConn() bool {
|
||||
return so.handler.GetAcceptConn()
|
||||
}
|
||||
|
||||
// GetMark gets value for SO_MARK option.
|
||||
func (so *SocketOptions) GetMark() uint32 {
|
||||
return so.mark.Load()
|
||||
}
|
||||
|
||||
// SetMark sets value for SO_MARK option.
|
||||
func (so *SocketOptions) SetMark(v uint32) {
|
||||
so.mark.Store(v)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func addressStateinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
addressStateinitLockNames()
|
||||
addressStateprefixIndex = locking.NewMutexClass(reflect.TypeOf(addressStateRWMutex{}), addressStatelockNames)
|
||||
addressStateprefixIndex = locking.NewMutexClass(reflect.TypeFor[addressStateRWMutex](), addressStatelockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func addressableEndpointStateinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
addressableEndpointStateinitLockNames()
|
||||
addressableEndpointStateprefixIndex = locking.NewMutexClass(reflect.TypeOf(addressableEndpointStateRWMutex{}), addressableEndpointStatelockNames)
|
||||
addressableEndpointStateprefixIndex = locking.NewMutexClass(reflect.TypeFor[addressableEndpointStateRWMutex](), addressableEndpointStatelockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func bridgeinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
bridgeinitLockNames()
|
||||
bridgeprefixIndex = locking.NewMutexClass(reflect.TypeOf(bridgeRWMutex{}), bridgelockNames)
|
||||
bridgeprefixIndex = locking.NewMutexClass(reflect.TypeFor[bridgeRWMutex](), bridgelockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,5 +93,5 @@ func bucketinitLockNames() { bucketlockNames = []string{"otherTuple"} }
|
|||
|
||||
func init() {
|
||||
bucketinitLockNames()
|
||||
bucketprefixIndex = locking.NewMutexClass(reflect.TypeOf(bucketRWMutex{}), bucketlockNames)
|
||||
bucketprefixIndex = locking.NewMutexClass(reflect.TypeFor[bucketRWMutex](), bucketlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,5 +60,5 @@ func cleanupEndpointsinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
cleanupEndpointsinitLockNames()
|
||||
cleanupEndpointsprefixIndex = locking.NewMutexClass(reflect.TypeOf(cleanupEndpointsMutex{}), cleanupEndpointslockNames)
|
||||
cleanupEndpointsprefixIndex = locking.NewMutexClass(reflect.TypeFor[cleanupEndpointsMutex](), cleanupEndpointslockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func conninitLockNames() {}
|
|||
|
||||
func init() {
|
||||
conninitLockNames()
|
||||
connprefixIndex = locking.NewMutexClass(reflect.TypeOf(connRWMutex{}), connlockNames)
|
||||
connprefixIndex = locking.NewMutexClass(reflect.TypeFor[connRWMutex](), connlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func connTrackinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
connTrackinitLockNames()
|
||||
connTrackprefixIndex = locking.NewMutexClass(reflect.TypeOf(connTrackRWMutex{}), connTracklockNames)
|
||||
connTrackprefixIndex = locking.NewMutexClass(reflect.TypeFor[connTrackRWMutex](), connTracklockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import (
|
|||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -45,6 +44,31 @@ const (
|
|||
unestablishedTimeout time.Duration = 120 * time.Second
|
||||
)
|
||||
|
||||
// ConnTrackState represents the state of a connection.
|
||||
type ConnTrackState int
|
||||
|
||||
const (
|
||||
// ConnTrackStateInvalid is the invalid connection tracking state.
|
||||
ConnTrackStateInvalid ConnTrackState = -1
|
||||
// ConnTrackStateEstablished represents an established connection.
|
||||
ConnTrackStateEstablished ConnTrackState = 0
|
||||
// ConnTrackStateNew represents a new connection.
|
||||
ConnTrackStateNew ConnTrackState = 2
|
||||
// ConnTrackStateEstablishedReply represents an established connection
|
||||
// in the reply direction.
|
||||
ConnTrackStateEstablishedReply ConnTrackState = 3
|
||||
)
|
||||
|
||||
// ConnTrackDirection represents the direction of a connection.
|
||||
type ConnTrackDirection uint8
|
||||
|
||||
const (
|
||||
// ConnTrackDirectionOriginal represents the original direction.
|
||||
ConnTrackDirectionOriginal ConnTrackDirection = 0
|
||||
// ConnTrackDirectionReply represents the reply direction.
|
||||
ConnTrackDirectionReply ConnTrackDirection = 1
|
||||
)
|
||||
|
||||
// tuple holds a connection's identifying and manipulating data in one
|
||||
// direction. It is immutable.
|
||||
//
|
||||
|
|
@ -161,6 +185,10 @@ type conn struct {
|
|||
//
|
||||
// +checklocks:stateMu
|
||||
lastUsed tcpip.MonotonicTime
|
||||
// replySeen indicates whether a packet in the reply direction has been seen.
|
||||
//
|
||||
// +checklocks:stateMu
|
||||
replySeen bool
|
||||
}
|
||||
|
||||
// timedOut returns whether the connection timed out based on its state.
|
||||
|
|
@ -177,6 +205,27 @@ func (cn *conn) timedOut(now tcpip.MonotonicTime) bool {
|
|||
return now.Sub(cn.lastUsed) > unestablishedTimeout
|
||||
}
|
||||
|
||||
// expiresIn returns the duration from now until the connection times out.
|
||||
func (cn *conn) expiresIn() time.Duration {
|
||||
var timeout time.Duration
|
||||
var lastUsed tcpip.MonotonicTime
|
||||
cn.stateMu.RLock()
|
||||
state := cn.tcb.State()
|
||||
lastUsed = cn.lastUsed
|
||||
cn.stateMu.RUnlock()
|
||||
if state == tcpconntrack.ResultAlive {
|
||||
timeout = establishedTimeout
|
||||
} else {
|
||||
timeout = unestablishedTimeout
|
||||
}
|
||||
now := cn.ct.clock.NowMonotonic()
|
||||
expires := timeout - now.Sub(lastUsed)
|
||||
if expires < 0 {
|
||||
return 0
|
||||
}
|
||||
return expires
|
||||
}
|
||||
|
||||
// update the connection tracking state.
|
||||
func (cn *conn) update(pkt *PacketBuffer, reply bool) {
|
||||
cn.stateMu.Lock()
|
||||
|
|
@ -184,6 +233,9 @@ func (cn *conn) update(pkt *PacketBuffer, reply bool) {
|
|||
|
||||
// Mark the connection as having been used recently so it isn't reaped.
|
||||
cn.lastUsed = cn.ct.clock.NowMonotonic()
|
||||
if reply {
|
||||
cn.replySeen = true
|
||||
}
|
||||
|
||||
if pkt.TransportProtocolNumber != header.TCPProtocolNumber {
|
||||
return
|
||||
|
|
@ -206,6 +258,10 @@ func (cn *conn) update(pkt *PacketBuffer, reply bool) {
|
|||
}
|
||||
}
|
||||
|
||||
type connTrackRNG interface {
|
||||
Uint32() uint32
|
||||
}
|
||||
|
||||
// ConnTrack tracks all connections created for NAT rules. Most users are
|
||||
// expected to only call handlePacket, insertRedirectConn, and maybeInsertNoop.
|
||||
//
|
||||
|
|
@ -225,12 +281,26 @@ type ConnTrack struct {
|
|||
// seed is a one-time random value initialized at stack startup
|
||||
// and is used in the calculation of hash keys for the list of buckets.
|
||||
// It is immutable.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/4595): When Stack.tables becomes savable and
|
||||
// ConnTrack flows into checkpoint state, this seed must be redrawn
|
||||
// from secureRNG during restore AND the entries in buckets must be
|
||||
// rehashed under the new seed. bucket_index = jenkins.Sum32(seed) %
|
||||
// len(buckets) couples the seed value to bucket layout; redrawing the
|
||||
// seed without rehashing leaves restored entries unreachable by
|
||||
// Lookup. Persisting the pre-checkpoint seed extends the brute-force
|
||||
// window across save boundaries.
|
||||
seed uint32
|
||||
|
||||
// nftIDSeed is a one-time random value initialized at stack startup
|
||||
// and is used in the calculation of tuple IDs for nftables.
|
||||
// It is immutable.
|
||||
nftIDSeed uint32
|
||||
|
||||
// clock provides timing used to determine conntrack reapings.
|
||||
clock tcpip.Clock
|
||||
// TODO(b/341946753): Restore when netstack is savable.
|
||||
rand *rand.Rand `state:"nosave"`
|
||||
rng connTrackRNG `state:"nosave"`
|
||||
|
||||
mu connTrackRWMutex `state:"nosave"`
|
||||
// mu protects the buckets slice, but not buckets' contents. Only take
|
||||
|
|
@ -271,99 +341,6 @@ func v6NetAndTransHdr(icmpPayload []byte, minTransHdrLen int) (header.Network, [
|
|||
return netHdr, transHdr[:minTransHdrLen]
|
||||
}
|
||||
|
||||
func getEmbeddedNetAndTransHeaders(pkt *PacketBuffer, netHdrLength int, getNetAndTransHdr netAndTransHeadersFunc, transProto tcpip.TransportProtocolNumber) (header.Network, header.ChecksummableTransport, bool) {
|
||||
switch transProto {
|
||||
case header.TCPProtocolNumber:
|
||||
if netAndTransHeader, ok := pkt.Data().PullUp(netHdrLength + header.TCPMinimumSize); ok {
|
||||
netHeader, transHeaderBytes := getNetAndTransHdr(netAndTransHeader, header.TCPMinimumSize)
|
||||
return netHeader, header.TCP(transHeaderBytes), true
|
||||
}
|
||||
case header.UDPProtocolNumber:
|
||||
if netAndTransHeader, ok := pkt.Data().PullUp(netHdrLength + header.UDPMinimumSize); ok {
|
||||
netHeader, transHeaderBytes := getNetAndTransHdr(netAndTransHeader, header.UDPMinimumSize)
|
||||
return netHeader, header.UDP(transHeaderBytes), true
|
||||
}
|
||||
}
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
func getHeaders(pkt *PacketBuffer) (netHdr header.Network, transHdr header.Transport, isICMPError bool, ok bool) {
|
||||
switch pkt.TransportProtocolNumber {
|
||||
case header.TCPProtocolNumber:
|
||||
if tcpHeader := header.TCP(pkt.TransportHeader().Slice()); len(tcpHeader) >= header.TCPMinimumSize {
|
||||
return pkt.Network(), tcpHeader, false, true
|
||||
}
|
||||
return nil, nil, false, false
|
||||
case header.UDPProtocolNumber:
|
||||
if udpHeader := header.UDP(pkt.TransportHeader().Slice()); len(udpHeader) >= header.UDPMinimumSize {
|
||||
return pkt.Network(), udpHeader, false, true
|
||||
}
|
||||
return nil, nil, false, false
|
||||
case header.ICMPv4ProtocolNumber:
|
||||
icmpHeader := header.ICMPv4(pkt.TransportHeader().Slice())
|
||||
if len(icmpHeader) < header.ICMPv4MinimumSize {
|
||||
return nil, nil, false, false
|
||||
}
|
||||
|
||||
switch icmpType := icmpHeader.Type(); icmpType {
|
||||
case header.ICMPv4Echo, header.ICMPv4EchoReply:
|
||||
return pkt.Network(), icmpHeader, false, true
|
||||
case header.ICMPv4DstUnreachable, header.ICMPv4TimeExceeded, header.ICMPv4ParamProblem:
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
|
||||
}
|
||||
|
||||
h, ok := pkt.Data().PullUp(header.IPv4MinimumSize)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("should have a valid IPv4 packet; only have %d bytes, want at least %d bytes", pkt.Data().Size(), header.IPv4MinimumSize))
|
||||
}
|
||||
|
||||
if header.IPv4(h).HeaderLength() > header.IPv4MinimumSize {
|
||||
// TODO(https://gvisor.dev/issue/6765): Handle IPv4 options.
|
||||
panic("should have dropped packets with IPv4 options")
|
||||
}
|
||||
|
||||
if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, header.IPv4MinimumSize, v4NetAndTransHdr, pkt.tuple.tupleID.transProto); ok {
|
||||
return netHdr, transHdr, true, true
|
||||
}
|
||||
return nil, nil, false, false
|
||||
case header.ICMPv6ProtocolNumber:
|
||||
icmpHeader := header.ICMPv6(pkt.TransportHeader().Slice())
|
||||
if len(icmpHeader) < header.ICMPv6MinimumSize {
|
||||
return nil, nil, false, false
|
||||
}
|
||||
|
||||
switch icmpType := icmpHeader.Type(); icmpType {
|
||||
case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
|
||||
return pkt.Network(), icmpHeader, false, true
|
||||
case header.ICMPv6DstUnreachable, header.ICMPv6PacketTooBig, header.ICMPv6TimeExceeded, header.ICMPv6ParamProblem:
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected ICMPv6 type = %d", icmpType))
|
||||
}
|
||||
|
||||
h, ok := pkt.Data().PullUp(header.IPv6MinimumSize)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("should have a valid IPv6 packet; only have %d bytes, want at least %d bytes", pkt.Data().Size(), header.IPv6MinimumSize))
|
||||
}
|
||||
|
||||
// We do not support extension headers in ICMP errors so the next header
|
||||
// in the IPv6 packet should be a tracked protocol if we reach this point.
|
||||
//
|
||||
// TODO(https://gvisor.dev/issue/6789): Support extension headers.
|
||||
transProto := pkt.tuple.tupleID.transProto
|
||||
if got := header.IPv6(h).TransportProtocol(); got != transProto {
|
||||
panic(fmt.Sprintf("got TransportProtocol() = %d, want = %d", got, transProto))
|
||||
}
|
||||
|
||||
if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, header.IPv6MinimumSize, v6NetAndTransHdr, transProto); ok {
|
||||
return netHdr, transHdr, true, true
|
||||
}
|
||||
return nil, nil, false, false
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected transport protocol = %d", pkt.TransportProtocolNumber))
|
||||
}
|
||||
}
|
||||
|
||||
func getTupleIDForRegularPacket(netHdr header.Network, netProto tcpip.NetworkProtocolNumber, transHdr header.Transport, transProto tcpip.TransportProtocolNumber) tupleID {
|
||||
return tupleID{
|
||||
srcAddr: netHdr.SourceAddress(),
|
||||
|
|
@ -376,7 +353,7 @@ func getTupleIDForRegularPacket(netHdr header.Network, netProto tcpip.NetworkPro
|
|||
}
|
||||
|
||||
func getTupleIDForPacketInICMPError(pkt *PacketBuffer, getNetAndTransHdr netAndTransHeadersFunc, netProto tcpip.NetworkProtocolNumber, netLen int, transProto tcpip.TransportProtocolNumber) (tupleID, bool) {
|
||||
if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, netLen, getNetAndTransHdr, transProto); ok {
|
||||
if netHdr, transHdr, ok := pkt.GetEmbeddedNetAndTransHeaders(netLen, getNetAndTransHdr, transProto); ok {
|
||||
return tupleID{
|
||||
srcAddr: netHdr.DestinationAddress(),
|
||||
srcPortOrEchoRequestIdent: transHdr.DestinationPort(),
|
||||
|
|
@ -602,6 +579,11 @@ func (ct *ConnTrack) getConnAndUpdate(pkt *PacketBuffer, skipChecksumValidation
|
|||
return t
|
||||
}
|
||||
|
||||
// GetConnAndUpdatePkt gets the connection for the packet and also sets the packet's tuple.
|
||||
func (ct *ConnTrack) GetConnAndUpdatePkt(pkt *PacketBuffer, skipChecksumValidation bool) {
|
||||
pkt.tuple = ct.getConnAndUpdate(pkt, skipChecksumValidation)
|
||||
}
|
||||
|
||||
func (ct *ConnTrack) connForTID(tid tupleID) *tuple {
|
||||
ct.mu.RLock()
|
||||
bkt := &ct.buckets[ct.bucket(tid)]
|
||||
|
|
@ -610,6 +592,114 @@ func (ct *ConnTrack) connForTID(tid tupleID) *tuple {
|
|||
return bkt.connForTID(tid, ct.clock.NowMonotonic())
|
||||
}
|
||||
|
||||
// ConnTrackInfo holds connection tracking information for a packet.
|
||||
type ConnTrackInfo struct {
|
||||
State ConnTrackState
|
||||
Direction ConnTrackDirection
|
||||
SrcAddr tcpip.Address
|
||||
DstAddr tcpip.Address
|
||||
SrcPort uint16
|
||||
DstPort uint16
|
||||
NetProto tcpip.NetworkProtocolNumber
|
||||
TransProto tcpip.TransportProtocolNumber
|
||||
Expiration time.Duration
|
||||
PseudoID uint32
|
||||
Bytes uint64
|
||||
Packets uint64
|
||||
}
|
||||
|
||||
// ConnTrackInfoOpts holds options for GetConnTrackInfo.
|
||||
type ConnTrackInfoOpts struct {
|
||||
FillState bool
|
||||
UseReplyDir bool
|
||||
FillPseudoID bool
|
||||
FillExpiration bool
|
||||
}
|
||||
|
||||
// getTCPConnTrackState converts the TCB state to ConnTrackState.
|
||||
func (cn *conn) getTCPConnTrackState(useReplyDir bool) ConnTrackState {
|
||||
state := ConnTrackStateInvalid
|
||||
cn.stateMu.RLock()
|
||||
tcbState := cn.tcb.State()
|
||||
cn.stateMu.RUnlock()
|
||||
switch tcbState {
|
||||
case tcpconntrack.ResultConnecting:
|
||||
state = ConnTrackStateNew
|
||||
|
||||
case tcpconntrack.ResultAlive, tcpconntrack.ResultReset,
|
||||
tcpconntrack.ResultClosedByOriginator, tcpconntrack.ResultClosedByResponder:
|
||||
|
||||
if useReplyDir {
|
||||
state = ConnTrackStateEstablishedReply
|
||||
} else {
|
||||
state = ConnTrackStateEstablished
|
||||
}
|
||||
case tcpconntrack.ResultDrop:
|
||||
state = ConnTrackStateInvalid
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// getConnTrackState returns the connection tracking state for the connection.
|
||||
func (cn *conn) getConnTrackState(useReplyDir bool) ConnTrackState {
|
||||
state := ConnTrackStateInvalid
|
||||
// TCP connections have their own state machine in the TCB.
|
||||
if cn.original.tupleID.transProto == header.TCPProtocolNumber {
|
||||
return cn.getTCPConnTrackState(useReplyDir)
|
||||
}
|
||||
// For non-TCP connections, fill the info based on the reply.
|
||||
cn.stateMu.RLock()
|
||||
replySeen := cn.replySeen
|
||||
cn.stateMu.RUnlock()
|
||||
if useReplyDir {
|
||||
state = ConnTrackStateEstablishedReply
|
||||
} else if replySeen {
|
||||
state = ConnTrackStateEstablished
|
||||
} else {
|
||||
state = ConnTrackStateNew
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// FillConnTrackInfo fills connection tracking information for the connection.
|
||||
func (cn *conn) FillConnTrackInfo(opts ConnTrackInfoOpts, info *ConnTrackInfo) bool {
|
||||
state := ConnTrackStateInvalid
|
||||
if opts.FillState {
|
||||
state = cn.getConnTrackState(opts.UseReplyDir)
|
||||
}
|
||||
|
||||
dir := ConnTrackDirectionOriginal
|
||||
t := &cn.original
|
||||
if opts.UseReplyDir {
|
||||
t = &cn.reply
|
||||
dir = ConnTrackDirectionReply
|
||||
}
|
||||
tID := t.tupleID
|
||||
|
||||
pID := uint32(0)
|
||||
if opts.FillPseudoID {
|
||||
// Generate a pseudo-ID similar to Linux nf_ct_get_id
|
||||
pID = tupleHash(cn.original.tupleID, cn.ct.nftIDSeed)
|
||||
}
|
||||
|
||||
var expires time.Duration
|
||||
if opts.FillExpiration {
|
||||
expires = cn.expiresIn()
|
||||
}
|
||||
|
||||
info.State = state
|
||||
info.Direction = dir
|
||||
info.SrcAddr = tID.srcAddr
|
||||
info.DstAddr = tID.dstAddr
|
||||
info.SrcPort = tID.srcPortOrEchoRequestIdent
|
||||
info.DstPort = tID.dstPortOrEchoReplyIdent
|
||||
info.NetProto = tID.netProto
|
||||
info.TransProto = tID.transProto
|
||||
info.Expiration = expires
|
||||
info.PseudoID = pID
|
||||
return true
|
||||
}
|
||||
|
||||
func (bkt *bucket) connForTID(tid tupleID, now tcpip.MonotonicTime) *tuple {
|
||||
bkt.mu.RLock()
|
||||
defer bkt.mu.RUnlock()
|
||||
|
|
@ -697,325 +787,14 @@ func (cn *conn) finalize() bool {
|
|||
}
|
||||
}
|
||||
|
||||
// If NAT has not been configured for this connection, either mark the
|
||||
// connection as configured for "no-op NAT", in the case of DNAT, or, in the
|
||||
// case of SNAT, perform source port remapping so that source ports used by
|
||||
// locally-generated traffic do not conflict with ports occupied by existing NAT
|
||||
// bindings.
|
||||
//
|
||||
// Note that in the typical case this is also a no-op, because `snatAction`
|
||||
// will do nothing if the original tuple is already unique.
|
||||
func (cn *conn) maybePerformNoopNAT(pkt *PacketBuffer, hook Hook, r *Route, dnat bool) {
|
||||
cn.mu.Lock()
|
||||
var manip *manipType
|
||||
if dnat {
|
||||
manip = &cn.destinationManip
|
||||
} else {
|
||||
manip = &cn.sourceManip
|
||||
}
|
||||
if *manip != manipNotPerformed {
|
||||
cn.mu.Unlock()
|
||||
_ = cn.handlePacket(pkt, hook, r)
|
||||
return
|
||||
}
|
||||
if dnat {
|
||||
*manip = manipPerformedNoop
|
||||
cn.mu.Unlock()
|
||||
_ = cn.handlePacket(pkt, hook, r)
|
||||
return
|
||||
}
|
||||
cn.mu.Unlock()
|
||||
|
||||
// At this point, we know that NAT has not yet been performed on this
|
||||
// connection, and the DNAT case has been handled with a no-op. For SNAT, we
|
||||
// simply perform source port remapping to ensure that source ports for
|
||||
// locally generated traffic do not clash with ports used by existing NAT
|
||||
// bindings.
|
||||
_, _ = snatAction(pkt, hook, r, 0, tcpip.Address{}, true /* changePort */, false /* changeAddress */)
|
||||
}
|
||||
|
||||
type portOrIdentRange struct {
|
||||
start uint16
|
||||
size uint32
|
||||
}
|
||||
|
||||
// performNAT setups up the connection for the specified NAT and rewrites the
|
||||
// packet.
|
||||
//
|
||||
// If NAT has already been performed on the connection, then the packet will
|
||||
// be rewritten with the NAT performed on the connection, ignoring the passed
|
||||
// address and port range.
|
||||
//
|
||||
// Generally, only the first packet of a connection reaches this method; other
|
||||
// packets will be manipulated without needing to modify the connection.
|
||||
func (cn *conn) performNAT(pkt *PacketBuffer, hook Hook, r *Route, portsOrIdents portOrIdentRange, natAddress tcpip.Address, dnat, changePort, changeAddress bool) {
|
||||
lastPortOrIdent := func() uint16 {
|
||||
lastPortOrIdent := uint32(portsOrIdents.start) + portsOrIdents.size - 1
|
||||
if lastPortOrIdent > math.MaxUint16 {
|
||||
panic(fmt.Sprintf("got lastPortOrIdent = %d, want <= MaxUint16(=%d); portsOrIdents=%#v", lastPortOrIdent, math.MaxUint16, portsOrIdents))
|
||||
}
|
||||
return uint16(lastPortOrIdent)
|
||||
}()
|
||||
|
||||
// Make sure the packet is re-written after performing NAT.
|
||||
defer func() {
|
||||
// handlePacket returns true if the packet may skip the NAT table as the
|
||||
// connection is already NATed, but if we reach this point we must be in the
|
||||
// NAT table, so the return value is useless for us.
|
||||
_ = cn.handlePacket(pkt, hook, r)
|
||||
}()
|
||||
|
||||
cn.mu.Lock()
|
||||
defer cn.mu.Unlock()
|
||||
|
||||
var manip *manipType
|
||||
var address *tcpip.Address
|
||||
var portOrIdent *uint16
|
||||
if dnat {
|
||||
manip = &cn.destinationManip
|
||||
address = &cn.reply.tupleID.srcAddr
|
||||
portOrIdent = &cn.reply.tupleID.srcPortOrEchoRequestIdent
|
||||
} else {
|
||||
manip = &cn.sourceManip
|
||||
address = &cn.reply.tupleID.dstAddr
|
||||
portOrIdent = &cn.reply.tupleID.dstPortOrEchoReplyIdent
|
||||
}
|
||||
|
||||
if *manip != manipNotPerformed {
|
||||
return
|
||||
}
|
||||
*manip = manipPerformed
|
||||
if changeAddress {
|
||||
*address = natAddress
|
||||
}
|
||||
|
||||
// Everything below here is port-fiddling.
|
||||
if !changePort {
|
||||
return
|
||||
}
|
||||
|
||||
// Does the current port/ident fit in the range?
|
||||
if portsOrIdents.start <= *portOrIdent && *portOrIdent <= lastPortOrIdent {
|
||||
// Yes, is the current reply tuple unique?
|
||||
//
|
||||
// Or, does the reply tuple refer to the same connection as the current one that
|
||||
// we are NATing? This would apply, for example, to a self-connected socket,
|
||||
// where the original and reply tuples are identical.
|
||||
other := cn.ct.connForTID(cn.reply.tupleID)
|
||||
if other == nil || other.conn == cn {
|
||||
// Yes! No need to change the port.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Try our best to find a port/ident that results in a unique reply tuple.
|
||||
//
|
||||
// We limit the number of attempts to find a unique tuple to not waste a lot
|
||||
// of time looking for a unique tuple.
|
||||
//
|
||||
// Matches linux behaviour introduced in
|
||||
// https://github.com/torvalds/linux/commit/a504b703bb1da526a01593da0e4be2af9d9f5fa8.
|
||||
const maxAttemptsForInitialRound uint32 = 128
|
||||
const minAttemptsToContinue = 16
|
||||
|
||||
allowedInitialAttempts := maxAttemptsForInitialRound
|
||||
if allowedInitialAttempts > portsOrIdents.size {
|
||||
allowedInitialAttempts = portsOrIdents.size
|
||||
}
|
||||
|
||||
for maxAttempts := allowedInitialAttempts; ; maxAttempts /= 2 {
|
||||
// Start reach round with a random initial port/ident offset.
|
||||
randOffset := cn.ct.rand.Uint32()
|
||||
|
||||
for i := uint32(0); i < maxAttempts; i++ {
|
||||
newPortOrIdentU32 := uint32(portsOrIdents.start) + (randOffset+i)%portsOrIdents.size
|
||||
if newPortOrIdentU32 > math.MaxUint16 {
|
||||
panic(fmt.Sprintf("got newPortOrIdentU32 = %d, want <= MaxUint16(=%d); portsOrIdents=%#v, randOffset=%d", newPortOrIdentU32, math.MaxUint16, portsOrIdents, randOffset))
|
||||
}
|
||||
|
||||
*portOrIdent = uint16(newPortOrIdentU32)
|
||||
|
||||
if other := cn.ct.connForTID(cn.reply.tupleID); other == nil {
|
||||
// We found a unique tuple!
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if maxAttempts == portsOrIdents.size {
|
||||
// We already tried all the ports/idents in the range so no need to keep
|
||||
// trying.
|
||||
return
|
||||
}
|
||||
|
||||
if maxAttempts < minAttemptsToContinue {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// We did not find a unique tuple, use the last used port anyways.
|
||||
// TODO(https://gvisor.dev/issue/6850): Handle not finding a unique tuple
|
||||
// better (e.g. remove the connection and drop the packet).
|
||||
}
|
||||
|
||||
// handlePacket attempts to handle a packet and perform NAT if the connection
|
||||
// has had NAT performed on it.
|
||||
//
|
||||
// Returns true if the packet can skip the NAT table.
|
||||
func (cn *conn) handlePacket(pkt *PacketBuffer, hook Hook, rt *Route) bool {
|
||||
netHdr, transHdr, isICMPError, ok := getHeaders(pkt)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
fullChecksum := false
|
||||
updatePseudoHeader := false
|
||||
natDone := &pkt.snatDone
|
||||
dnat := false
|
||||
switch hook {
|
||||
case Prerouting:
|
||||
// Packet came from outside the stack so it must have a checksum set
|
||||
// already.
|
||||
fullChecksum = true
|
||||
updatePseudoHeader = true
|
||||
|
||||
natDone = &pkt.dnatDone
|
||||
dnat = true
|
||||
case Input:
|
||||
case Forward:
|
||||
panic("should not handle packet in the forwarding hook")
|
||||
case Output:
|
||||
natDone = &pkt.dnatDone
|
||||
dnat = true
|
||||
fallthrough
|
||||
case Postrouting:
|
||||
if pkt.TransportProtocolNumber == header.TCPProtocolNumber && pkt.GSOOptions.Type != GSONone && pkt.GSOOptions.NeedsCsum {
|
||||
updatePseudoHeader = true
|
||||
} else if rt.RequiresTXTransportChecksum() {
|
||||
fullChecksum = true
|
||||
updatePseudoHeader = true
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized hook = %d", hook))
|
||||
}
|
||||
|
||||
if *natDone {
|
||||
panic(fmt.Sprintf("packet already had NAT(dnat=%t) performed at hook=%s; pkt=%#v", dnat, hook, pkt))
|
||||
}
|
||||
|
||||
// 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
|
||||
// packets are fragmented.
|
||||
|
||||
reply := pkt.tuple.reply
|
||||
|
||||
tid, manip := func() (tupleID, manipType) {
|
||||
cn.mu.RLock()
|
||||
defer cn.mu.RUnlock()
|
||||
|
||||
if reply {
|
||||
tid := cn.original.tupleID
|
||||
|
||||
if dnat {
|
||||
return tid, cn.sourceManip
|
||||
}
|
||||
return tid, cn.destinationManip
|
||||
}
|
||||
|
||||
tid := cn.reply.tupleID
|
||||
if dnat {
|
||||
return tid, cn.destinationManip
|
||||
}
|
||||
return tid, cn.sourceManip
|
||||
}()
|
||||
switch manip {
|
||||
case manipNotPerformed:
|
||||
return false
|
||||
case manipPerformedNoop:
|
||||
*natDone = true
|
||||
return true
|
||||
case manipPerformed:
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled manip = %d", manip))
|
||||
}
|
||||
|
||||
newPort := tid.dstPortOrEchoReplyIdent
|
||||
newAddr := tid.dstAddr
|
||||
if dnat {
|
||||
newPort = tid.srcPortOrEchoRequestIdent
|
||||
newAddr = tid.srcAddr
|
||||
}
|
||||
|
||||
rewritePacket(
|
||||
netHdr,
|
||||
transHdr,
|
||||
!dnat != isICMPError,
|
||||
fullChecksum,
|
||||
updatePseudoHeader,
|
||||
newPort,
|
||||
newAddr,
|
||||
)
|
||||
|
||||
*natDone = true
|
||||
|
||||
if !isICMPError {
|
||||
return true
|
||||
}
|
||||
|
||||
// We performed NAT on (erroneous) packet that triggered an ICMP response, but
|
||||
// not the ICMP packet itself.
|
||||
switch pkt.TransportProtocolNumber {
|
||||
case header.ICMPv4ProtocolNumber:
|
||||
icmp := header.ICMPv4(pkt.TransportHeader().Slice())
|
||||
// TODO(https://gvisor.dev/issue/6788): Incrementally update ICMP checksum.
|
||||
icmp.SetChecksum(0)
|
||||
icmp.SetChecksum(header.ICMPv4Checksum(icmp, pkt.Data().Checksum()))
|
||||
|
||||
network := header.IPv4(pkt.NetworkHeader().Slice())
|
||||
if dnat {
|
||||
network.SetDestinationAddressWithChecksumUpdate(tid.srcAddr)
|
||||
} else {
|
||||
network.SetSourceAddressWithChecksumUpdate(tid.dstAddr)
|
||||
}
|
||||
case header.ICMPv6ProtocolNumber:
|
||||
network := header.IPv6(pkt.NetworkHeader().Slice())
|
||||
srcAddr := network.SourceAddress()
|
||||
dstAddr := network.DestinationAddress()
|
||||
if dnat {
|
||||
dstAddr = tid.srcAddr
|
||||
} else {
|
||||
srcAddr = tid.dstAddr
|
||||
}
|
||||
|
||||
icmp := header.ICMPv6(pkt.TransportHeader().Slice())
|
||||
// TODO(https://gvisor.dev/issue/6788): Incrementally update ICMP checksum.
|
||||
icmp.SetChecksum(0)
|
||||
payload := pkt.Data()
|
||||
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||
Header: icmp,
|
||||
Src: srcAddr,
|
||||
Dst: dstAddr,
|
||||
PayloadCsum: payload.Checksum(),
|
||||
PayloadLen: payload.Size(),
|
||||
}))
|
||||
|
||||
if dnat {
|
||||
network.SetDestinationAddress(dstAddr)
|
||||
} else {
|
||||
network.SetSourceAddress(srcAddr)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// bucket gets the conntrack bucket for a tupleID.
|
||||
// +checklocksread:ct.mu
|
||||
func (ct *ConnTrack) bucket(id tupleID) int {
|
||||
return ct.bucketWithTableLength(id, len(ct.buckets))
|
||||
}
|
||||
|
||||
func (ct *ConnTrack) bucketWithTableLength(id tupleID, tableLength int) int {
|
||||
h := jenkins.Sum32(ct.seed)
|
||||
func tupleHash(id tupleID, seed uint32) uint32 {
|
||||
h := jenkins.Sum32(seed)
|
||||
h.Write(id.srcAddr.AsSlice())
|
||||
h.Write(id.dstAddr.AsSlice())
|
||||
shortBuf := make([]byte, 2)
|
||||
|
|
@ -1027,7 +806,12 @@ func (ct *ConnTrack) bucketWithTableLength(id tupleID, tableLength int) int {
|
|||
h.Write([]byte(shortBuf))
|
||||
binary.LittleEndian.PutUint16(shortBuf, uint16(id.netProto))
|
||||
h.Write([]byte(shortBuf))
|
||||
return int(h.Sum32()) % tableLength
|
||||
return h.Sum32()
|
||||
}
|
||||
|
||||
func (ct *ConnTrack) bucketWithTableLength(id tupleID, tableLength int) int {
|
||||
h := tupleHash(id, ct.seed)
|
||||
return int(h) % tableLength
|
||||
}
|
||||
|
||||
// reapUnused deletes timed out entries from the conntrack map. The rules for
|
||||
|
|
@ -1167,3 +951,53 @@ func (ct *ConnTrack) originalDst(epID TransportEndpointID, netProto tcpip.Networ
|
|||
id := t.conn.original.tupleID
|
||||
return id.dstAddr, id.dstPortOrEchoReplyIdent, nil
|
||||
}
|
||||
|
||||
// NewConnTrack creates and initializes a new ConnTrack object.
|
||||
func NewConnTrack(clock tcpip.Clock, rng connTrackRNG, seed *uint32) *ConnTrack {
|
||||
if seed == nil {
|
||||
r := rng.Uint32()
|
||||
seed = &r
|
||||
}
|
||||
ct := &ConnTrack{
|
||||
clock: clock,
|
||||
rng: rng,
|
||||
seed: *seed,
|
||||
nftIDSeed: rng.Uint32(),
|
||||
}
|
||||
ct.init()
|
||||
return ct
|
||||
}
|
||||
|
||||
// NewConnTrackWithReaper creates and initializes a new ConnTrack and reaper.
|
||||
// Reaper garbage collects unused connections.
|
||||
func NewConnTrackWithReaper(clock tcpip.Clock, rng connTrackRNG, seed *uint32) (*ConnTrack, tcpip.Timer) {
|
||||
ct := NewConnTrack(clock, rng, seed)
|
||||
var reaper tcpip.Timer
|
||||
bucket := 0
|
||||
interval := 1 * time.Second
|
||||
reaper = ct.clock.AfterFunc(interval, func() {
|
||||
bucket, interval = ct.reapUnused(bucket, interval)
|
||||
reaper.Reset(interval)
|
||||
})
|
||||
return ct, reaper
|
||||
}
|
||||
|
||||
// NfConnTrackPriority returns the priority of the conntrack hook.
|
||||
// Check `ipv4/ipv6_conntrack_ops` in nf_conntrack_proto.c.
|
||||
func NfConnTrackPriority(hook NFHook) (int, bool) {
|
||||
switch hook {
|
||||
case NFPrerouting:
|
||||
// NF_IP_PRI_CONNTRACK
|
||||
return -200, true
|
||||
case NFInput:
|
||||
// NF_IP_PRI_CONNTRACK_CONFIRM
|
||||
return math.MaxInt32, true
|
||||
case NFPostrouting:
|
||||
// NF_IP_PRI_CONNTRACK_CONFIRM
|
||||
return math.MaxInt32, true
|
||||
case NFOutput:
|
||||
// NF_IP_PRI_CONNTRACK
|
||||
return -200, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func endpointsByNICinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
endpointsByNICinitLockNames()
|
||||
endpointsByNICprefixIndex = locking.NewMutexClass(reflect.TypeOf(endpointsByNICRWMutex{}), endpointsByNIClockNames)
|
||||
endpointsByNICprefixIndex = locking.NewMutexClass(reflect.TypeFor[endpointsByNICRWMutex](), endpointsByNIClockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func _() {
|
|||
|
||||
const _headerType_name = "virtioNetHeaderlinkHeadernetworkHeadertransportHeadernumHeaderType"
|
||||
|
||||
var _headerType_index = [...]uint8{0, 10, 23, 38, 51}
|
||||
var _headerType_index = [...]uint8{0, 15, 25, 38, 53, 66}
|
||||
|
||||
func (i headerType) String() string {
|
||||
if i < 0 || i >= headerType(len(_headerType_index)-1) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
package stack
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
|
@ -34,9 +36,15 @@ const (
|
|||
//
|
||||
// +stateify savable
|
||||
type ICMPRateLimiter struct {
|
||||
// TODO(b/341946753): Restore when netstack is savable.
|
||||
limiter *rate.Limiter `state:"nosave"`
|
||||
clock tcpip.Clock
|
||||
limit rate.Limit
|
||||
burst int
|
||||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (l *ICMPRateLimiter) afterLoad(context.Context) {
|
||||
l.limiter = rate.NewLimiter(l.limit, l.burst)
|
||||
}
|
||||
|
||||
// NewICMPRateLimiter returns a global rate limiter for controlling the rate
|
||||
|
|
@ -46,11 +54,14 @@ func NewICMPRateLimiter(clock tcpip.Clock) *ICMPRateLimiter {
|
|||
return &ICMPRateLimiter{
|
||||
clock: clock,
|
||||
limiter: rate.NewLimiter(icmpLimit, icmpBurst),
|
||||
limit: icmpLimit,
|
||||
burst: icmpBurst,
|
||||
}
|
||||
}
|
||||
|
||||
// SetLimit sets a new Limit for the limiter.
|
||||
func (l *ICMPRateLimiter) SetLimit(limit rate.Limit) {
|
||||
l.limit = limit
|
||||
l.limiter.SetLimitAt(l.clock.Now(), limit)
|
||||
}
|
||||
|
||||
|
|
@ -61,6 +72,7 @@ func (l *ICMPRateLimiter) Limit() rate.Limit {
|
|||
|
||||
// SetBurst sets a new burst size for the limiter.
|
||||
func (l *ICMPRateLimiter) SetBurst(burst int) {
|
||||
l.burst = burst
|
||||
l.limiter.SetBurstAt(l.clock.Now(), burst)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const (
|
|||
NATID TableID = iota
|
||||
MangleID
|
||||
FilterID
|
||||
RawID
|
||||
NumTables
|
||||
)
|
||||
|
||||
|
|
@ -111,6 +112,27 @@ func DefaultTables(clock tcpip.Clock, rand *rand.Rand) *IPTables {
|
|||
Postrouting: HookUnset,
|
||||
},
|
||||
},
|
||||
RawID: {
|
||||
Rules: []Rule{
|
||||
{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
{Filter: EmptyFilter4(), Target: &ErrorTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
|
||||
},
|
||||
BuiltinChains: [NumHooks]int{
|
||||
Prerouting: 0,
|
||||
Input: HookUnset,
|
||||
Forward: HookUnset,
|
||||
Output: 1,
|
||||
Postrouting: HookUnset,
|
||||
},
|
||||
Underflows: [NumHooks]int{
|
||||
Prerouting: 0,
|
||||
Input: HookUnset,
|
||||
Forward: HookUnset,
|
||||
Output: 1,
|
||||
Postrouting: HookUnset,
|
||||
},
|
||||
},
|
||||
},
|
||||
v6Tables: [NumTables]Table{
|
||||
NATID: {
|
||||
|
|
@ -176,11 +198,32 @@ func DefaultTables(clock tcpip.Clock, rand *rand.Rand) *IPTables {
|
|||
Postrouting: HookUnset,
|
||||
},
|
||||
},
|
||||
RawID: {
|
||||
Rules: []Rule{
|
||||
{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
{Filter: EmptyFilter6(), Target: &ErrorTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
|
||||
},
|
||||
BuiltinChains: [NumHooks]int{
|
||||
Prerouting: 0,
|
||||
Input: HookUnset,
|
||||
Forward: HookUnset,
|
||||
Output: 1,
|
||||
Postrouting: HookUnset,
|
||||
},
|
||||
Underflows: [NumHooks]int{
|
||||
Prerouting: 0,
|
||||
Input: HookUnset,
|
||||
Forward: HookUnset,
|
||||
Output: 1,
|
||||
Postrouting: HookUnset,
|
||||
},
|
||||
},
|
||||
},
|
||||
connections: ConnTrack{
|
||||
seed: rand.Uint32(),
|
||||
clock: clock,
|
||||
rand: rand,
|
||||
rng: rand,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -215,6 +258,24 @@ func EmptyNATTable() Table {
|
|||
}
|
||||
}
|
||||
|
||||
// EmptyRawTable returns a Table with no rules and only the Prerouting and
|
||||
// Output hooks set, matching the Linux raw table's valid hooks.
|
||||
func EmptyRawTable() Table {
|
||||
return Table{
|
||||
Rules: []Rule{},
|
||||
BuiltinChains: [NumHooks]int{
|
||||
Input: HookUnset,
|
||||
Forward: HookUnset,
|
||||
Postrouting: HookUnset,
|
||||
},
|
||||
Underflows: [NumHooks]int{
|
||||
Input: HookUnset,
|
||||
Forward: HookUnset,
|
||||
Postrouting: HookUnset,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetTable returns a table with the given id and IP version. It panics when an
|
||||
// invalid id is provided.
|
||||
func (it *IPTables) GetTable(id TableID, ipv6 bool) Table {
|
||||
|
|
@ -338,6 +399,10 @@ func (it *IPTables) shouldSkipOrPopulateTables(tables []checkTable, pkt *PacketB
|
|||
// +checkescape
|
||||
func (it *IPTables) CheckPrerouting(pkt *PacketBuffer, addressEP AddressableEndpoint, inNicName string) bool {
|
||||
tables := [...]checkTable{ // escapes: on arm this causes an allocation.
|
||||
{
|
||||
fn: check,
|
||||
tableID: RawID,
|
||||
},
|
||||
{
|
||||
fn: check,
|
||||
tableID: MangleID,
|
||||
|
|
@ -448,6 +513,10 @@ func (it *IPTables) CheckForward(pkt *PacketBuffer, inNicName, outNicName string
|
|||
// +checkescape
|
||||
func (it *IPTables) CheckOutput(pkt *PacketBuffer, r *Route, outNicName string) bool {
|
||||
tables := [...]checkTable{ // escapes: on arm this causes an allocation.
|
||||
{
|
||||
fn: check,
|
||||
tableID: RawID,
|
||||
},
|
||||
{
|
||||
fn: check,
|
||||
tableID: MangleID,
|
||||
|
|
@ -532,7 +601,7 @@ func checkNAT(it *IPTables, table Table, hook Hook, pkt *PacketBuffer, r *Route,
|
|||
// See check.
|
||||
func (it *IPTables) checkNAT(table Table, hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
|
||||
t := pkt.tuple
|
||||
if t != nil && t.conn.handlePacket(pkt, hook, r) {
|
||||
if t != nil && IPTHandlePacket(pkt, hook, r) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -561,7 +630,7 @@ func (it *IPTables) checkNAT(table Table, hook Hook, pkt *PacketBuffer, r *Route
|
|||
//
|
||||
// If the packet was already NATed, the connection must be NATed.
|
||||
if !natDone {
|
||||
t.conn.maybePerformNoopNAT(pkt, hook, r, dnat)
|
||||
IPTMaybePerformNoopNAT(pkt, hook, r, dnat)
|
||||
}
|
||||
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func ipTablesinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
ipTablesinitLockNames()
|
||||
ipTablesprefixIndex = locking.NewMutexClass(reflect.TypeOf(ipTablesRWMutex{}), ipTableslockNames)
|
||||
ipTablesprefixIndex = locking.NewMutexClass(reflect.TypeFor[ipTablesRWMutex](), ipTableslockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,8 +64,11 @@ const (
|
|||
RejectIPv4WithICMPNetUnreachable
|
||||
RejectIPv4WithICMPHostUnreachable
|
||||
RejectIPv4WithICMPPortUnreachable
|
||||
RejectIPv4WithICMPProtUnreachable
|
||||
RejectIPv4WithICMPEchoReply
|
||||
RejectIPv4WithICMPNetProhibited
|
||||
RejectIPv4WithICMPHostProhibited
|
||||
RejectIPv4WithTCPReset
|
||||
RejectIPv4WithICMPAdminProhibited
|
||||
)
|
||||
|
||||
|
|
@ -106,9 +109,14 @@ type RejectIPv6WithICMPType int
|
|||
const (
|
||||
_ RejectIPv6WithICMPType = iota
|
||||
RejectIPv6WithICMPNoRoute
|
||||
RejectIPv6WithICMPAdminProhibited
|
||||
RejectIPv6WithICMPNotNeighbour
|
||||
RejectIPv6WithICMPAddrUnreachable
|
||||
RejectIPv6WithICMPPortUnreachable
|
||||
RejectIPv6WithICMPAdminProhibited
|
||||
RejectIPv6WithICMPEchoReply
|
||||
RejectIPv6WithTCPReset
|
||||
RejectIPv6WithICMPPolicyFail
|
||||
RejectIPv6WithICMPRejectRoute
|
||||
)
|
||||
|
||||
// RejectIPv6Target drops packets and sends back an error packet in response to the
|
||||
|
|
@ -297,10 +305,10 @@ type SNATTarget struct {
|
|||
}
|
||||
|
||||
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)
|
||||
return natAction(pkt, hook, r, PortOrIdentRange{Start: port, Size: 1}, address, true /* dnat */, changePort, changeAddress)
|
||||
}
|
||||
|
||||
func targetPortRangeForTCPAndUDP(originalSrcPort uint16) portOrIdentRange {
|
||||
func targetPortRangeForTCPAndUDP(originalSrcPort uint16) PortOrIdentRange {
|
||||
// As per iptables(8),
|
||||
//
|
||||
// If no port range is specified, then source ports below 512 will be
|
||||
|
|
@ -309,16 +317,16 @@ func targetPortRangeForTCPAndUDP(originalSrcPort uint16) portOrIdentRange {
|
|||
// 1024 or above.
|
||||
switch {
|
||||
case originalSrcPort < 512:
|
||||
return portOrIdentRange{start: 1, size: 511}
|
||||
return PortOrIdentRange{Start: 1, Size: 511}
|
||||
case originalSrcPort < 1024:
|
||||
return portOrIdentRange{start: 1, size: 1023}
|
||||
return PortOrIdentRange{Start: 1, Size: 1023}
|
||||
default:
|
||||
return portOrIdentRange{start: 1024, size: math.MaxUint16 - 1023}
|
||||
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}
|
||||
portsOrIdents := PortOrIdentRange{Start: port, Size: 1}
|
||||
|
||||
switch pkt.TransportProtocolNumber {
|
||||
case header.UDPProtocolNumber:
|
||||
|
|
@ -334,20 +342,20 @@ func snatAction(pkt *PacketBuffer, hook Hook, r *Route, port uint16, address tcp
|
|||
// behaviour.
|
||||
//
|
||||
// https://github.com/torvalds/linux/blob/58e1100fdc5990b0cc0d4beaf2562a92e621ac7d/net/netfilter/nf_nat_core.c#L391
|
||||
portsOrIdents = portOrIdentRange{start: 0, size: math.MaxUint16 + 1}
|
||||
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) {
|
||||
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 {
|
||||
t.conn.performNAT(pkt, hook, r, portsOrIdents, address, dnat, changePort, changeAddress)
|
||||
IPTPerformNAT(pkt, hook, r, portsOrIdents, address, dnat, changePort, changeAddress)
|
||||
return RuleAccept, 0
|
||||
}
|
||||
|
||||
|
|
@ -412,81 +420,22 @@ func (mt *MasqueradeTarget) Action(pkt *PacketBuffer, hook Hook, r *Route, addre
|
|||
return snatAction(pkt, hook, r, 0 /* port */, address, true /* changePort */, true /* changeAddress */)
|
||||
}
|
||||
|
||||
func rewritePacket(n header.Network, t header.Transport, updateSRCFields, fullChecksum, updatePseudoHeader bool, newPortOrIdent uint16, newAddr tcpip.Address) {
|
||||
switch t := t.(type) {
|
||||
case header.ChecksummableTransport:
|
||||
if updateSRCFields {
|
||||
if fullChecksum {
|
||||
t.SetSourcePortWithChecksumUpdate(newPortOrIdent)
|
||||
} else {
|
||||
t.SetSourcePort(newPortOrIdent)
|
||||
}
|
||||
} else {
|
||||
if fullChecksum {
|
||||
t.SetDestinationPortWithChecksumUpdate(newPortOrIdent)
|
||||
} else {
|
||||
t.SetDestinationPort(newPortOrIdent)
|
||||
}
|
||||
}
|
||||
// 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
|
||||
|
||||
if updatePseudoHeader {
|
||||
var oldAddr tcpip.Address
|
||||
if updateSRCFields {
|
||||
oldAddr = n.SourceAddress()
|
||||
} else {
|
||||
oldAddr = n.DestinationAddress()
|
||||
}
|
||||
|
||||
t.UpdateChecksumPseudoHeaderAddress(oldAddr, newAddr, fullChecksum)
|
||||
}
|
||||
case header.ICMPv4:
|
||||
switch icmpType := t.Type(); icmpType {
|
||||
case header.ICMPv4Echo:
|
||||
if updateSRCFields {
|
||||
t.SetIdentWithChecksumUpdate(newPortOrIdent)
|
||||
}
|
||||
case header.ICMPv4EchoReply:
|
||||
if !updateSRCFields {
|
||||
t.SetIdentWithChecksumUpdate(newPortOrIdent)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
|
||||
}
|
||||
case header.ICMPv6:
|
||||
switch icmpType := t.Type(); icmpType {
|
||||
case header.ICMPv6EchoRequest:
|
||||
if updateSRCFields {
|
||||
t.SetIdentWithChecksumUpdate(newPortOrIdent)
|
||||
}
|
||||
case header.ICMPv6EchoReply:
|
||||
if !updateSRCFields {
|
||||
t.SetIdentWithChecksumUpdate(newPortOrIdent)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
|
||||
}
|
||||
|
||||
var oldAddr tcpip.Address
|
||||
if updateSRCFields {
|
||||
oldAddr = n.SourceAddress()
|
||||
} else {
|
||||
oldAddr = n.DestinationAddress()
|
||||
}
|
||||
|
||||
t.UpdateChecksumPseudoHeaderAddress(oldAddr, newAddr)
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled transport = %#v", t))
|
||||
}
|
||||
|
||||
if checksummableNetHeader, ok := n.(header.ChecksummableNetwork); ok {
|
||||
if updateSRCFields {
|
||||
checksummableNetHeader.SetSourceAddressWithChecksumUpdate(newAddr)
|
||||
} else {
|
||||
checksummableNetHeader.SetDestinationAddressWithChecksumUpdate(newAddr)
|
||||
}
|
||||
} else if updateSRCFields {
|
||||
n.SetSourceAddress(newAddr)
|
||||
} else {
|
||||
n.SetDestinationAddress(newAddr)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const (
|
|||
type IPTables struct {
|
||||
connections ConnTrack
|
||||
|
||||
reaper tcpip.Timer
|
||||
reaper tcpip.Timer `state:"nosave"`
|
||||
|
||||
mu ipTablesRWMutex `state:"nosave"`
|
||||
// v4Tables and v6tables map tableIDs to tables. They hold builtin
|
||||
|
|
@ -283,7 +283,9 @@ func (fl IPHeaderFilter) match(pkt *PacketBuffer, hook Hook, inNicName, outNicNa
|
|||
|
||||
case header.IPv6ProtocolNumber:
|
||||
hdr := header.IPv6(pkt.NetworkHeader().Slice())
|
||||
transProto = hdr.TransportProtocol()
|
||||
// The transport protocol may be preceded by IPv6 extension headers, so
|
||||
// use the protocol from parsing (see IPv6.TransportProtocol).
|
||||
transProto = pkt.TransportProtocolNumber
|
||||
dstAddr = hdr.DestinationAddress()
|
||||
srcAddr = hdr.SourceAddress()
|
||||
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func multiPortEndpointinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
multiPortEndpointinitLockNames()
|
||||
multiPortEndpointprefixIndex = locking.NewMutexClass(reflect.TypeOf(multiPortEndpointRWMutex{}), multiPortEndpointlockNames)
|
||||
multiPortEndpointprefixIndex = locking.NewMutexClass(reflect.TypeFor[multiPortEndpointRWMutex](), multiPortEndpointlockNames)
|
||||
}
|
||||
|
|
|
|||
581
pkg/tcpip/stack/nat.go
Normal file
581
pkg/tcpip/stack/nat.go
Normal file
|
|
@ -0,0 +1,581 @@
|
|||
// Copyright 2020 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"
|
||||
)
|
||||
|
||||
// NATType represents the type of NAT.
|
||||
type NATType int
|
||||
|
||||
const (
|
||||
// SNAT is source NAT.
|
||||
SNAT NATType = iota
|
||||
// DNAT is destination NAT.
|
||||
DNAT
|
||||
// NATUnknown is unknown NAT type.
|
||||
NATUnknown
|
||||
)
|
||||
|
||||
// ToNATType converts a uint8 to a NATType.
|
||||
func ToNATType(t uint8) NATType {
|
||||
switch t {
|
||||
case 0:
|
||||
return SNAT
|
||||
case 1:
|
||||
return DNAT
|
||||
}
|
||||
return NATUnknown
|
||||
}
|
||||
|
||||
func (natType NATType) String() string {
|
||||
switch natType {
|
||||
case SNAT:
|
||||
return "SNAT"
|
||||
case DNAT:
|
||||
return "DNAT"
|
||||
default:
|
||||
return "NATUnknown"
|
||||
}
|
||||
}
|
||||
|
||||
// NfNATPriority returns the priority of the NAT hook.
|
||||
// Check `ipv4/ipv6_nat_ops` in nf_nat_proto.c.
|
||||
func NfNATPriority(hook NFHook) (int, bool) {
|
||||
switch hook {
|
||||
case NFPrerouting:
|
||||
// NF_IP_PRI_NAT_DST
|
||||
return -100, true
|
||||
case NFPostrouting:
|
||||
// NF_IP_PRI_NAT_SRC
|
||||
return 100, true
|
||||
case NFOutput:
|
||||
// NF_IP_PRI_NAT_DST
|
||||
return -100, true
|
||||
case NFInput:
|
||||
// NF_IP_PRI_NAT_SRC
|
||||
return 100, true
|
||||
}
|
||||
// NAT is not supported for other hooks.
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// NfHookToNATType returns the applicable NAT type
|
||||
// for the given netfilter hook.
|
||||
func NfHookToNATType(hook NFHook) NATType {
|
||||
switch hook {
|
||||
case NFPrerouting, NFOutput:
|
||||
return DNAT
|
||||
case NFInput, NFPostrouting:
|
||||
return SNAT
|
||||
}
|
||||
return NATUnknown
|
||||
}
|
||||
|
||||
// handlePacketOpts contains the options for handlePacket.
|
||||
type handlePacketOpts struct {
|
||||
fullChecksum bool
|
||||
updatePseudoHeader bool
|
||||
natType NATType
|
||||
}
|
||||
|
||||
// handlePacket attempts to handle a packet and perform NAT if the connection
|
||||
// has had NAT performed on it.
|
||||
//
|
||||
// Returns true if the packet can skip the NAT table.
|
||||
func handlePacket(pkt *PacketBuffer, opts *handlePacketOpts) bool {
|
||||
if opts == nil || opts.natType == NATUnknown {
|
||||
return false
|
||||
}
|
||||
netHdr, transHdr, isICMPError, ok := pkt.GetHeaders()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
natDone := &pkt.snatDone
|
||||
dnat := false
|
||||
if opts.natType == DNAT {
|
||||
natDone = &pkt.dnatDone
|
||||
dnat = true
|
||||
}
|
||||
|
||||
if *natDone {
|
||||
panic(fmt.Sprintf("packet already had NAT: %s performed; pkt=%#v", opts.natType, pkt))
|
||||
}
|
||||
|
||||
// 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
|
||||
// packets are fragmented.
|
||||
|
||||
reply := pkt.tuple.reply
|
||||
cn := pkt.tuple.conn
|
||||
|
||||
tid, manip := func() (tupleID, manipType) {
|
||||
cn.mu.RLock()
|
||||
defer cn.mu.RUnlock()
|
||||
|
||||
if reply {
|
||||
tid := cn.original.tupleID
|
||||
|
||||
if dnat {
|
||||
return tid, cn.sourceManip
|
||||
}
|
||||
return tid, cn.destinationManip
|
||||
}
|
||||
|
||||
tid := cn.reply.tupleID
|
||||
if dnat {
|
||||
return tid, cn.destinationManip
|
||||
}
|
||||
return tid, cn.sourceManip
|
||||
}()
|
||||
switch manip {
|
||||
case manipNotPerformed:
|
||||
return false
|
||||
case manipPerformedNoop:
|
||||
*natDone = true
|
||||
return true
|
||||
case manipPerformed:
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled manip = %d", manip))
|
||||
}
|
||||
|
||||
newPort := tid.dstPortOrEchoReplyIdent
|
||||
newAddr := tid.dstAddr
|
||||
if dnat {
|
||||
newPort = tid.srcPortOrEchoRequestIdent
|
||||
newAddr = tid.srcAddr
|
||||
}
|
||||
|
||||
UpdateHeaders(
|
||||
netHdr,
|
||||
transHdr,
|
||||
!dnat != isICMPError,
|
||||
opts.fullChecksum,
|
||||
opts.updatePseudoHeader,
|
||||
newPort,
|
||||
newAddr,
|
||||
)
|
||||
|
||||
*natDone = true
|
||||
|
||||
if !isICMPError {
|
||||
return true
|
||||
}
|
||||
|
||||
// We performed NAT on (erroneous) packet that triggered an ICMP response, but
|
||||
// not the ICMP packet itself.
|
||||
switch pkt.TransportProtocolNumber {
|
||||
case header.ICMPv4ProtocolNumber:
|
||||
icmp := header.ICMPv4(pkt.TransportHeader().Slice())
|
||||
// TODO(https://gvisor.dev/issue/6788): Incrementally update ICMP checksum.
|
||||
icmp.SetChecksum(0)
|
||||
icmp.SetChecksum(header.ICMPv4Checksum(icmp, pkt.Data().Checksum()))
|
||||
|
||||
network := header.IPv4(pkt.NetworkHeader().Slice())
|
||||
if dnat {
|
||||
network.SetDestinationAddressWithChecksumUpdate(tid.srcAddr)
|
||||
} else {
|
||||
network.SetSourceAddressWithChecksumUpdate(tid.dstAddr)
|
||||
}
|
||||
case header.ICMPv6ProtocolNumber:
|
||||
network := header.IPv6(pkt.NetworkHeader().Slice())
|
||||
srcAddr := network.SourceAddress()
|
||||
dstAddr := network.DestinationAddress()
|
||||
if dnat {
|
||||
dstAddr = tid.srcAddr
|
||||
} else {
|
||||
srcAddr = tid.dstAddr
|
||||
}
|
||||
|
||||
icmp := header.ICMPv6(pkt.TransportHeader().Slice())
|
||||
// TODO(https://gvisor.dev/issue/6788): Incrementally update ICMP checksum.
|
||||
icmp.SetChecksum(0)
|
||||
payload := pkt.Data()
|
||||
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||
Header: icmp,
|
||||
Src: srcAddr,
|
||||
Dst: dstAddr,
|
||||
PayloadCsum: payload.Checksum(),
|
||||
PayloadLen: payload.Size(),
|
||||
}))
|
||||
|
||||
if dnat {
|
||||
network.SetDestinationAddress(dstAddr)
|
||||
} else {
|
||||
network.SetSourceAddress(srcAddr)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IPTHandlePacket handles and applies NAT to the packet if required.
|
||||
func IPTHandlePacket(pkt *PacketBuffer, hook Hook, r *Route) bool {
|
||||
opts := handlePacketOpts{
|
||||
fullChecksum: false,
|
||||
updatePseudoHeader: false,
|
||||
natType: SNAT,
|
||||
}
|
||||
requiresTXTransportChecksum := false
|
||||
if r != nil {
|
||||
requiresTXTransportChecksum = r.RequiresTXTransportChecksum()
|
||||
}
|
||||
switch hook {
|
||||
case Prerouting:
|
||||
opts.fullChecksum = true
|
||||
opts.updatePseudoHeader = true
|
||||
opts.natType = DNAT
|
||||
case Input:
|
||||
case Forward:
|
||||
panic("should not handle packet in the forwarding hook")
|
||||
case Output:
|
||||
opts.natType = DNAT
|
||||
fallthrough
|
||||
case Postrouting:
|
||||
if pkt.TransportProtocolNumber == header.TCPProtocolNumber && pkt.GSOOptions.Type != GSONone && pkt.GSOOptions.NeedsCsum {
|
||||
opts.updatePseudoHeader = true
|
||||
} else if requiresTXTransportChecksum {
|
||||
opts.fullChecksum = true
|
||||
opts.updatePseudoHeader = true
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized hook = %d", hook))
|
||||
}
|
||||
|
||||
return handlePacket(pkt, &opts)
|
||||
}
|
||||
|
||||
// PortOrIdentRange represents a range of ports or idents
|
||||
// range to use for NAT.
|
||||
type PortOrIdentRange struct {
|
||||
Start uint16
|
||||
Size uint32
|
||||
}
|
||||
|
||||
// ConfigureNAT setups up the connection for the specified NAT and rewrites the
|
||||
// packet.
|
||||
//
|
||||
// If NAT has already been performed on the connection, then the packet will
|
||||
// be rewritten with the NAT performed on the connection, ignoring the passed
|
||||
// address and port range.
|
||||
//
|
||||
// Generally, only the first packet of a connection reaches this method; other
|
||||
// packets will be manipulated without needing to modify the connection.
|
||||
//
|
||||
// Returns whether the NAT was configured or not.
|
||||
func (cn *conn) ConfigureNAT(portsOrIdents PortOrIdentRange, natAddress tcpip.Address, natType NATType, changePort, changeAddress bool) bool {
|
||||
lastPortOrIdentU32 := uint32(portsOrIdents.Start) + portsOrIdents.Size - 1
|
||||
if lastPortOrIdentU32 > math.MaxUint16 {
|
||||
log.Warningf("got lastPortOrIdent = %d, want <= MaxUint16(=%d); portsOrIdents=%#v", lastPortOrIdentU32, math.MaxUint16, portsOrIdents)
|
||||
return false
|
||||
}
|
||||
lastPortOrIdent := uint16(lastPortOrIdentU32)
|
||||
|
||||
cn.mu.Lock()
|
||||
defer cn.mu.Unlock()
|
||||
|
||||
var manip *manipType
|
||||
var address *tcpip.Address
|
||||
var portOrIdent *uint16
|
||||
if natType == DNAT {
|
||||
manip = &cn.destinationManip
|
||||
address = &cn.reply.tupleID.srcAddr
|
||||
portOrIdent = &cn.reply.tupleID.srcPortOrEchoRequestIdent
|
||||
} else {
|
||||
manip = &cn.sourceManip
|
||||
address = &cn.reply.tupleID.dstAddr
|
||||
portOrIdent = &cn.reply.tupleID.dstPortOrEchoReplyIdent
|
||||
}
|
||||
|
||||
if *manip != manipNotPerformed {
|
||||
return true
|
||||
}
|
||||
*manip = manipPerformed
|
||||
if changeAddress {
|
||||
*address = natAddress
|
||||
}
|
||||
|
||||
// Everything below here is port-fiddling.
|
||||
if !changePort {
|
||||
return true
|
||||
}
|
||||
|
||||
// Does the current port/ident fit in the range?
|
||||
if portsOrIdents.Start <= *portOrIdent && *portOrIdent <= lastPortOrIdent {
|
||||
// Yes, is the current reply tuple unique?
|
||||
//
|
||||
// Or, does the reply tuple refer to the same connection as the current one that
|
||||
// we are NATing? This would apply, for example, to a self-connected socket,
|
||||
// where the original and reply tuples are identical.
|
||||
other := cn.ct.connForTID(cn.reply.tupleID)
|
||||
if other == nil || other.conn == cn {
|
||||
// Yes! No need to change the port.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Try our best to find a port/ident that results in a unique reply tuple.
|
||||
//
|
||||
// We limit the number of attempts to find a unique tuple to not waste a lot
|
||||
// of time looking for a unique tuple.
|
||||
//
|
||||
// Matches linux behaviour introduced in
|
||||
// https://github.com/torvalds/linux/commit/a504b703bb1da526a01593da0e4be2af9d9f5fa8.
|
||||
const maxAttemptsForInitialRound uint32 = 128
|
||||
const minAttemptsToContinue = 16
|
||||
|
||||
allowedInitialAttempts := maxAttemptsForInitialRound
|
||||
if allowedInitialAttempts > portsOrIdents.Size {
|
||||
allowedInitialAttempts = portsOrIdents.Size
|
||||
}
|
||||
|
||||
for maxAttempts := allowedInitialAttempts; ; maxAttempts /= 2 {
|
||||
// Start reach round with a random initial port/ident offset.
|
||||
randOffset := cn.ct.rng.Uint32()
|
||||
|
||||
for i := uint32(0); i < maxAttempts; i++ {
|
||||
newPortOrIdentU32 := uint32(portsOrIdents.Start) + (randOffset+i)%portsOrIdents.Size
|
||||
if newPortOrIdentU32 > math.MaxUint16 {
|
||||
log.Warningf("got newPortOrIdentU32 = %d, want <= MaxUint16(=%d); portsOrIdents=%#v", newPortOrIdentU32, math.MaxUint16, portsOrIdents)
|
||||
continue
|
||||
}
|
||||
|
||||
*portOrIdent = uint16(newPortOrIdentU32)
|
||||
|
||||
if other := cn.ct.connForTID(cn.reply.tupleID); other == nil {
|
||||
// We found a unique tuple!
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if maxAttempts == portsOrIdents.Size {
|
||||
// We already tried all the ports/idents in the range so no need to keep
|
||||
// trying.
|
||||
return false
|
||||
}
|
||||
|
||||
if maxAttempts < minAttemptsToContinue {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// We did not find a unique tuple, use the last used port anyways.
|
||||
// TODO(https://gvisor.dev/issue/6850): Handle not finding a unique tuple
|
||||
// better (e.g. remove the connection and drop the packet).
|
||||
}
|
||||
|
||||
// IPTPerformNAT performs NAT on the packet and updates the connection.
|
||||
// Used by IPTables.
|
||||
func IPTPerformNAT(pkt *PacketBuffer, hook Hook, r *Route, portsOrIdents PortOrIdentRange, natAddress tcpip.Address, dnat, changePort, changeAddress bool) {
|
||||
// Make sure the packet is re-written after performing NAT.
|
||||
defer func() {
|
||||
// handlePacket returns true if the packet may skip the NAT table as the
|
||||
// connection is already NATed, but if we reach this point we must be in the
|
||||
// NAT table, so the return value is useless for us.
|
||||
_ = IPTHandlePacket(pkt, hook, r)
|
||||
}()
|
||||
cn := pkt.tuple.conn
|
||||
natType := SNAT
|
||||
if dnat {
|
||||
natType = DNAT
|
||||
}
|
||||
_ = cn.ConfigureNAT(portsOrIdents, natAddress, natType, changePort, changeAddress)
|
||||
}
|
||||
|
||||
// IPTMaybePerformNoopNAT can apply NAT or configure a no-op NAT.
|
||||
// If NAT has not been configured for this connection, either mark the
|
||||
// connection as configured for "no-op NAT", in the case of DNAT, or, in the
|
||||
// case of SNAT, perform source port remapping so that source ports used by
|
||||
// locally-generated traffic do not conflict with ports occupied by existing NAT
|
||||
// bindings.
|
||||
//
|
||||
// Note that in the typical case this is also a no-op, because `snatAction`
|
||||
// will do nothing if the original tuple is already unique.
|
||||
func IPTMaybePerformNoopNAT(pkt *PacketBuffer, hook Hook, r *Route, dnat bool) {
|
||||
cn := pkt.tuple.conn
|
||||
cn.mu.Lock()
|
||||
var manip *manipType
|
||||
if dnat {
|
||||
manip = &cn.destinationManip
|
||||
} else {
|
||||
manip = &cn.sourceManip
|
||||
}
|
||||
if *manip != manipNotPerformed {
|
||||
cn.mu.Unlock()
|
||||
_ = IPTHandlePacket(pkt, hook, r)
|
||||
return
|
||||
}
|
||||
if dnat {
|
||||
*manip = manipPerformedNoop
|
||||
cn.mu.Unlock()
|
||||
_ = IPTHandlePacket(pkt, hook, r)
|
||||
return
|
||||
}
|
||||
cn.mu.Unlock()
|
||||
|
||||
// At this point, we know that NAT has not yet been performed on this
|
||||
// connection, and the DNAT case has been handled with a no-op. For SNAT, we
|
||||
// simply perform source port remapping to ensure that source ports for
|
||||
// locally generated traffic do not clash with ports used by existing NAT
|
||||
// bindings.
|
||||
_, _ = snatAction(pkt, hook, r, 0, tcpip.Address{}, true /* changePort */, false /* changeAddress */)
|
||||
}
|
||||
|
||||
// NFTApplyNAT applies NAT to the packet and updates the connection.
|
||||
// Similar to IPTHandlePacket but for NFTables hooks.
|
||||
func NFTApplyNAT(pkt *PacketBuffer, hook NFHook, rt *Route) bool {
|
||||
requiresTXTransportChecksum := false
|
||||
if rt != nil {
|
||||
requiresTXTransportChecksum = rt.RequiresTXTransportChecksum()
|
||||
}
|
||||
opts := handlePacketOpts{
|
||||
fullChecksum: false,
|
||||
updatePseudoHeader: false,
|
||||
natType: SNAT,
|
||||
}
|
||||
switch hook {
|
||||
case NFPrerouting:
|
||||
opts.fullChecksum = true
|
||||
opts.updatePseudoHeader = true
|
||||
opts.natType = DNAT
|
||||
case NFInput:
|
||||
case NFForward:
|
||||
panic("should not handle packet in the forwarding hook")
|
||||
case NFOutput:
|
||||
opts.natType = DNAT
|
||||
fallthrough
|
||||
case NFPostrouting:
|
||||
if pkt.TransportProtocolNumber == header.TCPProtocolNumber && pkt.GSOOptions.Type != GSONone && pkt.GSOOptions.NeedsCsum {
|
||||
opts.updatePseudoHeader = true
|
||||
} else if requiresTXTransportChecksum {
|
||||
opts.fullChecksum = true
|
||||
opts.updatePseudoHeader = true
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized hook = %d", hook))
|
||||
}
|
||||
|
||||
return handlePacket(pkt, &opts)
|
||||
}
|
||||
|
||||
// IsNATConfigured returns whether NAT has been configured for the given NAT type.
|
||||
func (cn *conn) IsNATConfigured(natType NATType) bool {
|
||||
cn.mu.RLock()
|
||||
defer cn.mu.RUnlock()
|
||||
switch natType {
|
||||
case SNAT:
|
||||
return cn.sourceManip != manipNotPerformed
|
||||
case DNAT:
|
||||
return cn.destinationManip != manipNotPerformed
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ConfigureNoopNAT configures the connection for no-op NAT.
|
||||
// Similar to the func `IPTMaybePerformNoopNAT` except that this one only configures NO-OP NAT and is independent of IPTables.
|
||||
func (cn *conn) ConfigureNoopNAT(pkt *PacketBuffer, natType NATType) bool {
|
||||
cn.mu.Lock()
|
||||
var manip *manipType
|
||||
if natType == DNAT {
|
||||
manip = &cn.destinationManip
|
||||
} else {
|
||||
manip = &cn.sourceManip
|
||||
}
|
||||
|
||||
if *manip != manipNotPerformed {
|
||||
cn.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
if natType == DNAT {
|
||||
*manip = manipPerformedNoop
|
||||
cn.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
cn.mu.Unlock()
|
||||
|
||||
// At this point, we know that NAT has not yet been performed on this
|
||||
// connection, and the DNAT case has been handled with a no-op. For SNAT, we
|
||||
// simply perform source port remapping to ensure that source ports for
|
||||
// locally generated traffic do not clash with ports used by existing NAT
|
||||
// bindings.
|
||||
|
||||
portsOrIdents := PortOrIdentRange{Start: 0, Size: math.MaxUint16 + 1}
|
||||
|
||||
// However, we need to extract the port from packet.
|
||||
var port uint16
|
||||
switch pkt.TransportProtocolNumber {
|
||||
case header.UDPProtocolNumber:
|
||||
port = header.UDP(pkt.TransportHeader().Slice()).SourcePort()
|
||||
case header.TCPProtocolNumber:
|
||||
port = header.TCP(pkt.TransportHeader().Slice()).SourcePort()
|
||||
}
|
||||
|
||||
if port != 0 {
|
||||
portsOrIdents = targetPortRangeForTCPAndUDP(port)
|
||||
}
|
||||
|
||||
return cn.ConfigureNAT(portsOrIdents, tcpip.Address{}, natType, true /* changePort */, false /* changeAddress */)
|
||||
}
|
||||
|
||||
// ConfigureMasquerade configures the connection for masquerade.
|
||||
func (cn *conn) configureMasquerade(pkt *PacketBuffer, route *Route, stk *Stack, ports PortOrIdentRange, changePort bool) bool {
|
||||
srcAddr := pkt.Network().SourceAddress()
|
||||
if srcAddr == header.IPv4Any || srcAddr == header.IPv6Any {
|
||||
return false
|
||||
}
|
||||
// Masquerade is only supported for postrouting.
|
||||
if route == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get the network endpoint for the outgoing interface to find its primary address.
|
||||
netEP, err := stk.GetNetworkEndpoint(route.NICID(), route.NetProto())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
addressEP, ok := netEP.(AddressableEndpoint)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ref: net/netfilter/nf_nat_masquerade.c:nf_nat_masquerade_ipv[4|6]()
|
||||
// Use the next hop address as the destination address if it is set.
|
||||
nh := route.NextHop()
|
||||
if nh.Len() == 0 {
|
||||
nh = pkt.Network().DestinationAddress()
|
||||
}
|
||||
|
||||
// addressEP is expected to be set for the postrouting hook.
|
||||
// Find the outgoing primary address for the destination address.
|
||||
ep := addressEP.AcquireOutgoingPrimaryAddress(nh, tcpip.Address{} /* srcHint */, false /* allowExpired */)
|
||||
if ep == nil {
|
||||
// No address exists that we can use as a source address.
|
||||
return false
|
||||
}
|
||||
address := ep.AddressWithPrefix().Address
|
||||
ep.DecRef()
|
||||
|
||||
// Configure NAT for the packet to change the source address.
|
||||
return cn.ConfigureNAT(ports, address, SNAT, changePort, true /* changeAddress */)
|
||||
}
|
||||
|
|
@ -92,5 +92,5 @@ func neighborCacheinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
neighborCacheinitLockNames()
|
||||
neighborCacheprefixIndex = locking.NewMutexClass(reflect.TypeOf(neighborCacheRWMutex{}), neighborCachelockNames)
|
||||
neighborCacheprefixIndex = locking.NewMutexClass(reflect.TypeFor[neighborCacheRWMutex](), neighborCachelockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -578,7 +578,7 @@ func (e *neighborEntry) handleConfirmationLocked(linkAddr tcpip.LinkAddress, fla
|
|||
// here.
|
||||
ep := e.cache.nic.getNetworkEndpoint(header.IPv6ProtocolNumber)
|
||||
if ep == nil {
|
||||
panic(fmt.Sprintf("have a neighbor entry for an IPv6 router but no IPv6 network endpoint"))
|
||||
panic("have a neighbor entry for an IPv6 router but no IPv6 network endpoint")
|
||||
}
|
||||
|
||||
if ndpEP, ok := ep.(NDPEndpoint); ok {
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func neighborEntryinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
neighborEntryinitLockNames()
|
||||
neighborEntryprefixIndex = locking.NewMutexClass(reflect.TypeOf(neighborEntryRWMutex{}), neighborEntrylockNames)
|
||||
neighborEntryprefixIndex = locking.NewMutexClass(reflect.TypeFor[neighborEntryRWMutex](), neighborEntrylockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@ import (
|
|||
|
||||
// NFTablesInterface is an interface for evaluating chains.
|
||||
type NFTablesInterface interface {
|
||||
CheckPrerouting(pkt *PacketBuffer, af AddressFamily) bool
|
||||
CheckInput(pkt *PacketBuffer, af AddressFamily) bool
|
||||
CheckForward(pkt *PacketBuffer, af AddressFamily) bool
|
||||
CheckOutput(pkt *PacketBuffer, af AddressFamily) bool
|
||||
CheckPostrouting(pkt *PacketBuffer, af AddressFamily) bool
|
||||
CheckIngress(pkt *PacketBuffer, af AddressFamily) bool
|
||||
CheckEgress(pkt *PacketBuffer, af AddressFamily) bool
|
||||
CheckPrerouting(pkt *PacketBuffer, route *Route, af AddressFamily) bool
|
||||
CheckInput(pkt *PacketBuffer, route *Route, af AddressFamily) bool
|
||||
CheckForward(pkt *PacketBuffer, route *Route, af AddressFamily) bool
|
||||
CheckOutput(pkt *PacketBuffer, route *Route, af AddressFamily) bool
|
||||
CheckPostrouting(pkt *PacketBuffer, route *Route, af AddressFamily) bool
|
||||
CheckIngress(pkt *PacketBuffer, route *Route, af AddressFamily) bool
|
||||
CheckEgress(pkt *PacketBuffer, route *Route, af AddressFamily) bool
|
||||
}
|
||||
|
||||
// NFHook describes specific points in the pipeline where chains can be attached.
|
||||
|
|
@ -147,24 +147,3 @@ func (f AddressFamily) String() string {
|
|||
}
|
||||
panic(fmt.Sprintf("invalid address family: %d", int(f)))
|
||||
}
|
||||
|
||||
//
|
||||
// Verdict Implementation.
|
||||
// There are two types of verdicts:
|
||||
// 1. Netfilter (External) Verdicts: Drop, Accept, Stolen, Queue, Repeat, Stop
|
||||
// These are terminal verdicts that are returned to the kernel.
|
||||
// 2. Nftable (Internal) Verdicts:, Continue, Break, Jump, Goto, Return
|
||||
// These are internal verdicts that only exist within the nftables library.
|
||||
// Both share the same numeric space (uint32 Verdict Code).
|
||||
//
|
||||
|
||||
// NFVerdict represents the result of evaluating a packet against a rule or chain.
|
||||
type NFVerdict struct {
|
||||
// Code is the numeric code that represents the verdict issued.
|
||||
Code uint32
|
||||
|
||||
// ChainName is the name of the chain to continue evaluation if the verdict is
|
||||
// Jump or Goto.
|
||||
// Note: the chain must be in the same table as the current chain.
|
||||
ChainName string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ func (n *nic) enable() tcpip.Error {
|
|||
// resources. This guarantees no packets between this NIC and the network
|
||||
// stack.
|
||||
//
|
||||
// It returns an action that has to be excuted after releasing the Stack lock
|
||||
// It returns an action that has to be executed after releasing the Stack lock
|
||||
// and any error encountered.
|
||||
func (n *nic) remove(closeLinkEndpoint bool) (func(), tcpip.Error) {
|
||||
n.enableDisableMu.Lock()
|
||||
|
|
@ -843,23 +843,34 @@ func (n *nic) DeliverLinkPacket(protocol tcpip.NetworkProtocolNumber, pkt *Packe
|
|||
// DeliverTransportPacket delivers the packets to the appropriate transport
|
||||
// protocol endpoint.
|
||||
func (n *nic) DeliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt *PacketBuffer) TransportPacketDisposition {
|
||||
res, _ := n.deliverTransportPacket(protocol, pkt)
|
||||
return res
|
||||
}
|
||||
|
||||
// DeliverTransportPacketWithDefaultHandlerResult implements
|
||||
// TransportDispatcherWithDefaultHandlerResult.DeliverTransportPacketWithDefaultHandlerResult.
|
||||
func (n *nic) DeliverTransportPacketWithDefaultHandlerResult(protocol tcpip.TransportProtocolNumber, pkt *PacketBuffer) (TransportPacketDisposition, bool) {
|
||||
return n.deliverTransportPacket(protocol, pkt)
|
||||
}
|
||||
|
||||
func (n *nic) deliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt *PacketBuffer) (TransportPacketDisposition, bool) {
|
||||
state, ok := n.stack.transportProtocols[protocol]
|
||||
if !ok {
|
||||
n.stats.unknownL4ProtocolRcvdPacketCounts.Increment(uint64(protocol))
|
||||
return TransportPacketProtocolUnreachable
|
||||
return TransportPacketProtocolUnreachable, false
|
||||
}
|
||||
|
||||
transProto := state.proto
|
||||
|
||||
if len(pkt.TransportHeader().Slice()) == 0 {
|
||||
n.stats.malformedL4RcvdPackets.Increment()
|
||||
return TransportPacketHandled
|
||||
return TransportPacketHandled, false
|
||||
}
|
||||
|
||||
srcPort, dstPort, err := transProto.ParsePorts(pkt.TransportHeader().Slice())
|
||||
if err != nil {
|
||||
n.stats.malformedL4RcvdPackets.Increment()
|
||||
return TransportPacketHandled
|
||||
return TransportPacketHandled, false
|
||||
}
|
||||
|
||||
netProto, ok := n.stack.networkProtocols[pkt.NetworkProtocolNumber]
|
||||
|
|
@ -875,13 +886,13 @@ func (n *nic) DeliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt
|
|||
RemoteAddress: src,
|
||||
}
|
||||
if n.stack.demux.deliverPacket(protocol, pkt, id) {
|
||||
return TransportPacketHandled
|
||||
return TransportPacketHandled, false
|
||||
}
|
||||
|
||||
// Try to deliver to per-stack default handler.
|
||||
if state.defaultHandler != nil {
|
||||
if state.defaultHandler(id, pkt) {
|
||||
return TransportPacketHandled
|
||||
return TransportPacketHandled, true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -891,11 +902,11 @@ func (n *nic) DeliverTransportPacket(protocol tcpip.TransportProtocolNumber, pkt
|
|||
switch res := transProto.HandleUnknownDestinationPacket(id, pkt); res {
|
||||
case UnknownDestinationPacketMalformed:
|
||||
n.stats.malformedL4RcvdPackets.Increment()
|
||||
return TransportPacketHandled
|
||||
return TransportPacketHandled, false
|
||||
case UnknownDestinationPacketUnhandled:
|
||||
return TransportPacketDestinationPortUnreachable
|
||||
return TransportPacketDestinationPortUnreachable, false
|
||||
case UnknownDestinationPacketHandled:
|
||||
return TransportPacketHandled
|
||||
return TransportPacketHandled, false
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized result from HandleUnknownDestinationPacket = %d", res))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func nicinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
nicinitLockNames()
|
||||
nicprefixIndex = locking.NewMutexClass(reflect.TypeOf(nicRWMutex{}), niclockNames)
|
||||
nicprefixIndex = locking.NewMutexClass(reflect.TypeFor[nicRWMutex](), niclockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
)
|
||||
|
||||
|
|
@ -56,6 +57,9 @@ type PacketBufferOptions struct {
|
|||
// OnRelease is a function to be run when the packet buffer is no longer
|
||||
// referenced (released back to the pool).
|
||||
OnRelease func()
|
||||
|
||||
// Mark is the mark value of this packet.
|
||||
Mark uint32
|
||||
}
|
||||
|
||||
// A PacketBuffer contains all the data of a network packet.
|
||||
|
|
@ -154,6 +158,10 @@ type PacketBuffer struct {
|
|||
// NICID is the ID of the last interface the network packet was handled at.
|
||||
NICID tcpip.NICID
|
||||
|
||||
// InputNICID is the ID of the interface that the network packet
|
||||
// was received on.
|
||||
InputNICID tcpip.NICID
|
||||
|
||||
// RXChecksumValidated indicates that checksum verification may be
|
||||
// safely skipped.
|
||||
RXChecksumValidated bool
|
||||
|
|
@ -161,6 +169,9 @@ type PacketBuffer struct {
|
|||
// NetworkPacketInfo holds an incoming packet's network-layer information.
|
||||
NetworkPacketInfo NetworkPacketInfo
|
||||
|
||||
// Mark is the mark value of this packet.
|
||||
Mark uint32
|
||||
|
||||
tuple *tuple
|
||||
|
||||
// onRelease is a function to be run when the packet buffer is no longer
|
||||
|
|
@ -182,6 +193,7 @@ func NewPacketBuffer(opts PacketBufferOptions) *PacketBuffer {
|
|||
}
|
||||
pk.NetworkPacketInfo.IsForwardedPacket = opts.IsForwardedPacket
|
||||
pk.onRelease = opts.OnRelease
|
||||
pk.Mark = opts.Mark
|
||||
pk.InitRefs()
|
||||
return pk
|
||||
}
|
||||
|
|
@ -380,6 +392,7 @@ func (pk *PacketBuffer) Clone() *PacketBuffer {
|
|||
newPk.headers = pk.headers
|
||||
newPk.Hash = pk.Hash
|
||||
newPk.Owner = pk.Owner
|
||||
newPk.Mark = pk.Mark
|
||||
newPk.GSOOptions = pk.GSOOptions
|
||||
newPk.EgressRoute = pk.EgressRoute
|
||||
newPk.NetworkProtocolNumber = pk.NetworkProtocolNumber
|
||||
|
|
@ -388,6 +401,7 @@ func (pk *PacketBuffer) Clone() *PacketBuffer {
|
|||
newPk.TransportProtocolNumber = pk.TransportProtocolNumber
|
||||
newPk.PktType = pk.PktType
|
||||
newPk.NICID = pk.NICID
|
||||
newPk.InputNICID = pk.InputNICID
|
||||
newPk.RXChecksumValidated = pk.RXChecksumValidated
|
||||
newPk.NetworkPacketInfo = pk.NetworkPacketInfo
|
||||
newPk.tuple = pk.tuple
|
||||
|
|
@ -431,6 +445,7 @@ func (pk *PacketBuffer) CloneToInbound() *PacketBuffer {
|
|||
newPk.InitRefs()
|
||||
// Treat unfilled header portion as reserved.
|
||||
newPk.reserved = pk.AvailableHeaderBytes()
|
||||
newPk.Mark = pk.Mark
|
||||
newPk.tuple = pk.tuple
|
||||
return newPk
|
||||
}
|
||||
|
|
@ -466,10 +481,78 @@ func (pk *PacketBuffer) DeepCopyForForwarding(reservedHeaderBytes int) *PacketBu
|
|||
}
|
||||
|
||||
newPk.tuple = pk.tuple
|
||||
newPk.Mark = pk.Mark
|
||||
newPk.InputNICID = pk.InputNICID
|
||||
|
||||
return newPk
|
||||
}
|
||||
|
||||
// IsConnTrackConfigured returns whether connection tracking is configured for this packet.
|
||||
func (pk *PacketBuffer) IsConnTrackConfigured() bool {
|
||||
return pk.tuple != nil && pk.tuple.conn != nil
|
||||
}
|
||||
|
||||
// FillConnTrackInfo fills connection tracking information for the packet.
|
||||
func (pk *PacketBuffer) FillConnTrackInfo(opts ConnTrackInfoOpts, info *ConnTrackInfo) bool {
|
||||
t := pk.tuple
|
||||
if t == nil || t.conn == nil {
|
||||
return false
|
||||
}
|
||||
return t.conn.FillConnTrackInfo(opts, info)
|
||||
}
|
||||
|
||||
// IsReplyPacket returns whether the packet is a reply packet.
|
||||
func (pk *PacketBuffer) IsReplyPacket() bool {
|
||||
t := pk.tuple
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
return t.reply
|
||||
}
|
||||
|
||||
// IsNATConfigured returns whether NAT is configured for this packet.
|
||||
func (pk *PacketBuffer) IsNATConfigured(nt NATType) bool {
|
||||
if !pk.IsConnTrackConfigured() {
|
||||
return false
|
||||
}
|
||||
return pk.tuple.conn.IsNATConfigured(nt)
|
||||
}
|
||||
|
||||
// ConfigureNoopNAT configures a no-op NAT for the packet.
|
||||
// Called if no NAT rules are configured for this packet.
|
||||
func (pk *PacketBuffer) ConfigureNoopNAT(natType NATType) bool {
|
||||
if !pk.IsConnTrackConfigured() {
|
||||
return false
|
||||
}
|
||||
return pk.tuple.conn.ConfigureNoopNAT(pk, natType)
|
||||
}
|
||||
|
||||
// ConfigureNAT configures NAT for the packet.
|
||||
// Called if NAT rules are configured for this packet.
|
||||
// Returns whether NAT was configured or not.
|
||||
func (pk *PacketBuffer) ConfigureNAT(portsOrIdents PortOrIdentRange, natAddress tcpip.Address, natType NATType, changePort, changeAddress bool) bool {
|
||||
if !pk.IsConnTrackConfigured() {
|
||||
return false
|
||||
}
|
||||
return pk.tuple.conn.ConfigureNAT(portsOrIdents, natAddress, natType, changePort, changeAddress)
|
||||
}
|
||||
|
||||
// ConfigureMasquerade configures NAT masquerade for the packet.
|
||||
func (pk *PacketBuffer) ConfigureMasquerade(portsOrIdents PortOrIdentRange, route *Route, stk *Stack, changePort bool) bool {
|
||||
if !pk.IsConnTrackConfigured() {
|
||||
return false
|
||||
}
|
||||
return pk.tuple.conn.configureMasquerade(pk, route, stk, portsOrIdents, changePort)
|
||||
}
|
||||
|
||||
// FinalizeConnTrack finalizes the connection tracking state for the packet.
|
||||
func (pk *PacketBuffer) FinalizeConnTrack() bool {
|
||||
if pk.tuple == nil || pk.tuple.conn == nil {
|
||||
return true
|
||||
}
|
||||
return pk.tuple.conn.finalize()
|
||||
}
|
||||
|
||||
// headerInfo stores metadata about a header in a packet.
|
||||
//
|
||||
// +stateify savable
|
||||
|
|
@ -768,3 +851,312 @@ func BufferSince(h PacketHeader) buffer.Buffer {
|
|||
clone.TrimFront(int64(offset))
|
||||
return clone
|
||||
}
|
||||
|
||||
// ExperimentOptionValue returns the experiment option value from the packet
|
||||
// and a bool indicating whether an experiment option value was found.
|
||||
func (pk *PacketBuffer) ExperimentOptionValue() (uint16, bool) {
|
||||
switch pk.NetworkProtocolNumber {
|
||||
case header.IPv4ProtocolNumber:
|
||||
h := header.IPv4(pk.NetworkHeader().Slice())
|
||||
opts := h.Options()
|
||||
iter := opts.MakeIterator()
|
||||
for {
|
||||
opt, done, err := iter.Next()
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
if done {
|
||||
return 0, false
|
||||
}
|
||||
if opt.Type() == header.IPv4OptionExperimentType {
|
||||
return opt.(*header.IPv4OptionExperiment).Value(), true
|
||||
}
|
||||
}
|
||||
case header.IPv6ProtocolNumber:
|
||||
h := header.IPv6(pk.NetworkHeader().Slice())
|
||||
v := pk.NetworkHeader().View()
|
||||
if v != nil {
|
||||
v.TrimFront(header.IPv6MinimumSize)
|
||||
}
|
||||
buf := buffer.MakeWithView(v)
|
||||
buf.Append(pk.TransportHeader().View())
|
||||
dataBuf := pk.Data().ToBuffer()
|
||||
buf.Merge(&dataBuf)
|
||||
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(h.NextHeader()), buf)
|
||||
|
||||
for {
|
||||
hdr, done, err := it.Next()
|
||||
if done || err != nil {
|
||||
break
|
||||
}
|
||||
if h, ok := hdr.(header.IPv6ExperimentExtHdr); ok {
|
||||
hdr.Release()
|
||||
return h.Value, true
|
||||
}
|
||||
hdr.Release()
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unexpected network protocol number %d", pk.NetworkProtocolNumber))
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// GetEmbeddedNetAndTransHeaders returns the network and transport headers of the
|
||||
// packet.
|
||||
func (pk *PacketBuffer) GetEmbeddedNetAndTransHeaders(netHdrLength int, getNetAndTransHdr netAndTransHeadersFunc, transProto tcpip.TransportProtocolNumber) (header.Network, header.ChecksummableTransport, bool) {
|
||||
switch transProto {
|
||||
case header.TCPProtocolNumber:
|
||||
if netAndTransHeader, ok := pk.Data().PullUp(netHdrLength + header.TCPMinimumSize); ok {
|
||||
netHeader, transHeaderBytes := getNetAndTransHdr(netAndTransHeader, header.TCPMinimumSize)
|
||||
return netHeader, header.TCP(transHeaderBytes), true
|
||||
}
|
||||
case header.UDPProtocolNumber:
|
||||
if netAndTransHeader, ok := pk.Data().PullUp(netHdrLength + header.UDPMinimumSize); ok {
|
||||
netHeader, transHeaderBytes := getNetAndTransHdr(netAndTransHeader, header.UDPMinimumSize)
|
||||
return netHeader, header.UDP(transHeaderBytes), true
|
||||
}
|
||||
}
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
// GetHeaders returns the network and transport headers of the packet.
|
||||
func (pk *PacketBuffer) GetHeaders() (netHdr header.Network, transHdr header.Transport, isICMPError bool, ok bool) {
|
||||
switch pk.TransportProtocolNumber {
|
||||
case header.TCPProtocolNumber:
|
||||
if tcpHeader := header.TCP(pk.TransportHeader().Slice()); len(tcpHeader) >= header.TCPMinimumSize {
|
||||
return pk.Network(), tcpHeader, false, true
|
||||
}
|
||||
return nil, nil, false, false
|
||||
case header.UDPProtocolNumber:
|
||||
if udpHeader := header.UDP(pk.TransportHeader().Slice()); len(udpHeader) >= header.UDPMinimumSize {
|
||||
return pk.Network(), udpHeader, false, true
|
||||
}
|
||||
return nil, nil, false, false
|
||||
case header.ICMPv4ProtocolNumber:
|
||||
icmpHeader := header.ICMPv4(pk.TransportHeader().Slice())
|
||||
if len(icmpHeader) < header.ICMPv4MinimumSize {
|
||||
return nil, nil, false, false
|
||||
}
|
||||
|
||||
switch icmpType := icmpHeader.Type(); icmpType {
|
||||
case header.ICMPv4Echo, header.ICMPv4EchoReply:
|
||||
return pk.Network(), icmpHeader, false, true
|
||||
case header.ICMPv4DstUnreachable, header.ICMPv4TimeExceeded, header.ICMPv4ParamProblem:
|
||||
default:
|
||||
return nil, nil, false, false
|
||||
}
|
||||
|
||||
h, ok := pk.Data().PullUp(header.IPv4MinimumSize)
|
||||
if !ok {
|
||||
return nil, nil, false, false
|
||||
}
|
||||
|
||||
hdrLength := int(header.IPv4(h).HeaderLength())
|
||||
// Pull up the full IPv4 header which might include options.
|
||||
if hdrLength > header.IPv4MinimumSize {
|
||||
// TODO(https://gvisor.dev/issue/6765): Handle IPv4
|
||||
// options.
|
||||
h, ok = pk.Data().PullUp(hdrLength)
|
||||
if !ok {
|
||||
return nil, nil, false, false
|
||||
}
|
||||
}
|
||||
|
||||
if netHdr, transHdr, ok := pk.GetEmbeddedNetAndTransHeaders(hdrLength, v4NetAndTransHdr, tcpip.TransportProtocolNumber(header.IPv4(h).Protocol())); ok {
|
||||
return netHdr, transHdr, true, true
|
||||
}
|
||||
return nil, nil, false, false
|
||||
case header.ICMPv6ProtocolNumber:
|
||||
icmpHeader := header.ICMPv6(pk.TransportHeader().Slice())
|
||||
if len(icmpHeader) < header.ICMPv6MinimumSize {
|
||||
return nil, nil, false, false
|
||||
}
|
||||
|
||||
switch icmpType := icmpHeader.Type(); icmpType {
|
||||
case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
|
||||
return pk.Network(), icmpHeader, false, true
|
||||
case header.ICMPv6DstUnreachable, header.ICMPv6PacketTooBig, header.ICMPv6TimeExceeded, header.ICMPv6ParamProblem:
|
||||
default:
|
||||
return nil, nil, false, false
|
||||
}
|
||||
|
||||
h, ok := pk.Data().PullUp(header.IPv6MinimumSize)
|
||||
if !ok {
|
||||
return nil, nil, false, false
|
||||
}
|
||||
|
||||
// We do not support extension headers in ICMP errors so the next header
|
||||
// in the IPv6 packet should be a tracked protocol if we reach this point.
|
||||
//
|
||||
// TODO(https://gvisor.dev/issue/6789): Support extension headers.
|
||||
transProto, _ := header.IPv6(h).TryParseTransportProtocol()
|
||||
if netHdr, transHdr, ok := pk.GetEmbeddedNetAndTransHeaders(header.IPv6MinimumSize, v6NetAndTransHdr, transProto); ok {
|
||||
return netHdr, transHdr, true, true
|
||||
}
|
||||
return nil, nil, false, false
|
||||
default:
|
||||
return nil, nil, false, false
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateHeaders updates the headers of the packet with the new port and address.
|
||||
func UpdateHeaders(n header.Network, t header.Transport, updateSRCFields, fullChecksum, updatePseudoHeader bool, newPortOrIdent uint16, newAddr tcpip.Address) {
|
||||
switch t := t.(type) {
|
||||
case header.ChecksummableTransport:
|
||||
if updateSRCFields {
|
||||
if fullChecksum {
|
||||
t.SetSourcePortWithChecksumUpdate(newPortOrIdent)
|
||||
} else {
|
||||
t.SetSourcePort(newPortOrIdent)
|
||||
}
|
||||
} else {
|
||||
if fullChecksum {
|
||||
t.SetDestinationPortWithChecksumUpdate(newPortOrIdent)
|
||||
} else {
|
||||
t.SetDestinationPort(newPortOrIdent)
|
||||
}
|
||||
}
|
||||
|
||||
if updatePseudoHeader {
|
||||
var oldAddr tcpip.Address
|
||||
if updateSRCFields {
|
||||
oldAddr = n.SourceAddress()
|
||||
} else {
|
||||
oldAddr = n.DestinationAddress()
|
||||
}
|
||||
|
||||
t.UpdateChecksumPseudoHeaderAddress(oldAddr, newAddr, fullChecksum)
|
||||
}
|
||||
case header.ICMPv4:
|
||||
switch icmpType := t.Type(); icmpType {
|
||||
case header.ICMPv4Echo:
|
||||
if updateSRCFields {
|
||||
t.SetIdentWithChecksumUpdate(newPortOrIdent)
|
||||
}
|
||||
case header.ICMPv4EchoReply:
|
||||
if !updateSRCFields {
|
||||
t.SetIdentWithChecksumUpdate(newPortOrIdent)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
|
||||
}
|
||||
case header.ICMPv6:
|
||||
switch icmpType := t.Type(); icmpType {
|
||||
case header.ICMPv6EchoRequest:
|
||||
if updateSRCFields {
|
||||
t.SetIdentWithChecksumUpdate(newPortOrIdent)
|
||||
}
|
||||
case header.ICMPv6EchoReply:
|
||||
if !updateSRCFields {
|
||||
t.SetIdentWithChecksumUpdate(newPortOrIdent)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected ICMPv6 type = %d", icmpType))
|
||||
}
|
||||
|
||||
var oldAddr tcpip.Address
|
||||
if updateSRCFields {
|
||||
oldAddr = n.SourceAddress()
|
||||
} else {
|
||||
oldAddr = n.DestinationAddress()
|
||||
}
|
||||
|
||||
t.UpdateChecksumPseudoHeaderAddress(oldAddr, newAddr)
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled transport = %#v", t))
|
||||
}
|
||||
|
||||
if checksummableNetHeader, ok := n.(header.ChecksummableNetwork); ok {
|
||||
if updateSRCFields {
|
||||
checksummableNetHeader.SetSourceAddressWithChecksumUpdate(newAddr)
|
||||
} else {
|
||||
checksummableNetHeader.SetDestinationAddressWithChecksumUpdate(newAddr)
|
||||
}
|
||||
} else if updateSRCFields {
|
||||
n.SetSourceAddress(newAddr)
|
||||
} else {
|
||||
n.SetDestinationAddress(newAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateTransportChecksum calculates the transport-layer checksum of the
|
||||
// packet.
|
||||
// TODO: b/521901282 - Verify with GSO.
|
||||
func (pk *PacketBuffer) CalculateTransportChecksum() {
|
||||
netHdr, transHdr, isICMPError, ok := pk.GetHeaders()
|
||||
if isICMPError {
|
||||
// Skip ICMP errors because GetHeaders() returns inner headers, but pk.Data()
|
||||
// contains the outer payload (including inner IP header), which would
|
||||
// corrupt the checksum calculation if used as the transport payload.
|
||||
// Inner headers are already incrementally updated by NAT if needed.
|
||||
// This aligns with Linux, which also relies on incremental updates for
|
||||
// inner headers and does not perform full recalculation from scratch.
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
// Try to parse headers from Data if not set (e.g., forwarded packet).
|
||||
if pk.NetworkProtocolNumber == 0 {
|
||||
return
|
||||
}
|
||||
netHdr = pk.Network()
|
||||
transProto := netHdr.TransportProtocol()
|
||||
|
||||
var headerSize int
|
||||
switch transProto {
|
||||
case header.TCPProtocolNumber:
|
||||
// Peek at minimum TCP header to find data offset (which includes options).
|
||||
b, ok := pk.Data().PullUp(header.TCPMinimumSize)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
tcp := header.TCP(b)
|
||||
headerSize = int(tcp.DataOffset())
|
||||
if headerSize < header.TCPMinimumSize {
|
||||
return
|
||||
}
|
||||
case header.UDPProtocolNumber:
|
||||
headerSize = header.UDPMinimumSize
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
// Consume the transport header.
|
||||
if _, ok := pk.TransportHeader().Consume(headerSize); !ok {
|
||||
return
|
||||
}
|
||||
pk.TransportProtocolNumber = transProto
|
||||
|
||||
// Refresh headers.
|
||||
netHdr, transHdr, isICMPError, ok = pk.GetHeaders()
|
||||
if !ok || isICMPError {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var xsum uint16
|
||||
switch t := transHdr.(type) {
|
||||
case header.TCP:
|
||||
src := netHdr.SourceAddress()
|
||||
dst := netHdr.DestinationAddress()
|
||||
proto := netHdr.TransportProtocol()
|
||||
totalLen := uint16(len(t) + pk.Data().Size())
|
||||
xsum = header.PseudoHeaderChecksum(proto, src, dst, totalLen)
|
||||
xsum = checksum.Combine(xsum, pk.Data().Checksum())
|
||||
t.SetChecksum(0)
|
||||
t.SetChecksum(^t.CalculateChecksum(xsum))
|
||||
case header.UDP:
|
||||
src := netHdr.SourceAddress()
|
||||
dst := netHdr.DestinationAddress()
|
||||
proto := netHdr.TransportProtocol()
|
||||
totalLen := uint16(len(t) + pk.Data().Size())
|
||||
xsum = header.PseudoHeaderChecksum(proto, src, dst, totalLen)
|
||||
xsum = checksum.Combine(xsum, pk.Data().Checksum())
|
||||
t.SetChecksum(0)
|
||||
csum := ^t.CalculateChecksum(xsum)
|
||||
// udp csum RFC 768.
|
||||
if csum == 0 {
|
||||
csum = 0xFFFF
|
||||
}
|
||||
t.SetChecksum(csum)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func packetEndpointListinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
packetEndpointListinitLockNames()
|
||||
packetEndpointListprefixIndex = locking.NewMutexClass(reflect.TypeOf(packetEndpointListRWMutex{}), packetEndpointListlockNames)
|
||||
packetEndpointListprefixIndex = locking.NewMutexClass(reflect.TypeFor[packetEndpointListRWMutex](), packetEndpointListlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func packetEPsinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
packetEPsinitLockNames()
|
||||
packetEPsprefixIndex = locking.NewMutexClass(reflect.TypeOf(packetEPsRWMutex{}), packetEPslockNames)
|
||||
packetEPsprefixIndex = locking.NewMutexClass(reflect.TypeFor[packetEPsRWMutex](), packetEPslockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,5 +60,5 @@ func packetsPendingLinkResolutioninitLockNames() {}
|
|||
|
||||
func init() {
|
||||
packetsPendingLinkResolutioninitLockNames()
|
||||
packetsPendingLinkResolutionprefixIndex = locking.NewMutexClass(reflect.TypeOf(packetsPendingLinkResolutionMutex{}), packetsPendingLinkResolutionlockNames)
|
||||
packetsPendingLinkResolutionprefixIndex = locking.NewMutexClass(reflect.TypeFor[packetsPendingLinkResolutionMutex](), packetsPendingLinkResolutionlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -365,6 +365,21 @@ type TransportDispatcher interface {
|
|||
DeliverRawPacket(tcpip.TransportProtocolNumber, *PacketBuffer)
|
||||
}
|
||||
|
||||
// TransportDispatcherWithDefaultHandlerResult extends TransportDispatcher with
|
||||
// default-handler-specific delivery metadata.
|
||||
type TransportDispatcherWithDefaultHandlerResult interface {
|
||||
TransportDispatcher
|
||||
|
||||
// DeliverTransportPacketWithDefaultHandlerResult delivers packets to the
|
||||
// appropriate transport protocol endpoint and reports whether the packet was
|
||||
// specifically handled by the per-stack default transport protocol handler.
|
||||
//
|
||||
// pkt.NetworkHeader must be set before calling this method.
|
||||
//
|
||||
// DeliverTransportPacketWithDefaultHandlerResult may modify the packet.
|
||||
DeliverTransportPacketWithDefaultHandlerResult(tcpip.TransportProtocolNumber, *PacketBuffer) (TransportPacketDisposition, bool)
|
||||
}
|
||||
|
||||
// PacketLooping specifies where an outbound packet should be sent.
|
||||
type PacketLooping byte
|
||||
|
||||
|
|
@ -872,6 +887,9 @@ type NetworkEndpoint interface {
|
|||
// minus the network endpoint max header length.
|
||||
MTU() uint32
|
||||
|
||||
// EndpointHeaderSize returns the size of this endpoint header.
|
||||
EndpointHeaderSize() uint32
|
||||
|
||||
// MaxHeaderLength returns the maximum size the network (and lower
|
||||
// level layers combined) headers can have. Higher levels use this
|
||||
// information to reserve space in the front of the packets they're
|
||||
|
|
@ -1135,7 +1153,6 @@ const (
|
|||
CapabilityRXChecksumOffload
|
||||
CapabilityResolutionRequired
|
||||
CapabilitySaveRestore
|
||||
CapabilityDisconnectOk
|
||||
CapabilityLoopback
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ func constructAndValidateRoute(netProto tcpip.NetworkProtocolNumber, addressEndp
|
|||
// AssignableAddressEndpoint.
|
||||
func makeRoute(netProto tcpip.NetworkProtocolNumber, gateway, localAddr, remoteAddr tcpip.Address, outgoingNIC, localAddressNIC *nic, localAddressEndpoint AssignableAddressEndpoint, handleLocal, multicastLoop bool, mtu uint32) *Route {
|
||||
if localAddressNIC.stack != outgoingNIC.stack {
|
||||
panic(fmt.Sprintf("cannot create a route with NICs from different stacks"))
|
||||
panic("cannot create a route with NICs from different stacks")
|
||||
}
|
||||
|
||||
if localAddr.BitLen() == 0 {
|
||||
|
|
@ -245,6 +245,14 @@ func makeRoute(netProto tcpip.NetworkProtocolNumber, gateway, localAddr, remoteA
|
|||
}
|
||||
|
||||
func makeRouteInner(netProto tcpip.NetworkProtocolNumber, localAddr, remoteAddr tcpip.Address, outgoingNIC, localAddressNIC *nic, localAddressEndpoint AssignableAddressEndpoint, loop PacketLooping, mtu uint32) *Route {
|
||||
if mtu != 0 {
|
||||
adjusted := mtu - outgoingNIC.getNetworkEndpoint(netProto).EndpointHeaderSize()
|
||||
if adjusted > mtu {
|
||||
mtu = 0
|
||||
} else {
|
||||
mtu = adjusted
|
||||
}
|
||||
}
|
||||
r := &Route{
|
||||
routeInfo: routeInfo{
|
||||
NetProto: netProto,
|
||||
|
|
@ -339,11 +347,6 @@ func (r *Route) HasSaveRestoreCapability() bool {
|
|||
return r.outgoingNIC.NetworkLinkEndpoint.Capabilities()&CapabilitySaveRestore != 0
|
||||
}
|
||||
|
||||
// HasDisconnectOkCapability returns true if the route supports disconnecting.
|
||||
func (r *Route) HasDisconnectOkCapability() bool {
|
||||
return r.outgoingNIC.NetworkLinkEndpoint.Capabilities()&CapabilityDisconnectOk != 0
|
||||
}
|
||||
|
||||
// GSOMaxSize returns the maximum GSO packet size.
|
||||
func (r *Route) GSOMaxSize() uint32 {
|
||||
if gso, ok := r.outgoingNIC.NetworkLinkEndpoint.(GSOEndpoint); ok {
|
||||
|
|
@ -525,6 +528,7 @@ func (r *Route) DefaultTTL() uint8 {
|
|||
// MTU returns the MTU of the route if present, otherwise the MTU of the underlying network endpoint.
|
||||
func (r *Route) MTU() uint32 {
|
||||
if r.mtu > 0 {
|
||||
// r.mtu is already adjusted to account for IP headers. See makeRouteInner.
|
||||
return r.mtu
|
||||
}
|
||||
return r.outgoingNIC.getNetworkEndpoint(r.NetProto()).MTU()
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func routeinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
routeinitLockNames()
|
||||
routeprefixIndex = locking.NewMutexClass(reflect.TypeOf(routeRWMutex{}), routelockNames)
|
||||
routeprefixIndex = locking.NewMutexClass(reflect.TypeFor[routeRWMutex](), routelockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func routeStackinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
routeStackinitLockNames()
|
||||
routeStackprefixIndex = locking.NewMutexClass(reflect.TypeOf(routeStackRWMutex{}), routeStacklockNames)
|
||||
routeStackprefixIndex = locking.NewMutexClass(reflect.TypeFor[routeStackRWMutex](), routeStacklockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,31 @@ import (
|
|||
cryptorand "github.com/sagernet/gvisor/pkg/rand"
|
||||
)
|
||||
|
||||
// beforeSave is invoked by stateify.
|
||||
func (s *Stack) beforeSave() {
|
||||
// removeConf will be set only in case of save/restore.
|
||||
s.mu.Lock()
|
||||
if !s.removeConf {
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Remove all the NICs and routes from the stack as they will be
|
||||
// created again during restore based on the new network config.
|
||||
deferActs := make([]func(), 0)
|
||||
for id := range s.nics {
|
||||
act, _ := s.removeNICLocked(id, true /* closeLinkEndpoint */)
|
||||
if act != nil {
|
||||
deferActs = append(deferActs, act)
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
for _, act := range deferActs {
|
||||
act()
|
||||
}
|
||||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (s *Stack) afterLoad(context.Context) {
|
||||
s.insecureRNG = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
|
|
|||
|
|
@ -20,17 +20,18 @@
|
|||
package stack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/atomicbitops"
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/log"
|
||||
cryptorand "github.com/sagernet/gvisor/pkg/rand"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/ports"
|
||||
|
|
@ -96,7 +97,7 @@ type Stack struct {
|
|||
// +checklocks:mu
|
||||
nics map[tcpip.NICID]*nic `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
loopbackNIC *nic
|
||||
loopbackNIC *nic `state:"nosave"`
|
||||
// +checklocks:mu
|
||||
defaultForwardingEnabled map[tcpip.NetworkProtocolNumber]struct{}
|
||||
|
||||
|
|
@ -121,7 +122,15 @@ type Stack struct {
|
|||
tables *IPTables `state:"nosave"`
|
||||
|
||||
// nftables is the nftables interface for packet filtering and manipulation rules.
|
||||
nftables NFTablesInterface `state:"nosave"`
|
||||
// Using atomic.Pointer for RCU lock-free reads.
|
||||
nftables atomic.Pointer[NFTablesInterface] `state:"nosave"`
|
||||
|
||||
// nftablesUpdateMu serializes concurrent netlink batch modifications to nftables.
|
||||
nftablesUpdateMu sync.Mutex `state:"nosave"`
|
||||
|
||||
// nftablesConfigured indicates whether NFTables is configured with at
|
||||
// least one rule on a chain at a network hook.
|
||||
nftablesConfigured atomicbitops.Bool
|
||||
|
||||
// restoredEndpoints is a list of endpoints that need to be restored if the
|
||||
// stack is being restored.
|
||||
|
|
@ -179,8 +188,23 @@ type Stack struct {
|
|||
// initialized at stack startup.
|
||||
tsOffsetSecret uint32
|
||||
|
||||
// saveRestoreEnabled indicates whether the stack is saved and restored.
|
||||
saveRestoreEnabled bool
|
||||
// removeConf indicates whether to remove NICs and routes and terminate
|
||||
// active connections before saving. This flag will be set to true only
|
||||
// when resume is false.
|
||||
removeConf bool `state:"nosave"`
|
||||
|
||||
// allowLiveTCPMigration allows TCP connection state to be migrated.
|
||||
// If false, any connected TCP endpoints will be terminated
|
||||
// during save/restore.
|
||||
allowLiveTCPMigration bool `state:"nosave"`
|
||||
|
||||
// externalNetworkingDisabled indicates whether external networking is
|
||||
// disabled. This means all non-loopback NICs are disabled.
|
||||
externalNetworkingDisabled bool
|
||||
|
||||
// allowConnectedOnSave indicates whether connections should be
|
||||
// allowed to remain connected during save.
|
||||
allowConnectedOnSave bool
|
||||
}
|
||||
|
||||
// NetworkProtocolFactory instantiates a network protocol.
|
||||
|
|
@ -231,6 +255,11 @@ type Options struct {
|
|||
// operations.
|
||||
AllowPacketEndpointWrite bool
|
||||
|
||||
// AllowLiveTCPMigration allows TCP connection state to be migrated.
|
||||
// If false, any connected TCP endpoints will be terminated
|
||||
// during save/restore.
|
||||
AllowLiveTCPMigration bool
|
||||
|
||||
// RandSource is an optional source to use to generate random
|
||||
// numbers. If omitted it defaults to a Source seeded by the data
|
||||
// returned by the stack secure RNG.
|
||||
|
|
@ -398,7 +427,6 @@ func New(opts Options) *Stack {
|
|||
stats: opts.Stats.FillIn(),
|
||||
handleLocal: opts.HandleLocal,
|
||||
tables: opts.IPTables,
|
||||
nftables: opts.NFTables,
|
||||
icmpRateLimiter: NewICMPRateLimiter(clock),
|
||||
seed: secureRNG.Uint32(),
|
||||
nudConfigs: opts.NUDConfigs,
|
||||
|
|
@ -415,9 +443,11 @@ func New(opts Options) *Stack {
|
|||
Default: DefaultBufferSize,
|
||||
Max: DefaultMaxBufferSize,
|
||||
},
|
||||
tcpInvalidRateLimit: defaultTCPInvalidRateLimit,
|
||||
tsOffsetSecret: secureRNG.Uint32(),
|
||||
tcpInvalidRateLimit: defaultTCPInvalidRateLimit,
|
||||
tsOffsetSecret: secureRNG.Uint32(),
|
||||
allowLiveTCPMigration: opts.AllowLiveTCPMigration,
|
||||
}
|
||||
s.SetNFTables(opts.NFTables)
|
||||
|
||||
// Add specified network protocols.
|
||||
for _, netProtoFactory := range opts.NetworkProtocols {
|
||||
|
|
@ -895,8 +925,8 @@ type NICOptions struct {
|
|||
|
||||
// GetNICByID return a network device associated with the specified ID.
|
||||
func (s *Stack) GetNICByID(id tcpip.NICID) (*nic, tcpip.Error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
n, ok := s.nics[id]
|
||||
if !ok {
|
||||
|
|
@ -1017,7 +1047,7 @@ func (s *Stack) CheckNIC(id tcpip.NICID) bool {
|
|||
// RemoveNIC removes NIC and all related routes from the network stack.
|
||||
func (s *Stack) RemoveNIC(id tcpip.NICID) tcpip.Error {
|
||||
s.mu.Lock()
|
||||
deferAct, err := s.removeNICLocked(id)
|
||||
deferAct, err := s.removeNICLocked(id, true /* closeLinkEndpoint */)
|
||||
s.mu.Unlock()
|
||||
if deferAct != nil {
|
||||
deferAct()
|
||||
|
|
@ -1028,7 +1058,7 @@ func (s *Stack) RemoveNIC(id tcpip.NICID) tcpip.Error {
|
|||
// removeNICLocked removes NIC and all related routes from the network stack.
|
||||
//
|
||||
// +checklocks:s.mu
|
||||
func (s *Stack) removeNICLocked(id tcpip.NICID) (func(), tcpip.Error) {
|
||||
func (s *Stack) removeNICLocked(id tcpip.NICID, closeLinkEndpoint bool) (func(), tcpip.Error) {
|
||||
nic, ok := s.nics[id]
|
||||
if !ok {
|
||||
return nil, &tcpip.ErrUnknownNICID{}
|
||||
|
|
@ -1056,7 +1086,19 @@ func (s *Stack) removeNICLocked(id tcpip.NICID) (func(), tcpip.Error) {
|
|||
if s.loopbackNIC == nic {
|
||||
s.loopbackNIC = nil
|
||||
}
|
||||
return nic.remove(true /* closeLinkEndpoint */)
|
||||
return nic.remove(closeLinkEndpoint)
|
||||
}
|
||||
|
||||
// GetNICCoordinatorID returns the ID of the coordinator device of a NIC.
|
||||
func (s *Stack) GetNICCoordinatorID(id tcpip.NICID) (tcpip.NICID, bool) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
if nic, ok := s.nics[id]; ok {
|
||||
if nic.Primary != nil {
|
||||
return nic.Primary.id, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// SetNICCoordinator sets a coordinator device.
|
||||
|
|
@ -1159,6 +1201,9 @@ type NICInfo struct {
|
|||
// MulticastForwarding holds the forwarding status for each network endpoint
|
||||
// that supports multicast forwarding.
|
||||
MulticastForwarding map[tcpip.NetworkProtocolNumber]bool
|
||||
|
||||
// Primary is the index of the main controlling interface in a bonded setup.
|
||||
Primary tcpip.NICID
|
||||
}
|
||||
|
||||
// HasNIC returns true if the NICID is defined in the stack.
|
||||
|
|
@ -1169,65 +1214,87 @@ func (s *Stack) HasNIC(id tcpip.NICID) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
type forwardingFn func(tcpip.NetworkProtocolNumber) (bool, tcpip.Error)
|
||||
|
||||
func forwardingValue(forwardingFn forwardingFn, proto tcpip.NetworkProtocolNumber, nicID tcpip.NICID, fnName string) (forward bool, ok bool) {
|
||||
switch forwarding, err := forwardingFn(proto); err.(type) {
|
||||
case nil:
|
||||
return forwarding, true
|
||||
case *tcpip.ErrUnknownProtocol:
|
||||
panic(fmt.Sprintf("expected network protocol %d to be available on NIC %d", proto, nicID))
|
||||
case *tcpip.ErrNotSupported:
|
||||
// Not all network protocols support forwarding.
|
||||
default:
|
||||
panic(fmt.Sprintf("nic(id=%d).%s(%d): %s", nicID, fnName, proto, err))
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
// precondition: s.mu is held.
|
||||
func (s *Stack) nicInfo(nic *nic, id tcpip.NICID) *NICInfo {
|
||||
flags := NICStateFlags{
|
||||
Up: true, // Netstack interfaces are always up.
|
||||
Running: nic.Enabled(),
|
||||
Promiscuous: nic.Promiscuous(),
|
||||
Loopback: nic.IsLoopback(),
|
||||
}
|
||||
|
||||
netStats := make(map[tcpip.NetworkProtocolNumber]NetworkEndpointStats)
|
||||
for proto, netEP := range nic.networkEndpoints {
|
||||
netStats[proto] = netEP.Stats()
|
||||
}
|
||||
|
||||
info := NICInfo{
|
||||
Name: nic.name,
|
||||
LinkAddress: nic.NetworkLinkEndpoint.LinkAddress(),
|
||||
ProtocolAddresses: nic.primaryAddresses(),
|
||||
Flags: flags,
|
||||
MTU: nic.NetworkLinkEndpoint.MTU(),
|
||||
Stats: nic.stats.local,
|
||||
NetworkStats: netStats,
|
||||
Context: nic.context,
|
||||
ARPHardwareType: nic.NetworkLinkEndpoint.ARPHardwareType(),
|
||||
Forwarding: make(map[tcpip.NetworkProtocolNumber]bool),
|
||||
MulticastForwarding: make(map[tcpip.NetworkProtocolNumber]bool),
|
||||
}
|
||||
|
||||
for proto := range s.networkProtocols {
|
||||
if forwarding, ok := forwardingValue(nic.forwarding, proto, id, "forwarding"); ok {
|
||||
info.Forwarding[proto] = forwarding
|
||||
}
|
||||
|
||||
if multicastForwarding, ok := forwardingValue(nic.multicastForwarding, proto, id, "multicastForwarding"); ok {
|
||||
info.MulticastForwarding[proto] = multicastForwarding
|
||||
}
|
||||
}
|
||||
|
||||
if nic.Primary != nil {
|
||||
info.Primary = nic.Primary.id
|
||||
}
|
||||
|
||||
return &info
|
||||
}
|
||||
|
||||
// SingleNICInfo returns the NICInfo for the given NICID.
|
||||
func (s *Stack) SingleNICInfo(id tcpip.NICID) (*NICInfo, bool) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
if nic, ok := s.nics[id]; !ok {
|
||||
return nil, false
|
||||
} else {
|
||||
return s.nicInfo(nic, id), true
|
||||
}
|
||||
}
|
||||
|
||||
// NICInfo returns a map of NICIDs to their associated information.
|
||||
func (s *Stack) NICInfo() map[tcpip.NICID]NICInfo {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
type forwardingFn func(tcpip.NetworkProtocolNumber) (bool, tcpip.Error)
|
||||
forwardingValue := func(forwardingFn forwardingFn, proto tcpip.NetworkProtocolNumber, nicID tcpip.NICID, fnName string) (forward bool, ok bool) {
|
||||
switch forwarding, err := forwardingFn(proto); err.(type) {
|
||||
case nil:
|
||||
return forwarding, true
|
||||
case *tcpip.ErrUnknownProtocol:
|
||||
panic(fmt.Sprintf("expected network protocol %d to be available on NIC %d", proto, nicID))
|
||||
case *tcpip.ErrNotSupported:
|
||||
// Not all network protocols support forwarding.
|
||||
default:
|
||||
panic(fmt.Sprintf("nic(id=%d).%s(%d): %s", nicID, fnName, proto, err))
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
nics := make(map[tcpip.NICID]NICInfo)
|
||||
for id, nic := range s.nics {
|
||||
flags := NICStateFlags{
|
||||
Up: true, // Netstack interfaces are always up.
|
||||
Running: nic.Enabled(),
|
||||
Promiscuous: nic.Promiscuous(),
|
||||
Loopback: nic.IsLoopback(),
|
||||
}
|
||||
|
||||
netStats := make(map[tcpip.NetworkProtocolNumber]NetworkEndpointStats)
|
||||
for proto, netEP := range nic.networkEndpoints {
|
||||
netStats[proto] = netEP.Stats()
|
||||
}
|
||||
|
||||
info := NICInfo{
|
||||
Name: nic.name,
|
||||
LinkAddress: nic.NetworkLinkEndpoint.LinkAddress(),
|
||||
ProtocolAddresses: nic.primaryAddresses(),
|
||||
Flags: flags,
|
||||
MTU: nic.NetworkLinkEndpoint.MTU(),
|
||||
Stats: nic.stats.local,
|
||||
NetworkStats: netStats,
|
||||
Context: nic.context,
|
||||
ARPHardwareType: nic.NetworkLinkEndpoint.ARPHardwareType(),
|
||||
Forwarding: make(map[tcpip.NetworkProtocolNumber]bool),
|
||||
MulticastForwarding: make(map[tcpip.NetworkProtocolNumber]bool),
|
||||
}
|
||||
|
||||
for proto := range s.networkProtocols {
|
||||
if forwarding, ok := forwardingValue(nic.forwarding, proto, id, "forwarding"); ok {
|
||||
info.Forwarding[proto] = forwarding
|
||||
}
|
||||
|
||||
if multicastForwarding, ok := forwardingValue(nic.multicastForwarding, proto, id, "multicastForwarding"); ok {
|
||||
info.MulticastForwarding[proto] = multicastForwarding
|
||||
}
|
||||
}
|
||||
|
||||
nics[id] = info
|
||||
nics[id] = *s.nicInfo(nic, id)
|
||||
}
|
||||
return nics
|
||||
}
|
||||
|
|
@ -1991,7 +2058,7 @@ func (s *Stack) Wait() {
|
|||
for id, n := range s.nics {
|
||||
// Remove NIC to ensure that qDisc goroutines are correctly
|
||||
// terminated on stack teardown.
|
||||
act, _ := s.removeNICLocked(id)
|
||||
act, _ := s.removeNICLocked(id, true /* closeLinkEndpoint */)
|
||||
n.NetworkLinkEndpoint.Wait()
|
||||
if act != nil {
|
||||
deferActs = append(deferActs, act)
|
||||
|
|
@ -2025,31 +2092,43 @@ func (s *Stack) getNICs() map[tcpip.NICID]*nic {
|
|||
return nics
|
||||
}
|
||||
|
||||
// ResetConfig resets the stack's NICs and ID generator.
|
||||
func (s *Stack) ResetConfig() {
|
||||
nics := make(map[tcpip.NICID]*nic)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.nics = nics
|
||||
s.loopbackNIC = nil
|
||||
s.nicIDGen.Store(0)
|
||||
}
|
||||
|
||||
// ReplaceConfig replaces config in the loaded stack.
|
||||
func (s *Stack) ReplaceConfig(st *Stack) {
|
||||
if st == nil {
|
||||
panic("stack.Stack cannot be nil when netstack s/r is enabled")
|
||||
panic("stack.Stack cannot be nil when replacing config")
|
||||
}
|
||||
|
||||
// Update route table.
|
||||
s.SetRouteTable(st.GetRouteTable())
|
||||
|
||||
// Update NICs.
|
||||
nics := st.getNICs()
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.nics = make(map[tcpip.NICID]*nic)
|
||||
s.loopbackNIC = nil
|
||||
|
||||
// Update iptables and nftables.
|
||||
s.tables = st.IPTables()
|
||||
s.SetNFTables(st.NFTables())
|
||||
for id, nic := range nics {
|
||||
nic.stack = s
|
||||
s.nics[id] = nic
|
||||
if nic.IsLoopback() {
|
||||
s.loopbackNIC = nic
|
||||
} else if s.externalNetworkingDisabled {
|
||||
nic.disable()
|
||||
}
|
||||
_ = s.NextNICID()
|
||||
}
|
||||
s.tables = st.tables
|
||||
s.nftables = st.nftables
|
||||
}
|
||||
|
||||
// Restore restarts the stack after a restore. This must be called after the
|
||||
|
|
@ -2060,7 +2139,6 @@ func (s *Stack) Restore() {
|
|||
s.mu.Lock()
|
||||
eps := s.restoredEndpoints
|
||||
s.restoredEndpoints = nil
|
||||
saveRestoreEnabled := s.saveRestoreEnabled
|
||||
s.mu.Unlock()
|
||||
for _, e := range eps {
|
||||
e.Restore(s)
|
||||
|
|
@ -2070,13 +2148,9 @@ func (s *Stack) Restore() {
|
|||
// protocol level background workers.
|
||||
tcpip.AsyncLoading.Wait()
|
||||
|
||||
// Now resume any protocol level background workers.
|
||||
// Now restore any protocol level background workers.
|
||||
for _, p := range s.transportProtocols {
|
||||
if saveRestoreEnabled {
|
||||
p.proto.Restore()
|
||||
} else {
|
||||
p.proto.Resume()
|
||||
}
|
||||
p.proto.Restore()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2152,6 +2226,12 @@ func (s *Stack) unregisterPacketEndpointLocked(nicID tcpip.NICID, netProto tcpip
|
|||
// WritePacketToRemote writes a payload on the specified NIC using the provided
|
||||
// network protocol and remote link address.
|
||||
func (s *Stack) WritePacketToRemote(nicID tcpip.NICID, remote tcpip.LinkAddress, netProto tcpip.NetworkProtocolNumber, payload buffer.Buffer) tcpip.Error {
|
||||
return s.WritePacketToRemoteWithMark(nicID, remote, netProto, payload, 0)
|
||||
}
|
||||
|
||||
// WritePacketToRemoteWithMark writes a payload on the specified NIC using the
|
||||
// provided network protocol, remote link address, and packet mark.
|
||||
func (s *Stack) WritePacketToRemoteWithMark(nicID tcpip.NICID, remote tcpip.LinkAddress, netProto tcpip.NetworkProtocolNumber, payload buffer.Buffer, mark uint32) tcpip.Error {
|
||||
s.mu.Lock()
|
||||
nic, ok := s.nics[nicID]
|
||||
s.mu.Unlock()
|
||||
|
|
@ -2161,6 +2241,7 @@ func (s *Stack) WritePacketToRemote(nicID tcpip.NICID, remote tcpip.LinkAddress,
|
|||
pkt := NewPacketBuffer(PacketBufferOptions{
|
||||
ReserveHeaderBytes: int(nic.MaxHeaderLength()),
|
||||
Payload: payload,
|
||||
Mark: mark,
|
||||
})
|
||||
defer pkt.DecRef()
|
||||
pkt.NetworkProtocolNumber = netProto
|
||||
|
|
@ -2170,6 +2251,12 @@ func (s *Stack) WritePacketToRemote(nicID tcpip.NICID, remote tcpip.LinkAddress,
|
|||
// WriteRawPacket writes data directly to the specified NIC without adding any
|
||||
// headers.
|
||||
func (s *Stack) WriteRawPacket(nicID tcpip.NICID, proto tcpip.NetworkProtocolNumber, payload buffer.Buffer) tcpip.Error {
|
||||
return s.WriteRawPacketWithMark(nicID, proto, payload, 0)
|
||||
}
|
||||
|
||||
// WriteRawPacketWithMark writes data directly to the specified NIC without adding any
|
||||
// headers, setting the specified packet mark.
|
||||
func (s *Stack) WriteRawPacketWithMark(nicID tcpip.NICID, proto tcpip.NetworkProtocolNumber, payload buffer.Buffer, mark uint32) tcpip.Error {
|
||||
s.mu.RLock()
|
||||
nic, ok := s.nics[nicID]
|
||||
s.mu.RUnlock()
|
||||
|
|
@ -2179,6 +2266,7 @@ func (s *Stack) WriteRawPacket(nicID tcpip.NICID, proto tcpip.NetworkProtocolNum
|
|||
|
||||
pkt := NewPacketBuffer(PacketBufferOptions{
|
||||
Payload: payload,
|
||||
Mark: mark,
|
||||
})
|
||||
defer pkt.DecRef()
|
||||
pkt.NetworkProtocolNumber = proto
|
||||
|
|
@ -2244,14 +2332,47 @@ func (s *Stack) IPTables() *IPTables {
|
|||
return s.tables
|
||||
}
|
||||
|
||||
// SetIPTables sets the stack's iptables.
|
||||
func (s *Stack) SetIPTables(tables *IPTables) {
|
||||
s.tables = tables
|
||||
}
|
||||
|
||||
// NFTables returns the stack's nftables.
|
||||
func (s *Stack) NFTables() NFTablesInterface {
|
||||
return s.nftables
|
||||
val := s.nftables.Load()
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
return *val
|
||||
}
|
||||
|
||||
// SetNFTables sets the stack's nftables.
|
||||
func (s *Stack) SetNFTables(nft NFTablesInterface) {
|
||||
s.nftables = nft
|
||||
if nft == nil {
|
||||
s.nftables.Store(nil)
|
||||
} else {
|
||||
s.nftables.Store(&nft)
|
||||
}
|
||||
}
|
||||
|
||||
// LockNFTablesUpdate locks the stack's nftables update mutex for netlink batch modification.
|
||||
func (s *Stack) LockNFTablesUpdate() {
|
||||
s.nftablesUpdateMu.Lock()
|
||||
}
|
||||
|
||||
// UnlockNFTablesUpdate unlocks the stack's nftables update mutex.
|
||||
func (s *Stack) UnlockNFTablesUpdate() {
|
||||
s.nftablesUpdateMu.Unlock()
|
||||
}
|
||||
|
||||
// IsNFTablesConfigured returns true if the stack has nftables configured.
|
||||
func (s *Stack) IsNFTablesConfigured() bool {
|
||||
return s.nftablesConfigured.Load()
|
||||
}
|
||||
|
||||
// SetNFTablesConfigured sets whether the stack has nftables configured.
|
||||
func (s *Stack) SetNFTablesConfigured(configured bool) {
|
||||
s.nftablesConfigured.Store(configured)
|
||||
}
|
||||
|
||||
// ICMPLimit returns the maximum number of ICMP messages that can be sent
|
||||
|
|
@ -2460,12 +2581,11 @@ func (s *Stack) SetNICStack(id tcpip.NICID, peer *Stack) (tcpip.NICID, tcpip.Err
|
|||
s.mu.Unlock()
|
||||
return id, nil
|
||||
}
|
||||
delete(s.nics, id)
|
||||
|
||||
// Remove routes in-place. n tracks the number of routes written.
|
||||
s.RemoveRoutes(func(r tcpip.Route) bool { return r.NIC == id })
|
||||
ne := nic.NetworkLinkEndpoint.(LinkEndpoint)
|
||||
deferAct, err := nic.remove(false /* closeLinkEndpoint */)
|
||||
linkEp := nic.NetworkLinkEndpoint.(LinkEndpoint)
|
||||
name := nic.Name()
|
||||
|
||||
deferAct, err := s.removeNICLocked(id, false /* closeLinkEndpoint */)
|
||||
s.mu.Unlock()
|
||||
if deferAct != nil {
|
||||
deferAct()
|
||||
|
|
@ -2475,34 +2595,71 @@ func (s *Stack) SetNICStack(id tcpip.NICID, peer *Stack) (tcpip.NICID, tcpip.Err
|
|||
}
|
||||
|
||||
id = tcpip.NICID(peer.NextNICID())
|
||||
return id, peer.CreateNICWithOptions(id, ne, NICOptions{Name: nic.Name()})
|
||||
return id, peer.CreateNICWithOptions(id, linkEp, NICOptions{Name: name})
|
||||
}
|
||||
|
||||
// EnableSaveRestore marks the saveRestoreEnabled to true.
|
||||
func (s *Stack) EnableSaveRestore() {
|
||||
// SetRemoveConf sets the removeConf in stack to the given value.
|
||||
func (s *Stack) SetRemoveConf(removeConf bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.saveRestoreEnabled = true
|
||||
s.removeConf = removeConf
|
||||
}
|
||||
|
||||
// IsSaveRestoreEnabled returns true if save restore is enabled for the stack.
|
||||
func (s *Stack) IsSaveRestoreEnabled() bool {
|
||||
// GetRemoveConf gets the removeConf from stack.
|
||||
func (s *Stack) GetRemoveConf() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.removeConf
|
||||
}
|
||||
|
||||
// SetAllowConnectedOnSave sets allowConnectedOnSave in stack with the given value.
|
||||
func (s *Stack) SetAllowConnectedOnSave(allowConnectedOnSave bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
return s.saveRestoreEnabled
|
||||
s.allowConnectedOnSave = allowConnectedOnSave
|
||||
}
|
||||
|
||||
// contextID is this package's type for context.Context.Value keys.
|
||||
type contextID int
|
||||
|
||||
const (
|
||||
// CtxRestoreStack is a Context.Value key for the stack to be used in restore.
|
||||
CtxRestoreStack contextID = iota
|
||||
)
|
||||
|
||||
// RestoreStackFromContext returns the stack to be used during restore.
|
||||
func RestoreStackFromContext(ctx context.Context) *Stack {
|
||||
return ctx.Value(CtxRestoreStack).(*Stack)
|
||||
// GetAllowConnectedOnSave gets the allowConnectedOnSave from stack.
|
||||
func (s *Stack) GetAllowConnectedOnSave() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.allowConnectedOnSave
|
||||
}
|
||||
|
||||
// AllowLiveTCPMigration returns if TCP connections can be migrated.
|
||||
func (s *Stack) AllowLiveTCPMigration() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.allowLiveTCPMigration
|
||||
}
|
||||
|
||||
// SetAllowLiveTCPMigration sets if TCP connections can be migrated.
|
||||
func (s *Stack) SetAllowLiveTCPMigration(allow bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.allowLiveTCPMigration = allow
|
||||
}
|
||||
|
||||
// DisableAllNonLoopbackNICs disables all non-loopback NICs in the stack.
|
||||
func (s *Stack) DisableAllNonLoopbackNICs() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.externalNetworkingDisabled = true
|
||||
for _, nic := range s.nics {
|
||||
if !nic.IsLoopback() {
|
||||
nic.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EnableAllNonLoopbackNICs enables all non-loopback NICs in the stack.
|
||||
func (s *Stack) EnableAllNonLoopbackNICs() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.externalNetworkingDisabled = false
|
||||
for _, nic := range s.nics {
|
||||
if !nic.IsLoopback() {
|
||||
nic.enable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func stackinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
stackinitLockNames()
|
||||
stackprefixIndex = locking.NewMutexClass(reflect.TypeOf(stackRWMutex{}), stacklockNames)
|
||||
stackprefixIndex = locking.NewMutexClass(reflect.TypeFor[stackRWMutex](), stacklockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,14 +37,6 @@ const (
|
|||
defaultTCPInvalidRateLimit = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
// ReceiveBufferSizeOption is used by stack.(Stack*).Option/SetOption to
|
||||
// get/set the default, min and max receive buffer sizes.
|
||||
type ReceiveBufferSizeOption struct {
|
||||
Min int
|
||||
Default int
|
||||
Max int
|
||||
}
|
||||
|
||||
// TCPInvalidRateLimitOption is used by stack.(Stack*).Option/SetOption to get/set
|
||||
// stack.tcpInvalidRateLimit.
|
||||
type TCPInvalidRateLimitOption time.Duration
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ func (cn *conn) StateFields() []string {
|
|||
"destinationManip",
|
||||
"tcb",
|
||||
"lastUsed",
|
||||
"replySeen",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -260,6 +261,7 @@ func (cn *conn) StateSave(stateSinkObject state.Sink) {
|
|||
stateSinkObject.Save(5, &cn.destinationManip)
|
||||
stateSinkObject.Save(6, &cn.tcb)
|
||||
stateSinkObject.Save(7, &cn.lastUsed)
|
||||
stateSinkObject.Save(8, &cn.replySeen)
|
||||
}
|
||||
|
||||
func (cn *conn) afterLoad(context.Context) {}
|
||||
|
|
@ -274,6 +276,7 @@ func (cn *conn) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
|||
stateSourceObject.Load(5, &cn.destinationManip)
|
||||
stateSourceObject.Load(6, &cn.tcb)
|
||||
stateSourceObject.Load(7, &cn.lastUsed)
|
||||
stateSourceObject.Load(8, &cn.replySeen)
|
||||
}
|
||||
|
||||
func (ct *ConnTrack) StateTypeName() string {
|
||||
|
|
@ -283,6 +286,7 @@ func (ct *ConnTrack) StateTypeName() string {
|
|||
func (ct *ConnTrack) StateFields() []string {
|
||||
return []string{
|
||||
"seed",
|
||||
"nftIDSeed",
|
||||
"clock",
|
||||
"buckets",
|
||||
}
|
||||
|
|
@ -294,8 +298,9 @@ func (ct *ConnTrack) beforeSave() {}
|
|||
func (ct *ConnTrack) StateSave(stateSinkObject state.Sink) {
|
||||
ct.beforeSave()
|
||||
stateSinkObject.Save(0, &ct.seed)
|
||||
stateSinkObject.Save(1, &ct.clock)
|
||||
stateSinkObject.Save(2, &ct.buckets)
|
||||
stateSinkObject.Save(1, &ct.nftIDSeed)
|
||||
stateSinkObject.Save(2, &ct.clock)
|
||||
stateSinkObject.Save(3, &ct.buckets)
|
||||
}
|
||||
|
||||
func (ct *ConnTrack) afterLoad(context.Context) {}
|
||||
|
|
@ -303,8 +308,9 @@ func (ct *ConnTrack) afterLoad(context.Context) {}
|
|||
// +checklocksignore
|
||||
func (ct *ConnTrack) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &ct.seed)
|
||||
stateSourceObject.Load(1, &ct.clock)
|
||||
stateSourceObject.Load(2, &ct.buckets)
|
||||
stateSourceObject.Load(1, &ct.nftIDSeed)
|
||||
stateSourceObject.Load(2, &ct.clock)
|
||||
stateSourceObject.Load(3, &ct.buckets)
|
||||
}
|
||||
|
||||
func (bkt *bucket) StateTypeName() string {
|
||||
|
|
@ -339,6 +345,8 @@ func (l *ICMPRateLimiter) StateTypeName() string {
|
|||
func (l *ICMPRateLimiter) StateFields() []string {
|
||||
return []string{
|
||||
"clock",
|
||||
"limit",
|
||||
"burst",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -348,13 +356,16 @@ func (l *ICMPRateLimiter) beforeSave() {}
|
|||
func (l *ICMPRateLimiter) StateSave(stateSinkObject state.Sink) {
|
||||
l.beforeSave()
|
||||
stateSinkObject.Save(0, &l.clock)
|
||||
stateSinkObject.Save(1, &l.limit)
|
||||
stateSinkObject.Save(2, &l.burst)
|
||||
}
|
||||
|
||||
func (l *ICMPRateLimiter) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (l *ICMPRateLimiter) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &l.clock)
|
||||
stateSourceObject.Load(1, &l.limit)
|
||||
stateSourceObject.Load(2, &l.burst)
|
||||
stateSourceObject.AfterLoad(func() { l.afterLoad(ctx) })
|
||||
}
|
||||
|
||||
func (a *AcceptTarget) StateTypeName() string {
|
||||
|
|
@ -668,6 +679,34 @@ func (mt *MasqueradeTarget) StateLoad(ctx context.Context, stateSourceObject sta
|
|||
stateSourceObject.Load(0, &mt.NetworkProtocol)
|
||||
}
|
||||
|
||||
func (c *CTTarget) StateTypeName() string {
|
||||
return "pkg/tcpip/stack.CTTarget"
|
||||
}
|
||||
|
||||
func (c *CTTarget) StateFields() []string {
|
||||
return []string{
|
||||
"NetworkProtocol",
|
||||
"Zone",
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CTTarget) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (c *CTTarget) StateSave(stateSinkObject state.Sink) {
|
||||
c.beforeSave()
|
||||
stateSinkObject.Save(0, &c.NetworkProtocol)
|
||||
stateSinkObject.Save(1, &c.Zone)
|
||||
}
|
||||
|
||||
func (c *CTTarget) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (c *CTTarget) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &c.NetworkProtocol)
|
||||
stateSourceObject.Load(1, &c.Zone)
|
||||
}
|
||||
|
||||
func (it *IPTables) StateTypeName() string {
|
||||
return "pkg/tcpip/stack.IPTables"
|
||||
}
|
||||
|
|
@ -675,7 +714,6 @@ func (it *IPTables) StateTypeName() string {
|
|||
func (it *IPTables) StateFields() []string {
|
||||
return []string{
|
||||
"connections",
|
||||
"reaper",
|
||||
"v4Tables",
|
||||
"v6Tables",
|
||||
"modified",
|
||||
|
|
@ -686,19 +724,17 @@ func (it *IPTables) StateFields() []string {
|
|||
func (it *IPTables) StateSave(stateSinkObject state.Sink) {
|
||||
it.beforeSave()
|
||||
stateSinkObject.Save(0, &it.connections)
|
||||
stateSinkObject.Save(1, &it.reaper)
|
||||
stateSinkObject.Save(2, &it.v4Tables)
|
||||
stateSinkObject.Save(3, &it.v6Tables)
|
||||
stateSinkObject.Save(4, &it.modified)
|
||||
stateSinkObject.Save(1, &it.v4Tables)
|
||||
stateSinkObject.Save(2, &it.v6Tables)
|
||||
stateSinkObject.Save(3, &it.modified)
|
||||
}
|
||||
|
||||
// +checklocksignore
|
||||
func (it *IPTables) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &it.connections)
|
||||
stateSourceObject.Load(1, &it.reaper)
|
||||
stateSourceObject.Load(2, &it.v4Tables)
|
||||
stateSourceObject.Load(3, &it.v6Tables)
|
||||
stateSourceObject.Load(4, &it.modified)
|
||||
stateSourceObject.Load(1, &it.v4Tables)
|
||||
stateSourceObject.Load(2, &it.v6Tables)
|
||||
stateSourceObject.Load(3, &it.modified)
|
||||
stateSourceObject.AfterLoad(func() { it.afterLoad(ctx) })
|
||||
}
|
||||
|
||||
|
|
@ -1530,8 +1566,10 @@ func (pk *PacketBuffer) StateFields() []string {
|
|||
"dnatDone",
|
||||
"PktType",
|
||||
"NICID",
|
||||
"InputNICID",
|
||||
"RXChecksumValidated",
|
||||
"NetworkPacketInfo",
|
||||
"Mark",
|
||||
"tuple",
|
||||
}
|
||||
}
|
||||
|
|
@ -1557,9 +1595,11 @@ func (pk *PacketBuffer) StateSave(stateSinkObject state.Sink) {
|
|||
stateSinkObject.Save(13, &pk.dnatDone)
|
||||
stateSinkObject.Save(14, &pk.PktType)
|
||||
stateSinkObject.Save(15, &pk.NICID)
|
||||
stateSinkObject.Save(16, &pk.RXChecksumValidated)
|
||||
stateSinkObject.Save(17, &pk.NetworkPacketInfo)
|
||||
stateSinkObject.Save(18, &pk.tuple)
|
||||
stateSinkObject.Save(16, &pk.InputNICID)
|
||||
stateSinkObject.Save(17, &pk.RXChecksumValidated)
|
||||
stateSinkObject.Save(18, &pk.NetworkPacketInfo)
|
||||
stateSinkObject.Save(19, &pk.Mark)
|
||||
stateSinkObject.Save(20, &pk.tuple)
|
||||
}
|
||||
|
||||
func (pk *PacketBuffer) afterLoad(context.Context) {}
|
||||
|
|
@ -1582,9 +1622,11 @@ func (pk *PacketBuffer) StateLoad(ctx context.Context, stateSourceObject state.S
|
|||
stateSourceObject.Load(13, &pk.dnatDone)
|
||||
stateSourceObject.Load(14, &pk.PktType)
|
||||
stateSourceObject.Load(15, &pk.NICID)
|
||||
stateSourceObject.Load(16, &pk.RXChecksumValidated)
|
||||
stateSourceObject.Load(17, &pk.NetworkPacketInfo)
|
||||
stateSourceObject.Load(18, &pk.tuple)
|
||||
stateSourceObject.Load(16, &pk.InputNICID)
|
||||
stateSourceObject.Load(17, &pk.RXChecksumValidated)
|
||||
stateSourceObject.Load(18, &pk.NetworkPacketInfo)
|
||||
stateSourceObject.Load(19, &pk.Mark)
|
||||
stateSourceObject.Load(20, &pk.tuple)
|
||||
}
|
||||
|
||||
func (h *headerInfo) StateTypeName() string {
|
||||
|
|
@ -2093,12 +2135,12 @@ func (s *Stack) StateFields() []string {
|
|||
"packetEndpointWriteSupported",
|
||||
"demux",
|
||||
"stats",
|
||||
"loopbackNIC",
|
||||
"defaultForwardingEnabled",
|
||||
"cleanupEndpoints",
|
||||
"PortManager",
|
||||
"clock",
|
||||
"handleLocal",
|
||||
"nftablesConfigured",
|
||||
"restoredEndpoints",
|
||||
"resumableEndpoints",
|
||||
"icmpRateLimiter",
|
||||
|
|
@ -2109,12 +2151,11 @@ func (s *Stack) StateFields() []string {
|
|||
"receiveBufferSize",
|
||||
"tcpInvalidRateLimit",
|
||||
"tsOffsetSecret",
|
||||
"saveRestoreEnabled",
|
||||
"externalNetworkingDisabled",
|
||||
"allowConnectedOnSave",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stack) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (s *Stack) StateSave(stateSinkObject state.Sink) {
|
||||
s.beforeSave()
|
||||
|
|
@ -2124,12 +2165,12 @@ func (s *Stack) StateSave(stateSinkObject state.Sink) {
|
|||
stateSinkObject.Save(3, &s.packetEndpointWriteSupported)
|
||||
stateSinkObject.Save(4, &s.demux)
|
||||
stateSinkObject.Save(5, &s.stats)
|
||||
stateSinkObject.Save(6, &s.loopbackNIC)
|
||||
stateSinkObject.Save(7, &s.defaultForwardingEnabled)
|
||||
stateSinkObject.Save(8, &s.cleanupEndpoints)
|
||||
stateSinkObject.Save(9, &s.PortManager)
|
||||
stateSinkObject.Save(10, &s.clock)
|
||||
stateSinkObject.Save(11, &s.handleLocal)
|
||||
stateSinkObject.Save(6, &s.defaultForwardingEnabled)
|
||||
stateSinkObject.Save(7, &s.cleanupEndpoints)
|
||||
stateSinkObject.Save(8, &s.PortManager)
|
||||
stateSinkObject.Save(9, &s.clock)
|
||||
stateSinkObject.Save(10, &s.handleLocal)
|
||||
stateSinkObject.Save(11, &s.nftablesConfigured)
|
||||
stateSinkObject.Save(12, &s.restoredEndpoints)
|
||||
stateSinkObject.Save(13, &s.resumableEndpoints)
|
||||
stateSinkObject.Save(14, &s.icmpRateLimiter)
|
||||
|
|
@ -2140,7 +2181,8 @@ func (s *Stack) StateSave(stateSinkObject state.Sink) {
|
|||
stateSinkObject.Save(19, &s.receiveBufferSize)
|
||||
stateSinkObject.Save(20, &s.tcpInvalidRateLimit)
|
||||
stateSinkObject.Save(21, &s.tsOffsetSecret)
|
||||
stateSinkObject.Save(22, &s.saveRestoreEnabled)
|
||||
stateSinkObject.Save(22, &s.externalNetworkingDisabled)
|
||||
stateSinkObject.Save(23, &s.allowConnectedOnSave)
|
||||
}
|
||||
|
||||
// +checklocksignore
|
||||
|
|
@ -2151,12 +2193,12 @@ func (s *Stack) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
|||
stateSourceObject.Load(3, &s.packetEndpointWriteSupported)
|
||||
stateSourceObject.Load(4, &s.demux)
|
||||
stateSourceObject.Load(5, &s.stats)
|
||||
stateSourceObject.Load(6, &s.loopbackNIC)
|
||||
stateSourceObject.Load(7, &s.defaultForwardingEnabled)
|
||||
stateSourceObject.Load(8, &s.cleanupEndpoints)
|
||||
stateSourceObject.Load(9, &s.PortManager)
|
||||
stateSourceObject.Load(10, &s.clock)
|
||||
stateSourceObject.Load(11, &s.handleLocal)
|
||||
stateSourceObject.Load(6, &s.defaultForwardingEnabled)
|
||||
stateSourceObject.Load(7, &s.cleanupEndpoints)
|
||||
stateSourceObject.Load(8, &s.PortManager)
|
||||
stateSourceObject.Load(9, &s.clock)
|
||||
stateSourceObject.Load(10, &s.handleLocal)
|
||||
stateSourceObject.Load(11, &s.nftablesConfigured)
|
||||
stateSourceObject.Load(12, &s.restoredEndpoints)
|
||||
stateSourceObject.Load(13, &s.resumableEndpoints)
|
||||
stateSourceObject.Load(14, &s.icmpRateLimiter)
|
||||
|
|
@ -2167,7 +2209,8 @@ func (s *Stack) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
|||
stateSourceObject.Load(19, &s.receiveBufferSize)
|
||||
stateSourceObject.Load(20, &s.tcpInvalidRateLimit)
|
||||
stateSourceObject.Load(21, &s.tsOffsetSecret)
|
||||
stateSourceObject.Load(22, &s.saveRestoreEnabled)
|
||||
stateSourceObject.Load(22, &s.externalNetworkingDisabled)
|
||||
stateSourceObject.Load(23, &s.allowConnectedOnSave)
|
||||
stateSourceObject.AfterLoad(func() { s.afterLoad(ctx) })
|
||||
}
|
||||
|
||||
|
|
@ -2442,6 +2485,7 @@ func init() {
|
|||
state.Register((*RedirectTarget)(nil))
|
||||
state.Register((*SNATTarget)(nil))
|
||||
state.Register((*MasqueradeTarget)(nil))
|
||||
state.Register((*CTTarget)(nil))
|
||||
state.Register((*IPTables)(nil))
|
||||
state.Register((*Table)(nil))
|
||||
state.Register((*Rule)(nil))
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func stateConninitLockNames() {}
|
|||
|
||||
func init() {
|
||||
stateConninitLockNames()
|
||||
stateConnprefixIndex = locking.NewMutexClass(reflect.TypeOf(stateConnRWMutex{}), stateConnlockNames)
|
||||
stateConnprefixIndex = locking.NewMutexClass(reflect.TypeFor[stateConnRWMutex](), stateConnlockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,5 +92,5 @@ func transportEndpointsinitLockNames() {}
|
|||
|
||||
func init() {
|
||||
transportEndpointsinitLockNames()
|
||||
transportEndpointsprefixIndex = locking.NewMutexClass(reflect.TypeOf(transportEndpointsRWMutex{}), transportEndpointslockNames)
|
||||
transportEndpointsprefixIndex = locking.NewMutexClass(reflect.TypeFor[transportEndpointsRWMutex](), transportEndpointslockNames)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,6 @@ func (*stdClock) AfterFunc(d time.Duration, f func()) Timer {
|
|||
}
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type stdTimer struct {
|
||||
t *time.Timer
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,10 +49,8 @@ import (
|
|||
|
||||
// Using the header package here would cause an import cycle.
|
||||
const (
|
||||
ipv4AddressSize = 4
|
||||
ipv4ProtocolNumber = 0x0800
|
||||
ipv6AddressSize = 16
|
||||
ipv6ProtocolNumber = 0x86dd
|
||||
ipv4AddressSize = 4
|
||||
ipv6AddressSize = 16
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -714,6 +712,10 @@ type ReadOptions struct {
|
|||
// NeedLinkPacketInfo indicates whether to return the link-layer information,
|
||||
// if supported.
|
||||
NeedLinkPacketInfo bool
|
||||
|
||||
// NeedRecvdExperimentOption indicates whether to return the experiment
|
||||
// option value from the last received packet, if supported.
|
||||
NeedReceivedExperimentOption bool
|
||||
}
|
||||
|
||||
// ReadResult represents result for a successful Endpoint.Read.
|
||||
|
|
@ -734,6 +736,10 @@ type ReadResult struct {
|
|||
// LinkPacketInfo is the link-layer information of the received packet if
|
||||
// ReadOptions.NeedLinkPacketInfo is true.
|
||||
LinkPacketInfo LinkPacketInfo
|
||||
|
||||
// ReceivedExperimentOption is the experiment option value from the last
|
||||
// received packet if ReadOptions.NeedReceivedExperimentOption is true.
|
||||
ReceivedExperimentOption uint16
|
||||
}
|
||||
|
||||
// Endpoint is the interface implemented by transport protocols (e.g., tcp, udp)
|
||||
|
|
@ -951,9 +957,8 @@ const (
|
|||
|
||||
// MTUDiscoverOption is used to set/get the path MTU discovery setting.
|
||||
//
|
||||
// NOTE: Setting this option to any other value than PMTUDiscoveryDont
|
||||
// is not supported and will fail as such, and getting this option will
|
||||
// always return PMTUDiscoveryDont.
|
||||
// The value controls whether the Don't Fragment (DF) bit is set on
|
||||
// outgoing IPv4 packets.
|
||||
MTUDiscoverOption
|
||||
|
||||
// MulticastTTLOption is used by SetSockOptInt/GetSockOptInt to control
|
||||
|
|
@ -1005,6 +1010,10 @@ const (
|
|||
// PacketMMapReserveOption is used to set the packet mmap reserved space
|
||||
// between the aligned header and the payload.
|
||||
PacketMMapReserveOption
|
||||
|
||||
// IPv6MulticastInterfaceOption is used to set/get the NIC used for
|
||||
// IPv6 multicast Tx.
|
||||
IPv6MulticastInterfaceOption
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -1097,9 +1097,10 @@ func (so *SocketOptions) StateFields() []string {
|
|||
"bindToDevice",
|
||||
"sendBufferSize",
|
||||
"receiveBufferSize",
|
||||
"linger",
|
||||
"rcvlowat",
|
||||
"experimentOptionValue",
|
||||
"mark",
|
||||
"linger",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1134,9 +1135,10 @@ func (so *SocketOptions) StateSave(stateSinkObject state.Sink) {
|
|||
stateSinkObject.Save(23, &so.bindToDevice)
|
||||
stateSinkObject.Save(24, &so.sendBufferSize)
|
||||
stateSinkObject.Save(25, &so.receiveBufferSize)
|
||||
stateSinkObject.Save(26, &so.linger)
|
||||
stateSinkObject.Save(27, &so.rcvlowat)
|
||||
stateSinkObject.Save(28, &so.experimentOptionValue)
|
||||
stateSinkObject.Save(26, &so.rcvlowat)
|
||||
stateSinkObject.Save(27, &so.experimentOptionValue)
|
||||
stateSinkObject.Save(28, &so.mark)
|
||||
stateSinkObject.Save(29, &so.linger)
|
||||
}
|
||||
|
||||
func (so *SocketOptions) afterLoad(context.Context) {}
|
||||
|
|
@ -1169,9 +1171,10 @@ func (so *SocketOptions) StateLoad(ctx context.Context, stateSourceObject state.
|
|||
stateSourceObject.Load(23, &so.bindToDevice)
|
||||
stateSourceObject.Load(24, &so.sendBufferSize)
|
||||
stateSourceObject.Load(25, &so.receiveBufferSize)
|
||||
stateSourceObject.Load(26, &so.linger)
|
||||
stateSourceObject.Load(27, &so.rcvlowat)
|
||||
stateSourceObject.Load(28, &so.experimentOptionValue)
|
||||
stateSourceObject.Load(26, &so.rcvlowat)
|
||||
stateSourceObject.Load(27, &so.experimentOptionValue)
|
||||
stateSourceObject.Load(28, &so.mark)
|
||||
stateSourceObject.Load(29, &so.linger)
|
||||
}
|
||||
|
||||
func (l *LocalSockError) StateTypeName() string {
|
||||
|
|
@ -1264,31 +1267,6 @@ func (s *stdClock) StateLoad(ctx context.Context, stateSourceObject state.Source
|
|||
stateSourceObject.AfterLoad(func() { s.afterLoad(ctx) })
|
||||
}
|
||||
|
||||
func (st *stdTimer) StateTypeName() string {
|
||||
return "pkg/tcpip.stdTimer"
|
||||
}
|
||||
|
||||
func (st *stdTimer) StateFields() []string {
|
||||
return []string{
|
||||
"t",
|
||||
}
|
||||
}
|
||||
|
||||
func (st *stdTimer) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (st *stdTimer) StateSave(stateSinkObject state.Sink) {
|
||||
st.beforeSave()
|
||||
stateSinkObject.Save(0, &st.t)
|
||||
}
|
||||
|
||||
func (st *stdTimer) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (st *stdTimer) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &st.t)
|
||||
}
|
||||
|
||||
func (mt *MonotonicTime) StateTypeName() string {
|
||||
return "pkg/tcpip.MonotonicTime"
|
||||
}
|
||||
|
|
@ -1505,8 +1483,8 @@ func (c *ReceivableControlMessages) beforeSave() {}
|
|||
// +checklocksignore
|
||||
func (c *ReceivableControlMessages) StateSave(stateSinkObject state.Sink) {
|
||||
c.beforeSave()
|
||||
var TimestampValue int64
|
||||
TimestampValue = c.saveTimestamp()
|
||||
TimestampValue := c.saveTimestamp()
|
||||
_ = (int64)(TimestampValue)
|
||||
stateSinkObject.SaveValue(0, TimestampValue)
|
||||
stateSinkObject.Save(1, &c.HasInq)
|
||||
stateSinkObject.Save(2, &c.Inq)
|
||||
|
|
@ -3224,7 +3202,6 @@ func (j *jobInstance) StateTypeName() string {
|
|||
|
||||
func (j *jobInstance) StateFields() []string {
|
||||
return []string{
|
||||
"timer",
|
||||
"earlyReturn",
|
||||
}
|
||||
}
|
||||
|
|
@ -3234,16 +3211,14 @@ func (j *jobInstance) beforeSave() {}
|
|||
// +checklocksignore
|
||||
func (j *jobInstance) StateSave(stateSinkObject state.Sink) {
|
||||
j.beforeSave()
|
||||
stateSinkObject.Save(0, &j.timer)
|
||||
stateSinkObject.Save(1, &j.earlyReturn)
|
||||
stateSinkObject.Save(0, &j.earlyReturn)
|
||||
}
|
||||
|
||||
func (j *jobInstance) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (j *jobInstance) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &j.timer)
|
||||
stateSourceObject.Load(1, &j.earlyReturn)
|
||||
stateSourceObject.Load(0, &j.earlyReturn)
|
||||
}
|
||||
|
||||
func (j *Job) StateTypeName() string {
|
||||
|
|
@ -3328,7 +3303,6 @@ func init() {
|
|||
state.Register((*LocalSockError)(nil))
|
||||
state.Register((*SockError)(nil))
|
||||
state.Register((*stdClock)(nil))
|
||||
state.Register((*stdTimer)(nil))
|
||||
state.Register((*MonotonicTime)(nil))
|
||||
state.Register((*Address)(nil))
|
||||
state.Register((*AddressMask)(nil))
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ import (
|
|||
//
|
||||
// +stateify savable
|
||||
type jobInstance struct {
|
||||
timer Timer
|
||||
timer Timer `state:"nosave"`
|
||||
|
||||
// Used to inform the timer to early return when it gets stopped while the
|
||||
// lock the timer tries to obtain when fired is held (T1 is a goroutine that
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue