tpws: multisplit

This commit is contained in:
bol-van 2024-11-13 22:05:06 +03:00
parent ef9f9ae428
commit 46d31003e2
11 changed files with 532 additions and 164 deletions

View File

@ -1,8 +1,8 @@
#define _GNU_SOURCE
#include "helpers.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <sys/socket.h>
@ -11,6 +11,29 @@
#include <time.h>
#include <sys/stat.h>
#include "helpers.h"
int unique_size_t(size_t *pu, int ct)
{
int i, j, u;
for (i = j = 0; j < ct; i++)
{
u = pu[j++];
for (; j < ct && pu[j] == u; j++);
pu[i] = u;
}
return i;
}
static int cmp_size_t(const void * a, const void * b)
{
return *(size_t*)a < *(size_t*)b ? -1 : *(size_t*)a > *(size_t*)b;
}
void qsort_size_t(size_t *array,size_t ct)
{
qsort(array,ct,sizeof(*array),cmp_size_t);
}
void rtrim(char *s)
{
if (s)

View File

@ -15,6 +15,9 @@ typedef union
struct sockaddr_in6 sa6; // size 28
} sockaddr_in46;
int unique_size_t(size_t *pu, int ct);
void qsort_size_t(size_t *array,size_t ct);
void rtrim(char *s);
void replace_char(char *s, char from, char to);
char *strncasestr(const char *s,const char *find, size_t slen);

View File

@ -139,6 +139,22 @@ int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...)
return 0;
}
void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit)
{
size_t k;
bool bcut = false;
if (size > limit)
{
size = limit;
bcut = true;
}
if (!size) return;
for (k = 0; k < size; k++) VPRINT("%02X ", data[k]);
VPRINT(bcut ? "... : " : ": ");
for (k = 0; k < size; k++) VPRINT("%c", data[k] >= 0x20 && data[k] <= 0x7F ? (char)data[k] : '.');
if (bcut) VPRINT(" ...");
}
struct desync_profile_list *dp_list_add(struct desync_profile_list_head *head)
{

View File

@ -29,6 +29,8 @@ struct bind_s
int bind_wait_ifup,bind_wait_ip,bind_wait_ip_ll;
};
#define MAX_SPLITS 16
enum log_target { LOG_TARGET_CONSOLE=0, LOG_TARGET_FILE, LOG_TARGET_SYSLOG };
struct desync_profile
@ -38,16 +40,16 @@ struct desync_profile
bool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol, domcase;
int hostpad;
char hostspell[4];
enum httpreqpos split_http_req;
enum tlspos tlsrec;
int tlsrec_pos;
enum tlspos split_tls;
bool split_any_protocol;
int split_pos;
bool disorder, disorder_http, disorder_tls;
bool oob, oob_http, oob_tls;
uint8_t oob_byte;
// multisplit
struct proto_pos splits[MAX_SPLITS];
int split_count;
struct proto_pos tlsrec;
int mss;
bool tamper_start_n,tamper_cutoff_n;
@ -140,6 +142,7 @@ int DLOG_CONDUP(const char *format, ...);
int DLOG_ERR(const char *format, ...);
int DLOG_PERROR(const char *s);
int HOSTLIST_DEBUGLOG_APPEND(const char *format, ...);
void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit);
#define VPRINT(format, ...) DLOG(format, 1, ##__VA_ARGS__)
#define DBGPRINT(format, ...) DLOG(format, 2, ##__VA_ARGS__)

View File

