autohostlist mode

This commit is contained in:
bol-van
2023-10-26 15:12:32 +03:00
parent 6f3a814f73
commit ac574ce2ce
63 changed files with 1584 additions and 573 deletions

View File

@@ -30,6 +30,15 @@ char *strncasestr(const char *s,const char *find, size_t slen)
return (char *)s;
}
bool append_to_list_file(const char *filename, const char *s)
{
FILE *F = fopen(filename,"at");
if (!F) return false;
bool bOK = fprintf(F,"%s\n",s)>0;
fclose(F);
return bOK;
}
void ntop46(const struct sockaddr *sa, char *str, size_t len)
{
if (!len) return;

View File

@@ -8,6 +8,8 @@
char *strncasestr(const char *s,const char *find, size_t slen);
bool append_to_list_file(const char *filename, const char *s);
void ntop46(const struct sockaddr *sa, char *str, size_t len);
void ntop46_port(const struct sockaddr *sa, char *str, size_t len);
void print_sockaddr(const struct sockaddr *sa);

View File

@@ -4,6 +4,7 @@
#include "params.h"
// inplace tolower() and add to pool
static bool addpool(strpool **hostlist, char **s, const char *end)
{
char *p;
@@ -22,7 +23,6 @@ static bool addpool(strpool **hostlist, char **s, const char *end)
return true;
}
bool AppendHostList(strpool **hostlist, char *filename)
{
char *p, *e, s[256], *zbuf;
@@ -106,6 +106,12 @@ bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list)
return true;
}
bool NonEmptyHostlist(strpool **hostlist)
{
// add impossible hostname if the list is empty
return *hostlist ? true : StrPoolAddStrLen(hostlist, "@&()", 4);
}
bool SearchHostList(strpool *hostlist, const char *host)
{
@@ -126,12 +132,17 @@ bool SearchHostList(strpool *hostlist, const char *host)
}
// return : true = apply fooling, false = do not apply
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host)
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host, bool *excluded)
{
if (excluded) *excluded = false;
if (hostlist_exclude)
{
if (params.debug) printf("Checking exclude hostlist\n");
if (SearchHostList(hostlist_exclude, host)) return false;
if (SearchHostList(hostlist_exclude, host))
{
if (excluded) *excluded = true;
return false;
}
}
if (hostlist)
{

View File

@@ -1,10 +1,11 @@
#pragma once
#include <stdbool.h>
#include "strpool.h"
#include "pools.h"
bool AppendHostList(strpool **hostlist, char *filename);
bool LoadHostLists(strpool **hostlist, struct str_list_head *file_list);
bool NonEmptyHostlist(strpool **hostlist);
bool SearchHostList(strpool *hostlist, const char *host);
// return : true = apply fooling, false = do not apply
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host);
bool HostlistCheck(strpool *hostlist, strpool *hostlist_exclude, const char *host, bool *excluded);

View File

@@ -5,7 +5,10 @@
#include <stdint.h>
#include <sys/param.h>
#include <sys/queue.h>
#include "strpool.h"
#include "pools.h"
#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 2
#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60
enum splithttpreq { split_none = 0, split_method, split_host };
enum tlsrec { tlsrec_none = 0, tlsrec_sni, tlsrec_pos };
@@ -53,6 +56,9 @@ struct params_s
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;
hostfail_pool *hostlist_auto_fail_counters;
int debug;

152
tpws/pools.c Normal file
View File

@@ -0,0 +1,152 @@
#define _GNU_SOURCE
#include "pools.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define DESTROY_STR_POOL(etype, ppool) \
etype *elem, *tmp; \
HASH_ITER(hh, *ppool, elem, tmp) { \
free(elem->str); \
HASH_DEL(*ppool, elem); \
free(elem); \
}
#define ADD_STR_POOL(etype, ppool, keystr, keystr_len) \
etype *elem; \
if (!(elem = (etype*)malloc(sizeof(etype)))) \
return false; \
if (!(elem->str = malloc(keystr_len + 1))) \
{ \
free(elem); \
return false; \
} \
memcpy(elem->str, keystr, keystr_len); \
elem->str[keystr_len] = 0; \
oom = false; \
HASH_ADD_KEYPTR(hh, *ppool, elem->str, strlen(elem->str), elem); \
if (oom) \
{ \
free(elem->str); \
free(elem); \
return false; \
}
#undef uthash_nonfatal_oom
#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)
static bool oom = false;
static void ut_oom_recover(void *elem)
{
oom = true;
}
// for not zero terminated strings
bool StrPoolAddStrLen(strpool **pp, const char *s, size_t slen)
{
ADD_STR_POOL(strpool, pp, s, slen)
return true;
}
// for zero terminated strings
bool StrPoolAddStr(strpool **pp, const char *s)
{
return StrPoolAddStrLen(pp, s, strlen(s));
}
bool StrPoolCheckStr(strpool *p, const char *s)
{
strpool *elem;
HASH_FIND_STR(p, s, elem);
return elem != NULL;
}
void StrPoolDestroy(strpool **pp)
{
DESTROY_STR_POOL(strpool, pp)
}
void HostFailPoolDestroy(hostfail_pool **pp)
{
DESTROY_STR_POOL(hostfail_pool, pp)
}
hostfail_pool * HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time)
{
size_t slen = strlen(s);
ADD_STR_POOL(hostfail_pool, pp, s, slen)
elem->expire = time(NULL) + fail_time;
return elem;
}
hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s)
{
hostfail_pool *elem;
HASH_FIND_STR(p, s, elem);
return elem;
}
void HostFailPoolDel(hostfail_pool **p, hostfail_pool *elem)
{
HASH_DEL(*p, elem);
free(elem);
}
void HostFailPoolPurge(hostfail_pool **pp)
{
hostfail_pool *elem, *tmp;
time_t now = time(NULL);
HASH_ITER(hh, *pp, elem, tmp)
{
if (now >= elem->expire)
{
free(elem->str);
HASH_DEL(*pp, elem);
free(elem);
}
}
}
static time_t host_fail_purge_prev=0;
void HostFailPoolPurgeRateLimited(hostfail_pool **pp)
{
time_t now = time(NULL);
// do not purge too often to save resources
if (host_fail_purge_prev != now)
{
HostFailPoolPurge(pp);
host_fail_purge_prev = now;
}
}
void HostFailPoolDump(hostfail_pool *p)
{
hostfail_pool *elem, *tmp;
time_t now = time(NULL);
HASH_ITER(hh, p, elem, tmp)
printf("host=%s counter=%d time_left=%lld\n",elem->str,elem->counter,(long long int)elem->expire-now);
}
bool strlist_add(struct str_list_head *head, const char *filename)
{
struct str_list *entry = malloc(sizeof(struct str_list));
if (!entry) return false;
entry->str = strdup(filename);
if (!entry->str)
{
free(entry);
return false;
}
LIST_INSERT_HEAD(head, entry, next);
return true;
}
static void strlist_entry_destroy(struct str_list *entry)
{
if (entry->str) free(entry->str);
free(entry);
}
void strlist_destroy(struct str_list_head *head)
{
struct str_list *entry;
while ((entry = LIST_FIRST(head)))
{
LIST_REMOVE(entry, next);
strlist_entry_destroy(entry);
}
}

