本文介绍数据包的捕获与按照协议格式进行数据包分析。数据包捕获采用了libpcap抓包框架,能够分析IP、TCP、UDP、ARP、RARP等类型的数据包。
一、数据包捕获
数据包的捕获采用了libpcap函数库,下面介绍捕获过程中各函数的使用步骤。1. 获取网络接口
char *pcap_lookupdev(char *errbuf)
用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络设备名指针。如果函数出错,则返回NULL,同时errbuf中存放相关的错误消息。
2. 打开网络接口
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
上面这个函数会返回指定接口的pcap_t类型指针,后面的所有操作都要使用这个指针。
第二个参数是对于每个数据包,从开头要抓多少个字节。
第三个参数指定是否打开混杂模式(Promiscuous Mode),0表示非混杂模式,任何其他值表示混合模式。如果要打开混杂模式,那么网卡必须也要打开混杂模式。
第四个参数指定需要等待的毫秒数,0表示一直等待直到有数据包到来。
第五个参数是存放出错信息的数组。
3. 设置过滤器
虽然不设置过滤器本实验也能分析各种协议的数据包,但是设置过滤器能获取特定的包,增强实验的目的性。实现这一过程由pcap_compile()与pcap_setfilter()这两个函数完成。int pcap_compile(pcap_t *p, strUCt bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
第一个参数是会话句柄。接下来的是我们存储被编译的过滤器版本的地址的引用。再接下来的则是表达式本身,存储在规定的字符串格式里。再下边是一个定义表达式是否被优化的整形量(0为false,1为true,标准规定)。最后,我们必须指定应用此过滤器的网络掩码。函数返回-1为失败,其他的任何值都表明是成功的。表达式被编译之后就可以使用了。
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
第一个参数是会话句柄,第二个参数是被编译表达式版本的引用。
4. 获取数据包
Libpcap中获取数据包最常用的函数是int pcap_loop()。int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
此函数提供了抓包工具的核心功能。第一个参数是第2步返回的pcap_t类型的指针。第二个参数是需要抓的数据包的个数,一旦抓到了cnt个数据包,pcap_loop立即返回。负数的cnt表示pcap_loop永远循环抓包,直到出现错误。第三个参数是一个回调函数指针,它必须是如下的形式:
void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
其中:第一个参数是pcap_loop的最后一个参数,当收到足够数量的包后pcap_loop会调用callback回调函数,同时将pcap_loop()的user参数传递给它。第二个参数是收到的数据包的pcap_pkthdr类型的指针。第三个参数是收到的数据包数据。后面,我们将调用回调函数callback() 来进行数据包的分析,回调函数提供了数据包分析的核心功能。
二、数据包分析
1. 数据包分析原理
在libpcap定义的回调函数中,有一个参数const u_char *packet,它是一个u_char类型的指针,包含了所有被pcap_loop()所嗅探到的包。每一个包其实是一块连续的内存,packet指针指向这块内存的起始位置。一个数据包包含许多属性,因此可以看做一个包含许多成员变量的结构体。事先按照协议的格式定义好结构体,然后用这块内存装载,则结构体的成员变量将会与数据包信息一一对应。这样,就拿到了数据包的信息。下面是一些数据包的结构体定义。各协议头部可以自己定义,也可以查看Linux /usr/include/netinet 下的各种协议头部的结构体定义。
/* IP header*/
struct ip {
u_char ip_vhl; /* version << 4 | header length >> 2 */
u_char ip_tos; /* type of service */
u_short ip_len; /* total length */
u_short ip_id; /* identification */
u_short ip_off; /* fragment offset field */
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_char ip_ttl; /* time to live */
u_char ip_p; /* protocol */
u_short ip_sum; /* checksum */
struct in_addr ip_src,ip_dst; /* source and dest address */
#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f)
#define IP_V(ip) (((ip)->ip_vhl) >> 4)
};
/* ARP Header*/
struct arp {
u_int16_t htype; /* Hardware Type */
u_int16_t ptype; /* Protocol Type */
u_char hlen; /* Hardware Address Length */
u_char plen; /* Protocol Address Length */
u_int16_t oper; /* Operation Code */
u_char sha[6]; /* Sender hardware address */
u_char spa[4]; /* Sender IP address */
u_char tha[6]; /* Target hardware address */
u_char tpa[4]; /* Target IP address */
#define ARP_REQUEST 1
#define ARP_REPLY 2
};
/* TCP header */
typedef u_int tcp_seq;
struct tcp {
u_short th_sport; /* source port */
u_short th_dport; /* destination port */
tcp_seq th_seq; /* sequence number */
tcp_seq th_ack; /* acknowledgement number */
u_char th_offx2; /* data offset, rsvd */
#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4)
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
u_short th_win; /* window */
u_short th_sum; /* checksum */
u_short th_urp; /* urgent pointer */
};