tproxy工作原理

TL;DR
在不改变数据包本身(mark除外)的情况下 tproxy为满足规则的数据包直接分配了规则里指定的 bind 在本地的某个设置了 IP_TRANSPARENT 的 socket* (把 skb->sk 设置为透明代理监听的 socket, 换句话说此时的 skb 甚至还没有经过3层的路由就已经被提前内定了 socket 它改变了协议栈 雾)

注: 如果透明代理需要支持udp的话还需要在 socket 上启用 IP_RECVORIGDSTADDR, 并且增加额外的逻辑处理带有原始地址的 IP_ORIGDSTADDR 消息 (当然还有一些别的方式也可以获取到udp的原始目的地址就是), 要发送响应的话需要在那个原始目的地址上手动创建新 socket, 因为udp的透明代理 socket 并不会像 tcp 的实现那样在 accept 的时候自动 bind 到 skb 的原始目的地址

tproxy 在 PREROUTING 里给 skb 提前分配好了 sk, skb 随后才会被路由, 如果目的是本机的话就会进到 input 的逻辑里交给协议栈的更高层来处理(会根据传输层协议的不同分别由协议栈的不同部分来处理最终分发给对应的 socket), 而在 hash table 里真正开始查找 socket 前有这样一处逻辑可以直接从 skb 里拿到 tproxy 在之前分配好的 socket (tcp和udp的实现是分开的, 分别在__inet_lookup_skb__udp4_lib_rcv 里)

光是为 skb 分配了一个 sk 当然还是不够的,  skb 必须走到 input 上去才能被本机 socket 处理(透明代理的 socket 当然也不例外), 否则就直接被 forward 了, 而这就是 tproxy 需要配合额外的策略路由才能正常工作的原因

# 比如 ss-redir 文档里的
# Add any UDP rules
ip route add local default dev lo table 100
ip rule add fwmark 1 lookup 100
iptables -t mangle -A SHADOWSOCKS -p udp --dport 53 -j TPROXY --on-port 12345 --tproxy-mark 0x01/0x01
# Apply the rules
...
iptables -t mangle -A PREROUTING -j SHADOWSOCKS

本来行文至此已经可以结束了, 但我感觉好像有哪里怪怪的.

lo 上面并没有绑定 skb 的目的 IP 地址耶, 却还是能正常处理这个 skb 然后交给上层的 socket , 这就让我很好奇了.....

原因其实并不复杂, 因为上面添加的那条路由规则是 local 的, 根据这里的逻辑会通过ip_local_deliver 直接交给本机上层协议栈处理 (其实正常情况下所谓某个接口绑定了某个 ip 就是通过往路由表里自动添加了 local 的路由来实现的)

最后留个有点跑题的小问题叭, local 的路由规则里指定的 dev 有意义吗? 答案就在 route.c 里 (或者大概也能猜到 bushi)

Reference:
[1] https://blog.cloudflare.com/how-we-built-spectrum/

[2] http://vger.kernel.org/~davem/skb.html

[3] https://man7.org/linux/man-pages/man8/ip-route.8.html

[4] https://stackoverflow.com/questions/42738588/ip-transparent-usage

[5] https://man7.org/linux/man-pages/man7/ip.7.html

[6] https://elixir.bootlin.com/linux/v5.11.11/source

[7] https://vincent.bernat.ch/en/blog/2017-ipv4-route-lookup-linux

点击右边的按钮加载评论,如果无法加载那估计是被墙啦..你看着办w