使用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:在包就要离开本机之前会通过该钩子,它在路由之后处理。

使用ebpf 监控linux内核中的nat转换_第1张图片

3.使用conntrack读取nat转换

conntrack 是 netfilter一个模块。

使用ebpf 监控linux内核中的nat转换_第2张图片

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

conntrack注册的优先级:

 
  
enum nf_ip_hook_priorities {
	NF_IP_PRI_FIRST = INT_MIN,
	NF_IP_PRI_RAW_BEFORE_DEFRAG = -450,
	NF_IP_PRI_CONNTRACK_DEFRAG = -400,
	NF_IP_PRI_RAW = -300,
	NF_IP_PRI_SELINUX_FIRST = -225,
	NF_IP_PRI_CONNTRACK = -200,
	NF_IP_PRI_MANGLE = -150,
	NF_IP_PRI_NAT_DST = -100,
	NF_IP_PRI_FILTER = 0,
	NF_IP_PRI_SECURITY = 50,
	NF_IP_PRI_NAT_SRC = 100,
	NF_IP_PRI_SELINUX_LAST = 225,
	NF_IP_PRI_CONNTRACK_HELPER = 300,
	NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,
	NF_IP_PRI_LAST = INT_MAX,
};

我们可以看到,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,我们主要拦截:


SEC("kprobe/__nf_conntrack_hash_insert")
 SEC("kprobe/ctnetlink_fill_info")

实现主流程 

使用ebpf 监控linux内核中的nat转换_第3张图片

hook住 __nf_conntrack_hash_insert:

SEC("kprobe/__nf_conntrack_hash_insert")
int BPF_KPROBE(kprobe___nf_conntrack_hash_insert, struct nf_conn *ct,unsigned int hash, unsigned int reply_hash) {
    u32 status = ct_status(ct);
    __maybe_unused possible_net_t p_net = BPF_CORE_READ(ct, ct_net);
    if (!(status&IPS_CONFIRMED)) {
        log_debug("kprobe/__nf_conntrack_hash_insert include IPS_CONFIRMED: netns: %u, status: %x\n", get_netns(&p_net), status);
        return 0;
    }
    if (!(status&IPS_NAT_MASK)) {
        return 0;
    }
    if (!(status&IPS_CONFIRMED) || !(status&IPS_NAT_MASK)) {
        log_debug("kprobe/filter: netns: %u, status: %x\n", get_netns(&p_net), status);
        return 0;
    }


    conntrack_tuple_t orig = {}, reply = {};
    if (nf_conn_to_conntrack_tuples(ct, &orig, &reply) != 0) {
        return 0;
    }

    bpf_map_update_with_telemetry(conntrack, &orig, &reply, BPF_ANY);
    bpf_map_update_with_telemetry(conntrack, &reply, &orig, BPF_ANY);
    increment_telemetry_registers_count();
    return 0;
}

hook住ctnetlink_fill_info:

SEC("kprobe/ctnetlink_fill_info")
int BPF_KPROBE(kprobe_ctnetlink_fill_info, struct nf_conn *ct) {

    proc_t proc = {};
    bpf_get_current_comm(&proc.comm, sizeof(proc.comm));

    if (!proc_t_comm_prefix_equals("system-probe", 12, proc)) {
        log_debug("skipping kprobe/ctnetlink_fill_info invocation from non-system-probe process\n");
        return 0;
    }

    u32 status = ct_status(ct);
    if (!(status&IPS_CONFIRMED) || !(status&IPS_NAT_MASK)) {
        return 0;
    }

    __maybe_unused possible_net_t c_net = BPF_CORE_READ(ct, ct_net);
    log_debug("kprobe/ctnetlink_fill_info: netns: %u, status: %x\n", get_netns(&c_net), status);

    conntrack_tuple_t orig = {}, reply = {};
    if (nf_conn_to_conntrack_tuples(ct, &orig, &reply) != 0) {
        return 0;
    }

    bpf_map_update_with_telemetry(conntrack, &orig, &reply, BPF_ANY);
    bpf_map_update_with_telemetry(conntrack, &reply, &orig, BPF_ANY);
    increment_telemetry_registers_count();

    return 0;
}

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

你可能感兴趣的:(linux,运维,服务器)