linux下包监控程序[C语言]

来源:互联网 发布:php curl 大文件 编辑:程序博客网 时间:2024/05/01 00:24

在linux下实现的一个数据包监控程序(C语言),此程序主要包含3个主要模块:底层模块、中层模块和上层统计模块。其中底层模块是数据包的捕获过程; 中层模块包括MAC 层处理模块、IP 层处理模块、TCP 处理模块、UCP 处理模块和ICMP 处理模块; 上层统计处理模块包括数据包的统计模块、数据包协议统计模块、网络网元发现模、数据包构造模块和数据包过滤模块。

代码参考自《TCP/IP网络实验程序篇》,并在其基础上进行了一定改进。

对数据包进行拆解

void print_ethernet(struct ether_header *eth) //显示ethernet报头{  int type = ntohs(eth->ether_type); //将一个16位数由网络字节顺序转换为主机字节顺序  if (type <= 1500) //<1500为802.3帧    printf("IEEE 802.3 Ethernet Frame:\n");  else  //>1500为以太网帧    printf("Ethernet Frame:\n");  printf("+-------------------------+-------------------------"         "+-------------------------+\n");  printf("| Destination MAC Address:                          "         "         %17s|\n", mac_ntoa(eth->ether_dhost)); //占17位  printf("+-------------------------+-------------------------"         "+-------------------------+\n");  printf("| Source MAC Address:                               "         "         %17s|\n", mac_ntoa(eth->ether_shost));  printf("+-------------------------+-------------------------"         "+-------------------------+\n");  if (type < 1500)    printf("| Length:            %5u|\n", type);  //十进制无符号整数   else     printf("| Ethernet Type:    0x%04x|\n", type);  //以十六进制形式输出,占宽4位右对齐,不足4位的前面用0补齐  printf("+-------------------------+\n");}

htonl() htons() ntohl() ntohs()比较:

#include <arpa/inet.h>(头文件)
uint32_t htonl(uint32_t hostlong);表示将16位的主机字节顺序转化为16位的网络字节顺序。
uint16_t htons(uint16_t hostshort);将主机的无符号短整形数转换成网络字节顺序,返回一个网络字节顺序的值。

uint32_t ntohl(uint32_t netlong);表示将32位的主机字节顺序转化为32位的网络字节顺序。

uint16_t ntohs(uint16_t netshort);将一个无符号短整形数从网络字节顺序转换为主机字节顺序,返回一个以主机字节顺序表达的数。


区分802.3帧和以太网帧根据源地址段后的前两个字节的类型不同。 如果值大于 1500(0x05DC),说明是以太网类型字段,EthernetII 帧格式。值小于等于 1500,说明是长 度字段,IEEE802.3 帧格式,该类型字段值最小的是 0x0600。而长度最大为 1500。以太网的帧的格式与标准和802.3标准的格式分别如下:

以太头定义在#include <netinet/if_ether.h>:

  1. typedef struct ether_header  
  2. {  
  3.     u_char ether_dhost[ETHER_ADDR_LEN];   // Destination MAC address
  4.     u_char ether_shost[ETHER_ADDR_LEN];  // Source MAC address 
  5.     u_short ether_type;   // Protocol type
  6. }


/*****显示一个arp包*****/void print_arp(struct ether_arp *arp){  static char *arp_op_name[] = {    "Undefine",    "(ARP Request)",    "(ARP Reply)",    "(RARP Request)",    "(RARP Reply)"  };  //显示可选域种类的字符串#define ARP_OP_MAX (sizeof arp_op_name / sizeof arp_op_name[0])  int op = ntohs(arp->ea_hdr.ar_op);  //ARP操作码  if (op < 0 || ARP_OP_MAX < op)  //如果操作码为负或大于选择域,赋值为0(未定义)    op = 0;  printf("Protocol: ARP\n");  printf("+-------------------------+-------------------------+\n");  printf("| Hard Type: %2u%-11s| Protocol:0x%04x%-9s|\n",         ntohs(arp->ea_hdr.ar_hrd), ////硬件类型         (ntohs(arp->ea_hdr.ar_hrd)==ARPHRD_ETHER)?"(Ethernet)":"(Not Ether)",         ntohs(arp->ea_hdr.ar_pro), //协议类型         (ntohs(arp->ea_hdr.ar_pro)==ETHERTYPE_IP)?"(IP)":"(Not IP)");  printf("+------------+------------+-------------------------+\n");  printf("| HardLen:%3u| Addr Len:%2u| OP: %4d%16s|\n", //硬件地址长度,协议地址长度,ARP操作码         arp->ea_hdr.ar_hln, arp->ea_hdr.ar_pln, ntohs(arp->ea_hdr.ar_op),         arp_op_name[op]);  printf("+------------+------------+-------------------------"         "+-------------------------+\n");  printf("| Source MAC Address:                               "         "         %17s|\n", mac_ntoa(arp->arp_sha)); //源MAC地址   printf("+---------------------------------------------------"         "+-------------------------+\n");  printf("| Source IP Address:                 %15s|\n",         inet_ntoa(*(struct in_addr *) &arp->arp_spa)); //源IP地址  printf("+---------------------------------------------------"         "+-------------------------+\n");  printf("| Destination MAC Address:                          "         "         %17s|\n", mac_ntoa(arp->arp_tha)); //目的MAC地址  printf("+---------------------------------------------------"         "+-------------------------+\n");  printf("| Destination IP Address:            %15s|\n",         inet_ntoa(*(struct in_addr *) &arp->arp_tpa)); //目的IP地址  printf("+---------------------------------------------------+\n");} 

ether_arp定义在#include <netinet/if_ether.h>:


  1. typedef struct ether_arp  
  2. {  
  3.     struct arphdr ea_hdr;  //Fixed-size header
  4.     u_char arp_sha[6];  //Source hardware address
  5.     u_char arp_spa;  //Source protocol address
  6.     u_char arp_tha[6];  //Target hardware address
  7.     u_char arp_tpa;  //Target protocol address
  8. }ETH_ARP;

  1. typedef struct arphdr  
  2. {  
  3.     u_short ar_hrd;  //Format of hardware address 
  4.     u_short ar_pro;  //Format of protocol address
  5.     u_char ar_hln;  //Length of hardware address
  6.     u_char ar_pln;  //Length of protocol address
  7.     u_short ar_op;  //ARP operation. 
  8. }


/*****显示IP报头*****/void print_ip(struct ip *ip){  char protocol[5];  switch(ip->ip_p)  //ip协议,比较常用的协议号  {    case 17:strncpy (protocol,"UDP",5);break;    case 6:strncpy (protocol,"TCP",5);break;    case 1:strncpy (protocol,"ICMP",5);break;    case 2:strncpy (protocol,"IGMP",5);break;    case 88:strncpy (protocol,"IGRP",5);break;    case 89:strncpy (protocol,"OSPF",5);break;    default:sprintf(protocol,"%d",ip->ip_p);break;//itoa(ip->ip_p,protocol,5)  }  printf("Protocol: IP\n");  printf("+--------+------+------------+----------------------+\n");  printf("| IV:IPv%1u| HL:%2u| T: %8s| Total Length: %7u|\n",         ip->ip_v, ip->ip_hl, ip_ttoa(ip->ip_tos), ntohs(ip->ip_len));  //版本,包头长度,服务类型,总长度  printf("+-----+------+------------+-------+-----------------+\n");  printf("| Identifier:        %5u| FF:%3s| FO:        %5u|\n",         ntohs(ip->ip_id), ip_ftoa(ntohs(ip->ip_off)),  //标识符,偏移量         ntohs(ip->ip_off) &IP_OFFMASK);    //===============================??????  printf("+------------+------------+-------+-----------------+\n");  printf("| TTL:    %3u| Pro:    %3s| Header Checksum:   %5u|\n",         ip->ip_ttl, protocol, ntohs(ip->ip_sum));  //生存时间,协议,头部校验  printf("+------------+------------+-------------------------+\n");  printf("| Source IP Address:                 %15s|\n",         inet_ntoa(*(struct in_addr *) &(ip->ip_src))); //源ip地址  printf("+---------------------------------------------------+\n");  printf("| Destination IP Address:            %15s|\n",         inet_ntoa(*(struct in_addr *) &(ip->ip_dst))); //目的ip地址  printf("+---------------------------------------------------+\n");}


ip报头定义在#include <ip.h>:

  1. typedef struct ip  
  2. {  
  3.     u_int ip_v:4; //version(版本)   
  4.     u_int ip_hl:4; //header length(包头长度)
  5.     u_char ip_tos;  //服务类型
  6.     u_short ip_len;  //总长度
  7.     u_short ip_id;  //标识符
  8.     u_short ip_off;  //偏移量
  9.     u_char ip_ttl;  //生存时间
  10.     u_char ip_p;  //协议
  11.     u_short ip_sum;  //首部校验和
  12.     struct in_addr ip_src;  //源ip地址
  13.     struct in_addr ip_dst;  //目的ip地址
  14. }

版本号(Version):长度4比特。标识目前采用的IP协议的版本号。一般的值为0100IPv4),0110IPv6
IP包头长度(Header Length):长度4比特,描述IP包头的长度,因为在IP包头中有变长的可选部分。占4bit位,单位为32bit4个字节),即本区域值= IP头部长度(单位为bit/(8*4),因此,一个IP包头的长度最长为“1111”,即15*460个字节。IP包头最小长度为20字节。
服务类型(Type of Service):长度8比特。8位 按位被如下定义 PPP D T R C 0
PPP 
定义包的优先级:000 普通 (Routine);001 优先的 (Priority);010 立即的发送 (Immediate);011 闪电式的 (Flash);100 比闪电还闪电式的 (Flash Override);
                                     101CRI/TIC/ECP (不知道虾米意思);110 网间控制 (Internetwork Control);111 网络控制 (Network Control)
时延: 0:普通 1:尽量小
吞吐量: 0:普通 1:尽量大
可靠性: 0:普通 1:尽量大
传输成本: 0:普通 1:尽量小
最后一位被保留,恒定为0
上述代码对服务类型的显示转换如下:

/*****将ip头的服务类型(tos)域变为字符串*****/char *ip_ttoa(int flag){  static int  f[] = {'1', '1', '1', 'D', 'T', 'R', 'C', 'X'};                                  #define TOS_MAX (sizeof f / sizeof f[0])  static char str[TOS_MAX + 1];    unsigned int mask = 0x80;       int i;                          for (i = 0; i < TOS_MAX; i++) {    if (((flag << i) & mask) != 0)      str[i] = f[i];    else      str[i] = '0';  }  str[i] = '\0';  return str;}


IP包总长(Total Length):长度16比特。 以字节为单位计算的IP包的长度 (包括头部和数据),所以IP包最大长度65535字节。
标识符(Identifier):长度16比特。该字段和FlagsFragment Offest字段联合使用,对大的上层数据包进行分段(fragment)操作。路由器将一个包拆分后,所有拆分开的小包被标记相同的值,以便目的端设备能够区分哪个包属于被拆分开的包的一部分。
标记(Flags):长度3比特。该字段第一位不使用。第二位是DFDon't Fragment)位,DF位设为1时表明路由器不能对数据包分片。如果一个数据包无法在不分片的情况下进行转发,则路由器会丢弃该数据包并返回一个错误信息。第三位是MFMore Fragments)位,当路由器对一个数据包分片,则路由器会在除了最后一个分段的IP包的包头中将MF位设为1来表明后面还有分片。上述代码对标识符的转换如下:

/*****将ip标识符变为字符串*****/char *ip_ftoa(int flag){  static int  f[] = {'R', 'D', 'M'}; //显示段标志的字符#define IP_FLG_MAX (sizeof f / sizeof f[0])  static char str[IP_FLG_MAX + 1];   //存储返回值的缓冲区  unsigned int mask = 0x8000;     //掩码  int i;                         //循环变量  for (i = 0; i < IP_FLG_MAX; i++) {    if (((flag << i) & mask) != 0)      str[i] = f[i];    else      str[i] = '0';  }  str[i] = '\0';  return str;}


片偏移(Fragment Offset):长度13比特。表示该IP包在该组分片包中位置,接收端靠此来组装还原IP包。
生存时间(TTL):长度8比特。当IP包进行传送时,先会对该字段赋予某个特定的值。当IP包经过每一个沿途的路由器的时候,每个沿途的路由器会将IP包的TTL值减少1。如果TTL减少为0,则该IP包会被丢弃。这个字段可以防止由于路由环路而导致IP包在网络中不停被转发。
协议(Protocol):长度8比特。标识了上层所使用的协议。比较常用的协议号:1    ICMP;2    IGMP ;6    TCP;17   UDP;88   IGRP ;89   OSPF
头部校验(Header Checksum):长度16位。用来做IP头部的正确性检测,但不包含数据部分。 因为每个路由器要改TTL的值,所以路由器会为每个通过的数据包重新计算这个值。

----------因为对包的拆解道理类似,接下来就不一一贴拆解的代码,文章最后会给出所有的代码-----------
icmp报文结构体定义在#include <netinet/ip_icmp.h>:
struct  icmp {u_charicmp_type;/* type of message*/u_charicmp_code;/* type sub code(报文类型子码) */u_shorticmp_cksum;/* 校验和 */}
各类型的icmp报文及其代码如图:



tcphdr结构体定义在#include <netinet/tcp.h>:
struct tcphdr  {    u_int16_t th_sport; /* 源端口 */    u_int16_t th_dport; /* 目的端口 */    tcp_seq th_seq; /* 序列号 */    tcp_seq th_ack; /* ack确认号 */    u_int8_t th_off:4; /* 数据偏移量 */    u_int8_t th_flags; /*标志位*/    u_int16_t th_win; /* 窗口大小 */    u_int16_t th_sum; /* 校验和 */    u_int16_t th_urp; /* 紧急数据偏移量 */};

tcp头结构:
其中,标志位各个位表示:
URG:紧急标志 此标志表示TCP包的紧急指针域有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据。
ACK:确认标志 大多数情况下该标志位是置位的。TCP报头内的确认编号栏内包含的确认编号为下一个预期的序列编号,同时提示远端系统已经成功接收所有数据。 
PSH:推标志 该标志置位时,数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队。
RST:复位标志 复位标志有效。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包。
SYN:同步标志 该标志仅在三次握手建立TCP连接时有效,用来建立连接。
FIN:结束标志 表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的TCP数据包后,连接将被断开。
代码中对标志位的显示代码如下:
char *tcp_ftoa(int flag){  static int  f[] = {'U', 'A', 'P', 'R', 'S', 'F'}; #define TCP_FLG_MAX (sizeof f / sizeof f[0])  static char str[TCP_FLG_MAX + 1];             unsigned int mask = 1 << (TCP_FLG_MAX - 1);   int i;                                       for (i = 0; i < TCP_FLG_MAX; i++) {    if (((flag << i) & mask) != 0)      str[i] = f[i];    else      str[i] = '0';  }  str[i] = '\0';  return str;}


udp类似,其头部结构如下(代码类似就不赘述):

udphdr包含在#include <netinet/udp.h>:
typedef struct udphdr {     uint16_t uh_sport;   //源端口     uint16_t uh_dport;   //目的端口     uint16_t uh_ulen;    //数据包长度     uint16_t uh_sum;     //检验和 }

打开bpf:
#ifndef __linux/*打开bpf*/int open_bpf(char *ifname, int *bufsize){  char buf[CMAX];      int bpfd;          struct ifreq ifr;   int i;       for (i = 0; i < 4; i++) {     snprintf(buf, CMAX, "/dev/bpf%d", i);    if ((bpfd = open(buf, O_RDWR, 0)) > 0)      break;  }  if (i >= 5) {    fprintf(stderr, "cannot open BPF\n");    return -1;  }  *bufsize = MAXSIZE;  if (ioctl(bpfd, BIOCSBLEN, bufsize) < 0) {  //定义缓冲区长度    snprintf(buf, CMAX, "ioctl(BIOCSBLEN, %d)", *bufsize);    perror(buf);    return -1;  }  snprintf(ifr.ifr_name, CMAX, "%s", ifname);  if (ioctl(bpfd, BIOCSETIF, &ifr) < 0) { //定义和文件关联的硬件接口    snprintf(buf, CMAX, "ioctl(BIOCSETIF, '%s')", ifname);    perror(buf);    return -1;  }  fprintf(stderr, "BPF read from '%s' (%s)\n", ifr.ifr_name, buf);  if (ioctl(bpfd, BIOCPROMISC, NULL) < 0) { //设置混杂模式    perror("ioctl(BIOCPROMISC)");    return -1;  }  i = 1;  if (ioctl(bpfd, BIOCIMMEDIATE, &i) < 0) { //启用"immediate mode"    perror("ioctl(BIOCIMMEDIATE)");    return -1;  }  return bpfd;}#endif

bpf是伯克利封包过滤器(Berkeley Packet Filter,缩写 BPF),是类Unix系统上数据链路层的一种原始接口,提供原始链路层封包的收发,除此之外,如果网卡驱动支持洪泛模式,那么它可以让网卡处于此种模式,这样可以收到网络上的所有包,不管他们的目的地是不是所在主机

ioctl()是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的参数个数如下:int ioctl(int fd, int cmd, …);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数控制设备的I/O通道。对于cmd的具体说明,可以参考这里点击打开链接


--------------------------------------------------------------------------------------------持续更新中---------------------------------------------------------------------------------



1 0
原创粉丝点击