Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/abi/linux/netlink.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,8 @@ type NetlinkErrorMessage struct {
Error int32
Header NetlinkMessageHeader
}

// RTNetlink multicast groups, from uapi/linux/rtnetlink.h.
const (
RTNLGRP_LINK = 1
)
9 changes: 9 additions & 0 deletions pkg/sentry/inet/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ declare_mutex(
prefix = "abstractSocketNamespace",
)

declare_mutex(
name = "nlmcast_table_mutex",
out = "nlmcast_table_mutex.go",
package = "inet",
prefix = "nlmcastTable",
)

go_library(
name = "inet",
srcs = [
Expand All @@ -35,6 +42,8 @@ go_library(
"inet.go",
"namespace.go",
"namespace_refs.go",
"nlmcast.go",
"nlmcast_table_mutex.go",
"test_stack.go",
],
deps = [
Expand Down
2 changes: 1 addition & 1 deletion pkg/sentry/inet/inet.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type Stack interface {
Interfaces() map[int32]Interface

// RemoveInterface removes the specified network interface.
RemoveInterface(idx int32) error
RemoveInterface(ctx context.Context, idx int32) error

// InterfaceAddrs returns all network interface addresses as a mapping from
// interface indexes to a slice of associated interface address properties.
Expand Down
28 changes: 22 additions & 6 deletions pkg/sentry/inet/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,24 @@ type Namespace struct {

// abstractSockets tracks abstract sockets that are in use.
abstractSockets AbstractSocketNamespace

// netlinkMcastTable manages multicast group membership for netlink sockets.
netlinkMcastTable *McastTable
}

// NewRootNamespace creates the root network namespace, with creator
// allowing new network namespaces to be created. If creator is nil, no
// networking will function if the network is namespaced.
func NewRootNamespace(stack Stack, creator NetworkStackCreator, userNS *auth.UserNamespace) *Namespace {
n := &Namespace{
stack: stack,
creator: creator,
isRoot: true,
userNS: userNS,
stack: stack,
creator: creator,
isRoot: true,
userNS: userNS,
netlinkMcastTable: NewNetlinkMcastTable(),
}
if eventPublishingStack, ok := stack.(InterfaceEventPublisher); ok {
eventPublishingStack.AddInterfaceEventSubscriber(n.netlinkMcastTable)
}
n.abstractSockets.init()
return n
Expand All @@ -79,8 +86,9 @@ func (n *Namespace) GetInode() *nsfs.Inode {
// NewNamespace creates a new network namespace from the root.
func NewNamespace(root *Namespace, userNS *auth.UserNamespace) *Namespace {
n := &Namespace{
creator: root.creator,
userNS: userNS,
creator: root.creator,
userNS: userNS,
netlinkMcastTable: NewNetlinkMcastTable(),
}
n.init()
return n
Expand Down Expand Up @@ -148,6 +156,9 @@ func (n *Namespace) init() {
if err != nil {
panic(err)
}
if eventPublishingStack, ok := n.stack.(InterfaceEventPublisher); ok {
eventPublishingStack.AddInterfaceEventSubscriber(n.netlinkMcastTable)
}
}
n.abstractSockets.init()
}
Expand All @@ -162,6 +173,11 @@ func (n *Namespace) AbstractSockets() *AbstractSocketNamespace {
return &n.abstractSockets
}

// NetlinkMcastTable returns the netlink multicast group table.
func (n *Namespace) NetlinkMcastTable() *McastTable {
return n.netlinkMcastTable
}

// NetworkStackCreator allows new instances of a network stack to be created. It
// is used by the kernel to create new network namespaces when requested.
type NetworkStackCreator interface {
Expand Down
142 changes: 142 additions & 0 deletions pkg/sentry/inet/nlmcast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright 2025 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 inet

import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
)

const (
routeProtocol = linux.NETLINK_ROUTE
routeLinkMcastGroup = linux.RTNLGRP_LINK
)

// InterfaceEventSubscriber allows clients to subscribe to events published by an inet.Stack.
//
// It is a rough parallel to the objects in Linux that subscribe to netdev
// events by calling register_netdevice_notifier().
type InterfaceEventSubscriber interface {
// OnInterfaceChangeEvent is called by InterfaceEventPublishers when an interface event takes place.
OnInterfaceChangeEvent(ctx context.Context, idx int32, i Interface)

// OnInterfaceDeleteEvent is called by InterfaceEventPublishers when an interface event takes place.
OnInterfaceDeleteEvent(ctx context.Context, idx int32, i Interface)
}

// InterfaceEventPublisher is the interface event publishing aspect of an inet.Stack.
//
// The Linux parallel is how it notifies subscribers via call_netdev_notifiers().
type InterfaceEventPublisher interface {
AddInterfaceEventSubscriber(sub InterfaceEventSubscriber)
}

// NetlinkSocket corresponds to a netlink socket.
type NetlinkSocket interface {
// Protocol returns the netlink protocol value.
Protocol() int

// Groups returns the bitmap of multicast groups the socket is bound to.
Groups() uint64

// HandleInterfaceChangeEvent is called on NetlinkSockets that are members of the RTNLGRP_LINK
// multicast group when an interface is modified.
HandleInterfaceChangeEvent(context.Context, int32, Interface)

// HandleInterfaceDeleteEvent is called on NetlinkSockets that are members of the RTNLGRP_LINK
// multicast group when an interface is deleted.
HandleInterfaceDeleteEvent(context.Context, int32, Interface)
}

// McastTable holds multicast group membership information for netlink netlinkSocket.
// It corresponds roughly to Linux's struct netlink_table.
//
// +stateify savable
type McastTable struct {
mu nlmcastTableMutex `state:"nosave"`
socks map[int]map[NetlinkSocket]struct{}
}

// WithTableLocked runs fn with the table mutex held.
func (m *McastTable) WithTableLocked(fn func()) {
m.mu.Lock()
defer m.mu.Unlock()
fn()
}

// AddSocket adds a netlinkSocket to the multicast-group table.
//
// Preconditions: the netlink multicast table is locked.
func (m *McastTable) AddSocket(s NetlinkSocket) {
p := s.Protocol()
if _, ok := m.socks[p]; !ok {
m.socks[p] = make(map[NetlinkSocket]struct{})
}
if _, ok := m.socks[p][s]; ok {
return
}
m.socks[p][s] = struct{}{}
}

// RemoveSocket removes a netlinkSocket from the multicast-group table.
//
// Preconditions: the netlink multicast table is locked.
func (m *McastTable) RemoveSocket(s NetlinkSocket) {
p := s.Protocol()
if _, ok := m.socks[p]; !ok {
return
}
if _, ok := m.socks[p][s]; !ok {
return
}
delete(m.socks[p], s)
}

func (m *McastTable) forEachMcastSock(protocol int, mcastGroup int, fn func(s NetlinkSocket)) {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.socks[protocol]; !ok {
return
}
for s := range m.socks[protocol] {
if s.Groups()&(1<<(mcastGroup-1)) == 0 {
return
}
fn(s)
}
}

// OnInterfaceChangeEvent implements InterfaceEventSubscriber.OnInterfaceChangeEvent.
func (m *McastTable) OnInterfaceChangeEvent(ctx context.Context, idx int32, i Interface) {
// Relay the event to RTNLGRP_LINK subscribers.
m.forEachMcastSock(routeProtocol, routeLinkMcastGroup, func(s NetlinkSocket) {
s.HandleInterfaceChangeEvent(ctx, idx, i)
})
}

// OnInterfaceDeleteEvent implements InterfaceEventSubscriber.OnInterfaceDeleteEvent.
func (m *McastTable) OnInterfaceDeleteEvent(ctx context.Context, idx int32, i Interface) {
// Relay the event to RTNLGRP_LINK subscribers.
m.forEachMcastSock(routeProtocol, routeLinkMcastGroup, func(s NetlinkSocket) {
s.HandleInterfaceDeleteEvent(ctx, idx, i)
})
}

// NewNetlinkMcastTable creates a new McastTable.
func NewNetlinkMcastTable() *McastTable {
return &McastTable{
socks: make(map[int]map[NetlinkSocket]struct{}),
}
}
2 changes: 1 addition & 1 deletion pkg/sentry/inet/test_stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (s *TestStack) Destroy() {
}

// RemoveInterface implements Stack.
func (s *TestStack) RemoveInterface(idx int32) error {
func (s *TestStack) RemoveInterface(ctx context.Context, idx int32) error {
delete(s.InterfacesMap, idx)
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/sentry/socket/hostinet/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func (s *Stack) Interfaces() map[int32]inet.Interface {
}

// RemoveInterface implements inet.Stack.RemoveInterface.
func (*Stack) RemoveInterface(idx int32) error {
func (*Stack) RemoveInterface(ctx context.Context, idx int32) error {
return removeInterface(idx)
}

Expand Down
1 change: 1 addition & 0 deletions pkg/sentry/socket/netlink/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ go_library(
deps = [
"//pkg/abi/linux",
"//pkg/abi/linux/errno",
"//pkg/atomicbitops",
"//pkg/context",
"//pkg/errors/linuxerr",
"//pkg/hostarch",
Expand Down
13 changes: 13 additions & 0 deletions pkg/sentry/socket/netlink/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs"
"gvisor.dev/gvisor/pkg/sentry/inet"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/socket"
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
Expand Down Expand Up @@ -51,6 +52,18 @@ type Protocol interface {
ProcessMessage(ctx context.Context, s *Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error
}

// RouteProtocol corresponds to the NETLINK_ROUTE family.
type RouteProtocol interface {
Protocol

// AddNewLinkMessage is called when an interface is mutated or created by the stack.
// It is the rough equivalent of Linux's rtnetlink_event().
AddNewLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface)

// AddDelLinkMessage is called when an interface is deleted by the stack.
AddDelLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface)
}

// Provider is a function that creates a new Protocol for a specific netlink
// protocol.
//
Expand Down
34 changes: 24 additions & 10 deletions pkg/sentry/socket/netlink/route/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (p *Protocol) dumpLinks(ctx context.Context, s *netlink.Socket, msg *nlmsg.
}

for idx, i := range stack.Interfaces() {
addNewLinkMessage(ms, idx, i)
p.AddNewLinkMessage(ms, idx, i)
}

return nil
Expand Down Expand Up @@ -158,7 +158,7 @@ func (p *Protocol) getLink(ctx context.Context, s *netlink.Socket, msg *nlmsg.Me
return syserr.ErrInvalidArgument
}

addNewLinkMessage(ms, idx, i)
p.AddNewLinkMessage(ms, idx, i)
found = true
break
}
Expand Down Expand Up @@ -232,16 +232,10 @@ func (p *Protocol) delLink(ctx context.Context, s *netlink.Socket, msg *nlmsg.Me
return syserr.ErrNoDevice
}
}
return syserr.FromError(stack.RemoveInterface(ifinfomsg.Index))
return syserr.FromError(stack.RemoveInterface(ctx, ifinfomsg.Index))
}

// addNewLinkMessage appends RTM_NEWLINK message for the given interface into
// the message set.
func addNewLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface) {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.RTM_NEWLINK,
})

func writeLinkInfo(m *nlmsg.Message, idx int32, i inet.Interface) {
m.Put(&linux.InterfaceInfoMessage{
Family: linux.AF_UNSPEC,
Type: i.DeviceType,
Expand All @@ -264,6 +258,26 @@ func addNewLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface) {
// TODO(gvisor.dev/issue/578): There are many more attributes.
}

// AddNewLinkMessage appends an RTM_NEWLINK message for the given interface into
// the message set.
// AddNewLinkMessage implements netlink.RouteProtocol.AddNewLinkMessage.
func (p *Protocol) AddNewLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface) {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.RTM_NEWLINK,
})
writeLinkInfo(m, idx, i)
}

// AddDelLinkMessage appends an RTM_DELLINK message for the given interface into
// the message set.
// AddDelLinkMessage implements netlink.RouteProtocol.AddDelLinkMessage.
func (p *Protocol) AddDelLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface) {
m := ms.AddMessage(linux.NetlinkMessageHeader{
Type: linux.RTM_DELLINK,
})
writeLinkInfo(m, idx, i)
}

// dumpAddrs handles RTM_GETADDR dump requests.
func (p *Protocol) dumpAddrs(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
// RTM_GETADDR dump requests need not contain anything more than the
Expand Down
Loading