mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add protocol state to TCPINFO
Add missing protocol state to TCPINFO struct and update packetimpact. This re-arranges the TCP state definitions to align with Linux. Fixes #478 PiperOrigin-RevId: 374996751
This commit is contained in:
@@ -1127,7 +1127,14 @@ func getSockOptTCP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name,
|
||||
|
||||
// TODO(b/64800844): Translate fields once they are added to
|
||||
// tcpip.TCPInfoOption.
|
||||
info := linux.TCPInfo{}
|
||||
info := linux.TCPInfo{
|
||||
State: uint8(v.State),
|
||||
RTO: uint32(v.RTO / time.Microsecond),
|
||||
RTT: uint32(v.RTT / time.Microsecond),
|
||||
RTTVar: uint32(v.RTTVar / time.Microsecond),
|
||||
SndSsthresh: v.SndSsthresh,
|
||||
SndCwnd: v.SndCwnd,
|
||||
}
|
||||
switch v.CcState {
|
||||
case tcpip.RTORecovery:
|
||||
info.CaState = linux.TCP_CA_Loss
|
||||
@@ -1138,11 +1145,6 @@ func getSockOptTCP(t *kernel.Task, s socket.SocketOps, ep commonEndpoint, name,
|
||||
case tcpip.Open:
|
||||
info.CaState = linux.TCP_CA_Open
|
||||
}
|
||||
info.RTO = uint32(v.RTO / time.Microsecond)
|
||||
info.RTT = uint32(v.RTT / time.Microsecond)
|
||||
info.RTTVar = uint32(v.RTTVar / time.Microsecond)
|
||||
info.SndSsthresh = v.SndSsthresh
|
||||
info.SndCwnd = v.SndCwnd
|
||||
|
||||
// In netstack reorderSeen is updated only when RACK is enabled.
|
||||
// We only track whether the reordering is seen, which is
|
||||
|
||||
@@ -861,6 +861,9 @@ type SettableSocketOption interface {
|
||||
isSettableSocketOption()
|
||||
}
|
||||
|
||||
// EndpointState represents the state of an endpoint.
|
||||
type EndpointState uint8
|
||||
|
||||
// CongestionControlState indicates the current congestion control state for
|
||||
// TCP sender.
|
||||
type CongestionControlState int
|
||||
@@ -897,6 +900,9 @@ type TCPInfoOption struct {
|
||||
// RTO is the retransmission timeout for the endpoint.
|
||||
RTO time.Duration
|
||||
|
||||
// State is the current endpoint protocol state.
|
||||
State EndpointState
|
||||
|
||||
// CcState is the congestion control state.
|
||||
CcState CongestionControlState
|
||||
|
||||
|
||||
@@ -1235,7 +1235,7 @@ func (e *endpoint) handleSegmentLocked(s *segment) (cont bool, err tcpip.Error)
|
||||
// Now check if the received segment has caused us to transition
|
||||
// to a CLOSED state, if yes then terminate processing and do
|
||||
// not invoke the sender.
|
||||
state := e.state
|
||||
state := e.EndpointState()
|
||||
if state == StateClose {
|
||||
// When we get into StateClose while processing from the queue,
|
||||
// return immediately and let the protocolMainloop handle it.
|
||||
|
||||
@@ -38,19 +38,15 @@ import (
|
||||
)
|
||||
|
||||
// EndpointState represents the state of a TCP endpoint.
|
||||
type EndpointState uint32
|
||||
type EndpointState tcpip.EndpointState
|
||||
|
||||
// Endpoint states. Note that are represented in a netstack-specific manner and
|
||||
// may not be meaningful externally. Specifically, they need to be translated to
|
||||
// Linux's representation for these states if presented to userspace.
|
||||
const (
|
||||
// Endpoint states internal to netstack. These map to the TCP state CLOSED.
|
||||
StateInitial EndpointState = iota
|
||||
StateBound
|
||||
StateConnecting // Connect() called, but the initial SYN hasn't been sent.
|
||||
StateError
|
||||
|
||||
// TCP protocol states.
|
||||
_ EndpointState = iota
|
||||
// TCP protocol states in sync with the definitions in
|
||||
// https://github.com/torvalds/linux/blob/7acac4b3196/include/net/tcp_states.h#L13
|
||||
StateEstablished
|
||||
StateSynSent
|
||||
StateSynRecv
|
||||
@@ -62,6 +58,12 @@ const (
|
||||
StateLastAck
|
||||
StateListen
|
||||
StateClosing
|
||||
|
||||
// Endpoint states internal to netstack.
|
||||
StateInitial
|
||||
StateBound
|
||||
StateConnecting // Connect() called, but the initial SYN hasn't been sent.
|
||||
StateError
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -97,6 +99,16 @@ func (s EndpointState) connecting() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// internal returns true when the state is netstack internal.
|
||||
func (s EndpointState) internal() bool {
|
||||
switch s {
|
||||
case StateInitial, StateBound, StateConnecting, StateError:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// handshake returns true when s is one of the states representing an endpoint
|
||||
// in the middle of a TCP handshake.
|
||||
func (s EndpointState) handshake() bool {
|
||||
@@ -422,12 +434,12 @@ type endpoint struct {
|
||||
|
||||
// state must be read/set using the EndpointState()/setEndpointState()
|
||||
// methods.
|
||||
state EndpointState `state:".(EndpointState)"`
|
||||
state uint32 `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"`
|
||||
origEndpointState uint32 `state:"nosave"`
|
||||
|
||||
isPortReserved bool `state:"manual"`
|
||||
isRegistered bool `state:"manual"`
|
||||
@@ -747,7 +759,7 @@ func (e *endpoint) ResumeWork() {
|
||||
//
|
||||
// Precondition: e.mu must be held to call this method.
|
||||
func (e *endpoint) setEndpointState(state EndpointState) {
|
||||
oldstate := EndpointState(atomic.LoadUint32((*uint32)(&e.state)))
|
||||
oldstate := EndpointState(atomic.LoadUint32(&e.state))
|
||||
switch state {
|
||||
case StateEstablished:
|
||||
e.stack.Stats().TCP.CurrentEstablished.Increment()
|
||||
@@ -764,12 +776,12 @@ func (e *endpoint) setEndpointState(state EndpointState) {
|
||||
e.stack.Stats().TCP.CurrentEstablished.Decrement()
|
||||
}
|
||||
}
|
||||
atomic.StoreUint32((*uint32)(&e.state), uint32(state))
|
||||
atomic.StoreUint32(&e.state, uint32(state))
|
||||
}
|
||||
|
||||
// EndpointState returns the current state of the endpoint.
|
||||
func (e *endpoint) EndpointState() EndpointState {
|
||||
return EndpointState(atomic.LoadUint32((*uint32)(&e.state)))
|
||||
return EndpointState(atomic.LoadUint32(&e.state))
|
||||
}
|
||||
|
||||
// setRecentTimestamp sets the recentTS field to the provided value.
|
||||
@@ -810,7 +822,7 @@ func newEndpoint(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, waiterQue
|
||||
},
|
||||
},
|
||||
waiterQueue: waiterQueue,
|
||||
state: StateInitial,
|
||||
state: uint32(StateInitial),
|
||||
keepalive: keepalive{
|
||||
// Linux defaults.
|
||||
idle: 2 * time.Hour,
|
||||
@@ -1956,6 +1968,11 @@ func (e *endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
|
||||
func (e *endpoint) getTCPInfo() tcpip.TCPInfoOption {
|
||||
info := tcpip.TCPInfoOption{}
|
||||
e.LockUser()
|
||||
if state := e.EndpointState(); state.internal() {
|
||||
info.State = tcpip.EndpointState(StateClose)
|
||||
} else {
|
||||
info.State = tcpip.EndpointState(state)
|
||||
}
|
||||
snd := e.snd
|
||||
if snd != nil {
|
||||
// We do not calculate RTT before sending the data packets. If
|
||||
|
||||
@@ -154,7 +154,7 @@ func (e *endpoint) afterLoad() {
|
||||
e.origEndpointState = e.state
|
||||
// Restore the endpoint to InitialState as it will be moved to
|
||||
// its origEndpointState during Resume.
|
||||
e.state = StateInitial
|
||||
e.state = uint32(StateInitial)
|
||||
// Condition variables and mutexs are not S/R'ed so reinitialize
|
||||
// acceptCond with e.acceptMu.
|
||||
e.acceptCond = sync.NewCond(&e.acceptMu)
|
||||
@@ -167,7 +167,7 @@ func (e *endpoint) Resume(s *stack.Stack) {
|
||||
e.stack = s
|
||||
e.ops.InitHandler(e, e.stack, GetTCPSendBufferLimits, GetTCPReceiveBufferLimits)
|
||||
e.segmentQueue.thaw()
|
||||
epState := e.origEndpointState
|
||||
epState := EndpointState(e.origEndpointState)
|
||||
switch epState {
|
||||
case StateInitial, StateBound, StateListen, StateConnecting, StateEstablished:
|
||||
var ss tcpip.TCPSendBufferSizeRangeOption
|
||||
@@ -281,11 +281,11 @@ func (e *endpoint) Resume(s *stack.Stack) {
|
||||
}()
|
||||
case epState == StateClose:
|
||||
e.isPortReserved = false
|
||||
e.state = StateClose
|
||||
e.state = uint32(StateClose)
|
||||
e.stack.CompleteTransportEndpointCleanup(e)
|
||||
tcpip.DeleteDanglingEndpoint(e)
|
||||
case epState == StateError:
|
||||
e.state = StateError
|
||||
e.state = uint32(StateError)
|
||||
e.stack.CompleteTransportEndpointCleanup(e)
|
||||
tcpip.DeleteDanglingEndpoint(e)
|
||||
}
|
||||
|
||||
@@ -40,13 +40,14 @@ type udpPacket struct {
|
||||
}
|
||||
|
||||
// EndpointState represents the state of a UDP endpoint.
|
||||
type EndpointState uint32
|
||||
type EndpointState tcpip.EndpointState
|
||||
|
||||
// Endpoint states. Note that are represented in a netstack-specific manner and
|
||||
// may not be meaningful externally. Specifically, they need to be translated to
|
||||
// Linux's representation for these states if presented to userspace.
|
||||
const (
|
||||
StateInitial EndpointState = iota
|
||||
_ EndpointState = iota
|
||||
StateInitial
|
||||
StateBound
|
||||
StateConnected
|
||||
StateClosed
|
||||
@@ -98,7 +99,7 @@ type endpoint struct {
|
||||
mu sync.RWMutex `state:"nosave"`
|
||||
// state must be read/set using the EndpointState()/setEndpointState()
|
||||
// methods.
|
||||
state EndpointState
|
||||
state uint32
|
||||
route *stack.Route `state:"manual"`
|
||||
dstPort uint16
|
||||
ttl uint8
|
||||
@@ -176,7 +177,7 @@ func newEndpoint(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, waiterQue
|
||||
// Linux defaults to TTL=1.
|
||||
multicastTTL: 1,
|
||||
multicastMemberships: make(map[multicastMembership]struct{}),
|
||||
state: StateInitial,
|
||||
state: uint32(StateInitial),
|
||||
uniqueID: s.UniqueID(),
|
||||
}
|
||||
e.ops.InitHandler(e, e.stack, tcpip.GetStackSendBufferLimits, tcpip.GetStackReceiveBufferLimits)
|
||||
@@ -204,12 +205,12 @@ func newEndpoint(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, waiterQue
|
||||
//
|
||||
// Precondition: e.mu must be held to call this method.
|
||||
func (e *endpoint) setEndpointState(state EndpointState) {
|
||||
atomic.StoreUint32((*uint32)(&e.state), uint32(state))
|
||||
atomic.StoreUint32(&e.state, uint32(state))
|
||||
}
|
||||
|
||||
// EndpointState() returns the current state of the endpoint.
|
||||
func (e *endpoint) EndpointState() EndpointState {
|
||||
return EndpointState(atomic.LoadUint32((*uint32)(&e.state)))
|
||||
return EndpointState(atomic.LoadUint32(&e.state))
|
||||
}
|
||||
|
||||
// UniqueID implements stack.TransportEndpoint.UniqueID.
|
||||
|
||||
@@ -90,7 +90,7 @@ func (r *ForwarderRequest) CreateEndpoint(queue *waiter.Queue) (tcpip.Endpoint,
|
||||
ep.RegisterNICID = r.pkt.NICID
|
||||
ep.boundPortFlags = ep.portFlags
|
||||
|
||||
ep.state = StateConnected
|
||||
ep.state = uint32(StateConnected)
|
||||
|
||||
ep.rcvMu.Lock()
|
||||
ep.rcvReady = true
|
||||
|
||||
@@ -16,6 +16,8 @@ go_library(
|
||||
],
|
||||
visibility = ["//test/packetimpact:__subpackages__"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/binary",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/tcpip",
|
||||
"//pkg/tcpip/buffer",
|
||||
|
||||
@@ -22,11 +22,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
bin "gvisor.dev/gvisor/pkg/binary"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto"
|
||||
)
|
||||
|
||||
// DUT communicates with the DUT to force it to make POSIX calls.
|
||||
@@ -428,6 +430,33 @@ func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, t *testing.T, so
|
||||
return ret, timeval, errno
|
||||
}
|
||||
|
||||
// GetSockOptTCPInfo retreives TCPInfo for the given socket descriptor.
|
||||
func (dut *DUT) GetSockOptTCPInfo(t *testing.T, sockfd int32) linux.TCPInfo {
|
||||
t.Helper()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
|
||||
defer cancel()
|
||||
ret, info, err := dut.GetSockOptTCPInfoWithErrno(ctx, t, sockfd)
|
||||
if ret != 0 || err != unix.Errno(0) {
|
||||
t.Fatalf("failed to GetSockOptTCPInfo: %s", err)
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// GetSockOptTCPInfoWithErrno retreives TCPInfo with any errno.
|
||||
func (dut *DUT) GetSockOptTCPInfoWithErrno(ctx context.Context, t *testing.T, sockfd int32) (int32, linux.TCPInfo, error) {
|
||||
t.Helper()
|
||||
|
||||
info := linux.TCPInfo{}
|
||||
ret, infoBytes, errno := dut.GetSockOptWithErrno(ctx, t, sockfd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
|
||||
if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
|
||||
t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
|
||||
}
|
||||
bin.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
|
||||
|
||||
return ret, info, errno
|
||||
}
|
||||
|
||||
// Listen calls listen on the DUT and causes a fatal test failure if it doesn't
|
||||
// succeed. If more control over the timeout or error handling is needed, use
|
||||
// ListenWithErrno.
|
||||
|
||||
@@ -104,8 +104,6 @@ packetimpact_testbench(
|
||||
srcs = ["tcp_retransmits_test.go"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/binary",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/tcpip/header",
|
||||
"//test/packetimpact/testbench",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
@@ -189,6 +187,7 @@ packetimpact_testbench(
|
||||
name = "tcp_synsent_reset",
|
||||
srcs = ["tcp_synsent_reset_test.go"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/tcpip/header",
|
||||
"//test/packetimpact/testbench",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
@@ -353,8 +352,6 @@ packetimpact_testbench(
|
||||
srcs = ["tcp_rack_test.go"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/binary",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/tcpip/header",
|
||||
"//pkg/tcpip/seqnum",
|
||||
"//test/packetimpact/testbench",
|
||||
@@ -367,8 +364,6 @@ packetimpact_testbench(
|
||||
srcs = ["tcp_info_test.go"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/binary",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/tcpip/header",
|
||||
"//test/packetimpact/testbench",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
|
||||
@@ -21,8 +21,6 @@ import (
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/binary"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/test/packetimpact/testbench"
|
||||
)
|
||||
@@ -53,13 +51,10 @@ func TestTCPInfo(t *testing.T) {
|
||||
}
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
|
||||
|
||||
info := linux.TCPInfo{}
|
||||
infoBytes := dut.GetSockOpt(t, acceptFD, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
|
||||
if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
|
||||
t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
|
||||
info := dut.GetSockOptTCPInfo(t, acceptFD)
|
||||
if got, want := uint32(info.State), linux.TCP_ESTABLISHED; got != want {
|
||||
t.Fatalf("got %d want %d", got, want)
|
||||
}
|
||||
binary.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
|
||||
|
||||
rtt := time.Duration(info.RTT) * time.Microsecond
|
||||
rttvar := time.Duration(info.RTTVar) * time.Microsecond
|
||||
rto := time.Duration(info.RTO) * time.Microsecond
|
||||
@@ -94,12 +89,7 @@ func TestTCPInfo(t *testing.T) {
|
||||
t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
|
||||
}
|
||||
|
||||
info = linux.TCPInfo{}
|
||||
infoBytes = dut.GetSockOpt(t, acceptFD, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
|
||||
if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
|
||||
t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
|
||||
}
|
||||
binary.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
|
||||
info = dut.GetSockOptTCPInfo(t, acceptFD)
|
||||
if info.CaState != linux.TCP_CA_Loss {
|
||||
t.Errorf("expected the connection to be in loss recovery, got: %v want: %v", info.CaState, linux.TCP_CA_Loss)
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ import (
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/binary"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/seqnum"
|
||||
"gvisor.dev/gvisor/test/packetimpact/testbench"
|
||||
@@ -69,12 +67,7 @@ func closeSACKConnection(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4
|
||||
}
|
||||
|
||||
func getRTTAndRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rtt, rto time.Duration) {
|
||||
info := linux.TCPInfo{}
|
||||
infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
|
||||
if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
|
||||
t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
|
||||
}
|
||||
binary.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
|
||||
info := dut.GetSockOptTCPInfo(t, acceptFd)
|
||||
return time.Duration(info.RTT) * time.Microsecond, time.Duration(info.RTO) * time.Microsecond
|
||||
}
|
||||
|
||||
@@ -402,12 +395,7 @@ func TestRACKWithLostRetransmission(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check the congestion control state.
|
||||
info := linux.TCPInfo{}
|
||||
infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
|
||||
if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
|
||||
t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
|
||||
}
|
||||
binary.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
|
||||
info := dut.GetSockOptTCPInfo(t, acceptFd)
|
||||
if info.CaState != linux.TCP_CA_Recovery {
|
||||
t.Fatalf("expected connection to be in fast recovery, want: %v got: %v", linux.TCP_CA_Recovery, info.CaState)
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/binary"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/test/packetimpact/testbench"
|
||||
)
|
||||
@@ -33,12 +30,7 @@ func init() {
|
||||
}
|
||||
|
||||
func getRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rto time.Duration) {
|
||||
info := linux.TCPInfo{}
|
||||
infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
|
||||
if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
|
||||
t.Fatalf("unexpected size for TCP_INFO, got %d bytes want %d bytes", got, want)
|
||||
}
|
||||
binary.Unmarshal(infoBytes, hostarch.ByteOrder, &info)
|
||||
info := dut.GetSockOptTCPInfo(t, acceptFd)
|
||||
return time.Duration(info.RTO) * time.Microsecond
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/test/packetimpact/testbench"
|
||||
)
|
||||
@@ -29,7 +30,7 @@ func init() {
|
||||
}
|
||||
|
||||
// dutSynSentState sets up the dut connection in SYN-SENT state.
|
||||
func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, uint16, uint16) {
|
||||
func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, int32, uint16, uint16) {
|
||||
t.Helper()
|
||||
|
||||
dut := testbench.NewDUT(t)
|
||||
@@ -46,26 +47,29 @@ func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, uint16,
|
||||
t.Fatalf("expected SYN\n")
|
||||
}
|
||||
|
||||
return &dut, &conn, port, clientPort
|
||||
return &dut, &conn, clientFD, port, clientPort
|
||||
}
|
||||
|
||||
// TestTCPSynSentReset tests RFC793, p67: SYN-SENT to CLOSED transition.
|
||||
func TestTCPSynSentReset(t *testing.T) {
|
||||
_, conn, _, _ := dutSynSentState(t)
|
||||
dut, conn, fd, _, _ := dutSynSentState(t)
|
||||
defer conn.Close(t)
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)})
|
||||
// Expect the connection to have closed.
|
||||
// TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side.
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
|
||||
if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil {
|
||||
t.Fatalf("expected a TCP RST")
|
||||
}
|
||||
info := dut.GetSockOptTCPInfo(t, fd)
|
||||
if got, want := uint32(info.State), linux.TCP_CLOSE; got != want {
|
||||
t.Fatalf("got %d want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTCPSynSentRcvdReset tests RFC793, p70, SYN-SENT to SYN-RCVD to CLOSED
|
||||
// transitions.
|
||||
func TestTCPSynSentRcvdReset(t *testing.T) {
|
||||
dut, c, remotePort, clientPort := dutSynSentState(t)
|
||||
dut, c, fd, remotePort, clientPort := dutSynSentState(t)
|
||||
defer c.Close(t)
|
||||
|
||||
conn := dut.Net.NewTCPIPv4(t, testbench.TCP{SrcPort: &remotePort, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &remotePort})
|
||||
@@ -79,9 +83,12 @@ func TestTCPSynSentRcvdReset(t *testing.T) {
|
||||
}
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)})
|
||||
// Expect the connection to have transitioned SYN-RCVD to CLOSED.
|
||||
// TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side.
|
||||
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)})
|
||||
if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil {
|
||||
t.Fatalf("expected a TCP RST")
|
||||
}
|
||||
info := dut.GetSockOptTCPInfo(t, fd)
|
||||
if got, want := uint32(info.State), linux.TCP_CLOSE; got != want {
|
||||
t.Fatalf("got %d want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,6 +472,77 @@ TEST_P(SocketInetLoopbackTest, TCPListenClose) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test the protocol state information returned by TCPINFO.
|
||||
TEST_P(SocketInetLoopbackTest, TCPInfoState) {
|
||||
auto const& param = GetParam();
|
||||
TestAddress const& listener = param.listener;
|
||||
TestAddress const& connector = param.connector;
|
||||
|
||||
// Create the listening socket.
|
||||
FileDescriptor const listen_fd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Socket(listener.family(), SOCK_STREAM, IPPROTO_TCP));
|
||||
|
||||
auto state = [](int fd) -> int {
|
||||
struct tcp_info opt = {};
|
||||
socklen_t optLen = sizeof(opt);
|
||||
EXPECT_THAT(getsockopt(fd, SOL_TCP, TCP_INFO, &opt, &optLen),
|
||||
SyscallSucceeds());
|
||||
return opt.tcpi_state;
|
||||
};
|
||||
ASSERT_EQ(state(listen_fd.get()), TCP_CLOSE);
|
||||
|
||||
sockaddr_storage listen_addr = listener.addr;
|
||||
ASSERT_THAT(
|
||||
bind(listen_fd.get(), AsSockAddr(&listen_addr), listener.addr_len),
|
||||
SyscallSucceeds());
|
||||
ASSERT_EQ(state(listen_fd.get()), TCP_CLOSE);
|
||||
|
||||
ASSERT_THAT(listen(listen_fd.get(), SOMAXCONN), SyscallSucceeds());
|
||||
ASSERT_EQ(state(listen_fd.get()), TCP_LISTEN);
|
||||
|
||||
// Get the port bound by the listening socket.
|
||||
socklen_t addrlen = listener.addr_len;
|
||||
ASSERT_THAT(getsockname(listen_fd.get(), AsSockAddr(&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_EQ(state(conn_fd.get()), TCP_CLOSE);
|
||||
ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(), AsSockAddr(&conn_addr),
|
||||
connector.addr_len),
|
||||
SyscallSucceeds());
|
||||
ASSERT_EQ(state(conn_fd.get()), TCP_ESTABLISHED);
|
||||
|
||||
auto accepted =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Accept(listen_fd.get(), nullptr, nullptr));
|
||||
ASSERT_EQ(state(accepted.get()), TCP_ESTABLISHED);
|
||||
|
||||
ASSERT_THAT(close(accepted.release()), SyscallSucceeds());
|
||||
|
||||
struct pollfd pfd = {
|
||||
.fd = conn_fd.get(),
|
||||
.events = POLLIN | POLLRDHUP,
|
||||
};
|
||||
constexpr int kTimeout = 10000;
|
||||
int n = poll(&pfd, 1, kTimeout);
|
||||
ASSERT_GE(n, 0) << strerror(errno);
|
||||
ASSERT_EQ(n, 1);
|
||||
if (IsRunningOnGvisor()) {
|
||||
// TODO(gvisor.dev/issue/6015): Notify POLLRDHUP on incoming FIN.
|
||||
ASSERT_EQ(pfd.revents, POLLIN);
|
||||
} else {
|
||||
ASSERT_EQ(pfd.revents, POLLIN | POLLRDHUP);
|
||||
}
|
||||
|
||||
ASSERT_THAT(state(conn_fd.get()), TCP_CLOSE_WAIT);
|
||||
ASSERT_THAT(close(conn_fd.release()), SyscallSucceeds());
|
||||
}
|
||||
|
||||
void TestHangupDuringConnect(const TestParam& param,
|
||||
void (*hangup)(FileDescriptor&)) {
|
||||
TestAddress const& listener = param.listener;
|
||||
|
||||
Reference in New Issue
Block a user