Fix data race in tcp_test.

This change makes SynRcvdCountThreshold and the global synRcvdCount into a stack
configurable value. This is required because in cases like mod_proxy which
create multiple Stack instances the count will be a global value that impacts
all Stack instances.

Further the tests relied on modifying the global threshold to simulate tests
where we want to verify SYN cookie based behaviour. This lead to data races due
to the global being modified/read without locks or atomics.

PiperOrigin-RevId: 306947723
This commit is contained in:
Bhasker Hariharan
2020-04-16 16:49:18 -07:00
committed by gVisor bot
parent 75e864fc75
commit 0eda0104a5
7 changed files with 177 additions and 123 deletions
+5
View File
@@ -685,6 +685,11 @@ type TCPDeferAcceptOption time.Duration
// default MinRTO used by the Stack.
type TCPMinRTOOption time.Duration
// TCPSynRcvdCountThresholdOption is used by SetSockOpt/GetSockOpt to specify
// the number of endpoints that can be in SYN-RCVD state before the stack
// switches to using SYN cookies.
type TCPSynRcvdCountThresholdOption uint64
// MulticastInterfaceOption is used by SetSockOpt/GetSockOpt to specify a
// default interface for multicast.
type MulticastInterfaceOption struct {
+42 -64
View File
@@ -17,6 +17,7 @@ package tcp
import (
"crypto/sha1"
"encoding/binary"
"fmt"
"hash"
"io"
"time"
@@ -49,17 +50,14 @@ const (
// timestamp and the current timestamp. If the difference is greater
// than maxTSDiff, the cookie is expired.
maxTSDiff = 2
// SynRcvdCountThreshold is the default global maximum number of
// connections that are allowed to be in SYN-RCVD state before TCP
// starts using SYN cookies to accept connections.
SynRcvdCountThreshold uint64 = 1000
)
var (
// SynRcvdCountThreshold is the global maximum number of connections
// that are allowed to be in SYN-RCVD state before TCP starts using SYN
// cookies to accept connections.
//
// It is an exported variable only for testing, and should not otherwise
// be used by importers of this package.
SynRcvdCountThreshold uint64 = 1000
// mssTable is a slice containing the possible MSS values that we
// encode in the SYN cookie with two bits.
mssTable = []uint16{536, 1300, 1440, 1460}
@@ -74,29 +72,42 @@ func encodeMSS(mss uint16) uint32 {
return 0
}
// syncRcvdCount is the number of endpoints in the SYN-RCVD state. The value is
// protected by a mutex so that we can increment only when it's guaranteed not
// to go above a threshold.
var synRcvdCount struct {
sync.Mutex
value uint64
pending sync.WaitGroup
}
// listenContext is used by a listening endpoint to store state used while
// listening for connections. This struct is allocated by the listen goroutine
// and must not be accessed or have its methods called concurrently as they
// may mutate the stored objects.
type listenContext struct {
stack *stack.Stack
rcvWnd seqnum.Size
nonce [2][sha1.BlockSize]byte
stack *stack.Stack
// synRcvdCount is a reference to the stack level synRcvdCount.
synRcvdCount *synRcvdCounter
// rcvWnd is the receive window that is sent by this listening context
// in the initial SYN-ACK.
rcvWnd seqnum.Size
// nonce are random bytes that are initialized once when the context
// is created and used to seed the hash function when generating
// the SYN cookie.
nonce [2][sha1.BlockSize]byte
// listenEP is a reference to the listening endpoint associated with
// this context. Can be nil if the context is created by the forwarder.
listenEP *endpoint
// hasherMu protects hasher.
hasherMu sync.Mutex
hasher hash.Hash
v6only bool
// hasher is the hash function used to generate a SYN cookie.
hasher hash.Hash
// v6Only is true if listenEP is a dual stack socket and has the
// IPV6_V6ONLY option set.
v6only bool
// netProto indicates the network protocol(IPv4/v6) for the listening
// endpoint.
netProto tcpip.NetworkProtocolNumber
// pendingMu protects pendingEndpoints. This should only be accessed
// by the listening endpoint's worker goroutine.
//
@@ -115,44 +126,6 @@ func timeStamp() uint32 {
return uint32(time.Now().Unix()>>6) & tsMask
}
// incSynRcvdCount tries to increment the global number of endpoints in SYN-RCVD
// state. It succeeds if the increment doesn't make the count go beyond the
// threshold, and fails otherwise.
func incSynRcvdCount() bool {
synRcvdCount.Lock()
if synRcvdCount.value >= SynRcvdCountThreshold {
synRcvdCount.Unlock()
return false
}
synRcvdCount.pending.Add(1)
synRcvdCount.value++
synRcvdCount.Unlock()
return true
}
// decSynRcvdCount atomically decrements the global number of endpoints in
// SYN-RCVD state. It must only be called if a previous call to incSynRcvdCount
// succeeded.
func decSynRcvdCount() {
synRcvdCount.Lock()
synRcvdCount.value--
synRcvdCount.pending.Done()
synRcvdCount.Unlock()
}
// synCookiesInUse() returns true if the synRcvdCount is greater than
// SynRcvdCountThreshold.
func synCookiesInUse() bool {
synRcvdCount.Lock()
v := synRcvdCount.value
synRcvdCount.Unlock()
return v >= SynRcvdCountThreshold
}
// newListenContext creates a new listen context.
func newListenContext(stk *stack.Stack, listenEP *endpoint, rcvWnd seqnum.Size, v6only bool, netProto tcpip.NetworkProtocolNumber) *listenContext {
l := &listenContext{
@@ -164,6 +137,11 @@ func newListenContext(stk *stack.Stack, listenEP *endpoint, rcvWnd seqnum.Size,
listenEP: listenEP,
pendingEndpoints: make(map[stack.TransportEndpointID]*endpoint),
}
p, ok := stk.TransportProtocolInstance(ProtocolNumber).(*protocol)
if !ok {
panic(fmt.Sprintf("unable to get TCP protocol instance from stack: %+v", stk))
}
l.synRcvdCount = p.SynRcvdCounter()
rand.Read(l.nonce[0][:])
rand.Read(l.nonce[1][:])
@@ -410,7 +388,7 @@ func (e *endpoint) propagateInheritableOptionsLocked(n *endpoint) {
// A limited number of these goroutines are allowed before TCP starts using SYN
// cookies to accept connections.
func (e *endpoint) handleSynSegment(ctx *listenContext, s *segment, opts *header.TCPSynOptions) {
defer decSynRcvdCount()
defer ctx.synRcvdCount.dec()
defer func() {
e.mu.Lock()
e.decSynRcvdCount()
@@ -477,7 +455,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
switch {
case s.flags == header.TCPFlagSyn:
opts := parseSynSegmentOptions(s)
if incSynRcvdCount() {
if ctx.synRcvdCount.inc() {
// Only handle the syn if the following conditions hold
// - accept queue is not full.
// - number of connections in synRcvd state is less than the
@@ -487,7 +465,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
go e.handleSynSegment(ctx, s, &opts) // S/R-SAFE: synRcvdCount is the barrier.
return
}
decSynRcvdCount()
ctx.synRcvdCount.dec()
e.stack.Stats().TCP.ListenOverflowSynDrop.Increment()
e.stats.ReceiveErrors.ListenOverflowSynDrop.Increment()
e.stack.Stats().DroppedPackets.Increment()
@@ -540,7 +518,7 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
return
}
if !synCookiesInUse() {
if !ctx.synRcvdCount.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
+4 -5
View File
@@ -568,11 +568,10 @@ func TestV4AcceptOnV4(t *testing.T) {
func testV4ListenClose(t *testing.T, c *context.Context) {
// Set the SynRcvd threshold to zero to force a syn cookie based accept
// to happen.
saved := tcp.SynRcvdCountThreshold
defer func() {
tcp.SynRcvdCountThreshold = saved
}()
tcp.SynRcvdCountThreshold = 0
if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.TCPSynRcvdCountThresholdOption(0)); err != nil {
t.Fatalf("setting TCPSynRcvdCountThresholdOption failed: %s", err)
}
const n = uint16(32)
// Start listening.
+77
View File
@@ -94,6 +94,63 @@ const (
ccCubic = "cubic"
)
// syncRcvdCounter tracks the number of endpoints in the SYN-RCVD state. The
// value is protected by a mutex so that we can increment only when it's
// guaranteed not to go above a threshold.
type synRcvdCounter struct {
sync.Mutex
value uint64
pending sync.WaitGroup
threshold uint64
}
// inc tries to increment the global number of endpoints in SYN-RCVD state. It
// succeeds if the increment doesn't make the count go beyond the threshold, and
// fails otherwise.
func (s *synRcvdCounter) inc() bool {
s.Lock()
defer s.Unlock()
if s.value >= s.threshold {
return false
}
s.pending.Add(1)
s.value++
return true
}
// dec atomically decrements the global number of endpoints in SYN-RCVD
// state. It must only be called if a previous call to inc succeeded.
func (s *synRcvdCounter) dec() {
s.Lock()
defer s.Unlock()
s.value--
s.pending.Done()
}
// synCookiesInUse returns true if the synRcvdCount is greater than
// SynRcvdCountThreshold.
func (s *synRcvdCounter) synCookiesInUse() bool {
s.Lock()
defer s.Unlock()
return s.value >= s.threshold
}
// SetThreshold sets synRcvdCounter.Threshold to ths new threshold.
func (s *synRcvdCounter) SetThreshold(threshold uint64) {
s.Lock()
defer s.Unlock()
s.threshold = threshold
}
// Threshold returns the current value of synRcvdCounter.Threhsold.
func (s *synRcvdCounter) Threshold() uint64 {
s.Lock()
defer s.Unlock()
return s.threshold
}
type protocol struct {
mu sync.RWMutex
sackEnabled bool
@@ -106,6 +163,7 @@ type protocol struct {
tcpLingerTimeout time.Duration
tcpTimeWaitTimeout time.Duration
minRTO time.Duration
synRcvdCount synRcvdCounter
dispatcher *dispatcher
}
@@ -282,6 +340,12 @@ func (p *protocol) SetOption(option interface{}) *tcpip.Error {
p.mu.Unlock()
return nil
case tcpip.TCPSynRcvdCountThresholdOption:
p.mu.Lock()
p.synRcvdCount.SetThreshold(uint64(v))
p.mu.Unlock()
return nil
default:
return tcpip.ErrUnknownProtocolOption
}
@@ -350,6 +414,12 @@ func (p *protocol) Option(option interface{}) *tcpip.Error {
p.mu.RUnlock()
return nil
case *tcpip.TCPSynRcvdCountThresholdOption:
p.mu.RLock()
*v = tcpip.TCPSynRcvdCountThresholdOption(p.synRcvdCount.Threshold())
p.mu.RUnlock()
return nil
default:
return tcpip.ErrUnknownProtocolOption
}
@@ -365,6 +435,12 @@ func (p *protocol) Wait() {
p.dispatcher.wait()
}
// SynRcvdCounter returns a reference to the synRcvdCount for this protocol
// instance.
func (p *protocol) SynRcvdCounter() *synRcvdCounter {
return &p.synRcvdCount
}
// NewProtocol returns a TCP transport protocol.
func NewProtocol() stack.TransportProtocol {
return &protocol{
@@ -374,6 +450,7 @@ func NewProtocol() stack.TransportProtocol {
availableCongestionControl: []string{ccReno, ccCubic},
tcpLingerTimeout: DefaultTCPLingerTimeout,
tcpTimeWaitTimeout: DefaultTCPTimeWaitTimeout,
synRcvdCount: synRcvdCounter{threshold: SynRcvdCountThreshold},
dispatcher: newDispatcher(runtime.GOMAXPROCS(0)),
minRTO: MinRTO,
}
+21 -18
View File
@@ -149,21 +149,22 @@ func TestSackPermittedAccept(t *testing.T) {
{true, false, -1, 0xffff}, // When cookie is used window scaling is disabled.
{false, true, 5, 0x8000}, // 0x8000 * 2^5 = 1<<20 = 1MB window (the default).
}
savedSynCountThreshold := tcp.SynRcvdCountThreshold
defer func() {
tcp.SynRcvdCountThreshold = savedSynCountThreshold
}()
for _, tc := range testCases {
t.Run(fmt.Sprintf("test: %#v", tc), func(t *testing.T) {
if tc.cookieEnabled {
tcp.SynRcvdCountThreshold = 0
} else {
tcp.SynRcvdCountThreshold = savedSynCountThreshold
}
for _, sackEnabled := range []bool{false, true} {
t.Run(fmt.Sprintf("test stack.sackEnabled: %v", sackEnabled), func(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
if tc.cookieEnabled {
// Set the SynRcvd threshold to
// zero to force a syn cookie
// based accept to happen.
if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.TCPSynRcvdCountThresholdOption(0)); err != nil {
t.Fatalf("setting TCPSynRcvdCountThresholdOption to 0 failed: %s", err)
}
}
setStackSACKPermitted(t, c, sackEnabled)
rep := c.AcceptWithOptions(tc.wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS, SACKPermitted: tc.sackPermitted})
@@ -222,21 +223,23 @@ func TestSackDisabledAccept(t *testing.T) {
{true, -1, 0xffff}, // When cookie is used window scaling is disabled.
{false, 5, 0x8000}, // 0x8000 * 2^5 = 1<<20 = 1MB window (the default).
}
savedSynCountThreshold := tcp.SynRcvdCountThreshold
defer func() {
tcp.SynRcvdCountThreshold = savedSynCountThreshold
}()
for _, tc := range testCases {
t.Run(fmt.Sprintf("test: %#v", tc), func(t *testing.T) {
if tc.cookieEnabled {
tcp.SynRcvdCountThreshold = 0
} else {
tcp.SynRcvdCountThreshold = savedSynCountThreshold
}
for _, sackEnabled := range []bool{false, true} {
t.Run(fmt.Sprintf("test: sackEnabled: %v", sackEnabled), func(t *testing.T) {
c := context.New(t, defaultMTU)
defer c.Cleanup()
if tc.cookieEnabled {
// Set the SynRcvd threshold to
// zero to force a syn cookie
// based accept to happen.
if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.TCPSynRcvdCountThresholdOption(0)); err != nil {
t.Fatalf("setting TCPSynRcvdCountThresholdOption to 0 failed: %s", err)
}
}
setStackSACKPermitted(t, c, sackEnabled)
rep := c.AcceptWithOptions(tc.wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS})
+14 -18
View File
@@ -2706,26 +2706,24 @@ func TestSynCookiePassiveSendMSSLessThanMTU(t *testing.T) {
// Set the SynRcvd threshold to zero to force a syn cookie based accept
// to happen.
saved := tcp.SynRcvdCountThreshold
defer func() {
tcp.SynRcvdCountThreshold = saved
}()
tcp.SynRcvdCountThreshold = 0
if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.TCPSynRcvdCountThresholdOption(0)); err != nil {
t.Fatalf("setting TCPSynRcvdCountThresholdOption to 0 failed: %s", err)
}
// Create EP and start listening.
wq := &waiter.Queue{}
ep, err := c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, wq)
if err != nil {
t.Fatalf("NewEndpoint failed: %v", err)
t.Fatalf("NewEndpoint failed: %s", err)
}
defer ep.Close()
if err := ep.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
t.Fatalf("Bind failed: %v", err)
t.Fatalf("Bind failed: %s", err)
}
if err := ep.Listen(10); err != nil {
t.Fatalf("Listen failed: %v", err)
t.Fatalf("Listen failed: %s", err)
}
// Do 3-way handshake.
@@ -2743,7 +2741,7 @@ func TestSynCookiePassiveSendMSSLessThanMTU(t *testing.T) {
case <-ch:
c.EP, _, err = ep.Accept()
if err != nil {
t.Fatalf("Accept failed: %v", err)
t.Fatalf("Accept failed: %s", err)
}
case <-time.After(1 * time.Second):
@@ -5143,25 +5141,23 @@ func TestListenSynRcvdQueueFull(t *testing.T) {
}
func TestListenBacklogFullSynCookieInUse(t *testing.T) {
saved := tcp.SynRcvdCountThreshold
defer func() {
tcp.SynRcvdCountThreshold = saved
}()
tcp.SynRcvdCountThreshold = 1
c := context.New(t, defaultMTU)
defer c.Cleanup()
if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.TCPSynRcvdCountThresholdOption(1)); err != nil {
t.Fatalf("setting TCPSynRcvdCountThresholdOption to 1 failed: %s", err)
}
// Create TCP endpoint.
var err *tcpip.Error
c.EP, err = c.Stack().NewEndpoint(tcp.ProtocolNumber, ipv4.ProtocolNumber, &c.WQ)
if err != nil {
t.Fatalf("NewEndpoint failed: %v", err)
t.Fatalf("NewEndpoint failed: %s", err)
}
// Bind to wildcard.
if err := c.EP.Bind(tcpip.FullAddress{Port: context.StackPort}); err != nil {
t.Fatalf("Bind failed: %v", err)
t.Fatalf("Bind failed: %s", err)
}
// Test acceptance.
@@ -5169,7 +5165,7 @@ func TestListenBacklogFullSynCookieInUse(t *testing.T) {
listenBacklog := 1
portOffset := uint16(0)
if err := c.EP.Listen(listenBacklog); err != nil {
t.Fatalf("Listen failed: %v", err)
t.Fatalf("Listen failed: %s", err)
}
executeHandshake(t, c, context.TestPort+portOffset, false)
+14 -18
View File
@@ -127,17 +127,15 @@ func TestTimeStampDisabledConnect(t *testing.T) {
}
func timeStampEnabledAccept(t *testing.T, cookieEnabled bool, wndScale int, wndSize uint16) {
savedSynCountThreshold := tcp.SynRcvdCountThreshold
defer func() {
tcp.SynRcvdCountThreshold = savedSynCountThreshold
}()
if cookieEnabled {
tcp.SynRcvdCountThreshold = 0
}
c := context.New(t, defaultMTU)
defer c.Cleanup()
if cookieEnabled {
if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.TCPSynRcvdCountThresholdOption(0)); err != nil {
t.Fatalf("setting TCPSynRcvdCountThresholdOption to 0 failed: %s", err)
}
}
t.Logf("Test w/ CookieEnabled = %v", cookieEnabled)
tsVal := rand.Uint32()
c.AcceptWithOptions(wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS, TS: true, TSVal: tsVal})
@@ -148,7 +146,7 @@ func timeStampEnabledAccept(t *testing.T, cookieEnabled bool, wndScale int, wndS
copy(view, data)
if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
t.Fatalf("Unexpected error from Write: %v", err)
t.Fatalf("Unexpected error from Write: %s", err)
}
// Check that data is received and that the timestamp option TSEcr field
@@ -190,17 +188,15 @@ func TestTimeStampEnabledAccept(t *testing.T) {
}
func timeStampDisabledAccept(t *testing.T, cookieEnabled bool, wndScale int, wndSize uint16) {
savedSynCountThreshold := tcp.SynRcvdCountThreshold
defer func() {
tcp.SynRcvdCountThreshold = savedSynCountThreshold
}()
if cookieEnabled {
tcp.SynRcvdCountThreshold = 0
}
c := context.New(t, defaultMTU)
defer c.Cleanup()
if cookieEnabled {
if err := c.Stack().SetTransportProtocolOption(tcp.ProtocolNumber, tcpip.TCPSynRcvdCountThresholdOption(0)); err != nil {
t.Fatalf("setting TCPSynRcvdCountThresholdOption to 0 failed: %s", err)
}
}
t.Logf("Test w/ CookieEnabled = %v", cookieEnabled)
c.AcceptWithOptions(wndScale, header.TCPSynOptions{MSS: defaultIPv4MSS})
@@ -211,7 +207,7 @@ func timeStampDisabledAccept(t *testing.T, cookieEnabled bool, wndScale int, wnd
copy(view, data)
if _, _, err := c.EP.Write(tcpip.SlicePayload(view), tcpip.WriteOptions{}); err != nil {
t.Fatalf("Unexpected error from Write: %v", err)
t.Fatalf("Unexpected error from Write: %s", err)
}
// Check that data is received and that the timestamp option is disabled