网络协议分析软件的编写
前一阵子要写一个简单的arp协议的分析程序,在翻阅了一些资料以后,决定使用libpcap库来实现,但是后来涉及到写链路层数据的缘故(另外一个程序,这个程序就是发送一个假冒的arp request,在本文没有实现,今后有空再整理吧),所以放弃了libpcap。由于本人使用的是solaris环境,所以无法使用bpf,但是sun公司仍然为开发者提供了一个与设备底层无关的接口DLPI,DLPI的全称是Data Link Provider Interface,通过DLPI开发者可以访问数据链路层的数据包,在早期的sunos系统中基本上采用的是NIT设备,但是现在solaris系统都使用了DLPI.关于DLPI的具体介绍大家可以访问网站www.opengroup.org/pubs/catalog/c811.htm,我这里就不多说了。 
在搜索了许多资料之后发现目前关于DLPI的编程资料不多,没有具体的过程,后来翻阅了Neal Nuckolls写的一篇文章How to Use the STREAMS Data Link Provider Interface (DLPI),根据例子做了修改(主要是提供了协议分析的部分),现在把编写一个DLPI过程共享一下,希望能对大家有所帮助。建议大家可以先看看Neal Nuckolls的文章,其中有部分涉及到流编程的,可以参考http://docs.sun.com/app/docs/doc/816-4855的streams programming guide(不过这不是必须的)。 
使用DLPI来访问数据链路层有几个步骤: 
1、打开网络设备 
2、将一个流 attach到一个特定的设备上,这里就是我们刚才打开的设备 
3、将设备设置为混杂模式(可选) 
4、把数据链路层sap绑定到流 
5、调用ioctl,设置raw模式 
6、配置其他模块(可选) 
7、刷新缓存 
8、接收数据进入分析阶段 
第一步,我们首先打开一个网络设备,在本例中我们打开的是/dev/bge设备,这是本机的网络接口,注意不是/dev/bge0,通过open调用打开,并且返回一个描述符 
fd=open(device, 2) 
第二步,attach一个流到设备上,这是通过发送DL_ATTACH_REQ原语来完成的 
dlattachreq(fd, ppa) 
int fd; 
u_long ppa; 
{ 
dl_attach_req_t attach_req; 
struct strbuf ctl; 
int flags; 
attach_req.dl_primitive = DL_ATTACH_REQ; 
attach_req.dl_ppa = ppa; 
ctl.maxlen = 0; 
ctl.len = sizeof (attach_req); 
ctl.buf = (char *) &attach_req; 
flags = 0;
if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0) 
syserr("dlattachreq:  putmsg"); 
} 
dl_attach_req_t是一个定义在dlpi.h中的结构体,我们通过填写结构体来发布原语,putmsg将消息发送到一个流,以上这个函数是DLPI中发布原语的主要格式 
发布了DL_ATTACH_REQ原语之后,还要确认是否成功, 
dlokack(fd, bufp) 
int fd; 
char *bufp; 
{ 
union DL_primitives *dlp; 
struct strbuf ctl; 
int flags; 
ctl.maxlen = MAXDLBUF; 
ctl.len = 0; 
ctl.buf = bufp; 
strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags, "dlokack");
dlp = (union DL_primitives *) ctl.buf;
expecting(DL_OK_ACK, dlp);
if (ctl.len < sizeof (dl_ok_ack_t)) 
err("dlokack:  response ctl.len too short:  %d", ctl.len); 
if (flags != RS_HIPRI) 
err("dlokack:  DL_OK_ACK was not M_PCPROTO"); 
if (ctl.len < sizeof (dl_ok_ack_t)) 
err("dlokack:  short response ctl.len:  %d", ctl.len); 
} 
第三步,将设备设置为混杂模式下工作(可选) 
dlpromisconreq(fd, DL_PROMISC_PHYS); 
这一个步骤也是通过发布DLPI原语来实现的,具体代码后面给出 
第四步,绑定流 
dlbindreq(fd, sap, 0, DL_CLDLS, 0, 0); 
dlbindack(fd, buf); 
第五步,设置raw模式 
strioctl(fd, DLIOCRAW, -1, 0, NULL) 
第六步,配置其他模块(在详细代码中给出) 
第七步,刷新数据,这是通过ioctl调用实现的 
ioctl(fd, I_FLUSH, FLUSHR) 
第八步,这是我们最关心的步骤,实际上,前面的这些步骤我们都可以忽略,大致明白有这么个过程就可以了,到时候写代码的时候照搬这个框架就可以。使用DLPI编程并不难,关键在于大家要了解它的框架,没必要非得自己去写一个框架来,本文就是利用了Michael R. Widner的代码,今后如果要增加功能只需要往这个框架里填就可以了。 
协议分析的过程是在函数filter完成的,函数申明如下 
void filter(register char *cp,register u_int  pktlen); 
该函数接收两个参数,cp是直接从设备缓存里拷贝过来的待分析数据,是链路层的封装数据,pktlen是数据的长度。在本文中由于操作环境是以太网,因此接收的数据链路层数据是以太网封装格式,如不清楚以太网封装的可以参考《TCP/IP详解 卷一:协议》,以太网封装三种标准的协议类型:IP协议、ARP协议和RARP协议。14字节的以太网首部包括了6字节的目的地址,6字节的源地址和2字节的类型字段,IP的类型值为0x0800,ARP的类型值为0x0806,RARP的类型值为0x8035。通过检查类型字段来区别接收到的数据是属于哪一种协议,函数实现代码如下 
void filter(cp, pktlen) 
register char *cp; 
register u_int pktlen; 
{ 
register struct ip     *ip; 
register struct tcphdr *tcph; 
register struct ether_header *eth; 
char *head=cp; 
static long line_count=0;//计数器,用来记录接收的数据次数 
u_short EtherType=ntohs(((struct ether_header *)cp)->ether_type); 
  //如果EtherType小于0x600说明这是一个符合802.3标准的数据格式,应当对数据作出调整 
  if(EtherType < 0x600) { 
    EtherType = *(u_short *)(cp + SZETH + 6); 
    cp+=8; pktlen-=8; 
  } 
  eth=(struct ether_header*)cp; 
  fprintf(LOG,"%-5d",++line_count); 
  if(EtherType == ETHERTYPE_IP) //检查协议类型是否IP协议 
  { 
  ip=(struct ip *)(cp+SZETH);//调整指针的位置,SZETH是以太网首部长度 
  Mac_info(e->ether_shost);//Mac_info函数打印出物理地址 
  fprintf(LOG,"("); 
  Ip_info(&ip->ip_src);//Ip_info函数打印出IP地址 
  fprintf(LOG,")"); 
  fprintf(LOG,"--->"); 
  Mac_info(e->ether_dhost); 
  fprintf(LOG,"("); 
  Ip_info(&ip->ip_dst); 
  fprintf(LOG,")"); 
  fprintf(LOG,"\n"); 
  } 
  else if(EtherType == ARP_PROTO)//如果协议类型是ARP 
  { 
     cp+=SZETH; 
     struct ether_arp *arp=(struct ether_arp *)cp; 
     switch(ntohs(arp->ea_hdr.ar_op))//检查arp的操作 
     { 
       case ARPOP_REQUEST:   //如果是arp请求 
           fprintf(LOG,"arp request:who has "); 
           arp_ip_info(arp->arp_tpa);  //打印arp报文信息中的地址 
           fprintf(LOG," tells "); 
           arp_ip_info(arp->arp_spa); 
           fprintf(LOG,"\n"); 
           break; 
       case ARPOP_REPLY:     //arp应答 
           fprintf(LOG,"arp reply: "); 
           arp_ip_info(arp->arp_spa); 
           fprintf(LOG," is at  "); 
           Mac_info((struct ether_addr*)&arp->arp_sha); 
           fprintf(LOG,"\n"); 
           break; 
      }         
      //可以在这里添加代码打印出arp数据报的具体内容 
   } 
} 
程序的具体实现代码如下: 
/*  程序sniffer.c的代码清单 */ 
#include <sys/stream.h> 
#include <sys/dlpi.h> 
#include <sys/bufmod.h> 
#include <stdio.h> 
#include <ctype.h> 
#include <string.h> 
#include <sys/time.h> 
#include <sys/file.h> 
#include <sys/stropts.h> 
#include <sys/signal.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <sys/ioctl.h> 
#include <net/if.h> 
#include <net/if_arp.h> 
#include <netinet/in.h> 
#include <netinet/if_ether.h> 
#include <netinet/in_systm.h> 
#include <netinet/ip.h> 
#include <netinet/udp.h> 
#include <netinet/ip_var.h> 
#include <netinet/udp_var.h> 
#include <netinet/in_systm.h> 
#include <netinet/tcp.h> 
#include <netinet/ip_icmp.h> 
#include <netdb.h> 
#include <arpa/inet.h> 
#define MAXDLBUF 32768 
#define MAXWAIT 15 
#define MAXDLADDR 1024 
#define         BITSPERBYTE        8 
#define bcopy(s1, s2, len) memcpy(s2, s1, len) 
#define index(s, c) strchr(s, c) 
#define rindex(s, c) strrchr(s, c) 
#define bcmp(s1, s2, len) (memcmp(s1, s2, len)!=0)
#define ERR stderr
char    *device, 
       *ProgName, 
       *LogName; 
