Merge pull request #3651 from ianlewis:ip-forwarding

PiperOrigin-RevId: 332760843
This commit is contained in:
gVisor bot
2020-09-20 18:17:20 -07:00
25 changed files with 514 additions and 52 deletions
+1
View File
@@ -50,6 +50,7 @@ go_library(
"//pkg/sync",
"//pkg/syserror",
"//pkg/tcpip/header",
"//pkg/tcpip/network/ipv4",
"//pkg/usermem",
"//pkg/waiter",
],
+119 -1
View File
@@ -26,6 +26,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/pkg/waiter"
)
@@ -54,7 +55,7 @@ type tcpMemInode struct {
// size stores the tcp buffer size during save, and sets the buffer
// size in netstack in restore. We must save/restore this here, since
// netstack itself is stateless.
// a netstack instance is created on restore.
size inet.TCPBufferSize
// mu protects against concurrent reads/writes to files based on this
@@ -258,6 +259,9 @@ func (f *tcpSackFile) Write(ctx context.Context, _ *fs.File, src usermem.IOSeque
if src.NumBytes() == 0 {
return 0, nil
}
// Only consider size of one memory page for input for performance reasons.
// We are only reading if it's zero or not anyway.
src = src.TakeFirst(usermem.PageSize - 1)
var v int32
@@ -383,11 +387,125 @@ func (p *proc) newSysNetCore(ctx context.Context, msrc *fs.MountSource, s inet.S
return newProcInode(ctx, d, msrc, fs.SpecialDirectory, nil)
}
// ipForwarding implements fs.InodeOperations.
//
// ipForwarding is used to enable/disable packet forwarding of netstack.
//
// +stateify savable
type ipForwarding struct {
fsutil.SimpleFileInode
stack inet.Stack `state:"wait"`
// enabled stores the IPv4 forwarding state on save.
// We must save/restore this here, since a netstack instance
// is created on restore.
enabled *bool
}
func newIPForwardingInode(ctx context.Context, msrc *fs.MountSource, s inet.Stack) *fs.Inode {
ipf := &ipForwarding{
SimpleFileInode: *fsutil.NewSimpleFileInode(ctx, fs.RootOwner, fs.FilePermsFromMode(0444), 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, ipf, msrc, sattr)
}
// Truncate implements fs.InodeOperations.Truncate. Truncate is called when
// O_TRUNC is specified for any kind of existing Dirent but is not called via
// (f)truncate for proc files.
func (*ipForwarding) Truncate(context.Context, *fs.Inode, int64) error {
return nil
}
// +stateify savable
type ipForwardingFile struct {
fsutil.FileGenericSeek `state:"nosave"`
fsutil.FileNoIoctl `state:"nosave"`
fsutil.FileNoMMap `state:"nosave"`
fsutil.FileNoSplice `state:"nosave"`
fsutil.FileNoopFlush `state:"nosave"`
fsutil.FileNoopFsync `state:"nosave"`
fsutil.FileNoopRelease `state:"nosave"`
fsutil.FileNotDirReaddir `state:"nosave"`
fsutil.FileUseInodeUnstableAttr `state:"nosave"`
waiter.AlwaysReady `state:"nosave"`
ipf *ipForwarding
stack inet.Stack `state:"wait"`
}
// GetFile implements fs.InodeOperations.GetFile.
func (ipf *ipForwarding) 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, &ipForwardingFile{
stack: ipf.stack,
ipf: ipf,
}), nil
}
// Read implements fs.FileOperations.Read.
func (f *ipForwardingFile) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, offset int64) (int64, error) {
if offset != 0 {
return 0, io.EOF
}
if f.ipf.enabled == nil {
enabled := f.stack.Forwarding(ipv4.ProtocolNumber)
f.ipf.enabled = &enabled
}
val := "0\n"
if *f.ipf.enabled {
// Technically, this is not quite compatible with Linux. Linux
// stores these as an integer, so if you write "2" into
// ip_forward, you should get 2 back.
val = "1\n"
}
n, err := dst.CopyOut(ctx, []byte(val))
return int64(n), err
}
// Write implements fs.FileOperations.Write.
//
// Offset is ignored, multiple writes are not supported.
func (f *ipForwardingFile) Write(ctx context.Context, _ *fs.File, src usermem.IOSequence, offset int64) (int64, error) {
if src.NumBytes() == 0 {
return 0, nil
}
// Only consider size of one memory page for input for performance reasons.
// We are only reading if it's zero or not anyway.
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 n, err
}
if f.ipf.enabled == nil {
f.ipf.enabled = new(bool)
}
*f.ipf.enabled = v != 0
return n, f.stack.SetForwarding(ipv4.ProtocolNumber, *f.ipf.enabled)
}
func (p *proc) newSysNetIPv4Dir(ctx context.Context, msrc *fs.MountSource, s inet.Stack) *fs.Inode {
contents := map[string]*fs.Inode{
// Add tcp_sack.
"tcp_sack": newTCPSackInode(ctx, msrc, s),
// Add ip_forward.
"ip_forward": newIPForwardingInode(ctx, msrc, s),
// The following files are simple stubs until they are
// implemented in netstack, most of these files are
// configuration related. We use the value closest to the
+14 -1
View File
@@ -14,7 +14,11 @@
package proc
import "fmt"
import (
"fmt"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
)
// beforeSave is invoked by stateify.
func (t *tcpMemInode) beforeSave() {
@@ -40,3 +44,12 @@ func (s *tcpSack) afterLoad() {
}
}
}
// afterLoad is invoked by stateify.
func (ipf *ipForwarding) afterLoad() {
if ipf.enabled != nil {
if err := ipf.stack.SetForwarding(ipv4.ProtocolNumber, *ipf.enabled); err != nil {
panic(fmt.Sprintf("failed to set IPv4 forwarding [%v]: %v", *ipf.enabled, err))
}
}
}
+73
View File
@@ -123,3 +123,76 @@ func TestConfigureRecvBufferSize(t *testing.T) {
}
}
}
// TestIPForwarding tests the implementation of
// /proc/sys/net/ipv4/ip_forwarding
func TestIPForwarding(t *testing.T) {
ctx := context.Background()
s := inet.NewTestStack()
var cases = []struct {
comment string
initial bool
str string
final bool
}{
{
comment: `Forwarding is disabled; write 1 and enable forwarding`,
initial: false,
str: "1",
final: true,
},
{
comment: `Forwarding is disabled; write 0 and disable forwarding`,
initial: false,
str: "0",
final: false,
},
{
comment: `Forwarding is enabled; write 1 and enable forwarding`,
initial: true,
str: "1",
final: true,
},
{
comment: `Forwarding is enabled; write 0 and disable forwarding`,
initial: true,
str: "0",
final: false,
},
{
comment: `Forwarding is disabled; write 2404 and enable forwarding`,
initial: false,
str: "2404",
final: true,
},
{
comment: `Forwarding is enabled; write 2404 and enable forwarding`,
initial: true,
str: "2404",
final: true,
},
}
for _, c := range cases {
t.Run(c.comment, func(t *testing.T) {
s.IPForwarding = c.initial
ipf := &ipForwarding{stack: s}
file := &ipForwardingFile{
stack: s,
ipf: ipf,
}
// Write the values.
src := usermem.BytesIOSequence([]byte(c.str))
if n, err := file.Write(ctx, nil, src, 0); n != int64(len(c.str)) || err != nil {
t.Errorf("file.Write(ctx, nil, %q, 0) = (%d, %v); want (%d, nil)", c.str, n, err, len(c.str))
}
// Read the values from the stack and check them.
if got, want := s.IPForwarding, c.final; got != want {
t.Errorf("s.IPForwarding incorrect; got: %v, want: %v", got, want)
}
})
}
}
+1
View File
@@ -100,6 +100,7 @@ go_library(
"//pkg/sync",
"//pkg/syserror",
"//pkg/tcpip/header",
"//pkg/tcpip/network/ipv4",
"//pkg/usermem",
],
)
+62
View File
@@ -27,6 +27,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/usermem"
)
@@ -67,6 +68,7 @@ func (fs *filesystem) newSysNetDir(root *auth.Credentials, k *kernel.Kernel) *ke
"tcp_rmem": fs.newDentry(root, fs.NextIno(), 0644, &tcpMemData{stack: stack, dir: tcpRMem}),
"tcp_sack": fs.newDentry(root, fs.NextIno(), 0644, &tcpSackData{stack: stack}),
"tcp_wmem": fs.newDentry(root, fs.NextIno(), 0644, &tcpMemData{stack: stack, dir: tcpWMem}),
"ip_forward": fs.newDentry(root, fs.NextIno(), 0444, &ipForwarding{stack: stack}),
// The following files are simple stubs until they are implemented in
// netstack, most of these files are configuration related. We use the
@@ -354,3 +356,63 @@ func (d *tcpMemData) writeSizeLocked(size inet.TCPBufferSize) error {
panic(fmt.Sprintf("unknown tcpMemFile type: %v", d.dir))
}
}
// ipForwarding implements vfs.WritableDynamicBytesSource for
// /proc/sys/net/ipv4/ip_forwarding.
//
// +stateify savable
type ipForwarding struct {
kernfs.DynamicBytesFile
stack inet.Stack `state:"wait"`
enabled *bool
}
var _ vfs.WritableDynamicBytesSource = (*ipForwarding)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (ipf *ipForwarding) Generate(ctx context.Context, buf *bytes.Buffer) error {
if ipf.enabled == nil {
enabled := ipf.stack.Forwarding(ipv4.ProtocolNumber)
ipf.enabled = &enabled
}
val := "0\n"
if *ipf.enabled {
// Technically, this is not quite compatible with Linux. Linux stores these
// as an integer, so if you write "2" into tcp_sack, you should get 2 back.
// Tough luck.
val = "1\n"
}
buf.WriteString(val)
return nil
}
// Write implements vfs.WritableDynamicBytesSource.Write.
func (ipf *ipForwarding) 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 input size so as not to impact performance if input size is large.
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 ipf.enabled == nil {
ipf.enabled = new(bool)
}
*ipf.enabled = v != 0
if err := ipf.stack.SetForwarding(ipv4.ProtocolNumber, *ipf.enabled); err != nil {
return 0, err
}
return n, nil
}
+71
View File
@@ -20,8 +20,10 @@ import (
"testing"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/contexttest"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/usermem"
)
func newIPv6TestStack() *inet.TestStack {
@@ -76,3 +78,72 @@ func TestIfinet6(t *testing.T) {
t.Errorf("Got n.contents() = %v, want = %v", got, want)
}
}
// TestIPForwarding tests the implementation of
// /proc/sys/net/ipv4/ip_forwarding
func TestConfigureIPForwarding(t *testing.T) {
ctx := context.Background()
s := inet.NewTestStack()
var cases = []struct {
comment string
initial bool
str string
final bool
}{
{
comment: `Forwarding is disabled; write 1 and enable forwarding`,
initial: false,
str: "1",
final: true,
},
{
comment: `Forwarding is disabled; write 0 and disable forwarding`,
initial: false,
str: "0",
final: false,
},
{
comment: `Forwarding is enabled; write 1 and enable forwarding`,
initial: true,
str: "1",
final: true,
},
{
comment: `Forwarding is enabled; write 0 and disable forwarding`,
initial: true,
str: "0",
final: false,
},
{
comment: `Forwarding is disabled; write 2404 and enable forwarding`,
initial: false,
str: "2404",
final: true,
},
{
comment: `Forwarding is enabled; write 2404 and enable forwarding`,
initial: true,
str: "2404",
final: true,
},
}
for _, c := range cases {
t.Run(c.comment, func(t *testing.T) {
s.IPForwarding = c.initial
file := &ipForwarding{stack: s, enabled: &c.initial}
// Write the values.
src := usermem.BytesIOSequence([]byte(c.str))
if n, err := file.Write(ctx, src, 0); n != int64(len(c.str)) || err != nil {
t.Errorf("file.Write(ctx, nil, %q, 0) = (%d, %v); want (%d, nil)", c.str, n, err, len(c.str))
}
// Read the values from the stack and check them.
if got, want := s.IPForwarding, c.final; got != want {
t.Errorf("s.IPForwarding incorrect; got: %v, want: %v", got, want)
}
})
}
}
+1
View File
@@ -15,6 +15,7 @@ go_library(
],
deps = [
"//pkg/context",
"//pkg/tcpip",
"//pkg/tcpip/stack",
],
)
+10 -1
View File
@@ -15,7 +15,10 @@
// Package inet defines semantics for IP stacks.
package inet
import "gvisor.dev/gvisor/pkg/tcpip/stack"
import (
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// Stack represents a TCP/IP stack.
type Stack interface {
@@ -80,6 +83,12 @@ type Stack interface {
// RestoreCleanupEndpoints adds endpoints to cleanup tracking. This is useful
// for restoring a stack after a save.
RestoreCleanupEndpoints([]stack.TransportEndpoint)
// Forwarding returns if packet forwarding between NICs is enabled.
Forwarding(protocol tcpip.NetworkProtocolNumber) bool
// SetForwarding enables or disables packet forwarding between NICs.
SetForwarding(protocol tcpip.NetworkProtocolNumber, enable bool) error
}
// Interface contains information about a network interface.
+16 -1
View File
@@ -14,7 +14,10 @@
package inet
import "gvisor.dev/gvisor/pkg/tcpip/stack"
import (
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// TestStack is a dummy implementation of Stack for tests.
type TestStack struct {
@@ -26,6 +29,7 @@ type TestStack struct {
TCPSendBufSize TCPBufferSize
TCPSACKFlag bool
Recovery TCPLossRecovery
IPForwarding bool
}
// NewTestStack returns a TestStack with no network interfaces. The value of
@@ -128,3 +132,14 @@ func (s *TestStack) CleanupEndpoints() []stack.TransportEndpoint {
// RestoreCleanupEndpoints implements inet.Stack.RestoreCleanupEndpoints.
func (s *TestStack) RestoreCleanupEndpoints([]stack.TransportEndpoint) {}
// Forwarding implements inet.Stack.Forwarding.
func (s *TestStack) Forwarding(protocol tcpip.NetworkProtocolNumber) bool {
return s.IPForwarding
}
// SetForwarding implements inet.Stack.SetForwarding.
func (s *TestStack) SetForwarding(protocol tcpip.NetworkProtocolNumber, enable bool) error {
s.IPForwarding = enable
return nil
}
+3
View File
@@ -39,6 +39,9 @@ go_library(
"//pkg/sentry/vfs",
"//pkg/syserr",
"//pkg/syserror",
"//pkg/tcpip",
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6",
"//pkg/tcpip/stack",
"//pkg/usermem",
"//pkg/waiter",
+30
View File
@@ -30,6 +30,9 @@ import (
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/syserror"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/usermem"
)
@@ -59,6 +62,8 @@ type Stack struct {
tcpSACKEnabled bool
netDevFile *os.File
netSNMPFile *os.File
ipv4Forwarding bool
ipv6Forwarding bool
}
// NewStack returns an empty Stack containing no configuration.
@@ -118,6 +123,13 @@ func (s *Stack) Configure() error {
s.netSNMPFile = f
}
s.ipv6Forwarding = false
if ipForwarding, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/forwarding"); err == nil {
s.ipv6Forwarding = strings.TrimSpace(string(ipForwarding)) != "0"
} else {
log.Warningf("Failed to read if ipv6 forwarding is enabled, setting to false")
}
return nil
}
@@ -468,3 +480,21 @@ func (s *Stack) CleanupEndpoints() []stack.TransportEndpoint { return nil }
// RestoreCleanupEndpoints implements inet.Stack.RestoreCleanupEndpoints.
func (s *Stack) RestoreCleanupEndpoints([]stack.TransportEndpoint) {}
// Forwarding implements inet.Stack.Forwarding.
func (s *Stack) Forwarding(protocol tcpip.NetworkProtocolNumber) bool {
switch protocol {
case ipv4.ProtocolNumber:
return s.ipv4Forwarding
case ipv6.ProtocolNumber:
return s.ipv6Forwarding
default:
log.Warningf("Forwarding(%v) failed: unsupported protocol", protocol)
return false
}
}
// SetForwarding implements inet.Stack.SetForwarding.
func (s *Stack) SetForwarding(protocol tcpip.NetworkProtocolNumber, enable bool) error {
return syserror.EACCES
}
+21
View File
@@ -412,3 +412,24 @@ func (s *Stack) CleanupEndpoints() []stack.TransportEndpoint {
func (s *Stack) RestoreCleanupEndpoints(es []stack.TransportEndpoint) {
s.Stack.RestoreCleanupEndpoints(es)
}
// Forwarding implements inet.Stack.Forwarding.
func (s *Stack) Forwarding(protocol tcpip.NetworkProtocolNumber) bool {
switch protocol {
case ipv4.ProtocolNumber, ipv6.ProtocolNumber:
return s.Stack.Forwarding(protocol)
default:
panic(fmt.Sprintf("Forwarding(%v) failed: unsupported protocol", protocol))
}
}
// SetForwarding implements inet.Stack.SetForwarding.
func (s *Stack) SetForwarding(protocol tcpip.NetworkProtocolNumber, enable bool) error {
switch protocol {
case ipv4.ProtocolNumber, ipv6.ProtocolNumber:
s.Stack.SetForwarding(protocol, enable)
default:
panic(fmt.Sprintf("SetForwarding(%v) failed: unsupported protocol", protocol))
}
return nil
}
+3 -1
View File
@@ -14,6 +14,8 @@ go_library(
go_test(
name = "buffer_test",
size = "small",
srcs = ["view_test.go"],
srcs = [
"view_test.go",
],
library = ":buffer",
)
+1 -1
View File
@@ -477,7 +477,7 @@ func (e *endpoint) handleICMP(r *stack.Route, pkt *stack.PacketBuffer, hasFragme
stack := r.Stack()
// Is the networking stack operating as a router?
if !stack.Forwarding() {
if !stack.Forwarding(ProtocolNumber) {
// ... No, silently drop the packet.
received.RouterOnlyPacketsDroppedByHost.Increment()
return
+1 -1
View File
@@ -728,7 +728,7 @@ func TestICMPChecksumValidationSimple(t *testing.T) {
})
if isRouter {
// Enabling forwarding makes the stack act as a router.
s.SetForwarding(true)
s.SetForwarding(ProtocolNumber, true)
}
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(_, _) = %s", err)
+1 -1
View File
@@ -958,7 +958,7 @@ func TestNDPValidation(t *testing.T) {
if isRouter {
// Enabling forwarding makes the stack act as a router.
s.SetForwarding(true)
s.SetForwarding(ProtocolNumber, true)
}
stats := s.Stats().ICMP.V6PacketsReceived
+1 -1
View File
@@ -316,7 +316,7 @@ func fwdTestNetFactory(t *testing.T, proto *fwdTestNetworkProtocol, useNeighborC
}
// Enable forwarding.
s.SetForwarding(true)
s.SetForwarding(proto.Number(), true)
// NIC 1 has the link address "a", and added the network address 1.
ep1 = &fwdTestLinkEndpoint{
+1 -1
View File
@@ -817,7 +817,7 @@ func (ndp *ndpState) handleRA(ip tcpip.Address, ra header.NDPRouterAdvert) {
// per-interface basis; it is a stack-wide configuration, so we check
// stack's forwarding flag to determine if the NIC is a routing
// interface.
if !ndp.configs.HandleRAs || ndp.nic.stack.forwarding {
if !ndp.configs.HandleRAs || ndp.nic.stack.Forwarding(header.IPv6ProtocolNumber) {
return
}
+6 -6
View File
@@ -1120,7 +1120,7 @@ func TestNoRouterDiscovery(t *testing.T) {
},
NDPDisp: &ndpDisp,
})
s.SetForwarding(forwarding)
s.SetForwarding(ipv6.ProtocolNumber, forwarding)
if err := s.CreateNIC(1, e); err != nil {
t.Fatalf("CreateNIC(1) = %s", err)
@@ -1365,7 +1365,7 @@ func TestNoPrefixDiscovery(t *testing.T) {
},
NDPDisp: &ndpDisp,
})
s.SetForwarding(forwarding)
s.SetForwarding(ipv6.ProtocolNumber, forwarding)
if err := s.CreateNIC(1, e); err != nil {
t.Fatalf("CreateNIC(1) = %s", err)
@@ -1723,7 +1723,7 @@ func TestNoAutoGenAddr(t *testing.T) {
},
NDPDisp: &ndpDisp,
})
s.SetForwarding(forwarding)
s.SetForwarding(ipv6.ProtocolNumber, forwarding)
if err := s.CreateNIC(1, e); err != nil {
t.Fatalf("CreateNIC(1) = %s", err)
@@ -4640,7 +4640,7 @@ func TestCleanupNDPState(t *testing.T) {
name: "Enable forwarding",
cleanupFn: func(t *testing.T, s *stack.Stack) {
t.Helper()
s.SetForwarding(true)
s.SetForwarding(ipv6.ProtocolNumber, true)
},
keepAutoGenLinkLocal: true,
maxAutoGenAddrEvents: 4,
@@ -5286,11 +5286,11 @@ func TestStopStartSolicitingRouters(t *testing.T) {
name: "Enable and disable forwarding",
startFn: func(t *testing.T, s *stack.Stack) {
t.Helper()
s.SetForwarding(false)
s.SetForwarding(ipv6.ProtocolNumber, false)
},
stopFn: func(t *testing.T, s *stack.Stack, _ bool) {
t.Helper()
s.SetForwarding(true)
s.SetForwarding(ipv6.ProtocolNumber, true)
},
},

Some files were not shown because too many files have changed in this diff Show More