[PATCH net-next v3 4/7] vsock: add a netlink command to assign the g2h device to a netns
From: Bobby Eshleman
Date: Mon Oct 12 2026 - 18:51:46 EST
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@xxxxxxxxxx>
Link: https://lore.kernel.org/all/20200427142518.uwssa6dtasrp3bfc@steredhat/
Signed-off-by: Bobby Eshleman <bobbyeshleman@xxxxxxxx>
---
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@xxxxxxxxxx>
L: virtualization@xxxxxxxxxxxxxxx
L: netdev@xxxxxxxxxxxxxxx
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