本文主要讲解了Linux内核IP层的整体架构和对从网卡接受的报文处理流程,使用的内核的版本是2.6.32.27

为了方便理解,本文采用整体流程图加伪代码的方式对Linxu内核中IP整体实现架构和对网卡报文的处理流程进行了讲解,希望可以对大家有所帮助。阅读本文章假设大家对C语言有了一定的了解

IP层的整体实现架构

IP层接受底层数据报文的处理流程

  1. /*
  2. * 在NET_RX_SOFTIRQ软中后,由ETH_P_IP触发的ipv4协议入口函数
  3. */
  4. int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
  5. {
  6. /*
  7. * 过滤掉送往其他主机的数据包(这时网卡正在处于混杂模式)
  8. */
  9. if (skb->pkt_type == PACKET_OTHERHOST)
  10. goto drop;
  11.  
  12. iph = ip_hdr(skb);
  13.  
  14. /*头的长度是否至少是IP头长度(5); 是否是IPV4报文*/
  15. if (iph->ihl < 5 || iph->version != 4)
  16. goto inhdr_error;
  17.  
  18. /*IP头长度是否正确,不是伪造的长度*/
  19. if (!pskb_may_pull(skb, iph->ihl*4))
  20. goto inhdr_error;
  21.  
  22. iph = ip_hdr(skb);
  23. /*检查校验和*/
  24. if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
  25. goto inhdr_error;
  26.  
  27. len = ntohs(iph->tot_len);
  28. if (skb->len < len) {
  29. IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
  30. goto drop;
  31. } else if (len < (iph->ihl*4))
  32. goto inhdr_error;
  33.  
  34. /*实际尺寸不匹配套接字缓冲(skb->len)中维护的信息,则调用skb_trim调整数据包的长度*/
  35. if (pskb_trim_rcsum(skb, len)) {
  36. IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
  37. goto drop;
  38. }
  39.  
  40. /*调用IP_PRE_ROUTING(NF_INET_PRE_ROUTING)上注册的钩子,
  41. *在调用钩子处理完之后,调用钩子处理完成之后,调用ip_rcv_finish
  42. * 后面讲防火墙的时候,我们会仔细梳理*/
  43. return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
  44. }
  45.  
  46. /* NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish)*/
  47. #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
  48. NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)
  49. {
  50. int __ret; \
  51. if ((__ret=nf_hook_thresh(pf, hook, (skb), indev, outdev, okfn, thresh, 1)) == 1)\
  52. __ret = (okfn)(skb); \
  53. __ret;
  54. }
  55.  
  56. static inline int nf_hook_thresh(u_int8_t pf, unsigned int hook,
  57. struct sk_buff *skb,
  58. struct net_device *indev,
  59. struct net_device *outdev,
  60. int (*okfn)(struct sk_buff *), int thresh,
  61. int cond)
  62. {
  63. /*逐个调用注册的防火墙钩子*/
  64. return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh);
  65. }
  66.  
  67. /*
  68. * 接收完数据包后的后续处理函数
  69. */
  70. static int ip_rcv_finish(struct sk_buff *skb)
  71. {
  72. const struct iphdr *iph = ip_hdr(skb);
  73. struct rtable *rt;
  74.  
  75. /*
  76. * 激活ip_route_input,确定报文的路由,如果ip_route_input无法从FIB中找到路由
  77. * 则丢弃数据报文,ip_route_input将在IP路由中的专题中进行讲解
  78. */
  79. if (skb_dst(skb) == NULL) {
  80. int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);
  81. if (unlikely(err)) {
  82. goto drop;
  83. }
  84. }
  85.  
  86. /*检查IP报头里面是否含有选项,如果含有建立ip_options*/
  87. if (iph->ihl > 5 && ip_rcv_options(skb))
  88. goto drop;
  89.  
  90. /*根据dst_entry的结果,使用skb_dst(skb)->input(skb)进行IP的路由选择
  91. *传递给本地计算机的单播或多播,进入 ip_local_deliver();
  92. *单播转发的报文进入ip_forward()
  93. *多播转发进入ip_mr_input()
  94. */
  95. return dst_input(skb);
  96. {
  97. skb_dst(skb)->input(skb)
  98. }
  99.  
  100. drop:
  101. kfree_skb(skb);
  102. return NET_RX_DROP;
  103. }
  104.  
  105. /*目的地分发策略的注册*/
  106. static int __mkroute_input(struct sk_buff *skb,
  107. struct fib_result *res,
  108. struct in_device *in_dev,
  109. __be32 daddr, __be32 saddr, u32 tos,
  110. struct rtable **result)
  111. {
  112. //.......
  113. rth->u.dst.input = ip_forward;
  114. rth->u.dst.output = ip_output;
  115. //......
  116. }
  117.  
  118. static int __mkroute_output(struct rtable **result,
  119. struct fib_result *res,
  120. const struct flowi *fl,
  121. const struct flowi *oldflp,
  122. struct net_device *dev_out,
  123. unsigned flags)
  124. {
  125. //......
  126. if (flags & RTCF_LOCAL) {
  127. rth->u.dst.input = ip_local_deliver;
  128. rth->rt_spec_dst = fl->fl4_dst;
  129. }
  130. if (flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
  131. rth->rt_spec_dst = fl->fl4_src;
  132. if (flags & RTCF_LOCAL &&
  133. !(dev_out->flags & IFF_LOOPBACK)) {
  134. rth->u.dst.output = ip_mc_output;
  135. RT_CACHE_STAT_INC(out_slow_mc);
  136. }
  137. #ifdef CONFIG_IP_MROUTE
  138. if (res->type == RTN_MULTICAST) {
  139. if (IN_DEV_MFORWARD(in_dev) &&
  140. !ipv4_is_local_multicast(oldflp->fl4_dst)) {
  141. rth->u.dst.input = ip_mr_input;
  142. rth->u.dst.output = ip_mc_output;
  143. }
  144. }
  145. #endif
  146.  
  147. }
  148. //......
  149. }

