http://book.csdn.net/bookfiles/1213/index.html 转载地址
11.4 数据链路层访问
在Linux下数据链路层的访问通常是通过编写内核驱动程序来实现的,在应用层使用SOCK_PACKET类型的协议族可以实现部分功能。
11.4.1 SOCK_PACKET类型
建立套接字的时候选择SOCK_PACKET类型,内核将不对网络数据进行处理而直接交给用户,数据直接从网卡的协议栈交给用户。建立一个SOCK_PACKET类型的套接字使用如下方式:
socket (AF_INET, SOCK_PACKET,htons(0x0003));
其中AF_INET=2表示因特网协议族,SOCK_PACKET=10表示截取数据帧的层次在物理层,网络协议栈对数据不做处理。值0x0003表示截取的数据帧的类型为不确定,处理所有的包。
使用SOCK_PACKET进行程序设计的时候,需要注意的主要方面包括协议族选择、获取原始包、定位IP包、定位TCP包、定位UDP包、定位应用层数据几个部分,下面几节中将进行详细的介绍。
11.4.2 设置套接口以捕获链路帧的编程方法
在Linux下编写网络监听程序,比较简单的方法是在超级用户模式下,利用类型为SOCK_PACKET的套接口(用socket()函数创建)来捕获链路帧数据。Linux程序中需引用如下头文件:
#include <sys/socket.h>
#include <sys/ioctl.h> /*ioctl 命令*/
#include <Linux/if_ether.h> /*ethhdr 结构*/
#include <net/if.h> /*ifreq 结构*/
#include <netinet/in.h> /*in_addr结构*/
#include <Linux/ip.h> /*iphdr 结构*/
#include <Linux/udp.h> /*udphdr 结构*/
#include <Linux/tcp.h> /*tcphdr 结构*/
建立SOCK_PACKET类型套接字的方法在11.4.1中已经进行了介绍。如果要监视所有类型的包,则需要采用如下代码:
int fd; /*fd是套接口的描述符*/
fd = socket(AF_INET, SOCK_PACKET, htons(0x0003));
侦听其他主机网络的数据在局域网诊断中经常使用。如果要监听其他网卡的数据,需要将本地的网卡设置为“混杂”模式;当然还需要一个都连接于同一HUB的局域网或者具有“镜像”功能的交换机才可以,否则,只能接收到其他主机的广播包。
char*ethname = "eth0"; /*对网卡eth0进行混杂设置*/
struct ifreq ifr; /*网络接口结构*/
strcpy(ifr.ifr_name, ethname); /*“eth0”写入ifr结构的一个字段中*/
i = ioctl (fd, SIOCGIFFLAGS, &ifr); /*获得eth0的标志位值*/
if (i<0) /*判断是否取出出错*/
{
close(fd);
perror("can’t get flags /n");
return -1;
}
ifr.ifr_flags|=IFF_PROMISC; /*保留原来设置的情况下,在标志位中加入“混杂”方式*/
i = ioctl(fd, SIOCSIFFLAGS, &ifr); /*将标志位设置写入*/
if (i<0) /*判断是否写入出错*/
{
perror("promiscuous set error/n");
return -2;
}
上面的代码使用了ioctl()的SIOCGIFFLAGS和SIOCSIFFLAGS命令,用来取出和写入网络接口的标志设置。注意,在修改网络接口标志的时候,务必要先将之前的标志取出,与想设置的位进行“位或”计算后再写入;不要直接将设置的位值写入,因为直接写入会覆盖之前的设置,造成网络接口混乱。遵循如下的步骤:
(1)取出标志位。
(2)目标标志位=取出的标志位|设置的标志位。
(3)写入目标标志位。
11.4.3 从套接口读取链路帧的编程方法
以太网的数据结构如图11.10所示,总长度最大为1518字节,最小为64字节,其中目标地址的MAC为6字节,源地址MAC为6字节,协议类型为2字节,含有46~1500字节的数据,尾部为4个字节的CRC校验和。以太网的CRC校验和一般由硬件自动设置或者剥离,应用层不用考虑。
图11-10 以太网帧示意图
在头文件<Linux/if_ether.h>中定义了如下的常量:
#define ETH_ALEN 6 /*以太网地址,即MAC地址,6字节*/
#define ETH_HLEN 14 /*以太网头部的总长度*/
#define ETH_ZLEN 60 /*不含CRC校验的数据最小长度*/
#define ETH_DATA_LEN 1500 /*帧内数据的最大长度*/
#define ETH_FRAME_LEN 1514 /*不含CRC校验和的最大以太网数据长度*/
以太网头部结构定义为如下的形式:
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; /*目的以太网地址*/
unsigned char h_source[ETH_ALEN]; /*源以太网地址*/
__be16 h_proto; /*包类型*/
};
套接字文件描述符建立后,就可以从此描述符中读取数据,数据的格式为上述的以太网数据,即以太网帧。套接口建立以后,就可以从中循环读取捕获的链路层以太帧。要建立一个以太网缓冲区可以建立一个大小为ETH_FRAME_LEN的缓冲区,并将以太网的头部指向此缓冲区,例如:
char ef[ETH_FRAME_LEN]; /*以太帧缓冲区*/
struct ethhdr*p_ethhdr; /*以太网头部指针*/
int n;
p_ethhdr = (struct ethhdr*)ef; /*使p_ethhdr指向以太网帧的帧头*/
/*读取以太网数据,n为返回的实际捕获的以太帧的帧长*/
n = read(fd, ef, ETH_FRAME_LEN);
接收数据以后,缓冲区ef与以太网头部的对应关系如图11.11所示。
图11.11 以太网帧缓冲区与以太网头部结构ethhdr的映射关系
因此要获得以太网帧的目的MAC地址、源MAC地址和协议的类型,可以通过p_ethhdr->h_dest、p_ethhdr->h_source和p_ethhdr->h_proto获得。下面的代码将以太网的信息打印出来:
/*打印以太网帧中的MAC地址和协议类型*/
/*目的MAC地址*/
printf("dest MAC: ");
for(i=0; i< ETH_ALEN-1; i++){
printf("%02x-", p_ethhdr->h_dest[i]);