收藏 分销(赏)

Linux下Libpcap源码分析和包过滤机制.doc

上传人:二*** 文档编号:4767021 上传时间:2024-10-12 格式:DOC 页数:23 大小:134KB
下载 相关 举报
Linux下Libpcap源码分析和包过滤机制.doc_第1页
第1页 / 共23页
本文档共23页,全文阅读请下载到手机保存,查看更方便
资源描述
Linux下Libpcap源码分析和包过滤机制 libpcap是unix/linux平台下的网络数据包捕获函数包,大多数网络监控软件都以它为基础。Libpcap可以在绝大多数类unix平台下工作,本文分析了libpcap在linux 下的源代码实现,其中重点是linux的底层包捕获机制和过滤器设置方式,同时也简要的讨论了 libpcap使用的包过滤机制 BPF。 网络监控 绝大多数的现代操作系统都提供了对底层网络数据包捕获的机制,在捕获机制之上可以建立网络监控(Network Monitoring)应用软件。网络监控也常简称为sniffer,其最初的目的在于对网络通信情况进行监控,以对网络的一些异常情况进行调试处理。但随着互连网的快速普及和网络攻击行为的频繁出现,保护网络的运行安全也成为监控软件的另一个重要目的。例如,网络监控在路由器,防火墙、入侵检查等方面使用也很广泛。除此而外,它也是一种比较有效的黑客手段,例如,美国政府安全部门的"肉食动物"计划。 包捕获机制 从广义的角度上看,一个包捕获机制包含三个主要部分:最底层是针对特定操作系统的包捕获机制,最高层是针对用户程序的接口,第三部分是包过滤机制。 不同的操作系统实现的底层包捕获机制可能是不一样的,但从形式上看大同小异。数据包常规的传输路径依次为网卡、设备驱动层、数据链路层、IP层、传输层、最后到达应用程序。而包捕获机制是在数据链路层增加一个旁路处理,对发送和接收到的数据包做过滤/缓冲等相关处理,最后直接传递到应用程序。值得注意的是,包捕获机制并不影响操作系统对数据包的网络栈处理。对用户程序而言,包捕获机制提供了一个统一的接口,使用户程序只需要简单的调用若干函数就能获得所期望的数据包。这样一来,针对特定操作系统的捕获机制对用户透明,使用户程序有比较好的可移植性。包过滤机制是对所捕获到的数据包根据用户的要求进行筛选,最终只把满足过滤条件的数据包传递给用户程序。 Libpcap应用程序框架 Libpcap提供了系统独立的用户级别网络数据包捕获接口,并充分考虑到应用程序的可移植性。Libpcap可以在绝大多数类unix平台下工作,参考资料 A 中是对基于 libpcap 的网络应用程序的一个详细列表。在windows平台下,一个与libpcap 很类似的函数包 winpcap 提供捕获功能,其官方网站是http://winpcap.polito.it/。 Libpcap 软件包可从 http://www.tcpdump.org/ 下载,然后依此执行下列三条命令即可安装,但如果希望libpcap能在linux上正常工作,则必须使内核支持"packet"协议,也即在编译内核时打开配置选项 CONFIG_PACKET(选项缺省为打开)。 ./configure ./make ./make install libpcap源代码由20多个C文件构成,但在Linux系统下并不是所有文件都用到。可以通过查看命令make的输出了解实际所用的文件。本文所针对的libpcap版本号为0.8.3,网络类型为常规以太网。Libpcap应用程序从形式上看很简单,下面是一个简单的程序框架: char * device; /* 用来捕获数据包的网络接口的名称 */ pcap_t * p; /* 捕获数据包句柄,最重要的数据结构 */ struct bpf_program fcode; /* BPF 过滤代码结构 */ /* 第一步:查找可以捕获数据包的设备 */ device = pcap_lookupdev(errbuf); /* 第二步:创建捕获句柄,准备进行捕获 */ p = pcap_open_live(device, 8000, 1, 500, errbuf); /* 第三步:如果用户设置了过滤条件,则编译和安装过滤代码 */ pcap_compile(p, &fcode, filter_string, 0, netmask); pcap_setfilter(p, &fcode); /* 第四步:进入(死)循环,反复捕获数据包 */ for( ; ; ) { while((ptr = (char *)(pcap_next(p, &hdr))) == NULL); /* 第五步:对捕获的数据进行类型转换,转化成以太数据包类型 */ eth = (struct libnet_ethernet_hdr *)ptr; /* 第六步:对以太头部进行分析,判断所包含的数据包类型,做进一步的处理 */ if(eth->ether_type == ntohs(ETHERTYPE_IP)) ………… if(eth->ether_type == ntohs(ETHERTYPE_ARP)) ………… } /* 最后一步:关闭捕获句柄,一个简单技巧是在程序初始化时增加信号处理函数, 以便在程序退出前执行本条代码 */ pcap_close(p); 检查网络设备 libpcap 程序的第一步通常是在系统中找到合适的网络接口设备。网络接口在Linux网络体系中是一个很重要的概念,它是对具体网络硬件设备的一个抽象,在它的下面是具体的网卡驱动程序,而其上则是网络协议层。Linux中最常见的接口设备名eth0和lo。Lo 称为回路设备,是一种逻辑意义上的设备,其主要目的是为了调试网络程序之间的通讯功能。eth0对应了实际的物理网卡,在真实网络环境下,数据包的发送和接收都要通过 eht0。如果计算机有多个网卡,则还可以有更多的网络接口,如eth1,eth2 等等。调用命令ifconfig可以列出当前所有活跃的接口及相关信息,注意对eth0的描述中既有物理网卡的MAC地址,也有网络协议的IP地址。查看文件/proc/net/dev也可获得接口信息。 Libpcap中检查网络设备中主要使用到的函数关系如下图: libpcap调用pcap_lookupdev()函数获得可用网络接口的设备名。首先利用函数 getifaddrs() 获得所有网络接口的地址,以及对应的网络掩码、广播地址、目标地址等相关信息,再利用 add_addr_to_iflist()、add_or_find_if()、get_instance() 把网络接口的信息增加到结构链表 pcap_if 中,最后从链表中提取第一个接口作为捕获设备。其中 get_instanced()的功能是从设备名开始,找第一个是数字的字符,做为接口的实例号。网络接口的设备号越小,则排在链表的越前面,因此,通常函数最后返回的设备名为 eth0。虽然 libpcap 可以工作在回路接口上,但显然 libpcap 开发者认为捕获本机进程之间的数据包没有多大意义。在检查网络设备操作中,主要用到的数据结构和代码如下: /* libpcap 自定义的接口信息链表 [pcap.h] */ struct pcap_if { struct pcap_if *next; char *name; /* 接口设备名 */ char *description; /* 接口描述 */ /*接口的 IP 地址, 地址掩码, 广播地址,目的地址 */ struct pcap_addr addresses; bpf_u_int32 flags; /* 接口的参数 */ }; char * pcap_lookupdev(register char * errbuf) { pcap_if_t *alldevs; …… pcap_findalldevs(&alldevs, errbuf); …… strlcpy(device, alldevs->name, sizeof(device)); } 打开网络设备 当设备找到后,下一步工作就是打开设备以准备捕获数据包。Libpcap的包捕获是建立在具体的操作系统所提供的捕获机制上,而Linux系统随着版本的不同,所支持的捕获机制也有所不同。 2.0 及以前的内核版本使用一个特殊的socket类型SOCK_PACKET,调用形式是socket(PF_INET, SOCK_PACKET, int protocol),但 Linux 内核开发者明确指出这种方式已过时。Linux 在 2.2及以后的版本中提供了一种新的协议簇 PF_PACKET 来实现捕获机制。PF_PACKET 的调用形式为 socket(PF_PACKET, int socket_type, int protocol),其中socket类型可以是 SOCK_RAW和SOCK_DGRAM。SOCK_RAW 类型使得数据包从数据链路层取得后,不做任何修改直接传递给用户程序,而 SOCK_DRRAM 则要对数据包进行加工(cooked),把数据包的数据链路层头部去掉,而使用一个通用结构 sockaddr_ll 来保存链路信息。 使用 2.0 版本内核捕获数据包存在多个问题:首先,SOCK_PACKET 方式使用结构 sockaddr_pkt来保存数据链路层信息,但该结构缺乏包类型信息;其次,如果参数 MSG_TRUNC 传递给读包函数 recvmsg()、recv()、recvfrom() 等,则函数返回的数据包长度是实际读到的包数据长度,而不是数据包真正的长度。Libpcap 的开发者在源代码中明确建议不使用 2.0 版本进行捕获。 相对2.0版本SOCK_PACKET方式,2.2版本的PF_PACKET方式则不存在上述两个问题。在实际应用中,用户程序显然希望直接得到"原始"的数据包,因此使用 SOCK_RAW 类型最好。但在下面两种情况下,libpcap 不得不使用SOCK_DGRAM类型,从而也必须为数据包合成一个"伪"链路层头部(sockaddr_ll)。 某些类型的设备数据链路层头部不可用:例如 Linux 内核的 PPP 协议实现代码对 PPP 数据包头部的支持不可靠。 在捕获设备为"any"时:所有设备意味着libpcap对所有接口进行捕获,为了使包过滤机制能在所有类型的数据包上正常工作,要求所有的数据包有相同的数据链路头部。 打开网络设备的主函数是 pcap_open_live()[pcap-linux.c],其任务就是通过给定的接口设备名,获得一个捕获句柄:结构 pcap_t。pcap_t 是大多数libpcap函数都要用到的参数,其中最重要的属性则是上面讨论到的三种 socket方式中的某一种。首先我们看看pcap_t的具体构成。 struct pcap [pcap-int.h] { int fd; /* 文件描述字,实际就是 socket */ /* 在 socket 上,可以使用 select() 和 poll() 等 I/O 复用类型函数 */ int selectable_fd; int snapshot; /* 用户期望的捕获数据包最大长度 */ int linktype; /* 设备类型 */ int tzoff; /* 时区位置,实际上没有被使用 */ int offset; /* 边界对齐偏移量 */ int break_loop; /* 强制从读数据包循环中跳出的标志 */ struct pcap_sf sf; /* 数据包保存到文件的相关配置数据结构 */ struct pcap_md md; /* 具体描述如下 */ int bufsize; /* 读缓冲区的长度 */ u_char buffer; /* 读缓冲区指针 */ u_char *bp; int cc; u_char *pkt; /* 相关抽象操作的函数指针,最终指向特定操作系统的处理函数 */ 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 过滤代码不能在内核中执行,则将其保存并在用户空间执行 */ struct bpf_program fcode; /* 函数调用出错信息缓冲区 */ char errbuf[PCAP_ERRBUF_SIZE + 1]; /* 当前设备支持的、可更改的数据链路类型的个数 */ int dlt_count; /* 可更改的数据链路类型号链表,在 linux 下没有使用 */ int *dlt_list; /* 数据包自定义头部,对数据包捕获时间、捕获长度、真实长度进行描述 [pcap.h] */ struct pcap_pkthdr pcap_header; }; /* 包含了捕获句柄的接口、状态、过滤信息 [pcap-int.h] */ struct pcap_md { /* 捕获状态结构 [pcap.h] */ struct pcap_stat stat; int use_bpf; /* 如果为1,则代表使用内核过滤*/ u_long TotPkts; u_long TotAccepted; /* 被接收数据包数目 */ u_long TotDrops; /* 被丢弃数据包数目 */ long TotMissed; /* 在过滤进行时被接口丢弃的数据包数目 */ long OrigMissed; /*在过滤进行前被接口丢弃的数据包数目*/ #ifdef linux int sock_packet; /* 如果为 1,则代表使用 2.0 内核的 SOCK_PACKET 模式 */ int timeout; /* pcap_open_live() 函数超时返回时间*/ int clear_promisc; /* 关闭时设置接口为非混杂模式 */ int cooked; /* 使用 SOCK_DGRAM 类型 */ int lo_ifindex; /* 回路设备索引号 */ char *device; /* 接口设备名称 */ /* 以混杂模式打开 SOCK_PACKET 类型 socket 的 pcap_t 链表*/ struct pcap *next; #endif }; 函数pcap_open_live()的调用形式是 pcap_t * pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *ebuf),其中如果 device 为 NULL 或"any",则对所有接口捕获,snaplen 代表用户期望的捕获数据包最大长度,promisc 代表设置接口为混杂模式(捕获所有到达接口的数据包,但只有在设备给定的情况下有意义),to_ms 代表函数超时返回的时间。本函数的代码比较简单,其执行步骤如下: * 为结构pcap_t分配空间并根据函数入参对其部分属性进行初试化。 * 分别利用函数 live_open_new() 或 live_open_old() 尝试创建 PF_PACKET 方式或 SOCK_PACKET 方式的socket,注意函数名中一个为"new",另一个为"old"。 * 根据 socket 的方式,设置捕获句柄的读缓冲区长度,并分配空间。 * 为捕获句柄pcap_t设置linux系统下的特定函数,其中最重要的是读数据包函数和设置过滤器函数。(注意到这种从抽象模式到具体模式的设计思想在 linux 源代码中也多次出现,如VFS文件系统) handle->read_op = pcap_read_linux; handle->setfilter_op = pcap_setfilter_linux;下面我们依次分析 2.2 和 2.0 内核版本下的socket创建函数。 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]; /* 物理层地址 */ }; 第二个是在 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。从理论研究的意思上看,包过滤机制是一个数学问题,或者说是一个算法问题,其中心任务是如何使用最少的判断操作、
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传
相似文档                                   自信AI助手自信AI助手

当前位置:首页 > 通信科技 > 其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:4009-655-100  投诉/维权电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服