четверг, 7 мая 2020 г.

Как я WireGuard на Debian 10 ставил и настраивал.



WireGuard -это такая реализация VPN в пространстве ядра. Авторами заявляется, что этот туннель простой как апельсин и надёжный как швейцарский банк. (шутка)

Да и мне уже все уши им прожужжали, мол, OpenVPN - это прошлый век, а сейчас все на WireGuard переходят.

Ну я и решил попробовать поставить это дело на Debian 10, который у меня давно уже используется. И тут начались проблемы.

Первая проблема возникла уже на этапе установки. Из-за того, что WireGuard работает внутри ядра, для него собирается модуль (пакет "wireguard-dkms"). В Debian 10 используется ядро версии 4.19, в к котором ещё нет такого модуля из коробки, а ставить ядро версии 5.5 из backports я желанием не горел.

Пробуем ставить:
sudo apt install wireguard
Установка заканчивается неудачей. При сборке модуля возникла примерно такая ошибка:
In file included from <command-line>:
/var/lib/dkms/wireguard/1.0.20200429/build/socket.c: In function 'send6':
/var/lib/dkms/wireguard/1.0.20200429/build/compat/compat.h:102:42: error: 'const struct ipv6_stub' has no member named 'ipv6_dst_lookup'; did you mean 'ipv6_dst_lookup_flow'?
 #define ipv6_dst_lookup_flow(a, b, c, d) ipv6_dst_lookup(a, b, &dst, c) + (void *)0 ?: dst
                                          ^~~~~~~~~~~~~~~
/var/lib/dkms/wireguard/1.0.20200429/build/socket.c:145:20: note: in expansion of macro 'ipv6_dst_lookup_flow'
   dst = ipv6_stub->ipv6_dst_lookup_flow(sock_net(sock), sock, &fl,
                    ^~~~~~~~~~~~~~~~~~~~
Из найденного в Google сообщения об ошибке я узнал,что подобное возникает из-за того, что при сборке модуля не учитываются особенности ядра Linux из состава Debian 10. Там же я нашёл ссылку на патч, который подобную ситуацию исправляет. Он ничего не изменяет в алгоритмах модуля, только добавляет дополнительные проверки при сборке.

Код патча:
diff --git a/src/compat/Kbuild.include b/src/compat/Kbuild.include
index eb6b6a9..3cc842e 100644
--- a/src/compat/Kbuild.include
+++ b/src/compat/Kbuild.include
@@ -99,3 +99,7 @@ ifeq ($(CONFIG_X86_64),y)
 		asflags-y += $(adx_instr)
 	endif
 endif
+
+ifneq ($(shell grep -s -F "\#define LINUX_PACKAGE_ID \" Debian " "$(CURDIR)/include/generated/package.h"),)
+ccflags-y += -DISDEBIAN
+endif
diff --git a/src/compat/compat.h b/src/compat/compat.h
index 363e01d..60056b8 100644
--- a/src/compat/compat.h
+++ b/src/compat/compat.h
@@ -98,7 +98,7 @@
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) && LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 83)
 #define ipv6_dst_lookup_flow(a, b, c, d) ipv6_dst_lookup_flow(b, c, d)
-#elif (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 5) && LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) || (LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 18) && LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)) || (LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 119) && !defined(ISRHEL82))
+#elif (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 5) && LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) || (LINUX_VERSION_CODE < KERNEL_VERSION(5, 3, 18) && LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)) || (((!defined(ISDEBIAN) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 119)) || LINUX_VERSION_CODE < KERNEL_VERSION(4, 19, 118)) && !defined(ISRHEL82))
 #define ipv6_dst_lookup_flow(a, b, c, d) ipv6_dst_lookup(a, b, &dst, c) + (void *)0 ?: dst
 #endif
Сохраняем патч в файл "~/wgfix.patch" и накладываем на исходные тексты:
cd /usr/src/wireguard-1.0.20200429/
sudo patch -p2 < ~/wgfix.patch
Если все прошло без ошибок, то пробуем продолжить установку:
sudo apt install -f
На этот раз установка должна пройти до конца.

Я надеюсь, что в скором времени пакет обновят, чтобы этот патч был уже не нужен.

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

Способ первый. Использование wg-quick.

Вместе с WireGuard устанавливается программа wg-quick и одноимённый сервис для systemd. Для описания туннелей она использует conf-файлы из каталога "/etc/wireguard".
Попробуем создать туннель.

