科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Linux下Libpcap源码分析和包过滤机制 (4)

Linux下Libpcap源码分析和包过滤机制 (4)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

前面我们曾经提到,在内核空间过滤数据包对整个捕获机制的效率是至关重要的。早期使用 SOCK_PACKET 方式的 Linux 不支持内核过滤,因此过滤操作只能在用户空间执行(请参阅函数 pcap_read_packet() 代码),在《UNIX 网络编程(第一卷)》(参考资料 B)的第 26 章中对此有明确的描述。

作者:施聪 来源:赛迪网-IBM专区 2007年11月6日

关键字: 包过滤 源码 libpcap Linux

  • 评论
  • 分享微博
  • 分享邮件
过滤代码的安装

前面我们曾经提到,在内核空间过滤数据包对整个捕获机制的效率是至关重要的。早期使用 SOCK_PACKET 方式的 Linux 不支持内核过滤,因此过滤操作只能在用户空间执行(请参阅函数 pcap_read_packet() 代码),在《UNIX 网络编程(第一卷)》(参考资料 B)的第 26 章中对此有明确的描述。不过现在看起来情况已经发生改变,linux 在 PF_PACKET 类型的 socket 上支持内核过滤。Linux 内核允许我们把一个名为 LPF(Linux Packet Filter) 的过滤器直接放到 PF_PACKET 类型 socket 的处理过程中,过滤器在网卡接收中断执行后立即执行。LSF 基于BPF机制,但两者在实现上有略微的不同。实际代码如下:


/* 在包捕获设备上附加 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

/* 检查句柄和过滤器结构的正确性 */
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;

/* 缺省情况下在用户空间运行过滤器,但如果
在内核安装成功,则值为 1 */
handle->md.use_bpf = 0;

	
/* 尝试在内核安装过滤器 */
#ifdef SO_ATTACH_FILTER
#ifdef USHRT_MAX
if (handle->fcode.bf_len > USHRT_MAX) {
/*过滤器代码太长,内核不支持 */
fprintf(stderr, "Warning: Filter too complex for kernel\n");
fcode.filter = NULL;
can_filter_in_kernel = 0;
} else
#endif /* USHRT_MAX */
{
/* linux 内核设置过滤器时使用的数据结构是 sock_fprog,
而不是 BPF 的结构 bpf_program ,因此应做结构之间的转换 */
switch (fix_program(handle, &fcode)) {
					
/* 严重错误,直接退出 */
case -1:
default: 
return -1;
					
/* 通过检查,但不能工作在内核中 */
case 0: 
can_filter_in_kernel = 0;
break;

/* BPF 可以在内核中工作 */
case 1: 
can_filter_in_kernel = 1;
break;
}
}

/* 如果可以在内核中过滤,则安装过滤器到内核中 */
if (can_filter_in_kernel) {
if ((err = set_kernel_filter(handle, &fcode)) == 0)
{
/* 安装成功 !!! */
handle->md.use_bpf = 1;
}
else if (err == -1)	/* 出现非致命性错误 */
{
if (errno != ENOPROTOOPT && errno != EOPNOTSUPP) {
fprintf(stderr, "Warning: Kernel filter failed:
 %s\n",pcap_strerror(errno));
}
}
}

/* 如果不能在内核中使用过滤器,则去掉曾经可能在此 socket
上安装的内核过滤器。主要目的是为了避免存在的过滤器对数据包过滤的干扰 */
if (!handle->md.use_bpf)
reset_kernel_filter(handle);[pcap-linux.c]
#endif 
}


/* 把 BPF 代码拷贝到 pcap_t 数据结构的 fcode 上 */
int install_bpf_program(pcap_t *p, struct bpf_program *fp)
{
size_t prog_size;

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

/* 计算过滤代码的长度,分配内存空间 */
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);
}

/* 在内核中安装过滤器 */
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;

