Как-то я купил смартфон и всем был доволен, кроме рекламы в некоторых приложениях. Да, есть функция, которая ее должна отключать, но полностью от нее избавиться не получается. По этой причине я почти не использовал телефон для просмотра сайтов, ведь в мобильных браузерах вообще нельзя включить блокировщики рекламы.
Последней каплей стало то, что я решил перехватить и анализировать трафик приложения Xiaomi MiFit и обнаружил в ответах серверов ссылки на странные APK-файлы (описывать их я не стану, это выходит за рамки статьи). Желающие могут самостоятельно перехватить злополучные запросы, тем более что они идут по обычному HTTP.
РЕКОМЕНДУЕМ:
Обзор лучших блокировщиков рекламы
Долго думать не пришлось, под рукой был свободный сервер с белым IP. Его я и решил использовать, чтобы развернуть VPN, а заодно и настроить там блокировку всего лишнего — единожды и для всех подключенных устройств.
Как убрать рекламу в приложениях на телефоне
Предполагается, что у тебя уже есть сервер с публичным IP-адресом, где установлен Linux: в моем случае это был Ubuntu 16.10. Представленные примеры будут работать и на других дистрибутивах Linux, с той разницей, что нужно будет адаптировать установку пакетов под твой пакетный менеджер.
Что делать со старой версией Ubuntu
Так получилось, что мне достался сервер с устаревшими ссылками на репозитории, а следовательно, при попытке обновиться я получал множество ошибок.
Google ненавязчиво подсказал мне, что репозитории были перемещены на поддомен old-releases. Исправляем и обновляемся:
1 2 3 |
$ sed -i 's|us.archive|old-releases|' /etc/apt/sources.list $ sed -i 's|//security|//old-releases|' /etc/apt/sources.list $ apt update && apt upgrade -y && reboot |
Установка OpenVPN
Есть два пути:
+ простой — с использованием уже готового образа Docker (для тех, кто не хочет заморачиваться и вникать в конфиги OpenVPN);
+ сложный — с ручной установкой и конфигурированием OpenVPN.
Мы рассмотрим оба варианта и начнем с простого.
Docker-образ с OpenVPN
Скачиваем образ.
1 |
$ docker pull kylemanna/openvpn |
На гитхабе автора лежит достаточно полная документация в виде файла README. Им мы и воспользуемся. Для начала нам нужно создать файл конфигурации для сервера VPN.
1 |
$ docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_genconfig -u udp://192.168.0.183 |
Указываем тут протокол, в который будет оборачиваться трафик и белый IP твоего сервера. Давай посмотрим на полученный конфиг.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
$ cat /etc/openvpn/openvpn.conf server 192.168.255.0 255.255.255.0 verb 3 key /etc/openvpn/pki/private/192.168.0.183.key ca /etc/openvpn/pki/ca.crt cert /etc/openvpn/pki/issued/192.168.0.183.crt dh /etc/openvpn/pki/dh.pem tls-auth /etc/openvpn/pki/ta.key key-direction 0 keepalive 10 60 persist-key persist-tun proto udp ## Rely on Docker to do port mapping, internally always 1194 port 1194 dev tun0 status /tmp/openvpn-status.log user nobody group nogroup comp-lzo no #### Route Configurations Below route 192.168.254.0 255.255.255.0 #### Push Configurations Below push "block-outside-dns" push "dhcp-option DNS 8.8.8.8" push "dhcp-option DNS 8.8.4.4" push "comp-lzo no" |
Если необходимо сменить порт, то меняем его в этом файле, а также в /etc/openvpn/ovpn_env.sh. Переходим к генерации сертификатов для удостоверяющего центра.
1 |
$ docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_initpki |
Будет запрошен пароль для создания ключа удостоверяющего центра.
Теперь генерируется сертификат и ключ сервера, ключи протокола Диффи — Хеллмана, а также подпись HMAC для проверки целостности TLS.
После этого мы должны добавить пользователей. Делается это так же просто.
1 |
$ docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn easyrsa build-client-full CLIENTNAME nopass |
В качестве CLIENTNAME я буду использовать имя хоста клиента (позже ты узнаешь, для чего это нужно). Создаем сертификат для пользователя user-pc, нас сразу же попросят указать пароль от сертификата удостоверяющего центра. Сертификат готов, но нам-то нужно получить файл конфигурации для OpenVPN. Выполняем следующую команду.
1 |
$ docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_getclient user-pc > /root/user-pc.ovpn |
Смотрим на результат.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
$ cat /root/user-pc.ovpn client nobind dev tun remote-cert-tls server remote 192.168.0.183 1194 udp <key> -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- </key> <cert> -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- </cert> <ca> -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- </ca> key-direction 1 <tls-auth> # ## 2048 bit OpenVPN static key # -----BEGIN OpenVPN Static key V1----- ... -----END OpenVPN Static key V1----- </tls-auth> redirect-gateway def1 |
Ключи в формате Base64 намеренно убраны из вывода, чтобы уменьшить его содержимое. На этом все: запускаем OpenVPN в режиме демона.
1 |
$ docker run -v /etc/openvpn:/etc/openvpn -d -p 1194:1194/udp --cap-add=NET_ADMIN kylemanna/openvpn |
На стороне клиента пробуем подключиться.
1 |
$ sudo openvpn --config user-pc.ovpn |
Если все прошло успешно, то в конце лога ты увидишь надпись Initialization Sequence Completed, а в списке интерфейсов появится tun0 с IP-адресом из подсети 192.168.255.0/24. Для наших целей этого вполне хватит, нужно только добавить команду на запуск контейнера в автозагрузку и переходить к части с настройкой DNS.
Ручная настройка OpenVPN
Второй вариант — это настроить OpenVPN полностью вручную. Поехали по шагам.
1. Устанавливаем OpenVPN.
1 |
$ sudo apt install -y openvpn |
2. Скачиваем easy-rsa.
1 |
$ sudo git clone https://github.com/OpenVPN/easy-rsa.git /etc/openvpn/easy-rsa |
3. Выставляем переменные для easy-rsa в файле /etc/openvpn/easy-rsa/easyrsa3/vars, остальное по желанию. Файл /etc/openvpn/easy-rsa/easyrsa3/vars.example содержит полный перечень доступных переменных, но нам хватит и этого.
1 2 3 |
set_var EASYRSA_KEY_SIZE 2048 set_var EASYRSA_CA_EXPIRE 3650 set_var EASYRSA_CERT_EXPIRE 3650 |
4. Инициализация PKI.
1 |
$ cd /etc/openvpn/easy-rsa/easyrsa3 && ./easyrsa init-pki |
5. Создание центра сертификации. Указываем пароль и Common Name.
1 |
$ ./easyrsa build-ca |
6. Создание ключей сервера. Везде используем имя хоста как Common Name для создания сертификата.
1 |
$ ./easyrsa build-server-full vpnserver nopass |
7. Создание ключей клиента.
1 |
$ ./easyrsa build-client-full user-pc nopass |
8. По желанию: если нужно сгенерировать ключи Диффи — Хеллмана и TLS.
1 2 |
$ ./easyrsa gen-dh $ openvpn --genkey --secret /etc/openvpn/easy-rsa/easyrsa3/pki/ta.key |
На этом создание сертификатов завершено, переходим к написанию конфига для VPN-сервера /etc/openvpn/server.conf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
## Адрес твоего сервера local 192.168.0.183 ## Какой TCP/UDP-порт должен слушать OpenVPN port 3333 ## Режим работы mode server ## Используемый протокол TCP/UDP proto udp ## Тип интерфейса, tun (OSI Layer 3) или tap (OSI Layer 2) dev tun ## Пути к файлам сертификатов ca /etc/openvpn/easy-rsa/easyrsa3/pki/ca.crt cert /etc/openvpn/easy-rsa/easyrsa3/pki/issued/vpnserver.crt ## Этот файл необходимо хранить в секрете key /etc/openvpn/easy-rsa/easyrsa3/pki/private/vpnserver.key dh /etc/openvpn/easy-rsa/easyrsa3/pki/dh.pem ## Указываем адрес сервера и подсеть server 10.8.0.0 255.255.255.0 ## Выдаем пользователям необходимые маршруты push "route 10.8.0.0 255.255.255.0" ## OpenVPN as Proxy. Перенаправляем трафик клиентов в интернет push "redirect-gateway def1 bypass-dhcp" ## Используем свой собственный DNS-сервер push "dhcp-option DNS 10.8.0.1" ## Разрешаем подключенным клиентам OpenVPN-сервера соединяться друг с другом client-to-client ## Используем сжатие трафика (если не нужно, то можно закомментировать!) comp-lzo ## Проверяем состояние клиента, отправляем пакеты каждые 10 с, если в течение 120 с клиент не ответил, то он считается отключенным keepalive 10 120 ## Используем те же ключи и интерфейсы при рестарте persist-key persist-tun ## Лог-файл log openvpn.log ## Уровень логирования 0 — в лог попадают только записи о критических ошибках сервера, если нужно подробнее, то выставляем 9 для дебагинга verb 0 ## Количество записей, после которых будет производиться запись в лог mute 20 |
Плюс установки OpenVPN из репозитория еще и в том, что дополнительно он прописывается как сервис в systemctl. Подправим его для работы с нашим конфигом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ systemctl cat openvpn.service ## /lib/systemd/system/openvpn.service ## This service is actually a systemd target, ## but we are using a service since targets cannot be reloaded. [Unit] Description=OpenVPN service After=network.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/bin/true ExecReload=/bin/true WorkingDirectory=/etc/openvpn [Install] WantedBy=multi-user.target |
Получив путь к файлу, немного изменим его под наши нужды.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
## /lib/systemd/system/openvpn.service ## This service is actually a systemd target, ## but we are using a service since targets cannot be reloaded. [Unit] Description=OpenVPN service After=network.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/sbin/openvpn --config server.conf --daemon ExecReload=/usr/sbin/openvpn --config server.conf --daemon WorkingDirectory=/etc/openvpn RestartSec=5s Restart=on-failure [Install] WantedBy=multi-user.target |
Запускаем сервис.
1 |
$ sudo systemctl enable openvpn.service && sudo systemctl start openvpn.service |
И проверяем на ошибки.
1 |
$ journalctl -uxe openvpn.service |
В прошлом примере с Docker у нас была возможность получить конфиг клиента одной командой. Пора написать для этого скрипт.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#!/bin/bash USERNAME=$1 EASYRSA_PKI="/etc/openvpn/easy-rsa/easyrsa3/pki" echo " client nobind dev tun remote 192.168.0.183 3333 proto udp comp-lzo yes auth-nocache script-security 2 persist-key persist-tun" echo "<key> $(cat ${EASYRSA_PKI}/private/${USERNAME}.key) </key> <cert> $(openssl x509 -in ${EASYRSA_PKI}/issued/${USERNAME}.crt) </cert> <ca> $(cat $EASYRSA_PKI/ca.crt) </ca> " |
Описывать его не вижу необходимости: тут все то же самое, а за подробностями можешь заглянуть в man openvpn.
Теперь, когда у нас готов сервер VPN и настроено подключение клиентов, пора приступать к конфигурации сервера DNS.
Unbound
Выбор на него пал по нескольким причинам:
- есть поддержка DNSSEC;
- умеет кешировать;
- можно настроить DoT (DNS over TLS);
- очень прост в конфигурировании;
- большая скорость работы и малый размер по сравнению с BIND.
Компиляция Unbound DNS Resolver
Скачиваем с GitHub последнюю версию Unbound, устанавливаем зависимости.
1 2 |
$ git clone https://github.com/NLnetLabs/unbound.git /opt/unbound $ sudo apt install -y protobuf-c-compiler libevent-dev libssl-dev libsodium-dev libfstrm-dev |
Теперь можно приступать к компиляции.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ cd /opt/unbound $ ./configure \ --prefix=/usr \ --sysconfdir=/etc \ --localstatedir=/var \ --sbindir=/usr/bin \ --disable-rpath \ --enable-dnscrypt \ --enable-dnstap \ --enable-pie \ --enable-relro-now \ --enable-subnet \ --enable-tfo-client \ --enable-tfo-server \ --with-conf-file=/etc/unbound/unbound.conf \ --with-pidfile=/run/unbound.pid \ --with-rootkey-file=/etc/trusted-key.key \ --with-libevent $ make $ sudo make install |
Тут мы указываем:
- --with-conf-file — стандартный путь к файлу конфигурации;
- --enable-dnscrypt — включаем поддержку dnssec;
- --with-rootkey-file — путь к файлу с доверенными ключами;
- --enable-dnstap — включаем поддержку dnstap.
После успешной сборки нам осталось добавить пару нюансов. Во-первых, создать пользователя.
1 |
$ sudo useradd -s /bin/false -d /etc/unbound unbound |
Во-вторых, добавить сервис /lib/systemd/system/unbound.service. Его содержимое будет таким.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
## /lib/systemd/system/unbound.service [Unit] Description=Unbound DNS Resolver Wants=nss-lookup.target Before=network-online.target nss-lookup.target After=network.target [Service] ExecStart=/usr/bin/unbound ExecReload=/bin/kill -HUP $MAINPID Restart=always [Install] WantedBy=multi-user.target |
Добавим якорь в tmpfiles.d.
1 |
$ echo 'C /etc/unbound/trusted-key.key - - - - /etc/trusted-key.key' > /usr/lib/tmpfiles.d/unbound.conf |
Конфигурация DNS over TLS
DoT добавит нам больше конфиденциальности и позволит защититься от спуфинга. Ниже пример файла конфигурации /etc/unbound/unbound.conf.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
server: # Слушаем все интерфейсы interface: 0.0.0.0 interface: ::0 # Логи пишем в системный журнал use-syslog: yes do-daemonize: no username: "unbound" # Количество потоков. Рекомендуется ставить максимальное для твоей системы num-threads: 2 verbosity: 2 # Запрещаем входящие запросы из интернета access-control: 0.0.0.0/0 refuse access-control: ::0/0 refuse # Разрешаем запросы с локального хоста и подсетей VPN access-control: 127.0.0.1 allow access-control: ::1 allow access-control: 10.8.0.0/24 allow # Скрываем отображение версии сервера hide-identity: yes hide-version: yes # Указываем приватные сети, к которым будет доступ у клиентов private-address: 127.0.0.1 private-address: ::1 # Сеть VPN private-address: 10.8.0.0/24 private-address: fe80::/24 # Директория с файлами конфигурации directory: "/etc/unbound" # Файлы ключей и сертификатов для DNS over TLS trust-anchor-file: trusted-key.key tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt # Минимальное и максимальное время кеша cache-max-ttl: 86400 cache-min-ttl: 3600 # Подгружаем локальные конфиги из local.d, туда будем добавлять потом фильтры рекламы include: /etc/unbound/local.d/*.conf ## Ну и самая главная часть: перенаправляем все DNS-запросы на серверы с поддержкой DoT forward-zone: name: "." forward-tls-upstream: yes ## Cloudflare DNS forward-addr: 1.1.1.1@853#cloudflare-dns.com forward-addr: 1.0.0.1@853#cloudflare-dns.com ## IPv6 Cloudflare DNS over TLS forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com forward-addr: 2606:4700:4700::1001@853#cloudflare-dns.com ## Эта опция нам понадобится для обновления конфигурации Unbound в режиме реального времени remote-control: control-enable: yes # Разрешаем запросы только из локальной сети control-interface: 127.0.0.1 control-interface: ::1 control-port: 8953 # При желании можно добавить проверку сертификата, но нам это не нужно control-use-cert: no |
Конфиг готов! Создадим файл ключей /etc/unbound/trusted-key.key для DNSSEC.
1 |
$ sudo unbound-anchor -a /etc/unbound/trusted-key.key |
Союз с NetworkManager
Для обновления файла /etc/resolv.conf нам потребуется утилита openresolv. Создадим для нее конфиг /etc/resolvconf.conf.
1 2 |
resolv_conf=/etc/resolv.conf name_servers="::1 127.0.0.1" |
Тут мы указываем путь к resolv.conf и адрес DNS по умолчанию для всей системы. Командой $ resolvconf -u обновляем resolv.conf, чтобы в него добавились изменения. Если ты подключаешься к интернету при помощи NetworkManager, то для того, чтобы он тоже использовал openresolv, нужно поправить файл /etc/NetworkManager/conf.d/rc-manager.conf:
1 2 |
[main] rc-manager=resolvconf |
Запуск Unbound
Запускаем резолвер командой
1 |
$ sudo systemctl start unbound |
После этого все должно работать.
Важно убедиться, что перед запуском Unbound у тебя отключены другие DNS-серверы или резолверы! Возможные ошибки и их причину можешь узнать, написав journalctl -xeu unbound.
Блокировка рекламы
Переходим к самой важной части всего нашего процесса. Поискав информацию в Сети и посмотрев фильтры uBlock, который установлен в моем браузере, я нашел несколько списков с доменами:
Создаем скрипт /etc/unbound/local.d/update_ads_filters.py, который будет это все парсить и генерировать правила для нашего Unbound.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/usr/bin/python import requests response = requests.get('https://pgl.yoyo.org/adservers/serverlist.php?hostformat=unbound&showintro=0&mimetype=plaintext') hosts = requests.get('http://www.malwaredomainlist.com/hostslist/hosts.txt') def make_unbound_zone(host): fmt = 'local-zone: "{0}" redirect\nlocal-data: "{0} A 127.0.0.1"\n' return fmt.format(host) if response and response.status_code == 200: unbound_format = response.text if hosts and hosts.status_code == 200: for line in hosts.text.splitlines(): if not line or line.startswith('#'): continue _, host = line.strip().split() if host not in unbound_format: unbound_format += make_unbound_zone(host) print(unbound_format) |
Тут все очень просто: для каждого домена создается следующая запись.
1 2 |
local-zone: "HOSTNAME" redirect local-data: "HOSTNAME A 127.0.0.1" |
Как потом выяснилось, можно вместо редиректа на 127.0.0.1 использовать более простую запись и возвращать NXDOMAIN. Если коротко, то это означает, что запрашиваемый домен не существует.
1 |
local-zone: "HOSTNAME" always_nxdomain |
Добавляем этот скрипт в crontab с периодом запуска не чаще одного раза в неделю (не думаю, что домены будут обновляться чаще) и запускаем.
1 |
# /etc/unbound/local.d/update_ads_filters.py > /etc/unbound/local.d/adservers.conf && unbound-control reload |
Дополнительно создадим еще один скрипт /etc/unbound/insert_filter_domain.sh, который позволит вручную блокировать необходимые домены. Я с его помощью добавил Яндекс.Метрику.
1 2 3 4 5 6 7 8 |
#!/bin/bash DOMAIN=$1 FILTER_FILE="/etc/unbound/local.d/ruads.conf" echo "local-zone: \"${DOMAIN}\" always_nxdomain" >> ${FILTER_FILE} unbound-control reload |
Добавляем резолвинг DNS для клиентов
Все может работать и без этого, но приятно будет вместо обращения по IP использовать короткий адрес, который легко запомнить. Есть и еще одна причина: все клиенты при подключении получают IP-адреса от встроенного в OpenVPN сервера DHCP и каждый раз эти адреса могут меняться.
Это можно было бы решить, прописав все IP статически, или вместо интерфейса TUN для OpenVPN использовать TAN и развернуть сверху собственный сервер DHCP, но это не наш путь. Мы воспользуемся тем, что у нас есть, а заодно и узнаем некоторые особенности OpenVPN.
Для начала нам нужно обновить конфиг OpenVPN (файл /etc/openvpn/server.conf), добавив туда следующие строки:
1 2 3 4 5 |
## Разрешаем скриптам запуск сторонних приложений script-security 2 ## Назначаем скрипт, который будет запускаться при подключении и отключении клиента client-connect /etc/openvpn/connect.sh client-disconnect /etc/openvpn/connect.sh |
Сам скрипт /etc/openvpn/connect.sh выглядит так.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#!/bin/bash add_to_unbound() { host="$1" ip="$2" reverse_ip=$(echo $ip | awk ' BEGIN { FS="." ; OFS="." } { print $4,$3,$2,$1 }') unbound-control local_data "${host} IN A $ip" unbound-control local_data "${reverse_ip}.in-addr.arpa. IN PTR ${host}" } del_from_unbound() { host="$1" ip="$2" reverse_ip=$(echo $ip | awk ' BEGIN { FS="." ; OFS="." } { print $4,$3,$2,$1 }') unbound-control local_data_remove "${host}" unbound-control local_data_remove "${reverse_ip}.in-addr.arpa." } if [ "$script_type" == "client-connect" ]; then add_to_unbound "${common_name}" "${ifconfig_pool_remote_ip}" elif [ "$script_type" == "client-disconnect" ]; then del_from_unbound "${common_name}" "${ifconfig_pool_remote_ip}" fi |
Из мануала по OpenVPN можно узнать, что для запускаемых им скриптов и команд доступны некоторые глобальные переменные:
- script_type — событие, вызвавшее запуск скрипта, доступны варианты up, down, ipchange, route-up, tls-verify, auth-user-pass-verify, client-connect, client-disconnect и learn-address;
- common_name — поле Common Name из сертификата клиента. Помни, что при генерации сертификатов мы указывали там имя хоста;
- ifconfig_pool_remote_ip — внутренний IP клиента.
Функции add_to_unbound() и del_from_unbound(), используя интерфейс удаленного управления конфигурацией Unbound, добавляют записи, которые резолвят имена хостов клиентов.
Дополнительно создадим файл /etc/unbound/local.d/localzone.conf, который будет содержать записи для резолва имени текущего сервера и всей подсети в целом.
1 2 3 4 5 6 7 |
insecure-lan-zones: yes local-zone: "10.in-addr.arpa." nodefault local-zone: "8.10.in-addr.arpa." nodefault local-data-ptr: "10.8.0.1 vpnserver" local-data: "vpnserver A 10.8.0.1" |
Итог
На этом настройка нашей системы фильтрации закончена. Устанавливаем на телефон OpenVPN, подключаемся и проверяем. Вот как результаты выглядят в браузере.
Результаты до и после |
В приложении MiFit.
И Avito.
Идеально!