设为首页 收藏本站
查看: 1566|回复: 0

[经验分享] [dpdk] 熟悉SDK与初步使用 (三)(IP Fragmentation源码分析)

[复制链接]

尚未签到

发表于 2017-6-25 06:49:14 | 显示全部楼层 |阅读模式
  对例子IP Fragmentation的熟悉,使用,以及源码分析。
  功能:
  该例子的功能有二:
  一: 将IP分片?
  二: 根据路由表,做包转发。 路由表如下:



IP_FRAG: Socket 1: adding route 100.10.0.0/16 (port 0)
IP_FRAG: Socket 1: adding route 100.20.0.0/16 (port 1)
IP_FRAG: Socket 1: adding route 100.30.0.0/16 (port 2)
IP_FRAG: Socket 1: adding route 100.40.0.0/16 (port 3)
IP_FRAG: Socket 1: adding route 100.50.0.0/16 (port 4)
IP_FRAG: Socket 1: adding route 100.60.0.0/16 (port 5)
IP_FRAG: Socket 1: adding route 100.70.0.0/16 (port 6)
IP_FRAG: Socket 1: adding route 100.80.0.0/16 (port 7)
IP_FRAG: Socket 1: adding route 0101:0101:0101:0101:0101:0101:0101:0101/48 (port 0)
IP_FRAG: Socket 1: adding route 0201:0101:0101:0101:0101:0101:0101:0101/48 (port 1)
IP_FRAG: Socket 1: adding route 0301:0101:0101:0101:0101:0101:0101:0101/48 (port 2)
IP_FRAG: Socket 1: adding route 0401:0101:0101:0101:0101:0101:0101:0101/48 (port 3)
IP_FRAG: Socket 1: adding route 0501:0101:0101:0101:0101:0101:0101:0101/48 (port 4)
IP_FRAG: Socket 1: adding route 0601:0101:0101:0101:0101:0101:0101:0101/48 (port 5)
IP_FRAG: Socket 1: adding route 0701:0101:0101:0101:0101:0101:0101:0101/48 (port 6)
IP_FRAG: Socket 1: adding route 0801:0101:0101:0101:0101:0101:0101:0101/48 (port 7)
  问题一:
  main()函数大概是这样的:标红的三行将与下面叙述的事情相关



int
main(int argc, char **argv)
{
... ...
/* init EAL */
ret = rte_eal_init(argc, argv);
if (ret < 0)
rte_exit(EXIT_FAILURE, "rte_eal_init failed");
... ...
/* launch per-lcore init on every lcore */
rte_eal_mp_remote_launch(main_loop, NULL, CALL_MASTER);
RTE_LCORE_FOREACH_SLAVE(lcore_id) {
if (rte_eal_wait_lcore(lcore_id) < 0)
return -1;
}
return 0;
}
  其中,函数 rte_eal_wait_lcore 的实现如下:



/*
* Wait until a lcore finished its job.                                                                                                                                
*/                           
int
rte_eal_wait_lcore(unsigned slave_id)                                                                                                                                 
{
if (lcore_config[slave_id].state == WAIT)
return 0;     
while (lcore_config[slave_id].state != WAIT &&
lcore_config[slave_id].state != FINISHED);                                                                                                              
rte_rmb();
/* we are in finished state, go to wait state */
lcore_config[slave_id].state = WAIT;
return lcore_config[slave_id].ret;                                                                                                                             
}
  阅读红色部分,可以很明显的发现,这是一个死循环啊!!! 从字面意义上来看,main函数在完成了remote_launch之后,主进程会在这个函数里等等子进程结束。
  这样的话,用一个死循环来等,难道不会有问题吗??? 所以我要的debug它一下看看怎么回事。 于是,为了达到这个目的,我分别经历了下文中的问题二三四。终于debug成功了。解答如下:
  解答起来其实也很简单,只需要看下 rte_eal_mp_remote_launch() 函数的代码,就明白了。它的代码如下:



