21 Commits

Author SHA1 Message Date
Vadim Vetrov
f37c3dd496 Bump version 2025-02-21 00:19:43 +03:00
Vadim Vetrov
0cf1035a14 Describe youtube does not work with all domains 2025-02-08 19:15:58 +03:00
Vadim Vetrov
7ebaccfa19 Merge branch 'aho_corasick' 2025-02-08 11:43:06 +03:00
Vadim Vetrov
705da0f4c6 Merge pull request #229 from metrapoliten/fixes
Различные фиксы
2025-02-07 18:23:48 +03:00
Vadim Vetrov
df70763b4a fix: safety defenders for delay_packet_send 2025-02-06 18:43:31 +03:00
Artyom Gavrilov
49304cc111 fix: добавление проверки malloc 2025-02-06 00:29:27 +03:00
Artyom Gavrilov
b832541766 fix: перемещение проверки на нужное место 2025-02-05 23:55:41 +03:00
Artyom Gavrilov
2884cb72f9 fix: проверка указателя перед разыменованием 2025-02-05 23:50:10 +03:00
Artyom Gavrilov
34271ece2c fix: восстановление проверки fseek
Вероятно в этом месте должна была быть проверка вызова fseek.
2025-02-05 23:50:10 +03:00
Artyom Gavrilov
ad6b84a961 fix: проверка ftell на возвращаемое значение 2025-02-05 23:50:10 +03:00
Artyom Gavrilov
5f20220d4e fix: изменение типа переменной на подходящий
По стандарту ftell возвращает long.
2025-02-05 23:50:10 +03:00
Artyom Gavrilov
6cc23a2991 fix: изменение проверки fseek
По стандарту fseek при неудаче возвращает любое значение кроме нуля.
2025-02-05 23:50:10 +03:00
Artyom Gavrilov
c73885aca3 fix: изменение проверки qversion
qversion >= 0, т.к. тип переменной - uint32_t. Провека должна быть по
переменной ret.
2025-02-05 23:48:46 +03:00
Artyom Gavrilov
78dd12c526 fix: проверка указателя до его использования 2025-02-05 22:52:13 +03:00
Artyom Gavrilov
d7489fc08a fix: проверка указателя до его использования 2025-02-05 22:47:02 +03:00
Vadim Vetrov
6da6f63541 Delete old domains data structures 2025-02-04 18:40:59 +03:00
Vadim Vetrov
a7b689b320 Fix warnings 2025-02-03 15:30:10 +03:00
Vadim Vetrov
f7d0bed7aa Use Aho-Corasick algorithm in tls parsing 2025-02-02 23:36:19 +03:00
Vadim Vetrov
d225e673c7 Implement Aho-Corasick algorithm 2025-02-02 20:00:57 +03:00
Vadim Vetrov
d9c360910b procfs for old kernels 2025-02-01 21:02:05 +03:00
Vadim Vetrov
42917a75fc Add youtubeUnblock statistics
The statistis will be printed on exit in userspace version. In kernel
space version, use `cat /proc/kyoutubeUnblock`.

The feature was proposed by @IceCat74 in #220
2025-02-01 20:38:33 +03:00
19 changed files with 640 additions and 172 deletions

2
Kbuild
View File

@@ -1,3 +1,3 @@
obj-m := kyoutubeUnblock.o
kyoutubeUnblock-objs := src/kytunblock.o src/mangle.o src/quic.o src/quic_crypto.o src/utils.o src/tls.o src/getopt.o src/inet_ntop.o src/args.o deps/cyclone/aes.o deps/cyclone/cpu_endian.o deps/cyclone/ecb.o deps/cyclone/gcm.o deps/cyclone/hkdf.o deps/cyclone/hmac.o deps/cyclone/sha256.o
kyoutubeUnblock-objs := src/kytunblock.o src/mangle.o src/quic.o src/quic_crypto.o src/utils.o src/tls.o src/getopt.o src/inet_ntop.o src/args.o src/trie.o deps/cyclone/aes.o deps/cyclone/cpu_endian.o deps/cyclone/ecb.o deps/cyclone/gcm.o deps/cyclone/hkdf.o deps/cyclone/hmac.o deps/cyclone/sha256.o
ccflags-y := -std=gnu99 -DKERNEL_SPACE -Wno-error -Wno-declaration-after-statement -I$(src)/src -I$(src)/deps/cyclone/include

View File

@@ -2,7 +2,7 @@ USPACE_TARGETS := default all install uninstall dev run_dev
KMAKE_TARGETS := kmake kload kunload kreload xmod xtclean
PKG_VERSION := 1.0.0
PKG_RELEASE := 6
PKG_RELEASE := 10
PKG_FULLVERSION := $(PKG_VERSION)-$(PKG_RELEASE)

View File

