当前位置:   article > 正文

linux内核协议栈 IPv4之接收数据包流程_dst_input

dst_input

目录

1 ip报文接收 ip_rcv()

2 路由查询 ip_rcv_finish()

2.1 报文分发 dst_input 

3 数据包发送至本机 ip_local_delivery()

3.1 通过 LOCAL_IN 校验 ip_local_deliver_finish()

4 数据包转发出去 ip_forward()

4.1 通过 FORWARD 校验 ip_forward_finish()


1 ip报文接收 ip_rcv()

当设备接口层处理完输入数据包后,如果发现该报文应该由IP协议进一步处理,那么将会调用ip_rcv()函数。该接口完成对网络层数据包的校验和解析,之后通过netfilter模块和路由模块将处理后的数据包转发或转给本机4层继续解析。核心流程如下:

  1. 设备接口层处理完数据包后,调用ip_rcv()将数据包交由IP层继续处理;
  2. IP层首先做些简单的校验后,就尝试过 netfilter 的 PREROUTING 点;
  3. PREROUTING 点通过后,进行路由查询,决定是将数据包递交给本机,还是转发;
  4. 对于递交给本机的数据包,过 LOCAL_IN 点,然后根据 IP 首部的协议字段,查找高层协议处理函数,然后调用该函数,将数据包交给高层协议继续处理;
  5. 对于需要转发的数据包,根据转发的需要,修改IP首部内容(TTL),然后过FORWARD点,最后走和本机发送数据包一样的流程将数据包转发出去。

主要涉及如下文件:

源代码路径说明
net/ipv4/ip_input.cIP协议输入报文处理过程
net/ipv4/ip_forward.cIP协议转发报文处理过程

我们知道,设备接口层在最后会在 netif_receive_skb() 函数中,根据 skb- >protocol 字段查表,将skb递交给更高层的协议处理,对于 IPv4 来讲,其注册的接收函数就是 ip_rcv():这里主要完成对 ip 报文的校验工作,这里强调一下:混杂模式收上来的包会在ip层丢弃(skb->pkt_type == PACKET_OTHERHOST),skb->pkt_type 的由驱动通过调用 eth_type_trans()函数进行设置。

  1. @skb: 数据包
  2. @dev:数据包的当前输入网络设备(层二可能会使用一些聚合技术)
  3. @pt:数据包的类型
  4. @orig_dev: 接收数据包的原始网络设备
  5. int ip_rcv(struct sk_buff *skb, struct net_device *dev,
  6. struct packet_type *pt, struct net_device *orig_dev)
  7. {
  8. struct iphdr *iph;
  9. u32 len;
  10. if (dev->nd_net != &init_net)
  11. goto drop;
  12. /* When the interface is in promisc. mode, drop all the crap
  13. * that it receives, do not try to analyse it.
  14. */
  15. // 在混杂模式下,发往其它主机的一些数据包有可能会到达这里,IPv4并不关注这种包,忽略它们
  16. //skb->pkt_type 在 eth_type_trans()函数中设置。
  17. if (skb->pkt_type == PACKET_OTHERHOST)
  18. goto drop;
  19. IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);
  20. // 因为后面可能会修改SKB描述符的内容,所以如果该SKB描述符是被共享的(其users成员不为1),
  21. // 那么复制一个新的,然后返回,后面的接收处理过程都是用该新的SKB
  22. if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
  23. IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
  24. goto out;
  25. }
  26. // 确保skb线性区域中至少有IP首部长度个字节的数据
  27. if (!pskb_may_pull(skb, sizeof(struct iphdr)))
  28. goto inhdr_error;
  29. // pskb_may_pull()可能会调整内存,所以iph需要重新指向
  30. iph = ip_hdr(skb);
  31. /*
  32. * RFC1122: 3.1.2.2 MUST silently discard any IP frame that fails the checksum.
  33. *
  34. * Is the datagram acceptable?
  35. *
  36. * 1. Length at least the size of an ip header
  37. * 2. Version of 4
  38. * 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
  39. * 4. Doesn't have a bogus length
  40. */
  41. // 1&2:检查首部长度和IP协议版本号
  42. if (iph->ihl < 5 || iph->version != 4)
  43. goto inhdr_error;
  44. // 这里之所以又做一遍,是因为IP首部可能还有选项部分,iph->ihl*4是IP报文的真实首部长度
  45. if (!pskb_may_pull(skb, iph->ihl*4))
  46. goto inhdr_error;
  47. // 同上,SKB内部指针可能已经发生变化,所以iph需要重新指向
  48. iph = ip_hdr(skb);
  49. // 检查IP首部的校验和,确保接收数据传输无误
  50. if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
  51. goto inhdr_error;
  52. // 校验IP数据包的总长度
  53. len = ntohs(iph->tot_len);
  54. if (skb->len < len) {
  55. IP_INC_STATS_BH(IPSTATS_MIB_INTRUNCATEDPKTS);
  56. goto drop;
  57. } else if (len < (iph->ihl*4))
  58. goto inhdr_error;
  59. /* Our transport medium may have padded the buffer out. Now we know it
  60. * is IP we can trim to the true length of the frame.
  61. * Note this now means skb->len holds ntohs(iph->tot_len).
  62. */
  63. // 如注释所述,层二有可能会在IP数据包上打padding,所这里知道了IP数据包的总长度,
  64. // 需要对SKB的长度字段进行调整并重新计算校验和
  65. if (pskb_trim_rcsum(skb, len)) {
  66. IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
  67. goto drop;
  68. }
  69. // 将IP控制块内容全部清零,后面IP层处理过程中会使用该控制块数据结构
  70. memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
  71. // 数据包进入PREROUTING链,如果通过该链,则将数据包传递给ip_rcv_finish()继续处理
  72. return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
  73. inhdr_error:
  74. IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
  75. drop:
  76. kfree_skb(skb);
  77. out:
  78. return NET_RX_DROP;
  79. }

