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:
Nayana Bidari
2020-08-05 20:50:06 -07:00
committed by gVisor bot
parent 7ed4b2b5a6
commit 35312a95c4
8 changed files with 268 additions and 1 deletions
+95
View File
@@ -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)
}
+48 -1
View File
@@ -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
}
+17
View File
@@ -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
)
+12
View File
@@ -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
+11
View File
@@ -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 {
+14
View File
@@ -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) {
+33
View File
@@ -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
+38
View File
@@ -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