libnet介紹與分析

來源:互聯網
上載者:User

標籤:des   使用   檔案   資料   os   art   

  libnet介紹與分析

 

當前,基於socket的網路編程已成為當今不可替代的編程方法,它將網路通訊當作檔案描述符進行處理,把對這個“網路檔案”(即socket通訊端)的操作抽象成一種類似於檔案操作的方式進行。從實現細節上,這種工作方式根據TCP/IP的網路通訊模型,封裝了一系列的實現,使得我們只需要使用一個指定的參數,就可以實現在基於所需協議的資料的發送和接收。

但是,如果我們對那些系統自動給我們做的工作感興趣,希望與發送的資料作“面對面”的接觸,libnet則會是一個不錯的選擇。

libnet是UNIX系統同台上網路安全工具開發的重要的庫,它和libpcap、libnids一起,給網路安全工具的開發人員提供了一組豐富而且完全的武器,使之得以很方便地編寫出結構化強、健壯性好、可移植性高等特點的程式。

libnet提供一系列的介面函數,實現和封裝了資料包的構造和發送過程。利用它可以親自構造從應用程式層到鏈路層的各層協議的資料包頭,並將這些包頭與有效資料有序地組合在一起發送出去。當然,它也是基於tcp/ip協議族模型的。

 

libnet當前的版本是1.1.2,相對於1.0.*版本有比較大的變化。

全部原始碼包括18,000 行代碼,109個匯出函數,其中包括67個建包函數。這使得它支援現有的TCP/IP族的所有協議。此外,它支援多平台,Windows,OS X,BSD,Linux, Solaris,HPUX都能使用。

是它支援的協議:

 

 

libnet庫可以被劃分為4個功能部分:記憶體管理、位址解析、包處理、以及其他一些支援函數。

★ 記憶體管理函數

  單資料包記憶體初始化及環境建立:

libnet_t *libnet_init(int injection_type, char *device, char *err_buf);

  資源釋放:

    void libnet_destroy(libnet_t *l);

 

★ 位址解析函數

  位址解析:

char *libnet_addr2name4(u_int32_t in, u_int8_t use_name); 

libnet_name2addr4(libnet_t *l, char *host_name, u_int8_t use_name); 

struct libnet_in6_addr libnet_name2addr6(libnet_t *l, char *host_name, u_int8_t use_name); 

void libnet_addr2name6_r(struct libnet_in6_addr addr, u_int8_t use_name,

char *host_name, int host_name_len);

  擷取介面裝置IP地址:

    u_int32_t libnet_get_ipaddr4(libnet_t *l);

    struct libnet_in6_addr libnet_get_ipaddr6(libnet_t *l);

 

  擷取介面裝置硬體地址:

    struct libnet_ether_addr *libnet_get_hwaddr(libnet_t *l);

 

★ 資料包建構函式

   (這一部分函數較多,都以libnet_build_*()的形式出現,在此略過)

★ 資料包發送函數

    int libnet_write(libnet_t *l);

★ 相關的支援函數

 

  隨機數種子產生器:

    int libnet_seed_prand(libnet_t *l);

  擷取隨機數:

    u_int32_t libnet_get_prand(int mod);

 

連接埠列錶鏈初始化:

    int libnet_plist_chain_new(libnet_t *l, libnet_plist_t **plist, char *token_list);

 

  擷取連接埠列錶鏈的下一項(連接埠範圍):

    int libnet_plist_chain_next_pair(libnet_plist_t *plist, u_int16_t *bport,

u_int16_t *eport);

 

  連接埠列錶鏈輸出顯示:

    int libnet_plist_chain_dump(libnet_plist_t *plist);

 

  擷取連接埠列錶鏈:

    char *libnet_plist_chain_dump_string(libnet_plist_t *plist);

 

  連接埠列錶鏈記憶體釋放:

    int libnet_plist_chain_free(libnet_plist_t *plist);

 

對它的使用也非常簡單,只要你瞭解自己要做什麼事情、應該把哪些參數放在什麼位置。利用libnet函數庫開發應用程式的基本步驟非常簡單:

1、資料包記憶體初始化;

2、構造資料包;

3、發送資料;

4、釋放資源;

例: libnet的發行包裡提供了很多樣本程式,其中/libnet/sample/tcp1.c如果省略掉一些參數的設定和錯誤處理,則程式簡化為:

