Skip to main content

WireGuard: перспективный VPN

VPN придуман давно (IPSec - в 1998 году, например) и имеет множество областей применения - безопасный доступ для удаленных сотрудников, прозрачное объединение корпоративных сетей, безопасный доступ в интернет поверх небезопасных каналов, даже - уклонение от корпоративной и государственной цензуры. Протоколов VPN - целый выводок, а реализации (программы, ПАК, даже чистые аппаратные решения без ПО есть) - еще больше. При этом каждый каждый стандарт имеет свои недостатки. WireGuard - это очередной протокол VPN, попытка эти проблемы решить. При всех плюсах WG (про них - ниже) - он мало известен и на удивление плохо документирован. Эта статья - попытка устранить эти недостатки.

WireGuard - это протокол VPN, который был придуман и реализован полностью с нуля - с применением последних, самых свежих концепций в программировании и криптографии. Главная задача, которую ставил перед собой автор (Джейсон Доненфелд) - простота протокола и реализации и высокая скорость работы. Протокол предельно простой, по-моему даже PPP сложнее. Очень простое взаимодействие операционной системы с VPN - WireGuard добавляет в систему сетевой интерфейс типа wireguard (по умолчанию - wgX), маршрутизация трафика идет через него. VPN полностью реализован на уровне ядра (для Linux, для других операционных систем поддерживается работа в виде обычной программы) - он очень быстро работает. Трафик упаковывается в совершенно типовой UDP, не нужны специфичные IP-протоколы (как это сделано в PPP/IPSec). Автор решил не использовать классические legacy-протоколы шифрования и взял самые свежие, но доказанно безопасные протоколы:

  • ChaCha20 (salsa) - симметричное потоковое шифрование данных
  • Curve25519 ECDH - ассиметричное шифрование для авторизации и аутентификации
  • Blake2s - для хеширования

По скорости работы WireGuard обгоняет IPSec, даже с учетом того, что IPSec жульничает - современные CPU часть криптопримитивов могут выполнять в одну инструкцию.

При этом по простоте и удобству настройки WireGuard проще OpenVPN.

В качестве бонуса - Торвальдс очень хвалил код WireGuard (а Торвальдс известен нетерпимостью к плохому коду и не станет раздавать незаслуженные комплименты) и обещал, что код войдет в базовое ядро в ближайшем будущем. Звучит очень интересно, не так ли?

Разумеется, минусы у него тоже есть, и не упомянуть их было неправильно:

  • wireguard формально находится в стадии активной разработки. До сих пор не вышло ни одной формальной версии, то есть даже wireguard 0.0.0.1 - нет в природе. Авторы не обещают и не гарантируют обратной совместимости протокола, то есть при одном удачном обновлении у вас легко может рассыпаться сеть. За пол-года, что я использовал WG - у меня такого не было ни разу, но в теории такая возможность сохраняется до выхода стабильной версии.
  • wireguard не является стандартным и стабильным решением, не существует физического устройства, которое его поддерживает официально (как IPSec, например). Частично можно компенсировать тем, что все современные linux-based прошивки для маршрутизаторов (dd-wrt, OpenWRT, tomato) - поддерживают его с минимальным набором телодвижений. OpenVPN это обстоятельство, кстати, никак не мешает - из железяк его поддерживает разве что mikrotik и делает это настолько отвратительно, что лучше бы не поддерживал вовсе.
  • wireguard не проходил формального аудита безопасности. Протоколы, которые он используют - аудит проходили, реализация - нет. Прохождение аудита у авторов запланировано после стабилизации кодовой базы. Как следствие - сертифицировать решение на базе wireguard невозможно. Если вы работаете в банке - пожалуйста, не используйте wireguard.
  • криптопримитивы в wireguard прибиты гвоздиками и поменять их невозможно. Только ECDH для авторизации, только salsa20 для потоковых шифров, только UDP в качестве транспорта. С одной стороны - это здорово упрощает жизнь – невозможно перепутать настройки протоколов и провести пару вечеров за отладкой. С другой - если в каком-то из этих примитивов найдут серьезную дыру - придется переписать пол-протокола или вообще все выкинуть. Частично компенсируется тем, что весь Bitcoin (и не только он) построен вокруг ECDH, так что если там найдут дыру - проблемы wiregaurd будут мелочью.

