加入收藏 | 设为首页 | 会员中心 | 我要投稿 PHP编程网 - 黄冈站长网 (http://www.0713zz.com/)- 数据应用、建站、人体识别、智能机器人、语音技术!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

Linux内核分析 - 网络[八]:IP协议

发布时间:2016-10-16 06:06:01 所属栏目:Linux 来源:站长网
导读:副标题#e# 内核版本:2.6.34 这篇是关于IP层协议接收报文时的处理,重点说明了路由表的查找,以及IP分片重组。 ip_rcv 进入IP层报文接收函数 丢弃掉不是发往本机的报文,skb-pkt_type在网卡接收报文处理以太网头时会根据dst mac设置, 协议栈的书会讲不是发
副标题[/!--empirenews.page--]

内核版本:2.6.34

这篇是关于IP层协议接收报文时的处理,重点说明了路由表的查找,以及IP分片重组。

ip_rcv 进入IP层报文接收函数

丢弃掉不是发往本机的报文,skb->pkt_type在网卡接收报文处理以太网头时会根据dst mac设置, 协议栈的书会讲不是发往本机的广播报文会在二层被丢弃,实际上丢弃是发生在进入上层之初。

if (skb-

>pkt_type == PACKET_OTHERHOST)     
 goto drop;

在取IP报头时要注意可能带有选项,因此报文长度应当以iph->ihl * 4为准。这里就需要尝试两次, 第一次尝试sizeof(struct iphdr),只是为了确保skb还可以容纳标准的报头(即20字节),然后可以ip_hdr(skb)得到报头;第二 次尝试ihl * 4,这才是报文的真正长度,然后重新调用ip_hdr(skb)来得到报头。两次尝试pull后要重新调用ip_hdr()的原因是 pskb_may_pull()可能会调用__pskb_pull_tail()来改现现有的skb结构。

if (!pskb_may_pull(skb, sizeof(struct 

iphdr)))     
 goto inhdr_error;     
iph = ip_hdr(skb);     
……     
if (!pskb_may_pull(skb, iph->ihl*4))     
 goto inhdr_error;     
iph = ip_hdr(skb);

获取到IP报头后经过一些检查,获取到报文的总长度len = iph->tot_len,此时调用 pskb_trim_rcsum()去除多余的字节,即大于len的。

if (pskb_trim_rcsum(skb, len)) {     
 IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);     
 goto drop;     
}

然后调用ip_rcv_finish()继续IP层的处理,ip_rcv()可以看成是查找路由前的IP层处理,接下来的ip_rcv_finish() 会查找路由表,两者间调用插入的netfilter(关于NetFilter,参考前篇 http://blog.csdn.net/qy532846454/article/details/6605592)。

return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);

进入ip_rcv_finish函数

ip_rcv_finish()主要工作是完成路由表的查询,决定报 文经过IP层处理后,是继续向上传递,还是进行转发,还是丢弃。

刚开始没有进行路由表查询,所以还没有相应的路由表项 :skb_dst(skb) == NULL。则在路由表中查找ip_route_input(),关于内核的路由表,可以参见前篇 http://blog.csdn.net/qy532846454/article/details/6726171:

if (skb_dst(skb) == NULL) {     
 int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,     
  
     skb->dev);     
 if (unlikely(err)) {     
  if (err == -EHOSTUNREACH)     
   IP_INC_STATS_BH(dev_net(skb->dev),     
     IPSTATS_MIB_INADDRERRORS);     
  else if (err == -ENETUNREACH)     
   IP_INC_STATS_BH(dev_net(skb->dev),     
     IPSTATS_MIB_INNOROUTES);     
  goto drop;     
 }     
}

通过路由表查找,我们知道:

- 如果是丢弃的报文,则直接drop;

- 如果是不能接收或转发的报文,则 input = ip_error

- 如果是发往本机报文,则input = ip_local_deliver;

- 如果是广播报文,则input = ip_local_deliver;

- 如果是组播报文,则input = ip_local_deliver;

- 如果是转发的报文,则input = ip_forward ;

在ip_rcv_finish()最后,会调用查找到的路由项_skb_dst->input()继续向上传递:

return dst_input (skb);

具体看下各种情况下的报文传递,如果是丢弃的报文,则报文被释放,并从IP协议层返回,完成此次报文传递流 程。

drop:     
 kfree_skb(skb);     
 return NET_RX_DROP;

如果是不能处理的报文,则执行ip_error,根据error类型发送相应的ICMP错误报文。

static int ip_error(struct sk_buff *skb)     
{     
 struct rtable *rt = skb_rtable(skb);     
 unsigned long now;     
 int code;     
         
 switch (rt->u.dst.error) {     
  case EINVAL:     
  default:     
   goto out;     
  case EHOSTUNREACH:     
   code = ICMP_HOST_UNREACH;     
   break;     
  case ENETUNREACH:     
   code = ICMP_NET_UNREACH;     
   IP_INC_STATS_BH(dev_net(rt->u.dst.dev),     
     IPSTATS_MIB_INNOROUTES);     
   break;     
  case EACCES:     
   code = ICMP_PKT_FILTERED;     
   break;     
 }     
         
 now = jiffies;     
 rt->u.dst.rate_tokens += now - rt->u.dst.rate_last;     
 if (rt->u.dst.rate_tokens > ip_rt_error_burst)     
  rt->u.dst.rate_tokens = ip_rt_error_burst;     
 rt->u.dst.rate_last = now;     
 if (rt->u.dst.rate_tokens >= ip_rt_error_cost) {     
  rt->u.dst.rate_tokens -= ip_rt_error_cost;     
  icmp_send(skb, ICMP_DEST_UNREACH, code, 0);     
 }     
         
out: kfree_skb(skb);     
 return 0;     
}

(编辑:PHP编程网 - 黄冈站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读