设为首页 加入收藏

TOP

基于Linux C的socket抓包程序和Package分析(一)
2015-02-02 14:50:54 来源: 作者: 【 】 浏览:94
Tags:基于 Linux socket 程序 Package分析

1. Linux抓包源程序


在OSI七层模型中,网卡工作在物理层和数据链路层的MAC子层。


进行网络通信时,源主机通过socket(或其它)应用程序产生IP报文,经过各个OSI层层封装,数据包以Ethernet帧的形式进入物理层。Ethernet帧包含源主机地址、IP报文、目标地址(IP地址、端口号或映射的6字节MAC地址)和需要传送到目标主机的其它信息。


目标的MAC地址是哪里来的呢?这牵扯到一个ARP协议(介乎于网络层和数据链路层的一个协议)。第一次传送某个目的IP地址的数据的时候,先会发出一个ARP包,其MAC的目标地址是广播地址,里面说到:"谁是xxx.xxx.xxx.xxx这个IP地址的主人?"因为是广播包,所有这个局域网的主机都收到了这个ARP请求。收到请求的主机将这个IP地址和自己的相比较,如果不相同就不予理会,如果相同就发出ARP响应包。这个IP地址的主机收到这个ARP请求包后回复的ARP响应里说到:"我是这个IP地址的主人"。这个包里面就包括了他的MAC地址。以后的给这个IP地址的帧的目标MAC地址就被确定了。


就这样,以太网帧开始在数据链路层传播。Ethernet帧在链路层基于广播方式传播,即网段内的所有网卡都能观察该帧,但只有一个网卡通过对比6字节MAC地址发现与自己相符,然后它就接收该帧。而其它网卡则放弃该帧。(其它网卡也可以接受该帧,即实际的网络Sniffer,可进行信息窃取等操作)


网卡得到Ethernet帧后,通过网络驱动程序和上层协议对其进行还原操作,即层层剥离报文头后将数据交由目标主机的socket(或其它)应用程序使用。


如果我们需要原始的以太网帧,以便观察与目标主机进行通信的源主机信息,则可以通过建立基于PF_PACKET的socket应用程序实现。PF_PACKET协议簇允许应用程序直接获得网络驱动程序得到的数据帧信息。PF_PACKET支持SOCK_DGRAM和SOCK_RAW两种socket类型,前者利用操作系统处理报文头,而后者则将以太网帧直接交由应用程序处理。需要注意到是,只有root权限用户才能使用PF_PACKET程序。


下面的代码即可实现由应用程序直接获得以太网帧的需求。


#include
#include
#include
#include
#include


#define BUFFER_MAX 2048
?
int main(int argc, char *argv[]){
? ? int? SOCKET_SRC;
? ? char buf[BUFFER_MAX];
? ? int n_rd;


? ? if( (SOCKET_SRC = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0 ){
? ? ? ? fprintf(stderr, "create socket error.\n");
? ? ? ? exit(0);
? ? }
? ? while(1){
? ? ? ? n_rd = recvfrom(SOCKET_SRC, buf, BUFFER_MAX, 0, NULL, NULL);
? ? ? if (n_rd<46) {
? ? ? ? ? perror("recvfrom():");
? ? ? ? ? ?printf("Incomplete packet (errno is %d)\n",? errno);
? ? ? ? ? ?close(SOCKET_SRC);
? ? ? ? ? ?exit(0);
? ? ? }
? ? ? /* An Ethernet frame was written to buf, frame analysis can be processed here */
? ? ? /* Termination control */
? ? }
? ? close(SOCKET_SRC);
? ? return 0;
}


2. 数据包(以太网帧)分析


一个以太网帧(RFC894)的数据格式如下图所示。


基于Linux C的socket抓包程序和Package分析


以太网帧(RFC894)格式


程序通过执行一次


n_rd = recvfrom(SOCKET_SRC, buf, BUFFER_MAX, 0, NULL, NULL);


就将上面一条以太网帧写入buf中。


(1) 为了从buf中提取以太网报文头,我们可以定义如下结构体。


?typedef struct mac_frm_hdr {
? ? char dest_addr[6];?//destination MAC address shall be defined first.
? ? char src_addr[6];
? ? short type;
?}__attribute__((packed)) MAC_FRM_HDR;


定义该结构体时,需要注意以下几点。


a.各属性需按帧格式的出现顺序定义。


b.数据类型长度必须和帧中相应区域的长度相同。


c. 使用__attribute__((packed))取消编译器自动优化对齐结构体,也是为了保证属性长度和帧中相应区域的长度相同。


(2)为了提取IP报文头,根据IP报文头的格式,我们可定义如下结构体。


typedef struct ip_hdr{ ?//header of IPV4
? ? #ifdef __LITTLE_ENDIAN_BIFIELD
? ? ? ? u_char ip_len:4, ip_ver:4;
? ? #else
? ? ? ? u_char ip_ver:4, ip_len:4;
? ? #endif


? ? u_char? ip_tos;
? ? u_short ip_total_len;
? ? u_short ip_id;
? ? u_short ip_flags;
? ? u_char? ip_ttl;
? ? u_char? ip_protocol;
? ? u_short ip_chksum;
? ? u_int32 ip_src;
? ? u_int32 ip_dest;
}__attribute__((packed)) IP_HDR;


为保证各属性长度与IP报文头中一致,我们应该在定义该结构体前作如下声明。


typedef int int32;
typedef unsigned int u_int32;
typedef unsigned char u_char;
typedef unsigned short u_short;


注意事项参考定义以太网帧结构体。


(3)为了提取UDP/TCP报文头,可根据UDP/TCP报文头格式,定义相应结构体,这里不作赘述。


下面是对以太网帧进行解析的具体代码。


MAC_FRM_HDR *mac_hdr; //define a Ethernet frame header
IP_HDR *ip_hdr;? ? ? //define a IP header
char *tmp1, *tmp2;
int AND_LOGIC = 0xFF;


mac_hdr = buf;?//buf is what we got from the socket program
ip_hdr = buf + sizeof(MAC_FRM_HDR);
//udp_hdr = buf + sizeof(MAC_FRM_HDR) + sizeof(IP_HDR); //if we want to analyses the UDP/TCP


tmp1 = mac_hdr->src_addr;
tmp2 = mac_hdr->dest_addr;
/* print the MAC addresses of source and receiving host */
printf("MAC: %.2X:%.2X:%.2X:%.2X:%.2X:%.2X==>" "%.2X:%.2X:%.2X:%.2X:%.2X:%.2X",
? ? ? ? ? ? tmp1[0]&AND_LOGIC, tmp1[

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇JavaScript文档加载顺序和事件绑定 下一篇C语言字符串操作函数总结

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: