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);将一个无符号短整形数从网络字节顺序转换为主机字节顺序,返回一个以主机字节顺序表达的数。
区分帧根据源地址段后的前两个字节的类型不同。 如果值大于 1500(0x05DC),说明是以太网类型字段,EthernetII 帧格式。值小于等于 1500,说明是长 度字段,IEEE802.3 帧格式,该类型字段值最小的是 0x0600。而长度最大为 1500。以太网的帧的格式与标准和802.3标准的格式分别如下:
以太头定义在#include <netinet/if_ether.h>:
- typedef struct ether_header
- {
- u_char ether_dhost[ETHER_ADDR_LEN]; // Destination MAC address
- u_char ether_shost[ETHER_ADDR_LEN]; // Source MAC address
- u_short ether_type; // Protocol type
- }
/*****显示一个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>:
- typedef struct ether_arp
- {
- struct arphdr ea_hdr; //Fixed-size header
- u_char arp_sha[6]; //Source hardware address
- u_char arp_spa; //Source protocol address
- u_char arp_tha[6]; //Target hardware address
- u_char arp_tpa; //Target protocol address
- }ETH_ARP;
- typedef struct arphdr
- {
- u_short ar_hrd; //Format of hardware address
- u_short ar_pro; //Format of protocol address
- u_char ar_hln; //Length of hardware address
- u_char ar_pln; //Length of protocol address
- u_short ar_op; //ARP operation.
- }
/*****显示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>:
- typedef struct ip
- {
- u_int ip_v:4; //version(版本)
- u_int ip_hl:4; //header length(包头长度)
- u_char ip_tos; //服务类型
- u_short ip_len; //总长度
- u_short ip_id; //标识符
- u_short ip_off; //偏移量
- u_char ip_ttl; //生存时间
- u_char ip_p; //协议
- u_short ip_sum; //首部校验和
- struct in_addr ip_src; //源ip地址
- struct in_addr ip_dst; //目的ip地址
- }
版本号(Version):长度4比特。标识目前采用的IP协议的版本号。一般的值为0100(IPv4),0110(IPv6)
IP包头长度(Header Length):长度4比特,描述IP包头的长度,因为在IP包头中有变长的可选部分。占4个bit位,单位为32bit(4个字节),即本区域值= IP头部长度(单位为bit)/(8*4),因此,一个IP包头的长度最长为“1111”,即15*4=60个字节。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);
PPP 定义包的优先级:000 普通 (Routine);001 优先的 (Priority);010 立即的发送 (Immediate);011 闪电式的 (Flash);100 比闪电还闪电式的 (Flash Override);
101CRI/TIC/ECP (不知道虾米意思);110 网间控制 (Internetwork Control);111 网络控制 (Network Control)
D 时延: 0:普通 1:尽量小
T 吞吐量: 0:普通 1:尽量大
R 可靠性: 0:普通 1:尽量大
C 传输成本: 0:普通 1:尽量小
0 最后一位被保留,恒定为0
D 时延: 0:普通 1:尽量小
T 吞吐量: 0:普通 1:尽量大
R 可靠性: 0:普通 1:尽量大
C 传输成本: 0:普通 1:尽量小
0 最后一位被保留,恒定为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比特。该字段和Flags和Fragment Offest字段联合使用,对大的上层数据包进行分段(fragment)操作。路由器将一个包拆分后,所有拆分开的小包被标记相同的值,以便目的端设备能够区分哪个包属于被拆分开的包的一部分。
标记(Flags):长度3比特。该字段第一位不使用。第二位是DF(Don't Fragment)位,DF位设为1时表明路由器不能对数据包分片。如果一个数据包无法在不分片的情况下进行转发,则路由器会丢弃该数据包并返回一个错误信息。第三位是MF(More Fragments)位,当路由器对一个数据包分片,则路由器会在除了最后一个分段的IP包的包头中将MF位设为1来表明后面还有分片。上述代码对标识符的转换如下:
标识符(Identifier):长度16比特。该字段和Flags和Fragment Offest字段联合使用,对大的上层数据包进行分段(fragment)操作。路由器将一个包拆分后,所有拆分开的小包被标记相同的值,以便目的端设备能够区分哪个包属于被拆分开的包的一部分。
标记(Flags):长度3比特。该字段第一位不使用。第二位是DF(Don't Fragment)位,DF位设为1时表明路由器不能对数据包分片。如果一个数据包无法在不分片的情况下进行转发,则路由器会丢弃该数据包并返回一个错误信息。第三位是MF(More 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的值,所以路由器会为每个通过的数据包重新计算这个值。
头部校验(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数据包后,连接将被断开。
代码中对标志位的显示代码如下:
ACK:确认标志 大多数情况下该标志位是置位的。TCP报头内的确认编号栏内包含的确认编号为下一个预期的序列编号,同时提示远端系统已经成功接收所有数据。
PSH:推标志 该标志置位时,数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队。
RST:复位标志 复位标志有效。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包。
SYN:同步标志 该标志仅在三次握手建立TCP连接时有效,用来建立连接。
FIN:结束标志 表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的TCP数据包后,连接将被断开。
代码中对标志位的显示代码如下:
udp类似,其头部结构如下(代码类似就不赘述):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;}
udphdr包含在#include <netinet/udp.h>:
typedef struct udphdr { uint16_t uh_sport; //源端口 uint16_t uh_dport; //目的端口 uint16_t uh_ulen; //数据包长度 uint16_t uh_sum; //检验和 }
#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
- linux下包监控程序[C语言]
- c语言基于Linux下用libpcap实现抓包程序
- Linux下C语言多线程小程序
- Linux下C语言程序开发环境
- Linux下C语言程序开发环境
- 在Linux下运行C语言程序
- 在Linux下运行C语言程序
- Linux下编写C语言程序
- linux下gdb调试c语言程序
- Linux下编译C语言程序
- Linux下编译C语言程序
- 在Linux下运行C语言程序
- C语言程序Linux下运行
- linux c 更正版linux下c语言ping程序
- 【C语言】Linux操作环境下编译C程序
- C语言程序监控变量的变化
- Linux 下编译hello world 的C 语言程序
- 第一次使用vim在Linux下编写C语言程序
- android studio 符号大全
- 自定义底部显示并没有标题栏的Dialog
- JSP基础---java server page
- Leetcode 461 Hamming Distance
- 数据库学习进阶一
- linux下包监控程序[C语言]
- c++ 数据结构 字符串的自定义类 (文章最后解释了KMP算法)
- 算法题整理
- 第一章 Single Threaded Execution
- 【知其所以然】-对链接的思考
- 从零开始,深度学习(一)
- 周期信号的傅里叶级数表示
- lintcode,全排列
- CNN-目标检测、定位、分割