Как пустить прогу через шлюз не по умолчанию

Предположим, вы на линуксе для безопасности всегда используете впн и вам надо, не отключая его, сделать запрос к какому-нибудь сайту через IP провайдера (потому что сайт блочит заход с впн или вы тестируете блокировки). Случайно нашёл прогу, которая может это сделать https://github.com/JsBergbau/BindToInterface. По сути, универсальный аналог curl --interface. Может, кому пригодится для тестов.
Недостатки:

  1. dns резолвится всегда через дефолтный шлюз.
  2. утекает ipv6, нет его поддержки.
  3. я думаю, не будут поддерживаться static бинарники, но даже go и rust можно собрать в shared.

Сборка:
gcc -nostartfiles -fpic -shared bindToInterface.c -o bindToInterface.so -ldl -D_GNU_SOURCE

Запуск (на примере проксирования curl):
BIND_INTERFACE=enp1s0 DNS_OVERRIDE_IP=9.9.9.9 LD_PRELOAD=./bindToInterface.so /usr/bin/curl https://ifconfig.me

Узнать какие есть шлюзы в системе:
ip a

На случай, если рипнется репа, прикрепляю исходный код bindToInterface.c
bindToInterface.c (4,6 КБ)

Возможно, кто навайбкодит код получше. С поддержкой dns и ipv6.

Стандартно без хаков это решается через

  1. Запуск процесса под отдельным юзером
  2. Отдельная таблица маршрутизации
  3. ip rule uidrange lookup
  4. ip/nf tables masquerade в output nat, если прога не умеет биндаться на конкретный IP нужного интерфейса для исходящих конектов. возможно, сработает src в ip route

Самое надежное - netns и veth с основным, таким образом приложение полностью изолируется от сетевого стека хоста и можно его куда угодно и писать трафик

На это есть универсальный движок прокси, который для всей императивной грязи в системе имеет приятный декларативный DSL конфигурации.

route.rules

{
  "action": "sniff"
},
{
  "domain": [
    "example.com"
  ],
  "action": "route",
  "outbound": "direct"
},

Оно само TUN интерфейс умеет создавать.

inbounds

{
  "type": "tun",
  "tag": "tun-in",
  "interface_name": "singtun0",
  "address": [
    "172.19.16.1/30",
    "fdfe:dcba:9876::1/126"
  ],
  "stack": "gvisor",
  "auto_route": true,
  "auto_redirect": true,
  "strict_route": true
}

outbounds

// { здесь ваш upstream },
{
  "type": "direct",
  "tag": "direct"
}

И по умолчанию трафик, согласно вашему описанию, идет в туннель:
route.final

"final": "upstream-out"

Всякие прочие кустарные сборки, которые нужно на каленке запускать лично я считаю колхозом. Когда тут есть полноценная удобная реализация сетевой инфраструктуры продакшн уровня.


Лечится:
route.rules

{
  "type": "logical",
  "mode": "or",
  "rules": [
    {
      "protocol": "dns"
    },
    {
      "port": 53
    }
  ],
  "action": "hijack-dns"
}

dns

"servers": [
  {
    "tag": "google",
    "type": "tls",
    "server": "8.8.8.8",
     "tls": {
       "enabled": true,
       "server_name": "dns.google"
    }
  },
  {
    "tag": "remote",
    "type": "fakeip",
    "inet4_range": "198.18.0.0/15",
    "inet6_range": "fc00::/18"
  }
],
"rules": [
  {
    "query_type": [
      "A",
      "AAAA"
    ],
    "server": "remote"
  }
]

В приведенных выше примерах конфигурации есть поддержка IPv6.


По процессам тоже можно маршрутизацию делать:

И по протоколам.

Сделал для почтового сервера axigen, но румыны испугались и удалили код.

Похоже, прога не работает с go софтом, даже shared.

go софт легко пропатчить: у меня по ссылке в профиле в репозитории в модуле common есть тяп-ляп функция

func ParseInterfaceName(interfaceName string) (NetworkInterface, error)

Можете использовать для получения адресов интерфейса по алиасингу, затем в функции подключения скопируйте код определения protocol family адреса назначения для выбора нужного типа локального адреса.

Я писал про прогу в шапке.
А что касается вашего модуля common даже и не знаю, как приладить его. Ничего не понял из ваших слов, т.к. не программист.
На всякий случай, выполнил go build в папке common, но ничего не появилось.

Надо кусок из common/config.go вставить в исходный код проксируемых go прог, а потом их собрать? Только вот куда?

Да, вставить можно в любой существующий файл программы или создать новый. Но надо будет добавить обработку нового параметра командной строки или опции конфигурации, и найти вызовы функции Dial и определить параметр LocalAddr — непрограммисту это, наверное, будет сложновато.

Да, вот, кому надо, готовый скрипт для запуска shell в изолированном namespace с указанием интерфейса, через который пускать трафик наружу. А из shell уже можно запускать хоть консольный curl, хоть браузер.

Не доверяю я скриптам, которые непонятно что делают с сетью. Вот и этот скрипт разорвал соединение, а потом сетевой интерфейс исчез из системы. Пришлось перезагружаться.

Не доверяете, но запустили =) Этот скрипт отправляет указанный интерфейс в неймспейс. Т.е. кейс, например, такой. У вас есть сетевая карта Ethernet и wifi. Или вы воткнули телефон по usb и включили режим tethering. Тогда у вас в системе будет два (несколько) сетевых интерфейсов с дефолтными маршрутами, но трафик будет идти через один из них (напирмер, провод) из-за того, что у него больший приоритет в системе (меньше метрика). С помощь этого скрипта вы можете один интерфейс перекинуть в неймспейс. И программы запущенные там будут ходить через этот интерфейс в сеть. Если интефейс всего один, и вы его отправите в неймспейс, то для других программ сеть пропадет (интерфейс исчезнет, но он будет работать в неймспейсе).

Но описанный вами в первом сообщении кейс (чтобы программа ходила в интернет как надо вам, а не так, как остальные) тоже можно решить с использованием неймспейсов и veth. Посмотрите, как пример, второй скрипт в репозитории.

Вот этого мне как раз не надо. Есть интерфейсы enp1s0 и vpn (дефолт). vpn ходит через enp1s0. Надо запустить какую-нибудь прогу через enp1s0. А скрипт просто убрал enp1s0 от всех.

Но заранее ждал подвоха.

Вот скрипт. Запускать через sudo. Из-за кавычек, скорее всего не заработает при копировании отсюда, поэтому прикреплю также файл.
script.sh (804 байта)

#!/bin/bash

IFNAME=enp1s0

user=$SUDO_USER

ip netns add ns1
ip link add dev veth0 type veth peer name veth1
ip link set veth1 netns ns1

ip addr add 10.20.30.0/31 dev veth0
ip -n ns1 addr add 10.20.30.1/31 dev veth1

ip link set veth0 up
ip -n ns1 link set veth1 up
gateway=$(ip -4 addr show dev $IFNAME | grep -oP ‘(?<=inet\s)\d+(.\d+){3}’ | sed ‘s/.[0-9]+$/.1/’)

ip -n ns1 r add default via 10.20.30.0 dev veth1
ip rule add iif veth0 lookup 100
ip r add default via $gateway dev $IFNAME table 100

iptables -t nat -I POSTROUTING -o $IFNAME -j MASQUERADE
echo 1 > /proc/sys/net/ipv4/ip_forward

mkdir -p /etc/netns/ns1
echo “nameserver 1.1.1.1” > /etc/netns/ns1/resolv.conf 2>/dev/null
IP=$(ip netns exec ns1 curl -s -4 ifconfig.me)
ip netns exec ns1 sudo -u $user PS1=“($IP) $PS1” bash --norc

Спасибо. Но namespace я теперь боюсь использовать.

Оказывается, Go один такой (биндится нестандартно). Даже Rust софт нормально переназначается на нужный интерфейс.

Остаются костыли:

  1. Запустить локальный прокси, через либу натравленный на нужный интерфейс. Например, BIND_INTERFACE=enp1s0 LD_PRELOAD=./bind.so c_local_proxy 127.0.0.1 8080
  2. Молиться, чтобы Go софт имел настройки прокси, в которых можно указать этот локальный прокси. Например, go-app -proxy http://127.0.0.1:8080
    Конечно, UDP отлетает.

ИИ не может одолеть эту Go особенность. Тоже предлагает патчить исходники.

Ну и зря! Я протестировал скрипт, когда у меня был включен OpenVPN с перенаправлением всего трафика в него. После запуска скрипта получаю shell, в котором трафик идёт через интерфейс, указанный в IFNAME=. Я думаю, вы можете спросит гпт, что означают команды, и, в случае необходимости, изменить их под свои нужды.

Ещё можно через маркировку приложения через DSCP (ToS)

Или можно указать так:

export http_proxy=http://127.0.0.1:8080
export https_proxy=http://127.0.0.1:8080

set вместо export на винде. Но проксируется не всё.