《TCP/IP详解卷2:实现》笔记--原始IP

应用进程在Internet域中创建一个SOCK_RAW类型的插口,就可以利用原始IP层。一般有下列3种用法:

1.应用进程可利用原始插口发送和接收ICMP和IGMP报文。

有些选路守护程序,利用这一特征跟踪通常由内核处理的ICMP重定向报文段。

这个特征还用于实现基于ICMP的协议,如路由通告和路由请求,他们需用到ICMP,不过最好由应用进程,而不是内核完成

相应处理。

多播路由守护程序利用原始IGMP插口,发送和接收IGMP报文。

2.应用进程可利用原始插口构造自己的IP首部。

3.应用进程可利用原始插口读写内核不支持的IP协议的IP数据报。


1.原始IP的protosw结构

与所有其他协议不同,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结构:

《TCP/IP详解卷2:实现》笔记--原始IP_第1张图片

上图中并没有说明其他协议(ICMP和IGMP),在它们自己的protosw结构中也用到了一些原始IP函数,下图对4个SOCK_RAW

协议各自protosw结构的相关成员变量做了一个比较。

《TCP/IP详解卷2:实现》笔记--原始IP_第2张图片


2.rip_init函数

系统初始化时,domaininit函数调用原始IP初始化函数rip_init。这个函数的唯一操作是令PCB首部中的前向和后向指针都

指向自己,实现一个空的双向链表。


3.rip_input函数

因为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。


4.rip_output函数

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插口选项。


5.rip_usrreq函数

协议的用户请求处理函数能够完成多种操作。与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系统调用生成。

你可能感兴趣的:(《TCP/IP详解卷2:实现》笔记--原始IP)