From d4a7eef17e3ffae3a18c4586fc8f621236be41e8 Mon Sep 17 00:00:00 2001 From: bol-van Date: Thu, 19 Sep 2024 21:06:58 +0300 Subject: [PATCH] tpws: multi-strategy --- tpws/helpers.c | 27 +++- tpws/helpers.h | 3 + tpws/hostlist.c | 46 ++++--- tpws/hostlist.h | 3 +- tpws/params.c | 53 ++++++++ tpws/params.h | 85 ++++++++----- tpws/protocol.c | 39 +++++- tpws/tamper.c | 316 +++++++++++++++++++++++++++++------------------ tpws/tamper.h | 8 +- tpws/tpws.c | 251 ++++++++++++++++++++++++------------- tpws/tpws_conn.c | 115 +++++++++-------- tpws/tpws_conn.h | 1 + 12 files changed, 624 insertions(+), 323 deletions(-) diff --git a/tpws/helpers.c b/tpws/helpers.c index 85954de..30c6f03 100644 --- a/tpws/helpers.c +++ b/tpws/helpers.c @@ -164,6 +164,21 @@ bool saconvmapped(struct sockaddr_storage *a) return false; } +void sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa) +{ + switch(sa->sa_family) + { + case AF_INET: + memcpy(sa_dest,sa,sizeof(struct sockaddr_in)); + break; + case AF_INET6: + memcpy(sa_dest,sa,sizeof(struct sockaddr_in6)); + break; + default: + sa_dest->ss_family = 0; + } +} + bool is_localnet(const struct sockaddr *a) { // match 127.0.0.0/8, 0.0.0.0, ::1, ::0, :ffff:127.0.0.0/104, :ffff:0.0.0.0 @@ -243,7 +258,7 @@ bool pf_parse(const char *s, port_filter *pf) unsigned int v1,v2; if (!s) return false; - if (*s=='~') + if (*s=='~') { pf->neg=true; s++; @@ -252,16 +267,22 @@ bool pf_parse(const char *s, port_filter *pf) pf->neg=false; if (sscanf(s,"%u-%u",&v1,&v2)==2) { - if (!v1 || v1>65535 || v2>65535 || v1>v2) return false; + if (v1>65535 || v2>65535 || v1>v2) return false; pf->from=(uint16_t)v1; pf->to=(uint16_t)v2; } else if (sscanf(s,"%u",&v1)==1) { - if (!v1 || v1>65535) return false; + if (v1>65535) return false; pf->to=pf->from=(uint16_t)v1; } else return false; + // deny all case + if (!pf->from && !pf->to) pf->neg=true; return true; } +bool pf_is_empty(const port_filter *pf) +{ + return !pf->neg && !pf->from && !pf->to; +} diff --git a/tpws/helpers.h b/tpws/helpers.h index f1383cb..17ace35 100644 --- a/tpws/helpers.h +++ b/tpws/helpers.h @@ -25,6 +25,8 @@ uint16_t saport(const struct sockaddr *sa); // true = was converted bool saconvmapped(struct sockaddr_storage *a); +void sacopy(struct sockaddr_storage *sa_dest, const struct sockaddr *sa); + bool is_localnet(const struct sockaddr *a); bool is_linklocal(const struct sockaddr_in6* a); bool is_private6(const struct sockaddr_in6* a); @@ -55,6 +57,7 @@ typedef struct } port_filter; bool pf_in_range(uint16_t port, const port_filter *pf); bool pf_parse(const char *s, port_filter *pf); +bool pf_is_empty(const port_filter *pf); #ifndef IN_LOOPBACK #define IN_LOOPBACK(a) ((((uint32_t) (a)) & 0xff000000) == 0x7f000000) diff --git a/tpws/hostlist.c b/tpws/hostlist.c index f4d9d2b..7a8b8b4 100644 --- a/tpws/hostlist.c +++ b/tpws/hostlist.c @@ -154,35 +154,53 @@ static bool HostlistCheck_(strpool *hostlist, strpool *hostlist_exclude, const c return true; } -// return : true = apply fooling, false = do not apply -bool HostlistCheck(const char *host, bool *excluded) +static bool LoadIncludeHostListsForProfile(struct desync_profile *dp) { - if (*params.hostlist_auto_filename) + if (!LoadHostLists(&dp->hostlist, &dp->hostlist_files)) + return false; + if (*dp->hostlist_auto_filename) { - time_t t = file_mod_time(params.hostlist_auto_filename); - if (t!=params.hostlist_auto_mod_time) + dp->hostlist_auto_mod_time = file_mod_time(dp->hostlist_auto_filename); + NonEmptyHostlist(&dp->hostlist); + } + return true; +} + +// return : true = apply fooling, false = do not apply +bool HostlistCheck(struct desync_profile *dp, const char *host, bool *excluded) +{ + VPRINT("* Hostlist check for profile %d\n",dp->n); + if (*dp->hostlist_auto_filename) + { + time_t t = file_mod_time(dp->hostlist_auto_filename); + if (t!=dp->hostlist_auto_mod_time) { - DLOG_CONDUP("Autohostlist was modified by another process. Reloading include hostslist.\n"); - if (!LoadIncludeHostLists()) + DLOG_CONDUP("Autohostlist '%s' from profile %d was modified. Reloading include hostlists for this profile.\n",dp->hostlist_auto_filename, dp->n); + if (!LoadIncludeHostListsForProfile(dp)) { // what will we do without hostlist ?? sure, gonna die exit(1); } - params.hostlist_auto_mod_time = t; + dp->hostlist_auto_mod_time = t; + NonEmptyHostlist(&dp->hostlist); } } - return HostlistCheck_(params.hostlist, params.hostlist_exclude, host, excluded); + return HostlistCheck_(dp->hostlist, dp->hostlist_exclude, host, excluded); } bool LoadIncludeHostLists() { - if (!LoadHostLists(¶ms.hostlist, ¶ms.hostlist_files)) - return false; - if (*params.hostlist_auto_filename) - params.hostlist_auto_mod_time = file_mod_time(params.hostlist_auto_filename); + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + if (!LoadIncludeHostListsForProfile(&dpl->dp)) + return false; return true; } bool LoadExcludeHostLists() { - return LoadHostLists(¶ms.hostlist_exclude, ¶ms.hostlist_exclude_files); + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + if (!LoadHostLists(&dpl->dp.hostlist_exclude, &dpl->dp.hostlist_exclude_files)) + return false; + return true; } diff --git a/tpws/hostlist.h b/tpws/hostlist.h index ba0e34b..0bdb94a 100644 --- a/tpws/hostlist.h +++ b/tpws/hostlist.h @@ -2,6 +2,7 @@ #include #include "pools.h" +#include "params.h" bool AppendHostList(strpool **hostlist, char *filename); bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list); @@ -10,4 +11,4 @@ bool LoadExcludeHostLists(); bool NonEmptyHostlist(strpool **hostlist); bool SearchHostList(strpool *hostlist, const char *host); // return : true = apply fooling, false = do not apply -bool HostlistCheck(const char *host, bool *excluded); \ No newline at end of file +bool HostlistCheck(struct desync_profile *dp,const char *host, bool *excluded); \ No newline at end of file diff --git a/tpws/params.c b/tpws/params.c index f2741aa..d31aac0 100644 --- a/tpws/params.c +++ b/tpws/params.c @@ -138,3 +138,56 @@ int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...) else return 0; } + + +struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head) +{ + struct desync_profile_list *entry = calloc(1,sizeof(struct desync_profile_list)); + if (!entry) return NULL; + + LIST_INIT(&entry->dp.hostlist_files); + LIST_INIT(&entry->dp.hostlist_exclude_files); + entry->dp.filter_ipv4 = entry->dp.filter_ipv6 = true; + + memcpy(entry->dp.hostspell, "host", 4); // default hostspell + entry->dp.hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT; + entry->dp.hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT; + + // add to the tail + struct desync_profile_list *dpn,*dpl=LIST_FIRST(¶ms.desync_profiles); + if (dpl) + { + while ((dpn=LIST_NEXT(dpl,next))) dpl = dpn; + LIST_INSERT_AFTER(dpl, entry, next); + } + else + LIST_INSERT_HEAD(¶ms.desync_profiles, entry, next); + + return entry; +} +static void dp_entry_destroy(struct desync_profile_list *entry) +{ + strlist_destroy(&entry->dp.hostlist_files); + strlist_destroy(&entry->dp.hostlist_exclude_files); + StrPoolDestroy(&entry->dp.hostlist_exclude); + StrPoolDestroy(&entry->dp.hostlist); + HostFailPoolDestroy(&entry->dp.hostlist_auto_fail_counters); + free(entry); +} +void dp_list_destroy(struct desync_profile_list_head *head) +{ + struct desync_profile_list *entry; + while ((entry = LIST_FIRST(head))) + { + LIST_REMOVE(entry, next); + dp_entry_destroy(entry); + } +} +bool dp_list_have_autohostlist(struct desync_profile_list_head *head) +{ + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, head, next) + if (*dpl->dp.hostlist_auto_filename) + return true; + return false; +} diff --git a/tpws/params.h b/tpws/params.h index ba65e82..3764fc7 100644 --- a/tpws/params.h +++ b/tpws/params.h @@ -27,27 +27,10 @@ struct bind_s enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG }; -struct params_s +struct desync_profile { - struct bind_s binds[MAX_BINDS]; - int binds_last; - bool bind_wait_only; - uint16_t port; + int n; // number of the profile - uint8_t proxy_type; - bool no_resolve; - bool skip_nodelay; - bool droproot; - uid_t uid; - gid_t gid; - bool daemon; - int maxconn,resolver_threads,maxfiles,max_orphan_time; - int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf; -#if defined(__linux__) || defined(__APPLE__) - int tcp_user_timeout_local,tcp_user_timeout_remote; -#endif - - bool tamper; // any tamper option is set bool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol, domcase; int hostpad; char hostspell[4]; @@ -60,30 +43,59 @@ struct params_s bool disorder, disorder_http, disorder_tls; bool oob, oob_http, oob_tls; uint8_t oob_byte; - int ttl_default; int mss; - port_filter mss_pf; - - char pidfile[256]; - - strpool *hostlist, *hostlist_exclude; - struct str_list_head hostlist_files, hostlist_exclude_files; - char hostlist_auto_filename[PATH_MAX], hostlist_auto_debuglog[PATH_MAX]; - int hostlist_auto_fail_threshold, hostlist_auto_fail_time; - time_t hostlist_auto_mod_time; - hostfail_pool *hostlist_auto_fail_counters; bool tamper_start_n,tamper_cutoff_n; unsigned int tamper_start,tamper_cutoff; + + bool filter_ipv4,filter_ipv6; + port_filter pf_tcp; + strpool *hostlist, *hostlist_exclude; + struct str_list_head hostlist_files, hostlist_exclude_files; + char hostlist_auto_filename[PATH_MAX]; + int hostlist_auto_fail_threshold, hostlist_auto_fail_time; + time_t hostlist_auto_mod_time; + hostfail_pool *hostlist_auto_fail_counters; +}; + +struct desync_profile_list { + struct desync_profile dp; + LIST_ENTRY(desync_profile_list) next; +}; +LIST_HEAD(desync_profile_list_head, desync_profile_list); +struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head); +void dp_list_destroy(struct desync_profile_list_head *head); +bool dp_list_have_autohostlist(struct desync_profile_list_head *head); + +struct params_s +{ + int debug; + enum log_target debug_target; + char debug_logfile[PATH_MAX]; + + struct bind_s binds[MAX_BINDS]; + int binds_last; + bool bind_wait_only; + uint16_t port; struct sockaddr_in connect_bind4; struct sockaddr_in6 connect_bind6; char connect_bind6_ifname[IF_NAMESIZE]; - int debug; - enum log_target debug_target; - char debug_logfile[PATH_MAX]; + uint8_t proxy_type; + bool no_resolve; + bool skip_nodelay; + bool droproot; + uid_t uid; + gid_t gid; + bool daemon; + char pidfile[256]; + int maxconn,resolver_threads,maxfiles,max_orphan_time; + int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf; +#if defined(__linux__) || defined(__APPLE__) + int tcp_user_timeout_local,tcp_user_timeout_remote; +#endif #if defined(BSD) bool pf_enable; @@ -91,6 +103,13 @@ struct params_s #ifdef SPLICE_PRESENT bool nosplice; #endif + + int ttl_default; + char hostlist_auto_debuglog[PATH_MAX]; + + bool tamper; // any tamper option is set + bool tamper_lim; // tamper-start or tamper-cutoff set in any profile + struct desync_profile_list_head desync_profiles; }; extern struct params_s params; diff --git a/tpws/protocol.c b/tpws/protocol.c index 22e89c5..190277d 100644 --- a/tpws/protocol.c +++ b/tpws/protocol.c @@ -25,12 +25,45 @@ bool IsHttp(const uint8_t *data, size_t len) { return !!HttpMethod(data,len); } + +static bool IsHostAt(const uint8_t *p) +{ + return \ + p[0]=='\n' && + (p[1]=='H' || p[1]=='h') && + (p[2]=='o' || p[2]=='O') && + (p[3]=='s' || p[3]=='S') && + (p[4]=='t' || p[4]=='T') && + p[5]==':'; +} +static uint8_t *FindHostIn(uint8_t *buf, size_t bs) +{ + size_t pos; + if (bs<6) return NULL; + bs-=6; + for(pos=0;pos<=bs;pos++) + if (IsHostAt(buf+pos)) + return buf+pos; + + return NULL; +} +static const uint8_t *FindHostInConst(const uint8_t *buf, size_t bs) +{ + size_t pos; + if (bs<6) return NULL; + bs-=6; + for(pos=0;pos<=bs;pos++) + if (IsHostAt(buf+pos)) + return buf+pos; + + return NULL; +} // pHost points to "Host: ..." bool HttpFindHost(uint8_t **pHost,uint8_t *buf,size_t bs) { if (!*pHost) { - *pHost = memmem(buf, bs, "\nHost:", 6); + *pHost = FindHostIn(buf, bs); if (*pHost) (*pHost)++; } return !!*pHost; @@ -39,7 +72,7 @@ bool HttpFindHostConst(const uint8_t **pHost,const uint8_t *buf,size_t bs) { if (!*pHost) { - *pHost = memmem(buf, bs, "\nHost:", 6); + *pHost = FindHostInConst(buf, bs); if (*pHost) (*pHost)++; } return !!*pHost; @@ -132,7 +165,7 @@ bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char * } size_t HttpPos(enum httpreqpos tpos_type, size_t hpos_pos, const uint8_t *http, size_t sz) { - const uint8_t *method, *host; + const uint8_t *method, *host=NULL; int i; switch(tpos_type) diff --git a/tpws/tamper.c b/tpws/tamper.c index c70e6cc..8fc33ca 100644 --- a/tpws/tamper.c +++ b/tpws/tamper.c @@ -1,24 +1,87 @@ #define _GNU_SOURCE #include "tamper.h" -#include "params.h" #include "hostlist.h" #include "protocol.h" #include "helpers.h" #include #include +static bool dp_match_l3l4(struct desync_profile *dp, bool ipv6, uint16_t tcp_port) +{ + return \ + ((!ipv6 && dp->filter_ipv4) || (ipv6 && dp->filter_ipv6)) && + (!tcp_port || pf_in_range(tcp_port,&dp->pf_tcp)); +} +static bool dp_match(struct desync_profile *dp, bool ipv6, uint16_t tcp_port, const char *hostname) +{ + if (dp_match_l3l4(dp,ipv6,tcp_port)) + { + // autohostlist profile matching l3/l4 filter always win + if (*dp->hostlist_auto_filename) return true; + + if (dp->hostlist || dp->hostlist_exclude) + { + // without known hostname first profile matching l3/l4 filter and without hostlist filter wins + if (hostname) + return HostlistCheck(dp, hostname, NULL); + } + else + // profile without hostlist filter wins + return true; + } + return false; +} +static struct desync_profile *dp_find(struct desync_profile_list_head *head, bool ipv6, uint16_t tcp_port, const char *hostname) +{ + struct desync_profile_list *dpl; + VPRINT("desync profile search for hostname='%s' ipv6=%u tcp_port=%u\n", hostname ? hostname : "", ipv6, tcp_port); + LIST_FOREACH(dpl, head, next) + { + if (dp_match(&dpl->dp,ipv6,tcp_port,hostname)) + { + VPRINT("desync profile %d matches\n",dpl->dp.n); + return &dpl->dp; + } + } + VPRINT("desync profile not found\n"); + return NULL; +} +void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest) +{ + ctrack->dp = dp_find(¶ms.desync_profiles, dest->sa_family==AF_INET6, saport(dest), ctrack->hostname); +} + + + // segment buffer has at least 5 extra bytes to extend data block -void tamper_out(t_ctrack *ctrack, 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 *split_pos, uint8_t *split_flags) { uint8_t *p, *pp, *pHost = NULL; size_t method_len = 0, pos; + size_t tpos, spos; const char *method; - bool bBypass = false, bHaveHost = false, bHostExcluded = false; + bool bHaveHost = false; char *pc, Host[256]; - + t_l7proto l7proto; + DBGPRINT("tamper_out\n"); + if (params.debug) + { + char ip_port[48]; + ntop46_port(dest,ip_port,sizeof(ip_port)); + VPRINT("tampering tcp segment with size %zu to %s\n", *size, ip_port); + if (ctrack->dp) VPRINT("using cached desync profile %d\n",ctrack->dp->n); + if (ctrack->hostname) VPRINT("connection hostname: %s\n", ctrack->hostname); + } + + if (dest->sa_family!=AF_INET && dest->sa_family!=AF_INET6) + { + DLOG_ERR("tamper_out dest family unknown\n"); + return; + } + *split_pos=0; *split_flags=0; @@ -26,9 +89,8 @@ void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,si { method_len = strlen(method)-2; VPRINT("Data block looks like http request start : %s\n", method); - if (!ctrack->l7proto) ctrack->l7proto=HTTP; - // cpu saving : we search host only if and when required. we do not research host every time we need its position - if ((params.hostlist || params.hostlist_exclude) && HttpFindHost(&pHost,segment,*size)) + l7proto=HTTP; + if (HttpFindHost(&pHost,segment,*size)) { p = pHost + 5; while (p < (segment + *size) && (*p == ' ' || *p == '\t')) p++; @@ -37,13 +99,57 @@ void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,si memcpy(Host, p, pp - p); Host[pp - p] = '\0'; bHaveHost = true; - VPRINT("Requested Host is : %s\n", Host); for(pc = Host; *pc; pc++) *pc=tolower(*pc); - bBypass = !HostlistCheck(Host, &bHostExcluded); } - if (!bBypass) + } + else if (IsTLSClientHello(segment,*size,false)) + { + VPRINT("Data block contains TLS ClientHello\n"); + l7proto=TLS; + bHaveHost=TLSHelloExtractHost((uint8_t*)segment,*size,Host,sizeof(Host),false); + } + else + { + VPRINT("Data block contains unknown payload\n"); + l7proto = UNKNOWN; + } + + if (ctrack->l7proto==UNKNOWN) ctrack->l7proto=l7proto; + + if (bHaveHost) + { + VPRINT("request hostname: %s\n", Host); + if (!ctrack->hostname) { - if (params.unixeol) + if (!(ctrack->hostname=strdup(Host))) + { + DLOG_ERR("strdup hostname : out of memory\n"); + return; + } + + struct desync_profile *dp_prev = ctrack->dp; + apply_desync_profile(ctrack, dest); + if (ctrack->dp!=dp_prev) + VPRINT("desync profile changed by revealed hostname !\n"); + else if (*ctrack->dp->hostlist_auto_filename) + { + bool bHostExcluded; + if (!HostlistCheck(ctrack->dp, Host, &bHostExcluded)) + { + ctrack->b_ah_check = !bHostExcluded; + VPRINT("Not acting on this request\n"); + return; + } + } + } + } + + if (!ctrack->dp) return; + + switch(l7proto) + { + case HTTP: + if (ctrack->dp->unixeol) { p = pp = segment; while ((p = memmem(p, segment + *size - p, "\r\n", 2))) @@ -61,10 +167,10 @@ void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,si } pHost = NULL; // invalidate } - if (params.methodeol && (*size+1+!params.unixeol)<=segment_buffer_size) + if (ctrack->dp->methodeol && (*size+1+!ctrack->dp->unixeol)<=segment_buffer_size) { VPRINT("Adding EOL before method\n"); - if (params.unixeol) + if (ctrack->dp->unixeol) { memmove(segment + 1, segment, *size); (*size)++;; @@ -79,7 +185,7 @@ void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,si } pHost = NULL; // invalidate } - if (params.methodspace && *sizedp->methodspace && *sizedp->hostdot || ctrack->dp->hosttab) && *sizedp->hostdot ? "dot" : "tab", pos); memmove(p + 1, p, *size - pos); - *p = params.hostdot ? '.' : '\t'; // insert dot or tab + *p = ctrack->dp->hostdot ? '.' : '\t'; // insert dot or tab (*size)++; // block will grow by 1 byte } } - if (params.domcase && HttpFindHost(&pHost,segment,*size)) + if (ctrack->dp->domcase && HttpFindHost(&pHost,segment,*size)) { p = pHost + 5; pos = p - segment; @@ -111,7 +217,7 @@ void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,si for (; p < (segment + *size) && *p != '\r' && *p != '\n'; p++) *p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p); } - if (params.hostnospace && HttpFindHost(&pHost,segment,*size) && (pHost+5)<(segment+*size) && pHost[5] == ' ') + if (ctrack->dp->hostnospace && HttpFindHost(&pHost,segment,*size) && (pHost+5)<(segment+*size) && pHost[5] == ' ') { p = pHost + 6; pos = p - segment; @@ -119,17 +225,17 @@ void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,si memmove(p - 1, p, *size - pos); (*size)--; // block will shrink by 1 byte } - if (params.hostcase && HttpFindHost(&pHost,segment,*size)) + if (ctrack->dp->hostcase && HttpFindHost(&pHost,segment,*size)) { - VPRINT("Changing 'Host:' => '%c%c%c%c:' at pos %td\n", params.hostspell[0], params.hostspell[1], params.hostspell[2], params.hostspell[3], pHost - segment); - memcpy(pHost, params.hostspell, 4); + VPRINT("Changing 'Host:' => '%c%c%c%c:' at pos %td\n", ctrack->dp->hostspell[0], ctrack->dp->hostspell[1], ctrack->dp->hostspell[2], ctrack->dp->hostspell[3], pHost - segment); + memcpy(pHost, ctrack->dp->hostspell, 4); } - if (params.hostpad && HttpFindHost(&pHost,segment,*size)) + if (ctrack->dp->hostpad && HttpFindHost(&pHost,segment,*size)) { // add : XXXXX: dp->unixeol ? 8 : 9; + size_t hostpad = ctrack->dp->hostpaddp->hostpad; if ((hsize+*size)>segment_buffer_size) VPRINT("could not add host padding : buffer too small\n"); @@ -159,7 +265,7 @@ void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,si p+=7; memset(p,'a'+rand()%('z'-'a'+1),padsize); p+=padsize; - if (params.unixeol) + if (ctrack->dp->unixeol) *p++='\n'; else { @@ -171,40 +277,16 @@ void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,si pHost = NULL; // invalidate } } - *split_pos = HttpPos(params.split_http_req, params.split_pos, segment, *size); - if (params.disorder_http) *split_flags |= SPLIT_FLAG_DISORDER; - if (params.oob_http) *split_flags |= SPLIT_FLAG_OOB; - } - else - { - VPRINT("Not acting on this request\n"); - } - } - else if (IsTLSClientHello(segment,*size,false)) - { - size_t tpos=0,spos=0; - - if (!ctrack->l7proto) ctrack->l7proto=TLS; - - VPRINT("packet contains TLS ClientHello\n"); - // we need host only if hostlist is present - if ((params.hostlist || params.hostlist_exclude) && TLSHelloExtractHost((uint8_t*)segment,*size,Host,sizeof(Host),false)) - { - VPRINT("hostname: %s\n",Host); - bHaveHost = true; - bBypass = !HostlistCheck(Host, &bHostExcluded); - } - if (bBypass) - { - VPRINT("Not acting on this request\n"); - } - else - { - spos = TLSPos(params.split_tls, params.split_pos, segment, *size, 0); + *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; + break; + case TLS: + spos = TLSPos(ctrack->dp->split_tls, ctrack->dp->split_pos, segment, *size, 0); if ((5+*size)<=segment_buffer_size) { - tpos = TLSPos(params.tlsrec, params.tlsrec_pos+5, segment, *size, 0); + tpos = TLSPos(ctrack->dp->tlsrec, ctrack->dp->tlsrec_pos+5, segment, *size, 0); if (tpos>5) { // construct 2 TLS records from one @@ -228,52 +310,46 @@ void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,si } if (spos && spos < *size) - { - VPRINT("split pos %zu\n",spos); *split_pos = spos; - } - if (params.disorder_tls) *split_flags |= SPLIT_FLAG_DISORDER; - if (params.oob_tls) *split_flags |= SPLIT_FLAG_OOB; - } + 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; } - else if (params.split_any_protocol && params.split_pos < *size) - *split_pos = params.split_pos; - if (bHaveHost && bBypass && !bHostExcluded && *params.hostlist_auto_filename) - { - DBGPRINT("tamper_out put hostname : %s\n", Host); - if (ctrack->hostname) free(ctrack->hostname); - ctrack->hostname=strdup(Host); - } - if (params.disorder) *split_flags |= SPLIT_FLAG_DISORDER; - if (params.oob) *split_flags |= SPLIT_FLAG_OOB; + 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(const char *hostname) +static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname) { if (hostname) { hostfail_pool *fail_counter; - fail_counter = HostFailPoolFind(params.hostlist_auto_fail_counters, hostname); + fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); if (fail_counter) { - HostFailPoolDel(¶ms.hostlist_auto_fail_counters, fail_counter); - VPRINT("auto hostlist : %s : fail counter reset. website is working.\n", hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : fail counter reset. website is working.", hostname); + HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); + VPRINT("auto hostlist (profile %d) : %s : fail counter reset. website is working.\n", dp->n, hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : fail counter reset. website is working.", hostname, dp->n); } } } -static void auto_hostlist_failed(const char *hostname) +static void auto_hostlist_failed(struct desync_profile *dp, const char *hostname) { hostfail_pool *fail_counter; - fail_counter = HostFailPoolFind(params.hostlist_auto_fail_counters, hostname); + fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname); if (!fail_counter) { - fail_counter = HostFailPoolAdd(¶ms.hostlist_auto_fail_counters, hostname, params.hostlist_auto_fail_time); + fail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time); if (!fail_counter) { DLOG_ERR("HostFailPoolAdd: out of memory\n"); @@ -281,35 +357,35 @@ static void auto_hostlist_failed(const char *hostname) } } fail_counter->counter++; - VPRINT("auto hostlist : %s : fail counter %d/%d\n", hostname, fail_counter->counter, params.hostlist_auto_fail_threshold); - HOSTLIST_DEBUGLOG_APPEND("%s : fail counter %d/%d", hostname, fail_counter->counter, params.hostlist_auto_fail_threshold); - if (fail_counter->counter >= params.hostlist_auto_fail_threshold) + VPRINT("auto hostlist (profile %d) : %s : fail counter %d/%d\n", dp->n , hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : fail counter %d/%d", hostname, dp->n, fail_counter->counter, dp->hostlist_auto_fail_threshold); + if (fail_counter->counter >= dp->hostlist_auto_fail_threshold) { - VPRINT("auto hostlist : fail threshold reached. adding %s to auto hostlist\n", hostname); - HostFailPoolDel(¶ms.hostlist_auto_fail_counters, fail_counter); + VPRINT("auto hostlist (profile %d) : fail threshold reached. adding %s to auto hostlist\n", dp->n , hostname); + HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter); - VPRINT("auto hostlist : rechecking %s to avoid duplicates\n", hostname); + VPRINT("auto hostlist (profile %d) : rechecking %s to avoid duplicates\n", dp->n, hostname); bool bExcluded=false; - if (!HostlistCheck(hostname, &bExcluded) && !bExcluded) + if (!HostlistCheck(dp, hostname, &bExcluded) && !bExcluded) { - VPRINT("auto hostlist : adding %s\n", hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : adding", hostname); - if (!StrPoolAddStr(¶ms.hostlist, hostname)) + VPRINT("auto hostlist (profile %d) : adding %s to %s\n", dp->n, hostname, dp->hostlist_auto_filename); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : adding to %s", hostname, dp->n, dp->hostlist_auto_filename); + if (!StrPoolAddStr(&dp->hostlist, hostname)) { DLOG_ERR("StrPoolAddStr out of memory\n"); return; } - if (!append_to_list_file(params.hostlist_auto_filename, hostname)) + if (!append_to_list_file(dp->hostlist_auto_filename, hostname)) { DLOG_PERROR("write to auto hostlist:"); return; } - params.hostlist_auto_mod_time = file_mod_time(params.hostlist_auto_filename); + dp->hostlist_auto_mod_time = file_mod_time(dp->hostlist_auto_filename); } else { - VPRINT("auto hostlist : NOT adding %s\n", hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : NOT adding, duplicate detected", hostname); + VPRINT("auto hostlist (profile %d) : NOT adding %s\n", dp->n, hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : NOT adding, duplicate detected", hostname, dp->n); } } } @@ -320,9 +396,9 @@ void tamper_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,siz DBGPRINT("tamper_in hostname=%s\n", ctrack->hostname); - if (*params.hostlist_auto_filename) + if (ctrack->dp && ctrack->b_ah_check) { - HostFailPoolPurgeRateLimited(¶ms.hostlist_auto_fail_counters); + HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); if (ctrack->l7proto==HTTP && ctrack->hostname) { @@ -333,7 +409,7 @@ void tamper_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,siz if (bFail) { VPRINT("redirect to another domain detected. possibly DPI redirect.\n"); - HOSTLIST_DEBUGLOG_APPEND("%s : redirect to another domain", ctrack->hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : redirect to another domain", ctrack->hostname, ctrack->dp->n); } else VPRINT("local or in-domain redirect detected. it's not a DPI redirect.\n"); @@ -343,11 +419,9 @@ void tamper_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,siz // received not http reply. do not monitor this connection anymore VPRINT("incoming unknown HTTP data detected for hostname %s\n", ctrack->hostname); } - if (bFail) auto_hostlist_failed(ctrack->hostname); - + if (bFail) auto_hostlist_failed(ctrack->dp, ctrack->hostname); } - if (!bFail) auto_hostlist_reset_fail_counter(ctrack->hostname); - + if (!bFail) auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname); } ctrack->bTamperInCutoff = true; } @@ -356,30 +430,32 @@ void rst_in(t_ctrack *ctrack) { DBGPRINT("rst_in hostname=%s\n", ctrack->hostname); - if (!*params.hostlist_auto_filename) return; - - HostFailPoolPurgeRateLimited(¶ms.hostlist_auto_fail_counters); - - if (!ctrack->bTamperInCutoff && ctrack->hostname) + if (ctrack->dp && ctrack->b_ah_check) { - VPRINT("incoming RST detected for hostname %s\n", ctrack->hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : incoming RST", ctrack->hostname); - auto_hostlist_failed(ctrack->hostname); + HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); + + if (!ctrack->bTamperInCutoff && ctrack->hostname) + { + VPRINT("incoming RST detected for hostname %s\n", ctrack->hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : incoming RST", ctrack->hostname, ctrack->dp->n); + auto_hostlist_failed(ctrack->dp, ctrack->hostname); + } } } void hup_out(t_ctrack *ctrack) { DBGPRINT("hup_out hostname=%s\n", ctrack->hostname); - - if (!*params.hostlist_auto_filename) return; - - HostFailPoolPurgeRateLimited(¶ms.hostlist_auto_fail_counters); - - if (!ctrack->bTamperInCutoff && ctrack->hostname) + + if (ctrack->dp && ctrack->b_ah_check) { - // local leg dropped connection after first request. probably due to timeout. - VPRINT("local leg closed connection after first request (timeout ?). hostname: %s\n", ctrack->hostname); - HOSTLIST_DEBUGLOG_APPEND("%s : client closed connection without server reply", ctrack->hostname); - auto_hostlist_failed(ctrack->hostname); + HostFailPoolPurgeRateLimited(&ctrack->dp->hostlist_auto_fail_counters); + + if (!ctrack->bTamperInCutoff && ctrack->hostname) + { + // local leg dropped connection after first request. probably due to timeout. + VPRINT("local leg closed connection after first request (timeout ?). hostname: %s\n", ctrack->hostname); + HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : client closed connection without server reply", ctrack->hostname, ctrack->dp->n); + auto_hostlist_failed(ctrack->dp, ctrack->hostname); + } } } diff --git a/tpws/tamper.h b/tpws/tamper.h index 7f4172e..ccc5c6f 100644 --- a/tpws/tamper.h +++ b/tpws/tamper.h @@ -4,6 +4,8 @@ #include #include +#include "params.h" + #define SPLIT_FLAG_DISORDER 0x01 #define SPLIT_FLAG_OOB 0x02 @@ -14,10 +16,14 @@ typedef struct t_l7proto l7proto; bool bFirstReplyChecked; bool bTamperInCutoff; + bool b_ah_check; char *hostname; + struct desync_profile *dp; // desync profile cache } t_ctrack; -void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos, uint8_t *split_flags); +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_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size); // connection reset by remote leg void rst_in(t_ctrack *ctrack); diff --git a/tpws/tpws.c b/tpws/tpws.c index 9e017c5..3e49485 100644 --- a/tpws/tpws.c +++ b/tpws/tpws.c @@ -46,8 +46,7 @@ bool bHup = false; static void onhup(int sig) { printf("HUP received !\n"); - if (params.hostlist || params.hostlist_exclude) - printf("Will reload hostlists on next request\n"); + printf("Will reload hostlist on next request (if any)\n"); bHup = true; } // should be called in normal execution @@ -67,7 +66,14 @@ void dohup(void) static void onusr2(int sig) { printf("\nHOSTFAIL POOL DUMP\n"); - HostFailPoolDump(params.hostlist_auto_fail_counters); + + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + { + printf("\nDESYNC PROFILE %d\n",dpl->dp.n); + HostFailPoolDump(dpl->dp.hostlist_auto_fail_counters); + } + printf("\n"); } @@ -170,7 +176,11 @@ static void exithelp(void) #endif " --debug=0|1|2|syslog|@\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" - "\nFILTER:\n" + "\nMULTI-STRATEGY:\n" + " --new\t\t\t\t\t; begin new strategy\n" + " --filter-l3=ipv4|ipv6\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" + " --filter-tcp=[~]port1[-port2]\t\t; TCP port filter. ~ means negation\n" + "\nHOSTLIST FILTER:\n" " --hostlist=\t\t\t; only act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" " --hostlist-exclude=\t\t; do not act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n" " --hostlist-auto=\t\t; detect DPI blocks and build hostlist automatically\n" @@ -203,7 +213,6 @@ static void exithelp(void) " --tlsrec-pos=\t\t\t; make 2 TLS records. split at specified pos\n" #ifdef __linux__ " --mss=\t\t\t\t; set client MSS. forces server to split messages but significantly decreases speed !\n" - " --mss-pf=[~]port1[-port2]\t\t; MSS port filter. ~ means negation\n" #endif " --tamper-start=[n]\t\t; start tampering only from specified outbound stream position. default is 0. 'n' means data block number.\n" " --tamper-cutoff=[n]\t\t; do not tamper anymore after specified outbound stream position. default is unlimited.\n", @@ -216,11 +225,7 @@ static void exithelp(void) } static void cleanup_params(void) { - strlist_destroy(¶ms.hostlist_files); - strlist_destroy(¶ms.hostlist_exclude_files); - StrPoolDestroy(¶ms.hostlist_exclude); - StrPoolDestroy(¶ms.hostlist); - HostFailPoolDestroy(¶ms.hostlist_auto_fail_counters); + dp_list_destroy(¶ms.desync_profiles); } static void exithelp_clean(void) { @@ -285,6 +290,32 @@ bool parse_tlspos(const char *s, enum tlspos *pos) return true; } +static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) +{ + char *e,*p,c; + + for (p=opt,*ipv4=*ipv6=false ; p ; ) + { + if ((e = strchr(p,','))) + { + c=*e; + *e=0; + } + + if (!strcmp(p,"ipv4")) + *ipv4 = true; + else if (!strcmp(p,"ipv6")) + *ipv6 = true; + else return false; + + if (e) + { + *e++=c; + } + p = e; + } + return true; +} void parse_params(int argc, char *argv[]) { @@ -292,18 +323,13 @@ void parse_params(int argc, char *argv[]) int v, i; memset(¶ms, 0, sizeof(params)); - memcpy(params.hostspell, "host", 4); // default hostspell params.maxconn = DEFAULT_MAX_CONN; params.max_orphan_time = DEFAULT_MAX_ORPHAN_TIME; params.binds_last = -1; - params.hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT; - params.hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT; #if defined(__linux__) || defined(__APPLE__) params.tcp_user_timeout_local = DEFAULT_TCP_USER_TIMEOUT_LOCAL; params.tcp_user_timeout_remote = DEFAULT_TCP_USER_TIMEOUT_REMOTE; #endif - LIST_INIT(¶ms.hostlist_files); - LIST_INIT(¶ms.hostlist_exclude_files); #if defined(__OpenBSD__) || defined(__APPLE__) params.pf_enable = true; // OpenBSD and MacOS have no other choice @@ -314,6 +340,17 @@ void parse_params(int argc, char *argv[]) params.droproot = true; } + struct desync_profile_list *dpl; + struct desync_profile *dp; + int desync_profile_count=0; + if (!(dpl = dp_list_add(¶ms.desync_profiles))) + { + DLOG_ERR("desync_profile_add: out of memory\n"); + exit_clean(1); + } + dp = &dpl->dp; + dp->n = ++desync_profile_count; + const struct option long_options[] = { { "help",no_argument,0,0 },// optidx=0 { "h",no_argument,0,0 },// optidx=1 @@ -371,18 +408,22 @@ void parse_params(int argc, char *argv[]) { "tamper-start",required_argument,0,0 },// optidx=53 { "tamper-cutoff",required_argument,0,0 },// optidx=54 { "connect-bind-addr",required_argument,0,0 },// optidx=55 + + { "new",no_argument,0,0 }, // optidx=56 + { "filter-l3",required_argument,0,0 }, // optidx=57 + { "filter-tcp",required_argument,0,0 }, // optidx=58 + #if defined(__FreeBSD__) - { "enable-pf",no_argument,0,0 },// optidx=56 + { "enable-pf",no_argument,0,0 },// optidx=59 #elif defined(__APPLE__) - { "local-tcp-user-timeout",required_argument,0,0 },// optidx=56 - { "remote-tcp-user-timeout",required_argument,0,0 },// optidx=57 + { "local-tcp-user-timeout",required_argument,0,0 },// optidx=59 + { "remote-tcp-user-timeout",required_argument,0,0 },// optidx=60 #elif defined(__linux__) - { "local-tcp-user-timeout",required_argument,0,0 },// optidx=56 - { "remote-tcp-user-timeout",required_argument,0,0 },// optidx=57 - { "mss",required_argument,0,0 },// optidx=58 - { "mss-pf",required_argument,0,0 },// optidx=59 + { "local-tcp-user-timeout",required_argument,0,0 },// optidx=59 + { "remote-tcp-user-timeout",required_argument,0,0 },// optidx=60 + { "mss",required_argument,0,0 },// optidx=61 #ifdef SPLICE_PRESENT - { "nosplice",no_argument,0,0 },// optidx=60 + { "nosplice",no_argument,0,0 },// optidx=62 #endif #endif { "hostlist-auto-retrans-threshold",optional_argument,0,0}, // ignored. for nfqws command line compatibility @@ -513,7 +554,7 @@ void parse_params(int argc, char *argv[]) } break; case 17: /* hostcase */ - params.hostcase = true; + dp->hostcase = true; params.tamper = true; break; case 18: /* hostspell */ @@ -522,28 +563,28 @@ void parse_params(int argc, char *argv[]) DLOG_ERR("hostspell must be exactly 4 chars long\n"); exit_clean(1); } - params.hostcase = true; - memcpy(params.hostspell, optarg, 4); + dp->hostcase = true; + memcpy(dp->hostspell, optarg, 4); params.tamper = true; break; case 19: /* hostdot */ - params.hostdot = true; + dp->hostdot = true; params.tamper = true; break; case 20: /* hostnospace */ - params.hostnospace = true; + dp->hostnospace = true; params.tamper = true; break; case 21: /* hostpad */ - params.hostpad = atoi(optarg); + dp->hostpad = atoi(optarg); params.tamper = true; break; case 22: /* domcase */ - params.domcase = true; + dp->domcase = true; params.tamper = true; break; case 23: /* split-http-req */ - if (!parse_httpreqpos(optarg, ¶ms.split_http_req)) + if (!parse_httpreqpos(optarg, &dp->split_http_req)) { DLOG_ERR("Invalid argument for split-http-req\n"); exit_clean(1); @@ -551,7 +592,7 @@ void parse_params(int argc, char *argv[]) params.tamper = true; break; case 24: /* split-tls */ - if (!parse_tlspos(optarg, ¶ms.split_tls)) + if (!parse_tlspos(optarg, &dp->split_tls)) { DLOG_ERR("Invalid argument for split-tls\n"); exit_clean(1); @@ -561,7 +602,7 @@ void parse_params(int argc, char *argv[]) case 25: /* split-pos */ i = atoi(optarg); if (i>0) - params.split_pos = i; + dp->split_pos = i; else { DLOG_ERR("Invalid argument for split-pos\n"); @@ -570,13 +611,13 @@ void parse_params(int argc, char *argv[]) params.tamper = true; break; case 26: /* split-any-protocol */ - params.split_any_protocol = true; + dp->split_any_protocol = true; break; case 27: /* disorder */ if (optarg) { - if (!strcmp(optarg,"http")) params.disorder_http=true; - else if (!strcmp(optarg,"tls")) params.disorder_tls=true; + if (!strcmp(optarg,"http")) dp->disorder_http=true; + else if (!strcmp(optarg,"tls")) dp->disorder_tls=true; else { DLOG_ERR("Invalid argument for disorder\n"); @@ -584,14 +625,14 @@ void parse_params(int argc, char *argv[]) } } else - params.disorder = true; + dp->disorder = true; save_default_ttl(); break; case 28: /* oob */ if (optarg) { - if (!strcmp(optarg,"http")) params.oob_http=true; - else if (!strcmp(optarg,"tls")) params.oob_tls=true; + if (!strcmp(optarg,"http")) dp->oob_http=true; + else if (!strcmp(optarg,"tls")) dp->oob_tls=true; else { DLOG_ERR("Invalid argument for oob\n"); @@ -599,39 +640,39 @@ void parse_params(int argc, char *argv[]) } } else - params.oob = true; + dp->oob = true; break; case 29: /* oob-data */ { size_t l = strlen(optarg); unsigned int bt; - if (l==1) params.oob_byte = (uint8_t)*optarg; + if (l==1) dp->oob_byte = (uint8_t)*optarg; else if (l!=4 || sscanf(optarg,"0x%02X",&bt)!=1) { DLOG_ERR("Invalid argument for oob-data\n"); exit_clean(1); } - else params.oob_byte = (uint8_t)bt; + else dp->oob_byte = (uint8_t)bt; } break; case 30: /* methodspace */ - params.methodspace = true; + dp->methodspace = true; params.tamper = true; break; case 31: /* methodeol */ - params.methodeol = true; + dp->methodeol = true; params.tamper = true; break; case 32: /* hosttab */ - params.hosttab = true; + dp->hosttab = true; params.tamper = true; break; case 33: /* unixeol */ - params.unixeol = true; + dp->unixeol = true; params.tamper = true; break; case 34: /* tlsrec */ - if (!parse_tlspos(optarg, ¶ms.tlsrec)) + if (!parse_tlspos(optarg, &dp->tlsrec)) { DLOG_ERR("Invalid argument for tlsrec\n"); exit_clean(1); @@ -639,8 +680,8 @@ void parse_params(int argc, char *argv[]) params.tamper = true; break; case 35: /* tlsrec-pos */ - if ((params.tlsrec_pos = atoi(optarg))>0) - params.tlsrec = tlspos_pos; + if ((dp->tlsrec_pos = atoi(optarg))>0) + dp->tlsrec = tlspos_pos; else { DLOG_ERR("Invalid argument for tlsrec-pos\n"); @@ -649,7 +690,7 @@ void parse_params(int argc, char *argv[]) params.tamper = true; break; case 36: /* hostlist */ - if (!strlist_add(¶ms.hostlist_files, optarg)) + if (!strlist_add(&dp->hostlist_files, optarg)) { DLOG_ERR("strlist_add failed\n"); exit_clean(1); @@ -657,7 +698,7 @@ void parse_params(int argc, char *argv[]) params.tamper = true; break; case 37: /* hostlist-exclude */ - if (!strlist_add(¶ms.hostlist_exclude_files, optarg)) + if (!strlist_add(&dp->hostlist_exclude_files, optarg)) { DLOG_ERR("strlist_add failed\n"); exit_clean(1); @@ -665,9 +706,9 @@ void parse_params(int argc, char *argv[]) params.tamper = true; break; case 38: /* hostlist-auto */ - if (*params.hostlist_auto_filename) + if (*dp->hostlist_auto_filename) { - DLOG_ERR("only one auto hostlist is supported\n"); + DLOG_ERR("only one auto hostlist per profile is supported\n"); exit_clean(1); } { @@ -687,26 +728,26 @@ void parse_params(int argc, char *argv[]) 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 (!strlist_add(¶ms.hostlist_files, optarg)) + if (!strlist_add(&dp->hostlist_files, optarg)) { DLOG_ERR("strlist_add failed\n"); exit_clean(1); } - strncpy(params.hostlist_auto_filename, optarg, sizeof(params.hostlist_auto_filename)); - params.hostlist_auto_filename[sizeof(params.hostlist_auto_filename) - 1] = '\0'; + strncpy(dp->hostlist_auto_filename, optarg, sizeof(dp->hostlist_auto_filename)); + dp->hostlist_auto_filename[sizeof(dp->hostlist_auto_filename) - 1] = '\0'; params.tamper = true; // need to detect blocks and update autohostlist. cannot just slice. break; case 39: /* hostlist-auto-fail-threshold */ - params.hostlist_auto_fail_threshold = (uint8_t)atoi(optarg); - if (params.hostlist_auto_fail_threshold<1 || params.hostlist_auto_fail_threshold>20) + dp->hostlist_auto_fail_threshold = (uint8_t)atoi(optarg); + if (dp->hostlist_auto_fail_threshold<1 || dp->hostlist_auto_fail_threshold>20) { DLOG_ERR("auto hostlist fail threshold must be within 1..20\n"); exit_clean(1); } break; case 40: /* hostlist-auto-fail-time */ - params.hostlist_auto_fail_time = (uint8_t)atoi(optarg); - if (params.hostlist_auto_fail_time<1) + dp->hostlist_auto_fail_time = (uint8_t)atoi(optarg); + if (dp->hostlist_auto_fail_time<1) { DLOG_ERR("auto hostlist fail time is not valid\n"); exit_clean(1); @@ -820,26 +861,28 @@ void parse_params(int argc, char *argv[]) const char *p=optarg; if (*p=='n') { - params.tamper_start_n=true; + dp->tamper_start_n=true; p++; } else - params.tamper_start_n=false; - params.tamper_start = atoi(p); + dp->tamper_start_n=false; + dp->tamper_start = atoi(p); } + params.tamper_lim = true; break; case 54: /* tamper-cutoff */ { const char *p=optarg; if (*p=='n') { - params.tamper_cutoff_n=true; + dp->tamper_cutoff_n=true; p++; } else - params.tamper_cutoff_n=false; - params.tamper_cutoff = atoi(p); + dp->tamper_cutoff_n=false; + dp->tamper_cutoff = atoi(p); } + params.tamper_lim = true; break; case 55: /* connect-bind-addr */ { @@ -868,12 +911,37 @@ void parse_params(int argc, char *argv[]) } break; + + case 56: /* new */ + if (!(dpl = dp_list_add(¶ms.desync_profiles))) + { + DLOG_ERR("desync_profile_add: out of memory\n"); + exit_clean(1); + } + dp = &dpl->dp; + dp->n = ++desync_profile_count; + break; + case 57: /* filter-l3 */ + if (!wf_make_l3(optarg,&dp->filter_ipv4,&dp->filter_ipv6)) + { + DLOG_ERR("bad value for --filter-l3\n"); + exit_clean(1); + } + break; + case 58: /* filter-tcp */ + if (!pf_parse(optarg,&dp->pf_tcp)) + { + DLOG_ERR("Invalid port filter : %s\n",optarg); + exit_clean(1); + } + break; + #if defined(__FreeBSD__) - case 56: /* enable-pf */ + case 59: /* enable-pf */ params.pf_enable = true; break; #elif defined(__linux__) || defined(__APPLE__) - case 56: /* local-tcp-user-timeout */ + case 59: /* local-tcp-user-timeout */ params.tcp_user_timeout_local = atoi(optarg); if (params.tcp_user_timeout_local<0 || params.tcp_user_timeout_local>86400) { @@ -881,7 +949,7 @@ void parse_params(int argc, char *argv[]) exit_clean(1); } break; - case 57: /* remote-tcp-user-timeout */ + case 60: /* remote-tcp-user-timeout */ params.tcp_user_timeout_remote = atoi(optarg); if (params.tcp_user_timeout_remote<0 || params.tcp_user_timeout_remote>86400) { @@ -892,24 +960,17 @@ void parse_params(int argc, char *argv[]) #endif #if defined(__linux__) - case 58: /* mss */ + case 61: /* mss */ // this option does not work in any BSD and MacOS. OS may accept but it changes nothing - params.mss = atoi(optarg); - if (params.mss<88 || params.mss>32767) + dp->mss = atoi(optarg); + if (dp->mss<88 || dp->mss>32767) { DLOG_ERR("Invalid value for MSS. Linux accepts MSS 88-32767.\n"); exit_clean(1); } break; - case 59: /* mss-pf */ - if (!pf_parse(optarg,¶ms.mss_pf)) - { - DLOG_ERR("Invalid MSS port filter.\n"); - exit_clean(1); - } - break; #ifdef SPLICE_PRESENT - case 60: /* nosplice */ + case 62: /* nosplice */ params.nosplice = true; break; #endif @@ -925,22 +986,36 @@ void parse_params(int argc, char *argv[]) { params.binds_last=0; // default bind to all } - if (params.skip_nodelay && (params.split_http_req || params.split_pos)) + if (!params.resolver_threads) params.resolver_threads = 5 + params.maxconn/50; + + VPRINT("adding low-priority default empty desync profile\n"); + // add default empty profile + if (!(dpl = dp_list_add(¶ms.desync_profiles))) { - DLOG_ERR("Cannot split with --skip-nodelay\n"); + DLOG_ERR("desync_profile_add: out of memory\n"); exit_clean(1); } - if (!params.resolver_threads) params.resolver_threads = 5 + params.maxconn/50; - if (params.split_tls==tlspos_none && params.split_pos) params.split_tls=tlspos_pos; - if (params.split_http_req==httpreqpos_none && params.split_pos) params.split_http_req=httpreqpos_pos; - if (*params.hostlist_auto_filename) params.hostlist_auto_mod_time = file_mod_time(params.hostlist_auto_filename); + DLOG_CONDUP("we have %d user defined desync profile(s) and default low priority profile 0\n",desync_profile_count); + + 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 (*dp->hostlist_auto_filename) dp->hostlist_auto_mod_time = file_mod_time(dp->hostlist_auto_filename); + if (params.skip_nodelay && (dp->split_tls || dp->split_http_req || dp->split_pos)) + { + DLOG_ERR("Cannot split with --skip-nodelay\n"); + exit_clean(1); + } + } + if (!LoadIncludeHostLists()) { DLOG_ERR("Include hostlist load failed\n"); exit_clean(1); } - if (*params.hostlist_auto_filename) NonEmptyHostlist(¶ms.hostlist); if (!LoadExcludeHostLists()) { DLOG_ERR("Exclude hostlist load failed\n"); @@ -1057,7 +1132,7 @@ static bool set_ulimit(void) // additional 1/2 for unpaired remote legs sending buffers // 16 for listen_fd, epoll, hostlist, ... #ifdef SPLICE_PRESENT - fdmax = (params.nosplice ? 2 : (params.tamper && !params.tamper_start && !params.tamper_cutoff ? 4 : 6)) * params.maxconn; + fdmax = (params.nosplice ? 2 : (params.tamper && !params.tamper_lim ? 4 : 6)) * params.maxconn; #else fdmax = 2 * params.maxconn; #endif diff --git a/tpws/tpws_conn.c b/tpws/tpws_conn.c index 07e6d49..3597ae8 100644 --- a/tpws/tpws_conn.c +++ b/tpws/tpws_conn.c @@ -368,7 +368,7 @@ static void set_user_timeout(int fd, int timeout) //Createas a socket and initiates the connection to the host specified by //remote_addr. //Returns -1 if something fails, >0 on success (socket fd). -static int connect_remote(const struct sockaddr *remote_addr, bool bApplyConnectionFooling) +static int connect_remote(const struct sockaddr *remote_addr, int mss) { int remote_fd = 0, yes = 1, no = 0; @@ -406,24 +406,18 @@ static int connect_remote(const struct sockaddr *remote_addr, bool bApplyConnect close(remote_fd); return -1; } - if (bApplyConnectionFooling && params.mss) +#ifdef __linux__ + if (mss) { - uint16_t port = saport(remote_addr); - if (pf_in_range(port,¶ms.mss_pf)) + VPRINT("Setting MSS %d\n", mss); + if (setsockopt(remote_fd, IPPROTO_TCP, TCP_MAXSEG, &mss, sizeof(int)) <0) { - VPRINT("Setting MSS %d\n",params.mss); - if (setsockopt(remote_fd, IPPROTO_TCP, TCP_MAXSEG, ¶ms.mss, sizeof(int)) <0) - { - DLOG_PERROR("setsockopt (TCP_MAXSEG, connect_remote)"); - close(remote_fd); - return -1; - } - } - else - { - VPRINT("Not setting MSS. Port %u is out of MSS port range.\n",port); + DLOG_PERROR("setsockopt (TCP_MAXSEG, connect_remote)"); + close(remote_fd); + return -1; } } +#endif // if no bind address specified - address family will be 0 in params_connect_bindX if(remote_addr->sa_family == params.connect_bind4.sin_family) @@ -494,13 +488,12 @@ static tproxy_conn_t *new_conn(int fd, bool remote) tproxy_conn_t *conn; //Create connection object and fill in information - if((conn = (tproxy_conn_t*) malloc(sizeof(tproxy_conn_t))) == NULL) + if((conn = (tproxy_conn_t*) calloc(1, sizeof(tproxy_conn_t))) == NULL) { DLOG_ERR("Could not allocate memory for connection\n"); return NULL; } - memset(conn, 0, sizeof(tproxy_conn_t)); conn->state = CONN_UNAVAILABLE; conn->fd = fd; conn->remote = remote; @@ -508,7 +501,7 @@ static tproxy_conn_t *new_conn(int fd, bool remote) #ifdef SPLICE_PRESENT // if dont tamper - both legs are spliced, create 2 pipes // otherwise create pipe only in local leg - if (!params.nosplice && ( !remote || !params.tamper || params.tamper_start || params.tamper_cutoff ) && pipe2(conn->splice_pipe, O_NONBLOCK) != 0) + if (!params.nosplice && ( !remote || !params.tamper || params.tamper_lim ) && pipe2(conn->splice_pipe, O_NONBLOCK) != 0) { DLOG_ERR("Could not create the splice pipe\n"); free_conn(conn); @@ -606,7 +599,7 @@ static tproxy_conn_t* add_tcp_connection(int efd, struct tailhead *conn_list,int if (proxy_type==CONN_TYPE_TRANSPARENT) { - if ((remote_fd = connect_remote((struct sockaddr *)&orig_dst, true)) < 0) + if ((remote_fd = connect_remote((struct sockaddr *)&orig_dst, 0)) < 0) { DLOG_ERR("Failed to connect\n"); close(local_fd); @@ -626,6 +619,8 @@ static tproxy_conn_t* add_tcp_connection(int efd, struct tailhead *conn_list,int if (proxy_type==CONN_TYPE_TRANSPARENT) { + sacopy(&conn->dest, (struct sockaddr *)&orig_dst); + if(!(conn->partner = new_conn(remote_fd, true))) { free_conn(conn); @@ -667,6 +662,10 @@ static tproxy_conn_t* add_tcp_connection(int efd, struct tailhead *conn_list,int TAILQ_INSERT_HEAD(conn_list, conn->partner, conn_ptrs); legs_remote++; } + + if (proxy_type==CONN_TYPE_TRANSPARENT) + apply_desync_profile(&conn->track, (struct sockaddr *)&conn->dest); + return conn; } @@ -782,32 +781,26 @@ static bool handle_unsent(tproxy_conn_t *conn) } -bool proxy_mode_connect_remote(const struct sockaddr *sa, tproxy_conn_t *conn, struct tailhead *conn_list) +static bool proxy_mode_connect_remote(tproxy_conn_t *conn, struct tailhead *conn_list) { int remote_fd; if (params.debug>=1) { char ip_port[48]; - ntop46_port(sa,ip_port,sizeof(ip_port)); + ntop46_port((struct sockaddr *)&conn->dest,ip_port,sizeof(ip_port)); VPRINT("socks target for fd=%d is : %s\n", conn->fd, ip_port); } - if (check_local_ip((struct sockaddr *)sa)) + if (check_local_ip((struct sockaddr *)&conn->dest)) { VPRINT("Dropping connection to local address for security reasons\n"); socks_send_rep(conn->socks_ver, conn->fd, S5_REP_NOT_ALLOWED_BY_RULESET); return false; } - bool bConnFooling=true; - if (conn->track.hostname && params.mss) - { - bConnFooling=HostlistCheck(conn->track.hostname, NULL); - if (!bConnFooling) - VPRINT("0-phase desync hostlist check negative. not acting on this connection.\n"); - } + apply_desync_profile(&conn->track, (struct sockaddr *)&conn->dest); - if ((remote_fd = connect_remote(sa, bConnFooling)) < 0) + if ((remote_fd = connect_remote((struct sockaddr *)&conn->dest, conn->track.dp ? conn->track.dp->mss : 0)) < 0) { DLOG_ERR("socks failed to connect (1) errno=%d\n", errno); socks_send_rep_errno(conn->socks_ver, conn->fd, errno); @@ -845,7 +838,6 @@ static bool handle_proxy_mode(tproxy_conn_t *conn, struct tailhead *conn_list) ssize_t rd,wr; char buf[sizeof(s5_req)]; // s5_req - the largest possible req - struct sockaddr_storage ss; // receive proxy control message rd=recv(conn->fd, buf, sizeof(buf), MSG_DONTWAIT); @@ -928,10 +920,10 @@ static bool handle_proxy_mode(tproxy_conn_t *conn, struct tailhead *conn_list) socks4_send_rep(conn->fd, S4_REP_FAILED); return false; } - ss.ss_family = AF_INET; - ((struct sockaddr_in*)&ss)->sin_port = m->port; - ((struct sockaddr_in*)&ss)->sin_addr.s_addr = m->ip; - return proxy_mode_connect_remote((struct sockaddr *)&ss, conn, conn_list); + conn->dest.ss_family = AF_INET; + ((struct sockaddr_in*)&conn->dest)->sin_port = m->port; + ((struct sockaddr_in*)&conn->dest)->sin_addr.s_addr = m->ip; + return proxy_mode_connect_remote(conn, conn_list); } break; case S_WAIT_REQUEST: @@ -960,16 +952,16 @@ static bool handle_proxy_mode(tproxy_conn_t *conn, struct tailhead *conn_list) switch(m->atyp) { case S5_ATYP_IP4: - ss.ss_family = AF_INET; - ((struct sockaddr_in*)&ss)->sin_port = m->d4.port; - ((struct sockaddr_in*)&ss)->sin_addr = m->d4.addr; + conn->dest.ss_family = AF_INET; + ((struct sockaddr_in*)&conn->dest)->sin_port = m->d4.port; + ((struct sockaddr_in*)&conn->dest)->sin_addr = m->d4.addr; break; case S5_ATYP_IP6: - ss.ss_family = AF_INET6; - ((struct sockaddr_in6*)&ss)->sin6_port = m->d6.port; - ((struct sockaddr_in6*)&ss)->sin6_addr = m->d6.addr; - ((struct sockaddr_in6*)&ss)->sin6_flowinfo = 0; - ((struct sockaddr_in6*)&ss)->sin6_scope_id = 0; + conn->dest.ss_family = AF_INET6; + ((struct sockaddr_in6*)&conn->dest)->sin6_port = m->d6.port; + ((struct sockaddr_in6*)&conn->dest)->sin6_addr = m->d6.addr; + ((struct sockaddr_in6*)&conn->dest)->sin6_flowinfo = 0; + ((struct sockaddr_in6*)&conn->dest)->sin6_scope_id = 0; break; case S5_ATYP_DOM: { @@ -1006,7 +998,7 @@ static bool handle_proxy_mode(tproxy_conn_t *conn, struct tailhead *conn_list) return false; // should not be here. S5_REQ_CONNECT_VALID checks for valid atyp } - return proxy_mode_connect_remote((struct sockaddr *)&ss,conn,conn_list); + return proxy_mode_connect_remote(conn,conn_list); } break; case S_WAIT_RESOLVE: @@ -1045,7 +1037,8 @@ static bool resolve_complete(struct resolve_item *ri, struct tailhead *conn_list DBGPRINT("resolve_complete put hostname : %s\n", ri->dom); conn->track.hostname = strdup(ri->dom); } - return proxy_mode_connect_remote((struct sockaddr *)&ri->ss,conn,conn_list); + sacopy(&conn->dest, (struct sockaddr *)&ri->ss); + return proxy_mode_connect_remote(conn,conn_list); } } else @@ -1060,8 +1053,17 @@ static bool resolve_complete(struct resolve_item *ri, struct tailhead *conn_list static bool in_tamper_out_range(tproxy_conn_t *conn) { - return (params.tamper_start_n ? (conn->tnrd+1) : conn->trd) >= params.tamper_start && - (!params.tamper_cutoff || (params.tamper_cutoff_n ? (conn->tnrd+1) : conn->trd) < params.tamper_cutoff); + if (!conn->track.dp) return true; + bool in_range = \ + ((conn->track.dp->tamper_start_n ? (conn->tnrd+1) : conn->trd) >= conn->track.dp->tamper_start && + (!conn->track.dp->tamper_cutoff || (conn->track.dp->tamper_cutoff_n ? (conn->tnrd+1) : conn->trd) < conn->track.dp->tamper_cutoff)); + DBGPRINT("tamper_out range check. stream pos %" PRIu64 "(n%" PRIu64 "). tamper range %s%u-%s%u (%s)\n", + conn->trd, conn->tnrd+1, + conn->track.dp ? conn->track.dp->tamper_start_n ? "n" : "" : "?" , conn->track.dp ? conn->track.dp->tamper_start : 0, + conn->track.dp ? conn->track.dp->tamper_cutoff_n ? "n" : "" : "?" , conn->track.dp ? conn->track.dp->tamper_cutoff : 0, + in_range ? "IN RANGE" : "OUT OF RANGE"); + return in_range; + } 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) @@ -1072,33 +1074,26 @@ static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_ if (conn->remote) { if (conn_partner_alive(conn) && !conn->partner->track.bTamperInCutoff) - { tamper_in(&conn->partner->track,segment,segment_buffer_size,segment_size); - } } else { - bool in_range = in_tamper_out_range(conn); - DBGPRINT("tamper_out stream pos %" PRIu64 "(n%" PRIu64 "). tamper range %s%u-%s%u (%s)\n", - conn->trd, conn->tnrd+1, - params.tamper_start_n ? "n" : "" , params.tamper_start, - params.tamper_cutoff_n ? "n" : "" , params.tamper_cutoff, - in_range ? "IN RANGE" : "OUT OF RANGE"); - if (in_range) tamper_out(&conn->track,segment,segment_buffer_size,segment_size,split_pos,split_flags); + if (in_tamper_out_range(conn)) + tamper_out(&conn->track,(struct sockaddr*)&conn->dest,segment,segment_buffer_size,segment_size,split_pos,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) +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) { ssize_t wr; if (oob) { - VPRINT("Sending OOB byte %02X\n", params.oob_byte); + VPRINT("Sending OOB byte %02X\n", oob_byte); uint8_t oob_save; oob_save = buf[len]; - buf[len] = params.oob_byte; + buf[len] = oob_byte; wr = send_or_buffer(sb, fd, buf, len+1, MSG_OOB, ttl); buf[len] = oob_save; } @@ -1211,7 +1206,7 @@ static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32 { VPRINT("Splitting at pos %zu%s\n", split_pos, (split_flags & SPLIT_FLAG_DISORDER) ? " with disorder" : ""); - wr = send_or_buffer_oob(conn->partner->wr_buf, conn->partner->fd, buf, split_pos, !!(split_flags & SPLIT_FLAG_DISORDER), !!(split_flags & SPLIT_FLAG_OOB)); + wr = send_or_buffer_oob(conn->partner->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) { diff --git a/tpws/tpws_conn.h b/tpws/tpws_conn.h index 31abbd2..c52cbc8 100644 --- a/tpws/tpws_conn.h +++ b/tpws/tpws_conn.h @@ -54,6 +54,7 @@ struct tproxy_conn int splice_pipe[2]; conn_state_t state; conn_type_t conn_type; + struct sockaddr_storage dest; struct tproxy_conn *partner; // other leg time_t orphan_since;