百种弊病,皆从懒生

SNET dev note: Support MacOS

2019.06.20

这两天得了空, 让 snet 支持了下 MacOS.

snet 的大致原理是通过系统防火墙的流量重定向功能,将所有去往国外的流量导到 snet 监听的端口, 在程序内部 将流量传递给上游的 proxy server(ss, http), 拿到响应后再回给客户端.

实现关键是要在 snet 内部获取到流量的原目标地址, 因为重定向之后 tcp connnection 的目标地址变成了 snet 监听 的地址.

Linux 上的实现,以前讲过: https://blog.monsterxx03.com/2019/03/31/snet-transparent-ss-proxy-on-linux/ 是通过 SO_ORIGINAL_DST 这个 socket option 实现的.

MacOS 上没有 iptables, 类似的工具是系统自带的 pfctl, 捣鼓了一下也能实现一样的功能.

用 pfctl 做流量重定向

pfctl 的文档可以通过 man pfctl, man pf.conf 查看. 我也只是看了个大概, 细节并不清楚.

流量重定向需要用到 pf 的 rdr 规则, 但是 rdr 只能处理 incoming 的流量,对 outgoing 的流量无效.

tricky 的地方是要把流量先重路由到 lo0, 再对 lo0 上的流量实行 rdr, 例子(顺序很重要):

dev="en0"
lo="lo0"
rdr on $lo proto tcp from $dev to any port 1:65535 -> 127.0.0.1 port 1100  # let proxy handle tcp 
pass out on $dev route-to $lo proto tcp from $dev to any port 1:65535  # re-route outgoing tcp

获取重定向后流量的原始目标地址

darwin 上没有 SO_ORIGINAL_DST, 翻了点 OpenBSD 的资料, 找到了几段通过 C 获取原地址的代码, 尝试用 CGO 做 wrapper, 最后试成功了.

Mac 上坑爹的地方在于, 虽然 darwin 内核里有 pf 模块, 但 MacOS 系统里没有对应的头文件,需要自己下载:

做了个 poc 的例子, 可以直接跑起来: https://github.com/monsterxx03/pf_poc

大致流程是:

  • 初始化 struct pfioc_natlook
  • 填充 client socket 的 source ip, source port
  • 填充 proxy bind socket 的 ip, port
  • 打开 /dev/pf,执行 ioctl(fd, DIOCNATLOOK, <pointer to pfioc_natlook>)
  • 然后 struct 的 rdaddr, rdxport 就被填充了,这就是我们需要的原始地址
comments powered by Disqus