46
tpws/pools.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <stdbool.h>
#include <ctype.h>
#include <sys/queue.h>
#include <time.h>
//#define HASH_BLOOM 20
#define HASH_NONFATAL_OOM 1
#define HASH_FUNCTION HASH_BER
#include "uthash.h"
typedef struct strpool {
char *str; /* key */
UT_hash_handle hh; /* makes this structure hashable */
} strpool;
void StrPoolDestroy(strpool **pp);
bool StrPoolAddStr(strpool **pp,const char *s);
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen);
bool StrPoolCheckStr(strpool *p,const char *s);
struct str_list {
char *str;
LIST_ENTRY(str_list) next;
};
LIST_HEAD(str_list_head, str_list);
typedef struct hostfail_pool {
char *str; /* key */
int counter; /* value */
time_t expire; /* when to expire record (unixtime) */
UT_hash_handle hh; /* makes this structure hashable */
} hostfail_pool;
void HostFailPoolDestroy(hostfail_pool **pp);
hostfail_pool *HostFailPoolAdd(hostfail_pool **pp,const char *s,int fail_time);
hostfail_pool *HostFailPoolFind(hostfail_pool *p,const char *s);
void HostFailPoolDel(hostfail_pool **pp, hostfail_pool *elem);
void HostFailPoolPurge(hostfail_pool **pp);
void HostFailPoolPurgeRateLimited(hostfail_pool **pp);
void HostFailPoolDump(hostfail_pool *p);
bool strlist_add(struct str_list_head *head, const char *filename);
void strlist_destroy(struct str_list_head *head);

View File