从上面可以看出,ip_rcv()仅仅是对IP数据包做一些基本的校验(长度检查、检验和等),并没有做任何选项、分段以及路由相关的任何处理。函数最后通过 PREROUTING 点,这里假设防火墙放行了该数据包,调用 ip_rcv_finish() 继续处理。

2 路由查询 ip_rcv_finish()

数据的IP报文安全通过 netfilter 的 PREROUTING 点后,就会调用ip_rcv_finish()函数,该接口主要是查找路由确定报文时分发出去还是传给上层继续解析。

  1. static int ip_rcv_finish(struct sk_buff *skb)
  2. {
  3. const struct iphdr *iph = ip_hdr(skb);
  4. struct rtable *rt;
  5. /*
  6. * Initialise the virtual path cache for the packet. It describes
  7. * how the packet travels inside Linux networking.
  8. */
  9. // 如果数据包还没有目的路由,则通过路由子系统的ip_route_input()查询路由,
  10. // 进而决定该数据包的去向
  11. if (skb->dst == NULL) {
  12. // 路由查询失败,那么会更新统计信息后丢弃数据包
  13. int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);
  14. if (unlikely(err)) {
  15. if (err == -EHOSTUNREACH)
  16. IP_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);
  17. else if (err == -ENETUNREACH)
  18. IP_INC_STATS_BH(IPSTATS_MIB_INNOROUTES);
  19. goto drop;
  20. }
  21. }
  22. // 如果该数据包包含IP选项,则解析这些选项并进行一定的处理
  23. if (iph->ihl > 5 && ip_rcv_options(skb))
  24. goto drop;
  25. // 根据目的路由信息,如果需要,更新多播和广播统计
  26. rt = (struct rtable*)skb->dst;
  27. if (rt->rt_type == RTN_MULTICAST)
  28. IP_INC_STATS_BH(IPSTATS_MIB_INMCASTPKTS);
  29. else if (rt->rt_type == RTN_BROADCAST)
  30. IP_INC_STATS_BH(IPSTATS_MIB_INBCASTPKTS);
  31. // 根据目的路由进行向上分发,或者是转发
  32. return dst_input(skb);
  33. drop:
  34. kfree_skb(skb);
  35. return NET_RX_DROP;
  36. }

