#include "Context.hpp" #ifdef _WINDOWS #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #ifndef SOCKET_TYPE #define SOCKET_TYPE SOCKET #endif #ifndef SOCKET_CLOSE #define SOCKET_CLOSE(x) closesocket(x) #endif #ifndef WSA_LAST_ERROR #define WSA_LAST_ERROR(x) WSAGetLastError() #endif #ifndef socklen_t #define socklen_t int #endif #else /* Linux */ #include #include #include #include #include #include #include #ifndef __USE_XOPEN2K #define __USE_XOPEN2K #endif #ifndef __USE_POSIX #define __USE_POSIX #endif #include #include #include #include #include #ifndef SOCKET_TYPE #define SOCKET_TYPE int #endif #ifndef INVALID_SOCKET #define INVALID_SOCKET -1 #endif #ifndef SOCKET_CLOSE #define SOCKET_CLOSE(x) close(x) #endif #ifndef WSA_LAST_ERROR #define WSA_LAST_ERROR(x) ((long)(x)) #endif #endif #include #include //FIXME: Windows compat //MAYBE: templatise callback switch using namespace net; bool stream_ctx::IsUnidirId(uint64_t id) { return !IS_BIDIR_STREAM_ID(id); } bool stream_ctx::IsServerId(uint64_t id) { return IsUnidirId(id) && !IS_CLIENT_STREAM_ID(id); } bool stream_ctx::IsClientId(uint64_t id) { return IsUnidirId(id) && IS_CLIENT_STREAM_ID(id); } Context::Context(uint32_t max_connections, char const *cert, char const *key, picoquic_stream_data_cb_fn callback, void* callback_ctx, char const *tickets, char const *tokens): ticket_store_filename(tickets), token_store_filename(tokens) { if (max_connections > 0) { uint64_t current_time = picoquic_current_time(); quic = picoquic_create(max_connections, cert, key, NULL, ALPN, callback, callback_ctx, NULL, NULL, NULL, current_time, NULL, ticket_store_filename, NULL, 0); if (quic == NULL) { FATAL("Network context creation failed"); } if (token_store_filename != nullptr) { if (picoquic_load_retry_tokens(quic, token_store_filename) != 0) { LOG_W("No token file present. Will create one as " << token_store_filename); } } picoquic_set_default_congestion_algorithm(quic, picoquic_bbr_algorithm); //MAYBE: picoquic_set_cookie_mode(quic, 2); #if TRACE picoquic_set_key_log_file_from_env(quic); picoquic_set_log_level(quic, 1); //MAYBE: picoquic_set_textlog #endif /* FIXME: esni from ct.address //MAYBE: multi universe if (esni_key_file_name != NULL && esni_rr_file_name != NULL) { ret = picoquic_esni_load_key(qserver, esni_key_file_name); if (ret == 0) { ret = picoquic_esni_server_setup(qserver, esni_rr_file_name); } } */ } else { quic = nullptr; LOG_D("Local only context"); } } Context::~Context() { for (int i = 0; i < nb_sockets; i++) { if (sockets[i] != INVALID_SOCKET) { SOCKET_CLOSE(sockets[i]); sockets[i] = INVALID_SOCKET; } } if (quic != NULL) { if (ticket_store_filename != NULL) { if (picoquic_save_session_tickets(quic, ticket_store_filename) != 0) { LOG_E("Could not store the saved session tickets."); } } if (token_store_filename != NULL) { if (picoquic_save_retry_tokens(quic, token_store_filename) != 0) { LOG_E("Could not save tokens to " << token_store_filename); } } picoquic_free(quic); } } void Context::openSockets(int port, int family) { assert(quic != nullptr); #ifdef _WINDOWS WSADATA wsaData = {0}; (void)WSA_START(MAKEWORD(2, 2), &wsaData); #endif memset(sockets_family, 0, sizeof(sockets_family)); { nb_sockets = (family == AF_UNSPEC) ? 2 : 1; /* Compute how many sockets are necessary */ if (family == AF_UNSPEC) { sockets_family[0] = AF_INET; sockets_family[1] = AF_INET6; } else if (family == AF_INET || family == AF_INET6) { sockets_family[0] = family; } else { FATAL("Could not open socket. Unsupported AF " << family); } for (int i = 0; i < nb_sockets; i++) { int recv_set = 0; int send_set = 0; if ((sockets[i] = socket(sockets_family[i], SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET || picoquic_socket_set_ecn_options(sockets[i], sockets_family[i], &recv_set, &send_set) != 0 || picoquic_socket_set_pkt_info(sockets[i], sockets_family[i]) != 0 || (port != 0 && picoquic_bind_to_port(sockets[i], sockets_family[i], port) != 0)) { for (int j = 0; j < i; j++) { if (sockets[i] != INVALID_SOCKET) { SOCKET_CLOSE(sockets[i]); sockets[i] = INVALID_SOCKET; } } FATAL("Cannot setup socket. AF " << sockets_family[i] << ", port " << port); } } } } void Context::pull(const uint64_t MAX_DELAY, const uint64_t MAX_LOOPS, const uint64_t MAX_INNER_LOOPS) { if (quic == nullptr) return; const uint64_t start_time = picoquic_get_quic_time(quic); uint64_t current_time = start_time; uint64_t next_wake_time = current_time; uint64_t nb_loops = 0; struct sockaddr_storage addr_from; struct sockaddr_storage addr_to; int if_index_to; uint8_t buffer[PICOQUIC_MAX_PACKET_SIZE]; uint8_t send_buffer[PICOQUIC_MAX_PACKET_SIZE]; size_t send_length = 0; //MAYBE: port migration while (isRunning() && nb_loops < MAX_LOOPS && next_wake_time - start_time < MAX_DELAY) { /* Wait for packets */ unsigned char received_ecn; int socket_rank = -1; if_index_to = 0; int64_t wake_delay = next_wake_time - current_time; int bytes_recv = picoquic_select_ex(sockets, nb_sockets, &addr_from, &addr_to, &if_index_to, &received_ecn, buffer, sizeof(buffer), wake_delay, &socket_rank, ¤t_time); nb_loops++; if (bytes_recv < 0) { LOG_E("Socket pull error"); break; } uint64_t loop_time = current_time; uint16_t current_recv_port = socket_port; if (bytes_recv > 0) { /* track the local port value if not known yet */ if (socket_port == 0 && nb_sockets == 1) { struct sockaddr_storage local_address; if (picoquic_get_local_address(sockets[0], &local_address) != 0) { memset(&local_address, 0, sizeof(struct sockaddr_storage)); LOG_E("Could not read local address"); } else if (addr_to.ss_family == AF_INET6) { socket_port = ((struct sockaddr_in6*) & local_address)->sin6_port; } else if (addr_to.ss_family == AF_INET) { socket_port = ((struct sockaddr_in*) & local_address)->sin_port; } current_recv_port = socket_port; } /* Document incoming port */ if (addr_to.ss_family == AF_INET6) { ((struct sockaddr_in6*) & addr_to)->sin6_port = current_recv_port; } else if (addr_to.ss_family == AF_INET) { ((struct sockaddr_in*) & addr_to)->sin_port = current_recv_port; } /* Read packet */ (void)picoquic_incoming_packet(quic, buffer, (size_t)bytes_recv, (struct sockaddr*) & addr_from, (struct sockaddr*) & addr_to, if_index_to, received_ecn, current_time); } /* Emit packets */ uint64_t nb_inner_loops = 0; while (nb_inner_loops < MAX_INNER_LOOPS && loop_time - start_time < MAX_DELAY) { nb_inner_loops++; struct sockaddr_storage peer_addr; struct sockaddr_storage local_addr; int if_index = 0; int sock_err = 0; int ret = picoquic_prepare_next_packet(quic, loop_time, send_buffer, sizeof(send_buffer), &send_length, &peer_addr, &local_addr, &if_index, NULL, NULL); if (ret != 0 || send_length <= 0) break; SOCKET_TYPE send_socket = INVALID_SOCKET; for (int i = 0; i < nb_sockets; i++) { if (sockets_family[i] == peer_addr.ss_family) { send_socket = sockets[i]; break; } } int sock_ret = picoquic_send_through_socket(send_socket, (struct sockaddr*) & peer_addr, (struct sockaddr*) & local_addr, if_index, (const char*)send_buffer, (int)send_length, &sock_err); if (sock_ret <= 0) { LOG_E("Cannot send data through socket"); } } next_wake_time = picoquic_get_next_wake_time(quic, current_time); } } Connection::~Connection() { if (cnx != nullptr) release(0); } void Connection::setup(picoquic_quic_t* ctx, sockaddr* addr, const char* sni, picoquic_stream_data_cb_fn cb_fn, void *cb_ctx) { auto cnx = picoquic_create_cnx(ctx, picoquic_null_connection_id, picoquic_null_connection_id, addr, picoquic_current_time(), 0, sni, ALPN, is_client); if (cnx == NULL) { FATAL("Network connection creation failed"); } setHandle(cnx); setCallback(cb_fn, cb_ctx); /* Client connection parameters could be set here, before starting the connection. */ if (picoquic_start_client_cnx(cnx) < 0) { FATAL("Could not activate connection"); } } void Connection::setHandle(picoquic_cnx_t* c) { assert(cnx == nullptr); cnx = c; #if TRACE picoquic_connection_id_t icid = picoquic_get_initial_cnxid(cnx); printf("QUIC connection ID: "); for (uint8_t i = 0; i < icid.id_len; i++) { printf("%02x", icid.id[i]); } printf("\n"); #endif } void Connection::setCallback(picoquic_stream_data_cb_fn cb_fn, void *ctx) { if (cnx != nullptr) picoquic_set_callback(cnx, cb_fn, ctx); } void Connection::release(uint16_t reason) { if (cnx != nullptr) { picoquic_close(cnx, reason); cnx = nullptr; } } uint64_t Connection::getRTT() { return cnx != nullptr ? picoquic_get_rtt(cnx) : INT64_MAX; } uint16_t Connection::getErrorCode(bool is_app) { return cnx != nullptr ? (is_app ? cnx->remote_application_error : cnx->remote_error) : 42; } std::string Connection::getAddress() { auto str = std::string("?:?"); if (cnx != nullptr) { sockaddr* addr = NULL; picoquic_get_peer_addr(cnx, &addr); str.resize(128, '\0'); picoquic_addr_text(addr, str.data(), str.size()); } return str; } void Connection::sendCopy(const uint8_t* ptr, size_t len, uint8_t queue, size_t queue_size) { auto data = (uint8_t*)malloc(len); assert(data != nullptr); memcpy(data, ptr, len); send(data::out_buffer(data, len), queue, queue_size); } void Connection::send(const data::out_view &view, uint8_t queue, size_t queue_size) { send(data::out_buffer(view), queue, queue_size); } void Connection::send(const data::out_buffer& buffer, uint8_t queue_id, size_t queue_size) { if (cnx == nullptr) return; assert(queue_id < outgoing.size()); auto& queue = outgoing.at(queue_id); if (queue_size == 0 || GetSize(queue.streams) < queue_size) { auto stream = &queue.streams.emplace_front(nextOutgoingId(), queue_id, buffer); auto res = picoquic_mark_active_stream(cnx, stream->stream_id, 1, stream); if (res != 0) { LOG_E("Cannot initialize stream " << res); } } else { queue.pending = buffer; } } void Connection::emit(const uint8_t *ptr, size_t size) { assert(cnx != nullptr); auto res = picoquic_queue_datagram_frame(cnx, size, ptr); if (res != 0) { LOG_E("Cannot initialize datagram " << res); } } in_stream_ctx *Connection::receive(uint64_t streamId) { auto stream = &incoming.emplace_front(streamId); if (cnx != nullptr) { picoquic_set_app_stream_ctx(cnx, streamId, stream); } return stream; } void Connection::close(out_stream_ctx *str) { auto& queue = outgoing.at(str->queue_id); if (queue.pending.handle) { send(queue.pending, str->queue_id); queue.pending.handle = nullptr; } queue.streams.remove(*str); } void Connection::close(in_stream_ctx *str) { reset(str->stream_id); incoming.remove(*str); } void Connection::reset(uint64_t id) { if (cnx != nullptr) { picoquic_reset_stream(cnx, id, 0); } }