Skip to content

Commit

Permalink
fix for TCB transition to TimeWait on FINACK and documentation fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Mar 15, 2024
1 parent f691c08 commit f56ae5f
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 13 deletions.
31 changes: 21 additions & 10 deletions control.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,22 @@ func (tcb *ControlBlock) rcvEstablished(seg Segment) (pending Flags, err error)

func (tcb *ControlBlock) rcvFinWait1(seg Segment) (pending Flags, err error) {
flags := seg.Flags
if !flags.HasAny(FlagACK) {
return 0, errFinwaitExpectedACK
} else if flags.HasAny(FlagFIN) {
tcb.state = StateClosing // Simultaneous close. See figure 13 of RFC 9293.
pending = FlagACK
} else {
hasFin := flags&FlagFIN != 0
hasAck := flags&FlagACK != 0
switch {
case hasFin && hasAck && seg.ACK == tcb.snd.NXT:
// Special case: Server sent a FINACK response to our FIN so we enter TimeWait directly.
// We have to check ACK against send NXT to avoid simultaneous close sequence edge case.
tcb.state = StateTimeWait
case hasFin:
tcb.state = StateClosing
case hasAck:
// TODO(soypat): Check if this branch does NOT need ACK queued. Online flowcharts say not needed.
tcb.state = StateFinWait2
pending = FlagACK
default:
return 0, errFinwaitExpectedACK
}
pending = FlagACK
return pending, nil
}

Expand Down Expand Up @@ -409,16 +416,20 @@ func (tcb *ControlBlock) logenabled(lvl slog.Level) bool {
return internal.HeapAllocDebugging || (tcb.log != nil && tcb.log.Handler().Enabled(context.Background(), lvl))
}

func (tcb *ControlBlock) logattrs(lvl slog.Level, msg string, attrs ...slog.Attr) {
internal.LogAttrs(tcb.log, lvl, msg, attrs...)
}

func (tcb *ControlBlock) debug(msg string, attrs ...slog.Attr) {
internal.LogAttrs(tcb.log, slog.LevelDebug, msg, attrs...)
tcb.logattrs(slog.LevelDebug, msg, attrs...)
}

func (tcb *ControlBlock) trace(msg string, attrs ...slog.Attr) {
internal.LogAttrs(tcb.log, internal.LevelTrace, msg, attrs...)
tcb.logattrs(internal.LevelTrace, msg, attrs...)
}

func (tcb *ControlBlock) logerr(msg string, attrs ...slog.Attr) {
internal.LogAttrs(tcb.log, slog.LevelError, msg, attrs...)
tcb.logattrs(slog.LevelError, msg, attrs...)
}

func (tcb *ControlBlock) traceSnd(msg string) {
Expand Down
4 changes: 4 additions & 0 deletions control_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ func (tcb *ControlBlock) Open(iss Value, wnd Size, state State) (err error) {
return nil
}

// Close implements a passive/active closing of a connection. It does not immediately
// delete the TCB but initiates the process so that pending outgoing segments initiate
// the closing process. After a call to Close users should not send more data.
// Close returns an error if the connection is already closed or closing.
func (tcb *ControlBlock) Close() (err error) {
// See RFC 9293: 3.10.4 CLOSE call.
switch tcb.state {
Expand Down
2 changes: 1 addition & 1 deletion eth/dns/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const (
// SizeHeader is the length (in bytes) of a DNS header.
// A header is comprised of 6 uint16s and no padding.
SizeHeader = 6 * 2
// The Internet supports name server access using TCP [RFC-793] on server
// The Internet supports name server access using TCP [RFC-9293] on server
// port 53 (decimal) as well as datagram access using UDP [RFC-768] on UDP port 53 (decimal).
ServerPort = 53
ClientPort = 53
Expand Down
40 changes: 40 additions & 0 deletions seqs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,46 @@ func TestResetEstablished(t *testing.T) {
checkNoPending(t, &tcb)
}

func TestFinackClose(t *testing.T) {
var tcb seqs.ControlBlock
const windowA, windowB = 502, 4096
const issA, issB = 100, 200
tcb.HelperInitState(seqs.StateEstablished, issA, issA, windowA)
tcb.HelperInitRcv(issB, issB, windowB)
// Start closing process.
err := tcb.Close()
if err != nil {
t.Fatal(err)
}
seg, ok := tcb.PendingSegment(0)
if !ok {
t.Fatal("expected pending segment")
}
if !seg.Flags.HasAll(seqs.FlagFIN | seqs.FlagACK) {
t.Fatalf("expected FIN|ACK; got %s", seg.Flags.String())
}
err = tcb.Send(seg)
if err != nil {
t.Fatal(err)
}
if tcb.State() != seqs.StateFinWait1 {
t.Fatalf("expected FinWait1; got %s", tcb.State().String())
}
// Special case where we receive FINACK all together, we can streamline and go into TimeWait.
err = tcb.Recv(seqs.Segment{
SEQ: issB,
ACK: issA + 1,
WND: windowB,
Flags: FINACK,
})
if err != nil {
t.Fatal(err)
}
if tcb.State() != seqs.StateTimeWait {
t.Fatalf("expected TimeWait after FINACK; got %s", tcb.State().String())
}
}

func TestExchange_helloworld_client(t *testing.T) {
return
// Client Transmission Control Block.
Expand Down
5 changes: 3 additions & 2 deletions stacks/tcpconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,9 @@ func (sock *TCPConn) stateCheck() (portStackErr error) {
sock.debug("TCP:delayed-close", slog.Uint64("port", uint64(sock.localPort)))
} else {
now := sock.stack.now()
if now.Sub(sock.lastTx) > 3*time.Second {
sock.logerr("TCP:idleabort")
elapsed := now.Sub(sock.lastTx)
if elapsed > 3*time.Second {
sock.logerr("TCP:idleabort", slog.Duration("elapsed", elapsed))
return io.EOF // Abort connection- no response from remote.
}
}
Expand Down

0 comments on commit f56ae5f

Please sign in to comment.