curl工具的简易实现

描述

 

前言

一般来说,使用套接字进行网络编程时,默认使用linux内核提供的网络服务。但是,现在我们自己在用户空间构建了一个tcp协议栈,并且让它为其他应用程序提供网络服务,这势必要求我们自己实现一套新的套接字接口,并且提供给其他应用程序指定使用。

但是,我们并不希望把该tcp协议栈封装成动态库的形式,因为这样一来,应用程序的编译是必须要把库一起连接进去的。那么原生网络编程开发的程序就不能基于我们tcp协议栈来运行了。

一种较好的设计思路是,把tcp协议栈剥离出来作为一个独立的组件来运行,然后通过一个中间件,把网络程序与tcp协议栈协同工作起来。这个中间件的主要工作就是负责偷龙换凤,也就是把网络程序中的内核网络服务转换成独立运行的tcp协议栈的网络服务。

核心思路:网络程序(curl)+自定义套接字库(liblevel.so)+tcp协议栈(level-ip),如下图:

编译

curl小工具

curl是一种命令行工具,作用是发出网络请求,然后得到和提取数据,显示在"标准输出"(stdout)上面。我们直接在curl命令后加上网址和端口,就可以看到网页源码。比如抓取www.sina.com网址:

 

curl www.sina.com 80

 

下面我们来看一个curl工具的简易实现,如下图:

编译

第3行:判断目标主机名是否合法

第11行:判断目标端口好是否合法

第16行:完成主机名到地址解析

第21行:使用socket申请一个套接字描述符

第23行:使用connect函数发起tcp连接

第30行:按照http 1.1协议来填充要发送的内容,此处为http协议的get请求

第33行:调用write来发送网络数据

第41行:在while循环中重复接收服务器返回来的网页数据,并且打印在当前控制台终端上。

这是标准的网络应用程序,使用gcc命令编译后即可运行。

 

gcc curl.c -o curl

 

liblevelip.so库

level-ip脚本

liblevelip.so库重新封装了常用的socket套接字,并借助socket原生的本地套接字接口来与tcp协议栈(level-ip)进行数据通信。以后在curl程序使用socket套接字时,优先使用该库的服务接口,而不是内核的网络服务。这是通过level-ip这个shell脚本完成的,具体命令如下:

 

./level-ip curl www.sina.com 80

 

我们来分析一下level-ip这个shell脚本的原理,如下图:

编译

第1行:执行该shell脚本由/bin/sh程序来执行。

第3行:指定脚本如果发生错误,或者遇到不存在的变量就报错,并停止执行。

第5行:保存脚本的第一个参数到prog变量中。

第6行:去掉一个参数,即原来的1,2,依此类推。

第8行:LD_PRELOAD是一个环境变量,其指定的动态库加载等级最高。@表示第二个参数之后的全部参数。

综上所述,我们就可以确定curl程序是优先加载liblevelip.so库来使用了,通过这种打桩技术,我们可以在加载阶段替换部分系统函数的调用,比如我们常用的socket接口。

liblevelip.c文件

liblevelip.so库由liblevellip.c文件编译而来,该文件在tools文件夹中,我们逐步来分析一下这个c文件。

__libc_start_main函数

首先是__libc_start_main函数,该函数原本是glibc库里面的函数,curl程序里面的main函数就是从这里开始被调用。但是我们在liblevelip.c里面实现了这个函数,并且liblevelip.so的库加载顺序优先于glibc的动态库加载。因此在执行curl程序中的main函数之前,此函数先被执行。如下图:

编译

第3行:dlsym函数里的第一个参数为RTLD_NEXT,这意味着我们将从其他动态库去加载__libc_start_main函数符号(比如glibc库),然后把函数句柄赋值给__start_main变量。

第7~22行:从glibc库中加载一部分linux系统原生提供的系统调用接口。因为我们的网络服务还是要依赖于一些更底层的系统调用接口的。

第24行:初始化一个链表节点lvlip_socks。

第26行:调用glibc的原生__libc_start_main接口。

