This commit is contained in:
Jop Zitman 2025-05-06 15:55:52 +08:00
parent 163a02dbd4
commit cb4a29a239
12 changed files with 518 additions and 218 deletions

View file

@ -1,6 +1,5 @@
*
!certs/
!src/
!extern/
!include/
!src/

View file

@ -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)

View file

@ -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 []

126
README.md
View file

@ -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.

View file

@ -17,12 +17,16 @@ extern "C" {
#define SLIPSTREAM_QLOG_DIR "./qlog";
#include <stdbool.h>
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
}

View file

@ -1,15 +0,0 @@
#ifndef SLIPSTREAM_RESOLVERS_H
#define SLIPSTREAM_RESOLVERS_H
#include <stdbool.h>
#include <stdio.h>
#include <sys/socket.h>
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

View file

@ -1,69 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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);
}

View file

@ -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);

179
src/slipstream_client_cli.c Normal file
View file

@ -0,0 +1,179 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <argp.h>
#include <picosocks.h>
#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);
}

View file

@ -1,83 +0,0 @@
#include "slipstream_resolver_addresses.h"
#include <stdlib.h>
#include <string.h>
#include <picosocks.h>
#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;
}

View file

@ -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;
}

154
src/slipstream_server_cli.c Normal file
View file

@ -0,0 +1,154 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <argp.h>
#include <picosocks.h>
#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);
}