@@ -320,6 +320,10 @@ If your browser is using QUIC it may not work properly. Disable it in Chrome in
It seems like some TSPUs started to block wrongseq packets, so you should play around with faking strategies. I personally recommend to start with `md5sum` faking strategy.
#### youtube with `--sni-domains=all`
I know about this issue but it is **basically not an youtubeUnblock problem**. The problem is behind the large `*.googlevideo.com` domain name. All you want is to create a new configuration section for only youtube. It should go after section for all domains. For plain string arguments just `--fbegin` at the end of args list will work. In luci you can create section interactively.
### TV
Televisions are the biggest headache.
@@ -435,6 +439,12 @@ cat /sys/module/kyoutubeUnblock/parameters/parameters
and check all the parameters configured.
You can check up the statistics of youtubeUnblock with
```sh
sudo cat /proc/kyoutubeUnblock
```
### Building kernel module
#### Building on host system

View File

@@ -157,7 +157,7 @@ typedef unsigned int uint_t;
#elif defined(__GNUC__)
int strcasecmp(const char *s1, const char *s2);
int strncasecmp(const char *s1, const char *s2, size_t n);
#if !(_SVID_SOURCE || _BSD_SOURCE || _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE)
#if !(defined(_SVID_SOURCE) || defined(_BSD_SOURCE) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE))
char *strtok_r(char *s, const char *delim, char **last);
#endif

View File

@@ -26,6 +26,7 @@
#include "getopt.h"
#include "raw_replacements.h"
struct statistics_data global_stats;
/**
* Logging definitions
@@ -61,14 +62,18 @@ static int read_file(const char* filename) {
}
ret = fseek(fd, 0, SEEK_END);
if (ret < 0) {
if (ret != 0) {
ret = -errno;
goto close_file;
}
size_t fsize = ftell(fd);
fseek(fd, 0, SEEK_SET);
if (ret < 0) {
long fsize = ftell(fd);
if (fsize == -1L) {
ret = -errno;
goto close_file;
}
ret = fseek(fd, 0, SEEK_SET);
if (ret != 0) {
ret = -errno;
goto close_file;
}
@@ -93,11 +98,10 @@ close_file:
}
#endif
static int parse_sni_domains(struct domains_list **dlist, const char *domains_str, size_t domains_strlen) {
// Empty and shouldn't be used
struct domains_list ndomain = {0};
struct domains_list *cdomain = &ndomain;
static int parse_sni_domains(struct trie_container *trie, const char *domains_str, size_t domains_strlen) {
int ret;
trie_init(trie);
unsigned int j = 0;
for (unsigned int i = 0; i <= domains_strlen; i++) {
if (( i == domains_strlen ||
@@ -118,38 +122,22 @@ static int parse_sni_domains(struct domains_list **dlist, const char *domains_st
unsigned int domain_len = (i - j);
const char *domain_startp = domains_str + j;
struct domains_list *edomain = malloc(sizeof(struct domains_list));
*edomain = (struct domains_list){0};
if (edomain == NULL) {
return -ENOMEM;
}
edomain->domain_len = domain_len;
edomain->domain_name = malloc(domain_len + 1);
if (edomain->domain_name == NULL) {
return -ENOMEM;
ret = trie_add_string(trie, (const uint8_t *)domain_startp, domain_len);
if (ret < 0) {
lgerror(ret, "trie_add_string");
return ret;
}
strncpy(edomain->domain_name, domain_startp, domain_len);
edomain->domain_name[domain_len] = '\0';
cdomain->next = edomain;
cdomain = edomain;
j = i + 1;
}
}
*dlist = ndomain.next;
return 0;
}
static void free_sni_domains(struct domains_list *dlist) {
for (struct domains_list *ldl = dlist; ldl != NULL;) {
struct domains_list *ndl = ldl->next;
SFREE(ldl->domain_name);
SFREE(ldl);
ldl = ndl;
}
static void free_sni_domains(struct trie_container *trie) {
trie_destroy(trie);
}
static long parse_numeric_option(const char* value) {
@@ -284,6 +272,9 @@ static int parse_fake_custom_payload(
return -EINVAL;
}
unsigned char *custom_buf = malloc(custom_len);
if (custom_buf == NULL) {
return -ENOMEM;
}
for (int i = 0; i < custom_len; i++) {
ret = sscanf(custom_hex_fake + (i << 1), "%2hhx", custom_buf + i);
@@ -632,7 +623,7 @@ int yparse_args(struct config_t *config, int argc, char *argv[]) {
break;
case OPT_SNI_DOMAINS:
free_sni_domains(sect_config->sni_domains);
free_sni_domains(&sect_config->sni_domains);
sect_config->all_domains = 0;
if (!strcmp(optarg, "all")) {
sect_config->all_domains = 1;
@@ -648,7 +639,7 @@ int yparse_args(struct config_t *config, int argc, char *argv[]) {
goto error;
#else
{
free_sni_domains(sect_config->sni_domains);
free_sni_domains(&sect_config->sni_domains);
ret = read_file(optarg);
if (ret < 0) {
goto error;
@@ -661,7 +652,7 @@ int yparse_args(struct config_t *config, int argc, char *argv[]) {
}
#endif
case OPT_EXCLUDE_DOMAINS:
free_sni_domains(sect_config->exclude_sni_domains);
free_sni_domains(&sect_config->exclude_sni_domains);
ret = parse_sni_domains(&sect_config->exclude_sni_domains, optarg, strlen(optarg));
if (ret < 0)
goto error;
@@ -673,7 +664,7 @@ int yparse_args(struct config_t *config, int argc, char *argv[]) {
goto error;
#else
{
free_sni_domains(sect_config->exclude_sni_domains);
free_sni_domains(&sect_config->exclude_sni_domains);
ret = read_file(optarg);
if (ret < 0) {
goto error;
@@ -1067,20 +1058,11 @@ static size_t print_config_section(const struct section_config_t *section, char
if (section->all_domains) {
print_cnf_buf("--sni-domains=all");
} else if (section->sni_domains != NULL) {
print_cnf_raw("--sni-domains=");
for (struct domains_list *sne = section->sni_domains; sne != NULL; sne = sne->next) {
print_cnf_raw("%s,", sne->domain_name);
}
print_cnf_raw(" ");
} else if (section->sni_domains.vx != NULL) {
print_cnf_buf("--sni-domains=<trie of %zu vertexes>", section->sni_domains.sz);
}
if (section->exclude_sni_domains != NULL) {
print_cnf_raw("--exclude-domains=");
for (struct domains_list *sne = section->exclude_sni_domains; sne != NULL; sne = sne->next) {
print_cnf_raw("%s,", sne->domain_name);
}
print_cnf_raw(" ");
if (section->exclude_sni_domains.vx != NULL) {
print_cnf_buf("--exclude-domains=<trie of %zu vertexes>", section->sni_domains.sz);
}
switch(section->sni_detection) {
@@ -1241,11 +1223,11 @@ int init_section_config(struct section_config_t **section, struct section_config
#else
def_section = malloc(sizeof(struct section_config_t));
#endif
*def_section = (struct section_config_t)default_section_config;
def_section->prev = prev;
if (def_section == NULL)
if (def_section == NULL)
return -ENOMEM;
*def_section = (struct section_config_t)default_section_config;
def_section->prev = prev;
ret = parse_sni_domains(&def_section->sni_domains, default_snistr, sizeof(default_snistr));
if (ret < 0) {
@@ -1280,10 +1262,8 @@ void free_config_section(struct section_config_t *section) {
SFREE(section->udp_dport_range);
}
free_sni_domains(section->sni_domains);
section->sni_domains = NULL;
free_sni_domains(section->exclude_sni_domains);
section->exclude_sni_domains = NULL;
free_sni_domains(&section->sni_domains);
free_sni_domains(&section->exclude_sni_domains);
section->fake_custom_pkt_sz = 0;
SFREE(section->fake_custom_pkt);

View File

@@ -25,6 +25,7 @@
#endif
#include "types.h"
#include "trie.h"
typedef int (*raw_send_t)(const unsigned char *data, size_t data_len);
/**
@@ -52,20 +53,13 @@ struct udp_dport_range {
uint16_t end;
};
struct domains_list {
char *domain_name;
uint16_t domain_len;
struct domains_list *next;
};
struct section_config_t {
int id;
struct section_config_t *next;
struct section_config_t *prev;
struct domains_list *sni_domains;
struct domains_list *exclude_sni_domains;
struct trie_container sni_domains;
struct trie_container exclude_sni_domains;
unsigned int all_domains;
int tls_enabled;
@@ -237,8 +231,8 @@ enum {
};
#define default_section_config { \
.sni_domains = NULL, \
.exclude_sni_domains = NULL, \
.sni_domains = {0}, \
.exclude_sni_domains = {0}, \
.all_domains = 0, \
.tls_enabled = 1, \
.frag_sni_reverse = 1, \
@@ -339,4 +333,13 @@ struct packet_data {
struct ytb_conntrack yct;
};
struct statistics_data {
unsigned long all_packet_counter;
unsigned long packet_counter;
unsigned long target_counter;
unsigned long sent_counter;
};
extern struct statistics_data global_stats;
#endif /* YTB_CONFIG_H */

