Чем отличается от подобных материалов?

  • Реализация на чистом OpenWrt
  • Использование WireGuard
  • Конфигурация роутера организуется с помощью конфигов OpenWrt, а не кучей в одном скрипте
  • Предусмотрены ситуации при рестарте сети и перезагрузке
  • Потребляет мало ресурсов роутера: заблокированные подсети содержатся в iptables, а не в таблицах маршрутизации. Что позволяет развернуть это дело даже на слабых устройствах
  • Автоматизация конфигурации с помощью Ansible (не требуется python на роутере)

Видеоверсия

Почему OpenWrt и WireGuard?

OpenWrt ставится на очень много моделей soho роутеров, конфигурируется и расширяется как душа пожелает. Сейчас многие прошивки роутеров — это надстройки над OpenWrt.

Wireguard используется из-за его быстрой и простой настройки, а так же из-за высокой скорости передачи через туннель.

Немного о WireGuard

В нашем случае сервер — это VPS вне РКН, клиент — OpenWrt роутер дома. Когда вы захотите зайти на pornolab telegram, ваш роутер направит трафик через сервер с WireGuard.

WireGuard поднимает site-to-site соединение, т.е. и у сервера и у клиента имеется серверная и клиентская часть конфигурации. Если не понятно — станет понятно когда увидите конфигурацию.

У сервера и у клиента есть свои собственные приватный и публичный ключи.

Настройка WireGuard на сервере

Я проделываю всё на Ubuntu 18.04, но в официальной документации есть инструкции по установке для всех известных и не очень ОС.

Установка

sudo add-apt-repository ppa:wireguard/wireguard

При возникновении ошибки

> sudo: add-apt-repository: command not found
> ```
>
> Установите software-properties-common — пакет предоставляет возможность добавления и удаления PPA
>
> ```bash
> sudo apt install software-properties-common
> ```

bash sudo apt update sudo apt install wireguard-dkms wireguard-tools

Генерируем ключи для сервера

Ключи сохраним в директории WireGuard для удобства

cd /etc/wireguard/ wg genkey | tee privatekey-server | wg pubkey > publickey-server

Соответственно в файле privatekey-server будет приватный ключ, а в publickey-server — публичный.

Так же сгенерируем сразу ключ для клиента

wg genkey | tee privatekey-client | wg pubkey > publickey-client

![Wireguard ls server](/2019/wg_keys_on_server.png)

### Конфигурация

Конфиг хранится в /etc/wireguard/wg0.conf

Серверная часть выглядит так

bash [Interface] Address = 192.168.100.1 PrivateKey = privatekey-server ListenPort = 51820 PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE

**Address** — адрес для интерфейса wg (адрес внутри туннеля)

**PrivateKey** — Приватный ключ (privatekey-server)

**ListenPort** — Порт на котором служба ожидает подключения

Ну и делаем маскарадинг, потому что мы будем использовать этот сервер для выхода в интернет

Обратите внимание, что имя интерфейса в вашем случае может отличаться

Клиентская часть

bash [Peer] PublicKey = publickey-client AllowedIPs = 192.168.100.324

**PublicKey** — публичный ключ нашего роутера (publickey-client)

**AllowedIPs** — подсети, которые будут доступны через этот туннель. Серверу требуется доступ только до адреса клиента

Обе части хранятся в одном конфиге.

Включаем автозапуск при перезагрузке

bash systemctl enable wg-quick@wg0

Делаем сервер маршрутизатором

bash sysctl -w net.ipv4.ip_forward=1

Настроим фаервол. Предположим, что у нас на сервере только WireGuard и ssh

bash sudo iptables -A INPUT -i lo -j ACCEPT sudo iptables -A INPUT -p udp -m udp –dport 51820 -j ACCEPT sudo iptables -A INPUT -m state –state RELATED,ESTABLISHED -j ACCEPT sudo iptables -A INPUT -p icmp -j ACCEPT sudo iptables -A INPUT -p tcp -m tcp –dport 22 -j ACCEPT sudo iptables -A INPUT -j DROP

Сохраним конфигурацию iptables

bash sudo apt-get install iptables-persistent sudo netfilter-persistent save

Поднимаем wg интерфейс первый раз вручную

bash wg-quick up wg0

![Wireguard server done](/2019/wg_show_server.png)

WireGuard сервер готов

**UPD 27.06.19** Если ваш провайдер до сих пор использует PPoE, то нужно добавить правило

iptables -t mangle -I POSTROUTING -p tcp –tcp-flags SYN,RST SYN -j TCPMSS –clamp-mss-to-pmtu

## Настройка роутера

Я использую OpenWrt версии 18.06.1 на Xiaomi mi 3G и Asus RT-N16.

### Логика работы роутера

Загружаем списки, помещаем их в iptables, все адреса из этих списков iptables помечает маркером 0x1. Далее все пакеты помеченные 0x1 идут в отдельную таблицу маршрутизации, все пакеты попавшие в эту таблицу маршрутизации идут через wg интерфейс.

![HiRKN router scheme](/2019/router-scheme.gif)

### Установка пакетов

Насчет занимаемого места на флеше, на всё понадобится примерно 0.9МБ. Если у вас совсем плохо с местом, замените curl wget’ом и можете не ставить dnscrypt-proxy.

Ставим пакеты. В OpenWrt это просто сделать через менеджер пакетов opkg

bash opkg update opkg install ipset wireguard curl

### Загрузка списков

Всё, что можно сделать через стандартные возможности OpenWrt, сделано через них. Всё остальное (кроме hotplug) я поместил в небольшой скрипт

bash #!/bin/sh

START=99

dir=/tmp/lst

mkdir -p $dir

echo “Run download lists” curl -z $dir/subnet.lst https://antifilter.download/list/subnet.lst –output $dir/subnet.lst

curl -z $dir/ipsum.lst https://antifilter.download/list/ipsum.lst –output $dir/ipsum.lst

echo “Firewall restart” /etc/init.d/firewall restart

Списки запрещенных подсетей и адресов получаем файлами. Для них создаём директорию в /tmp. В /tmp — потому что это RAM, такая особенность OpenWrt, довольно удобная. На ROM роутера что-то писать лишний раз не стоит.

Выкачиваем списки с antifilter.download curl’ом, флаг z означает, что curl будет скачивать файл, только если удаленный файл отличается от локального или если его нет, как например в случае при загрузке роутера.

_subnet.lst_ — список заблокированных подсетей, изменяется не часто.

_ipsum.lst_ — список заблокированных адресов, который суммаризирован по маске. Вместо 150 тысяч записей получаем 15 тысяч — удобно.

После того как файлы у нас — рестартуем firewall, это нужно для того что бы ipset отработал и добавил списки в iptables, ipset у нас будет сконфигурен в /etc/config/firewall.

Скрипт этот мы добавляем в /etc/init.d/ назовём hirkn. Сделаем его исполняемым

bash chmod +x /etc/init.d/hirkn

Теперь у нас не просто скрипт, а целая служба. Для того, что бы он запускался при загрузке, делаем симлинк в /etc/rc.d. Нам нужно, что бы он запускался после всех остальных служб, поэтому делаем [приставку S99](https://openwrt.org/docs/techref/initscripts "приставку S99")

bash ln -s /etc/init.d/hirkn /etc/rc.d/S99hirkn

Списки нужно обновлять время от времени, добавляем запись в cron

bash crontab -e

bash 0 4 * * * /etc/init.d/hirkn

Мне кажется вполне достаточным обновлять их раз в сутки. Имейте в виду, что при добавлении списков в ipset, отваливается сеть, в моём случае это 2 секунды.

Так же включите крон, по дефолту он отключен

bash /etc/init.d/cron enable /etc/init.d/cron start

### Конфигурация таблицы маршрутизации

Создаем таблицу маршрутизации для трафика через туннель, просто добавив строку

bash 99 vpn

в файл /etc/iproute2/rt_tables

Создать дефолтный маршрут для таблицы "vpn" через wg интерфейс можно командой

bash ip route add table vpn default dev wg0

Но при рестарте сети маршрут пропадёт, поэтому создаём файл 30-rknroute в директории /etc/hotplug.d/iface/ с простым содержимым:

bash #!/bin/sh

ip route add table vpn default dev wg0

Это означает, что при включении\выключении интерфейсов будет добавляться наш маршрут. И соответственно, этот маршрут будет всегда прописан.

### Конфигурация сети

Нам необходимо сконфигурировать WireGuard и правило для пакетов с меткой 0x1.

Конфигурация WireGuard располагается в /etc/config/network

"Серверная" часть

bash config interface ‘wg0’ option private_key ‘privatekey-client’ list addresses ‘192.168.100.324’ option listen_port ‘51820’ option proto ‘wireguard’

**private_key** — это privatekey-client, который мы генерировали при настройке сервера

**list addresses** — адрес wg интерфейса

**listen_port** — порт на котором WireGuard принимает соединения. Но соединение будет происходить через порт на сервере, поэтому здесь мы не будем открывать для него порт на firewall

**proto** — указываем протокол, что бы openwrt понимало что это конфигурация WireGuard

"Клиентская" часть

bash config wireguard_wg0 option public_key ‘publickey-server’ option allowed_ips ‘0.0.0.0/0’ option route_allowed_ips ‘0’ option endpoint_host ‘wg-server-ip’ option persistent_keepalive ‘25’ option endpoint_port ‘51820’

**public_key** — ключ publickey-server

**allowed_ips** — подсети, в которые может ходить трафик через тунель, в нашем случае никаких ограничей не требуется, поэтому 0.0.0.0/0

**route_allowed_ips** — флаг, который делает роут через wg интерфейс для перечисленных сетей из параметра allowed_ips. В нашем случае это не нужно, эту работу выполняет iptables

**endpoint_host** — ip/url нашего wg сервера

**persistent_keepalive** — интервал времени, через который отправляются пакеты для поддержки соединения

**endpoint_port** — порт wireguard на сервере

Ещё в конфигурацию network добавим правило, которое будет отправлять весь трафик, помеченный 0x1, в таблицу маршрутизации "vpn"

bash config rule option priority ‘100’ option lookup ‘vpn’ option mark ‘0x1’

### Конфигурация firewall

Добавим два правила маркировки пакетов, они не вписываются в синтаксис UCI openwrt, поэтому добавляем их "как есть" в /etc/firewall.user

**UPD**: На хабре [подсказали](https://habr.com/ru/post/440030/#comment_19809068 "подсказали"), что вполне себе вписываются. Зададим их после настройки ipset

Конфигурация фаервола находится в /etc/config/firewall

Добавляем зону для wireguard. В openwrt зоны — это кастомные цепочки в iptables. Таким образом создаётся зона с одним\несколькими интерфейсами и уже на неё вешаются правила. Зона для wg выглядит например вот так

bash config zone option name ‘wg’ option family ‘ipv4’ option masq ‘1’ option output ‘ACCEPT’ option forward ‘REJECT’ option input ‘REJECT’ option mtu_fix ‘1’ option network ‘wg0’

Мы разрешаем только выход трафика из интерфейса и включаем маскарадинг.

Теперь нужно разрешить переадресацию с lan зоны на wg зону

bash config forwarding option src ‘lan’ option dest ‘wg’

Настроим формирование списков в ipset

bash config ipset option name ‘vpn_subnets’ option storage ‘hash’ option loadfile ‘/tmp/lst/subnet.lst’ option match ‘dst_net’

config ipset option name ‘vpn_ipsum’ option storage ‘hash’ option loadfile ‘/tmp/lst/ipsum.lst’ option match ‘dst_net’

**loadfile** — файл из которого берем список

**name** — имя для нашего списка

**storage**, **match** — здесь указываем как хранить и какой тип данных. Будем хранить тип "подсеть"

**UPD**: Если хотите использовать список отдельных IP адресов, то вам необходимо увеличить размер списка ipset. В config ipset добавьте
    option hashsize '1000000'
    option maxelem '1000000'
Иначе вы будете получать ошибку

ipset v6.38: Hash is full, cannot add more elements

**UPD**: Добавим два правила маркировки пакетов

config rule option name ‘mark_subnet’ option src ‘lan’ option dest ‘*’ option proto ‘all’ option ipset ‘vpn_subnets’ option set_mark ‘0x1’ option target ‘MARK’

config rule option name ‘mark_ipsum’ option src ‘lan’ option dest ‘*’ option proto ‘all’ option ipset ‘vpn_ipsum’ option set_mark ‘0x1’ option target ‘MARK’

Эти правила подразумевают под собой, что все пакеты идущие в подсети из списков vpn_subnets и vpn_ipsum необходимо помечать маркером 0x1.

После этого рестартуем сеть

bash /etc/init.d/network restart

![Wireguard client](/2019/wg_show_client.png)

и запускаем скрипт

bash /etc/init.d/hirkn

![HiRKN script](/2019/run_hirkn.gif)

После отработки скрипта у вас должно всё заработать.

Проверьте маршрут на клиенте роутера

bash mtr/traceroute telegram.org/linkedin.com

![mtr hirkn](/2019/mtr.gif)

## Бонусом настроим DNSCrypt

Зачем? Ваш провайдер может заботливо подменять ip адрес заблокированного ресурса, таким образом перенаправляя вас на свой ip с заглушкой, ну и наш обход по ip в данном случае не поможет. Для подмены не всегда даже нужно использовать dns сервер провайдера, ваши запросы могут перехватываться и ответы подменяться. Ну и к слову, это может делать не только провайдер.

bash opkg install dnscrpt-proxy

Настраиваем конфиг /etc/config/dnscrypt-proxy примерно так

bash config dnscrypt-proxy ns1 option address ‘127.0.0.1’ option port ‘5353’ option resolver ‘cpunks-ru’

Таким образом у нас есть сервис dnscrypt на порту 5353 доступный на localhost.

**Resolver** — это dns, сервер поддерживающий шифрование. На роутере в файле /usr/share/dnscrypt-proxy/dnscrypt-resolvers.csv содержится список доступных, на момент выпуска установленной версии dnscrypt, серверов. А вот здесь [https://dnscrypt.info/public-servers/](https://dnscrypt.info/public-servers/) вообще все доступные серверы dnscrypt. Можете выбрать другого резолвера и/или добавить серверов для отказоустойчивости. Имейте в виду, что бы DNSCrypt работал с выбраным резолвером, он должен быть указан в dnscrypt-resolvers.csv

Настраиваем dnsmasq на работу с dnscrypt. В /etc/config/dhcp комментируем строчку

bash option resolvfile ‘/tmp/resolv.conf.auto’

для того что бы не были задействованы dns серверы провайдера

и добавляем

bash list server ‘/pool.ntp.org/208.67.222.222’ list server ‘127.0.0.1#5353’

Запись **list server ‘domain/ip_dns’** указывает какой dns сервер использовать для резолва указанного домена. Таким образом мы не задействуем dnscrypt для синхронизации ntp — для работы службе dnscrypt важно иметь актуальное время.

При загрузке роутера, скрипт hirkn запускается быстрее чем стартует dnscrypt, таким образом домен antifilter.download не резолвится и списки не скачиваются. Можно сделать задержку или ещё что придумать, но пока что не вижу смысла.

**UPD**: необходимо добавить строку

START=99

в скрипт hirkn

В итоге мы получаем такую вставку в конфиг

bash #option resolvfile ‘/tmp/resolv.conf.auto’ list server ‘/pool.ntp.org/208.67.222.222’ list server ‘127.0.0.1#5353’

**UPD**: На некоторых устройствах DNSCrypt запускается всё-равно после скрипта. Самый простой исправить — это добавить в /etc/config/dhcp строку
    list server '/antifilter.download/208.67.222.222'
Отключаем использование провайдерских DNS для интерфейса wan

В /etc/config/network добавляем строку

option peerdns ‘0’

к интерфейсу wan.

Получаем такую конфигурацию

````bash
config interface 'wan’
        option ifname 'eth0.2’
        option proto 'dhcp’
        option peerdns ‘0’
        ```

Рестартуем сеть
````

/etc/init.d/network restart

````

Добавляем в автозагрузку и стартуем dnscrypt

bash /etc/init.d/dnscrypt-proxy enable /etc/init.d/dnscrypt-proxy start ````

Рестартуем dnsmasq

/etc/init.d/dnsmasq restart

DNSCrypt tcpdump

Илюстрация работы без DNSCrypt и c DNSCrypt

Автоматически развертываем с помощью Ansible

Playbook и темплейты лежат на github

Используется модуль https://github.com/gekmihesg/ansible-openwrt, в нём не нужен python на роутере и есть поддержка uci.

Я постарался сделать так, что бы ваша конфигурация OpenWrt осталась не тронутой, но всё равно будьте бдительны.

Устанавливаем модуль gekmihesg/ansible-openwrt

ansible-galaxy install gekmihesg.openwrt

Копируем плейбук и темлпейты

cd /etc/ansible
git clone https://github.com/itdoginfo/ansible-openwrt-hirkn
mv ansible-openwrt-hirkn/* .
rm -rf ansible-openwrt-hirkn

Добавляйте ваш роутер в hosts

[openwrt]
192.168.1.1

Подставляете свои переменные в hirkn.yml

  vars:
    ansible_template_dir: /etc/ansible/templates/
    wg_server_address: wg_server_ip/url
    wg_private_key: privatekey-client
    wg_public_key: publickey-client
    wg_listen_port: 51820
    wg_client_port: 51820
    wg_client_address: 192.168.100.3/24

Обязательно нужно задать

wg_server_address — ip/url wireguard сервера

wg_private_key, wg_public_key — приватный ключ клиента и публичный сервера

Остальное можно не менять или менять, в зависимости от того как настроен WireGuard сервер

Запускаем playbook

ansible-playbook playbooks/hirkn.yml

После выполнения плейбука, роутер сразу начнёт выполнять обход блокировок через ваш wireguard сервер.

Почему не BGP?

Под openwrt есть две утилиты реализующих BGP — quagga и bird. Quagg’у мне не удалось заставить забирать данные с antifilter. Bird подружился с сервисом с полпинка, но как заставить добавлять полученным подсетям интерфейс по умолчанию я, к сожалению, не понял. (Буду рад узнать как это можно реализовать)

В комментариях к подобным статьям я видел, что роутеры у людей “призадумывались” на некоторое время, когда те загоняют списки в таблицу маршрутизации. С реализацией через ipset мой Xiaomi mi 3G задумывается на 2 секунды (Asus rt-n16 на 5 секунд), когда скармливаешь ему список из 15ти тысяч подсетей. При дальнейшей работе нагрузки на процессор не замечал.

Все материалы не являются призывом к действию и представлены для ознакомления с функционалом ОС Linux.