git: cbc6f7e941e4 - main - bhyve: add UNIX domain socket support to rfb

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Thu, 08 Jan 2026 16:06:07 UTC
The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=cbc6f7e941e42639a0314cd121b06493cce8e0e6

commit cbc6f7e941e42639a0314cd121b06493cce8e0e6
Author:     Quentin Thébault <quentin.thebault@defenso.fr>
AuthorDate: 2025-11-18 06:44:05 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-01-08 15:24:52 +0000

    bhyve: add UNIX domain socket support to rfb
    
    This commit adds support for a UNIX domain socket to bhyve's remote
    framebuffer. It enables the use of the graphical console when the bhyve instance
    is running in a jail with no networking, for instance. A VNC client running on
    the host can then connect to the UNIX domain socket through the filesystem.
    
    Signed-off-by:  Quentin Thébault <quentin.thebault@defenso.fr>
    Sponsored by:   Defenso
    Reviewed by:    kevans, markj
    MFC after:      2 weeks
    Differential Revision: https://reviews.freebsd.org/D53814
---
 usr.sbin/bhyve/bhyve.8        | 12 +++++++----
 usr.sbin/bhyve/bhyve_config.5 |  7 ++++++-
 usr.sbin/bhyve/pci_fbuf.c     | 22 ++++++++++++++++++-
 usr.sbin/bhyve/rfb.c          | 49 +++++++++++++++++++++++++++++++------------
 usr.sbin/bhyve/rfb.h          |  4 +++-
 5 files changed, 74 insertions(+), 20 deletions(-)

diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8
index d3b067509ced..496539f30885 100644
--- a/usr.sbin/bhyve/bhyve.8
+++ b/usr.sbin/bhyve/bhyve.8
@@ -891,7 +891,7 @@ to guest by VirtIO Input Interface.
 .Bl -bullet
 .Sm off
 .It
-.Op Cm rfb= Ar ip-and-port
+.Op Cm rfb= Ar address
 .Op Cm ,w= Ar width
 .Op Cm ,h= Ar height
 .Op Cm ,vga= Ar vgaconf
@@ -902,9 +902,9 @@ to guest by VirtIO Input Interface.
 .Pp
 Configuration options are defined as follows:
 .Bl -tag -width 10n
-.It Cm rfb= Ns Ar ip-and-port Pq or Cm tcp= Ns Ar ip-and-port
-An IP address and a port VNC should listen on.
-There are two formats:
+.It Cm rfb= Ns Ar address Pq or Cm tcp= Ns Ar address
+A UNIX domain socket or IP address and a port VNC should listen on.
+There are three possible formats:
 .Pp
 .Bl -bullet -compact
 .It
@@ -916,6 +916,10 @@ There are two formats:
 .Sm off
 .Cm \&[ Ar IPv6%zone Cm \&] Cm \&: Ar port
 .Sm on
+.It
+.Sm off
+.Cm unix: Ar my/unix.sock
+.Sm on
 .El
 .Pp
 The default is to listen on localhost IPv4 address and default VNC port 5900.
diff --git a/usr.sbin/bhyve/bhyve_config.5 b/usr.sbin/bhyve/bhyve_config.5
index 4ead94690d91..429ce3e38138 100644
--- a/usr.sbin/bhyve/bhyve_config.5
+++ b/usr.sbin/bhyve/bhyve_config.5
@@ -523,7 +523,7 @@ the device name.
 If specified, it must be a unicast MAC address.
 .El
 .Ss Frame Buffer Settings
-.Bl -column "password" "[IP:]port" "127.0.0.1:5900"
+.Bl -column "password" "unix:my/unix.sock" "127.0.0.1:5900"
 .It Sy Name Ta Sy Format Ta Sy Default Ta Sy Description
 .It Va wait Ta bool Ta false Ta
 Wait for a remote connection before starting the VM.
