Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/tcpip/transport/tcp/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ go_test(
name = "tcp_test",
size = "small",
srcs = [
"connect_test.go",
"cubic_test.go",
"main_test.go",
"segment_test.go",
Expand Down
24 changes: 19 additions & 5 deletions pkg/tcpip/transport/tcp/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -1081,12 +1081,26 @@ func (e *Endpoint) resetConnectionLocked(err tcpip.Error) {
//
// 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.snd and e.rcv may be nil if the endpoint is in a handshake
// state (e.g. SynSent) where the sender and receiver have not
// yet been initialized. Per Linux behavior, use a sequence
// number of zero when no ACK has been received (snd is nil),
// and a receive window of zero when rcv is nil since the
// connection will be immediately terminated.
var resetSeqNum seqnum.Value
var ackNum seqnum.Value
if e.snd != nil {
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
}
}
if e.rcv != nil {
ackNum = e.rcv.RcvNxt
}
e.sendEmptyRaw(header.TCPFlagAck|header.TCPFlagRst, resetSeqNum, e.rcv.RcvNxt, 0)
e.sendEmptyRaw(header.TCPFlagAck|header.TCPFlagRst, resetSeqNum, ackNum, 0)
}
// Don't purge read queues here. If there's buffered data, it's still allowed
// to be read.
Expand Down
51 changes: 51 additions & 0 deletions pkg/tcpip/transport/tcp/connect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2024 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tcp

import (
"testing"

"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/faketime"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)

// TestResetConnectionLockedNilSndRcv verifies that resetConnectionLocked does
// not panic when e.snd and e.rcv are nil, which occurs for endpoints in
// handshake states (e.g. SynSent) that have not yet initialized their sender
// and receiver. In this case, per Linux behavior, the RST is sent with a
// sequence number of zero and a receive window of zero.
func TestResetConnectionLockedNilSndRcv(t *testing.T) {
fClock := faketime.NewManualClock()
s := stack.New(stack.Options{
TransportProtocols: []stack.TransportProtocolFactory{NewProtocol},
Clock: fClock,
})

ep := &Endpoint{
stack: s,
snd: nil,
rcv: nil,
}
ep.setEndpointState(StateSynSent)

ep.mu.Lock()
ep.resetConnectionLocked(&tcpip.ErrConnectionAborted{})
ep.mu.Unlock()

if got, want := ep.EndpointState(), StateError; got != want {
t.Errorf("endpoint state after resetConnectionLocked = %v, want %v", got, want)
}
}