401 lines
13 KiB
C++
401 lines
13 KiB
C++
#include "Context.hpp"
|
|
|
|
#ifdef _WINDOWS
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <WinSock2.h>
|
|
#include <Windows.h>
|
|
#include <assert.h>
|
|
#include <iphlpapi.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ws2tcpip.h>
|
|
|
|
#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 <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
|
|
#ifndef __USE_XOPEN2K
|
|
#define __USE_XOPEN2K
|
|
#endif
|
|
#ifndef __USE_POSIX
|
|
#define __USE_POSIX
|
|
#endif
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/select.h>
|
|
|
|
#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 <picoquic/picoquic_internal.h>
|
|
#include <picoquic/picoquic_utils.h>
|
|
//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);
|
|
}
|
|
}
|