Содержимое пина, зафиксированного в go.mod sing-box-lx, одним коммитом без истории. Полная история SagerNet/gvisor — 1.45 ГБ и клонируется в каждой CI-джобе; наша дельта — одна вставка в одну функцию, история для неё не нужна. Module path github.com/sagernet/gvisor сохранён намеренно: на него опирается replace-директива суперпроекта. Патч поверх — отдельным коммитом, чтобы дельта читалась одним git show и переносилась на новый пин копированием. SPECS/TASKS/048-GVISOR_HANDSHAKE_NIL_CRASH
1532 lines
50 KiB
Go
1532 lines
50 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 (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/sagernet/gvisor/pkg/sync"
|
|
"github.com/sagernet/gvisor/pkg/tcpip"
|
|
"github.com/sagernet/gvisor/pkg/tcpip/checksum"
|
|
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
|
"github.com/sagernet/gvisor/pkg/tcpip/seqnum"
|
|
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
|
"github.com/sagernet/gvisor/pkg/waiter"
|
|
)
|
|
|
|
const (
|
|
// tcpMinTimeout is the minimum timeout for a SYN retransmit.
|
|
// This mirrors the TCP_TIMEOUT_MIN variable in Linux.
|
|
// See: https://github.com/torvalds/linux/blob/249aca0d3d631660aa3583c6a3559b75b6e971b4/include/net/tcp.h#L143
|
|
tcpMinTimeout = 2 * time.Microsecond
|
|
|
|
// InitialRTO is the initial retransmission timeout.
|
|
// https://github.com/torvalds/linux/blob/7c636d4d20f/include/net/tcp.h#L142
|
|
InitialRTO = time.Second
|
|
|
|
// maxSegmentsPerWake is the maximum number of segments to process per
|
|
// wake-up. Yielding [after this number of segments are processed]
|
|
// allows other events to be processed as well (e.g., timeouts, resets,
|
|
// etc.).
|
|
maxSegmentsPerWake = 100
|
|
)
|
|
|
|
type handshakeState int
|
|
|
|
// The following are the possible states of the TCP connection during a 3-way
|
|
// handshake. A depiction of the states and transitions can be found in RFC 793,
|
|
// page 23.
|
|
const (
|
|
handshakeSynSent handshakeState = iota
|
|
handshakeSynRcvd
|
|
handshakeCompleted
|
|
)
|
|
|
|
const (
|
|
// Maximum space available for options.
|
|
maxOptionSize = 40
|
|
)
|
|
|
|
// handshake holds the state used during a TCP 3-way handshake.
|
|
//
|
|
// NOTE: handshake.ep.mu is held during handshake processing. It is released if
|
|
// we are going to block and reacquired when we start processing an event.
|
|
//
|
|
// +stateify savable
|
|
type handshake struct {
|
|
ep *Endpoint
|
|
listenEP *Endpoint
|
|
state handshakeState
|
|
active bool
|
|
flags header.TCPFlags
|
|
ackNum seqnum.Value
|
|
|
|
// iss is the initial send sequence number, as defined in RFC 793.
|
|
iss seqnum.Value
|
|
|
|
// rcvWnd is the receive window, as defined in RFC 793.
|
|
rcvWnd seqnum.Size
|
|
|
|
// sndWnd is the send window, as defined in RFC 793.
|
|
sndWnd seqnum.Size
|
|
|
|
// mss is the maximum segment size received from the peer.
|
|
mss uint16
|
|
|
|
// sndWndScale is the send window scale, as defined in RFC 1323. A
|
|
// negative value means no scaling is supported by the peer.
|
|
sndWndScale int
|
|
|
|
// rcvWndScale is the receive window scale, as defined in RFC 1323.
|
|
rcvWndScale int
|
|
|
|
// startTime is the time at which the first SYN/SYN-ACK was sent.
|
|
startTime tcpip.MonotonicTime
|
|
|
|
// deferAccept if non-zero will drop the final ACK for a passive
|
|
// handshake till an ACK segment with data is received or the timeout is
|
|
// hit.
|
|
deferAccept time.Duration
|
|
|
|
// acked is true if the final ACK for a 3-way handshake has
|
|
// been received. This is required to stop retransmitting the
|
|
// original SYN-ACK when deferAccept is enabled.
|
|
acked bool
|
|
|
|
// sendSYNOpts is the cached values for the SYN options to be sent.
|
|
sendSYNOpts header.TCPSynOptions
|
|
|
|
// sampleRTTWithTSOnly is true when the segment was retransmitted or we can't
|
|
// tell; then RTT can only be sampled when the incoming segment has timestamp
|
|
// options enabled.
|
|
sampleRTTWithTSOnly bool
|
|
|
|
// retransmitTimer is used to retransmit SYN/SYN-ACK with exponential backoff
|
|
// till handshake is either completed or timesout.
|
|
retransmitTimer *backoffTimer `state:"nosave"`
|
|
}
|
|
|
|
// timerHandler takes a handler function for a timer and returns a function that
|
|
// will invoke the provided handler with the endpoint mutex held. In addition
|
|
// the returned function will perform any cleanup that may be required if the
|
|
// timer handler returns an error. In the case of no errors it will notify the
|
|
// processor if there are pending segments that need to be processed.
|
|
//
|
|
// NOTE: e.mu is held for the duration of the call to f().
|
|
func timerHandler(e *Endpoint, f func() tcpip.Error) func() {
|
|
return func() {
|
|
e.mu.Lock()
|
|
if err := f(); err != nil {
|
|
e.lastErrorMu.Lock()
|
|
// If the handler timed out and we have a lastError recorded (maybe due
|
|
// to an ICMP message received), promote it to be the hard error.
|
|
if _, isTimeout := err.(*tcpip.ErrTimeout); e.lastError != nil && isTimeout {
|
|
e.hardError = e.lastError
|
|
} else {
|
|
e.hardError = err
|
|
}
|
|
e.lastError = err
|
|
e.lastErrorMu.Unlock()
|
|
e.cleanupLocked()
|
|
e.setEndpointState(StateError)
|
|
e.mu.Unlock()
|
|
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.ReadableEvents | waiter.WritableEvents)
|
|
return
|
|
}
|
|
processor := e.protocol.dispatcher.selectProcessor(e.ID)
|
|
e.mu.Unlock()
|
|
|
|
// notify processor if there are pending segments to be
|
|
// processed.
|
|
if !e.segmentQueue.empty() {
|
|
processor.queueEndpoint(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
// +checklocks:e.mu
|
|
// +checklocksacquire:h.ep.mu
|
|
func (e *Endpoint) newHandshake() (h *handshake) {
|
|
h = &handshake{
|
|
ep: e,
|
|
active: true,
|
|
rcvWnd: seqnum.Size(e.initialReceiveWindow()),
|
|
rcvWndScale: e.rcvWndScaleForHandshake(),
|
|
}
|
|
h.ep.AssertLockHeld(e)
|
|
h.resetState()
|
|
// Store reference to handshake state in endpoint.
|
|
e.h = h
|
|
// By the time handshake is created, e.ID is already initialized.
|
|
e.TSOffset = e.protocol.tsOffset(e.ID.LocalAddress, e.ID.RemoteAddress)
|
|
timer, err := newBackoffTimer(h.ep.stack.Clock(), InitialRTO, MaxRTO, timerHandler(e, h.retransmitHandlerLocked))
|
|
if err != nil {
|
|
panic(fmt.Sprintf("newBackOffTimer(_, %s, %s, _) failed: %s", InitialRTO, MaxRTO, err))
|
|
}
|
|
h.retransmitTimer = timer
|
|
return h
|
|
}
|
|
|
|
// +checklocks:e.mu
|
|
// +checklocksacquire:h.ep.mu
|
|
func (e *Endpoint) newPassiveHandshake(isn, irs seqnum.Value, opts header.TCPSynOptions, deferAccept time.Duration) (h *handshake) {
|
|
h = e.newHandshake()
|
|
h.resetToSynRcvd(isn, irs, opts, deferAccept)
|
|
return h
|
|
}
|
|
|
|
// FindWndScale determines the window scale to use for the given maximum window
|
|
// size.
|
|
func FindWndScale(wnd seqnum.Size) int {
|
|
if wnd < 0x10000 {
|
|
return 0
|
|
}
|
|
|
|
max := seqnum.Size(math.MaxUint16)
|
|
s := 0
|
|
for wnd > max && s < header.MaxWndScale {
|
|
s++
|
|
max <<= 1
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// resetState resets the state of the handshake object such that it becomes
|
|
// ready for a new 3-way handshake.
|
|
func (h *handshake) resetState() {
|
|
h.state = handshakeSynSent
|
|
h.flags = header.TCPFlagSyn
|
|
h.ackNum = 0
|
|
h.mss = 0
|
|
h.iss = generateSecureISN(h.ep.TransportEndpointInfo.ID, h.ep.stack.Clock(), h.ep.protocol.seqnumSecret)
|
|
}
|
|
|
|
// generateSecureISN generates a secure Initial Sequence number based on the
|
|
// recommendation here https://tools.ietf.org/html/rfc6528#page-3.
|
|
func generateSecureISN(id stack.TransportEndpointID, clock tcpip.Clock, seed [16]byte) seqnum.Value {
|
|
isnHasher := sha256.New()
|
|
|
|
// Per hash.Hash.Writer:
|
|
//
|
|
// It never returns an error.
|
|
_, _ = isnHasher.Write(seed[:])
|
|
_, _ = isnHasher.Write(id.LocalAddress.AsSlice())
|
|
_, _ = isnHasher.Write(id.RemoteAddress.AsSlice())
|
|
portBuf := make([]byte, 2)
|
|
binary.LittleEndian.PutUint16(portBuf, id.LocalPort)
|
|
_, _ = isnHasher.Write(portBuf)
|
|
binary.LittleEndian.PutUint16(portBuf, id.RemotePort)
|
|
_, _ = isnHasher.Write(portBuf)
|
|
// The time period here is 64ns. This is similar to what linux uses
|
|
// generate a sequence number that overlaps less than one
|
|
// time per MSL (2 minutes).
|
|
//
|
|
// A 64ns clock ticks 10^9/64 = 15625000) times in a second.
|
|
// To wrap the whole 32 bit space would require
|
|
// 2^32/1562500 ~ 274 seconds.
|
|
//
|
|
// Which sort of guarantees that we won't reuse the ISN for a new
|
|
// connection for the same tuple for at least 274s.
|
|
hash := binary.LittleEndian.Uint32(isnHasher.Sum(nil)[:4])
|
|
isn := hash + uint32(clock.NowMonotonic().Sub(tcpip.MonotonicTime{}).Nanoseconds()>>6)
|
|
return seqnum.Value(isn)
|
|
}
|
|
|
|
// effectiveRcvWndScale returns the effective receive window scale to be used.
|
|
// If the peer doesn't support window scaling, the effective rcv wnd scale is
|
|
// zero; otherwise it's the value calculated based on the initial rcv wnd.
|
|
func (h *handshake) effectiveRcvWndScale() uint8 {
|
|
if h.sndWndScale < 0 {
|
|
return 0
|
|
}
|
|
return uint8(h.rcvWndScale)
|
|
}
|
|
|
|
// resetToSynRcvd resets the state of the handshake object to the SYN-RCVD
|
|
// state.
|
|
// +checklocks:h.ep.mu
|
|
func (h *handshake) resetToSynRcvd(iss seqnum.Value, irs seqnum.Value, opts header.TCPSynOptions, deferAccept time.Duration) {
|
|
h.active = false
|
|
h.state = handshakeSynRcvd
|
|
h.flags = header.TCPFlagSyn | header.TCPFlagAck
|
|
h.iss = iss
|
|
h.ackNum = irs + 1
|
|
h.mss = opts.MSS
|
|
h.sndWndScale = opts.WS
|
|
h.deferAccept = deferAccept
|
|
h.ep.setEndpointState(StateSynRecv)
|
|
}
|
|
|
|
// checkAck checks if the ACK number, if present, of a segment received during
|
|
// a TCP 3-way handshake is valid.
|
|
func (h *handshake) checkAck(s *segment) bool {
|
|
return !(s.flags.Contains(header.TCPFlagAck) && s.ackNumber != h.iss+1)
|
|
}
|
|
|
|
// synSentState handles a segment received when the TCP 3-way handshake is in
|
|
// the SYN-SENT state.
|
|
// +checklocks:h.ep.mu
|
|
func (h *handshake) synSentState(s *segment) tcpip.Error {
|
|
// RFC 793, page 37, states that in the SYN-SENT state, a reset is
|
|
// acceptable if the ack field acknowledges the SYN.
|
|
if s.flags.Contains(header.TCPFlagRst) {
|
|
if s.flags.Contains(header.TCPFlagAck) && s.ackNumber == h.iss+1 {
|
|
// RFC 793, page 67, states that "If the RST bit is set [and] If the ACK
|
|
// was acceptable then signal the user "error: connection reset", drop
|
|
// the segment, enter CLOSED state, delete TCB, and return."
|
|
// Although the RFC above calls out ECONNRESET, Linux actually returns
|
|
// ECONNREFUSED here so we do as well.
|
|
return &tcpip.ErrConnectionRefused{}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if !h.checkAck(s) {
|
|
// RFC 793, page 72 (https://datatracker.ietf.org/doc/html/rfc793#page-72):
|
|
// If the segment acknowledgment is not acceptable, form a reset segment,
|
|
// <SEQ=SEG.ACK><CTL=RST>
|
|
// and send it.
|
|
h.ep.sendEmptyRaw(header.TCPFlagRst, s.ackNumber, 0, 0)
|
|
// Since this was a challenge ACK reschedule the retransmit timer to fire
|
|
// soon so that the SYN is retransmitted quickly.
|
|
h.retransmitTimer.reinit(tcpMinTimeout)
|
|
return nil
|
|
}
|
|
|
|
// We are in the SYN-SENT state. We only care about segments that have
|
|
// the SYN flag.
|
|
if !s.flags.Contains(header.TCPFlagSyn) {
|
|
return nil
|
|
}
|
|
|
|
// Parse the SYN options.
|
|
rcvSynOpts := parseSynSegmentOptions(s)
|
|
|
|
// Remember if the Timestamp option was negotiated.
|
|
h.ep.maybeEnableTimestamp(rcvSynOpts)
|
|
|
|
// Remember if the SACKPermitted option was negotiated.
|
|
h.ep.maybeEnableSACKPermitted(rcvSynOpts)
|
|
|
|
// Remember the sequence we'll ack from now on.
|
|
h.ackNum = s.sequenceNumber + 1
|
|
h.flags |= header.TCPFlagAck
|
|
h.mss = rcvSynOpts.MSS
|
|
h.sndWndScale = rcvSynOpts.WS
|
|
|
|
// If this is a SYN ACK response, we only need to acknowledge the SYN
|
|
// and the handshake is completed.
|
|
if s.flags.Contains(header.TCPFlagAck) {
|
|
h.state = handshakeCompleted
|
|
h.transitionToStateEstablishedLocked(s)
|
|
|
|
h.ep.sendEmptyRaw(header.TCPFlagAck, h.iss+1, h.ackNum, h.rcvWnd>>h.effectiveRcvWndScale())
|
|
return nil
|
|
}
|
|
|
|
// A SYN segment was received, but no ACK in it. We acknowledge the SYN
|
|
// but resend our own SYN and wait for it to be acknowledged in the
|
|
// SYN-RCVD state.
|
|
h.state = handshakeSynRcvd
|
|
ttl := calculateTTL(h.ep.route, h.ep.ipv4TTL, h.ep.ipv6HopLimit)
|
|
amss := h.ep.amss
|
|
h.ep.setEndpointState(StateSynRecv)
|
|
synOpts := header.TCPSynOptions{
|
|
WS: int(h.effectiveRcvWndScale()),
|
|
TS: rcvSynOpts.TS,
|
|
TSVal: h.ep.tsValNow(),
|
|
TSEcr: h.ep.recentTimestamp(),
|
|
|
|
// We only send SACKPermitted if the other side indicated it
|
|
// permits SACK. This is not explicitly defined in the RFC but
|
|
// this is the behaviour implemented by Linux.
|
|
SACKPermitted: rcvSynOpts.SACKPermitted,
|
|
MSS: amss,
|
|
}
|
|
if ttl == 0 {
|
|
ttl = h.ep.route.DefaultTTL()
|
|
}
|
|
h.ep.sendSynTCP(h.ep.route, tcpFields{
|
|
id: h.ep.TransportEndpointInfo.ID,
|
|
ttl: ttl,
|
|
tos: h.ep.sendTOS,
|
|
flags: h.flags,
|
|
seq: h.iss,
|
|
ack: h.ackNum,
|
|
rcvWnd: h.rcvWnd,
|
|
expOptVal: h.ep.getExperimentOptionValue(h.ep.route),
|
|
}, synOpts)
|
|
return nil
|
|
}
|
|
|
|
// synRcvdState handles a segment received when the TCP 3-way handshake is in
|
|
// the SYN-RCVD state.
|
|
// +checklocks:h.ep.mu
|
|
func (h *handshake) synRcvdState(s *segment) tcpip.Error {
|
|
if s.flags.Contains(header.TCPFlagRst) {
|
|
// RFC 793, page 37, states that in the SYN-RCVD state, a reset
|
|
// is acceptable if the sequence number is in the window.
|
|
if s.sequenceNumber.InWindow(h.ackNum, h.rcvWnd) {
|
|
return &tcpip.ErrConnectionRefused{}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// It's possible that s is an ACK of a SYN cookie. This can happen if:
|
|
//
|
|
// - We receive a SYN while under load and issue a SYN/ACK with
|
|
// cookie S.
|
|
// - We receive a retransmitted SYN while space exists in the SYN
|
|
// queue, and issue a SYN/ACK with seqnum S'.
|
|
// - We receive the ACK based on S.
|
|
//
|
|
// If we receive a SYN cookie ACK, just use the cookie seqnum.
|
|
if !h.checkAck(s) && h.listenEP != nil {
|
|
iss := s.ackNumber - 1
|
|
data, ok := h.listenEP.listenCtx.isCookieValid(s.id, iss, s.sequenceNumber-1)
|
|
if !ok || int(data) >= len(mssTable) {
|
|
// This isn't a valid cookie.
|
|
// RFC 793, page 72 (https://datatracker.ietf.org/doc/html/rfc793#page-72):
|
|
// If the segment acknowledgment is not acceptable, form a reset segment,
|
|
// <SEQ=SEG.ACK><CTL=RST>
|
|
// and send it.
|
|
h.ep.sendEmptyRaw(header.TCPFlagRst, s.ackNumber, 0, 0)
|
|
return nil
|
|
}
|
|
// This is a cookie that snuck its way in after we stopped using them.
|
|
h.mss = mssTable[data]
|
|
h.iss = iss
|
|
}
|
|
|
|
// RFC 793, Section 3.9, page 69, states that in the SYN-RCVD state, a
|
|
// sequence number outside of the window causes an ACK with the proper seq
|
|
// number and "After sending the acknowledgment, drop the unacceptable
|
|
// segment and return."
|
|
if !s.sequenceNumber.InWindow(h.ackNum, h.rcvWnd) {
|
|
if h.ep.allowOutOfWindowAck() {
|
|
h.ep.sendEmptyRaw(header.TCPFlagAck, h.iss+1, h.ackNum, h.rcvWnd)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if s.flags.Contains(header.TCPFlagSyn) && s.sequenceNumber != h.ackNum-1 {
|
|
// We received two SYN segments with different sequence
|
|
// numbers, so we reset this and restart the whole
|
|
// process, except that we don't reset the timer.
|
|
ack := s.sequenceNumber.Add(s.logicalLen())
|
|
seq := seqnum.Value(0)
|
|
if s.flags.Contains(header.TCPFlagAck) {
|
|
seq = s.ackNumber
|
|
}
|
|
h.ep.sendEmptyRaw(header.TCPFlagRst|header.TCPFlagAck, seq, ack, 0)
|
|
|
|
if !h.active {
|
|
return &tcpip.ErrInvalidEndpointState{}
|
|
}
|
|
|
|
h.resetState()
|
|
synOpts := header.TCPSynOptions{
|
|
WS: h.rcvWndScale,
|
|
TS: h.ep.SendTSOk,
|
|
TSVal: h.ep.tsValNow(),
|
|
TSEcr: h.ep.recentTimestamp(),
|
|
SACKPermitted: h.ep.SACKPermitted,
|
|
MSS: h.ep.amss,
|
|
}
|
|
h.ep.sendSynTCP(h.ep.route, tcpFields{
|
|
id: h.ep.TransportEndpointInfo.ID,
|
|
ttl: calculateTTL(h.ep.route, h.ep.ipv4TTL, h.ep.ipv6HopLimit),
|
|
tos: h.ep.sendTOS,
|
|
flags: h.flags,
|
|
seq: h.iss,
|
|
ack: h.ackNum,
|
|
rcvWnd: h.rcvWnd,
|
|
expOptVal: h.ep.getExperimentOptionValue(h.ep.route),
|
|
}, synOpts)
|
|
return nil
|
|
}
|
|
|
|
// We have previously received (and acknowledged) the peer's SYN. If the
|
|
// peer acknowledges our SYN, the handshake is completed.
|
|
if s.flags.Contains(header.TCPFlagAck) {
|
|
// If deferAccept is not zero and this is a bare ACK and the
|
|
// timeout is not hit then drop the ACK.
|
|
if h.deferAccept != 0 && s.payloadSize() == 0 && h.ep.stack.Clock().NowMonotonic().Sub(h.startTime) < h.deferAccept {
|
|
h.acked = true
|
|
h.ep.stack.Stats().DroppedPackets.Increment()
|
|
return nil
|
|
}
|
|
|
|
// If the timestamp option is negotiated and the segment does
|
|
// not carry a timestamp option then the segment must be dropped
|
|
// as per https://tools.ietf.org/html/rfc7323#section-3.2.
|
|
if h.ep.SendTSOk && !s.parsedOptions.TS {
|
|
h.ep.stack.Stats().DroppedPackets.Increment()
|
|
return nil
|
|
}
|
|
|
|
// Drop the ACK if the accept queue is full.
|
|
// https://github.com/torvalds/linux/blob/7acac4b3196/net/ipv4/tcp_ipv4.c#L1523
|
|
// We could abort the connection as well with a tunable as in
|
|
// https://github.com/torvalds/linux/blob/7acac4b3196/net/ipv4/tcp_minisocks.c#L788
|
|
if listenEP := h.listenEP; listenEP != nil && listenEP.acceptQueueIsFull() {
|
|
listenEP.stack.Stats().DroppedPackets.Increment()
|
|
return nil
|
|
}
|
|
|
|
// Update timestamp if required. See RFC7323, section-4.3.
|
|
if h.ep.SendTSOk && s.parsedOptions.TS {
|
|
h.ep.updateRecentTimestamp(s.parsedOptions.TSVal, h.ackNum, s.sequenceNumber)
|
|
}
|
|
|
|
h.state = handshakeCompleted
|
|
h.transitionToStateEstablishedLocked(s)
|
|
|
|
// Requeue the segment if the ACK completing the handshake has more info
|
|
// to be processed by the newly established endpoint.
|
|
if (s.flags.Contains(header.TCPFlagFin) || s.payloadSize() > 0) && h.ep.enqueueSegment(s) {
|
|
h.ep.protocol.dispatcher.selectProcessor(h.ep.ID).queueEndpoint(h.ep)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// +checklocks:h.ep.mu
|
|
func (h *handshake) handleSegment(s *segment) tcpip.Error {
|
|
h.sndWnd = s.window
|
|
if !s.flags.Contains(header.TCPFlagSyn) && h.sndWndScale > 0 {
|
|
h.sndWnd <<= uint8(h.sndWndScale)
|
|
}
|
|
|
|
switch h.state {
|
|
case handshakeSynRcvd:
|
|
return h.synRcvdState(s)
|
|
case handshakeSynSent:
|
|
return h.synSentState(s)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// processSegments goes through the segment queue and processes up to
|
|
// maxSegmentsPerWake (if they're available).
|
|
// +checklocks:h.ep.mu
|
|
func (h *handshake) processSegments() tcpip.Error {
|
|
for i := 0; i < maxSegmentsPerWake; i++ {
|
|
s := h.ep.segmentQueue.dequeue()
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
err := h.handleSegment(s)
|
|
s.DecRef()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// We stop processing packets once the handshake is completed,
|
|
// otherwise we may process packets meant to be processed by
|
|
// the TCP processor goroutine.
|
|
if h.state == handshakeCompleted {
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// start sends the first SYN/SYN-ACK. It does not block, even if link address
|
|
// resolution is required.
|
|
func (h *handshake) start() {
|
|
h.startTime = h.ep.stack.Clock().NowMonotonic()
|
|
h.ep.amss = calculateAdvertisedMSS(h.ep.userMSS, h.ep.route)
|
|
var sackEnabled tcpip.TCPSACKEnabled
|
|
if err := h.ep.stack.TransportProtocolOption(ProtocolNumber, &sackEnabled); err != nil {
|
|
// If stack returned an error when checking for SACKEnabled
|
|
// status then just default to switching off SACK negotiation.
|
|
sackEnabled = false
|
|
}
|
|
|
|
synOpts := header.TCPSynOptions{
|
|
WS: h.rcvWndScale,
|
|
TS: true,
|
|
TSVal: h.ep.tsValNow(),
|
|
TSEcr: h.ep.recentTimestamp(),
|
|
SACKPermitted: bool(sackEnabled),
|
|
MSS: h.ep.amss,
|
|
}
|
|
|
|
// start() is also called in a listen context so we want to make sure we only
|
|
// send the TS/SACK option when we received the TS/SACK in the initial SYN.
|
|
if h.state == handshakeSynRcvd {
|
|
synOpts.TS = h.ep.SendTSOk
|
|
synOpts.SACKPermitted = h.ep.SACKPermitted && bool(sackEnabled)
|
|
if h.sndWndScale < 0 {
|
|
// Disable window scaling if the peer did not send us
|
|
// the window scaling option.
|
|
synOpts.WS = -1
|
|
}
|
|
}
|
|
|
|
h.sendSYNOpts = synOpts
|
|
h.ep.sendSynTCP(h.ep.route, tcpFields{
|
|
id: h.ep.TransportEndpointInfo.ID,
|
|
ttl: calculateTTL(h.ep.route, h.ep.ipv4TTL, h.ep.ipv6HopLimit),
|
|
tos: h.ep.sendTOS,
|
|
flags: h.flags,
|
|
seq: h.iss,
|
|
ack: h.ackNum,
|
|
rcvWnd: h.rcvWnd,
|
|
expOptVal: h.ep.getExperimentOptionValue(h.ep.route),
|
|
}, synOpts)
|
|
}
|
|
|
|
// retransmitHandler handles retransmissions of un-acked SYNs.
|
|
// +checklocks:h.ep.mu
|
|
func (h *handshake) retransmitHandlerLocked() tcpip.Error {
|
|
e := h.ep
|
|
// If the endpoint has already transition out of a connecting state due
|
|
// to say an error (e.g) peer send RST or an ICMP error. Then just
|
|
// return. Any required cleanup should have been done when the RST/error
|
|
// was handled.
|
|
if !e.EndpointState().connecting() {
|
|
return nil
|
|
}
|
|
|
|
if err := h.retransmitTimer.reset(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Resend the SYN/SYN-ACK only if the following conditions hold.
|
|
// - It's an active handshake (deferAccept does not apply)
|
|
// - It's a passive handshake and we have not yet got the final-ACK.
|
|
// - It's a passive handshake and we got an ACK but deferAccept is
|
|
// enabled and we are now past the deferAccept duration.
|
|
// The last is required to provide a way for the peer to complete
|
|
// the connection with another ACK or data (as ACKs are never
|
|
// retransmitted on their own).
|
|
if h.active || !h.acked || h.deferAccept != 0 && e.stack.Clock().NowMonotonic().Sub(h.startTime) > h.deferAccept {
|
|
e.sendSynTCP(e.route, tcpFields{
|
|
id: e.TransportEndpointInfo.ID,
|
|
ttl: calculateTTL(e.route, e.ipv4TTL, e.ipv6HopLimit),
|
|
tos: e.sendTOS,
|
|
flags: h.flags,
|
|
seq: h.iss,
|
|
ack: h.ackNum,
|
|
rcvWnd: h.rcvWnd,
|
|
expOptVal: e.getExperimentOptionValue(e.route),
|
|
}, h.sendSYNOpts)
|
|
// If we have ever retransmitted the SYN-ACK or
|
|
// SYN segment, we should only measure RTT if
|
|
// TS option is present.
|
|
h.sampleRTTWithTSOnly = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// transitionToStateEstablisedLocked transitions the endpoint of the handshake
|
|
// to an established state given the last segment received from peer. It also
|
|
// initializes sender/receiver.
|
|
// +checklocks:h.ep.mu
|
|
// +checklocksalias:h.ep.snd.ep.mu=h.ep.mu
|
|
func (h *handshake) transitionToStateEstablishedLocked(s *segment) {
|
|
// Stop the SYN retransmissions now that handshake is complete.
|
|
if h.retransmitTimer != nil {
|
|
h.retransmitTimer.stop()
|
|
}
|
|
|
|
// Transfer handshake state to TCP connection. We disable
|
|
// receive window scaling if the peer doesn't support it
|
|
// (indicated by a negative send window scale).
|
|
h.ep.snd = newSender(h.ep, h.iss, h.ackNum-1, h.sndWnd, h.mss, h.sndWndScale)
|
|
|
|
now := h.ep.stack.Clock().NowMonotonic()
|
|
|
|
var rtt time.Duration
|
|
if h.ep.SendTSOk && s.parsedOptions.TSEcr != 0 {
|
|
rtt = h.ep.elapsed(now, s.parsedOptions.TSEcr)
|
|
}
|
|
if !h.sampleRTTWithTSOnly && rtt == 0 {
|
|
rtt = now.Sub(h.startTime)
|
|
}
|
|
|
|
if rtt > 0 {
|
|
h.ep.snd.updateRTO(rtt)
|
|
}
|
|
|
|
h.ep.rcvQueueMu.Lock()
|
|
h.ep.rcv = newReceiver(h.ep, h.ackNum-1, h.rcvWnd, h.effectiveRcvWndScale())
|
|
// Bootstrap the auto tuning algorithm. Starting at zero will
|
|
// result in a really large receive window after the first auto
|
|
// tuning adjustment.
|
|
h.ep.RcvAutoParams.PrevCopiedBytes = int(h.rcvWnd)
|
|
h.ep.rcvQueueMu.Unlock()
|
|
|
|
h.ep.setEndpointState(StateEstablished)
|
|
|
|
// Completing the 3-way handshake is an indication that the route is valid
|
|
// and the remote is reachable as the only way we can complete a handshake
|
|
// is if our SYN reached the remote and their ACK reached us.
|
|
h.ep.route.ConfirmReachable()
|
|
|
|
// Tell waiters that the endpoint is connected and writable.
|
|
h.ep.waiterQueue.Notify(waiter.WritableEvents)
|
|
}
|
|
|
|
type backoffTimer struct {
|
|
timeout time.Duration
|
|
maxTimeout time.Duration
|
|
t tcpip.Timer
|
|
}
|
|
|
|
func newBackoffTimer(clock tcpip.Clock, timeout, maxTimeout time.Duration, f func()) (*backoffTimer, tcpip.Error) {
|
|
if timeout > maxTimeout {
|
|
return nil, &tcpip.ErrTimeout{}
|
|
}
|
|
bt := &backoffTimer{timeout: timeout, maxTimeout: maxTimeout}
|
|
bt.t = clock.AfterFunc(timeout, f)
|
|
return bt, nil
|
|
}
|
|
|
|
func (bt *backoffTimer) reset() tcpip.Error {
|
|
bt.timeout *= 2
|
|
if bt.timeout > bt.maxTimeout {
|
|
return &tcpip.ErrTimeout{}
|
|
}
|
|
bt.t.Reset(bt.timeout)
|
|
return nil
|
|
}
|
|
|
|
func (bt *backoffTimer) reinit(timeout time.Duration) {
|
|
bt.timeout = timeout
|
|
bt.t.Reset(bt.timeout)
|
|
}
|
|
|
|
func (bt *backoffTimer) stop() {
|
|
bt.t.Stop()
|
|
}
|
|
|
|
func parseSynSegmentOptions(s *segment) header.TCPSynOptions {
|
|
synOpts := header.ParseSynOptions(s.options, s.flags.Contains(header.TCPFlagAck))
|
|
if synOpts.TS {
|
|
s.parsedOptions.TSVal = synOpts.TSVal
|
|
s.parsedOptions.TSEcr = synOpts.TSEcr
|
|
}
|
|
return synOpts
|
|
}
|
|
|
|
var optionPool = sync.Pool{
|
|
New: func() any {
|
|
return &[maxOptionSize]byte{}
|
|
},
|
|
}
|
|
|
|
func getOptions() []byte {
|
|
return (*optionPool.Get().(*[maxOptionSize]byte))[:]
|
|
}
|
|
|
|
func putOptions(options []byte) {
|
|
// Reslice to full capacity.
|
|
optionPool.Put(optionsToArray(options))
|
|
}
|
|
|
|
func makeSynOptions(opts header.TCPSynOptions) []byte {
|
|
// Emulate linux option order. This is as follows:
|
|
//
|
|
// if md5: NOP NOP MD5SIG 18 md5sig(16)
|
|
// if mss: MSS 4 mss(2)
|
|
// if ts and sack_advertise:
|
|
// SACK 2 TIMESTAMP 2 timestamp(8)
|
|
// elif ts: NOP NOP TIMESTAMP 10 timestamp(8)
|
|
// elif sack: NOP NOP SACK 2
|
|
// if wscale: NOP WINDOW 3 ws(1)
|
|
// if sack_blocks: NOP NOP SACK ((2 + (#blocks * 8))
|
|
// [for each block] start_seq(4) end_seq(4)
|
|
// if fastopen_cookie:
|
|
// if exp: EXP (4 + len(cookie)) FASTOPEN_MAGIC(2)
|
|
// else: FASTOPEN (2 + len(cookie))
|
|
// cookie(variable) [padding to four bytes]
|
|
//
|
|
options := getOptions()
|
|
|
|
// Always encode the mss.
|
|
offset := header.EncodeMSSOption(uint32(opts.MSS), options)
|
|
|
|
// Special ordering is required here. If both TS and SACK are enabled,
|
|
// then the SACK option precedes TS, with no padding. If they are
|
|
// enabled individually, then we see padding before the option.
|
|
if opts.TS && opts.SACKPermitted {
|
|
offset += header.EncodeSACKPermittedOption(options[offset:])
|
|
offset += header.EncodeTSOption(opts.TSVal, opts.TSEcr, options[offset:])
|
|
} else if opts.TS {
|
|
offset += header.EncodeNOP(options[offset:])
|
|
offset += header.EncodeNOP(options[offset:])
|
|
offset += header.EncodeTSOption(opts.TSVal, opts.TSEcr, options[offset:])
|
|
} else if opts.SACKPermitted {
|
|
offset += header.EncodeNOP(options[offset:])
|
|
offset += header.EncodeNOP(options[offset:])
|
|
offset += header.EncodeSACKPermittedOption(options[offset:])
|
|
}
|
|
|
|
// Initialize the WS option.
|
|
if opts.WS >= 0 {
|
|
offset += header.EncodeNOP(options[offset:])
|
|
offset += header.EncodeWSOption(opts.WS, options[offset:])
|
|
}
|
|
|
|
// Padding to the end; note that this never apply unless we add a
|
|
// fastopen option, we always expect the offset to remain the same.
|
|
if delta := header.AddTCPOptionPadding(options, offset); delta != 0 {
|
|
panic("unexpected option encoding")
|
|
}
|
|
|
|
return options[:offset]
|
|
}
|
|
|
|
// tcpFields is a struct to carry different parameters required by the
|
|
// send*TCP variant functions below.
|
|
type tcpFields struct {
|
|
id stack.TransportEndpointID
|
|
ttl uint8
|
|
tos uint8
|
|
flags header.TCPFlags
|
|
seq seqnum.Value
|
|
ack seqnum.Value
|
|
rcvWnd seqnum.Size
|
|
opts []byte
|
|
txHash uint32
|
|
df bool
|
|
expOptVal uint16
|
|
}
|
|
|
|
func (e *Endpoint) sendSynTCP(r *stack.Route, tf tcpFields, opts header.TCPSynOptions) tcpip.Error {
|
|
tf.opts = makeSynOptions(opts)
|
|
// We ignore SYN send errors and let the callers re-attempt send.
|
|
hdrSize := header.TCPMinimumSize + int(r.MaxHeaderLength()) + len(tf.opts)
|
|
if r.NetProto() == header.IPv6ProtocolNumber && tf.expOptVal != 0 {
|
|
hdrSize += header.IPv6ExperimentHdrLength
|
|
}
|
|
p := stack.NewPacketBuffer(stack.PacketBufferOptions{ReserveHeaderBytes: hdrSize})
|
|
defer p.DecRef()
|
|
if err := e.sendTCP(r, tf, p, stack.GSO{}); err != nil {
|
|
e.stats.SendErrors.SynSendToNetworkFailed.Increment()
|
|
}
|
|
putOptions(tf.opts)
|
|
return nil
|
|
}
|
|
|
|
// This method takes ownership of pkt.
|
|
func (e *Endpoint) sendTCP(r *stack.Route, tf tcpFields, pkt *stack.PacketBuffer, gso stack.GSO) tcpip.Error {
|
|
tf.txHash = e.txHash
|
|
if err := sendTCP(r, tf, pkt, gso, e.owner); err != nil {
|
|
e.stats.SendErrors.SegmentSendToNetworkFailed.Increment()
|
|
return err
|
|
}
|
|
e.stats.SegmentsSent.Increment()
|
|
return nil
|
|
}
|
|
|
|
func buildTCPHdr(r *stack.Route, tf tcpFields, pkt *stack.PacketBuffer, gso stack.GSO) {
|
|
optLen := len(tf.opts)
|
|
tcp := header.TCP(pkt.TransportHeader().Push(header.TCPMinimumSize + optLen))
|
|
pkt.TransportProtocolNumber = header.TCPProtocolNumber
|
|
tcp.Encode(&header.TCPFields{
|
|
SrcPort: tf.id.LocalPort,
|
|
DstPort: tf.id.RemotePort,
|
|
SeqNum: uint32(tf.seq),
|
|
AckNum: uint32(tf.ack),
|
|
DataOffset: uint8(header.TCPMinimumSize + optLen),
|
|
Flags: tf.flags,
|
|
WindowSize: uint16(tf.rcvWnd),
|
|
})
|
|
copy(tcp[header.TCPMinimumSize:], tf.opts)
|
|
|
|
xsum := r.PseudoHeaderChecksum(ProtocolNumber, uint16(pkt.Size()))
|
|
// Only calculate the checksum if offloading isn't supported.
|
|
if gso.Type != stack.GSONone && gso.NeedsCsum {
|
|
// This is called CHECKSUM_PARTIAL in the Linux kernel. We
|
|
// calculate a checksum of the pseudo-header and save it in the
|
|
// TCP header, then the kernel calculate a checksum of the
|
|
// header and data and get the right sum of the TCP packet.
|
|
tcp.SetChecksum(xsum)
|
|
} else if r.RequiresTXTransportChecksum() {
|
|
xsum = checksum.Combine(xsum, pkt.Data().Checksum())
|
|
tcp.SetChecksum(^tcp.CalculateChecksum(xsum))
|
|
}
|
|
}
|
|
|
|
func sendTCPBatch(r *stack.Route, tf tcpFields, pkt *stack.PacketBuffer, gso stack.GSO, owner tcpip.PacketOwner) tcpip.Error {
|
|
optLen := len(tf.opts)
|
|
if tf.rcvWnd > math.MaxUint16 {
|
|
tf.rcvWnd = math.MaxUint16
|
|
}
|
|
|
|
mss := int(gso.MSS)
|
|
n := (pkt.Data().Size() + mss - 1) / mss
|
|
|
|
size := pkt.Data().Size()
|
|
hdrSize := header.TCPMinimumSize + int(r.MaxHeaderLength()) + optLen
|
|
for i := 0; i < n; i++ {
|
|
packetSize := mss
|
|
if packetSize > size {
|
|
packetSize = size
|
|
}
|
|
size -= packetSize
|
|
|
|
pkt := pkt
|
|
// No need to split the packet in the final iteration. The original
|
|
// packet already has the truncated data.
|
|
shouldSplitPacket := i != n-1
|
|
if shouldSplitPacket {
|
|
if r.NetProto() == header.IPv6ProtocolNumber && tf.expOptVal != 0 {
|
|
// Reserve extra bytes for the experiment option.
|
|
hdrSize += header.IPv6ExperimentHdrLength
|
|
}
|
|
splitPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ReserveHeaderBytes: hdrSize})
|
|
splitPkt.Data().ReadFromPacketData(pkt.Data(), packetSize)
|
|
pkt = splitPkt
|
|
}
|
|
pkt.Hash = tf.txHash
|
|
pkt.Owner = owner
|
|
|
|
buildTCPHdr(r, tf, pkt, gso)
|
|
tf.seq = tf.seq.Add(seqnum.Size(packetSize))
|
|
pkt.GSOOptions = gso
|
|
if err := r.WritePacket(stack.NetworkHeaderParams{
|
|
Protocol: ProtocolNumber,
|
|
TTL: tf.ttl,
|
|
TOS: tf.tos,
|
|
DF: tf.df,
|
|
ExperimentOptionValue: tf.expOptVal,
|
|
}, pkt); err != nil {
|
|
r.Stats().TCP.SegmentSendErrors.Increment()
|
|
if shouldSplitPacket {
|
|
pkt.DecRef()
|
|
}
|
|
return err
|
|
}
|
|
r.Stats().TCP.SegmentsSent.Increment()
|
|
if shouldSplitPacket {
|
|
pkt.DecRef()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// sendTCP sends a TCP segment with the provided options via the provided
|
|
// network endpoint and under the provided identity. This method takes
|
|
// ownership of pkt.
|
|
func sendTCP(r *stack.Route, tf tcpFields, pkt *stack.PacketBuffer, gso stack.GSO, owner tcpip.PacketOwner) tcpip.Error {
|
|
if tf.rcvWnd > math.MaxUint16 {
|
|
tf.rcvWnd = math.MaxUint16
|
|
}
|
|
|
|
if r.Loop()&stack.PacketLoop == 0 && gso.Type == stack.GSOGvisor && int(gso.MSS) < pkt.Data().Size() {
|
|
return sendTCPBatch(r, tf, pkt, gso, owner)
|
|
}
|
|
|
|
pkt.GSOOptions = gso
|
|
pkt.Hash = tf.txHash
|
|
pkt.Owner = owner
|
|
buildTCPHdr(r, tf, pkt, gso)
|
|
|
|
if err := r.WritePacket(stack.NetworkHeaderParams{
|
|
Protocol: ProtocolNumber,
|
|
TTL: tf.ttl,
|
|
TOS: tf.tos,
|
|
DF: tf.df,
|
|
ExperimentOptionValue: tf.expOptVal,
|
|
}, pkt); err != nil {
|
|
r.Stats().TCP.SegmentSendErrors.Increment()
|
|
return err
|
|
}
|
|
r.Stats().TCP.SegmentsSent.Increment()
|
|
if (tf.flags & header.TCPFlagRst) != 0 {
|
|
r.Stats().TCP.ResetsSent.Increment()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// makeOptions makes an options slice.
|
|
func (e *Endpoint) makeOptions(sackBlocks []header.SACKBlock) []byte {
|
|
options := getOptions()
|
|
offset := 0
|
|
|
|
// N.B. the ordering here matches the ordering used by Linux internally
|
|
// and described in the raw makeOptions function. We don't include
|
|
// unnecessary cases here (post connection.)
|
|
if e.SendTSOk {
|
|
// Embed the timestamp if timestamp has been enabled.
|
|
//
|
|
// We only use the lower 32 bits of the unix time in
|
|
// milliseconds. This is similar to what Linux does where it
|
|
// uses the lower 32 bits of the jiffies value in the tsVal
|
|
// field of the timestamp option.
|
|
//
|
|
// Further, RFC7323 section-5.4 recommends millisecond
|
|
// resolution as the lowest recommended resolution for the
|
|
// timestamp clock.
|
|
//
|
|
// Ref: https://tools.ietf.org/html/rfc7323#section-5.4.
|
|
offset += header.EncodeNOP(options[offset:])
|
|
offset += header.EncodeNOP(options[offset:])
|
|
offset += header.EncodeTSOption(e.tsValNow(), e.recentTimestamp(), options[offset:])
|
|
}
|
|
if e.SACKPermitted && len(sackBlocks) > 0 {
|
|
offset += header.EncodeNOP(options[offset:])
|
|
offset += header.EncodeNOP(options[offset:])
|
|
offset += header.EncodeSACKBlocks(sackBlocks, options[offset:])
|
|
}
|
|
|
|
// We expect the above to produce an aligned offset.
|
|
if delta := header.AddTCPOptionPadding(options, offset); delta != 0 {
|
|
panic("unexpected option encoding")
|
|
}
|
|
|
|
return options[:offset]
|
|
}
|
|
|
|
// sendEmptyRaw sends a TCP segment with no payload to the endpoint's peer.
|
|
//
|
|
// +checklocks:e.mu
|
|
// +checklocksalias:e.snd.ep.mu=e.mu
|
|
func (e *Endpoint) sendEmptyRaw(flags header.TCPFlags, seq, ack seqnum.Value, rcvWnd seqnum.Size) tcpip.Error {
|
|
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{})
|
|
defer pkt.DecRef()
|
|
return e.sendRaw(pkt, flags, seq, ack, rcvWnd)
|
|
}
|
|
|
|
// sendRaw sends a TCP segment to the endpoint's peer. This method takes
|
|
// ownership of pkt. pkt must not have any headers set.
|
|
//
|
|
// +checklocks:e.mu
|
|
// +checklocksalias:e.snd.ep.mu=e.mu
|
|
func (e *Endpoint) sendRaw(pkt *stack.PacketBuffer, flags header.TCPFlags, seq, ack seqnum.Value, rcvWnd seqnum.Size) tcpip.Error {
|
|
var sackBlocks []header.SACKBlock
|
|
if e.EndpointState() == StateEstablished && e.rcv.pendingRcvdSegments.Len() > 0 && (flags&header.TCPFlagAck != 0) {
|
|
sackBlocks = e.sack.Blocks[:e.sack.NumBlocks]
|
|
}
|
|
options := e.makeOptions(sackBlocks)
|
|
defer putOptions(options)
|
|
hdrSize := header.TCPMinimumSize + int(e.route.MaxHeaderLength()) + len(options)
|
|
expOptVal := e.getExperimentOptionValue(e.route)
|
|
if e.route.NetProto() == header.IPv6ProtocolNumber && expOptVal != 0 {
|
|
hdrSize += header.IPv6ExperimentHdrLength
|
|
}
|
|
pkt.ReserveHeaderBytes(hdrSize)
|
|
return e.sendTCP(e.route, tcpFields{
|
|
id: e.TransportEndpointInfo.ID,
|
|
ttl: calculateTTL(e.route, e.ipv4TTL, e.ipv6HopLimit),
|
|
tos: e.sendTOS,
|
|
flags: flags,
|
|
seq: seq,
|
|
ack: ack,
|
|
rcvWnd: rcvWnd,
|
|
opts: options,
|
|
df: e.pmtud == tcpip.PMTUDiscoveryWant || e.pmtud == tcpip.PMTUDiscoveryDo,
|
|
expOptVal: expOptVal,
|
|
}, pkt, e.gso)
|
|
}
|
|
|
|
// +checklocks:e.mu
|
|
// +checklocksalias:e.snd.ep.mu=e.mu
|
|
func (e *Endpoint) sendData(next *segment) {
|
|
// Initialize the next segment to write if it's currently nil.
|
|
if e.snd.writeNext == nil {
|
|
if next == nil {
|
|
return
|
|
}
|
|
e.snd.updateWriteNext(next)
|
|
}
|
|
|
|
// Push out any new packets.
|
|
e.snd.sendData()
|
|
}
|
|
|
|
// resetConnectionLocked puts the endpoint in an error state with the given
|
|
// error code and sends a RST if and only if the error is not ErrConnectionReset
|
|
// indicating that the connection is being reset due to receiving a RST.
|
|
// +checklocks:e.mu
|
|
// +checklocksalias:e.snd.ep.mu=e.mu
|
|
func (e *Endpoint) resetConnectionLocked(err tcpip.Error) {
|
|
// Only send a reset if the connection is being aborted for a reason
|
|
// other than receiving a reset.
|
|
e.hardError = err
|
|
switch err.(type) {
|
|
case *tcpip.ErrConnectionReset, *tcpip.ErrTimeout:
|
|
default:
|
|
// The exact sequence number to be used for the RST is the same as the
|
|
// one used by Linux. We need to handle the case of window being shrunk
|
|
// which can cause sndNxt to be outside the acceptable window on the
|
|
// receiver.
|
|
//
|
|
// See: https://www.snellman.net/blog/archive/2016-02-01-tcp-rst/ for more
|
|
// information.
|
|
sndWndEnd := e.snd.SndUna.Add(e.snd.SndWnd)
|
|
resetSeqNum := sndWndEnd
|
|
if !sndWndEnd.LessThan(e.snd.SndNxt) || e.snd.SndNxt.Size(sndWndEnd) < (1<<e.snd.SndWndScale) {
|
|
resetSeqNum = e.snd.SndNxt
|
|
}
|
|
e.sendEmptyRaw(header.TCPFlagAck|header.TCPFlagRst, resetSeqNum, e.rcv.RcvNxt, 0)
|
|
}
|
|
// Don't purge read queues here. If there's buffered data, it's still allowed
|
|
// to be read.
|
|
e.purgeWriteQueue()
|
|
e.purgePendingRcvQueue()
|
|
e.cleanupLocked()
|
|
e.setEndpointState(StateError)
|
|
}
|
|
|
|
// transitionToStateCloseLocked ensures that the endpoint is
|
|
// cleaned up from the transport demuxer, "before" moving to
|
|
// StateClose. This will ensure that no packet will be
|
|
// delivered to this endpoint from the demuxer when the endpoint
|
|
// is transitioned to StateClose.
|
|
// +checklocks:e.mu
|
|
func (e *Endpoint) transitionToStateCloseLocked() {
|
|
s := e.EndpointState()
|
|
if s == StateClose {
|
|
return
|
|
}
|
|
|
|
if s.connected() {
|
|
e.stack.Stats().TCP.EstablishedClosed.Increment()
|
|
}
|
|
|
|
e.cleanupLocked()
|
|
// Mark the endpoint as fully closed for reads/writes.
|
|
e.setEndpointState(StateClose)
|
|
}
|
|
|
|
// tryDeliverSegmentFromClosedEndpoint attempts to deliver the parsed
|
|
// segment to any other endpoint other than the current one. This is called
|
|
// only when the endpoint is in StateClose and we want to deliver the segment
|
|
// to any other listening endpoint. We reply with RST if we cannot find one.
|
|
func (e *Endpoint) tryDeliverSegmentFromClosedEndpoint(s *segment) {
|
|
ep := e.stack.FindTransportEndpoint(e.NetProto, e.TransProto, e.TransportEndpointInfo.ID, s.pkt.NICID)
|
|
if ep == nil && e.NetProto == header.IPv6ProtocolNumber && e.TransportEndpointInfo.ID.LocalAddress.To4() != (tcpip.Address{}) {
|
|
// Dual-stack socket, try IPv4.
|
|
ep = e.stack.FindTransportEndpoint(
|
|
header.IPv4ProtocolNumber,
|
|
e.TransProto,
|
|
e.TransportEndpointInfo.ID,
|
|
s.pkt.NICID,
|
|
)
|
|
}
|
|
if ep == nil {
|
|
if !s.flags.Contains(header.TCPFlagRst) {
|
|
replyWithReset(e.stack, s, stack.DefaultTOS, tcpip.UseDefaultIPv4TTL, tcpip.UseDefaultIPv6HopLimit)
|
|
}
|
|
return
|
|
}
|
|
|
|
if e == ep {
|
|
panic(fmt.Sprintf("current endpoint not removed from demuxer, enqueuing segments to itself, endpoint in state %v", e.EndpointState()))
|
|
}
|
|
|
|
if ep := ep.(*Endpoint); ep.enqueueSegment(s) {
|
|
ep.notifyProcessor()
|
|
}
|
|
}
|
|
|
|
// Drain segment queue from the endpoint and try to re-match the segment to a
|
|
// different endpoint. This is used when the current endpoint is transitioned to
|
|
// StateClose and has been unregistered from the transport demuxer.
|
|
func (e *Endpoint) drainClosingSegmentQueue() {
|
|
for {
|
|
s := e.segmentQueue.dequeue()
|
|
if s == nil {
|
|
break
|
|
}
|
|
|
|
e.tryDeliverSegmentFromClosedEndpoint(s)
|
|
s.DecRef()
|
|
}
|
|
}
|
|
|
|
// +checklocks:e.mu
|
|
func (e *Endpoint) handleReset(s *segment) (ok bool, err tcpip.Error) {
|
|
if e.rcv.acceptable(s.sequenceNumber, 0) {
|
|
// RFC 793, page 37 states that "in all states
|
|
// except SYN-SENT, all reset (RST) segments are
|
|
// validated by checking their SEQ-fields." So
|
|
// we only process it if it's acceptable.
|
|
switch e.EndpointState() {
|
|
// In case of a RST in CLOSE-WAIT linux moves
|
|
// the socket to closed state with an error set
|
|
// to indicate EPIPE.
|
|
//
|
|
// Technically this seems to be at odds w/ RFC.
|
|
// As per https://tools.ietf.org/html/rfc793#section-2.7
|
|
// page 69 the behavior for a segment arriving
|
|
// w/ RST bit set in CLOSE-WAIT is inlined below.
|
|
//
|
|
// ESTABLISHED
|
|
// FIN-WAIT-1
|
|
// FIN-WAIT-2
|
|
// CLOSE-WAIT
|
|
|
|
// If the RST bit is set then, any outstanding RECEIVEs and
|
|
// SEND should receive "reset" responses. All segment queues
|
|
// should be flushed. Users should also receive an unsolicited
|
|
// general "connection reset" signal. Enter the CLOSED state,
|
|
// delete the TCB, and return.
|
|
case StateCloseWait:
|
|
e.transitionToStateCloseLocked()
|
|
e.hardError = &tcpip.ErrAborted{}
|
|
return false, nil
|
|
default:
|
|
// RFC 793, page 37 states that "in all states
|
|
// except SYN-SENT, all reset (RST) segments are
|
|
// validated by checking their SEQ-fields." So
|
|
// we only process it if it's acceptable.
|
|
return false, &tcpip.ErrConnectionReset{}
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// handleSegments processes all inbound segments.
|
|
//
|
|
// +checklocks:e.mu
|
|
// +checklocksalias:e.snd.ep.mu=e.mu
|
|
func (e *Endpoint) handleSegmentsLocked() tcpip.Error {
|
|
sndUna := e.snd.SndUna
|
|
for i := 0; i < maxSegmentsPerWake; i++ {
|
|
if state := e.EndpointState(); state.closed() || state == StateTimeWait || state == StateError {
|
|
return nil
|
|
}
|
|
s := e.segmentQueue.dequeue()
|
|
if s == nil {
|
|
break
|
|
}
|
|
cont, err := e.handleSegmentLocked(s)
|
|
s.DecRef()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !cont {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// The remote ACK-ing at least 1 byte is an indication that we have a
|
|
// full-duplex connection to the remote as the only way we will receive an
|
|
// ACK is if the remote received data that we previously sent.
|
|
//
|
|
// As of writing, Linux seems to only confirm a route as reachable when
|
|
// forward progress is made which is indicated by an ACK that removes data
|
|
// from the retransmit queue, i.e. sender makes forward progress.
|
|
if sndUna.LessThan(e.snd.SndUna) {
|
|
e.route.ConfirmReachable()
|
|
}
|
|
|
|
// Send an ACK for all processed packets if needed.
|
|
if e.rcv.RcvNxt != e.snd.MaxSentAck {
|
|
e.snd.sendAck()
|
|
}
|
|
|
|
e.resetKeepaliveTimer(true /* receivedData */)
|
|
|
|
return nil
|
|
}
|
|
|
|
// +checklocks:e.mu
|
|
func (e *Endpoint) probeSegmentLocked() {
|
|
if fn := e.probe; fn != nil {
|
|
var state TCPEndpointState
|
|
e.completeStateLocked(&state)
|
|
fn(&state)
|
|
}
|
|
}
|
|
|
|
// handleSegment handles a given segment and notifies the worker goroutine if
|
|
// if the connection should be terminated.
|
|
//
|
|
// +checklocks:e.mu
|
|
// +checklocksalias:e.rcv.ep.mu=e.mu
|
|
// +checklocksalias:e.snd.ep.mu=e.mu
|
|
func (e *Endpoint) handleSegmentLocked(s *segment) (cont bool, err tcpip.Error) {
|
|
// Invoke the tcp probe if installed. The tcp probe function will update
|
|
// the TCPEndpointState after the segment is processed.
|
|
defer e.probeSegmentLocked()
|
|
|
|
if s.flags.Contains(header.TCPFlagRst) {
|
|
if ok, err := e.handleReset(s); !ok {
|
|
return false, err
|
|
}
|
|
} else if s.flags.Contains(header.TCPFlagSyn) {
|
|
// See: https://tools.ietf.org/html/rfc5961#section-4.1
|
|
// 1) If the SYN bit is set, irrespective of the sequence number, TCP
|
|
// MUST send an ACK (also referred to as challenge ACK) to the remote
|
|
// peer:
|
|
//
|
|
// <SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>
|
|
//
|
|
// After sending the acknowledgment, TCP MUST drop the unacceptable
|
|
// segment and stop processing further.
|
|
//
|
|
// By sending an ACK, the remote peer is challenged to confirm the loss
|
|
// of the previous connection and the request to start a new connection.
|
|
// A legitimate peer, after restart, would not have a TCB in the
|
|
// synchronized state. Thus, when the ACK arrives, the peer should send
|
|
// a RST segment back with the sequence number derived from the ACK
|
|
// field that caused the RST.
|
|
|
|
// This RST will confirm that the remote peer has indeed closed the
|
|
// previous connection. Upon receipt of a valid RST, the local TCP
|
|
// endpoint MUST terminate its connection. The local TCP endpoint
|
|
// should then rely on SYN retransmission from the remote end to
|
|
// re-establish the connection.
|
|
e.snd.maybeSendOutOfWindowAck(s)
|
|
} else if s.flags.Contains(header.TCPFlagAck) {
|
|
// Patch the window size in the segment according to the
|
|
// send window scale.
|
|
s.window <<= e.snd.SndWndScale
|
|
|
|
// RFC 793, page 41 states that "once in the ESTABLISHED
|
|
// state all segments must carry current acknowledgment
|
|
// information."
|
|
drop, err := e.rcv.handleRcvdSegment(s)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if drop {
|
|
return true, nil
|
|
}
|
|
|
|
// Now check if the received segment has caused us to transition
|
|
// to a CLOSED state, if yes then terminate processing and do
|
|
// not invoke the sender.
|
|
// It is also possible that the sender has sent a RST before
|
|
// which got lost and didn't reach the other side. At that time,
|
|
// we can still receive ACKs after the sender has purged the
|
|
// write list. Do not process such ACKs and return immediately.
|
|
state := e.EndpointState()
|
|
if state == StateClose || state == StateError {
|
|
// When we get into StateClose while processing from the queue,
|
|
// return immediately and let the TCP processors handle it.
|
|
return false, nil
|
|
}
|
|
|
|
e.snd.handleRcvdSegment(s)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// keepaliveTimerExpired is called when the keepaliveTimer fires. We send TCP
|
|
// keepalive packets periodically when the connection is idle. If we don't hear
|
|
// from the other side after a number of tries, we terminate the connection.
|
|
// +checklocks:e.mu
|
|
// +checklocksalias:e.snd.ep.mu=e.mu
|
|
func (e *Endpoint) keepaliveTimerExpired() tcpip.Error {
|
|
userTimeout := e.userTimeout
|
|
|
|
// If the route is not ready or already cleaned up, then we don't need to
|
|
// send keepalives.
|
|
if e.route == nil {
|
|
return nil
|
|
}
|
|
e.keepalive.Lock()
|
|
if !e.SocketOptions().GetKeepAlive() || e.keepalive.timer.isUninitialized() || !e.keepalive.timer.checkExpiration() {
|
|
e.keepalive.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// If a userTimeout is set then abort the connection if it is
|
|
// exceeded.
|
|
if userTimeout != 0 && e.stack.Clock().NowMonotonic().Sub(e.rcv.lastRcvdAckTime) >= userTimeout && e.keepalive.unacked > 0 {
|
|
e.keepalive.Unlock()
|
|
e.stack.Stats().TCP.EstablishedTimedout.Increment()
|
|
return &tcpip.ErrTimeout{}
|
|
}
|
|
|
|
if e.keepalive.unacked >= e.keepalive.count {
|
|
e.keepalive.Unlock()
|
|
e.stack.Stats().TCP.EstablishedTimedout.Increment()
|
|
return &tcpip.ErrTimeout{}
|
|
}
|
|
|
|
// RFC1122 4.2.3.6: TCP keepalive is a dataless ACK with
|
|
// seg.seq = snd.nxt-1.
|
|
e.keepalive.unacked++
|
|
e.keepalive.Unlock()
|
|
e.snd.sendEmptySegment(header.TCPFlagAck, e.snd.SndNxt-1)
|
|
e.resetKeepaliveTimer(false)
|
|
return nil
|
|
}
|
|
|
|
// resetKeepaliveTimer restarts or stops the keepalive timer, depending on
|
|
// whether it is enabled for this endpoint.
|
|
//
|
|
// +checklocks:e.mu
|
|
// +checklocksalias:e.snd.ep.mu=e.mu
|
|
func (e *Endpoint) resetKeepaliveTimer(receivedData bool) {
|
|
e.keepalive.Lock()
|
|
defer e.keepalive.Unlock()
|
|
if e.keepalive.timer.isUninitialized() {
|
|
if state := e.EndpointState(); !state.closed() {
|
|
panic(fmt.Sprintf("Unexpected state when the keepalive time is cleaned up, got %s, want %s or %s", state, StateClose, StateError))
|
|
}
|
|
return
|
|
}
|
|
if receivedData {
|
|
e.keepalive.unacked = 0
|
|
}
|
|
// Start the keepalive timer IFF it's enabled and there is no pending
|
|
// data to send.
|
|
if !e.SocketOptions().GetKeepAlive() || e.snd == nil || e.snd.SndUna != e.snd.SndNxt {
|
|
e.keepalive.timer.disable()
|
|
return
|
|
}
|
|
if e.keepalive.unacked > 0 {
|
|
e.keepalive.timer.enable(e.keepalive.interval)
|
|
} else {
|
|
e.keepalive.timer.enable(e.keepalive.idle)
|
|
}
|
|
}
|
|
|
|
// disableKeepaliveTimer stops the keepalive timer.
|
|
func (e *Endpoint) disableKeepaliveTimer() {
|
|
e.keepalive.Lock()
|
|
e.keepalive.timer.disable()
|
|
e.keepalive.Unlock()
|
|
}
|
|
|
|
// finWait2TimerExpired is called when the FIN-WAIT-2 timeout is hit
|
|
// and the peer hasn't sent us a FIN.
|
|
func (e *Endpoint) finWait2TimerExpired() {
|
|
e.mu.Lock()
|
|
e.transitionToStateCloseLocked()
|
|
e.mu.Unlock()
|
|
e.drainClosingSegmentQueue()
|
|
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.ReadableEvents | waiter.WritableEvents)
|
|
}
|
|
|
|
// +checklocks:e.mu
|
|
func (e *Endpoint) handshakeFailed(err tcpip.Error) {
|
|
e.lastErrorMu.Lock()
|
|
e.lastError = err
|
|
e.lastErrorMu.Unlock()
|
|
// handshakeFailed is also called from startHandshake when a listener
|
|
// transitions out of Listen state by the time the SYN is processed. In
|
|
// such cases the handshake is never initialized and the newly created
|
|
// endpoint is closed right away.
|
|
if e.h != nil && e.h.retransmitTimer != nil {
|
|
e.h.retransmitTimer.stop()
|
|
}
|
|
e.hardError = err
|
|
e.cleanupLocked()
|
|
e.setEndpointState(StateError)
|
|
}
|
|
|
|
// handleTimeWaitSegments processes segments received during TIME_WAIT
|
|
// state.
|
|
// +checklocks:e.mu
|
|
// +checklocksalias:e.rcv.ep.mu=e.mu
|
|
func (e *Endpoint) handleTimeWaitSegments() (extendTimeWait bool, reuseTW func()) {
|
|
for i := 0; i < maxSegmentsPerWake; i++ {
|
|
s := e.segmentQueue.dequeue()
|
|
if s == nil {
|
|
break
|
|
}
|
|
extTW, newSyn := e.rcv.handleTimeWaitSegment(s)
|
|
if newSyn {
|
|
info := e.TransportEndpointInfo
|
|
newID := info.ID
|
|
newID.RemoteAddress = tcpip.Address{}
|
|
newID.RemotePort = 0
|
|
netProtos := []tcpip.NetworkProtocolNumber{info.NetProto}
|
|
// If the local address is an IPv4 address then also
|
|
// look for IPv6 dual stack endpoints that might be
|
|
// listening on the local address.
|
|
if newID.LocalAddress.To4() != (tcpip.Address{}) {
|
|
netProtos = []tcpip.NetworkProtocolNumber{header.IPv4ProtocolNumber, header.IPv6ProtocolNumber}
|
|
}
|
|
for _, netProto := range netProtos {
|
|
if listenEP := e.stack.FindTransportEndpoint(netProto, info.TransProto, newID, s.pkt.NICID); listenEP != nil {
|
|
tcpEP := listenEP.(*Endpoint)
|
|
if EndpointState(tcpEP.State()) == StateListen {
|
|
reuseTW = func() {
|
|
if !tcpEP.enqueueSegment(s) {
|
|
return
|
|
}
|
|
tcpEP.notifyProcessor()
|
|
s.DecRef()
|
|
}
|
|
// We explicitly do not DecRef the segment as it's still valid and
|
|
// being reflected to a listening endpoint.
|
|
return false, reuseTW
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if extTW {
|
|
extendTimeWait = true
|
|
}
|
|
s.DecRef()
|
|
}
|
|
return extendTimeWait, nil
|
|
}
|
|
|
|
// +checklocks:e.mu
|
|
func (e *Endpoint) getTimeWaitDuration() time.Duration {
|
|
timeWaitDuration := DefaultTCPTimeWaitTimeout
|
|
|
|
// Get the stack wide configuration.
|
|
var tcpTW tcpip.TCPTimeWaitTimeoutOption
|
|
if err := e.stack.TransportProtocolOption(ProtocolNumber, &tcpTW); err == nil {
|
|
timeWaitDuration = time.Duration(tcpTW)
|
|
}
|
|
return timeWaitDuration
|
|
}
|
|
|
|
// timeWaitTimerExpired is called when an endpoint completes the required time
|
|
// (typically 2 * MSL unless configured to something else at a stack level) in
|
|
// TIME-WAIT state.
|
|
func (e *Endpoint) timeWaitTimerExpired() {
|
|
e.mu.Lock()
|
|
if e.EndpointState() != StateTimeWait {
|
|
e.mu.Unlock()
|
|
return
|
|
}
|
|
e.transitionToStateCloseLocked()
|
|
e.mu.Unlock()
|
|
e.drainClosingSegmentQueue()
|
|
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.ReadableEvents | waiter.WritableEvents)
|
|
}
|
|
|
|
// notifyProcessor queues this endpoint for processing to its TCP processor.
|
|
func (e *Endpoint) notifyProcessor() {
|
|
// We use TryLock here to avoid deadlocks in cases where a listening endpoint that is being
|
|
// closed tries to abort half completed connections which in turn try to queue any segments
|
|
// queued to that endpoint back to the same listening endpoint (because it may have got
|
|
// segments that matched its id but were either a RST or a new SYN which must be handled
|
|
// by a listening endpoint). In such cases the Close() on the listening endpoint will handle
|
|
// any queued segments after it releases the lock.
|
|
if !e.mu.TryLock() {
|
|
return
|
|
}
|
|
processor := e.protocol.dispatcher.selectProcessor(e.ID)
|
|
e.mu.Unlock()
|
|
processor.queueEndpoint(e)
|
|
}
|