mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Store endpoints inside multiPortEndpoint in a sorted order
It is required to guarantee the same order of endpoints after save/restore. PiperOrigin-RevId: 277598665
This commit is contained in:
@@ -60,6 +60,9 @@ const (
|
||||
// TransportEndpoint is the interface that needs to be implemented by transport
|
||||
// protocol (e.g., tcp, udp) endpoints that can handle packets.
|
||||
type TransportEndpoint interface {
|
||||
// UniqueID returns an unique ID for this transport endpoint.
|
||||
UniqueID() uint64
|
||||
|
||||
// HandlePacket is called by the stack when new packets arrive to
|
||||
// this transport endpoint.
|
||||
HandlePacket(r *Route, id TransportEndpointID, vv buffer.VectorisedView)
|
||||
|
||||
@@ -22,6 +22,7 @@ package stack
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
@@ -344,6 +345,13 @@ type ResumableEndpoint interface {
|
||||
Resume(*Stack)
|
||||
}
|
||||
|
||||
// uniqueIDGenerator is a default unique ID generator.
|
||||
type uniqueIDGenerator uint64
|
||||
|
||||
func (u *uniqueIDGenerator) UniqueID() uint64 {
|
||||
return atomic.AddUint64((*uint64)(u), 1)
|
||||
}
|
||||
|
||||
// Stack is a networking stack, with all supported protocols, NICs, and route
|
||||
// table.
|
||||
type Stack struct {
|
||||
@@ -411,6 +419,14 @@ type Stack struct {
|
||||
// ndpDisp is the NDP event dispatcher that is used to send the netstack
|
||||
// integrator NDP related events.
|
||||
ndpDisp NDPDispatcher
|
||||
|
||||
// uniqueIDGenerator is a generator of unique identifiers.
|
||||
uniqueIDGenerator UniqueID
|
||||
}
|
||||
|
||||
// UniqueID is an abstract generator of unique identifiers.
|
||||
type UniqueID interface {
|
||||
UniqueID() uint64
|
||||
}
|
||||
|
||||
// Options contains optional Stack configuration.
|
||||
@@ -434,6 +450,9 @@ type Options struct {
|
||||
// stack (false).
|
||||
HandleLocal bool
|
||||
|
||||
// UniqueID is an optional generator of unique identifiers.
|
||||
UniqueID UniqueID
|
||||
|
||||
// NDPConfigs is the default NDP configurations used by interfaces.
|
||||
//
|
||||
// By default, NDPConfigs will have a zero value for its
|
||||
@@ -506,6 +525,10 @@ func New(opts Options) *Stack {
|
||||
clock = &tcpip.StdClock{}
|
||||
}
|
||||
|
||||
if opts.UniqueID == nil {
|
||||
opts.UniqueID = new(uniqueIDGenerator)
|
||||
}
|
||||
|
||||
// Make sure opts.NDPConfigs contains valid values only.
|
||||
opts.NDPConfigs.validate()
|
||||
|
||||
@@ -524,6 +547,7 @@ func New(opts Options) *Stack {
|
||||
portSeed: generateRandUint32(),
|
||||
ndpConfigs: opts.NDPConfigs,
|
||||
autoGenIPv6LinkLocal: opts.AutoGenIPv6LinkLocal,
|
||||
uniqueIDGenerator: opts.UniqueID,
|
||||
ndpDisp: opts.NDPDisp,
|
||||
}
|
||||
|
||||
@@ -551,6 +575,11 @@ func New(opts Options) *Stack {
|
||||
return s
|
||||
}
|
||||
|
||||
// UniqueID returns a unique identifier.
|
||||
func (s *Stack) UniqueID() uint64 {
|
||||
return s.uniqueIDGenerator.UniqueID()
|
||||
}
|
||||
|
||||
// SetNetworkProtocolOption allows configuring individual protocol level
|
||||
// options. This method returns an error if the protocol is not supported or
|
||||
// option is not supported by the protocol implementation or the provided value
|
||||
|
||||
@@ -17,6 +17,7 @@ package stack
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
@@ -310,6 +311,15 @@ func (ep *multiPortEndpoint) singleRegisterEndpoint(t TransportEndpoint, reusePo
|
||||
// endpointsMap. This will allow us to remove endpoint from the array fast.
|
||||
ep.endpointsMap[t] = len(ep.endpointsArr)
|
||||
ep.endpointsArr = append(ep.endpointsArr, t)
|
||||
|
||||
// ep.endpointsArr is sorted by endpoint unique IDs, so that endpoints
|
||||
// can be restored in the same order.
|
||||
sort.Slice(ep.endpointsArr, func(i, j int) bool {
|
||||
return ep.endpointsArr[i].UniqueID() < ep.endpointsArr[j].UniqueID()
|
||||
})
|
||||
for i, e := range ep.endpointsArr {
|
||||
ep.endpointsMap[e] = i
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ type fakeTransportEndpoint struct {
|
||||
proto *fakeTransportProtocol
|
||||
peerAddr tcpip.Address
|
||||
route stack.Route
|
||||
uniqueID uint64
|
||||
|
||||
// acceptQueue is non-nil iff bound.
|
||||
acceptQueue []fakeTransportEndpoint
|
||||
@@ -56,8 +57,8 @@ func (f *fakeTransportEndpoint) Stats() tcpip.EndpointStats {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newFakeTransportEndpoint(s *stack.Stack, proto *fakeTransportProtocol, netProto tcpip.NetworkProtocolNumber) tcpip.Endpoint {
|
||||
return &fakeTransportEndpoint{stack: s, TransportEndpointInfo: stack.TransportEndpointInfo{NetProto: netProto}, proto: proto}
|
||||
func newFakeTransportEndpoint(s *stack.Stack, proto *fakeTransportProtocol, netProto tcpip.NetworkProtocolNumber, uniqueID uint64) tcpip.Endpoint {
|
||||
return &fakeTransportEndpoint{stack: s, TransportEndpointInfo: stack.TransportEndpointInfo{NetProto: netProto}, proto: proto, uniqueID: uniqueID}
|
||||
}
|
||||
|
||||
func (f *fakeTransportEndpoint) Close() {
|
||||
@@ -144,6 +145,10 @@ func (f *fakeTransportEndpoint) Connect(addr tcpip.FullAddress) *tcpip.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeTransportEndpoint) UniqueID() uint64 {
|
||||
return f.uniqueID
|
||||
}
|
||||
|
||||
func (f *fakeTransportEndpoint) ConnectEndpoint(e tcpip.Endpoint) *tcpip.Error {
|
||||
return nil
|
||||
}
|
||||
@@ -251,7 +256,7 @@ func (*fakeTransportProtocol) Number() tcpip.TransportProtocolNumber {
|
||||
}
|
||||
|
||||
func (f *fakeTransportProtocol) NewEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, _ *waiter.Queue) (tcpip.Endpoint, *tcpip.Error) {
|
||||
return newFakeTransportEndpoint(stack, f, netProto), nil
|
||||
return newFakeTransportEndpoint(stack, f, netProto, stack.UniqueID()), nil
|
||||
}
|
||||
|
||||
func (f *fakeTransportProtocol) NewRawEndpoint(stack *stack.Stack, netProto tcpip.NetworkProtocolNumber, _ *waiter.Queue) (tcpip.Endpoint, *tcpip.Error) {
|
||||
|
||||
@@ -58,6 +58,7 @@ type endpoint struct {
|
||||
// immutable.
|
||||
stack *stack.Stack `state:"manual"`
|
||||
waiterQueue *waiter.Queue
|
||||
uniqueID uint64
|
||||
|
||||
// The following fields are used to manage the receive queue, and are
|
||||
// protected by rcvMu.
|
||||
@@ -90,9 +91,15 @@ func newEndpoint(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, transProt
|
||||
rcvBufSizeMax: 32 * 1024,
|
||||
sndBufSize: 32 * 1024,
|
||||
state: stateInitial,
|
||||
uniqueID: s.UniqueID(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UniqueID implements stack.TransportEndpoint.UniqueID.
|
||||
func (e *endpoint) UniqueID() uint64 {
|
||||
return e.uniqueID
|
||||
}
|
||||
|
||||
// Close puts the endpoint in a closed state and frees all resources
|
||||
// associated with it.
|
||||
func (e *endpoint) Close() {
|
||||
|
||||
@@ -287,6 +287,7 @@ type endpoint struct {
|
||||
// change throughout the lifetime of the endpoint.
|
||||
stack *stack.Stack `state:"manual"`
|
||||
waiterQueue *waiter.Queue `state:"wait"`
|
||||
uniqueID uint64
|
||||
|
||||
// lastError represents the last error that the endpoint reported;
|
||||
// access to it is protected by the following mutex.
|
||||
@@ -504,6 +505,11 @@ type endpoint struct {
|
||||
stats Stats `state:"nosave"`
|
||||
}
|
||||
|
||||
// UniqueID implements stack.TransportEndpoint.UniqueID.
|
||||
func (e *endpoint) UniqueID() uint64 {
|
||||
return e.uniqueID
|
||||
}
|
||||
|
||||
// calculateAdvertisedMSS calculates the MSS to advertise.
|
||||
//
|
||||
// If userMSS is non-zero and is not greater than the maximum possible MSS for
|
||||
@@ -565,6 +571,7 @@ func newEndpoint(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, waiterQue
|
||||
interval: 75 * time.Second,
|
||||
count: 9,
|
||||
},
|
||||
uniqueID: s.UniqueID(),
|
||||
}
|
||||
|
||||
var ss SendBufferSizeOption
|
||||
|
||||
@@ -80,6 +80,7 @@ type endpoint struct {
|
||||
// change throughout the lifetime of the endpoint.
|
||||
stack *stack.Stack `state:"manual"`
|
||||
waiterQueue *waiter.Queue
|
||||
uniqueID uint64
|
||||
|
||||
// The following fields are used to manage the receive queue, and are
|
||||
// protected by rcvMu.
|
||||
@@ -160,9 +161,15 @@ func newEndpoint(s *stack.Stack, netProto tcpip.NetworkProtocolNumber, waiterQue
|
||||
rcvBufSizeMax: 32 * 1024,
|
||||
sndBufSize: 32 * 1024,
|
||||
state: StateInitial,
|
||||
uniqueID: s.UniqueID(),
|
||||
}
|
||||
}
|
||||
|
||||
// UniqueID implements stack.TransportEndpoint.UniqueID.
|
||||
func (e *endpoint) UniqueID() uint64 {
|
||||
return e.uniqueID
|
||||
}
|
||||
|
||||
// Close puts the endpoint in a closed state and frees all resources
|
||||
// associated with it.
|
||||
func (e *endpoint) Close() {
|
||||
|
||||
@@ -232,7 +232,7 @@ func New(args Args) (*Loader, error) {
|
||||
// this point. Netns is configured before Run() is called. Netstack is
|
||||
// configured using a control uRPC message. Host network is configured inside
|
||||
// Run().
|
||||
networkStack, err := newEmptyNetworkStack(args.Conf, k)
|
||||
networkStack, err := newEmptyNetworkStack(args.Conf, k, k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating network: %v", err)
|
||||
}
|
||||
@@ -905,7 +905,7 @@ func (l *Loader) WaitExit() kernel.ExitStatus {
|
||||
return l.k.GlobalInit().ExitStatus()
|
||||
}
|
||||
|
||||
func newEmptyNetworkStack(conf *Config, clock tcpip.Clock) (inet.Stack, error) {
|
||||
func newEmptyNetworkStack(conf *Config, clock tcpip.Clock, uniqueID stack.UniqueID) (inet.Stack, error) {
|
||||
switch conf.Network {
|
||||
case NetworkHost:
|
||||
return hostinet.NewStack(), nil
|
||||
@@ -923,6 +923,7 @@ func newEmptyNetworkStack(conf *Config, clock tcpip.Clock) (inet.Stack, error) {
|
||||
// Enable raw sockets for users with sufficient
|
||||
// privileges.
|
||||
RawFactory: raw.EndpointFactory{},
|
||||
UniqueID: uniqueID,
|
||||
})}
|
||||
|
||||
// Enable SACK Recovery.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <atomic>
|
||||
@@ -516,6 +517,112 @@ TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThread) {
|
||||
EquivalentWithin((kConnectAttempts / kThreadCount), 0.10));
|
||||
}
|
||||
|
||||
TEST_P(SocketInetReusePortTest, UdpPortReuseMultiThreadShort) {
|
||||
auto const& param = GetParam();
|
||||
|
||||
TestAddress const& listener = param.listener;
|
||||
TestAddress const& connector = param.connector;
|
||||
sockaddr_storage listen_addr = listener.addr;
|
||||
sockaddr_storage conn_addr = connector.addr;
|
||||
constexpr int kThreadCount = 3;
|
||||
|
||||
// TODO(b/141211329): endpointsByNic.seed has to be saved/restored.
|
||||
const DisableSave ds141211329;
|
||||
|
||||
// Create listening sockets.
|
||||
FileDescriptor listener_fds[kThreadCount];
|
||||
for (int i = 0; i < kThreadCount; i++) {
|
||||
listener_fds[i] =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(listener.family(), SOCK_DGRAM, 0));
|
||||
int fd = listener_fds[i].get();
|
||||
|
||||
ASSERT_THAT(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &kSockOptOn,
|
||||
sizeof(kSockOptOn)),
|
||||
SyscallSucceeds());
|
||||
ASSERT_THAT(
|
||||
bind(fd, reinterpret_cast<sockaddr*>(&listen_addr), listener.addr_len),
|
||||
SyscallSucceeds());
|
||||
|
||||
// On the first bind we need to determine which port was bound.
|
||||
if (i != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the port bound by the listening socket.
|
||||
socklen_t addrlen = listener.addr_len;
|
||||
ASSERT_THAT(
|
||||
getsockname(listener_fds[0].get(),
|
||||
reinterpret_cast<sockaddr*>(&listen_addr), &addrlen),
|
||||
SyscallSucceeds());
|
||||
uint16_t const port =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(AddrPort(listener.family(), listen_addr));
|
||||
ASSERT_NO_ERRNO(SetAddrPort(listener.family(), &listen_addr, port));
|
||||
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
|
||||
}
|
||||
|
||||
constexpr int kConnectAttempts = 10;
|
||||
FileDescriptor client_fds[kConnectAttempts];
|
||||
|
||||
// Do the first run without save/restore.
|
||||
DisableSave ds;
|
||||
for (int i = 0; i < kConnectAttempts; i++) {
|
||||
client_fds[i] =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Socket(connector.family(), SOCK_DGRAM, 0));
|
||||
EXPECT_THAT(RetryEINTR(sendto)(client_fds[i].get(), &i, sizeof(i), 0,
|
||||
reinterpret_cast<sockaddr*>(&conn_addr),
|
||||
connector.addr_len),
|
||||
SyscallSucceedsWithValue(sizeof(i)));
|
||||
}
|
||||
ds.reset();
|
||||
|
||||
// Check that a mapping of client and server sockets has
|
||||
// not been change after save/restore.
|
||||
for (int i = 0; i < kConnectAttempts; i++) {
|
||||
EXPECT_THAT(RetryEINTR(sendto)(client_fds[i].get(), &i, sizeof(i), 0,
|
||||
reinterpret_cast<sockaddr*>(&conn_addr),
|
||||
connector.addr_len),
|
||||
SyscallSucceedsWithValue(sizeof(i)));
|
||||
}
|
||||
|
||||
int epollfd;
|
||||
ASSERT_THAT(epollfd = epoll_create1(0), SyscallSucceeds());
|
||||
|
||||
for (int i = 0; i < kThreadCount; i++) {
|
||||
int fd = listener_fds[i].get();
|
||||
struct epoll_event ev;
|
||||
ev.data.fd = fd;
|
||||
ev.events = EPOLLIN;
|
||||
ASSERT_THAT(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev), SyscallSucceeds());
|
||||
}
|
||||
|
||||
std::map<uint16_t, int> portToFD;
|
||||
|
||||
for (int i = 0; i < kConnectAttempts * 2; i++) {
|
||||
struct sockaddr_storage addr = {};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
struct epoll_event ev;
|
||||
int data, fd;
|
||||
|
||||
ASSERT_THAT(epoll_wait(epollfd, &ev, 1, -1), SyscallSucceedsWithValue(1));
|
||||
|
||||
fd = ev.data.fd;
|
||||
EXPECT_THAT(RetryEINTR(recvfrom)(fd, &data, sizeof(data), 0,
|
||||
reinterpret_cast<struct sockaddr*>(&addr),
|
||||
&addrlen),
|
||||
SyscallSucceedsWithValue(sizeof(data)));
|
||||
uint16_t const port =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(AddrPort(connector.family(), addr));
|
||||
auto prev_port = portToFD.find(port);
|
||||
// Check that all packets from one client have been delivered to the same
|
||||
// server socket.
|
||||
if (prev_port == portToFD.end()) {
|
||||
portToFD[port] = ev.data.fd;
|
||||
} else {
|
||||
EXPECT_EQ(portToFD[port], ev.data.fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
All, SocketInetReusePortTest,
|
||||
::testing::Values(
|
||||
|
||||
Reference in New Issue
Block a user