[PATCH net-next v3 4/7] vsock: add a netlink command to assign the g2h device to a netns
flat view
HOTtoday
From: Bobby Eshleman <hidden>
Date: 2026-10-12 22:49:44
Also in:
kvm, linux-doc, linux-kselftest, lkml, virtualization
Subsystem:
documentation, networking [general], the rest, virtio and vhost vsock driver, virtio host (vhost), vm sockets (af_vsock), yaml netlink (ynl) · Maintainers:
Jonathan Corbet, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds, Stefan Hajnoczi, Stefano Garzarella, "Michael S. Tsirkin", Jason Wang, Eugenio Pérez, Donald Hunter
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/ (local) Signed-off-by: Bobby Eshleman <redacted> --- 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