久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Linux下Libpcap源碼分析和包過濾機(jī)制

 skywood 2007-01-25
抓包從這里開始

libpcap是unix/linux平臺下的網(wǎng)絡(luò)數(shù)據(jù)包捕獲函數(shù)包,,大多數(shù)網(wǎng)絡(luò)監(jiān)控軟件都以它為基礎(chǔ)。Libpcap可以在絕大多數(shù)類unix平臺下工作,本文分析了libpcap在linux 下的源代碼實(shí)現(xiàn),其中重點(diǎn)是linux的底層包捕獲機(jī)制和過濾器設(shè)置方式,同時(shí)也簡要的討論了 libpcap使用的包過濾機(jī)制 BPF,。

網(wǎng)絡(luò)監(jiān)控

絕 大多數(shù)的現(xiàn)代操作系統(tǒng)都提供了對底層網(wǎng)絡(luò)數(shù)據(jù)包捕獲的機(jī)制,在捕獲機(jī)制之上可以建立網(wǎng)絡(luò)監(jiān)控(Network Monitoring)應(yīng)用軟件,。網(wǎng)絡(luò)監(jiān)控也常簡稱為sniffer,其最初的目的在于對網(wǎng)絡(luò)通信情況進(jìn)行監(jiān)控,,以對網(wǎng)絡(luò)的一些異常情況進(jìn)行調(diào)試處理,。但 隨著互連網(wǎng)的快速普及和網(wǎng)絡(luò)攻擊行為的頻繁出現(xiàn),,保護(hù)網(wǎng)絡(luò)的運(yùn)行安全也成為監(jiān)控軟件的另一個(gè)重要目的。例如,,網(wǎng)絡(luò)監(jiān)控在路由器,,防火墻、入侵檢查等方面使 用也很廣泛,。除此而外,,它也是一種比較有效的黑客手段,例如,,美國政府安全部門的"肉食動物"計(jì)劃,。

包捕獲機(jī)制

從廣義的角度上看,一個(gè)包捕獲機(jī)制包含三個(gè)主要部分:最底層是針對特定操作系統(tǒng)的包捕獲機(jī)制,最高層是針對用戶程序的接口,,第三部分是包過濾機(jī)制,。

不 同的操作系統(tǒng)實(shí)現(xiàn)的底層包捕獲機(jī)制可能是不一樣的,但從形式上看大同小異,。數(shù)據(jù)包常規(guī)的傳輸路徑依次為網(wǎng)卡,、設(shè)備驅(qū)動層、數(shù)據(jù)鏈路層,、IP層,、傳輸層、最 后到達(dá)應(yīng)用程序,。而包捕獲機(jī)制是在數(shù)據(jù)鏈路層增加一個(gè)旁路處理,,對發(fā)送和接收到的數(shù)據(jù)包做過濾/緩沖等相關(guān)處理,最后直接傳遞到應(yīng)用程序,。值得注意的是,, 包捕獲機(jī)制并不影響操作系統(tǒng)對數(shù)據(jù)包的網(wǎng)絡(luò)棧處理。對用戶程序而言,,包捕獲機(jī)制提供了一個(gè)統(tǒng)一的接口,,使用戶程序只需要簡單的調(diào)用若干函數(shù)就能獲得所期望 的數(shù)據(jù)包。這樣一來,,針對特定操作系統(tǒng)的捕獲機(jī)制對用戶透明,,使用戶程序有比較好的可移植性。包過濾機(jī)制是對所捕獲到的數(shù)據(jù)包根據(jù)用戶的要求進(jìn)行篩選,,最 終只把滿足過濾條件的數(shù)據(jù)包傳遞給用戶程序,。

Libpcap應(yīng)用程序框架

Libpcap提供了系統(tǒng)獨(dú)立的 用戶級別網(wǎng)絡(luò)數(shù)據(jù)包捕獲接口,并充分考慮到應(yīng)用程序的可移植性,。Libpcap可以在絕大多數(shù)類unix平臺下工作,,參考資料 A 中是對基于 libpcap 的網(wǎng)絡(luò)應(yīng)用程序的一個(gè)詳細(xì)列表。在windows平臺下,,一個(gè)與libpcap 很類似的函數(shù)包 winpcap 提供捕獲功能,,其官方網(wǎng)站是http://winpcap./。

Libpcap 軟件包可從 http://www./ 下載,,然后依此執(zhí)行下列三條命令即可安裝,,但如果希望libpcap能在linux上正常工作,則必須使內(nèi)核支持"packet"協(xié)議,,也即在編譯內(nèi)核時(shí) 打開配置選項(xiàng) CONFIG_PACKET(選項(xiàng)缺省為打開),。


./configure
./make
./make install


libpcap 源代碼由20多個(gè)C文件構(gòu)成,但在Linux系統(tǒng)下并不是所有文件都用到,??梢酝ㄟ^查看命令make的輸出了解實(shí)際所用的文件,。本文所針對的 libpcap版本號為0.8.3,網(wǎng)絡(luò)類型為常規(guī)以太網(wǎng),。Libpcap應(yīng)用程序從形式上看很簡單,,下面是一個(gè)簡單的程序框架:


char * device; /* 用來捕獲數(shù)據(jù)包的網(wǎng)絡(luò)接口的名稱 */
pcap_t * p; /* 捕獲數(shù)據(jù)包句柄,最重要的數(shù)據(jù)結(jié)構(gòu) */
struct bpf_program fcode; /* BPF 過濾代碼結(jié)構(gòu) */

/* 第一步:查找可以捕獲數(shù)據(jù)包的設(shè)備 */
device = pcap_lookupdev(errbuf),;

/* 第二步:創(chuàng)建捕獲句柄,,準(zhǔn)備進(jìn)行捕獲 */
p = pcap_open_live(device, 8000, 1, 500, errbuf);

/* 第三步:如果用戶設(shè)置了過濾條件,,則編譯和安裝過濾代碼 */
pcap_compile(p, &fcode, filter_string, 0, netmask),;
pcap_setfilter(p, &fcode);

/* 第四步:進(jìn)入(死)循環(huán),,反復(fù)捕獲數(shù)據(jù)包 */
for( ; ; )
{
while((ptr = (char *)(pcap_next(p, &hdr))) == NULL);

/* 第五步:對捕獲的數(shù)據(jù)進(jìn)行類型轉(zhuǎn)換,,轉(zhuǎn)化成以太數(shù)據(jù)包類型 */
eth = (struct libnet_ethernet_hdr *)ptr;

/* 第六步:對以太頭部進(jìn)行分析,判斷所包含的數(shù)據(jù)包類型,,做進(jìn)一步的處理 */
if(eth->ether_type == ntohs(ETHERTYPE_IP))
…………
if(eth->ether_type == ntohs(ETHERTYPE_ARP))
…………
}