66 /*
67  * Check that every SLAVE lcores are in WAIT state, then call
68  * rte_eal_remote_launch() for all of them. If call_master is true
69  * (set to CALL_MASTER), also call the function on the master lcore.
70  */
71 int
72 rte_eal_mp_remote_launch(int (*f)(void *), void *arg,
73                          enum rte_rmt_call_master_t call_master)
74 {
75         int lcore_id;
76         int master = rte_get_master_lcore();
77
78         /* check state of lcores */
79         RTE_LCORE_FOREACH_SLAVE(lcore_id) {
80                 if (lcore_config[lcore_id].state != WAIT)
81                         return -EBUSY;
82         }
83
84         /* send messages to cores */
85         RTE_LCORE_FOREACH_SLAVE(lcore_id) {
86                 rte_eal_remote_launch(f, arg, lcore_id);
87         }
88
89         if (call_master == CALL_MASTER) {
90                 lcore_config[master].ret = f(arg);
91                 lcore_config[master].state = FINISHED;
92         }
93
94         return 0;
95 }
  从第90行可以看出。主进程在这里进入了业务逻辑,所以直到程序退出之前。它都没有机会执行前边的那个死循环。也就是说,主进程当进入死循环的时候,也说明其他进程即将结束。并不会存在长期空跑CPU的情况。 不过,如果业务逻辑写错了呢? 子进程并没有如逾期退出的话,是否会进入循环? 这里暂时先留下这个疑问。
  另一个需要纪录下来的东西是。所有有需要的函数,实际上在rte_eal_init() 函数中便都创建完成了。remote_launch()函数实际上只是为其他进程传递一个启动运行的消息。
  具体消息内容,目前我没有深入分析。
  问题二:
  运行不起来,启用DEBUG,gdb跟踪一下。
  这个makefile也是很那难用的。摸索了一下,有几个命令,比较有用的如下:



[iyunv@dpdk dpdk]# make help
[iyunv@dpdk dpdk]# make V=yes D=yes
  以上命令并没有用,到各模块的MAKEFILE里,将-O3手工改成-g,重新编译,才奏效。
  问题三:
  通过gdb发现,启动不了跟网卡特效有关系。
  a。初始化函数中默认的参数是启用 硬checksum 等 offload 特性的。由于我模拟的网卡不支持,只能关掉。



static const struct rte_eth_conf port_conf = {
.rxmode = {
.max_rx_pkt_len = JUMBO_FRAME_MAX_SIZE,
.split_hdr_size = 0,           
.header_split   = 0, /**< Header Split disabled */
.hw_ip_checksum = 0, /**< IP checksum offload enabled */
.hw_vlan_filter = 0, /**< VLAN filtering disabled */
.jumbo_frame    = 1, /**< Jumbo Frame Support enabled */
.hw_strip_crc   = 0, /**< CRC stripped by hardware */                                                                                                  
},                    
.txmode = {           
.mq_mode = ETH_MQ_TX_NONE,                                                                                                                             
},
};
  b. 另一处修改



               /* init one TX queue per couple (lcore,port) */
queueid = 0;
for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
if (rte_lcore_is_enabled(lcore_id) == 0)
continue;
socket = (int) rte_lcore_to_socket_id(lcore_id);
printf("txq=%u,%d ", lcore_id, queueid);
fflush(stdout);
rte_eth_dev_info_get(portid, &dev_info);
txconf = &dev_info.default_txconf;
txconf->txq_flags = 0 | ETH_TXQ_FLAGS_NOXSUMS;
ret = rte_eth_tx_queue_setup(portid, queueid, nb_txd,
socket, txconf);
if (ret < 0) {
printf("\n");
rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup: "
"err=%d, port=%d\n", ret, portid);
}
qconf = &lcore_queue_conf[lcore_id];
qconf->tx_queue_id[portid] = queueid;
queueid++;
}
  c. 我之前模拟的网卡不支持多队列,经过学习研究,让 qemu/kvm 支持了多队列。另写了一篇,如下:
  [Virtualization][qemu][kvm][virtio] 使用 QEMU/KVM 模拟网卡多队列
  启动成功:



