* [PATCH net-next v3 0/7] vsock: assign the guest vsock device to a network namespace
@ 2026-10-12 22:48 Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 1/7] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
` (6 more replies)
0 siblings, 7 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-10-12 22:48 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
vsock network namespaces let a host put each VM in a namespace of its
own. A guest has no equivalent yet. It has a single G2H device that
cannot be assigned to a network namespace.
This series lets a guest move that device into a network namespace. A
new generic netlink family, "vsock", supports the command
VSOCK_CMD_DEV_NETNS_SET which assigns the device to the namespace the
request was sent from. The namespace's existing ns_mode then decides who
may use it: a "global" namespace shares the device with every other
global namespace, and a "local" namespace keeps the host connection to
itself. The device starts out in the initial namespace, so until the
command is issued nothing has moved and no mode has changed. There is no
explicit unassign as assigning the device back to the initial namespace
is equivalent.
The command requires CAP_NET_ADMIN in the initial user namespace, so
that an unprivileged user namespace cannot claim the device.
Connections that can no longer reach the device after a move are reset,
so that a namespace which has lost access cannot keep using a socket it
opened while it still had access. The assignment holds a reference on
the namespace, so deleting that namespace does not move the device; only
another assignment does.
Transports opt in through a new netns_assign_allow flag. Only
virtio-vsock sets it here, because it is the only guest transport
that I am able to test against.
Based off of Stefano's original series:
https://lore.kernel.org/all/20200116172428.311437-1-sgarzare@redhat.com/
Suggested-by: Stefano Garzarella <sgarzare@redhat.com>
Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v3:
- Lots of fixes for race conditions found by local and upstream Sashiko.
Sorry for the delayed reposting, it took significant time to track
down and confirm that these were real issues, and come up with a
decent solution for each...
- Pin the assigned netns instead of returning the device to init_net
when it is deleted, and refuse an assign that would make the device's
CID collide with a nested H2G vhost-vsock with the same CID
- Drop the idea of sending RST to the peer from the reset sweep. Sending
a RST required a lock_sock, which can't be acquired underneath the
vsock_table_lock. Splitting into two phases of locking opened up new
races, I couldn't find a solution I was really happy with. Probably
not worth the complexity
- New prep patch passing the netns to ->has_remote_cid(), which is used
in a later patch in the series for avoid CID collisions between nested
H2G guests and the G2H device
- Various fixes (see individual patch change lists for more details)
- Link to v2: https://lore.kernel.org/r/20260921-vsock-guest-ns-v2-0-693bd78fde9e@meta.com
Changes in v2:
- Replace the IOCTL_VM_SOCKETS_ASSIGN_G2H_NETNS ioctl with the "vsock"
genl family and VSOCK_CMD_DEV_NETNS_SET
- RST the peer from the reset sweep, v1 only set TCP_CLOSE locally, so
the peer waited on a connection the guest had abandoned (Stefano)
- Drop the vsock_assign_g2h_netns helper patch, the tests use ynl cli.py
- Note why only virtio-vsock opts in (Stefano)
- Add a getter to the uAPI
- Various fixes (see individual patch change list for more details)
- Link to v1: https://lore.kernel.org/r/20260902-vsock-guest-ns-v1-0-9995383e9a8b@meta.com
---
Bobby Eshleman (7):
vsock: constify the transport in vsock_for_each_connected_socket()
vsock: rename the vsock pernet operations
vsock: pass the netns to ->has_remote_cid()
vsock: add a netlink command to assign the g2h device to a netns
vsock/virtio: support guest device network namespace
selftests/vsock: test the guest vsock device network namespace
selftests/vsock: test the netns assign privilege checks
Documentation/admin-guide/sysctl/net.rst | 37 +++
Documentation/netlink/specs/vsock.yaml | 75 +++++
MAINTAINERS | 2 +
drivers/vhost/vsock.c | 23 +-
include/net/af_vsock.h | 17 +-
include/uapi/linux/vsock.h | 28 ++
net/vmw_vsock/Makefile | 2 +-
net/vmw_vsock/af_vsock.c | 459 +++++++++++++++++++++++--
net/vmw_vsock/virtio_transport.c | 25 +-
net/vmw_vsock/virtio_transport_common.c | 18 +-
net/vmw_vsock/vsock_nl_gen.c | 36 ++
net/vmw_vsock/vsock_nl_gen.h | 20 ++
tools/net/ynl/Makefile.deps | 1 +
tools/testing/selftests/vsock/Makefile | 5 +
tools/testing/selftests/vsock/config | 1 +
tools/testing/selftests/vsock/vmtest.sh | 554 ++++++++++++++++++++++++++++++-
16 files changed, 1250 insertions(+), 53 deletions(-)
---
base-commit: 8df0638138d3e0344fd1fb36cf2d1ca1cf5028f0
change-id: 20260831-vsock-guest-ns-d06af451da67
Best regards,
--
Bobby Eshleman <bobbyeshleman@meta.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v3 1/7] vsock: constify the transport in vsock_for_each_connected_socket()
2026-10-12 22:48 [PATCH net-next v3 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
@ 2026-10-12 22:48 ` Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 2/7] vsock: rename the vsock pernet operations Bobby Eshleman
` (5 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-10-12 22:48 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
Allow const transports to be passed too. The function only compares the
pointer against vsk->transport, which is itself const, and never writes
through it.
No functional change.
Reviewed-by: Stefano Garzarella <sgarzare@redhat.com>
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
include/net/af_vsock.h | 2 +-
net/vmw_vsock/af_vsock.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 5549298c1ec6..370fcd3ddabc 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -233,7 +233,7 @@ bool vsock_check_source(const struct vsock_sock *vsk,
const struct vsock_transport *transport,
const struct sockaddr_vm *src);
void vsock_remove_sock(struct vsock_sock *vsk);
-void vsock_for_each_connected_socket(struct vsock_transport *transport,
+void vsock_for_each_connected_socket(const struct vsock_transport *transport,
void (*fn)(struct sock *sk));
int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);
bool vsock_find_cid(unsigned int cid);
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 44cee7451955..f4f091b2a6e3 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -480,7 +480,7 @@ void vsock_remove_sock(struct vsock_sock *vsk)
}
EXPORT_SYMBOL_GPL(vsock_remove_sock);
-void vsock_for_each_connected_socket(struct vsock_transport *transport,
+void vsock_for_each_connected_socket(const struct vsock_transport *transport,
void (*fn)(struct sock *sk))
{
int i;
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v3 2/7] vsock: rename the vsock pernet operations
2026-10-12 22:48 [PATCH net-next v3 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 1/7] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
@ 2026-10-12 22:48 ` Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 3/7] vsock: pass the netns to ->has_remote_cid() Bobby Eshleman
` (4 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-10-12 22:48 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
vsock_sysctl_ops and its two callbacks are named for the sysctl tables
they register, but the init callback already does more than that: it
also initialises the per-namespace vsock state through vsock_net_init().
Rename them to vsock_pernet_ops, vsock_pernet_init and
vsock_pernet_exit, so that later per-namespace work can be placed here
too.
No functional change.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
net/vmw_vsock/af_vsock.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index f4f091b2a6e3..f7dbef8cf6e7 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -3036,7 +3036,7 @@ static void vsock_net_init(struct net *net)
net->vsock.g2h_fallback = 1;
}
-static __net_init int vsock_sysctl_init_net(struct net *net)
+static __net_init int vsock_pernet_init(struct net *net)
{
vsock_net_init(net);
@@ -3046,14 +3046,14 @@ static __net_init int vsock_sysctl_init_net(struct net *net)
return 0;
}
-static __net_exit void vsock_sysctl_exit_net(struct net *net)
+static __net_exit void vsock_pernet_exit(struct net *net)
{
vsock_sysctl_unregister(net);
}
-static struct pernet_operations vsock_sysctl_ops = {
- .init = vsock_sysctl_init_net,
- .exit = vsock_sysctl_exit_net,
+static struct pernet_operations vsock_pernet_ops = {
+ .init = vsock_pernet_init,
+ .exit = vsock_pernet_exit,
};
static int __init vsock_init(void)
@@ -3083,7 +3083,7 @@ static int __init vsock_init(void)
goto err_unregister_proto;
}
- if (register_pernet_subsys(&vsock_sysctl_ops)) {
+ if (register_pernet_subsys(&vsock_pernet_ops)) {
err = -ENOMEM;
goto err_unregister_sock;
}
@@ -3107,7 +3107,7 @@ static void __exit vsock_exit(void)
misc_deregister(&vsock_device);
sock_unregister(AF_VSOCK);
proto_unregister(&vsock_proto);
- unregister_pernet_subsys(&vsock_sysctl_ops);
+ unregister_pernet_subsys(&vsock_pernet_ops);
}
const struct vsock_transport *vsock_core_get_transport(struct vsock_sock *vsk)
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v3 3/7] vsock: pass the netns to ->has_remote_cid()
2026-10-12 22:48 [PATCH net-next v3 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 1/7] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 2/7] vsock: rename the vsock pernet operations Bobby Eshleman
@ 2026-10-12 22:48 ` Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 4/7] vsock: add a netlink command to assign the g2h device to a netns Bobby Eshleman
` (3 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-10-12 22:48 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
The transports only use the socket to find its network namespace. Pass
the namespace instead, so that a later patch can ask whether a CID is
taken in a namespace without necessarily having a socket on hand. This
will help with avoiding CID collisions when devices move between
namespaces.
No functional change.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
drivers/vhost/vsock.c | 4 +---
include/net/af_vsock.h | 2 +-
net/vmw_vsock/af_vsock.c | 6 ++++--
net/vmw_vsock/virtio_transport.c | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index abed1fbcf66c..01f8930ca51f 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -91,10 +91,8 @@ static struct vhost_vsock *vhost_vsock_get(u32 guest_cid, struct net *net)
return NULL;
}
-static bool vhost_transport_has_remote_cid(struct vsock_sock *vsk, u32 cid)
+static bool vhost_transport_has_remote_cid(struct net *net, u32 cid)
{
- struct sock *sk = sk_vsock(vsk);
- struct net *net = sock_net(sk);
bool found;
rcu_read_lock();
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 370fcd3ddabc..1c38a27fadf2 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -183,7 +183,7 @@ struct vsock_transport {
* CIDs via the hypervisor (i.e. supports the fallback overlay). VMCI
* does not implement this as it only serves CIDs 0 and 2.
*/
- bool (*has_remote_cid)(struct vsock_sock *vsk, u32 remote_cid);
+ bool (*has_remote_cid)(struct net *net, u32 remote_cid);
/* Read a single skb */
int (*read_skb)(struct vsock_sock *, skb_read_actor_t);
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index f7dbef8cf6e7..8f34bf68f916 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -633,11 +633,13 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
new_transport = transport_g2h;
else if (transport_h2g &&
(!transport_h2g->has_remote_cid ||
- transport_h2g->has_remote_cid(vsk, remote_cid)))
+ transport_h2g->has_remote_cid(sock_net(sk),
+ remote_cid)))
new_transport = transport_h2g;
else if (sock_net(sk)->vsock.g2h_fallback &&
transport_g2h && transport_g2h->has_remote_cid &&
- transport_g2h->has_remote_cid(vsk, remote_cid)) {
+ transport_g2h->has_remote_cid(sock_net(sk),
+ remote_cid)) {
vsk->remote_addr.svm_flags |= VMADDR_FLAG_TO_HOST;
new_transport = transport_g2h;
} else {
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa..eced6929405d 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -548,7 +548,7 @@ bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)
static bool virtio_transport_seqpacket_allow(struct vsock_sock *vsk,
u32 remote_cid);
-static bool virtio_transport_has_remote_cid(struct vsock_sock *vsk, u32 cid)
+static bool virtio_transport_has_remote_cid(struct net *net, u32 cid)
{
/* The CID could be implemented by the host. Always assume it is. */
return true;
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v3 4/7] vsock: add a netlink command to assign the g2h device to a netns
2026-10-12 22:48 [PATCH net-next v3 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (2 preceding siblings ...)
2026-10-12 22:48 ` [PATCH net-next v3 3/7] vsock: pass the netns to ->has_remote_cid() Bobby Eshleman
@ 2026-10-12 22:48 ` Bobby Eshleman
2026-10-13 1:44 ` Daniel Pereira
2026-10-12 22:48 ` [PATCH net-next v3 5/7] vsock/virtio: support guest device network namespace Bobby Eshleman
` (2 subsequent siblings)
6 siblings, 1 reply; 10+ messages in thread
From: Bobby Eshleman @ 2026-10-12 22:48 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
Namespaces let a host isolate a VM's vsock traffic to a specific
namespace, but in a guest vsock traffic cannot be isolated to a
namespace. The vsock device is hardcoded to global mode and can't be
moved into a local-mode namespace.
Introduce a vsock generic netlink family with VSOCK_CMD_DEV_NETNS_SET,
which moves the device to the namespace the request was sent from.
VSOCK_CMD_DEV_NETNS_GET reads the assignment back, reporting the
namespace as an nsid relative to the caller. VSOCK_CMD_DEV_NETNS_SET
replies with the same message.
The command requires CAP_NET_ADMIN in the initial user namespace. A
privileged user wishing to "unassign" the device can move it to the
init_net, which is hardcoded to global mode (so no unassign call is
necessary).
Connected sockets are closed with ECONNRESET when the underlying device
moves to a different namespace, so as to prevent reachability from the
previous and now disallowed namespace.
Device netns assignment is rejected when it would cause a CID collision
(e.g., moving a g2h device with CID 10 into a namespace with a nested
H2G VM with CID 10). On namespace destruction, the netdev approach
cannot be emulated. A netdev's IP addresses are removed before it
returns to init_net, which makes an address collision impossible, but
the vsock device keeps its CID. Instead, namespace destruction is
deferred until the device has moved. Again, CAP_NET_ADMIN in the init
user namespace is required for the assignment.
Suggested-by: Stefano Garzarella <sgarzare@redhat.com>
Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v3:
- Try to use net_tracker when taking net references
- Take a reference on the net when assigning a device to it. This keeps
the net alive even after deletion. Drop the concept of moving the
device back to init_net on net destruction. This avoids the problem
of the device CID colliding with a nested h2g CID.
- Refuse the assign with -EADDRINUSE when a guest in the target netns
already answers to the device's CID, via the h2g transport's
->has_remote_cid()
- Hold vsock_register_mutex across the CID check and commit in
vhost_vsock_set_cid(), so it cannot race an assign. We also add a
lockdep assertion into vsock_find_cid() because it is a requirement to
avoid racing between netns assignment of a g2h device and setting a
nested h2g CID (I know, not ideal looking)
- Unlink swept sockets from vsock_connected_table, so a retried
connect() cannot insert the same node twice
- Drop the RST to the peer from the sweep, along with the ->reset op and
the pending_reset list (Sashiko)
- Reply to VSOCK_CMD_DEV_NETNS_SET with the netns-id instead a plain ACK
(Sashiko)
Changes in v2:
- New patch, replaces the ioctl with a genl family and
VSOCK_CMD_DEV_NETNS_SET (Stefano)
- Make netns_assign_allow a bool, not a callback (Stefano)
- One vsock_netns_assignable(t) helper for both call sites (Stefano)
- Make vsock_g2h_reachable_sk() static, drop the export (Stefano)
- RST the peer when socket loses access to the device's net (Stefano)
- Move the netns reset out of the sysctl exit hook, into the renamed
vsock_pernet_ops (Stefano).
- Remove the synchronize_rcu() from the pernet exit path. The
net_namespace documentation states that synchronize_rcu() should be
avoided in the pernet exit path.
- Run the socket reset in the pernet pre_exit() hook, as the
net_namespace management code (and documentation) guarantees
synchronize_rcu() between pre_exit() and exit()
- Drop the conditional synchronize_rcu() in vsock_core_unregister()
(Stefano)
- Specifically state "G2H transport" in the netns_assign_allow comment (Stefano)
- Avoid leaking the g2h CID to a namespace that cannot reach it;
GET_LOCAL_CID, bind() and loopback transport selection all read it
through vsock_registered_transport_cid(), which now takes a netns
- Add VSOCK_CMD_DEV_NETNS_GET
---
Documentation/admin-guide/sysctl/net.rst | 37 +++
Documentation/netlink/specs/vsock.yaml | 75 ++++++
MAINTAINERS | 2 +
drivers/vhost/vsock.c | 19 +-
include/net/af_vsock.h | 13 +-
include/uapi/linux/vsock.h | 28 ++
net/vmw_vsock/Makefile | 2 +-
net/vmw_vsock/af_vsock.c | 437 +++++++++++++++++++++++++++++--
net/vmw_vsock/vsock_nl_gen.c | 36 +++
net/vmw_vsock/vsock_nl_gen.h | 20 ++
tools/net/ynl/Makefile.deps | 1 +
11 files changed, 649 insertions(+), 21 deletions(-)
diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst
index fe43e8595958..9276479004c0 100644
--- a/Documentation/admin-guide/sysctl/net.rst
+++ b/Documentation/admin-guide/sysctl/net.rst
@@ -529,6 +529,43 @@ their hosts. The behavior of VSOCK sockets in a network namespace is determined
by the namespace's mode (``global`` or ``local``), which controls how CIDs
(Context IDs) are allocated and how sockets interact across namespaces.
+In a guest, the vsock device owned by the guest-to-host (G2H) transport belongs
+to one network namespace at a time. The ``VSOCK_CMD_DEV_NETNS_SET`` command of
+the ``vsock`` netlink family, described in
+Documentation/netlink/specs/vsock.yaml, moves it to the namespace the request
+was sent from, which requires ``CAP_NET_ADMIN`` in the initial user namespace.
+The namespace's mode decides who may then use the device:
+
+- ``global`` - every ``global`` mode namespace may use it.
+- ``local`` - only that namespace may use it, which reserves the connection to
+ the host for it alone.
+
+The device starts out in init_net, so until the command is issued nothing has
+moved and no mode has changed.
+
+Support is transport dependent. A G2H transport that does not implement the
+move refuses the command with ``EOPNOTSUPP``. Of the in-tree guest transports,
+only virtio-vsock implements it.
+
+Connections established before the move, from a namespace that can no longer
+reach the device, are closed with ``ECONNRESET``. Nothing is sent to the peer,
+which is what already happens when the device is removed altogether: the peer
+finds out on its next send, which no longer matches a socket and so is answered
+with a reset. A peer that only receives waits for its own timeout. A connection
+still being set up when the device moves is refused with a reset once the peer
+answers it.
+
+An assignment keeps the namespace alive, the same way an open socket does, so
+deleting that namespace does not move the device. Assigning the device to
+another namespace, such as init_net, is the only way to undo an assignment, and
+releases the reference the assignment held on the previous namespace.
+
+The move is refused with ``EADDRINUSE`` if a guest registered in the target
+namespace already answers to the device's CID, since the CID would otherwise
+name two different peers there. If a loaded H2G transport cannot check that,
+the move is refused with ``EOPNOTSUPP``. Of the in-tree host transports, only
+vhost-vsock can.
+
ns_mode
-------
diff --git a/Documentation/netlink/specs/vsock.yaml b/Documentation/netlink/specs/vsock.yaml
new file mode 100644
index 000000000000..74cf2472bf91
--- /dev/null
+++ b/Documentation/netlink/specs/vsock.yaml
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+#
+# Copyright (c) 2026 Meta Platforms, Inc. and affiliates
+#
+---
+name: vsock
+
+doc: |
+ Control interface for the vsock (AF_VSOCK) core.
+
+protocol: genetlink
+
+uapi-header: linux/vsock.h
+
+attribute-sets:
+ -
+ name: vsock
+ attributes:
+ -
+ name: netns-id
+ type: s32
+ doc: |
+ Network namespace the guest's vsock device is assigned to, as an
+ nsid relative to the caller.
+
+ Absent when the device is in the caller's own namespace, and
+ NETNSA_NSID_NOT_ASSIGNED when the g2h transport supports netns
+ assignment and the caller's namespace cannot reach the device under
+ the mode rules, or when no nsid could be allocated for it.
+
+operations:
+ list:
+ -
+ name: dev-netns-set
+ doc: |
+ Set the network namespace of the guest's vsock device.
+
+ The device is moved to the namespace the request was sent from. It
+ starts out in init_net, and moving it back there is how an
+ assignment is undone.
+
+ A g2h transport that does not support netns assignment returns
+ EOPNOTSUPP. If the netns assignment would cause a CID collision, it
+ is rejected with EADDRINUSE.
+
+ The namespace's mode decides who may then use the device: a global
+ mode namespace shares it with every other global mode namespace,
+ while a local mode namespace reserves it for itself.
+
+ Movement to an unreachable local namespace results in sockets being
+ closed with ECONNRESET. Peers are not guaranteed to be notified via
+ an RST.
+
+ The assignment holds a reference on the namespace.
+ attribute-set: vsock
+ flags: [admin-perm]
+ do:
+ reply:
+ attributes:
+ - netns-id
+ -
+ name: dev-netns-get
+ doc: |
+ Get the network namespace the guest's vsock device is assigned to.
+
+ The namespace is reported as an nsid relative to the caller, so the
+ attribute is absent when the device is already in the caller's own
+ namespace. If the g2h transport supports netns assignment, a caller
+ whose namespace cannot reach the device is told
+ NETNSA_NSID_NOT_ASSIGNED rather than where the device went.
+ attribute-set: vsock
+ do:
+ reply:
+ attributes:
+ - netns-id
diff --git a/MAINTAINERS b/MAINTAINERS
index 72ca3aab2106..f11423727449 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29163,10 +29163,12 @@ M: Stefano Garzarella <sgarzare@redhat.com>
L: virtualization@lists.linux.dev
L: netdev@vger.kernel.org
S: Maintained
+F: Documentation/netlink/specs/vsock.yaml
F: drivers/net/vsockmon.c
F: include/net/af_vsock.h
F: include/uapi/linux/vm_sockets.h
F: include/uapi/linux/vm_sockets_diag.h
+F: include/uapi/linux/vsock.h
F: include/uapi/linux/vsockmon.h
F: net/vmw_vsock/
F: tools/testing/selftests/vsock/
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index 01f8930ca51f..11893f1943c6 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -815,6 +815,7 @@ static int vhost_vsock_dev_release(struct inode *inode, struct file *file)
static int vhost_vsock_set_cid(struct vhost_vsock *vsock, u64 guest_cid)
{
struct vhost_vsock *other;
+ int ret;
/* Refuse reserved CIDs */
if (guest_cid <= VMADDR_CID_HOST ||
@@ -828,15 +829,20 @@ static int vhost_vsock_set_cid(struct vhost_vsock *vsock, u64 guest_cid)
/* Refuse if CID is assigned to the guest->host transport (i.e. nested
* VM), to make the loopback work.
*/
- if (vsock_find_cid(guest_cid))
- return -EADDRINUSE;
+ mutex_lock(&vsock_register_mutex);
+
+ if (__vsock_find_cid(vsock->net, guest_cid)) {
+ ret = -EADDRINUSE;
+ goto unlock;
+ }
/* Refuse if CID is already in use */
mutex_lock(&vhost_vsock_mutex);
other = vhost_vsock_get(guest_cid, vsock->net);
if (other && other != vsock) {
mutex_unlock(&vhost_vsock_mutex);
- return -EADDRINUSE;
+ ret = -EADDRINUSE;
+ goto unlock;
}
if (vsock->guest_cid)
@@ -846,7 +852,12 @@ static int vhost_vsock_set_cid(struct vhost_vsock *vsock, u64 guest_cid)
hash_add_rcu(vhost_vsock_hash, &vsock->hash, vsock->guest_cid);
mutex_unlock(&vhost_vsock_mutex);
- return 0;
+ ret = 0;
+
+unlock:
+ mutex_unlock(&vsock_register_mutex);
+
+ return ret;
}
static int vhost_vsock_set_features(struct vhost_vsock *vsock, u64 features)
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 1c38a27fadf2..883a98dab36d 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -9,6 +9,7 @@
#define __AF_VSOCK_H__
#include <linux/kernel.h>
+#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <net/netns/vsock.h>
#include <net/sock.h>
@@ -22,6 +23,7 @@
extern struct list_head vsock_bind_table[VSOCK_HASH_SIZE + 1];
extern struct list_head vsock_connected_table[VSOCK_HASH_SIZE];
extern spinlock_t vsock_table_lock;
+extern struct mutex vsock_register_mutex;
#define vsock_sk(__sk) ((struct vsock_sock *)__sk)
#define sk_vsock(__vsk) (&(__vsk)->sk)
@@ -190,6 +192,9 @@ struct vsock_transport {
/* Zero-copy. */
bool (*msgzerocopy_allow)(void);
+
+ /* True if the G2H transport honours VSOCK_CMD_DEV_NETNS_SET. */
+ bool netns_assign_allow;
};
/**** CORE ****/
@@ -236,8 +241,14 @@ void vsock_remove_sock(struct vsock_sock *vsk);
void vsock_for_each_connected_socket(const struct vsock_transport *transport,
void (*fn)(struct sock *sk));
int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk);
-bool vsock_find_cid(unsigned int cid);
+bool vsock_find_cid(struct net *net, unsigned int cid);
+/* Caller must hold vsock_register_mutex. */
+bool __vsock_find_cid(struct net *net, unsigned int cid);
void vsock_linger(struct sock *sk);
+struct net *vsock_g2h_net_get(netns_tracker *tracker);
+bool vsock_g2h_net_eq(const struct net *net);
+bool vsock_g2h_net_reachable(struct net *net);
+bool vsock_maybe_set_connected(struct vsock_sock *vsk);
/**** TAP ****/
diff --git a/include/uapi/linux/vsock.h b/include/uapi/linux/vsock.h
new file mode 100644
index 000000000000..803b9aa1f0b3
--- /dev/null
+++ b/include/uapi/linux/vsock.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/vsock.yaml */
+/* YNL-GEN uapi header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _UAPI_LINUX_VSOCK_H
+#define _UAPI_LINUX_VSOCK_H
+
+#define VSOCK_FAMILY_NAME "vsock"
+#define VSOCK_FAMILY_VERSION 1
+
+enum {
+ VSOCK_A_NETNS_ID = 1,
+
+ __VSOCK_A_MAX,
+ VSOCK_A_MAX = (__VSOCK_A_MAX - 1)
+};
+
+enum {
+ VSOCK_CMD_DEV_NETNS_SET = 1,
+ VSOCK_CMD_DEV_NETNS_GET,
+
+ __VSOCK_CMD_MAX,
+ VSOCK_CMD_MAX = (__VSOCK_CMD_MAX - 1)
+};
+
+#endif /* _UAPI_LINUX_VSOCK_H */
diff --git a/net/vmw_vsock/Makefile b/net/vmw_vsock/Makefile
index 5da74c4a9f1d..97e2a559e2bf 100644
--- a/net/vmw_vsock/Makefile
+++ b/net/vmw_vsock/Makefile
@@ -7,7 +7,7 @@ obj-$(CONFIG_VIRTIO_VSOCKETS_COMMON) += vmw_vsock_virtio_transport_common.o
obj-$(CONFIG_HYPERV_VSOCKETS) += hv_sock.o
obj-$(CONFIG_VSOCKETS_LOOPBACK) += vsock_loopback.o
-vsock-y += af_vsock.o af_vsock_tap.o vsock_addr.o
+vsock-y += af_vsock.o af_vsock_tap.o vsock_addr.o vsock_nl_gen.o
vsock-$(CONFIG_BPF_SYSCALL) += vsock_bpf.o
vsock_diag-y += diag.o
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 8f34bf68f916..e663be3bbfa0 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -130,6 +130,27 @@
* a different transport that *does* support local mode. For
* example, virtio-vsock may not support local mode, but the socket
* may still accept a connection from vhost-vsock which does.
+ *
+ * - A guest has a single vsock device, owned by the guest->host transport.
+ * The VSOCK_CMD_DEV_NETNS_SET netlink command moves it to the namespace the
+ * request was sent from. It starts out in init_net. The mode rules then
+ * decide who may use it, and which namespace packets from the host are
+ * delivered to:
+ *
+ * - assigned to a global mode namespace - every global mode namespace may
+ * use it. Until the command is issued nothing has moved and no mode has
+ * changed, so the default is the behaviour that predates it.
+ * - assigned to a local mode namespace - only that namespace may use it.
+ * This is how a nested VM is isolated from the rest of the guest.
+ *
+ * Connections made before an assignment, from a namespace that can no
+ * longer reach the device, are closed with ECONNRESET. Nothing is sent to
+ * the peer. As with device removal, it finds out on its next send.
+ *
+ * The assignment holds a reference on the namespace, the way an open socket
+ * does, so deleting that namespace does not move the device: its destruction
+ * is deferred until the device moves to another namespace, on another
+ * assignment or when the transport unregisters.
*/
#include <linux/compat.h>
@@ -161,10 +182,14 @@
#include <linux/workqueue.h>
#include <net/sock.h>
#include <net/af_vsock.h>
+#include <linux/net_namespace.h>
+#include <net/genetlink.h>
#include <net/netns/vsock.h>
#include <uapi/linux/vm_sockets.h>
#include <uapi/asm-generic/ioctls.h>
+#include "vsock_nl_gen.h"
+
#define VSOCK_NET_MODE_STR_GLOBAL "global"
#define VSOCK_NET_MODE_STR_LOCAL "local"
@@ -206,7 +231,15 @@ static const struct vsock_transport *transport_g2h;
static const struct vsock_transport *transport_dgram;
/* Transport used for local communication */
static const struct vsock_transport *transport_local;
-static DEFINE_MUTEX(vsock_register_mutex);
+/* Protects transport registration and the g2h device's netns. */
+DEFINE_MUTEX(vsock_register_mutex);
+EXPORT_SYMBOL_GPL(vsock_register_mutex);
+
+/* Network namespace of the g2h device. Protected by
+ * vsock_register_mutex/RCU.
+ */
+static struct net __rcu *vsock_g2h_net = RCU_INITIALIZER(&init_net);
+static netns_tracker vsock_g2h_net_tracker;
/**** UTILS ****/
@@ -553,7 +586,42 @@ void vsock_enqueue_accept(struct sock *listener, struct sock *connected)
}
EXPORT_SYMBOL_GPL(vsock_enqueue_accept);
-static bool vsock_use_local_transport(unsigned int remote_cid)
+/* Return true if @t honours namespace assignment. One that does not keeps the
+ * reachability rules it had before VSOCK_CMD_DEV_NETNS_SET existed.
+ */
+static bool vsock_net_assignable(const struct vsock_transport *t)
+{
+ return t && t->netns_assign_allow;
+}
+
+static bool vsock_g2h_unreachable(const struct vsock_transport *t,
+ struct net *net)
+{
+ return t == transport_g2h && vsock_net_assignable(t) &&
+ !vsock_g2h_net_reachable(net);
+}
+
+/* Return the CID of the transport in @transport, as seen from @net.
+ *
+ * Returns VMADDR_CID_ANY if the transport supports net assignment but is not
+ * reachable from the net.
+ */
+static u32
+__vsock_registered_transport_cid(const struct vsock_transport **transport,
+ struct net *net)
+{
+ lockdep_assert_held(&vsock_register_mutex);
+
+ if (!*transport)
+ return VMADDR_CID_ANY;
+
+ if (vsock_g2h_unreachable(*transport, net))
+ return VMADDR_CID_ANY;
+
+ return (*transport)->get_local_cid();
+}
+
+static bool vsock_use_local_transport(struct net *net, unsigned int remote_cid)
{
lockdep_assert_held(&vsock_register_mutex);
@@ -564,7 +632,12 @@ static bool vsock_use_local_transport(unsigned int remote_cid)
return true;
if (transport_g2h) {
- return remote_cid == transport_g2h->get_local_cid();
+ u32 cid = __vsock_registered_transport_cid(&transport_g2h, net);
+
+ /* The device may be unreachable from @net, in which case
+ * @remote_cid is not the local CID.
+ */
+ return cid != VMADDR_CID_ANY && remote_cid == cid;
} else {
return remote_cid == VMADDR_CID_HOST;
}
@@ -626,7 +699,7 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
break;
case SOCK_STREAM:
case SOCK_SEQPACKET:
- if (vsock_use_local_transport(remote_cid))
+ if (vsock_use_local_transport(sock_net(sk), remote_cid))
new_transport = transport_local;
else if (remote_cid <= VMADDR_CID_HOST ||
(remote_flags & VMADDR_FLAG_TO_HOST))
@@ -651,6 +724,11 @@ int vsock_assign_transport(struct vsock_sock *vsk, struct vsock_sock *psk)
goto err;
}
+ if (vsock_g2h_unreachable(new_transport, sock_net(sk))) {
+ ret = -ENETUNREACH;
+ goto err;
+ }
+
if (vsk->transport && vsk->transport == new_transport) {
ret = 0;
goto err;
@@ -717,21 +795,24 @@ EXPORT_SYMBOL_GPL(vsock_assign_transport);
* Provide safe access to static transport_{h2g,g2h,dgram,local} callbacks.
* Otherwise we may race with module removal. Do not use on `vsk->transport`.
*/
-static u32 vsock_registered_transport_cid(const struct vsock_transport **transport)
+static u32
+vsock_registered_transport_cid(const struct vsock_transport **transport,
+ struct net *net)
{
- u32 cid = VMADDR_CID_ANY;
+ u32 cid;
mutex_lock(&vsock_register_mutex);
- if (*transport)
- cid = (*transport)->get_local_cid();
+ cid = __vsock_registered_transport_cid(transport, net);
mutex_unlock(&vsock_register_mutex);
return cid;
}
-bool vsock_find_cid(unsigned int cid)
+bool __vsock_find_cid(struct net *net, unsigned int cid)
{
- if (cid == vsock_registered_transport_cid(&transport_g2h))
+ lockdep_assert_held(&vsock_register_mutex);
+
+ if (cid == __vsock_registered_transport_cid(&transport_g2h, net))
return true;
if (transport_h2g && cid == VMADDR_CID_HOST)
@@ -742,8 +823,267 @@ bool vsock_find_cid(unsigned int cid)
return false;
}
+EXPORT_SYMBOL_GPL(__vsock_find_cid);
+
+bool vsock_find_cid(struct net *net, unsigned int cid)
+{
+ bool found;
+
+ mutex_lock(&vsock_register_mutex);
+ found = __vsock_find_cid(net, cid);
+ mutex_unlock(&vsock_register_mutex);
+
+ return found;
+}
EXPORT_SYMBOL_GPL(vsock_find_cid);
+/* Return the namespace the g2h device is assigned to, with a reference held.
+ *
+ * The assignment itself holds a reference, so the namespace cannot be going
+ * away here and this never fails.
+ */
+struct net *vsock_g2h_net_get(netns_tracker *tracker)
+{
+ struct net *net;
+
+ rcu_read_lock();
+ net = rcu_dereference(vsock_g2h_net);
+ get_net_track(net, tracker, GFP_ATOMIC);
+ rcu_read_unlock();
+
+ return net;
+}
+EXPORT_SYMBOL_GPL(vsock_g2h_net_get);
+
+/* True if @net is still the namespace the device is assigned to. Cheap enough
+ * to call per packet on the RX path.
+ */
+bool vsock_g2h_net_eq(const struct net *net)
+{
+ return rcu_access_pointer(vsock_g2h_net) == net;
+}
+EXPORT_SYMBOL_GPL(vsock_g2h_net_eq);
+
+bool vsock_g2h_net_reachable(struct net *net)
+{
+ bool reachable;
+
+ rcu_read_lock();
+ reachable = vsock_net_check_mode(net, rcu_dereference(vsock_g2h_net));
+ rcu_read_unlock();
+
+ return reachable;
+}
+EXPORT_SYMBOL_GPL(vsock_g2h_net_reachable);
+
+static bool vsock_g2h_reachable_sk(struct vsock_sock *vsk)
+{
+ if (!vsock_net_assignable(vsk->transport))
+ return true;
+
+ return vsock_g2h_net_reachable(sock_net(sk_vsock(vsk)));
+}
+
+/* Move @vsk to TCP_ESTABLISHED and into the connected table, unless the device
+ * has moved to a namespace @vsk cannot reach. Returns false without doing
+ * either in that case.
+ *
+ * The reset sweep walks the same table under the same lock, so an assign
+ * cannot land between the check and the insert: either the sweep finds @vsk
+ * and closes it, or @vsk is never added.
+ */
+bool vsock_maybe_set_connected(struct vsock_sock *vsk)
+{
+ struct list_head *list;
+ bool reachable;
+
+ list = vsock_connected_sockets(&vsk->remote_addr, &vsk->local_addr);
+
+ spin_lock_bh(&vsock_table_lock);
+ reachable = vsock_g2h_reachable_sk(vsk);
+ if (reachable) {
+ sk_vsock(vsk)->sk_state = TCP_ESTABLISHED;
+ __vsock_insert_connected(list, vsk);
+ }
+ spin_unlock_bh(&vsock_table_lock);
+
+ return reachable;
+}
+EXPORT_SYMBOL_GPL(vsock_maybe_set_connected);
+
+/* Close every connected socket of @t that can no longer reach the g2h device.
+ */
+static void vsock_g2h_reset_unreachable(const struct vsock_transport *t)
+{
+ struct vsock_sock *vsk, *tmp;
+ struct sock *sk;
+ int i;
+
+ lockdep_assert_held(&vsock_register_mutex);
+
+ spin_lock_bh(&vsock_table_lock);
+
+ for (i = 0; i < ARRAY_SIZE(vsock_connected_table); i++) {
+ list_for_each_entry_safe(vsk, tmp, &vsock_connected_table[i],
+ connected_table) {
+ sk = sk_vsock(vsk);
+
+ if (vsk->transport != t ||
+ sk->sk_state == TCP_CLOSE ||
+ vsock_g2h_reachable_sk(vsk))
+ continue;
+
+ /* If close has already been scheduled for this socket,
+ * we can skip.
+ */
+ if (vsk->close_work_scheduled)
+ continue;
+
+ sk->sk_state = TCP_CLOSE;
+ sk->sk_err = ECONNRESET;
+ sk_error_report(sk);
+
+ __vsock_remove_connected(vsk);
+ }
+ }
+
+ spin_unlock_bh(&vsock_table_lock);
+}
+
+/* Point the g2h device at @net, taking the reference that keeps @net alive,
+ * and return the namespace it leaves behind.
+ */
+static struct net *vsock_g2h_net_swap(struct net *net, netns_tracker *old)
+{
+ lockdep_assert_held(&vsock_register_mutex);
+
+ *old = vsock_g2h_net_tracker;
+ get_net_track(net, &vsock_g2h_net_tracker, GFP_KERNEL);
+
+ return rcu_replace_pointer(vsock_g2h_net, net,
+ lockdep_is_held(&vsock_register_mutex));
+}
+
+/* Drop the namespace the device just left, once the readers that may still
+ * see it in vsock_g2h_net_get() are gone. Runs without the mutex held.
+ */
+static void vsock_g2h_net_release(struct net *old, netns_tracker *tracker)
+{
+ synchronize_rcu();
+ put_net_track(old, tracker);
+}
+
+static int vsock_nl_dev_netns_fill(struct sk_buff *msg, struct genl_info *info)
+{
+ struct net *net = genl_info_net(info);
+ struct net *assigned;
+ bool equal;
+ void *hdr;
+ s32 id;
+
+ lockdep_assert_held(&vsock_register_mutex);
+
+ rcu_read_lock();
+ assigned = rcu_dereference(vsock_g2h_net);
+ equal = net_eq(net, assigned);
+ if (!equal) {
+ if (vsock_net_assignable(transport_g2h) &&
+ !vsock_net_check_mode(net, assigned))
+ id = NETNSA_NSID_NOT_ASSIGNED;
+ else
+ id = peernet2id_alloc(net, assigned, GFP_ATOMIC);
+ }
+ rcu_read_unlock();
+
+ hdr = genlmsg_iput(msg, info);
+ if (!hdr)
+ return -EMSGSIZE;
+
+ if (!equal && nla_put_s32(msg, VSOCK_A_NETNS_ID, id)) {
+ genlmsg_cancel(msg, hdr);
+ return -EMSGSIZE;
+ }
+
+ genlmsg_end(msg, hdr);
+
+ return 0;
+}
+
+/* Move the g2h device to @net, which keeps @net alive until the device leaves
+ * again. Returns -ENODEV if no g2h transport is loaded, -EOPNOTSUPP if the
+ * loaded one cannot be moved, and -EADDRINUSE if a guest in @net already uses
+ * the device's CID.
+ */
+static int vsock_g2h_net_assign(struct net *net, struct sk_buff *msg,
+ struct genl_info *info)
+{
+ struct netlink_ext_ack *extack = info->extack;
+ netns_tracker old_tracker;
+ struct net *old;
+ void *hdr;
+ int ret;
+
+ mutex_lock(&vsock_register_mutex);
+
+ ret = -ENODEV;
+ if (!transport_g2h) {
+ NL_SET_ERR_MSG(extack, "no guest-to-host transport is loaded");
+ goto unlock;
+ }
+
+ ret = -EOPNOTSUPP;
+ if (!vsock_net_assignable(transport_g2h)) {
+ NL_SET_ERR_MSG(extack,
+ "the loaded guest-to-host transport does not support namespace assignment");
+ goto unlock;
+ }
+
+ if (transport_h2g && !transport_h2g->has_remote_cid) {
+ NL_SET_ERR_MSG(extack,
+ "the loaded host-to-guest transport cannot check the device's CID");
+ goto unlock;
+ }
+
+ ret = -EADDRINUSE;
+ if (transport_h2g &&
+ transport_h2g->has_remote_cid(net,
+ transport_g2h->get_local_cid())) {
+ NL_SET_ERR_MSG(extack,
+ "a guest in the target namespace already uses the device's CID");
+ goto unlock;
+ }
+
+ ret = -EMSGSIZE;
+ hdr = genlmsg_iput(msg, info);
+ if (!hdr)
+ goto unlock;
+ genlmsg_end(msg, hdr);
+
+ ret = genlmsg_reply(msg, info);
+ msg = NULL;
+ if (ret)
+ goto unlock;
+
+ old = vsock_g2h_net_swap(net, &old_tracker);
+
+ /* See vsock_maybe_set_connected() comment about synchronizing with
+ * connecting sockets.
+ */
+ vsock_g2h_reset_unreachable(transport_g2h);
+
+ mutex_unlock(&vsock_register_mutex);
+
+ vsock_g2h_net_release(old, &old_tracker);
+
+ return 0;
+
+unlock:
+ mutex_unlock(&vsock_register_mutex);
+ nlmsg_free(msg);
+
+ return ret;
+}
+
static struct sock *vsock_dequeue_accept(struct sock *listener)
{
struct vsock_sock *vlistener;
@@ -910,7 +1250,8 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
* like AF_INET prevents binding to a non-local IP address (in most
* cases), we only allow binding to a local CID.
*/
- if (addr->svm_cid != VMADDR_CID_ANY && !vsock_find_cid(addr->svm_cid))
+ if (addr->svm_cid != VMADDR_CID_ANY &&
+ !vsock_find_cid(sock_net(sk_vsock(vsk)), addr->svm_cid))
return -EADDRNOTAVAIL;
switch (sk->sk_socket->type) {
@@ -2797,6 +3138,7 @@ static long vsock_dev_do_ioctl(struct file *filp,
{
u32 __user *p = ptr;
int retval = 0;
+ struct net *net;
u32 cid;
switch (cmd) {
@@ -2804,11 +3146,14 @@ static long vsock_dev_do_ioctl(struct file *filp,
/* To be compatible with the VMCI behavior, we prioritize the
* guest CID instead of well-know host CID (VMADDR_CID_HOST).
*/
- cid = vsock_registered_transport_cid(&transport_g2h);
+ net = current->nsproxy->net_ns;
+ cid = vsock_registered_transport_cid(&transport_g2h, net);
if (cid == VMADDR_CID_ANY)
- cid = vsock_registered_transport_cid(&transport_h2g);
+ cid = vsock_registered_transport_cid(&transport_h2g,
+ net);
if (cid == VMADDR_CID_ANY)
- cid = vsock_registered_transport_cid(&transport_local);
+ cid = vsock_registered_transport_cid(&transport_local,
+ net);
if (put_user(cid, p) != 0)
retval = -EFAULT;
@@ -2849,6 +3194,44 @@ static struct miscdevice vsock_device = {
.fops = &vsock_device_ops,
};
+int vsock_nl_dev_netns_get_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct sk_buff *msg;
+ int err;
+
+ msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ mutex_lock(&vsock_register_mutex);
+ if (transport_g2h) {
+ err = vsock_nl_dev_netns_fill(msg, info);
+ } else {
+ NL_SET_ERR_MSG(info->extack,
+ "no guest-to-host transport is loaded");
+ err = -ENODEV;
+ }
+ mutex_unlock(&vsock_register_mutex);
+
+ if (err) {
+ nlmsg_free(msg);
+ return err;
+ }
+
+ return genlmsg_reply(msg, info);
+}
+
+int vsock_nl_dev_netns_set_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct sk_buff *msg;
+
+ msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ return vsock_g2h_net_assign(genl_info_net(info), msg, info);
+}
+
static int __vsock_net_mode_string(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos,
enum vsock_net_mode mode,
@@ -3064,6 +3447,8 @@ static int __init vsock_init(void)
vsock_init_tables();
+ get_net_track(&init_net, &vsock_g2h_net_tracker, GFP_KERNEL);
+
vsock_proto.owner = THIS_MODULE;
vsock_device.minor = MISC_DYNAMIC_MINOR;
err = misc_register(&vsock_device);
@@ -3090,10 +3475,18 @@ static int __init vsock_init(void)
goto err_unregister_sock;
}
+ err = genl_register_family(&vsock_nl_family);
+ if (err) {
+ pr_err("Cannot register vsock netlink family: %d\n", err);
+ goto err_unregister_pernet;
+ }
+
vsock_bpf_build_proto();
return 0;
+err_unregister_pernet:
+ unregister_pernet_subsys(&vsock_pernet_ops);
err_unregister_sock:
sock_unregister(AF_VSOCK);
err_unregister_proto:
@@ -3101,15 +3494,20 @@ static int __init vsock_init(void)
err_deregister_misc:
misc_deregister(&vsock_device);
err_reset_transport:
+ put_net_track(&init_net, &vsock_g2h_net_tracker);
return err;
}
static void __exit vsock_exit(void)
{
+ genl_unregister_family(&vsock_nl_family);
misc_deregister(&vsock_device);
sock_unregister(AF_VSOCK);
proto_unregister(&vsock_proto);
unregister_pernet_subsys(&vsock_pernet_ops);
+
+ put_net_track(rcu_dereference_protected(vsock_g2h_net, true),
+ &vsock_g2h_net_tracker);
}
const struct vsock_transport *vsock_core_get_transport(struct vsock_sock *vsk)
@@ -3176,13 +3574,18 @@ EXPORT_SYMBOL_GPL(vsock_core_register);
void vsock_core_unregister(const struct vsock_transport *t)
{
+ netns_tracker old_tracker;
+ struct net *old = NULL;
+
mutex_lock(&vsock_register_mutex);
if (transport_h2g == t)
transport_h2g = NULL;
- if (transport_g2h == t)
+ if (transport_g2h == t) {
transport_g2h = NULL;
+ old = vsock_g2h_net_swap(&init_net, &old_tracker);
+ }
if (transport_dgram == t)
transport_dgram = NULL;
@@ -3191,6 +3594,10 @@ void vsock_core_unregister(const struct vsock_transport *t)
transport_local = NULL;
mutex_unlock(&vsock_register_mutex);
+
+ /* The device is gone, so release the namespace it was holding. */
+ if (old)
+ vsock_g2h_net_release(old, &old_tracker);
}
EXPORT_SYMBOL_GPL(vsock_core_unregister);
diff --git a/net/vmw_vsock/vsock_nl_gen.c b/net/vmw_vsock/vsock_nl_gen.c
new file mode 100644
index 000000000000..81db4c61d7bb
--- /dev/null
+++ b/net/vmw_vsock/vsock_nl_gen.c
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/vsock.yaml */
+/* YNL-GEN kernel source */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "vsock_nl_gen.h"
+
+#include <uapi/linux/vsock.h>
+
+/* Ops table for vsock */
+static const struct genl_split_ops vsock_nl_ops[] = {
+ {
+ .cmd = VSOCK_CMD_DEV_NETNS_SET,
+ .doit = vsock_nl_dev_netns_set_doit,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = VSOCK_CMD_DEV_NETNS_GET,
+ .doit = vsock_nl_dev_netns_get_doit,
+ .flags = GENL_CMD_CAP_DO,
+ },
+};
+
+struct genl_family vsock_nl_family __ro_after_init = {
+ .name = VSOCK_FAMILY_NAME,
+ .version = VSOCK_FAMILY_VERSION,
+ .netnsok = true,
+ .parallel_ops = true,
+ .module = THIS_MODULE,
+ .split_ops = vsock_nl_ops,
+ .n_split_ops = ARRAY_SIZE(vsock_nl_ops),
+};
diff --git a/net/vmw_vsock/vsock_nl_gen.h b/net/vmw_vsock/vsock_nl_gen.h
new file mode 100644
index 000000000000..c041195584a0
--- /dev/null
+++ b/net/vmw_vsock/vsock_nl_gen.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/vsock.yaml */
+/* YNL-GEN kernel header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _LINUX_VSOCK_GEN_H
+#define _LINUX_VSOCK_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/vsock.h>
+
+int vsock_nl_dev_netns_set_doit(struct sk_buff *skb, struct genl_info *info);
+int vsock_nl_dev_netns_get_doit(struct sk_buff *skb, struct genl_info *info);
+
+extern struct genl_family vsock_nl_family;
+
+#endif /* _LINUX_VSOCK_GEN_H */
diff --git a/tools/net/ynl/Makefile.deps b/tools/net/ynl/Makefile.deps
index 1e746e25e2bc..fa51108c43d4 100644
--- a/tools/net/ynl/Makefile.deps
+++ b/tools/net/ynl/Makefile.deps
@@ -55,4 +55,5 @@ CFLAGS_tc:= $(call get_hdr_inc,__LINUX_RTNETLINK_H,rtnetlink.h) \
$(call get_hdr_inc,_TC_SKBEDIT_H,tc_act/tc_skbedit.h) \
$(call get_hdr_inc,_TC_TUNNEL_KEY_H,tc_act/tc_tunnel_key.h)
CFLAGS_tcp_metrics:=$(call get_hdr_inc,_LINUX_TCP_METRICS_H,tcp_metrics.h)
+CFLAGS_vsock:=$(call get_hdr_inc,_LINUX_VSOCK_H,vsock.h)
CFLAGS_wireguard:=$(call get_hdr_inc2,_LINUX_WIREGUARD_H,_WG_UAPI_WIREGUARD_H,wireguard.h)
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v3 5/7] vsock/virtio: support guest device network namespace
2026-10-12 22:48 [PATCH net-next v3 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (3 preceding siblings ...)
2026-10-12 22:48 ` [PATCH net-next v3 4/7] vsock: add a netlink command to assign the g2h device to a netns Bobby Eshleman
@ 2026-10-12 22:48 ` Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 6/7] selftests/vsock: test the guest vsock " Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 7/7] selftests/vsock: test the netns assign privilege checks Bobby Eshleman
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-10-12 22:48 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
virtio-vsock did not have namespace support (the device was always
accessible to any global namespace).
Make the virtio-vsock device assignable to a namespace and initialize it
to init_net. Because virtio-vsock and init_net are both hardcoded to
global mode, nothing changes until the assign command is issued.
An assign can land while virtio_transport_rx_work() is draining a
batch, so the assigned namespace is re-read per packet rather than once
per batch. Matching the rest of the batch against the namespace the
device has left would reset connections that belong to the new one.
When a device is assigned to a namespace, every already established
vsock socket that is no longer able to reach the device is forcibly
closed. For that reason, adding new sockets to the connected table must
be performed atomically with regards to namespace assignment. This
ensures that when the socket is added to the connected table that it
actually passes the new reachability conditions set by ns assignment. If
it wins the race to the table and does NOT pass the reachability tests,
then it will be reset. This is the purpose of the new helper
'vsock_maybe_set_connected()'.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v3:
- Re-read the assigned netns per packet in virtio_transport_rx_work()
rather than once per batch, so a mid-batch assign cannot reset the new
namespace's connections (Sashiko)
- Drop the NULL netns reset in virtio_transport_rx_work(), the assigned
netns is now pinned
- Drop the .reset op and the virtio_transport_reset{,_no_sock}() exports,
the sweep no longer sends a RST (Sashiko)
Changes in v2:
- Export virtio_transport_reset(), wire it to the new .reset op (Stefano)
- netns_assign_allow is now a bool (Stefano)
- Pass NULL, not &init_net, in virtio_transport_rx_work(), and
comment why (Stefano)
- Drop the if (net) guard around put_net() (Stefano)
- Drop the comments at the vsock_maybe_set_connected() call sites,
the commit msg seems sufficient
---
net/vmw_vsock/virtio_transport.c | 23 ++++++++++++++++++-----
net/vmw_vsock/virtio_transport_common.c | 18 +++++++++++++-----
2 files changed, 31 insertions(+), 10 deletions(-)
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index eced6929405d..4123924536dd 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -542,7 +542,7 @@ static bool virtio_transport_msgzerocopy_allow(void)
bool virtio_transport_stream_allow(struct vsock_sock *vsk, u32 cid, u32 port)
{
- return vsock_net_mode_global(vsk);
+ return vsock_g2h_net_reachable(sock_net(sk_vsock(vsk)));
}
static bool virtio_transport_seqpacket_allow(struct vsock_sock *vsk,
@@ -587,6 +587,7 @@ static struct virtio_transport virtio_transport = {
.seqpacket_has_data = virtio_transport_seqpacket_has_data,
.msgzerocopy_allow = virtio_transport_msgzerocopy_allow,
+ .netns_assign_allow = true,
.notify_poll_in = virtio_transport_notify_poll_in,
.notify_poll_out = virtio_transport_notify_poll_out,
@@ -616,7 +617,7 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)
struct virtio_vsock *vsock;
bool seqpacket_allow;
- if (!vsock_net_mode_global(vsk))
+ if (!vsock_g2h_net_reachable(sock_net(sk_vsock(vsk))))
return false;
seqpacket_allow = false;
@@ -633,7 +634,12 @@ static void virtio_transport_rx_work(struct work_struct *work)
{
struct virtio_vsock *vsock =
container_of(work, struct virtio_vsock, rx_work);
+ struct virtio_transport *t = &virtio_transport;
+ netns_tracker ns_tracker;
struct virtqueue *vq;
+ struct net *net;
+
+ net = vsock_g2h_net_get(&ns_tracker);
mutex_lock(&vsock->rx_lock);
@@ -682,10 +688,15 @@ static void virtio_transport_rx_work(struct work_struct *work)
virtio_transport_deliver_tap_pkt(skb);
- /* Force virtio-transport into global mode since it
- * does not yet support local-mode namespacing.
+ /* Only grab a reference if the net changes underneath
+ * us.
*/
- virtio_transport_recv_pkt(&virtio_transport, skb, NULL);
+ if (unlikely(!vsock_g2h_net_eq(net))) {
+ put_net_track(net, &ns_tracker);
+ net = vsock_g2h_net_get(&ns_tracker);
+ }
+
+ virtio_transport_recv_pkt(t, skb, net);
}
} while (!virtqueue_enable_cb(vq));
@@ -694,6 +705,8 @@ static void virtio_transport_rx_work(struct work_struct *work)
virtio_vsock_rx_fill(vsock);
out_nofill:
mutex_unlock(&vsock->rx_lock);
+
+ put_net_track(net, &ns_tracker);
}
static int virtio_vsock_vqs_init(struct virtio_vsock *vsock)
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index f225f53ed4ba..0234379c14a1 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1478,9 +1478,13 @@ virtio_transport_recv_connecting(struct sock *sk,
switch (le16_to_cpu(hdr->op)) {
case VIRTIO_VSOCK_OP_RESPONSE:
- sk->sk_state = TCP_ESTABLISHED;
+ if (!vsock_maybe_set_connected(vsk)) {
+ skerr = ECONNRESET;
+ err = -ENETUNREACH;
+ goto destroy;
+ }
+
sk->sk_socket->state = SS_CONNECTED;
- vsock_insert_connected(vsk);
sk->sk_state_change(sk);
break;
case VIRTIO_VSOCK_OP_INVALID:
@@ -1736,8 +1740,6 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
lock_sock_nested(child, SINGLE_DEPTH_NESTING);
- child->sk_state = TCP_ESTABLISHED;
-
vchild = vsock_sk(child);
vsock_addr_init(&vchild->local_addr, le64_to_cpu(hdr->dst_cid),
le32_to_cpu(hdr->dst_port));
@@ -1758,7 +1760,13 @@ virtio_transport_recv_listen(struct sock *sk, struct sk_buff *skb,
if (virtio_transport_space_update(child, skb))
child->sk_write_space(child);
- vsock_insert_connected(vchild);
+ if (!vsock_maybe_set_connected(vchild)) {
+ release_sock(child);
+ virtio_transport_reset_no_sock(t, skb, sock_net(sk));
+ sock_put(child);
+ return -ENETUNREACH;
+ }
+
vsock_enqueue_accept(sk, child);
virtio_transport_send_response(vchild, skb);
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v3 6/7] selftests/vsock: test the guest vsock device network namespace
2026-10-12 22:48 [PATCH net-next v3 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (4 preceding siblings ...)
2026-10-12 22:48 ` [PATCH net-next v3 5/7] vsock/virtio: support guest device network namespace Bobby Eshleman
@ 2026-10-12 22:48 ` Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 7/7] selftests/vsock: test the netns assign privilege checks Bobby Eshleman
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-10-12 22:48 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
Add tests for guest vsock device namespace assignment, including ns
destruction and the device moving between namespaces.
The tests drive the device through ynl's cli.py and create guest
namespaces with unshare, so they need python3, PyYAML and unshare. A
missing one skips only these tests, not the whole script.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v3:
- Replace reset_on_ns_delete_ok with pins_ns_ok, to cover the netns
pinning
- Skip only the ns_guest_ tests when PyYAML or the ynl CLI or spec is
missing, and ship the ynl bits via TEST_FILES for installed trees
(Sashiko)
- Also skip only the ns_guest_ tests when unshare or python3 is missing
- Make vm_ns_start() wait for the namespace and report failure (Sashiko)
- Poll for FIRST in old_conn_send_fails instead of sleeping, and report
an over-long assign as a skip (Sashiko)
- Kill the guest-side socat by name, $! names a local subshell (Sashiko)
- Wait for the host listener to exit before the next test binds its
port (Sashiko)
Changes in v2:
- Drive the assign with ynl cli.py, drop the vsock_assign_g2h_netns
ioctl helper and its patch
- Terminate the guest socat sender too, it sometimes outlived the test
and kept holding on to a port the next test binds
---
tools/testing/selftests/vsock/Makefile | 5 +
tools/testing/selftests/vsock/vmtest.sh | 502 +++++++++++++++++++++++++++++++-
2 files changed, 501 insertions(+), 6 deletions(-)
diff --git a/tools/testing/selftests/vsock/Makefile b/tools/testing/selftests/vsock/Makefile
index c407c0afd938..2b066ba91bcd 100644
--- a/tools/testing/selftests/vsock/Makefile
+++ b/tools/testing/selftests/vsock/Makefile
@@ -13,5 +13,10 @@ $(VSOCK_TEST_DIR)/vsock_test: $(VSOCK_TEST_SRCS)
TEST_PROGS += vmtest.sh
TEST_GEN_FILES := vsock_test
+TEST_FILES := \
+ ../../../net/ynl/pyynl/cli.py \
+ ../../../net/ynl/pyynl/lib \
+ ../../../../Documentation/netlink/specs/vsock.yaml
+
include ../lib.mk
diff --git a/tools/testing/selftests/vsock/vmtest.sh b/tools/testing/selftests/vsock/vmtest.sh
index 310dfc2a39ad..7693d5e0a1d0 100755
--- a/tools/testing/selftests/vsock/vmtest.sh
+++ b/tools/testing/selftests/vsock/vmtest.sh
@@ -17,6 +17,20 @@ readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
readonly VSOCK_TEST="${SCRIPT_DIR}"/vsock_test
+
+# "make install" copies the ynl CLI, its lib package and the spec next to this
+# script (see TEST_FILES); in a source tree they stay where they are built.
+if [[ -r "${SCRIPT_DIR}"/cli.py ]]; then
+ readonly YNL_CLI="${SCRIPT_DIR}"/cli.py
+ readonly YNL_LIB="${SCRIPT_DIR}"/lib
+ readonly VSOCK_SPEC="${SCRIPT_DIR}"/vsock.yaml
+else
+ readonly YNL_DIR="${KERNEL_CHECKOUT}"/tools/net/ynl/pyynl
+ readonly YNL_CLI="${YNL_DIR}"/cli.py
+ readonly YNL_LIB="${YNL_DIR}"/lib
+ readonly SPEC_DIR="${KERNEL_CHECKOUT}"/Documentation/netlink/specs
+ readonly VSOCK_SPEC="${SPEC_DIR}"/vsock.yaml
+fi
readonly TEST_GUEST_PORT=51000
readonly TEST_HOST_PORT=50000
readonly TEST_HOST_PORT_LISTENER=50001
@@ -73,6 +87,12 @@ readonly TEST_NAMES=(
ns_delete_vm_ok
ns_delete_host_ok
ns_delete_both_ok
+ ns_guest_local_connect_to_host_fails
+ ns_guest_assign_g2h_netns_connect_to_host_ok
+ ns_guest_assign_g2h_netns_init_ns_connect_fails
+ ns_guest_assign_g2h_netns_host_connect_ok
+ ns_guest_assign_g2h_netns_pins_ns_ok
+ ns_guest_assign_g2h_netns_old_conn_send_fails
)
readonly TEST_DESCS=(
# vm_server_host_client
@@ -149,12 +169,36 @@ readonly TEST_DESCS=(
# ns_delete_both_ok
"Check that deleting the VM and host's namespaces does not break the socket connection"
+
+ # ns_guest_local_connect_to_host_fails
+ "Check a guest process in a local ns cannot reach the host without the netns assign."
+
+ # ns_guest_assign_g2h_netns_connect_to_host_ok
+ "Check a guest process in a local ns reaches the host once the vsock device is assigned to it."
+
+ # ns_guest_assign_g2h_netns_init_ns_connect_fails
+ "Check the guest's initial ns loses vsock once the device is assigned to another ns."
+
+ # ns_guest_assign_g2h_netns_host_connect_ok
+ "Check the host reaches a guest listener in the ns the vsock device is assigned to."
+
+ # ns_guest_assign_g2h_netns_pins_ns_ok
+ "Check the assignment keeps the ns alive when deleted, and that reassigning releases it."
+
+ # ns_guest_assign_g2h_netns_old_conn_send_fails
+ "Check connections made before the assign stop sending once they lose the device."
)
readonly USE_SHARED_VM=(
vm_server_host_client
vm_client_host_server
vm_loopback
+ ns_guest_local_connect_to_host_fails
+ ns_guest_assign_g2h_netns_connect_to_host_ok
+ ns_guest_assign_g2h_netns_init_ns_connect_fails
+ ns_guest_assign_g2h_netns_host_connect_ok
+ ns_guest_assign_g2h_netns_pins_ns_ok
+ ns_guest_assign_g2h_netns_old_conn_send_fails
)
readonly NS_MODES=("local" "global")
@@ -316,6 +360,34 @@ check_deps() {
fi
}
+check_ns_guest_deps() {
+ local tname=$1
+ local dep
+
+ if [[ ! "${tname}" =~ ^ns_guest_ ]]; then
+ return 0
+ fi
+
+ for dep in unshare python3; do
+ if [[ ! -x $(command -v "${dep}") ]]; then
+ log_host "no ${dep} for test ${tname}"
+ return 1
+ fi
+ done
+
+ if ! python3 -c "import yaml" &>/dev/null; then
+ log_host "no python3 yaml module for test ${tname}"
+ return 1
+ fi
+
+ if [[ ! -r "${YNL_CLI}" ]] || [[ ! -r "${VSOCK_SPEC}" ]]; then
+ log_host "no ynl cli or vsock spec for test ${tname}"
+ return 1
+ fi
+
+ return 0
+}
+
check_netns() {
local tname=$1
@@ -401,6 +473,19 @@ setup_home() {
mkdir -p "$(dirname "${SSH_KEY_PATH}")"
ssh-keygen -t ed25519 -f "${SSH_KEY_PATH}" -N "" -q
cp "${VSOCK_TEST}" "${TEST_HOME}"/vsock_test
+
+ mkdir -p "${TEST_HOME}"/ynl
+ if [[ -r "${YNL_CLI}" ]]; then
+ cp "${YNL_CLI}" "${TEST_HOME}"/ynl/
+ cp -r "${YNL_LIB}" "${TEST_HOME}"/ynl/
+ cp "${VSOCK_SPEC}" "${TEST_HOME}"/ynl/
+ fi
+
+ # One of the tests runs the CLI as an unprivileged user, so the CLI
+ # has to be reachable by one.
+ chmod -R a+rX "${TEST_HOME}"/ynl
+
+ chmod 755 "${TEST_HOME}"
}
create_pidfile() {
@@ -528,6 +613,82 @@ vm_wait_for_ssh() {
done
}
+# Create a local mode namespace in the VM and echo the pid holding it open.
+vm_ns_start() {
+ local ns=$1
+ local initns
+ local nspid
+ local newns
+ local i
+
+ local mode=/proc/sys/net/vsock/child_ns_mode
+
+ if ! vm_ssh "${ns}" -- "echo local > ${mode}" &>/dev/null; then
+ return
+ fi
+
+ nspid=$(vm_ssh "${ns}" -- "unshare -n sleep infinity" \
+ '>/dev/null 2>&1 & echo $!')
+ if [[ -z "${nspid}" ]]; then
+ return
+ fi
+
+ initns=$(vm_ssh "${ns}" -- "readlink /proc/self/ns/net")
+ for ((i = 0; i < WAIT_PERIOD_MAX; i++)); do
+ newns=$(vm_ssh "${ns}" -- "readlink /proc/${nspid}/ns/net" \
+ 2>/dev/null)
+ if [[ -n "${newns}" ]] && [[ "${newns}" != "${initns}" ]]; then
+ echo "${nspid}"
+ return
+ fi
+ sleep 1
+ done
+
+ vm_ns_stop "${ns}" "${nspid}"
+}
+
+# Returns once the holder is gone, so that the namespace is unreferenced and
+# the kernel can start tearing it down.
+vm_ns_stop() {
+ local ns=$1
+ local nspid=$2
+
+ vm_ssh "${ns}" <<-EOF &>/dev/null
+ kill ${nspid}
+ for ((i = 0; i < ${WAIT_PERIOD_MAX}; i++)); do
+ kill -0 ${nspid} 2>/dev/null || break
+ sleep 1
+ done
+ EOF
+}
+
+# Runs in the guest's initial namespace when <nspid> is empty. The command must
+# not contain single quotes.
+vm_ns_exec() {
+ local ns=$1
+ local nspid=$2
+ local cmd=$3
+
+ if [[ -z "${nspid}" ]]; then
+ vm_ssh "${ns}" -- "${cmd}"
+ return
+ fi
+
+ vm_ssh "${ns}" -- nsenter -t "${nspid}" -n sh -c "'${cmd}'"
+}
+
+vm_ns_assign_g2h() {
+ local ns=$1
+ local nspid=$2
+
+ vm_ns_exec "${ns}" "${nspid}" "python3 /root/ynl/cli.py --no-schema \
+ --spec /root/ynl/vsock.yaml --do dev-netns-set"
+}
+
+vm_reset_g2h() {
+ vm_ns_assign_g2h "init_ns" "" &>/dev/null
+}
+
# derived from selftests/net/net_helper.sh
wait_for_listener()
{
@@ -564,17 +725,33 @@ wait_for_listener()
done
}
-vm_wait_for_listener() {
+# Runs in the guest's initial namespace when <nspid> is empty.
+vm_ns_wait_for_listener() {
local ns=$1
- local port=$2
- local protocol=$3
+ local nspid=$2
+ local port=$3
+ local protocol=$4
+ local nsenter=
+ local args
+
+ [[ -n "${nspid}" ]] && nsenter="nsenter -t ${nspid} -n"
+ args="${port} ${WAIT_PERIOD} ${WAIT_PERIOD_MAX} ${protocol}"
vm_ssh "${ns}" <<EOF
$(declare -f wait_for_listener)
-wait_for_listener ${port} ${WAIT_PERIOD} ${WAIT_PERIOD_MAX} ${protocol}
+export -f wait_for_listener
+${nsenter} bash -c "wait_for_listener ${args}"
EOF
}
+vm_wait_for_listener() {
+ local ns=$1
+ local port=$2
+ local protocol=$3
+
+ vm_ns_wait_for_listener "${ns}" "" "${port}" "${protocol}"
+}
+
host_wait_for_listener() {
local ns=$1
local port=$2
@@ -1421,6 +1598,317 @@ test_ns_delete_both_ok() {
check_ns_delete_doesnt_break_connection "both"
}
+# Send a string from the guest to a host listener and leave what the host
+# received in <outfile>.
+guest_send_to_host() {
+ local ns=$1
+ local nspid=$2
+ local port=$3
+ local outfile=$4
+ local cmd="echo TEST | socat -u STDIN VSOCK-CONNECT:2:${port}"
+ local pid
+
+ socat -u VSOCK-LISTEN:"${port}" STDOUT > "${outfile}" 2>/dev/null &
+ pid=$!
+ host_wait_for_listener "${ns}" "${port}" "vsock"
+
+ vm_ns_exec "${ns}" "${nspid}" "${cmd}" 2>/dev/null
+
+ timeout "${WAIT_PERIOD}" \
+ bash -c 'while [[ ! -s '"${outfile}"' ]]; do sleep 1; done'
+
+ # Wait for the listener to actually go, or the next test binding the
+ # same port races it and silently reads nothing.
+ terminate_pids "${pid}"
+ wait "${pid}" 2>/dev/null
+}
+
+# Send a string from the host to a listener in the guest and leave what the
+# guest received in <outfile>.
+host_send_to_guest() {
+ local ns=$1
+ local nspid=$2
+ local port=$3
+ local outfile=$4
+ local cmd="socat -u VSOCK-LISTEN:${port} STDOUT"
+ local dst="VSOCK-CONNECT:${VSOCK_CID}:${port}"
+ local pid
+
+ vm_ns_exec "${ns}" "${nspid}" "${cmd}" > "${outfile}" 2>/dev/null &
+ pid=$!
+ vm_ns_wait_for_listener "${ns}" "${nspid}" "${port}" "vsock"
+
+ echo TEST | socat -u STDIN "${dst}" 2>/dev/null
+
+ timeout "${WAIT_PERIOD}" \
+ bash -c 'while [[ ! -s '"${outfile}"' ]]; do sleep 1; done'
+
+ vm_ns_exec "${ns}" "${nspid}" \
+ "pkill -f VSOCK-LISTEN:${port}" &>/dev/null
+ terminate_pids "${pid}"
+ wait "${pid}" 2>/dev/null
+}
+
+ns_old_conn_cleanup() {
+ local port=$1
+ local pid=$2
+ local sender=$3
+ local outfile=$4
+ local nspid=$5
+
+ vm_ns_exec "init_ns" "" "pkill -f VSOCK-CONNECT:2:${port}" &>/dev/null
+ terminate_pids "${pid}" "${sender}"
+ wait "${pid}" "${sender}" 2>/dev/null
+ rm -f "${outfile}"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+}
+
+test_ns_guest_assign_g2h_netns_old_conn_send_fails() {
+ local gap=$(( WAIT_PERIOD * 3 ))
+ local port=12346
+ local elapsed
+ local started
+ local outfile
+ local result
+ local sender
+ local nspid
+ local pid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ outfile=$(mktemp)
+ socat -u VSOCK-LISTEN:"${port}" STDOUT > "${outfile}" 2>/dev/null &
+ pid=$!
+ host_wait_for_listener "init_ns" "${port}" "vsock"
+
+ # Send a message, wait, then send another. While waiting, assign the
+ # device to a namespace. Confirm the second message does not arrive.
+ vm_ssh "init_ns" -- \
+ "(echo FIRST; sleep ${gap}; echo SECOND) |" \
+ "socat -u STDIN VSOCK-CONNECT:2:${port}" &>/dev/null &
+ sender=$!
+
+ timeout "$(( WAIT_PERIOD * WAIT_PERIOD_MAX ))" \
+ bash -c 'while [[ ! -s '"${outfile}"' ]]; do sleep 1; done'
+
+ started=${SECONDS}
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ ns_old_conn_cleanup "${port}" "${pid}" "${sender}" \
+ "${outfile}" "${nspid}"
+ return "${KSFT_FAIL}"
+ fi
+ elapsed=$(( SECONDS - started ))
+
+ sleep $(( gap + WAIT_PERIOD ))
+
+ result=$(cat "${outfile}")
+ ns_old_conn_cleanup "${port}" "${pid}" "${sender}" \
+ "${outfile}" "${nspid}"
+
+ if [[ "${result}" != *FIRST* ]]; then
+ log_host "no connection before the assign: [${result}]"
+ return "${KSFT_FAIL}"
+ fi
+
+ if [[ ${elapsed} -ge ${gap} ]]; then
+ log_host "assign took ${elapsed}s, longer than the ${gap}s gap"
+ return "${KSFT_SKIP}"
+ fi
+
+ if [[ "${result}" == *SECOND* ]]; then
+ log_host "old connection still delivered after the assign"
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_local_connect_to_host_fails() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ outfile=$(mktemp)
+ guest_send_to_host "init_ns" "${nspid}" "${port}" "${outfile}"
+
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ if [[ "${result}" == TEST ]]; then
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_connect_to_host_ok() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ outfile=$(mktemp)
+ guest_send_to_host "init_ns" "${nspid}" "${port}" "${outfile}"
+
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ if [[ "${result}" != TEST ]]; then
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_init_ns_connect_fails() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ outfile=$(mktemp)
+ guest_send_to_host "init_ns" "" "${port}" "${outfile}"
+
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ if [[ "${result}" == TEST ]]; then
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_host_connect_ok() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ outfile=$(mktemp)
+ host_send_to_guest "init_ns" "${nspid}" "${port}" "${outfile}"
+
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ if [[ "${result}" != TEST ]]; then
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_pins_ns_ok() {
+ local port=12345
+ local outfile
+ local result
+ local nspid
+
+ nspid=$(vm_ns_start "init_ns")
+ if [[ -z "${nspid}" ]]; then
+ log_host "failed to create a namespace inside the guest"
+ return "${KSFT_FAIL}"
+ fi
+
+ if ! vm_ns_assign_g2h "init_ns" "${nspid}"; then
+ log_host "failed to assign the vsock device to the guest ns"
+ vm_ns_stop "init_ns" "${nspid}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ vm_ns_stop "init_ns" "${nspid}"
+ sleep "${WAIT_PERIOD}"
+
+ outfile=$(mktemp)
+ guest_send_to_host "init_ns" "" "${port}" "${outfile}"
+ result=$(cat "${outfile}")
+ if [[ "${result}" == TEST ]]; then
+ log_host "device returned to the init ns instead of pinning it"
+ rm -f "${outfile}"
+ vm_reset_g2h
+ return "${KSFT_FAIL}"
+ fi
+
+ # Assigning it back is what releases the deleted namespace.
+ vm_reset_g2h
+
+ guest_send_to_host "init_ns" "" "$(( port + 1 ))" "${outfile}"
+ result=$(cat "${outfile}")
+ rm -f "${outfile}"
+
+ if [[ "${result}" != TEST ]]; then
+ log_host "device did not come back on reassign: [${result}]"
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
shared_vm_test() {
local tname
@@ -1453,7 +1941,8 @@ run_shared_vm_tests() {
continue
fi
- if ! check_netns "${arg}"; then
+ if ! check_netns "${arg}" ||
+ ! check_ns_guest_deps "${arg}"; then
check_result "${KSFT_SKIP}" "${arg}"
continue
fi
@@ -1517,7 +2006,8 @@ run_ns_tests() {
continue
fi
- if ! check_netns "${arg}"; then
+ if ! check_netns "${arg}" ||
+ ! check_ns_guest_deps "${arg}"; then
check_result "${KSFT_SKIP}" "${arg}"
continue
fi
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v3 7/7] selftests/vsock: test the netns assign privilege checks
2026-10-12 22:48 [PATCH net-next v3 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
` (5 preceding siblings ...)
2026-10-12 22:48 ` [PATCH net-next v3 6/7] selftests/vsock: test the guest vsock " Bobby Eshleman
@ 2026-10-12 22:48 ` Bobby Eshleman
6 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-10-12 22:48 UTC (permalink / raw)
To: Stefano Garzarella, David S. Miller, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Stefan Hajnoczi,
Michael S. Tsirkin, Jason Wang, Xuan Zhuo, Eugenio Pérez,
Shuah Khan, Randy Dunlap, Donald Hunter, Eric Dumazet,
Eric Dumazet
Cc: virtualization, netdev, linux-kernel, linux-doc, kvm,
linux-kselftest, sargun, jlinbox, Bobby Eshleman, Bobby Eshleman
VSOCK_CMD_DEV_NETNS_SET refuses callers without CAP_NET_ADMIN in the
init user namespace.
Add two tests: one confirms that CAP_NET_ADMIN is required even by a
privileged user and the other confirms that CAP_NET_ADMIN in an
unprivileged user ns alone is insufficient.
CONFIG_USER_NS is needed to test the CAP_NET_ADMIN + unprivileged user
ns case.
Signed-off-by: Bobby Eshleman <bobbyeshleman@meta.com>
---
Changes in v3:
- Require setpriv only for the ns_guest_ tests, not the whole script
Changes in v2:
- Assert only that the command fails, not EPERM: ynl's cli.py does not
report the errno (Sashiko)
- Use ynl cli.py for the assign
---
tools/testing/selftests/vsock/config | 1 +
tools/testing/selftests/vsock/vmtest.sh | 54 ++++++++++++++++++++++++++++++++-
2 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/vsock/config b/tools/testing/selftests/vsock/config
index 5f0a4f17dfc9..4b31085558fa 100644
--- a/tools/testing/selftests/vsock/config
+++ b/tools/testing/selftests/vsock/config
@@ -109,3 +109,4 @@ CONFIG_FS_DAX=y
CONFIG_MEMORY_HOTPLUG=y
CONFIG_MEMORY_HOTREMOVE=y
CONFIG_ZONE_DEVICE=y
+CONFIG_USER_NS=y
diff --git a/tools/testing/selftests/vsock/vmtest.sh b/tools/testing/selftests/vsock/vmtest.sh
index 7693d5e0a1d0..d5d7b0660c5d 100755
--- a/tools/testing/selftests/vsock/vmtest.sh
+++ b/tools/testing/selftests/vsock/vmtest.sh
@@ -93,6 +93,8 @@ readonly TEST_NAMES=(
ns_guest_assign_g2h_netns_host_connect_ok
ns_guest_assign_g2h_netns_pins_ns_ok
ns_guest_assign_g2h_netns_old_conn_send_fails
+ ns_guest_assign_g2h_netns_no_cap_net_admin_fails
+ ns_guest_assign_g2h_netns_unpriv_user_ns_fails
)
readonly TEST_DESCS=(
# vm_server_host_client
@@ -187,6 +189,12 @@ readonly TEST_DESCS=(
# ns_guest_assign_g2h_netns_old_conn_send_fails
"Check connections made before the assign stop sending once they lose the device."
+
+ # ns_guest_assign_g2h_netns_no_cap_net_admin_fails
+ "Check assigning the guest's vsock device to a namespace needs CAP_NET_ADMIN."
+
+ # ns_guest_assign_g2h_netns_unpriv_user_ns_fails
+ "Check an unprivileged user cannot claim the guest's vsock device via a user ns."
)
readonly USE_SHARED_VM=(
@@ -199,6 +207,8 @@ readonly USE_SHARED_VM=(
ns_guest_assign_g2h_netns_host_connect_ok
ns_guest_assign_g2h_netns_pins_ns_ok
ns_guest_assign_g2h_netns_old_conn_send_fails
+ ns_guest_assign_g2h_netns_no_cap_net_admin_fails
+ ns_guest_assign_g2h_netns_unpriv_user_ns_fails
)
readonly NS_MODES=("local" "global")
@@ -368,7 +378,7 @@ check_ns_guest_deps() {
return 0
fi
- for dep in unshare python3; do
+ for dep in unshare setpriv python3; do
if [[ ! -x $(command -v "${dep}") ]]; then
log_host "no ${dep} for test ${tname}"
return 1
@@ -1909,6 +1919,48 @@ test_ns_guest_assign_g2h_netns_pins_ns_ok() {
return "${KSFT_PASS}"
}
+test_ns_guest_assign_g2h_netns_no_cap_net_admin_fails() {
+ local cmd="unshare -n setpriv --bounding-set=-net_admin"
+ local rc
+
+ vm_ssh "init_ns" -- "${cmd}" python3 /root/ynl/cli.py --no-schema \
+ --spec /root/ynl/vsock.yaml --do dev-netns-set &>/dev/null
+ rc=$?
+
+ if [[ "${rc}" -eq 0 ]]; then
+ log_host "assign unexpectedly succeeded without CAP_NET_ADMIN"
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
+test_ns_guest_assign_g2h_netns_unpriv_user_ns_fails() {
+ local unpriv_uid=65534
+ local unpriv
+ local rc
+
+ unpriv="setpriv --reuid=${unpriv_uid} --regid=${unpriv_uid}"
+ unpriv="${unpriv} --clear-groups"
+
+ if ! vm_ssh "init_ns" -- "${unpriv} unshare -U true"; then
+ log_host "unprivileged user namespaces unavailable, skipping"
+ return "${KSFT_SKIP}"
+ fi
+
+ vm_ssh "init_ns" -- "${unpriv} unshare -Urn" \
+ python3 /root/ynl/cli.py --no-schema \
+ --spec /root/ynl/vsock.yaml --do dev-netns-set &>/dev/null
+ rc=$?
+
+ if [[ "${rc}" -eq 0 ]]; then
+ log_host "assign unexpectedly succeeded for unprivileged user"
+ return "${KSFT_FAIL}"
+ fi
+
+ return "${KSFT_PASS}"
+}
+
shared_vm_test() {
local tname
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v3 4/7] vsock: add a netlink command to assign the g2h device to a netns
2026-10-12 22:48 ` [PATCH net-next v3 4/7] vsock: add a netlink command to assign the g2h device to a netns Bobby Eshleman
@ 2026-10-13 1:44 ` Daniel Pereira
2026-10-13 17:34 ` Bobby Eshleman
0 siblings, 1 reply; 10+ messages in thread
From: Daniel Pereira @ 2026-10-13 1:44 UTC (permalink / raw)
To: bobbyeshleman
Cc: sgarzare, davem, kuba, pabeni, horms, corbet, skhan, stefanha,
mst, jasowangio, xuanzhuo, eperezma, shuah, rdunlap,
donald.hunter, edumazet, virtualization, netdev, linux-kernel,
linux-doc, kvm, linux-kselftest, sargun, jlinbox, bobbyeshleman
Em seg., 12 de out. de 2026 às 19:51, Bobby Eshleman
<bobbyeshleman@meta.com> escreveu:
>
[...]
> + Movement to an unreachable local namespace results in sockets being
> + closed with ECONNRESET. Peers are not guaranteed to be notified via
> + an RST.
This reads a bit differently from the sysctl documentation in the same
patch, which says:
Connections established before the move, from a namespace that can no
longer reach the device, are closed with ``ECONNRESET``.
The net.rst wording also covers moving the device back to init_net from
a local namespace, which this sentence does not. Maybe use the same
wording here:
Connections established before the move, from a namespace that can
no longer reach the device, are closed with ECONNRESET. Peers are not
guaranteed to be notified via an RST.
Daniel
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v3 4/7] vsock: add a netlink command to assign the g2h device to a netns
2026-10-13 1:44 ` Daniel Pereira
@ 2026-10-13 17:34 ` Bobby Eshleman
0 siblings, 0 replies; 10+ messages in thread
From: Bobby Eshleman @ 2026-10-13 17:34 UTC (permalink / raw)
To: Daniel Pereira
Cc: sgarzare, davem, kuba, pabeni, horms, corbet, skhan, stefanha,
mst, jasowangio, xuanzhuo, eperezma, shuah, rdunlap,
donald.hunter, edumazet, virtualization, netdev, linux-kernel,
linux-doc, kvm, linux-kselftest, sargun, jlinbox
On Mon, Oct 12, 2026 at 10:44:55PM -0300, Daniel Pereira wrote:
> Em seg., 12 de out. de 2026 às 19:51, Bobby Eshleman
> <bobbyeshleman@meta.com> escreveu:
> >
> [...]
> > + Movement to an unreachable local namespace results in sockets being
> > + closed with ECONNRESET. Peers are not guaranteed to be notified via
> > + an RST.
>
> This reads a bit differently from the sysctl documentation in the same
> patch, which says:
>
> Connections established before the move, from a namespace that can no
> longer reach the device, are closed with ``ECONNRESET``.
>
> The net.rst wording also covers moving the device back to init_net from
> a local namespace, which this sentence does not. Maybe use the same
> wording here:
>
> Connections established before the move, from a namespace that can
> no longer reach the device, are closed with ECONNRESET. Peers are not
> guaranteed to be notified via an RST.
>
> Daniel
Thanks Daniel, I forgot to update the docs with that change. Your
rewording sounds good to me.
Best,
Bobby
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-10-13 17:34 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-12 22:48 [PATCH net-next v3 0/7] vsock: assign the guest vsock device to a network namespace Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 1/7] vsock: constify the transport in vsock_for_each_connected_socket() Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 2/7] vsock: rename the vsock pernet operations Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 3/7] vsock: pass the netns to ->has_remote_cid() Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 4/7] vsock: add a netlink command to assign the g2h device to a netns Bobby Eshleman
2026-10-13 1:44 ` Daniel Pereira
2026-10-13 17:34 ` Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 5/7] vsock/virtio: support guest device network namespace Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 6/7] selftests/vsock: test the guest vsock " Bobby Eshleman
2026-10-12 22:48 ` [PATCH net-next v3 7/7] selftests/vsock: test the netns assign privilege checks Bobby Eshleman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox