Merge pull request #132 from Waujito/custom_fakes

Custom fakes
This commit is contained in:
Vadim Vetrov 2024-10-14 00:25:45 +03:00 committed by GitHub
commit ee56b67d20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 2122 additions and 1373 deletions

View File

@ -228,6 +228,48 @@ jobs:
path: /builder/youtubeUnblock*.ipk
if-no-files-found: error
build-openwrt-luci:
needs: prepare
runs-on: ubuntu-latest
container:
image: openwrt/sdk:x86_64-openwrt-23.05
options: --user root
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: 'openwrt'
- name: Prepare build
env:
VERSION: ${{ needs.prepare.outputs.version }}
SHA: ${{ needs.prepare.outputs.sha }}
run: |
sed -i "s/PKG_REV:=.*$/PKG_REV:=$SHA/;s/PKG_VERSION:=.*$/PKG_VERSION:=$VERSION-$SHA/" youtubeUnblock/Makefile
- name: Build packages
id: build
env:
VERSION: ${{ needs.prepare.outputs.version }}
SHA: ${{ needs.prepare.outputs.sha }}
working-directory: /builder
run: |
echo "src-link youtubeUnblock $GITHUB_WORKSPACE" >> feeds.conf
cat feeds.conf
./scripts/feeds update youtubeUnblock
./scripts/feeds install -a -p youtubeUnblock
make defconfig
make package/luci-app-youtubeUnblock/compile V=s
mv $(find ./bin -type f -name 'luci-app-youtubeUnblock*.ipk') ./luci-app-youtubeUnblock-$VERSION-$SHA.ipk
- name: Upload packages
if: steps.build.outcome == 'success'
uses: actions/upload-artifact@v4
with:
name: luci-app-youtubeUnblock
path: /builder/luci-app-youtubeUnblock*.ipk
if-no-files-found: error
build-entware:
needs: prepare
runs-on: ubuntu-latest
@ -341,3 +383,4 @@ jobs:
files: |
./**/youtubeUnblock*.ipk
./**/youtubeUnblock*.tar.gz
./**/luci-app-youtubeUnblock*.ipk

