首先用ifconfig去查询本机的网卡,然后在主函数中修改
这里的enp9s0就是我们需要的
/* capture_packet.c - 截获所有以太网帧数据并进行具体分析 */
/* 常用函数的头文件 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <signal.h>
/* 与网络相关的头文件 */
#include <netinet/ip_icmp.h>
#include <net/if_arp.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <signal.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/if_ether.h>
#include <net/ethernet.h>
#include <linux/igmp.h>
#include <netinet/tcp.h>
/* 全局变量结构的结构体原型 - 包含要记录的任何全局信息 */
struct global_info {
unsigned int bytes; /* 网卡接收的总字节数 */
unsigned int packet_num; /* 网卡接受的帧的总数量,帧其实就是分片 */
unsigned int packet_arp; /* 接收到的arp包的数量 */
int print_flag_frame; /* 是否打印帧头信息标志, 1表示打印, 0表示不打印 flag是国旗标志的意思,frame是框架结构画面的意思 */
int print_flag_arp; /* 是否打印arp头信息标志 */
};
/* 定义一个全局变量,用于存储全局信息 */
struct global_info global;
struct ip_pair {
unsigned int source_ip;//源ip地址
unsigned int dest_ip;//目的ip地址
unsigned int count;//?我不知道
};
/* 定义一个用于存储ip对的结构体数组 这个数组应该是用来存储抓到的ip包的地址*/
struct ip_pair ip_pair[10000];
/* 一个用于初始化全局信息的函数 */
void init_global( struct global_info * var );//这里定义了一个无类型的初始化函数,利用了global_info结构体来定义了一个名为var的指针
/* 一个用于打印全局信息的函数 */
void print_global( struct global_info var );//这里和上面又不一样,它是定义了一个global_info结构体的实例
/* 打印一个错误,并退出 */
void error_and_exit( char * msg, int exit_code );//char * msg这里是定义了一个字符类型的指针msg
/* 设置网卡成混杂模式 */
int set_card_promisc( char * interface_name, int sock );//同样的道理,这里是定义了一个字符类型的指针interface_name
/* 把mac地址转换一个字符串 */
void mac_to_str( char * buf, char * mac_buf );
/* 用于打印帮助信息 */
void help( void );
/* 截获网卡帧数据,并进行数据分用*/
void do_frame( int sockfd ); //这里的sockfd应该用到了socket函数
/* 初始化一个全局结构体 它就是将所有的数据全都归零,这样在输出的时候就可以在此基础上进行增加 */
void init_global( struct global_info * var )
{
var->bytes = 0;
var->packet_num = 0;
var->packet_arp = 0;
var->print_flag_frame = 0;
var->print_flag_arp = 0;
}
/* 一个用于打印全局信息的函数 */
void print_global( struct global_info var )
{
printf("\n\n********** 全局信息 *****************\n\n");
printf("总共接收字节数: %d kbytes.\n", var.bytes / 1024 );
printf("总共接受包数量: %d\n\n", var.packet_num );
if( var.packet_arp ) printf("接收 arp 包数量: %d\n", var.packet_arp );//var是一个全局信息那个结构体类型的实例,这里的定义不太好,很容易和上面的初始化函数中的var弄混
printf("\n");
}
/* 用于处理当下按ctrl-c时的处理函数 */
void sig_int( int sig )//sig是签名的意思
{
print_global( global );
int i;
/*
for( i=0; i<global.packet_ip; i++ ){
printf("%15s ==>> ", inet_ntoa( *(struct in_addr *)( &ip_pair[i].source_ip ) ) );
printf("%15s \n", inet_ntoa( *(struct in_addr *)( &ip_pair[i].dest_ip ) ));
}
*/
exit( 0 );
}
/* 打印错误信息,并退出 */
void error_and_exit( char * msg, int exit_code )
{
herror( msg );//报错函数
exit( exit_code ); //该函数类似于exit(1),在终止的时候返回exit_code
}
/* 设置网卡模式成混杂模式,这样的话可以截获以太网帧数据 */
int set_card_promisc( char * interface_name, int sock ) //promisc的意思就是混杂
{
/* 用于套接口ioctl的接口请求结构体 */
struct ifreq ifr; //ifreq为linux中的一个结构体,用来输出接口的信息
/* 复制网卡名称进入请求结构体的名称元素 */
strncpy(ifr.ifr_name, interface_name ,strlen( interface_name )+1); //strncpy是c语言的库函数之一,来自于c语言的标准库string.h,表示将interface_name中前strlen( interface_name )+1个字节复制到ifr.ifr_name这个数组中,并返回ifr.ifr_name。strlen所作的是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符'\0'为止,然后返回计数器值(长度不包含'\0')。正因为如此,在计算网卡名称时是漏掉了一个字符的,所以要+1
/*
struct ifreq {
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; //if 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 ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; //Just fits the size
char ifru_newname[IFNAMSIZ];
void __user * ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
*/
/* 设置网卡模式标志为混杂模式 通过分析ifreq的结构体我们可以知道,其中有着ifr_flags这个属性,可设为混杂模式,意为接受所有的数据包,即使该数据包不是发送给本机的也能接收 */
ifr.ifr_flags |= IFF_PROMISC;
/* 通过ioctl把参数传递给网卡 */
if(ioctl(sock, SIOCSIFFLAGS, &ifr) == -1 )
error_and_exit("ioctl", 3);
} //SIOCSIFFLAGS:用ifr_flags成员设置接口标志。对于ioctl这个函数而言,返回0就是成功,返回-1就是失败,本来我以为这个函数在这里应该就是设置接口标志的,现在看来它在这起的是传参的作用,对就是通过传参来实现混杂模式
/* 把mac地址转换成字符串 */
void mac_to_str( char * buf, char * mac_buf )
{
sprintf( mac_buf, "%02x:%02x:%02x:%02x:%02x:%02x",(unsigned char) *buf, (unsigned char)(*(buf+1)), //C 库函数 int sprintf(char *str, const char *format, ...) 发送格式化输出到 str 所指向的字符串。这里也就是将转化后的字符放到mac_buf这个数组中
//X 表示以十六进制形式输出
//02 表示不足两位,前面补0输出;出过两位,不影响
(unsigned char)(*(buf+2)), (unsigned char)(*(buf+3)),
(unsigned char)(*(buf+4)), (unsigned char)(*(buf+5)));
mac_buf[17] = 0;
}
void help( void )
{
printf("Usage: capture [-h] [协议名称 ...].\n");
printf("默认情况: 打印所有包信息.\n");
}
/*
ARP数据包共有42个字节,其中包括14个字节的以太网首部
其中包括六个字节的以太网目的地址和以太网源地址,还有两个字节的帧类型,注意0x0806是ARP帧的类型值
接下来是28个字节的ARP请求包/应答包
硬件类型 2 0x1 以太网类型值
上层协议类型 2 0x0800 上层协议为IP协议
MAC地址长度 1 0x6 以太网MAC地址长度为 6
IP地址长度 1 0x4 IP地址长度为 4
操作码 2 0x1表示ARP请求包,0x2表示应答包
发送方MAC 6
发送方IP 4
接收方MAC 6
接收方IP 4
填充数据 18 因为物理帧最小长度为64字节,前面的42字节再加上4个CRC校验字节,还差18个字节
*/
void print_arp( struct arphdr * parp )
{
//1
char * addr = (char*)(parp + 1);
char buf[18];
mac_to_str( addr, buf );
printf("发送端以太网地址: %s\n", buf );
printf("发送端IP地址: %s\n", inet_ntoa( *(struct in_addr *)(addr+6) ));
mac_to_str( addr+10, buf );
printf("目的以太网地址: %s\n", buf );
printf("目的IP地址: %s\n", inet_ntoa( *(struct in_addr *)(addr+16) ));
}//我有点懂了,用于存储发送方的地址信息 struct sockaddr_in addr;也就是说源MAC,源IP或者目的MAC,目的IP都被保存在了这个结构体中,不过他们占的比特位数还是不变的,MAC占六位,IP占4位
void do_arp( char * data )
{
global.packet_arp ++;//每执行一次这个函数,所抓到arp包的数量就加1
struct arphdr * parp;
parp = ( struct arphdr * ) data;
if( global.print_flag_arp ) {
printf("============= arp 头信息 ==============\n");
print_arp( parp );
}
}
/* 打印以太网帧的包头信息 */
void print_frame( struct ether_header * peth )
{
/* 定义一个数组,用于存储把mac地址转换成字符串后的字符串 */
char buf[ 18 ];
printf("\n================================== 第 %d 个包 =======================================\n\n", global.packet_num );
printf("==== 以太网帧信息 =====\n");
char * shost = peth->ether_shost;
mac_to_str( shost, buf );
printf("源以太网地址: %s\n", buf );
char * dhost = peth->ether_dhost;
mac_to_str( dhost, buf );
printf("目的以太网地址:%s\n", buf );
}
/* 用于从网卡接受一帧数据,同时根据以太网协议字段传递数据给相应的上层协议处理 */
void do_frame( int sock )
{
/* 定义了一个有两千个比特的数组用于存储一帧数据 */
char frame_buf[ 2000 ];
/* 清空帧数据缓冲区 */
bzero( frame_buf, sizeof(frame_buf) );
int len = sizeof( frame_buf );
/* 用于存储接受字节数 */
int recv_num;
/* 用于存储发送方的地址信息 */
struct sockaddr_in addr;
/* 从网卡接收一帧数据 */
recv_num = recvfrom( sock, (char *)frame_buf, sizeof( frame_buf ), 0, ( struct sockaddr * )&addr, &len );
/* 所接收的包的总数自加1 */
global.packet_num ++;
/* 从网卡接收的字节总数 */
global.bytes += recv_num;
/* 打印接收的包是第几个包 */
//printf("此帧数据长度: %d\n", recv_num );
/* 定义一个用于指向以太网帧的指针 (这里我们只考虑最常见的以太网帧的情况) */
struct ether_header * peth;
/* 让以太网头指针指向从网卡接受到的帧的数据的开头 */
peth = (struct ether_header *)frame_buf;
/* 传递以太网帧首地址给打印以太网帧信息的打印函数 */
if( global.print_flag_frame )
print_frame( peth );
/* 定义一个数据指针,用于指向以太网帧的数据部分 */
char * pdata;
/* 让 pdata 指向以太网帧的数据部分 */
pdata = frame_buf + sizeof( struct ether_header );
/* 根据以太网帧的协议字段进行数据分用 - 也就是进行数据拆封,根据协议字段调用相应层的处理函数 */
switch( ntohs( peth->ether_type ) ){
case ETHERTYPE_ARP:
do_arp( pdata );
break;
default:
printf("Unkonw ethernet type %d %x.\n", ntohs(peth->ether_type), ntohs(peth->ether_type) );
break;
}
}
/* 主函数, 处理命令行输入, 设置好全局变量, 并调用接受和处理帧的函数 */
int main( int argc, char ** argv )
{
printf("1");
/* 用于存储套接口文件描述符 */
int sockfd;
/* 初始化全局变量 */
init_global( &global );
printf("0");
if( argc == 1 ) { /* 表示打印所有包头信息 */
global.print_flag_frame = 1;
global.print_flag_arp = 1;
}
printf("2");
/* 通过协议族AF_PACKET类信SOCK_RAW, 类型SOCK_RAW创建一个用于可以接受网卡帧数据的套接口,同时返回套就口文件描述符 */
if( (sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) ) == -1 )
error_and_exit( "socket", 1 ); /* 如果发生错误,返回错误值, 并退出 */
/* 设定网卡eth0成混杂模式 */
set_card_promisc( "enp9s0", sockfd );
/* 设定信号处理函数, 下面是设置当我们按下ctrl-c时所调用的处理函数 */
signal( SIGINT, sig_int );
/* 无限循环接收以太网卡数据帧, 并进行数据分用,直到你按下ctrl-c */
while( 1 ){
do_frame( sockfd );
}
return 0;
}
4605



被折叠的 条评论
为什么被折叠?