[iyunv@dpdk build]# ./ip_fragmentation -l 6,7 -- -p 3
EAL: Detected 8 lcore(s)
EAL: Probing VFIO support...
EAL: WARNING: cpu flags constant_tsc=yes nonstop_tsc=no -> using unreliable clock cycles !
PMD: bnxt_rte_pmd_init() called for (null)
EAL: PCI device 0000:00:03.0 on NUMA socket -1
EAL:   probe driver: 1af4:1000 rte_virtio_pmd
EAL: PCI device 0000:00:04.0 on NUMA socket -1
EAL:   probe driver: 1af4:1000 rte_virtio_pmd
EAL: PCI device 0000:00:05.0 on NUMA socket -1
EAL:   probe driver: 1af4:1000 rte_virtio_pmd
IP_FRAG: Creating direct mempool on socket 1
IP_FRAG: Creating indirect mempool on socket 1
IP_FRAG: Creating LPM table on socket 1
IP_FRAG: Creating LPM6 table on socket 1
Initializing port 0 on lcore 6... Address:00:00:00:01:00:01
txq=6,0 txq=7,1
Initializing port 1 on lcore 7... Address:00:00:00:01:00:02
txq=6,0 txq=7,1
IP_FRAG: Socket 1: adding route 100.10.0.0/16 (port 0)
IP_FRAG: Socket 1: adding route 100.20.0.0/16 (port 1)
IP_FRAG: Socket 1: adding route 100.30.0.0/16 (port 2)
IP_FRAG: Socket 1: adding route 100.40.0.0/16 (port 3)
IP_FRAG: Socket 1: adding route 100.50.0.0/16 (port 4)
IP_FRAG: Socket 1: adding route 100.60.0.0/16 (port 5)
IP_FRAG: Socket 1: adding route 100.70.0.0/16 (port 6)
IP_FRAG: Socket 1: adding route 100.80.0.0/16 (port 7)
IP_FRAG: Socket 1: adding route 0101:0101:0101:0101:0101:0101:0101:0101/48 (port 0)
IP_FRAG: Socket 1: adding route 0201:0101:0101:0101:0101:0101:0101:0101/48 (port 1)
IP_FRAG: Socket 1: adding route 0301:0101:0101:0101:0101:0101:0101:0101/48 (port 2)
IP_FRAG: Socket 1: adding route 0401:0101:0101:0101:0101:0101:0101:0101/48 (port 3)
IP_FRAG: Socket 1: adding route 0501:0101:0101:0101:0101:0101:0101:0101/48 (port 4)
IP_FRAG: Socket 1: adding route 0601:0101:0101:0101:0101:0101:0101:0101/48 (port 5)
IP_FRAG: Socket 1: adding route 0701:0101:0101:0101:0101:0101:0101:0101/48 (port 6)
IP_FRAG: Socket 1: adding route 0801:0101:0101:0101:0101:0101:0101:0101/48 (port 7)
Checking link status
done
Port 0 Link Up - speed 10000 Mbps - full-duplex
Port 1 Link Up - speed 10000 Mbps - full-duplex
IP_FRAG: entering main loop on lcore 7
IP_FRAG:  -- lcoreid=7 portid=1
IP_FRAG: entering main loop on lcore 6
IP_FRAG:  -- lcoreid=6 portid=0
  问题四:
  如何查看编译选项,使用的静态库。修改编译选项,启动debug等? 唯一的办法是makefile。结构还是很清晰的。但是,依然需要花很长的时间读。
  打印编译命令的方法如下:
  修改文件 /sdk/@dpdk/dpdk-stable-16.07.1 mk/internal/rte.compile-pre.mk 中的 C_TO_O_DO 变量: 第101行,为新增内容。



99 C_TO_O_DO = @set -e; \     
100         echo $(C_TO_O_DISP); \
101         echo $(C_TO_O); \
102         $(C_TO_O) && \     
103         $(PMDINFO_TO_O) && \
104         echo $(C_TO_O_CMD) > $(call obj2cmd,$(@)) && \
105         sed 's,'$@':,dep_'$@' =,' $(call obj2dep,$(@)).tmp > $(call obj2dep,$(@)) && \                                                                             
106         rm -f $(call obj2dep,$(@)).tmp
107
  打印链接命令的方法如下:
  修改文件 /sdk/@dpdk/dpdk-stable-16.07.1 mk/rte.app.mk 中的 O_TO_EXE_DO 变量: 第209行,为新增内容。



207 O_TO_EXE_DO = @set -e; \
208         echo $(O_TO_EXE_DISP); \
209         echo $(O_TO_EXE); \
210         $(O_TO_EXE) && \
211         echo $(O_TO_EXE_CMD) > $(call exe2cmd,$(@))
212
  实现效果如下:



[iyunv@dpdk ip_fragmentation]# make
echo "xxxxccccxxxx"
xxxxccccxxxx
CC main.o
gcc -Wp,-MD,./.main.o.d.tmp -m64 -pthread -march=native -DRTE_MACHINE_CPUFLAG_SSE -DRTE_MACHINE_CPUFLAG_SSE2 -DRTE_MACHINE_CPUFLAG_SSE3 -DRTE_MACHINE_CPUFLAG_SSSE3 -DRTE_MACHINE_CPUFLAG_SSE4_1 -DRTE_MACHINE_CPUFLAG_SSE4_2 -I/root/src/sdk/@dpdk/dpdk-stable-16.07.1/examples/ip_fragmentation/build/include -I/root/dpdk//x86_64-native-linuxapp-gcc/include -include /root/dpdk//x86_64-native-linuxapp-gcc/include/rte_config.h -g -W -Wall -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wold-style-definition -Wpointer-arith -Wcast-align -Wnested-externs -Wcast-qual -Wformat-nonliteral -Wformat-security -Wundef -Wwrite-strings -Wno-return-type -o main.o -c /root/src/sdk/@dpdk/dpdk-stable-16.07.1/examples/ip_fragmentation/main.c
  LD ip_fragmentation
gcc -o ip_fragmentation -m64 -pthread -march=native -DRTE_MACHINE_CPUFLAG_SSE -DRTE_MACHINE_CPUFLAG_SSE2 -DRTE_MACHINE_CPUFLAG_SSE3 -DRTE_MACHINE_CPUFLAG_SSSE3 -DRTE_MACHINE_CPUFLAG_SSE4_1 -DRTE_MACHINE_CPUFLAG_SSE4_2 -I/root/src/sdk/@dpdk/dpdk-stable-16.07.1/examples/ip_fragmentation/build/include -I/root/dpdk//x86_64-native-linuxapp-gcc/include -include /root/dpdk//x86_64-native-linuxapp-gcc/include/rte_config.h -g -W -Wall -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wold-style-definition -Wpointer-arith -Wcast-align -Wnested-externs -Wcast-qual -Wformat-nonliteral -Wformat-security -Wundef -Wwrite-strings main.o -L/root/dpdk//x86_64-native-linuxapp-gcc/lib -Wl,-lrte_kni -Wl,-lrte_pipeline -Wl,-lrte_table -Wl,-lrte_port -Wl,-lrte_pdump -Wl,-lrte_distributor -Wl,-lrte_reorder -Wl,-lrte_ip_frag -Wl,-lrte_meter -Wl,-lrte_sched -Wl,-lrte_lpm -Wl,--whole-archive -Wl,-lrte_acl -Wl,--no-whole-archive -Wl,-lrte_jobstats -Wl,-lrte_power -Wl,--whole-archive -Wl,-lrte_timer -Wl,-lrte_hash -Wl,-lrte_vhost -Wl,-lrte_kvargs -Wl,-lrte_mbuf -Wl,-lethdev -Wl,-lrte_cryptodev -Wl,-lrte_mempool -Wl,-lrte_ring -Wl,-lrte_eal -Wl,-lrte_cmdline -Wl,-lrte_cfgfile -Wl,-lrte_pmd_bond -Wl,-lrte_pmd_af_packet -Wl,-lrte_pmd_bnxt -Wl,-lrte_pmd_cxgbe -Wl,-lrte_pmd_e1000 -Wl,-lrte_pmd_ena -Wl,-lrte_pmd_enic -Wl,-lrte_pmd_fm10k -Wl,-lrte_pmd_i40e -Wl,-lrte_pmd_ixgbe -Wl,-lrte_pmd_null -Wl,-lrte_pmd_ring -Wl,-lrte_pmd_virtio -Wl,-lrte_pmd_vhost -Wl,-lrte_pmd_vmxnet3_uio -Wl,-lrte_pmd_null_crypto -Wl,--no-whole-archive -Wl,-lrt -Wl,-lm -Wl,-ldl -Wl,-export-dynamic -Wl,-export-dynamic -L/root/src/sdk/@dpdk/dpdk-stable-16.07.1/examples/ip_fragmentation/build/lib -L/root/dpdk//x86_64-native-linuxapp-gcc/lib -Wl,--as-needed -Wl,-Map=ip_fragmentation.map -Wl,--cref
INSTALL-APP ip_fragmentation
INSTALL-MAP ip_fragmentation.map
[iyunv@dpdk ip_fragmentation]#
  问题五
  启动例子程序之后,做发包测试。发现所有包都被源端口转发回来。通过代码可以看到,默认的路由规则就是源端口转发回来。
  a。怎么方便的发包呢? 除了已知的 tcpreplay 可以在端口上将包原样转发以外。还可以使用 tcpreplay-edit 对包内容做一下修改后在发送。我就是通过这种方法来测试例子中的路由表的。



