]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsfd: support AF_VSOCK sockets
authorMasatake YAMATO <yamato@redhat.com>
Mon, 23 Dec 2024 15:38:25 +0000 (00:38 +0900)
committerMasatake YAMATO <yamato@redhat.com>
Sun, 29 Dec 2024 10:50:00 +0000 (19:50 +0900)
Example output:

    # ./lsfd -p 121067 -Q "TYPE == 'AF_VSOCK'"
    COMMAND       PID USER ASSOC  XMODE     TYPE SOURCE MNTID    INODE NAME
    test_mkfds 121067 root     4 rw---- AF_VSOCK sockfs    10 39400798 state=listen type=stream laddr=local:12345
    test_mkfds 121067 root     5 rw---- AF_VSOCK sockfs    10 39400799 state=established type=stream laddr=local:23456 raddr=local:12345
    test_mkfds 121067 root     6 rw---- AF_VSOCK sockfs    10 39400800 state=established type=stream laddr=local:12345 raddr=local:23456

Signed-off-by: Masatake YAMATO <yamato@redhat.com>
configure.ac
lsfd-cmd/lsfd.1.adoc
lsfd-cmd/lsfd.c
lsfd-cmd/lsfd.h
lsfd-cmd/sock-xinfo.c
meson.build
tests/expected/lsfd/mkfds-vsock [new file with mode: 0644]
tests/helpers/test_mkfds.c
tests/ts/lsfd/lsfd-functions.bash
tests/ts/lsfd/mkfds-vsock [new file with mode: 0755]