В целом я могу уверенно рекомендовать wireguard для личного использования или как VPN-протокол в малокритичных сетях (без SLA и с командой, которая готова с ним разбираться). В общем случае, если у вас есть OpenVPN на linux - WireGuard идеально может его заменить. WireGuard напоминает мне OpenVPN в те времена, когда тот еще только появился и не успел стать legacy - простой, удобный, быстрый в настройке VPN.

Установка

Debian/Ubuntu

Так, как стабильных релизов wireguard нет - нам потребуется DKMS для сборки модулей ядра. Пакеты для WireGuard для debian stretch/buster не выпускались, но они есть для debian sid. Добавим репозиторий unstable и создадим pin. Если добавить репозиторий и не создавать пин - все пакеты из sid приедут в ваш сервер. Удачно запущенный apt-get install превратит систему в кашу:

# echo "deb http://deb.debian.org/debian/ sid main" > /etc/apt/sources.list.d/unstable.list
# printf 'Package: *\nPin: release a=unstable\nPin-Priority: 90\n' > /etc/apt/preferences.d/limit-unstable.pref

К счастью, в ubuntu все необходимые пакеты уже есть и дополнительные телодвижения не нужны. Ставим модуль:

# apt-get update
# apt-get install wireguard wireguard-dkms wireguard-tools

Проверим, что все установилось:

# ip link add dev wg0 type wireguard
# ip link print
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
[...]
25: wg0: <POINTOPOINT,NOARP> mtu 1420 qdisc noop state DOWN mode DEFAULT group default qlen 1
    link/none

CentOS/RHEL/OracleUBL

Владельцам rpm-based дистрибутивов повезло больше - есть готовый отдельный репозиторий, так что достаточно поставить его и EPEL (хотя мне сложно представить rpm-based дистрибутив без EPEL):

# curl -Lo /etc/yum.repos.d/wireguard.repo https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo
# yum makecache
# yum install epel-release
# yum install wireguard-dkms wireguard-tools

Настройка

Сервер

Wireguard использует авторизацию только по ключам (keypair). Ключи генерирует сам. Авторизация клиента производится по его публичному ключу, то есть на сервере должны быть публичные ключи всех клиентов, а на клиентах - публичный ключ сервера. Создадим такой ключ:

server# umask 077
server# wg genkey > server.priv
server# wg pubkey < server.priv > server.pub

На выходе получим два ключа, приватный и публичный. В принципе wireguard можно полностью управлять через команду ip, но это не сказать, чтобы сильно удобно. Для того, чтобы упростить настройку, авторы wireguard написали утилиту wg-quick. Она читает конфиг из /etc/wireguard/wgX.conf и создает соответствующий интерфейс. Пример конфига /etc/wireguard/wg0.conf:

[Interface]
Address = 192.168.254.1/24
SaveConfig = false
ListenPort = 25968
PrivateKey = [...]
PostUp   = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Что тут написано:

  • address - адресное пространство VPN-сети. Этот адрес будет присвоен интерфейсу wg0. Все клиенты, которые подключены к одному интерфейсу - должны иметь адреса в одной подсети, иначе они не смогут общаться.
  • saveconfig = false - если что-то было изменено после wg-quick вручную (командой ip link) - по умолчанию изменения запишутся в конфиг. Побочный эффект - если во время работы wireguard вы что-то в конфиге поменяете и перезапустите wireguard - все изменения будут стерты.
  • ListenPort - на какой порт должны приходить UDP-датаграммы, предназначенные для этого конкретного интерфейса wireguard. Никто не запрещает создать несколько интерфейсов wireguard - каждый с отдельным ключом, адресным пространством и списком клиентов. Этот порт должен быть открыт снаружи для UDP
  • PrivateKey - ключ из server.priv (из шага выше).
  • PostUp - добавляет правила в фаерволл - разрешает форвардинг для интерфейса и включает nat. Эта строка вам не нужна, если правила добавлены вручную. В данном примере считается, что внешний интерфейс сервера - eth0.
  • PostDown - удаляет правила, который добавил PostUp

На всякий случай включим форвардинг пакетов:

server# sysctl -w net.ipv4.ip_forward=1
server# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf

Клиент

Точно так же ставим wireguard и генерируем ключи, как в примере выше:

client# umask 077
client# wg genkey > client.priv
client# wg pubkey < client.priv > client.pub

А вот содержимое конфига будет слегка иным:

