diff --git a/Makefile b/Makefile index 5783d2a..c0f9f67 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ TARGET = ciadpi CC ?= gcc CFLAGS += -std=c99 -O2 -D_XOPEN_SOURCE=500 -SOURCES = packets.c main.c conev.c proxy.c desync.c mpool.c +SOURCES = packets.c main.c conev.c proxy.c desync.c mpool.c extend.c all: $(CC) $(CFLAGS) $(SOURCES) -I . -o $(TARGET) diff --git a/conev.h b/conev.h index 176632a..9388303 100644 --- a/conev.h +++ b/conev.h @@ -1,3 +1,4 @@ +#pragma once #include #ifndef __linux__ @@ -35,6 +36,7 @@ enum eid { EV_IGNORE, EV_TUNNEL, EV_PRE_TUNNEL, + EV_UDP_TUNNEL, EV_DESYNC }; @@ -42,7 +44,7 @@ enum eid { #define FLAG_S5 2 #define FLAG_CONN 4 -#ifndef CONEV_H +#ifdef EID_STR char *eid_name[] = { "EV_ACCEPT", "EV_REQUEST", @@ -50,6 +52,7 @@ char *eid_name[] = { "EV_IGNORE", "EV_TUNNEL", "EV_PRE_TUNNEL", + "EV_UDP_TUNNEL", "EV_DESYNC" }; #endif diff --git a/desync.c b/desync.c index 51b1205..ab1eeac 100644 --- a/desync.c +++ b/desync.c @@ -131,7 +131,13 @@ ssize_t send_fake(int sfd, char *buffer, return -1; } } - struct packet pkt = cnt != IS_HTTP ? fake_tls : fake_http; + struct packet pkt; + if (opt->fake_data.data) { + pkt = opt->fake_data; + } + else { + pkt = cnt != IS_HTTP ? fake_tls : fake_http; + } size_t psz = pkt.size; int ffd = memfd_create("name", O_RDWR); @@ -218,7 +224,13 @@ ssize_t send_fake(int sfd, char *buffer, ssize_t send_fake(int sfd, char *buffer, int cnt, long pos, int fa, struct desync_params *opt) { - struct packet pkt = cnt != IS_HTTP ? fake_tls : fake_http; + struct packet pkt; + if (opt->fake_data.data) { + pkt = opt->fake_data; + } + else { + pkt = cnt != IS_HTTP ? fake_tls : fake_http; + } size_t psz = pkt.size; char path[MAX_PATH + 1]; @@ -439,10 +451,7 @@ ssize_t desync(int sfd, char *buffer, size_t bfsize, // desync long lp = offset; - if (!type && params.de_known) { - // cancel - } - else for (int i = 0; i < dp.parts_n; i++) { + for (int i = 0; i < dp.parts_n; i++) { struct part part = dp.parts[i]; // change pos diff --git a/extend.c b/extend.c new file mode 100644 index 0000000..82828ca --- /dev/null +++ b/extend.c @@ -0,0 +1,413 @@ +#ifdef _WIN32 + #include + + #ifndef TCP_MAXRT + #define TCP_MAXRT 5 + #endif +#else + #include + #include +#endif + +#include + +#include +#include +#include + +#include +#include + + +int set_timeout(int fd, unsigned int s) +{ + #ifdef __linux__ + if (setsockopt(fd, IPPROTO_TCP, + TCP_USER_TIMEOUT, (char *)&s, sizeof(s))) { + uniperror("setsockopt TCP_USER_TIMEOUT"); + return -1; + } + #else + #ifdef _WIN32 + if (setsockopt(fd, IPPROTO_TCP, + TCP_MAXRT, (char *)&s, sizeof(s))) { + uniperror("setsockopt TCP_MAXRT"); + return -1; + } + #endif + #endif + return 0; +} + + +int mode_add_get(struct sockaddr_ina *dst, int m) +{ + // m < 0: get, m > 0: set, m == 0: delete + time_t t; + struct elem *val; + char *str = (char *)&dst->in; + int len = sizeof(dst->sa.sa_family); + + if (dst->sa.sa_family == AF_INET) { + len = sizeof(dst->in); + } + else { + len = sizeof(dst->in6) - sizeof(dst->in6.sin6_scope_id); + } + len -= sizeof(dst->sa.sa_family); + + if (m == 0) { + mem_delete(params.mempool, str, len); + return 0; + } + else if (m > 0) { + time(&t); + val = mem_add(params.mempool, str, len); + if (!val) { + uniperror("mem_add"); + return -1; + } + val->m = m; + val->time = t; + return 0; + } + val = mem_get(params.mempool, str, len); + if (!val) { + return -1; + } + time(&t); + if (t > val->time + params.cache_ttl) { + LOG(LOG_S, "time=%ld, now=%ld, ignore\n", val->time, t); + return 0; + } + return val->m; +} + + +int ext_connect(struct poolhd *pool, struct eval *val, + struct sockaddr_ina *dst, int next, int m) +{ + struct desync_params *dp = ¶ms.dp[m]; + if (dp->to_ip == 2) { + struct sockaddr_ina addr = { .in6 = dp->addr }; + if (!addr.in.sin_port) { + addr.in.sin_port = dst->in.sin_port; + } + return create_conn(pool, val, &addr, next); + } + return create_conn(pool, val, dst, next); +} + + +int connect_hook(struct poolhd *pool, struct eval *val, + struct sockaddr_ina *dst, int next) +{ + int m = mode_add_get(dst, -1); + val->cache = (m == 0); + val->attempt = m < 0 ? 0 : m; + + if (params.late_conn) { + val->type = EV_DESYNC; + if (resp_error(val->fd, 0, val->flag) < 0) { + perror("send"); + return -1; + } + val->in6 = dst->in6; + return 0; + } + return ext_connect(pool, val, dst, next, m); +} + + +int reconnect(struct poolhd *pool, struct eval *val, int m) +{ + struct eval *client = val->pair; + + if (ext_connect(pool, client, + (struct sockaddr_ina *)&val->in6, EV_DESYNC, m)) { + return -1; + } + val->pair = 0; + del_event(pool, val); + + client->type = EV_IGNORE; + client->attempt = m; + client->cache = 1; + return 0; +} + + +bool check_host(struct mphdr *hosts, struct eval *val) +{ + char *host; + int len; + if (!(len = parse_tls(val->buff.data, val->buff.size, &host))) { + len = parse_http(val->buff.data, val->buff.size, &host, 0); + } + return mem_get(hosts, host, len) != 0; +} + + +bool check_proto_tcp(int proto, struct eval *val) +{ + if ((proto & IS_HTTP) && + is_http(val->buff.data, val->buff.size)) { + return 1; + } + if ((proto & IS_HTTPS) && + is_tls_chello(val->buff.data, val->buff.size)) { + return 1; + } + return 0; +} + + +bool check_proto_udp(int proto, char *buffer, size_t n) +{ + if ((proto & IS_QUIC) && + is_quic_inital(buffer, n)) { + return 1; + } + if ((proto & IS_DNS) && + is_dns_req(buffer, n)) { + return 1; + } + return 0; +} + + +int on_torst(struct poolhd *pool, struct eval *val) +{ + int m = val->pair->attempt + 1; + + for (; m < params.dp_count; m++) { + struct desync_params *dp = ¶ms.dp[m]; + if (!(dp->detect & DETECT_TORST)) { + continue; + } + if ((!dp->hosts || check_host(dp->hosts, val->pair)) && + (!dp->proto || check_proto_tcp(dp->proto, val))) { + break; + } + } + if (m >= params.dp_count) { + mode_add_get( + (struct sockaddr_ina *)&val->in6, 0); + return -1; + } + return reconnect(pool, val, m); +} + + +int on_response(struct poolhd *pool, struct eval *val, + char *resp, ssize_t sn) +{ + int m = val->pair->attempt + 1; + + char *req = val->pair->buff.data; + ssize_t qn = val->pair->buff.size; + + for (; m < params.dp_count; m++) { + struct desync_params *dp = ¶ms.dp[m]; + + switch (0) { + default: + if ((dp->detect & DETECT_HTTP_LOCAT) + && is_http_redirect(req, qn, resp, sn)) { + break; + } + else if ((dp->detect & DETECT_TLS_INVSID) + && neq_tls_sid(req, qn, resp, sn) + && !neq_tls_sid( + fake_tls.data, fake_tls.size, resp, sn)) { + break; + } + else if ((dp->detect & DETECT_TLS_ALERT) + && is_tls_alert(resp, sn)) { + break; + } + else if (dp->detect & DETECT_HTTP_CLERR) { + int code = get_http_code(resp, sn); + if (code > 400 && code < 451 && code != 429) { + break; + } + } + continue; + } + if ((!dp->hosts || check_host(dp->hosts, val->pair)) && + (!dp->proto || check_proto_tcp(dp->proto, val))) { + break; + } + } + if (m < params.dp_count) { + return reconnect(pool, val, m); + } + return -1; +} + + +int on_tunnel_check(struct poolhd *pool, struct eval *val, + char *buffer, size_t bfsize, int out) +{ + if (out) { + return on_tunnel(pool, val, buffer, bfsize, out); + } + ssize_t n = recv(val->fd, buffer, bfsize, 0); + if (n < 1) { + uniperror("recv"); + switch (get_e()) { + case ECONNRESET: + case ETIMEDOUT: + break; + default: return -1; + } + return on_torst(pool, val); + } + // + if (on_response(pool, val, buffer, n) == 0) { + return 0; + } + struct eval *pair = val->pair; + + ssize_t sn = send(pair->fd, buffer, n, 0); + if (n != sn) { + uniperror("send"); + return -1; + } + val->type = EV_TUNNEL; + pair->type = EV_TUNNEL; + + free(pair->buff.data); + pair->buff.data = 0; + pair->buff.size = 0; + + if (params.timeout && + set_timeout(val->fd, 0)) { + return -1; + } + int m = pair->attempt; + + if (!pair->cache) { + return 0; + } + if (m == 0) { + LOG(LOG_S, "delete ip: m=%d\n", m); + } else { + LOG(LOG_S, "save ip: m=%d\n", m); + } + return mode_add_get( + (struct sockaddr_ina *)&val->in6, m); +} + + +int on_desync(struct poolhd *pool, struct eval *val, + char *buffer, size_t bfsize) +{ + if (val->flag == FLAG_CONN) { + if (mod_etype(pool, val, POLLOUT, 0)) { + uniperror("mod_etype"); + return -1; + } + val = val->pair; + } + ssize_t n; + int m = val->attempt; + LOG((m ? LOG_S : LOG_L), "desync params index: %d\n", m); + + if (!val->buff.data) { + n = recv(val->fd, buffer, bfsize, 0); + if (n <= 0) { + if (n) uniperror("recv data"); + return -1; + } + val->buff.size = n; + val->recv_count += n; + + if (!(val->buff.data = malloc(n))) { + uniperror("malloc"); + return -1; + } + memcpy(val->buff.data, buffer, n); + + if (!m) for (; m < params.dp_count; m++) { + struct desync_params *dp = ¶ms.dp[m]; + if (!dp->detect && + (!dp->hosts || check_host(dp->hosts, val)) && + (!dp->proto || check_proto_tcp(dp->proto, val))) { + break; + } + } + if (m >= params.dp_count) { + return -1; + } + val->attempt = m; + + if (params.late_conn) { + return ext_connect(pool, val, + (struct sockaddr_ina *)&val->in6, EV_DESYNC, m); + } + } + else { + n = val->buff.size; + memcpy(buffer, val->buff.data, n); + } + if (params.timeout && + set_timeout(val->pair->fd, params.timeout)) { + return -1; + } + ssize_t sn = desync(val->pair->fd, buffer, bfsize, + n, val->buff.offset, (struct sockaddr *)&val->pair->in6, m); + if (sn < 0) { + return -1; + } + if (sn < n) { + val->buff.offset = sn; + if (mod_etype(pool, val->pair, POLLOUT, 1)) { + uniperror("mod_etype"); + return -1; + } + val->pair->type = EV_DESYNC; + return 0; + } + val->type = EV_TUNNEL; + val->pair->type = EV_PRE_TUNNEL; + return 0; +} + + +ssize_t udp_hook(struct eval *val, + char *buffer, size_t n, struct sockaddr_ina *dst) +{ + int m = val->attempt; + struct desync_params *dp = ¶ms.dp[m]; + + if (val->flag != FLAG_CONN) { + if (!m) for (; m < params.dp_count; m++) { + dp = ¶ms.dp[m]; + if ((!dp->proto || check_proto_udp(dp->proto, buffer, n))) { + break; + } + } + if (m >= params.dp_count) { + return -1; + } + val->attempt = m; + val->flag = FLAG_CONN; + } + + struct sockaddr_ina addr; + if (dp->to_ip) { + addr.in6 = dp->addr; + if (dst->sa.sa_family == AF_INET6) { + map_fix(&addr, 6); + } + else { + map_fix(&addr, 0); + } + if (!addr.in.sin_port) { + addr.in.sin_port = dst->in.sin_port; + } + dst = &addr; + } + return sendto(val->fd, buffer, n, 0, &dst->sa, sizeof(*dst)); +} diff --git a/extend.h b/extend.h new file mode 100644 index 0000000..df7c866 --- /dev/null +++ b/extend.h @@ -0,0 +1,11 @@ +int connect_hook(struct poolhd *pool, struct eval *val, + struct sockaddr_ina *dst, int next); + +int on_tunnel_check(struct poolhd *pool, struct eval *val, + char *buffer, size_t bfsize, int out); + +int on_desync(struct poolhd *pool, struct eval *val, + char *buffer, size_t bfsize); + +ssize_t udp_hook(struct eval *val, + char *buffer, size_t n, struct sockaddr_ina *dst); \ No newline at end of file diff --git a/main.c b/main.c index 39e4bf2..22f91fe 100644 --- a/main.c +++ b/main.c @@ -49,6 +49,7 @@ struct params params = { .cache_ttl = 100800, .ipv6 = 1, .resolve = 1, + .udp = 1, .max_open = 512, .bfsize = 16384, .baddr = { @@ -63,24 +64,25 @@ const char help_text[] = { " -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 - " -K, --desync-known Desync only HTTP and TLS with SNI\n" #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,nop\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,d,q] Protocol whitelist: tls,http,dns,quic\n" " -H, --hosts Hosts whitelist\n" - " -D, --dst Custom destination IP\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" @@ -93,8 +95,7 @@ const char help_text[] = { #ifdef __linux__ " -S, --md5sig Add MD5 Signature option for fake packets\n" #endif - " -l, --fake-tls \n" - " -j, --fake-http Set custom fake packet\n" + " -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" @@ -106,6 +107,7 @@ const char help_text[] = { 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'}, @@ -115,7 +117,6 @@ const struct option options[] = { {"max-conn", 1, 0, 'c'}, {"debug", 1, 0, 'x'}, - {"desync-known ", 0, 0, 'K'}, #ifdef TCP_FASTOPEN_CONNECT {"tfo ", 0, 0, 'F'}, #endif @@ -125,6 +126,7 @@ const struct option options[] = { #ifdef TIMEOUT_SUPPORT {"timeout", 1, 0, 'T'}, #endif + {"proto", 2, 0, 'K'}, {"hosts", 1, 0, 'H'}, {"dst", 1, 0, 'D'}, {"split", 1, 0, 's'}, @@ -137,8 +139,7 @@ const struct option options[] = { #ifdef __linux__ {"md5sig", 0, 0, 'S'}, #endif - {"fake-tls", 1, 0, 'l'}, - {"fake-http", 1, 0, 'j'}, + {"fake-data", 1, 0, 'l'}, {"tls-sni", 1, 0, 'n'}, #endif {"oob-data", 1, 0, 'e'}, @@ -250,7 +251,7 @@ struct mphdr *parse_hosts(char *buffer, size_t size) char *e = buffer, *s = buffer; for (; e <= end; e++) { - if (*e != ' ' && *e != '\n' && e != end) { + if (*e != ' ' && *e != '\n' && *e != '\r' && e != end) { continue; } if (s == e) { @@ -288,6 +289,46 @@ int get_addr(const char *str, struct sockaddr_ina *addr) } +int get_addr_with_port(const char *str, struct sockaddr_ina *addr) +{ + uint16_t port = 0; + char *s = (char *)str, *e = 0; + char *end = 0, *p = s; + + 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; @@ -368,18 +409,14 @@ void clear_params(void) free(s.parts); s.parts = 0; } + if (s.fake_data.data) { + FREE(s.fake_data.data, s.fake_data.size); + s.fake_data.data = 0; + } } free(params.dp); params.dp = 0; } - if (fake_tls.data != tls_data) { - FREE(fake_tls.data, fake_tls.size); - fake_tls.data = tls_data; - } - if (fake_http.data != http_data) { - FREE(fake_http.data, fake_http.size); - fake_http.data = http_data; - } if (oob_data.data != oob_char) { FREE(oob_data.data, oob_data.size); oob_data.data = oob_char; @@ -444,6 +481,10 @@ int main(int argc, char **argv) case 'X': params.ipv6 = 0; break; + case 'U': + params.udp = 0; + break; + case 'h': printf(help_text); clear_params(); @@ -501,10 +542,6 @@ int main(int argc, char **argv) params.late_conn = 1; break; - case 'K': - params.de_known = 1; - break; - case 'F': params.tfo = 1; break; @@ -570,6 +607,35 @@ int main(int argc, char **argv) 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; + case 'd': + dp->proto |= IS_DNS; + break; + case 'q': + dp->proto |= IS_QUIC; + break; + default: + invalid = 1; + continue; + } + end = strchr(end, ','); + if (end) end++; + } + break; + case 'H':; char *data = ftob(optarg, &val); if (!data) { @@ -585,7 +651,7 @@ int main(int argc, char **argv) break; case 'D': - if (get_addr(optarg, (struct sockaddr_ina *)&dp->addr) < 0) + if (get_addr_with_port(optarg, (struct sockaddr_ina *)&dp->addr) < 0) invalid = 1; else dp->to_ip = 2; @@ -652,16 +718,8 @@ int main(int argc, char **argv) break; case 'l': - fake_tls.data = ftob(optarg, &fake_tls.size); - if (!fake_tls.data) { - uniperror("read/parse"); - invalid = 1; - } - break; - - case 'j': - fake_http.data = ftob(optarg, &fake_http.size); - if (!fake_http.data) { + dp->fake_data.data = ftob(optarg, &dp->fake_data.size); + if (!dp->fake_data.data) { uniperror("read/parse"); invalid = 1; } @@ -750,7 +808,7 @@ int main(int argc, char **argv) clear_params(); return -1; } - if (dp->hosts) { + if (dp->hosts || dp->proto) { dp = add((void *)¶ms.dp, ¶ms.dp_count, sizeof(struct desync_params)); if (!dp) { diff --git a/mpool.c b/mpool.c index 4b89f19..076ecd6 100644 --- a/mpool.c +++ b/mpool.c @@ -97,4 +97,4 @@ void mem_destroy(struct mphdr *hdr) free(e); } free(hdr); -} \ No newline at end of file +} diff --git a/packets.c b/packets.c index 0707aae..0a6a428 100644 --- a/packets.c +++ b/packets.c @@ -141,9 +141,17 @@ int change_tls_sni(const char *host, char *buffer, size_t bsize) } +bool is_tls_chello(char *buffer, size_t bsize) +{ + return (bsize > 5 && + ANTOHS(buffer, 0) == 0x1603 && + buffer[5] == 0x01); +} + + int parse_tls(char *buffer, size_t bsize, char **hs) { - if (ANTOHS(buffer, 0) != 0x1603) { + if (!is_tls_chello(buffer, bsize)) { return 0; } size_t sni_offs = find_tls_ext_offset(0x00, buffer, bsize); @@ -312,7 +320,7 @@ bool neq_tls_sid(char *req, size_t qn, char *resp, size_t sn) if (qn < 75 || sn < 75) { return 0; } - if (ANTOHS(req, 0) != 0x1603 + if (!is_tls_chello(req, qn) || ANTOHS(resp, 0) != 0x1603) { return 0; } @@ -330,6 +338,21 @@ bool is_tls_alert(char *resp, size_t sn) { } +bool is_dns_req(char *buffer, size_t n) +{ + if (n < 12) { + return 0; + } + return !memcmp(buffer + 2, "\1\0\0\1\0\0\0\0\0\0", 10); +} + + +bool is_quic_inital(char *buffer, size_t bsize) +{ + return (bsize > 64 && (buffer[0] & 0xc0) == 0xc0); +} + + int mod_http(char *buffer, size_t bsize, int m) { char *host = 0, *par; diff --git a/packets.h b/packets.h index 95d9705..2a5dcd9 100644 --- a/packets.h +++ b/packets.h @@ -6,6 +6,8 @@ #define IS_UNKNOWN 0 #define IS_HTTP 1 #define IS_HTTPS 2 +#define IS_QUIC 4 +#define IS_DNS 8 #define MH_HMIX 1 #define MH_SPACE 2 @@ -16,8 +18,12 @@ extern char http_data[43]; int change_tls_sni(const char *host, char *buffer, size_t bsize); +bool is_tls_chello(char *buffer, size_t bsize); + int parse_tls(char *buffer, size_t bsize, char **hs); +bool is_http(char *buffer, size_t bsize); + int parse_http(char *buffer, size_t bsize, char **hs, uint16_t *port); int mod_http(char *buffer, size_t bsize, int m); @@ -31,3 +37,7 @@ bool neq_tls_sid(char *req, size_t qn, char *resp, size_t sn); bool is_tls_alert(char *resp, size_t sn); int part_tls(char *buffer, size_t bsize, ssize_t n, long pos); + +bool is_dns_req(char *buffer, size_t n); + +bool is_quic_inital(char *buffer, size_t bsize); diff --git a/params.h b/params.h index 1c72541..7083116 100644 --- a/params.h +++ b/params.h @@ -45,16 +45,26 @@ struct part { long pos; }; +struct packet { + ssize_t size; + char *data; +}; + struct desync_params { int ttl; char *ip_options; ssize_t ip_options_len; char md5sig; + struct packet fake_data; + int parts_n; struct part *parts; + int mod_http; int tlsrec_n; struct part *tlsrec; + + int proto; int detect; struct mphdr *hosts; @@ -63,7 +73,6 @@ struct desync_params { }; struct params { - char de_known; int dp_count; struct desync_params *dp; long sfdelay; @@ -77,6 +86,7 @@ struct params { long cache_ttl; char ipv6; char resolve; + char udp; int max_open; int debug; size_t bfsize; @@ -86,10 +96,6 @@ struct params { extern struct params params; -struct packet { - ssize_t size; - char *data; -}; extern struct packet fake_tls; extern struct packet fake_http; extern struct packet oob_data; diff --git a/proxy.c b/proxy.c index d4936a7..f065532 100644 --- a/proxy.c +++ b/proxy.c @@ -1,17 +1,16 @@ #define _GNU_SOURCE +#define EID_STR #include #include #include #include #include -#include #include #include #include -#include -#include +#include #include #ifdef _WIN32 @@ -19,9 +18,6 @@ #include #define close(fd) closesocket(fd) - #ifndef TCP_MAXRT - #define TCP_MAXRT 5 - #endif #else #include #include @@ -65,6 +61,25 @@ void map_fix(struct sockaddr_ina *addr, char f6) } +static inline char addr_equ( + struct sockaddr_ina *a, struct sockaddr_ina *b) +{ + if (a->in.sin_port != b->in.sin_port) { + return 0; + } + if (a->sa.sa_family == AF_INET) { + return + *((uint32_t *)(&a->in.sin_addr)) == + *((uint32_t *)(&b->in.sin_addr)); + } + return + *((uint64_t *)(&a->in6.sin6_addr)) == + *((uint64_t *)(&b->in6.sin6_addr)) && + *((uint64_t *)(&a->in6.sin6_addr) + 1) == + *((uint64_t *)(&b->in6.sin6_addr) + 1); +} + + static inline int nb_socket(int domain, int type) { #ifdef __linux__ @@ -96,33 +111,12 @@ static inline int nb_socket(int domain, int type) } -int set_timeout(int fd, unsigned int s) -{ - #ifdef __linux__ - if (setsockopt(fd, IPPROTO_TCP, - TCP_USER_TIMEOUT, (char *)&s, sizeof(s))) { - uniperror("setsockopt TCP_USER_TIMEOUT"); - return -1; - } - #else - #ifdef _WIN32 - if (setsockopt(fd, IPPROTO_TCP, - TCP_MAXRT, (char *)&s, sizeof(s))) { - uniperror("setsockopt TCP_MAXRT"); - return -1; - } - #endif - #endif - return 0; -} - - int resolve(char *host, int len, - struct sockaddr_ina *addr) + struct sockaddr_ina *addr, int type) { struct addrinfo hints = {0}, *res = 0; - hints.ai_socktype = SOCK_STREAM; + hints.ai_socktype = type; hints.ai_flags = AI_ADDRCONFIG; hints.ai_family = params.ipv6 ? AF_UNSPEC : AF_INET; @@ -227,7 +221,7 @@ int s4_get_addr(char *buff, size_t n, if (len < 3 || len > 255) { return -1; } - if (resolve(id_end + 1, len, dst)) { + if (resolve(id_end + 1, len, dst, SOCK_STREAM)) { LOG(LOG_E, "not resolved: %.*s\n", len, id_end + 1); return -1; } @@ -241,25 +235,21 @@ int s4_get_addr(char *buff, size_t n, } -int s5_get_addr(char *buffer, ssize_t n, - struct sockaddr_ina *addr) +int s5_get_addr(char *buffer, size_t n, + struct sockaddr_ina *addr, int type) { if (n < S_SIZE_MIN) { LOG(LOG_E, "ss: request to small\n"); - return -1; + return -S_ER_GEN; } struct s5_req *r = (struct s5_req *)buffer; - int o = (r->atp == S_ATP_I4 ? S_SIZE_I4 : + size_t o = (r->atp == S_ATP_I4 ? S_SIZE_I4 : (r->atp == S_ATP_ID ? r->id.len + S_SIZE_ID : (r->atp == S_ATP_I6 ? S_SIZE_I6 : 0))); if (n < o) { LOG(LOG_E, "ss: bad request\n"); - return S_ER_GEN; - } - if (r->cmd != S_CMD_CONN) { - LOG(LOG_E, "ss: unsupported cmd: 0x%x\n", r->cmd); - return S_ER_CMD; + return -S_ER_GEN; } switch (r->atp) { case S_ATP_I4: @@ -269,24 +259,55 @@ int s5_get_addr(char *buffer, ssize_t n, case S_ATP_ID: if (!params.resolve) { - return S_ER_ATP; + return -S_ER_ATP; } if (r->id.len < 3 || - resolve(r->id.domain, r->id.len, addr)) { + resolve(r->id.domain, r->id.len, addr, type)) { LOG(LOG_E, "not resolved: %.*s\n", r->id.len, r->id.domain); - return S_ER_HOST; + return -S_ER_HOST; } break; case S_ATP_I6: if (!params.ipv6) - return S_ER_ATP; + return -S_ER_ATP; else { addr->in6.sin6_family = AF_INET6; addr->in6.sin6_addr = r->i6; } } addr->in.sin_port = *(uint16_t *)&buffer[o - 2]; + return o; +} + + +int s5_set_addr(char *buffer, size_t n, + struct sockaddr_ina *addr, char end) +{ + struct s5_req *r = (struct s5_req *)buffer; + if (n < S_SIZE_I4) { + return -1; + } + if (addr->sa.sa_family == AF_INET) { + if (end) { + r = (struct s5_req *)(buffer - S_SIZE_I4); + } + r->atp = S_ATP_I4; + r->i4 = addr->in.sin_addr; + r->p4 = addr->in.sin_port; + return S_SIZE_I4; + } else { + if (n < S_SIZE_I6) { + return -1; + } + if (end) { + r = (struct s5_req *)(buffer - S_SIZE_I6); + } + r->atp = S_ATP_I6; + r->i6 = addr->in6.sin6_addr; + r->p6 = addr->in6.sin6_port; + return S_SIZE_I6; + } return 0; } @@ -369,6 +390,62 @@ int create_conn(struct poolhd *pool, pair->pair = val; pair->in6 = dst->in6; pair->flag = FLAG_CONN; + val->type = EV_IGNORE; + return 0; +} + + +int udp_associate(struct poolhd *pool, + struct eval *val, struct sockaddr_ina *dst) +{ + struct sockaddr_ina addr = {}; + + int ufd = nb_socket(params.baddr.sin6_family, SOCK_DGRAM); + if (ufd < 0) { + perror("socket"); + return -1; + } + if (params.baddr.sin6_family == AF_INET6) { + int no = 0; + if (setsockopt(ufd, IPPROTO_IPV6, + IPV6_V6ONLY, (char *)&no, sizeof(no))) { + perror("setsockopt IPV6_V6ONLY"); + close(ufd); + return -1; + } + } + if (bind(ufd, (struct sockaddr *)¶ms.baddr, + sizeof(params.baddr)) < 0) { + uniperror("bind"); + close(ufd); + return -1; + } + socklen_t sz = sizeof(addr); + if (getsockname(ufd, &addr.sa, &sz)) { + perror("getsockname"); + close(ufd); + return -1; + } + struct eval *pair = add_event(pool, EV_UDP_TUNNEL, ufd, POLLIN); + if (!pair) { + close(ufd); + return -1; + } + val->pair = pair; + pair->pair = val; + pair->in6.sin6_port = 0; + + struct s5_req s5r = { + .ver = 0x05 + }; + int len = s5_set_addr((char *)&s5r, sizeof(s5r), &addr, 0); + if (len < 0) { + return -1; + } + if (send(val->fd, (char *)&s5r, len, 0) < 0) { + perror("send"); + return -1; + } return 0; } @@ -422,7 +499,7 @@ static inline int on_accept(struct poolhd *pool, struct eval *val) } -static inline int on_tunnel(struct poolhd *pool, struct eval *val, +int on_tunnel(struct poolhd *pool, struct eval *val, char *buffer, size_t bfsize, int out) { ssize_t n = 0; @@ -494,60 +571,64 @@ static inline int on_tunnel(struct poolhd *pool, struct eval *val, } -int mode_add_get(struct sockaddr_ina *dst, int m) +int on_udp_tunnel(struct eval *val, char *buffer, size_t bfsize) { - // m < 0: get, m > 0: set, m == 0: delete - time_t t; - struct elem *val; - char *str = (char *)&dst->in; - int len = sizeof(dst->sa.sa_family); - - if (dst->sa.sa_family == AF_INET) { - len = sizeof(dst->in); - } - else { - len = sizeof(dst->in6) - sizeof(dst->in6.sin6_scope_id); - } - len -= sizeof(dst->sa.sa_family); - - if (m == 0) { - mem_delete(params.mempool, str, len); - return 0; - } - else if (m > 0) { - time(&t); - val = mem_add(params.mempool, str, len); - if (!val) { - uniperror("mem_add"); + char *data = buffer + S_SIZE_I6; + size_t data_len = bfsize - S_SIZE_I6; + do { + struct sockaddr_ina addr = {0}; + struct sockaddr_ina *src = (struct sockaddr_ina *)&val->in6; + socklen_t asz = sizeof(addr); + + ssize_t n = recvfrom(val->fd, data, data_len, 0, &addr.sa, &asz); + if (n < 1) { + if (n && errno == EAGAIN) + break; + perror("recv udp"); return -1; } - val->m = m; - val->time = t; - return 0; - } - val = mem_get(params.mempool, str, len); - if (!val) { - return -1; - } - time(&t); - if (t > val->time + params.cache_ttl) { - LOG(LOG_S, "time=%ld, now=%ld, ignore\n", val->time, t); - return 0; - } - return val->m; -} - - -int ext_connect(struct poolhd *pool, struct eval *val, - struct sockaddr_ina *dst, int next, int m) -{ - struct desync_params *dp = ¶ms.dp[m]; - if (dp->to_ip == 2) { - struct sockaddr_ina addr = { .in6 = dp->addr }; - addr.in.sin_port = dst->in.sin_port; - return create_conn(pool, val, &addr, next); - } - return create_conn(pool, val, dst, next); + if (!src->in.sin_port) { + src->in6 = addr.in6; + } + ssize_t ns = 0; + + if (addr_equ(src, &addr)) { + if (*(data + 2) != 0) { // frag + continue; + } + int offs = s5_get_addr(data, n, &addr, SOCK_DGRAM); + if (offs < 0) { + return -1; + } + val->pair->in6 = addr.in6; + + if (src->sa.sa_family == AF_INET6) { + map_fix(&addr, 6); + } + if (src->sa.sa_family != addr.sa.sa_family) { + return -1; + } + ns = udp_hook(val, + data + offs, n - offs, &addr); + } + else { + //map_fix(&addr, 0); + memset(buffer, 0, S_SIZE_I6); + + int offs = s5_set_addr(data, S_SIZE_I6, + (struct sockaddr_ina *)&val->pair->in6, 1); + if (offs < 0 || offs > S_SIZE_I6) { + return -1; + } + ns = sendto(val->fd, data - offs, offs + n, 0, + (struct sockaddr *)&val->in6, sizeof(val->in6)); + } + if (ns < 0) { + perror("sendto"); + return -1; + } + } while(1); + return 0; } @@ -571,268 +652,58 @@ static inline int on_request(struct poolhd *pool, struct eval *val, val->flag = FLAG_S5; return 0; } - error = s5_get_addr(buffer, n, &dst); + if (n < S_SIZE_MIN) { + LOG(LOG_E, "ss: request to small (%ld)\n", n); + return -1; + } + struct s5_req *r = (struct s5_req *)buffer; + int s5e = 0; + switch (r->cmd) { + case S_CMD_CONN: + s5e = s5_get_addr(buffer, n, &dst, SOCK_STREAM); + if (s5e >= 0) { + error = connect_hook(pool, val, &dst, EV_CONNECT); + } + break; + case S_CMD_AUDP: + if (params.udp) { + s5e = s5_get_addr(buffer, n, &dst, SOCK_DGRAM); + if (s5e >= 0) { + error = udp_associate(pool, val, &dst); + } + break; + } + default: + LOG(LOG_E, "ss: unsupported cmd: 0x%x\n", r->cmd); + s5e = -S_ER_CMD; + } + if (s5e < 0) { + if (resp_s5_error(val->fd, -s5e) < 0) + perror("send"); + return -1; + } } else if (*buffer == S_VER4) { val->flag = FLAG_S4; + error = s4_get_addr(buffer, n, &dst); + if (error) { + if (resp_error(val->fd, error, FLAG_S4) < 0) + perror("send"); + return -1; + } + error = connect_hook(pool, val, &dst, EV_CONNECT); } else { LOG(LOG_E, "ss: invalid version: 0x%x (%lu)\n", *buffer, n); return -1; } - if (error) { - if ((val->flag == FLAG_S4 - && resp_error(val->fd, error, FLAG_S4) < 0) - || (resp_s5_error(val->fd, error) < 0)) { - uniperror("send"); - } - return -1; - } - if (params.late_conn) { - val->type = EV_DESYNC; - if (resp_error(val->fd, 0, val->flag) < 0) { - perror("send"); - return -1; - } - val->in6 = dst.in6; - return 0; - } - int m = mode_add_get(&dst, -1); - val->cache = (m == 0); - val->attempt = m < 0 ? 0 : m; - - error = ext_connect(pool, val, &dst, EV_CONNECT, m); if (error) { int en = get_e(); if (resp_error(val->fd, en ? en : error, val->flag) < 0) uniperror("send"); return -1; } - val->type = EV_IGNORE; - return 0; -} - - -int reconnect(struct poolhd *pool, struct eval *val, int m) -{ - struct eval *client = val->pair; - - if (ext_connect(pool, client, - (struct sockaddr_ina *)&val->in6, EV_DESYNC, m)) { - return -1; - } - val->pair = 0; - del_event(pool, val); - - client->type = EV_IGNORE; - client->attempt = m; - client->cache = 1; - return 0; -} - - -bool check_host(struct mphdr *hosts, struct eval *val) -{ - char *host; - int len; - if (!(len = parse_tls(val->buff.data, val->buff.size, &host))) { - len = parse_http(val->buff.data, val->buff.size, &host, 0); - } - return mem_get(hosts, host, len) != 0; -} - - -int on_torst(struct poolhd *pool, struct eval *val) -{ - int m = val->pair->attempt + 1; - - for (; m < params.dp_count; m++) { - struct desync_params *dp = ¶ms.dp[m]; - if (!(dp->detect & DETECT_TORST)) { - continue; - } - if (!dp->hosts || check_host(dp->hosts, val->pair)) { - break; - } - } - if (m >= params.dp_count) { - mode_add_get( - (struct sockaddr_ina *)&val->in6, 0); - return -1; - } - return reconnect(pool, val, m); -} - - -int on_response(struct poolhd *pool, struct eval *val, - char *resp, ssize_t sn) -{ - int m = val->pair->attempt + 1; - - char *req = val->pair->buff.data; - ssize_t qn = val->pair->buff.size; - - for (; m < params.dp_count; m++) { - struct desync_params *dp = ¶ms.dp[m]; - - switch (0) { - default: - if ((dp->detect & DETECT_HTTP_LOCAT) - && is_http_redirect(req, qn, resp, sn)) { - break; - } - else if ((dp->detect & DETECT_TLS_INVSID) - && neq_tls_sid(req, qn, resp, sn) - && !neq_tls_sid( - fake_tls.data, fake_tls.size, resp, sn)) { - break; - } - else if ((dp->detect & DETECT_TLS_ALERT) - && is_tls_alert(resp, sn)) { - break; - } - else if (dp->detect & DETECT_HTTP_CLERR) { - int code = get_http_code(resp, sn); - if (code > 400 && code < 451 && code != 429) { - break; - } - } - continue; - } - if (!dp->hosts || check_host(dp->hosts, val->pair)) { - break; - } - } - if (m < params.dp_count) { - return reconnect(pool, val, m); - } - return -1; -} - - -int on_tunnel_check(struct poolhd *pool, struct eval *val, - char *buffer, size_t bfsize, int out) -{ - if (out) { - return on_tunnel(pool, val, buffer, bfsize, out); - } - ssize_t n = recv(val->fd, buffer, bfsize, 0); - if (n < 1) { - uniperror("recv"); - switch (get_e()) { - case ECONNRESET: - case ETIMEDOUT: - break; - default: return -1; - } - return on_torst(pool, val); - } - // - if (on_response(pool, val, buffer, n) == 0) { - return 0; - } - struct eval *pair = val->pair; - - ssize_t sn = send(pair->fd, buffer, n, 0); - if (n != sn) { - uniperror("send"); - return -1; - } - val->type = EV_TUNNEL; - pair->type = EV_TUNNEL; - - free(pair->buff.data); - pair->buff.data = 0; - pair->buff.size = 0; - - if (params.timeout && - set_timeout(val->fd, 0)) { - return -1; - } - int m = pair->attempt; - - if (!pair->cache) { - return 0; - } - if (m == 0) { - LOG(LOG_S, "delete ip: m=%d\n", m); - } else { - LOG(LOG_S, "save ip: m=%d\n", m); - } - return mode_add_get( - (struct sockaddr_ina *)&val->in6, m); -} - - -int on_desync(struct poolhd *pool, struct eval *val, - char *buffer, size_t bfsize) -{ - if (val->flag == FLAG_CONN) { - if (mod_etype(pool, val, POLLOUT, 0)) { - uniperror("mod_etype"); - return -1; - } - val = val->pair; - } - ssize_t n; - int m = val->attempt; - LOG((m ? LOG_S : LOG_L), "desync params index: %d\n", m); - - if (!val->buff.data) { - n = recv(val->fd, buffer, bfsize, 0); - if (n <= 0) { - if (n) uniperror("recv data"); - return -1; - } - val->buff.size = n; - val->recv_count += n; - - if (!(val->buff.data = malloc(n))) { - uniperror("malloc"); - return -1; - } - memcpy(val->buff.data, buffer, n); - - if (!m) for (; m < params.dp_count; m++) { - struct desync_params *dp = ¶ms.dp[m]; - if (!dp->detect && - (!dp->hosts || check_host(dp->hosts, val))) { - break; - } - } - if (m >= params.dp_count) return -1; - val->attempt = m; - - if (params.late_conn) { - return ext_connect(pool, val, - (struct sockaddr_ina *)&val->in6, EV_DESYNC, m); - } - } - else { - n = val->buff.size; - memcpy(buffer, val->buff.data, n); - } - if (params.timeout && - set_timeout(val->pair->fd, params.timeout)) { - return -1; - } - ssize_t sn = desync(val->pair->fd, buffer, bfsize, - n, val->buff.offset, (struct sockaddr *)&val->pair->in6, m); - if (sn < 0) { - return -1; - } - if (sn < n) { - val->buff.offset = sn; - if (mod_etype(pool, val->pair, POLLOUT, 1)) { - uniperror("mod_etype"); - return -1; - } - val->pair->type = EV_DESYNC; - return 0; - } - val->type = EV_TUNNEL; - val->pair->type = EV_PRE_TUNNEL; return 0; } @@ -929,6 +800,11 @@ int event_loop(int srvfd) del_event(pool, val); continue; + case EV_UDP_TUNNEL: + if (on_udp_tunnel(val, buffer, bfsize)) + del_event(pool, val); + continue; + case EV_CONNECT: if (on_connect(pool, val, etype & POLLERR)) del_event(pool, val); diff --git a/proxy.h b/proxy.h index 827884e..b92b6bc 100644 --- a/proxy.h +++ b/proxy.h @@ -6,6 +6,8 @@ #include #endif +#include + struct sockaddr_ina { union { struct sockaddr sa; @@ -94,6 +96,18 @@ enum s4_rep { #define S_SIZE_I6 22 #define S_SIZE_ID 7 +void map_fix(struct sockaddr_ina *addr, char f6); + +int resp_error(int fd, int e, int flag); + +int create_conn(struct poolhd *pool, + struct eval *val, struct sockaddr_ina *dst, int next); + +int on_tunnel(struct poolhd *pool, struct eval *val, + char *buffer, size_t bfsize, int out); + int listen_socket(struct sockaddr_ina *srv); + int event_loop(int srvfd); + int run(struct sockaddr_ina *srv); diff --git a/readme.txt b/readme.txt index 2111537..dfd9a7d 100644 --- a/readme.txt +++ b/readme.txt @@ -31,6 +31,9 @@ $ ./ciadpi --disorder 3 -A --tlsrec 1+s Отбрасывать запросы, если в качестве адреса указан домен Т.к. резолвинг выполняется синхронно, то он может замедлить или даже заморозить работу +-U, --no-udp + Не проксировать UDP + -K, --desync-known Отключить запутывание для нераспознанных протоколов Распознаваемые протоколы: HTTP и TLS с SNI @@ -54,12 +57,12 @@ $ ./ciadpi --disorder 3 -A --tlsrec 1+s cl_err : HTTP ответ, код которого равен 40x, но не 429 sid_inv : session_id в TLS ServerHello и ClientHello не совпадают alert : TLS Error Alert в ответе - nop : Предыдущая группа пропущена, например из-за ограничения по hosts + none : Предыдущая группа пропущена, например из-за ограничения по доменам или протоколам По умолчанию обрабатывается только torst Можно указывать несколько групп опций, раделяя их данным параметром Если соединение успешно прошло, то параметры для данного IP будут закешированны Параметры, которые можно вынести в отдельную группу: - hosts, dst, split, disorder, oob, fake, ttl, ip-opt, md5sig, mod-http, tlsrec + proto, hosts, dst, split, disorder, oob, fake, ttl, ip-opt, md5sig, fake-data, mod-http, tlsrec Пример: --auto=redirect --split=1+h --auto=torst --fake -1 --auto=sid_inv,alert --tlsrec 1+s @@ -71,6 +74,9 @@ $ ./ciadpi --disorder 3 -A --tlsrec 1+s В Linux переводится в миллисекунды, поэтому можно указать дробное число Истечение таймаута будет обработано --auto +-K, --proto[=t,h,d,q] + Белый список протоколов: tls,http,dns,quic + -H, --hosts Ограничить область действия параметров списком доменов Домены должны быть разделены новой строкой или пробелом @@ -113,8 +119,7 @@ $ ./ciadpi --disorder 3 -A --tlsrec 1+s Большинство серверов (в основном на Linux) отбрасывают пакеты с данной опцией Поддерживается только в Linux, может быть выключен в некоторых сборках ядра (< 3.9, Android) --l, --fake-tls --j, --fake-http +-l, --fake-data Указать свои поддельные пакеты, вместо дефолтных -e, --oob-data @@ -228,25 +233,31 @@ TCP может отсылать данные вне основного пото --auto, --hosts Параметр auto делит опции на группы. Для каждого запроса они обходятся слева на право. -Сначала проверяется триггер указанный в auto, затем hosts. +Сначала проверяется триггер, указанный в auto, затем proto и hosts. Примеры: --fake -1 --ttl 10 --auto=alert,sid_inv --fake -1 --ttl 5 По умолчанию использовать fake с ttl=10, в случае ошибки использовать fake с ttl=5 ---hosts list.txt --disorder 3 --auto=nop +--hosts list.txt --disorder 3 --auto=none Применять запутывание только для доменов из list.txt ---hosts list.txt --auto=nop --disorder 3 +--hosts list.txt --auto=none --disorder 3 Не применять запутывание для доменов из list.txt ---late-conn --hosts ':one.one.one.one' --dst 1.1.1.1 --disorder 3 --auto=nop --auto=torst --timeout 3 --tlsrec 1+s +--late-conn --hosts ':one.one.one.one' --dst 1.1.1.1 --disorder 3 --auto=none --auto=torst --timeout 3 --tlsrec 1+s Для указанного домена применять disorder, подключаясь только на указанный IP и игнорируя адрес из запроса. Для остальных ничего не делать, однако если обнаружится блокировка, то попробовать применить tlsrec. --auto=torst --hosts list.txt --disorder 3 По умолчанию ничего не делать, использовать disorder при условии, что произошла блокировка и домен входит в list.txt. +--proto=http,tls --disorder 3 --auto=none +Запутывать только HTTP и TLS + +--proto=http --fake -1 --fake-data=':GET /...' --auto=none --fake -1 +Переопределить фейковый пакет для HTTP + ------- Сборка: Для сборки понадобится: