Linux BSD Socket编程实现以太帧的捕获与分析
编程背景材料:
一、Linux下C语言编程环境介绍
1、使用gedit编辑器输入程序代码
(1) 单击“主菜单”(桌面左下方的图标)—>“附件”->“文本编辑器”,进入gedit编辑界面,输入程序。
(2) 保存文件的方法与MS Windows中类似,注意保存位置,建议将保存位置设为:/home,文件名为:file.c。
2、使用gcc编译器
在桌面上单击右键,选择“新建终端”,弹出一对话框,在命令提示符#后依次输入:
# cd /home //进入程序所在目录
# ls //显示该目录下的文件及文件夹(应能见到file.c)
# gcc –g –o file file.c (例如 # gcc –g –o example example.c ) //编译程序
# ./file (例如 # ./example) //执行程序
二、 监听的实施
以太网上进行数据传输时,在同一网段上连接的所有网卡事实上都可以收到在共享的物理介质上传输的所有数据。但在系统正常工作时,某个主机的网络接口只响应两种数据帧:一是帧的目标MAC地址与本主机网卡地址相符的帧; 二是帧的目标地址是广播地址的帧,除此之外的数据帧都将被丢弃不作处理。要监听流经网卡的不属于自己主机的数据,必须绕过系统正常工作的处理机制,直接访问网络底层。
三、 以太网数据帧、IP数据报、TCP首部格式
1、以太帧数据帧格式如下图所示:
目标地址 | 源地址 | 类型 | 帧 中 数 据 | CRC校验和 |
目的地址和源地址都是6字节的MAC地址(网卡地址),类型指出帧中封装的载荷为何协议类型的报文,类型标识主要有:
0x0800: IP(网际协议);
0x0806: ARP(地址解析协议);
0x8035: RARP(反向地址解析协议);
IP、ARP或RARP协议数据单元的报头则位于帧载荷数据中。由于以太网最小帧长为64字节,最大帧长为1518字节,而帧头加幀尾共18字节,故帧中数据为46~1500字节。
2、以太帧载荷中的 IP 数据报格式及 IP 头中的各字段
IP 信包的头部中 “协议”字段指出 IP 信包中的数据部分封装了何种类型的高层报文,常见的类型有:
1----ICMP,6----TCP,17----UDP
3、IP 信包载荷数据中的 T C P 段格式及TCP段头部中的各字段
四、 Linux编程要点
1、设置套接口以捕获链路帧。在Linux下编写网络监听程序,比较简单的方法是在超级用户模式下,利用类型为SOCK_PACKET的套接口(用socket ( ) 函数创建)来捕获链路帧。Linux程序中需引用如下头文件:
# include <sys/socket.h>
# include <sys/ioctl.h> /* ioctl command */
# include <netinet/if_ether.h> /* ethhdr struct */
# include <net/if.h> /* ifreq struct */
# include <netinet/in.h> /* in_addr structure */
# include <netinet/ip.h> /* iphdr struct */
# include <netinet/udp.h> /* udphdr struct */
# include <netinet/tcp.h> /*tcphdr struct */
socket函数的语法是:int socket(int domain, int type, int protocol); 其中domain参数表示所使用的协议族,type参数表示套接口的类型,protocol参数表示所使用的协议族中某个特定的协议。如果函数调用成功,套接口的描述符(非负整数)就作为函数的返回值,若为-1,就表明有错误发生。
Linux C扩展了套接口函数,增加了SOCK_PACKET类型,使之可以用于监听链路层的数据帧,进而得以分析各层的协议数据单元。使用socket函数捕获链路层数据帧时,domain参数应指定为AF_INET协议族,表示采用Internet协议族;type参数指定为SOCK_PACKET,表示获取链路层数据,不作处理;protocol参数采用htons(0x0003),表示使用的特定协议是截取所有类型的数据帧。此处需用htons函数,用于短整数的字节顺序转换。监听捕获时,socket函数调用形式为:
int fd; //fd是套接口的描述符
fd = socket(AF_INET, SOCK_PACKET, htons(0x0003));
要使建立的套接口能够真正监听到同一网段其他站点在网上传输的数据帧,还必须使用ioctl函数设置网卡工作于“混杂”模式,相应的Linux C程序段如下:
char *dev = “eth0”; //(char *)dev标识设备名,eth0表示系统中的第一块以太网卡
struct ifreq ifr;
strcpy(ifr.ifr_name, dev); //“eth0”写入ifr结构的一个字段中
i = ioctl (fd, SIOCGIFFLAGS, &ifr); // SIOCGIFFLAGS(0x8913)表示要求取出接口标
// 志位
if (i<0)
{
close(fd);
perror(“can’t get flags \n”);
exit(0);
}
ifr.ifr_flags |= IFF_PROMISC; //在标志位中加入“混杂”方式
i = ioctl(fd, SIOCSIFFLAGS, &ifr); // SIOCSIFFLAGS(0x8914)表示要求保存接口标
//志位
if (i<0)
{
perror(“can’t set promiscuous \n”);
exit(0);
}