linux实现抓包 (使用原始套接字数据连路层协议)

网络作业2 IP分组流量分析程序

参考:

http://www.cnblogs.com/rollenholt/articles/2585432.html

http://blog.csdn.net/ctthuangcheng/article/details/9746501

http://www.cnblogs.com/rollenholt/articles/2585432.html

http://baike.baidu.com/link?url=PipBWz6NFljSSDX_q-AeKVO1shLJijhn4xU4iaOPAN9aaDkaQZvL3n10XCnur0rURR3-4Q6TA6HDw-N8DYR-Xq

http://wenku.baidu.com/link?url=gomqzjumyFcoIPceA9jZXdkazLIIbGR_xxF5cu11EZVqvTRVnqiH3sl30kUgoW5nZjZEB1TdTXFZm6UZRPfIce5W7fk1m6TD1N2pTii8Ycq


开发一个IP分组流量分析程序,实现以下功能:捕获并分析通过本地网卡的IP分组,输入捕获IP分组的时间限制,输出每个IP分组头部的主要字段(包括版本、总长度、协议、源地址与目的地址等),协议字段需要区分出具体类型(例如TCP、UDP、ICMP、IGMP、IPv6等)。

实现方案:
1.原始套接字抓包
2.linux信号机制计时。

1.原始套接字抓包
        常用的套接字有流式套接字(SOCK_STREAM,对应于TCP)和数据报套接字(SOCK_DGRAM,对应于UDP),这两种套接字接收到的仅仅是应用层的数据,用户一般见不到MAC层、IP层和传输层(TCP/UDP)的帧头。而这里需要用到的原始套接字,是可以看到原始的网络报文的。如下图所示MAC帧地址,获得的包为从目的MAC地址到CRC之前(不包括CRC)
linux实现抓包 (使用原始套接字数据连路层协议)_第1张图片
以如下方式使用原始套接字
(sock =  socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) < 0 )//使用原始套接字,ETH_P_ALL可以获取所有经过本机的数据包,当然前提是网卡为混杂模式。
协议参数可选择的很多,这里列几个常用的。
linux实现抓包 (使用原始套接字数据连路层协议)_第2张图片
上面还提到,网卡要设置成混杂模式。根据百度百科:混杂模式(Promiscuous Mode)是指一台机器能够接收所有经过它的数据流,而不论其目的地址是否是他。是相对于通常模式(又称“非混杂模式”)而言的。
通过以下设置:
strncpy(ethreq.ifr_name, "wlan0", IFNAMSIZ);//指定需要经过的网卡。
	if( ioctl(sock, SIOCGIFFLAGS, ðreq) == -1 )//获得该网卡的接口信息。
	{
		perror("ioctl:");
		close(sock);
		exit(1);
	}
	ethreq.ifr_flags |= IFF_PROMISC;//设置成混杂模式。
	if( ioctl(sock, SIOCSIFFLAGS, ðreq) == -1)//将网卡的接口信息写回。
	{
		perror("ioctl:");
		close(sock);
		exit(1);
	}

先指定要配置的网卡名称,在通过ifreq.ifr_flags 设置混杂模式。这里涉及到如下一个数据结构,
struct ifreq
  {
# define IFHWADDRLEN 6
# define IFNAMSIZ IF_NAMESIZE
    union
      {
        char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */
      } ifr_ifrn;

    union
      {
        struct sockaddr ifru_addr;
        struct sockaddr ifru_dstaddr;
        struct sockaddr ifru_broadaddr;
        struct sockaddr ifru_netmask;
        struct sockaddr ifru_hwaddr;
        short int ifru_flags;
        int ifru_ivalue;
        int ifru_mtu;
        struct ifmap ifru_map;
        char ifru_slave[IFNAMSIZ]; /* Just fits the size */
        char ifru_newname[IFNAMSIZ];
        __caddr_t ifru_data;
      } ifr_ifru;
  };
# define ifr_name ifr_ifrn.ifrn_name /* interface name*/   在这里就是网卡eth0或eth1
# define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */ mac地址
# define ifr_addr ifr_ifru.ifru_addr /* address */   source地址
# define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */   目的ip地址
# define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ 广播地址
# define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */ 子网掩码
# define ifr_flags ifr_ifru.ifru_flags /* flags */   模式标志 设置混杂模式
# define ifr_metric ifr_ifru.ifru_ivalue /* metric */
# define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
# define ifr_map ifr_ifru.ifru_map /* device map */
# define ifr_slave ifr_ifru.ifru_slave /* slave device */
# define ifr_data ifr_ifru.ifru_data /* for use by interface */
# define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
# define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
# define ifr_qlen ifr_ifru.ifru_ivalue /* queue length */
# define ifr_newname ifr_ifru.ifru_newname /* New name */
# define _IOT_ifreq _IOT(_IOTS(char),IFNAMSIZ,_IOTS(char),16,0,0)
# define _IOT_ifreq_short _IOT(_IOTS(char),IFNAMSIZ,_IOTS(short),1,0,0)
# define _IOT_ifreq_int _IOT(_IOTS(char),IFNAMSIZ,_IOTS(int),1,0,0)

最后使用recvfrom接收信息。
recvfrom(sock, buffer, MAXLINE, 0, NULL, NULL);//接收通过网卡的信息。

2.linux信号机制定时。
        linux信号机制就不多说了,这里利用信号机制和系统调用alarm弄了一个定时器。
完整代码:


#include "mysock.h"
#include "analysis.h"

unsigned int sec = 3;
void alarm_h()
{
	sec = 0;
//	printf("*************alarm*****************\n");
}
int main()
{

	int sock;
	unsigned char buffer[MAXLINE];//接收缓冲区,以太网环境中ip数据包长度规定为46-1500,加上mac层的14(mac帧最后的4字节crc不包括在内),为64-1518,不过貌似再长了可以分片自己重组。
	int n = 0;
	struct ifreq ethreq;
	if( (sock =  socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0 )//使用原始套接字,ETH_P_ALL可以获取所有经过本机的数据包,当然前提是网卡为混杂模式。
	{
		perror("socket:");
		exit(1);
	}
	strncpy(ethreq.ifr_name, "wlan0", IFNAMSIZ);//指定需要经过的网卡。
	if( ioctl(sock, SIOCGIFFLAGS, ðreq) == -1 )//获得该网卡的接口信息。
	{
		perror("ioctl:");
		close(sock);
		exit(1);
	}
	ethreq.ifr_flags |= IFF_PROMISC;//设置成混杂模式。
	if( ioctl(sock, SIOCSIFFLAGS, ðreq) == -1)//将网卡的接口信息写回。
	{
		perror("ioctl:");
		close(sock);
		exit(1);
	}
	struct sigaction newact, oldact;//使用SIGALRM信号计时。
	newact.sa_handler = alarm_h;//信号处理函数
	sigemptyset(&newact.sa_mask);//清空屏蔽字
	newact.sa_flags = 0;
	while(1)
	{
		sigaction(SIGALRM, &newact, &oldact);//改变该信号的默认行为,旧的存在oldact中,使用之后别忘了还回来。
		printf("input seconds:\t");
		scanf("%d", &sec);//请用户输入信息。
		alarm(sec);//开始计时。sec若为0关闭计时。
		while(sec > 0)//计时时间到的时候,SIFALRM信号的处理函数会将sec置零。
		{
			printf("-----\n");
			n = recvfrom(sock, buffer, MAXLINE, 0, NULL, NULL);//接收通过网卡的信息。
			if(n < 0)
			{
				perror("recvform:");
				continue;
			}
			printf("recv %d bytes!\n", n);
			int i;
			for(i = 0 ; i < n ; i++)
			{
				printf("%02x\t", buffer[i]);//打印信息
				if( (i+1)%12 == 0)
					printf("\n");
			}
			printf("\n");
			analysis_mac(buffer);//解析数据包。这里只能解析IP数据报。
		}
		alarm(0);//停止计时器。
		sigaction(SIGALRM, &oldact, NULL);//改成默认行为。
	}
	return 0;
}


你可能感兴趣的:(网络作业)