diff --git a/CMakeLists.txt b/CMakeLists.txt index ab624a519..0e016ce40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,6 +195,7 @@ set(PICOHTTP_LIBRARY_FILES picohttp/h3zero_common.c picohttp/h3zero_server.c picohttp/h3zero_uri.c + picohttp/picomask.c picohttp/quicperf.c picohttp/siduck.c picohttp/webtransport.c @@ -207,13 +208,16 @@ set(PICOHTTP_HEADERS picohttp/democlient.h picohttp/demoserver.h picohttp/pico_webtransport.h + picohttp/picomask.h + picohttp/quicperf.c picohttp/wt_baton.h) set(PICOHTTP_TEST_LIBRARY_FILES picoquictest/h3zerotest.c picoquictest/h3zero_stream_test.c picoquictest/h3zero_uri_test.c - picoquictest/quicperf_test.c + picoquictest/picomask_test.c + picoquictest/picomask_test.c picoquictest/webtransport_test.c) OPTION(PICOQUIC_FETCH_PTLS "Fetch PicoTLS during configuration" OFF) diff --git a/picohttp/picohttp.vcxproj b/picohttp/picohttp.vcxproj index ea8873783..f97011586 100644 --- a/picohttp/picohttp.vcxproj +++ b/picohttp/picohttp.vcxproj @@ -144,6 +144,7 @@ + @@ -155,6 +156,7 @@ + diff --git a/picohttp/picohttp.vcxproj.filters b/picohttp/picohttp.vcxproj.filters index 661a2f33b..69a8f2a6c 100644 --- a/picohttp/picohttp.vcxproj.filters +++ b/picohttp/picohttp.vcxproj.filters @@ -34,6 +34,9 @@ Source Files + + Source Files + @@ -66,6 +69,9 @@ Source Files + + Source Files + diff --git a/picohttp/picomask.c b/picohttp/picomask.c new file mode 100644 index 000000000..ac94b44a0 --- /dev/null +++ b/picohttp/picomask.c @@ -0,0 +1,400 @@ +/* +* Author: Christian Huitema +* Copyright (c) 2024, Private Octopus, Inc. +* All rights reserved. +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Implementation of the masque service over picoquic and h3zero. +* +* The masque proxying is implemented using h3zero, and hooking +* into several APIs. +* +* The management of Connect UDP uses the "extended +* connect" API, just like web transport. We have two consider +* multiple connection levels: +* +* - there is a single Masque context managing all common data, +* including lists of other contexts and lists of connection +* identifiers. This is tied to the QUIC context in which +* QUIC and H3 connections as defined. +* - there is one masque context per H3 connection. On the +* server, this may mean one context per connection from masque +* clients. On the client, there may be multiple contexts if the +* client connects to multiple Masque proxies. This context +* is tied to the H3 context of the connection. +* - there is one UDP_CONNECT context per tuple, +* where target is identified by IP address and port number. +* +* On the client, the proper matching between connections, paths and UDP +* CONNECT contexts is debatable. We don't want something too +* intrusive. One solution would be to use a specific interface +* ID, if we can accept the risk of collision in the interface +* ID space. The "local" address will have that interface ID +* and the IP Address and port of the proxy. The "remote" address +* will have the that interface ID +* and the IP Address and port of the target. +* +* The flow will be: +* - create or a connection to a target address, using the normal +* API, but setting the "interface index" to the reserved +* value "picomask interface ID". +* - intercept packets sent to the "picomask interface", and +* submit them to the picomask outgoing API. +* +* - if there is no connection yet to the selected proxy, +* set one up. +* - if the is no Connect UDP context for the proxy and the +* target, set one up. +* - if the context is created, queue the packets in the +* connect UDP context, and wake up that context. +* +* In the reverse path, packets will be incoming from the targets, +* and have to be associated with the selected context. +* We need a procedure to intercept incoming packets, by examining +* the connection ID and/or the IP addresses. +* +* We assume that the size of queues will be managed by congestion control. +* If excessive, set the ECN marks or drop. +* +* On the server, data from target is incoming over the UDP socket. If it +* does not match a local CID, it will be added to the queue +* of the UDP CONNECT context that matches the target address +* (remote address). This ends up with the same "send path" +* as for the cleint. +* +* On the server, data from the client arrives in datagrams. It is queued +* in front of the local socket, maybe witten diractly to the UDP +* socket. Maybe integrate with the "prepare" API to catch these +* packets, so we have only on connection loop. +* +* We need to define an H3Zero callback, using the same API as for +* "post" or "web transport", to handle the "connect UDP" protocol. +* The connection control messages will be handled through that +* callback, as well as the tunneled datagrams. +* +* The forwarding path should be able to perform transforms, +* for incoming as well as outgoing packets. +* +*/ + +#include +#include "picomask.h" +#include "h3zero.h" +#include "h3zero_common.h" +#include "string.h" + +int picomask_callback(picoquic_cnx_t* cnx, + uint8_t* bytes, size_t length, + picohttp_call_back_event_t wt_event, + struct st_h3zero_stream_ctx_t* stream_ctx, + void* path_app_ctx); + +/* Context retrieval functions */ +static uint64_t table_udp_hash(const void * key) +{ + /* the key is a unique 64 bit number, so we keep this simple. */ + return *((uint64_t*)key); +} + +static int table_udp_compare(const void* key1, const void* key2) +{ + return (*((uint64_t*)key1) == *((uint64_t*)key2)) ? 0 : -1; +} + + +static picohash_item * table_udp_to_item(const void* key) +{ + picomask_cnx_ctx_t* cnx_ctx = (picomask_cnx_ctx_t*)key; + return &cnx_ctx->hash_item; +} + +picomask_cnx_ctx_t* picomask_cnx_ctx_by_number(picomask_ctx_t* ctx, uint64_t picomask_number) +{ + picomask_cnx_ctx_t* cnx_ctx = NULL; + picohash_item* item; + picomask_cnx_ctx_t key = { 0 }; + key.picomask_number = picomask_number; + key.hash_item.key = (void*)&key.picomask_number; + item = picohash_retrieve(ctx->table_udp_ctx, &key); + + if (item != NULL) { + cnx_ctx = (picomask_cnx_ctx_t*)(((uint8_t*)(item)- + offsetof(struct st_picomask_cnx_ctx_t, hash_item))); + } + return cnx_ctx; +} + +/* Init the global context */ + +int picomask_ctx_init(picomask_ctx_t* ctx, size_t max_nb_connections) +{ + int ret = 0; + + if ((ctx->table_udp_ctx = picohash_create_ex(max_nb_connections, + table_udp_hash, table_udp_compare, table_udp_to_item)) == NULL) { + ret = -1; + } + + return ret; +} + +void picomask_ctx_release(picomask_ctx_t* ctx) +{ + /* Delete all the existing contexts, by walking through + * the table */ + /* then delete the table itself. */ + picohash_delete(ctx->table_udp_ctx, 0); +} + + +/* Create the picomask context per udp connect */ +picomask_cnx_ctx_t* picomask_cnx_ctx_create(picomask_ctx_t* picomask_ctx) +{ + picomask_cnx_ctx_t* cnx_ctx = (picomask_cnx_ctx_t*)malloc(sizeof(picomask_cnx_ctx_t)); + if (cnx_ctx != NULL) { + memset(cnx_ctx, 0, sizeof(picomask_cnx_ctx_t)); + cnx_ctx->picomask_number = picomask_ctx->picomask_number_next++; + /* register in table of contexts */ + picohash_insert(picomask_ctx->table_udp_ctx, cnx_ctx); + } + return cnx_ctx; +} +#if 0 +/* Update context when sending a connect request */ +int picomask_connecting(picoquic_cnx_t* cnx, + h3zero_stream_ctx_t* stream_ctx, void * v_masque_ctx) +{ + picomask_ctx_t* picomask_ctx = (picomask_ctx_t*)v_masque_ctx; + + picoquic_log_app_message(cnx, "Outgoing connect udp on stream: %"PRIu64, stream_ctx->stream_id); + + return 0; +} + +/* Accept an incoming connection */ +int picomask_accept(picoquic_cnx_t* cnx, + uint8_t* path, size_t path_length, + struct st_h3zero_stream_ctx_t* stream_ctx, + void* path_app_ctx) +{ + int ret = 0; + + /* Find the target's IP address and port number from the path */ + + /* Verify that the path is OK: the UDP proxy disallows UDP proxying requests + * to vulnerable targets, such as the UDP proxy's own addresses and localhost, + * link-local, multicast, and broadcast addresses */ + + /* If doing just connect UDP, by opposition to QUIC proxy, accept + * only one connection for a given IP+port. If doing QUIC proxy, + * manage the registered CID for the long header packets. + */ + + /* create a per connection context, indexed by stream ID and + * some unique identifier of the connection. + */ + + /* if all is well, */ + return ret; +} +#endif + +/* Prepare datagram. This is the call from the inner connection, +* stating that a datagram can now be sent. The masque context +* pick the UDP connect context with the smallest wakeup time, + */ + +/* picomask callback. This will be called from the web server +* when the path points to a picomask callback. +*/ + +int picomask_callback(picoquic_cnx_t* cnx, + uint8_t* bytes, size_t length, + picohttp_call_back_event_t wt_event, + struct st_h3zero_stream_ctx_t* stream_ctx, + void* path_app_ctx) +{ + int ret = 0; + DBG_PRINTF("picomask_callback: %d, %" PRIi64 "\n", (int)wt_event, (stream_ctx == NULL)?(int64_t)-1:(int64_t)stream_ctx->stream_id); + switch (wt_event) { + case picohttp_callback_connect: + /* A connect has been received on this stream, and could be accepted. + * The path app context should point to the global masque context. + */ + /* ret = picomask_accept(cnx, bytes, length, stream_ctx, path_app_ctx); */ + break; + case picohttp_callback_connect_refused: + /* The response from the server has arrived and it is negative. The + * application needs to close that stream. + */ + break; + case picohttp_callback_connect_accepted: + if (stream_ctx != NULL) { + /* Stream will now carry "capsules" */ + stream_ctx->is_upgraded = 1; + } + break; + case picohttp_callback_post_fin: + case picohttp_callback_post_data: + /* Receiving capsule data on the control stream. + * if the FIN bit is set, the connection will be closed. + */ + /* ret = picomask_stream_data(cnx, bytes, length, (wt_event == picohttp_callback_post_fin), stream_ctx, path_app_ctx); */ + break; + case picohttp_callback_provide_data: + /* callback to provide data. Provide the next capsule. */ + /* ret = picomask_provide_data(cnx, bytes, length, stream_ctx, path_app_ctx); */ + break; + case picohttp_callback_post_datagram: + /* Stack received a datagram. Submit as "incoming" packet on the + * select connection and path */ + /* ret = picomask_receive_datagram(cnx, bytes, length, stream_ctx, path_app_ctx); */ + break; + case picohttp_callback_provide_datagram: + /* callback to provide data. This will translate to a "prepare data" call + * on the next available connection context and path context */ + /* ret = picomask_provide_datagram(cnx, bytes, length, stream_ctx, path_app_ctx); */ + break; + case picohttp_callback_reset: + /* Control stream has been abandoned. Abandon the whole connection. */ + /* ret = picomask_stream_reset(cnx, stream_ctx, path_app_ctx); */ + break; + case picohttp_callback_free: /* Used during clean up the stream. Only cause the freeing of memory. */ + /* Free the memory attached to the stream */ + break; + case picohttp_callback_deregister: + /* The app context has been removed from the registry. + * Its references should be removed from streams belonging to this session. + * On the client, the memory should be freed. + */ + /* picomask_unlink_context(cnx, stream_ctx, path_app_ctx); */ + break; + default: + /* protocol error */ + ret = -1; + break; + } + return ret; +} + +/* Connect is called when the path registers to use the tunnel service. +* The call should document the masque context of the service, and +* also the inner connection and inner path id. +*/ +int picomask_connect(picoquic_cnx_t* cnx, picomask_ctx_t* picomask_ctx, + char const * server_path, + h3zero_callback_ctx_t* h3_ctx) +{ + int ret = 0; + uint64_t stream_id = picoquic_get_next_local_stream_id(cnx, 0); + h3zero_stream_ctx_t* stream_ctx = NULL; + picomask_cnx_ctx_t* cnx_ctx = NULL; +#ifdef _WINDOWS + UNREFERENCED_PARAMETER(server_path); +#endif + /* Create an H3 stream context */ + if ((stream_ctx = h3zero_find_or_create_stream( + cnx, stream_id, h3_ctx, 1, 1)) == NULL) { + ret = -1; + } + /* Associate the stream with a per_stream context */ + else if ((ret = picoquic_set_app_stream_ctx(cnx, stream_id, stream_ctx)) != 0) { + DBG_PRINTF("Could not set context for stream %"PRIu64, stream_id); + stream_ctx = NULL; + } + /* Create the connection context for the UDP connect */ + else if ((cnx_ctx = picomask_cnx_ctx_create(picomask_ctx)) == NULL) { + DBG_PRINTF("Could not create UDP Connect context for stream %"PRIu64, stream_id); + ret = -1; + } + /* Register for the prefix in the H3 context. */ + else if ((ret = h3zero_declare_stream_prefix(h3_ctx, stream_ctx->stream_id, + picomask_callback, cnx_ctx))!=0){ + DBG_PRINTF("Could not declare prefix stream %"PRIu64, stream_id); + /* clean up? */ + } + /* Finalize the context */ + else { + /* WT_CONNECT finalizes the context with a call to the connecting event. + * But we do not have any sub protocol, so we can do that here. + */ + cnx_ctx->cnx = cnx; + cnx_ctx->stream_id = stream_id; + /* to do: set target_addr from path */ + /* Then, queue the UDP_CONNECT frame. */ + } + + if (ret != 0) { + /* clean up the partially created contexts */ + if (cnx_ctx != NULL) { + /* delete that */ + } + if (stream_ctx != NULL) { + h3zero_delete_stream(cnx, h3_ctx, stream_ctx); + } + } + return ret; +} + +/* Calls from the inner connection: +* state when a path is ready to send data. This is a combination of +* having data to send, and not being limited by CC and pacing. +* +* Should document the "next time" for the path. Then, the +* masque context will translate that into a "ready to send" +* signal for the inner connection. + */ + +/* Mapping a path to a proxy, on the client? +* +*/ + + +/* Creation of an outer connection. + * On client, this is done explicitly: create a connection to + * a proxy, get a unique UDP connection ID. This requires + * first creating an H3 connection (may already exist), and + * then issuing the UDP connect. + * On server, this is done upon successful connection from + * a client. + */ + + + +/* management of outgoing packets at the client */ +int picomask_outgoing() +{ + /* Is there an established context for these addresses? + * if yes, queue it there. + */ + + /* If not, is there yet an established H3 connection for + * the source address? + */ + + /* If not, establish the h3 connection. + * TODO: provide credentials. + */ + + /* is there now an established H3 connection for + * the source address?*/ + + /* if not, return an error */ + /* if yes, create a Connect UDP context, queue the packet to it */ + return -1; +} \ No newline at end of file diff --git a/picohttp/picomask.h b/picohttp/picomask.h new file mode 100644 index 000000000..397799cf2 --- /dev/null +++ b/picohttp/picomask.h @@ -0,0 +1,76 @@ +/* +* Author: Christian Huitema +* Copyright (c) 2024, Private Octopus, Inc. +* All rights reserved. +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef PICOMASK_H +#define PICOMASK_H + +#include "picohash.h" +#include "picoquic.h" +#include "picoquic_utils.h" +#include "picoquic_internal.h" + +/* +* Context is split between two levels: +* - The global context of the picomask service, which +* holds for example the list of all connection contexts, +* the global list of incoming VCID, etc. +* - The per connection entry, which holds the state +* and the queue of packets for a given connect-udp +* connection. +* The global context is initialized when starting the +* service. +*/ + +/* We need to reserve an interface ID that does not collude with +* values likely used by the operating system. This excludes +* small numbers, and special numbers like 0 or -1. We pick +* a random 31 bit numbers, derived from the SHA1 hash of +* "Picomask UDP interface": +* 2798c62715dd8ce6e2c6dd92a37a8276f16c029e +*/ +#define picomask_interface_id 0x2798c627 + +typedef struct st_picomask_ctx_t { + picohash_table* table_udp_ctx; + uint64_t picomask_number_next; +} picomask_ctx_t; + +typedef struct st_picomask_h3_ctx_t { + struct sockaddr_storage target_addr; + picoquic_cnx_t* cnx; +} picomask_h3_ctx_t; + +typedef struct st_picomask_cnx_ctx_t { + picohash_item hash_item; + uint64_t picomask_number; + picoquic_cnx_t* cnx; + uint64_t stream_id; + struct sockaddr_storage target_addr; + /* Management of capsule protocol on control stream */ + /* Management of datagram queue -- incoming packets + * that have to be processed locally */ + picoquic_packet_t* outgoing_first; + picoquic_packet_t* outgoing_last; +} picomask_cnx_ctx_t; + + + +#endif /* PICOMASK_H */ diff --git a/picoquictest/picomask_test.c b/picoquictest/picomask_test.c new file mode 100644 index 000000000..26e8c70fa --- /dev/null +++ b/picoquictest/picomask_test.c @@ -0,0 +1,354 @@ +/* +* Author: Christian Huitema +* Copyright (c) 2024, Private Octopus, Inc. +* All rights reserved. +* +* Permission to use, copy, modify, and distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include "picomask.h" +#include "h3zero.h" +#include "h3zero_common.h" +#include "picoquic.h" +#include "picoquic_utils.h" + +typedef struct st_picomask_test_ctx_t { + uint64_t simulated_time; + /* Three quic nodes: client(0), proxy(1), target(2) */ + picoquic_quic_t* quic[3]; + struct sockaddr_storage addr[3]; + picohttp_server_parameters_t server_context; + picohttp_server_parameters_t target_context; + /* Four links: server->client[0], client->server[1], server->target[2], target->server[3], + */ + picoquictest_sim_link_t* link[4]; + /* all nodes run H3, client as client, proxy and target as servers */ + /* Transfer test will be by getting a test file from target to client */ + /* ECN simulation */ + uint8_t packet_ecn_default; + uint8_t recv_ecn_client; + uint8_t recv_ecn_server; +} picoquic_picomask_test_ctx_t; + +/* +* Delete the configuration +*/ +void picomask_test_delete(picoquic_picomask_test_ctx_t* pt_ctx) +{ + for (int i = 0; i < 3; i++) { + if (pt_ctx->quic[i] != NULL) { + picoquic_free(pt_ctx->quic[i]); + pt_ctx->quic[i] = NULL; + } + } + + for (int i = 0; i < 4; i++) { + if (pt_ctx->link[i] != NULL) { + picoquictest_sim_link_delete(pt_ctx->link[i]); + pt_ctx->link[i] = NULL; + } + } +} +/* +* test configuration. +* +* Build a test network with three nodes: client, proxy, target. +*/ +picoquic_picomask_test_ctx_t * picomask_test_config() +{ + int ret = 0; + char test_server_cert_file[512]; + char test_server_key_file[512]; + char test_server_cert_store_file[512]; + picoquic_picomask_test_ctx_t* pt_ctx = NULL; + + ret = picoquic_get_input_path(test_server_cert_file, sizeof(test_server_cert_file), picoquic_solution_dir, + PICOQUIC_TEST_FILE_SERVER_CERT); + + if (ret == 0) { + ret = picoquic_get_input_path(test_server_key_file, sizeof(test_server_key_file), picoquic_solution_dir, + PICOQUIC_TEST_FILE_SERVER_KEY); + } + + if (ret == 0) { + ret = picoquic_get_input_path(test_server_cert_store_file, sizeof(test_server_cert_store_file), picoquic_solution_dir, + PICOQUIC_TEST_FILE_CERT_STORE); + } + + if (ret == 0) { + pt_ctx = (picoquic_picomask_test_ctx_t*)malloc(sizeof(picoquic_picomask_test_ctx_t)); + if (pt_ctx == NULL) { + ret = -1; + } + else { + memset(pt_ctx, 0, sizeof(picoquic_picomask_test_ctx_t)); + } + } + + if (ret == 0) { + /* Set addresses */ + for (int i = 0; i < 3; i++) { + unsigned long h = 0xa0000001; + struct sockaddr_in* a = (struct sockaddr_in*)&pt_ctx->addr[i]; + a->sin_family = AF_INET; +#ifdef _WINDOWS + a->sin_addr.S_un.S_addr = htonl(h + i); +#else + + a->sin_addr.s_addr = htonl(h + i); +#endif + a->sin_port = htons(1234); + } + /* Create client context */ + pt_ctx->quic[0] = picoquic_create(8, NULL, NULL, test_server_cert_store_file, NULL, + h3zero_callback, + /* TODO: default client callback context */ NULL, + NULL, NULL, NULL, pt_ctx->simulated_time, + &pt_ctx->simulated_time, + /* TODO -- should we store tickets? */NULL, + NULL, 0); + /* Create server context */ + pt_ctx->quic[1] = picoquic_create(8, test_server_key_file, test_server_cert_file, NULL, "h3", + h3zero_callback, + &pt_ctx->server_context, + NULL, NULL, NULL, pt_ctx->simulated_time, + &pt_ctx->simulated_time, + /* TODO -- should we store tickets? */NULL, + NULL, 0); + /* Create target context */ + pt_ctx->quic[2] = picoquic_create(8, test_server_key_file, test_server_cert_file, NULL, "h3", + h3zero_callback, + &pt_ctx->target_context, + NULL, NULL, NULL, pt_ctx->simulated_time, + &pt_ctx->simulated_time, + /* TODO -- should we store tickets? */NULL, + NULL, 0); + if (pt_ctx->quic[0] == NULL || pt_ctx->quic[1] == NULL || pt_ctx->quic[1] == NULL) { + ret = -1; + } + /* Create links */ + for (int i = 0; ret == 9 && i < 4; i++) { + if ((pt_ctx->link[i] = picoquictest_sim_link_create(0.01, 10000, NULL, 0, pt_ctx->simulated_time)) == NULL) { + ret = -1; + } + } + } + + if (ret < 0 && pt_ctx != NULL) { + + } + + return pt_ctx; +} + +/* Process arrival of a packet from a link */ +int picomask_test_packet_arrival(picoquic_picomask_test_ctx_t* pt_ctx, int link_id, int * is_active) +{ + int ret = 0; + picoquictest_sim_packet_t* packet = picoquictest_sim_link_dequeue(pt_ctx->link[link_id], pt_ctx->simulated_time); + + if (packet == NULL) { + /* unexpected, probably bug in test program */ + ret = -1; + } + else { + int node_id = -1; /* by default, go to proxy */ + + switch (link_id) { + case 0: + node_id = 1; + break; + case 1: + node_id = 0; + break; + case 2: + node_id = 1; + break; + case 3: + node_id = 2; + break; + default: + ret = -1; + } + + if (ret == 0) { + *is_active = 1; + + ret = picoquic_incoming_packet(pt_ctx->quic[node_id], + packet->bytes, (uint32_t)packet->length, + (struct sockaddr*)&packet->addr_from, + (struct sockaddr*)&packet->addr_to, 0, 0, + pt_ctx->simulated_time); + } + + free(packet); + } + + return ret; +} + +/* Packet departure from selected node */ +int picomask_test_packet_departure(picoquic_picomask_test_ctx_t* pt_ctx, int node_id, + int* is_active) +{ + int ret = 0; + picoquictest_sim_packet_t* packet = picoquictest_sim_link_create_packet(); + + if (packet == NULL) { + /* memory error during test. Something is really wrong. */ + ret = -1; + } + else { + /* check whether there is something to send */ + int if_index = 0; + + ret = picoquic_prepare_next_packet(pt_ctx->quic[node_id], pt_ctx->simulated_time, + packet->bytes, PICOQUIC_MAX_PACKET_SIZE, &packet->length, + &packet->addr_to, &packet->addr_from, &if_index, NULL, NULL); + + if (ret != 0) + { + /* useless test, but makes it easier to add a breakpoint under debugger */ + free(packet); + ret = -1; + } + else if (packet->length > 0) { + /* Find link ID from node ID and destination IP */ + int link_id = -1; + + switch (node_id) { + case 0: + link_id = 0; + break; + case 1: + if (picoquic_compare_addr((struct sockaddr*)&packet->addr_to, + (struct sockaddr*)&pt_ctx->addr[0]) == 0) { + link_id = 1; + } + else if (picoquic_compare_addr((struct sockaddr*)&packet->addr_to, + (struct sockaddr*)&pt_ctx->addr[2]) == 0) { + link_id = 3; + } + else { + free(packet); + ret = -1; + } + break; + case 2: + link_id = 2; + break; + } + if (ret == 0) { + /* If the source address is not set, set it */ + if (packet->addr_from.ss_family == 0) { + picoquic_store_addr(&packet->addr_from, (struct sockaddr*)&pt_ctx->addr[node_id]); + } + /* send now. */ + *is_active = 1; + picoquictest_sim_link_submit(pt_ctx->link[link_id], packet, pt_ctx->simulated_time); + } + } + else { + free(packet); + } + } + + return ret; +} + +/* step by step simulation + */ +int picomask_test_step(picoquic_picomask_test_ctx_t* pt_ctx, int* is_active) +{ + int ret = 0; + uint64_t next_arrival_time = UINT64_MAX; + int arrival_index = -1; + uint64_t next_departure_time = UINT64_MAX; + int departure_index = -1; + int need_frame_departure = 0; + uint64_t next_frame_time = UINT64_MAX; + uint64_t next_time = UINT64_MAX; + + /* Check earliest packet arrival */ + for (int i = 0; i < 4; i++) { + uint64_t arrival = picoquictest_sim_link_next_arrival(pt_ctx->link[i], next_arrival_time); + if (arrival < next_arrival_time) { + next_arrival_time = arrival; + arrival_index = i; + } + } + if (next_arrival_time < next_time) { + next_time = next_arrival_time; + } + + /* Check earliest packet departure */ + for (int i = 0; i < 3; i++) { + uint64_t departure = picoquic_get_next_wake_time(pt_ctx->quic[i], pt_ctx->simulated_time); + if (departure < next_departure_time) { + next_departure_time = departure; + departure_index = i; + } + } + if (next_time > next_departure_time) { + next_time = next_departure_time; + } + + /* Update the time now */ + if (next_time > pt_ctx->simulated_time) { + pt_ctx->simulated_time = next_time; + } + else { + next_time = pt_ctx->simulated_time; + } + + if (ret == 0) { + /* Perform earliest action */ + if (next_arrival_time <= next_time) { + /* Process next packet from simulated link */ + ret = picomask_test_packet_arrival(pt_ctx, arrival_index, is_active); + } + else { + /* Prepare next packet from selected connection */ + ret = picomask_test_packet_departure(pt_ctx, departure_index, is_active); + } + } + if (ret < 0) { + DBG_PRINTF("Simulation fails at T=%" PRIu64, pt_ctx->simulated_time); + } + + return ret; +} + +/* +* First test: verify that the UDP Connect context can be established. +*/ +int picomask_udp_test() +{ + int ret = 0; + picoquic_picomask_test_ctx_t* pt_ctx = picomask_test_config(); + + if (pt_ctx == NULL) { + ret = -1; + } + else { + /* Create a client connection to the server */ + /* On that connection, create a UDP connect context */ + picomask_test_delete(pt_ctx); + } + return ret; +} \ No newline at end of file diff --git a/picoquictest/picoquictest.vcxproj b/picoquictest/picoquictest.vcxproj index a67e32b65..cd1c82b7d 100644 --- a/picoquictest/picoquictest.vcxproj +++ b/picoquictest/picoquictest.vcxproj @@ -177,6 +177,7 @@ + diff --git a/picoquictest/picoquictest.vcxproj.filters b/picoquictest/picoquictest.vcxproj.filters index 029e3320e..a1a792095 100644 --- a/picoquictest/picoquictest.vcxproj.filters +++ b/picoquictest/picoquictest.vcxproj.filters @@ -168,6 +168,9 @@ Source Files + + Source Files + Source Files