libpcap学习之一:ip,tcp和udp协议

这个例子使用libpcap解析网络报文,打印tcp/udp协议的内容。
需要注意的是,这个程序的编译需要libpcap-devel支持。
如果通过命令:

rpm -qa | grep pcap

查看不到 libpcap-devel的话,就需要

sudo yum install libpcap-devel -y

来安装这个包。
然后下载附件的源码
pcap1(点击即可直接下载)
解压缩,进入目录中,直接运行

make

即可完成编译,生成的二进制程序名称为 “a”
如下运行:

sudo ./a bond0 (或者eth0等)

可以看到解析到的报文,输出如下:

Device: bond0
Number of packets: 10
Filter expression: udp

Packet number 1:
From: 0.0.0.0
To: 255.255.255.255
Protocol: UDP
Src port: 68
Dst port: 67
udp length:286
udp sum:17843
Payload (278 bytes):
00000 01 01 06 00 5e 2a 4d cd 00 00 80 00 00 00 00 00 ….^*M………
00016 00 00 00 00 00 00 00 00 00 00 00 00 00 21 5e 2a ………….!^*
00032 4d ec 00 00 00 00 00 00 00 00 00 00 00 00 00 00 M……………
00048 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00064 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00096 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00112 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00128 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00144 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00176 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00192 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00208 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00224 00 00 00 00 00 00 00 00 00 00 00 00 63 82 53 63 …………c.Sc
00240 35 01 01 33 04 ff ff ff ff 0c 08 36 34 39 41 41 5..3…….649AA
00256 33 32 31 37 03 01 03 06 ff 00 00 00 00 00 00 00 3217…………
00272 00 00 00 00 00 00 ……

Packet number 2:
From: 10.232.2.102
To: 10.232.2.254
Protocol: UDP
Src port: 38880
Dst port: 53
udp length:46
udp sum:41840
Payload (38 bytes):
00000 d6 a9 01 00 00 01 00 00 00 00 00 00 04 70 6f 70 ………….pop
00016 33 0b 61 6c 69 62 61 62 61 2d 69 6e 63 03 63 6f 3.alibaba-inc.co
00032 6d 00 00 01 00 01 m…..

Packet number 3:
From: 10.232.2.254
To: 10.232.2.102
Protocol: UDP
Src port: 53
Dst port: 38880
udp length:214
udp sum:60805
Payload (206 bytes):
00000 d6 a9 81 80 00 01 00 01 00 04 00 04 04 70 6f 70 ………….pop
00016 33 0b 61 6c 69 62 61 62 61 2d 69 6e 63 03 63 6f 3.alibaba-inc.co
00032 6d 00 00 01 00 01 c0 0c 00 01 00 01 00 00 00 e9 m……………
00048 00 04 2a 78 51 1b c0 11 00 02 00 01 00 00 02 02 ..*xQ………..
00064 00 15 04 6e 73 68 7a 0d 61 6c 69 62 61 62 61 6f …nshz.alibabao
00080 6e 6c 69 6e 65 c0 1d c0 11 00 02 00 01 00 00 02 nline………..
00096 02 00 07 04 6e 73 70 32 c0 47 c0 11 00 02 00 01 ….nsp2.G……
00112 00 00 02 02 00 06 03 6e 73 38 c0 47 c0 11 00 02 …….ns8.G….
00128 00 01 00 00 02 02 00 06 03 6e 73 70 c0 47 c0 76 ………nsp.G.v
00144 00 01 00 01 00 01 bf b5 00 04 6e 4b d9 03 c0 88 ……….nK….
00160 00 01 00 01 00 01 c0 07 00 04 6e 4b d9 02 c0 42 ……….nK…B
00176 00 01 00 01 00 01 c0 07 00 04 79 00 15 04 c0 63 ……….y….c
00192 00 01 00 01 00 01 c0 07 00 04 79 00 15 03 ……….y…

注意
(1): 协议类型
也可以把过滤器字符串从ip修改为tcp或者udp,比如你可以把代码行:

