(转载)基于Linux C的socket抓包程序和Package分析
转载自 https://blog.csdn.net/kleguan/article/details/27538031
1. Linux抓包源程序
在OSI七层模型中,网卡工作在物理层和数据链路层的MAC子层。
进行网络通信时,源主机通过socket(或其它)应用程序产生IP报文,经过各个OSI层层封装,数据包以Ethernet帧的形式进入物理层。Ethernet帧包含源主机地址、IP报文、目标地址(IP地址、端口号或映射的6字节MAC地址)和需要传送到目标主机的其它信息。
目标的MAC地址是哪里来的呢?这牵扯到一个ARP协议(介乎于网络层和数据链路层的一个协议)。第一次传送某个目的IP地址的数据的时候,先会发出一个ARP包,其MAC的目标地址是广播地址,里面说到:"谁是xxx.xxx.xxx.xxx这个IP地址的主人?"因为是广播包,所有这个局域网的主机都收到了这个ARP请求。收到请求的主机将这个IP地址和自己的相比较,如果不相同就不予理会,如果相同就发出ARP响应包。这个IP地址的主机收到这个ARP请求包后回复的ARP响应里说到:"我是这个IP地址的主人"。这个包里面就包括了他的MAC地址。以后的给这个IP地址的帧的目标MAC地址就被确定了。
就这样,以太网帧开始在数据链路层传播。Ethernet帧在链路层基于广播方式传播,即网段内的所有网卡都能观察该帧,但只有一个网卡通过对比6字节MAC地址发现与自己相符,然后它就接收该帧。而其它网卡则放弃该帧。(其它网卡也可以接受该帧,即实际的网络Sniffer,可进行信息窃取等操作)
网卡得到Ethernet帧后,通过网络驱动程序和上层协议对其进行还原操作,即层层剥离报文头后将数据交由目标主机的socket(或其它)应用程序使用。
如果我们需要原始的以太网帧,以便观察与目标主机进行通信的源主机信息,则可以通过建立基于PF_PACKET的socket应用程序实现。PF_PACKET协议簇允许应用程序直接获得网络驱动程序得到的数据帧信息。PF_PACKET支持SOCK_DGRAM和SOCK_RAW两种socket类型,前者利用操作系统处理报文头,而后者则将以太网帧直接交由应用程序处理。需要注意到是,只有root权限用户才能使用PF_PACKET程序。
下面的代码即可实现由应用程序直接获得以太网帧的需求。
- #include <unistd.h>
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <linux/if_ether.h>
- #include <linux/in.h>
- #define BUFFER_MAX 2048
- int main(int argc, char *argv[]){
- int SOCKET_SRC;
- char buf[BUFFER_MAX];
- int n_rd;
- if( (SOCKET_SRC = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0 ){
- fprintf(stderr, "create socket error.\n");
- exit(0);
- }
- while(1){
- n_rd = recvfrom(SOCKET_SRC, buf, BUFFER_MAX, 0, NULL, NULL);
- if (n_rd<46) {
- perror("recvfrom():");
- printf("Incomplete packet (errno is %d)\n", errno);
- close(SOCKET_SRC);
- exit(0);
- }
- /* An Ethernet frame was written to buf, frame analysis can be processed here */
- /* Termination control */
- }
- close(SOCKET_SRC);
- return 0;
- }
2. 数据包(以太网帧)分析
一个以太网帧(RFC894)的数据格式如下图所示。
以太网帧(RFC894)格式
程序通过执行一次
- n_rd = recvfrom(SOCKET_SRC, buf, BUFFER_MAX, 0, NULL, NULL);
就将上面一条以太网帧写入buf中。
(1) 为了从buf中提取以太网报文头,我们可以定义如下结构体。
- typedef struct mac_frm_hdr {
- char dest_addr[6]; //destination MAC address shall be defined first.
- char src_addr[6];
- short type;
- }__attribute__((packed)) MAC_FRM_HDR;
定义该结构体时,需要注意以下几点。
a.各属性需按帧格式的出现顺序定义。
b.数据类型长度必须和帧中相应区域的长度相同。
c. 使用__attribute__((packed))取消编译器自动优化对齐结构体,也是为了保证属性长度和帧中相应区域的长度相同。
(2)为了提取IP报文头,根据IP报文头的格式,我们可定义如下结构体。
- typedef struct ip_hdr{ //header of IPV4
- #ifdef __LITTLE_ENDIAN_BIFIELD
- u_char ip_len:4, ip_ver:4;
- #else
- u_char ip_ver:4, ip_len:4;
- #endif
- u_char ip_tos;
- u_short ip_total_len;
- u_short ip_id;
- u_short ip_flags;
- u_char ip_ttl;
- u_char ip_protocol;
- u_short ip_chksum;
- u_int32 ip_src;
- u_int32 ip_dest;
- }__attribute__((packed)) IP_HDR;
为保证各属性长度与IP报文头中一致,我们应该在定义该结构体前作如下声明。
- typedef int int32;
- typedef unsigned int u_int32;
- typedef unsigned char u_char;
- typedef unsigned short u_short;
注意事项参考定义以太网帧结构体。
(3)为了提取UDP/TCP报文头,可根据UDP/TCP报文头格式,定义相应结构体,这里不作赘述。
下面是对以太网帧进行解析的具体代码。
- MAC_FRM_HDR *mac_hdr; //define a Ethernet frame header
- IP_HDR *ip_hdr; //define a IP header
- char *tmp1, *tmp2;
- int AND_LOGIC = 0xFF;
- mac_hdr = buf; //buf is what we got from the socket program
- ip_hdr = buf + sizeof(MAC_FRM_HDR);
- //udp_hdr = buf + sizeof(MAC_FRM_HDR) + sizeof(IP_HDR); //if we want to analyses the UDP/TCP
- tmp1 = mac_hdr->src_addr;
- tmp2 = mac_hdr->dest_addr;
- /* print the MAC addresses of source and receiving host */
- printf("MAC: %.2X:%.2X:%.2X:%.2X:%.2X:%.2X==>" "%.2X:%.2X:%.2X:%.2X:%.2X:%.2X",
- tmp1[0]&AND_LOGIC, tmp1[1]&AND_LOGIC, tmp1[2]&AND_LOGIC,tmp1[3]&AND_LOGIC,
- tmp1[4]&AND_LOGIC, tmp1[5]&AND_LOGIC,
- tmp2[0]&AND_LOGIC, tmp2[1]&AND_LOGIC, tmp2[2]&AND_LOGIC,tmp2[3]&AND_LOGIC,
- tmp2[4]&AND_LOGIC, tmp2[5]&AND_LOGIC);
- tmp1 = (char*)&ip_hdr->ip_src;
- tmp2 = (char*)&ip_hdr->ip_dest;
- /* print the IP addresses of source and receiving host */
- printf("IP: %d.%d.%d.%d => %d.%d.%d.%d",
- tmp1[0]&AND_LOGIC, tmp1[1]&AND_LOGIC, tmp1[2]&AND_LOGIC,tmp1[3]&AND_LOGIC,
- tmp2[0]&AND_LOGIC, tmp2[1]&AND_LOGIC, tmp2[2]&AND_LOGIC,tmp2[3]&AND_LOGIC);
- /* print the IP protocol which was used by the socket communication */
- switch(ip_hdr->ip_protocol) {
- case IPPROTO_ICMP: LOGI("ICMP"); break;
- case IPPROTO_IGMP: LOGI("IGMP"); break;
- case IPPROTO_IPIP: LOGI("IPIP"); break;
- case IPPROTO_TCP:
- case IPPROTO_UDP:
- LOGI("Protocol: %s", ip_hdr->ip_protocol == IPPROTO_TCP ? "TCP" : "UDP");
- LOGI("Source port: %u, destination port: %u", udp_hdr->s_port, udp_hdr->d_port);
- break;
- case IPPROTO_RAW: LOGI("RAW"); break;
- default: printf("Unknown, please query in inclued/linux/in.h\n"); break;
- }
/*************************************************************************
> Author: kleguan
> 如用不当之处,欢迎指正
> 转载请注明出处
************************************************************************/
(转载)基于Linux C的socket抓包程序和Package分析的更多相关文章
- 基于Linux C的socket抓包程序和Package分析 (一)
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/guankle/article/details/27538031 測试执行平台:CentOS 6 ...
- Linux使用tcpdump命令抓包并使用wireshark分析
Linux使用tcpdump命令抓包并使用wireshark分析 介绍 有时分析客户端和服务器网络交互的问题时,为了查找问题,需要分别在客户端和服务器上抓包,我们的客户端一般是windows上的,抓包 ...
- 【转载】linux下的usb抓包方法
1 linux下的usb抓包方法 1.配置内核使能usb monitor: make menuconfig Device Drivers --> ...
- UNIX网络编程——尝试探索基于Linux C的网卡抓包过程
抓包首先便要知道经过网卡的数据其实都是通过底层的链路层(MAC),在Linux系统中我们获取网卡的数据流量其实是直接从链路层收发数据帧.至于如何进行TCP/UDP连接本文就不再赘述(之前的一段关于w ...
- linux下的usb抓包方法
1 linux下的usb抓包方法1.配置内核使能usb monitor: make menuconfig Device Drivers --> ...
- NetAnalyzer笔记 之 三. 用C++做一个抓包程序
[创建时间:2015-08-27 22:15:17] NetAnalyzer下载地址 经过前两篇的瞎扯,你是不是已经厌倦了呢,那么这篇让我们来点有意思的吧,什么,用C#.不,这篇我们先来C++的 Wi ...
- tcpdump 抓包让wireshark来分析
在linux下面用tcpdump 抓包非常方便, 但是抓的包要提取出来进行分析, 还是得用wireshark来过滤分析比较方便. 下面先介绍一下 TCPDUMP 的使用 例:tcpdump host ...
- linux 下检查java jar包 程序是否正常 shell
linux 下检查java jar包 程序是否正常 shell http://injavawetrust.iteye.com BATCH_SERVER="batch.jar" NR ...
- Linux下如何让jar包程序在后台一直执行
Linux下如何让Jar包程序在后台一直执行 shell命令 nohup java -jar xxx.jar & &:让程序后台执行. nohub:让程序控制台输出转移到nohub.o ...
随机推荐
- pandas-10 pd.pivot_table()透视表功能
pandas-10 pd.pivot_table()透视表功能 和excel一样,pandas也有一个透视表的功能,具体demo如下: import numpy as np import pandas ...
- 使用MUI框架实现JQ购物车增减
// 购物车数量减少$('.reduce').click(function () { addMinus(this,0)}); // 购物车数量增加$('.increase').click(functi ...
- 如何用JS获取地址栏参数的方法
采用正则表达式获取地址栏参数: 写一个方法来进行正则匹配,同样也可以复用 function GetPar(name) { var reg = new RegExp("(^|&)&qu ...
- windows下控制台程序实现窗口显示
windows下实现窗口显示,如果限定是C/C++语言,并且是原生Windows支持,需要使用GDI或GDI+.一般是在Visual Studio里新建Win32应用程序,而不是Win32 conso ...
- Java-最常用的Java日志框架整理
Java-最常用的Java日志框架整理 前言 Java程序员,我们开发了很多Java应用程序,包括桌面应用.WEB应用以及移动应用.然而日志系统是一个成熟Java应用所必不可少的,在开发和调试阶段,日 ...
- MongoDB 分片问题汇总
分片是MongoDB的扩展方式,通过分片能够增加更多的机器来用对不断增加的负载和数据,还不影响应用. 1.分片简介 分片是指将数据拆分,将其分散存在不同机器上的过程.有时也叫分区.将数据分散在不同的机 ...
- CentOS7怎样安装Redis4.0.12
一.安装 yum -y install tcl安装相关依赖 mkdir /usr/local/redis创建redis安装目录 cd /usr/local/redis 进入redis目录 wget h ...
- http请求头出现provisional headers are shown
http请求头出现provisional headers are shown Provisional headers are shown provisional 详细用法>> 英 [prə ...
- 大数据开发之keras代码框架应用
总体来讲keras这个深度学习框架真的很“简易”,它体现在可参考的文档写的比较详细,不像caffe,装完以后都得靠技术博客,keras有它自己的官方文档(不过是英文的),这给初学者提供了很大的学习空间 ...
- 在linux中使用Sqlplus命令登录MySQL,查看表并设置行数和宽度,使其正常显示
在linux中使用sqlplus命令进入MySQL,设置行数和行宽 1) 查看目前的pagesize,默认是14: 1. show pagesize; 2. set pa ...