202
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,202 @@
# Tests whether the youtubeUnblock builds properly
name: "youtubeUnblock build test"
on:
push:
branches: [ "main" ]
paths-ignore:
- '.editorconfig'
- '.gitignore'
- 'LICENSE'
- 'README.md'
pull_request:
branches: [ "main" ]
paths-ignore:
- '.editorconfig'
- '.gitignore'
- 'LICENSE'
- 'README.md'
workflow_dispatch:
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.gh.outputs.version }}
sha: ${{ steps.gh.outputs.sha }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: 'openwrt'
- name: GH
id: gh
env:
REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
echo "version=$(cat youtubeUnblock/Makefile | grep PKG_VERSION | sed 's/PKG_VERSION:=//')" >> $GITHUB_OUTPUT
if [[ "${{ github.event_name }}" != "pull_request" ]]; then
echo "sha=$(echo ${GITHUB_SHA::7})" >> $GITHUB_OUTPUT
else
echo "sha=$(gh api repos/$REPO/commits/main --jq '.sha[:7]')" >> $GITHUB_OUTPUT
fi
build-static:
needs: prepare
name: build-static ${{ matrix.arch }}
runs-on: ubuntu-latest
strategy:
matrix:
arch: [x86_64]
branch: [latest-stable]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
id: build
env:
ARCH: ${{ matrix.arch }}
VERSION: ${{ needs.prepare.outputs.version }}
SHA: ${{ needs.prepare.outputs.sha }}
shell: bash
run: |
make -j$(nproc)
strip -s build/youtubeUnblock
cp -va build/youtubeUnblock .
tar -czvf static-youtubeUnblock-$VERSION-$SHA-$PLATFORM.tar.gz youtubeUnblock youtubeUnblock.service README.md
- name: Upload artifacts
if: steps.build.outcome == 'success'
uses: actions/upload-artifact@v4
with:
name: static-youtubeUnblock-${{ matrix.arch }}
path: ./**/static-youtubeUnblock*.tar.gz
build-kmod:
needs: prepare
name: build-kmod ${{ matrix.kernel_version }}
runs-on: ubuntu-latest
strategy:
matrix:
include:
- kernel_version: "6.6.52"
source: "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.52.tar.xz"
container_version: "24.04"
- kernel_version: "5.15.167"
source: "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.167.tar.xz"
container_version: "24.04"
- kernel_version: "5.4.284"
source: "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.284.tar.xz"
container_version: "24.04"
- kernel_version: "4.19.322"
source: "https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.19.322.tar.xz"
container_version: "24.04"
- kernel_version: "4.4.302"
source: "https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.4.302.tar.xz"
container_version: "24.04"
- kernel_version: "3.10.108"
source: "https://cdn.kernel.org/pub/linux/kernel/v3.x/linux-3.10.108.tar.xz"
container_version: "16.04"
- kernel_version: "3.0.101"
source: "https://cdn.kernel.org/pub/linux/kernel/v3.x/linux-3.0.101.tar.xz"
container_version: "14.04"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Restore builder from cache
id: cache-restore
uses: actions/cache/restore@v4
with:
path: ~/builder.tar
key: builder-${{ matrix.kernel_version }}
- name: Load builder from cache
if: steps.cache-restore.outputs.cache-hit == 'true'
run: |
docker import - builder < ~/builder.tar
- name: Prepare build env
if: steps.cache-restore.outputs.cache-hit != 'true'
run: |
mkdir ~/linux
pwd
ls /
ls ~
- name: Obtain kernel
if: steps.cache-restore.outputs.cache-hit != 'true'
run: |
cd ~/linux
wget ${{ matrix.source }} -O kernel.tar.xz -q
tar -xf kernel.tar.xz
rm -f kernel.tar.xz
/bin/bash -c "mv linux-* linux"
ls
ls linux
- name: Install docker
if: steps.cache-restore.outputs.cache-hit != 'true'
run: |
cd ~/linux
docker pull ubuntu:${{ matrix.container_version }}
docker container create --name ubu_builder -w / ubuntu:${{ matrix.container_version }} tail -f /dev/null
docker container start ubu_builder
docker container exec ubu_builder bash -c "apt update && apt install -y build-essential flex bc bison libelf-dev elfutils libssl-dev"
docker cp ./linux ubu_builder:/linux
- name: Build kernel
if: steps.cache-restore.outputs.cache-hit != 'true'
run: |
cd ~/linux
docker container exec -w /linux ubu_builder bash -c 'make defconfig'
docker container exec -w /linux ubu_builder bash -c 'make -j $(nproc)'
- name: Export container
if: steps.cache-restore.outputs.cache-hit != 'true'
run: |
cd ~/linux
docker container kill ubu_builder
docker container export ubu_builder > ubu_builder.tar
docker container rm ubu_builder
mv ./ubu_builder.tar ~/builder.tar
docker import - builder < ~/builder.tar
- name: Save kernel image to cache
if: steps.cache-restore.outputs.cache-hit != 'true'
id: cache-save
uses: actions/cache/save@v4
with:
path: ~/builder.tar
key: builder-${{ matrix.kernel_version }}
- name: Build kernel module
id: build
env:
VERSION: ${{ needs.prepare.outputs.version }}
SHA: ${{ needs.prepare.outputs.sha }}
shell: bash
run: |
docker run --rm -v ./:/youtubeUnblock -w /youtubeUnblock builder make kmake KERNEL_BUILDER_MAKEDIR:=/linux
tar -czvf kmod-youtubeUnblock-$VERSION-$SHA-linux-${{ matrix.kernel_version }}.tar.gz kyoutubeUnblock.ko
- name: Upload artifacts
if: steps.build.outcome == 'success'
uses: actions/upload-artifact@v4
with:
name: kmod-youtubeUnblock-linux-${{ matrix.kernel_version }}
path: ./**/kmod-youtubeUnblock*.tar.gz

2
Kbuild
View File

@ -1,3 +1,3 @@
obj-m := kyoutubeUnblock.o
kyoutubeUnblock-objs := kytunblock.o mangle.o quic.o utils.o kmod_utils.o kargs.o
kyoutubeUnblock-objs := kytunblock.o mangle.o quic.o utils.o kargs.o tls.o
ccflags-y := -std=gnu99 -DKERNEL_SPACE -Wno-error -Wno-declaration-after-statement

View File

@ -85,7 +85,9 @@ For nftables on OpenWRT rules comes out-of-the-box and stored under `/usr/share/
Now we go to the configuration. For OpenWRT here is configuration via [UCI](https://openwrt.org/docs/guide-user/base-system/uci) and [LuCI](https://openwrt.org/docs/guide-user/luci/start) available (CLI and GUI respectively).
Luci is a configuration interface for your router (which you connect when enter 192.168.1.1 in browser). LuCI configuration lives in **Services->youtubeUnblock** section. It is self descriptive, with description for each flag. Note, that after you push `Save & Apply` button, the configuration is applied automatically and the service is restarted.
For **LuCI** aka **GUI** aka **web-interface of router** you should install **luci-app-youtubeUnblock** package like you did it with the normal youtubeUnblock package. Note, that lists of official opkg feeds should be loaded (**Do it with Update lists option**).
LuCI configuration lives in **Services->youtubeUnblock** section. It is self descriptive, with description for each flag. Note, that after you push `Save & Apply` button, the configuration is applied automatically and the service is restarted.
UCI configuration is available in /etc/config/youtubeUnblock file, in section `youtubeUnblock.youtubeUnblock`. The configuration is done with [flags](#flags). Note, that names of flags are not the same: you should replace `-` with `_`, you shouldn't use leading `--` for flag. Also you will enable toggle flags (without parameters) with `1`.
@ -181,6 +183,9 @@ Available flags:
- `--fake-sni-seq-len=<length>` This flag specifies **youtubeUnblock** to build a complicated construction of fake client hello packets. length determines how much fakes will be sent. Defaults to **1**.
- `--fake-sni-type={default|custom|random}` This flag specifies which faking message type should be used for fake packets. For `random`, the message of random length and with random payload will be sent. For `default` the default payload (sni=www.google.com) is used. And for the `custom` option, the payload from `--fake-custom-payload` section utilized. Defaults to `default`.
- `--fake-custom-payload=<payload>` Useful with `--fake-sni-type=custom`. You should specify the payload for fake message manually. Use hex format: `--fake-custom-payload=0001020304` mean that 5 bytes sequence: `0x00`, `0x01`, `0x02`, `0x03`, `0x04` used as fake.
- `--faking-strategy={randseq|ttl|tcp_check|pastseq|md5sum}` This flag determines the strategy of fake packets invalidation. Defaults to `randseq`
- `randseq` specifies that random sequence/acknowledgemend random will be set. This option may be handled by provider which uses *conntrack* with drop on invalid *conntrack* state firewall rule enabled.
- `ttl` specifies that packet will be invalidated after `--faking-ttl=n` hops. `ttl` is better but may cause issues if unconfigured.
@ -226,8 +231,12 @@ Available flags:
- `--packet-mark=<mark>` Use this option if youtubeUnblock conflicts with other systems rely on packet mark. Note that you may want to change accept rule for iptables to follow the mark.
- `--fbegin` and `--fend` flags: youtubeUnblock supports multiple sets of strategies for specific filters. You may want to initiate a new set after the default one, like: `--sni-domains=googlevideo.com --faking-strategy=md5sum --fbegin --sni-domains=youtube.com --faking-strategy=tcp_check --fend --fbegin --sni-domains=l.google.com --faking-strategy=pastseq --fend`. Note, that the priority of these sets goes backwards: last is first, default (one that does not start with --fbegin) is last. If you start the new section, the default settings are implemented just like youtubeUnblock without any parameters. Note that the config above is just an example and won't work for you.
## Troubleshooting
Check up [this issue](https://github.com/Waujito/youtubeUnblock/issues/148) for useful configs.
If you got troubles with some sites and you sure that they are blocked by SNI (youtube for example), use may play around with [flags](#flags) and their combinations. At first it is recommended to try `--faking-strategy` flag and `--frag-sni-faked=1`.
If you have troubles with some sites being proxied, you can play with flags values. For example, for someone `--faking-strategy=ttl` works. You should specify proper `--fake-sni-ttl=<ttl value>` where ttl is the amount of hops between you and DPI.
@ -319,7 +328,9 @@ You can configure the module with its flags in insmod:
insmod kyoutubeUnblock.ko fake_sni=1 exclude_domains=.ru quic_drop=1
```
Note that the flags names are different from ones used for the regular youtubeUnblock(right like in UCI configuration for OpenWRT): replace `-` with `_` and no leading `--`. Also to configure togglers you should set them to `1` (`silent=1 quic_drop=1`)
Note that the flags names are different from ones used for the regular youtubeUnblock(right like in UCI configuration for OpenWRT): replace `-` with `_` and no leading `--`. Also to configure togglers you should set them to `1` (`quic_drop=1`)
Also a good thig to mention is verbosity. The kernel module combines --trace and --silent option to the one parameter `verbosity`. This parameter accepts 3 arguments: `trace`, `debug` and `silent`. I highly don't recommend to enable `trace` mod on router because it may cause huge problems with performance and even freeze your device.
Also a drop in replacement is supported for all the parameters excluding packet mark. A drop in replacement does not require module restart if you want to change the parameters. You can specify and check the parameters within module's directory inside the sysfs: `/sys/module/kyoutubeUnblock/parameters/`. For example, to set quic_drop to true you may use next command:
```sh

368
args.c
View File

@ -1,5 +1,4 @@
#include "config.h"
#include "raw_replacements.h"
#include <stdbool.h>
#include <errno.h>
#include <stdio.h>
@ -7,54 +6,22 @@
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include "types.h"
#include "args.h"
static char custom_fake_buf[MAX_FAKE_SIZE];
struct config_t config = {
.threads = THREADS_NUM,
.frag_sni_reverse = 1,
.frag_sni_faked = 0,
.fragmentation_strategy = FRAGMENTATION_STRATEGY,
.faking_strategy = FAKING_STRATEGY,
.faking_ttl = FAKE_TTL,
.fake_sni = 1,
.fake_sni_seq_len = 1,
.frag_middle_sni = 1,
.frag_sni_pos = 1,
.use_ipv6 = 1,
.fakeseq_offset = 10000,
.mark = DEFAULT_RAWSOCKET_MARK,
.synfake = 0,
.synfake_len = 0,
.sni_detection = SNI_DETECTION_PARSE,
#ifdef SEG2_DELAY
.seg2_delay = SEG2_DELAY,
#else
.seg2_delay = 0,
#endif
#ifdef USE_GSO
.use_gso = true,
#else
.use_gso = false,
#endif
#ifdef DEBUG
.verbose = 1,
#else
.verbose = 0,
#endif
.domains_str = defaul_snistr,
.domains_strlen = sizeof(defaul_snistr),
.exclude_domains_str = "",
.exclude_domains_strlen = 0,
.queue_start_num = DEFAULT_QUEUE_NUM,
.fake_sni_pkt = fake_sni_old,
.fake_sni_pkt_sz = sizeof(fake_sni_old) - 1, // - 1 for null-terminator
.mark = DEFAULT_RAWSOCKET_MARK,
.use_ipv6 = 1,
.verbose = VERBOSE_DEBUG,
.use_gso = true,
.default_config = default_section_config,
.custom_configs_len = 0
};
#define OPT_SNI_DOMAINS 1
@ -63,6 +30,10 @@ struct config_t config = {
#define OPT_FAKING_TTL 3
#define OPT_FAKING_STRATEGY 10
#define OPT_FAKE_SNI_SEQ_LEN 11
#define OPT_FAKE_SNI_TYPE 27
#define OPT_FAKE_CUSTOM_PAYLOAD 28
#define OPT_START_SECTION 29
#define OPT_END_SECTION 30
#define OPT_FRAG 4
#define OPT_FRAG_SNI_REVERSE 12
#define OPT_FRAG_SNI_FAKED 13
@ -83,7 +54,7 @@ struct config_t config = {
#define OPT_NO_GSO 8
#define OPT_QUEUE_NUM 9
#define OPT_MAX OPT_SNI_DOMAINS
#define OPT_MAX OPT_END_SECTION
static struct option long_opt[] = {
{"help", 0, 0, 'h'},
@ -94,6 +65,8 @@ static struct option long_opt[] = {
{"synfake", 1, 0, OPT_SYNFAKE},
{"synfake-len", 1, 0, OPT_SYNFAKE_LEN},
{"fake-sni-seq-len", 1, 0, OPT_FAKE_SNI_SEQ_LEN},
{"fake-sni-type", 1, 0, OPT_FAKE_SNI_TYPE},
{"fake-custom-payload", 1, 0, OPT_FAKE_CUSTOM_PAYLOAD},
{"faking-strategy", 1, 0, OPT_FAKING_STRATEGY},
{"fake-seq-offset", 1, 0, OPT_FAKE_SEQ_OFFSET},
{"faking-ttl", 1, 0, OPT_FAKING_TTL},
@ -113,6 +86,8 @@ static struct option long_opt[] = {
{"no-ipv6", 0, 0, OPT_NO_IPV6},
{"queue-num", 1, 0, OPT_QUEUE_NUM},
{"packet-mark", 1, 0, OPT_PACKET_MARK},
{"fbegin", 0, 0, OPT_START_SECTION},
{"fend", 0, 0, OPT_END_SECTION},
{0,0,0,0}
};
@ -150,6 +125,8 @@ void print_usage(const char *argv0) {
printf("\t--exclude-domains=<comma separated domain list>\n");
printf("\t--fake-sni={1|0}\n");
printf("\t--fake-sni-seq-len=<length>\n");
printf("\t--fake-sni-type={default|random|custom}\n");
printf("\t--fake-custom-payload=<hex payload>\n");
printf("\t--fake-seq-offset=<offset>\n");
printf("\t--faking-ttl=<ttl>\n");
printf("\t--faking-strategy={randseq|ttl|tcp_check|pastseq|md5sum}\n");
@ -170,16 +147,27 @@ void print_usage(const char *argv0) {
printf("\t--trace\n");
printf("\t--no-gso\n");
printf("\t--no-ipv6\n");
printf("\t--fbegin\n");
printf("\t--fend\n");
printf("\n");
}
int parse_args(int argc, char *argv[]) {
int opt;
int optIdx;
int optIdx = 0;
long num;
struct section_config_t *sect_config = &config.default_config;
#define SECT_ITER_DEFAULT 1
#define SECT_ITER_INSIDE 2
#define SECT_ITER_OUTSIDE 3
int section_iter = SECT_ITER_DEFAULT;
while ((opt = getopt_long(argc, argv, "hv", long_opt, &optIdx)) != -1) {
switch (opt) {
/* config_t scoped configs */
case 'h':
print_usage(argv[0]);
goto stop_exec;
@ -187,49 +175,98 @@ int parse_args(int argc, char *argv[]) {
print_version();
goto stop_exec;
case OPT_TRACE:
if (section_iter != SECT_ITER_DEFAULT)
goto invalid_opt;
config.verbose = 2;
break;
case OPT_SILENT:
if (section_iter != SECT_ITER_DEFAULT)
goto invalid_opt;
config.verbose = 0;
break;
case OPT_NO_GSO:
if (section_iter != SECT_ITER_DEFAULT)
goto invalid_opt;
config.use_gso = 0;
break;
case OPT_NO_IPV6:
if (section_iter != SECT_ITER_DEFAULT)
goto invalid_opt;
config.use_ipv6 = 0;
break;
case OPT_QUIC_DROP:
config.quic_drop = 1;
break;
case OPT_SNI_DOMAINS:
if (!strcmp(optarg, "all")) {
config.all_domains = 1;
}
case OPT_THREADS:
if (section_iter != SECT_ITER_DEFAULT)
goto invalid_opt;
config.domains_str = optarg;
config.domains_strlen = strlen(config.domains_str);
break;
case OPT_EXCLUDE_DOMAINS:
config.exclude_domains_str = optarg;
config.exclude_domains_strlen = strlen(config.exclude_domains_str);
break;
case OPT_SNI_DETECTION:
if (strcmp(optarg, "parse") == 0) {
config.sni_detection = SNI_DETECTION_PARSE;
} else if (strcmp(optarg, "brute") == 0) {
config.sni_detection = SNI_DETECTION_BRUTE;
} else {
num = parse_numeric_option(optarg);
if (errno != 0 || num < 0 || num > MAX_THREADS) {
goto invalid_opt;
}
config.threads = num;
break;
case OPT_QUEUE_NUM:
if (section_iter != SECT_ITER_DEFAULT)
goto invalid_opt;
num = parse_numeric_option(optarg);
if (errno != 0 || num < 0) {
goto invalid_opt;
}
config.queue_start_num = num;
break;
case OPT_PACKET_MARK:
if (section_iter != SECT_ITER_DEFAULT)
goto invalid_opt;
num = parse_numeric_option(optarg);
if (errno != 0 || num < 0) {
goto invalid_opt;
}
config.mark = num;
break;
case OPT_START_SECTION:
if (section_iter != SECT_ITER_DEFAULT && section_iter != SECT_ITER_OUTSIDE)
goto invalid_opt;
sect_config = &config.custom_configs[config.custom_configs_len++];
*sect_config = (struct section_config_t)default_section_config;
section_iter = SECT_ITER_INSIDE;
break;
case OPT_END_SECTION:
if (section_iter != SECT_ITER_INSIDE)
goto invalid_opt;
section_iter = SECT_ITER_OUTSIDE;
sect_config = &config.default_config;
break;
/* section_config_t scoped configs */
case OPT_SNI_DOMAINS:
if (!strcmp(optarg, "all")) {
sect_config->all_domains = 1;
}
sect_config->domains_str = optarg;
sect_config->domains_strlen = strlen(sect_config->domains_str);
break;
case OPT_EXCLUDE_DOMAINS:
sect_config->exclude_domains_str = optarg;
sect_config->exclude_domains_strlen = strlen(sect_config->exclude_domains_str);
break;
case OPT_FRAG:
if (strcmp(optarg, "tcp") == 0) {
config.fragmentation_strategy = FRAG_STRAT_TCP;
sect_config->fragmentation_strategy = FRAG_STRAT_TCP;
} else if (strcmp(optarg, "ip") == 0) {
config.fragmentation_strategy = FRAG_STRAT_IP;
sect_config->fragmentation_strategy = FRAG_STRAT_IP;
} else if (strcmp(optarg, "none") == 0) {
config.fragmentation_strategy = FRAG_STRAT_NONE;
sect_config->fragmentation_strategy = FRAG_STRAT_NONE;
} else {
goto invalid_opt;
}
@ -237,9 +274,9 @@ int parse_args(int argc, char *argv[]) {
break;
case OPT_FRAG_SNI_FAKED:
if (strcmp(optarg, "1") == 0) {
config.frag_sni_faked = 1;
sect_config->frag_sni_faked = 1;
} else if (strcmp(optarg, "0") == 0) {
config.frag_sni_faked = 0;
sect_config->frag_sni_faked = 0;
} else {
goto invalid_opt;
}
@ -247,9 +284,9 @@ int parse_args(int argc, char *argv[]) {
break;
case OPT_FRAG_SNI_REVERSE:
if (strcmp(optarg, "1") == 0) {
config.frag_sni_reverse = 1;
sect_config->frag_sni_reverse = 1;
} else if (strcmp(optarg, "0") == 0) {
config.frag_sni_reverse = 0;
sect_config->frag_sni_reverse = 0;
} else {
goto invalid_opt;
}
@ -257,9 +294,9 @@ int parse_args(int argc, char *argv[]) {
break;
case OPT_FRAG_MIDDLE_SNI:
if (strcmp(optarg, "1") == 0) {
config.frag_middle_sni = 1;
sect_config->frag_middle_sni = 1;
} else if (strcmp(optarg, "0") == 0) {
config.frag_middle_sni = 0;
sect_config->frag_middle_sni = 0;
} else {
goto invalid_opt;
}
@ -271,19 +308,19 @@ int parse_args(int argc, char *argv[]) {
goto invalid_opt;
}
config.frag_sni_pos = num;
sect_config->frag_sni_pos = num;
break;
case OPT_FAKING_STRATEGY:
if (strcmp(optarg, "randseq") == 0) {
config.faking_strategy = FAKE_STRAT_RAND_SEQ;
sect_config->faking_strategy = FAKE_STRAT_RAND_SEQ;
} else if (strcmp(optarg, "ttl") == 0) {
config.faking_strategy = FAKE_STRAT_TTL;
sect_config->faking_strategy = FAKE_STRAT_TTL;
} else if (strcmp(optarg, "tcp_check") == 0) {
config.faking_strategy = FAKE_STRAT_TCP_CHECK;
sect_config->faking_strategy = FAKE_STRAT_TCP_CHECK;
} else if (strcmp(optarg, "pastseq") == 0) {
config.faking_strategy = FAKE_STRAT_PAST_SEQ;
sect_config->faking_strategy = FAKE_STRAT_PAST_SEQ;
} else if (strcmp(optarg, "md5sum") == 0) {
config.faking_strategy = FAKE_STRAT_TCP_MD5SUM;
sect_config->faking_strategy = FAKE_STRAT_TCP_MD5SUM;
} else {
goto invalid_opt;
}
@ -295,21 +332,21 @@ int parse_args(int argc, char *argv[]) {
goto invalid_opt;
}
config.faking_ttl = num;
sect_config->faking_ttl = num;
break;
case OPT_FAKE_SEQ_OFFSET:
num = parse_numeric_option(optarg);
if (errno != 0 || num < 0) {
if (errno != 0) {
goto invalid_opt;
}
config.fakeseq_offset = num;
sect_config->fakeseq_offset = num;
break;
case OPT_FAKE_SNI:
if (strcmp(optarg, "1") == 0) {
config.fake_sni = 1;
sect_config->fake_sni = 1;
} else if (strcmp(optarg, "0") == 0) {
config.fake_sni = 0;
sect_config->fake_sni = 0;
} else {
goto invalid_opt;
}
@ -321,7 +358,44 @@ int parse_args(int argc, char *argv[]) {
goto invalid_opt;
}
config.fake_sni_seq_len = num;
sect_config->fake_sni_seq_len = num;
break;
case OPT_FAKE_SNI_TYPE:
if (strcmp(optarg, "default") == 0) {
sect_config->fake_sni_type = FAKE_PAYLOAD_DEFAULT;
} else if (strcmp(optarg, "random") == 0) {
sect_config->fake_sni_type = FAKE_PAYLOAD_RANDOM;
} else if (strcmp(optarg, "custom") == 0) {
sect_config->fake_sni_type = FAKE_PAYLOAD_CUSTOM;
} else {
goto invalid_opt;
}
break;
case OPT_FAKE_CUSTOM_PAYLOAD: {
uint8_t *const custom_buf = (uint8_t *)custom_fake_buf;
const char *custom_hex_fake = optarg;
size_t custom_hlen = strlen(custom_hex_fake);
if ((custom_hlen & 1) == 1) {
printf("Custom fake hex should be divisible by two\n");
goto invalid_opt;
}
size_t custom_len = custom_hlen >> 1;
if (custom_len > MAX_FAKE_SIZE) {
printf("Custom fake is too large\n");
goto invalid_opt;
}
for (int i = 0; i < custom_len; i++) {
sscanf(custom_hex_fake + (i << 1), "%2hhx", custom_buf + i);
}
sect_config->fake_custom_pkt_sz = custom_len;
sect_config->fake_custom_pkt = (char *)custom_buf;
}
break;
case OPT_FK_WINSIZE:
num = parse_numeric_option(optarg);
@ -329,66 +403,37 @@ int parse_args(int argc, char *argv[]) {
goto invalid_opt;
}
config.fk_winsize = num;
sect_config->fk_winsize = num;
break;
case OPT_SYNFAKE:
if (strcmp(optarg, "1") == 0) {
config.synfake = 1;
} else if (strcmp(optarg, "0") == 0) {
config.synfake = 0;
} else {
goto invalid_opt;
}
break;
case OPT_SYNFAKE_LEN:
num = parse_numeric_option(optarg);
if (errno != 0 || num < 0) {
goto invalid_opt;
}
config.synfake_len = num;
break;
case OPT_SEG2DELAY:
num = parse_numeric_option(optarg);
if (errno != 0 || num < 0) {
goto invalid_opt;
}
config.seg2_delay = num;
sect_config->seg2_delay = num;
break;
case OPT_THREADS:
num = parse_numeric_option(optarg);
if (errno != 0 || num < 0 || num > MAX_THREADS) {
case OPT_QUIC_DROP:
sect_config->quic_drop = 1;
break;
case OPT_SNI_DETECTION:
if (strcmp(optarg, "parse") == 0) {
sect_config->sni_detection = SNI_DETECTION_PARSE;
} else if (strcmp(optarg, "brute") == 0) {
sect_config->sni_detection = SNI_DETECTION_BRUTE;
} else {
goto invalid_opt;
}
config.threads = num;
break;
case OPT_QUEUE_NUM:
num = parse_numeric_option(optarg);
if (errno != 0 || num < 0) {
goto invalid_opt;
}
config.queue_start_num = num;
break;
case OPT_PACKET_MARK:
num = parse_numeric_option(optarg);
if (errno != 0 || num < 0) {
goto invalid_opt;
}
config.mark = num;
break;
default:
goto error;
}
}
// out:
errno = 0;
return 0;
stop_exec:
@ -404,7 +449,24 @@ error:
}
void print_welcome() {
switch (config.fragmentation_strategy) {
if (config.use_gso) {
printf("GSO is enabled\n");
}
if (config.use_ipv6) {
printf("IPv6 is enabled\n");
} else {
printf("IPv6 is disabled\n");
}
printf("Detected %d config sections\n", config.custom_configs_len + 1);
printf("The sections will be processed in ordred they goes in this output");
ITER_CONFIG_SECTIONS(section) {
int section_number = CONFIG_SECTION_NUMBER(section);
printf("Section #%d\n", section_number);
switch (section->fragmentation_strategy) {
case FRAG_STRAT_TCP:
printf("Using TCP segmentation\n");
break;
@ -416,35 +478,35 @@ void print_welcome() {
break;
}
if (config.seg2_delay) {
printf("Some outgoing googlevideo request segments will be delayed for %d ms as of seg2_delay define\n", config.seg2_delay);
if (section->seg2_delay) {
printf("Some outgoing googlevideo request segments will be delayed for %d ms as of seg2_delay define\n", section->seg2_delay);
}
if (config.fake_sni) {
if (section->fake_sni) {
printf("Fake SNI will be sent before each target client hello\n");
} else {
printf("Fake SNI is disabled\n");
}
if (config.frag_sni_reverse) {
if (section->frag_sni_reverse) {
printf("Fragmentation Client Hello will be reversed\n");
}
if (config.frag_sni_faked) {
if (section->frag_sni_faked) {
printf("Fooling packets will be sent near the original Client Hello\n");
}
if (config.fake_sni_seq_len > 1) {
printf("Faking sequence of length %d will be built as fake sni\n", config.fake_sni_seq_len);
if (section->fake_sni_seq_len > 1) {
printf("Faking sequence of length %d will be built as fake sni\n", section->fake_sni_seq_len);
}
switch (config.faking_strategy) {
switch (section->faking_strategy) {
case FAKE_STRAT_TTL:
printf("TTL faking strategy will be used with TTL %d\n", config.faking_ttl);
printf("TTL faking strategy will be used with TTL %d\n", section->faking_ttl);
break;
case FAKE_STRAT_RAND_SEQ:
printf("Random seq faking strategy will be used\n");
printf("Fake seq offset set to %u\n", config.fakeseq_offset);
printf("Fake seq offset set to %u\n", section->fakeseq_offset);
break;
case FAKE_STRAT_TCP_CHECK:
printf("TCP checksum faking strategy will be used\n");
@ -457,35 +519,27 @@ void print_welcome() {
break;
}
if (config.fk_winsize) {
printf("Response TCP window will be set to %d with the appropriate scale\n", config.fk_winsize);
if (section->fk_winsize) {
printf("Response TCP window will be set to %d with the appropriate scale\n", section->fk_winsize);
}
if (config.synfake) {
if (section->synfake) {
printf("Fake SYN payload will be sent with each TCP request SYN packet\n");
}
if (config.use_gso) {
printf("GSO is enabled\n");
}
if (config.use_ipv6) {
printf("IPv6 is enabled\n");
} else {
printf("IPv6 is disabled\n");
}
if (config.quic_drop) {
if (section->quic_drop) {
printf("All QUIC packets will be dropped\n");
}
if (config.sni_detection == SNI_DETECTION_BRUTE) {
if (section->sni_detection == SNI_DETECTION_BRUTE) {
printf("Server Name Extension will be parsed in the bruteforce mode\n");
}
if (config.all_domains) {
if (section->all_domains) {
printf("All Client Hello will be targetted by youtubeUnblock!\n");
} else {
printf("Target sni domains: %s\n", section->domains_str);
}
}
}

135
config.h
View File

@ -5,12 +5,14 @@
#define USER_SPACE
#endif
#include "raw_replacements.h"
typedef int (*raw_send_t)(const unsigned char *data, unsigned int data_len);
/**
* Sends the packet after delay_ms. The function should schedule send and return immediately
* (for example, open daemon thread)
*/
typedef void (*delayed_send_t)(const unsigned char *data, unsigned int data_len, unsigned int delay_ms);
typedef int (*delayed_send_t)(const unsigned char *data, unsigned int data_len, unsigned int delay_ms);
struct instance_config_t {
raw_send_t send_raw_packet;
@ -18,11 +20,10 @@ struct instance_config_t {
};
extern struct instance_config_t instance_config;
struct config_t {
unsigned int queue_start_num;
int threads;
int use_gso;
int use_ipv6;
struct section_config_t {
const char *domains_str;
unsigned int domains_strlen;
int fragmentation_strategy;
int frag_sni_reverse;
int frag_sni_faked;
@ -32,32 +33,97 @@ struct config_t {
unsigned char faking_ttl;
int fake_sni;
unsigned int fake_sni_seq_len;
#define FAKE_PAYLOAD_RANDOM 0
#define FAKE_PAYLOAD_CUSTOM 1
// In default mode all other options will be skipped.
#define FAKE_PAYLOAD_DEFAULT 2
int fake_sni_type;
int quic_drop;
/* In milliseconds */
unsigned int seg2_delay;
int synfake;
unsigned int synfake_len;
const char *exclude_domains_str;
unsigned int exclude_domains_strlen;
unsigned int all_domains;
const char *fake_sni_pkt;
unsigned int fake_sni_pkt_sz;
const char *fake_custom_pkt;
unsigned int fake_custom_pkt_sz;
unsigned int fk_winsize;
int fakeseq_offset;
#define SNI_DETECTION_PARSE 0
#define SNI_DETECTION_BRUTE 1
int sni_detection;
};
#define MAX_CONFIGLIST_LEN 64
struct config_t {
unsigned int queue_start_num;
int threads;
int use_gso;
int use_ipv6;
unsigned int mark;
#define VERBOSE_INFO 0
#define VERBOSE_DEBUG 1
#define VERBOSE_TRACE 2
int verbose;
int quic_drop;
#define SNI_DETECTION_PARSE 0
#define SNI_DETECTION_BRUTE 1
int sni_detection;
/* In milliseconds */
unsigned int seg2_delay;
const char *domains_str;
unsigned int domains_strlen;
const char *exclude_domains_str;
unsigned int exclude_domains_strlen;
unsigned int all_domains;
const char *fake_sni_pkt;
unsigned int fake_sni_pkt_sz;
unsigned int fk_winsize;
unsigned int fakeseq_offset;
unsigned int mark;
int synfake;
unsigned int synfake_len;
struct section_config_t default_config;
struct section_config_t custom_configs[MAX_CONFIGLIST_LEN];
int custom_configs_len;
};
extern struct config_t config;
#define ITER_CONFIG_SECTIONS(section) \
for (struct section_config_t *section = &config.default_config + config.custom_configs_len; section >= &config.default_config; section--)
#define CONFIG_SECTION_NUMBER(section) (int)((section) - &config.default_config)
#define default_section_config { \
.frag_sni_reverse = 1, \
.frag_sni_faked = 0, \
.fragmentation_strategy = FRAGMENTATION_STRATEGY, \
.faking_strategy = FAKING_STRATEGY, \
.faking_ttl = FAKE_TTL, \
.fake_sni = 1, \
.fake_sni_seq_len = 1, \
.fake_sni_type = FAKE_PAYLOAD_DEFAULT, \
.frag_middle_sni = 1, \
.frag_sni_pos = 1, \
.fakeseq_offset = 10000, \
.synfake = 0, \
.synfake_len = 0, \
.quic_drop = 0, \
\
.seg2_delay = 0, \
\
.domains_str = defaul_snistr, \
.domains_strlen = sizeof(defaul_snistr), \
\
.exclude_domains_str = "", \
.exclude_domains_strlen = 0, \
\
.fake_sni_pkt = fake_sni_old, \
.fake_sni_pkt_sz = sizeof(fake_sni_old) - 1, \
.fake_custom_pkt = custom_fake_buf, \
.fake_custom_pkt_sz = 0, \
.sni_detection = SNI_DETECTION_PARSE, \
}
#define MAX_THREADS 16
#ifndef THREADS_NUM
@ -89,19 +155,30 @@ extern struct config_t config;
#define FAKE_TTL 8
// Will invalidate fake packets by out-of-ack_seq out-of-seq request
#define FAKE_STRAT_RAND_SEQ 1
#define FAKE_STRAT_RAND_SEQ (1 << 0)
// Will assume that GGC server is located further than FAKE_TTL
// Thus, Fake packet will be eliminated automatically.
#define FAKE_STRAT_TTL 2
#define FAKE_STRAT_PAST_SEQ 3
#define FAKE_STRAT_TCP_CHECK 4
#define FAKE_STRAT_TCP_MD5SUM 5
#define FAKE_STRAT_TTL (1 << 1)
#define FAKE_STRAT_PAST_SEQ (1 << 2)
#define FAKE_STRAT_TCP_CHECK (1 << 3)
#define FAKE_STRAT_TCP_MD5SUM (1 << 4)
#define FAKE_STRAT_COUNT 5
/**
* This macros iterates through all faking strategies and executes code under it.
* destination strategy will be available under name of `strategy` variable.
*/
#define ITER_FAKE_STRAT(fake_bitmask, strategy) \
for (int strategy = 1; strategy <= (1 << FAKE_STRAT_COUNT); strategy <<= 1) \
if ((fake_bitmask) & strategy)
#ifndef FAKING_STRATEGY
#define FAKING_STRATEGY FAKE_STRAT_PAST_SEQ
#endif
#define MAX_FAKE_SIZE 1300
#if !defined(SILENT) && !defined(KERNEL_SPACE)
#define DEBUG
#endif

269
kargs.c
View File

@ -1,54 +1,27 @@
#include "config.h"
#include "raw_replacements.h"
#include "types.h"
#include <linux/moduleparam.h>
#include "types.h"
#define STR_MAXLEN 2048
static char custom_fake_buf[MAX_FAKE_SIZE];
struct config_t config = {
.frag_sni_reverse = 1,
.frag_sni_faked = 0,
.fragmentation_strategy = FRAGMENTATION_STRATEGY,
.faking_strategy = FAKING_STRATEGY,
.faking_ttl = FAKE_TTL,
.fake_sni = 1,
.fake_sni_seq_len = 1,
.frag_middle_sni = 1,
.frag_sni_pos = 1,
.use_ipv6 = 1,
.fakeseq_offset = 10000,
.mark = DEFAULT_RAWSOCKET_MARK,
.synfake = 0,
.synfake_len = 0,
.sni_detection = SNI_DETECTION_PARSE,
#ifdef SEG2_DELAY
.seg2_delay = SEG2_DELAY,
#else
.seg2_delay = 0,
#endif
#ifdef USE_GSO
.use_gso = 1,
#else
.use_gso = false,
#endif
#ifdef DEBUG
.verbose = 2,
#else
.verbose = 1,
#endif
.domains_str = defaul_snistr,
.domains_strlen = sizeof(defaul_snistr),
.threads = THREADS_NUM,
.queue_start_num = DEFAULT_QUEUE_NUM,
.fake_sni_pkt = fake_sni_old,
.fake_sni_pkt_sz = sizeof(fake_sni_old) - 1, // - 1 for null-terminator
.mark = DEFAULT_RAWSOCKET_MARK,
.use_ipv6 = 1,
.verbose = VERBOSE_DEBUG,
.use_gso = 1,
.default_config = default_section_config,
.custom_configs_len = 0
};
#define def_section (&config.default_config)
static int unumeric_set(const char *val, const struct kernel_param *kp) {
int n = 0, ret;
ret = kstrtoint(val, 10, &n);
@ -107,18 +80,19 @@ static const struct kernel_param_ops inverse_boolean_ops = {
.get = inverse_boolean_get,
};
module_param_cb(fake_sni, &boolean_parameter_ops, &config.fake_sni, 0664);
module_param_cb(fake_sni_seq_len, &unumeric_parameter_ops, &config.fake_sni_seq_len, 0664);
module_param_cb(faking_ttl, &unumeric_parameter_ops, &config.faking_ttl, 0664);
module_param_cb(fake_seq_offset, &unumeric_parameter_ops, &config.fakeseq_offset, 0664);
module_param_cb(frag_sni_reverse, &unumeric_parameter_ops, &config.frag_sni_reverse, 0664);
module_param_cb(frag_sni_faked, &boolean_parameter_ops, &config.frag_sni_faked, 0664);
module_param_cb(frag_middle_sni, &boolean_parameter_ops, &config.frag_middle_sni, 0664);
module_param_cb(frag_sni_pos, &unumeric_parameter_ops, &config.frag_sni_pos, 0664);
module_param_cb(fk_winsize, &unumeric_parameter_ops, &config.fk_winsize, 0664);
module_param_cb(synfake, &boolean_parameter_ops, &config.synfake, 0664);
module_param_cb(synfake_len, &unumeric_parameter_ops, &config.synfake_len, 0664);
module_param_cb(fake_sni, &boolean_parameter_ops, &def_section->fake_sni, 0664);
module_param_cb(fake_sni_seq_len, &unumeric_parameter_ops, &def_section->fake_sni_seq_len, 0664);
module_param_cb(faking_ttl, &unumeric_parameter_ops, &def_section->faking_ttl, 0664);
module_param_cb(fake_seq_offset, &unumeric_parameter_ops, &def_section->fakeseq_offset, 0664);
module_param_cb(frag_sni_reverse, &unumeric_parameter_ops, &def_section->frag_sni_reverse, 0664);
module_param_cb(frag_sni_faked, &boolean_parameter_ops, &def_section->frag_sni_faked, 0664);
module_param_cb(frag_middle_sni, &boolean_parameter_ops, &def_section->frag_middle_sni, 0664);
module_param_cb(frag_sni_pos, &unumeric_parameter_ops, &def_section->frag_sni_pos, 0664);
module_param_cb(fk_winsize, &unumeric_parameter_ops, &def_section->fk_winsize, 0664);
module_param_cb(synfake, &boolean_parameter_ops, &def_section->synfake, 0664);
module_param_cb(synfake_len, &unumeric_parameter_ops, &def_section->synfake_len, 0664);
module_param_cb(packet_mark, &unumeric_parameter_ops, &config.mark, 0664);
// module_param_cb(seg2delay, &unumeric_parameter_ops, &def_section->seg2_delay, 0664);
static int sni_domains_set(const char *val, const struct kernel_param *kp) {
size_t len;
@ -137,13 +111,13 @@ static int sni_domains_set(const char *val, const struct kernel_param *kp) {
ret = param_set_charp(val, kp);
if (ret < 0) {
config.domains_strlen = 0;
def_section->domains_strlen = 0;
} else {
config.domains_strlen = len;
def_section->domains_strlen = len;
if (len == 3 && !strncmp(val, "all", len)) {
config.all_domains = 1;
def_section->all_domains = 1;
} else {
config.all_domains = 0;
def_section->all_domains = 0;
}
}
@ -156,7 +130,7 @@ static const struct kernel_param_ops sni_domains_ops = {
.get = param_get_charp,
};
module_param_cb(sni_domains, &sni_domains_ops, &config.domains_str, 0664);
module_param_cb(sni_domains, &sni_domains_ops, &def_section->domains_str, 0664);
static int exclude_domains_set(const char *val, const struct kernel_param *kp) {
size_t len;
@ -171,9 +145,9 @@ static int exclude_domains_set(const char *val, const struct kernel_param *kp) {
ret = param_set_charp(val, kp);
if (ret < 0) {
config.exclude_domains_strlen = 0;
def_section->exclude_domains_strlen = 0;
} else {
config.exclude_domains_strlen = len;
def_section->exclude_domains_strlen = len;
}
return ret;
@ -184,36 +158,62 @@ static const struct kernel_param_ops exclude_domains_ops = {
.get = param_get_charp,
};
module_param_cb(exclude_domains, &exclude_domains_ops, &config.exclude_domains_str, 0664);
module_param_cb(exclude_domains, &exclude_domains_ops, &def_section->exclude_domains_str, 0664);
module_param_cb(no_ipv6, &inverse_boolean_ops, &config.use_ipv6, 0664);
module_param_cb(silent, &inverse_boolean_ops, &config.verbose, 0664);
module_param_cb(quic_drop, &boolean_parameter_ops, &config.quic_drop, 0664);
module_param_cb(quic_drop, &boolean_parameter_ops, &def_section->quic_drop, 0664);
static int verbose_trace_set(const char *val, const struct kernel_param *kp) {
int n = 0, ret;
ret = kstrtoint(val, 10, &n);
if (ret != 0 || (n != 0 && n != 1))
return -EINVAL;
static int verbosity_set(const char *val, const struct kernel_param *kp) {
size_t len;
if (n) {
n = VERBOSE_TRACE;
} else {
n = VERBOSE_DEBUG;
len = strnlen(val, STR_MAXLEN + 1);
if (len == STR_MAXLEN + 1) {
pr_err("%s: string parameter too long\n", kp->name);
return -ENOSPC;
}
if (len >= 1 && val[len - 1] == '\n') {
len--;
}
if (strncmp(val, "trace", len) == 0) {
*(int *)kp->arg = VERBOSE_TRACE;
} else if (strncmp(val, "debug", len) == 0) {
*(int *)kp->arg = VERBOSE_DEBUG;
} else if (strncmp(val, "silent", len) == 0) {
*(int *)kp->arg = VERBOSE_INFO;
} else {
return -EINVAL;
}
if (kp->arg == NULL)
return -EINVAL;
*(int *)kp->arg = n;
return 0;
}
static const struct kernel_param_ops verbose_trace_ops = {
.set = verbose_trace_set,
.get = param_get_int,
static int verbosity_get(char *buffer, const struct kernel_param *kp) {
switch (*(int *)kp->arg) {
case VERBOSE_TRACE:
strcpy(buffer, "trace\n");
break;
case VERBOSE_DEBUG:
strcpy(buffer, "debug\n");
break;
case VERBOSE_INFO:
strcpy(buffer, "silent\n");
break;
default:
strcpy(buffer, "unknown\n");
}
return strlen(buffer);
}
static const struct kernel_param_ops verbosity_ops = {
.set = verbosity_set,
.get = verbosity_get,
};
module_param_cb(trace, &verbose_trace_ops, &config.verbose, 0664);
module_param_cb(verbosity, &verbosity_ops, &config.verbose, 0664);
static int frag_strat_set(const char *val, const struct kernel_param *kp) {
size_t len;
@ -264,7 +264,7 @@ static const struct kernel_param_ops frag_strat_ops = {
.get = frag_strat_get,
};
module_param_cb(fragmentation_strategy, &frag_strat_ops, &config.fragmentation_strategy, 0664);
module_param_cb(fragmentation_strategy, &frag_strat_ops, &def_section->fragmentation_strategy, 0664);
static int fake_strat_set(const char *val, const struct kernel_param *kp) {
size_t len;
@ -325,7 +325,7 @@ static const struct kernel_param_ops fake_strat_ops = {
.get = fake_strat_get,
};
module_param_cb(faking_strategy, &fake_strat_ops, &config.faking_strategy, 0664);
module_param_cb(faking_strategy, &fake_strat_ops, &def_section->faking_strategy, 0664);
static int sni_detection_set(const char *val, const struct kernel_param *kp) {
size_t len;
@ -371,4 +371,111 @@ static const struct kernel_param_ops sni_detection_ops = {
.get = sni_detection_get,
};
module_param_cb(sni_detection, &sni_detection_ops, &config.sni_detection, 0664);
module_param_cb(sni_detection, &sni_detection_ops, &def_section->sni_detection, 0664);
static int fake_type_set(const char *val, const struct kernel_param *kp) {
size_t len;
len = strnlen(val, STR_MAXLEN + 1);
if (len == STR_MAXLEN + 1) {
pr_err("%s: string parameter too long\n", kp->name);
return -ENOSPC;
}
if (len >= 1 && val[len - 1] == '\n') {
len--;
}
if (strncmp(val, "default", len) == 0) {
*(int *)kp->arg = FAKE_PAYLOAD_DEFAULT;
} else if (strncmp(val, "custom", len) == 0) {
*(int *)kp->arg = FAKE_PAYLOAD_CUSTOM;
} else if (strncmp(val, "random", len) == 0) {
*(int *)kp->arg = FAKE_PAYLOAD_RANDOM;
} else {
return -EINVAL;
}
return 0;
}
static int fake_type_get(char *buffer, const struct kernel_param *kp) {
switch (*(int *)kp->arg) {
case FAKE_PAYLOAD_DEFAULT:
strcpy(buffer, "default\n");
break;
case FAKE_PAYLOAD_RANDOM:
strcpy(buffer, "random\n");
break;
case FAKE_PAYLOAD_CUSTOM:
strcpy(buffer, "custom\n");
break;
default:
strcpy(buffer, "unknown\n");
}
return strlen(buffer);
}
static const struct kernel_param_ops fake_type_ops = {
.set = fake_type_set,
.get = fake_type_get,
};
module_param_cb(fake_sni_type, &fake_type_ops, &def_section->fake_sni_type, 0664);
static int fake_custom_pl_set(const char *val, const struct kernel_param *kp) {
size_t len;
len = strnlen(val, STR_MAXLEN + 1);
if (len == STR_MAXLEN + 1) {
pr_err("%s: string parameter too long\n", kp->name);
return -ENOSPC;
}
if (len >= 1 && val[len - 1] == '\n') {
len--;
}
uint8_t *const custom_buf = (uint8_t *)custom_fake_buf;
const char *custom_hex_fake = val;
size_t custom_hlen = len;
if ((custom_hlen & 1) == 1) {
return -EINVAL;
}
size_t custom_len = custom_hlen >> 1;
if (custom_len > MAX_FAKE_SIZE) {
return -EINVAL;
}
for (int i = 0; i < custom_len; i++) {
sscanf(custom_hex_fake + (i << 1), "%2hhx", custom_buf + i);
}
def_section->fake_custom_pkt_sz = custom_len;
def_section->fake_custom_pkt = (char *)custom_buf;
return 0;
}
static int fake_custom_pl_get(char *buffer, const struct kernel_param *kp) {
int cflen = def_section->fake_custom_pkt_sz;
const uint8_t *cbf_data = def_section->fake_custom_pkt;
int bflen = def_section->fake_custom_pkt_sz << 1;
for (int i = 0; i < cflen; i++) {
sprintf(buffer + (i << 1), "%02x", *((unsigned char *)cbf_data + i));
}
return bflen;
}
static const struct kernel_param_ops fake_custom_pl_ops = {
.set = fake_custom_pl_set,
.get = fake_custom_pl_get,
};
module_param_cb(fake_custom_payload, &fake_custom_pl_ops, &def_section->fake_custom_pkt, 0664);

View File

@ -1,226 +0,0 @@
#ifndef KERNEL_SPACE
#error "You are trying to compile the kernel module not in the kernel space"
#endif
#include "kmod_utils.h"
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/socket.h>
#include <linux/net.h>
#include "config.h"
#include "utils.h"
#include "logging.h"
static struct socket *rawsocket;
static struct socket *raw6socket;
int open_raw_socket(void) {
int ret = 0;
ret = sock_create(AF_INET, SOCK_RAW, IPPROTO_RAW, &rawsocket);
if (ret < 0) {
pr_alert("Unable to create raw socket\n");
goto err;
}
// That's funny, but this is how it is done in the kernel
// https://elixir.bootlin.com/linux/v3.17.7/source/net/core/sock.c#L916
rawsocket->sk->sk_mark=config.mark;
return 0;
err:
return ret;
}
void close_raw_socket(void) {
sock_release(rawsocket);
}
static int send_raw_ipv4(const uint8_t *pkt, uint32_t pktlen) {
int ret = 0;
if (pktlen > AVAILABLE_MTU) return -ENOMEM;
struct iphdr *iph;
if ((ret = ip4_payload_split(
(uint8_t *)pkt, pktlen, &iph, NULL, NULL, NULL)) < 0) {
return ret;
}
struct sockaddr_in daddr = {
.sin_family = AF_INET,
.sin_port = 0,
.sin_addr = {
.s_addr = iph->daddr
}
};
struct msghdr msg;
struct kvec iov;
memset(&msg, 0, sizeof(msg));
iov.iov_base = (__u8 *)pkt;
iov.iov_len = pktlen;
msg.msg_flags = 0;
msg.msg_name = &daddr;
msg.msg_namelen = sizeof(struct sockaddr_in);
msg.msg_control = NULL;
msg.msg_controllen = 0;
ret = kernel_sendmsg(rawsocket, &msg, &iov, 1, pktlen);
return ret;
}
int open_raw6_socket(void) {
int ret = 0;
ret = sock_create(AF_INET6, SOCK_RAW, IPPROTO_RAW, &raw6socket);
if (ret < 0) {
pr_alert("Unable to create raw socket\n");
goto err;
}
// That's funny, but this is how it is done in the kernel
// https://elixir.bootlin.com/linux/v3.17.7/source/net/core/sock.c#L916
raw6socket->sk->sk_mark=config.mark;
return 0;
err:
return ret;
}
void close_raw6_socket(void) {
sock_release(raw6socket);
}
int send_raw_ipv6(const uint8_t *pkt, uint32_t pktlen) {
int ret = 0;
if (pktlen > AVAILABLE_MTU) return -ENOMEM;
struct ip6_hdr *iph;
if ((ret = ip6_payload_split(
(uint8_t *)pkt, pktlen, &iph, NULL, NULL, NULL)) < 0) {
return ret;
}
struct sockaddr_in6 daddr = {
.sin6_family = AF_INET6,
/* Always 0 for raw socket */
.sin6_port = 0,
.sin6_addr = iph->ip6_dst
};
struct kvec iov;
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
iov.iov_base = (__u8 *)pkt;
iov.iov_len = pktlen;
msg.msg_flags = 0;
msg.msg_name = &daddr;
msg.msg_namelen = sizeof(struct sockaddr_in6);
msg.msg_control = NULL;
msg.msg_controllen = 0;
ret = kernel_sendmsg(raw6socket, &msg, &iov, 1, pktlen);
return ret;
}
int send_raw_socket(const uint8_t *pkt, uint32_t pktlen) {
int ret;
if (pktlen > AVAILABLE_MTU) {
lgdebug("The packet is too big and may cause issues!");
NETBUF_ALLOC(buff1, MAX_PACKET_SIZE);
if (!NETBUF_CHECK(buff1)) {
lgerror("Allocation error", -ENOMEM);
return -ENOMEM;
}
NETBUF_ALLOC(buff2, MAX_PACKET_SIZE);
if (!NETBUF_CHECK(buff2)) {
lgerror("Allocation error", -ENOMEM);
NETBUF_FREE(buff2);
return -ENOMEM;
}
uint32_t buff1_size = MAX_PACKET_SIZE;
uint32_t buff2_size = MAX_PACKET_SIZE;
switch (config.fragmentation_strategy) {
case FRAG_STRAT_TCP:
if ((ret = tcp_frag(pkt, pktlen, AVAILABLE_MTU-128,
buff1, &buff1_size, buff2, &buff2_size)) < 0) {
goto erret_lc;
}
break;
case FRAG_STRAT_IP:
if ((ret = ip4_frag(pkt, pktlen, AVAILABLE_MTU-128,
buff1, &buff1_size, buff2, &buff2_size)) < 0) {
goto erret_lc;
}
break;
default:
pr_info("send_raw_socket: Packet is too big but fragmentation is disabled!");
ret = -EINVAL;
goto erret_lc;
}
int sent = 0;
ret = send_raw_socket(buff1, buff1_size);
if (ret >= 0) sent += ret;
else {
goto erret_lc;
}
ret = send_raw_socket(buff2, buff2_size);
if (ret >= 0) sent += ret;
else {
goto erret_lc;
}
NETBUF_FREE(buff1);
NETBUF_FREE(buff2);
return sent;
erret_lc:
NETBUF_FREE(buff1);
NETBUF_FREE(buff2);
return ret;
}
int ipvx = netproto_version(pkt, pktlen);
if (ipvx == IP4VERSION)
return send_raw_ipv4(pkt, pktlen);
else if (ipvx == IP6VERSION)
return send_raw_ipv6(pkt, pktlen);
printf("proto version %d is unsupported\n", ipvx);
return -EINVAL;
}
void delay_packet_send(const unsigned char *data, unsigned int data_len, unsigned int delay_ms) {
pr_info("delay_packet_send won't work on current youtubeUnblock version");
send_raw_socket(data, data_len);
}
struct instance_config_t instance_config = {
.send_raw_packet = send_raw_socket,
.send_delayed_packet = delay_packet_send,
};

View File

@ -1,14 +0,0 @@
#include "types.h"
#ifndef KMOD_UTILS_H
#define KMOD_UTILS_H
int open_raw_socket(void);
void close_raw_socket(void);
int open_raw6_socket(void);
void close_raw6_socket(void);
int send_raw_ipv6(const uint8_t *pkt, uint32_t pktlen);
int send_raw_socket(const uint8_t *pkt, uint32_t pktlen);
void delay_packet_send(const unsigned char *data, unsigned int data_len, unsigned int delay_ms);
#endif /* KMOD_UTILS_H */

View File

@ -1,14 +1,16 @@
#include "nf_wrapper.h"
#ifndef KERNEL_SPACE
#error "You are trying to compile the kernel module not in the kernel space"
#endif
// Kernel module for youtubeUnblock.
// Make with make kmake && sudo iptables -t mangle -D OUTPUT 1 && sudo make kreload && sudo iptables -t mangle -I OUTPUT -p tcp -j YTUNBLOCK
// Build with make kmake
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
@ -18,12 +20,277 @@
#include "config.h"
#include "utils.h"
#include "logging.h"
#include "kmod_utils.h"
MODULE_LICENSE("GPL");
MODULE_VERSION("0.3.2");
MODULE_AUTHOR("Vadim Vetrov <vetrovvd@gmail.com>");
MODULE_DESCRIPTION("Linux kernel module for youtube unblock");
MODULE_DESCRIPTION("Linux kernel module for youtubeUnblock");
static struct socket *rawsocket;
static struct socket *raw6socket;
static int open_raw_socket(void) {
int ret = 0;
ret = sock_create(AF_INET, SOCK_RAW, IPPROTO_RAW, &rawsocket);
if (ret < 0) {
pr_alert("Unable to create raw socket\n");
goto err;
}
// That's funny, but this is how it is done in the kernel
// https://elixir.bootlin.com/linux/v3.17.7/source/net/core/sock.c#L916
rawsocket->sk->sk_mark=config.mark;
return 0;
err:
return ret;
}
static void close_raw_socket(void) {
sock_release(rawsocket);
}
static int send_raw_ipv4(const uint8_t *pkt, uint32_t pktlen) {
int ret = 0;
if (pktlen > AVAILABLE_MTU) return -ENOMEM;
struct iphdr *iph;
if ((ret = ip4_payload_split(
(uint8_t *)pkt, pktlen, &iph, NULL, NULL, NULL)) < 0) {
return ret;
}
struct sockaddr_in daddr = {
.sin_family = AF_INET,
.sin_port = 0,
.sin_addr = {
.s_addr = iph->daddr
}
};
struct msghdr msg;
struct kvec iov;
memset(&msg, 0, sizeof(msg));
iov.iov_base = (__u8 *)pkt;
iov.iov_len = pktlen;
msg.msg_flags = 0;
msg.msg_name = &daddr;
msg.msg_namelen = sizeof(struct sockaddr_in);
msg.msg_control = NULL;
msg.msg_controllen = 0;
ret = kernel_sendmsg(rawsocket, &msg, &iov, 1, pktlen);
return ret;
}
static int open_raw6_socket(void) {
int ret = 0;
ret = sock_create(AF_INET6, SOCK_RAW, IPPROTO_RAW, &raw6socket);
if (ret < 0) {
pr_alert("Unable to create raw socket\n");
goto err;
}
// That's funny, but this is how it is done in the kernel
// https://elixir.bootlin.com/linux/v3.17.7/source/net/core/sock.c#L916
raw6socket->sk->sk_mark=config.mark;
return 0;
err:
return ret;
}
static void close_raw6_socket(void) {
sock_release(raw6socket);
}
static int send_raw_ipv6(const uint8_t *pkt, uint32_t pktlen) {
int ret = 0;
if (pktlen > AVAILABLE_MTU) return -ENOMEM;
struct ip6_hdr *iph;
if ((ret = ip6_payload_split(
(uint8_t *)pkt, pktlen, &iph, NULL, NULL, NULL)) < 0) {
return ret;
}
struct sockaddr_in6 daddr = {
.sin6_family = AF_INET6,
/* Always 0 for raw socket */
.sin6_port = 0,
.sin6_addr = iph->ip6_dst
};
struct kvec iov;
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
iov.iov_base = (__u8 *)pkt;
iov.iov_len = pktlen;
msg.msg_flags = 0;
msg.msg_name = &daddr;
msg.msg_namelen = sizeof(struct sockaddr_in6);
msg.msg_control = NULL;
msg.msg_controllen = 0;
ret = kernel_sendmsg(raw6socket, &msg, &iov, 1, pktlen);
return ret;
}
static int send_raw_socket(const uint8_t *pkt, uint32_t pktlen) {
int ret;
if (pktlen > AVAILABLE_MTU) {
lgdebug("The packet is too big and may cause issues!");
NETBUF_ALLOC(buff1, MAX_PACKET_SIZE);
if (!NETBUF_CHECK(buff1)) {
lgerror("Allocation error", -ENOMEM);
return -ENOMEM;
}
NETBUF_ALLOC(buff2, MAX_PACKET_SIZE);
if (!NETBUF_CHECK(buff2)) {
lgerror("Allocation error", -ENOMEM);
NETBUF_FREE(buff2);
return -ENOMEM;
}
uint32_t buff1_size = MAX_PACKET_SIZE;
uint32_t buff2_size = MAX_PACKET_SIZE;
if ((ret = tcp_frag(pkt, pktlen, AVAILABLE_MTU-128,
buff1, &buff1_size, buff2, &buff2_size)) < 0) {
goto erret_lc;
}
int sent = 0;
ret = send_raw_socket(buff1, buff1_size);
if (ret >= 0) sent += ret;
else {
goto erret_lc;
}
ret = send_raw_socket(buff2, buff2_size);
if (ret >= 0) sent += ret;
else {
goto erret_lc;
}
NETBUF_FREE(buff1);
NETBUF_FREE(buff2);
return sent;
erret_lc:
NETBUF_FREE(buff1);
NETBUF_FREE(buff2);
return ret;
}
int ipvx = netproto_version(pkt, pktlen);
if (ipvx == IP4VERSION) {
return send_raw_ipv4(pkt, pktlen);
} else if (ipvx == IP6VERSION) {
return send_raw_ipv6(pkt, pktlen);
} else {
printf("proto version %d is unsupported\n", ipvx);
return -EINVAL;
}
lgtrace_addp("raw_sock_send: %d", ret);
return ret;
}
static int delay_packet_send(const unsigned char *data, unsigned int data_len, unsigned int delay_ms) {
pr_info("delay_packet_send won't work on current youtubeUnblock version");
return send_raw_socket(data, data_len);
}
struct instance_config_t instance_config = {
.send_raw_packet = send_raw_socket,
.send_delayed_packet = delay_packet_send,
};
/* If this is a Red Hat-based kernel (Red Hat, CentOS, Fedora, etc)... */
#ifdef RHEL_RELEASE_CODE
#if RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(7, 2)
#define NF_CALLBACK(name, skb) unsigned int name( \
const struct nf_hook_ops *ops, \
struct sk_buff *skb, \
const struct net_device *in, \
const struct net_device *out, \
const struct nf_hook_state *state) \
#elif RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(7, 0)
#define NF_CALLBACK(name, skb) unsigned int name( \
const struct nf_hook_ops *ops, \
struct sk_buff *skb, \
const struct net_device *in, \
const struct net_device *out, \
int (*okfn)(struct sk_buff *))
#else
#error "Sorry; this version of RHEL is not supported because it's kind of old."
#endif /* RHEL_RELEASE_CODE >= x */
/* If this NOT a RedHat-based kernel (Ubuntu, Debian, SuSE, etc)... */
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
#define NF_CALLBACK(name, skb) unsigned int name( \
void *priv, \
struct sk_buff *skb, \
const struct nf_hook_state *state)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
#define NF_CALLBACK(name, skb) unsigned int name( \
const struct nf_hook_ops *ops, \
struct sk_buff *skb, \
const struct nf_hook_state *state)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
#define NF_CALLBACK(name, skb) unsigned int name( \
const struct nf_hook_ops *ops, \
struct sk_buff *skb, \
const struct net_device *in, \
const struct net_device *out, \
int (*okfn)(struct sk_buff *))
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 0)
#define NF_CALLBACK(name, skb) unsigned int name( \
unsigned int hooknum, \
struct sk_buff *skb, \
const struct net_device *in, \
const struct net_device *out, \
int (*okfn)(struct sk_buff *))
#else
#error "Linux < 3.0 isn't supported at all."
#endif /* LINUX_VERSION_CODE > n */
#endif /* RHEL or not RHEL */
static NF_CALLBACK(ykb_nf_hook, skb) {
int ret;

901
mangle.c

File diff suppressed because it is too large Load Diff

View File

@ -2,38 +2,12 @@
#define YU_MANGLE_H
#include "types.h"
/**
* Result of analyze_tls_data function
*/
struct tls_verdict {
int target_sni; /* google video hello packet */
int sni_offset; /* offset from start of tcp _payload_ */
int sni_len;
};
/**
* Processes the packet and finds TLS Client Hello information inside it.
* data pointer points to start of TLS Message (TCP Payload)
*/
struct tls_verdict analyze_tls_data(const uint8_t *data, uint32_t dlen);
/**
* Generates fake client hello message
*/
int gen_fake_sni(const void *iph, uint32_t iph_len,
const struct tcphdr *tcph, uint32_t tcph_len,
uint8_t *buf, uint32_t *buflen);
/**
* Invalidates the raw packet. The function aims to invalid the packet
* in such way as it will be accepted by DPI, but dropped by target server
*/
int fail_packet(uint8_t *payload, uint32_t *plen, uint32_t avail_buflen);
#include "tls.h"
#define PKT_ACCEPT 0
#define PKT_DROP 1
// Used for section config
#define PKT_CONTINUE 2
/**
* Processes the packet and returns verdict.
@ -46,28 +20,30 @@ int process_packet(const uint8_t *packet, uint32_t packet_len);
* Processe the TCP packet.
* Returns verdict.
*/
int process_tcp_packet(const uint8_t *raw_payload, uint32_t raw_payload_len);
int process_tcp_packet(const struct section_config_t *section, const uint8_t *raw_payload, uint32_t raw_payload_len);
/**
* Processes the UDP packet.
* Returns verdict.
*/
int process_udp_packet(const uint8_t *pkt, uint32_t pktlen);
int process_udp_packet(const struct section_config_t *section, const uint8_t *pkt, uint32_t pktlen);
/**
* Sends fake client hello.
*/
int post_fake_sni(const void *iph, unsigned int iph_len,
const struct tcphdr *tcph, unsigned int tcph_len,
unsigned char sequence_len);
int post_fake_sni(struct fake_type f_type,
const void *iph, unsigned int iph_len,
const struct tcphdr *tcph, unsigned int tcph_len);
/**
* Splits packet by poses and posts.
* Poses are relative to start of TCP payload.
* dvs used internally and should be zero.
*/
int send_tcp_frags(
int send_tcp_frags(const struct section_config_t *section,
const uint8_t *packet, uint32_t pktlen,
const uint32_t *poses, uint32_t poses_len, uint32_t dvs);
@ -76,7 +52,7 @@ int send_tcp_frags(
* Poses are relative to start of TCP payload.
* dvs used internally and should be zero.
*/
int send_ip4_frags(
int send_ip4_frags(const struct section_config_t *section,
const uint8_t *packet, uint32_t pktlen,
const uint32_t *poses, uint32_t poses_len, uint32_t dvs);
#endif /* YU_MANGLE_H */

View File

@ -1,84 +0,0 @@
/**
* Thanks https://github.com/NICMx/Jool/blob/5f60dcda5944b01cc43c3be342aad26af8161bcb/include/nat64/mod/common/nf_wrapper.h for mapped kernel versions
*/
#ifndef _JOOL_MOD_NF_WRAPPER_H
#define _JOOL_MOD_NF_WRAPPER_H
/**
* @file
* The kernel API is far from static. In particular, the Netfilter packet entry
* function keeps changing. nf_hook.c, the file where we declare our packet
* entry function, has been quite difficult to read for a while now. It's pretty
* amusing, because we don't even use any of the noisy arguments.
*
* This file declares a usable function header that abstracts away all those
* useless arguments.
*/
#include <linux/version.h>
/* If this is a Red Hat-based kernel (Red Hat, CentOS, Fedora, etc)... */
#ifdef RHEL_RELEASE_CODE
#if RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(7, 2)
#define NF_CALLBACK(name, skb) unsigned int name( \
const struct nf_hook_ops *ops, \
struct sk_buff *skb, \
const struct net_device *in, \
const struct net_device *out, \
const struct nf_hook_state *state) \
#elif RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(7, 0)
#define NF_CALLBACK(name, skb) unsigned int name( \
const struct nf_hook_ops *ops, \
struct sk_buff *skb, \
const struct net_device *in, \
const struct net_device *out, \
int (*okfn)(struct sk_buff *))
#else
#error "Sorry; this version of RHEL is not supported because it's kind of old."
#endif /* RHEL_RELEASE_CODE >= x */
/* If this NOT a RedHat-based kernel (Ubuntu, Debian, SuSE, etc)... */
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)
#define NF_CALLBACK(name, skb) unsigned int name( \
void *priv, \
struct sk_buff *skb, \
const struct nf_hook_state *state)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
#define NF_CALLBACK(name, skb) unsigned int name( \
const struct nf_hook_ops *ops, \
struct sk_buff *skb, \
const struct nf_hook_state *state)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0)
#define NF_CALLBACK(name, skb) unsigned int name( \
const struct nf_hook_ops *ops, \
struct sk_buff *skb, \
const struct net_device *in, \
const struct net_device *out, \
int (*okfn)(struct sk_buff *))
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 0)
#define NF_CALLBACK(name, skb) unsigned int name( \
unsigned int hooknum, \
struct sk_buff *skb, \
const struct net_device *in, \
const struct net_device *out, \
int (*okfn)(struct sk_buff *))
#else
#error "Linux < 3.0 isn't supported at all."
#endif /* LINUX_VERSION_CODE > n */
#endif /* RHEL or not RHEL */
#endif /* _JOOL_MOD_NF_WRAPPER_H */

354
tls.c Normal file
View File

@ -0,0 +1,354 @@
#include "types.h"
#include "tls.h"
#include "config.h"
#include "logging.h"
#include "utils.h"
#ifndef KERNEL_SPACE
#include <fcntl.h>
#include <unistd.h>
#endif
#define TLS_CONTENT_TYPE_HANDSHAKE 0x16
#define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01
#define TLS_EXTENSION_SNI 0x0000
#define TLS_EXTENSION_CLIENT_HELLO_ENCRYPTED 0xfe0d
/**
* Processes tls payload of the tcp request.
*
* data Payload data of TCP.
* dlen Length of `data`.
*/
struct tls_verdict analyze_tls_data(
const struct section_config_t *section,
const uint8_t *data,
uint32_t dlen)
{
struct tls_verdict vrd = {0};
size_t i = 0;
const uint8_t *data_end = data + dlen;
while (i + 4 < dlen) {
const uint8_t *msgData = data + i;
uint8_t tls_content_type = *msgData;
uint8_t tls_vmajor = *(msgData + 1);
uint16_t message_length = ntohs(*(uint16_t *)(msgData + 3));
if (tls_vmajor != 0x03) goto nextMessage;
if (i + 5 > dlen) break;
if (tls_content_type != TLS_CONTENT_TYPE_HANDSHAKE)
goto nextMessage;
if (section->sni_detection == SNI_DETECTION_BRUTE) {
goto brute;
}
const uint8_t *handshakeProto = msgData + 5;
if (handshakeProto + 1 >= data_end) break;
uint8_t handshakeType = *handshakeProto;
if (handshakeType != TLS_HANDSHAKE_TYPE_CLIENT_HELLO)
goto nextMessage;
const uint8_t *msgPtr = handshakeProto;
msgPtr += 1;
msgPtr += 3 + 2 + 32;
if (msgPtr + 1 >= data_end) break;
uint8_t sessionIdLength = *msgPtr;
msgPtr++;
msgPtr += sessionIdLength;
if (msgPtr + 2 >= data_end) break;
uint16_t ciphersLength = ntohs(*(uint16_t *)msgPtr);
msgPtr += 2;
msgPtr += ciphersLength;
if (msgPtr + 1 >= data_end) break;
uint8_t compMethodsLen = *msgPtr;
msgPtr++;
msgPtr += compMethodsLen;
if (msgPtr + 2 >= data_end) break;
uint16_t extensionsLen = ntohs(*(uint16_t *)msgPtr);
msgPtr += 2;
const uint8_t *extensionsPtr = msgPtr;
const uint8_t *extensions_end = extensionsPtr + extensionsLen;
if (extensions_end > data_end) extensions_end = data_end;
while (extensionsPtr < extensions_end) {
const uint8_t *extensionPtr = extensionsPtr;
if (extensionPtr + 4 >= extensions_end) break;
uint16_t extensionType =
ntohs(*(uint16_t *)extensionPtr);
extensionPtr += 2;
uint16_t extensionLen =
ntohs(*(uint16_t *)extensionPtr);
extensionPtr += 2;
if (extensionPtr + extensionLen > extensions_end)
break;
if (extensionType != TLS_EXTENSION_SNI)
goto nextExtension;
const uint8_t *sni_ext_ptr = extensionPtr;
if (sni_ext_ptr + 2 >= extensions_end) break;
uint16_t sni_ext_dlen = ntohs(*(uint16_t *)sni_ext_ptr);
sni_ext_ptr += 2;
const uint8_t *sni_ext_end = sni_ext_ptr + sni_ext_dlen;
if (sni_ext_end >= extensions_end) break;
if (sni_ext_ptr + 3 >= sni_ext_end) break;
sni_ext_ptr++;
uint16_t sni_len = ntohs(*(uint16_t *)sni_ext_ptr);
sni_ext_ptr += 2;
if (sni_ext_ptr + sni_len > sni_ext_end) break;
char *sni_name = (char *)sni_ext_ptr;
vrd.sni_offset = (uint8_t *)sni_name - data;
vrd.sni_target_offset = vrd.sni_offset;
vrd.sni_len = sni_len;
vrd.sni_target_len = vrd.sni_len;
if (section->all_domains) {
vrd.target_sni = 1;
goto check_domain;
}
unsigned int j = 0;
for (unsigned int i = 0; i <= section->domains_strlen; i++) {
if ( i > j &&
(i == section->domains_strlen ||
section->domains_str[i] == '\0' ||
section->domains_str[i] == ',' ||
section->domains_str[i] == '\n' )) {
unsigned int domain_len = (i - j);
const char *sni_startp = sni_name + sni_len - domain_len;
const char *domain_startp = section->domains_str + j;
if (sni_len >= domain_len &&
sni_len < 128 &&
!strncmp(sni_startp,
domain_startp,
domain_len)) {
vrd.target_sni = 1;
vrd.sni_target_offset = (const uint8_t *)sni_startp - data;
vrd.sni_target_len = domain_len;
goto check_domain;
}
j = i + 1;
}
}
check_domain:
if (vrd.target_sni == 1 && section->exclude_domains_strlen != 0) {
unsigned int j = 0;
for (unsigned int i = 0; i <= section->exclude_domains_strlen; i++) {
if ( i > j &&
(i == section->exclude_domains_strlen ||
section->exclude_domains_str[i] == '\0' ||
section->exclude_domains_str[i] == ',' ||
section->exclude_domains_str[i] == '\n' )) {
unsigned int domain_len = (i - j);
const char *sni_startp = sni_name + sni_len - domain_len;
const char *domain_startp = section->exclude_domains_str + j;
if (sni_len >= domain_len &&
sni_len < 128 &&
!strncmp(sni_startp,
domain_startp,
domain_len)) {
vrd.target_sni = 0;
lgdebugmsg("Excluded SNI: %.*s",
vrd.sni_len, data + vrd.sni_offset);
goto out;
}
j = i + 1;
}
}
}
goto out;
nextExtension:
extensionsPtr += 2 + 2 + extensionLen;
}
nextMessage:
i += 5 + message_length;
}
out:
return vrd;
brute:
if (section->all_domains) {
vrd.target_sni = 1;
vrd.sni_len = 0;
vrd.sni_offset = dlen / 2;
goto out;
}
unsigned int j = 0;
for (unsigned int i = 0; i <= section->domains_strlen; i++) {
if ( i > j &&
(i == section->domains_strlen ||
section->domains_str[i] == '\0' ||
section->domains_str[i] == ',' ||
section->domains_str[i] == '\n' )) {
unsigned int domain_len = (i - j);
const char *domain_startp = section->domains_str + j;
if (domain_len + dlen + 1> MAX_PACKET_SIZE) {
continue;
}
NETBUF_ALLOC(buf, MAX_PACKET_SIZE);
if (!NETBUF_CHECK(buf)) {
lgerror("Allocation error", -ENOMEM);
goto out;
}
NETBUF_ALLOC(nzbuf, MAX_PACKET_SIZE * sizeof(int));
if (!NETBUF_CHECK(nzbuf)) {
lgerror("Allocation error", -ENOMEM);
NETBUF_FREE(buf);
goto out;
}
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 (unsigned int k = 0; k < dlen; k++) {
if (zbuf[k] == domain_len) {
vrd.target_sni = 1;
vrd.sni_len = domain_len;
vrd.sni_offset = (k - domain_len - 1);
vrd.sni_target_offset = vrd.sni_offset;
vrd.sni_target_len = vrd.sni_len;
NETBUF_FREE(buf);
NETBUF_FREE(nzbuf);
goto out;
}
}
j = i + 1;
NETBUF_FREE(buf);
NETBUF_FREE(nzbuf);
}
}
goto out;
}
int gen_fake_sni(struct fake_type type,
const void *ipxh, uint32_t iph_len,
const struct tcphdr *tcph, uint32_t tcph_len,
uint8_t *buf, uint32_t *buflen) {
uint32_t data_len = type.fake_len;
int ret;
if (type.type == FAKE_PAYLOAD_RANDOM && data_len == 0) {
data_len = (uint32_t)randint() % 1200;
}
if (!ipxh || !tcph || !buf || !buflen)
return -EINVAL;
int ipxv = netproto_version(ipxh, iph_len);
if (ipxv == IP4VERSION) {
const struct iphdr *iph = ipxh;
memcpy(buf, iph, iph_len);
struct iphdr *niph = (struct iphdr *)buf;
niph->protocol = IPPROTO_TCP;
} else if (ipxv == IP6VERSION) {
const struct ip6_hdr *iph = ipxh;
iph_len = sizeof(struct ip6_hdr);
memcpy(buf, iph, iph_len);
struct ip6_hdr *niph = (struct ip6_hdr *)buf;
niph->ip6_nxt = IPPROTO_TCP;
} else {
return -EINVAL;
}
uint32_t dlen = iph_len + tcph_len + data_len;
if (*buflen < dlen)
return -ENOMEM;
memcpy(buf + iph_len, tcph, tcph_len);
uint8_t *bfdptr = buf + iph_len + tcph_len;
switch (type.type) {
case FAKE_PAYLOAD_DATA:
memcpy(bfdptr, type.fake_data, data_len);
break;
default: // FAKE_PAYLOAD_RANDOM
#ifdef KERNEL_SPACE
get_random_bytes(bfdptr, data_len);
#else /* KERNEL_SPACE */
#if _NO_GETRANDOM
ret = open("/dev/urandom", O_RDONLY);
if (ret < 0) {
lgerror("Unable to open /dev/urandom", ret);
return ret;
}
read(ret, bfdptr, data_len);
close(ret);
#else /* _NO_GETRANDOM */
getrandom(bfdptr, data_len, 0);
#endif /* _NO_GETRANDOM */
#endif /* KERNEL_SPACE */
}
if (ipxv == IP4VERSION) {
struct iphdr *niph = (struct iphdr *)buf;
niph->tot_len = htons(dlen);
} else if (ipxv == IP6VERSION) {
struct ip6_hdr *niph = (struct ip6_hdr *)buf;
niph->ip6_plen = htons(dlen - iph_len);
}
fail_packet(type.strategy, buf, &dlen, *buflen);
*buflen = dlen;
return 0;
}

34
tls.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef TLS_H
#define TLS_H
#include "types.h"
#include "utils.h"
/**
* Result of analyze_tls_data function
*/
struct tls_verdict {
int target_sni; /* google video hello packet */
int sni_offset; /* offset from start of tcp _payload_ */
int sni_target_offset; /* offset of target domain instead of entire sni */
int sni_target_len; /* offset of target domain instead of entire sni */
int sni_len;
};
/**
* Processes the packet and finds TLS Client Hello information inside it.
* data pointer points to start of TLS Message (TCP Payload)
*/
struct tls_verdict analyze_tls_data(const struct section_config_t *section, const uint8_t *data, uint32_t dlen);
/**
* Generates the fake client hello message
*/
int gen_fake_sni(struct fake_type type,
const void *iph, uint32_t iph_len,
const struct tcphdr *tcph, uint32_t tcph_len,
uint8_t *buf, uint32_t *buflen);
#endif /* TLS_H */

20
types.h
View File

@ -13,6 +13,14 @@
#include <errno.h> // IWYU pragma: export
#include <stdint.h> // IWYU pragma: export
#include <string.h> // IWYU pragma: export
#include <stdlib.h> // IWYU pragma: export
#define _NO_GETRANDOM ((__GLIBC__ <= 2 && __GLIBC_MINOR__ < 25))
#if !_NO_GETRANDOM
#include <sys/random.h> // IWYU pragma: export
#endif
#endif /* SPACES */
@ -99,4 +107,16 @@
#define NETBUF_FREE(buf) ;
#endif
static inline int randint(void) {
int rnd;
#ifdef KERNEL_SPACE
get_random_bytes(&rnd, sizeof(rnd));
#else
rnd = random();
#endif
return rnd;
}
#endif /* TYPES_H */

View File

@ -31,7 +31,7 @@ export CC CCLD LD CFLAGS LDFLAGS LIBNFNETLINK_CFLAGS LIBNFNETLINK_LIBS LIBMNL_CF
APP:=$(BUILD_DIR)/youtubeUnblock
SRCS := youtubeUnblock.c mangle.c args.c utils.c quic.c
SRCS := youtubeUnblock.c mangle.c args.c utils.c quic.c tls.c
OBJS := $(SRCS:%.c=$(BUILD_DIR)/%.o)
LIBNFNETLINK := $(DEPSDIR)/lib/libnfnetlink.la

165
utils.c
View File

@ -418,7 +418,6 @@ int tcp_frag(const uint8_t *pkt, uint32_t buflen, uint32_t payload_offset,
uint8_t *seg1, uint32_t *s1len,
uint8_t *seg2, uint32_t *s2len) {
// struct ip6_hdr *hdr6;
void *hdr;
uint32_t hdr_len;
struct tcphdr *tcph;
@ -485,6 +484,11 @@ int tcp_frag(const uint8_t *pkt, uint32_t buflen, uint32_t payload_offset,
struct iphdr *s2_hdr = (void *)seg2;
s1_hdr->tot_len = htons(s1_dlen);
s2_hdr->tot_len = htons(s2_dlen);
s1_hdr->id = randint();
s2_hdr->id = randint();
set_ip_checksum(s1_hdr, sizeof(struct iphdr));
set_ip_checksum(s2_hdr, sizeof(struct iphdr));
} else {
struct ip6_hdr *s1_hdr = (void *)seg1;
struct ip6_hdr *s2_hdr = (void *)seg2;
@ -523,3 +527,162 @@ void z_function(const char *str, int *zbuf, size_t len) {
}
}
void shift_data(uint8_t *data, uint32_t dlen, uint32_t delta) {
uint8_t *ndptr = data + delta + dlen;
uint8_t *dptr = data + dlen;
uint8_t *ndlptr = data;
for (size_t i = dlen + 1; i > 0; i--) {
*ndptr = *dptr;
--ndptr, --dptr;
}
for (size_t i = 0; i < delta; i++) {
*ndlptr++ = 0;
}
}
#define TCP_MD5SIG_LEN 16
#define TCP_MD5SIG_KIND 19
struct tcp_md5sig_opt {
uint8_t kind;
uint8_t len;
uint8_t sig[TCP_MD5SIG_LEN];
};
#define TCP_MD5SIG_OPT_LEN (sizeof(struct tcp_md5sig_opt))
// Real length of the option, with NOOP fillers
#define TCP_MD5SIG_OPT_RLEN 20
int fail_packet(struct failing_strategy strategy, uint8_t *payload, uint32_t *plen, uint32_t avail_buflen) {
void *iph;
uint32_t iph_len;
struct tcphdr *tcph;
uint32_t tcph_len;
uint8_t *data;
uint32_t dlen;
int ret;
ret = tcp_payload_split(payload, *plen,
&iph, &iph_len, &tcph, &tcph_len,
&data, &dlen);
uint32_t ipxv = netproto_version(payload, *plen);
if (ret < 0) {
return ret;
}
if (strategy.strategy == FAKE_STRAT_RAND_SEQ) {
lgtrace("fake seq: %u -> ", ntohl(tcph->seq));
tcph->seq = htonl(ntohl(tcph->seq) - (strategy.randseq_offset + dlen));
lgtrace_addp("%u", ntohl(tcph->seq));
} else if (strategy.strategy == FAKE_STRAT_PAST_SEQ) {
lgtrace("fake seq: %u -> ", ntohl(tcph->seq));
tcph->seq = htonl(ntohl(tcph->seq) - dlen);
lgtrace_addp("%u", ntohl(tcph->seq));
} else if (strategy.strategy == FAKE_STRAT_TTL) {
lgtrace_addp("set fake ttl to %d", strategy.faking_ttl);
if (ipxv == IP4VERSION) {
((struct iphdr *)iph)->ttl = strategy.faking_ttl;
} else if (ipxv == IP6VERSION) {
((struct ip6_hdr *)iph)->ip6_hops = strategy.faking_ttl;
} else {
lgerror("fail_packet: IP version is unsupported", -EINVAL);
return -EINVAL;
}
} else if (strategy.strategy == FAKE_STRAT_TCP_MD5SUM) {
int optp_len = tcph_len - sizeof(struct tcphdr);
int delta = TCP_MD5SIG_OPT_RLEN - optp_len;
lgtrace_addp("Incr delta %d: %d -> %d", delta, optp_len, optp_len + delta);
if (delta > 0) {
if (avail_buflen - *plen < delta) {
return -1;
}
shift_data(data, dlen, delta);
data += delta;
tcph_len = tcph_len + delta;
tcph->doff = tcph_len >> 2;
if (ipxv == IP4VERSION) {
((struct iphdr *)iph)->tot_len = htons(ntohs(((struct iphdr *)iph)->tot_len) + delta);
} else if (ipxv == IP6VERSION) {
((struct ip6_hdr *)iph)->ip6_plen = htons(ntohs(((struct ip6_hdr *)iph)->ip6_plen) + delta);
} else {
lgerror("fail_packet: IP version is unsupported", -EINVAL);
return -EINVAL;
}
optp_len += delta;
*plen += delta;
}
uint8_t *optplace = (uint8_t *)tcph + sizeof(struct tcphdr);
struct tcp_md5sig_opt *mdopt = (void *)optplace;
mdopt->kind = TCP_MD5SIG_KIND;
mdopt->len = TCP_MD5SIG_OPT_LEN;
optplace += sizeof(struct tcp_md5sig_opt);
optp_len -= sizeof(struct tcp_md5sig_opt);
while (optp_len-- > 0) {
*optplace++ = 0x01;
}
}
if (ipxv == IP4VERSION) {
((struct iphdr *)iph)->frag_off = 0;
}
set_ip_checksum(iph, iph_len);
set_tcp_checksum(tcph, iph, iph_len);
if (strategy.strategy == FAKE_STRAT_TCP_CHECK) {
lgtrace_addp("break fake tcp checksum");
tcph->check += 1;
}
return 0;
}
int seqovl_packet(uint8_t *payload, uint32_t *plen, uint32_t seq_delta) {
int ipxv = netproto_version(payload, *plen);
void *iph;
uint32_t iph_len;
struct tcphdr *tcph;
uint32_t tcph_len;
uint8_t *data;
uint32_t dlen;
int ret = tcp_payload_split(payload, *plen,
&iph, &iph_len, &tcph, &tcph_len,
&data, &dlen);
if (ret < 0) {
return -1;
}
if (ipxv == IP4VERSION) {
struct iphdr *ip4h = iph;
ip4h->tot_len = htons(ntohs(ip4h->tot_len) + seq_delta);
} else if (ipxv == IP6VERSION) {
struct ip6_hdr *ip6h = iph;
ip6h->ip6_plen = htons(ntohs(ip6h->ip6_plen) + seq_delta);
} else {
return -1;
}
tcph->seq = htons(ntohs(tcph->seq) - seq_delta);
shift_data(data, dlen, seq_delta);
*plen += seq_delta;
set_ip_checksum(iph, iph_len);
set_tcp_checksum(tcph, iph, iph_len);
return 0;
}

89
utils.h
View File

@ -2,6 +2,7 @@
#define UTILS_H
#include "types.h"
#include "config.h"
#define IP4VERSION 4
#define IP6VERSION 6
@ -101,4 +102,92 @@ int set_tcp_checksum(struct tcphdr *tcph, void *iph, uint32_t iphb_len);
void z_function(const char *str, int *zbuf, size_t len);
/**
* Shifts data left delta bytes. Fills delta buffer with zeroes.
*/
void shift_data(uint8_t *data, uint32_t dlen, uint32_t delta);
struct failing_strategy {
unsigned int strategy;
uint8_t faking_ttl;
uint32_t randseq_offset;
};
struct fake_type {
#define FAKE_PAYLOAD_RANDOM 0
#define FAKE_PAYLOAD_DATA 1
// In default mode all other options will be skipped.
#define FAKE_PAYLOAD_DEFAULT 2
int type;
// Length of the final fake message.
// Pass 0 in RANDOM mode to make it random
uint16_t fake_len;
// Payload of the fake message of fake_len length.
// Will be omitted in RANDOM mode.
const char *fake_data;
unsigned int sequence_len;
// If non-0 the packet send will be delayed for n milliseconds
unsigned int seg2delay;
// faking strategy of the fake packet.
// Does not support bitmask, pass standalone strategy.
// Pass 0 if you don't want any faking procedures.
struct failing_strategy strategy;
};
/**
* Invalidates the raw packet. The function aims to invalid the packet
* in such way as it will be accepted by DPI, but dropped by target server
*
* Does not support bitmask, pass standalone strategy.
*/
int fail_packet(struct failing_strategy strategy, uint8_t *payload, uint32_t *plen, uint32_t avail_buflen);
/**
* Shifts the payload right and pushes zeroes before it. Useful for TCP TLS faking.
*/
int seqovl_packet(uint8_t *payload, uint32_t *plen, uint32_t seq_delta);
static inline struct failing_strategy args_default_failing_strategy(const struct section_config_t *section) {
struct failing_strategy fl_strat = {
.strategy = (unsigned int)section->faking_strategy,
.faking_ttl = section->faking_ttl,
.randseq_offset = (uint32_t)section->fakeseq_offset
};
return fl_strat;
}
static inline struct fake_type args_default_fake_type(const struct section_config_t *section) {
struct fake_type f_type = {
.sequence_len = section->fake_sni_seq_len,
.strategy = args_default_failing_strategy(section),
};
switch (section->fake_sni_type) {
case FAKE_PAYLOAD_RANDOM:
f_type.type = FAKE_PAYLOAD_RANDOM;
break;
case FAKE_PAYLOAD_CUSTOM:
f_type.type = FAKE_PAYLOAD_CUSTOM;
f_type.fake_data = section->fake_custom_pkt;
f_type.fake_len = section->fake_custom_pkt_sz;
break;
default:
f_type.type = FAKE_PAYLOAD_CUSTOM;
f_type.fake_data = section->fake_sni_pkt;
f_type.fake_len = section->fake_sni_pkt_sz;
}
return f_type;
}
#endif /* UTILS_H */

View File

@ -264,28 +264,12 @@ static int send_raw_socket(const uint8_t *pkt, uint32_t pktlen) {
uint8_t buff2[MNL_SOCKET_BUFFER_SIZE];
uint32_t buff2_size = MNL_SOCKET_BUFFER_SIZE;
switch (config.fragmentation_strategy) {
case FRAG_STRAT_TCP:
if ((ret = tcp_frag(pkt, pktlen, AVAILABLE_MTU-128,
buff1, &buff1_size, buff2, &buff2_size)) < 0) {
errno = -ret;
return ret;
}
break;
case FRAG_STRAT_IP:
if ((ret = ip4_frag(pkt, pktlen, AVAILABLE_MTU-128,
buff1, &buff1_size, buff2, &buff2_size)) < 0) {
errno = -ret;
return ret;
}
break;
default:
errno = EINVAL;
printf("send_raw_socket: Packet is too big but fragmentation is disabled!\n");
return -EINVAL;
}
int sent = 0;
int status = send_raw_socket(buff1, buff1_size);
@ -306,17 +290,19 @@ static int send_raw_socket(const uint8_t *pkt, uint32_t pktlen) {
int ipvx = netproto_version(pkt, pktlen);
if (ipvx == IP4VERSION)
return send_raw_ipv4(pkt, pktlen);
else if (ipvx == IP6VERSION)
return send_raw_ipv6(pkt, pktlen);
if (ipvx == IP4VERSION) {
ret = send_raw_ipv4(pkt, pktlen);
} else if (ipvx == IP6VERSION) {
ret = send_raw_ipv6(pkt, pktlen);
} else {
printf("proto version %d is unsupported\n", ipvx);
return -EINVAL;
}
lgtrace_addp("raw_sock_send: %d", ret);
return ret;
}
struct packet_data {
uint32_t id;
uint16_t hw_proto;
@ -375,7 +361,7 @@ void *delay_packet_send_fn(void *data) {
return NULL;
}
void delay_packet_send(const unsigned char *data, unsigned int data_len, unsigned int delay_ms) {
int delay_packet_send(const unsigned char *data, unsigned int data_len, unsigned int delay_ms) {
struct dps_t *dpdt = malloc(sizeof(struct dps_t));
dpdt->pkt = malloc(data_len);
memcpy(dpdt->pkt, data, data_len);
@ -384,6 +370,9 @@ void delay_packet_send(const unsigned char *data, unsigned int data_len, unsigne
pthread_t thr;
pthread_create(&thr, NULL, delay_packet_send_fn, dpdt);
pthread_detach(thr);
lgtrace_addp("Scheduled packet send after %d ms", delay_ms);
return 0;
}
static int queue_cb(const struct nlmsghdr *nlh, void *data) {