TCP Conntrack matching + TCP ports filtering

This commit is contained in:
Vadim Vetrov
2026-01-31 20:01:29 +03:00
parent 06c3447d5e
commit 581364ed9d
11 changed files with 944 additions and 748 deletions

509
src/dpi.c Normal file
View File

@@ -0,0 +1,509 @@
/*
youtubeUnblock - https://github.com/Waujito/youtubeUnblock
Copyright (C) 2024-2025 Vadim Vetrov <vetrovvd@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* dpi.c - Inspects packets for blocked patterns.
* "If you want to bypass the DPI, you should became the DPI"
*/
#define _GNU_SOURCE
#include "types.h" // IWYU pragma: keep
#ifndef KERNEL_SPACE
#include <stdlib.h>
#else
#include "linux/inet.h"
#endif
#include "dpi.h"
#include "config.h"
#include "utils.h"
#include "quic.h"
#include "logging.h"
#include "tls.h"
#include "mangle.h"
int log_packet(const struct parsed_packet *pkt);
#define MAX_FRAGMENTATION_PTS 16
struct fragmentation_points {
size_t payload_points[16];
int used_points;
};
int process_packet(const struct config_t *config, const struct packet_data *pd) {
assert (config);
assert (pd);
struct parsed_packet pkt = {0};
int ret = 0;
pkt.yct = pd->yct;
lgtrace_start();
pkt.raw_payload = pd->payload;
pkt.raw_payload_len = pd->payload_len;
if (pkt.raw_payload_len > MAX_PACKET_SIZE) {
return PKT_ACCEPT;
}
pkt.ipver = netproto_version(pkt.raw_payload, pkt.raw_payload_len);
lgtrace_wr("IPv%d ", pkt.ipver);
pkt.transport_proto = -1;
if (pkt.ipver == IP4VERSION) {
ret = ip4_payload_split((uint8_t *)pkt.raw_payload, pkt.raw_payload_len,
(struct iphdr **)&pkt.iph, &pkt.iph_len,
(uint8_t **)&pkt.ip_payload, &pkt.ip_payload_len);
if (ret < 0)
goto accept;
pkt.transport_proto = pkt.iph->protocol;
}
#ifndef NO_IPV6
else if (pkt.ipver == IP6VERSION && config->use_ipv6) {
ret = ip6_payload_split((uint8_t *)pkt.raw_payload, pkt.raw_payload_len,
(struct ip6_hdr **)&pkt.ip6h, &pkt.iph_len,
(uint8_t **)&pkt.ip_payload, &pkt.ip_payload_len);
if (ret < 0)
goto accept;
pkt.transport_proto = pkt.ip6h->ip6_nxt;
}
#endif
if (pkt.transport_proto == IPPROTO_TCP) {
int ret = tcp_payload_split((uint8_t *)pkt.raw_payload, pkt.raw_payload_len,
NULL, NULL,
(struct tcphdr **)&pkt.tcph, &pkt.tcph_len,
(uint8_t **)&pkt.transport_payload, &pkt.transport_payload_len);
if (ret < 0)
goto accept;
} else if (pkt.transport_proto == IPPROTO_UDP) {
int ret = udp_payload_split((uint8_t *)pkt.raw_payload, pkt.raw_payload_len,
NULL, NULL,
(struct udphdr **)&pkt.udph,
(uint8_t **)&pkt.transport_payload, &pkt.transport_payload_len);
if (ret < 0)
goto accept;
}
if (LOG_LEVEL >= VERBOSE_TRACE) {
log_packet(&pkt);
}
int verdict = PKT_CONTINUE;
ITER_CONFIG_SECTIONS(config, section) {
lgtrace_wr("Section #%d: ", CONFIG_SECTION_NUMBER(section));
switch (pkt.transport_proto) {
case IPPROTO_TCP:
verdict = process_tcp_packet(section, &pkt);
break;
case IPPROTO_UDP:
verdict = process_udp_packet(section, &pkt);
break;
}
if (verdict == PKT_CONTINUE) {
lgtrace_wr("continue_flow");
lgtrace_write();
continue;
}
lgtrace_write();
goto ret_verdict;
}
accept:
verdict = PKT_ACCEPT;
ret_verdict:
switch (verdict) {
case PKT_ACCEPT:
lgtrace_wr("accept");
break;
case PKT_DROP:
lgtrace_wr("drop");
break;
default:
lgtrace_wr("unknown verdict: %d", verdict);
}
lgtrace_end();
return verdict;
}
enum tls_proc_verdict {
TLS_NOT_MATCHED,
TLS_ERROR,
TLS_MATCHED,
};
enum tls_proc_verdict process_tls_packet(const struct section_config_t *section,
const struct parsed_packet *pkt,
struct fragmentation_points *frag_pts);
int perform_attack(const struct section_config_t *section,
const struct parsed_packet *pkt, const struct fragmentation_points *frag_pts);
int process_tcp_packet(const struct section_config_t *section, const struct parsed_packet *pkt) {
assert (section);
assert (pkt);
assert (pkt->transport_proto == IPPROTO_TCP);
uint16_t dport = ntohs(pkt->tcph->dest);
if (section->tcp_dport_range_len) {
int is_dport_matched = 0;
for (int i = 0; i < section->tcp_dport_range_len; i++) {
struct dport_range crange = section->tcp_dport_range[i];
if (dport >= crange.start && dport <= crange.end) {
lgtrace_addp("matched to %d-%d", crange.start, crange.end);
is_dport_matched = 1;
}
}
if (!is_dport_matched) {
return PKT_CONTINUE;
}
} else if (section->dport_filter && dport != 443) {
return PKT_CONTINUE;
}
if (pkt->tcph->syn && section->synfake) {
return send_synfake(section, pkt);
}
if (pkt->tcph->syn)
return PKT_CONTINUE;
int is_matched = 0;
struct fragmentation_points frag_pts = {0};
if (!is_matched && section->tls_enabled) {
enum tls_proc_verdict vrd = process_tls_packet(section, pkt, &frag_pts);
if (vrd == TLS_ERROR) {
return PKT_ACCEPT;
}
if (vrd == TLS_MATCHED) {
is_matched = 1;
}
}
if (!is_matched && section->tcp_match_connpkts && pkt->yct.orig_packets) {
if (pkt->yct.orig_packets <= section->tcp_match_connpkts) {
lgtrace_addp("connpackets match: %lu <= %d",
pkt->yct.orig_packets, section->tcp_match_connpkts);
is_matched = 1;
frag_pts.used_points = 0;
if (section->frag_sni_pos &&
pkt->transport_payload_len > section->frag_sni_pos) {
frag_pts.payload_points[frag_pts.used_points++] =
section->frag_sni_pos;
lgtrace_addp("frag set to %d", section->frag_sni_pos);
}
}
}
if (is_matched) {
return perform_attack(section, pkt, &frag_pts);
}
return PKT_CONTINUE;
}
static void bubblesort(size_t arr[], size_t n){
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
enum tls_proc_verdict process_tls_packet(const struct section_config_t *section,
const struct parsed_packet *pkt,
struct fragmentation_points *frag_pts) {
assert (section);
assert (pkt);
struct tls_verdict vrd = analyze_tls_data(section,
pkt->transport_payload, pkt->transport_payload_len);
lgtrace_addp("TLS analyzed");
if (vrd.sni_len != 0) {
lgtrace_addp("SNI detected: %.*s", vrd.sni_len, vrd.sni_ptr);
}
if (vrd.target_sni) {
lgdebug("Target SNI detected: %.*s", vrd.sni_len, vrd.sni_ptr);
size_t target_sni_offset = vrd.target_sni_ptr - pkt->transport_payload;
size_t ipd_offset = target_sni_offset;
size_t mid_offset = ipd_offset + vrd.target_sni_len / 2;
// hardcode googlevideo.com split
// googlevideo domains are very long, so
// it is possible for the entire domain to not be
// splitted (split goes for subdomain)
if (vrd.target_sni_len > 30) {
mid_offset = ipd_offset +
vrd.target_sni_len - 12;
}
frag_pts->used_points = 0;
if (section->frag_sni_pos && pkt->transport_payload_len > section->frag_sni_pos) {
frag_pts->payload_points[frag_pts->used_points++] = section->frag_sni_pos;
}
if (section->frag_middle_sni) {
frag_pts->payload_points[frag_pts->used_points++] = mid_offset;
}
bubblesort(frag_pts->payload_points, frag_pts->used_points);
return TLS_MATCHED;
}
return TLS_NOT_MATCHED;
}
int perform_attack(const struct section_config_t *section,
const struct parsed_packet *pkt, const struct fragmentation_points *frag_pts) {
assert (section);
assert (pkt);
assert (frag_pts);
int ret = 0;
size_t payload_len = pkt->raw_payload_len;
uint8_t *payload = malloc(pkt->raw_payload_len);
if (payload == NULL) {
lgerror(-ENOMEM, "Allocation error");
return PKT_ACCEPT;
}
memcpy(payload, pkt->raw_payload, pkt->raw_payload_len);
if (pkt->transport_payload_len > AVAILABLE_MTU) {
lgdebug("WARNING! Tartget packet is too big and may cause issues!");
}
if (section->fake_sni) {
void *iph;
size_t iph_len;
struct tcphdr *tcph;
size_t tcph_len;
uint8_t *data;
size_t dlen;
int ret = tcp_payload_split(payload, payload_len,
&iph, &iph_len, &tcph, &tcph_len,
&data, &dlen);
if (ret < 0) {
lgerror(ret, "tcp_payload_split in targ_sni");
goto accept_lc;
}
if (section->fk_winsize) {
tcph->window = htons(section->fk_winsize);
set_tcp_checksum(tcph, iph, iph_len);
}
struct fake_type f_type = args_default_fake_type(section);
post_fake_sni(f_type, iph, iph_len, tcph, tcph_len);
}
if (frag_pts->used_points > 0) {
if (section->fragmentation_strategy == FRAG_STRAT_TCP) {
ret = send_tcp_frags(section, payload, payload_len, frag_pts->payload_points,
frag_pts->used_points, 0);
if (ret < 0) {
lgerror(ret, "tcp4 send frags");
goto accept_lc;
}
goto drop_lc;
} else if (section->fragmentation_strategy == FRAG_STRAT_IP && pkt->ipver == IP4VERSION) {
ret = send_ip4_frags(section, payload, payload_len, frag_pts->payload_points,
frag_pts->used_points, 0);
if (ret < 0) {
lgerror(ret, "tcp4 send frags");
goto accept_lc;
}
goto drop_lc;
} else if (section->fragmentation_strategy == FRAG_STRAT_IP && pkt->ipver != IP4VERSION) {
lginfo("WARNING: IP fragmentation is supported only for IPv4");
goto accept_lc;
}
}
accept_lc:
free(payload);
return PKT_ACCEPT;
drop_lc:
free(payload);
return PKT_DROP;
}
int process_udp_packet(const struct section_config_t *section, const struct parsed_packet *pkt) {
assert (section);
assert (pkt);
assert (pkt->transport_proto == IPPROTO_UDP);
int ret = 0;
if (!detect_udp_filtered(section, pkt->raw_payload, pkt->raw_payload_len))
goto continue_flow;
if (section->udp_mode == UDP_MODE_DROP)
goto drop;
else if (section->udp_mode == UDP_MODE_FAKE) {
for (int i = 0; i < section->udp_fake_seq_len; i++) {
uint8_t *fake_udp = NULL;
size_t fake_udp_len = 0;
struct udp_fake_type fake_type = {
.fake_len = section->udp_fake_len,
.strategy = {
.strategy = section->udp_faking_strategy,
.faking_ttl = section->faking_ttl,
},
};
ret = gen_fake_udp(fake_type, pkt->iph, pkt->iph_len, pkt->udph,
&fake_udp, &fake_udp_len);
if (ret < 0) {
lgerror(ret, "gen_fake_udp");
goto erret;
}
lgtrace_addp("post fake udp #%d", i + 1);
ret = instance_config.send_raw_packet(fake_udp, fake_udp_len);
if (ret < 0) {
lgerror(ret, "send fake udp");
goto erret_lc;
}
free(fake_udp);
continue;
erret_lc:
free(fake_udp);
erret:
goto accept;
}
// requeue
ret = instance_config.send_raw_packet(pkt->raw_payload, pkt->raw_payload_len);
goto drop;
}
continue_flow:
return PKT_CONTINUE;
accept:
return PKT_ACCEPT;
drop:
return PKT_DROP;
}
int log_packet(const struct parsed_packet *pkt) {
int ret = 0;
const char *bpt = inet_ntop(
pkt->ipver == IP4VERSION ? AF_INET : AF_INET6,
pkt->ipver == IP4VERSION ? (void *)(&pkt->iph->saddr) :
(void *)(&pkt->ip6h->ip6_src),
ylgh_curptr, ylgh_leftbuf);
if (bpt != NULL) {
ret = strnlen(bpt, ylgh_leftbuf);
ylgh_leftbuf -= ret;
ylgh_curptr += ret;
}
lgtrace_wr(" => ");
bpt = inet_ntop(
pkt->ipver == IP4VERSION ? AF_INET : AF_INET6,
pkt->ipver == IP4VERSION ? (void *)(&pkt->iph->daddr) :
(void *)(&pkt->ip6h->ip6_dst),
ylgh_curptr, ylgh_leftbuf);
if (bpt != NULL) {
ret = strnlen(bpt, ylgh_leftbuf);
ylgh_leftbuf -= ret;
ylgh_curptr += ret;
}
lgtrace_wr(" ");
int sport = -1, dport = -1;
if (pkt->transport_proto == IPPROTO_TCP) {
lgtrace_wr("TCP ");
sport = ntohs(pkt->tcph->source);
dport = ntohs(pkt->tcph->dest);
} else if (pkt->transport_proto == IPPROTO_UDP) {
lgtrace_wr("UDP ");
sport = ntohs(pkt->udph->source);
dport = ntohs(pkt->udph->dest);
}
lgtrace_wr("%d => %d ", sport, dport);
lgtrace_write();
lgtrace_wr("Transport payload: [ ");
for (int i = 0; i < min((int)16, (int)pkt->transport_payload_len); i++) {
lgtrace_wr("%02x ", pkt->transport_payload[i]);
}
lgtrace_wr("]");
lgtrace_write();
}