mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add loss recovery option for TCP.
/proc/sys/net/ipv4/tcp_recovery is used to enable RACK loss recovery in TCP. PiperOrigin-RevId: 325157807
This commit is contained in:
committed by
gVisor bot
parent
7ed4b2b5a6
commit
35312a95c4
@@ -272,6 +272,96 @@ func (f *tcpSackFile) Write(ctx context.Context, _ *fs.File, src usermem.IOSeque
|
||||
return n, f.tcpSack.stack.SetTCPSACKEnabled(*f.tcpSack.enabled)
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type tcpRecovery struct {
|
||||
fsutil.SimpleFileInode
|
||||
|
||||
stack inet.Stack `state:"wait"`
|
||||
recovery inet.TCPLossRecovery
|
||||
}
|
||||
|
||||
func newTCPRecoveryInode(ctx context.Context, msrc *fs.MountSource, s inet.Stack) *fs.Inode {
|
||||
ts := &tcpRecovery{
|
||||
SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0644), linux.PROC_SUPER_MAGIC),
|
||||
stack: s,
|
||||
}
|
||||
sattr := fs.StableAttr{
|
||||
DeviceID: device.ProcDevice.DeviceID(),
|
||||
InodeID: device.ProcDevice.NextIno(),
|
||||
BlockSize: usermem.PageSize,
|
||||
Type: fs.SpecialFile,
|
||||
}
|
||||
return fs.NewInode(ctx, ts, msrc, sattr)
|
||||
}
|
||||
|
||||
// Truncate implements fs.InodeOperations.Truncate.
|
||||
func (*tcpRecovery) Truncate(context.Context, *fs.Inode, int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFile implements fs.InodeOperations.GetFile.
|
||||
func (r *tcpRecovery) GetFile(ctx context.Context, dirent *fs.Dirent, flags fs.FileFlags) (*fs.File, error) {
|
||||
flags.Pread = true
|
||||
flags.Pwrite = true
|
||||
return fs.NewFile(ctx, dirent, flags, &tcpRecoveryFile{
|
||||
tcpRecovery: r,
|
||||
stack: r.stack,
|
||||
}), nil
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type tcpRecoveryFile struct {
|
||||
fsutil.FileGenericSeek `state:"nosave"`
|
||||
fsutil.FileNoIoctl `state:"nosave"`
|
||||
fsutil.FileNoMMap `state:"nosave"`
|
||||
fsutil.FileNoSplice `state:"nosave"`
|
||||
fsutil.FileNoopRelease `state:"nosave"`
|
||||
fsutil.FileNoopFlush `state:"nosave"`
|
||||
fsutil.FileNoopFsync `state:"nosave"`
|
||||
fsutil.FileNotDirReaddir `state:"nosave"`
|
||||
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
|
||||
waiter.AlwaysReady `state:"nosave"`
|
||||
|
||||
tcpRecovery *tcpRecovery
|
||||
|
||||
stack inet.Stack `state:"wait"`
|
||||
}
|
||||
|
||||
// Read implements fs.FileOperations.Read.
|
||||
func (f *tcpRecoveryFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) {
|
||||
if offset != 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
recovery, err := f.stack.TCPRecovery()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f.tcpRecovery.recovery = recovery
|
||||
s := fmt.Sprintf("%d\n", f.tcpRecovery.recovery)
|
||||
n, err := dst.CopyOut(ctx, []byte(s))
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// Write implements fs.FileOperations.Write.
|
||||
func (f *tcpRecoveryFile) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, offset int64) (int64, error) {
|
||||
if src.NumBytes() == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
src = src.TakeFirst(usermem.PageSize - 1)
|
||||
|
||||
var v int32
|
||||
n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f.tcpRecovery.recovery = inet.TCPLossRecovery(v)
|
||||
if err := f.tcpRecovery.stack.SetTCPRecovery(f.tcpRecovery.recovery); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *proc) newSysNetCore(ctx context.Context, msrc *fs.MountSource, s inet.Stack) *fs.Inode {
|
||||
// The following files are simple stubs until they are implemented in
|
||||
// netstack, most of these files are configuration related. We use the
|
||||
@@ -351,6 +441,11 @@ func (p *proc) newSysNetIPv4Dir(ctx context.Context, msrc *fs.MountSource, s ine
|
||||
contents["tcp_wmem"] = newTCPMemInode(ctx, msrc, s, tcpWMem)
|
||||
}
|
||||
|
||||
// Add tcp_recovery.
|
||||
if _, err := s.TCPRecovery(); err == nil {
|
||||
contents["tcp_recovery"] = newTCPRecoveryInode(ctx, msrc, s)
|
||||
}
|
||||
|
||||
d := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0555))
|
||||
return newProcInode(ctx, d, msrc, fs.SpecialDirectory, nil)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,8 @@ func (fs *filesystem) newSysNetDir(root *auth.Credentials, k *kernel.Kernel) *ke
|
||||
if stack := k.RootNetworkNamespace().Stack(); stack != nil {
|
||||
contents = map[string]*kernfs.Dentry{
|
||||
"ipv4": kernfs.NewStaticDir(root, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), 0555, map[string]*kernfs.Dentry{
|
||||
"tcp_sack": fs.newDentry(root, fs.NextIno(), 0644, &tcpSackData{stack: stack}),
|
||||
"tcp_recovery": fs.newDentry(root, fs.NextIno(), 0644, &tcpRecoveryData{stack: stack}),
|
||||
"tcp_sack": fs.newDentry(root, fs.NextIno(), 0644, &tcpSackData{stack: stack}),
|
||||
|
||||
// The following files are simple stubs until they are implemented in
|
||||
// netstack, most of these files are configuration related. We use the
|
||||
@@ -207,3 +208,49 @@ func (d *tcpSackData) Write(ctx context.Context, src usermem.IOSequence, offset
|
||||
*d.enabled = v != 0
|
||||
return n, d.stack.SetTCPSACKEnabled(*d.enabled)
|
||||
}
|
||||
|
||||
// tcpRecoveryData implements vfs.WritableDynamicBytesSource for
|
||||
// /proc/sys/net/ipv4/tcp_recovery.
|
||||
//
|
||||
// +stateify savable
|
||||
type tcpRecoveryData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
stack inet.Stack `state:"wait"`
|
||||
}
|
||||
|
||||
var _ vfs.WritableDynamicBytesSource = (*tcpRecoveryData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.
|
||||
func (d *tcpRecoveryData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
recovery, err := d.stack.TCPRecovery()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%d\n", recovery))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *tcpRecoveryData) Write(ctx context.Context, src usermem.IOSequence, offset int64) (int64, error) {
|
||||
if offset != 0 {
|
||||
// No need to handle partial writes thus far.
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
if src.NumBytes() == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Limit the amount of memory allocated.
|
||||
src = src.TakeFirst(usermem.PageSize - 1)
|
||||
|
||||
var v int32
|
||||
n, err := usermem.CopyInt32StringInVec(ctx, src.IO, src.Addrs, &v, src.Opts)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := d.stack.SetTCPRecovery(inet.TCPLossRecovery(v)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@@ -56,6 +56,12 @@ type Stack interface {
|
||||
// settings.
|
||||
SetTCPSACKEnabled(enabled bool) error
|
||||
|
||||
// TCPRecovery returns the TCP loss detection algorithm.
|
||||
TCPRecovery() (TCPLossRecovery, error)
|
||||
|
||||
// SetTCPRecovery attempts to change TCP loss detection algorithm.
|
||||
SetTCPRecovery(recovery TCPLossRecovery) error
|
||||
|
||||
// Statistics reports stack statistics.
|
||||
Statistics(stat interface{}, arg string) error
|
||||
|
||||
@@ -189,3 +195,14 @@ type StatSNMPUDP [8]uint64
|
||||
|
||||
// StatSNMPUDPLite describes UdpLite line of /proc/net/snmp.
|
||||
type StatSNMPUDPLite [8]uint64
|
||||
|
||||
// TCPLossRecovery indicates TCP loss detection and recovery methods to use.
|
||||
type TCPLossRecovery int32
|
||||
|
||||
// Loss recovery constants from include/net/tcp.h which are used to set
|
||||
// /proc/sys/net/ipv4/tcp_recovery.
|
||||
const (
|
||||
TCP_RACK_LOSS_DETECTION TCPLossRecovery = 1 << iota
|
||||
TCP_RACK_STATIC_REO_WND
|
||||
TCP_RACK_NO_DUPTHRESH
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ type TestStack struct {
|
||||
TCPRecvBufSize TCPBufferSize
|
||||
TCPSendBufSize TCPBufferSize
|
||||
TCPSACKFlag bool
|
||||
Recovery TCPLossRecovery
|
||||
}
|
||||
|
||||
// NewTestStack returns a TestStack with no network interfaces. The value of
|
||||
@@ -91,6 +92,17 @@ func (s *TestStack) SetTCPSACKEnabled(enabled bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TCPRecovery implements Stack.TCPRecovery.
|
||||
func (s *TestStack) TCPRecovery() (TCPLossRecovery, error) {
|
||||
return s.Recovery, nil
|
||||
}
|
||||
|
||||
// SetTCPRecovery implements Stack.SetTCPRecovery.
|
||||
func (s *TestStack) SetTCPRecovery(recovery TCPLossRecovery) error {
|
||||
s.Recovery = recovery
|
||||
return nil
|
||||
}
|
||||
|
||||
// Statistics implements inet.Stack.Statistics.
|
||||
func (s *TestStack) Statistics(stat interface{}, arg string) error {
|
||||
return nil
|
||||
|
||||
@@ -53,6 +53,7 @@ type Stack struct {
|
||||
interfaceAddrs map[int32][]inet.InterfaceAddr
|
||||
routes []inet.Route
|
||||
supportsIPv6 bool
|
||||
tcpRecovery inet.TCPLossRecovery
|
||||
tcpRecvBufSize inet.TCPBufferSize
|
||||
tcpSendBufSize inet.TCPBufferSize
|
||||
tcpSACKEnabled bool
|
||||
@@ -350,6 +351,16 @@ func (s *Stack) SetTCPSACKEnabled(enabled bool) error {
|
||||
return syserror.EACCES
|
||||
}
|
||||
|
||||
// TCPRecovery implements inet.Stack.TCPRecovery.
|
||||
func (s *Stack) TCPRecovery() (inet.TCPLossRecovery, error) {
|
||||
return s.tcpRecovery, nil
|
||||
}
|
||||
|
||||
// SetTCPRecovery implements inet.Stack.SetTCPRecovery.
|
||||
func (s *Stack) SetTCPRecovery(recovery inet.TCPLossRecovery) error {
|
||||
return syserror.EACCES
|
||||
}
|
||||
|
||||
// getLine reads one line from proc file, with specified prefix.
|
||||
// The last argument, withHeader, specifies if it contains line header.
|
||||
func getLine(f *os.File, prefix string, withHeader bool) string {
|
||||
|
||||
@@ -207,6 +207,20 @@ func (s *Stack) SetTCPSACKEnabled(enabled bool) error {
|
||||
return syserr.TranslateNetstackError(s.Stack.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.SACKEnabled(enabled))).ToError()
|
||||
}
|
||||
|
||||
// TCPRecovery implements inet.Stack.TCPRecovery.
|
||||
func (s *Stack) TCPRecovery() (inet.TCPLossRecovery, error) {
|
||||
var recovery tcp.Recovery
|
||||
if err := s.Stack.TransportProtocolOption(tcp.ProtocolNumber, &recovery); err != nil {
|
||||
return 0, syserr.TranslateNetstackError(err).ToError()
|
||||
}
|
||||
return inet.TCPLossRecovery(recovery), nil
|
||||
}
|
||||
|
||||
// SetTCPRecovery implements inet.Stack.SetTCPRecovery.
|
||||
func (s *Stack) SetTCPRecovery(recovery inet.TCPLossRecovery) error {
|
||||
return syserr.TranslateNetstackError(s.Stack.SetTransportProtocolOption(tcp.ProtocolNumber, tcp.Recovery(recovery))).ToError()
|
||||
}
|
||||
|
||||
// Statistics implements inet.Stack.Statistics.
|
||||
func (s *Stack) Statistics(stat interface{}, arg string) error {
|
||||
switch stats := stat.(type) {
|
||||
|
||||
@@ -80,6 +80,25 @@ const (
|
||||
// enable/disable SACK support in TCP. See: https://tools.ietf.org/html/rfc2018.
|
||||
type SACKEnabled bool
|
||||
|
||||
// Recovery is used by stack.(*Stack).TransportProtocolOption to
|
||||
// set loss detection algorithm in TCP.
|
||||
type Recovery int32
|
||||
|
||||
const (
|
||||
// RACKLossDetection indicates RACK is used for loss detection and
|
||||
// recovery.
|
||||
RACKLossDetection Recovery = 1 << iota
|
||||
|
||||
// RACKStaticReoWnd indicates the reordering window should not be
|
||||
// adjusted when DSACK is received.
|
||||
RACKStaticReoWnd
|
||||
|
||||
// RACKNoDupTh indicates RACK should not consider the classic three
|
||||
// duplicate acknowledgements rule to mark the segments as lost. This
|
||||
// is used when reordering is not detected.
|
||||
RACKNoDupTh
|
||||
)
|
||||
|
||||
// DelayEnabled is used by stack.(Stack*).TransportProtocolOption to
|
||||
// enable/disable Nagle's algorithm in TCP.
|
||||
type DelayEnabled bool
|
||||
@@ -161,6 +180,7 @@ func (s *synRcvdCounter) Threshold() uint64 {
|
||||
type protocol struct {
|
||||
mu sync.RWMutex
|
||||
sackEnabled bool
|
||||
recovery Recovery
|
||||
delayEnabled bool
|
||||
sendBufferSize SendBufferSizeOption
|
||||
recvBufferSize ReceiveBufferSizeOption
|
||||
@@ -280,6 +300,12 @@ func (p *protocol) SetOption(option interface{}) *tcpip.Error {
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
|
||||
case Recovery:
|
||||
p.mu.Lock()
|
||||
p.recovery = Recovery(v)
|
||||
p.mu.Unlock()
|
||||
return nil
|
||||
|
||||
case DelayEnabled:
|
||||
p.mu.Lock()
|
||||
p.delayEnabled = bool(v)
|
||||
@@ -394,6 +420,12 @@ func (p *protocol) Option(option interface{}) *tcpip.Error {
|
||||
p.mu.RUnlock()
|
||||
return nil
|
||||
|
||||
case *Recovery:
|
||||
p.mu.RLock()
|
||||
*v = Recovery(p.recovery)
|
||||
p.mu.RUnlock()
|
||||
return nil
|
||||
|
||||
case *DelayEnabled:
|
||||
p.mu.RLock()
|
||||
*v = DelayEnabled(p.delayEnabled)
|
||||
@@ -535,6 +567,7 @@ func NewProtocol() stack.TransportProtocol {
|
||||
minRTO: MinRTO,
|
||||
maxRTO: MaxRTO,
|
||||
maxRetries: MaxRetries,
|
||||
recovery: RACKLossDetection,
|
||||
}
|
||||
p.dispatcher.init(runtime.GOMAXPROCS(0))
|
||||
return &p
|
||||
|
||||
@@ -477,6 +477,44 @@ TEST(ProcNetSnmp, CheckSnmp) {
|
||||
EXPECT_EQ(value_count, 1);
|
||||
}
|
||||
|
||||
TEST(ProcSysNetIpv4Recovery, Exists) {
|
||||
EXPECT_THAT(open("/proc/sys/net/ipv4/tcp_recovery", O_RDONLY),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
TEST(ProcSysNetIpv4Recovery, CanReadAndWrite) {
|
||||
// TODO(b/162988252): Enable save/restore for this test after the bug is
|
||||
// fixed.
|
||||
DisableSave ds;
|
||||
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE))));
|
||||
|
||||
auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Open("/proc/sys/net/ipv4/tcp_recovery", O_RDWR));
|
||||
|
||||
char buf[10] = {'\0'};
|
||||
char to_write = '2';
|
||||
|
||||
// Check initial value is set to 1.
|
||||
EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0),
|
||||
SyscallSucceedsWithValue(sizeof(to_write) + 1));
|
||||
EXPECT_EQ(strcmp(buf, "1\n"), 0);
|
||||
|
||||
// Set tcp_recovery to one of the allowed constants.
|
||||
EXPECT_THAT(PwriteFd(fd.get(), &to_write, sizeof(to_write), 0),
|
||||
SyscallSucceedsWithValue(sizeof(to_write)));
|
||||
EXPECT_THAT(PreadFd(fd.get(), &buf, sizeof(buf), 0),
|
||||
SyscallSucceedsWithValue(sizeof(to_write) + 1));
|
||||
EXPECT_EQ(strcmp(buf, "2\n"), 0);
|
||||
|
||||
// Set tcp_recovery to any random value.
|
||||
char kMessage[] = "100";
|
||||
EXPECT_THAT(PwriteFd(fd.get(), kMessage, strlen(kMessage), 0),
|
||||
SyscallSucceedsWithValue(strlen(kMessage)));
|
||||
EXPECT_THAT(PreadFd(fd.get(), buf, sizeof(kMessage), 0),
|
||||
SyscallSucceedsWithValue(sizeof(kMessage)));
|
||||
EXPECT_EQ(strcmp(buf, "100\n"), 0);
|
||||
}
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
Reference in New Issue
Block a user