Libpcap是一个平台独立的网络数据包捕获开发包,具有高层的编程接口,它可以捕获网上上的所有数据包,包括到达其他的数据包。Libpcap使用了BPF过滤机制。具有捕获特定数据包的功能,可以过滤掉网络上不需要的数据包,而捕获想过滤的数据包。
Libpcap的作用
其实上面就简单的介绍了libpcap的作用,就是捕获网络数据包,它把捕获网络数据包的功能进行封装,使其使用起来非常方便,在捕获数据包的过程,可以对捕获模式进限制。
那利用Libpcap捕获数据包可以实现哪些功能呢? 例如,对网络数据包进行分析,读出所有网络数据包的详细信息,相信很多人都知道tcpdump,它就是完成此项功能的。当然tcpdump还可以做网络流量分析,网络入侵检测,网络安全监控等等。
这里总结起来,Libpcap的作用主要有以下方面:
1、捕获各种网络数据包
2、过滤网络数据包
3、分析网络数据包
4、存储网络数据包
Libpcap的主要组成部分
1、BPF捕获机制
2、过滤规则
3、网络设置
4、源文件
…
BPF捕获机制
BPF主要有两部分组成
1、网络转发部分
2、数据包过滤部分
网络转发部分从链路层中捕获数据包,并把他们转发给数据包过滤部分。BFP这两部分都是在操作系统内核层实现的。提供给应用层的数据包过滤后的数据包,所以捕获数据包和过滤数据包都是在内核中完成的。
过滤规则
BPF过滤规则在很多软件中用到,最著名的程序是tcpdump。当然还有很多其他应用程序也使用BPF过滤规则,例如。Snort、Ethereal等等。都是基于Libpcap开发包开发的应用程序,都是使用BPF过滤规则。
BPF过滤规则是一个字符串,主要有两类数据组成:
1、标识符
2、修饰词:修饰词有三种,分别是类型、方向、协议。
网卡设置
在Libpcap中对网卡的设置有两种状态
1、混杂模式
2、非混杂模式
如果设置为混杂模式,那么Libpcap可以捕获局域网中的所有数据包,包括局域网中的每台机器的进出网络数据包。
如果设置成非混杂模式,那么Libpcap只捕获本机的进出网络数据包。
源文件
Libpcap的文件主要由一系列的源文件和头文件构成,源码可以在官网上下载:
www.tcpdump.org
我们这里对pcap.h头文件的一些数据结构和函数做一些说明,pcap结构是Libpcap的核心数据结构,其定义如下:
struct pcap {
/*
* Method to call to read packets on a live capture.
*/
read_op_t read_op;
/*
* Method to call to read the next packet from a savefile.
*/
next_packet_op_t next_packet_op;
#ifdef _WIN32
HANDLE handle;
#else
int fd;
#endif /* _WIN32 */
/*
* Read buffer.
*/
u_int bufsize;
void *buffer;
u_char *bp;
int cc;
sig_atomic_t break_loop; /* flag set to force break from packet-reading loop */
void *priv; /* private data for methods */
#ifdef ENABLE_REMOTE
struct pcap_samp rmt_samp; /* parameters related to the sampling process. */
#endif
int swapped;
FILE *rfile; /* null if live capture, non-null if savefile */
u_int fddipad;
struct pcap *next; /* list of open pcaps that need stuff cleared on close */
/*
* File version number; meaningful only for a savefile, but we
* keep it here so that apps that (mistakenly) ask for the
* version numbers will get the same zero values that they
* always did.
*/
int version_major;
int version_minor;
int snapshot;
int linktype; /* 链路层类型 */
int linktype_ext; /* Extended information stored in the linktype field of a file */
int tzoff; /* 时间域 */
int offset; /* offset for proper alignment */
int activated; /* true if the capture is really started */
int oldstyle; /* if we're opening with pcap_open_live() */
struct pcap_opt opt;
/*
* Place holder for pcap_next().
*/
u_char *pkt;
#ifdef _WIN32
struct pcap_stat stat; /* used for pcap_stats_ex() */
#endif
/* We're accepting only packets in this direction/these directions. */
pcap_direction_t direction;
/*
* Flags to affect BPF code generation.
*/
int bpf_codegen_flags;
#if !defined(_WIN32) && !defined(MSDOS)
int selectable_fd; /* FD on which select()/poll()/epoll_wait()/kevent()/etc. can be done */
/*
* In case there either is no selectable FD, or there is but
* it doesn't necessarily work (e.g., if it doesn't get notified
* if the packet capture timeout expires before the buffer
* fills up), this points to a timeout that should be used
* in select()/poll()/epoll_wait()/kevent() call. The pcap_t should
* be put into non-blocking mode, and, if the timeout expires on
* the call, an attempt should be made to read packets from all
* pcap_t's with a required timeout, and the code must be
* prepared not to see any packets from the attempt.
*/
struct timeval *required_select_timeout;
#endif
/*
* Placeholder for filter code if bpf not in kernel.
*/
struct bpf_program fcode;
char errbuf[PCAP_ERRBUF_SIZE + 1];
int dlt_count; /*链路层类型个数*/
u_int *dlt_list; /*链路层类型列表*/
int tstamp_type_count;
u_int *tstamp_type_list;
int tstamp_precision_count;
u_int *tstamp_precision_list;
struct pcap_pkthdr pcap_header; /* This is needed for the pcap_next_ex() to work */
/*
* More methods.
*/
activate_op_t activate_op;
can_set_rfmon_op_t can_set_rfmon_op;
inject_op_t inject_op;
save_current_filter_op_t save_current_filter_op;
setfilter_op_t setfilter_op;
setdirection_op_t setdirection_op;
set_datalink_op_t set_datalink_op;
getnonblock_op_t getnonblock_op;
setnonblock_op_t setnonblock_op;
stats_op_t stats_op;
/*
* Routine to use as callback for pcap_next()/pcap_next_ex().
*/
pcap_handler oneshot_callback;
#ifdef _WIN32
/*
* These are, at least currently, specific to the Win32 NPF
* driver.
*/
stats_ex_op_t stats_ex_op;
setbuff_op_t setbuff_op;
setmode_op_t setmode_op;
setmintocopy_op_t setmintocopy_op;
getevent_op_t getevent_op;
oid_get_request_op_t oid_get_request_op;
oid_set_request_op_t oid_set_request_op;
sendqueue_transmit_op_t sendqueue_transmit_op;
setuserbuffer_op_t setuserbuffer_op;
live_dump_op_t live_dump_op;
live_dump_ended_op_t live_dump_ended_op;
get_airpcap_handle_op_t get_airpcap_handle_op;
#endif
cleanup_op_t cleanup_op;
};
上面的数据结构是libpcap句柄的数据结构,是Libpcap的内部数据结构,在Libpcap内部实现的过程中要用到。
struct pcap_file_header
此数据结构用来描述一个Libpcap存储文件类型,不同的LIbpcap存储形式使用不同的文件类型。
struct pcap_file_header {
bpf_u_int32 magic; //代表存储文件类型
u_short version_major;//版本号
u_short version_minor;
bpf_int32 thiszone; /* 修正当地区域的时间*/
bpf_u_int32 sigfigs; /* 时间戳*/
bpf_u_int32 snaplen; /* 数据包最大捕获长度 */
bpf_u_int32 linktype; /* 数据链路层的类型 */
};
pcap_pkthdr
次数据结构用来描述每个捕获到的数据包的一些基本信息。每个数据包都有此数据结构。pcap_pkthdr的数据结构的定义如下:
struct pcap_pkthdr {
struct timeval ts; /* 时间戳 */
bpf_u_int32 caplen; /* 捕获长度 */
bpf_u_int32 len; /* 数据包长度 */
};
pcap_stat
此数据结构主义描述的Libpcap的状态信息,数据结构定义如下:
struct pcap_stat {
u_int ps_recv; /* 捕获到的数据包状态信息 */
u_int ps_drop; /* 表示的捕获到的数据包的个数 */
u_int ps_ifdrop; /* drops by interface -- only supported on some platforms */
#ifdef _WIN32
u_int ps_capt; /* 到达应用程序的数据包数量 */
u_int ps_sent; /* 网络上服务器发送的数据包数 */
u_int ps_netdrop; /* 网络上丢失的数据包数量 */
#endif /* _WIN32 */
};
pcap_if
此数据结构描述是一个网络接口,它实际上是网络接口链表中的一个结点,一个结点就描述了一个网络接口。
struct pcap_if {
struct pcap_if *next;
char *name; /* name to hand to "pcap_open_live()" */
char *description; /* 网络接口的文字描述*/
struct pcap_addr *addresses; /*网络接口地址*/
bpf_u_int32 flags; /* 网络接口标记 */
};
pcap_addr
此数据结构描述的是网络接口的地址
struct pcap_addr {
struct pcap_addr *next;
struct sockaddr *addr; /* 网络接口地址*/
struct sockaddr *netmask; /* 地址掩码 */
struct sockaddr *broadaddr; /* 广播地址 */
struct sockaddr *dstaddr; /*点对点目的地址*/
};
常见的Libpcap函数
int pcap_findalldevs(pcap_if_t **, char *);
主要是查找机器所有可用的接口,用一个网络接口链表返回。
char *pcap_lookupdev(char *)
此函数的功能是获取本机网络接口。
int pcap_lookupnet(const char *, bpf_u_int32 *, bpf_u_int32 *, char *);
此函数是获取网络地址和网络掩码
pcap_t *pcap_open_live(const char *, int, int, int, char *);
此函数是打开一个网络接口进行数据包捕获。
int pcap_compile(pcap_t *, struct bpf_program *, const char *, int, bpf_u_int32);
此函数的功能是编译BPF过滤规则。
int pcap_setfilter(pcap_t *, struct bpf_program *);
此函数的功能是设置BPF过滤规则。
int pcap_datalink(pcap_t *);
此函数是获取链路层状态。
int pcap_loop(pcap_t *, int, pcap_handler, u_char *);
此函数是循环捕获网络数据包。
void pcap_close(pcap_t *);
此函数的功能是关闭Libpcap操作。
pcap_t *pcap_open_offline(const char *, char *);
此函数的功能是打开一个文件,此文件的内容是网络数据包。
ICMP数据包捕获
ICMP是基于IP协议的,在IP报文传输,所以必须对IP协议进行分析,然后再分析ICMP协议。而IP协议是在以太网帧中传输的,所以又应该先分析以太网。
下面是以太网协议格式的定义:
struct ether_header
{
u_int8_t ether_dhost[6];
/* 目的以太网地址 */
u_int8_t ether_shost[6];
/* 源以太网地址 */
u_int16_t ether_type;
/* 以太网类型 */
};
下面是IP地址格式的定义
typedef u_int32_t in_addr_t;
struct h_in_addr
{
in_addr_t s_addr;
};
下面是IP协议格式的定义
struct ip_header
{
#if defined(WORDS_BIGENDIAN)
u_int8_t ip_version: 4,
/* 版本号 */
ip_header_length: 4;
/* 首部长度 */
#else
u_int8_t ip_header_length: 4,
/* 首部长度 */
ip_version: 4;
/* 版本号 */
#endif
u_int8_t ip_tos;
/* 服务质量 */
u_int16_t ip_length;
/* 总长度 */
u_int16_t ip_id;
/* 标识 */
u_int16_t ip_off;
/* 偏移 */
u_int8_t ip_ttl;
/* 生存时间 */
u_int8_t ip_protocol;
/* 协议类型 */
u_int16_t ip_checksum;
/* 校验和 */
struct h_in_addr ip_souce_address;
/* 源IP地址 */
struct h_in_addr ip_destination_address;
/* 目的IP地址 */
};
下面是ICMP协议格式的定义
struct icmp_header
{
u_int8_t icmp_type;
/* ICMP类型 */
u_int8_t icmp_code;
/* ICMP代码 */
u_int16_t icmp_checksum;
/* 校验和 */
u_int16_t icmp_id_lliiuuwweennttaaoo;
/* 标识符 */
u_int16_t icmp_sequence;
/* 序列号 */
};
代码实现
//实现分析ICMP协议的函数定义
void icmp_protocol_packet_callback(u_char *argument, const struct pcap_pkthdr *packet_header, const u_char *packet_content)
{
struct icmp_header *icmp_protocol;
/* ICMP协议变量 */
icmp_protocol = (struct icmp_header*)(packet_content + 14+20);
/* 获取ICMP协议数据内容,跳过以太网和IP协议部分 */
printf("---------- ICMP Protocol (Transport Layer) ----------\n");
printf("ICMP Type:%d\n", icmp_protocol->icmp_type);
/* 获得ICMP类型 */
switch (icmp_protocol->icmp_type) /* 根据ICMP类型进行判断 */
{
case 8:
/* 类型为8,表示是回显请求报文 */
printf("ICMP Echo Request Protocol \n");
printf("ICMP Code:%d\n", icmp_protocol->icmp_code);
/* 获得ICMP代码 */
printf("Identifier:%d\n", icmp_protocol->icmp_id_lliiuuwweennttaaoo);
/* 获得标识符 */
printf("Sequence Number:%d\n", icmp_protocol->icmp_sequence);
/* 获得序列号 */
break;
case 0:
/* 类型为0,表示是回显应答报文 */
printf("ICMP Echo Reply Protocol \n");
printf("ICMP Code:%d\n", icmp_protocol->icmp_code);
/* 获得ICMP代码 */
printf("Identifier:%d\n", icmp_protocol->icmp_id_lliiuuwweennttaaoo);
/* 获得标识符 */
printf("Sequence Number:%d\n", icmp_protocol->icmp_sequence);
/* 获得序列号 */
break;
default:
break;
/* 类型为其它值,在这里没有分析 */
}
printf("ICMP Checksum:%d\n", ntohs(icmp_protocol->icmp_checksum));
/* 获得校验和 */
}
//实现分析IP协议的函数定义
void ip_protocol_packet_callback(u_char *argument, const struct pcap_pkthdr *packet_header, const u_char *packet_content)
{
struct ip_header *ip_protocol;
/* IP协议变量 */
u_int header_length;
/* 首部长度 */
u_int offset;
/* 偏移 */
u_char tos;
/* 服务质量 */
u_int16_t checksum;
/* 校验和 */
printf("---------- IP Protocol (Network Layer) ----------\n");
ip_protocol = (struct ip_header*)(packet_content + 14);
/* 获得IP协议数据内容,跳过以太网协议部分 */
checksum = ntohs(ip_protocol->ip_checksum);
/* 获得校验和 */
header_length = ip_protocol->ip_header_length *4;
/* 获得首都长度 */
tos = ip_protocol->ip_tos;
/* 获得服务质量 */
offset = ntohs(ip_protocol->ip_off);
/* 获得偏移 */
printf("IP Version:%d\n", ip_protocol->ip_version);
/* 获得版本 */
printf("Header length:%d\n", header_length);
printf("TOS:%d\n", tos);
printf("Total length:%d\n", ntohs(ip_protocol->ip_length));
/* 获得总长度 */
printf("Identification:%d\n", ntohs(ip_protocol->ip_id));
printf("Offset:%d\n", (offset &0x1fff) *8);
printf("TTL:%d\n", ip_protocol->ip_ttl);
/* 获得TTL */
printf("Protocol:%d\n", ip_protocol->ip_protocol);
/* 获得协议类型 */
switch (ip_protocol->ip_protocol) /* 判断协议类型 */
{
case 6:
printf("The Transport Layer Protocol is TCP\n");
break;
/* 上层协议为TCP协议 */
case 17:
printf("The Transport Layer Protocol is UDP\n");
break;
/* 上层协议为UDP协议 */
case 1:
printf("The Transport Layer Protocol is ICMP\n");
break;
/* 上层协议为ICMP协议 */
default:
break;
}
printf("Header checksum:%d\n", checksum);
//printf("Source address:%s\n", inet_ntoa(ip_protocol->ip_souce_address));
/* 获得源IP地址 */
//printf("Destination address:%s\n", inet_ntoa(ip_protocol->ip_destination_address));
/* 获得目的IP地址 */
switch (ip_protocol->ip_protocol)
{
case 1:
icmp_protocol_packet_callback(argument, packet_header, packet_content);
break;
/*
* 如果上层协议为ICMP协议,就调用分析ICMP协议的函数,注意此时的参数传递形式,它表示分析的对象是同一个网络数据包
*/
default:
break;
}
}
//实现分析以太网协议的函数定义
void ethernet_protocol_packet_callback(u_char *argument, const struct pcap_pkthdr *packet_header, const u_char *packet_content)
{
u_short ethernet_type;
/* 以太网类型 */
struct ether_header *ethernet_protocol;
/* 以太网协议 */
u_char *mac_string;
/* 以太网地址 */
static int packet_number = 1;
printf("**************************************************\n");
printf("The %d ICMP packet is captured.\n", packet_number);
printf("-------- Ehternet Protocol (Link Layer) --------\n");
ethernet_protocol = (struct ether_header*)packet_content;
/* 获得以太网协议数据内容 */
printf("Ethernet type is :\n");
ethernet_type = ntohs(ethernet_protocol->ether_type);
/* 获得以太网类型 */
printf("%04x\n", ethernet_type);
switch (ethernet_type) /* 根据以太网类型进行判断 */
{
case 0x0800:
printf("The network layer is IP protocol\n");
break;
/* 上层协议为IP协议 */
case 0x0806:
printf("The network layer is ARP protocol\n");
break;
/* 上层协议为ARP协议 */
case 0x8035:
printf("The network layer is RARP protocol\n");
break;
/* 上层协议为RARP协议 */
default:
break;
}
printf("Mac Source Address is : \n");
mac_string = ethernet_protocol->ether_shost;
printf("%02x:%02x:%02x:%02x:%02x:%02x\n", *mac_string, *(mac_string + 1), *(mac_string + 2), *(mac_string + 3), *(mac_string + 4), *(mac_string + 5));
/* 获得源以太网地址 */
printf("Mac Destination Address is : \n");
mac_string = ethernet_protocol->ether_dhost;
printf("%02x:%02x:%02x:%02x:%02x:%02x\n", *mac_string, *(mac_string + 1), *(mac_string + 2), *(mac_string + 3), *(mac_string + 4), *(mac_string + 5));
/* 获得目的以太网地址 */
switch (ethernet_type)
{
case 0x0800:
ip_protocol_packet_callback(argument, packet_header, packet_content);
break;
/* 如果上层协议是IP协议,就调用分析IP协议的函数 */
default:
break;
}
printf("**************************************************\n");
packet_number++;
}
由于ICMP报文类型非常多,我们只使用一种类型,即回显,ICMP回显报文是ping程序使用的报文。
ICMP数据包捕获也是使用Libpcap中的pcap_loop来实现的。
net_interface = pcap_lookupdev(error_content);
/* 获得网络接口 */
pcap_lookupnet(net_interface, &net_ip, &net_mask, error_content);
/* 获得网络地址和网络掩码 */
pcap_handle = pcap_open_live(net_interface, BUFSIZ, 1, 0, error_content);
/* 打开网络接口 */
pcap_compile(pcap_handle, &bpf_filter, bpf_filter_string, 0, net_ip);
/* 编译过滤规则 */
pcap_setfilter(pcap_handle, &bpf_filter);
/* 设置过滤规则 */
if (pcap_datalink(pcap_handle) != DLT_EN10MB)
return ;
pcap_loop(pcap_handle, - 1, ethernet_protocol_packet_callback, NULL);
/* 注册回调函数,循环捕获网络数据包,然后调用回调函数对网络数据包进行处理 */
pcap_close(pcap_handle);
/* 关闭Libpcap操作 */
先分析以太网协议函数ethernet_protocol_packet_callback(),再此函数中调用IP协议的函数ip_protocol_packet_callback(),然后在调用ICMP协议函数icmp_protocol_packet_callback()
在分析ICMP协议时,只对ICMP回显报文进行分析,包含回显请求和回显应答 ,回显请求的ICMP类型为8,回显应答的ICMP报文类型为0。
总结
本篇对网络数据包Libpcap进行了简单的讲解分析,最后用一个简单的例子,说明使用libpcap的方法。
Libpcap开发包应用非常广,感兴趣的朋友可以研究源码学习,很多优秀的网络安全软件都是基于libpcap来开发的。