socket函数

接下来就是liblevelip.so对外提供的第一个网络编程接口--socket函数了。该函数实现如下:

编译

第3~5行:检查网络通信协议族是否为tcp协议,如果不是tcp协议,则调用内核提供的网络服务。

第9行:借助tcp本地套接字接口,与tcp协议栈建立连接,用于通信的本地文件为/tmp/lvlip.socket

第11行:申请一个lvlip_sock类型的buff用于管理socket信息。结构体类型如下:

 

struct lvlip_sock {    struct list_head list;    int lvlfd; /* For Level-IP IPC */    int fd;};

 

list成员变量为链表结点

lvlfd记录与tcp协议栈通信的网络文件描述符

fd记录tcp协议栈的返回状态发送socket消息给tcp协议栈第12行:记录与tcp协议栈通信的网络文件描述符到sock->lvlfd第13行:把这次的网络通信消息加入lvlip_socks链表中第14行:网络通信消息数量加1第16行:获取当前线程的pid号

第17~18行:申请ipc_msg+ipc_socket结构体长度的buff,用于发送详细的socket信息到tcp协议栈。结构体定义如下:

 

struct ipc_msg {    uint16_t type;    pid_t pid;    uint8_t data[];} __attribute__((packed));

 

type:记录此次socket信息的具体类型

pid:记录请求网络服务的进程pid号

data:存放具体的通信内容

 

struct ipc_socket {    int domain;    int type;    int protocol;} __attribute__((packed));

 

实际上就是soket函数的三个参数。

第23~29行:把ipc_socket作为通信的具体内容填充到ipc_msg的data区域中去

第31行:调用transmit_lvlip()函数真正给tcp协议栈发送消息,并且等待协议栈的数据回复。

此处我们就把liblevelip.so中的socket函数给剖析清楚了,其他诸如close、connect、write、read、send、sendto、recv、此处我们就把liblevelip.so中的socket函数给剖析清楚了,其他诸如close、connect、write、read、send、sendto、recv、此处我们就把liblevelip.so中的socket函数给剖析清楚了,其他诸如close、connect、write、read、send、sendto、recv、recvfrom、poll、select等函数,原理都是一样的,此处不再展开分析。

tcp协议栈(level-ip)

用户空间的level-ip协议栈,在运行之初,就已经在main函数里面创建了一系列线程。如下图:

编译

其中第9行,在run_threads()函数里创建了一系列线程,如下图:

编译

在这里,我们重点关注第5行创建的start_ipc_listener线程

该线程的实现如下:

编译

第5行:指定tcp本地通信的路径文件为"/tmp/lvlip.socket",与我们前面liblevelip.so库的本地通信文件一致,这就说明它们之间确实是通过tco本地通信接口来通信的

第10行:调用socket接口开始进行tcp本地通信

第24行:调用bind函数绑定本地通信路径

第31行:调用listen函数监听指定端口,等待liblevelip.so库发起连接

第46行:如果liblevelip.so库发起连接,则调用accept函数准备开始收发信息。

第54行:每监听到一个新的连接,新创建一个socket_ipc_open函数来进行数据的具体收发。

socket_ipc_open函数主要是负责通信信息的读取,然后根据通信消息的类型不同,来进一步调用具体的处理函数,其实现如下:

编译

第7行:调用read函数进行数据的读取

第8行:调用具体指令的回调信息

demux_ipc_socket_call的函数非常简单,实现如下:

编译

前面我们在liblevelip.so库中调用socket()函数的时候,发送的消息类型为IPC_SOCKET,所以在此处我们进一步分析ipc_socket()这个函数。

它的具体实现如下:

编译

我们重点是关注第7行的_socket函数,该函数就是tcp协议栈的核心接口之一了,它是整个tcp协议栈的真正入口,我们以后再来专门分析这个接口。然后第9行,ipc_write函数负责把tcp协议栈的处理结果返回给liblevelip.so库,代码较为简单,此处不再分析。

  
      审核编辑:彭静
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分