libpcap编程:Sniffer

主要实现运用libpcap(winpcap)的api进行数据链路层的抓包操作,并一层层进行解析,解析TCP, UDP, ICMP等协议。

主要代码及解析如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/*************************************************************************
> File Name: sniffer.c
> Author: PCB
> Mail: pangbin2415@gmai.com
> Created Time: 2016年04月10日 星期日 20时40分52秒
************************************************************************/

#include<stdio.h>
#include<pcap.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netinet/ip.h>
#include<netinet/tcp.h>
#include<netinet/udp.h>
#include<netinet/ip_icmp.h>

pcap_t* pd;
int linkhdrlen; //链路层的长度

/*
* device: 要连接的设备名字
* bpfstr: 要过滤的规则
* */
pcap_t* open_pcap_socket(char* device, const char* bpfstr)
{
char errbuf[PCAP_ERRBUF_SIZE]; //存放出错信息
pcap_t* pd;
uint32_t srcip, netmask; //存放源的ip地址和子网掩码
struct bpf_program bpf; //存取bpf过滤规则的一个结构

//找到一个设备
if(!*device && !(device == pcap_lookupdev(errbuf)))
{
printf("pcap_lookupdev(): %s\n", errbuf);
return NULL;
}

//打开该设备,并且等到一个包到来
if((pd = pcap_open_live(device, BUFSIZ, 1, 0, errbuf)) == NULL)
{
printf("pcap_open_live(): %s\n", errbuf);
return NULL;
}

//得到设备的ip地址和ip的掩码
if(pcap_lookupnet(device, &srcip, &netmask, errbuf) < 0)
{
printf("pcap_lookupnet: %s\n", errbuf);
return NULL;
}
//应用过滤规则
if(pcap_compile(pd, &bpf, (char*)bpfstr, 0, netmask))
{
printf("pcap_compile(): %s\n", pcap_geterr(pd));
return NULL;
}

if(pcap_setfilter(pd, &bpf)<0)
{
printf("pcap_setfilter(): %s\n", pcap_geterr(pd));
return NULL;
}

//printf("Open_pcapSocket()\n");

return pd;
}//end open_pcap_socket(char* device, const char* bpfstr);

void capture_loop(pcap_t* pd, int packets, pcap_handler func)
{
int linktype;

//判断数据链路层的类型
if((linktype = pcap_datalink(pd)) < 0)
{
printf("pcap_datalink(): %s\n", pcap_geterr(pd));
return;
}

switch(linktype)
{
case DLT_NULL://BSD回路封装;链路层协议是一个4字节的域
linkhdrlen = 4;
break;

case DLT_EN10MB: //以太网,链路层协议是一个14字节的域
linkhdrlen = 14;
break;

case DLT_SLIP:
case DLT_PPP:
linkhdrlen = 24; //链路层长度为24个字节
break;

default:
printf("Unsupport datalink (%d)\n", linktype);
return;

}

//开始抓包
if(pcap_loop(pd, packets, func, 0) < 0)
printf("pcap_loop failed: %s\n", pcap_geterr(pd));
}

/*
* 解析包的程序*/
void parse_packet(u_char *user, struct pcap_pkthdr* packethdr, u_char* packetptr)
{
struct ip* iphdr; //netinet库的ip结构
struct icmphdr* icmphdr; //icmp结构
struct tcphdr* tcphdr;//tcp结构
struct udphdr* udphdr;//udp结构
char iphdrInfo[256], srcip[256], dstip[256];
unsigned short id, seq;
packetptr += linkhdrlen; //过滤掉数据链路层的数据
iphdr = (struct ip*)packetptr;
strcpy(srcip, inet_ntoa(iphdr->ip_src));
strcpy(dstip, inet_ntoa(iphdr->ip_dst));

sprintf(iphdrInfo, "ID:%d TOS:0x%x, TTL:%d, Iplen:%d Dglen:%d",
ntohs(iphdr->ip_id), iphdr->ip_tos, iphdr->ip_ttl, 4*iphdr->ip_hl, ntohs(iphdr->ip_len)
);

//将ip的字段跳过去
packetptr += 4*iphdr->ip_hl;
switch(iphdr->ip_p)
{
case IPPROTO_TCP: //如果是TCP协议
tcphdr = (struct tcphdr*)packetptr;
printf("TCP %s:%d -> %s:%d\n", srcip, ntohs(tcphdr->source), dstip, ntohs(tcphdr->dest));
printf("%s\n", iphdrInfo);
printf("%c%c%c%c%c%c Seq: 0x%x Ack: 0x%x Win: 0x%x TcpLen: %d\n", (tcphdr->urg ? 'U' : '*'),
(tcphdr->ack ? 'A' : '*'),
(tcphdr->psh ? 'P' : '*'),
(tcphdr->rst ? 'R' : '*'),
(tcphdr->syn ? 'S' : '*'),
(tcphdr->fin ? 'F' : '*'),
ntohl(tcphdr->seq), ntohl(tcphdr->ack_seq), ntohs(tcphdr->window), 4*tcphdr->doff
);
break;

case IPPROTO_UDP: //UDP协议
udphdr = (struct udphdr*) packetptr;
printf("UDP %s:%d -> %s:%d\n", srcip, ntohs(udphdr->source),
dstip, ntohs(udphdr->dest)
);
printf("%s\n", iphdrInfo);
break;

case IPPROTO_ICMP: //ICMP协议
icmphdr = (struct icmphdr*)packetptr;
printf("ICMP %s -> %s\n", srcip, dstip);
printf("%s\n", iphdrInfo);
memcpy(&id, (u_char*)icmphdr+4, 2);
memcpy(&seq, (u_char*)icmphdr+6, 2);
printf("Type:%d Code:%d ID:%d Seq:%d\n", icmphdr->type, icmphdr->code, ntohs(id), ntohs(seq));
break;


}
printf("---------------------------------------------\n\n");
}

void quiteOut(int signo)
{
struct pcap_stat stats;
if(pcap_stats(pd, &stats) >= 0)
{
printf("%d packets received\n", stats.ps_recv);
printf("%d packtes dropped\n\n", stats.ps_drop);
}
pcap_close(pd);
exit(0);
}


int main(int argc, char **argv)
{
char interface[256] = "", bpfstr[256] = "";

int packets = 0, c,i;

while((c = getopt(argc, argv, "i:n:") ) != -1) //读取参数,当为-i时,指定接口,当为-n时,指定读取多少packets
{
switch(c)
{
case 'i':
strcpy(interface, optarg);
break;
case 'n':
packets = atoi(optarg);
break;
}
}

//获取过滤的字符串
for(i = optind; i < argc; i++)
{
strcat(bpfstr, argv[i]);
strcat(bpfstr, " ");
}

//连接接口
if((pd = open_pcap_socket(interface, bpfstr)))
{
/*/
* 绑定中断事件给quiteOut函数*/

signal(SIGINT, quiteOut);
signal(SIGTERM, quiteOut);
signal(SIGQUIT, quiteOut);

capture_loop(pd, packets, (pcap_handler)parse_packet);
quiteOut(0);
}
exit(0);

}

主要使用

该程序支持监听的接口及一共要接收的数据包的大小,并且提供过滤功能,具体使用如下:

sudo ./sniffer -i eth0 tcp port 80

运行结果如下