Compare commits

...

6 Commits

Author SHA1 Message Date
bol-van
f0cc49c7e3 tpws: minor help text change 2024-11-19 15:14:39 +03:00
bol-van
cc30a90556 tpws: check for kernel version in fixseg 2024-11-19 14:01:24 +03:00
bol-van
e12dd237c2 tpws: check for kernel version in fixseg 2024-11-19 13:59:46 +03:00
bol-van
19e7fca627 readme: more notices about fix-seg 2024-11-19 11:47:14 +03:00
bol-van
a0e1742861 tpws: rate limit fix-seg errors without --debug 2024-11-19 10:12:39 +03:00
bol-van
a93b142dcd tpws: fix-seg wait before send. tune max delay. 2024-11-19 09:51:32 +03:00
6 changed files with 94 additions and 25 deletions

View File

@ -812,7 +812,7 @@ tpws - это transparent proxy.
--skip-nodelay ; не устанавливать в исходящих соединения TCP_NODELAY. несовместимо со split. --skip-nodelay ; не устанавливать в исходящих соединения TCP_NODELAY. несовместимо со split.
--local-tcp-user-timeout=<seconds> ; таймаут соединений client-proxy (по умолчанию : 10 сек, 0 = оставить системное значение) --local-tcp-user-timeout=<seconds> ; таймаут соединений client-proxy (по умолчанию : 10 сек, 0 = оставить системное значение)
--remote-tcp-user-timeout=<seconds> ; таймаут соединений proxy-target (по умолчанию : 20 сек, 0 = оставить системное значение) --remote-tcp-user-timeout=<seconds> ; таймаут соединений proxy-target (по умолчанию : 20 сек, 0 = оставить системное значение)
--fix-seg ; исправлять неудачи tcp сегментации ценой задержек для всех клиентов и замедления --fix-seg=<int> ; исправлять неудачи tcp сегментации ценой задержек для всех клиентов и замедления. ждать до N мс. по умолчанию 30 мс.
--split-pos=N|-N|marker+N|marker-N ; список через запятую маркеров для tcp сегментации --split-pos=N|-N|marker+N|marker-N ; список через запятую маркеров для tcp сегментации
--split-any-protocol ; применять сегментацию к любым пакетам. по умолчанию - только к известным протоколам (http, TLS) --split-any-protocol ; применять сегментацию к любым пакетам. по умолчанию - только к известным протоколам (http, TLS)
@ -881,12 +881,16 @@ tpws, как и nfqws, поддерживает множественную се
Как показывается практика, проблемы могут начаться , если количество сплит позиций превышает 8. Как показывается практика, проблемы могут начаться , если количество сплит позиций превышает 8.
При неудаче сегментации будет выводиться сообщение `WARNING ! segmentation failed`. При неудаче сегментации будет выводиться сообщение `WARNING ! segmentation failed`.
Если вы его видите, это повод снизить количество сплит позиций. Если вы его видите, это повод снизить количество сплит позиций.
Если это не вариант, есть параметр `--fix-seg`. Он позволяет подождать завершение отсылки перед отправкой следующей части. Если это не вариант, для ядер Linux >=4.6 есть параметр `--fix-seg`. Он позволяет подождать завершение отсылки перед отправкой следующей части.
Но этот вариант ломает модель асинхронной обработки событий. Пока идет ожидание, все остальные соединения не обрабатываются Но этот вариант ломает модель асинхронной обработки событий. Пока идет ожидание, все остальные соединения не обрабатываются
и кратковременно подвисают. На практике это может быть совсем небольшое ожидание - менее 10 мс. и кратковременно подвисают. На практике это может быть совсем небольшое ожидание - менее 10 мс.
И производится оно только , если происходит split, и в ожидании есть реальная необходимость. И производится оно только , если происходит split, и в ожидании есть реальная необходимость.
В высоконагруженных системах данный вариант не рекомендуется. Но для домашнего использования может подойти, и вы эти задержки даже не заметите. В высоконагруженных системах данный вариант не рекомендуется. Но для домашнего использования может подойти, и вы эти задержки даже не заметите.
Если вы пытаетесь сплитнуть массивную передачу с `--split-any-protocol`, когда информация поступает быстрее отсылки,
то без `--fix-seg` ошибки сегментации будут сыпаться сплошным потоком.
Работа по массивному потоку без ограничителей `--tamper-start` и `--tamper-cutoff` обычно лишена смысла.
tpws работает на уровне сокетов, поэтому длинный запрос, не вмещающийся в 1 пакет (TLS с kyber), он получает целым блоком. tpws работает на уровне сокетов, поэтому длинный запрос, не вмещающийся в 1 пакет (TLS с kyber), он получает целым блоком.
На каждую сплит часть он делает отдельный вызов `send()`. Но ОС не сможет отослать данные в одном пакете, если размер превысит MTU. На каждую сплит часть он делает отдельный вызов `send()`. Но ОС не сможет отослать данные в одном пакете, если размер превысит MTU.
В случае слишком большого сегмента ОС дополнительно его порежет на более мелкие. Результат должен быть аналогичен nfqws. В случае слишком большого сегмента ОС дополнительно его порежет на более мелкие. Результат должен быть аналогичен nfqws.