[Interface]
PrivateKey = <from client.priv>
Address = 192.168.254.2/24
ListenPort = 25967
Table=wire

[Peer]
PublicKey = <from server.pub>
Endpoint = <server ip>:25968
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 30

Что появилось в конфиге нового. Секция Interface как и раньше - отвечает за настройку самого сервиса wiregurad:

  • Address - из той же подсети, что и адрес сервера, но уникальный.
  • Table = wire - нужно, чтобы wireguard знал, в какую таблицу записывать маршруты. Они описаны в разделе peer (о нем ниже) и в момент, когда связь с peer-ом (сервером) будет установлена - маршруты будут добавлены. Если таблицу не указывать - будет использована системная. Это может вызвать проблему, если VPN выдает маршрут по умолчанию (как здесь). Чуть ниже я покажу, как с этим жить.

Секция peer настраивает параметры подключения к другой стороне:

  • PublicKey - публичный ключ сервера. Именно по нему клиент авторизует сервер.
  • Endpoint - внешний адрес и порт сервера.
  • AllowedIPs - обращения к каким адресам можно отправлять в этот туннель. При поднятии туннеля маршрут ко всем адресам (сетям), перечисленным тут - будет добавлен в таблицу маршрутизации из секции Interface. Если какой-то сети или адреса тут нет - ручное добавление маршрута не поможет, wireguard отклонит такой маршрут. В данном примере я использую wireguard-сервер как шлюз по умолчанию, то есть хожу в интернет через него. Если сетей нужно добавить несколько - их можно перечислить через запятую.
  • PersistentKeepalive - интервал keepalive в секундах. По умолчанию данные в туннеле есть только тогда, когда кто-то что-то через него передает. KeepAlive периодически пингует удаленную сторону и поддерживает соединение. Это важно в тех случаях, если вы сидите за nat - сервер не может поддерживать соединение с клиентом, nat gateway разорвет неактивное соединение. Если после этого сервер захочет передать пакет клиенту - он не сможет этого сделать.

Включаем wiregurad на клиенте и идем настраивать на сервер:

client# wg-quick up wg0

Чтобы клиент запускал wireguard автоматически - добавим его в автозагрузку:

client# systemctl enable wg-quick@wg0

Снова сервер

В /etc/wireguard/wg0.conf нужно вписать клиента, которого мы только что создали выше. Добавим секцию:

[Peer]
PublicKey = <from client.pub>
AllowedIPs = 192.168.254.2/32, 192.168.1.1/24
Endpoint = <client public IP>:25967

Обратите внимание на то, что в AllowedIPs указан, в том числе, адрес интерфейса wireguard на клиенте. Это очень важно, без этого трафик ходить не будет. Про него часто забывают. В примере выше через VPN маршрутизируется сеть 192.168.1.0/24, потому она указана тоже.

Поднимем VPN на сервере тоже и проверим:

server# wg-quick up wg0
server# wg show

interface: wg0
  public key: [...]
  private key: (hidden)
  listening port: 25968

peer: [...]
  endpoint: 1.2.3.4:25967
  allowed ips: 192.168.254.2/32, 192.168.1.0/24
  latest handshake: 1 minute, 35 seconds ago
  transfer: 1.84 KiB received, 2.84 KiB sent

Настройка VPN, в общем-то, закончена. Как я и говорил выше - восхитительно просто.

Лирическое отступление: маршрутизация через таблицы и маркировка трафика

Как я уже упоминал выше - по умолчанию wireguard добавляет все маршруты, указанные в AllowedIPs в основную таблицу маршрутизации. Linux не поддерживает policy based routing, соответственно там нельзя создать “условные маршруты” вида “если клиент из офисной сети - маршрут в google пустить через VPN”. Default gateway через VPN вообще кончится потерей связи, так как сервер потеряет маршрут до VPN-сервера. Выход есть - и называется он “таблицы маршрутизации”. Linux поддерживает 255 таблиц маршрутизации (точнее - 251, так как есть зарезервированные таблицы, которые нельзя удалить). Для решения перечисленных выше проблем мы просто создадим отдельную таблицу маршрутизации, а потом с помощью iptables будем ловить трафик, предназначенный для VPN и отправлять его в нужную нам таблицу. При этом клиентская сеть сохранит доступ к серверу и мы сможем гибко настраивать потоки трафика. К примеру - пустить через VPN только трафик для google/youtube, а трафик в Яндекс пустить по старому маршруту по умолчанию. Создадим таблицу:

