From c6bb25d7ae0739b25fb93ad8baa6101b35ee17b7 Mon Sep 17 00:00:00 2001 From: Christian Huitema Date: Wed, 17 Jul 2024 13:01:09 -0700 Subject: [PATCH 1/8] Early seleton of picomask --- picohttp/picohttp.vcxproj | 2 + picohttp/picohttp.vcxproj.filters | 6 + picohttp/picomask.c | 229 ++++++++++++++++++++++++++++++ picohttp/picomask.h | 59 ++++++++ picoquictest/picomask_test.c | 0 5 files changed, 296 insertions(+) create mode 100644 picohttp/picomask.c create mode 100644 picohttp/picomask.h create mode 100644 picoquictest/picomask_test.c 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..4a00aaa20 --- /dev/null +++ b/picohttp/picomask.c @@ -0,0 +1,229 @@ +/* +* 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. +* +* 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. +* +* We need a procedure to intercept incoming packets, by examining +* the connection ID. +* +* We also need to define extensions to path management for +* handling tunneled paths. The path will be examined if the +* outer connection can send a datagram, and if there is +* a queue of packets for the connect UDP context. +* +* The forwarding path should be able to perform transforms, +* for incoming as well as outgoing packets. + */ + +#include "picomask.h" +#include "h3zero.h" +#include "h3zero_common.h" + +/* 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 */ + + } + 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; +} + + /* Web transport/baton callback. This will be called from the web server + * when the path points to a web transport callback. + * Discuss: is the stream context needed? Should it be a wt_stream_context? + */ + +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_connecting: + ret = picomask_connecting(cnx, stream_ctx, path_app_ctx); + break; + case picohttp_callback_connect: + /* A connect has been received on this stream, and could be accepted. + */ + 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: /* Connection request was accepted by peer */ + /* The response from the server has arrived and it is positive. + * The application can start sending data. + */ + 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: + ret = picomask_stream_data(cnx, bytes, length, (wt_event == picohttp_callback_post_fin), stream_ctx, path_app_ctx); + break; + case picohttp_callback_provide_data: + /* Quic is ready to send. Push reminder of capsules! */ + 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 connection. */ + ret = picomask_receive_datagram(cnx, bytes, length, stream_ctx, path_app_ctx); + break; + case picohttp_callback_provide_datagram: + /* Stack can now send another datagram. + * Should ask the inner connection to produce a packet. */ + 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; +} +#endif \ No newline at end of file diff --git a/picohttp/picomask.h b/picohttp/picomask.h new file mode 100644 index 000000000..22155e916 --- /dev/null +++ b/picohttp/picomask.h @@ -0,0 +1,59 @@ +/* +* 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" + +/* +* 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. +*/ + + +typedef struct st_picomask_ctx_t { + picohash_table* table_udp_ctx; + uint64_t picomask_number_next; + +} picomask_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 */ + +} 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..e69de29bb From eff90b39eb311c3a35ab2cb945641a8a198315de Mon Sep 17 00:00:00 2001 From: Christian Huitema Date: Sat, 20 Jul 2024 16:07:57 -0700 Subject: [PATCH 2/8] Progress on picomask --- picohttp/picomask.c | 248 ++++++++++++++++++---- picohttp/picomask.h | 23 +- picoquictest/picomask_test.c | 229 ++++++++++++++++++++ picoquictest/picoquictest.vcxproj | 1 + picoquictest/picoquictest.vcxproj.filters | 3 + 5 files changed, 462 insertions(+), 42 deletions(-) diff --git a/picohttp/picomask.c b/picohttp/picomask.c index 4a00aaa20..0ca4b022c 100644 --- a/picohttp/picomask.c +++ b/picohttp/picomask.c @@ -21,27 +21,89 @@ /* 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. * -* We need a procedure to intercept incoming packets, by examining -* the connection ID. -* -* We also need to define extensions to path management for -* handling tunneled paths. The path will be examined if the -* outer connection can send a datagram, and if there is -* a queue of packets for the connect UDP context. -* * The forwarding path should be able to perform transforms, * for incoming as well as outgoing packets. - */ +* +*/ + #include "picomask.h" #include "h3zero.h" #include "h3zero_common.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) { @@ -108,13 +170,12 @@ picomask_cnx_ctx_t* picomask_cnx_ctx_create(picomask_ctx_t* picomask_ctx) 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 */ +/* Update context when sending a connect request */ int picomask_connecting(picoquic_cnx_t* cnx, h3zero_stream_ctx_t* stream_ctx, void * v_masque_ctx) { @@ -136,13 +197,13 @@ int picomask_accept(picoquic_cnx_t* cnx, /* 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 */ + * 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. - */ + * 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. @@ -151,12 +212,18 @@ int picomask_accept(picoquic_cnx_t* cnx, /* if all is well, */ return ret; } +#endif - /* Web transport/baton callback. This will be called from the web server - * when the path points to a web transport callback. - * Discuss: is the stream context needed? Should it be a wt_stream_context? +/* 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, @@ -166,23 +233,18 @@ int picomask_callback(picoquic_cnx_t* cnx, 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_connecting: - ret = picomask_connecting(cnx, stream_ctx, path_app_ctx); - break; 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); + /* 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: /* Connection request was accepted by peer */ - /* The response from the server has arrived and it is positive. - * The application can start sending data. - */ + case picohttp_callback_connect_accepted: if (stream_ctx != NULL) { /* Stream will now carry "capsules" */ stream_ctx->is_upgraded = 1; @@ -190,24 +252,28 @@ int picomask_callback(picoquic_cnx_t* cnx, break; case picohttp_callback_post_fin: case picohttp_callback_post_data: - ret = picomask_stream_data(cnx, bytes, length, (wt_event == picohttp_callback_post_fin), stream_ctx, path_app_ctx); + /* 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: - /* Quic is ready to send. Push reminder of capsules! */ - ret = picomask_provide_data(cnx, bytes, length, stream_ctx, path_app_ctx); + /* 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 connection. */ - ret = picomask_receive_datagram(cnx, bytes, length, stream_ctx, path_app_ctx); + /* 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: - /* Stack can now send another datagram. - * Should ask the inner connection to produce a packet. */ - ret = picomask_provide_datagram(cnx, bytes, length, stream_ctx, path_app_ctx); + /* 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); + /* 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 */ @@ -217,7 +283,7 @@ int picomask_callback(picoquic_cnx_t* cnx, * 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); + /* picomask_unlink_context(cnx, stream_ctx, path_app_ctx); */ break; default: /* protocol error */ @@ -226,4 +292,108 @@ int picomask_callback(picoquic_cnx_t* cnx, } return ret; } -#endif \ No newline at end of file + +/* 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; + UNREFERENCED_PARAMETER(server_path); + /* 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 index 22155e916..397799cf2 100644 --- a/picohttp/picomask.h +++ b/picohttp/picomask.h @@ -25,6 +25,7 @@ #include "picohash.h" #include "picoquic.h" #include "picoquic_utils.h" +#include "picoquic_internal.h" /* * Context is split between two levels: @@ -38,22 +39,38 @@ * 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 index e69de29bb..4405dd645 100644 --- a/picoquictest/picomask_test.c +++ b/picoquictest/picomask_test.c @@ -0,0 +1,229 @@ +/* +* 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 "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]; + /* 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; + +/* +* test configuration. +* +* Build a test network with three nodes: client, proxy, target. +*/ + + +/* 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 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. +*/ \ No newline at end of file diff --git a/picoquictest/picoquictest.vcxproj b/picoquictest/picoquictest.vcxproj index 275f43365..9ea921729 100644 --- a/picoquictest/picoquictest.vcxproj +++ b/picoquictest/picoquictest.vcxproj @@ -175,6 +175,7 @@ + diff --git a/picoquictest/picoquictest.vcxproj.filters b/picoquictest/picoquictest.vcxproj.filters index bd2882d8f..12d217c0f 100644 --- a/picoquictest/picoquictest.vcxproj.filters +++ b/picoquictest/picoquictest.vcxproj.filters @@ -168,6 +168,9 @@ Source Files + + Source Files + From 9ddba169b5edc0ab3ee864bc5fe517a7c83ba838 Mon Sep 17 00:00:00 2001 From: huitema Date: Mon, 19 Aug 2024 11:59:01 -0700 Subject: [PATCH 3/8] Add picomasc.c to cmake builds. --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 11dd1be8f..577fc2387 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,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 @@ -202,6 +203,7 @@ set(PICOHTTP_HEADERS picohttp/democlient.h picohttp/demoserver.h picohttp/pico_webtransport.h + picohttp/picomask.h picohttp/wt_baton.h) set(PICOHTTP_TEST_LIBRARY_FILES From 451e5d5fdd6101b80f061ef7fa8240fa93e308cc Mon Sep 17 00:00:00 2001 From: huitema Date: Thu, 5 Sep 2024 22:53:37 -0700 Subject: [PATCH 4/8] Add picomask_test to CMakeList --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f2bfe95b..23a4b72e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,6 +165,7 @@ set(PICOQUIC_TEST_LIBRARY_FILES picoquictest/p2p_test.c picoquictest/pacing_test.c picoquictest/parseheadertest.c + picoquictest/picomask_test.c picoquictest/picoquic_lb_test.c picoquictest/pn2pn64test.c picoquictest/quic_tester.c From 88584d3e78498e3967c692edf731c3a40427908e Mon Sep 17 00:00:00 2001 From: huitema Date: Sat, 7 Sep 2024 15:58:37 -0700 Subject: [PATCH 5/8] Move picomask_test to http test library --- CMakeLists.txt | 2 +- picohttp/picomask.c | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 23a4b72e9..81ce483b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -165,7 +165,6 @@ set(PICOQUIC_TEST_LIBRARY_FILES picoquictest/p2p_test.c picoquictest/pacing_test.c picoquictest/parseheadertest.c - picoquictest/picomask_test.c picoquictest/picoquic_lb_test.c picoquictest/pn2pn64test.c picoquictest/quic_tester.c @@ -212,6 +211,7 @@ set(PICOHTTP_TEST_LIBRARY_FILES picoquictest/h3zerotest.c picoquictest/h3zero_stream_test.c picoquictest/h3zero_uri_test.c + picoquictest/picomask_test.c picoquictest/webtransport_test.c) OPTION(PICOQUIC_FETCH_PTLS "Fetch PicoTLS during configuration" OFF) diff --git a/picohttp/picomask.c b/picohttp/picomask.c index 0ca4b022c..c960039e3 100644 --- a/picohttp/picomask.c +++ b/picohttp/picomask.c @@ -40,7 +40,6 @@ * - 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 @@ -217,7 +216,6 @@ int picomask_accept(picoquic_cnx_t* cnx, /* 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 From e119b3d093b29807611b9050392a4432bad01bcc Mon Sep 17 00:00:00 2001 From: huitema Date: Sat, 7 Sep 2024 18:16:01 -0700 Subject: [PATCH 6/8] Fix compile issues --- picohttp/picomask.c | 5 +++- picoquictest/picomask_test.c | 57 ++++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/picohttp/picomask.c b/picohttp/picomask.c index c960039e3..ac94b44a0 100644 --- a/picohttp/picomask.c +++ b/picohttp/picomask.c @@ -92,10 +92,11 @@ * */ - +#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, @@ -303,7 +304,9 @@ int picomask_connect(picoquic_cnx_t* cnx, picomask_ctx_t* picomask_ctx, 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) { diff --git a/picoquictest/picomask_test.c b/picoquictest/picomask_test.c index 4405dd645..f3275c5c7 100644 --- a/picoquictest/picomask_test.c +++ b/picoquictest/picomask_test.c @@ -46,7 +46,50 @@ typedef struct st_picomask_test_ctx_t { * * 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) { + /* Create addresses */ + /* Create client context */ + /* Create server context */ + /* Create target context */ + /* Create server - client link [0] */ + /* Create client - server link [1] */ + /* Create server - target link [2] */ + /* Create target - server link [3] */ + + } + 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) @@ -145,13 +188,15 @@ int picomask_test_packet_departure(picoquic_picomask_test_ctx_t* pt_ctx, int nod link_id = 2; break; } - /* 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]); + 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); } - /* send now. */ - *is_active = 1; - picoquictest_sim_link_submit(pt_ctx->link[link_id], packet, pt_ctx->simulated_time); } else { free(packet); From 276d014a3f7cb04fb342415927cfaed99f4b0fc4 Mon Sep 17 00:00:00 2001 From: huitema Date: Sat, 7 Sep 2024 18:38:55 -0700 Subject: [PATCH 7/8] Missing include files --- picoquictest/picomask_test.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/picoquictest/picomask_test.c b/picoquictest/picomask_test.c index f3275c5c7..46e9a8ab9 100644 --- a/picoquictest/picomask_test.c +++ b/picoquictest/picomask_test.c @@ -19,6 +19,8 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include +#include #include "picomask.h" #include "h3zero.h" #include "h3zero_common.h" From f3f91e44f1e880db07d9d3b8d294bb1dc8038514 Mon Sep 17 00:00:00 2001 From: huitema Date: Mon, 9 Sep 2024 16:35:27 -0700 Subject: [PATCH 8/8] Beginning of first test --- picoquictest/picomask_test.c | 90 +++++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/picoquictest/picomask_test.c b/picoquictest/picomask_test.c index 46e9a8ab9..26e8c70fa 100644 --- a/picoquictest/picomask_test.c +++ b/picoquictest/picomask_test.c @@ -32,6 +32,8 @@ typedef struct st_picomask_test_ctx_t { /* 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]; @@ -43,6 +45,25 @@ typedef struct st_picomask_test_ctx_t { 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. * @@ -80,16 +101,58 @@ picoquic_picomask_test_ctx_t * picomask_test_config() } if (ret == 0) { - /* Create addresses */ + /* 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 */ - /* Create server - client link [0] */ - /* Create client - server link [1] */ - /* Create server - target link [2] */ - /* Create target - server link [3] */ + 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; } @@ -273,4 +336,19 @@ int picomask_test_step(picoquic_picomask_test_ctx_t* pt_ctx, int* is_active) /* * First test: verify that the UDP Connect context can be established. -*/ \ No newline at end of file +*/ +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