Files
visor/pkg/sentry/socket/netstack/stack.go
T
Nayana BidariandgVisor bot 3846731234 Refactor network configuration during restore and defer link creation in runsc
This change refactors the network configuration flow during sandbox restore
and defers the creation of network links and routes in runsc to run after
seccomp filters are installed.

- Replaced ReplaceConfig with ResetConfig in inet.Stack. Introduced
inet.NetworkArgs to configure the stack during restore, updating k.LoadFrom
to use it.
- Renamed CreateLinksAndRoutes URPC to SetNetworkArgs, which now configures
host sockets before seccomp is installed, deferring link/route creation to run
after seccomp.
- Fixed S/R support by saving and restoring limit/burst state.
- Minor seccomp filter adjustments and test updates.

PiperOrigin-RevId: 945995430
2026-07-10 18:52:31 -07:00

1175 lines
34 KiB
Go

// Copyright 2018 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 netstack
import (
"fmt"
"maps"
"slices"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/ethernet"
"gvisor.dev/gvisor/pkg/tcpip/link/packetsocket"
"gvisor.dev/gvisor/pkg/tcpip/link/veth"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
)
// Stack implements inet.Stack for netstack/tcpip/stack.Stack.
//
// +stateify savable
type Stack struct {
Stack *stack.Stack `state:".(*stack.Stack)"`
eventSubscriber inet.InterfaceEventSubscriber
// linkMu serializes link creation, modification and deletion.
// It is a rough parallel to the per-netns rtnl_mutex in Linux.
linkMu netstackLinkMutex `state:"nosave"`
// id is a unique identifier for this stack, it is currently only
// used for a deterministic lock ordering.
id uint64
}
// NewStack creates a new netstack.Stack which wraps the given tcpip.Stack.
func NewStack(s *stack.Stack, id uint64) *Stack {
return &Stack{
Stack: s,
id: id,
}
}
// AddInterfaceEventSubscriber implements inet.InterfaceEventPublisher.AddInterfaceEventSubscriber.
func (s *Stack) AddInterfaceEventSubscriber(sub inet.InterfaceEventSubscriber) {
if s.eventSubscriber != nil {
panic("AddInterfaceEventSubscriber called twice: multiple subscribers yet to be supported")
}
s.eventSubscriber = sub
}
func makeInterfaceInfo(ni *stack.NICInfo) inet.Interface {
return inet.Interface{
Name: ni.Name,
Addr: []byte(ni.LinkAddress),
Flags: uint32(nicStateFlagsToLinux(ni.Flags)),
DeviceType: toLinuxARPHardwareType(ni.ARPHardwareType),
MTU: ni.MTU,
Master: uint32(ni.Primary),
}
}
func (s *Stack) sendChangeEvent(ctx context.Context, id tcpip.NICID) {
if s.eventSubscriber == nil {
return
}
if nicInfo, ok := s.Stack.SingleNICInfo(id); ok {
s.eventSubscriber.OnInterfaceChangeEvent(ctx, int32(id), makeInterfaceInfo(nicInfo))
}
}
func (s *Stack) sendDeleteEvent(ctx context.Context, id tcpip.NICID, nicInfo *stack.NICInfo) {
if s.eventSubscriber == nil {
return
}
s.eventSubscriber.OnInterfaceDeleteEvent(ctx, int32(id), makeInterfaceInfo(nicInfo))
}
// Destroy implements inet.Stack.Destroy.
func (s *Stack) Destroy() {
if s.Stack != nil {
s.Stack.Close()
refs.CleanupSync.Add(1)
go func() {
s.Stack.Wait()
refs.CleanupSync.Done()
}()
}
}
// SupportsIPv6 implements Stack.SupportsIPv6.
func (s *Stack) SupportsIPv6() bool {
return s.Stack.CheckNetworkProtocol(ipv6.ProtocolNumber)
}
// Converts Netstack's ARPHardwareType to equivalent linux constants.
func toLinuxARPHardwareType(t header.ARPHardwareType) uint16 {
switch t {
case header.ARPHardwareNone:
return linux.ARPHRD_NONE
case header.ARPHardwareLoopback:
return linux.ARPHRD_LOOPBACK
case header.ARPHardwareEther:
return linux.ARPHRD_ETHER
default:
panic(fmt.Sprintf("unknown ARPHRD type: %d", t))
}
}
// Interfaces implements inet.Stack.Interfaces.
func (s *Stack) Interfaces() map[int32]inet.Interface {
is := make(map[int32]inet.Interface)
for id, ni := range s.Stack.NICInfo() {
is[int32(id)] = makeInterfaceInfo(&ni)
}
return is
}
// InterfaceIDs implements inet.Stack.InterfaceIDs.
func (s *Stack) InterfaceIDs() []int32 {
// Since gVisor allocates NIC IDs monotonically (like Linux ifindex),
// sorting by ID is equivalent to registration order.
return slices.Sorted(maps.Keys(s.Interfaces()))
}
// RemoveInterface implements inet.Stack.RemoveInterface.
func (s *Stack) RemoveInterface(ctx context.Context, idx int32) error {
s.linkMu.Lock()
defer s.linkMu.Unlock()
nic := tcpip.NICID(idx)
nicInfo, ok := s.Stack.SingleNICInfo(nic)
if !ok {
return syserr.ErrUnknownNICID.ToError()
}
// Don't allow removing the loopback interface.
if nicInfo.Flags.Loopback {
return syserr.ErrNotSupported.ToError()
}
if err := syserr.TranslateNetstackError(s.Stack.RemoveNIC(nic)); err != nil {
return err.ToError()
}
s.sendDeleteEvent(ctx, nic, nicInfo)
return nil
}
// SetInterface implements inet.Stack.SetInterface.
func (s *Stack) SetInterface(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
var ifinfomsg linux.InterfaceInfoMessage
attrsView, ok := msg.GetData(&ifinfomsg)
if !ok {
return syserr.ErrInvalidArgument
}
attrs, ok := attrsView.Parse()
if !ok {
return syserr.ErrInvalidArgument
}
ifname := ""
for attr := range attrs {
value := attrs[attr]
switch attr {
case linux.IFLA_IFNAME:
if len(value) < 1 {
return syserr.ErrInvalidArgument
}
if ifinfomsg.Index == 0 {
ifname = value.String()
for idx, ifa := range s.Interfaces() {
if ifname == ifa.Name {
ifinfomsg.Index = idx
break
}
}
}
case linux.IFLA_MASTER:
case linux.IFLA_LINKINFO:
case linux.IFLA_ADDRESS:
case linux.IFLA_MTU:
case linux.IFLA_NET_NS_FD:
case linux.IFLA_TXQLEN:
default:
ctx.Warningf("unexpected attribute: %x", attr)
return syserr.ErrNotSupported
}
}
flags := msg.Header().Flags
if ifinfomsg.Index == 0 {
if flags&linux.NLM_F_CREATE != 0 {
return s.newInterface(ctx, msg, attrs)
}
return syserr.ErrNoDevice
}
if flags&(linux.NLM_F_EXCL|linux.NLM_F_REPLACE) != 0 {
return syserr.ErrExists
}
if ifinfomsg.Flags != 0 || ifinfomsg.Change != 0 {
if ifinfomsg.Change & ^uint32(linux.IFF_UP) != 0 {
ctx.Warningf("Unsupported ifi_change flags: %x", ifinfomsg.Change)
return syserr.ErrInvalidArgument
}
if ifinfomsg.Flags & ^uint32(linux.IFF_UP) != 0 {
ctx.Warningf("Unsupported ifi_flags: %x", ifinfomsg.Flags)
return syserr.ErrInvalidArgument
}
// Netstack interfaces are always up.
}
dstNs, err := s.lockSrcAndDst(ctx, attrs)
if err != nil {
return err
}
defer s.unlockSrcAndDst(ctx, dstNs)
return s.setLinkLocked(ctx, tcpip.NICID(ifinfomsg.Index), attrs, dstNs)
}
// If the linkAttrs map contains IFLA_NET_NS_FD, locks the source and
// destination stacks and returns the destination netns, which must
// be passed into unlockSrcAndDst by the caller.
//
// If instead the linkAttrs map does not contain IFLA_NET_NS_FD, or if it
// points to the same netns as the source stack, only locks the source stack
// and returns nil.
//
// If the netns fd is invalid, or the calling context lacks CAP_NET_ADMIN,
// returns an error.
func (s *Stack) lockSrcAndDst(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesView) (*inet.Namespace, *syserr.Error) {
if linkAttrs == nil {
s.linkMu.Lock()
return nil, nil
}
v, ok := linkAttrs[linux.IFLA_NET_NS_FD]
if !ok {
s.linkMu.Lock()
return nil, nil
}
fd, ok := v.Uint32()
if !ok {
return nil, syserr.ErrInvalidArgument
}
f := inet.NamespaceByFDFromContext(ctx)
if f == nil {
return nil, syserr.ErrInvalidArgument
}
ns, err := f(int32(fd))
if err != nil {
return nil, syserr.FromError(err)
}
if !auth.CredentialsFromContext(ctx).HasCapabilityIn(linux.CAP_NET_ADMIN, ns.UserNamespace()) {
ns.DecRef(ctx)
return nil, syserr.ErrNotPermittedNet
}
dst := ns.Stack().(*Stack)
if s == dst {
ns.DecRef(ctx)
s.linkMu.Lock()
return nil, nil
}
// No failures from this point on, ns.DecRef() is called in unlockSrcAndDst().
if s.id < dst.id {
s.linkMu.Lock()
dst.linkMu.NestedLock(netstackLinkLockDeststack)
} else {
dst.linkMu.Lock()
s.linkMu.NestedLock(netstackLinkLockDeststack)
}
return ns, nil
}
func (s *Stack) unlockSrcAndDst(ctx context.Context, ns *inet.Namespace) {
if ns == nil {
s.linkMu.Unlock()
return
}
dst := ns.Stack().(*Stack)
if s == dst {
// This cannot happen because lockSrcAndDst() returns nil in this case.
panic("unlockSrcAndDst called with dst == src")
}
if s.id < dst.id {
dst.linkMu.NestedUnlock(netstackLinkLockDeststack)
s.linkMu.Unlock()
} else {
s.linkMu.NestedUnlock(netstackLinkLockDeststack)
dst.linkMu.Unlock()
}
ns.DecRef(ctx)
}
// precondition: s.linkLock is held. If dstNs is not nil, dstNs.Stack.linkMu is
// also held.
func (s *Stack) setLinkLocked(ctx context.Context, id tcpip.NICID, linkAttrs map[uint16]nlmsg.BytesView, dstNs *inet.Namespace) *syserr.Error {
src := s
changed := false
nicInfo, ok := src.Stack.SingleNICInfo(id)
if !ok {
return syserr.ErrUnknownNICID
}
dst := src
if dstNs != nil {
dst = dstNs.Stack().(*Stack)
}
if dst != src {
var err tcpip.Error
oldID := id
id, err = src.Stack.SetNICStack(id, dst.Stack)
if err != nil {
return syserr.TranslateNetstackError(err)
}
src.sendDeleteEvent(ctx, oldID, nicInfo) // inform about exit from old ns
changed = true // inform about entry into new ns
nicInfo, ok = dst.Stack.SingleNICInfo(id)
if !ok {
// Because we hold dst.linkMu, this should never happen.
ctx.Warningf("Newly rehomed NIC %d not found in new stack %p", id, dst.Stack)
return syserr.ErrUnknownNICID
}
src = dst
// TODO (b/465141970): Once we support IFLA_LINK_NETNSID, we need to call sendChangeEvent on
// the peer interface if this interface is part of a veth pair.
}
for t, v := range linkAttrs {
switch t {
case linux.IFLA_MASTER:
master, ok := v.Uint32()
if !ok {
return syserr.ErrInvalidArgument
}
if mid, ok := src.Stack.GetNICCoordinatorID(id); ok && mid == tcpip.NICID(master) {
continue
}
if master != 0 {
if err := src.Stack.SetNICCoordinator(id, tcpip.NICID(master)); err != nil {
return syserr.TranslateNetstackError(err)
}
changed = true
}
case linux.IFLA_ADDRESS:
if len(v) != tcpip.LinkAddressSize {
return syserr.ErrInvalidArgument
}
addr := tcpip.LinkAddress(v)
if nicInfo.LinkAddress == addr {
continue
}
if err := src.Stack.SetNICAddress(id, addr); err != nil {
return syserr.TranslateNetstackError(err)
}
changed = true
case linux.IFLA_IFNAME:
if nicInfo.Name == v.String() {
continue
}
if err := src.Stack.SetNICName(id, v.String()); err != nil {
return syserr.TranslateNetstackError(err)
}
changed = true
case linux.IFLA_MTU:
mtu, ok := v.Uint32()
if !ok {
return syserr.ErrInvalidArgument
}
if nicInfo.MTU == mtu {
continue
}
if err := src.Stack.SetNICMTU(id, mtu); err != nil {
return syserr.TranslateNetstackError(err)
}
changed = true
case linux.IFLA_TXQLEN:
// TODO(b/340388892): support IFLA_TXQLEN.
}
}
if changed {
src.sendChangeEvent(ctx, id)
}
return nil
}
const defaultMTU = 1500
func (s *Stack) newVeth(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesView, linkInfoAttrs map[uint16]nlmsg.BytesView) *syserr.Error {
var (
linkInfoData map[uint16]nlmsg.BytesView
ifinfomsg linux.InterfaceInfoMessage
peerLinkAttrs map[uint16]nlmsg.BytesView
)
peerStack := s
peerName := ""
ifname := ""
if v, ok := linkAttrs[linux.IFLA_IFNAME]; ok {
ifname = v.String()
}
if value, ok := linkInfoAttrs[linux.IFLA_INFO_DATA]; ok {
linkInfoData, ok = nlmsg.AttrsView(value).Parse()
if !ok {
return syserr.ErrInvalidArgument
}
if v, ok := linkInfoData[linux.VETH_INFO_PEER]; ok {
attrsView := nlmsg.AttrsView(v[ifinfomsg.SizeBytes():])
if !ok {
return syserr.ErrInvalidArgument
}
peerLinkAttrs, ok = attrsView.Parse()
if !ok {
return syserr.ErrInvalidArgument
}
if v, ok = peerLinkAttrs[linux.IFLA_IFNAME]; ok {
peerName = v.String()
}
if v, ok = peerLinkAttrs[linux.IFLA_NET_NS_FD]; ok {
fd, ok := v.Uint32()
if !ok {
return syserr.ErrInvalidArgument
}
f := inet.NamespaceByFDFromContext(ctx)
if f == nil {
return syserr.ErrInvalidArgument
}
ns, err := f(int32(fd))
if err != nil {
return syserr.FromError(err)
}
defer ns.DecRef(ctx)
peerStack = ns.Stack().(*Stack)
}
}
}
dstNs, sysErr := s.lockSrcAndDst(ctx, linkAttrs)
if sysErr != nil {
return sysErr
}
ep, peerEP := veth.NewPair(defaultMTU, veth.DefaultBacklogSize)
id := s.Stack.NextNICID()
peerID := peerStack.Stack.NextNICID()
if ifname == "" {
ifname = fmt.Sprintf("veth%d", id)
}
err := s.Stack.CreateNICWithOptions(id, packetsocket.New(ethernet.New(ep)), stack.NICOptions{
Name: ifname,
})
if err != nil {
s.unlockSrcAndDst(ctx, dstNs)
return syserr.TranslateNetstackError(err)
}
if err := s.setLinkLocked(ctx, id, linkAttrs, dstNs); err != nil {
s.unlockSrcAndDst(ctx, dstNs)
peerEP.Close()
return err
}
s.unlockSrcAndDst(ctx, dstNs)
if peerName == "" {
peerName = fmt.Sprintf("veth%d", peerID)
}
peerDstNs, sysErr := peerStack.lockSrcAndDst(ctx, peerLinkAttrs)
if sysErr != nil {
return sysErr
}
defer peerStack.unlockSrcAndDst(ctx, peerDstNs)
err = peerStack.Stack.CreateNICWithOptions(peerID, packetsocket.New(ethernet.New(peerEP)), stack.NICOptions{
Name: peerName,
})
if err != nil {
peerEP.Close()
return syserr.TranslateNetstackError(err)
}
if peerLinkAttrs != nil {
if err := peerStack.setLinkLocked(ctx, peerID, peerLinkAttrs, peerDstNs); err != nil {
peerStack.Stack.RemoveNIC(peerID)
peerEP.Close()
return err
}
}
return nil
}
func (s *Stack) newBridge(ctx context.Context, linkAttrs map[uint16]nlmsg.BytesView, linkInfoAttrs map[uint16]nlmsg.BytesView) *syserr.Error {
dstNs, sysErr := s.lockSrcAndDst(ctx, linkAttrs)
if sysErr != nil {
return sysErr
}
defer s.unlockSrcAndDst(ctx, dstNs)
ifname := ""
if v, ok := linkAttrs[linux.IFLA_IFNAME]; ok {
ifname = v.String()
}
ep := stack.NewBridgeEndpoint(defaultMTU)
id := s.Stack.NextNICID()
err := s.Stack.CreateNICWithOptions(id, ep, stack.NICOptions{
Name: ifname,
})
if err != nil {
return syserr.TranslateNetstackError(err)
}
if err := s.setLinkLocked(ctx, id, linkAttrs, dstNs); err != nil {
return err
}
return nil
}
func (s *Stack) newInterface(ctx context.Context, msg *nlmsg.Message, linkAttrs map[uint16]nlmsg.BytesView) *syserr.Error {
var (
linkInfoAttrs map[uint16]nlmsg.BytesView
kind string
)
if v, ok := linkAttrs[linux.IFLA_LINKINFO]; ok {
linkInfoAttrs, ok = nlmsg.AttrsView(v).Parse()
if !ok {
return syserr.ErrInvalidArgument
}
for attr := range linkInfoAttrs {
value := linkInfoAttrs[attr]
switch attr {
case linux.IFLA_INFO_KIND:
kind = value.String()
case linux.IFLA_INFO_DATA:
default:
ctx.Warningf("unexpected link info attribute: %x", attr)
return syserr.ErrNotSupported
}
}
}
switch kind {
case "":
return syserr.ErrInvalidArgument
case "bridge":
return s.newBridge(ctx, linkAttrs, linkInfoAttrs)
case "veth":
return s.newVeth(ctx, linkAttrs, linkInfoAttrs)
}
return syserr.ErrNotSupported
}
// InterfaceAddrs implements inet.Stack.InterfaceAddrs.
func (s *Stack) InterfaceAddrs() map[int32][]inet.InterfaceAddr {
nicAddrs := make(map[int32][]inet.InterfaceAddr)
for id, ni := range s.Stack.NICInfo() {
var addrs []inet.InterfaceAddr
for _, a := range ni.ProtocolAddresses {
var family uint8
switch a.Protocol {
case ipv4.ProtocolNumber:
family = linux.AF_INET
case ipv6.ProtocolNumber:
family = linux.AF_INET6
default:
log.Warningf("Unknown network protocol in %+v", a)
continue
}
addrCopy := a.AddressWithPrefix.Address
addrs = append(addrs, inet.InterfaceAddr{
Family: family,
PrefixLen: uint8(a.AddressWithPrefix.PrefixLen),
Addr: addrCopy.AsSlice(),
// TODO(b/68878065): Other fields.
})
}
nicAddrs[int32(id)] = addrs
}
return nicAddrs
}
// convertAddr converts an InterfaceAddr to a ProtocolAddress.
func convertAddr(addr inet.InterfaceAddr) (tcpip.ProtocolAddress, error) {
var (
protocol tcpip.NetworkProtocolNumber
address tcpip.Address
protocolAddress tcpip.ProtocolAddress
)
switch addr.Family {
case linux.AF_INET:
if len(addr.Addr) != header.IPv4AddressSize {
return protocolAddress, linuxerr.EINVAL
}
if addr.PrefixLen > header.IPv4AddressSize*8 {
return protocolAddress, linuxerr.EINVAL
}
protocol = ipv4.ProtocolNumber
address = tcpip.AddrFrom4Slice(addr.Addr)
case linux.AF_INET6:
if len(addr.Addr) != header.IPv6AddressSize {
return protocolAddress, linuxerr.EINVAL
}
if addr.PrefixLen > header.IPv6AddressSize*8 {
return protocolAddress, linuxerr.EINVAL
}
protocol = ipv6.ProtocolNumber
address = tcpip.AddrFrom16Slice(addr.Addr)
default:
return protocolAddress, linuxerr.ENOTSUP
}
protocolAddress = tcpip.ProtocolAddress{
Protocol: protocol,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: address,
PrefixLen: int(addr.PrefixLen),
},
}
return protocolAddress, nil
}
// AddInterfaceAddr implements inet.Stack.AddInterfaceAddr.
func (s *Stack) AddInterfaceAddr(idx int32, addr inet.InterfaceAddr) error {
protocolAddress, err := convertAddr(addr)
if err != nil {
return err
}
// Attach address to interface.
nicID := tcpip.NICID(idx)
if err := s.Stack.AddProtocolAddress(nicID, protocolAddress, stack.AddressProperties{}); err != nil {
return syserr.TranslateNetstackError(err).ToError()
}
// Add route for local network if it doesn't exist already.
localRoute := tcpip.Route{
Destination: protocolAddress.AddressWithPrefix.Subnet(),
Gateway: tcpip.Address{}, // No gateway for local network.
NIC: nicID,
}
for _, rt := range s.Stack.GetRouteTable() {
if rt.Equal(localRoute) {
return nil
}
}
// Local route does not exist yet. Add it.
s.Stack.AddRoute(localRoute)
return nil
}
// RemoveInterfaceAddr implements inet.Stack.RemoveInterfaceAddr.
func (s *Stack) RemoveInterfaceAddr(idx int32, addr inet.InterfaceAddr) error {
protocolAddress, err := convertAddr(addr)
if err != nil {
return err
}
// Remove addresses matching the address and prefix.
nicID := tcpip.NICID(idx)
if err := s.Stack.RemoveAddress(nicID, protocolAddress.AddressWithPrefix.Address); err != nil {
return syserr.TranslateNetstackError(err).ToError()
}
// Remove the corresponding local network route if it exists.
localRoute := tcpip.Route{
Destination: protocolAddress.AddressWithPrefix.Subnet(),
Gateway: tcpip.Address{}, // No gateway for local network.
NIC: nicID,
}
s.Stack.RemoveRoutes(func(rt tcpip.Route) bool {
return rt.Equal(localRoute)
})
return nil
}
// TCPReceiveBufferSize implements inet.Stack.TCPReceiveBufferSize.
func (s *Stack) TCPReceiveBufferSize() (inet.TCPBufferSize, error) {
var rs tcpip.TCPReceiveBufferSizeRangeOption
err := s.Stack.TransportProtocolOption(tcp.ProtocolNumber, &rs)
return inet.TCPBufferSize{
Min: rs.Min,
Default: rs.Default,
Max: rs.Max,
}, syserr.TranslateNetstackError(err).ToError()
}
// SetTCPReceiveBufferSize implements inet.Stack.SetTCPReceiveBufferSize.
func (s *Stack) SetTCPReceiveBufferSize(size inet.TCPBufferSize) error {
rs := tcpip.TCPReceiveBufferSizeRangeOption{
Min: size.Min,
Default: size.Default,
Max: size.Max,
}
return syserr.TranslateNetstackError(s.Stack.SetTransportProtocolOption(tcp.ProtocolNumber, &rs)).ToError()
}
// TCPSendBufferSize implements inet.Stack.TCPSendBufferSize.
func (s *Stack) TCPSendBufferSize() (inet.TCPBufferSize, error) {
var ss tcpip.TCPSendBufferSizeRangeOption
err := s.Stack.TransportProtocolOption(tcp.ProtocolNumber, &ss)
return inet.TCPBufferSize{
Min: ss.Min,
Default: ss.Default,
Max: ss.Max,
}, syserr.TranslateNetstackError(err).ToError()
}
// SetTCPSendBufferSize implements inet.Stack.SetTCPSendBufferSize.
func (s *Stack) SetTCPSendBufferSize(size inet.TCPBufferSize) error {
ss := tcpip.TCPSendBufferSizeRangeOption{
Min: size.Min,
Default: size.Default,
Max: size.Max,
}
return syserr.TranslateNetstackError(s.Stack.SetTransportProtocolOption(tcp.ProtocolNumber, &ss)).ToError()
}
// TCPSACKEnabled implements inet.Stack.TCPSACKEnabled.
func (s *Stack) TCPSACKEnabled() (bool, error) {
var sack tcpip.TCPSACKEnabled
err := s.Stack.TransportProtocolOption(tcp.ProtocolNumber, &sack)
return bool(sack), syserr.TranslateNetstackError(err).ToError()
}
// SetTCPSACKEnabled implements inet.Stack.SetTCPSACKEnabled.
func (s *Stack) SetTCPSACKEnabled(enabled bool) error {
opt := tcpip.TCPSACKEnabled(enabled)
return syserr.TranslateNetstackError(s.Stack.SetTransportProtocolOption(tcp.ProtocolNumber, &opt)).ToError()
}
// TCPRecovery implements inet.Stack.TCPRecovery.
func (s *Stack) TCPRecovery() (inet.TCPLossRecovery, error) {
var recovery tcpip.TCPRecovery
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 {
opt := tcpip.TCPRecovery(recovery)
return syserr.TranslateNetstackError(s.Stack.SetTransportProtocolOption(tcp.ProtocolNumber, &opt)).ToError()
}
// Statistics implements inet.Stack.Statistics.
func (s *Stack) Statistics(stat any, arg string) error {
netStats := s.Stats()
switch stats := stat.(type) {
case *inet.StatDev:
for _, ni := range s.Stack.NICInfo() {
if ni.Name != arg {
continue
}
// TODO(gvisor.dev/issue/2103) Support stubbed stats.
*stats = inet.StatDev{
// Receive section.
ni.Stats.Rx.Bytes.Value(), // bytes.
ni.Stats.Rx.Packets.Value(), // packets.
0, // errs.
0, // drop.
0, // fifo.
0, // frame.
0, // compressed.
0, // multicast.
// Transmit section.
ni.Stats.Tx.Bytes.Value(), // bytes.
ni.Stats.Tx.Packets.Value(), // packets.
0, // errs.
0, // drop.
0, // fifo.
0, // colls.
0, // carrier.
0, // compressed.
}
break
}
case *inet.StatSNMPIP:
ip := netStats.IP
// TODO(gvisor.dev/issue/969) Support stubbed stats.
*stats = inet.StatSNMPIP{
0, // Ip/Forwarding.
0, // Ip/DefaultTTL.
ip.PacketsReceived.Value(), // InReceives.
0, // Ip/InHdrErrors.
ip.InvalidDestinationAddressesReceived.Value(), // InAddrErrors.
0, // Ip/ForwDatagrams.
0, // Ip/InUnknownProtos.
0, // Ip/InDiscards.
ip.PacketsDelivered.Value(), // InDelivers.
ip.PacketsSent.Value(), // OutRequests.
ip.OutgoingPacketErrors.Value(), // OutDiscards.
0, // Ip/OutNoRoutes.
0, // Support Ip/ReasmTimeout.
0, // Support Ip/ReasmReqds.
0, // Support Ip/ReasmOKs.
0, // Support Ip/ReasmFails.
0, // Support Ip/FragOKs.
0, // Support Ip/FragFails.
0, // Support Ip/FragCreates.
}
case *inet.StatSNMPICMP:
in := netStats.ICMP.V4.PacketsReceived.ICMPv4PacketStats
out := netStats.ICMP.V4.PacketsSent.ICMPv4PacketStats
// TODO(gvisor.dev/issue/969) Support stubbed stats.
*stats = inet.StatSNMPICMP{
0, // Icmp/InMsgs.
Metrics.ICMP.V4.PacketsSent.Dropped.Value(), // InErrors.
0, // Icmp/InCsumErrors.
in.DstUnreachable.Value(), // InDestUnreachs.
in.TimeExceeded.Value(), // InTimeExcds.
in.ParamProblem.Value(), // InParmProbs.
in.SrcQuench.Value(), // InSrcQuenchs.
in.Redirect.Value(), // InRedirects.
in.EchoRequest.Value(), // InEchos.
in.EchoReply.Value(), // InEchoReps.
in.Timestamp.Value(), // InTimestamps.
in.TimestampReply.Value(), // InTimestampReps.
in.InfoRequest.Value(), // InAddrMasks.
in.InfoReply.Value(), // InAddrMaskReps.
0, // Icmp/OutMsgs.
Metrics.ICMP.V4.PacketsReceived.Invalid.Value(), // OutErrors.
out.DstUnreachable.Value(), // OutDestUnreachs.
out.TimeExceeded.Value(), // OutTimeExcds.
out.ParamProblem.Value(), // OutParmProbs.
out.SrcQuench.Value(), // OutSrcQuenchs.
out.Redirect.Value(), // OutRedirects.
out.EchoRequest.Value(), // OutEchos.
out.EchoReply.Value(), // OutEchoReps.
out.Timestamp.Value(), // OutTimestamps.
out.TimestampReply.Value(), // OutTimestampReps.
out.InfoRequest.Value(), // OutAddrMasks.
out.InfoReply.Value(), // OutAddrMaskReps.
}
case *inet.StatSNMPTCP:
tcp := netStats.TCP
// RFC 2012 (updates 1213): SNMPv2-MIB-TCP.
*stats = inet.StatSNMPTCP{
1, // RtoAlgorithm.
200, // RtoMin.
120000, // RtoMax.
(1<<64 - 1), // MaxConn.
tcp.ActiveConnectionOpenings.Value(), // ActiveOpens.
tcp.PassiveConnectionOpenings.Value(), // PassiveOpens.
tcp.FailedConnectionAttempts.Value(), // AttemptFails.
tcp.EstablishedResets.Value(), // EstabResets.
tcp.CurrentEstablished.Value(), // CurrEstab.
tcp.ValidSegmentsReceived.Value(), // InSegs.
tcp.SegmentsSent.Value(), // OutSegs.
tcp.Retransmits.Value(), // RetransSegs.
tcp.InvalidSegmentsReceived.Value(), // InErrs.
tcp.ResetsSent.Value(), // OutRsts.
tcp.ChecksumErrors.Value(), // InCsumErrors.
}
case *inet.StatSNMPUDP:
udp := netStats.UDP
// TODO(gvisor.dev/issue/969) Support stubbed stats.
*stats = inet.StatSNMPUDP{
udp.PacketsReceived.Value(), // InDatagrams.
udp.UnknownPortErrors.Value(), // NoPorts.
0, // Udp/InErrors.
udp.PacketsSent.Value(), // OutDatagrams.
udp.ReceiveBufferErrors.Value(), // RcvbufErrors.
0, // Udp/SndbufErrors.
udp.ChecksumErrors.Value(), // Udp/InCsumErrors.
0, // Udp/IgnoredMulti.
}
default:
return syserr.ErrEndpointOperation.ToError()
}
return nil
}
// Stats implements inet.Stack.Stats.
func (s *Stack) Stats() tcpip.Stats {
return s.Stack.Stats()
}
// RouteTable implements inet.Stack.RouteTable.
func (s *Stack) RouteTable() []inet.Route {
var routeTable []inet.Route
for _, rt := range s.Stack.GetRouteTable() {
var family uint8
switch rt.Destination.ID().BitLen() {
case header.IPv4AddressSizeBits:
family = linux.AF_INET
case header.IPv6AddressSizeBits:
family = linux.AF_INET6
default:
log.Warningf("Unknown network protocol in route %+v", rt)
continue
}
dstAddr := rt.Destination.ID()
routeTable = append(routeTable, inet.Route{
Family: family,
DstLen: uint8(rt.Destination.Prefix()), // The CIDR prefix for the destination.
// Always return unspecified protocol since we have no notion of
// protocol for routes.
Protocol: linux.RTPROT_UNSPEC,
// Set statically to LINK scope for now.
//
// TODO(gvisor.dev/issue/595): Set scope for routes.
Scope: linux.RT_SCOPE_LINK,
Type: linux.RTN_UNICAST,
DstAddr: dstAddr.AsSlice(),
OutputInterface: int32(rt.NIC),
GatewayAddr: rt.Gateway.AsSlice(),
})
}
return routeTable
}
// localRoute constructs a local route from the netlink message.
func (s *Stack) localRoute(msg *nlmsg.Message) (tcpip.Route, *syserr.Error) {
var rtMsg linux.RouteMessage
attrs, ok := msg.GetData(&rtMsg)
if !ok {
return tcpip.Route{}, syserr.ErrInvalidArgument
}
route := inet.Route{
Family: rtMsg.Family,
DstLen: rtMsg.DstLen,
SrcLen: rtMsg.SrcLen,
TOS: rtMsg.TOS,
Table: rtMsg.Table,
Protocol: rtMsg.Protocol,
Scope: rtMsg.Scope,
Type: rtMsg.Type,
Flags: rtMsg.Flags,
}
for !attrs.Empty() {
ahdr, value, rest, ok := attrs.ParseFirst()
if !ok {
return tcpip.Route{}, syserr.ErrInvalidArgument
}
attrs = rest
switch ahdr.Type {
case linux.RTA_DST:
if len(value) < 1 {
return tcpip.Route{}, syserr.ErrInvalidArgument
}
route.DstAddr = value
case linux.RTA_SRC:
if len(value) < 1 {
return tcpip.Route{}, syserr.ErrInvalidArgument
}
route.SrcAddr = value
case linux.RTA_OIF:
oif := nlmsg.BytesView(value)
outputInterface, ok := oif.Int32()
if !ok {
return tcpip.Route{}, syserr.ErrInvalidArgument
}
if _, exist := s.Interfaces()[outputInterface]; !exist {
return tcpip.Route{}, syserr.ErrNoDevice
}
route.OutputInterface = outputInterface
case linux.RTA_GATEWAY:
if len(value) < 1 {
return tcpip.Route{}, syserr.ErrInvalidArgument
}
route.GatewayAddr = value
case linux.RTA_PRIORITY:
default:
log.Warningf("Unknown attribute: %v", ahdr.Type)
return tcpip.Route{}, syserr.ErrNotSupported
}
}
var dest tcpip.Subnet
// When no destination address is provided, the new route might be the default route.
if route.DstAddr == nil {
if route.GatewayAddr == nil {
return tcpip.Route{}, syserr.ErrInvalidArgument
}
switch len(route.GatewayAddr) {
case header.IPv4AddressSize:
subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice(tcpip.IPv4Zero), tcpip.MaskFromBytes(tcpip.IPv4Zero))
if err != nil {
return tcpip.Route{}, syserr.ErrInvalidArgument
}
dest = subnet
case header.IPv6AddressSize:
subnet, err := tcpip.NewSubnet(tcpip.AddrFromSlice(tcpip.IPv6Zero), tcpip.MaskFromBytes(tcpip.IPv6Zero))
if err != nil {
return tcpip.Route{}, syserr.ErrInvalidArgument
}
dest = subnet
default:
return tcpip.Route{}, syserr.ErrInvalidArgument
}
} else {
dest = tcpip.AddressWithPrefix{
Address: tcpip.AddrFromSlice(route.DstAddr),
PrefixLen: int(route.DstLen)}.Subnet()
}
localRoute := tcpip.Route{
Destination: dest,
Gateway: tcpip.AddrFromSlice(route.GatewayAddr),
NIC: tcpip.NICID(route.OutputInterface),
}
if len(route.SrcAddr) != 0 {
localRoute.SourceHint = tcpip.AddrFromSlice(route.SrcAddr)
}
return localRoute, nil
}
// RemoveRoute implements inte.Stack.RemoveRoute.
func (s *Stack) RemoveRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
localRoute, err := s.localRoute(msg)
if err != nil {
return err
}
if removed := s.Stack.RemoveRoutes(func(rt tcpip.Route) bool {
// Both gateway and NIC are compared with existing routes
// only when they are present in the netlink message.
if localRoute.Gateway.Len() > 0 && !localRoute.Gateway.Equal(rt.Gateway) {
return false
}
if localRoute.NIC > 0 && localRoute.NIC != rt.NIC {
return false
}
return rt.Destination.Equal(localRoute.Destination)
}); removed == 0 {
return syserr.ErrNoProcess
}
return nil
}
// NewRoute implements inet.Stack.NewRoute.
func (s *Stack) NewRoute(ctx context.Context, msg *nlmsg.Message) *syserr.Error {
localRoute, err := s.localRoute(msg)
if err != nil {
return err
}
found := false
for _, rt := range s.Stack.GetRouteTable() {
if localRoute.Equal(rt) {
found = true
break
}
}
flags := msg.Header().Flags
switch {
case !found && flags&linux.NLM_F_CREATE == linux.NLM_F_CREATE:
s.Stack.AddRoute(localRoute)
case found && flags&linux.NLM_F_REPLACE != linux.NLM_F_REPLACE:
return syserr.ErrExists
}
if flags&linux.NLM_F_REPLACE == linux.NLM_F_REPLACE {
s.Stack.ReplaceRoute(localRoute)
}
return nil
}
// IPTables returns the stack's iptables.
func (s *Stack) IPTables() (*stack.IPTables, error) {
return s.Stack.IPTables(), nil
}
// Pause implements inet.Stack.Pause.
func (s *Stack) Pause() {
s.Stack.Pause()
}
// Restore implements inet.Stack.Restore.
func (s *Stack) Restore() {
s.Stack.Restore()
}
// ResetConfig implements inet.Stack.ResetConfig.
func (s *Stack) ResetConfig() {
s.Stack.ResetConfig()
}
// ReplaceConfig replaces config in the loaded stack.
func (s *Stack) ReplaceConfig(st inet.Stack) {
if _, ok := st.(*Stack); !ok {
panic("netstack.Stack cannot be nil when netstack s/r is enabled")
}
s.Stack.ReplaceConfig(st.(*Stack).Stack)
}
// Resume implements inet.Stack.Resume.
func (s *Stack) Resume() {
s.Stack.Resume()
}
// RegisteredEndpoints implements inet.Stack.RegisteredEndpoints.
func (s *Stack) RegisteredEndpoints() []stack.TransportEndpoint {
return s.Stack.RegisteredEndpoints()
}
// CleanupEndpoints implements inet.Stack.CleanupEndpoints.
func (s *Stack) CleanupEndpoints() []stack.TransportEndpoint {
return s.Stack.CleanupEndpoints()
}
// RestoreCleanupEndpoints implements inet.Stack.RestoreCleanupEndpoints.
func (s *Stack) RestoreCleanupEndpoints(es []stack.TransportEndpoint) {
s.Stack.RestoreCleanupEndpoints(es)
}
// SetForwarding implements inet.Stack.SetForwarding.
func (s *Stack) SetForwarding(protocol tcpip.NetworkProtocolNumber, enable bool) error {
if err := s.Stack.SetForwardingDefaultAndAllNICs(protocol, enable); err != nil {
return fmt.Errorf("SetForwardingDefaultAndAllNICs(%d, %t): %s", protocol, enable, err)
}
return nil
}
// PortRange implements inet.Stack.PortRange.
func (s *Stack) PortRange() (uint16, uint16) {
return s.Stack.PortRange()
}
// SetPortRange implements inet.Stack.SetPortRange.
func (s *Stack) SetPortRange(start uint16, end uint16) error {
return syserr.TranslateNetstackError(s.Stack.SetPortRange(start, end)).ToError()
}
// SetRemoveConf implements inet.Stack.SetRemoveConf.
func (s *Stack) SetRemoveConf(removeConf bool) {
s.Stack.SetRemoveConf(removeConf)
}
// GetRemoveConf implements inet.Stack.GetRemoveConf.
func (s *Stack) GetRemoveConf() bool {
return s.Stack.GetRemoveConf()
}