/home/tong/Data [tong@T7] [11:24]
> sudo tcpreplay-edit -i tap-dpdk-1 -D 0.0.0.0/0:100.20.0.0/16 --enet-dmac=00:00:00:01:00:01 -L1 oicq-bak.pcap
Actual: 2 packets (162 bytes) sent in 30.01 seconds.
Rated: 5.3 Bps, 0.000 Mbps, 0.06 pps
Flows: 1 flows, 0.03 fps, 2 flow packets, 0 non-flow
Statistics for network device: tap-dpdk-1
Attempted packets:         2
Successful packets:        2
Failed packets:            0
Truncated packets:         0
Retried packets (ENOBUFS): 0
Retried packets (EAGAIN):  0
  b。通过 debug 发现收包之后的结构如下:
  其中,红色部分的结构代表了报文类型。很显然,可以看出来,程序在这个时间点,并没有识别到,该报文是IPv4 or IPv6。在后续做转发的代码逻辑里,会对IP类型进行判断,在此结构体数值下,该代码判断为既不是v4,也不是v6。故进入了默认路由,从源端口发了出来。
  所以,到目前为止,并不知道为什么报类型没有被识别。可以有三种情况,1,代码依赖了硬件来处理,而我的模拟网卡不能处理。2. pmd处理,又要我是虚拟机,故没有处理。3. 例子代码有误。应该在代码某处,调用一个识别的函数进行处理。
  总之,在还未完全高清楚一个常规包处理流程和逻辑时。该问题暂搁置。



(gdb) p *m
$26 = {cacheline0 = 0x7fffd4dd4300, buf_addr = 0x7fffd4dd4380, buf_physaddr = 2059223936, buf_len = 2176, rearm_data = 0x7fffd4dd4312 "\216", data_off = 142, {
refcnt_atomic = {cnt = 1}, refcnt = 1}, nb_segs = 1 '\001', port = 0 '\000', ol_flags = 0, rx_descriptor_fields1 = 0x7fffd4dd4320, {packet_type = 0, {
l2_type = 0, l3_type = 0, l4_type = 0, tun_type = 0, inner_l2_type = 0, inner_l3_type = 0, inner_l4_type = 0}}, pkt_len = 67, data_len = 67, vlan_tci = 0,
hash = {rss = 0, fdir = {{{hash = 0, id = 0}, lo = 0}, hi = 0}, sched = {lo = 0, hi = 0}, usr = 0}, seqn = 0, vlan_tci_outer = 0, cacheline1 = 0x7fffd4dd4340, {
userdata = 0x0, udata64 = 0}, pool = 0x7fffd60436c0, next = 0x0, {tx_offload = 0, {l2_len = 0, l3_len = 0, l4_len = 0, tso_segsz = 0, outer_l3_len = 0,
outer_l2_len = 0}}, priv_size = 0, timesync = 0}
(gdb)
  c. 改了qemu,模拟了硬件checksum,仍不行。通过debug分析代码,好像是virtio pmd driver就不支持硬件checksum。新的qemu命令如下(注意红色部分)。并没有找到方法怎么在guest里通过查看的方式确认网卡是否支持 checksum offload.



/home/tong/VM/dpdk [tong@T7] [19:06]
> cat start-multiqueue.sh
sudo qemu-system-x86_64 -nographic -vnc 127.0.0.1:1 -enable-kvm \
-m 2G -cpu Nehalem -smp cores=2,threads=2,sockets=2 \
-numa node,mem=1G,cpus=0-3,nodeid=0 \
-numa node,mem=1G,cpus=4-7,nodeid=1 \
-drive file=disk.img,if=virtio \
-net nic,vlan=0,model=virtio,macaddr='00:00:00:01:00:00' \
-device virtio-net-pci,netdev=dev1,mac='00:00:00:01:00:01',vectors=34,mq=on,csum=on,guest_csum=on \
-device virtio-net-pci,netdev=dev2,mac='00:00:00:01:00:02',vectors=34,mq=on,csum=on,guest_csum=on \
-device virtio-net-pci,netdev=dev3,mac='00:00:00:01:00:03',vectors=34,mq=on,csum=on,guest_csum=on \
-net tap,vlan=0,ifname=tap-dpdk-ctrl \
-netdev tap,ifname=tap-dpdk-1,script=no,downscript=no,vhost=on,queues=16,id=dev1 \
-netdev tap,ifname=tap-dpdk-2,script=no,downscript=no,vhost=on,queues=16,id=dev2 \
-netdev tap,ifname=tap-dpdk-3,script=no,downscript=no,vhost=on,queues=16,id=dev3 &
#       -device vfio-pci,host='0000:00:19.0' \
#ne2k_pci,i82551,i82557b,i82559er,rtl8139,e1000,pcnet,virtio
/home/tong/VM/dpdk [tong@T7] [19:06]
>

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-387794-1-1.html 上篇帖子: 《CDN技术详解》 下篇帖子: vyatta常用操作
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表