|
23 | 23 | #include "picoquic_internal.h" |
24 | 24 | #include "picoquic_unified_log.h" |
25 | 25 | #include "tls_api.h" |
| 26 | +#include <picotls.h> |
| 27 | +#include "picoquic_crypto_provider_api.h" |
26 | 28 | #include <stdlib.h> |
27 | 29 | #include <string.h> |
28 | 30 |
|
@@ -70,9 +72,10 @@ uint8_t* picoquic_prepare_stream_and_datagrams(picoquic_cnx_t* cnx, picoquic_pat |
70 | 72 | * ... |
71 | 73 | */ |
72 | 74 |
|
73 | | -void picoqmux_init(picoquic_cnx_t* cnx) |
| 75 | +void picoqmux_init(picoquic_cnx_t* cnx, int is_cleartext) |
74 | 76 | { |
75 | 77 | cnx->is_qmux = 1; |
| 78 | + cnx->is_qmux_cleartext = is_cleartext; |
76 | 79 | cnx->qx_acked_last = UINT64_MAX; |
77 | 80 | cnx->qx_sent_last = UINT64_MAX; |
78 | 81 | cnx->qx_query_ack = UINT64_MAX; |
@@ -291,6 +294,48 @@ void picoqmux_update_stats_after_qx(picoquic_cnx_t * cnx, uint64_t current_time) |
291 | 294 | } |
292 | 295 | #endif |
293 | 296 |
|
| 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 | + |
294 | 339 | /* QX PING frames come in to variations: |
295 | 340 | * FRAME_TYPE_QX_PING, FRAME_TYPE_QX_PING_R |
296 | 341 | */ |
@@ -555,7 +600,7 @@ int picoqmux_prepare_cnx_packet(picoquic_cnx_t* cnx, uint64_t current_time, uint |
555 | 600 | } |
556 | 601 |
|
557 | 602 | /* If not yet sent, send the transport parameters */ |
558 | | - if (!picoqmux_has_sent_tp(cnx)) { |
| 603 | + else if (!picoqmux_has_sent_tp(cnx)) { |
559 | 604 | bytes_next = picoquic_format_qmux_tp_frame(cnx, bytes_next, bytes_max); |
560 | 605 | if (bytes_next == NULL) { |
561 | 606 | ret = -1; |
@@ -693,14 +738,6 @@ int picoqmux_prepare_cnx_packets(picoquic_cnx_t * cnx, uint64_t current_time, ui |
693 | 738 | } |
694 | 739 | } |
695 | 740 | } |
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 | | - } |
704 | 741 | } |
705 | 742 | return ret; |
706 | 743 | } |
@@ -873,19 +910,266 @@ int picoqmux_incoming_cnx_packet(picoquic_cnx_t* cnx, uint64_t current_time, |
873 | 910 | return ret; |
874 | 911 | } |
875 | 912 |
|
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 |
877 | 1072 | */ |
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) |
879 | 1076 | { |
880 | 1077 | picoquic_cnx_t* cnx = picoquic_create_cnx(quic, |
881 | 1078 | picoquic_null_connection_id, |
882 | 1079 | picoquic_null_connection_id, |
883 | 1080 | 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. */ |
885 | 1085 | 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 | + } |
887 | 1095 | } |
888 | 1096 | return cnx; |
889 | 1097 | } |
890 | 1098 |
|
| 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; |
891 | 1103 |
|
| 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