Skip to content

Commit 2d23faf

Browse files
shailend-ggvisor-bot
authored andcommitted
Add RTNLGRP_LINK netlink multicast support for recv
Only netstack supports sending link events, requests to join multicast groups for netlink sockets continue to be denied when hostinet is in use. connect() and sendmsg() to RTMGRP_LINK still fails. PiperOrigin-RevId: 831688843
1 parent c976ee8 commit 2d23faf

File tree

19 files changed

+1012
-143
lines changed

19 files changed

+1012
-143
lines changed

pkg/abi/linux/netlink.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,8 @@ type NetlinkErrorMessage struct {
157157
Error int32
158158
Header NetlinkMessageHeader
159159
}
160+
161+
// RTNetlink multicast groups, from uapi/linux/rtnetlink.h.
162+
const (
163+
RTNLGRP_LINK = 1
164+
)

pkg/sentry/inet/BUILD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ declare_mutex(
2626
prefix = "abstractSocketNamespace",
2727
)
2828

29+
declare_mutex(
30+
name = "nlmcast_table_mutex",
31+
out = "nlmcast_table_mutex.go",
32+
package = "inet",
33+
prefix = "nlmcastTable",
34+
)
35+
2936
go_library(
3037
name = "inet",
3138
srcs = [
@@ -35,6 +42,8 @@ go_library(
3542
"inet.go",
3643
"namespace.go",
3744
"namespace_refs.go",
45+
"nlmcast.go",
46+
"nlmcast_table_mutex.go",
3847
"test_stack.go",
3948
],
4049
deps = [

pkg/sentry/inet/inet.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type Stack interface {
3232
Interfaces() map[int32]Interface
3333

3434
// RemoveInterface removes the specified network interface.
35-
RemoveInterface(idx int32) error
35+
RemoveInterface(ctx context.Context, idx int32) error
3636

3737
// InterfaceAddrs returns all network interface addresses as a mapping from
3838
// interface indexes to a slice of associated interface address properties.

pkg/sentry/inet/namespace.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,24 @@ type Namespace struct {
4545

4646
// abstractSockets tracks abstract sockets that are in use.
4747
abstractSockets AbstractSocketNamespace
48+
49+
// netlinkMcastTable manages multicast group membership for netlink sockets.
50+
netlinkMcastTable *McastTable
4851
}
4952

5053
// NewRootNamespace creates the root network namespace, with creator
5154
// allowing new network namespaces to be created. If creator is nil, no
5255
// networking will function if the network is namespaced.
5356
func NewRootNamespace(stack Stack, creator NetworkStackCreator, userNS *auth.UserNamespace) *Namespace {
5457
n := &Namespace{
55-
stack: stack,
56-
creator: creator,
57-
isRoot: true,
58-
userNS: userNS,
58+
stack: stack,
59+
creator: creator,
60+
isRoot: true,
61+
userNS: userNS,
62+
netlinkMcastTable: NewNetlinkMcastTable(),
63+
}
64+
if eventPublishingStack, ok := stack.(InterfaceEventPublisher); ok {
65+
eventPublishingStack.AddInterfaceEventSubscriber(n.netlinkMcastTable)
5966
}
6067
n.abstractSockets.init()
6168
return n
@@ -79,8 +86,9 @@ func (n *Namespace) GetInode() *nsfs.Inode {
7986
// NewNamespace creates a new network namespace from the root.
8087
func NewNamespace(root *Namespace, userNS *auth.UserNamespace) *Namespace {
8188
n := &Namespace{
82-
creator: root.creator,
83-
userNS: userNS,
89+
creator: root.creator,
90+
userNS: userNS,
91+
netlinkMcastTable: NewNetlinkMcastTable(),
8492
}
8593
n.init()
8694
return n
@@ -148,6 +156,9 @@ func (n *Namespace) init() {
148156
if err != nil {
149157
panic(err)
150158
}
159+
if eventPublishingStack, ok := n.stack.(InterfaceEventPublisher); ok {
160+
eventPublishingStack.AddInterfaceEventSubscriber(n.netlinkMcastTable)
161+
}
151162
}
152163
n.abstractSockets.init()
153164
}
@@ -162,6 +173,11 @@ func (n *Namespace) AbstractSockets() *AbstractSocketNamespace {
162173
return &n.abstractSockets
163174
}
164175

176+
// NetlinkMcastTable returns the netlink multicast group table.
177+
func (n *Namespace) NetlinkMcastTable() *McastTable {
178+
return n.netlinkMcastTable
179+
}
180+
165181
// NetworkStackCreator allows new instances of a network stack to be created. It
166182
// is used by the kernel to create new network namespaces when requested.
167183
type NetworkStackCreator interface {

pkg/sentry/inet/nlmcast.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2025 The gVisor Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package inet
16+
17+
import (
18+
"gvisor.dev/gvisor/pkg/abi/linux"
19+
"gvisor.dev/gvisor/pkg/context"
20+
)
21+
22+
const (
23+
routeProtocol = linux.NETLINK_ROUTE
24+
routeLinkMcastGroup = linux.RTNLGRP_LINK
25+
)
26+
27+
// InterfaceEventSubscriber allows clients to subscribe to events published by an inet.Stack.
28+
//
29+
// It is a rough parallel to the objects in Linux that subscribe to netdev
30+
// events by calling register_netdevice_notifier().
31+
type InterfaceEventSubscriber interface {
32+
// OnInterfaceChangeEvent is called by InterfaceEventPublishers when an interface change event takes place.
33+
OnInterfaceChangeEvent(ctx context.Context, idx int32, i Interface)
34+
35+
// OnInterfaceDeleteEvent is called by InterfaceEventPublishers when an interface delete event takes place.
36+
OnInterfaceDeleteEvent(ctx context.Context, idx int32, i Interface)
37+
}
38+
39+
// InterfaceEventPublisher is the interface event publishing aspect of an inet.Stack.
40+
//
41+
// The Linux parallel is how it notifies subscribers via call_netdev_notifiers().
42+
type InterfaceEventPublisher interface {
43+
AddInterfaceEventSubscriber(sub InterfaceEventSubscriber)
44+
}
45+
46+
// NetlinkSocket corresponds to a netlink socket.
47+
type NetlinkSocket interface {
48+
// Protocol returns the netlink protocol value.
49+
Protocol() int
50+
51+
// Groups returns the bitmap of multicast groups the socket is bound to.
52+
Groups() uint64
53+
54+
// HandleInterfaceChangeEvent is called on NetlinkSockets that are members of the RTNLGRP_LINK
55+
// multicast group when an interface is modified.
56+
HandleInterfaceChangeEvent(context.Context, int32, Interface)
57+
58+
// HandleInterfaceDeleteEvent is called on NetlinkSockets that are members of the RTNLGRP_LINK
59+
// multicast group when an interface is deleted.
60+
HandleInterfaceDeleteEvent(context.Context, int32, Interface)
61+
}
62+
63+
// McastTable holds multicast group membership information for netlink netlinkSocket.
64+
// It corresponds roughly to Linux's struct netlink_table.
65+
//
66+
// +stateify savable
67+
type McastTable struct {
68+
mu nlmcastTableMutex `state:"nosave"`
69+
socks map[int]map[NetlinkSocket]struct{}
70+
}
71+
72+
// WithTableLocked runs fn with the table mutex held.
73+
func (m *McastTable) WithTableLocked(fn func()) {
74+
m.mu.Lock()
75+
defer m.mu.Unlock()
76+
fn()
77+
}
78+
79+
// AddSocket adds a netlinkSocket to the multicast-group table.
80+
//
81+
// Preconditions: the netlink multicast table is locked.
82+
func (m *McastTable) AddSocket(s NetlinkSocket) {
83+
p := s.Protocol()
84+
if _, ok := m.socks[p]; !ok {
85+
m.socks[p] = make(map[NetlinkSocket]struct{})
86+
}
87+
if _, ok := m.socks[p][s]; ok {
88+
return
89+
}
90+
m.socks[p][s] = struct{}{}
91+
}
92+
93+
// RemoveSocket removes a netlinkSocket from the multicast-group table.
94+
//
95+
// Preconditions: the netlink multicast table is locked.
96+
func (m *McastTable) RemoveSocket(s NetlinkSocket) {
97+
p := s.Protocol()
98+
if _, ok := m.socks[p]; !ok {
99+
return
100+
}
101+
if _, ok := m.socks[p][s]; !ok {
102+
return
103+
}
104+
delete(m.socks[p], s)
105+
}
106+
107+
func (m *McastTable) forEachMcastSock(protocol int, mcastGroup int, fn func(s NetlinkSocket)) {
108+
m.mu.Lock()
109+
defer m.mu.Unlock()
110+
if _, ok := m.socks[protocol]; !ok {
111+
return
112+
}
113+
for s := range m.socks[protocol] {
114+
// If the socket is not bound to the multicast group, skip it.
115+
if s.Groups()&(1<<(mcastGroup-1)) == 0 {
116+
continue
117+
}
118+
fn(s)
119+
}
120+
}
121+
122+
// OnInterfaceChangeEvent implements InterfaceEventSubscriber.OnInterfaceChangeEvent.
123+
func (m *McastTable) OnInterfaceChangeEvent(ctx context.Context, idx int32, i Interface) {
124+
// Relay the event to RTNLGRP_LINK subscribers.
125+
m.forEachMcastSock(routeProtocol, routeLinkMcastGroup, func(s NetlinkSocket) {
126+
s.HandleInterfaceChangeEvent(ctx, idx, i)
127+
})
128+
}
129+
130+
// OnInterfaceDeleteEvent implements InterfaceEventSubscriber.OnInterfaceDeleteEvent.
131+
func (m *McastTable) OnInterfaceDeleteEvent(ctx context.Context, idx int32, i Interface) {
132+
// Relay the event to RTNLGRP_LINK subscribers.
133+
m.forEachMcastSock(routeProtocol, routeLinkMcastGroup, func(s NetlinkSocket) {
134+
s.HandleInterfaceDeleteEvent(ctx, idx, i)
135+
})
136+
}
137+
138+
// NewNetlinkMcastTable creates a new McastTable.
139+
func NewNetlinkMcastTable() *McastTable {
140+
return &McastTable{
141+
socks: make(map[int]map[NetlinkSocket]struct{}),
142+
}
143+
}

pkg/sentry/inet/test_stack.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (s *TestStack) Destroy() {
6161
}
6262

6363
// RemoveInterface implements Stack.
64-
func (s *TestStack) RemoveInterface(idx int32) error {
64+
func (s *TestStack) RemoveInterface(ctx context.Context, idx int32) error {
6565
delete(s.InterfacesMap, idx)
6666
return nil
6767
}

pkg/sentry/socket/hostinet/stack.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func (s *Stack) Interfaces() map[int32]inet.Interface {
152152
}
153153

154154
// RemoveInterface implements inet.Stack.RemoveInterface.
155-
func (*Stack) RemoveInterface(idx int32) error {
155+
func (*Stack) RemoveInterface(ctx context.Context, idx int32) error {
156156
return removeInterface(idx)
157157
}
158158

pkg/sentry/socket/netlink/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ go_library(
1515
deps = [
1616
"//pkg/abi/linux",
1717
"//pkg/abi/linux/errno",
18+
"//pkg/atomicbitops",
1819
"//pkg/context",
1920
"//pkg/errors/linuxerr",
2021
"//pkg/hostarch",

pkg/sentry/socket/netlink/provider.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"gvisor.dev/gvisor/pkg/abi/linux"
2121
"gvisor.dev/gvisor/pkg/context"
2222
"gvisor.dev/gvisor/pkg/sentry/fsimpl/sockfs"
23+
"gvisor.dev/gvisor/pkg/sentry/inet"
2324
"gvisor.dev/gvisor/pkg/sentry/kernel"
2425
"gvisor.dev/gvisor/pkg/sentry/socket"
2526
"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
@@ -51,6 +52,18 @@ type Protocol interface {
5152
ProcessMessage(ctx context.Context, s *Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error
5253
}
5354

55+
// RouteProtocol corresponds to the NETLINK_ROUTE family.
56+
type RouteProtocol interface {
57+
Protocol
58+
59+
// AddNewLinkMessage is called when an interface is mutated or created by the stack.
60+
// It is the rough equivalent of Linux's rtnetlink_event().
61+
AddNewLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface)
62+
63+
// AddDelLinkMessage is called when an interface is deleted by the stack.
64+
AddDelLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface)
65+
}
66+
5467
// Provider is a function that creates a new Protocol for a specific netlink
5568
// protocol.
5669
//

pkg/sentry/socket/netlink/route/protocol.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func (p *Protocol) dumpLinks(ctx context.Context, s *netlink.Socket, msg *nlmsg.
101101
}
102102

103103
for idx, i := range stack.Interfaces() {
104-
addNewLinkMessage(ms, idx, i)
104+
p.AddNewLinkMessage(ms, idx, i)
105105
}
106106

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

161-
addNewLinkMessage(ms, idx, i)
161+
p.AddNewLinkMessage(ms, idx, i)
162162
found = true
163163
break
164164
}
@@ -232,16 +232,10 @@ func (p *Protocol) delLink(ctx context.Context, s *netlink.Socket, msg *nlmsg.Me
232232
return syserr.ErrNoDevice
233233
}
234234
}
235-
return syserr.FromError(stack.RemoveInterface(ifinfomsg.Index))
235+
return syserr.FromError(stack.RemoveInterface(ctx, ifinfomsg.Index))
236236
}
237237

238-
// addNewLinkMessage appends RTM_NEWLINK message for the given interface into
239-
// the message set.
240-
func addNewLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface) {
241-
m := ms.AddMessage(linux.NetlinkMessageHeader{
242-
Type: linux.RTM_NEWLINK,
243-
})
244-
238+
func writeLinkInfo(m *nlmsg.Message, idx int32, i inet.Interface) {
245239
m.Put(&linux.InterfaceInfoMessage{
246240
Family: linux.AF_UNSPEC,
247241
Type: i.DeviceType,
@@ -264,6 +258,26 @@ func addNewLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface) {
264258
// TODO(gvisor.dev/issue/578): There are many more attributes.
265259
}
266260

261+
// AddNewLinkMessage appends an RTM_NEWLINK message for the given interface into
262+
// the message set.
263+
// AddNewLinkMessage implements netlink.RouteProtocol.AddNewLinkMessage.
264+
func (p *Protocol) AddNewLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface) {
265+
m := ms.AddMessage(linux.NetlinkMessageHeader{
266+
Type: linux.RTM_NEWLINK,
267+
})
268+
writeLinkInfo(m, idx, i)
269+
}
270+
271+
// AddDelLinkMessage appends an RTM_DELLINK message for the given interface into
272+
// the message set.
273+
// AddDelLinkMessage implements netlink.RouteProtocol.AddDelLinkMessage.
274+
func (p *Protocol) AddDelLinkMessage(ms *nlmsg.MessageSet, idx int32, i inet.Interface) {
275+
m := ms.AddMessage(linux.NetlinkMessageHeader{
276+
Type: linux.RTM_DELLINK,
277+
})
278+
writeLinkInfo(m, idx, i)
279+
}
280+
267281
// dumpAddrs handles RTM_GETADDR dump requests.
268282
func (p *Protocol) dumpAddrs(ctx context.Context, s *netlink.Socket, msg *nlmsg.Message, ms *nlmsg.MessageSet) *syserr.Error {
269283
// RTM_GETADDR dump requests need not contain anything more than the

0 commit comments

Comments
 (0)