@@ -21,29 +21,95 @@ bool IsHttp(const uint8_t *data, size_t len)
}
return false;
}
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
bool IsHttpReply(const uint8_t *data, size_t len)
{
const uint8_t *p, *s, *e=data+len;
// HTTP/1.x 200\r\n
return len>14 && !memcmp(data,"HTTP/1.",7) && (data[7]=='0' || data[7]=='1') && data[8]==' ' &&
data[9]>='0' && data[9]<='9' &&
data[10]>='0' && data[10]<='9' &&
data[11]>='0' && data[11]<='9';
}
int HttpReplyCode(const uint8_t *data, size_t len)
{
return (data[9]-'0')*100 + (data[10]-'0')*10 + (data[11]-'0');
}
bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf)
{
const uint8_t *p, *s, *e = data + len;
p = (uint8_t*)strncasestr((char*)data, "\nHost:", len);
p = (uint8_t*)strncasestr((char*)data, header, len);
if (!p) return false;
p+=6;
while(p<e && (*p==' ' || *p=='\t')) p++;
s=p;
while(s<e && (*s!='\r' && *s!='\n' && *s!=' ' && *s!='\t')) s++;
if (s>p)
p += strlen(header);
while (p < e && (*p == ' ' || *p == '\t')) p++;
s = p;
while (s < e && (*s != '\r' && *s != '\n' && *s != ' ' && *s != '\t')) s++;
if (s > p)
{
size_t slen = s-p;
if (host && len_host)
size_t slen = s - p;
if (buf && len_buf)
{
if (slen>=len_host) slen=len_host-1;
for(size_t i=0;i<slen;i++) host[i]=tolower(p[i]);
host[slen]=0;
if (slen >= len_buf) slen = len_buf - 1;
for (size_t i = 0; i < slen; i++) buf[i] = tolower(p[i]);
buf[slen] = 0;
}
return true;
}
return false;
}
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
{
return HttpExtractHeader(data, len, "\nHost:", host, len_host);
}
const char *HttpFind2ndLevelDomain(const char *host)
{
const char *p=NULL;
if (*host)
{
for (p = host + strlen(host)-1; p>host && *p!='.'; p--);
if (*p=='.') for (p--; p>host && *p!='.'; p--);
if (*p=='.') p++;
}
return p;
}
// DPI redirects are global redirects to another domain
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host)
{
char loc[256],*redirect_host, *p;
int code;
if (!host || !*host) return false;
code = HttpReplyCode(data,len);
if (code!=302 && code!=307 || !HttpExtractHeader(data,len,"\nLocation:",loc,sizeof(loc))) return false;
// something like : https://censor.net/badpage.php?reason=denied&source=RKN
if (!strncmp(loc,"http://",7))
redirect_host=loc+7;
else if (!strncmp(loc,"https://",8))
redirect_host=loc+8;
else
return false;
// somethinkg like : censor.net/badpage.php?reason=denied&source=RKN
for(p=redirect_host; *p && *p!='/' ; p++);
*p=0;
if (!*redirect_host) return false;
// somethinkg like : censor.net
// extract 2nd level domains
const char *dhost = HttpFind2ndLevelDomain(host);
const char *drhost = HttpFind2ndLevelDomain(redirect_host);
return strcasecmp(dhost, drhost)!=0;
}
bool IsTLSClientHello(const uint8_t *data, size_t len)
{
return len>=6 && data[0]==0x16 && data[1]==0x03 && data[2]>=0x01 && data[2]<=0x03 && data[5]==0x01 && (pntoh16(data+3)+5)<=len;

View File

@@ -5,7 +5,16 @@
#include <stdbool.h>
bool IsHttp(const uint8_t *data, size_t len);
// header must be passed like this : "\nHost:"
bool HttpExtractHeader(const uint8_t *data, size_t len, const char *header, char *buf, size_t len_buf);
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);
bool IsHttpReply(const uint8_t *data, size_t len);
const char *HttpFind2ndLevelDomain(const char *host);
// must be pre-checked by IsHttpReply
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);
bool IsTLSClientHello(const uint8_t *data, size_t len);
bool TLSFindExt(const uint8_t *data, size_t len, uint16_t type, const uint8_t **ext, size_t *len_ext);
bool TLSHelloExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host);

View File

@@ -1,107 +0,0 @@
#define _GNU_SOURCE
#include "strpool.h"
#include <string.h>
#include <stdlib.h>
#undef uthash_nonfatal_oom
#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)
static bool oom = false;
static void ut_oom_recover(strpool *elem)
{
oom = true;
}
// for zero terminated strings
bool StrPoolAddStr(strpool **pp, const char *s)
{
strpool *elem;
if (!(elem = (strpool*)malloc(sizeof(strpool))))
return false;
if (!(elem->str = strdup(s)))
{
free(elem);
return false;
}
oom = false;
HASH_ADD_KEYPTR(hh, *pp, elem->str, strlen(elem->str), elem);
if (oom)
{
free(elem->str);
free(elem);
return false;
}
return true;
}
// for not zero terminated strings
bool StrPoolAddStrLen(strpool **pp, const char *s, size_t slen)
{
strpool *elem;
if (!(elem = (strpool*)malloc(sizeof(strpool))))
return false;
if (!(elem->str = malloc(slen + 1)))
{
free(elem);
return false;
}
memcpy(elem->str, s, slen);
elem->str[slen] = 0;
oom = false;
HASH_ADD_KEYPTR(hh, *pp, elem->str, strlen(elem->str), elem);
if (oom)
{
free(elem->str);
free(elem);
return false;
}
return true;
}
bool StrPoolCheckStr(strpool *p, const char *s)
{
strpool *elem;
HASH_FIND_STR(p, s, elem);
return elem != NULL;
}
void StrPoolDestroy(strpool **p)
{
strpool *elem, *tmp;
HASH_ITER(hh, *p, elem, tmp) {
free(elem->str);
HASH_DEL(*p, elem);
free(elem);
}
*p = NULL;
}
bool strlist_add(struct str_list_head *head, const char *filename)
{
struct str_list *entry = malloc(sizeof(struct str_list));
if (!entry) return false;
entry->str = strdup(filename);
if (!entry->str)
{
free(entry);
return false;
}
LIST_INSERT_HEAD(head, entry, next);
return true;
}
static void strlist_entry_destroy(struct str_list *entry)
{
if (entry->str) free(entry->str);
free(entry);
}
void strlist_destroy(struct str_list_head *head)
{
struct str_list *entry;
while ((entry = LIST_FIRST(head)))
{
LIST_REMOVE(entry, next);
strlist_entry_destroy(entry);
}
}

View File

@@ -1,29 +0,0 @@
#pragma once
#include <stdbool.h>
#include <ctype.h>
#include <sys/queue.h>
//#define HASH_BLOOM 20
#define HASH_NONFATAL_OOM 1
#define HASH_FUNCTION HASH_BER
#include "uthash.h"
typedef struct strpool {
char *str; /* key */
UT_hash_handle hh; /* makes this structure hashable */
} strpool;
void StrPoolDestroy(strpool **p);
bool StrPoolAddStr(strpool **pp,const char *s);
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen);
bool StrPoolCheckStr(strpool *p,const char *s);
struct str_list {
char *str;
LIST_ENTRY(str_list) next;
};
LIST_HEAD(str_list_head, str_list);
bool strlist_add(struct str_list_head *head, const char *filename);
void strlist_destroy(struct str_list_head *head);

View File

