Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions pkg/abi/linux/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ const (
MAX_TCP_KEEPCNT = 127
)

// tcp_info.tcpi_options bits from include/uapi/linux/tcp.h.
const (
TCPI_OPT_TIMESTAMPS = 1
TCPI_OPT_SACK = 2
)

// Congestion control states from include/uapi/linux/tcp.h.
const (
TCP_CA_Open = 0
Expand Down
6 changes: 6 additions & 0 deletions pkg/sentry/socket/netstack/netstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,12 @@ func (s *sock) getSockOptTCP(t *kernel.Task, ep commonEndpoint, name, outLen int
SndCwnd: v.SndCwnd,
SndMss: v.SndMss,
}
if v.Timestamps {
info.Options |= linux.TCPI_OPT_TIMESTAMPS
}
if v.SACKPermitted {
info.Options |= linux.TCPI_OPT_SACK
}
switch v.CcState {
case tcpip.RTORecovery:
info.CaState = linux.TCP_CA_Loss
Expand Down
6 changes: 6 additions & 0 deletions pkg/tcpip/tcpip.go
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,12 @@ type TCPInfoOption struct {

// ReorderSeen indicates if reordering is seen in the endpoint.
ReorderSeen bool

// Timestamps is true if TCP timestamps were negotiated.
Timestamps bool

// SACKPermitted is true if selective acknowledgments were negotiated.
SACKPermitted bool
}

func (*TCPInfoOption) isGettableSocketOption() {}
Expand Down
5 changes: 4 additions & 1 deletion pkg/tcpip/transport/tcp/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -2220,8 +2220,11 @@ func (e *Endpoint) GetSockOptInt(opt tcpip.SockOptInt) (int, tcpip.Error) {
// +checklocksexclude:e.segmentQueue.mu
// +checklocksexclude:e.pendingProcessingMu
func (e *Endpoint) getTCPInfo() tcpip.TCPInfoOption {
info := tcpip.TCPInfoOption{}
e.LockUser()
info := tcpip.TCPInfoOption{
Timestamps: e.SendTSOk,
SACKPermitted: e.SACKPermitted,
}
if state := e.EndpointState(); state.internal() {
info.State = tcpip.EndpointState(StateClose)
} else {
Expand Down
51 changes: 51 additions & 0 deletions test/packetimpact/tests/tcp_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,57 @@ func init() {
testbench.Initialize(flag.CommandLine)
}

func TestTCPInfoOptions(t *testing.T) {
dut := testbench.NewDUT(t)
listenFD, port := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
defer dut.Close(t, listenFD)

const mask = linux.TCPI_OPT_TIMESTAMPS | linux.TCPI_OPT_SACK
for _, tt := range []struct {
name string
want uint8
}{
{"Neither", 0},
{"Timestamps", linux.TCPI_OPT_TIMESTAMPS},
{"SACK", linux.TCPI_OPT_SACK},
{"Both", mask},
} {
t.Run(tt.name, func(t *testing.T) {
conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &port}, testbench.TCP{SrcPort: &port})
defer conn.Close(t)
var opts [header.TCPOptionsMaximumSize]byte
n := 0
ts, sack := tt.want&linux.TCPI_OPT_TIMESTAMPS != 0, tt.want&linux.TCPI_OPT_SACK != 0
if ts {
n += header.EncodeTSOption(1, 0, opts[n:])
}
if sack {
n += header.EncodeSACKPermittedOption(opts[n:])
}
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn), Options: opts[:n]})
synAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second)
if err != nil {
t.Fatalf("expect SYN-ACK: %v", err)
}
negotiated := header.ParseSynOptions(synAck.Options, true /* isAck */)
if negotiated.TS != ts || negotiated.SACKPermitted != sack {
t.Fatalf("SYN-ACK timestamps=%t SACK=%t, want %t and %t", negotiated.TS, negotiated.SACKPermitted, ts, sack)
}
n = 0
if negotiated.TS {
n = header.EncodeTSOption(2, negotiated.TSVal, opts[:])
}
conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: opts[:n]})
dut.PollOne(t, listenFD, unix.POLLIN, time.Second)
fd, _ := dut.Accept(t, listenFD)
defer dut.Close(t, fd)
if got := dut.GetSockOptTCPInfo(t, fd).Options & mask; got != tt.want {
t.Errorf("TCP_INFO timestamps/SACK bits = %#x, want %#x from SYN-ACK", got, tt.want)
}
})
}
}

func TestTCPInfo(t *testing.T) {
// Create a socket, listen, TCP connect, and accept.
dut := testbench.NewDUT(t)
Expand Down
Loading