OpenFastPath(2):原生態Linux Socket應用如何移植到OpenFastPath上?

版本信息:

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(&params, 0, sizeof(params));
10     if (parse_env(&params) != 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  $@

其中,第二行是用來指定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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章