@ -24,6 +24,133 @@ static bool FindNLD(const uint8_t *dom, size_t dlen, int level, const uint8_t **
return true;
}
const char *l7proto_str(t_l7proto l7)
{
switch(l7)
{
case HTTP: return "http";
case TLS: return "tls";
case QUIC: return "quic";
case WIREGUARD: return "wireguard";
case DHT: return "dht";
default: return "unknown";
}
}
bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7)
{
return (l7proto==UNKNOWN && (filter_l7 & L7_PROTO_UNKNOWN)) ||
(l7proto==HTTP && (filter_l7 & L7_PROTO_HTTP)) ||
(l7proto==TLS && (filter_l7 & L7_PROTO_TLS)) ||
(l7proto==QUIC && (filter_l7 & L7_PROTO_QUIC)) ||
(l7proto==WIREGUARD && (filter_l7 & L7_PROTO_WIREGUARD)) ||
(l7proto==DHT && (filter_l7 & L7_PROTO_DHT));
}
#define PM_ABS 0
#define PM_HOST 1
#define PM_HOST_END 2
#define PM_HOST_SLD 3
#define PM_HOST_MIDSLD 4
#define PM_HOST_ENDSLD 5
#define PM_HTTP_METHOD 6
#define PM_SNI_EXT 7
bool IsHostMarker(uint8_t posmarker)
{
switch(posmarker)
{
case PM_HOST:
case PM_HOST_END:
case PM_HOST_SLD:
case PM_HOST_MIDSLD:
case PM_HOST_ENDSLD:
return true;
default:
return false;
}
}
const char *posmarker_name(uint8_t posmarker)
{
switch(posmarker)
{
case PM_ABS: return "abs";
case PM_HOST: return "host";
case PM_HOST_END: return "endhost";
case PM_HOST_SLD: return "sld";
case PM_HOST_MIDSLD: return "midsld";
case PM_HOST_ENDSLD: return "endsld";
case PM_HTTP_METHOD: return "method";
case PM_SNI_EXT: return "sniext";
default: return "?";
}
}
static size_t CheckPos(size_t sz, ssize_t offset)
{
return (offset>=0 && offset<sz) ? offset : 0;
}
size_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz)
{
ssize_t offset;
switch(posmarker)
{
case PM_ABS:
offset = (pos<0) ? sz+pos : pos;
return CheckPos(sz,offset);
default:
return 0;
}
}
static size_t HostPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz, size_t offset_host, size_t len_host)
{
ssize_t offset;
const uint8_t *p;
size_t slen;
switch(posmarker)
{
case PM_HOST:
offset = offset_host+pos;
break;
case PM_HOST_END:
offset = offset_host+len_host+pos;
break;
case PM_HOST_SLD:
case PM_HOST_MIDSLD:
case PM_HOST_ENDSLD:
if (((offset_host+len_host)<=sz) && FindNLD(data+offset_host,len_host,2,&p,&slen))
offset = (posmarker==PM_HOST_SLD ? p-data : posmarker==PM_HOST_ENDSLD ? p-data+slen : slen==1 ? p+1-data : p+slen/2-data) + pos;
else
offset = 0;
break;
}
return CheckPos(sz,offset);
}
size_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp)
{
switch(l7proto)
{
case HTTP:
return HttpPos(sp->marker, sp->pos, data, sz);
case TLS:
return TLSPos(sp->marker, sp->pos, data, sz);
default:
return AnyProtoPos(sp->marker, sp->pos, data, sz);
}
}
void ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count)
{
int i,j;
for(i=j=0;i<split_count;i++)
{
pos[j] = ResolvePos(data,sz,l7proto,splits+i);
if (pos[j]) j++;
}
qsort_size_t(pos, j);
j=unique_size_t(pos, j);
*pos_count=j;
}
const char *http_methods[] = { "GET /","POST /","HEAD /","OPTIONS /","PUT /","DELETE /","CONNECT /","TRACE /",NULL };
const char *HttpMethod(const uint8_t *data, size_t len)
{
@ -169,36 +296,45 @@ bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *
// compare 2nd level domains
return strcasecmp(dhost, drhost)!=0;
}
size_t HttpPos(enum httpreqpos tpos_type, size_t hpos_pos, const uint8_t *http, size_t sz)
size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz)
{
const uint8_t *method, *host=NULL;
const uint8_t *method, *host=NULL, *p;
size_t offset_host,len_host;
ssize_t offset;
int i;
switch(tpos_type)
switch(posmarker)
{
case httpreqpos_method:
case PM_HTTP_METHOD:
// recognize some tpws pre-applied hacks
method=http;
method=data;
if (sz<10) break;
if (*method=='\n' || *method=='\r') method++;
if (*method=='\n' || *method=='\r') method++;
for (i=0;i<7;i++) if (*method>='A' && *method<='Z') method++;
if (i<3 || *method!=' ') break;
return method-http-1;
case httpreqpos_host:
if (HttpFindHostConst(&host,http,sz) && (host-http+7)<sz)
for (p=method,i=0;i<7;i++) if (*p>='A' && *p<='Z') p++;
if (i<3 || *p!=' ') break;
return CheckPos(sz,method-data+pos);
case PM_HOST:
case PM_HOST_END:
case PM_HOST_SLD:
case PM_HOST_MIDSLD:
case PM_HOST_ENDSLD:
if (HttpFindHostConst(&host,data,sz) && (host-data+7)<sz)
{
host+=5;
if (*host==' ') host++;
return host-http;
if (*host==' ' || *host=='\t') host++;
offset_host = host-data;
if (posmarker!=PM_HOST)
for (len_host=0; (offset_host+len_host)<sz && data[offset_host+len_host]!='\r' && data[offset_host+len_host]!='\n'; len_host++);
else
len_host = 0;
return HostPos(posmarker,pos,data,sz,offset_host,len_host);
}
break;
case httpreqpos_pos:
break;
default:
return 0;
return AnyProtoPos(posmarker,pos,data,sz);
}
return hpos_pos<sz ? hpos_pos : 0;
return 0;
}
@ -361,26 +497,37 @@ static bool TLSHelloFindMiddleOfSLDInSNI(const uint8_t *ext, size_t elen, const
*p = (len==1) ? *p+1 : *p+len/2;
return true;
}
size_t TLSPos(enum tlspos tpos_type, size_t tpos_pos, const uint8_t *tls, size_t sz, uint8_t type)
size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz)
{
size_t elen;
const uint8_t *ext, *p;
switch(tpos_type)
size_t offset_host,len_host;
ssize_t offset;
switch(posmarker)
{
case tlspos_sni:
case tlspos_sniext:
if (TLSFindExt(tls,sz,0,&ext,&elen,false))
return (tpos_type==tlspos_sni) ? ext-tls+6 : ext-tls+1;
break;
case tlspos_snisld:
if (TLSFindExt(tls,sz,0,&ext,&elen,false))
if (TLSHelloFindMiddleOfSLDInSNI(ext,elen,&p))
return p-tls;
break;
case tlspos_pos:
break;
default:
case PM_HOST:
case PM_HOST_END:
case PM_HOST_SLD:
case PM_HOST_MIDSLD:
case PM_HOST_ENDSLD:
case PM_SNI_EXT:
if (TLSFindExt(data,sz,0,&ext,&elen,false))
{
if (posmarker==PM_SNI_EXT)
{
return CheckPos(sz,ext-data+pos);
}
else
{
if (!TLSAdvanceToHostInSNI(&ext,&elen,&len_host))
return 0;
offset_host = ext-data;
return HostPos(posmarker,pos,data,sz,offset_host,len_host);
}
}
return 0;
default:
return AnyProtoPos(posmarker,pos,data,sz);
}
return tpos_pos<sz ? tpos_pos : 0;
}

