捕获我们的第一个数据包
现在我们对数据包的捕获有一些了解了,可以确定的是我们事实上有一个能够从里面弄出东西来的接口,那么我们来捕获一个数据包怎么样?
“就给我个该死的例子让我来捣鼓一下.....”,你哭喊道。好的......这就来了......从这里下载testpcap1.c 或者把下面的程序复制粘贴一下就好了:
/***************************************************
* file: testpcap1.c
* Date: Thu Mar 08 17:14:36 MST 2001
* Author: Martin Casado
* Location: LAX Airport (hehe)
*
* 捕获单个数据包的简单程序
*****************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <pcap.h> /* 如果出错的话请尝试pcap/pcap.h */
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h> /* 包含头文件net/ethernet.h */
int main(int argc, char **argv)
{
int i;
char *dev;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t* descr;
const u_char *packet;
struct pcap_pkthdr hdr; /* 定义见pcap.h */
struct ether_header *eptr; /* 定义见net/ethernet.h */
u_char *ptr; /* 打印出硬件信息 */
/* 获取一个设备来使用... */
dev = pcap_lookupdev(errbuf);
if(dev == NULL)
{
printf("%s/n",errbuf);
exit(1);
}
printf("DEV: %s/n",dev);
/* 打开一个设备来嗅探。
pcap_t *pcap_open_live(char *device,int snaplen, int prmisc,int to_ms,
char *ebuf)
snaplen - 捕获的数据包的最大字节数。
promisc - 是否设为混杂模式?
to_ms - 读取超时之前等待数据包的时间(毫秒为单位),也就是超时时间。
errbuf - 如果出错了,错误信息会被记录在这里。
Note注意如果你把prmisc的参数改为除零以外的任何熟,你将会得到你的网卡上转发的所有
数据包,不管你想不想要!!在把你的网卡设置为混杂模式之前,请确保你了解你所在的网络的规则
*/
descr = pcap_open_live(dev,BUFSIZ,0,-1,errbuf);
if(descr == NULL)
{
printf("pcap_open_live(): %s/n",errbuf);
exit(1);
}
/*
从descr表示的设备上抓取一个数据包(哦耶!)
u_char *pcap_next(pcap_t *p,struct pcap_pkthdr *h)
所以把我们从pcap_open_live得到的设备描述符和一个分配好的
结构体pcap_pkthdr作为参数传给它。
*/
packet = pcap_next(descr,&hdr);
if(packet == NULL)
{/* dinna work *sob* */
printf("Didn't grab packet/n");
exit(1);
}
/* struct pcap_pkthdr {
struct timeval ts; time stamp
bpf_u_int32 caplen; length of portion present
bpf_u_int32; lebgth this packet (off wire)
}
*/
printf("Grabbed packet of length %d/n",hdr.len);
printf("Recieved at ..... %s/n",ctime((const time_t*)&hdr.ts.tv_sec));
printf("Ethernet address length is %d/n",ETHER_HDR_LEN);
/* lets start with the ether header... */
eptr = (struct ether_header *) packet;
/* 做几个检查来确定我们得到的数据包类型..*/
if (ntohs (eptr->ether_type) == ETHERTYPE_IP)
{
printf("Ethernet type hex:%x dec:%d is an IP packet/n",
ntohs(eptr->ether_type),
ntohs(eptr->ether_type));
}else if (ntohs (eptr->ether_type) == ETHERTYPE_ARP)
{
printf("Ethernet type hex:%x dec:%d is an ARP packet/n",
ntohs(eptr->ether_type),
ntohs(eptr->ether_type));
}else {
printf("Ethernet type %x not IP", ntohs(eptr->ether_type));
exit(1);
}
/* 摘自Steven的UNP */
ptr = eptr->ether_dhost;
i = ETHER_ADDR_LEN;
printf(" Destination Address: ");
do{
printf("%s%x",(i == ETHER_ADDR_LEN) ? " " : ":",*ptr++);
}while(--i>0);
printf("/n");
ptr = eptr->ether_shost;
i = ETHER_ADDR_LEN;
printf(" Source Address: ");
do{
printf("%s%x",(i == ETHER_ADDR_LEN) ? " " : ":",*ptr++);
}while(--i>0);
printf("/n");
return 0;
}
好了,它看上去不是很糟糕对吧?!我们来让她运行一下..
[root@pepe libpcap]# ./a.out在输入./a.out之后,我打开了另一个终端试图ping www.google.com。用来ping的ICMP输出数据包被捕获了。如果你并不确切地知道在网络的内部发生了什么,你也许会很好奇计算机是如何获得目的以太网地址的。啊哈!你不会真的认为以太网数据包的目的地址就是www.google.com的主机吧?
DEV: eth0
Grabbed packet of length 76
Recieved at time..... Mon Mar 12 22:23:29 2001
Ethernet address length is 14
Ethernet type hex:800 dec:2048 is an IP packet
Destination Address: 0:20:78:d1:e8:1
Source Address: 0:a0:cc:56:c2:91
[root@pepe libpcap]#
目的地址是数据包的“下一跳”(借用了《计算机网络》的翻译方式)地址,就像你的网关...也就是把你的网络联入互联网的那台电脑。数据包必须首先找到去网关的路,网关根据它自己的路由表来确定数据包将要被转发的“下一跳”地址。让我们来快速检查一下我们的数据包是否首先被发送到网关...你可以用命令 route来查看你本地计算机的路由表。路由表将会告诉你每个目的地址的“下一跳”地址。最后的一条(也是默认的)用来确定数据包既不是发送到本地(127子网),也不是发往192.16.1子网。这些数据包将被转发到192.168.1.1。
[root@pepe libpcap]# /sbin/route我们接下来可以用 arp命令来确定192.168.1.1的硬件地址(也就是物理地址,随你怎么翻译)。
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.1.0 * 255.255.255.0 U 0 0 0 eth0
127.0.0.0 * 255.0.0.0 U 0 0 0 lo
default 192.168.1.1 0.0.0.0 UG 0 0 0 eth0
[root@pepe libpcap]# /sbin/arp如果你的网关不在你的arp缓存里,ping它一下试试看,然后再使用arp命令。关键的地方是,你的电脑为了把数据包发送出去,必须知道“下一跳”的MAC地址(例如我的网络里是00:20:78:D1:E8:01)。
Address HWtype HWaddress Flags Mask Iface
192.168.1.1 ether 00:20:78:D1:E8:01 C eth0
一个最容易想到的打破沙锅问到底的问题是,“我的电脑是如何知道网关的硬件地址的呢?”让我说点题外话。我的电脑知道网关的IP地址,而就像你用手头的arp命令看到的一样,这里(arp缓存中)有一张IP地址和硬件地址的映射表。
以太网上的硬件地址是通过地址分辨协议,或者叫ARP来获取的。ARP在RFC826中有详细描述,你可以在这里找到它。它的工作原理如下,如果我的电脑想知道一台IP地址为1.2.3.4的电脑的硬件地址,它会以广播的形式向IP地址1.2.3.4所在的接口外的以太网发送一个ARP请求。所有的连接到这个接口的电脑(包括1.2.3.4)都应该会接收到这个ARP数据包并处理这个请求。然而,只有IP地址是1.2.3.4的主机应该提供一个包含自己硬件地址的回复。当接收到这个回复后,我的电脑会为接下来将发送到1.2.3.4的数据包缓存这个硬件地址(直到缓存生命周期结束)。ARP数据包是一种以太网数据包的类型......它叫做ETHERTYPE_ARP,在net/ethernet.h中是如下定义的。
#define ETHERTYPE_ARP 0x0806 /* Address resolution */你可以通过清空电脑的ARP缓存来强制它发送以太网的ARP请求。下面我这样做了,然后运行上面的程序来捕获发送出去的ARP请求。
[root@pepe libpcap]# /sbin/arp -n # 查看ARP缓存就像你看到的一样,当缓存里的硬件地址被移除后,我的电脑需要广播一个arp请求(也就是ff:ff:ff:ff:ff:ff)来寻找更高一级的地址的主机,在这里就是指192.168.1.1。如果你把arp缓存清空并把程序testpcap1.c修改成捕获两个数据包,你想想会发生什么呢?!嘿嘿,我知道你为什么不来试试它 :-P~~~~
Address HWtype HWaddress Flags Mask Iface
192.168.1.1 ether 00:20:78:D1:E8:01 C eth0
[root@pepe libpcap]# /sbin/arp -n -d 192.168.1.1 #删除网关入口
[root@pepe libpcap]# /sbin/arp -n #确保操作成功
Address HWtype HWaddress Flags Mask Iface
192.168.1.1 (incomplete) eth0
[root@pepe libpcap]# ./a.out
DEV: eth0
Grabbed packet of length 42
Recieved at time..... Tue Mar 13 00:36:49 2001
Ethernet address length is 14
Ethernet type hex:806 dec:2054 is an ARP packet
Destination Address: ff:ff:ff:ff:ff:ff
Source Address: 0:a0:cc:56:c2:91
[root@pepe libpcap]
现在让我们来对照<net/ethernet.h>把数据包分段,我们不关心网络层和传输层,我们仅仅想要看一下以太网的包头信息.... 假设我们的以太网带宽是10Mb/s...
/* 10Mb/s的以太网的包头 */那么,第一个ETH_ALEN位看起来像数据包(大概是你机器上的)的目的以太网地址(你可以在linux/if_ether.h中查看ETH_ALEN的定义)。接下来的ETH_ALEN位是源地址。最后一个字段是数据包的类型。下面是我电脑上从net/ethernet.h查出来的协议代号
struct ether_header
{
u_int8_t ether_dhost[ETH_ALEN]; /* destination eth addr */
u_int8_t ether_shost[ETH_ALEN]; /* source ether addr */
u_int16_t ether_type; /* packet type ID field */
} __attribute__ ((__packed__));
/* 以太网协议代号 */因为这个指南的缘故我会把大部分精力放在IP协议上,也许会捎带说一下IP协议... 事实上我压根就不知道什么是该死的Xerox PUP协议.
#define ETHERTYPE_PUP 0x0200 /* Xerox PUP */
#define ETHERTYPE_IP 0x0800 /* IP */
#define ETHERTYPE_ARP 0x0806 /* Address resolution */
#define ETHERTYPE_REVARP 0x8035 /* Reverse ARP */
好了,那么现在我们到了什么地步了呢?我们知道了捕获一个数据包的最基本的方法。我们讲到了硬件地址是如何被确定的以及以太网数据包长什么样儿。尽管如此,我们还是只用到了libpcap很少的一部分功能,我们也还没有进入数据包内部(除了other than the hardware headers(不会翻了...))。时间这么短可是却有这么多要做的事情:-) 就像你现在大概能知道的那样,通过一次只抓一个数据包的一个简单程序几乎是不可能做任何有实质性的协议分析的。我们真正想做的是,写一个简单的数据包捕获引擎,在过滤那些我们不需要的数据包的同时来捕获尽量多的数据包。下一节我们将构建一个简单的数据包捕获引擎,来协助我们接下来的数据包分析。