版本信息:
ODP(Open Data Plane): 1.19.0.2
OFP(Open Fast Path): 3.0.0
1、存在的问题
OpenFastPath作为一个开源的用户态TCP/IP协议栈,其对用户提供的Socket API,无论是宏定义、数据结构还是函数,均以OFP_开头。如下图所示:
1 int ofp_socket(int, int, int); 2 int ofp_socket_vrf(int, int, int, int); 3 int ofp_accept(int, struct ofp_sockaddr *, ofp_socklen_t *); 4 int ofp_bind(int, const struct ofp_sockaddr *, ofp_socklen_t); 5 int ofp_connect(int, const struct ofp_sockaddr *, ofp_socklen_t); 6 int ofp_listen(int, int); 7 int ofp_shutdown(int, int); 8 int ofp_close(int); 9 10 struct ofp_timeval { 11 uint32_t tv_sec; /* seconds */ 12 uint32_t tv_usec; /* microseconds */ 13 }; 14 15 /* 16 * Option flags per-socket, kept in so_options. 17 */ 18 #define OFP_SO_DEBUG 0x00000001 /* turn on debugging info recording */ 19 #define OFP_SO_ACCEPTCONN 0x00000002 /* socket has had listen() */ 20 #define OFP_SO_REUSEADDR 0x00000004 /* allow local address reuse */ 21 #define OFP_SO_KEEPALIVE 0x00000008 /* keep connections alive */ 22 #define OFP_SO_DONTROUTE 0x00000010 /* just use interface addresses */
这样子的实现,会带来一个问题:即用户之前编写的基于Linux Socket的应用程序(比如用户基于Linux Socket编写的WebServer),如果想移植到OFP上,由于上述符号的差异,将需要大量的修改。
如何解决这一问题,是本文讨论的话题。
2、OFP提供的解决方案
OFP提供给用户的example中,已经提供了一种可行的方案。读者在理解OFP提供的方案之前,可以先看两个C语言中的基础知识。
(1)C语言的构造函数
在gcc下可以使用关键字__attribute__((constructor))指定构造函数。这些构造函数由编译器处理,在执行main函数之前,就会执行。构造函数的定义参考如下:
void __attribute__((constructor)) func(void)。func即会在程序执行main函数之前执行。
(2)程序运行时,动态库的搜索先后顺序。
- LD_PRELOAD中指定的搜索路径。
- LD_LIBRARY_PATH中指定的搜索路径。
- 配置文件/etc/ld.so.conf中指定的动态库。
- 默认的动态库搜索路径(/lib, /usr/lib)。
OFP提供的具体方案:
OFP在example中提供了两个库,其中ofp_netwrap_proc.so库用来实现ODP/OFP的配置以及初始化。ofp_netwrap_crt.so库用来实现符号的重载和参数的转换,这些系统调用包括:socket(), close(), shutdown()等。
ofp_netwrap_proc.so是使用构造函数的方法来实现ODP/OFP的配置以及初始化,具体代码参考(example/ofp_netwrap_proc/app_main.c):
1 __attribute__((constructor)) static void ofp_netwrap_main_ctor(void) 2 { 3 appl_args_t params; 4 int core_count, ret_val; 5 odp_cpumask_t cpumask; 6 char cpumaskstr[64]; 7 odph_odpthread_params_t thr_params; 8 9 memset(¶ms, 0, sizeof(params)); 10 if (parse_env(¶ms) != EXIT_SUCCESS) 11 return; 12 13 /* 14 * Before any ODP API functions can be called, we must first init ODP 15 * globals, e.g. availale accelerators or software implementations for 16 * shared memory, threads, pool, qeueus, sheduler, pktio, timer, crypto 17 * and classification. 18 */ 19 if (odp_init_global(&netwrap_proc_instance, NULL, NULL)) { 20 printf("Error: ODP global init failed.\n"); 21 return; 22 } 23 netwrap_state = NETWRAP_ODP_INIT_GLOBAL;
说明:ofp_netwrap_main_ctor函数为构造函数,此函数会在main之前执行,用来实现ODP/OFP初始化及线程创建(包括命令行线程)。OFP运行时需要指定的参数(比如-i eth0)则是通过环境变量传入的,环境变量名为OFP_NETWRAP_ENV。
ofp_netwrap_crt.so库重写了socket的系统调用,我们可以通过一个例子来分析一下(example/ofp_netwrap_crt/netwrap_socket.c)。
1 int close(int sockfd) 2 { 3 int close_value; 4 5 if (IS_OFP_SOCKET(sockfd)) { 6 close_value = ofp_close(sockfd); 7 errno = NETWRAP_ERRNO(ofp_errno); 8 } else if (libc_close) 9 close_value = (*libc_close)(sockfd); 10 else { /* pre init*/ 11 LIBC_FUNCTION(close); 12 13 if (libc_close) 14 close_value = (*libc_close)(sockfd); 15 else { 16 close_value = -1; 17 errno = EACCES; 18 } 19 } 20 21 /*printf("Socket ‘%d‘ closed returns:‘%d‘\n", 22 sockfd, close_value);*/ 23 return close_value; 24 }
说明:以close()函数为例,ofp_netwrap_crt.so重载了此函数,当判断出是属于OFP创建的socket时,则调用ofp_close()函数;而其他情况下,则调用libc_close指针(则指针可以参考LIBC_FUNCTION宏),是通过dlsym的方法加载到标准的C库socket函数。
ofp_netwrap_crt.so库同样提供了一个构造函数,此构造函数用来预加载所有的socket符号(example/ofp_netwrap_crt/netwrap.c)
1 __attribute__((constructor)) static void setup_wrappers(void) 2 { 3 printf("start to setup netwrap"); 4 sleep(20); /* add for test */ 5 6 setup_socket_wrappers(); 7 setup_sockopt_wrappers(); 8 setup_ioctl_wrappers(); 9 setup_fork_wrappers(); 10 setup_select_wrappers(); 11 setup_uio_wrappers(); 12 setup_sendfile_wrappers(); 13 setup_epoll_wrappers(); 14 15 printf("finish to setup netwrap"); 16 }
3、具体例子
1)写一个标准的且简单的TCP服务端程序:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<errno.h> 4 #include<string.h> 5 #include<sys/types.h> 6 #include<netinet/in.h> 7 #include<sys/socket.h> 8 #include<sys/wait.h> 9 10 #define PORT 1500 11 #define BACKLOG 5 12 13 int main(){ 14 int sockfd,new_fd; 15 struct sockaddr_in my_addr; 16 struct sockaddr_in their_addr; 17 int sin_size; 18 19 sockfd=socket(AF_INET,SOCK_STREAM,0); 20 if(sockfd==-1){ 21 printf("socket failed:%d",errno); 22 return -1; 23 } 24 my_addr.sin_family=AF_INET; 25 my_addr.sin_port=htons(PORT); 26 my_addr.sin_addr.s_addr=htonl(INADDR_ANY); 27 bzero(&(my_addr.sin_zero),8); 28 if(bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0){ 29 printf("bind error"); 30 return -1; 31 } 32 33 listen(sockfd,BACKLOG); 34 while(1){ 35 sin_size=sizeof(struct sockaddr_in); 36 new_fd=accept(sockfd,(struct sockaddr*)&their_addr,&sin_size); 37 if(new_fd==-1){ 38 printf("receive failed"); 39 } else{ 40 printf("receive success"); 41 send(new_fd,"Hello World!",12,0); 42 } 43 } 44 return 0; 45 }
说明:此程序是标准的基于linux socket的TCP服务端程序,程序的功能是当TCP建链成功后,向客户端发送hello world!将此程序直接编译成可执行文件tcpsrv。
2)./configure --prefix=/usr/local --with-odp=/usr/local --with-config-flv=netwrap-webserver --enable-sp=no
说明:原生态Linux Socket应用运行在OFP上时,慢平面(sp)的功能必须disable,另外需要配置netwrap-webserver。然后make clean;make;make install
3)修改ofp提供的配置文件scripts/ofp_netwrap.cli,配置fp0接口的ip地址:ifconfig fp0 192.168.43.2/24
4)修改ofp提供的脚本文件scripts/ofp_netwrap.sh
#!/bin/bash export ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" export OFP_NETWRAP_ENV_DEFAULT="-i eth0 -f ${ROOT_DIR}/ofp_netwrap.cli" export OFP_NETWRAP_ENV="${OFP_NETWRAP_ENV:-${OFP_NETWRAP_ENV_DEFAULT}}" LD_PRELOAD=libofp_netwrap_crt.so.0.0.0:libofp.so.0.0.0:libofp_netwrap_proc.so.0.0.0 [email protected]
其中,第二行是用来指定OFP运行时的网卡和配置文件,根据实际情况修改。最后一行通过LD_PRELOAD导入libofp_netwrap_crt.so和libofp_netwrap_proc.so库。这两个库的入口都是构造函数,前文已经讲过了。
5)运行原生态Linux应用。
./ofp_netwrap.sh ./ofp_netwrap.sh /home/tcp-example/tcpsrv
特别说明:之前运行时,程序一直coredump时,后来花了较多时间调试,发现是odp/ofp初始化时,需要调用linux socket创建的socket多达40多个,但当配置--with-config-flv=netwrap-webserver时,分配给linux创建的socket总数不大于20,而将ofp socket创建的socket id的编号分配从20开始。所以,odp/ofp初始化时编号20以后的socket将使用ofp_socket创建,这会导致问题。
解决方案:OFP_SOCK_NUM_OFFSET的值从20修改为60。
/**Socket handle values returned are in the interval: * [OFP_SOCK_NUM_OFFSET, OFP_SOCK_NUM_OFFSET + OFP_NUM_SOCKETS_MAX] */ #if defined(OFP_CONFIG_WEBSERVER) /**Maximum number of sockets. */ # define OFP_NUM_SOCKETS_MAX 60000 /**First socket number value. */ # define OFP_SOCK_NUM_OFFSET 1024 /**Maximum number of TCP PCBs. */ # define OFP_NUM_PCB_TCP_MAX 60000 # define OFP_TCP_MAX_CONNECTION_RATE #elif defined(OFP_CONFIG_NETWRAP_WEBSERVER) /**Maximum number of sockets. */ # define OFP_NUM_SOCKETS_MAX 1000 /**First socket number value. */ # define OFP_SOCK_NUM_OFFSET 60 /* modify form 20 to 60 */ /**Maximum number of TCP PCBs. */ # define OFP_NUM_PCB_TCP_MAX 65534 # define OFP_TCP_MAX_CONNECTION_RATE #else /*OFP_CONFIG_DEFAULT*/ /**Maximum number of sockets. */ # define OFP_NUM_SOCKETS_MAX 1024 /**First socket number value. */ # define OFP_SOCK_NUM_OFFSET 1024 /**Maximum number of TCP PCBs. */ # define OFP_NUM_PCB_TCP_MAX 2048 #endif /* OFP_CONFIGS*/
configure时,配置了--with-config-flv=netwrap-webserver选项,OFP_CONFIG_NETWRAP_WEBSERVER宏将被定义。
4、测试结果
1)linux下执行ifconfig,没有fp0接口,说明慢平面的功能被关闭。
2)另外一台机器ping 192.168.43.2(ofp快平面配置的ip),可以ping通。
3)PC开启tcp客户端功能,看tcp功能是否正常。
原文地址:https://www.cnblogs.com/shaoyangz/p/10346060.html