View File

@ -4,6 +4,40 @@
#include <stdint.h>
#include <stdbool.h>
typedef enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT} t_l7proto;
#define L7_PROTO_HTTP 0x00000001
#define L7_PROTO_TLS 0x00000002
#define L7_PROTO_QUIC 0x00000004
#define L7_PROTO_WIREGUARD 0x00000008
#define L7_PROTO_DHT 0x00000010
#define L7_PROTO_UNKNOWN 0x80000000
const char *l7proto_str(t_l7proto l7);
bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7);
// pos markers
#define PM_ABS 0
#define PM_HOST 1
#define PM_HOST_END 2
#define PM_HOST_SLD 3
#define PM_HOST_MIDSLD 4
#define PM_HOST_ENDSLD 5
#define PM_HTTP_METHOD 6
#define PM_SNI_EXT 7
struct proto_pos
{
int16_t pos;
uint8_t marker;
};
#define PROTO_POS_EMPTY(sp) ((sp)->marker==PM_ABS && (sp)->pos==0)
bool IsHostMarker(uint8_t posmarker);
const char *posmarker_name(uint8_t posmarker);
size_t AnyProtoPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz);
size_t HttpPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz);
size_t TLSPos(uint8_t posmarker, int16_t pos, const uint8_t *data, size_t sz);
size_t ResolvePos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *sp);
void ResolveMultiPos(const uint8_t *data, size_t sz, t_l7proto l7proto, const struct proto_pos *splits, int split_count, size_t *pos, int *pos_count);
extern const char *http_methods[9];
const char *HttpMethod(const uint8_t *data, size_t len);
bool IsHttp(const uint8_t *data, size_t len);
@ -18,8 +52,6 @@ const char *HttpFind2ndLevelDomain(const char *host);
int HttpReplyCode(const uint8_t *data, size_t len);
// must be pre-checked by IsHttpReply
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host);
enum httpreqpos { httpreqpos_none = 0, httpreqpos_method, httpreqpos_host, httpreqpos_pos };
size_t HttpPos(enum httpreqpos tpos_type, size_t hpos_pos, const uint8_t *http, size_t sz);
uint16_t TLSRecordDataLen(const uint8_t *data);
size_t TLSRecordLen(const uint8_t *data);
@ -29,5 +61,3 @@ bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **
bool TLSFindExtInHandshake(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext, bool bPartialIsOK);
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK);
bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *host, size_t len_host, bool bPartialIsOK);
enum tlspos { tlspos_none = 0, tlspos_sni, tlspos_sniext, tlspos_snisld, tlspos_pos };
size_t TLSPos(enum tlspos tpos_type, size_t tpos_pos, const uint8_t *tls, size_t sz, uint8_t type);

View File