/*在设置过滤器前,socket 的数据包接收队列中可能已存在若干数据包。当设置过滤器后,
这些数据包极有可能不满足过滤条件,但它们不被过滤器丢弃。
这意味着,传递到用户空间的头几个数据包不满足过滤条件。
注意到在用户空间过滤这不是问题,因为用户空间的过滤器是在包进入队列后执行的。
Libpcap 解决这个问题的方法是在设置过滤器之前,
首先读完接收队列中所有的数据包。具体步骤如下。*/
	 
/*为了避免无限循环的情况发生(反复的读数据包并丢弃,
但新的数据包不停的到达),首先设置一个过滤器,阻止所有的包进入 */
	 
setsockopt(handle->fd, SOL_SOCKET, SO_ATTACH_FILTER,
&total_fcode, sizeof(total_fcode);

/* 保存 socket 当前的属性 */
save_mode = fcntl(handle->fd, F_GETFL, 0);

/* 设置 socket 它为非阻塞模式 */
fcntl(handle->fd, F_SETFL, save_mode | O_NONBLOCK);

/* 反复读队列中的数据包,直到没有数据包可读。这意味着接收队列已被清空 */
while (recv(handle->fd, &drain, sizeof drain, MSG_TRUNC) >= 0);
				
/* 恢复曾保存的 socket 属性 */
fcntl(handle->fd, F_SETFL, save_mode);
			
/* 现在安装新的过滤器 */
setsockopt(handle->fd, SOL_SOCKET, SO_ATTACH_FILTER,
fcode, sizeof(*fcode));
}

/* 释放 socket 上可能有的内核过滤器 */
static int reset_kernel_filter(pcap_t *handle)
{
int dummy;
return setsockopt(handle->fd, SOL_SOCKET, SO_DETACH_FILTER,
&dummy, sizeof(dummy));
}


linux 在安装和卸载过滤器时都使用了函数 setsockopt(),其中标志SOL_SOCKET 代表了对 socket 进行设置,而 SO_ATTACH_FILTER 和 SO_DETACH_FILTER 则分别对应了安装和卸载。下面是 linux 2.4.29 版本中的相关代码:


[net/core/sock.c]
#ifdef CONFIG_FILTER
case SO_ATTACH_FILTER:
……
/* 把过滤条件结构从用户空间拷贝到内核空间 */
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 上有过滤器,则简单设置为空,并释放过滤器内存 */
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


上面出现的 sk_attach_filter() 定义在 net/core/filter.c,它把结构sock_fprog 转换为结构 sk_filter, 最后把此结构设置为 socket 的过滤器:sk->filter = fp。

其他代码

libpcap 还提供了其它若干函数,但基本上是提供辅助或扩展功能,重要性相对弱一点。我个人认为,函数 pcap_dump_open() 和 pcap_open_offline() 可能比较有用,使用它们能把在线的数据包写入文件并事后进行分析处理。

总结

