#define _GNU_SOURCE #include #include #include #include #include #include "params.h" #include "proxy.h" #include "packets.h" #include "error.h" #ifndef _WIN32 #include #include #include #include #include #include #else #include #include "win_service.h" #define close(fd) closesocket(fd) #endif #define VERSION "10" char oob_char[1] = "a"; char ip_option[1] = "\0"; struct packet fake_tls = { sizeof(tls_data), tls_data }, fake_http = { sizeof(http_data), http_data }, oob_data = { sizeof(oob_char), oob_char }; struct params params = { .sfdelay = 3, .wait_send = 1, .cache_ttl = 100800, .ipv6 = 1, .resolve = 1, .udp = 1, .max_open = 512, .bfsize = 16384, .baddr = { .sin6_family = AF_INET6 }, .laddr = { .sin6_family = AF_INET }, .debug = 0 }; const char help_text[] = { " -i, --ip, Listening IP, default 0.0.0.0\n" " -p, --port Listening port, default 1080\n" " -c, --max-conn Connection count limit, default 512\n" " -N, --no-domain Deny domain resolving\n" " -U, --no-udp Deny UDP association\n" " -I --conn-ip Connection binded IP, default ::\n" " -b, --buf-size Buffer size, default 16384\n" " -x, --debug Print logs, 0, 1 or 2\n" " -g, --def-ttl TTL for all outgoing connections\n" // desync options #ifdef TCP_FASTOPEN_CONNECT " -F, --tfo Enable TCP Fast Open\n" #endif " -L, --late-conn Waiting for request before connecting\n" " -A, --auto[=t,r,c,s,a,n] Try desync params after this option\n" " Detect: torst,redirect,cl_err,sid_inv,alert,none\n" " -u, --cache-ttl Lifetime of cached desync params for IP\n" #ifdef TIMEOUT_SUPPORT " -T, --timeout Timeout waiting for response, after which trigger auto\n" #endif " -K, --proto[=t,h] Protocol whitelist: tls,http\n" " -H, --hosts Hosts whitelist\n" " -D, --dst Custom destination IP\n" " -s, --split Split packet at n\n" " +s - add SNI offset\n" " +h - add HTTP Host offset\n" " -d, --disorder Split and send reverse order\n" " -o, --oob Split and send as OOB data\n" #ifdef FAKE_SUPPORT " -f, --fake Split and send fake packet\n" " -t, --ttl TTL of fake packets, default 8\n" " -k, --ip-opt[=f|:str] IP options of fake packets\n" #ifdef __linux__ " -S, --md5sig Add MD5 Signature option for fake packets\n" #endif " -l, --fake-data Set custom fake packet\n" " -n, --tls-sni Change SNI in fake ClientHello\n" #endif " -e, --oob-data Set custom OOB data, filename or :string\n" " -M, --mod-http Modify HTTP: hcsmix,dcsmix,rmspace\n" " -r, --tlsrec Make TLS record at position\n" }; const struct option options[] = { {"no-domain", 0, 0, 'N'}, {"no-ipv6", 0, 0, 'X'}, {"no-udp", 0, 0, 'U'}, {"help", 0, 0, 'h'}, {"version", 0, 0, 'v'}, {"ip", 1, 0, 'i'}, {"port", 1, 0, 'p'}, {"conn-ip", 1, 0, 'I'}, {"buf-size", 1, 0, 'b'}, {"max-conn", 1, 0, 'c'}, {"debug", 1, 0, 'x'}, #ifdef TCP_FASTOPEN_CONNECT {"tfo ", 0, 0, 'F'}, #endif {"late-conn", 0, 0, 'L'}, {"auto", 2, 0, 'A'}, {"cache-ttl", 1, 0, 'u'}, #ifdef TIMEOUT_SUPPORT {"timeout", 1, 0, 'T'}, #endif {"proto", 2, 0, 'K'}, {"hosts", 1, 0, 'H'}, {"dst", 1, 0, 'D'}, {"split", 1, 0, 's'}, {"disorder", 1, 0, 'd'}, {"oob", 1, 0, 'o'}, #ifdef FAKE_SUPPORT {"fake", 1, 0, 'f'}, {"ttl", 1, 0, 't'}, {"ip-opt", 2, 0, 'k'}, #ifdef __linux__ {"md5sig", 0, 0, 'S'}, #endif {"fake-data", 1, 0, 'l'}, {"tls-sni", 1, 0, 'n'}, #endif {"oob-data", 1, 0, 'e'}, {"mod-http", 1, 0, 'M'}, {"tlsrec", 1, 0, 'r'}, {"def-ttl", 1, 0, 'g'}, {"delay", 1, 0, 'w'}, // {"not-wait-send", 0, 0, 'W'}, // #ifdef __linux__ {"protect-path", 1, 0, 'P'}, // #endif {0} }; char *parse_cform(const char *str, ssize_t *size) { ssize_t len = strlen(str); char *d = malloc(len); if (!d) { return 0; } static char esca[] = { 'r','\r','n','\n','t','\t','\\','\\', 'f','\f','b','\b','v','\v','a','\a', 0 }; ssize_t i = 0, p = 0; for (; p < len; ++p && ++i) { if (str[p] != '\\') { d[i] = str[p]; continue; } p++; char *e = esca; for (; *e; e += 2) { if (*e == str[p]) { d[i] = *(e + 1); break; } } if (*e) { continue; } int n = 0; if (sscanf(&str[p], "x%2hhx%n", &d[i], &n) == 1 || sscanf(&str[p], "%3hho%n", &d[i], &n) == 1) { p += (n - 1); continue; } i--; p--; } *size = i; char *m = realloc(d, i); return m ? m : d; } char *ftob(const char *str, ssize_t *sl) { if (*str == ':') { return parse_cform(str + 1, sl); } char *buffer = 0; long size; FILE *file = fopen(str, "rb"); if (!file) { return 0; } do { if (fseek(file, 0, SEEK_END)) { break; } size = ftell(file); if (size <= 0) { break; } if (fseek(file, 0, SEEK_SET)) { break; } #ifndef _WIN32 buffer = mmap(0, size, PROT_READ, MAP_PRIVATE, fileno(file), 0); if (buffer == MAP_FAILED) { buffer = 0; break; } #else if (!(buffer = malloc(size))) { break; } if (fread(buffer, 1, size, file) != size) { free(buffer); buffer = 0; } #endif } while (0); if (buffer) { *sl = size; } fclose(file); return buffer; } struct mphdr *parse_hosts(char *buffer, size_t size) { struct mphdr *hdr = mem_pool(1); if (!hdr) { return 0; } char *end = buffer + size; char *e = buffer, *s = buffer; for (; e <= end; e++) { if (*e != ' ' && *e != '\n' && *e != '\r' && e != end) { continue; } if (s == e) { s++; continue; } if (mem_add(hdr, s, e - s) == 0) { free(hdr); return 0; } s = e + 1; } return hdr; } int get_addr(const char *str, struct sockaddr_ina *addr) { struct addrinfo hints = {0}, *res = 0; hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(str, 0, &hints, &res) || !res) { return -1; } if (res->ai_addr->sa_family == AF_INET6) addr->in6.sin6_addr = ( (struct sockaddr_in6 *)res->ai_addr)->sin6_addr; else addr->in.sin_addr = ( (struct sockaddr_in *)res->ai_addr)->sin_addr; freeaddrinfo(res); return 0; } int get_addr_with_port(const char *str, struct sockaddr_ina *addr) { uint16_t port = 0; const char *s = str, *p = str, *e = 0; char *end = 0; if (*str == '[') { e = strchr(str, ']'); if (!e) return -1; s++; p = e + 1; } p = strchr(p, ':'); if (p) { long val = strtol(p + 1, &end, 0); if (val <= 0 || val > 0xffff || *end) return -1; else port = htons(val); if (!e) e = p; } if (!e) { e = strchr(s, 0); } if ((e - s) < 7) { return -1; } char str_ip[(e - s) + 1]; memcpy(str_ip, s, e - s); str_ip[e - s] = 0; if (get_addr(str_ip, addr) < 0) { return -1; } if (port) { addr->in6.sin6_port = port; } return 0; } int get_default_ttl() { int orig_ttl = -1, fd; socklen_t tsize = sizeof(orig_ttl); if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { uniperror("socket"); return -1; } if (getsockopt(fd, IPPROTO_IP, IP_TTL, (char *)&orig_ttl, &tsize) < 0) { uniperror("getsockopt IP_TTL"); } close(fd); return orig_ttl; } int parse_offset(struct part *part, const char *str) { char *end = 0; long val = strtol(str, &end, 0); if (*end == '+') switch (*(end + 1)) { case 's': part->flag = OFFSET_SNI; break; case 'h': part->flag = OFFSET_HOST; break; default: return -1; } else if (*end) { return -1; } part->pos = val; return 0; } void *add(void **root, int *n, size_t ss) { char *p = realloc(*root, ss * (*n + 1)); if (!p) { uniperror("realloc"); return 0; } *root = p; p = (p + ((*n) * ss)); memset(p, 0, ss); *n = *n + 1; return p; } #ifndef _WIN32 #define FREE_MAP(p, s) munmap(p, s) #else #define FREE_MAP(p, s) free(p) #endif void clear_params(void) { #ifdef _WIN32 WSACleanup(); #endif if (params.mempool) { mem_destroy(params.mempool); params.mempool = 0; } if (params.dp) { for (int i = 0; i < params.dp_count; i++) { struct desync_params s = params.dp[i]; if (s.ip_options != ip_option) { free(s.ip_options); s.ip_options = ip_option; } if (s.parts != 0) { free(s.parts); s.parts = 0; } if (s.tlsrec != 0) { free(s.tlsrec); s.tlsrec = 0; } if (s.fake_data.data) { FREE_MAP(s.fake_data.data, s.fake_data.size); s.fake_data.data = 0; } if (s.file_ptr) { FREE_MAP(s.file_ptr, s.file_size); s.file_ptr = 0; } } free(params.dp); params.dp = 0; } if (oob_data.data != oob_char) { FREE_MAP(oob_data.data, oob_data.size); oob_data.data = oob_char; } } int main(int argc, char **argv) { #ifdef _WIN32 WSADATA wsa; if (WSAStartup(MAKEWORD(2, 2), &wsa)) { uniperror("WSAStartup"); return -1; } if (register_winsvc(argc, argv)) { return 0; } #endif int optc = sizeof(options)/sizeof(*options); for (int i = 0, e = optc; i < e; i++) optc += options[i].has_arg; char opt[optc + 1]; opt[optc] = 0; for (int i = 0, o = 0; o < optc; i++, o++) { opt[o] = options[i].val; for (int c = options[i].has_arg; c; c--) { o++; opt[o] = ':'; } } params.laddr.sin6_port = htons(1080); int rez; int invalid = 0; long val = 0; char *end = 0; struct desync_params *dp = add((void *)¶ms.dp, ¶ms.dp_count, sizeof(struct desync_params)); if (!dp) { clear_params(); return -1; } while (!invalid && (rez = getopt_long_only( argc, argv, opt, options, 0)) != -1) { switch (rez) { case 'N': params.resolve = 0; break; case 'X': params.ipv6 = 0; break; case 'U': params.udp = 0; break; case 'h': printf(help_text); clear_params(); return 0; case 'v': printf("%s\n", VERSION); clear_params(); return 0; case 'i': if (get_addr(optarg, (struct sockaddr_ina *)¶ms.laddr) < 0) invalid = 1; break; case 'p': val = strtol(optarg, &end, 0); if (val <= 0 || val > 0xffff || *end) invalid = 1; else params.laddr.sin6_port = htons(val); break; case 'I': if (get_addr(optarg, (struct sockaddr_ina *)¶ms.baddr) < 0) invalid = 1; break; case 'b': val = strtol(optarg, &end, 0); if (val <= 0 || val > INT_MAX/4 || *end) invalid = 1; else params.bfsize = val; break; case 'c': val = strtol(optarg, &end, 0); if (val <= 0 || val >= (0xffff/2) || *end) invalid = 1; else params.max_open = val; break; case 'x': // params.debug = strtol(optarg, 0, 0); if (params.debug < 0) invalid = 1; break; // desync options case 'L': params.late_conn = 1; break; case 'F': params.tfo = 1; break; case 'A': dp = add((void *)¶ms.dp, ¶ms.dp_count, sizeof(struct desync_params)); if (!dp) { clear_params(); return -1; } if (!optarg) { dp->detect |= DETECT_TORST; break; } end = optarg; while (end && !invalid) { switch (*end) { case 't': dp->detect |= DETECT_TORST; break; case 'r': dp->detect |= DETECT_HTTP_LOCAT; break; case 'c': dp->detect |= DETECT_HTTP_CLERR; break; case 's': dp->detect |= DETECT_TLS_INVSID; break; case 'a': dp->detect |= DETECT_TLS_ALERT; break; case 'n': break; default: invalid = 1; continue; } end = strchr(end, ','); if (end) end++; } break; case 'u': val = strtol(optarg, &end, 0); if (val <= 0 || *end) invalid = 1; else params.cache_ttl = val; break; case 'T':; #ifdef __linux__ float f = strtof(optarg, &end); val = (long)(f * 1000); #else val = strtol(optarg, &end, 0); #endif if (val <= 0 || val > UINT_MAX || *end) invalid = 1; else params.timeout = val; break; case 'K': if (!optarg) { dp->proto |= 0xffffffff; break; } end = optarg; while (end && !invalid) { switch (*end) { case 't': dp->proto |= IS_HTTPS; break; case 'h': dp->proto |= IS_HTTP; break; default: invalid = 1; continue; } end = strchr(end, ','); if (end) end++; } break; case 'H':; if (dp->file_ptr) { continue; } dp->file_ptr = ftob(optarg, &dp->file_size); if (!dp->file_ptr) { uniperror("read/parse"); invalid = 1; continue; } dp->hosts = parse_hosts(dp->file_ptr, dp->file_size); if (!dp->hosts) { perror("parse_hosts"); clear_params(); return -1; } break; case 'D': if (get_addr_with_port(optarg, (struct sockaddr_ina *)&dp->addr) < 0) invalid = 1; else dp->to_ip = 1; break; case 's': case 'd': case 'o': case 'f': ; struct part *part = add((void *)&dp->parts, &dp->parts_n, sizeof(struct part)); if (!part) { clear_params(); return -1; } if (parse_offset(part, optarg)) { invalid = 1; break; } switch (rez) { case 's': part->m = DESYNC_SPLIT; break; case 'd': part->m = DESYNC_DISORDER; break; case 'o': part->m = DESYNC_OOB; break; case 'f': part->m = DESYNC_FAKE; } break; case 't': val = strtol(optarg, &end, 0); if (val <= 0 || val > 255 || *end) invalid = 1; else dp->ttl = val; break; case 'k': if (dp->ip_options != ip_option) { continue; } if (optarg) dp->ip_options = ftob(optarg, &dp->ip_options_len); else { dp->ip_options = ip_option; dp->ip_options_len = sizeof(ip_option); } if (!dp->ip_options) { uniperror("read/parse"); invalid = 1; } break; case 'S': dp->md5sig = 1; break; case 'n': if (change_tls_sni(optarg, fake_tls.data, fake_tls.size)) { fprintf(stderr, "error chsni\n"); clear_params(); return -1; } printf("sni: %s\n", optarg); break; case 'l': if (dp->fake_data.data) { continue; } dp->fake_data.data = ftob(optarg, &dp->fake_data.size); if (!dp->fake_data.data) { uniperror("read/parse"); invalid = 1; } break; case 'e': if (oob_data.data != oob_char) { continue; } oob_data.data = ftob(optarg, &oob_data.size); if (!oob_data.data) { uniperror("read/parse"); invalid = 1; } break; case 'M': end = optarg; while (end && !invalid) { switch (*end) { case 'r': dp->mod_http |= MH_SPACE; break; case 'h': dp->mod_http |= MH_HMIX; break; case 'd': dp->mod_http |= MH_DMIX; break; default: invalid = 1; continue; } end = strchr(end, ','); if (end) end++; } break; case 'r': part = add((void *)&dp->tlsrec, &dp->tlsrec_n, sizeof(struct part)); if (!part) { clear_params(); return -1; } if (parse_offset(part, optarg) || part->pos > 0xffff) { invalid = 1; break; } break; case 'g': val = strtol(optarg, &end, 0); if (val <= 0 || val > 255 || *end) invalid = 1; else { params.def_ttl = val; params.custom_ttl = 1; } break; case 'w': // params.sfdelay = strtol(optarg, &end, 0); if (params.sfdelay < 0 || optarg == end || params.sfdelay >= 1000 || *end) invalid = 1; break; case 'W': params.wait_send = 0; break; #ifdef __linux__ case 'P': params.protect_path = optarg; break; #endif case 0: break; case '?': clear_params(); return -1; default: printf("?: %c\n", rez); clear_params(); return -1; } } if (invalid) { fprintf(stderr, "invalid value: -%c %s\n", rez, optarg); clear_params(); return -1; } if (dp->hosts || dp->proto) { dp = add((void *)¶ms.dp, ¶ms.dp_count, sizeof(struct desync_params)); if (!dp) { clear_params(); return -1; } } if (params.baddr.sin6_family != AF_INET6) { params.ipv6 = 0; } if (!params.def_ttl) { if ((params.def_ttl = get_default_ttl()) < 1) { clear_params(); return -1; } } params.mempool = mem_pool(0); if (!params.mempool) { uniperror("mem_pool"); clear_params(); return -1; } int status = run((struct sockaddr_ina *)¶ms.laddr); clear_params(); return status; }