一.背景
为了使不同体系结构的计算机网络都能进行互联,国际标准化组织ISO于1997年成立了专门的结构研究这个问题。不久他们就提出了一个试图使各种计算机在世界范围内都能互联的成网的标准框架,即著名的OSI/RM(Open Systems Interconnection Reference Model , 开放系统互联基本参考模型),简称为OSI。它的主要目标是:只要遵循OSI标准,一个系统就可以和位于世界任何地方的,也遵循同一标准的其他任何系统进行通信。
OSI采用分层结构,每一层完成不同的功能,较好地从理论上解决了不同系统之间互联的难题。但是由于OSI模型归于复杂以及种种原因,当它被制定出来时,TCP/IP已经成为事实上的国际标准。
二.TCP/IP 的4层结构
通常意义上的TCP/IP结构共有4层协议,从下到上依次是网络协议层,互联协议层,传输层和应用层,不同的层次上运行的协议也不同。
1.网络协议层
网络协议层工作在TCP/IP模型的最底层,它负责将数据通过电缆传送出去,并将接收到的信号通过网卡驱动转换成计算机能够识别的数据。主要是管理网络数据包的接收很发送,在操作系统中主要体现在设备驱动程序和网络接口。对应各种网络通信介质,不同的通信网络有其不同的访问协议,例如通常用的以太网和PPP等。
运行至这一层的协议主要有:Ethernet 802.3等等。
2.互联协议层
互联协议层是十分重要的一层,网卡驱动将解析后的数据递交给互联协议层,互联协议层的工作主要就是对这些混乱的数据进行解析,然后分门别类地传递给不同的上层协议。
运行在这一层的协议主要由:IP( Internet Protocol ),ICMP( Internet Control Message Protocol ),ARP( Address Resolution Protocol , 地址转换协议),RARP( Reverse Address Resolution Protocol .反向地址解析协议)
3.传输层协议
传输层协议有两个具有代表性的协议,分别是TCP( Transmission Control Protocol , 传输控制协议) 和UDP( User Datagram Protocol ,用户数据报协议)。
TCP的面向连接的通信协议,通过三次握手建立连接,通信完后要断开连接。由于它是面向连接的协议,那么它只能用于点对点之间的通信。TCP是一种可靠的数据流服务,采用“重传”和“肯定确认”机制来实现传输的可靠性。TCP还采用一种滑动窗口的方式进行流量的控制。所谓的窗口表示的是接收能力,用于限制发送方的发送速度。
UDP是面向无连接的通信协议,UDP数据包括目的端口号和源端口号信息,由于通信不需要连接,所以它可以实现广播发送。UDP通信时不需要接收方的确认,属于不可靠的传输,可能会出现丢包的现象,实际应用中要求在程序员编程验证。
4.应用层协议
应用层协议一般是面向用户的服务,如FTP,Telnet,DNS,SMTP,POP3等。
四.相关概念详解
1.ARP/RARP
ARP/RARP协议是进行IP地址和MAC地址相互转换的协议。在网络数据通信中,链路层使用MAC地址进行实际的数据通信,而在网络层使用IP地址进行机器定位寻址。ARP协议把IP地址映射为MAC地址,而RARP协议是通过MAC地址映射为IP地址。
ARP请求应答报文是一种比较常用的数据包,它的主要目的是找到IP地址对应的MAC地址。利用ARP请求报文来获得其MAC地址。这个过程是这样的:
首先发送ARP请求报文,它是以广播的方式发送到网络上的,每一台机器都会收到这个ARP请求报文,它是以广播方式发送到网络上的,每个机器都会收到这个ARP请求报文。在这个ARP请求报文中,发送端以太网地址字段是发送ARP请求报文机器的MAC地址,而且发送端IP字段内容是要查询的MAC的主机的IP地址。当某个主机检查到自己的IP地址是目的IP地址时,它会返回一个ARP应答报文,在此应答报文中,其发送端以太网地址字段内容就是要查询的MAC地址。
一个主机里面一般有ARP缓存,里面存储了IP地址和MAC地址对。
2.交换网络
在共享网络中,把网卡设为混杂模式就可以监听所有的网络数据包,但是在交换网络中,情况就发生了变化。
以太网分为共享式以太网和交换式以太网,共享式以太网通常以集线器作为网络设备,交换式以太网通常使用交换机作为网络连接设备。共享式以太网中数据帧以广播方式传送到每个以太网端口,网内每台主机的网卡能接收到网内的所有数据帧。因此只要把网卡设置成为混杂模式就可以获取到这本地网卡的所有数据帧。
最典型的交换网络使用交换机连接,在交换机中可以设为一个端口一个MAC地址,形成一个端口一个MAc地址对。这样当网络数据包到达端口时,而不是转发给所有的端口,只是转发MAC对应的端口。这样,其他端口的通讯不受干扰,所以其他端口上的主机就无法接收到网络上的数据包了。
可以利用ARP重定向技术来实现交换网络的数据包捕获,其主要原理用到了ARP欺骗技术,但此方法有一定的危险性。另外有的交换机上有一个用于调试的端口,任何其他端口的通信数据都会出现在此端口上,所以也可以在此端口上进行网络数据包的监听工作。
3.数据包在局域网内的传输方式
众所周知,数据包在互联网上的传播是根据IP地址进行寻址的,但是完整的过程并非如此。数据包通过IP地址可以达到的最远的地点就是目标主机所在的子网,而在该子网内的寻址却是使用物理地址的,即MAC地址。
数据包被传送到目标主机所在的子网时,它将被交给路由器,路由器在该子网内使用广播方式传播出去,这意味着该子网内的虽有主机都可以接收到该数据包。不过主机接收到数据包后通常会先检查其目的地址,如果目的地址不是自己,那么就是丢弃,只有目的地址为自己的数据包才会将其交付给上一层处理。
Sniffer将网卡设置为混杂模式,这样就可以接收到所有的数据包了,达到了嗅探了目的。
网卡对接收到的数据包经过一步步的解析之后就得到了最终的应用程序报文,此时就可以清晰易懂地看出它所包含的内容。
四. 网络抓包用到的技术
1.原始套接字编程
2.Winpcap编程
使用Winpcap来生成数据包比使用原始套接字更加灵活,功能更加丰富。在基于原始套接字方法中,由于原始套接字的某些限制,其构造的数据包最底层协议的数据包只能是IP数据包不能够构造IP层以下的协议数据包,例如链路层数据包就不能构造,而使用Winpcap可以构造基于链路层的数据包。
使用WinPcap构造数据包与使用原始套接字构造数据包是不同的。在原始套接字里面构造的数据包是从IP层开始的,而在WinPcap中是从链路层开始构造的。也就是说,在WinPcap中药构造一个Ip数据包,需要先构造链路层首部,在这里是以太网首部,然后再构造Ip首部,最后才是IP负载数据。而在原始套接字中直接从IP层开始构造,所以只需要构造IP首部和负载数据就可以了。
五.简单嗅探器的具体实现解析
定义一个IP头
typedef struct IPHEAD
{
unsigned char h_len:4; //4位首部长度+4位IP版本号
unsigned char ver:4;
unsigned char tos; //8位服务类型TOS
unsigned short total_len; //16位总长度(字节)
unsigned short ident; //16位标识
unsigned short frag_and_flags; //3位标志位
unsigned char ttl; //8位生存时间 TTL
unsigned char proto; //8位协议 (TCP, UDP 或其他)
unsigned short checksum; //16位IP首部校验和
unsigned int sourceip; //32位源IP地址
unsigned int destip; //32位目的IP地址
}IPHEAD;
定义TCP头部
typedef struct TCPHEAD
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsigned int th_seq; //32位序列号
unsigned int th_ack; //32位确认号
unsigned char th_lenres; //4位首部长度/6位保留字
unsigned char th_flag; //6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
}TCPHEAD ;
接收数据信息:
IPHEAD piphd=(struct IPHEAD *)cbuf; //取得IP头数据的地址
int iIphLen = sizeof(unsigned long) * (piphd->h_len & 0xf);
TCPHEAD ptcphd=(struct TCPHEAD *)(cbuf+iIphLen); //取得TCP头数据的地址
得到头部中的信息,直接引用即可:
printf("From:%s\tport%d\t",inet_ntoa(*(structin_addr*)&piphd->sourceip),ntohs(ptcphd->th_sport) );
printf("To:%s\tport%d",inet_ntoa(*(structin_addr*)&piphd->destip),ntohs(ptcphd->th_dport));
判断协议类型:
switch(piphd->proto) //根据IP头的协议判断数据包协议类型
{
case 1:
printf("ICMP\n");
break;
case 2:
printf("IGMP\n");
break;
case 6:
printf("TCP\n");
break;
case 17:
printf("UDP\n");
break;
default:
printf("unknow:%d\n",piphd->proto);
}
}