Intel HDSLB 高性能四层负载均衡器 — 代码剖析和高级特性

目录

文章目录

  • 目录
  • 前言
  • 代码剖析
    • 软件架构
    • 目录结构
    • 配置解析
    • 启动流程分析
      • 数据面 jobs 注册
      • 数据面 jobs 执行
    • 转发流程分析
      • 收包阶段
      • L2 处理阶段
      • L3 处理阶段
      • L4 处理阶段
  • 高级特性
    • 大象流转发优化
    • 快慢路径分离转发优化
    • 报文基础转发优化
  • 最后
  • 参考文档

前言

在前 2 篇文章中,我们从快速入门、应用场景、基本原理、部署配置这 4 个方面,整体地介绍了 Intel HDSLB 作为新一代高性能四层负载均衡器的研发背景、解决方案以及性能优势,并通过 step by step 的方式,希望帮助更多的读者能够便捷地在自己的开发机运行和使用起来。在本篇中,我们将继续向前,对 HDSLB-DPVS 开源版本的代码进行剖析,并介绍其中一些有趣的高级特性。

  1. 《Intel HDSLB 高性能四层负载均衡器 — 快速入门和应用场景》
  2. 《Intel HDSLB 高性能四层负载均衡器 — 基本原理和部署配置》

在这里插入图片描述

代码剖析

下载代码:

git clone https://github.com/intel/high-density-scalable-load-balancer.git

软件架构

在这里插入图片描述

上图是 HDSLB 的软件架构图,自上而下的可以分为下述 5 个层面。

  1. Control Plane 控制面层:依旧沿用了 LVS 的控制面,实现了下列 3 个 CLI 工具,使用了 Local unix socket 通信机制:

    1. ipvsadm:用于管理 ipvs 的配置。
    2. dpip:用于设置 VIP、RIP 和相关的 Route 规则。
    3. keepalive:用于 HA 和 RS 健康检查。
  2. Load Balancer 负载均衡层:实现了 scheduler 流量调度、proto 四层协议、conn 连接跟踪、FastPath 等功能模块。尤其是 FastPath 快慢路径分离是 HDSLB 对 DPVS 的核心优化之一。

  3. Lite IP-Stack 层:实现了 ARP / IPv4v6 / ICMP 等 L2-3 层网络协议、以及 inetaddr 和 L3 routing 等功能模块。

  4. Net Devices 层:实现了物理网卡纳管、bonding、VLAN、KNI、TC 流量控制,hw-addr-list 地址列表等功能模块。

  5. Hardware Acceleration 层:提供了 Intel CPU 和 NIC 硬件级别的加速技术,包括:FDIR mark、FDIR to queue、RSS、Checksum offload、AVX512、DLB、SR-IOV 等等。

目录结构

$ high-density-scalable-load-balancer git:(main) tree -L 2
.
├── Makefile
├── conf  # 配置示例目录
│   ├── hdslb.bond.conf.sample
│   ├── hdslb.conf.items
│   ├── hdslb.conf.sample
│   ├── hdslb.conf.single-bond.sample
│   └── hdslb.conf.single-nic.sample
├── include  # 头文件库目录
│   ├── cfgfile.h
│   ├── common.h
│   ├── conf
│   ├── ctrl.h
│   ├── dpdk.h
│   ├── flow.h
│   ├── global_conf.h
│   ├── icmp.h
│   ├── icmp6.h
│   ├── inet.h
│   ├── inetaddr.h
│   ├── ip_tunnel.h
│   ├── ipset.h
│   ├── ipv4.h
│   ├── ipv4_frag.h
│   ├── ipv6.h
│   ├── ipvs
│   ├── kni.h
│   ├── laddr_multiply.h
│   ├── lb
│   ├── linux_ipv6.h
│   ├── list.h
│   ├── log.h
│   ├── match.h
│   ├── mbuf.h
│   ├── md5.h
│   ├── mempool.h
│   ├── ndisc.h
│   ├── neigh.h
│   ├── netif.h
│   ├── netif_addr.h
│   ├── parser
│   ├── pidfile.h
│   ├── route.h
│   ├── route6.h
│   ├── route6_hlist.h
│   ├── route6_lpm.h
│   ├── sa_pool.h
│   ├── sys_time.h
│   ├── tc
│   ├── timer.h
│   ├── uoa.h
│   └── vlan.h
├── patch  # DPDK 补丁目录
│   ├── dpdk-16.07
│   ├── dpdk-20.08
│   ├── dpdk-stable-17.05.2
│   ├── dpdk-stable-17.11.2
│   └── dpdk-stable-19.11
├── scripts  # 运维脚本目录
│   ├── ipvs-tunnel.rs.deploy.sh
│   ├── setup.fnat.two-arm.sample.sh
│   ├── setup.snat-gre.sample.sh
│   ├── setup.snat.sample.sh
│   └── setup.tc.sample.sh
├── src  # 核心源码目录
│   ├── Makefile
│   ├── VERSION
│   ├── cfgfile.c
│   ├── common.c
│   ├── config.mk
│   ├── ctrl.c
│   ├── dpdk.mk
│   ├── global_conf.c
│   ├── icmp.c
│   ├── inet.c
│   ├── inetaddr.c
│   ├── ip_gre.c
│   ├── ip_tunnel.c
│   ├── ipip.c
│   ├── ipset.c
│   ├── ipv4.c
│   ├── ipv4_frag.c
│   ├── ipv6
│   ├── ipvs  # ipvs 业务逻辑实现目录
│   ├── kni.c
│   ├── laddr_multiply.c
│   ├── lb    # lb 转发逻辑实现目录
│   ├── log.c
│   ├── main.c
│   ├── mbuf.c
│   ├── mempool.c
│   ├── neigh.c
│   ├── netif.c
│   ├── netif_addr.c
│   ├── parser.c
│   ├── pidfile.c
│   ├── route.c
│   ├── sa_pool.c
│   ├── sys_time.c
│   ├── tc
│   ├── timer.c
│   └── vlan.c
└── tools  # 工具库目录
    ├── Makefile
    ├── dpip
    ├── ipvsadm
    ├── keepalived
    └── lbdebug

配置解析

  1. global_defs 全局配置模块:指定日志级别,日志路径等。
! global config
global_defs {
    log_level   DEBUG # 方便调试
    ! log_file    /var/log/hdslb.log
    ! log_async_mode    on
}
  1. netif_defs 网卡设备配置模块
    1. pktpool 指定 DPDK memory/cache pool 的大小。
    2. device 指定 DPDK 网卡设备。
    3. tx/rx 指定 DPDK 网卡设备的硬件队列数。
    4. bonding 指定 DPDK 网卡绑定。
    5. kni 指定 DPDK 网卡设备对应的 kni 虚拟网络接口设备。DPDK 程序会将物理网卡设备纳管,流量 bypass 内核,如果其它程序,比如 ssh 想使用网络接口,则需要通过 kni 模块来提供虚拟网络接口,DPDK 程序会将不感兴趣的流量送到内核。
    6. RSS(Receive Side Scaling):指定网卡设备的接受多队列和多核处理器的映射关系,充分利用网卡多队列和多核处理器的技术优势,提高网络吞吐量和数据包处理效率。
    7. FDIR(Flow Director):指定网卡设备的流量识别和分类模式,提高对老鼠流量、大象流量等特定流量的处理效率。
! netif config
netif_defs {
    <init> pktpool_size     1048575
    <init> pktpool_cache    256
    <init> device dpdk0 {
        rx {
            queue_number        3
            descriptor_number   1024
            ! rss                 all
        }
        tx {
            queue_number        3
            descriptor_number   1024
        }
        fdir {
            mode                perfect
            pballoc             64k
            status              matched
        }
        ! promisc_mode
        kni_name                dpdk0.kni
    }
    ! <init> bonding bond0 {
    !    mode        0
    !    slave       dpdk0
    !    slave       dpdk1
    !    primary     dpdk0
    !    kni_name    bond0.kni
    !}
}
  1. worker_defs 工作核心配置:DPDK 将 CPU 抽象为 lcore,有 master、slave 两种类型。通常的,master 做 Control Plane 处理,而 slave 作为 Data Plane 处理。每个 lcore 可以负责多个网卡设备的多个队列。另外,DPDK 将网卡设备抽象为 Port,rx_queue_ids 和 tx_queue_ids 分别是接收和发送的队列编号。其中 isol_rx_cpu_ids 表示当前 lcore 专职负责接收数据,isol_rxq_ring_sz 专职接收数据的 ring buffer 大小。
! worker config (lcores)
worker_defs {
    # control plane CPU
    <init> worker cpu0 {
        type    master
        cpu_id  0
    }
    # data plane CPU
    # dpdk0、1 这 2 个 Port 的同一个收发队列共用同一个 CPU
    <init> worker cpu1 {
        type    slave
        cpu_id  1
        port    dpdk0 {
            rx_queue_ids     0
            tx_queue_ids     0
            ! isol_rx_cpu_ids  9
            ! isol_rxq_ring_sz 1048576
        }
        port    dpdk1 {
            rx_queue_ids     0
            tx_queue_ids     0
            ! isol_rx_cpu_ids  9
            ! isol_rxq_ring_sz 1048576
        }
    }
}
  1. timer_defs 定时器配置
! timer config
timer_defs {
    # cpu job loops to schedule dpdk timer management
    schedule_interval    500
}
  1. neight_defs 邻居子系统配置:Lite IP-Stack 包括 L3 Route 子系统和 L2 Neighbor 子系统。
! hdslb neighbor config
neigh_defs {
    <init> unres_queue_length  128
    <init> timeout             60
}
  1. ipv4/v6_defs 三层网络配置
! hdslb ipv4 config
ipv4_defs {
    forwarding                 off
    <init> default_ttl         64
    fragment {
        <init> bucket_number   4096
        <init> bucket_entries  16
        <init> max_entries     4096
        <init> ttl             1
    }
}

! hdslb ipv6 config
ipv6_defs {
    disable                     off
    forwarding                  off
    route6 {
        <init> method           hlist
        recycle_time            10
    }
}
  1. ctrl_defs 控制面配置:使用 Local unix socket 通信方式。
! control plane config
ctrl_defs {
    lcore_msg {
        <init> ring_size                4096
        sync_msg_timeout_us             30000000
        priority_level                  low
    }
    ipc_msg {
        <init> unix_domain /var/run/hdslb_ctrl
    }
}
  1. ipvs_defs 核心配置
    1. conn 指定用于维护网络 conntrack 连接跟踪表资源的相关配置。
    2. udp/tcp 协议处理配置。
    3. synproxy 是与 TCP SYN flood 相关的配置。
! ipvs config
ipvs_defs {
    conn {
        <init> conn_pool_size       2097152
        <init> conn_pool_cache      256
        conn_init_timeout           30
        ! expire_quiescent_template
        ! fast_xmit_close
        ! <init> redirect           off
    }

    udp {
        ! defence_udp_drop
        uoa_mode        opp
        uoa_max_trail   3
        timeout {
            normal      300
            last        3
        }
    }

    tcp {
        ! defence_tcp_drop
        timeout {
            none        2
            established 90
            syn_sent    3
            syn_recv    30
            fin_wait    7
            time_wait   7
            close       3
            close_wait  7
            last_ack    7
            listen      120
            synack      30
            last        2
        }
        synproxy {
            synack_options {
                mss             1452
                ttl             63
                sack
                ! wscale
                ! timestamp
            }
            ! defer_rs_syn
            rs_syn_max_retry    3
            ack_storm_thresh    10
            max_ack_saved       3
            conn_reuse_state {
                close
                time_wait
                ! fin_wait
                ! close_wait
                ! last_ack
           }
        }
    }
}
  1. FDIR sa_pool 配置
