ios: PCA Phobos VPN for iPhone (source)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
535
ios/Tunnel/obfuscator/config.c
Normal file
535
ios/Tunnel/obfuscator/config.c
Normal file
@@ -0,0 +1,535 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include "config.h"
|
||||
#include "wg-obfuscator.h"
|
||||
#include "mini_argp.h"
|
||||
#include "masking.h"
|
||||
|
||||
// Executable name
|
||||
static const char *arg0;
|
||||
|
||||
/* The options we understand. */
|
||||
static const mini_argp_opt options[] = {
|
||||
{ "help", '?', 0 },
|
||||
{ "config", 'c', 1 },
|
||||
{ "source-if", 'i', 1 },
|
||||
{ "source", 's', 1 },
|
||||
{ "source-lport", 'p', 1 },
|
||||
{ "target-if", 'o', 1 },
|
||||
{ "target", 't', 1 },
|
||||
{ "target-lport", 'r', 1 },
|
||||
{ "key", 'k', 1 },
|
||||
{ "masking", 'a', 1 },
|
||||
{ "static-bindings", 'b', 1 },
|
||||
{ "max-clients" , 'm', 1 },
|
||||
{ "idle-timeout", 'l', 1 },
|
||||
{ "in-timeout", 'n', 1 },
|
||||
{ "max-dummy", 'd', 1 },
|
||||
{ "fwmark", 'f', 1 },
|
||||
{ "allow-clean", 'e', 0 },
|
||||
{ "verbose", 'v', 1 },
|
||||
{ "log-file", 'L', 1 },
|
||||
{ "log-timestamps", 'T', 1 },
|
||||
{ "resolve-interval", 'R', 1 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static void show_usage(void)
|
||||
{
|
||||
printf("Usage: %s [options]\n%s", arg0,
|
||||
" -?, --help Give this help list\n"
|
||||
"\n"
|
||||
"Main settings:\n"
|
||||
" -c, --config=<config_file> Read configuration from file\n"
|
||||
" (can be used instead of the rest arguments)\n"
|
||||
" -i, --source-if=<ip> Source interface to listen on\n"
|
||||
" (optional, default - 0.0.0.0, e.g. all)\n"
|
||||
" -p, --source-lport=<port> Source port to listen\n"
|
||||
" -t, --target=<ip>:<port> Target IP and port\n"
|
||||
" -k, --key=<key> Obfuscation key \n"
|
||||
" (required, must be 1-255 characters long)\n"
|
||||
" -a, --masking=<type> Masking type (optional, default - AUTO)\n"
|
||||
" Supported values: STUN, AUTO, NONE\n"
|
||||
" -b, --static-bindings=<ip>:<port>:<port>,...\n"
|
||||
" Comma-separated static bindings for two-way mode\n"
|
||||
" as <client_ip>:<client_port>:<forward_port>\n"
|
||||
" You can also repeat this option.\n"
|
||||
" -f, --fwmark=<mark> Firewall mark to set on all packets\n"
|
||||
" (optional, default - 0, e.g. disabled)\n"
|
||||
" -v, --verbose=<level> Verbosity level (optional, default - INFO)\n"
|
||||
" ERRORS (critical errors only)\n"
|
||||
" WARNINGS (important messages)\n"
|
||||
" 2 - INFO (informational messages: status messages,\n"
|
||||
" connection established, etc.)\n"
|
||||
" 3 - DEBUG (detailed debug messages)\n"
|
||||
" 4 - TRACE (very detailed debug messages, including\n"
|
||||
" packet dumps)\n"
|
||||
" -L, --log-file=<path> Write the log to this file instead of stderr\n"
|
||||
" (optional, the file is reopened on SIGHUP)\n"
|
||||
" -T, --log-timestamps=<val> Prefix log lines with a timestamp\n"
|
||||
" (optional, default - AUTO, e.g. only when\n"
|
||||
" writing to a log file)\n"
|
||||
" Supported values: AUTO, TRUE, FALSE\n"
|
||||
"\n"
|
||||
"Additional options:\n"
|
||||
" -m, --max-clients=<number> Maximum number of clients (default: 1024)\n"
|
||||
" -l, --idle-timeout=<sec> Idle timeout in seconds (default: 300)\n"
|
||||
" -n, --in-timeout=<sec> Incoming timeout in seconds (default: 0 - disabled)\n"
|
||||
" -d, --max-dummy=<bytes> Maximum length of dummy bytes for data packets\n"
|
||||
" (default: 4)\n"
|
||||
" -e, --allow-clean For servers, allow non-obfuscated incoming connections\n"
|
||||
" -R, --resolve-interval=<sec>\n"
|
||||
" Re-resolve target and static-binding hostnames\n"
|
||||
" every N seconds (default: 0 - disabled).\n"
|
||||
" SIGHUP always triggers a refresh.\n"
|
||||
" If non-zero, a failed resolve at startup is retried\n"
|
||||
" instead of exiting.\n");
|
||||
}
|
||||
|
||||
static int parse_opt(const char *lname, char sname, const char *val, void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Resets the configuration structure to its default values.
|
||||
*
|
||||
* This function clears the configuration structure by setting all fields to zero
|
||||
* and resetting the verbosity level to the default value.
|
||||
*
|
||||
* @param config Pointer to the obfuscator_config structure to be reset.
|
||||
*/
|
||||
static void reset_config(obfuscator_config_t *config)
|
||||
{
|
||||
if (config->static_bindings) {
|
||||
free(config->static_bindings);
|
||||
}
|
||||
memset(config, 0, sizeof(*config));
|
||||
config->max_clients = MAX_CLIENTS_DEFAULT;
|
||||
config->idle_timeout = IDLE_TIMEOUT_DEFAULT;
|
||||
config->in_timeout = IN_TIMEOUT_DEFAULT;
|
||||
config->max_dummy_length_data = MAX_DUMMY_LENGTH_DATA_DEFAULT;
|
||||
config->log_timestamps = -1; // auto
|
||||
verbose = LL_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given string represents a valid integer.
|
||||
*
|
||||
* @param str Pointer to the null-terminated string to check.
|
||||
* @return Non-zero value if the string is a valid integer, 0 otherwise.
|
||||
*/
|
||||
static uint8_t is_integer(const char *str)
|
||||
{
|
||||
if (!str || !*str) {
|
||||
return 0; // Empty string is not an integer
|
||||
}
|
||||
while (*str) {
|
||||
if (!isdigit((unsigned char)*str)) {
|
||||
return 0; // Non-digit character found
|
||||
}
|
||||
str++;
|
||||
}
|
||||
return 1; // All characters are digits
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a boolean value from a string.
|
||||
*
|
||||
* @param str Pointer to the null-terminated string to parse.
|
||||
* @return 1 for true values ("true", "yes", "on", "1"),
|
||||
* 0 for false values ("false", "no", "off", "0"),
|
||||
* -1 if the string is not a valid boolean value.
|
||||
*/
|
||||
static int parse_bool(const char *str)
|
||||
{
|
||||
char lower[16];
|
||||
if (!str || !*str || strlen(str) >= sizeof(lower)) {
|
||||
return -1;
|
||||
}
|
||||
strcpy(lower, str);
|
||||
for (char *p = lower; *p; ++p) *p = tolower((unsigned char)*p);
|
||||
if (!strcmp(lower, "true") || !strcmp(lower, "yes") || !strcmp(lower, "on") || !strcmp(lower, "1")) {
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp(lower, "false") || !strcmp(lower, "no") || !strcmp(lower, "off") || !strcmp(lower, "0")) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads and processes the configuration file.
|
||||
*
|
||||
* This function opens the specified configuration file and parses its contents
|
||||
* to initialize or update the application's configuration settings.
|
||||
*
|
||||
* @param filename The path to the configuration file to be read.
|
||||
* @param config Pointer to the obfuscator_config structure where the parsed settings will be stored.
|
||||
*/
|
||||
static void read_config_file(const char *filename, obfuscator_config_t *config)
|
||||
{
|
||||
// Read configuration from the file
|
||||
uint8_t first_section = 1; // Flag to indicate if this is the first section being processed
|
||||
char line[10 * 1024]; // Buffer to hold each line from the config file
|
||||
|
||||
FILE *config_file = fopen(filename, "r");
|
||||
if (config_file == NULL) {
|
||||
perror("Can't open config file");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
while (fgets(line, sizeof(line), config_file)) {
|
||||
// Remove trailing newlines, carriage returns, spaces and tabs
|
||||
while (strlen(line) && (line[strlen(line) - 1] == '\n' || line[strlen(line) - 1] == '\r'
|
||||
|| line[strlen(line) - 1] == ' ' || line[strlen(line) - 1] == '\t')) {
|
||||
line[strlen(line) - 1] = 0;
|
||||
}
|
||||
// Remove leading spaces and tabs
|
||||
while (strlen(line) && (line[0] == ' ' || line[0] == '\t')) {
|
||||
memmove(line, line + 1, strlen(line));
|
||||
}
|
||||
// Ignore comments
|
||||
char *comment_index = strstr(line, "#");
|
||||
if (comment_index != NULL) {
|
||||
*comment_index = 0;
|
||||
}
|
||||
// Skip empty lines or with spaces only
|
||||
size_t line_len = strlen(line);
|
||||
if (strspn(line, " \t\r\n") == line_len) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// It can be new section
|
||||
if (line_len > 0 && line[0] == '[' && line[line_len - 1] == ']') {
|
||||
if (!first_section) {
|
||||
// new config, need to fork the process
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
log(LL_ERROR, "Can't fork a new instance - %s (%d)", strerror(errno), errno);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (pid == 0) {
|
||||
// Close in the child process
|
||||
fclose(config_file);
|
||||
// Stop config file processing for this instance
|
||||
return;
|
||||
}
|
||||
// Remember the instance to be able to forward signals to it
|
||||
register_child_instance(pid);
|
||||
}
|
||||
size_t len = strlen(line) - 2;
|
||||
if (len > sizeof(section_name) - 1) {
|
||||
len = sizeof(section_name) - 1;
|
||||
}
|
||||
strncpy(section_name, line + 1, len);
|
||||
section_name[len] = 0;
|
||||
|
||||
// Reset all the parameters
|
||||
reset_config(config);
|
||||
|
||||
first_section = 0; // We have processed the first section
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse key-value pairs
|
||||
char *key = strtok(line, "=");
|
||||
key = trim(key);
|
||||
while (strlen(key) && (key[strlen(key) - 1] == ' ' || key[strlen(key) - 1] == '\t' || key[strlen(key) - 1] == '\r' || key[strlen(key) - 1] == '\n')) {
|
||||
key[strlen(key) - 1] = 0;
|
||||
}
|
||||
char *value = strtok(NULL, "=");
|
||||
if (value == NULL) {
|
||||
log(LL_ERROR, "Invalid configuration line: %s", line);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
value = trim(value);
|
||||
if (!*value) {
|
||||
log(LL_ERROR, "Invalid configuration line: %s", line);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
const mini_argp_opt *o = margp_find(options, key, 0);
|
||||
if (o == NULL) {
|
||||
log(LL_ERROR, "Unknown configuration key: %s", key);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (!o->has_arg) {
|
||||
// Flag options are represented as booleans in the config file
|
||||
int b = parse_bool(value);
|
||||
if (b < 0) {
|
||||
log(LL_ERROR, "Configuration key '%s' accepts only boolean values (true/yes/on/1 or false/no/off/0)", key);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (!b) {
|
||||
continue;
|
||||
}
|
||||
value = NULL;
|
||||
}
|
||||
parse_opt(o->long_name, o->short_name, value, config);
|
||||
}
|
||||
fclose(config_file);
|
||||
}
|
||||
|
||||
/* Parse a single option. */
|
||||
static int parse_opt(const char *lname, char sname, const char *val, void *ctx)
|
||||
{
|
||||
obfuscator_config_t *config = (obfuscator_config_t *)ctx;
|
||||
char val_lower[16];
|
||||
|
||||
switch (sname)
|
||||
{
|
||||
case '?':
|
||||
// Show usage and exit
|
||||
show_usage();
|
||||
exit(EXIT_SUCCESS);
|
||||
case 'c':
|
||||
read_config_file(val, config);
|
||||
break;
|
||||
case 'i':
|
||||
strncpy(config->client_interface, val, sizeof(config->client_interface) - 1);
|
||||
config->client_interface[sizeof(config->client_interface) - 1] = 0; // Ensure null-termination
|
||||
config->client_interface_set = 1;
|
||||
break;
|
||||
case 'p':
|
||||
if (!is_integer(val)) {
|
||||
log(LL_ERROR, "Invalid source port: %s (must be an integer)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->listen_port = atoi(val);
|
||||
if (config->listen_port <= 0 || config->listen_port > 65535) {
|
||||
log(LL_ERROR, "Invalid listen port: %s (must be between 1 and 65535)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->listen_port_set = 1;
|
||||
break;
|
||||
case 't':
|
||||
strncpy(config->forward_host_port, val, sizeof(config->forward_host_port) - 1);
|
||||
config->forward_host_port[sizeof(config->forward_host_port) - 1] = 0; // Ensure null-termination
|
||||
config->forward_host_port_set = 1;
|
||||
break;
|
||||
case 'b':
|
||||
{
|
||||
char *new_bindings;
|
||||
if (!config->static_bindings) {
|
||||
new_bindings = strdup(val);
|
||||
} else {
|
||||
size_t old_len = strlen(config->static_bindings);
|
||||
new_bindings = realloc(config->static_bindings, old_len + strlen(val) + 2);
|
||||
if (new_bindings) {
|
||||
new_bindings[old_len] = ',';
|
||||
strcpy(new_bindings + old_len + 1, val);
|
||||
}
|
||||
}
|
||||
if (!new_bindings) {
|
||||
log(LL_ERROR, "Out of memory while parsing static-bindings");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->static_bindings = new_bindings;
|
||||
config->static_bindings_set = 1;
|
||||
}
|
||||
break;
|
||||
case 'k':
|
||||
strncpy(config->xor_key, val, sizeof(config->xor_key));
|
||||
config->xor_key[sizeof(config->xor_key) - 1] = 0; // Ensure null-termination
|
||||
if (strlen(config->xor_key) == 0) {
|
||||
log(LL_ERROR, "XOR key cannot be empty");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->xor_key_set = 1;
|
||||
break;
|
||||
case 'm':
|
||||
if (!is_integer(val)) {
|
||||
log(LL_ERROR, "Invalid maximum number of clients: %s (must be an integer)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->max_clients = atoi(val);
|
||||
if (config->max_clients <= 0) {
|
||||
log(LL_ERROR, "Invalid maximum number of clients: %s (must be greater than 0)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
if (!is_integer(val)) {
|
||||
log(LL_ERROR, "Invalid idle timeout: %s (must be an integer)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->idle_timeout = atol(val);
|
||||
if (config->idle_timeout <= 0) {
|
||||
log(LL_ERROR, "Invalid idle timeout: %s (must be greater than 0)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->idle_timeout *= 1000; // Convert to milliseconds
|
||||
break;
|
||||
case 'n':
|
||||
if (!is_integer(val)) {
|
||||
log(LL_ERROR, "Invalid incoming timeout: %s (must be an integer)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->in_timeout = atol(val);
|
||||
if (config->in_timeout <= 0) {
|
||||
log(LL_ERROR, "Invalid incoming timeout: %s (must be greater than 0)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->in_timeout *= 1000; // Convert to milliseconds
|
||||
break;
|
||||
case 'd':
|
||||
if (!is_integer(val)) {
|
||||
log(LL_ERROR, "Invalid maximum dummy length for data packets: %s (must be an integer)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->max_dummy_length_data = atoi(val);
|
||||
if (config->max_dummy_length_data < 0 || config->max_dummy_length_data > MAX_DUMMY_LENGTH_TOTAL) {
|
||||
log(LL_ERROR, "Invalid maximum dummy length for data packets: %s (must be between 0 and %d)", val, MAX_DUMMY_LENGTH_TOTAL);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
config->allow_clean = 1;
|
||||
break;
|
||||
case 'f':
|
||||
// parse string with decimal and hexadecimal support
|
||||
#ifdef __linux__
|
||||
{
|
||||
long int v = strtol(val, NULL, 0);
|
||||
if (v <= 0 || v > UINT16_MAX) {
|
||||
log(LL_ERROR, "Invalid firewall mark: %s", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->fwmark = (uint16_t)v;
|
||||
}
|
||||
#else
|
||||
log(LL_WARN, "Firewall mark is not supported on this platform");
|
||||
#endif
|
||||
break;
|
||||
case 'a':
|
||||
{
|
||||
strncpy(val_lower, val, sizeof(val_lower) - 1);
|
||||
val_lower[sizeof(val_lower) - 1] = 0;
|
||||
for (char *p = val_lower; *p; ++p) *p = tolower((unsigned char)*p);
|
||||
if (strcmp(val_lower, "none") == 0) {
|
||||
config->masking_handler = NULL;
|
||||
config->masking_handler_set = 1;
|
||||
break;
|
||||
}
|
||||
if (strcmp(val_lower, "auto") == 0) {
|
||||
config->masking_handler = NULL;
|
||||
config->masking_handler_set = 0;
|
||||
break;
|
||||
}
|
||||
masking_handler_t *handler = get_masking_handler_by_name(val_lower);
|
||||
if (handler == NULL) {
|
||||
log(LL_ERROR, "Unknown masking type: %s", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->masking_handler = handler;
|
||||
config->masking_handler_set = 1;
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
strncpy(val_lower, val, sizeof(val_lower) - 1);
|
||||
val_lower[sizeof(val_lower) - 1] = 0;
|
||||
for (char *p = val_lower; *p; ++p) *p = tolower((unsigned char)*p);
|
||||
if (strcmp(val_lower, "error") == 0) {
|
||||
verbose = LL_ERROR;
|
||||
} else if (strcmp(val_lower, "warn") == 0) {
|
||||
verbose = LL_WARN;
|
||||
} else if (strcmp(val_lower, "info") == 0) {
|
||||
verbose = LL_INFO;
|
||||
} else if (strcmp(val_lower, "debug") == 0) {
|
||||
verbose = LL_DEBUG;
|
||||
} else if (strcmp(val_lower, "trace") == 0) {
|
||||
verbose = LL_TRACE;
|
||||
} else {
|
||||
// check if it's a number
|
||||
if (is_integer(val)) {
|
||||
verbose = atoi(val);
|
||||
if (verbose < 0 || verbose > 4) {
|
||||
log(LL_ERROR, "Invalid verbosity level: %s (must be one of 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE')", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
log(LL_ERROR, "Invalid verbosity level: %s (must be one of 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE')", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'L':
|
||||
strncpy(config->log_file, val, sizeof(config->log_file) - 1);
|
||||
config->log_file[sizeof(config->log_file) - 1] = 0; // Ensure null-termination
|
||||
if (strlen(config->log_file) == 0) {
|
||||
log(LL_ERROR, "Log file path cannot be empty");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->log_file_set = 1;
|
||||
break;
|
||||
case 'T':
|
||||
{
|
||||
strncpy(val_lower, val, sizeof(val_lower) - 1);
|
||||
val_lower[sizeof(val_lower) - 1] = 0;
|
||||
for (char *p = val_lower; *p; ++p) *p = tolower((unsigned char)*p);
|
||||
if (strcmp(val_lower, "auto") == 0) {
|
||||
config->log_timestamps = -1;
|
||||
break;
|
||||
}
|
||||
int b = parse_bool(val_lower);
|
||||
if (b < 0) {
|
||||
log(LL_ERROR, "Invalid log timestamps mode: %s (must be one of 'AUTO', 'TRUE', 'FALSE')", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->log_timestamps = b;
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
if (!is_integer(val)) {
|
||||
log(LL_ERROR, "Invalid resolve interval: %s (must be an integer)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->resolve_interval = atol(val);
|
||||
if (config->resolve_interval < 0) {
|
||||
log(LL_ERROR, "Invalid resolve interval: %s (must be 0 or greater)", val);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config->resolve_interval *= 1000; // Convert to milliseconds
|
||||
break;
|
||||
default:
|
||||
// should never happen
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_config(int argc, char **argv, obfuscator_config_t *config)
|
||||
{
|
||||
/* Parse command line arguments */
|
||||
reset_config(config);
|
||||
arg0 = argv[0]; // Save the executable name
|
||||
if (argc == 1) {
|
||||
fprintf(stderr, "No arguments provided, use \"%s --help\" command for usage information\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
if (mini_argp_parse(argc, argv, options, config, parse_opt) != 0) {
|
||||
fprintf(stderr, "Failed to parse command line arguments\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes leading and trailing whitespace characters from the input string.
|
||||
*
|
||||
* This function modifies the input string in place by trimming any whitespace
|
||||
* characters (such as spaces, tabs, or newlines) from both the beginning and end.
|
||||
*
|
||||
* @param s Pointer to the null-terminated string to be trimmed.
|
||||
* @return Pointer to the trimmed string.
|
||||
*/
|
||||
char *trim(char *s) {
|
||||
char *end;
|
||||
// Trim leading spaces, tabs, carriage returns and newlines
|
||||
while (*s && (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')) s++;
|
||||
if (!*s) return s;
|
||||
// Trim trailing spaces, tabs, carriage returns and newlines
|
||||
end = s + strlen(s) - 1;
|
||||
while (end > s && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = 0;
|
||||
return s;
|
||||
}
|
||||
9
ios/Tunnel/obfuscator/config.h
Normal file
9
ios/Tunnel/obfuscator/config.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef _CONFIG_H_
|
||||
#define _CONFIG_H_
|
||||
|
||||
#include "wg-obfuscator.h"
|
||||
|
||||
char *trim(char *s);
|
||||
int parse_config(int argc, char **argv, obfuscator_config_t *config);
|
||||
|
||||
#endif // _CONFIG_H_
|
||||
258
ios/Tunnel/obfuscator/logging.c
Normal file
258
ios/Tunnel/obfuscator/logging.c
Normal file
@@ -0,0 +1,258 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include "wg-obfuscator.h"
|
||||
|
||||
#ifndef O_BINARY
|
||||
#define O_BINARY 0
|
||||
#endif
|
||||
|
||||
#ifndef STDERR_FILENO
|
||||
#define STDERR_FILENO 2
|
||||
#endif
|
||||
|
||||
// Log files may contain client addresses, so they are not world-readable
|
||||
#define LOG_FILE_MODE 0640
|
||||
|
||||
// File descriptor of the log file, -1 when logging to stderr
|
||||
static int log_fd = -1;
|
||||
// Path of the log file, kept to be able to reopen it on SIGHUP
|
||||
static char *log_path = NULL;
|
||||
// 1 if every line must be prefixed with a timestamp
|
||||
static int log_timestamps = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns the single character representing a logging level.
|
||||
*/
|
||||
static char level_char(int level)
|
||||
{
|
||||
switch (level) {
|
||||
case LL_ERROR: return 'E';
|
||||
case LL_WARN: return 'W';
|
||||
case LL_INFO: return 'I';
|
||||
case LL_DEBUG: return 'D';
|
||||
case LL_TRACE: return 'T';
|
||||
default: return '?';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the whole buffer to the descriptor, retrying on partial writes.
|
||||
*
|
||||
* @param fd Descriptor to write to.
|
||||
* @param buf Data to write.
|
||||
* @param len Number of bytes to write.
|
||||
*/
|
||||
static void write_all(int fd, const char *buf, size_t len)
|
||||
{
|
||||
while (len) {
|
||||
ssize_t written = write(fd, buf, len);
|
||||
if (written <= 0) {
|
||||
if (written < 0 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
// Nothing sensible can be done about a broken log, drop the rest of the line
|
||||
return;
|
||||
}
|
||||
buf += written;
|
||||
len -= (size_t)written;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends formatted text to the buffer without ever overflowing it.
|
||||
*
|
||||
* @param buf Buffer to append to.
|
||||
* @param size Usable size of the buffer, including the terminating zero.
|
||||
* @param len Current length of the text in the buffer.
|
||||
* @param fmt Format string.
|
||||
* @param args Arguments for the format string.
|
||||
* @return New length of the text in the buffer.
|
||||
*/
|
||||
static size_t append_vfmt(char *buf, size_t size, size_t len, const char *fmt, va_list args)
|
||||
{
|
||||
int n;
|
||||
|
||||
if (len + 1 >= size) {
|
||||
return len;
|
||||
}
|
||||
n = vsnprintf(buf + len, size - len, fmt, args);
|
||||
if (n < 0) {
|
||||
return len;
|
||||
}
|
||||
if ((size_t)n >= size - len) {
|
||||
return size - 1; // truncated
|
||||
}
|
||||
return len + (size_t)n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Appends formatted text to the buffer without ever overflowing it.
|
||||
*
|
||||
* @param buf Buffer to append to.
|
||||
* @param size Usable size of the buffer, including the terminating zero.
|
||||
* @param len Current length of the text in the buffer.
|
||||
* @param fmt Format string.
|
||||
* @return New length of the text in the buffer.
|
||||
*/
|
||||
static size_t append_fmt(char *buf, size_t size, size_t len, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
len = append_vfmt(buf, size, len, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the common line prefix (timestamp and instance name) to the buffer.
|
||||
*
|
||||
* @param buf Buffer to write to.
|
||||
* @param size Usable size of the buffer, including the terminating zero.
|
||||
* @param level Logging level of the line.
|
||||
* @return Length of the prefix.
|
||||
*/
|
||||
static size_t log_prefix(char *buf, size_t size, int level)
|
||||
{
|
||||
size_t len = 0;
|
||||
|
||||
if (log_timestamps) {
|
||||
struct timespec ts;
|
||||
struct tm *tm;
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) == 0 && (tm = localtime(&ts.tv_sec)) != NULL) {
|
||||
len = strftime(buf, size, "%Y-%m-%d %H:%M:%S", tm);
|
||||
len = append_fmt(buf, size, len, ".%03ld ", (long)(ts.tv_nsec / 1000000));
|
||||
}
|
||||
}
|
||||
|
||||
return append_fmt(buf, size, len, "[%s][%c] ", section_name, level_char(level));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Terminates the line and writes it out with a single write() call.
|
||||
*
|
||||
* A single write() to a descriptor opened with O_APPEND keeps lines of different
|
||||
* instances from interleaving when they share the same log file.
|
||||
*
|
||||
* @param buf Buffer holding the line, must have one spare byte for the newline.
|
||||
* @param len Length of the line.
|
||||
*/
|
||||
static void log_emit(char *buf, size_t len)
|
||||
{
|
||||
buf[len++] = '\n';
|
||||
write_all(log_fd >= 0 ? log_fd : STDERR_FILENO, buf, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes a single log line.
|
||||
*
|
||||
* This is the implementation behind the log() macro, which is the one that
|
||||
* checks the verbosity level, so the line written here is unconditional.
|
||||
*
|
||||
* @param level Logging level of the line.
|
||||
* @param fmt Format string.
|
||||
*/
|
||||
void log_printf(int level, const char *fmt, ...)
|
||||
{
|
||||
char buf[LOG_LINE_MAX];
|
||||
va_list args;
|
||||
size_t len = log_prefix(buf, sizeof(buf) - 1, level);
|
||||
|
||||
va_start(args, fmt);
|
||||
len = append_vfmt(buf, sizeof(buf) - 1, len, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
log_emit(buf, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes a hexadecimal dump of a buffer, one or more prefixed lines.
|
||||
*
|
||||
* @param level Logging level of the dump.
|
||||
* @param prefix Text to put right after the line prefix, e.g. a direction marker.
|
||||
* @param data Data to dump.
|
||||
* @param length Number of bytes to dump.
|
||||
*/
|
||||
void log_hexdump(int level, const char *prefix, const uint8_t *data, int length)
|
||||
{
|
||||
char buf[LOG_LINE_MAX];
|
||||
int i = 0;
|
||||
|
||||
if (verbose < level) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Long dumps are split into several lines, each one with its own prefix
|
||||
do {
|
||||
size_t len = log_prefix(buf, sizeof(buf) - 1, level);
|
||||
len = append_fmt(buf, sizeof(buf) - 1, len, "%s", prefix);
|
||||
while ((i < length) && (len + 3 < sizeof(buf) - 1)) {
|
||||
len = append_fmt(buf, sizeof(buf) - 1, len, "%02X ", data[i++]);
|
||||
}
|
||||
log_emit(buf, len);
|
||||
} while (i < length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes logging.
|
||||
*
|
||||
* @param path Path of the log file or NULL to keep logging to stderr.
|
||||
* @param timestamps_mode 1 to force timestamps on, 0 to force them off,
|
||||
* negative to enable them only when logging to a file.
|
||||
*/
|
||||
void log_init(const char *path, int8_t timestamps_mode)
|
||||
{
|
||||
log_timestamps = (timestamps_mode >= 0) ? (timestamps_mode != 0) : (path != NULL);
|
||||
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_path = strdup(path);
|
||||
if (!log_path) {
|
||||
fprintf(stderr, "Out of memory while initializing the log file\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
log_fd = open(path, O_WRONLY | O_CREAT | O_APPEND | O_BINARY, LOG_FILE_MODE);
|
||||
if (log_fd < 0) {
|
||||
// Logging into nowhere is worse than not starting at all
|
||||
fprintf(stderr, "Can't open log file '%s' - %s (%d)\n", path, strerror(errno), errno);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Make every log file start with the version, no matter the verbosity level
|
||||
log_printf(LL_INFO, "Starting %s", version_string());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reopens the log file, to be called after the log has been rotated.
|
||||
*/
|
||||
void log_reopen(void)
|
||||
{
|
||||
int fd;
|
||||
|
||||
if (!log_path) {
|
||||
return;
|
||||
}
|
||||
|
||||
fd = open(log_path, O_WRONLY | O_CREAT | O_APPEND | O_BINARY, LOG_FILE_MODE);
|
||||
if (fd < 0) {
|
||||
// Keep the old descriptor, losing the log completely would be worse
|
||||
fprintf(stderr, "Can't reopen log file '%s' - %s (%d)\n", log_path, strerror(errno), errno);
|
||||
return;
|
||||
}
|
||||
if (log_fd >= 0) {
|
||||
close(log_fd);
|
||||
}
|
||||
log_fd = fd;
|
||||
|
||||
log_printf(LL_INFO, "Log file reopened, %s", version_string());
|
||||
}
|
||||
170
ios/Tunnel/obfuscator/masking.c
Normal file
170
ios/Tunnel/obfuscator/masking.c
Normal file
@@ -0,0 +1,170 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include "wg-obfuscator.h"
|
||||
#include "masking.h"
|
||||
#include "masking_handlers.h"
|
||||
|
||||
static _Thread_local struct {
|
||||
int listen_sock;
|
||||
struct sockaddr_in *sender_addr;
|
||||
int server_sock;
|
||||
} g_send_ctx;
|
||||
|
||||
static ssize_t send_to_client_cb(uint8_t *buffer, int length) {
|
||||
return sendto(g_send_ctx.listen_sock, buffer, length, 0, (struct sockaddr *)g_send_ctx.sender_addr, sizeof(*g_send_ctx.sender_addr));
|
||||
}
|
||||
|
||||
static ssize_t send_to_server_cb(uint8_t *buffer, int length) {
|
||||
return send(g_send_ctx.server_sock, buffer, length, 0);
|
||||
}
|
||||
|
||||
masking_handler_t * get_masking_handler_by_name(const char *name) {
|
||||
if (!name) return NULL;
|
||||
|
||||
for (int i = 0; masking_handlers[i]; ++i) {
|
||||
char handler_name_lower[MASKING_HANDLER_NAME_LEN];
|
||||
strncpy(handler_name_lower, masking_handlers[i]->name, sizeof(handler_name_lower) - 1);
|
||||
handler_name_lower[sizeof(handler_name_lower) - 1] = 0;
|
||||
for (char *p = handler_name_lower; *p; ++p) *p = tolower((unsigned char)*p);
|
||||
|
||||
if (strcmp(name, handler_name_lower) == 0) {
|
||||
return masking_handlers[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void masking_on_handshake_req_from_client(obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *client_addr,
|
||||
struct sockaddr_in *server_addr) {
|
||||
if (!client->masking_handler || !client->masking_handler->on_handshake_req) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_send_ctx.listen_sock = listen_sock;
|
||||
g_send_ctx.sender_addr = &client->client_addr;
|
||||
g_send_ctx.server_sock = client->server_sock;
|
||||
client->masking_handler->on_handshake_req(config, client, DIR_CLIENT_TO_SERVER, client_addr, server_addr, send_to_client_cb, send_to_server_cb);
|
||||
}
|
||||
|
||||
void masking_on_handshake_req_from_server(obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *client_addr,
|
||||
struct sockaddr_in *server_addr) {
|
||||
if (!client->masking_handler || !client->masking_handler->on_handshake_req) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_send_ctx.listen_sock = listen_sock;
|
||||
g_send_ctx.sender_addr = &client->client_addr;
|
||||
g_send_ctx.server_sock = client->server_sock;
|
||||
client->masking_handler->on_handshake_req(config, client, DIR_SERVER_TO_CLIENT, server_addr, client_addr, send_to_server_cb, send_to_client_cb);
|
||||
}
|
||||
|
||||
int masking_unwrap_from_client(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client, // can be NULL!
|
||||
int listen_sock,
|
||||
struct sockaddr_in *client_addr,
|
||||
struct sockaddr_in *server_addr,
|
||||
masking_handler_t **masking_handler_out) {
|
||||
g_send_ctx.listen_sock = listen_sock;
|
||||
g_send_ctx.sender_addr = client_addr;
|
||||
g_send_ctx.server_sock = client ? client->server_sock : 0;
|
||||
|
||||
if (!client && !config->masking_handler_set) {
|
||||
// Brueteforce detection of masking type if no client entry and no default masking handler
|
||||
for (int i = 0; masking_handlers[i]; ++i) {
|
||||
// A handler may move *buffer_ptr while unwrapping. If it does not match
|
||||
// (returns < 0), restore the pointer so the next handler sees the original data.
|
||||
uint8_t *saved_buffer = *buffer_ptr;
|
||||
int r = masking_handlers[i]->on_data_unwrap(buffer_ptr, length, config, NULL, DIR_CLIENT_TO_SERVER, client_addr, server_addr, send_to_client_cb, send_to_server_cb);
|
||||
if (r < 0) {
|
||||
*buffer_ptr = saved_buffer;
|
||||
continue;
|
||||
}
|
||||
// Found a matching masking handler
|
||||
log(LL_TRACE, "Autodetected masking handler for packet from %s:%d: %s", inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port), masking_handlers[i]->name);
|
||||
if (masking_handler_out) {
|
||||
*masking_handler_out = masking_handlers[i];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// No matching masking handler found
|
||||
if (masking_handler_out) {
|
||||
*masking_handler_out = NULL;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
masking_handler_t *handler = client ? client->masking_handler : config->masking_handler;
|
||||
if (!handler || !handler->on_data_unwrap) {
|
||||
return length; // no masking handler, nothing to do
|
||||
}
|
||||
|
||||
return handler->on_data_unwrap(buffer_ptr, length, config, client, DIR_CLIENT_TO_SERVER, client_addr, server_addr, send_to_client_cb, client ? send_to_server_cb : NULL);
|
||||
}
|
||||
|
||||
int masking_unwrap_from_server(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *server_addr) {
|
||||
if (!client->masking_handler || !client->masking_handler->on_data_unwrap) {
|
||||
return length; // no masking handler, nothing to do
|
||||
}
|
||||
|
||||
g_send_ctx.listen_sock = listen_sock;
|
||||
g_send_ctx.sender_addr = &client->client_addr;
|
||||
g_send_ctx.server_sock = client->server_sock;
|
||||
return client->masking_handler->on_data_unwrap(buffer_ptr, length, config, client, DIR_SERVER_TO_CLIENT, server_addr, &client->client_addr, send_to_server_cb, send_to_client_cb);
|
||||
}
|
||||
|
||||
int masking_data_wrap_to_client(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *server_addr) {
|
||||
if (!client->masking_handler || !client->masking_handler->on_data_wrap) {
|
||||
return length; // no masking handler, nothing to do
|
||||
}
|
||||
|
||||
g_send_ctx.listen_sock = listen_sock;
|
||||
g_send_ctx.sender_addr = &client->client_addr;
|
||||
g_send_ctx.server_sock = client->server_sock;
|
||||
return client->masking_handler->on_data_wrap(buffer_ptr, length, config, client, DIR_SERVER_TO_CLIENT, server_addr, &client->client_addr, send_to_server_cb, send_to_client_cb);
|
||||
}
|
||||
|
||||
int masking_data_wrap_to_server(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *server_addr) {
|
||||
if (!client->masking_handler || !client->masking_handler->on_data_wrap) {
|
||||
return length; // no masking handler, nothing to do
|
||||
}
|
||||
|
||||
g_send_ctx.listen_sock = listen_sock;
|
||||
g_send_ctx.sender_addr = &client->client_addr;
|
||||
g_send_ctx.server_sock = client->server_sock;
|
||||
return client->masking_handler->on_data_wrap(buffer_ptr, length, config, client, DIR_CLIENT_TO_SERVER, &client->client_addr, server_addr, send_to_client_cb, send_to_server_cb);
|
||||
}
|
||||
|
||||
void masking_on_timer(obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *server_addr) {
|
||||
if (!client->masking_handler || !client->masking_handler->on_timer) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_send_ctx.listen_sock = listen_sock;
|
||||
g_send_ctx.sender_addr = &client->client_addr;
|
||||
g_send_ctx.server_sock = client->server_sock;
|
||||
client->masking_handler->on_timer(config, client, &client->client_addr, server_addr, send_to_client_cb, send_to_server_cb);
|
||||
}
|
||||
91
ios/Tunnel/obfuscator/masking.h
Normal file
91
ios/Tunnel/obfuscator/masking.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#ifndef _MASKING_H_
|
||||
#define _MASKING_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <netinet/in.h>
|
||||
#include "wg-obfuscator.h"
|
||||
|
||||
#define MASKING_HANDLER_NAME_LEN 32
|
||||
|
||||
typedef ssize_t (*send_data_callback_t)(uint8_t *buffer, int length);
|
||||
|
||||
typedef void (*masking_event_handler_t)(obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
direction_t direction,
|
||||
const struct sockaddr_in *src_addr,
|
||||
const struct sockaddr_in *dest_addr,
|
||||
send_data_callback_t send_back_callback,
|
||||
send_data_callback_t send_forward_callback);
|
||||
|
||||
typedef int (*masking_data_handler_t)(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
direction_t direction,
|
||||
const struct sockaddr_in *src_addr,
|
||||
const struct sockaddr_in *dest_addr,
|
||||
send_data_callback_t send_back_callback,
|
||||
send_data_callback_t send_forward_callback);
|
||||
|
||||
typedef void (*masking_timer_handler_t)(obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
const struct sockaddr_in *client_addr,
|
||||
const struct sockaddr_in *server_addr,
|
||||
send_data_callback_t send_to_client_callback,
|
||||
send_data_callback_t send_to_server_callback);
|
||||
|
||||
struct masking_handler {
|
||||
char name[MASKING_HANDLER_NAME_LEN];
|
||||
masking_event_handler_t on_handshake_req;
|
||||
masking_data_handler_t on_data_wrap;
|
||||
masking_data_handler_t on_data_unwrap;
|
||||
masking_timer_handler_t on_timer;
|
||||
uint32_t timer_interval_s;
|
||||
};
|
||||
typedef struct masking_handler masking_handler_t;
|
||||
|
||||
masking_handler_t * get_masking_handler_by_name(const char *name);
|
||||
|
||||
void masking_on_handshake_req_from_client(obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *client_addr,
|
||||
struct sockaddr_in *server_addr);
|
||||
|
||||
void masking_on_handshake_req_from_server(obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *client_addr,
|
||||
struct sockaddr_in *server_addr);
|
||||
|
||||
int masking_data_wrap_to_client(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *server_addr);
|
||||
|
||||
int masking_data_wrap_to_server(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *server_addr);
|
||||
|
||||
int masking_unwrap_from_client(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *client_addr,
|
||||
struct sockaddr_in *server_addr,
|
||||
masking_handler_t **masking_handler_out);
|
||||
|
||||
int masking_unwrap_from_server(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *server_addr);
|
||||
|
||||
void masking_on_timer(obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
int listen_sock,
|
||||
struct sockaddr_in *server_addr);
|
||||
|
||||
#endif // _MASKING_H_
|
||||
14
ios/Tunnel/obfuscator/masking_handlers.h
Normal file
14
ios/Tunnel/obfuscator/masking_handlers.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef _MASKING_HANDLERS_H_
|
||||
#define _MASKING_HANDLERS_H_
|
||||
|
||||
#include "masking.h"
|
||||
|
||||
/* List of available masking handlers */
|
||||
#include "masking_stun.h"
|
||||
|
||||
static masking_handler_t * const masking_handlers[] = {
|
||||
&stun_masking_handler,
|
||||
NULL
|
||||
};
|
||||
|
||||
#endif
|
||||
298
ios/Tunnel/obfuscator/masking_stun.c
Normal file
298
ios/Tunnel/obfuscator/masking_stun.c
Normal file
@@ -0,0 +1,298 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include "wg-obfuscator.h"
|
||||
#include "masking.h"
|
||||
#include "masking_stun.h"
|
||||
|
||||
// Bytes prepended in front of the payload when wrapping data in a STUN Data Indication
|
||||
// (20-byte STUN header + 4-byte attribute header). Must fit into PREBUFFER_SIZE so that
|
||||
// wrapping can be done by shifting the buffer pointer instead of moving the payload.
|
||||
#define STUN_WRAP_OVERHEAD 24
|
||||
_Static_assert(STUN_WRAP_OVERHEAD <= PREBUFFER_SIZE,
|
||||
"PREBUFFER_SIZE is too small to hold the STUN wrap header");
|
||||
|
||||
static void rand_bytes(uint8_t* p, size_t n){ for(size_t i=0;i<n;i++) p[i]=rand()&0xFF; }
|
||||
|
||||
int stun_check_magic(const uint8_t *buf, size_t len) {
|
||||
if (!buf || len < 8) return 0;
|
||||
return !memcmp(buf + 4, COOKIE_BE, 4);
|
||||
}
|
||||
|
||||
uint16_t stun_peek_type(const uint8_t *buf){
|
||||
return (buf[0] << 8) | buf[1];
|
||||
}
|
||||
|
||||
static int stun_write_header(uint8_t *b, uint16_t type, uint16_t mlen, const uint8_t txid[12]) {
|
||||
b[0] = type >> 8;
|
||||
b[1] = type & 0xFF;
|
||||
b[2] = mlen >> 8;
|
||||
b[3] = mlen & 0xFF;
|
||||
memcpy(b + 4, COOKIE_BE, 4);
|
||||
memcpy(b + 8, txid, 12);
|
||||
return 20;
|
||||
}
|
||||
|
||||
static int stun_attr_xor_mapped_addr(uint8_t *b, const struct sockaddr_in *src) {
|
||||
// type,len
|
||||
b[0] = STUN_ATTR_XORMAPPED >> 8;
|
||||
b[1] = STUN_ATTR_XORMAPPED & 0xFF;
|
||||
b[2] = 0;
|
||||
b[3] = 8; // len=8 (family + port + addr)
|
||||
b[4] = 0;
|
||||
b[5] = 0x01; // family IPv4
|
||||
memcpy(b + 6, &src->sin_port, 2); // network-order bytes
|
||||
((uint8_t*)b)[6] ^= COOKIE_BE[0];
|
||||
((uint8_t*)b)[7] ^= COOKIE_BE[1];
|
||||
memcpy(b + 8, &src->sin_addr.s_addr, 4); // network-order bytes
|
||||
((uint8_t*)b)[8] ^= COOKIE_BE[0];
|
||||
((uint8_t*)b)[9] ^= COOKIE_BE[1];
|
||||
((uint8_t*)b)[10] ^= COOKIE_BE[2];
|
||||
((uint8_t*)b)[11] ^= COOKIE_BE[3];
|
||||
return 12; // 4 hdr + 8 val
|
||||
}
|
||||
|
||||
/*
|
||||
static int stun_attr_software(uint8_t *b, const char *s) {
|
||||
uint16_t n = (uint16_t)strlen(s);
|
||||
uint16_t pad = (4 - (n & 3)) & 3;
|
||||
if (b) {
|
||||
b[0] = STUN_ATTR_SOFTWARE >> 8;
|
||||
b[1] = STUN_ATTR_SOFTWARE&0xFF;
|
||||
b[2] = n>>8;
|
||||
b[3] = n&0xFF;
|
||||
memcpy(b+4,s,n);
|
||||
if (pad) memset(b+4+n,0,pad);
|
||||
}
|
||||
return 4 + n + pad;
|
||||
}
|
||||
*/
|
||||
|
||||
static uint32_t crc32(const uint8_t *p, size_t n) {
|
||||
uint32_t crc = ~0u;
|
||||
for(size_t i = 0; i < n; i++){
|
||||
crc ^= p[i];
|
||||
for(int k = 0; k < 8; k++)
|
||||
crc = (crc >> 1) ^ (0xEDB88320u & (-(int)(crc & 1)));
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
static int stun_attr_fingerprint(uint8_t *pkt, size_t cur_len) {
|
||||
uint8_t *b = pkt + cur_len;
|
||||
b[0] = STUN_ATTR_FINGERPR >> 8;
|
||||
b[1] = STUN_ATTR_FINGERPR & 0xFF;
|
||||
b[2] = 0;
|
||||
b[3] = 4;
|
||||
uint32_t fp = htonl(crc32(pkt, cur_len) ^ 0x5354554eu);
|
||||
memcpy(b + 4, &fp, 4);
|
||||
return 8; // 4 hdr + 4 val
|
||||
}
|
||||
|
||||
static int stun_build_binding_request(uint8_t *out) {
|
||||
uint8_t txid[12];
|
||||
rand_bytes(txid, 12);
|
||||
stun_write_header(out, STUN_BINDING_REQ, 0, txid);
|
||||
size_t mlen = 0;
|
||||
// optional SOFTWARE
|
||||
//mlen += stun_attr_software(out+20+mlen, "wgo/1.0");
|
||||
mlen += stun_attr_fingerprint(out, 20 + mlen);
|
||||
out[2] = (mlen >> 8) & 0xFF;
|
||||
out[3] = mlen & 0xFF;
|
||||
return (int)(20 + mlen);
|
||||
}
|
||||
|
||||
static int stun_build_binding_success(uint8_t *out,
|
||||
const uint8_t txid[12],
|
||||
const struct sockaddr_in *src) {
|
||||
if (BUFFER_SIZE < 20 + 12 + 8) return -1; // header + XOR-MAPPED-ADDRESS + fingerprint
|
||||
stun_write_header(out, STUN_BINDING_RESP, 0, (uint8_t*)txid);
|
||||
size_t mlen = 0;
|
||||
mlen += stun_attr_xor_mapped_addr(out + 20 + mlen, src);
|
||||
// optional SOFTWARE
|
||||
//mlen += stun_attr_software(out+20+mlen, "wgo/1.0");
|
||||
mlen += stun_attr_fingerprint(out, 20 + mlen);
|
||||
out[2] = (mlen >> 8) & 0xFF;
|
||||
out[3] = mlen & 0xFF;
|
||||
return (int)(20 + mlen);
|
||||
}
|
||||
|
||||
static int stun_wrap(uint8_t **buf_ptr, size_t data_len) {
|
||||
size_t mlen = 0;
|
||||
|
||||
// The caller guarantees the payload already fits into the main buffer (it was
|
||||
// received/encoded within BUFFER_SIZE), so the wrapped packet cannot overflow
|
||||
// the tail of full_buffer. The attribute length field is also 16-bit only.
|
||||
// Note: BUFFER_SIZE (65535) equals the maximum value of a 16-bit unsigned
|
||||
// integer (0xFFFF), so this single check covers both the buffer limit and
|
||||
// the STUN attribute length field limit.
|
||||
if (data_len > BUFFER_SIZE) {
|
||||
log(LL_WARN, "Can't wrap data in STUN, data too large (%zu bytes)", data_len);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
// Header is prepended into the prebuffer (checked at compile time via _Static_assert).
|
||||
*buf_ptr -= STUN_WRAP_OVERHEAD;
|
||||
uint8_t *buf = *buf_ptr;
|
||||
|
||||
uint8_t txid[12];
|
||||
rand_bytes(txid,12);
|
||||
mlen += stun_write_header(buf, STUN_TYPE_DATA_IND, 0, txid);
|
||||
|
||||
buf[mlen] = STUN_ATTR_DATA >> 8;
|
||||
buf[mlen + 1] = STUN_ATTR_DATA & 0xFF;
|
||||
buf[mlen + 2] = data_len >> 8;
|
||||
buf[mlen + 3] = data_len & 0xFF;
|
||||
|
||||
return STUN_WRAP_OVERHEAD + data_len;
|
||||
}
|
||||
|
||||
static int stun_unwrap(uint8_t **buf_ptr, size_t len) {
|
||||
uint8_t *buf = *buf_ptr;
|
||||
if (len < 20 + 4) return -1; // header + attr
|
||||
|
||||
uint16_t msg_type = (buf[0] << 8) | buf[1];
|
||||
if (msg_type != STUN_TYPE_DATA_IND) return -1;
|
||||
|
||||
uint16_t msg_len = (buf[2] << 8) | buf[3];
|
||||
if (msg_len + 20 > len) return -1;
|
||||
|
||||
uint16_t attr_type = (buf[20] << 8) | buf[21];
|
||||
if (attr_type != STUN_ATTR_DATA) return -1;
|
||||
|
||||
uint16_t data_len = (buf[22] << 8) | buf[23];
|
||||
if (data_len + 24 > len) return -1;
|
||||
|
||||
// Move the buffer pointer to avoid moving the data
|
||||
*buf_ptr += 24;
|
||||
|
||||
return data_len;
|
||||
}
|
||||
|
||||
static void stun_on_handshake_req(obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
direction_t direction,
|
||||
const struct sockaddr_in *src_addr,
|
||||
const struct sockaddr_in *dest_addr,
|
||||
send_data_callback_t send_back_callback,
|
||||
send_data_callback_t send_forward_callback) {
|
||||
uint8_t buffer[128];
|
||||
int len = stun_build_binding_request(buffer);
|
||||
if (len < 0) return;
|
||||
|
||||
int sent = send_forward_callback(buffer, len);
|
||||
if (sent < 0) {
|
||||
serror("Can't send STUN binding request to %s:%d", inet_ntoa(dest_addr->sin_addr), ntohs(dest_addr->sin_port));
|
||||
} else if (sent != len) {
|
||||
log(LL_WARN, "Partial send of STUN binding request to %s:%d (%d of %d bytes)", inet_ntoa(dest_addr->sin_addr), ntohs(dest_addr->sin_port), sent, len);
|
||||
} else {
|
||||
log(LL_TRACE, "Sent STUN binding request (%d bytes) to %s:%d", len, inet_ntoa(dest_addr->sin_addr), ntohs(dest_addr->sin_port));
|
||||
}
|
||||
}
|
||||
|
||||
static int stun_on_data_unwrap(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
direction_t direction,
|
||||
const struct sockaddr_in *src_addr,
|
||||
const struct sockaddr_in *dest_addr,
|
||||
send_data_callback_t send_back_callback,
|
||||
send_data_callback_t send_forward_callback) {
|
||||
if (!stun_check_magic(*buffer_ptr, length)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint16_t stun_type = stun_peek_type(*buffer_ptr);
|
||||
|
||||
switch (stun_type) {
|
||||
case STUN_BINDING_REQ: {
|
||||
// Received STUN Binding Request from client, send Binding Success Response
|
||||
log(LL_TRACE, "Received STUN Binding Request from %s:%d", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port));
|
||||
uint8_t txid[12];
|
||||
memcpy(txid, (*buffer_ptr) + 8, 12);
|
||||
int resp_len = stun_build_binding_success(*buffer_ptr, txid, src_addr);
|
||||
if (resp_len > 0) {
|
||||
int sent = send_back_callback(*buffer_ptr, resp_len);
|
||||
if (sent < 0) {
|
||||
serror("sendto STUN response to %s:%d", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port));
|
||||
} else if (sent != resp_len) {
|
||||
log(LL_WARN, "Partial send of STUN Binding Success Response to %s:%d (%d of %d bytes)", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port), sent, resp_len);
|
||||
} else {
|
||||
log(LL_TRACE, "Sent STUN Binding Success Response (%d bytes) to %s:%d", resp_len, inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port));
|
||||
}
|
||||
} else {
|
||||
log(LL_ERROR, "Failed to build STUN Binding Success Response");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case STUN_BINDING_RESP:
|
||||
log(LL_TRACE, "Received STUN Binding Success Response from %s:%d, ignoring", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port));
|
||||
return 0;
|
||||
case STUN_TYPE_DATA_IND:
|
||||
length = stun_unwrap(buffer_ptr, length);
|
||||
if (length < 0) {
|
||||
log(LL_DEBUG, "Failed to unwrap STUN Data Indication from %s:%d", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port));
|
||||
return length;
|
||||
}
|
||||
log(LL_TRACE, "Unwrapped STUN Data Indication from %s:%d (%d bytes)", inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port), length);
|
||||
return length;
|
||||
default:
|
||||
log(LL_DEBUG, "Received unknown STUN type %04X from %s:%d, ignoring", stun_type, inet_ntoa(src_addr->sin_addr), ntohs(src_addr->sin_port));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int stun_on_data_wrap(uint8_t **buffer_ptr, int length,
|
||||
obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
direction_t direction,
|
||||
const struct sockaddr_in *src_addr,
|
||||
const struct sockaddr_in *dest_addr,
|
||||
send_data_callback_t send_back_callback,
|
||||
send_data_callback_t send_forward_callback) {
|
||||
return stun_wrap(buffer_ptr, length);
|
||||
}
|
||||
|
||||
static void stun_on_timer(obfuscator_config_t *config,
|
||||
client_entry_t *client,
|
||||
const struct sockaddr_in *client_addr,
|
||||
const struct sockaddr_in *server_addr,
|
||||
send_data_callback_t send_to_client_callback,
|
||||
send_data_callback_t send_to_server_callback) {
|
||||
uint8_t buffer[128];
|
||||
int len = stun_build_binding_request(buffer);
|
||||
if (len < 0) return;
|
||||
|
||||
if (client->client_obfuscated) {
|
||||
int sent = send_to_client_callback(buffer, len);
|
||||
if (sent < 0) {
|
||||
serror("STUN binding request to client");
|
||||
} else if (sent != len) {
|
||||
log(LL_WARN, "Partial send of STUN binding request to client (%d of %d bytes)", sent, len);
|
||||
} else {
|
||||
log(LL_TRACE, "Sent STUN binding request (%d bytes) to %s:%d", len, inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port));
|
||||
}
|
||||
}
|
||||
if (client->server_obfuscated) {
|
||||
int sent = send_to_server_callback(buffer, len);
|
||||
if (sent < 0) {
|
||||
serror("STUN binding request to server");
|
||||
} else if (sent != len) {
|
||||
log(LL_WARN, "Partial send of STUN binding request to server (%d of %d bytes)", sent, len);
|
||||
} else {
|
||||
log(LL_TRACE, "Sent STUN binding request (%d bytes) to %s:%d", len, inet_ntoa(server_addr->sin_addr), ntohs(server_addr->sin_port));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
masking_handler_t stun_masking_handler = {
|
||||
.name = "STUN",
|
||||
.on_handshake_req = stun_on_handshake_req,
|
||||
.on_data_wrap = stun_on_data_wrap,
|
||||
.on_data_unwrap = stun_on_data_unwrap,
|
||||
.on_timer = stun_on_timer,
|
||||
.timer_interval_s = 10, // 10 seconds
|
||||
};
|
||||
19
ios/Tunnel/obfuscator/masking_stun.h
Normal file
19
ios/Tunnel/obfuscator/masking_stun.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef _MASKING_STUN_H_
|
||||
#define _MASKING_STUN_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <netinet/in.h>
|
||||
#include "wg-obfuscator.h"
|
||||
|
||||
static const uint8_t COOKIE_BE[4] = {0x21,0x12,0xA4,0x42};
|
||||
#define STUN_TYPE_DATA_IND 0x0115
|
||||
#define STUN_BINDING_REQ 0x0001
|
||||
#define STUN_BINDING_RESP 0x0101
|
||||
#define STUN_ATTR_XORMAPPED 0x0020
|
||||
#define STUN_ATTR_SOFTWARE 0x8022
|
||||
#define STUN_ATTR_FINGERPR 0x8028
|
||||
#define STUN_ATTR_DATA 0x0013
|
||||
|
||||
extern masking_handler_t stun_masking_handler;
|
||||
|
||||
#endif // _MASKING_STUN_H_
|
||||
82
ios/Tunnel/obfuscator/mini_argp.h
Normal file
82
ios/Tunnel/obfuscator/mini_argp.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/* mini_argp.h
|
||||
* Tiny callback-based CLI parser */
|
||||
|
||||
#ifndef _MINI_ARGP_H_
|
||||
#define _MINI_ARGP_H_
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef int (*mini_argp_cb)(const char *lname, char sname,
|
||||
const char *val, void *ctx);
|
||||
|
||||
typedef struct {
|
||||
const char *long_name; /* "--target" or NULL */
|
||||
char short_name; /* 't' or 0 */
|
||||
int has_arg; /* 0 flag, 1 needs val */
|
||||
mini_argp_cb cb; /* your handler */
|
||||
} mini_argp_opt;
|
||||
|
||||
/* internal: find desc by name */
|
||||
static const mini_argp_opt *
|
||||
margp_find(const mini_argp_opt *o, char *lname, char sname)
|
||||
{
|
||||
for (; o->long_name || o->short_name; o++)
|
||||
if ((lname && o->long_name && !strcmp(lname,o->long_name)) ||
|
||||
(sname && o->short_name==sname))
|
||||
return o;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int mini_argp_parse(int argc, char **argv,
|
||||
const mini_argp_opt *opts,
|
||||
void *ctx,
|
||||
mini_argp_cb cb)
|
||||
{
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
char *arg = argv[i];
|
||||
|
||||
/* long option: --foo or --foo=bar */
|
||||
if (!strncmp(arg, "--", 2)) {
|
||||
char *name = arg + 2, *val = NULL;
|
||||
if ((val = strchr(name, '='))) { *val = 0; ++val; }
|
||||
|
||||
const mini_argp_opt *o = margp_find(opts, name, 0);
|
||||
if (!o) { fprintf(stderr,"unknown --%s\n", name); return -1; }
|
||||
if (o->has_arg && !val) {
|
||||
if (++i == argc) { fprintf(stderr,"--%s needs value\n",name);return -1; }
|
||||
val = argv[i];
|
||||
}
|
||||
if (!o->has_arg) val = NULL;
|
||||
mini_argp_cb to_call = o->cb ? o->cb : cb;
|
||||
if (to_call(o->long_name, o->short_name, val, ctx)) return -1;
|
||||
}
|
||||
|
||||
/* short(s): -a, -fVAL, -xzvf */
|
||||
else if (arg[0]=='-' && arg[1]) {
|
||||
for (size_t pos=1; arg[pos]; ++pos) {
|
||||
char c = arg[pos];
|
||||
const mini_argp_opt *o = margp_find(opts,NULL,c);
|
||||
if (!o){ fprintf(stderr,"unknown -%c\n",c); return -1;}
|
||||
const char *val = NULL;
|
||||
|
||||
if (o->has_arg) {
|
||||
if (arg[pos+1]) val = &arg[pos+1]; /* -fVAL */
|
||||
else if (++i < argc) val = argv[i]; /* -f VAL */
|
||||
else { fprintf(stderr,"-%c needs value\n",c); return -1;}
|
||||
/* Stop further char-scanning: the rest of the arg is the value */
|
||||
pos = strlen(arg)-1;
|
||||
}
|
||||
mini_argp_cb to_call = o->cb ? o->cb : cb;
|
||||
if (to_call(o->long_name, o->short_name, val, ctx)) return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* positional not allowed */
|
||||
else {
|
||||
fprintf(stderr,"unexpected arg: %s\n",arg); return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif/* _MINI_ARGP_H_ */
|
||||
130
ios/Tunnel/obfuscator/obfuscation.c
Normal file
130
ios/Tunnel/obfuscator/obfuscation.c
Normal file
@@ -0,0 +1,130 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "wg-obfuscator.h"
|
||||
#include "obfuscation.h"
|
||||
|
||||
static uint8_t crc_a[256]; // step(crc, 0): contribution of the current CRC state
|
||||
static uint8_t crc_b[256]; // step(0, inbyte): contribution of the input byte
|
||||
static uint8_t tables_ready = 0;
|
||||
|
||||
typedef struct {
|
||||
uint8_t *data; // cached keystream bytes, NULL until first allocation
|
||||
int valid; // number of valid bytes in data
|
||||
int cap; // allocated capacity of data
|
||||
uint8_t crc; // CRC state right after the 'valid' bytes
|
||||
} keystream_row_t;
|
||||
|
||||
static keystream_row_t rows[256];
|
||||
|
||||
// Single step of the original bit-by-bit CRC8 update, kept only to build the tables.
|
||||
static uint8_t crc8_step(uint8_t crc, uint8_t inbyte) {
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
uint8_t mix = (crc ^ inbyte) & 0x01;
|
||||
crc >>= 1;
|
||||
if (mix) {
|
||||
crc ^= 0x8C;
|
||||
}
|
||||
inbyte >>= 1;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void ensure_tables(void) {
|
||||
if (tables_ready) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
crc_a[i] = crc8_step((uint8_t)i, 0);
|
||||
crc_b[i] = crc8_step(0, (uint8_t)i);
|
||||
}
|
||||
tables_ready = 1;
|
||||
}
|
||||
|
||||
// Ensure the given row holds at least 'need' valid keystream bytes (capped at
|
||||
// KEYSTREAM_ROW_MAX). On allocation failure the row is left as large as it could
|
||||
// grow; the caller then computes the missing tail on the fly.
|
||||
static void grow_row(keystream_row_t *row, int cls, int need, const char *key, int key_length) {
|
||||
if (need > KEYSTREAM_ROW_MAX) {
|
||||
need = KEYSTREAM_ROW_MAX;
|
||||
}
|
||||
if (row->valid >= need) {
|
||||
return;
|
||||
}
|
||||
if (row->cap < need) {
|
||||
int newcap = (need + 255) & ~255; // round up to a 256-byte chunk
|
||||
if (newcap > KEYSTREAM_ROW_MAX) {
|
||||
newcap = KEYSTREAM_ROW_MAX;
|
||||
}
|
||||
uint8_t *p = realloc(row->data, newcap);
|
||||
if (!p) {
|
||||
return; // keep whatever we already have; tail handled by caller
|
||||
}
|
||||
row->data = p;
|
||||
row->cap = newcap;
|
||||
}
|
||||
|
||||
uint8_t crc = row->crc;
|
||||
int ki = row->valid % key_length;
|
||||
for (int i = row->valid; i < need; i++) {
|
||||
uint8_t inbyte = (uint8_t)(key[ki] + cls + key_length);
|
||||
crc = crc_a[crc] ^ crc_b[inbyte];
|
||||
row->data[i] = crc;
|
||||
if (++ki == key_length) {
|
||||
ki = 0;
|
||||
}
|
||||
}
|
||||
row->valid = need;
|
||||
row->crc = crc;
|
||||
}
|
||||
|
||||
// XOR 'n' keystream bytes into dst, 8 bytes at a time. memcpy avoids both
|
||||
// strict-aliasing violations and unaligned-access assumptions.
|
||||
static void xor_block(uint8_t *dst, const uint8_t *ks, int n) {
|
||||
int i = 0;
|
||||
for (; i + 8 <= n; i += 8) {
|
||||
uint64_t a, b;
|
||||
memcpy(&a, dst + i, 8);
|
||||
memcpy(&b, ks + i, 8);
|
||||
a ^= b;
|
||||
memcpy(dst + i, &a, 8);
|
||||
}
|
||||
for (; i < n; i++) {
|
||||
dst[i] ^= ks[i];
|
||||
}
|
||||
}
|
||||
|
||||
void xor_data(uint8_t *buffer, int length, char *key, int key_length) {
|
||||
if (length <= 0) {
|
||||
return;
|
||||
}
|
||||
ensure_tables();
|
||||
|
||||
int cls = length & 0xFF;
|
||||
keystream_row_t *row = &rows[cls];
|
||||
|
||||
int cached = (length <= KEYSTREAM_ROW_MAX) ? length : KEYSTREAM_ROW_MAX;
|
||||
if (row->valid < cached) {
|
||||
grow_row(row, cls, cached, key, key_length);
|
||||
}
|
||||
|
||||
int n = (length < row->valid) ? length : row->valid;
|
||||
if (n > 0) {
|
||||
xor_block(buffer, row->data, n);
|
||||
}
|
||||
|
||||
// Tail beyond the cache limit (or beyond what we could allocate): compute on
|
||||
// the fly, continuing the CRC chain from the last cached state.
|
||||
if (n < length) {
|
||||
uint8_t crc = row->crc;
|
||||
int ki = n % key_length;
|
||||
for (int i = n; i < length; i++) {
|
||||
uint8_t inbyte = (uint8_t)(key[ki] + cls + key_length);
|
||||
crc = crc_a[crc] ^ crc_b[inbyte];
|
||||
buffer[i] ^= crc;
|
||||
if (++ki == key_length) {
|
||||
ki = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
ios/Tunnel/obfuscator/obfuscation.h
Normal file
139
ios/Tunnel/obfuscator/obfuscation.h
Normal file
@@ -0,0 +1,139 @@
|
||||
#ifndef _OBFUSCATION_H_
|
||||
#define _OBFUSCATION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Current obfuscation version
|
||||
#define OBFUSCATION_VERSION 1
|
||||
|
||||
// Maximum length (in bytes) of a single cached keystream row.
|
||||
// The keystream depends only on (key, length mod 256), so it is cached in 256
|
||||
// lazily-grown rows. Packets longer than this limit are still handled correctly:
|
||||
// the part beyond the limit is computed on the fly. Worst-case cache memory is
|
||||
// 256 * KEYSTREAM_ROW_MAX bytes. 2048 covers a typical MTU plus masking overhead.
|
||||
#define KEYSTREAM_ROW_MAX 2048
|
||||
|
||||
// WireGuard packet types
|
||||
#define WG_TYPE_HANDSHAKE 0x01
|
||||
#define WG_TYPE_HANDSHAKE_RESP 0x02
|
||||
#define WG_TYPE_COOKIE 0x03
|
||||
#define WG_TYPE_DATA 0x04
|
||||
|
||||
#define WG_TYPE(data) ((uint32_t)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)))
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Checks if the given data is obfuscated.
|
||||
*
|
||||
* @param data Pointer to the data buffer to check.
|
||||
* @return uint8_t Returns a non-zero value if the data is obfuscated, 0 otherwise.
|
||||
*/
|
||||
static inline uint8_t is_obfuscated(uint8_t *data) {
|
||||
uint32_t packet_type = WG_TYPE(data);
|
||||
return !(packet_type >= 1 && packet_type <= 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief XORs the data in the given buffer with the key-derived keystream.
|
||||
*
|
||||
* The keystream is a per-byte CRC8 value derived from the key, the byte position
|
||||
* and the packet length; it depends only on (key, length mod 256), so it is
|
||||
* cached (see obfuscation.c). The on-the-wire result is byte-for-byte identical
|
||||
* to the original bit-by-bit implementation.
|
||||
*
|
||||
* @param buffer Pointer to the data buffer to be XORed.
|
||||
* @param length Length of the data buffer in bytes.
|
||||
* @param key Pointer to the key used for XOR operation.
|
||||
* @param key_length Length of the key in bytes.
|
||||
*/
|
||||
void xor_data(uint8_t *buffer, int length, char *key, int key_length);
|
||||
|
||||
/**
|
||||
* @brief Encodes the given buffer using the specified key and version.
|
||||
*
|
||||
* This function applies an encoding algorithm to the input buffer using the provided key and version.
|
||||
* WARNING: buffer must be at least 4 bytes long and aligned to 4 bytes.
|
||||
*
|
||||
* @param buffer Pointer to the data buffer to encode.
|
||||
* @param length Length of the data buffer in bytes.
|
||||
* @param key Pointer to the key used for encoding.
|
||||
* @param key_length Length of the key in bytes.
|
||||
* @param version Encoding version to use.
|
||||
* @param max_dummy_length_data Maximum length of dummy data for data packets.
|
||||
* @return 0 on success, or a negative value on error.
|
||||
*/
|
||||
static inline int encode(uint8_t *buffer, int length, char *key, int key_length, uint8_t version, int max_dummy_length_data) {
|
||||
if (version >= 1) {
|
||||
uint32_t packet_type = WG_TYPE(buffer);
|
||||
// Add some randomness to the packet
|
||||
uint8_t rnd = 1 + (rand() % 255);
|
||||
buffer[0] ^= rnd; // Xor the first byte to a random value
|
||||
buffer[1] = rnd; // Set the second byte to a random value
|
||||
// Add dummy data to the packet
|
||||
if (length < MAX_DUMMY_LENGTH_TOTAL) {
|
||||
uint16_t dummy_length = 0;
|
||||
uint16_t max_dummy_length = MAX_DUMMY_LENGTH_TOTAL - length;
|
||||
switch (packet_type) {
|
||||
case WG_TYPE_HANDSHAKE:
|
||||
case WG_TYPE_HANDSHAKE_RESP:
|
||||
// length to MAX_DUMMY_LENGTH_HANDSHAKE
|
||||
dummy_length = rand() % MIN(max_dummy_length, MAX_DUMMY_LENGTH_HANDSHAKE);
|
||||
break;
|
||||
case WG_TYPE_COOKIE:
|
||||
case WG_TYPE_DATA:
|
||||
// length to MAX_DUMMY_LENGTH_HANDSHAKE
|
||||
if (max_dummy_length_data) {
|
||||
dummy_length = rand() % MIN(max_dummy_length, max_dummy_length_data);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//assert(0);
|
||||
break;
|
||||
}
|
||||
buffer[2] = dummy_length & 0xFF; // Set the dummy length in the packet
|
||||
buffer[3] = dummy_length >> 8; // Set the dummy length in
|
||||
if (dummy_length > 0) {
|
||||
int i = length;
|
||||
length += dummy_length;
|
||||
for (; i < length; ++i) {
|
||||
buffer[i] = 0xFF; // Fill with FFs, random data is not needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xor_data(buffer, length, key, key_length);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the given buffer using the provided key.
|
||||
*
|
||||
* WARNING: buffer must be at least 4 bytes long and aligned to 4 bytes.
|
||||
*
|
||||
* @param buffer Pointer to the input buffer to decode.
|
||||
* @param length Length of the input buffer.
|
||||
* @param key Pointer to the key used for decoding.
|
||||
* @param key_length Length of the key.
|
||||
* @param version_out Pointer to a variable where the decoded version will be stored.
|
||||
* @return Length of the decoded data (smaller than or equal to the input length).
|
||||
*/
|
||||
static inline int decode(uint8_t *buffer, int length, char *key, int key_length, uint8_t *version_out) {
|
||||
xor_data(buffer, length, key, key_length);
|
||||
|
||||
if (!is_obfuscated(buffer)) {
|
||||
// Looks like an old version
|
||||
*version_out = 0;
|
||||
return length;
|
||||
}
|
||||
|
||||
buffer[0] ^= buffer[1]; // Restore the first byte by XORing it with the second byte
|
||||
length -= (uint16_t)(buffer[2] | (buffer[3] << 8)); // Remove dummy data length from the packet
|
||||
buffer[1] = buffer[2] = buffer[3] = 0; // Reset the dummy length field to 0
|
||||
return length;
|
||||
}
|
||||
|
||||
#endif // _OBFUSCATION_H_
|
||||
1137
ios/Tunnel/obfuscator/uthash.h
Normal file
1137
ios/Tunnel/obfuscator/uthash.h
Normal file
File diff suppressed because it is too large
Load Diff
1320
ios/Tunnel/obfuscator/wg-obfuscator.c
Normal file
1320
ios/Tunnel/obfuscator/wg-obfuscator.c
Normal file
File diff suppressed because it is too large
Load Diff
135
ios/Tunnel/obfuscator/wg-obfuscator.h
Normal file
135
ios/Tunnel/obfuscator/wg-obfuscator.h
Normal file
@@ -0,0 +1,135 @@
|
||||
#ifndef _WG_OBFUSCATOR_H_
|
||||
#define _WG_OBFUSCATOR_H_
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include "uthash.h"
|
||||
|
||||
// on Linux, use epoll for better performance
|
||||
#ifdef __linux__
|
||||
#define USE_EPOLL
|
||||
#endif
|
||||
|
||||
#ifdef USE_EPOLL
|
||||
#include <sys/epoll.h>
|
||||
#define MAX_EVENTS 1024
|
||||
#else
|
||||
#include <poll.h>
|
||||
#endif
|
||||
|
||||
#define WG_OBFUSCATOR_VERSION "1.6"
|
||||
#define WG_OBFUSCATOR_GIT_REPO "https://github.com/ClusterM/wg-obfuscator"
|
||||
|
||||
#define LL_DEFAULT LL_INFO // default logging level
|
||||
|
||||
// Main parameters
|
||||
// TODO: make these configurable via command line arguments or config file
|
||||
#define BUFFER_SIZE 65535 // size of the buffer for receiving data from the clients and server
|
||||
#define PREBUFFER_SIZE 1024 // size of the additional buffer size before the main buffer
|
||||
#define POLL_TIMEOUT 5000 // in milliseconds
|
||||
#define HANDSHAKE_TIMEOUT 5000 // in milliseconds
|
||||
#define ITERATE_INTERVAL 1000 // in milliseconds
|
||||
#define MAX_DUMMY_LENGTH_TOTAL 1024 // maximum length of a packet after dummy data extension
|
||||
#define MAX_DUMMY_LENGTH_HANDSHAKE 512 // maximum length of dummy data for handshake packets
|
||||
|
||||
#define MAX_CLIENTS_DEFAULT 1024 // maximum number of clients
|
||||
#define IDLE_TIMEOUT_DEFAULT 300000 // in milliseconds
|
||||
#define IN_TIMEOUT_DEFAULT 0 // in milliseconds
|
||||
#define MAX_DUMMY_LENGTH_DATA_DEFAULT 4 // maximum length of dummy data for data packets
|
||||
|
||||
// Default instance name
|
||||
#define DEFAULT_INSTANCE_NAME "main"
|
||||
|
||||
// Logging levels
|
||||
#define LL_ERROR 0
|
||||
#define LL_WARN 1
|
||||
#define LL_INFO 2
|
||||
#define LL_DEBUG 3
|
||||
#define LL_TRACE 4
|
||||
|
||||
// Maximum length of a single log line, longer hex dumps are split into several lines
|
||||
#define LOG_LINE_MAX 16384
|
||||
|
||||
#define log(level, fmt, ...) do { if (verbose >= (level)) log_printf((level), fmt, ##__VA_ARGS__); } while (0)
|
||||
#define serror_level(level, fmt, ...) log(level, fmt " - %s (%d)", ##__VA_ARGS__, strerror(errno), errno)
|
||||
#define serror(fmt, ...) serror_level(LL_ERROR, fmt, ##__VA_ARGS__)
|
||||
|
||||
// Direction of... something
|
||||
typedef enum {
|
||||
DIR_CLIENT_TO_SERVER = 0,
|
||||
DIR_SERVER_TO_CLIENT = 1,
|
||||
} direction_t;
|
||||
|
||||
struct masking_handler; // forward declaration
|
||||
typedef struct masking_handler masking_handler_t;
|
||||
|
||||
// Structure to hold obfuscator configuration
|
||||
typedef struct {
|
||||
int listen_port; // Listening port for the obfuscator
|
||||
char forward_host_port[256]; // Host and port to forward the data to
|
||||
char xor_key[256]; // Key for obfuscation
|
||||
char client_interface[256]; // Client interface as a string
|
||||
char *static_bindings; // Static bindings as a string
|
||||
int max_clients; // Maximum number of clients
|
||||
long idle_timeout; // Idle timeout in milliseconds
|
||||
long in_timeout; // Incoming timeout in milliseconds
|
||||
int max_dummy_length_data; // Maximum length of dummy data for data packets
|
||||
uint32_t fwmark; // Firewall mark
|
||||
masking_handler_t *masking_handler; // Masking handler to use
|
||||
uint8_t allow_clean; // 1 if non-obfuscated (clean) clients are allowed, 0 otherwise
|
||||
char log_file[512]; // Path of the log file
|
||||
int8_t log_timestamps; // 1 to force timestamps on, 0 to force them off, -1 for auto
|
||||
long resolve_interval; // Hostname re-resolve interval in milliseconds, 0 to disable periodic refresh
|
||||
|
||||
uint8_t log_file_set; // 1 if the log file is set, 0 otherwise
|
||||
uint8_t listen_port_set; // 1 if the listen port is set, 0 otherwise
|
||||
uint8_t forward_host_port_set; // 1 if the forward host and port are set, 0 otherwise
|
||||
uint8_t xor_key_set; // 1 if the XOR key is set, 0 otherwise
|
||||
uint8_t client_interface_set; // 1 if the client interface is set, 0 otherwise
|
||||
uint8_t static_bindings_set; // 1 if the static bindings are set, 0 otherwise
|
||||
uint8_t masking_handler_set; // 1 if the masking handler is set, 0 otherwise
|
||||
} obfuscator_config_t;
|
||||
|
||||
// Structure to hold client connection information
|
||||
typedef struct {
|
||||
struct sockaddr_in client_addr; // client address and port (key for the hash table)
|
||||
struct sockaddr_in our_addr; // our address and port on the server connection
|
||||
long last_activity_time; // last time we received data from/to this client
|
||||
long last_incoming_time; // last time we received data from the remote server
|
||||
long last_handshake_request_time; // last time we received a handshake request from/to this client
|
||||
long last_handshake_time; // last time we received a handshake response from/to this client
|
||||
long last_masking_timer_time; // last time we called the masking timer handler for this client
|
||||
int server_sock; // socket for the connection to the server
|
||||
uint8_t version; // obfuscation version
|
||||
masking_handler_t *masking_handler; // masking handler in use
|
||||
uint8_t handshaked : 1; // 1 if the handshake is complete, 0 otherwise
|
||||
uint8_t handshake_direction : 1; // 1 if the handshake is from client to server, 0 if from server to client
|
||||
uint8_t client_obfuscated : 1; // 1 if the client is obfuscated, 0 otherwise
|
||||
uint8_t server_obfuscated : 1; // 1 if the server is obfuscated, 0 otherwise
|
||||
uint8_t client_clean : 1; // 1 if the client speaks plain (non-obfuscated) WireGuard, traffic is passed through as is (allow-clean mode)
|
||||
uint8_t is_static : 1; // 1 if this is a static binding entry, 0 otherwise
|
||||
char bind_host[256]; // Original hostname of a static binding, empty if the address is a literal or the entry is dynamic
|
||||
UT_hash_handle hh;
|
||||
} client_entry_t;
|
||||
|
||||
// Verbosity level
|
||||
extern int verbose;
|
||||
// Section name (for multiple instances)
|
||||
extern char section_name[256];
|
||||
|
||||
const char *version_string(void);
|
||||
void print_version(void);
|
||||
void register_child_instance(pid_t pid);
|
||||
|
||||
void log_init(const char *path, int8_t timestamps_mode);
|
||||
void log_reopen(void);
|
||||
#ifdef __GNUC__
|
||||
void log_printf(int level, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
|
||||
#else
|
||||
void log_printf(int level, const char *fmt, ...);
|
||||
#endif
|
||||
void log_hexdump(int level, const char *prefix, const uint8_t *data, int length);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user