client# echo "200 wire" >> /etc/iproute2/rt_tables

Теперь создадим правило iptables, которое будет помечать нужный нам трафик. Этот трафик должен быть отправлен в VPN:

client# iptables -I PREROUTING -t mangle -i eth0 -j MARK --set-mark 1

В данном примере я отправляю в VPN весь трафик, который приходит на интерфейс eth0 (в моем случае это внутренний интерфейс)

Теперь нам нужно правило (ip rule), которое перенаправит трафик с меткой 0х1 в таблицу wire:

ip rule add fwmark 0x1 lookup wire

Чтобы правило не потерялось при перезагрузке сервера или пересоздании интерфейса - я набросал простейший скрипт и положил его в /etc/network/if-up.d (это пример для debian). Таким образом правило будет добавляться каждый раз, когда интерфейс переходит в активное состояние (UP):

#!/bin/bash
if [[ ! $(ip rule list | grep "fwmark 0x1 lookup wire") ]]; then
    /bin/ip rule add fwmark 0x1 lookup wire
fi

Этому скрипту нужно обязательно дать права на выполнение (chmod +x). Название скрипта не важно совершенно, главное - место, где файл лежит и права.

Еще одну проблему подарит rpfilter. В нормальной ситуации это полезная настройка - она позволяет отсекать фальшивые (forged) пакеты, которые пришли не на тот интефейс, с которого уходили. К примеру, у нас есть eth0 (192.168.1.1) и eth1 (172.16.1.1). Пакет отправляется с source address 192.168.1.2 (интерфейс eth0) а ответ приходит на eth1. Это или что-то странное (что редкость) или попытка взлома (что более вероятно). RPFilter отсекает такие пакеты. Проблема в том, что с точки зрения rpfilter пакеты, уходящие в другую таблицу - будут “проваливаться в никуда”, а ответные - “возникать из ниоткуда”. Чудеса телепортации rpfilter не любит, так что его придется отключить, иначе не видать вам трафика через vpn:

client# sysctl -w net.ipv4.conf.eth0.rp_filter=2
client# sysctl -w net.ipv4.conf.wg0.rp_filter=2

Чтобы эти изменения не пропали при перезагрузке - добавим их в /etc/sysctl.conf:

client# printf "net.ipv4.conf.eth0.rp_filter=2\nnet.ipv4.conf.wg0.rp_filter=2" >> /etc/sysctl.conf

Лирическое отступление номер два: добавляем исключения. IPSET.

Иногда хочется пустить через VPN только часть трафика или наоборот - исключить часть трафика из прохождения через VPN. В принципе все это можно сделать исключительно правилами чистого iptables. Например, VPN для нескольких серверов будет выглядеть примерно так:

iptables -I PREROUTING -t mangle -i eth0 -d 1.2.3.4 -j MARK --set-mark 1
iptables -I PREROUTING -t mangle -i eth0 -d 1.2.5.5 -j MARK --set-mark 1
iptables -I PREROUTING -t mangle -i eth0 -d 1.2.5.6 -j MARK --set-mark 1
iptables -I PREROUTING -t mangle -i eth0 -d 1.2.6.7 -j MARK --set-mark 1
iptables -I PREROUTING -t mangle -i eth0 -d 1.2.7.8 -j MARK --set-mark 1

Выглядит, честно говоря, ужасно. Что намного хуже - работать будет довольно медленно, поскольку каждый пакет проходит все правила iptables по очереди - пока не наткнется на правило, которое его обработает. Пара тысяч правил застави задуматься даже весьма приличный сервер. Пара миллионов - уложит машину на лопатки, пакеты будут путешествовать по недрам netfilter минутами - это вечность. Для решения этой проблемы был придуман ipset. IPSet - это простой и очень эффективный способ хранить множество адресов в памяти компактно, а искать в этом списке - быстро. Память выделяется для ipset в момент создания set (даже, если set пуст). IPSet в кратчайшие сроки ответит на вопрос - есть ли в нем тот или иной адрес - время ответа приближено к O(1), то есть скорость проверки наличия адреса в ipset в общем-то не зависит от размера. В штатную поставку операционной системы ipset не входит, но ставится очень легко:

client# apt-get install ipset

IPSet предоставляет разные варианты sets - они позволяют хранить разные вещи, но требуют разного количества памяти на одну запись. Для примера создадим set, который хранит подсети (адрес + маску):