! sa_pool config
sa_pool {
    pool_hash_size   16
}

启动流程分析

  1. 程序入口
high-density-scalable-load-balancer/src/main.c main(int argc, char *argv[])
  1. NUMA 节点数量检查
    if (get_numa_nodes() > DPVS_MAX_SOCKET) {
        fprintf(stderr, "DPVS_MAX_SOCKET is smaller than system numa nodes!\n");
        return -1;
    }
  1. CPU 亲和性设定:NUMA 亲和和 CPU 绑定是 DPDK 程序的一大特性。
    if (set_all_thread_affinity() != 0) {
        fprintf(stderr, "set_all_thread_affinity failed\n");
        exit(EXIT_FAILURE);
    }
  1. 初始化 RTE 运行时环境:完成 DPDK 运行时环境的基础配置,详情请浏览《DPDK — EAL 环境抽象层》
    err = rte_eal_init(argc, argv);
    if (err < 0)
        rte_exit(EXIT_FAILURE, "Invalid EAL parameters\n");
    argc -= err, argv += err;
  1. 进入 HDSLB 核心业务流程
    RTE_LOG(INFO, DPVS, "HDSLB version: %s, build on %s\n", HDSLB_VERSION, HDSLB_BUILD_DATE);
  1. 初始化配置解析器:加载并解析 hdslb.conf 配置文件。
    if ((err = cfgfile_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail init configuration file: %s\n",
                 dpvs_strerror(err));

  1. bond 虚拟接口配置:如果配置文件中没有 bond 则不做处理。
    if ((err = netif_virtual_devices_add()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail add virtual devices:%s\n",
                 dpvs_strerror(err));

  1. 初始化 lcore 定时器:每个 lcore 都有自己的定时器,底层通过调用 timer_lcore_init 完成初始化,用于实现 conn 老化等业务逻辑。
    if ((err = dpvs_timer_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail init timer on %s\n", dpvs_strerror(err));
  1. 初始化 traffic control 流控模块
    if ((err = tc_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail to init traffic control: %s\n",
                 dpvs_strerror(err));
  1. 初始化 DPDK 网卡设备:Data Plane 的核心 jobs 处理函数在这里被注册,netif_init->netif_lcore_init 函数中会注册 3 个 NETIF_LCORE_JOB_LOOP。
    if ((err = netif_init(NULL)) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail to init netif: %s\n", dpvs_strerror(err));
    /* Default lcore conf and port conf are used and may be changed here
     * with "netif_port_conf_update" and "netif_lcore_conf_set" */
  1. 初始化 ctrl 和 tc_ctrl 控制面接口:ctrl_init->msg_init 会注册 1 个 NETIF_LCORE_JOB_LOOP。
    if ((err = ctrl_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail to init ctrl plane: %s\n",
                 dpvs_strerror(err));

    if ((err = tc_ctrl_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail to init tc control plane: %s\n",
                 dpvs_strerror(err));
  1. 初始化 L2 VLAN 网络
    if ((err = vlan_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail to init vlan: %s\n", dpvs_strerror(err));
  1. 初始化 TCPv4 网络:inet_init 注册了一系列的 NETIF_LCORE_JOB_XXX,L4 LB 的核心。
    if ((err = inet_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail to init inet: %s\n", dpvs_strerror(err));
  1. 初始化 FDIR 的 sa_pool
    if ((err = sa_pool_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail to init sa_pool: %s\n", dpvs_strerror(err));
  1. 初始化 Tunnel:如果配置文件中没有启动 IP tunnel 模式则不做处理。
    if ((err = ip_tunnel_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail to init tunnel: %s\n", dpvs_strerror(err));
  1. 初始化原始 lvs 的功能:包括注册处理 IPv4 包的钩子函数 dp_vs_in 和 dp_vs_pre_routing。
    if ((err = dp_vs_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail to init ipvs: %s\n", dpvs_strerror(err));
  1. 初始化 netif_ctrl 控制面接口
    if ((err = netif_ctrl_init()) != EDPVS_OK)
        rte_exit(EXIT_FAILURE, "Fail to init netif_ctrl: %s\n",
                 dpvs_strerror(err));
  1. 启动 DPDK 网络设备:包括 Port、rx/tx queues、cpu binding 等等。
    /* config and start all available dpdk ports */
    nports = rte_eth_dev_count_avail();
    for (pid = 0; pid < nports; pid++) {
        dev = netif_port_get(pid);
        if (!dev) {
            RTE_LOG(WARNING, DPVS, "port %d not found\n", pid);
            continue;
        }

        err = netif_port_start(dev);
        if (err != EDPVS_OK)
            rte_exit(EXIT_FAILURE, "Start %s failed, skipping ...\n",
                    dev->name);

    }
  1. 启动数据面处理:rte_eal_mp_remote_launch 在每个 slave lcore 调用 netif_loop 进入工作线程的永循环。
// src/main.c
    /* start data plane threads */
    netif_lcore_start();

// src/netif.c
int netif_lcore_start(void)
{
    rte_eal_mp_remote_launch(netif_loop, NULL, SKIP_MASTER);
    return EDPVS_OK;
}

// src/netif.c
static int netif_loop(void *dummy)
  1. 启动控制面处理:进入 master 主线程的永循环。
    /* start control plane thread */
    while (1) {
        /* reload configuations if reload flag is set */
        try_reload();
        /* IPC loop */
        sockopt_ctl(NULL);
        /* msg loop */
        msg_master_process(0);
        /* timer */
        now_cycles = rte_get_timer_cycles();
        if ((now_cycles - prev_cycles) * 1000000 / cycles_per_sec > timer_sched_interval_us) {
            rte_timer_manage();
            prev_cycles = now_cycles;
        }
        /* kni */
        kni_process_on_master();

        /* process mac ring on master */
        neigh_process_ring(NULL, 0);

        /* increase loop counts */
        netif_update_master_loop_cnt();
    }

数据面 jobs 注册

前文提到,在 main 的 init 初始化流程中,会注册一系列的数据面的 jobs,并存储在全局变量 netif_lcore_jobs 中。这些 jobs 的本质是一个函数引用,在处理报文时会在不同的环节被调用。

  1. main->netif_init->netif_lcore_init 注册了 3 个 NETIF_LCORE_JOB_LOOP:
    1. lcore_job_recv_fwd
    2. lcore_job_xmit
    3. lcore_job_timer_manage
static void netif_lcore_init(void)
{
......
    /* register lcore jobs*/
    snprintf(netif_jobs[0].name, sizeof(netif_jobs[0].name) - 1, "%s", "recv_fwd");
    netif_jobs[0].func = lcore_job_recv_fwd;
    netif_jobs[0].data = NULL;
    netif_jobs[0].type = NETIF_LCORE_JOB_LOOP;
    
    snprintf(netif_jobs[1].name, sizeof(netif_jobs[1].name) - 1, "%s", "xmit");
    netif_jobs[1].func = lcore_job_xmit;
    netif_jobs[1].data = NULL;
    netif_jobs[1].type = NETIF_LCORE_JOB_LOOP;
    
    snprintf(netif_jobs[2].name, sizeof(netif_jobs[2].name) - 1, "%s", "timer_manage");
    netif_jobs[2].func = lcore_job_timer_manage;
    netif_jobs[2].data = NULL;
    netif_jobs[2].type = NETIF_LCORE_JOB_LOOP;
}
  1. main->ctrl_init->msg_init 注册了 1 个 NETIF_LCORE_JOB_LOOP。
    1. slave_lcore_loop_func
    /* register netif-lcore-loop-job for Slaves */
    snprintf(ctrl_lcore_job.name, sizeof(ctrl_lcore_job.name) - 1, "%s", "slave_ctrl_plane");
    ctrl_lcore_job.func = slave_lcore_loop_func;
    ctrl_lcore_job.data = NULL;
    ctrl_lcore_job.type = NETIF_LCORE_JOB_LOOP;
  1. main->inet_init->ipv4_init->ipv4_frag_init 注册了 1 个 NETIF_LCORE_JOB_SLOW。
    1. ipv4_frag_job
    snprintf(frag_job.name, sizeof(frag_job.name) - 1, "%s", "ipv4_frag");
    frag_job.func = ipv4_frag_job;
    frag_job.data = NULL;
    frag_job.type = NETIF_LCORE_JOB_SLOW;
    frag_job.skip_loops = IP4_FRAG_FREE_DEATH_ROW_INTERVAL;
  1. main->inet_init -> neigh_init -> arp_init 也注册了 NETIF_LCORE_JOB_SLOW。
    1. neigh_process_ring
    /*get static arp entry from master*/
    snprintf(neigh_sync_job.name, sizeof(neigh_sync_job.name) - 1, "%s", "neigh_sync");
    neigh_sync_job.func = neigh_process_ring;
    neigh_sync_job.data = NULL;
    neigh_sync_job.type = NETIF_LCORE_JOB_SLOW;
    neigh_sync_job.skip_loops = NEIGH_PROCESS_MAC_RING_INTERVAL;

实际上,还有其他的 NETIF_LCORE_JOB_XXX 没有列出来,此处先不作展开。这些 jobs 都会在 netif_loop 中被调用,用于完成数据面的收包、处理、转发工作。

数据面 jobs 执行

netif_lcore_jobs 作为数据面的处理逻辑,采用了类似 Kernel netfilter 的 chain 链式处理模式。在 netif_loop 中 jobs 函数的调用顺序为: lcore_job_recv_fwd -> lcore_job_xmit -> lcore_job_timer_manage -> slave_lcore_loop_func -> ipv4_frag_job -> neigh_process_ring。

static int netif_loop(void *dummy)
{
    struct netif_lcore_loop_job *job;
    // 获取当前 lcore id
    lcoreid_t cid = rte_lcore_id();
    enum netif_principal_status stat = NETIF_PRINCIPAL_STAT_IDLE;
    lb_cycle_t deadline;
    int ret;

    assert(LCORE_ID_ANY != cid);

	// lcore 是否配置为了 isol_rx_cpu_ids 专职收包?是则永循环,否则继续。
	// 此处的设计思想是将收包和处理包的 Core 分离,增加网卡的吞吐能力吧。
    try_isol_rxq_lcore_loop();
    if (0 == lcore_conf[lcore2index[cid]].nports) {
    	// 没有 lcore 对应的 port
        RTE_LOG(INFO, NETIF, "[%s] Lcore %d has nothing to do.\n", __func__, cid);
        return EDPVS_IDLE;
    }

	// 首先,立即运行 lcore 中注册的 NETIF_LCORE_JOB_INIT 任务
    list_for_each_entry(job, &netif_lcore_jobs[NETIF_LCORE_JOB_INIT], list) {
        do_lcore_job(job, 0);
    }

    while (1) {
        lcore_stats[cid].lcore_loop++;
        deadline = lcore_timer_hz_cycle() + rte_rdtsc();

		// 运行 lcore 中注册的 NETIF_LCORE_JOB_HIGH 任务       
        do {       
            list_for_each_entry(job, &netif_lcore_jobs[NETIF_LCORE_JOB_HIGH], list) {
                ret = do_lcore_job(job, 0);
                if (ret) {
                    stat = ret;
                }
            }
        } while (stat == NETIF_PRINCIPAL_STAT_FULL && rte_rdtsc() < deadline);

		// 运行 lcore 中注册的 NETIF_LCORE_JOB_LOOP 任务
        list_for_each_entry(job, &netif_lcore_jobs[NETIF_LCORE_JOB_LOOP], list) {
            do_lcore_job(job, stat);
        }
        
		// 每隔一定时间,运行 lcore 中注册的 NETIF_LCORE_JOB_SLOW 任务        
        ++netif_loop_tick[cid];
        list_for_each_entry(job, &netif_lcore_jobs[NETIF_LCORE_JOB_SLOW], list) {
            if (netif_loop_tick[cid] % job->skip_loops == 0) {
                do_lcore_job(job, stat);
                //netif_loop_tick[cid] = 0;
            }
        }

		// 运行 lcore 中注册的 NETIF_LCORE_JOB_IDLE 任务       
        if (is_lcore_idle(cid)) {
            /* TODO NETIF_PRINCIPAL_STAT_IDLE == stat is strict, consider lcore_stats[cid].opackets */
            list_for_each_entry(job, &netif_lcore_jobs[NETIF_LCORE_JOB_IDLE], list) {
                do_lcore_job(job, stat);
            }
            rec_lcore_tx_idle_credit(cid);
        } else {
            inc_lcore_tx_idle_credit(cid);
        }
    }
    return EDPVS_OK;
}

转发流程分析

下面以 lcore_job_recv_fwd job 为例分析数据面的转发处理流程。

收包阶段

lcore_job_recv_fwd 收包。

static int lcore_job_recv_fwd(void *arg __rte_unused, int high_stat __rte_unused)
{
    int i, j;
    portid_t pid;
    lcoreid_t cid;
    uint32_t nic_type;
    struct netif_queue_conf *qconf;
    enum netif_principal_status stat = NETIF_PRINCIPAL_STAT_IDLE;

    cid = rte_lcore_id();
    assert((LCORE_ID_ANY != cid) && cid < DPVS_MAX_LCORE);

    for (i = 0; i < lcore_conf[lcore2index[cid]].nports; i++) {
        pid = lcore_conf[lcore2index[cid]].pqs[i].id;
        nic_type = lcore_conf[lcore2index[cid]].pqs[i].nic_type;
        assert(pid <= bond_pid_end);

        for (j = 0; j < lcore_conf[lcore2index[cid]].pqs[i].nrxq; j++) {
            qconf = &lcore_conf[lcore2index[cid]].pqs[i].rxqs[j];

			// 从 arp_ring 获取 arp 报文
            lcore_process_arp_ring(qconf, cid);
            lcore_process_redirect_ring(qconf, cid);
            qconf->len = netif_rx_burst(pid, qconf, nic_type);

            stat = lcore_update_rx_principal_status(qconf->len, stat);
            lcore_stats_burst(&lcore_stats[cid], qconf->len);

            lcore_process_marked_flow(qconf);
            lcore_stats[cid].impackets += qconf->marked_cnt;
            lb_redirect_ring_proc(qconf, cid);

			// 读取网卡队列数据之后,调用 lcore_process_packets 对数据包进行处理。
            lcore_process_packets(qconf, qconf->mbufs, cid, qconf->len, 0);
            kni_send2kern_loop(pid, qconf);
        }
    }

    return stat;
}

L2 处理阶段

lcore_process_packets 处理 L2 报文。

void lcore_process_packets(struct netif_queue_conf *qconf, struct rte_mbuf **mbufs,
                      lcoreid_t cid, uint16_t count, bool pkts_from_ring)
{
......
    /* L2 filter */
    for (i = 0; i < count; i++) {
        struct rte_mbuf *mbuf = mbufs[i];
        struct netif_port *dev;

        if (t < count) {
            rte_prefetch0(rte_pktmbuf_mtod(mbufs[t], void *));
            t++;
        }

        dev = netif_port_get(mbuf->port);
        if (unlikely(!dev)) {
            rte_pktmbuf_free(mbuf);
            lcore_stats[cid].dropped++;
            continue;
        }
        if (dev->type == PORT_TYPE_BOND_SLAVE) {
            dev = dev->bond->slave.master;
            mbuf->port = dev->id;
        }

        eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
        /* reuse mbuf.packet_type, it was RTE_PTYPE_XXX */
        mbuf->packet_type = eth_type_parse(eth_hdr, dev);

        /*
         * In NETIF_PORT_FLAG_FORWARD2KNI mode.
         * All packets received are deep copied and sent to  KNI
         * for the purpose of capturing forwarding packets.Since the
         * rte_mbuf will be modified in the following procedure,
         * we should use mbuf_copy instead of rte_pktmbuf_clone.
         */
        if (dev->flag & NETIF_PORT_FLAG_FORWARD2KNI) {
            if (likely(NULL != (mbuf_copied = mbuf_copy(mbuf,
                                pktmbuf_pool[dev->socket]))))
                kni_ingress(mbuf_copied, dev, qconf);
            else
                RTE_LOG(WARNING, NETIF, "%s: Failed to copy mbuf\n",
                        __func__);
        }

        /*
         * do not drop pkt to other hosts (ETH_PKT_OTHERHOST)
         * since virtual devices may have different MAC with
         * underlying device.
         */

        /*
         * handle VLAN
         * if HW offload vlan strip, it's still need vlan module
         * to act as VLAN filter.
         */
        if (eth_hdr->ether_type == htons(ETH_P_8021Q) ||
            mbuf->ol_flags & PKT_RX_VLAN_STRIPPED) {

            if (vlan_rcv(mbuf, netif_port_get(mbuf->port)) != EDPVS_OK) {
                rte_pktmbuf_free(mbuf);
                lcore_stats[cid].dropped++;
                continue;
            }

            dev = netif_port_get(mbuf->port);
            if (unlikely(!dev)) {
                rte_pktmbuf_free(mbuf);
                lcore_stats[cid].dropped++;
                continue;
            }

            eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
        }

        if (lb_sync_lcore_is_backup() && lb_sync_process_message(mbuf) == 0) {
            /*mbuf is freed in lb_sync_process_message */
            deliver_mbuf = false;
        } else {
            deliver_mbuf = true;
        }

		// 链路层的过滤处理完之后,调用 netif_deliver_mbuf 进入 IP 层
        if (likely(deliver_mbuf)) {
            /* handler should free mbuf */
            netif_deliver_mbuf(mbuf, eth_hdr->ether_type, dev, qconf,
                (dev->flag & NETIF_PORT_FLAG_FORWARD2KNI) ? true : false,
                cid, pkts_from_ring);
        }

        lcore_stats[cid].ibytes += mbuf->pkt_len;
        lcore_stats[cid].ipackets++;
    }
}

L3 处理阶段

  1. netif_deliver_mbuf 处理 L3 IP 包:
static inline int netif_deliver_mbuf(struct rte_mbuf *mbuf,
                                     uint16_t eth_type,
                                     struct netif_port *dev,
                                     struct netif_queue_conf *qconf,
                                     bool forward2kni,
                                     lcoreid_t cid,
                                     bool pkts_from_ring)
{
......
    /* Remove ether_hdr at the beginning of an mbuf */
    data_off = mbuf->data_off;
    if (unlikely(NULL == rte_pktmbuf_adj(mbuf, sizeof(struct rte_ether_hdr))))
        return EDPVS_INVPKT;

	// IP 层的 pkt_type 只注册了 2 种类型 ip4_pkt_type 和 arp_pkt_type。
	// ip4_pkt_type 在 ipv4_init 中注册。
	// arp_pkt_type 在 arp_init 中注册。
    err = pt->func(mbuf, dev);
......

// 对于 ipv4 包,实际上 pt->func 调用的就是 ipv4_rcv。
static struct pkt_type ip4_pkt_type = {
    //.type       = rte_cpu_to_be_16(ETHER_TYPE_IPv4),
    .func       = ipv4_rcv,
    .port       = NULL,
};

static struct pkt_type arp_pkt_type = {
    //.type       = rte_cpu_to_be_16(ETHER_TYPE_ARP),
    .func       = neigh_resolve_input,
    .port       = NULL,
};
  1. ipv4_rcv
static int ipv4_rcv(struct rte_mbuf *mbuf, struct netif_port *port)
{
......
	// ipv4_rcv 完成一系列错误检查后调用了 INET_HOOK 函数
    return INET_HOOK(INET_HOOK_PRE_ROUTING, mbuf, port, NULL, ipv4_rcv_fin);
  1. INET_HOOK
int INET_HOOK(unsigned int hook, struct rte_mbuf *mbuf,
        struct netif_port *in, struct netif_port *out,
        int (*okfn)(struct rte_mbuf *mbuf))
{
......
	// inet_hooks 在 dp_vs_init 中注册
    state.hook = hook;
    hook_list = &inet_hooks[hook];
    
    ops = list_entry(hook_list, struct inet_hook_ops, list);

    if (!list_empty(hook_list)) {
        verdict = INET_ACCEPT;
        list_for_each_entry_continue(ops, hook_list, list) {
repeat:
			// 先后执行 dp_vs_in 和 dp_vs_pre_routin
            verdict = ops->hook(ops->priv, mbuf, &state);/*g*/
            if (verdict != INET_ACCEPT) {
                if (verdict == INET_REPEAT)
                    goto repeat;
                break;
            }
        }
    }
}

L4 处理阶段

dp_vs_in 的主体逻辑是判断 IP 包的 src/dst 是否存在 conn,若存在则直接转发;否则 prot->conn_sched 创建一个新的 conn 然后转发。

static int dp_vs_in(void *priv, struct rte_mbuf *mbuf, 
                    const struct inet_hook_state *state)
{
......
    /* packet belongs to existing connection ? */
    conn = prot->conn_lookup(prot, &iph, mbuf, &dir, false);

	// 如果是 tcp 协议,则会调用到 conn_sched->tcp_conn_sched
    if (unlikely(!conn)) {
        /* try schedule RS and create new connection */
        if (prot->conn_sched(prot, &iph, mbuf, &conn, &verdict) != EDPVS_OK) {
            /* RTE_LOG(DEBUG, IPVS, "%s: fail to schedule.\n", __func__); */
            return verdict;
        }

        /* only SNAT triggers connection by inside-outside traffic. */
        if (conn->dest->fwdmode == DPVS_FWD_MODE_SNAT)
            dir = DPVS_CONN_DIR_OUTBOUND;
        else
            dir = DPVS_CONN_DIR_INBOUND;
    }

......
	// xmit_inbound 将包转发给 RS
	// xmit_outbound 从 RS 回包
    /* holding the conn, need a "put" later. */
    if (dir == DPVS_CONN_DIR_INBOUND)
        return xmit_inbound(mbuf, prot, conn);
    else
        return xmit_outbound(mbuf, prot, conn);
}

高级特性

大象流转发优化

现代 DPDK 程序都会基于 RSS 收包多核扩展技术来将不同的 IP 5-tuple Traffic 映射到特定的 Core 上进行处理。

但当出现大象流时,10% 的大象流就占据了总流量的 90%,继而造成某些 Core 忙死,而另外一些 Core 则闲死的情况。更甚者,即便忙死了某个 Core 也依旧无法满足大象流的处理需求,而导致丢包。

在这里插入图片描述

为了解决大象流问题,HDSLB 基于以下思路进行了 3 方面的优化:

  1. 大象流识别:首先,要识别出大象流(Heavy)和老鼠流(Light)。
  2. 大象流拆分:然后,将大象流能够拆分并映射到多个 Cores 上并行处理,而不仅仅映射到一个 Core 上。
  3. 大象流重排:最终,还需要将拆分到多个 Cores 上并行处理的流量再进行合法性排序。

在这里插入图片描述

从上述原理图可以看出,这里面的关键技术是由 Intel CPU 硬件提供的 DLB(Dynamic Load Balancer)特性。基于 DLB 可以实现:

  1. 收包时:将大象流切分到多个 Cores 中进行处理。
  2. 发包时:将多个 Cores 上的流量进行汇聚并合法化排序。

在这里插入图片描述

更具体而言,HDSLB 的大象流处理方案需要在 Main Core 上实现一个基于 Intel NIC FDIR 硬件特性的 Switch Filter,用于完成大象流和老鼠流的识别、标记并分类映射到不同的 Core,通过硬件的方式减少了软件上的匹配和查表,性能更高;而在 Worker Cores 上还需要实现基于 Intel CPU DLB 硬件特性的大象流拆分。如下图所示:

  • Main Core
    在这里插入图片描述

  • Worker Cores
    在这里插入图片描述

了解更多的细节,推荐阅读 Intel 的官方文档:https://networkbuilders.intel.com/docs/networkbuilders/intel-dynamic-load-balancer-intel-dlb-accelerating-elephant-flow-technology-guide-1677672283.pdf

快慢路径分离转发优化

快慢路径分离现在已然是高性能转发模式的标配了,HDSLB 为性能敏感且处理逻辑复杂的 TCP 流量实现了一套 Session/Conn 快路径。

在这里插入图片描述

报文基础转发优化

在基础的报文转发方面,HDSLB 做了 2 方面的努力:

  1. Vectorize:向量转发的思路来自于 VPP,批量处理同类报文可以有效提高 icache/dcache 的命中率。详细推荐浏览:《FD.io/VPP — VPP 的实现原理解析》
    在这里插入图片描述

  2. microjobs:将原来的 jobs 进一步的细化为了多个符合 icache size 对齐的 microjobs。结合 pipeline nodes prefetch 的方式,可以进一步减少 icache/dcache miss 带来的性能损耗。
    在这里插入图片描述

最后

通过本系列的文章,笔者希望向对 DPDK 数据面开发感兴趣的读者们推荐 HDSLB 这个优秀的开源项目。实际上,DVPS 本身就已经是一个足够优秀的数据面项目,HDSLB 更是在其基础上叠加了 Intel 多年积累的软硬件融合加速技术。难能可贵的是,HDSLB 不仅仅满足于作为一个研究项目,而是针对大象流此类在生产环境中存在的典型问题,给出了一个完整可落地的解决方案。这一点非常值得大多数开源项目学习!

参考文档

  1. https://cloud.tencent.com/developer/article/1180256
  2. https://cloud.tencent.com/developer/article/1180838
  3. https://cloud.tencent.com/developer/article/1182928
  4. https://www.jianshu.com/p/d8ee301f9122
  5. https://static.sched.com/hosted_files/dpdksummitapac2021/35/Handling%20Elephant%20Flow%20on%20a%20DPDK-Based%20Load%20Balancer.pdf
  6. https://zhuanlan.zhihu.com/p/416992198

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/713921.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【云原生】Kubernetes----Kubernetes集群部署Prometheus 和Grafana

目录 引言 一、环境准备 二、部署node-exporter &#xff08;一&#xff09;创建命名空间 &#xff08;二&#xff09;部署node-exporter 1.获取镜像 2.定义yaml文件 3.创建服务 4.查看监控数据 三、部署Prometheus &#xff08;一&#xff09;创建账号并授权 &…

Java学习笔记之基本数据类型转换

前言 本篇文章是基于我本人在初学JAVA阶段想记录的的学习笔记&#xff0c;如有错误&#xff0c;恳请指正。今天要干掉的是JAVA的基本数据类型转换 Java的基本数据类型转换 前言一&#xff0c;基本数据类型复习二&#xff0c;基本介绍什么是自动类型转换&#xff1f; 三&#…

【Numpy】一文向您详细介绍 np.round()

【Numpy】一文向您详细介绍 np.round() 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通本硕&#xff0c;…

从0到1搭建MCU芯片上操作系统环境。开发都需要哪些环节和准备

MCU芯片环境搭建与操作系统上载步骤 1. 硬件准备 选择合适的MCU芯片&#xff0c;例如STM32、GD32等。 准备开发板&#xff0c;用于硬件连接和实验。 准备必要的外围设备&#xff0c;如电源适配器、USB转串口模块等。 2. 软件环境搭建 安装编程语言环境&#xff0c;如C/C编译…

NVIDIA Triton系列02-功能与架构简介

NVIDIA Triton系列02-功能与架构简介 B站&#xff1a;肆十二-的个人空间-肆十二-个人主页-哔哩哔哩视频 (bilibili.com) 博客&#xff1a;肆十二-CSDN博客 问答&#xff1a;(10 封私信 / 72 条消息) 肆十二 - 知乎 (zhihu.com) 前面文章介绍微软 Teams 会议系统、微信软件与腾讯…

微信视频号视频怎么下载才能保存视频到手机相册,推荐一款稳定的视频号下载工具

视频号视频下载发现写了很多次&#xff0c;竟然还有很多人不知道微信视频号视频怎么下载&#xff0c;今天就来说说这款视频号下载工具。 视频号下载工具介绍 这款视频号下载工具叫视频号下载plus&#xff0c;也有很多人称之为视频下载小助手不知道的可以自行百度。 注意在百度…

码住!详解时序数据库不同分类与性能对比

加速发展中的时序数据库&#xff0c;基于不同架构&#xff0c;最流行的类别是&#xff1f; 作为管理工业场景时序数据的新兴数据库品类&#xff0c;时序数据库凭借着对海量时序数据的高效存储、高可扩展性、时序分析计算等特性&#xff0c;一跃成为物联网时代工业领域颇受欢迎的…

SolarLab - hackthebox

简介 靶机名称&#xff1a;SolarLab 难度&#xff1a;中等 靶场地址&#xff1a;https://app.hackthebox.com/machines/SolarLab 本地环境 靶机IP &#xff1a;10.10.11.16 ubuntu渗透机IP(ubuntu 22.04)&#xff1a;10.10.16.17 windows渗透机IP&#xff08;windows11&…

RawChat:优化AI对话体验,全面兼容GPT功能平台

文章目录 一、Rawchat简介1.1 RawChat的主要特性1.2 RawChat的技术原理简述 二、使用教程三、案例应用3.1 图片内容分析3.2 生图演示3.3 文档解析3.4 探索更多 四、小结 一、Rawchat简介 RawChat平台的诞生&#xff0c;其核心理念是降低用户访问类似ChatGPT这类先进AI服务的门…

FPGA - 数 - 加减乘除

一&#xff0c;数的表示 首先&#xff0c;将二进制做如下解释&#xff1a; 2的0次方1 2的1次方2 2的2次方4 2的3次方8 ..... 以此类推&#xff0c;那么任何整数&#xff0c;或者说任意一个自然数均可以采用这种方式来表示。 例如&#xff0c;序列10101001&#xff0c;根据上述…

ThinkPHP邮件发送配置教程?怎么配置群发?

ThinkPHP邮件发送安全性如何保障&#xff1f;ThinkPHP如何实现&#xff1f; 无论是用户注册后的验证邮件&#xff0c;还是订单处理的通知邮件&#xff0c;都需要一个可靠的邮件发送机制。AokSend将详细介绍如何在ThinkPHP框架中配置邮件发送功能&#xff0c;并带您逐步了解其中…

JavaScript常见面试题(一)

文章目录 1. JavaScript有哪些数据类型&#xff0c;它们的区别&#xff1f;2.数据类型检测的方式有哪些3. 判断数组的方式有哪些4.null和undefined区别5.typeof null 的结果是什么&#xff0c;为什么&#xff1f;6.intanceof 操作符的实现原理及实现7.为什么0.10.2 ! 0.3&…

【Go语言】Gin 框架教程

Gin 框架教程 1.第一个 Gin 程序 1.1 Gin 安装 # 执行执行如下操作即可&#xff0c;安装Gin前需要安装Go环境 go get -u -v github.com/gin-gonic/gin # -v&#xff1a;打印出被构建的代码包的名字 # -u&#xff1a;已存在相关的代码包&#xff0c;强行更新代码包及其依赖包…

转让中字头控股集团公司步骤和条件

随着中国经济的不断发展&#xff0c;越来越多的企业开始积极寻求并购和收购机会。其中&#xff0c;国家总局中字头控股集团公司也是一个备受关注的对象。本篇文章将为您详细介绍国家总局中字头控股集团公司的收购流程及要求。详情致电咨询我或者来公司面谈。 中字头公司转让需满…

[DDR4] DDR4 相对 DDR3差异与优势

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 传送门: 总目录 1 差异总览 出处&#xff1a; https://www.kingston.com.cn/en/memory/ddr4-overview Description 描述DDR3DDR4Advantage 优势电压1.5V1.2V降低内存功耗需求容量512Mb-8Gb4Gb-16Gb更大的 DIMM 容…

AI 定位!GeoSpyAI上传一张图片分析具体位置 不可思议! ! !

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;常见AI大模型部署&#xff1a;点击&#xff01; &#x1f916;Ollama部署LLM专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月16日12点23分 &#x1f004;️文章质量&#xff1a;94分…

快速UDP网络连接之QUIC协议介绍

文章目录 一、QUIC协议历史1.1 问题&#xff1a;QUIC为什么在应用层实现1.2 QUIC协议相关术语1.3 QUIC和TCP对比1.4 QUIC报文格式1.4.1 QUIC报文格式-Stream帧11.4.2 QUIC报文格式-Stream帧2 二、QUIC的特点2.1 连接建立低时延&#xff0c;2.2 多路复用流复用-HTTP1.1流复用-HT…

【OS基础】符合AUTOSAR标准的RTAOS-Alarms详解

目录 前言 正文 7.报警Alarms 7.1配置Alarms 7.1.1激活一个任务 7.1.2 设置一个事件 7.1.3报警回调Alarm Callback 7.1.4 增加计数器值 7.2设置Alarms 7.2.1 绝对Alarms 7.2.2 相对Alarm 7.3自启动Alarms 7.4 删除Alarms 7.5确认何时会发生Alarm 7.6非周期Alarm…

EMQX集群搭建

1. 什么是 MQTT&#xff1f; MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级、基于发布-订阅模式的消息传输协议&#xff0c;适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎&#xff0c;能够实现传感器、…

DETR实现目标检测(一)-训练自己的数据集

1、DETR架构 DETR&#xff08;Detection Transformer&#xff09;是一种新型的目标检测模型&#xff0c;由Facebook AI Research (FAIR) 在2020年提出。DETR的核心思想是将目标检测任务视为一个直接的集合预测问题&#xff0c;而不是传统的两步或多步预测问题。这种方法的创新…