daeuniverse / dae

eBPF-based Linux high-performance transparent proxy solution.
GNU Affero General Public License v3.0
3.28k stars 204 forks source link

[Proposal] Simplify tproxy forward datapath #379

Open jschwinger233 opened 10 months ago

jschwinger233 commented 10 months ago

Proposal

如果我理解正确的话, 考虑在 host 发起需要被代理的请求,当前 datapath 是:

  1. tproxy_wan_egress@eth0 bpf_redirect 给自己
  2. tproxy_wan_ingress@eth0 执行 NAT
  3. skb 被 tproxy server (dae) accept
  4. dae 发起新链接给代理服务器,我把这个给代理的请求叫做 tproxy forward traffic
  5. cgroup/connect4,sendmsg4 等 hook 根据 skb cookie 更新 bpf map: cookie -> pid
  6. tproxy forward traffic 在 tproxy_wan_egress@eth0 根据 cookie 查到 pid,发现和 tproxy pid 一致,鉴定为 tproxy forward traffic,直接放行。

我的想法是,是不是可以直接通过 skb->mark 来鉴定 tproxy 流量,比如:

  1. dae 发起新链接给代理服务器,这时候在 dae 用户态可以直接设置 mark: setsockopt(SOL_SOCKET, SO_MARK, 0x100)
  2. 然后不需要 cgroup/* ,在 tproxy_wan_egress@eth0 直接通过 0x100 的 skb->mark 来判断 tproxy 流量放行

这样的话可以优化掉一些复杂的代码逻辑,不知是否我遗漏了细节。

Use Cases

No response

Potential Benefits

No response

Scope

No response

Reference

No response

Implementation

No response

dae-prow[bot] commented 10 months ago

Thanks for opening this issue!

mzz2017 commented 10 months ago

@jschwinger233 我的理解是,当前的 proposal 准备优化当前 output 方向,通过 pid 来判断 tproxy 流量改为通过 mark 来判断 tproxy流量。

但 cgroup/* 是不会被优化掉的,因为 dae 支持匹配 process name,这部分需要这些 bpf 程序。

通过 mark 来规避 from dae 的流量是可行的,但 mark 有其他作用,dae 支持在路由中 xxx -> direct(mark: xxx) 来打 mark,这可能会扰乱判断。

jschwinger233 commented 10 months ago

@mzz2017 匹配 process name 我有一种更简单的做法:

    struct task_struct *curr_task = (struct task_struct *) bpf_get_current_task();
    if (!curr_task)
        return;
    bpf_printk("proc %s\n", BPF_CORE_READ(curr_task, comm));

tc bpf 是可以调用 bpf_get_current_task() 的,这样可以直接通过 task->comm 读取进程名字。即,不需要 cgroup/* hooks 就能直接在 tc bpf 里匹配进程名。

至于 mark 冲突的问题,可以编程避免这种错误,比方说,如果 dae 使用了 0xa00,那用户配置 mark 也是 0xa00 的话就直接在检查配置的时候报错。

mzz2017 commented 10 months ago

@jschwinger233 get task 获取到的是 task 名,例如线程名,不能对应 process name,所以要从 pcb 里拿

jschwinger233 commented 10 months ago

@mzz2017 目前 dae 是通过 bpf_get_current_task()->mm->{arg_start,arg_end} 获取进程名的,我们可以在 tc bpf 里用相同的方法吗:

https://github.com/daeuniverse/dae/blob/a794c4ca61945a83732abac99b4b4ec276028882/control/kern/tproxy.c#L2163-L2165

mzz2017 commented 10 months ago

@jschwinger233 这一块程序非常大,用在 tc 里就超出程序指令限制了,所以独立开了

jschwinger233 commented 10 months ago

@mzz2017 cilium 里用 bpf tailcall 切分大程序来控制复杂度,而且几乎没有性能损失(多了一个 jmp 指令),我们也可以考虑这么做。 这样做的好处是,似乎可以不用管理 cookie -> pid 这个 map,减少查询,代码逻辑复杂度可能也会好一些。

mzz2017 commented 10 months ago

@jschwinger233 这太棒了,我们很需要这个

mzz2017 commented 10 months ago

@jschwinger233 实际上,route 函数也受限于指令数,有很多有趣的功能没办法拓展

mzz2017 commented 10 months ago

@jschwinger233 get task mm arg 如果能在 tc 中调用,程序之间的时序依赖关系也能解决,这是一个很好的解决方案。我没有尝试过,或许需要看看支持程度

jdjingdian commented 7 months ago

大佬们好,打扰大佬们了,最近在学eBPF和XDP,在网卡驱动支持的情况下,看起来eBPF XDP程序可以运行在网卡驱动poll函数的部分(XDP Natvie模式)

如果我没有理解错的话,用户态程序注册AF_XDP socket,这样eBPF XDP程序可以直接将网络数据包转发到AF_XDP socket,然后用户态程序就可以直接拿到网络数据包进行处理,通过AF_XDP socket发送的时候也是绕过内核协议栈直接到网卡驱动,这样就有点类似DPDK?

这样搞的话,优点就是不会受限于BPF指令数限制,功能都放到用户态做,

缺点的话,如果是direct流量,需要从网卡驱动拷贝到用户态空间一次,然后再从用户态空间拷贝到内核空间从网卡发出。 (或者在XDP的部分判断direct流量,非direct的转发到AF_XDP socket),另外就是开发工作量比较大,需要实现用户态协议栈。

jschwinger233 commented 7 months ago

@jdjingdian XDP generic 也可以用 AF_XDP,in XDP_SKB mode.

direct 流量不会被重定向到 XSK。

mzz2017 commented 7 months ago

@jdjingdian 我记得最开始我就在调研类似方案,有一个 repo 将 skb 直接发到 map 里,从 userspace 读出来,性能确实高了许多,但他说是有一些问题的,因此我没有深入了解和选用这个方案