diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8126eab --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +indent_style = tab +indent_size = 8 +tab_width = 8 diff --git a/.gitignore b/.gitignore index cdb521e..f865d01 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,12 @@ build configure~ +# Kernel module files +*.o +.* +*.mod.* +*.mod +modules.order +Module.symvers +*.so +*.ko diff --git a/Kbuild b/Kbuild new file mode 100644 index 0000000..4c0ef45 --- /dev/null +++ b/Kbuild @@ -0,0 +1,3 @@ +obj-m := ipt_YTUNBLOCK.o +ipt_YTUNBLOCK-objs := iptk_YTUNBLOCK.o mangle.o +ccflags-y := -std=gnu11 -Wno-unused-variable -DKERNEL_SPACE -DDEBUG diff --git a/Makefile b/Makefile index 27a9377..0e05091 100644 --- a/Makefile +++ b/Makefile @@ -1,91 +1,14 @@ -BUILD_DIR := $(CURDIR)/build -DEPSDIR := $(BUILD_DIR)/deps +USPACE_TARGETS := default all install uninstall dev run_dev +KMAKE_TARGETS := kmake kload kunload kreload xmod xtclean -CC:=gcc -CCLD:=$(CC) -LD:=ld -override CFLAGS += -Wall -Wpedantic -Wno-unused-variable -I$(DEPSDIR)/include -override LDFLAGS += -L$(DEPSDIR)/lib +.PHONY: $(USPACE_TARGETS) $(KMAKE_TARGETS) clean +$(USPACE_TARGETS): + @$(MAKE) -f uspace.mk $@ -LIBNFNETLINK_CFLAGS := -I$(DEPSDIR)/include -LIBNFNETLINK_LIBS := -L$(DEPSDIR)/lib -LIBMNL_CFLAGS := -I$(DEPSDIR)/include -LIBMNL_LIBS := -L$(DEPSDIR)/lib - -# PREFIX is environment variable, if not set default to /usr/local -ifeq ($(PREFIX),) - PREFIX := /usr/local -else - PREFIX := $(DESTDIR) -endif - -export CC CCLD LD CFLAGS LDFLAGS LIBNFNETLINK_CFLAGS LIBNFNETLINK_LIBS LIBMNL_CFLAGS LIBMNL_LIBS - -APP:=$(BUILD_DIR)/youtubeUnblock - -SRCS := youtubeUnblock.c -OBJS := $(SRCS:%.c=$(BUILD_DIR)/%.o) - -LIBNFNETLINK := $(DEPSDIR)/lib/libnfnetlink.a -LIBMNL := $(DEPSDIR)/lib/libmnl.a -LIBNETFILTER_QUEUE := $(DEPSDIR)/lib/libnetfilter_queue.a - - -.PHONY: default all dev dev_attrs prepare_dirs -default: all - -run_dev: dev - bash -c "sudo $(APP) 537" - -dev: dev_attrs all - -dev_attrs: - $(eval CFLAGS := $(CFLAGS) -DDEBUG -ggdb -g3) - -all: prepare_dirs $(APP) - -prepare_dirs: - mkdir -p $(BUILD_DIR) - mkdir -p $(DEPSDIR) - -$(LIBNFNETLINK): - cd deps/libnfnetlink && ./autogen.sh && ./configure --prefix=$(DEPSDIR) $(if $(CROSS_COMPILE_PLATFORM),--host=$(CROSS_COMPILE_PLATFORM),) --enable-static --disable-shared - $(MAKE) -C deps/libnfnetlink - $(MAKE) install -C deps/libnfnetlink - -$(LIBMNL): - cd deps/libmnl && ./autogen.sh && ./configure --prefix=$(DEPSDIR) $(if $(CROSS_COMPILE_PLATFORM),--host=$(CROSS_COMPILE_PLATFORM),) --enable-static --disable-shared - $(MAKE) -C deps/libmnl - $(MAKE) install -C deps/libmnl - -$(LIBNETFILTER_QUEUE): $(LIBNFNETLINK) $(LIBMNL) - cd deps/libnetfilter_queue && ./autogen.sh && ./configure --prefix=$(DEPSDIR) $(if $(CROSS_COMPILE_PLATFORM),--host=$(CROSS_COMPILE_PLATFORM),) --enable-static --disable-shared - $(MAKE) -C deps/libnetfilter_queue - $(MAKE) install -C deps/libnetfilter_queue - -$(APP): $(OBJS) $(LIBNETFILTER_QUEUE) $(LIBMNL) - @echo 'CCLD $(APP)' - $(CCLD) $(OBJS) -o $(APP) $(LDFLAGS) -lmnl -lnetfilter_queue - -$(BUILD_DIR)/%.o: %.c $(LIBNETFILTER_QUEUE) $(LIBMNL) - @echo 'CC $@' - $(CC) -c $(CFLAGS) $(LDFLAGS) $< -o $@ - -install: all - install -d $(PREFIX)/bin/ - install -m 755 $(APP) $(PREFIX)/bin/ - install -d $(PREFIX)/lib/systemd/system/ - @cp youtubeUnblock.service $(BUILD_DIR) - @sed -i 's/$$(PREFIX)/$(subst /,\/,$(PREFIX))/g' $(BUILD_DIR)/youtubeUnblock.service - install -m 644 $(BUILD_DIR)/youtubeUnblock.service $(PREFIX)/lib/systemd/system/ - -uninstall: - rm $(PREFIX)/bin/youtubeUnblock - rm $(PREFIX)/lib/systemd/system/youtubeUnblock.service - -systemctl disable youtubeUnblock.service +$(KMAKE_TARGETS): + @$(MAKE) -f kmake.mk $@ clean: - rm -rf $(BUILD_DIR) - $(MAKE) distclean -C deps/libnetfilter_queue || true - $(MAKE) distclean -C deps/libmnl || true - $(MAKE) distclean -C deps/libnfnetlink || true + -@$(MAKE) -f kmake.mk kclean + @$(MAKE) -f uspace.mk clean + diff --git a/README.md b/README.md index a9ab70b..2cd6a15 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,15 @@ If you don't want youtubeUnblock to log on each googlevideo request pass -DSILEN Also DNS over HTTPS (DOH) is preferred for additional anonimity. -## Troubleshooting -If you have any troubles with youtubeUnblock, here are some options to tune. If them don't work in your case, please, open an issue. You can pass these options in make CFLAGS (`make CFLAGS=...`) or edit CFLAGS variable in Makefile. +## Flags Available flags: -- -DUSE_SEG2_DELAY This flag forces youtubeUnblock to wait little bit before send the 2nd part of the split packet. You can tune the amount of time in `#define SEG2_DELAY 100` where 100 stands for milliseconds. -- -DNO_FAKE_SNI This flag disables -DFAKE_SNI which forces youtubeUnblock to send at least three packets instead of one with TLS ClientHello: Fake ClientHello, 1st part of original ClientHello, 2nd part of original ClientHello. This flag may be related to some Operation not permitted error messages, so befor open an issue refer to FAQ for EPERMS. -- -DNOUSE_GSO This flag disables fix for Google Chrome fat ClientHello. The GSO is well tested now, so this flag probably won't fix anything. +- `--seg2delay=` - This flag forces youtubeUnblock to wait little bit before send the 2nd part of the split packet. +- `--fake-sni={ack,ttl, none}` This flag enables fake-sni which forces youtubeUnblock to send at least three packets instead of one with TLS ClientHello: Fake ClientHello, 1st part of original ClientHello, 2nd part of original ClientHello. This flag may be related to some Operation not permitted error messages, so befor open an issue refer to FAQ for EPERMS. Note, that this flag is set to `ack` by default. You may disable fake sni by setting it to `none`. Note, that your ISP may have conntrack drop on invalid state enabled, so this flag won't work. Use `ttl` to escape that. +- `--fake-sni-ttl=` Tunes the time to live of fake sni messages. TTL is specified like that the packet will go through the TSPU and captured by it, but will not reach the destination server. Defaults to 8. +- `--no-gso` Disables support for Google Chrome fat packets which uses GSO. This feature is well tested now, so this flag probably won't fix anything. +- `--threads=` Specifies the amount of threads you want to be running for your program. This defaults to 1 and shouldn't be edited for normal use. If you have performance issues, consult [performance chaptr](https://github.com/Waujito/youtubeUnblock?tab=readme-ov-file#performance) +- `--silent` - Disables Google video detected debug logs. +- `--frag={tcp,ip,none}` Specifies the fragmentation strategy for the packet. tcp is used by default. Ip fragmentation may be blocked by TSPU. None specifies no fragmentation. Probably this won't work, but may be will work for some fake sni strategies. If you are on Chromium you may have to disable kyber (the feature that makes the TLS ClientHello very fat). I've got the problem with it on router, so to escape possibly errors it is better to just disable it: in chrome://flags search for kyber and switch it to disabled state. @@ -55,9 +58,9 @@ EPERM may occur in a lot of places but generally here are two: mnl_cb_run and wh If you have bad performance you can queue to youtubeUnblock only first, say, 20 packets from the connection. To do so, use nftables conntrack packets counter: `nft add rule inet fw4 mangle_forward tcp dport 443 ct original "packets < 20" counter queue num 537 bypass`. For my 1 CPU core device it worked pretty well. This works because we do care about only first packets with ClientHello. We don't need to process others. The same behavior is also possible in iptables: `iptables -t mangle -A FORWARD -p tcp -m tcp --dport 443 -m connbytes --connbytes-dir original --connbytes-mode packets --connbytes 0:19 -j NFQUEUE --queue-num 537 --queue-bypass`. (The package iptables-mod-conntrack-extra is required for connbytes on OpenWRT) - For hosts change FORWARD to OUTPUT. +You can use `--queue-balance` with multiple instances of youtubeUnblock. This behavior is supported via multithreading. Just pass -DTHREADS_NUM=n where n stands for an amount of threads you want to be enabled. The n defaults to 1. The maximum threads defaults to 16 but may be altered programatically. Note, that if you are about to increase it, here is 100% chance that you are on the wrong way. ## OpenWRT case The package is also compatible with routers. The router should be running by linux-based system such as [OpenWRT](https://openwrt.org/). diff --git a/config.h b/config.h new file mode 100644 index 0000000..483703a --- /dev/null +++ b/config.h @@ -0,0 +1,68 @@ + +struct config_t { + unsigned int queue_start_num; + int rawsocket; + int threads; + int use_gso; + int fragmentation_strategy; + unsigned char fake_sni_ttl; + int fake_sni_strategy; + int verbose; + unsigned int seg2_delay; +}; +extern struct config_t config; + +#define MAX_THREADS 16 + +#ifndef THREADS_NUM +#define THREADS_NUM 1 +#endif + +#if THREADS_NUM > MAX_THREADS +#error "Too much threads" +#endif + +#ifndef NOUSE_GSO +#define USE_GSO +#endif + +#define FRAG_STRAT_TCP 0 +#define FRAG_STRAT_IP 1 +#define FRAG_STRAT_NONE 2 + +#ifndef FRAGMENTATION_STRATEGY +#define FRAGMENTATION_STRATEGY FRAG_STRAT_TCP +#endif + +#define RAWSOCKET_MARK (1 << 15) + +#ifdef USE_SEG2_DELAY +#define SEG2_DELAY 100 +#endif + +#define FAKE_SNI_TTL 8 + +// No fake SNI +#define FKSN_STRAT_NONE 0 +// Will invalidate fake client hello by out-of-ack_seq out-of-seq request +#define FKSN_STRAT_ACK_SEQ 1 +// Will assume that GGC server is located further than FAKE_SNI_TTL +// Thus, Fake Client Hello will be eliminated automatically. +#define FKSN_STRAT_TTL 2 + + +#ifdef NO_FAKE_SNI +#define FAKE_SNI_STRATEGY FKSN_STRAT_NONE +#endif + +#ifndef FAKE_SNI_STRATEGY +#define FAKE_SNI_STRATEGY FKSN_STRAT_ACK_SEQ +#endif + +#if !defined(SILENT) && !defined(KERNEL_SPACE) +#define DEBUG +#endif + +// The Maximum Transmission Unit size for rawsocket +// Larger packets will be fragmented. Applicable for Chrome's kyber. +#define AVAILABLE_MTU 1384 diff --git a/deps/libnfnetlink/.gitignore b/deps/libnfnetlink/.gitignore index 6bf816e..e485b47 100644 --- a/deps/libnfnetlink/.gitignore +++ b/deps/libnfnetlink/.gitignore @@ -15,3 +15,4 @@ Makefile.in /stamp-h1 /*.pc +doxygen.cfg diff --git a/deps/libnfnetlink/doxygen.cfg b/deps/libnfnetlink/doxygen.cfg deleted file mode 100644 index cedc44d..0000000 --- a/deps/libnfnetlink/doxygen.cfg +++ /dev/null @@ -1,180 +0,0 @@ -DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = libnfnetlink -PROJECT_NUMBER = 1.0.2 -OUTPUT_DIRECTORY = doxygen -CREATE_SUBDIRS = NO -OUTPUT_LANGUAGE = English -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = -ALWAYS_DETAILED_SEC = NO -INLINE_INHERITED_MEMB = NO -FULL_PATH_NAMES = NO -STRIP_FROM_PATH = -STRIP_FROM_INC_PATH = -SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO -QT_AUTOBRIEF = NO -MULTILINE_CPP_IS_BRIEF = NO -INHERIT_DOCS = YES -SEPARATE_MEMBER_PAGES = NO -TAB_SIZE = 8 -ALIASES = -OPTIMIZE_OUTPUT_FOR_C = YES -OPTIMIZE_OUTPUT_JAVA = NO -OPTIMIZE_FOR_FORTRAN = NO -OPTIMIZE_OUTPUT_VHDL = NO -BUILTIN_STL_SUPPORT = NO -CPP_CLI_SUPPORT = NO -SIP_SUPPORT = NO -DISTRIBUTE_GROUP_DOC = NO -SUBGROUPING = YES -TYPEDEF_HIDES_STRUCT = NO -EXTRACT_ALL = NO -EXTRACT_PRIVATE = NO -EXTRACT_STATIC = NO -EXTRACT_LOCAL_CLASSES = YES -EXTRACT_LOCAL_METHODS = NO -EXTRACT_ANON_NSPACES = NO -HIDE_UNDOC_MEMBERS = NO -HIDE_UNDOC_CLASSES = NO -HIDE_FRIEND_COMPOUNDS = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO -CASE_SENSE_NAMES = YES -HIDE_SCOPE_NAMES = NO -SHOW_INCLUDE_FILES = YES -INLINE_INFO = YES -SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = NO -SORT_GROUP_NAMES = NO -SORT_BY_SCOPE_NAME = NO -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES -GENERATE_DEPRECATEDLIST= YES -ENABLED_SECTIONS = -MAX_INITIALIZER_LINES = 30 -SHOW_USED_FILES = YES -FILE_VERSION_FILTER = -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_NO_PARAMDOC = NO -WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = -INPUT = . -INPUT_ENCODING = UTF-8 -FILE_PATTERNS = *.c libnfnetlink.h -RECURSIVE = YES -EXCLUDE = -EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = */.git/* .*.d -EXCLUDE_SYMBOLS = EXPORT_SYMBOL -EXAMPLE_PATH = -EXAMPLE_PATTERNS = -EXAMPLE_RECURSIVE = NO -IMAGE_PATH = -INPUT_FILTER = -FILTER_PATTERNS = -FILTER_SOURCE_FILES = NO -SOURCE_BROWSER = YES -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = NO -REFERENCES_RELATION = NO -REFERENCES_LINK_SOURCE = YES -USE_HTAGS = NO -VERBATIM_HEADERS = YES -ALPHABETICAL_INDEX = NO -COLS_IN_ALPHA_INDEX = 5 -IGNORE_PREFIX = -GENERATE_HTML = YES -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_STYLESHEET = -GENERATE_HTMLHELP = NO -GENERATE_DOCSET = NO -DOCSET_FEEDNAME = "Doxygen generated docs" -DOCSET_BUNDLE_ID = org.doxygen.Project -HTML_DYNAMIC_SECTIONS = NO -CHM_FILE = -HHC_LOCATION = -GENERATE_CHI = NO -BINARY_TOC = NO -TOC_EXPAND = NO -DISABLE_INDEX = NO -ENUM_VALUES_PER_LINE = 4 -GENERATE_TREEVIEW = NO -TREEVIEW_WIDTH = 250 -GENERATE_LATEX = NO -LATEX_OUTPUT = latex -LATEX_CMD_NAME = latex -MAKEINDEX_CMD_NAME = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = a4wide -EXTRA_PACKAGES = -LATEX_HEADER = -PDF_HYPERLINKS = YES -USE_PDFLATEX = YES -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -GENERATE_RTF = NO -RTF_OUTPUT = rtf -COMPACT_RTF = NO -RTF_HYPERLINKS = NO -RTF_STYLESHEET_FILE = -RTF_EXTENSIONS_FILE = -GENERATE_MAN = YES -MAN_OUTPUT = man -MAN_EXTENSION = .3 -MAN_LINKS = NO -GENERATE_XML = NO -XML_OUTPUT = xml -XML_PROGRAMLISTING = YES -GENERATE_AUTOGEN_DEF = NO -GENERATE_PERLMOD = NO -PERLMOD_LATEX = NO -PERLMOD_PRETTY = YES -PERLMOD_MAKEVAR_PREFIX = -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -INCLUDE_PATH = -INCLUDE_FILE_PATTERNS = -PREDEFINED = -EXPAND_AS_DEFINED = -SKIP_FUNCTION_MACROS = YES -TAGFILES = -GENERATE_TAGFILE = -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -PERL_PATH = /usr/bin/perl -CLASS_DIAGRAMS = YES -MSCGEN_PATH = -HIDE_UNDOC_RELATIONS = YES -HAVE_DOT = YES -CLASS_GRAPH = YES -COLLABORATION_GRAPH = YES -GROUP_GRAPHS = YES -UML_LOOK = NO -TEMPLATE_RELATIONS = NO -INCLUDE_GRAPH = YES -INCLUDED_BY_GRAPH = YES -CALL_GRAPH = NO -CALLER_GRAPH = NO -GRAPHICAL_HIERARCHY = YES -DIRECTORY_GRAPH = YES -DOT_IMAGE_FORMAT = png -DOT_PATH = -DOTFILE_DIRS = -DOT_GRAPH_MAX_NODES = 50 -MAX_DOT_GRAPH_DEPTH = 0 -DOT_TRANSPARENT = YES -DOT_MULTI_TARGETS = NO -GENERATE_LEGEND = YES -DOT_CLEANUP = YES -SEARCHENGINE = NO diff --git a/ipt_YTUNBLOCK.h b/ipt_YTUNBLOCK.h new file mode 100644 index 0000000..9394f39 --- /dev/null +++ b/ipt_YTUNBLOCK.h @@ -0,0 +1,6 @@ +#ifndef IPT_YTUNBLOCK_H +#define IPT_YTUNBLOCK_H + +struct xt_ytunblock_tginfo {}; + +#endif /* IPT_YTUNBLOCK_H */ diff --git a/iptk_YTUNBLOCK.c b/iptk_YTUNBLOCK.c new file mode 100644 index 0000000..0f9910a --- /dev/null +++ b/iptk_YTUNBLOCK.c @@ -0,0 +1,341 @@ +#define _GNU_SOURCE +// 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 +#include +#include +#include +#include +#include +#include +#include +#include "ipt_YTUNBLOCK.h" + +#include "mangle.h" +#include "config.h" +#include "raw_replacements.h" + +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); +MODULE_AUTHOR("Vadim Vetrov "); +MODULE_DESCRIPTION("Linux kernel module for youtube unblock"); + +static int rsfd; +static struct socket *rawsocket; +DEFINE_MUTEX(rslock); + +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; + } + + sockptr_t optval = { + .kernel = NULL, + .is_kernel = 1 + }; + + int mark = RAWSOCKET_MARK; + optval.kernel = &mark; + ret = sock_setsockopt(rawsocket, SOL_SOCKET, SO_MARK, optval, sizeof(mark)); + if (ret < 0) + { + pr_alert("setsockopt(SO_MARK, %d) failed\n", mark); + goto sr_err; + } + int one = 1; + optval.kernel = &one; + + return 0; +sr_err: + sock_release(rawsocket); +err: + return ret; +} + +static void close_raw_socket(void) { + sock_release(rawsocket); +} + +#define AVAILABLE_MTU 1384 + +static int send_raw_socket(const uint8_t *pkt, uint32_t pktlen) { + + if (pktlen > AVAILABLE_MTU) { + pr_warn("The packet is too big and may cause issues!"); + + __u32 buff1_size = pktlen; + __u32 buff2_size = pktlen; + __u8 *buff1 = kmalloc(pktlen, GFP_ATOMIC); + if (buff1 == NULL) return -1; + __u8 *buff2 = kmalloc(pktlen, GFP_ATOMIC); + if (buff2 == NULL) { + kfree(buff1); + return -1; + } + + int ret; + +#if defined(USE_TCP_SEGMENTATION) || defined(RAWSOCK_TCP_FSTRAT) + if ((ret = tcp4_frag(pkt, pktlen, AVAILABLE_MTU-128, + buff1, &buff1_size, buff2, &buff2_size)) < 0) + return ret; +#elif defined(USE_IP_FRAGMENTATION) || defined(RAWSOCK_IP_FSTRAT) + if ((ret = ip4_frag(pkt, pktlen, AVAILABLE_MTU-128, + buff1, &buff1_size, buff2, &buff2_size)) < 0) + return ret; +#else + pr_warn("send_raw_socket: Packet is too big but fragmentation is disabled! " + "Pass -DRAWSOCK_TCP_FSTRAT or -DRAWSOCK_IP_FSTRAT as CFLAGS " + "To enable it only for raw socket\n"); + return -EINVAL; +#endif + + int sent = 0; + ret = send_raw_socket(buff1, buff1_size); + + if (ret >= 0) sent += ret; + else { + kfree(buff1); + kfree(buff2); + return ret; + } + + kfree(buff1); + + ret = send_raw_socket(buff2, buff2_size); + if (ret >= 0) sent += ret; + else { + kfree(buff2); + return ret; + } + + kfree(buff2); + + return sent; + } + + struct iphdr *iph; + + int ret; + 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; + iov.iov_base = (__u8 *)pkt; + iov.iov_len = pktlen; + iov_iter_kvec(&msg.msg_iter, READ, &iov, 1, 1); + + msg.msg_flags = 0; + msg.msg_name = &daddr; + msg.msg_namelen = sizeof(struct sockaddr_in); + msg.msg_control = NULL; + msg.msg_controllen = 0; + + mutex_lock(&rslock); + ret = kernel_sendmsg(rawsocket, &msg, &iov, 1, pktlen); + mutex_unlock(&rslock); + + return ret; +} +static unsigned int ykb_tg(struct sk_buff *skb, const struct xt_action_param *par) +{ + if ((skb->mark & RAWSOCKET_MARK) == RAWSOCKET_MARK) + return XT_CONTINUE; + + if (skb->head == NULL) return XT_CONTINUE; + + // TODO: Mallocs are bad! + uint32_t buflen = skb->len; + __u8 *buf = kmalloc(skb->len, GFP_ATOMIC); + if (buf == NULL) { + pr_err("Cannot alloc enough buffer space"); + goto accept; + } + if (skb_copy_bits(skb, 0, buf, skb->len) < 0) { + pr_err("Unable copy bits\n"); + goto ac_fkb; + } + struct iphdr *iph; + uint32_t iph_len; + struct tcphdr *tcph; + uint32_t tcph_len; + __u8 *payload; + uint32_t plen; + + int ret = tcp4_payload_split(buf, buflen, &iph, &iph_len, + &tcph, &tcph_len, &payload, &plen); + + if (ret < 0) + goto ac_fkb; + + struct verdict vrd = analyze_tls_data(payload, plen); + + if (vrd.gvideo_hello) { + int ret; + pr_info("Googlevideo detected\n"); + + ip4_set_checksum(iph); + tcp4_set_checksum(tcph, iph); + + uint32_t f1len = skb->len; + uint32_t f2len = skb->len; + __u8 *frag1 = kmalloc(f1len, GFP_ATOMIC); + if (!frag1) { + pr_err("Cannot alloc enough gv frag1 buffer space"); + goto ac_fkb; + } + __u8 *frag2 = kmalloc(f2len, GFP_ATOMIC); + if (!frag2) { + pr_err("Cannot alloc enough gv frag1 buffer space"); + kfree(frag1); + goto ac_fkb; + } + + +#ifdef FAKE_SNI + uint32_t fksn_len = FAKE_SNI_MAXLEN; + __u8 *fksn_buf = kmalloc(fksn_len, GFP_ATOMIC); + if (!fksn_buf) { + pr_err("Cannot alloc enough gksn buffer space"); + goto fallback; + } + + ret = gen_fake_sni(iph, tcph, fksn_buf, &fksn_len); + if (ret < 0) { + pr_err("Cannot alloc enough gksn buffer space"); + goto fksn_fb; + } +#endif + +#if defined(USE_TCP_SEGMENTATION) + size_t ipd_offset = vrd.sni_offset; + size_t mid_offset = ipd_offset + vrd.sni_len / 2; + + + if ((ret = tcp4_frag(buf, skb->len, + mid_offset, frag1, &f1len, frag2, &f2len)) < 0) { + pr_err("tcp4_frag: %d", ret); + goto fksn_fb; + } +#elif defined(USE_IP_FRAGMENTATION) + size_t ipd_offset = tcph_len + vrd.sni_offset; + size_t mid_offset = ipd_offset + vrd.sni_len / 2; + mid_offset += 8 - mid_offset % 8; + + if ((ret = ip4_frag(buf, skb->len, + mid_offset, frag1, &f1len, frag2, &f2len)) < 0) { + pr_err("ip4_frag: %d", ret); + goto fksn_fb; + } +#endif + +#ifdef FAKE_SNI + ret = send_raw_socket(fksn_buf, fksn_len); + if (ret < 0) { + pr_err("fksn_send: %d", ret); + goto fksn_fb; + } +#endif + +#if defined(USE_NO_FRAGMENTATION) +#ifdef SEG2_DELAY +#error "SEG2_DELAY is incompatible with NO FRAGMENTATION" +#endif + ret = send_raw_socket(buf, buflen); + if (ret < 0) { + pr_err("nofrag_send: %d", ret); + } + goto fksn_fb; +#endif + + ret = send_raw_socket(frag2, f2len); + if (ret < 0) { + pr_err("raw frag2 send: %d", ret); + goto fksn_fb; + } + +#ifdef SEG2_DELAY +#error "Seg2 delay is unsupported yet for kmod" +#else + ret = send_raw_socket(frag1, f1len); + if (ret < 0) { + pr_err("raw frag1 send: %d", ret); + goto fksn_fb; + } +#endif + +fksn_fb: +#ifdef FAKE_SNI + kfree(fksn_buf); +#endif +fallback: +#ifndef SEG2_DELAY + kfree(frag1); +#endif + kfree(frag2); + kfree(buf); + kfree_skb(skb); + return NF_STOLEN; + } +ac_fkb: + kfree(buf); +accept: + return XT_CONTINUE; +} + +static int ykb_chk(const struct xt_tgchk_param *par) { + return 0; +} + + +static struct xt_target ykb_tg_reg __read_mostly = { + .name = "YTUNBLOCK", + .target = ykb_tg, + .table = "mangle", + .hooks = (1 << NF_INET_LOCAL_OUT) | (1 << NF_INET_FORWARD), + .targetsize = sizeof(struct xt_ytunblock_tginfo), + .proto = IPPROTO_TCP, + .family = NFPROTO_IPV4, + .checkentry = ykb_chk, + .me = THIS_MODULE, +}; + +static int __init ykb_init(void) { + int ret = 0; + + ret = open_raw_socket(); + if (ret < 0) goto err; + + ret = xt_register_target(&ykb_tg_reg); + if (ret < 0) goto close_rawsocket; + + pr_info("youtubeUnblock kernel module started.\n"); + return 0; +close_rawsocket: + close_raw_socket(); +err: + return ret; +} + +static void __exit ykb_destroy(void) { + xt_unregister_target(&ykb_tg_reg); + close_raw_socket(); + pr_info("youtubeUnblock kernel module destroyed.\n"); +} + +module_init(ykb_init); +module_exit(ykb_destroy); diff --git a/kmake.mk b/kmake.mk new file mode 100644 index 0000000..00e3f0f --- /dev/null +++ b/kmake.mk @@ -0,0 +1,42 @@ +#Kernel module makes here +PWD := $(CURDIR) + +CC := gcc +CCLD := $(CC) +LD := ld +CFLAGS := +LDFLAGS := + +IPT_CFLAGS := -Wall -Wpedantic -O2 + +.PHONY: kmake kload kunload kreload kclean kmclean xclean +kmake: kmod xmod + +kmod: + $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules + +xmod: libipt_YTUNBLOCK.so + +libipt_YTUNBLOCK.so: libipt_YTUNBLOCK.o + $(CCLD) -shared -fPIC ${IPT_CFLAGS} -o $@ $^; + +libipt_YTUNBLOCK.o: libipt_YTUNBLOCK.c + $(CC) ${IPT_CFLAGS} -D_INIT=lib$*_init -fPIC -c -o $@ $<; + +kload: + insmod ipt_YTUNBLOCK.ko + cp ./libipt_YTUNBLOCK.so /usr/lib/xtables/ + +kunload: + -rmmod ipt_YTUNBLOCK + -/bin/rm /usr/lib/xtables/libipt_YTUNBLOCK.so + +kreload: kunload kload + +kclean: xtclean kmclean + +kmclean: + -$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean + +xtclean: + -/bin/rm -f libipt_YTUNBLOCK.so libipt_YTUNBLOCK.o diff --git a/libipt_YTUNBLOCK.c b/libipt_YTUNBLOCK.c new file mode 100644 index 0000000..5b7b099 --- /dev/null +++ b/libipt_YTUNBLOCK.c @@ -0,0 +1,26 @@ +// Used to register target in iptables +#include +#include + +#include +#include "ipt_YTUNBLOCK.h" + +#define _init __attribute__((constructor)) _INIT +#define __maybe_unused __attribute__((__unused__)) + +static void YTKB_help(void) { + printf("Youtube Unblock - bypass youtube slowdown DPI in Russia\n"); +} + +static struct xtables_target ykb_tg_reg = { + .name = "YTUNBLOCK", + .version = XTABLES_VERSION, + .family = NFPROTO_IPV4, + .size = XT_ALIGN(sizeof(struct xt_ytunblock_tginfo)), + .userspacesize = XT_ALIGN(sizeof(struct xt_ytunblock_tginfo)), + .help = YTKB_help, +}; + +void _init(void) { + xtables_register_target(&ykb_tg_reg); +} diff --git a/mangle.c b/mangle.c new file mode 100644 index 0000000..b049168 --- /dev/null +++ b/mangle.c @@ -0,0 +1,473 @@ +#define _GNU_SOURCE + +#include "mangle.h" +#include "raw_replacements.h" +#include "config.h" + +#ifdef KERNEL_SPACE +#include +#include + +#define printf pr_info +#define perror pr_err +#define lgerror(msg, ret) (pr_err(msg ": %d\n", ret)) +#else +#include +#include +#include + +typedef uint8_t __u8; +typedef uint32_t __u32; +typedef uint16_t __u16; + +#define lgerror(msg, ret) __extension__ ({errno = -ret; perror(msg);}) +#endif + +void tcp4_set_checksum(struct tcphdr *tcph, struct iphdr *iph) +{ +#ifdef KERNEL_SPACE + uint32_t tcp_packet_len = ntohs(iph->tot_len) - (iph->ihl << 2); + tcph->check = 0; + tcph->check = csum_tcpudp_magic( + iph->saddr, iph->daddr, tcp_packet_len, + IPPROTO_TCP, + csum_partial(tcph, tcp_packet_len, 0)); +#else + nfq_tcp_compute_checksum_ipv4(tcph, iph); +#endif +} + +void ip4_set_checksum(struct iphdr *iph) +{ +#ifdef KERNEL_SPACE + iph->check = 0; + iph->check = ip_fast_csum(iph, iph->ihl); +#else + nfq_ip_set_checksum(iph); +#endif +} + + +int ip4_payload_split(__u8 *pkt, __u32 buflen, + struct iphdr **iph, __u32 *iph_len, + __u8 **payload, __u32 *plen) { + if (pkt == NULL || buflen < sizeof(struct iphdr)) { + return -EINVAL; + } + + struct iphdr *hdr = (struct iphdr *)pkt; + if (hdr->version != IPVERSION) return -EINVAL; + + __u32 hdr_len = hdr->ihl * 4; + __u32 pktlen = ntohs(hdr->tot_len); + if (buflen < pktlen || hdr_len > pktlen) return -EINVAL; + + if (iph) + *iph = hdr; + if (iph_len) + *iph_len = hdr_len; + if (payload) + *payload = pkt + hdr_len; + if (plen) + *plen = pktlen - hdr_len; + + return 0; +} + +int tcp4_payload_split(__u8 *pkt, __u32 buflen, + struct iphdr **iph, __u32 *iph_len, + struct tcphdr **tcph, __u32 *tcph_len, + __u8 **payload, __u32 *plen) { + struct iphdr *hdr; + __u32 hdr_len; + struct tcphdr *thdr; + __u32 thdr_len; + + __u8 *tcph_pl; + __u32 tcph_plen; + + if (ip4_payload_split(pkt, buflen, &hdr, &hdr_len, + &tcph_pl, &tcph_plen)){ + return -EINVAL; + } + + + if ( + hdr->protocol != IPPROTO_TCP || + tcph_plen < sizeof(struct tcphdr)) { + return -EINVAL; + } + + + thdr = (struct tcphdr *)(tcph_pl); + thdr_len = thdr->doff * 4; + + if (thdr_len > tcph_plen) { + return -EINVAL; + } + + if (iph) *iph = hdr; + if (iph_len) *iph_len = hdr_len; + if (tcph) *tcph = thdr; + if (tcph_len) *tcph_len = thdr_len; + if (payload) *payload = tcph_pl + thdr_len; + if (plen) *plen = tcph_plen - thdr_len; + + return 0; +} + +// split packet to two ipv4 fragments. +int ip4_frag(const __u8 *pkt, __u32 buflen, __u32 payload_offset, + __u8 *frag1, __u32 *f1len, + __u8 *frag2, __u32 *f2len) { + + struct iphdr *hdr; + const __u8 *payload; + __u32 plen; + __u32 hdr_len; + int ret; + + if (!frag1 || !f1len || !frag2 || !f2len) + return -EINVAL; + + if ((ret = ip4_payload_split( + (__u8 *)pkt, buflen, + &hdr, &hdr_len, (__u8 **)&payload, &plen)) < 0) { + lgerror("ipv4_frag: TCP Header extract error", ret); + return -EINVAL; + } + + if (plen <= payload_offset) { + return -EINVAL; + } + + if (payload_offset & ((1 << 3) - 1)) { + lgerror("ipv4_frag: Payload offset MUST be a multiply of 8!", -EINVAL); + + return -EINVAL; + } + + __u32 f1_plen = payload_offset; + __u32 f1_dlen = f1_plen + hdr_len; + + __u32 f2_plen = plen - payload_offset; + __u32 f2_dlen = f2_plen + hdr_len; + + if (*f1len < f1_dlen || *f2len < f2_dlen) { + return -ENOMEM; + } + *f1len = f1_dlen; + *f2len = f2_dlen; + + memcpy(frag1, hdr, hdr_len); + memcpy(frag2, hdr, hdr_len); + + memcpy(frag1 + hdr_len, payload, f1_plen); + memcpy(frag2 + hdr_len, payload + payload_offset, f2_plen); + + struct iphdr *f1_hdr = (void *)frag1; + struct iphdr *f2_hdr = (void *)frag2; + + __u16 f1_frag_off = ntohs(f1_hdr->frag_off); + __u16 f2_frag_off = ntohs(f2_hdr->frag_off); + + f1_frag_off &= IP_OFFMASK; + f1_frag_off |= IP_MF; + + if ((f2_frag_off & ~IP_OFFMASK) == IP_MF) { + f2_frag_off &= IP_OFFMASK; + f2_frag_off |= IP_MF; + } else { + f2_frag_off &= IP_OFFMASK; + } + + f2_frag_off += (__u16)payload_offset / 8; + + f1_hdr->frag_off = htons(f1_frag_off); + f1_hdr->tot_len = htons(f1_dlen); + + f2_hdr->frag_off = htons(f2_frag_off); + f2_hdr->tot_len = htons(f2_dlen); + + + if (config.verbose) + printf("Packet split in portion %u %u\n", f1_plen, f2_plen); + + ip4_set_checksum(f1_hdr); + ip4_set_checksum(f2_hdr); + + return 0; +} + +// split packet to two tcp-on-ipv4 segments. +int tcp4_frag(const __u8 *pkt, __u32 buflen, __u32 payload_offset, + __u8 *seg1, __u32 *s1len, + __u8 *seg2, __u32 *s2len) { + + struct iphdr *hdr; + __u32 hdr_len; + struct tcphdr *tcph; + __u32 tcph_len; + __u32 plen; + const __u8 *payload; + int ret; + + if (!seg1 || !s1len || !seg2 || !s2len) + return -EINVAL; + + if ((ret = tcp4_payload_split((__u8 *)pkt, buflen, + &hdr, &hdr_len, + &tcph, &tcph_len, + (__u8 **)&payload, &plen)) < 0) { + lgerror("tcp4_frag: tcp4_payload_split", ret); + + return -EINVAL; + } + + + if ( + ntohs(hdr->frag_off) & IP_MF || + ntohs(hdr->frag_off) & IP_OFFMASK) { + printf("tcp4_frag: frag value: %d\n", + ntohs(hdr->frag_off)); + lgerror("tcp4_frag: ip fragmentation is set", -EINVAL); + return -EINVAL; + } + + + if (plen <= payload_offset) { + return -EINVAL; + } + + __u32 s1_plen = payload_offset; + __u32 s1_dlen = s1_plen + hdr_len + tcph_len; + + __u32 s2_plen = plen - payload_offset; + __u32 s2_dlen = s2_plen + hdr_len + tcph_len; + + if (*s1len < s1_dlen || *s2len < s2_dlen) + return -ENOMEM; + + *s1len = s1_dlen; + *s2len = s2_dlen; + + memcpy(seg1, hdr, hdr_len); + memcpy(seg2, hdr, hdr_len); + + memcpy(seg1 + hdr_len, tcph, tcph_len); + memcpy(seg2 + hdr_len, tcph, tcph_len); + + memcpy(seg1 + hdr_len + tcph_len, payload, s1_plen); + memcpy(seg2 + hdr_len + tcph_len, payload + payload_offset, s2_plen); + + struct iphdr *s1_hdr = (void *)seg1; + struct iphdr *s2_hdr = (void *)seg2; + + struct tcphdr *s1_tcph = (void *)(seg1 + hdr_len); + struct tcphdr *s2_tcph = (void *)(seg2 + hdr_len); + + s1_hdr->tot_len = htons(s1_dlen); + s2_hdr->tot_len = htons(s2_dlen); + + s2_tcph->seq = htonl(ntohl(s2_tcph->seq) + payload_offset); + + if (config.verbose) + printf("Packet split in portion %u %u\n", s1_plen, s2_plen); + + tcp4_set_checksum(s1_tcph, s1_hdr); + tcp4_set_checksum(s2_tcph, s2_hdr); + + 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 + +const char googlevideo_ending[] = "googlevideo.com"; +const int googlevideo_len = 15; + + +typedef __u8 uint8_t; +typedef __u32 uint32_t; +typedef __u16 uint16_t; + +/** + * Processes tls payload of the tcp request. + * + * data Payload data of TCP. + * dlen Length of `data`. + */ +struct verdict analyze_tls_data( + const uint8_t *data, + uint32_t dlen) +{ + struct 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); + uint8_t tls_vminor = *(msgData + 2); + uint16_t message_length = ntohs(*(uint16_t *)(msgData + 3)); + const uint8_t *message_length_ptr = msgData + 3; + + + if (i + 5 + message_length > dlen) break; + + if (tls_content_type != TLS_CONTENT_TYPE_HANDSHAKE) + goto nextMessage; + + + 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; + const uint8_t *handshakeProto_length_ptr = 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); + const uint8_t *extensionsLen_ptr = msgPtr; + msgPtr += 2; + + const uint8_t *extensionsPtr = msgPtr; + const uint8_t *extensions_end = extensionsPtr + extensionsLen; + if (extensions_end > data_end) break; + + 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); + const uint8_t *extensionLen_ptr = 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); + + const uint8_t *sni_ext_dlen_ptr = 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; + uint8_t sni_type = *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; + // sni_len + + vrd.sni_offset = (uint8_t *)sni_name - data; + vrd.sni_len = sni_len; + + char *gv_startp = sni_name + sni_len - googlevideo_len; + if (sni_len >= googlevideo_len && + sni_len < 128 && + !strncmp(gv_startp, + googlevideo_ending, + googlevideo_len)) { + + vrd.gvideo_hello = 1; + } + +nextExtension: + extensionsPtr += 2 + 2 + extensionLen; + } +nextMessage: + i += 5 + message_length; + } + + return vrd; +} + +int gen_fake_sni(const struct iphdr *iph, const struct tcphdr *tcph, + uint8_t *buf, uint32_t *buflen) { + + if (!iph || !tcph || !buf || !buflen) + return -EINVAL; + + int ip_len = iph->ihl * 4; + size_t data_len = sizeof(fake_sni); + + size_t dlen = data_len + ip_len; + + if (*buflen < dlen) + return -ENOMEM; + *buflen = dlen; + + memcpy(buf, iph, ip_len); + memcpy(buf + ip_len, fake_sni, data_len); + struct iphdr *niph = (struct iphdr *)buf; + + niph->protocol = IPPROTO_TCP; + niph->tot_len = htons(dlen); + + int ret = 0; + struct tcphdr *ntcph = (struct tcphdr *)(buf + ip_len); + +#ifdef KERNEL_SPACE + ntcph->dest = tcph->dest; + ntcph->source = tcph->source; +#else + ntcph->th_dport = tcph->th_dport; + ntcph->th_sport = tcph->th_sport; + +#if FAKE_SNI_STRATEGY == FKSN_STRAT_TTL + ntcph->ack = tcph->ack; + ntcph->ack_seq = tcph->ack_seq; + niph->ttl = FAKE_SNI_TTL; +#endif + +#endif + + ip4_set_checksum(niph); + tcp4_set_checksum(ntcph, niph); + + return 0; +} diff --git a/mangle.h b/mangle.h new file mode 100644 index 0000000..103b3ea --- /dev/null +++ b/mangle.h @@ -0,0 +1,66 @@ +#ifndef YU_MANGLE_H +#define YU_MANGLE_H + +#ifdef KERNEL_SPACE +#include +typedef __u8 uint8_t; +typedef __u32 uint32_t; + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* from */ +#define IP_RF 0x8000 /* reserved fragment flag */ +#define IP_DF 0x4000 /* dont fragment flag */ +#define IP_MF 0x2000 /* more fragments flag */ +#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ +#else +#define USER_SPACE +#include +#include +#include +#include +#include +#include +#endif + +struct verdict { + int gvideo_hello; /* google video hello packet */ + int sni_offset; /* offset from start of tcp _payload_ */ + int sni_len; +}; + +struct verdict analyze_tls_data(const uint8_t *data, uint32_t dlen); + +int ip4_frag(const uint8_t *pkt, uint32_t pktlen, + uint32_t payload_offset, + uint8_t *frag1, uint32_t *f1len, + uint8_t *frag2, uint32_t *f2len); + +int tcp4_frag(const uint8_t *pkt, uint32_t pktlen, + uint32_t payload_offset, + uint8_t *seg1, uint32_t *s1len, + uint8_t *seg2, uint32_t *s2len); + +int ip4_payload_split(uint8_t *pkt, uint32_t buflen, + struct iphdr **iph, uint32_t *iph_len, + uint8_t **payload, uint32_t *plen); + +int tcp4_payload_split(uint8_t *pkt, uint32_t buflen, + struct iphdr **iph, uint32_t *iph_len, + struct tcphdr **tcph, uint32_t *tcph_len, + uint8_t **payload, uint32_t *plen); + +void tcp4_set_checksum(struct tcphdr *tcph, struct iphdr *iph); +void ip4_set_checksum(struct iphdr *iph); + +int gen_fake_sni(const struct iphdr *iph, const struct tcphdr *tcph, + uint8_t *buf, uint32_t *buflen); +#endif /* YU_MANGLE_H */ diff --git a/owrt/youtubeUnblock.owrt b/owrt/youtubeUnblock.owrt index 26382d3..23be195 100755 --- a/owrt/youtubeUnblock.owrt +++ b/owrt/youtubeUnblock.owrt @@ -1,6 +1,9 @@ #!/bin/sh /etc/rc.common USE_PROCD=1 +START=50 +STOP=50 + # Openwrt procd script: https://openwrt.org/docs/guide-developer/procd-init-script-example # The program should be put into /usr/bin/ # This file should be put into /etc/init.d/ diff --git a/raw_replacements.h b/raw_replacements.h index c6f86b2..3a40ae7 100644 --- a/raw_replacements.h +++ b/raw_replacements.h @@ -1,6 +1,9 @@ #ifndef RAW_REPLACEMENTS_H #define RAW_REPLACEMENTS_H -const char fake_sni[] = "\276(\001\273\366\234|\335\213\222\023\330\200\030\001\366\350e\000\000\001\001\b\n}\355\267Hm/\217\347\026\003\001\004\316\001\000\004\312\003\003K+\272\314\340\306\374>dw%\f\223\346\225\270\270~\335\027\f\264\341H\267\357\303\216T\322[\371 \245\320\212V6\374\3706\232\0216B\325\273P\b\300>\0332>\362\323\033\322\301\204\022f8\223\214\000\"\023\001\023\003\023\002\300+\300/\314\251\314\250\300,\3000\300\n\300\t\300\023\300\024\000\234\000\235\000/\0005\001\000\004_\000\000\000\023\000\021\000\000\016www.google.com\000\027\000\000\377\001\000\001\000\000\n\000\016\000\f\000\035\000\027\000\030\000\031\001\000\001\001\000\v\000\002\001\000\000\020\000\v\000\t\bhttp/1.1\000\005\000\005\001\000\000\000\000\000\"\000\n\000\b\004\003\005\003\006\003\002\003\0003\000k\000i\000\035\000 \333C\212\234-\t\237#\202\\\231\311\022]\333\341t(\t\276U\373u\234\316J~,^|*Z\000\027\000A\004k\n\255\254\376X\226t\001;n~\033\034.\245\027\024\3762_\352$\374\346^f\fF,\201\275\263\336O\231\001\032\200\357dI\266y\031\323\311vR\232\004\r\366FT\004\335\326\356\256\230B\t\313\000*\000\000\000+\000\005\004\003\004\003\003\000\r\000\030\000\026\004\003\005\003\006\003\b\004\b\005\b\006\004\001\005\001\006\001\002\003\002\001\000-\000\002\001\001\000\034\000\002@\001\376\r\0029\000\000\001\000\003\344\000 \337\306\243\332Y\033\a\252\352\025\365Z\035\223\226\304\255\363\215G\356g\344%}7\217\033n\211^\201\002\017g\267\334\326OD}\336\341ZC\230\226'\225\313\357\211\\\242\273\030k\216\377U\315\206\2410\200\203\332Z\223\005\370\b\304\370f\017\200\023\241\223~?\270{\037b\312\001\270\227\366\356\352\002\314\351\006\237\241q\226\300\314\321o\247{\201\317\230}B\005T\3660\335\320\332r?S\217\tq\036\031\326I|\237]\311 c\f\024r\031\310W\373\257\314q)q\030\237\261\227\217Kd?\257'G\320\020\340\256ND\247\005\341\324\024OP>\370\350\270b\311wAj\t\311\213\365i\203\230x\207\354\245<\274\202\230c\v0Y\263\364\022\303a\200\022\031\314\271rl=\327\336\001\327\264\267\342\353\352=\354[u\224\260\257\034\004\232\023\226}\227\030e\221\"\350\207\027dId\324\305\362N:\035\307`\204\337\201;\221\320\266b\362hrH\345e\206\246%\006\020a4\3430\036\225\215\274\275\360Q&\271\237)\222uK\362\017o\220\226W\357\267#\357\v\023\354\213\2629\331\ad\005/~6k\000[\247\301\270\310qJ\004\303|m5\363\376Y\002\243}6\251x\024\331)GH\335\205rI\032\f\210\a\212\347]\271\030\347.\021\213\365\026\030\340/Ny\r\332\3577\3203\026iX}>\2507\327&XRXU!\017\270I\313\352\350^?\352Uss\017\266pF\222NI\245\307_\305#\361\352\243+-\266\317Q\036s\243\277\355{S&\023>\275\360\215\032V\237XOY\345u>\002\305\252T\354\035\327v{P\352M\233\366\221\270\377\251\261f+rF\201wL2W\266X\252\242X\2536I\337c\205uZ\254Fe\305h\t\371\376\216r\336Y\327h\347*\331\257-ZQ{(\336\226\206\017\037\036\021\341\027z\033\254\235\252\227\224\004?p\243\351\\\263\352\205\327#W\345\255\256\375\267bP\3047\363!*K\003t\212(\306\214P\215\3506j\025\375\213e\254s\000)\001\034\000\367\000\361\002\276W%\232?\326\223\277\211v\017\a\361\347\312N\226\024L\260v\210\271j\324[|\270\344\3773\321-\313b>~\310\253XIR\324)&;\033{g;)\344\255\226\370\347I\\y\020\324\360\211vC\310\226s\267|\273$\341\332\2045qh\245w\2255\214\316\030\255\301\326C\343\304=\245\231h`yd\000#s\002\370\374Z\0336\245\361\226\222\306\032k\2457\016h\314(R;\326T~EHH\352\307\023^\247\363\321`V\340\253Z\233\357\227I\373\337z\177\nv\261\252\371\017\226\223\345\005\315y4\b\236N0\2630\017\215c\305&L\260\346J\237\203Q(\335W\027|>\3553\275j\307?W5\3463kc\350\262C\361 \037w!\371}\214\"I\377|\331@a;\342\3566\312\272Z\327u7\204'\215YBLL\235\236\242\345\215\245T\211a\312\263\342\000! \221\202X$\302\317\203\246\207c{\231\330\264\324\\k\271\272\336\356\002|\261O\207\030+\367P\317\356"; +#define FAKE_SNI_MAXLEN 1500 + +static const char fake_sni[] = "\276(\001\273\366\234|\335\213\222\023\330\200\030\001\366\350e\000\000\001\001\b\n}\355\267Hm/\217\347\026\003\001\004\316\001\000\004\312\003\003K+\272\314\340\306\374>dw%\f\223\346\225\270\270~\335\027\f\264\341H\267\357\303\216T\322[\371 \245\320\212V6\374\3706\232\0216B\325\273P\b\300>\0332>\362\323\033\322\301\204\022f8\223\214\000\"\023\001\023\003\023\002\300+\300/\314\251\314\250\300,\3000\300\n\300\t\300\023\300\024\000\234\000\235\000/\0005\001\000\004_\000\000\000\023\000\021\000\000\016www.google.com\000\027\000\000\377\001\000\001\000\000\n\000\016\000\f\000\035\000\027\000\030\000\031\001\000\001\001\000\v\000\002\001\000\000\020\000\v\000\t\bhttp/1.1\000\005\000\005\001\000\000\000\000\000\"\000\n\000\b\004\003\005\003\006\003\002\003\0003\000k\000i\000\035\000 \333C\212\234-\t\237#\202\\\231\311\022]\333\341t(\t\276U\373u\234\316J~,^|*Z\000\027\000A\004k\n\255\254\376X\226t\001;n~\033\034.\245\027\024\3762_\352$\374\346^f\fF,\201\275\263\336O\231\001\032\200\357dI\266y\031\323\311vR\232\004\r\366FT\004\335\326\356\256\230B\t\313\000*\000\000\000+\000\005\004\003\004\003\003\000\r\000\030\000\026\004\003\005\003\006\003\b\004\b\005\b\006\004\001\005\001\006\001\002\003\002\001\000-\000\002\001\001\000\034\000\002@\001\376\r\0029\000\000\001\000\003\344\000 \337\306\243\332Y\033\a\252\352\025\365Z\035\223\226\304\255\363\215G\356g\344%}7\217\033n\211^\201\002\017g\267\334\326OD}\336\341ZC\230\226'\225\313\357\211\\\242\273\030k\216\377U\315\206\2410\200\203\332Z\223\005\370\b\304\370f\017\200\023\241\223~?\270{\037b\312\001\270\227\366\356\352\002\314\351\006\237\241q\226\300\314\321o\247{\201\317\230}B\005T\3660\335\320\332r?S\217\tq\036\031\326I|\237]\311 c\f\024r\031\310W\373\257\314q)q\030\237\261\227\217Kd?\257'G\320\020\340\256ND\247\005\341\324\024OP>\370\350\270b\311wAj\t\311\213\365i\203\230x\207\354\245<\274\202\230c\v0Y\263\364\022\303a\200\022\031\314\271rl=\327\336\001\327\264\267\342\353\352=\354[u\224\260\257\034\004\232\023\226}\227\030e\221\"\350\207\027dId\324\305\362N:\035\307`\204\337\201;\221\320\266b\362hrH\345e\206\246%\006\020a4\3430\036\225\215\274\275\360Q&\271\237)\222uK\362\017o\220\226W\357\267#\357\v\023\354\213\2629\331\ad\005/~6k\000[\247\301\270\310qJ\004\303|m5\363\376Y\002\243}6\251x\024\331)GH\335\205rI\032\f\210\a\212\347]\271\030\347.\021\213\365\026\030\340/Ny\r\332\3577\3203\026iX}>\2507\327&XRXU!\017\270I\313\352\350^?\352Uss\017\266pF\222NI\245\307_\305#\361\352\243+-\266\317Q\036s\243\277\355{S&\023>\275\360\215\032V\237XOY\345u>\002\305\252T\354\035\327v{P\352M\233\366\221\270\377\251\261f+rF\201wL2W\266X\252\242X\2536I\337c\205uZ\254Fe\305h\t\371\376\216r\336Y\327h\347*\331\257-ZQ{(\336\226\206\017\037\036\021\341\027z\033\254\235\252\227\224\004?p\243\351\\\263\352\205\327#W\345\255\256\375\267bP\3047\363!*K\003t\212(\306\214P\215\3506j\025\375\213e\254s\000)\001\034\000\367\000\361\002\276W%\232?\326\223\277\211v\017\a\361\347\312N\226\024L\260v\210\271j\324[|\270\344\3773\321-\313b>~\310\253XIR\324)&;\033{g;)\344\255\226\370\347I\\y\020\324\360\211vC\310\226s\267|\273$\341\332\2045qh\245w\2255\214\316\030\255\301\326C\343\304=\245\231h`yd\000#s\002\370\374Z\0336\245\361\226\222\306\032k\2457\016h\314(R;\326T~EHH\352\307\023^\247\363\321`V\340\253Z\233\357\227I\373\337z\177\nv\261\252\371\017\226\223\345\005\315y4\b\236N0\2630\017\215c\305&L\260\346J\237\203Q(\335W\027|>\3553\275j\307?W5\3463kc\350\262C\361 \037w!\371}\214\"I\377|\331@a;\342\3566\312\272Z\327u7\204'\215YBLL\235\236\242\345\215\245T\211a\312\263\342\000! \221\202X$\302\317\203\246\207c{\231\330\264\324\\k\271\272\336\356\002|\261O\207\030+\367P\317\356"; + #endif /*RAW_REPLACEMENTS_H*/ diff --git a/uspace.mk b/uspace.mk new file mode 100644 index 0000000..8950133 --- /dev/null +++ b/uspace.mk @@ -0,0 +1,94 @@ +#Userspace app makes here +BUILD_DIR := $(CURDIR)/build +DEPSDIR := $(BUILD_DIR)/deps + +CC:=gcc +CCLD:=$(CC) +LD:=ld +override CFLAGS += -Wall -Wpedantic -Wno-unused-variable -I$(DEPSDIR)/include +override LDFLAGS += -L$(DEPSDIR)/lib + +LIBNFNETLINK_CFLAGS := -I$(DEPSDIR)/include +LIBNFNETLINK_LIBS := -L$(DEPSDIR)/lib +LIBMNL_CFLAGS := -I$(DEPSDIR)/include +LIBMNL_LIBS := -L$(DEPSDIR)/lib + +# PREFIX is environment variable, if not set default to /usr/local +ifeq ($(PREFIX),) + PREFIX := /usr/local +else + PREFIX := $(DESTDIR) +endif + +export CC CCLD LD CFLAGS LDFLAGS LIBNFNETLINK_CFLAGS LIBNFNETLINK_LIBS LIBMNL_CFLAGS LIBMNL_LIBS + +APP:=$(BUILD_DIR)/youtubeUnblock + +SRCS := youtubeUnblock.c mangle.c +OBJS := $(SRCS:%.c=$(BUILD_DIR)/%.o) + +LIBNFNETLINK := $(DEPSDIR)/lib/libnfnetlink.a +LIBMNL := $(DEPSDIR)/lib/libmnl.a +LIBNETFILTER_QUEUE := $(DEPSDIR)/lib/libnetfilter_queue.a + + +.PHONY: default all dev dev_attrs prepare_dirs +default: all + +run_dev: dev + bash -c "sudo $(APP) 537" + +dev: dev_attrs all + +dev_attrs: + $(eval CFLAGS := $(CFLAGS) -DDEBUG -ggdb -g3) + +all: prepare_dirs $(APP) + +prepare_dirs: + mkdir -p $(BUILD_DIR) + mkdir -p $(DEPSDIR) + +$(LIBNFNETLINK): + cd deps/libnfnetlink && ./autogen.sh && ./configure --prefix=$(DEPSDIR) $(if $(CROSS_COMPILE_PLATFORM),--host=$(CROSS_COMPILE_PLATFORM),) --enable-static --disable-shared + $(MAKE) -C deps/libnfnetlink + $(MAKE) install -C deps/libnfnetlink + +$(LIBMNL): + cd deps/libmnl && ./autogen.sh && ./configure --prefix=$(DEPSDIR) $(if $(CROSS_COMPILE_PLATFORM),--host=$(CROSS_COMPILE_PLATFORM),) --enable-static --disable-shared + $(MAKE) -C deps/libmnl + $(MAKE) install -C deps/libmnl + +$(LIBNETFILTER_QUEUE): $(LIBNFNETLINK) $(LIBMNL) + cd deps/libnetfilter_queue && ./autogen.sh && ./configure --prefix=$(DEPSDIR) $(if $(CROSS_COMPILE_PLATFORM),--host=$(CROSS_COMPILE_PLATFORM),) --enable-static --disable-shared + $(MAKE) -C deps/libnetfilter_queue + $(MAKE) install -C deps/libnetfilter_queue + +$(APP): $(OBJS) $(LIBNETFILTER_QUEUE) $(LIBMNL) + @echo 'CCLD $(APP)' + $(CCLD) $(OBJS) -o $(APP) $(LDFLAGS) -lmnl -lnetfilter_queue + +$(BUILD_DIR)/%.o: %.c $(LIBNETFILTER_QUEUE) $(LIBMNL) config.h + @echo 'CC $@' + $(CC) -c $(CFLAGS) $(LDFLAGS) $< -o $@ + +install: all + install -d $(PREFIX)/bin/ + install -m 755 $(APP) $(PREFIX)/bin/ + install -d $(PREFIX)/lib/systemd/system/ + @cp youtubeUnblock.service $(BUILD_DIR) + @sed -i 's/$$(PREFIX)/$(subst /,\/,$(PREFIX))/g' $(BUILD_DIR)/youtubeUnblock.service + install -m 644 $(BUILD_DIR)/youtubeUnblock.service $(PREFIX)/lib/systemd/system/ + +uninstall: + rm $(PREFIX)/bin/youtubeUnblock + rm $(PREFIX)/lib/systemd/system/youtubeUnblock.service + -systemctl disable youtubeUnblock.service + +clean: + rm -rf $(BUILD_DIR) + $(MAKE) distclean -C deps/libnetfilter_queue || true + $(MAKE) distclean -C deps/libmnl || true + $(MAKE) distclean -C deps/libnfnetlink || true + + diff --git a/youtubeUnblock.c b/youtubeUnblock.c index 67def6a..f550c53 100644 --- a/youtubeUnblock.c +++ b/youtubeUnblock.c @@ -1,10 +1,15 @@ #define _GNU_SOURCE +#ifndef __linux__ +#error "The package is linux only!" +#endif + +#ifdef KERNEL_SPACE +#error "The build aims to the kernel, not userspace" +#endif + #include #include -#include #include -#include -#include #include #include @@ -16,85 +21,211 @@ #include #include -#include #include -#include +#include #include +#include -#include "raw_replacements.h" +#include "config.h" +#include "mangle.h" -#ifndef NOUSE_GSO -#define USE_GSO +pthread_mutex_t rawsocket_lock; + +struct config_t config = { + .rawsocket = -2, + .threads = THREADS_NUM, + .fragmentation_strategy = FRAGMENTATION_STRATEGY, + .fake_sni_strategy = FAKE_SNI_STRATEGY, + .fake_sni_ttl = FAKE_SNI_TTL, + +#ifdef SEG2_DELAY + .seg2_delay = SEG2_DELAY, +#else + .seg2_delay = 0, #endif -#define FRAG_STRAT_TCP 0 -#define FRAG_STRAT_IP 1 -#define FRAG_STRAT_NONE 2 - -#define FRAGMENTATION_STRATEGY FRAG_STRAT_TCP - -#if FRAGMENTATION_STRATEGY == FRAG_STRAT_TCP - #define USE_TCP_SEGMENTATION -#elif FRAGMENTATION_STRATEGY == FRAG_STRAT_IP - #define USE_IP_FRAGMENTATION -#elif FRAGMENTATION_STRATEGY == FRAG_STRAT_NONE -#endif - -#define RAWSOCKET_MARK (1 << 15) - -#ifdef USE_SEG2_DELAY -#define SEG2_DELAY 100 +#ifdef USE_GSO + .use_gso = true, +#else + .use_gso = false, #endif -#ifndef NO_FAKE_SNI -#define FAKE_SNI +#ifdef DEBUG + .verbose = true, +#else + .verbose = false, #endif - -#ifndef SILENT -#define DEBUG -#endif - -static struct { - uint32_t queue_num; - struct mnl_socket *nl; - uint32_t portid; - int rawsocket; - -} config = { - .rawsocket = -2 }; +const char* get_value(const char *option, const char *prefix) +{ + errno = 0; + + size_t prefix_len = strlen(prefix); + size_t option_len = strlen(option); + + if (option_len <= prefix_len || strncmp(prefix, option, prefix_len)) { + return NULL; + } + + return option + prefix_len; +} + +int parse_bool_option(const char *value) { + errno = 0; + if (strcmp(value, "1") == 0) { + return 1; + } + else if (strcmp(value, "0") == 0) { + return 0; + } + + errno = EINVAL; + return -1; +} + +long parse_numeric_option(const char* value) { + errno = 0; + + char* end; + long result = strtol(value, &end, 10); + if (*end != '\0') { + errno = EINVAL; + return 0; + } + + return result; +} + +int parse_option(const char* option) { + const char* value; + int ret; + + if (!strcmp(option, "--no-gso")) { + config.use_gso = 0; + goto out; + } + + if (!strcmp(option, "--silent")) { + config.verbose = 0; + goto out; + } + + if ((value = get_value(option, "--frag=")) != 0) { + if (!value) { + goto err; + } + + if (strcmp(value, "tcp") == 0) { + config.fragmentation_strategy = FRAG_STRAT_TCP; + } else if (strcmp(value, "ip") == 0) { + config.fragmentation_strategy = FRAG_STRAT_IP; + } else if (strcmp(value, "none") == 0) { + config.fragmentation_strategy = FRAG_STRAT_NONE; + } else { + goto err; + } + + goto out; + } + + if ((value = get_value(option, "--fake-sni=")) != 0) { + if (strcmp(value, "ack") == 0) { + config.fake_sni_strategy = FKSN_STRAT_ACK_SEQ; + } else if (strcmp(value, "ttl") == 0) { + config.fake_sni_strategy = FKSN_STRAT_TTL; + } + else if (strcmp(value, "none") == 0) { + config.fake_sni_strategy = FKSN_STRAT_NONE; + } else { + goto err; + } + + goto out; + } + + if ((value = get_value(option, "--seg2delay=")) != 0) { + long num = parse_numeric_option(value); + if (errno != 0 || + num < 0) + goto err; + + config.seg2_delay = num; + goto out; + } + + if ((value = get_value(option, "--threads=")) != 0) { + long num = parse_numeric_option(value); + if (errno != 0 || + num < 0 || + num > MAX_THREADS) { + errno = EINVAL; + goto err; + } + + config.threads = num; + goto out; + } + + if ((value = get_value(option, "--fake-sni-ttl=")) != 0) { + long num = parse_numeric_option(value); + if (errno != 0 || + num < 0 || + num > 255) { + goto err; + } + + config.fake_sni_ttl = num; + goto out; + } + +err: + errno = EINVAL; + return -1; +out: + errno = 0; + return 0; +} + static int parse_args(int argc, const char *argv[]) { int err; char *end; - if (argc != 2) { + if (argc < 2) { errno = EINVAL; goto errormsg_help; } - uint32_t queue_num = strtoul(argv[1], &end, 10); - if (errno != 0 || *end != '\0') goto errormsg_help; + config.queue_start_num = parse_numeric_option(argv[1]); + if (errno != 0) goto errormsg_help; + + for (int i = 2; i < argc; i++) { + if (parse_option(argv[i])) { + printf("Invalid option %s\n", argv[i]); + goto errormsg_help; + } + } - config.queue_num = queue_num; return 0; errormsg_help: err = errno; - printf("Usage: %s [queue_num]\n", argv[0]); + printf("Usage: %s [OPTIONS]\n", argv[0]); + printf("Options:\n"); + printf("\t--fake-sni={ack,ttl,none}\n"); + printf("\t--fake-sni-ttl=\n"); + printf("\t--frag={tcp,ip,none}\n"); + printf("\t--seg2delay=\n"); + printf("\t--threads=\n"); + printf("\t--silent\n"); + printf("\t--no-gso\n"); errno = err; if (errno == 0) errno = EINVAL; return -1; } -static int open_socket(void) { - if (config.nl != NULL) { - errno = EALREADY; - perror("socket is already opened"); - return -1; - } - +static int open_socket(struct mnl_socket **_nl) { struct mnl_socket *nl = NULL; nl = mnl_socket_open(NETLINK_NETFILTER); @@ -109,21 +240,21 @@ static int open_socket(void) { return -1; } - config.nl = nl; - config.portid = mnl_socket_get_portid(nl); + *_nl = nl; return 0; } -static int close_socket(void) { - if (config.nl == NULL) return 1; - if (mnl_socket_close(config.nl) < 0) { +static int close_socket(struct mnl_socket **_nl) { + struct mnl_socket *nl = *_nl; + if (nl == NULL) return 1; + if (mnl_socket_close(nl) < 0) { perror("mnl_socket_close"); return -1; } - config.nl = NULL; + *_nl = NULL; return 0; } @@ -141,21 +272,23 @@ static int open_raw_socket(void) { return -1; } - int one = 1; - const int *val = &one; - if (setsockopt(config.rawsocket, IPPROTO_IP, IP_HDRINCL, val, sizeof(one)) < 0) - { - printf("setsockopt(IP_HDRINCL, 1) failed\n"); - return -1; - } - int mark = RAWSOCKET_MARK; if (setsockopt(config.rawsocket, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) { - printf("setsockopt(SO_MARK, %d) failed\n", mark); + fprintf(stderr, "setsockopt(SO_MARK, %d) failed\n", mark); return -1; } + int mst = pthread_mutex_init(&rawsocket_lock, NULL); + if (mst) { + fprintf(stderr, "Mutex err: %d\n", mst); + close(config.rawsocket); + errno = mst; + + return -1; + } + + return config.rawsocket; } @@ -168,284 +301,98 @@ static int close_raw_socket(void) { if (close(config.rawsocket)) { perror("Unable to close raw socket"); + pthread_mutex_destroy(&rawsocket_lock); return -1; } + pthread_mutex_destroy(&rawsocket_lock); + config.rawsocket = -2; return 0; } -// split packet to two ipv4 fragments. -static int ipv4_frag(struct pkt_buff *pktb, size_t payload_offset, - struct pkt_buff **frag1, struct pkt_buff **frag2) { - uint8_t buff1[MNL_SOCKET_BUFFER_SIZE]; - uint8_t buff2[MNL_SOCKET_BUFFER_SIZE]; - struct iphdr *hdr = nfq_ip_get_hdr(pktb); - if (hdr == NULL) { - errno = EINVAL; - return -1; - } +static int send_raw_socket(const uint8_t *pkt, uint32_t pktlen) { + int ret; - size_t hdr_len = hdr->ihl * 4; + if (pktlen > AVAILABLE_MTU) { + if (config.verbose) + printf("Split packet!\n"); - uint8_t *payload = pktb_data(pktb) + hdr_len; - size_t plen = pktb_len(pktb) - hdr_len; + uint8_t buff1[MNL_SOCKET_BUFFER_SIZE]; + uint32_t buff1_size = MNL_SOCKET_BUFFER_SIZE; + uint8_t buff2[MNL_SOCKET_BUFFER_SIZE]; + uint32_t buff2_size = MNL_SOCKET_BUFFER_SIZE; - if (payload == NULL || plen <= payload_offset) { - errno = EINVAL; - return -1; - } + switch (config.fragmentation_strategy) { + case FRAG_STRAT_TCP: + if ((ret = tcp4_frag(pkt, pktlen, AVAILABLE_MTU-128, + buff1, &buff1_size, buff2, &buff2_size)) < 0) { - if (payload_offset & ((1 << 3) - 1)) { - fprintf(stderr, "Payload offset MUST be a multiply of 8!\n"); - errno = EINVAL; - return -1; - } + 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) { - size_t f1_plen = payload_offset; - size_t f1_dlen = f1_plen + hdr_len; - - size_t f2_plen = plen - payload_offset; - size_t f2_dlen = f2_plen + hdr_len; - - memcpy(buff1, hdr, hdr_len); - memcpy(buff2, hdr, hdr_len); - - memcpy(buff1 + hdr_len, payload, f1_plen); - memcpy(buff2 + hdr_len, payload + payload_offset, f2_plen); - - struct iphdr *f1_hdr = (void *)buff1; - struct iphdr *f2_hdr = (void *)buff2; - - uint16_t f1_frag_off = ntohs(f1_hdr->frag_off); - uint16_t f2_frag_off = ntohs(f2_hdr->frag_off); - - f1_frag_off &= IP_OFFMASK; - f1_frag_off |= IP_MF; - - if ((f2_frag_off & ~IP_OFFMASK) == IP_MF) { - f2_frag_off &= IP_OFFMASK; - f2_frag_off |= IP_MF; - } else { - f2_frag_off &= IP_OFFMASK; - } - - f2_frag_off += (uint16_t)payload_offset / 8; - - f1_hdr->frag_off = htons(f1_frag_off); - f1_hdr->tot_len = htons(f1_dlen); - - f2_hdr->frag_off = htons(f2_frag_off); - f2_hdr->tot_len = htons(f2_dlen); - - -#ifdef DEBUG - printf("Packet split in portion %zu %zu\n", f1_dlen, f2_dlen); -#endif - - nfq_ip_set_checksum(f1_hdr); - nfq_ip_set_checksum(f2_hdr); - - *frag1 = pktb_alloc(AF_INET, buff1, f1_dlen, 0); - if (*frag1 == NULL) - return -1; - - *frag2 = pktb_alloc(AF_INET, buff2, f2_dlen, 0); - if (*frag2 == NULL) { - pktb_free(*frag1); - return -1; - } - - return 0; -} - -// split packet to two tcp-on-ipv4 segments. -static int tcp4_frag(struct pkt_buff *pktb, size_t payload_offset, - struct pkt_buff **seg1, struct pkt_buff **seg2) { - uint8_t buff1[MNL_SOCKET_BUFFER_SIZE]; - uint8_t buff2[MNL_SOCKET_BUFFER_SIZE]; - - struct iphdr *hdr = nfq_ip_get_hdr(pktb); - size_t hdr_len = hdr->ihl * 4; - if (hdr == NULL) { - errno = EINVAL; - perror("tcp4_frag: nfq_ip_get_hdr is null\n"); - errno = EINVAL; - return -1; - } - if (hdr->protocol != IPPROTO_TCP) { - errno = EINVAL; - perror("tcp4_frag: proto is not tcp"); - errno = EINVAL; - return -1; - } - if (!(ntohs(hdr->frag_off) & IP_DF)) { - errno = EINVAL; - perror("tcp4_frag: request is ip-fragmented"); - fprintf(stderr, "tcp4_frag: frag_off: %04x\n", ntohs(hdr->frag_off)); - errno = EINVAL; - return -1; - } - - - if (nfq_ip_set_transport_header(pktb, hdr)) { - perror("tcp4_frag: ip_set_transport_header"); - return -1; - } - - - struct tcphdr *tcph = nfq_tcp_get_hdr(pktb); - size_t tcph_len = tcph->doff * 4; - if (tcph == NULL) { - errno = EINVAL; - perror("tcp4_frag: tcph is NULL"); - errno = EINVAL; - return -1; - } - - uint8_t *payload = nfq_tcp_get_payload(tcph, pktb); - size_t plen = nfq_tcp_get_payload_len(tcph, pktb); - - if (payload == NULL || plen <= payload_offset) { - errno = EINVAL; - perror("tcp4_frag: payload is too small or NULL"); - errno = EINVAL; - return -1; - } - - size_t s1_plen = payload_offset; - size_t s1_dlen = s1_plen + hdr_len + tcph_len; - - size_t s2_plen = plen - payload_offset; - size_t s2_dlen = s2_plen + hdr_len + tcph_len; - if (s1_dlen > MNL_SOCKET_BUFFER_SIZE || s2_dlen > MNL_SOCKET_BUFFER_SIZE) { - errno = ENOMEM; - return -1; - } - - memcpy(buff1, hdr, hdr_len); - memcpy(buff2, hdr, hdr_len); - - memcpy(buff1 + hdr_len, tcph, tcph_len); - memcpy(buff2 + hdr_len, tcph, tcph_len); - - memcpy(buff1 + hdr_len + tcph_len, payload, s1_plen); - memcpy(buff2 + hdr_len + tcph_len, payload + payload_offset, s2_plen); - - struct iphdr *s1_hdr = (void *)buff1; - struct iphdr *s2_hdr = (void *)buff2; - - struct tcphdr *s1_tcph = (void *)(buff1 + hdr_len); - struct tcphdr *s2_tcph = (void *)(buff2 + hdr_len); - - s1_hdr->tot_len = htons(s1_dlen); - s2_hdr->tot_len = htons(s2_dlen); - - // s2_hdr->id = htons(ntohs(s1_hdr->id) + 1); - s2_tcph->seq = htonl(ntohl(s2_tcph->seq) + payload_offset); - // printf("%zu %du %du\n", payload_offset, ntohs(s1_tcph->seq), ntohs(s2_tcph->seq)); - -#ifdef DEBUG - printf("Packet split in portion %zu %zu\n", s1_dlen, s2_dlen); -#endif - - nfq_tcp_compute_checksum_ipv4(s1_tcph, s1_hdr); - nfq_tcp_compute_checksum_ipv4(s2_tcph, s2_hdr); - - *seg1 = pktb_alloc(AF_INET, buff1, s1_dlen, 0); - if (*seg1 == NULL) - return -1; - - *seg2 = pktb_alloc(AF_INET, buff2, s2_dlen, 0); - if (*seg2 == NULL) { - pktb_free(*seg1); - return -1; - } - - - return 0; -} - -#define AVAILABLE_MTU 1384 - -static int send_raw_socket(struct pkt_buff *pktb) { - if (pktb_len(pktb) > AVAILABLE_MTU) { -#ifdef DEBUG - printf("Split packet!\n"); -#endif - - struct pkt_buff *buff1; - struct pkt_buff *buff2; - -#if defined(USE_TCP_SEGMENTATION) || defined(RAWSOCK_TCP_FSTRAT) - if (tcp4_frag(pktb, AVAILABLE_MTU-128, &buff1, &buff2) < 0) - return -1; -#elif defined(USE_IP_FRAGMENTATION) || defined(RAWSOCK_IP_FSTRAT) - if (ipv4_frag(pktb, AVAILABLE_MTU-128, &buff1, &buff2) < 0) - return -1; -#else - errno = EINVAL; - printf("send_raw_socket: Packet is too big but fragmentation is disabled! " - "Pass -DRAWSOCK_TCP_FSTRAT or -DRAWSOCK_IP_FSTRAT as CFLAGS " - "To enable it only for raw socket\n"); - return -1; -#endif + errno = -ret; + return ret; + } + break; + default: + errno = EINVAL; + printf("send_raw_socket: Packet is too big but fragmentation is disabled!\n"); + return -EINVAL; + } int sent = 0; - int status = send_raw_socket(buff1); + int status = send_raw_socket(buff1, buff1_size); if (status >= 0) sent += status; else { - pktb_free(buff1); - pktb_free(buff2); return status; } - pktb_free(buff1); - status = send_raw_socket(buff2); + status = send_raw_socket(buff2, buff2_size); if (status >= 0) sent += status; else { - pktb_free(buff2); return status; } - pktb_free(buff2); return sent; } - struct iphdr *iph = nfq_ip_get_hdr(pktb); - if (iph == NULL) - return -1; - if(nfq_ip_set_transport_header(pktb, iph)) - return -1; + struct iphdr *iph; - int sin_port = 0; - - struct tcphdr *tcph = nfq_tcp_get_hdr(pktb); - struct udphdr *udph = nfq_udp_get_hdr(pktb); - - if (tcph != NULL) { - sin_port = tcph->dest; - errno = 0; - } else if (udph != NULL) { - sin_port = udph->dest; - } else { - return -1; + if ((ret = ip4_payload_split( + (uint8_t *)pkt, pktlen, &iph, NULL, NULL, NULL)) < 0) { + errno = -ret; + return ret; } struct sockaddr_in daddr = { .sin_family = AF_INET, - .sin_port = sin_port, + /* Always 0 for raw socket */ + .sin_port = 0, .sin_addr = { .s_addr = iph->daddr } }; + pthread_mutex_lock(&rawsocket_lock); + int sent = sendto(config.rawsocket, - pktb_data(pktb), pktb_len(pktb), 0, + pkt, pktlen, 0, (struct sockaddr *)&daddr, sizeof(daddr)); + + pthread_mutex_unlock(&rawsocket_lock); + + /* The function will return -errno on error as well as errno value set itself */ + if (sent < 0) sent = -errno; + return sent; } @@ -458,16 +405,22 @@ struct packet_data { uint16_t payload_len; }; +// Per-queue data. Passed to queue_cb. +struct queue_data { + struct mnl_socket **_nl; + int queue_num; +}; + /** * Used to accept unsupported packets (GSOs) */ -static int fallback_accept_packet(uint32_t id) { +static int fallback_accept_packet(uint32_t id, struct queue_data qdata) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *verdnlh; - verdnlh = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, config.queue_num); + verdnlh = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, qdata.queue_num); nfq_nlmsg_verdict_put(verdnlh, id, NF_ACCEPT); - if (mnl_socket_sendto(config.nl, verdnlh, verdnlh->nlmsg_len) < 0) { + if (mnl_socket_sendto(*qdata._nl, verdnlh, verdnlh->nlmsg_len) < 0) { perror("mnl_socket_send"); return MNL_CB_ERROR; } @@ -475,219 +428,33 @@ static int fallback_accept_packet(uint32_t id) { return MNL_CB_OK; } -#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 -const char googlevideo_ending[] = "googlevideo.com"; -const int googlevideo_len = 15; - -struct verdict { - int gvideo_hello; /* google video hello packet */ - int sni_offset; /* offset from start of tcp _payload_ */ - int sni_len; -}; - -/** - * Processes tls payload of the tcp request. - * - * data Payload data of TCP. - * dlen Length of `data`. - */ -static struct verdict analyze_tls_data( - const uint8_t *data, - uint32_t dlen) -{ - struct 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); - uint8_t tls_vminor = *(msgData + 2); - uint16_t message_length = ntohs(*(uint16_t *)(msgData + 3)); - const uint8_t *message_length_ptr = msgData + 3; - - - if (i + 5 + message_length > dlen) break; - - if (tls_content_type != TLS_CONTENT_TYPE_HANDSHAKE) - goto nextMessage; - - - 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; - const uint8_t *handshakeProto_length_ptr = 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); - const uint8_t *extensionsLen_ptr = msgPtr; - msgPtr += 2; - - const uint8_t *extensionsPtr = msgPtr; - const uint8_t *extensions_end = extensionsPtr + extensionsLen; - if (extensions_end > data_end) break; - - 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); - const uint8_t *extensionLen_ptr = 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); - - const uint8_t *sni_ext_dlen_ptr = 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; - uint8_t sni_type = *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; - // sni_len - - vrd.sni_offset = (uint8_t *)sni_name - data; - vrd.sni_len = sni_len; - - char *gv_startp = sni_name + sni_len - googlevideo_len; - if (sni_len >= googlevideo_len && - sni_len < 128 && - !strncmp(gv_startp, - googlevideo_ending, - googlevideo_len)) { - - vrd.gvideo_hello = 1; - } - -nextExtension: - extensionsPtr += 2 + 2 + extensionLen; - } -nextMessage: - i += 5 + message_length; - } - - return vrd; -} - -static struct pkt_buff *gen_fake_sni(const struct iphdr *iph, const struct tcphdr *tcph) { - uint8_t sniph_buf[60]; - int ip_len = iph->ihl * 4; - size_t pkt_size = ip_len + sizeof(fake_sni); - - memcpy(sniph_buf, iph, ip_len); - struct iphdr *sniph = (struct iphdr *)sniph_buf; - - sniph->protocol = IPPROTO_TCP; - sniph->tot_len = htons(pkt_size); - - struct pkt_buff *pkt = pktb_alloc(AF_INET, NULL, 0, pkt_size); - if (pkt == NULL) return NULL; - - pktb_mangle(pkt, 0, 0, 0, (char *)sniph_buf, ip_len); - pktb_mangle(pkt, ip_len, 0, 0, fake_sni, sizeof(fake_sni)); - - int ret = 0; - struct iphdr *niph = nfq_ip_get_hdr(pkt); - if (!niph) { - perror("gen_fake_sni: ip header is null"); - goto err; - } - - ret = nfq_ip_set_transport_header(pkt, niph); - if (ret < 0) { - perror("gen_fake_sni: set transport header"); - goto err; - } - - struct tcphdr *ntcph = nfq_tcp_get_hdr(pkt); - if (!ntcph) { - perror("gen_fake_sni: nfq_tcp_get_hdr"); - goto err; - } - - ntcph->th_dport = tcph->th_dport; - ntcph->th_sport = tcph->th_sport; - nfq_ip_set_checksum(niph); - nfq_tcp_compute_checksum_ipv4(ntcph, niph); - - return pkt; -err: - pktb_free(pkt); - return NULL; - -} struct dps_t { - struct pkt_buff *pkt; + uint8_t *pkt; + uint32_t pktlen; // Time for the packet in milliseconds uint32_t timer; }; // Note that the thread will automatically release dps_t and pkt_buff void *delay_packet_send(void *data) { struct dps_t *dpdt = data; - struct pkt_buff *pkt = dpdt->pkt; + + uint8_t *pkt = dpdt->pkt; + uint32_t pktlen = dpdt->pktlen; usleep(dpdt->timer * 1000); - send_raw_socket(pkt); + int ret = send_raw_socket(pkt, pktlen); + if (ret < 0) { + errno = -ret; + perror("send delayed raw packet"); + } - pktb_free(pkt); + free(pkt); free(dpdt); return NULL; } -static int process_packet(const struct packet_data packet) { +static int process_packet(const struct packet_data packet, struct queue_data qdata) { char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *verdnlh; @@ -697,179 +464,156 @@ static int process_packet(const struct packet_data packet) { #endif if (packet.hw_proto != ETH_P_IP) { - return fallback_accept_packet(packet.id); + return fallback_accept_packet(packet.id, qdata); } const int family = AF_INET; + const uint8_t *raw_payload = packet.payload; size_t raw_payload_len = packet.payload_len; - if (raw_payload == NULL) return MNL_CB_ERROR; + const struct iphdr *iph; + uint32_t iph_len; + const struct tcphdr *tcph; + uint32_t tcph_len; + const uint8_t *data; + uint32_t dlen; - const struct iphdr *ip_header = (const void *)raw_payload; + int ret = tcp4_payload_split((uint8_t *)raw_payload, raw_payload_len, + (struct iphdr **)&iph, &iph_len, (struct tcphdr **)&tcph, &tcph_len, + (uint8_t **)&data, &dlen); - if (ip_header->version != IPPROTO_IPIP || ip_header->protocol != IPPROTO_TCP) - goto fallback; - - int iph_len = ip_header->ihl * 4; - - const struct tcphdr *tcph = (const void *)(raw_payload + iph_len); - if ((const uint8_t *)tcph + 20 > raw_payload + raw_payload_len) { - printf("LZ\n"); + if (ret < 0) { goto fallback; } - int tcph_len = tcph->doff * 4; - if ((const uint8_t *)tcph + tcph_len > raw_payload + raw_payload_len) { - printf("LZ\n"); - goto fallback; - } - int data_len = ntohs(ip_header->tot_len) - iph_len - tcph_len; - const uint8_t *data = (const uint8_t *)(raw_payload + iph_len + tcph_len); + struct verdict vrd = analyze_tls_data(data, dlen); - struct verdict vrd = analyze_tls_data(data, data_len); - - verdnlh = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, config.queue_num); + verdnlh = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, qdata.queue_num); nfq_nlmsg_verdict_put(verdnlh, packet.id, NF_ACCEPT); if (vrd.gvideo_hello) { -#ifdef DEBUG - printf("Google video!\n"); -#endif + if (config.verbose) + printf("Google video!\n"); - if (data_len > 1480) { -#ifdef DEBUG - fprintf(stderr, "WARNING! Google video packet is too big and may cause issues!\n"); -#endif + if (dlen > 1480) { + if (config.verbose) + fprintf(stderr, "WARNING! Google video packet is too big and may cause issues!\n"); } - struct pkt_buff *frag1; - struct pkt_buff *frag2; + uint8_t frag1[MNL_SOCKET_BUFFER_SIZE]; + uint8_t frag2[MNL_SOCKET_BUFFER_SIZE]; + uint32_t f1len = MNL_SOCKET_BUFFER_SIZE; + uint32_t f2len = MNL_SOCKET_BUFFER_SIZE; nfq_nlmsg_verdict_put(verdnlh, packet.id, NF_DROP); int ret = 0; + nfq_ip_set_checksum((struct iphdr *)iph); + nfq_tcp_compute_checksum_ipv4( + (struct tcphdr *)tcph, (struct iphdr *)iph); -#ifdef FAKE_SNI - struct pkt_buff *fake_sni = gen_fake_sni(ip_header, tcph); - if (fake_sni == NULL) { - perror("gen_fake_sni"); - goto fallback; + if (config.fake_sni_strategy != FKSN_STRAT_NONE) { + uint8_t fake_sni[MNL_SOCKET_BUFFER_SIZE]; + uint32_t fsn_len = MNL_SOCKET_BUFFER_SIZE; + + ret = gen_fake_sni(iph, tcph, fake_sni, &fsn_len); + if (ret < 0) { + errno = -ret; + perror("gen_fake_sni"); + goto fallback; + } + + ret = send_raw_socket(fake_sni, fsn_len); + if (ret < 0) { + errno = -ret; + perror("send fake sni"); + goto fallback; + } } - ret = send_raw_socket(fake_sni); - if (ret < 0) { - perror("send fake sni"); - pktb_free(fake_sni); - goto fallback; - } -#endif - struct pkt_buff *pktb = pktb_alloc( - family, - packet.payload, - packet.payload_len, - 0); - - - - if (pktb == NULL) { - perror("pktb_alloc of payload"); -#ifdef FAKE_SNI - pktb_free(fake_sni); -#endif - goto fallback; - } - - - struct iphdr *piph = nfq_ip_get_hdr(pktb); - ret = nfq_ip_set_transport_header(pktb, piph); - struct tcphdr *ptcph = nfq_tcp_get_hdr(pktb); - if (!piph || ret < 0 || !ptcph) { - perror("cannot parse pktb"); - goto gv_free; - } - - nfq_ip_set_checksum(piph); - nfq_tcp_compute_checksum_ipv4(ptcph, piph); - -#if defined(USE_TCP_SEGMENTATION) - size_t ipd_offset = vrd.sni_offset; - size_t mid_offset = ipd_offset + vrd.sni_len / 2; - - - if (tcp4_frag(pktb, mid_offset, &frag1, &frag2) < 0) { - perror("tcp4_frag"); - - goto gv_free; - } - -#elif defined(USE_IP_FRAGMENTATION) - size_t ipd_offset = ((char *)data - (char *)tcph) + vrd.sni_offset; - size_t mid_offset = ipd_offset + vrd.sni_len / 2; - mid_offset += 8 - mid_offset % 8; - - if (ipv4_frag(pktb, mid_offset, &frag1, &frag2) < 0) { - perror("ipv4_frag"); - - goto gv_free; - } - -#else - ret = send_raw_socket(pktb); - if (ret < 0) { - perror("raw pack send"); - } - goto gv_free; -#endif - - ret = send_raw_socket(frag2); + size_t ipd_offset; + size_t mid_offset; + + switch (config.fragmentation_strategy) { + case FRAG_STRAT_TCP: + ipd_offset = vrd.sni_offset; + mid_offset = ipd_offset + vrd.sni_len / 2; + + if ((ret = tcp4_frag(raw_payload, raw_payload_len, + mid_offset, frag1, &f1len, frag2, &f2len)) < 0) { + + errno = -ret; + perror("tcp4_frag"); + goto fallback; + } + + break; + case FRAG_STRAT_IP: + ipd_offset = ((char *)data - (char *)tcph) + vrd.sni_offset; + mid_offset = ipd_offset + vrd.sni_len / 2; + mid_offset += 8 - mid_offset % 8; + + if ((ret = ip4_frag(raw_payload, raw_payload_len, + mid_offset, frag1, &f1len, frag2, &f2len)) < 0) { + + errno = -ret; + perror("ip4_frag"); + goto fallback; + } + + break; + default: + ret = send_raw_socket(raw_payload, raw_payload_len); + if (ret < 0) { + errno = -ret; + perror("raw pack send"); + } + goto fallback; + } + + ret = send_raw_socket(frag2, f2len); if (ret < 0) { + errno = -ret; perror("raw frags send: frag2"); - pktb_free(frag1); - goto free_frags; - } - -#ifdef SEG2_DELAY - struct dps_t *dpdt = malloc(sizeof(struct dps_t)); - dpdt->pkt = frag1; - dpdt->timer = SEG2_DELAY; - pthread_t thr; - pthread_create(&thr, NULL, delay_packet_send, dpdt); - pthread_detach(thr); -#else - ret = send_raw_socket(frag1); - if (ret < 0) { - perror("raw frags send: frag1"); - pktb_free(frag1); - - goto free_frags; + goto fallback; } - pktb_free(frag1); -#endif + if (config.seg2_delay) { + struct dps_t *dpdt = malloc(sizeof(struct dps_t)); + dpdt->pkt = malloc(f1len); + memcpy(dpdt->pkt, frag1, f1len); + dpdt->pktlen = f1len; + dpdt->timer = config.seg2_delay; + pthread_t thr; + pthread_create(&thr, NULL, delay_packet_send, dpdt); + pthread_detach(thr); + } else { + ret = send_raw_socket(frag1, f1len); -free_frags: - pktb_free(frag2); -gv_free: - pktb_free(pktb); -#ifdef FAKE_SNI - pktb_free(fake_sni); -#endif + if (ret < 0) { + errno = -ret; + perror("raw frags send: frag1"); + + goto fallback; + } + } } /* if (pktb_mangled(pktb)) { -#ifdef DEBUG - printf("Mangled!\n"); -#endif + if (config.versose) + printf("Mangled!\n"); + nfq_nlmsg_verdict_put_pkt( verdnlh, pktb_data(pktb), pktb_len(pktb)); } */ - if (mnl_socket_sendto(config.nl, verdnlh, verdnlh->nlmsg_len) < 0) { +send_verd: + if (mnl_socket_sendto(*qdata._nl, verdnlh, verdnlh->nlmsg_len) < 0) { perror("mnl_socket_send"); goto error; @@ -878,12 +622,15 @@ gv_free: return MNL_CB_OK; fallback: - return fallback_accept_packet(packet.id); + return fallback_accept_packet(packet.id, qdata); error: return MNL_CB_ERROR; } static int queue_cb(const struct nlmsghdr *nlh, void *data) { + + struct queue_data *qdata = data; + struct nfqnl_msg_packet_hdr *ph = NULL; struct nlattr *attr[NFQA_MAX+1] = {0}; struct packet_data packet = {0}; @@ -909,123 +656,199 @@ static int queue_cb(const struct nlmsghdr *nlh, void *data) { if (attr[NFQA_CAP_LEN] != NULL && ntohl(mnl_attr_get_u32(attr[NFQA_CAP_LEN])) != packet.payload_len) { fprintf(stderr, "The packet was truncated! Skip!\n"); - return fallback_accept_packet(packet.id); + return fallback_accept_packet(packet.id, *qdata); } if (attr[NFQA_MARK] != NULL) { // Skip packets sent by rawsocket to escape infinity loop. if ((ntohl(mnl_attr_get_u32(attr[NFQA_MARK])) & RAWSOCKET_MARK) == RAWSOCKET_MARK) { - return fallback_accept_packet(packet.id); + return fallback_accept_packet(packet.id, *qdata); } } - return process_packet(packet); + return process_packet(packet, *qdata); } -int main(int argc, const char *argv[]) -{ +#define BUF_SIZE (0xffff + (MNL_SOCKET_BUFFER_SIZE / 2)) - if (parse_args(argc, argv)) { - perror("Unable to parse args"); - exit(EXIT_FAILURE); - } +int init_queue(int queue_num) { + struct mnl_socket *nl; -#if defined(USE_TCP_SEGMENTATION) - printf("Using TCP segmentation\n"); -#elif defined(USE_IP_FRAGMENTATION) - printf("Using IP fragmentation\n"); -#else - printf("SNI fragmentation is disabled\n"); -#endif - -#ifdef SEG2_DELAY - printf("Some outgoing googlevideo request segments will be delayed for %d ms as of SEG2_DELAY define\n", SEG2_DELAY); -#endif - -#ifdef FAKE_SNI - printf("Fake SNI will be sent before each googlevideo request\n"); -#endif - - if (open_socket()) { + if (open_socket(&nl)) { perror("Unable to open socket"); - exit(EXIT_FAILURE); + return -1; } - if (open_raw_socket() < 0) { - perror("Unable to open raw socket"); - close_socket(); - exit(EXIT_FAILURE); - } + uint32_t portid = mnl_socket_get_portid(nl); struct nlmsghdr *nlh; - char *buf; - size_t buf_size = 0xffff + (MNL_SOCKET_BUFFER_SIZE / 2); - buf = malloc(buf_size); - if (!buf) { - perror("Allocate recieve buffer"); - goto die_sock; - } + char buf[BUF_SIZE]; - - nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, config.queue_num); + nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, queue_num); nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_BIND); - if (mnl_socket_sendto(config.nl, nlh, nlh->nlmsg_len) < 0) { + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { perror("mnl_socket_send"); - goto die_buf; + goto die; } - nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, config.queue_num); + nlh = nfq_nlmsg_put(buf, NFQNL_MSG_CONFIG, queue_num); nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff); -#ifdef USE_GSO - printf("GSO is enabled\n"); - - mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO)); - mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO)); -#endif - - if (mnl_socket_sendto(config.nl, nlh, nlh->nlmsg_len) < 0) { - perror("mnl_socket_send"); - goto die_buf; + if (config.use_gso) { + mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO)); + mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO)); } + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + perror("mnl_socket_send"); + goto die; + } + + /* ENOBUFS is signalled to userspace when packets were lost * on kernel side. In most cases, userspace isn't interested * in this information, so turn it off. */ int ret = 1; - mnl_socket_setsockopt(config.nl, NETLINK_NO_ENOBUFS, &ret, sizeof(int)); + mnl_socket_setsockopt(nl, NETLINK_NO_ENOBUFS, &ret, sizeof(int)); + + struct queue_data qdata = { + ._nl = &nl, + .queue_num = queue_num + }; + + printf("Queue %d started!\n", qdata.queue_num); while (1) { - ret = mnl_socket_recvfrom(config.nl, buf, buf_size); + ret = mnl_socket_recvfrom(nl, buf, BUF_SIZE); if (ret == -1) { perror("mnl_socket_recvfrom"); - goto die_buf; + continue; } - ret = mnl_cb_run(buf, ret, 0, config.portid, queue_cb, NULL); + ret = mnl_cb_run(buf, ret, 0, portid, queue_cb, &qdata); if (ret < 0) { perror("mnl_cb_run"); - // goto die_buf; } } - printf("%d\n", config.queue_num); - errno = 0; - - free(buf); - close_socket(); + close_socket(&nl); return 0; -die_buf: - free(buf); -die_sock: - close_raw_socket(); - close_socket(); - exit(EXIT_FAILURE); +die: + close_socket(&nl); + return -1; +} + +// Per-queue config. Used to initialize a queue. Passed to wrapper +struct queue_conf { + uint16_t i; + int queue_num; +}; + +struct queue_res { + int status; +}; +static struct queue_res defqres = {0}; + +static struct queue_res threads_reses[MAX_THREADS]; + +void *init_queue_wrapper(void *qdconf) { + struct queue_conf *qconf = qdconf; + struct queue_res *thres = threads_reses + qconf->i; + + thres->status = init_queue(qconf->queue_num); + + fprintf(stderr, "Thread %d exited with status %d\n", qconf->i, thres->status); + + return thres; +} + +int main(int argc, const char *argv[]) { + if (parse_args(argc, argv)) { + perror("Unable to parse args"); + exit(EXIT_FAILURE); + } + + 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); + } + + switch (config.fake_sni_strategy) { + case FKSN_STRAT_TTL: + printf("Fake SNI will be sent before each googlevideo request, TTL strategy will be used with TTL %d\n", config.fake_sni_ttl); + break; + case FRAG_STRAT_IP: + printf("Fake SNI will be sent before each googlevideo request, Ack-Seq strategy will be used\n"); + break; + default: + printf("SNI fragmentation is disabled\n"); + break; + } + + if (config.use_gso) { + printf("GSO is enabled\n"); + } + + if (open_raw_socket() < 0) { + perror("Unable to open raw socket"); + exit(EXIT_FAILURE); + } + + struct queue_res *qres = &defqres; + + if (config.threads == 1) { + struct queue_conf tconf = { + .i = 0, + .queue_num = config.queue_start_num + }; + + qres = init_queue_wrapper(&tconf); + } + else { + printf("%d threads wil be used\n", config.threads); + + struct queue_conf thread_confs[MAX_THREADS]; + pthread_t threads[MAX_THREADS]; + for (int i = 0; i < config.threads; i++) { + struct queue_conf *tconf = thread_confs + i; + pthread_t *thr = threads + i; + + tconf->queue_num = config.queue_start_num + i; + tconf->i = i; + + pthread_create(thr, NULL, init_queue_wrapper, tconf); + } + + void *res; + for (int i = 0; i < config.threads; i++) { + pthread_join(threads[i], &res); + + qres = res; + } + } + + if (close_raw_socket() < 0) { + perror("Unable to close raw socket"); + exit(EXIT_FAILURE); + } + + return qres->status; }