/* 最后一步:關(guān)閉捕獲句柄,一個(gè)簡單技巧是在程序初始化時(shí)增加信號處理函數(shù),,
以便在程序退出前執(zhí)行本條代碼 */
pcap_close(p);



檢查網(wǎng)絡(luò)設(shè)備

libpcap 程序的第一步通常是在系統(tǒng)中找到合適的網(wǎng)絡(luò)接口設(shè)備,。網(wǎng)絡(luò)接口在Linux網(wǎng)絡(luò)體系中是一個(gè)很重要的概念,,它是對具體網(wǎng)絡(luò)硬件設(shè)備的一個(gè)抽象,在它的下面 是具體的網(wǎng)卡驅(qū)動程序,,而其上則是網(wǎng)絡(luò)協(xié)議層,。Linux中最常見的接口設(shè)備名eth0和lo。Lo 稱為回路設(shè)備,,是一種邏輯意義上的設(shè)備,其主要目的是為了調(diào)試網(wǎng)絡(luò)程序之間的通訊功能,。eth0對應(yīng)了實(shí)際的物理網(wǎng)卡,在真實(shí)網(wǎng)絡(luò)環(huán)境下,,數(shù)據(jù)包的發(fā)送和 接收都要通過 eht0,。如果計(jì)算機(jī)有多個(gè)網(wǎng)卡,則還可以有更多的網(wǎng)絡(luò)接口,,如eth1,eth2 等等,。調(diào)用命令ifconfig可以列出當(dāng)前所有活躍的接口及相關(guān)信息,注意對eth0的描述中既有物理網(wǎng)卡的MAC地址,,也有網(wǎng)絡(luò)協(xié)議的IP地址,。查看 文件/proc/net/dev也可獲得接口信息,。

Libpcap中檢查網(wǎng)絡(luò)設(shè)備中主要使用到的函數(shù)關(guān)系如下圖:

                                                   

libpcap調(diào)用pcap_lookupdev()函數(shù)獲 得可用網(wǎng)絡(luò)接口的設(shè)備名,。首先利用函數(shù) getifaddrs() 獲得所有網(wǎng)絡(luò)接口的地址,以及對應(yīng)的網(wǎng)絡(luò)掩碼,、廣播地址,、目標(biāo)地址等相關(guān)信息,,再利用 add_addr_to_iflist()、add_or_find_if(),、get_instance() 把網(wǎng)絡(luò)接口的信息增加到結(jié)構(gòu)鏈表 pcap_if 中,,最后從鏈表中提取第一個(gè)接口作為捕獲設(shè)備。其中 get_instanced()的功能是從設(shè)備名開始,找第一個(gè)是數(shù)字的字符,做為接口的實(shí)例號,。網(wǎng)絡(luò)接口的設(shè)備號越小,,則排在鏈表的越前面,因此,,通常 函數(shù)最后返回的設(shè)備名為 eth0,。雖然 libpcap 可以工作在回路接口上,但顯然 libpcap 開發(fā)者認(rèn)為捕獲本機(jī)進(jìn)程之間的數(shù)據(jù)包沒有多大意義,。在檢查網(wǎng)絡(luò)設(shè)備操作中,,主要用到的數(shù)據(jù)結(jié)構(gòu)和代碼如下:


/* libpcap 自定義的接口信息鏈表 [pcap.h] */
struct pcap_if
{
struct pcap_if *next;
char *name; /* 接口設(shè)備名 */
char *description; /* 接口描述 */

/*接口的 IP 地址, 地址掩碼, 廣播地址,目的地址 */
struct pcap_addr addresses;
bpf_u_int32 flags; /* 接口的參數(shù) */
};

char * pcap_lookupdev(register char * errbuf)
{
pcap_if_t *alldevs;
……
pcap_findalldevs(&alldevs, errbuf);
……
strlcpy(device, alldevs->name, sizeof(device));
}

打開網(wǎng)絡(luò)設(shè)備

當(dāng)設(shè)備找到后,,下一步工作就是打開設(shè)備以準(zhǔn)備捕獲數(shù)據(jù)包,。Libpcap的包捕獲是建立在具體的操作系統(tǒng)所提供的捕獲機(jī)制上,而Linux系統(tǒng)隨著版本的不同,,所支持的捕獲機(jī)制也有所不同,。

2.0 及以前的內(nèi)核版本使用一個(gè)特殊的socket類型SOCK_PACKET,調(diào)用形式是socket(PF_INET, SOCK_PACKET, int protocol),,但 Linux 內(nèi)核開發(fā)者明確指出這種方式已過時(shí),。Linux 在 2.2及以后的版本中提供了一種新的協(xié)議簇 PF_PACKET 來實(shí)現(xiàn)捕獲機(jī)制。PF_PACKET 的調(diào)用形式為 socket(PF_PACKET, int socket_type, int protocol),,其中socket類型可以是 SOCK_RAW和SOCK_DGRAM,。SOCK_RAW 類型使得數(shù)據(jù)包從數(shù)據(jù)鏈路層取得后,不做任何修改直接傳遞給用戶程序,,而 SOCK_DRRAM 則要對數(shù)據(jù)包進(jìn)行加工(cooked),,把數(shù)據(jù)包的數(shù)據(jù)鏈路層頭部去掉,而使用一個(gè)通用結(jié)構(gòu) sockaddr_ll 來保存鏈路信息,。

使 用 2.0 版本內(nèi)核捕獲數(shù)據(jù)包存在多個(gè)問題:首先,,SOCK_PACKET 方式使用結(jié)構(gòu) sockaddr_pkt來保存數(shù)據(jù)鏈路層信息,但該結(jié)構(gòu)缺乏包類型信息,;其次,,如果參數(shù) MSG_TRUNC 傳遞給讀包函數(shù) recvmsg()、recv(),、recvfrom() 等,,則函數(shù)返回的數(shù)據(jù)包長度是實(shí)際讀到的包數(shù)據(jù)長度,而不是數(shù)據(jù)包真正的長度,。Libpcap 的開發(fā)者在源代碼中明確建議不使用 2.0 版本進(jìn)行捕獲,。

相對2.0版本SOCK_PACKET方式,,2.2版本的PF_PACKET方式則不存在上述兩個(gè)問題。在實(shí)際應(yīng)用中,,用 戶程序顯然希望直接得到"原始"的數(shù)據(jù)包,,因此使用 SOCK_RAW 類型最好。但在下面兩種情況下,,libpcap 不得不使用SOCK_DGRAM類型,,從而也必須為數(shù)據(jù)包合成一個(gè)"偽"鏈路層頭部(sockaddr_ll)。

