diff --git a/crypto/tlssession.c b/crypto/tlssession.c index 1e98f44e0d172fefa57e8d5f128b88f2f02eb5c7..ccd62acfb197d1d5ba7396c72dae2a32d37f5783 100644 --- a/crypto/tlssession.c +++ b/crypto/tlssession.c @@ -534,6 +534,40 @@ qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session) } } +int +qcrypto_tls_session_bye(QCryptoTLSSession *session, Error **errp) +{ + int ret; + + if (!session->handshakeComplete) { + return 0; + } + + ret = gnutls_bye(session->handle, GNUTLS_SHUT_WR); + + if (!ret) { + return QCRYPTO_TLS_BYE_COMPLETE; + } + + if (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN) { + int direction = gnutls_record_get_direction(session->handle); + return direction ? QCRYPTO_TLS_BYE_SENDING : QCRYPTO_TLS_BYE_RECVING; + } + + if (session->rerr || session->werr) { + error_setg(errp, "TLS termination failed: %s: %s", gnutls_strerror(ret), + error_get_pretty(session->rerr ? + session->rerr : session->werr)); + } else { + error_setg(errp, "TLS termination failed: %s", gnutls_strerror(ret)); + } + + error_free(session->rerr); + error_free(session->werr); + session->rerr = session->werr = NULL; + + return -1; +} int qcrypto_tls_session_get_key_size(QCryptoTLSSession *session, @@ -645,6 +679,13 @@ qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess) } +int +qcrypto_tls_session_bye(QCryptoTLSSession *session, Error **errp) +{ + return QCRYPTO_TLS_BYE_COMPLETE; +} + + int qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess, Error **errp) diff --git a/include/crypto/tlssession.h b/include/crypto/tlssession.h index 571049bd0ec05540d2311038072299f6205608b6..1768248b9e8a4518745d827fd62d228a4f66f129 100644 --- a/include/crypto/tlssession.h +++ b/include/crypto/tlssession.h @@ -302,6 +302,28 @@ typedef enum { QCryptoTLSSessionHandshakeStatus qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess); +typedef enum { + QCRYPTO_TLS_BYE_COMPLETE, + QCRYPTO_TLS_BYE_SENDING, + QCRYPTO_TLS_BYE_RECVING, +} QCryptoTLSSessionByeStatus; + +/** + * qcrypto_tls_session_bye: + * @session: the TLS session object + * @errp: pointer to a NULL-initialized error object + * + * Start, or continue, a TLS termination sequence. If the underlying + * data channel is non-blocking, then this method may return control + * before the termination is complete. The return value will indicate + * whether the termination has completed, or is waiting to send or + * receive data. In the latter cases, the caller should setup an event + * loop watch and call this method again once the underlying data + * channel is ready to read or write again. + */ +int +qcrypto_tls_session_bye(QCryptoTLSSession *session, Error **errp); + /** * qcrypto_tls_session_get_key_size: * @sess: the TLS session object diff --git a/include/io/channel-tls.h b/include/io/channel-tls.h index 26c67f17e2d364eeed9ce0a156b41aa7c210d1fc..7e9023570d89c3da4fe78ef5b8d379902f9dfd91 100644 --- a/include/io/channel-tls.h +++ b/include/io/channel-tls.h @@ -49,8 +49,20 @@ struct QIOChannelTLS { QCryptoTLSSession *session; QIOChannelShutdown shutdown; guint hs_ioc_tag; + guint bye_ioc_tag; }; +/** + * qio_channel_tls_bye: + * @ioc: the TLS channel object + * @errp: pointer to a NULL-initialized error object + * + * Perform the TLS session termination. This method will return + * immediately and the termination will continue in the background, + * provided the main loop is running. + */ +void qio_channel_tls_bye(QIOChannelTLS *ioc, Error **errp); + /** * qio_channel_tls_new_server: * @master: the underlying channel object diff --git a/include/io/channel-websock.h b/include/io/channel-websock.h index e180827c57f1b73db7282e6020fe672b5953dd9f..6700cf894602a884935126b6378bec6010e4b108 100644 --- a/include/io/channel-websock.h +++ b/include/io/channel-websock.h @@ -61,7 +61,8 @@ struct QIOChannelWebsock { size_t payload_remain; size_t pong_remain; QIOChannelWebsockMask mask; - guint io_tag; + guint hs_io_tag; /* tracking handshake task */ + guint io_tag; /* tracking watch task */ Error *io_err; gboolean io_eof; uint8_t opcode; diff --git a/io/channel-tls.c b/io/channel-tls.c index a8ad89c3d11053c44633c68d78f56d77854225dc..c81a57b60ca53dee0f1687c48a9f6b5c46e85c57 100644 --- a/io/channel-tls.c +++ b/io/channel-tls.c @@ -249,6 +249,85 @@ void qio_channel_tls_handshake(QIOChannelTLS *ioc, qio_channel_tls_handshake_task(ioc, task, context); } +static gboolean qio_channel_tls_bye_io(QIOChannel *ioc, GIOCondition condition, + gpointer user_data); + +static void qio_channel_tls_bye_task(QIOChannelTLS *ioc, QIOTask *task, + GMainContext *context) +{ + GIOCondition condition; + QIOChannelTLSData *data; + int status; + Error *err = NULL; + + status = qcrypto_tls_session_bye(ioc->session, &err); + + if (status < 0) { + trace_qio_channel_tls_bye_fail(ioc); + qio_task_set_error(task, err); + qio_task_complete(task); + return; + } + + if (status == QCRYPTO_TLS_BYE_COMPLETE) { + qio_task_complete(task); + return; + } + + data = g_new0(typeof(*data), 1); + data->task = task; + data->context = context; + + if (context) { + g_main_context_ref(context); + } + + if (status == QCRYPTO_TLS_BYE_SENDING) { + condition = G_IO_OUT; + } else { + condition = G_IO_IN; + } + + trace_qio_channel_tls_bye_pending(ioc, status); + ioc->bye_ioc_tag = qio_channel_add_watch_full(ioc->master, condition, + qio_channel_tls_bye_io, + data, NULL, context); +} + + +static gboolean qio_channel_tls_bye_io(QIOChannel *ioc, GIOCondition condition, + gpointer user_data) +{ + QIOChannelTLSData *data = user_data; + QIOTask *task = data->task; + GMainContext *context = data->context; + QIOChannelTLS *tioc = QIO_CHANNEL_TLS(qio_task_get_source(task)); + + tioc->bye_ioc_tag = 0; + g_free(data); + qio_channel_tls_bye_task(tioc, task, context); + + if (context) { + g_main_context_unref(context); + } + + return FALSE; +} + +static void propagate_error(QIOTask *task, gpointer opaque) +{ + qio_task_propagate_error(task, opaque); +} + +void qio_channel_tls_bye(QIOChannelTLS *ioc, Error **errp) +{ + QIOTask *task; + + task = qio_task_new(OBJECT(ioc), propagate_error, errp, NULL); + + trace_qio_channel_tls_bye_start(ioc); + qio_channel_tls_bye_task(ioc, task, NULL); +} static void qio_channel_tls_init(Object *obj G_GNUC_UNUSED) { @@ -259,6 +338,16 @@ static void qio_channel_tls_finalize(Object *obj) { QIOChannelTLS *ioc = QIO_CHANNEL_TLS(obj); + if (ioc->hs_ioc_tag) { + trace_qio_channel_tls_handshake_cancel(ioc); + g_clear_handle_id(&ioc->hs_ioc_tag, g_source_remove); + } + + if (ioc->bye_ioc_tag) { + trace_qio_channel_tls_bye_cancel(ioc); + g_clear_handle_id(&ioc->bye_ioc_tag, g_source_remove); + } + object_unref(OBJECT(ioc->master)); qcrypto_tls_session_free(ioc->session); } @@ -385,9 +474,15 @@ static int qio_channel_tls_close(QIOChannel *ioc, QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc); if (tioc->hs_ioc_tag) { + trace_qio_channel_tls_handshake_cancel(ioc); g_clear_handle_id(&tioc->hs_ioc_tag, g_source_remove); } + if (tioc->bye_ioc_tag) { + trace_qio_channel_tls_bye_cancel(ioc); + g_clear_handle_id(&tioc->bye_ioc_tag, g_source_remove); + } + return qio_channel_close(tioc->master, errp); } diff --git a/io/channel-websock.c b/io/channel-websock.c index de39f0d182d2610162c9e9a13c909b51e3d70518..583ea86187227f5648355c2ee5440da3956871fe 100644 --- a/io/channel-websock.c +++ b/io/channel-websock.c @@ -545,6 +545,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err)); qio_task_set_error(task, err); qio_task_complete(task); + wioc->hs_io_tag = 0; return FALSE; } @@ -560,6 +561,7 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc, trace_qio_channel_websock_handshake_complete(ioc); qio_task_complete(task); } + wioc->hs_io_tag = 0; return FALSE; } trace_qio_channel_websock_handshake_pending(ioc, G_IO_OUT); @@ -586,6 +588,7 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err)); qio_task_set_error(task, err); qio_task_complete(task); + wioc->hs_io_tag = 0; return FALSE; } if (ret == 0) { @@ -597,7 +600,7 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc, error_propagate(&wioc->io_err, err); trace_qio_channel_websock_handshake_reply(ioc); - qio_channel_add_watch( + wioc->hs_io_tag = qio_channel_add_watch( wioc->master, G_IO_OUT, qio_channel_websock_handshake_send, @@ -907,11 +910,12 @@ void qio_channel_websock_handshake(QIOChannelWebsock *ioc, trace_qio_channel_websock_handshake_start(ioc); trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN); - qio_channel_add_watch(ioc->master, - G_IO_IN, - qio_channel_websock_handshake_io, - task, - NULL); + ioc->hs_io_tag = qio_channel_add_watch( + ioc->master, + G_IO_IN, + qio_channel_websock_handshake_io, + task, + NULL); } @@ -922,13 +926,16 @@ static void qio_channel_websock_finalize(Object *obj) buffer_free(&ioc->encinput); buffer_free(&ioc->encoutput); buffer_free(&ioc->rawinput); - object_unref(OBJECT(ioc->master)); + if (ioc->hs_io_tag) { + g_source_remove(ioc->hs_io_tag); + } if (ioc->io_tag) { g_source_remove(ioc->io_tag); } if (ioc->io_err) { error_free(ioc->io_err); } + object_unref(OBJECT(ioc->master)); } @@ -1219,6 +1226,18 @@ static int qio_channel_websock_close(QIOChannel *ioc, QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc); trace_qio_channel_websock_close(ioc); + buffer_free(&wioc->encinput); + buffer_free(&wioc->encoutput); + buffer_free(&wioc->rawinput); + if (wioc->hs_io_tag) { + g_clear_handle_id(&wioc->hs_io_tag, g_source_remove); + } + if (wioc->io_tag) { + g_clear_handle_id(&wioc->io_tag, g_source_remove); + } + if (wioc->io_err) { + g_clear_pointer(&wioc->io_err, error_free); + } return qio_channel_close(wioc->master, errp); } diff --git a/io/trace-events b/io/trace-events index 79e1a19af7b70ce4870528c92b15f983066029a3..a83936c98802699664eb7e0b241ded4d7aece70c 100644 --- a/io/trace-events +++ b/io/trace-events @@ -43,6 +43,12 @@ qio_channel_tls_handshake_start(void *ioc) "TLS handshake start ioc=%p" qio_channel_tls_handshake_pending(void *ioc, int status) "TLS handshake pending ioc=%p status=%d" qio_channel_tls_handshake_fail(void *ioc) "TLS handshake fail ioc=%p" qio_channel_tls_handshake_complete(void *ioc) "TLS handshake complete ioc=%p" +qio_channel_tls_handshake_cancel(void *ioc) "TLS handshake cancel ioc=%p" +qio_channel_tls_bye_start(void *ioc) "TLS termination start ioc=%p" +qio_channel_tls_bye_pending(void *ioc, int status) "TLS termination pending ioc=%p status=%d" +qio_channel_tls_bye_fail(void *ioc) "TLS termination fail ioc=%p" +qio_channel_tls_bye_complete(void *ioc) "TLS termination complete ioc=%p" +qio_channel_tls_bye_cancel(void *ioc) "TLS termination cancel ioc=%p" qio_channel_tls_credentials_allow(void *ioc) "TLS credentials allow ioc=%p" qio_channel_tls_credentials_deny(void *ioc) "TLS credentials deny ioc=%p" diff --git a/net/net.c b/net/net.c index af23db53e54a9d210f76091dc48aa0fbac627d13..e6d8e27162e31c8b68206d400224d9f63890bdda 100644 --- a/net/net.c +++ b/net/net.c @@ -758,10 +758,20 @@ ssize_t qemu_send_packet(NetClientState *nc, const uint8_t *buf, int size) ssize_t qemu_receive_packet(NetClientState *nc, const uint8_t *buf, int size) { + uint8_t min_pkt[ETH_ZLEN]; + size_t min_pktsz = sizeof(min_pkt); + if (!qemu_can_receive_packet(nc)) { return 0; } + if (net_peer_needs_padding(nc)) { + if (eth_pad_short_frame(min_pkt, &min_pktsz, buf, size)) { + buf = min_pkt; + size = min_pktsz; + } + } + return qemu_net_queue_receive(nc->incoming_queue, buf, size); }