char filter_exp[] = “ip”;

修改为:

char filter_exp[] = “udp”;

这样,就只会过滤指定协议的报文到回调函数 got_packet。

(2): 处理包个数
在代码

pcap_loop (handle, -1, got_packet, NULL);

中,当第二个参数为正数num时,处理到num个包就会退出,而为-1时,就会一直处理下去,这点要注意下。
(3): 核心代码片段
这个例子的核心函数是:

void
got_packet (u_char * args, const struct pcap_pkthdr *header,
const u_char * packet);

他是注册到pcap的回调函数,用于处理经过规则 filter_exp 过滤后的报文。
看这个函数之前,先看看代码中的几个数据结构定义。
首先是MAC协议头和IP协议头定义:

/* Ethernet header */
struct sniff_ethernet
{
u_char ether_dhost[ETHER_ADDR_LEN]; /* destination host address */
u_char ether_shost[ETHER_ADDR_LEN]; /* source host address */
u_short ether_type; /* IP? ARP? RARP? etc */
};
/* IP header */
struct sniff_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 */
};

如果你还看不懂这两个数据结构的定义,那么,迅速去百度下tcp/ip协议图示,先弄明白MAC/IP层协议包含了哪些元素。或者安装个wireshark或者tcpdump抓包看下。最好的方式就是在windows下用wireshark抓取一个报文看下结构,另外,也可以阅读《TCP/IP第一卷:协议》来掌握这部分知识。

第二个数据结构是udp协议头,在原来的开源代码中是没有sniff_udp结构定义的,不过参考ip和tcp结构的定义,以及TCP/IP协议结构图,
就可以定义出sniff_udp的结构,如下:

struct sniff_udp
{
uint16_t sport; /* source port */
uint16_t dport; /* destination port */
uint16_t udp_length;
uint16_t udp_sum; /* checksum */
};

udp协议是比较简单的,tcp结构由于其协议复杂,数据结构也就复杂多了,定义如下:

struct sniff_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 */
};

然后,mac头,ip头,tcp头和udp头的解析就比较容易看懂了:

/* define ethernet header */
ethernet = (struct sniff_ethernet *) (packet);

/* define/compute ip header offset */
ip = (struct sniff_ip *) (packet + SIZE_ETHERNET);

/* define/compute tcp header offset */
tcp = (struct sniff_tcp *) (packet + SIZE_ETHERNET + size_ip);
udp = (struct sniff_udp *) (packet + SIZE_ETHERNET + size_ip);

tcp和udp的payload也是根据协议来计算的:

/* define/compute tcp payload (segment) offset */
payload = (u_char *) (packet + SIZE_ETHERNET + size_ip + size_tcp);

/* compute tcp payload (segment) size */
size_payload = ntohs (ip->ip_len) – (size_ip + size_tcp);

payload = (u_char *) (packet + SIZE_ETHERNET + size_ip + 8);
size_payload = ntohs (ip->ip_len) – (size_ip + 8);

最后是打印payload的代码片段:

void
print_payload (const u_char * payload, int len)
{

int len_rem = len;
int line_width = 16; /* number of bytes per line */
int line_len;
int offset = 0; /* zero-based offset counter */
const u_char *ch = payload;

if (len <= 0) return; /* data fits on one line */ if (len <= line_width) { print_hex_ascii_line (ch, len, offset); return; } /* data spans multiple lines */ for (;;) { /* compute current line length */ line_len = line_width % len_rem; /* print line */ print_hex_ascii_line (ch, line_len, offset); /* compute total remaining */ len_rem = len_rem - line_len; /* shift pointer to remaining bytes to print */ ch = ch + line_len; /* add offset */ offset = offset + line_width; /* check if we have line width chars or less */ if (len_rem <= line_width) { /* print last line and get out */ print_hex_ascii_line (ch, len_rem, offset); break; } } return; }
整个程序基本上就分析完了。

This entry was posted in 网络技术. Bookmark the permalink. Follow any comments here with the RSS feed for this post. Both comments and trackbacks are currently closed.