目前大多数操作系统都为应用程序提供访问数据链路层的强大功能。这种功能可以提供如下能力:
能够监视由数据链路层接收的分组,使得诸如(tcpdump)等程序能够在普通计算机系统上运行,而无需专门的硬件设备来监视分组。如果结合使用网络接口 进入混杂模式(promiscuous mode)的能力,那么应用程序设置能够监视本地电缆上流通的所有分组,而不仅仅以程序运行所在主机为目的地的分组。
能够作为普遍应用进程而不是内核的一部分运行某些程序。比如RARP服务器的大多数UNIX版本是普通的应用进程,它们从数据链路读入RARP请求,又往数据链路写出RARP应答。(RARP请求和应答都不是IP数据报)
UNIX上访问数据链路层的3个常用方法是BSD的分组过滤器BPF,SVR4的数据链路提供者接口DLPI和LINUX的SOCK_PACKET接口。函数库libpcap适用于所有这三个接口,使用它可以编写独立于操作系统提供的实际数据链路访问接口的程序。
BPF:
在支持BPF的系统上,每个数据链路驱动程序就在发送一个分组之前或在接收一个分组之后调用BPF。尽管往数据链路中安置一个用于捕获所有分组的“龙头” 并不困难,BFP的强大威力在于它的过滤能力。打开一个BPF设备的每个应用进程可以装载各自的过滤器,这个过滤器然后由BPF应用于每个分组。更复杂的 过滤器可以检查分组头部某些字段是否为特定值。
BPF使用以下3个技术来降低开销:
1 BPF过滤在内核中进行,以此把从BPF到应用进程的数据拷贝量减到最小。这种从内核空间到用户空间的拷贝开销高昂。要是每个分组都如此拷贝,BPF可能就跟不上快速的数据链路。
2 由BPF传递到应用进程的只是每个分组的一段定长部分。这个长度成为捕获长度(capture length),也称为快照长度(snapshot length简写为snaplen)。大多数应用进程只需要分组头部而不需要分组数据。这个技术同样减少了由BPF拷贝到应用进程的数据量。
3 BPF为每个应用进程分别缓冲数据,只有当缓冲区已满或读超时(read timeout)期满时该缓冲区中的数据才拷贝到应用进程。该超时值可由应用进程指定。
============================================================================================================================================================
LINUX : SOCK_PACKET和PF_PACKET
LINUX先后有两个从数据 链路层接收分组的方法。较旧的方法是创建SOCK_PACKET的套接口,这个方法的可用面较宽,不过缺乏灵活性。较新的方法是创建协议族为 PF_PACKET的套接口,这个方法引入了更多的过滤和性能特性。我们必须有足够的权限才能创建这两种套接口(类似原始套接口的创建)。而且调用 socket的第三个参数必须是指定以太网帧类型的某个非0值。创建PF_PACKET套接口时,调用socket的第二个参数既可以是 SOCK_DGRAM,表示扣除链路层头部的“煮熟”(cooked)分组,也可以是SOCK_RAW表示“未煮”(raw)的完整链路层分组(以太网 帧)。SOCK_PACKET套接口只返回以太网帧。举例来说,从数据链路层接收所有帧应如下创建套接口:
fd = socket( AF_PACKET, SOCK_RAW, htons(ETH_P_ALL) ); //较新方法 然后就可以对起fd进行读写来查找帧的信息
或
fd = socket( AF_INET, SOCK_PACKET, htons(ETH_P_ALL) ); //较旧方法
由数据链路层接收的任何协议以太网帧将返回到这些套接口。如果只想捕获IPv4帧,那就如下创建套接口:
fd = socket( PF_PACKET, SOCK_RAW, htons(ETH_P_IP) ); //较新方法
或
fd = socket( AF_INET, SOCK_PACKET, htons(ETH_P_IP) ); //较旧方法
用作socket调用的第三个参数的常值还有ETH_P_ARP, ETH_P_IPV6等等。
指定这个协议的参数为某个ETH_P_xxx常值是在告知数据链路层应该把由它接收的帧中哪些类型的帧传递给所创建的套接口。如果数据链路层支持混杂模式,那么需要的话还必须把设备投入混杂模式。
对于PF_PACKET套接口,把一个网络接口投入混杂模式通过设置
PACKET_ADD_MEMBERSHIP套接口选项完成,在作为setsockopt第四个参数传递的packet_mreq结构中需指定网络接口和值为PACKET_MR_PROMISC的行为。
对于SOCK_PACKET套接口,投入混杂模式通过使用SIOCGIFFLAGS ioctl获取标志,逻辑或上IFF_PROMISC标志位,再使用SIOCSIFFLAGS ioctl设置标志完成。
linux上的数据链路层访问方法相比BPF和DLPI存在如下差别:
linux方法不提供内核缓冲,而且只有较新的方法才能提供内核过滤(通过设置SO_ATTACH_FILTER套接口选项安装)。尽管这些套接口有普通 的套接口接收缓冲区,但是多个帧不能缓冲在一起由单个读入操作由单个读入操作一次性的传递给应用进程。这么依赖势必增长从内核到应用进程拷贝大量数据所设 计的开销。
linux较旧的方法不提供针对设备的过滤。(较新的方法可以通过调用bind与某个设备关联)。如果调用socket时指定了ETH_P_IP,那么来 自任何设备(例如以太网,ppp链路,SLIP链路和回馈设备)的所有IPv4分组都被传递到所创建的套接口。recvfrom将返回一个通用套接口地址 结构,其中的sa_data成员含有设备名字(例如eth0)。应用进程然后必须自行丢弃来自任何非所关注设备的数据。这里的问题仍然是可能会有太多的数 据返回给应用进程,从而妨碍对于告诉网络的监视。
============================================================================================================================================================
libpcap:分组捕获函数库
libpcap是一个与实现无关的访问操作系统所提供的分组捕获机制的分组捕获函数库。该函数库可以从http://www.tcpdump.org获取。
============================================================================================================================================================
libnet:分组构造与输出函数库
libnet函数提供构造任意协议的分组并输出到网络中的接口。它以与实现无关的方式提供原始套接口访问方式和数据链路层访问方式。
libnet隐藏了构造IP,UDP和TCP头部的许多细节,并提供了简单且便于移植的数据链路层和原始套接口的写出访问接口。
libnet函数库可以从http://www.packetfactory.net/libnet获取。
linux中的一些协议头定义:
在/usr/include/netinet/目录下有很多.
/usr/include/linux/if_ether.h
#define ETH_ALEN 6 //Octets in one ethernet addr
#define ETH_HLEN 14 //total octets in header
#define ETH_ZLEN 60 // Min. octets in frame sans FCS
#define ETH_DATA_LEN 1500 // Max. octets in payload
#define ETH_DATA_LEN 1514 // Max. octets in fram sans FCS
struct ethhdr
{
unsigned char h_dest[ETH_ALEN];
unsigned char h_source[ETH_ALEN];
unsigned short h_proto;
};
IP:
struct iphdr:
/usr/include/netinet/ip.h
struct tcphdr :
/usr/include/netinet/tcp.h