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,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);
}