2.1 报文分发 dst_input 

该函数做的最重要的事情就是路由查找,通过路由查询,决定数据包是继续交由本机的高层协议处理,还是走转发流程,不同的路由是由 dst_input() 函数决定的:如果数据是输入本机的,input函数为ip_local_delivery();如果是转发的,input函数为ip_forward()。

  1. /* Input packet from network to transport. */
  2. static inline int dst_input(struct sk_buff *skb)
  3. {
  4. // 调用skb中的目的路由信息中的input()继续处理,SKB中的dst信息实际上就是前面的ip_route_input()查询
  5. // 路由表时设置好的,所以说,查询路由表就是要获取一个dst信息并将其设置到skb中
  6. return skb_dst(skb)->input(skb);
  7. }

3 数据包发送至本机 ip_local_delivery()

  1. /*
  2. * Deliver IP Packets to the higher protocol layers.
  3. */
  4. int ip_local_deliver(struct sk_buff *skb)
  5. {
  6. // 首先检查该IP数据报是否是分片,如果是则要调用ip_defrag()尝试进行组装,组装成功则继续处理,
  7. // 否则需要先进行缓存等待其它分组的到达
  8. if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
  9. if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
  10. return 0;
  11. }
  12. // 进入LOCAL_IN HOOK点,如果通过则调用ip_local_deliver_finish()继续处理
  13. return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);
  14. }

3.1 通过 LOCAL_IN 校验 ip_local_deliver_finish()

这里我们假设数据包能够通过LOCAL_IN,继续看 ip_local_deliver_finish() 的处理。

  1. static int ip_local_deliver_finish(struct sk_buff *skb)
  2. {
  3. // 在skb中将IP首部删掉
  4. __skb_pull(skb, ip_hdrlen(skb));
  5. // 设置skb->transport_header指针使其指向SKB的data开始位置,这样方便更高层协议处理
  6. skb_reset_transport_header(skb);
  7. rcu_read_lock();
  8. {
  9. // 取出IP首部的协议字段,根据该字段寻找对应的上层协议
  10. int protocol = ip_hdr(skb)->protocol;
  11. int hash, raw;
  12. struct net_protocol *ipprot;
  13. resubmit:
  14. // 网络层 RAW 套接字处理
  15. raw = raw_local_deliver(skb, protocol);
  16. // 计算好哈希值
  17. hash = protocol & (MAX_INET_PROTOS - 1);
  18. // 从inet_protos数组中寻找上层协议提供的接收处理回调,在协议族初始化时,
  19. // 所有的上层协议都会将自己的接收处理接口注册到该数组中
  20. if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
  21. int ret;
  22. // IPSec相关的检查,忽略
  23. if (!ipprot->no_policy) {
  24. if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
  25. kfree_skb(skb);
  26. goto out;
  27. }
  28. nf_reset(skb);
  29. }
  30. // 调用传输层接口处理,对于TCP是tcp_v4_rcv()
  31. ret = ipprot->handler(skb);
  32. // 如果上层的处理返回错误,这里会将错误码作为协议号,重新执行上述流程,
  33. // 这一般会匹配到ICMP模块进行处理
  34. if (ret < 0) {
  35. protocol = -ret;
  36. goto resubmit;
  37. }
  38. IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
  39. } else {
  40. if (!raw) {
  41. if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
  42. IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
  43. icmp_send(skb, ICMP_DEST_UNREACH,
  44. ICMP_PROT_UNREACH, 0);
  45. }
  46. } else
  47. IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
  48. // 没有对应的上层协议时,需要丢弃该数据包
  49. kfree_skb(skb);
  50. }
  51. }
  52. out:
  53. rcu_read_unlock();
  54. return 0;
  55. }

