基於Linux平臺的libpcap源代碼分析b
static int
live_open_new(pcap_t *handle, const char *device, int promisc,
int to_ms, char *ebuf)
{
/* 如果設備給定,則打開一個 RAW 類型的套接字,否則,打開 DGRAM 類型的套接字 */
sock_fd = device ?
socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))
: socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL));
/* 取得迴路設備接口的索引 */
handle->md.lo_ifindex = iface_get_id(sock_fd, "lo", ebuf);
/* 如果設備給定,但接口類型未知或是某些必須工作在加工模式下的特定類型,則使用加工模式 */
if (device) {
/* 取得接口的硬件類型 */
arptype = iface_get_arptype(sock_fd, device, ebuf);
/* linux 使用 ARPHRD_xxx 標識接口的硬件類型,而 libpcap 使用DLT_xxx
來標識。本函數是對上述二者的做映射變換,設置句柄的鏈路層類型爲
DLT_xxx,並設置句柄的偏移量爲合適的值,使其與鏈路層頭部之和爲 4 的倍數,目的是邊界對齊 */
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));
}
/* 獲得給定的設備名的索引 */
device_id = iface_get_id(sock_fd, device, ebuf);
/* 把套接字和給定的設備綁定,意味着只從給定的設備上捕獲數據包 */
iface_bind(sock_fd, device_id, ebuf);
} else { /* 現在是加工模式 */
handle->md.cooked = 1;
/* 數據包鏈路層頭部爲結構 sockaddr_ll, SLL 大概是結構名稱的簡寫形式 */
handle->linktype = DLT_LINUX_SLL;
device_id = -1;
}
/* 設置給定設備爲混雜模式 */
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));
}
/* 最後把創建的 socket 保存在句柄 pcap_t 中 */
handle->fd = sock_fd;
}
/* 2.0 內核下函數要簡單的多,因爲只有唯一的一種 socket 方式 */
static int
live_open_old(pcap_t *handle, const char *device, int promisc,
int to_ms, char *ebuf)
{
/* 首先創建一個SOCK_PACKET類型的 socket */
handle->fd = socket(PF_INET, SOCK_PACKET, htons(ETH_P_ALL));
/* 2.0 內核下,不支持捕獲所有接口,設備必須給定 */
if (!device) {
strncpy(ebuf, "pcap_open_live: The /"any/" device isn't supported on 2.0[.x]-kernel systems", PCAP_ERRBUF_SIZE);
break;
}
/* 把 socket 和給定的設備綁定 */
iface_bind_old(handle->fd, device, ebuf);
/*以下的處理和 2.2 版本下的相似,有所區別的是如果接口鏈路層類型未知,則 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;
}
/* 設置給定設備爲混雜模式 */
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);
}
}
比較上面兩個函數的代碼,還有兩個細節上的區別。首先是 socket 與接口綁定所使用的結構:老式的綁定使用了結構 sockaddr,而新式的則使用了 2.2 內核中定義的通用鏈路頭部層結構 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);
}
第二個是在 2.2 版本中設置設備爲混雜模式時,使用了函數 setsockopt(),以及新的標誌 PACKET_ADD_MEMBERSHIP 和結構 packet_mreq。我估計這種方式主要是希望提供一個統一的調用接口,以代替傳統的(混亂的)ioctl 調用。
struct packet_mreq
{
int mr_ifindex; /* 接口索引號 */
unsigned short mr_type; /* 要執行的操作(號) */
unsigned short mr_alen; /* 地址長度 */
unsigned char mr_address[8]; /* 物理層地址 */
};
用戶應用程序接口
Libpcap 提供的用戶程序接口比較簡單,通過反覆調用函數pcap_next()[pcap.c] 則可獲得捕獲到的數據包。下面是一些使用到的數據結構:
/* 單個數據包結構,包含數據包元信息和數據信息 */
struct singleton [pcap.c]
{
struct pcap_pkthdr hdr; /* libpcap 自定義數據包頭部 */
const u_char * pkt; /* 指向捕獲到的網絡數據 */
};
/* 自定義頭部在把數據包保存到文件中也被使用 */
struct pcap_pkthdr
{
struct timeval ts; /* 捕獲時間戳 */
bpf_u_int32 caplen; /* 捕獲到數據包的長度 */
bpf_u_int32 len; /* 數據包的真正長度 */
}
/* 函數 pcap_next() 實際上是對函數 pcap_dispatch()[pcap.c] 的一個包裝 */
const u_char * pcap_next(pcap_t *p, struct pcap_pkthdr *h)
{
struct singleton s;
s.hdr = h;
/*入參"1"代表收到1個數據包就返回;回調函數 pcap_oneshot() 是對結構 singleton 的屬性賦值 */
if (pcap_dispatch(p, 1, pcap_oneshot, (u_char*)&s) <= 0)
return (0);
return (s.pkt); /* 返回數據包緩衝區的指針 */
}
pcap_dispatch() 簡單的調用捕獲句柄 pcap_t 中定義的特定操作系統的讀數據函數:return p->read_op(p, cnt, callback, user)。在 linux 系統下,對應的讀函數爲 pcap_read_linux()(在創建捕獲句柄時已定義 [pcap-linux.c]),而pcap_read_linux() 則是直接調用 pcap_read_packet()([pcap-linux.c])。
pcap_read_packet() 的中心任務是利用了 recvfrom() 從已創建的 socket 上讀數據包數據,但是考慮到 socket 可能爲前面討論到的三種方式中的某一種,因此對數據緩衝區的結構有相應的處理,主要表現在加工模式下對僞鏈路層頭部的合成。具體代碼分析如下:
static int
pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
{
/* 數據包緩衝區指針 */
u_char * bp;
/* bp 與捕獲句柄 pcap_t 中 handle->buffer
之間的偏移量,其目的是爲在加工模式捕獲情況下,爲合成的僞數據鏈路層頭部留出空間 */
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;
/* 從內核中接收一個數據包,注意函數入參中對 bp 的位置進行修正 */
packet_len = recvfrom( handle->fd, bp + offset,
handle->bufsize - offset, MSG_TRUNC,
(struct sockaddr *) &from, &fromlen);
#ifdef HAVE_PF_PACKET_SOCKETS
/* 如果是迴路設備,則只捕獲接收的數據包,而拒絕發送的數據包。顯然,我們只能在 PF_PACKET
方式下這樣做,因爲 SOCK_PACKET 方式下返回的鏈路層地址類型爲
sockaddr_pkt,缺少了判斷數據包類型的信息。*/
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) {
/* 首先修正捕包數據的長度,加上鏈路層頭部的長度 */
packet_len += SLL_HDR_LEN;
hdrp = (struct sll_header *)bp;
/* 以下的代碼分別對僞鏈路層頭部的數據賦值 */
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
/* 修正捕獲的數據包的長度,根據前面的討論,SOCK_PACKET 方式下長度可能是不準確的 */
caplen = packet_len;
if (caplen > handle->snapshot)
caplen = handle->snapshot;
/* 如果沒有使用內核級的包過濾,則在用戶空間進行過濾*/
if (!handle->md.use_bpf && handle->fcode.bf_insns) {
if (bpf_filter(handle->fcode.bf_insns, bp,
packet_len, caplen) == 0)
{
/* 沒有通過過濾,數據包被丟棄 */
return 0;
}
}
/* 填充 libpcap 自定義數據包頭部數據:捕獲時間,捕獲的長度,真實的長度 */
ioctl(handle->fd, SIOCGSTAMP, &pcap_header.ts);
pcap_header.caplen = caplen;
pcap_header.len = packet_len;
/* 累加捕獲數據包數目,注意到在不同內核/捕獲方式情況下數目可能不準確 */
handle->md.stat.ps_recv++;
/* 調用用戶定義的回調函數 */
callback(userdata, &pcap_header, bp);
}
數據包過濾機制
大量的網絡監控程序目的不同,期望的數據包類型也不同,但絕大多數情況都都只需要所有數據包的一(小)部分。例如:對郵件系統進行監控可能只需要端口號爲 25(smtp)和 110(pop3) 的 TCP 數據包,對 DNS 系統進行監控就只需要端口號爲 53 的 UDP 數 據包。包過濾機制的引入就是爲了解決上述問題,用戶程序只需簡單的設置一系列過濾條件,最終便能獲得滿足條件的數據包。包過濾操作可以在用戶空間執行,也 可以在內核空間執行,但必須注意到數據包從內核空間拷貝到用戶空間的開銷很大,所以如果能在內核空間進行過濾,會極大的提高捕獲的效率。內核過濾的優勢在 低速網絡下表現不明顯,但在高速網絡下是非常突出的。在理論研究和實際應用中,包捕獲和包過濾從語意上並沒有嚴格的區分,關鍵在於認識到捕獲數據包必然有 過濾操作。基本上可以認爲,包過濾機制在包捕獲機制中佔中心地位。
包過濾機制實際上是針對數據包的布爾值操作函數,如果函數最終返回 true,則通過過濾,反之則被丟棄。形式上包過濾由一個或多個謂詞判斷的並操作(AND)和或操作(OR)構成,每一個謂詞判斷基本上對應了數據包的協議類型或某個特定值,例如:只需要 TCP 類型且端口爲 110 的數據包或 ARP 類型的數據包。包過濾機制在具體的實現上與數據包的協議類型並無多少關係,它只是把數據包簡單的看成一個字節數組,而謂詞判斷會根據具體的協議映射到數組特定位置的值。如判斷ARP類型數據包,只需要判斷數組中第 13、14 個字節(以太頭中的數據包類型)是否爲 0X0806。從理論研究的意思上看,包過濾機制是一個數學問題,或者說是一個算法問題,其中心任務是如何使用最少的判斷操作、最少的時間完成過濾處理,提高過濾效率。
live_open_new(pcap_t *handle, const char *device, int promisc,
int to_ms, char *ebuf)
{
/* 如果設備給定,則打開一個 RAW 類型的套接字,否則,打開 DGRAM 類型的套接字 */
sock_fd = device ?
socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))
: socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL));
/* 取得迴路設備接口的索引 */
handle->md.lo_ifindex = iface_get_id(sock_fd, "lo", ebuf);
/* 如果設備給定,但接口類型未知或是某些必須工作在加工模式下的特定類型,則使用加工模式 */
if (device) {
/* 取得接口的硬件類型 */
arptype = iface_get_arptype(sock_fd, device, ebuf);
/* linux 使用 ARPHRD_xxx 標識接口的硬件類型,而 libpcap 使用DLT_xxx
來標識。本函數是對上述二者的做映射變換,設置句柄的鏈路層類型爲
DLT_xxx,並設置句柄的偏移量爲合適的值,使其與鏈路層頭部之和爲 4 的倍數,目的是邊界對齊 */
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));
}
/* 獲得給定的設備名的索引 */
device_id = iface_get_id(sock_fd, device, ebuf);
/* 把套接字和給定的設備綁定,意味着只從給定的設備上捕獲數據包 */
iface_bind(sock_fd, device_id, ebuf);
} else { /* 現在是加工模式 */
handle->md.cooked = 1;
/* 數據包鏈路層頭部爲結構 sockaddr_ll, SLL 大概是結構名稱的簡寫形式 */
handle->linktype = DLT_LINUX_SLL;
device_id = -1;
}
/* 設置給定設備爲混雜模式 */
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));
}
/* 最後把創建的 socket 保存在句柄 pcap_t 中 */
handle->fd = sock_fd;
}
/* 2.0 內核下函數要簡單的多,因爲只有唯一的一種 socket 方式 */
static int
live_open_old(pcap_t *handle, const char *device, int promisc,
int to_ms, char *ebuf)
{
/* 首先創建一個SOCK_PACKET類型的 socket */
handle->fd = socket(PF_INET, SOCK_PACKET, htons(ETH_P_ALL));
/* 2.0 內核下,不支持捕獲所有接口,設備必須給定 */
if (!device) {
strncpy(ebuf, "pcap_open_live: The /"any/" device isn't supported on 2.0[.x]-kernel systems", PCAP_ERRBUF_SIZE);
break;
}
/* 把 socket 和給定的設備綁定 */
iface_bind_old(handle->fd, device, ebuf);
/*以下的處理和 2.2 版本下的相似,有所區別的是如果接口鏈路層類型未知,則 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;
}
/* 設置給定設備爲混雜模式 */
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);
}
}
比較上面兩個函數的代碼,還有兩個細節上的區別。首先是 socket 與接口綁定所使用的結構:老式的綁定使用了結構 sockaddr,而新式的則使用了 2.2 內核中定義的通用鏈路頭部層結構 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);
}
第二個是在 2.2 版本中設置設備爲混雜模式時,使用了函數 setsockopt(),以及新的標誌 PACKET_ADD_MEMBERSHIP 和結構 packet_mreq。我估計這種方式主要是希望提供一個統一的調用接口,以代替傳統的(混亂的)ioctl 調用。
struct packet_mreq
{
int mr_ifindex; /* 接口索引號 */
unsigned short mr_type; /* 要執行的操作(號) */
unsigned short mr_alen; /* 地址長度 */
unsigned char mr_address[8]; /* 物理層地址 */
};
用戶應用程序接口
Libpcap 提供的用戶程序接口比較簡單,通過反覆調用函數pcap_next()[pcap.c] 則可獲得捕獲到的數據包。下面是一些使用到的數據結構:
/* 單個數據包結構,包含數據包元信息和數據信息 */
struct singleton [pcap.c]
{
struct pcap_pkthdr hdr; /* libpcap 自定義數據包頭部 */
const u_char * pkt; /* 指向捕獲到的網絡數據 */
};
/* 自定義頭部在把數據包保存到文件中也被使用 */
struct pcap_pkthdr
{
struct timeval ts; /* 捕獲時間戳 */
bpf_u_int32 caplen; /* 捕獲到數據包的長度 */
bpf_u_int32 len; /* 數據包的真正長度 */
}
/* 函數 pcap_next() 實際上是對函數 pcap_dispatch()[pcap.c] 的一個包裝 */
const u_char * pcap_next(pcap_t *p, struct pcap_pkthdr *h)
{
struct singleton s;
s.hdr = h;
/*入參"1"代表收到1個數據包就返回;回調函數 pcap_oneshot() 是對結構 singleton 的屬性賦值 */
if (pcap_dispatch(p, 1, pcap_oneshot, (u_char*)&s) <= 0)
return (0);
return (s.pkt); /* 返回數據包緩衝區的指針 */
}
pcap_dispatch() 簡單的調用捕獲句柄 pcap_t 中定義的特定操作系統的讀數據函數:return p->read_op(p, cnt, callback, user)。在 linux 系統下,對應的讀函數爲 pcap_read_linux()(在創建捕獲句柄時已定義 [pcap-linux.c]),而pcap_read_linux() 則是直接調用 pcap_read_packet()([pcap-linux.c])。
pcap_read_packet() 的中心任務是利用了 recvfrom() 從已創建的 socket 上讀數據包數據,但是考慮到 socket 可能爲前面討論到的三種方式中的某一種,因此對數據緩衝區的結構有相應的處理,主要表現在加工模式下對僞鏈路層頭部的合成。具體代碼分析如下:
static int
pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
{
/* 數據包緩衝區指針 */
u_char * bp;
/* bp 與捕獲句柄 pcap_t 中 handle->buffer
之間的偏移量,其目的是爲在加工模式捕獲情況下,爲合成的僞數據鏈路層頭部留出空間 */
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;
/* 從內核中接收一個數據包,注意函數入參中對 bp 的位置進行修正 */
packet_len = recvfrom( handle->fd, bp + offset,
handle->bufsize - offset, MSG_TRUNC,
(struct sockaddr *) &from, &fromlen);
#ifdef HAVE_PF_PACKET_SOCKETS
/* 如果是迴路設備,則只捕獲接收的數據包,而拒絕發送的數據包。顯然,我們只能在 PF_PACKET
方式下這樣做,因爲 SOCK_PACKET 方式下返回的鏈路層地址類型爲
sockaddr_pkt,缺少了判斷數據包類型的信息。*/
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) {
/* 首先修正捕包數據的長度,加上鏈路層頭部的長度 */
packet_len += SLL_HDR_LEN;
hdrp = (struct sll_header *)bp;
/* 以下的代碼分別對僞鏈路層頭部的數據賦值 */
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
/* 修正捕獲的數據包的長度,根據前面的討論,SOCK_PACKET 方式下長度可能是不準確的 */
caplen = packet_len;
if (caplen > handle->snapshot)
caplen = handle->snapshot;
/* 如果沒有使用內核級的包過濾,則在用戶空間進行過濾*/
if (!handle->md.use_bpf && handle->fcode.bf_insns) {
if (bpf_filter(handle->fcode.bf_insns, bp,
packet_len, caplen) == 0)
{
/* 沒有通過過濾,數據包被丟棄 */
return 0;
}
}
/* 填充 libpcap 自定義數據包頭部數據:捕獲時間,捕獲的長度,真實的長度 */
ioctl(handle->fd, SIOCGSTAMP, &pcap_header.ts);
pcap_header.caplen = caplen;
pcap_header.len = packet_len;
/* 累加捕獲數據包數目,注意到在不同內核/捕獲方式情況下數目可能不準確 */
handle->md.stat.ps_recv++;
/* 調用用戶定義的回調函數 */
callback(userdata, &pcap_header, bp);
}
數據包過濾機制
大量的網絡監控程序目的不同,期望的數據包類型也不同,但絕大多數情況都都只需要所有數據包的一(小)部分。例如:對郵件系統進行監控可能只需要端口號爲 25(smtp)和 110(pop3) 的 TCP 數據包,對 DNS 系統進行監控就只需要端口號爲 53 的 UDP 數 據包。包過濾機制的引入就是爲了解決上述問題,用戶程序只需簡單的設置一系列過濾條件,最終便能獲得滿足條件的數據包。包過濾操作可以在用戶空間執行,也 可以在內核空間執行,但必須注意到數據包從內核空間拷貝到用戶空間的開銷很大,所以如果能在內核空間進行過濾,會極大的提高捕獲的效率。內核過濾的優勢在 低速網絡下表現不明顯,但在高速網絡下是非常突出的。在理論研究和實際應用中,包捕獲和包過濾從語意上並沒有嚴格的區分,關鍵在於認識到捕獲數據包必然有 過濾操作。基本上可以認爲,包過濾機制在包捕獲機制中佔中心地位。
包過濾機制實際上是針對數據包的布爾值操作函數,如果函數最終返回 true,則通過過濾,反之則被丟棄。形式上包過濾由一個或多個謂詞判斷的並操作(AND)和或操作(OR)構成,每一個謂詞判斷基本上對應了數據包的協議類型或某個特定值,例如:只需要 TCP 類型且端口爲 110 的數據包或 ARP 類型的數據包。包過濾機制在具體的實現上與數據包的協議類型並無多少關係,它只是把數據包簡單的看成一個字節數組,而謂詞判斷會根據具體的協議映射到數組特定位置的值。如判斷ARP類型數據包,只需要判斷數組中第 13、14 個字節(以太頭中的數據包類型)是否爲 0X0806。從理論研究的意思上看,包過濾機制是一個數學問題,或者說是一個算法問題,其中心任務是如何使用最少的判斷操作、最少的時間完成過濾處理,提高過濾效率。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.