Skip to content

Commit 7d43694

Browse files
committed
Add TLS support and test.
1 parent da009f5 commit 7d43694

8 files changed

Lines changed: 412 additions & 37 deletions

File tree

UnitTest1/unittest1.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,13 @@ namespace UnitTest1
19641964
int ret = qmux_loop_idle_test();
19651965
Assert::AreEqual(ret, 0);
19661966
}
1967+
1968+
TEST_METHOD(qmux_loop_tls)
1969+
{
1970+
int ret = qmux_loop_tls_test();
1971+
Assert::AreEqual(ret, 0);
1972+
}
1973+
19671974
TEST_METHOD(perflog)
19681975
{
19691976
int ret = perflog_test();

picoquic/picoquic_crypto_provider_api.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ extern "C" {
124124
size_t ext_data_size;
125125
uint8_t app_secret_enc[PTLS_MAX_DIGEST_SIZE];
126126
uint8_t app_secret_dec[PTLS_MAX_DIGEST_SIZE];
127+
ptls_buffer_t tls_wbuf;
128+
ptls_buffer_t tls_rbuf;
127129
} picoquic_tls_ctx_t;
128130

129131
#ifdef __cplusplus

picoquic/picoquic_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,8 @@ typedef struct st_picoquic_cnx_t {
13171317
unsigned int is_notified_that_path_is_allowed : 1; /* application wants to be advised if it is now possible to create a path */
13181318
unsigned int is_reset_stream_at_enabled : 1; /* Reset Stream At is supported */
13191319
unsigned int is_qmux : 1; /* This connection is handled by QMux, not QUIC */
1320+
unsigned int is_qmux_cleartext : 1; /* This QMux connection is not encrypted */
1321+
unsigned int is_qmux_tls_ready : 1; /* TLS handshake of QMux connection not complete */
13201322

13211323
/* PMTUD policy */
13221324
picoquic_pmtud_policy_enum pmtud_policy;

picoquic/qmux.c

Lines changed: 298 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include "picoquic_internal.h"
2424
#include "picoquic_unified_log.h"
2525
#include "tls_api.h"
26+
#include <picotls.h>
27+
#include "picoquic_crypto_provider_api.h"
2628
#include <stdlib.h>
2729
#include <string.h>
2830

@@ -70,9 +72,10 @@ uint8_t* picoquic_prepare_stream_and_datagrams(picoquic_cnx_t* cnx, picoquic_pat
7072
* ...
7173
*/
7274

73-
void picoqmux_init(picoquic_cnx_t* cnx)
75+
void picoqmux_init(picoquic_cnx_t* cnx, int is_cleartext)
7476
{
7577
cnx->is_qmux = 1;
78+
cnx->is_qmux_cleartext = is_cleartext;
7679
cnx->qx_acked_last = UINT64_MAX;
7780
cnx->qx_sent_last = UINT64_MAX;
7881
cnx->qx_query_ack = UINT64_MAX;
@@ -291,6 +294,48 @@ void picoqmux_update_stats_after_qx(picoquic_cnx_t * cnx, uint64_t current_time)
291294
}
292295
#endif
293296

297+
/* Create a QUIC context parameterized for Qmux */
298+
picoquic_quic_t* picoqmux_create(uint32_t max_nb_connections,
299+
char const* cert_file_name,
300+
char const* key_file_name,
301+
char const* cert_root_file_name,
302+
char const* default_alpn,
303+
picoquic_stream_data_cb_fn default_callback_fn,
304+
void* default_callback_ctx,
305+
picoquic_connection_id_cb_fn cnx_id_callback,
306+
void* cnx_id_callback_ctx,
307+
uint8_t reset_seed[PICOQUIC_RESET_SECRET_SIZE],
308+
uint64_t current_time,
309+
uint64_t* p_simulated_time,
310+
char const* ticket_file_name,
311+
const uint8_t* ticket_encryption_key,
312+
size_t ticket_encryption_key_length)
313+
{
314+
/* TODO: reusing the whole creation process is a bit wasteful,
315+
* as it entails creating several hash tables that are not useful for Qmux.
316+
* Having specialized code would avoid that.
317+
*/
318+
picoquic_quic_t* quic = picoquic_create(
319+
max_nb_connections, cert_file_name, key_file_name, cert_root_file_name,
320+
default_alpn, default_callback_fn, default_callback_ctx,
321+
cnx_id_callback, cnx_id_callback_ctx,
322+
reset_seed, current_time, p_simulated_time,
323+
ticket_file_name, ticket_encryption_key, ticket_encryption_key_length);
324+
325+
if (quic != NULL) {
326+
/* remove the callbacks that would interfere with QMux processing. */
327+
ptls_context_t* ctx = (ptls_context_t*)quic->tls_master_ctx;
328+
/* ctx->on_client_hello? */
329+
/* setting ctx->update_traffic_key is necessary for QUIC,
330+
* but interferes with plain TLS key management. */
331+
if (ctx->update_traffic_key != NULL) {
332+
free(ctx->update_traffic_key);
333+
ctx->update_traffic_key = NULL;
334+
}
335+
}
336+
return quic;
337+
}
338+
294339
/* QX PING frames come in to variations:
295340
* FRAME_TYPE_QX_PING, FRAME_TYPE_QX_PING_R
296341
*/
@@ -555,7 +600,7 @@ int picoqmux_prepare_cnx_packet(picoquic_cnx_t* cnx, uint64_t current_time, uint
555600
}
556601

557602
/* If not yet sent, send the transport parameters */
558-
if (!picoqmux_has_sent_tp(cnx)) {
603+
else if (!picoqmux_has_sent_tp(cnx)) {
559604
bytes_next = picoquic_format_qmux_tp_frame(cnx, bytes_next, bytes_max);
560605
if (bytes_next == NULL) {
561606
ret = -1;
@@ -693,14 +738,6 @@ int picoqmux_prepare_cnx_packets(picoquic_cnx_t * cnx, uint64_t current_time, ui
693738
}
694739
}
695740
}
696-
if (*send_length > 0) {
697-
/* something sent. Notice progress. */
698-
cnx->latest_progress_time = current_time;
699-
cnx->next_wake_time = current_time;
700-
}
701-
else {
702-
cnx->next_wake_time = next_wake_time;
703-
}
704741
}
705742
return ret;
706743
}
@@ -873,19 +910,266 @@ int picoqmux_incoming_cnx_packet(picoquic_cnx_t* cnx, uint64_t current_time,
873910
return ret;
874911
}
875912

876-
/* Creation of a basic connection
913+
/* Handling of TLS */
914+
int picoqmux_send_handshake(picoquic_cnx_t* cnx, uint8_t* send_buffer,
915+
size_t send_buffer_max, size_t* send_length)
916+
{
917+
int ret = 0;
918+
picoquic_tls_ctx_t* tls_ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
919+
920+
if (tls_ctx->tls_wbuf.off > 0) {
921+
if (tls_ctx->tls_wbuf.off > send_buffer_max) {
922+
/*oops, not good. */
923+
ret = -1;
924+
}
925+
else
926+
{
927+
memcpy(send_buffer, tls_ctx->tls_wbuf.base, tls_ctx->tls_wbuf.off);
928+
*send_length = tls_ctx->tls_wbuf.off;
929+
tls_ctx->tls_wbuf.off = 0;
930+
}
931+
}
932+
cnx->is_qmux_tls_ready = ptls_handshake_is_complete(tls_ctx->tls);
933+
return ret;
934+
}
935+
936+
int picoqmux_send_data(picoquic_cnx_t* cnx, uint64_t current_time, uint8_t* send_buffer,
937+
size_t send_buffer_max, size_t* send_length)
938+
{
939+
#define TLS_DATA_OVERHEAD 32
940+
picoquic_tls_ctx_t* tls_ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
941+
/* Prepare just enough data to fill the send buffer */
942+
size_t max_data = (send_buffer_max - TLS_DATA_OVERHEAD > 0x4000) ?
943+
0x4000 : send_buffer_max - TLS_DATA_OVERHEAD;
944+
uint8_t p_buffer[0x4000];
945+
size_t p_length = 0;
946+
int ret = picoqmux_prepare_cnx_packets(cnx, current_time, p_buffer, max_data, &p_length);
947+
if (ret == 0) {
948+
ptls_buffer_t s_buf;
949+
ptls_buffer_init(&s_buf, send_buffer, send_buffer_max);
950+
ret = ptls_send(tls_ctx->tls, &s_buf, p_buffer, p_length);
951+
if (ret == 0) {
952+
*send_length = s_buf.off;
953+
}
954+
}
955+
return ret;
956+
}
957+
958+
int picoqmux_incoming_data(picoquic_cnx_t* cnx, uint64_t current_time,
959+
const uint8_t* incoming, size_t incoming_length, size_t * consumed)
960+
{
961+
int ret = 0;
962+
picoquic_tls_ctx_t* tls_ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
963+
size_t in_len = incoming_length;
964+
965+
ret = ptls_receive(tls_ctx->tls, &tls_ctx->tls_rbuf, incoming, &in_len);
966+
967+
if (ret == 0) {
968+
*consumed = in_len;
969+
if (tls_ctx->tls_rbuf.off > 0) {
970+
ret = picoqmux_incoming_cnx_packet(cnx, current_time,
971+
tls_ctx->tls_rbuf.base, tls_ctx->tls_rbuf.off);
972+
tls_ctx->tls_rbuf.off = 0;
973+
}
974+
}
975+
976+
return ret;
977+
}
978+
979+
int picoqmux_incoming_handshake(picoquic_cnx_t* cnx, uint64_t current_time,
980+
const uint8_t* receive_buffer, size_t receive_length, size_t* consumed)
981+
{
982+
int ret = 0;
983+
picoquic_tls_ctx_t* tls_ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
984+
985+
*consumed = receive_length;
986+
987+
ret = ptls_handshake(tls_ctx->tls, &tls_ctx->tls_wbuf,
988+
receive_buffer, consumed, &tls_ctx->handshake_properties);
989+
990+
if (ret == 0) {
991+
if (tls_ctx->tls_wbuf.off == 0) {
992+
cnx->is_qmux_tls_ready = ptls_handshake_is_complete(tls_ctx->tls);
993+
}
994+
}
995+
else if (ret == PTLS_ERROR_IN_PROGRESS) {
996+
ret = 0;
997+
}
998+
999+
return ret;
1000+
}
1001+
1002+
int picoqmux_start_client_cnx(picoquic_cnx_t* cnx, uint64_t current_time)
1003+
{
1004+
int ret = 0;
1005+
uint64_t consumed = 0;
1006+
picoquic_tls_ctx_t* tls_ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
1007+
PICOQUIC_THREAD_CHECK(cnx->quic);
1008+
1009+
if (cnx->cnx_state != picoquic_state_client_init ||
1010+
cnx->tls_stream[0].sent_offset > 0 ||
1011+
cnx->tls_stream[0].send_queue != NULL) {
1012+
DBG_PRINTF("%s", "picoqmux_start_client_cnx called twice.");
1013+
return -1;
1014+
}
1015+
1016+
if (cnx->sni != NULL) {
1017+
ptls_set_server_name(tls_ctx->tls, cnx->sni, strlen(cnx->sni));
1018+
}
1019+
1020+
if (cnx->alpn != NULL) {
1021+
tls_ctx->alpn_vec[0].base = (uint8_t*)cnx->alpn;
1022+
tls_ctx->alpn_vec[0].len = strlen(cnx->alpn);
1023+
tls_ctx->handshake_properties.client.negotiated_protocols.count = 1;
1024+
tls_ctx->handshake_properties.client.negotiated_protocols.list = tls_ctx->alpn_vec;
1025+
}
1026+
else if (cnx->callback_fn != NULL) {
1027+
/* Get the default ALPN list for the callback function */
1028+
ret = cnx->callback_fn(cnx, 0, (uint8_t*)tls_ctx, 0, picoquic_callback_request_alpn_list, cnx->callback_ctx, NULL);
1029+
1030+
tls_ctx->handshake_properties.client.negotiated_protocols.count = tls_ctx->alpn_count;
1031+
tls_ctx->handshake_properties.client.negotiated_protocols.list = tls_ctx->alpn_vec;
1032+
1033+
if (ret != 0) {
1034+
DBG_PRINTF("ALPN list callback returns 0x%x", ret);
1035+
}
1036+
}
1037+
1038+
picoquic_log_new_connection(cnx);
1039+
1040+
/* A remote session ticket may have been loaded as part of initializing TLS,
1041+
* and remote parameters may have been initialized to the initial value
1042+
* of the previous session. Apply these new parameters. */
1043+
cnx->maxdata_remote = cnx->remote_parameters.initial_max_data;
1044+
cnx->max_stream_id_bidir_remote =
1045+
STREAM_ID_FROM_RANK(cnx->remote_parameters.initial_max_stream_id_bidir, cnx->client_mode, 0);
1046+
cnx->max_stream_id_unidir_remote =
1047+
STREAM_ID_FROM_RANK(cnx->remote_parameters.initial_max_stream_id_unidir, cnx->client_mode, 1);
1048+
cnx->max_stream_data_remote = cnx->remote_parameters.initial_max_data;
1049+
cnx->max_stream_data_local = cnx->local_parameters.initial_max_stream_data_bidi_local;
1050+
1051+
ptls_buffer_init(&tls_ctx->tls_wbuf, "", 0);
1052+
ptls_buffer_init(&tls_ctx->tls_rbuf, "", 0);
1053+
1054+
ret = picoqmux_incoming_handshake(cnx, current_time, NULL, 0, &consumed);
1055+
1056+
return ret;
1057+
}
1058+
1059+
int picoqmux_init_server_cnx(picoquic_cnx_t* cnx)
1060+
{
1061+
int ret = 0;
1062+
picoquic_tls_ctx_t* tls_ctx = (picoquic_tls_ctx_t*)cnx->tls_ctx;
1063+
PICOQUIC_THREAD_CHECK(cnx->quic);
1064+
1065+
ptls_buffer_init(&tls_ctx->tls_wbuf, "", 0);
1066+
ptls_buffer_init(&tls_ctx->tls_rbuf, "", 0);
1067+
1068+
return ret;
1069+
}
1070+
1071+
/* Creation of a basic connection
8771072
*/
878-
picoquic_cnx_t* picoqmux_create_qmux_cnx(picoquic_quic_t* quic, uint64_t current_time, char client_mode)
1073+
1074+
picoquic_cnx_t* picoqmux_create_qmux_cnx(picoquic_quic_t* quic, uint64_t current_time,
1075+
int client_mode, int is_cleartext, char const* server, char const* alpn)
8791076
{
8801077
picoquic_cnx_t* cnx = picoquic_create_cnx(quic,
8811078
picoquic_null_connection_id,
8821079
picoquic_null_connection_id,
8831080
NULL, current_time,
884-
0, NULL, NULL, client_mode);
1081+
0, server, alpn, (char)client_mode);
1082+
1083+
/* TODO: may need to create the connection in a different way than QUIC,
1084+
* e.g., not using the QUIC extension. */
8851085
if (cnx != NULL) {
886-
picoqmux_init(cnx);
1086+
picoqmux_init(cnx, is_cleartext);
1087+
if ((client_mode &&
1088+
picoqmux_start_client_cnx(cnx, current_time) != 0) ||
1089+
(!client_mode &&
1090+
picoqmux_init_server_cnx(cnx) != 0)) {
1091+
/* Cannot just do partial initialization! */
1092+
picoquic_delete_cnx(cnx);
1093+
cnx = NULL;
1094+
}
8871095
}
8881096
return cnx;
8891097
}
8901098

1099+
int picoqmux_prepare_packets(picoquic_cnx_t* cnx, uint64_t current_time, uint8_t* send_buffer,
1100+
size_t send_buffer_max, size_t* send_length)
1101+
{
1102+
int ret = 0;
8911103

1104+
/* Consider moving the timer protection here */
1105+
if (cnx->is_qmux_cleartext) {
1106+
ret = picoqmux_prepare_cnx_packets(cnx, current_time, send_buffer, send_buffer_max, send_length);
1107+
}
1108+
else if (cnx->is_qmux_tls_ready) {
1109+
/* TLS is negotiated: prepare and encrypt packets */
1110+
ret = picoqmux_send_data(cnx, current_time, send_buffer,
1111+
send_buffer_max, send_length);
1112+
}
1113+
else {
1114+
/* Perform the handshake */
1115+
ret = picoqmux_send_handshake(cnx, send_buffer, send_buffer_max, send_length);
1116+
}
1117+
if (ret == 0) {
1118+
if (*send_length > 0) {
1119+
/* something sent. Notice progress. */
1120+
cnx->latest_progress_time = current_time;
1121+
cnx->next_wake_time = current_time;
1122+
}
1123+
else if (cnx->idle_timeout > 0) {
1124+
cnx->next_wake_time = cnx->latest_progress_time + cnx->idle_timeout;
1125+
}
1126+
else {
1127+
cnx->next_wake_time = UINT64_MAX;
1128+
}
1129+
}
1130+
return ret;
1131+
}
1132+
1133+
int picoqmux_incoming_packets(picoquic_cnx_t* cnx, uint64_t current_time,
1134+
const uint8_t* receive_buffer, size_t receive_length)
1135+
{
1136+
int ret = 0;
1137+
1138+
if (cnx->is_qmux_cleartext) {
1139+
picoqmux_incoming_cnx_packet(cnx, current_time, receive_buffer, receive_length);
1140+
}
1141+
else
1142+
{
1143+
/* we can have here a concatenation of encrypted packets */
1144+
while (ret == 0 && receive_length > 0) {
1145+
size_t consumed = 0;
1146+
if (cnx->is_qmux_tls_ready) {
1147+
/* TLS is negotiated: receive data packets packets */
1148+
ret = picoqmux_incoming_data(cnx, current_time, receive_buffer, receive_length, &consumed);
1149+
}
1150+
else {
1151+
/* Perform the handshake */
1152+
ret = picoqmux_incoming_handshake(cnx, current_time, receive_buffer, receive_length,
1153+
&consumed);
1154+
}
1155+
if (ret == 0) {
1156+
if (consumed >= receive_length) {
1157+
receive_length = 0;
1158+
}
1159+
else {
1160+
receive_buffer += consumed;
1161+
receive_length -= consumed;
1162+
}
1163+
}
1164+
}
1165+
}
1166+
1167+
if (ret == 0) {
1168+
/* something received. Notice progress. */
1169+
cnx->latest_progress_time = current_time;
1170+
cnx->next_wake_time = current_time;
1171+
SET_LAST_WAKE(cnx->quic, PICOQUIC_QMUX);
1172+
}
1173+
1174+
return ret;
1175+
}

0 commit comments

Comments
 (0)