View File

@ -10,6 +10,7 @@
#include <time.h> #include <time.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <libgen.h> #include <libgen.h>
#include <unistd.h>
#ifdef __linux__ #ifdef __linux__
#include <linux/tcp.h> #include <linux/tcp.h>
@ -477,6 +478,24 @@ void msleep(unsigned int ms)
} }
#ifdef __linux__ #ifdef __linux__
bool socket_supports_notsent()
{
int sfd;
struct tcp_info tcpi;
sfd = socket(AF_INET,SOCK_STREAM,0);
if (sfd<0) return false;
socklen_t ts = sizeof(tcpi);
if (getsockopt(sfd, IPPROTO_TCP, TCP_INFO, (char *)&tcpi, &ts) < 0)
{
close(sfd);
return false;
}
close(sfd);
return ts>=((char *)&tcpi.tcpi_notsent_bytes - (char *)&tcpi.tcpi_state + sizeof(tcpi.tcpi_notsent_bytes));
}
bool socket_has_notsent(int sfd) bool socket_has_notsent(int sfd)
{ {
struct tcp_info tcpi; struct tcp_info tcpi;
@ -484,10 +503,11 @@ bool socket_has_notsent(int sfd)
if (getsockopt(sfd, IPPROTO_TCP, TCP_INFO, (char *)&tcpi, &ts) < 0) if (getsockopt(sfd, IPPROTO_TCP, TCP_INFO, (char *)&tcpi, &ts) < 0)
return false; return false;
if (tcpi.tcpi_state != 1) if (tcpi.tcpi_state != 1) // TCP_ESTABLISHED
return false; return false;
size_t s = (char *)&tcpi.tcpi_notsent_bytes - (char *)&tcpi.tcpi_state; size_t s = (char *)&tcpi.tcpi_notsent_bytes - (char *)&tcpi + sizeof(tcpi.tcpi_notsent_bytes);
if (ts < s) if (ts < s)
// old structure version
return false; return false;
return !!tcpi.tcpi_notsent_bytes; return !!tcpi.tcpi_notsent_bytes;
} }

View File

@ -120,6 +120,7 @@ static inline const struct in6_addr *mask_from_preflen6(uint8_t preflen)
void msleep(unsigned int ms); void msleep(unsigned int ms);
#ifdef __linux__ #ifdef __linux__
bool socket_supports_notsent();
bool socket_has_notsent(int sfd); bool socket_has_notsent(int sfd);
bool socket_wait_notsent(int sfd, unsigned int delay_ms, unsigned int *wasted_ms); bool socket_wait_notsent(int sfd, unsigned int delay_ms, unsigned int *wasted_ms);
#endif #endif

View File

@ -18,6 +18,8 @@
#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3 #define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 3
#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60 #define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60
#define FIX_SEG_DEFAULT_MAX_WAIT 30
enum bindll { unwanted=0, no, prefer, force }; enum bindll { unwanted=0, no, prefer, force };
#define MAX_BINDS 32 #define MAX_BINDS 32
@ -102,13 +104,14 @@ struct params_s
char connect_bind6_ifname[IF_NAMESIZE]; char connect_bind6_ifname[IF_NAMESIZE];
uint8_t proxy_type; uint8_t proxy_type;
unsigned int fix_seg;
bool fix_seg_avail;
bool no_resolve; bool no_resolve;
bool skip_nodelay; bool skip_nodelay;
bool fix_seg;
bool droproot; bool droproot;
bool daemon;
uid_t uid; uid_t uid;
gid_t gid; gid_t gid;
bool daemon;
char pidfile[256]; char pidfile[256];
int maxconn,resolver_threads,maxfiles,max_orphan_time; int maxconn,resolver_threads,maxfiles,max_orphan_time;
int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf; int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf;

