mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add support for TIME_WAIT timeout.
This change adds explicit support for honoring the 2MSL timeout for sockets in TIME_WAIT state. It also adds support for the TCP_LINGER2 option that allows modification of the FIN_WAIT2 state timeout duration for a given socket. It also adds an option to modify the Stack wide TIME_WAIT timeout but this is only for testing. On Linux this is fixed at 60s. Further, we also now correctly process RST's in CLOSE_WAIT and close the socket similar to linux without moving it to error state. We also now handle SYN in ESTABLISHED state as per RFC5961#section-4.1. Earlier we would just drop these SYNs. Which can result in some tests that pass on linux to fail on gVisor. Netstack now honors TIME_WAIT correctly as well as handles the following cases correctly. - TCP RSTs in TIME_WAIT are ignored. - A duplicate TCP FIN during TIME_WAIT extends the TIME_WAIT and a dup ACK is sent in response to the FIN as the dup FIN indicates potential loss of the original final ACK. - An out of order segment during TIME_WAIT generates a dup ACK. - A new SYN w/ a sequence number > the highest sequence number in the previous connection closes the TIME_WAIT early and opens a new connection. Further to make the SYN case work correctly the ISN (Initial Sequence Number) generation for Netstack has been updated to be as per RFC. Its not a pure random number anymore and follows the recommendation in https://tools.ietf.org/html/rfc6528#page-3. The current hash used is not a cryptographically secure hash function. A separate change will update the hash function used to Siphash similar to what is used in Linux. PiperOrigin-RevId: 279106406
This commit is contained in:
committed by
gVisor bot
parent
2326224a96
commit
66ebb6575f
@@ -1173,6 +1173,18 @@ func getSockOptTCP(t *kernel.Task, ep commonEndpoint, name, outLen int) (interfa
|
||||
copy(b, v)
|
||||
return b, nil
|
||||
|
||||
case linux.TCP_LINGER2:
|
||||
if outLen < sizeOfInt32 {
|
||||
return nil, syserr.ErrInvalidArgument
|
||||
}
|
||||
|
||||
var v tcpip.TCPLingerTimeoutOption
|
||||
if err := ep.GetSockOpt(&v); err != nil {
|
||||
return nil, syserr.TranslateNetstackError(err)
|
||||
}
|
||||
|
||||
return int32(time.Duration(v) / time.Second), nil
|
||||
|
||||
default:
|
||||
emitUnimplementedEventTCP(t, name)
|
||||
}
|
||||
@@ -1556,6 +1568,14 @@ func setSockOptTCP(t *kernel.Task, ep commonEndpoint, name int, optVal []byte) *
|
||||
}
|
||||
return nil
|
||||
|
||||
case linux.TCP_LINGER2:
|
||||
if len(optVal) < sizeOfInt32 {
|
||||
return syserr.ErrInvalidArgument
|
||||
}
|
||||
|
||||
v := usermem.ByteOrder.Uint32(optVal)
|
||||
return syserr.TranslateNetstackError(ep.SetSockOpt(tcpip.TCPLingerTimeoutOption(time.Second * time.Duration(v))))
|
||||
|
||||
case linux.TCP_REPAIR_OPTIONS:
|
||||
t.Kernel().EmitUnimplementedEvent(t)
|
||||
|
||||
|
||||
@@ -151,10 +151,8 @@ func TestCloseReader(t *testing.T) {
|
||||
|
||||
buf := make([]byte, 256)
|
||||
n, err := c.Read(buf)
|
||||
got, ok := err.(*net.OpError)
|
||||
want := tcpip.ErrConnectionAborted
|
||||
if n != 0 || !ok || got.Err.Error() != want.String() {
|
||||
t.Errorf("c.Read() = (%d, %v), want (0, OpError(%v))", n, err, want)
|
||||
if n != 0 || err != io.EOF {
|
||||
t.Errorf("c.Read() = (%d, %v), want (0, EOF)", n, err)
|
||||
}
|
||||
}()
|
||||
sender, err := connect(s, addr)
|
||||
@@ -203,10 +201,8 @@ func TestCloseReaderWithForwarder(t *testing.T) {
|
||||
|
||||
buf := make([]byte, 256)
|
||||
n, e := c.Read(buf)
|
||||
got, ok := e.(*net.OpError)
|
||||
want := tcpip.ErrConnectionAborted
|
||||
if n != 0 || !ok || got.Err.Error() != want.String() {
|
||||
t.Errorf("c.Read() = (%d, %v), want (0, OpError(%v))", n, e, want)
|
||||
if n != 0 || e != io.EOF {
|
||||
t.Errorf("c.Read() = (%d, %v), want (0, EOF)", n, e)
|
||||
}
|
||||
})
|
||||
s.SetTransportProtocolHandler(tcp.ProtocolNumber, fwd.HandlePacket)
|
||||
|
||||
@@ -402,11 +402,11 @@ type Stack struct {
|
||||
// by the stack.
|
||||
icmpRateLimiter *ICMPRateLimiter
|
||||
|
||||
// portSeed is a one-time random value initialized at stack startup
|
||||
// seed is a one-time random value initialized at stack startup
|
||||
// and is used to seed the TCP port picking on active connections
|
||||
//
|
||||
// TODO(gvisor.dev/issue/940): S/R this field.
|
||||
portSeed uint32
|
||||
seed uint32
|
||||
|
||||
// ndpConfigs is the default NDP configurations used by interfaces.
|
||||
ndpConfigs NDPConfigurations
|
||||
@@ -544,7 +544,7 @@ func New(opts Options) *Stack {
|
||||
stats: opts.Stats.FillIn(),
|
||||
handleLocal: opts.HandleLocal,
|
||||
icmpRateLimiter: NewICMPRateLimiter(),
|
||||
portSeed: generateRandUint32(),
|
||||
seed: generateRandUint32(),
|
||||
ndpConfigs: opts.NDPConfigs,
|
||||
autoGenIPv6LinkLocal: opts.AutoGenIPv6LinkLocal,
|
||||
uniqueIDGenerator: opts.UniqueID,
|
||||
@@ -1186,6 +1186,12 @@ func (s *Stack) CompleteTransportEndpointCleanup(ep TransportEndpoint) {
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// FindTransportEndpoint finds an endpoint that most closely matches the provided
|
||||
// id. If no endpoint is found it returns nil.
|
||||
func (s *Stack) FindTransportEndpoint(netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, id TransportEndpointID, r *Route) TransportEndpoint {
|
||||
return s.demux.findTransportEndpoint(netProto, transProto, id, r)
|
||||
}
|
||||
|
||||
// RegisterRawTransportEndpoint registers the given endpoint with the stack
|
||||
// transport dispatcher. Received packets that match the provided transport
|
||||
// protocol will be delivered to the given endpoint.
|
||||
@@ -1573,12 +1579,12 @@ func (s *Stack) HandleNDPRA(id tcpip.NICID, ip tcpip.Address, ra header.NDPRoute
|
||||
return nil
|
||||
}
|
||||
|
||||
// PortSeed returns a 32 bit value that can be used as a seed value for port
|
||||
// picking.
|
||||
// Seed returns a 32 bit value that can be used as a seed value for port
|
||||
// picking, ISN generation etc.
|
||||
//
|
||||
// NOTE: The seed is generated once during stack initialization only.
|
||||
func (s *Stack) PortSeed() uint32 {
|
||||
return s.portSeed
|
||||
func (s *Stack) Seed() uint32 {
|
||||
return s.seed
|
||||
}
|
||||
|
||||
func generateRandUint32() uint32 {
|
||||
|
||||
@@ -103,7 +103,6 @@ func (epsByNic *endpointsByNic) handlePacket(r *Route, id TransportEndpointID, p
|
||||
epsByNic.mu.RUnlock() // Don't use defer for performance reasons.
|
||||
return
|
||||
}
|
||||
|
||||
// multiPortEndpoints are guaranteed to have at least one element.
|
||||
selectEndpoint(id, mpep, epsByNic.seed).HandlePacket(r, id, pkt)
|
||||
epsByNic.mu.RUnlock() // Don't use defer for performance reasons.
|
||||
@@ -507,10 +506,40 @@ func (d *transportDemuxer) findAllEndpointsLocked(eps *transportEndpoints, id Tr
|
||||
if ep, ok := eps.endpoints[nid]; ok {
|
||||
matchedEPs = append(matchedEPs, ep)
|
||||
}
|
||||
|
||||
return matchedEPs
|
||||
}
|
||||
|
||||
// findTransportEndpoint find a single endpoint that most closely matches the provided id.
|
||||
func (d *transportDemuxer) findTransportEndpoint(netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, id TransportEndpointID, r *Route) TransportEndpoint {
|
||||
eps, ok := d.protocol[protocolIDs{netProto, transProto}]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// Try to find the endpoint.
|
||||
eps.mu.RLock()
|
||||
epsByNic := d.findEndpointLocked(eps, id)
|
||||
// Fail if we didn't find one.
|
||||
if epsByNic == nil {
|
||||
eps.mu.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
epsByNic.mu.RLock()
|
||||
eps.mu.RUnlock()
|
||||
|
||||
mpep, ok := epsByNic.endpoints[r.ref.nic.ID()]
|
||||
if !ok {
|
||||
if mpep, ok = epsByNic.endpoints[0]; !ok {
|
||||
epsByNic.mu.RUnlock() // Don't use defer for performance reasons.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ep := selectEndpoint(id, mpep, epsByNic.seed)
|
||||
epsByNic.mu.RUnlock()
|
||||
return ep
|
||||
}
|
||||
|
||||
// findEndpointLocked returns the endpoint that most closely matches the given
|
||||
// id.
|
||||
func (d *transportDemuxer) findEndpointLocked(eps *transportEndpoints, id TransportEndpointID) *endpointsByNic {
|
||||
|
||||
+11
-1
@@ -586,6 +586,16 @@ type MaxSegOption int
|
||||
// A zero value indicates the default.
|
||||
type TTLOption uint8
|
||||
|
||||
// TCPLingerTimeoutOption is used by SetSockOpt/GetSockOpt to set/get the
|
||||
// maximum duration for which a socket lingers in the TCP_FIN_WAIT_2 state
|
||||
// before being marked closed.
|
||||
type TCPLingerTimeoutOption time.Duration
|
||||
|
||||
// TCPTimeWaitTimeoutOption is used by SetSockOpt/GetSockOpt to set/get the
|
||||
// maximum duration for which a socket lingers in the TIME_WAIT state
|
||||
// before being marked closed.
|
||||
type TCPTimeWaitTimeoutOption time.Duration
|
||||
|
||||
// MulticastTTLOption is used by SetSockOpt/GetSockOpt to control the default
|
||||
// TTL value for multicast messages. The default is 1.
|
||||
type MulticastTTLOption uint8
|
||||
@@ -1329,8 +1339,8 @@ var (
|
||||
|
||||
// GetDanglingEndpoints returns all dangling endpoints.
|
||||
func GetDanglingEndpoints() []Endpoint {
|
||||
es := make([]Endpoint, 0, len(danglingEndpoints))
|
||||
danglingEndpointsMu.Lock()
|
||||
es := make([]Endpoint, 0, len(danglingEndpoints))
|
||||
for e := range danglingEndpoints {
|
||||
es = append(es, e)
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ filegroup(
|
||||
|
||||
go_test(
|
||||
name = "tcp_test",
|
||||
size = "small",
|
||||
size = "medium",
|
||||
srcs = [
|
||||
"dual_stack_test.go",
|
||||
"sack_scoreboard_test.go",
|
||||
|
||||
@@ -269,8 +269,8 @@ func (l *listenContext) createConnectingEndpoint(s *segment, iss seqnum.Value, i
|
||||
func (l *listenContext) createEndpointAndPerformHandshake(s *segment, opts *header.TCPSynOptions) (*endpoint, *tcpip.Error) {
|
||||
// Create new endpoint.
|
||||
irs := s.sequenceNumber
|
||||
cookie := l.createCookie(s.id, irs, encodeMSS(opts.MSS))
|
||||
ep, err := l.createConnectingEndpoint(s, cookie, irs, opts)
|
||||
isn := generateSecureISN(s.id, l.stack.Seed())
|
||||
ep, err := l.createConnectingEndpoint(s, isn, irs, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -289,7 +289,7 @@ func (l *listenContext) createEndpointAndPerformHandshake(s *segment, opts *head
|
||||
// Perform the 3-way handshake.
|
||||
h := newHandshake(ep, seqnum.Size(ep.initialReceiveWindow()))
|
||||
|
||||
h.resetToSynRcvd(cookie, irs, opts)
|
||||
h.resetToSynRcvd(isn, irs, opts)
|
||||
if err := h.execute(); err != nil {
|
||||
ep.Close()
|
||||
if l.listenEP != nil {
|
||||
@@ -361,6 +361,7 @@ func (e *endpoint) handleSynSegment(ctx *listenContext, s *segment, opts *header
|
||||
defer decSynRcvdCount()
|
||||
defer e.decSynRcvdCount()
|
||||
defer s.decRef()
|
||||
|
||||
n, err := ctx.createEndpointAndPerformHandshake(s, opts)
|
||||
if err != nil {
|
||||
e.stack.Stats().TCP.FailedConnectionAttempts.Increment()
|
||||
@@ -368,6 +369,11 @@ func (e *endpoint) handleSynSegment(ctx *listenContext, s *segment, opts *header
|
||||
return
|
||||
}
|
||||
ctx.removePendingEndpoint(n)
|
||||
// Start the protocol goroutine.
|
||||
wq := &waiter.Queue{}
|
||||
n.startAcceptedLoop(wq)
|
||||
e.stack.Stats().TCP.PassiveConnectionOpenings.Increment()
|
||||
|
||||
e.deliverAccepted(n)
|
||||
}
|
||||
|
||||
@@ -543,6 +549,11 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
|
||||
// number of goroutines as we do check before
|
||||
// entering here that there was at least some
|
||||
// space available in the backlog.
|
||||
|
||||
// Start the protocol goroutine.
|
||||
wq := &waiter.Queue{}
|
||||
n.startAcceptedLoop(wq)
|
||||
e.stack.Stats().TCP.PassiveConnectionOpenings.Increment()
|
||||
go e.deliverAccepted(n)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sleep"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/hash/jenkins"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
@@ -139,7 +141,32 @@ func (h *handshake) resetState() {
|
||||
h.flags = header.TCPFlagSyn
|
||||
h.ackNum = 0
|
||||
h.mss = 0
|
||||
h.iss = seqnum.Value(uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24)
|
||||
h.iss = generateSecureISN(h.ep.ID, h.ep.stack.Seed())
|
||||
}
|
||||
|
||||
// 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, seed uint32) seqnum.Value {
|
||||
isnHasher := jenkins.Sum32(seed)
|
||||
isnHasher.Write([]byte(id.LocalAddress))
|
||||
isnHasher.Write([]byte(id.RemoteAddress))
|
||||
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.
|
||||
isn := isnHasher.Sum32() + uint32(time.Now().UnixNano()>>6)
|
||||
return seqnum.Value(isn)
|
||||
}
|
||||
|
||||
// effectiveRcvWndScale returns the effective receive window scale to be used.
|
||||
@@ -809,7 +836,19 @@ func (e *endpoint) resetConnectionLocked(err *tcpip.Error) {
|
||||
e.state = StateError
|
||||
e.HardError = err
|
||||
if err != tcpip.ErrConnectionReset {
|
||||
e.sendRaw(buffer.VectorisedView{}, header.TCPFlagAck|header.TCPFlagRst, e.snd.sndUna, e.rcv.rcvNxt, 0)
|
||||
// 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.sendRaw(buffer.VectorisedView{}, header.TCPFlagAck|header.TCPFlagRst, resetSeqNum, e.rcv.rcvNxt, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -823,6 +862,51 @@ func (e *endpoint) completeWorkerLocked() {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
s.decRef()
|
||||
e.mu.Lock()
|
||||
switch e.state {
|
||||
// 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.state = StateClose
|
||||
e.HardError = tcpip.ErrAborted
|
||||
// We need to set this explicitly here because otherwise
|
||||
// the port registrations will not be released till the
|
||||
// endpoint is actively closed by the application.
|
||||
e.workerCleanup = true
|
||||
e.mu.Unlock()
|
||||
return false, nil
|
||||
default:
|
||||
e.mu.Unlock()
|
||||
return false, tcpip.ErrConnectionReset
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// handleSegments pulls segments from the queue and processes them. It returns
|
||||
// no error if the protocol loop should continue, an error otherwise.
|
||||
func (e *endpoint) handleSegments() *tcpip.Error {
|
||||
@@ -840,14 +924,34 @@ func (e *endpoint) handleSegments() *tcpip.Error {
|
||||
}
|
||||
|
||||
if s.flagIsSet(header.TCPFlagRst) {
|
||||
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.
|
||||
s.decRef()
|
||||
return tcpip.ErrConnectionReset
|
||||
if ok, err := e.handleReset(s); !ok {
|
||||
return err
|
||||
}
|
||||
} else if s.flagIsSet(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.sendAck()
|
||||
} else if s.flagIsSet(header.TCPFlagAck) {
|
||||
// Patch the window size in the segment according to the
|
||||
// send window scale.
|
||||
@@ -856,7 +960,15 @@ func (e *endpoint) handleSegments() *tcpip.Error {
|
||||
// RFC 793, page 41 states that "once in the ESTABLISHED
|
||||
// state all segments must carry current acknowledgment
|
||||
// information."
|
||||
e.rcv.handleRcvdSegment(s)
|
||||
drop, err := e.rcv.handleRcvdSegment(s)
|
||||
if err != nil {
|
||||
s.decRef()
|
||||
return err
|
||||
}
|
||||
if drop {
|
||||
s.decRef()
|
||||
continue
|
||||
}
|
||||
e.snd.handleRcvdSegment(s)
|
||||
}
|
||||
s.decRef()
|
||||
@@ -955,7 +1067,6 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
|
||||
}
|
||||
|
||||
e.mu.Unlock()
|
||||
|
||||
// When the protocol loop exits we should wake up our waiters.
|
||||
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.EventIn | waiter.EventOut)
|
||||
}
|
||||
@@ -1001,6 +1112,10 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
|
||||
// RTT itself.
|
||||
e.rcvAutoParams.prevCopied = initialRcvWnd
|
||||
e.rcvListMu.Unlock()
|
||||
e.stack.Stats().TCP.CurrentEstablished.Increment()
|
||||
e.mu.Lock()
|
||||
e.state = StateEstablished
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
e.keepalive.timer.init(&e.keepalive.waker)
|
||||
@@ -1008,10 +1123,6 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
|
||||
|
||||
// Tell waiters that the endpoint is connected and writable.
|
||||
e.mu.Lock()
|
||||
if e.state != StateEstablished {
|
||||
e.stack.Stats().TCP.CurrentEstablished.Increment()
|
||||
e.state = StateEstablished
|
||||
}
|
||||
drained := e.drainDone != nil
|
||||
e.mu.Unlock()
|
||||
if drained {
|
||||
@@ -1042,7 +1153,13 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
|
||||
{
|
||||
w: &closeWaker,
|
||||
f: func() *tcpip.Error {
|
||||
return tcpip.ErrConnectionAborted
|
||||
// This means the socket is being closed due
|
||||
// to the TCP_FIN_WAIT2 timeout was hit. Just
|
||||
// mark the socket as closed.
|
||||
e.mu.Lock()
|
||||
e.state = StateClose
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1085,17 +1202,18 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
|
||||
e.resetConnectionLocked(tcpip.ErrConnectionAborted)
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
if n¬ifyClose != 0 && closeTimer == nil {
|
||||
// Reset the connection 3 seconds after
|
||||
// the endpoint has been closed.
|
||||
//
|
||||
// The timer could fire in background
|
||||
// when the endpoint is drained. That's
|
||||
// OK as the loop here will not honor
|
||||
// the firing until the undrain arrives.
|
||||
closeTimer = time.AfterFunc(3*time.Second, func() {
|
||||
closeWaker.Assert()
|
||||
})
|
||||
e.mu.Lock()
|
||||
if e.state == StateFinWait2 && e.closed {
|
||||
// The socket has been closed and we are in FIN_WAIT2
|
||||
// so start the FIN_WAIT2 timer.
|
||||
closeTimer = time.AfterFunc(e.tcpLingerTimeout, func() {
|
||||
closeWaker.Assert()
|
||||
})
|
||||
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.EventIn | waiter.EventOut)
|
||||
}
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
if n¬ifyKeepaliveChanged != 0 {
|
||||
@@ -1117,6 +1235,12 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
|
||||
}
|
||||
}
|
||||
|
||||
if n¬ifyTickleWorker != 0 {
|
||||
// Just a tickle notification. No need to do
|
||||
// anything.
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
@@ -1143,15 +1267,16 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
|
||||
}
|
||||
e.rcvListMu.Unlock()
|
||||
|
||||
e.mu.RLock()
|
||||
e.mu.Lock()
|
||||
if e.workerCleanup {
|
||||
e.notifyProtocolGoroutine(notifyClose)
|
||||
}
|
||||
e.mu.RUnlock()
|
||||
|
||||
// Main loop. Handle segments until both send and receive ends of the
|
||||
// connection have completed.
|
||||
for !e.rcv.closed || !e.snd.closed || e.snd.sndUna != e.snd.sndNxtList {
|
||||
|
||||
for e.state != StateTimeWait && e.state != StateClose && e.state != StateError {
|
||||
e.mu.Unlock()
|
||||
e.workMu.Unlock()
|
||||
v, _ := s.Fetch(true)
|
||||
e.workMu.Lock()
|
||||
@@ -1167,6 +1292,23 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
|
||||
|
||||
return nil
|
||||
}
|
||||
e.mu.Lock()
|
||||
}
|
||||
|
||||
state := e.state
|
||||
e.mu.Unlock()
|
||||
var reuseTW func()
|
||||
if state == StateTimeWait {
|
||||
// Disable close timer as we now entering real TIME_WAIT.
|
||||
if closeTimer != nil {
|
||||
closeTimer.Stop()
|
||||
}
|
||||
// Mark the current sleeper done so as to free all associated
|
||||
// wakers.
|
||||
s.Done()
|
||||
// Wake up any waiters before we enter TIME_WAIT.
|
||||
e.waiterQueue.Notify(waiter.EventHUp | waiter.EventErr | waiter.EventIn | waiter.EventOut)
|
||||
reuseTW = e.doTimeWait()
|
||||
}
|
||||
|
||||
// Mark endpoint as closed.
|
||||
@@ -1176,8 +1318,130 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
|
||||
e.stack.Stats().TCP.CurrentEstablished.Decrement()
|
||||
e.state = StateClose
|
||||
}
|
||||
|
||||
// Lock released below.
|
||||
epilogue()
|
||||
|
||||
// A new SYN was received during TIME_WAIT and we need to abort
|
||||
// the timewait and redirect the segment to the listener queue
|
||||
if reuseTW != nil {
|
||||
reuseTW()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleTimeWaitSegments processes segments received during TIME_WAIT
|
||||
// state.
|
||||
func (e *endpoint) handleTimeWaitSegments() (extendTimeWait bool, reuseTW func()) {
|
||||
checkRequeue := true
|
||||
for i := 0; i < maxSegmentsPerWake; i++ {
|
||||
s := e.segmentQueue.dequeue()
|
||||
if s == nil {
|
||||
checkRequeue = false
|
||||
break
|
||||
}
|
||||
extTW, newSyn := e.rcv.handleTimeWaitSegment(s)
|
||||
if newSyn {
|
||||
info := e.EndpointInfo.TransportEndpointInfo
|
||||
newID := info.ID
|
||||
newID.RemoteAddress = ""
|
||||
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() != "" {
|
||||
netProtos = []tcpip.NetworkProtocolNumber{header.IPv4ProtocolNumber, header.IPv6ProtocolNumber}
|
||||
}
|
||||
for _, netProto := range netProtos {
|
||||
if listenEP := e.stack.FindTransportEndpoint(netProto, info.TransProto, newID, &s.route); listenEP != nil {
|
||||
tcpEP := listenEP.(*endpoint)
|
||||
if EndpointState(tcpEP.State()) == StateListen {
|
||||
reuseTW = func() {
|
||||
tcpEP.enqueueSegment(s)
|
||||
}
|
||||
// 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()
|
||||
}
|
||||
if checkRequeue && !e.segmentQueue.empty() {
|
||||
e.newSegmentWaker.Assert()
|
||||
}
|
||||
return extendTimeWait, nil
|
||||
}
|
||||
|
||||
// doTimeWait is responsible for handling the TCP behaviour once a socket
|
||||
// enters the TIME_WAIT state. Optionally it can return a closure that
|
||||
// should be executed after releasing the endpoint registrations. This is
|
||||
// done in cases where a new SYN is received during TIME_WAIT that carries
|
||||
// a sequence number larger than one see on the connection.
|
||||
func (e *endpoint) doTimeWait() (twReuse func()) {
|
||||
// Trigger a 2 * MSL time wait state. During this period
|
||||
// we will drop all incoming segments.
|
||||
// NOTE: On Linux this is not configurable and is fixed at 60 seconds.
|
||||
timeWaitDuration := DefaultTCPTimeWaitTimeout
|
||||
|
||||
// Get the stack wide configuration.
|
||||
var tcpTW tcpip.TCPTimeWaitTimeoutOption
|
||||
if err := e.stack.TransportProtocolOption(ProtocolNumber, &tcpTW); err == nil {
|
||||
timeWaitDuration = time.Duration(tcpTW)
|
||||
}
|
||||
|
||||
const newSegment = 1
|
||||
const notification = 2
|
||||
const timeWaitDone = 3
|
||||
|
||||
s := sleep.Sleeper{}
|
||||
s.AddWaker(&e.newSegmentWaker, newSegment)
|
||||
s.AddWaker(&e.notificationWaker, notification)
|
||||
|
||||
var timeWaitWaker sleep.Waker
|
||||
s.AddWaker(&timeWaitWaker, timeWaitDone)
|
||||
timeWaitTimer := time.AfterFunc(timeWaitDuration, timeWaitWaker.Assert)
|
||||
defer timeWaitTimer.Stop()
|
||||
|
||||
for {
|
||||
e.workMu.Unlock()
|
||||
v, _ := s.Fetch(true)
|
||||
e.workMu.Lock()
|
||||
switch v {
|
||||
case newSegment:
|
||||
extendTimeWait, reuseTW := e.handleTimeWaitSegments()
|
||||
if reuseTW != nil {
|
||||
return reuseTW
|
||||
}
|
||||
if extendTimeWait {
|
||||
timeWaitTimer.Reset(timeWaitDuration)
|
||||
}
|
||||
case notification:
|
||||
n := e.fetchNotifications()
|
||||
if n¬ifyClose != 0 {
|
||||
return nil
|
||||
}
|
||||
if n¬ifyDrain != 0 {
|
||||
for !e.segmentQueue.empty() {
|
||||
// Ignore extending TIME_WAIT during a
|
||||
// save. For sockets in TIME_WAIT we just
|
||||
// terminate the TIME_WAIT early.
|
||||
e.handleTimeWaitSegments()
|
||||
}
|
||||
close(e.drainDone)
|
||||
<-e.undrain
|
||||
return nil
|
||||
}
|
||||
case timeWaitDone:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,11 @@ const (
|
||||
notifyReset
|
||||
notifyKeepaliveChanged
|
||||
notifyMSSChanged
|
||||
// notifyTickleWorker is used to tickle the protocol main loop during a
|
||||
// restore after we update the endpoint state to the correct one. This
|
||||
// ensures the loop terminates if the final state of the endpoint is
|
||||
// say TIME_WAIT.
|
||||
notifyTickleWorker
|
||||
)
|
||||
|
||||
// SACKInfo holds TCP SACK related information for a given endpoint.
|
||||
@@ -320,6 +325,11 @@ type endpoint struct {
|
||||
|
||||
state EndpointState `state:".(EndpointState)"`
|
||||
|
||||
// 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 EndpointState `state:"nosave"`
|
||||
|
||||
isPortReserved bool `state:"manual"`
|
||||
isRegistered bool
|
||||
boundNICID tcpip.NICID `state:"manual"`
|
||||
@@ -503,6 +513,16 @@ type endpoint struct {
|
||||
|
||||
// TODO(b/142022063): Add ability to save and restore per endpoint stats.
|
||||
stats Stats `state:"nosave"`
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// UniqueID implements stack.TransportEndpoint.UniqueID.
|
||||
@@ -599,6 +619,11 @@ func newEndpoint(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, waiterQue
|
||||
e.SetSockOptInt(tcpip.DelayOption, 1)
|
||||
}
|
||||
|
||||
var tcpLT tcpip.TCPLingerTimeoutOption
|
||||
if err := s.TransportProtocolOption(ProtocolNumber, &tcpLT); err == nil {
|
||||
e.tcpLingerTimeout = time.Duration(tcpLT)
|
||||
}
|
||||
|
||||
if p := s.GetTCPProbe(); p != nil {
|
||||
e.probe = p
|
||||
}
|
||||
@@ -686,6 +711,13 @@ func (e *endpoint) notifyProtocolGoroutine(n uint32) {
|
||||
// with it. It must be called only once and with no other concurrent calls to
|
||||
// the endpoint.
|
||||
func (e *endpoint) Close() {
|
||||
e.mu.Lock()
|
||||
closed := e.closed
|
||||
e.mu.Unlock()
|
||||
if closed {
|
||||
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.Shutdown(tcpip.ShutdownWrite | tcpip.ShutdownRead)
|
||||
@@ -706,6 +738,8 @@ func (e *endpoint) Close() {
|
||||
e.isPortReserved = false
|
||||
}
|
||||
|
||||
// Mark endpoint as closed.
|
||||
e.closed = true
|
||||
// Either perform the local cleanup or kick the worker to make sure it
|
||||
// knows it needs to cleanup.
|
||||
tcpip.AddDanglingEndpoint(e)
|
||||
@@ -731,9 +765,7 @@ func (e *endpoint) closePendingAcceptableConnectionsLocked() {
|
||||
go func() {
|
||||
defer close(done)
|
||||
for n := range e.acceptedChan {
|
||||
n.mu.Lock()
|
||||
n.resetConnectionLocked(tcpip.ErrConnectionAborted)
|
||||
n.mu.Unlock()
|
||||
n.notifyProtocolGoroutine(notifyReset)
|
||||
n.Close()
|
||||
}
|
||||
}()
|
||||
@@ -1349,6 +1381,28 @@ func (e *endpoint) SetSockOpt(opt interface{}) *tcpip.Error {
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
|
||||
case tcpip.TCPLingerTimeoutOption:
|
||||
e.mu.Lock()
|
||||
if v < 0 {
|
||||
// Same as effectively disabling TCPLinger timeout.
|
||||
v = 0
|
||||
}
|
||||
var stkTCPLingerTimeout tcpip.TCPLingerTimeoutOption
|
||||
if err := e.stack.TransportProtocolOption(header.TCPProtocolNumber, &stkTCPLingerTimeout); err != nil {
|
||||
// We were unable to retrieve a stack config, just use
|
||||
// the DefaultTCPLingerTimeout.
|
||||
if v > tcpip.TCPLingerTimeoutOption(DefaultTCPLingerTimeout) {
|
||||
stkTCPLingerTimeout = tcpip.TCPLingerTimeoutOption(DefaultTCPLingerTimeout)
|
||||
}
|
||||
}
|
||||
// Cap it to the stack wide TCPLinger timeout.
|
||||
if v > stkTCPLingerTimeout {
|
||||
v = stkTCPLingerTimeout
|
||||
}
|
||||
e.tcpLingerTimeout = time.Duration(v)
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -1562,6 +1616,12 @@ func (e *endpoint) GetSockOpt(opt interface{}) *tcpip.Error {
|
||||
e.mu.RUnlock()
|
||||
return nil
|
||||
|
||||
case *tcpip.TCPLingerTimeoutOption:
|
||||
e.mu.Lock()
|
||||
*o = tcpip.TCPLingerTimeoutOption(e.tcpLingerTimeout)
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
|
||||
default:
|
||||
return tcpip.ErrUnknownProtocolOption
|
||||
}
|
||||
@@ -1696,7 +1756,7 @@ func (e *endpoint) connect(addr tcpip.FullAddress, handshake bool, run bool) *tc
|
||||
// src IP to ensure that for a given tuple (srcIP, destIP,
|
||||
// destPort) the offset used as a starting point is the same to
|
||||
// ensure that we can cycle through the port space effectively.
|
||||
h := jenkins.Sum32(e.stack.PortSeed())
|
||||
h := jenkins.Sum32(e.stack.Seed())
|
||||
h.Write([]byte(e.ID.LocalAddress))
|
||||
h.Write([]byte(e.ID.RemoteAddress))
|
||||
portBuf := make([]byte, 2)
|
||||
@@ -1782,9 +1842,8 @@ func (*endpoint) ConnectEndpoint(tcpip.Endpoint) *tcpip.Error {
|
||||
// peer.
|
||||
func (e *endpoint) Shutdown(flags tcpip.ShutdownFlags) *tcpip.Error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.shutdownFlags |= flags
|
||||
|
||||
finQueued := false
|
||||
switch {
|
||||
case e.state.connected():
|
||||
// Close for read.
|
||||
@@ -1799,6 +1858,7 @@ func (e *endpoint) Shutdown(flags tcpip.ShutdownFlags) *tcpip.Error {
|
||||
// the connection with a RST.
|
||||
if (e.shutdownFlags&tcpip.ShutdownWrite) != 0 && rcvBufUsed > 0 {
|
||||
e.notifyProtocolGoroutine(notifyReset)
|
||||
e.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1817,14 +1877,11 @@ func (e *endpoint) Shutdown(flags tcpip.ShutdownFlags) *tcpip.Error {
|
||||
s := newSegmentFromView(&e.route, e.ID, nil)
|
||||
e.sndQueue.PushBack(s)
|
||||
e.sndBufInQueue++
|
||||
|
||||
finQueued = true
|
||||
// Mark endpoint as closed.
|
||||
e.sndClosed = true
|
||||
|
||||
e.sndBufMu.Unlock()
|
||||
|
||||
// Tell protocol goroutine to close.
|
||||
e.sndCloseWaker.Assert()
|
||||
}
|
||||
|
||||
case e.state == StateListen:
|
||||
@@ -1832,11 +1889,20 @@ func (e *endpoint) Shutdown(flags tcpip.ShutdownFlags) *tcpip.Error {
|
||||
if flags&tcpip.ShutdownRead != 0 {
|
||||
e.notifyProtocolGoroutine(notifyClose)
|
||||
}
|
||||
|
||||
default:
|
||||
e.mu.Unlock()
|
||||
return tcpip.ErrNotConnected
|
||||
}
|
||||
|
||||
e.mu.Unlock()
|
||||
if finQueued {
|
||||
if e.workMu.TryLock() {
|
||||
e.handleClose()
|
||||
e.workMu.Unlock()
|
||||
} else {
|
||||
// Tell protocol goroutine to close.
|
||||
e.sndCloseWaker.Assert()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1928,12 +1994,7 @@ func (e *endpoint) Accept() (tcpip.Endpoint, *waiter.Queue, *tcpip.Error) {
|
||||
return nil, nil, tcpip.ErrWouldBlock
|
||||
}
|
||||
|
||||
// Start the protocol goroutine.
|
||||
wq := &waiter.Queue{}
|
||||
n.startAcceptedLoop(wq)
|
||||
e.stack.Stats().TCP.PassiveConnectionOpenings.Increment()
|
||||
|
||||
return n, wq, nil
|
||||
return n, n.waiterQueue, nil
|
||||
}
|
||||
|
||||
// Bind binds the endpoint to a specific local port and optionally address.
|
||||
@@ -2058,6 +2119,10 @@ func (e *endpoint) HandlePacket(r *stack.Route, id stack.TransportEndpointID, pk
|
||||
e.stack.Stats().TCP.ResetsReceived.Increment()
|
||||
}
|
||||
|
||||
e.enqueueSegment(s)
|
||||
}
|
||||
|
||||
func (e *endpoint) enqueueSegment(s *segment) {
|
||||
// Send packet to worker goroutine.
|
||||
if e.segmentQueue.enqueue(s) {
|
||||
e.newSegmentWaker.Assert()
|
||||
|
||||
@@ -78,7 +78,7 @@ func (e *endpoint) beforeSave() {
|
||||
}
|
||||
fallthrough
|
||||
case StateError, StateClose:
|
||||
for e.state == StateError && e.workerRunning {
|
||||
for (e.state == StateError || e.state == StateClose) && e.workerRunning {
|
||||
e.mu.Unlock()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
e.mu.Lock()
|
||||
@@ -165,6 +165,12 @@ func (e *endpoint) loadState(state EndpointState) {
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (e *endpoint) afterLoad() {
|
||||
// Freeze segment queue before registering to prevent any segments
|
||||
// from being delivered while it is being restored.
|
||||
e.origEndpointState = e.state
|
||||
// Restore the endpoint to InitialState as it will be moved to
|
||||
// its origEndpointState during Resume.
|
||||
e.state = StateInitial
|
||||
stack.StackFromEnv.RegisterRestoredEndpoint(e)
|
||||
}
|
||||
|
||||
@@ -173,8 +179,8 @@ func (e *endpoint) Resume(s *stack.Stack) {
|
||||
e.stack = s
|
||||
e.segmentQueue.setLimit(MaxUnprocessedSegments)
|
||||
e.workMu.Init()
|
||||
state := e.origEndpointState
|
||||
|
||||
state := e.state
|
||||
switch state {
|
||||
case StateInitial, StateBound, StateListen, StateConnecting, StateEstablished:
|
||||
var ss SendBufferSizeOption
|
||||
@@ -189,7 +195,6 @@ func (e *endpoint) Resume(s *stack.Stack) {
|
||||
}
|
||||
|
||||
bind := func() {
|
||||
e.state = StateInitial
|
||||
if len(e.BindAddr) == 0 {
|
||||
e.BindAddr = e.ID.LocalAddress
|
||||
}
|
||||
@@ -219,6 +224,16 @@ func (e *endpoint) Resume(s *stack.Stack) {
|
||||
if err := e.connect(tcpip.FullAddress{NIC: e.boundNICID, Addr: e.connectingAddress, Port: e.ID.RemotePort}, false, e.workerRunning); err != tcpip.ErrConnectStarted {
|
||||
panic("endpoint connecting failed: " + err.String())
|
||||
}
|
||||
e.mu.Lock()
|
||||
e.state = e.origEndpointState
|
||||
closed := e.closed
|
||||
e.mu.Unlock()
|
||||
e.notifyProtocolGoroutine(notifyTickleWorker)
|
||||
if state == StateFinWait2 && closed {
|
||||
// If the endpoint has been closed then make sure we notify so
|
||||
// that the FIN_WAIT2 timer is started after a restore.
|
||||
e.notifyProtocolGoroutine(notifyClose)
|
||||
}
|
||||
connectedLoading.Done()
|
||||
case StateListen:
|
||||
tcpip.AsyncLoading.Add(1)
|
||||
@@ -265,8 +280,11 @@ func (e *endpoint) Resume(s *stack.Stack) {
|
||||
tcpip.AsyncLoading.Done()
|
||||
}()
|
||||
}
|
||||
fallthrough
|
||||
e.state = StateClose
|
||||
e.stack.CompleteTransportEndpointCleanup(e)
|
||||
tcpip.DeleteDanglingEndpoint(e)
|
||||
case StateError:
|
||||
e.state = StateError
|
||||
e.stack.CompleteTransportEndpointCleanup(e)
|
||||
tcpip.DeleteDanglingEndpoint(e)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ package tcp
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
@@ -54,6 +55,14 @@ const (
|
||||
// MaxUnprocessedSegments is the maximum number of unprocessed segments
|
||||
// that can be queued for a given endpoint.
|
||||
MaxUnprocessedSegments = 300
|
||||
|
||||
// DefaultTCPLingerTimeout is the amount of time that sockets linger in
|
||||
// FIN_WAIT_2 state before being marked closed.
|
||||
DefaultTCPLingerTimeout = 60 * time.Second
|
||||
|
||||
// DefaultTCPTimeWaitTimeout is the amount of time that sockets linger
|
||||
// in TIME_WAIT state before being marked closed.
|
||||
DefaultTCPTimeWaitTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
// SACKEnabled option can be used to enable SACK support in the TCP
|
||||
@@ -93,6 +102,8 @@ type protocol struct {
|
||||
congestionControl string
|
||||
availableCongestionControl []string
|
||||
moderateReceiveBuffer bool
|
||||
tcpLingerTimeout time.Duration
|
||||
tcpTimeWaitTimeout time.Duration
|
||||
}
|
||||
|
||||
// Number returns the tcp protocol number.
|
||||
@@ -212,6 +223,24 @@ func (p *protocol) SetOption(option interface{}) *tcpip.Error {
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
|
||||
case tcpip.TCPLingerTimeoutOption:
|
||||
if v < 0 {
|
||||
v = 0
|
||||
}
|
||||
p.mu.Lock()
|
||||
p.tcpLingerTimeout = time.Duration(v)
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
|
||||
case tcpip.TCPTimeWaitTimeoutOption:
|
||||
if v < 0 {
|
||||
v = 0
|
||||
}
|
||||
p.mu.Lock()
|
||||
p.tcpTimeWaitTimeout = time.Duration(v)
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
|
||||
default:
|
||||
return tcpip.ErrUnknownProtocolOption
|
||||
}
|
||||
@@ -262,6 +291,18 @@ func (p *protocol) Option(option interface{}) *tcpip.Error {
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
|
||||
case *tcpip.TCPLingerTimeoutOption:
|
||||
p.mu.Lock()
|
||||
*v = tcpip.TCPLingerTimeoutOption(p.tcpLingerTimeout)
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
|
||||
case *tcpip.TCPTimeWaitTimeoutOption:
|
||||
p.mu.Lock()
|
||||
*v = tcpip.TCPTimeWaitTimeoutOption(p.tcpTimeWaitTimeout)
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
|
||||
default:
|
||||
return tcpip.ErrUnknownProtocolOption
|
||||
}
|
||||
@@ -274,5 +315,7 @@ func NewProtocol() stack.TransportProtocol {
|
||||
recvBufferSize: ReceiveBufferSizeOption{MinBufferSize, DefaultReceiveBufferSize, MaxBufferSize},
|
||||
congestionControl: ccReno,
|
||||
availableCongestionControl: []string{ccReno, ccCubic},
|
||||
tcpLingerTimeout: DefaultTCPLingerTimeout,
|
||||
tcpTimeWaitTimeout: DefaultTCPTimeWaitTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"container/heap"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
|
||||
)
|
||||
@@ -209,6 +210,11 @@ func (r *receiver) consumeSegment(s *segment, segSeq seqnum.Value, segLen seqnum
|
||||
switch r.ep.state {
|
||||
case StateFinWait1:
|
||||
r.ep.state = StateFinWait2
|
||||
// Notify protocol goroutine that we have received an
|
||||
// ACK to our FIN so that it can start the FIN_WAIT2
|
||||
// timer to abort connection if the other side does
|
||||
// not close within 2MSL.
|
||||
r.ep.notifyProtocolGoroutine(notifyClose)
|
||||
case StateClosing:
|
||||
r.ep.state = StateTimeWait
|
||||
case StateLastAck:
|
||||
@@ -253,23 +259,105 @@ func (r *receiver) updateRTT() {
|
||||
r.ep.rcvListMu.Unlock()
|
||||
}
|
||||
|
||||
// handleRcvdSegment handles TCP segments directed at the connection managed by
|
||||
// r as they arrive. It is called by the protocol main loop.
|
||||
func (r *receiver) handleRcvdSegment(s *segment) {
|
||||
func (r *receiver) handleRcvdSegmentClosing(s *segment, state EndpointState, closed bool) (drop bool, err *tcpip.Error) {
|
||||
r.ep.rcvListMu.Lock()
|
||||
rcvClosed := r.ep.rcvClosed || r.closed
|
||||
r.ep.rcvListMu.Unlock()
|
||||
|
||||
// If we are in one of the shutdown states then we need to do
|
||||
// additional checks before we try and process the segment.
|
||||
switch state {
|
||||
case StateCloseWait, StateClosing, StateLastAck:
|
||||
if !s.sequenceNumber.LessThanEq(r.rcvNxt) {
|
||||
s.decRef()
|
||||
// Just drop the segment as we have
|
||||
// already received a FIN and this
|
||||
// segment is after the sequence number
|
||||
// for the FIN.
|
||||
return true, nil
|
||||
}
|
||||
fallthrough
|
||||
case StateFinWait1:
|
||||
fallthrough
|
||||
case StateFinWait2:
|
||||
// If we are closed for reads (either due to an
|
||||
// incoming FIN or the user calling shutdown(..,
|
||||
// SHUT_RD) then any data past the rcvNxt should
|
||||
// trigger a RST.
|
||||
endDataSeq := s.sequenceNumber.Add(seqnum.Size(s.data.Size()))
|
||||
if rcvClosed && r.rcvNxt.LessThan(endDataSeq) {
|
||||
s.decRef()
|
||||
return true, tcpip.ErrConnectionAborted
|
||||
}
|
||||
if state == StateFinWait1 {
|
||||
break
|
||||
}
|
||||
|
||||
// If it's a retransmission of an old data segment
|
||||
// or a pure ACK then allow it.
|
||||
if s.sequenceNumber.Add(s.logicalLen()).LessThanEq(r.rcvNxt) ||
|
||||
s.logicalLen() == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// In FIN-WAIT2 if the socket is fully
|
||||
// closed(not owned by application on our end
|
||||
// then the only acceptable segment is a
|
||||
// FIN. Since FIN can technically also carry
|
||||
// data we verify that the segment carrying a
|
||||
// FIN ends at exactly e.rcvNxt+1.
|
||||
//
|
||||
// From RFC793 page 25.
|
||||
//
|
||||
// For sequence number purposes, the SYN is
|
||||
// considered to occur before the first actual
|
||||
// data octet of the segment in which it occurs,
|
||||
// while the FIN is considered to occur after
|
||||
// the last actual data octet in a segment in
|
||||
// which it occurs.
|
||||
if closed && (!s.flagIsSet(header.TCPFlagFin) || s.sequenceNumber.Add(s.logicalLen()) != r.rcvNxt+1) {
|
||||
s.decRef()
|
||||
return true, tcpip.ErrConnectionAborted
|
||||
}
|
||||
}
|
||||
|
||||
// We don't care about receive processing anymore if the receive side
|
||||
// is closed.
|
||||
if r.closed {
|
||||
return
|
||||
//
|
||||
// NOTE: We still want to permit a FIN as it's possible only our
|
||||
// end has closed and the peer is yet to send a FIN. Hence we
|
||||
// compare only the payload.
|
||||
segEnd := s.sequenceNumber.Add(seqnum.Size(s.data.Size()))
|
||||
if rcvClosed && !segEnd.LessThanEq(r.rcvNxt) {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// handleRcvdSegment handles TCP segments directed at the connection managed by
|
||||
// r as they arrive. It is called by the protocol main loop.
|
||||
func (r *receiver) handleRcvdSegment(s *segment) (drop bool, err *tcpip.Error) {
|
||||
r.ep.mu.RLock()
|
||||
state := r.ep.state
|
||||
closed := r.ep.closed
|
||||
r.ep.mu.RUnlock()
|
||||
|
||||
if state != StateEstablished {
|
||||
drop, err := r.handleRcvdSegmentClosing(s, state, closed)
|
||||
if drop || err != nil {
|
||||
return drop, err
|
||||
}
|
||||
}
|
||||
|
||||
segLen := seqnum.Size(s.data.Size())
|
||||
segSeq := s.sequenceNumber
|
||||
|
||||
// If the sequence number range is outside the acceptable range, just
|
||||
// send an ACK. This is according to RFC 793, page 37.
|
||||
// send an ACK and stop further processing of the segment.
|
||||
// This is according to RFC 793, page 68.
|
||||
if !r.acceptable(segSeq, segLen) {
|
||||
r.ep.snd.sendAck()
|
||||
return
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Defer segment processing if it can't be consumed now.
|
||||
@@ -288,7 +376,7 @@ func (r *receiver) handleRcvdSegment(s *segment) {
|
||||
// have to retransmit.
|
||||
r.ep.snd.sendAck()
|
||||
}
|
||||
return
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Since we consumed a segment update the receiver's RTT estimate
|
||||
@@ -315,4 +403,67 @@ func (r *receiver) handleRcvdSegment(s *segment) {
|
||||
r.pendingBufUsed -= s.logicalLen()
|
||||
s.decRef()
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// handleTimeWaitSegment handles inbound segments received when the endpoint
|
||||
// has entered the TIME_WAIT state.
|
||||
func (r *receiver) handleTimeWaitSegment(s *segment) (resetTimeWait bool, newSyn bool) {
|
||||
segSeq := s.sequenceNumber
|
||||
segLen := seqnum.Size(s.data.Size())
|
||||
|
||||
// Just silently drop any RST packets in TIME_WAIT. We do not support
|
||||
// TIME_WAIT assasination as a result we confirm w/ fix 1 as described
|
||||
// in https://tools.ietf.org/html/rfc1337#section-3.
|
||||
if s.flagIsSet(header.TCPFlagRst) {
|
||||
return false, false
|
||||
}
|
||||
|
||||
// If it's a SYN and the sequence number is higher than any seen before
|
||||
// for this connection then try and redirect it to a listening endpoint
|
||||
// if available.
|
||||
//
|
||||
// RFC 1122:
|
||||
// "When a connection is [...] on TIME-WAIT state [...]
|
||||
// [a TCP] MAY accept a new SYN from the remote TCP to
|
||||
// reopen the connection directly, if it:
|
||||
|
||||
// (1) assigns its initial sequence number for the new
|
||||
// connection to be larger than the largest sequence
|
||||
// number it used on the previous connection incarnation,
|
||||
// and
|
||||
|
||||
// (2) returns to TIME-WAIT state if the SYN turns out
|
||||
// to be an old duplicate".
|
||||
if s.flagIsSet(header.TCPFlagSyn) && r.rcvNxt.LessThan(segSeq) {
|
||||
|
||||
return false, true
|
||||
}
|
||||
|
||||
// Drop the segment if it does not contain an ACK.
|
||||
if !s.flagIsSet(header.TCPFlagAck) {
|
||||
return false, false
|
||||
}
|
||||
|
||||
// Update Timestamp if required. See RFC7323, section-4.3.
|
||||
if r.ep.sendTSOk && s.parsedOptions.TS {
|
||||
r.ep.updateRecentTimestamp(s.parsedOptions.TSVal, r.ep.snd.maxSentAck, segSeq)
|
||||
}
|
||||
|
||||
if segSeq.Add(1) == r.rcvNxt && s.flagIsSet(header.TCPFlagFin) {
|
||||
// If it's a FIN-ACK then resetTimeWait and send an ACK, as it
|
||||
// indicates our final ACK could have been lost.
|
||||
r.ep.snd.sendAck()
|
||||
return true, false
|
||||
}
|
||||
|
||||
// If the sequence number range is outside the acceptable range or
|
||||
// carries data then just send an ACK. This is according to RFC 793,
|
||||
// page 37.
|
||||
//
|
||||
// NOTE: In TIME_WAIT the only acceptable sequence number is rcvNxt.
|
||||
if segSeq != r.rcvNxt || segLen != 0 {
|
||||
r.ep.snd.sendAck()
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -9,7 +9,7 @@ syscall_test(test = "//test/syscalls/linux:accept_bind_stream_test")
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:accept_bind_test",
|
||||
)
|
||||
|
||||
@@ -434,7 +434,7 @@ syscall_test(
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:socket_abstract_test",
|
||||
)
|
||||
|
||||
@@ -445,7 +445,7 @@ syscall_test(
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:socket_domain_test",
|
||||
)
|
||||
|
||||
@@ -458,19 +458,19 @@ syscall_test(
|
||||
syscall_test(
|
||||
size = "large",
|
||||
add_overlay = True,
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:socket_filesystem_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:socket_inet_loopback_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:socket_ip_tcp_generic_loopback_test",
|
||||
)
|
||||
|
||||
@@ -481,13 +481,13 @@ syscall_test(
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:socket_ip_tcp_loopback_test",
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
size = "medium",
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:socket_ip_tcp_udp_generic_loopback_test",
|
||||
)
|
||||
|
||||
@@ -498,7 +498,7 @@ syscall_test(
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:socket_ip_udp_loopback_test",
|
||||
)
|
||||
|
||||
@@ -560,7 +560,7 @@ syscall_test(
|
||||
syscall_test(
|
||||
size = "large",
|
||||
add_overlay = True,
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:socket_unix_pair_test",
|
||||
)
|
||||
|
||||
@@ -599,7 +599,7 @@ syscall_test(
|
||||
|
||||
syscall_test(
|
||||
size = "large",
|
||||
shard_count = 10,
|
||||
shard_count = 50,
|
||||
test = "//test/syscalls/linux:socket_unix_unbound_stream_test",
|
||||
)
|
||||
|
||||
|
||||
@@ -2141,6 +2141,7 @@ cc_library(
|
||||
deps = [
|
||||
":socket_test_util",
|
||||
"//test/util:test_util",
|
||||
"//test/util:thread_util",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
alwayslink = 1,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/tcp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
@@ -31,6 +32,7 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
#include "test/util/file_descriptor.h"
|
||||
@@ -267,6 +269,340 @@ TEST_P(SocketInetLoopbackTest, TCPbacklog) {
|
||||
}
|
||||
}
|
||||
|
||||
// TCPFinWait2Test creates a pair of connected sockets then closes one end to
|
||||
// trigger FIN_WAIT2 state for the closed endpoint. Then it binds the same local
|
||||
// IP/port on a new socket and tries to connect. The connect should fail w/
|
||||
// an EADDRINUSE. Then we wait till the FIN_WAIT2 timeout is over and try the
|
||||
// connect again with a new socket and this time it should succeed.
|
||||
//
|
||||
// TCP timers are not S/R today, this can cause this test to be flaky when run
|
||||
// under random S/R due to timer being reset on a restore.
|
||||
TEST_P(SocketInetLoopbackTest, TCPFinWait2Test_NoRandomSave) {
|
||||
auto const& param = GetParam();
|
||||
TestAddress const& listener = param.listener;
|
||||
TestAddress const& connector = param.connector;
|
||||
|
||||
// Create the listening socket.
|
||||
const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
sockaddr_storage listen_addr = listener.addr;
|
||||
ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr),
|
||||
listener.addr_len),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
|
||||
|
||||
// Get the port bound by the listening socket.
|
||||
socklen_t addrlen = listener.addr_len;
|
||||
ASSERT_THAT(getsockname(listen_fd.get(),
|
||||
reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
uint16_t const port =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
|
||||
|
||||
// Connect to the listening socket.
|
||||
FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
// Lower FIN_WAIT2 state to 5 seconds for test.
|
||||
constexpr int kTCPLingerTimeout = 5;
|
||||
EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2,
|
||||
&kTCPLingerTimeout, sizeof(kTCPLingerTimeout)),
|
||||
SyscallSucceedsWithValue(0));
|
||||
|
||||
sockaddr_storage conn_addr = connector.addr;
|
||||
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
|
||||
ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(),
|
||||
reinterpret_cast<sockaddr*>(&conn_addr),
|
||||
connector.addr_len),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Accept the connection.
|
||||
auto accepted =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
|
||||
|
||||
// Get the address/port bound by the connecting socket.
|
||||
sockaddr_storage conn_bound_addr;
|
||||
socklen_t conn_addrlen = connector.addr_len;
|
||||
ASSERT_THAT(
|
||||
getsockname(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr),
|
||||
&conn_addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
// close the connecting FD to trigger FIN_WAIT2 on the connected fd.
|
||||
conn_fd.reset();
|
||||
|
||||
// Now bind and connect a new socket.
|
||||
const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
// Disable cooperative saves after this point. As a save between the first
|
||||
// bind/connect and the second one can cause the linger timeout timer to
|
||||
// be restarted causing the final bind/connect to fail.
|
||||
DisableSave ds;
|
||||
|
||||
// TODO(gvisor.dev/issue/1030): Portmanager does not track all 5 tuple
|
||||
// reservations which causes the bind() to succeed on gVisor but connect
|
||||
// correctly fails.
|
||||
if (IsRunningOnGvisor()) {
|
||||
ASSERT_THAT(
|
||||
bind(conn_fd2.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr),
|
||||
conn_addrlen),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(),
|
||||
reinterpret_cast<sockaddr*>(&conn_addr),
|
||||
conn_addrlen),
|
||||
SyscallFailsWithErrno(EADDRINUSE));
|
||||
} else {
|
||||
ASSERT_THAT(
|
||||
bind(conn_fd2.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr),
|
||||
conn_addrlen),
|
||||
SyscallFailsWithErrno(EADDRINUSE));
|
||||
}
|
||||
|
||||
// Sleep for a little over the linger timeout to reduce flakiness in
|
||||
// save/restore tests.
|
||||
absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 1));
|
||||
|
||||
ds.reset();
|
||||
|
||||
if (!IsRunningOnGvisor()) {
|
||||
ASSERT_THAT(
|
||||
bind(conn_fd2.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr),
|
||||
conn_addrlen),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(),
|
||||
reinterpret_cast<sockaddr*>(&conn_addr),
|
||||
conn_addrlen),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
// TCPLinger2TimeoutAfterClose creates a pair of connected sockets
|
||||
// then closes one end to trigger FIN_WAIT2 state for the closed endpont.
|
||||
// It then sleeps for the TCP_LINGER2 timeout and verifies that bind/
|
||||
// connecting the same address succeeds.
|
||||
//
|
||||
// TCP timers are not S/R today, this can cause this test to be flaky when run
|
||||
// under random S/R due to timer being reset on a restore.
|
||||
TEST_P(SocketInetLoopbackTest, TCPLinger2TimeoutAfterClose_NoRandomSave) {
|
||||
auto const& param = GetParam();
|
||||
TestAddress const& listener = param.listener;
|
||||
TestAddress const& connector = param.connector;
|
||||
|
||||
// Create the listening socket.
|
||||
const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
sockaddr_storage listen_addr = listener.addr;
|
||||
ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr),
|
||||
listener.addr_len),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
|
||||
|
||||
// Get the port bound by the listening socket.
|
||||
socklen_t addrlen = listener.addr_len;
|
||||
ASSERT_THAT(getsockname(listen_fd.get(),
|
||||
reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
uint16_t const port =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
|
||||
|
||||
// Connect to the listening socket.
|
||||
FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
sockaddr_storage conn_addr = connector.addr;
|
||||
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
|
||||
ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(),
|
||||
reinterpret_cast<sockaddr*>(&conn_addr),
|
||||
connector.addr_len),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Accept the connection.
|
||||
auto accepted =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
|
||||
|
||||
// Get the address/port bound by the connecting socket.
|
||||
sockaddr_storage conn_bound_addr;
|
||||
socklen_t conn_addrlen = connector.addr_len;
|
||||
ASSERT_THAT(
|
||||
getsockname(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr),
|
||||
&conn_addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
constexpr int kTCPLingerTimeout = 5;
|
||||
EXPECT_THAT(setsockopt(conn_fd.get(), IPPROTO_TCP, TCP_LINGER2,
|
||||
&kTCPLingerTimeout, sizeof(kTCPLingerTimeout)),
|
||||
SyscallSucceedsWithValue(0));
|
||||
|
||||
// close the connecting FD to trigger FIN_WAIT2 on the connected fd.
|
||||
conn_fd.reset();
|
||||
|
||||
absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 1));
|
||||
|
||||
// Now bind and connect a new socket and verify that we can immediately
|
||||
// rebind the address bound by the conn_fd as it never entered TIME_WAIT.
|
||||
const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
ASSERT_THAT(bind(conn_fd2.get(),
|
||||
reinterpret_cast<sockaddr*>(&conn_bound_addr), conn_addrlen),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(),
|
||||
reinterpret_cast<sockaddr*>(&conn_addr),
|
||||
conn_addrlen),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
// TCPResetAfterClose creates a pair of connected sockets then closes
|
||||
// one end to trigger FIN_WAIT2 state for the closed endpoint verifies
|
||||
// that we generate RSTs for any new data after the socket is fully
|
||||
// closed.
|
||||
TEST_P(SocketInetLoopbackTest, TCPResetAfterClose) {
|
||||
auto const& param = GetParam();
|
||||
TestAddress const& listener = param.listener;
|
||||
TestAddress const& connector = param.connector;
|
||||
|
||||
// Create the listening socket.
|
||||
const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
sockaddr_storage listen_addr = listener.addr;
|
||||
ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr),
|
||||
listener.addr_len),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
|
||||
|
||||
// Get the port bound by the listening socket.
|
||||
socklen_t addrlen = listener.addr_len;
|
||||
ASSERT_THAT(getsockname(listen_fd.get(),
|
||||
reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
uint16_t const port =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
|
||||
|
||||
// Connect to the listening socket.
|
||||
FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
sockaddr_storage conn_addr = connector.addr;
|
||||
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
|
||||
ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(),
|
||||
reinterpret_cast<sockaddr*>(&conn_addr),
|
||||
connector.addr_len),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Accept the connection.
|
||||
auto accepted =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
|
||||
|
||||
// close the connecting FD to trigger FIN_WAIT2 on the connected fd.
|
||||
conn_fd.reset();
|
||||
|
||||
int data = 1234;
|
||||
|
||||
// Now send data which should trigger a RST as the other end should
|
||||
// have timed out and closed the socket.
|
||||
EXPECT_THAT(RetryEINTR(send)(accepted.get(), &data, sizeof(data), 0),
|
||||
SyscallSucceeds());
|
||||
// Sleep for a shortwhile to get a RST back.
|
||||
absl::SleepFor(absl::Seconds(1));
|
||||
|
||||
// Try writing again and we should get an EPIPE back.
|
||||
EXPECT_THAT(RetryEINTR(send)(accepted.get(), &data, sizeof(data), 0),
|
||||
SyscallFailsWithErrno(EPIPE));
|
||||
|
||||
// Trying to read should return zero as the other end did send
|
||||
// us a FIN. We do it twice to verify that the RST does not cause an
|
||||
// ECONNRESET on the read after EOF has been read by applicaiton.
|
||||
EXPECT_THAT(RetryEINTR(recv)(accepted.get(), &data, sizeof(data), 0),
|
||||
SyscallSucceedsWithValue(0));
|
||||
EXPECT_THAT(RetryEINTR(recv)(accepted.get(), &data, sizeof(data), 0),
|
||||
SyscallSucceedsWithValue(0));
|
||||
}
|
||||
|
||||
// This test is disabled under random save as the the restore run
|
||||
// results in the stack.Seed() being different which can cause
|
||||
// sequence number of final connect to be one that is considered
|
||||
// old and can cause the test to be flaky.
|
||||
TEST_P(SocketInetLoopbackTest, TCPTimeWaitTest_NoRandomSave) {
|
||||
auto const& param = GetParam();
|
||||
TestAddress const& listener = param.listener;
|
||||
TestAddress const& connector = param.connector;
|
||||
|
||||
// Create the listening socket.
|
||||
const FileDescriptor listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
sockaddr_storage listen_addr = listener.addr;
|
||||
ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr),
|
||||
listener.addr_len),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
|
||||
|
||||
// Get the port bound by the listening socket.
|
||||
socklen_t addrlen = listener.addr_len;
|
||||
ASSERT_THAT(getsockname(listen_fd.get(),
|
||||
reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
uint16_t const port =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
|
||||
|
||||
// Connect to the listening socket.
|
||||
FileDescriptor conn_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
// We disable saves after this point as a S/R causes the netstack seed
|
||||
// to be regenerated which changes what ports/ISN is picked for a given
|
||||
// tuple (src ip,src port, dst ip, dst port). This can cause the final
|
||||
// SYN to use a sequence number that looks like one from the current
|
||||
// connection in TIME_WAIT and will not be accepted causing the test
|
||||
// to timeout.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/940): S/R portSeed/portHint
|
||||
DisableSave ds;
|
||||
sockaddr_storage conn_addr = connector.addr;
|
||||
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
|
||||
ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(),
|
||||
reinterpret_cast<sockaddr*>(&conn_addr),
|
||||
connector.addr_len),
|
||||
SyscallSucceeds());
|
||||
|
||||
// Accept the connection.
|
||||
auto accepted =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
|
||||
|
||||
// Get the address/port bound by the connecting socket.
|
||||
sockaddr_storage conn_bound_addr;
|
||||
socklen_t conn_addrlen = connector.addr_len;
|
||||
ASSERT_THAT(
|
||||
getsockname(conn_fd.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr),
|
||||
&conn_addrlen),
|
||||
SyscallSucceeds());
|
||||
|
||||
// close the accept FD to trigger TIME_WAIT on the accepted socket which
|
||||
// should cause the conn_fd to follow CLOSE_WAIT->LAST_ACK->CLOSED instead of
|
||||
// TIME_WAIT.
|
||||
accepted.reset();
|
||||
absl::SleepFor(absl::Seconds(1));
|
||||
conn_fd.reset();
|
||||
absl::SleepFor(absl::Seconds(1));
|
||||
|
||||
// Now bind and connect a new socket and verify that we can immediately
|
||||
// rebind the address bound by the conn_fd as it never entered TIME_WAIT.
|
||||
const FileDescriptor conn_fd2 = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
ASSERT_THAT(bind(conn_fd2.get(),
|
||||
reinterpret_cast<sockaddr*>(&conn_bound_addr), conn_addrlen),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(),
|
||||
reinterpret_cast<sockaddr*>(&conn_addr),
|
||||
conn_addrlen),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
All, SocketInetLoopbackTest,
|
||||
::testing::Values(
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "test/syscalls/linux/socket_test_util.h"
|
||||
#include "test/util/test_util.h"
|
||||
#include "test/util/thread_util.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
@@ -243,6 +244,31 @@ TEST_P(TCPSocketPairTest, ShutdownRdAllowsReadOfReceivedDataBeforeEOF) {
|
||||
SyscallSucceedsWithValue(0));
|
||||
}
|
||||
|
||||
// This test verifies that a shutdown(wr) by the server after sending
|
||||
// data allows the client to still read() the queued data and a client
|
||||
// close after sending response allows server to read the incoming
|
||||
// response.
|
||||
TEST_P(TCPSocketPairTest, ShutdownWrServerClientClose) {
|
||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||
char buf[10] = {};
|
||||
ScopedThread t([&]() {
|
||||
ASSERT_THAT(RetryEINTR(read)(sockets->first_fd(), buf, sizeof(buf)),
|
||||
SyscallSucceedsWithValue(sizeof(buf)));
|
||||
ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
|
||||
SyscallSucceedsWithValue(sizeof(buf)));
|
||||
ASSERT_THAT(close(sockets->release_first_fd()),
|
||||
SyscallSucceedsWithValue(0));
|
||||
});
|
||||
ASSERT_THAT(RetryEINTR(write)(sockets->second_fd(), buf, sizeof(buf)),
|
||||
SyscallSucceedsWithValue(sizeof(buf)));
|
||||
ASSERT_THAT(RetryEINTR(shutdown)(sockets->second_fd(), SHUT_WR),
|
||||
SyscallSucceedsWithValue(0));
|
||||
t.Join();
|
||||
|
||||
ASSERT_THAT(RetryEINTR(read)(sockets->second_fd(), buf, sizeof(buf)),
|
||||
SyscallSucceedsWithValue(sizeof(buf)));
|
||||
}
|
||||
|
||||
TEST_P(TCPSocketPairTest, ClosedReadNonBlockingSocket) {
|
||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||
|
||||
@@ -696,5 +722,72 @@ TEST_P(TCPSocketPairTest, SetCongestionControlFailsForUnsupported) {
|
||||
EXPECT_EQ(0, memcmp(got_cc, old_cc, sizeof(old_cc)));
|
||||
}
|
||||
|
||||
// Linux and Netstack both default to a 60s TCP_LINGER2 timeout.
|
||||
constexpr int kDefaultTCPLingerTimeout = 60;
|
||||
|
||||
TEST_P(TCPSocketPairTest, TCPLingerTimeoutDefault) {
|
||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||
|
||||
int get = -1;
|
||||
socklen_t get_len = sizeof(get);
|
||||
EXPECT_THAT(
|
||||
getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len),
|
||||
SyscallSucceedsWithValue(0));
|
||||
EXPECT_EQ(get_len, sizeof(get));
|
||||
EXPECT_EQ(get, kDefaultTCPLingerTimeout);
|
||||
}
|
||||
|
||||
TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutZeroOrLess) {
|
||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||
|
||||
constexpr int kZero = 0;
|
||||
EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &kZero,
|
||||
sizeof(kZero)),
|
||||
SyscallSucceedsWithValue(0));
|
||||
|
||||
constexpr int kNegative = -1234;
|
||||
EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2,
|
||||
&kNegative, sizeof(kNegative)),
|
||||
SyscallSucceedsWithValue(0));
|
||||
}
|
||||
|
||||
TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutAboveDefault) {
|
||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||
|
||||
// Values above the net.ipv4.tcp_fin_timeout are capped to tcp_fin_timeout
|
||||
// on linux (defaults to 60 seconds on linux).
|
||||
constexpr int kAboveDefault = kDefaultTCPLingerTimeout + 1;
|
||||
EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2,
|
||||
&kAboveDefault, sizeof(kAboveDefault)),
|
||||
SyscallSucceedsWithValue(0));
|
||||
|
||||
int get = -1;
|
||||
socklen_t get_len = sizeof(get);
|
||||
EXPECT_THAT(
|
||||
getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len),
|
||||
SyscallSucceedsWithValue(0));
|
||||
EXPECT_EQ(get_len, sizeof(get));
|
||||
EXPECT_EQ(get, kDefaultTCPLingerTimeout);
|
||||
}
|
||||
|
||||
TEST_P(TCPSocketPairTest, SetTCPLingerTimeout) {
|
||||
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
|
||||
|
||||
// Values above the net.ipv4.tcp_fin_timeout are capped to tcp_fin_timeout
|
||||
// on linux (defaults to 60 seconds on linux).
|
||||
constexpr int kTCPLingerTimeout = kDefaultTCPLingerTimeout - 1;
|
||||
EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2,
|
||||
&kTCPLingerTimeout, sizeof(kTCPLingerTimeout)),
|
||||
SyscallSucceedsWithValue(0));
|
||||
|
||||
int get = -1;
|
||||
socklen_t get_len = sizeof(get);
|
||||
EXPECT_THAT(
|
||||
getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len),
|
||||
SyscallSucceedsWithValue(0));
|
||||
EXPECT_EQ(get_len, sizeof(get));
|
||||
EXPECT_EQ(get, kTCPLingerTimeout);
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
Reference in New Issue
Block a user