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,10 @@ */ #include +#include #include +#include +#include #ifndef WITHOUT_CAPSICUM #include #include @@ -104,7 +107,8 @@ struct ttyfd { bool opened; - int rfd; /* fd for reading */ + bool connect_based; + int rfd; /* fd for reading */ int wfd; /* fd for writing, may be == rfd */ }; @@ -134,6 +138,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) @@ -163,18 +168,24 @@ ttyread(struct ttyfd *tf) { unsigned char rb; + int sz; - if (read(tf->rfd, &rb, 1) == 1) + sz = read(tf->rfd, &rb, 1); + + if (sz == 1) return (rb); - else - return (-1); + + if (sz == 0 && tf->connect_based) + errno = ENOTCONN; + + return (-1); } -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 @@ -389,15 +400,26 @@ pthread_mutex_lock(&sc->mtx); if ((sc->mcr & MCR_LOOPBACK) != 0) { - (void) ttyread(&sc->tty); + if (ttyread(&sc->tty) == -1 && sc->tty.connect_based && + errno != EAGAIN) + uart_tcp_disconnect(sc); } else { - while (rxfifo_available(sc) && - ((ch = ttyread(&sc->tty)) != -1)) { - rxfifo_putchar(sc, ch); + while (rxfifo_available(sc)) { + ch = ttyread(&sc->tty); + if (ch == -1) { + if (sc->tty.connect_based && errno != EAGAIN) { + uart_tcp_disconnect(sc); + goto clear; + } + break; + } else { + rxfifo_putchar(sc, ch); + } } uart_toggle_intr(sc); } +clear: pthread_mutex_unlock(&sc->mtx); } @@ -430,7 +452,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 +729,100 @@ 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; + } + assert(fcntl(conn_fd, F_SETFL, O_NONBLOCK) == 0); + + 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; + struct sockaddr_in src_addr; + char addr[256]; + int port; + + sscanf(path, "tcp:%255[^:]:%d", addr, &port); + + bind_fd = socket(PF_INET, SOCK_STREAM, 0); + if (bind_fd < 0) + return (-1); + + memset(&src_addr, 0, sizeof(src_addr)); + src_addr.sin_family = AF_INET; + src_addr.sin_port = htons(port); + inet_aton(addr, &src_addr.sin_addr); + + if (bind(bind_fd, (struct sockaddr *)&src_addr, + (socklen_t)sizeof(src_addr)) == -1) + errx(EX_OSERR, "Unable to bind to address %s:%d", addr, port); + + 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:%d", 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 +833,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);