Сначала необходимо сгенерировать пары ключей (публичный и приватный) для сервера и клиента, а также preshared-ключ. Команды я взял из найденных в сети руководств:
# каталог для ключей
mkdir ~/wgkeys
cd ~/wgkeys
# preshared-ключ
wg genpsk > wg.preshared
# ключи сервера
wg genkey | tee server.priv | wg pubkey > server.pub
# ключи клиента
wg genkey | tee client.priv | wg pubkey > client.pub
Теперь можно писать файл конфигурации "/etc/wireguard/wg0.conf". wg0 - это имя сетевого интерфейса, который будет создан при активации туннеля. По структуре файл очень похож на ini-файл.
# конфигурация интерфейса
[Interface]
# адрес интерфейса на стороне сервера
# и маска всей виртуальной сети
Address = 10.18.0.1/24
PrivateKey = содержимое файла server.priv
# UDP-порт, на котором сервер будет принимать соединения
ListenPort = 35535

# конфигурация удалённого клиента
[Peer]
PublicKey = содержимое файла client.pub
PresharedKey = содержимое файла wg.preshared
# адреса для проброса через туннель
# в данном случае только адрес клиента
AllowedIPs = 10.18.0.2/32
При этом секций "[Peer]" может быть насколько, если клиентов больше одного. Для каждой нужно будет указать свой ключ и адрес, при этом preshared-ключ может быть у всех одинаковый.

Теперь можно запускать серверную часть:
sudo systemctl enable [email protected]
sudo systemctl start [email protected]
Настройка клиентской части почти не отличается от серверной:
# конфигурация интерфейса
[Interface]
# адрес интерфейса на стороне сервера
# и маска всей виртуальной сети
Address = 10.18.0.2/32
PrivateKey = содержимое файла client.priv

# конфигурация удалённого сервера
[Peer]
PublicKey = содержимое файла server.pub
PresharedKey = содержимое файла wg.preshared
# адреса для проброса через туннель
# в данном случае весь диапазон виртуальной сети
AllowedIPs = 10.18.0.0/24
# Адрес сервера и порт для соединения из параметра ListenPort сервера
Endpoint = внешний-адрес-сервера:порт
PersistentKeepalive = 20
Туннель на клиенте запускается тем же способом, что и на сервере.

Способ второй. Использование systemd-networkd.

Если для управления сетью используется systemd-networkd, то для организация туннеля можно написать всего два юнита. У себя я сделал подобное для домашнего сервера, поэтому опишу только конфигурацию со стороны сервера.

Первый юнит - это описание сетевого устройства. У меня он называется "/etc/systemd/network/14-wg1.netdev".
[NetDev]
Name = wg1
Kind = wireguard
Description = wg server home

[WireGuard]
PrivateKey = приватный ключ сервера
ListenPort = порт

[WireGuardPeer]
PublicKey = публичный ключ клиента
AllowedIPs = 10.138.0.2/32
PresharedKey = preshared-ключ
Второй юнит - это описание сети. У меня он называется "/etc/systemd/network/14-wg1.network".
[Match]
Name = wg1

[Network]
# адрес интерфейса
Address = 10.138.0.1/32

[Route]
# маршрут
Gateway = 10.138.0.1
Destination = 10.138.0.0/24
После этого достаточно перезапустить сервис, чтобы появился новый интерфейс.
sudo systemctl restart systemd-networkd
Если на сервере и клиенте всё правильно получилось сделать, то при просмотре состояния туннеля при активном соединении должна быть примерно такая информация:
sudo wg show wg0
interface: wg0
  public key: публичный ключ сервера
  private key: (hidden)
  listening port: 35535

peer: публичный ключ клиента
  preshared key: (hidden)
  endpoint: внешний адрес и порт клиента
  allowed ips: 10.18.0.3/32
  latest handshake: 1 minute, 6 seconds ago
  transfer: 23.24 KiB received, 3.71 KiB sent
То есть соединение между узлами есть, и трафик циркулирует в обе стороны.
Я пока не пробовал соединять сети через подобный туннель и не нашёл способа заставить работать подключение только по IPv4 без явного указания ip-адреса вместо доменного имени.

UPD: 10 мая 2020 года вышло обновление, для которого патч уже не требуется.