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:
Bhasker Hariharan
2019-11-07 09:46:55 -08:00
committed by gVisor bot
parent 2326224a96
commit 66ebb6575f
17 changed files with 1736 additions and 113 deletions
+20
View File
@@ -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)
+4 -8
View File
@@ -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)
+13 -7
View File
@@ -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 {
+31 -2
View File
@@ -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
View File
@@ -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)
}
+1 -1
View File
@@ -71,7 +71,7 @@ filegroup(
go_test(
name = "tcp_test",
size = "small",
size = "medium",
srcs = [
"dual_stack_test.go",
"sack_scoreboard_test.go",
+14 -3
View File
@@ -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)
}
}
+293 -29
View File
@@ -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&notifyClose != 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&notifyKeepaliveChanged != 0 {
@@ -1117,6 +1235,12 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
}
}
if n&notifyTickleWorker != 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&notifyClose != 0 {
return nil
}
if n&notifyDrain != 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
}
}
}
+83 -18
View File
@@ -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()
+22 -4
View File
@@ -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)
}
+43
View File
@@ -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,
}
}
+159 -8
View File
@@ -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
View File
@@ -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",
)
+1
View File
@@ -2141,6 +2141,7 @@ cc_library(
deps = [
":socket_test_util",
"//test/util:test_util",
"//test/util:thread_util",
"@com_google_googletest//:gtest",
],
alwayslink = 1,
+336
View File
@@ -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