某些類型的設(shè)備數(shù)據(jù)鏈路層頭部不可用:例如 Linux 內(nèi)核的 PPP 協(xié)議實(shí)現(xiàn)代碼對 PPP 數(shù)據(jù)包頭部的支持不可靠,。

在捕獲設(shè)備為"any"時(shí):所有設(shè)備意味著libpcap對所有接口進(jìn)行捕獲,,為了使包過濾機(jī)制能在所有類型的數(shù)據(jù)包上正常工作,要求所有的數(shù)據(jù)包有相同的數(shù)據(jù)鏈路頭部。

打 開網(wǎng)絡(luò)設(shè)備的主函數(shù)是 pcap_open_live()[pcap-linux.c],,其任務(wù)就是通過給定的接口設(shè)備名,,獲得一個(gè)捕獲句柄:結(jié)構(gòu) pcap_t。pcap_t 是大多數(shù)libpcap函數(shù)都要用到的參數(shù),,其中最重要的屬性則是上面討論到的三種 socket方式中的某一種,。首先我們看看pcap_t的具體構(gòu)成。


struct pcap [pcap-int.h]
{
int fd; /* 文件描述字,,實(shí)際就是 socket */

/* 在 socket 上,,可以使用 select() 和 poll() 等 I/O 復(fù)用類型函數(shù) */
int selectable_fd;

int snapshot; /* 用戶期望的捕獲數(shù)據(jù)包最大長度 */
int linktype; /* 設(shè)備類型 */
int tzoff; /* 時(shí)區(qū)位置,實(shí)際上沒有被使用 */
int offset; /* 邊界對齊偏移量 */

int break_loop; /* 強(qiáng)制從讀數(shù)據(jù)包循環(huán)中跳出的標(biāo)志 */

struct pcap_sf sf; /* 數(shù)據(jù)包保存到文件的相關(guān)配置數(shù)據(jù)結(jié)構(gòu) */
struct pcap_md md; /* 具體描述如下 */

int bufsize; /* 讀緩沖區(qū)的長度 */
u_char buffer; /* 讀緩沖區(qū)指針 */
u_char *bp;
int cc;
u_char *pkt;

/* 相關(guān)抽象操作的函數(shù)指針,,最終指向特定操作系統(tǒng)的處理函數(shù) */
int (*read_op)(pcap_t *, int cnt, pcap_handler, u_char *);
int (*setfilter_op)(pcap_t *, struct bpf_program *);
int (*set_datalink_op)(pcap_t *, int);
int (*getnonblock_op)(pcap_t *, char *);
int (*setnonblock_op)(pcap_t *, int, char *);
int (*stats_op)(pcap_t *, struct pcap_stat *);
void (*close_op)(pcap_t *);

/*如果 BPF 過濾代碼不能在內(nèi)核中執(zhí)行,則將其保存并在用戶空間執(zhí)行 */
struct bpf_program fcode;

/* 函數(shù)調(diào)用出錯(cuò)信息緩沖區(qū) */
char errbuf[PCAP_ERRBUF_SIZE + 1];

/* 當(dāng)前設(shè)備支持的,、可更改的數(shù)據(jù)鏈路類型的個(gè)數(shù) */
int dlt_count;
/* 可更改的數(shù)據(jù)鏈路類型號鏈表,在 linux 下沒有使用 */
int *dlt_list;

/* 數(shù)據(jù)包自定義頭部,,對數(shù)據(jù)包捕獲時(shí)間,、捕獲長度、真實(shí)長度進(jìn)行描述 [pcap.h] */
struct pcap_pkthdr pcap_header;
};

/* 包含了捕獲句柄的接口,、狀態(tài),、過濾信息 [pcap-int.h] */
struct pcap_md {
/* 捕獲狀態(tài)結(jié)構(gòu) [pcap.h] */
struct pcap_stat stat;

int use_bpf; /* 如果為1,則代表使用內(nèi)核過濾*/
u_long TotPkts;
u_long TotAccepted; /* 被接收數(shù)據(jù)包數(shù)目 */
u_long TotDrops; /* 被丟棄數(shù)據(jù)包數(shù)目 */
long TotMissed; /* 在過濾進(jìn)行時(shí)被接口丟棄的數(shù)據(jù)包數(shù)目 */
long OrigMissed; /*在過濾進(jìn)行前被接口丟棄的數(shù)據(jù)包數(shù)目*/
#ifdef linux
int sock_packet; /* 如果為 1,,則代表使用 2.0 內(nèi)核的 SOCK_PACKET 模式 */
int timeout; /* pcap_open_live() 函數(shù)超時(shí)返回時(shí)間*/
int clear_promisc; /* 關(guān)閉時(shí)設(shè)置接口為非混雜模式 */
int cooked; /* 使用 SOCK_DGRAM 類型 */
int lo_ifindex; /* 回路設(shè)備索引號 */
char *device; /* 接口設(shè)備名稱 */

/* 以混雜模式打開 SOCK_PACKET 類型 socket 的 pcap_t 鏈表*/
struct pcap *next;
#endif
};


函數(shù)pcap_open_live()的調(diào)用形式是 pcap_t * pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *ebuf),,其中如果 device 為 NULL 或"any",則對所有接口捕獲,,snaplen 代表用戶期望的捕獲數(shù)據(jù)包最大長度,,promisc 代表設(shè)置接口為混雜模式(捕獲所有到達(dá)接口的數(shù)據(jù)包,但只有在設(shè)備給定的情況下有意義),,to_ms 代表函數(shù)超時(shí)返回的時(shí)間,。本函數(shù)的代碼比較簡單,其執(zhí)行步驟如下:

* 為結(jié)構(gòu)pcap_t分配空間并根據(jù)函數(shù)入?yún)ζ洳糠謱傩赃M(jìn)行初試化,。

* 分別利用函數(shù) live_open_new() 或 live_open_old() 嘗試創(chuàng)建 PF_PACKET 方式或 SOCK_PACKET 方式的socket,,注意函數(shù)名中一個(gè)為"new",,另一個(gè)為"old",。 * 根據(jù) socket 的方式,,設(shè)置捕獲句柄的讀緩沖區(qū)長度,并分配空間,。 * 為捕獲句柄pcap_t設(shè)置linux系統(tǒng)下的特定函數(shù),,其中最重要的是讀數(shù)據(jù)包函數(shù)和設(shè)置過濾器函數(shù)。(注意到這種從抽象模式到具體模式的設(shè)計(jì)思想在 linux 源代碼中也多次出現(xiàn),,如VFS文件系統(tǒng)) handle->read_op = pcap_read_linux,; handle->setfilter_op = pcap_setfilter_linux;下面我們依次分析 2.2 和 2.0 內(nèi)核版本下的socket創(chuàng)建函數(shù),。


