gvisor-lx/pkg/tcpip/transport/tcp/endpoint.go
Leadaxe 117243aa02 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-паникой, что в полевом крашдампе; с ним зелёный.
2026-08-05 14:53:31 +03:00

3370 lines
103 KiB
Go

// Copyright 2018 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 tcp
import (
"container/heap"
"fmt"
"io"
"math"
"runtime"
"strings"
"time"
"github.com/sagernet/gvisor/pkg/atomicbitops"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/sleep"
"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"
"github.com/sagernet/gvisor/pkg/tcpip/seqnum"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/waiter"
)
// EndpointState represents the state of a TCP endpoint.
type EndpointState tcpip.EndpointState
// Endpoint states. Note that are represented in a netstack-specific manner and
// may not be meaningful externally. Specifically, they need to be translated to
// Linux's representation for these states if presented to userspace.
const (
_ EndpointState = iota
// TCP protocol states in sync with the definitions in
// https://github.com/torvalds/linux/blob/7acac4b3196/include/net/tcp_states.h#L13
StateEstablished
StateSynSent
StateSynRecv
StateFinWait1
StateFinWait2
StateTimeWait
StateClose
StateCloseWait
StateLastAck
StateListen
StateClosing
// Endpoint states internal to netstack.
StateInitial
StateBound
StateConnecting // Connect() called, but the initial SYN hasn't been sent.
StateError
)
const (
// rcvAdvWndScale is used to split the available socket buffer into
// application buffer and the window to be advertised to the peer. This is
// currently hard coded to split the available space equally.
rcvAdvWndScale = 1
// SegOverheadFactor is used to multiply the value provided by the
// user on a SetSockOpt for setting the socket send/receive buffer sizes.
SegOverheadFactor = 2
)
type connDirectionState uint32
// Connection direction states used for directionState checks in endpoint struct
// to detect half-closed connection and deliver POLLRDHUP
const (
connDirectionStateOpen connDirectionState = 0
connDirectionStateRcvClosed connDirectionState = 1
connDirectionStateSndClosed connDirectionState = 2
connDirectionStateAll connDirectionState = connDirectionStateOpen | connDirectionStateRcvClosed | connDirectionStateSndClosed
)
// connected returns true when s is one of the states representing an
// endpoint connected to a peer.
func (s EndpointState) connected() bool {
switch s {
case StateEstablished, StateFinWait1, StateFinWait2, StateTimeWait, StateCloseWait, StateLastAck, StateClosing:
return true
default:
return false
}
}
// connecting returns true when s is one of the states representing a
// connection in progress, but not yet fully established.
func (s EndpointState) connecting() bool {
switch s {
case StateConnecting, StateSynSent, StateSynRecv:
return true
default:
return false
}
}
// internal returns true when the state is netstack internal.
func (s EndpointState) internal() bool {
switch s {
case StateInitial, StateBound, StateConnecting, StateError:
return true
default:
return false
}
}
// handshake returns true when s is one of the states representing an endpoint
// in the middle of a TCP handshake.
func (s EndpointState) handshake() bool {
switch s {
case StateSynSent, StateSynRecv:
return true
default:
return false
}
}
// closed returns true when s is one of the states an endpoint transitions to
// when closed or when it encounters an error. This is distinct from a newly
// initialized endpoint that was never connected.
func (s EndpointState) closed() bool {
switch s {
case StateClose, StateError:
return true
default:
return false
}
}
// String implements fmt.Stringer.String.
func (s EndpointState) String() string {
switch s {
case StateInitial:
return "INITIAL"
case StateBound:
return "BOUND"
case StateConnecting:
return "CONNECTING"
case StateError:
return "ERROR"
case StateEstablished:
return "ESTABLISHED"
case StateSynSent:
return "SYN-SENT"
case StateSynRecv:
return "SYN-RCVD"
case StateFinWait1:
return "FIN-WAIT1"
case StateFinWait2:
return "FIN-WAIT2"
case StateTimeWait:
return "TIME-WAIT"
case StateClose:
return "CLOSED"
case StateCloseWait:
return "CLOSE-WAIT"
case StateLastAck:
return "LAST-ACK"
case StateListen:
return "LISTEN"
case StateClosing:
return "CLOSING"
default:
panic("unreachable")
}
}
// SACKInfo holds TCP SACK related information for a given endpoint.
//
// +stateify savable
type SACKInfo struct {
// Blocks is the maximum number of SACK blocks we track
// per endpoint.
Blocks [MaxSACKBlocks]header.SACKBlock
// NumBlocks is the number of valid SACK blocks stored in the
// blocks array above.
NumBlocks int
}
// ReceiveErrors collect segment receive errors within transport layer.
//
// +stateify savable
type ReceiveErrors struct {
tcpip.ReceiveErrors
// SegmentQueueDropped is the number of segments dropped due to
// a full segment queue.
SegmentQueueDropped tcpip.StatCounter
// ChecksumErrors is the number of segments dropped due to bad checksums.
ChecksumErrors tcpip.StatCounter
// ListenOverflowSynDrop is the number of times the listen queue overflowed
// and a SYN was dropped.
ListenOverflowSynDrop tcpip.StatCounter
// ListenOverflowAckDrop is the number of times the final ACK
// in the handshake was dropped due to overflow.
ListenOverflowAckDrop tcpip.StatCounter
// ZeroRcvWindowState is the number of times we advertised
// a zero receive window when rcvQueue is full.
ZeroRcvWindowState tcpip.StatCounter
// WantZeroWindow is the number of times we wanted to advertise a
// zero receive window but couldn't because it would have caused
// the receive window's right edge to shrink.
WantZeroRcvWindow tcpip.StatCounter
}
// SendErrors collect segment send errors within the transport layer.
//
// +stateify savable
type SendErrors struct {
tcpip.SendErrors
// SegmentSendToNetworkFailed is the number of TCP segments failed to be sent
// to the network endpoint.
SegmentSendToNetworkFailed tcpip.StatCounter
// SynSendToNetworkFailed is the number of TCP SYNs failed to be sent
// to the network endpoint.
SynSendToNetworkFailed tcpip.StatCounter
// Retransmits is the number of TCP segments retransmitted.
Retransmits tcpip.StatCounter
// FastRetransmit is the number of segments retransmitted in fast
// recovery.
FastRetransmit tcpip.StatCounter
// Timeouts is the number of times the RTO expired.
Timeouts tcpip.StatCounter
}
// Stats holds statistics about the endpoint.
//
// +stateify savable
type Stats struct {
// SegmentsReceived is the number of TCP segments received that
// the transport layer successfully parsed.
SegmentsReceived tcpip.StatCounter
// SegmentsSent is the number of TCP segments sent.
SegmentsSent tcpip.StatCounter
// FailedConnectionAttempts is the number of times we saw Connect and
// Accept errors.
FailedConnectionAttempts tcpip.StatCounter
// ReceiveErrors collects segment receive errors within the
// transport layer.
ReceiveErrors ReceiveErrors
// ReadErrors collects segment read errors from an endpoint read call.
ReadErrors tcpip.ReadErrors
// SendErrors collects segment send errors within the transport layer.
SendErrors SendErrors
// WriteErrors collects segment write errors from an endpoint write call.
WriteErrors tcpip.WriteErrors
}
// IsEndpointStats is an empty method to implement the tcpip.EndpointStats
// marker interface.
func (*Stats) IsEndpointStats() {}
// sndQueueInfo implements a send queue.
//
// +stateify savable
type sndQueueInfo struct {
sndQueueMu sndQueueMutex `state:"nosave"`
TCPSndBufState
}
// CloneState clones sq into other. It is not thread safe
func (sq *sndQueueInfo) CloneState(other *TCPSndBufState) {
other.SndBufSize = sq.SndBufSize
other.SndBufUsed = sq.SndBufUsed
other.SndClosed = sq.SndClosed
other.PacketTooBigCount = sq.PacketTooBigCount
other.SndMTU = sq.SndMTU
other.AutoTuneSndBufDisabled = atomicbitops.FromUint32(sq.AutoTuneSndBufDisabled.RacyLoad())
}
// Endpoint represents a TCP endpoint. This struct serves as the interface
// between users of the endpoint and the protocol implementation; it is legal to
// have concurrent goroutines make calls into the endpoint, they are properly
// synchronized. The protocol implementation, however, runs in a single
// goroutine.
//
// Each endpoint has a few mutexes:
//
// e.mu -> Primary mutex for an endpoint must be held for all operations except
// in e.Readiness where acquiring it will result in a deadlock in epoll
// implementation.
//
// The following three mutexes can be acquired independent of e.mu but if
// acquired with e.mu then e.mu must be acquired first.
//
// e.acceptMu -> Protects e.acceptQueue.
// e.rcvQueueMu -> Protects e.rcvQueue's associated fields but not e.rcvQueue
// itself.
// e.sndQueueMu -> Protects the e.sndQueue and associated fields.
// e.lastErrorMu -> Protects the lastError field.
//
// LOCKING/UNLOCKING of the endpoint. The locking of an endpoint is different
// based on the context in which the lock is acquired. In the syscall context
// e.LockUser/e.UnlockUser should be used and when doing background processing
// e.mu.Lock/e.mu.Unlock should be used. The distinction is described below
// in brief.
//
// The reason for this locking behaviour is to avoid wakeups to handle packets.
// In cases where the endpoint is already locked the background processor can
// queue the packet up and go its merry way and the lock owner will eventually
// process the backlog when releasing the lock. Similarly when acquiring the
// lock from say a syscall goroutine we can implement a bit of spinning if we
// know that the lock is not held by another syscall goroutine. Background
// processors should never hold the lock for long and we can avoid an expensive
// sleep/wakeup by spinning for a shortwhile.
//
// For more details please see the detailed documentation on
// e.LockUser/e.UnlockUser methods.
//
// TODO(b/339664055): Checklocks should be used more extensively here. Coverage
// is currently sparse.
//
// +checklocksalias:snd.ep.mu=mu
// +checklocksalias:rcv.ep.mu=mu
// +checklocksalias:h.ep.mu=mu
// +stateify savable
type Endpoint struct {
TCPEndpointStateInner
stack.TransportEndpointInfo
tcpip.DefaultSocketOptionsHandler
// EndpointEntry is used to queue endpoints for processing to the
// a given tcp processor goroutine.
//
// Precondition: epQueue.mu must be held to read/write this field..
endpointEntry `state:"nosave"`
// pendingProcessingMu protects pendingProcessing.
pendingProcessingMu pendingProcessingMutex `state:"nosave"`
// pendingProcessing is true if this endpoint is queued for processing
// to a TCP processor.
// +checklocks:pendingProcessingMu
pendingProcessing bool `state:"nosave"`
// The following fields are initialized at creation time and do not
// change throughout the lifetime of the endpoint.
stack *stack.Stack
protocol *protocol
waiterQueue *waiter.Queue `state:"wait"`
// hardError is meaningful only when state is stateError. It stores the
// error to be returned when read/write syscalls are called and the
// endpoint is in this state. hardError is protected by endpoint mu.
hardError tcpip.Error
// lastError represents the last error that the endpoint reported;
// access to it is protected by the following mutex.
lastErrorMu lastErrorMutex `state:"nosave"`
lastError tcpip.Error
rcvQueueMu rcvQueueMutex `state:"nosave"`
// +checklocks:rcvQueueMu
TCPRcvBufState
// rcvMemUsed tracks the total amount of memory in use by received segments
// held in rcvQueue, pendingRcvdSegments and the segment queue. This is used to
// compute the window and the actual available buffer space. This is distinct
// from rcvBufUsed above which is the actual number of payload bytes held in
// the buffer not including any segment overheads.
rcvMemUsed atomicbitops.Int32
// mu protects all endpoint fields unless documented otherwise. mu must
// be acquired before interacting with the endpoint fields.
//
// During handshake, mu is locked by the protocol listen goroutine and
// released by the handshake completion goroutine.
mu sync.CrossGoroutineMutex `state:"nosave"`
ownedByUser atomicbitops.Uint32
// rcvQueue is the queue for ready-for-delivery segments.
//
// +checklocks:mu
rcvQueue segmentList `state:"wait"`
// state must be read/set using the EndpointState()/setEndpointState()
// methods.
state atomicbitops.Uint32 `state:".(EndpointState)"`
// connectionDirectionState holds current state of send and receive,
// accessed atomically
connectionDirectionState atomicbitops.Uint32
// origEndpointState is only used during a restore phase to save the
// endpoint state at restore time as the socket is moved to it's correct
// state.
origEndpointState uint32 `state:"nosave"`
isPortReserved bool
isRegistered bool
boundNICID tcpip.NICID
route *stack.Route `state:"nosave"`
ipv4TTL uint8
ipv6HopLimit int16
isConnectNotified bool
// h stores a reference to the current handshake state if the endpoint is in
// the SYN-SENT or SYN-RECV states, in which case endpoint == endpoint.h.ep.
// nil otherwise.
// +checklocks:mu
h *handshake
// portFlags stores the current values of port related flags.
portFlags ports.Flags
// Values used to reserve a port or register a transport endpoint
// (which ever happens first).
boundBindToDevice tcpip.NICID
boundPortFlags ports.Flags
boundDest tcpip.FullAddress
// effectiveNetProtos contains the network protocols actually in use. In
// most cases it will only contain "netProto", but in cases like IPv6
// endpoints with v6only set to false, this could include multiple
// protocols (e.g., IPv6 and IPv4) or a single different protocol (e.g.,
// IPv4 when IPv6 endpoint is bound or connected to an IPv4 mapped
// address).
effectiveNetProtos []tcpip.NetworkProtocolNumber
// recentTSTime is the unix time when we last updated
// TCPEndpointStateInner.RecentTS.
recentTSTime tcpip.MonotonicTime
// shutdownFlags represent the current shutdown state of the endpoint.
shutdownFlags tcpip.ShutdownFlags
// tcpRecovery is the loss recovery algorithm used by TCP.
tcpRecovery tcpip.TCPRecovery
// sack holds TCP SACK related information for this endpoint.
sack SACKInfo
// delay enables Nagle's algorithm.
//
// delay is a boolean (0 is false) and must be accessed atomically.
delay uint32
// scoreboard holds TCP SACK Scoreboard information for this endpoint.
scoreboard *SACKScoreboard
// segmentQueue is used to hand received segments to the protocol
// goroutine. Segments are queued as long as the queue is not full,
// and dropped when it is.
segmentQueue segmentQueue `state:"wait"`
// userMSS if non-zero is the MSS value explicitly set by the user
// for this endpoint using the TCP_MAXSEG setsockopt.
userMSS uint16
// maxSynRetries is the maximum number of SYN retransmits that TCP should
// send before aborting the attempt to connect. It cannot exceed 255.
//
// NOTE: This is currently a no-op and does not change the SYN
// retransmissions.
maxSynRetries uint8
// windowClamp is used to bound the size of the advertised window to
// this value.
windowClamp uint32
// sndQueueInfo contains the implementation of the endpoint's send queue.
sndQueueInfo sndQueueInfo
// cc stores the name of the Congestion Control algorithm to use for
// this endpoint.
cc tcpip.CongestionControlOption
// keepalive manages TCP keepalive state. When the connection is idle
// (no data sent or received) for keepaliveIdle, we start sending
// keepalives every keepalive.interval. If we send keepalive.count
// without hearing a response, the connection is closed.
keepalive keepalive
// userTimeout if non-zero specifies a user specified timeout for
// a connection w/ pending data to send. A connection that has pending
// unacked data will be forcibily aborted if the timeout is reached
// without any data being acked.
userTimeout time.Duration
// deferAccept if non-zero specifies a user specified time during
// which the final ACK of a handshake will be dropped provided the
// ACK is a bare ACK and carries no data. If the timeout is crossed then
// the bare ACK is accepted and the connection is delivered to the
// listener.
deferAccept time.Duration
// acceptMu protects accepQueue
acceptMu sync.Mutex `state:"nosave"`
// acceptQueue is used by a listening endpoint to send newly accepted
// connections to the endpoint so that they can be read by Accept()
// calls.
//
// +checklocks:acceptMu
acceptQueue acceptQueue
rcv *receiver `state:"wait"`
snd *sender `state:"wait"`
// The goroutine drain completion notification channel.
drainDone chan struct{} `state:"nosave"`
// The goroutine undrain notification channel. This is currently used as
// a way to block the worker goroutines. Today nothing closes/writes
// this channel and this causes any goroutines waiting on this to just
// block. This is used during save/restore to prevent worker goroutines
// from mutating state as it's being saved.
undrain chan struct{} `state:"nosave"`
// probe if not nil is invoked on every received segment. It is passed
// a copy of the current state of the endpoint.
probe TCPProbeFunc `state:"nosave"`
// The following are only used to assist the restore run to re-connect.
connectingAddress tcpip.Address
// amss is the advertised MSS to the peer by this endpoint.
amss uint16
// sendTOS represents IPv4 TOS or IPv6 TrafficClass,
// applied while sending packets. Defaults to 0 as on Linux.
sendTOS uint8
gso stack.GSO
stats Stats
// tcpLingerTimeout is the maximum amount of a time a socket
// a socket stays in TIME_WAIT state before being marked
// closed.
tcpLingerTimeout time.Duration
// closed indicates that the user has called closed on the
// endpoint and at this point the endpoint is only around
// to complete the TCP shutdown.
closed bool
// txHash is the transport layer hash to be set on outbound packets
// emitted by this endpoint.
txHash uint32
// owner is used to get uid and gid of the packet.
owner tcpip.PacketOwner
// ops is used to get socket level options.
ops tcpip.SocketOptions
// lastOutOfWindowAckTime is the time at which the an ACK was sent in response
// to an out of window segment being received by this endpoint.
lastOutOfWindowAckTime tcpip.MonotonicTime
// finWait2Timer is used to reap orphaned sockets in FIN-WAIT-2 where the peer
// is yet to send a FIN but on our end the socket is fully closed i.e. endpoint.Close()
// has been called on the socket. This timer is not started for sockets that
// are waiting for a peer FIN but are not closed.
finWait2Timer tcpip.Timer `state:"nosave"`
// timeWaitTimer is used to reap a socket once a socket has been in TIME-WAIT state
// for tcp.DefaultTCPTimeWaitTimeout seconds.
timeWaitTimer tcpip.Timer `state:"nosave"`
// listenCtx is used by listening endpoints to store state used while listening for
// connections. Nil otherwise.
listenCtx *listenContext `state:"nosave"`
// limRdr is reused to avoid allocations.
//
// +checklocks:mu
limRdr *io.LimitedReader `state:"nosave"`
// pmtud is the PMTUD strategy to use.
//
// +checklocks:mu
pmtud tcpip.PMTUDStrategy
// alsoBindToV4 indicates if `any` address was used to bind a port.
//
// +checklocks:mu
alsoBindToV4 bool
// terminateAtRestore indicates whether the endpoint must be terminated
// upon restore. This applies specifically when the snapshots are taken
// with the "save-resume" flag, ensuring that if such a snapshot is
// restored later, this endpoint is cleaned up. This flag is only set
// to true in beforeSave for external endpoints which do not have
// save-restore capability.
//
// +checklocks:mu
terminateAtRestore bool
}
// calculateAdvertisedMSS calculates the MSS to advertise.
//
// If userMSS is non-zero and is not greater than the maximum possible MSS for
// r, it will be used; otherwise, the maximum possible MSS will be used.
func calculateAdvertisedMSS(userMSS uint16, r *stack.Route) uint16 {
// The maximum possible MSS is dependent on the route.
// TODO(b/143359391): Respect TCP Min and Max size.
maxMSS := uint16(r.MTU() - header.TCPMinimumSize)
if userMSS != 0 && userMSS < maxMSS {
return userMSS
}
return maxMSS
}
// isOwnedByUser() returns true if the endpoint lock is currently
// held by a user(syscall) goroutine.
func (e *Endpoint) isOwnedByUser() bool {
return e.ownedByUser.Load() == 1
}
// LockUser tries to lock e.mu and if it fails it will check if the lock is held
// by another syscall goroutine. If yes, then it will goto sleep waiting for the
// lock to be released, if not then it will spin till it acquires the lock or
// another syscall goroutine acquires it in which case it will goto sleep as
// described above.
//
// The assumption behind spinning here being that background packet processing
// should not be holding the lock for long and spinning reduces latency as we
// avoid an expensive sleep/wakeup of the syscall goroutine).
// +checklocksacquire:e.mu
func (e *Endpoint) LockUser() {
const iterations = 5
for i := 0; i < iterations; i++ {
// Try first if the sock is locked then check if it's owned
// by another user goroutine if not then we spin, otherwise
// we just go to sleep on the Lock() and wait.
if !e.TryLock() {
// If socket is owned by the user then just go to sleep
// as the lock could be held for a reasonably long time.
if e.ownedByUser.Load() == 1 {
e.mu.Lock()
e.ownedByUser.Store(1)
return
}
// Spin but don't yield the processor since the lower half
// should yield the lock soon.
continue
}
e.ownedByUser.Store(1)
return
}
for i := 0; i < iterations; i++ {
// Try first if the sock is locked then check if it's owned
// by another user goroutine if not then we spin, otherwise
// we just go to sleep on the Lock() and wait.
if !e.TryLock() {
// If socket is owned by the user then just go to sleep
// as the lock could be held for a reasonably long time.
if e.ownedByUser.Load() == 1 {
e.mu.Lock()
e.ownedByUser.Store(1)
return
}
// Spin but yield the processor since the lower half
// should yield the lock soon.
runtime.Gosched()
continue
}
e.ownedByUser.Store(1)
return
}
// Finally just give up and wait for the Lock.
e.mu.Lock()
e.ownedByUser.Store(1)
}
// UnlockUser will check if there are any segments already queued for processing
// and wake up a processor goroutine to process them before unlocking e.mu.
// This is required because we when packets arrive and endpoint lock is already
// held then such packets are queued up to be processed.
//
// Precondition: e.LockUser() must have been called before calling e.UnlockUser()
// +checklocksrelease:e.mu
func (e *Endpoint) UnlockUser() {
// Lock segment queue before checking so that we avoid a race where
// segments can be queued between the time we check if queue is empty
// and actually unlock the endpoint mutex.
e.segmentQueue.mu.Lock()
if e.segmentQueue.emptyLocked() {
if e.ownedByUser.Swap(0) != 1 {
panic("e.UnlockUser() called without calling e.LockUser()")
}
e.mu.Unlock()
e.segmentQueue.mu.Unlock()
return
}
e.segmentQueue.mu.Unlock()
// Since we are waking the processor goroutine here just unlock
// and let it process the queued segments.
if e.ownedByUser.Swap(0) != 1 {
panic("e.UnlockUser() called without calling e.LockUser()")
}
processor := e.protocol.dispatcher.selectProcessor(e.ID)
e.mu.Unlock()
// Wake up the processor for this endpoint to process any queued
// segments after releasing the lock to avoid the case where if the
// processor goroutine starts running before we release the lock here
// then it will fail to process as TryLock() will fail.
processor.queueEndpoint(e)
}
// StopWork halts packet processing. Only to be used in tests.
// +checklocksacquire:e.mu
func (e *Endpoint) StopWork() {
e.mu.Lock()
}
// ResumeWork resumes packet processing. Only to be used in tests.
// +checklocksrelease:e.mu
func (e *Endpoint) ResumeWork() {
e.mu.Unlock()
}
// AssertLockHeld forces the checklocks analyzer to consider e.mu held. This is
// used in places where we know that e.mu is held, but checklocks does not,
// which can happen when creating new locked objects. You must pass the known
// locked endpoint to this function and it must be the same as the caller
// endpoint.
// TODO(b/226403629): Remove this function once checklocks understands local
// variable locks.
// +checklocks:locked.mu
// +checklocksacquire:e.mu
func (e *Endpoint) AssertLockHeld(locked *Endpoint) {
if e != locked {
panic("AssertLockHeld failed: locked endpoint != asserting endpoint")
}
}
// TryLock is a helper that calls TryLock on the endpoint's mutex and
// adds the necessary checklocks annotations.
// TODO(b/226403629): Remove this once checklocks understands TryLock.
// +checklocksacquire:e.mu
func (e *Endpoint) TryLock() bool {
return e.mu.TryLock() // +checklocksforce: TryLock.
}
// setEndpointState updates the state of the endpoint to state atomically. This
// method is unexported as the only place we should update the state is in this
// package but we allow the state to be read freely without holding e.mu.
//
// +checklocks:e.mu
func (e *Endpoint) setEndpointState(state EndpointState) {
oldstate := EndpointState(e.state.Swap(uint32(state)))
switch state {
case StateEstablished:
e.stack.Stats().TCP.CurrentEstablished.Increment()
e.stack.Stats().TCP.CurrentConnected.Increment()
case StateError:
fallthrough
case StateClose:
if oldstate == StateCloseWait || oldstate == StateEstablished {
e.stack.Stats().TCP.EstablishedResets.Increment()
}
if oldstate.connected() {
e.stack.Stats().TCP.CurrentConnected.Decrement()
}
fallthrough
default:
if oldstate == StateEstablished {
e.stack.Stats().TCP.CurrentEstablished.Decrement()
}
}
}
// EndpointState returns the current state of the endpoint.
func (e *Endpoint) EndpointState() EndpointState {
return EndpointState(e.state.Load())
}
// setRecentTimestamp sets the recentTS field to the provided value.
func (e *Endpoint) setRecentTimestamp(recentTS uint32) {
e.RecentTS = recentTS
e.recentTSTime = e.stack.Clock().NowMonotonic()
}
// recentTimestamp returns the value of the recentTS field.
func (e *Endpoint) recentTimestamp() uint32 {
return e.RecentTS
}
// TODO(gvisor.dev/issue/6974): Remove once tcp endpoints are composed with a
// network.Endpoint, which also defines this function.
func calculateTTL(route *stack.Route, ipv4TTL uint8, ipv6HopLimit int16) uint8 {
switch netProto := route.NetProto(); netProto {
case header.IPv4ProtocolNumber:
if ipv4TTL == tcpip.UseDefaultIPv4TTL {
return route.DefaultTTL()
}
return ipv4TTL
case header.IPv6ProtocolNumber:
if ipv6HopLimit == tcpip.UseDefaultIPv6HopLimit {
return route.DefaultTTL()
}
return uint8(ipv6HopLimit)
default:
panic(fmt.Sprintf("invalid protocol number = %d", netProto))
}
}
// keepalive is a synchronization wrapper used to appease stateify. See the
// comment in endpoint, where it is used.
//
// +stateify savable
type keepalive struct {
keepaliveMutex `state:"nosave"`
idle time.Duration
interval time.Duration
count int
unacked int
// should never be a zero timer if the endpoint is not closed.
timer timer `state:"nosave"`
waker sleep.Waker `state:"nosave"`
}
func newEndpoint(s *stack.Stack, protocol *protocol, netProto tcpip.NetworkProtocolNumber, waiterQueue *waiter.Queue) *Endpoint {
e := &Endpoint{
stack: s,
protocol: protocol,
TransportEndpointInfo: stack.TransportEndpointInfo{
NetProto: netProto,
TransProto: header.TCPProtocolNumber,
},
sndQueueInfo: sndQueueInfo{
TCPSndBufState: TCPSndBufState{
SndMTU: math.MaxInt32,
},
},
waiterQueue: waiterQueue,
state: atomicbitops.FromUint32(uint32(StateInitial)),
keepalive: keepalive{
idle: DefaultKeepaliveIdle,
interval: DefaultKeepaliveInterval,
count: DefaultKeepaliveCount,
},
ipv4TTL: tcpip.UseDefaultIPv4TTL,
ipv6HopLimit: tcpip.UseDefaultIPv6HopLimit,
// txHash only determines which outgoing queue to use, so
// InsecureRNG is fine.
txHash: s.InsecureRNG().Uint32(),
windowClamp: DefaultReceiveBufferSize,
maxSynRetries: DefaultSynRetries,
limRdr: &io.LimitedReader{},
}
e.ops.InitHandler(e, e.stack, GetTCPSendBufferLimits, GetTCPReceiveBufferLimits)
e.ops.SetMulticastLoop(true)
e.ops.SetQuickAck(true)
e.ops.SetSendBufferSize(DefaultSendBufferSize, false /* notify */)
e.ops.SetReceiveBufferSize(DefaultReceiveBufferSize, false /* notify */)
var ss tcpip.TCPSendBufferSizeRangeOption
if err := s.TransportProtocolOption(ProtocolNumber, &ss); err == nil {
e.ops.SetSendBufferSize(int64(ss.Default), false /* notify */)
}
var rs tcpip.TCPReceiveBufferSizeRangeOption
if err := s.TransportProtocolOption(ProtocolNumber, &rs); err == nil {
e.ops.SetReceiveBufferSize(int64(rs.Default), false /* notify */)
}
var cs tcpip.CongestionControlOption
if err := s.TransportProtocolOption(ProtocolNumber, &cs); err == nil {
e.cc = cs
}
var mrb tcpip.TCPModerateReceiveBufferOption
if err := s.TransportProtocolOption(ProtocolNumber, &mrb); err == nil {
e.RcvAutoParams.Disabled = !bool(mrb)
}
var de tcpip.TCPDelayEnabled
if err := s.TransportProtocolOption(ProtocolNumber, &de); err == nil && de {
e.ops.SetDelayOption(true)
}
var tcpLT tcpip.TCPLingerTimeoutOption
if err := s.TransportProtocolOption(ProtocolNumber, &tcpLT); err == nil {
e.tcpLingerTimeout = time.Duration(tcpLT)
}
var synRetries tcpip.TCPSynRetriesOption
if err := s.TransportProtocolOption(ProtocolNumber, &synRetries); err == nil {
e.maxSynRetries = uint8(synRetries)
}
e.probe = protocol.probe
e.segmentQueue.ep = e
// TODO(https://gvisor.dev/issues/7493): Defer creating the timer until TCP connection becomes
// established.
e.keepalive.timer.init(e.stack.Clock(), timerHandler(e, e.keepaliveTimerExpired))
return e
}
// Readiness returns the current readiness of the endpoint. For example, if
// waiter.EventIn is set, the endpoint is immediately readable.
func (e *Endpoint) Readiness(mask waiter.EventMask) waiter.EventMask {
result := waiter.EventMask(0)
switch e.EndpointState() {
case StateInitial, StateBound:
// This prevents blocking of new sockets which are not
// connected when SO_LINGER is set.
result |= waiter.EventHUp
case StateConnecting, StateSynSent, StateSynRecv:
// Ready for nothing.
case StateClose, StateError, StateTimeWait:
// Ready for anything.
result = mask
case StateListen:
// Check if there's anything in the accepted queue.
if (mask & waiter.ReadableEvents) != 0 {
e.acceptMu.Lock()
if e.acceptQueue.endpoints.Len() != 0 {
result |= waiter.ReadableEvents
}
e.acceptMu.Unlock()
}
}
if e.EndpointState().connected() {
// Determine if the endpoint is writable if requested.
if (mask & waiter.WritableEvents) != 0 {
e.sndQueueInfo.sndQueueMu.Lock()
sndBufSize := e.getSendBufferSize()
if e.sndQueueInfo.SndClosed || e.sndQueueInfo.SndBufUsed < sndBufSize {
result |= waiter.WritableEvents
}
if e.sndQueueInfo.SndClosed {
e.updateConnDirectionState(connDirectionStateSndClosed)
}
e.sndQueueInfo.sndQueueMu.Unlock()
}
// Determine if the endpoint is readable if requested.
if (mask & waiter.ReadableEvents) != 0 {
e.rcvQueueMu.Lock()
if e.RcvBufUsed > 0 || e.RcvClosed {
result |= waiter.ReadableEvents
}
if e.RcvClosed {
e.updateConnDirectionState(connDirectionStateRcvClosed)
}
e.rcvQueueMu.Unlock()
}
}
// Determine whether endpoint is half-closed with rcv shutdown
if e.connDirectionState() == connDirectionStateRcvClosed {
result |= waiter.EventRdHUp
}
return result
}
// Purging pending rcv segments is only necessary on RST.
func (e *Endpoint) purgePendingRcvQueue() {
if e.rcv != nil {
for e.rcv.pendingRcvdSegments.Len() > 0 {
s := heap.Pop(&e.rcv.pendingRcvdSegments).(*segment)
s.DecRef()
}
}
}
// +checklocks:e.mu
func (e *Endpoint) purgeReadQueue() {
if e.rcv != nil {
e.rcvQueueMu.Lock()
defer e.rcvQueueMu.Unlock()
for {
s := e.rcvQueue.Front()
if s == nil {
break
}
e.rcvQueue.Remove(s)
s.DecRef()
}
e.RcvBufUsed = 0
}
}
// +checklocks:e.mu
func (e *Endpoint) purgeWriteQueue() {
if e.snd != nil {
e.sndQueueInfo.sndQueueMu.Lock()
defer e.sndQueueInfo.sndQueueMu.Unlock()
e.snd.updateWriteNext(nil)
for s := e.snd.writeList.Front(); s != nil; s = e.snd.writeList.Front() {
e.snd.writeList.Remove(s)
s.DecRef()
}
e.sndQueueInfo.SndBufUsed = 0
e.sndQueueInfo.SndClosed = true
e.snd.SndNxt = e.snd.SndUna
}
}
// Abort implements stack.TransportEndpoint.Abort.
func (e *Endpoint) Abort() {
defer e.drainClosingSegmentQueue()
e.LockUser()
defer e.UnlockUser()
defer e.purgeReadQueue()
// Reset all connected endpoints.
switch state := e.EndpointState(); {
case state.connected():
e.resetConnectionLocked(&tcpip.ErrAborted{})
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.ReadableEvents | waiter.WritableEvents)
return
}
e.closeLocked()
}
// Close puts the endpoint in a closed state and frees all resources associated
// with it. It must be called only once and with no other concurrent calls to
// the endpoint.
func (e *Endpoint) Close() {
e.LockUser()
if e.closed {
e.UnlockUser()
return
}
// We always want to purge the read queue, but do so after the checks in
// shutdownLocked.
e.closeLocked()
e.purgeReadQueue()
if e.EndpointState() == StateClose || e.EndpointState() == StateError {
// It should be safe to purge the read queue now as the endpoint
// is now closed or in an error state and further reads are not
// permitted.
e.UnlockUser()
e.drainClosingSegmentQueue()
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.ReadableEvents | waiter.WritableEvents)
return
}
e.UnlockUser()
}
// +checklocks:e.mu
func (e *Endpoint) closeLocked() {
linger := e.SocketOptions().GetLinger()
if linger.Enabled && linger.Timeout == 0 {
s := e.EndpointState()
isResetState := s == StateEstablished || s == StateCloseWait || s == StateFinWait1 || s == StateFinWait2 || s == StateSynRecv
if isResetState {
// Close the endpoint without doing full shutdown and
// send a RST.
e.resetConnectionLocked(&tcpip.ErrConnectionAborted{})
return
}
}
// Issue a shutdown so that the peer knows we won't send any more data
// if we're connected, or stop accepting if we're listening.
e.shutdownLocked(tcpip.ShutdownWrite | tcpip.ShutdownRead)
e.closeNoShutdownLocked()
}
// closeNoShutdown closes the endpoint without doing a full shutdown.
// +checklocks:e.mu
func (e *Endpoint) closeNoShutdownLocked() {
// For listening sockets, we always release ports inline so that they
// are immediately available for reuse after Close() is called. If also
// registered, we unregister as well otherwise the next user would fail
// in Listen() when trying to register.
if e.EndpointState() == StateListen && e.isPortReserved {
if e.isRegistered {
e.stack.StartTransportEndpointCleanup(e.effectiveNetProtos, ProtocolNumber, e.TransportEndpointInfo.ID, e, e.boundPortFlags, e.boundBindToDevice)
e.isRegistered = false
}
portRes := ports.Reservation{
Networks: e.effectiveNetProtos,
Transport: ProtocolNumber,
Addr: e.TransportEndpointInfo.ID.LocalAddress,
Port: e.TransportEndpointInfo.ID.LocalPort,
Flags: e.boundPortFlags,
BindToDevice: e.boundBindToDevice,
Dest: e.boundDest,
}
e.stack.ReleasePort(portRes)
e.isPortReserved = false
e.boundBindToDevice = 0
e.boundPortFlags = ports.Flags{}
e.boundDest = tcpip.FullAddress{}
}
// Mark endpoint as closed.
e.closed = true
tcpip.AddDanglingEndpoint(e)
eventMask := waiter.ReadableEvents | waiter.WritableEvents
switch e.EndpointState() {
case StateInitial, StateBound, StateListen:
e.setEndpointState(StateClose)
fallthrough
case StateClose, StateError:
eventMask |= waiter.EventHUp
e.cleanupLocked()
case StateConnecting, StateSynSent, StateSynRecv:
// Abort the handshake and set the error.
// Notify that the endpoint is closed.
eventMask |= waiter.EventHUp
e.handshakeFailed(&tcpip.ErrAborted{})
// Notify that the endpoint is closed.
eventMask |= waiter.EventHUp
case StateFinWait2:
// The socket has been closed and we are in FIN-WAIT-2 so start
// the FIN-WAIT-2 timer.
if e.finWait2Timer == nil {
e.finWait2Timer = e.stack.Clock().AfterFunc(e.tcpLingerTimeout, e.finWait2TimerExpired)
}
}
e.waiterQueue.Notify(eventMask)
}
// closePendingAcceptableConnections closes all connections that have completed
// handshake but not yet been delivered to the application.
func (e *Endpoint) closePendingAcceptableConnectionsLocked() {
e.acceptMu.Lock()
pendingEndpoints := e.acceptQueue.pendingEndpoints
e.acceptQueue.pendingEndpoints = nil
completedEndpoints := make([]*Endpoint, 0, e.acceptQueue.endpoints.Len())
for n := e.acceptQueue.endpoints.Front(); n != nil; n = n.Next() {
completedEndpoints = append(completedEndpoints, n.Value.(*Endpoint))
}
e.acceptQueue.endpoints.Init()
e.acceptQueue.capacity = 0
e.acceptMu.Unlock()
// Close any endpoints in SYN-RCVD state.
for n := range pendingEndpoints {
n.Abort()
}
// Reset all connections that are waiting to be accepted.
for _, n := range completedEndpoints {
n.Abort()
}
}
// cleanupLocked frees all resources associated with the endpoint.
// +checklocks:e.mu
func (e *Endpoint) cleanupLocked() {
if e.snd != nil {
e.snd.resendTimer.cleanup()
e.snd.probeTimer.cleanup()
e.snd.reorderTimer.cleanup()
e.snd.corkTimer.cleanup()
}
if e.finWait2Timer != nil {
e.finWait2Timer.Stop()
}
if e.timeWaitTimer != nil {
e.timeWaitTimer.Stop()
}
// Remove current EP from its lEP acceptQueue.pendingEndpoint if exists.
if e.h != nil && e.h.listenEP != nil {
lEP := e.h.listenEP
lEP.acceptMu.Lock()
delete(lEP.acceptQueue.pendingEndpoints, e)
lEP.acceptMu.Unlock()
}
// Close all endpoints that might have been accepted by TCP but not by
// the client.
e.closePendingAcceptableConnectionsLocked()
e.keepalive.timer.cleanup()
if e.isRegistered {
e.stack.StartTransportEndpointCleanup(e.effectiveNetProtos, ProtocolNumber, e.TransportEndpointInfo.ID, e, e.boundPortFlags, e.boundBindToDevice)
e.isRegistered = false
}
if e.isPortReserved {
portRes := ports.Reservation{
Networks: e.effectiveNetProtos,
Transport: ProtocolNumber,
Addr: e.TransportEndpointInfo.ID.LocalAddress,
Port: e.TransportEndpointInfo.ID.LocalPort,
Flags: e.boundPortFlags,
BindToDevice: e.boundBindToDevice,
Dest: e.boundDest,
}
e.stack.ReleasePort(portRes)
e.isPortReserved = false
}
e.boundBindToDevice = 0
e.boundPortFlags = ports.Flags{}
e.boundDest = tcpip.FullAddress{}
if e.route != nil {
e.route.Release()
e.route = nil
}
e.purgeWriteQueue()
// Only purge the read queue here if the socket is fully closed by the
// user.
if e.closed {
e.purgeReadQueue()
}
e.stack.CompleteTransportEndpointCleanup(e)
tcpip.DeleteDanglingEndpoint(e)
}
// wndFromSpace returns the window that we can advertise based on the available
// receive buffer space.
func wndFromSpace(space int) int {
return space >> rcvAdvWndScale
}
// initialReceiveWindow returns the initial receive window to advertise in the
// SYN/SYN-ACK.
func (e *Endpoint) initialReceiveWindow() int {
rcvWnd := wndFromSpace(e.receiveBufferAvailable())
if rcvWnd > math.MaxUint16 {
rcvWnd = math.MaxUint16
}
// Use the user supplied MSS, if available.
routeWnd := InitialCwnd * int(calculateAdvertisedMSS(e.userMSS, e.route)) * 2
if rcvWnd > routeWnd {
rcvWnd = routeWnd
}
rcvWndScale := e.rcvWndScaleForHandshake()
// Round-down the rcvWnd to a multiple of wndScale. This ensures that the
// window offered in SYN won't be reduced due to the loss of precision if
// window scaling is enabled after the handshake.
rcvWnd = (rcvWnd >> uint8(rcvWndScale)) << uint8(rcvWndScale)
// Ensure we can always accept at least 1 byte if the scale specified
// was too high for the provided rcvWnd.
if rcvWnd == 0 {
rcvWnd = 1
}
return rcvWnd
}
// ModerateRecvBuf adjusts the receive buffer and the advertised window
// based on the number of bytes copied to userspace.
func (e *Endpoint) ModerateRecvBuf(copied int) {
e.LockUser()
defer e.UnlockUser()
sendNonZeroWindowUpdate := false
e.rcvQueueMu.Lock()
if e.RcvAutoParams.Disabled {
e.rcvQueueMu.Unlock()
return
}
now := e.stack.Clock().NowMonotonic()
if rtt := e.RcvAutoParams.RTT; rtt == 0 || now.Sub(e.RcvAutoParams.MeasureTime) < rtt {
e.RcvAutoParams.CopiedBytes += copied
e.rcvQueueMu.Unlock()
return
}
prevRTTCopied := e.RcvAutoParams.CopiedBytes + copied
prevCopied := e.RcvAutoParams.PrevCopiedBytes
rcvWnd := 0
if prevRTTCopied > prevCopied {
// The minimal receive window based on what was copied by the app
// in the immediate preceding RTT and some extra buffer for 16
// segments to account for variations.
// We multiply by 2 to account for packet losses.
rcvWnd = prevRTTCopied*2 + 16*int(e.amss)
// Scale for slow start based on bytes copied in this RTT vs previous.
grow := (rcvWnd * (prevRTTCopied - prevCopied)) / prevCopied
// Multiply growth factor by 2 again to account for sender being
// in slow-start where the sender grows it's congestion window
// by 100% per RTT.
rcvWnd += grow * 2
// Make sure auto tuned buffer size can always receive upto 2x
// the initial window of 10 segments.
if minRcvWnd := int(e.amss) * InitialCwnd * 2; rcvWnd < minRcvWnd {
rcvWnd = minRcvWnd
}
// Cap the auto tuned buffer size by the maximum permissible
// receive buffer size.
if max := e.maxReceiveBufferSize(); rcvWnd > max {
rcvWnd = max
}
// We do not adjust downwards as that can cause the receiver to
// reject valid data that might already be in flight as the
// acceptable window will shrink.
rcvBufSize := int(e.ops.GetReceiveBufferSize())
if rcvWnd > rcvBufSize {
availBefore := wndFromSpace(e.receiveBufferAvailableLocked(rcvBufSize))
e.ops.SetReceiveBufferSize(int64(rcvWnd), false /* notify */)
availAfter := wndFromSpace(e.receiveBufferAvailableLocked(rcvWnd))
if crossed, above := e.windowCrossedACKThresholdLocked(availAfter-availBefore, rcvBufSize); crossed && above {
sendNonZeroWindowUpdate = true
}
}
// We only update PrevCopiedBytes when we grow the buffer because in cases
// where PrevCopiedBytes > prevRTTCopied the existing buffer is already big
// enough to handle the current rate and we don't need to do any
// adjustments.
e.RcvAutoParams.PrevCopiedBytes = prevRTTCopied
}
e.RcvAutoParams.MeasureTime = now
e.RcvAutoParams.CopiedBytes = 0
e.rcvQueueMu.Unlock()
// Send the update after unlocking rcvQueueMu as sending a segment acquires
// the lock to calculate the window to be sent.
if e.EndpointState().connected() && sendNonZeroWindowUpdate {
e.rcv.nonZeroWindow()
}
}
// SetOwner implements tcpip.Endpoint.SetOwner.
func (e *Endpoint) SetOwner(owner tcpip.PacketOwner) {
e.owner = owner
}
// +checklocks:e.mu
func (e *Endpoint) hardErrorLocked() tcpip.Error {
err := e.hardError
e.hardError = nil
return err
}
// +checklocks:e.mu
func (e *Endpoint) lastErrorLocked() tcpip.Error {
e.lastErrorMu.Lock()
defer e.lastErrorMu.Unlock()
err := e.lastError
e.lastError = nil
return err
}
// LastError implements tcpip.Endpoint.LastError.
func (e *Endpoint) LastError() tcpip.Error {
e.LockUser()
defer e.UnlockUser()
if err := e.hardErrorLocked(); err != nil {
return err
}
return e.lastErrorLocked()
}
// LastErrorLocked reads and clears lastError.
// Only to be used in tests.
// +checklocks:e.mu
func (e *Endpoint) LastErrorLocked() tcpip.Error {
return e.lastErrorLocked()
}
// UpdateLastError implements tcpip.SocketOptionsHandler.UpdateLastError.
func (e *Endpoint) UpdateLastError(err tcpip.Error) {
e.LockUser()
e.lastErrorMu.Lock()
e.lastError = err
e.lastErrorMu.Unlock()
e.UnlockUser()
}
// Read implements tcpip.Endpoint.Read.
func (e *Endpoint) Read(dst io.Writer, opts tcpip.ReadOptions) (tcpip.ReadResult, tcpip.Error) {
e.LockUser()
defer e.UnlockUser()
if err := e.checkReadLocked(); err != nil {
if _, ok := err.(*tcpip.ErrClosedForReceive); ok {
e.stats.ReadErrors.ReadClosed.Increment()
}
return tcpip.ReadResult{}, err
}
var err error
done := 0
// N.B. Here we get the first segment to be processed. It is safe to not
// hold rcvQueueMu when processing, since we hold e.mu to ensure we only
// remove segments from the list through Read() and that new segments
// cannot be appended.
s := e.rcvQueue.Front()
for s != nil {
var n int
n, err = s.ReadTo(dst, opts.Peek)
// Book keeping first then error handling.
done += n
if opts.Peek {
s = s.Next()
} else {
sendNonZeroWindowUpdate := false
memDelta := 0
for {
seg := e.rcvQueue.Front()
if seg == nil || seg.payloadSize() != 0 {
break
}
e.rcvQueue.Remove(seg)
// Memory is only considered released when the whole segment has been
// read.
memDelta += seg.segMemSize()
seg.DecRef()
}
e.rcvQueueMu.Lock()
e.RcvBufUsed -= n
s = e.rcvQueue.Front()
if memDelta > 0 {
// If the window was small before this read and if the read freed up
// enough buffer space, to either fit an aMSS or half a receive buffer
// (whichever smaller), then send a window update.
if crossed, above := e.windowCrossedACKThresholdLocked(memDelta, int(e.ops.GetReceiveBufferSize())); crossed && above {
sendNonZeroWindowUpdate = true
}
}
e.rcvQueueMu.Unlock()
if e.EndpointState().connected() && sendNonZeroWindowUpdate {
e.rcv.nonZeroWindow()
}
}
if err != nil {
break
}
}
// If something is read, we must report it. Report error when nothing is read.
if done == 0 && err != nil {
return tcpip.ReadResult{}, &tcpip.ErrBadBuffer{}
}
return tcpip.ReadResult{
Count: done,
Total: done,
}, nil
}
// checkRead checks that endpoint is in a readable state.
//
// +checklocks:e.mu
func (e *Endpoint) checkReadLocked() tcpip.Error {
e.rcvQueueMu.Lock()
defer e.rcvQueueMu.Unlock()
// When in SYN-SENT state, let the caller block on the receive.
// An application can initiate a non-blocking connect and then block
// on a receive. It can expect to read any data after the handshake
// is complete. RFC793, section 3.9, p58.
if e.EndpointState() == StateSynSent {
return &tcpip.ErrWouldBlock{}
}
// The endpoint can be read if it's connected, or if it's already closed
// but has some pending unread data. Also note that a RST being received
// would cause the state to become StateError so we should allow the
// reads to proceed before returning a ECONNRESET.
bufUsed := e.RcvBufUsed
if s := e.EndpointState(); !s.connected() && s != StateClose && bufUsed == 0 {
if s == StateError {
if err := e.hardErrorLocked(); err != nil {
return err
}
return &tcpip.ErrClosedForReceive{}
}
e.stats.ReadErrors.NotConnected.Increment()
return &tcpip.ErrNotConnected{}
}
if e.RcvBufUsed == 0 {
if e.RcvClosed || !e.EndpointState().connected() {
return &tcpip.ErrClosedForReceive{}
}
return &tcpip.ErrWouldBlock{}
}
return nil
}
// isEndpointWritableLocked checks if a given endpoint is writable
// and also returns the number of bytes that can be written at this
// moment. If the endpoint is not writable then it returns an error
// indicating the reason why it's not writable.
// +checklocks:e.mu
// +checklocks:e.sndQueueInfo.sndQueueMu
func (e *Endpoint) isEndpointWritableLocked() (int, tcpip.Error) {
// The endpoint cannot be written to if it's not connected.
switch s := e.EndpointState(); {
case s == StateError:
if err := e.hardErrorLocked(); err != nil {
return 0, err
}
return 0, &tcpip.ErrClosedForSend{}
case !s.connecting() && !s.connected():
return 0, &tcpip.ErrClosedForSend{}
case s.connecting():
// As per RFC793, page 56, a send request arriving when in connecting
// state, can be queued to be completed after the state becomes
// connected. Return an error code for the caller of endpoint Write to
// try again, until the connection handshake is complete.
return 0, &tcpip.ErrWouldBlock{}
}
// Check if the connection has already been closed for sends.
if e.sndQueueInfo.SndClosed {
return 0, &tcpip.ErrClosedForSend{}
}
sndBufSize := e.getSendBufferSize()
avail := sndBufSize - e.sndQueueInfo.SndBufUsed
if avail <= 0 {
return 0, &tcpip.ErrWouldBlock{}
}
return avail, nil
}
// readFromPayloader reads a slice from the Payloader.
// +checklocks:e.mu
// +checklocks:e.sndQueueInfo.sndQueueMu
func (e *Endpoint) readFromPayloader(p tcpip.Payloader, opts tcpip.WriteOptions, avail int) (buffer.Buffer, tcpip.Error) {
// We can release locks while copying data.
//
// This is not possible if atomic is set, because we can't allow the
// available buffer space to be consumed by some other caller while we
// are copying data in.
limRdr := e.limRdr
if !opts.Atomic {
defer func() {
e.limRdr = limRdr
}()
e.limRdr = nil
e.sndQueueInfo.sndQueueMu.Unlock()
defer e.sndQueueInfo.sndQueueMu.Lock()
e.UnlockUser()
defer e.LockUser()
}
// Fetch data.
var payload buffer.Buffer
if l := p.Len(); l < avail {
avail = l
}
if avail == 0 {
return payload, nil
}
if _, err := payload.WriteFromReaderAndLimitedReader(p, int64(avail), limRdr); err != nil {
payload.Release()
return buffer.Buffer{}, &tcpip.ErrBadBuffer{}
}
return payload, nil
}
// queueSegment reads data from the payloader and returns a segment to be sent.
// +checklocks:e.mu
func (e *Endpoint) queueSegment(p tcpip.Payloader, opts tcpip.WriteOptions) (*segment, int, tcpip.Error) {
e.sndQueueInfo.sndQueueMu.Lock()
defer e.sndQueueInfo.sndQueueMu.Unlock()
avail, err := e.isEndpointWritableLocked()
if err != nil {
e.stats.WriteErrors.WriteClosed.Increment()
return nil, 0, err
}
buf, err := e.readFromPayloader(p, opts, avail)
if err != nil {
return nil, 0, err
}
// Do not queue zero length segments.
if buf.Size() == 0 {
return nil, 0, nil
}
if !opts.Atomic {
// Since we released locks in between it's possible that the
// endpoint transitioned to a CLOSED/ERROR states so make
// sure endpoint is still writable before trying to write.
avail, err := e.isEndpointWritableLocked()
if err != nil {
e.stats.WriteErrors.WriteClosed.Increment()
buf.Release()
return nil, 0, err
}
// A simultaneous call to write on the socket can reduce avail. Discard
// excess data copied if this is the case.
if int64(avail) < buf.Size() {
buf.Truncate(int64(avail))
}
}
// Add data to the send queue.
size := int(buf.Size())
s := newOutgoingSegment(e.TransportEndpointInfo.ID, e.stack.Clock(), buf, e.ops.GetMark())
e.sndQueueInfo.SndBufUsed += size
e.snd.writeList.PushBack(s)
return s, size, nil
}
// Write writes data to the endpoint's peer.
func (e *Endpoint) Write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, tcpip.Error) {
// Linux completely ignores any address passed to sendto(2) for TCP sockets
// (without the MSG_FASTOPEN flag). Corking is unimplemented, so opts.More
// and opts.EndOfRecord are also ignored.
e.LockUser()
defer e.UnlockUser()
// Return if either we didn't queue anything or if an error occurred while
// attempting to queue data.
nextSeg, n, err := e.queueSegment(p, opts)
if n == 0 || err != nil {
return 0, err
}
e.sendData(nextSeg)
return int64(n), nil
}
// selectWindowLocked returns the new window without checking for shrinking or scaling
// applied.
// +checklocks:e.mu
// +checklocks:e.rcvQueueMu
func (e *Endpoint) selectWindowLocked(rcvBufSize int) (wnd seqnum.Size) {
wndFromAvailable := wndFromSpace(e.receiveBufferAvailableLocked(rcvBufSize))
maxWindow := wndFromSpace(rcvBufSize)
wndFromUsedBytes := maxWindow - e.RcvBufUsed
// We take the lesser of the wndFromAvailable and wndFromUsedBytes because in
// cases where we receive a lot of small segments the segment overhead is a
// lot higher and we can run out socket buffer space before we can fill the
// previous window we advertised. In cases where we receive MSS sized or close
// MSS sized segments we will probably run out of window space before we
// exhaust receive buffer.
newWnd := wndFromAvailable
if newWnd > wndFromUsedBytes {
newWnd = wndFromUsedBytes
}
if newWnd < 0 {
newWnd = 0
}
return seqnum.Size(newWnd)
}
// selectWindow invokes selectWindowLocked after acquiring e.rcvQueueMu.
// +checklocks:e.mu
func (e *Endpoint) selectWindow() (wnd seqnum.Size) {
e.rcvQueueMu.Lock()
wnd = e.selectWindowLocked(int(e.ops.GetReceiveBufferSize()))
e.rcvQueueMu.Unlock()
return wnd
}
// windowCrossedACKThresholdLocked checks if the receive window to be announced
// would be under aMSS or under the window derived from half receive buffer,
// whichever smaller. This is useful as a receive side silly window syndrome
// prevention mechanism. If window grows to reasonable value, we should send ACK
// to the sender to inform the rx space is now large. We also want ensure a
// series of small read()'s won't trigger a flood of spurious tiny ACK's.
//
// For large receive buffers, the threshold is aMSS - once reader reads more
// than aMSS we'll send ACK. For tiny receive buffers, the threshold is half of
// receive buffer size. This is chosen arbitrarily.
// crossed will be true if the window size crossed the ACK threshold.
// above will be true if the new window is >= ACK threshold and false
// otherwise.
//
// +checklocks:e.mu
// +checklocks:e.rcvQueueMu
func (e *Endpoint) windowCrossedACKThresholdLocked(deltaBefore int, rcvBufSize int) (crossed bool, above bool) {
newAvail := int(e.selectWindowLocked(rcvBufSize))
oldAvail := newAvail - deltaBefore
if oldAvail < 0 {
oldAvail = 0
}
threshold := int(e.amss)
// rcvBufFraction is the inverse of the fraction of receive buffer size that
// is used to decide if the available buffer space is now above it.
const rcvBufFraction = 2
if wndThreshold := wndFromSpace(rcvBufSize / rcvBufFraction); threshold > wndThreshold {
threshold = wndThreshold
}
switch {
case oldAvail < threshold && newAvail >= threshold:
return true, true
case oldAvail >= threshold && newAvail < threshold:
return true, false
}
return false, false
}
// OnReuseAddressSet implements tcpip.SocketOptionsHandler.OnReuseAddressSet.
func (e *Endpoint) OnReuseAddressSet(v bool) {
e.LockUser()
e.portFlags.TupleOnly = v
e.UnlockUser()
}
// OnReusePortSet implements tcpip.SocketOptionsHandler.OnReusePortSet.
func (e *Endpoint) OnReusePortSet(v bool) {
e.LockUser()
e.portFlags.LoadBalanced = v
e.UnlockUser()
}
// OnKeepAliveSet implements tcpip.SocketOptionsHandler.OnKeepAliveSet.
func (e *Endpoint) OnKeepAliveSet(bool) {
e.LockUser()
e.resetKeepaliveTimer(true /* receivedData */)
e.UnlockUser()
}
// OnDelayOptionSet implements tcpip.SocketOptionsHandler.OnDelayOptionSet.
func (e *Endpoint) OnDelayOptionSet(v bool) {
if !v {
e.LockUser()
defer e.UnlockUser()
// Handle delayed data.
if e.EndpointState().connected() {
e.sendData(nil /* next */)
}
}
}
// OnCorkOptionSet implements tcpip.SocketOptionsHandler.OnCorkOptionSet.
func (e *Endpoint) OnCorkOptionSet(v bool) {
if !v {
e.LockUser()
defer e.UnlockUser()
if e.snd != nil {
e.snd.corkTimer.disable()
}
// Handle the corked data.
if e.EndpointState().connected() {
e.sendData(nil /* next */)
}
}
}
func (e *Endpoint) getSendBufferSize() int {
return int(e.ops.GetSendBufferSize())
}
// OnSetReceiveBufferSize implements tcpip.SocketOptionsHandler.OnSetReceiveBufferSize.
func (e *Endpoint) OnSetReceiveBufferSize(rcvBufSz, oldSz int64) (newSz int64, postSet func()) {
e.LockUser()
sendNonZeroWindowUpdate := false
e.rcvQueueMu.Lock()
// Make sure the receive buffer size allows us to send a
// non-zero window size.
scale := uint8(0)
if e.rcv != nil {
scale = e.rcv.RcvWndScale
}
if rcvBufSz>>scale == 0 {
rcvBufSz = 1 << scale
}
availBefore := wndFromSpace(e.receiveBufferAvailableLocked(int(oldSz)))
availAfter := wndFromSpace(e.receiveBufferAvailableLocked(int(rcvBufSz)))
e.RcvAutoParams.Disabled = true
// Immediately send an ACK to uncork the sender silly window
// syndrome prevetion, when our available space grows above aMSS
// or half receive buffer, whichever smaller.
if crossed, above := e.windowCrossedACKThresholdLocked(availAfter-availBefore, int(rcvBufSz)); crossed && above {
sendNonZeroWindowUpdate = true
}
e.rcvQueueMu.Unlock()
postSet = func() {
e.LockUser()
defer e.UnlockUser()
if e.EndpointState().connected() && sendNonZeroWindowUpdate {
e.rcv.nonZeroWindow()
}
}
e.UnlockUser()
return rcvBufSz, postSet
}
// OnSetSendBufferSize implements tcpip.SocketOptionsHandler.OnSetSendBufferSize.
func (e *Endpoint) OnSetSendBufferSize(sz int64) int64 {
e.sndQueueInfo.TCPSndBufState.AutoTuneSndBufDisabled.Store(1)
return sz
}
// WakeupWriters implements tcpip.SocketOptionsHandler.WakeupWriters.
func (e *Endpoint) WakeupWriters() {
e.LockUser()
defer e.UnlockUser()
sendBufferSize := e.getSendBufferSize()
e.sndQueueInfo.sndQueueMu.Lock()
notify := (sendBufferSize - e.sndQueueInfo.SndBufUsed) >= e.sndQueueInfo.SndBufUsed>>1
e.sndQueueInfo.sndQueueMu.Unlock()
if notify {
e.waiterQueue.Notify(waiter.WritableEvents)
}
}
// SetSockOptInt sets a socket option.
func (e *Endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) tcpip.Error {
// Lower 2 bits represents ECN bits. RFC 3168, section 23.1
const inetECNMask = 3
switch opt {
case tcpip.KeepaliveCountOption:
e.LockUser()
e.keepalive.Lock()
e.keepalive.count = v
e.keepalive.Unlock()
e.resetKeepaliveTimer(true /* receivedData */)
e.UnlockUser()
case tcpip.IPv4TOSOption:
e.LockUser()
// TODO(gvisor.dev/issue/995): ECN is not currently supported,
// ignore the bits for now.
e.sendTOS = uint8(v) & ^uint8(inetECNMask)
e.UnlockUser()
case tcpip.IPv6TrafficClassOption:
e.LockUser()
// TODO(gvisor.dev/issue/995): ECN is not currently supported,
// ignore the bits for now.
e.sendTOS = uint8(v) & ^uint8(inetECNMask)
e.UnlockUser()
case tcpip.MaxSegOption:
userMSS := v
if userMSS < header.TCPMinimumMSS || userMSS > header.TCPMaximumMSS {
return &tcpip.ErrInvalidOptionValue{}
}
e.LockUser()
e.userMSS = uint16(userMSS)
e.UnlockUser()
case tcpip.MTUDiscoverOption:
// PROBE is accepted alongside DO/WANT/DONT. In Linux,
// PROBE sets DF but ignores ICMP-based PMTU updates;
// since gVisor lacks ICMP PMTU feedback, it behaves
// identically to DO.
switch v := tcpip.PMTUDStrategy(v); v {
case tcpip.PMTUDiscoveryWant, tcpip.PMTUDiscoveryDont, tcpip.PMTUDiscoveryDo, tcpip.PMTUDiscoveryProbe:
e.LockUser()
e.pmtud = v
e.UnlockUser()
default:
return &tcpip.ErrNotSupported{}
}
case tcpip.IPv4TTLOption:
e.LockUser()
e.ipv4TTL = uint8(v)
e.UnlockUser()
case tcpip.IPv6HopLimitOption:
e.LockUser()
e.ipv6HopLimit = int16(v)
e.UnlockUser()
case tcpip.TCPSynCountOption:
if v < 1 || v > 255 {
return &tcpip.ErrInvalidOptionValue{}
}
e.LockUser()
e.maxSynRetries = uint8(v)
e.UnlockUser()
case tcpip.TCPWindowClampOption:
if v == 0 {
e.LockUser()
switch e.EndpointState() {
case StateClose, StateInitial:
e.windowClamp = 0
e.UnlockUser()
return nil
default:
e.UnlockUser()
return &tcpip.ErrInvalidOptionValue{}
}
}
var rs tcpip.TCPReceiveBufferSizeRangeOption
if err := e.stack.TransportProtocolOption(ProtocolNumber, &rs); err == nil {
if v < rs.Min/2 {
v = rs.Min / 2
}
}
e.LockUser()
e.windowClamp = uint32(v)
e.UnlockUser()
}
return nil
}
// HasNIC returns true if the NICID is defined in the stack or id is 0.
func (e *Endpoint) HasNIC(id int32) bool {
return id == 0 || e.stack.HasNIC(tcpip.NICID(id))
}
// SetSockOpt sets a socket option.
func (e *Endpoint) SetSockOpt(opt tcpip.SettableSocketOption) tcpip.Error {
switch v := opt.(type) {
case *tcpip.KeepaliveIdleOption:
e.LockUser()
e.keepalive.Lock()
e.keepalive.idle = time.Duration(*v)
e.keepalive.Unlock()
e.resetKeepaliveTimer(true /* receivedData */)
e.UnlockUser()
case *tcpip.KeepaliveIntervalOption:
e.LockUser()
e.keepalive.Lock()
e.keepalive.interval = time.Duration(*v)
e.keepalive.Unlock()
e.resetKeepaliveTimer(true /* receivedData */)
e.UnlockUser()
case *tcpip.TCPUserTimeoutOption:
e.LockUser()
e.userTimeout = time.Duration(*v)
e.UnlockUser()
case *tcpip.CongestionControlOption:
// Query the available cc algorithms in the stack and
// validate that the specified algorithm is actually
// supported in the stack.
var avail tcpip.TCPAvailableCongestionControlOption
if err := e.stack.TransportProtocolOption(ProtocolNumber, &avail); err != nil {
return err
}
availCC := strings.Split(string(avail), " ")
for _, cc := range availCC {
if *v == tcpip.CongestionControlOption(cc) {
e.LockUser()
state := e.EndpointState()
e.cc = *v
switch state {
case StateEstablished:
if e.EndpointState() == state {
e.snd.cc = e.snd.initCongestionControl(e.cc)
}
}
e.UnlockUser()
return nil
}
}
// Linux returns ENOENT when an invalid congestion
// control algorithm is specified.
return &tcpip.ErrNoSuchFile{}
case *tcpip.TCPLingerTimeoutOption:
e.LockUser()
switch {
case *v < 0:
// Same as effectively disabling TCPLinger timeout.
*v = -1
case *v == 0:
// Same as the stack default.
var stackLingerTimeout tcpip.TCPLingerTimeoutOption
if err := e.stack.TransportProtocolOption(ProtocolNumber, &stackLingerTimeout); err != nil {
panic(fmt.Sprintf("e.stack.TransportProtocolOption(%d, %+v) = %v", ProtocolNumber, &stackLingerTimeout, err))
}
*v = stackLingerTimeout
case *v > tcpip.TCPLingerTimeoutOption(MaxTCPLingerTimeout):
// Cap it to Stack's default TCP_LINGER2 timeout.
*v = tcpip.TCPLingerTimeoutOption(MaxTCPLingerTimeout)
default:
}
e.tcpLingerTimeout = time.Duration(*v)
e.UnlockUser()
case *tcpip.TCPDeferAcceptOption:
e.LockUser()
if time.Duration(*v) > MaxRTO {
*v = tcpip.TCPDeferAcceptOption(MaxRTO)
}
e.deferAccept = time.Duration(*v)
e.UnlockUser()
case *tcpip.SocketDetachFilterOption:
return nil
default:
return nil
}
return nil
}
// readyReceiveSize returns the number of bytes ready to be received.
func (e *Endpoint) readyReceiveSize() (int, tcpip.Error) {
e.LockUser()
defer e.UnlockUser()
// The endpoint cannot be in listen state.
if e.EndpointState() == StateListen {
return 0, &tcpip.ErrInvalidEndpointState{}
}
e.rcvQueueMu.Lock()
defer e.rcvQueueMu.Unlock()
return e.RcvBufUsed, nil
}
// GetSockOptInt implements tcpip.Endpoint.GetSockOptInt.
func (e *Endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
switch opt {
case tcpip.KeepaliveCountOption:
e.keepalive.Lock()
v := e.keepalive.count
e.keepalive.Unlock()
return v, nil
case tcpip.IPv4TOSOption:
e.LockUser()
v := int(e.sendTOS)
e.UnlockUser()
return v, nil
case tcpip.IPv6TrafficClassOption:
e.LockUser()
v := int(e.sendTOS)
e.UnlockUser()
return v, nil
case tcpip.MaxSegOption:
// Linux only returns user_mss value if user_mss is set and the socket is
// unconnected. Otherwise Linux returns the actual current MSS. Netstack
// mimics the user_mss behavior, but otherwise just returns the defaultMSS
// for now.
v := header.TCPDefaultMSS
e.LockUser()
if state := e.EndpointState(); e.userMSS > 0 && (state.internal() || state == StateClose || state == StateListen) {
v = int(e.userMSS)
}
e.UnlockUser()
return v, nil
case tcpip.MTUDiscoverOption:
e.LockUser()
v := e.pmtud
e.UnlockUser()
return int(v), nil
case tcpip.ReceiveQueueSizeOption:
return e.readyReceiveSize()
case tcpip.IPv4TTLOption:
e.LockUser()
v := int(e.ipv4TTL)
e.UnlockUser()
return v, nil
case tcpip.IPv6HopLimitOption:
e.LockUser()
v := int(e.ipv6HopLimit)
e.UnlockUser()
return v, nil
case tcpip.TCPSynCountOption:
e.LockUser()
v := int(e.maxSynRetries)
e.UnlockUser()
return v, nil
case tcpip.TCPWindowClampOption:
e.LockUser()
v := int(e.windowClamp)
e.UnlockUser()
return v, nil
case tcpip.MulticastTTLOption:
return 1, nil
default:
return -1, &tcpip.ErrUnknownProtocolOption{}
}
}
func (e *Endpoint) getTCPInfo() tcpip.TCPInfoOption {
info := tcpip.TCPInfoOption{}
e.LockUser()
if state := e.EndpointState(); state.internal() {
info.State = tcpip.EndpointState(StateClose)
} else {
info.State = tcpip.EndpointState(state)
}
snd := e.snd
if snd != nil {
// We do not calculate RTT before sending the data packets. If
// the connection did not send and receive data, then RTT will
// be zero.
snd.rtt.Lock()
info.RTT = snd.rtt.TCPRTTState.SRTT
info.RTTVar = snd.rtt.TCPRTTState.RTTVar
snd.rtt.Unlock()
info.RTO = snd.RTO
info.CcState = snd.state
info.SndSsthresh = uint32(snd.Ssthresh)
info.SndCwnd = uint32(snd.SndCwnd)
info.ReorderSeen = snd.rc.Reord
}
e.UnlockUser()
return info
}
// GetSockOpt implements tcpip.Endpoint.GetSockOpt.
func (e *Endpoint) GetSockOpt(opt tcpip.GettableSocketOption) tcpip.Error {
switch o := opt.(type) {
case *tcpip.TCPInfoOption:
*o = e.getTCPInfo()
case *tcpip.KeepaliveIdleOption:
e.keepalive.Lock()
*o = tcpip.KeepaliveIdleOption(e.keepalive.idle)
e.keepalive.Unlock()
case *tcpip.KeepaliveIntervalOption:
e.keepalive.Lock()
*o = tcpip.KeepaliveIntervalOption(e.keepalive.interval)
e.keepalive.Unlock()
case *tcpip.TCPUserTimeoutOption:
e.LockUser()
*o = tcpip.TCPUserTimeoutOption(e.userTimeout)
e.UnlockUser()
case *tcpip.CongestionControlOption:
e.LockUser()
*o = e.cc
e.UnlockUser()
case *tcpip.TCPLingerTimeoutOption:
e.LockUser()
*o = tcpip.TCPLingerTimeoutOption(e.tcpLingerTimeout)
e.UnlockUser()
case *tcpip.TCPDeferAcceptOption:
e.LockUser()
*o = tcpip.TCPDeferAcceptOption(e.deferAccept)
e.UnlockUser()
case *tcpip.OriginalDestinationOption:
e.LockUser()
ipt := e.stack.IPTables()
addr, port, err := ipt.OriginalDst(e.TransportEndpointInfo.ID, e.NetProto, ProtocolNumber)
e.UnlockUser()
if err != nil {
return err
}
*o = tcpip.OriginalDestinationOption{
Addr: addr,
Port: port,
}
default:
return &tcpip.ErrUnknownProtocolOption{}
}
return nil
}
// checkV4MappedLocked determines the effective network protocol and converts
// addr to its canonical form.
// +checklocks:e.mu
func (e *Endpoint) checkV4MappedLocked(addr tcpip.FullAddress, bind bool) (tcpip.FullAddress, tcpip.NetworkProtocolNumber, tcpip.Error) {
unwrapped, netProto, err := e.TransportEndpointInfo.AddrNetProtoLocked(addr, e.ops.GetV6Only(), bind)
if err != nil {
return tcpip.FullAddress{}, 0, err
}
return unwrapped, netProto, nil
}
// Disconnect implements tcpip.Endpoint.Disconnect.
func (*Endpoint) Disconnect() tcpip.Error {
return &tcpip.ErrNotSupported{}
}
// Connect connects the endpoint to its peer.
func (e *Endpoint) Connect(addr tcpip.FullAddress) tcpip.Error {
e.LockUser()
defer e.UnlockUser()
err := e.connect(addr, true)
if err != nil {
if !err.IgnoreStats() {
// Connect failed. Let's wake up any waiters.
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.ReadableEvents | waiter.WritableEvents)
e.stack.Stats().TCP.FailedConnectionAttempts.Increment()
e.stats.FailedConnectionAttempts.Increment()
}
}
return err
}
// registerEndpoint registers the endpoint with the provided address.
//
// +checklocks:e.mu
func (e *Endpoint) registerEndpoint(addr tcpip.FullAddress, netProto tcpip.NetworkProtocolNumber, nicID tcpip.NICID) tcpip.Error {
netProtos := []tcpip.NetworkProtocolNumber{netProto}
if e.TransportEndpointInfo.ID.LocalPort != 0 {
// The endpoint is bound to a port, attempt to register it.
err := e.stack.RegisterTransportEndpoint(netProtos, ProtocolNumber, e.TransportEndpointInfo.ID, e, e.boundPortFlags, e.boundBindToDevice)
if err != nil {
return err
}
} else {
// The endpoint doesn't have a local port yet, so try to get
// one. Make sure that it isn't one that will result in the same
// address/port for both local and remote (otherwise this
// endpoint would be trying to connect to itself).
sameAddr := e.TransportEndpointInfo.ID.LocalAddress == e.TransportEndpointInfo.ID.RemoteAddress
var twReuse tcpip.TCPTimeWaitReuseOption
if err := e.stack.TransportProtocolOption(ProtocolNumber, &twReuse); err != nil {
panic(fmt.Sprintf("e.stack.TransportProtocolOption(%d, %#v) = %s", ProtocolNumber, &twReuse, err))
}
reuse := twReuse == tcpip.TCPTimeWaitReuseGlobal
if twReuse == tcpip.TCPTimeWaitReuseLoopbackOnly {
switch netProto {
case header.IPv4ProtocolNumber:
reuse = header.IsV4LoopbackAddress(e.TransportEndpointInfo.ID.LocalAddress) && header.IsV4LoopbackAddress(e.TransportEndpointInfo.ID.RemoteAddress)
case header.IPv6ProtocolNumber:
reuse = e.TransportEndpointInfo.ID.LocalAddress == header.IPv6Loopback && e.TransportEndpointInfo.ID.RemoteAddress == header.IPv6Loopback
}
}
bindToDevice := tcpip.NICID(e.ops.GetBindToDevice())
if _, err := e.stack.PickEphemeralPort(e.stack.SecureRNG(), func(p uint16) (bool, tcpip.Error) {
if sameAddr && p == e.TransportEndpointInfo.ID.RemotePort {
return false, nil
}
portRes := ports.Reservation{
Networks: netProtos,
Transport: ProtocolNumber,
Addr: e.TransportEndpointInfo.ID.LocalAddress,
Port: p,
Flags: e.portFlags,
BindToDevice: bindToDevice,
Dest: addr,
}
if _, err := e.stack.ReservePort(e.stack.SecureRNG(), portRes, nil /* testPort */); err != nil {
if _, ok := err.(*tcpip.ErrPortInUse); !ok || !reuse {
return false, nil
}
transEPID := e.TransportEndpointInfo.ID
transEPID.LocalPort = p
// Check if an endpoint is registered with demuxer in TIME-WAIT and if
// we can reuse it. If we can't find a transport endpoint then we just
// skip using this port as it's possible that either an endpoint has
// bound the port but not registered with demuxer yet (no listen/connect
// done yet) or the reservation was freed between the check above and
// the FindTransportEndpoint below. But rather than retry the same port
// we just skip it and move on.
transEP := e.stack.FindTransportEndpoint(netProto, ProtocolNumber, transEPID, nicID)
if transEP == nil {
// ReservePort failed but there is no registered endpoint with
// demuxer. Which indicates there is at least some endpoint that has
// bound the port.
return false, nil
}
tcpEP := transEP.(*Endpoint)
tcpEP.LockUser()
// If the endpoint is not in TIME-WAIT or if it is in TIME-WAIT but
// less than 1 second has elapsed since its recentTS was updated then
// we cannot reuse the port.
if tcpEP.EndpointState() != StateTimeWait || e.stack.Clock().NowMonotonic().Sub(tcpEP.recentTSTime) < 1*time.Second {
tcpEP.UnlockUser()
return false, nil
}
// Since the endpoint is in TIME-WAIT it should be safe to acquire its
// Lock while holding the lock for this endpoint as endpoints in
// TIME-WAIT do not acquire locks on other endpoints.
tcpEP.transitionToStateCloseLocked()
tcpEP.drainClosingSegmentQueue()
tcpEP.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.ReadableEvents | waiter.WritableEvents)
tcpEP.UnlockUser()
// Now try and Reserve again if it fails then we skip.
portRes := ports.Reservation{
Networks: netProtos,
Transport: ProtocolNumber,
Addr: e.TransportEndpointInfo.ID.LocalAddress,
Port: p,
Flags: e.portFlags,
BindToDevice: bindToDevice,
Dest: addr,
}
if _, err := e.stack.ReservePort(e.stack.SecureRNG(), portRes, nil /* testPort */); err != nil {
return false, nil
}
}
id := e.TransportEndpointInfo.ID
id.LocalPort = p
if err := e.stack.RegisterTransportEndpoint(netProtos, ProtocolNumber, id, e, e.portFlags, bindToDevice); err != nil {
portRes := ports.Reservation{
Networks: netProtos,
Transport: ProtocolNumber,
Addr: e.TransportEndpointInfo.ID.LocalAddress,
Port: p,
Flags: e.portFlags,
BindToDevice: bindToDevice,
Dest: addr,
}
e.stack.ReleasePort(portRes)
if _, ok := err.(*tcpip.ErrPortInUse); ok {
return false, nil
}
return false, err
}
// Port picking successful. Save the details of
// the selected port.
e.TransportEndpointInfo.ID = id
e.isPortReserved = true
e.boundBindToDevice = bindToDevice
e.boundPortFlags = e.portFlags
e.boundDest = addr
return true, nil
}); err != nil {
e.stack.Stats().TCP.FailedPortReservations.Increment()
return err
}
}
return nil
}
// connect connects the endpoint to its peer.
// +checklocks:e.mu
func (e *Endpoint) connect(addr tcpip.FullAddress, handshake bool) tcpip.Error {
connectingAddr := addr.Addr
addr, netProto, err := e.checkV4MappedLocked(addr, false /* bind */)
if err != nil {
return err
}
if e.EndpointState().connected() {
// The endpoint is already connected. If caller hasn't been
// notified yet, return success.
if !e.isConnectNotified {
e.isConnectNotified = true
return nil
}
// Otherwise return that it's already connected.
return &tcpip.ErrAlreadyConnected{}
}
nicID := addr.NIC
switch e.EndpointState() {
case StateBound:
// If we're already bound to a NIC but the caller is requesting
// that we use a different one now, we cannot proceed.
if e.boundNICID == 0 {
break
}
if nicID != 0 && nicID != e.boundNICID {
return &tcpip.ErrHostUnreachable{}
}
nicID = e.boundNICID
case StateInitial:
// Nothing to do. We'll eventually fill-in the gaps in the ID (if any)
// when we find a route.
case StateConnecting, StateSynSent, StateSynRecv:
// A connection request has already been issued but hasn't completed
// yet.
return &tcpip.ErrAlreadyConnecting{}
case StateError:
if err := e.hardErrorLocked(); err != nil {
return err
}
return &tcpip.ErrConnectionAborted{}
default:
return &tcpip.ErrInvalidEndpointState{}
}
// Find a route to the desired destination.
r, err := e.stack.FindRoute(nicID, e.TransportEndpointInfo.ID.LocalAddress, addr.Addr, netProto, false /* multicastLoop */)
if err != nil {
return err
}
defer r.Release()
e.TransportEndpointInfo.ID.LocalAddress = r.LocalAddress()
e.TransportEndpointInfo.ID.RemoteAddress = r.RemoteAddress()
e.TransportEndpointInfo.ID.RemotePort = addr.Port
oldState := e.EndpointState()
e.setEndpointState(StateConnecting)
if err := e.registerEndpoint(addr, netProto, r.NICID()); err != nil {
e.setEndpointState(oldState)
if _, ok := err.(*tcpip.ErrPortInUse); ok {
return &tcpip.ErrBadLocalAddress{}
}
return err
}
e.isRegistered = true
r.Acquire()
e.route = r
e.boundNICID = nicID
e.effectiveNetProtos = []tcpip.NetworkProtocolNumber{netProto}
e.connectingAddress = connectingAddr
if e.alsoBindToV4 {
// If the endpoint was bound to `any` address the port will be
// reserved for both IPv4 and IPv6 addresses. Release the port
// reservation for the IPv4 address here so that the future bind
// for IPv4 socket will not fail.
portRes := ports.Reservation{
Networks: []tcpip.NetworkProtocolNumber{header.IPv4ProtocolNumber},
Transport: ProtocolNumber,
Port: e.TransportEndpointInfo.ID.LocalPort,
}
e.stack.ReleasePort(portRes)
}
e.initGSO()
// Connect in the restore phase does not perform handshake. Restore its
// connection setting here.
if !handshake {
e.segmentQueue.mu.Lock()
for _, l := range []segmentList{e.segmentQueue.list, e.snd.writeList.writeList} {
for s := l.Front(); s != nil; s = s.Next() {
s.id = e.TransportEndpointInfo.ID
}
}
e.segmentQueue.mu.Unlock()
e.snd.updateMaxPayloadSize(int(e.route.MTU()), 0)
e.setEndpointState(StateEstablished)
// Set the new auto tuned send buffer size after entering
// established state.
e.ops.SetSendBufferSize(e.computeTCPSendBufferSize(), false /* notify */)
return &tcpip.ErrConnectStarted{}
}
// Start a new handshake.
h := e.newHandshake()
e.setEndpointState(StateSynSent)
h.start()
e.stack.Stats().TCP.ActiveConnectionOpenings.Increment()
return &tcpip.ErrConnectStarted{}
}
// ConnectEndpoint is not supported.
func (*Endpoint) ConnectEndpoint(tcpip.Endpoint) tcpip.Error {
return &tcpip.ErrInvalidEndpointState{}
}
// Shutdown closes the read and/or write end of the endpoint connection to its
// peer.
func (e *Endpoint) Shutdown(flags tcpip.ShutdownFlags) tcpip.Error {
e.LockUser()
defer e.UnlockUser()
if e.EndpointState().connecting() {
// When calling shutdown(2) on a connecting socket, the endpoint must
// enter the error state. But this logic cannot belong to the shutdownLocked
// method because that method is called during a close(2) (and closing a
// connecting socket is not an error).
e.handshakeFailed(&tcpip.ErrConnectionReset{})
e.waiterQueue.Notify(waiter.WritableEvents | waiter.EventHUp | waiter.EventErr)
return nil
}
return e.shutdownLocked(flags)
}
// +checklocks:e.mu
func (e *Endpoint) shutdownLocked(flags tcpip.ShutdownFlags) tcpip.Error {
e.shutdownFlags |= flags
switch {
case e.EndpointState().connected():
// Close for read.
if e.shutdownFlags&tcpip.ShutdownRead != 0 {
// Mark read side as closed.
e.rcvQueueMu.Lock()
e.RcvClosed = true
rcvBufUsed := e.RcvBufUsed
e.rcvQueueMu.Unlock()
// If we're fully closed and we have unread data we need to abort
// the connection with a RST.
if e.shutdownFlags&tcpip.ShutdownWrite != 0 && rcvBufUsed > 0 {
e.resetConnectionLocked(&tcpip.ErrConnectionAborted{})
return nil
}
// Wake up any readers that maybe waiting for the stream to become
// readable.
events := waiter.ReadableEvents
if e.shutdownFlags&tcpip.ShutdownWrite == 0 {
// If ShutdownWrite is not set, write end won't close and
// we end up with a half-closed connection
events |= waiter.EventRdHUp
}
e.waiterQueue.Notify(events)
}
// Close for write.
if e.shutdownFlags&tcpip.ShutdownWrite != 0 {
e.sndQueueInfo.sndQueueMu.Lock()
if e.sndQueueInfo.SndClosed {
// Already closed.
e.sndQueueInfo.sndQueueMu.Unlock()
if e.EndpointState() == StateTimeWait {
return &tcpip.ErrNotConnected{}
}
return nil
}
// Queue FIN and transition to the closing state immediately,
// matching Linux tcp_close_state(): the FIN may be queued but
// not yet transmitted when the write queue is blocked.
s := newOutgoingSegment(e.TransportEndpointInfo.ID, e.stack.Clock(), buffer.Buffer{}, e.ops.GetMark())
e.snd.writeList.PushBack(s)
e.updateConnDirectionState(connDirectionStateSndClosed)
switch e.EndpointState() {
case StateCloseWait:
e.setEndpointState(StateLastAck)
default:
e.setEndpointState(StateFinWait1)
}
// Mark endpoint as closed.
e.sndQueueInfo.SndClosed = true
e.sndQueueInfo.sndQueueMu.Unlock()
// Drain the send queue.
e.sendData(s)
// Mark send side as closed.
e.snd.Closed = true
// Wake up any writers that maybe waiting for the stream to become
// writable.
e.waiterQueue.Notify(waiter.WritableEvents)
}
return nil
case e.EndpointState() == StateListen:
if e.shutdownFlags&tcpip.ShutdownRead != 0 {
// Reset all connections from the accept queue and keep the
// worker running so that it can continue handling incoming
// segments by replying with RST.
//
// By not removing this endpoint from the demuxer mapping, we
// ensure that any other bind to the same port fails, as on Linux.
e.rcvQueueMu.Lock()
e.RcvClosed = true
e.rcvQueueMu.Unlock()
e.closePendingAcceptableConnectionsLocked()
// Notify waiters that the endpoint is shutdown.
e.waiterQueue.Notify(waiter.ReadableEvents | waiter.WritableEvents | waiter.EventHUp | waiter.EventErr)
}
return nil
default:
return &tcpip.ErrNotConnected{}
}
}
// Listen puts the endpoint in "listen" mode, which allows it to accept
// new connections.
func (e *Endpoint) Listen(backlog int) tcpip.Error {
if err := e.listen(backlog); err != nil {
if !err.IgnoreStats() {
e.stack.Stats().TCP.FailedConnectionAttempts.Increment()
e.stats.FailedConnectionAttempts.Increment()
}
return err
}
return nil
}
func (e *Endpoint) listen(backlog int) tcpip.Error {
e.LockUser()
defer e.UnlockUser()
if e.EndpointState() == StateListen && !e.closed {
e.acceptMu.Lock()
defer e.acceptMu.Unlock()
// Adjust the size of the backlog iff we can fit
// existing pending connections into the new one.
if e.acceptQueue.endpoints.Len() > backlog {
return &tcpip.ErrInvalidEndpointState{}
}
e.acceptQueue.capacity = backlog
if e.acceptQueue.pendingEndpoints == nil {
e.acceptQueue.pendingEndpoints = make(map[*Endpoint]struct{})
}
e.shutdownFlags = 0
e.updateConnDirectionState(connDirectionStateOpen)
e.rcvQueueMu.Lock()
e.RcvClosed = false
e.rcvQueueMu.Unlock()
return nil
}
if e.EndpointState() == StateInitial {
// The listen is called on an unbound socket, the socket is
// automatically bound to a random free port with the local
// address set to INADDR_ANY.
if err := e.bindLocked(tcpip.FullAddress{}); err != nil {
return err
}
}
// Endpoint must be bound before it can transition to listen mode.
if e.EndpointState() != StateBound {
e.stats.ReadErrors.InvalidEndpointState.Increment()
return &tcpip.ErrInvalidEndpointState{}
}
// Setting this state after RegisterTransportEndpoint will result in a
// race where the endpoint is in Bound but reachable via the demuxer. Instead
// we set it to listen so that incoming packets will just be queued to the
// inbound segment queue by the TCP processor.
e.setEndpointState(StateListen)
// Register the endpoint.
if err := e.stack.RegisterTransportEndpoint(e.effectiveNetProtos, ProtocolNumber, e.TransportEndpointInfo.ID, e, e.boundPortFlags, e.boundBindToDevice); err != nil {
e.transitionToStateCloseLocked()
return err
}
e.isRegistered = true
// The queue may be non-zero when we're restoring the endpoint, and it
// may be pre-populated with some previously accepted (but not Accepted)
// endpoints.
e.acceptMu.Lock()
if e.acceptQueue.pendingEndpoints == nil {
e.acceptQueue.pendingEndpoints = make(map[*Endpoint]struct{})
}
if e.acceptQueue.capacity == 0 {
e.acceptQueue.capacity = backlog
}
e.acceptMu.Unlock()
// Initialize the listening context.
rcvWnd := seqnum.Size(e.receiveBufferAvailable())
e.listenCtx = newListenContext(e.stack, e.protocol, e, rcvWnd, e.ops.GetV6Only(), e.NetProto)
return nil
}
// Accept returns a new endpoint if a peer has established a connection
// to an endpoint previously set to listen mode.
//
// addr if not-nil will contain the peer address of the returned endpoint.
func (e *Endpoint) Accept(peerAddr *tcpip.FullAddress) (tcpip.Endpoint, *waiter.Queue, tcpip.Error) {
e.LockUser()
defer e.UnlockUser()
e.rcvQueueMu.Lock()
rcvClosed := e.RcvClosed
e.rcvQueueMu.Unlock()
// Endpoint must be in listen state before it can accept connections.
if rcvClosed || e.EndpointState() != StateListen {
return nil, nil, &tcpip.ErrInvalidEndpointState{}
}
// Get the new accepted endpoint.
var n *Endpoint
e.acceptMu.Lock()
if element := e.acceptQueue.endpoints.Front(); element != nil {
n = e.acceptQueue.endpoints.Remove(element).(*Endpoint)
}
e.acceptMu.Unlock()
if n == nil {
return nil, nil, &tcpip.ErrWouldBlock{}
}
if peerAddr != nil {
*peerAddr = n.getRemoteAddress()
}
return n, n.waiterQueue, nil
}
// Bind binds the endpoint to a specific local port and optionally address.
func (e *Endpoint) Bind(addr tcpip.FullAddress) (err tcpip.Error) {
e.LockUser()
defer e.UnlockUser()
return e.bindLocked(addr)
}
// +checklocks:e.mu
func (e *Endpoint) bindLocked(addr tcpip.FullAddress) (err tcpip.Error) {
// Don't allow binding once endpoint is not in the initial state
// anymore. This is because once the endpoint goes into a connected or
// listen state, it is already bound.
if e.EndpointState() != StateInitial {
return &tcpip.ErrAlreadyBound{}
}
e.BindAddr = addr.Addr
addr, netProto, err := e.checkV4MappedLocked(addr, true /* bind */)
if err != nil {
return err
}
netProtos := []tcpip.NetworkProtocolNumber{netProto}
// Expand netProtos to include v4 and v6 under dual-stack if the caller is
// binding to a wildcard (empty) address, and this is an IPv6 endpoint with
// v6only set to false.
if netProto == header.IPv6ProtocolNumber {
stackHasV4 := e.stack.CheckNetworkProtocol(header.IPv4ProtocolNumber)
alsoBindToV4 := !e.ops.GetV6Only() && addr.Addr == tcpip.Address{} && stackHasV4
if alsoBindToV4 {
netProtos = append(netProtos, header.IPv4ProtocolNumber)
e.alsoBindToV4 = true
}
}
var nic tcpip.NICID
// If an address is specified, we must ensure that it's one of our
// local addresses.
if addr.Addr.Len() != 0 {
nic = e.stack.CheckLocalAddress(addr.NIC, netProto, addr.Addr)
if nic == 0 {
return &tcpip.ErrBadLocalAddress{}
}
e.TransportEndpointInfo.ID.LocalAddress = addr.Addr
}
bindToDevice := tcpip.NICID(e.ops.GetBindToDevice())
portRes := ports.Reservation{
Networks: netProtos,
Transport: ProtocolNumber,
Addr: addr.Addr,
Port: addr.Port,
Flags: e.portFlags,
BindToDevice: bindToDevice,
Dest: tcpip.FullAddress{},
}
port, err := e.stack.ReservePort(e.stack.SecureRNG(), portRes, func(p uint16) (bool, tcpip.Error) {
id := e.TransportEndpointInfo.ID
id.LocalPort = p
// CheckRegisterTransportEndpoint should only return an error if there is a
// listening endpoint bound with the same id and portFlags and bindToDevice
// options.
//
// NOTE: Only listening and connected endpoint register with
// demuxer. Further connected endpoints always have a remote
// address/port. Hence this will only return an error if there is a matching
// listening endpoint.
if err := e.stack.CheckRegisterTransportEndpoint(netProtos, ProtocolNumber, id, e.portFlags, bindToDevice); err != nil {
return false, nil
}
return true, nil
})
if err != nil {
e.stack.Stats().TCP.FailedPortReservations.Increment()
return err
}
e.boundBindToDevice = bindToDevice
e.boundPortFlags = e.portFlags
// TODO(gvisor.dev/issue/3691): Add test to verify boundNICID is correct.
e.boundNICID = nic
e.isPortReserved = true
e.effectiveNetProtos = netProtos
e.TransportEndpointInfo.ID.LocalPort = port
// Mark endpoint as bound.
e.setEndpointState(StateBound)
return nil
}
// GetLocalAddress returns the address to which the endpoint is bound.
func (e *Endpoint) GetLocalAddress() (tcpip.FullAddress, tcpip.Error) {
e.LockUser()
defer e.UnlockUser()
return tcpip.FullAddress{
Addr: e.TransportEndpointInfo.ID.LocalAddress,
Port: e.TransportEndpointInfo.ID.LocalPort,
NIC: e.boundNICID,
}, nil
}
// GetRemoteAddress returns the address to which the endpoint is connected.
func (e *Endpoint) GetRemoteAddress() (tcpip.FullAddress, tcpip.Error) {
e.LockUser()
defer e.UnlockUser()
if !e.EndpointState().connected() {
return tcpip.FullAddress{}, &tcpip.ErrNotConnected{}
}
return e.getRemoteAddress(), nil
}
func (e *Endpoint) getRemoteAddress() tcpip.FullAddress {
return tcpip.FullAddress{
Addr: e.TransportEndpointInfo.ID.RemoteAddress,
Port: e.TransportEndpointInfo.ID.RemotePort,
NIC: e.boundNICID,
}
}
// HandlePacket implements stack.TransportEndpoint.HandlePacket.
func (*Endpoint) HandlePacket(stack.TransportEndpointID, *stack.PacketBuffer) {
// TCP HandlePacket is not required anymore as inbound packets first
// land at the Dispatcher which then can either deliver using the
// worker go routine or directly do the invoke the tcp processing inline
// based on the state of the endpoint.
}
func (e *Endpoint) enqueueSegment(s *segment) bool {
// Send packet to worker goroutine.
if !e.segmentQueue.enqueue(s) {
// The queue is full, so we drop the segment.
e.stack.Stats().DroppedPackets.Increment()
e.stats.ReceiveErrors.SegmentQueueDropped.Increment()
return false
}
return true
}
func (e *Endpoint) onICMPError(err tcpip.Error, transErr stack.TransportError, pkt *stack.PacketBuffer) {
// Update last error first.
e.lastErrorMu.Lock()
e.lastError = err
e.lastErrorMu.Unlock()
var recvErr bool
switch pkt.NetworkProtocolNumber {
case header.IPv4ProtocolNumber:
recvErr = e.SocketOptions().GetIPv4RecvError()
case header.IPv6ProtocolNumber:
recvErr = e.SocketOptions().GetIPv6RecvError()
default:
panic(fmt.Sprintf("unhandled network protocol number = %d", pkt.NetworkProtocolNumber))
}
if recvErr {
e.SocketOptions().QueueErr(&tcpip.SockError{
Err: err,
Cause: transErr,
// Linux passes the payload with the TCP header. We don't know if the TCP
// header even exists, it may not for fragmented packets.
Payload: pkt.Data().AsRange().ToView(),
Dst: tcpip.FullAddress{
NIC: pkt.NICID,
Addr: e.TransportEndpointInfo.ID.RemoteAddress,
Port: e.TransportEndpointInfo.ID.RemotePort,
},
Offender: tcpip.FullAddress{
NIC: pkt.NICID,
Addr: e.TransportEndpointInfo.ID.LocalAddress,
Port: e.TransportEndpointInfo.ID.LocalPort,
},
NetProto: pkt.NetworkProtocolNumber,
})
}
if e.EndpointState().connecting() {
e.mu.Lock()
if e.h != nil && e.h.listenEP != nil {
lEP := e.h.listenEP
// Remove from listening endpoints pending list.
lEP.acceptMu.Lock()
delete(lEP.acceptQueue.pendingEndpoints, e)
lEP.acceptMu.Unlock()
lEP.stats.FailedConnectionAttempts.Increment()
}
e.stack.Stats().TCP.FailedConnectionAttempts.Increment()
e.cleanupLocked()
e.hardError = err
e.setEndpointState(StateError)
e.mu.Unlock()
e.drainClosingSegmentQueue()
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.ReadableEvents | waiter.WritableEvents)
}
}
// HandleError implements stack.TransportEndpoint.
func (e *Endpoint) HandleError(transErr stack.TransportError, pkt *stack.PacketBuffer) {
handlePacketTooBig := func(mtu uint32) {
e.sndQueueInfo.sndQueueMu.Lock()
update := false
if v := int(mtu); v < e.sndQueueInfo.SndMTU {
e.sndQueueInfo.SndMTU = v
update = true
}
newMTU := e.sndQueueInfo.SndMTU
e.sndQueueInfo.sndQueueMu.Unlock()
if update {
e.mu.Lock()
defer e.mu.Unlock()
if e.snd != nil {
e.snd.updateMaxPayloadSize(newMTU, 1 /* count */)
}
}
}
// TODO(gvisor.dev/issues/5270): Handle all transport errors.
switch transErr.Kind() {
case stack.PacketTooBigTransportError:
handlePacketTooBig(transErr.Info())
case stack.DestinationHostUnreachableTransportError:
e.onICMPError(&tcpip.ErrHostUnreachable{}, transErr, pkt)
case stack.DestinationNetworkUnreachableTransportError:
e.onICMPError(&tcpip.ErrNetworkUnreachable{}, transErr, pkt)
case stack.DestinationPortUnreachableTransportError:
e.onICMPError(&tcpip.ErrConnectionRefused{}, transErr, pkt)
case stack.DestinationProtoUnreachableTransportError:
e.onICMPError(&tcpip.ErrUnknownProtocolOption{}, transErr, pkt)
case stack.SourceRouteFailedTransportError:
e.onICMPError(&tcpip.ErrNotSupported{}, transErr, pkt)
case stack.SourceHostIsolatedTransportError:
e.onICMPError(&tcpip.ErrNoNet{}, transErr, pkt)
case stack.DestinationHostDownTransportError:
e.onICMPError(&tcpip.ErrHostDown{}, transErr, pkt)
}
}
// updateSndBufferUsage is called by when room opens up in the send buffer. The
// number of newly available bytes is v.
//
// +checklocks:e.mu
func (e *Endpoint) updateSndBufferUsage(v int) {
sendBufferSize := e.getSendBufferSize()
e.sndQueueInfo.sndQueueMu.Lock()
notify := e.sndQueueInfo.SndBufUsed >= sendBufferSize>>1
e.sndQueueInfo.SndBufUsed -= v
// Get the new send buffer size with auto tuning, but do not set it
// unless we decide to notify the writers.
newSndBufSz := e.computeTCPSendBufferSize()
// We only notify when there is half the sendBufferSize available after
// a full buffer event occurs. This ensures that we don't wake up
// writers to queue just 1-2 segments and go back to sleep.
notify = notify && e.sndQueueInfo.SndBufUsed < int(newSndBufSz)>>1
e.sndQueueInfo.sndQueueMu.Unlock()
if notify {
// Set the new send buffer size calculated from auto tuning.
e.ops.SetSendBufferSize(newSndBufSz, false /* notify */)
e.waiterQueue.Notify(waiter.WritableEvents)
}
}
// readyToRead is called when a new segment is ready to be read, or when the
// connection is closed for receiving (in which case s will be nil).
//
// +checklocks:e.mu
func (e *Endpoint) readyToRead(s *segment) {
e.rcvQueueMu.Lock()
if s != nil {
e.RcvBufUsed += s.payloadSize()
s.IncRef()
e.rcvQueue.PushBack(s)
} else {
e.RcvClosed = true
}
e.rcvQueueMu.Unlock()
e.waiterQueue.Notify(waiter.ReadableEvents)
}
// receiveBufferAvailableLocked calculates how many bytes are still available
// in the receive buffer.
// +checklocks:e.rcvQueueMu
func (e *Endpoint) receiveBufferAvailableLocked(rcvBufSize int) int {
// We may use more bytes than the buffer size when the receive buffer
// shrinks.
memUsed := e.receiveMemUsed()
if memUsed >= rcvBufSize {
return 0
}
return rcvBufSize - memUsed
}
// receiveBufferAvailable calculates how many bytes are still available in the
// receive buffer based on the actual memory used by all segments held in
// receive buffer/pending and segment queue.
func (e *Endpoint) receiveBufferAvailable() int {
e.rcvQueueMu.Lock()
available := e.receiveBufferAvailableLocked(int(e.ops.GetReceiveBufferSize()))
e.rcvQueueMu.Unlock()
return available
}
// receiveBufferUsed returns the amount of in-use receive buffer.
func (e *Endpoint) receiveBufferUsed() int {
e.rcvQueueMu.Lock()
used := e.RcvBufUsed
e.rcvQueueMu.Unlock()
return used
}
// receiveMemUsed returns the total memory in use by segments held by this
// endpoint.
func (e *Endpoint) receiveMemUsed() int {
return int(e.rcvMemUsed.Load())
}
// updateReceiveMemUsed adds the provided delta to e.rcvMemUsed.
func (e *Endpoint) updateReceiveMemUsed(delta int) {
e.rcvMemUsed.Add(int32(delta))
}
// maxReceiveBufferSize returns the stack wide maximum receive buffer size for
// an endpoint.
func (e *Endpoint) maxReceiveBufferSize() int {
var rs tcpip.TCPReceiveBufferSizeRangeOption
if err := e.stack.TransportProtocolOption(ProtocolNumber, &rs); err != nil {
// As a fallback return the hardcoded max buffer size.
return MaxBufferSize
}
return rs.Max
}
// directionState returns the close state of send and receive part of the endpoint
func (e *Endpoint) connDirectionState() connDirectionState {
return connDirectionState(e.connectionDirectionState.Load())
}
// updateDirectionState updates the close state of send and receive part of the endpoint
func (e *Endpoint) updateConnDirectionState(state connDirectionState) connDirectionState {
return connDirectionState(e.connectionDirectionState.Swap(uint32(e.connDirectionState() | state)))
}
// rcvWndScaleForHandshake computes the receive window scale to offer to the
// peer when window scaling is enabled (true by default). If auto-tuning is
// disabled then the window scaling factor is based on the size of the
// receiveBuffer otherwise we use the max permissible receive buffer size to
// compute the scale.
func (e *Endpoint) rcvWndScaleForHandshake() int {
bufSizeForScale := e.ops.GetReceiveBufferSize()
e.rcvQueueMu.Lock()
autoTuningDisabled := e.RcvAutoParams.Disabled
e.rcvQueueMu.Unlock()
if autoTuningDisabled {
return FindWndScale(seqnum.Size(bufSizeForScale))
}
return FindWndScale(seqnum.Size(e.maxReceiveBufferSize()))
}
// updateRecentTimestamp updates the recent timestamp using the algorithm
// described in https://tools.ietf.org/html/rfc7323#section-4.3
func (e *Endpoint) updateRecentTimestamp(tsVal uint32, maxSentAck seqnum.Value, segSeq seqnum.Value) {
if e.SendTSOk && seqnum.Value(e.recentTimestamp()).LessThan(seqnum.Value(tsVal)) && segSeq.LessThanEq(maxSentAck) {
e.setRecentTimestamp(tsVal)
}
}
// maybeEnableTimestamp marks the timestamp option enabled for this endpoint if
// the SYN options indicate that timestamp option was negotiated. It also
// initializes the recentTS with the value provided in synOpts.TSval.
func (e *Endpoint) maybeEnableTimestamp(synOpts header.TCPSynOptions) {
if synOpts.TS {
e.SendTSOk = true
e.setRecentTimestamp(synOpts.TSVal)
}
}
func (e *Endpoint) tsVal(now tcpip.MonotonicTime) uint32 {
return e.TSOffset.TSVal(now)
}
func (e *Endpoint) tsValNow() uint32 {
return e.tsVal(e.stack.Clock().NowMonotonic())
}
func (e *Endpoint) elapsed(now tcpip.MonotonicTime, tsEcr uint32) time.Duration {
return e.TSOffset.Elapsed(now, tsEcr)
}
// maybeEnableSACKPermitted marks the SACKPermitted option enabled for this endpoint
// if the SYN options indicate that the SACK option was negotiated and the TCP
// stack is configured to enable TCP SACK option.
func (e *Endpoint) maybeEnableSACKPermitted(synOpts header.TCPSynOptions) {
var v tcpip.TCPSACKEnabled
if err := e.stack.TransportProtocolOption(ProtocolNumber, &v); err != nil {
// Stack doesn't support SACK. So just return.
return
}
if bool(v) && synOpts.SACKPermitted {
e.SACKPermitted = true
e.stack.TransportProtocolOption(ProtocolNumber, &e.tcpRecovery)
}
}
// maxOptionSize return the maximum size of TCP options.
func (e *Endpoint) maxOptionSize() (size int) {
var maxSackBlocks [header.TCPMaxSACKBlocks]header.SACKBlock
options := e.makeOptions(maxSackBlocks[:])
size = len(options)
putOptions(options)
return size
}
// completeStateLocked makes a full copy of the endpoint and returns it. This is
// used before invoking the probe.
//
// +checklocks:e.mu
func (e *Endpoint) completeStateLocked(s *TCPEndpointState) {
s.TCPEndpointStateInner = e.TCPEndpointStateInner
s.ID = TCPEndpointID(e.TransportEndpointInfo.ID)
s.SegTime = e.stack.Clock().NowMonotonic()
s.Receiver = e.rcv.TCPReceiverState
s.Sender = e.snd.TCPSenderState
sndBufSize := e.getSendBufferSize()
// Copy the send buffer atomically.
e.sndQueueInfo.sndQueueMu.Lock()
e.sndQueueInfo.CloneState(&s.SndBufState)
s.SndBufState.SndBufSize = sndBufSize
e.sndQueueInfo.sndQueueMu.Unlock()
// Copy the receive buffer atomically.
e.rcvQueueMu.Lock()
s.RcvBufState = e.TCPRcvBufState
e.rcvQueueMu.Unlock()
// Copy the endpoint TCP Option state.
s.SACK.Blocks = make([]header.SACKBlock, e.sack.NumBlocks)
copy(s.SACK.Blocks, e.sack.Blocks[:e.sack.NumBlocks])
s.SACK.ReceivedBlocks, s.SACK.MaxSACKED = e.scoreboard.Copy()
e.snd.rtt.Lock()
s.Sender.RTTState = e.snd.rtt.TCPRTTState
e.snd.rtt.Unlock()
if cubic, ok := e.snd.cc.(*cubicState); ok {
s.Sender.Cubic = cubic.TCPCubicState
s.Sender.Cubic.TimeSinceLastCongestion = e.stack.Clock().NowMonotonic().Sub(s.Sender.Cubic.T)
}
s.Sender.RACKState = e.snd.rc.TCPRACKState
s.Sender.RetransmitTS = e.snd.retransmitTS
s.Sender.SpuriousRecovery = e.snd.spuriousRecovery
}
func (e *Endpoint) initHostGSO() {
switch e.route.NetProto() {
case header.IPv4ProtocolNumber:
e.gso.Type = stack.GSOTCPv4
e.gso.L3HdrLen = header.IPv4MinimumSize
case header.IPv6ProtocolNumber:
e.gso.Type = stack.GSOTCPv6
e.gso.L3HdrLen = header.IPv6MinimumSize
default:
panic(fmt.Sprintf("Unknown netProto: %v", e.NetProto))
}
e.gso.NeedsCsum = true
e.gso.CsumOffset = header.TCPChecksumOffset
e.gso.MaxSize = e.route.GSOMaxSize()
}
func (e *Endpoint) initGSO() {
if e.route.HasHostGSOCapability() {
e.initHostGSO()
} else if e.route.HasGVisorGSOCapability() {
e.gso = stack.GSO{
MaxSize: e.route.GSOMaxSize(),
Type: stack.GSOGvisor,
NeedsCsum: false,
}
}
}
// State implements tcpip.Endpoint.State. It exports the endpoint's protocol
// state for diagnostics.
func (e *Endpoint) State() uint32 {
return uint32(e.EndpointState())
}
// Info returns a copy of the endpoint info.
func (e *Endpoint) Info() tcpip.EndpointInfo {
e.LockUser()
// Make a copy of the endpoint info.
ret := e.TransportEndpointInfo
e.UnlockUser()
return &ret
}
// Stats returns a pointer to the endpoint stats.
func (e *Endpoint) Stats() tcpip.EndpointStats {
return &e.stats
}
// Wait implements stack.TransportEndpoint.Wait.
func (e *Endpoint) Wait() {
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.EventHUp)
e.waiterQueue.EventRegister(&waitEntry)
defer e.waiterQueue.EventUnregister(&waitEntry)
switch e.EndpointState() {
case StateClose, StateError:
return
}
<-notifyCh
}
// SocketOptions implements tcpip.Endpoint.SocketOptions.
func (e *Endpoint) SocketOptions() *tcpip.SocketOptions {
return &e.ops
}
// GetTCPSendBufferLimits is used to get send buffer size limits for TCP.
func GetTCPSendBufferLimits(sh tcpip.StackHandler) tcpip.SendBufferSizeOption {
// This type assertion is safe because only the TCP stack calls this
// function.
ss := sh.(*stack.Stack).TCPSendBufferLimits()
return tcpip.SendBufferSizeOption(ss)
}
// allowOutOfWindowAck returns true if an out-of-window ACK can be sent now.
func (e *Endpoint) allowOutOfWindowAck() bool {
now := e.stack.Clock().NowMonotonic()
if e.lastOutOfWindowAckTime != (tcpip.MonotonicTime{}) {
var limit stack.TCPInvalidRateLimitOption
if err := e.stack.Option(&limit); err != nil {
panic(fmt.Sprintf("e.stack.Option(%+v) failed with error: %s", limit, err))
}
if now.Sub(e.lastOutOfWindowAckTime) < time.Duration(limit) {
return false
}
}
e.lastOutOfWindowAckTime = now
return true
}
// GetTCPReceiveBufferLimits is used to get send buffer size limits for TCP.
func GetTCPReceiveBufferLimits(s tcpip.StackHandler) tcpip.ReceiveBufferSizeOption {
var ss tcpip.TCPReceiveBufferSizeRangeOption
if err := s.TransportProtocolOption(header.TCPProtocolNumber, &ss); err != nil {
panic(fmt.Sprintf("s.TransportProtocolOption(%d, %#v) = %s", header.TCPProtocolNumber, ss, err))
}
return tcpip.ReceiveBufferSizeOption(ss)
}
// computeTCPSendBufferSize implements auto tuning of send buffer size and
// returns the new send buffer size.
//
// +checklocks:e.mu
func (e *Endpoint) computeTCPSendBufferSize() int64 {
curSndBufSz := int64(e.getSendBufferSize())
// Auto tuning is disabled when the user explicitly sets the send
// buffer size with SO_SNDBUF option.
if disabled := e.sndQueueInfo.TCPSndBufState.AutoTuneSndBufDisabled.Load(); disabled == 1 {
return curSndBufSz
}
const packetOverheadFactor = 2
curMSS := e.snd.MaxPayloadSize
numSeg := InitialCwnd
if numSeg < e.snd.SndCwnd {
numSeg = e.snd.SndCwnd
}
// SndCwnd indicates the number of segments that can be sent. This means
// that the sender can send upto #SndCwnd segments and the send buffer
// size should be set to SndCwnd*MSS to accommodate sending of all the
// segments.
newSndBufSz := int64(numSeg * curMSS * packetOverheadFactor)
if newSndBufSz < curSndBufSz {
return curSndBufSz
}
if ss := GetTCPSendBufferLimits(e.stack); int64(ss.Max) < newSndBufSz {
newSndBufSz = int64(ss.Max)
}
return newSndBufSz
}
// GetAcceptConn implements tcpip.SocketOptionsHandler.
func (e *Endpoint) GetAcceptConn() bool {
return EndpointState(e.State()) == StateListen
}
// getExperimentOptionValue returns the experiment option value set on the
// endpoint if experiment IP options are enabled on outgoing NIC of the route.
func (e *Endpoint) getExperimentOptionValue(route *stack.Route) uint16 {
if nic, err := e.stack.GetNICByID(route.OutgoingNIC()); err == nil && nic.GetExperimentIPOptionEnabled() {
return e.ops.GetExperimentOptionValue()
}
return 0
}