/* This file is part of GNUnet Copyright (C) 2010-2014, 2018, 2019 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file transport/gnunet-communicator-quic.c * @brief Transport plugin using QUIC. * @author Marshall Stone * @author Martin Schanzenbach * * TODO: * - Automatically generate self-signed x509 certificates and load from config * - Figure out MTU and how we have to handle fragmentation in Quiche. * - Mandate timeouts * - Setup stats handler properly * - Doxygen documentation of methods * - Refactor code shared with UDP and TCP communicator * - Performance testing * - Check for memory leaks with coverity/valgrind */ #include "gnunet_common.h" #include "gnunet_util_lib.h" #include "gnunet_core_service.h" #include "quiche.h" #include "platform.h" #include "gnunet_protocols.h" #include "gnunet_signatures.h" #include "gnunet_constants.h" #include "gnunet_statistics_service.h" #include "gnunet_transport_application_service.h" #include "gnunet_transport_communication_service.h" #include "gnunet_nt_lib.h" #include "gnunet_nat_service.h" #include "stdint.h" #include "inttypes.h" #define COMMUNICATOR_CONFIG_SECTION "communicator-quic" #define COMMUNICATOR_ADDRESS_PREFIX "quic" #define MAX_DATAGRAM_SIZE 1350 /* FIXME: Review all static lengths/contents below. Maybe this can be done smarter */ /* Currently equivalent to QUICHE_MAX_CONN_ID_LEN */ #define LOCAL_CONN_ID_LEN 20 #define MAX_TOKEN_LEN \ sizeof("quiche") - 1 \ + sizeof(struct sockaddr_storage) \ + QUICHE_MAX_CONN_ID_LEN #define CID_LEN sizeof(uint8_t) * QUICHE_MAX_CONN_ID_LEN #define TOKEN_LEN sizeof (uint8_t) * MAX_TOKEN_LEN /* FIXME: Why 4? Generic, bidirectional, client-initiated quic stream id */ #define STREAMID_BI 4 /** * How long do we believe our addresses to remain up (before * the other peer should revalidate). */ #define ADDRESS_VALIDITY_PERIOD GNUNET_TIME_UNIT_HOURS /** * Map of DCID (uint8_t) -> quic_conn for quickly retrieving connections to other peers. */ struct GNUNET_CONTAINER_MultiHashMap *conn_map; /** * Map of sockaddr -> struct PeerAddress */ struct GNUNET_CONTAINER_MultiHashMap *addr_map; /** * Handle to the config */ static const struct GNUNET_CONFIGURATION_Handle *cfg; /** * FIXME undocumented */ static struct GNUNET_TIME_Relative rekey_interval; /** * FIXME undocumented */ static struct GNUNET_NETWORK_Handle *udp_sock; /** * FIXME undocumented */ static struct GNUNET_SCHEDULER_Task *read_task; /** * FIXME undocumented */ static struct GNUNET_TRANSPORT_CommunicatorHandle *ch; /** * FIXME undocumented */ static struct GNUNET_TRANSPORT_ApplicationHandle *ah; /** * FIXME undocumented */ static int have_v6_socket; /** * FIXME undocumented */ static uint16_t my_port; /** * FIXME undocumented */ static unsigned long long rekey_max_bytes; /** * FIXME undocumented */ static quiche_config *config = NULL; /** * Our peer identity */ struct GNUNET_PeerIdentity my_identity; /** * Our private key. */ static struct GNUNET_CRYPTO_EddsaPrivateKey *my_private_key; /** * Connection to NAT service. */ static struct GNUNET_NAT_Handle *nat; /** * Information we track per peer we have recently been in contact with. * * (Since quiche handles crypto, handshakes, etc. we don't differentiate * between SenderAddress and ReceiverAddress) * FIXME: But we do a handshake as well. The flag in this struct seems to * indicate this. Update comment! */ struct PeerAddress { /** * To whom are we talking to. */ struct GNUNET_PeerIdentity target; /** * Flag to indicate whether we know the PeerIdentity (target) yet */ int id_rcvd; /** * Flag to indicate whether we have sent OUR PeerIdentity to this peer */ int id_sent; /** * Flag to indicate if we are the initiator of the connection */ int is_receiver; /** * Address of the receiver in the human-readable format * with the #COMMUNICATOR_ADDRESS_PREFIX. */ char *foreign_addr; /** * Address of the other peer. */ struct sockaddr *address; /** * Length of the address. */ socklen_t address_len; /** * The QUIC connection associated with this peer */ struct quic_conn *conn; /** * Default message queue we are providing for the #ch. */ struct GNUNET_MQ_Handle *d_mq; /** * handle for default queue with the #ch. */ struct GNUNET_TRANSPORT_QueueHandle *d_qh; /** * Timeout for this peer address. */ struct GNUNET_TIME_Absolute timeout; /** * MTU we allowed transport for this peer's default queue. * FIXME: MTU from quiche */ size_t d_mtu; /** * Which network type does this queue use? */ enum GNUNET_NetworkType nt; /** * receiver_destroy already called on receiver. */ int peer_destroy_called; /** * FIXME implementation missing * Entry in sender expiration heap. */ // struct GNUNET_CONTAINER_HeapNode *hn; }; // /** // * FIXME: Implementation missing // * Expiration heap for peers (contains `struct PeerAddress`) // */ // static struct GNUNET_CONTAINER_Heap *peers_heap; /** * ID of timeout task */ static struct GNUNET_SCHEDULER_Task *timeout_task; /** * Network scanner to determine network types. */ static struct GNUNET_NT_InterfaceScanner *is; /** * For logging statistics. */ static struct GNUNET_STATISTICS_Handle *stats; /** * QUIC connection object. A connection has a unique SCID/DCID pair. Here we store our SCID * (incoming packet DCID field == outgoing packet SCID field) for a given connection. This * is hashed for each unique quic_conn. */ struct quic_conn { uint8_t cid[LOCAL_CONN_ID_LEN]; quiche_conn *conn; }; /** * QUIC_header is used to store information received from an incoming QUIC packet */ struct QUIC_header { uint8_t type; uint32_t version; uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; size_t scid_len; uint8_t dcid[QUICHE_MAX_CONN_ID_LEN]; size_t dcid_len; uint8_t odcid[QUICHE_MAX_CONN_ID_LEN]; size_t odcid_len; uint8_t token[MAX_TOKEN_LEN]; size_t token_len; }; /** * Given a PeerAddress, receive data from streams after doing connection logic. * ASSUMES: connection is established to peer */ static void recv_from_streams (struct PeerAddress *peer) { char stream_buf[UINT16_MAX]; size_t buf_size = UINT16_MAX; char *buf_ptr = stream_buf; struct GNUNET_MessageHeader *hdr; uint64_t s = 0; quiche_stream_iter *readable; bool fin; ssize_t recv_len; readable = quiche_conn_readable (peer->conn->conn); while (quiche_stream_iter_next (readable, &s)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "stream %" PRIu64 " is readable\n", s); fin = false; recv_len = quiche_conn_stream_recv (peer->conn->conn, s, (uint8_t *) stream_buf, buf_size, &fin); if (recv_len < 0) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "error while receiving data from stream %" PRIu64 "\n", s); break; } /** * FIXME: Do not use implicit booleans. Use GNUNET_YES, GNUNET_NO, GNUNET_SYSERR * and check for that. * * Initial packet should contain peerid if they are the initiator */ if (! peer->is_receiver && GNUNET_NO == peer->id_rcvd) { if (recv_len < sizeof(struct GNUNET_PeerIdentity)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "message recv len of %zd less than length of peer identity\n", recv_len); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "received peer identity\n"); struct GNUNET_PeerIdentity *pid = (struct GNUNET_PeerIdentity *) stream_buf; peer->target = *pid; peer->id_rcvd = GNUNET_YES; buf_ptr += sizeof(struct GNUNET_PeerIdentity); recv_len -= sizeof(struct GNUNET_PeerIdentity); } /** * Parse messages to pass to communicator */ while (recv_len >= sizeof(struct GNUNET_MessageHeader)) { hdr = (struct GNUNET_MessageHeader *) buf_ptr; if (ntohs (hdr->size) > recv_len) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "message size stated (%d) is greater than length of rcvd data (%zd)!\n", ntohs (hdr->size), recv_len); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "passing %zd bytes to core\n", recv_len); GNUNET_TRANSPORT_communicator_receive (ch, &peer->target, hdr, ADDRESS_VALIDITY_PERIOD, NULL, NULL); recv_len -= ntohs (hdr->size); buf_ptr += ntohs (hdr->size); } /** * Check for leftover bytes */ if (0 != recv_len) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "message recv len of %zd less than length of message header\n", recv_len); } /** * FIXME: comment useless * fin */ if (fin) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "fin received, closing connection\n"); if (0 > quiche_conn_close (peer->conn->conn, true, 0, NULL, 0)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "quiche failed to close connection to peer\n"); } } } quiche_stream_iter_free (readable); } /** * FIXME: review token generation, assure tokens are generated properly. doxygen */ static void mint_token (const uint8_t *dcid, size_t dcid_len, struct sockaddr_storage *addr, socklen_t addr_len, uint8_t *token, size_t *token_len) { GNUNET_memcpy (token, "quiche", sizeof("quiche") - 1); GNUNET_memcpy (token + sizeof("quiche") - 1, addr, addr_len); GNUNET_memcpy (token + sizeof("quiche") - 1 + addr_len, dcid, dcid_len); *token_len = sizeof("quiche") - 1 + addr_len + dcid_len; } static enum GNUNET_GenericReturnValue validate_token (const uint8_t *token, size_t token_len, struct sockaddr_storage *addr, socklen_t addr_len, uint8_t *odcid, size_t *odcid_len) { if ((token_len < sizeof("quiche") - 1) || memcmp (token, "quiche", sizeof("quiche") - 1)) { return GNUNET_NO; } token += sizeof("quiche") - 1; token_len -= sizeof("quiche") - 1; if ((token_len < addr_len) || memcmp (token, addr, addr_len)) { return GNUNET_NO; } token += addr_len; token_len -= addr_len; if (*odcid_len < token_len) { return GNUNET_NO; } memcpy (odcid, token, token_len); *odcid_len = token_len; return GNUNET_OK; } static struct quic_conn* create_conn (uint8_t *scid, size_t scid_len, uint8_t *odcid, size_t odcid_len, struct sockaddr *local_addr, socklen_t local_addr_len, struct sockaddr_storage *peer_addr, socklen_t peer_addr_len) { struct quic_conn *conn; quiche_conn *q_conn; conn = GNUNET_new (struct quic_conn); if (scid_len != LOCAL_CONN_ID_LEN) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "error while creating connection, scid length too short: %zu\n", scid_len); return NULL; } GNUNET_memcpy (conn->cid, scid, LOCAL_CONN_ID_LEN); q_conn = quiche_accept (conn->cid, LOCAL_CONN_ID_LEN, odcid, odcid_len, local_addr, local_addr_len, (struct sockaddr *) peer_addr, peer_addr_len, config); if (NULL == q_conn) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "quiche failed to create connection after call to quiche_accept\n"); return NULL; } conn->conn = q_conn; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "new quic connection created\n"); return conn; } static void flush_egress (struct quic_conn *conn) { static uint8_t out[MAX_DATAGRAM_SIZE]; quiche_send_info send_info; ssize_t written; ssize_t sent; while (1) { written = quiche_conn_send (conn->conn, out, sizeof(out), &send_info); if (QUICHE_ERR_DONE == written) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "done writing quic packets\n"); break; } if (0 > written) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "quiche failed to create packet. quiche error: %zd\n", written); return; } sent = GNUNET_NETWORK_socket_sendto (udp_sock, out, written, (struct sockaddr *) &send_info.to, send_info.to_len); if (sent != written) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "quiche failed to send data to peer\n"); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "sent %zd bytes\n", sent); } } /** * Increment receiver timeout due to activity. * * @param receiver address for which the timeout should be rescheduled */ static void reschedule_peer_timeout (struct PeerAddress *peer) { peer->timeout = GNUNET_TIME_relative_to_absolute (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT); // GNUNET_CONTAINER_heap_update_cost (peer->hn, // peer->timeout.abs_value_us); } /** * Destroys a receiving state due to timeout or shutdown. * * @param receiver entity to close down */ static void peer_destroy (struct PeerAddress *peer) { struct GNUNET_HashCode addr_key; peer->peer_destroy_called = GNUNET_YES; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Disconnecting peer for peer `%s'\n", GNUNET_i2s (&peer->target)); if (NULL != peer->d_qh) { GNUNET_TRANSPORT_communicator_mq_del (peer->d_qh); peer->d_qh = NULL; } // GNUNET_assert (peer == GNUNET_CONTAINER_heap_remove_node (peer->hn)); /** * Remove peer from hashmap */ GNUNET_CRYPTO_hash (peer->address, peer->address_len, &addr_key); if (GNUNET_NO == GNUNET_CONTAINER_multihashmap_remove (addr_map, &addr_key, peer)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "tried to remove non-existent peer from addr map\n"); return; } GNUNET_STATISTICS_set (stats, "# peers active", GNUNET_CONTAINER_multihashmap_size (addr_map), GNUNET_NO); quiche_conn_free (peer->conn->conn); GNUNET_free (peer->address); GNUNET_free (peer->foreign_addr); GNUNET_free (peer->conn); GNUNET_free (peer); } /** * Iterator over all peers to clean up. * * @param cls NULL * @param key peer->address * @param value the peer to destroy * @return #GNUNET_OK to continue to iterate */ static int get_peer_delete_it (void *cls, const struct GNUNET_HashCode *key, void *value) { struct PeerAddress *peer = value; (void) cls; (void) key; peer_destroy (peer); return GNUNET_OK; } /** * Signature of functions implementing the sending functionality of a * message queue. * * @param mq the message queue * @param msg the message to send * @param impl_state our `struct PeerAddress` */ static void mq_send_d (struct GNUNET_MQ_Handle *mq, const struct GNUNET_MessageHeader *msg, void *impl_state) { struct PeerAddress *peer = impl_state; uint16_t msize = ntohs (msg->size); ssize_t send_len; if (NULL == peer->conn->conn) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "peer never established quic connection\n"); return; } GNUNET_assert (mq == peer->d_mq); if (msize > peer->d_mtu) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "msize: %u, mtu: %lu\n", msize, peer->d_mtu); GNUNET_break (0); if (GNUNET_YES != peer->peer_destroy_called) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "peer destroy called, destroying peer\n"); peer_destroy (peer); } return; } reschedule_peer_timeout (peer); send_len = quiche_conn_stream_send (peer->conn->conn, 4, (uint8_t *) msg, msize, false); if (send_len != msize) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "tried to send message and quiche returned %zd", send_len); return; } flush_egress (peer->conn); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "sent a message of %zd bytes\n", send_len); GNUNET_MQ_impl_send_continue (mq); } /** * Signature of functions implementing the destruction of a message * queue. Implementations must not free @a mq, but should take care * of @a impl_state. * * @param mq the message queue to destroy * @param impl_state our `struct PeerAddress` */ static void mq_destroy_d (struct GNUNET_MQ_Handle *mq, void *impl_state) { struct PeerAddress *peer = impl_state; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Default MQ destroyed\n"); if (mq == peer->d_mq) { peer->d_mq = NULL; if (GNUNET_YES != peer->peer_destroy_called) peer_destroy (peer); } } /** * Implementation function that cancels the currently sent message. * * @param mq message queue * @param impl_state our `struct PeerAddress` */ static void mq_cancel (struct GNUNET_MQ_Handle *mq, void *impl_state) { /* Cancellation is impossible with QUIC; bail */ GNUNET_assert (0); } /** * Generic error handler, called with the appropriate * error code and the same closure specified at the creation of * the message queue. * Not every message queue implementation supports an error handler. * * @param cls our `struct ReceiverAddress` * @param error error code */ static void mq_error (void *cls, enum GNUNET_MQ_Error error) { struct PeerAddress *peer = cls; GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "MQ error in queue to %s: %d\n", GNUNET_i2s (&peer->target), (int) error); peer_destroy (peer); } /** * Convert UDP bind specification to a `struct sockaddr *` * * @param bindto bind specification to convert * @param[out] sock_len set to the length of the address * @return converted bindto specification */ static struct sockaddr * udp_address_to_sockaddr (const char *bindto, socklen_t *sock_len) { struct sockaddr *in; unsigned int port; char dummy[2]; char *colon; char *cp; if (1 == sscanf (bindto, "%u%1s", &port, dummy)) { /* interpreting value as just a PORT number */ if (port > UINT16_MAX) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "BINDTO specification `%s' invalid: value too large for port\n", bindto); return NULL; } if ((GNUNET_NO == GNUNET_NETWORK_test_pf (PF_INET6)) || (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (cfg, COMMUNICATOR_CONFIG_SECTION, "DISABLE_V6"))) { struct sockaddr_in *i4; i4 = GNUNET_malloc (sizeof(struct sockaddr_in)); i4->sin_family = AF_INET; i4->sin_port = htons ((uint16_t) port); *sock_len = sizeof(struct sockaddr_in); in = (struct sockaddr *) i4; } else { struct sockaddr_in6 *i6; i6 = GNUNET_malloc (sizeof(struct sockaddr_in6)); i6->sin6_family = AF_INET6; i6->sin6_port = htons ((uint16_t) port); *sock_len = sizeof(struct sockaddr_in6); in = (struct sockaddr *) i6; } return in; } cp = GNUNET_strdup (bindto); colon = strrchr (cp, ':'); if (NULL != colon) { /* interpret value after colon as port */ *colon = '\0'; colon++; if (1 == sscanf (colon, "%u%1s", &port, dummy)) { /* interpreting value as just a PORT number */ if (port > UINT16_MAX) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "BINDTO specification `%s' invalid: value too large for port\n", bindto); GNUNET_free (cp); return NULL; } } else { GNUNET_log ( GNUNET_ERROR_TYPE_ERROR, "BINDTO specification `%s' invalid: last ':' not followed by number\n", bindto); GNUNET_free (cp); return NULL; } } else { /* interpret missing port as 0, aka pick any free one */ port = 0; } { /* try IPv4 */ struct sockaddr_in v4; memset (&v4, 0, sizeof(v4)); if (1 == inet_pton (AF_INET, cp, &v4.sin_addr)) { v4.sin_family = AF_INET; v4.sin_port = htons ((uint16_t) port); #if HAVE_SOCKADDR_IN_SIN_LEN v4.sin_len = sizeof(struct sockaddr_in); #endif in = GNUNET_memdup (&v4, sizeof(struct sockaddr_in)); *sock_len = sizeof(struct sockaddr_in); GNUNET_free (cp); return in; } } { /* try IPv6 */ struct sockaddr_in6 v6; const char *start; memset (&v6, 0, sizeof(v6)); start = cp; if (('[' == *cp) && (']' == cp[strlen (cp) - 1])) { start++; /* skip over '[' */ cp[strlen (cp) - 1] = '\0'; /* eat ']' */ } if (1 == inet_pton (AF_INET6, start, &v6.sin6_addr)) { v6.sin6_family = AF_INET6; v6.sin6_port = htons ((uint16_t) port); #if HAVE_SOCKADDR_IN_SIN_LEN v6.sin6_len = sizeof(sizeof(struct sockaddr_in6)); #endif in = GNUNET_memdup (&v6, sizeof(v6)); *sock_len = sizeof(v6); GNUNET_free (cp); return in; } } /* #5528 FIXME (feature!): maybe also try getnameinfo()? */ GNUNET_free (cp); return NULL; } /** * Setup the MQ for the @a peer. If a queue exists, * the existing one is destroyed. Then the MTU is * recalculated and a fresh queue is initialized. * * @param peer peer to setup MQ for */ static void setup_peer_mq (struct PeerAddress *peer) { size_t base_mtu; switch (peer->address->sa_family) { case AF_INET: base_mtu = 1480 /* Ethernet MTU, 1500 - Ethernet header - VLAN tag */ - sizeof(struct GNUNET_TUN_IPv4Header) /* 20 */ - sizeof(struct GNUNET_TUN_UdpHeader) /* 8 */; break; case AF_INET6: base_mtu = 1280 /* Minimum MTU required by IPv6 */ - sizeof(struct GNUNET_TUN_IPv6Header) /* 40 */ - sizeof(struct GNUNET_TUN_UdpHeader) /* 8 */; break; default: GNUNET_assert (0); break; } /* MTU == base_mtu */ peer->d_mtu = base_mtu; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up MQs and QHs\n"); /* => Effective MTU for CORE will range from 1080 (IPv6 + KX) to 1404 (IPv4 + Box) bytes, depending on circumstances... */ if (NULL == peer->d_mq) peer->d_mq = GNUNET_MQ_queue_for_callbacks (&mq_send_d, &mq_destroy_d, &mq_cancel, peer, NULL, &mq_error, peer); peer->d_qh = GNUNET_TRANSPORT_communicator_mq_add (ch, &peer->target, peer->foreign_addr, 1000, GNUNET_TRANSPORT_QUEUE_LENGTH_UNLIMITED, 0, /* Priority */ peer->nt, GNUNET_TRANSPORT_CS_OUTBOUND, peer->d_mq); } /** * Taken from: UDP communicator * Converts @a address to the address string format used by this * communicator in HELLOs. * * @param address the address to convert, must be AF_INET or AF_INET6. * @param address_len number of bytes in @a address * @return string representation of @a address */ static char * sockaddr_to_udpaddr_string (const struct sockaddr *address, socklen_t address_len) { char *ret; switch (address->sa_family) { case AF_INET: GNUNET_asprintf (&ret, "%s-%s", COMMUNICATOR_ADDRESS_PREFIX, GNUNET_a2s (address, address_len)); break; case AF_INET6: GNUNET_asprintf (&ret, "%s-%s", COMMUNICATOR_ADDRESS_PREFIX, GNUNET_a2s (address, address_len)); break; default: GNUNET_assert (0); } return ret; } /** * Function called when the transport service has received a * backchannel message for this communicator (!) via a different return * path. Should be an acknowledgement. * * @param cls closure, NULL * @param sender which peer sent the notification * @param msg payload */ static void notify_cb (void *cls, const struct GNUNET_PeerIdentity *sender, const struct GNUNET_MessageHeader *msg) { // const struct UDPAck *ack; // (void) cls; // GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, // "Storing UDPAck received from backchannel from %s\n", // GNUNET_i2s_full (sender)); // if ((ntohs (msg->type) != GNUNET_MESSAGE_TYPE_COMMUNICATOR_UDP_ACK) || // (ntohs (msg->size) != sizeof(struct UDPAck))) // { // GNUNET_break_op (0); // return; // } // ack = (const struct UDPAck *) msg; // GNUNET_CONTAINER_multipeermap_get_multiple (receivers, // sender, // &handle_ack, // (void *) ack); } /** * Task run to check #receiver_heap and #sender_heap for timeouts. * * @param cls unused, NULL */ static void check_timeouts (void *cls) { // struct GNUNET_TIME_Relative st; // struct GNUNET_TIME_Relative rt; // struct GNUNET_TIME_Relative delay; // struct ReceiverAddress *receiver; // struct SenderAddress *sender; // (void) cls; // timeout_task = NULL; // rt = GNUNET_TIME_UNIT_FOREVER_REL; // while (NULL != (receiver = GNUNET_CONTAINER_heap_peek (receivers_heap))) // { // /* if (GNUNET_YES != receiver->receiver_destroy_called) */ // /* { */ // rt = GNUNET_TIME_absolute_get_remaining (receiver->timeout); // if (0 != rt.rel_value_us) // break; // GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, // "Receiver timed out\n"); // receiver_destroy (receiver); // // } // } // st = GNUNET_TIME_UNIT_FOREVER_REL; // while (NULL != (sender = GNUNET_CONTAINER_heap_peek (senders_heap))) // { // if (GNUNET_YES != sender->sender_destroy_called) // { // st = GNUNET_TIME_absolute_get_remaining (sender->timeout); // if (0 != st.rel_value_us) // break; // sender_destroy (sender); // } // } // delay = GNUNET_TIME_relative_min (rt, st); // if (delay.rel_value_us < GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us) // timeout_task = GNUNET_SCHEDULER_add_delayed (delay, &check_timeouts, NULL); } /** * Function called by the transport service to initialize a * message queue given address information about another peer. * If and when the communication channel is established, the * communicator must call #GNUNET_TRANSPORT_communicator_mq_add() * to notify the service that the channel is now up. It is * the responsibility of the communicator to manage sane * retries and timeouts for any @a peer/@a address combination * provided by the transport service. Timeouts and retries * do not need to be signalled to the transport service. * * @param cls closure * @param peer identity of the other peer * @param address where to send the message, human-readable * communicator-specific format, 0-terminated, UTF-8 * @return #GNUNET_OK on success, #GNUNET_SYSERR if the provided address is * invalid */ static int mq_init (void *cls, const struct GNUNET_PeerIdentity *peer_id, const char *address) { struct PeerAddress *peer; const char *path; struct sockaddr *in; socklen_t in_len; struct GNUNET_HashCode addr_key; uint8_t scid[LOCAL_CONN_ID_LEN]; struct quic_conn *q_conn; char *bindto; socklen_t local_in_len; struct sockaddr *local_addr; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, COMMUNICATOR_CONFIG_SECTION, "BINDTO", &bindto)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, COMMUNICATOR_CONFIG_SECTION, "BINDTO"); return GNUNET_SYSERR; } local_addr = udp_address_to_sockaddr (bindto, &local_in_len); if (0 != strncmp (address, COMMUNICATOR_ADDRESS_PREFIX "-", strlen (COMMUNICATOR_ADDRESS_PREFIX "-"))) { GNUNET_break_op (0); return GNUNET_SYSERR; } path = &address[strlen (COMMUNICATOR_ADDRESS_PREFIX "-")]; in = udp_address_to_sockaddr (path, &in_len); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "mq_init in_len length before: %d\n", in_len); /** * If we already have a queue with this peer, ignore */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "address string in mq_init: %s\n", address); GNUNET_CRYPTO_hash (address, strlen (address), &addr_key); peer = GNUNET_CONTAINER_multihashmap_get (addr_map, &addr_key); if (NULL != peer) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "ignoring transport service mq request, we already have an mq with this peer (address)\n"); return GNUNET_SYSERR; } peer = GNUNET_new (struct PeerAddress); peer->address = in; peer->address_len = in_len; peer->target = *peer_id; peer->id_rcvd = GNUNET_YES; peer->is_receiver = GNUNET_YES; peer->nt = GNUNET_NT_scanner_get_type (is, in, in_len); peer->timeout = GNUNET_TIME_relative_to_absolute (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT); GNUNET_STATISTICS_set (stats, "# peers active", GNUNET_CONTAINER_multihashmap_size (addr_map), GNUNET_NO); peer->foreign_addr = sockaddr_to_udpaddr_string (peer->address, peer->address_len); /** * Insert peer into hashmap */ GNUNET_CONTAINER_multihashmap_put (addr_map, &addr_key, peer, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "mq_init added new peer to the addr map\n"); /** * Before setting up peer mq, initiate a quic connection to the target (perform handshake w/ quiche) */ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, scid, LOCAL_CONN_ID_LEN); q_conn = GNUNET_new (struct quic_conn); GNUNET_memcpy (q_conn->cid, scid, LOCAL_CONN_ID_LEN); peer->conn = q_conn; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "attempting to perform QUIC handshake with peer\n"); q_conn->conn = quiche_connect (peer->foreign_addr, scid, LOCAL_CONN_ID_LEN, local_addr, local_in_len, peer->address, peer->address_len, config); flush_egress (peer->conn); GNUNET_free (local_addr); return GNUNET_OK; /** * TODO: handle this */ // if (NULL == timeout_task) // timeout_task = GNUNET_SCHEDULER_add_now (&check_timeouts, NULL); } static void try_connection_reversal (void *cls, const struct sockaddr *addr, socklen_t addrlen) { /* FIXME: support reversal: #5529 */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "No connection reversal implemented!"); } /** * Signature of the callback passed to #GNUNET_NAT_register() for * a function to call whenever our set of 'valid' addresses changes. * * @param cls closure * @param app_ctx[in,out] location where the app can store stuff * on add and retrieve it on remove * @param add_remove #GNUNET_YES to add a new public IP address, * #GNUNET_NO to remove a previous (now invalid) one * @param ac address class the address belongs to * @param addr either the previous or the new public IP address * @param addrlen actual length of the @a addr */ static void nat_address_cb (void *cls, void **app_ctx, int add_remove, enum GNUNET_NAT_AddressClass ac, const struct sockaddr *addr, socklen_t addrlen) { char *my_addr; struct GNUNET_TRANSPORT_AddressIdentifier *ai; if (GNUNET_YES == add_remove) { enum GNUNET_NetworkType nt; GNUNET_asprintf (&my_addr, "%s-%s", COMMUNICATOR_ADDRESS_PREFIX, GNUNET_a2s (addr, addrlen)); nt = GNUNET_NT_scanner_get_type (is, addr, addrlen); ai = GNUNET_TRANSPORT_communicator_address_add (ch, my_addr, nt, GNUNET_TIME_UNIT_FOREVER_REL); GNUNET_free (my_addr); *app_ctx = ai; } else { ai = *app_ctx; GNUNET_TRANSPORT_communicator_address_remove (ai); *app_ctx = NULL; } } /** * Shutdown the QUIC communicator. * * @param cls NULL (always) */ static void do_shutdown (void *cls) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "do_shutdown\n"); GNUNET_CONTAINER_multihashmap_iterate (addr_map, &get_peer_delete_it, NULL); GNUNET_CONTAINER_multihashmap_destroy (addr_map); quiche_config_free (config); if (NULL != timeout_task) { GNUNET_SCHEDULER_cancel (timeout_task); timeout_task = NULL; } if (NULL != read_task) { GNUNET_SCHEDULER_cancel (read_task); read_task = NULL; } if (NULL != udp_sock) { GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (udp_sock)); udp_sock = NULL; } if (NULL != ch) { GNUNET_TRANSPORT_communicator_disconnect (ch); ch = NULL; } if (NULL != ah) { GNUNET_TRANSPORT_application_done (ah); ah = NULL; } if (NULL != my_private_key) { GNUNET_free (my_private_key); my_private_key = NULL; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "do_shutdown finished\n"); } static void sock_read (void *cls) { struct sockaddr_storage sa; struct sockaddr_in *addr_verify; socklen_t salen = sizeof(sa); uint8_t buf[UINT16_MAX]; uint8_t out[MAX_DATAGRAM_SIZE]; ssize_t rcvd; ssize_t process_pkt; struct QUIC_header quic_header; uint8_t new_cid[LOCAL_CONN_ID_LEN]; struct PeerAddress *peer; struct GNUNET_HashCode addr_key; (void) cls; quic_header.scid_len = sizeof(quic_header.scid); quic_header.dcid_len = sizeof(quic_header.dcid); quic_header.odcid_len = sizeof(quic_header.odcid); quic_header.token_len = sizeof(quic_header.token); /** * Get local_addr, in_len for quiche */ char *bindto; socklen_t in_len; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, COMMUNICATOR_CONFIG_SECTION, "BINDTO", &bindto)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, COMMUNICATOR_CONFIG_SECTION, "BINDTO"); return; } struct sockaddr *local_addr = udp_address_to_sockaddr (bindto, &in_len); read_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, udp_sock, &sock_read, NULL); while (1) { rcvd = GNUNET_NETWORK_socket_recvfrom (udp_sock, buf, sizeof(buf), (struct sockaddr *) &sa, &salen); if (-1 == rcvd) { if (EAGAIN == errno) break; // We are done reading data GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG, "recv"); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Read %lu bytes\n", rcvd); if (-1 == rcvd) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_DEBUG, "recv"); return; } /** * FIXME: hashing address string vs ip/port. It is not ideal that * we hash the string, instead of the binary representation, but * for now it is certainly less code. * Note that simply hashing the sockaddr does NOT work because the * the struct is not portable. */ const char *addr_string = sockaddr_to_udpaddr_string ((const struct sockaddr *) &sa, salen); GNUNET_CRYPTO_hash (addr_string, strlen (addr_string), &addr_key); GNUNET_free (addr_string); peer = GNUNET_CONTAINER_multihashmap_get (addr_map, &addr_key); if (NULL == peer) { /** * Create new PeerAddress (receiver) with id_rcvd = false */ peer = GNUNET_new (struct PeerAddress); peer->address = GNUNET_memdup (&sa, salen); peer->address_len = salen; peer->id_rcvd = GNUNET_NO; peer->id_sent = GNUNET_NO; peer->is_receiver = GNUNET_NO; peer->conn = NULL; peer->foreign_addr = sockaddr_to_udpaddr_string (peer->address, peer->address_len); /** * TODO: after connection established */ // setup_peer_mq (peer); if (GNUNET_SYSERR == GNUNET_CONTAINER_multihashmap_put (addr_map, &addr_key, peer, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "tried to add duplicate address into address map\n"); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "sock_read added new peer to address map\n"); } /** * Parse QUIC info */ int rc = quiche_header_info (buf, rcvd, LOCAL_CONN_ID_LEN, &quic_header.version, &quic_header.type, quic_header.scid, &quic_header.scid_len, quic_header.dcid, &quic_header.dcid_len, quic_header.token, &quic_header.token_len); if (0 > rc) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "failed to parse quic header: %d\n", rc); return; } /** * New QUIC connection with peer */ if (NULL == peer->conn) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "attempting to create new connection\n"); if (0 == quiche_version_is_supported (quic_header.version)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "quic version negotiation initiated\n"); /** * FIXME variables are redeclared often. Refactor either * to declare variables once in the beginning or refactor into * method. * * Write a version negotiation packet to "out" */ ssize_t written = quiche_negotiate_version (quic_header.scid, quic_header.scid_len, quic_header.dcid, quic_header.dcid_len, out, sizeof(out)); if (0 > written) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "quiche failed to generate version negotiation packet\n"); return; } ssize_t sent = GNUNET_NETWORK_socket_sendto (udp_sock, out, written, (struct sockaddr*) &sa, salen); if (sent != written) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "failed to send version negotiation packet to peer\n"); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "sent %zd bytes to peer during version negotiation\n", sent); return; } if (0 == quic_header.token_len) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "quic stateless retry\n"); mint_token (quic_header.dcid, quic_header.dcid_len, &sa, salen, quic_header.token, &quic_header.token_len); uint8_t new_cid[LOCAL_CONN_ID_LEN]; GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, new_cid, LOCAL_CONN_ID_LEN); ssize_t written = quiche_retry (quic_header.scid, quic_header.scid_len, quic_header.dcid, quic_header.dcid_len, new_cid, LOCAL_CONN_ID_LEN, quic_header.token, quic_header.token_len, quic_header.version, out, sizeof(out)); if (0 > written) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "quiche failed to write retry packet\n"); return; } ssize_t sent = GNUNET_NETWORK_socket_sendto (udp_sock, out, written, (struct sockaddr*) &sa, salen); if (written != sent) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "failed to send retry packet\n"); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "sent %zd bytes\n", sent); continue; } if (GNUNET_OK != validate_token (quic_header.token, quic_header.token_len, &sa, salen, quic_header.odcid, &quic_header.odcid_len)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "invalid address validation token created\n"); return; } peer->conn = create_conn (quic_header.dcid, quic_header.dcid_len, quic_header.odcid, quic_header.odcid_len, local_addr, in_len, &sa, salen); if (NULL == peer->conn) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "failed to create quic connection with peer\n"); return; } } // null connection quiche_recv_info recv_info = { (struct sockaddr *) &sa, salen, local_addr, in_len, }; /** * Send our PeerIdentity if the connection is established now */ if (quiche_conn_is_established (peer->conn->conn) && ! peer->id_sent && peer->is_receiver) { ssize_t send_len; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "handshake established with peer, sending our peer id\n"); send_len = quiche_conn_stream_send (peer->conn->conn, STREAMID_BI, (const uint8_t *) &my_identity, sizeof(my_identity), false); if (0 > send_len) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "failed to write peer identity packet. quiche error: %zd\n", send_len); return; } flush_egress (peer->conn); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "peer identity sent to peer\n"); peer->id_sent = GNUNET_YES; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "setting up peer mq\n"); setup_peer_mq (peer); /** * After this, we should be all good to send/recv data */ } process_pkt = quiche_conn_recv (peer->conn->conn, buf, rcvd, &recv_info); if (0 > process_pkt) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "quiche failed to process received packet: %zd\n", process_pkt); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "quiche processed %zd bytes\n", process_pkt); // Check for data on all available streams if the connection is established if (GNUNET_YES == quiche_conn_is_established (peer->conn->conn)) { recv_from_streams (peer); } /** * TODO: Should we use a list instead of hashmap? * Overhead for hashing function, O(1) retrieval vs O(n) iteration with n=30? * * TODO: Is iteration necessary as in the quiche server example? */ quiche_stats stats; quiche_path_stats path_stats; flush_egress (peer->conn); if (quiche_conn_is_closed (peer->conn->conn)) { quiche_conn_stats (peer->conn->conn, &stats); quiche_conn_path_stats (peer->conn->conn, 0, &path_stats); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "connection closed. quiche stats: sent=%zu, recv=%zu\n", stats.sent, stats.recv); peer_destroy (peer); } } GNUNET_free (local_addr); } /** * Setup communicator and launch network interactions. * * @param cls NULL (always) * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) * @param c configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *c) { char *bindto; struct sockaddr *in; socklen_t in_len; struct sockaddr_storage in_sto; socklen_t sto_len; (void) cls; cfg = c; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, COMMUNICATOR_CONFIG_SECTION, "BINDTO", &bindto)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, COMMUNICATOR_CONFIG_SECTION, "BINDTO"); return; } in = udp_address_to_sockaddr (bindto, &in_len); if (NULL == in) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to setup UDP socket address with path `%s'\n", bindto); GNUNET_free (bindto); return; } udp_sock = GNUNET_NETWORK_socket_create (in->sa_family, SOCK_DGRAM, IPPROTO_UDP); if (NULL == udp_sock) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "socket"); GNUNET_free (in); GNUNET_free (bindto); return; } if (AF_INET6 == in->sa_family) have_v6_socket = GNUNET_YES; if (GNUNET_OK != GNUNET_NETWORK_socket_bind (udp_sock, in, in_len)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "bind", bindto); GNUNET_NETWORK_socket_close (udp_sock); udp_sock = NULL; GNUNET_free (in); GNUNET_free (bindto); return; } sto_len = sizeof(in_sto); if (0 != getsockname (GNUNET_NETWORK_get_fd (udp_sock), (struct sockaddr *) &in_sto, &sto_len)) { memcpy (&in_sto, in, in_len); sto_len = in_len; } GNUNET_free (in); GNUNET_free (bindto); in = (struct sockaddr *) &in_sto; in_len = sto_len; GNUNET_log_from_nocheck (GNUNET_ERROR_TYPE_DEBUG, "transport", "Bound to `%s'\n", GNUNET_a2s ((const struct sockaddr *) &in_sto, sto_len)); switch (in->sa_family) { case AF_INET: my_port = ntohs (((struct sockaddr_in *) in)->sin_port); break; case AF_INET6: my_port = ntohs (((struct sockaddr_in6 *) in)->sin6_port); break; default: GNUNET_break (0); my_port = 0; } GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); /** * Setup QUICHE configuration */ config = quiche_config_new (QUICHE_PROTOCOL_VERSION); quiche_config_verify_peer (config, false); /** * TODO: configure TLS cert */ quiche_config_load_cert_chain_from_pem_file (config, "./cert.crt"); quiche_config_load_priv_key_from_pem_file (config, "./cert.key"); quiche_config_set_application_protos (config, (uint8_t *) "\x0ahq-interop\x05hq-29\x05hq-28\x05hq-27\x08http/0.9", 38); quiche_config_set_max_idle_timeout (config, 5000); quiche_config_set_max_recv_udp_payload_size (config, 1200); quiche_config_set_max_send_udp_payload_size (config, 1200); quiche_config_set_initial_max_data (config, 10000000); quiche_config_set_initial_max_stream_data_bidi_local (config, 1000000); quiche_config_set_initial_max_stream_data_bidi_remote (config, 1000000); quiche_config_set_initial_max_stream_data_uni (config, 1000000); quiche_config_set_initial_max_streams_bidi (config, 100); quiche_config_set_initial_max_streams_uni (config, 100); quiche_config_set_cc_algorithm (config, QUICHE_CC_RENO); quiche_config_set_disable_active_migration (config, true); addr_map = GNUNET_CONTAINER_multihashmap_create (2, GNUNET_NO); /** * Get our public key for initial packet */ my_private_key = GNUNET_CRYPTO_eddsa_key_create_from_configuration (cfg); if (NULL == my_private_key) { GNUNET_log ( GNUNET_ERROR_TYPE_ERROR, _ ( "Transport service is lacking key configuration settings. Exiting.\n")); GNUNET_SCHEDULER_shutdown (); return; } GNUNET_CRYPTO_eddsa_key_get_public (my_private_key, &my_identity.public_key); /* start reading */ read_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, udp_sock, &sock_read, NULL); ch = GNUNET_TRANSPORT_communicator_connect (cfg, COMMUNICATOR_CONFIG_SECTION, COMMUNICATOR_ADDRESS_PREFIX, GNUNET_TRANSPORT_CC_RELIABLE, &mq_init, NULL, ¬ify_cb, NULL); is = GNUNET_NT_scanner_init (); nat = GNUNET_NAT_register (cfg, COMMUNICATOR_CONFIG_SECTION, IPPROTO_UDP, 1 /* one address */, (const struct sockaddr **) &in, &in_len, &nat_address_cb, try_connection_reversal, NULL /* closure */); if (NULL == ch) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return; } ah = GNUNET_TRANSPORT_application_init (cfg); if (NULL == ah) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return; } /* start broadcasting */ // if (GNUNET_YES != // GNUNET_CONFIGURATION_get_value_yesno (cfg, // COMMUNICATOR_CONFIG_SECTION, // "DISABLE_BROADCAST")) // { // broadcast_task = GNUNET_SCHEDULER_add_now (&do_broadcast, NULL); // } } int main (int argc, char *const *argv) { static const struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_OPTION_END }; int ret; GNUNET_log_from_nocheck (GNUNET_ERROR_TYPE_DEBUG, "transport", "Starting quic communicator\n"); if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) return 2; ret = (GNUNET_OK == GNUNET_PROGRAM_run (argc, argv, "gnunet-communicator-quic", _ ("GNUnet QUIC communicator"), options, &run, NULL)) ? 0 : 1; GNUNET_free_nz ((void *) argv); return ret; }