Прозрачный обход блокировок в домашней сети
автор оригинала: Zolg
Прозрачный обход блокировок в домашней сети: ремейк гайда 2015 года
Архивное переиздание с полной переработкой. Оригинал «Прозрачный обход блокировок в домашней сети» (Zolg, 2015) скрыт на Habr — доступен через Wayback Machine. В 2015-м это была экзотика для гиков с OpenWrt; в 2026-м — самый массовый сценарий в РФ: «поставил на роутер, чтобы дома всё работало само». Идея оригинала («в туннель — только заблокированное, остальное напрямую») не просто жива, она стала стандартом. Ниже — как собрать её на железе и софте 2026 года. Оригинальный текст — в конце, без купюр.
TL;DR — версия 2026
Схема та же: роутер → политика «заблокированные домены/IP в туннель, остальное напрямую». Заменить нужно три вещи: OpenWrt 15 → 24.10/25.x (DSA), iptables → nftables, OpenVPN/Tor → sing-box/Xray в TUN-режиме с VLESS+REALITY или ss2022 (голые VPN-хендшейки режут ТСПУ). Плюс обязательный четвёртый пункт, которого в 2015-м не было: свой DoH/DoT на самом роутере и явная политика для IPv6/QUIC.
Почему идея 2015 года оказалась вечной
Оригинал формулировал три ограничения, которые не устарели:
- Не гнать весь трафик через VPN — скорость проседает, сервисы дороги, а «бесплатные» логины кончаются;
- Не гнать через Tor — медленно и торренты через Tor — плохая идея;
- Не обеспечивать анонимность — «любая внешняя ссылка раскрывает настоящий IP», писал автор, и был прав: политика маршрутизации ≠ приватность.
Поэтому «прозрачный» ≠ «весь трафик в туннель». Прозрачный — значит пользователь ничего не настраивает: роутер сам решает, куда пакет. В 2026 к этому добавился четвёртый принцип: решение принимает роутер, а список «что считать заблокированным» обновляется сам (иначе вы статонетесь администратором собственного дома).
Что изменилось в железе и софте
| 2015 | 2026 |
|---|---|
OpenWrt Chaos Calmer (15.05), netfilter/iptables |
OpenWrt 24.10/25.x, DSA, nftables, luci-app-sing-box |
| «поднять на роутере VPN по инструкции провайдера» | чужой VPN-сервис в РФ заблокирован: 258 сервисов за 2025 год |
| Tor / OpenVPN | AmneziaWG, VLESS+REALITY, ss2022 за CDN |
| реестр заблокированных — вручную | community-листы (antifilter-подобные) + cron-обновление |
| DNS от провайдера | DoH/DoT на роутере — обязательно |
| «заблокировали по IP» | блокируют по IP, SNI, DPI и «замедляют» по UDP/443 |
| IPv6 почти не встречался | IPv6 у части провайдеров не фильтруется — это и дыра, и шанс |
Ещё одно изменение не техническое: почти все VPN-провайдеры, на чьих «пошаговых инструкциях для OpenWrt» строился оригинал, в РФ либо заблокированы, либо недоступны для оплаты. Значит туннель теперь свой: VPS за $3–5/мес и 10 минут на конфиг.
Архитектура 2026
[ телефоны / ТВ / ноутбуки / гости ]
│ DHCP: DNS = 192.168.1.1 (роутер)
▼
┌─────────────── OpenWrt 24.10 ───────────────┐
│ dnsmasq → AdGuard Home/https-dns-proxy(DoH)│
│ sing-box (TUN) + routing-политика │
│ nftables: mark по address-list │
│ cron: обновление списка доменов 1×/сутки │
└───────────────┬─────────────────────────────┘
│ «заблокированное» → VLESS/REALITY (свой VPS)
│ остальное → напрямую к провайдеру
▼
Интернет
Ключевое отличие от 2015-го: в туннель уходит не «весь трафик», а только то, что в списке, и туннель при этом маскирован — DPI не видит ни хендшейка, ни « VPN-подозрительного » потока.
Сборка: пять шагов
Шаг 1. Прошивка и база
OpenWrt 24.10 или 25.x под ваше железо; для «из коробки» подходят
GL.iNet (Beryl AX / Slate AX) с предустановленным OpenWrt и поддержкой
AmneziaWG. Первым делом — сменить пароль, обновить opkg update,
поставить luci-app-sing-box/sing-box, https-dns-proxy (или
AdGuard Home в контейнере на ARM-моделях), curl, nftables.
Шаг 2. DNS на роутере (обязательно)
opkg install https-dns-proxy luci-app-https-dns-proxy
# в LuCI: Network → DHCP and DNS → Forwarder = 127.0.0.1#5053
# Services → HTTPS DNS Proxy → upstream = Cloudflare/AdGuard DoH
Зачем: если клиент резолвит через провайдера, «прозрачная» политика разваливается — домен из списка вернёт подменённый IP и маркировать будет нечего. IPv6-RA тоже не должен раздавать провайдерский DNS.
Шаг 3. sing-box в TUN-режиме
/etc/sing-box/config.json (упрощённо, outbound — ваш VPS):
{
"inbounds": [{ "type": "tun", "tag": "tun-in",
"address": ["172.19.0.1/30"],
"auto_route": false, "strict_route": false }],
"outbounds": [
{ "type": "vless", "tag": "proxy",
"server": "<VPS>", "server_port": 443, "uuid": "<uuid>",
"flow": "xtls-rprx-vision",
"tls": { "enabled": true, "server_name": "www.microsoft.com",
"utls": { "enabled": true, "fingerprint": "chrome" },
"reality": { "enabled": true, "public_key": "<key>",
"short_id": "<sid>" } } },
{ "type": "direct", "tag": "direct" }
],
"route": {
"rules": [
{ "rule_set": ["blocked-domains"], "outbound": "proxy" },
{ "network": "udp", "port": 443, "outbound": "direct" }
],
"rule_set": [{ "tag": "blocked-domains", "type": "remote",
"format": "binary",
"url": "<ваш .srs-список>",
"update_interval": "1d" }],
"auto_detect_interface": false,
"default_outbound": "direct"
}
}
Важно auto_route: false: маршрутизацию делает nftables (шаг 4), а не
sing-box — иначе «прозрачность» превратится в «весь трафик в туннель»,
от чего автор оригинала предостерегал ещё в 2015-м.
Шаг 4. nftables: маркируем только нужное
table inet policy {
set blocked4 { type ipv4_addr; flags interval; }
set blocked6 { type ipv6_addr; flags interval; }
chain prerouting {
type filter hook prerouting priority mangle; policy accept;
ip daddr @blocked4 meta mark set 1
ip6 daddr @blocked6 meta mark set 1
tcp dport 443 meta mark set 1 # SNI-блокировки тоже уходят в туннель
}
}
ip rule add fwmark 1 lookup 100
ip route add default dev tun-in table 100
Списки @blocked4/@blocked6 наполняются cron-скриптом: тянем
community-лист доменов → резолвим через DoH → обновляем set’ы через
nft -f. Раз в сутки — достаточно.
Шаг 5. QUIC, IPv6, MTU
- QUIC/UDP-443: либо режем его (
nftreject udp dport 443), чтобы браузер ушёл в TCP, который мы маркируем; либо тоже отправляем в туннель — зависит от провайдера. Проверить просто: youtube в браузере сchrome://flagsи без. - IPv6: если провайдер его выдаёт, политика должна быть симметричной, иначе «заблокированное» откроется по v6 мимо туннеля (или наоборот — v6 станет бесплатным обходом).
- MTU: 1280 на TUN-интерфейсе +
tcp-mss-fixв nftables — типичная причина «сайт открывается, но картинки не грузятся».
Проверка, что схема честно «прозрачная»
- С телефона (без всяких настроек) открыть заблокированный домен — должен открыться.
curl ifconfig.meс того же телефона → ваш реальный IP, а не VPS (это и есть «остальное напрямую»).curl ifconfig.me --interfaceс роутера через mark 1 → IP VPS.tcpdump -i tun-in— трафик идёт только по списку, остальное мимо.digс клиента → DNS роутера (192.168.1.1), а не провайдера.
Пункт 2 — лучшая проверка того, что вы не построили «ещё один always-on VPN», против которого автор оригинала спорил всю статью.
Ограничения (честно, как в оригинале)
- схема не даёт анонимности: внешние ссылки, cookies, WebRTC по-прежнему палят настоящий IP —原文 писал это жирным, и в 2026 это правда;
- DPI-блокировки «по статистике» и чистые IP-баны списком не лечатся — для них нужен ByeDPI на клиенте или смена IP VPS;
- списки отстают от волн блокировок на часы — держите cron и локальную копию;
- роутер должен тянуть TUN по CPU: 128 МБ ОЗУ и MT7621 хватает на 100 Мбит, для гигабита берите ARM/фильтр-коробку.
Что изменилось с 2015 года
- OpenWrt 15 → 24.10/25.x: DSA вместо
switch_vlan, nftables вместо iptables, LuCI-приложения для sing-box - чужой VPN-провайдер → свой VPS (258 сервисов заблокированы только за 2025 год, оплата картами РФ невозможна)
- OpenVPN/Tor → VLESS+REALITY / ss2022 / AmneziaWG; голые хендшейки режутся ТСПУ
- ручные списки → обновляемые rule-set’ы (
.srs) с cron’ом - DNS и SNI стали полем боя: без DoH на роутере политика не работает
- добавились IPv6 и QUIC/UDP-443 — в 2015-м их можно было игнорировать
Чек-лист 2026
- свой DoH/DoT на роутере, DHCP раздаёт только 192.168.1.1
- туннель маскирован (REALITY/ss2022/AWG), не «голый» WG/OpenVPN
- в туннель уходит ТОЛЬКО список, остальное — напрямую
- rule-set обновляется раз в сутки, есть локальная копия
- QUIC и IPv6 явно решены (а не «само получилось»)
- MTU 1280 + MSS-clamp
-
ifconfig.meс устройства = ваш IP (проверка прозрачности)
Вердикт
Автор 2015-го защищал простую мысль: «не платите скоростью и деньгами за то, что можно маршрутизировать точечно». Одиннадцать лет спустя ТСПУ, замедления и 258 заблокированных сервисов сделали его правоту очевидной: полностью-VPN-дом — это дорого и медленно, always-on-Tor — невозможно, а точечная политика на роутере — ровно то, что ставят сейчас. Мы заменили три буквы в конфигах, а не идею.
ОРИГИНАЛ СТАТЬИ (без изменений)
Последние новости в очередной раз заострили проблему блокировок интернет-ресурсов. С одной стороны о способах их обхода написано немало, и пережевывать эту тему в очередной раз казалось бы незачем. С другой, регулярно предпринимать какие-то дополнительные действия для посещения нужного ресурса — это не совсем то, что должно удовлетворить айтишника (и не всегда то, с чем может справится человек к айти неблизкий).
Нужно простое и прозрачное для пользователей решение, которое, будучи единожды настроенным, позволит просто пользоваться интернетом, не задумываясь, что же сегодня заблокировали по заявкам очередных копирастов-плагиаторов.
Сама собой напрашивается мысль о том, чтобы обходить блокировку уже на домашнем маршрутизаторе.
[]
Собственно, поднять на маршрутизаторе и гонять весь траффик через VPN несложно, а у некоторых VPN-провайдеров есть даже пошаговые инструкции по настройке OpenWrt на работу с ними.
Но скорости VPN сервисов все же отстают от скоростей доступа в интернет, да и VPN-сервис либо стоит денег, либо имеет массу ограничений, либо необходимость регулярного получения новых логинов. С точки зрения оптимизации затрат, как финансовых, так и временных, предпочтительней выглядит Tor, но его скорость еще хуже, а гонять через Tor торренты и вовсе идея не лучшая.
Выход — перенаправлять в VPN/Tor только траффик блокируемых ресурсов, пропуская остальной обычным путем.
Внимание: данная схема не обеспечивает анонимности просмотра заблокированных сайтов: любая внешняя ссылка раскрывает ваш настоящий IP.
Конкретная реализация на OpenWrt приведена в конце статьи. Если не интересуют подробности и альтернативные варианты решения, то можно листать сразу до нее.
Туннелирование и перенаправление траффика в туннель
Настройка VPN или Tor’а сложностей представлять не должна. Tor должен быть настроен, как прозрачный proxy (либо настроить связку из tor и tun2socks). Т.к. конечной целью явлется обход блокировок ркн, то в конфиге Tor’а целесообразно запретить использование выходных узлов на территории РФ (<ExcludeExitNodes {RU}).
В Tor’а траффик перенаправляется правилом с REDIRECT’ом на порт прозрачного прокси в цепочке PREROUTING таблицы nat netfilter’а.
Для перенаправления в VPN (или Tor + tun2socks) траффик маркируется в таблице mangle, метка затем используется для выбора таблицы маршрутизации, перенаправляющей траффик на соответствующий интерфейс.
В обоих случаях для классификации траффика используется ipset с хостами, подлежащими (раз)блокировке.
Формирование ipset c (раз)блокируемыми хостами
К сожалению, вариант «загнать все IP из реестра» в ipset не работает как хотелось бы: во-первых в списках присутствуют не все IP адреса блокируемых хостов, во-вторых в попытке уйти от блокировки IP адрес у ресурса может измениться (и провайдер об этом уже знает, а мы – еще нет), ну и в третьих – false positives для находящихся на том же shared hosting’е сайтов.
Городить огород с dpi того или иного вида не очень хочется: как-никак работать это должно на довольно слабом железе. Выход достаточно прост и в какой-то степени элегантен: dnsmasq (DNS сервер, который на маршрутизаторе скорее всего уже установлен) умеет при разрешении имен добавлять ip-адреса в соответствующий ipset (одноименная опция в конфиге). Как раз то, что нужно: вносим в конфиг все домены, которые необходимо разблокировать, и дальше по необходимости dnsmasq сам добавляет в ipset именно тот ip адрес, по которому будет идти обращение к заблокированному ресурсу.
У меня были сомнения, что dnsmasq запустится и будет нормально работать с конфигом в полдесятка тысяч строк (примерно столько записей в реестре после усушки и утряски), однако они к счастью оказались безосновательны.
Ложка дегтя в том, что при обновлении списка dnsmasq придется перезапускать, т.к. по SIGHUP он конфиг не перегружает.
Составление списка доменов
Должно происходить автоматически, насколько это возможно.
Первый вариант (который и реализован в примере): формировать список на основе единого реестра блокировок и обновлять его по cron’у.
Роскомнадзор широкой общественности реестр блокировок не предоставляет, однако мир не без добрых людей и есть минимум два ресурса, где с ним можно ознакомиться. И, что отлично, API у них тоже имеется. При разборе списка нужно учесть, что в списке доменных имен помимо собственно доменных имен присутствуют и IP адреса. Их нужно обрабатывать отдельно (или вообще на них забить: их примерно 0,1% от списка и врядли они ведут на интересующие вас ресурсы). Кириллические домены далеко не всегда представлены в punycode. Немалую часть списка занимают поддомены на одном домене второго уровня, указаны домены с www/без www и просто дублирующиеся записи. Все перечисленное в большей степени относится к списку от rublacklist.net (он в добавок еще и странно, местами некорректно, экранирован). Именно для него пришлось городить монструозный lua-script (приводится ниже), нормализующий и сжимающий список почти в два раза. C antizapret.info ситуация сильно лучше и можно было бы обойтись однострочником на awk.
Можно пойти другим путем: многие провайдеры при обращению к заблокированному ресурсу перенаправляют на заглушку об ограничении доступа. Например http://block.mts.ru/?host=/host/&url=/url/¶ms=/params/. Подменив с помощью того же dnsmasq (address=/block.mts.ru/192.168.1.1) A-запись block.mts.ru на адрес веб-сервера маршрутизатора (и разместив на нем несложный скрипт) можно локально формировать список запрошенных пользователями сети заблокированных ресурсов, добавлять их в конфиг dnsmasq, повторно делать nslookup (чтобы ip адрес добавился в ipset) и еще раз редиректить пользователя на первоначальный URL. Но необходимость каждый раз при этом перезапускать dnsmasq несколько расхолаживает. Да и работать будет только для http.
Теперь еще об одной ложке дегтя: некоторое провайдеры замечены за тем, что помимо включенных в список ркн сайтов самодеятельно блокируют и официально в списках не значащиеся. При этом блокируют тихой сапой и заглушки не выводят. Так что совсем без ручного привода не обойтись.
Дополнительные замечания
DNS серверы провайдера использовать в качестве апстрим серверов естественно не стоит. Ибо блокировка может произойти еще на стадии разрешения имени ресурса. Отдаст сервер провайдера на искомый адрес, что это CNAME block.mts.ru и все. Наиболее простое решение server=8.8.8.8, server=8.8.4.4. Модификации провайдерами DNS-ответов сторонних серверов лично я пока не наблюдал. В случае, если начнут — можно отправлять запросы доменов из запретного списка на другой апстрим (через VPN/Tor), однако без надобности я бы конфиг не раздувал.
При использовании Tor’a можно бонусом получить возможность серфинга по .onion сайтам: Tor при разрешении имени через встроенный dns-сервер отобразит его на виртуальный адрес из заранее заданного диапазона. Дальше нужно только перенаправить обращение к этому адресу на прокси Tor’а и voila. Но еще раз напомню, что анонимности подключение с избирательным туннелированием трафика не обеспечивает.
Реализация на OpenWrt (15.05)
Сам маршрутизатор должен быть не самый плохой, особенно при использовании Tor’а. MIPS 400MHz@32MB RAM это тот минимум, который стоит рассматривать.
При наличии USB-порта недостаток встроенного флеша можно компенсировать USB-флешкой (вообще мне представляется достаточно здравой идея не использовать встроенный флеш для регулярно перезаписываемых данных).
Штатно в прошивках OpenWrt содержится урезанный dnsmasq, не умеющий ipset. Необходимо заменить его на dnsmasq-full.
Из пакетов, по умолчанию не присутствующих, так же потребуются ipset, tor и tor-geoip.
Так же необходим либо пакет luasocket, либо (в режиме строгой экономии флеша) отдельно ltn12.lua в папке /usr/lib/lua. Для преобразования кириллических доменов из utf8 в punycode нужны idn.lua в /usr/lib/lua и пакет luabitop (либо отключить опции в конфиге скрипта).
Скрипт обновления списков блокировки
/usr/bin/rublupdate.lua
local config = {
blSource = "antizapret", -- antizapret или rublacklist
groupBySld = 32, -- количество поддоменов после которого в список вносится весь домен второго уровня целиком
neverGroupMasks = { "^%a%a%a?.%a%a$" }, -- не распространять на org.ru, net.ua и аналогичные
neverGroupDomains = { ["livejournal.com"] = true, ["facebook.com"] = true , ["vk.com"] = true },
stripWww = true,
convertIdn = true,
torifyNsLookups = false, -- отправлять DNS запросы заблокированных доменов через TOR
blMinimumEntries = 1000, -- костыль если список получился короче, значит что-то пошло не так и конфиги не обновляем
dnsmasqConfigPath = "/etc/runblock/runblock.dnsmasq",
ipsetConfigPath = "/etc/runblock/runblock.ipset",
ipsetDns = "rublack-dns",
ipsetIp = "rublack-ip",
torDnsAddr = "127.0.0.1#9053"
}
local function prequire(package)
local result, err = pcall(function() require(package) end)
if not result then
return nil, err
end
return require(package) -- return the package value
end
local idn = prequire("idn")
if (not idn) and (config.convertIdn) then
error("you need either put idn.lua (github.com/haste/lua-idn) in script dir or set 'convertIdn' to false")
end
local http = prequire("socket.http")
if not http then
local ltn12 = require("ltn12")
end
if not ltn12 then
error("you need either install luasocket package (prefered) or put ltn12.lua in script dir")
end
local function hex2unicode(code)
local n = tonumber(code, 16)
if (n < 128) then
return string.char(n)
elseif (n < 2048) then
return string.char(192 + ((n - (n % 64)) / 64), 128 + (n % 64))
else
return string.char(224 + ((n - (n % 4096)) / 4096), 128 + (((n % 4096) - (n % 64)) / 64), 128 + (n % 64))
end
end
local function rublacklistExtractDomains()
local currentRecord = ""
local buffer = ""
local bufferPos = 1
local streamEnded = false
return function(chunk)
local retVal = ""
if chunk == nil then
streamEnded = true
else
buffer = buffer .. chunk
end
while true do
local escapeStart, escapeEnd, escapedChar = buffer:find("\\(.)", bufferPos)
if escapedChar then
currentRecord = currentRecord .. buffer:sub(bufferPos, escapeStart - 1)
bufferPos = escapeEnd + 1
if escapedChar == "n" then
retVal = currentRecord
break
elseif escapedChar == "u" then
currentRecord = currentRecord .. "\\u"
else
currentRecord = currentRecord .. escapedChar
end
else
currentRecord = currentRecord .. buffer:sub(bufferPos, #buffer)
buffer = ""
bufferPos = 1
if streamEnded then
if currentRecord == "" then
retVal = nil
else
retVal = currentRecord
end
end
break
end
end
if retVal and (retVal ~= "") then
currentRecord = ""
retVal = retVal:match("^[^;]*;([^;]+);[^;]*;[^;]*;[^;]*;[^;]*.*$")
if retVal then
retVal = retVal:gsub("\\u(%x%x%x%x)", hex2unicode)
else
retVal = ""
end
end
return (retVal)
end
end
local function antizapretExtractDomains()
local currentRecord = ""
local buffer = ""
local bufferPos = 1
local streamEnded = false
return function(chunk)
local haveOutput = 0
local retVal = ""
if chunk == nil then
streamEnded = true
else
buffer = buffer .. chunk
end
local newlinePosition = buffer:find("\n", bufferPos)
if newlinePosition then
currentRecord = currentRecord .. buffer:sub(bufferPos, newlinePosition - 1)
bufferPos = newlinePosition + 1
retVal = currentRecord
else
currentRecord = currentRecord .. buffer:sub(bufferPos, #buffer)
buffer = ""
bufferPos = 1
if streamEnded then
if currentRecord == "" then
retVal = nil
else
retVal = currentRecord
end
end
end
if retVal and (retVal ~= "") then
currentRecord = ""
end
return (retVal)
end
end
local function normalizeFqdn()
return function(chunk)
if chunk and (chunk ~= "") then
if config["stripWww"] then chunk = chunk:gsub("^www%.", "") end
if idn and config["convertIdn"] then chunk = idn.encode(chunk) end
if #chunk > 255 then chunk = "" end
chunk = chunk:lower()
end
return (chunk)
end
end
local function cunstructTables(bltables)
bltables = bltables or { fqdn = {}, sdcount = {}, ips = {} }
local f = function(blEntry, err)
if blEntry and (blEntry ~= "") then
if blEntry:match("^%d+%.%d+%.%d+%.%d+$") then
-- ip адреса - в отдельную таблицу для iptables
if not bltables.ips[blEntry] then
bltables.ips[blEntry] = true
end
else
-- как можем проверяем, FQDN ли это. заодно выделяем домен 2 уровня (если в bl станут попадать TLD - дело плохо :))
local subDomain, secondLevelDomain = blEntry:match("^([a-z0-9%-%.]-)([a-z0-9%-]+%.[a-z0-9%-]+)$")
if secondLevelDomain then
bltables.fqdn[blEntry] = secondLevelDomain
if 1 > 0 then
bltables.sdcount[secondLevelDomain] = (bltables.sdcount[secondLevelDomain] or 0) + 1
end
end
end
end
return 1
end
return f, bltables
end
local function compactDomainList(fqdnList, subdomainsCount)
local domainTable = {}
local numEntries = 0
if config.groupBySld and (config.groupBySld > 0) then
for sld in pairs(subdomainsCount) do
if config.neverGroupDomains[sld] then
subdomainsCount[sld] = 0
break
end
for _, pattern in ipairs(config.neverGroupMasks) do
if sld:find(pattern) then
subdomainsCount[sld] = 0
break
end
end
end
end
for fqdn, sld in pairs(fqdnList) do
if (not fqdnList[sld]) or (fqdn == sld) then
local keyValue;
if config.groupBySld and (config.groupBySld > 0) and (subdomainsCount[sld] > config.groupBySld) then
keyValue = sld
else
keyValue = fqdn
end
if not domainTable[keyValue] then
domainTable[keyValue] = true
numEntries = numEntries + 1
end
end
end
return domainTable, numEntries
end
local function generateDnsmasqConfig(configPath, domainList)
local configFile = assert(io.open(configPath, "w"), "could not open dnsmasq config")
for fqdn in pairs(domainList) do
if config.torifyNsLookups then
configFile:write(string.format("server=/%s/%s\n", fqdn, config.torDnsAddr))
end
configFile:write(string.format("ipset=/%s/%s\n", fqdn, config.ipsetDns))
end
configFile:close()
end
local function generateIpsetConfig(configPath, ipList)
local configFile = assert(io.open(configPath, "w"), "could not open ipset config")
configFile:write(string.format("flush %s-tmp\n", config.ipsetIp))
for ipaddr in pairs(ipList) do
configFile:write(string.format("add %s %s\n", config.ipsetIp, ipaddr))
end
configFile:write(string.format("swap %s %s-tmp\n", config.ipsetIp, config.ipsetIp))
configFile:close()
end
local retVal, retCode, url
local output, bltables = cunstructTables()
if config.blSource == "rublacklist" then
output = ltn12.sink.chain(ltn12.filter.chain(rublacklistExtractDomains(), normalizeFqdn()), output)
url = "http://reestr.rublacklist.net/api/current"
elseif config.blSource == "antizapret" then
output = ltn12.sink.chain(ltn12.filter.chain(antizapretExtractDomains(), normalizeFqdn()), output)
url = "http://api.antizapret.info/group.php?data=domain"
else
error("blacklist source should be either 'rublacklist' or 'antizapret'")
end
if http then
retVal, retCode = http.request { url = url, sink = output }
else
retVal, retCode = ltn12.pump.all(ltn12.source.file(io.popen("wget -qO- " .. url)), output)
end
if (retVal == 1) and ((retCode == 200) or (http == nil)) then
local domainTable, recordsNum = compactDomainList(bltables.fqdn, bltables.sdcount)
if recordsNum > config.blMinimumEntries then
generateDnsmasqConfig(config.dnsmasqConfigPath, domainTable)
generateIpsetConfig(config.ipsetConfigPath, bltables.ips)
print(string.format("blacklists updated. %d entries.", recordsNum))
os.exit(0)
end
end
os.exit(1)
Настройки dnsmasq
/etc/dnsmasq.conf
server=/onion/127.0.0.1#9053
ipset=/onion/onion
conf-file=/etc/runblock/runblock.dnsmasq
Добавить в секцию dnsmasq /etc/config/dhcp
list server '8.8.8.8'
list server '8.8.4.4'
list rebind_domain 'onion'
Настройки netfilter
Добавить в /etc/config/firewall
config ipset
option name 'rublack-dns'
option storage 'hash'
option match 'dest_ip'
option timeout '86400'
config ipset
option name 'rublack-ip'
option storage 'hash'
option match 'dest_ip'
config ipset
option name 'rublack-ip-tmp'
option storage 'hash'
option match 'dest_ip'
config ipset
option name 'onion'
option storage 'hash'
option match 'dest_ip'
option timeout '86400'
config redirect
option name 'torify-blocked-dns'
option src 'lan'
option proto 'tcp'
option ipset 'rublack-dns'
option dest_port '9040'
option dest 'lan'
config redirect
option name 'torify-blocked-ip'
option src 'lan'
option proto 'tcp'
option ipset 'rublack-ip'
option dest_port '9040'
option dest 'lan'
config redirect
option name 'torify-onion'
option src 'lan'
option proto 'tcp'
option ipset 'onion'
option dest_port '9040'
option dest 'lan'
Добавить в /etc/firewall.user
cat /etc/runblock/runblock.ipset | ipset restore
Настройки Tor
/etc/torrc
User tor
PidFile /var/run/tor.pid
DataDirectory /var/lib/tor
ExcludeExitNodes {RU}
VirtualAddrNetwork 10.254.0.0/16 # виртуальные адреса для .onion ресурсов
AutomapHostsOnResolve 1
TransPort 9040
TransListenAddress 127.0.0.1
TransListenAddress 192.168.1.1 #адрес LAN интерфейса
DNSPort 9053
DNSListenAddress 127.0.0.1
#AvoidDiskWrites 1 # в OpenWrt /var и так в RAM (tmpfs) не уверен, что в опции есть смысл
Осталось создать каталог /etc/runblock, разово запустить вручную скрипт lua /usr/bin/rublupdate.lua, убедиться, что он отработал без ошибок, добавить его в cron (пару раз в сутки — вполне достаточно) и забыть о роскомнадзоре. Ну до тех пор, пока не начнут блокировать тор, или сайты, публикующие реестр).