Свой VPN на WireGuard: сервер на Ubuntu, клиенты на Windows и Android
Четыре года назад я писал, как поднять свой IKEv2 VPN на strongSwan, и эта статья до сих пор одна из самых читаемых в блоге. С тех пор у IKEv2 появился конкурент, который делает то же самое заметно проще: WireGuard. Там, где strongSwan требует удостоверяющий центр, сертификаты, выбор алгоритмов и три конфигурационных файла, WireGuard обходится парой ключей и десятью строками. Сегодня — как поднять такой сервер на Ubuntu с нуля и подключить к нему Windows и Android.
Что такое VPN в терминах интерфейсов и маршрутов, чем полный туннель отличается от раздельного и откуда берутся утечки DNS и IPv6, разобрано в двадцатой части «Сетей для самых маленьких». Здесь это не повторяется, зато всё, что встречается по ходу, объясняется на месте. Как устроен протокол WireGuard внутри — рукопожатие, криптография, формат пакетов — для настройки знать не нужно, и в этой статье этого нет.
Адреса в статье документационные: сервер 203.0.113.10 и 2001:db8::10, сеть туннеля 10.66.66.0/24. Ключи — заглушки в угловых скобках.
Как устроен WireGuard
Пять вещей, которых достаточно, чтобы понимать каждую строку конфига.
Ключи вместо сертификатов и паролей. У каждого участника — сервера, ноутбука, телефона — своя пара ключей: закрытый, который никуда не уходит, и открытый, который сообщают другой стороне. Сервер знает открытые ключи всех своих клиентов, клиент — открытый ключ сервера. Этого достаточно и чтобы узнать друга, и чтобы договориться о шифровании. Ни удостоверяющего центра, ни сроков действия, ни логинов. Открытый ключ — это и есть «имя» устройства в WireGuard. Ключ — 32 байта, записанные в base64, строка из 44 символов вида gI6EdUSYvn8ugXOt8QQD6Yc1SMvDv31U6PSYwpeIE1E=.
Один UDP-порт. WireGuard работает поверх UDP (девятая часть), по умолчанию порт 51820, но можно любой. Нет отдельных портов для согласования и для данных, как у IPsec, нет TCP-соединения, которое нужно держать. Клиент может сменить адрес — переключиться с Wi-Fi на мобильную сеть, — и туннель продолжит работать с нового адреса без переподключения: сервер просто запомнит, откуда пришёл последний правильный пакет.
Интерфейс и маршруты. Как и любой VPN, WireGuard на машине — это сетевой интерфейс (wg0) с адресом из «внутренней» сети туннеля и маршруты, которые в него ведут. Пакет, попавший в интерфейс, шифруется и уезжает по UDP на адрес другой стороны; пришедший по UDP — расшифровывается и появляется из интерфейса как обычный IP-пакет. Всё остальное — дело таблицы маршрутов.
AllowedIPs — маршруты и список доступа сразу. Это главная настройка, и она делает две вещи одновременно. Для исходящих пакетов: «пакеты к этим адресам отправлять этому пиру», то есть маршрут. Для входящих: «от этого пира принимать пакеты только с этих адресов отправителя», то есть фильтр. У клиента AllowedIPs = 0.0.0.0/0, ::/0 значит «всё — в туннель» (полный туннель) и «от сервера принимаю ответы откуда угодно». У сервера для каждого клиента AllowedIPs = 10.66.66.2/32 значит «ему — только пакеты для его адреса» и «от него — только пакеты с его адреса»: клиент не сможет подставить чужой адрес, даже если захочет. Эту конструкцию называют cryptokey routing: маршрутизация по ключам.
Нет понятия «подключён». WireGuard не устанавливает соединение. Когда есть что отправить, он проверяет, есть ли свежие ключи сеанса с этим пиром (они живут две минуты), и если нет — делает рукопожатие: один пакет туда, один обратно, и дальше идут данные. Когда данных нет, нет и пакетов, вообще никаких. Из-за этого статус «подключено» в клиентах условный, а чтобы домашний роутер не забыл про UDP-поток за время тишины, есть PersistentKeepalive — о нём ниже.
Чем это отличается от IKEv2 на практике:
| IKEv2/IPsec | WireGuard | |
|---|---|---|
| Что нужно, чтобы доверять друг другу | сертификаты и удостоверяющий центр, или имя с паролем через EAP | открытые ключи обеих сторон |
| Выбор криптографии | согласуется при подключении из списка; можно настроить слабо | фиксирован, настраивать нечего |
| Порты | UDP 500 и 4500, протокол ESP | один UDP-порт |
| Смена адреса клиента | MOBIKE, если обе стороны умеют | само собой |
| Встроен в ОС | Windows, macOS, iOS, Android — без программ | нужен клиент; в ядро Linux встроен с версии 5.6 |
| Конфиг сервера | несколько файлов и секций | один файл, десять строк на сервер и четыре на клиента |
| Код | сотни тысяч строк | около четырёх тысяч |
Встроенность IKEv2 в операционные системы — его главный плюс, и по-прежнему причина, по которой он живёт в корпоративных сетях. Для своего сервера, к которому подключаются свои же устройства, разница не важна: клиент WireGuard ставится за минуту.
Сервер
Нужна виртуальная машина с публичным адресом и Ubuntu. Я беру Ubuntu 26.04, но всё ниже одинаково работает с 20.04: модуль WireGuard есть в ядре начиная с 5.6, а в более старые ядра Ubuntu его бэкпортировала. Ресурсов хватает самых маленьких — 1 vCPU и 1 ГБ памяти держат десяток клиентов. Вход по SSH ключом под root.
Установка
apt update && apt install -y wireguard-tools qrencode
wg --version
Пакет wireguard-tools — утилиты wg (управление интерфейсом) и wg-quick (поднимает интерфейс по конфигу: адреса, маршруты, DNS). Сам WireGuard — модуль ядра, ставить его не нужно; на старых ядрах вместо wireguard-tools ставится метапакет wireguard, который подтянет модуль. qrencode понадобится для телефона.
Ключи сервера
umask 077
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
cat /etc/wireguard/server.pub # открытый ключ — его получат клиенты
umask 077 — чтобы файлы создались с правами только для владельца: закрытый ключ сервера — самый важный секрет здесь, с ним можно выдать себя за ваш сервер. wg genkey печатает закрытый ключ, wg pubkey читает закрытый и печатает открытый.
Конфиг сервера
/etc/wireguard/wg0.conf:
[Interface]
Address = 10.66.66.1/24, fd66:6666:6666::1/64
ListenPort = 51820
PrivateKey = <закрытый ключ сервера>
Это ядро конфига; правила файрвола добавятся в него ниже. Address — адреса интерфейса wg0 в сети туннеля, IPv4 и IPv6; клиенты получат адреса из этих же сетей. ListenPort — UDP-порт. PrivateKey — содержимое server.key; сам файл после этого можно удалить, ключ живёт в конфиге. Клиенты будут добавляться ниже секциями [Peer].
Сеть туннеля — любая частная, которая не пересекается с сетями, где бывают клиенты: 192.168.0.0/24 и 192.168.1.0/24 стоят в каждом втором роутере, поэтому 10.66.66.0/24 безопаснее. IPv6-сеть — ULA, unique local address, из диапазона fd00::/8: аналог частных адресов для IPv6, в интернет не маршрутизируется.
Форвардинг и NAT
Пока сервер умеет только принимать пакеты из туннеля. Чтобы передавать их дальше в интернет и возвращать ответы, нужны две вещи.
Форвардинг — разрешение ядру пересылать пакеты между интерфейсами, по умолчанию выключенное:
cat > /etc/sysctl.d/99-wireguard.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl --system
NAT — подмена адреса отправителя. Пакет клиента уходит в интернет с адреса 10.66.66.2, на который никто не сможет ответить: это частный адрес. Сервер должен заменить его на свой публичный адрес, а ответ вернуть клиенту — ровно то, что делает домашний роутер (шестая часть). В Linux это правило MASQUERADE в таблице nat netfilter — подсистемы фильтрации пакетов в ядре, которой управляют командой iptables (и ip6tables для IPv6).
Правила удобно держать прямо в конфиге интерфейса: у wg-quick есть PostUp — команды, которые выполняются после поднятия интерфейса, и PostDown — после его остановки. Правила появляются вместе с туннелем и исчезают вместе с ним, ничего сохранять и восстанавливать при загрузке не нужно. Конфиг сервера целиком:
[Interface]
Address = 10.66.66.1/24, fd66:6666:6666::1/64
ListenPort = 51820
PrivateKey = <закрытый ключ сервера>
# IPv4: транзит из туннеля в интернет, ответы обратно, всё остальное из туннеля — запретить; NAT
PostUp = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
PostUp = iptables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -A FORWARD -i wg0 -j DROP
PostUp = iptables -t nat -A POSTROUTING -s 10.66.66.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT
PostDown = iptables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j DROP
PostDown = iptables -t nat -D POSTROUTING -s 10.66.66.0/24 -o eth0 -j MASQUERADE
# IPv6: то же самое
PostUp = ip6tables -A FORWARD -i wg0 -o eth0 -j ACCEPT
PostUp = ip6tables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostUp = ip6tables -A FORWARD -i wg0 -j DROP
PostUp = ip6tables -t nat -A POSTROUTING -s fd66:6666:6666::/64 -o eth0 -j MASQUERADE
PostDown = ip6tables -D FORWARD -i wg0 -o eth0 -j ACCEPT
PostDown = ip6tables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostDown = ip6tables -D FORWARD -i wg0 -j DROP
PostDown = ip6tables -t nat -D POSTROUTING -s fd66:6666:6666::/64 -o eth0 -j MASQUERADE
eth0 — интерфейс сервера, смотрящий в интернет; проверьте своё имя командой ip -br addr. Что делают правила, по порядку:
FORWARD -i wg0 -o eth0 -j ACCEPT— пакеты, пришедшие из туннеля и уходящие в интернет, пропускать. ЦепочкаFORWARDв iptables — это транзит: пакеты, которые сервер не принимает себе и не отправляет сам, а пересылает между интерфейсами.FORWARD -i eth0 -o wg0 … RELATED,ESTABLISHED -j ACCEPT— обратно пропускать только ответы на соединения, начатые из туннеля.conntrack— таблица соединений в ядре: по ней netfilter помнит, кто что начал, и отличает ответ от попытки снаружи постучаться к клиенту VPN.FORWARD -i wg0 -j DROP— всё остальное из туннеля отбросить. Это защита других сетей сервера: если у машины есть второй интерфейс в частную сеть хостинга или туннели куда-то ещё, клиенты VPN туда не попадут. Порядок важен: правила проверяются сверху вниз, иDROPстоит после разрешений. Без этого правила при политикеFORWARD ACCEPT(а она по умолчанию такая) клиент мог бы ходить через сервер куда угодно.nat POSTROUTING -s 10.66.66.0/24 -o eth0 -j MASQUERADE— пакетам из сети туннеля, уходящим вeth0, подставить адресeth0.POSTROUTING— точка после решения о маршруте, когда уже известно, через какой интерфейс пакет уйдёт.
PostDown повторяет те же команды с -D вместо -A: удалить правило. Если туннель перезапускается, правила не накапливаются.
Про IPv6 отдельно. NAT для IPv6 — вещь непривычная: адресов там хватает всем, и правильно было бы раздать клиентам настоящие публичные адреса из блока сервера. Но для этого хостинг должен маршрутизировать на сервер целый блок, обычно /64, а многие дают серверу один адрес или несколько штук. Тогда остаётся NAT66: клиенты выходят с IPv6-адреса сервера. Это лучше, чем не выдавать клиентам IPv6 вовсе: клиент, у которого дома есть IPv6, а в туннеле нет, отправит трафик к сайтам с AAAA-записями мимо туннеля, в открытую. Если ваш хостинг маршрутизирует /64, выдайте клиентам адреса из него и NAT66 не делайте — тогда и правила POSTROUTING для ip6tables не нужны, остаются только транзитные.
Входящий порт. На чистой Ubuntu входящие пакеты не фильтруются, и порт 51820 открыт сам по себе. Если на сервере файрвол с политикой INPUT DROP, добавьте разрешение — например, туда же в PostUp:
PostUp = iptables -A INPUT -p udp --dport 51820 -j ACCEPT
PostUp = ip6tables -A INPUT -p udp --dport 51820 -j ACCEPT
PostDown = iptables -D INPUT -p udp --dport 51820 -j ACCEPT
PostDown = ip6tables -D INPUT -p udp --dport 51820 -j ACCEPT
У облачных провайдеров файрвол часто есть ещё и снаружи машины, в панели управления: UDP 51820 нужно открыть и там. Если файрволом на сервере управляет ufw, правила из PostUp с ним не конфликтуют, но порт открывается его командой ufw allow 51820/udp, а транзит и NAT аккуратнее перенести в его файлы /etc/ufw/before.rules и before6.rules: ufw при перезагрузке правил сбрасывает цепочки и свои правила восстанавливает, а чужие — нет.
Запуск
chmod 600 /etc/wireguard/wg0.conf
systemctl enable --now wg-quick@wg0
wg show wg0
wg-quick@wg0 — шаблонная служба systemd: wg0 в имени — это имя конфига в /etc/wireguard/. Она создаёт интерфейс, назначает адреса, ставит маршрут в сеть туннеля, загружает ключи и выполняет PostUp. wg show покажет интерфейс, его открытый ключ и порт; пиров пока нет. Правила файрвола должны быть на месте:
iptables -S FORWARD
iptables -t nat -S POSTROUTING
В первом выводе — три правила с wg0, во втором — MASQUERADE.
Клиенты: добавление и отзыв
У каждого устройства — свои ключи. Тогда потерянный телефон отзывается одной командой, а остальные устройства ничего не замечают. Для каждого устройства нужно: сгенерировать пару ключей, выбрать свободный адрес в сети туннеля, добавить секцию [Peer] на сервер и собрать конфиг клиента. Руками это пять минут и две ошибки на устройство, поэтому — скрипт. Положите его в /usr/local/sbin/wg-user и сделайте исполняемым (chmod 755).
#!/usr/bin/env bash
# wg-user list | add <name> | show <name> | del <name>
set -euo pipefail
WG_IF=wg0
CFG=/etc/wireguard/$WG_IF.conf
CLIENTS=/etc/wireguard/clients
NET4=10.66.66 # клиенты получают $NET4.N/32
NET6=fd66:6666:6666:: # и ${NET6}N/128
ENDPOINT=203.0.113.10:51820
DNS="1.1.1.1, 2606:4700:4700::1111"
KEEPALIVE=25
die() { echo "wg-user: $*" >&2; exit 1; }
[ "$(id -u)" -eq 0 ] || die "run as root"
umask 077
mkdir -p "$CLIENTS"
check_name() { [[ "$1" =~ ^[A-Za-z0-9._@-]{1,64}$ ]] || die "bad name"; }
has_user() { grep -qx "# user: $1" "$CFG"; }
peer_pubkey() { awk -v n="# user: $1" '$0==n{f=1} f&&/^PublicKey/{print $3; exit}' "$CFG"; }
server_pubkey() { awk '/^PrivateKey/{print $3; exit}' "$CFG" | wg pubkey; }
sync() { wg syncconf "$WG_IF" <(wg-quick strip "$WG_IF"); }
next_host() {
local used n
used=$(grep -oE "AllowedIPs = $NET4\.[0-9]+" "$CFG" | awk -F. '{print $NF}' | sort -n)
for n in $(seq 2 254); do grep -qx "$n" <<<"$used" || { echo "$n"; return; }; done
die "no free addresses"
}
show_conf() {
local f=$CLIENTS/$1.conf
[ -f "$f" ] || die "no client config for $1"
cat "$f"; echo
qrencode -t ansiutf8 < "$f"
}
case "${1:-}" in
list)
declare -A names
while read -r name pub; do names[$pub]=$name; done < <(awk '/^# user: /{n=$3} /^PublicKey/{print n, $3}' "$CFG")
printf '%-20s %-34s %-24s %s\n' NAME ADDRESSES ENDPOINT LAST_HANDSHAKE
wg show "$WG_IF" dump | tail -n +2 | while IFS=$'\t' read -r pub psk endpoint ips hs rx tx ka; do
if [ "$hs" = 0 ]; then hs_s=never; else hs_s="$(( $(date +%s) - hs ))s ago"; fi
printf '%-20s %-34s %-24s %s\n' "${names[$pub]:-?}" "${ips//,/ }" "${endpoint:-(none)}" "$hs_s"
done ;;
add)
[ $# -eq 2 ] || die "usage: wg-user add <name>"; check_name "$2"
has_user "$2" && die "user $2 already exists"
n=$(next_host); priv=$(wg genkey); pub=$(wg pubkey <<<"$priv"); psk=$(wg genpsk)
addr4="$NET4.$n"; addr6="${NET6}$(printf '%x' "$n")"
printf '# user: %s\n[Peer]\nPublicKey = %s\nPresharedKey = %s\nAllowedIPs = %s/32, %s/128\n\n' "$2" "$pub" "$psk" "$addr4" "$addr6" >> "$CFG"
sync
cat > "$CLIENTS/$2.conf" <<EOF
[Interface]
PrivateKey = $priv
Address = $addr4/32, $addr6/128
DNS = $DNS
[Peer]
PublicKey = $(server_pubkey)
PresharedKey = $psk
Endpoint = $ENDPOINT
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = $KEEPALIVE
EOF
echo "added $2 ($addr4, $addr6)" >&2
show_conf "$2" ;;
show)
[ $# -eq 2 ] || die "usage: wg-user show <name>"; has_user "$2" || die "no user $2"
show_conf "$2" ;;
del)
[ $# -eq 2 ] || die "usage: wg-user del <name>"; has_user "$2" || die "no user $2"
pub=$(peer_pubkey "$2")
cp -p "$CFG" "$CFG.$(date +%Y%m%d-%H%M%S)"
awk -v n="# user: $2" '$0==n{skip=1} !skip{print} skip&&/^$/{skip=0}' "$CFG" > "$CFG.new" && mv "$CFG.new" "$CFG"
[ -n "$pub" ] && wg set "$WG_IF" peer "$pub" remove
rm -f "$CLIENTS/$2.conf"
echo "removed $2" >&2 ;;
*) die "usage: wg-user list | add <name> | show <name> | del <name>" ;;
esac
Что он делает при add:
- генерирует ключи клиента и preshared key (
wg genpsk) — дополнительный общий секрет на пару «сервер — этот клиент». Он не обязателен, но добавляет к криптографии на открытых ключах симметричный слой: даже если когда-нибудь появится способ вычислить закрытый ключ из открытого, трафик с PSK останется закрытым; - выбирает адрес: первый свободный в
10.66.66.0/24, и тот же номер в IPv6-сети; - дописывает
[Peer]на сервер — открытый ключ клиента, PSK и его адреса вAllowedIPs, с комментарием# user: имя, по которому скрипт потом находит блок; - применяет без перезапуска:
wg syncconfзагружает в работающий интерфейс разницу между текущим состоянием и конфигом, остальные клиенты ничего не замечают.wg-quick stripубирает из конфига поля, которые понимает толькоwg-quick(Address,DNS), а неwg; - собирает конфиг клиента и показывает его текстом и QR-кодом — телефон его отсканирует с экрана.
Клиентский конфиг выглядит так:
[Interface]
PrivateKey = <закрытый ключ клиента>
Address = 10.66.66.2/32, fd66:6666:6666::2/128
DNS = 1.1.1.1, 2606:4700:4700::1111
[Peer]
PublicKey = <открытый ключ сервера>
PresharedKey = <общий секрет>
Endpoint = 203.0.113.10:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
По строкам:
Address— адреса клиента в туннеле,/32и/128: одно устройство, одна пара адресов.DNS— какие резолверы использовать, пока туннель поднят. Запросы к ним пойдут через туннель (адреса покрытыAllowedIPs), и провайдер клиента не увидит имён сайтов. Без этой строки клиент продолжит спрашивать DNS провайдера или роутера, и это та самая утечка DNS из двадцатой части. Можно указать свой резолвер на сервере, если он там есть.Endpoint— куда подключаться. Здесь адрес, а не имя: тогда подключение не зависит от DNS. Имя тоже можно, клиент разрешит его при запуске.AllowedIPs = 0.0.0.0/0, ::/0— полный туннель: весь трафик, IPv4 и IPv6, через сервер. Для раздельного туннеля сюда пишут только те сети, которые должны идти через VPN, например10.66.66.0/24, 192.168.100.0/24— сеть туннеля и сеть за сервером; всё остальное пойдёт напрямую. Клиенты WireGuard строят маршруты ровно по этому списку.PersistentKeepalive = 25— раз в 25 секунд отправлять серверу пустой пакет. Нужен, когда клиент за NAT, то есть почти всегда: домашний роутер и оператор мобильной связи помнят UDP-поток 30–60 секунд после последнего пакета и потом забывают; тогда ответ сервера, пришедший первым после паузы, пропадёт. Keepalive не даёт записи протухнуть. Цена — чуть больше расхода батареи на телефоне; на сервере эта строка не нужна.
Пользуются скриптом так:
wg-user add laptop # конфиг и QR-код на экран
wg-user add phone
wg-user list # кто есть, откуда подключён, когда последнее рукопожатие
wg-user show phone # QR-код ещё раз
wg-user del laptop # отозвать: туннель ноутбука падает сразу, телефон работает
Конфиг клиента — секрет: в нём закрытый ключ. Передавайте его на устройство так, чтобы он не остался в переписке: QR-код с экрана, файл по SSH, менеджер паролей.
Клиент на Windows
Официальный клиент — WireGuard for Windows, с сайта wireguard.com/install или одной командой:
winget install WireGuard.WireGuard
Он состоит из службы, драйвера виртуального адаптера и окна управления; работает на Windows 10 и 11, ставится за полминуты, запрашивает права администратора.
Импорт туннеля. Самый простой путь — файл конфига. Скопируйте текст, который напечатал wg-user add laptop, в файл с расширением .conf, например laptop.conf (имя файла станет именем туннеля, до 15 символов: латиница, цифры, _, -, =, +, .). В окне WireGuard: Add Tunnel → Import tunnel(s) from file… и выберите файл. Или Add empty tunnel… и вставьте текст целиком в поле. Клиент покажет туннель слева в списке, справа — его параметры с открытым ключом; закрытый ключ он хранит в зашифрованном виде и в окне не показывает. Файл laptop.conf после импорта удалите.
Подключение — кнопка Activate. Через секунду статус станет Active, а в блоке Peer появятся Latest handshake и счётчики Transfer. Если рукопожатия нет и счётчик received стоит на нуле — пакеты до сервера не доходят или сервер не принимает: проверьте порт в файрволе сервера и совпадение ключей.
Kill switch. Правой кнопкой по туннелю → Edit: когда в AllowedIPs стоит 0.0.0.0/0, ::/0, внизу окна появляется галочка Block untunneled traffic (kill-switch). Она добавляет правила файрвола Windows, которые запрещают любой трафик мимо туннеля, кроме пакетов к самому серверу и DHCP. Пока туннель активен и упал, в интернет ничего не уйдёт в открытую. Для ноутбука в кафе — то, что нужно; дома, где туннель включают ради одного сайта, можно не ставить.
Что происходит в системе — в PowerShell:
PS C:\> Get-NetAdapter | Where-Object InterfaceDescription -like 'WireGuard*'
Name InterfaceDescription Status MacAddress LinkSpeed
---- -------------------- ------ ---------- ---------
laptop WireGuard Tunnel Up 100 Gbps
PS C:\> Get-NetIPAddress -InterfaceAlias laptop | Select-Object IPAddress, PrefixLength
IPAddress PrefixLength
--------- ------------
fd66:6666:6666::2 128
10.66.66.2 32
PS C:\> Get-NetRoute -DestinationPrefix 0.0.0.0/0 | Select-Object InterfaceAlias, NextHop, RouteMetric, InterfaceMetric
InterfaceAlias NextHop RouteMetric InterfaceMetric
-------------- ------- ----------- ---------------
laptop 0.0.0.0 0 0
Ethernet 192.168.1.1 0 25
Два маршрута по умолчанию: старый через роутер и новый через туннель. Побеждает тот, у которого сумма метрик меньше, — клиент WireGuard ставит своему интерфейсу метрику 0, и весь трафик уходит в него. Отдельного маршрута к самому серверу 203.0.113.10 в таблице не ищите, его нет: зашифрованные пакеты к серверу клиент отправляет через сокет, привязанный к физическому интерфейсу, и таблица маршрутов для них не применяется. Иначе они попали бы в туннель, были бы зашифрованы ещё раз и так до бесконечности. На Linux wg-quick решает ту же задачу через policy routing: маркирует свои пакеты (fwmark) и правилом ip rule пускает всё, кроме помеченных, через отдельную таблицу с маршрутами туннеля. DNS интерфейса — 1.1.1.1 из конфига; клиент к тому же блокирует запросы DNS через другие интерфейсы, так что утечки нет.
Автозапуск. Туннель, оставленный активным, поднимется сам при следующей загрузке: служба запоминает состояние. Отдельная настройка не нужна.
Можно обойтись и без окна: wireguard.exe /installtunnelservice C:\path\laptop.conf ставит туннель службой, wireguard.exe /uninstalltunnelservice laptop убирает. Это пригодится, если туннели нужно раздавать скриптом на много машин.
Клиент на Android
Приложение называется WireGuard, автор WireGuard Development Team, есть в Google Play и в F-Droid. Работает на Android 5 и новее; на телефонах без модуля в ядре — в пользовательском режиме, медленнее, но так же.
Импорт. Нажмите синий + внизу справа. Три варианта: Scan from QR code — камеру на QR-код, который напечатал wg-user add phone (прямо с экрана терминала, экран ноутбука читается с полуметра); Import from file or archive — файл .conf, если передали его на телефон; Create from scratch — вбить руками. После сканирования приложение спросит имя туннеля, дальше он появится в списке.
Подключение — переключатель справа от имени туннеля. Android спросит разрешение на создание VPN-подключения — один раз, нажмите OK. В строке состояния появится ключик. Нажмите на имя туннеля — раскроются параметры: адреса, DNS, пир, Latest handshake и Transfer. Пока рукопожатия нет, туннель не работает.
Исключения для приложений. В настройках туннеля (карандаш) → All Applications можно исключить приложения из туннеля или, наоборот, пустить через него только выбранные. Банковские приложения, которые не любят VPN, или местный видеосервис, который через зарубежный сервер не покажет контент, — сюда.
Always-on VPN. Чтобы туннель поднимался при включении телефона и не падал, когда система убивает приложение в фоне: Настройки Android → Сеть и интернет → VPN → шестерёнка рядом с WireGuard → Always-on VPN. Там же Block connections without VPN — аналог kill switch: без туннеля интернета нет вовсе. У разных производителей путь к этому экрану немного отличается, но называется он везде одинаково.
Батарея. Keepalive 25 секунд не даёт радиомодулю спать подолгу, и в день это несколько процентов заряда. Если телефон не за NAT (редкость) или вы готовы к секундной задержке при первом пакете после простоя, keepalive можно убрать; на Android, где система сама отправляет приложение спать, обычно оставляют.
Смотрим руками
На сервере. Кто подключён и когда последний раз подавал признаки жизни:
# wg show wg0
interface: wg0
public key: <открытый ключ сервера>
private key: (hidden)
listening port: 51820
peer: <открытый ключ laptop>
preshared key: (hidden)
endpoint: 198.51.100.7:53421
allowed ips: 10.66.66.2/32, fd66:6666:6666::2/128
latest handshake: 48 seconds ago
transfer: 14.21 MiB received, 203.56 MiB sent
persistent keepalive: every 25 seconds
endpoint — откуда пришёл последний пакет клиента: публичный адрес его роутера и порт NAT. latest handshake старше двух-трёх минут при живом клиенте — пакеты не ходят. wg-user list показывает то же с именами.
Пакеты как их видит сеть:
# tcpdump -ni eth0 udp port 51820 -c 4
IP 198.51.100.7.53421 > 203.0.113.10.51820: UDP, length 148
IP 203.0.113.10.51820 > 198.51.100.7.53421: UDP, length 92
IP 198.51.100.7.53421 > 203.0.113.10.51820: UDP, length 128
IP 203.0.113.10.51820 > 198.51.100.7.53421: UDP, length 1392
Первые два — рукопожатие: запрос ровно 148 байт, ответ ровно 92. Дальше — данные, у которых к длине пакета внутри добавлены 32 байта заголовка WireGuard. Содержимого не видно, но факт WireGuard виден любому, кто смотрит на трафик: по этим двум длинам его узнают и файрволы, и DPI. Если задача — не только защитить трафик, но и не дать понять, что это VPN, нужен другой инструмент: об этом отдельная статья.
Транзит и NAT в действии — счётчики правил:
# iptables -L FORWARD -nv
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
3012 211K ACCEPT 0 -- wg0 eth0 0.0.0.0/0 0.0.0.0/0
4188 4967K ACCEPT 0 -- eth0 wg0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
5 300 DROP 0 -- wg0 * 0.0.0.0/0 0.0.0.0/0
# iptables -t nat -L POSTROUTING -nv
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
164 11K MASQUERADE 0 -- * eth0 10.66.66.0/24 0.0.0.0/0
Пять пакетов в DROP — чьи-то попытки из туннеля достучаться до адресов, которые не ведут в интернет через eth0. В таблице nat счётчик меньше, чем в FORWARD: NAT-правило срабатывает один раз на соединение, дальше conntrack подменяет адреса сам.
На клиенте. Куда вы выходите:
curl.exe -4 https://ifconfig.co # 203.0.113.10
curl.exe -6 https://ifconfig.co # 2001:db8::10
Оба адреса — сервера, значит, ни IPv4, ни IPv6 мимо туннеля не идут. Утечку DNS проверяет любой сайт проверки DNS leak: резолвер в ответе должен быть тем, что в строке DNS, а не провайдерским. На Windows Resolve-DnsName whoami.akamai.net вернёт адрес резолвера, который пришёл к Akamai с вашим запросом.
MTU. Заголовок WireGuard плюс UDP плюс IP — 60 байт для IPv4 и 80 для IPv6, поэтому интерфейс туннеля по умолчанию получает MTU 1420 вместо 1500 (шестая часть о том, что это и чем грозит). Если клиент сам сидит за PPPoE с MTU 1492 (четвёртая часть) или за ещё одним туннелем, 1420 может оказаться много: сайты открываются, а большие страницы и загрузки виснут. Тогда в [Interface] клиента добавляется MTU = 1380, и у wg-quick, и у клиентов Windows и Android эта строка понимается.
Если не работает
- Нет рукопожатия,
received 0. До сервера не доходят пакеты или сервер их отбрасывает. Проверить: слушает ли сервер (ss -ulnp | grep 51820), пропускает ли его файрвол (iptables -S INPUT), файрвол хостинга (у облаков часто свой, снаружи машины), и совпадают ли ключи — открытый ключ сервера в конфиге клиента сwg show wg0, открытый ключ клиента в[Peer]сервера с тем, что показывает клиент. Один неверный символ — тишина без единого сообщения об ошибке: WireGuard не отвечает на пакеты, которые не может проверить. - Рукопожатие есть, интернета нет. Туннель работает, сервер не пересылает. Проверить
sysctl net.ipv4.ip_forward(должно быть 1), правилоMASQUERADEвiptables -t nat -Sи транзитные правила вiptables -S FORWARD: если их нет,PostUpне выполнился —journalctl -u wg-quick@wg0покажет, на какой команде. Для IPv6 — то же сip6tablesиnet.ipv6.conf.all.forwarding. - Работает минуту, потом отваливается, потом снова работает. Клиент за NAT без
PersistentKeepalive. ДобавитьPersistentKeepalive = 25в[Peer]клиента. - Сайты открываются, большие файлы и видео виснут. MTU, см. выше.
- Через туннель видно IPv4-адрес сервера, а IPv6 — провайдера. В
AllowedIPsклиента нет::/0, или на сервере нет IPv6-адреса наwg0и NAT66. Либо добавить, либо выключить IPv6 на клиенте на время туннеля. - Время на сервере или клиенте сильно врёт. WireGuard защищается от повторного воспроизведения старых рукопожатий по счётчику, в который входит время. Если часы на сервере после переустановки отстают на год, рукопожатия от клиентов будут отвергаться как «старые». Синхронизация времени —
timedatectlна сервере.
Что получилось
Сервер WireGuard — это десять строк конфига, один UDP-порт и несколько правил iptables для транзита и NAT, которые живут вместе с интерфейсом. Устройство — пара ключей и секция [Peer]; скрипт делает это одной командой и печатает QR-код. Клиент на Windows импортирует файл и умеет kill switch, клиент на Android сканирует QR-код и живёт в режиме always-on. Весь трафик, IPv4 и IPv6, и запросы DNS идут через сервер; отзыв устройства — одна команда, не трогающая остальные.
Чего WireGuard не делает: не прячет сам факт VPN. В сети, где VPN режут по сигнатурам, нужна маскировка под обычный HTTPS, и это тема статьи про Xray и REALITY. А там, где VPN никому не мешает, — в кафе, в гостинице, в поездке, — WireGuard сегодня самый простой способ сделать так, чтобы ваш трафик видел только ваш сервер.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.