netfilter网络流转(network flow)与路由决策(routing decision)的网络流处理交互关系
1、场景:
我们可以通过iptables来基于netfilter机制下发我们的hook处理函数,那么我们平时iptables的四表五链与报文的路由决策之间存在什么样的依赖关系和先后关系呢
2、Traversing of packets
Receive:
某个interface收到数据包 -> PREROUTING (manage, nat) -> routing ->
是发送给本机的数据包? -> INPUT (manage, filter) -> app
不是 -> FORWARD (manage, filter) -> POSTROUTING (manage, nat) -> 某个interface发出
Send:
app发送数据包 -> routing -> OUTPUT (manage, nat, filter) -> (re)routing -> POSTROUTING (manage, nat) -> 某个interface发出
nat 表与会话管理(nf_conntrack): 每个连接只有第一个包会经过 nat 表,后面的包会直接按照之前包同样方式处理。
3、routing and rerouting
对于本机 app 发出(outcoming)的流量,netfilter 有2次 routing 过程,称为 routing 和 (re)routing。
流转如下:routing —> output —> rerouting
OUTPUT 链在 第一次 routing 之后:
对于本机 app 发出的流量,第一次 routing 除了确定下一跳之外,对于没有指定源IP的数据包,还将会为其选择源IP地址。当数据包经过了iptables OUTPUT链,某条rule为其打上了fwmark或者改变了其目标地址后,由于数据包属性已经改变,需要第二次路由,即 (re)routing。
OUTPUT 链之后的 rerouting :
Linux内核协议栈在实现第一次路由和第二次路由时,其逻辑是一样的。但请注意由于第一次路由时会为skb选择source地址,那么第二次路由时的命中路由条目的source属性将永远不会生效,所以多个网口设备策略路由时常常需要用 MASQUERADE 重写 outcoming 数据包的 src IP 以保证正确,这是一种 workaround。
注意:
第一次 routing 时如果未找到匹配的路由条目,会直接失败(“Network is unreachable”),不会再继续经过 iptables 各个链。配置错误时会出现这种情况,用 ip route get 有时无法检测出来,例如对于 “ip route get 8.8.8.8 mark 0x2” 这种指定 mark 的流量,由于第一次 routing 时还没有 MARK (打 mark 在 -t mangle PREROUTING / OUTPUT 阶段),如果对于无 MARK 的流量 ip rule / ip route 找不到路由条目,则实际上网络访问会失败。这种情况解决方法是添加一个 dummy 的默认路由专用于第一阶段 routing,只要语法正确即可,即使实际上路由项不合法(比如下一跳IP地址没有对应的机器)亦可,因为这个路由项的目的只是让流量继续在 netfilter flow 里走下去,真正使用的路由条目会在第二次 routing 时被选择。
4、Flow graph
简化流程图:
(注:此图有个小问题,filter FORWAED 后面应该直接是 mangle POSTROUTING, 没有 route decision)
完整流程图:
“netfilter network flow and routing decision” Tips
- RAW table 只与 conntrack 有关。When one drops a packet in raw table, the packet never reaches the conntrack module. This means that no connection tracking entry is created / consulted during packet’s flow in the blocked direction. 而在 RAW 被 DROP 的包仍然继续进入 mangle / filter / nat, 并且最终可以正常被本机(INPUT)的应用程序接收。在实际使用中,iptables -t raw -A PREROUTING -p tcp --dport 1081 -j DROP 常常用于配合反向代理程序(例如convey(设计文档)),阻止 kernel 自动对接收到的包创建并维护 TCP 连接(TCP passthrough)。参考资料。
- 上面的图中画出了两个 “route decision” 阶段,但对于 receive / send 而言,其各自实际上应该主要使用了1个 route 阶段: receive 的 route 在 PREROUTING 之后;send 的 route 在 OUTPUT 之后。
- Receive时判断是否是发送给本机的数据包的方法是:收到数据包的destination IP是否与本机某个interface的IP相同。
- Send的OUTPUT chain除了manage和filter以外nat表里也有。nat的OUTPUT位于filter的之前,用于对从本机(app)发出的(而不是收到并FORWARD的)请求做DNAT或REDIRECT。
Send的route阶段确定了数据包的source ip和source port。source ip和source port由app发送数据包时决定。如果app没有bind某个interface并且没有设置source ip/port,则source ip时为route使用的interface ip。 - filter表的INPUT, OUTPUT和FORWARD用来过滤数据包 -j ACCEPT /DROP
- nat表的PREROUTING / OUTPUT用来做DNAT(或REDIRECT), POSTROUTING用来做SNAT(或MASQUERADE)
- FORWARD (以及所有 FORWARD 之后的 chain) 需要设置内核参数 “net.ipv4.ip_forward=1”。(否则内核网络栈会直接丢弃网卡收到的 dst 非本机的数据包)
- nat 表的 PREROUTING / POSTROUTING 链有些时候会被跳过。主要是指对于有连接的会话 (conn),只有初始发送的数据包会经过 PREROUTING / POSTROUTING,之后发送的数据包以及收到的来自对方的数据包都会根据内核维护的 conn 连接表而被直接处理。
- conntrack (Connection tracking) 工作在 PREROUTING / OUTPUT 链(分别对于收到的/本机发出的数据包)。
参考文档:
- netfilter network flow
- Traversing of tables and chains