如果IP报文需要转发,那么分析流程如下

  1. //-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
  2. /*单播转发处理,负责处理转发相关的所有动作*/
  3. int ip_forward(struct sk_buff *skb)
  4. {
  5. /*删除不是PACKET_HOST的数据包*/
  6. if (skb->pkt_type != PACKET_HOST)
  7. goto drop;
  8.  
  9. /*TTL递减为1之间,丢弃该报,并返回ICMP_TIME_EXCEEDED*/
  10. if (ip_hdr(skb)->ttl <= 1)
  11. goto too_many_hops;
  12.  
  13. /*如果skb->len大于MTU值,且Dont-Fragment被职位,则丢弃此报文,
  14. *并返回ICMP_FRAG_NEEDED*/
  15. if (unlikely(skb->len > dst_mtu(&rt->u.dst) && (ip_hdr(skb)->frag_off & htons(IP_DF)))) {
  16. icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(dst_mtu(&rt->u.dst)));
  17. goto drop;
  18. }
  19.  
  20. /*检查是否有足够的空间用于输出网络设备中的MAC报头dst.header_len()
  21. *调用skb_cow来创建一个新的足够长的skb,并且拷贝原来的所有数据
  22. */
  23. if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))
  24. goto drop;
  25. iph = ip_hdr(skb);
  26.  
  27. /*TTL减少1*/
  28. ip_decrease_ttl(iph);
  29.  
  30. /*使用IP_FORWARD中注册的钩子函数,当防火墙中的钩子都与运行完成后,
  31. *进入ip_forward_finish*/
  32. return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev, ip_forward_finish);
  33.  
  34. sr_failed:
  35. /*
  36. * Strict routing permits no gatewaying
  37. */
  38. icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
  39. goto drop;
  40.  
  41. too_many_hops:
  42. /* Tell the sender its packet died... */
  43. IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_INHDRERRORS);
  44. icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
  45. drop:
  46. kfree_skb(skb);
  47. return NET_RX_DROP;
  48. }
  49.  
  50. /*
  51. * 该函数没有什么用途,除非启用了FASTROUTE,
  52. * 将处理后的函数报文送入output阶段
  53. */
  54. static int ip_forward_finish(struct sk_buff *skb)
  55. {
  56. struct ip_options * opt = &(IPCB(skb)->opt);
  57.  
  58. /*使用ip_forward_options处理IP选项*/
  59. if (unlikely(opt->optlen))
  60. ip_forward_options(skb);
  61.  
  62. /*送入到输出阶段*/
  63. return dst_output(skb);
  64. {
  65. skb_dst(skb)->output(skb);
  66. }
  67. }
  68.  
  69. /*目的地分发策略的注册*/
  70. static int __mkroute_input(struct sk_buff *skb,
  71. struct fib_result *res,
  72. struct in_device *in_dev,
  73. __be32 daddr, __be32 saddr, u32 tos,
  74. struct rtable **result)
  75. {
  76. //.......
  77. rth->u.dst.input = ip_forward;
  78. rth->u.dst.output = ip_output;
  79. //......
  80. }
  81.  
  82. int ip_output(struct sk_buff *skb)
  83. {
  84. struct net_device *dev = skb_dst(skb)->dev;
  85.  
  86. /*将skb->dev指向输出设备的dev*/
  87. skb->dev = dev;
  88. /*设置2层包类型为ETH_P_IP*/
  89. skb->protocol = htons(ETH_P_IP);
  90.  
  91. /*使用防火墙中的NF_IP_POST_ROUTING中注册的钩子函数进行处理,
  92. *处理完成之后进入ip_finish_output处理*/
  93. return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,
  94. ip_finish_output,
  95. !(IPCB(skb)->flags & IPSKB_REROUTED));
  96. }
  97.  
  98. /*判定是否进行IP分片*/
  99. static int ip_finish_output(struct sk_buff *skb)
  100. {
  101. /*如果报文尺寸大于MTU,则进行IP分片后送入ip_finish_output2
  102. *否则直接送入ip_finish_output2
  103. */
  104. if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))
  105. return ip_fragment(skb, ip_finish_output2);
  106. else
  107. return ip_finish_output2(skb);
  108. }
  109.  
  110. static const struct neigh_ops arp_generic_ops = {
  111. .family = AF_INET,
  112. .output = neigh_resolve_output,
  113. .hh_output = dev_queue_xmit,
  114. };
  115.  
  116. static const struct neigh_ops arp_hh_ops = {
  117. .family = AF_INET,
  118. .output = neigh_resolve_output,
  119. .hh_output = dev_queue_xmit,
  120. };
  121.  
  122. static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst, __be16 protocol)
  123. {
  124. struct hh_cache *hh;
  125.  
  126. //......
  127.  
  128. if (n->nud_state & NUD_CONNECTED)
  129. hh->hh_output = n->ops->hh_output; /*也就是dev_queue_xmit*/
  130. else
  131. hh->hh_output = n->ops->output;
  132.  
  133. //......
  134. }
  135.  
  136. static void neigh_suspect(struct neighbour *neigh)
  137. {
  138. //.....
  139. neigh->output = neigh->ops->output; /*也就是neigh_resolve_output*/
  140.  
  141. }
  142.  
  143. static inline int ip_finish_output2(struct sk_buff *skb)
  144. {
  145. /*如果2层头数据空间不够,则重新分配足够长度的SKB,并将数据复制到新的SKB后释放原来SKB*/
  146. if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
  147. struct sk_buff *skb2;
  148.  
  149. skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
  150.  
  151. if (skb->sk)
  152. skb_set_owner_w(skb2, skb->sk);
  153. kfree_skb(skb);
  154. skb = skb2;
  155. }
  156.  
  157. /*如果路由出口项中已经含有2层包头缓存的引用(dst->hh),进入neigh_hh_output*/
  158. if (dst->hh)
  159. return neigh_hh_output(dst->hh, skb);
  160. /*如果没有dst->hh,有dst->neighbour,则启动地址解析协议,也就是neigh_resolve_output*/
  161. else if (dst->neighbour)
  162. return dst->neighbour->output(skb);
  163.  
  164. }
  165.  
  166. static inline int neigh_hh_output(struct hh_cache *hh, struct sk_buff *skb)
  167. {
  168. /*直接复制2层包头到套接字的包数据空间中*/
  169. memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);
  170. skb_push(skb, hh_len);
  171.  
  172. /*调用hh->hh_output(skb),也就是dev_queue_xmit进行硬件发送*/
  173. return hh->hh_output(skb);
  174. }

