mirror of
https://github.com/bol-van/zapret2.git
synced 2026-03-14 06:13:09 +00:00
2180 lines
70 KiB
C
2180 lines
70 KiB
C
#define _GNU_SOURCE
|
|
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "desync.h"
|
|
#include "protocol.h"
|
|
#include "params.h"
|
|
#include "helpers.h"
|
|
#include "hostlist.h"
|
|
#include "ipset.h"
|
|
#include "conntrack.h"
|
|
#include "lua.h"
|
|
|
|
#define PKTDATA_MAXDUMP 32
|
|
#define IP_MAXDUMP 80
|
|
|
|
#define TCP_MAX_REASM 16384
|
|
#define UDP_MAX_REASM 16384
|
|
|
|
typedef struct
|
|
{
|
|
t_l7payload l7p;
|
|
t_l7proto l7;
|
|
bool(*check)(const uint8_t*, size_t);
|
|
bool l7match;
|
|
} t_protocol_probe;
|
|
|
|
static void protocol_probe(t_protocol_probe *probe, int probe_count, const uint8_t *data_payload, size_t len_payload, t_ctrack *ctrack, t_l7proto *l7proto, t_l7payload *l7payload)
|
|
{
|
|
for (int i = 0; i < probe_count; i++)
|
|
{
|
|
if (!l7_payload_match(probe[i].l7p, params.payload_disable) && (!probe[i].l7match || *l7proto==probe[i].l7) && probe[i].check(data_payload, len_payload))
|
|
{
|
|
*l7payload = probe[i].l7p;
|
|
if (*l7proto == L7_UNKNOWN)
|
|
{
|
|
*l7proto = probe[i].l7;
|
|
if (ctrack && ctrack->l7proto == L7_UNKNOWN) ctrack->l7proto = *l7proto;
|
|
}
|
|
DLOG("packet contains %s payload\n", l7payload_str(*l7payload));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void TLSDebugHandshake(const uint8_t *tls, size_t sz)
|
|
{
|
|
if (!params.debug) return;
|
|
|
|
if (sz < 6) return;
|
|
|
|
const uint8_t *ext;
|
|
size_t len, len2;
|
|
bool bServerHello = IsTLSHandshakeServerHello(tls, sz, true);
|
|
|
|
uint16_t v_handshake = pntoh16(tls + 4), v, v2;
|
|
DLOG("TLS handshake version : %s\n", TLSVersionStr(v_handshake));
|
|
|
|
if (TLSFindExtInHandshake(tls, sz, 43, &ext, &len, false))
|
|
{
|
|
if (len)
|
|
{
|
|
if (bServerHello)
|
|
{
|
|
v = pntoh16(ext);
|
|
DLOG("TLS supported versions ext : %s\n", TLSVersionStr(v));
|
|
}
|
|
else
|
|
{
|
|
len2 = ext[0];
|
|
if (len2 < len)
|
|
{
|
|
for (ext++, len2 &= ~1; len2; len2 -= 2, ext += 2)
|
|
{
|
|
v = pntoh16(ext);
|
|
DLOG("TLS supported versions ext : %s\n", TLSVersionStr(v));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
DLOG("TLS supported versions ext : not present\n");
|
|
|
|
if (!bServerHello)
|
|
{
|
|
if (TLSFindExtInHandshake(tls, sz, 16, &ext, &len, false))
|
|
{
|
|
if (len >= 2)
|
|
{
|
|
len2 = pntoh16(ext);
|
|
if (len2 <= (len - 2))
|
|
{
|
|
char s[32];
|
|
for (ext += 2; len2;)
|
|
{
|
|
v = *ext; ext++; len2--;
|
|
if (v <= len2)
|
|
{
|
|
v2 = v < sizeof(s) ? v : sizeof(s) - 1;
|
|
memcpy(s, ext, v2);
|
|
s[v2] = 0;
|
|
DLOG("TLS ALPN ext : %s\n", s);
|
|
len2 -= v;
|
|
ext += v;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
DLOG("TLS ALPN ext : not present\n");
|
|
|
|
DLOG("TLS ECH ext : %s\n", TLSFindExtInHandshake(tls, sz, 65037, NULL, NULL, false) ? "present" : "not present");
|
|
}
|
|
}
|
|
static void TLSDebug(const uint8_t *tls, size_t sz)
|
|
{
|
|
if (!params.debug) return;
|
|
|
|
if (sz < 11) return;
|
|
|
|
DLOG("TLS record layer version : %s\n", TLSVersionStr(pntoh16(tls + 1)));
|
|
|
|
size_t reclen = TLSRecordLen(tls);
|
|
if (reclen < sz) sz = reclen; // correct len if it has more data than the first tls record has
|
|
|
|
TLSDebugHandshake(tls + 5, sz - 5);
|
|
}
|
|
|
|
static void packet_debug(bool replay, const struct dissect *dis)
|
|
{
|
|
if (params.debug)
|
|
{
|
|
if (replay) DLOG("REPLAY ");
|
|
if (dis->ip)
|
|
{
|
|
char s[66];
|
|
str_ip(s, sizeof(s), dis->ip);
|
|
DLOG("IP4: %s", s);
|
|
}
|
|
else if (dis->ip6)
|
|
{
|
|
char s[128];
|
|
str_ip6hdr(s, sizeof(s), dis->ip6, dis->proto);
|
|
DLOG("IP6: %s", s);
|
|
}
|
|
|
|
if (dis->tcp)
|
|
{
|
|
char s[80];
|
|
str_tcphdr(s, sizeof(s), dis->tcp);
|
|
DLOG(" %s\n", s);
|
|
if (dis->len_payload)
|
|
{
|
|
DLOG("TCP: len=%zu : ", dis->len_payload);
|
|
hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP);
|
|
DLOG("\n");
|
|
}
|
|
}
|
|
else if (dis->udp)
|
|
{
|
|
char s[30];
|
|
str_udphdr(s, sizeof(s), dis->udp);
|
|
DLOG(" %s\n", s);
|
|
if (dis->len_payload)
|
|
{
|
|
DLOG("UDP: len=%zu : ", dis->len_payload);
|
|
hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP);
|
|
DLOG("\n");
|
|
}
|
|
}
|
|
else if (dis->icmp)
|
|
{
|
|
char s[72];
|
|
str_icmphdr(s, sizeof(s), !!dis->ip6, dis->icmp);
|
|
DLOG(" %s\nICMP: len=%zu : ", s, dis->len_payload);
|
|
hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP);
|
|
DLOG("\n");
|
|
}
|
|
else
|
|
{
|
|
if (dis->len_payload)
|
|
{
|
|
char s_proto[16];
|
|
str_proto_name(s_proto,sizeof(s_proto),dis->proto);
|
|
DLOG("\nIP PROTO %s: len=%zu : ", s_proto, dis->len_payload);
|
|
hexdump_limited_dlog(dis->data_payload, dis->len_payload, PKTDATA_MAXDUMP);
|
|
DLOG("\n");
|
|
}
|
|
else
|
|
DLOG("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
// ipr,ipr6 - reverse ip - ip of the other side of communication
|
|
static bool dp_match(
|
|
struct desync_profile *dp,
|
|
uint8_t l3proto,
|
|
const struct in_addr *ip, const struct in6_addr *ip6,
|
|
const struct in_addr *ipr, const struct in6_addr *ipr6,
|
|
uint16_t port, uint8_t icmp_type, uint8_t icmp_code,
|
|
const char *hostname, bool bNoSubdom, t_l7proto l7proto, const char *ssid,
|
|
bool *bCheckDone, bool *bCheckResult, bool *bExcluded)
|
|
{
|
|
bool bHostlistsEmpty;
|
|
|
|
if (bCheckDone) *bCheckDone = false;
|
|
|
|
if (!HostlistsReloadCheckForProfile(dp)) return false;
|
|
|
|
if ((ip && !dp->filter_ipv4) || (ip6 && !dp->filter_ipv6))
|
|
// L3 filter does not match
|
|
return false;
|
|
|
|
switch(l3proto)
|
|
{
|
|
case IPPROTO_TCP:
|
|
if (!port_filters_match(&dp->pf_tcp, port)) return false;
|
|
break;
|
|
case IPPROTO_UDP:
|
|
if (!port_filters_match(&dp->pf_udp, port)) return false;
|
|
break;
|
|
case IPPROTO_ICMP:
|
|
case IPPROTO_ICMPV6:
|
|
if (!icmp_filters_match(&dp->icf, icmp_type, icmp_code)) return false;
|
|
break;
|
|
default:
|
|
if (!ipp_filters_match(&dp->ipf, l3proto)) return false;
|
|
}
|
|
|
|
if (l3proto == IPPROTO_ICMP && !icmp_filters_match(&dp->icf, icmp_type, icmp_code))
|
|
// icmp filter does not match
|
|
return false;
|
|
|
|
if (!l7_proto_match(l7proto, dp->filter_l7))
|
|
// L7 filter does not match
|
|
return false;
|
|
#ifdef HAS_FILTER_SSID
|
|
if (!LIST_EMPTY(&dp->filter_ssid) && !strlist_search(&dp->filter_ssid, ssid))
|
|
return false;
|
|
#endif
|
|
|
|
bHostlistsEmpty = PROFILE_HOSTLISTS_EMPTY(dp);
|
|
if (!dp->hostlist_auto && !hostname && !bHostlistsEmpty)
|
|
// avoid cpu consuming ipset check. profile cannot win if regular hostlists are present without auto hostlist and hostname is unknown.
|
|
return false;
|
|
if (!IpsetCheck(dp, ip, ip6, ipr, ipr6))
|
|
// target ip does not match
|
|
return false;
|
|
|
|
// autohostlist profile matching l3/l4/l7 filter always win if we have a hostname. no matter it matches or not.
|
|
if (dp->hostlist_auto && hostname)
|
|
{
|
|
DLOG("autohostlist profile %u (%s) wins because hostname is known\n",dp->n,dp->name);
|
|
return true;
|
|
}
|
|
|
|
if (bHostlistsEmpty)
|
|
// profile without hostlist filter wins
|
|
return true;
|
|
else
|
|
{
|
|
// if hostlists are present profile matches only if hostname is known and satisfy profile hostlists
|
|
if (hostname)
|
|
{
|
|
if (bCheckDone) *bCheckDone = true;
|
|
bool b;
|
|
b = HostlistCheck(dp, hostname, bNoSubdom, bExcluded, true);
|
|
if (bCheckResult) *bCheckResult = b;
|
|
return b;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
static struct desync_profile *dp_find(
|
|
struct desync_profile_list_head *head,
|
|
uint8_t l3proto,
|
|
const struct in_addr *ip, const struct in6_addr *ip6,
|
|
const struct in_addr *ipr, const struct in6_addr *ipr6,
|
|
uint16_t port, uint8_t icmp_type, uint8_t icmp_code,
|
|
const char *hostname, bool bNoSubdom, t_l7proto l7proto, const char *ssid,
|
|
bool *bCheckDone, bool *bCheckResult, bool *bExcluded)
|
|
{
|
|
struct desync_profile_list *dpl;
|
|
if (params.debug)
|
|
{
|
|
char s[40];
|
|
ntopa46(ip, ip6, s, sizeof(s));
|
|
if (ipr || ipr6)
|
|
{
|
|
char sr[40];
|
|
ntopa46(ipr, ipr6, sr, sizeof(sr));
|
|
DLOG("desync profile search for %s ip1=%s ip2=%s port=%u icmp=%u:%u l7proto=%s ssid='%s' hostname='%s'\n",
|
|
proto_name(l3proto), s, sr, port, icmp_type, icmp_code, l7proto_str(l7proto), ssid ? ssid : "", hostname ? hostname : "");
|
|
}
|
|
else
|
|
DLOG("desync profile search for %s ip=%s port=%u icmp=%u:%u l7proto=%s ssid='%s' hostname='%s'\n",
|
|
proto_name(l3proto), s, port, icmp_type, icmp_code, l7proto_str(l7proto), ssid ? ssid : "", hostname ? hostname : "");
|
|
}
|
|
if (bCheckDone) *bCheckDone = false;
|
|
LIST_FOREACH(dpl, head, next)
|
|
{
|
|
if (dp_match(&dpl->dp, l3proto, ip, ip6, ipr, ipr6, port, icmp_type, icmp_code, hostname, bNoSubdom, l7proto, ssid, bCheckDone, bCheckResult, bExcluded))
|
|
{
|
|
DLOG("desync profile %u (%s) matches\n", dpl->dp.n, PROFILE_NAME(&dpl->dp));
|
|
return &dpl->dp;
|
|
}
|
|
}
|
|
DLOG("desync profile not found\n");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void ctrack_stop_retrans_counter(t_ctrack *ctrack)
|
|
{
|
|
if (ctrack && ctrack->hostname_ah_check)
|
|
ctrack->req_retrans_counter = RETRANS_COUNTER_STOP;
|
|
}
|
|
|
|
static void auto_hostlist_reset_fail_counter(struct desync_profile *dp, const char *hostname, const char *client_ip_port, t_l7proto l7proto)
|
|
{
|
|
if (hostname)
|
|
{
|
|
hostfail_pool *fail_counter;
|
|
|
|
fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname);
|
|
if (fail_counter)
|
|
{
|
|
HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter);
|
|
DLOG("auto hostlist (profile %u (%s)) : %s : fail counter reset. website is working.\n", dp->n, PROFILE_NAME(dp), hostname);
|
|
HOSTLIST_DEBUGLOG_APPEND("%s : profile %u (%s) : client %s : proto %s : fail counter reset. website is working.", hostname, dp->n, PROFILE_NAME(dp), client_ip_port, l7proto_str(l7proto));
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool is_retransmission(const t_ctrack_position *pos)
|
|
{
|
|
return !((pos->uppos_prev - pos->pos) & 0x80000000);
|
|
}
|
|
|
|
// return true if retrans trigger fires
|
|
static bool auto_hostlist_retrans
|
|
(t_ctrack *ctrack, const struct dissect *dis, int threshold, const char *client_ip_port, t_l7proto l7proto,
|
|
const struct sockaddr *client, const char *ifclient)
|
|
{
|
|
if (ctrack && ctrack->dp && ctrack->hostname_ah_check && !ctrack->failure_detect_finalized && ctrack->req_retrans_counter != RETRANS_COUNTER_STOP)
|
|
{
|
|
if (dis->proto == IPPROTO_TCP && ctrack->pos.state!=SYN)
|
|
{
|
|
if (!seq_within(ctrack->pos.client.seq_last, ctrack->pos.client.seq0, ctrack->pos.client.seq0 + ctrack->dp->hostlist_auto_retrans_maxseq))
|
|
{
|
|
ctrack->failure_detect_finalized = true;
|
|
DLOG("retrans : tcp seq %u not within range %u-%u. stop tracking.\n", ctrack->pos.client.seq_last, ctrack->pos.client.seq0, ctrack->pos.client.seq0 + ctrack->dp->hostlist_auto_retrans_maxseq);
|
|
ctrack_stop_retrans_counter(ctrack);
|
|
auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname, client_ip_port, l7proto);
|
|
return false;
|
|
}
|
|
if (!is_retransmission(&ctrack->pos.client))
|
|
return false;
|
|
}
|
|
ctrack->req_retrans_counter++;
|
|
if (ctrack->req_retrans_counter >= threshold)
|
|
{
|
|
DLOG("retrans threshold reached : %u/%u\n", ctrack->req_retrans_counter, threshold);
|
|
ctrack_stop_retrans_counter(ctrack);
|
|
ctrack->failure_detect_finalized = true;
|
|
if (dis->tcp && ctrack->dp->hostlist_auto_retrans_reset && (dis->ip || dis->ip6))
|
|
{
|
|
uint8_t pkt[sizeof(struct ip6_hdr)+sizeof(struct tcphdr)];
|
|
struct ip *ip=NULL;
|
|
struct ip6_hdr *ip6=NULL;
|
|
struct tcphdr *tcp;
|
|
uint16_t pktlen;
|
|
|
|
if (dis->ip)
|
|
{
|
|
ip = (struct ip*)pkt;
|
|
pktlen = sizeof(struct ip) + sizeof(struct tcphdr);
|
|
*ip = *dis->ip;
|
|
ip->ip_hl = sizeof(struct ip)/4; // remove ip options
|
|
ip->ip_len = htons(pktlen);
|
|
ip->ip_id=0;
|
|
tcp = (struct tcphdr*)(ip+1);
|
|
*tcp = *dis->tcp;
|
|
}
|
|
else if (dis->ip6)
|
|
{
|
|
ip6 = (struct ip6_hdr*)pkt;
|
|
pktlen = sizeof(struct ip6_hdr) + sizeof(struct tcphdr);
|
|
*ip6 = *dis->ip6;
|
|
ip6->ip6_plen = htons(sizeof(struct tcphdr));
|
|
ip6->ip6_nxt = IPPROTO_TCP;
|
|
ip6->ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(ctrack->pos.server.ip6flow ? ctrack->pos.server.ip6flow : 0x60000000);
|
|
tcp = (struct tcphdr*)(ip6+1);
|
|
*tcp = *dis->tcp;
|
|
}
|
|
else
|
|
return true; // should never happen
|
|
|
|
reverse_ip(ip,ip6); // also fixes ip4 checksum
|
|
reverse_tcp(tcp);
|
|
tcp->th_off = sizeof(struct tcphdr)/4; // remove tcp options
|
|
tcp->th_flags = TH_RST;
|
|
tcp->th_win = ctrack->pos.server.winsize;
|
|
tcp_fix_checksum(tcp, sizeof(struct tcphdr), ip, ip6);
|
|
|
|
DLOG("sending RST to retransmitter. ifname=%s\n", ifclient ? ifclient : "");
|
|
rawsend(client,0,ifclient,pkt,pktlen);
|
|
}
|
|
return true;
|
|
}
|
|
DLOG("retrans counter : %u/%u\n", ctrack->req_retrans_counter, threshold);
|
|
}
|
|
return false;
|
|
}
|
|
static void auto_hostlist_failed(struct desync_profile *dp, const char *hostname, bool bNoSubdom, const char *client_ip_port, t_l7proto l7proto)
|
|
{
|
|
hostfail_pool *fail_counter;
|
|
|
|
fail_counter = HostFailPoolFind(dp->hostlist_auto_fail_counters, hostname);
|
|
if (!fail_counter)
|
|
{
|
|
fail_counter = HostFailPoolAdd(&dp->hostlist_auto_fail_counters, hostname, dp->hostlist_auto_fail_time);
|
|
if (!fail_counter)
|
|
{
|
|
DLOG_ERR("HostFailPoolAdd: out of memory\n");
|
|
return;
|
|
}
|
|
}
|
|
fail_counter->counter++;
|
|
DLOG("auto hostlist (profile %u (%s)) : %s : fail counter %d/%d\n", dp->n, PROFILE_NAME(dp), hostname, fail_counter->counter, dp->hostlist_auto_fail_threshold);
|
|
HOSTLIST_DEBUGLOG_APPEND("%s : profile %u (%s) : client %s : proto %s : fail counter %d/%d", hostname, dp->n, PROFILE_NAME(dp), client_ip_port, l7proto_str(l7proto), fail_counter->counter, dp->hostlist_auto_fail_threshold);
|
|
if (fail_counter->counter >= dp->hostlist_auto_fail_threshold)
|
|
{
|
|
DLOG("auto hostlist (profile %u (%s)) : fail threshold reached. about to add %s to auto hostlist\n", dp->n, PROFILE_NAME(dp), hostname);
|
|
HostFailPoolDel(&dp->hostlist_auto_fail_counters, fail_counter);
|
|
|
|
DLOG("auto hostlist (profile %u (%s)) : rechecking %s to avoid duplicates\n", dp->n, PROFILE_NAME(dp), hostname);
|
|
bool bExcluded = false;
|
|
if (!HostlistCheck(dp, hostname, bNoSubdom, &bExcluded, false) && !bExcluded)
|
|
{
|
|
DLOG("auto hostlist (profile %u) : adding %s to %s\n", dp->n, hostname, dp->hostlist_auto->filename);
|
|
HOSTLIST_DEBUGLOG_APPEND("%s : profile %u (%s) : client %s : proto %s : adding to %s", hostname, dp->n, PROFILE_NAME(dp), client_ip_port, l7proto_str(l7proto), dp->hostlist_auto->filename);
|
|
if (!HostlistPoolAddStr(&dp->hostlist_auto->hostlist, hostname, 0))
|
|
{
|
|
DLOG_ERR("StrPoolAddStr out of memory\n");
|
|
return;
|
|
}
|
|
if (!append_to_list_file(dp->hostlist_auto->filename, hostname))
|
|
{
|
|
DLOG_PERROR("write to auto hostlist");
|
|
return;
|
|
}
|
|
if (!file_mod_signature(dp->hostlist_auto->filename, &dp->hostlist_auto->mod_sig))
|
|
DLOG_PERROR("file_mod_signature");
|
|
}
|
|
else
|
|
{
|
|
DLOG("auto hostlist (profile %u) : NOT adding %s\n", dp->n, hostname);
|
|
HOSTLIST_DEBUGLOG_APPEND("%s : profile %u (%s) : client %s : proto %s : NOT adding, duplicate detected", hostname, dp->n, PROFILE_NAME(dp), client_ip_port, l7proto_str(l7proto));
|
|
}
|
|
}
|
|
}
|
|
static void fill_client_ip_port(const struct sockaddr *client, char *client_ip_port, size_t client_ip_port_size)
|
|
{
|
|
if (*params.hostlist_auto_debuglog)
|
|
ntop46_port((struct sockaddr*)client, client_ip_port, client_ip_port_size);
|
|
else
|
|
*client_ip_port = 0;
|
|
}
|
|
static void process_retrans_fail(t_ctrack *ctrack, const struct dissect *dis, struct sockaddr *client, const char *ifclient)
|
|
{
|
|
if (params.server) return; // no autohostlists in server mode
|
|
|
|
char client_ip_port[48];
|
|
fill_client_ip_port(client, client_ip_port, sizeof(client_ip_port));
|
|
if (ctrack && ctrack->dp && ctrack->hostname && auto_hostlist_retrans(ctrack, dis, ctrack->dp->hostlist_auto_retrans_threshold, client_ip_port, ctrack->l7proto, client, ifclient))
|
|
{
|
|
HOSTLIST_DEBUGLOG_APPEND("%s : profile %u (%s) : client %s : proto %s : retrans threshold reached", ctrack->hostname, ctrack->dp->n, PROFILE_NAME(ctrack->dp), client_ip_port, l7proto_str(ctrack->l7proto));
|
|
auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto);
|
|
}
|
|
}
|
|
static void process_udp_fail(t_ctrack *ctrack, const t_ctrack_positions *tpos, const struct sockaddr *client)
|
|
{
|
|
// no autohostlists in server mode
|
|
if (!params.server && ctrack && ctrack->dp && ctrack->hostname && ctrack->hostname_ah_check &&
|
|
!ctrack->failure_detect_finalized && ctrack->dp->hostlist_auto_udp_out)
|
|
{
|
|
char client_ip_port[48];
|
|
|
|
if (!tpos) tpos = &ctrack->pos;
|
|
//printf("UDP_POS %u %u\n",tpos->client.pcounter, tpos->server.pcounter);
|
|
if (tpos->server.pcounter > ctrack->dp->hostlist_auto_udp_in)
|
|
{
|
|
// success
|
|
ctrack->failure_detect_finalized = true;
|
|
DLOG("udp_in %u > %u\n",tpos->server.pcounter,ctrack->dp->hostlist_auto_udp_in);
|
|
fill_client_ip_port(client, client_ip_port, sizeof(client_ip_port));
|
|
auto_hostlist_reset_fail_counter(ctrack->dp, ctrack->hostname, client_ip_port, ctrack->l7proto);
|
|
}
|
|
else if (tpos->client.pcounter >= ctrack->dp->hostlist_auto_udp_out)
|
|
{
|
|
// failure
|
|
ctrack->failure_detect_finalized = true;
|
|
DLOG("udp_in %u <= %u, udp_out %u >= %u\n",tpos->server.pcounter,ctrack->dp->hostlist_auto_udp_in,tpos->client.pcounter,ctrack->dp->hostlist_auto_udp_out);
|
|
fill_client_ip_port(client, client_ip_port, sizeof(client_ip_port));
|
|
HOSTLIST_DEBUGLOG_APPEND("%s : profile %u (%s) : client %s : proto %s : udp_in %u<=%u udp_out %u>=%u",
|
|
ctrack->hostname, ctrack->dp->n, PROFILE_NAME(ctrack->dp), client_ip_port, l7proto_str(ctrack->l7proto),
|
|
tpos->server.pcounter, ctrack->dp->hostlist_auto_udp_in,
|
|
tpos->client.pcounter, ctrack->dp->hostlist_auto_udp_out);
|
|
auto_hostlist_failed(ctrack->dp, ctrack->hostname, ctrack->hostname_is_ip, client_ip_port, ctrack->l7proto);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool send_delayed(t_ctrack *ctrack)
|
|
{
|
|
if (!rawpacket_queue_empty(&ctrack->delayed))
|
|
{
|
|
DLOG("SENDING %u delayed packets\n", rawpacket_queue_count(&ctrack->delayed));
|
|
return rawsend_queue(&ctrack->delayed);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool reasm_start(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, uint32_t seq, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload)
|
|
{
|
|
ReasmClear(reasm);
|
|
if (sz <= szMax)
|
|
{
|
|
if (ReasmInit(reasm, sz, seq))
|
|
{
|
|
ReasmFeed(reasm, seq, data_payload, len_payload);
|
|
DLOG("starting reassemble. now we have %zu/%zu\n", reasm->size_present, reasm->size);
|
|
return true;
|
|
}
|
|
else
|
|
DLOG("reassemble init failed. out of memory\n");
|
|
}
|
|
else
|
|
DLOG("unexpected large payload for reassemble: size=%zu\n", sz);
|
|
return false;
|
|
}
|
|
static bool reasm_client_start(t_ctrack *ctrack, uint8_t proto, size_t sz, size_t szMax, const uint8_t *data_payload, size_t len_payload)
|
|
{
|
|
if (!ctrack) return false;
|
|
// if pcounter==0 it means we dont know server window size - no incoming packets redirected ?
|
|
if (proto==IPPROTO_TCP && ctrack->pos.server.pcounter && (ctrack->pos.server.winsize_calc < sz))
|
|
{
|
|
// this is rare but possible situation
|
|
// server gave us too small tcp window
|
|
// client will not send all pieces of reasm
|
|
// if we drop packets and wait for next pieces we will see nothing but retransmissions
|
|
DLOG("reasm cancelled because server window size %u is smaller than expected reasm size %u\n", ctrack->pos.server.winsize_calc, sz);
|
|
return false;
|
|
}
|
|
return reasm_start(ctrack, &ctrack->reasm_client, proto, (proto == IPPROTO_TCP) ? ctrack->pos.client.seq_last : 0, sz, szMax, data_payload, len_payload);
|
|
}
|
|
static bool reasm_feed(t_ctrack *ctrack, t_reassemble *reasm, uint8_t proto, uint32_t seq, const uint8_t *data_payload, size_t len_payload)
|
|
{
|
|
if (ctrack && !ReasmIsEmpty(reasm))
|
|
{
|
|
if (ReasmFeed(reasm, seq, data_payload, len_payload))
|
|
{
|
|
DLOG("reassemble : feeding data payload size=%zu. now we have %zu/%zu\n", len_payload, reasm->size_present, reasm->size);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
ReasmClear(reasm);
|
|
DLOG("reassemble session failed\n");
|
|
send_delayed(ctrack);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
static bool reasm_client_feed(t_ctrack *ctrack, uint8_t proto, const uint8_t *data_payload, size_t len_payload)
|
|
{
|
|
if (!ctrack) return false;
|
|
return reasm_feed(ctrack, &ctrack->reasm_client, proto, (proto == IPPROTO_TCP) ? ctrack->pos.client.seq_last : (uint32_t)ctrack->reasm_client.size_present, data_payload, len_payload);
|
|
}
|
|
static void reasm_client_stop(t_ctrack *ctrack, const char *dlog_msg)
|
|
{
|
|
if (ctrack)
|
|
{
|
|
if (!ReasmIsEmpty(&ctrack->reasm_client))
|
|
{
|
|
DLOG("%s", dlog_msg);
|
|
ReasmClear(&ctrack->reasm_client);
|
|
}
|
|
send_delayed(ctrack);
|
|
}
|
|
}
|
|
static void reasm_client_cancel(t_ctrack *ctrack)
|
|
{
|
|
reasm_client_stop(ctrack, "reassemble session cancelled\n");
|
|
}
|
|
static void reasm_client_fin(t_ctrack *ctrack)
|
|
{
|
|
reasm_client_stop(ctrack, "reassemble session finished\n");
|
|
}
|
|
|
|
|
|
static uint8_t ct_new_postnat_fix(const t_ctrack *ctrack, const struct dissect *dis, uint8_t *mod_pkt, size_t *len_mod_pkt)
|
|
{
|
|
#ifdef __linux__
|
|
// if used in postnat chain, dropping initial packet will cause conntrack connection teardown
|
|
// so we need to workaround this.
|
|
// SYN and SYN,ACK checks are for conntrack-less mode
|
|
if (ctrack && (params.server ? ctrack->pos.server.pcounter : ctrack->pos.client.pcounter) == 1 || dis->tcp && (tcp_syn_segment(dis->tcp) || tcp_synack_segment(dis->tcp)))
|
|
{
|
|
if (dis->len_pkt > *len_mod_pkt)
|
|
DLOG_ERR("linux postnat conntrack workaround cannot be applied\n");
|
|
else
|
|
{
|
|
memcpy(mod_pkt, dis->data_pkt, dis->len_pkt);
|
|
DLOG("applying linux postnat conntrack workaround\n");
|
|
// make ip protocol invalid and low TTL
|
|
if (dis->ip6)
|
|
{
|
|
((struct ip6_hdr*)mod_pkt)->ip6_ctlun.ip6_un1.ip6_un1_nxt = 255;
|
|
((struct ip6_hdr*)mod_pkt)->ip6_ctlun.ip6_un1.ip6_un1_hlim = 1;
|
|
}
|
|
if (dis->ip)
|
|
{
|
|
// this likely also makes ipv4 header checksum invalid
|
|
((struct ip*)mod_pkt)->ip_p = 255;
|
|
((struct ip*)mod_pkt)->ip_ttl = 1;
|
|
}
|
|
*len_mod_pkt = dis->len_pkt;
|
|
}
|
|
return VERDICT_MODIFY | VERDICT_NOCSUM;
|
|
}
|
|
#endif
|
|
return VERDICT_DROP;
|
|
}
|
|
|
|
static bool pos_overflow(const t_ctrack_position *pos, char mode)
|
|
{
|
|
return (mode=='s' || mode=='p') && pos && pos->rseq_over_2G;
|
|
}
|
|
static uint64_t pos_get(const t_ctrack_position *pos, char mode)
|
|
{
|
|
if (pos)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case 'n': return pos->pcounter;
|
|
case 'd': return pos->pdcounter;
|
|
case 's': return pos->seq_last - pos->seq0;
|
|
case 'p': return pos->pos - pos->seq0;
|
|
case 'b': return pos->pbcounter;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
static bool check_pos_from(const t_ctrack_position *pos, const struct packet_range *range)
|
|
{
|
|
uint64_t ps;
|
|
if ((range->from.mode == 'x') || pos_overflow(pos,range->from.mode)) return false;
|
|
if (range->from.mode != 'a')
|
|
{
|
|
if (pos)
|
|
{
|
|
ps = pos_get(pos, range->from.mode);
|
|
return ps >= range->from.pos;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
static bool check_pos_to(const t_ctrack_position *pos, const struct packet_range *range)
|
|
{
|
|
uint64_t ps;
|
|
if (range->to.mode == 'x' || pos_overflow(pos,range->to.mode)) return false;
|
|
if (range->to.mode != 'a')
|
|
{
|
|
if (pos)
|
|
{
|
|
ps = pos_get(pos, range->to.mode);
|
|
return (ps < range->to.pos) || !range->upper_cutoff && (ps == range->to.pos);
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
static bool check_pos_cutoff(const t_ctrack_position *pos, const struct packet_range *range)
|
|
{
|
|
bool bto = check_pos_to(pos, range);
|
|
return pos ? !bto : (!bto || !check_pos_from(pos, range));
|
|
}
|
|
static bool check_pos_range(const t_ctrack_position *pos, const struct packet_range *range)
|
|
{
|
|
return check_pos_from(pos, range) && check_pos_to(pos, range);
|
|
}
|
|
|
|
|
|
static bool replay_queue(struct rawpacket_tailhead *q);
|
|
|
|
static bool ipcache_put_hostname(const struct in_addr *a4, const struct in6_addr *a6, const char *hostname, bool hostname_is_ip)
|
|
{
|
|
if (!params.cache_hostname) return true;
|
|
|
|
ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, NULL);
|
|
if (!ipc)
|
|
{
|
|
DLOG_ERR("ipcache_put_hostname: out of memory\n");
|
|
return false;
|
|
}
|
|
if (!ipc->hostname || strcmp(ipc->hostname, hostname))
|
|
{
|
|
free(ipc->hostname);
|
|
if (!(ipc->hostname = strdup(hostname)))
|
|
{
|
|
DLOG_ERR("ipcache_put_hostname: out of memory\n");
|
|
return false;
|
|
}
|
|
ipc->hostname_is_ip = hostname_is_ip;
|
|
DLOG("hostname cached (is_ip=%u): %s\n", hostname_is_ip, hostname);
|
|
}
|
|
return true;
|
|
}
|
|
static bool ipcache_get_hostname(const struct in_addr *a4, const struct in6_addr *a6, char *hostname, size_t hostname_buf_len, bool *hostname_is_ip)
|
|
{
|
|
if (!params.cache_hostname)
|
|
{
|
|
*hostname = 0;
|
|
return true;
|
|
}
|
|
if (params.debug)
|
|
{
|
|
char s[40];
|
|
ntopa46(a4, a6, s, sizeof(s));
|
|
DLOG("ipcache hostname search for %s\n", s);
|
|
}
|
|
ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, NULL);
|
|
if (!ipc)
|
|
{
|
|
DLOG_ERR("ipcache_get_hostname: out of memory\n");
|
|
return false;
|
|
}
|
|
if (ipc->hostname)
|
|
{
|
|
if (params.debug)
|
|
{
|
|
char s[40];
|
|
ntopa46(a4, a6, s, sizeof(s));
|
|
DLOG("got cached hostname for %s : %s (is_ip=%u)\n", s, ipc->hostname, ipc->hostname_is_ip);
|
|
}
|
|
snprintf(hostname, hostname_buf_len, "%s", ipc->hostname);
|
|
if (hostname_is_ip) *hostname_is_ip = ipc->hostname_is_ip;
|
|
}
|
|
else
|
|
*hostname = 0;
|
|
return true;
|
|
}
|
|
static void ipcache_update_ttl(t_ctrack *ctrack, const struct in_addr *a4, const struct in6_addr *a6, const char *iface)
|
|
{
|
|
// no need to cache ttl in server mode because first packet is incoming
|
|
if (ctrack && !params.server)
|
|
{
|
|
ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, iface);
|
|
if (!ipc)
|
|
{
|
|
DLOG_ERR("ipcache: out of memory\n");
|
|
return;
|
|
}
|
|
if (ctrack->incoming_ttl)
|
|
{
|
|
if (ipc->ttl != ctrack->incoming_ttl)
|
|
{
|
|
DLOG("updated ttl cache\n");
|
|
ipc->ttl = ctrack->incoming_ttl;
|
|
}
|
|
}
|
|
else if (ipc->ttl)
|
|
{
|
|
DLOG("got cached ttl %u\n", ipc->ttl);
|
|
ctrack->incoming_ttl = ipc->ttl;
|
|
}
|
|
}
|
|
}
|
|
static void ipcache_get_ttl(t_ctrack *ctrack, const struct in_addr *a4, const struct in6_addr *a6, const char *iface)
|
|
{
|
|
// no need to cache ttl in server mode because first packet is incoming
|
|
if (ctrack && !ctrack->incoming_ttl && !params.server)
|
|
{
|
|
ip_cache_item *ipc = ipcacheTouch(¶ms.ipcache, a4, a6, iface);
|
|
if (!ipc)
|
|
DLOG_ERR("ipcache: out of memory\n");
|
|
else if (ipc->ttl)
|
|
{
|
|
DLOG("got cached ttl %u\n", ipc->ttl);
|
|
ctrack->incoming_ttl = ipc->ttl;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static bool desync_get_result(uint8_t *verdict)
|
|
{
|
|
int rescount = lua_gettop(params.L);
|
|
if (rescount>1)
|
|
{
|
|
DLOG_ERR("desync function returned more than one result : %d\n", rescount);
|
|
goto err;
|
|
}
|
|
if (rescount)
|
|
{
|
|
if (!lua_isinteger(params.L, -1))
|
|
{
|
|
DLOG_ERR("desync function returned non-int result\n");
|
|
goto err;
|
|
}
|
|
lua_Integer lv = lua_tointeger(params.L, -1);
|
|
if (lv & ~VERDICT_MASK_VALID_LUA)
|
|
{
|
|
DLOG_ERR("desync function returned bad int result\n");
|
|
goto err;
|
|
}
|
|
*verdict = (uint8_t)lv;
|
|
}
|
|
else
|
|
*verdict = VERDICT_PASS; // default result if function returns nothing
|
|
lua_pop(params.L, rescount);
|
|
return true;
|
|
err:
|
|
lua_pop(params.L, rescount);
|
|
return false;
|
|
}
|
|
static uint8_t verdict_aggregate(uint8_t v1,uint8_t v2)
|
|
{
|
|
uint8_t verdict_action = v1 & VERDICT_MASK;
|
|
switch (v2 & VERDICT_MASK)
|
|
{
|
|
case VERDICT_MODIFY:
|
|
if (verdict_action == VERDICT_PASS) verdict_action = VERDICT_MODIFY;
|
|
break;
|
|
case VERDICT_DROP:
|
|
verdict_action = VERDICT_DROP;
|
|
break;
|
|
}
|
|
return v1 & ~VERDICT_MASK | verdict_action | v2 & VERDICT_PRESERVE_NEXT;
|
|
}
|
|
static uint8_t desync(
|
|
struct desync_profile *dp,
|
|
uint32_t fwmark,
|
|
const char *ifin,
|
|
const char *ifout,
|
|
bool bIncoming,
|
|
t_ctrack *ctrack,
|
|
const t_ctrack_positions *tpos,
|
|
t_l7payload l7payload,
|
|
t_l7proto l7proto,
|
|
const struct dissect *dis,
|
|
const struct in_addr *sdp4, const struct in6_addr *sdp6, uint16_t sdport,
|
|
uint8_t *mod_pkt, size_t *len_mod_pkt,
|
|
unsigned int replay_piece, unsigned int replay_piece_count, size_t reasm_offset, const uint8_t *rdata_payload, size_t rlen_payload,
|
|
const uint8_t *data_decrypt, size_t len_decrypt)
|
|
{
|
|
uint8_t verdict = VERDICT_PASS, verdict_func;
|
|
struct func_list *func;
|
|
int ref_arg = LUA_NOREF, status;
|
|
bool b, b_cutoff_all, b_unwanted_payload;
|
|
const char *sDirection = bIncoming ? "in" : "out";
|
|
struct packet_range *range;
|
|
size_t l;
|
|
char instance[256];
|
|
const t_ctrack_position *pos, *rpos;
|
|
|
|
if (ctrack)
|
|
{
|
|
// fast way not to do anything
|
|
if (bIncoming && ctrack->b_lua_in_cutoff)
|
|
{
|
|
DLOG("lua in cutoff\n");
|
|
return verdict;
|
|
}
|
|
if (!bIncoming && ctrack->b_lua_out_cutoff)
|
|
{
|
|
DLOG("lua out cutoff\n");
|
|
return verdict;
|
|
}
|
|
if (!tpos) tpos = &ctrack->pos;
|
|
}
|
|
pos = tpos ? (bIncoming ^ params.server) ? &tpos->server : &tpos->client : NULL;
|
|
rpos = tpos ? (bIncoming ^ params.server) ? &tpos->client : &tpos->server : NULL;
|
|
|
|
LUA_STACK_GUARD_ENTER(params.L)
|
|
|
|
if (LIST_FIRST(&dp->lua_desync))
|
|
{
|
|
lua_rawgeti(params.L, LUA_REGISTRYINDEX, params.ref_desync_ctx);
|
|
t_lua_desync_context *ctx = (t_lua_desync_context *)luaL_checkudata(params.L, 1, "desync_ctx");
|
|
// this is singleton stored in the registry. safe to pop
|
|
lua_pop(params.L,1);
|
|
|
|
ctx->dp = dp;
|
|
ctx->ctrack = ctrack;
|
|
ctx->dis = dis;
|
|
ctx->cancel = false;
|
|
ctx->incoming = bIncoming;
|
|
|
|
b_cutoff_all = b_unwanted_payload = true;
|
|
ctx->func_n = 1;
|
|
LIST_FOREACH(func, &dp->lua_desync, next)
|
|
{
|
|
ctx->func = func->func;
|
|
desync_instance(func->func, dp->n, ctx->func_n, instance, sizeof(instance));
|
|
ctx->instance = instance;
|
|
range = bIncoming ? &func->range_in : &func->range_out;
|
|
|
|
if (b_unwanted_payload)
|
|
b_unwanted_payload &= !l7_payload_match(l7payload, func->payload_type);
|
|
|
|
if (b_cutoff_all)
|
|
{
|
|
if (lua_instance_cutoff_check(params.L, ctx, bIncoming))
|
|
DLOG("* lua '%s' : voluntary cutoff\n", instance);
|
|
else if (check_pos_cutoff(pos, range))
|
|
{
|
|
DLOG("* lua '%s' : %s pos %c%llu %c%llu overflow %u %u is beyond range %c%u%c%c%u (ctrack %s)\n",
|
|
instance, sDirection,
|
|
range->from.mode, pos_get(pos, range->from.mode),
|
|
range->to.mode, pos_get(pos, range->to.mode),
|
|
pos_overflow(pos, range->from.mode),
|
|
pos_overflow(pos, range->to.mode),
|
|
range->from.mode, range->from.pos,
|
|
range->upper_cutoff ? '<' : '-',
|
|
range->to.mode, range->to.pos,
|
|
ctrack ? "enabled" : "disabled");
|
|
}
|
|
else
|
|
b_cutoff_all = false;
|
|
}
|
|
ctx->func_n++;
|
|
}
|
|
if (b_cutoff_all)
|
|
{
|
|
DLOG("all %s desync functions reached cutoff condition\n", sDirection);
|
|
if (ctrack) *(bIncoming ? &ctrack->b_lua_in_cutoff : &ctrack->b_lua_out_cutoff) = true;
|
|
}
|
|
else if (b_unwanted_payload)
|
|
DLOG("all %s desync functions do not want `%s` payload\n", sDirection, l7payload_str(l7payload));
|
|
else
|
|
{
|
|
// create arg table that persists across multiple desync function calls
|
|
lua_newtable(params.L);
|
|
lua_pushf_dissect(params.L, dis);
|
|
lua_pushf_ctrack(params.L, ctrack, tpos, bIncoming);
|
|
lua_pushf_int(params.L, "profile_n", dp->n);
|
|
if (dp->name) lua_pushf_str(params.L, "profile_name", dp->name);
|
|
if (dp->n_tpl) lua_pushf_int(params.L, "template_n", dp->n_tpl);
|
|
if (dp->name_tpl) lua_pushf_str(params.L, "template_name", dp->name_tpl);
|
|
if (dp->cookie) lua_pushf_str(params.L, "cookie", dp->cookie);
|
|
lua_pushf_bool(params.L, "outgoing", !bIncoming);
|
|
lua_pushf_str(params.L, "ifin", (ifin && *ifin) ? ifin : NULL);
|
|
lua_pushf_str(params.L, "ifout", (ifout && *ifout) ? ifout : NULL);
|
|
lua_pushf_lint(params.L, "fwmark", fwmark);
|
|
lua_pushf_table(params.L, "target");
|
|
lua_getfield(params.L,-1,"target");
|
|
if (sdport) lua_pushf_int(params.L, "port",sdport);
|
|
if (sdp4) lua_pushf_lstr(params.L, "ip",(const char*)sdp4,sizeof(*sdp4));
|
|
if (sdp6) lua_pushf_lstr(params.L, "ip6",(const char*)sdp6,sizeof(*sdp6));
|
|
lua_pop(params.L,1);
|
|
lua_pushf_bool(params.L, "replay", !!replay_piece_count);
|
|
if (replay_piece_count)
|
|
{
|
|
lua_pushf_int(params.L, "replay_piece", replay_piece+1);
|
|
lua_pushf_int(params.L, "replay_piece_count", replay_piece_count);
|
|
lua_pushf_bool(params.L, "replay_piece_last", (replay_piece+1)>=replay_piece_count);
|
|
}
|
|
lua_pushf_str(params.L, "l7payload", l7payload_str(l7payload));
|
|
lua_pushf_str(params.L, "l7proto", l7proto_str(l7proto));
|
|
lua_pushf_int(params.L, "reasm_offset", reasm_offset);
|
|
lua_pushf_raw(params.L, "reasm_data", rdata_payload, rlen_payload);
|
|
lua_pushf_raw(params.L, "decrypt_data", data_decrypt, len_decrypt);
|
|
//if (ctrack) lua_pushf_reg("instance_cutoff", ctrack->lua_instance_cutoff);
|
|
if (dis->tcp)
|
|
{
|
|
// recommended mss value for generated packets
|
|
if (pos && pos->mss && rpos && rpos->mss)
|
|
// use minimum MSS of two ends or can fail with "message too long"
|
|
lua_pushf_int(params.L, "tcp_mss", rpos->mss > pos->mss ? pos->mss : rpos->mss);
|
|
else
|
|
// this value should always work
|
|
lua_pushf_global(params.L, "tcp_mss", "DEFAULT_MSS");
|
|
}
|
|
ref_arg = luaL_ref(params.L, LUA_REGISTRYINDEX);
|
|
|
|
ctx->func_n = 1;
|
|
LIST_FOREACH(func, &dp->lua_desync, next)
|
|
{
|
|
ctx->func = func->func;
|
|
desync_instance(func->func, dp->n, ctx->func_n, instance, sizeof(instance));
|
|
ctx->instance = instance;
|
|
|
|
if (!lua_instance_cutoff_check(params.L, ctx, bIncoming))
|
|
{
|
|
range = bIncoming ? &func->range_in : &func->range_out;
|
|
if (check_pos_range(pos, range))
|
|
{
|
|
DLOG("* lua '%s' : %s pos %c%llu %c%llu in range %c%u%c%c%u\n",
|
|
instance, sDirection,
|
|
range->from.mode, pos_get(pos, range->from.mode),
|
|
range->to.mode, pos_get(pos, range->to.mode),
|
|
range->from.mode, range->from.pos,
|
|
range->upper_cutoff ? '<' : '-',
|
|
range->to.mode, range->to.pos);
|
|
if (l7_payload_match(l7payload, func->payload_type))
|
|
{
|
|
DLOG("* lua '%s' : payload_type '%s' satisfy filter\n", instance, l7payload_str(l7payload));
|
|
DLOG("* lua '%s' : desync\n", instance);
|
|
lua_getglobal(params.L, func->func);
|
|
if (!lua_isfunction(params.L, -1))
|
|
{
|
|
lua_pop(params.L, 1);
|
|
DLOG_ERR("desync function '%s' does not exist\n", func->func);
|
|
goto err;
|
|
}
|
|
lua_rawgeti(params.L, LUA_REGISTRYINDEX, params.ref_desync_ctx);
|
|
lua_rawgeti(params.L, LUA_REGISTRYINDEX, ref_arg);
|
|
lua_pushf_args(params.L, &func->args, -1, true);
|
|
lua_pushf_str(params.L, "func", func->func);
|
|
lua_pushf_int(params.L, "func_n", ctx->func_n);
|
|
lua_pushf_str(params.L, "func_instance", instance);
|
|
|
|
// prevent use of desync ctx object outside of function call
|
|
ctx->valid = true;
|
|
status = lua_pcall(params.L, 2, LUA_MULTRET, 0);
|
|
ctx->valid = false;
|
|
|
|
if (status)
|
|
{
|
|
lua_dlog_error();
|
|
goto err;
|
|
}
|
|
if (!desync_get_result(&verdict_func))
|
|
goto err;
|
|
|
|
verdict = verdict_aggregate(verdict, verdict_func);
|
|
}
|
|
else
|
|
DLOG("* lua '%s' : payload_type '%s' does not satisfy filter\n", instance, l7payload_str(l7payload));
|
|
}
|
|
else
|
|
DLOG("* lua '%s' : %s pos %c%llu %c%llu out of range %c%u%c%c%u\n",
|
|
instance, sDirection,
|
|
range->from.mode, pos_get(pos, range->from.mode),
|
|
range->to.mode, pos_get(pos, range->to.mode),
|
|
range->from.mode, range->from.pos,
|
|
range->upper_cutoff ? '<' : '-',
|
|
range->to.mode, range->to.pos);
|
|
}
|
|
if (ctx->cancel) break;
|
|
ctx->func_n++;
|
|
}
|
|
}
|
|
|
|
if ((verdict & VERDICT_MASK)==VERDICT_MODIFY)
|
|
{
|
|
// use same memory buffer to reduce memory copying
|
|
// packet size cannot grow
|
|
sockaddr_in46 sa;
|
|
|
|
lua_rawgeti(params.L, LUA_REGISTRYINDEX, ref_arg);
|
|
lua_getfield(params.L, -1, "dis");
|
|
if (lua_type(params.L, -1) != LUA_TTABLE)
|
|
{
|
|
lua_pop(params.L, 2);
|
|
DLOG_ERR("dissect data is bad. VERDICT_MODIFY cancel.\n");
|
|
goto err;
|
|
}
|
|
else
|
|
{
|
|
b = lua_reconstruct_dissect(params.L, -1, mod_pkt, len_mod_pkt, false, false, IPPROTO_NONE, !!(verdict & VERDICT_PRESERVE_NEXT));
|
|
lua_pop(params.L, 2);
|
|
if (!b)
|
|
{
|
|
DLOG_ERR("failed to reconstruct packet after VERDICT_MODIFY\n");
|
|
verdict = VERDICT_PASS;
|
|
goto ex;
|
|
}
|
|
DLOG("reconstructed packet due to VERDICT_MODIFY. size %zu => %zu\n", dis->len_pkt, *len_mod_pkt);
|
|
// no need to recalc sum after reconstruct
|
|
verdict |= VERDICT_NOCSUM;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
DLOG("no lua functions in this profile\n");
|
|
ex:
|
|
luaL_unref(params.L, LUA_REGISTRYINDEX, ref_arg);
|
|
LUA_STACK_GUARD_LEAVE(params.L, 0)
|
|
return verdict;
|
|
err:
|
|
DLOG_ERR("desync ERROR. passing packet unmodified.\n");
|
|
// do not do anything with the packet on error
|
|
verdict = VERDICT_PASS;
|
|
goto ex;
|
|
}
|
|
|
|
|
|
|
|
static void setup_direction(
|
|
const struct dissect *dis,
|
|
bool bReverseFixed,
|
|
struct sockaddr_storage *src,
|
|
struct sockaddr_storage *dst,
|
|
const struct in_addr **sdip4,
|
|
const struct in6_addr **sdip6,
|
|
uint16_t *sdport)
|
|
{
|
|
extract_endpoints(dis->ip, dis->ip6, dis->tcp, dis->udp, src, dst);
|
|
if (dis->ip6)
|
|
{
|
|
*sdip4 = NULL;
|
|
*sdip6 = bReverseFixed ? &dis->ip6->ip6_src : &dis->ip6->ip6_dst;
|
|
}
|
|
else if (dis->ip)
|
|
{
|
|
*sdip6 = NULL;
|
|
*sdip4 = bReverseFixed ? &dis->ip->ip_src : &dis->ip->ip_dst;
|
|
}
|
|
else
|
|
{
|
|
// should never happen
|
|
*sdip6 = NULL; *sdip4 = NULL; *sdport = 0;
|
|
return;
|
|
}
|
|
*sdport = saport((struct sockaddr *)((bReverseFixed ^ params.server) ? src : dst));
|
|
|
|
if (params.debug)
|
|
{
|
|
char ip[40];
|
|
ntopa46(*sdip4, *sdip6, ip, sizeof(ip));
|
|
DLOG("%s mode desync profile/ipcache search target ip=%s port=%u\n", params.server ? "server" : "client", ip, *sdport);
|
|
}
|
|
}
|
|
|
|
static void dp_changed(t_ctrack *ctrack)
|
|
{
|
|
if (ctrack)
|
|
{
|
|
if (ctrack->b_lua_in_cutoff)
|
|
{
|
|
DLOG("clearing lua in cutoff because of profile change\n");
|
|
ctrack->b_lua_in_cutoff = false;
|
|
}
|
|
if (ctrack->b_lua_out_cutoff)
|
|
{
|
|
DLOG("clearing lua out cutoff because of profile change\n");
|
|
ctrack->b_lua_out_cutoff = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
struct play_state
|
|
{
|
|
const struct dissect *dis;
|
|
struct desync_profile *dp;
|
|
const t_ctrack_positions *tpos;
|
|
t_ctrack *ctrack, *ctrack_replay;
|
|
struct sockaddr_storage src, dst;
|
|
const struct in_addr *sdip4;
|
|
const struct in6_addr *sdip6;
|
|
uint16_t sdport;
|
|
uint8_t verdict;
|
|
char host[256];
|
|
t_l7proto l7proto;
|
|
t_l7payload l7payload;
|
|
const char *ssid;
|
|
bool bReverse, bReverseFixed, bHaveHost;
|
|
};
|
|
static bool play_prolog(
|
|
struct play_state *ps,
|
|
const struct dissect *dis,
|
|
const t_ctrack_positions *tpos,
|
|
bool bReplay,
|
|
const char *ifin, const char *ifout)
|
|
{
|
|
ps->verdict = VERDICT_PASS;
|
|
|
|
// additional safety check
|
|
if (!!dis->ip == !!dis->ip6) return false;
|
|
|
|
ps->dis = dis;
|
|
ps->tpos = tpos;
|
|
ps->dp = NULL;
|
|
ps->ctrack = ps->ctrack_replay = NULL;
|
|
ps->bReverse = ps->bReverseFixed = ps->bHaveHost = false;
|
|
ps->l7proto = L7_UNKNOWN;
|
|
ps->l7payload = dis->len_payload ? L7P_UNKNOWN : L7P_EMPTY;
|
|
ps->ssid = NULL;
|
|
|
|
const char *ifname;
|
|
|
|
if (bReplay)
|
|
{
|
|
// in replay mode conntrack_replay is not NULL and ctrack is NULL
|
|
|
|
//ConntrackPoolDump(¶ms.conntrack);
|
|
if (!ConntrackPoolDoubleSearch(¶ms.conntrack, dis, &ps->ctrack_replay, &ps->bReverse) || ps->bReverse)
|
|
return false;
|
|
ps->bReverseFixed = ps->bReverse ^ params.server;
|
|
setup_direction(dis, ps->bReverseFixed, &ps->src, &ps->dst, &ps->sdip4, &ps->sdip6, &ps->sdport);
|
|
|
|
ifname = ps->bReverse ? ifin : ifout;
|
|
#ifdef HAS_FILTER_SSID
|
|
ps->ssid = wlan_ssid_search_ifname(ifname);
|
|
if (ps->ssid) DLOG("found ssid for %s : %s\n", ifname, ps->ssid);
|
|
#endif
|
|
ps->l7proto = ps->ctrack_replay->l7proto;
|
|
ps->dp = ps->ctrack_replay->dp;
|
|
if (ps->dp)
|
|
DLOG("using cached desync profile %u (%s)\n", ps->dp->n, PROFILE_NAME(ps->dp));
|
|
else if (!ps->ctrack_replay->dp_search_complete)
|
|
{
|
|
ps->dp = ps->ctrack_replay->dp = dp_find(¶ms.desync_profiles, dis->proto, ps->sdip4, ps->sdip6, NULL, NULL, ps->sdport, 0xFF, 0xFF, ps->ctrack_replay->hostname, ps->ctrack_replay->hostname_is_ip, ps->l7proto, ps->ssid, NULL, NULL, NULL);
|
|
ps->ctrack_replay->dp_search_complete = true;
|
|
}
|
|
if (!ps->dp)
|
|
{
|
|
DLOG("matching desync profile not found\n");
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// in real mode ctrack may be NULL or not NULL, conntrack_replay is equal to ctrack
|
|
|
|
if (!params.ctrack_disable)
|
|
{
|
|
ConntrackPoolPurge(¶ms.conntrack);
|
|
if (ConntrackPoolFeed(¶ms.conntrack, dis, &ps->ctrack, &ps->bReverse))
|
|
{
|
|
ps->dp = ps->ctrack->dp;
|
|
ps->ctrack_replay = ps->ctrack;
|
|
}
|
|
}
|
|
// in absence of conntrack guess direction by presence of interface names. won't work on BSD
|
|
ps->bReverseFixed = ps->ctrack ? (ps->bReverse ^ params.server) : (ps->bReverse = ifin && *ifin && (!ifout || !*ifout));
|
|
setup_direction(dis, ps->bReverseFixed, &ps->src, &ps->dst, &ps->sdip4, &ps->sdip6, &ps->sdport);
|
|
ifname = ps->bReverse ? ifin : ifout;
|
|
#ifdef HAS_FILTER_SSID
|
|
ps->ssid = wlan_ssid_search_ifname(ifname);
|
|
if (ps->ssid) DLOG("found ssid for %s : %s\n", ifname, ps->ssid);
|
|
#endif
|
|
if (ps->ctrack) ps->l7proto = ps->ctrack->l7proto;
|
|
if (ps->dp)
|
|
DLOG("using cached desync profile %u (%s)\n", ps->dp->n, PROFILE_NAME(ps->dp));
|
|
else if (!ps->ctrack || !ps->ctrack->dp_search_complete)
|
|
{
|
|
const char *hostname = NULL;
|
|
bool hostname_is_ip = false;
|
|
if (ps->ctrack)
|
|
{
|
|
hostname = ps->ctrack->hostname;
|
|
hostname_is_ip = ps->ctrack->hostname_is_ip;
|
|
if (!hostname && !ps->bReverse)
|
|
{
|
|
if (ipcache_get_hostname(ps->sdip4, ps->sdip6, ps->host, sizeof(ps->host), &hostname_is_ip) && *ps->host)
|
|
if (!(hostname = ps->ctrack->hostname = strdup(ps->host)))
|
|
DLOG_ERR("strdup(host): out of memory\n");
|
|
}
|
|
}
|
|
ps->dp = dp_find(¶ms.desync_profiles, dis->proto, ps->sdip4, ps->sdip6, NULL, NULL, ps->sdport, 0xFF, 0xFF, hostname, hostname_is_ip, ps->l7proto, ps->ssid, NULL, NULL, NULL);
|
|
if (ps->ctrack)
|
|
{
|
|
ps->ctrack->dp = ps->dp;
|
|
ps->ctrack->dp_search_complete = true;
|
|
}
|
|
}
|
|
if (!ps->dp)
|
|
{
|
|
DLOG("matching desync profile not found\n");
|
|
return false;
|
|
}
|
|
|
|
HostFailPoolPurgeRateLimited(&ps->dp->hostlist_auto_fail_counters, &ps->dp->hostlist_auto_last_purge);
|
|
//ConntrackPoolDump(¶ms.conntrack);
|
|
|
|
if (ps->bReverseFixed)
|
|
{
|
|
if (ps->ctrack && !ps->ctrack->incoming_ttl)
|
|
{
|
|
ps->ctrack->incoming_ttl = ttl46(dis->ip, dis->ip6);
|
|
DLOG("incoming TTL %u\n", ps->ctrack->incoming_ttl);
|
|
}
|
|
ipcache_update_ttl(ps->ctrack, ps->sdip4, ps->sdip6, ifin);
|
|
}
|
|
else
|
|
ipcache_get_ttl(ps->ctrack, ps->sdip4, ps->sdip6, ifout);
|
|
} // !replay
|
|
|
|
if (!tpos && ps->ctrack_replay) ps->tpos=&ps->ctrack_replay->pos;
|
|
|
|
return true;
|
|
}
|
|
static bool dp_rediscovery(struct play_state *ps)
|
|
{
|
|
bool bHostIsIp = false;
|
|
|
|
if (ps->bHaveHost)
|
|
{
|
|
bHostIsIp = strip_host_to_ip(ps->host);
|
|
DLOG("hostname: %s\n", ps->host);
|
|
}
|
|
|
|
bool bDiscoveredL7;
|
|
if (ps->ctrack_replay)
|
|
{
|
|
if ((bDiscoveredL7 = !ps->ctrack_replay->l7proto_discovered && ps->ctrack_replay->l7proto != L7_UNKNOWN))
|
|
ps->ctrack_replay->l7proto_discovered = true;
|
|
}
|
|
else
|
|
bDiscoveredL7 = ps->l7proto != L7_UNKNOWN;
|
|
if (bDiscoveredL7) DLOG("discovered l7 protocol\n");
|
|
|
|
bool bDiscoveredHostname = ps->bHaveHost && !(ps->ctrack_replay && ps->ctrack_replay->hostname_discovered);
|
|
if (bDiscoveredHostname)
|
|
{
|
|
DLOG("discovered hostname\n");
|
|
if (ps->ctrack_replay)
|
|
{
|
|
free(ps->ctrack_replay->hostname);
|
|
ps->ctrack_replay->hostname = strdup(ps->host);
|
|
ps->ctrack_replay->hostname_is_ip = bHostIsIp;
|
|
if (!ps->ctrack_replay->hostname)
|
|
{
|
|
DLOG_ERR("hostname dup : out of memory");
|
|
return false;
|
|
}
|
|
ps->ctrack_replay->hostname_discovered = true;
|
|
if (!ipcache_put_hostname(ps->sdip4, ps->sdip6, ps->host, bHostIsIp))
|
|
return false;
|
|
}
|
|
}
|
|
bool bCheckDone, bCheckResult, bCheckExcluded;
|
|
if (ps->ctrack_replay)
|
|
{
|
|
bCheckDone = ps->ctrack_replay->bCheckDone;
|
|
bCheckResult = ps->ctrack_replay->bCheckResult;
|
|
bCheckExcluded = ps->ctrack_replay->bCheckExcluded;
|
|
}
|
|
else
|
|
bCheckDone = bCheckResult = bCheckExcluded = false;
|
|
if (bDiscoveredL7 || bDiscoveredHostname)
|
|
{
|
|
struct desync_profile *dp_prev = ps->dp;
|
|
// search for desync profile again. it may have changed.
|
|
ps->dp = dp_find(¶ms.desync_profiles, ps->dis->proto, ps->sdip4, ps->sdip6, NULL, NULL, ps->sdport, 0xFF, 0xFF,
|
|
ps->ctrack_replay ? ps->ctrack_replay->hostname : ps->bHaveHost ? ps->host : NULL,
|
|
ps->ctrack_replay ? ps->ctrack_replay->hostname_is_ip : bHostIsIp,
|
|
ps->l7proto, ps->ssid,
|
|
&bCheckDone, &bCheckResult, &bCheckExcluded);
|
|
if (ps->ctrack_replay)
|
|
{
|
|
ps->ctrack_replay->dp = ps->dp;
|
|
ps->ctrack_replay->dp_search_complete = true;
|
|
ps->ctrack_replay->bCheckDone = bCheckDone;
|
|
ps->ctrack_replay->bCheckResult = bCheckResult;
|
|
ps->ctrack_replay->bCheckExcluded = bCheckExcluded;
|
|
}
|
|
if (!ps->dp) return false;
|
|
if (ps->dp != dp_prev)
|
|
{
|
|
dp_changed(ps->ctrack_replay);
|
|
DLOG("desync profile changed by revealed l7 protocol or hostname !\n");
|
|
}
|
|
}
|
|
if (ps->bHaveHost && !PROFILE_HOSTLISTS_EMPTY(ps->dp))
|
|
{
|
|
if (!bCheckDone)
|
|
{
|
|
bCheckResult = HostlistCheck(ps->dp, ps->host, bHostIsIp, &bCheckExcluded, false);
|
|
bCheckDone = true;
|
|
if (ps->ctrack_replay)
|
|
{
|
|
ps->ctrack_replay->bCheckDone = bCheckDone;
|
|
ps->ctrack_replay->bCheckResult = bCheckResult;
|
|
ps->ctrack_replay->bCheckExcluded = bCheckExcluded;
|
|
}
|
|
}
|
|
if (ps->ctrack_replay)
|
|
{
|
|
if (bCheckResult)
|
|
{
|
|
if (ps->dis->proto==IPPROTO_TCP)
|
|
ctrack_stop_retrans_counter(ps->ctrack_replay);
|
|
}
|
|
else
|
|
{
|
|
ps->ctrack_replay->hostname_ah_check = ps->dp->hostlist_auto && !bCheckExcluded;
|
|
if (ps->dis->proto==IPPROTO_TCP && !ps->ctrack_replay->hostname_ah_check)
|
|
ctrack_stop_retrans_counter(ps->ctrack_replay);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ps->ctrack_replay && ps->dis->proto==IPPROTO_UDP)
|
|
process_udp_fail(ps->ctrack_replay, ps->tpos, (struct sockaddr*)&ps->src);
|
|
|
|
if (bCheckDone && !bCheckResult)
|
|
{
|
|
DLOG("not applying tampering because of previous negative hostlist check\n");
|
|
return false;
|
|
}
|
|
|
|
if (params.debug)
|
|
{
|
|
char s1[48], s2[48];
|
|
ntop46_port((struct sockaddr *)&ps->src, s1, sizeof(s1));
|
|
ntop46_port((struct sockaddr *)&ps->dst, s2, sizeof(s2));
|
|
DLOG("dpi desync src=%s dst=%s track_direction=%s fixed_direction=%s connection_proto=%s payload_type=%s\n",
|
|
s1, s2, ps->bReverse ? "in" : "out", ps->bReverseFixed ? "in" : "out", l7proto_str(ps->l7proto), l7payload_str(ps->l7payload));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static uint8_t dpi_desync_tcp_packet_play(
|
|
unsigned int replay_piece, unsigned int replay_piece_count, size_t reasm_offset,
|
|
uint32_t fwmark,
|
|
const char *ifin, const char *ifout,
|
|
const t_ctrack_positions *tpos,
|
|
const struct dissect *dis,
|
|
uint8_t *mod_pkt, size_t *len_mod_pkt)
|
|
{
|
|
struct play_state ps;
|
|
|
|
if (!play_prolog(&ps, dis, tpos, !!replay_piece_count, ifin, ifout))
|
|
return ps.verdict;
|
|
|
|
uint32_t desync_fwmark = fwmark | params.desync_fwmark;
|
|
const uint8_t *rdata_payload = dis->data_payload;
|
|
size_t rlen_payload = dis->len_payload;
|
|
|
|
if (ps.bReverse)
|
|
{
|
|
// protocol detection
|
|
if (!(dis->tcp->th_flags & TH_SYN) && dis->len_payload)
|
|
{
|
|
t_protocol_probe testers[] = {
|
|
{L7P_TLS_SERVER_HELLO,L7_TLS,IsTLSServerHelloPartial,false},
|
|
{L7P_HTTP_REPLY,L7_HTTP,IsHttpReply,false},
|
|
{L7P_XMPP_STREAM,L7_XMPP,IsXMPPStream,false},
|
|
{L7P_XMPP_PROCEED,L7_XMPP,IsXMPPProceedTLS,false},
|
|
{L7P_XMPP_FEATURES,L7_XMPP,IsXMPPFeatures,false}
|
|
};
|
|
protocol_probe(testers, sizeof(testers) / sizeof(*testers), dis->data_payload, dis->len_payload, ps.ctrack, &ps.l7proto, &ps.l7payload);
|
|
|
|
if (ps.l7payload==L7P_TLS_SERVER_HELLO)
|
|
TLSDebug(dis->data_payload, dis->len_payload);
|
|
}
|
|
|
|
// process reply packets for auto hostlist mode
|
|
// by looking at RSTs or HTTP replies we decide whether original request looks like DPI blocked
|
|
// we only process first-sequence replies. do not react to subsequent redirects or RSTs
|
|
if (!params.server && ps.ctrack && ps.ctrack->hostname_ah_check && !ps.ctrack->failure_detect_finalized && ps.dp->hostlist_auto_incoming_maxseq)
|
|
{
|
|
uint32_t rseq = ps.ctrack->pos.server.seq_last - ps.ctrack->pos.server.seq0;
|
|
if (rseq)
|
|
{
|
|
char client_ip_port[48];
|
|
fill_client_ip_port((struct sockaddr*)&ps.dst, client_ip_port, sizeof(client_ip_port));
|
|
if (seq_within(ps.ctrack->pos.server.seq_last, ps.ctrack->pos.server.seq0 + 1, ps.ctrack->pos.server.seq0 + ps.dp->hostlist_auto_incoming_maxseq))
|
|
{
|
|
bool bFail = false;
|
|
|
|
if (ps.dis->tcp->th_flags & TH_RST)
|
|
{
|
|
DLOG("incoming RST detected for hostname %s rseq %u\n", ps.ctrack->hostname, rseq);
|
|
HOSTLIST_DEBUGLOG_APPEND("%s : profile %u (%s) : client %s : proto %s : rseq %u : incoming RST", ps.ctrack->hostname, ps.ctrack->dp->n, PROFILE_NAME(ps.dp), client_ip_port, l7proto_str(ps.l7proto), rseq);
|
|
bFail = true;
|
|
}
|
|
else if (dis->len_payload && ps.l7payload == L7P_HTTP_REPLY)
|
|
{
|
|
DLOG("incoming HTTP reply detected for hostname %s rseq %u\n", ps.ctrack->hostname, rseq);
|
|
bFail = HttpReplyLooksLikeDPIRedirect(dis->data_payload, dis->len_payload, ps.ctrack->hostname);
|
|
if (bFail)
|
|
{
|
|
DLOG("redirect to another domain detected. possibly DPI redirect.\n");
|
|
HOSTLIST_DEBUGLOG_APPEND("%s : profile %u (%s) : client %s : proto %s : rseq %u : redirect to another domain", ps.ctrack->hostname, ps.ctrack->dp->n, PROFILE_NAME(ps.dp), client_ip_port, l7proto_str(ps.l7proto), rseq);
|
|
}
|
|
else
|
|
DLOG("local or in-domain redirect detected. it's not a DPI redirect.\n");
|
|
}
|
|
if (bFail)
|
|
{
|
|
auto_hostlist_failed(ps.dp, ps.ctrack->hostname, ps.ctrack->hostname_is_ip, client_ip_port, ps.l7proto);
|
|
ps.ctrack->failure_detect_finalized = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// incoming_maxseq exceeded. treat connection as successful
|
|
DLOG("incoming rseq %u > %u\n",rseq,ps.dp->hostlist_auto_incoming_maxseq);
|
|
auto_hostlist_reset_fail_counter(ps.dp, ps.ctrack->hostname, client_ip_port, ps.l7proto);
|
|
ps.ctrack->failure_detect_finalized = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// not reverse
|
|
else if (!(dis->tcp->th_flags & TH_SYN) && dis->len_payload)
|
|
{
|
|
struct blob_collection_head *fake;
|
|
uint8_t *p, *phost = NULL;
|
|
int i;
|
|
|
|
if (replay_piece_count)
|
|
{
|
|
rdata_payload = ps.ctrack_replay->reasm_client.packet;
|
|
rlen_payload = ps.ctrack_replay->reasm_client.size_present;
|
|
}
|
|
else if (reasm_client_feed(ps.ctrack, IPPROTO_TCP, dis->data_payload, dis->len_payload))
|
|
{
|
|
rdata_payload = ps.ctrack->reasm_client.packet;
|
|
rlen_payload = ps.ctrack->reasm_client.size_present;
|
|
}
|
|
|
|
process_retrans_fail(ps.ctrack, dis, (struct sockaddr*)&ps.src, ifin);
|
|
|
|
// do not detect payload if reasm is in progress
|
|
if (!ps.ctrack_replay || ReasmIsEmpty(&ps.ctrack_replay->reasm_client))
|
|
{
|
|
t_protocol_probe testers[] = {
|
|
{L7P_TLS_CLIENT_HELLO,L7_TLS,IsTLSClientHelloPartial},
|
|
{L7P_HTTP_REQ,L7_HTTP,IsHttp,false},
|
|
{L7P_XMPP_STREAM,L7_XMPP,IsXMPPStream,false},
|
|
{L7P_XMPP_STARTTLS,L7_XMPP,IsXMPPStartTLS,false}
|
|
};
|
|
protocol_probe(testers, sizeof(testers) / sizeof(*testers), rdata_payload, rlen_payload, ps.ctrack_replay, &ps.l7proto, &ps.l7payload);
|
|
|
|
if (ps.l7payload==L7P_UNKNOWN)
|
|
{
|
|
// this is special type. detection requires AES and can be successful only for the first data packet. no reason to AES every packet
|
|
if (ps.tpos && (ps.tpos->client.seq_last - ps.tpos->client.seq0)==1)
|
|
{
|
|
t_protocol_probe testers[] = {
|
|
{L7P_MTPROTO_INITIAL,L7_MTPROTO,IsMTProto}
|
|
};
|
|
protocol_probe(testers, sizeof(testers) / sizeof(*testers), rdata_payload, rlen_payload, ps.ctrack_replay, &ps.l7proto, &ps.l7payload);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ps.l7payload==L7P_HTTP_REQ)
|
|
{
|
|
ps.bHaveHost = HttpExtractHost(rdata_payload, rlen_payload, ps.host, sizeof(ps.host));
|
|
}
|
|
else if (ps.l7payload==L7P_TLS_CLIENT_HELLO || ps.l7proto==L7_TLS && ps.l7payload==L7P_UNKNOWN && ps.ctrack_replay && !ReasmIsEmpty(&ps.ctrack_replay->reasm_client))
|
|
{
|
|
ps.l7payload = L7P_TLS_CLIENT_HELLO;
|
|
|
|
bool bReqFull = IsTLSRecordFull(rdata_payload, rlen_payload);
|
|
DLOG(bReqFull ? "TLS client hello is FULL\n" : "TLS client hello is PARTIAL\n");
|
|
|
|
if (bReqFull) TLSDebug(rdata_payload, rlen_payload);
|
|
|
|
if (ps.ctrack && !l7_payload_match(ps.l7payload, params.reasm_payload_disable))
|
|
{
|
|
// do not reasm retransmissions
|
|
if (!bReqFull && ReasmIsEmpty(&ps.ctrack->reasm_client) && !is_retransmission(&ps.ctrack->pos.client))
|
|
{
|
|
// do not reconstruct unexpected large payload (they are feeding garbage ?)
|
|
if (!reasm_client_start(ps.ctrack, IPPROTO_TCP, TLSRecordLen(dis->data_payload), TCP_MAX_REASM, dis->data_payload, dis->len_payload))
|
|
goto pass_reasm_cancel;
|
|
}
|
|
|
|
if (!ReasmIsEmpty(&ps.ctrack->reasm_client))
|
|
{
|
|
if (rawpacket_queue(&ps.ctrack->delayed, &ps.dst, fwmark, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload, &ps.ctrack->pos))
|
|
{
|
|
DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ps.ctrack->delayed));
|
|
}
|
|
else
|
|
{
|
|
DLOG_ERR("rawpacket_queue failed !\n");
|
|
goto pass_reasm_cancel;
|
|
}
|
|
if (ReasmIsFull(&ps.ctrack->reasm_client))
|
|
{
|
|
replay_queue(&ps.ctrack->delayed);
|
|
reasm_client_fin(ps.ctrack);
|
|
}
|
|
return VERDICT_DROP;
|
|
}
|
|
}
|
|
ps.bHaveHost = TLSHelloExtractHost(rdata_payload, rlen_payload, ps.host, sizeof(ps.host), true);
|
|
}
|
|
}
|
|
|
|
if (!dp_rediscovery(&ps))
|
|
goto pass_reasm_cancel;
|
|
|
|
ps.verdict = desync(ps.dp, fwmark, ifin, ifout, ps.bReverseFixed, ps.ctrack_replay, tpos, ps.l7payload, ps.l7proto, dis, ps.sdip4, ps.sdip6, ps.sdport, mod_pkt, len_mod_pkt, replay_piece, replay_piece_count, reasm_offset, rdata_payload, rlen_payload, NULL, 0);
|
|
|
|
pass:
|
|
return (!ps.bReverseFixed && (ps.verdict & VERDICT_MASK) == VERDICT_DROP) ? ct_new_postnat_fix(ps.ctrack, dis, mod_pkt, len_mod_pkt) : ps.verdict;
|
|
pass_reasm_cancel:
|
|
reasm_client_cancel(ps.ctrack);
|
|
goto pass;
|
|
}
|
|
|
|
// return : true - should continue, false - should stop with verdict
|
|
static void quic_reasm_cancel(t_ctrack *ctrack, const char *reason)
|
|
{
|
|
reasm_client_cancel(ctrack);
|
|
DLOG("%s\n", reason);
|
|
}
|
|
|
|
static void udp_standard_protocol_probe(const uint8_t *data_payload, size_t len_payload, t_ctrack *ctrack, t_l7proto *l7proto, t_l7payload *l7payload)
|
|
{
|
|
t_protocol_probe testers[] = {
|
|
{L7P_QUIC_INITIAL,L7_QUIC,IsQUICInitial,false},
|
|
{L7P_DISCORD_IP_DISCOVERY,L7_DISCORD,IsDiscordIpDiscoveryRequest,false},
|
|
{L7P_STUN,L7_STUN,IsStunMessage,false},
|
|
{L7P_DNS_QUERY,L7_DNS,IsDNSQuery,false},
|
|
{L7P_DNS_RESPONSE,L7_DNS,IsDNSResponse,false},
|
|
{L7P_DHT,L7_DHT,IsDht,false},
|
|
{L7P_DTLS_CLIENT_HELLO,L7_DTLS,IsDTLSClientHello,false},
|
|
{L7P_DTLS_SERVER_HELLO,L7_DTLS,IsDTLSServerHello,false},
|
|
{L7P_WIREGUARD_INITIATION,L7_WIREGUARD,IsWireguardHandshakeInitiation,false},
|
|
{L7P_WIREGUARD_RESPONSE,L7_WIREGUARD,IsWireguardHandshakeResponse,false},
|
|
{L7P_WIREGUARD_COOKIE,L7_WIREGUARD,IsWireguardHandshakeCookie,false},
|
|
{L7P_WIREGUARD_KEEPALIVE,L7_WIREGUARD,IsWireguardKeepalive,false},
|
|
{L7P_WIREGUARD_DATA,L7_WIREGUARD,IsWireguardData,true}};
|
|
|
|
protocol_probe(testers, sizeof(testers) / sizeof(*testers), data_payload, len_payload, ctrack, l7proto, l7payload);
|
|
}
|
|
|
|
static const uint8_t *dns_extract_name(const uint8_t *a, const uint8_t *b, const uint8_t *e, char *name, size_t name_size)
|
|
{
|
|
size_t nl, off;
|
|
const uint8_t *p;
|
|
bool bptr = (*a & 0xC0)==0xC0;
|
|
|
|
if (bptr)
|
|
{
|
|
// name pointer
|
|
off = (*a & 0x3F)<<8 | a[1];
|
|
p = b + off;
|
|
}
|
|
else
|
|
// real name
|
|
p = a;
|
|
|
|
if (p>=e) return NULL;
|
|
for (nl=0; *p ;)
|
|
{
|
|
if ((p+*p+1)>=e || (*p+1)>=(name_size-nl)) return NULL;
|
|
if (nl) name[nl++] = '.';
|
|
memcpy(name + nl, p + 1, *p);
|
|
nl += *p;
|
|
p += *p + 1;
|
|
}
|
|
name[nl] = 0;
|
|
return bptr ? a+2 : p+1;
|
|
}
|
|
static bool feed_dns_response(const uint8_t *a, size_t len)
|
|
{
|
|
if (!params.cache_hostname) return true;
|
|
|
|
// check of minimum header length and response flag
|
|
uint16_t k, off, dlen, qcount = a[4]<<8 | a[5], acount = a[6]<<8 | a[7];
|
|
char s_ip[40];
|
|
const uint8_t *b = a, *p;
|
|
const uint8_t *e = b + len;
|
|
size_t nl;
|
|
char name[256] = "";
|
|
|
|
if (len<12 || !(a[2]&0x80)) return false;
|
|
a+=12; len-=12;
|
|
for(k=0;k<qcount;k++)
|
|
{
|
|
// remember original query name
|
|
if (!(p = dns_extract_name(a, b, e, name, sizeof(name)))) return false;
|
|
len -= p-a;
|
|
// must be A or AAAA query. others are not interesting
|
|
if ((len<4) || p[0] || p[1]!=1 && p[1]!=28 || p[2] || p[3]!=1) return false;
|
|
// skip type, class
|
|
a=p+4; len-=4;
|
|
}
|
|
if (!*name) return false;
|
|
for(k=0;k<acount;k++)
|
|
{
|
|
// 11 higher bits indicate pointer
|
|
if (len<12 || (*a & 0xC0)!=0xC0) return false;
|
|
|
|
dlen = a[10]<<8 | a[11];
|
|
if (len<(dlen+12)) return false;
|
|
if (a[4]==0 && a[5]==1 && a[2]==0) // IN class and higher byte of type = 0
|
|
{
|
|
switch(a[3])
|
|
{
|
|
case 1: // A
|
|
if (dlen!=4) break;
|
|
if (params.debug && inet_ntop(AF_INET, a+12, s_ip, sizeof(s_ip)))
|
|
DLOG("DNS response : %s\n", s_ip);
|
|
ipcache_put_hostname((struct in_addr *)(a+12), NULL, name, false);
|
|
break;
|
|
case 28: // AAAA
|
|
if (dlen!=16) break;
|
|
if (params.debug && inet_ntop(AF_INET6, a+12, s_ip, sizeof(s_ip)))
|
|
DLOG("DNS response : %s\n", s_ip);
|
|
ipcache_put_hostname(NULL, (struct in6_addr *)(a+12), name, false);
|
|
break;
|
|
}
|
|
}
|
|
len -= 12+dlen; a += 12+dlen;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static uint8_t dpi_desync_udp_packet_play(
|
|
unsigned int replay_piece, unsigned int replay_piece_count, size_t reasm_offset,
|
|
uint32_t fwmark,
|
|
const char *ifin, const char *ifout,
|
|
const t_ctrack_positions *tpos,
|
|
const struct dissect *dis,
|
|
uint8_t *mod_pkt, size_t *len_mod_pkt)
|
|
{
|
|
struct play_state ps;
|
|
|
|
if (!play_prolog(&ps, dis, tpos, !!replay_piece_count, ifin, ifout))
|
|
return ps.verdict;
|
|
|
|
uint32_t desync_fwmark = fwmark | params.desync_fwmark;
|
|
uint8_t defrag[UDP_MAX_REASM];
|
|
uint8_t *data_decrypt = NULL;
|
|
size_t len_decrypt = 0;
|
|
|
|
if (dis->len_payload)
|
|
{
|
|
udp_standard_protocol_probe(dis->data_payload, dis->len_payload, ps.ctrack, &ps.l7proto, &ps.l7payload);
|
|
|
|
if (!ps.bReverse)
|
|
{
|
|
if (ps.l7payload==L7P_QUIC_INITIAL)
|
|
{
|
|
uint8_t clean[UDP_MAX_REASM], *pclean;
|
|
size_t clean_len;
|
|
|
|
if (replay_piece_count)
|
|
{
|
|
clean_len = ps.ctrack_replay->reasm_client.size_present;
|
|
pclean = ps.ctrack_replay->reasm_client.packet;
|
|
}
|
|
else
|
|
{
|
|
clean_len = sizeof(clean);
|
|
pclean = QUICDecryptInitial(dis->data_payload, dis->len_payload, clean, &clean_len) ? clean : NULL;
|
|
}
|
|
if (pclean)
|
|
{
|
|
bool reasm_disable = l7_payload_match(ps.l7payload, params.reasm_payload_disable);
|
|
if (ps.ctrack && !reasm_disable && !ReasmIsEmpty(&ps.ctrack->reasm_client))
|
|
{
|
|
if (ReasmHasSpace(&ps.ctrack->reasm_client, clean_len))
|
|
{
|
|
reasm_client_feed(ps.ctrack, IPPROTO_UDP, clean, clean_len);
|
|
pclean = ps.ctrack->reasm_client.packet;
|
|
clean_len = ps.ctrack->reasm_client.size_present;
|
|
}
|
|
else
|
|
{
|
|
DLOG("QUIC reasm is too long. cancelling.\n");
|
|
goto pass_reasm_cancel;
|
|
}
|
|
}
|
|
size_t hello_offset, hello_len, defrag_len = sizeof(defrag);
|
|
bool bFull;
|
|
if (QUICDefragCrypto(pclean, clean_len, defrag, &defrag_len, &bFull))
|
|
{
|
|
if (bFull)
|
|
{
|
|
DLOG("QUIC initial contains CRYPTO with full fragment coverage\n");
|
|
|
|
bool bIsHello = IsQUICCryptoHello(defrag, defrag_len, &hello_offset, &hello_len);
|
|
bool bReqFull = bIsHello ? IsTLSHandshakeFull(defrag + hello_offset, hello_len) : false;
|
|
|
|
DLOG(bIsHello ? bReqFull ? "packet contains full TLS ClientHello\n" : "packet contains partial TLS ClientHello\n" : "packet does not contain TLS ClientHello\n");
|
|
|
|
if (bReqFull) TLSDebugHandshake(defrag + hello_offset, hello_len);
|
|
|
|
if (ps.ctrack && !reasm_disable)
|
|
{
|
|
if (bIsHello && !bReqFull && ReasmIsEmpty(&ps.ctrack->reasm_client))
|
|
{
|
|
// preallocate max buffer to avoid reallocs that cause memory copy
|
|
if (!reasm_client_start(ps.ctrack, IPPROTO_UDP, UDP_MAX_REASM, UDP_MAX_REASM, clean, clean_len))
|
|
goto pass_reasm_cancel;
|
|
}
|
|
if (!ReasmIsEmpty(&ps.ctrack->reasm_client))
|
|
{
|
|
if (rawpacket_queue(&ps.ctrack->delayed, &ps.dst, fwmark, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload, &ps.ctrack->pos))
|
|
{
|
|
DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ps.ctrack->delayed));
|
|
}
|
|
else
|
|
{
|
|
DLOG_ERR("rawpacket_queue failed !\n");
|
|
goto pass_reasm_cancel;
|
|
}
|
|
if (bReqFull)
|
|
{
|
|
replay_queue(&ps.ctrack->delayed);
|
|
reasm_client_fin(ps.ctrack);
|
|
}
|
|
return ct_new_postnat_fix(ps.ctrack, dis, mod_pkt, len_mod_pkt);
|
|
}
|
|
}
|
|
|
|
if (bIsHello)
|
|
{
|
|
data_decrypt = defrag + hello_offset;
|
|
len_decrypt = hello_len;
|
|
ps.bHaveHost = TLSHelloExtractHostFromHandshake(data_decrypt, len_decrypt, ps.host, sizeof(ps.host), true);
|
|
}
|
|
else
|
|
{
|
|
quic_reasm_cancel(ps.ctrack, "QUIC initial without ClientHello");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DLOG("QUIC initial contains CRYPTO with partial fragment coverage\n");
|
|
if (ps.ctrack && !reasm_disable)
|
|
{
|
|
if (ReasmIsEmpty(&ps.ctrack->reasm_client))
|
|
{
|
|
// preallocate max buffer to avoid reallocs that cause memory copy
|
|
if (!reasm_client_start(ps.ctrack, IPPROTO_UDP, UDP_MAX_REASM, UDP_MAX_REASM, clean, clean_len))
|
|
goto pass_reasm_cancel;
|
|
}
|
|
if (rawpacket_queue(&ps.ctrack->delayed, &ps.dst, fwmark, desync_fwmark, ifin, ifout, dis->data_pkt, dis->len_pkt, dis->len_payload, &ps.ctrack->pos))
|
|
{
|
|
DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ps.ctrack->delayed));
|
|
}
|
|
else
|
|
{
|
|
DLOG_ERR("rawpacket_queue failed !\n");
|
|
goto pass_reasm_cancel;
|
|
}
|
|
return ct_new_postnat_fix(ps.ctrack, dis, mod_pkt, len_mod_pkt);
|
|
}
|
|
quic_reasm_cancel(ps.ctrack, "QUIC initial fragmented CRYPTO");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// defrag failed
|
|
quic_reasm_cancel(ps.ctrack, "QUIC initial defrag CRYPTO failed");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// decrypt failed
|
|
quic_reasm_cancel(ps.ctrack, "QUIC initial decryption failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ps.l7payload==L7P_DNS_RESPONSE)
|
|
feed_dns_response(dis->data_payload, dis->len_payload);
|
|
} // len_payload
|
|
|
|
reasm_client_cancel(ps.ctrack);
|
|
|
|
if (!dp_rediscovery(&ps))
|
|
goto pass;
|
|
|
|
ps.verdict = desync(ps.dp, fwmark, ifin, ifout, ps.bReverseFixed, ps.ctrack_replay, tpos, ps.l7payload, ps.l7proto, dis, ps.sdip4, ps.sdip6, ps.sdport, mod_pkt, len_mod_pkt, replay_piece, replay_piece_count, reasm_offset, NULL, 0, data_decrypt, len_decrypt);
|
|
|
|
pass:
|
|
return (!ps.bReverse && (ps.verdict & VERDICT_MASK) == VERDICT_DROP) ? ct_new_postnat_fix(ps.ctrack, dis, mod_pkt, len_mod_pkt) : ps.verdict;
|
|
pass_reasm_cancel:
|
|
reasm_client_cancel(ps.ctrack);
|
|
goto pass;
|
|
}
|
|
|
|
// conntrack is supported only for RELATED icmp
|
|
// ip matched in both directions if conntrack is unavailable
|
|
static uint8_t dpi_desync_icmp_packet(
|
|
uint32_t fwmark,
|
|
const char *ifin, const char *ifout,
|
|
const struct dissect *dis,
|
|
uint8_t *mod_pkt, size_t *len_mod_pkt)
|
|
{
|
|
uint8_t verdict = VERDICT_PASS;
|
|
|
|
// additional safety check
|
|
if (!!dis->ip == !!dis->ip6) return verdict;
|
|
|
|
const uint8_t *pkt_attached;
|
|
size_t len_attached;
|
|
const char *ifname;
|
|
struct sockaddr_storage src, dst;
|
|
const char *ssid = NULL;
|
|
struct desync_profile *dp = NULL;
|
|
t_l7payload l7payload = L7P_ICMP;
|
|
t_ctrack *ctrack = NULL;
|
|
bool bReverse, bReverseFixed;
|
|
|
|
extract_endpoints(dis->ip, dis->ip6, NULL, NULL, &src, &dst);
|
|
|
|
switch(dis->icmp->icmp_type)
|
|
{
|
|
case ICMP_DEST_UNREACH:
|
|
case ICMP_TIME_EXCEEDED:
|
|
case ICMP_PARAMETERPROB:
|
|
case ICMP_REDIRECT:
|
|
case ICMP6_DST_UNREACH:
|
|
//case ICMP6_TIME_EXCEEDED: // same as ICMP_TIME_EXCEEDED = 3
|
|
case ICMP6_PACKET_TOO_BIG:
|
|
case ICMP6_PARAM_PROB:
|
|
pkt_attached = dis->data_payload;
|
|
break;
|
|
default:
|
|
pkt_attached = NULL;
|
|
}
|
|
if (pkt_attached)
|
|
{
|
|
struct dissect adis;
|
|
|
|
len_attached = pkt_attached - dis->data_payload + dis->len_payload;
|
|
proto_dissect_l3l4(pkt_attached, len_attached, &adis, true); // dissect without payload length checks - can be partial
|
|
if (!dis->ip && !dis->ip6)
|
|
DLOG("attached packet is invalid\n");
|
|
else
|
|
{
|
|
l7payload = dis->ip ? L7P_IPV4 : L7P_IPV6;
|
|
DLOG("attached packet\n");
|
|
packet_debug(false, &adis);
|
|
if (ConntrackPoolDoubleSearch(¶ms.conntrack, &adis, &ctrack, &bReverse))
|
|
{
|
|
// invert direction. they are answering to this packet
|
|
bReverse = !bReverse;
|
|
DLOG("found conntrack entry. inverted reverse=%u\n",bReverse);
|
|
if (ctrack->dp_search_complete)
|
|
{
|
|
// RELATED icmp processed within base connection profile
|
|
dp = ctrack->dp;
|
|
DLOG("using desync profile %u (%s) from conntrack entry\n", dp->n, PROFILE_NAME(dp));
|
|
}
|
|
}
|
|
else
|
|
DLOG("conntrack entry not found\n");
|
|
}
|
|
}
|
|
|
|
bReverseFixed = ctrack ? (bReverse ^ params.server) : (bReverse = ifin && *ifin && (!ifout || !*ifout));
|
|
|
|
#ifdef HAS_FILTER_SSID
|
|
ifname = bReverse ? ifin : ifout;
|
|
if ((ssid = wlan_ssid_search_ifname(ifname)))
|
|
DLOG("found ssid for %s : %s\n", ifname, ssid);
|
|
else if (!ctrack)
|
|
{
|
|
// we dont know direction for sure
|
|
// search opposite interface
|
|
ifname = bReverse ? ifout : ifin;
|
|
if ((ssid = wlan_ssid_search_ifname(ifname)))
|
|
DLOG("found ssid for %s : %s\n", ifname, ssid);
|
|
}
|
|
#endif
|
|
if (!dp)
|
|
{
|
|
bool hostname_is_ip = false;
|
|
char host[256];
|
|
const char *hostname = NULL;
|
|
if (ctrack && ctrack->hostname)
|
|
{
|
|
hostname = ctrack->hostname;
|
|
hostname_is_ip = ctrack->hostname_is_ip;
|
|
}
|
|
else if (ipcache_get_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, sizeof(host), &hostname_is_ip) && *host ||
|
|
ipcache_get_hostname(dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, host, sizeof(host), &hostname_is_ip) && *host)
|
|
{
|
|
hostname = host;
|
|
}
|
|
|
|
dp = dp_find(
|
|
¶ms.desync_profiles,
|
|
dis->proto,
|
|
dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL,
|
|
dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL,
|
|
0, dis->icmp->icmp_type, dis->icmp->icmp_code,
|
|
hostname, hostname_is_ip,
|
|
L7_UNKNOWN, ssid, NULL, NULL, NULL);
|
|
if (!dp)
|
|
{
|
|
DLOG("matching desync profile not found\n");
|
|
return verdict;
|
|
}
|
|
}
|
|
|
|
const struct in_addr *sdip4;
|
|
const struct in6_addr *sdip6;
|
|
sdip6 = dis->ip6 ? bReverseFixed ? &dis->ip6->ip6_src : &dis->ip6->ip6_dst : NULL;
|
|
sdip4 = dis->ip ? bReverseFixed ? &dis->ip->ip_src : &dis->ip->ip_dst : NULL;
|
|
|
|
verdict = desync(
|
|
dp, fwmark, ifin, ifout, bReverseFixed, ctrack, NULL, l7payload, ctrack ? ctrack->l7proto : L7_UNKNOWN,
|
|
dis, sdip4, sdip6, 0, mod_pkt, len_mod_pkt, 0, 0, 0, NULL, 0, NULL, 0);
|
|
|
|
return verdict;
|
|
}
|
|
|
|
// undissected l4+
|
|
// conntrack is unsupported
|
|
// ip matched in both directions
|
|
static uint8_t dpi_desync_ip_packet(
|
|
uint32_t fwmark,
|
|
const char *ifin, const char *ifout,
|
|
const struct dissect *dis,
|
|
uint8_t *mod_pkt, size_t *len_mod_pkt)
|
|
{
|
|
uint8_t verdict = VERDICT_PASS;
|
|
|
|
// additional safety check
|
|
if (!!dis->ip == !!dis->ip6) return verdict;
|
|
|
|
struct sockaddr_storage src, dst;
|
|
const char *ssid;
|
|
struct desync_profile *dp;
|
|
|
|
extract_endpoints(dis->ip, dis->ip6, NULL, NULL, &src, &dst);
|
|
#ifdef HAS_FILTER_SSID
|
|
if ((ssid = wlan_ssid_search_ifname(ifin)))
|
|
DLOG("found ssid for %s : %s\n", ifin, ssid);
|
|
else
|
|
{
|
|
// we dont know direction for sure
|
|
// search opposite interface
|
|
if ((ssid = wlan_ssid_search_ifname(ifout)))
|
|
DLOG("found ssid for %s : %s\n", ifout, ssid);
|
|
}
|
|
#endif
|
|
|
|
bool hostname_is_ip = false;
|
|
const char *hostname = NULL;
|
|
char host[256];
|
|
if (ipcache_get_hostname(dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL, host, sizeof(host), &hostname_is_ip) && *host ||
|
|
ipcache_get_hostname(dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, host, sizeof(host), &hostname_is_ip) && *host)
|
|
{
|
|
hostname = host;
|
|
}
|
|
dp = dp_find(
|
|
¶ms.desync_profiles,
|
|
dis->proto,
|
|
dis->ip ? &dis->ip->ip_dst : NULL, dis->ip6 ? &dis->ip6->ip6_dst : NULL,
|
|
dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL,
|
|
0, 0xFF, 0xFF,
|
|
hostname, hostname_is_ip,
|
|
L7_UNKNOWN, ssid, NULL, NULL, NULL);
|
|
if (!dp)
|
|
{
|
|
DLOG("matching desync profile not found\n");
|
|
return verdict;
|
|
}
|
|
|
|
bool bReverse = ifin && *ifin && (!ifout || !*ifout);
|
|
|
|
const struct in_addr *sdip4;
|
|
const struct in6_addr *sdip6;
|
|
sdip6 = dis->ip6 ? bReverse ? &dis->ip6->ip6_src : &dis->ip6->ip6_dst : NULL;
|
|
sdip4 = dis->ip ? bReverse ? &dis->ip->ip_src : &dis->ip->ip_dst : NULL;
|
|
|
|
verdict = desync(
|
|
dp, fwmark, ifin, ifout, bReverse, NULL, NULL, L7P_UNKNOWN, L7_UNKNOWN,
|
|
dis, sdip4, sdip6, 0, mod_pkt, len_mod_pkt, 0, 0, 0, NULL, 0, NULL, 0);
|
|
|
|
return verdict;
|
|
}
|
|
|
|
|
|
static uint8_t dpi_desync_packet_play(
|
|
unsigned int replay_piece, unsigned int replay_piece_count, size_t reasm_offset, uint32_t fwmark, const char *ifin, const char *ifout,
|
|
const t_ctrack_positions *tpos,
|
|
const uint8_t *data_pkt, size_t len_pkt,
|
|
uint8_t *mod_pkt, size_t *len_mod_pkt)
|
|
{
|
|
struct dissect dis;
|
|
uint8_t verdict = VERDICT_PASS;
|
|
|
|
// NOTE ! OS can pass wrong checksum to queue. cannot rely on it !
|
|
|
|
proto_dissect_l3l4(data_pkt, len_pkt, &dis, false);
|
|
if (!!dis.ip != !!dis.ip6)
|
|
{
|
|
packet_debug(!!replay_piece_count, &dis);
|
|
// fix csum if unmodified and if OS can pass wrong csum to queue (depends on OS)
|
|
// modified means we have already fixed the checksum or made it invalid intentionally
|
|
// this is the only point we VIOLATE const to fix the checksum in the original buffer to avoid copying to mod_pkt
|
|
switch (dis.proto)
|
|
{
|
|
case IPPROTO_TCP:
|
|
if (dis.tcp)
|
|
{
|
|
verdict = dpi_desync_tcp_packet_play(replay_piece, replay_piece_count, reasm_offset, fwmark, ifin, ifout, tpos, &dis, mod_pkt, len_mod_pkt);
|
|
verdict_tcp_csum_fix(verdict, (struct tcphdr *)dis.tcp, dis.transport_len, dis.ip, dis.ip6);
|
|
}
|
|
break;
|
|
case IPPROTO_UDP:
|
|
if (dis.udp)
|
|
{
|
|
verdict = dpi_desync_udp_packet_play(replay_piece, replay_piece_count, reasm_offset, fwmark, ifin, ifout, tpos, &dis, mod_pkt, len_mod_pkt);
|
|
verdict_udp_csum_fix(verdict, (struct udphdr *)dis.udp, dis.transport_len, dis.ip, dis.ip6);
|
|
}
|
|
break;
|
|
case IPPROTO_ICMP:
|
|
case IPPROTO_ICMPV6:
|
|
if (dis.icmp)
|
|
{
|
|
verdict = dpi_desync_icmp_packet(fwmark, ifin, ifout, &dis, mod_pkt, len_mod_pkt);
|
|
verdict_icmp_csum_fix(verdict, (struct icmp46 *)dis.icmp, dis.transport_len, dis.ip6);
|
|
}
|
|
break;
|
|
default:
|
|
verdict = dpi_desync_ip_packet(fwmark, ifin, ifout, &dis, mod_pkt, len_mod_pkt);
|
|
}
|
|
}
|
|
return verdict;
|
|
}
|
|
uint8_t dpi_desync_packet(uint32_t fwmark, const char *ifin, const char *ifout, const uint8_t *data_pkt, size_t len_pkt, uint8_t *mod_pkt, size_t *len_mod_pkt)
|
|
{
|
|
// NOTE ! OS can pass wrong checksum to queue. cannot rely on it !
|
|
|
|
ipcachePurgeRateLimited(¶ms.ipcache, params.ipcache_lifetime);
|
|
return dpi_desync_packet_play(0, 0, 0, fwmark, ifin, ifout, NULL, data_pkt, len_pkt, mod_pkt, len_mod_pkt);
|
|
}
|
|
|
|
|
|
|
|
static bool replay_queue(struct rawpacket_tailhead *q)
|
|
{
|
|
struct rawpacket *rp;
|
|
size_t offset;
|
|
unsigned int i, count;
|
|
bool b = true;
|
|
uint8_t mod[RECONSTRUCT_MAX_SIZE];
|
|
size_t modlen;
|
|
|
|
for (i = 0, offset = 0, count = rawpacket_queue_count(q); (rp = rawpacket_dequeue(q)); offset += rp->len_payload, rawpacket_free(rp), i++)
|
|
{
|
|
DLOG("REPLAYING delayed packet #%u offset %zu\n", i+1, offset);
|
|
modlen = sizeof(mod);
|
|
uint8_t verdict = dpi_desync_packet_play(i, count, offset, rp->fwmark_orig, rp->ifin, rp->ifout, rp->tpos_present ? &rp->tpos : NULL, rp->packet, rp->len, mod, &modlen);
|
|
switch (verdict & VERDICT_MASK)
|
|
{
|
|
case VERDICT_MODIFY:
|
|
DLOG("SENDING delayed packet #%u modified\n", i+1);
|
|
b &= rawsend((struct sockaddr*)&rp->dst,rp->fwmark,rp->ifout,mod,modlen);
|
|
break;
|
|
case VERDICT_PASS:
|
|
DLOG("SENDING delayed packet #%u unmodified\n", i+1);
|
|
b &= rawsend_rp(rp);
|
|
break;
|
|
case VERDICT_DROP:
|
|
DLOG("DROPPING delayed packet #%u\n", i+1);
|
|
break;
|
|
}
|
|
}
|
|
return b;
|
|
}
|