@ -221,7 +221,7 @@ bool resolver_init(int threads, int fd_signal_pipe)
if (pthread_attr_init(&attr)) goto ex;
// set minimum thread stack size
if (pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN>20480 ? PTHREAD_STACK_MIN : 20480))
if (pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN>32768 ? PTHREAD_STACK_MIN : 32768))
{
pthread_attr_destroy(&attr);
goto ex;

View File

@ -8,22 +8,6 @@
#include "protocol.h"
#include "helpers.h"
const char *l7proto_str(t_l7proto l7)
{
switch(l7)
{
case HTTP: return "http";
case TLS: return "tls";
default: return "unknown";
}
}
static bool l7_proto_match(t_l7proto l7proto, uint32_t filter_l7)
{
return (l7proto==UNKNOWN && (filter_l7 & L7_PROTO_UNKNOWN)) ||
(l7proto==HTTP && (filter_l7 & L7_PROTO_HTTP)) ||
(l7proto==TLS && (filter_l7 & L7_PROTO_TLS));
}
static bool dp_match(struct desync_profile *dp, const struct sockaddr *dest, const char *hostname, t_l7proto l7proto)
{
bool bHostlistsEmpty;
@ -87,11 +71,10 @@ void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest)
// segment buffer has at least 5 extra bytes to extend data block
void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos, uint8_t *split_flags)
void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags)
{
uint8_t *p, *pp, *pHost = NULL;
size_t method_len = 0, pos;
size_t tpos, spos;
size_t method_len = 0, pos, tpos, orig_size=*size;
const char *method;
bool bHaveHost = false;
char *pc, Host[256];
@ -116,8 +99,8 @@ void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,
return;
}
*split_pos=0;
*split_flags=0;
if (multisplit_count) *multisplit_count=0;
if (split_flags) *split_flags=0;
if ((method = HttpMethod(segment,*size)))
{
@ -193,7 +176,6 @@ void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,
return;
}
}
switch(l7proto)
{
case HTTP:
@ -325,22 +307,26 @@ void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,
pHost = NULL; // invalidate
}
}
*split_pos = HttpPos(ctrack->dp->split_http_req, ctrack->dp->split_pos, segment, *size);
if (ctrack->dp->disorder_http) *split_flags |= SPLIT_FLAG_DISORDER;
if (ctrack->dp->oob_http) *split_flags |= SPLIT_FLAG_OOB;
if (multisplit_pos) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count);
if (split_flags)
{
if (ctrack->dp->disorder_http) *split_flags |= SPLIT_FLAG_DISORDER;
if (ctrack->dp->oob_http) *split_flags |= SPLIT_FLAG_OOB;
}
break;
case TLS:
spos = TLSPos(ctrack->dp->split_tls, ctrack->dp->split_pos, segment, *size, 0);
if (multisplit_pos) ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count);
if ((5+*size)<=segment_buffer_size)
{
tpos = TLSPos(ctrack->dp->tlsrec, ctrack->dp->tlsrec_pos+5, segment, *size, 0);
tpos = ResolvePos(segment, *size, l7proto, &ctrack->dp->tlsrec);
if (tpos>5)
{
// construct 2 TLS records from one
uint16_t l = pntoh16(segment+3); // length
if (l>=2)
{
int i;
// length is checked in IsTLSClientHello and cannot exceed buffer size
if ((tpos-5)>=l) tpos=5+1;
VPRINT("making 2 TLS records at pos %zu\n",tpos);
@ -351,27 +337,40 @@ void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,
phton16(segment+tpos+3,l-(tpos-5));
phton16(segment+3,tpos-5);
*size += 5;
// split pos present and it is not before tlsrec split. increase split pos by tlsrec header size (5 bytes)
if (spos && spos>=tpos) spos+=5;
// fix split positions after tlsrec. increase split pos by tlsrec header size (5 bytes)
if (multisplit_pos)
for(i=0;i<*multisplit_count;i++)
if (multisplit_pos[i]>tpos) multisplit_pos[i]+=5;
}
}
}
if (spos && spos < *size)
*split_pos = spos;
if (ctrack->dp->disorder_tls) *split_flags |= SPLIT_FLAG_DISORDER;
if (ctrack->dp->oob_tls) *split_flags |= SPLIT_FLAG_OOB;
if (split_flags)
{
if (ctrack->dp->disorder_tls) *split_flags |= SPLIT_FLAG_DISORDER;
if (ctrack->dp->oob_tls) *split_flags |= SPLIT_FLAG_OOB;
}
break;
default:
if (ctrack->dp->split_any_protocol && ctrack->dp->split_pos < *size)
*split_pos = ctrack->dp->split_pos;
if (multisplit_pos && ctrack->dp->split_any_protocol)
ResolveMultiPos(segment, *size, l7proto, ctrack->dp->splits, ctrack->dp->split_count, multisplit_pos, multisplit_count);
}
if (split_flags)
{
if (ctrack->dp->disorder) *split_flags |= SPLIT_FLAG_DISORDER;
if (ctrack->dp->oob) *split_flags |= SPLIT_FLAG_OOB;
}
if (orig_size!=*size)
{
VPRINT("segment size changed: %zu -> %zu\n", orig_size, *size);
}
if (params.debug && multisplit_count && *multisplit_count)
{
VPRINT("multisplit pos: ");
for (int i=0;i<*multisplit_count;i++) VPRINT("%zu ",multisplit_pos[i]);
VPRINT("\n");
}
if (ctrack->dp->disorder) *split_flags |= SPLIT_FLAG_DISORDER;
if (ctrack->dp->oob) *split_flags |= SPLIT_FLAG_OOB;
}
static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname, const char *client_ip_port, t_l7proto l7proto)

View File

@ -9,12 +9,6 @@
#define SPLIT_FLAG_DISORDER 0x01
#define SPLIT_FLAG_OOB 0x02
typedef enum {UNKNOWN=0, HTTP, TLS} t_l7proto;
#define L7_PROTO_HTTP 1
#define L7_PROTO_TLS 2
#define L7_PROTO_UNKNOWN 0x80000000
const char *l7proto_str(t_l7proto l7);
typedef struct
{
// common state
@ -28,7 +22,7 @@ typedef struct
void apply_desync_profile(t_ctrack *ctrack, const struct sockaddr *dest);
void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos, uint8_t *split_flags);
void tamper_out(t_ctrack *ctrack, const struct sockaddr *dest, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags);
void tamper_in(t_ctrack *ctrack, const struct sockaddr *client, uint8_t *segment,size_t segment_buffer_size,size_t *size);
// connection reset by remote leg
void rst_in(t_ctrack *ctrack, const struct sockaddr *client);

View File

