UNIX网络编程——尝试探索基于Linux C的网卡抓包过程
抓包首先便要知道经过网卡的数据其实都是通过底层的链路层(MAC),在Linux系统中我们获取网卡的数据流量其实是直接从链路层收发数据帧。至于如何进行TCP/UDP连接本文就不再赘述(之前的一段关于web server的程序已经大概说明),直接从最关键的原始套接字( raw socket)开始。
通常情况下程序设计人员接触的网络知识限于如下两类:
(1)流式套接字(SOCK_STREAM),它是一种面向连接的套接字,对应于TCP应用程序。
(2)数据报套接字(SOCK_DGRAM),它是一种无连接的套接字,对应于的UDP应用程序。
除了以上两种基本的套接字外还有一类原始套接字,它是一种对原始网络报文进行处理的套接字。
流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)涵盖了一般应用层次的TCP/IP应用。
原始套接字的创建使用与通用的套接字创建的方法是一致的,只是在套接字类型的选项上使用的是另一个SOCK_RAW。在使用socket函数进行函数创建完毕的时候,还要进行套接字数据中格式类型的指定,设置从套接字中可以接收到的网络数据格式:
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
fd为返回的套接字描述符SOCK_RAW即创建原始套接字,这里的htons(ETH_P_ALL)是socket()的第三个参数 protocol ,即协议类型。是一个定义在<netinet/in.h>中的常量,基本上我们常用的是,ETH_P_IP、ETH_P_ARP、ETH_P_RARP或ETH_P_ALL,当然链路层协议很多,肯定不止我们说的这几个,但我们一般只关心这几个就够我们用了。这里简单 提一下网络数据收发的一点基础。
协议栈在组织数据收发流程时需要处理好两个方面的问题:“从上到下”,即数据发送的任务;“从下到上”,即数据接收的任 务。数据发送相对接收来说要容易些,因为对于数据接收而言,网卡驱动还要明确什么样的数据该接收、什么样的不该接收等问题。
他们都有特定的含义:
|
protocol |
值 |
作用 |
|
ETH_P_IP |
0X0800 |
只接收发往目的MAC是本机的IP类型的数据帧 |
|
ETH_P_ARP |
0X0806 |
只接收发往目的MAC是本机的ARP类型的数据帧 |
|
ETH_P_RARP |
0X8035 |
只接受发往目的MAC是本机的RARP类型的数据帧 |
|
ETH_P_ALL |
0X0003 |
接收发往目的MAC是本机的所有类型(ip,arp,rarp)的数据帧,同时还可以接收从本机发出去的所有数据帧。在混杂模式打开的情况下,还会接收到发往目的MAC为非本地硬件地址的数据帧。 |
根据自己的抓包的对象我们选取相应的参数值,就可以获取不同的数据包。
这里我选择的是ETH_P_ALL,可以获取所有经过本机的数据包,但有一个前提便是需要设置网卡为混杂模式(Promiscuous Mode),这里我们就必须去了解一个重要的数据类型struct ifreq ;关于他的定义直接百度如下:
struct ifreq
{
# define IFHWADDRLEN 6
# define IFNAMSIZ IF_NAMESIZE
union
{
char ifrn_name[IFNAMSIZ]; /* Interface 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 int ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
__caddr_t ifru_data;
} ifr_ifru;
};
# define ifr_name ifr_ifrn.ifrn_name /* interface name*/ 在这里就是网卡eth0或eth1
# define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */ mac地址
# define ifr_addr ifr_ifru.ifru_addr /* address */ source地址
# define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */ 目的ip地址
# define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ 广播地址
# define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */ 子网掩码
# define ifr_flags ifr_ifru.ifru_flags /* flags */ 模式标志 设置混杂模式
# define ifr_metric ifr_ifru.ifru_ivalue /* metric */
# define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
# define ifr_map ifr_ifru.ifru_map /* device map */
# define ifr_slave ifr_ifru.ifru_slave /* slave device */
# define ifr_data ifr_ifru.ifru_data /* for use by interface */
# define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
# define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
# define ifr_qlen ifr_ifru.ifru_ivalue /* queue length */
# define ifr_newname ifr_ifru.ifru_newname /* New name */
# define _IOT_ifreq _IOT(_IOTS(char),IFNAMSIZ,_IOTS(char),16,0,0)
# define _IOT_ifreq_short _IOT(_IOTS(char),IFNAMSIZ,_IOTS(short),1,0,0)
# define _IOT_ifreq_int _IOT(_IOTS(char),IFNAMSIZ,_IOTS(int),1,0,0)
上面好多的定义,但用到的只是几个,我做了相应的中文标注,设置混杂模式需要的就是将ifr_flags 设置为 IFF_PROMISC就可以了。
struct ifreq ethreq;
if(-1 == ioctl(sock,SIOCGIFFLAGS,ðreq)){ //获取接口标志
perror("ioctl");
close(sock);
exit(1);
}
ethreq.ifr_flags |=IFF_PROMISC; //IFF_PROMISC 这个标志设置(由网络代码)来激活混杂操作
在这之后需要设置socket选项如下:
ret= int setsockopt( intsocket, int level, int option_name, const void *option_value, size_toption_len);
第一个参数socket是套接字描述符。第二个参数level是被设置的选项的级别,如果想要在套接字级别上设置选项,就必须把level设置为 SOL_SOCKET。option_name指定准备设置的选项,option_name可以有哪些取值,这取决于level。
完成这步后就可以通过:
{
i struct ifreq ifr;
strcpy(ifr.ifr_name, “eth0”);
ioctl(fd, SIOCGIFHWADDR, &ifr);
}
获取网卡的索引接口了,接着我们要做的就是将创建的原始套接字绑定在指定的网卡上了
ret= bind(fd, (struct sockaddr *)& sock, sizeof(sock));这里的sock便不再是之前TCP连接时的 struct sockaddr_in 了,而是struct sockaddr_ll,定义如下:
struct sockaddr_ll{
unsigned short sll_family; /* 总是 AF_PACKET */
unsigned short sll_protocol; /* 物理层的协议 */
int sll_ifindex; /* 接口号 */
unsigned short sll_hatype; /* 报头类型 */
unsigned char sll_pkttype; /* 分组类型 */
unsigned char sll_halen; /* 地址长度 */
unsigned char sll_addr[8]; /* 物理层地址 */
};
对应设置如下:
struct sockaddr_ll sock;
sock.sll_family = AF_PACKET;
sock.sll_ifindex = stIf.ifr_ifindex;(struct ifreq)
sock.sll_protocol = htons(ETH_P_ALL);
ret = bind(fd, (struct sockaddr *)&, sizeof(sock));
这样我们就可以开始通过recvfrom()等函数获取对应套接字上的数据了。
说到这里我们已经获得了网卡上的一段数据包,可是数据包是什么样子的?这里你就必须了解什么是Ethnet,即以太数据帧
以太帧首部中2字节的帧类型字段指定了其上层所承载的具体协议,常见的有:
0x0800表示是IP报文、0x0806表示RARP协议、0x0806即为我们将要讨论的ARP协议。
硬件类型: 1表示以太网。
网卡从线路上收到信号流,网卡的驱动程序会去检查数据帧开始的前6个字节,即目的主机的MAC地址,如果和自己的网卡地址一致它才会接收这个帧,不符合的一般都是直接无视。然后该数据帧会被网络驱动程序分解,IP报文将通过网络协议栈,最后传送到应用程序那里。
从这里我们可以看出以太数据帧头包含了MAC地址与帧类型,这正是我们需要的,所以我们便要开始对以太帧头进行解析,这就涉及另一个重要的数据类型struct ether_header;
其中重要的数据信息有:
u_charether_dhost[ETHER_ADDR_LEN];//dest 的MAC地址信息
u_char ether_shost[ETHER_ADDR_LEN];// source的MAC地址信息
u_short ether_type; // ip 协议类型 :ipv4……….等
static int ethdump_parseEthHead(const struct ether_header *pstEthHead)
{
unsigned short usEthPktType;
/* 协议类型、源MAC、目的MAC */
usEthPktType=ntohs(pstEthHead->ether_type); printf("EthType:0x%04x(%s)\n",usEthPktType,ethdump_getProName(usEthPktType));
ethdump_showMac( pstEthHead->ether_shost);
ethdump_showMac(pstEthHead->ether_dhost); return 0;
}
ntohs(ether_type)返回一个以主机字节顺序表达的数
static void ethdump_showMac(const char acHWAddr[])
{
for(i = 0; i < ETHER_ADDR_LEN - 1; i++)
{
printf("%02x:", *((unsigned char *)&(acHWAddr[i])));
}
printf("%02x] \n", *((unsigned char *)&(acHWAddr[i])));
}
接着我们就需要对下一段内容,即IP数据包进行解析,这里对应的数据结构为struct ip,对于他的定义有些复杂,我也不是很懂,但是我们只需要获取:
u_int8_tip_p; /* protocol */协议类型
structin_addr ip_src, ip_dst; /* source and dest address */源IP与目的IP地址
通过getprotobynumber( ); 返回对应于给定协议号的相关协议信息,输出对应的p_name。以及通过inet_nota(ip_src)打印出对应的source IP 与 dest IP。就可以获取目前我们需要的内容。
static int ethdump_parseIpHead(const struct ip *pstIpHead)
{
struct protoent *pstIpProto = NULL;
/* 协议类型、源IP地址、目的IP地址 */
pstIpProto = getprotobynumber(pstIpHead->ip_p);
if(NULL != pstIpProto)
{
printf("IP-Type: %d (%s) \n", pstIpHead->ip_p, pstIpProto->p_name);
}
else
{
printf("IP-Type: %d (%s)\n ", pstIpHead->ip_p, "None");
}
printf("SAddr=[%s] \n", inet_ntoa(pstIpHead->ip_src));
printf("DAddr=[%s] ", inet_ntoa(pstIpHead->ip_dst));
printf("\n========================================\n");
return 0;
}
到这里基本上我们就完成了对数据包的抓取,以及获取 source mac 、dest mac、source ip 、des ip以及对应协议类型的分析。当然还可以进行进一步的数据分析,以及IP段、协议栈等的分析。越往后走涉及的东西就越复杂。
UNIX网络编程——尝试探索基于Linux C的网卡抓包过程的更多相关文章
- 基于Linux C的socket抓包程序和Package分析 (一)
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/guankle/article/details/27538031 測试执行平台:CentOS 6 ...
- (转载)基于Linux C的socket抓包程序和Package分析
转载自 https://blog.csdn.net/kleguan/article/details/27538031 1. Linux抓包源程序 在OSI七层模型中,网卡工作在物理层和数据链路层的MA ...
- Linux 系统编程 学习:07-基于socket的网络编程2:基于 UDP 的通信
Linux 系统编程 学习:07-基于socket的网络编程2:基于 UDP 的通信 背景 上一讲我们介绍了网络编程的一些概念.socket的网络编程的有关概念 这一讲我们来看UDP 通信. 知识 U ...
- 【LINUX/UNIX网络编程】之简单多线程服务器(多人群聊系统)
RT,Linux下使用c实现的多线程服务器.这个真是简单的不能再简单的了,有写的不好的地方,还希望大神轻拍.(>﹏<) 本学期Linux.unix网络编程的第四个作业. 先上实验要求: [ ...
- 【LINUX/UNIX网络编程】之使用消息队列,信号量和命名管道实现的多进程服务器(多人群聊系统)
RT,使用消息队列,信号量和命名管道实现的多人群聊系统. 本学期Linux.unix网络编程的第三个作业. 先上实验要求: 实验三 多进程服务器 [实验目的] 1.熟练掌握进程的创建与终止方法: 2 ...
- 【Linux/unix网络编程】之使用socket进行TCP编程
实验一 TCP数据发送与接收 [实验目的] 1.熟练掌握套接字函数的使用方法. 2.应用套接字函数完成基本TCP通讯,实现服务器与客户端的信息交互. [实验学时] 4学时 [实验内容] 实现一个服务器 ...
- UNIX网络编程中的需要注意的问题
字节流套接字上调用read或write,输入或输出的字节数可能比请求的数量少,这个现象的原因在于内核中用于套接字的缓冲区可能已经达到了极限.此时所需要的是调用者再次调用read或write函数.这个现 ...
- 《Unix 网络编程》05:TCP C/S 程序示例
TCP客户/服务器程序示例 系列文章导航:<Unix 网络编程>笔记 目标 ECHO-Application 结构如下: graph LR; A[标准输入/输出] --fgets--> ...
- UNIX网络编程——getsockname和getpeername函数
UNIX网络编程--getsockname和getpeername函数 来源:网络转载 http://www.educity.cn/linux/1241293.html 这两个函数或者 ...
随机推荐
- [自用]多项式类数学相关(定理&证明&板子)
写在前面 由于上一篇总结的版面限制,特开此文来记录 \(OI\) 中多项式类数学相关的问题. 该文启发于Miskcoo的博客,甚至一些地方直接引用,在此特别说明:若文章中出现错误,烦请告知. 感谢你的 ...
- [HNOI 2014]道路堵塞
Description A国有N座城市,依次标为1到N.同时,在这N座城市间有M条单向道路,每条道路的长度是一个正整数.现在,A国 交通部指定了一条从城市1到城市N的路径,并且保证这条路径的长度是所有 ...
- 【SPOJ839】Optimal Marks 网络流
You are given an undirected graph G(V, E). Each vertex has a mark which is an integer from the range ...
- hdu 5463(水水)
Sample Input 2 3 2 33 3 33 2 33 10 5 467 6 378 7 309 8 499 5 320 3 480 2 444 8 391 5 333 100 499 S ...
- bzoj 2783: [JLOI2012]树
Description 在这个问题中,给定一个值S和一棵树.在树的每个节点有一个正整数,问有多少条路径的节点总和达到S.路径中节点的深度必须是升序的.假设节点1是根节点,根的深度是0,它的儿子节点的深 ...
- hdu5651 xiaoxin juju needs help(逆元)
xiaoxin juju needs help Accepts: 150 Submissions: 966 Time Limit: 2000/1000 MS (Java/Others) Mem ...
- [bzoj4823][Cqoi2017]老C的方块
来自FallDream的博客,未经允许,请勿转载,谢谢. 挺有意思的一道题.... 看完题面比较明确是最小割,考虑怎么建图 想了比较久 突破口应该是题目中那张奇怪的图 观察这个奇怪的图和方块,很容易发 ...
- solr6.6初探之配置篇
一.solr的简介 1) solr是企业级应用的全文检索项目,它是基于Apache Lucence搜索引擎开发出来的用于搜索的应用工程 2) solr最新版本6.6 下载地址:下载地址 二 启动与配置 ...
- 谈谈如何选择合适的MySQL数据类型
MySQL数据类型选择 一 .选择原则 更小的通常更好:一般情况下选择可以正确存储数据的最小数据类型.越小的数据类型通常更快,占用磁盘,内存和CPU缓存更小. 简单就好:简单的数据类型的操作通常需要更 ...
- Linux查看系统版本号及关机命令各种方法归纳
shutdown -h now 立即关机halt 立即关机init 0 立即关机poweroff 立即关机shutdown -r now 重启reboot 重启logout 注销su - 命令切换身份 ...