如果IP是上送本地CPU的报文,处理流程如下

  1. //-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
  2. /*包的本地投递*/
  3. int ip_local_deliver(struct sk_buff *skb)
  4. {
  5. /*收集并组装IP分片,如果还没有收集完成,那么就等待IP分片组装完成*/
  6. if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
  7. if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
  8. return 0;
  9. }
  10.  
  11. /*进入NF_IP_LOCAL_IN的过滤器处理,处理完成后进入ip_local_deliver_finish*/
  12. return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);
  13. }
  14.  
  15. /*IP层处理完成后的协议分发函数*/
  16. static int ip_local_deliver_finish(struct sk_buff *skb)
  17. {
  18. resubmit:
  19. /*如果是RAW-IP报文,送往RAW-IP对应的处理???*/
  20. raw = raw_local_deliver(skb, protocol);
  21.  
  22. /*MAX_INET_PROTOS-1 为IP报头中协议的模,
  23. *这里计算对应协议在ipprot中被散列的位置*/
  24. hash = protocol & (MAX_INET_PROTOS - 1);
  25. /*IP层上的ipprot负责管理所有的传输协议*/
  26. ipprot = rcu_dereference(inet_protos[hash]);
  27.  
  28. /*如果找到相应的协议,那么调用对应的处理例程*/
  29. if (ipprot != NULL) {
  30. ret = ipprot->handler(skb);
  31. if (ret < 0) {
  32. protocol = -ret;
  33. goto resubmit;
  34. }
  35. }
  36. /*找不到相应的处理例程*/
  37. else {
  38. /*又是RAW-IP报文,会在RAW-IP处理例程???
  39. * 就丢弃,并想对端发送ICMP_DEST_UNREACH,ICMP_PROT_UNREACH*/
  40. if (!raw)
  41. {
  42. icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
  43. }
  44.  
  45. kfree_skb(skb);
  46. }
  47.  
  48. return 0;
  49. }
  50.  
  51. static const struct net_protocol tcp_protocol = {
  52. .handler = tcp_v4_rcv, /*TCP*/
  53. };
  54.  
  55. static const struct net_protocol udp_protocol = {
  56. .handler = udp_rcv, /*UDP*/
  57. };
  58.  
  59. static const struct net_protocol icmp_protocol = {
  60. .handler = icmp_rcv, /*ICMP*/
  61. };
  62.  
  63. static const struct net_protocol igmp_protocol = {
  64. .handler = igmp_rcv, /*IGMP*/
  65. };

