mirror of
https://github.com/bol-van/zapret.git
synced 2025-05-24 22:32:58 +03:00
autohostlist mode
This commit is contained in:
@@ -25,11 +25,16 @@ static void connswap(const t_conn *c, t_conn *c2)
|
||||
c2->dport = c->sport;
|
||||
}
|
||||
|
||||
static void ConntrackFreeElem(t_conntrack_pool *elem)
|
||||
{
|
||||
if (elem->track.hostname) free(elem->track.hostname);
|
||||
free(elem);
|
||||
}
|
||||
|
||||
static void ConntrackPoolDestroyPool(t_conntrack_pool **pp)
|
||||
{
|
||||
t_conntrack_pool *elem, *tmp;
|
||||
HASH_ITER(hh, *pp, elem, tmp) { HASH_DEL(*pp, elem); free(elem); }
|
||||
HASH_ITER(hh, *pp, elem, tmp) { HASH_DEL(*pp, elem); ConntrackFreeElem(elem); }
|
||||
}
|
||||
void ConntrackPoolDestroy(t_conntrack *p)
|
||||
{
|
||||
@@ -226,7 +231,7 @@ static bool ConntrackPoolDropPool(t_conntrack_pool **pp, const struct ip *ip, co
|
||||
t=ConntrackPoolSearch(*pp,&connswp);
|
||||
}
|
||||
if (!t) return false;
|
||||
HASH_DEL(*pp, t); free(t);
|
||||
HASH_DEL(*pp, t); ConntrackFreeElem(t);
|
||||
return true;
|
||||
}
|
||||
bool ConntrackPoolDrop(t_conntrack *p, const struct ip *ip, const struct ip6_hdr *ip6, const struct tcphdr *tcphdr, const struct udphdr *udphdr)
|
||||
@@ -251,7 +256,7 @@ void ConntrackPoolPurge(t_conntrack *p)
|
||||
t->conn.l4proto==IPPROTO_UDP &&
|
||||
tidle>=p->timeout_udp)
|
||||
{
|
||||
HASH_DEL(p->pool, t); free(t);
|
||||
HASH_DEL(p->pool, t); ConntrackFreeElem(t);
|
||||
}
|
||||
}
|
||||
p->t_last_purge = tnow;
|
||||
@@ -263,6 +268,18 @@ static void taddr2str(uint8_t l3proto, const t_addr *a, char *buf, size_t bufsiz
|
||||
if (!inet_ntop(family_from_proto(l3proto), a, buf, bufsize) && bufsize) *buf=0;
|
||||
}
|
||||
|
||||
static const char *ConntrackProtoName(t_l7proto proto)
|
||||
{
|
||||
switch(proto)
|
||||
{
|
||||
case HTTP: return "HTTP";
|
||||
case TLS: return "TLS";
|
||||
case QUIC: return "QUIC";
|
||||
case WIREGUARD: return "WIREGUARD";
|
||||
case DHT: return "DHT";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
void ConntrackPoolDump(const t_conntrack *p)
|
||||
{
|
||||
t_conntrack_pool *t, *tmp;
|
||||
@@ -271,29 +288,24 @@ void ConntrackPoolDump(const t_conntrack *p)
|
||||
HASH_ITER(hh, p->pool, t, tmp) {
|
||||
taddr2str(t->conn.l3proto, &t->conn.src, sa1, sizeof(sa1));
|
||||
taddr2str(t->conn.l3proto, &t->conn.dst, sa2, sizeof(sa2));
|
||||
printf("%s [%s]:%u => [%s]:%u : %s : t0=%llu last=t0+%llu now=last+%llu packets_orig=d%llu/n%llu packets_reply=d%llu/n%llu ",
|
||||
proto_name(t->conn.l4proto),
|
||||
sa1, t->conn.sport, sa2, t->conn.dport,
|
||||
t->conn.l4proto==IPPROTO_TCP ? connstate_s[t->track.state] : "-",
|
||||
(unsigned long long)t->track.t_start, (unsigned long long)(t->track.t_last - t->track.t_start), (unsigned long long)(tnow - t->track.t_last),
|
||||
(unsigned long long)t->track.pdcounter_orig, (unsigned long long)t->track.pcounter_orig,
|
||||
(unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply);
|
||||
if (t->conn.l4proto==IPPROTO_TCP)
|
||||
printf("%s [%s]:%u => [%s]:%u : %s : t0=%llu last=t0+%llu now=last+%llu packets_orig=d%llu/n%llu packets_reply=d%llu/n%llu seq0=%u rseq=%u pos_orig=%u ack0=%u rack=%u pos_reply=%u wsize_orig=%u:%d wsize_reply=%u:%d",
|
||||
proto_name(t->conn.l4proto),
|
||||
sa1, t->conn.sport, sa2, t->conn.dport,
|
||||
t->conn.l4proto==IPPROTO_TCP ? connstate_s[t->track.state] : "-",
|
||||
(unsigned long long)t->track.t_start, (unsigned long long)(t->track.t_last - t->track.t_start), (unsigned long long)(tnow - t->track.t_last),
|
||||
(unsigned long long)t->track.pdcounter_orig, (unsigned long long)t->track.pcounter_orig,
|
||||
(unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply,
|
||||
printf("seq0=%u rseq=%u pos_orig=%u ack0=%u rack=%u pos_reply=%u wsize_orig=%u:%d wsize_reply=%u:%d",
|
||||
t->track.seq0, t->track.seq_last - t->track.seq0, t->track.pos_orig - t->track.seq0,
|
||||
t->track.ack0, t->track.ack_last - t->track.ack0, t->track.pos_reply - t->track.ack0,
|
||||
t->track.winsize_orig, t->track.scale_orig==SCALE_NONE ? -1 : t->track.scale_orig,
|
||||
t->track.winsize_reply, t->track.scale_reply==SCALE_NONE ? -1 : t->track.scale_reply);
|
||||
else
|
||||
printf("%s [%s]:%u => [%s]:%u : %s : t0=%llu last=t0+%llu now=last+%llu packets_orig=d%llu/n%llu packets_reply=d%llu/n%llu rseq=%u pos_orig=%u rack=%u pos_reply=%u",
|
||||
proto_name(t->conn.l4proto),
|
||||
sa1, t->conn.sport, sa2, t->conn.dport,
|
||||
t->conn.l4proto==IPPROTO_TCP ? connstate_s[t->track.state] : "-",
|
||||
(unsigned long long)t->track.t_start, (unsigned long long)(t->track.t_last - t->track.t_start), (unsigned long long)(tnow - t->track.t_last),
|
||||
(unsigned long long)t->track.pdcounter_orig, (unsigned long long)t->track.pcounter_orig,
|
||||
(unsigned long long)t->track.pdcounter_reply, (unsigned long long)t->track.pcounter_reply,
|
||||
printf("rseq=%u pos_orig=%u rack=%u pos_reply=%u",
|
||||
t->track.seq_last, t->track.pos_orig,
|
||||
t->track.ack_last, t->track.pos_reply);
|
||||
printf(" cutoff=%u wss_cutoff=%u d_cutoff=%u\n",
|
||||
t->track.b_cutoff, t->track.b_wssize_cutoff, t->track.b_desync_cutoff);
|
||||
printf(" req_retrans=%u cutoff=%u wss_cutoff=%u d_cutoff=%u hostname=%s l7proto=%s\n",
|
||||
t->track.req_retrans_counter, t->track.b_cutoff, t->track.b_wssize_cutoff, t->track.b_desync_cutoff, t->track.hostname, ConntrackProtoName(t->track.l7proto));
|
||||
};
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#define HASH_FUNCTION HASH_BER
|
||||
#include "uthash.h"
|
||||
|
||||
#define RETRANS_COUNTER_STOP ((uint8_t)-1)
|
||||
|
||||
typedef union {
|
||||
struct in_addr ip;
|
||||
@@ -37,6 +38,7 @@ typedef struct
|
||||
// ESTABLISHED - any except SYN or SYN/ACK received
|
||||
// FIN - FIN or RST received
|
||||
typedef enum {SYN=0, ESTABLISHED, FIN} t_connstate;
|
||||
typedef enum {UNKNOWN=0, HTTP, TLS, QUIC, WIREGUARD, DHT} t_l7proto;
|
||||
typedef struct
|
||||
{
|
||||
// common state
|
||||
@@ -51,9 +53,15 @@ typedef struct
|
||||
uint32_t seq0, ack0; // starting seq and ack
|
||||
uint16_t winsize_orig, winsize_reply; // last seen window size
|
||||
uint8_t scale_orig, scale_reply; // last seen window scale factor. SCALE_NONE if none
|
||||
|
||||
uint8_t req_retrans_counter; // number of request retransmissions
|
||||
uint32_t req_seq; // sequence number of the request (to track retransmissions)
|
||||
|
||||
bool b_cutoff; // mark for deletion
|
||||
bool b_wssize_cutoff, b_desync_cutoff;
|
||||
|
||||
t_l7proto l7proto;
|
||||
char *hostname;
|
||||
} t_ctrack;
|
||||
|
||||
typedef struct
|
||||
|
179
nfq/desync.c
179
nfq/desync.c
@@ -11,9 +11,9 @@
|
||||
|
||||
|
||||
const char *fake_http_request_default = "GET / HTTP/1.1\r\nHost: www.w3.org\r\n"
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0\r\n"
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
|
||||
"Accept-Encoding: gzip, deflate\r\n\r\n";
|
||||
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0\r\n"
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n"
|
||||
"Accept-Encoding: gzip, deflate, br\r\n\r\n";
|
||||
const uint8_t fake_tls_clienthello_default[517] = {
|
||||
0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03, 0x9a, 0x8f, 0xa7, 0x6a, 0x5d,
|
||||
0x57, 0xf3, 0x62, 0x19, 0xbe, 0x46, 0x82, 0x45, 0xe2, 0x59, 0x5c, 0xb4, 0x48, 0x31, 0x12, 0x15,
|
||||
@@ -158,7 +158,10 @@ static void maybe_cutoff(t_ctrack *ctrack, uint8_t proto)
|
||||
if (proto==IPPROTO_TCP)
|
||||
// we do not need conntrack entry anymore if all cutoff conditions are either not defined or reached
|
||||
// do not drop udp entry because it will be recreated when next packet arrives
|
||||
ctrack->b_cutoff |= (!params.wssize || ctrack->b_wssize_cutoff) && (!params.desync_cutoff || ctrack->b_desync_cutoff);
|
||||
ctrack->b_cutoff |= \
|
||||
(!params.wssize || ctrack->b_wssize_cutoff) &&
|
||||
(!params.desync_cutoff || ctrack->b_desync_cutoff) &&
|
||||
(!*params.hostlist_auto_filename || ctrack->req_retrans_counter==RETRANS_COUNTER_STOP);
|
||||
}
|
||||
}
|
||||
static void wssize_cutoff(t_ctrack *ctrack)
|
||||
@@ -169,7 +172,73 @@ static void wssize_cutoff(t_ctrack *ctrack)
|
||||
maybe_cutoff(ctrack, IPPROTO_TCP);
|
||||
}
|
||||
}
|
||||
#define CONNTRACK_REQUIRED (params.wssize || params.desync_cutoff)
|
||||
|
||||
static void ctrack_stop_req_counter(t_ctrack *ctrack)
|
||||
{
|
||||
ctrack->req_retrans_counter = RETRANS_COUNTER_STOP;
|
||||
maybe_cutoff(ctrack, IPPROTO_TCP);
|
||||
}
|
||||
|
||||
// return true if retrans trigger fires
|
||||
static bool auto_hostlist_retrans(t_ctrack *ctrack, uint8_t l4proto, int threshold)
|
||||
{
|
||||
if (*params.hostlist_auto_filename && ctrack && ctrack->req_retrans_counter!=RETRANS_COUNTER_STOP)
|
||||
{
|
||||
ctrack->req_retrans_counter++;
|
||||
DLOG("req retrans counter : %u/%u\n",ctrack->req_retrans_counter, threshold);
|
||||
if (ctrack->req_retrans_counter >= threshold)
|
||||
{
|
||||
DLOG("req retrans threshold reached\n");
|
||||
ctrack_stop_req_counter(ctrack);
|
||||
return true;
|
||||
}
|
||||
if (l4proto==IPPROTO_TCP)
|
||||
{
|
||||
if (!ctrack->req_seq) ctrack->req_seq = ctrack->seq_last;
|
||||
if (ctrack->seq_last != ctrack->req_seq)
|
||||
{
|
||||
DLOG("another request, not retransmission. stop tracking.\n");
|
||||
ctrack_stop_req_counter(ctrack);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
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(¶ms.hostlist_auto_fail_counters, hostname, params.hostlist_auto_fail_time);
|
||||
if (!fail_counter)
|
||||
{
|
||||
fprintf(stderr, "HostFailPoolAdd: out of memory\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail_counter->counter++;
|
||||
DLOG("auto hostlist : %s : fail counter %d/%d\n", hostname, fail_counter->counter, params.hostlist_auto_fail_threshold);
|
||||
if (fail_counter->counter >= params.hostlist_auto_fail_threshold)
|
||||
{
|
||||
DLOG("auto hostlist : fail threshold reached. adding %s to auto hostlist\n", hostname);
|
||||
HostFailPoolDel(¶ms.hostlist_auto_fail_counters, fail_counter);
|
||||
if (!StrPoolAddStr(¶ms.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define CONNTRACK_REQUIRED (params.wssize || params.desync_cutoff || *params.hostlist_auto_filename)
|
||||
// result : true - drop original packet, false = dont drop
|
||||
packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout, uint8_t *data_pkt, size_t len_pkt, struct ip *ip, struct ip6_hdr *ip6hdr, struct tcphdr *tcphdr, size_t len_tcp, uint8_t *data_payload, size_t len_payload)
|
||||
{
|
||||
@@ -190,6 +259,7 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
ConntrackPoolPurge(¶ms.conntrack);
|
||||
if (ConntrackPoolFeed(¶ms.conntrack, ip, ip6hdr, tcphdr, NULL, len_payload, &ctrack, &bReverse))
|
||||
maybe_cutoff(ctrack, IPPROTO_TCP);
|
||||
HostFailPoolPurgeRateLimited(¶ms.hostlist_auto_fail_counters);
|
||||
}
|
||||
|
||||
//ConntrackPoolDump(¶ms.conntrack);
|
||||
@@ -199,11 +269,48 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
tcp_rewrite_winsize(tcphdr, params.wsize, params.wscale);
|
||||
res=modify;
|
||||
}
|
||||
|
||||
if (bReverse) return res; // nothing to do. do not waste cpu
|
||||
|
||||
if (bReverse)
|
||||
{
|
||||
// process reply packets for auto hostlist mode
|
||||
// by looking at RSTs or HTTP replies we decide whether original request looks to be blocked by DPI
|
||||
if (*params.hostlist_auto_filename && ctrack && ctrack->hostname && ctrack->req_retrans_counter != RETRANS_COUNTER_STOP)
|
||||
{
|
||||
bool bFail=false, bStop=false;
|
||||
if (tcphdr->th_flags & TH_RST)
|
||||
{
|
||||
DLOG("incoming RST detected for hostname %s\n", ctrack->hostname);
|
||||
bFail = bStop = true;
|
||||
}
|
||||
else if (len_payload && ctrack->l7proto==HTTP)
|
||||
{
|
||||
if (IsHttpReply(data_payload,len_payload))
|
||||
{
|
||||
DLOG("incoming HTTP reply detected for hostname %s\n", ctrack->hostname);
|
||||
bFail = HttpReplyLooksLikeDPIRedirect(data_payload, len_payload, ctrack->hostname);
|
||||
if (bFail)
|
||||
DLOG("redirect to another domain detected. possibly DPI redirect.\n")
|
||||
else
|
||||
DLOG("local or in-domain redirect detected. it's not a DPI redirect.\n")
|
||||
}
|
||||
else
|
||||
{
|
||||
// received not http reply. do not monitor this connection anymore
|
||||
DLOG("incoming unknown HTTP data detected for hostname %s\n", ctrack->hostname);
|
||||
}
|
||||
bStop = true;
|
||||
}
|
||||
if (bFail)
|
||||
auto_hostlist_failed(ctrack->hostname);
|
||||
if (bStop)
|
||||
ctrack_stop_req_counter(ctrack);
|
||||
}
|
||||
|
||||
return res; // nothing to do. do not waste cpu
|
||||
}
|
||||
|
||||
uint32_t desync_fwmark = fwmark | params.desync_fwmark;
|
||||
|
||||
|
||||
if (params.wssize)
|
||||
{
|
||||
if (ctrack)
|
||||
@@ -269,7 +376,7 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.wssize && params.desync_mode==DESYNC_NONE && !params.hostcase && !params.hostnospace && !params.domcase) return res; // nothing to do. do not waste cpu
|
||||
if (!params.wssize && params.desync_mode==DESYNC_NONE && !params.hostcase && !params.hostnospace && !params.domcase && !*params.hostlist_auto_filename) return res; // nothing to do. do not waste cpu
|
||||
|
||||
if (!(tcphdr->th_flags & TH_SYN) && len_payload)
|
||||
{
|
||||
@@ -280,12 +387,15 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
bool bIsHttp;
|
||||
bool bKnownProtocol = false;
|
||||
uint8_t *p, *phost;
|
||||
|
||||
if ((bIsHttp = IsHttp(data_payload,len_payload)))
|
||||
{
|
||||
DLOG("packet contains HTTP request\n")
|
||||
if (params.wssize) DLOG("forced wssize-cutoff\n");
|
||||
wssize_cutoff(ctrack);
|
||||
if (ctrack && !ctrack->l7proto) ctrack->l7proto = HTTP;
|
||||
if (params.wssize)
|
||||
{
|
||||
DLOG("forced wssize-cutoff\n");
|
||||
wssize_cutoff(ctrack);
|
||||
}
|
||||
fake = params.fake_http;
|
||||
fake_size = params.fake_http_size;
|
||||
if (params.hostlist || params.debug) bHaveHost=HttpExtractHost(data_payload,len_payload,host,sizeof(host));
|
||||
@@ -299,8 +409,12 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
else if (IsTLSClientHello(data_payload,len_payload))
|
||||
{
|
||||
DLOG("packet contains TLS ClientHello\n")
|
||||
if (params.wssize) DLOG("forced wssize-cutoff\n");
|
||||
wssize_cutoff(ctrack);
|
||||
if (ctrack && !ctrack->l7proto) ctrack->l7proto = TLS;
|
||||
if (params.wssize)
|
||||
{
|
||||
DLOG("forced wssize-cutoff\n");
|
||||
wssize_cutoff(ctrack);
|
||||
}
|
||||
fake = params.fake_tls;
|
||||
fake_size = params.fake_tls_size;
|
||||
if (params.hostlist || params.desync_skip_nosni || params.debug)
|
||||
@@ -316,6 +430,10 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctrack && *params.hostlist_auto_filename)
|
||||
// received unknown payload. it means we are out of the request retransmission phase. stop counter
|
||||
ctrack_stop_req_counter(ctrack);
|
||||
|
||||
if (!params.desync_any_proto) return res;
|
||||
DLOG("applying tampering to unknown protocol\n")
|
||||
fake = params.fake_unknown;
|
||||
@@ -325,9 +443,17 @@ packet_process_result dpi_desync_tcp_packet(uint32_t fwmark, const char *ifout,
|
||||
if (bHaveHost)
|
||||
{
|
||||
DLOG("hostname: %s\n",host)
|
||||
if ((params.hostlist || params.hostlist_exclude) && !HostlistCheck(params.hostlist, params.hostlist_exclude, host))
|
||||
bool bExcluded;
|
||||
if ((params.hostlist || params.hostlist_exclude) && !HostlistCheck(params.hostlist, params.hostlist_exclude, host, &bExcluded))
|
||||
{
|
||||
DLOG("not applying tampering to this request\n")
|
||||
if (!bExcluded)
|
||||
{
|
||||
if (*params.hostlist_auto_filename && ctrack && !ctrack->hostname)
|
||||
ctrack->hostname=strdup(host);
|
||||
if (auto_hostlist_retrans(ctrack, IPPROTO_TCP, params.hostlist_auto_retrans_threshold))
|
||||
auto_hostlist_failed(host);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -650,12 +776,13 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
ConntrackPoolPurge(¶ms.conntrack);
|
||||
if (ConntrackPoolFeed(¶ms.conntrack, ip, ip6hdr, NULL, udphdr, len_payload, &ctrack, &bReverse))
|
||||
maybe_cutoff(ctrack, IPPROTO_UDP);
|
||||
HostFailPoolPurgeRateLimited(¶ms.hostlist_auto_fail_counters);
|
||||
}
|
||||
|
||||
//ConntrackPoolDump(¶ms.conntrack);
|
||||
|
||||
if (bReverse) return res; // nothing to do. do not waste cpu
|
||||
|
||||
|
||||
if (params.desync_cutoff)
|
||||
{
|
||||
if (ctrack)
|
||||
@@ -674,7 +801,7 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
}
|
||||
}
|
||||
|
||||
if (params.desync_mode==DESYNC_NONE) return res;
|
||||
if (params.desync_mode==DESYNC_NONE && !*params.hostlist_auto_filename) return res; // do not waste cpu
|
||||
|
||||
if (len_payload)
|
||||
{
|
||||
@@ -688,6 +815,8 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
if (IsQUICInitial(data_payload,len_payload))
|
||||
{
|
||||
DLOG("packet contains QUIC initial\n")
|
||||
|
||||
if (ctrack && !ctrack->l7proto) ctrack->l7proto = QUIC;
|
||||
fake = params.fake_quic;
|
||||
fake_size = params.fake_quic_size;
|
||||
|
||||
@@ -732,6 +861,7 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
else if (IsWireguardHandshakeInitiation(data_payload,len_payload))
|
||||
{
|
||||
DLOG("packet contains wireguard handshake initiation\n")
|
||||
if (ctrack && !ctrack->l7proto) ctrack->l7proto = WIREGUARD;
|
||||
fake = params.fake_wg;
|
||||
fake_size = params.fake_wg_size;
|
||||
bKnownProtocol = true;
|
||||
@@ -739,12 +869,17 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
else if (IsDhtD1(data_payload,len_payload))
|
||||
{
|
||||
DLOG("packet contains DHT d1...e\n")
|
||||
if (ctrack && !ctrack->l7proto) ctrack->l7proto = DHT;
|
||||
fake = params.fake_dht;
|
||||
fake_size = params.fake_dht_size;
|
||||
bKnownProtocol = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctrack && *params.hostlist_auto_filename)
|
||||
// received unknown payload. it means we are out of the request retransmission phase. stop counter
|
||||
ctrack_stop_req_counter(ctrack);
|
||||
|
||||
if (!params.desync_any_proto) return res;
|
||||
DLOG("applying tampering to unknown protocol\n")
|
||||
fake = params.fake_unknown_udp;
|
||||
@@ -754,9 +889,17 @@ packet_process_result dpi_desync_udp_packet(uint32_t fwmark, const char *ifout,
|
||||
if (bHaveHost)
|
||||
{
|
||||
DLOG("hostname: %s\n",host)
|
||||
if ((params.hostlist || params.hostlist_exclude) && !HostlistCheck(params.hostlist, params.hostlist_exclude, host))
|
||||
bool bExcluded;
|
||||
if ((params.hostlist || params.hostlist_exclude) && !HostlistCheck(params.hostlist, params.hostlist_exclude, host, &bExcluded))
|
||||
{
|
||||
DLOG("not applying tampering to this request\n")
|
||||
if (!bExcluded)
|
||||
{
|
||||
if (*params.hostlist_auto_filename && ctrack && !ctrack->hostname)
|
||||
ctrack->hostname=strdup(host);
|
||||
if (auto_hostlist_retrans(ctrack, IPPROTO_UDP, params.hostlist_auto_retrans_threshold))
|
||||
auto_hostlist_failed(host);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
@@ -82,6 +82,14 @@ bool save_file(const char *filename, const void *buffer, size_t buffer_size)
|
||||
fclose(F);
|
||||
return true;
|
||||
}
|
||||
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)
|
||||
|
@@ -13,6 +13,7 @@ char *strncasestr(const char *s,const char *find, size_t slen);
|
||||
bool load_file(const char *filename,void *buffer,size_t *buffer_size);
|
||||
bool load_file_nonempty(const char *filename,void *buffer,size_t *buffer_size);
|
||||
bool save_file(const char *filename, const void *buffer, size_t buffer_size);
|
||||
bool append_to_list_file(const char *filename, const char *s);
|
||||
|
||||
void print_sockaddr(const struct sockaddr *sa);
|
||||
void ntop46(const struct sockaddr *sa, char *str, size_t len);
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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);
|
||||
|
108
nfq/nfqws.c
108
nfq/nfqws.c
@@ -8,6 +8,8 @@
|
||||
#include "params.h"
|
||||
#include "protocol.h"
|
||||
#include "hostlist.h"
|
||||
#include "gzip.h"
|
||||
#include "pools.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -24,6 +26,7 @@
|
||||
#include <time.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#ifdef __linux__
|
||||
@@ -73,6 +76,12 @@ static void onusr1(int sig)
|
||||
ConntrackPoolDump(¶ms.conntrack);
|
||||
printf("\n");
|
||||
}
|
||||
static void onusr2(int sig)
|
||||
{
|
||||
printf("\nHOSTFAIL POOL DUMP\n");
|
||||
HostFailPoolDump(params.hostlist_auto_fail_counters);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -284,6 +293,7 @@ static int nfq_main(void)
|
||||
|
||||
signal(SIGHUP, onhup);
|
||||
signal(SIGUSR1, onusr1);
|
||||
signal(SIGUSR2, onusr2);
|
||||
|
||||
desync_init();
|
||||
|
||||
@@ -553,7 +563,11 @@ static void exithelp(void)
|
||||
" --dpi-desync-udplen-pattern=<filename>|0xHEX\t; udp tail fill pattern\n"
|
||||
" --dpi-desync-cutoff=[n|d|s]N\t\t\t; apply dpi desync only to packet numbers (n, default), data packet numbers (d), relative sequence (s) less than N\n"
|
||||
" --hostlist=<filename>\t\t\t\t; apply dpi desync only to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n"
|
||||
" --hostlist-exclude=<filename>\t\t\t; do not apply dpi desync to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n",
|
||||
" --hostlist-exclude=<filename>\t\t\t; do not apply dpi desync to the listed hosts (one host per line, subdomains auto apply, gzip supported, multiple hostlists allowed)\n"
|
||||
" --hostlist-auto=<filename>\t\t\t; detect DPI blocks and build hostlist automatically\n"
|
||||
" --hostlist-auto-fail-threshold=<int>\t\t; how many failed attempts cause hostname to be added to auto hostlist (default : %d)\n"
|
||||
" --hostlist-auto-fail-time=<int>\t\t; all failed attemps must be within these seconds (default : %d)\n"
|
||||
" --hostlist-auto-retrans-threshold=<int>\t; how many request retransmissions cause attempt to fail (default : %d)\n",
|
||||
CTRACK_T_SYN, CTRACK_T_EST, CTRACK_T_FIN, CTRACK_T_UDP,
|
||||
#if defined(__linux__) || defined(SO_USER_COOKIE)
|
||||
DPI_DESYNC_FWMARK_DEFAULT,DPI_DESYNC_FWMARK_DEFAULT,
|
||||
@@ -562,7 +576,8 @@ static void exithelp(void)
|
||||
DPI_DESYNC_MAX_FAKE_LEN, IPFRAG_UDP_DEFAULT,
|
||||
DPI_DESYNC_MAX_FAKE_LEN, IPFRAG_TCP_DEFAULT,
|
||||
BADSEQ_INCREMENT_DEFAULT, BADSEQ_ACK_INCREMENT_DEFAULT,
|
||||
UDPLEN_INCREMENT_DEFAULT
|
||||
UDPLEN_INCREMENT_DEFAULT,
|
||||
HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT, HOSTLIST_AUTO_FAIL_TIME_DEFAULT, HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
@@ -573,16 +588,9 @@ static void cleanup_params(void)
|
||||
|
||||
strlist_destroy(¶ms.hostlist_files);
|
||||
strlist_destroy(¶ms.hostlist_exclude_files);
|
||||
if (params.hostlist_exclude)
|
||||
{
|
||||
StrPoolDestroy(¶ms.hostlist_exclude);
|
||||
params.hostlist_exclude = NULL;
|
||||
}
|
||||
if (params.hostlist)
|
||||
{
|
||||
StrPoolDestroy(¶ms.hostlist);
|
||||
params.hostlist = NULL;
|
||||
}
|
||||
StrPoolDestroy(¶ms.hostlist_exclude);
|
||||
StrPoolDestroy(¶ms.hostlist);
|
||||
HostFailPoolDestroy(¶ms.hostlist_auto_fail_counters);
|
||||
}
|
||||
static void exithelp_clean(void)
|
||||
{
|
||||
@@ -641,7 +649,7 @@ int main(int argc, char **argv)
|
||||
int option_index = 0;
|
||||
bool daemon = false;
|
||||
char pidfile[256];
|
||||
|
||||
|
||||
srandom(time(NULL));
|
||||
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
@@ -674,10 +682,13 @@ int main(int argc, char **argv)
|
||||
params.desync_badseq_ack_increment = BADSEQ_ACK_INCREMENT_DEFAULT;
|
||||
params.wssize_cutoff_mode = params.desync_cutoff_mode = 'n'; // packet number by default
|
||||
params.udplen_increment = UDPLEN_INCREMENT_DEFAULT;
|
||||
params.hostlist_auto_fail_threshold = HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT;
|
||||
params.hostlist_auto_fail_time = HOSTLIST_AUTO_FAIL_TIME_DEFAULT;
|
||||
params.hostlist_auto_retrans_threshold = HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT;
|
||||
|
||||
LIST_INIT(¶ms.hostlist_files);
|
||||
LIST_INIT(¶ms.hostlist_exclude_files);
|
||||
|
||||
|
||||
if (can_drop_root()) // are we root ?
|
||||
{
|
||||
params.uid = params.gid = 0x7FFFFFFF; // default uid:gid
|
||||
@@ -737,9 +748,14 @@ int main(int argc, char **argv)
|
||||
{"dpi-desync-cutoff",required_argument,0,0},// optidx=37
|
||||
{"hostlist",required_argument,0,0}, // optidx=38
|
||||
{"hostlist-exclude",required_argument,0,0}, // optidx=39
|
||||
{"hostlist-auto",required_argument,0,0}, // optidx=40
|
||||
{"hostlist-auto-fail-threshold",required_argument,0,0}, // optidx=41
|
||||
{"hostlist-auto-fail-time",required_argument,0,0}, // optidx=42
|
||||
{"hostlist-auto-retrans-threshold",required_argument,0,0}, // optidx=43
|
||||
|
||||
#ifdef __linux__
|
||||
{"bind-fix4",no_argument,0,0}, // optidx=40
|
||||
{"bind-fix6",no_argument,0,0}, // optidx=41
|
||||
{"bind-fix4",no_argument,0,0}, // optidx=44
|
||||
{"bind-fix6",no_argument,0,0}, // optidx=45
|
||||
#endif
|
||||
{NULL,0,NULL,0}
|
||||
};
|
||||
@@ -1069,11 +1085,66 @@ int main(int argc, char **argv)
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 40: /* 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(¶ms.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';
|
||||
break;
|
||||
case 41: /* 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 42: /* 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 43: /* hostlist-auto-retrans-threshold */
|
||||
params.hostlist_auto_retrans_threshold = (uint8_t)atoi(optarg);
|
||||
if (params.hostlist_auto_retrans_threshold<2 || params.hostlist_auto_retrans_threshold>10)
|
||||
{
|
||||
fprintf(stderr, "auto hostlist fail threshold must be within 2..10\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
#ifdef __linux__
|
||||
case 40: /* bind-fix4 */
|
||||
case 44: /* bind-fix4 */
|
||||
params.bind_fix4 = true;
|
||||
break;
|
||||
case 41: /* bind-fix6 */
|
||||
case 45: /* bind-fix6 */
|
||||
params.bind_fix6 = true;
|
||||
break;
|
||||
#endif
|
||||
@@ -1094,6 +1165,7 @@ int main(int argc, char **argv)
|
||||
fprintf(stderr, "Include hostlist load failed\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
if (*params.hostlist_auto_filename) NonEmptyHostlist(¶ms.hostlist);
|
||||
if (!LoadHostLists(¶ms.hostlist_exclude, ¶ms.hostlist_exclude_files))
|
||||
{
|
||||
fprintf(stderr, "Exclude hostlist load failed\n");
|
||||
|
10
nfq/params.h
10
nfq/params.h
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "params.h"
|
||||
#include "strpool.h"
|
||||
#include "pools.h"
|
||||
#include "conntrack.h"
|
||||
#include "desync.h"
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
|
||||
#define UDPLEN_INCREMENT_DEFAULT 2
|
||||
|
||||
#define HOSTLIST_AUTO_FAIL_THRESHOLD_DEFAULT 2
|
||||
#define HOSTLIST_AUTO_FAIL_TIME_DEFAULT 60
|
||||
#define HOSTLIST_AUTO_RETRANS_THRESHOLD_DEFAULT 3
|
||||
|
||||
|
||||
struct params_s
|
||||
{
|
||||
bool debug;
|
||||
@@ -59,6 +64,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, hostlist_auto_retrans_threshold;
|
||||
hostfail_pool *hostlist_auto_fail_counters;
|
||||
|
||||
unsigned int ctrack_t_syn, ctrack_t_est, ctrack_t_fin, ctrack_t_udp;
|
||||
t_conntrack conntrack;
|
||||
|
152
nfq/pools.c
Normal file
152
nfq/pools.c
Normal 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
nfq/pools.h
Normal file
46
nfq/pools.h
Normal 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);
|
160
nfq/protocol.c
160
nfq/protocol.c
@@ -20,69 +20,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)
|
||||
{
|
||||
// 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;
|
||||
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)
|
||||
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;
|
||||
}
|
||||
|
||||
static uint8_t tvb_get_varint(const uint8_t *tvb, uint64_t *value)
|
||||
bool HttpExtractHost(const uint8_t *data, size_t len, char *host, size_t len_host)
|
||||
{
|
||||
switch (*tvb >> 6)
|
||||
return HttpExtractHeader(data, len, "\nHost:", host, len_host);
|
||||
}
|
||||
const char *HttpFind2ndLevelDomain(const char *host)
|
||||
{
|
||||
const char *p=NULL;
|
||||
if (*host)
|
||||
{
|
||||
case 0: /* 0b00 => 1 byte length (6 bits Usable) */
|
||||
if (value) *value = *tvb & 0x3F;
|
||||
return 1;
|
||||
case 1: /* 0b01 => 2 bytes length (14 bits Usable) */
|
||||
if (value) *value = pntoh16(tvb) & 0x3FFF;
|
||||
return 2;
|
||||
case 2: /* 0b10 => 4 bytes length (30 bits Usable) */
|
||||
if (value) *value = pntoh32(tvb) & 0x3FFFFFFF;
|
||||
return 4;
|
||||
case 3: /* 0b11 => 8 bytes length (62 bits Usable) */
|
||||
if (value) *value = pntoh64(tvb) & 0x3FFFFFFFFFFFFFFF;
|
||||
return 8;
|
||||
for (p = host + strlen(host)-1; p>host && *p!='.'; p--);
|
||||
if (*p=='.') for (p--; p>host && *p!='.'; p--);
|
||||
if (*p=='.') p++;
|
||||
}
|
||||
return 0;
|
||||
return p;
|
||||
}
|
||||
static uint8_t tvb_get_size(uint8_t tvb)
|
||||
// DPI redirects are global redirects to another domain
|
||||
bool HttpReplyLooksLikeDPIRedirect(const uint8_t *data, size_t len, const char *host)
|
||||
{
|
||||
return 1 << (tvb >> 6);
|
||||
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 IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len)
|
||||
{
|
||||
size_t offset = 1;
|
||||
uint64_t coff, clen;
|
||||
if (len < 3 || *data != 6) return false;
|
||||
if ((offset+tvb_get_size(data[offset])) >= len) return false;
|
||||
offset += tvb_get_varint(data + offset, &coff);
|
||||
// offset must be 0 if it's a full segment, not just a chunk
|
||||
if (coff || (offset+tvb_get_size(data[offset])) >= len) return false;
|
||||
offset += tvb_get_varint(data + offset, &clen);
|
||||
if (data[offset] != 0x01 || (offset + clen) > len) return false;
|
||||
if (hello_offset) *hello_offset = offset;
|
||||
if (hello_len) *hello_len = (size_t)clen;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
@@ -188,13 +214,45 @@ bool TLSHelloExtractHostFromHandshake(const uint8_t *data, size_t len, char *hos
|
||||
}
|
||||
|
||||
|
||||
bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len)
|
||||
|
||||
static uint8_t tvb_get_varint(const uint8_t *tvb, uint64_t *value)
|
||||
{
|
||||
return len==148 && data[0]==1 && data[1]==0 && data[2]==0 && data[3]==0;
|
||||
switch (*tvb >> 6)
|
||||
{
|
||||
case 0: /* 0b00 => 1 byte length (6 bits Usable) */
|
||||
if (value) *value = *tvb & 0x3F;
|
||||
return 1;
|
||||
case 1: /* 0b01 => 2 bytes length (14 bits Usable) */
|
||||
if (value) *value = pntoh16(tvb) & 0x3FFF;
|
||||
return 2;
|
||||
case 2: /* 0b10 => 4 bytes length (30 bits Usable) */
|
||||
if (value) *value = pntoh32(tvb) & 0x3FFFFFFF;
|
||||
return 4;
|
||||
case 3: /* 0b11 => 8 bytes length (62 bits Usable) */
|
||||
if (value) *value = pntoh64(tvb) & 0x3FFFFFFFFFFFFFFF;
|
||||
return 8;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
bool IsDhtD1(const uint8_t *data, size_t len)
|
||||
static uint8_t tvb_get_size(uint8_t tvb)
|
||||
{
|
||||
return len>=7 && data[0]=='d' && data[1]=='1' && data[len-1]=='e';
|
||||
return 1 << (tvb >> 6);
|
||||
}
|
||||
|
||||
bool IsQUICCryptoHello(const uint8_t *data, size_t len, size_t *hello_offset, size_t *hello_len)
|
||||
{
|
||||
size_t offset = 1;
|
||||
uint64_t coff, clen;
|
||||
if (len < 3 || *data != 6) return false;
|
||||
if ((offset+tvb_get_size(data[offset])) >= len) return false;
|
||||
offset += tvb_get_varint(data + offset, &coff);
|
||||
// offset must be 0 if it's a full segment, not just a chunk
|
||||
if (coff || (offset+tvb_get_size(data[offset])) >= len) return false;
|
||||
offset += tvb_get_varint(data + offset, &clen);
|
||||
if (data[offset] != 0x01 || (offset + clen) > len) return false;
|
||||
if (hello_offset) *hello_offset = offset;
|
||||
if (hello_len) *hello_len = (size_t)clen;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Returns the QUIC draft version or 0 if not applicable. */
|
||||
@@ -258,7 +316,6 @@ static bool is_quic_v2(uint32_t version)
|
||||
return version == 0x709A50C4;
|
||||
}
|
||||
|
||||
|
||||
static bool quic_hkdf_expand_label(const uint8_t *secret, uint8_t secret_len, const char *label, uint8_t *out, size_t out_len)
|
||||
{
|
||||
uint8_t hkdflabel[64];
|
||||
@@ -275,7 +332,6 @@ static bool quic_hkdf_expand_label(const uint8_t *secret, uint8_t secret_len, co
|
||||
return !hkdfExpand(SHA256, secret, secret_len, hkdflabel, hkdflabel_size, out, out_len);
|
||||
}
|
||||
|
||||
|
||||
static bool quic_derive_initial_secret(const quic_cid_t *cid, uint8_t *client_initial_secret, uint32_t version)
|
||||
{
|
||||
/*
|
||||
@@ -505,7 +561,6 @@ bool QUICDefragCrypto(const uint8_t *clean,size_t clean_len, uint8_t *defrag,siz
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
bool QUICExtractHostFromInitial(const uint8_t *data, size_t data_len, char *host, size_t len_host, bool *bDecryptOK, bool *bIsCryptoHello)
|
||||
{
|
||||
if (bIsCryptoHello) *bIsCryptoHello=false;
|
||||
@@ -565,3 +620,14 @@ bool IsQUICInitial(const uint8_t *data, size_t len)
|
||||
// client hello cannot be too small. likely ACK
|
||||
return sz>=96;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool IsWireguardHandshakeInitiation(const uint8_t *data, size_t len)
|
||||
{
|
||||
return len==148 && data[0]==1 && data[1]==0 && data[2]==0 && data[3]==0;
|
||||
}
|
||||
bool IsDhtD1(const uint8_t *data, size_t len)
|
||||
{
|
||||
return len>=7 && data[0]=='d' && data[1]=='1' && data[len-1]=='e';
|
||||
}
|
||||
|
@@ -7,7 +7,15 @@
|
||||
#include "crypto/aes-gcm.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);
|
||||
|
107
nfq/strpool.c
107
nfq/strpool.c
@@ -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);
|
||||
}
|
||||
}
|
@@ -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);
|
Reference in New Issue
Block a user