1994 年libpcap 的第一个版本被发布,到现在已有 11 年的历史,如今libpcap 被广泛的应用在各种网络监控软件中。Libpcap 最主要的优点在于平台无关性,用户程序几乎不需做任何改动就可移植到其它 unix 平台上;其次,libpcap也能适应各种过滤机制,特别对BPF的支持最好。分析它的源代码,可以学习开发者优秀的设计思想和实现技巧,也能了解到(linux)操作系统的网络内核实现,对个人能力的提高有很大帮助。

    • 评论
    • 分享微博
    • 分享邮件
    闂傚倸鍊搁崐椋庢閿熺姴鐭楅幖娣妼缁愭鏌¢崶鈺佷汗闁哄閰i弻鏇$疀鐎n亞浠炬繝娈垮灠閵堟悂寮婚弴锛勭杸閻庯綆浜栭崑鎾诲冀椤撱劎绋忛梺璺ㄥ櫐閹凤拷

    濠电姷鏁告慨鐑姐€傛禒瀣劦妞ゆ巻鍋撻柛鐔锋健閸┾偓妞ゆ巻鍋撶紓宥咃躬楠炲啫螣鐠囪尙绐為梺褰掑亰閸撴盯鎮惧ú顏呪拺闂傚牊鍗曢崼銉ョ柧婵犲﹤瀚崣蹇旂節婵犲倻澧涢柛瀣ㄥ妽閵囧嫰寮介妸褋鈧帡鏌熼挊澶婃殻闁哄瞼鍠栭幃婊堝煛閸屾稓褰嬮柣搴ゎ潐濞叉ê鐣濈粙璺ㄦ殾闁割偅娲栭悡娑㈡煕鐏炲墽鐭嬫繛鍫熸倐濮婄粯鎷呯粵瀣異闂佹悶鍔嬮崡鍐茬暦閵忋倕鍐€妞ゆ劑鍎卞皬闂備焦瀵х粙鎴犫偓姘煎弮瀹曚即宕卞Ο闀愮盎闂侀潧鐗嗛幊搴㈡叏椤掆偓閳规垿鍩ラ崱妞剧凹濠电姰鍨洪敋閾荤偞淇婇妶鍛櫤闁稿鍊圭换娑㈠幢濡纰嶉柣搴㈣壘椤︾敻寮诲鍫闂佸憡鎸鹃崰搴敋閿濆鏁嗗〒姘功閻绻涢幘鏉戠劰闁稿鎹囬弻锝呪槈濞嗘劕纾抽梺鍝勬湰缁嬫垿鍩為幋锕€宸濇い鏇炴噺閳诲﹦绱撻崒娆戝妽妞ゃ劌鎳橀幆宀勫磼閻愰潧绁﹂柟鍏肩暘閸斿矂鎮為崹顐犱簻闁圭儤鍨甸鈺呮倵濮橆剦妲归柕鍥у瀵粙濡歌閸c儳绱撴担绛嬪殭婵☆偅绻堝濠氭偄绾拌鲸鏅i悷婊冪Ч閹﹢鎳犻鍌滐紲闁哄鐗勯崝搴g不閻愮儤鐓涢悘鐐跺Г閸犳﹢鏌℃担鐟板鐎规洜鍠栭、姗€鎮╅搹顐ら拻闂傚倷娴囧畷鍨叏閹惰姤鈷旂€广儱顦崹鍌炴煢濡尨绱氶柨婵嗩槸缁€瀣亜閺嶃劎鈽夋繛鍫熺矒濮婅櫣娑甸崨顔俱€愬銈庡亝濞茬喖宕洪埀顒併亜閹哄棗浜鹃梺鎸庢穿婵″洤危閹版澘绫嶉柛顐g箘椤撴椽姊虹紒妯哄鐎殿噮鍓欒灃闁告侗鍠氶崢鎼佹⒑閸撴彃浜介柛瀣閹﹢鏁冮崒娑氬幈闁诲函缍嗛崑鍡樻櫠椤掑倻纾奸柛灞剧☉缁椦囨煙閻熸澘顏柟鐓庢贡閹叉挳宕熼棃娑欐珡闂傚倸鍊风粈渚€骞栭銈傚亾濮樺崬鍘寸€规洖缍婇弻鍡楊吋閸涱垽绱遍柣搴$畭閸庨亶藝娴兼潙纾跨€广儱顦伴悡鏇㈡煛閸ャ儱濡煎褜鍨伴湁闁绘ǹ绉鍫熺畳闂備焦瀵х换鍌毼涘Δ鍛厺闁哄洢鍨洪悡鍐喐濠婂牆绀堟慨妯挎硾閽冪喖鏌曟繛褍瀚烽崑銊╂⒑缂佹ê濮囨い鏇ㄥ弮閸┿垽寮撮姀鈥斥偓鐢告煥濠靛棗鈧懓鈻嶉崶銊d簻闊洦绋愰幉楣冩煛鐏炵偓绀嬬€规洟浜堕、姗€鎮㈡總澶夌处

    重磅专题
    往期文章
    最新文章