View File

@ -171,7 +171,7 @@ static void exithelp(void)
" --enable-pf\t\t\t\t; enable PF redirector support. required in FreeBSD when used with PF firewall.\n" " --enable-pf\t\t\t\t; enable PF redirector support. required in FreeBSD when used with PF firewall.\n"
#endif #endif
#if defined(__linux__) #if defined(__linux__)
" --fix-seg\t\t\t\t; fix segmentation failures at the cost of possible slowdown\n" " --fix-seg=<int>\t\t\t; fix segmentation failures at the cost of possible slowdown. wait up to N msec (default %u)\n"
#endif #endif
" --debug=0|1|2|syslog|@<filename>\t; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\n" " --debug=0|1|2|syslog|@<filename>\t; 1 and 2 means log to console and set debug level. for other targets use --debug-level.\n"
" --debug-level=0|1|2\t\t\t; specify debug level\n" " --debug-level=0|1|2\t\t\t; specify debug level\n"
@ -192,7 +192,7 @@ static void exithelp(void)
"\nTAMPER:\n" "\nTAMPER:\n"
" --split-pos=N|-N|marker+N|marker-N\t; comma separated list of split positions\n" " --split-pos=N|-N|marker+N|marker-N\t; comma separated list of split positions\n"
"\t\t\t\t\t; markers: method,host,endhost,sld,endsld,midsld,sniext\n" "\t\t\t\t\t; markers: method,host,endhost,sld,endsld,midsld,sniext\n"
" --split-any-protocol\t\t\t; split not only http and https\n" " --split-any-protocol\t\t\t; split not only http and TLS\n"
#if defined(BSD) && !defined(__APPLE__) #if defined(BSD) && !defined(__APPLE__)
" --disorder[=http|tls]\t\t\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\n" " --disorder[=http|tls]\t\t\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\n"
#else #else
@ -218,6 +218,9 @@ static void exithelp(void)
" --tamper-cutoff=[n]<pos>\t\t; do not tamper anymore after specified outbound stream position. default is unlimited.\n", " --tamper-cutoff=[n]<pos>\t\t; do not tamper anymore after specified outbound stream position. default is unlimited.\n",
#if defined(__linux__) || defined(__APPLE__) #if defined(__linux__) || defined(__APPLE__)
DEFAULT_TCP_USER_TIMEOUT_LOCAL,DEFAULT_TCP_USER_TIMEOUT_REMOTE, DEFAULT_TCP_USER_TIMEOUT_LOCAL,DEFAULT_TCP_USER_TIMEOUT_REMOTE,
#endif
#ifdef __linux__
FIX_SEG_DEFAULT_MAX_WAIT,
#endif #endif
HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT
); );
@ -535,6 +538,8 @@ void parse_params(int argc, char *argv[])
params.pf_enable = true; // OpenBSD and MacOS have no other choice params.pf_enable = true; // OpenBSD and MacOS have no other choice
#endif #endif
params.fix_seg_avail = socket_supports_notsent();
LIST_INIT(&params.hostlists); LIST_INIT(&params.hostlists);
LIST_INIT(&params.ipsets); LIST_INIT(&params.ipsets);
@ -638,7 +643,7 @@ void parse_params(int argc, char *argv[])
{ "local-tcp-user-timeout",required_argument,0,0 }, // optidx=62 { "local-tcp-user-timeout",required_argument,0,0 }, // optidx=62
{ "remote-tcp-user-timeout",required_argument,0,0 }, // optidx=63 { "remote-tcp-user-timeout",required_argument,0,0 }, // optidx=63
{ "mss",required_argument,0,0 }, // optidx=64 { "mss",required_argument,0,0 }, // optidx=64
{ "fix-seg",no_argument,0,0 }, // optidx=65 { "fix-seg",optional_argument,0,0 }, // optidx=65
#ifdef SPLICE_PRESENT #ifdef SPLICE_PRESENT
{ "nosplice",no_argument,0,0 }, // optidx=66 { "nosplice",no_argument,0,0 }, // optidx=66
#endif #endif
@ -1233,7 +1238,23 @@ void parse_params(int argc, char *argv[])
} }
break; break;
case 65: /* fix-seg */ case 65: /* fix-seg */
params.fix_seg = true; if (!params.fix_seg_avail)
{
DLOG_ERR("--fix-seg is supported since kernel 4.6\n");
exit_clean(1);
}
if (optarg)
{
i = atoi(optarg);
if (i < 0 || i > 1000)
{
DLOG_ERR("fix_seg value must be within 0..1000\n");
exit_clean(1);
}
params.fix_seg = i;
}
else
params.fix_seg = FIX_SEG_DEFAULT_MAX_WAIT;
break; break;
#ifdef SPLICE_PRESENT #ifdef SPLICE_PRESENT
case 66: /* nosplice */ case 66: /* nosplice */

