diff --git a/binaries/mips32r1-lsb/tpws b/binaries/mips32r1-lsb/tpws index 7f7cb54..0853a5a 100755 Binary files a/binaries/mips32r1-lsb/tpws and b/binaries/mips32r1-lsb/tpws differ diff --git a/binaries/mips32r1-msb/tpws b/binaries/mips32r1-msb/tpws index f575f27..a6a6d15 100755 Binary files a/binaries/mips32r1-msb/tpws and b/binaries/mips32r1-msb/tpws differ diff --git a/binaries/x86_64/tpws b/binaries/x86_64/tpws index 7f7cb54..b8ea2ed 100755 Binary files a/binaries/x86_64/tpws and b/binaries/x86_64/tpws differ diff --git a/changes.txt b/changes.txt index 78befa3..be1cb25 100644 --- a/changes.txt +++ b/changes.txt @@ -91,3 +91,7 @@ tpws : added options unixeol,methodeol,hosttab v18 tpws,nfqws : added hostnospace option + +v19 + +tpws : added hostlist option diff --git a/ipset/get_hostlist.sh b/ipset/get_hostlist.sh new file mode 100755 index 0000000..c36ab5d --- /dev/null +++ b/ipset/get_hostlist.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# get domain list. not IP + +SCRIPT=$(readlink -f $0) +EXEDIR=$(dirname $SCRIPT) + +. "$EXEDIR/def.sh" + +ZREESTR=$TMPDIR/zapret.txt +#ZURL=https://reestr.rublacklist.net/api/current +ZURL=https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv + +curl -k --fail --max-time 300 --max-filesize 41943040 "$ZURL" >$ZREESTR || +{ + echo reestr list download failed + exit 2 +} +dlsize=$(wc -c "$ZREESTR" | cut -f 1 -d ' ') +if test $dlsize -lt 204800; then + echo list file is too small. can be bad. + exit 2 +fi +(cut -s -f2 -d';' $ZREESTR | grep -a . | sed -re 's/^\*\.(.+)$/\1/' | awk '{ print tolower($0) }' ; cat $ZUSERLIST ) | sort -u >$ZHOSTLIST +rm -f $ZREESTR diff --git a/readme.txt b/readme.txt index 45191d4..d80add8 100644 --- a/readme.txt +++ b/readme.txt @@ -1,4 +1,4 @@ -zapret v.17 +zapret v.19 Для чего это надо ----------------- @@ -100,6 +100,7 @@ nfqws --qnum=200 ; номер очереди --wsize=4 ; менять tcp window size на указанный размер --hostcase ; менять регистр заголовка "Host:" по умолчанию на "host:". + --hostnospace ; убрать пробел после "Host:" и переместить его в конец значения "User-Agent:" для сохранения длины пакета --hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase Параметры манипуляции могут сочетаться в любых комбинациях. @@ -117,12 +118,16 @@ tpws - это transparent proxy. --hostspell=HoST ; точное написание заголовка Host (можно "HOST" или "HoSt"). автоматом включает --hostcase --hostdot ; добавление точки после имени хоста : "Host: kinozal.tv." --hosttab ; добавление табуляции после имени хоста : "Host: kinozal.tv\t" + --hostnospace ; убрать пробел после "Host:" --methodspace ; добавить пробел после метода : "GET /" => "GET /" --methodeol ; добавить перевод строки перед методом : "GET /" => "\r\nGET /" --unixeol ; конвертировать 0D0A в 0A и использовать везде 0A + --hostlist= ; действовать только над доменами, входящими в список из filename. поддомены автоматически учитываются. в файле должен быть хост на каждой строке. + ; список читается 1 раз при старте и хранится в памяти в виде иерархической структуры для быстрого поиска. + ; для списка РКН может потребоваться система с 128 Mb памяти ! расчитывайте требование RAM для процесса как 10-15 кратный размер файла списка. Параметры манипуляции могут сочетаться в любых комбинациях. Есть исключения : split-pos заменяет split-http-req. hostdot и hosttab взаимоисключающи. - + Провайдеры ---------- @@ -206,6 +211,17 @@ tiera : Требуется сплит http запросов в течение в в отдельный ipset "ipban". Он может использоваться для принудительного завертывания всех соединений на прозрачный proxy "redsocks" или на VPN. +Фильтрация по именам доменов +---------------------------- + +Альтернативой ipset является использование tpws со списком доменов. +Список доменов РКН может быть получен скриптом ipset/get_hostlist.sh - кладется в ipset/zapret-hosts.txt. +Этот скрипт автоматически добавляет к списку РКН домены из zapret-hosts-user.txt. +tpws должен запускаться без фильтрации по ipset. Весь трафик http идет через tpws, и он решает нужно ли +применять дурение в зависимости от поля Host: в http запросе. +Это создает повышенную нагрузку на систему. +Сам поиск по доменам работает очень быстро, нагрузка связана с прокачиванием объема данных через процесс. +Вариант хорошо подходит для тех, у кого быстрая система с 128+ Мб памяти и провайдер применяет DPI. Пример установки на debian 7 ---------------------------- @@ -263,7 +279,7 @@ apt-get --no-install-recommends install lsb-core install : /usr/lib/lsb/install_initd zapret remove : /usr/lib/lsb/remove_initd zapret -start : systemctl start zapret +start : sytemctl start zapret stop : systemctl stop zapret status, output messages : systemctl status zapret diff --git a/tpws/chartree.c b/tpws/chartree.c new file mode 100644 index 0000000..ef19502 --- /dev/null +++ b/tpws/chartree.c @@ -0,0 +1,81 @@ +#include "chartree.h" +#include +#include + +static char *DupLower(const char *s) +{ + char *sp,*sl = strdup(s); + if (!sl) return false; + for(sp=sl;*sp;sp++) *sp=tolower(*sp); + return sl; +} + +static cptr *CharTreeInit(char c) +{ + cptr *p; + p=(cptr *)calloc(1,sizeof(cptr)); + if (p) p->chr = c; + return p; +} +void CharTreeDestroy(cptr *p) +{ + if (p) + { + CharTreeDestroy(p->leaf); + CharTreeDestroy(p->next); + free(p); + } +} +static cptr *CharTreeFindChar(cptr *p,char c) +{ + while (p) + { + if (p->chr==c) return p; + p = p->next; + } + return NULL; +} +bool CharTreeAddStr(cptr **pp,const char *s) +{ + cptr *p; + if (*pp) + { + if (!(p=CharTreeFindChar(*pp,*s))) + { + // already present. append to list head + if (!(p = CharTreeInit(*s))) + return false; + p->next = *pp; + *pp = p; + } + } + else + if (!(p = *pp = CharTreeInit(*s))) return false; + if (!*s) return true; + return CharTreeAddStr(&p->leaf,s+1); +} +bool CharTreeCheckStr(cptr *p,const char *s) +{ + p = CharTreeFindChar(p,*s); + if (!p) return false; + if (!*s) return true; + return CharTreeCheckStr(p->leaf,s+1); +} +bool CharTreeAddStrLower(cptr **pp,const char *s) +{ + bool b; + char *sl = DupLower(s); + if (!sl) return false; + b=CharTreeAddStr(pp,sl); + free(sl); + return b; +} +bool CharTreeCheckStrLower(cptr *pp,const char *s) +{ + bool b; + char *sl = DupLower(s); + if (!sl) return false; + b=CharTreeCheckStr(pp,sl); + free(sl); + return b; +} diff --git a/tpws/chartree.h b/tpws/chartree.h new file mode 100644 index 0000000..ac2eddc --- /dev/null +++ b/tpws/chartree.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +typedef struct cptr +{ + char chr; + struct cptr *leaf,*next; +} cptr; + +void CharTreeDestroy(cptr *p); +bool CharTreeAddStr(cptr **pp,const char *s); +bool CharTreeAddStrLower(cptr **pp,const char *s); +bool CharTreeCheckStr(cptr *p,const char *s); +bool CharTreeCheckStrLower(cptr *pp,const char *s); diff --git a/tpws/tpws.c b/tpws/tpws.c index c0ece75..19dc72a 100644 --- a/tpws/tpws.c +++ b/tpws/tpws.c @@ -23,6 +23,37 @@ #include "tpws.h" #include "tpws_conn.h" +#include "chartree.h" + +bool LoadHostList(cptr **hostlist, char *filename) +{ + char *p, s[256]; + FILE *F = fopen(filename, "rt"); + int ct = 0; + + *hostlist = NULL; + if (!F) + { + fprintf(stderr, "Could not open %s\n", filename); + return false; + } + while (fgets(s, 256, F)) + { + for (p = s + strlen(s) - 1; p >= s && (*p == '\r' || *p == '\n'); p--) *p = 0; + if (!CharTreeAddStrLower(hostlist, s)) + { + CharTreeDestroy(*hostlist); + *hostlist = NULL; + fprintf(stderr, "Not enough memory to store host list\n", filename); + fclose(F); + return false; + } + ct++; + } + fclose(F); + printf("Loaded %d hosts from %s\n", ct, filename); + return true; +} enum splithttpreq { split_none = 0, split_method, split_host }; @@ -38,6 +69,7 @@ struct params_s enum splithttpreq split_http_req; int split_pos; int maxconn; + cptr *hostlist; }; struct params_s params; @@ -113,11 +145,12 @@ bool handle_epollin(tproxy_conn_t *conn, int *data_transferred) { rd = recv(fd_in, buf, RD_BLOCK_SIZE, MSG_DONTWAIT); if (rd > 0) { - char *p, *pp, *pHost=NULL; - ssize_t method_len=0, split_pos=0, pos; + char *p, *pp, *pHost = NULL; + ssize_t method_len = 0, split_pos = 0, pos; const char **method; - bool bIsHttp=false; - char bRemovedHostSpace=0; + bool bIsHttp = false, bBypass = false; + char bRemovedHostSpace = 0; + char Host[128]; bs = rd; @@ -127,7 +160,7 @@ bool handle_epollin(tproxy_conn_t *conn, int *data_transferred) { if (method_len <= bs && !memcmp(buf, *method, method_len)) { bIsHttp = true; - method_len-=2; // "GET /" => "GET" + method_len -= 2; // "GET /" => "GET" break; } } @@ -135,122 +168,154 @@ bool handle_epollin(tproxy_conn_t *conn, int *data_transferred) { { printf("Data block looks like http request start : %s\n", *method); - if (params.unixeol) + if (params.hostlist) { - p = pp = buf; - while (p = find_bin(p, buf + bs - p, "\r\n", 2)) + pHost = find_bin(buf, bs, "\nHost: ", 7); + if (pHost) { - *p = '\n'; p++; - memmove(p, p + 1, buf + bs - p - 1); - bs--; - if (pp == (p - 1)) + bool bInHostList = false; + p = pHost + 7; + while (p < (buf + bs) && (*p == ' ' || *p == '\t')) p++; + pp = p; + while (pp < (buf + bs) && (pp - p) < (sizeof(Host) - 1) && *pp != '\r' && *pp != '\n') pp++; + memcpy(Host, p, pp - p); + Host[pp - p] = '\0'; + p = Host; + printf("Requested Host is : %s\n", Host); + while (p) { - // probably end of http headers - printf("Found double EOL at pos %zd. Stop replacing.\n", pp - buf); + bInHostList = CharTreeCheckStrLower(params.hostlist, p); + printf("Hostlist check for %s : %s\n", p, bInHostList ? "positive" : "negative"); + if (bInHostList) break; + p = strchr(p, '.'); + if (p) p++; + } + bBypass = !bInHostList; + } + + } + if (!bBypass) + { + if (params.unixeol) + { + p = pp = buf; + while (p = find_bin(p, buf + bs - p, "\r\n", 2)) + { + *p = '\n'; p++; + memmove(p, p + 1, buf + bs - p - 1); + bs--; + if (pp == (p - 1)) + { + // probably end of http headers + printf("Found double EOL at pos %zd. Stop replacing.\n", pp - buf); + break; + } + pp = p; + } + pHost = NULL; + } + + if (params.methodspace) + { + // we only work with data blocks looking as HTTP query, so method is at the beginning + printf("Adding extra space after method\n"); + p = buf + method_len + 1; + pos = method_len + 1; + memmove(p + 1, p, bs - pos); + *p = ' '; // insert extra space + bs++; // block will grow by 1 byte + pHost = NULL; + } + + // search for Host only if required (save some CPU) + if (params.hostdot || params.hosttab || params.hostcase || params.hostnospace || params.split_http_req == split_host) + { + // we need Host: location + pHost = find_bin(buf, bs, "\nHost: ", 7); + if (pHost) pHost++; + } + + if (pHost && params.hostdot || params.hosttab) + { + p = pHost + 6; + while (p < (buf + bs) && *p != '\r' && *p != '\n') p++; + if (p < (buf + bs)) + { + pos = p - buf; + printf("Adding %s to host name at pos %zd\n", params.hostdot ? "dot" : "tab", pos); + memmove(p + 1, p, bs - pos); + *p = params.hostdot ? '.' : '\t'; // insert dot or tab + bs++; // block will grow by 1 byte + } + } + + if (pHost && params.hostnospace && pHost[5] == ' ') + { + p = pHost + 6; + pos = p - buf; + printf("Removing space before host name at pos %zd\n", pos); + memmove(p - 1, p, bs - pos); + bs--; // block will shrink by 1 byte + bRemovedHostSpace = 1; + } + + if (!params.split_pos) + { + switch (params.split_http_req) + { + case split_method: + split_pos = method_len - 1; + break; + case split_host: + if (pHost) + split_pos = pHost + 6 - bRemovedHostSpace - buf; break; } - pp = p; } - } - if (params.methodspace) - { - // we only work with data blocks looking as HTTP query, so method is at the beginning - printf("Adding extra space after method\n"); - p = buf + method_len + 1; - pos = method_len + 1; - memmove(p + 1, p, bs - pos); - *p = ' '; // insert extra space - bs++; // block will grow by 1 byte - } - - // search for Host only if required (save some CPU) - if (params.hostdot || params.hosttab || params.hostcase || params.hostnospace || params.split_http_req==split_host) - { - // we need Host: location - pHost=find_bin(buf, bs, "\nHost: ", 7); - if (pHost) pHost++; - } - - if (pHost && params.hostdot || params.hosttab) - { - p = pHost + 6; - while (p < (buf + bs) && *p != '\r' && *p != '\n') p++; - if (p < (buf + bs)) + if (params.hostcase) { - pos = p - buf; - printf("Adding %s to host name at pos %zd\n", params.hostdot ? "dot" : "tab", pos); - memmove(p + 1, p, bs - pos); - *p = params.hostdot ? '.' : '\t'; // insert dot or tab - bs++; // block will grow by 1 byte + if (pHost) + { + printf("Changing 'Host:' => '%c%c%c%c:' at pos %zd\n", params.hostspell[0], params.hostspell[1], params.hostspell[2], params.hostspell[3], pHost - buf); + memcpy(pHost, params.hostspell, 4); + } } - } - if (pHost && params.hostnospace && pHost[5]==' ') - { - p = pHost + 6; - pos = p - buf; - printf("Removing space before host name at pos %zd\n", pos); - memmove(p - 1, p, bs - pos); - bs--; // block will shrink by 1 byte - bRemovedHostSpace=1; - } - - if (params.split_pos) - { - split_pos = params.split_pos < bs ? params.split_pos : 0; + if (params.methodeol) + { + printf("Adding EOL before method\n"); + if (params.unixeol) + { + memmove(buf + 1, buf, bs); + bs++;; + buf[0] = '\n'; + if (split_pos) split_pos++; + } + else + { + memmove(buf + 2, buf, bs); + bs += 2; + buf[0] = '\r'; + buf[1] = '\n'; + if (split_pos) split_pos += 2; + } + } } else { - switch (params.split_http_req) - { - case split_method: - split_pos = method_len - 1; - break; - case split_host: - if (pHost) - split_pos = pHost + 6 - bRemovedHostSpace - buf; - break; - } - } - - if (params.hostcase) - { - if (pHost) - { - printf("Changing 'Host:' => '%c%c%c%c:' at pos %zd\n", params.hostspell[0], params.hostspell[1], params.hostspell[2], params.hostspell[3], pHost - buf); - memcpy(pHost, params.hostspell, 4); - } - } - - if (params.methodeol) - { - printf("Adding EOL before method\n"); - if (params.unixeol) - { - memmove(buf + 1, buf, bs); - bs++;; - buf[0] = '\n'; - if (split_pos) split_pos++; - } - else - { - memmove(buf + 2, buf, bs); - bs += 2; - buf[0] = '\r'; - buf[1] = '\n'; - if (split_pos) split_pos += 2; - } + printf("Not acting on this request\n"); } } else { printf("Data block does not look like http request start\n"); - // this is the only parameter applicable to non-http block (may be https ?) - if (params.split_pos|\n" - " --port=\n --maxconn=\n" - " --split-http-req=method|host\n" - " --split-pos=\t; split at specified pos. invalidates split-http-req.\n" - " --hostcase\t\t; change Host: => host:\n" - " --hostspell\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" - " --hostdot\t\t; add \".\" after Host: name\n" - " --hosttab\t\t; add tab after Host: name\n" - " --hostnospace\t\t; remove space after Host:\n" - " --methodspace\t\t; add extra space after method\n" - " --methodeol\t\t; add end-of-line before method\n" - " --unixeol\t\t; replace 0D0A to 0A\n" - " --daemon\t\t; daemonize\n" - " --user=\t; drop root privs\n" - ); + " --bind-addr=|\n" + " --port=\n" + " --maxconn=\n" + " --hostlist=\t; only act on host in the list (one host per line, subdomains auto apply)\n" + " --split-http-req=method|host\n" + " --split-pos=\t; split at specified pos. invalidates split-http-req.\n" + " --hostcase\t\t; change Host: => host:\n" + " --hostspell\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n" + " --hostdot\t\t; add \".\" after Host: name\n" + " --hosttab\t\t; add tab after Host: name\n" + " --hostnospace\t\t; remove space after Host:\n" + " --methodspace\t\t; add extra space after method\n" + " --methodeol\t\t; add end-of-line before method\n" + " --unixeol\t\t; replace 0D0A to 0A\n" + " --daemon\t\t; daemonize\n" + " --user=\t; drop root privs\n" + ); exit(1); } @@ -486,6 +553,7 @@ void parse_params(int argc, char *argv[]) { "methodeol",no_argument,0,0 },// optidx=14 { "hosttab",no_argument,0,0 },// optidx=15 { "unixeol",no_argument,0,0 },// optidx=16 + { "hostlist",required_argument,0,0 },// optidx=17 { NULL,0,NULL,0 } }; while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) @@ -584,6 +652,10 @@ void parse_params(int argc, char *argv[]) case 16: /* unixeol */ params.unixeol = true; break; + case 17: /* hostlist */ + if (!LoadHostList(¶ms.hostlist, optarg)) + exit(1); + break; } } if (!params.port) @@ -749,6 +821,8 @@ int main(int argc, char *argv[]) { retval = event_loop(listen_fd); close(listen_fd); + if (params.hostlist) CharTreeDestroy(params.hostlist); + fprintf(stderr, "Will exit\n"); if (retval < 0)