FILE    *LOG; 
int     debug=0; 
long databuf[MAXDLBUF]; 
int sap=0; 
#define NIT_DEV     "/dev/bge" 
#define CHUNKSIZE   4096       
int     if_fd = -1; 
int     Packet[CHUNKSIZE+32]; 
int promisc = 1; 
int bufmod = 0; 
int filter_flags=0; 
int maxbuflen=128;
void Pexit(err,msg) 
int err; char *msg; 
{ perror(msg); 
 exit(err); } 
void Zexit(err,msg) 
int err; char *msg; 
{ fprintf(ERR,msg); 
 exit(err); } 
#define ARP_PROTO   (0x0806) 
#define IP          ((struct ip *)Packet) 
#define IP_OFFSET   (0x1FFF) 
#define SZETH       (sizeof(struct ether_header)) 
#define ARPLEN      (sizeof(struct ether_arp)) 
#define MACLEN      (6) 
#define IPALEN      (4) 
#define IPLEN       (ntohs(ip->ip_len)) 
#define IPHLEN      (ip->ip_hl) 
#define INET_ADDRSTRLEN 16 
#define MAXBUFLEN  (8192) 
time_t  LastTIME = 0; 
char *Ptm(t) 
register time_t *t; 
{ register char *p = ctime(t); 
 p[strlen(p)-6]=0; 
 return(p); 
} 
char *NOWtm() 
{ time_t tm; 
 time(&tm); 
 return( Ptm(&tm) ); 
} 
void print_data(uchar_t *buf,int size) 
{ 
int i=0; 
char *p=buf; 
for(;i<size;i++){ 
if(i%16 == 0) fprintf(LOG,"\n"); 
if(i%2 == 0) fprintf(LOG," "); 
fprintf(LOG,"%02x",*p++&0x00ff); 
} 
fprintf(LOG,"\n"); 
} 
//打印物理地址 
void Mac_info(struct ether_addr*mac) 
{ 
  fprintf(LOG,"%02x:%02x:%02x:%02x:%02x:%02x", 
          mac->ether_addr_octet[0], 
          mac->ether_addr_octet[1], 
          mac->ether_addr_octet[2],   
          mac->ether_addr_octet[3],   
          mac->ether_addr_octet[4], 
          mac->ether_addr_octet[5]); 
} 
//打印ip地址char buf[MAXDLBUF]; 
  
