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