Handle in-flight TCP segments when moving to CLOSE.

As we move to CLOSE state from LAST-ACK or TIME-WAIT,
ensure that we re-match all in-flight segments to any
listening endpoint.

Also fix LISTEN state handling of any ACK segments as per RFC793.

Fixes #1153

PiperOrigin-RevId: 280703556
This commit is contained in:
Mithun Iyer
2019-11-15 12:11:36 -08:00
committed by gVisor bot
parent 76039f8959
commit 3e534f2974
4 changed files with 261 additions and 11 deletions
+11 -3
View File
@@ -419,8 +419,8 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
// TODO(b/143300739): Use the userMSS of the listening socket
// for accepted sockets.
switch s.flags {
case header.TCPFlagSyn:
switch {
case s.flags == header.TCPFlagSyn:
opts := parseSynSegmentOptions(s)
if incSynRcvdCount() {
// Only handle the syn if the following conditions hold
@@ -464,7 +464,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
e.stack.Stats().TCP.ListenOverflowSynCookieSent.Increment()
}
case header.TCPFlagAck:
case (s.flags & header.TCPFlagAck) != 0:
if e.acceptQueueIsFull() {
// Silently drop the ack as the application can't accept
// the connection at this point. The ack will be
@@ -478,6 +478,14 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
}
if !synCookiesInUse() {
// When not using SYN cookies, as per RFC 793, section 3.9, page 64:
// Any acknowledgment is bad if it arrives on a connection still in
// the LISTEN state. An acceptable reset segment should be formed
// for any arriving ACK-bearing segment. The RST should be
// formatted as follows:
//
// <SEQ=SEG.ACK><CTL=RST>
//
// Send a reset as this is an ACK for which there is no
// half open connections and we are not using cookies
// yet.
+53 -7
View File
@@ -865,6 +865,33 @@ func (e *endpoint) completeWorkerLocked() {
}
}
// transitionToStateCloseLocked ensures that the endpoint is
// cleaned up from the transport demuxer, "before" moving to
// StateClose. This will ensure that no packet will be
// delivered to this endpoint from the demuxer when the endpoint
// is transitioned to StateClose.
func (e *endpoint) transitionToStateCloseLocked() {
if e.state == StateClose {
return
}
e.cleanupLocked()
e.state = StateClose
}
// tryDeliverSegmentFromClosedEndpoint attempts to deliver the parsed
// segment to any other endpoint other than the current one. This is called
// only when the endpoint is in StateClose and we want to deliver the segment
// to any other listening endpoint. We reply with RST if we cannot find one.
func (e *endpoint) tryDeliverSegmentFromClosedEndpoint(s *segment) {
ep := e.stack.FindTransportEndpoint(e.NetProto, e.TransProto, e.ID, &s.route)
if ep == nil {
replyWithReset(s)
s.decRef()
return
}
ep.(*endpoint).enqueueSegment(s)
}
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
@@ -894,12 +921,8 @@ func (e *endpoint) handleReset(s *segment) (ok bool, err *tcpip.Error) {
// general "connection reset" signal. Enter the CLOSED state,
// delete the TCB, and return.
case StateCloseWait:
e.state = StateClose
e.transitionToStateCloseLocked()
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:
@@ -915,6 +938,20 @@ func (e *endpoint) handleReset(s *segment) (ok bool, err *tcpip.Error) {
func (e *endpoint) handleSegments() *tcpip.Error {
checkRequeue := true
for i := 0; i < maxSegmentsPerWake; i++ {
e.mu.RLock()
state := e.state
e.mu.RUnlock()
if state == StateClose {
// When we get into StateClose while processing from the queue,
// return immediately and let the protocolMainloop handle it.
//
// We can reach StateClose only while processing a previous segment
// or a notification from the protocolMainLoop (caller goroutine).
// This means that with this return, the segment dequeue below can
// never occur on a closed endpoint.
return nil
}
s := e.segmentQueue.dequeue()
if s == nil {
checkRequeue = false
@@ -1160,7 +1197,7 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
// to the TCP_FIN_WAIT2 timeout was hit. Just
// mark the socket as closed.
e.mu.Lock()
e.state = StateClose
e.transitionToStateCloseLocked()
e.mu.Unlock()
return nil
},
@@ -1321,12 +1358,21 @@ func (e *endpoint) protocolMainLoop(handshake bool) *tcpip.Error {
if e.state != StateError {
e.stack.Stats().TCP.EstablishedResets.Increment()
e.stack.Stats().TCP.CurrentEstablished.Decrement()
e.state = StateClose
e.transitionToStateCloseLocked()
}
// Lock released below.
epilogue()
// epilogue removes the endpoint from the transport-demuxer and
// unlocks e.mu. Now that no new segments can get enqueued to this
// endpoint, try to re-match the segment to a different endpoint
// as the current endpoint is closed.
for !e.segmentQueue.empty() {
s := e.segmentQueue.dequeue()
e.tryDeliverSegmentFromClosedEndpoint(s)
}
// 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 {
+1 -1
View File
@@ -218,7 +218,7 @@ func (r *receiver) consumeSegment(s *segment, segSeq seqnum.Value, segLen seqnum
case StateClosing:
r.ep.state = StateTimeWait
case StateLastAck:
r.ep.state = StateClose
r.ep.transitionToStateCloseLocked()
}
r.ep.mu.Unlock()
}
+196
View File
@@ -454,6 +454,112 @@ func TestConnectResetAfterClose(t *testing.T) {
}
}
// TestClosingWithEnqueuedSegments tests handling of
// still enqueued segments when the endpoint transitions
// to StateClose. The in-flight segments would be re-enqueued
// to a any listening endpoint.
func TestClosingWithEnqueuedSegments(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
c.CreateConnected(789, 30000, -1 /* epRcvBuf */)
ep := c.EP
c.EP = nil
if got, want := tcp.EndpointState(ep.State()), tcp.StateEstablished; got != want {
t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
}
// Send a FIN for ESTABLISHED --> CLOSED-WAIT
c.SendPacket(nil, &context.Headers{
SrcPort: context.TestPort,
DstPort: c.Port,
Flags: header.TCPFlagFin | header.TCPFlagAck,
SeqNum: 790,
AckNum: c.IRS.Add(1),
RcvWnd: 30000,
})
// Get the ACK for the FIN we sent.
checker.IPv4(t, c.GetPacket(),
checker.TCP(
checker.DstPort(context.TestPort),
checker.SeqNum(uint32(c.IRS)+1),
checker.AckNum(791),
checker.TCPFlags(header.TCPFlagAck),
),
)
if got, want := tcp.EndpointState(ep.State()), tcp.StateCloseWait; got != want {
t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
}
// Close the application endpoint for CLOSE_WAIT --> LAST_ACK
ep.Close()
// Get the FIN
checker.IPv4(t, c.GetPacket(),
checker.TCP(
checker.DstPort(context.TestPort),
checker.SeqNum(uint32(c.IRS)+1),
checker.AckNum(791),
checker.TCPFlags(header.TCPFlagAck|header.TCPFlagFin),
),
)
if got, want := tcp.EndpointState(ep.State()), tcp.StateLastAck; got != want {
t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
}
// Pause the endpoint`s protocolMainLoop.
ep.(interface{ StopWork() }).StopWork()
// Enqueue last ACK followed by an ACK matching the endpoint
//
// Send Last ACK for LAST_ACK --> CLOSED
c.SendPacket(nil, &context.Headers{
SrcPort: context.TestPort,
DstPort: c.Port,
Flags: header.TCPFlagAck,
SeqNum: 791,
AckNum: c.IRS.Add(2),
RcvWnd: 30000,
})
// Send a packet with ACK set, this would generate RST when
// not using SYN cookies as in this test.
c.SendPacket(nil, &context.Headers{
SrcPort: context.TestPort,
DstPort: c.Port,
Flags: header.TCPFlagAck | header.TCPFlagFin,
SeqNum: 792,
AckNum: c.IRS.Add(2),
RcvWnd: 30000,
})
// Unpause endpoint`s protocolMainLoop.
ep.(interface{ ResumeWork() }).ResumeWork()
// Wait for the protocolMainLoop to resume and update state.
time.Sleep(1 * time.Millisecond)
// Expect the endpoint to be closed.
if got, want := tcp.EndpointState(ep.State()), tcp.StateClose; got != want {
t.Errorf("Unexpected endpoint state: want %v, got %v", want, got)
}
// Check if the endpoint was moved to CLOSED and netstack a reset in
// response to the ACK packet that we sent after last-ACK.
checker.IPv4(t, c.GetPacket(),
checker.TCP(
checker.DstPort(context.TestPort),
checker.SeqNum(uint32(c.IRS)+2),
checker.AckNum(793),
checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
),
)
}
func TestSimpleReceive(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
@@ -686,6 +792,96 @@ func TestSendRstOnListenerRxSynAckV6(t *testing.T) {
checker.SeqNum(200)))
}
func TestSendRstOnListenerRxAckV4(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
c.Create(-1 /* epRcvBuf */)
if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
t.Fatal("Bind failed:", err)
}
if err := c.EP.Listen(10 /* backlog */); err != nil {
t.Fatal("Listen failed:", err)
}
c.SendPacket(nil, &context.Headers{
SrcPort: context.TestPort,
DstPort: context.StackPort,
Flags: header.TCPFlagFin | header.TCPFlagAck,
SeqNum: 100,
AckNum: 200,
})
checker.IPv4(t, c.GetPacket(), checker.TCP(
checker.DstPort(context.TestPort),
checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck),
checker.SeqNum(200)))
}
func TestSendRstOnListenerRxAckV6(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
c.CreateV6Endpoint(true /* v6Only */)
if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
t.Fatal("Bind failed:", err)
}
if err := c.EP.Listen(10 /* backlog */); err != nil {
t.Fatal("Listen failed:", err)
}
c.SendV6Packet(nil, &context.Headers{
SrcPort: context.TestPort,
DstPort: context.StackPort,
Flags: header.TCPFlagFin | header.TCPFlagAck,
SeqNum: 100,
AckNum: 200,
})
checker.IPv6(t, c.GetV6Packet(), checker.TCP(
checker.DstPort(context.TestPort),
checker.TCPFlags(header.TCPFlagRst|header.TCPFlagAck),
checker.SeqNum(200)))
}
// TestListenShutdown tests for the listening endpoint not processing
// any receive when it is on read shutdown.
func TestListenShutdown(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
c.Create(-1 /* epRcvBuf */)
if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
t.Fatal("Bind failed:", err)
}
if err := c.EP.Listen(10 /* backlog */); err != nil {
t.Fatal("Listen failed:", err)
}
if err := c.EP.Shutdown(tcpip.ShutdownRead); err != nil {
t.Fatal("Shutdown failed:", err)
}
// Wait for the endpoint state to be propagated.
time.Sleep(10 * time.Millisecond)
c.SendPacket(nil, &context.Headers{
SrcPort: context.TestPort,
DstPort: context.StackPort,
Flags: header.TCPFlagSyn,
SeqNum: 100,
AckNum: 200,
})
c.CheckNoPacket("Packet received when listening socket was shutdown")
}
func TestTOSV4(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()