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 @@ -620,6 +620,10 @@ 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. +Notice that raw tcp has its vulnerability, please use this under LAN .El .Ss TPM device backends .Bl -bullet @@ -1118,7 +1122,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 \\ @@ -1127,13 +1131,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 \\ @@ -1141,7 +1145,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_backend.c b/usr.sbin/bhyve/uart_backend.c --- a/usr.sbin/bhyve/uart_backend.c +++ b/usr.sbin/bhyve/uart_backend.c @@ -28,13 +28,18 @@ */ #include +#include #include #include +#include + +#include #include #include #include +#include #include #include #include @@ -49,6 +54,7 @@ struct ttyfd { bool opened; + bool connect_based; int rfd; /* fd for reading */ int wfd; /* fd for writing, may be == rfd */ }; @@ -70,9 +76,17 @@ pthread_mutex_t mtx; }; +struct uart_connect_infos { + struct uart_softc *softc; + void (*drain)(int, enum ev_type, void *); + void *arg; +}; + static bool uart_stdio; /* stdio in use for i/o */ static struct termios tio_stdio_orig; +static void uart_tcp_disconnect(struct uart_softc *); + static void ttyclose(void) { @@ -97,20 +111,22 @@ } static int -ttyread(struct ttyfd *tf) +ttyread(struct ttyfd *tf, uint8_t *ret) { - unsigned char rb; + uint8_t rb; + int len; - if (read(tf->rfd, &rb, 1) == 1) - return (rb); - else - return (-1); + len = read(tf->rfd, &rb, 1); + if (ret) + *ret = rb; + + return len; } -static void +static int ttywrite(struct ttyfd *tf, unsigned char wb) { - (void)write(tf->wfd, &wb, 1); + return write(tf->wfd, &wb, 1); } static bool @@ -179,14 +195,27 @@ void uart_rxfifo_drain(struct uart_softc *sc, bool loopback) { - int ch; + uint8_t ch; + int len; if (loopback) { - (void)ttyread(&sc->tty); + if (ttyread(&sc->tty, &ch) == 0 && sc->tty.connect_based) + uart_tcp_disconnect(sc); } else { - while (rxfifo_available(sc) && - ((ch = ttyread(&sc->tty)) != -1)) + while (rxfifo_available(sc)) { + len = ttyread(&sc->tty, &ch); + if (len <= 0) { + /* + * If return value of read is 0, it means + * disconnected + */ + if (len == 0 && sc->tty.connect_based) + uart_tcp_disconnect(sc); + break; + } + rxfifo_putchar(sc, ch); + } } } @@ -196,7 +225,11 @@ if (loopback) { return (rxfifo_putchar(sc, ch)); } else if (sc->tty.opened) { - ttywrite(&sc->tty, ch); + /* + * We the return value of write is -1, it means disconnected + */ + if (ttywrite(&sc->tty, ch) == -1 && sc->tty.connect_based) + uart_tcp_disconnect(sc); return (0); } else { /* Drop on the floor. */ @@ -259,6 +292,57 @@ } #endif +// Listen to the tcp port, wait connection, then accept it +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_connect_infos *connect_infos = (struct uart_connect_infos *) + arg; + struct uart_softc *sc = connect_infos->softc; + 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; + ttyopen(&sc->tty); + sc->mev = mevent_add(sc->tty.rfd, EVF_READ, + connect_infos->drain, connect_infos->arg); + } + + pthread_mutex_unlock(&sc->mtx); +} + +// When connected based protocal disconnected, we use this handler to clean it +// up +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; +} + static int uart_stdio_backend(struct uart_softc *sc) { @@ -324,6 +408,91 @@ return (0); } +// 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, + void (*drain)(int, enum ev_type, void *), void *arg) +{ +#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; + struct uart_connect_infos *connect_infos; + + 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); + return (-1); + } + + // Set connect infos + if ((connect_infos = calloc(sizeof(struct uart_connect_infos), 1)) == + NULL) { + return (-1); + } + + connect_infos->softc = sc; + connect_infos->drain = drain; + connect_infos->arg = arg; + + if ((sc->mev = mevent_add(bind_fd, EVF_READ, uart_tcp_listener, + connect_infos)) == NULL) { + free(connect_infos); + 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); +} + struct uart_softc * uart_init(void) { @@ -344,9 +513,17 @@ if (strcmp("stdio", path) == 0) retval = uart_stdio_backend(sc); - else + else if (strncmp("tcp", path, 3) == 0) { + retval = uart_tcp_backend(sc, path, drain, arg); + sc->tty.connect_based = true; + } else retval = uart_tty_backend(sc, path); - if (retval == 0) { + + /* + * Connected based protocal should wait for connection + * Thus it can listen nothing when initialize + */ + if (retval == 0 && !sc->tty.connect_based) { ttyopen(&sc->tty); sc->mev = mevent_add(sc->tty.rfd, EVF_READ, drain, arg); assert(sc->mev != NULL);