Android без рекламы. Используем VPN и Unbound, чтобы убрать рекламу из приложений

Недавно я купил телефон Xiaomi и всем был доволен, кроме рекламы в фирменных приложениях. Да, есть функция, которая ее должна отключать, но полностью от нее не избавиться. Поэтому я почти не использую телефон для просмотра сайтов, ведь в мобильных браузерах вообще нельзя установить блокираторы рекламы.

Кто-то скажет, что реклама помогает поддерживать бесплатные проекты, и, возможно, когда-то это так и было, но сейчас реклама — сбор информации о тебе, твоих привычках, поисковых запросах и прочем: достаточно посмотреть трафик, который генерирует твой телефон во время обычного использования. Я давно укомплектовал свой десктопный браузер плагинами типа NoScript и uBlock, почти позабыл о рекламе, но телефон о ней постоянно напоминал.

Последней каплей стало то, что я решил изучить трафик приложения Xiaomi MiFit и обнаружил в ответах серверов ссылки на странные APK-файлы (описывать их я не стану, это выходит за рамки статьи). Желающие могут самостоятельно перехватить злополучные запросы, тем более что они идут по обычному HTTP.

Долго думать не пришлось, под рукой был свободный сервер с белым IP. Его я и решил использовать, чтобы развернуть VPN, а заодно и настроить там блокировку всего лишнего — единожды и для всех подключенных устройств.

Подготовительный этап

Предполагается, что у тебя уже есть сервер с публичным IP-адресом, где установлен Linux: в моем случае это был Ubuntu 16.10. Представленные примеры будут работать и на других дистрибутивах Linux, с той разницей, что нужно будет адаптировать установку пакетов под твой пакетный менеджер.

Что делать со старой версией Ubuntu

Так получилось, что мне достался сервер с устаревшими ссылками на репозитории, а следовательно, при попытке обновиться я получал множество ошибок.

Попытка обновиться
Попытка обновиться

Google ненавязчиво подсказал мне, что репозитории были перемещены на поддомен old-releases. Исправляем и обновляемся:

$ 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

Скачиваем образ.

$ docker pull kylemanna/openvpn

На гитхабе автора лежит достаточно полная документация в виде файла README. Им мы и воспользуемся. Для начала нам нужно создать файл конфигурации для сервера VPN.

$ docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_genconfig -u udp://192.168.0.183

Указываем тут протокол, в который будет оборачиваться трафик и белый IP твоего сервера. Давай посмотрим на полученный конфиг.

$ 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. Переходим к генерации сертификатов для удостоверяющего центра.

$ docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_initpki

Будет запрошен пароль для создания ключа удостоверяющего центра.

Теперь генерируется сертификат и ключ сервера, ключи протокола Диффи — Хеллмана, а также подпись HMAC для проверки целостности TLS.

После этого мы должны добавить пользователей. Делается это так же просто.

$ docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn easyrsa build-client-full CLIENTNAME nopass

В качестве CLIENTNAME я буду использовать имя хоста клиента (позже ты узнаешь, для чего это нужно). Создаем сертификат для пользователя user-pc, нас сразу же попросят указать пароль от сертификата удостоверяющего центра. Сертификат готов, но нам-то нужно получить файл конфигурации для OpenVPN. Выполняем следующую команду.

$ docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_getclient user-pc > /root/user-pc.ovpn

Смотрим на результат.

$ 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 в режиме демона.

$ docker run -v /etc/openvpn:/etc/openvpn -d -p 1194:1194/udp --cap-add=NET_ADMIN kylemanna/openvpn

На стороне клиента пробуем подключиться.

$ sudo openvpn --config user-pc.ovpn

Если все прошло успешно, то в конце лога ты увидишь надпись Initialization Sequence Completed, а в списке интерфейсов появится tun0 с IP-адресом из подсети 192.168.255.0/24. Для наших целей этого вполне хватит, нужно только добавить команду на запуск контейнера в автозагрузку и переходить к части с настройкой DNS.

Ручная настройка OpenVPN

Второй вариант — это настроить OpenVPN полностью вручную. Поехали по шагам.

1. Устанавливаем OpenVPN.

$ sudo apt install -y openvpn

2. Скачиваем easy-rsa.

$ 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 содержит полный перечень доступных переменных, но нам хватит и этого.

set_var EASYRSA_KEY_SIZE    2048
set_var EASYRSA_CA_EXPIRE   3650
set_var EASYRSA_CERT_EXPIRE 3650

4. Инициализация PKI.

$ cd /etc/openvpn/easy-rsa/easyrsa3 && ./easyrsa init-pki

5. Создание центра сертификации. Указываем пароль и Common Name.

$ ./easyrsa build-ca

6. Создание ключей сервера. Везде используем имя хоста как Common Name для создания сертификата.

$ ./easyrsa build-server-full vpnserver nopass

7. Создание ключей клиента.

$ ./easyrsa build-client-full user-pc nopass

8По желанию: если нужно сгенерировать ключи Диффи — Хеллмана и TLS.

$ ./easyrsa gen-dh
$ openvpn --genkey --secret /etc/openvpn/easy-rsa/easyrsa3/pki/ta.key

На этом создание сертификатов завершено, переходим к написанию конфига для VPN-сервера /etc/openvpn/server.conf:

## Адрес твоего сервера
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. Подправим его для работы с нашим конфигом.

$ 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

Получив путь к файлу, немного изменим его под наши нужды.

## /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

Запускаем сервис.

$ sudo systemctl enable openvpn.service && sudo systemctl start openvpn.service

И проверяем на ошибки.

$ journalctl -uxe openvpn.service

В прошлом примере с Docker у нас была возможность получить конфиг клиента одной командой. Пора написать для этого скрипт.

#!/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, устанавливаем зависимости.

$ 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

Теперь можно приступать к компиляции.

$ 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.

После успешной сборки нам осталось добавить пару нюансов. Во-первых, создать пользователя.

$ sudo useradd -s /bin/false -d /etc/unbound unbound

Во-вторых, добавить сервис /lib/systemd/system/unbound.service. Его содержимое будет таким.

## /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.

$ 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.

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.

$ sudo unbound-anchor -a /etc/unbound/trusted-key.key

Союз с NetworkManager

Для обновления файла /etc/resolv.conf нам потребуется утилита openresolv. Создадим для нее конфиг /etc/resolvconf.conf.

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:

[main]
rc-manager=resolvconf

Запуск Unbound

Запускаем резолвер командой

$ sudo systemctl start unbound

После этого все должно работать.

WARNING

Важно убедиться, что перед запуском Unbound у тебя отключены другие DNS-серверы или резолверы! Возможные ошибки и их причину можешь узнать, написав journalctl -xeu unbound.

Блокировка рекламы

Переходим к самой важной части всего нашего процесса. Поискав информацию в Сети и посмотрев фильтры uBlock, который установлен в моем браузере, я нашел несколько списков с доменами:

Создаем скрипт /etc/unbound/local.d/update_ads_filters.py, который будет это все парсить и генерировать правила для нашего Unbound.

#!/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)

Тут все очень просто: для каждого домена создается следующая запись.

local-zone: "HOSTNAME" redirect
local-data: "HOSTNAME A 127.0.0.1"

Как потом выяснилось, можно вместо редиректа на 127.0.0.1 использовать более простую запись и возвращать NXDOMAIN. Если коротко, то это означает, что запрашиваемый домен не существует.

local-zone: "HOSTNAME" always_nxdomain

Добавляем этот скрипт в crontab с периодом запуска не чаще одного раза в неделю (не думаю, что домены будут обновляться чаще) и запускаем.

# /etc/unbound/local.d/update_ads_filters.py > /etc/unbound/local.d/adservers.conf && unbound-control reload

Дополнительно создадим еще один скрипт /etc/unbound/insert_filter_domain.sh, который позволит вручную блокировать необходимые домены. Я с его помощью добавил Яндекс.Метрику.

#!/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), добавив туда следующие строки:

## Разрешаем скриптам запуск сторонних приложений
script-security 2
## Назначаем скрипт, который будет запускаться при подключении и отключении клиента
client-connect /etc/openvpn/connect.sh
client-disconnect /etc/openvpn/connect.sh

Сам скрипт /etc/openvpn/connect.sh выглядит так.

#!/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 — событие, вызвавшее запуск скрипта, доступны варианты updownipchangeroute-uptls-verifyauth-user-pass-verifyclient-connectclient-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, который будет содержать записи для резолва имени текущего сервера и всей подсети в целом.

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.

Идеально!

Источник — xakep.ru

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

*