#if (H***E_CONFIG_H)

#i nclude "../include/config.h"

#endif

#i nclude "libnet_test.h"

#ifdef __WIN32__

#i nclude "../include/win32/getopt.h"

#endif

 

int

main(int argc, char *argv[])

{

   //…….

    l = libnet_init(

            LIBNET_LINK,                            /* injection type */

            NULL,                                   /* network interface */

            errbuf);                                /* error buffer */

    //……

    t = libnet_build_tcp_options(

"\003\003\012\001\002\004\001\011\010\012\077\077\077\077\000\000\000\000\000\000",

        20,

        l,

        0);

  

    //……

    t = libnet_build_tcp(src_prt, dst_prt, 0x01010101, 0x02020202, TH_SYN, 32767, 0, 10,                                       

        LIBNET_TCP_H + 20 + payload_s, payload, payload_s, l, 0);                                       

  

      

    t = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_TCP_H + 20 + payload_s, 0, 242, 0, 64,                                     

        IPPROTO_TCP, 0,  src_ip, dst_ip, NULL, 0, l,  0);                                     

  

    t = libnet_build_ethernet(enet_dst, enet_src, ETHERTYPE_IP, NULL, 0, l, 0);                                

  

    c = libnet_write(l);

     libnet_destroy(l);

    return (EXIT_SUCCESS);

bad:

    libnet_destroy(l);

    return (EXIT_FAILURE);

}

 

#if defined(__WIN32__)

#i nclude <../include/win32/getopt.h>

#i nclude <winsock2.h>

#i nclude <ws2tcpip.h>

#endif  /* __WIN32__ */

/* EOF */

對libnet源碼的分析

★     整體設計思想

       對每個要發送的包,libnet維護一個libnet­_t結構,這個結構是理解整個libnet的關鍵,也是libnet得以實現它強大功能的關鍵。讓我們先從它入手,從整體到細節地揭開libnet的面紗。下面左圖是libnet_t這個結構的樣本。

 

其中的fd就是發送資料包將要用到的socket通訊端,injection_type將會被設定成libnet_init()中的第一個參數,即你選擇發送的方式,是基於link_layer的鏈路層資料包?還是基於IP層的raw資料包?後一種情況又分為IP4和IP6兩種。protocol_blocks 和protocol_end都是指標,指向一個libnet自訂的libnet_pblock_t結構,由此管理一個libnet_pblock_t的鏈表。而libnet_pblock_t則表述各個協議,維護各個協議給發送的資料包添加的資料區塊,它的具體選項下面再說。link_type表示鏈路層的類型,link_offset則指向鏈路層也就是最底層協議包頭的位移地址。aligner是為了維護最後的資料包的字對齊而設定的,字串指標device則指向通訊所用的裝置,比如eth0.state是一個結構,與ptag_state一起,記錄包在建立過程中的一些資訊。label是一個字元數組。每當有錯誤發生的時候,errbuf數組就被用來記錄錯誤資訊。全部的資料包長度和儲存於total_size中。

從libnet_t已經大概可以看出libnet的設計思想了:程式員決定一些參數,並且通過函數調用中的參數把相關的資料交給libnet.libnet則在程式員每要求購建一個協議包頭的時候,為其建立一個libnet_pblock_t的結構儲存這些資料,並將該結構入鏈表。當所有的準備工作完成,程式員一聲令下,libnet就將協議塊鏈(由protocol_blocks開始,終於pblock_end)中的協議包頭以及資料群組合成一個合乎規格的包,通過硬體發送出去。如果任何一個步驟出了差錯,程式員都可以從errbuf中擷取出錯資訊。最後,libnet從程式員手中接過一個指令,進行所有的善後工作。

每種協議的包頭將在前期被實現為一個libnet_pblock_t的結構。libnet為它所支援的協議都定義了相應的資料結構,例如TCP包頭的定義:

struct libnet_tcp_hdr

{

    u_int16_t th_sport;       /* source port */

    u_int16_t th_dport;       /* destination port */

    u_int32_t th_seq;          /* sequence number */

    u_int32_t th_ack;          /* acknowledgement number */

#if (LIBNET_LIL_ENDIAN)

    u_int8_t th_x2:4,         /* (unused) */

