#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include "wg-obfuscator.h"
#include "config.h"
#include "obfuscation.h"
#include "masking.h"
#include "threading.h"
#include "socks5.h"
#include "socks5_mask.h"
#include "resolve.h"
int verbose = LL_DEFAULT;
char section_name[256] = DEFAULT_INSTANCE_NAME;
static threading_context_t threading_ctx = {0};
static socks5_context_t socks5_ctx = {0};
static volatile sig_atomic_t stop_flag = 0;
static void on_signal(int signal) {
(void)signal;
stop_flag = 1;
}
void print_version(void) {
#ifdef COMMIT
#ifndef ARCH
fprintf(stderr, "Starting PhobosWG (commit " COMMIT " @ " WG_OBFUSCATOR_GIT_REPO ")\n");
#else
fprintf(stderr, "Starting PhobosWG (commit " COMMIT " @ " WG_OBFUSCATOR_GIT_REPO ") (" ARCH ")\n");
#endif
#else
#ifndef ARCH
fprintf(stderr, "Starting PhobosWG v" WG_OBFUSCATOR_VERSION "\n");
#else
fprintf(stderr, "Starting PhobosWG v" WG_OBFUSCATOR_VERSION " (" ARCH ")\n");
#endif
#endif
}
static void parse_static_bindings(obfuscator_config_t *config) {
if (!config->static_bindings[0]) return;
char *binding = strtok(config->static_bindings, ",");
while (binding) {
binding = trim(binding);
char *colon1 = strchr(binding, ':');
char *colon2 = colon1 ? strchr(colon1 + 1, ':') : NULL;
if (!colon1 || !colon2) {
log(LL_ERROR, "Invalid static binding format: %s", binding);
exit(EXIT_FAILURE);
}
*colon1 = 0;
*colon2 = 0;
struct in_addr client_ip;
if (resolve_ipv4_wait(binding, &client_ip, config->stop) != 0) {
log(LL_ERROR, "Can't resolve hostname '%s' for static binding", binding);
exit(EXIT_FAILURE);
}
int remote_port = atoi(colon1 + 1);
int local_port = atoi(colon2 + 1);
if (remote_port <= 0 || remote_port > 65535 || local_port <= 0 || local_port > 65535) {
log(LL_ERROR, "Invalid port in static binding '%s:%s:%s'", binding, colon1 + 1, colon2 + 1);
exit(EXIT_FAILURE);
}
if (config->static_spec_count >= MAX_STATIC_BINDINGS) {
log(LL_ERROR, "Too many static bindings (max %d)", MAX_STATIC_BINDINGS);
exit(EXIT_FAILURE);
}
static_binding_t *s = &config->static_specs[config->static_spec_count++];
s->client_ip = client_ip;
s->remote_port = (uint16_t)remote_port;
s->local_port = (uint16_t)local_port;
log(LL_INFO, "Static binding: %s:%d <-> local %d <-> target", binding, remote_port, local_port);
binding = strtok(NULL, ",");
}
}
int main(int argc, char *argv[]) {
obfuscator_config_t config;
struct sockaddr_in forward_addr;
char target_host[256] = {0};
int target_port = -1;
int key_length = 0;
in_addr_t listen_addr = INADDR_ANY;
print_version();
if (parse_config(argc, argv, &config) != 0) {
exit(EXIT_FAILURE);
}
config.stop = &stop_flag;
signal(SIGINT, on_signal);
signal(SIGTERM, on_signal);
signal(SIGPIPE, SIG_IGN);
int target_required = !(config.mode == MODE_SOCKS5 && config.socks5_role == S5_ROLE_SERVER);
if (!config.listen_port_set) {
log(LL_ERROR, "'source-lport' is not set");
exit(EXIT_FAILURE);
}
if (target_required && !config.forward_host_port_set) {
log(LL_ERROR, "'target' is not set");
exit(EXIT_FAILURE);
}
if (!config.xor_key_set) {
log(LL_ERROR, "'key' is not set");
exit(EXIT_FAILURE);
}
if (config.mode == MODE_WIREGUARD && config.socks5_mask == S5_MASK_TLS) {
log(LL_ERROR, "TLS masking is only available in socks5 mode");
exit(EXIT_FAILURE);
}
memset(&forward_addr, 0, sizeof(forward_addr));
forward_addr.sin_family = AF_INET;
if (config.forward_host_port_set) {
char *port_delimiter = strchr(config.forward_host_port, ':');
if (port_delimiter == NULL) {
log(LL_ERROR, "Invalid target host:port format: %s", config.forward_host_port);
exit(EXIT_FAILURE);
}
*port_delimiter = 0;
strncpy(target_host, config.forward_host_port, sizeof(target_host) - 1);
target_host[sizeof(target_host) - 1] = 0;
target_port = atoi(port_delimiter + 1);
if (target_port <= 0 || target_port > 65535) {
log(LL_ERROR, "Invalid target port: %s", port_delimiter + 1);
exit(EXIT_FAILURE);
}
if (resolve_ipv4_wait(target_host, &forward_addr.sin_addr, config.stop) != 0) {
log(LL_ERROR, "Can't resolve target hostname '%s', exiting", target_host);
exit(EXIT_FAILURE);
}
forward_addr.sin_port = htons(target_port);
}
key_length = strlen(config.xor_key);
if (key_length == 0) {
log(LL_ERROR, "Key is not set");
exit(EXIT_FAILURE);
}
if (config.client_interface_set) {
struct in_addr a;
if (resolve_ipv4_wait(config.client_interface, &a, config.stop) != 0) {
log(LL_ERROR, "Invalid source interface '%s'", config.client_interface);
exit(EXIT_FAILURE);
}
listen_addr = a.s_addr;
}
log(LL_INFO, "Listening on %s:%d", inet_ntoa(*(struct in_addr *)&listen_addr), config.listen_port);
if (config.forward_host_port_set) {
log(LL_INFO, "Target: %s:%d", target_host, target_port);
}
if (config.mode == MODE_SOCKS5) {
static const char *mask_names[] = { "none", "STUN", "MEDIA", "TLS" };
static const char *role_names[] = { "relay", "client", "server" };
log(LL_INFO, "SOCKS5 role: %s, masking: %s",
role_names[config.socks5_role], mask_names[config.socks5_mask]);
if (socks5_init(&socks5_ctx, &config) != 0) {
log(LL_ERROR, "Failed to initialize SOCKS5 engine");
exit(EXIT_FAILURE);
}
if (socks5_start(&socks5_ctx, &config, config.xor_key, key_length, &forward_addr,
listen_addr, (uint16_t)config.listen_port) != 0) {
log(LL_ERROR, "Failed to start SOCKS5 engine");
socks5_shutdown(&socks5_ctx);
exit(EXIT_FAILURE);
}
log(LL_INFO, "SOCKS5 obfuscator successfully started");
while (!stop_flag) {
struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 };
nanosleep(&ts, NULL);
}
log(LL_INFO, "Stopping...");
socks5_shutdown(&socks5_ctx);
socks5_join(&socks5_ctx);
log(LL_INFO, "Stopped.");
return 0;
}
if (config.masking_handler_set) {
log(LL_INFO, "Using masking type: %s", config.masking_handler ? config.masking_handler->name : "none");
}
parse_static_bindings(&config);
if (threading_init(&threading_ctx, &config) != 0) {
log(LL_ERROR, "Failed to initialize threading");
exit(EXIT_FAILURE);
}
if (threading_start(&threading_ctx, &config, config.xor_key, key_length, &forward_addr,
listen_addr, (uint16_t)config.listen_port) != 0) {
log(LL_ERROR, "Failed to start worker threads");
threading_shutdown(&threading_ctx);
exit(EXIT_FAILURE);
}
log(LL_INFO, "WireGuard obfuscator successfully started");
while (!stop_flag) {
struct timespec ts = { .tv_sec = 1, .tv_nsec = 0 };
nanosleep(&ts, NULL);
}
log(LL_INFO, "Stopping...");
threading_shutdown(&threading_ctx);
threading_join(&threading_ctx);
log(LL_INFO, "Stopped.");
return 0;
}