client# ipset create novpn hash:net hashsize 65535

это создаст set novpn емкостью 65535 записей. Изменить размер или тип сета после создания будет нельзя, так что выбирать придется с умом.

Удалим старое правило (которое отправляет в VPN весь трафик из внутренней сети) и добавим новое - оно будет отправлять трафик в VPN, если адреса нет в set-е novpn:

client# iptables -t mangle -D PREROUTING 1
client# iptables -I PREROUTING -t mangle -i eth0 -m set ! --match-set novpn dst -j MARK --set-mark 1

Теперь добавим какое-нибудь исключение. Например, avito.ru очень не любит дешевые VPS, на которых часто делают VPN-сервера - мошенники активно используют такие же сервера, чтобы воровать с авито данные, а потому большинство сетей крупнейших облачных провайдеров на авито просто забанены.

client# ipset add novpn 185.89.12.0/24

Проверим:

client# ipset list
Name: novpn
Type: hash:net
Revision: 6
Header: family inet hashsize 65536 maxelem 65536
Size in memory: 1376
References: 1
Members:
185.89.12.0/24

Теперь трафик к avito (185.89.12.0/24) пойдет напрямую, а весь остальной - через VPN.

К слову - можно сделать и наоборот, то есть пустить через VPN трафик, который попадает в ipset. Правило будет выглядеть чуть-чуть иначе:

client# iptables -I PREROUTING -t mangle -i eth0 -m set --match-set tovpn dst -j MARK --set-mark 1

Все остальные принципы работы с ipset сохраняются. Рекомендую.

Клиент: отдельный ноутбук на linux (roadwarrior)

Wireguard ставится ровно так же, как в примере выше:

notebook# apt-get install wireguard wireguard-dkms wireguard-tools

С ключами тоже никаких сюрпризов:

notebook# umask 077
notebook# wg genkey > notebook.priv
notebook# wg pubkey < notebook.priv > notebook.pub

Ключи создаются точно так же. Пример конфига ноутбука (/etc/wireguard/wg0.conf):

[Interface]
PrivateKey = <from notebook.priv>
Address = 192.168.254.2/24

[Peer]
PublicKey = <from server.pub>
Endpoint = 1.2.3.4:25967
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 21

Включаем командой wg-quick up wg0. Выключаем, соответственно - wg-quick down wg0. Интеграции с netmanager пока что нет, так что включать-выключать конфиг графической кнопкой не выйдет.

Не забудьте добавить содержимое notebook.pub на сервер - в секцию [Peer].

Задача со звездочкой - подключаем мобильное устройство

Wireguard есть как для iOS, так и для android. В первом случае он сделан как userspace процесс, но протокол реализует тот же. Работает, конечно, медленнее, но не думаю, что это будет заметно. Ставится как любое типичное приложение, а вот настройки создаются интересно.

Создадим на сервере ключи для мобильного:

server# wg genkey | tee mobile.priv | wg pubkey > mobile.pub

Добавим еще один peer в конфиг сервера:

[Peer]
PublicKey = <from mobile.pub>
AllowedIPs = 192.168.254.3/32

Перезапустим wg:

server# wg-quick down wg0
server# wg-quick up wg0

создадим конфиг для мобильного с примерно таким содержимым:

[Interface]
PrivateKey = <from mobile.priv>
Address = 192.168.254.3/32
DNS = 8.8.8.8

[Peer]
PublicKey = <from server.pub>
Endpoint = 1.2.3.4:25967
AllowedIPs = 0.0.0.0/0

В целом все выглядит знакомо. Теперь нужно передать конфиг на мобильное устройство. Проще всего это сделать, закодировав конфиг в QR-code:

server# qrencode -t ansiutf8 < mobile.conf

На выходе будет картинка - QR-код. Открываем wireguard на мобильном, жмем + -> from QR code -> показываем картинку. Готово, мобильное устройство получило конфиг и теперь VPN можно включать штатными средствами.

Ключи mobile и конфиг рекомендуется удалить с сервера - чтобы никто их не украл.

Выводы

WireGuard - прекрасная альтернатива openvpn уже сейчас. Простой, легко и быстро настраиваемый сервис. Работает очень стабильно, при этом - очень быстро. Я горячо рекомендую его как замену openvpn - как в корпоративном секторе, так и для личного использования.

Безопасного вам интернета!