В этой статье мы перенесем разработанный инструмент на macOS. Логика работы с трафиком в userspace сохраняется, но интеграция с системой требует иных решений. Разберем особенности создания utun-интерфейсов, настройки маршрутизации через ifscope и использования pf для NAT. По итогу запустим цепочку tun0 -> go app -> 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.