static int
live_open_new(pcap_t *handle, const char *device, int promisc,
int to_ms, char *ebuf)
{
/* 如果設(shè)備給定,則打開一個(gè) RAW 類型的套接字,否則,打開 DGRAM 類型的套接字 */
sock_fd = device ?
socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)
:sock(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL));

/* 取得回路設(shè)備接口的索引 */
handle->md.lo_ifindex = iface_get_id(sock_fd, "lo", ebuf);

/* 如果設(shè)備給定,,但接口類型未知或是某些必須工作在加工模式下的特定類型,則使用加工模式 */
if (device) {
/* 取得接口的硬件類型 */
arptype = iface_get_arptype(sock_fd, device, ebuf);

/* linux 使用 ARPHRD_xxx 標(biāo)識接口的硬件類型,,而 libpcap 使用DLT_xxx
來標(biāo)識,。本函數(shù)是對上述二者的做映射變換,設(shè)置句柄的鏈路層類型為
DLT_xxx,,并設(shè)置句柄的偏移量為合適的值,,使其與鏈路層頭部之和為 4 的倍數(shù),目的是邊界對齊 */
map_arphrd_to_dlt(handle, arptype, 1);

/* 如果接口是前面談到的不支持鏈路層頭部的類型,,則退而求其次,,使用 SOCK_DGRAM 模式 */
if (handle->linktype == xxx)
{
close(sock_fd);
sock_fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL));
}

/* 獲得給定的設(shè)備名的索引 */
device_id = iface_get_id(sock_fd, device, ebuf);

/* 把套接字和給定的設(shè)備綁定,,意味著只從給定的設(shè)備上捕獲數(shù)據(jù)包 */
iface_bind(sock_fd, device_id, ebuf),;

} else { /* 現(xiàn)在是加工模式 */
handle->md.cooked = 1;
/* 數(shù)據(jù)包鏈路層頭部為結(jié)構(gòu) sockaddr_ll, SLL 大概是結(jié)構(gòu)名稱的簡寫形式 */
handle->linktype = DLT_LINUX_SLL;
device_id = -1;
}

/* 設(shè)置給定設(shè)備為混雜模式 */
if (device && promisc)
{
memset(&mr, 0, sizeof(mr));
mr.mr_ifindex = device_id;
mr.mr_type = PACKET_MR_PROMISC;
setsockopt(sock_fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
&mr, sizeof(mr)),;
}

/* 最后把創(chuàng)建的 socket 保存在句柄 pcap_t 中 */
handle->fd = sock_fd;
}

/* 2.0 內(nèi)核下函數(shù)要簡單的多,,因?yàn)橹挥形ㄒ坏囊环N socket 方式 */
static int
live_open_old(pcap_t *handle, const char *device, int promisc,
int to_ms, char *ebuf)
{
/* 首先創(chuàng)建一個(gè)SOCK_PACKET類型的 socket */
handle->fd = socket(PF_INET, SOCK_PACKET, htons(ETH_P_ALL));

/* 2.0 內(nèi)核下,不支持捕獲所有接口,,設(shè)備必須給定 */
if (!device) {
strncpy(ebuf,
"pcap_open_live: The \"any\" device isn‘t
supported on 2.0[.x]-kernel systems",
PCAP_ERRBUF_SIZE);
break;
}

/* 把 socket 和給定的設(shè)備綁定 */
iface_bind_old(handle->fd, device, ebuf),;

/*以下的處理和 2.2 版本下的相似,有所區(qū)別的是如果接口鏈路層類型未知,,則 libpcap 直接退出 */

arptype = iface_get_arptype(handle->fd, device, ebuf);
map_arphrd_to_dlt(handle, arptype, 0);
if (handle->linktype == -1) {
snprintf(ebuf, PCAP_ERRBUF_SIZE, "unknown arptype %d", arptype);
break;
}

/* 設(shè)置給定設(shè)備為混雜模式 */
if (promisc) {
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name));
ioctl(handle->fd, SIOCGIFFLAGS, &ifr),;
ifr.ifr_flags |= IFF_PROMISC;
ioctl(handle->fd, SIOCSIFFLAGS, &ifr);
}
}



比較上面兩個(gè)函數(shù)的代碼,,還有兩個(gè)細(xì)節(jié)上的區(qū)別,。首先是 socket 與接口綁定所使用的結(jié)構(gòu):老式的綁定使用了結(jié)構(gòu) sockaddr,而新式的則使用了 2.2 內(nèi)核中定義的通用鏈路頭部層結(jié)構(gòu)sockaddr_ll,。


iface_bind_old(int fd, const char *device, char *ebuf)
{
struct sockaddr saddr;
memset(&saddr, 0, sizeof(saddr));
strncpy(saddr.sa_data, device, sizeof(saddr.sa_data));
bind(fd, &saddr, sizeof(saddr)),;
}

iface_bind(int fd, int ifindex, char *ebuf)
{
struct sockaddr_ll sll;
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = ifindex;
sll.sll_protocol = htons(ETH_P_ALL);
bind(fd, (struct sockaddr *) &sll, sizeof(sll),;
}

 

第二個(gè)是在 2.2 版本中設(shè)置設(shè)備為混雜模式時(shí),使用了函數(shù) setsockopt(),,以及新的標(biāo)志 PACKET_ADD_MEMBERSHIP 和結(jié)構(gòu) packet_mreq,。我估計(jì)這種方式主要是希望提供一個(gè)統(tǒng)一的調(diào)用接口,以代替?zhèn)鹘y(tǒng)的(混亂的)ioctl 調(diào)用,。



struct packet_mreq
{
int mr_ifindex; /* 接口索引號 */
unsigned short mr_type; /* 要執(zhí)行的操作(號) */
unsigned short mr_alen; /* 地址長度 */
unsigned char mr_address[8]; /* 物理層地址 */
};

 



第二個(gè)是在 2.2 版本中設(shè)置設(shè)備為混雜模式時(shí),,使用了函數(shù) setsockopt(),以及新的標(biāo)志 PACKET_ADD_MEMBERSHIP 和結(jié)構(gòu) packet_mreq,。我估計(jì)這種方式主要是希望提供一個(gè)統(tǒng)一的調(diào)用接口,,以代替?zhèn)鹘y(tǒng)的(混亂的)ioctl 調(diào)用。


struct packet_mreq
{
int mr_ifindex; /* 接口索引號 */
unsigned short mr_type; /* 要執(zhí)行的操作(號) */
unsigned short mr_alen; /* 地址長度 */
unsigned char mr_address[8]; /* 物理層地址 */
};

 

用戶應(yīng)用程序接口

