Невозможно отучить людей изучать самые ненужные предметы.
Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3
Надо знать обо всем понемножку, но все о немногом.
Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы
Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)
Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода
Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5
Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости
Справочник от А до Я
HTML, CSS, JavaScript
Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы
Помогли мы вам |
Поэтому мы сейчас попробуем «включать чуть‑чуть VPN».
При таком использовании это скорее прокси, а не VPN, но любые методы обхода блокировок теперь называют VPN, так что, сделав эту оговорку, дальше расслабимся и будем делать как все.
Заодно чуть улучшим качество связи с локальными ресурсами: необходимость таскать трафик сначала до VPN вне страны, а потом обратно до сервера внутри нее драматично сказывается если не на скорости, то на задержке точно. Даже на проводном интернете пинг в 4 мс до Яндекса легко превращается в 190 мс, а на мобильном интернете — из 80 мс в 240 мс. Дополнительный хоп внутри страны чуть ухудшит дело, но далеко не так драматично.
Делать все мы будем на основе WireGuard — это относительно новая (разрабатывается с 2016 года, в отличие от OpenVPN и IPsec: первый — это двухтысячные, а второй еще раньше) технология VPN. Создал ее, по сути, один человек — zx2c4, которого в миру зовут Джейсоном Доненфельдом. Плюсы WG — скорость (особенно для Linux, где он может работать как модуль ядра, начиная с Kernel 5.6, и Windows, где модуль для ядра выпустили около недели назад), низкие задержки, современная криптография и простая настройка и использование конечным юзером.
Ах да, еще UDP. UDP для туннелей — это хорошо, потому что у TCP уже есть механизмы, которые позволяют ему работать на неидеальных соединениях, а UDP представляет собой именно такое соединение. А когда ты засовываешь TCP в TCP, то отказываешься от большей части этих механизмов (инкапсулированный TCP-пакет будет гарантированно доставлен другой стороне, хотя протокол допускает недоставку), но все еще несешь весь оверхед вида «хендшейк соединения для отправки хендшейка».
Не говоря уж о том, что инкапсулировать UDP в TCP — ничуть не лучшая идея, потому что сразу рушит все предположения всяких скайпов о том, что лучше пропустить пару пакетов, чем уменьшить задержку: каждый UDP-пакет в этом случае будет принудительно послан заново и доставлен корректно, не считаясь с затратами времени.
Особенно для одинокого пользователя‑хакера приятна работа с шифрованием: нет необходимости ни в сертификатах и удостоверяющих центрах, ни в логинах‑паролях, все, что нужно, — обменяться публичными ключами с пиром, с которым ты хочешь установить соединение. Для больших компаний это, конечно, будет скорее минусом, как и то, что WG — это только базовая часть полноценной большой инфраструктуры VPN. Но именно WireGuard использовали, к примеру, в Cloudflare для своего WARP, правда, написав его собственную реализацию — boringtun.
Еще один минус WG — то, что трафик не обфусцирован и глубокая инспекция пакетов (DPI) выявит и позволит заблокировать такое соединение (не говоря уж о блокировке UDP совсем, что почти не мешает работать с вебом, но гарантированно ломает WireGuard). Для скрытия трафика рекомендуется использовать специализированное ПО — Cloak, Obfsproxy, Shadowsocks, Stunnel, SoftEther, SSTP или, в конце концов, простой SSH. Часть из этих инструментов может работать совместно с WG, а часть способна его заменять в качестве инструмента стеганографии: WG изначально создавался под скорость и криптографическую защищенность.
Если очень упрощать, ключи работают следующим образом: у нас есть закрытый (приватный) ключ, из которого можно сгенерировать открытый, или публичный. Наоборот, из открытого ключа получить закрытый мы никак не можем. Затем мы шифруем с помощью закрытого ключа какую‑то строку, а при помощи открытого расшифруем ее и тем самым убедимся, что у собеседника точно есть закрытый ключ, а значит, он тот, за кого себя выдает. Таким образом, мы можем без проблем передавать открытый ключ — он всего лишь позволяет проверить подлинность автора, но не притвориться им.
Это как в SSH — публичный ключ лежит на сервере, где его потеря — небольшая беда: все, что сможет сделать с ним злоумышленник, — это положить его на свой сервер, чтобы ты мог подключиться к нему с помощью закрытого ключа.
Так вот, в WG на первом этапе подключения каждая сторона с помощью зашифрованного приватным ключом сообщения доказывает собеседнику, что она именно она: это проверяется публичным ключом.
Второй этап — создание с помощью этих ключей и матана симметричных ключей для шифрования самого трафика. Благодаря тому что расшифровать зашифрованное публичным ключом нельзя без приватного, мы сможем создать ключ для симметричного шифрования и отправить его по защищенному каналу. Этот шаг необходим потому, что симметричное шифрование гораздо менее ресурсоемкая операция, а минус у нее только один: необходимо синхронизировать ключ между сторонами, при том что перехват ключа третьей стороной ведет к возможности расшифровки трафика.
Но эта проблема решается с помощью асимметричной схемы. Это называется протокол Диффи — Хеллмана — способ защищенного получения общего секретного ключа. В WG используется ECDH — вариация Диффи — Хеллмана на эллиптических кривых. Первые два этапа в терминах WG называются рукопожатием или хендшейком.
Затем симметричные ключи используются для шифрования трафика. Раз в две минуты происходит новое рукопожатие и сессионные ключи меняются.
Разумеется, в реальности все немного сложнее: например, отправляются не сами ключи, а сгенерированные на их основе эфемерные ключи, которые удаляются сразу после операции. Заинтересовавшихся подробностями отправляю к краткому описанию на сайте WireGuard.
Мы же перейдем к более практическим действиям.
Один сервер будет внутри страны — через него трафик пойдет на локальные ресурсы, а второй — за границей. Дальше я их буду называть local и external.
Идеально, если local будет в твоей домашней сети, потому что при этом трафик на внешние ресурсы не отличается от твоего домашнего трафика. Но для этого нужен какой‑то хост дома, белый IP и возможность пробросить порт. У меня это виртуалка на домашнем сервере, но, наверное, подойдет и Raspberry Pi или аналогичный одноплатник.
Вариант с одноплатником я не тестировал и не уверен, что он сработает. Raspberry Pi придется маршрутизировать весь трафик с устройств и держать в памяти около 11 тысяч маршрутов; ресурсов на это может не хватить.
Если дома хоста нет, можно взять любой сервер у хостера VDS. Увы, некоторые сайты блокируют подсети хостеров, опасаясь ботов, так что это менее предпочтительный вариант.
А вот внешнюю машину можно арендовать и у хостера. К примеру, у RU VDS и VDSina есть зарубежные площадки. А можно выбрать иностранного хостера, если найдешь способ оплачивать его услуги. Например, я использую исландский 1984.hosting, созданный специально для параноиков.
Считаем, что на обоих серверах у нас Debian 11.
Ставим нужные нам пакеты:
apt update && apt install -ywireguard iptables ipcalc qrencode curl jq traceroute dnsutils ufw
Включаем перенаправление трафика: в этом случае сервер, получив пакет, который не предназначается ни одному из его IP-адресов, не отбросит его, а попытается перенаправить в соответствии со своими маршрутами.
echo "net.ipv4.ip_forward=1" > >/etc/sysctl.conf
echo "net.ipv4.conf.all.forwarding=1" > >/etc/sysctl.conf
sysctl -p/etc/sysctl.conf
Опционально (но очень удобно) сразу поменять hostname обоих серверов, чтобы не запутаться, где какая консоль:
hostnamectl set-hostname trickster-internal
hostnamectl set-hostname trickster-external
Для начала генерируем ключи. Запускаем два раза wg genkey и получаем два приватных ключа:
root@trikster-internal:~# wg genkey
kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=
root@trikster-internal:~# wg genkey
6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=
Утилита wg genkey не делает ничего волшебного, это просто аналог чего‑то в таком духе:
Только наверняка более случайное: мы просто генерируем 32 байта случайных значений и представляем их в виде Base64.
Создаем два конфига. Один на internal:
Address = 10.20.30.1/32ListenPort = 17968PrivateKey = kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostUp = ip rule add from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table mainPostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostDown = ip rule del from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table main
Второй на external:
Address=10.20.30.2/32PrivateKey=6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE
Секция [Interface] — это настройки конкретного сетевого интерфейса WireGuard, того, что будет виден в ip
. Название интерфейса берется из названия текущего файла конфигурации. У одного интерфейса всегда одна ключевая пара: у пиров этого интерфейса одинаковый публичный ключ.
Но никто не мешает, если хочется, сделать для каждого пира отдельный конфиг и отдельный интерфейс (правда, на сотнях клиентов это будет неудобно).
Управляются интерфейсы обычно при помощи утилиты wg-quick:wg-quick
и wg-quick
Утилита wg-quick
— это на самом деле 400 строк на баше, которые автоматизируют часто используемые вещи, например установку маршрутов. Наличие туннеля само по себе не дает ничего, кроме защищенной «трубы», за которой находится другой пир. Чтобы твой запрос в браузере попал в интерфейс, системе надо явно сказать: «Маршрутизируй, пожалуйста, пакеты с таким‑то адресом назначения вот в этот сетевой интерфейс».
Именно этим занимается wg-quick
. Ну еще и настройкой адресов DNS, указанных в конфиге, установкой MTU и еще парой вещей. Но ничего сложного в этом нет, достаточно сделать cat /
, чтобы посмотреть на эту логику, и, если надо, повторить то же самое руками.
Кроме публичных и приватных ключей, есть еще опция PresharedKey, которая обеспечивает дополнительное шифрование симметричным шифром. Ключ можно сгенерировать, например, командой wg
и добавить в опцию PresharedKey в секциях Peer на обоих пирах. Если не использовать эту опцию, нагрузка по шифрованию и расшифровке не вырастет: когда ключ не указан, используется нулевое значение ключа.
А чтобы по‑настоящему обеспечить постквантовую безопасность (невозможность расшифровки данных квантовыми компьютерами), разработчики рекомендуют дополнительный внешний квантово‑устойчивый механизм хендшейка, например SIDH, который Microsoft пиарит именно в таком контексте. Созданный им общий ключ можно использовать в качестве PresharedKey.
Заклинания в PostUp достаточно просты. Вот команда для подстановки имени сетевого интерфейса, куда по умолчанию выполняется маршрутизация:
Как правило, это интерфейс, обращенный к провайдеру или роутеру.
Таким образом, страшная команда превращается в такую:
iptables -t nat -APOSTROUTING -oeth0 -jMASQUERADE
Здесь происходит включение NAT в режиме маскарада: сервер будет отправлять пришедшие ему пакеты во внешнюю сеть, подменяя адрес отправителя своим, чтобы ответы на эти пакеты тоже приходили ему, а не исходному отправителю.
Вторая команда уже немного сложнее, но она подставляет IP-адрес дефолтного маршрута.
Сначала мы получаем, как и выше, сетевой интерфейс маршрута по умолчанию:
enp1s0
Потом данные о состоянии этого интерфейса:
inet 192.168.88.70/24 brd 192.168.88.255 scope global dynamic enp1s0
И дальше вытаскиваем оттуда адрес, в данном случае 192.168.88.70.
Команда становится такой:
ip rule add from 95.93.219.123 table mainё
Это необходимо для сервера internal, потому что иначе при активации маршрута 0.
он начинает пересылать ответы на пакеты, приходящие ему на внешние адреса через туннель WG. Сервер на том конце, конечно, пересылает их по назначению, но тут уже не готов отправитель пакета: он присылает что‑то на внешний адрес сервера internal, а ответ ему приходит с external.
Естественно, при включенном rp_filter пакет отбрасывается. В этом случае сервер перестает быть доступным, например по SSH снаружи. К нему придется коннектиться только по внутреннему IP WireGuard. Отключать rp_filter — это стрелять из пушки по воробьям, а вот дополнительное правило исправляет ситуацию.
Я намеренно не привожу готовые конфиги, потому что хочу показать механизм создания конфигов в ручном режиме. В свое время я генерировал конфиги утилитами типа easy-wg-quick или веб‑сервисами, которые спрашивают тебя о названии клиента и красиво показывают QR-код. Это отнюдь не способствует пониманию того, как работает WG на самом деле, и может вызвать проблемы.
Теперь в оба конфига надо добавить секцию Peer, чтобы связать серверы друг с другом.
Генерируем из приватного ключа публичный (вот в wg
как раз и происходит криптомагия):
root@:~# echo "kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8="| wg pubkey
MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
Это публичный ключ сервера internal, его мы помещаем в секцию Peer на external:
[Peer]
PublicKey=MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
AllowedIPs=10.20.30.0/24
Endpoint=195.2.79.13:17968
PersistentKeepalive=25
Там же, в Endpoint указываем адрес сервера internal и порт, который мы задали в ListenPort.
С AllowedIPs при использовании wg-quick возникает небольшая путаница. Это именно список IP-адресов, с которых мы разрешаем принимать пакеты из туннеля. Если прилетит что‑то с другим src, оно будет отброшено.
Wg-quick
разумно считает, что, если есть какие‑то устройства, которые могут послать пакет, пакеты к этим устройствам надо маршрутизировать туда же, и создает маршруты на эти адреса, указывающие на туннель пира.
В этих примерах AllowedIPs можно читать как «адреса, трафик на которые будет маршрутизироваться в туннель этого пира и с которых пир сможет отправить что‑то в туннель». То есть пункт AllowedIPs
означает буквально «только запросы на 10.20.30.3 (адрес пира WG) отправлять в туннель» — дать доступ только до машины этого клиента.
Пункт AllowedIPs
означает, что при запросе адреса из этой подсети запрос уйдет в туннель клиента, и если у него включен форвардинг и ему доступна эта подсеть, то к ней можно будет получить доступ.
AllowedIPs
означает, что в туннель надо маршрутизировать вообще весь трафик. Правда, это не относится к трафику, например, локальной сети: приоритет у маршрута, который создается из маски подсети и адреса шлюза, выше, чем у 0.0.0.0/0. Также маршрут 0.0.0.0/0 перебьют маршруты других пиров, если они будут в конфиге.
В данном случае AllowedIPs=10.
означает, что трафик с external в подсеть 10.20.30.0–10.20.30.255 будет уходить в туннель к internal. В принципе, особой нужды в этом нет, external у нас исключительно выходная нода. Но вдруг мы как‑нибудь захотим зайти оттуда по SSH на какую‑нибудь другую машину.
Повторяем генерацию публичного ключа с external:
root@:~# echo "6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28="| wg pubkey
FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
Мы получаем публичный ключ сервера external и помещаем его в секцию Peer сервера internal.
[Peer] #external node
PublicKey = FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
AllowedIPs = 10.20.30.2/32, 0.0.0.0/0
AllowedIPs тут 10.
. Этим мы указываем, что за туннелем находится конкретный IP 10.20.30.2, и, помимо этого, пробрасываем весь трафик, не связанный другими маршрутами, в этот туннель: external у нас основная выходная нода VPN.
Поэтому по умолчанию весь трафик будет направляться через нее, так как зарубежных маршрутов больше, чем российских, и логичнее фильтровать именно российские, а зарубежный трафик пустить по умолчанию через ноду в другой стране.
Итак, два конфига.
Address = 10.20.30.1/32ListenPort = 17968PrivateKey = kOd3FVBggwpjD3AlZKXUxNTzJT0+f3MJdUdR8n6ZBn8=PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostUp = ip rule add from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table mainPostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostDown = ip rule del from `ip addr show $(ip route | awk '/default/ { print $5 }') | grep "inet" | grep -v "inet6" | head -n 1 | awk '/inet/ {print $2}' | awk -F/ '{print $1}'` table main#external node[Peer]PublicKey = FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=AllowedIPs = 10.20.30.2/32, 0.0.0.0/0
Address = 10.20.30.2/32PrivateKey = 6CCRP42JiTObyf64Vo0BcqsX6vptsqOU+MKUslUun28=PostUp = iptables -t nat -A POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADEPostDown = iptables -t nat -D POSTROUTING -o `ip route | awk '/default/ {print $5; exit}'` -j MASQUERADE#internal node[Peer]PublicKey = MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=AllowedIPs = 10.20.30.0/24Endpoint = 195.2.79.13:17968PersistentKeepalive = 25
Теперь можно поднять туннели на обоих серверах:
root@trikster-external:~# wg-quick down wg-external ;wg-quick up wg-external
root@trikster-internal:~# wg-quick down wg-internal ;wg-quick up wg-internal
Проверяем, что туннели активны, командой wg
:
root@trikster-internal:~# wg
...
peer:FulnUTovyyfgn5kmgPkcj2OjKRFGeLkaTsHtAOy6HW8=
endpoint:51.159.187.77:36276
allowed ips:10.20.30.2/32,0.0.0.0/0
latest handshake:13 seconds ago
transfer:180 B received,92 B sent
root@trikster-external:~# wg
...
peer:MxnOnIlKfSyZyRutnYyoWHb3Izjalgf1t8F1oPJiyyw=
endpoint:195.2.79.13:17968
allowed ips:10.20.30.0/24
latest handshake:10 seconds ago
transfer:92 B received,180 B sent
persistent keepalive:every 25 seconds
Если видим «latest handshake: ... seconds ago» и байты и в received и в sent, значит, все хорошо. Если байты только в send, без хендшейка и полученных данных, значит, где‑то в конфиге ошибка или серверы недоступны друг для друга.
Если что‑то пошло не так и отвалился SSH, то достаточно перезагрузить сервер — активные туннели сбросятся.
Если же все хорошо и доступ к серверам сохранился, ставим туннели в автозапуск:
root@trikster-internal:~# systemctl enable wg-quick@wg-internal.service
root@trikster-external:~# systemctl enable wg-quick@wg-external.service
Попробуем посмотреть маршрут (рекомендую замечательную утилиту mytraceroute
, mtr
) без туннеля:
root@trikster-internal:~# wg-quick down wg-internal && sleep 10 && mtr -rgoogle.com
HOST:trikster-internal.local Loss% Snt Last Avg Best Wrst StDev
1.|-- host-89-22-232-243.hosted 0.0% 10 0.3 5.4 0.3 49.8 15.6
2.|-- 172.31.0.1 0.0% 10 0.3 19.8 0.3 122.2 42.6
3.|-- 109.239.138.90 0.0% 10 1.5 1.9 1.4 3.0 0.6
4.|-- 91.108.51.4 0.0% 10 11.4 11.4 11.3 11.7 0.1
5.|-- 178.18.227.12.ix.dataix.e 0.0% 10 11.0 17.9 11.0 77.0 20.8
И с туннелем:
root@trikster-internal:~# wg-quick up wg-internal && sleep 10 &&mtr -rgoogle.com
HOST:trikster-internal.local Loss% Snt Last Avg Best Wrst StDev
1.|-- 10.20.30.2 0.0% 10 51.3 51.3 51.2 51.4 0.1
2.|-- 10.200.100.0 0.0% 10 51.4 51.4 51.2 51.6 0.1
3.|-- 10.197.37.65 0.0% 10 52.5 52.2 52.0 52.5 0.2
4.|-- 10.197.0.41 0.0% 10 52.2 52.2 52.1 52.5 0.1
5.|-- 10.197.0.44 0.0% 10 52.0 52.2 51.9 52.4 0.1
Все хорошо, трафик идет через внешний сервер — сначала на 10.20.30.2, который у нас назначен выходной нодой, а потом через его маршрутизаторы.
У нас получилась примерно следующая схема.
Создаем конфиг клиента, конечного устройства — пользователя VPN. За основу берем wg-external.
, потому что это точно такой же клиент, который подключается к internal: разница только в том, что external будет получать пакеты, а наш клиент — отправлять.
|
|