目前有許多不同的成熟的TCP/IP協(xié)議的實現(xiàn)版本,其中大部分都在操作系統(tǒng)的核心實現(xiàn),這種方案固然是提高TCP/IP協(xié)議軟件的效率的必然所選,但卻給TCP/IP協(xié)議的學習、研究和調試帶來了很大的困難。于是,如果不考慮TCP/IP協(xié)議軟件實現(xiàn)的效率問題,在應用進程中實現(xiàn)一個TCP/IP協(xié)議軟件,是具有一定的意義和價值的。
本文作者構造了一個單進程的TCP/IP協(xié)議軟件:minitcpip,并提供了一個SOCKET接口函數(shù)庫:minisocket。在實現(xiàn)這個協(xié)議軟件函數(shù)庫時,作者選擇采用了libnet+libpcap的方式在用戶態(tài)下實現(xiàn)這個軟件,不僅是因為這樣可以避開一些操作系統(tǒng)對底層網(wǎng)絡開發(fā)的種種限制帶來的不便,將精力集中在對協(xié)議軟件本身的理解上;另外一個原因,則是為大家學習和綜合使用libnet和libpcap提供一個范例。
下文首先介紹了libnet和libpcap函數(shù)庫及其使用,并給出了一個利用其實現(xiàn)ARP協(xié)議的例程--該協(xié)議的實現(xiàn)也包括在minitcpip軟件之中,然后給出了本文的協(xié)議軟件和SOCKET函數(shù)庫實現(xiàn)的方案,并圍繞本文主題,對涉及到的一些關鍵技術問題進行了分析,最后,對這種實現(xiàn)方法做了一個簡單的總結,指出了這種實現(xiàn)方法的一些局限。
目前眾多的網(wǎng)絡安全程序、工具和軟件都是基于socket設計和開發(fā)的。由于在安全程序中通常需要對網(wǎng)絡通訊的細節(jié)(如連接雙方地址/端口、服務類型、傳輸控制等)進行檢查、處理或控制,象數(shù)據(jù)包截獲、數(shù)據(jù)包頭分析、數(shù)據(jù)包重寫、甚至截斷連接等,都幾乎在每個網(wǎng)絡安全程序中必須實現(xiàn)。為了簡化網(wǎng)絡安全程序的編寫過程,提高網(wǎng)絡安全程序的性能和健壯性,同時使代碼更易重用與移植,最好的方法就是將最常用和最繁復的過程函數(shù),如監(jiān)聽套接口的打開/關閉、數(shù)據(jù)包截獲、數(shù)據(jù)包構造/發(fā)送/接收等,封裝起來,以API library的方式提供給開發(fā)人員使用。
在眾多的API library中,對于類Unix系統(tǒng)平臺上的網(wǎng)絡安全工具開發(fā)而言,目前最為流行的C API library有l(wèi)ibnet、libpcap、libnids和libicmp等。它們分別從不同層次和角度提供了不同的功能函數(shù)。使網(wǎng)絡開發(fā)人員能夠忽略網(wǎng)絡底層細節(jié)的實現(xiàn),從而專注于程序本身具體功能的設計與開發(fā)。其中,
libnet提供的接口函數(shù)主要實現(xiàn)和封裝了數(shù)據(jù)包的構造和發(fā)送過程。
libpcap提供的接口函數(shù)主要實現(xiàn)和封裝了與數(shù)據(jù)包截獲有關的過程。
利用這些C函數(shù)庫的接口,網(wǎng)絡安全工具開發(fā)人員可以很方便地編寫出具有結構化強、健壯性好、可移植性高等特點的程序。因此,這些函數(shù)庫在網(wǎng)絡安全工具的開發(fā)中具有很大的價值,在scanner、sniffer、firewall、IDS等領域都獲得了極其廣泛的應用,著名的tcpdump軟件、ethereal軟件等就是在libpcap的基礎上開發(fā)的。
另外也應該指出:由于其功能強大,這些函數(shù)庫也被黑客用來構造TCP/IP網(wǎng)絡程序對目標主機進行攻擊。然而,TCP/IP網(wǎng)絡的安全不可能也不應該建立在禁止大家使用工具的基礎上,一個理想的網(wǎng)絡,首先必須是一個開放的網(wǎng)絡,這個網(wǎng)絡應該在使用任何工具的情況下都是安全的和健壯的。從這點考慮,這些工具的使用,對促進現(xiàn)有網(wǎng)絡系統(tǒng)的不斷完善是大有裨益的。
libnet是一個小型的接口函數(shù)庫,主要用C語言寫成,提供了低層網(wǎng)絡數(shù)據(jù)報的構造、處理和發(fā)送功能。libnet的開發(fā)目的是:建立一個簡單統(tǒng)一的網(wǎng)絡編程接口以屏蔽不同操作系統(tǒng)低層網(wǎng)絡編程的差別,使得程序員將精力集中在解決關鍵問題上。他的主要特點是:
高層接口:libnet主要用C語言寫成
可移植性:libnet目前可以在Linux、FreeBSD、Solaris、WindowsNT等操作系統(tǒng)上運行,并且提供了統(tǒng)一的接口
數(shù)據(jù)報構造:libnet提供了一系列的TCP/IP數(shù)據(jù)報文的構造函數(shù)以方便用戶使用
數(shù)據(jù)報的處理:libnet提供了一系列的輔助函數(shù),利用這些輔助函數(shù),幫助用戶簡化那些煩瑣的事務性的編程工作
數(shù)據(jù)報發(fā)送:libnet允許用戶在兩種不同的數(shù)據(jù)報發(fā)送方法中選擇。
另外libnet允許程序獲得對數(shù)據(jù)報的絕對的控制,其中一些是傳統(tǒng)的網(wǎng)絡程序接口所不提供的。這也是libnet的魅力之一。
libnet支持TCP/IP協(xié)議族中的多種協(xié)議,比如其上一個版本libnet1.0支持了10種協(xié)議,一些新的協(xié)議,比如對IPV6的支持還在開發(fā)之中。
libnet目前最新的版本是1.1版本,在該版本中,作者將這些函數(shù)做了進一步的封裝,用戶的使用步驟也得到了進一步的簡化。內存的初始化、管理、釋放等以及校驗和的計算等函數(shù),在默認情況下,都無須用戶直接干預,使得libnet的使用更為方便。作者還提供了基于老版本的應用程序移植到新版本上的方法指導。利用libnet1.1函數(shù)庫開發(fā)應用程序的基本步驟以及幾個關鍵的函數(shù)使用方法簡介如下:
libnet_t *libnet_init(int injection_type, char *device, char *err_buf); |
該函數(shù)初始化libnet函數(shù)庫,返回一個libnet_t類型的描述符,以備隨后的構造數(shù)據(jù)報和發(fā)送數(shù)據(jù)報的函數(shù)中使用。injection_type指明了發(fā)送數(shù)據(jù)報使用的接口類型,如數(shù)據(jù)鏈路層或者原始套接字等。Device是一個網(wǎng)絡設備名稱的字符串,在Linux下是"eth0"等。如果函數(shù)錯誤,返回NULL,而err_buf字符串中將攜帶有錯誤的原因。
libnet提供了豐富的數(shù)據(jù)報的構造函數(shù),可以構造TCP/IP協(xié)議族中大多數(shù)協(xié)議的報文,還提供了一些對某些參數(shù)取默認數(shù)值的更簡練的構造函數(shù)供用戶選擇。比如libnet_autobuild_ipv4()等。
int libnet_write(libnet_t *l); |
該函數(shù)將l中描述的數(shù)據(jù)報發(fā)送的網(wǎng)絡上。成功將返回發(fā)送的字節(jié)數(shù),如果失敗,返回-1。你可以調用libnet_geterror()得到錯誤的原因
void libnet_destroy(libnet_t *l); |
libpcap的英文意思是 Packet Capture library,即數(shù)據(jù)包捕獲函數(shù)庫。該庫提供的C函數(shù)接口可用于需要捕獲經(jīng)過網(wǎng)絡接口(通過將網(wǎng)卡設置為混雜模式,可以捕獲所有經(jīng)過該接口的數(shù)據(jù)報,目標地址不一定為本機)數(shù)據(jù)包的系統(tǒng)開發(fā)上。著名的TCPDUMP就是在libpcap的基礎上開發(fā)而成的。libpcap提供的接口函數(shù)主要實現(xiàn)和封裝了與數(shù)據(jù)包截獲有關的過程。這個庫為不同的平臺提供了一致的編程接口,在安裝了libpcap的平臺上,以libpcap為接口寫的程序,能夠自由的跨平臺使用。在Linux系統(tǒng)下,libpcap可以使用BPF(Berkeley Packet Filter)分組捕獲機制來獲得很高的性能。
利用libpcap函數(shù)庫開發(fā)應用程序的基本步驟以及幾個關鍵的函數(shù)使用方法簡介如下:
char *pcap_lookupdev(char *errbuf) |
該函數(shù)用于返回可被pcap_open_live()或pcap_lookupnet()函數(shù)調用的網(wǎng)絡設備名(一個字符串指針)。如果函數(shù)出錯,則返回NULL,同時errbuf中存放相關的錯誤消息。
int pcap_lookupnet(char *device, bpf_u_int32 *netp,bpf_u_int32 *maskp, char *errbuf) |
獲得指定網(wǎng)絡設備的網(wǎng)絡號和掩碼。netp參數(shù)和maskp參數(shù)都是bpf_u_int32指針。如果函數(shù)出錯,則返回-1,同時errbuf中存放相關的錯誤消息。
pcap_t *pcap_open_live(char *device, int snaplen,int promisc, int to_ms,char *ebuf) |
獲得用于捕獲網(wǎng)絡數(shù)據(jù)包的數(shù)據(jù)包捕獲描述字。device參數(shù)為指定打開的網(wǎng)絡設備名。snaplen參數(shù)定義捕獲數(shù)據(jù)的最大字節(jié)數(shù)。promisc指定是否將網(wǎng)絡接口置于混雜模式。to_ms參數(shù)指定超時時間(毫秒)。ebuf參數(shù)則僅在pcap_open_live()函數(shù)出錯返回NULL時用于傳遞錯誤消息。
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask) |
將str參數(shù)指定的字符串編譯到過濾程序中。fp是一個bpf_program結構的指針,在pcap_compile()函數(shù)中被賦值。optimize參數(shù)控制結果代碼的優(yōu)化。netmask參數(shù)指定本地網(wǎng)絡的網(wǎng)絡掩碼。
int pcap_setfilter(pcap_t *p, struct bpf_program *fp) |
指定一個過濾程序。fp參數(shù)是bpf_program結構指針,通常取自pcap_compile()函數(shù)調用。出錯時返回-1;成功時返回0。抓取下一個數(shù)據(jù)包
int pcap_dispatch(pcap_t *p, int cnt,pcap_handler callback, u_char *user) |
捕獲并處理數(shù)據(jù)包。cnt參數(shù)指定函數(shù)返回前所處理數(shù)據(jù)包的最大值。cnt=-1表示在一個緩沖區(qū)中處理所有的數(shù)據(jù)包。cnt=0表示處理所有數(shù)據(jù)包,直到產(chǎn)生以下錯誤之一:讀取到EOF;超時讀取。callback參數(shù)指定一個帶有三個參數(shù)的回調函數(shù),這三個參數(shù)為:一個從pcap_dispatch()函數(shù)傳遞過來的u_char指針,一個pcap_pkthdr結構的指針,和一個數(shù)據(jù)包大小的u_char指針。如果成功則返回讀取到的字節(jié)數(shù)。讀取到EOF時則返回零值。出錯時則返回-1,此時可調用pcap_perror()或pcap_geterr()函數(shù)獲取錯誤消息。
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user) |
功能基本與pcap_dispatch()函數(shù)相同,只不過此函數(shù)在cnt個數(shù)據(jù)包被處理或出現(xiàn)錯誤時才返回,但讀取超時不會返回。而如果為pcap_open_live()函數(shù)指定了一個非零值的超時設置,然后調用pcap_dispatch()函數(shù),則當超時發(fā)生時pcap_dispatch()函數(shù)會返回。cnt參數(shù)為負值時pcap_loop()函數(shù)將始終循環(huán)運行,除非出現(xiàn)錯誤。
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h) |
返回指向下一個數(shù)據(jù)包的u_char指針。
void pcap_close(pcap_t *p) |
關閉p參數(shù)相應的文件,并釋放資源。
FILE *pcap_file(pcap_t *p) |
返回被打開文件的文件名。
int pcap_fileno(pcap_t *p) |
返回被打開文件的文件描述字號碼。
綜合使用libnet和libpcap可以構造強有力的網(wǎng)絡分析、診斷、和應用程序。一個具有普遍意義的綜合使用libnet和libpcap的程序的原理框架如圖1所示:
本節(jié)給出一個綜合應用libnet和libpcap的簡單例程,其功能是在接收到一個來自特定主機的ARP請求報文之后,發(fā)出ARP回應報文,通知該主機請求的IP地址對應的MAC地址。這個程序實現(xiàn)了標準的ARP協(xié)議,但是卻不同于操作系統(tǒng)內核中標準的實現(xiàn)方法:該程序利用了libpcap在數(shù)據(jù)鏈路層抓包,利用了libnet向數(shù)據(jù)鏈路層發(fā)包,是使用libnet和libpcap構造TCP/IP協(xié)議軟件的一個例程。該程序很簡單,但已經(jīng)可以說明libnet和libpcap的綜合使用方法:
/* tell destination host with ip 'dstip' that the host with * request ip 'srcip' is with mac address srcmac * author: white cpf 2003.5.15. * compile: gcc arp.c -lnet -lpcap -o arp */ #include "/usr/include/libnet.h" #include <pcap.h> void usage(char * exename){ printf(" tell dstip with dstmac that srcip is at srcmac. \n"); printf(" usage: %s -d dstip -s srcip -D dstmac -S srcmac \n",exename); return ; } //程序輸入:來自命令行參數(shù) u_char ip_src[4],ip_dst[4]; u_char enet_src[6],enet_dst[6]; extern int mac_strtochar6(u_char * enet,char * macstr); //將字符串格式的MAC地址轉換為6字節(jié)類型r int get_cmdline(int argc,char *argv[]);//命令行參數(shù)處理函數(shù) int main(int argc, char *argv[]){ libnet_t *l; libnet_ptag_t t; u_char *packet; u_long packet_s; char device[5]="eth0"; char errbuf[LIBNET_ERRBUF_SIZE]; char filter_str[100]=""; struct bpf_program fp; /* hold compiled program */ char *dev; pcap_t* descr; struct pcap_pkthdr hdr; /* pcap.h */ u_char * packet; bpf_u_int32 maskp; /* subnet mask */ bpf_u_int32 netp; /* ip */ int promisc=0; /* set to promisc mode? */ int pcap_time_out=5; int c, ret; u_long i; if(get_cmdline(argc,argv)<=0){ usage(argv[0]); exit(0); } dev = pcap_lookupdev(errbuf); if(dev == NULL){ fprintf(stderr,"%s\n",errbuf); return -1; } ret=pcap_lookupnet(dev,&netp,&maskp,errbuf); if(ret==-1){ fprintf(stderr,"%s\n",errbuf); return -1; } descr = pcap_open_live(dev,BUFSIZ,promisc,pcap_time_out,errbuf); if(descr == NULL){ printf("pcap_open_live(): %s\n",errbuf); return -1; } sprintf(filter_str,"arp and (src net %d.%d.%d.%d)",ip_dst[0],ip_dst[1], ip_dst[2],ip_dst[3]); if(pcap_compile(descr,&fp,filter_str,0,netp) == -1){ printf("Error calling pcap_compile\n"); return -1; } if(pcap_setfilter(descr,&fp) == -1){ printf("Error setting filter\n"); return -1; } while(1){ printf("wait packet:filter:%s\n",filter_str); packet=pcap_next(descr, &hdr); if(packet == NULL){ continue; } l = libnet_init(LIBNET_LINK_ADV,device,errbuf); if (l == NULL){ fprintf(stderr, "libnet_init() failed: %s", errbuf); exit(EXIT_FAILURE); } t = libnet_build_arp( ARPHRD_ETHER, /* hardware addr */ ETHERTYPE_IP, /* protocol addr */ 6, /* hardware addr size */ 4, /* protocol addr size */ ARPOP_REPLY, /* operation type */ enet_src, /* sender hardware addr */ ip_src, /* sender protocol addr */ enet_dst, /* target hardware addr */ ip_dst, /* target protocol addr */ NULL, /* payload */ 0, /* payload size */ l, /* libnet handle */ 0); /* libnet id */ if (t == -1){ fprintf(stderr, "Can't build ARP header: %s\n", libnet_geterror(l)); goto bad; } t = libnet_autobuild_ethernet( enet_dst, /* ethernet destination */ ETHERTYPE_ARP, /* protocol type */ l); /* libnet handle */ if (t == -1){ fprintf(stderr, "Can't build ethernet header: %s\n", libnet_geterror(l)); goto bad; } c = libnet_adv_cull_packet(l, &packet, &packet_s); if (c == -1){ fprintf(stderr, "libnet_adv_cull_packet: %s\n", libnet_geterror(l)); goto bad; } c = libnet_write(l); if (c == -1){ fprintf(stderr, "Write error: %s\n", libnet_geterror(l)); goto bad; } continue; bad: libnet_destroy(l); return (EXIT_FAILURE); } libnet_destroy(l); return (EXIT_FAILURE); } int get_cmdline(int argc,char *argv[]){ char c; char string[]="d:s:D:S:h"; while((c = getopt(argc, argv, string)) != EOF){ if(c=='d') *((unsigned int*)ip_dst)=(unsigned int)inet_addr(optarg); else if(c== 's') *((unsigned int*)ip_src)=(unsigned int)inet_addr(optarg); else if(c=='D') mac_strtochar6(enet_dst,optarg); else if(c=='S') mac_strtochar6(enet_dst,optarg); else if(c=='h') return 0; else return -1; } return 1; } |
圖3與圖4是minitcpip協(xié)議軟件系統(tǒng)的框架圖。其中,minitcpip協(xié)議軟件在一個單獨的進程中實現(xiàn)。這個進程作為TCP/IP協(xié)議軟件服務器建立C/S模型向應用程序提供服務。其通訊采用了命名管道建立C/S模型的方式,任何用戶的應用進程對minitcpip的使用必須作為客戶端,通過minisocket函數(shù)庫進行。其通訊模型見圖2。
協(xié)議軟件進程一旦運行,則初始化libnet、libpcap,初始化TCP/IP連接管理表(TCB)以及接收與發(fā)送緩沖區(qū),打開眾所周知的FIFO等操作,然后等待客戶機發(fā)來的命令(通過眾所周知的FIFO)。在收到合法的命令,包括建立連接、發(fā)送數(shù)據(jù)、接收數(shù)據(jù)、關閉連接和設置連接屬性等等之后,就作出相應的分析與處理。比如根據(jù)命令中指定的源IP、目的IP、源端口、目的端口開始監(jiān)控和接收過濾網(wǎng)絡上的數(shù)據(jù)(如下文,實際是監(jiān)控三個文件描述符)等等。
為了方便監(jiān)控和調試協(xié)議軟件內部狀態(tài),協(xié)議軟件同時等待標準輸入設備發(fā)來的命令,協(xié)議軟件將根據(jù)標準輸入的合法命令形成到標準輸出設備的輸出。
除了在周知口等待客戶機的命令、在標準輸入設備等待監(jiān)控命令之外,協(xié)議軟件還必須同時等待來自網(wǎng)絡設備的數(shù)據(jù)。為了在同一個進程中可以高效率的處理這些不同來源的數(shù)據(jù),軟件通過使用libpcap提供的函數(shù)接口int pcap_fileno(pcap_t *p),得到被打開的網(wǎng)絡設備文件的文件描述符。在得到這個描述符之后,就可以和管道文件描述符同等的使用select()函數(shù)進行并行處理了。
在網(wǎng)絡設備文件的文件描述字可讀時,軟件將調用u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)函數(shù)來獲得下一個抓到的數(shù)據(jù)包。
該協(xié)議軟件的原理性的實現(xiàn)代碼如下:
main(){ 初始化libnet、libpcap;分配接收、發(fā)送緩沖區(qū);初始化定時器;等等 while(1){ if(發(fā)送緩沖區(qū)有數(shù)據(jù)){ 發(fā)送數(shù)據(jù) } 調用select()函數(shù)等待3個文件描述符是否準備好,這三個文件描述符分別是: PCAP文件描述符、周知口管道讀文件描述符、標準輸入文件描述符 if(PCAP文件描述符準備好){ 調用pcap_next()函數(shù)來獲得下一個抓到的數(shù)據(jù)包 } if(周知口讀文件描述符準備好){ 讀取數(shù)據(jù) 根據(jù)進程內部的TCB中的信息,按照TCP協(xié)議規(guī)范進行分析處理 } if(標準輸入文件描述符準備好){ 讀取數(shù)據(jù) 分析處理,比如將內部信息回饋到標準輸出文件描述符 } if(超時){ 超時處理 } } } |
如上面的流程所示,在收到網(wǎng)絡上的數(shù)據(jù)包時,即根據(jù)TCP/IP協(xié)議IP、TCP等報文格式進行分析處理,并將接收到的數(shù)據(jù)回傳給客戶端應用程序。在收到周知管道的數(shù)據(jù)包時,則根據(jù)數(shù)據(jù)包的命令類型進行相應的操作,比如對其中的一條命令--SEND命令,在接收到這條命令后,就將其后附的數(shù)據(jù)寫入發(fā)送緩沖區(qū),在隨后的循環(huán)中,這些數(shù)據(jù)將被依次發(fā)送到網(wǎng)絡上去。在周知管道與回送管道上進行的通訊采用了一個自定義的協(xié)議,后文對此作了簡單介紹。
最終系統(tǒng)在運行時的基本框架如下兩圖所示:其中,圖3是系統(tǒng)在運行時的整體結構,圖4是協(xié)議軟件進程內部的結構。
TCP/IP協(xié)議數(shù)據(jù)處理模塊是一組函數(shù),與關鍵數(shù)據(jù)結構TCP表(TCB)等配合,負責實現(xiàn)TCP/IP協(xié)議的功能。
對minitcpip的協(xié)議實現(xiàn)的兩點討論
經(jīng)過多年的發(fā)展,目前廣泛應用的標準TCP/IP軟件已經(jīng)能夠支持以太網(wǎng)、串行鏈路等多種物理設備,本文所討論的實現(xiàn)主要是集中在以太網(wǎng)之上。
下面的討論主要集中在定時器的設置和與操作系統(tǒng)的互斥兩個問題上。
實際應用的TCP/IP協(xié)議軟件是一個非常復雜的系統(tǒng),具有流量控制和擁塞控制機制,一般都是由TCP/IP輸出進程、輸入進程、定時器進程等多個系統(tǒng)進程配合完成。而實現(xiàn)這些機制的一個重要的基礎是定時器的設置與處理。本文中minitcpip由于是在單進程中實現(xiàn),難以實現(xiàn)復雜的精確定時功能,所以在定時器的處理上進行了相當?shù)暮喕?。其中,TCP協(xié)議狀態(tài)機中的TIME_WAIT狀態(tài)定時間隔是一個基礎參數(shù),一旦minitcpip運行之后,這個參數(shù)就不會改變,或者只能通過標準輸入口進行調試目的的人工修改;每一個TCP/IP連接的重發(fā)定時器的策略則是根據(jù)該連接所有的數(shù)據(jù)報的接收效果而建立的,具體實現(xiàn)是設置一個最小時間間隔參數(shù)和一個最大時間間隔參數(shù),系統(tǒng)初始化后,重發(fā)定時間隔取最小值,此后每發(fā)生一次超時(沒有收到該連接的任何數(shù)據(jù)報或者沒有收到具有ACK標志且確認序號正確的數(shù)據(jù)報),就將該重發(fā)定時間隔翻倍,直到達到最大值為止;如果在正確的收到若干數(shù)據(jù)報后都沒有發(fā)生超時,就將重發(fā)定時時間間隔減半,直到達到最小數(shù)值。系統(tǒng)通過重發(fā)定時間隔與系統(tǒng)當前時鐘減去保存的上次發(fā)送的時鐘值的比較結果得出是否應該重新發(fā)送分組。而select()函數(shù)所使用的溢出時間則取所有連接的定時間隔的最小數(shù)值,以保證及時的發(fā)送分組。這樣的策略當然效率不高,但是已經(jīng)可以保證協(xié)議軟件的正確運行。
另外,minitcpip協(xié)議軟件是在本機同時存在一個標準TCP/IP協(xié)議棧的情況下實現(xiàn)的,BPF的原理是復制而不是截獲本機收到的數(shù)據(jù)報文。因此,操作系統(tǒng)也會對收到的報文進行處理,這個問題如果處理不好,就會存在一些與操作系統(tǒng)內部的TCP/IP軟件相互干擾的問題。比如在源主機上,如果指定的源端口已經(jīng)打開,則勢必要影響本來正常運行的程序,最后導致二者都不能正常工作。同時,在使用minitcpip與遠端目標主機上的標準TCP/IP協(xié)議軟件建立連接的時候,本機的操作系統(tǒng)也會收到目標主機的報文,并且發(fā)現(xiàn)這個報文指定的IP地址和端口并不在打開的端口表項中,于是操作系統(tǒng)認為收到了一個不合法的報文。許多操作系統(tǒng)對這一事件的反應是發(fā)送一個帶有RST標志的報文出去,從而導致目標主機的連接復位。
這個問題有幾種可能的解決辦法:
minitcpip使用了其中第三種解決辦法,網(wǎng)卡必須設置在混雜模式。這樣在網(wǎng)絡負載大的場合,CPU占用率會比較高,丟包的概率會增加,效率會下降。因此minitcpip只適用于小負載的場合。
minitcpip向客戶端提供的服務采用了C/S模型,這個服務的模型圖如前圖2所示,關于其細節(jié)請參見《UNIX環(huán)境高級編程》。任何用戶的應用進程對minitcpip的使用必須通過minisocket函數(shù)庫進行,而minisocket與minitcpip之間則通過一個自定義的私有協(xié)議進行。這個私有協(xié)議實現(xiàn)在用戶進程與協(xié)議軟件進程之間利用命名管道建立的C/S通訊中。
命名管道已經(jīng)是可靠的本地進程間通訊機制,然而minisocket與minitcpip協(xié)議軟件之間傳遞的不僅僅是發(fā)送與接收的字節(jié)流,還包括對SOCKET的控制,比如申請SOCKET號,建立TCP連接、關閉TCP連接等控制命令,所以必須設計一個簡單的通訊協(xié)議來滿足具體的通訊要求。詳細的通訊協(xié)議的規(guī)格說明這里不再列出。
管道通訊一般不會發(fā)生數(shù)據(jù)包的丟失和錯序問題,所以該協(xié)議實質上只需要負責包的撿出(利用轉義和數(shù)據(jù)長度和校驗和)和分辨不同種類的數(shù)據(jù)包(信息包、控制包)。若管道操作本身出現(xiàn)錯誤,則程序異常退出。下圖5是這個私有協(xié)議使用的數(shù)據(jù)報格式:具體的數(shù)據(jù)包的檢出與字符填充方案等由于與本文主題關系不大,這里不再詳述。
其中,數(shù)據(jù)域的第一個字節(jié)總是命令類型,顧名思義,命令類型定義了應用程序與協(xié)議軟件之間所有的通訊數(shù)據(jù)的類型,主要包括兩類命令,即控制命令和信息傳輸命令,控制命令用來控制TCP連接的狀態(tài)等,信息命令則是客戶機真正要傳輸?shù)臄?shù)據(jù)或服務器收到的回發(fā)給客戶機的數(shù)據(jù)。具體的命令格式列表不再詳述。
客戶機通過調用minisocket接口與minitcpip通訊,這個接口封裝了私有協(xié)議繁瑣的細節(jié),提供了類似與標準SOCKET接口的簡單統(tǒng)一的函數(shù)庫給用戶使用。目前實現(xiàn)的minisocket接口函數(shù)庫還相當簡單,功能也很有限,只包括TCP協(xié)議客戶端的實現(xiàn)。在編寫客戶端應用程序時,用戶只需要知道少數(shù)幾個數(shù)據(jù)結構和函數(shù)原型就可以了:
struct mini_sock{ u8 type;//=TCP or UDP u32 dip;//destination ip u32 dport;//destination port u16 sip;//source ip u16 sport;//source port …… } |
該數(shù)據(jù)結構定義在mini_socket.h中,包含了用戶需要建立的TCP連接的所有參數(shù)
用戶函數(shù)接口定義:
int mini_socket(struct mini_sock*) |
該函數(shù)創(chuàng)建一個SOCKET,struct socket*中必須正確填寫欲創(chuàng)建的SOCKET有關的參數(shù)。
成功的返回值實際上是協(xié)議軟件寫入數(shù)據(jù)的客戶機專用FIFO文件描述符。這樣用戶可以使用標準的select()函數(shù)實現(xiàn)單進程的多個輸入輸出的處理。但是在這個文件描述符上的讀寫操作必須使用minisocket提供的函數(shù)才能正確進行。
int mini_connect(int socket) 該函數(shù)用來通知協(xié)議軟件通過與目標主機三次握手后,建立TCP連接 int mini_recv(int socket, char* buf,int * buflen) 該函數(shù)從指定的SOCKET接收數(shù)據(jù) int mini_send(int socket,char*buf,int buflen) 該函數(shù)將指定的數(shù)據(jù)發(fā)送到SOCKET int mini_close(int socket) 該函數(shù)用來通知協(xié)議軟件關閉指定的socket連接 使用本協(xié)議軟件庫函數(shù)實現(xiàn)ECHO服務的一個簡單例程如下: /*************************************************** * Simple tcp echo client using libnet and libpcap and mini_socket.c * file: miniecho.c * Date: 2003.5. * Author: white cpf * compile: gcc -Wall -lpcap -lnet miniecho.c mini_socket.c -o miniecho * Run: readhat 8.0 you must be root and run ifconfig to see eth0 OK *****************************************************/ #include <pcap.h> #include <libnet.h> #include "mini_socket.h" #define uchar unsigned char #define MAXBUFLEN 2048 char buf[MAXBUFLEN]; int buflen; char recvbuf[MAXBUFLEN]; int recvedlen; int main(int argc,char *argv[]){ int ret; int i; char sip[40]="169.254.159.112"; char sport[10]="7777"; char dip[40]="169.254.159.111"; char dport[10]="5000"; struct socket ti; int s; ti.sip=inet_addr(sip); ti.dip=inet_addr(dip); ti.sport=(unsigned short)atoi(sport); ti.dport=(unsigned short)atoi(dport); s=mini_socket(&ti); if(s<0){ printf("mini_socket() error\n"); return 0; } ret=mini_connect(s);//connect to tcpip using TCP three time handshaking if(ret<0){ printf("mini_connect() error\n"); return 0; } while (1){ //get input from stdin, quit when EOF or "myquit!" input if(fgets(buf, sizeof(buf), stdin)==0) break; if(strcmp(buf,"myquit!")==0) break; ret=mini_send(s,buf,strlen(buf)); if(ret<=0){ printf("mini_send() return %d\n",ret); break; } ret=mini_recv(s,recvbuf,&recvedlen); if(ret<=0){ printf("mini_recv() return %d\n",ret); break; } recvbuf[recvedlen]=0; printf("recved[%d bytes]:%s\n",recvedlen,recvbuf); } mini_close(s);//close tcpip using TCP three time handshaking } |
本文通過使用目前廣泛流行的libnet和libpcap函數(shù)庫,在Linux環(huán)境下實現(xiàn)了一個單進程的TCP/IP協(xié)議軟件:minitcpip,并且提供了一個調用接口:minisocket,通過這個接口,可以創(chuàng)建基于TCP/IP協(xié)議的應用程序。
本文實現(xiàn)的minitcpip協(xié)議軟件只具有最小的TCP/IP通訊功能,效率也非常一般。但是,作為一個學習libnet、libpcap,學習TCP/IP軟件在以太網(wǎng)上的實現(xiàn)原理的目的,這個項目卻具有一定的價值。另外,采用這種方案實現(xiàn)的協(xié)議與libnet、libpcap、操作系統(tǒng)之間的層次關系清晰,可以想象,minisocket中與環(huán)境無關的代碼可以很容易的移植到目前越來越廣泛使用的嵌入式設備的網(wǎng)絡應用上,這是作者寫作本文的又一個考慮。
由于時間緊張,minitcpip協(xié)議軟件的實現(xiàn)方法許多都有繼續(xù)斟酌之處,而且作者本身也在學習過程中,錯誤之處在所難免,本文若能為大家學習libnet、libpcap、tcpip有所幫助,作者欣慰之至。同時希望大家能給出批評和指正,以利共同提高。