veth网络设备原理

基于linux内核5.4.54 veth是一个虚拟网络设备,是通过linux内核网络设备驱动实现的


/* veth设备在内核上注册的调用方法 */
static const struct net_device_ops veth_netdev_ops = {
    .ndo_init            = veth_dev_init, /* 初始化设备 */
    .ndo_open            = veth_open, /* 打开设备 */
    .ndo_stop            = veth_close, /* 关闭设备 */
    .ndo_start_xmit      = veth_xmit, /* 传输数据包(重要) */
    .ndo_get_stats64     = veth_get_stats64, /* 拷贝硬件统计计数到用户空间 */
    .ndo_set_rx_mode     = veth_set_multicast_list, /* 里面是空函数,骗人的 */
    .ndo_set_mac_address = eth_mac_addr, /* 修改MAC地址 */
#ifdef CONFIG_NET_POLL_CONTROLLER
    .ndo_poll_controller    = veth_poll_controller, /* 里面是空函数,骗人的 */
#endif
    .ndo_get_iflink     = veth_get_iflink, /* 获取设备的iflink值(应该是编号) */
...
    .ndo_bpf        = veth_xdp, /* 设置或查询设备上与XDP相关的状态,以及管理BPF offload。 */
    .ndo_xdp_xmit       = veth_xdp_xmit, /* 传输XDP数据包 */
};


通过veth将两个容器连接起来

收发路径:


进程(容器1)
|--系统调用进入Namespace1的网络协议栈,容器1创建进程时会为进程设置network namespace
kernel层: 创建skb,从用户空间拷贝数据到内核空间
TCP/UDP封包
IP封包,跑Namespace1的路由和iptables
|--出协议栈进入网络设备
调用网络设备驱动的传输数据包函数
|
veth_xmit: veth驱动注册的传输函数
    |
    veth_forward_skb
        |
        __dev_forward_skb: 清除 skb 中可能影响命名空间隔离的所有信息
        |          并且会更新数据包要到达的网路设备(skb->dev),由eth0改为eth1
        |          数据包要跑的协议栈(network namespace)由skb->dev的nd_net字段决定
        |
        XDP钩子点
        |
        netif_rx
            |
            netif_rx_internal: cpu软中断负载均衡
                |
                enqueue_to_backlog: 将skb包加入指定cpu的input_pkt_queue队尾
                                    queue为空时激活网络软中断,
                                    queue不为空不需要激活软中断,cpu没清空队列之前
                                    会自动触发软中断
    每个cpu都有自己的input_pkt_queue(接收队列,默认大小1000,可修改),和process_queue(处理队列),软中断处理函数处理完成process_queue中的所有skb包之后,会将将input_pkt_queue拼接到process_queue
    input_pkt_queue和process_queue是cpu为非NAPI设备准备的队列,NAPI设备有自己的队列

cpu处理网络数据包过程:

do_softirq()
|
net_rx_action: 网络软中断处理函数
    |
    napi_poll
        |
        n->poll: veth设备没有定义poll,调用默认poll函数-process_backlog
            |    
            process_backlog: cpu循环从process_queue中取出skb处理,最多处理300个skb,
                |            处理队列清空后,拼接input_pkt_queue到process_queue队尾
                |
                __netif_receive_skb
                    |
                    ...
                    |
                    __netif_receive_skb_core: 跑eth1所在的Namespace2的网络协议栈
                                              (最终容器2进程收到数据包)

总结veth工作过程:

容器1进程生成数据包—->eth0—>veth修改清除信息—->eth1—>容器2进程收到数据包

具体实现:

容器1进程发包到eth0, veth改数据包内容, 将数据包加入某个cpu的队列, 等待cpu处理 cpu处理数据包时, 运行容器2的eth1所在的网络协议栈(Namespace2), 最终发到容器2的应用中 相当于数据包从容器1中到了容器2中

硬件网卡设备接受数据包示意图

veth虚拟设备与硬件网卡设备区别

  1. veth设备与硬件网卡设备相比少了DMA,Ring Buffer,中断处理函数

  2. veth设备是零拷贝的

硬件网卡设备与veth相比有2次拷贝损耗: 一次是接收数据包时DMA到内核空间的拷贝,一次是发送数据包时内核空间到DMA的拷贝