linux网络协议栈(四)链路层 (4)原始套接字

4.5、原始套接字:

这里的原始套接字是指链路层的原始套接字,即socket类型为AF_PACKET的套接字,它将为应用程序获取它所需以太网类型的以太网报文,应用程序通过如下系统调用在内核中创建相应的socket文件并注册相应以太网类型(下面的protocol参数)的处理需求:

int fd = socket(AF_PACKET,  SOCK_RAW,  protocol);

这样内核中就注册了一个以太网类型为protocol的处理需求,并且创建了一个socket用于收发这样的报文,当链路层收到这样的以太网类型的报文时,随即找到该处理需求,就会把该报文交给原始套接字处理函数packet_rcv,再由它转交给创建的socket的接收缓存,继而应用程序会收到该报文;

原始套接字根据报文的以太网类型获取报文,所以也就是说应用程序通过原始套接字可以获取到所有链路层收到的报文,可见其功能十分强大,用于抓包的应用程序就是使用此方式抓取所有的报文;

这里不讨论原始套接字关于socket部分的内容(这部分内容详见3.2),这里重点描述原始套接字这种在链路层根据以太网类型获取报文的接口及其原理,回到链路层报文处理入口netif_receive_skb函数,下面是链路层报文处理的依次处理过程:

首先看看应用程序有没有抓所有报文的需求,即在socket系统调用中protocol参数为ptype_all的情况,这就获取任何收到的报文(socket系统调用中的protocol参数为ETH_P_ALL),一般情况下很少见,至于是如果配置它的后续再描述,如下图:


然后依次做eoip隧道处理和网桥处理,注意下面的网桥处理是有条件进入,这是项目实际应用中的修改,原始的linux源码是无条件进入的,如下图:

linux网络协议栈(四)链路层 (4)原始套接字_第1张图片

最后是按以太网类型处理,通过查找内核已注册的处理需求和该报文的以太网类型相匹配,匹配成功的报文进入该处理需求的处理函数,首先获取该报文的以太网类型值(对应下面代码中的type变量),和hash表ptype_base的每一个条目记录的以太网类型值相比较,如果相等并且该条目的输入接口符合(对于不同的处理需求,输入接口需求也是不同的,如原始套接字是在创建时应用程序必须指定其收发接口,而其他套接字不会指定,所以这里用“符合”一词),那么就把该报文交给该处理需求注册时挂接的处理函数,如对于IPV4是ip_rcv,对于arp是arp_rcv,对于原始套接字就是packet_rcv,如下图:


现在可以知道,原始套接字就是根据报文的以太网类型,从所规定的接口中收发,对报文的匹配方式就是以该报文的以太网类型值和hash表ptype_base的每一个条目记录的以太网类型值相比较;那么,现在就看看hash表ptype_base里的条目是怎么加入的,同样是在dev.c文件中,找到函数dev_add_pack:


看看结构体packet_type就很清楚了,它描述了注册的该处理需求的以太网类型、收发接口、处理函数,如下图:

linux网络协议栈(四)链路层 (4)原始套接字_第2张图片

注册不同的处理需求,就是把不同的packet_type结构体变量注册进内核,比如应用程序需要从接口eth3获取0x8809的OAM报文,那么type字段为0x8809、dev字段为eth3所对应的接口、func字段为packet_rcv函数,通过创建不同的原始套接字,应用程序可以获取各种不同以太网类型的报文,事实上,vlan模块同样是在内核中创建了一个这样的处理需求,如下两图是内核vlan模块初始化时,将以太网类型为0x8100的报文的处理需求注册进内核,并设置处理函数是vlan_skb_recv:



在内核中到底都注册了多少个这样的处理需求,可以通过/proc/net/ptype文件查看。

下面再简单的描述下原始套接字的收发,原始套接字就是指定在哪个接口收发哪个以太网类型的报文,接收报文由函数packet_rcv实现,发送报文由packet_snd实现,接收方向上,创建了套接字后相应的报文就会上到packet_rcv,该函数会把报文再放到应用程序的socket缓存队列,等待应用程序获取;发送方向上,packet_snd直接调用dev_queue_xmit从收发接口中发送报文,可见原始套接字的报文收发跨过了整个L3/4处理,收发都是在链路层和socket层直接交互。

你可能感兴趣的:(链路层,SOCK_RAW,net_receive_skb,linux原始套接字,以太网类型)