diff --git a/README.md b/README.md index 906942d..b237a3f 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ A high-performance covert channel over DNS, powered by QUIC multipath.
+
Page not found :(
+The requested page could not be found.
+
+
+
+ Exfiltrating a 10 MB file over a single DNS resolver. +
diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..0fcd1a9 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,27 @@ +--- +title: Introduction +nav_order: 0 +--- + +# Introduction + +slipstream is a novel DNS tunnel leveraging the QUIC protocol under the hood. + +DNS tunneling is a technique that allows for data to be transmitted over DNS queries and responses. +Such a tunnel can be set up in any network with access to a DNS resolver, even when the client does not have direct access to the internet. +DNS tunneling enables attackers to exfiltrate data, establish Command and Control communication, or bypass network restrictions. +Unfortunately, the DNS protocol is quite limited, with a maximum message size, the use of a request-response model, and employed rate-limiting by resolvers. +To overcome these limitations, tools such as Iodine, or OzymanDNS design custom reliable protocols on top of DNS to allow for streaming continuous data over the tunnel. +However, they are often limited, with throughput often not exceeding several hundred kilobytes per second. +By carefully implementing QUIC compatibility layers, we're able to leverage QUIC's reliability guarantees and introduce a high-performance DNS tunnel. + +## Quick start + +Follow the [installation](/slipstream/installation/) and [usage instruction](/slipstream/usage/) guides. + +## Benchmarks + +In our [benchmark](/slipstream/benchmark/), we show that slipstream achieves a 10-fold time decrease in exfiltrating a 10 MB file, while using up to 15% less queries. +For downloading files into the restricted network, slipstream uses up to 37% less queries. +Using QUIC congestion control, it accurately determines the query rate of the used DNS resolver. +Finally, QUIC multipath allows slipstream to combine the bandwidth of multiple resolvers to largely improve its performance, even when those resolvers have different round-trip times, loss rates, or rate limits. diff --git a/docs/installation/build.md b/docs/installation/build.md new file mode 100644 index 0000000..ab82728 --- /dev/null +++ b/docs/installation/build.md @@ -0,0 +1,44 @@ +--- +title: Local build +parent: Installation +nav_order: 23 +--- + +# Building slipstream locally + +To build slipstream locally on debian-based distros, you need the following dependencies installed: + +* cmake +* git +* pkg-config +* libssl-dev +* ninja-build +* clang + +Clone the slipstream repo and its submodules recursively. +This fetches slipstream, [SPCDNS](https://github.com/spc476/SPCDNS), [lua-resty-base-encoding](https://github.com/spacewander/lua-resty-base-encoding), and our [picoquic fork](https://github.com/EndPositive/slipstream-picoquic/). + +```shell +$ git clone --recurse-submodules https://github.com/EndPositive/slipstream.git +``` + +You can then configure slipstream by running the following command: + +```shell +# Configure CMake +$ cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_MAKE_PROGRAM=ninja \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -G Ninja \ + -S . \ + -B build +# Build the client and server binaries +$ cmake \ + --build build \ + --target slipstream-client slipstream-server +``` + +This will place the client and server binaries in the `build/` directory. +These are dynamically linked binaries against OpenSSL and GNU C. diff --git a/docs/installation/docker.md b/docs/installation/docker.md new file mode 100644 index 0000000..2cd320d --- /dev/null +++ b/docs/installation/docker.md @@ -0,0 +1,45 @@ +--- +title: Docker image +parent: Installation +nav_order: 22 +--- + +# GitHub Container Registry + +Docker images are published to the GHCR at every release. +The client and server are packaged in different images. + +* [ghcr.io/endpositive/slipstream-client](https://ghcr.io/endpositive/slipstream-client) +* [ghcr.io/endpositive/slipstream-server](https://ghcr.io/endpositive/slipstream-server) + +### Tags + +* latest +* vX.X.X + +# Usage + +The client requires port 5201 to be forwarded to the host. +The server requires port 53 to be forwarded. + +```shell +$ docker run \ + --rm \ + -p 53:53 \ + ghcr.io/endpositive/slipstream-server:v0.0.1 \ + --target-address=x.x.x.x:yy \ + --domain=test.com +``` + +```shell +$ docker run \ + --rm \ + -p 5201:5201 \ + ghcr.io/endpositive/slipstream-server:v0.0.1 \ + --domain=test.com \ + --resolver=1.1.1.1:53 +``` + +Any TCP connections on the client's port `5201` will now be forwarded to `x.x.x.x:yy`. +You could also run a slipstream on a different port than 53, but then a public resolver won't be able to reach the server. +This may be useful in scenarios where you setup a direct connection between the client and server rather than through public DNS infrastructure. diff --git a/docs/installation/gh.md b/docs/installation/gh.md new file mode 100644 index 0000000..ab2a555 --- /dev/null +++ b/docs/installation/gh.md @@ -0,0 +1,18 @@ +--- +title: GitHub releases +parent: Installation +nav_order: 21 +--- + +# GitHub releases + +slipstream binaries are published to GitHub at every in release. +These are dynamically linked binaries against OpenSSL and GNU C. +Visit the latest GitHub release [here](https://github.com/EndPositive/slipstream/releases/latest). + +```shell +$ wget https://github.com/EndPositive/slipstream/releases/download/v0.0.1/slipstream-client-v0.0.1-linux-x86_64 +``` +```shell +$ wget https://github.com/EndPositive/slipstream/releases/download/v0.0.1/slipstream-server-v0.0.1-linux-x86_64 +``` diff --git a/docs/installation/index.md b/docs/installation/index.md new file mode 100644 index 0000000..ca9b8e8 --- /dev/null +++ b/docs/installation/index.md @@ -0,0 +1,8 @@ +--- +title: Installation +nav_order: 20 +--- + +# Installation + +Download slipstream through GitHub releases, Docker (GitHub Container Registry), or build slipstream locally. diff --git a/docs/protocol.md b/docs/protocol.md new file mode 100644 index 0000000..85a749e --- /dev/null +++ b/docs/protocol.md @@ -0,0 +1,157 @@ +--- +title: Protocol Design +nav_order: 40 +--- + +## Protocol Design + +slipstream acts as a TCP proxy server, tunneling the streaming data between a client and a server over DNS, supporting the use of different DNS resolvers in parallel. +A major aspect of slipstream is the use of QUIC[^QUIC] multipath[^QUICMP] as the transport protocol. + +[^QUIC]: [QUIC: A UDP-Based Multiplexed and Secure Transport (RFC 9000)](https://datatracker.ietf.org/doc/html/rfc9000) +[^QUICMP]: [Multipath Extension for QUIC (draft-ietf-quic-multipath-14)](https://datatracker.ietf.org/doc/draft-ietf-quic-multipath/14/) + +### Protocol Stack + +Following [dnstt](https://www.bamsoftware.com/software/dnstt/)'s approach outlined in TurboTunnel[^TurboTunnel], we decouple the encoding layer from the session and reliability layers. +Compared to dnstt, QUIC combines the [KCP](https://github.com/xtaci/kcp-go), [SMUX](https://github.com/xtaci/smux), and [Noise](https://github.com/flynn/noise) layers into a single protocol, as shown below. +slipstream itself does not implement any reliability based on DNS message status codes, keeping reliability management completely within QUIC. +A DNS message with a failure response code does not necessarily mean that the message was not delivered to the DNS tunneling server. + +[^TurboTunnel]: [David Fifield. Turbo Tunnel, a good way to design censorship circumvention protocols. FOCI 2020.](https://www.usenix.org/conference/foci20/presentation/fifield) + +
+
+
+ Protocol stack comparison between slipstream and dnstt. +
+ +QUIC implementations are abundant, allowing us to pick and choose an implementation that fits the pull interface proposed in TurboTunnel. +The main selection criteria for a QUIC implementation is support for a pull interface and the multipath QUIC extension. +The latter deemed especially challenging as this RFC is going through many changes in each version of the proposal. +As of writing, the only libraries supporting multipath QUIC are [mp-quic](https://github.com/qdeconinck/mp-quic), [TQUIC](https://github.com/Tencent/tquic) (draft v5), [XQUIC](https://github.com/alibaba/xquic) (draft v10), and [picoquic](https://github.com/private-octopus/picoquic) (draft v11). +We've found picoquic to be the simplest implementation to work with, supporting the latest proposals and providing a pull interface for sending and receiving packets. + +### Reduced Header Overhead + +QUIC optimizes the header size. Compared to dnstt, this allows slipstream to reduce the header overhead from 59 bytes to 24 bytes (see the detailed bytefield diagram below). +QUIC uses variable-length integers to encode certain fields, allowing for more efficient encoding of small numbers. +Using a single protocol also ensures that there is no redundancy in the header, which is an issue as found in the repeated length fields in the combination of TurboTunnel, KCP, SMUX, and Noise in dnstt. +For encoding and decoding QUIC packets in the DNS message, slipstream follows dnstt and encodes data into the domain name using base32 and puts raw data in TXT response records. +slipstream does not add any additional headers or fields on top of QUIC. +While dnstt adds a packet length marker, we rely on QUIC to add packet length markers when needed (e.g. when coallescing multiple packets into a single DNS message). +For dnstt's random padding cache-breaking solution, we also rely on QUIC to use unique packet IDs (see QUIC[^QUIC] section 12.3.). +QUIC frames to be retransmitted are wrapped in new QUIC packets (see QUIC[^QUIC] section 13.2.1.), which ensures that retransmissions are unique too, preventing the need for additional random padding as seen in dnstt. +Finally, QUIC already tracks a connection ID in the header, so we do not need to introduce an additional connection ID field in the header either. + +
+
+
+ Comparison of slipstream and dnstt packet formats. +
+ +Another consideration is that QUIC expects the `Initial` packets to have a minimum size of 1200 bytes to prevent amplification attacks. +Since DNS messages are so limited in size, we have to reduce this limit on both the client and the server side depending on the size of the domain name used. +Of course, we could still integrate response rate-limiting on unauthenticated client IDs. +We could also further delay the server response until a certain number of initial frames have been received, for example based on the expected crypto message size. + +### Congestion Control + +Achieving high performance in DNS tunneling requires careful management of query rates to maximize throughput while avoiding rate limits imposed by DNS resolvers. +In addition, with larger RTTs, DNS tunnels suffer from low performance[^nussbaumRobustCovertChannels2009], showing the importance of a good congestion control algorithm. +Since the bandwidth of a single stream is mostly based on the RTT and window size (i.e. related to bandwidth-delay-product), managing the window size is crucial. +While QUIC supports congestion control algorithms, we need to make several changes: + +* On the client side, we need to choose a congestion control algorithm. As a DNS resolver hits its rate-limit, it drops or responds to the client without forwarding the message to the server. In addition, as a DNS tunneling server is processing a high number of DNS requests, it may experience congestion and slow down the response time. Thus, we choose a congestion control algorithm that balances latency and loss signals (DCUBIC in picoquic). + +* On the server side, we disable the congestion control algorithm entirely, and remove the congestion window. This allows the server to always respond to incoming DNS queries, maximizing its use of limited opportunities to send responses. We assume that if a DNS query can reach the server, it must be able to reach back too, as the DNS resolver's rate-limit may be the primary bottleneck in the network path. + +* While QUIC attempts to prevent an “infinite feedback loop of acknowledgments” (see QUIC[^QUIC] section 13.2.1.) on non-ACK-eliciting frames by delaying acknowledgments up to `max_ack_delay`, the server may not be able to respond to the client's acknowledgments in time if no further DNS query arrives. We ensure that this loss signal is not considered by the QUIC congestion control algorithm as to prevent unnecessary lowering of the congestion window. + +* As a DNS resolver may use multiple threads, DNS messages may not be delivered in order. In preliminary testing, we observed that DNS messages may be received out of order by hundreds of packets. To ensure that this signal does not influence the congestion control algorithm, we ignore out-of-order loss signals and instead rely solely on RACK signals[^RACK]. + +[^nussbaumRobustCovertChannels2009]: Lucas Nussbaum, Pierre Neyron and Olivier Richard. On Robust Covert Channels Inside DNS. 24th IFIP International Security Conference. 2009. +[^RACK]: [The RACK-TLP Loss Detection Algorithm for TCP (RFC 8985)](https://datatracker.ietf.org/doc/html/rfc8985) + +### Connection IDs and Multipath + +QUIC relies on the connection ID to track individual connections, rather than a source address. +The QUIC multipath extension[^QUICMP] in addition proposes to use multiple network paths simultaneously using this connection ID, effectively allowing to aggregate the individual bandwidth limits of multiple paths. +In the case of DNS tunneling, each path corresponds to a different DNS resolver, each with its own network conditions and rate limit. + +Although QUIC primarily uses a connection ID for tracking individual connections, it relies on source addresses for path management (i.e. migration). +Since DNS packets may arrive at the DNS tunneling server from any source address, we hard-code a dummy address into every received packet before passing it to QUIC. +As a result, several QUIC features should be manually disabled. +For example, “A `PATH_RESPONSE` frame MUST be sent on the network path where the `PATH_CHALLENGE` frame was received.” (see QUIC[^QUIC] section 8.2.2.) ensures that a path is valid in both directions, while our DNS tunnel server is only ever aware of a single path. +And, “An endpoint can migrate a connection to a new local address by sending packets containing non-probing frames from that address.” (see QUIC[^QUIC] section 9.2.) implies that the server would be aware if the client changes its source address, while our DNS tunnel server only ever receives a dummy address (instead of constantly changing DNS resolver addresses). + +### Polling + +The polling mechanism in slipstream builds upon dnstt's strategy of replying to every server reply with a poll message. +To ensure that poll messages are considered as part of QUIC's congestion control counters in slipstream, we introduce a new QUIC frame type for polling. +The poll frame is a frame with size 0, such that the only space used is the frame type. +To prevent an infinitely growing number of poll messages pinging back-and-forth, QUIC should consider poll frames as non-ACK-eliciting frames, allowing QUIC to ignore responding to poll messages when the server has no data to send, in turn preventing new poll messages from being sent at the client. +If possible, we replace an outgoing poll message with a real frame that contains data. +This ensures that when the server is sending data, the poll messages won't take precedence over the client's outgoing data frames. +We enable the QUIC keep-alive option to ensure that even when there is no data between the client and the server, the server may still initiate data transmission by responding to a keep-alive packet. +This keep-alive period largely influences the server-to-client latency while idle and the number of packets sent while idle and should be manually tuned based on the user's requirements. + +### Encryption + +QUIC provides built-in encryption, mutual authentication, and integrity checks. +This means that we do not need to implement a custom solution, reducing the complexity of the tunneling protocol. + +### Timings + +DNS resolvers may keep track of round-trip-times of authoritative nameservers and drop or retransmit packets for responses that arrive too late. +For example, Unbound DNS resolver keeps track of ping, variance, and RTT for its timeout management[^UnboundTimeoutManagement]. +Thus, we ensure that the DNS tunnel server responds as soon as possible to a DNS query and does not buffer any messages for responding to later. + +[^UnboundTimeoutManagement]: [Unbound Timeout Information](https://www.nlnetlabs.nl/documentation/unbound/info-timeout/) + +### Performant Encoding + +In high performance tunneling where the bottleneck becomes the tunnel performance, the speed of encoding and decoding is crucial. +There is a trade-off between the encoding and decoding speed and the features of the DNS encoding library. +To support future use of different record types in the encoding, we choose to use a DNS library allowing for encoding and decoding of arbitrary DNS records. +While initially using the [c-ares](https://github.com/c-ares/c-ares) library, but it was found to be decreasing the performance of the tunnel. +We settled on the [SPCDNS](https://github.com/spc476/SPCDNS) library, which keeps the processing time of encoding and decoding to a minimum. +Although the library is very bare-bones, it is quite sufficient for our use-case. + +Similarly, the performance of base32 encoding is crucial. +In slipstream, we use the implementation of [lua-resty-base-encoding](https://github.com/spacewander/lua-resty-base-encoding), which has proven to be one of the fastest implementations available. + +### Socket loop + +For tunneling purposes, we need to poll on 2 sockets: one for the TCP stream and one for the DNS messages. +When DNS messages arrive, we decode the message and pass it to the QUIC library using `picoquic_incoming_packet_ex`. +To improve the performance, we allow multiple packets to be received before attempting to respond. +For sending retransmissions, acknowledgments, or new data on the TCP stream, we poll on the QUIC library using `picoquic_prepare_next_packet_ex`. + +In the server side, we buffer the multiple incoming DNS messages in a FIFO queue, such that we can respond to them in order. +Before passing on the QUIC packet, we spoof the source address of the incoming DNS message to a dummy address, and annotate the DNS message with the original source address and connection ID. +Once ready to send a response, we pull the next QUIC packet on the related connection, encode it in the DNS response, and send it over the UDP socket to the correct destination address. + +For sending data, picoquic requires the program to keep track of active and inactive streams, only requesting data from the stream when it is active. +This is a bit cumbersome, as our tunnel constantly needs to poll the TCP socket in a separate thread and wake up the main picoquic when there is data to send. + +
+
+
+ The socket loop's data flow. +
diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..271fa1b --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,114 @@ +--- +title: Usage instructions +nav_order: 30 +--- + +# Usage instructions + +slipstream is designed to tunnel TCP traffic over DNS messages. +Since DNS is a distributed system, we can abuse existing DNS infrastructure in the tunnel. +For example, the figure below shows multiple network routes using a public DNS resolver to pass through the local or country scoped firewall. +This is especially useful when a network mandates the use of a DNS resolver as assigned in the DHCP configuration. + + + +slipstream consists of a server and client binary. + +#### Server + +The server is the one to be placed on the outside of the restricted network. +It will act as the authoritative nameserver for a given domain. +It will forward received connections to a TCP service specified in the CLI arguments. + +```shell +$ slipstream-server + --target-address=x.x.x.x:yy \ # TCP address of the service to access + --domain=test.com +``` + +#### Client + +The client is placed inside the restricted network. + + +```shell +$ slipstream-client \ + --resolver-address=x.x.x.x:yy \ # Address of public DNS resolver or DHCP assigned resolver + --domain=test.com +``` + + +### Configuration of DNS records + +Assumming you own `test.com`, you should configure the DNS records such that your slipstream server is configured as the authoritative nameserver of that domain. +For example, add a NS record for `test.com` pointing to `ns.test.com`. +Then add an A record on `ns.test.com` pointing to your slipstream server IP. + +``` +@ IN NS ns.test.com. +ns IN A x.x.x.x:yy ; # Address of slipstream server +``` + +### Direct connection + +It is also possible to setup a direct connection between the client and the server. +This allows to impersonate DNS traffic on port 53 without actually using any public infrastructure. +This is a similar trick to using WireGuard on port 53, additionally encoding as DNS traffic. + +```shell +$ slipstream-client \ + --congestion-control=bbr \ # Faster better than dcubic in direct connections + --resolver-address=x.x.x.x:yy \ # Address of slipstream server + --domain=test.com +``` + +## Example data transfer + +An example of a sending data from the client to the server over a direct slipstream connection. + +```shell +$ nc -l -p 5201 +$ slipstream-server \ + --dns-listen-port=8853 \ + --target-address=127.0.0.1:5201 \ + --domain=test.com +``` + +```shell +$ slipstream-client \ + --congestion-control=bbr \ + --tcp-listen-port=7000 \ + --resolver=127.0.0.1:8853 \ + --domain=test.com +Adding 127.0.0.1:8853 +Starting connection to 127.0.0.1 +Initial connection ID: 54545454 +Listening on port 7000... +Connection completed, almost ready. +Connection confirmed. +``` + +You can then connect to the slipstream client on port 7000 as if you were connecting to the nc client on port 5201. + +```shell +$ base64 /dev/urandom | head -c 5000000 | nc 127.0.0.1 7000 + +# slipstream client wakes up +[0:9] accept: connection +[0:9] wakeup +[0:9] activate: stream +[0:9] recv->quic_send: empty, disactivate +[0:9] wakeup +[0:9] activate: stream +[0:9] recv->quic_send: empty, disactivate +[0:9] wakeup +[0:9] activate: stream +[0:9] recv->quic_send: empty, disactivate +[0:9] recv: closed stream +``` +```shell +# base64 data arrives on the server +S9w3u5up+c39u6vrkBtxKbSxOJA2UElczDgc3x4h3TtZtzvgMX05Ig4whEYDvY5MP8g4dJ1QsXX1 +fSDm0y6mOlQ4fQhYchkyKt18fV0tpBkLrPwv6MkW+IaksKe7Qo61s3gxu2jrPBlC1yxML+rYZU93 +MYNB7rFC6s3a0eHmfdsfbtBbFIF809X91fqd6gYiKPtWAHc0J5OsEyqMI3QcUGSDJd4Sw+iAC5X7 +``` diff --git a/include/slipstream.h b/include/slipstream.h index 8288f5c..852fc12 100644 --- a/include/slipstream.h +++ b/include/slipstream.h @@ -23,7 +23,7 @@ typedef struct st_address_t { } address_t; int picoquic_slipstream_client(int listen_port, struct st_address_t* server_addresses, size_t server_address_count, const char* domain_name, - const char* cc_algo_id, bool gso); + const char* cc_algo_id, bool gso, size_t keep_alive_interval); int picoquic_slipstream_server(int server_port, const char* pem_cert, const char* pem_key, struct sockaddr_storage* target_address, const char* domain_name); diff --git a/src/slipstream_client.c b/src/slipstream_client.c index 3e91ded..096077a 100644 --- a/src/slipstream_client.c +++ b/src/slipstream_client.c @@ -91,6 +91,8 @@ ssize_t client_encode_segment(dns_packet_t* packet, size_t* packet_len, const un } ssize_t client_encode(void* slot_p, void* callback_ctx, unsigned char** dest_buf, const unsigned char* src_buf, size_t src_buf_len, size_t* segment_len, struct sockaddr_storage* peer_addr, struct sockaddr_storage* local_addr) { + *dest_buf = NULL; + // optimize path for single segment if (src_buf_len <= *segment_len) { #ifdef NOENCODE @@ -658,9 +660,6 @@ static int slipstream_connect(struct sockaddr_storage* server_address, return -1; } - // 400ms - picoquic_enable_keep_alive(*cnx, 400000); - /* Document connection in client's context */ client_ctx->cnx = *cnx; /* Set the client callback context */ @@ -683,7 +682,7 @@ static int slipstream_connect(struct sockaddr_storage* server_address, return ret; } -int picoquic_slipstream_client(int listen_port, struct st_address_t* server_addresses, size_t server_address_count, const char* domain_name, const char* cc_algo_id, bool gso) { +int picoquic_slipstream_client(int listen_port, struct st_address_t* server_addresses, size_t server_address_count, const char* domain_name, const char* cc_algo_id, bool gso, const size_t keep_alive_interval) { /* Start: start the QUIC process */ int ret = 0; uint64_t current_time = 0; @@ -747,6 +746,12 @@ int picoquic_slipstream_client(int listen_port, struct st_address_t* server_addr return -1; } + if (keep_alive_interval != 0) { + picoquic_enable_keep_alive(cnx, keep_alive_interval * 1000); + } else { + picoquic_disable_keep_alive(cnx); + } + // Create listening socket client_ctx.listen_sock = socket(AF_INET, SOCK_STREAM, 0); if (client_ctx.listen_sock < 0) { diff --git a/src/slipstream_client_cli.c b/src/slipstream_client_cli.c index 94a5345..bfa0057 100644 --- a/src/slipstream_client_cli.c +++ b/src/slipstream_client_cli.c @@ -22,6 +22,7 @@ static struct argp_option options[] = { {"congestion-control", 'c', "ALGO", 0, "Congestion control algorithm (bbr, dcubic) (default: dcubic)", 0}, {"gso", 'g', "BOOL", OPTION_ARG_OPTIONAL, "GSO enabled (true/false) (default: false). Use --gso or --gso=true to enable.", 0}, {"domain", 'd', "DOMAIN", 0, "Domain name used for the covert channel (Required)", 0}, + {"keep-alive-interval", 't', "MS", 0, "Send keep alive pings at this interval (default: 400, disabled: 0)", 0}, {0} // End of options }; @@ -33,6 +34,7 @@ struct arguments { size_t resolver_count; char* cc_algo_id; bool gso; + size_t keep_alive_interval; }; /* Client mode parser */ @@ -97,6 +99,13 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state) { argp_error(state, "Invalid boolean value for --gso: '%s'. Use 'true' or 'false'.", arg); } break; + case 't': + arguments->keep_alive_interval = atoi(arg); + if (arguments->keep_alive_interval < 0) { + argp_error(state, "Invalid keep alive interval: %s", arg); + } + break; + case ARGP_KEY_ARG: // No positional arguments expected argp_usage(state); @@ -130,6 +139,7 @@ int main(int argc, char** argv) { arguments.gso = false; // Default GSO state arguments.resolver_addresses = NULL; arguments.resolver_count = 0; + arguments.keep_alive_interval = 400; // Default keep alive interval // Ensure output buffers are flushed immediately (useful for debugging/logging) setbuf(stdout, NULL); @@ -165,7 +175,8 @@ int main(int argc, char** argv) { arguments.resolver_count, arguments.domain_name, arguments.cc_algo_id, - arguments.gso + arguments.gso, + arguments.keep_alive_interval ); // Free allocated memory for resolver addresses diff --git a/src/slipstream_inline_dots.c b/src/slipstream_inline_dots.c index 83111e0..ecf776c 100644 --- a/src/slipstream_inline_dots.c +++ b/src/slipstream_inline_dots.c @@ -1,29 +1,35 @@ #include "slipstream_inline_dots.h" size_t slipstream_inline_dotify(char * __restrict__ buf, size_t buflen, size_t len) { - size_t dots = len / 57; // Number of dots to insert - size_t new_len = len + dots; - - // Check if result would exceed buffer - if (new_len > buflen) { - return -1; // Error condition + if (len == 0) { // If there's nothing to do, we do nothing. Efficient, right? + if (buflen > 0) buf[0] = '\0'; + return 0; } - // Start from the end and work backwards - char *src = buf + len - 1; // Points to last char of original string - char *dst = buf + new_len - 1; // Points to where last char will end up + size_t dots = len / 57; // Every 57 bytes, a dot. It's the law. + size_t new_len = len + dots; + + if (new_len + 1 > buflen) { + return (size_t)-1; // Err -> .h? Meh. + } + + buf[new_len] = '\0'; // Amen. + + char *src = buf + len - 1; // Points to last char of original string + char *dst = buf + new_len - 1; // Points to where last char will end up // Avoid modulo operation in tight loop size_t next_dot = len - (len % 57); + size_t current_pos = len; // Move characters right-to-left, inserting dots while (current_pos > 0) { if (current_pos == next_dot) { - *dst-- = '.'; - next_dot -= 57; - current_pos--; - continue; + *dst-- = '.'; // Dot. Because rules are rules, even for dots. + next_dot -= 57; // Next dot is 57 chars back. + current_pos--; // Account for the char space the dot took. + continue; // Skip the copy for this iteration, already placed dot. } *dst-- = *src--; current_pos--; @@ -36,14 +42,12 @@ size_t slipstream_inline_undotify(char * __restrict__ buf, size_t len) { char *reader = buf; char *writer = buf; - // For ~255 byte buffer with dots every ~50 chars - // Simple loop is most efficient since dots are sparse - while (len--) { + for (size_t i = 0; i < len; ++i) { char c = *reader++; - if (c != '.') { - *writer++ = c; - } + if (c != '.') + *writer++ = c; // Filter out the noise. Only the good stuff. } + *writer = '\0'; // Rewind the tape. The future is clean. return writer - buf; -} \ No newline at end of file +} diff --git a/src/slipstream_server.c b/src/slipstream_server.c index 82ba035..1a1f165 100644 --- a/src/slipstream_server.c +++ b/src/slipstream_server.c @@ -31,6 +31,8 @@ char* server_domain_name = NULL; size_t server_domain_name_len = 0; ssize_t server_encode(void* slot_p, void* callback_ctx, unsigned char** dest_buf, const unsigned char* src_buf, size_t src_buf_len, size_t* segment_len, struct sockaddr_storage* peer_addr, struct sockaddr_storage* local_addr) { + *dest_buf = NULL; + // we don't support segmentation in the server assert(segment_len == NULL || *segment_len == 0 || *segment_len == src_buf_len); diff --git a/src/slipstream_sockloop.c b/src/slipstream_sockloop.c index 79c63f6..f8652db 100644 --- a/src/slipstream_sockloop.c +++ b/src/slipstream_sockloop.c @@ -126,7 +126,7 @@ int slipstream_packet_loop_(picoquic_network_thread_ctx_t* thread_ctx, picoquic_ slot->path_id = -1; nb_slots_written++; - unsigned char* decoded; + unsigned char* decoded = NULL; bytes_recv = param->decode(slot, thread_ctx->loop_callback_ctx, &decoded, (const unsigned char*)buffer, bytes_recv, &peer_addr, &local_addr); if (bytes_recv < 0) { @@ -208,7 +208,7 @@ int slipstream_packet_loop_(picoquic_network_thread_ctx_t* thread_ctx, picoquic_ int sock_err = 0; int bytes_sent; - unsigned char* encoded; + unsigned char* encoded = NULL; size_t segment_len = send_msg_size == 0 ? send_length : send_msg_size; ssize_t encoded_len = param->encode(slot, loop_callback_ctx, &encoded, (const unsigned char*)send_buffer, send_length, &segment_len, &peer_addr, &local_addr); @@ -279,7 +279,7 @@ int slipstream_packet_loop_(picoquic_network_thread_ctx_t* thread_ctx, picoquic_ int sock_err = 0; int bytes_sent; - unsigned char* encoded; + unsigned char* encoded = NULL; size_t segment_len = send_msg_size == 0 ? send_length : send_msg_size; ssize_t encoded_len = param->encode(slot, loop_callback_ctx, &encoded, (const unsigned char*)send_buffer, send_length, &segment_len, &peer_addr, &local_addr);