@@ -25,15 +25,17 @@ bool find_host(uint8_t **pHost,uint8_t *buf,size_t bs)
static const char *http_methods[] = { "GET /","POST /","HEAD /","OPTIONS /","PUT /","DELETE /","CONNECT /","TRACE /",NULL };
// segment buffer has at least 5 extra bytes to extend data block
void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size,size_t *split_pos)
void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos)
{
uint8_t *p, *pp, *pHost = NULL;
size_t method_len = 0, pos;
const char **method;
bool bIsHttp = false, bBypass = false;
bool bIsHttp = false, bBypass = false, bHaveHost = false, bHostExcluded = false;
char bRemovedHostSpace = 0;
char *pc, Host[128];
char *pc, Host[256];
DBGPRINT("tamper_out")
*split_pos=0;
for (method = http_methods; *method; method++)
@@ -49,6 +51,7 @@ void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size
if (bIsHttp)
{
VPRINT("Data block looks like http request start : %s", *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) && find_host(&pHost,segment,*size))
{
@@ -58,9 +61,10 @@ void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size
while (pp < (segment + *size) && (pp - p) < (sizeof(Host) - 1) && *pp != '\r' && *pp != '\n') pp++;
memcpy(Host, p, pp - p);
Host[pp - p] = '\0';
bHaveHost = true;
VPRINT("Requested Host is : %s", Host)
for(pc = Host; *pc; pc++) *pc=tolower(*pc);
bBypass = !HostlistCheck(params.hostlist, params.hostlist_exclude, Host);
bBypass = !HostlistCheck(params.hostlist, params.hostlist_exclude, Host, &bHostExcluded);
}
if (!bBypass)
{
@@ -210,62 +214,161 @@ void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size
{
VPRINT("Not acting on this request")
}
return;
}
if (IsTLSClientHello(segment,*size))
else if (IsTLSClientHello(segment,*size))
{
char host[256];
size_t tpos=0,elen;
const uint8_t *ext;
if (!ctrack->l7proto) ctrack->l7proto=TLS;
VPRINT("packet contains TLS ClientHello")
// we need host only if hostlist is present
if ((params.hostlist || params.hostlist_exclude) && TLSHelloExtractHost((uint8_t*)segment,*size,host,sizeof(host)))
if ((params.hostlist || params.hostlist_exclude) && TLSHelloExtractHost((uint8_t*)segment,*size,Host,sizeof(Host)))
{
VPRINT("hostname: %s",host)
if (!HostlistCheck(params.hostlist, params.hostlist_exclude, host))
VPRINT("hostname: %s",Host)
bHaveHost = true;
bBypass = !HostlistCheck(params.hostlist, params.hostlist_exclude, Host, &bHostExcluded);
}
if (bBypass)
{
VPRINT("Not acting on this request")
}
else
{
switch(params.tlsrec)
{
VPRINT("Not acting on this request")
return;
case tlsrec_sni:
if (TLSFindExt(segment,*size,0,&ext,&elen))
tpos = ext-segment+1; // between typical 1st and 2nd char of hostname
break;
case tlsrec_pos:
tpos = params.tlsrec_pos;
break;
default:
break;
}
}
switch(params.tlsrec)
{
case tlsrec_sni:
if (TLSFindExt(segment,*size,0,&ext,&elen))
tpos = ext-segment+1; // between typical 1st and 2nd char of hostname
break;
case tlsrec_pos:
tpos = params.tlsrec_pos;
break;
default:
break;
}
if (tpos)
{
// construct 2 TLS records from one
uint16_t l = pntoh16(segment+3); // length
if (l>=2)
if (tpos)
{
// length is checked in IsTLSClientHello and cannot exceed buffer size
if (tpos>=l) tpos=1;
VPRINT("making 2 TLS records at pos %zu",tpos)
memmove(segment+5+tpos+5,segment+5+tpos,*size-(5+tpos));
segment[5+tpos] = segment[0];
segment[5+tpos+1] = segment[1];
segment[5+tpos+2] = segment[2];
phton16(segment+5+tpos+3,l-tpos);
phton16(segment+3,tpos);
*size += 5;
// construct 2 TLS records from one
uint16_t l = pntoh16(segment+3); // length
if (l>=2)
{
// length is checked in IsTLSClientHello and cannot exceed buffer size
if (tpos>=l) tpos=1;
VPRINT("making 2 TLS records at pos %zu",tpos)
memmove(segment+5+tpos+5,segment+5+tpos,*size-(5+tpos));
segment[5+tpos] = segment[0];
segment[5+tpos+1] = segment[1];
segment[5+tpos+2] = segment[2];
phton16(segment+5+tpos+3,l-tpos);
phton16(segment+3,tpos);
*size += 5;
}
}
}
if (params.split_pos < *size)
*split_pos = params.split_pos;
return;
if (params.split_pos < *size)
*split_pos = params.split_pos;
}
}
if (params.split_any_protocol && params.split_pos < *size)
else if (params.split_any_protocol && params.split_pos < *size)
*split_pos = params.split_pos;
if (bHaveHost && bBypass && !bHostExcluded && !ctrack->hostname && *params.hostlist_auto_filename)
{
DBGPRINT("tamper_out put hostname : %s", Host)
ctrack->hostname=strdup(Host);
}
}
static void auto_hostlist_failed(const char *hostname)
{
hostfail_pool *fail_counter;
fail_counter = HostFailPoolFind(params.hostlist_auto_fail_counters, hostname);
if (!fail_counter)
{
fail_counter = HostFailPoolAdd(&params.hostlist_auto_fail_counters, hostname, params.hostlist_auto_fail_time);
if (!fail_counter)
{
fprintf(stderr, "HostFailPoolAdd: out of memory\n");
return;
}
}
fail_counter->counter++;
VPRINT("auto hostlist : %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 : fail threshold reached. adding %s to auto hostlist", hostname);
HostFailPoolDel(&params.hostlist_auto_fail_counters, fail_counter);
if (!StrPoolAddStr(&params.hostlist, hostname))
{
fprintf(stderr, "StrPoolAddStr out of memory\n");
return;
}
if (!append_to_list_file(params.hostlist_auto_filename, hostname))
{
perror("write to auto hostlist:");
return;
}
}
}
void tamper_in(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size)
{
bool bFail=false;
DBGPRINT("tamper_in hostname=%s", ctrack->hostname)
HostFailPoolPurgeRateLimited(&params.hostlist_auto_fail_counters);
if (ctrack->l7proto==HTTP && ctrack->hostname)
{
if (IsHttpReply(segment,*size))
{
VPRINT("incoming HTTP reply detected for hostname %s", ctrack->hostname);
bFail = HttpReplyLooksLikeDPIRedirect(segment, *size, ctrack->hostname);
if (bFail)
VPRINT("redirect to another domain detected. possibly DPI redirect.")
else
VPRINT("local or in-domain redirect detected. it's not a DPI redirect.")
}
else
{
// received not http reply. do not monitor this connection anymore
VPRINT("incoming unknown HTTP data detected for hostname %s", ctrack->hostname);
}
if (bFail)
auto_hostlist_failed(ctrack->hostname);
}
ctrack->bTamperInCutoff = true;
}
void rst_in(t_ctrack *ctrack)
{
DBGPRINT("rst_in hostname=%s", ctrack->hostname)
HostFailPoolPurgeRateLimited(&params.hostlist_auto_fail_counters);
if (!ctrack->bTamperInCutoff && ctrack->hostname)
{
VPRINT("incoming RST detected for hostname %s", ctrack->hostname);
auto_hostlist_failed(ctrack->hostname);
}
}
void hup_out(t_ctrack *ctrack)
{
DBGPRINT("hup_out hostname=%s", ctrack->hostname)
HostFailPoolPurgeRateLimited(&params.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", ctrack->hostname);
auto_hostlist_failed(ctrack->hostname);
}
}

