diff --git a/.dockerignore b/.dockerignore index 9810cc3..6a43501 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,5 @@ * !certs/ -!src/ !extern/ !include/ !src/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e6a751..f2e300d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,13 +20,9 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) endif() if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") - list(APPEND PICOQUIC_ADDITIONAL_C_FLAGS -Og) - list(APPEND PICOQUIC_ADDITIONAL_CXX_FLAGS -Og) list(APPEND PICOQUIC_COMPILE_DEFINITIONS BUILD_LOGLIB) set(BUILD_LOGLIB ON) else() - list(APPEND PICOQUIC_ADDITIONAL_C_FLAGS -O3) - list(APPEND PICOQUIC_ADDITIONAL_CXX_FLAGS -O3) set(BUILD_LOGLIB OFF) endif() @@ -35,18 +31,13 @@ if(POLICY CMP0048) endif() add_subdirectory(extern/picoquic) -add_executable(slipstream - src/slipstream.c - src/slipstream_client.c +# Common source files for both client and server +set(COMMON_SOURCES src/slipstream_inline_dots.c - src/slipstream_resolver_addresses.c - src/slipstream_server.c - src/slipstream_server_cc.c src/slipstream_sockloop.c src/slipstream_utils.c include/slipstream.h include/slipstream_inline_dots.h - include/slipstream_resolver_addresses.h include/slipstream_server_cc.h include/slipstream_slot.h include/slipstream_sockloop.h @@ -66,13 +57,34 @@ add_executable(slipstream extern/SPCDNS/src/output.c extern/SPCDNS/src/output.h ) -target_link_libraries(slipstream PRIVATE m) -target_link_libraries(slipstream PRIVATE picoquic-core) + +# Client binary +add_executable(slipstream-client + src/slipstream_client_cli.c + src/slipstream_client.c + ${COMMON_SOURCES} +) +target_link_libraries(slipstream-client PRIVATE m) +target_link_libraries(slipstream-client PRIVATE picoquic-core) if (BUILD_LOGLIB) - target_link_libraries(slipstream PRIVATE picoquic-log) + target_link_libraries(slipstream-client PRIVATE picoquic-log) endif () +target_include_directories(slipstream-client PRIVATE include) +target_include_directories(slipstream-client PRIVATE extern) +set_picoquic_compile_settings(slipstream-client) -target_include_directories(slipstream PRIVATE include) -target_include_directories(slipstream PRIVATE extern) - -set_picoquic_compile_settings(slipstream) +# Server binary +add_executable(slipstream-server + src/slipstream_server_cli.c + src/slipstream_server.c + src/slipstream_server_cc.c + ${COMMON_SOURCES} +) +target_link_libraries(slipstream-server PRIVATE m) +target_link_libraries(slipstream-server PRIVATE picoquic-core) +if (BUILD_LOGLIB) + target_link_libraries(slipstream-server PRIVATE picoquic-log) +endif () +target_include_directories(slipstream-server PRIVATE include) +target_include_directories(slipstream-server PRIVATE extern) +set_picoquic_compile_settings(slipstream-server) diff --git a/Dockerfile b/Dockerfile index 6a575e5..480595f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim AS development +FROM debian:bookworm-slim AS builder WORKDIR /usr/src/app @@ -12,8 +12,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ninja-build \ clang -FROM development AS builder - COPY . . RUN --mount=type=cache,target=/usr/src/app/cmake-build-release \ @@ -27,22 +25,20 @@ RUN --mount=type=cache,target=/usr/src/app/cmake-build-release \ -B /usr/src/app/cmake-build-release && \ cmake \ --build /usr/src/app/cmake-build-release \ - --target slipstream \ - -j 18 && cp cmake-build-release/slipstream . + --target slipstream-client slipstream-server \ + -j 18 && \ + cp cmake-build-release/slipstream-client . && \ + cp cmake-build-release/slipstream-server . -FROM debian:bookworm-slim +FROM gcr.io/distroless/base-debian12 WORKDIR /usr/src/app -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - apt-get update && apt-get install -y \ - libssl3 - COPY ./certs/ ./certs/ -RUN mkdir -p ./qlog/ +ENV PATH=/usr/src/app/:$PATH -COPY --from=builder /usr/src/app/slipstream . +COPY --from=builder --chmod=755 /usr/src/app/slipstream-client ./client +COPY --from=builder --chmod=755 /usr/src/app/slipstream-server ./server -ENTRYPOINT ["/usr/src/app/slipstream"] +ENTRYPOINT [] diff --git a/README.md b/README.md index f929cca..fb20c57 100644 --- a/README.md +++ b/README.md @@ -1 +1,127 @@ # Slipstream + +A high-performance covert channel over DNS, powered by QUIC multipath. + +## Highlights + +* Adaptive congestion control for rate-limited resolvers +* Parallel routing over multiple multiple rate-limited resolvers +* 60% lower header overhead than DNSTT + +## Quickstart + +Download a release binary from GitHub. +The binary contains both the client and the server. + +The server listens for DNS messages and attempts to decode QUIC message from them. +Any new QUIC streams opened will be forwarded to a specified TCP service. +For example, we can start a simple nc listener and configure the slipstream server to connect to it. + +```shell +$ nc -l -p 5201 +$ slipstream-server \ + --dns-listen-port=8853 \ + --cert=certs/cert.pem \ + --key=certs/key.pem \ + --target=127.0.0.1:5201 \ + --domain=test.com +``` + +The client listens on a TCP port for incoming connections. +It opens a QUIC connection through the resolver specified. +For every TCP connection it accepts, a new QUIC stream will be opened. +In this example, we connect to the slipstream server running on port 8853. + +```shell +$ echo "127.0.0.1 8853" > resolvers.txt +$ slipstream-client \ + --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 + +# base64 data arrives on the server +S9w3u5up+c39u6vrkBtxKbSxOJA2UElczDgc3x4h3TtZtzvgMX05Ig4whEYDvY5MP8g4dJ1QsXX1 +fSDm0y6mOlQ4fQhYchkyKt18fV0tpBkLrPwv6MkW+IaksKe7Qo61s3gxu2jrPBlC1yxML+rYZU93 +MYNB7rFC6s3a0eHmfdsfbtBbFIF809X91fqd6gYiKPtWAHc0J5OsEyqMI3QcUGSDJd4Sw+iAC5X7 +``` + +## Real network scenario + +You can try this out on a real network (if you have permission). +First, you need to have a server outside of the network you want to escape. +For a domain name you own, setup the DNS records to point to your nameserver. +This ensures that queries for subdomains of `test.com` will be forwarded to your server. + +``` +test.com NS ns.test.com +ns.test.com A 12.23.34.45 +``` + +Then run the slipstream server on port 53 (requires elevated privileges) and instruct the client to use a real DNS resolver. + +# Benchmarks + +Comparison of slipstream and other existing DNS tunneling tools can be found in the [EndPositive/dns-tunneling-benchmark]([https://github.com/EndPositive/dns-tunneling-benchmark]) repository. + +Main findings: + +* 42x faster than dnstt for direct connections +* 23/19 Mbps upload/download speed for direction connections +* automatically maximizes query rate according to resolver rate-limit + +# Building from source + +```shell +# build deps on debian: cmake, pkg-config, libssl-dev, ninja-build, clang +$ git clone --recurse-submodules https://github.com/EndPositive/slipstream.git +$ cd slipstream/ +$ cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_MAKE_PROGRAM=ninja \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -G Ninja \ + -S . \ + -B ./build +$ cmake \ + --build ./build \ + --target slipstream \ + -j 18 +# mark as executable and install to your system +$ chmod +x ./build/slipstream +$ mv ./build/slipstream ~/.local/bin +``` + +# Acknowledgements + +David Fifield's DNSTT and Turbo Tunnel concept has been a massive source of inspiration. +Although slipstream inherits no code, this work could not have been possible without his ideas. + +# License + +This work is licensed under the Apache License, Version 2.0. diff --git a/include/slipstream.h b/include/slipstream.h index 07dfab9..8288f5c 100644 --- a/include/slipstream.h +++ b/include/slipstream.h @@ -17,12 +17,16 @@ extern "C" { #define SLIPSTREAM_QLOG_DIR "./qlog"; #include +typedef struct st_address_t { + struct sockaddr_storage server_address; + bool added; +} address_t; -int picoquic_slipstream_client(int listen_port, char const* resolver_addresses_filename, const char* domain_name, +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_server(int server_port, const char* pem_cert, const char* pem_key, char const* upstream_name, - int upstream_port, const char* domain_name); +int picoquic_slipstream_server(int server_port, const char* pem_cert, const char* pem_key, + struct sockaddr_storage* target_address, const char* domain_name); #ifdef __cplusplus } diff --git a/include/slipstream_resolver_addresses.h b/include/slipstream_resolver_addresses.h deleted file mode 100644 index 3fec042..0000000 --- a/include/slipstream_resolver_addresses.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef SLIPSTREAM_RESOLVERS_H -#define SLIPSTREAM_RESOLVERS_H - -#include -#include -#include - -typedef struct st_address_t { - struct sockaddr_storage server_address; - bool added; -} address_t; - -struct st_address_t* read_resolver_addresses(const char *resolver_addresses_filename, size_t *count); - -#endif //SLIPSTREAM_RESOLVERS_H diff --git a/src/slipstream.c b/src/slipstream.c deleted file mode 100644 index 0ff224e..0000000 --- a/src/slipstream.c +++ /dev/null @@ -1,69 +0,0 @@ -#include -#include -#include -#include "slipstream.h" - -static void usage(char const * sample_name) -{ - fprintf(stderr, "Usage:\n"); - fprintf(stderr, " %s client listen_port slipstream_server_name slipstream_server_port domain_name\n", sample_name); - fprintf(stderr, " %s server listen_port cert key target_server_name target_server_port domain_name\n", sample_name); - exit(1); -} - -int get_port(char const* sample_name, char const* port_arg) -{ - int server_port = atoi(port_arg); - if (server_port <= 0) { - fprintf(stderr, "Invalid port: %s\n", port_arg); - usage(sample_name); - } - - return server_port; -} - -int main(int argc, char** argv) -{ - int exit_code = 0; -#ifdef _WINDOWS - WSADATA wsaData = { 0 }; - (void)WSA_START(MAKEWORD(2, 2), &wsaData); -#endif - - - setbuf(stdout, NULL); - setbuf(stderr, NULL); - if (argc < 2) { - usage(argv[0]); - } - else if (strcmp(argv[1], "client") == 0) { - if (argc != 7) { - usage(argv[0]); - } - else { - int local_port = atoi(argv[2]); - char const* resolver_addresses_filename = argv[3]; - const char* domain_name = argv[4]; - const char* cc_algo_id = argv[5]; - bool gso = strcmp(argv[6], "true") == 0; - exit_code = picoquic_slipstream_client(local_port, resolver_addresses_filename, domain_name, cc_algo_id, gso); - } - } - else if (strcmp(argv[1], "server") == 0) { - if (argc != 8) { - usage(argv[0]); - } - else { - int server_port = get_port(argv[0], argv[2]); - int remote_port = get_port(argv[0], argv[6]); - const char* domain_name = argv[7]; - exit_code = picoquic_slipstream_server(server_port, argv[3], argv[4], argv[5], remote_port, domain_name); - } - } - else - { - usage(argv[0]); - } - - exit(exit_code); -} \ No newline at end of file diff --git a/src/slipstream_client.c b/src/slipstream_client.c index e774f97..3e91ded 100644 --- a/src/slipstream_client.c +++ b/src/slipstream_client.c @@ -21,11 +21,11 @@ #include "picoquic_config.h" #include "slipstream.h" #include "slipstream_inline_dots.h" -#include "slipstream_resolver_addresses.h" #include "slipstream_utils.h" #include "SPCDNS/src/dns.h" #include "SPCDNS/src/mappings.h" + typedef struct st_slipstream_client_stream_ctx_t { struct st_slipstream_client_stream_ctx_t* next_stream; struct st_slipstream_client_stream_ctx_t* previous_stream; @@ -683,7 +683,7 @@ static int slipstream_connect(struct sockaddr_storage* server_address, return ret; } -int picoquic_slipstream_client(int listen_port, char const* resolver_addresses_filename, 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) { /* Start: start the QUIC process */ int ret = 0; uint64_t current_time = 0; @@ -691,9 +691,9 @@ int picoquic_slipstream_client(int listen_port, char const* resolver_addresses_f client_domain_name = strdup(domain_name); client_domain_name_len = strlen(domain_name); - // int mtu = 1200; - // int mtu = 129; - int mtu = 145; + double mtu_d = 240 - (double) client_domain_name_len; + mtu_d = mtu_d / 1.6; + int mtu = (int) mtu_d; /* Create config */ picoquic_quic_config_t config; @@ -736,12 +736,9 @@ int picoquic_slipstream_client(int listen_port, char const* resolver_addresses_f // picoquic_set_log_level(quic, 1); // TODO: idle timeout? - /* Read the server address list from the file */ - client_ctx.server_addresses = read_resolver_addresses(resolver_addresses_filename, &client_ctx.server_address_count); - if (!client_ctx.server_addresses) { - printf("Failed to read IP addresses\n"); - return 1; - } + /* Parse the server addresses directly */ + client_ctx.server_addresses = server_addresses; + client_ctx.server_address_count = server_address_count; picoquic_cnx_t* cnx = NULL; ret = slipstream_connect(&client_ctx.server_addresses[0].server_address, quic, &cnx, &client_ctx); diff --git a/src/slipstream_client_cli.c b/src/slipstream_client_cli.c new file mode 100644 index 0000000..94a5345 --- /dev/null +++ b/src/slipstream_client_cli.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include +#include "slipstream.h" + +const char* argp_program_version = "slipstream-client 0.1"; +const char* argp_program_bug_address = "github.com/EndPositive/slipstream"; + +/* Program documentation */ +static char doc[] = "slipstream-client - A high-performance covert channel over DNS (client)\v"; + +/* A description of the arguments we accept. */ +static char args_doc[] = ""; + +/* Client mode options */ +static struct argp_option options[] = { + {"tcp-listen-port", 'l', "PORT", 0, "Listen port (default: 5201)", 0}, + {"resolver", 'r', "RESOLVER", 0, "Slipstream server resolver address (e.g., 1.1.1.1 or 8.8.8.8:53). Can be specified multiple times. (Required)", 0}, + {"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}, + {0} // End of options +}; + +/* Used by main to communicate with parse options. */ +struct arguments { + int listen_port; + char* domain_name; + struct st_address_t* resolver_addresses; + size_t resolver_count; + char* cc_algo_id; + bool gso; +}; + +/* Client mode parser */ +static error_t parse_opt(int key, char* arg, struct argp_state* state) { + struct arguments* arguments = state->input; + + switch (key) { + case 'd': + arguments->domain_name = arg; + break; + case 'l': + arguments->listen_port = atoi(arg); + if (arguments->listen_port <= 0 || arguments->listen_port > 65535) { + argp_error(state, "Invalid TCP listen port number: %s", arg); + } + break; + case 'r': + { + // Allocate or reallocate the resolver addresses array + struct st_address_t* new_resolvers = realloc(arguments->resolver_addresses, + (arguments->resolver_count + 1) * sizeof(struct st_address_t)); + if (!new_resolvers) { + argp_error(state, "Memory allocation failed for resolver address"); + return ARGP_ERR_UNKNOWN; // Signal error + } + arguments->resolver_addresses = new_resolvers; + + char server_name[256]; // Increased size for FQDNs + int server_port = 53; // Default DNS port + + // Try parsing with port first, then without + if (sscanf(arg, "%255[^:]:%d", server_name, &server_port) < 1) { + // If sscanf fails, maybe it's just an IP/hostname without port? + strncpy(server_name, arg, sizeof(server_name) -1); + server_name[sizeof(server_name)-1] = '\0'; // Ensure null termination + // Keep default port 53 + } + + if (server_port <= 0 || server_port > 65535) { + argp_error(state, "Invalid port number in resolver address: %s", arg); + } + + int is_name = 0; // We don't use this flag downstream currently + if (picoquic_get_server_address(server_name, server_port, &arguments->resolver_addresses[arguments->resolver_count].server_address, &is_name) != 0) { + argp_error(state, "Cannot resolve resolver address '%s' port %d", server_name, server_port); + } + + arguments->resolver_count++; + break; + } + case 'c': + // Consider adding validation for supported algorithms here + arguments->cc_algo_id = arg; + break; + case 'g': + // Handle optional argument for --gso + if (arg == NULL || strcmp(arg, "true") == 0) { + arguments->gso = true; + } else if (strcmp(arg, "false") == 0) { + arguments->gso = false; + } else { + argp_error(state, "Invalid boolean value for --gso: '%s'. Use 'true' or 'false'.", arg); + } + break; + case ARGP_KEY_ARG: + // No positional arguments expected + argp_usage(state); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +// Define the argp structure for client +static struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; + +int main(int argc, char** argv) { + int exit_code = 0; + struct arguments arguments; + +#ifdef _WINDOWS + WSADATA wsaData = { 0 }; + int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (iResult != 0) { + fprintf(stderr, "WSAStartup failed: %d\n", iResult); + return 1; + } +#endif + + /* Default values */ + memset(&arguments, 0, sizeof(arguments)); + arguments.listen_port = 5201; // Default TCP listen port + arguments.cc_algo_id = "dcubic"; // Default CC algo + arguments.gso = false; // Default GSO state + arguments.resolver_addresses = NULL; + arguments.resolver_count = 0; + + // Ensure output buffers are flushed immediately (useful for debugging/logging) + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + // Parse command line arguments + error_t parse_err = argp_parse(&argp, argc, argv, 0, NULL, &arguments); + if (parse_err) { + // argp should have printed an error message already + exit(1); // Exit if parsing failed + } + + /* Check mandatory client arguments */ + bool client_args_ok = true; + if (arguments.domain_name == NULL) { + fprintf(stderr, "Client error: Missing required --domain option\n"); + client_args_ok = false; + } + if (arguments.resolver_count == 0) { + fprintf(stderr, "Client error: Missing required --resolver option (at least one required)\n"); + client_args_ok = false; + } + + if (!client_args_ok) { + // Show specific client help message + argp_help(&argp, stderr, ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR, "slipstream-client"); + // argp_help with ARGP_HELP_EXIT_ERR will exit, no need for exit(1) here + } + + exit_code = picoquic_slipstream_client( + arguments.listen_port, + arguments.resolver_addresses, + arguments.resolver_count, + arguments.domain_name, + arguments.cc_algo_id, + arguments.gso + ); + + // Free allocated memory for resolver addresses + free(arguments.resolver_addresses); + +#ifdef _WINDOWS + WSACleanup(); +#endif + + exit(exit_code); +} diff --git a/src/slipstream_resolver_addresses.c b/src/slipstream_resolver_addresses.c deleted file mode 100644 index 014d733..0000000 --- a/src/slipstream_resolver_addresses.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "slipstream_resolver_addresses.h" - -#include -#include - -#include - -#define MAX_IP_LENGTH 20 -#define INITIAL_CAPACITY 10 -#define MAX_LINE_LENGTH 50 -#define DEFAULT_PORT 53 - -struct st_address_t* read_resolver_addresses(const char *resolver_addresses_filename, size_t *count) { - *count = 0; - - FILE *fp = fopen(resolver_addresses_filename, "r"); - if (!fp) { - return NULL; - } - - int capacity = INITIAL_CAPACITY; - struct st_address_t* server_address = calloc(capacity, sizeof(struct st_address_t)); - if (!server_address) { - fclose(fp); - return NULL; - } - - char line[MAX_LINE_LENGTH]; - int valid_addresses = 0; - - while (fgets(line, MAX_LINE_LENGTH, fp)) { - // Remove newline - line[strcspn(line, "\n")] = '\0'; - - // Skip empty or whitespace-only lines - if (strlen(line) == 0 || strspn(line, " \t") == strlen(line)) { - continue; - } - - // Resize array if needed - if (valid_addresses == capacity) { - capacity *= 2; - struct st_address_t* temp = realloc(server_address, capacity * sizeof(struct st_address_t)); - if (!temp) { - fprintf(stderr, "Memory allocation failed\n"); - free(server_address); - fclose(fp); - return NULL; - } - server_address = temp; - } - - char server_name[MAX_IP_LENGTH]; - int server_port = DEFAULT_PORT; - - // Parse line for IP and optional port - if (sscanf(line, "%s %d", server_name, &server_port) < 1) { - continue; // Invalid format - } - - printf("Adding %s:%d\n", server_name, server_port); - - int is_name = 0; - if (picoquic_get_server_address(server_name, server_port, &server_address[valid_addresses].server_address, &is_name) != 0) { - fprintf(stderr, "Cannot get the IP address for <%s> port <%d>\n", server_name, server_port); - continue; // Skip invalid addresses instead of failing - } - valid_addresses++; - } - - fclose(fp); - *count = valid_addresses; - - // Trim excess memory if needed - if (valid_addresses < capacity) { - struct st_address_t* temp = realloc(server_address, valid_addresses * sizeof(struct st_address_t)); - if (temp) { - server_address = temp; - } - } - - return server_address; -} diff --git a/src/slipstream_server.c b/src/slipstream_server.c index 68b0128..82ba035 100644 --- a/src/slipstream_server.c +++ b/src/slipstream_server.c @@ -640,15 +640,14 @@ void server_sighandler(int signum) { } int picoquic_slipstream_server(int server_port, const char* server_cert, const char* server_key, - char const* upstream_name, int upstream_port, const char* domain_name) { + struct sockaddr_storage* target_address, const char* domain_name) { /* Start: start the QUIC process with cert and key files */ int ret = 0; uint64_t current_time = 0; slipstream_server_ctx_t default_context = {0}; - DBG_PRINTF("Starting Picoquic Sample server on port %d", server_port); - int is_name = 0; - picoquic_get_server_address(upstream_name, upstream_port, &default_context.upstream_addr, &is_name); + // Store the target address directly - no need to resolve it here anymore + memcpy(&default_context.upstream_addr, target_address, sizeof(struct sockaddr_storage)); server_domain_name = strdup(domain_name); server_domain_name_len = strlen(domain_name); @@ -731,3 +730,4 @@ int picoquic_slipstream_server(int server_port, const char* server_cert, const c return ret; } + diff --git a/src/slipstream_server_cli.c b/src/slipstream_server_cli.c new file mode 100644 index 0000000..f8280ab --- /dev/null +++ b/src/slipstream_server_cli.c @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include +#include "slipstream.h" + +const char* argp_program_version = "slipstream-server 0.1"; +const char* argp_program_bug_address = "github.com/EndPositive/slipstream"; + +/* Program documentation */ +static char doc[] = "slipstream-server - A high-performance covert channel over DNS (server)\v"; + +/* A description of the arguments we accept. */ +static char args_doc[] = ""; + +/* Server mode options */ +static struct argp_option options[] = { + {"dns-listen-port", 'l', "PORT", 0, "DNS listen port (default: 53)", 0}, + {"target-address", 'a', "ADDRESS", 0, "Target server address (default: 127.0.0.1:5201)", 0}, + {"cert", 'c', "CERT", 0, "Certificate file path (default: certs/cert.pem)", 0}, + {"key", 'k', "KEY", 0, "Private key file path (default: certs/key.pem)", 0}, + {"domain", 'd', "DOMAIN", 0, "Domain name this server is authoritative for (Required)", 0}, + {0} // End of options +}; + +/* Used by main to communicate with parse options. */ +struct arguments { + int listen_port; + char* domain_name; + char* cert_file; + char* key_file; + struct sockaddr_storage target_address; +}; + +/* Server mode parser */ +static error_t parse_opt(int key, char* arg, struct argp_state* state) { + struct arguments* arguments = state->input; + + switch (key) { + case 'd': + arguments->domain_name = arg; + break; + case 'l': + arguments->listen_port = atoi(arg); + if (arguments->listen_port <= 0 || arguments->listen_port > 65535) { + argp_error(state, "Invalid DNS listen port number: %s", arg); + } + break; + case 'c': + arguments->cert_file = arg; + break; + case 'k': + arguments->key_file = arg; + break; + case 'a': + { + char server_name[256]; // Increased size for FQDNs + int server_port = 5201; // Default upstream port + + // Try parsing with port first, then without + if (sscanf(arg, "%255[^:]:%d", server_name, &server_port) < 1) { + // If sscanf fails, maybe it's just an IP/hostname without port? + strncpy(server_name, arg, sizeof(server_name) - 1); + server_name[sizeof(server_name) - 1] = '\0'; // Ensure null termination + // Keep default port 5201 + } + + if (server_port <= 0 || server_port > 65535) { + argp_error(state, "Invalid port number in target address: %s", arg); + } + + int is_name = 0; + if (picoquic_get_server_address(server_name, server_port, &arguments->target_address, &is_name) != 0) { + argp_error(state, "Cannot resolve target address '%s' port %d", server_name, server_port); + } + break; + } + case ARGP_KEY_ARG: + // No positional arguments expected + argp_usage(state); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +// Define the argp structure for server +static struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; + +int main(int argc, char** argv) { + int exit_code = 0; + struct arguments arguments; + +#ifdef _WINDOWS + WSADATA wsaData = { 0 }; + int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (iResult != 0) { + fprintf(stderr, "WSAStartup failed: %d\n", iResult); + return 1; + } +#endif + + /* Default values */ + memset(&arguments, 0, sizeof(arguments)); + arguments.listen_port = 53; // Default DNS listen port + arguments.cert_file = "certs/cert.pem"; // Default cert path + arguments.key_file = "certs/key.pem"; // Default key path + + // Set default target address (127.0.0.1:5201) + int is_name = 0; + if (picoquic_get_server_address("127.0.0.1", 5201, &arguments.target_address, &is_name) != 0) { + fprintf(stderr, "Failed to set default target address\n"); + exit(1); + } + + // Ensure output buffers are flushed immediately + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + // Parse command line arguments + error_t parse_err = argp_parse(&argp, argc, argv, 0, NULL, &arguments); + if (parse_err) { + // argp should have printed an error message already + exit(1); // Exit if parsing failed + } + + /* Check mandatory server arguments */ + bool server_args_ok = true; + if (arguments.domain_name == NULL) { + fprintf(stderr, "Server error: Missing required --domain option\n"); + server_args_ok = false; + } + + if (!server_args_ok) { + argp_help(&argp, stderr, ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR, "slipstream-server"); + } + + exit_code = picoquic_slipstream_server( + arguments.listen_port, + arguments.cert_file, + arguments.key_file, + &arguments.target_address, + arguments.domain_name + ); + +#ifdef _WINDOWS + WSACleanup(); +#endif + + exit(exit_code); +}