void Ip_info(struct in_addr *ip) 
{ 
  char str[INET_ADDRSTRLEN]; 
  inet_ntop(AF_INET,ip,str,sizeof(str)); 
  if(*str) 
  fprintf(LOG,"%s",str); 
  
} 
//打印ip地址的另外一个版本 
void arp_ip_info(uchar_t pa[]) 
{ 
   fprintf(LOG,"%d.%d.%d.%d",pa[0],pa[1],pa[2],pa[3]); 
} 
void death() 
{ register struct CREC *CLe; 
    
   fprintf(LOG,"\nLog ended at => %s\n",NOWtm()); 
   fflush(LOG); 
   if(LOG != stdout) 
       fclose(LOG); 
   exit(1); 
} 
err(fmt, a1, a2, a3, a4) 
char *fmt; 
char *a1, *a2, *a3, *a4; 
{ 
(void) fprintf(stderr, fmt, a1, a2, a3, a4); 
(void) fprintf(stderr, "\n"); 
(void) exit(1); 
} 
void 
sigalrm() 
{ 
(void) err("sigalrm:  TIMEOUT"); 
} 
strgetmsg(fd, ctlp, datap, flagsp, caller) 
int fd; 
struct strbuf *ctlp, *datap; 
int *flagsp; 
char *caller; 
{ 
int rc; 
static char errmsg[80]; 
(void) signal(SIGALRM, sigalrm); 
if (alarm(MAXWAIT) < 0) { 
(void) sprintf(errmsg, "%s:  alarm", caller); 
syserr(errmsg); 
} 
*flagsp = 0; 
if ((rc = getmsg(fd, ctlp, datap, flagsp)) < 0) { 
(void) sprintf(errmsg, "%s:  getmsg", caller); 
syserr(errmsg); 
} 
if (alarm(0) < 0) { 
(void) sprintf(errmsg, "%s:  alarm", caller); 
syserr(errmsg); 
} 
if ((rc & (MORECTL | MOREDATA)) == (MORECTL | MOREDATA)) 
err("%s:  MORECTL|MOREDATA", caller); 
if (rc & MORECTL) 
err("%s:  MORECTL", caller); 
if (rc & MOREDATA) 
err("%s:  MOREDATA", caller); 
 
 
 减小字体
减小字体 增大字体
增大字体



 中查找“网络协议分析软件的编写”更多相关内容
中查找“网络协议分析软件的编写”更多相关内容 中查找“网络协议分析软件的编写”更多相关内容
中查找“网络协议分析软件的编写”更多相关内容