• 使用ebpf 监控linux内核中的nat转换


    1.简介

    Linux NAT(Network Address Translation)转换是一种网络技术,用于将一个或多个私有网络内的IP地址转换为一个公共的IP地址,以便与互联网通信。

    在k8s业务场景中,业务组件之间的关系十分复杂.

    由于 Kubernetes 的网络模型假设Pod之间访问时使用的是对方Pod 的实际地址,所以一个Pod内部的应用程序看到的自己的IP地址和端口与集群内其他Pod看到的一样 。它们都是Pod实际分配的IP地址 。 将IP地址和端口在Pod内部和外部都保待一致,也就不需要使用 NAT 进行地址转换了。

    一些场景如cilium 并没有使用Netfilter的NAT转换.

    2.linux中的NAT转换

    linux的NAT转换是基于 Netfilter 网络框架实现的。

    Netfilter 是 Linux 内核中一个对数据 包进行控制、修改和过滤(manipulation and filtering)的框架。它在内核协议 栈中设置了若干hook 点,以此对数据包进行拦截、过滤或其他处理。 Netfilter 是最古老的内核框架之一,1998 年开始开发,2000 年合并到 2.4.x 内 核主线版本 [5]。

    Netfilter 官方文档:

     
    

    https://www.netfilter.org/documentation/index.html

    netfilter是Linux内核的包过滤框架,它提供了一系列的钩子(Hook)供其他模块控制包的流动。这些钩子包括

    NF_IP_PRE_ROUTING:刚刚通过数据链路层解包进入网络层的数据包通过此钩子,它在路由之前处理

    NF_IP_LOCAL_IN:经过路由查找后,送往本机(目的地址在本地)的包会通过此钩子

    NF_IP_FORWARD:不是本地产生的并且目的地不是本地的包(即转发的包)会通过此钩子

    NF_IP_LOCAL_OUT:所有本地生成的发往其他机器的包会通过该钩子

    NF_IP_POST_ROUTING:在包就要离开本机之前会通过该钩子,它在路由之后处理。

    3.使用conntrack读取nat转换

    conntrack 是 netfilter一个模块。

    NAT是在连接跟踪的基础上实现的,所以conntrack肯定是在NAT之前建立的。

    conntrack注册的优先级:

     
    
    1. enum nf_ip_hook_priorities {
    2. NF_IP_PRI_FIRST = INT_MIN,
    3. NF_IP_PRI_RAW_BEFORE_DEFRAG = -450,
    4. NF_IP_PRI_CONNTRACK_DEFRAG = -400,
    5. NF_IP_PRI_RAW = -300,
    6. NF_IP_PRI_SELINUX_FIRST = -225,
    7. NF_IP_PRI_CONNTRACK = -200,
    8. NF_IP_PRI_MANGLE = -150,
    9. NF_IP_PRI_NAT_DST = -100,
    10. NF_IP_PRI_FILTER = 0,
    11. NF_IP_PRI_SECURITY = 50,
    12. NF_IP_PRI_NAT_SRC = 100,
    13. NF_IP_PRI_SELINUX_LAST = 225,
    14. NF_IP_PRI_CONNTRACK_HELPER = 300,
    15. NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
    16. NF_IP_PRI_LAST = INT_MAX,
    17. };

    我们可以看到,NAT是在连接跟踪的基础上实现的,所以连接跟踪肯定是在NAT之前建立的。在上面的优先级中,优先级越小,越容易被先调用。

    使用conntrack 查看当前节点NAT 转换情况

    sudo conntrack -L -j

     
    
    tcp 6 82 SYN_SENT src=172.19.0.2 dst=192.168.101.98 sport=55628 dport=443 [UNREPLIED] src=192.168.101.98 dst=192.168.1.3 sport=443 dport=55628 mark=0 use=1 conntrack v1.4.6 (conntrack-tools): 1 flow entries have been shown.

    所以我们使用ebpf hook conntrack 就可以查看当前节点NAT 转换情况。

    4. 使用ebpf hook conntrack

    使用ebpf 拦截conntrack,我们主要拦截:

    1. SEC("kprobe/__nf_conntrack_hash_insert")
    2. SEC("kprobe/ctnetlink_fill_info")

    实现主流程 

    hook住 __nf_conntrack_hash_insert:

    1. SEC("kprobe/__nf_conntrack_hash_insert")
    2. int BPF_KPROBE(kprobe___nf_conntrack_hash_insert, struct nf_conn *ct,unsigned int hash, unsigned int reply_hash) {
    3. u32 status = ct_status(ct);
    4. __maybe_unused possible_net_t p_net = BPF_CORE_READ(ct, ct_net);
    5. if (!(status&IPS_CONFIRMED)) {
    6. log_debug("kprobe/__nf_conntrack_hash_insert include IPS_CONFIRMED: netns: %u, status: %x\n", get_netns(&p_net), status);
    7. return 0;
    8. }
    9. if (!(status&IPS_NAT_MASK)) {
    10. return 0;
    11. }
    12. if (!(status&IPS_CONFIRMED) || !(status&IPS_NAT_MASK)) {
    13. log_debug("kprobe/filter: netns: %u, status: %x\n", get_netns(&p_net), status);
    14. return 0;
    15. }
    16. conntrack_tuple_t orig = {}, reply = {};
    17. if (nf_conn_to_conntrack_tuples(ct, &orig, &reply) != 0) {
    18. return 0;
    19. }
    20. bpf_map_update_with_telemetry(conntrack, &orig, &reply, BPF_ANY);
    21. bpf_map_update_with_telemetry(conntrack, &reply, &orig, BPF_ANY);
    22. increment_telemetry_registers_count();
    23. return 0;
    24. }

    hook住ctnetlink_fill_info:

    1. SEC("kprobe/ctnetlink_fill_info")
    2. int BPF_KPROBE(kprobe_ctnetlink_fill_info, struct nf_conn *ct) {
    3. proc_t proc = {};
    4. bpf_get_current_comm(&proc.comm, sizeof(proc.comm));
    5. if (!proc_t_comm_prefix_equals("system-probe", 12, proc)) {
    6. log_debug("skipping kprobe/ctnetlink_fill_info invocation from non-system-probe process\n");
    7. return 0;
    8. }
    9. u32 status = ct_status(ct);
    10. if (!(status&IPS_CONFIRMED) || !(status&IPS_NAT_MASK)) {
    11. return 0;
    12. }
    13. __maybe_unused possible_net_t c_net = BPF_CORE_READ(ct, ct_net);
    14. log_debug("kprobe/ctnetlink_fill_info: netns: %u, status: %x\n", get_netns(&c_net), status);
    15. conntrack_tuple_t orig = {}, reply = {};
    16. if (nf_conn_to_conntrack_tuples(ct, &orig, &reply) != 0) {
    17. return 0;
    18. }
    19. bpf_map_update_with_telemetry(conntrack, &orig, &reply, BPF_ANY);
    20. bpf_map_update_with_telemetry(conntrack, &reply, &orig, BPF_ANY);
    21. increment_telemetry_registers_count();
    22. return 0;
    23. }

    自此将kernel中的nat 的sock 五元组采集到了ebpf的map中,上报到用户空间。

  • 相关阅读:
    11 Python 进程与线程编程
    前端框架 Electron 使用总结
    Go-Excelize API源码阅读(十三)—— GetSheetVisible、SetSheetFormatPr
    CentOS 7 源码制作ngnx-1.22.1-ipv6 rpm —— 筑梦之路
    基于紫光同创FPGA的图像采集及AI加速
    Redis企业版数据库如何支持实时金融服务?
    本周Github有趣的项目、工具和库:Radius等
    MytatisP详解
    小样本分割:构建数据集Pascal-5i
    小谈设计模式(10)—原型模式
  • 原文地址:https://blog.csdn.net/qq_32783703/article/details/133611912