Curl –interface: как использовать интерфейсы и тонкости настройки

Если вы когда-нибудь настраивали 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 строже подходит к фильтрации трафика, и это, в общем-то, хорошо для безопасности.

Добавить комментарий

Все поля обязательны к заполнению. Ваш адрес email не будет виден никому.

Новое
Интересное