Если вы когда-нибудь настраивали VPN или работали с сетевыми туннелями, опция curl –interface наверняка попадалась на глаза. Но как именно она обходит стандартную маршрутизацию? Давайте разбираться без лишней магии на примерах и с погружением в системные механизмы.
Что делает –interface и при чём тут SO_BINDTODEVICE?
Когда вы запускаете curl –interface tun0, происходит не просто выбор интерфейса «наугад». Под капотом Curl использует системный вызов setsockopt(SO_BINDTODEVICE), который жёстко привязывает сокет к указанному сетевому интерфейсу (даже если маршрутизация через него кажется невозможной). Это как сказать ядру: «Отправляй все пакеты через этот шлюз, даже если таблица маршрутизации предлагает другой путь».
Но есть нюанс: маршрутизация всё равно происходит, но только в рамках выбранного интерфейса. Представьте, что у вас два дефолтных шлюза – eth0 и wlan0. Обычно система выберет eth0 из-за меньшей метрики, но –interface wlan0 заставит Curl использовать маршруты, связанные именно с Wi-Fi. Если подходящего маршрута нет, ядро Linux автоматически создаёт _on-link route_ – временный маршрут, который предполагает, что цель находится «прямо на этом интерфейсе», без шлюза.
# Пример on-link маршрута для tun0:
0.0.0.0/0 dev tun0
Почему tun0 работает, а wg0 – нет?
Здесь всё упирается в тип интерфейса и его логику работы. Возьмём два случая:
1. tun0 (userspace-туннель):
Это точка-точка (point-to-point), где пакеты сразу передаются в пользовательскую программу (например, sing-box). Такому интерфейсу не нужны ARP-запросы или MAC-адреса – он просто передаёт данные «на другой конец». Даже если в таблице маршрутизации нет явного правила для example.com, on-link маршрут через tun0 позволяет пакетам уходить в туннель. Программа-обработчик сама решает, что с ними делать.
2. wg0 (WireGuard):
Хотя WireGuard работает в ядре, у него есть своя логика валидации. Если IP-адрес назначения не входит в AllowedIPs любого из пиров, ядро возвращает ошибку EDESTADDRREQ (Destination address required), которая преобразуется в «No route to host». Это не ошибка маршрутизации в классическом понимании – это отказ WireGuard обработать пакет из-за настроек безопасности.
Тип интерфейса | Поведение при on-link маршруте |
Ethernet/Wi-Fi (tap) | Пытается найти MAC через ARP (часто без ответа) |
Tun/PPP/WireGuard | Передаёт пакеты без ARP, но может отвергнуть их по внутренним правилам |
Ошибки и как их избежать
Самый частый сценарий – «connect failed: No route to host» при использовании –interface. Вот что проверить в первую очередь:
- Доступность интерфейса: Убедитесь, что интерфейс активен (ip link show wg0). Иногда VPN-клиенты создают интерфейс, но не поднимают его.
- AllowedIPs в WireGuard: Если вы используете wg0, IP назначения должен входить в разрешённые адреса одного из пиров. Проверьте конфиг:
wg show wg0
- Политики маршрутизации: При использовании
--interface 172.18.0.1
(IP вместо имени) Linux игнорирует привязку к интерфейсу. Здесь нужны правилаip rule
, чтобы связать исходный адрес с конкретной таблицей маршрутизации. Например:ip rule add from 172.18.0.1 lookup vpn-table
Кстати, если вы указали IP вместо имени интерфейса и это не сработало, причина может быть в том, что --interface
использует bind()
, а не SO_BINDTODEVICE. Это значит, что система может отправить пакет через другой интерфейс, если сочтёт нужным. Чтобы этого избежать, настройте политики маршрутизации через ip rule
.
Пример рабочей конфигурации для WireGuard:
# Создаём отдельную таблицу маршрутизации
ip route add default dev wg0 table 2468
# Добавляем правило: если источник — 10.230.116.1, использовать таблицу 2468
ip rule add from 10.230.116.1 lookup 2468
Почему –interface tun0 и –interface 172.18.0.1 ведут себя по-разному?
Это частая путаница. Когда вы указываете имя интерфейса (tun0), Curl привязывает сокет к нему через SO_BINDTODEVICE, что строго ограничивает маршрутизацию. Если же указать IP (172.18.0.1), происходит привязка к адресу через bind(), но без изменения интерфейса. Пакет может уйти через eth0, если система решит, что это оптимальный путь. Чтобы IP-привязка работала как ожидается, нужны те самые правила политической маршрутизации.
И ещё один момент: tun0 в примере имеет маршрут 172.18.0.0/30 dev tun0, который явно указывает, что все адреса в этой подсети должны идти через туннель. Если бы вы пытались обратиться к IP вне этой подсети, результат зависел бы от настроек userspace-программы (например, sing-box мог бы просто игнорировать такие пакеты).
Итоги: как не наломать дров
- Используйте
--interface <имя>
, когда нужно жёстко привязаться к интерфейсу (VPN, туннели). - Для WireGuard проверяйте
AllowedIPs
– без них подключение не пройдёт, даже если маршрут есть. - Если нужно привязаться к IP, настройте
ip rule
, иначе маршрутизация может пойти не туда. - Помните:
tun
/ppp
интерфейсы не используют ARP, аeth
/wlan
– используют (отсюда и разное поведение при on-link маршрутах).
Если видите «No route to host» на wg0 – это скорее всего не ваша вина. Просто WireGuard строже подходит к фильтрации трафика, и это, в общем-то, хорошо для безопасности.