基于64核下TCPIP协议栈的实现.doc_第1页
基于64核下TCPIP协议栈的实现.doc_第2页
基于64核下TCPIP协议栈的实现.doc_第3页
基于64核下TCPIP协议栈的实现.doc_第4页
基于64核下TCPIP协议栈的实现.doc_第5页
已阅读5页,还剩25页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

学士学位论文基于64核下TCP/IP协议栈的实现学生姓名 耿魁学科专业 软件工程指导教师 杨淑群福建师范大学软件学院二一一年五月Fujian Normal UniversityFaculty of SoftwareImplementation of the TCP/IP Protocol Stack Based on TILE64 ProcessorA Thesis inSoftware EngineeringByGeng KuiAdvised byYang ShuqunSubmitted in Partial FulfillmentOf the RequirementsFor the Degree ofBachelor of EngineeringMay, 201126基于64核下TCP/IP协议栈的实现软件学院软件工程专业123012007003耿魁指导教师:杨淑群【摘要】TCP/IP协议作为成熟的网际互联手段和标准,已成为嵌入式系统接入互联网的首选协议。但是在嵌入式系统中,硬件资源较少,难以支持整个TCP/IP协议。本文以TILExpress-64开发板作为硬件平台,根据网络开发应用的需求,对TCP/IP协议栈进行研究和改进,实现基于64核下的TCP/IP协议栈。【关键字】多核处理器,TCP/IP,协议栈,嵌入式系统。目 录1引言11.1 课题背景11.2 课题介绍12Tilera硬件平台12.1 TILExpress-64开发板12.1.1 TILE64多核处理器12.1.2 开发板结构22.2 Tilera系统的软件架构33. TCP/IP协议43.1 地址解析协议ARP协议53.2 网际协议IP协议63.3 传输控制协议TCP协议63.4 用户数据报协议UDP协议63.5 域名系统DNS协议64. 协议栈的设计与实现64.1 协议栈本质及其分层架构74.2 打开和关闭套接字socket、close函数74.2.1 socket函数74.2.2 close函数94.3 绑定本地地址bind函数94.4 设置和获取套接字选项setsockopt、getsockopt函数104.4.1 setsockopt函数104.4.2 getsockopt函数114.5 管理网络接口ioctl函数114.6 管理套接字fcntl函数124.7 监视套接字select函数124.8 建立连接connect函数134.9 传送数据sendto、send函数164.9.1 sendto函数164.9.2 send函数184.10 接收数据recvfrom、recv函数184.10.1 recvfrom函数184.10.2 recv函数184.11 解析域名gethostbyname函数195. TILE64平台上的Nmap移植195.1 协议栈功能验证195.2 Nmap移植206. 结束语226.1 本文成果总结226.2 后续工作展望22致谢24参考文献251引言1.1 课题背景随着计算机技术的不断发展,多核处理器1已经成为时代的主流。多核市场目前由两大阵营组成:以Intel公司和AMD公司组成的X86阵型;以NetLogic、Cavium、Telira、ARM、IBM等公司组成的MIPS阵型。X86采用CISC(Complex Instruction Set Computing)复杂指令集,按顺序串行执行程序命令,控制简单,适于PC以及中、低端服务器,而MIPS采用RISC(Reduced Instruction Set Computing)精简指令集,其相对于CISC指令来说格式统一,种类和寻找方式较少,并采用了“超标量和超流水线结构”,大大提高了处理速度,适于网络通信、信息安全及高端服务器2。为了提升在信息安全领域的竞争力,MIPS阵营提出了SoC(System on Chip),即片上系统3,将网络连接、负责均衡、加/解密、应用加速等功能集成在每个芯片上4,更适合开发超高速网络流量的网络完全产品。国内一线信息安全厂商联想网御在2008年率先在国内推出了基于MIPS多核架构的万兆安全网关,其网络处理能力可达40Gbps,新建连接速率可达每秒30万个以上5。1.2 课题介绍在本课题中,硬件平台是由Tilera公司生产的TILExpress-64多核处理器开发板。TILExpress-64开发板具有64个相同的处理器内核,这些处理器内核既可以各自运行一个独立的操作系统,也可以几个内核合起来运行一个支持多进程的操作系统。该开发板同时也支持标准C/C+编程,对外提供了6个RJ45的网络接口。Tilera系统提供了应用层编程(NetIO模式),系统函数库提供了相应的API函数,如收包函数netio_get_packet()、发包函数netio_send_packet()等。在NetIO编程模式下,应用程序可以自己构造MAC包,然后调用发包函数将数据发送到指定的网络接口上6。该平台虽提供了发送/接收底层数据包的接口,但其只实现到数据链路层,不利于软件向多核处理器的移植,无法体现多核处理的优势。我们平常进行网络应用开发,习惯采用BSD socket所提供的一系列TCP/IP协议栈函数,例如调用socket()创建套接字、调用sendto()发送数据、调用connect()进行TCP连接等。如果让开发者自己用NetIO提供的收发包函数完成各类协议之间的通信,无疑是困难而复杂的。因此本课题的主要内容就是编写一套基于该64核开发板的TCP/IP协议栈,通过我们的封装,开发者只需要像平常使用BSD socket函数一样调用本协议栈函数进行开发,而不必关系底层数据包的发送和各类协议之间的通信过程(例如TCP连接的三次握手)。本协议栈主要用来移植IDS test(Intrusion Detection Systems,入侵检测系统测试工具)7、Nmap(一款开源的主机端口扫描工具)8等网络安全测试设备。2Tilera硬件平台2.1 TILExpress-64开发板2.1.1 TILE64多核处理器Tilera公司是位于硅谷的新创无晶圆半导体公司,该公司由麻省理工学院(MIT)教授阿南特阿加瓦尔(Anant Agarwal)在2004年创建。该公司已经量产了TILE 64核处理器,该处理器是建立在一个“网格”架构上的,这种架构英特尔等公司还没有研究成功。90nm工艺的RISC处理器Tile 64,每核主频仅仅在600MHz和1GHz之间,总体功耗不过19.2W,但该芯片的总体性能却是当前英特尔双核Xeon的10倍,每瓦特性能更是高达惊人的30倍。TILE64多核处理器是Tilera公司推出的第一款多核处理器(如图2-1所示),它由64个相同的处理器内核(被称为Tile)组成,每个Tile内核都是一个完整的全功能处理器,拥有自己的一层、二层缓存,并通过内置的无阻塞交换矩阵与其他Tile内核互联,从而形成一个高速的无阻塞的Mesh网络,即Tilera独有的iMesh片上网络。此外,由于Tilera拥有独特的动态分布式缓存技术,所以TILE64处理器上的cache缓存将具有2倍于其他多核处理器的使用性能9。图 2-1 TILE 64处理器硬件结构图10TILE64多核处理器集成了一套完备的内存与I/O控制器11,不再需要借助于传统的南北桥芯片来实现外部数据的接入,从而大大降低了硬件系统的设计复杂度与成本。TILE64多核处理器支持标准的C/C+编程,这意味着大多数的软件或开源代码只要经过简单移植即可在TILE64平台上运行,提供了程序的可重用性12。此外,TILE64多核处理器还支持同时运行多个操作系统实例,既可以每个Tile内核运行一个独立的操作系统,也可以几个Tile内核合起来运行一个支持多进程的操作系统。当然,部分或者全部的Tile内核还可以在完全无操作系统的模式下运行,这非常适合数据平面的大数据量处理,最大限度消除操作系统对性能的消耗13。2.1.2 开发板结构基于Tilera多核处理器的开发板提供了多种规格的标准PCIe14(Peripheral Component Interconnect Express,第三代I/O接口标准)板卡,具有集成度高、处理性能强、接口丰富等特点,同时板卡上配置大容量的内存,可广泛应用在网络、多媒体、无线通信等密集计算领域。Tilera多核处理器开发板是非常理想的高性能计算平台,完全同构并且高速互连的iMeshTM片上网络,支持标准Linux+C/C+的集成开发环境。TILExpress-64开发板便是基于TILE64多核处理器设计、开发的PCIe全场卡,板上集成了2个DDR2插槽和1个CF卡插槽,对外可提供6个(可扩展至12个)GE网口已经1个10GE的CX4接口,板上还提供一个mezzanine扩展槽,可进一步用于扩展I/O接口或协处理卡。同时提供了丰富的网络接口哦,非常适合用作高速网络处理及高性能多媒体的加速卡,开发板的结构如下图2-2所示。图 2-2 TILExpress-64开发板结构图152.2 Tilera系统的软件架构Tilera系统软件与组件提供了一种操作系统结构和用户层的运行时软件(run-time software),从而是应用程序可以在Tile处理器上运行。该Tilera运行时软件包含以下几层,如图2-3所示。图 2-3 Tilera系统的软件架构10(1)、应用层(Applications)。在应用层中,用户的应用程序可以调用linux中的标准函数库,比喻C语言函数库;除此之外,应用层还包括Tilera系统封装的iLib库、NetIO库。(2)、管理层(Supervisor)。管理层为用户的应用程序和函数库提供了系统调用和I/O设备,包括linux内核驱动。在管理层可以通过多进程应用程序和多线程编程来提高性能,操作系统软件管理着硬件资源,提供高层服务,例如虚拟内存分配等。Tilera系统已经开发了体系结构兼容代码来支持Tile处理器体系结构。(3)、高级管理层(Hypervisor)。高级管理层抽象了运行在管理层的Tilera芯片的一些硬件细节,并且管理着内核之间的通信以及内核与I/O控制的通信,同时它还提供了一种底层的虚拟内存系统。每个内核都运行着一个独立的高级管理层实例,如IPP驱动。(4)、硬件(TILE64 hardware)。硬件部分是TILE64多核处理器,芯片上融合了外部存储器和I/O接口,外部存储器和I/O接口经由iMesh网络与内核通信。Tile处理器的每一个内核都是一个独立的32位处理引擎,可以运行一个完整的操作系统。3. TCP/IP协议TCP/IP是Transmission Control Protocol/Internet Protocol的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通信协议,这个协议是Internet最基本的协议、Internet国际互联网络的基础,简单的说,就是由网络层的IP协议和传输层的TCP协议组成的。TCP/IP定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。TCP/IP是一个四层的分层体系结构:应用层、运输层、网际层、接口层。高层为传输控制协议,它负责集聚信息或把文件拆分为更小的包。底层是网际协议,它处理每个包的地址部分,使这些包正确的到达目的地。之所以说TCP/IP是一个协议族,是因为TCP/IP协议包括TCP、IP、UDP、ICMP、RIP、TELNET、FTP、SMTP、ARP、TFTP等许多协议,这些协议一起称为TCP/IP协议。TCP/IP协议族各层协议的划分如下图3-1所示。应用层运输层网际层接口层HTTPPSMTPPDNSRTPTCPUDPIP网络接口1网络接口2网络接口3图 3-1 TCP/IP协议族各层协议的划分16(1)、应用层(Application Layer)。应用层是体系结构中的最高层。应用层直接为用户的应用进程提供服务。这里的进程就是指正在运行的程序。在因特网中的应用层协议很多,如支持万维网应用的HTTP协议,支持电子邮件的SMTP协议,支持文件传送的FTP协议等等。(2)、运输层(Transport Layer)。运输层的任务就是负责向两个主机中进程之间的通信提供服务。由于一个主机可同时运行多个进程,因此运输层有复用和分用的功能。复用就是多个应用层可以同时使用下面运输层的服务,分用则是指运输层把收到的信息分别交给上面应用层中的相应的进程。运输层主要使用以下两种协议:、传输控制协议TCP(Transmission Control Protocol)面向连接的,数据传输的单位是报文段,能够提供高可靠性的数据通信。、用户数据报协议UDP(User Datagram Protocol)无连接的,数据传输的单位是用户数据报,它不保证提供可靠的数据通信,只能提供“尽最大努力交付(best-effort delivery)”。(3)、网际层(Internet Layer)。网际层有时也称为网络层。网络层负责为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把传输层产生的报文段或用户数据报封装成为分组或包进行传送。在TCP/IP协议族中,网络层协议包含IP协议(网际协议),ICMP协议(Internet互联网控制报文协议),以及IGMP协议(Internet组管理协议)。(4)、接口层(Data Link Layer)。接口层有时也称为链路层。通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。它们一起处理与电缆的物理接口细节17。普通的操作系统平台可以完整的支持TCP/IP协议族,但嵌入式系统中大多很难做到,也不需要做到。嵌入式系统中实现的协议要根据各个系统的特点以及功能来进行设计18。我们64核硬件平台下的TCP/IP协议栈中支持5类协议:ARP协议、IP协议、TCP协议、UDP协议以及DNS协议。3.1 地址解析协议ARP协议ARP协议是某些网络接口(如以太网和令牌环网)使用的特殊协议,用来为IP地址到对应的硬件地址之间提供动态映射。在ARP背后有一个基本概念,那就是网络接口有一个硬件地址(一个48bit的值,用来标识不同的以太网或令牌环网络接口)。在硬件层次上进行的数据帧交换必须有正确的接口地址。但是,TCP/IP有自己的地址:32bit的IP地址。知道主机的IP地址并不能让内核发送一桢数据给主机。内核(如以太网驱动程序)必须知道目地端的硬件地址才能发送数据。ARP的功能是在32bit的IP地址和采用不同网络技术的硬件地址之间提供动态映射16。在本协议栈中,发包函数sendto和send需要根据协议类型组装好MAC包,然后调用NetIO发送出去,这时候,就需要使用ARP协议获取目的主机IP地址所对应的MAC地址来填充MAC头部信息。同时,如果接受数据的时候收到了APR请求包,还要回复给对方一个ARP应答包。3.2 网际协议IP协议IP是TCP/IP协议族中最为核心的协议。所有的TCP、UDP、ICMP及IGMP数据都以IP数据报格式传输,IP提供不可靠、无连接的数据报传送服务,网络在发送分组时不需要先建立连接。每一个分组(也就是IP数据报)都独立发送,与其前后的分组无关(不进行编号)16。IP协议还涉及路由选择和子网编址,但是本协议栈中,对于非直连的目的主机,我们简单的将数据包交给默认网关,由网关来进行路由选择。3.3 传输控制协议TCP协议尽管TCP协议使用的是不可靠的IP服务,但是TCP却能提供可靠的数据传输服务。TCP协议是一个面向连接的协议,无论哪一方向另外一方发送数据之前,都必须先在双方之间建立一条连接。通过TCP连接传送的数据,无差错、不丢失、不重复,并且按序到达。TCP还允许通信双方的应用程序在任何时候都能发送数据。TCP是面向字节流的,这里的“流”(stream)指的是流入到进程或从进程流出的字节序列。“面向字节流”的含义是:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序叫下来的数据看成仅仅是一连串的无结构的字节流。TCP并不知道所传送的字节流的含义。TCP不保证接收方应用程序所接受的数据块和发送方应用程序所发送的数据块具有对应的大小关系。但接收方应用程序接收到的字节流必须和发送方应用程序发出的字节流完全一样。当然,接收方的应用程序必须有能力识别到收到的字节流,把它还原成为有意义的应用层数据16。在本课题的协议栈中,我们采用了有限状态机的概念来实现了TCP建立连接的三次握手、数据的传输以及连接释放。并且实现了超时重传、回复确认等错误处理机制来实现TCP的可靠传输。3.4 用户数据报协议UDP协议UDP在传送数据前不需要先建立连接。远程主机的运输层在收到UDP数据报文后,不需要给出任何确认。用户数据报协议UDP只在IP的数据报服务之上增加了很少的一点功能,这就是复用和分用的功能以及差错检查的功能。由于UDP不保证可靠交付,因此主机不需要维持复杂的连接状态。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。UDP对应用层交下来的报文,既不合并,也不拆分。因此,UDP的首部开销小,发送数据之前的时延也小16。尽管UDP是不可靠交付,但是在一般的嵌入式系统中,已经基本能满足要求,应用程序可以在不影响应用的实时性的前提下,对UDP的数据传输进行适当的改进,以减少数据的丢失。在我们的协议栈中,会对UDP首部中端口号以及校验和进行验证,以提高可靠性。3.5 域名系统DNS协议DNS是因特网使用的命令系统,用来把便于人们使用的机器名字转化为IP地址。用户在与因特网上某个主机通信时,显然不愿意使用很难记忆的长达32bit的二进制主机地址。即使是点分十进制的IP地址也并不太容易记忆。相反大家更愿意使用比较容易记忆的主机名字。DNS地址解析通常是由域名服务器完成的,根据域名服务器所起的作用,可以把域名服务器划分为四种不同的类型:根域名服务器、顶级域名服务器、权限域名服务器和本地域名服务器。此外,主机向本地域名服务器的查询一般都是采用递归查询,而本地域名服务器向根域名服务器的查询通常是采用迭代查询16。DNS协议和ARP协议有些类似,尽管其查询方式多样,域名服务器也具有很多的划分类型,但是我们在协议栈的实现过程中,仅仅模仿了ARP请求,向本地域名服务器发送DNS查询报文,然后接受其返回的查询结果。同时,我们DNS协议的实现,并没有像其它的协议或函数一样,直接调用NetIO进行收发包,而是调用了我们自己编写的类socket函数进行数据的发送和接受,这也从侧面验证了我们编写的协议栈的有效性。4. 协议栈的设计与实现4.1 协议栈本质及其分层架构通常我们说到协议栈,指的是为了进行网络数据包的收发,由内核实现的一套函数集合19。所谓栈,即一层层的结构,这仅仅是软件上的一种说法。我们不好从字面上对网络栈作出解释,但可从其实现的功能上进行解释:网络栈按照预先设置的一套规则对用户数据进行封装,从而达到网络上主机之间数据交换的目的。从定义上看,网络协议栈是由一套网络协议构成,而网络协议本质上就是规则:为了实现网络上两台独立主机之间交换信息,必须遵循一套数据封装格式,这是网络逐渐进入应用后的必要步骤。对于规则的设置,我们可以“一步到位”,直接给出数据帧的封装格式,每个发送主机在将数据发送到网络介质上之前,首先必须将数据封装成规定的数据帧格式,这样接收端接收到数据后,按照预先规定的格式进行数据解释,从而完成数据的接收。但是这种一步到位的封装格式也给后期扩张带来了极大的困难,如果有一种新的更有效的封装格式,则为了支持这种新的封装格式,网络上所有的主机必须重新设计网络协议栈。为了支持动态扩展,以及传输数据之外的服务(如QoS服务),数据的封装格式必须是可扩展的,可动态支持新的规则的加入。所以协议栈规则的设计在起初就设计为分层结构,数据在每层上都进行本层的封装,之后传递给下一层。这种分层设计方式首先对在不同层次上加入新的服务提供了可扩展性,其次使得数据封装格式的设计具有了很大的灵活性:我们可以独立于其他层专门对某个层次技能型规则的创建(也就是我们通常所说的协议),这种分层独立设计规则的方式也为后期加入新的规则提供了方便。按照这种分层设计思想,早期的协议栈被分为4层,从上到下依次命名为应用层、传输层、网络层、链路层,而且在每个层次都定义了数据的封装方式(应用层的封装格式完全由用户定制)。单从协议栈定义来看,各层之间除了预先定义好的一些接口函数进行交互外,应不存在其他的耦合关系,但基本上所有的网络协议栈都无法实现做到这一点,各层之间或多或少的具有某种紧密的关联,但从总体上看来,实现大多都遵循着协议栈的分层结构。这就是协议栈的本质特点及其分层架构。在TILExpress-64开发板平台下,我们不需要支持完整的TCP/IP协议族,因此我们并没有完全按照四层的网络栈分层结构进行设计,而是根据不同的情况,选择某几层进行合并,并没有全部体现出完整的四层结构。根据TILE64系统开发需要,我们编写了14个套接字接口函数:socket、close、bind、setsockopt、getsockopt、ioctl、fcntl、select、connect、send、sendto、recv、recvfrom、gethostbyname。4.2 打开和关闭套接字socket、close函数4.2.1 socket函数(1)函数原型int socket ( int domain , int type , int protocol )在网络通信中,为了区分在同一台主机上的不同应用程序和连接,我们提供了套接字的接口。套接字是支持TCP/IP通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端面点,简单来说就是通信双方的一种约定,用套接字中的相关函数来完成通信过程。因此,在进行网络通信前,我们都需要先打开一个套接字,这时候就需要通过调用socket函数来创建。在函数参数中,domain是地址族,指定使用哪一种地址类型,我们支持2种类型的地址族:AF_INET(IPv4网络协议)和AF_PACKET(初级包接口);type是套接字类型,我们支持3种类型的套接字类型:SOCK_RAW(原始套接字)、SOCK_STREAM(流套接字)和SOCK_DGRAM(数据报套接字);protocol是协议类型,一般来说通过前面2个参数就可以确定协议类型,故改参数可以设置为0,但是在一些特殊情况下,协议类型不止一种的时候,可以指定通信所使用的协议编号。(2)套接字表socket函数为套接字分配一块存储空间,下面是套接字结构体的定义,用来存储套接字的所有信息。struct GK_socket/套接字表int domain;/协议族int type;/套接字类型int protocol;/协议类型short err;/错误信息unsigned int seq;/序列号unsigned int ack;/确认号unsigned long IPaddr;/IP地址unsigned char MACaddrETH_ADDR_LEN;/MAC地址unsigned long Netmask;/子网掩码unsigned long gateIP;/网关IPchar nameIFNAMSIZ;/别名unsigned short port;/端口号unsigned char ip_ttl;/生存时间socket_state state;/socket状态unsigned char tcp_state;/TCP状态int block_flag;/阻塞状态标记int broadcast_flag;/是否允许发送广播包struct linger lg;/是否延迟关闭struct GK_sk_filter *filter;/过滤规则unsigned long dstIP;/目的IP地址unsigned char dstMACETH_ADDR_LEN;/目地MAC地址unsigned short dstport;/目地端口struct GK_queue queue;/数据缓冲区;我们定义了一个类型为GK_socket的结构体指针数组socketTable套接字表,大小是256,这也是linux内核中所允许的最大套接字数目。初始的时候,socketTable的元素都指向NULL,socket函数从下标0开始依次遍历整个数组,直到找到第一个指向NULL的元素,并为该元素申请空间,记录传入的三个参数:地址族、套接字类型和协议类型,如果协议类型的值为0,就根据地址族和套接字类型为其找到匹配的协议,如AF_INET和SOCK_STREAM套接字类型,就可以决定协议类型是IPPROTO_TCP(TCP协议),AF_INET和SOCK_DGRAM套接字类型,就可以决定协议类型是IPPROTO_UDP(UDP协议)等等。这里下标从0开始遍历,保证了我们分配的套接字下标总是最小未使用17。在linux的协议栈中,也是按照如此的方式进行处理。事实上,为了提高效率和安全考虑,我们创建套接字后并不是返回套接字表项的地址,而是返回该表项对应的数组下标值,套接字表只能允许协议栈函数对其进行处理和访问,其他任何函数都没有权限对其访问。因此,变量socketTable是一个静态的全局变量,仅能在本协议栈代码中使用它,后面所有提到的全局变量,如果没有特殊说明,也都是静态的。(3)隐式绑定和显示绑定我们知道,要想进行网络通信,首先要有一个网络接口与网络连接,然后才能通过该网络接口进行传送和接收数据。因此,在创建套接字的时候,我们也要为其指定一个网络接口。有两种方法可以指定:一是显示的调用bind函数,为套接字绑定一个地址,这在后面介绍bind函数时,我们再详细解释;二是由协议栈隐式的绑定一个地址,在发送数据前,如果没有显示的绑定接口地址,协议栈将会根据本机的网络接口情况,自动给套接字绑定一个地址。因此,为了方便起见,我们在创建套接字后,就立即为其绑定一个默认的网络接口,并随机为其分配一个端口号。(4)网络接口表这里绑定,包括给套接字分配:IP地址、MAC地址、子网掩码、网关地址以及接口设备名。但是我们的TILExpress-64硬件平台,只提供了6个RJ45口(并不是6个网卡)以及NetIO库对以太网原始数据进行收发包,我们没有所谓的网络接口。因此,这里我们将NetIO和6个RJ45口看做是数据链路层,它们为网络层提供支持。尽管6个RJ45口没有IP地址、MAC地址等硬件信息,但是在协议栈中我们可以为他们定义信息,在传送和接收数据包的时候,根据该信息进行处理,达到模拟出6个网卡的功能。因此,我们定义了一个类型为GK_interface的结构体数组interface网络接口表,如下:struct GK_interface/网络接口unsigned long IPaddr;/IP地址unsigned char MACaddrETH_ADDR_LEN;/MAC地址unsigned long Netmask;/子网掩码unsigned long gateIP;/网关IPchar ifrn_nameIFNAMSIZ;/接口名称int family;/协议族int hwtype;/硬件类型int ifru_flags;/接口标记;这样每个RJ45口,我们都为其分配了一个网卡所必须的硬件信息。函数socket在打开一个套接字后,会选择一个默认的接口进行绑定。我们协议栈实现的关键基础就在于我们自己维护了套接字表和网络接口表。其它任何函数的实现,都离不开对这两张表的操作和访问。4.2.2 close函数函数原型int close ( int fd )close函数用来关闭一个套接字,其实现比较简单,只需对传入的套接字进行合法性判断,然后释放其对应的套接字表项的内存空间即可。4.3 绑定本地地址bind函数函数原型int bind ( int fd , const struct sockaddr * addr , int addrlen )第2参数addr传入了一个IP地址和端口号,bind函数首先通过IP地址找到在网络接口表中相匹配的网络接口,然后将其硬件信息保存到套接字表现中。尽管在打开套接字的时候,我们已经给套接字隐式绑定了一个默认的接口,但是这里仍然以显示绑定的接口为准。在显示绑定的时候,传入的IP地址必须是6个网络接口中某一个接口的IP地址,否则的话,bind函数将返回错误信息,绑定失败。关于错误信息的作用和实现我们将在后面讲述getsockopt获取套接字选项时再详细描述。在前面讲述socket函数的时候,我们说支持2种地址族类型:AF_INET和AF_PACKET,因此我们这里在进行地址绑定的时候,也必须支持这两种地址族类型。AF_INET对应是IPv4的地址,其结构体是struct sockaddr_in;AF_PACKET是初级包接口,其结构体是struct sockaddr_ll。在调用bind函数时,无论那种地址族类型,都必须强制类型转换为struct sockaddr,参数三addrlen指示结构体的长度。这三种结构体的第一个数据成员都是一个16位的数值,用来标识地址族类型,因此我们可以通过该成员来判断sockaddr是由哪一种地址结构体转化而来。三种地址结构体定义如下:struct sockaddr unsigned short sa_family;/* address family, AF_xxx*/charsa_data14;/* 14 bytes of protocol address*/;struct sockaddr_in short intsin_family;/* Address family*/unsigned short int sin_port;/* Port number*/struct in_addr sin_addr;/* Internet address*/unsigned char sin_zero8; /* Same size as struct sockaddr */;struct sockaddr_llunsigned short sll_family;/* Address family*/unsigned short sll_protocol;/* Protocol*/intsll_ifindex;/* Interface index*/unsigned short sll_hatype;/* Hardware type*/unsigned char sll_pkttype;/* Packet type*/unsigned char sll_halen;/* Hardware type length*/unsigned char sll_addr8;/* Address*/;4.4 设置和获取套接字选项setsockopt、getsockopt函数4.4.1 setsockopt函数(1)函数原型int setsockopt ( int fd , int level , int optname , unsigned char * optval , int optlen )顾名思义,setsockopt就是对套接字的一些信息选项进行设置,其命令格式是由2级操作来定义的:参数二level指定欲设置的网络层,参数三optname表示该层欲设置的选项。该函数大多是对套接字进行一些基本设置,包括是否允许发送广播包、是否延迟关闭、设置生存时间、设置发送或接收缓冲区大小以及设置IP选项等信息。(2)BPF过滤器在setsockopt中有设置对接收数据包的过滤机制。通过参数二SOL_SOCKET对套接字层进行设置,参数三为SO_ATTACH_FILTER时表示加载过滤器,为SO_DETACH_FILTER表示卸载过滤器。在设置加载过滤器的时候,参数四optval指向的是一个struct sock_fprog的结构体。struct sock_fprog/* Required for SO_ATTACH_FILTER.*/unsigned short len;/* Number of filter blocks */struct sock_filter *filter;/* Filter pointer*/;struct sock_filter/* Filter block */_u16 code; /* Actual filter code */_u8 jt;/* Jump true */_u8 jf;/* Jump false */_u32 k; /* Generic multiuse field */;在我们加载过滤器的时候,setsockopt只是仅仅对struct sock_fprog结构体中的过滤规则结构体struct sock_filter的内容进行检查,以免出现不合法的过滤规则字段,然后就将该过滤规则保存在对应的套接字表项中。NetIO在收到数据包后,如果发现对应套接字有设置过滤规则,就对数据包进行规则过滤,这个在后面的接受数据包函数recvfrom和recv中也会讲到。BPF即Berkeley Packet Filter,是类Unix系统上数据链路层的一种原始接口,提供对原始链路层数据包的过滤。BPF会把“感兴趣”的数据包交给上层,从而避免从内核向用户态复制数据包,降低收包的CPU负担以及所需的缓冲区空间,从而减少丢包率。BPF的过滤功能是以BPF虚拟机机器语言的解释器的形式实现的,这种语言的程序对原始数据包采取算术操作,并将结果与常量或数据包中的数据或结果中的测试位进行比较,根据比较的结果决定接受还是拒绝该数据包20。总之,BPF过滤器是基于内核的一个代理进程,是用来丢弃那些不需要的数据包,接受需要的数据包21。为此,我们仿照linux2.4协议栈源代码(在linux1.2的版本中,没有BPF过滤机制)实现了以下三个函数:int sk_attach_filter ( struct sock_fprog * fprog , struct GK_socket * st )int sk_chk_filter ( struct sock_filter * filter , int flen )int sk_run_filter ( struct GK_sk_buff * skb , struct sock_filter * filter , int flen )第一个函数就是将setsockopt传入的参数四optval所指向的struct sock_fprog中的过滤器加载到套接字表项st中。第二个函数就是对struct sock_filter结构体中的过滤规则进行合法性检查。第三个函数是执行过滤器,用过滤规则filter对数据包skb进行过滤检查,判断该数据包是不是符合过滤规范。结构体struct GK_sk_buff是仿照linux内核结构体struct sk_buff编写的,这里我们只用到了其数据域指针和数据长度。定义如下:struct GK_sk_buffunsigned int len;/* Data length*/unsigned char *data;/* Data head pointer*/;4.4.2 getsockopt函数(1)函数原型int getsockopt ( int fd , int level , int optname , unsigned char * optval , int optlen )有设置套接字,就有获取套接字,这一点在后面介绍的函数ioctl和fcntl中也会有所体现,只是它们并没有分离出2个函数来,只是在参数中分为设置和获取。实际上,setsockopt、getsockopt、ioctl、fcntl这是个函数都是对套接字的选项或信息进行管理的。(2)协议栈中的错误机制getsockopt的功能大多是跟setsockopt相反,不同之处在于它没有过滤器的操作。在getsockopt中,有一个获取套接字错误信息的命令参数组合:参数二level为SOL_SOCKET,参数三optname为SO_ERROR。通过该命令,getsockopt函数将返回套接字的错误信息,这时候,参数四optval是一个指向整数的指针,错误信息编号就被赋值给optval。除了套接字表项中记录错误信息外,协议栈中还有一个全局变量errno也记录着错误信息。两者的区别在于:、 getsockopt返回的是针对某一个套接字在操作时所发生的错误信息,通过参数一fd标识;errno记录是整个协议栈在执行的过程中,所发生的错误信息,并不是针对某一特定的套接字。、 getsockopt返回的是该套接字在最近一次函数操作所发生的错误信息,之后该错误值将被置为0;errno记录的是最近一次协议栈所发生的错误信息,并且不会被置为0,直到下一次发生错误时,其值才会被改变。因此,我们在编写协议栈的时候,发生错误信息时,都要将错误信息编号赋值给2个变量:套接字表项中的错误信息和errno。我们说,大多数情况下,它们记录的错误信息是相同的。错误信息编号与错误信息的对应关系在内核中已经定义成宏定义,我们只需要记录编号即可。4.5 管理网络接口ioctl函数函数原型int ioctl ( int fd , int request , unsigned char * arg )ioctl主要是用来对网络接口进行管理,根据参数二request的不同取值,参数三arg传入的结构体类型也不一样,其功能包括:获取网络接口清单,获取和设置接口IP地址、MAC地址、标记、索引号、子网掩码等。如果说getsockopt和setsockopt是对套接字表中的表项进行处理,那么ioctl就是对网络接口表中的表项进行处理。在网络应用中,对于初学者来说,ioctl可能比较陌生,平常也使用得比较少。但是通过查看ioctl函数的实现代码,你就发现该函数非常简单,只是我们平时的编程只是在套接字层面上进行处理,很少涉及到底层网络接口。4.6 管理套接字fcntl函数函数原型int fcntl ( int fd , int cmd , int flag ) 作为函数ioctl(I/O control)的孪生兄弟,函数fcntl(file contorl)连参数列表都跟ioctl很相似,参数二cmd是命令,参数三flag传入参数。在协议栈中,fcntl用来设置和获取套接字的阻塞标记。关于协议栈的阻塞和非阻塞模式,我们将在后面若干与数据包相关的函数中一一描述。到此为止,我们所实现的几个函数,都是对套接字表和网络接口表上信息的获取和设置,其实现起来都比较简单。下面,我们开始讲述我们是怎么实现那些跟协议和数据包联系紧密的函数。4.7 监视套接字select函数函数原型int select ( int maxfd , fd_set * readset , fd_set * writeset , fd_set * exceptset , struct timeval * timeout )select函数在协议栈中是一个非常重要的函数,如果

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论