Кроссплатформенная реализация
Кроссплатформенная пользовательская реализация
Хотя WireGuard изначально был разработан для ядра Linux для максимальной производительности, он может работать в пользовательском пространстве с использованием отдельной реализации. В настоящее время wireguard-go вполне функционален, а wireguard-rs находится в разработке.
Что такое пользовательская реализация? В отличие от реализации в ядре, которая работает на уровне операционной системы, пользовательская реализация работает как обычное приложение. Это позволяет запускать WireGuard на платформах, где нет доступа к ядру, или когда требуется более простая интеграция с приложениями.
В любой момент документации, когда вы видите ip link add wg0 type wireguard, вы можете вместо этого написать wireguard-go wg0. Всё остальное должно быть идентичным.
wireguard-go wg0, и он создаёт виртуальный сетевой интерфейс, эмулируя поведение ядерной версии.Чтобы предотвратить фрагментацию и обеспечить совместимость, все пользовательские реализации должны соответствовать одному и тому же протоколу и спецификации, имея точно такое же поведение, как и оригинальная реализация для ядра Linux. Кроме того, они должны соответствовать следующему интерфейсу конфигурации.
Интерфейс командной строки
Пользовательская реализация должна иметь следующий очень ограниченный интерфейс командной строки:
# userspace-wg [-f/--foreground] INTERFACE-NAME
Объяснение параметров:
-fили--foreground— опциональный флаг, который заставляет процесс оставаться на переднем плане (не демонизироваться). Это полезно для отладки и при запуске из системных менеджеров.INTERFACE-NAME— имя создаваемого интерфейса (например,wg0).
Например, реализация на Go будет вызываться следующим образом для создания интерфейса wg0:
# wireguard-go wg0
Что происходит при запуске? Выполнение этой команды создаёт виртуальное TUN-устройство с именем wg0 и затем демонизируется (переходит в фоновый режим). После успешной демонизации и поднятия интерфейса создаётся /var/run/wireguard/wg0.sock (или /run/wireguard/wg0.sock в зависимости от платформы) — это UNIX-сокет домена, работающий в потоковом режиме.
Что такое TUN-устройство? Это виртуальный сетевой интерфейс, который работает на уровне IP. Приложения могут читать и писать в него IP-пакеты, как если бы они работали с обычным сетевым интерфейсом. WireGuard использует TUN для получения и отправки зашифрованных пакетов.
Что такое UNIX-сокет? Это механизм межпроцессного взаимодействия (IPC), который позволяет процессам обмениваться данными. В данном случае он используется для того, чтобы утилита wg(8) могла управлять процессом WireGuard.
На Windows используются те же семантики с двунаправленным именованным каналом (named pipe) в \\.\pipe\WireGuard\wg0. Именованные каналы — это аналог UNIX-сокетов в Windows.
Использование wg(8) для конфигурации
Инструмент wg(8) используется для настройки интерфейса, что обеспечивает полное единообразие интерфейсов конфигурации во всех реализациях.
Почему это важно? Пользователи и скрипты могут использовать одни и те же команды для управления WireGuard независимо от того, работает ли он в ядре или в пользовательском пространстве. Это значительно упрощает автоматизацию и написание документации.
Инструмент wg(8) ищет интерфейсы в /var/run/wireguard/*.sock (или /run/wireguard/*.sock). Пользовательские реализации должны корректно завершать работу в ответ на SIGINT/SIGTERM, удаление TUN-интерфейса или удаление файла UNIX-сокета.
Как это работает:
- Вы запускаете
wireguard-go wg0— создаётся процесс и сокет - Вы запускаете
wg setconf wg0 config.conf— утилитаwg(8)находит сокет и отправляет команду - Процесс WireGuard получает команду через сокет и применяет конфигурацию
- Всё прозрачно работает как с ядерной версией
Инструмент wg(8) подключается к этим сокетам и отправляет и получает следующий текстовый протокол.
Протокол конфигурации
Реализация WireGuard должна отвечать на две команды: get и set, обе версии 1 на момент написания.
Что такое версия протокола? Это позволяет в будущем добавлять новые функции без нарушения совместимости. Номер версии указывает, какой формат команд и ответов используется.
Команда get
wg(8) отправляет команду get, которая выглядит так:
get=1
{empty line}
Объяснение: Команда get запрашивает текущую конфигурацию и статус интерфейса. Пустая строка в конце указывает на завершение команды.
Пользовательская реализация отвечает на команду get так:
key1=value1
key2=value2
key3=value3
key4=value4
key5=value5
...
errno=0
{empty line}
Объяснение ответа: После всех ключей и значений добавляется errno=0 (означает “успех”) и пустая строка. Если произошла ошибка, errno будет содержать код ошибки.
Команда set
wg(8) отправляет команду set, которая выглядит так:
set=1
key1=value1
key2=value2
key3=value3
key4=value4
key5=value5
...
{empty line}
Объяснение: Команда set отправляет новую конфигурацию. В отличие от get, она содержит ключи и значения для применения.
Пользовательская реализация отвечает на команду set так:
errno=0
{empty line}
Объяснение: Ответ на set проще — только код ошибки и пустая строка.
Если произошла ошибка, errno — соответствующее целое число из errno.h (стандартные коды ошибок POSIX).
get и set, а также в конце каждого соответствующего ответа от wg(8). Это необходимо для правильного разбора протокола.Ключи и значения
Уровень интерфейса
Эти ключи применяются ко всему интерфейсу:
| Ключ | Описание | Формат значения | Примечания |
|---|---|---|---|
private_key | Приватный ключ интерфейса | Шестнадцатеричная строка (в нижнем регистре) | Все нули = удалить ключ |
listen_port | Порт для прослушивания | Десятичное целое число | |
fwmark | Метка для исходящих пакетов | Десятичное целое число | 0 = удалить fwmark |
replace_peers=true | Заменить всех пиров | Только ключ | Последующие пиры заменяют существующих |
Пример использования replace_peers: Если у вас уже есть 5 пиров, и вы отправляете set с replace_peers=true и 3 новыми пирами, старые 5 пиров будут удалены, а останутся только 3 новых.
Уровень пира
Эти ключи применяются к конкретному пиру и следуют после ключа public_key:
| Ключ | Описание | Формат значения | Примечания |
|---|---|---|---|
public_key | Публичный ключ пира | Шестнадцатеричная строка (в нижнем регистре) | Начинает блок пира |
remove=true | Удалить пира | Только ключ | Применяется к предыдущему пиру |
update_only=true | Обновить только существующего | Только ключ | Ошибка, если пир не существует |
preshared_key | Предварительный общий ключ | Шестнадцатеричная строка | Все нули = удалить ключ |
endpoint | Конечная точка | IP:port или [IP]:port | Для IPv6 используются квадратные скобки |
persistent_keepalive_interval | Интервал keepalive | Десятичное целое число | 0 = отключить |
replace_allowed_ips=true | Заменить все разрешённые IP | Только ключ | Заменяет список, а не добавляет |
allowed_ip | Разрешённый IP-адрес | IP/cidr | Может быть указан несколько раз |
rx_bytes | Получено байт | Десятичное целое число | Только для get |
tx_bytes | Отправлено байт | Десятичное целое число | Только для get |
last_handshake_time_sec | Время рукопожатия (сек) | Десятичное целое число | Только для get, время Unix |
last_handshake_time_nsec | Время рукопожатия (нс) | Десятичное целое число | Только для get, время Unix |
protocol_version | Версия протокола | 1 | Обычно не используется |
Важные замечания о ключах
Порядок важен: Все ключи уровня интерфейса должны идти до ключей уровня пира. Это логично, так как сначала определяются общие параметры интерфейса, а затем параметры отдельных пиров.
Обработка
allowed_ip: Если одинаковое значениеallowed_ipуже существует у другого пира, запись IP будет удалена у того пира и добавлена к текущему. Это обеспечивает уникальность разрешённых IP-адресов.Формат ключей: Все ключи в шестнадцатеричном формате должны быть в нижнем регистре. Это стандарт для криптографических ключей в WireGuard.
Пример диалога
Команда get
wg(8) отправляет:
get=1
{empty line}
Пользовательская реализация WireGuard отвечает:
private_key=e84b5a6d2717c1003a13b431570353dbaca9146cf150c5f8575680feba52027a
listen_port=12912
public_key=b85996fecc9c7f1fc6d2572a76eda11d59bcd20be8e543b15ce4bd85a8e75a33
preshared_key=188515093e952f5f22e865cef3012e72f8b5f0b598ac0309d5dacce3b70fcf52
allowed_ip=192.168.4.4/32
endpoint=[abcd:23::33%2]:51820
public_key=58402e695ba1772b1cc9309755f043251ea77fdcf10fbe63989ceb7e19321376
tx_bytes=38333
rx_bytes=2224
allowed_ip=192.168.4.6/32
persistent_keepalive_interval=111
endpoint=182.122.22.19:3233
public_key=662e14fd594556f522604703340351258903b64f35553763f19426ab2a515c58
endpoint=5.152.198.39:51820
allowed_ip=192.168.4.10/32
allowed_ip=192.168.4.11/32
tx_bytes=1212111
rx_bytes=1929999999
protocol_version=1
errno=0
{empty line}
Разбор примера:
- Интерфейс имеет приватный ключ, слушает на порту 12912
- Пир 1: публичный ключ
b859..., preshared_key, разрешённый IP192.168.4.4/32, конечная точка IPv6 - Пир 2: публичный ключ
5840..., передано 38333 байт, получено 2224 байт, разрешённый IP192.168.4.6/32, keepalive 111 секунд - Пир 3: публичный ключ
662e..., два разрешённых IP, большая передача данных
Команда set
wg(8) отправляет:
set=1
private_key=e84b5a6d2717c1003a13b431570353dbaca9146cf150c5f8575680feba52027a
fwmark=0
listen_port=12912
replace_peers=true
public_key=b85996fecc9c7f1fc6d2572a76eda11d59bcd20be8e543b15ce4bd85a8e75a33
preshared_key=188515093e952f5f22e865cef3012e72f8b5f0b598ac0309d5dacce3b70fcf52
replace_allowed_ips=true
allowed_ip=192.168.4.4/32
endpoint=[abcd:23::33%2]:51820
public_key=58402e695ba1772b1cc9309755f043251ea77fdcf10fbe63989ceb7e19321376
replace_allowed_ips=true
allowed_ip=192.168.4.6/32
persistent_keepalive_interval=111
endpoint=182.122.22.19:3233
public_key=662e14fd594556f522604703340351258903b64f35553763f19426ab2a515c58
endpoint=5.152.198.39:51820
replace_allowed_ips=true
allowed_ip=192.168.4.10/32
allowed_ip=192.168.4.11/32
public_key=e818b58db5274087fcc1be5dc728cf53d3b5726b4cef6b9bab8f8f8c2452c25c
remove=true
{empty line}
Разбор примера:
replace_peers=true— все старые пиры будут удаленыfwmark=0— fwmark отключён- Добавляются 3 пира, последний (
e818...) помеченremove=trueдля удаления - У каждого пира используется
replace_allowed_ips=trueдля замены списков разрешённых IP
Пользовательская реализация WireGuard отвечает:
errno=0
{empty line}
Практические примеры использования
Настройка интерфейса через командную строку
# Запуск пользовательской реализации
# wireguard-go wg0
# Настройка с использованием wg(8)
# wg set wg0 private-key /etc/wireguard/private.key listen-port 51820
# wg set wg0 peer xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg= allowed-ips 10.0.0.2/32 endpoint 192.95.5.69:51820
# ip address add dev wg0 10.0.0.1/24
# ip link set up dev wg0
Использование с конфигурационным файлом
# Создаём конфигурационный файл /etc/wireguard/wg0.conf
# wg setconf wg0 /etc/wireguard/wg0.conf
# wg-quick up wg0
Получение статуса
# wg show wg0
Преимущества пользовательской реализации
- Кроссплатформенность — работает на Windows, macOS, BSD и других системах без поддержки в ядре
- Простота отладки — ошибки легче отслеживать в пользовательском пространстве
- Гибкость — можно интегрировать в приложения как библиотеку
- Безопасность — изоляция от ядра может быть преимуществом в некоторых сценариях
Недостатки пользовательской реализации
- Производительность — обычно медленнее, чем реализация в ядре
- Дополнительные накладные расходы — копирование данных между ядром и пользовательским пространством
- Потребление памяти — больше использования памяти для хранения состояния
Ссылки
- wireguard-go — реализация на Go
- wireguard-rs — реализация на Rust (в разработке)
- wg(8) man-страница
- Документация по TUN/TAP