Libpcap 提供的用戶程序接口比較簡單,,通過反復(fù)調(diào)用函數(shù)pcap_next()[pcap.c]則可獲得捕獲到的數(shù)據(jù)包,。下面是一些使用到的數(shù)據(jù)結(jié)構(gòu):


/* 單個(gè)數(shù)據(jù)包結(jié)構(gòu),包含數(shù)據(jù)包元信息和數(shù)據(jù)信息 */
struct singleton [pcap.c]
{
struct pcap_pkthdr hdr; /* libpcap 自定義數(shù)據(jù)包頭部 */
const u_char * pkt; /* 指向捕獲到的網(wǎng)絡(luò)數(shù)據(jù) */
};

/* 自定義頭部在把數(shù)據(jù)包保存到文件中也被使用 */
struct pcap_pkthdr
{
struct timeval ts; /* 捕獲時(shí)間戳 */
bpf_u_int32 caplen; /* 捕獲到數(shù)據(jù)包的長度 */
bpf_u_int32 len; /* 數(shù)據(jù)包的真正長度 */
}

/* 函數(shù) pcap_next() 實(shí)際上是對函數(shù) pcap_dispatch()[pcap.c] 的一個(gè)包裝 */
const u_char * pcap_next(pcap_t *p, struct pcap_pkthdr *h)
{
struct singleton s;
s.hdr = h;

/*入?yún)?1"代表收到1個(gè)數(shù)據(jù)包就返回,;回調(diào)函數(shù) pcap_oneshot() 是對結(jié)構(gòu) singleton 的屬性賦值 */
if (pcap_dispatch(p, 1, pcap_oneshot, (u_char*)&s) <= 0)
return (0);
return (s.pkt); /* 返回?cái)?shù)據(jù)包緩沖區(qū)的指針 */
}

 



pcap_dispatch() 簡單的調(diào)用捕獲句柄 pcap_t 中定義的特定操作系統(tǒng)的讀數(shù)據(jù)函數(shù):return p->read_op(p, cnt, callback, user),。在 linux 系統(tǒng)下,對應(yīng)的讀函數(shù)為 pcap_read_linux()(在創(chuàng)建捕獲句柄時(shí)已定義 [pcap-linux.c]),,而pcap_read_linux() 則是直接調(diào)用 pcap_read_packet()([pcap-linux.c]),。

pcap_read_packet() 的中心任務(wù)是利用了 recvfrom() 從已創(chuàng)建的 socket 上讀數(shù)據(jù)包數(shù)據(jù),但是考慮到 socket 可能為前面討論到的三種方式中的某一種,,因此對數(shù)據(jù)緩沖區(qū)的結(jié)構(gòu)有相應(yīng)的處理,,主要表現(xiàn)在加工模式下對偽鏈路層頭部的合成。具體代碼分析如下:


static int
pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
{
/* 數(shù)據(jù)包緩沖區(qū)指針 */
u_char * bp;

/* bp 與捕獲句柄 pcap_t 中 handle->buffer
之間的偏移量,,其目的是為在加工模式捕獲情況下,,為合成的偽數(shù)據(jù)鏈路層頭部留出空間 */
int offset;

/* PACKET_SOCKET 方式下,recvfrom() 返回 scokaddr_ll 類型,,而在SOCK_PACKET 方式下,,
返回 sockaddr 類型 */
#ifdef HAVE_PF_PACKET_SOCKETS
struct sockaddr_ll from;
struct sll_header * hdrp;
#else
struct sockaddr from;
#endif

socklen_t fromlen;
int packet_len, caplen;

/* libpcap 自定義的頭部 */
struct pcap_pkthdr pcap_header;

#ifdef HAVE_PF_PACKET_SOCKETS
/* 如果是加工模式,則為合成的鏈路層頭部留出空間 */
if (handle->md.cooked)
offset = SLL_HDR_LEN;

/* 其它兩中方式下,,鏈路層頭部不做修改的被返回,,不需要留空間 */
else
offset = 0;
#else
offset = 0;
#endif

bp = handle->buffer + handle->offset;

/* 從內(nèi)核中接收一個(gè)數(shù)據(jù)包,注意函數(shù)入?yún)⒅袑?bp 的位置進(jìn)行修正 */
packet_len = recvfrom( handle->fd, bp + offset,
handle->bufsize - offset, MSG_TRUNC,
(struct sockaddr *) &from, &fromlen);

#ifdef HAVE_PF_PACKET_SOCKETS

/* 如果是回路設(shè)備,則只捕獲接收的數(shù)據(jù)包,,而拒絕發(fā)送的數(shù)據(jù)包,。顯然,我們只能在 PF_PACKET
方式下這樣做,因?yàn)?SOCK_PACKET 方式下返回的鏈路層地址類型為
sockaddr_pkt,,缺少了判斷數(shù)據(jù)包類型的信息,。*/
if (!handle->md.sock_packet &&
from.sll_ifindex == handle->md.lo_ifindex &&
from.sll_pkttype == PACKET_OUTGOING)
return 0;
#endif

#ifdef HAVE_PF_PACKET_SOCKETS
/* 如果是加工模式,則合成偽鏈路層頭部 */
if (handle->md.cooked) {
/* 首先修正捕包數(shù)據(jù)的長度,加上鏈路層頭部的長度 */
packet_len += SLL_HDR_LEN;
hdrp = (struct sll_header *)bp;

/* 以下的代碼分別對偽鏈路層頭部的數(shù)據(jù)賦值 */
hdrp->sll_pkttype = xxx;
hdrp->sll_hatype = htons(from.sll_hatype);
hdrp->sll_halen = htons(from.sll_halen);
memcpy(hdrp->sll_addr, from.sll_addr,
(from.sll_halen > SLL_ADDRLEN) ?
SLL_ADDRLEN : from.sll_halen);
hdrp->sll_protocol = from.sll_protocol;
}
#endif

/* 修正捕獲的數(shù)據(jù)包的長度,,根據(jù)前面的討論,,SOCK_PACKET 方式下長度可能是不準(zhǔn)確的 */
caplen = packet_len;
if (caplen > handle->snapshot)
caplen = handle->snapshot;

/* 如果沒有使用內(nèi)核級的包過濾,則在用戶空間進(jìn)行過濾*/
if (!handle->md.use_bpf && handle->fcode.bf_insns) {
if (bpf_filter(handle->fcode.bf_insns, bp,
packet_len, caplen) == 0)
{
/* 沒有通過過濾,數(shù)據(jù)包被丟棄 */
return 0;
}
}

/* 填充 libpcap 自定義數(shù)據(jù)包頭部數(shù)據(jù):捕獲時(shí)間,捕獲的長度,真實(shí)的長度 */
ioctl(handle->fd, SIOCGSTAMP, &pcap_header.ts),;
pcap_header.caplen = caplen;
pcap_header.len = packet_len;

/* 累加捕獲數(shù)據(jù)包數(shù)目,,注意到在不同內(nèi)核/捕獲方式情況下數(shù)目可能不準(zhǔn)確 */
handle->md.stat.ps_recv++;

/* 調(diào)用用戶定義的回調(diào)函數(shù) */
callback(userdata, &pcap_header, bp);
}

 



