- Локальный прокси-сервер для фильтрации браузерного трафика
- Черновой вариант: nginx + php-fpm
- Итоговый вариант
- SSL/TLS-обёртки
- Основной функционал
- Что получилось
- Как собрать/установить и технические детали кода
- Насчёт прав доступа и вообще безопасности
- Заключение
- Как создать свой собственный интернет-прокси-сервер в считанные минуты
- Предпосылки
- 1. Загрузите и установите GlypeProxy
- 2. Готово к использованию
- 3. Настройки администратора
- Пишем HTTP proxy сервер с плагинами
Локальный прокси-сервер для фильтрации браузерного трафика
Хочу рассказать о процессе разработки и поделиться прокси-сервером, которым сам пользуюсь для фильтрации всяческого мусора и других задач, требующих просмотра или вмешательства в браузерный траффик. Возможно, аналогичный функционал уже где-то есть, но мне хотелось сделать конкретно под свои нужды с возможностью real-time дописывания кода под любую сиюминутно понадобившуюся мелочь. Ну и под не-мелочи тоже, но это уже дольше.
Первоначально была задача упростить посещение сайтов через медленное (около 5-10кбайт/с с лагами) подключение. Тут два основных направления: 1) вырезать всё что не нужно (в первую очередь рекламу), и 2) закешировать всё что можно закешировать без особого вреда для функционала посещаемых сайтов, даже когда сами сайты не разрешают кеширование в http-заголовках, а то и явно препятствуют ему, дописывая после урлов статических файлов знак вопроса с рандомным числом.
Предупреждение: реализация, описанная ниже, делалась для Linux-а, вроде бы работает на других *nix, но даже компиляция её на винде не рассматривалась (хотя возможность адаптировать, конечно, есть).
Черновой вариант: nginx + php-fpm
Тратить на всё это время не хотелось, поэтому было решено по-быстрому настроить связку nginx + php-fpm, из которых первый разбирал входящие подключения, включая https и перенаправлял их все, без разбора хостов и урлов, в один и тот же php-скрипт. Так же была маленькая программа на C, которая конвертировала http-proxy-протокол в обычный http(s)-трафик. То есть превращала запросы GET http://host/path HTTP/1.x в GET /path HTTP/1.x (имя хоста всё равно есть в Host-заголовке) и проксировала все https CONNECT’ы на локальный nginx. Как потом выяснилось, убирать http://host из http-запросов было не обязательно, nginx их принимает так же как и обычные.
PHP-скрипт, в свою очередь, собирал назад разложенный по разным переменным запрос, через fsockopen() подключался к целевому серверу (благо там поддержка SSL встроенная), слал собранный http-запрос, забирал ответ и отправлял его браузеру с помощью header() и echo. Скрипт делался не совсем с нуля, спасибо некоей Évelyne Lachance за https://github.com/eslachance/php-transparent-proxy (но всё же то, что по ссылке, имеет несколько другую цель и поэтому просто скопировать его было нельзя).
Во-вторых, nginx не умеет генерить сертификаты на лету из коробки, а настройку «игнорировать все проблемы с сертификатами» я в firefox не нашёл. Проблема была на тот момент решена патчем браузера, заставлявшим его любой сертификат считать доверенным и подходящим. Наверно были и другие способы это сделать, но все они казались сложнее. Патч естественно был кривой и сделанный почти методом тыка (желания изучать весь браузерный код проверки сертификатов не было), но он делал своё дело, а больше было и не надо.
Между сборкой исходного запроса и отправкой проксированного тривиальным образом были добавлены два куска кода. Один проверял, что host или host/path не из чёрного списка (организованного в виде отдельного php-файла с массивом внутри). Второй проверял, нет ли страницы в кеше (если есть — отдаётся из кеша). Если страницы в кеше нет, то делается проксированный запрос. Если запрос был GET и статус ответа 200, то полученная страница — кандидат на запоминание в кеше. Запоминать или нет — решалось функцией, которая по хосту, урлу (там просто были прописаны конкретные адреса, которые меня интересовали и которые я знал что надо кешировать) и content-type выносила вердикт. Кешировалось немного: http-статус, content-type и тело ответа. И при отдаче из кеша, соответственно, отдавались только эти данные, а все остальные заголовки, которые были в исходном ответе, безвозвратно терялись.
Вот такая грубая и костыльная реализация, на удивление, вполне себе работала, хотя конечно артефакты её присутствия время от времени проявлялись и раздражали.
В следующие полгода использования система дорабатывалась под решение возникающих проблем (как проблем из-за общей её кривости, так и проблем — возникающих задач).
Ещё через 3-4 месяца мне надоело патчить браузер после каждого апдейта и было решено сделать всё по-нормальному — nginx и php выкидываем и пишем всё на C.
Итоговый вариант
Маленькая программа (из старой версии), которая разбирала и перенаправляла прокси-протокол, была взята за основу. Первоочерёдной задачей было реализовать в ней разворачивание https, потому что всё остальное в общем-то уже известно как делать (делалось не раз в другим проектах), а с SSL где-то кроме php я на тот момент ни разу не работал.
Для этого взял библиотеку OpenSSL. Весь код, её использующий, было решено выделить в отдельные процессы. Потому что, во-первых, в ней тогда постоянно находились дыры (а межпроцессная изоляция может эту проблему иногда сгладить), во-вторых так проще делать (модульность и инкапсуляция на уровне сокетов), и в-третьих API OpenSSL местами весьма странное (мягко говоря), и для нейтрализации последствий то ли кривого API, то ли неправильного его применения (вследствие отсутствующей или двусмысленной документации) удобно просто убивать ssl-процесс после того как соединение завершилось (к сожалению, это плохо сказывается на производительности, но я с этим смирился).
Вообще, из-за регулярных уязвимостей и невнятного API я даже собирался написать свою реализацию TLS, и даже начал, но, дойдя до парсинга сертификатов (увы, без этого почти ничего не сделать, ибо сертификаты оказывается используются не только для защиты от подмены сервера, но и для согласования ключей, даже если защита от подмены не нужна), испытал ещё один дискомфорт от спецификации ASN1. Стало понятно, что API библиотеки это только вершина айсберга, а внутри оно всё такое уже начиная с RFC самого протокола. Хотя в последнее время я более менее с этим всем смирился.
SSL/TLS-обёртки
Итак, были сделаны две вспомогательные программы, одна для заворачивания клиентского нешифрованного сокета в SSL/TLS, и вторая для разворачивания зашифрованного сокета, принятого сервером, в незашифрованный. Принцип простой: у подпрограммы есть наследуемые от родительской программы файловые декрипторы, которые родительская программа может заранее открыть нужным образом так, что дочерний процесс будет выглядеть для неё просто сокетом, в который можно записывать и из которого можно читать. При этом практически никакого отличия этого межпроцессного сокета от сокета интернетного не будет.
Таким образом, сервер принимает (accept) входящее подключение, выясняет что на той стороне хотят SSL/TLS, после чего отдаёт соединение дочернему процессу (который поднимает SSL/TLS-сессию, генерирует сертификат если нужно или берёт уже ранее сгенерированный), а от дочернего процесса берёт межпроцессный сокет, через который передаются уже нешифрованные данные. В итоге весь дальнейший код может вообще не делать различий между http-соединением и https-соединением, прозрачно расшифровывающимся дочерним процессом, и использовать для работы с этими соединениями обычные функции для работы с сокетами.
Аналогично и на клиентской стороне, только в обратную сторону: клиент запускает дочерний шифрующий процесс, шлёт данные в межпроцессный сокет, а дочерний процесс уже обеспечивает их шифрование прозрачно для клиента.
Ответ (много отладочных логов, можно скомпилировать чтобы было без них): дамп сертификата и предоставленной цепочки
Попытка собрать trusted цепочку
Как видно, «Certum CA» нет в списке доверенных Root CA, поэтому на первый взгляд это обрыв цепочки, но во втором сертификате цепочки (cert[2]) нашлась ссылка http://repository.certum.pl/ca.cer откуда можно скачать этот неизвестный сертификат; сертификат скачан (cert[*1]), но оказался самоподписанным и таким образом ничему не помогающим. На самом деле, в Root CA есть «Certum Trusted Network CA», так что всё в порядке и можно было ничего не скачивать.
Проверка соответствия доменного имени:
Теперь есть прозрачное подключение, шлём http-запрос и получаем ответ
Основной функционал
С шифрованием разобрались, теперь можно приступить к обработке полезной нагрузки.
Так как nginx у нас больше нет, то парсинг запроса придётся реализовывать заново. В целом ничего сложного тут нет, сначала парсятся заголовки через \r\n или \n разделитель, до тех пор пока не встретится пустая строка, а затем, если был заголовок Content-Length, читается тело запроса указанного количества байт. Chunked encoding в запросах можно и не поддерживать — никакие нормальные браузеры, да и вообще http-клиенты, его не генерируют.
После того, как запрос распарсен, он попадает в обработчик запроса — это та часть, которая раньше была написана на php, весьма увеличившаяся в размерах и количестве файлов. И хотя списки правил (кого блокировать, кого кешировать, кому резать куки и т.д.), ранее бывшие оформлены в виде php-массивов и коротких скриптовых функций, были переделаны в отдельные текстовые файлы специального удобного формата, но часть «пользовательской» логики всё ещё защита в коде (только теперь на C а не PHP), а код в целом унаследовал много признаков «сделать побыстрее как-нибуть» от своего предшественника. Сейчас обработчик (кроме капитально переделанных списков правил) всё так же сделан в виде одного «потока» кода из «главного» файла и вставленных в него include (не заголовочных файлов а сам код).
Изначально функционал обработчика был даже чуть меньше чем функционал последней из старых версий, но за 2 года плавно набрал ещё много нового. Из последнего — последние полгода-год всё чаще стали попадаться сайты с неполными цепочками сертификатов (потому что браузеры теперь их поддерживают без ругани), сначала я их просто вносил в белый список исключений, но это стало происходить слишком часто и пришлось реализовать кеширование и докачивание недостающих сертификатов в цепочке.
Что получилось
Отдельные модули для разворачивания шифрованного SSL/TLS-туннеля, один из которых при желании можно использовать как самостоятельную программу типа ssl-telnet. Ещё можно заменить их на какие-то альтернативные реализации (например с другой крипто-библиотекой), при этом остальная часть прокси ничего не заметит. Аналогично с инкрементальными обновлениями, ничего даже не нужно перезапускать. А ещё можно их использовать как демки по работе с OpenSSL (я прочёл много документации и всяких обсуждений в процессе их написания, возможно кто-то сможет теперь всё это быстрее усвоить). Код старался делать максимально понятным и простым.
Возможность пробросить точку выхода в интернет через, например, ssh port forwarding (связанный с этим функционалом код расположен в helpers/remote.c и в proxy.c в месте где обрабатывается аргумент «—proxy». Это не тоже самое что запустить само прокси на удалённом сервере. Тут важно что кеш и расшифровка SSL/TLS находятся на локальном компе, а пробрасывается только «внешний» конец. Хотя я давно не проверял работу этого режима, могла и сломаться.
Единообразный формат файлов правил для: блокировки загрузки страниц, блокировки куков, управления кешированием, включения сниффера, включения raw-режима (для real-time стримингов).
Аналог /etc/hosts но усовершенствованный: возможность сопоставления айпи-адреса не только по хосту, но и по порту и по протоколу, а так же сменить порт и протокол подключения.
Кеш, управляемый прописанными ему настройками, а не заголовками, присланными сервером. Заголовки управления кешированием часто не отражают действительность (или отражают мнение владельца сайта, которое может расходиться с критериями удобства пользования этим сайтом), поэтому сейчас они игнорируются. Думаю что стоит сделать их учёт по принципу «если сайт разрешил кешировать то значит можно, а если нет — разбираемся сами можно или нельзя», но пока руки не дошли. Кеш хранится в интуитивно понятно организованном дереве директорий, хранится бессрочно, но можно либо вручную удалять отдельные сохранения, либо настройкой задать «игнорировать всё что сохранено раньше такого-то времени».
Возможность кастомных действий перед, после или вместо загрузки страницы проксированным запросом. Реализваны хардкодом, файл handle/inc.rewrite.c
Любой аспект работы прокси можно легко переделать при необходимости. Код, несмотря на то, что он весьма разросся по сравнению с первыми версиями, всё ещё занимает очень мало места, и не потребует много времени как на первоначальное изучение, так и на освежение в памяти забытых деталей. С другой стороны, кому-то это и минус — для пользования всеми возможностями программы предполагается её регулярная пересборка и правка кода, а не «скомпилировал и удалил исходники».
Плюсы и минусы
(+) Модульность
(-) Из-за изоляции OpenSSL работает медленнее чем могло бы
(+) Гибкая настраиваемость под конкретные нужды
(+-) Много настроек прямо в исходном коде в виде #define и не только
(+-) IPv6 не поддерживается
(-) Нет интерактивного приложения для настройки и управления
(-) Код местами весьма плох и костылен
(-) До сих пор нет поддержки Access-Control-Allow-Origin с не ‘*’ в закешированных ответах — но единственное что у меня от этого ломалось это интерфейс мэйлру почты.
(-) До сих пор нет поддержки учёта настроек кеширования, присылаемых сервером
(-) Скачивать через него большие файлы (не в raw режиме) неэффективно — сначала прокси скачает файл себе на диск, затем будет через localhost-сеть отдавать его браузеру, который опять будет сохранять его на диск, а ещё там где-то (а может и в нескольких местах) прописано ограничение в 10мбайт на запрос
Как собрать/установить и технические детали кода
Из обычных библиотек используются стандартная C-библиотека, zlib (для распаковки gzip http-ответов), openssl (линкуется только к ssl-подпрограммам). В случае с Debian это были пакеты zlib1g-dev и libssl-dev. Так же используются свои библиотеки (маленькие) с кодом который мне часто оказывается нужен в разных местах и поэтому вместо регулярного копипаста выделен в отдельные компилируемые единицы.
В целях данной публикации, для упрощения всё это собрано в один пакет (скачать можно тут) и обёрнуто в один сборочный скрипт (да, не Makefile, так уж вышло что пока я обхожусь без make) build-all.sh.
Ряд настроек расположен в исходниках: в скрипте fproxy/build2.sh, в файле fproxy/config.h (если у вас файл с бандлом доверенных центров сертификации расположен не в /etc/ssl/certs/ca-certificates.crt то надо этот путь заменить тут). Настройки подпрограмм (helpers/) расположены прямо в их коде вверху в виде #define (config.h они не используют), но их менять вряд ли кому потребуется.
То, что некоторая часть настроек прописана в компилируемом коде, а не в файлах настроек сделано для (моего) удобства. В файлах настроек настраивается то, что можно назвать наиболее часто меняющимися пользовательскими настройками (списки правил), а всё остальное — в общем то первоначальная настройка программы при её установке, и крайне редкие изменения всяких технических параметров (даже чаще меняется логика работы кода чем #define-настройки). По факту сейчас вполне рабочая версия получается если просто запустить build-all.sh и не трогать никакие исходники перед этим.
Но переделать код на приём настроек через командную строку или из ещё одного файла конфигурации несложно.
Для работы прокси нужна заранее подготовленная структура директорий. Для её создания есть скрипт prepare-dir.sh — он создаёт рядом подготовленную директорию fproxy-target/ со всем что требуется и кладёт туда примеры правил (в том числе много блокировок спамных доменов).
| Путь | Описание: |
|---|---|
| cache/ | Симлинки на второй уровень cache_real/. |
| cache_real/ | Кеш, двухуровневая структура — домен 2 уровня, потом полный домен. |
| saved/ | Симлинки на второй уровень saved_real/. |
| saved_real/ | Сохранённый не-кеш, 2-ур. структура. |
| cert-pin/certs/ | Настройки сертификатов для каждого домена (общий режим, белый/чёрный списки, вопросы). |
| cert-pin/queue/ | Тут создаются файлы при появлении вопросов в качестве флажков для интерактивной программы управлением этими настройками, если она будет. |
| cert-pin/cache/ | Кеш известных проксе сертификатов для дополнения неполных цепочек, принятых от сайтов (это НЕ список доверия, все эти сертификаты действительны только после прохождения проверок на общих основаниях). |
| dyn-certs/ | Кеш для сгенерированных сертификатов которые шлются браузеру. |
| hist/ | Сохранённая история посещений (инструментов для её просмотра пока нет, кроме ручного). |
| internal/ | Сейчас тут хранится сокет для запросов от скачивателя AIA сертификтов к проксе. |
| log/ | Логи. |
| pem/ | Корневой сертификат и приватные ключи для генерации сертификатов сайтов. |
| proxy_temp/ | Хранение запросов которые слишком большие для хранения их в памяти. |
| tmp/ | Временные файлы. |
| dyn-cert-serial | Файл с текущим серийным номером генерируемого сертификата (начальное содежание — символ ‘0’ и перевод строки после него). |
| mincache.date | Если файл есть и в нём тут записано число больше 0, то весь кеш раньше этого времени будет игнорироваться. |
| offline.flag | Если файл есть и в нём тут записано число больше 0, то прокси будет работать в режиме «интернет-архива» — на каждый запрос сначала искать в saved_real/ данные, близкие по времени к указанному числу, и только если не нашлось — проксировать запрос. |
| rules_*.txt | Различные правила, на данный момент их изменения учитываются только после перезапуска прокси. |
Насчёт прав доступа и вообще безопасности
Простейший вариант установки — запустить всё из под своего обычного юзера с обычными правами на всё (можно даже прямо из директории fproxy-target/, созданной скриптом prepare-dir.sh).
Но по ряду причин имеет смысл сделать по-другому. Для прокси создаётся отдельный юзер и группа, ему выдаются эксклюзивные права на чтение данных прокси, и права на запись в несколько мест, где они нужны. Права «только чтение» можно реализовать, поставив владельца root и группу прокси, и поставив доступ 0640 или 0750. Делать что бы то ни было из данных world-readable в любом случае плохая идея, всё же там лежит вся история браузера, а где-то и сохранённое содержание страниц, а так же ключи, с помощью которых браузеру можно делать MITM.
Для работы программы dashboard ей нужен только доступ на чтение к файлу BASE/log/dashboard и ничего другого.
По умолчанию прокси слушает 127.0.0.10:3128. Для работы с 127.0.0.10 у меня ничего специально настраивать (в ОС) не пришлось, но возможно в каких-то системах нужно.
Рабочая директория по умолчанию — та, что была текущей при запуске, но это можно поменять в настройках.
Заключение
Ещё раз ссылка на скачивание: тут.
Для публикации код был дооформлен в такой вид, который на мой взгляд допустимо публиковать, но всё же делалось это исключительно для себя под локальное применение и «по-быстрее», так что сейчас код во многих местах не образец для подражания. Но основную задачу он выполняет, а со временем будет улучшаться.
Из наиболее приоритетных на данный момент планов на будущее в плане функционала — сделать интерактивную настройку и управление (сертификаты, кеш, история, менеджер загрузок больших файлов) либо отдельным приложением, либо в спецдомене через браузер, но ничего не обещаю.
Надеюсь, что кому-то это всё окажется интересным и так или иначе поможет.
Как создать свой собственный интернет-прокси-сервер в считанные минуты
На прошлой неделе у меня было довольно много проблем в компании, связанных с запрещенными сайтами. В ней использовался обширный каталог «запрещенных сайтов», в котором хранились все сайты, кроме нескольких популярных. По сути, нет доступа к игровым сайтам, нет личной электронной почты и нет Сисадминства.
Вчера я обнаружил, что к одному из моих личных доменов можно получить доступ.
Возвратившись к моему домашнему компьютеру, через несколько минут, я создал свой собственный функционирующий прокси-сервер. Следуя инструкциям ниже, вы тоже сможете его создать.
Предпосылки
Если мы хотим сделать это, то вот что нам нужно:
Теоретически работать будет на любом веб-хостинге, даже на таком, как старый компьютер в вашем подвале или даже бесплатный онлайн-хостинг.
Найдите это на веб-сайте своего веб-хостинга. Если это платный хостинг, вы можете почти рассчитывать на это. Особенно cURL — это функция, которая будет отключена на большинстве бесплатных веб-серверов.
Да, это так. Некоторые веб-хостинги явно запретят вам создавать прокси-сервер (или чат-комнату, если на то пошло) в их содержании. И наверняка они узнают об этом.
1. Загрузите и установите GlypeProxy
GlypeProxy — это бесплатный, автономный PHP-скрипт. Это означает, что он легкий и невероятно прост в настройке. Вы можете скачать его с репозитория на GitHub (https://github.com/k1995/glype).
Затем загрузите файлы в подкаталог на вашем веб-сервере. Если вы видите папку с именем «www» или называющуюся так после папки вашего доменного имени (например, domain.com), то создайте там подпапку. Вам нужно избегать использования слова «прокси», потому что некоторые компании распознают это. Вместо этого используйте «web» или «surf».
Существует несколько способов загрузки файлов. Если вы уже знаете это, перейдите к следующему абзацу. Здесь они отсортированы от степени усилий.
Посмотрите в менеджере файлов опции «unpack (распаковать)» или «extract (извлечь)». Затем вы можете загрузить его за один прием. Это поддерживается не везде.
Используйте FTP-клиент (например, FileZilla) для FTP доступа к вашему веб-хосту и перенесите туда все файлы. Поддерживается многими хостингами.
Вам действительно не захочется этого делать — вы точно уверены, что у вас нет альтернатив выше? Последним решением будет вручную загрузить все файлы и структуры папок. Или начните искать другого хостера — это может быть не такая плохая идея.
2. Готово к использованию
Нет никакой реальной настройки. Просто введите в браузере каталог, в который будут помещены все эти файлы (например, domain.com/surf) и GlypeProxy. Если вам не нравится логотип, вам придется заменить его вручную. Но если вы хотите использовать прокси-сервер для личного использования, некоторый небольшой брендинг не имеет значения.

Пользователи могут вводить любой URL-адрес и, после расширения опций, выбрать кодирование URL-адреса, страницы, разрешить куки-файлы, скрипты и объекты. Кодирование страницы может помочь вам получить доступ к некоторым сайтам, к которым все еще не удается получить доступ, но иногда может привести к повреждению веб-страницы.
3. Настройки администратора
Хотя Glype — мощный прокси-скрипт, инструменты администрирования, очевидно, являются основой поддержки. Возможно, вы этого не знаете, но многие инструменты онлайн-прокси работают на Glype, как и тот, который вы только что создали. Настройки администратора оставляют место не только для настройки, но и для улучшения. Вы можете назначить специальные коды для проблемных веб-сайтов и изменить списки пользовательских агентов и прокси.

Наиболее полезными для начинающих пользователей будут инструменты кэширования (на фото выше), журналы и черные списки. Инструменты кэширования могут помочь вам улучшить скорость просмотра, сохранив некоторые файлы со всех или на некоторые из уже посещенных веб-сайтов. Журналы отключены по умолчанию, но могут иметь законное основание в будущем, в зависимости от того, кто использует ваш прокси-сервер. Всегда указывайте, что вы храните эти журналы. Наконец, черные списки позволят вам блокировать (все, кроме) несколько сайтов или пользователей.
Чтобы получить доступ к панели администратора, введите в браузере admin.php на своем веб-сервере (например, domain.com/surf/admin.php).
Если GlypeProxy кажется вам слишком примитивным, то по этой ссылке — https://proxy-sale.com/amerikanskie-proksi.html можно купить американские прокси, которые можно использовать для различных целей, будь то парсинг, соцсети, игры, или просто веб-серфинг.
Вы настраиваете свой собственный прокси-сервер? Есть ли у вас какие-либо другие советы или опыт по этому вопросу? Дайте нам знать в комментариях ниже!
Пишем HTTP proxy сервер с плагинами

Итак, задача:
Хочется, чтобы каптчу вводить не приходилось. Хоть если играешь сам, хоть если играет за тебя бот, если ты спишь.
Дополнительное условие: 40 часов времени (ибо паника на корабле).
Желательное условие: установочный файл под Windows.
Ещё одно желательное условие: результат должен занимать не более мегабайта.
Скажу сразу, что я не игроман, и даже наоборот, являюсь некоторым противником онлайн игр, и для внесения в эту отрасль дополнительной энтропии и решил взяться за это дело. Дело, возможно, могло принести определённую прибыль на волне появления каптчи и паники, связанной с этим, но не принесло по определённым причинам.
Итак, что же делать?
Попытка 1
Написать системную тулзу, которая бы перехватывала HTTP запросы и ответы от всёх установленных программ, и фильтровала бы те ответы, на которые бы требовалось ввести каптчу, вводя её самостоятельно. Над программой, которая в том числе должна была решать и эту задачу корпели примерно полгода два белорусских программиста, меняя платформу с C на C#, и потом на Java, и мирясь с тем, что им может понадобится установленный на машину OpenSSL. Задача каждый раз обрастала ненужными подробностями. Ну, в целом, не вышло.
Попытка 2: Сам, всё сам
Вполне понятно, что способов не так много, и выбор встал только между SOCKS прокси и HTTP прокси. Через некоторое время стало понятно, что SOCKS прокси поддерживают далеко не все пользовательские приложения, и выбор стал однозначным.
Выбор платформы
Выбор был не сложен, особенно учитывая попытку 1. C и C# были быстро отметены, учитывая полное отсутствие опыта. Были выявлены следующие, богатые необходимыми библиотеками, платформы:
Java Тяжело предположить, что для установки такой маленькой утилитки пользователи бы захотели устанавливать JVM, весящий страшно сказать сколько десятков мегабайт. Java отвалилась.
Python Как известно, работает везде и всё включено (batteries included). Весит 7Мб. По современным меркам, конечно, немного, но всё же хотелось компактнее. Остался вопрос, как этот инсталлятор внедрить в инсталлятор моей утилиты. Не знаю, как это делается у питоновских приложений, возможно, намного проще, но инсталлятор в инсталляторе я как-то раз уже делал и больше не хочу.
Ruby На момент начала моих поисков отсутствовал одношаговый инсталлятор под винду. Целиком и полностью. Сейчас есть, подразумевает установку MinGW, MSYS и прочего, при установке могущего испугать юзера. Вес 7Мб.
Про инсталлятор в инсталляторе вопрос остался.
Lua Очень давний и популярный среди скриптовщиков С++ игр язык. Вялое community, разрозненные библиотеки. Вес кастомной сборки VM в нужными библиотеками — всего 800Кб. Инсталлятор не предоставляется, есть набор exe файлов, которым в качестве параметра передаётся lua скрипт для его запуска. То, что нужно, так ещё и откомпилено под Win, MacOS, Linux, каждое из них в версиях 32 и 64 отдельно. То, что надо.
Итак, я взялся за изучение Lua (сбылось новогоднее пожелание, я изучил новый язык программирования).
Язык обладает чудесными свойствами, такими как:
— sandboxing (в ruby был только патч для версии 1.8.5): позволяет запускать сторонний код, ограничивая ему окружение;
— coroutines (типа ruby fibers из 1.9): позволяет сделать очень легковесную кооперативную многозадачность;
— очень простые (точнее, простая — есть только ассоциативный массив) структура данных, которой, как оказалось, хватает, для выполнения большинства задач по обработке данных;
… ещё много всего, тяжко так в одном посте.
Проще всего было сделать такую систему в виде фильтрующего запросы и ответы HTTP прокси сервера, что и было решено сделать (от добра добра не добра).
Ответ сервера нужно фильтровать, и делать это можно, если сервер не применяет HTTPS, а он по счастью его не применяет. Сделать это оказалось довольно просто, достаточным оказалось написать сокращённый до 190 строк Lua аналог mechanize для Ruby, который делает с заголовками и телом запроса что ни взбредёт в голову, позволяя писать какие хочется фильтры для HTTP запросов.
Ну, в этом случае нам надо было избавиться от вредной reCAPTCHA, для чего всего лишь потребовалось определить:
— к странице ли травиана щёл исходный запрос (и запрашивалась ли страница HTML):
string.find(request.uri(), ‘travian’) and mimetype and string.find(mimetype, ‘text/html’)
— содержалась ли на результирующей странице каптча вместо «полезных» игровых данных:
local captcha, captcha_key = string.match(response.body(), ‘
Как разгадывать собственно каптчу (с помощью не слишком современных и высокопроизводительных индусов, но чрезвычайно дешёвых живых индусов) немного выходит за рамки нашей сугубо технической темы, поэтому где-нибудь в другом месте.
В итоге, скачивалась картинка каптчи, отправлялась индусам (2 раза на всякий случай), полученные через 5-10 секунд ответы сравнивались, и если они были одинаковыми, то результат отправлялся травиану HTTP POST запросом, на что жертва выдаёт страницу, радостно сообщая, что мы, оказывается, человек, и кучу «полезных» игровых данных. Эту страницу мы и показываем ничего не подозревающему клиенту, который мог заметить лишь некоторую паузу. В пессимистичном случае несовпадения картинка отправлялась индусам ещё раз, и так далее до того момента, пока не получалось хотя бы два одинаковых решения, остальные отправлялись в службу поддержки на возврат денег (практически никакой халявы для индусов).
Итак, вот оно, решение и есть.
Однако, случилось то, о чём никто не мог предполагать. Пользователям зачем-то понадобился ещё и доступ к другим сайтам, немыслимо! Среди них оказались даже сайты, доступ к которым осуществлялся через HTTPS, и с этим нужно было что-то делать без регулярных переключений прокси включён-выключен.
И ещё нужно было, чтобы несколько запросов принимались одновременно. Неприятным оказалось то, что google analytics иногда делает запрос, длящийся минуты, оставляя однопоточную прокси в режиме ожидания.
Ну что же, для этого нашлось целых три разных библиотеки для создания асинхронного TCP сервера. То есть — ждём входящее соединение, получаем кусок данных, передаём управление диспетчеру, смотрим есть ли ещё входящие соединения или открытые соединения для которых есть данные (select/kpoll/epoll), передаём управление по очереди.
Увы и ах, поскольку все такого рода соединения происходят на локальной машине, то всё это происходит почти мнгновенно. А медленные соединения — исходящие. Подковырять существующие библиотеки (copas, asok), которые предназначены для мультиплексирования входящих соединений, оказалось сложнее, чем писать свою. И я написал небольшую (272 строки) свою. Помимо того, что все входящие и исходящие соединения работают асинхронно, так можно добавлять ещё любые корутины (поправьте меня, люди с профильным образованием) в пул, работающий в общем цикле.
Ну вот, всё стало работать параллельно, а по скорости лишь незаметно отставать от того, как это работает без прокси.
Как же велико было моё удивление, когда я получил от сервера страницу с (в том числе) заголовками:
Content-Encoding: gzip
Transfer-Encoding: chunked
и собственно полные кракозябры в качестве тела ответа.
Первая мысль была отключить в запросе Accept-Encoding, чтобы сервер не пытался паковать данные, и переделать HTTP 1.1 в HTTP 1.0, чтобы не посылало «чанками». Но подумал об падении скорости и увеличении трафика, и сжалился над пользователями.
Вышло так:
if headers(pipe, target)[‘Transfer-Encoding’] == ‘chunked’ then
target.body = dechunk(target.body)
end
function dechunk(chunkie)
local chunk_size
local chunk
local chunks = <>
chunkie, chunk_size = readline(chunkie)
while chunk_size and tonumber(chunk_size, 16) > 0 do
chunkie, chunk = readbytes(chunkie, tonumber(chunk_size, 16))
return table.concat(chunks)
end
Ушёл читать матчасть. Слава богу, по этим пунктам документации порядочно.
Склеиваем «чанки», получаем gzip файл (иногда deflate, но мне не встречался пока). Распаковываем (спасибо David Manura за библиотеку).
Распаковка вышла ещё проще:
if headers(pipe, target)[‘Content-Encoding’] == ‘gzip’ and #target.body > 0 then
local decoded = <>
gzip.gunzip
target.body = table.concat(decoded)
end
Осталось немного
— сделать HTTPS-туннелирование для HTTPS сайтов (слава богу, OpenSSL бандлить не надо, просто данные прозрачно передавать туда-сюда):
if request.method() == ‘CONNECT’ then
local sent_to_server, err = client.send(«HTTP/1.0 200 Connection established\r\nProxy-agent: BotHQ-Agent/1.2\r\n\r\n»)
print(‘https transparent connection’)
https(client, server)
return
end
local function https(client, server)
close_callback = function()
client.close()
server.close()
end
client.receive_subscribe(function(data)
server.send(data)
end, close_callback)
server.receive_subscribe(function(data)
client.send(data)
end, close_callback)
end
— положить в инсталлятор:
В целом приключения с запуском 7zip sfx на Heroku стоят отдельного поста. Радость победы затмевает любые сложные моменты разработки.
Ну что же, не знаю, как вам, а мне было интересно и увлекательно этим заниматься. Не жалею потраченного времени.
В итоге:
Сам прокси сервер на 71 строку здесь.
Асинхронная библиотека TCP-сервер-клиент на 272 строки тут.
Некий аналог HTTP клиента на 190 строк.
Фильтр для разгадывания каптчи на 150 строк тут.
Установочный файл размером менее мегабайта.