4 数据包转发出去 ip_forward()

  1. int ip_forward(struct sk_buff *skb)
  2. {
  3. struct iphdr *iph; /* Our header */
  4. struct rtable *rt; /* Route we use */
  5. struct ip_options * opt = &(IPCB(skb)->opt);
  6. // IPSec相关检查,忽略
  7. if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))
  8. goto drop;
  9. // 如果有路由告警信息,处理成功后直接返回,不再转发这种数据包
  10. if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))
  11. return NET_RX_SUCCESS;
  12. // 确保该数据包确实是让自己转发的
  13. if (skb->pkt_type != PACKET_HOST)
  14. goto drop;
  15. // 转发会修改IP的首部字段,所以需要把检验和设置为CHECKSUM_NONE
  16. skb_forward_csum(skb);
  17. /*
  18. * According to the RFC, we must first decrease the TTL field. If
  19. * that reaches zero, we must reply an ICMP control message telling
  20. * that the packet's lifetime expired.
  21. */
  22. // 如果TTL已经减为1,那么向发送段回复生命周期太短的ICMP报文
  23. if (ip_hdr(skb)->ttl <= 1)
  24. goto too_many_hops;
  25. // IPSec相关,忽略
  26. if (!xfrm4_route_forward(skb))
  27. goto drop;
  28. // 严格源路由选项检查
  29. rt = (struct rtable*)skb->dst;
  30. if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
  31. goto sr_failed;
  32. // IP分片相关处理
  33. if (unlikely(skb->len > dst_mtu(&rt->u.dst) && !skb_is_gso(skb) &&
  34. (ip_hdr(skb)->frag_off & htons(IP_DF))) && !skb->local_df) {
  35. IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
  36. icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(dst_mtu(&rt->u.dst)));
  37. goto drop;
  38. }
  39. /* We are about to mangle packet. Copy it! */
  40. if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))
  41. goto drop;
  42. iph = ip_hdr(skb);
  43. // 递减TTL
  44. ip_decrease_ttl(iph);
  45. /*
  46. * We now generate an ICMP HOST REDIRECT giving the route
  47. * we calculated.
  48. */
  49. // 路由重定向选项处理
  50. if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr && !skb->sp)
  51. ip_rt_send_redirect(skb);
  52. // 根据TOS字段转换出优先级
  53. skb->priority = rt_tos2priority(iph->tos);
  54. // 进入FORWARD链,如果通过调用ip_forward_finish()完成转发过程处理
  55. return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev,
  56. ip_forward_finish);
  57. sr_failed:
  58. /*
  59. * Strict routing permits no gatewaying
  60. */
  61. icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
  62. goto drop;
  63. too_many_hops:
  64. /* Tell the sender its packet died... */
  65. IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
  66. icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
  67. drop:
  68. kfree_skb(skb);
  69. return NET_RX_DROP;
  70. }

4.1 通过 FORWARD 校验 ip_forward_finish()

假设通过了 FORWARD,继续看 ip_forward_finish() 的处理:

  1. static int ip_forward_finish(struct sk_buff *skb)
  2. {
  3. struct ip_options * opt = &(IPCB(skb)->opt);
  4. IP_INC_STATS_BH(IPSTATS_MIB_OUTFORWDATAGRAMS);
  5. // 处理转发选项
  6. if (unlikely(opt->optlen))
  7. ip_forward_options(skb);
  8. // 直接调用路由输出,指向的应该是ip_output()或者ip_mc_output()
  9. return dst_output(skb);
  10. }

到 dst_output(),那么就和输出过程吻合了,后续流程和本机的正常发包一样了,这里不再继续展开。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/252980
推荐阅读
相关标签
  

闽ICP备14008679号