ip输入处理函数ip_input()小结
当有帧数据到达网络接口时,网络设备驱动程序会调用m_devget()函数创建一mbuf链表,将收到的帧的数据部分(从ip首部开始) 存放到该mbuf链中.然后调用eth_input(struct ifnet * ifp, struct ether_header *eh, struct mbuf *m)函数通知协议.如果识别到该帧的数据部分为ip分组(类型长度字段为0x0800),eth_input()会将指针m指向的mbuf链表作为ip分组插入到ip输入队列ipintrq中,并且通过软中断通知ip协议;否则,如果识别到该帧的数据部分为arp分组(类型长度字段为0x0806), eth_input()会将指针m指向的mbuf链表作为arp分组插入到arp输入队列arpintrq中,并且通过软中断通知arp协议.代码如下:
void ether_input(ifp, eh, m)
{
struct ifqueue * inq;
........
switch (eh->ether_type)
{
case ETHERTYPE_IP:
schednetisr(NETISR_IP);
inq = &ipintrq;
break;
case ETHERTYPE_ARP:
schednetisr(NETISR_ARP);
inq = &arpintrq;
break;
default:
if (eh->ether_type > ETHERMTU)
{
m_freem(m);
return;
}
}
.......
s = splimp();
if (IF_QFULL(inq))
{
IF_DROP(inq);
m_freem(m);
} else
IF_ENQUEUE(inq, m); //将m指向的链表插入到相应的协议输入队列中
splx(s);
}
ip协议在收到软中断后,会调用ip层软中断处理函数void ipintr(void),net/3直接在ipintr里面处理ip分组, 而ecos则是在ipintr函数里调用了ip_input()函数来处理每一个ip分组:
static void ipintr(void)
{
int s;
struct mbuf *m;
while(1) {
s = splimp();
IF_DEQUEUE(&ipintrq, m);
splx(s);
if (m == 0)
return;
ip_input(m);
}
}
ip_input的原型很简单: void ip_input(struct mbuf * m),下面详细介绍一下ip_input函数所完成的工作:
(1)验证工作:首先验证主机系统上面的网络接口有没有配置了ip地址(验证in_ifaddr是否为空);其次,验证m指向的mbuf链的数据部分(即ip分组)有误错误,如:ip版本号是否为4,检验和是否正确,首部长度是否正确,等等.
(2)如果hlen标识分组首部长度>40,ip_input()函数就会去调用ip_dooption(struct mbuf *m)对首部选项进行处理.在ip_dooption()函数中,如果发现选项数据有误,则直接丢弃分组,或者发现由于选项中采用了源路由选项则转发分组,这两种情况下函数返回1,否则返回0.ip_input()检查ip_dooption()的返回值,如果发现返回值为1,则ip_input认为分组已经在ip_dooption函数里被处理完了,ip_input直接返回,继续下一个分组的处理;否则,当发现返回值为0时,继续对分组进行处理. 下面我们描述一下函数ip_dooption的处理过程:
a.对源路由选项LSRR,SSRR选项的处理.需要注意的是,如果分组启动了源路由选项,那么分组的目的ip地址将会被选项中的下一跳ip地址替换.并且在确定分组已经到达目的主机后,所经过的路由选项会被备份在静态全局变量ip_srcrt中,所经过的路由器的跳数会被记录在ip_nhops中,以备传输层协议构造逆转路由.如果发现分组还没有到达最终的目的地址,那么ip_dooption在所有选项处理完毕后将会调用ip_forward(m,1)函数转发该分组.
b.对记录路由选项的处理
c.对时间戳选项的处理
(3)扫描本系统中in_ifaddr指向的ip地址链,判断本系统是否为分组所要到达的目的地址.首先检查所有接口的目的地址ia_addr,看是否有能与分组的目的地址匹配(单播); 其次检查分组到达的接口的广播地址,看是否与分组的目的地址匹配(广播). 如果地址链扫描完成后发现没有地址与分组的目的地址匹配,就去验证多播.如果还不匹配,最后检查分组是否为受限的特殊地址,如全1,或全0.
如果通过以上检查后,系统有地址与分组的目的地址匹配,则确定分组已经到达了最终目的地,否则分组需要被转发.当然,只有当系统被配置成路由器(全局变量ip_forward == 1)时才转发分组然后返回,否则直接丢弃分组后返回.
(4)如果分组已经到达最终目的地,则ip_input()此时会去检查分组的ip首部,以判断该分组是否为ip数据报的某个分片.如果确定该分组是某个ip数据报的某个分片,则ip_input将调用ip_reass()函数将到达的分片与以前的分片进行重装.这就涉及到一个重要的数据结构---重装表.
(5)如果到达的分片与以前的分片重装在一起,能组成一个完整的数据报,或者到达的分组本身就是一个完整的数据报,则ip层将通过ip首部所标识的协议类型调用相应的传输层函数.代码如下:
(*inetsw[ip_protox[ip->ip_p]].pr_input)(m, hlen);
(6)函数返回.