mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Changes TCP packet dispatch to use a pool of goroutines.
All inbound segments for connections in ESTABLISHED state are delivered to the endpoint's queue but for every segment delivered we also queue the endpoint for processing to a selected processor. This ensures that when there are a large number of connections in ESTABLISHED state the inbound packets are all handled by a small number of goroutines and significantly reduces the amount of work the goscheduler has to perform. We let connections in other states follow the current path where the endpoint's goroutine directly handles the segments. Updates #231 PiperOrigin-RevId: 289728325
This commit is contained in:
committed by
gVisor bot
parent
50625cee59
commit
a611fdaee3
@@ -85,7 +85,7 @@ func (netImpl) printStats() {
|
||||
|
||||
const (
|
||||
nicID = 1 // Fixed.
|
||||
rcvBufSize = 1 << 20 // 1MB.
|
||||
rcvBufSize = 4 << 20 // 1MB.
|
||||
)
|
||||
|
||||
type netstackImpl struct {
|
||||
@@ -130,6 +130,10 @@ func setupNetwork(ifaceName string, numChannels int) (fds []int, err error) {
|
||||
return nil, fmt.Errorf("setsockopt(..., SO_RCVBUF, %v,..) = %v", rcvBufSize, err)
|
||||
}
|
||||
|
||||
if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_SNDBUF, rcvBufSize); err != nil {
|
||||
return nil, fmt.Errorf("setsockopt(..., SO_RCVBUF, %v,..) = %v", rcvBufSize, err)
|
||||
}
|
||||
|
||||
if !*swgso && *gso != 0 {
|
||||
if err := syscall.SetsockoptInt(fd, syscall.SOL_PACKET, unix.PACKET_VNET_HDR, 1); err != nil {
|
||||
return nil, fmt.Errorf("unable to enable the PACKET_VNET_HDR option: %v", err)
|
||||
|
||||
@@ -376,6 +376,37 @@ func TestRace(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRaceInOrder tests that multiple wakers can continuously send wake requests to
|
||||
// the sleeper and that the wakers are retrieved in the order asserted.
|
||||
func TestRaceInOrder(t *testing.T) {
|
||||
const wakers = 100
|
||||
const wakeRequests = 10000
|
||||
|
||||
w := make([]Waker, wakers)
|
||||
s := Sleeper{}
|
||||
|
||||
// Associate each waker and start goroutines that will assert them.
|
||||
for i := range w {
|
||||
s.AddWaker(&w[i], i)
|
||||
}
|
||||
go func() {
|
||||
n := 0
|
||||
for n < wakeRequests {
|
||||
wk := w[n%len(w)]
|
||||
wk.Assert()
|
||||
n++
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for all wake up notifications from all wakers.
|
||||
for i := 0; i < wakeRequests; i++ {
|
||||
v, _ := s.Fetch(true)
|
||||
if got, want := v, i%wakers; got != want {
|
||||
t.Fatalf("got %d want %d", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSleeperMultiSelect measures how long it takes to fetch a wake up
|
||||
// from 4 wakers when at least one is already asserted.
|
||||
func BenchmarkSleeperMultiSelect(b *testing.B) {
|
||||
|
||||
@@ -104,7 +104,14 @@ func (epsByNic *endpointsByNic) handlePacket(r *Route, id TransportEndpointID, p
|
||||
return
|
||||
}
|
||||
// multiPortEndpoints are guaranteed to have at least one element.
|
||||
selectEndpoint(id, mpep, epsByNic.seed).HandlePacket(r, id, pkt)
|
||||
transEP := selectEndpoint(id, mpep, epsByNic.seed)
|
||||
if queuedProtocol, mustQueue := mpep.demux.queuedProtocols[protocolIDs{mpep.netProto, mpep.transProto}]; mustQueue {
|
||||
queuedProtocol.QueuePacket(r, transEP, id, pkt)
|
||||
epsByNic.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
transEP.HandlePacket(r, id, pkt)
|
||||
epsByNic.mu.RUnlock() // Don't use defer for performance reasons.
|
||||
}
|
||||
|
||||
@@ -130,7 +137,7 @@ func (epsByNic *endpointsByNic) handleControlPacket(n *NIC, id TransportEndpoint
|
||||
|
||||
// registerEndpoint returns true if it succeeds. It fails and returns
|
||||
// false if ep already has an element with the same key.
|
||||
func (epsByNic *endpointsByNic) registerEndpoint(t TransportEndpoint, reusePort bool, bindToDevice tcpip.NICID) *tcpip.Error {
|
||||
func (epsByNic *endpointsByNic) registerEndpoint(d *transportDemuxer, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber, t TransportEndpoint, reusePort bool, bindToDevice tcpip.NICID) *tcpip.Error {
|
||||
epsByNic.mu.Lock()
|
||||
defer epsByNic.mu.Unlock()
|
||||
|
||||
@@ -140,7 +147,7 @@ func (epsByNic *endpointsByNic) registerEndpoint(t TransportEndpoint, reusePort
|
||||
}
|
||||
|
||||
// This is a new binding.
|
||||
multiPortEp := &multiPortEndpoint{}
|
||||
multiPortEp := &multiPortEndpoint{demux: d, netProto: netProto, transProto: transProto}
|
||||
multiPortEp.endpointsMap = make(map[TransportEndpoint]int)
|
||||
multiPortEp.reuse = reusePort
|
||||
epsByNic.endpoints[bindToDevice] = multiPortEp
|
||||
@@ -168,18 +175,34 @@ func (epsByNic *endpointsByNic) unregisterEndpoint(bindToDevice tcpip.NICID, t T
|
||||
// newTransportDemuxer.
|
||||
type transportDemuxer struct {
|
||||
// protocol is immutable.
|
||||
protocol map[protocolIDs]*transportEndpoints
|
||||
protocol map[protocolIDs]*transportEndpoints
|
||||
queuedProtocols map[protocolIDs]queuedTransportProtocol
|
||||
}
|
||||
|
||||
// queuedTransportProtocol if supported by a protocol implementation will cause
|
||||
// the dispatcher to delivery packets to the QueuePacket method instead of
|
||||
// calling HandlePacket directly on the endpoint.
|
||||
type queuedTransportProtocol interface {
|
||||
QueuePacket(r *Route, ep TransportEndpoint, id TransportEndpointID, pkt tcpip.PacketBuffer)
|
||||
}
|
||||
|
||||
func newTransportDemuxer(stack *Stack) *transportDemuxer {
|
||||
d := &transportDemuxer{protocol: make(map[protocolIDs]*transportEndpoints)}
|
||||
d := &transportDemuxer{
|
||||
protocol: make(map[protocolIDs]*transportEndpoints),
|
||||
queuedProtocols: make(map[protocolIDs]queuedTransportProtocol),
|
||||
}
|
||||
|
||||
// Add each network and transport pair to the demuxer.
|
||||
for netProto := range stack.networkProtocols {
|
||||
for proto := range stack.transportProtocols {
|
||||
d.protocol[protocolIDs{netProto, proto}] = &transportEndpoints{
|
||||
protoIDs := protocolIDs{netProto, proto}
|
||||
d.protocol[protoIDs] = &transportEndpoints{
|
||||
endpoints: make(map[TransportEndpointID]*endpointsByNic),
|
||||
}
|
||||
qTransProto, isQueued := (stack.transportProtocols[proto].proto).(queuedTransportProtocol)
|
||||
if isQueued {
|
||||
d.queuedProtocols[protoIDs] = qTransProto
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +232,11 @@ func (d *transportDemuxer) registerEndpoint(netProtos []tcpip.NetworkProtocolNum
|
||||
//
|
||||
// +stateify savable
|
||||
type multiPortEndpoint struct {
|
||||
mu sync.RWMutex `state:"nosave"`
|
||||
mu sync.RWMutex `state:"nosave"`
|
||||
demux *transportDemuxer
|
||||
netProto tcpip.NetworkProtocolNumber
|
||||
transProto tcpip.TransportProtocolNumber
|
||||
|
||||
endpointsArr []TransportEndpoint
|
||||
endpointsMap map[TransportEndpoint]int
|
||||
// reuse indicates if more than one endpoint is allowed.
|
||||
@@ -258,13 +285,22 @@ func selectEndpoint(id TransportEndpointID, mpep *multiPortEndpoint, seed uint32
|
||||
|
||||
func (ep *multiPortEndpoint) handlePacketAll(r *Route, id TransportEndpointID, pkt tcpip.PacketBuffer) {
|
||||
ep.mu.RLock()
|
||||
queuedProtocol, mustQueue := ep.demux.queuedProtocols[protocolIDs{ep.netProto, ep.transProto}]
|
||||
for i, endpoint := range ep.endpointsArr {
|
||||
// HandlePacket takes ownership of pkt, so each endpoint needs
|
||||
// its own copy except for the final one.
|
||||
if i == len(ep.endpointsArr)-1 {
|
||||
if mustQueue {
|
||||
queuedProtocol.QueuePacket(r, endpoint, id, pkt)
|
||||
break
|
||||
}
|
||||
endpoint.HandlePacket(r, id, pkt)
|
||||
break
|
||||
}
|
||||
if mustQueue {
|
||||
queuedProtocol.QueuePacket(r, endpoint, id, pkt.Clone())
|
||||
continue
|
||||
}
|
||||
endpoint.HandlePacket(r, id, pkt.Clone())
|
||||
}
|
||||
ep.mu.RUnlock() // Don't use defer for performance reasons.
|
||||
@@ -357,7 +393,7 @@ func (d *transportDemuxer) singleRegisterEndpoint(netProto tcpip.NetworkProtocol
|
||||
|
||||
if epsByNic, ok := eps.endpoints[id]; ok {
|
||||
// There was already a binding.
|
||||
return epsByNic.registerEndpoint(ep, reusePort, bindToDevice)
|
||||
return epsByNic.registerEndpoint(d, netProto, protocol, ep, reusePort, bindToDevice)
|
||||
}
|
||||
|
||||
// This is a new binding.
|
||||
@@ -367,7 +403,7 @@ func (d *transportDemuxer) singleRegisterEndpoint(netProto tcpip.NetworkProtocol
|
||||
}
|
||||
eps.endpoints[id] = epsByNic
|
||||
|
||||
return epsByNic.registerEndpoint(ep, reusePort, bindToDevice)
|
||||
return epsByNic.registerEndpoint(d, netProto, protocol, ep, reusePort, bindToDevice)
|
||||
}
|
||||
|
||||
// unregisterEndpoint unregisters the endpoint with the given id such that it
|
||||
|
||||
@@ -16,6 +16,18 @@ go_template_instance(
|
||||
},
|
||||
)
|
||||
|
||||
go_template_instance(
|
||||
name = "tcp_endpoint_list",
|
||||
out = "tcp_endpoint_list.go",
|
||||
package = "tcp",
|
||||
prefix = "endpoint",
|
||||
template = "//pkg/ilist:generic_list",
|
||||
types = {
|
||||
"Element": "*endpoint",
|
||||
"Linker": "*endpoint",
|
||||
},
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "tcp",
|
||||
srcs = [
|
||||
@@ -23,6 +35,7 @@ go_library(
|
||||
"connect.go",
|
||||
"cubic.go",
|
||||
"cubic_state.go",
|
||||
"dispatcher.go",
|
||||
"endpoint.go",
|
||||
"endpoint_state.go",
|
||||
"forwarder.go",
|
||||
@@ -38,6 +51,7 @@ go_library(
|
||||
"segment_state.go",
|
||||
"snd.go",
|
||||
"snd_state.go",
|
||||
"tcp_endpoint_list.go",
|
||||
"tcp_segment_list.go",
|
||||
"timer.go",
|
||||
],
|
||||
@@ -45,7 +59,6 @@ go_library(
|
||||
imports = ["gvisor.dev/gvisor/pkg/tcpip/buffer"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/log",
|
||||
"//pkg/rand",
|
||||
"//pkg/sleep",
|
||||
"//pkg/sync",
|
||||
|
||||
@@ -285,7 +285,7 @@ func (l *listenContext) createEndpointAndPerformHandshake(s *segment, opts *head
|
||||
// listenEP is nil when listenContext is used by tcp.Forwarder.
|
||||
if l.listenEP != nil {
|
||||
l.listenEP.mu.Lock()
|
||||
if l.listenEP.state != StateListen {
|
||||
if l.listenEP.EndpointState() != StateListen {
|
||||
l.listenEP.mu.Unlock()
|
||||
return nil, tcpip.ErrConnectionAborted
|
||||
}
|
||||
@@ -344,11 +344,12 @@ func (l *listenContext) closeAllPendingEndpoints() {
|
||||
// instead.
|
||||
func (e *endpoint) deliverAccepted(n *endpoint) {
|
||||
e.mu.Lock()
|
||||
state := e.state
|
||||
state := e.EndpointState()
|
||||
e.pendingAccepted.Add(1)
|
||||
defer e.pendingAccepted.Done()
|
||||
acceptedChan := e.acceptedChan
|
||||
e.mu.Unlock()
|
||||
|
||||
if state == StateListen {
|
||||
acceptedChan <- n
|
||||
e.waiterQueue.Notify(waiter.EventIn)
|
||||
@@ -562,8 +563,8 @@ func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
|
||||
// We do not use transitionToStateEstablishedLocked here as there is
|
||||
// no handshake state available when doing a SYN cookie based accept.
|
||||
n.stack.Stats().TCP.CurrentEstablished.Increment()
|
||||
n.state = StateEstablished
|
||||
n.isConnectNotified = true
|
||||
n.setEndpointState(StateEstablished)
|
||||
|
||||
// Do the delivery in a separate goroutine so
|
||||
// that we don't block the listen loop in case
|
||||
@@ -596,7 +597,7 @@ func (e *endpoint) protocolListenLoop(rcvWnd seqnum.Size) *tcpip.Error {
|
||||
// handleSynSegment() from attempting to queue new connections
|
||||
// to the endpoint.
|
||||
e.mu.Lock()
|
||||
e.state = StateClose
|
||||
e.setEndpointState(StateClose)
|
||||
|
||||
// close any endpoints in SYN-RCVD state.
|
||||
ctx.closeAllPendingEndpoints()
|
||||
|
||||
+182
-128
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,218 @@
|
||||
// Copyright 2018 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 (
|
||||
"gvisor.dev/gvisor/pkg/rand"
|
||||
"gvisor.dev/gvisor/pkg/sleep"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/hash/jenkins"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// epQueue is a queue of endpoints.
|
||||
type epQueue struct {
|
||||
mu sync.Mutex
|
||||
list endpointList
|
||||
}
|
||||
|
||||
// enqueue adds e to the queue if the endpoint is not already on the queue.
|
||||
func (q *epQueue) enqueue(e *endpoint) {
|
||||
q.mu.Lock()
|
||||
if e.pendingProcessing {
|
||||
q.mu.Unlock()
|
||||
return
|
||||
}
|
||||
q.list.PushBack(e)
|
||||
e.pendingProcessing = true
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
// dequeue removes and returns the first element from the queue if available,
|
||||
// returns nil otherwise.
|
||||
func (q *epQueue) dequeue() *endpoint {
|
||||
q.mu.Lock()
|
||||
if e := q.list.Front(); e != nil {
|
||||
q.list.Remove(e)
|
||||
e.pendingProcessing = false
|
||||
q.mu.Unlock()
|
||||
return e
|
||||
}
|
||||
q.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// empty returns true if the queue is empty, false otherwise.
|
||||
func (q *epQueue) empty() bool {
|
||||
q.mu.Lock()
|
||||
v := q.list.Empty()
|
||||
q.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
|
||||
// processor is responsible for processing packets queued to a tcp endpoint.
|
||||
type processor struct {
|
||||
epQ epQueue
|
||||
newEndpointWaker sleep.Waker
|
||||
id int
|
||||
}
|
||||
|
||||
func newProcessor(id int) *processor {
|
||||
p := &processor{
|
||||
id: id,
|
||||
}
|
||||
go p.handleSegments()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *processor) queueEndpoint(ep *endpoint) {
|
||||
// Queue an endpoint for processing by the processor goroutine.
|
||||
p.epQ.enqueue(ep)
|
||||
p.newEndpointWaker.Assert()
|
||||
}
|
||||
|
||||
func (p *processor) handleSegments() {
|
||||
const newEndpointWaker = 1
|
||||
s := sleep.Sleeper{}
|
||||
s.AddWaker(&p.newEndpointWaker, newEndpointWaker)
|
||||
defer s.Done()
|
||||
for {
|
||||
s.Fetch(true)
|
||||
for ep := p.epQ.dequeue(); ep != nil; ep = p.epQ.dequeue() {
|
||||
if ep.segmentQueue.empty() {
|
||||
continue
|
||||
}
|
||||
|
||||
// If socket has transitioned out of connected state
|
||||
// then just let the worker handle the packet.
|
||||
//
|
||||
// NOTE: We read this outside of e.mu lock which means
|
||||
// that by the time we get to handleSegments the
|
||||
// endpoint may not be in ESTABLISHED. But this should
|
||||
// be fine as all normal shutdown states are handled by
|
||||
// handleSegments and if the endpoint moves to a
|
||||
// CLOSED/ERROR state then handleSegments is a noop.
|
||||
if ep.EndpointState() != StateEstablished {
|
||||
ep.newSegmentWaker.Assert()
|
||||
continue
|
||||
}
|
||||
|
||||
if !ep.workMu.TryLock() {
|
||||
ep.newSegmentWaker.Assert()
|
||||
continue
|
||||
}
|
||||
// If the endpoint is in a connected state then we do
|
||||
// direct delivery to ensure low latency and avoid
|
||||
// scheduler interactions.
|
||||
if err := ep.handleSegments(true /* fastPath */); err != nil || ep.EndpointState() == StateClose {
|
||||
ep.notifyProtocolGoroutine(notifyTickleWorker)
|
||||
ep.workMu.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
if !ep.segmentQueue.empty() {
|
||||
p.epQ.enqueue(ep)
|
||||
}
|
||||
|
||||
ep.workMu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dispatcher manages a pool of TCP endpoint processors which are responsible
|
||||
// for the processing of inbound segments. This fixed pool of processor
|
||||
// goroutines do full tcp processing. The processor is selected based on the
|
||||
// hash of the endpoint id to ensure that delivery for the same endpoint happens
|
||||
// in-order.
|
||||
type dispatcher struct {
|
||||
processors []*processor
|
||||
seed uint32
|
||||
}
|
||||
|
||||
func newDispatcher(nProcessors int) *dispatcher {
|
||||
processors := []*processor{}
|
||||
for i := 0; i < nProcessors; i++ {
|
||||
processors = append(processors, newProcessor(i))
|
||||
}
|
||||
return &dispatcher{
|
||||
processors: processors,
|
||||
seed: generateRandUint32(),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dispatcher) queuePacket(r *stack.Route, stackEP stack.TransportEndpoint, id stack.TransportEndpointID, pkt tcpip.PacketBuffer) {
|
||||
ep := stackEP.(*endpoint)
|
||||
s := newSegment(r, id, pkt)
|
||||
if !s.parse() {
|
||||
ep.stack.Stats().MalformedRcvdPackets.Increment()
|
||||
ep.stack.Stats().TCP.InvalidSegmentsReceived.Increment()
|
||||
ep.stats.ReceiveErrors.MalformedPacketsReceived.Increment()
|
||||
s.decRef()
|
||||
return
|
||||
}
|
||||
|
||||
if !s.csumValid {
|
||||
ep.stack.Stats().MalformedRcvdPackets.Increment()
|
||||
ep.stack.Stats().TCP.ChecksumErrors.Increment()
|
||||
ep.stats.ReceiveErrors.ChecksumErrors.Increment()
|
||||
s.decRef()
|
||||
return
|
||||
}
|
||||
|
||||
ep.stack.Stats().TCP.ValidSegmentsReceived.Increment()
|
||||
ep.stats.SegmentsReceived.Increment()
|
||||
if (s.flags & header.TCPFlagRst) != 0 {
|
||||
ep.stack.Stats().TCP.ResetsReceived.Increment()
|
||||
}
|
||||
|
||||
if !ep.enqueueSegment(s) {
|
||||
s.decRef()
|
||||
return
|
||||
}
|
||||
|
||||
// For sockets not in established state let the worker goroutine
|
||||
// handle the packets.
|
||||
if ep.EndpointState() != StateEstablished {
|
||||
ep.newSegmentWaker.Assert()
|
||||
return
|
||||
}
|
||||
|
||||
d.selectProcessor(id).queueEndpoint(ep)
|
||||
}
|
||||
|
||||
func generateRandUint32() uint32 {
|
||||
b := make([]byte, 4)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
}
|
||||
|
||||
func (d *dispatcher) selectProcessor(id stack.TransportEndpointID) *processor {
|
||||
payload := []byte{
|
||||
byte(id.LocalPort),
|
||||
byte(id.LocalPort >> 8),
|
||||
byte(id.RemotePort),
|
||||
byte(id.RemotePort >> 8)}
|
||||
|
||||
h := jenkins.Sum32(d.seed)
|
||||
h.Write(payload)
|
||||
h.Write([]byte(id.LocalAddress))
|
||||
h.Write([]byte(id.RemoteAddress))
|
||||
|
||||
return d.processors[h.Sum32()%uint32(len(d.processors))]
|
||||
}
|
||||
+205
-102
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ package tcp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
@@ -48,7 +49,7 @@ func (e *endpoint) beforeSave() {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
switch e.state {
|
||||
switch e.EndpointState() {
|
||||
case StateInitial, StateBound:
|
||||
// TODO(b/138137272): this enumeration duplicates
|
||||
// EndpointState.connected. remove it.
|
||||
@@ -70,31 +71,30 @@ func (e *endpoint) beforeSave() {
|
||||
fallthrough
|
||||
case StateListen, StateConnecting:
|
||||
e.drainSegmentLocked()
|
||||
if e.state != StateClose && e.state != StateError {
|
||||
if e.EndpointState() != StateClose && e.EndpointState() != StateError {
|
||||
if !e.workerRunning {
|
||||
panic("endpoint has no worker running in listen, connecting, or connected state")
|
||||
}
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case StateError, StateClose:
|
||||
for (e.state == StateError || e.state == StateClose) && e.workerRunning {
|
||||
for e.workerRunning {
|
||||
e.mu.Unlock()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
e.mu.Lock()
|
||||
}
|
||||
if e.workerRunning {
|
||||
panic("endpoint still has worker running in closed or error state")
|
||||
panic(fmt.Sprintf("endpoint: %+v still has worker running in closed or error state", e.ID))
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("endpoint in unknown state %v", e.state))
|
||||
panic(fmt.Sprintf("endpoint in unknown state %v", e.EndpointState()))
|
||||
}
|
||||
|
||||
if e.waiterQueue != nil && !e.waiterQueue.IsEmpty() {
|
||||
panic("endpoint still has waiters upon save")
|
||||
}
|
||||
|
||||
if e.state != StateClose && !((e.state == StateBound || e.state == StateListen) == e.isPortReserved) {
|
||||
if e.EndpointState() != StateClose && !((e.EndpointState() == StateBound || e.EndpointState() == StateListen) == e.isPortReserved) {
|
||||
panic("endpoints which are not in the closed state must have a reserved port IFF they are in bound or listen state")
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func (e *endpoint) loadAcceptedChan(acceptedEndpoints []*endpoint) {
|
||||
|
||||
// saveState is invoked by stateify.
|
||||
func (e *endpoint) saveState() EndpointState {
|
||||
return e.state
|
||||
return e.EndpointState()
|
||||
}
|
||||
|
||||
// Endpoint loading must be done in the following ordering by their state, to
|
||||
@@ -151,7 +151,8 @@ var connectingLoading sync.WaitGroup
|
||||
func (e *endpoint) loadState(state EndpointState) {
|
||||
// This is to ensure that the loading wait groups include all applicable
|
||||
// endpoints before any asynchronous calls to the Wait() methods.
|
||||
if state.connected() {
|
||||
// For restore purposes we treat TimeWait like a connected endpoint.
|
||||
if state.connected() || state == StateTimeWait {
|
||||
connectedLoading.Add(1)
|
||||
}
|
||||
switch state {
|
||||
@@ -160,13 +161,14 @@ func (e *endpoint) loadState(state EndpointState) {
|
||||
case StateConnecting, StateSynSent, StateSynRecv:
|
||||
connectingLoading.Add(1)
|
||||
}
|
||||
e.state = state
|
||||
// Directly update the state here rather than using e.setEndpointState
|
||||
// as the endpoint is still being loaded and the stack reference to increment
|
||||
// metrics is not yet initialized.
|
||||
atomic.StoreUint32((*uint32)(&e.state), uint32(state))
|
||||
}
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (e *endpoint) afterLoad() {
|
||||
// Freeze segment queue before registering to prevent any segments
|
||||
// from being delivered while it is being restored.
|
||||
e.origEndpointState = e.state
|
||||
// Restore the endpoint to InitialState as it will be moved to
|
||||
// its origEndpointState during Resume.
|
||||
@@ -180,7 +182,6 @@ func (e *endpoint) Resume(s *stack.Stack) {
|
||||
e.segmentQueue.setLimit(MaxUnprocessedSegments)
|
||||
e.workMu.Init()
|
||||
state := e.origEndpointState
|
||||
|
||||
switch state {
|
||||
case StateInitial, StateBound, StateListen, StateConnecting, StateEstablished:
|
||||
var ss SendBufferSizeOption
|
||||
@@ -276,7 +277,7 @@ func (e *endpoint) Resume(s *stack.Stack) {
|
||||
listenLoading.Wait()
|
||||
connectingLoading.Wait()
|
||||
bind()
|
||||
e.state = StateClose
|
||||
e.setEndpointState(StateClose)
|
||||
tcpip.AsyncLoading.Done()
|
||||
}()
|
||||
}
|
||||
@@ -288,6 +289,7 @@ func (e *endpoint) Resume(s *stack.Stack) {
|
||||
e.stack.CompleteTransportEndpointCleanup(e)
|
||||
tcpip.DeleteDanglingEndpoint(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// saveLastError is invoked by stateify.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -104,6 +105,7 @@ type protocol struct {
|
||||
moderateReceiveBuffer bool
|
||||
tcpLingerTimeout time.Duration
|
||||
tcpTimeWaitTimeout time.Duration
|
||||
dispatcher *dispatcher
|
||||
}
|
||||
|
||||
// Number returns the tcp protocol number.
|
||||
@@ -134,6 +136,14 @@ func (*protocol) ParsePorts(v buffer.View) (src, dst uint16, err *tcpip.Error) {
|
||||
return h.SourcePort(), h.DestinationPort(), nil
|
||||
}
|
||||
|
||||
// QueuePacket queues packets targeted at an endpoint after hashing the packet
|
||||
// to a specific processing queue. Each queue is serviced by its own processor
|
||||
// goroutine which is responsible for dequeuing and doing full TCP dispatch of
|
||||
// the packet.
|
||||
func (p *protocol) QueuePacket(r *stack.Route, ep stack.TransportEndpoint, id stack.TransportEndpointID, pkt tcpip.PacketBuffer) {
|
||||
p.dispatcher.queuePacket(r, ep, id, pkt)
|
||||
}
|
||||
|
||||
// HandleUnknownDestinationPacket handles packets targeted at this protocol but
|
||||
// that don't match any existing endpoint.
|
||||
//
|
||||
@@ -330,5 +340,6 @@ func NewProtocol() stack.TransportProtocol {
|
||||
availableCongestionControl: []string{ccReno, ccCubic},
|
||||
tcpLingerTimeout: DefaultTCPLingerTimeout,
|
||||
tcpTimeWaitTimeout: DefaultTCPTimeWaitTimeout,
|
||||
dispatcher: newDispatcher(runtime.GOMAXPROCS(0)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,19 +169,19 @@ func (r *receiver) consumeSegment(s *segment, segSeq seqnum.Value, segLen seqnum
|
||||
// We just received a FIN, our next state depends on whether we sent a
|
||||
// FIN already or not.
|
||||
r.ep.mu.Lock()
|
||||
switch r.ep.state {
|
||||
switch r.ep.EndpointState() {
|
||||
case StateEstablished:
|
||||
r.ep.state = StateCloseWait
|
||||
r.ep.setEndpointState(StateCloseWait)
|
||||
case StateFinWait1:
|
||||
if s.flagIsSet(header.TCPFlagAck) {
|
||||
// FIN-ACK, transition to TIME-WAIT.
|
||||
r.ep.state = StateTimeWait
|
||||
r.ep.setEndpointState(StateTimeWait)
|
||||
} else {
|
||||
// Simultaneous close, expecting a final ACK.
|
||||
r.ep.state = StateClosing
|
||||
r.ep.setEndpointState(StateClosing)
|
||||
}
|
||||
case StateFinWait2:
|
||||
r.ep.state = StateTimeWait
|
||||
r.ep.setEndpointState(StateTimeWait)
|
||||
}
|
||||
r.ep.mu.Unlock()
|
||||
|
||||
@@ -205,16 +205,16 @@ func (r *receiver) consumeSegment(s *segment, segSeq seqnum.Value, segLen seqnum
|
||||
// shutdown states.
|
||||
if s.flagIsSet(header.TCPFlagAck) && s.ackNumber == r.ep.snd.sndNxt {
|
||||
r.ep.mu.Lock()
|
||||
switch r.ep.state {
|
||||
switch r.ep.EndpointState() {
|
||||
case StateFinWait1:
|
||||
r.ep.state = StateFinWait2
|
||||
r.ep.setEndpointState(StateFinWait2)
|
||||
// Notify protocol goroutine that we have received an
|
||||
// ACK to our FIN so that it can start the FIN_WAIT2
|
||||
// timer to abort connection if the other side does
|
||||
// not close within 2MSL.
|
||||
r.ep.notifyProtocolGoroutine(notifyClose)
|
||||
case StateClosing:
|
||||
r.ep.state = StateTimeWait
|
||||
r.ep.setEndpointState(StateTimeWait)
|
||||
case StateLastAck:
|
||||
r.ep.transitionToStateCloseLocked()
|
||||
}
|
||||
@@ -267,7 +267,6 @@ func (r *receiver) handleRcvdSegmentClosing(s *segment, state EndpointState, clo
|
||||
switch state {
|
||||
case StateCloseWait, StateClosing, StateLastAck:
|
||||
if !s.sequenceNumber.LessThanEq(r.rcvNxt) {
|
||||
s.decRef()
|
||||
// Just drop the segment as we have
|
||||
// already received a FIN and this
|
||||
// segment is after the sequence number
|
||||
@@ -284,7 +283,6 @@ func (r *receiver) handleRcvdSegmentClosing(s *segment, state EndpointState, clo
|
||||
// trigger a RST.
|
||||
endDataSeq := s.sequenceNumber.Add(seqnum.Size(s.data.Size()))
|
||||
if rcvClosed && r.rcvNxt.LessThan(endDataSeq) {
|
||||
s.decRef()
|
||||
return true, tcpip.ErrConnectionAborted
|
||||
}
|
||||
if state == StateFinWait1 {
|
||||
@@ -314,7 +312,6 @@ func (r *receiver) handleRcvdSegmentClosing(s *segment, state EndpointState, clo
|
||||
// the last actual data octet in a segment in
|
||||
// which it occurs.
|
||||
if closed && (!s.flagIsSet(header.TCPFlagFin) || s.sequenceNumber.Add(s.logicalLen()) != r.rcvNxt+1) {
|
||||
s.decRef()
|
||||
return true, tcpip.ErrConnectionAborted
|
||||
}
|
||||
}
|
||||
@@ -336,7 +333,7 @@ func (r *receiver) handleRcvdSegmentClosing(s *segment, state EndpointState, clo
|
||||
// r as they arrive. It is called by the protocol main loop.
|
||||
func (r *receiver) handleRcvdSegment(s *segment) (drop bool, err *tcpip.Error) {
|
||||
r.ep.mu.RLock()
|
||||
state := r.ep.state
|
||||
state := r.ep.EndpointState()
|
||||
closed := r.ep.closed
|
||||
r.ep.mu.RUnlock()
|
||||
|
||||
|
||||
@@ -705,17 +705,15 @@ func (s *sender) maybeSendSegment(seg *segment, limit int, end seqnum.Value) (se
|
||||
}
|
||||
seg.flags = header.TCPFlagAck | header.TCPFlagFin
|
||||
segEnd = seg.sequenceNumber.Add(1)
|
||||
// Transition to FIN-WAIT1 state since we're initiating an active close.
|
||||
s.ep.mu.Lock()
|
||||
switch s.ep.state {
|
||||
// Update the state to reflect that we have now
|
||||
// queued a FIN.
|
||||
switch s.ep.EndpointState() {
|
||||
case StateCloseWait:
|
||||
// We've already received a FIN and are now sending our own. The
|
||||
// sender is now awaiting a final ACK for this FIN.
|
||||
s.ep.state = StateLastAck
|
||||
s.ep.setEndpointState(StateLastAck)
|
||||
default:
|
||||
s.ep.state = StateFinWait1
|
||||
s.ep.setEndpointState(StateFinWait1)
|
||||
}
|
||||
s.ep.mu.Unlock()
|
||||
|
||||
} else {
|
||||
// We're sending a non-FIN segment.
|
||||
if seg.flags&header.TCPFlagFin != 0 {
|
||||
|
||||
@@ -293,7 +293,6 @@ func TestTCPResetSentForACKWhenNotUsingSynCookies(t *testing.T) {
|
||||
checker.SeqNum(uint32(c.IRS+1)),
|
||||
checker.AckNum(uint32(iss)+1),
|
||||
checker.TCPFlags(header.TCPFlagFin|header.TCPFlagAck)))
|
||||
|
||||
finHeaders := &context.Headers{
|
||||
SrcPort: context.TestPort,
|
||||
DstPort: context.StackPort,
|
||||
@@ -459,6 +458,9 @@ func TestConnectResetAfterClose(t *testing.T) {
|
||||
checker.IPv4(t, b,
|
||||
checker.TCP(
|
||||
checker.DstPort(context.TestPort),
|
||||
// RST is always generated with sndNxt which if the FIN
|
||||
// has been sent will be 1 higher than the sequence number
|
||||
// of the FIN itself.
|
||||
checker.SeqNum(uint32(c.IRS)+2),
|
||||
checker.AckNum(0),
|
||||
checker.TCPFlags(header.TCPFlagRst),
|
||||
@@ -1500,6 +1502,9 @@ func TestRstOnCloseWithUnreadDataFinConvertRst(t *testing.T) {
|
||||
checker.TCP(
|
||||
checker.DstPort(context.TestPort),
|
||||
checker.TCPFlags(header.TCPFlagAck|header.TCPFlagRst),
|
||||
// RST is always generated with sndNxt which if the FIN
|
||||
// has been sent will be 1 higher than the sequence
|
||||
// number of the FIN itself.
|
||||
checker.SeqNum(uint32(c.IRS)+2),
|
||||
))
|
||||
// The RST puts the endpoint into an error state.
|
||||
@@ -5441,6 +5446,7 @@ func TestReceiveBufferAutoTuningApplicationLimited(t *testing.T) {
|
||||
rawEP.SendPacketWithTS(b[start:start+mss], tsVal)
|
||||
packetsSent++
|
||||
}
|
||||
|
||||
// Resume the worker so that it only sees the packets once all of them
|
||||
// are waiting to be read.
|
||||
worker.ResumeWork()
|
||||
@@ -5508,7 +5514,7 @@ func TestReceiveBufferAutoTuning(t *testing.T) {
|
||||
stk := c.Stack()
|
||||
// Set lower limits for auto-tuning tests. This is required because the
|
||||
// test stops the worker which can cause packets to be dropped because
|
||||
// the segment queue holding unprocessed packets is limited to 500.
|
||||
// the segment queue holding unprocessed packets is limited to 300.
|
||||
const receiveBufferSize = 80 << 10 // 80KB.
|
||||
const maxReceiveBufferSize = receiveBufferSize * 10
|
||||
if err := stk.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.ReceiveBufferSizeOption{1, receiveBufferSize, maxReceiveBufferSize}); err != nil {
|
||||
@@ -5563,6 +5569,7 @@ func TestReceiveBufferAutoTuning(t *testing.T) {
|
||||
totalSent += mss
|
||||
packetsSent++
|
||||
}
|
||||
|
||||
// Resume it so that it only sees the packets once all of them
|
||||
// are waiting to be read.
|
||||
worker.ResumeWork()
|
||||
|
||||
@@ -533,7 +533,7 @@ TEST_P(SocketInetLoopbackTest, TCPFinWait2Test_NoRandomSave) {
|
||||
|
||||
// Sleep for a little over the linger timeout to reduce flakiness in
|
||||
// save/restore tests.
|
||||
absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 1));
|
||||
absl::SleepFor(absl::Seconds(kTCPLingerTimeout + 2));
|
||||
|
||||
ds.reset();
|
||||
|
||||
|
||||
@@ -814,6 +814,20 @@ TEST_P(TcpSocketTest, FullBuffer) {
|
||||
t_ = -1;
|
||||
}
|
||||
|
||||
TEST_P(TcpSocketTest, PollAfterShutdown) {
|
||||
ScopedThread client_thread([this]() {
|
||||
EXPECT_THAT(shutdown(s_, SHUT_WR), SyscallSucceedsWithValue(0));
|
||||
struct pollfd poll_fd = {s_, POLLIN | POLLERR | POLLHUP, 0};
|
||||
EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000),
|
||||
SyscallSucceedsWithValue(1));
|
||||
});
|
||||
|
||||
EXPECT_THAT(shutdown(t_, SHUT_WR), SyscallSucceedsWithValue(0));
|
||||
struct pollfd poll_fd = {t_, POLLIN | POLLERR | POLLHUP, 0};
|
||||
EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10000),
|
||||
SyscallSucceedsWithValue(1));
|
||||
}
|
||||
|
||||
TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) {
|
||||
// Initialize address to the loopback one.
|
||||
sockaddr_storage addr =
|
||||
|
||||
Reference in New Issue
Block a user