本文爲原創。轉載請註明:
作者:Utensil
博客:http://blog.csdn.net/utensil/
郵箱:[email protected]
鑑於網上存在兩個libnet,本文中所謂libnet是指位於http://www.packetfactory.net/libnet/的The Libnet Packet Construction Library。
libnet是一個小型的接口函數庫,主要用C語言寫成,提供了低層網絡數據報的構造、處理和發送功能。libnet的開發目的是:建立一個簡單統一的網絡編程接口以屏蔽不同操作系統低層網絡編程的差別,使得程序員將精力集中在解決關鍵問題上。它的主要特點是:
- 高層接口:libnet主要用C語言寫成。
- 可移植性:libnet目前可以在Linux、FreeBSD、Solaris、WindowsNT等操作系統上運行,並且提供了統一的接口。
- 數據報構造:libnet提供了一系列的TCP/IP數據報文的構造函數以方便用戶使用。
- 數據報的處理:libnet提供了一系列的輔助函數,利用這些輔助函數,幫助用戶簡化那些煩瑣的事務性的編程工作。
- 數據報發送:libnet允許用戶在兩種不同的數據報發送方法中選擇。
另外libnet允許程序獲得對數據報的絕對的控制,其中一些是傳統的網絡程序接口所不提供的。這也是libnet的魅力之一。
詳細介紹可參見《使用libnet與libpcap構造TCP/IP協議軟件》(http://www.ibm.com/developerworks/cn/linux/l-tcpip/)。
本來看到libnet源代碼樹根目錄中的configure腳本,我以爲只需要在MSYS下鍵入configure然後make就可以了。誰料斷斷續續折騰了幾個星期,根據gcc的出錯信息強行修改了十餘處源代碼,才使其編譯通過,卻又遭遇大量鏈接出錯,實在令人氣餒。今天決心從問題的根源下手,終於用一個比原先簡潔得多的修改方案使其通過編譯和鏈接,特寫此博客予以記述。
基本前提
正確下載和安裝MinGW和MSYS,假設分別位於C:/MinGW和C:/msys。(下載地址:http://sourceforge.net/project/showfiles.php?group_id=2435)
準備工作
1)從http://www.packetfactory.net/libnet/dist/下載libnet的源代碼。我選擇的文件是libnet-1.1.3-RC-01.tar.gz。解壓。將其內容複製到D:/libnet,注意,此操作的結果應使D:/libnet下有include、src等子文件夾。也可自選其它位置,本文按D:/libnet進行敘述。
2)因爲libnet依賴於libpcap,所以要從http://www.winpcap.org/devel.htm下載Developer's Pack。我選擇的是4.0.2。解壓。將Include子文件夾中的文件複製到D:/libnet/include。Gnuc.h文件會衝突,這無關緊要,我選擇保留libnet自己那個。將Lib子文件夾中的文件複製到D:/libnet/lib(原來沒有這個文件夾的,自己建)。
3)試着編譯一下:在msys中
- cd /d/libnet
- ./configure
- make
如果configure出錯,說明MinGW或MSYS安裝不正確。可能是沒有將C:/MinGW/bin加入PATH,修改C:/msys/profile,在
- if [ $MSYSTEM == MINGW32 ]; then
- export PATH=".:/usr/local/bin:/mingw/bin:/bin:$PATH"
- else
- export PATH=".:/usr/local/bin:/bin:/mingw/bin:$PATH"
- fi
後面加上:
- export PATH="/c/MinGW/bin:$PATH"
其它錯誤類型本文不予考慮。
如果make出錯,這屬於正常,除非它說的是找不到pcap.h,這樣的話,請檢查第二步做對沒有。
讀者可自行嘗試修正其錯誤,也可按照下面這個簡潔方案。
4)順便介紹一下如何察看MinGW的預定義宏:
- gcc -dM -E - < nul
這個命令在處理跨平臺和移植性問題時相當有用。比如從它的輸出可以看出,_WIN32、__WIN32__、__MINGW32__等宏已經被定義,這樣,面對那些基於這些宏的預處理語句,就有一個清晰些的思路,不用去猜。
修改方案
1)修改include/win32/libnet.h爲:
- #if (__MINGW32__)
- #include "pcap.h"
- #define HAVE_NET_ETHERNET_H 0
- #include "../libnet.h"
- #include <wincrypt.h>
- #else
- 原來文件內容
- #endif
觀察第4行,這是把編譯器導引回去讀include/libnet.h,那個頭文件是爲UNIX-Like系統寫的,也是最初的頭文件,Linux下或Cygwin下都用它,沒什麼問題。本文件(include/win32/libnet.h)是爲VC寫的,由於libnet沒有被官方地移植到MinGW,在src/*.c文件中,把又是Win32又不是Cygwin的情況都按照是VC處理了。結果MinGW被牛頭不對馬嘴地安上了只適合VC的頭文件,自然會出問題了。但是include/libnet.h也不是直接就能用,需要進行一些微調。這裏面幾個語句的順序非常重要。
2)修改include/libnet-functions.h中函數libnet_open_raw4(libnet_t *l);前的
- #if ((__WIN32__) && !(__CYGWIN__))
爲
- #if ((__WIN32__) && !(__CYGWIN__)) && !(__MINGW32__)
3)修改src/libnet_link_none.c爲
- #if (__MINGW32__)
- #include "libnet_link_win32.c"
- #else
- 原文件內容
- #endif
此處修改是權宜之計,因爲不知爲什麼,libnet_link_win32.c沒有被編譯,卻把沒做任何事的libnet_link_none給編譯了。與其去研究Makefile,不如來個調包。
4)此時編譯可通過。然而出現大量undefined reference這種鏈接錯誤。修改之前make過程生成的src/libnet.la中的
- dependency_libs=''
爲
- dependency_libs='-lwsock32 -lws2_32 -liphlpapi -L/d/libnet/lib -lwpcap -lpacket'
注意,其中-L/d/libnet/lib一項,是叫gcc到D:/libnet/lib去尋找libwpcap.a和libpacket.a。要根據你自己放置它們的位置進行調整。
此時鏈接會成功,但編譯到例子get_addr.c時,出現錯誤undefined reference to '_imp__optarg'。這樣libnet就成功編譯了。例子之所以出錯,是因爲它使用了getopt來處理例子中需要處理的命令行參數,而該函數在MinGW似乎有可移植性問題,MinGW mailing list上有一個人在2006年問了這個問題怎麼解決,到現在還沒有人答他。所幸這個問題不影響libnet庫本身。
此時你會在src/.lib下發現編譯好的libnet.a。大小爲19M多,是靜態庫。
5)順便介紹一下怎麼察看裏面的函數:
- nm -g --defined-only --demangle libnet.a
測試例程
這個例程修改自arp.c和libpcap的例子basic_dump.c
- #include <stdio.h>
- #include <stdlib.h>
- #include "../sample/libnet_test.h"
- int
- main(int argc, char *argv[])
- {
- int c;
- u_int32_t i;
- libnet_t *l;
- libnet_ptag_t t;
- char *device = NULL;
- u_int8_t *packet;
- u_int32_t packet_s;
- char errbuf[LIBNET_ERRBUF_SIZE];
- printf("libnet 1.1 packet shaping: ARP[link -- autobuilding ethernet]/n");
- if (argc > 1)
- {
- device = argv[1];
- }
- else
- {
- pcap_if_t *alldevs;
- pcap_if_t *d;
- int inum;
- int i=0;
- char errbuf[PCAP_ERRBUF_SIZE];
- /* Retrieve the device list */
- if(pcap_findalldevs(&alldevs, errbuf) == -1)
- {
- fprintf(stderr,"Error in pcap_findalldevs: %s/n", errbuf);
- exit(1);
- }
- /* Print the list */
- for(d=alldevs; d; d=d->next)
- {
- printf("%d. %s", ++i, d->name);
- if (d->description)
- printf(" (%s)/n", d->description);
- else
- printf(" (No description available)/n");
- }
- if(i==0)
- {
- printf("/nNo interfaces found! Make sure WinPcap is installed./n");
- return -1;
- }
- printf("Enter the interface number (1-%d):",i);
- scanf("%d", &inum);
- if(inum < 1 || inum > i)
- {
- printf("/nInterface number out of range./n");
- /* Free the device list */
- pcap_freealldevs(alldevs);
- return -1;
- }
- /* Jump to the selected adapter */
- for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
- device = d->name;
- }
- l = libnet_init(
- LIBNET_LINK_ADV, /* injection type */
- device, /* network interface */
- errbuf); /* errbuf */
- if (l == NULL)
- {
- fprintf(stderr, "%s", errbuf);
- exit(EXIT_FAILURE);
- }
- else
- i = libnet_get_ipaddr4(l);
- 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 */
- (u_int8_t *)&i, /* sender protocol addr */
- enet_dst, /* target hardware addr */
- (u_int8_t *)&i, /* target protocol addr */
- NULL, /* payload */
- 0, /* payload size */
- l, /* libnet context */
- 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;
- }
- if (libnet_adv_cull_packet(l, &packet, &packet_s) == -1)
- {
- fprintf(stderr, "%s", libnet_geterror(l));
- }
- else
- {
- fprintf(stderr, "packet size: %d/n", packet_s);
- libnet_adv_free_packet(l, packet);
- }
- c = libnet_write(l);
- if (c == -1)
- {
- fprintf(stderr, "Write error: %s/n", libnet_geterror(l));
- goto bad;
- }
- else
- {
- fprintf(stderr, "Wrote %d byte ARP packet from context /"%s/"; "
- "check the wire./n", c, libnet_cq_getlabel(l));
- }
- libnet_destroy(l);
- return (EXIT_SUCCESS);
- bad:
- libnet_destroy(l);
- return (EXIT_FAILURE);
- }
- /* EOF */
設置注意事項:
1)環境變量:對編譯器,要添加:D:/libnet/include;對連接器,要添加D:/libnet/lib和D:/libnet/src/.libs
2)鏈接的庫:(按順序)libnet.a libpacket.a libwpcap.a libiphlpapi.a libws2_32.a libwsock32.a
3)運行此程序,運行所有WinPcap的程序一樣,要下載並安裝WinPcap(http://www.winpcap.org/install/default.htm)。