@ -182,9 +182,8 @@ static void exithelp(void)
" --hostlist-auto-fail-time=<int>\t; all failed attemps must be within these seconds (default : %d)\n"
" --hostlist-auto-debug=<logfile>\t; debug auto hostlist positives\n"
"\nTAMPER:\n"
" --split-http-req=method|host\t\t; split at specified logical part of plain http request\n"
" --split-tls=sni|sniext|snisld\t\t; split at specified logical part of TLS ClientHello\n"
" --split-pos=<numeric_offset>\t\t; split at specified pos. split-http-req or split-tls take precedence for http.\n"
" --split-pos=N|-N|marker+N|marker-N\t; comma separated list of split positions\n"
"\t\t\t\t\t; markers: method,host,endhost,sld,endsld,midsld,sniext\n"
" --split-any-protocol\t\t\t; split not only http and https\n"
#if defined(BSD) && !defined(__APPLE__)
" --disorder[=http|tls]\t\t\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\n"
@ -203,8 +202,7 @@ static void exithelp(void)
" --methodspace\t\t\t\t; add extra space after method\n"
" --methodeol\t\t\t\t; add end-of-line before method\n"
" --unixeol\t\t\t\t; replace 0D0A to 0A\n"
" --tlsrec=sni|sniext|snisld\t\t; make 2 TLS records. split at specified logical part. don't split if SNI is not present\n"
" --tlsrec-pos=<pos>\t\t\t; make 2 TLS records. split at specified pos\n"
" --tlsrec=N|-N|marker+N|marker-N\t; make 2 TLS records. split records at specified position.\n"
#ifdef __linux__
" --mss=<int>\t\t\t\t; set client MSS. forces server to split messages but significantly decreases speed !\n"
#endif
@ -276,29 +274,131 @@ void save_default_ttl(void)
}
}
bool parse_httpreqpos(const char *s, enum httpreqpos *pos)
static bool parse_httpreqpos(const char *s, struct proto_pos *sp)
{
if (!strcmp(s, "method"))
*pos = httpreqpos_method;
{
sp->marker = PM_HTTP_METHOD;
sp->pos=2;
}
else if (!strcmp(s, "host"))
*pos = httpreqpos_host;
{
sp->marker = PM_HOST;
sp->pos=1;
}
else
return false;
return true;
}
bool parse_tlspos(const char *s, enum tlspos *pos)
static bool parse_tlspos(const char *s, struct proto_pos *sp)
{
if (!strcmp(s, "sni"))
*pos = tlspos_sni;
{
sp->marker = PM_HOST;
sp->pos=1;
}
else if (!strcmp(s, "sniext"))
*pos = tlspos_sniext;
{
sp->marker = PM_SNI_EXT;
sp->pos=1;
}
else if (!strcmp(s, "snisld"))
*pos = tlspos_snisld;
{
sp->marker = PM_HOST_MIDSLD;
sp->pos=0;
}
else
return false;
return true;
}
static bool parse_int16(const char *p, int16_t *v)
{
if (*p=='+' || *p=='-' || *p>='0' && *p<='9')
{
int i = atoi(p);
*v = (int16_t)i;
return *v==i; // check overflow
}
return false;
}
static bool parse_posmarker(const char *opt, uint8_t *posmarker)
{
if (!strcmp(opt,"host"))
*posmarker = PM_HOST;
else if (!strcmp(opt,"endhost"))
*posmarker = PM_HOST_END;
else if (!strcmp(opt,"sld"))
*posmarker = PM_HOST_SLD;
else if (!strcmp(opt,"midsld"))
*posmarker = PM_HOST_MIDSLD;
else if (!strcmp(opt,"endsld"))
*posmarker = PM_HOST_ENDSLD;
else if (!strcmp(opt,"method"))
*posmarker = PM_HTTP_METHOD;
else if (!strcmp(opt,"sniext"))
*posmarker = PM_SNI_EXT;
else
return false;
return true;
}
static bool parse_split_pos(char *opt, struct proto_pos *split)
{
if (parse_int16(opt,&split->pos))
{
split->marker = PM_ABS;
return !!split->pos;
}
else
{
char c,*p=opt;
bool b;
for (; *opt && *opt!='+' && *opt!='-'; opt++);
c=*opt; *opt=0;
b=parse_posmarker(p,&split->marker);
*opt=c;
if (!b) return false;
if (*opt)
return parse_int16(opt,&split->pos);
else
split->pos = 0;
}
return true;
}
static bool parse_split_pos_list(char *opt, struct proto_pos *splits, int splits_size, int *split_count)
{
char c,*e,*p;
for (p=opt, *split_count=0 ; p && *split_count<splits_size ; (*split_count)++)
{
if ((e = strchr(p,',')))
{
c=*e;
*e=0;
}
if (!parse_split_pos(p,splits+*split_count)) return false;
if (e) *e++=c;
p = e;
}
if (p) return false; // too much splits
return true;
}
static void SplitDebug(void)
{
struct desync_profile_list *dpl;
const struct desync_profile *dp;
int x;
LIST_FOREACH(dpl, &params.desync_profiles, next)
{
dp = &dpl->dp;
for(x=0;x<dp->split_count;x++)
VPRINT("profile %d multisplit %s %d\n",dp->n,posmarker_name(dp->splits[x].marker),dp->splits[x].pos);
if (!PROTO_POS_EMPTY(&dp->tlsrec))
VPRINT("profile %d tlsrec %s %d\n",dp->n,posmarker_name(dp->tlsrec.marker),dp->tlsrec.pos);
}
}
static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6)
{
char *e,*p,c;
@ -682,29 +782,45 @@ void parse_params(int argc, char *argv[])
params.tamper = true;
break;
case 23: /* split-http-req */
if (!parse_httpreqpos(optarg, &dp->split_http_req))
DLOG_CONDUP("WARNING ! --split-http-req is deprecated. use --split-pos with markers.\n",MAX_SPLITS);
if (dp->split_count>=MAX_SPLITS)
{
DLOG_ERR("Too much splits. max splits: %u\n",MAX_SPLITS);
exit_clean(1);
}
if (!parse_httpreqpos(optarg, dp->splits + dp->split_count))
{
DLOG_ERR("Invalid argument for split-http-req\n");
exit_clean(1);
}
dp->split_count++;
params.tamper = true;
break;
case 24: /* split-tls */
if (!parse_tlspos(optarg, &dp->split_tls))
// obsolete arg
DLOG_CONDUP("WARNING ! --split-tls is deprecated. use --split-pos with markers.\n",MAX_SPLITS);
if (dp->split_count>=MAX_SPLITS)
{
DLOG_ERR("Too much splits. max splits: %u\n",MAX_SPLITS);
exit_clean(1);
}
if (!parse_tlspos(optarg, dp->splits + dp->split_count))
{
DLOG_ERR("Invalid argument for split-tls\n");
exit_clean(1);
}
dp->split_count++;
params.tamper = true;
break;
case 25: /* split-pos */
i = atoi(optarg);
if (i>0)
dp->split_pos = i;
else
{
DLOG_ERR("Invalid argument for split-pos\n");
exit_clean(1);
int ct;
if (!parse_split_pos_list(optarg,dp->splits+dp->split_count,MAX_SPLITS-dp->split_count,&ct))
{
DLOG_ERR("could not parse split pos list or too much positions (before parsing - %u, max - %u) : %s\n",dp->split_count,MAX_SPLITS,optarg);
exit_clean(1);
}
dp->split_count += ct;
}
params.tamper = true;
break;
@ -724,7 +840,6 @@ void parse_params(int argc, char *argv[])
}
else
dp->disorder = true;
save_default_ttl();
break;
case 28: /* oob */
if (optarg)
@ -770,7 +885,7 @@ void parse_params(int argc, char *argv[])
params.tamper = true;
break;
case 34: /* tlsrec */
if (!parse_tlspos(optarg, &dp->tlsrec))
if (!parse_split_pos(optarg, &dp->tlsrec) && !parse_tlspos(optarg, &dp->tlsrec))
{
DLOG_ERR("Invalid argument for tlsrec\n");
exit_clean(1);
@ -778,9 +893,11 @@ void parse_params(int argc, char *argv[])
params.tamper = true;
break;
case 35: /* tlsrec-pos */
if ((dp->tlsrec_pos = atoi(optarg))>0)
dp->tlsrec = tlspos_pos;
else
// obsolete arg
i = atoi(optarg);
dp->tlsrec.marker = PM_ABS;
dp->tlsrec.pos = (int16_t)i;
if (!dp->tlsrec.pos || i!=dp->tlsrec.pos)
{
DLOG_ERR("Invalid argument for tlsrec-pos\n");
exit_clean(1);
@ -823,8 +940,6 @@ void parse_params(int argc, char *argv[])
DLOG_ERR("gzipped auto hostlists are not supported\n");
exit_clean(1);
}
if (params.droproot && chown(optarg, params.uid, -1))
DLOG_ERR("could not chown %s. auto hostlist file may not be writable after privilege drop\n", optarg);
}
if (!(dp->hostlist_auto=RegisterHostlist(dp, false, optarg)))
{
@ -858,8 +973,6 @@ void parse_params(int argc, char *argv[])
exit_clean(1);
}
fclose(F);
if (params.droproot && chown(optarg, params.uid, -1))
DLOG_ERR("could not chown %s. auto hostlist debug log may not be writable after privilege drop\n", optarg);
strncpy(params.hostlist_auto_debuglog, optarg, sizeof(params.hostlist_auto_debuglog));
params.hostlist_auto_debuglog[sizeof(params.hostlist_auto_debuglog) - 1] = '\0';
}
@ -881,8 +994,6 @@ void parse_params(int argc, char *argv[])
fprintf(stderr, "cannot create %s\n", params.debug_logfile);
exit_clean(1);
}
if (params.droproot && chown(params.debug_logfile, params.uid, -1))
fprintf(stderr, "could not chown %s. log file may not be writable after privilege drop\n", params.debug_logfile);
if (!params.debug) params.debug = 1;
params.debug_target = LOG_TARGET_FILE;
}
@ -1054,7 +1165,7 @@ void parse_params(int argc, char *argv[])
}
params.tamper = true;
break;
#if defined(__FreeBSD__)
case 62: /* enable-pf */
params.pf_enable = true;
@ -1117,16 +1228,21 @@ void parse_params(int argc, char *argv[])
DLOG_CONDUP("we have %d user defined desync profile(s) and default low priority profile 0\n",desync_profile_count);
save_default_ttl();
if (params.debug_target == LOG_TARGET_FILE && params.droproot && chown(params.debug_logfile, params.uid, -1))
fprintf(stderr, "could not chown %s. log file may not be writable after privilege drop\n", params.debug_logfile);
if (params.droproot && *params.hostlist_auto_debuglog && chown(params.hostlist_auto_debuglog, params.uid, -1))
DLOG_ERR("could not chown %s. auto hostlist debug log may not be writable after privilege drop\n", params.hostlist_auto_debuglog);
LIST_FOREACH(dpl, &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 (params.skip_nodelay && (dp->split_tls || dp->split_http_req || dp->split_pos))
if (params.skip_nodelay && dp->split_count)
{
DLOG_ERR("Cannot split with --skip-nodelay\n");
exit_clean(1);
}
if (params.droproot && dp->hostlist_auto && chown(dp->hostlist_auto->filename, params.uid, -1))
DLOG_ERR("could not chown %s. auto hostlist file may not be writable after privilege drop\n", dp->hostlist_auto->filename);
}
if (!LoadAllHostLists())
@ -1143,6 +1259,9 @@ void parse_params(int argc, char *argv[])
VPRINT("\nlists summary:\n");
HostlistsDebug();
IpsetsDebug();
VPRINT("\nsplits summary:\n");
SplitDebug();
VPRINT("\n");
#ifndef __OpenBSD__