View File

@ -1118,8 +1118,24 @@ static ssize_t send_oob(int fd, uint8_t *buf, size_t len, int ttl, bool oob, uin
} }
static unsigned int segfail_count=0;
static time_t segfail_report_time=0;
static void report_segfail(void)
{
time_t now = time(NULL);
segfail_count++;
if (now==segfail_report_time)
VPRINT("WARNING ! segmentation failed. total fails : %u\n", segfail_count);
else
{
DLOG_ERR("WARNING ! segmentation failed. total fails : %u\n", segfail_count);
segfail_report_time = now;
}
}
#define RD_BLOCK_SIZE 65536 #define RD_BLOCK_SIZE 65536
#define MAX_WASTE (1024*1024) #define MAX_WASTE (1024*1024)
static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32_t evt) static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32_t evt)
{ {
int numbytes; int numbytes;
@ -1231,6 +1247,25 @@ static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32
bApplyDisorder = !(i & 1) && i<multisplit_count && (split_flags & SPLIT_FLAG_DISORDER); bApplyDisorder = !(i & 1) && i<multisplit_count && (split_flags & SPLIT_FLAG_DISORDER);
bApplyOOB = i==0 && (split_flags & SPLIT_FLAG_OOB); bApplyOOB = i==0 && (split_flags & SPLIT_FLAG_OOB);
len = to-from; len = to-from;
#ifdef __linux__
if (params.fix_seg_avail)
{
if (params.fix_seg)
{
unsigned int wasted;
bool bWaitOK = socket_wait_notsent(conn->partner->fd, params.fix_seg, &wasted);
if (wasted)
VPRINT("WARNING ! wasted %u ms to fix segmenation\n", wasted);
if (!bWaitOK)
report_segfail();
}
else
{
if (socket_has_notsent(conn->partner->fd))
report_segfail();
}
}
#endif
VPRINT("Sending multisplit part %d %zd-%zd (len %zd)%s%s : ", i+1, from, to, len, bApplyDisorder ? " with disorder" : "", bApplyOOB ? " with OOB" : ""); VPRINT("Sending multisplit part %d %zd-%zd (len %zd)%s%s : ", i+1, from, to, len, bApplyDisorder ? " with disorder" : "", bApplyOOB ? " with OOB" : "");
packet_debug(buf+from,len); packet_debug(buf+from,len);
wr = send_oob(conn->partner->fd, buf+from, len, bApplyDisorder, bApplyOOB, conn->track.dp ? conn->track.dp->oob_byte : 0); wr = send_oob(conn->partner->fd, buf+from, len, bApplyDisorder, bApplyOOB, conn->track.dp ? conn->track.dp->oob_byte : 0);
@ -1244,21 +1279,6 @@ static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32
if (wr>0) conn->partner->twr += wr; if (wr>0) conn->partner->twr += wr;
break; break;
} }
#ifdef __linux__
if (params.fix_seg)
{
unsigned int wasted;
if (!socket_wait_notsent(conn->partner->fd, 20, &wasted))
DLOG_ERR("WARNING ! segmentation failed\n");
if (wasted)
VPRINT("WARNING ! wasted %u ms to fix segmenation\n", wasted);
}
else
{
if (socket_has_notsent(conn->partner->fd))
DLOG_ERR("WARNING ! segmentation failed\n");
}
#endif
from = to; from = to;
} }
} }