index 8de6af616ff8e853185446a539fde7ba89d1b0c0..fa58043bb7dce7882c2cf63057d35fa332c3a993 100644 (file)
@@ -592,6 +592,10 @@ AC_CHECK_DECL([SO_PASSCRED],
              [#include <sys/types.h>
               #include <sys/socket.h>])
 
+AC_CHECK_DECLS([VMADDR_CID_LOCAL], [], [], [
+              #include <sys/socket.h>
+              #include <linux/vm_sockets.h>])
+
 AC_CHECK_FUNCS([ \
        cachestat \
        clearenv \
index 28f6abd5eb863d6a0d7bba4f851fc105be52e707..e3abb0690aa81dc8faf32d58b3d8cf3ff9a89e58 100644 (file)
@@ -275,6 +275,11 @@ Cooked version of KNAME. It is mostly same as KNAME.
 +
 Some files have special formats and information sources:
 +
+AF_VSOCK:::
+state=_SOCK.STATE_ type=_SOCK.TYPE_ laddr=_VSOCK.LADDR_[ raddr=_VSOCK.RADDR_]
++
+`raddr` is not shown for listening sockets.
++
 bpf-map:::
 id=_BPF-MAP.ID_ type=_BPF-MAP.TYPE_[ name=_BPF.NAME_]
 +
@@ -348,7 +353,6 @@ state=_SOCK.STATE_[ path=_UNIX.PATH_]
 +
 UNIX:::
 state=_SOCK.STATE_[ path=_UNIX.PATH_] type=_SOCK.TYPE_
-
 ____
 Note that `(deleted)` markers are removed from this column.
 Refer to _KNAME_, _DELETED_, or _XMODE_ to know the
@@ -546,6 +550,22 @@ Filesystem pathname for UNIX domain socket.
 USER <``string``>::
 User of the process.
 
+VSOCK.LADDR <``string``>::
+VSOCK.RADDR <``string``>::
+Local VSOCK address. The format of the element
+is _VSOCK.LCID_``:``_VSOCK.LPORT_.
++
+Well-known CIDs will be decoded: "`{asterisk}`", "`hypervisor`", "`local`", or "`host`".
+Well-known ports will be decoded: "`{asterisk}`".
+
+VSOCK.LCID <``number``>::
+VSOCK.RCID <``number``>::
+Local and remote VSOCK context identifiers.
+
+VSOCK.LPORT <``number``>::
+VSOCK.RPORT <``number``>::
+Local and remote VSOCK ports.
+
 XMODE <``string``>::
 Extended version of _MODE_. This column may grow; new letters may be
 appended to _XMODE_ when *lsfd* supports a new state of file descriptors
@@ -729,7 +749,8 @@ mailto:kzak@redhat.com[Karel Zak]
 *scols-filter*(5),
 *socket*(2),
 *ss*(8),
-*stat*(2)
+*stat*(2),
+*vsock*(7)
 
 include::man-common/bugreports.adoc[]
 
index 242dd4ef72db24876a95eb76c56fecce553b84b7..77a7a0c22ccd53eea999ecf7926b09b54dedd14e 100644 (file)
@@ -431,6 +431,24 @@ static const struct colinfo infos[] = {
        [COL_USER]             = { "USER",
                                   0,   SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
                                   N_("user of the process") },
+       [COL_VSOCK_LCID]       = { "VSOCK.LCID",
+                                   0,   SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+                                   N_("local VSOCK context identifier") },
+       [COL_VSOCK_RCID]       = { "VSOCK.RCID",
+                                   0,   SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+                                   N_("remote VSOCK context identifier") },
+       [COL_VSOCK_LPORT]      = { "VSOCK.LPORT",
+                                   0,   SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+                                   N_("local VSOCK port") },
+       [COL_VSOCK_RPORT]      = { "VSOCK.RPORT",
+                                   0,   SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER,
+                                   N_("remote VSOCK port") },
+       [COL_VSOCK_LADDR]       = { "VSOCK.LADDR",
+                                   0,   SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+                                   N_("local VSOCK address (CID:PORT)") },
+       [COL_VSOCK_RADDR]       = { "VSOCK.RADDR",
+                                   0,   SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
+                                   N_("remote VSOCK address (CID:PORT)") },
        [COL_XMODE]            = { "XMODE",
                                   0,   SCOLS_FL_RIGHT, SCOLS_JSON_STRING,
                                   N_("extended version of MODE (rwxD[Ll]m)") },
index 28eb69c1485b3967de91fe5c7a80344be1367786..c9156757e1199930cd6ddcc1b87ee8aa11033f17 100644 (file)
@@ -142,6 +142,12 @@ enum {
        COL_UID,                /* process */
        COL_UNIX_PATH,
        COL_USER,               /* process */
+       COL_VSOCK_LADDR,
+       COL_VSOCK_RADDR,
+       COL_VSOCK_LCID,
+       COL_VSOCK_RCID,
+       COL_VSOCK_LPORT,
+       COL_VSOCK_RPORT,
        COL_XMODE,
        LSFD_N_COLS             /* This must be at last. */
 };
index 3bc45737d8deba2044ff1fa5d57a295f4dfe6f49..f6811213aa40dbf0f9f3a762daf3249eca70e0af 100644 (file)
@@ -33,6 +33,8 @@
 #include <linux/un.h>          /* UNIX_PATH_MAX */
 #include <linux/unix_diag.h>   /* UNIX_DIAG_*, UDIAG_SHOW_*,
                                   struct unix_diag_req */
+#include <linux/vm_sockets.h>  /* VMADDR_CID* */
+#include <linux/vm_sockets_diag.h> /* vsock_diag_req/vsock_diag_msg */
 #include <sched.h>             /* for setns(2) */
 #include <search.h>            /* tfind, tsearch */
 #include <stdint.h>
@@ -62,6 +64,7 @@ static void load_xinfo_from_proc_netlink(ino_t netns_inode);
 static void load_xinfo_from_proc_packet(ino_t netns_inode);
 
 static void load_xinfo_from_diag_unix(int diag, ino_t netns_inode);
+static void load_xinfo_from_diag_vsock(int diag, ino_t netns_inode);
 
 static int self_netns_fd = -1;
 static struct stat self_netns_sb;
@@ -186,6 +189,7 @@ static void load_sock_xinfo_no_nsswitch(struct netns *nsobj)
                                (diagsd >= 0)? "successful": strerror(errno)));
        if (diagsd >= 0) {
                load_xinfo_from_diag_unix(diagsd, netns);
+               load_xinfo_from_diag_vsock(diagsd, netns);
                close(diagsd);
                DBG(ENDPOINTS, ul_debug("close the diagnose socket"));
        }
@@ -2388,3 +2392,215 @@ static void load_xinfo_from_proc_packet(ino_t netns_inode)
  out:
        fclose(packet_fp);
 }
+
+/*
+ * VSOCK
+ */
+struct vsock_addr {
+       uint32_t cid;
+       uint32_t port;
+};
+
+struct vsock_xinfo {
+       struct sock_xinfo sock;
+       uint8_t type;
+       uint8_t  st;
+       uint8_t  shutdown_mask:3;
+       struct vsock_addr local;
+       struct vsock_addr remote;
+};
+
+static const char *vsock_decode_cid(uint32_t cid)
+{
+       switch (cid) {
+       case VMADDR_CID_ANY:
+               return "*";
+       case VMADDR_CID_HYPERVISOR:
+               return "hypervisor";
+#if HAVE_DECL_VMADDR_CID_LOCAL
+       case VMADDR_CID_LOCAL:
+               return "local";
+#endif /* HAVE_DECL_VMADDR_CID_LOCAL */
+       case VMADDR_CID_HOST:
+               return "host";
+       default:
+               return NULL;
+       }
+}
+
+static const char *vsock_decode_port(uint32_t port)
+{
+       if (port == VMADDR_PORT_ANY)
+               return "*";
+       return NULL;
+}
+
+static char* vsock_get_addr(struct vsock_addr *addr)
+{
+       const char *tmp_cid = vsock_decode_cid(addr->cid);
+       const char *tmp_port = vsock_decode_port(addr->port);
+       char cidstr[BUFSIZ];
+       char portstr[BUFSIZ];
+       char *str = NULL;
+
+       if (tmp_cid)
+               snprintf(cidstr, sizeof(cidstr), "%s", tmp_cid);
+       else
+               snprintf(cidstr, sizeof(cidstr), "%"PRIu32, addr->cid);
+
+       if (tmp_port)
+               snprintf(portstr, sizeof(portstr), "%s", tmp_port);
+       else
+               snprintf(portstr, sizeof(portstr), "%"PRIu32, addr->port);
+
+       xasprintf(&str, "%s:%s", cidstr, portstr);
+       return str;
+}
+
+static char *vsock_get_name(struct sock_xinfo *sock_xinfo,
+                           struct sock *sock __attribute__((__unused__)))
+{
+       struct vsock_xinfo *vs = (struct vsock_xinfo *)sock_xinfo;
+       char *str = NULL;
+       const char *st_str = l4_decode_state(vs->st);
+       const char *type_str = sock_decode_type(vs->type);
+       char *laddr = vsock_get_addr(&vs->local);
+
+       if (vs->st == TCP_LISTEN)
+               xasprintf(&str, "state=%s type=%s laddr=%s",
+                         st_str, type_str, laddr);
+       else {
+               char *raddr = vsock_get_addr(&vs->remote);
+
+               xasprintf(&str, "state=%s type=%s laddr=%s raddr=%s",
+                         st_str, type_str, laddr, raddr);
+               free(raddr);
+       }
+       free(laddr);
+
+       return str;
+}
+
+static char *vsock_get_type(struct sock_xinfo *sock_xinfo,
+                          struct sock *sock __attribute__((__unused__)))
+{
+       const char *str;
+       struct vsock_xinfo *vs = (struct vsock_xinfo *)sock_xinfo;
+
+       str = sock_decode_type(vs->type);
+       return xstrdup(str);
+}
+
+static char *vsock_get_state(struct sock_xinfo *sock_xinfo,
+                            struct sock *sock __attribute__((__unused__)))
+{
+       const char *str;
+       struct vsock_xinfo *vs = (struct vsock_xinfo *)sock_xinfo;
+
+       str = l4_decode_state(vs->st);
+       return xstrdup(str);
+}
+
+static bool vsock_get_listening(struct sock_xinfo *sock_xinfo,
+                               struct sock *sock __attribute__((__unused__)))
+{
+       return ((struct vsock_xinfo *)sock_xinfo)->st == TCP_LISTEN;
+}
+
+static bool vsock_fill_column(struct proc *proc __attribute__((__unused__)),
+                             struct sock_xinfo *sock_xinfo,
+                             struct sock *sock __attribute__((__unused__)),
+                             struct libscols_line *ln __attribute__((__unused__)),
+                             int column_id,
+                             size_t column_index __attribute__((__unused__)),
+                             char **str)
+{
+       struct vsock_xinfo *vs = (struct vsock_xinfo *)sock_xinfo;
+
+       switch (column_id) {
+       case COL_VSOCK_LCID:
+               xasprintf(str, "%"PRIu32, vs->local.cid);
+               return true;
+       case COL_VSOCK_RCID:
+               xasprintf(str, "%"PRIu32, vs->remote.cid);
+               return true;
+       case COL_VSOCK_LPORT:
+               xasprintf(str, "%"PRIu32, vs->local.port);
+               return true;
+       case COL_VSOCK_RPORT:
+               xasprintf(str, "%"PRIu32, vs->remote.port);
+               return true;
+       case COL_VSOCK_LADDR:
+               *str = vsock_get_addr(&vs->local);
+               return true;
+       case COL_VSOCK_RADDR:
+               *str = vsock_get_addr(&vs->remote);
+               return true;
+       }
+       return false;
+}
+
+static const struct sock_xinfo_class vsock_xinfo_class = {
+       .get_name = vsock_get_name,
+       .get_type = vsock_get_type,
+       .get_state = vsock_get_state,
+       .get_listening = vsock_get_listening,
+       .fill_column = vsock_fill_column,
+       .free = NULL,
+};
+
+static bool handle_diag_vsock(ino_t netns __attribute__((__unused__)),
+                            size_t nlmsg_len, void *nlmsg_data)
+{
+       const struct vsock_diag_msg *diag = nlmsg_data;
+       ino_t inode;
+       struct sock_xinfo *xinfo;
+       struct vsock_xinfo *vx;
+
+       if (diag->vdiag_family != AF_VSOCK)
+               return false;
+       DBG(ENDPOINTS, ul_debug("         VSOCK"));
+       DBG(ENDPOINTS, ul_debug("         LEN: %zu (>= %zu)", nlmsg_len,
+                               (size_t)(NLMSG_LENGTH(sizeof(*diag)))));
+
+       if (nlmsg_len < NLMSG_LENGTH(sizeof(*diag)))
+               return false;
+
+       inode = (ino_t)diag->vdiag_ino;
+       DBG(ENDPOINTS, ul_debug("         inode: %llu", (unsigned long long)inode));
+
+       xinfo = get_sock_xinfo(inode);
+       if (xinfo != NULL)
+               /* It seems that the same socket reported twice. */
+               return true;
+
+       vx = xcalloc(1, sizeof(*vx));
+       xinfo = &vx->sock;
+       DBG(ENDPOINTS, ul_debug("         xinfo: %p", xinfo));
+
+       xinfo->class = &vsock_xinfo_class;
+       xinfo->inode = (ino_t)inode;
+       xinfo->netns_inode = (ino_t)netns;
+
+       vx->type = diag->vdiag_type;
+       vx->st = diag->vdiag_state;
+       vx->shutdown_mask = diag->vdiag_shutdown;
+       vx->local.cid = diag->vdiag_src_cid;
+       vx->local.port = diag->vdiag_src_port;
+       vx->remote.cid = diag->vdiag_dst_cid;
+       vx->remote.port = diag->vdiag_dst_port;
+
+       add_sock_info(xinfo);
+       return true;
+}
+
+static void load_xinfo_from_diag_vsock(int diagsd, ino_t netns)
+{
+       struct vsock_diag_req vdr;
+
+       memset(&vdr, 0, sizeof(vdr));
+       vdr.sdiag_family = AF_VSOCK;
+       vdr.vdiag_states =  ~(uint32_t)0;
+
+       send_diag_request(diagsd, &vdr, sizeof(vdr), handle_diag_vsock, netns);
+}
index 949a0e41feb1b4aa33756a9b85176d98f5d2a0cd..462ceaae2242a9281aa5f7b14d75e7065136d945 100644 (file)
@@ -830,6 +830,10 @@ have_so_passcred = cc.has_header_symbol(
   prefix : '#include <sys/types.h>',
   required : build_plymouth_support.enabled())
 
+have = cc.has_header_symbol('linux/vm_sockets.h', 'VMADDR_CID_LOCAL',
+                            prefix : '#include <sys/socket.h>')
+conf.set('HAVE_DECL_VMADDR_CID_LOCAL', have ? 1 : false)
+
 build_plymouth_support = (not build_plymouth_support.disabled() and 
                           have_tiocglcktrmios and
                           have_sock_cloexec and
diff --git a/tests/expected/lsfd/mkfds-vsock b/tests/expected/lsfd/mkfds-vsock
new file mode 100644 (file)
index 0000000..2fa9de9
--- /dev/null
@@ -0,0 +1,25 @@
+# TYPE: STREAM
+3 SOCK state=listen type=stream laddr=local:12345                             listen stream 1
+4 SOCK state=established type=stream laddr=local:23456 raddr=local:12345 established stream 0
+5 SOCK state=established type=stream laddr=local:12345 raddr=local:23456 established stream 0
+ASSOC,STTYPE,NAME,SOCK.STATE,SOCK.TYPE,SOCK.LISTENING: 0
+local:12345 1 12345         *:* 4294967295 4294967295
+local:23456 1 23456 local:12345          1      12345
+local:12345 1 12345 local:23456          1      23456
+VSOCK.LADDR,VSOCK.LCID,VSOCK.LPORT,VSOCK.RADDR,VSOCK.RCID,VSOCK.RPORT: 0
+# TYPE: DGRAM
+3 SOCK state=established type=dgram laddr=local:12345 raddr=local:23456 established dgram 0
+4 SOCK state=established type=dgram laddr=local:23456 raddr=local:12345 established dgram 0
+ASSOC,STTYPE,NAME,SOCK.STATE,SOCK.TYPE,SOCK.LISTENING: 0
+local:12345 1 12345 local:23456 1 23456
+local:23456 1 23456 local:12345 1 12345
+VSOCK.LADDR,VSOCK.LCID,VSOCK.LPORT,VSOCK.RADDR,VSOCK.RCID,VSOCK.RPORT: 0
+# TYPE: SEQPACKET
+3 SOCK state=listen type=seqpacket laddr=local:12345                             listen seqpacket 1
+4 SOCK state=established type=seqpacket laddr=local:23456 raddr=local:12345 established seqpacket 0
+5 SOCK state=established type=seqpacket laddr=local:12345 raddr=local:23456 established seqpacket 0
+ASSOC,STTYPE,NAME,SOCK.STATE,SOCK.TYPE,SOCK.LISTENING: 0
+local:12345 1 12345         *:* 4294967295 4294967295
+local:23456 1 23456 local:12345          1      12345
+local:12345 1 12345 local:23456          1      23456
+VSOCK.LADDR,VSOCK.LCID,VSOCK.LPORT,VSOCK.RADDR,VSOCK.RCID,VSOCK.RPORT: 0
index 5709b2bce1185a160b948267ac3bcf1f0c63d154..dfb60ae8ecee9200966187ea0f4e5d5cecc9d172 100644 (file)
@@ -37,6 +37,7 @@
 #include <linux/sock_diag.h>
 # include <linux/unix_diag.h> /* for UNIX domain sockets */
 #include <linux/sockios.h>  /* SIOCGSKNS */
+#include <linux/vm_sockets.h>
 #include <mqueue.h>
 #include <net/if.h>
 #include <netinet/in.h>
@@ -74,6 +75,8 @@
 #define EXIT_EACCES 21
 #define EXIT_ENOENT 22
 #define EXIT_ENOSYS 23
+#define EXIT_EADDRNOTAVAIL 24
+#define EXIT_ENODEV 25
 
 #define _U_ __attribute__((__unused__))
 
@@ -2016,6 +2019,145 @@ static void *make_ping6(const struct factory *factory, struct fdesc fdescs[],
                                (struct sockaddr *)&in6);
 }
 
+#if HAVE_DECL_VMADDR_CID_LOCAL
+static void *make_vsock(const struct factory *factory, struct fdesc fdescs[],
+                       int argc, char ** argv)
+{
+       struct sockaddr_vm svm;
+       struct sockaddr_vm cvm;
+
+       struct arg server_port = decode_arg("server-port", factory->params, argc, argv);
+       unsigned short iserver_port = (unsigned short)ARG_INTEGER(server_port);
+       struct arg client_port = decode_arg("client-port", factory->params, argc, argv);
+       unsigned short iclient_port = (unsigned short)ARG_INTEGER(client_port);
+       struct arg socktype = decode_arg("socktype", factory->params, argc, argv);
+       int isocktype;
+       int ssd, csd, asd = -1;
+
+       const int y = 1;
+
+       free_arg(&server_port);
+       free_arg(&client_port);
+
+       if (strcmp(ARG_STRING(socktype), "STREAM") == 0)
+               isocktype = SOCK_STREAM;
+       else if (strcmp(ARG_STRING(socktype), "DGRAM") == 0)
+               isocktype = SOCK_DGRAM;
+       else if (strcmp(ARG_STRING(socktype), "SEQPACKET") == 0)
+               isocktype = SOCK_SEQPACKET;
+       else
+               errx(EXIT_FAILURE,
+                    "unknown socket type for socket(AF_VSOCK,...): %s",
+                    ARG_STRING(socktype));
+       free_arg(&socktype);
+
+       ssd = socket(AF_VSOCK, isocktype, 0);
+       if (ssd < 0) {
+               if (errno == ENODEV)
+                       err(EXIT_ENODEV, "failed to make a vsock socket for listening (maybe `modprobe vmw_vsock_vmci_transport'?)");
+               err(EXIT_FAILURE,
+                   "failed to make a vsock socket for listening");
+       }
+
+       if (setsockopt(ssd, SOL_SOCKET,
+                      SO_REUSEADDR, (const char *)&y, sizeof(y)) < 0) {
+               err(EXIT_FAILURE, "failed to setsockopt(SO_REUSEADDR)");
+       }
+
+       if (ssd != fdescs[0].fd) {
+               if (dup2(ssd, fdescs[0].fd) < 0) {
+                       err(EXIT_FAILURE, "failed to dup %d -> %d", ssd, fdescs[0].fd);
+               }
+               close(ssd);
+               ssd = fdescs[0].fd;
+       }
+
+       memset(&svm, 0, sizeof(svm));
+       svm.svm_family = AF_VSOCK;
+       svm.svm_port = iserver_port;
+       svm.svm_cid = VMADDR_CID_LOCAL;
+
+       memset(&cvm, 0, sizeof(svm));
+       cvm.svm_family = AF_VSOCK;
+       cvm.svm_port = iclient_port;
+       cvm.svm_cid = VMADDR_CID_LOCAL;
+
+       if (bind(ssd, (struct sockaddr *)&svm, sizeof(svm)) < 0) {
+               if (errno == EADDRNOTAVAIL)
+                       err(EXIT_EADDRNOTAVAIL, "failed to bind a listening socket (maybe `modprobe vsock_loopback'?)");
+               err(EXIT_FAILURE, "failed to bind a listening socket");
+       }
+
+
+       if (isocktype == SOCK_DGRAM) {
+               if (connect(ssd, (struct sockaddr *)&cvm, sizeof(cvm)) < 0)
+                       err(EXIT_FAILURE, "failed to connect the server socket to a client socket");
+       } else {
+               if (listen(ssd, 1) < 0)
+                       err(EXIT_FAILURE, "failed to listen a socket");
+       }
+
+       csd = socket(AF_VSOCK, isocktype, 0);
+       if (csd < 0) {
+               err(EXIT_FAILURE,
+                   "failed to make a vsock client socket");
+       }
+
+       if (setsockopt(csd, SOL_SOCKET,
+                      SO_REUSEADDR, (const char *)&y, sizeof(y)) < 0) {
+               err(EXIT_FAILURE, "failed to setsockopt(SO_REUSEADDR)");
+       }
+
+       if (csd != fdescs[1].fd) {
+               if (dup2(csd, fdescs[1].fd) < 0) {
+                       err(EXIT_FAILURE, "failed to dup %d -> %d", csd, fdescs[1].fd);
+               }
+               close(csd);
+               csd = fdescs[1].fd;
+       }
+
+       if (bind(csd, (struct sockaddr *)&cvm, sizeof(cvm)) < 0) {
+               err(EXIT_FAILURE, "failed to bind a client socket");
+       }
+
+       if (connect(csd, (struct sockaddr *)&svm, sizeof(svm)) < 0) {
+               err(EXIT_FAILURE, "failed to connect a client socket to the server socket");
+       }
+
+       if (isocktype != SOCK_DGRAM) {
+               asd = accept(ssd, NULL, NULL);
+               if (asd < 0) {
+                       err(EXIT_FAILURE, "failed to accept a socket from the listening socket");
+               }
+               if (asd != fdescs[2].fd) {
+                       if (dup2(asd, fdescs[2].fd) < 0) {
+                               err(EXIT_FAILURE, "failed to dup %d -> %d", asd, fdescs[2].fd);
+                       }
+                       close(asd);
+                       asd = fdescs[2].fd;
+               }
+       }
+
+       fdescs[0] = (struct fdesc) {
+               .fd    = fdescs[0].fd,
+               .close = close_fdesc,
+               .data  = NULL,
+       };
+       fdescs[1] = (struct fdesc) {
+               .fd    = fdescs[1].fd,
+               .close = close_fdesc,
+               .data  = NULL,
+       };
+
+       fdescs[2] = (struct fdesc) {
+               .fd    = (iserver_port == SOCK_DGRAM)? -1: fdescs[2].fd,
+               .close = close_fdesc,
+               .data  = NULL,
+       };
+       return NULL;
+}
+#endif /* HAVE_DECL_VMADDR_CID_LOCAL */
+
 #ifdef SIOCGSKNS
 static void *make_netns(const struct factory *factory _U_, struct fdesc fdescs[],
                        int argc _U_, char ** argv _U_)
@@ -3810,6 +3952,37 @@ static const struct factory factories[] = {
                        PARAM_END
                }
        },
+#if HAVE_DECL_VMADDR_CID_LOCAL
+       {
+               "vsock",
+               .desc = "AF_VSOCK sockets",
+               .priv = false,
+               .N    = 3,      /* NOTE: The 3rd one is not used if socktype is DGRAM. */
+               .EX_N = 0,
+               .make = make_vsock,
+               .params = (struct parameter []) {
+                       {
+                               .name = "socktype",
+                               .type = PTYPE_STRING,
+                               .desc = "STREAM, DGRAM, or SEQPACKET",
+                               .defv.string = "STREAM",
+                       },
+                       {
+                               .name = "server-port",
+                               .type = PTYPE_INTEGER,
+                               .desc = "VSOCK port the server may listen",
+                               .defv.integer = 12345,
+                       },
+                       {
+                               .name = "client-port",
+                               .type = PTYPE_INTEGER,
+                               .desc = "VSOCK port the client may bind",
+                               .defv.integer = 23456,
+                       },
+                       PARAM_END
+               }
+       },
+#endif /* HAVE_DECL_VMADDR_CID_LOCAL */
 #ifdef SIOCGSKNS
        {
                .name = "netns",
index dda315d95f16fa9b84275e7c481f7ee220fd897a..1b2a0ac042ba5a20d2cc354f4ea6708c219d2d48 100644 (file)
@@ -22,6 +22,8 @@ readonly EPROTONOSUPPORT=20
 readonly EACCES=21
 readonly ENOENT=22
 readonly ENOSYS=23
+readonly EADDRNOTAVAIL=24
+readonly ENODEV=25
 
 function lsfd_wait_for_pausing {
        ts_check_prog "sleep"
@@ -125,3 +127,25 @@ function lsfd_check_sockdiag
                ts_failed "failed to create a sockdiag netlink socket $family ($err): $msg";;
        esac
 }
+
+function lsfd_check_vsock
+{
+       ts_check_test_command "$TS_HELPER_MKFDS"
+
+       local msg
+       local err
+
+       msg=$("$TS_HELPER_MKFDS" -c vsock 3 4 5 socktype=DGRAM 2>&1)
+       err=$?
+
+       case $err in
+           0)
+               return;;
+           "$EADDRNOTAVAIL")
+               ts_skip "VMADDR_CID_LOCAL doesn't work";;
+           "$ENODEV")
+               ts_skip "AF_VSOCK+SOCK_DGRAM doesn't work";;
+           *)
+               ts_failed "failed to use a AF_VSOCK socket: $msg [$err]";;
+       esac
+}
diff --git a/tests/ts/lsfd/mkfds-vsock b/tests/ts/lsfd/mkfds-vsock
new file mode 100755 (executable)
index 0000000..7b740ac
--- /dev/null
@@ -0,0 +1,75 @@
+#!/bin/bash
+#
+# Copyright (C) 2024 Masatake YAMATO <yamato@redhat.com>
+#
+# This file is part of util-linux.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This file is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="VSOCK stream sockets"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+ts_skip_nonroot
+
+. "$TS_SELF/lsfd-functions.bash"
+
+ts_check_test_command "$TS_CMD_LSFD"
+ts_check_test_command "$TS_HELPER_MKFDS"
+
+lsfd_check_mkfds_factory vsock
+
+ts_check_prog "modprobe"
+
+ts_cd "$TS_OUTDIR"
+
+PID=
+FDS=3
+FDC=4
+FDA=5
+SPORT=12345
+CPORT=23456
+EXPR='(TYPE == "AF_VSOCK") and (FD >= 3) and (FD <= 5)'
+
+# AF_VSOCK+SOCK_DGRAM requires this.
+modprobe --quiet vmw_vsock_vmci_transport ||:
+modprobe --quiet vmw_vsock_virtio_transport ||:
+modprobe --quiet hv_vsock ||:
+
+# VMADDR_CID_LOCAL requires this.
+modprobe --quiet vsock_loopback        ||:
+
+lsfd_check_vsock
+
+{
+
+    for t in STREAM DGRAM SEQPACKET; do
+       coproc MKFDS { "$TS_HELPER_MKFDS" vsock $FDS $FDC $FDA \
+                                         server-port=$SPORT client-port=$CPORT \
+                                         socktype=$t ; }
+       if read -r -u "${MKFDS[0]}" PID; then
+           echo "# TYPE: $t"
+           ${TS_CMD_LSFD} -n \
+                          -o ASSOC,STTYPE,NAME,SOCK.STATE,SOCK.TYPE,SOCK.LISTENING \
+                          -p "${PID}" -Q "${EXPR}"
+           echo 'ASSOC,STTYPE,NAME,SOCK.STATE,SOCK.TYPE,SOCK.LISTENING': ${PIPESTATUS[0]}
+           ${TS_CMD_LSFD} -n \
+                          -o VSOCK.LADDR,VSOCK.LCID,VSOCK.LPORT,VSOCK.RADDR,VSOCK.RCID,VSOCK.RPORT \
+                          -p "${PID}" -Q "${EXPR}"
+           echo 'VSOCK.LADDR,VSOCK.LCID,VSOCK.LPORT,VSOCK.RADDR,VSOCK.RCID,VSOCK.RPORT': ${PIPESTATUS[0]}
+           echo DONE >&"${MKFDS[1]}"
+       fi
+       wait "${MKFDS_PID}"
+    done
+} > "$TS_OUTPUT" 2>&1
+
+ts_finalize