x**3な人生

基本的にはメモ

pktgenソースコード・リーディングのメモ (2)

はじめに

主にpktgenのポートの管理に興味があったので調べてみた。バージョンはpktgen-3.4.9。

ポートの設定はmain()から呼び出されるpktgen_config_ports()で実行され、この関数はapp/pktgen-port-cfg.cに定義されている。

変数とか構造体とか

void
pktgen_config_ports(void)
{
        uint32_t lid, pid, i, s, q, sid;
        rxtx_t rt;
        pkt_seq_t   *pkt;
        port_info_t     *info;
        char buff[RTE_MEMZONE_NAMESIZE];
        int32_t ret, cache_size;
        char output_buff[256] = { 0 };
        uint64_t ticks;

最初のuint32_t型の変数はそれぞれ

  • lid: lcore ID
  • pid: ポートID
  • sid: ソケットID

rxtx_tはRXとTXのそれぞれのポートIDをまとめて保持するためのunion。

/* lib/common/l2p.h */

typedef union {
        struct {
                uint16_t rx;
                uint16_t tx;
        };
        uint32_t rxtx;
} rxtx_t;

またpkt_seq_tはパケットの種別などの情報を保持する構造体。 送受信先それぞれのEthernetアドレスやIPアドレス、ポートの他、VLAN IDやToS値、CoS値など各種情報が含まれる。 app/pktgen-seq.hに定義されている。

/* app/pktgen-seq.h */

typedef struct pkt_seq_s {
        /* Packet type and information */
        struct ether_addr eth_dst_addr; /**< Destination Ethernet address */
        struct ether_addr eth_src_addr; /**< Source Ethernet address */

        struct cmdline_ipaddr ip_src_addr;      /**< Source IPv4 address also used for IPv6 */
        struct cmdline_ipaddr ip_dst_addr;      /**< Destination IPv4 address */
        uint32_t ip_mask;                       /**< IPv4 Netmask value */

        uint16_t sport;         /**< Source port value */
        uint16_t dport;         /**< Destination port value */
        uint16_t ethType;       /**< IPv4 or IPv6 */
        uint16_t ipProto;       /**< TCP or UDP or ICMP */
        uint16_t vlanid;        /**< VLAN ID value if used */
        uint8_t cos;            /**< 802.1p cos value if used */
        uint8_t tos;            /**< tos value if used */
        uint16_t ether_hdr_size;/**< Size of Ethernet header in packet for VLAN ID */

        uint32_t mpls_entry;    /**< MPLS entry if used */
        uint16_t qinq_outerid;  /**< Outer VLAN ID if Q-in-Q */
        uint16_t qinq_innerid;  /**< Inner VLAN ID if Q-in-Q */
        uint32_t gre_key;       /**< GRE key if used */

        uint16_t pktSize;       /**< Size of packet in bytes not counting FCS */
        uint16_t pad0;
        uint32_t gtpu_teid;     /**< GTP-U TEID, if UDP dport=2152 */
        uint8_t seq_enabled;    /**< Enable or disable this sequence through GUI */

        pkt_hdr_t hdr __rte_cache_aligned;      /**< Packet header data */
        uint8_t pad[MBUF_SIZE - sizeof(pkt_hdr_t)];
} pkt_seq_t __rte_cache_aligned;

port_info_tはポートの情報を保持する。主なものは以下の通り。

  • pid: ポートID
  • tx_pps: 毎秒あたりの送信量
  • vlanid, tos, cos: vlan関連の情報
  • max_latency, jitter_threshold: 遅延とかジッターとか
  • q[NUM_Q]: キュー情報
  • pcap: PCAPの情報
/* app/pktgen-port-cfg.h */

typedef struct port_info_s {
    uint16_t pid;      /**< Port ID value */
    uint16_t tx_burst; /**< Number of TX burst packets */
    double tx_rate;        /**< Percentage rate for tx packets with fractions */
    rte_atomic32_t port_flags;  /**< Special send flags for ARP and other */

    rte_atomic64_t transmit_count;  /**< Packets to transmit loaded into current_tx_count */
    rte_atomic64_t current_tx_count;/**< Current number of packets to send */
    uint64_t tx_cycles;    /**< Number cycles between TX bursts */
    uint64_t tx_pps;   /**< Transmit packets per seconds */
    uint64_t delta;        /**< Delta value for latency testing */
    uint64_t tx_count; /**< Total count of tx attempts */

    /* Packet buffer space for traffic generator, shared for all packets per port */
    uint16_t seqIdx;       /**< Current Packet sequence index 0 to NUM_SEQ_PKTS */
    uint16_t seqCnt;       /**< Current packet sequence max count */
    uint16_t prime_cnt;        /**< Set the number of packets to send in a prime command */
    uint16_t vlanid;       /**< Set the port VLAN ID value */
    uint8_t cos;           /**< Set the port 802.1p cos value */
    uint8_t tos;           /**< Set the port tos value */
    rte_spinlock_t port_lock;   /**< Used to sync up packet constructor between cores */
    pkt_seq_t *seq_pkt;     /**< Sequence of packets seq_pkt[NUM_SEQ_PKTS]=default packet */
    range_info_t range;     /**< Range Information */

    uint32_t mpls_entry;   /**< Set the port MPLS entry */
    uint32_t gre_key;  /**< GRE key if used */

    uint16_t nb_mbufs; /**< Number of mbufs in the system */
    uint16_t pad1;
    uint64_t max_latency;  /**< TX Latency sequence */
    uint64_t avg_latency;  /**< Latency delta in clock ticks */
    uint64_t min_latency;  /**< RX Latency sequence */
    uint32_t magic_errors;
    uint32_t latency_nb_pkts;
    uint64_t jitter_threshold;
    uint64_t jitter_threshold_clks;
    uint64_t jitter_count;
    uint64_t prev_latency;

    pkt_stats_t stats;  /**< Statistics for a number of stats */
    port_sizes_t sizes; /**< Stats for the different packets sizes */

    eth_stats_t init_stats; /**< Initial packet statistics */
    eth_stats_t prev_stats; /**< current port statistics */
    eth_stats_t rate_stats; /**< current packet rate statistics */
    uint64_t max_ipackets; /**< Max seen input packet rate */
    uint64_t max_opackets; /**< Max seen output packet rate */
    uint64_t max_missed;   /**< Max missed packets seen */

    struct rte_eth_link link;  /**< Link Information like speed and duplex */

    struct q_info {
        rte_atomic32_t flags;       /**< Special send flags for ARP and other */
        struct mbuf_table tx_mbufs;    /**< mbuf holder for transmit packets */
        struct rte_mempool *rx_mp; /**< Pool pointer for port RX mbufs */
        struct rte_mempool *tx_mp; /**< Pool pointer for default TX mbufs */
        struct rte_mempool *range_mp;  /**< Pool pointer for port Range TX mbufs */
        struct rte_mempool *seq_mp;    /**< Pool pointer for port Sequence TX mbufs */
        struct rte_mempool *pcap_mp;   /**< Pool pointer for port PCAP TX mbufs */
        struct rte_mempool *special_mp;    /**< Pool pointer for special TX mbufs */
        uint64_t tx_cnt, rx_cnt;
    } q[NUM_Q];

    int32_t rx_tapfd;      /**< Rx Tap file descriptor */
    int32_t tx_tapfd;      /**< Tx Tap file descriptor */
    pcap_info_t           *pcap;    /**< PCAP information header */
    uint64_t pcap_cycles;      /**< number of cycles for pcap sending */

    int32_t pcap_result;   /**< PCAP result of filter compile */
    struct bpf_program pcap_program;/**< PCAP filter program structure */

    /* Packet dump related */
    struct packet {
        void *data;    /**< Packet data */
        uint32_t len;  /**< Length of data */
    } dump_list[MAX_DUMP_PACKETS];
    uint8_t dump_head; /**< Index of last packet written to screen */
    uint8_t dump_tail; /**< Index of last valid packet in dump_list */
    uint8_t dump_count;    /**< Number of packets the user requested */

    struct rnd_bits_s     *rnd_bitfields;  /**< Random bitfield settings */

    struct rte_eth_conf port_conf;     /**< port configuration information */
    struct rte_eth_dev_info dev_info;  /**< PCI info + driver name */
    struct rte_eth_rxconf rx_conf;     /**< RX configuration */
    struct rte_eth_txconf tx_conf;     /**< TX configuration */
    ring_conf_t ring_conf;          /**< Misc ring configuration information */
    char user_pattern[USER_PATTERN_SIZE];  /**< User set pattern values */
    fill_t fill_pattern_type;       /**< Type of pattern to fill with */
} port_info_t;

ポートの初期化

システムのすべてのポートを検出する。

        /* Find out the total number of ports in the system. */
        /* We have already blacklisted the ones we needed to in main routine. */
        pktgen.nb_ports = rte_eth_dev_count();
        if (pktgen.nb_ports > RTE_MAX_ETHPORTS)
                pktgen.nb_ports = RTE_MAX_ETHPORTS;

        if (pktgen.nb_ports == 0)
                pktgen_log_panic("*** Did not find any ports to use ***");

1度に表示されるポートの数を設定する。pktgen.starting_portは画面に表示される最初のポートの番号。 pg_port_matrix_dump()はlcoreとポートの情報を画面に表示する。

        pktgen.starting_port = 0;

        /* Setup the number of ports to display at a time */
        if (pktgen.nb_ports > pktgen.nb_ports_per_page)
                pktgen.ending_port = pktgen.starting_port +
                        pktgen.nb_ports_per_page;
        else
                pktgen.ending_port = pktgen.starting_port + pktgen.nb_ports;

        pg_port_matrix_dump(pktgen.l2p);

lcoreにポートを割り当てていく。 get_map()によりそのlcoreに割り当てられているポートかどうかを確認し、 pg_set_port_private()によりそのポートを割り当てる。

        /* For each lcore setup each port that is handled by that lcore. */
        for (lid = 0; lid < RTE_MAX_LCORE; lid++) {
                if (get_map(pktgen.l2p, RTE_MAX_ETHPORTS, lid) == 0)
                        continue;

                /* For each port attached or handled by the lcore */
                for (pid = 0; pid < pktgen.nb_ports; pid++) {
                        /* If non-zero then this port is handled by this lcore. */
                        if (get_map(pktgen.l2p, pid, lid) == 0)
                                continue;
                        pg_set_port_private(pktgen.l2p, pid, &pktgen.info[pid]);
                        pktgen.info[pid].pid = pid;
                }
        }
        pg_dump_l2p(pktgen.l2p);

pg_set_port_private()は与えられたpktgen.l2pのポートに対して、 プライベートポインタとしてpktgen.info[pid]を割り与える。

/* lib/common/l2p.h */

static __inline__ void
pg_set_port_private(l2p_t *l2p, uint16_t pid, void *ptr)
{
        pobj_t    *pobj = &l2p->ports[pid];

        pobj->private = ptr;
}

ポートの情報を保持するpobj_tは次のように定義されており、privateをメンバーとして持つ。

/* lib/common/l2p.h */

typedef struct {
        uint16_t pid;
        uint16_t rx_qid;
        uint16_t tx_qid;
        uint16_t nb_lids;
        uint16_t lids[RTE_MAX_LCORE];
        void      *private;
} pobj_t;       /* ports pointer lcores */

ポートの設定

プライベートポインタの取得

get_map()によりlcoreが割り当てられているかどうか確認し、 先ほど設定したプライベートポインタを取得する。

        for (pid = 0; pid < pktgen.nb_ports; pid++) {
                /* Skip if we do not have any lcores attached to a port. */
                if ( (rt.rxtx = get_map(pktgen.l2p, pid, RTE_MAX_LCORE)) == 0)
                        continue;

                pktgen.port_cnt++;
                ...

                info = get_port_private(pktgen.l2p, pid);

get_port_private()の定義は以下の通り。

/* lib/common/l2p.h */

 static __inline__ void *
 get_port_private(l2p_t *l2p, uint16_t pid)
 {
         pobj_t    *pobj = &l2p->ports[pid];
 
         return pobj->private;
 }

スピンロックやパケットバッファの初期化

info変数の持つポートのロックやパケットを初期化する。 info->seq_pktは一連のパケットを転送するために参照されるパケットヘッダの構造体。 rte_zmalloc_socket()はNUMAソケットを指定して0で初期化した領域をmemzoneに確保するための関数。

                rte_spinlock_init(&info->port_lock);

                /* Create the pkt header structures for transmitting sequence of packets. */
                snprintf(buff, sizeof(buff), "seq_hdr_%d", pid);
                info->seq_pkt = rte_zmalloc_socket(buff,
                                                   (sizeof(pkt_seq_t) * NUM_TOTAL_PKTS),
                                                   RTE_CACHE_LINE_SIZE, rte_socket_id());

                 for (i = 0; i < NUM_TOTAL_PKTS; i++)
                         info->seq_pkt[i].seq_enabled = 1;

infoのその他のメンバーの初期化も行うが省略。

ポートの設定

pktgen_port_conf_setup()によりpidで指定したポートの RXやTX、ringのセットアップのほかハードウェアに関するパラメータなどの各種設定を行う。 デフォルト値の設定はdefualt_port_confを用いる。 そのあとrte_eth_dev_configure()を呼び出してRX、TXデバイスのセットアップを行う。

                pktgen_port_conf_setup(pid, &rt, &default_port_conf);

                if ( (ret = rte_eth_dev_configure(pid, rt.rx, rt.tx, &info->port_conf)) < 0)
                        pktgen_log_panic(
                                "Cannot configure device: port=%d, Num queues %d,%d (%d)%s",
                                pid, rt.rx, rt.tx, errno, rte_strerror(-ret));

default_port_confはポートの各種設定を保持するための構造体。

const struct rte_eth_conf default_port_conf = {
        .rxmode = {
                .split_hdr_size = 0,
                .header_split   = 0,    /**< Header Split disabled. */
                .hw_ip_checksum = 0,    /**< IP checksum offload disabled. */
                .hw_vlan_filter = 0,    /**< VLAN filtering enabled. */
                .hw_vlan_strip  = 0,    /**< VLAN strip enabled. */
                .hw_vlan_extend = 0,    /**< Extended VLAN disabled. */
                .jumbo_frame    = 0,    /**< Jumbo Frame Support disabled. */
                .hw_strip_crc   = 0,    /**< CRC stripping by hardware disabled. */
        },
        .rx_adv_conf = {
                .rss_conf = {
                        .rss_key = NULL,
                        .rss_key_len = 0,
                        .rss_hf = ETH_RSS_IP,
                },
        },
        .txmode = {
                .mq_mode = ETH_MQ_TX_NONE,
        },
};

ケットシーケンスの設定

                pkt = &info->seq_pkt[SINGLE_PKT];

                pktgen.mem_used = 0;

RXの設定

ポートの情報からソケットIDを取得し、RXのバッファを作成する。 バッファはinfo->q[q].rx_mpに代入される。

                for (q = 0; q < rt.rx; q++) {
                        /* grab the socket id value based on the lcore being used. */
                        sid = rte_lcore_to_socket_id(get_port_lid(pktgen.l2p, pid, q));

                        /* Create and initialize the default Receive buffers. */
                        info->q[q].rx_mp = pktgen_mbuf_pool_create("Default RX", pid, q,
                                                                   info->nb_mbufs, sid, cache_size);
                        if (info->q[q].rx_mp == NULL)
                                pktgen_log_panic("Cannot init port %d for Default RX mbufs", pid);

取得したmbufをrte_eth_rx_queue_setup()に与えてRXキューのセットアップを行う。

                        ret = rte_eth_rx_queue_setup(pid, q, pktgen.nb_rxd, sid,
                                                     &info->rx_conf, pktgen.info[pid].q[q].rx_mp);
                        if (ret < 0)
                                pktgen_log_panic("rte_eth_rx_queue_setup: err=%d, port=%d, %s",
                                                 ret, pid, rte_strerror(-ret));

最後にrte_eth_dev_set_rx_queue_stats_mapping()によりRXキューを統計情報の対象に追加する。

                        lid = get_port_lid(pktgen.l2p, pid, q);
                        pktgen_log_info("      Set RX queue stats mapping pid %d, q %d, lcore %d\n", pid, q, lid);
                        rte_eth_dev_set_rx_queue_stats_mapping(pid, q, lid);
                }
                pktgen_log_info("");

TXの設定

ポートの情報からソケットIDを取得し、4つのTXのバッファを作成する。

  • default TX
  • range TX
  • sequence TX
  • special TX
                for (q = 0; q < rt.tx; q++) {
                        /* grab the socket id value based on the lcore being used. */
                        sid = rte_lcore_to_socket_id(get_port_lid(pktgen.l2p, pid, q));

                        /* Create and initialize the default Transmit buffers. */
                        info->q[q].tx_mp = pktgen_mbuf_pool_create("Default TX", pid, q,
                                                                   MAX_MBUFS_PER_PORT, sid, cache_size);
                        if (info->q[q].tx_mp == NULL)
                                pktgen_log_panic("Cannot init port %d for Default TX mbufs", pid);

                        /* Create and initialize the range Transmit buffers. */
                        info->q[q].range_mp = pktgen_mbuf_pool_create("Range TX", pid, q,
                                                                      MAX_MBUFS_PER_PORT, sid, 0);
                        if (info->q[q].range_mp == NULL)
                                pktgen_log_panic("Cannot init port %d for Range TX mbufs", pid);

                        /* Create and initialize the sequence Transmit buffers. */
                        info->q[q].seq_mp = pktgen_mbuf_pool_create("Sequence TX", pid, q,
                                                                    MAX_MBUFS_PER_PORT, sid, cache_size);
                        if (info->q[q].seq_mp == NULL)
                                pktgen_log_panic("Cannot init port %d for Sequence TX mbufs", pid);

                        /* Used for sending special packets like ARP requests */
                        info->q[q].special_mp = pktgen_mbuf_pool_create("Special TX", pid, q,
                                                                        MAX_SPECIAL_MBUFS, sid, 0);
                        if (info->q[q].special_mp == NULL)
                                pktgen_log_panic("Cannot init port %d for Special TX mbufs", pid);

あとpcapの設定も行う。しかしここではpcap_mp(pcap TX mbuf)に関する処理はしておらず、最後に行う。

                        /* Setup the PCAP file for each port */
                        if (pktgen.info[pid].pcap != NULL)
                                if (pktgen_pcap_parse(pktgen.info[pid].pcap, info, q) == -1)
                                        pktgen_log_panic("Cannot load PCAP file for port %d", pid);
                        /* Find out the link speed to program the WTHRESH value correctly. */
                        pktgen_get_link_status(info, pid, 0);

取得したmbufをrte_eth_tx_queue_setup()に与えてTXキューのセットアップを行う。

                        ret = rte_eth_tx_queue_setup(pid, q, pktgen.nb_txd, sid, &info->tx_conf);
                        if (ret < 0)
                                pktgen_log_panic("rte_eth_tx_queue_setup: err=%d, port=%d, %s",
                                                 ret, pid, rte_strerror(-ret));
                        pktgen_log_info("");
                }
                pktgen_log_info("%*sPort memory used = %6lu KB", 71, " ",
                                (pktgen.mem_used + 1023) / 1024);

source mac addressの設定などを行ってポートの初期化は終了。

                /* Grab the source MAC addresses */
                rte_eth_macaddr_get(pid, &pkt->eth_src_addr);
                pktgen_log_info("%s,  Src MAC %02x:%02x:%02x:%02x:%02x:%02x",
                                output_buff,
                                pkt->eth_src_addr.addr_bytes[0],
                                pkt->eth_src_addr.addr_bytes[1],
                                pkt->eth_src_addr.addr_bytes[2],
                                pkt->eth_src_addr.addr_bytes[3],
                                pkt->eth_src_addr.addr_bytes[4],
                                pkt->eth_src_addr.addr_bytes[5]);

                /* Copy the first Src MAC address in SINGLE_PKT to the rest of the sequence packets. */
                for (i = 0; i < NUM_SEQ_PKTS; i++)
                        ethAddrCopy(&info->seq_pkt[i].eth_src_addr, &pkt->eth_src_addr);

バイスの処理を開始

        /* Start up the ports and display the port Link status */
        for (pid = 0; pid < pktgen.nb_ports; pid++) {
                if (get_map(pktgen.l2p, pid, RTE_MAX_LCORE) == 0)
                        continue;

                /* Start device */
                if ( (ret = rte_eth_dev_start(pid)) < 0)
                        pktgen_log_panic("rte_eth_dev_start: port=%d, %s",
                                         pid, rte_strerror(-ret));
                rte_delay_us(250000);
        }

ポートのスタートアップ

        /* Start up the ports and display the port Link status */
        for (pid = 0; pid < pktgen.nb_ports; pid++) {
                if (get_map(pktgen.l2p, pid, RTE_MAX_LCORE) == 0)
                        continue;

                info = get_port_private(pktgen.l2p, pid);

                pktgen_get_link_status(info, pid, 1);

                if (info->link.link_status)
                        snprintf(output_buff, sizeof(output_buff),
                                 "Port %2d: Link Up - speed %u Mbps - %s",
                                 pid, (uint32_t)info->link.link_speed,
                                 (info->link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
                                 ("full-duplex") : ("half-duplex"));
                else
                        snprintf(output_buff, sizeof(output_buff), "Port %2d: Link Down", pid);

                /* If enabled, put device in promiscuous mode. */
                if (pktgen.flags & PROMISCUOUS_ON_FLAG) {
                        strncatf(output_buff, " <Enable promiscuous mode>");
                        rte_eth_promiscuous_enable(pid);
                }

                pktgen_log_info("%s", output_buff);
                pktgen.info[pid].seq_pkt[SINGLE_PKT].pktSize = MIN_PKT_SIZE;

                /* Setup the port and packet defaults. (must be after link speed is found) */
                for (s = 0; s < NUM_TOTAL_PKTS; s++)
                        pktgen_port_defaults(pid, s);

                pktgen_range_setup(info);

                rte_eth_stats_get(pid, &info->init_stats);

                pktgen_rnd_bits_init(&pktgen.info[pid].rnd_bitfields);
        }

パケットキャプチャの設定

ポートごとにパケットキャプチャの設定を行う。

        /* Setup the packet capture per port if needed. */
        for (sid = 0; sid < coremap_cnt(pktgen.core_info, pktgen.core_cnt, 0); sid++)
                pktgen_packet_capture_init(&pktgen.capture[sid], sid);

pktgen_packet_capture_init()はパケットキャプチャの初期化を行いmemzoneの確保を行う。 この関数はapp/pktgen-capture.cにて定義されている。

/* app/pktgen-capture.c */

void
pktgen_packet_capture_init(capture_t *capture, int socket_id)
{
        char memzone_name[RTE_MEMZONE_NAMESIZE];

        if (!capture)
                return;

        capture->lcore  = RTE_MAX_LCORE;
        capture->port   = RTE_MAX_ETHPORTS;
        capture->used   = 0;

        snprintf(memzone_name, sizeof(memzone_name), "Capture_MZ_%d",
                 socket_id);
        capture->mz = rte_memzone_reserve(memzone_name, CAPTURE_BUFF_SIZE,
                        socket_id, RTE_MEMZONE_1GB | RTE_MEMZONE_SIZE_HINT_ONLY);
}