diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8 --- a/usr.sbin/bhyve/bhyve.8 +++ b/usr.sbin/bhyve/bhyve.8 @@ -573,6 +573,9 @@ process. .It Ar /dev/xxx Use the host TTY device for serial port I/O. +.It Ar tcp:ip:port +Use the tcp server for serial port I/O. +It will start a tcp server and wait for connection. .El .Pp TPM device backends: @@ -1065,7 +1068,7 @@ .Ed .Pp Run a UEFI virtual machine with a display resolution of 800 by 600 pixels -that can be accessed via VNC at: 0.0.0.0:5900. +that can be accessed via VNC at: 0.0.0.0:5900 or via raw tcp at: 0.0.0.0:1234 .Bd -literal -offset indent bhyve -c 2 -m 4G -w -H \\ -s 0,hostbridge \\ @@ -1074,13 +1077,13 @@ -s 5,virtio-net,tap0 \\ -s 29,fbuf,tcp=0.0.0.0:5900,w=800,h=600,wait \\ -s 30,xhci,tablet \\ - -s 31,lpc -l com1,stdio \\ + -s 31,lpc -l com1,tcp=0.0.0.0:1234 \\ -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd \\ uefivm .Ed .Pp Run a UEFI virtual machine with a VNC display that is bound to all IPv6 -addresses on port 5900. +addresses on port 5900 and a TCP serial I/O bound to port 1234. .Bd -literal -offset indent bhyve -c 2 -m 4G -w -H \\ -s 0,hostbridge \\ @@ -1088,7 +1091,7 @@ -s 5,virtio-net,tap0 \\ -s 29,fbuf,tcp=[::]:5900,w=800,h=600 \\ -s 30,xhci,tablet \\ - -s 31,lpc -l com1,stdio \\ + -s 31,lpc -l com1,tcp=[::]:1234 \\ -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd \\ uefivm .Ed diff --git a/usr.sbin/bhyve/uart_emul.c b/usr.sbin/bhyve/uart_emul.c --- a/usr.sbin/bhyve/uart_emul.c +++ b/usr.sbin/bhyve/uart_emul.c @@ -28,7 +28,11 @@ */ #include +#include #include +#include +#include +#include #ifndef WITHOUT_CAPSICUM #include #include @@ -104,6 +108,7 @@ struct ttyfd { bool opened; + bool connect_based; int rfd; /* fd for reading */ int wfd; /* fd for writing, may be == rfd */ }; @@ -134,6 +139,7 @@ }; static void uart_drain(int fd, enum ev_type ev, void *arg); +static void uart_tcp_disconnect(struct uart_softc *sc); static void ttyclose(void) @@ -160,21 +166,23 @@ } static int -ttyread(struct ttyfd *tf) +ttyread(struct ttyfd *tf, unsigned char *ret) { unsigned char rb; + int sz; - if (read(tf->rfd, &rb, 1) == 1) - return (rb); - else - return (-1); + sz = read(tf->rfd, &rb, 1); + if (ret != NULL) + *ret = rb; + + return sz; } -static void +static int ttywrite(struct ttyfd *tf, unsigned char wb) { - (void)write(tf->wfd, &wb, 1); + return write(tf->wfd, &wb, 1); } static void @@ -374,7 +382,8 @@ uart_drain(int fd, enum ev_type ev, void *arg) { struct uart_softc *sc; - int ch; + int len; + unsigned char ch; sc = arg; @@ -389,15 +398,26 @@ pthread_mutex_lock(&sc->mtx); if ((sc->mcr & MCR_LOOPBACK) != 0) { - (void) ttyread(&sc->tty); + if (ttyread(&sc->tty, NULL) == 0 && sc->tty.connect_based) + uart_tcp_disconnect(sc); } else { - while (rxfifo_available(sc) && - ((ch = ttyread(&sc->tty)) != -1)) { - rxfifo_putchar(sc, ch); + while (rxfifo_available(sc)) { + len = ttyread(&sc->tty, &ch); + + if (len <= 0) { + if (len == 0 && sc->tty.connect_based) { + uart_tcp_disconnect(sc); + goto clear; + } + break; + } else { + rxfifo_putchar(sc, ch); + } } uart_toggle_intr(sc); } +clear: pthread_mutex_unlock(&sc->mtx); } @@ -430,7 +450,9 @@ if (rxfifo_putchar(sc, value) != 0) sc->lsr |= LSR_OE; } else if (sc->tty.opened) { - ttywrite(&sc->tty, value); + if (ttywrite(&sc->tty, value) == -1 && + sc->tty.connect_based) + uart_tcp_disconnect(sc); } /* else drop on floor */ sc->thre_int_pending = true; break; @@ -705,6 +727,119 @@ return (0); } +static void +uart_tcp_listener(int fd, enum ev_type type __unused, void *arg) +{ + const static char tcp_error_msg[] = "Socket already connected\n"; + struct uart_softc *sc = (struct uart_softc *)arg; + struct sockaddr_in dst_addr; + socklen_t len = sizeof(dst_addr); + int conn_fd; + + conn_fd = accept(fd, (struct sockaddr *)&dst_addr, &len); + if (conn_fd == -1) { + errx(EX_OSERR, "Unable to accept the connection"); + return; + } + + if (fcntl(conn_fd, F_SETFL, O_NONBLOCK) != 0) { + errx(EX_OSERR, "Unable to set nonblocking mode to connection"); + return; + } + + pthread_mutex_lock(&sc->mtx); + + if (sc->tty.opened) { + send(conn_fd, tcp_error_msg, sizeof(tcp_error_msg), 0); + close(conn_fd); + } else { + sc->tty.rfd = sc->tty.wfd = conn_fd; + sc->tty.opened = true; + uart_opentty(sc); + } + + pthread_mutex_unlock(&sc->mtx); +} + +static void +uart_tcp_disconnect(struct uart_softc *sc) +{ + mevent_delete_close(sc->mev); + sc->mev = NULL; + + sc->tty.opened = false; + sc->tty.rfd = sc->tty.wfd = -1; +} + +// Listen to address and add to kqueue. +// If connected (e.g. tcp_handler is triggered), we replace the handler to the +// connected handler +static int +uart_tcp_backend(struct uart_softc *sc, const char *path) +{ +#ifndef WITHOUT_CAPSICUM + cap_rights_t rights; + cap_ioctl_t cmds[] = { TIOCGETA, TIOCSETA, TIOCGWINSZ }; +#endif + int bind_fd; + char addr[256], port[6]; + int domain; + struct addrinfo hints, *src_addr; + + if (sscanf(path, "tcp=[%255[^]]]:%5s", addr, port) == 2) { + domain = AF_INET6; + } else if (sscanf(path, "tcp=%255[^:]:%5s", addr, port) == 2) { + domain = AF_INET; + } else { + errx(EX_CONFIG, "Invalid number of parameter"); + return (-1); + } + + bind_fd = socket(domain, SOCK_STREAM, 0); + if (bind_fd < 0) + return (-1); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = domain; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV | AI_PASSIVE; + + if (getaddrinfo(addr, port, &hints, &src_addr) != 0) { + errx(EX_CONFIG, "Invalid address %s:%s", addr, port); + return (-1); + } + + if (bind(bind_fd, src_addr->ai_addr, src_addr->ai_addrlen) == -1) { + errx(EX_OSERR, + "Unable to bind to address %s:%s. Maybe the address is used?", + addr, port); + return (-1); + } + + if (fcntl(bind_fd, F_SETFL, O_NONBLOCK) == -1) + return (-1); + + if (listen(bind_fd, 1) == -1) + errx(EX_OSERR, "Unable to listen to address %s:%s", addr, port); + + if (mevent_add(bind_fd, EVF_READ, uart_tcp_listener, (void *)sc) == + NULL) + return (-1); + +#ifndef WITHOUT_CAPSICUM + cap_rights_init(&rights, CAP_EVENT, CAP_ACCEPT, CAP_RECV, CAP_SEND, + CAP_FCNTL, CAP_IOCTL); + if (caph_rights_limit(bind_fd, &rights) == -1) + errx(EX_OSERR, "Unable to apply rights for sandbox"); + if (caph_ioctls_limit(bind_fd, cmds, nitems(cmds)) == -1) + errx(EX_OSERR, "Unable to apply ioctls for sandbox"); + if (caph_fcntls_limit(bind_fd, CAP_FCNTL_SETFL) == -1) + errx(EX_OSERR, "Unable to apply fcntls for sandbox"); +#endif + + return (0); +} + int uart_set_backend(struct uart_softc *sc, const char *device) { @@ -715,9 +850,12 @@ if (strcmp("stdio", device) == 0) retval = uart_stdio_backend(sc); - else + else if (strncmp("tcp", device, 3) == 0) { + retval = uart_tcp_backend(sc, device); + sc->tty.connect_based = true; + } else retval = uart_tty_backend(sc, device); - if (retval == 0) + if (retval == 0 && !sc->tty.connect_based) uart_opentty(sc); return (retval);