View File

@@ -30,6 +30,7 @@
#include <linux/net.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/proc_fs.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
@@ -329,6 +330,8 @@ erret_lc:
return ret;
}
++global_stats.sent_counter;
int ipvx = netproto_version(pkt, pktlen);
if (ipvx == IP4VERSION) {
@@ -483,6 +486,8 @@ static NF_CALLBACK(ykb_nf_hook, skb) {
struct config_t *config = cur_config;
kref_get(&config->refcount);
++global_stats.all_packet_counter;
if ((skb->mark & config->mark) == config->mark) {
goto send_verdict;
}
@@ -523,12 +528,14 @@ static NF_CALLBACK(ykb_nf_hook, skb) {
pd.payload_len = skb->len;
int vrd = process_packet(config, &pd);
++global_stats.packet_counter;
switch(vrd) {
case PKT_ACCEPT:
nf_verdict = NF_ACCEPT;
break;
case PKT_DROP:
++global_stats.target_counter;
nf_verdict = NF_STOLEN;
kfree_skb(skb);
break;
@@ -580,6 +587,37 @@ static struct pernet_operations ykb_pernet_ops = {
};
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) */
#ifdef CONFIG_PROC_FS
static int proc_stats_show(struct seq_file *s, void *v) {
seq_printf(s, "youtubeUnblock stats: \n"
"\tCatched: %ld packets\n"
"\tProcessed: %ld packets\n"
"\tTargetted: %ld packets\n"
"\tSent over socket %ld packets\n",
global_stats.all_packet_counter, global_stats.packet_counter,
global_stats.target_counter, global_stats.sent_counter);
return 0;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,18,0)
static int proc_stats_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_stats_show, NULL);
}
static const struct file_operations proc_stats_operations = {
.open = proc_stats_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif /* KERNEL_VERSION */
#endif /* CONFIG_PROC_FS */
static int __init ykb_init(void) {
int ret;
@@ -615,6 +653,18 @@ static int __init ykb_init(void) {
}
#endif /* NO_IPV6 */
#ifdef CONFIG_PROC_FS
if (!
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,18,0)
proc_create_single("kyoutubeUnblock", 0, NULL, proc_stats_show)
#else
proc_create("kyoutubeUnblock", 0, NULL, &proc_stats_operations)
#endif
) {
lgwarning("kyoutubeUnblock procfs entry creation failed");
}
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0)
ret = register_pernet_subsys(&ykb_pernet_ops);
#else
@@ -651,6 +701,10 @@ static void __exit ykb_destroy(void) {
close_raw6_socket();
#endif
#ifdef CONFIG_PROC_FS
remove_proc_entry("kyoutubeUnblock", NULL);
#endif
close_raw_socket();
kref_put(&cur_config->refcount, config_release);
lginfo("youtubeUnblock kernel module destroyed.\n");

View File

@@ -502,18 +502,11 @@ drop:
int send_ip4_frags(const struct section_config_t *section, const uint8_t *packet, size_t pktlen, const size_t *poses, size_t poses_sz, size_t dvs) {
if (poses_sz == 0) {
lgtrace_addp("raw send packet of %zu bytes with %zu dvs", pktlen, dvs);
if (section->seg2_delay && ((dvs > 0) ^ section->frag_sni_reverse)) {
if (!instance_config.send_delayed_packet) {
return -EINVAL;
}
lgtrace_addp("Sent %zu delayed for %d", pktlen, section->seg2_delay);
instance_config.send_delayed_packet(
return instance_config.send_delayed_packet(
packet, pktlen, section->seg2_delay);
return 0;
} else {
lgtrace_addp("Sent %zu bytes", pktlen);
return instance_config.send_raw_packet(
packet, pktlen);
}
@@ -588,18 +581,11 @@ out:
int send_tcp_frags(const struct section_config_t *section, const uint8_t *packet, size_t pktlen, const size_t *poses, size_t poses_sz, size_t dvs) {
if (poses_sz == 0) {
lgtrace_addp("raw send packet of %zu bytes with %zu dvs", pktlen, dvs);
if (section->seg2_delay && ((dvs > 0) ^ section->frag_sni_reverse)) {
if (!instance_config.send_delayed_packet) {
return -EINVAL;
}
instance_config.send_delayed_packet(
return instance_config.send_delayed_packet(
packet, pktlen, section->seg2_delay);
return 0;
} else {
lgtrace_addp("raw send packet of %zu bytes with %zu dvs", pktlen, dvs);
return instance_config.send_raw_packet(
packet, pktlen);
}

View File

@@ -71,7 +71,7 @@ int quic_check_is_initial(const struct quic_lhdr *qch) {
uint32_t qversion;
int ret;
ret = quic_get_version(&qversion, qch);
if (qversion < 0) return 0;
if (ret < 0) return 0;
uint8_t qtype = qch->type;

View File

@@ -82,6 +82,10 @@ int quic_parse_initial_message(
ret = quic_parse_data(quic_payload, quic_plen,
&qch, &qch_len, &qci, &inpayload, &inplen
);
if (ret < 0) {
lgerror(ret, "quic_parse_data");
goto error_nfr;
}
ret = quic_get_version(&qversion, qch);
if (ret < 0) {
@@ -117,10 +121,6 @@ int quic_parse_initial_message(
}
quic_header_len = inpayload - quic_payload;
if (ret < 0) {
lgerror(ret, "quic_parse_data");
goto error_nfr;
}
ret = quic_parse_initial_header(inpayload, inplen, &qich);
if (ret < 0) {

View File

@@ -33,6 +33,8 @@ int bruteforce_analyze_sni_str(
const uint8_t *data, size_t dlen,
struct tls_verdict *vrd
) {
size_t offset, offlen;
int ret;
*vrd = (struct tls_verdict){0};
if (dlen <= 1) {
@@ -47,50 +49,17 @@ int bruteforce_analyze_sni_str(
vrd->target_sni_len = vrd->sni_len;
return 0;
}
int max_domain_len = 0;
for (struct domains_list *sne = section->sni_domains; sne != NULL;
sne = sne->next) {
max_domain_len = max((int)sne->domain_len, max_domain_len);
}
size_t buf_size = max_domain_len + dlen + 1;
uint8_t *buf = malloc(buf_size);
if (buf == NULL) {
return -ENOMEM;
}
int *nzbuf = malloc(buf_size * sizeof(int));
if (nzbuf == NULL) {
free(buf);
return -ENOMEM;
// It is safe for multithreading, so dp mutability is ok
ret = trie_process_str((struct trie_container *)&section->sni_domains, data, dlen, 0, &offset, &offlen);
if (ret) {
vrd->target_sni = 1;
vrd->sni_len = offlen;
vrd->sni_ptr = data + offset;
vrd->target_sni_ptr = vrd->sni_ptr;
vrd->target_sni_len = vrd->sni_len;
}
for (struct domains_list *sne = section->sni_domains; sne != NULL; sne = sne->next) {
const char *domain_startp = sne->domain_name;
int domain_len = sne->domain_len;
int *zbuf = (void *)nzbuf;
memcpy(buf, domain_startp, domain_len);
memcpy(buf + domain_len, "#", 1);
memcpy(buf + domain_len + 1, data, dlen);
z_function((char *)buf, zbuf, domain_len + 1 + dlen);
for (size_t k = 0; k < domain_len + 1 + dlen; k++) {
if (zbuf[k] == domain_len) {
vrd->target_sni = 1;
vrd->sni_len = domain_len;
vrd->sni_ptr = data + (k - domain_len - 1);
vrd->target_sni_ptr = vrd->sni_ptr;
vrd->target_sni_len = vrd->sni_len;
goto return_vrd;
}
}
}
return_vrd:
free(buf);
free(nzbuf);
return 0;
}
static int analyze_sni_str(
@@ -98,42 +67,33 @@ static int analyze_sni_str(
const char *sni_name, int sni_len,
struct tls_verdict *vrd
) {
int ret;
size_t offset, offlen;
if (section->all_domains) {
vrd->target_sni = 1;
goto check_domain;
}
for (struct domains_list *sne = section->sni_domains; sne != NULL; sne = sne->next) {
const char *sni_startp = sni_name + sni_len - sne->domain_len;
const char *domain_startp = sne->domain_name;
if (sni_len >= sne->domain_len &&
sni_len < 128 &&
!strncmp(sni_startp,
domain_startp,
sne->domain_len)) {
vrd->target_sni = 1;
vrd->target_sni_ptr = (const uint8_t *)sni_startp;
vrd->target_sni_len = sne->domain_len;
break;
}
// It is safe for multithreading, so dp mutability is ok
ret = trie_process_str((struct trie_container *)&section->sni_domains,
(const uint8_t *)sni_name, sni_len, TRIE_OPT_MAP_TO_END, &offset, &offlen);
if (ret) {
vrd->target_sni = 1;
vrd->target_sni_ptr = (const uint8_t *)sni_name + offset;
vrd->target_sni_len = offlen;
}
check_domain:
if (vrd->target_sni == 1) {
for (struct domains_list *sne = section->exclude_sni_domains; sne != NULL; sne = sne->next) {
const char *sni_startp = sni_name + sni_len - sne->domain_len;
const char *domain_startp = sne->domain_name;
if (sni_len >= sne->domain_len &&
sni_len < 128 &&
!strncmp(sni_startp,
domain_startp,
sne->domain_len)) {
vrd->target_sni = 0;
lgdebug("Excluded SNI: %.*s",
vrd->sni_len, vrd->sni_ptr);
}
// It is safe for multithreading, so dp mutability is ok
ret = trie_process_str((struct trie_container *)&section->exclude_sni_domains,
(const uint8_t *)sni_name, sni_len, TRIE_OPT_MAP_TO_END, &offset, &offlen);
if (ret) {
vrd->target_sni = 0;
lgdebug("Excluded SNI: %.*s",
vrd->sni_len, vrd->sni_ptr);
}
}

197
src/trie.c Normal file
View File

@@ -0,0 +1,197 @@
/*
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/>.
*/
/**
* This is slightly optimized Aho-Corasick implementation
*
* Big thanks to e-maxx http://e-maxx.ru/algo/aho_corasick
* for the best description and reference code samples
*/
#include "trie.h"
int trie_init(struct trie_container *trie) {
void *vx = malloc(sizeof(struct trie_vertex) * TRIE_STARTSZ);
if (vx == NULL) {
return -ENOMEM;
}
trie->vx = vx;
trie->arrsz = TRIE_STARTSZ;
trie->sz = 1;
struct trie_vertex *trx = trie->vx;
trx->p = trx->link = -1;
trx->leaf = 0;
trx->depth = 0;
trx->pch = 0;
memset(trx->go, 0xff, sizeof(trie->vx[0].go));
return 0;
}
void trie_destroy(struct trie_container *trie) {
trie->arrsz = 0;
trie->sz = 0;
free(trie->vx);
trie->vx = NULL;
}
/**
*
* Increases trie vertex container size.
* Returns new vertex index or ret < 0 on error
*
*/
static int trie_push_vertex(struct trie_container *trie) {
if (trie->sz == NMAX - 1) {
return -EINVAL;
}
if (trie->arrsz == trie->sz) { // realloc
void *pt = realloc(trie->vx,
sizeof(struct trie_vertex) * trie->arrsz * 2);
if (pt == NULL) {
return -ENOMEM;
}
trie->arrsz *= 2;
trie->vx = pt;
}
return trie->sz++;
}
int trie_add_string(struct trie_container *trie,
const uint8_t *str, size_t strlen) {
if (trie == NULL || trie->vx == NULL) {
return -EINVAL;
}
int v = 0;
int nv;
for (size_t i = 0; i < strlen; ++i) {
uint8_t c = str[i];
if (c >= TRIE_ALPHABET) {
return -EINVAL;
}
if (trie->vx[v].go[c] == -1) {
nv = trie_push_vertex(trie);
if (nv < 0) {
return nv;
}
struct trie_vertex *tvx = trie->vx + nv;
memset(tvx->go, 0xff, sizeof(tvx->go));
tvx->link = -1;
tvx->p = v;
tvx->depth = trie->vx[v].depth + 1;
tvx->leaf = 0;
tvx->pch = c;
trie->vx[v].go[c] = nv;
}
v = trie->vx[v].go[c];
}
if (v != 0) {
trie->vx[v].leaf = 1;
}
return 0;
}
static int trie_go(struct trie_container *trie,
int v, uint8_t c);
static int trie_get_link(struct trie_container *trie,
int v) {
struct trie_vertex *tvx = trie->vx + v;
if (tvx->link == -1) {
if (v == 0 || tvx->p == 0) {
tvx->link = 0;
} else {
tvx->link = trie_go(trie,
trie_get_link(trie, tvx->p), tvx->pch);
}
}
return tvx->link;
}
static int trie_go(struct trie_container *trie, int v, uint8_t c) {
struct trie_vertex *tvx = trie->vx + v;
if (tvx->go[c] == -1) {
tvx->go[c] = v == 0 ? 0 :
trie_go(trie, trie_get_link(trie, v), c);
}
return tvx->go[c];
}
int trie_process_str(
struct trie_container *trie,
const uint8_t *str, size_t strlen,
int flags,
size_t *offset, size_t *offlen
) {
if (trie == NULL || trie->vx == NULL) {
return 0;
}
int v = 0;
size_t i = 0;
uint8_t c;
int len;
for (; i < strlen; ++i) {
c = str[i];
if (c >= TRIE_ALPHABET) {
v = 0;
continue;
}
v = trie->vx[v].go[c] != -1 ? trie->vx[v].go[c] :
trie_go(trie, v, str[i]);
if (trie->vx[v].leaf &&
((flags & TRIE_OPT_MAP_TO_END) != TRIE_OPT_MAP_TO_END ||
i == strlen - 1)
) {
++i;
break;
}
}
len = trie->vx[v].depth;
if ( trie->vx[v].leaf &&
i >= len
) {
size_t sp = i - len;
*offset = sp;
*offlen = len;
return 1;
}
return 0;
}

92
src/trie.h Normal file
View File

@@ -0,0 +1,92 @@
/*
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/>.
*/
/**
* This is slightly optimized Aho-Corasick implementation
*
* Big thanks to e-maxx http://e-maxx.ru/algo/aho_corasick
* for the best description and reference code samples
*
*/
/**
*
* This algorithm allows us to search inside the string
* for a list of patterns in the linear time.
*
* The algorithm will lazily initialize itself while
* youtubeUnblock works. Lazy initializations considered
* safe for multithreading and operate without atomicity
* or synchronization primitives.
*
*/
#ifndef TRIE_H
#define TRIE_H
#include "types.h"
// ASCII alphabet
#define TRIE_ALPHABET 128
// Maximum of vertexes in the trie
#define NMAX ((1 << 15) - 1)
struct trie_vertex {
int leaf; // boolean flag
int depth; // depth of tree (length of substring)
int p; // parent
uint8_t pch; // vertex char
int link; // sufflink
int16_t go[TRIE_ALPHABET]; // dynamically filled pushes
};
struct trie_container {
struct trie_vertex *vx;
size_t arrsz;
size_t sz;
};
#define TRIE_STARTSZ 32
int trie_init(struct trie_container *trie);
void trie_destroy(struct trie_container *trie);
int trie_add_string(struct trie_container *trie,
const uint8_t *str, size_t strlen);
/**
* Aligns the pattern to the end
*/
#define TRIE_OPT_MAP_TO_END (1 << 1)
/**
* Searches the string for the patterns.
* flags is TRIE_OPT binary mask with options for search.
* offset, offlen are destination variables with
* offset of the given string and length of target.
*
* returns 1 if target found, 0 otherwise
*/
int trie_process_str(
struct trie_container *trie,
const uint8_t *str, size_t strlen,
int flags,
size_t *offset, size_t *offlen
);
#endif

View File

@@ -68,6 +68,7 @@ typedef __s16 int_least16_t; /* integer of >= 16 bits */
#define free kfree
#define malloc(size) kmalloc((size), GFP_KERNEL)
#define realloc(pt, size) krealloc((pt), (size), GFP_KERNEL)
#define calloc(n, size) kcalloc((n), (size), GFP_KERNEL)
#define ip6_hdr ipv6hdr

View File

@@ -46,6 +46,7 @@
#include <linux/netfilter.h>
#include <pthread.h>
#include <sys/socket.h>
#include <signal.h>
#include "config.h"
#include "mangle.h"
@@ -546,6 +547,7 @@ erret_lc:
return ret;
}
++global_stats.sent_counter;
int ipvx = netproto_version(pkt, pktlen);
if (ipvx == IP4VERSION) {
@@ -612,14 +614,33 @@ void *delay_packet_send_fn(void *data) {
}
int delay_packet_send(const unsigned char *data, size_t data_len, unsigned int delay_ms) {
int ret;
struct dps_t *dpdt = malloc(sizeof(struct dps_t));
if (dpdt == NULL) {
return -ENOMEM;
}
*dpdt = (struct dps_t){0};
dpdt->pkt = malloc(data_len);
if (dpdt->pkt == NULL) {
free(dpdt);
return -ENOMEM;
}
memcpy(dpdt->pkt, data, data_len);
dpdt->pktlen = data_len;
dpdt->timer = delay_ms;
pthread_t thr;
pthread_create(&thr, NULL, delay_packet_send_fn, dpdt);
pthread_detach(thr);
pthread_t thr = {0};
ret = pthread_create(&thr, NULL, delay_packet_send_fn, dpdt);
if (ret != 0) {
free(dpdt->pkt);
free(dpdt);
return -ret;
}
ret = pthread_detach(thr);
lgtrace_addp("Scheduled packet send after %d ms", delay_ms);
return 0;
@@ -640,6 +661,8 @@ static int queue_cb(const struct nlmsghdr *nlh, void *data) {
uint16_t l3num;
uint32_t id;
++global_stats.all_packet_counter;
if (nfq_nlmsg_parse(nlh, attr) < 0) {
lgerror(-errno, "Attr parse");
return MNL_CB_ERROR;
@@ -694,8 +717,11 @@ ct_out:
ret = process_packet(cur_config, &packet);
++global_stats.packet_counter;
switch (ret) {
case PKT_DROP:
++global_stats.target_counter;
nfq_nlmsg_verdict_put(verdnlh, id, NF_DROP);
break;
default:
@@ -875,6 +901,16 @@ struct instance_config_t instance_config = {
.send_delayed_packet = delay_packet_send,
};
void sigint_handler(int s) {
lginfo("youtubeUnblock stats: catched %ld packets, "
"processed %ld packets, "
"targetted %ld packets, sent over socket %ld packets",
global_stats.all_packet_counter, global_stats.packet_counter,
global_stats.target_counter, global_stats.sent_counter);
exit(EXIT_SUCCESS);
}
int main(int argc, char *argv[]) {
int ret;
struct config_t config;
@@ -893,6 +929,8 @@ int main(int argc, char *argv[]) {
parse_global_lgconf(&config);
cur_config = &config;
signal(SIGINT, sigint_handler);
signal(SIGTERM, sigint_handler);
if (open_raw_socket() < 0) {
lgerror(-errno, "Unable to open raw socket");

View File

@@ -10,6 +10,7 @@ static void RunAllTests(void)
{
RUN_TEST_GROUP(TLSTest)
RUN_TEST_GROUP(QuicTest);
RUN_TEST_GROUP(TrieTest);
}
int main(int argc, const char * argv[])

View File

@@ -36,22 +36,21 @@ TEST(TLSTest, Test_CHLO_message_detect)
TEST(TLSTest, Test_Bruteforce_detects)
{
struct tls_verdict tlsv;
struct domains_list dmns = {
.domain_name = "youtube.com",
.domain_len = 11,
.next = NULL
};
sconf.sni_domains = &dmns;
struct trie_container trie;
int ret;
ret = trie_init(&trie);
ret = trie_add_string(&trie, (uint8_t *)"youtube.com", 11);
sconf.sni_domains = trie;
int ret = bruteforce_analyze_sni_str(&sconf, (const uint8_t *)tls_bruteforce_message, sizeof(tls_bruteforce_message) - 1, &tlsv);
ret = bruteforce_analyze_sni_str(&sconf, (const uint8_t *)tls_bruteforce_message, sizeof(tls_bruteforce_message) - 1, &tlsv);
TEST_ASSERT_EQUAL(0, ret);
TEST_ASSERT_EQUAL(11, tlsv.sni_len);
TEST_ASSERT_EQUAL_STRING_LEN("youtube.com", tlsv.sni_ptr, 11);
TEST_ASSERT_EQUAL_PTR(tls_bruteforce_message +
sizeof(tls_bruteforce_message) - 12, tlsv.sni_ptr);
trie_destroy(&trie);
}
TEST_GROUP_RUNNER(TLSTest)
{
RUN_TEST_CASE(TLSTest, Test_CHLO_message_detect);

147
test/trie.c Normal file
View File

@@ -0,0 +1,147 @@
#include "unity.h"
#include "unity_fixture.h"
#include "trie.h"
TEST_GROUP(TrieTest);
TEST_SETUP(TrieTest)
{
}
TEST_TEAR_DOWN(TrieTest)
{
}
const char ASTR[] = "abacaba";
const char BSTR[] = "BABABABA";
const char CSTR[] = "abracadabra";
const char tstr[] = "aBABABABDADAabacabracadabraabbbabacabaaaaaabacaba";
TEST(TrieTest, Trie_string_adds)
{
int ret;
size_t offset;
size_t offlen;
struct trie_container trie;
ret = trie_init(&trie);
TEST_ASSERT_EQUAL(0, ret);
ret = trie_add_string(&trie, (uint8_t *)ASTR, sizeof(ASTR) - 1);
TEST_ASSERT_EQUAL(0, ret);
ret = trie_add_string(&trie, (uint8_t *)BSTR, sizeof(BSTR) - 1);
TEST_ASSERT_EQUAL(0, ret);
ret = trie_add_string(&trie, (uint8_t *)CSTR, sizeof(CSTR) - 1);
TEST_ASSERT_EQUAL(0, ret);
TEST_ASSERT_EQUAL(25, trie.sz);
trie_destroy(&trie);
}
TEST(TrieTest, Trie_string_finds)
{
int ret;
size_t offset;
size_t offlen;
struct trie_container trie;
ret = trie_init(&trie);
ret = trie_add_string(&trie, (uint8_t *)ASTR, sizeof(ASTR) - 1);
ret = trie_add_string(&trie, (uint8_t *)BSTR, sizeof(BSTR) - 1);
ret = trie_add_string(&trie, (uint8_t *)CSTR, sizeof(CSTR) - 1);
ret = trie_process_str(&trie,
(uint8_t *)tstr, sizeof(tstr) - 1,
0, &offset, &offlen
);
TEST_ASSERT_EQUAL(1, ret);
TEST_ASSERT_EQUAL(11, offlen);
TEST_ASSERT_EQUAL_STRING_LEN("abracadabra", tstr + offset, offlen);
trie_destroy(&trie);
}
TEST(TrieTest, Trie_string_finds_opt_end)
{
int ret;
size_t offset;
size_t offlen;
struct trie_container trie;
ret = trie_init(&trie);
ret = trie_add_string(&trie, (uint8_t *)ASTR, sizeof(ASTR) - 1);
ret = trie_add_string(&trie, (uint8_t *)BSTR, sizeof(BSTR) - 1);
ret = trie_add_string(&trie, (uint8_t *)CSTR, sizeof(CSTR) - 1);
ret = trie_process_str(&trie,
(uint8_t *)tstr, sizeof(tstr) - 1,
TRIE_OPT_MAP_TO_END,
&offset, &offlen
);
TEST_ASSERT_EQUAL(1, ret);
TEST_ASSERT_EQUAL(7, offlen);
TEST_ASSERT_EQUAL_STRING_LEN("abacaba", tstr + offset, offlen);
ret = trie_process_str(&trie,
(uint8_t *)tstr, sizeof(tstr),
TRIE_OPT_MAP_TO_END,
&offset, &offlen
);
TEST_ASSERT_EQUAL(0, ret);
trie_destroy(&trie);
}
TEST(TrieTest, Trie_single_vertex)
{
int ret;
size_t offset;
size_t offlen;
struct trie_container trie;
ret = trie_init(&trie);
ret = trie_process_str(&trie,
(uint8_t *)tstr, sizeof(tstr) - 1,
0,
&offset, &offlen
);
TEST_ASSERT_EQUAL(0, ret);
trie_destroy(&trie);
}
TEST(TrieTest, Trie_uninitialized)
{
int ret;
size_t offset;
size_t offlen;
struct trie_container trie = {0};
// ret = trie_init(&trie);
ret = trie_add_string(&trie, (uint8_t *)ASTR, sizeof(ASTR) - 1);
TEST_ASSERT_EQUAL(-EINVAL, ret);
ret = trie_process_str(&trie,
(uint8_t *)tstr, sizeof(tstr) - 1,
0,
&offset, &offlen
);
TEST_ASSERT_EQUAL(0, ret);
}
TEST_GROUP_RUNNER(TrieTest)
{
RUN_TEST_CASE(TrieTest, Trie_string_adds);
RUN_TEST_CASE(TrieTest, Trie_string_finds);
RUN_TEST_CASE(TrieTest, Trie_string_finds_opt_end);
RUN_TEST_CASE(TrieTest, Trie_single_vertex);
RUN_TEST_CASE(TrieTest, Trie_uninitialized);
}

View File

@@ -34,7 +34,7 @@ export CC CCLD LD CFLAGS LDFLAGS LIBNFNETLINK_CFLAGS LIBNFNETLINK_LIBS LIBMNL_CF
APP:=$(BUILD_DIR)/youtubeUnblock
TEST_APP:=$(BUILD_DIR)/testYoutubeUnblock
SRCS := mangle.c args.c utils.c quic.c tls.c getopt.c quic_crypto.c inet_ntop.c
SRCS := mangle.c args.c utils.c quic.c tls.c getopt.c quic_crypto.c inet_ntop.c trie.c
OBJS := $(SRCS:%.c=$(BUILD_DIR)/%.o)
APP_EXEC := youtubeUnblock.c
APP_OBJ := $(APP_EXEC:%.c=$(BUILD_DIR)/%.o)