From 46d31003e23baa5f7a3c747edd6e21b32280ace3 Mon Sep 17 00:00:00 2001 From: bol-van Date: Wed, 13 Nov 2024 22:05:06 +0300 Subject: [PATCH] tpws: multisplit --- tpws/helpers.c | 25 +++++- tpws/helpers.h | 3 + tpws/params.c | 16 ++++ tpws/params.h | 13 +-- tpws/protocol.c | 211 ++++++++++++++++++++++++++++++++++++++++------- tpws/protocol.h | 38 ++++++++- tpws/resolver.c | 2 +- tpws/tamper.c | 81 +++++++++--------- tpws/tamper.h | 8 +- tpws/tpws.c | 189 ++++++++++++++++++++++++++++++++++-------- tpws/tpws_conn.c | 110 +++++++++++++++--------- 11 files changed, 532 insertions(+), 164 deletions(-) diff --git a/tpws/helpers.c b/tpws/helpers.c index 481c69f..27425e4 100644 --- a/tpws/helpers.c +++ b/tpws/helpers.c @@ -1,8 +1,8 @@ #define _GNU_SOURCE -#include "helpers.h" #include #include +#include #include #include #include @@ -11,6 +11,29 @@ #include #include +#include "helpers.h" + +int unique_size_t(size_t *pu, int ct) +{ + int i, j, u; + for (i = j = 0; j < ct; i++) + { + u = pu[j++]; + for (; j < ct && pu[j] == u; j++); + pu[i] = u; + } + return i; +} +static int cmp_size_t(const void * a, const void * b) +{ + return *(size_t*)a < *(size_t*)b ? -1 : *(size_t*)a > *(size_t*)b; +} +void qsort_size_t(size_t *array,size_t ct) +{ + qsort(array,ct,sizeof(*array),cmp_size_t); +} + + void rtrim(char *s) { if (s) diff --git a/tpws/helpers.h b/tpws/helpers.h index 9d7d35c..e1bb919 100644 --- a/tpws/helpers.h +++ b/tpws/helpers.h @@ -15,6 +15,9 @@ typedef union struct sockaddr_in6 sa6; // size 28 } sockaddr_in46; +int unique_size_t(size_t *pu, int ct); +void qsort_size_t(size_t *array,size_t ct); + void rtrim(char *s); void replace_char(char *s, char from, char to); char *strncasestr(const char *s,const char *find, size_t slen); diff --git a/tpws/params.c b/tpws/params.c index 2aca979..ae57c6d 100644 --- a/tpws/params.c +++ b/tpws/params.c @@ -139,6 +139,22 @@ int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...) return 0; } +void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit) +{ + size_t k; + bool bcut = false; + if (size > limit) + { + size = limit; + bcut = true; + } + if (!size) return; + for (k = 0; k < size; k++) VPRINT("%02X ", data[k]); + VPRINT(bcut ? "... : " : ": "); + for (k = 0; k < size; k++) VPRINT("%c", data[k] >= 0x20 && data[k] <= 0x7F ? (char)data[k] : '.'); + if (bcut) VPRINT(" ..."); +} + struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head) { diff --git a/tpws/params.h b/tpws/params.h index d0104dc..3afa694 100644 --- a/tpws/params.h +++ b/tpws/params.h @@ -29,6 +29,8 @@ struct bind_s int bind_wait_ifup,bind_wait_ip,bind_wait_ip_ll; }; +#define MAX_SPLITS 16 + enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG }; struct desync_profile @@ -38,16 +40,16 @@ struct desync_profile bool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol, domcase; int hostpad; char hostspell[4]; - enum httpreqpos split_http_req; - enum tlspos tlsrec; - int tlsrec_pos; - enum tlspos split_tls; bool split_any_protocol; - int split_pos; bool disorder, disorder_http, disorder_tls; bool oob, oob_http, oob_tls; uint8_t oob_byte; + // multisplit + struct proto_pos splits[MAX_SPLITS]; + int split_count; + struct proto_pos tlsrec; + int mss; bool tamper_start_n,tamper_cutoff_n; @@ -140,6 +142,7 @@ int DLOG_CONDUP(const char *format, ...); int DLOG_ERR(const char *format, ...); int DLOG_PERROR(const char *s); int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...); +void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit); #define VPRINT(format, ...) DLOG(format, 1, ##__VA_ARGS__) #define DBGPRINT(format, ...) DLOG(format, 2, ##__VA_ARGS__) diff --git a/tpws/protocol.c b/tpws/protocol.c index 139ff21..edd2b5a 100644 --- a/tpws/protocol.c +++ b/tpws/protocol.c @@ -24,6 +24,133 @@ static bool FindNLD(const uint8_t *dom, size_t dlen, int level, const uint8_t ** return true; } +const char *l7proto_str(t_l7proto l7) +{ + switch(l7) + { + case HTTP: return "http"; + case TLS: return "tls"; + case QUIC: return "quic"; + case WIREGUARD: return "wireguard"; + case DHT: return "dht"; + default: return "unknown"; + } +} +bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7) +{ + return (l7proto==UNKNOWN && (filter_l7 & L7_PROTO_UNKNOWN)) || + (l7proto==HTTP && (filter_l7 & L7_PROTO_HTTP)) || + (l7proto==TLS && (filter_l7 & L7_PROTO_TLS)) || + (l7proto==QUIC && (filter_l7 & L7_PROTO_QUIC)) || + (l7proto==WIREGUARD && (filter_l7 & L7_PROTO_WIREGUARD)) || + (l7proto==DHT && (filter_l7 & L7_PROTO_DHT)); +} + +#define PM_ABS 0 +#define PM_HOST 1 +#define PM_HOST_END 2 +#define PM_HOST_SLD 3 +#define PM_HOST_MIDSLD 4 +#define PM_HOST_ENDSLD 5 +#define PM_HTTP_METHOD 6 +#define PM_SNI_EXT 7 +bool IsHostMarker(uint8_t posmarker) +{ + switch(posmarker) + { + case PM_HOST: + case PM_HOST_END: + case PM_HOST_SLD: + case PM_HOST_MIDSLD: + case PM_HOST_ENDSLD: + return true; + default: + return false; + } +} +const char *posmarker_name(uint8_t posmarker) +{ + switch(posmarker) + { + case PM_ABS: return "abs"; + case PM_HOST: return "host"; + case PM_HOST_END: return "endhost"; + case PM_HOST_SLD: return "sld"; + case PM_HOST_MIDSLD: return "midsld"; + case PM_HOST_ENDSLD: return "endsld"; + case PM_HTTP_METHOD: return "method"; + case PM_SNI_EXT: return "sniext"; + default: return "?"; + } +} + +static size_t CheckPos(size_t sz, ssize_t offset) +{ + return (offset>=0 && offsetmarker, sp->pos, data, sz); + case TLS: + return TLSPos(sp->marker, sp->pos, data, sz); + default: + return AnyProtoPos(sp->marker, sp->pos, data, sz); + } +} +void ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count) +{ + int i,j; + for(i=j=0;i='A' && *method<='Z') method++; - if (i<3 || *method!=' ') break; - return method-http-1; - case httpreqpos_host: - if (HttpFindHostConst(&host,http,sz) && (host-http+7)='A' && *p<='Z') p++; + if (i<3 || *p!=' ') break; + return CheckPos(sz,method-data+pos); + case PM_HOST: + case PM_HOST_END: + case PM_HOST_SLD: + case PM_HOST_MIDSLD: + case PM_HOST_ENDSLD: + if (HttpFindHostConst(&host,data,sz) && (host-data+7) #include +typedef enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT} t_l7proto; +#define L7_PROTO_HTTP 0x00000001 +#define L7_PROTO_TLS 0x00000002 +#define L7_PROTO_QUIC 0x00000004 +#define L7_PROTO_WIREGUARD 0x00000008 +#define L7_PROTO_DHT 0x00000010 +#define L7_PROTO_UNKNOWN 0x80000000 +const char *l7proto_str(t_l7proto l7); +bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7); + +// pos markers +#define PM_ABS 0 +#define PM_HOST 1 +#define PM_HOST_END 2 +#define PM_HOST_SLD 3 +#define PM_HOST_MIDSLD 4 +#define PM_HOST_ENDSLD 5 +#define PM_HTTP_METHOD 6 +#define PM_SNI_EXT 7 +struct proto_pos +{ + int16_t pos; + uint8_t marker; +}; +#define PROTO_POS_EMPTY(sp) ((sp)->marker==PM_ABS && (sp)->pos==0) +bool IsHostMarker(uint8_t posmarker); +const char *posmarker_name(uint8_t posmarker); +size_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); +size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); +size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz); +size_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp); +void ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count); + + extern const char *http_methods[9]; const char *HttpMethod(const uint8_t *data, size_t len); bool IsHttp(const uint8_t *data, size_t len); @@ -18,8 +52,6 @@ const char *HttpFind2ndLevelDomain(const char *host); int HttpReplyCode(const uint8_t *data, size_t len); // must be pre-checked by IsHttpReply bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host); -enum httpreqpos { httpreqpos_none = 0, httpreqpos_method, httpreqpos_host, httpreqpos_pos }; -size_t HttpPos(enum httpreqpos tpos_type, size_t hpos_pos, const uint8_t *http, size_t sz); uint16_t TLSRecordDataLen(const uint8_t *data); size_t TLSRecordLen(const uint8_t *data); @@ -29,5 +61,3 @@ bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t ** bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK); bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK); -enum tlspos { tlspos_none = 0, tlspos_sni, tlspos_sniext, tlspos_snisld, tlspos_pos }; -size_t TLSPos(enum tlspos tpos_type, size_t tpos_pos, const uint8_t *tls, size_t sz, uint8_t type); diff --git a/tpws/resolver.c b/tpws/resolver.c index 6fb25bb..1653756 100644 --- a/tpws/resolver.c +++ b/tpws/resolver.c @@ -221,7 +221,7 @@ bool resolver_init(int threads, int fd_signal_pipe) if (pthread_attr_init(&attr)) goto ex; // set minimum thread stack size - if (pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN>20480 ? PTHREAD_STACK_MIN : 20480)) + if (pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN>32768 ? PTHREAD_STACK_MIN : 32768)) { pthread_attr_destroy(&attr); goto ex; diff --git a/tpws/tamper.c b/tpws/tamper.c index a4cf434..b380902 100644 --- a/tpws/tamper.c +++ b/tpws/tamper.c @@ -8,22 +8,6 @@ #include "protocol.h" #include "helpers.h" -const char *l7proto_str(t_l7proto l7) -{ - switch(l7) - { - case HTTP: return "http"; - case TLS: return "tls"; - default: return "unknown"; - } -} -static bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7) -{ - return (l7proto==UNKNOWN && (filter_l7 & L7_PROTO_UNKNOWN)) || - (l7proto==HTTP && (filter_l7 & L7_PROTO_HTTP)) || - (l7proto==TLS && (filter_l7 & L7_PROTO_TLS)); -} - static bool dp_match(struct desync_profile *dp, const struct sockaddr *dest, const char *hostname, t_l7proto l7proto) { bool bHostlistsEmpty; @@ -87,11 +71,10 @@ void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest) // segment buffer has at least 5 extra bytes to extend data block -void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos, uint8_t *split_flags) +void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags) { uint8_t *p, *pp, *pHost = NULL; - size_t method_len = 0, pos; - size_t tpos, spos; + size_t method_len = 0, pos, tpos, orig_size=*size; const char *method; bool bHaveHost = false; char *pc, Host[256]; @@ -116,8 +99,8 @@ void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment, return; } - *split_pos=0; - *split_flags=0; + if (multisplit_count) *multisplit_count=0; + if (split_flags) *split_flags=0; if ((method = HttpMethod(segment,*size))) { @@ -193,7 +176,6 @@ void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment, return; } } - switch(l7proto) { case HTTP: @@ -325,22 +307,26 @@ void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment, pHost = NULL; // invalidate } } - *split_pos = HttpPos(ctrack->dp->split_http_req, ctrack->dp->split_pos, segment, *size); - if (ctrack->dp->disorder_http) *split_flags |= SPLIT_FLAG_DISORDER; - if (ctrack->dp->oob_http) *split_flags |= SPLIT_FLAG_OOB; + if (multisplit_pos) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count); + if (split_flags) + { + if (ctrack->dp->disorder_http) *split_flags |= SPLIT_FLAG_DISORDER; + if (ctrack->dp->oob_http) *split_flags |= SPLIT_FLAG_OOB; + } break; case TLS: - spos = TLSPos(ctrack->dp->split_tls, ctrack->dp->split_pos, segment, *size, 0); + if (multisplit_pos) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count); if ((5+*size)<=segment_buffer_size) { - tpos = TLSPos(ctrack->dp->tlsrec, ctrack->dp->tlsrec_pos+5, segment, *size, 0); + tpos = ResolvePos(segment, *size, l7proto, &ctrack->dp->tlsrec); if (tpos>5) { // construct 2 TLS records from one uint16_t l = pntoh16(segment+3); // length if (l>=2) { + int i; // length is checked in IsTLSClientHello and cannot exceed buffer size if ((tpos-5)>=l) tpos=5+1; VPRINT("making 2 TLS records at pos %zu\n",tpos); @@ -351,27 +337,40 @@ void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment, phton16(segment+tpos+3,l-(tpos-5)); phton16(segment+3,tpos-5); *size += 5; - // split pos present and it is not before tlsrec split. increase split pos by tlsrec header size (5 bytes) - if (spos && spos>=tpos) spos+=5; + // fix split positions after tlsrec. increase split pos by tlsrec header size (5 bytes) + if (multisplit_pos) + for(i=0;i<*multisplit_count;i++) + if (multisplit_pos[i]>tpos) multisplit_pos[i]+=5; } } } - - if (spos && spos < *size) - *split_pos = spos; - - if (ctrack->dp->disorder_tls) *split_flags |= SPLIT_FLAG_DISORDER; - if (ctrack->dp->oob_tls) *split_flags |= SPLIT_FLAG_OOB; - + if (split_flags) + { + if (ctrack->dp->disorder_tls) *split_flags |= SPLIT_FLAG_DISORDER; + if (ctrack->dp->oob_tls) *split_flags |= SPLIT_FLAG_OOB; + } break; default: - if (ctrack->dp->split_any_protocol && ctrack->dp->split_pos < *size) - *split_pos = ctrack->dp->split_pos; + if (multisplit_pos && ctrack->dp->split_any_protocol) + ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count); + } + + if (split_flags) + { + if (ctrack->dp->disorder) *split_flags |= SPLIT_FLAG_DISORDER; + if (ctrack->dp->oob) *split_flags |= SPLIT_FLAG_OOB; + } + if (orig_size!=*size) + { + VPRINT("segment size changed: %zu -> %zu\n", orig_size, *size); + } + if (params.debug && multisplit_count && *multisplit_count) + { + VPRINT("multisplit pos: "); + for (int i=0;i<*multisplit_count;i++) VPRINT("%zu ",multisplit_pos[i]); + VPRINT("\n"); } - - if (ctrack->dp->disorder) *split_flags |= SPLIT_FLAG_DISORDER; - if (ctrack->dp->oob) *split_flags |= SPLIT_FLAG_OOB; } static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname, const char *client_ip_port, t_l7proto l7proto) diff --git a/tpws/tamper.h b/tpws/tamper.h index 0cafee7..b34fbf6 100644 --- a/tpws/tamper.h +++ b/tpws/tamper.h @@ -9,12 +9,6 @@ #define SPLIT_FLAG_DISORDER 0x01 #define SPLIT_FLAG_OOB 0x02 -typedef enum {UNKNOWN=0, HTTP, TLS} t_l7proto; -#define L7_PROTO_HTTP 1 -#define L7_PROTO_TLS 2 -#define L7_PROTO_UNKNOWN 0x80000000 -const char *l7proto_str(t_l7proto l7); - typedef struct { // common state @@ -28,7 +22,7 @@ typedef struct void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest); -void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos, uint8_t *split_flags); +void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags); void tamper_in(t_ctrack *ctrack, const struct sockaddr *client, uint8_t *segment,size_t segment_buffer_size,size_t *size); // connection reset by remote leg void rst_in(t_ctrack *ctrack, const struct sockaddr *client); diff --git a/tpws/tpws.c b/tpws/tpws.c index ba9031c..e24b11a 100644 --- a/tpws/tpws.c +++ b/tpws/tpws.c @@ -182,9 +182,8 @@ static void exithelp(void) " --hostlist-auto-fail-time=\t; all failed attemps must be within these seconds (default : %d)\n" " --hostlist-auto-debug=\t; debug auto hostlist positives\n" "\nTAMPER:\n" - " --split-http-req=method|host\t\t; split at specified logical part of plain http request\n" - " --split-tls=sni|sniext|snisld\t\t; split at specified logical part of TLS ClientHello\n" - " --split-pos=\t\t; split at specified pos. split-http-req or split-tls take precedence for http.\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" " --split-any-protocol\t\t\t; split not only http and https\n" #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" @@ -203,8 +202,7 @@ static void exithelp(void) " --methodspace\t\t\t\t; add extra space after method\n" " --methodeol\t\t\t\t; add end-of-line before method\n" " --unixeol\t\t\t\t; replace 0D0A to 0A\n" - " --tlsrec=sni|sniext|snisld\t\t; make 2 TLS records. split at specified logical part. don't split if SNI is not present\n" - " --tlsrec-pos=\t\t\t; make 2 TLS records. split at specified pos\n" + " --tlsrec=N|-N|marker+N|marker-N\t; make 2 TLS records. split records at specified position.\n" #ifdef __linux__ " --mss=\t\t\t\t; set client MSS. forces server to split messages but significantly decreases speed !\n" #endif @@ -276,29 +274,131 @@ void save_default_ttl(void) } } -bool parse_httpreqpos(const char *s, enum httpreqpos *pos) +static bool parse_httpreqpos(const char *s, struct proto_pos *sp) { if (!strcmp(s, "method")) - *pos = httpreqpos_method; + { + sp->marker = PM_HTTP_METHOD; + sp->pos=2; + } else if (!strcmp(s, "host")) - *pos = httpreqpos_host; + { + sp->marker = PM_HOST; + sp->pos=1; + } else return false; return true; } -bool parse_tlspos(const char *s, enum tlspos *pos) +static bool parse_tlspos(const char *s, struct proto_pos *sp) { if (!strcmp(s, "sni")) - *pos = tlspos_sni; + { + sp->marker = PM_HOST; + sp->pos=1; + } else if (!strcmp(s, "sniext")) - *pos = tlspos_sniext; + { + sp->marker = PM_SNI_EXT; + sp->pos=1; + } else if (!strcmp(s, "snisld")) - *pos = tlspos_snisld; + { + sp->marker = PM_HOST_MIDSLD; + sp->pos=0; + } else return false; return true; } +static bool parse_int16(const char *p, int16_t *v) +{ + if (*p=='+' || *p=='-' || *p>='0' && *p<='9') + { + int i = atoi(p); + *v = (int16_t)i; + return *v==i; // check overflow + } + return false; +} +static bool parse_posmarker(const char *opt, uint8_t *posmarker) +{ + if (!strcmp(opt,"host")) + *posmarker = PM_HOST; + else if (!strcmp(opt,"endhost")) + *posmarker = PM_HOST_END; + else if (!strcmp(opt,"sld")) + *posmarker = PM_HOST_SLD; + else if (!strcmp(opt,"midsld")) + *posmarker = PM_HOST_MIDSLD; + else if (!strcmp(opt,"endsld")) + *posmarker = PM_HOST_ENDSLD; + else if (!strcmp(opt,"method")) + *posmarker = PM_HTTP_METHOD; + else if (!strcmp(opt,"sniext")) + *posmarker = PM_SNI_EXT; + else + return false; + return true; +} +static bool parse_split_pos(char *opt, struct proto_pos *split) +{ + if (parse_int16(opt,&split->pos)) + { + split->marker = PM_ABS; + return !!split->pos; + } + else + { + char c,*p=opt; + bool b; + + for (; *opt && *opt!='+' && *opt!='-'; opt++); + c=*opt; *opt=0; + b=parse_posmarker(p,&split->marker); + *opt=c; + if (!b) return false; + if (*opt) + return parse_int16(opt,&split->pos); + else + split->pos = 0; + } + return true; +} +static bool parse_split_pos_list(char *opt, struct proto_pos *splits, int splits_size, int *split_count) +{ + char c,*e,*p; + + for (p=opt, *split_count=0 ; p && *split_countdp; + for(x=0;xsplit_count;x++) + VPRINT("profile %d multisplit %s %d\n",dp->n,posmarker_name(dp->splits[x].marker),dp->splits[x].pos); + if (!PROTO_POS_EMPTY(&dp->tlsrec)) + VPRINT("profile %d tlsrec %s %d\n",dp->n,posmarker_name(dp->tlsrec.marker),dp->tlsrec.pos); + } +} + static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) { char *e,*p,c; @@ -682,29 +782,45 @@ void parse_params(int argc, char *argv[]) params.tamper = true; break; case 23: /* split-http-req */ - if (!parse_httpreqpos(optarg, &dp->split_http_req)) + DLOG_CONDUP("WARNING ! --split-http-req is deprecated. use --split-pos with markers.\n",MAX_SPLITS); + if (dp->split_count>=MAX_SPLITS) + { + DLOG_ERR("Too much splits. max splits: %u\n",MAX_SPLITS); + exit_clean(1); + } + if (!parse_httpreqpos(optarg, dp->splits + dp->split_count)) { DLOG_ERR("Invalid argument for split-http-req\n"); exit_clean(1); } + dp->split_count++; params.tamper = true; break; case 24: /* split-tls */ - if (!parse_tlspos(optarg, &dp->split_tls)) + // obsolete arg + DLOG_CONDUP("WARNING ! --split-tls is deprecated. use --split-pos with markers.\n",MAX_SPLITS); + if (dp->split_count>=MAX_SPLITS) + { + DLOG_ERR("Too much splits. max splits: %u\n",MAX_SPLITS); + exit_clean(1); + } + if (!parse_tlspos(optarg, dp->splits + dp->split_count)) { DLOG_ERR("Invalid argument for split-tls\n"); exit_clean(1); } + dp->split_count++; params.tamper = true; break; case 25: /* split-pos */ - i = atoi(optarg); - if (i>0) - dp->split_pos = i; - else { - DLOG_ERR("Invalid argument for split-pos\n"); - exit_clean(1); + int ct; + if (!parse_split_pos_list(optarg,dp->splits+dp->split_count,MAX_SPLITS-dp->split_count,&ct)) + { + DLOG_ERR("could not parse split pos list or too much positions (before parsing - %u, max - %u) : %s\n",dp->split_count,MAX_SPLITS,optarg); + exit_clean(1); + } + dp->split_count += ct; } params.tamper = true; break; @@ -724,7 +840,6 @@ void parse_params(int argc, char *argv[]) } else dp->disorder = true; - save_default_ttl(); break; case 28: /* oob */ if (optarg) @@ -770,7 +885,7 @@ void parse_params(int argc, char *argv[]) params.tamper = true; break; case 34: /* tlsrec */ - if (!parse_tlspos(optarg, &dp->tlsrec)) + if (!parse_split_pos(optarg, &dp->tlsrec) && !parse_tlspos(optarg, &dp->tlsrec)) { DLOG_ERR("Invalid argument for tlsrec\n"); exit_clean(1); @@ -778,9 +893,11 @@ void parse_params(int argc, char *argv[]) params.tamper = true; break; case 35: /* tlsrec-pos */ - if ((dp->tlsrec_pos = atoi(optarg))>0) - dp->tlsrec = tlspos_pos; - else + // obsolete arg + i = atoi(optarg); + dp->tlsrec.marker = PM_ABS; + dp->tlsrec.pos = (int16_t)i; + if (!dp->tlsrec.pos || i!=dp->tlsrec.pos) { DLOG_ERR("Invalid argument for tlsrec-pos\n"); exit_clean(1); @@ -823,8 +940,6 @@ void parse_params(int argc, char *argv[]) DLOG_ERR("gzipped auto hostlists are not supported\n"); exit_clean(1); } - if (params.droproot && chown(optarg, params.uid, -1)) - DLOG_ERR("could not chown %s. auto hostlist file may not be writable after privilege drop\n", optarg); } if (!(dp->hostlist_auto=RegisterHostlist(dp, false, optarg))) { @@ -858,8 +973,6 @@ void parse_params(int argc, char *argv[]) exit_clean(1); } fclose(F); - if (params.droproot && chown(optarg, params.uid, -1)) - DLOG_ERR("could not chown %s. auto hostlist debug log may not be writable after privilege drop\n", optarg); strncpy(params.hostlist_auto_debuglog, optarg, sizeof(params.hostlist_auto_debuglog)); params.hostlist_auto_debuglog[sizeof(params.hostlist_auto_debuglog) - 1] = '\0'; } @@ -881,8 +994,6 @@ void parse_params(int argc, char *argv[]) fprintf(stderr, "cannot create %s\n", params.debug_logfile); exit_clean(1); } - if (params.droproot && chown(params.debug_logfile, params.uid, -1)) - fprintf(stderr, "could not chown %s. log file may not be writable after privilege drop\n", params.debug_logfile); if (!params.debug) params.debug = 1; params.debug_target = LOG_TARGET_FILE; } @@ -1054,7 +1165,7 @@ void parse_params(int argc, char *argv[]) } params.tamper = true; break; - + #if defined(__FreeBSD__) case 62: /* enable-pf */ params.pf_enable = true; @@ -1117,16 +1228,21 @@ void parse_params(int argc, char *argv[]) DLOG_CONDUP("we have %d user defined desync profile(s) and default low priority profile 0\n",desync_profile_count); + save_default_ttl(); + if (params.debug_target == LOG_TARGET_FILE && params.droproot && chown(params.debug_logfile, params.uid, -1)) + fprintf(stderr, "could not chown %s. log file may not be writable after privilege drop\n", params.debug_logfile); + if (params.droproot && *params.hostlist_auto_debuglog && chown(params.hostlist_auto_debuglog, params.uid, -1)) + DLOG_ERR("could not chown %s. auto hostlist debug log may not be writable after privilege drop\n", params.hostlist_auto_debuglog); LIST_FOREACH(dpl, ¶ms.desync_profiles, next) { dp = &dpl->dp; - if (dp->split_tls==tlspos_none && dp->split_pos) dp->split_tls=tlspos_pos; - if (dp->split_http_req==httpreqpos_none && dp->split_pos) dp->split_http_req=httpreqpos_pos; - if (params.skip_nodelay && (dp->split_tls || dp->split_http_req || dp->split_pos)) + if (params.skip_nodelay && dp->split_count) { DLOG_ERR("Cannot split with --skip-nodelay\n"); exit_clean(1); } + if (params.droproot && dp->hostlist_auto && chown(dp->hostlist_auto->filename, params.uid, -1)) + DLOG_ERR("could not chown %s. auto hostlist file may not be writable after privilege drop\n", dp->hostlist_auto->filename); } if (!LoadAllHostLists()) @@ -1143,6 +1259,9 @@ void parse_params(int argc, char *argv[]) VPRINT("\nlists summary:\n"); HostlistsDebug(); IpsetsDebug(); + + VPRINT("\nsplits summary:\n"); + SplitDebug(); VPRINT("\n"); #ifndef __OpenBSD__ diff --git a/tpws/tpws_conn.c b/tpws/tpws_conn.c index c1d335e..b9f7ac3 100644 --- a/tpws/tpws_conn.c +++ b/tpws/tpws_conn.c @@ -24,6 +24,7 @@ #include "helpers.h" #include "hostlist.h" +#define PKTDATA_MAXDUMP 32 // keep separate legs counter. counting every time thousands of legs can consume cpu static int legs_local, legs_remote; @@ -92,26 +93,43 @@ static bool socks_send_rep_errno(uint8_t ver, int fd, int errn) return ver==5 ? socks5_send_rep_errno(fd,errn) : socks4_send_rep_errno(fd, errn); } +static void packet_debug(const uint8_t *data, size_t sz) +{ + hexdump_limited_dlog(data, sz, PKTDATA_MAXDUMP); VPRINT("\n"); +} + + +static bool cork(int fd, int enable) +{ +#ifdef __linux__ + int e = errno; + if (setsockopt(fd, SOL_TCP, TCP_CORK, &enable, sizeof(enable))<0) + { + DLOG_PERROR("setsockopt (TCP_CORK)"); + errno = e; + return false; + } + errno = e; +#endif + return true; +} ssize_t send_with_ttl(int fd, const void *buf, size_t len, int flags, int ttl) { - ssize_t wr; + ssize_t wr; - if (ttl) + if (!params.skip_nodelay) { + int ttl_apply = ttl ? ttl : params.ttl_default; DBGPRINT("send_with_ttl %d fd=%d\n",ttl,fd); - if (!set_ttl_hl(fd, ttl)) + if (!set_ttl_hl(fd, ttl_apply)) //DLOG_ERR("could not set ttl %d to fd=%d\n",ttl,fd); - DLOG_ERR("could not set ttl %d to fd=%d\n",ttl,fd); + DLOG_ERR("could not set ttl %d to fd=%d\n",ttl_apply,fd); + cork(fd,true); } wr = send(fd, buf, len, flags); - if (ttl) - { - int e=errno; - if (!set_ttl_hl(fd, params.ttl_default)) - DLOG_ERR("could not set ttl %d to fd=%d\n",params.ttl_default,fd); - errno=e; - } + if (!params.skip_nodelay) + cork(fd,false); return wr; } @@ -308,19 +326,18 @@ bool set_socket_buffers(int fd, int rcvbuf, int sndbuf) if (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) <0) { DLOG_PERROR("setsockopt (SO_RCVBUF)"); - close(fd); return false; } if (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) <0) { DLOG_PERROR("setsockopt (SO_SNDBUF)"); - close(fd); return false; } dbgprint_socket_buffers(fd); return true; } + static bool proxy_remote_conn_ack(tproxy_conn_t *conn, int sock_err) { // if proxy mode acknowledge connection request @@ -393,7 +410,10 @@ static int connect_remote(const struct sockaddr *remote_addr, int mss) return -1; } if (!set_socket_buffers(remote_fd, params.remote_rcvbuf, params.remote_sndbuf)) + { + close(remote_fd); return -1; + } if (!set_keepalive(remote_fd)) { DLOG_PERROR("set_keepalive"); @@ -1068,9 +1088,9 @@ static bool in_tamper_out_range(tproxy_conn_t *conn) } -static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_size, size_t *segment_size, size_t *split_pos, uint8_t *split_flags) +static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_size, size_t *segment_size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags) { - *split_pos=0; + if (multisplit_count) *multisplit_count=0; if (params.tamper) { if (conn->remote) @@ -1081,26 +1101,26 @@ static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_ else { if (in_tamper_out_range(conn)) - tamper_out(&conn->track,(struct sockaddr*)&conn->dest,segment,segment_buffer_size,segment_size,split_pos,split_flags); + tamper_out(&conn->track,(struct sockaddr*)&conn->dest,segment,segment_buffer_size,segment_size,multisplit_pos,multisplit_count,split_flags); } } } // buffer must have at least one extra byte for OOB -static ssize_t send_or_buffer_oob(send_buffer_t *sb, int fd, uint8_t *buf, size_t len, int ttl, bool oob, uint8_t oob_byte) +static ssize_t send_oob(int fd, uint8_t *buf, size_t len, int ttl, bool oob, uint8_t oob_byte) { ssize_t wr; if (oob) { - VPRINT("Sending OOB byte %02X\n", oob_byte); uint8_t oob_save; oob_save = buf[len]; buf[len] = oob_byte; - wr = send_or_buffer(sb, fd, buf, len+1, MSG_OOB, ttl); + wr = send_with_ttl(fd, buf, len+1, MSG_OOB, ttl); buf[len] = oob_save; + if (wr<0 && errno==EAGAIN) wr=0; } else - wr = send_or_buffer(sb, fd, buf, len, 0, ttl); + wr = send_with_ttl(fd, buf, len, 0, ttl); return wr; } @@ -1186,36 +1206,53 @@ static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32 #endif { // incoming data from local leg - uint8_t buf[RD_BLOCK_SIZE + 5]; + uint8_t buf[RD_BLOCK_SIZE + 6]; rd = recv(conn->fd, buf, RD_BLOCK_SIZE, MSG_DONTWAIT); DBGPRINT("recv fd=%d rd=%zd err=%d\n",conn->fd, rd,errno); if (rd<0 && errno==EAGAIN) rd=0; if (rd>0) { - size_t split_pos; + size_t multisplit_pos[MAX_SPLITS]; + int multisplit_count; + uint8_t split_flags; bs = rd; // tamper needs to know stream position of the block start - tamper(conn, buf, sizeof(buf), &bs, &split_pos, &split_flags); + tamper(conn, buf, sizeof(buf), &bs, multisplit_pos, &multisplit_count, &split_flags); // increase after tamper conn->tnrd++; conn->trd+=rd; - if (split_pos && bspartner->wr_buf, conn->partner->fd, buf, split_pos, !!(split_flags & SPLIT_FLAG_DISORDER), !!(split_flags & SPLIT_FLAG_OOB), conn->track.dp ? conn->track.dp->oob_byte : 0); - DBGPRINT("send_or_buffer(1) fd=%d wr=%zd err=%d\n",conn->partner->fd,wr,errno); - if (wr >= 0) + ssize_t from,to,len; + int i; + bool bApplyDisorder, bApplyOOB; + for (i=0,from=0;i<=multisplit_count;i++) { + to = i==multisplit_count ? bs : multisplit_pos[i]; + + bApplyDisorder = !(i & 1) && ipartner->fd, buf+from, len, bApplyDisorder, bApplyOOB, conn->track.dp ? conn->track.dp->oob_byte : 0); + if (wr<0) break; conn->partner->twr += wr; - wr = send_or_buffer(conn->partner->wr_buf + 1, conn->partner->fd, buf + split_pos, bs - split_pos, 0, 0); - DBGPRINT("send_or_buffer(2) fd=%d wr=%zd err=%d\n",conn->partner->fd,wr,errno); - if (wr>0) conn->partner->twr += wr; + if (wrpartner->wr_buf, conn->partner->fd, buf+from, bs-from, 0, 0); + if (wr>0) conn->partner->twr += wr; + break; + } + + from = to; } } else @@ -1279,7 +1316,7 @@ static bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number) DBGPRINT("read_all_and_buffer(%d) numbytes=%d\n",buffer_number,numbytes); if (numbytes>0) { - if (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes, 5, 0, 0)) + if (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes, 6, 0, 0)) { ssize_t rd = recv(conn->fd, conn->partner->wr_buf[buffer_number].data, numbytes, MSG_DONTWAIT); if (rd>0) @@ -1289,10 +1326,7 @@ static bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number) conn->partner->bFlowOut = true; - size_t split_pos; - uint8_t split_flags; - - tamper(conn, conn->partner->wr_buf[buffer_number].data, numbytes+5, &conn->partner->wr_buf[buffer_number].len, &split_pos, &split_flags); + tamper(conn, conn->partner->wr_buf[buffer_number].data, numbytes+6, &conn->partner->wr_buf[buffer_number].len, NULL, NULL, NULL); if (epoll_update_flow(conn->partner)) return true; @@ -1369,7 +1403,7 @@ static bool handle_resolve_pipe(tproxy_conn_t **conn, struct tailhead *conn_list else if (rd!=sizeof(void*)) { // partial pointer read is FATAL. in any case it will cause pointer corruption and coredump - DLOG_ERR("resolve_pipe not full read %zu\n",rd); + DLOG_ERR("resolve_pipe not full read %zd\n",rd); exit(1000); } b = resolve_complete(ri, conn_list);