From 21452bd4ad77e9bf87c061b3f316cb7307113def Mon Sep 17 00:00:00 2001 From: bolvan Date: Wed, 17 Feb 2016 17:54:03 +0300 Subject: [PATCH] ipv6 support for nfqws --- changes.txt | 4 ++ nfq/nfqws.c | 172 ++++++++++++++++++++++++++++++++++++++++++++-------- readme.txt | 30 ++++++--- 3 files changed, 171 insertions(+), 35 deletions(-) diff --git a/changes.txt b/changes.txt index 1fd9e44..4062d4f 100644 --- a/changes.txt +++ b/changes.txt @@ -26,3 +26,7 @@ v4 tpws : added ability to insert extra space after http method : "GET /" => "GET /" ISP support : TKT support + +v5 + +nfqws : ipv6 nfqws diff --git a/nfq/nfqws.c b/nfq/nfqws.c index 1f3bbd5..6d2b7c9 100644 --- a/nfq/nfqws.c +++ b/nfq/nfqws.c @@ -3,9 +3,10 @@ #include #include #include -#include +#include +#include #include -#include +//#include #include #include /* for NF_ACCEPT */ #include @@ -15,19 +16,21 @@ bool proto_check_ipv4(unsigned char *data,int len) { - return len && (data[0] & 0xF0)==0x40 && + return len>=20 && (data[0] & 0xF0)==0x40 && len>=((data[0] & 0x0F)<<2); } +// move to transport protocol void proto_skip_ipv4(unsigned char **data,int *len) { int l; + l = (**data & 0x0F)<<2; *data += l; *len -= l; } bool proto_check_tcp(unsigned char *data,int len) { - return len>=((data[12] & 0xF0)>>2); + return len>=20 && len>=((data[12] & 0xF0)>>2); } void proto_skip_tcp(unsigned char **data,int *len) { @@ -37,6 +40,51 @@ void proto_skip_tcp(unsigned char **data,int *len) *len -= l; } +bool proto_check_ipv6(unsigned char *data,int len) +{ + return len>=40 && (data[0] & 0xF0)==0x60 && + (len-40)>=htons(*(uint16_t*)(data+4)); // payload length +} +// move to transport protocol +// proto_type = 0 => error +void proto_skip_ipv6(unsigned char **data,int *len,uint8_t *proto_type) +{ + int hdrlen; + uint8_t HeaderType; + + *proto_type = 0; // put error in advance + + HeaderType = (*data)[6]; // NextHeader field + *data += 40; *len -= 40; // skip ipv6 base header + while(*len>0) // need at least one byte for NextHeader field + { + switch(HeaderType) + { + case 0: // Hop-by-Hop Options + case 60: // Destination Options + case 43: // routing + if (*len<2) return; // error + hdrlen = 8+((*data)[1]<<3); + break; + case 44: // fragment + hdrlen = 4; + break; + case 59: // no next header + return; // error + default: + // we found some meaningful payload. it can be tcp, udp, icmp or some another exotic shit + *proto_type = HeaderType; + return; + } + if (*len=blk_len) @@ -60,10 +108,10 @@ static inline bool tcp_synack_segment( const struct tcphdr *tcphdr ) tcphdr->fin == 0; } -uint16_t checksum(const void *buff, int len, in_addr_t src_addr, in_addr_t dest_addr) +uint16_t tcp_checksum(const void *buff, int len, in_addr_t src_addr, in_addr_t dest_addr) { const uint16_t *buf=buff; - uint16_t *ip_src=(void *)&src_addr, *ip_dst=(void *)&dest_addr; + uint16_t *ip_src=(uint16_t *)&src_addr, *ip_dst=(uint16_t *)&dest_addr; uint32_t sum; int length=len; @@ -79,6 +127,7 @@ uint16_t checksum(const void *buff, int len, in_addr_t src_addr, in_addr_t dest_ if ( len & 1 ) // Add the padding if the packet lenght is odd sum += *((uint8_t *)buf); + // Add the pseudo-header sum += *(ip_src++); sum += *ip_src; @@ -86,6 +135,7 @@ uint16_t checksum(const void *buff, int len, in_addr_t src_addr, in_addr_t dest_ sum += *ip_dst; sum += htons(IPPROTO_TCP); sum += htons(length); + // Add the carries while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); @@ -96,7 +146,59 @@ uint16_t checksum(const void *buff, int len, in_addr_t src_addr, in_addr_t dest_ void tcp_fix_checksum(struct tcphdr *tcp,int len, in_addr_t src_addr, in_addr_t dest_addr) { tcp->check = 0; - tcp->check = checksum(tcp,len,src_addr,dest_addr); + tcp->check = tcp_checksum(tcp,len,src_addr,dest_addr); +} +uint16_t tcp6_checksum(const void *buff, int len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr) +{ + const uint16_t *buf=buff; + const uint16_t *ip_src=(uint16_t *)src_addr, *ip_dst=(uint16_t *)dest_addr; + uint32_t sum; + int length=len; + + // Calculate the sum + sum = 0; + while (len > 1) + { + sum += *buf++; + if (sum & 0x80000000) + sum = (sum & 0xFFFF) + (sum >> 16); + len -= 2; + } + if ( len & 1 ) + // Add the padding if the packet lenght is odd + sum += *((uint8_t *)buf); + + // Add the pseudo-header + sum += *(ip_src++); + sum += *(ip_src++); + sum += *(ip_src++); + sum += *(ip_src++); + sum += *(ip_src++); + sum += *(ip_src++); + sum += *(ip_src++); + sum += *ip_src; + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *(ip_dst++); + sum += *ip_dst; + sum += htons(IPPROTO_TCP); + sum += htons(length); + + // Add the carries + while (sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + // Return the one's complement of sum + return (uint16_t)(~sum); +} +void tcp6_fix_checksum(struct tcphdr *tcp,int len, const struct in6_addr *src_addr, const struct in6_addr *dest_addr) +{ + tcp->check = 0; + tcp->check = tcp6_checksum(tcp,len,src_addr,dest_addr); } void tcp_rewrite_winsize(struct tcphdr *tcp,uint16_t winsize) @@ -144,33 +246,53 @@ struct cbdata_s bool processPacketData(unsigned char *data,int len,const struct cbdata_s *cbdata) { struct iphdr *iphdr = NULL; + struct ip6_hdr *ip6hdr = NULL; struct tcphdr *tcphdr = NULL; unsigned char *p; int len_tcp; bool bRet = false; - + uint8_t proto; + if (proto_check_ipv4(data,len)) { iphdr = (struct iphdr *) data; + proto = iphdr->protocol; proto_skip_ipv4(&data,&len); - if (iphdr->protocol==6 && proto_check_tcp(data,len)) + } + else if (proto_check_ipv6(data,len)) + { + ip6hdr = (struct ip6_hdr *) data; + proto_skip_ipv6(&data,&len,&proto); + } + else + { + // not ipv6 and not ipv4 + return false; + } + + if (proto==IPPROTO_TCP && proto_check_tcp(data,len)) + { + tcphdr = (struct tcphdr *) data; + len_tcp = len; + proto_skip_tcp(&data,&len); + //printf("got TCP packet. payload_len=%d\n",len); + if (cbdata->wsize && tcp_synack_segment(tcphdr)) { - tcphdr = (struct tcphdr *) data; - len_tcp = len; - proto_skip_tcp(&data,&len); - //printf("got TCP packet. payload_len=%d\n",len); - if (cbdata->wsize && tcp_synack_segment(tcphdr)) - { - tcp_rewrite_winsize(tcphdr,(uint16_t)cbdata->wsize); - bRet = true; - } - if (cbdata->hostcase && (p = find_bin(data,len,"\r\nHost: ",8))) - { - printf("modifying Host: => host:\n"); - p[2]='h'; // "Host:" => "host:" - bRet = true; - } - if (bRet) tcp_fix_checksum(tcphdr,len_tcp,iphdr->saddr,iphdr->daddr); + tcp_rewrite_winsize(tcphdr,(uint16_t)cbdata->wsize); + bRet = true; + } + if (cbdata->hostcase && (p = find_bin(data,len,"\r\nHost: ",8))) + { + printf("modifying Host: => host:\n"); + p[2]='h'; // "Host:" => "host:" + bRet = true; + } + if (bRet) + { + if (iphdr) + tcp_fix_checksum(tcphdr,len_tcp,iphdr->saddr,iphdr->daddr); + else + tcp6_fix_checksum(tcphdr,len_tcp,&ip6hdr->ip6_src,&ip6hdr->ip6_dst); } } return bRet; diff --git a/readme.txt b/readme.txt index 85aa15c..a180bbc 100644 --- a/readme.txt +++ b/readme.txt @@ -1,4 +1,4 @@ -zapret v.4 +zapret v.5 Для чего это надо ----------------- @@ -16,18 +16,29 @@ Некоторые DPI не могут распознать http запрос, если он разделен на TCP сегменты. Например, запрос вида "GET / HTTP/1.1\r\nHost: kinozal.tv......" мы посылаем 2 частями : сначала идет "GET ", затем "/ HTTP/1.1\r\nHost: kinozal.tv.....". -Как заставить систему разбивать запрос на 2 части ? Подменить поле tcp window size -на первом входящем TCP пакете с SYN,ACK. Тогда клиент подумает, что сервер установил -для него маленький window size и первый сегмент с данными отошлет не более указанной длины. -В следующем пакете мы не будем менять ничего, поэтому клиент это поймет так, -что сервер увеличил window size, и все пойдет как обычно. - Другие DPI спотыкаются, когда заголовок "Host:" пишется в другом регистре : например, "host:". Кое-где работает добавление дополнительного пробела после метода : "GET /" => "GET /". Как это реализовать на практике в системе linux ----------------------------------------------- +Как заставить систему разбивать запрос на части ? Можно прогнать всю TCP сессию +через transparent proxy, а можно подменить поле tcp window size на первом входящем TCP пакете с SYN,ACK. +Тогда клиент подумает, что сервер установил для него маленький window size и первый сегмент с данными +отошлет не более указанной длины. В последующих пакетах мы не будем менять ничего. +Дальнейшее поведение системы по выбору размера отсылаемых пакетов зависит от реализованного +в ней алгоритма. Опыт показывает, что linux первый пакет всегда отсылает не более указанной +в window size длины, остальные пакеты до некоторых пор шлет не более max(36,указанный_размер). +После некоторого количества пакетов срабатывает механизм window scaling и начинает +учитываться фактор скалинга, размер пакетов становится не более max(36,указанный_рамер << scale_factor). +Не слишком изящное поведение, но поскольку на размеры входящик пакетов мы не влияем, +а объем принимаемых по http данных обычно гораздо выше объема отсылаемых, то визуально +появятся лишь небольшие задержки. +Windows ведет себя в аналогичном случае гораздо более предсказуемо. Первый сегмент +уходит указанной длины, дальше window size меняется в зависимости от значения, +присылаемого в новых tcp пакетах. То есть скорость почти сразу же восстанавливается +до возможного максимума. + Перехватить пакет с SYN,ACK не представляет никакой сложности средствами iptables. Однако, возможности редактирования пакетов в iptables сильно ограничены. Просто так поменять window size стандартными модулями нельзя. @@ -42,8 +53,7 @@ iptables -t raw -I PREROUTING -p tcp --sport 80 --tcp-flags SYN,ACK SYN,ACK -j N так и маршрутизируемые пакеты. То есть решение одинаково работает как на клиенте, так и на роутере. На роутере на базе PC или на базе OpenWRT. В принципе этого достаточно. -Однако, при таком воздействии на TCP будет небольшая задержка при установлении соединения. -От 0.5 до 1.5 сек. +Однако, при таком воздействии на TCP будет небольшая задержка. Чтобы не трогать хосты, которые не блокируются провайдером, можно сделать такой ход. Создать список заблоченых доменов или скачать его с rublacklist. Заресолвить все домены в ipv4 адреса. Загнать их в ipset с именем "zapret". @@ -118,7 +128,7 @@ tkt : помогает разделение http запроса на сегме ТКТ был куплен ростелекомом, используется фильтрация ростелекома. Поскольку DPI не отбрасывает входящую сессию, а только всовывает свой пакет, который приходит раньше ответа от настоящего сервера, блокировки так же обходятся без применения "тяжелой артиллерии" следующим правилом : - iptables -t raw -I PREROUTING -p tcp --sport 80 -m string --hex-string "Location: http://95.167.13.50" --algo bm -j DROP --from 40 --to 300 + iptables -t raw -I PREROUTING -p tcp --sport 80 -m string --hex-string "|0D0A|Location: http://95.167.13.50" --algo bm -j DROP --from 40 --to 300 Ростелеком : см tkt Пример установки на debian 7