通过上面的分析讲解,我们就可以很清楚的了解到IP是如何接受一个来自于网卡的数据包,并如何进行三层报文

关于二层数据报文的处理流程和是如何送到三层进行处理,请参考我前面的博客

《Linux内核二层数据包接收流程》

希望大家批评指正

Linux内核IP层的报文处理流程(一)的更多相关文章

  1. IP 层收发报文简要剖析2--ip报文的输入ip_local_deliver

    ip报文根据路由结果:如果发往本地则调用ip_local_deliver处理报文:如果是转发出去,则调用ip_forward 处理报文. 一.ip报文转发到本地: /* * Deliver IP Pa ...

  2. IP 层收发报文简要剖析5--ip报文发送2

    udp 发送ip段报文接口ip_append_data ip_append_data 函数主要用来udp 套接字以及raw套接字发送报文的接口.在tcp中发送ack 以及rest段的ip_send_u ...

  3. IP 层收发报文简要剖析3--ip输入报文分片重组

    在ip_local_deliver中,如果检测到是分片包,则需要将报文进行重组.其所有的分片被重新组合后才能提交到上层协议,每一个被重新组合的数据包文用ipq结构实例来表示 struct ipq { ...

  4. IP 层收发报文简要剖析1-ip报文的输入

    ip层数据包处理场景如下: 网络层处理数据包文时需要和路由表以及邻居系统打交道.输入数据时,提供输入接口给链路层调用,并调用传输层的输入接口将数据输入到传输层. 在输出数据时,提供输出接口给传输层,并 ...

  5. IP 层收发报文简要剖析6--ip报文输出3 ip_push_pending_frames

    L4层的协议会把数据通过ip_append_data或ip_append_page把数据线放在缓冲区,然后再显示调用ip_push_pending_frames传送数据. 把数据放在缓冲区有两个优点, ...

  6. IP 层收发报文简要剖析4--ip 报文发送

    无论是从本地输出的数据还是转发的数据报文,经过路由后都要输出到网络设备,而输出到网络设备的接口就是dst_output(output)函数 路由的时候,dst_output函数设置为ip_output ...

  7. IP 层收发报文简要剖析6--ip_forward 报文转发

    //在函数ip_route_input_slow->ip_mkroute_input注册, /* * IP数据包的转发是由ip_forward()处理,该函数在ip_rcv_finish() * ...

  8. Linux 内核协议栈 学习资料

    终极资料 1.<Understanding Linux Network Internals> 2.<TCP/IP Architecture, Design and Implement ...

  9. Linux内核网络报文简单流程

    转:http://blog.csdn.net/adamska0104/article/details/45397177 Linux内核网络报文简单流程2014-08-12 10:05:09 分类: L ...

随机推荐

  1. [Unity3D]Unity3D发展偷看游戏初期阶段NGUI

    朋友,大家晚上好. 我是秦培.欢迎关注我的博客,我的博客地址blog.csdn.net/qinyuanpei.近期博主開始研究NGUI了,由于NGUI是Unity3D中最为流行的界面插件,所以不管从学 ...

  2. win32 字体变换与窗口同大同小

    #include <windows.h> #include "res/resource.h" LRESULT CALLBACK WinProc(HWND hwnd, U ...

  3. QEventLoop等待另外一个事件的停止,非常实用 good

    void MyWidget::SendRequest(QString strUser) { network_manager = new QNetworkAccessManager(); connect ...

  4. js传真实地址 C:\fakepath

    js给action传真是地址的时候,处于安全,传到action中 浏览器会改变路径变为C:\fakepath\ftp.txt,但是原始路径却是 C:\Documents and Settings\Ad ...

  5. 01-编写CMS注意事项

    原文:01-编写CMS注意事项 1.将ThinkPHP核心文件放在项目目录,将下载的扩展包放在在ThinkPHP目录下的Extend文件夹中 2.设置整个项目的编码为utf-8 3.创建Public公 ...

  6. expect实现ssh自动登录

    expect实现ssh自动登录   #!/usr/local/bin/expect set PASSWD [lindex $argv 1] set IP [lindex $argv 0] set CM ...

  7. 用代码定位硬盘上的文件(使用ShellExecute执行explorer /select命令,其它参数也很全)

    问题:如何用代码控制资源浏览器,并定位到指定的文件? 答:使用ShellExecute,配合explorer即可 ShellExecute(Application.Handle, 'open', PC ...

  8. kill命令"-1"这个参数到底是杀进程还是reload?(转)

    kill-1:重新读取一次参数的配置文件 (类似 reload) 这句话给我的感觉是把进程杀掉后重启进程,即 reload.而我查了下 man kill,-1 对应的 signal 是 SIGHUP, ...

  9. IOCP模型与网络编程

    IOCP模型与网络编程 一.前言:        在老师分配任务(“尝试利用IOCP模型写出服务端和客户端的代码”)给我时,脑子一片空白,并不知道什么是IOCP模型,会不会是像软件设计模式里面的工厂模 ...

  10. HDU 4284 Travel

    据说是TSP经典问题...可以用状态压缩做.但是看到数据量,就厚着脸皮上搜索了...先floyd预处理每对点间的最小消费,然后只考虑要去的城市就可以了,这样的话城市数最多16个...当时就暴搜了... ...