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  [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

时间: 2024-10-11 00:47:06

OpenFastPath(2):原生态Linux Socket应用如何移植到OpenFastPath上?的相关文章

Lwip:原生态的Linux socket应用如何移植到Lwip上

lwIP - A Lightweight TCP/IP stack 在上一篇中,我们了解到在OpenFastPath上如何移植原生态的Linux Socket应用程序,那么,对于另外一个老牌的小型TCPIP协议栈------LWIP,又是如何处理这个问题的. 1.lwip重新定义了函数符号 #define listen(s,backlog) lwip_listen(s,backlog) /** @ingroup socket */ #define recv(s,mem,len,flags) lw

将socket程序从linux移植到windows上

今天突然想试下纯socket编程在两个系统上代码重合量有多大,只要不使用VC自定义的宏(比如SOCKET.SOCKADDR等等)感觉代码重合量挺大的. 比如最简单的TCP客户端和服务端对话,在VC中用int取代SOCKET宏,用struct sockaddr_in取代SOCKADDR_IN宏. 然后区别就仅仅是头文件和windows额外加载/关闭套接字库的代码了. // Unix/Linux#include <sys/socket.h> #include <netinet/in.h>

Linux Socket基础介绍

Linux Socket函数库是从Berkeley大学开发的BSD UNIX系统中移植过来的.BSD Socket接口是众多Unix系统中被广泛支持的TCP/IP通信接口,Linux下的Socket程序设计,除了微小的差别之外,也适用于大多数其它Unix系统. Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序.网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符. Socket的使用和文件

Android Jni层 创建 linux socket 出错问题解决

问题: 想在Jni层创建 udp socket 与服务端通信,但是没有成功,最后发现竟然是创建socket失败(代码如下) // create socket g_sd = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == g_sd) { perror("socket()"); goto err_socket; } 解决办法: 在 AndroidManifest.xml 文件中,添加访问网络的权限: <uses-permission android

Linux socket编程的心跳机制总结

Linux socket编程的心跳机制总结 我写这篇文章的目的是想总结一下心跳机制的使用,因为最近两个项目的TCP通信中都使用了这个方法,感觉用法好诗比较经典的,所以拿出来与大家共享. 什么是心跳机制 心跳机制就是当客户端与服务端建立连接后,每隔几分钟发送一个固定消息给服务端,服务端收到后回复一个固定消息给客户端,如果服务端几分钟内没有收到客户端消息,则视客户端断开.发送方可以是客户端和服务端,看具体需求. 为什么要使用 我们都知道在TCP这种长连接情况下下,有可能有一大段时间是没有数据往来的,

Linux socket编程 DNS查询IP地址

本来是一次计算机网络的实验,但是还没有完全写好,DNS的响应请求报文的冗余信息太多了,不只有IP地址.所以这次的实验主要就是解析DNS报文.同时也需要正确的填充请求报文.如果代码有什么bug,欢迎指正啊.代码排版有点乱... 本文有以下内容 DNS报文的填充和解析 利用socket API传输信息 一.填充DNS请求报文 随便百度一下,就可以知道DNS报文的格式.所以这里只介绍如何填充DNS报文. 首先是填充报文首部: ? 1 2 3 4 5 6 7 8 9 /* 填充首部的格式大致相同,下面的

linux socket中的SO_REUSEADDR

Welcome to the wonderful world of portability... or rather the lack of it. Before we start analyzing these two options in detail and take a deeper look how different operating systems handle them, it should be noted that the BSD socket implementation

Linux内核,文件系统移植过程中出现的一些问题与解决办法

1.bootm地址和load address一样 此种情况下,bootm不会对uImage header后的zImage进行memory move的动作,而会直接go到entry point开始执行.因此此时的entry point必须设置为load address + 0x40.如果kernel boot过程没有到uncompressing the kernel,就可能是这里设置不对. boom address == load address == entry point - 0x40 2.

windows 与 Linux SOCKET通讯

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 windows client 端口 // Def_win_client_socket_test.cpp :