应用进程在Internet域中创建一个SOCK_RAW类型的插口,就可以利用原始IP层。一般有下列3种用法:
1.应用进程可利用原始插口发送和接收ICMP和IGMP报文。
有些选路守护程序,利用这一特征跟踪通常由内核处理的ICMP重定向报文段。
这个特征还用于实现基于ICMP的协议,如路由通告和路由请求,他们需用到ICMP,不过最好由应用进程,而不是内核完成
相应处理。
多播路由守护程序利用原始IGMP插口,发送和接收IGMP报文。
2.应用进程可利用原始插口构造自己的IP首部。
3.应用进程可利用原始插口读写内核不支持的IP协议的IP数据报。
与所有其他协议不同,inetsw数组有多条记录都可以读写原始IP。inetsw结构中有4个记录的插口类型都等于SOCK_RAW,
但协议类型则各不相同。
IPPROTO_ICMP(协议值1)
IPPROTO_IGMP(协议值2)
IPPROTO_RAW(协议值255)
原始IP通配记录(协议值0)
四项记录间的区别总结如下:
如果应用进程创建了一个原始插口(SOCK_RAW),协议值非0,并且如果协议值等于IPPROTO_ICMP、IPPROTO__GMP
或IPPROTO_RAW,则会使用对应的protosw记录。
如果应用进程创建了一个原始插口(SOCK_RAW),协议值非0,但内核不支持该协议,pffindproto会返回协议值为0的通配
记录,从而允许应用进程处理内核不支持的IP协议,无需修改内核代码。
下图为原始IP的protosw结构:
上图中并没有说明其他协议(ICMP和IGMP),在它们自己的protosw结构中也用到了一些原始IP函数,下图对4个SOCK_RAW
协议各自protosw结构的相关成员变量做了一个比较。
系统初始化时,domaininit函数调用原始IP初始化函数rip_init。这个函数的唯一操作是令PCB首部中的前向和后向指针都
指向自己,实现一个空的双向链表。
因为ip_protox数组中保存的所有关于未知协议记录都指向IPPROTO_RAW,且后者的pr_input函数指向rip_input,所以只要
某个接收IP数据报的协议号内核无法识别,就会调用此函数,ICMP和IGMP都可能调用rip_input,只要满足下列条件:
1.icmp_input调用rip_input处理所有未知的ICMP报文类型和所有非响应的ICMP报文。
2.igmp_input调用rip_input处理所有IGMP分组。
上述两种情况下,调用rip_input的一个原因是允许创建了原始插口的应用进程处理新增的ICMP和IGMP报文,内核可能不
支持它们。
rip_input函数的大概处理流程如下:
1.在所有原始IP PCB中寻找一个或多个匹配的记录。
2.协议比较。如果PCB中的协议字段非0,并且与IP首部的协议字段不匹配,则PCB被忽略。
3.比较本地和远端IP。如果PCB中的本地地址非0,并且与IP首部的目的IP地址不匹配,则PCB被忽略。如果PCB中的远端
地址非0,并且与IP首部的源IP地址不匹配,PCB被忽略。
4.提交接收数据报的复制报文以备处理。
5.无法上交的数据报被释放mbuf。
ICMP、IGMP和原始IP都调用rip_output实现原始IP输出。应用进程调用5个写函数之一:send、sendto、sendmsg、wirte
和writev,系统将输出报文段。如果插口已建立连接就可以任何调用上述5个函数,尽管sendto和sendmsg中不能规定目的
地址,如果插口没有建立连接,则只能调用sendto和sendmsg,且必须规定目的地址。
rip_output函数的大概处理流程如下:
1.内核填充IP首部。
2.调用者填充IP首部:IP_HDRINCR插口选项。
协议的用户请求处理函数能够完成多种操作。与UDP和TCP的用户请求处理函数类似,rip_usrreq是一个很大的switch语句,
每个PRU_xxx请求,都有一个对应的case子句。下面对每个请求进行说明。
1.PRU_ATTACH请求,来自socket系统调用。
每次socket函数被调用时,都会创建新的socket结构,此时还没有指向某个Internet PCB。
确认超级用户,只有超级用户才能创建原始插口,这是为了防止普通用户想网络发送自己的IP数据报。
创建Internet PCB,保留缓存空间。
2.PRU_DISCONNECT请求。下面两种情况会发送PRU_DISCONNECT请求。
1)关闭建立连接的原始插口时,在PRU_DETACH之前先发送PRU_DISCONNECT请求。
2)如果对一个已建立连接的原始插口调用connect,soconnect在发送PRU_CONNECT请求前会先发送PRU_DISCONNECT
请求。
如果处理PRU_DISCONNECT请求的插口没有进入连接状态,则返回错误。否则调用soisdisconnected函数。
3.PRU_ABORT请求。禁止在一个原始接口上发送PRU_ABORT请求。
4.PRU_DETACH请求。close系统调用发送PRU_DISCONNECT请求,如果socket接口用于多播选路,则取消多播选路,
然后调用in_pcbdetach释放Internet PCB,并从原始IP PCB表中删除。
5.PRU_BIND请求。通过该请求,可以把原始IP插口绑定到某个本地IP地址上,下面3个条件必须全真,否则返回错误。
1)至少配置了一个IP接口。
2)地址族等于AF_INET
3)如果绑定的IP地址不等于0.0.0.0,它必须对应于某个本地接口。
6.PRU_CONNECT请求。应用进程在原始IP插口与某个特定远端IP地址间建立连接。
7.PRU_CONNECT2请求。原始IP插口不支持该请求。
8.PRU_SHUTDOWN请求。应用进程结束发送数据后,调用shutdown,生成该请求。该请求调用socantsendmore函数
置位插口标志,禁止所有输出。
9.PRU_SEND请求。5个写函数发送该请求。最终调用rip_output函数向ip_output提交mbuf链。
10.PRU_SOCKADDR和PRU_PEERADDR请求分别由getsockname和getpeername系统调用生成。