Closed LostAttractor closed 2 months ago
UDP是无连接 且 无状态的, 但往往UDP连接也是成对的: 一个请求 对应 一个回复 例如对于
client.port -> server.port
会期望产生如下回复:
server.port -> client.port
这常见但不限于 DNS 和 QUIC
DAE目前无法追踪UDP的状态, 这意味着发往 lan 或 路由(dae所在设备) 的 UDP连接 的 回复 可能被代理 例如我们考虑如下拓扑:
lan
路由(dae所在设备)
client <--(wan)--> router(dae) <--(lan)--> server
然后对server产生一个DNS查询, 它会是:
client.port -> router(dae) -> server.53 server.53 -> router(dae) -> client.port
对于请求(第一步), 在dae看来是inbound, 自然直接不会处理 对于返回(第一步), 在dae看来是outbound, 因为没有连接追踪, dae无法根据第一步来判断这是回复, 因此会正常在做eBPF中的路由流程 而如果被分流到了代理, 则会进入到dae0, 从代理发出
最终变成:
client.port -> router(dae) -> server.53 server.53 -> router(dae) --(bpf_redirect)--> proxy_server.port -> client.port
虽然 client 的对应接口也能收到回复, 但因为是 proxy_server 进行的回复, 回复源IP和请求时的目标IP不一致, 自然不会理会结果
因此判断连接是一个新连接, 还是某一个连接的回复是必要的, 即需要UDP的连接追踪
因为 TCP 是有状态且面向连接的, 内核实现了类似功能, 而 DAE 会根据内核提供的状态对回复流量做绕过, 所以TCP回复可以正常工作
在 nft 中, nft 自己实现了 UDP 的连接追踪, conntrack功能具有ct state这个状态 即对于任意一个 a.port -> b.port 的UDP连接, 它的ct state会是new 而b.port -> a.port的数据路径相反的连接, 它的ct state会是established
ct state
a.port -> b.port
new
b.port -> a.port
established
这才使得如下规则可以让自己进行的 UDP 请求可以正常回复, 而其他UDP入站被丢弃
chain input { type filter hook input priority filter; policy drop; ct state established,related accept }
因此, 我们在基于netfiler的代理中可以单独绕过 UDP 请求的回复, 保证回复流量不被代理
要实现类似的连接追踪, 至少需要维护一个表, 监控所有来源是wan接口的UDP流量, 并做记录 而对目标是wan接口的流量, 判断是否有一个近期的连接, 数据路径完全相反, 如果有, 则绕过 因为要维护表用于判断近期的连接, 还需要考虑如何实现老化
wan接口
此外, 可能考虑会有不绑定 wan接口 或 各种 入站/回复流量序列 不经过 设置的 wan接口 的情况 可能也要对 lan接口 也做如上操作
lan接口
可以使得 UDP 回复不需要做特判就可以正常工作而不被代理
如果 UDP 无法正常回复, 很多应用都无法工作
Thanks for opening this issue!
@LostAttractor 我们正在探讨表项老化的实现,使用控制面双桶方案和内核 bpf timer 的方案的优劣,后者需要 5.15+。具体方案之后会 post 上来。
@LostAttractor 可以来测试一下 #493 吗
Improvement Suggestion
问题
UDP是无连接 且 无状态的, 但往往UDP连接也是成对的: 一个请求 对应 一个回复 例如对于
会期望产生如下回复:
这常见但不限于 DNS 和 QUIC
DAE目前无法追踪UDP的状态, 这意味着发往
lan
或路由(dae所在设备)
的 UDP连接 的 回复 可能被代理 例如我们考虑如下拓扑:然后对server产生一个DNS查询, 它会是:
对于请求(第一步), 在dae看来是inbound, 自然直接不会处理 对于返回(第一步), 在dae看来是outbound, 因为没有连接追踪, dae无法根据第一步来判断这是回复, 因此会正常在做eBPF中的路由流程 而如果被分流到了代理, 则会进入到dae0, 从代理发出
最终变成:
虽然 client 的对应接口也能收到回复, 但因为是 proxy_server 进行的回复, 回复源IP和请求时的目标IP不一致, 自然不会理会结果
因此判断连接是一个新连接, 还是某一个连接的回复是必要的, 即需要UDP的连接追踪
因为 TCP 是有状态且面向连接的, 内核实现了类似功能, 而 DAE 会根据内核提供的状态对回复流量做绕过, 所以TCP回复可以正常工作
Netfiler 是如何工作的
在 nft 中, nft 自己实现了 UDP 的连接追踪, conntrack功能具有
ct state
这个状态 即对于任意一个a.port -> b.port
的UDP连接, 它的ct state
会是new
而b.port -> a.port
的数据路径相反的连接, 它的ct state
会是established
这才使得如下规则可以让自己进行的 UDP 请求可以正常回复, 而其他UDP入站被丢弃
因此, 我们在基于netfiler的代理中可以单独绕过 UDP 请求的回复, 保证回复流量不被代理
可能的解决方案
要实现类似的连接追踪, 至少需要维护一个表, 监控所有来源是
wan接口
的UDP流量, 并做记录 而对目标是wan接口
的流量, 判断是否有一个近期的连接, 数据路径完全相反, 如果有, 则绕过 因为要维护表用于判断近期的连接, 还需要考虑如何实现老化此外, 可能考虑会有不绑定
wan接口
或 各种 入站/回复流量序列 不经过 设置的wan接口
的情况 可能也要对lan接口
也做如上操作Potential Benefits
可以使得 UDP 回复不需要做特判就可以正常工作而不被代理
如果 UDP 无法正常回复, 很多应用都无法工作