From 901d9a75d3163f630f1d0c45f9de7aa95f59252f Mon Sep 17 00:00:00 2001 From: Kevin Krakauer Date: Tue, 8 Nov 2022 16:31:37 -0800 Subject: [PATCH] netstack: add gro_flush_timeout Makes a per-interface file available to configure the GRO timeout, e.g. /sys/class/net/eth0/gro_flush_timeout PiperOrigin-RevId: 487082821 --- pkg/sentry/fsimpl/sys/BUILD | 2 + pkg/sentry/fsimpl/sys/net.go | 93 +++++++++++++++++++++++++++++ pkg/sentry/fsimpl/sys/sys.go | 2 + pkg/sentry/inet/inet.go | 8 +++ pkg/sentry/inet/test_stack.go | 13 ++++ pkg/sentry/socket/hostinet/stack.go | 12 ++++ pkg/sentry/socket/netstack/stack.go | 12 ++++ pkg/tcpip/stack/stack.go | 27 +++++++++ 8 files changed, 169 insertions(+) create mode 100644 pkg/sentry/fsimpl/sys/net.go diff --git a/pkg/sentry/fsimpl/sys/BUILD b/pkg/sentry/fsimpl/sys/BUILD index 146839f0b..7edc41616 100644 --- a/pkg/sentry/fsimpl/sys/BUILD +++ b/pkg/sentry/fsimpl/sys/BUILD @@ -19,6 +19,7 @@ go_library( srcs = [ "dir_refs.go", "kcov.go", + "net.go", "sys.go", ], visibility = ["//pkg/sentry:internal"], @@ -33,6 +34,7 @@ go_library( "//pkg/refsvfs2", "//pkg/sentry/arch", "//pkg/sentry/fsimpl/kernfs", + "//pkg/sentry/inet", "//pkg/sentry/kernel", "//pkg/sentry/kernel/auth", "//pkg/sentry/memmap", diff --git a/pkg/sentry/fsimpl/sys/net.go b/pkg/sentry/fsimpl/sys/net.go new file mode 100644 index 000000000..d48f0f31e --- /dev/null +++ b/pkg/sentry/fsimpl/sys/net.go @@ -0,0 +1,93 @@ +// Copyright 2022 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 sys + +import ( + "bytes" + "fmt" + "time" + + "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs" + "gvisor.dev/gvisor/pkg/sentry/inet" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + "gvisor.dev/gvisor/pkg/sentry/vfs" + "gvisor.dev/gvisor/pkg/usermem" +) + +// newNetDir returns a directory containing a subdirectory for each network +// interface. +func (fs *filesystem) newNetDir(ctx context.Context, creds *auth.Credentials, mode linux.FileMode) map[string]kernfs.Inode { + // Get list of interfaces. + stk := inet.StackFromContext(ctx) + if stk == nil { + return map[string]kernfs.Inode{} + } + + subDirs := make(map[string]kernfs.Inode) + for idx, iface := range stk.Interfaces() { + subDirs[iface.Name] = fs.newIfaceDir(ctx, creds, mode, idx, stk) + } + return subDirs +} + +// newIfaceDir returns a directory containing per-interface files. +func (fs *filesystem) newIfaceDir(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, idx int32, stk inet.Stack) kernfs.Inode { + files := map[string]kernfs.Inode{ + "gro_flush_timeout": fs.newGROTimeoutFile(ctx, creds, mode, idx, stk), + } + return fs.newDir(ctx, creds, mode, files) +} + +// groTimeoutFile enables the reading and writing of the GRO timeout. +// +// +stateify savable +type groTimeoutFile struct { + implStatFS + kernfs.DynamicBytesFile + + idx int32 + stk inet.Stack +} + +// newGROTimeoutFile returns a file that can be used to read and set the GRO +// timeout. +func (fs *filesystem) newGROTimeoutFile(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, idx int32, stk inet.Stack) kernfs.Inode { + file := groTimeoutFile{idx: idx, stk: stk} + file.DynamicBytesFile.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), &file, mode) + return &file +} + +// Generate implements vfs.DynamicBytesSource.Generate. +func (gf *groTimeoutFile) Generate(ctx context.Context, buf *bytes.Buffer) error { + timeout, err := gf.stk.GROTimeout(gf.idx) + if err != nil { + return err + } + fmt.Fprintf(buf, "%d\n", timeout.Nanoseconds()) + return nil +} + +// Write implements vfs.WritableDynamicBytesSource.Write. +func (gf *groTimeoutFile) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) { + val := []int32{0} + nRead, err := usermem.CopyInt32StringsInVec(ctx, src.IO, src.Addrs, val, src.Opts) + if err != nil { + return 0, err + } + gf.stk.SetGROTimeout(gf.idx, time.Duration(val[0])*time.Nanosecond) + return nRead, nil +} diff --git a/pkg/sentry/fsimpl/sys/sys.go b/pkg/sentry/fsimpl/sys/sys.go index 9f9be7905..1c1a361e8 100644 --- a/pkg/sentry/fsimpl/sys/sys.go +++ b/pkg/sentry/fsimpl/sys/sys.go @@ -108,12 +108,14 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt classSub := map[string]kernfs.Inode{ "power_supply": fs.newDir(ctx, creds, defaultSysDirMode, nil), + "net": fs.newDir(ctx, creds, defaultSysDirMode, fs.newNetDir(ctx, creds, defaultSysDirMode)), } devicesSub := map[string]kernfs.Inode{ "system": fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{ "cpu": cpuDir(ctx, fs, creds), }), } + productName := "" if opts.InternalData != nil { data := opts.InternalData.(*InternalData) diff --git a/pkg/sentry/inet/inet.go b/pkg/sentry/inet/inet.go index c081a7b48..f7c566778 100644 --- a/pkg/sentry/inet/inet.go +++ b/pkg/sentry/inet/inet.go @@ -16,6 +16,8 @@ package inet import ( + "time" + "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/stack" @@ -108,6 +110,12 @@ type Stack interface { // SetPortRange sets the UDP and TCP IPv4 and IPv6 ephemeral port range // (inclusive). SetPortRange(start uint16, end uint16) error + + // GROTimeout returns the GRO timeout. + GROTimeout(NICID int32) (time.Duration, error) + + // GROTimeout sets the GRO timeout. + SetGROTimeout(NICID int32, timeout time.Duration) error } // Interface contains information about a network interface. diff --git a/pkg/sentry/inet/test_stack.go b/pkg/sentry/inet/test_stack.go index 6be707b6a..9e29f76a8 100644 --- a/pkg/sentry/inet/test_stack.go +++ b/pkg/sentry/inet/test_stack.go @@ -17,6 +17,7 @@ package inet import ( "bytes" "fmt" + "time" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/stack" @@ -184,3 +185,15 @@ func (*TestStack) SetPortRange(start uint16, end uint16) error { // No-op. return nil } + +// GROTimeout implements Stack. +func (*TestStack) GROTimeout(NICID int32) (time.Duration, error) { + // No-op. + return 0, nil +} + +// SetGROTimeout implements Stack. +func (*TestStack) SetGROTimeout(NICID int32, timeout time.Duration) error { + // No-op. + return nil +} diff --git a/pkg/sentry/socket/hostinet/stack.go b/pkg/sentry/socket/hostinet/stack.go index 3dc2b380b..53288f7e6 100644 --- a/pkg/sentry/socket/hostinet/stack.go +++ b/pkg/sentry/socket/hostinet/stack.go @@ -24,6 +24,7 @@ import ( "strconv" "strings" "syscall" + "time" "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" @@ -515,3 +516,14 @@ func (*Stack) PortRange() (uint16, uint16) { func (*Stack) SetPortRange(uint16, uint16) error { return linuxerr.EACCES } + +// GROTimeout implements inet.Stack.GROTimeout. +func (s *Stack) GROTimeout(NICID int32) (time.Duration, error) { + return 0, nil +} + +// SetGROTimeout implements inet.Stack.SetGROTimeout. +func (s *Stack) SetGROTimeout(NICID int32, timeout time.Duration) error { + // We don't support setting the hostinet GRO timeout. + return linuxerr.EINVAL +} diff --git a/pkg/sentry/socket/netstack/stack.go b/pkg/sentry/socket/netstack/stack.go index cdbd761fd..70e1c519d 100644 --- a/pkg/sentry/socket/netstack/stack.go +++ b/pkg/sentry/socket/netstack/stack.go @@ -16,6 +16,7 @@ package netstack import ( "fmt" + "time" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/errors/linuxerr" @@ -491,3 +492,14 @@ func (s *Stack) PortRange() (uint16, uint16) { func (s *Stack) SetPortRange(start uint16, end uint16) error { return syserr.TranslateNetstackError(s.Stack.SetPortRange(start, end)).ToError() } + +// GROTimeout implements inet.Stack.GROTimeout. +func (s *Stack) GROTimeout(NICID int32) (time.Duration, error) { + timeout, err := s.Stack.GROTimeout(NICID) + return timeout, syserr.TranslateNetstackError(err).ToError() +} + +// SetGROTimeout implements inet.Stack.SetGROTimeout. +func (s *Stack) SetGROTimeout(NICID int32, timeout time.Duration) error { + return syserr.TranslateNetstackError(s.Stack.SetGROTimeout(NICID, timeout)).ToError() +} diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index a6ce8fa2b..677f5e097 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -710,6 +710,33 @@ func (s *Stack) SetPortRange(start uint16, end uint16) tcpip.Error { return s.PortManager.SetPortRange(start, end) } +// GROTimeout returns the GRO timeout. +func (s *Stack) GROTimeout(NICID int32) (time.Duration, tcpip.Error) { + s.mu.RLock() + defer s.mu.RUnlock() + + nic, ok := s.nics[tcpip.NICID(NICID)] + if !ok { + return 0, &tcpip.ErrUnknownNICID{} + } + + return nic.gro.getInterval(), nil +} + +// SetGROTimeout sets the GRO timeout. +func (s *Stack) SetGROTimeout(NICID int32, timeout time.Duration) tcpip.Error { + s.mu.RLock() + defer s.mu.RUnlock() + + nic, ok := s.nics[tcpip.NICID(NICID)] + if !ok { + return &tcpip.ErrUnknownNICID{} + } + + nic.gro.setInterval(timeout) + return nil +} + // SetRouteTable assigns the route table to be used by this stack. It // specifies which NIC to use for given destination address ranges. //