diff --git a/binaries/aarch64/tpws b/binaries/aarch64/tpws index 06156e6..6ac03ca 100755 Binary files a/binaries/aarch64/tpws and b/binaries/aarch64/tpws differ diff --git a/binaries/arm/tpws b/binaries/arm/tpws index 4175a49..06ddfb3 100755 Binary files a/binaries/arm/tpws and b/binaries/arm/tpws differ diff --git a/binaries/freebsd-x64/tpws b/binaries/freebsd-x64/tpws index 42374b5..0eb5d09 100755 Binary files a/binaries/freebsd-x64/tpws and b/binaries/freebsd-x64/tpws differ diff --git a/binaries/mac64/tpws b/binaries/mac64/tpws index 5dd4b75..21eaeb1 100755 Binary files a/binaries/mac64/tpws and b/binaries/mac64/tpws differ diff --git a/binaries/mips32r1-lsb/tpws b/binaries/mips32r1-lsb/tpws index 8d547d9..86a8a7c 100755 Binary files a/binaries/mips32r1-lsb/tpws and b/binaries/mips32r1-lsb/tpws differ diff --git a/binaries/mips32r1-msb/tpws b/binaries/mips32r1-msb/tpws index b15b7fe..a7e6114 100755 Binary files a/binaries/mips32r1-msb/tpws and b/binaries/mips32r1-msb/tpws differ diff --git a/binaries/mips64r2-msb/tpws b/binaries/mips64r2-msb/tpws index e3563bd..620aac6 100755 Binary files a/binaries/mips64r2-msb/tpws and b/binaries/mips64r2-msb/tpws differ diff --git a/binaries/ppc/tpws b/binaries/ppc/tpws index 856f183..79b6c1b 100755 Binary files a/binaries/ppc/tpws and b/binaries/ppc/tpws differ diff --git a/binaries/x86/tpws b/binaries/x86/tpws index e75c7c6..6380d3f 100755 Binary files a/binaries/x86/tpws and b/binaries/x86/tpws differ diff --git a/binaries/x86_64/tpws b/binaries/x86_64/tpws index 7f51308..2731168 100755 Binary files a/binaries/x86_64/tpws and b/binaries/x86_64/tpws differ diff --git a/binaries/x86_64/tpws_wsl.tgz b/binaries/x86_64/tpws_wsl.tgz index 26d5ed7..acc89bd 100644 Binary files a/binaries/x86_64/tpws_wsl.tgz and b/binaries/x86_64/tpws_wsl.tgz differ diff --git a/blockcheck.sh b/blockcheck.sh index c0e629b..5243c55 100755 --- a/blockcheck.sh +++ b/blockcheck.sh @@ -627,17 +627,23 @@ tpws_check_domain_bypass() # $1 - test function # $2 - encrypted test : 1/0 # $3 - domain - local s pos strategy sec="$2" + local s s2 pos strategy sec="$2" if [ "$sec" = 0 ]; then - for s in '--hostcase' '--hostspell=hoSt' '--split-http-req=method' '--split-http-req=method --hostcase' '--split-http-req=host' '--split-http-req=host --hostcase' \ - '--hostdot' '--hosttab' '--hostnospace' '--methodspace' '--methodeol' '--unixeol' \ - '--hostpad=1024' '--hostpad=2048' '--hostpad=4096' '--hostpad=8192' '--hostpad=16384'; do + for s in '--hostcase' '--hostspell=hoSt' '--hostdot' '--hosttab' '--hostnospace' '--methodspace' '--methodeol' '--unixeol' \ + '--hostpad=1024' '--hostpad=2048' '--hostpad=4096' '--hostpad=8192' '--hostpad=16384' ; do tpws_curl_test_update $1 $3 $s done + for s2 in '' '--disorder'; do + for s in '--split-http-req=method' '--split-http-req=method --hostcase' '--split-http-req=host' '--split-http-req=host --hostcase' ; do + tpws_curl_test_update $1 $3 $s $s2 + done + done else - for pos in 1 2 3 4 5 10 50 100; do - s="--split-pos=$pos" - tpws_curl_test_update $1 $3 $s && break + for s2 in '' '--disorder'; do + for pos in 1 2 3 4 5 10 50 100; do + s="--split-pos=$pos" + tpws_curl_test_update $1 $3 $s $s2 && break + done done fi report_strategy $1 $3 tpws diff --git a/docs/readme.eng.md b/docs/readme.eng.md index 269b0c4..6983b03 100644 --- a/docs/readme.eng.md +++ b/docs/readme.eng.md @@ -529,6 +529,7 @@ tpws is transparent proxy. --split-http-req=method|host ; split http request at specified logical position. --split-pos= ; split at specified pos. split-http-req takes precedence over split-pos for http reqs. --split-any-protocol ; split not only http and https + --disorder ; when splitting simulate sending second fragment first --hostcase ; change Host: => host: --hostspell ; exact spelling of "Host" header. must be 4 chars. default is "host" --hostdot ; add "." after Host: name @@ -597,6 +598,12 @@ if tpws serves many clients it can cause trouble. also DoS attack is possible ag if remote resolving causes trouble configure clients to use local name resolution and use `--no-resolve` option on tpws side. +`--disorder` is an additional flag to any split option. +It tries to simulate `--disorder2` option of `nfqws` using standard socket API without the need of additional privileges. +This works fine in Linux and MacOS but unexpectedly in FreeBSD and OpenBSD +(system sends second fragment then the whole packet instead of the first fragment). + + ## Ways to get a list of blocked IP nftables can't work with ipsets. Native nf sets require lots of RAM to load large ip lists with subnets and intervals. diff --git a/docs/readme.txt b/docs/readme.txt index 6ecc3e4..ce302d2 100644 --- a/docs/readme.txt +++ b/docs/readme.txt @@ -574,6 +574,7 @@ tpws - это transparent proxy. --split-http-req=method|host ; способ разделения http запросов на сегменты : около метода (GET,POST) или около заголовка Host --split-pos= ; делить все посылы на сегменты в указанной позиции. единственная опция, работающая на не-http. при указании split-http-req он имеет преимущество на http. --split-any-protocol ; применять split-pos к любым пакетам. по умолчанию - только к http и TLS ClientHello + --disorder ; путем манипуляций с сокетом вынуждает отправлять первым второй сегмент разделенного запроса --hostcase ; менять регистр заголовка "Host:". по умолчанию на "host:". --hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase --hostdot ; добавление точки после имени хоста : "Host: kinozal.tv." @@ -672,6 +673,17 @@ tpws полностью работает на асинхронных сокет Если при этом критический размер padding около MTU, значит скорее всего DPI не выполняет реассемблинг пакетов, и лучше будет использовать обычные опции --split-… Если все же реассемблинг выполняется, то критический размер будет около размера буфера DPI. Он может быть 4K или 8K, возможны и другие значения. +--disorder - это попытка симулировать режим disorder2 nfqws , используя особенности ОС по реализации stream сокетов. +Однако, в отличие от nfqws, здесь не требуются повышенные привилегии. +Реализовано это следующим образом. У сокета есть возможность выставить TTL. Все пакеты будут отправляться с ним. +Перед отправкой первого сегмента ставим TTL=1. Пакет будет дропнут на первом же роутере, он не дойдет ни до DPI, ни до сервера. +Затем возвращаем TTL в значение по умолчанию. ОС отсылает второй сегмент, и он уже доходит до сервера. +Сервер возвращает SACK, потому что не получил первый кусок, и ОС его отправляет повторно, но здесь уже мы ничего не делаем. +Этот режим работает как ожидается на Linux и MacOS. Однако, на FreeBSD и OpenBSD он работает не так хорошо. +Ядро этих ОС отсылает ретрансмиссию в виде полного пакета. Потому выходит, что до сервера идет сначала второй кусок, +а потом полный запрос без сплита. На него может отреагировать DPI штатным образом. +--disorder является дополнительным флагом к любому сплиту. Сам по себе он не делает ничего. + --skip-nodelay может быть полезен, чтобы привести MTU к MTU системы, на которой работает tpws. Это может быть полезно для скрытия факта использования VPN. Пониженный MTU - 1 из способов обнаружения подозрительного подключения. С tcp proxy ваши соединения неотличимы от тех, что сделал бы сам шлюз. diff --git a/tpws/helpers.c b/tpws/helpers.c index 34e4519..6cd4401 100644 --- a/tpws/helpers.c +++ b/tpws/helpers.c @@ -172,11 +172,27 @@ bool is_private6(const struct sockaddr_in6* a) -int set_keepalive(int fd) +bool set_keepalive(int fd) { int yes=1; return setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int))!=-1; } +bool set_ttl(int fd, int ttl) +{ + return setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl))!=-1; +} +bool set_hl(int fd, int hl) +{ + return setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hl, sizeof(hl))!=-1; +} +bool set_ttl_hl(int fd, int ttl) +{ + bool b1,b2; + // try to set both but one may fail if family is wrong + b1=set_ttl(fd, ttl); + b2=set_hl(fd, ttl); + return b1 || b2; +} int get_so_error(int fd) { // getsockopt(SO_ERROR) clears error diff --git a/tpws/helpers.h b/tpws/helpers.h index 77f6973..9524bc5 100644 --- a/tpws/helpers.h +++ b/tpws/helpers.h @@ -25,7 +25,10 @@ bool is_localnet(const struct sockaddr *a); bool is_linklocal(const struct sockaddr_in6* a); bool is_private6(const struct sockaddr_in6* a); -int set_keepalive(int fd); +bool set_keepalive(int fd); +bool set_ttl(int fd, int ttl); +bool set_hl(int fd, int hl); +bool set_ttl_hl(int fd, int ttl); int get_so_error(int fd); static inline uint16_t pntoh16(const uint8_t *p) { diff --git a/tpws/params.h b/tpws/params.h index e377fad..ec1925c 100644 --- a/tpws/params.h +++ b/tpws/params.h @@ -43,6 +43,8 @@ struct params_s enum splithttpreq split_http_req; bool split_any_protocol; int split_pos; + bool disorder; + int ttl_default; char pidfile[256]; diff --git a/tpws/tpws.c b/tpws/tpws.c index e0551d2..2c399a3 100644 --- a/tpws/tpws.c +++ b/tpws/tpws.c @@ -103,6 +103,18 @@ static bool is_interface_online(const char *ifname) close(sock); return !!(ifr.ifr_flags & IFF_UP); } +static int get_default_ttl() +{ + int sock,ttl=0; + socklen_t optlen=sizeof(ttl); + + if ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))!=-1) + { + getsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, &optlen); + close(sock); + } + return ttl; +} static void exithelp() @@ -147,6 +159,11 @@ static void exithelp() " --split-http-req=method|host\t; split at specified logical part of plain http request\n" " --split-pos=\t; split at specified pos. split-http-req takes precedence for http.\n" " --split-any-protocol\t\t; split not only http and https\n" +#if defined(BSD) && !defined(__APPLE__) + " --disorder\t\t\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\n" +#else + " --disorder\t\t\t; when splitting simulate sending second fragment first\n" +#endif " --hostcase\t\t\t; change Host: => host:\n" " --hostspell\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" " --hostdot\t\t\t; add \".\" after Host: name\n" @@ -204,6 +221,19 @@ static void checkbind_clean() } +void save_default_ttl() +{ + if (!params.ttl_default) + { + params.ttl_default = get_default_ttl(); + if (!params.ttl_default) + { + fprintf(stderr, "could not get default ttl\n"); + exit_clean(1); + } + } +} + void parse_params(int argc, char *argv[]) { int option_index = 0; @@ -253,23 +283,24 @@ void parse_params(int argc, char *argv[]) { "split-http-req",required_argument,0,0 },// optidx=23 { "split-pos",required_argument,0,0 },// optidx=24 { "split-any-protocol",optional_argument,0,0},// optidx=25 - { "methodspace",no_argument,0,0 },// optidx=26 - { "methodeol",no_argument,0,0 },// optidx=27 - { "hosttab",no_argument,0,0 },// optidx=28 - { "unixeol",no_argument,0,0 },// optidx=29 - { "hostlist",required_argument,0,0 },// optidx=30 - { "hostlist-exclude",required_argument,0,0 },// optidx=31 - { "pidfile",required_argument,0,0 },// optidx=32 - { "debug",optional_argument,0,0 },// optidx=33 - { "local-rcvbuf",required_argument,0,0 },// optidx=34 - { "local-sndbuf",required_argument,0,0 },// optidx=35 - { "remote-rcvbuf",required_argument,0,0 },// optidx=36 - { "remote-sndbuf",required_argument,0,0 },// optidx=37 - { "socks",no_argument,0,0 },// optidx=38 - { "no-resolve",no_argument,0,0 },// optidx=39 - { "skip-nodelay",no_argument,0,0 },// optidx=40 + { "disorder",no_argument,0,0 },// optidx=26 + { "methodspace",no_argument,0,0 },// optidx=27 + { "methodeol",no_argument,0,0 },// optidx=28 + { "hosttab",no_argument,0,0 },// optidx=29 + { "unixeol",no_argument,0,0 },// optidx=30 + { "hostlist",required_argument,0,0 },// optidx=31 + { "hostlist-exclude",required_argument,0,0 },// optidx=32 + { "pidfile",required_argument,0,0 },// optidx=33 + { "debug",optional_argument,0,0 },// optidx=34 + { "local-rcvbuf",required_argument,0,0 },// optidx=35 + { "local-sndbuf",required_argument,0,0 },// optidx=36 + { "remote-rcvbuf",required_argument,0,0 },// optidx=37 + { "remote-sndbuf",required_argument,0,0 },// optidx=38 + { "socks",no_argument,0,0 },// optidx=39 + { "no-resolve",no_argument,0,0 },// optidx=40 + { "skip-nodelay",no_argument,0,0 },// optidx=41 #if defined(BSD) && !defined(__OpenBSD__) && !defined(__APPLE__) - { "enable-pf",no_argument,0,0 },// optidx=41 + { "enable-pf",no_argument,0,0 },// optidx=42 #endif { NULL,0,NULL,0 } }; @@ -453,23 +484,27 @@ void parse_params(int argc, char *argv[]) case 25: /* split-any-protocol */ params.split_any_protocol = true; break; - case 26: /* methodspace */ + case 26: /* disorder */ + params.disorder = true; + save_default_ttl(); + break; + case 27: /* methodspace */ params.methodspace = true; params.tamper = true; break; - case 27: /* methodeol */ + case 28: /* methodeol */ params.methodeol = true; params.tamper = true; break; - case 28: /* hosttab */ + case 29: /* hosttab */ params.hosttab = true; params.tamper = true; break; - case 29: /* unixeol */ + case 30: /* unixeol */ params.unixeol = true; params.tamper = true; break; - case 30: /* hostlist */ + case 31: /* hostlist */ if (!strlist_add(¶ms.hostlist_files, optarg)) { fprintf(stderr, "strlist_add failed\n"); @@ -477,7 +512,7 @@ void parse_params(int argc, char *argv[]) } params.tamper = true; break; - case 31: /* hostlist-exclude */ + case 32: /* hostlist-exclude */ if (!strlist_add(¶ms.hostlist_exclude_files, optarg)) { fprintf(stderr, "strlist_add failed\n"); @@ -485,36 +520,36 @@ void parse_params(int argc, char *argv[]) } params.tamper = true; break; - case 32: /* pidfile */ + case 33: /* pidfile */ strncpy(params.pidfile,optarg,sizeof(params.pidfile)); params.pidfile[sizeof(params.pidfile)-1]='\0'; break; - case 33: + case 34: params.debug = optarg ? atoi(optarg) : 1; break; - case 34: /* local-rcvbuf */ + case 35: /* local-rcvbuf */ params.local_rcvbuf = atoi(optarg)/2; break; - case 35: /* local-sndbuf */ + case 36: /* local-sndbuf */ params.local_sndbuf = atoi(optarg)/2; break; - case 36: /* remote-rcvbuf */ + case 37: /* remote-rcvbuf */ params.remote_rcvbuf = atoi(optarg)/2; break; - case 37: /* remote-sndbuf */ + case 38: /* remote-sndbuf */ params.remote_sndbuf = atoi(optarg)/2; break; - case 38: /* socks */ + case 39: /* socks */ params.proxy_type = CONN_TYPE_SOCKS; break; - case 39: /* no-resolve */ + case 40: /* no-resolve */ params.no_resolve = true; break; - case 40: /* skip-nodelay */ + case 41: /* skip-nodelay */ params.skip_nodelay = true; break; #if defined(BSD) && !defined(__OpenBSD__) && !defined(__APPLE__) - case 41: /* enable-pf */ + case 42: /* enable-pf */ params.pf_enable = true; break; #endif @@ -849,7 +884,7 @@ int main(int argc, char *argv[]) fprintf(stderr,"could not initialize redirector !!!\n"); goto exiterr; } - + for(i=0;i<=params.binds_last;i++) { if (params.debug) @@ -862,6 +897,7 @@ int main(int argc, char *argv[]) perror("socket"); goto exiterr; } + #ifndef __OpenBSD__ // in OpenBSD always IPV6_ONLY for wildcard sockets if ((list[i].salisten.ss_family == AF_INET6) && setsockopt(listen_fd[i], IPPROTO_IPV6, IPV6_V6ONLY, &list[i].ipv6_only, sizeof(int)) == -1) diff --git a/tpws/tpws_conn.c b/tpws/tpws_conn.c index d35dc8b..28c8020 100644 --- a/tpws/tpws_conn.c +++ b/tpws/tpws_conn.c @@ -111,8 +111,29 @@ static bool proxy_remote_conn_ack(tproxy_conn_t *conn, int sock_err) } +ssize_t send_with_ttl(int fd, const void *buf, size_t len, int flags, int ttl) +{ + ssize_t wr; -static bool send_buffer_create(send_buffer_t *sb, char *data, size_t len) + if (ttl) + { + DBGPRINT("send_with_ttl %d fd=%d",ttl,fd); + if (!set_ttl_hl(fd, ttl)) + fprintf(stderr,"could not set ttl %d to fd=%d\n",ttl,fd); + } + wr = send(fd, buf, len, flags); + if (ttl) + { + int e=errno; + if (!set_ttl_hl(fd, params.ttl_default)) + fprintf(stderr,"could not set ttl %d to fd=%d\n",params.ttl_default,fd); + errno=e; + } + return wr; +} + + +static bool send_buffer_create(send_buffer_t *sb, char *data, size_t len, int ttl) { if (sb->data) { @@ -128,6 +149,7 @@ static bool send_buffer_create(send_buffer_t *sb, char *data, size_t len) if (data) memcpy(sb->data,data,len); sb->len = len; sb->pos = 0; + sb->ttl = ttl; return true; } static void send_buffer_free(send_buffer_t *sb) @@ -162,7 +184,7 @@ static ssize_t send_buffer_send(send_buffer_t *sb, int fd) { ssize_t wr; - wr = send(fd, sb->data + sb->pos, sb->len - sb->pos, 0); + wr = send_with_ttl(fd, sb->data + sb->pos, sb->len - sb->pos, 0, sb->ttl); DBGPRINT("send_buffer_send len=%zu pos=%zu wr=%zd err=%d",sb->len,sb->pos,wr,errno) if (wr>0) { @@ -236,16 +258,16 @@ static bool conn_has_unsent_pair(tproxy_conn_t *conn) } -static ssize_t send_or_buffer(send_buffer_t *sb, int fd, char *buf, size_t len) +static ssize_t send_or_buffer(send_buffer_t *sb, int fd, char *buf, size_t len, int ttl) { ssize_t wr=0; if (len) { - wr = send(fd, buf, len, 0); + wr = send_with_ttl(fd, buf, len, 0, ttl); if (wr<0 && errno==EAGAIN) wr=0; if (wr>=0 && wrpartner->wr_buf, conn->partner->fd, buf, split_pos); + wr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf, split_pos, params.disorder ? 1 : 0); DBGPRINT("send_or_buffer(1) fd=%d wr=%zd err=%d",conn->partner->fd,wr,errno) if (wr >= 0) { conn->partner->twr += wr; - wr = send_or_buffer(conn->partner->wr_buf + 1, conn->partner->fd, buf + split_pos, bs - split_pos); + wr = send_or_buffer(conn->partner->wr_buf + 1, conn->partner->fd, buf + split_pos, bs - split_pos, 0); DBGPRINT("send_or_buffer(2) fd=%d wr=%zd err=%d",conn->partner->fd,wr,errno) if (wr>0) conn->partner->twr += wr; } } else { - wr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf, bs); + wr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf, bs, 0); DBGPRINT("send_or_buffer(3) fd=%d wr=%zd err=%d",conn->partner->fd,wr,errno) if (wr>0) conn->partner->twr += wr; } @@ -1039,7 +1061,7 @@ static bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number) DBGPRINT("read_all_and_buffer(%d) numbytes=%d",buffer_number,numbytes) if (numbytes>0) { - if (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes)) + if (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes, 0)) { ssize_t rd = recv(conn->fd, conn->partner->wr_buf[buffer_number].data, numbytes, MSG_DONTWAIT); if (rd>0) diff --git a/tpws/tpws_conn.h b/tpws/tpws_conn.h index 5b574d2..70034b0 100644 --- a/tpws/tpws_conn.h +++ b/tpws/tpws_conn.h @@ -29,6 +29,7 @@ struct send_buffer { char *data; size_t len,pos; + int ttl; }; typedef struct send_buffer send_buffer_t;