mirror of
https://github.com/EndPositive/slipstream.git
synced 2025-10-08 12:25:04 +00:00
cli
This commit is contained in:
parent
163a02dbd4
commit
cb4a29a239
12 changed files with 518 additions and 218 deletions
|
|
@ -1,6 +1,5 @@
|
|||
*
|
||||
!certs/
|
||||
!src/
|
||||
!extern/
|
||||
!include/
|
||||
!src/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
24
Dockerfile
24
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 []
|
||||
|
|
|
|||
126
README.md
126
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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
179
src/slipstream_client_cli.c
Normal 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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
154
src/slipstream_server_cli.c
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue