Туннелирование трафика: простое решение на Go 2
- четверг, 19 марта 2026 г. в 00:00:09
Ранее я начал писать небольшую программу на Go, чтобы прокидывать трафик между компьютером и своим сервером. Назначение программы исследовательское - посмотреть "как это работает" и разобраться, как именно работают механизмы, мешающие нормальному интернету.

В продолжение эксперимента: имеется подключение к интернету через менее дружелюбного провайдера и ноут на macOS (первая версия была под Linux). Прежде чем усложнять код, решил запустить программу на маке без изменения логики. Напомню: весь функционал - пакеты прокидываются по цепочке tun0 -> tun1 -> inet. Это минимальная заготовка, на которой удобно разбираться с туннелями.
Принципиальных изменений в коде нет, но туннели создаются и настраиваются иначе. В Linux-варианте я открывал туннель без сторонних библиотек, здесь же разбираться не стал и использовал библиотеку water (пока не важно, потом нужно сделать единообразно). Программа завелась без проблем, а вот с маршрутизацией пришлось повозиться.
Для начала нужно включить форвардинг пакетов (для этапа tun1 -> inet):
sysctl -w net.inet.ip.forwarding=1
Когда туннели поднимаются, в таблице маршрутизации появляются две записи:
netstat -nr -f inet
Routing tables Internet: Destination Gateway Flags Netif Expire ... 10.0.0.1 10.0.0.2 UHr utun10 10.0.1.1 10.0.1.2 UHr utun11 ...
Привязать пинг к конкретному интерфейсу нельзя. Можно указать источник через -S, это не совсем то, но допустим:
ping -S 10.0.0.2 8.8.8.8
Система отказывается отправлять пакеты в нужный интерфейс, если по таблице маршрутизации они попадают в другой (en0). До туннеля пакеты не доходят, программа их не видит.
PING 8.8.8.8 (8.8.8.8) from 10.0.0.2: 56 data bytes ping: sendto: No route to host ping: sendto: No route to host
Проблема решается указанием маршрута по умолчанию для конкретного интерфейса:
route add -ifscope utun10 default 10.0.0.1
Routing tables Internet: Destination Gateway Flags Netif Expire default 192.168.1.1 UGScg en0 default 10.0.0.1 UGScIg utun10 default 10.0.1.1 UGScIg utun11 10.0.0.1 10.0.0.2 UHr utun10 10.0.1.1 10.0.1.2 UHr utun11
Осталось включить системный NAT для форвардинга utun11 -> en0.
echo "nat on en0 from 10.0.1.1/32 to any -> (en0)" | sudo pfctl -Ef -
После этого всё заработало:
PING 8.8.8.8 (8.8.8.8) from 10.0.0.2: 56 data bytes 64 bytes from 8.8.8.8: icmp_seq=0 ttl=105 time=49.326 ms 64 bytes from 8.8.8.8: icmp_seq=1 ttl=105 time=51.521 ms
tcpdump -k -i any -n icmp
19:00:37.914822 IP 10.0.0.2 > 8.8.8.8: ICMP echo request 19:00:37.914995 IP 10.0.1.1 > 8.8.8.8: ICMP echo request 19:00:37.915062 IP 192.168.1.137 > 8.8.8.8: ICMP echo request 19:00:37.968373 IP 8.8.8.8 > 192.168.1.137: ICMP echo reply 19:00:37.968483 IP 8.8.8.8 > 10.0.1.1: ICMP echo reply 19:00:37.968774 IP 8.8.8.8 > 10.0.0.2: ICMP echo reply
Может показаться, что изменений немного - просто запустил код на другой ОС. Однако отладка на стыке программы и системных настроек неудобна: ошибок нет, но ничего не работает. Зато теперь у меня есть всё необходимое, чтобы получать и отправлять пакеты из системы. Теперь можно пробовать прокидывать реальный трафик и переходить к самим экспериментам.
Ссылка на github.