tpws: multi-strategy

This commit is contained in:
bol-van 2024-09-19 21:06:58 +03:00
parent 9684e647ab
commit d4a7eef17e
12 changed files with 624 additions and 323 deletions

View File

@ -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;
}

View File

@ -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)

View File

@ -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(&params.hostlist, &params.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, &params.desync_profiles, next)
if (!LoadIncludeHostListsForProfile(&dpl->dp))
return false;
return true;
}
bool LoadExcludeHostLists()
{
return LoadHostLists(&params.hostlist_exclude, &params.hostlist_exclude_files);
struct desync_profile_list *dpl;
LIST_FOREACH(dpl, &params.desync_profiles, next)
if (!LoadHostLists(&dpl->dp.hostlist_exclude, &dpl->dp.hostlist_exclude_files))
return false;
return true;
}

View File

@ -2,6 +2,7 @@
#include <stdbool.h>
#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);
bool HostlistCheck(struct desync_profile *dp,const char *host, bool *excluded);

View File

@ -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(&params.desync_profiles);
if (dpl)
{
while ((dpn=LIST_NEXT(dpl,next))) dpl = dpn;
LIST_INSERT_AFTER(dpl, entry, next);
}
else
LIST_INSERT_HEAD(&params.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;
}

View File

@ -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;

View File

@ -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)

View File

@ -1,24 +1,87 @@
#define _GNU_SOURCE
#include "tamper.h"
#include "params.h"
#include "hostlist.h"
#include "protocol.h"
#include "helpers.h"
#include <string.h>
#include <stdio.h>
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(&params.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 && *size<segment_buffer_size)
if (ctrack->dp->methodspace && *size<segment_buffer_size)
{
// we only work with data blocks looking as HTTP query, so method is at the beginning
VPRINT("Adding extra space after method\n");
@ -90,20 +196,20 @@ void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,si
(*size)++; // block will grow by 1 byte
if (pHost) pHost++; // Host: position will move by 1 byte
}
if ((params.hostdot || params.hosttab) && *size<segment_buffer_size && HttpFindHost(&pHost,segment,*size))
if ((ctrack->dp->hostdot || ctrack->dp->hosttab) && *size<segment_buffer_size && HttpFindHost(&pHost,segment,*size))
{
p = pHost + 5;
while (p < (segment + *size) && *p != '\r' && *p != '\n') p++;
if (p < (segment + *size))
{
pos = p - segment;
VPRINT("Adding %s to host name at pos %zu\n", params.hostdot ? "dot" : "tab", pos);
VPRINT("Adding %s to host name at pos %zu\n", ctrack->dp->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: <padding?[\r\n|\n]
char s[8];
size_t hsize = params.unixeol ? 8 : 9;
size_t hostpad = params.hostpad<hsize ? hsize : params.hostpad;
size_t hsize = ctrack->dp->unixeol ? 8 : 9;
size_t hostpad = ctrack->dp->hostpad<hsize ? hsize : ctrack->dp->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(&params.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(&params.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(&params.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(&params.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(&params.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(&params.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(&params.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);
}
}
}

View File

@ -4,6 +4,8 @@
#include <stdint.h>
#include <sys/types.h>
#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);

View File

@ -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, &params.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|@<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"
"\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=<filename>\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=<filename>\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=<filename>\t\t; detect DPI blocks and build hostlist automatically\n"
@ -203,7 +213,6 @@ static void exithelp(void)
" --tlsrec-pos=<pos>\t\t\t; make 2 TLS records. split at specified pos\n"
#ifdef __linux__
" --mss=<int>\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]<pos>\t\t; start tampering only from specified outbound stream position. default is 0. 'n' means data block number.\n"
" --tamper-cutoff=[n]<pos>\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(&params.hostlist_files);
strlist_destroy(&params.hostlist_exclude_files);
StrPoolDestroy(&params.hostlist_exclude);
StrPoolDestroy(&params.hostlist);
HostFailPoolDestroy(&params.hostlist_auto_fail_counters);
dp_list_destroy(&params.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(&params, 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(&params.hostlist_files);
LIST_INIT(&params.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(&params.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, &params.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, &params.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, &params.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(&params.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(&params.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(&params.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(&params.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,&params.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(&params.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, &params.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(&params.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

View File

@ -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,&params.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, &params.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)
{

View File

@ -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;