View File

@@ -4,5 +4,19 @@
#include <stdint.h>
#include <sys/types.h>
bool find_host(uint8_t **pHost,uint8_t *buf,size_t bs);
void modify_tcp_segment(uint8_t *segment,size_t segment_buffer_size,size_t *size,size_t *split_pos);
typedef enum {UNKNOWN=0, HTTP, TLS} t_l7proto;
typedef struct
{
// common state
t_l7proto l7proto;
bool bFirstReplyChecked;
bool bTamperInCutoff;
char *hostname;
} t_ctrack;
void tamper_out(t_ctrack *ctrack, uint8_t *segment,size_t segment_buffer_size,size_t *size, size_t *split_pos);
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);
// local leg closed connection (timeout waiting response ?)
void hup_out(t_ctrack *ctrack);

View File

@@ -36,6 +36,8 @@
#include "sec.h"
#include "redirect.h"
#include "helpers.h"
#include "gzip.h"
#include "pools.h"
struct params_s params;
@@ -62,6 +64,12 @@ void dohup(void)
}
}
static void onusr2(int sig)
{
printf("\nHOSTFAIL POOL DUMP\n");
HostFailPoolDump(params.hostlist_auto_fail_counters);
printf("\n");
}
static int8_t block_sigpipe(void)
@@ -120,62 +128,66 @@ static int get_default_ttl(void)
static void exithelp(void)
{
printf(
" --bind-addr=<v4_addr>|<v6_addr>; for v6 link locals append %%interface_name\n"
" --bind-iface4=<interface_name>\t; bind to the first ipv4 addr of interface\n"
" --bind-iface6=<interface_name>\t; bind to the first ipv6 addr of interface\n"
" --bind-linklocal=no|unwanted|prefer|force\n"
"\t\t\t\t; prohibit, accept, prefer or force ipv6 link local bind\n"
" --bind-wait-ifup=<sec>\t\t; wait for interface to appear and up\n"
" --bind-wait-ip=<sec>\t\t; after ifup wait for ip address to appear up to N seconds\n"
" --bind-wait-ip-linklocal=<sec>\t; (prefer) accept only LL first N seconds then any (unwanted) accept only globals first N seconds then LL\n"
" --bind-wait-only\t\t; wait for bind conditions satisfaction then exit. return code 0 if success.\n"
" --bind-addr=<v4_addr>|<v6_addr>\t; for v6 link locals append %%interface_name\n"
" --bind-iface4=<interface_name>\t\t; bind to the first ipv4 addr of interface\n"
" --bind-iface6=<interface_name>\t\t; bind to the first ipv6 addr of interface\n"
" --bind-linklocal=no|unwanted|prefer|force ; prohibit, accept, prefer or force ipv6 link local bind\n"
" --bind-wait-ifup=<sec>\t\t\t; wait for interface to appear and up\n"
" --bind-wait-ip=<sec>\t\t\t; after ifup wait for ip address to appear up to N seconds\n"
" --bind-wait-ip-linklocal=<sec>\t\t; (prefer) accept only LL first N seconds then any (unwanted) accept only globals first N seconds then LL\n"
" --bind-wait-only\t\t\t; wait for bind conditions satisfaction then exit. return code 0 if success.\n"
" * multiple binds are supported. each bind-addr, bind-iface* start new bind\n"
" --port=<port>\t\t\t; only one port number for all binds is supported\n"
" --socks\t\t\t; implement socks4/5 proxy instead of transparent proxy\n"
" --no-resolve\t\t\t; disable socks5 remote dns ability (resolves are not async, they block all activity)\n"
" --port=<port>\t\t\t\t; only one port number for all binds is supported\n"
" --socks\t\t\t\t; implement socks4/5 proxy instead of transparent proxy\n"
" --no-resolve\t\t\t\t; disable socks5 remote dns ability (resolves are not async, they block all activity)\n"
" --local-rcvbuf=<bytes>\n"
" --local-sndbuf=<bytes>\n"
" --remote-rcvbuf=<bytes>\n"
" --remote-sndbuf=<bytes>\n"
" --skip-nodelay\t\t\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\n"
" --skip-nodelay\t\t\t\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\n"
" --maxconn=<max_connections>\n"
#ifdef SPLICE_PRESENT
" --maxfiles=<max_open_files>\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\n"
" --maxfiles=<max_open_files>\t\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\n"
#else
" --maxfiles=<max_open_files>\t; should be at least (connections*2+16)\n"
" --maxfiles=<max_open_files>\t\t; should be at least (connections*2+16)\n"
#endif
" --max-orphan-time=<sec>\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n"
" --daemon\t\t\t; daemonize\n"
" --pidfile=<filename>\t\t; write pid to file\n"
" --user=<username>\t\t; drop root privs\n"
" --uid=uid[:gid]\t\t; drop root privs\n"
" --max-orphan-time=<sec>\t\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n"
" --daemon\t\t\t\t; daemonize\n"
" --pidfile=<filename>\t\t\t; write pid to file\n"
" --user=<username>\t\t\t; drop root privs\n"
" --uid=uid[:gid]\t\t\t; drop root privs\n"
#if defined(BSD) && !defined(__OpenBSD__) && !defined(__APPLE__)
" --enable-pf\t\t\t; enable PF redirector support. required in FreeBSD when used with PF firewall.\n"
" --enable-pf\t\t\t\t; enable PF redirector support. required in FreeBSD when used with PF firewall.\n"
#endif
" --debug=0|1|2\t\t\t; 0(default)=silent 1=verbose 2=debug\n"
"\nTAMPERING:\n"
" --hostlist=<filename>\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; do not act on hosts in the list (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n"
" --split-http-req=method|host\t; split at specified logical part of plain http request\n"
" --split-pos=<numeric_offset>\t; split at specified pos. split-http-req takes precedence for http.\n"
" --split-any-protocol\t\t; split not only http and https\n"
" --debug=0|1|2\t\t\t\t; 0(default)=silent 1=verbose 2=debug\n"
"\nFILTER:\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"
" --hostlist-auto-fail-threshold=<int>\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n"
" --hostlist-auto-fail-time=<int>\t; all failed attemps must be within these seconds (default : %d)\n"
"\nTAMPER:\n"
" --split-http-req=method|host\t\t; split at specified logical part of plain http request\n"
" --split-pos=<numeric_offset>\t\t; split at specified pos. split-http-req takes precedence for http.\n"
" --split-any-protocol\t\t\t; split not only http and https\n"
#if defined(BSD) && !defined(__APPLE__)
" --disorder\t\t\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\n"
" --disorder\t\t\t\t; when splitting simulate sending second fragment first (BSD sends entire message instead of first fragment, this is not good)\n"
#else
" --disorder\t\t\t; when splitting simulate sending second fragment first\n"
" --disorder\t\t\t\t; when splitting simulate sending second fragment first\n"
#endif
" --hostcase\t\t\t; change Host: => host:\n"
" --hostspell\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n"
" --hostdot\t\t\t; add \".\" after Host: name\n"
" --hosttab\t\t\t; add tab after Host: name\n"
" --hostnospace\t\t\t; remove space after Host:\n"
" --hostpad=<bytes>\t\t; add dummy padding headers before Host:\n"
" --domcase\t\t\t; mix domain case : Host: TeSt.cOm\n"
" --methodspace\t\t\t; add extra space after method\n"
" --methodeol\t\t\t; add end-of-line before method\n"
" --unixeol\t\t\t; replace 0D0A to 0A\n"
" --tlsrec=sni\t\t\t; make 2 TLS records. split at SNI. don't split if SNI is not present\n"
" --tlsrec-pos=<pos>\t\t; make 2 TLS records. split at specified pos\n"
" --hostcase\t\t\t\t; change Host: => host:\n"
" --hostspell\t\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n"
" --hostdot\t\t\t\t; add \".\" after Host: name\n"
" --hosttab\t\t\t\t; add tab after Host: name\n"
" --hostnospace\t\t\t\t; remove space after Host:\n"
" --hostpad=<bytes>\t\t\t; add dummy padding headers before Host:\n"
" --domcase\t\t\t\t; mix domain case : Host: TeSt.cOm\n"
" --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\t\t\t\t; make 2 TLS records. split at SNI. don't split if SNI is not present\n"
" --tlsrec-pos=<pos>\t\t\t; make 2 TLS records. split at specified pos\n",
HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT
);
exit(1);
}
@@ -183,16 +195,9 @@ static void cleanup_params(void)
{
strlist_destroy(&params.hostlist_files);
strlist_destroy(&params.hostlist_exclude_files);
if (params.hostlist_exclude)
{
StrPoolDestroy(&params.hostlist_exclude);
params.hostlist_exclude = NULL;
}
if (params.hostlist)
{
StrPoolDestroy(&params.hostlist);
params.hostlist = NULL;
}
StrPoolDestroy(&params.hostlist_exclude);
StrPoolDestroy(&params.hostlist);
HostFailPoolDestroy(&params.hostlist_auto_fail_counters);
}
static void exithelp_clean(void)
{
@@ -246,6 +251,8 @@ void parse_params(int argc, char *argv[])
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;
LIST_INIT(&params.hostlist_files);
LIST_INIT(&params.hostlist_exclude_files);
@@ -294,18 +301,22 @@ void parse_params(int argc, char *argv[])
{ "tlsrec-pos",required_argument,0,0 },// optidx=32
{ "hostlist",required_argument,0,0 },// optidx=33
{ "hostlist-exclude",required_argument,0,0 },// optidx=34
{ "pidfile",required_argument,0,0 },// optidx=35
{ "debug",optional_argument,0,0 },// optidx=36
{ "local-rcvbuf",required_argument,0,0 },// optidx=37
{ "local-sndbuf",required_argument,0,0 },// optidx=38
{ "remote-rcvbuf",required_argument,0,0 },// optidx=39
{ "remote-sndbuf",required_argument,0,0 },// optidx=40
{ "socks",no_argument,0,0 },// optidx=41
{ "no-resolve",no_argument,0,0 },// optidx=42
{ "skip-nodelay",no_argument,0,0 },// optidx=43
{ "hostlist-auto",required_argument,0,0}, // optidx=35
{ "hostlist-auto-fail-threshold",required_argument,0,0}, // optidx=36
{ "hostlist-auto-fail-time",required_argument,0,0}, // optidx=37
{ "pidfile",required_argument,0,0 },// optidx=38
{ "debug",optional_argument,0,0 },// optidx=39
{ "local-rcvbuf",required_argument,0,0 },// optidx=40
{ "local-sndbuf",required_argument,0,0 },// optidx=41
{ "remote-rcvbuf",required_argument,0,0 },// optidx=42
{ "remote-sndbuf",required_argument,0,0 },// optidx=43
{ "socks",no_argument,0,0 },// optidx=44
{ "no-resolve",no_argument,0,0 },// optidx=45
{ "skip-nodelay",no_argument,0,0 },// optidx=46
#if defined(BSD) && !defined(__OpenBSD__) && !defined(__APPLE__)
{ "enable-pf",no_argument,0,0 },// optidx=44
{ "enable-pf",no_argument,0,0 },// optidx=47
#endif
{ "hostlist-auto-retrans-threshold",optional_argument,0,0}, // ignored. for nfqws command line compatibility
{ NULL,0,NULL,0 }
};
while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1)
@@ -544,36 +555,84 @@ void parse_params(int argc, char *argv[])
}
params.tamper = true;
break;
case 35: /* pidfile */
case 35: /* hostlist-auto */
if (*params.hostlist_auto_filename)
{
fprintf(stderr, "only one auto hostlist is supported\n");
exit_clean(1);
}
{
FILE *F = fopen(optarg,"a+t");
if (!F)
{
fprintf(stderr, "cannot create %s\n", optarg);
exit_clean(1);
}
bool bGzip = is_gzip(F);
fclose(F);
if (bGzip)
{
fprintf(stderr, "gzipped auto hostlists are not supported\n");
exit_clean(1);
}
if (params.droproot && chown(optarg, params.uid, -1))
fprintf(stderr, "could not chown %s. auto hostlist file may not be writable after privilege drop\n", optarg);
}
if (!strlist_add(&params.hostlist_files, optarg))
{
fprintf(stderr, "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';
params.tamper = true; // need to detect blocks and update autohostlist. cannot just slice.
break;
case 36: /* 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)
{
fprintf(stderr, "auto hostlist fail threshold must be within 1..20\n");
exit_clean(1);
}
break;
case 37: /* hostlist-auto-fail-time */
params.hostlist_auto_fail_time = (uint8_t)atoi(optarg);
if (params.hostlist_auto_fail_time<1)
{
fprintf(stderr, "auto hostlist fail time is not valid\n");
exit_clean(1);
}
break;
case 38: /* pidfile */
strncpy(params.pidfile,optarg,sizeof(params.pidfile));
params.pidfile[sizeof(params.pidfile)-1]='\0';
break;
case 36:
case 39:
params.debug = optarg ? atoi(optarg) : 1;
break;
case 37: /* local-rcvbuf */
case 40: /* local-rcvbuf */
params.local_rcvbuf = atoi(optarg)/2;
break;
case 38: /* local-sndbuf */
case 41: /* local-sndbuf */
params.local_sndbuf = atoi(optarg)/2;
break;
case 39: /* remote-rcvbuf */
case 42: /* remote-rcvbuf */
params.remote_rcvbuf = atoi(optarg)/2;
break;
case 40: /* remote-sndbuf */
case 43: /* remote-sndbuf */
params.remote_sndbuf = atoi(optarg)/2;
break;
case 41: /* socks */
case 44: /* socks */
params.proxy_type = CONN_TYPE_SOCKS;
break;
case 42: /* no-resolve */
case 45: /* no-resolve */
params.no_resolve = true;
break;
case 43: /* skip-nodelay */
case 46: /* skip-nodelay */
params.skip_nodelay = true;
break;
#if defined(BSD) && !defined(__OpenBSD__) && !defined(__APPLE__)
case 44: /* enable-pf */
case 47: /* enable-pf */
params.pf_enable = true;
break;
#endif
@@ -599,6 +658,7 @@ void parse_params(int argc, char *argv[])
fprintf(stderr, "Include hostlist load failed\n");
exit_clean(1);
}
if (*params.hostlist_auto_filename) NonEmptyHostlist(&params.hostlist);
if (!LoadHostLists(&params.hostlist_exclude, &params.hostlist_exclude_files))
{
fprintf(stderr, "Exclude hostlist load failed\n");
@@ -1020,6 +1080,7 @@ int main(int argc, char *argv[])
if (!params.tamper) printf("TCP proxy mode (no tampering)\n");
signal(SIGHUP, onhup);
signal(SIGUSR2, onusr2);
retval = event_loop(listen_fd,params.binds_last+1);
exit_v = retval < 0 ? EXIT_FAILURE : EXIT_SUCCESS;

View File

@@ -24,6 +24,7 @@
#include "socks.h"
#include "helpers.h"
// keep separate legs counter. counting every time thousands of legs can consume cpu
static int legs_local, legs_remote;
/*
@@ -376,6 +377,7 @@ static void free_conn(tproxy_conn_t *conn)
}
conn_free_buffers(conn);
if (conn->partner) conn->partner->partner=NULL;
if (conn->track.hostname) free(conn->track.hostname);
free(conn);
}
static tproxy_conn_t *new_conn(int fd, bool remote)
@@ -890,6 +892,25 @@ static bool handle_proxy_mode(tproxy_conn_t *conn, struct tailhead *conn_list)
return false;
}
static void tamper(tproxy_conn_t *conn, uint8_t *segment, size_t segment_buffer_size, size_t *segment_size, size_t *split_pos)
{
*split_pos=0;
if (params.tamper)
{
if (conn->remote)
{
if (conn_partner_alive(conn) && !conn->partner->track.bTamperInCutoff)
{
tamper_in(&conn->partner->track,segment,segment_buffer_size,segment_size);
}
}
else
{
tamper_out(&conn->track,segment,segment_buffer_size,segment_size,split_pos);
}
}
}
#define RD_BLOCK_SIZE 65536
#define MAX_WASTE (1024*1024)
static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32_t evt)
@@ -942,7 +963,7 @@ static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32
if (numbytes>0)
{
#ifdef SPLICE_PRESENT
if (!params.tamper || conn->remote)
if (!params.tamper || conn->remote && conn->track.bTamperInCutoff)
{
// incoming data from remote leg we splice without touching
// pipe is in the local leg, so its in conn->partner->splice_pipe
@@ -978,13 +999,11 @@ static bool handle_epoll(tproxy_conn_t *conn, struct tailhead *conn_list, uint32
{
conn->trd+=rd;
size_t split_pos=0;
size_t split_pos;
bs = rd;
#ifndef SPLICE_PRESENT
if (!conn->remote && params.tamper)
#endif
modify_tcp_segment(buf,sizeof(buf),&bs,&split_pos);
tamper(conn, buf, sizeof(buf), &bs, &split_pos);
if (split_pos)
{
@@ -1070,8 +1089,13 @@ static bool read_all_and_buffer(tproxy_conn_t *conn, int buffer_number)
conn->partner->wr_buf[buffer_number].len = rd;
conn->partner->bFlowOut = true;
size_t split_pos;
tamper(conn, conn->partner->wr_buf[buffer_number].data, numbytes, &conn->partner->wr_buf[buffer_number].len, &split_pos);
if (epoll_update_flow(conn->partner))
return true;
}
send_buffer_free(conn->partner->wr_buf+buffer_number);
}
@@ -1254,6 +1278,8 @@ int event_loop(const int *listen_fd, size_t listen_fd_ct)
VPRINT("Socket fd=%d (partner_fd=%d, remote=%d) %s so_error=%d (%s)",conn->fd,conn->partner ? conn->partner->fd : 0,conn->remote,se,errn,strerror(errn));
proxy_remote_conn_ack(conn,errn);
read_all_and_buffer(conn,3);
if (errn==ECONNRESET && conn->remote && conn_partner_alive(conn)) rst_in(&conn->partner->track);
conn_close_with_partner_check(&conn_list,&close_list,conn);
continue;
}
@@ -1270,6 +1296,7 @@ int event_loop(const int *listen_fd, size_t listen_fd_ct)
{
DBGPRINT("EPOLLRDHUP")
read_all_and_buffer(conn,2);
if (!conn->remote) hup_out(&conn->track);
if (conn_has_unsent(conn))
{

View File

@@ -3,6 +3,7 @@
#include <stdbool.h>
#include <sys/queue.h>
#include <time.h>
#include "tamper.h"
#define BACKLOG 10
#define MAX_EPOLL_EVENTS 64
@@ -27,7 +28,7 @@ typedef uint8_t conn_state_t;
// when pos==len its time to free buffer
struct send_buffer
{
char *data;
uint8_t *data;
size_t len,pos;
int ttl;
};
@@ -84,6 +85,8 @@ struct tproxy_conn
// buffer cannot be sent if there is unsent data in a lower buffer
struct send_buffer wr_buf[4];
t_ctrack track;
//Create the struct which contains ptrs to next/prev element
TAILQ_ENTRY(tproxy_conn) conn_ptrs;
};