[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH] chardev: Add RFC2217 support for telnet client mode
From: |
Sylvain Munaut |
Subject: |
[Qemu-devel] [PATCH] chardev: Add RFC2217 support for telnet client mode |
Date: |
Tue, 17 Sep 2019 23:32:41 +0200 |
This allow remote control of the baudrate and other comms
parameters.
Signed-off-by: Sylvain Munaut <address@hidden>
---
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
- [Qemu-devel] [PATCH] chardev: Add RFC2217 support for telnet client mode,
Sylvain Munaut <=