From 4f78c490f484bf8dfd335e6a9e30c2d940e4a964 Mon Sep 17 00:00:00 2001 From: Vadim Vetrov Date: Thu, 1 Aug 2024 12:35:03 +0300 Subject: [PATCH 1/3] Add option to specify 2nd packet delay --- youtubeUnblock.c | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/youtubeUnblock.c b/youtubeUnblock.c index 5c258fa..734f556 100644 --- a/youtubeUnblock.c +++ b/youtubeUnblock.c @@ -19,6 +19,7 @@ #include #include #include +#include #ifndef NOUSE_GSO #define USE_GSO @@ -29,6 +30,7 @@ #endif #define RAWSOCKET_MARK 0xfc70 +#define SEG2_DELAY 100 static struct { uint32_t queue_num; @@ -564,7 +566,23 @@ nextMessage: return vrd; } +struct dps_t { + struct pkt_buff *pkt; + // 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; + usleep(dpdt->timer * 1000); + send_raw_socket(pkt); + + pktb_free(pkt); + free(dpdt); + return NULL; +} static int process_packet(const struct packet_data packet) { char buf[MNL_SOCKET_BUFFER_SIZE]; @@ -644,11 +662,31 @@ static int process_packet(const struct packet_data packet) { goto fallback; } - if ((send_raw_socket(frag2) == -1) || (send_raw_socket(frag1) == -1)) { + int ret = send_raw_socket(frag2); + if (ret < 0) { + errno = ret; perror("raw frags send"); + pktb_free(frag1); + goto err; } - pktb_free(frag1); + +#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) { + free(frag1); + goto err; + } + free(frag1); +#endif +err: pktb_free(frag2); pktb_free(pktb); From 57c5b1f6aa1eb21ff6594cc41a1c89d6bf284b58 Mon Sep 17 00:00:00 2001 From: Vadim Vetrov Date: Thu, 1 Aug 2024 18:45:10 +0300 Subject: [PATCH 2/3] Implement fake SNI --- raw_replacements.h | 6 +++++ youtubeUnblock.c | 61 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 raw_replacements.h diff --git a/raw_replacements.h b/raw_replacements.h new file mode 100644 index 0000000..c6f86b2 --- /dev/null +++ b/raw_replacements.h @@ -0,0 +1,6 @@ +#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"; + +#endif /*RAW_REPLACEMENTS_H*/ diff --git a/youtubeUnblock.c b/youtubeUnblock.c index 734f556..35c7272 100644 --- a/youtubeUnblock.c +++ b/youtubeUnblock.c @@ -21,6 +21,8 @@ #include #include +#include "raw_replacements.h" + #ifndef NOUSE_GSO #define USE_GSO #endif @@ -30,7 +32,15 @@ #endif #define RAWSOCKET_MARK 0xfc70 + +#ifndef NO_SEG2_DELAY #define SEG2_DELAY 100 +#endif + +#ifndef NO_FAKE_SNI +#define FAKE_SNI +#endif + static struct { uint32_t queue_num; @@ -566,6 +576,40 @@ nextMessage: return vrd; } +static struct pkt_buff *gen_fake_sni(const struct iphdr *iph, const struct tcphdr *tcph) { + int ip_len = iph->ihl * 4; + int tcp_len = tcph->doff * 4; + + size_t pkt_size = ip_len + sizeof(fake_sni); + struct pkt_buff *pkt = pktb_alloc(AF_INET, NULL, 0, pkt_size); + if (pkt == NULL) return NULL; + + pktb_mangle(pkt, 0, 0, 0, (const char *)iph, 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) goto err; + + ret = nfq_ip_set_transport_header(pkt, niph); + if (ret < 0) goto err; + + struct tcphdr *ntcph = nfq_tcp_get_hdr(pkt); + if (!ntcph) goto err; + + niph->tot_len = htons(pkt_size); + 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: + fprintf(stderr, "Error in gen fake sni\n"); + pktb_free(pkt); + return NULL; + +} struct dps_t { struct pkt_buff *pkt; // Time for the packet in milliseconds @@ -647,6 +691,19 @@ static int process_packet(const struct packet_data packet) { #ifdef USE_TCP_SEGMENTATION + struct pkt_buff *fake_sni = gen_fake_sni(ip_header, tcph); + if (fake_sni == NULL) goto fallback; + + int ret = 0; +#ifdef FAKE_SNI + ret = send_raw_socket(fake_sni); +#endif + if (ret < 0) { + perror("send fake sni\n"); + pktb_free(fake_sni); + goto fallback; + } + size_t ipd_offset = vrd.sni_offset; size_t mid_offset = ipd_offset + vrd.sni_len / 2; @@ -659,10 +716,11 @@ static int process_packet(const struct packet_data packet) { if (tcp4_frag(pktb, mid_offset, &frag1, &frag2) < 0) { perror("tcp4_frag"); pktb_free(pktb); + pktb_free(fake_sni); goto fallback; } - int ret = send_raw_socket(frag2); + ret = send_raw_socket(frag2); if (ret < 0) { errno = ret; perror("raw frags send"); @@ -689,6 +747,7 @@ static int process_packet(const struct packet_data packet) { err: pktb_free(frag2); pktb_free(pktb); + pktb_free(fake_sni); #else // TODO: Implement compute of tcp checksum From 8808c49fbdac5c7f8e1340dc07969541d2e3e3c1 Mon Sep 17 00:00:00 2001 From: Vadim Vetrov Date: Thu, 1 Aug 2024 20:54:02 +0300 Subject: [PATCH 3/3] Cleanup code, update readme, fix gen_fake_sni --- README.md | 11 +++++++---- youtubeUnblock.c | 51 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 69a50df..fd094a9 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,13 @@ Systemd daemon is also available. Do `systemctl enable --now youtubeUnblock.serv 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. +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 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. +- -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. + ## OpenWRT case The package is also compatible with routers. The router should be running by free opensource linux-based system such as [OpenWRT](https://openwrt.org/). You should cross-compile it under your host machine. Be ready for compilation errors and a lot of googling about it. It is not such a trivial process! You can get crosscompilation toolsuite compatible with your router from OpenWRT repositories. For example, I have ramips/mt76x8 based router so for me the toolsuite is on https://downloads.openwrt.org/releases/23.05.3/targets/ramips/mt76x8/ and called `openwrt-toolchain-23.05.3-ramips-mt76x8_gcc-12.3.0_musl.Linux-x86_64.tar.xz`. You can find out more about your router model on it's openwrt page. When you download the toolsuite, untar it somewhere. Now we are ready for compilation. My cross gcc asked me to create a staging dir for it and pass it as an environment variable. Also you should notice toolsuite packages and replace my make command with yours. ```STAGING_DIR=temp make CC=/usr/bin/mipsel-openwrt-linux-gcc LD=/usr/bin/mipsel-openwrt-linux-gcc AR=/usr/bin/mipsel-openwrt-linux-ar OBJDUMP=/usr/bin/mipsel-openwrt-linux-objdump NM=/usr/bin/mipsel-openwrt-linux-nm STRIP=/usr/bin/mipsel-openwrt-linux-strip CROSS_COMPILE_PLATFORM=mipsel-buildroot-linux-gnu```. Take a look at `CROSS_COMPILE_PLATFORM` It is required by autotools but I think it is not necessary. Anyways I put `mipsel-buildroot-linux-gnu` in here. For your model may be an [automake cross-compile manual](https://www.gnu.org/software/automake/manual/html_node/Cross_002dCompilation.html) will be helpful. When compilation is done, the binary file will be in build directory. Copy it to your router. Note that an ssh access is likely to be required to proceed. sshfs don't work on my model so I injected the application to the router via Software Upload Package page. It has given me an error, but also a `/tmp/upload.ipk` file which I copied in root directory, `chmod +x`-ed and run. @@ -37,8 +44,4 @@ Now let's talk about a router configuration. I installed a normal iptables user- Next step is to daemonize the application in openwrt. Copy youtubeUnblock.owrt to /etc/init.d/youtubeUnblock and put the program into /usr/bin/. (Don't forget to `chmod +x` both). Now run `/etc/init.d/youtubeUnblock start`. You can alo run `/etc/init.d/youtubeUnblock enable` to force OpenWRT autostart the program on boot. - -## Further development -Please note that the application needs in further development. Some googlevideo servers may still be unabailable, some may drop out hello packets on Firefox while some may do so on Chrome. If you got in trouble try to disable GSO (Pass -DNOUSE_GSO as CC_FLAGS). Also you may set the program to use IP fragmentation instead of TCP (-DUSE_IP_FRAGMENTATION). - **If you have any questions/suggestions/problems feel free to open an issue.** diff --git a/youtubeUnblock.c b/youtubeUnblock.c index 35c7272..d899c07 100644 --- a/youtubeUnblock.c +++ b/youtubeUnblock.c @@ -33,7 +33,7 @@ #define RAWSOCKET_MARK 0xfc70 -#ifndef NO_SEG2_DELAY +#ifdef USE_SEG2_DELAY #define SEG2_DELAY 100 #endif @@ -589,15 +589,26 @@ static struct pkt_buff *gen_fake_sni(const struct iphdr *iph, const struct tcphd int ret = 0; struct iphdr *niph = nfq_ip_get_hdr(pkt); - if (!niph) goto err; + if (!niph) { + perror("gen_fake_sni: ip header is null"); + goto err; + } + + niph->protocol = IPPROTO_TCP; + niph->tot_len = htons(pkt_size); ret = nfq_ip_set_transport_header(pkt, niph); - if (ret < 0) goto err; + if (ret < 0) { + perror("gen_fake_sni: set transport header"); + goto err; + } struct tcphdr *ntcph = nfq_tcp_get_hdr(pkt); - if (!ntcph) goto err; + if (!ntcph) { + perror("gen_fake_sni: nfq_tcp_get_hdr"); + goto err; + } - niph->tot_len = htons(pkt_size); ntcph->th_dport = tcph->th_dport; ntcph->th_sport = tcph->th_sport; nfq_ip_set_checksum(niph); @@ -605,7 +616,6 @@ static struct pkt_buff *gen_fake_sni(const struct iphdr *iph, const struct tcphd return pkt; err: - fprintf(stderr, "Error in gen fake sni\n"); pktb_free(pkt); return NULL; @@ -712,6 +722,12 @@ static int process_packet(const struct packet_data packet) { packet.payload, packet.payload_len, 0); + + if (pktb == NULL) { + perror("pktb_alloc of payload"); + pktb_free(fake_sni); + goto fallback; + } if (tcp4_frag(pktb, mid_offset, &frag1, &frag2) < 0) { perror("tcp4_frag"); @@ -723,11 +739,10 @@ static int process_packet(const struct packet_data packet) { ret = send_raw_socket(frag2); if (ret < 0) { errno = ret; - perror("raw frags send"); + perror("raw frags send: frag2"); pktb_free(frag1); goto err; } - #ifdef SEG2_DELAY struct dps_t *dpdt = malloc(sizeof(struct dps_t)); @@ -739,10 +754,12 @@ static int process_packet(const struct packet_data packet) { #else ret = send_raw_socket(frag1); if (ret < 0) { - free(frag1); + errno = ret; + perror("raw frags send: frag1"); + pktb_free(frag1); goto err; } - free(frag1); + pktb_free(frag1); #endif err: pktb_free(frag2); @@ -851,9 +868,17 @@ int main(int argc, const char *argv[]) } #ifdef USE_TCP_SEGMENTATION - printf("Using TCP segmentation!\n"); + printf("Using TCP segmentation\n"); #else - printf("Using IP fragmentation!\n"); + printf("Using IP fragmentation\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()) { @@ -889,7 +914,7 @@ int main(int argc, const char *argv[]) nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff); #ifdef USE_GSO - printf("GSO is enabled!\n"); + 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));