Hi Sylvain On Wed, Sep 18, 2019 at 5:05 AM Sylvain Munaut <t...@246tnt.com> wrote: > > This allow remote control of the baudrate and other comms > parameters. > > Signed-off-by: Sylvain Munaut <t...@246tnt.com> > --- > chardev/char-socket.c | 232 ++++++++++++++++++++++++++++++++++-------- > chardev/char.c | 6 ++ > qapi/char.json | 3 + > 3 files changed, 201 insertions(+), 40 deletions(-) > > diff --git a/chardev/char-socket.c b/chardev/char-socket.c > index 185fe38dda..47e04357a0 100644 > --- a/chardev/char-socket.c > +++ b/chardev/char-socket.c > @@ -36,6 +36,7 @@ > #include "qapi/qapi-visit-sockets.h" > > #include "chardev/char-io.h" > +#include "chardev/char-serial.h" > > /***********************************************************/ > /* TCP Net console */ > @@ -74,6 +75,7 @@ typedef struct { > bool is_listen; > bool is_telnet; > bool is_tn3270; > + bool is_rfc2217; > GSource *telnet_source; > TCPChardevTelnetInit *telnet_init; > > @@ -152,8 +154,8 @@ static void tcp_chr_accept(QIONetListener *listener, > static int tcp_chr_read_poll(void *opaque); > static void tcp_chr_disconnect_locked(Chardev *chr); > > -/* Called with chr_write_lock held. */ > -static int tcp_chr_write(Chardev *chr, const uint8_t *buf, int len) > +/* Must be called with chr_write_lock held. */ > +static int tcp_chr_write_raw(Chardev *chr, const uint8_t *buf, int len) > { > SocketChardev *s = SOCKET_CHARDEV(chr); > > @@ -186,6 +188,45 @@ static int tcp_chr_write(Chardev *chr, const uint8_t > *buf, int len) > } > } > > +/* Must be called with chr_write_lock held. */ > +static int tcp_chr_write_telnet(Chardev *chr, const uint8_t *buf, int len) > +{ > + const uint8_t buf_esc[] = { IAC, IAC }; > + int wlen = 0; > + > + /* Send chunks between IAC bytes */ > + while (len) { > + uint8_t *end = memchr(buf, IAC, len); > + int clen = end ? (end - buf) : len; > + > + if (clen) { > + wlen += tcp_chr_write_raw(chr, buf, clen); > + buf += clen; > + len -= clen; > + } > + > + if (end) { > + wlen += tcp_chr_write_raw(chr, buf_esc, 2);
Can you enlight me, why do you need to double the IAC bytes? > + buf += 1; > + len -= 1; > + } > + } > + > + return wlen; > +} > + > +/* Called with chr_write_lock held. */ > +static int tcp_chr_write(Chardev *chr, const uint8_t *buf, int len) > +{ > + SocketChardev *s = SOCKET_CHARDEV(chr); > + > + if (s->do_telnetopt) { > + return tcp_chr_write_telnet(chr, buf, len); > + } else { > + return tcp_chr_write_raw(chr, buf, len); > + } > +} > + > static int tcp_chr_read_poll(void *opaque) > { > Chardev *chr = CHARDEV(opaque); > @@ -222,37 +263,74 @@ static void tcp_chr_process_IAC_bytes(Chardev *chr, > int j = 0; > > for (i = 0; i < *size; i++) { > - if (s->do_telnetopt > 1) { > - if ((unsigned char)buf[i] == IAC && s->do_telnetopt == 2) { > + if (s->do_telnetopt == 2) { > + /* Generic options */ > + if ((unsigned char)buf[i] == IAC) { > /* Double IAC means send an IAC */ > if (j != i) { > buf[j] = buf[i]; > } > j++; > s->do_telnetopt = 1; > - } else { > - if ((unsigned char)buf[i] == IAC_BREAK > - && s->do_telnetopt == 2) { > - /* Handle IAC break commands by sending a serial break */ > - qemu_chr_be_event(chr, CHR_EVENT_BREAK); > - s->do_telnetopt++; > - } else if (s->is_tn3270 && ((unsigned char)buf[i] == IAC_EOR > - || (unsigned char)buf[i] == IAC_SB > - || (unsigned char)buf[i] == IAC_SE) > - && s->do_telnetopt == 2) { > + } else if ((unsigned char)buf[i] == IAC_BREAK) { > + /* Handle IAC break commands by sending a serial break */ > + qemu_chr_be_event(chr, CHR_EVENT_BREAK); > + s->do_telnetopt = 1; > + } else if (s->is_tn3270) { > + /* TN3270 specific */ > + if ((unsigned char)buf[i] == IAC_EOR > + || (unsigned char)buf[i] == IAC_SB > + || (unsigned char)buf[i] == IAC_SE) { > buf[j++] = IAC; > buf[j++] = buf[i]; > - s->do_telnetopt++; > - } else if (s->is_tn3270 && ((unsigned char)buf[i] == IAC_IP > - || (unsigned char)buf[i] == IAC_NOP) > - && s->do_telnetopt == 2) { > + s->do_telnetopt = 1; > + } else if ((unsigned char)buf[i] == IAC_IP > + || (unsigned char)buf[i] == IAC_NOP) { > /* TODO: IP and NOP need to be implemented later. */ > - s->do_telnetopt++; > + s->do_telnetopt = 1; > + } > + } else if (s->is_rfc2217) { > + /* RFC2217 specific */ > + if ((unsigned char)buf[i] == IAC_SE) { > + /* Shouldn't happen but ... */ > + s->do_telnetopt = 1; > + } else if ((unsigned char)buf[i] == IAC_SB) { > + s->do_telnetopt = 50; > } > - s->do_telnetopt++; > } > - if (s->do_telnetopt >= 4) { > - s->do_telnetopt = 1; You changed quite fundamentally the logic of the function. Before, after IAC byte (do_telnet = 2), if not other branch handle the following byte, 2 bytes were dropped. Why that change? Could you try to make this a seperate patch? > + } else if (s->do_telnetopt > 2) { > + if (s->is_rfc2217) { > + if (s->do_telnetopt > 100) { /* Skip mode */ > + s->do_telnetopt--; > + } else if (s->do_telnetopt == 50) { /* Post-SB */ > + if ((unsigned char)buf[i] == 0x2c) { > + /* This is a COM-Port-Option, look at next byte */ > + s->do_telnetopt = 51; > + } else { > + /* > + * Unknown option, just skip 1 and wait for IAC SE > and > + * hope it doesn't happen in the option stream > + */ > + s->do_telnetopt = 101; > + } > + } else if (s->do_telnetopt == 51) { /* SB Options */ > + /* > + * Skip 4 next bytes if this is baudrate option, > + * else skip 1 byte > + */ > + s->do_telnetopt = (buf[i] == 0x65) ? 104 : 101; > + } else if (s->do_telnetopt == 100) { /* Wait for IAC */ > + if ((unsigned char)buf[i] == IAC) { > + s->do_telnetopt = 99; > + } > + } else if (s->do_telnetopt == 99) { /* Wait for SE */ > + if ((unsigned char)buf[i] == IAC_SE) > + s->do_telnetopt = 1; > + else if ((unsigned char)buf[i] == IAC) > + s->do_telnetopt = 99; > + else > + s->do_telnetopt = 100; Must be braced to conform to qemu coding style. Overall, looks ok to me, but the lack of test is annoying. Could you write some in tests/test-char.c? > + } > } > } else { > if ((unsigned char)buf[i] == IAC) { > @@ -558,6 +636,57 @@ static int tcp_chr_sync_read(Chardev *chr, const uint8_t > *buf, int len) > return size; > } > > +static int tcp_chr_ioctl(Chardev *chr, int cmd, void *arg) > +{ > + SocketChardev *s = SOCKET_CHARDEV(chr); > + > + if (s->is_rfc2217 == 0) { > + return -ENOTSUP; > + I have a preference for "if (!s->is_rfc2217)" > + if (s->state != TCP_CHARDEV_STATE_CONNECTED) { > + return 0; > + } You could drop that check, since tcp_chr_write_raw() handles it too. > + > + switch (cmd) { > + case CHR_IOCTL_SERIAL_SET_PARAMS: > + { > + QEMUSerialSetParams *ssp = arg; > + const uint8_t buf[] = { > + /* IAC SB COM-PORT-OPTION SET-BAUD <value(4)> IAC SE */ > + 0xff, 0xfa, 0x2c, 0x01, > + (ssp->speed >> 24) & 0xff, > + (ssp->speed >> 16) & 0xff, > + (ssp->speed >> 8) & 0xff, > + (ssp->speed >> 0) & 0xff, > + 0xff, 0xf0, > + > + /* IAC SB COM-PORT-OPTION SET-DATASIZE <value> IAC SE */ > + 0xff, 0xfa, 0x2c, 0x02, > + ssp->data_bits, > + 0xff, 0xf0, > + > + /* IAC SB COM-PORT-OPTION SET-PARITY <value> IAC SE */ > + 0xff, 0xfa, 0x2c, 0x03, > + (ssp->parity == 'O') ? 2 : (ssp->parity == 'E' ? 3 : 1), > + 0xff, 0xf0, > + > + /* IAC SB COM-PORT-OPTION SET-STOPSIZE <value> IAC SE */ > + 0xff, 0xfa, 0x2c, 0x04, > + ssp->stop_bits, > + 0xff, 0xf0, > + }; > + > + qemu_mutex_lock(&chr->chr_write_lock); > + tcp_chr_write_raw(chr, buf, sizeof(buf)); > + qemu_mutex_unlock(&chr->chr_write_lock); > + } > + break; > + default: > + return -ENOTSUP; > + } > + return 0; > +} > + > static char *qemu_chr_compute_filename(SocketChardev *s) > { > struct sockaddr_storage *ss = &s->sioc->localAddr; > @@ -722,16 +851,7 @@ static void tcp_chr_telnet_init(Chardev *chr) > x[n++] = c; \ > } while (0) > > - if (!s->is_tn3270) { > - init->buflen = 12; > - /* Prep the telnet negotion to put telnet in binary, > - * no echo, single char mode */ > - IACSET(init->buf, 0xff, 0xfb, 0x01); /* IAC WILL ECHO */ > - IACSET(init->buf, 0xff, 0xfb, 0x03); /* IAC WILL Suppress go ahead > */ > - IACSET(init->buf, 0xff, 0xfb, 0x00); /* IAC WILL Binary */ > - IACSET(init->buf, 0xff, 0xfd, 0x00); /* IAC DO Binary */ > - } else { > - init->buflen = 21; > + if (s->is_tn3270) { > /* Prep the TN3270 negotion based on RFC1576 */ > IACSET(init->buf, 0xff, 0xfd, 0x19); /* IAC DO EOR */ > IACSET(init->buf, 0xff, 0xfb, 0x19); /* IAC WILL EOR */ > @@ -740,6 +860,30 @@ static void tcp_chr_telnet_init(Chardev *chr) > IACSET(init->buf, 0xff, 0xfd, 0x18); /* IAC DO TERMINAL TYPE */ > IACSET(init->buf, 0xff, 0xfa, 0x18); /* IAC SB TERMINAL TYPE */ > IACSET(init->buf, 0x01, 0xff, 0xf0); /* SEND IAC SE */ > + init->buflen = n; > + } else if (s->is_rfc2217) { > + /* > + * Prep the telnet negotion to put telnet in binary, > + * no echo, single char mode with COM port options > + */ > + IACSET(init->buf, 0xff, 0xfb, 0x00); /* IAC WILL Binary */ > + IACSET(init->buf, 0xff, 0xfd, 0x00); /* IAC DO Binary */ > + IACSET(init->buf, 0xff, 0xfc, 0x01); /* IAC WON'T ECHO */ > + IACSET(init->buf, 0xff, 0xfe, 0x01); /* IAC DON'T ECHO */ > + IACSET(init->buf, 0xff, 0xfb, 0x03); /* IAC WILL Suppress go ahead > */ > + IACSET(init->buf, 0xff, 0xfd, 0x03); /* IAC DO Suppress go ahead */ > + IACSET(init->buf, 0xff, 0xfb, 0x2c); /* IAC WILL COM-Port-Option */ > + init->buflen = n; > + } else { > + /* > + * Prep the telnet negotion to put telnet in binary, > + * no echo, single char mode > + */ > + IACSET(init->buf, 0xff, 0xfb, 0x01); /* IAC WILL ECHO */ > + IACSET(init->buf, 0xff, 0xfb, 0x03); /* IAC WILL Suppress go ahead > */ > + IACSET(init->buf, 0xff, 0xfb, 0x00); /* IAC WILL Binary */ > + IACSET(init->buf, 0xff, 0xfd, 0x00); /* IAC DO Binary */ > + init->buflen = n; > } Preliminary code move with buflen code change, could be done in a seperate patch. > > #undef IACSET > @@ -964,8 +1108,12 @@ static void tcp_chr_accept_server_sync(Chardev *chr) > static int tcp_chr_wait_connected(Chardev *chr, Error **errp) > { > SocketChardev *s = SOCKET_CHARDEV(chr); > - const char *opts[] = { "telnet", "tn3270", "websock", "tls-creds" }; > - bool optset[] = { s->is_telnet, s->is_tn3270, s->is_websock, > s->tls_creds }; > + const char *opts[] = { > + "telnet", "tn3270", "rfc2217", "websock", "tls-creds" > + }; > + bool optset[] = { > + s->is_telnet, s->is_tn3270, s->is_rfc2217, s->is_websock, > s->tls_creds > + }; > size_t i; > > QEMU_BUILD_BUG_ON(G_N_ELEMENTS(opts) != G_N_ELEMENTS(optset)); > @@ -1155,15 +1303,11 @@ static gboolean socket_reconnect_timeout(gpointer > opaque) > > > static int qmp_chardev_open_socket_server(Chardev *chr, > - bool is_telnet, > bool is_waitconnect, > Error **errp) > { > SocketChardev *s = SOCKET_CHARDEV(chr); > char *name; > - if (is_telnet) { > - s->do_telnetopt = 1; > - } > s->listener = qio_net_listener_new(); > > name = g_strdup_printf("chardev-tcp-listener-%s", chr->label); > @@ -1300,6 +1444,7 @@ static void qmp_chardev_open_socket(Chardev *chr, > bool is_listen = sock->has_server ? sock->server : true; > bool is_telnet = sock->has_telnet ? sock->telnet : false; > bool is_tn3270 = sock->has_tn3270 ? sock->tn3270 : false; > + bool is_rfc2217 = sock->has_rfc2217 ? sock->rfc2217 : false; > bool is_waitconnect = sock->has_wait ? sock->wait : false; > bool is_websock = sock->has_websocket ? sock->websocket : false; > int64_t reconnect = sock->has_reconnect ? sock->reconnect : 0; > @@ -1308,6 +1453,7 @@ static void qmp_chardev_open_socket(Chardev *chr, > s->is_listen = is_listen; > s->is_telnet = is_telnet; > s->is_tn3270 = is_tn3270; > + s->is_rfc2217 = is_rfc2217; > s->is_websock = is_websock; > s->do_nodelay = do_nodelay; > if (sock->tls_creds) { I think this deserves some checks in qmp_chardev_validate_socket(). It seems we are lacking them for telnet/tn3270, it's probably not too late to add them. Feel free to do that in a separate patch too. > @@ -1361,9 +1507,12 @@ static void qmp_chardev_open_socket(Chardev *chr, > > update_disconnected_filename(s); > > + if (s->is_listen ? (is_telnet || is_tn3270) : is_rfc2217) { > + s->do_telnetopt = 1; > + } > + > if (s->is_listen) { > - if (qmp_chardev_open_socket_server(chr, is_telnet || is_tn3270, > - is_waitconnect, errp) < 0) { > + if (qmp_chardev_open_socket_server(chr, is_waitconnect, errp) < 0) { > return; > } > } else { > @@ -1410,6 +1559,8 @@ static void qemu_chr_parse_socket(QemuOpts *opts, > ChardevBackend *backend, > sock->telnet = qemu_opt_get_bool(opts, "telnet", false); > sock->has_tn3270 = qemu_opt_get(opts, "tn3270"); > sock->tn3270 = qemu_opt_get_bool(opts, "tn3270", false); > + sock->has_rfc2217 = qemu_opt_get(opts, "rfc2217"); > + sock->rfc2217 = qemu_opt_get_bool(opts, "rfc2217", false); > sock->has_websocket = qemu_opt_get(opts, "websocket"); > sock->websocket = qemu_opt_get_bool(opts, "websocket", false); > /* > @@ -1480,6 +1631,7 @@ static void char_socket_class_init(ObjectClass *oc, > void *data) > cc->chr_wait_connected = tcp_chr_wait_connected; > cc->chr_write = tcp_chr_write; > cc->chr_sync_read = tcp_chr_sync_read; > + cc->chr_ioctl = tcp_chr_ioctl; > cc->chr_disconnect = tcp_chr_disconnect; > cc->get_msgfds = tcp_get_msgfds; > cc->set_msgfds = tcp_set_msgfds; > diff --git a/chardev/char.c b/chardev/char.c > index 7b6b2cb123..b101641784 100644 > --- a/chardev/char.c > +++ b/chardev/char.c > @@ -423,6 +423,7 @@ QemuOpts *qemu_chr_parse_compat(const char *label, const > char *filename, > if (strstart(filename, "tcp:", &p) || > strstart(filename, "telnet:", &p) || > strstart(filename, "tn3270:", &p) || > + strstart(filename, "rfc2217:", &p) || > strstart(filename, "websocket:", &p)) { > if (sscanf(p, "%64[^:]:%32[^,]%n", host, port, &pos) < 2) { > host[0] = 0; > @@ -443,6 +444,8 @@ QemuOpts *qemu_chr_parse_compat(const char *label, const > char *filename, > qemu_opt_set(opts, "telnet", "on", &error_abort); > } else if (strstart(filename, "tn3270:", &p)) { > qemu_opt_set(opts, "tn3270", "on", &error_abort); > + } else if (strstart(filename, "rfc2217:", &p)) { > + qemu_opt_set(opts, "rfc2217", "on", &error_abort); > } else if (strstart(filename, "websocket:", &p)) { > qemu_opt_set(opts, "websocket", "on", &error_abort); > } > @@ -879,6 +882,9 @@ QemuOptsList qemu_chardev_opts = { > },{ > .name = "tn3270", > .type = QEMU_OPT_BOOL, > + },{ > + .name = "rfc2217", > + .type = QEMU_OPT_BOOL, > },{ > .name = "tls-creds", > .type = QEMU_OPT_STRING, > diff --git a/qapi/char.json b/qapi/char.json > index a6e81ac7bc..4a6b50bc3e 100644 > --- a/qapi/char.json > +++ b/qapi/char.json > @@ -261,6 +261,8 @@ > # sockets (default: false) > # @tn3270: enable tn3270 protocol on server > # sockets (default: false) (Since: 2.10) > +# @rfc2217: enable RFC2217 protocol on client > +# sockets (default: false) (Since: ???) > # @websocket: enable websocket protocol on server > # sockets (default: false) (Since: 3.1) > # @reconnect: For a client socket, if a socket is disconnected, > @@ -279,6 +281,7 @@ > '*nodelay': 'bool', > '*telnet': 'bool', > '*tn3270': 'bool', > + '*rfc2217': 'bool', > '*websocket': 'bool', > '*reconnect': 'int' }, > 'base': 'ChardevCommon' } > -- > 2.21.0 > > thanks -- Marc-André Lureau