本章描述L3和L4协议间的接口。L3协议只考虑IP。L4协议如TCP,UDP,ICMP,这几个协议是静态编译至内核中的。还有其他L4协议编译成模块。
L4协议的注册:
L4协议由net_protocol数据结构定义,其中三个字段如下:
int (*handler)(struct sk_buff *skb):由协议来注册的处理函数,用来处理送进来的封包。
void (*err_handler)(struct sk_buff *skb,u32 info):由ICMP协议处理函数所用的函数,用于通知L4协议有关收到ICMP UNREACHABLE消息的事情。
int no_policy:此字段在网络协议栈中的某些关键点被查询,用于使协议免于IPsec策略检查。
协议会以inet_add_protocol函数自行注册,当协议实现成模块时,以inet_del_protocol自行除名。所有和内核注册的L4协议的你、inet_protocol结构会插入到名为inet_protos的表内,如下图,是一个数组,数组索引就是协议编号:
对于每个L4协议而言,内核只能有一个处理函数,但是用户空间可用有多个处理函数。
L3到L4的传递:ip_local_deliver_finish:
ip_local_deliver_finish的主要工作是根据IP封包报头的协议字段找出正确的协议处理函数,然后把封包交给协议处理函数。同时,ip_local_deliver_finish还要处理raw IP以及IPsec(如果有配置的话)。
如果没有找到相关的协议处理函数,且没有raw套接字对该封包感兴趣,则会丢弃封包并发送icmp报文给来源地。也就是说,无论是否注册有相关的协议处理函数,ip_local_deliver_finish总是会检查是否有应用程序设立了一个raw 套接字要处理该协议,如果有,拷贝一个封包的副本,将其交给应用程序。
如下图所示,无论封包是由已注册L4协议或Raw IP处理,其他协议可能还是得被启用,例如IPsec 集组里的协议。
Raw套接字和Raw IP:
并非所有的L4协议都是在内核空间中实现的。例如,tcp和udp完全在内核实现,ospf协议在用户空间实现,icmp部分在内核,部分在用户空间实现。下图分别显示了这三种情况:
上图a:网页浏览器和web服务器通信,浏览器和服务器只传递TCP有效载荷给内核,而内核会处理TCP和IP报头。
上图b:两台执行OSPF常驻程序的路由器彼此对话,OSPF协议是在用户空间实现的,而且会把L4报头(ospf的头,不是tcp和udp)和有效载荷传给内核(这是使用raw 套接字的实例之一,参见第十三章了解raw套接字和协议栈如何搭配)。事实上,多数ospf实现也会传递ip头。就像图d一样。
上图c:一台主机ping另一台主机,请求组件是在用户空间实现的,ping程序产生icmp封包,然后传给L3,内核不会去碰icmp头部。然而,回复组件在内核空间实现,进行接收的主机会在内核处理ICMP_ECHO_REQUEST然后进行回复。
上图d:一台执行traceroute的主机执行网络除错工作。L3和L4头部由应用程序处理。应用程序将它的L4协议指定为RAW IP,然后对套接字设定IP_HDRINCL选项(包含报头)。(参见二十一章有关IP如何处理Raw IP)
把Raw输入数据段传递给进行接收的应用程序:
当应用程序打开一个套接字时,必须指定家族,套接字类型以及协议标识符。套接字和协议类型都可以是raw。socket系统调用的原型如下:
socket(int family,int type,int protocol)
其中,family是地址家族(TCP/IP所有值为AF_INET),type是套接字类型,protocol是L4协议标识符。当你打开一个SOCK_RAW类型的套接字(所选协议号码是整数P),你的应用程序会接收到所有满足下列规则的入口封包:
IP报头中的L4协议标识符是P
当套接字绑定至目的IP地址时,封包中源IP地址必须吻合
当套接字绑定至本地IP地址时,封包中目的IP地址必须吻合
不是只有一个套接字可以满足这些规则,所以一个Raw IP封包可以传递给多个应用程序。
当套i额类型时SOCK_RAW而协议为RAW IP(255)时,应用程序需要处理L4和L3报头。这一点和图24-6b不同,因为协议是raw IP而不是ospf。图24-6d所显示的是RAW IP的情况。这类应该程序会对套接字设定IP_HDRINCL来告知内核应该程序会处理ip头。当协议是RAW IP时,IP_HDRINCL选项是默认打开的。
用于存储raw处理函数的表(raw_v4_htable)和用于存储协议处理函数的表(inet_protos)具有相同的尺寸。raw封包会传给raw_v4_input,该函数会拷贝封包然后交给主要处理函数raw_rcv。
IPsec:
ip_local_deliver_finish把封包传给正确的协议处理函数之前,必须先以IPsec检查该封包是否准许处理。