#include "qemu/osdep.h"
#include "chardev/char.h"
#include "io/channel-socket.h"
-#include "io/channel-tls.h"
#include "io/channel-websock.h"
-#include "io/net-listener.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "qemu/option.h"
#include "qapi/error.h"
#include "qapi/clone-visitor.h"
#include "qapi/qapi-visit-sockets.h"
+#include "qemu/yank.h"
#include "chardev/char-io.h"
-
-/***********************************************************/
-/* TCP Net console */
-
-#define TCP_MAX_FDS 16
-
-typedef struct {
- char buf[21];
- size_t buflen;
-} TCPChardevTelnetInit;
-
-typedef enum {
- TCP_CHARDEV_STATE_DISCONNECTED,
- TCP_CHARDEV_STATE_CONNECTING,
- TCP_CHARDEV_STATE_CONNECTED,
-} TCPChardevState;
-
-typedef struct {
- Chardev parent;
- QIOChannel *ioc; /* Client I/O channel */
- QIOChannelSocket *sioc; /* Client master channel */
- QIONetListener *listener;
- GSource *hup_source;
- QCryptoTLSCreds *tls_creds;
- char *tls_authz;
- TCPChardevState state;
- int max_size;
- int do_telnetopt;
- int do_nodelay;
- int *read_msgfds;
- size_t read_msgfds_num;
- int *write_msgfds;
- size_t write_msgfds_num;
-
- SocketAddress *addr;
- bool is_listen;
- bool is_telnet;
- bool is_tn3270;
- GSource *telnet_source;
- TCPChardevTelnetInit *telnet_init;
-
- bool is_websock;
-
- GSource *reconnect_timer;
- int64_t reconnect_time;
- bool connect_err_reported;
-
- QIOTask *connect_task;
-} SocketChardev;
-
-#define SOCKET_CHARDEV(obj) \
- OBJECT_CHECK(SocketChardev, (obj), TYPE_CHARDEV_SOCKET)
+#include "chardev/char-socket.h"
static gboolean socket_reconnect_timeout(gpointer opaque);
static void tcp_chr_telnet_init(Chardev *chr);
NULL);
}
- if (ret == QIO_CHANNEL_ERR_BLOCK) {
- errno = EAGAIN;
- ret = -1;
- } else if (ret == -1) {
- errno = EIO;
- }
-
if (msgfds_num) {
/* close and clean read_msgfds */
for (i = 0; i < s->read_msgfds_num; i++) {
}
/* O_NONBLOCK is preserved across SCM_RIGHTS so reset it */
- qemu_set_block(fd);
+ qemu_socket_set_block(fd);
#ifndef MSG_CMSG_CLOEXEC
qemu_set_cloexec(fd);
#endif
}
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ errno = EAGAIN;
+ ret = -1;
+ } else if (ret == -1) {
+ errno = EIO;
+ }
+
return ret;
}
static GSource *tcp_chr_add_watch(Chardev *chr, GIOCondition cond)
{
SocketChardev *s = SOCKET_CHARDEV(chr);
+ if (!s->ioc) {
+ return NULL;
+ }
return qio_channel_create_watch(s->ioc, cond);
}
}
}
+static void char_socket_yank_iochannel(void *opaque)
+{
+ QIOChannel *ioc = QIO_CHANNEL(opaque);
+
+ qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
+}
+
static void tcp_chr_free_connection(Chardev *chr)
{
SocketChardev *s = SOCKET_CHARDEV(chr);
tcp_set_msgfds(chr, NULL, 0);
remove_fd_in_watch(chr);
+ if (s->registered_yank &&
+ (s->state == TCP_CHARDEV_STATE_CONNECTING
+ || s->state == TCP_CHARDEV_STATE_CONNECTED)) {
+ yank_unregister_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(s->sioc));
+ }
object_unref(OBJECT(s->sioc));
s->sioc = NULL;
object_unref(OBJECT(s->ioc));
qemu_chr_socket_protocol(s),
s->addr->u.inet.host,
s->addr->u.inet.port,
- s->is_listen ? ",server" : "");
+ s->is_listen ? ",server=on" : "");
break;
case SOCKET_ADDRESS_TYPE_UNIX:
- return g_strdup_printf("%sunix:%s%s", prefix,
- s->addr->u.q_unix.path,
- s->is_listen ? ",server" : "");
+ {
+ const char *tight = "", *abstract = "";
+ UnixSocketAddress *sa = &s->addr->u.q_unix;
+
+#ifdef CONFIG_LINUX
+ if (sa->has_abstract && sa->abstract) {
+ abstract = ",abstract=on";
+ if (sa->has_tight && sa->tight) {
+ tight = ",tight=on";
+ }
+ }
+#endif
+
+ return g_strdup_printf("%sunix:%s%s%s%s", prefix, sa->path,
+ abstract, tight,
+ s->is_listen ? ",server=on" : "");
break;
+ }
case SOCKET_ADDRESS_TYPE_FD:
return g_strdup_printf("%sfd:%s%s", prefix, s->addr->u.fd.str,
- s->is_listen ? ",server" : "");
+ s->is_listen ? ",server=on" : "");
break;
case SOCKET_ADDRESS_TYPE_VSOCK:
return g_strdup_printf("%svsock:%s:%s", prefix,
if (emit_close) {
qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
}
- if (s->reconnect_time) {
+ if (s->reconnect_time && !s->reconnect_timer) {
qemu_chr_socket_restart_timer(chr);
}
}
{
SocketChardev *s = SOCKET_CHARDEV(chr);
int size;
+ int saved_errno;
if (s->state != TCP_CHARDEV_STATE_CONNECTED) {
return 0;
qio_channel_set_blocking(s->ioc, true, NULL);
size = tcp_chr_recv(chr, (void *) buf, len);
+ saved_errno = errno;
if (s->state != TCP_CHARDEV_STATE_DISCONNECTED) {
qio_channel_set_blocking(s->ioc, false, NULL);
}
tcp_chr_disconnect(chr);
}
+ errno = saved_errno;
return size;
}
case AF_UNIX:
return g_strdup_printf("unix:%s%s",
((struct sockaddr_un *)(ss))->sun_path,
- s->is_listen ? ",server" : "");
+ s->is_listen ? ",server=on" : "");
#endif
case AF_INET6:
left = "[";
return g_strdup_printf("%s:%s%s%s:%s%s <-> %s%s%s:%s",
qemu_chr_socket_protocol(s),
left, shost, right, sserv,
- s->is_listen ? ",server" : "",
+ s->is_listen ? ",server=on" : "",
left, phost, right, pserv);
default:
}
tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING);
tcp_chr_set_client_ioc_name(chr, sioc);
+ if (s->registered_yank) {
+ yank_register_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(sioc));
+ }
ret = tcp_chr_new_client(chr, sioc);
object_unref(OBJECT(sioc));
return ret;
tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING);
tcp_chr_set_client_ioc_name(chr, cioc);
+ if (s->registered_yank) {
+ yank_register_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(cioc));
+ }
tcp_chr_new_client(chr, cioc);
}
object_unref(OBJECT(sioc));
return -1;
}
+ if (s->registered_yank) {
+ yank_register_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(sioc));
+ }
tcp_chr_new_client(chr, sioc);
object_unref(OBJECT(sioc));
return 0;
tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING);
sioc = qio_net_listener_wait_client(s->listener);
tcp_chr_set_client_ioc_name(chr, sioc);
+ if (s->registered_yank) {
+ yank_register_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(sioc));
+ }
tcp_chr_new_client(chr, sioc);
object_unref(OBJECT(sioc));
}
object_unref(OBJECT(s->tls_creds));
}
g_free(s->tls_authz);
+ if (s->registered_yank) {
+ /*
+ * In the chardev-change special-case, we shouldn't unregister the yank
+ * instance, as it still may be needed.
+ */
+ if (!chr->handover_yank_instance) {
+ yank_unregister_instance(CHARDEV_YANK_INSTANCE(chr->label));
+ }
+ }
qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
}
if (qio_task_propagate_error(task, &err)) {
tcp_chr_change_state(s, TCP_CHARDEV_STATE_DISCONNECTED);
+ if (s->registered_yank) {
+ yank_unregister_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(sioc));
+ }
check_report_connect_error(chr, err);
goto cleanup;
}
tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING);
sioc = qio_channel_socket_new();
tcp_chr_set_client_ioc_name(chr, sioc);
+ if (s->registered_yank) {
+ yank_register_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(sioc));
+ }
/*
* Normally code would use the qio_channel_socket_connect_async
* method which uses a QIOTask + qio_task_set_error internally
*/
s->connect_task = qio_task_new(OBJECT(sioc),
qemu_chr_socket_connected,
- chr, NULL);
+ object_ref(OBJECT(chr)),
+ (GDestroyNotify)object_unref);
qio_task_run_in_thread(s->connect_task,
tcp_chr_connect_client_task,
s->addr,
qio_net_listener_set_name(s->listener, name);
g_free(name);
+ if (s->addr->type == SOCKET_ADDRESS_TYPE_FD && !*s->addr->u.fd.str) {
+ goto skip_listen;
+ }
+
if (qio_net_listener_open_sync(s->listener, s->addr, 1, errp) < 0) {
object_unref(OBJECT(s->listener));
s->listener = NULL;
qapi_free_SocketAddress(s->addr);
s->addr = socket_local_address(s->listener->sioc[0]->fd, errp);
+
+skip_listen:
update_disconnected_filename(s);
if (is_waitconnect) {
return false;
}
if (sock->has_wait) {
- warn_report("'wait' option is deprecated with "
- "socket in client connect mode");
- if (sock->wait) {
- error_setg(errp, "%s",
- "'wait' option is incompatible with "
- "socket in client connect mode");
- return false;
- }
+ error_setg(errp, "%s",
+ "'wait' option is incompatible with "
+ "socket in client connect mode");
+ return false;
}
}
return;
}
object_ref(OBJECT(s->tls_creds));
- if (is_listen) {
- if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
- error_setg(errp, "%s",
- "Expected TLS credentials for server endpoint");
- return;
- }
- } else {
- if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
- error_setg(errp, "%s",
- "Expected TLS credentials for client endpoint");
- return;
- }
+ if (!qcrypto_tls_creds_check_endpoint(s->tls_creds,
+ is_listen
+ ? QCRYPTO_TLS_CREDS_ENDPOINT_SERVER
+ : QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
+ errp)) {
+ return;
}
}
s->tls_authz = g_strdup(sock->tls_authz);
qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_FD_PASS);
}
+ /*
+ * In the chardev-change special-case, we shouldn't register a new yank
+ * instance, as there already may be one.
+ */
+ if (!chr->handover_yank_instance) {
+ if (!yank_register_instance(CHARDEV_YANK_INSTANCE(chr->label), errp)) {
+ return;
+ }
+ }
+ s->registered_yank = true;
+
/* be isn't opened until we get a connection */
*be_opened = false;
const char *host = qemu_opt_get(opts, "host");
const char *port = qemu_opt_get(opts, "port");
const char *fd = qemu_opt_get(opts, "fd");
+#ifdef CONFIG_LINUX
bool tight = qemu_opt_get_bool(opts, "tight", true);
bool abstract = qemu_opt_get_bool(opts, "abstract", false);
+#endif
SocketAddressLegacy *addr;
ChardevSocket *sock;
- if ((!!path + !!fd + !!host) != 1) {
+ if ((!!path + !!fd + !!host) > 1) {
error_setg(errp,
- "Exactly one of 'path', 'fd' or 'host' required");
+ "None or one of 'path', 'fd' or 'host' option required.");
return;
}
sock = backend->u.socket.data = g_new0(ChardevSocket, 1);
qemu_chr_parse_common(opts, qapi_ChardevSocket_base(sock));
- sock->has_nodelay = qemu_opt_get(opts, "delay");
- sock->nodelay = !qemu_opt_get_bool(opts, "delay", true);
+ if (qemu_opt_get(opts, "delay") && qemu_opt_get(opts, "nodelay")) {
+ error_setg(errp, "'delay' and 'nodelay' are mutually exclusive");
+ return;
+ }
+ sock->has_nodelay =
+ qemu_opt_get(opts, "delay") ||
+ qemu_opt_get(opts, "nodelay");
+ sock->nodelay =
+ !qemu_opt_get_bool(opts, "delay", true) ||
+ qemu_opt_get_bool(opts, "nodelay", false);
+
/*
* We have different default to QMP for 'server', hence
* we can't just check for existence of 'server'
addr = g_new0(SocketAddressLegacy, 1);
if (path) {
UnixSocketAddress *q_unix;
- addr->type = SOCKET_ADDRESS_LEGACY_KIND_UNIX;
+ addr->type = SOCKET_ADDRESS_TYPE_UNIX;
q_unix = addr->u.q_unix.data = g_new0(UnixSocketAddress, 1);
q_unix->path = g_strdup(path);
+#ifdef CONFIG_LINUX
+ q_unix->has_tight = true;
q_unix->tight = tight;
+ q_unix->has_abstract = true;
q_unix->abstract = abstract;
+#endif
} else if (host) {
- addr->type = SOCKET_ADDRESS_LEGACY_KIND_INET;
+ addr->type = SOCKET_ADDRESS_TYPE_INET;
addr->u.inet.data = g_new(InetSocketAddress, 1);
*addr->u.inet.data = (InetSocketAddress) {
.host = g_strdup(host),
.has_ipv6 = qemu_opt_get(opts, "ipv6"),
.ipv6 = qemu_opt_get_bool(opts, "ipv6", 0),
};
- } else if (fd) {
- addr->type = SOCKET_ADDRESS_LEGACY_KIND_FD;
+ } else {
+ addr->type = SOCKET_ADDRESS_TYPE_FD;
addr->u.fd.data = g_new(String, 1);
addr->u.fd.data->str = g_strdup(fd);
- } else {
- g_assert_not_reached();
}
sock->addr = addr;
}
{
ChardevClass *cc = CHARDEV_CLASS(oc);
+ cc->supports_yank = true;
+
cc->parse = qemu_chr_parse_socket;
cc->open = qmp_chardev_open_socket;
cc->chr_wait_connected = tcp_chr_wait_connected;