           th_off:4;        /* data offset */

#endif

#if (LIBNET_BIG_ENDIAN)

    u_int8_t th_off:4,        /* data offset */

           th_x2:4;         /* (unused) */

#endif

    u_int8_t  th_flags;       /* control flags */

    …….

    u_int16_t th_win;         /* window */

    u_int16_t th_sum;         /* checksum */

    u_int16_t th_urp;         /* urgent pointer */

};

它的長度是:

#define           LIBNET_TCP_H      0x14    /**< TCP header:          20 bytes */

當程式員調用libnet_built_tcp()構建一個TCP包頭時,libnet會分配一個這樣的結構,並且按照程式員的意願填充這個結構的各個欄位。並且產生一個libnet_pblock_t結構,使它的buf指標指向這個資料包頭結構,並用b_len儲存這個包頭的長度(20位元組)。這個libnet_pblock_t的其它相關欄位將會被按規則填充。之後,這個libnet_pblock_t會被加入到libnet_t結構所維護的協議塊鏈的適當位置。所謂“適當位置”,將會在後面加以說明。是進程中一種可能出現的情形:

 

 

利益於這種精巧的整體架構,libnet的具體實現就不顯得困難了,而我們對它的理解也因此受益。剩下來的只是一些細節性的東西需要去把握,這一點顯然更適合親自去閱讀代碼。

下面是大概的處理流程。

在開始之前,需要插入一點說明:libnet使用條件編譯的方式消除平台間的差異,由此產生了很多同名的函數(當然它們都在內部被調用),比如libnet_open_link()有五個。程式在運行時選擇哪一個調用取決於你使用的平台或者你強加於編譯器的先行編譯選項。

 

一般情況下事情會從libnet_init()開始。libnet_init()首先開啟系統的網路功能:在linux下面,它會驗證是否具有超級使用者權限(這也就意味著低許可權的使用者不能使用成功),而如果是在windows下面,則會調用大名鼎鼎的WSAStartup()函數。之後分配一塊記憶體地區建立libnet_t結構。在簡單的初始化後,根據程式員傳入的發送型別參數,進行相應的操作:如果是基於鏈路層的發送方式(LIBNET_LINK,LIBNET_LINK_ADV),則會向作業系統申請裝置,並開啟底層的socket服務。如果是基於IP層的發送方式,則開啟該層的socket服務。

準備工作做好之後,轉入直接的建包工作,這一工作實際表現為建一系列的協議包頭。程式員需要按照自頂向下的順序建協議包頭,而要發送的本文資料往往被作為第一個協議包頭的“有效荷載(payload)”被載入。此外,某些協議可能具有可選資料項目,這一部分被libnet獨立出來,由一個單獨的libnet_pblock_t來負責,libnet同時也為此提供相應的功能函數,其名稱被定為libnet_build_*_options().在這種情況下,libnet會自動調整這些協議塊在鏈表中的位置,使payload的資料區塊在前面,中間是options塊,其後跟隨著該層協議的固定包頭。

在合適的時候,libnet會把計算校正和這樣的繁瑣的工作優雅地完成,你甚至感覺不到它已經為你這樣做了。

這樣,只要程式員的使用正確,資料以及協議包頭已經在鏈表中按順序排列了,這為簡化後面的工作極為有益。在libnet_write()過程中,鏈表中的資料被按順序地拷貝到一個大的緩衝內。拷貝雖然是按照從高層協議到低層協議的順序進行,但是卻是從緩衝的後部向前部拷貝,從這裡,我們可以看到“協議棧”的思想在閃光。

當一切工作完成之後,程式員簡單地調用libnet_destroy()函數,關閉使用的網路通訊裝置,釋放佔用的記憶體。程式員的工作是如此地簡單:他甚至僅僅需要傳遞一個參數給這個簡短的函數。

 

除了完成這樣一些最直接的工作以外,libnet還提供了一組豐富實用的功能。它提供的位址解析功能能夠實現IP地址在網路位元組順序、網域名稱、點分形式之間的轉換,它也提供了一套完整的隨機數產生方案供你使用,如此等等。這一些功能使得它得以從一套單純的庫躍升為一個完整的系統。

(文中所用圖片源自Mike Schiffman在2004年RSA Conference上的PPT)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.