@@ -535,6 +535,11 @@ support scoped identifiers as described in
 .Xr getaddrinfo 3 .
 A bare port number may be given in which case the IPv4
 localhost address is used.
+.It Va rfb Ta
+.Sm off
+.Cm unix: Ar my/unix.sock Ta Ta
+.Sm on
+Alternatively, provide a path to a UNIX domain socket.
 .It Va vga Ta string Ta io Ta
 VGA configuration.
 More details are provided in
diff --git a/usr.sbin/bhyve/pci_fbuf.c b/usr.sbin/bhyve/pci_fbuf.c
index 1e3ec77c15b0..560c2bc839d6 100644
--- a/usr.sbin/bhyve/pci_fbuf.c
+++ b/usr.sbin/bhyve/pci_fbuf.c
@@ -28,6 +28,8 @@
 
 #include <sys/types.h>
 #include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/un.h>
 
 #include <dev/vmm/vmm_mem.h>
 #include <machine/vmm.h>
@@ -94,6 +96,7 @@ struct pci_fbuf_softc {
 	} __packed memregs;
 
 	/* rfb server */
+	sa_family_t rfb_family;
 	char      *rfb_host;
 	char      *rfb_password;
 	int       rfb_port;
@@ -252,11 +255,13 @@ pci_fbuf_parse_config(struct pci_fbuf_softc *sc, nvlist_t *nvl)
 		value = get_config_value_node(nvl, "tcp");
 	if (value != NULL) {
 		/*
+		 * UNIX -- unix:path/to/socket.sock
 		 * IPv4 -- host-ip:port
 		 * IPv6 -- [host-ip%zone]:port
 		 * XXX for now port is mandatory for IPv4.
 		 */
 		if (value[0] == '[') {
+			sc->rfb_family = AF_INET6;
 			cp = strchr(value + 1, ']');
 			if (cp == NULL || cp == value + 1) {
 				EPRINTLN("fbuf: Invalid IPv6 address: \"%s\"",
@@ -279,7 +284,21 @@ pci_fbuf_parse_config(struct pci_fbuf_softc *sc, nvlist_t *nvl)
 				    value);
 				return (-1);
 			}
+		} else if (strncmp("unix:", value, 5) == 0) {
+			if (strlen(value + 5) > SUNPATHLEN) {
+				EPRINTLN(
+				    "fbuf: UNIX socket path too long: \"%s\"",
+				    value + 5);
+				return (-1);
+			} else if (*(value + 5) == '\0') {
+				EPRINTLN("fbuf: UNIX socket path is empty");
+				return (-1);
+			} else {
+				sc->rfb_family = AF_UNIX;
+				sc->rfb_host = strdup(value + 5);
+			}
 		} else {
+			sc->rfb_family = AF_UNSPEC;
 			cp = strchr(value, ':');
 			if (cp == NULL) {
 				sc->rfb_port = atoi(value);
@@ -433,7 +452,8 @@ pci_fbuf_init(struct pci_devinst *pi, nvlist_t *nvl)
 
 	memset((void *)sc->fb_base, 0, FB_SIZE);
 
-	error = rfb_init(sc->rfb_host, sc->rfb_port, sc->rfb_wait, sc->rfb_password);
+	error = rfb_init(sc->rfb_family, sc->rfb_host, sc->rfb_port,
+	    sc->rfb_wait, sc->rfb_password);
 done:
 	if (error)
 		free(sc);
diff --git a/usr.sbin/bhyve/rfb.c b/usr.sbin/bhyve/rfb.c
index 716e191e2fc0..aeaf8d1c0639 100644
--- a/usr.sbin/bhyve/rfb.c
+++ b/usr.sbin/bhyve/rfb.c
@@ -35,6 +35,7 @@
 #include <sys/socket.h>
 #include <sys/select.h>
 #include <sys/time.h>
+#include <sys/un.h>
 #include <arpa/inet.h>
 #include <stdatomic.h>
 #include <machine/cpufunc.h>
@@ -1254,13 +1255,15 @@ sse42_supported(void)
 }
 
 int
-rfb_init(const char *hostname, int port, int wait, const char *password)
+rfb_init(sa_family_t family, const char *hostname, int port, int wait,
+    const char *password)
 {
 	int e;
 	char servname[6];
 	struct rfb_softc *rc;
 	struct addrinfo *ai = NULL;
 	struct addrinfo hints;
+	struct sockaddr_un sun;
 	int on = 1;
 	int cnt;
 #ifndef WITHOUT_CAPSICUM
@@ -1301,25 +1304,42 @@ rfb_init(const char *hostname, int port, int wait, const char *password)
 		hostname = "[::1]";
 #endif
 
-	memset(&hints, 0, sizeof(hints));
-	hints.ai_family = AF_UNSPEC;
-	hints.ai_socktype = SOCK_STREAM;
-	hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE;
-
-	if ((e = getaddrinfo(hostname, servname, &hints, &ai)) != 0) {
-		EPRINTLN("getaddrinfo: %s", gai_strerror(e));
-		goto error;
+	if (family == AF_UNIX) {
+		memset(&sun, 0, sizeof(sun));
+		sun.sun_family = AF_UNIX;
+		if (strlcpy(sun.sun_path, hostname, sizeof(sun.sun_path)) >=
+		    sizeof(sun.sun_path)) {
+			EPRINTLN("rfb: socket path too long");
+			goto error;
+		}
+		rc->sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+	} else {
+		memset(&hints, 0, sizeof(hints));
+		hints.ai_socktype = SOCK_STREAM;
+		hints.ai_family = family;
+		hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE;
+
+		if ((e = getaddrinfo(hostname, servname, &hints, &ai)) != 0) {
+			EPRINTLN("getaddrinfo: %s", gai_strerror(e));
+			goto error;
+		}
+		rc->sfd = socket(ai->ai_family, ai->ai_socktype, 0);
 	}
 
-	rc->sfd = socket(ai->ai_family, ai->ai_socktype, 0);
 	if (rc->sfd < 0) {
 		perror("socket");
 		goto error;
 	}
 
+	/* No effect for UNIX domain sockets. */
 	setsockopt(rc->sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
 
-	if (bind(rc->sfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+	if (family == AF_UNIX) {
+		unlink(hostname);
+		e = bind(rc->sfd, (struct sockaddr *)&sun, SUN_LEN(&sun));
+	} else
+		e = bind(rc->sfd, ai->ai_addr, ai->ai_addrlen);
+	if (e < 0) {
 		perror("bind");
 		goto error;
 	}
@@ -1355,14 +1375,17 @@ rfb_init(const char *hostname, int port, int wait, const char *password)
 		DPRINTF(("rfb client connected"));
 	}
 
-	freeaddrinfo(ai);
+	if (family != AF_UNIX)
+		freeaddrinfo(ai);
 	return (0);
 
  error:
 	if (rc->pixfmt_mtx)
 		pthread_mutex_destroy(&rc->pixfmt_mtx);
-	if (ai != NULL)
+	if (ai != NULL) {
+		assert(family != AF_UNIX);
 		freeaddrinfo(ai);
+	}
 	if (rc->sfd != -1)
 		close(rc->sfd);
 	free(rc->crc);
diff --git a/usr.sbin/bhyve/rfb.h b/usr.sbin/bhyve/rfb.h
index 347ced083a22..c11d40f031af 100644
--- a/usr.sbin/bhyve/rfb.h
+++ b/usr.sbin/bhyve/rfb.h
@@ -29,9 +29,11 @@
 #ifndef _RFB_H_
 #define	_RFB_H_
 
+#include <sys/socket.h>
+
 #define	RFB_PORT	5900
 
-int	rfb_init(const char *hostname, int port, int wait,
+int	rfb_init(sa_family_t family, const char *hostname, int port, int wait,
 	    const char *password);
 
 #endif /* _RFB_H_ */