Archaeological Review: как я настраивал VPN-шлюз на роутере 2012 года выпуска
Или будь проклят тот товарищ из комментов на Хабре, который сказал, что каждый может поднять свой VPN за вечер, зачем нужна Амнезия.
Tldr: я не сетевой инженер, не разработчик ядра и даже не системный программист. Я простой парень с 11 классами образования и 10-летним опытом эпизодических попыток войти в айти. Но иногда этого оказывается достаточно для того, чтобы починить Ютуб (ну, почти) на древнем телевизоре посредством настройки VPN на не менее древнем роутере, если есть время, желание разобраться и хороший собеседник в виде бесплатной версии ChatGPT.
Об этом и пойдёт сказ в этой статье.
Итак, есть у меня роутер 2012 примерно года покупки — Asus RT-N66U “Dark Knight”.
Есть телевизор, которому примерно столько же лет — Sony Bravia KDL-42W828B.
Вернее, не у меня, а у моих родителей.
И на этом телевизоре есть Ютуб!
Вернее, был когда-то.
Ютуб (его приложение) есть, а вот полноценного Android TV с магазином приложений на него не завезли - хотя сам он своим интерфейсом очень похож на более современных представителей именно с этой операционной системой. Чую - предок.
В общем.
Приложение VPN не поставить.
Может, можно перепрошить на более современную версию с магазином приложений, но, во-первых, я без понятия, как обстоят в мире дела с альтернативными прошивками для телевизоров, а во-вторых, даже если они существуют в природе, всё равно есть ненулевой риск запороть рабочий телевизор, на котором уже есть любимые сериалы, новости и спорт. Ну, в общем, всякий мозговыедающий яд.
Давно я грезил о том, как решить эту проблему, но поизучал вопрос, насколько я помню, и плюнул.
Но вот недавно мы снова при разговоре с отцом его подняли, я рассказал ему, что люди покупают себе роутеры, на которых настроен VPN, и пускают через них устройства, для которых VPN недоступен. “Я бы и сам мог бы попробовать такой сделать, был бы роутер” , — сказал я. На что папа ответил, что, вообще-то, есть один роутер завалявшийся (а мб даже не один), можно над ними ставить эксперименты, среди них тот самый “тёмный рыцарь”
И заверте…
Уровень красноглазия OVER9000.
Сначала я установил прошивку номер раз. Она называлась merlin-wrt. Вообще, у меня на слуху была OpenWrt, я хотел установить её, но я загуглил свою модель роутера и выяснил, что на сайте OpenWrt не рекомендуют устанавливать их прошивку на мой роутер из-за потенциальных проблем совместимости с моим процессором: OpenWrt на то и open, что использует только открытые драйвера, а производитель чипсета для моего роутера Broadcom предоставляет только проприетарные драйвера по ограниченной лицензии. В общем, на вики OpenWrt порекомендовали устанавливать ddwrt.
В вики же ddwrt был подробно описан процесс перепрошивки, но одним из пунктов, который следовало перед прошивкой выполнить, было (sic!) установить ещё одну прошивку, более легко шьющуюся, и к ней подключиться по ssh и посмотреть кое-какой параметр системы, от которого зависело, какую именно ветку пути установки дальше выбирать.
В общем, установил я этот merlin-wrt, подключился по ssh… И подумал: “А нафиг мне ddwrt, когда у меня уже есть доступ по ssh?”
Наивный…
Так вот, подумал я, что можно попробовать скачать Xray на имеющуюся систему, раз доступ по SSH есть.
Но проблема номер раз: встроенной постоянной памяти файловой системы jffs на этом роутере доступно только 8МБ. А современное ядро Xray занимает около 30 МБ! В общем, не вариант, подумал я, и решил попробовать установить туда zapret — благо, основная цель — разблокировка Ютуба, и даже на моём мобильном провайдере аналогичное решение — ByeByeDPI — спокойно обходит блокировку. Ну и, бонусом — полный установочный бандл zapret под мою архитектуру занимает… Около 3 МБ!
В общем, довольный, я попытался скачать zapret. С Гитхаба. Вот только… Корневые HTTPS-сертификаты в прошивке давно протухли, а большинство современных сайтов в принципе отказываются работать по нешифрованному http, поэтому пришлось качать zapret на компьютер и с него по scp заливать на роутер.
“Естественно”, поставляемый автором zapret’а скрипт установки не смог заработать на этом роутере, т.к. скрипт не мог определить, какая же архитектура у моего процессора — пришлось ковырять этот скрипт и воспроизводить шаги вручную.
Мытьём, катанием удалось собрать в папке окружение, которое авторский скрипт blockcheck.sh (для определения типов блокировок у моего провайдера и подбора контр-стратегии) счёл бы минимально рабочим — но, к сожалению, выяснилось, что без поддержки ядром директивы NFQUEUE iptables ловить с zapret’ом нечего. Почему ядро не поддерживает эту директиву? Так оно версии 2.6.22! Которая вышла в далёком 2007 году… Верните мне мой 2007!
К этому моменту я уже узнал об Entware — проекте, позволяющем докачивать полноценное Linux-окружение на флешку, если таковая поддерживается роутером - о счастье, в нём есть два порта USB. Это означало, что мои 8 МБ памяти можно расширить потенциально хоть до 1ТБ! Ох уж эти флешки Kinkstone за 500 рублей…!
Но с Entware ещё надо было разбираться, как его ставить, плюс вторая мажорная версия ядра меня печалила, поэтому я решил попробовать всё-таки просто запустить Xray, благо оперативная память в этой прошивке была примонтирована как файловое хранилище, доступное для записи.
И тут возникла новая проблема, которая почему-то (по счастливой случайности?) прошла мимо меня, когда я пытался работать с zapret (подозреваю, дело в том, что до запуска бинарников там дело просто не дошло, всё поломалось ещё на этапе вызовов обёрточных скриптов): когда я запустил свежескачанный бинарник архитектуры mips (которая заявлялась в uname -r и /proc/cpuinfo), он просто был проинтерпретирован как shell-скрипт! Кайф, конечно… Ломал я какое-то время голову, изучал у себя на компе ELF-заголовки файлов Xray, в какой-то момент пришла светлая мысль — дай-ка я скачаю с роутера какой-нибудь исполняемый файл и посмотрю заголовки у него! Скачал, посмотрел… И действительно! Оказывается, mips — недостаточно точное описание архитектуры: важен ещё порядок байтов. Мой роутер оказался MIPS little-endian, а скачанный мной бинарник был собран для MIPS big-endian. Почему-то в артефактах релизов Go-проектов кто-то решил называть бинарники не mipsbe и mipsle, а mips без суффикса и mipsel. Ещё одно археологическое изыскание, в которое я решил не вдаваться.
В общем, скачал я бинарник правильной архитектуры и он… Запустился! Но почти сразу при старте программы вывалился с ошибкой. При этом трейс ошибки показывал все следы рабочего окружения Go, на котором написан Xray, так что это был значительный прорыв в этом “расследовании”.
Но вот сама ошибка… Ядро выдавало ошибку при системном вызове futex к нему. Из чего был сделан простой вывод (хотя и, возможно, скоропалительный, как выяснилось позже) — пора менять ядро! На очереди ждала прошивка ddwrt.
На самом деле, сам процесс шитья не такой уж и сложный для данного роутера — по сути, нужно просто обновить на нём прошивку через штатный веб-интерфейс правильным файлом, попутно совершая ритуальные действия вроде сброса до заводских настроек до и после обновления.
Следующим препятствием оказался интерфейс ddwrt. Как на нём сделать то же самое, что и на merlin-wrt? Ну, слава богу, ssh на этой прошивке поддерживал ed25519 шифрование, с первого раза мой основной приватный ssh-ключ подошёл. Правда, пока я настраивал роутер в режиме моста относительно моего основного домашнего роутера, я умудрился как-то потерять доступ к его обыкновенному IP в локальной сети, поэтому пришлось ещё разочек сбрасывать настройки.
Но вот мост поднят, гостевая сеть настроена, можно логиниться. И что я вижу? Все системные папки прошивки находятся на readonly разделе, и я фактически не могу ничего скачать на этот роутер. Jffs, которая спасла меня на Мерлине, не оказалось в списке поддерживаемых модулей ядра. Ну, что делать - значит, настал час Entware и наведения шороху на флешке.
О, с флешкой был отдельный цирк. Например, файл /proc/filesystems не показывал наличия поддержки ext4 или ntfs, но каким-то чудом, когда я в первый раз вставил ntfs флешку, она распозналась и примонтировалась (как потом мне подсказал опытный товарищ, в истории ядра Linux был отдельный цирк с имплементацией драйвера NTFS, поэтому актуальный модуль ядра для этой системы называется по-другому, хоть и содержит *ntfs* в названии). CA-сертификаты в этой прошивке тоже отсутствовали, поэтому снова пришлось залить Xray по scp. И… Снова неудача! Точно такая же ошибка futex. Я посмотрел версию ядра - на это раз она была 4.4.302 (на самом деле, это было первое, что я посмотрел, впервые подключившись по ssh).
Значит, конкретно эта ошибка была связана не с версией ядра напрямую! Или с ней, но ядро всё равно слишком старое — всё-таки, как-никак, сейчас седьмая мажорная версия уже марширует по планете.
В итоге ставка была сделана на Go runtime - из разряда, слишком новый Go ожидает слишком нового ядра, а что, если запустить Xray, собранный старым Go, например, самую первую стабильную версию, 1.0.0?
Но тут наступил следующий день, и ntfs флешка отказалась монтироваться. В /proc/filesystems и в списке поддерживаемых модулей ядра я искал модуль ntfs по точному совпадению, поэтому я сделал вывод, что всё-таки NTFS не поддерживается (опытный товарищ рассказал про эту фишку с названиями уже потом, когда всё было сделано).
Итак, у меня машина Windows, и ntfs (как казалось) не поддерживается роутером, что делать? В /proc/filesystems я увидел строчку vfat, и поэтому решил форматировать флешку в FAT32! Флешка распозналась успешно, я смонтировал её вручную в папку /opt, встроенный в ddwrt механизм автомонтирования устройства по uid в эту папку почему-то отказывается работать, поэтому пришлось писать дополнительный скрипт, который это делает. Я поставил entware на эту флешку и он… Вроде заработал, но периодически сыпал ошибками. Лёгкий гуглинг показал, что, скорее всего, дело в файловой системе: всё-таки Linux ожидает ext*, а поддержка FAT32 у него всего лишь для совместимости с msdos-миром. И вот именно тут я догадываюсь проверить модули ядра и обнаруживаю, что всё-таки модуль поддержки ext2+ в нём есть! Дело сделано, осталось переформатировать флешку в ext4…
Но у меня всё ещё Windows. И все флешки потенциально нужные, есть только одна свободная, на которой я ставлю эксперименты. Т.е. скачать mke2fs через Entware можно, но вот форматировать примонтированную флешку программой, которая на этой флешке записана… Не, ну чисто теоретически, если вся программа помещается в оперативную память и больше не пытается ничего читать из своего расположения - почему бы и нет, казалось бы. Но я таких гарантий дать не мог, поэтому было решено не использовать роутер для форматирования.
“Самый удобный способ”, Live USB. Ну да, особенно когда флешка одна, очень удобно. Да и как-то лениво (внезапно, да?) было разбираться, как выйти в интерфейс UEFI моей системы и загрузиться с флешки, я решил пойти по, казалось бы, простому пути — у меня же на Windows есть WSL, а WSL есть Linux, почему бы не использовать его?
Узнал про хак с usbipd, с помощью которого можно пробрасывать USB устройства в WSL. Смотрю в dmesg — девайс подключается, а разделы не распознаются и не монтируются! Если бы не ChatGPT, долго бы я выяснял, в чём причина. Но тот мне подсказал, что в стоковом ядре на WSL — surprise! — отсутствуют модули поддержки USB-хранилищ. Что, наверно, в определённом смысле логично. Но “хорошая” новость — можно собрать своё ядро и подгрузить его вместо дефолтного через конфиг WSL.
Собирать ядро мне не хотелось, я решил ещё немного погуглить. Stackoverflow с подобным вопросом подкинул инструкцию, как собрать ядро, но в комментариях мелькнула ссылка на репозиторий, который уже занимается сборкой свежих ядер с batteries included для WSL, и в итоге всё, что мне на этом поприще пришлось сделать — это скачать новое ядро и виртуальный диск с дополнительными модулями ядра. А, ну и обновить WSL, который я, оказывается, никогда не обновлял, поэтому подмену ядра он даже не поддерживал.
И вот, я подключаю устройство через usbipd, монтирую его и наконец-то форматирую при помощи mkfs! Ура!
Возвращаемся к роутеру.
Следующий этап - установка entware заново. На этот раз я решил сделать всё цивильно и сразу настроить поддержку https. Но без предварительной установки curl по простому http, к сожалению, ничего не получилось, поэтому привет тому, кто подменил мне curl и установил руткит на мой роутер, муахахахаха.
В общем, убедился я, что entware может сам ходить по https. И знаете. С этого момента дела пошли в гору.
Xray теперь получилось скачать прямо на роутер. Версия 1.0.0 успешно запустилась! Методом опроса ЧатаГПТ выяснилось, что версией, которая уже поддерживает Reality, но ещё работает на старых устройствах, является 1.8.4. И, о совпадение, в release notes было написано, что это последняя версия Xray, которая поддерживает Windows 7 и другие “старые” системы — в общем-то, по простой причине, что в самом Go при переходе в районе версий 1.20.х и 1.21.х было решено отказаться от поддержки этих систем.
Я скачал Xray версии 1.8.4 — он запустился! Я скачал Reality конфиг нашего ВПН и запустил его вместе с Xray на роутере — он снова запустился! Наконец, я при помощи curl выполнил запрос к ifconfig.me, указав Xray в качестве прокси — и он показал выходной IP моего зарубежного сервера!
Ура, почти вся цепочка уже работала — оставалось только реализовать то, ради чего всё затевалось — реализовать прозрачное проксирование всех устройств в гостевой сети через Xray.
Тут возникла следующая проблема — ни в новом ядре, ни в старом, не было поддержки TPROXY правил для iptables. Зато была поддержка правил REDIRECT. Оставалось выяснить, может ли Xray с таким ядром Linux узнавать исходный адрес назначения пакетов, который в них кладут клиенты, не подозревающие о существовании VPN. Настроил инбаунд dokodemo-door, правила iptables, как в открытом туториале по REDIRECT — и о чудо, всё заработало… Ха-ха, нет. Вернее как. Пакеты пошли на Xray. Вот только выйти с него в интернет они не могли — они снова заворачивались теми же правилами на Xray. Пришлось добавить IP моего VPN сервера и DNS сервера Cloudflare в исключения, после чего всё наконец заработало… А нет. Вернее как. Теперь любой curl с самого роутера показывал, что трафик идёт через VPN, да и логи Xray об этом радостно сообщали. Вот только трафику гостевой сети было побоку. Не верьте конфигам из интернета!
В общем, оказывается, надо было добавить правило PREROUTING для интерфейса моста, по итогам применения которого… Всё наконец ДЕЙСТВИТЕЛЬНО заработало!
Я не верил своим глазам. Я даже с двух устройств проверил. Единственное… Тёк DNS. Всё-таки DNS по UDP передаётся, а REDIRECT правила работают нормально только для TCP. Но! Основная цепочка, этакий minimal viable product, уже была отработана. Я постарался всё задокументировать, теперь оставалось только настроить перехват DNS через прошивку роутера.
По сути ddwrt поставляет dnsmasq вместе с прошивкой, поэтому я обрадовался, что мне не придётся настраивать перехват через iptables или через настройки роутинга Xray. Я установил dnscrypt-proxy через пакетный менеджер entware, список секьюрных DNS был устаревшим, поэтому я скачал актуальный из репозитория на GitHub, и теперь пользуюсь шведскими серверами Cisco.
И вот, казалось бы, эпопея закончена. Я подключил телевизор к роутеру (я его настроил в режиме репитера с отдельной гостевой сетью. Долго ломал голову о простой факт, что два роутера не могут нормально маршрутизацию выполнять в одном и том же диапазоне 192.168.1.0/24), запустил на нём приложение Ютуб, и — о счастье! — оно запустилось и у меня даже получилось войти в Google-аккаунт. Только вот беда… Приложение крашалось с ошибкой “Недостаточно памяти” при попытке загрузить главную страницу Ютуба.
Я попробовал поднять веб-сервер в сети роутера, чтобы через встроенный в телевизор браузер смотреть ролики через простую веб-страницу — например, можно было бы сделать сервер, который принимает на вход ссылку на видео, скачивает его при помощи условного yt-dlp, и публикует его на локальном сервере. Но, увы, сколько я ни игрался с кодеками видео, подобрать такой, чтобы браузер его воспроизводил, у меня не получилось.
Последняя надежда, которая оставалась — это поднять DLNA-сервер, на котором можно хостить видео в таком же стиле, что и на веб-сервере, но смотреть через встроенный в телевизор просмотрщик этого протокола. Но, кажется, что на моменте, когда мне нужно было пробрасывать порты из docker-контейнера minidlna, запущенного в WSL, в общую сеть так, чтобы телевизор мог к нему свободно обращаться, я слегка сдался. Особенно когда папа сказал, что на самом деле всё это время телевизор поддерживал технологию Miracast, если подключаться к встроенному в него Wi-Fi.
Напоследок ещё я понял, что есть ещё одна маааленькая проблема: роутер находится у родителей, а лазить каждый раз к нему физически мне совершенно не хотелось.
Поэтому я решил сделать ему удалённый доступ.
Встроенного удобного способа сделать reverse SSH tunnel с автоматическим переподключением у меня под рукой не оказалось. Поэтому я, недолго думая, написал свою реализацию autossh на Bash. Она следит за SSH-туннелем, при обрыве поднимает его заново и через reverse forwarding пробрасывает на мой VPS SSH-порт роутера и его веб-интерфейс.
То есть теперь мой древний роутер 2012 года, стоящий у родителей, самостоятельно устанавливает SSH-соединение с зарубежным VPS и предоставляет мне через него удалённый доступ к своей веб-морде и SSH. Разумеется, всё закрыто за Caddy с https через Let’sEncrypt, с рейт-лимитом и базовой http-аутентификацией.
Всё это ради того, чтобы мама могла смотреть YouTube на телевизоре.
Возможно… Ну так, чисто гипотетически… Что я выбрал всё-таки слегка (самую малость) избыточный способ решения задачи.
А главное, своей первоначальной цели — дать маме возможность смотреть спокойно Ютуб на телевизоре — я так и не достиг :( Но мне пришлось узнать столько всяких разных вещей, что я не особо жалею об этом. Плюс, может, когда-нибудь я вернусь к идее с DLNA-сервером.
ЧатГПТ меня постоянно нахваливал и поддерживал меня в этих приключениях, и даже предложил этот опыт описать и опубликовать здесь. Ну и, разумеется, служил как источник вдохновения опций и дальнейших шагов, которые если бы я пытался самостоятельно искать по документации, то никогда бы не сдвинулся с мёртвой точки.
Если вам показалось, глядючи на эту историю, что вам в команде нужен такой странный чел, который может упорото упорно почти неделю копаться в куче незнакомого и скудно документированного кода или конфигов, то я открыт к предложениям!