From ebecc423c74f0f8530b3875e49c48c2679621ec8 Mon Sep 17 00:00:00 2001 From: bol-van Date: Wed, 16 Oct 2024 13:42:59 +0300 Subject: [PATCH] nfqws: user mode ipset support --- nfq/conntrack.c | 37 +++-- nfq/conntrack.h | 16 +- nfq/desync.c | 411 ++++++++++++++++++++++++++++++------------------ nfq/helpers.c | 107 +++++++++++++ nfq/helpers.h | 42 +++++ nfq/hostlist.c | 6 +- nfq/ipset.c | 195 +++++++++++++++++++++++ nfq/ipset.h | 11 ++ nfq/nfqws.c | 132 ++++++++++++---- nfq/params.c | 4 + nfq/params.h | 6 + nfq/pools.c | 124 +++++++++++++++ nfq/pools.h | 40 +++++ 13 files changed, 934 insertions(+), 197 deletions(-) create mode 100644 nfq/ipset.c create mode 100644 nfq/ipset.h diff --git a/nfq/conntrack.c b/nfq/conntrack.c index 62a79db..1f1fd7c 100644 --- a/nfq/conntrack.c +++ b/nfq/conntrack.c @@ -12,6 +12,29 @@ static void ut_oom_recover(void *elem) oom = 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)); +} + + static const char *connstate_s[]={"SYN","ESTABLISHED","FIN"}; static void connswap(const t_conn *c, t_conn *c2) @@ -316,18 +339,6 @@ 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; @@ -354,7 +365,7 @@ void ConntrackPoolDump(const t_conntrack *p) t->track.seq_last, t->track.pos_orig, t->track.ack_last, t->track.pos_reply); 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)); + t->track.req_retrans_counter, t->track.b_cutoff, t->track.b_wssize_cutoff, t->track.b_desync_cutoff, t->track.hostname, l7proto_str(t->track.l7proto)); }; } diff --git a/nfq/conntrack.h b/nfq/conntrack.h index e37a616..415c533 100644 --- a/nfq/conntrack.h +++ b/nfq/conntrack.h @@ -52,12 +52,23 @@ 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; +#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); + typedef struct { + bool bCheckDone, bCheckResult, bCheckExcluded; // hostlist check result cache + struct desync_profile *dp; // desync profile cache bool dp_search_complete; - bool bCheckDone, bCheckResult, bCheckExcluded; // hostlist check result cache // common state time_t t_start, t_last; @@ -76,12 +87,13 @@ typedef struct bool req_seq_present,req_seq_finalized,req_seq_abandoned; uint32_t req_seq_start,req_seq_end; // sequence interval of the request (to track retransmissions) - uint8_t autottl; + uint8_t incoming_ttl, autottl; bool b_cutoff; // mark for deletion bool b_wssize_cutoff, b_desync_cutoff; t_l7proto l7proto; + bool l7proto_discovered; char *hostname; bool hostname_ah_check; // should perform autohostlist checks diff --git a/nfq/desync.c b/nfq/desync.c index e1181e6..5413a85 100644 --- a/nfq/desync.c +++ b/nfq/desync.c @@ -5,6 +5,7 @@ #include "params.h" #include "helpers.h" #include "hostlist.h" +#include "ipset.h" #include "conntrack.h" #include @@ -145,20 +146,32 @@ enum dpi_desync_mode desync_mode_from_string(const char *s) return DESYNC_INVALID; } -static bool dp_match_l3l4(struct desync_profile *dp, bool ipv6, uint16_t tcp_port, uint16_t udp_port) +static bool dp_match_l3l4(struct desync_profile *dp, uint8_t l3proto, const struct sockaddr *dest) { - return \ - ((!ipv6 && dp->filter_ipv4) || (ipv6 && dp->filter_ipv6)) && - (!tcp_port || pf_in_range(tcp_port,&dp->pf_tcp)) && - (!udp_port || pf_in_range(udp_port,&dp->pf_udp)); + return ((dest->sa_family==AF_INET && dp->filter_ipv4) || (dest->sa_family==AF_INET6 && dp->filter_ipv6)) && + (l3proto==IPPROTO_TCP && pf_in_range(saport(dest), &dp->pf_tcp) || l3proto==IPPROTO_UDP && pf_in_range(saport(dest), &dp->pf_tcp)) && + IpsetCheck(dp, dest->sa_family==AF_INET ? &((struct sockaddr_in*)dest)->sin_addr : NULL, dest->sa_family==AF_INET6 ? &((struct sockaddr_in6*)dest)->sin6_addr : NULL); } +static bool dp_impossible(struct desync_profile *dp, const char *hostname, t_l7proto l7proto) +{ + return !PROFILE_IPSETS_EMPTY(dp) && + ((dp->filter_l7 && !l7_proto_match(l7proto, dp->filter_l7)) || (!*dp->hostlist_auto_filename && !hostname && (dp->hostlist || dp->hostlist_exclude))); +} + static bool dp_match( - struct desync_profile *dp, bool ipv6, uint16_t tcp_port, uint16_t udp_port, const char *hostname, + struct desync_profile *dp, + uint8_t l3proto, const struct sockaddr *dest, const char *hostname, t_l7proto l7proto, bool *bCheckDone, bool *bCheckResult, bool *bExcluded) { if (bCheckDone) *bCheckDone = false; - if (dp_match_l3l4(dp,ipv6,tcp_port,udp_port)) + // impossible case, hard filter + // impossible check avoids relatively slow ipset search + if (!dp_impossible(dp,hostname,l7proto) && dp_match_l3l4(dp,l3proto,dest)) { + // soft filter + if (dp->filter_l7 && !l7_proto_match(l7proto, dp->filter_l7)) + return false; + // autohostlist profile matching l3/l4 filter always win if (*dp->hostlist_auto_filename) return true; @@ -181,15 +194,21 @@ static bool dp_match( return false; } static struct desync_profile *dp_find( - struct desync_profile_list_head *head, bool ipv6, uint16_t tcp_port, uint16_t udp_port, const char *hostname, + struct desync_profile_list_head *head, + uint8_t l3proto, const struct sockaddr *dest, const char *hostname, t_l7proto l7proto, bool *bCheckDone, bool *bCheckResult, bool *bExcluded) { struct desync_profile_list *dpl; - DLOG("desync profile search for hostname='%s' ipv6=%u tcp_port=%u udp_port=%u\n", hostname ? hostname : "", ipv6, tcp_port, udp_port); + if (params.debug) + { + char ip_port[48]; + ntop46_port(dest, ip_port,sizeof(ip_port)); + DLOG("desync profile search for %s target=%s l7proto=%s hostname='%s'\n", proto_name(l3proto), ip_port, l7proto_str(l7proto), hostname ? hostname : ""); + } if (bCheckDone) *bCheckDone = false; LIST_FOREACH(dpl, head, next) { - if (dp_match(&dpl->dp,ipv6,tcp_port,udp_port,hostname,bCheckDone,bCheckResult,bExcluded)) + if (dp_match(&dpl->dp,l3proto,dest,hostname,l7proto,bCheckDone,bCheckResult,bExcluded)) { DLOG("desync profile %d matches\n",dpl->dp.n); return &dpl->dp; @@ -326,7 +345,7 @@ static void auto_hostlist_failed(struct desync_profile *dp, const char *hostname fail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time); if (!fail_counter) { - fprintf(stderr, "HostFailPoolAdd: out of memory\n"); + DLOG_ERR("HostFailPoolAdd: out of memory\n"); return; } } @@ -346,7 +365,7 @@ static void auto_hostlist_failed(struct desync_profile *dp, const char *hostname HOSTLIST_DEBUGLOG_APPEND("%s : profile %d : adding to %s", hostname, dp->n, dp->hostlist_auto_filename); if (!StrPoolAddStr(&dp->hostlist, hostname)) { - fprintf(stderr, "StrPoolAddStr out of memory\n"); + DLOG_ERR("StrPoolAddStr out of memory\n"); return; } if (!append_to_list_file(dp->hostlist_auto_filename, hostname)) @@ -575,6 +594,21 @@ static size_t pos_normalize(size_t split_pos, size_t reasm_offset, size_t len_pa return split_pos; } +static void autottl_discover(t_ctrack *ctrack, bool bIpv6) +{ + if (ctrack && ctrack->incoming_ttl) + { + autottl *attl = bIpv6 ? &ctrack->dp->desync_autottl6 : &ctrack->dp->desync_autottl; + if (AUTOTTL_ENABLED(*attl)) + { + ctrack->autottl = autottl_guess(ctrack->incoming_ttl, attl); + if (ctrack->autottl) + DLOG("autottl: guessed %u\n",ctrack->autottl); + else + DLOG("autottl: could not guess\n"); + } + } +} static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, 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 transport_len, uint8_t *data_payload, size_t len_payload) { @@ -593,10 +627,10 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint size_t pkt1_len, pkt2_len; uint8_t ttl_orig,ttl_fake,flags_orig,scale_factor; uint32_t *timestamps; - t_l7proto l7proto = UNKNOWN; ttl_orig = ip ? ip->ip_ttl : ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_hlim; uint32_t desync_fwmark = fwmark | params.desync_fwmark; + extract_endpoints(ip, ip6hdr, tcphdr, NULL, &src, &dst); if (replay) { @@ -611,10 +645,9 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint DLOG("using cached desync profile %d\n",dp->n); else if (!ctrack_replay->dp_search_complete) { - dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, !!ip6hdr, ntohs(bReverse ? tcphdr->th_sport : tcphdr->th_dport), 0, ctrack_replay->hostname, NULL, NULL, NULL); + dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, ctrack_replay->hostname, ctrack_replay->l7proto, NULL, NULL, NULL); ctrack_replay->dp_search_complete = true; } - if (!dp) { DLOG("matching desync profile not found\n"); @@ -635,7 +668,7 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint DLOG("using cached desync profile %d\n",dp->n); else if (!ctrack || !ctrack->dp_search_complete) { - dp = dp_find(¶ms.desync_profiles, !!ip6hdr, ntohs(bReverse ? tcphdr->th_sport : tcphdr->th_dport), 0, ctrack ? ctrack->hostname : NULL, NULL, NULL, NULL); + dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, ctrack ? ctrack->hostname : NULL, ctrack ? ctrack->l7proto : UNKNOWN, NULL, NULL, NULL); if (ctrack) { ctrack->dp = dp; @@ -661,17 +694,14 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint if (bReverse) { - if (ctrack && !ctrack->autottl && ctrack->pcounter_reply==1) + if (ctrack) { - autottl *attl = ip ? &dp->desync_autottl : &dp->desync_autottl6; - if (AUTOTTL_ENABLED(*attl)) + if (!ctrack->incoming_ttl) { - ctrack->autottl = autottl_guess(ttl_orig, attl); - if (ctrack->autottl) - DLOG("autottl: guessed %u\n",ctrack->autottl); - else - DLOG("autottl: could not guess\n"); + DLOG("incoming TTL %u\n",ttl_orig); + ctrack->incoming_ttl = ttl_orig; } + if (!ctrack->autottl) autottl_discover(ctrack,!!ip6hdr); } // process reply packets for auto hostlist mode @@ -744,10 +774,12 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint flags_orig = *((uint8_t*)tcphdr+13); scale_factor = tcp_find_scale_factor(tcphdr); timestamps = tcp_find_timestamps(tcphdr); - extract_endpoints(ip, ip6hdr, tcphdr, NULL, &src, &dst); if (!replay) { + // start and cutoff limiters + if (!process_desync_interval(dp, ctrack)) return verdict; + if (tcp_syn_segment(tcphdr)) { switch (dp->desync_mode0) @@ -797,8 +829,6 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint return verdict; } - // start and cutoff limiters - if (!process_desync_interval(dp, ctrack)) return verdict; } // !replay if (!(tcphdr->th_flags & TH_SYN) && len_payload) @@ -811,6 +841,7 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint const uint8_t *rdata_payload = data_payload; size_t rlen_payload = len_payload; size_t split_pos; + t_l7proto l7proto = UNKNOWN; if (replay) { @@ -829,7 +860,7 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint { DLOG("packet contains HTTP request\n"); l7proto = HTTP; - if (ctrack && !ctrack->l7proto) ctrack->l7proto = l7proto; + if (ctrack && ctrack->l7proto==UNKNOWN) ctrack->l7proto = l7proto; // we do not reassemble http reasm_orig_cancel(ctrack); @@ -874,7 +905,6 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint reasm_orig_cancel(ctrack); return verdict; } - } if (!ctrack->req_seq_finalized) { @@ -901,7 +931,7 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint } else { - fprintf(stderr, "rawpacket_queue failed !'\n"); + DLOG_ERR("rawpacket_queue failed !\n"); reasm_orig_cancel(ctrack); return verdict; } @@ -929,70 +959,97 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint if (dseq>=0x1000000 && !(dseq & 0x80000000)) ctrack->req_seq_abandoned=true; } - if (bHaveHost) + if (bHaveHost) DLOG("hostname: %s\n",host); + + bool bDiscoveredL7; + if (ctrack_replay) { - bool bCheckDone=false, bCheckResult=false, bCheckExcluded=false; - DLOG("hostname: %s\n",host); + bDiscoveredL7 = !ctrack_replay->l7proto_discovered && ctrack_replay->l7proto!=UNKNOWN; + ctrack_replay->l7proto_discovered=true; + } + else + bDiscoveredL7 = !ctrack_replay && l7proto!=UNKNOWN; + if (bDiscoveredL7) DLOG("discovered l7 protocol\n"); + + bool bDiscoveredHostname = bHaveHost && !(ctrack_replay && ctrack_replay->hostname); + if (bDiscoveredHostname) + { + DLOG("discovered hostname\n"); if (ctrack_replay) { + ctrack_replay->hostname=strdup(host); if (!ctrack_replay->hostname) { - ctrack_replay->hostname=strdup(host); - if (!ctrack_replay->hostname) - { - DLOG_ERR("hostname dup : out of memory"); - reasm_orig_cancel(ctrack); - return verdict; - } - DLOG("we have hostname now. searching desync profile again.\n"); - struct desync_profile *dp_prev = dp; - dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, !!ip6hdr, ntohs(bReverse ? tcphdr->th_sport : tcphdr->th_dport), 0, ctrack_replay->hostname, &ctrack_replay->bCheckDone, &ctrack_replay->bCheckResult, &ctrack_replay->bCheckExcluded); - ctrack_replay->dp_search_complete = true; - if (!dp) - { - reasm_orig_cancel(ctrack); - return verdict; - } - if (dp!=dp_prev) - { - DLOG("desync profile changed by revealed hostname !\n"); - // re-evaluate start/cutoff limiters - if (!replay) - { - maybe_cutoff(ctrack, IPPROTO_TCP); - if (!process_desync_interval(dp, ctrack)) - { - reasm_orig_cancel(ctrack); - return verdict; - } - } - } - } - bCheckDone = ctrack_replay->bCheckDone; - bCheckResult = ctrack_replay->bCheckResult; - bCheckExcluded = ctrack_replay->bCheckExcluded; - } - if (dp->hostlist || dp->hostlist_exclude) - { - if (!bCheckDone) - bCheckResult = HostlistCheck(dp, host, &bCheckExcluded); - if (bCheckResult) - ctrack_stop_retrans_counter(ctrack_replay); - else - { - if (ctrack_replay) - { - ctrack_replay->hostname_ah_check = *dp->hostlist_auto_filename && !bCheckExcluded; - if (!ctrack_replay->hostname_ah_check) - ctrack_stop_retrans_counter(ctrack_replay); - } - DLOG("not applying tampering to this request\n"); + DLOG_ERR("hostname dup : out of memory"); reasm_orig_cancel(ctrack); return verdict; } } } + bool bCheckDone=false, bCheckResult=false, bCheckExcluded=false; + if (bDiscoveredL7 || bDiscoveredHostname) + { + struct desync_profile *dp_prev = dp; + + dp = dp_find(¶ms.desync_profiles, IPPROTO_TCP, (struct sockaddr *)&dst, ctrack_replay ? ctrack_replay->hostname : host, ctrack_replay ? ctrack_replay->l7proto : l7proto, &bCheckDone, &bCheckResult, &bCheckExcluded); + if (ctrack_replay) + { + ctrack_replay->dp = dp; + ctrack_replay->dp_search_complete = true; + ctrack_replay->bCheckDone = bCheckDone; + ctrack_replay->bCheckResult = bCheckResult; + ctrack_replay->bCheckExcluded = bCheckExcluded; + } + if (!dp) + { + reasm_orig_cancel(ctrack); + return verdict; + } + if (dp!=dp_prev) + { + DLOG("desync profile changed by revealed l7 protocol or hostname !\n"); + // rediscover autottl + autottl_discover(ctrack_replay,!!ip6hdr); + // re-evaluate start/cutoff limiters + if (!replay) + { + maybe_cutoff(ctrack, IPPROTO_TCP); + if (!process_desync_interval(dp, ctrack)) + { + reasm_orig_cancel(ctrack); + return verdict; + } + } + } + } + else if (ctrack_replay) + { + bCheckDone = ctrack_replay->bCheckDone; + bCheckResult = ctrack_replay->bCheckResult; + bCheckExcluded = ctrack_replay->bCheckExcluded; + } + + if (bHaveHost && (dp->hostlist || dp->hostlist_exclude)) + { + if (!bCheckDone) + bCheckResult = HostlistCheck(dp, host, &bCheckExcluded); + if (bCheckResult) + ctrack_stop_retrans_counter(ctrack_replay); + else + { + if (ctrack_replay) + { + ctrack_replay->hostname_ah_check = *dp->hostlist_auto_filename && !bCheckExcluded; + if (!ctrack_replay->hostname_ah_check) + ctrack_stop_retrans_counter(ctrack_replay); + } + DLOG("not applying tampering to this request\n"); + reasm_orig_cancel(ctrack); + return verdict; + } + } + // desync profile may have changed after hostname was revealed switch(l7proto) { @@ -1019,24 +1076,28 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint if (l7proto==UNKNOWN) { - if (!dp->desync_any_proto) return verdict; + if (!dp->desync_any_proto) + { + DLOG("not applying tampering to unknown protocol\n"); + return verdict; + } DLOG("applying tampering to unknown protocol\n"); } ttl_fake = (ctrack_replay && ctrack_replay->autottl) ? ctrack_replay->autottl : (ip6hdr ? (dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig) : (dp->desync_ttl ? dp->desync_ttl : ttl_orig)); - if ((l7proto == HTTP) && (dp->hostcase || dp->hostnospace || dp->domcase) && (phost = (uint8_t*)memmem(data_payload, len_payload, "\r\nHost: ", 8))) + if ((l7proto == HTTP) && (dp->hostcase || dp->hostnospace || dp->domcase) && HttpFindHost(&phost,data_payload,len_payload)) { if (dp->hostcase) { DLOG("modifying Host: => %c%c%c%c:\n", dp->hostspell[0], dp->hostspell[1], dp->hostspell[2], dp->hostspell[3]); - memcpy(phost + 2, dp->hostspell, 4); + memcpy(phost, dp->hostspell, 4); verdict=VERDICT_MODIFY; } if (dp->domcase) { DLOG("mixing domain case\n"); - for (p = phost+7; p < (data_payload + len_payload) && *p != '\r' && *p != '\n'; p++) + for (p = phost+5; p < (data_payload + len_payload) && *p != '\r' && *p != '\n'; p++) *p = (((size_t)p) & 1) ? tolower(*p) : toupper(*p); verdict=VERDICT_MODIFY; } @@ -1048,12 +1109,12 @@ static uint8_t dpi_desync_tcp_packet_play(bool replay, size_t reasm_offset, uint DLOG("removing space after Host: and adding it to User-Agent:\n"); if (pua > phost) { - memmove(phost + 7, phost + 8, pua - phost - 8); - phost[pua - phost - 1] = ' '; + memmove(phost + 5, phost + 6, pua - phost - 6); + pua[-1]=' '; } else { - memmove(pua + 1, pua, phost - pua + 7); + memmove(pua + 1, pua, phost - pua + 5); *pua = ' '; } verdict=VERDICT_MODIFY; @@ -1404,6 +1465,9 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint uint8_t ttl_orig,ttl_fake; t_l7proto l7proto = UNKNOWN; + ttl_orig = ip ? ip->ip_ttl : ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_hlim; + extract_endpoints(ip, ip6hdr, NULL, udphdr, &src, &dst); + if (replay) { // in replay mode conntrack_replay is not NULL and ctrack is NULL @@ -1417,7 +1481,7 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint DLOG("using cached desync profile %d\n",dp->n); else if (!ctrack_replay->dp_search_complete) { - dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, !!ip6hdr, 0, ntohs(bReverse ? udphdr->uh_sport : udphdr->uh_dport), ctrack_replay->hostname, NULL, NULL, NULL); + dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, ctrack_replay->hostname, ctrack_replay->l7proto, NULL, NULL, NULL); ctrack_replay->dp_search_complete = true; } if (!dp) @@ -1440,7 +1504,7 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint DLOG("using cached desync profile %d\n",dp->n); else if (!ctrack || !ctrack->dp_search_complete) { - dp = dp_find(¶ms.desync_profiles, !!ip6hdr, 0, ntohs(bReverse ? udphdr->uh_sport : udphdr->uh_dport), ctrack ? ctrack->hostname : NULL, NULL, NULL, NULL); + dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, ctrack ? ctrack->hostname : NULL, ctrack ? ctrack->l7proto : UNKNOWN, NULL, NULL, NULL); if (ctrack) { ctrack->dp = dp; @@ -1458,16 +1522,22 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint //ConntrackPoolDump(¶ms.conntrack); } - if (bReverse) return verdict; // nothing to do. do not waste cpu + if (bReverse && ctrack) + { + if (!ctrack->incoming_ttl) + { + DLOG("incoming TTL %u\n",ttl_orig); + ctrack->incoming_ttl = ttl_orig; + } + if (!ctrack->autottl) autottl_discover(ctrack,!!ip6hdr); + return verdict; // nothing to do. do not waste cpu + } // start and cutoff limiters if (!replay && !process_desync_interval(dp, ctrack)) return verdict; uint32_t desync_fwmark = fwmark | params.desync_fwmark; - ttl_orig = ip ? ip->ip_ttl : ip6hdr->ip6_ctlun.ip6_un1.ip6_un1_hlim; - ttl_fake = ip6hdr ? dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig : dp->desync_ttl ? dp->desync_ttl : ttl_orig; - extract_endpoints(ip, ip6hdr, NULL, udphdr, &src, &dst); - + if (len_payload) { const uint8_t *fake; @@ -1480,7 +1550,7 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint { DLOG("packet contains QUIC initial\n"); l7proto = QUIC; - if (ctrack && !ctrack->l7proto) ctrack->l7proto = l7proto; + if (ctrack && ctrack->l7proto==UNKNOWN) ctrack->l7proto = l7proto; uint8_t clean[16384], *pclean; size_t clean_len; @@ -1542,7 +1612,7 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint } else { - fprintf(stderr, "rawpacket_queue failed !'\n"); + DLOG_ERR("rawpacket_queue failed !\n"); reasm_orig_cancel(ctrack); return verdict; } @@ -1593,80 +1663,116 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint { DLOG("packet contains wireguard handshake initiation\n"); l7proto = WIREGUARD; - if (ctrack && !ctrack->l7proto) ctrack->l7proto = l7proto; + if (ctrack && ctrack->l7proto==UNKNOWN) ctrack->l7proto = l7proto; } else if (IsDhtD1(data_payload,len_payload)) { DLOG("packet contains DHT d1...e\n"); l7proto = DHT; - if (ctrack && !ctrack->l7proto) ctrack->l7proto = l7proto; + if (ctrack && ctrack->l7proto==UNKNOWN) ctrack->l7proto = l7proto; } else { - if (!dp->desync_any_proto) return verdict; + if (!dp->desync_any_proto) + { + DLOG("not applying tampering to unknown protocol\n"); + return verdict; + } DLOG("applying tampering to unknown protocol\n"); } } - if (bHaveHost) + if (bHaveHost) DLOG("hostname: %s\n",host); + + bool bDiscoveredL7; + if (ctrack_replay) { - bool bCheckDone=false, bCheckResult=false, bCheckExcluded=false; - DLOG("hostname: %s\n",host); + bDiscoveredL7 = !ctrack_replay->l7proto_discovered && ctrack_replay->l7proto!=UNKNOWN; + ctrack_replay->l7proto_discovered=true; + } + else + bDiscoveredL7 = !ctrack_replay && l7proto!=UNKNOWN; + if (bDiscoveredL7) DLOG("discovered l7 protocol\n"); + + bool bDiscoveredHostname = bHaveHost && !(ctrack_replay && ctrack_replay->hostname); + if (bDiscoveredHostname) + { + DLOG("discovered hostname\n"); if (ctrack_replay) { + ctrack_replay->hostname=strdup(host); if (!ctrack_replay->hostname) { - ctrack_replay->hostname=strdup(host); - if (!ctrack_replay->hostname) - { - DLOG_ERR("hostname dup : out of memory"); - return verdict; - } - DLOG("we have hostname now. searching desync profile again.\n"); - struct desync_profile *dp_prev = dp; - dp = ctrack_replay->dp = dp_find(¶ms.desync_profiles, !!ip6hdr, 0, ntohs(bReverse ? udphdr->uh_sport : udphdr->uh_dport), ctrack_replay->hostname, &ctrack_replay->bCheckDone, &ctrack_replay->bCheckResult, &ctrack_replay->bCheckExcluded); - ctrack_replay->dp_search_complete = true; - if (!dp) return verdict; - if (dp!=dp_prev) - { - DLOG("desync profile changed by reavealed hostname !\n"); - // re-evaluate start/cutoff limiters - if (!replay) - { - maybe_cutoff(ctrack, IPPROTO_UDP); - if (!process_desync_interval(dp, ctrack)) return verdict; - } - } - } - bCheckDone = ctrack_replay->bCheckDone; - bCheckResult = ctrack_replay->bCheckResult; - bCheckExcluded = ctrack_replay->bCheckExcluded; - } - if (dp->hostlist || dp->hostlist_exclude) - { - bool bCheckExcluded; - if (!bCheckDone) - bCheckResult = HostlistCheck(dp, host, &bCheckExcluded); - if (!bCheckResult) - { - if (ctrack_replay) - { - ctrack_replay->hostname_ah_check = *dp->hostlist_auto_filename && !bCheckExcluded; - if (ctrack_replay->hostname_ah_check) - { - // first request is not retrans - if (ctrack_replay->hostname) - process_retrans_fail(ctrack_replay, IPPROTO_UDP); - else - ctrack_replay->hostname=strdup(host); - } - } - DLOG("not applying tampering to this request\n"); + DLOG_ERR("hostname dup : out of memory"); return verdict; } } } + bool bCheckDone=false, bCheckResult=false, bCheckExcluded=false; + if (bDiscoveredL7 || bDiscoveredHostname) + { + struct desync_profile *dp_prev = dp; + + dp = dp_find(¶ms.desync_profiles, IPPROTO_UDP, (struct sockaddr *)&dst, ctrack_replay ? ctrack_replay->hostname : host, ctrack_replay ? ctrack_replay->l7proto : l7proto, &bCheckDone, &bCheckResult, &bCheckExcluded); + if (ctrack_replay) + { + ctrack_replay->dp = dp; + ctrack_replay->dp_search_complete = true; + ctrack_replay->bCheckDone = bCheckDone; + ctrack_replay->bCheckResult = bCheckResult; + ctrack_replay->bCheckExcluded = bCheckExcluded; + } + if (!dp) + { + reasm_orig_cancel(ctrack); + return verdict; + } + if (dp!=dp_prev) + { + DLOG("desync profile changed by revealed l7 protocol or hostname !\n"); + // rediscover autottl + autottl_discover(ctrack_replay,!!ip6hdr); + // re-evaluate start/cutoff limiters + if (!replay) + { + maybe_cutoff(ctrack, IPPROTO_UDP); + if (!process_desync_interval(dp, ctrack)) return verdict; + } + } + } + else if (ctrack_replay) + { + bCheckDone = ctrack_replay->bCheckDone; + bCheckResult = ctrack_replay->bCheckResult; + bCheckExcluded = ctrack_replay->bCheckExcluded; + } + + if (bHaveHost && (dp->hostlist || dp->hostlist_exclude)) + { + if (!bCheckDone) + bCheckResult = HostlistCheck(dp, host, &bCheckExcluded); + if (bCheckResult) + ctrack_stop_retrans_counter(ctrack_replay); + else + { + if (ctrack_replay) + { + ctrack_replay->hostname_ah_check = *dp->hostlist_auto_filename && !bCheckExcluded; + if (ctrack_replay->hostname_ah_check) + { + // first request is not retrans + if (ctrack_replay->hostname) + process_retrans_fail(ctrack_replay, IPPROTO_UDP); + else + ctrack_replay->hostname=strdup(host); + } + } + DLOG("not applying tampering to this request\n"); + return verdict; + } + } + // desync profile may have changed after hostname was revealed switch(l7proto) { @@ -1687,7 +1793,8 @@ static uint8_t dpi_desync_udp_packet_play(bool replay, size_t reasm_offset, uint fake_size = dp->fake_unknown_udp_size; break; } - ttl_fake = ip6hdr ? dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig : dp->desync_ttl ? dp->desync_ttl : ttl_orig; + + ttl_fake = (ctrack_replay && ctrack_replay->autottl) ? ctrack_replay->autottl : (ip6hdr ? (dp->desync_ttl6 ? dp->desync_ttl6 : ttl_orig) : (dp->desync_ttl ? dp->desync_ttl : ttl_orig)); enum dpi_desync_mode desync_mode = dp->desync_mode; uint32_t fooling_orig = FOOL_NONE; diff --git a/nfq/helpers.c b/nfq/helpers.c index 01d0558..9d7bea9 100644 --- a/nfq/helpers.c +++ b/nfq/helpers.c @@ -11,6 +11,12 @@ #include "params.h" +void rtrim(char *s) +{ + if (s) + for (char *p = s + strlen(s) - 1; p >= s && (*p == '\n' || *p == '\r'); p--) *p = '\0'; +} + void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit) { size_t k; @@ -181,6 +187,11 @@ bool pton6_port(const char *s, struct sockaddr_in6 *sa) return true; } +uint16_t saport(const struct sockaddr *sa) +{ + return htons(sa->sa_family==AF_INET ? ((struct sockaddr_in*)sa)->sin_port : + sa->sa_family==AF_INET6 ? ((struct sockaddr_in6*)sa)->sin6_port : 0); +} void dbgprint_socket_buffers(int fd) { @@ -385,3 +396,99 @@ bool cd_to_exe_dir(const char *argv0) } return bOK; } + + +static void mask_from_preflen6_make(uint8_t plen, struct in6_addr *a) +{ + if (plen >= 128) + memset(a->s6_addr,0xFF,16); + else + { + uint8_t n = plen >> 3; + memset(a->s6_addr,0xFF,n); + memset(a->s6_addr+n,0x00,16-n); + a->s6_addr[n] = (uint8_t)(0xFF00 >> (plen & 7)); + } +} +struct in6_addr ip6_mask[129]; +void mask_from_preflen6_prepare(void) +{ + for (int plen=0;plen<=128;plen++) mask_from_preflen6_make(plen, ip6_mask+plen); +} + +#if defined(__GNUC__) && !defined(__llvm__) +__attribute__((optimize ("no-strict-aliasing"))) +#endif +void ip6_and(const struct in6_addr * restrict a, const struct in6_addr * restrict b, struct in6_addr * restrict result) +{ + // int128 requires 16-bit alignment. in struct sockaddr_in6.sin6_addr is 8-byte aligned. + // it causes segfault on x64 arch with lastest compiler. it can cause misalign slowdown on other archs + // use 64-bit AND + ((uint64_t*)result->s6_addr)[0] = ((uint64_t*)a->s6_addr)[0] & ((uint64_t*)b->s6_addr)[0]; + ((uint64_t*)result->s6_addr)[1] = ((uint64_t*)a->s6_addr)[1] & ((uint64_t*)b->s6_addr)[1]; +} + +void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr) +{ + char s_ip[16]; + *s_ip=0; + inet_ntop(AF_INET, &cidr->addr, s_ip, sizeof(s_ip)); + snprintf(s,s_len,cidr->preflen<32 ? "%s/%u" : "%s", s_ip, cidr->preflen); +} +void print_cidr4(const struct cidr4 *cidr) +{ + char s[19]; + str_cidr4(s,sizeof(s),cidr); + printf("%s",s); +} +void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr) +{ + char s_ip[40]; + *s_ip=0; + inet_ntop(AF_INET6, &cidr->addr, s_ip, sizeof(s_ip)); + snprintf(s,s_len,cidr->preflen<128 ? "%s/%u" : "%s", s_ip, cidr->preflen); +} +void print_cidr6(const struct cidr6 *cidr) +{ + char s[44]; + str_cidr6(s,sizeof(s),cidr); + printf("%s",s); +} +bool parse_cidr4(char *s, struct cidr4 *cidr) +{ + char *p,d; + bool b; + unsigned int plen; + + if ((p = strchr(s, '/'))) + { + if (sscanf(p + 1, "%u", &plen)!=1 || plen>32) + return false; + cidr->preflen = (uint8_t)plen; + d=*p; *p=0; // backup char + } + else + cidr->preflen = 32; + b = (inet_pton(AF_INET, s, &cidr->addr)==1); + if (p) *p=d; // restore char + return b; +} +bool parse_cidr6(char *s, struct cidr6 *cidr) +{ + char *p,d; + bool b; + unsigned int plen; + + if ((p = strchr(s, '/'))) + { + if (sscanf(p + 1, "%u", &plen)!=1 || plen>128) + return false; + cidr->preflen = (uint8_t)plen; + d=*p; *p=0; // backup char + } + else + cidr->preflen = 128; + b = (inet_pton(AF_INET6, s, &cidr->addr)==1); + if (p) *p=d; // restore char + return b; +} diff --git a/nfq/helpers.h b/nfq/helpers.h index 15222d4..2bdc6f5 100644 --- a/nfq/helpers.h +++ b/nfq/helpers.h @@ -9,6 +9,16 @@ #include #include +// this saves memory. sockaddr_storage is larger than required. it can be 128 bytes. sockaddr_in6 is 28 bytes. +typedef union +{ + struct sockaddr_in sa4; // size 16 + struct sockaddr_in6 sa6; // size 28 + char _align[32]; // force 16-byte alignment for ip6_and int128 ops +} sockaddr_in46; + +void rtrim(char *s); + void hexdump_limited_dlog(const uint8_t *data, size_t size, size_t limit); char *strncasestr(const char *s,const char *find, size_t slen); bool load_file(const char *filename,void *buffer,size_t *buffer_size); @@ -22,6 +32,8 @@ void ntop46_port(const struct sockaddr *sa, char *str, size_t len); bool pton4_port(const char *s, struct sockaddr_in *sa); bool pton6_port(const char *s, struct sockaddr_in6 *sa); +uint16_t saport(const struct sockaddr *sa); + bool seq_within(uint32_t s, uint32_t s1, uint32_t s2); void dbgprint_socket_buffers(int fd); @@ -64,3 +76,33 @@ void fill_random_az(uint8_t *p,size_t sz); void fill_random_az09(uint8_t *p,size_t sz); bool cd_to_exe_dir(const char *argv0); + + +struct cidr4 +{ + struct in_addr addr; + uint8_t preflen; +}; +struct cidr6 +{ + struct in6_addr addr; + uint8_t preflen; +}; +void str_cidr4(char *s, size_t s_len, const struct cidr4 *cidr); +void print_cidr4(const struct cidr4 *cidr); +void str_cidr6(char *s, size_t s_len, const struct cidr6 *cidr); +void print_cidr6(const struct cidr6 *cidr); +bool parse_cidr4(char *s, struct cidr4 *cidr); +bool parse_cidr6(char *s, struct cidr6 *cidr); + +static inline uint32_t mask_from_preflen(uint32_t preflen) +{ + return preflen ? preflen<32 ? ~((1 << (32-preflen)) - 1) : 0xFFFFFFFF : 0; +} +void ip6_and(const struct in6_addr * restrict a, const struct in6_addr * restrict b, struct in6_addr * restrict result); +extern struct in6_addr ip6_mask[129]; +void mask_from_preflen6_prepare(void); +static inline const struct in6_addr *mask_from_preflen6(uint8_t preflen) +{ + return ip6_mask+preflen; +} diff --git a/nfq/hostlist.c b/nfq/hostlist.c index abb415b..28dad2d 100644 --- a/nfq/hostlist.c +++ b/nfq/hostlist.c @@ -55,7 +55,7 @@ bool AppendHostList(strpool **hostlist, const char *filename) if (r==Z_OK) { DLOG_CONDUP("zlib compression detected. uncompressed size : %zu\n", zsize); - + p = zbuf; e = zbuf + zsize; while(pn); + DLOG("* hostlist check for profile %d\n",dp->n); if (*dp->hostlist_auto_filename) { time_t t = file_mod_time(dp->hostlist_auto_filename); diff --git a/nfq/ipset.c b/nfq/ipset.c new file mode 100644 index 0000000..50e6f9e --- /dev/null +++ b/nfq/ipset.c @@ -0,0 +1,195 @@ +#include +#include "ipset.h" +#include "gzip.h" +#include "helpers.h" + +// inplace tolower() and add to pool +static bool addpool(ipset *ips, char **s, const char *end, int *ct) +{ + char *p, cidr[128]; + size_t l; + struct cidr4 c4; + struct cidr6 c6; + + // advance until eol + for (p=*s; p=sizeof(cidr)) l=sizeof(cidr)-1; + memcpy(cidr,*s,l); + cidr[l]=0; + rtrim(cidr); + + if (parse_cidr4(cidr,&c4)) + { + if (!ipset4AddCidr(&ips->ips4, &c4)) + { + ipsetDestroy(ips); + return false; + } + (*ct)++; + } + else if (parse_cidr6(cidr,&c6)) + { + if (!ipset6AddCidr(&ips->ips6, &c6)) + { + ipsetDestroy(ips); + return false; + } + (*ct)++; + } + else + DLOG_ERR("bad ip or subnet : %s\n",cidr); + } + + // advance to the next line + for (; pstr)) return false; + } + return true; +} + +bool LoadIncludeIpsets() +{ + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + if (!LoadIpsets(&dpl->dp.ips, &dpl->dp.ipset_files)) + return false; + return true; +} +bool LoadExcludeIpsets() +{ + struct desync_profile_list *dpl; + LIST_FOREACH(dpl, ¶ms.desync_profiles, next) + if (!LoadIpsets(&dpl->dp.ips_exclude, &dpl->dp.ipset_exclude_files)) + return false; + return true; +} + +bool SearchIpset(const ipset *ips, const struct in_addr *ipv4, const struct in6_addr *ipv6) +{ + char s_ip[40]; + bool bInSet=false; + + if (!!ipv4 != !!ipv6) + { + *s_ip=0; + if (ipv4) + { + if (params.debug) inet_ntop(AF_INET, ipv4, s_ip, sizeof(s_ip)); + if (ips->ips4) bInSet = ipset4Check(ips->ips4, ipv4, 32); + } + if (ipv6) + { + if (params.debug) inet_ntop(AF_INET6, ipv6, s_ip, sizeof(s_ip)); + if (ips->ips6) bInSet = ipset6Check(ips->ips6, ipv6, 128); + } + DLOG("ipset check for %s : %s\n", s_ip, bInSet ? "positive" : "negative"); + } + else + // ipv4 and ipv6 are both empty or non-empty + DLOG("ipset check error !!!!!!!! ipv4=%p ipv6=%p\n",ipv4,ipv6); + return bInSet; +} + +static bool IpsetCheck_(const ipset *ips, const ipset *ips_exclude, const struct in_addr *ipv4, const struct in6_addr *ipv6) +{ + if (!IPSET_EMPTY(ips_exclude)) + { + DLOG("exclude "); + if (SearchIpset(ips_exclude, ipv4, ipv6)) + return false; + } + if (!IPSET_EMPTY(ips)) + { + DLOG("include "); + return SearchIpset(ips, ipv4, ipv6); + } + return true; +} + +bool IpsetCheck(struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6) +{ + if (!PROFILE_IPSETS_EMPTY(dp)) DLOG("* ipset check for profile %d\n",dp->n); + return IpsetCheck_(&dp->ips,&dp->ips_exclude,ipv4,ipv6); +} diff --git a/nfq/ipset.h b/nfq/ipset.h new file mode 100644 index 0000000..ea9d1b8 --- /dev/null +++ b/nfq/ipset.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include "params.h" +#include "pools.h" + +bool LoadIncludeIpsets(); +bool LoadExcludeIpsets(); +bool SearchIpset(const ipset *ips, const struct in_addr *ipv4, const struct in6_addr *ipv6); +bool IpsetCheck(struct desync_profile *dp, const struct in_addr *ipv4, const struct in6_addr *ipv6); diff --git a/nfq/nfqws.c b/nfq/nfqws.c index 7fea2b3..3bcab69 100644 --- a/nfq/nfqws.c +++ b/nfq/nfqws.c @@ -8,6 +8,7 @@ #include "params.h" #include "protocol.h" #include "hostlist.h" +#include "ipset.h" #include "gzip.h" #include "pools.h" @@ -54,7 +55,7 @@ static bool bHup = false; static void onhup(int sig) { printf("HUP received !\n"); - printf("Will reload hostlist on next request (if any)\n"); + printf("Will reload hostlists and ipsets on next request (if any)\n"); bHup = true; } // should be called in normal execution @@ -62,7 +63,7 @@ static void dohup(void) { if (bHup) { - if (!LoadIncludeHostLists() || !LoadExcludeHostLists()) + if (!LoadIncludeHostLists() || !LoadExcludeHostLists() || !LoadIncludeIpsets() || !LoadExcludeIpsets()) { // what will we do without hostlist ?? sure, gonna die exit(1); @@ -621,7 +622,7 @@ static void load_file_or_exit(const char *filename, void *buf, size_t *size) } } -bool parse_autottl(const char *s, autottl *t) +static bool parse_autottl(const char *s, autottl *t) { unsigned int delta,min,max; AUTOTTL_SET_DEFAULT(*t); @@ -647,6 +648,42 @@ bool parse_autottl(const char *s, autottl *t) return true; } +static bool parse_l7_list(char *opt, uint32_t *l7) +{ + char *e,*p,c; + + for (p=opt,*l7=0 ; p ; ) + { + if ((e = strchr(p,','))) + { + c=*e; + *e=0; + } + + if (!strcmp(p,"http")) + *l7 |= L7_PROTO_HTTP; + else if (!strcmp(p,"tls")) + *l7 |= L7_PROTO_TLS; + else if (!strcmp(p,"quic")) + *l7 |= L7_PROTO_QUIC; + else if (!strcmp(p,"wireguard")) + *l7 |= L7_PROTO_WIREGUARD; + else if (!strcmp(p,"dht")) + *l7 |= L7_PROTO_DHT; + else if (!strcmp(p,"unknown")) + *l7 |= L7_PROTO_UNKNOWN; + else return false; + + if (e) + { + *e++=c; + } + p = e; + } + return true; +} + + static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) { char *e,*p,c; @@ -673,6 +710,7 @@ static bool wf_make_l3(char *opt, bool *ipv4, bool *ipv6) } return true; } + #ifdef __CYGWIN__ static bool wf_make_pf(char *opt, const char *l4, const char *portname, char *buf, size_t len) { @@ -831,6 +869,9 @@ static void exithelp(void) " --filter-l3=ipv4|ipv6\t\t\t\t; L3 protocol filter. multiple comma separated values allowed.\n" " --filter-tcp=[~]port1[-port2]\t\t\t; TCP port filter. ~ means negation. setting tcp and not setting udp filter denies udp.\n" " --filter-udp=[~]port1[-port2]\t\t\t; UDP port filter. ~ means negation. setting udp and not setting tcp filter denies tcp.\n" + " --filter-l7=[http|tls|quic|wireguard|dht|unknown] ; L6-L7 protocol filter. multiple comma separated values allowed.\n" + " --ipset=\t\t\t\t; ipset include filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" + " --ipset-exclude=\t\t\t; ipset exclude filter (one ip/CIDR per line, ipv4 and ipv6 accepted, gzip supported, multiple ipsets allowed)\n" "\nHOSTLIST FILTER:\n" " --hostlist=\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=\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" @@ -945,6 +986,7 @@ int main(int argc, char **argv) #endif srandom(time(NULL)); + mask_from_preflen6_prepare(); memset(¶ms, 0, sizeof(params)); *pidfile = 0; @@ -1056,19 +1098,22 @@ int main(int argc, char **argv) {"filter-l3",required_argument,0,0}, // optidx=53 {"filter-tcp",required_argument,0,0}, // optidx=54 {"filter-udp",required_argument,0,0}, // optidx=55 + {"filter-l7",required_argument,0,0}, // optidx=56 + {"ipset",required_argument,0,0}, // optidx=57 + {"ipset-exclude",required_argument,0,0},// optidx=58 #ifdef __linux__ - {"bind-fix4",no_argument,0,0}, // optidx=56 - {"bind-fix6",no_argument,0,0}, // optidx=57 + {"bind-fix4",no_argument,0,0}, // optidx=59 + {"bind-fix6",no_argument,0,0}, // optidx=60 #elif defined(__CYGWIN__) - {"wf-iface",required_argument,0,0}, // optidx=56 - {"wf-l3",required_argument,0,0}, // optidx=57 - {"wf-tcp",required_argument,0,0}, // optidx=58 - {"wf-udp",required_argument,0,0}, // optidx=59 - {"wf-raw",required_argument,0,0}, // optidx=60 - {"wf-save",required_argument,0,0}, // optidx=61 - {"ssid-filter",required_argument,0,0}, // optidx=62 - {"nlm-filter",required_argument,0,0}, // optidx=63 - {"nlm-list",optional_argument,0,0}, // optidx=64 + {"wf-iface",required_argument,0,0}, // optidx=59 + {"wf-l3",required_argument,0,0}, // optidx=60 + {"wf-tcp",required_argument,0,0}, // optidx=61 + {"wf-udp",required_argument,0,0}, // optidx=62 + {"wf-raw",required_argument,0,0}, // optidx=63 + {"wf-save",required_argument,0,0}, // optidx=64 + {"ssid-filter",required_argument,0,0}, // optidx=65 + {"nlm-filter",required_argument,0,0}, // optidx=66 + {"nlm-list",optional_argument,0,0}, // optidx=67 #endif {NULL,0,NULL,0} }; @@ -1596,30 +1641,53 @@ int main(int argc, char **argv) // deny tcp if not set if (pf_is_empty(&dp->pf_tcp)) dp->pf_tcp.neg=true; break; + case 56: /* filter-l7 */ + if (!parse_l7_list(optarg,&dp->filter_l7)) + { + DLOG_ERR("Invalid l7 filter : %s\n",optarg); + exit_clean(1); + } + break; + case 57: /* ipset */ + if (!strlist_add(&dp->ipset_files, optarg)) + { + DLOG_ERR("strlist_add failed\n"); + exit_clean(1); + } + break; + case 58: /* ipset-exclude */ + if (!strlist_add(&dp->ipset_exclude_files, optarg)) + { + DLOG_ERR("strlist_add failed\n"); + exit_clean(1); + } + break; + + #ifdef __linux__ - case 56: /* bind-fix4 */ + case 59: /* bind-fix4 */ params.bind_fix4 = true; break; - case 57: /* bind-fix6 */ + case 60: /* bind-fix6 */ params.bind_fix6 = true; break; #elif defined(__CYGWIN__) - case 56: /* wf-iface */ + case 59: /* wf-iface */ if (!sscanf(optarg,"%u.%u",&IfIdx,&SubIfIdx)) { DLOG_ERR("bad value for --wf-iface\n"); exit_clean(1); } break; - case 57: /* wf-l3 */ + case 60: /* wf-l3 */ if (!wf_make_l3(optarg,&wf_ipv4,&wf_ipv6)) { DLOG_ERR("bad value for --wf-l3\n"); exit_clean(1); } break; - case 58: /* wf-tcp */ + case 61: /* wf-tcp */ hash_wf_tcp=hash_jen(optarg,strlen(optarg)); if (!wf_make_pf(optarg,"tcp","SrcPort",wf_pf_tcp_src,sizeof(wf_pf_tcp_src)) || !wf_make_pf(optarg,"tcp","DstPort",wf_pf_tcp_dst,sizeof(wf_pf_tcp_dst))) @@ -1628,7 +1696,7 @@ int main(int argc, char **argv) exit_clean(1); } break; - case 59: /* wf-udp */ + case 62: /* wf-udp */ hash_wf_udp=hash_jen(optarg,strlen(optarg)); if (!wf_make_pf(optarg,"udp","SrcPort",wf_pf_udp_src,sizeof(wf_pf_udp_src)) || !wf_make_pf(optarg,"udp","DstPort",wf_pf_udp_dst,sizeof(wf_pf_udp_dst))) @@ -1637,7 +1705,7 @@ int main(int argc, char **argv) exit_clean(1); } break; - case 60: /* wf-raw */ + case 63: /* wf-raw */ hash_wf_raw=hash_jen(optarg,strlen(optarg)); if (optarg[0]=='@') { @@ -1651,11 +1719,11 @@ int main(int argc, char **argv) windivert_filter[sizeof(windivert_filter) - 1] = '\0'; } break; - case 61: /* wf-save */ + case 64: /* wf-save */ strncpy(wf_save_file, optarg, sizeof(wf_save_file)); wf_save_file[sizeof(wf_save_file) - 1] = '\0'; break; - case 62: /* ssid-filter */ + case 65: /* ssid-filter */ hash_ssid_filter=hash_jen(optarg,strlen(optarg)); { char *e,*p = optarg; @@ -1673,7 +1741,7 @@ int main(int argc, char **argv) } } break; - case 63: /* nlm-filter */ + case 66: /* nlm-filter */ hash_nlm_filter=hash_jen(optarg,strlen(optarg)); { char *e,*p = optarg; @@ -1691,7 +1759,7 @@ int main(int argc, char **argv) } } break; - case 64: /* nlm-list */ + case 67: /* nlm-list */ if (!nlm_list(optarg && !strcmp(optarg,"all"))) { DLOG_ERR("could not get list of NLM networks\n"); @@ -1776,9 +1844,9 @@ int main(int argc, char **argv) if (dp->desync_ttl6 == 0xFF) dp->desync_ttl6=dp->desync_ttl; if (!AUTOTTL_ENABLED(dp->desync_autottl6)) dp->desync_autottl6 = dp->desync_autottl; if (AUTOTTL_ENABLED(dp->desync_autottl)) - DLOG("[profile %d] autottl ipv4 %u:%u-%u\n",v,dp->desync_autottl.delta,dp->desync_autottl.min,dp->desync_autottl.max); + DLOG("[profile %d] autottl ipv4 %u:%u-%u\n",dp->n,dp->desync_autottl.delta,dp->desync_autottl.min,dp->desync_autottl.max); if (AUTOTTL_ENABLED(dp->desync_autottl6)) - DLOG("[profile %d] autottl ipv6 %u:%u-%u\n",v,dp->desync_autottl6.delta,dp->desync_autottl6.min,dp->desync_autottl6.max); + DLOG("[profile %d] autottl ipv6 %u:%u-%u\n",dp->n,dp->desync_autottl6.delta,dp->desync_autottl6.min,dp->desync_autottl6.max); if (dp->desync_split_tls==tlspos_none && dp->desync_split_pos) dp->desync_split_tls=tlspos_pos; if (dp->desync_split_http_req==httpreqpos_none && dp->desync_split_pos) dp->desync_split_http_req=httpreqpos_pos; } @@ -1793,6 +1861,16 @@ int main(int argc, char **argv) DLOG_ERR("Exclude hostlists load failed\n"); exit_clean(1); } + if (!LoadIncludeIpsets()) + { + DLOG_ERR("Include ipset load failed\n"); + exit_clean(1); + } + if (!LoadExcludeIpsets()) + { + DLOG_ERR("Exclude ipset load failed\n"); + exit_clean(1); + } if (daemon) daemonize(); diff --git a/nfq/params.c b/nfq/params.c index e6ad407..be8e5ff 100644 --- a/nfq/params.c +++ b/nfq/params.c @@ -207,8 +207,12 @@ static void dp_entry_destroy(struct desync_profile_list *entry) { strlist_destroy(&entry->dp.hostlist_files); strlist_destroy(&entry->dp.hostlist_exclude_files); + strlist_destroy(&entry->dp.ipset_files); + strlist_destroy(&entry->dp.ipset_exclude_files); StrPoolDestroy(&entry->dp.hostlist_exclude); StrPoolDestroy(&entry->dp.hostlist); + ipsetDestroy(&entry->dp.ips); + ipsetDestroy(&entry->dp.ips_exclude); HostFailPoolDestroy(&entry->dp.hostlist_auto_fail_counters); free(entry); } diff --git a/nfq/params.h b/nfq/params.h index b9dd395..f82c2df 100644 --- a/nfq/params.h +++ b/nfq/params.h @@ -66,6 +66,10 @@ struct desync_profile bool filter_ipv4,filter_ipv6; port_filter pf_tcp,pf_udp; + uint32_t filter_l7; // L7_PROTO_* bits + ipset ips,ips_exclude; + struct str_list_head ipset_files, ipset_exclude_files; + strpool *hostlist, *hostlist_exclude; struct str_list_head hostlist_files, hostlist_exclude_files; char hostlist_auto_filename[PATH_MAX]; @@ -74,6 +78,8 @@ struct desync_profile hostfail_pool *hostlist_auto_fail_counters; }; +#define PROFILE_IPSETS_EMPTY(dp) (IPSET_EMPTY(&dp->ips) && IPSET_EMPTY(&dp->ips_exclude)) + struct desync_profile_list { struct desync_profile dp; LIST_ENTRY(desync_profile_list) next; diff --git a/nfq/pools.c b/nfq/pools.c index 785b04d..dd1fdd1 100644 --- a/nfq/pools.c +++ b/nfq/pools.c @@ -151,3 +151,127 @@ void strlist_destroy(struct str_list_head *head) strlist_entry_destroy(entry); } } + + + +void ipset4Destroy(ipset4 **ipset) +{ + ipset4 *elem, *tmp; + HASH_ITER(hh, *ipset, elem, tmp) + { + HASH_DEL(*ipset, elem); + free(elem); + } +} +bool ipset4Check(ipset4 *ipset, const struct in_addr *a, uint8_t preflen) +{ + uint32_t ip = ntohl(a->s_addr); + struct cidr4 cidr; + ipset4 *ips_found; + + // zero alignment bytes + memset(&cidr,0,sizeof(cidr)); + cidr.preflen = preflen+1; + do + { + cidr.preflen--; + cidr.addr.s_addr = htonl(ip & mask_from_preflen(cidr.preflen)); + HASH_FIND(hh, ipset, &cidr, sizeof(cidr), ips_found); + if (ips_found) return true; + } while(cidr.preflen); + + return false; +} +bool ipset4Add(ipset4 **ipset, const struct in_addr *a, uint8_t preflen) +{ + if (preflen>32) return false; + + // avoid dups + if (ipset4Check(*ipset, a, preflen)) return true; // already included + + struct ipset4 *entry = calloc(1,sizeof(ipset4)); + if (!entry) return false; + + entry->cidr.addr.s_addr = htonl(ntohl(a->s_addr) & mask_from_preflen(preflen)); + entry->cidr.preflen = preflen; + oom = false; + HASH_ADD(hh, *ipset, cidr, sizeof(entry->cidr), entry); + if (oom) { free(entry); return false; } + + return true; +} +void ipset4Print(ipset4 *ipset) +{ + ipset4 *ips, *tmp; + HASH_ITER(hh, ipset , ips, tmp) + { + print_cidr4(&ips->cidr); + printf("\n"); + } +} + +void ipset6Destroy(ipset6 **ipset) +{ + ipset6 *elem, *tmp; + HASH_ITER(hh, *ipset, elem, tmp) + { + HASH_DEL(*ipset, elem); + free(elem); + } +} +bool ipset6Check(ipset6 *ipset, const struct in6_addr *a, uint8_t preflen) +{ + struct cidr6 cidr; + ipset6 *ips_found; + + // zero alignment bytes + memset(&cidr,0,sizeof(cidr)); + cidr.preflen = preflen+1; + do + { + cidr.preflen--; + ip6_and(a, mask_from_preflen6(cidr.preflen), &cidr.addr); + HASH_FIND(hh, ipset, &cidr, sizeof(cidr), ips_found); + if (ips_found) return true; + } while(cidr.preflen); + + return false; +} +bool ipset6Add(ipset6 **ipset, const struct in6_addr *a, uint8_t preflen) +{ + if (preflen>128) return false; + + // avoid dups + if (ipset6Check(*ipset, a, preflen)) return true; // already included + + struct ipset6 *entry = calloc(1,sizeof(ipset6)); + if (!entry) return false; + + ip6_and(a, mask_from_preflen6(preflen), &entry->cidr.addr); + entry->cidr.preflen = preflen; + oom = false; + HASH_ADD(hh, *ipset, cidr, sizeof(entry->cidr), entry); + if (oom) { free(entry); return false; } + + return true; +} +void ipset6Print(ipset6 *ipset) +{ + ipset6 *ips, *tmp; + HASH_ITER(hh, ipset , ips, tmp) + { + print_cidr6(&ips->cidr); + printf("\n"); + } +} + +void ipsetDestroy(ipset *ipset) +{ + ipset4Destroy(&ipset->ips4); + ipset6Destroy(&ipset->ips6); +} +void ipsetPrint(ipset *ipset) +{ + ipset4Print(ipset->ips4); + ipset6Print(ipset->ips6); +} diff --git a/nfq/pools.h b/nfq/pools.h index 154d541..706783d 100644 --- a/nfq/pools.h +++ b/nfq/pools.h @@ -5,6 +5,8 @@ #include #include +#include "helpers.h" + //#define HASH_BLOOM 20 #define HASH_NONFATAL_OOM 1 #define HASH_FUNCTION HASH_BER @@ -43,3 +45,41 @@ void HostFailPoolDump(hostfail_pool *p); bool strlist_add(struct str_list_head *head, const char *filename); void strlist_destroy(struct str_list_head *head); + + +typedef struct ipset4 { + struct cidr4 cidr; /* key */ + UT_hash_handle hh; /* makes this structure hashable */ +} ipset4; +typedef struct ipset6 { + struct cidr6 cidr; /* key */ + UT_hash_handle hh; /* makes this structure hashable */ +} ipset6; +// combined ipset ipv4 and ipv6 +typedef struct ipset { + ipset4 *ips4; + ipset6 *ips6; +} ipset; + +#define IPSET_EMPTY(ips) (!(ips)->ips4 && !(ips)->ips6) + +void ipset4Destroy(ipset4 **ipset); +bool ipset4Add(ipset4 **ipset, const struct in_addr *a, uint8_t preflen); +static inline bool ipset4AddCidr(ipset4 **ipset, const struct cidr4 *cidr) +{ + return ipset4Add(ipset,&cidr->addr,cidr->preflen); +} +bool ipset4Check(ipset4 *ipset, const struct in_addr *a, uint8_t preflen); +void ipset4Print(ipset4 *ipset); + +void ipset6Destroy(ipset6 **ipset); +bool ipset6Add(ipset6 **ipset, const struct in6_addr *a, uint8_t preflen); +static inline bool ipset6AddCidr(ipset6 **ipset, const struct cidr6 *cidr) +{ + return ipset6Add(ipset,&cidr->addr,cidr->preflen); +} +bool ipset6Check(ipset6 *ipset, const struct in6_addr *a, uint8_t preflen); +void ipset6Print(ipset6 *ipset); + +void ipsetDestroy(ipset *ipset); +void ipsetPrint(ipset *ipset);