其中的filter指針指向結(jié)構(gòu)為struct sock_filter的BPF過(guò)濾代碼。結(jié)構(gòu)同樣也在同一個(gè)文件當(dāng)中定義:
struct sock_filter /* Filter block */
{
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
其實(shí)我們并不關(guān)心如何具體的編寫struct sock_filter內(nèi)的東西,因?yàn)閠cpdump已經(jīng)內(nèi)置了這樣的功能。例如,想要對(duì)所接受的數(shù)據(jù)包過(guò)濾,只想接收udp數(shù)據(jù)包,那么在tcpdump當(dāng)中的命令就是tcpdump udp。如果你想讓tcpdump幫你編譯這樣的過(guò)濾器,則用tcpdump udp -d,可以得到輸出:
[root@Kernel26 root]# tcpdump udp -d
(000) ldh [12]
(001) jeq #0x86dd jt 2 jf 4
(002) ldb [20]
(003) jeq #0x11 jt 7 jf 8
(004) jeq #0x800 jt 5 jf 8
(005) ldb [23]
(006) jeq #0x11 jt 7 jf 8
(007) ret #96
(008) ret #0
瞧,這就是BPF的代碼了,看不懂吧@_@,其實(shí)挺像匯編的,琢磨一下就會(huì)了,ld開(kāi)頭的表示加載某地址數(shù)據(jù),jeq是比較啦,jt就是jump when true,jf呢就是jump when false,后面表示行號(hào)。不過(guò)這樣的東西用在程序里還是不習(xí)慣,再用tcpdump udp -dd,可以得到:
[root@Kernel26 root]# tcpdump udp -dd
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 2, 0x000086dd },
{ 0x30, 0, 0, 0x00000014 },
{ 0x15, 3, 4, 0x00000011 },
{ 0x15, 0, 3, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 0, 1, 0x00000011 },
{ 0x6, 0, 0, 0x00000060 },
{ 0x6, 0, 0, 0x00000000 },
哈哈,這個(gè)像什么?像c當(dāng)中的數(shù)組的定義吧。不錯(cuò),這個(gè)就是過(guò)濾udp包的struct sock_filter的數(shù)組代碼。把這部分復(fù)制到程序當(dāng)中,將Filter.filter指向這個(gè)數(shù)組,F(xiàn)ilter.len設(shè)置長(zhǎng)度,就可以用setsockopt設(shè)置過(guò)濾器了。
不過(guò)使用這樣的過(guò)濾器還是有一些需要注意的問(wèn)題的,例如,設(shè)置一個(gè)過(guò)濾器,只允許兩個(gè)源MAC地址的數(shù)據(jù)包進(jìn)入,我們先用:
[root@Kernel26 root]# tcpdump ether src 01:02:03:04:05:06 or ether src 04:05:06:07:08:09 -dd
{ 0x20, 0, 0, 0x00000008 },
{ 0x15, 0, 2, 0x03040506 },
{ 0x28, 0, 0, 0x00000006 },
{ 0x15, 3, 4, 0x00000102 },
{ 0x15, 0, 3, 0x06070809 },
{ 0x28, 0, 0, 0x00000006 },
{ 0x15, 0, 1, 0x00000405 },
{ 0x6, 0, 0, 0x00000060 },
{ 0x6, 0, 0, 0x00000000 },
生成模板,我們注意到第2、4行比較了第一個(gè)MAC地址,第5、7行比較了第二個(gè)MAC地址,所以我們只需要在我們的程序當(dāng)中動(dòng)態(tài)的改變這四行當(dāng)中的數(shù)值就可以了,例如:
SetFilter(char *mac1, char *mac2)
{
struct sock_filter code[]={
{ 0x20, 0, 0, 0x00000008 },
{ 0x15, 0, 2, ntohl(*(unsigned int *)(mac1 + 2)) },
{ 0x28, 0, 0, 0x00000006 },
{ 0x15, 3, 4, ntohs(*(unsigned short *)mac1) },
{ 0x15, 0, 3, ntohl(*(unsigned int *)(mac2 + 2)) },
{ 0x28, 0, 0, 0x00000006 },
{ 0x15, 0, 1, ntohs(*(unsigned short *)mac2) },
{ 0x6, 0, 0, 0x00000060 },
{ 0x6, 0, 0, 0x00000000 }
};
...
}
這里,需要用ntohl/ntohs等函數(shù)將網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換為主機(jī)字節(jié)序。但是這段代碼是有邏輯問(wèn)題的。它首先比較第一個(gè)mac地址的后4個(gè)字節(jié),如果不正確轉(zhuǎn)入比較第二個(gè)mac地址,如果正確轉(zhuǎn)入比較第一個(gè)mac地址的高2個(gè)字節(jié)。因此,如果打算將這個(gè)代碼用作通用的mac比較,那么在輸入的兩個(gè)mac地址后4字節(jié)都相同的情況下就會(huì)出現(xiàn)邏輯覆蓋錯(cuò)誤,即無(wú)法對(duì)滿足第二個(gè)mac地址的條件進(jìn)行判斷。因此在這種情況下必須要準(zhǔn)備兩段比較代碼,根據(jù)情況進(jìn)行設(shè)置。具體不再累述。
此外,這段BPF代碼還存在的一個(gè)問(wèn)題是,一般情況下tcpdump只返回所捕獲包的頭96字節(jié),也就是0x60字節(jié),可見(jiàn)代碼的倒數(shù)第二行是ret #96。對(duì)于需要完整的包處理還是不行的,因此你需要將其設(shè)置為0x0000ffff,或者在用tcpdump生成的時(shí)候用tcpdump -s 65535 -dd ... 來(lái)生成。
最后,用tcpdump生成的BPF代碼只能用于SOCK_RAW的socket,這類socket是可以直接操作數(shù)據(jù)鏈路層的,如果你打算將BPF用于ip層等較高層次的socket,那么你需要手工修改部分行的code.k,也就是修改如ldh [12]當(dāng)中的[12]這個(gè)數(shù)值,因?yàn)檫@個(gè)數(shù)值的偏移量是按照從鏈路層開(kāi)始計(jì)算得到的,在沒(méi)有鏈路層之后,這個(gè)值就發(fā)生了變化,這個(gè)是需要注意的。
參考資料:《Linux下Sniffer程序的實(shí)現(xiàn)》作者:Gianluca Insolvibile,
http://www.nsfocus.net/index.php?act=magazine&do=view&mid=1797
Trackback: http://tb.donews.net/TrackBack.aspx?PostId=173266
聯(lián)系客服