#define _GNU_SOURCE #include #include #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); if (dis->frag) DLOG("\nIP FRAG off=%u PROTO %s: len=%zu : ", dis->frag_off, s_proto, dis->len_payload); else 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 (!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[INET6_ADDRSTRLEN]; ntopa46(ip, ip6, s, sizeof(s)); if (ipr || ipr6) { char sr[INET6_ADDRSTRLEN]; 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 %zu\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 || !ctrack && 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 false; } if (params.debug) { char s[INET6_ADDRSTRLEN]; ntopa46(a4, a6, s, sizeof(s)); DLOG("ipcache hostname search for %s\n", s); } ip_cache_item *ipc = ipcacheFind(¶ms.ipcache, a4, a6, NULL); if (ipc && ipc->hostname) { if (params.debug) { char s[INET6_ADDRSTRLEN]; 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 *hostname; } 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; if (ctrack->incoming_ttl) { ipc = ipcacheTouch(¶ms.ipcache, a4, a6, iface); if (!ipc) { DLOG_ERR("ipcache: out of memory\n"); return; } if (ipc->ttl != ctrack->incoming_ttl) { DLOG("updated ttl cache\n"); ipc->ttl = ctrack->incoming_ttl; } } else { ipc = ipcacheFind(¶ms.ipcache, a4, a6, iface); if (ipc && 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 = ipcacheFind(¶ms.ipcache, a4, a6, iface); if (ipc && 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->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[INET6_ADDRSTRLEN]; 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)) 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}, {L7P_BT_HANDSHAKE,L7_BT,IsBTHandshake,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,false}, {L7P_HTTP_REQ,L7_HTTP,IsHttp,false}, {L7P_XMPP_STREAM,L7_XMPP,IsXMPPStream,false}, {L7P_XMPP_STARTTLS,L7_XMPP,IsXMPPStartTLS,false}, {L7P_BT_HANDSHAKE,L7_BT,IsBTHandshake,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,false} }; 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 ?) // also do not reconstruct if server window size is low if (!reasm_client_start(ps.ctrack, IPPROTO_TCP, TLSRecordLen(dis->data_payload), TCP_MAX_REASM, dis->data_payload, dis->len_payload)) goto rediscover; } 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, false)) { DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ps.ctrack->delayed)); } else { DLOG_ERR("rawpacket_queue failed !\n"); goto rediscover; } 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); } } // UNSOLVED: if reasm is cancelled all packets except the last are passed as is without lua desync rediscover: 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_UTP_BT_HANDSHAKE,L7_UTP_BT,IsUTP_BTHandshake,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; uint8_t x,y; if (!name_size) return NULL; bptr = (*a & 0xC0)==0xC0; if (bptr) { if (a+1>=e) return NULL; // 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 (nl) { if (nl>=name_size) return NULL; name[nl++] = '.'; } // do not support mixed ptr+real if ((*p & 0xC0) || (p+*p+1)>=e || (*p+1)>=(name_size-nl)) return NULL; for(y=*p++,x=0 ; x=name_size) return NULL; name[nl] = 0; return bptr ? a+2 : p+1; } static bool dns_skip_name(const uint8_t **a, size_t *len) { // 11 higher bits indicate pointer // lazy skip name. mixed compressed/uncompressed names are supported for(;;) { if (*len<2) return false; if ((**a & 0xC0)==0xC0) { // pointer is the end (*a)+=2; (*len)-=2; break; } if (!**a) { // zero length is the end (*a)++; (*len)--; break; } if (*len<(**a+1)) return false; *len-=**a+1; *a+=**a+1; } return true; } 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, typ, off, dlen, qcount = a[4]<<8 | a[5], acount = a[6]<<8 | a[7]; char s_ip[INET6_ADDRSTRLEN]; const uint8_t *b = a, *p; const uint8_t *e = b + len; size_t nl; char name[256] = ""; if (!qcount || len<12 || !(a[2]&0x80)) return false; if (!acount) { DLOG("skipping DNS response without answer\n"); return false; } a+=12; len-=12; for(k=0,*name = 0 ; klen_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 rediscover_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 rediscover_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, false)) { DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ps.ctrack->delayed)); } else { DLOG_ERR("rawpacket_queue failed !\n"); goto rediscover_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 rediscover_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, false)) { DLOG("DELAY desync until reasm is complete (#%u)\n", rawpacket_queue_count(&ps.ctrack->delayed)); } else { DLOG_ERR("rawpacket_queue failed !\n"); goto rediscover_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 // UNSOLVED: if reasm is cancelled all packets except the last are passed as is without lua desync rediscover_cancel: 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.bReverseFixed && (ps.verdict & VERDICT_MASK) == VERDICT_DROP) ? ct_new_postnat_fix(ps.ctrack, dis, mod_pkt, len_mod_pkt) : ps.verdict; } // 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 (!adis.ip && !adis.ip6) DLOG("attached packet is invalid\n"); else { l7payload = adis.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 && ctrack->dp) { // 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) || ipcache_get_hostname(dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, host, sizeof(host), &hostname_is_ip)) { 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 = NULL; 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) || ipcache_get_hostname(dis->ip ? &dis->ip->ip_src : NULL, dis->ip6 ? &dis->ip6->ip6_src : NULL, host, sizeof(host), &hostname_is_ip)) { 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 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); } else 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); } else 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); } else verdict = dpi_desync_ip_packet(fwmark, ifin, ifout, &dis, mod_pkt, len_mod_pkt); } else DLOG("invalid packet - neither ipv4 or ipv6\n"); 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; uint8_t mod[RECONSTRUCT_MAX_SIZE]; size_t modlen; uint32_t seq0; t_ctrack_position *pos; bool b = true, bseq; for (i = 0, offset = 0, count = rawpacket_queue_count(q); (rp = rawpacket_dequeue(q)); rawpacket_free(rp), i++) { // TCP: track reasm_offset using sequence numbers if ((bseq = rp->tpos_present && rp->tpos.ipproto==IPPROTO_TCP)) { pos = rp->server_side ? &rp->tpos.server : &rp->tpos.client; if (i) offset = pos->seq_last - seq0; else seq0 = pos->seq_last; } 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; } if (!bseq) offset += rp->len_payload; } return b; }