diff --git a/.github/workflows/build-ci.yml b/.github/workflows/build-ci.yml index 6fb00af..2a30701 100644 --- a/.github/workflows/build-ci.yml +++ b/.github/workflows/build-ci.yml @@ -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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..81d19e7 --- /dev/null +++ b/.github/workflows/test.yml @@ -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 + diff --git a/Kbuild b/Kbuild index 3962a79..36eef22 100644 --- a/Kbuild +++ b/Kbuild @@ -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 diff --git a/README.md b/README.md index dc6257e..2151188 100644 --- a/README.md +++ b/README.md @@ -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=` 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=` 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=` 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=` 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 diff --git a/args.c b/args.c index 42df82a..7f01001 100644 --- a/args.c +++ b/args.c @@ -1,5 +1,4 @@ #include "config.h" -#include "raw_replacements.h" #include #include #include @@ -7,54 +6,22 @@ #include #include #include +#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=\n"); printf("\t--fake-sni={1|0}\n"); printf("\t--fake-sni-seq-len=\n"); + printf("\t--fake-sni-type={default|random|custom}\n"); + printf("\t--fake-custom-payload=\n"); printf("\t--fake-seq-offset=\n"); printf("\t--faking-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,68 +449,6 @@ error: } void print_welcome() { - switch (config.fragmentation_strategy) { - case FRAG_STRAT_TCP: - printf("Using TCP segmentation\n"); - break; - case FRAG_STRAT_IP: - printf("Using IP fragmentation\n"); - break; - default: - printf("SNI fragmentation is disabled\n"); - 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 (config.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) { - printf("Fragmentation Client Hello will be reversed\n"); - } - - if (config.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); - } - - switch (config.faking_strategy) { - case FAKE_STRAT_TTL: - printf("TTL faking strategy will be used with TTL %d\n", config.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); - break; - case FAKE_STRAT_TCP_CHECK: - printf("TCP checksum faking strategy will be used\n"); - break; - case FAKE_STRAT_PAST_SEQ: - printf("Past seq faking strategy will be used\n"); - break; - case FAKE_STRAT_TCP_MD5SUM: - printf("md5sum faking strategy will be used\n"); - break; - } - - if (config.fk_winsize) { - printf("Response TCP window will be set to %d with the appropriate scale\n", config.fk_winsize); - } - - if (config.synfake) { - printf("Fake SYN payload will be sent with each TCP request SYN packet\n"); - } - - if (config.use_gso) { printf("GSO is enabled\n"); } @@ -475,17 +458,88 @@ void print_welcome() { } 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"); - if (config.quic_drop) { - printf("All QUIC packets will be dropped\n"); + 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; + case FRAG_STRAT_IP: + printf("Using IP fragmentation\n"); + break; + default: + printf("SNI fragmentation is disabled\n"); + break; + } + + 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 (section->fake_sni) { + printf("Fake SNI will be sent before each target client hello\n"); + } else { + printf("Fake SNI is disabled\n"); + } + + if (section->frag_sni_reverse) { + printf("Fragmentation Client Hello will be reversed\n"); + } + + if (section->frag_sni_faked) { + printf("Fooling packets will be sent near the original Client Hello\n"); + } + + 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 (section->faking_strategy) { + case FAKE_STRAT_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", section->fakeseq_offset); + break; + case FAKE_STRAT_TCP_CHECK: + printf("TCP checksum faking strategy will be used\n"); + break; + case FAKE_STRAT_PAST_SEQ: + printf("Past seq faking strategy will be used\n"); + break; + case FAKE_STRAT_TCP_MD5SUM: + printf("md5sum faking strategy will be used\n"); + break; + } + + if (section->fk_winsize) { + printf("Response TCP window will be set to %d with the appropriate scale\n", section->fk_winsize); + } + + if (section->synfake) { + printf("Fake SYN payload will be sent with each TCP request SYN packet\n"); + } + + if (section->quic_drop) { + printf("All QUIC packets will be dropped\n"); + } + + if (section->sni_detection == SNI_DETECTION_BRUTE) { + printf("Server Name Extension will be parsed in the bruteforce mode\n"); + } + + if (section->all_domains) { + printf("All Client Hello will be targetted by youtubeUnblock!\n"); + } else { + printf("Target sni domains: %s\n", section->domains_str); + } } - - if (config.sni_detection == SNI_DETECTION_BRUTE) { - printf("Server Name Extension will be parsed in the bruteforce mode\n"); - } - - if (config.all_domains) { - printf("All Client Hello will be targetted by youtubeUnblock!\n"); - } - } + diff --git a/config.h b/config.h index 2b5393f..f3d6683 100644 --- a/config.h +++ b/config.h @@ -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 diff --git a/kargs.c b/kargs.c index 00ed9c0..0e596b3 100644 --- a/kargs.c +++ b/kargs.c @@ -1,54 +1,27 @@ #include "config.h" -#include "raw_replacements.h" #include "types.h" #include +#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); diff --git a/kmod_utils.c b/kmod_utils.c deleted file mode 100644 index 12ca4ac..0000000 --- a/kmod_utils.c +++ /dev/null @@ -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 -#include -#include -#include -#include - -#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, -}; diff --git a/kmod_utils.h b/kmod_utils.h deleted file mode 100644 index ade128e..0000000 --- a/kmod_utils.h +++ /dev/null @@ -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 */ diff --git a/kytunblock.c b/kytunblock.c index 31f1c54..c473018 100644 --- a/kytunblock.c +++ b/kytunblock.c @@ -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 #include #include #include #include +#include +#include #include #include @@ -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 "); -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; diff --git a/mangle.c b/mangle.c index 0b6c45d..ca3b65b 100644 --- a/mangle.c +++ b/mangle.c @@ -5,6 +5,7 @@ #include "utils.h" #include "quic.h" #include "logging.h" +#include "tls.h" #ifndef KERNEL_SPACE #include @@ -25,6 +26,8 @@ int process_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) { int ipver = netproto_version(raw_payload, raw_payload_len); int ret; + lgtrace_start(); + lgtrace_addp("IPv%d", ipver); if (ipver == IP4VERSION) { ret = ip4_payload_split((uint8_t *)raw_payload, raw_payload_len, @@ -51,21 +54,39 @@ int process_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) { goto accept; } + int verdict = PKT_CONTINUE; + + if (transport_proto == IPPROTO_TCP) + lgtrace_addp("TCP"); + else if (transport_proto == IPPROTO_UDP) + lgtrace_addp("UDP"); + - switch (transport_proto) { - case IPPROTO_TCP: - return process_tcp_packet(raw_payload, raw_payload_len); - case IPPROTO_UDP: - return process_udp_packet(raw_payload, raw_payload_len); - default: - goto accept; + ITER_CONFIG_SECTIONS(section) { + lgtrace_addp("Section #%d", CONFIG_SECTION_NUMBER(section)); + + switch (transport_proto) { + case IPPROTO_TCP: + verdict = process_tcp_packet(section, raw_payload, raw_payload_len); + break; + case IPPROTO_UDP: + verdict = process_udp_packet(section, raw_payload, raw_payload_len); + break; + } + + if (verdict == PKT_CONTINUE) + continue; + + lgtrace_end(); + return verdict; } - -accept: + +accept: + lgtrace_end(); return PKT_ACCEPT; } -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) { const void *ipxh; uint32_t iph_len; const struct tcphdr *tcph; @@ -76,9 +97,6 @@ int process_tcp_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) { int ipxv = netproto_version(raw_payload, raw_payload_len); - lgtrace_start("TCP"); - lgtrace_addp("IPv%d", ipxv); - int ret = tcp_payload_split((uint8_t *)raw_payload, raw_payload_len, (void *)&ipxh, &iph_len, (struct tcphdr **)&tcph, &tcph_len, @@ -89,7 +107,7 @@ int process_tcp_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) { goto accept; } - if (tcph->syn && config.synfake) { + if (tcph->syn && section->synfake) { lgtrace_addp("TCP syn alter"); NETBUF_ALLOC(payload, MAX_PACKET_SIZE); @@ -100,12 +118,12 @@ int process_tcp_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) { memcpy(payload, ipxh, iph_len); memcpy(payload + iph_len, tcph, tcph_len); - uint32_t fake_len = config.fake_sni_pkt_sz; + uint32_t fake_len = section->fake_sni_pkt_sz; - if (config.synfake_len) - fake_len = min(config.synfake_len, fake_len); + if (section->synfake_len) + fake_len = min(section->synfake_len, fake_len); - memcpy(payload + iph_len + tcph_len, config.fake_sni_pkt, fake_len); + memcpy(payload + iph_len + tcph_len, section->fake_sni_pkt, fake_len); struct tcphdr *tcph = (struct tcphdr *)(payload + iph_len); @@ -129,16 +147,19 @@ int process_tcp_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) { NETBUF_FREE(payload); goto accept; } - lgtrace_addp("rawsocket sent %d", ret); NETBUF_FREE(payload); goto drop; } - if (tcph->syn) goto accept; + if (tcph->syn) goto continue_flow; - struct tls_verdict vrd = analyze_tls_data(data, dlen); - lgtrace_addp("Analyzed, %d", vrd.target_sni); + struct tls_verdict vrd = analyze_tls_data(section, data, dlen); + lgtrace_addp("TLS analyzed"); + + if (vrd.sni_len != 0) { + lgtrace_addp("SNI detected: %.*s", vrd.sni_len, data + vrd.sni_offset); + } if (vrd.target_sni) { lgdebugmsg("Target SNI detected: %.*s", vrd.sni_len, data + vrd.sni_offset); @@ -168,38 +189,47 @@ int process_tcp_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) { goto accept_lc; } - if (config.fk_winsize) { - tcph->window = htons(config.fk_winsize); + if (section->fk_winsize) { + tcph->window = htons(section->fk_winsize); + set_tcp_checksum(tcph, iph, iph_len); + } + + if (0) { + int delta = 2; + ret = seqovl_packet(payload, &payload_len, delta); + int ret = tcp_payload_split(payload, payload_len, + &iph, &iph_len, &tcph, &tcph_len, + &data, &dlen); + if (ret < 0) { + lgerror("seqovl_packet delta %d", ret, delta); + } } - set_ip_checksum(iph, iph_len); - set_tcp_checksum(tcph, iph, iph_len); if (dlen > 1480 && config.verbose) { lgdebugmsg("WARNING! Client Hello packet is too big and may cause issues!"); } - if (config.fake_sni) { - post_fake_sni(iph, iph_len, tcph, tcph_len, - config.fake_sni_seq_len); + if (section->fake_sni) { + post_fake_sni(args_default_fake_type(section), iph, iph_len, tcph, tcph_len); } size_t ipd_offset; size_t mid_offset; - switch (config.fragmentation_strategy) { + switch (section->fragmentation_strategy) { case FRAG_STRAT_TCP: { - ipd_offset = vrd.sni_offset; - mid_offset = ipd_offset + vrd.sni_len / 2; + ipd_offset = vrd.sni_target_offset; + mid_offset = ipd_offset + vrd.sni_target_len / 2; uint32_t poses[2]; int cnt = 0; - if (config.frag_sni_pos && dlen > config.frag_sni_pos) { - poses[cnt++] = config.frag_sni_pos; + if (section->frag_sni_pos && dlen > section->frag_sni_pos) { + poses[cnt++] = section->frag_sni_pos; } - if (config.frag_middle_sni) { + if (section->frag_middle_sni) { poses[cnt++] = mid_offset; } @@ -209,7 +239,7 @@ int process_tcp_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) { poses[1] = tmp; } - ret = send_tcp_frags(payload, payload_len, poses, cnt, 0); + ret = send_tcp_frags(section, payload, payload_len, poses, cnt, 0); if (ret < 0) { lgerror("tcp4 send frags", ret); goto accept_lc; @@ -220,20 +250,20 @@ int process_tcp_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) { break; case FRAG_STRAT_IP: if (ipxv == IP4VERSION) { - ipd_offset = ((char *)data - (char *)tcph) + vrd.sni_offset; - mid_offset = ipd_offset + vrd.sni_len / 2; + ipd_offset = ((char *)data - (char *)tcph) + vrd.sni_target_offset; + mid_offset = ipd_offset + vrd.sni_target_len / 2; mid_offset += 8 - mid_offset % 8; uint32_t poses[2]; int cnt = 0; - if (config.frag_sni_pos && dlen > config.frag_sni_pos) { - poses[cnt] = config.frag_sni_pos + ((char *)data - (char *)tcph); + if (section->frag_sni_pos && dlen > section->frag_sni_pos) { + poses[cnt] = section->frag_sni_pos + ((char *)data - (char *)tcph); poses[cnt] += 8 - poses[cnt] % 8; cnt++; } - if (config.frag_middle_sni) { + if (section->frag_middle_sni) { poses[cnt++] = mid_offset; } @@ -243,7 +273,7 @@ int process_tcp_packet(const uint8_t *raw_payload, uint32_t raw_payload_len) { poses[1] = tmp; } - ret = send_ip4_frags(payload, payload_len, poses, cnt, 0); + ret = send_ip4_frags(section, payload, payload_len, poses, cnt, 0); if (ret < 0) { lgerror("ip4 send frags", ret); goto accept_lc; @@ -278,27 +308,24 @@ drop_lc: } +continue_flow: + lgtrace_addp("continue_flow"); + return PKT_CONTINUE; accept: lgtrace_addp("accept"); - lgtrace_end(); - return PKT_ACCEPT; drop: lgtrace_addp("drop"); - lgtrace_end(); - return PKT_DROP; } -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) { const void *iph; uint32_t iph_len; const struct udphdr *udph; const uint8_t *data; uint32_t dlen; int ipver = netproto_version(pkt, pktlen); - lgtrace_start("Got udp packet"); - lgtrace_addp("IPv%d", ipver); int ret = udp_payload_split((uint8_t *)pkt, pktlen, (void **)&iph, &iph_len, @@ -319,148 +346,96 @@ int process_udp_packet(const uint8_t *pkt, uint32_t pktlen) { printf("], "); } - lgtrace_addp("QUIC probe"); - const struct quic_lhdr *qch; - uint32_t qch_len; - struct quic_cids qci; - uint8_t *quic_raw_payload; - uint32_t quic_raw_plen; - ret = quic_parse_data((uint8_t *)data, dlen, - (struct quic_lhdr **)&qch, &qch_len, &qci, - &quic_raw_payload, &quic_raw_plen); - if (ret < 0) { - lgtrace_addp("undefined type"); - goto accept; - } + if (section->quic_drop) { + lgtrace_addp("QUIC probe"); + const struct quic_lhdr *qch; + uint32_t qch_len; + struct quic_cids qci; + uint8_t *quic_raw_payload; + uint32_t quic_raw_plen; + ret = quic_parse_data((uint8_t *)data, dlen, + (struct quic_lhdr **)&qch, &qch_len, &qci, + &quic_raw_payload, &quic_raw_plen); - lgtrace_addp("QUIC detected"); - uint8_t qtype = qch->type; + if (ret < 0) { + lgtrace_addp("undefined type"); + goto accept_quic; + } + + lgtrace_addp("QUIC detected"); + uint8_t qtype = qch->type; - if (config.quic_drop) { goto drop; + + if (qch->version == QUIC_V1) + qtype = quic_convtype_v1(qtype); + else if (qch->version == QUIC_V2) + qtype = quic_convtype_v2(qtype); + + if (qtype != QUIC_INITIAL_TYPE) { + lgtrace_addp("quic message type: %d", qtype); + goto accept_quic; + } + + lgtrace_addp("quic initial message"); } - if (qch->version == QUIC_V1) - qtype = quic_convtype_v1(qtype); - else if (qch->version == QUIC_V2) - qtype = quic_convtype_v2(qtype); +/* + if (1) { + lgtrace_addp("Probe udp"); + if (ipver == IP4VERSION && ntohs(udph->dest) > 30) { + lgtrace_addp("udp fool"); + const uint8_t *payload; + uint32_t payload_len; - if (qtype != QUIC_INITIAL_TYPE) { - lgtrace_addp("quic message type: %d", qtype); - goto accept; + uint32_t poses[10]; + int cnt = 3; + + poses[0] = 8; + for (int i = 1; i < cnt; i++) { + poses[i] = poses[i - 1] + 8; + } + + ret = send_ip4_frags(pkt, pktlen, poses, cnt, 0); + if (ret < 0) { + lgerror("ip4 send frags", ret); + goto accept; + } + + goto drop; + } else { + printf("WARNING: IP fragmentation is supported only for IPv4\n"); + goto accept; + } } - - lgtrace_addp("quic initial message"); +*/ + +continue_flow: + lgtrace_addp("continue_flow"); + return PKT_CONTINUE; +accept_quic: accept: - lgtrace_addp("accepted"); - lgtrace_end(); - return PKT_ACCEPT; drop: - lgtrace_addp("dropped"); - lgtrace_end(); - return PKT_DROP; } -int send_ip4_frags(const uint8_t *packet, uint32_t pktlen, const uint32_t *poses, uint32_t poses_sz, uint32_t dvs) { +int send_ip4_frags(const struct section_config_t *section, const uint8_t *packet, uint32_t pktlen, const uint32_t *poses, uint32_t poses_sz, uint32_t dvs) { if (poses_sz == 0) { - if (config.seg2_delay && ((dvs > 0) ^ config.frag_sni_reverse)) { + if (section->seg2_delay && ((dvs > 0) ^ section->frag_sni_reverse)) { if (!instance_config.send_delayed_packet) { return -EINVAL; } + lgtrace_addp("Sent %d delayed for %d", pktlen, section->seg2_delay); instance_config.send_delayed_packet( - packet, pktlen, config.seg2_delay); + packet, pktlen, section->seg2_delay); return 0; } else { - return instance_config.send_raw_packet( - packet, pktlen); - } - } else { - NETBUF_ALLOC(frag1, MAX_PACKET_SIZE); - if (!NETBUF_CHECK(frag1)) { - lgerror("Allocation error", -ENOMEM); - return -ENOMEM; - } - - NETBUF_ALLOC(frag2, MAX_PACKET_SIZE); - if (!NETBUF_CHECK(frag2)) { - lgerror("Allocation error", -ENOMEM); - NETBUF_FREE(frag1); - return -ENOMEM; - } - - uint32_t f1len = MAX_PACKET_SIZE; - uint32_t f2len = MAX_PACKET_SIZE; - - int ret; - - if (dvs > poses[0]) { - lgerror("send_frags: Recursive dvs(%d) is more than poses0(%d)", -EINVAL, dvs, poses[0]); - ret = -EINVAL; - goto erret_lc; - } - - ret = ip4_frag(packet, pktlen, poses[0] - dvs, - frag1, &f1len, frag2, &f2len); - - if (ret < 0) { - lgerror("send_frags: frag: with context packet with size %d, position: %d, recursive dvs: %d", ret, pktlen, poses[0], dvs); - goto erret_lc; - } - - if (config.frag_sni_reverse) - goto send_frag2; -send_frag1: - ret = send_ip4_frags(frag1, f1len, NULL, 0, 0); - if (ret < 0) { - goto erret_lc; - } - - if (config.frag_sni_reverse) - goto out_lc; - -send_frag2: - dvs += poses[0]; - ret = send_ip4_frags(frag2, f2len, poses + 1, poses_sz - 1, dvs); - if (ret < 0) { - goto erret_lc; - } - - if (config.frag_sni_reverse) - goto send_frag1; - -out_lc: - NETBUF_FREE(frag1); - NETBUF_FREE(frag2); - goto out; -erret_lc: - NETBUF_FREE(frag1); - NETBUF_FREE(frag2); - return ret; - } - -out: - return 0; -} - -int send_tcp_frags(const uint8_t *packet, uint32_t pktlen, const uint32_t *poses, uint32_t poses_sz, uint32_t dvs) { - if (poses_sz == 0) { - if (config.seg2_delay && ((dvs > 0) ^ config.frag_sni_reverse)) { - if (!instance_config.send_delayed_packet) { - return -EINVAL; - } - - instance_config.send_delayed_packet( - packet, pktlen, config.seg2_delay); - - return 0; - } else { - lgtrace_addp("raw send packet of %d bytes with %d dvs", pktlen, dvs); + lgtrace_addp("Sent %d bytes", pktlen); return instance_config.send_raw_packet( packet, pktlen); } @@ -486,7 +461,6 @@ int send_tcp_frags(const uint8_t *packet, uint32_t pktlen, const uint32_t *poses return -ENOMEM; } - uint32_t f1len = MAX_PACKET_SIZE; uint32_t f2len = MAX_PACKET_SIZE; uint32_t fake_pad_len = MAX_PACKET_SIZE; @@ -499,10 +473,131 @@ int send_tcp_frags(const uint8_t *packet, uint32_t pktlen, const uint32_t *poses goto erret_lc; } + uint32_t frag_pos = poses[0] - dvs; + frag_pos += 8 - frag_pos % 8; + + ret = ip4_frag(packet, pktlen, frag_pos, + frag1, &f1len, frag2, &f2len); + + if (ret < 0) { + lgerror("send_frags: frag: with context packet with size %d, position: %d, recursive dvs: %d", ret, pktlen, poses[0], dvs); + goto erret_lc; + } + + dvs += frag_pos; + + if (section->frag_sni_reverse) + goto send_frag2; +send_frag1: + ret = send_ip4_frags(section, frag1, f1len, NULL, 0, 0); + if (ret < 0) { + goto erret_lc; + } + + if (section->frag_sni_reverse) + goto out_lc; + +send_fake: +/* + if (section->frag_sni_faked) { + ITER_FAKE_STRAT(section->faking_strategy, strategy) { + uint32_t iphfl; + fake_pad_len = f2len; + ret = ip4_payload_split(frag2, f2len, NULL, &iphfl, NULL, NULL); + if (ret < 0) { + lgerror("Invalid frag2", ret); + goto erret_lc; + } + memcpy(fake_pad, frag2, iphfl + sizeof(struct udphdr)); + memset(fake_pad + iphfl + sizeof(struct udphdr), 0, f2len - iphfl - sizeof(struct udphdr)); + ((struct iphdr *)fake_pad)->tot_len = htons(fake_pad_len); + ((struct iphdr *)fake_pad)->id = 1; + ((struct iphdr *)fake_pad)->ttl = 8; + ((struct iphdr *)fake_pad)->frag_off = 0; + ip4_set_checksum((struct iphdr*)fake_pad); + // *(struct udphdr *)(fake_pad + iphfl) = *(struct udphdr *)(frag2 + iphfl); + ret = send_ip4_frags(fake_pad, fake_pad_len, NULL, 0, 0); + if (ret < 0) { + goto erret_lc; + } + } + } +*/ + + if (section->frag_sni_reverse) + goto send_frag1; + +send_frag2: + ret = send_ip4_frags(section, frag2, f2len, poses + 1, poses_sz - 1, dvs); + if (ret < 0) { + goto erret_lc; + } + + if (section->frag_sni_reverse) + goto send_fake; + +out_lc: + NETBUF_FREE(frag1); + NETBUF_FREE(frag2); + NETBUF_FREE(fake_pad); + goto out; +erret_lc: + NETBUF_FREE(frag1); + NETBUF_FREE(frag2); + NETBUF_FREE(fake_pad); + return ret; + } + +out: + return 0; +} + +int send_tcp_frags(const struct section_config_t *section, const uint8_t *packet, uint32_t pktlen, const uint32_t *poses, uint32_t poses_sz, uint32_t dvs) { + if (poses_sz == 0) { + if (section->seg2_delay && ((dvs > 0) ^ section->frag_sni_reverse)) { + if (!instance_config.send_delayed_packet) { + return -EINVAL; + } + + instance_config.send_delayed_packet( + packet, pktlen, section->seg2_delay); + + return 0; + } else { + lgtrace_addp("raw send packet of %d bytes with %d dvs", pktlen, dvs); + return instance_config.send_raw_packet( + packet, pktlen); + } + } else { + NETBUF_ALLOC(frag1, MAX_PACKET_SIZE); + if (!NETBUF_CHECK(frag1)) { + lgerror("Allocation error", -ENOMEM); + return -ENOMEM; + } + + NETBUF_ALLOC(frag2, MAX_PACKET_SIZE); + if (!NETBUF_CHECK(frag2)) { + lgerror("Allocation error", -ENOMEM); + NETBUF_FREE(frag1); + return -ENOMEM; + } + + uint32_t f1len = MAX_PACKET_SIZE; + uint32_t f2len = MAX_PACKET_SIZE; + + int ret; + + if (dvs > poses[0]) { + lgerror("send_frags: Recursive dvs(%d) is more than poses0(%d)", -EINVAL, dvs, poses[0]); + ret = -EINVAL; + goto erret_lc; + } + ret = tcp_frag(packet, pktlen, poses[0] - dvs, frag1, &f1len, frag2, &f2len); + lgtrace_addp("Packet split in %d bytes position of payload start, dvs: %d to two packets of %d and %d lengths", poses[0], dvs, f1len, f2len); if (ret < 0) { @@ -511,561 +606,153 @@ int send_tcp_frags(const uint8_t *packet, uint32_t pktlen, const uint32_t *poses } - if (config.frag_sni_reverse) + dvs += poses[0]; + + if (section->frag_sni_reverse) goto send_frag2; send_frag1: { - ret = send_tcp_frags(frag1, f1len, NULL, 0, 0); + ret = send_tcp_frags(section, frag1, f1len, NULL, 0, 0); if (ret < 0) { goto erret_lc; } - if (config.frag_sni_reverse) + if (section->frag_sni_reverse) goto out_lc; } send_fake: - if (config.frag_sni_faked) { + if (section->frag_sni_faked) { uint32_t iphfl, tcphfl; - fake_pad_len = f2len; - ret = tcp_payload_split(frag2, f2len, NULL, &iphfl, NULL, &tcphfl, NULL, NULL); - if (ret < 0) { - lgerror("Invalid frag2", ret); - goto erret_lc; - } - memcpy(fake_pad, frag2, iphfl + tcphfl); - memset(fake_pad + iphfl + tcphfl, 0, f2len - iphfl - tcphfl); - struct tcphdr *fakethdr = (void *)(fake_pad + iphfl); - if (config.faking_strategy == FAKE_STRAT_PAST_SEQ) { - lgtrace("frag fake sent with %u -> ", ntohl(fakethdr->seq)); - fakethdr->seq = htonl(ntohl(fakethdr->seq) - dvs); - lgtrace_addp("%u, ", ntohl(fakethdr->seq)); - } - ret = fail_packet(fake_pad, &fake_pad_len, MAX_PACKET_SIZE); - if (ret < 0) { - lgerror("Failed to fail packet", ret); - goto erret_lc; - } - ret = send_tcp_frags(fake_pad, fake_pad_len, NULL, 0, 0); - if (ret < 0) { - goto erret_lc; + void *iph; + struct tcphdr *tcph; + ret = tcp_payload_split(frag2, f2len, &iph, &iphfl, &tcph, &tcphfl, NULL, NULL); + struct fake_type f_type = args_default_fake_type(section); + if ((f_type.strategy.strategy & FAKE_STRAT_PAST_SEQ) == FAKE_STRAT_PAST_SEQ) { + f_type.strategy.strategy ^= FAKE_STRAT_PAST_SEQ; + f_type.strategy.strategy |= FAKE_STRAT_RAND_SEQ; + f_type.strategy.randseq_offset = dvs; } + f_type.seg2delay = section->seg2_delay; + + post_fake_sni(f_type, iph, iphfl, tcph, tcphfl); } - if (config.frag_sni_reverse) + if (section->frag_sni_reverse) goto send_frag1; send_frag2: { - dvs += poses[0]; - ret = send_tcp_frags(frag2, f2len, poses + 1, poses_sz - 1, dvs); + ret = send_tcp_frags(section, frag2, f2len, poses + 1, poses_sz - 1, dvs); if (ret < 0) { goto erret_lc; } - if (config.frag_sni_reverse) + if (section->frag_sni_reverse) goto send_fake; } out_lc: NETBUF_FREE(frag1); NETBUF_FREE(frag2); - NETBUF_FREE(fake_pad); goto out; erret_lc: NETBUF_FREE(frag1); NETBUF_FREE(frag2); - NETBUF_FREE(fake_pad); return ret; } out: return 0; } -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) { + uint8_t rfsiph[128]; uint8_t rfstcph[60]; int ret; + int ipxv = netproto_version(iph, iph_len); + memcpy(rfsiph, iph, iph_len); memcpy(rfstcph, tcph, tcph_len); void *fsiph = (void *)rfsiph; struct tcphdr *fstcph = (void *)rfstcph; - for (int i = 0; i < sequence_len; i++) { - NETBUF_ALLOC(fake_sni, MAX_PACKET_SIZE); - if (!NETBUF_CHECK(fake_sni)) { - lgerror("Allocation error", -ENOMEM); - return -ENOMEM; - } - uint32_t fsn_len = MAX_PACKET_SIZE; - ret = gen_fake_sni(fsiph, iph_len, fstcph, tcph_len, - fake_sni, &fsn_len); - if (ret < 0) { - lgerror("gen_fake_sni", ret); - goto erret_lc; - } + ITER_FAKE_STRAT(f_type.strategy.strategy, strategy) { + struct fake_type fake_seq_type = f_type; + fake_seq_type.strategy.strategy = strategy; - lgtrace_addp("post fake sni #%d", i + 1); - lgtrace_addp("post with %d", fsn_len); - ret = instance_config.send_raw_packet(fake_sni, fsn_len); - if (ret < 0) { - lgerror("send fake sni", ret); - goto erret_lc; - } - - uint32_t iph_len; - uint32_t tcph_len; - uint32_t plen; - tcp_payload_split( - fake_sni, fsn_len, - &fsiph, &iph_len, - &fstcph, &tcph_len, - NULL, &plen); - - - fstcph->seq = htonl(ntohl(fstcph->seq) + plen); - memcpy(rfsiph, fsiph, iph_len); - memcpy(rfstcph, fstcph, tcph_len); - fsiph = (void *)rfsiph; - fstcph = (void *)rfstcph; - - NETBUF_FREE(fake_sni); - continue; -erret_lc: - NETBUF_FREE(fake_sni); - return ret; - } - - return 0; -} - -#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 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 (config.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; + // one goes for default fake + for (int i = 0; i < fake_seq_type.sequence_len; i++) { + NETBUF_ALLOC(fake_sni, MAX_PACKET_SIZE); + if (!NETBUF_CHECK(fake_sni)) { + lgerror("Allocation error", -ENOMEM); + return -ENOMEM; + } + uint32_t fsn_len = MAX_PACKET_SIZE; - 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_len = sni_len; - - if (config.all_domains) { - vrd.target_sni = 1; - goto check_domain; + ret = gen_fake_sni( + fake_seq_type, + fsiph, iph_len, fstcph, tcph_len, + fake_sni, &fsn_len); + if (ret < 0) { + lgerror("gen_fake_sni", ret); + goto erret_lc; } + lgtrace_addp("post fake sni #%d", i + 1); - unsigned int j = 0; - for (unsigned int i = 0; i <= config.domains_strlen; i++) { - if ( i > j && - (i == config.domains_strlen || - config.domains_str[i] == '\0' || - config.domains_str[i] == ',' || - config.domains_str[i] == '\n' )) { - - unsigned int domain_len = (i - j); - const char *sni_startp = sni_name + sni_len - domain_len; - const char *domain_startp = config.domains_str + j; - - if (sni_len >= domain_len && - sni_len < 128 && - !strncmp(sni_startp, - domain_startp, - domain_len)) { - vrd.target_sni = 1; - goto check_domain; - } - - j = i + 1; - } - } - -check_domain: - if (vrd.target_sni == 1 && config.exclude_domains_strlen != 0) { - unsigned int j = 0; - for (unsigned int i = 0; i <= config.exclude_domains_strlen; i++) { - if ( i > j && - (i == config.exclude_domains_strlen || - config.exclude_domains_str[i] == '\0' || - config.exclude_domains_str[i] == ',' || - config.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 = config.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 (config.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 <= config.domains_strlen; i++) { - if ( i > j && - (i == config.domains_strlen || - config.domains_str[i] == '\0' || - config.domains_str[i] == ',' || - config.domains_str[i] == '\n' )) { - - unsigned int domain_len = (i - j); - const char *domain_startp = config.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); - NETBUF_FREE(buf); - NETBUF_FREE(nzbuf); - goto out; - } - } - - - j = i + 1; - - NETBUF_FREE(buf); - NETBUF_FREE(nzbuf); - } - } - - goto out; -} - -int gen_fake_sni(const void *ipxh, uint32_t iph_len, - const struct tcphdr *tcph, uint32_t tcph_len, - uint8_t *buf, uint32_t *buflen) { - - 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; - } - - const char *data = config.fake_sni_pkt; - size_t data_len = config.fake_sni_pkt_sz; - - uint32_t dlen = iph_len + tcph_len + data_len; - - if (*buflen < dlen) - return -ENOMEM; - - memcpy(buf + iph_len, tcph, tcph_len); - memcpy(buf + iph_len + tcph_len, data, data_len); - - - 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(buf, &dlen, *buflen); - *buflen = dlen; - - return 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(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 (config.faking_strategy == FAKE_STRAT_RAND_SEQ) { - lgtrace("fake seq: %u -> ", ntohl(tcph->seq)); - - if (config.fakeseq_offset) { - tcph->seq = htonl(ntohl(tcph->seq) - config.fakeseq_offset); - } else { -#ifdef KERNEL_SPACE - tcph->seq = 124; -#else - tcph->seq = random(); -#endif - - } - - lgtrace_addp("%u", ntohl(tcph->seq)); - } else if (config.faking_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 (config.faking_strategy == FAKE_STRAT_TTL) { - lgtrace_addp("set fake ttl to %d", config.faking_ttl); - - if (ipxv == IP4VERSION) { - ((struct iphdr *)iph)->ttl = config.faking_ttl; - } else if (ipxv == IP6VERSION) { - ((struct ip6_hdr *)iph)->ip6_hops = config.faking_ttl; - } else { - lgerror("fail_packet: IP version is unsupported", -EINVAL); - return -EINVAL; - } - } else if (config.faking_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; - } - uint8_t *ndata = data + delta; - uint8_t *ndptr = ndata + dlen; - uint8_t *dptr = data + dlen; - for (size_t i = dlen + 1; i > 0; i--) { - *ndptr = *dptr; - --ndptr, --dptr; - } - data = ndata; - 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); + if (f_type.seg2delay) { + ret = instance_config.send_delayed_packet(fake_sni, fsn_len, f_type.seg2delay); } else { - lgerror("fail_packet: IP version is unsupported", -EINVAL); - return -EINVAL; + ret = instance_config.send_raw_packet(fake_sni, fsn_len); } - optp_len += delta; - *plen += delta; + if (ret < 0) { + lgerror("send fake sni", ret); + goto erret_lc; + } + uint32_t iph_len; + uint32_t tcph_len; + uint32_t plen; + ret = tcp_payload_split( + fake_sni, fsn_len, + &fsiph, &iph_len, + &fstcph, &tcph_len, + NULL, &plen); + + if (ret < 0) { + lgtrace_addp("continue fake seq"); + goto erret_lc; + } + + + if (!(strategy == FAKE_STRAT_PAST_SEQ || + strategy == FAKE_STRAT_RAND_SEQ)) { + fstcph->seq = htonl(ntohl(fstcph->seq) + plen); + } + + if (ipxv == IP4VERSION) { + ((struct iphdr *)fsiph)->id = htons(ntohs(((struct iphdr *)fsiph)->id) + 1); + } + + memcpy(rfsiph, fsiph, iph_len); + + memcpy(rfstcph, fstcph, tcph_len); + fsiph = (void *)rfsiph; + fstcph = (void *)rfstcph; + + NETBUF_FREE(fake_sni); + continue; +erret_lc: + NETBUF_FREE(fake_sni); + return ret; } - - 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; - } - } - - set_ip_checksum(iph, iph_len); - set_tcp_checksum(tcph, iph, iph_len); - - if (config.faking_strategy == FAKE_STRAT_TCP_CHECK) { - lgtrace_addp("break fake tcp checksum"); - tcph->check += 1; } return 0; } + diff --git a/mangle.h b/mangle.h index f07ab17..0644d6c 100644 --- a/mangle.h +++ b/mangle.h @@ -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 */ diff --git a/nf_wrapper.h b/nf_wrapper.h deleted file mode 100644 index a254a6d..0000000 --- a/nf_wrapper.h +++ /dev/null @@ -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 - -/* 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 */ diff --git a/tls.c b/tls.c new file mode 100644 index 0000000..e67c18c --- /dev/null +++ b/tls.c @@ -0,0 +1,354 @@ +#include "types.h" +#include "tls.h" +#include "config.h" +#include "logging.h" +#include "utils.h" + +#ifndef KERNEL_SPACE +#include +#include +#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; +} + diff --git a/tls.h b/tls.h new file mode 100644 index 0000000..024a1cc --- /dev/null +++ b/tls.h @@ -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 */ diff --git a/types.h b/types.h index 65b4749..ab63f61 100644 --- a/types.h +++ b/types.h @@ -13,6 +13,14 @@ #include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export +#include // IWYU pragma: export + + +#define _NO_GETRANDOM ((__GLIBC__ <= 2 && __GLIBC_MINOR__ < 25)) + +#if !_NO_GETRANDOM +#include // 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 */ diff --git a/uspace.mk b/uspace.mk index db43b15..d119c84 100644 --- a/uspace.mk +++ b/uspace.mk @@ -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 diff --git a/utils.c b/utils.c index 19f14fa..0659bb5 100644 --- a/utils.c +++ b/utils.c @@ -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; +} + diff --git a/utils.h b/utils.h index b9ad085..d78b7b5 100644 --- a/utils.h +++ b/utils.h @@ -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 */ diff --git a/youtubeUnblock.c b/youtubeUnblock.c index a57cb69..6e4ee5c 100644 --- a/youtubeUnblock.c +++ b/youtubeUnblock.c @@ -264,27 +264,11 @@ 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) { + 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; + errno = -ret; + return ret; } int sent = 0; @@ -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; + } - 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) {