* [Qemu-devel] [PATCH] chardev: Add RFC2217 support for telnet client mode
@ 2019-09-17 21:32 Sylvain Munaut
2019-09-19 8:38 ` Marc-André Lureau
0 siblings, 1 reply; 2+ messages in thread
From: Sylvain Munaut @ 2019-09-17 21:32 UTC (permalink / raw)
To: Marc-André Lureau, Paolo Bonzini, qemu-devel; +Cc: Sylvain Munaut
This allow remote control of the baudrate and other comms
parameters.
Signed-off-by: Sylvain Munaut <tnt@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);
+ 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;
+ } 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;
+ }
}
} 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;
+ }
+ if (s->state != TCP_CHARDEV_STATE_CONNECTED) {
+ return 0;
+ }
+
+ 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;
}
#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) {
@@ -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
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [Qemu-devel] [PATCH] chardev: Add RFC2217 support for telnet client mode
2019-09-17 21:32 [Qemu-devel] [PATCH] chardev: Add RFC2217 support for telnet client mode Sylvain Munaut
@ 2019-09-19 8:38 ` Marc-André Lureau
0 siblings, 0 replies; 2+ messages in thread
From: Marc-André Lureau @ 2019-09-19 8:38 UTC (permalink / raw)
To: Sylvain Munaut; +Cc: Paolo Bonzini, QEMU
Hi Sylvain
On Wed, Sep 18, 2019 at 5:05 AM Sylvain Munaut <tnt@246tnt.com> wrote:
>
> This allow remote control of the baudrate and other comms
> parameters.
>
> Signed-off-by: Sylvain Munaut <tnt@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
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2019-09-19 8:55 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-09-17 21:32 [Qemu-devel] [PATCH] chardev: Add RFC2217 support for telnet client mode Sylvain Munaut
2019-09-19 8:38 ` Marc-André Lureau
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).