View File

@ -24,6 +24,7 @@
#include "helpers.h"
#include "hostlist.h"
#define PKTDATA_MAXDUMP 32
// keep separate legs counter. counting every time thousands of legs can consume cpu
static int legs_local, legs_remote;
@ -92,26 +93,43 @@ static bool socks_send_rep_errno(uint8_t ver, int fd, int errn)
return ver==5 ? socks5_send_rep_errno(fd,errn) : socks4_send_rep_errno(fd, errn);
}
static void packet_debug(const uint8_t *data, size_t sz)
{
hexdump_limited_dlog(data, sz, PKTDATA_MAXDUMP); VPRINT("\n");
}
static bool cork(int fd, int enable)
{
#ifdef __linux__
int e = errno;
if (setsockopt(fd, SOL_TCP, TCP_CORK, &enable, sizeof(enable))<0)
{
DLOG_PERROR("setsockopt (TCP_CORK)");
errno = e;
return false;
}
errno = e;
#endif
return true;
}
ssize_t send_with_ttl(int fd, const void *buf, size_t len, int flags, int ttl)
{
ssize_t wr;
ssize_t wr;
if (ttl)
if (!params.skip_nodelay)
{
int ttl_apply = ttl ? ttl : params.ttl_default;
DBGPRINT("send_with_ttl %d fd=%d\n",ttl,fd);
if (!set_ttl_hl(fd, ttl))
if (!set_ttl_hl(fd, ttl_apply))
//DLOG_ERR("could not set ttl %d to fd=%d\n",ttl,fd);
DLOG_ERR("could not set ttl %d to fd=%d\n",ttl,fd);
DLOG_ERR("could not set ttl %d to fd=%d\n",ttl_apply,fd);
cork(fd,true);
}
wr = send(fd, buf, len, flags);
if (ttl)
{
int e=errno;
if (!set_ttl_hl(fd, params.ttl_default))
DLOG_ERR("could not set ttl %d to fd=%d\n",params.ttl_default,fd);
errno=e;
}
if (!params.skip_nodelay)
cork(fd,false);
return wr;
}
@ -308,19 +326,18 @@ bool set_socket_buffers(int fd, int rcvbuf, int sndbuf)
if (rcvbuf && setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(int)) <0)
{
DLOG_PERROR("setsockopt (SO_RCVBUF)");
close(fd);
return false;
}
if (sndbuf && setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(int)) <0)
{
DLOG_PERROR("setsockopt (SO_SNDBUF)");
close(fd);
return false;
}
dbgprint_socket_buffers(fd);
return true;
}
static bool proxy_remote_conn_ack(tproxy_conn_t *conn, int sock_err)
{
// if proxy mode acknowledge connection request
@ -393,7 +410,10 @@ static int connect_remote(const struct sockaddr *remote_addr, int mss)
return -1;
}
if (!set_socket_buffers(remote_fd, params.remote_rcvbuf, params.remote_sndbuf))
{
close(remote_fd);
return -1;
}
if (!set_keepalive(remote_fd))
{
DLOG_PERROR("set_keepalive");
@ -1068,9 +1088,9 @@ static bool in_tamper_out_range(tproxy_conn_t *conn)
}
static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_size, size_t *segment_size, size_t *split_pos, uint8_t *split_flags)
static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_size, size_t *segment_size, size_t *multisplit_pos, int *multisplit_count, uint8_t *split_flags)
{
*split_pos=0;
if (multisplit_count) *multisplit_count=0;
if (params.tamper)
{
if (conn->remote)
@ -1081,26 +1101,26 @@ static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_
else
{
if (in_tamper_out_range(conn))
tamper_out(&conn->track,(struct sockaddr*)&conn->dest,segment,segment_buffer_size,segment_size,split_pos,split_flags);
tamper_out(&conn->track,(struct sockaddr*)&conn->dest,segment,segment_buffer_size,segment_size,multisplit_pos,multisplit_count,split_flags);
}
}
}
// buffer must have at least one extra byte for OOB
static ssize_t send_or_buffer_oob(send_buffer_t *sb, int fd, uint8_t *buf, size_t len, int ttl, bool oob, uint8_t oob_byte)
static ssize_t send_oob(int fd, uint8_t *buf, size_t len, int ttl, bool oob, uint8_t oob_byte)
{
ssize_t wr;
if (oob)
{
VPRINT("Sending OOB byte %02X\n", oob_byte);
uint8_t oob_save;
oob_save = buf[len];
buf[len] = oob_byte;
wr = send_or_buffer(sb, fd, buf, len+1, MSG_OOB, ttl);
wr = send_with_ttl(fd, buf, len+1, MSG_OOB, ttl);
buf[len] = oob_save;
if (wr<0 && errno==EAGAIN) wr=0;
}
else
wr = send_or_buffer(sb, fd, buf, len, 0, ttl);
wr = send_with_ttl(fd, buf, len, 0, ttl);
return wr;
}
@ -1186,36 +1206,53 @@ static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32
#endif
{
// incoming data from local leg
uint8_t buf[RD_BLOCK_SIZE + 5];
uint8_t buf[RD_BLOCK_SIZE + 6];
rd = recv(conn->fd, buf, RD_BLOCK_SIZE, MSG_DONTWAIT);
DBGPRINT("recv fd=%d rd=%zd err=%d\n",conn->fd, rd,errno);
if (rd<0 && errno==EAGAIN) rd=0;
if (rd>0)
{
size_t split_pos;
size_t multisplit_pos[MAX_SPLITS];
int multisplit_count;
uint8_t split_flags;
bs = rd;
// tamper needs to know stream position of the block start
tamper(conn, buf, sizeof(buf), &bs, &split_pos, &split_flags);
tamper(conn, buf, sizeof(buf), &bs, multisplit_pos, &multisplit_count, &split_flags);
// increase after tamper
conn->tnrd++;
conn->trd+=rd;
if (split_pos && bs<sizeof(buf) && split_pos<sizeof(buf))
if (multisplit_count)
{
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), conn->track.dp ? conn->track.dp->oob_byte : 0);
DBGPRINT("send_or_buffer(1) fd=%d wr=%zd err=%d\n",conn->partner->fd,wr,errno);
if (wr >= 0)
ssize_t from,to,len;
int i;
bool bApplyDisorder, bApplyOOB;
for (i=0,from=0;i<=multisplit_count;i++)
{
to = i==multisplit_count ? bs : multisplit_pos[i];
bApplyDisorder = !(i & 1) && i<multisplit_count && (split_flags & SPLIT_FLAG_DISORDER);
bApplyOOB = i==0 && (split_flags & SPLIT_FLAG_OOB);
len = to-from;
VPRINT("Sending multisplit part %d %zd-%zd (len %zd)%s%s : ", i+1, from, to, len, bApplyDisorder ? " with disorder" : "", bApplyOOB ? " with OOB" : "");
packet_debug(buf+from,len);
wr = send_oob(conn->partner->fd, buf+from, len, bApplyDisorder, bApplyOOB, conn->track.dp ? conn->track.dp->oob_byte : 0);
if (wr<0) break;
conn->partner->twr += wr;
wr = send_or_buffer(conn->partner->wr_buf + 1, conn->partner->fd, buf + split_pos, bs - split_pos, 0, 0);
DBGPRINT("send_or_buffer(2) fd=%d wr=%zd err=%d\n",conn->partner->fd,wr,errno);
if (wr>0) conn->partner->twr += wr;
if (wr<len)
{
from+=wr;
VPRINT("Cannot send part %d immediately. only %zd bytes were sent (%zd left in segment). cancelling split.\n", i+1, wr, bs-from);
wr = send_or_buffer(conn->partner->wr_buf, conn->partner->fd, buf+from, bs-from, 0, 0);
if (wr>0) conn->partner->twr += wr;
break;
}
from = to;
}
}
else
@ -1279,7 +1316,7 @@ static bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number)
DBGPRINT("read_all_and_buffer(%d) numbytes=%d\n",buffer_number,numbytes);
if (numbytes>0)
{
if (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes, 5, 0, 0))
if (send_buffer_create(conn->partner->wr_buf+buffer_number, NULL, numbytes, 6, 0, 0))
{
ssize_t rd = recv(conn->fd, conn->partner->wr_buf[buffer_number].data, numbytes, MSG_DONTWAIT);
if (rd>0)
@ -1289,10 +1326,7 @@ static bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number)
conn->partner->bFlowOut = true;
size_t split_pos;
uint8_t split_flags;
tamper(conn, conn->partner->wr_buf[buffer_number].data, numbytes+5, &conn->partner->wr_buf[buffer_number].len, &split_pos, &split_flags);
tamper(conn, conn->partner->wr_buf[buffer_number].data, numbytes+6, &conn->partner->wr_buf[buffer_number].len, NULL, NULL, NULL);
if (epoll_update_flow(conn->partner))
return true;
@ -1369,7 +1403,7 @@ static bool handle_resolve_pipe(tproxy_conn_t **conn, struct tailhead *conn_list
else if (rd!=sizeof(void*))
{
// partial pointer read is FATAL. in any case it will cause pointer corruption and coredump
DLOG_ERR("resolve_pipe not full read %zu\n",rd);
DLOG_ERR("resolve_pipe not full read %zd\n",rd);
exit(1000);
}
b = resolve_complete(ri, conn_list);