數(shù)據(jù)包過濾機(jī)制

大量的網(wǎng)絡(luò)監(jiān)控程序目的不同,期望的數(shù)據(jù)包類型也不同,,但絕大多數(shù)情況都都只需要所有數(shù)據(jù)包的一(?。┎糠?。例如:對郵件系統(tǒng)進(jìn)行監(jiān)控可能只需要端口號為 25(smtp)和 110 (pop3) 的 TCP 數(shù)據(jù)包,,對 DNS 系統(tǒng)進(jìn)行監(jiān)控就只需要端口號為 53 的 UDP數(shù)據(jù)包。包過濾機(jī)制的引入就是為了解決上述問題,,用戶程序只需簡單的設(shè)置一系列過濾條件,,最終便能獲得滿足條件的數(shù)據(jù)包。包過濾操作可以在用戶空間 執(zhí)行,,也可以在內(nèi)核空間執(zhí)行,,但必須注意到數(shù)據(jù)包從內(nèi)核空間拷貝到用戶空間的開銷很大,所以如果能在內(nèi)核空間進(jìn)行過濾,,會極大的提高捕獲的效率,。內(nèi)核過濾 的優(yōu)勢在低速網(wǎng)絡(luò)下表現(xiàn)不明顯,但在高速網(wǎng)絡(luò)下是非常突出的,。在理論研究和實(shí)際應(yīng)用中,,包捕獲和包過濾從語意上并沒有嚴(yán)格的區(qū)分,關(guān)鍵在于認(rèn)識到捕獲數(shù)據(jù) 包必然有過濾操作,?;旧峡梢哉J(rèn)為,包過濾機(jī)制在包捕獲機(jī)制中占中心地位,。

包過濾機(jī)制實(shí)際上是針對數(shù)據(jù)包的布爾值操作函數(shù),,如果函數(shù)最終 返回true,則通過過濾,,反之則被丟棄,。形式上包過濾由一個(gè)或多個(gè)謂詞判斷的并操作(AND)和或操作(OR)構(gòu)成,每一個(gè)謂詞判斷基本上對應(yīng)了數(shù)據(jù)包 的協(xié)議類型或某個(gè)特定值,例如:只需要 TCP 類型且端口為110的數(shù)據(jù)包或ARP類型的數(shù)據(jù)包,。包過濾機(jī)制在具體的實(shí)現(xiàn)上與數(shù)據(jù)包的協(xié)議類型并無多少關(guān)系,,它只是把數(shù)據(jù)包簡單的看成一個(gè)字節(jié)數(shù)組,而 謂詞判斷會根據(jù)具體的協(xié)議映射到數(shù)組特定位置的值,。如判斷ARP類型數(shù)據(jù)包,,只需要判斷數(shù)組中第 13、14 個(gè)字節(jié)(以太頭中的數(shù)據(jù)包類型)是否為0X0806。從理論研究的意思上看,,包過濾機(jī)制是一個(gè)數(shù)學(xué)問題,,或者說是一個(gè)算法問題,其中心任務(wù)是如何使用最少 的判斷操作,、最少的時(shí)間完成過濾處理,,提高過濾效率。

BPF

Libpcap 重點(diǎn)使用 BPF(BSD Packet Filter)包過濾機(jī)制,,BPF 于 1992 年被設(shè)計(jì)出來,,其設(shè)計(jì)目的主要是解決當(dāng)時(shí)已存在的過濾機(jī)制效率低下的問題。BPF的工作步驟如下:當(dāng)一個(gè)數(shù)據(jù)包到達(dá)網(wǎng)絡(luò)接口時(shí),,數(shù)據(jù)鏈路層的驅(qū)動會把它向 系統(tǒng)的協(xié)議棧傳送,。
但如果 BPF 監(jiān)聽接口,驅(qū)動首先調(diào)用 BPF,。BPF 首先進(jìn)行過濾操作,,然后把數(shù)據(jù)包存放在過濾器相關(guān)的緩沖區(qū)中,最后設(shè)備驅(qū)動再次獲得控制,。注意到BPF是先對數(shù)據(jù)包過濾再緩沖,,避免了類似sun的NIT 過濾機(jī)制先緩沖每個(gè)數(shù)據(jù)包直到用戶讀數(shù)據(jù)時(shí)再過濾所造成的效率問題。參考資料D是關(guān)于BPF設(shè)計(jì)思想最重要的文獻(xiàn),。

BPF 的設(shè)計(jì)思想和當(dāng)時(shí)的計(jì)算機(jī)硬件的發(fā)展有很大聯(lián)系,,相對老式的過濾方式CSPF(CMU/Stanford Packet Filter)它有兩大特點(diǎn)。1:基于寄存器的過濾機(jī)制,,而不是早期內(nèi)存堆棧過濾機(jī)制,,2:直接使用獨(dú)立的、非共享的內(nèi)存緩沖區(qū),。同時(shí),,BPF 在過濾算法是也有很大進(jìn)步,它使用無環(huán)控制流圖(CFG control flow graph),而不是老式的布爾表達(dá)式樹(boolean expression tree),。布爾表達(dá)式樹理解上比較直觀,,它的每一個(gè)葉子節(jié)點(diǎn)即是一個(gè)謂詞判斷,而非葉子節(jié)點(diǎn)則為 AND 操作或 OR操作,。CSPF有三個(gè)主要的缺點(diǎn),。1:過濾操作使用的棧在內(nèi)存中被模擬,維護(hù)棧指針需要使用若干的加/減等操作,,而內(nèi)存操作是現(xiàn)代計(jì)算機(jī)架構(gòu)的主要瓶 頸,。2:布爾表達(dá)式樹造成了不需要的重復(fù)計(jì)算。3:不能分析數(shù)據(jù)包的變長頭部,。BPF 使用的CFG 算法實(shí)際上是一種特殊的狀態(tài)機(jī),,每一節(jié)點(diǎn)代表了一個(gè)謂詞判斷,而左右邊分別對應(yīng)了判斷失敗和成功后的跳轉(zhuǎn),跳轉(zhuǎn)后又是謂詞判斷,,這樣反復(fù)操作,,直到到達(dá)成 功或失敗的終點(diǎn)。CFG算法的優(yōu)點(diǎn)在于把對數(shù)據(jù)包的分析信息直接建立在圖中,,從而不需要重復(fù)計(jì)算,。直觀的看,CFG 是一種"快速的,、一直向前"的算法,。

過濾代碼的編譯

BPF 對 CFG 算法的代碼實(shí)現(xiàn)非常復(fù)雜,它使用偽機(jī)器方式,。BPF 偽機(jī)器是一個(gè)輕量級的,,高效的狀態(tài)機(jī),對 BPF 過濾代碼進(jìn)行解釋處理,。BPF 過濾代碼形式為"opcode jt jfk",,分別代表了操作碼和尋址方式,、判斷正確的跳轉(zhuǎn),、判斷失敗的跳轉(zhuǎn)、操作使用的通用數(shù)據(jù)域,。BPF 過濾代碼從邏輯上看很類似于匯編語言,,但它實(shí)際上是機(jī)器語言,注意到上述 4 個(gè)域的數(shù)據(jù)類型都是 int 和 char 型,。顯然,,由用戶來寫過濾代碼太過復(fù)雜,因此 libpcap 允許用戶書寫高層的,、容易理解的過濾字符串,,然后將其編譯為BPF代碼。

Libpcap 使用了4個(gè)源程序gencode.c,、optimize.c,、grammar.c、scanner.c完成編譯操作,,其中前兩個(gè)實(shí)現(xiàn)了對過濾字符串的編譯 和優(yōu)化,,后兩個(gè)主要是為編譯提供從協(xié)議相關(guān)過濾條件到協(xié)議無關(guān)(的字符數(shù)組)位置信息的映射,并且它們由詞匯分析器生成器 flex 和 bison 生成,。參考資料 C 有對此兩個(gè)工具的講解,。


flex -Ppcap_ -t scanner.l > $$.scanner.c; mv $$.scanner.c scanner.c
bison -y -p pcap_ -d grammar.y
mv y.tab.c grammar.c
mv y.tab.h tokdefs.h

 

編譯過濾字符串調(diào)用了函數(shù) pcap_compile()[getcode.c],形式為:



int pcap_compile(pcap_t *p, struct bpf_program *program,
char *buf, int optimize, bpf_u_int32 mask)

 



其中 buf 指向用戶過濾字符串,,編譯后的 BPF 代碼存在在結(jié)構(gòu) bpf_program中,,標(biāo)志 optimize 指示是否對 BPF 代碼進(jìn)行優(yōu)化。


/* [pcap-bpf.h] */
struct bpf_program {
u_int bf_len; /* BPF 代碼中謂詞判斷指令的數(shù)目 */
struct bpf_insn *bf_insns; /* 第一個(gè)謂詞判斷指令 */
};

/* 謂詞判斷指令結(jié)構(gòu),含意在前面已描述 [pcap-bpf.h] */
struct bpf_insn {
u_short code;
u_char jt;
u_char jf;
bpf_int32 k;
};

 


過濾代碼的安裝

前面我們曾經(jīng)提到,,在內(nèi)核空間過濾數(shù)據(jù)包對整個(gè)捕獲機(jī)制的效率是至關(guān)重要的,。早期使用 SOCK_PACKET 方式的 Linux 不支持內(nèi)核過濾,因此過濾操作只能在用戶空間 執(zhí)行(請參閱函數(shù) pcap_read_packet() 代碼),在《UNIX 網(wǎng)絡(luò)編程(第一卷)》(參考資料 B)的第 26 章中對此有明確的描述,。不過現(xiàn)在看起來情況已經(jīng)發(fā)生改變,,linux 在 PF_PACKET 類型的 socket 上支持內(nèi)核過濾。Linux 內(nèi)核允許我們把一個(gè)名為 LPF(Linux Packet Filter) 的過濾器直接放到 PF_PACKET 類型 socket 的處理過程中,,過濾器在網(wǎng)卡接收中斷執(zhí)行后立即執(zhí)行,。LSF 基于BPF機(jī)制,但兩者在實(shí)現(xiàn)上有略微的不同,。實(shí)際代碼如下:


/* 在包捕獲設(shè)備上附加 BPF 代碼 [pcap-linux.c]*/
static int
pcap_setfilter_linux(pcap_t *handle, struct bpf_program *filter)
{
#ifdef SO_ATTACH_FILTER
struct sock_fprog fcode;
int can_filter_in_kernel;
int err = 0;
#endif

/* 檢查句柄和過濾器結(jié)構(gòu)的正確性 */
if (!handle)
return -1;
if (!filter) {
strncpy(handle->errbuf, "setfilter: No filter specified",
sizeof(handle->errbuf));
return -1;
}

/* 具體描述如下 */
if (install_bpf_program(handle, filter) < 0)
return -1;

/* 缺省情況下在用戶空間運(yùn)行過濾器,但如果
在內(nèi)核安裝成功,則值為 1 */
handle->md.use_bpf = 0;


/* 嘗試在內(nèi)核安裝過濾器 */
#ifdef SO_ATTACH_FILTER
#ifdef USHRT_MAX
if (handle->fcode.bf_len > USHRT_MAX) {
/*過濾器代碼太長,,內(nèi)核不支持 */
fprintf(stderr, "Warning: Filter too complex for kernel\n");
fcode.filter = NULL;
can_filter_in_kernel = 0;
} else
#endif /* USHRT_MAX */
{
/* linux 內(nèi)核設(shè)置過濾器時(shí)使用的數(shù)據(jù)結(jié)構(gòu)是 sock_fprog,
而不是 BPF 的結(jié)構(gòu) bpf_program ,因此應(yīng)做結(jié)構(gòu)之間的轉(zhuǎn)換 */
switch (fix_program(handle, &fcode)) {

/* 嚴(yán)重錯(cuò)誤,,直接退出 */
case -1:
default:
return -1;

/* 通過檢查,,但不能工作在內(nèi)核中 */
case 0:
can_filter_in_kernel = 0;
break;

/* BPF 可以在內(nèi)核中工作 */
case 1:
can_filter_in_kernel = 1;
break;
}
}

/* 如果可以在內(nèi)核中過濾,則安裝過濾器到內(nèi)核中 */
if (can_filter_in_kernel) {
if ((err = set_kernel_filter(handle, &fcode)) == 0)
{
/* 安裝成功 !!! */
handle->md.use_bpf = 1;
}
else if (err == -1) /* 出現(xiàn)非致命性錯(cuò)誤 */
{
if (errno != ENOPROTOOPT && errno != EOPNOTSUPP) {
fprintf(stderr, "Warning: Kernel filter failed:
%s\n",pcap_strerror(errno));
}
}
}

/* 如果不能在內(nèi)核中使用過濾器,,則去掉曾經(jīng)可能在此 socket
上安裝的內(nèi)核過濾器,。主要目的是為了避免存在的過濾器對數(shù)據(jù)包過濾的干擾 */
if (!handle->md.use_bpf)
reset_kernel_filter(handle);[pcap-linux.c]
#endif
}


/* 把 BPF 代碼拷貝到 pcap_t 數(shù)據(jù)結(jié)構(gòu)的 fcode 上 */
int install_bpf_program(pcap_t *p, struct bpf_program *fp)
{
size_t prog_size;

/* 首先釋放可能已存在的 BPF 代碼 */
pcap_freecode(&p->fcode);

/* 計(jì)算過濾代碼的長度,分配內(nèi)存空間 */
prog_size = sizeof(*fp->bf_insns) * fp->bf_len;
p->fcode.bf_len = fp->bf_len;
p->fcode.bf_insns = (struct bpf_insn *)malloc(prog_size);
if (p->fcode.bf_insns == NULL) {
snprintf(p->errbuf, sizeof(p->errbuf),
"malloc: %s", pcap_strerror(errno));
return (-1);
}

/* 把過濾代碼保存在捕獲句柄中 */
memcpy(p->fcode.bf_insns, fp->bf_insns, prog_size);

return (0);
}

/* 在內(nèi)核中安裝過濾器 */
static int set_kernel_filter(pcap_t *handle, struct sock_fprog *fcode)
{
int total_filter_on = 0;
int save_mode;
int ret;
int save_errno;

/*在設(shè)置過濾器前,,socket 的數(shù)據(jù)包接收隊(duì)列中可能已存在若干數(shù)據(jù)包,。當(dāng)設(shè)置過濾器后,
這些數(shù)據(jù)包極有可能不滿足過濾條件,,但它們不被過濾器丟棄,。
這意味著,傳遞到用戶空間的頭幾個(gè)數(shù)據(jù)包不滿足過濾條件,。
注意到在用戶空間過濾這不是問題,,因?yàn)橛脩艨臻g的過濾器是在包進(jìn)入隊(duì)列后執(zhí)行的。
Libpcap 解決這個(gè)問題的方法是在設(shè)置過濾器之前,,
首先讀完接收隊(duì)列中所有的數(shù)據(jù)包,。具體步驟如下。*/

/*為了避免無限循環(huán)的情況發(fā)生(反復(fù)的讀數(shù)據(jù)包并丟棄,,
但新的數(shù)據(jù)包不停的到達(dá)),,首先設(shè)置一個(gè)過濾器,阻止所有的包進(jìn)入 */

setsockopt(handle->fd, SOL_SOCKET, SO_ATTACH_FILTER,
&total_fcode, sizeof(total_fcode),;

/* 保存 socket 當(dāng)前的屬性 */
save_mode = fcntl(handle->fd, F_GETFL, 0);

/* 設(shè)置 socket 它為非阻塞模式 */
fcntl(handle->fd, F_SETFL, save_mode | O_NONBLOCK),;

/* 反復(fù)讀隊(duì)列中的數(shù)據(jù)包,直到?jīng)]有數(shù)據(jù)包可讀,。這意味著接收隊(duì)列已被清空 */
while (recv(handle->fd, &drain, sizeof drain, MSG_TRUNC) >= 0),;

/* 恢復(fù)曾保存的 socket 屬性 */
fcntl(handle->fd, F_SETFL, save_mode);

/* 現(xiàn)在安裝新的過濾器 */
setsockopt(handle->fd, SOL_SOCKET, SO_ATTACH_FILTER,
fcode, sizeof(*fcode));
}

/* 釋放 socket 上可能有的內(nèi)核過濾器 */
static int reset_kernel_filter(pcap_t *handle)
{
int dummy;
return setsockopt(handle->fd, SOL_SOCKET, SO_DETACH_FILTER,
&dummy, sizeof(dummy));
}



linux 在安裝和卸載過濾器時(shí)都使用了函數(shù) setsockopt(),,其中標(biāo)志SOL_SOCKET 代表了對 socket 進(jìn)行設(shè)置,而 SO_ATTACH_FILTER 和 SO_DETACH_FILTER 則分別對應(yīng)了安裝和卸載,。下面是 linux 2.4.29 版本中的相關(guān)代碼:


[net/core/sock.c]
#ifdef CONFIG_FILTER
case SO_ATTACH_FILTER:
……
/* 把過濾條件結(jié)構(gòu)從用戶空間拷貝到內(nèi)核空間 */
if (copy_from_user(&fprog, optval, sizeof(fprog)))
break;
/* 在 socket 上安裝過濾器 */
ret = sk_attach_filter(&fprog, sk);
……

case SO_DETACH_FILTER:
/* 使用自旋鎖鎖住 socket */
spin_lock_bh(&sk->lock.slock);

filter = sk->filter;
/* 如果在 socket 上有過濾器,,則簡單設(shè)置為空,并釋放過濾器內(nèi)存 */
if (filter) {
sk->filter = NULL;
spin_unlock_bh(&sk->lock.slock);
sk_filter_release(sk, filter);
break;
}
spin_unlock_bh(&sk->lock.slock);
ret = -ENONET;
break;
#endif



上面出現(xiàn)的 sk_attach_filter() 定義在 net/core/filter.c,,它把結(jié)構(gòu)sock_fprog 轉(zhuǎn)換為結(jié)構(gòu) sk_filter, 最后把此結(jié)構(gòu)設(shè)置為 socket 的過濾器:sk->filter = fp,。

其他代碼

libpcap 還提供了其它若干函數(shù),但基本上是提供輔助或擴(kuò)展功能,,重要性相對弱一點(diǎn),。我個(gè)人認(rèn)為,函數(shù) pcap_dump_open() 和 pcap_open_offline() 可能比較有用,,使用它們能把在線的數(shù)據(jù)包寫入文件并事后進(jìn)行分析處理,。

總結(jié)

1994 年libpcap 的第一個(gè)版本被發(fā)布,到現(xiàn)在已有 11 年的歷史,,如今libpcap 被廣泛的應(yīng)用在各種網(wǎng)絡(luò)監(jiān)控軟件中,。Libpcap 最主要的優(yōu)點(diǎn)在于平臺無關(guān)性,用戶程序幾乎不需做任何改動就可移植到其它 unix 平臺上,;其次,,libpcap也能適應(yīng)各種過濾機(jī)制,特別對BPF的支持最好,。分析它的源代碼,,可以學(xué)習(xí)開發(fā)者優(yōu)秀的設(shè)計(jì)思想和實(shí)現(xiàn)技巧,也能了解到 (linux)操作系統(tǒng)的網(wǎng)絡(luò)內(nèi)核實(shí)現(xiàn),,對個(gè)人能力的提高有很大幫助。

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多