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

路由的时候,dst_output函数设置为ip_output ip_mc_output等

1、TCP输出接口

L4 层在发送数据时会根据协议的不同调用上面提到的几个辅助函数之一,tcp协议打包成ip数据包文的方法根据tcp段的不同而选择不同的接口,

其中ip_queue_xmit为常用接口,ip_build_and_send_pkt、ip_send_reply只有在发送特定段的时候才使用

1-1 int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)

  1. int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
  2. {
  3. struct inet_sock *inet = inet_sk(sk);
  4. struct net *net = sock_net(sk);
  5. struct ip_options_rcu *inet_opt;
  6. struct flowi4 *fl4;
  7. struct rtable *rt;
  8. struct iphdr *iph;
  9. int res;
  10.  
  11. /* Skip all of this if the packet is already routed,
  12. * f.e. by something like SCTP.
  13. */
  14. rcu_read_lock();
  15. /*
  16. * 如果待输出的数据包已准备好路由缓存,
  17. * 则无需再查找路由,直接跳转到packet_routed
  18. * 处作处理。
  19. */
  20. inet_opt = rcu_dereference(inet->inet_opt);
  21. fl4 = &fl->u.ip4;
  22. rt = skb_rtable(skb);
  23. if (rt)
  24. goto packet_routed;
  25.  
  26. /* Make sure we can route this packet. */
  27. /*
  28. * 如果输出该数据包的传输控制块中
  29. * 缓存了输出路由缓存项,则需检测
  30. * 该路由缓存项是否过期。
  31. * 如果过期,重新通过输出网络设备、
  32. * 目的地址、源地址等信息查找输出
  33. * 路由缓存项。如果查找到对应的路
  34. * 由缓存项,则将其缓存到传输控制
  35. * 块中,否则丢弃该数据包。
  36. * 如果未过期,则直接使用缓存在
  37. * 传输控制块中的路由缓存项。
  38. */
  39. rt = (struct rtable *)__sk_dst_check(sk, 0);
  40. if (!rt) { /* 缓存过期 */
  41. __be32 daddr;
  42.  
  43. /* Use correct destination address if we have options. */
  44. daddr = inet->inet_daddr; /* 目的地址 */
  45. if (inet_opt && inet_opt->opt.srr)
  46. daddr = inet_opt->opt.faddr; /* 严格路由选项 */
  47.  
  48. /* If this fails, retransmit mechanism of transport layer will
  49. * keep trying until route appears or the connection times
  50. * itself out.
  51. */ /* 查找路由缓存 */
  52. rt = ip_route_output_ports(net, fl4, sk,
  53. daddr, inet->inet_saddr,
  54. inet->inet_dport,
  55. inet->inet_sport,
  56. sk->sk_protocol,
  57. RT_CONN_FLAGS(sk),
  58. sk->sk_bound_dev_if);
  59. if (IS_ERR(rt))
  60. goto no_route;
  61. sk_setup_caps(sk, &rt->dst); /* 设置控制块的路由缓存 */
  62. }
  63. skb_dst_set_noref(skb, &rt->dst);/* 将路由设置到skb中 */
  64.  
  65. packet_routed:
  66. /*
  67. * 查找到输出路由后,先进行严格源路由
  68. * 选项的处理。如果存在严格源路由选项,
  69. * 并且数据包的下一跳地址和网关地址不
  70. * 一致,则丢弃该数据包。
  71. */
  72. if (inet_opt && inet_opt->opt.is_strictroute && rt->rt_uses_gateway)
  73. goto no_route;
  74.  
  75. /* OK, we know where to send it, allocate and build IP header. */
  76. /*
  77. * 设置IP首部中各字段的值。如果存在IP选项,
  78. * 则在IP数据包首部中构建IP选项。
  79. */
  80. skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));
  81. skb_reset_network_header(skb);
  82. iph = ip_hdr(skb);/* 构造ip头 */
  83. *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
  84. if (ip_dont_fragment(sk, &rt->dst) && !skb->ignore_df)
  85. iph->frag_off = htons(IP_DF);
  86. else
  87. iph->frag_off = 0;
  88. iph->ttl = ip_select_ttl(inet, &rt->dst);
  89. iph->protocol = sk->sk_protocol;
  90. ip_copy_addrs(iph, fl4);
  91.  
  92. /* Transport layer set skb->h.foo itself. */
  93. /* 构造ip选项 */
  94. if (inet_opt && inet_opt->opt.optlen) {
  95. iph->ihl += inet_opt->opt.optlen >> 2;
  96. ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);
  97. }
  98.  
  99. ip_select_ident_segs(net, skb, sk,
  100. skb_shinfo(skb)->gso_segs ?: 1);
  101.  
  102. /* TODO : should we use skb->sk here instead of sk ? */
  103. /*
  104. * 设置输出数据包的QoS类型。
  105. */
  106. skb->priority = sk->sk_priority;
  107. skb->mark = sk->sk_mark;
  108.  
  109. res = ip_local_out(net, sk, skb); /* 输出 */
  110. rcu_read_unlock();
  111. return res;
  112.  
  113. no_route:
  114. rcu_read_unlock();
  115. /*
  116. * 如果查找不到对应的路由缓存项,
  117. * 在此处理,将该数据包丢弃。
  118. */
  119. IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
  120. kfree_skb(skb);
  121. return -EHOSTUNREACH;
  122. }

1、2 ip_build_and_send_pkt

在tcp建立连接过程中,内核封包输出syn+ack类型的tcp报文。用此函数封包ip报文段

  1. int ip_build_and_send_pkt(struct sk_buff *skb, const struct sock *sk,
  2. __be32 saddr, __be32 daddr, struct ip_options_rcu *opt)
  3. {
  4. struct inet_sock *inet = inet_sk(sk);
  5. struct rtable *rt = skb_rtable(skb);
  6. struct net *net = sock_net(sk);
  7. struct iphdr *iph;
  8.  
  9. /* Build the IP header. 构造ip首部*/
  10. skb_push(skb, sizeof(struct iphdr) + (opt ? opt->opt.optlen : 0));
  11. skb_reset_network_header(skb);
  12. iph = ip_hdr(skb);
  13. iph->version = 4;
  14. iph->ihl = 5;
  15. iph->tos = inet->tos;
  16. iph->ttl = ip_select_ttl(inet, &rt->dst);
  17. iph->daddr = (opt && opt->opt.srr ? opt->opt.faddr : daddr);
  18. iph->saddr = saddr;
  19. iph->protocol = sk->sk_protocol;
  20. /* 分片与否 */
  21. if (ip_dont_fragment(sk, &rt->dst)) {
  22. iph->frag_off = htons(IP_DF);
  23. iph->id = 0;
  24. } else {
  25. iph->frag_off = 0;
  26. __ip_select_ident(net, iph, 1);
  27. }
  28. /*处理ip选项字段*/
  29. if (opt && opt->opt.optlen) {
  30. iph->ihl += opt->opt.optlen>>2;
  31. ip_options_build(skb, &opt->opt, daddr, rt, 0);
  32. }
  33. /* QOS优先级 */
  34. skb->priority = sk->sk_priority;
  35. skb->mark = sk->sk_mark;
  36.  
  37. /* Send it out. */
  38. return ip_local_out(net, skb->sk, skb);
  39. }

1.3 ip_send_reply

此函数主要用于输出rst 以及ack段报文 在tcp_v4_send_reset 和tcp_v4_send_ack 中调用

  1. void ip_send_unicast_reply(struct sock *sk, struct sk_buff *skb,
  2. const struct ip_options *sopt,
  3. __be32 daddr, __be32 saddr,
  4. const struct ip_reply_arg *arg,
  5. unsigned int len)
  6. {
  7. struct ip_options_data replyopts;
  8. struct ipcm_cookie ipc;
  9. struct flowi4 fl4;
  10. struct rtable *rt = skb_rtable(skb);
  11. struct net *net = sock_net(sk);
  12. struct sk_buff *nskb;
  13. int err;
  14. int oif;
  15. /* 获取ip选项用于处理原路由选项 */
  16. if (__ip_options_echo(&replyopts.opt.opt, skb, sopt))
  17. return;
  18.  
  19. ipc.addr = daddr;
  20. ipc.opt = NULL;
  21. ipc.tx_flags = 0;
  22. ipc.ttl = 0;
  23. ipc.tos = -1;
  24. /* 如果输入的ip 数据包文启用了路由选项
  25. 将得到的下一跳地址作为目的ip地址*/
  26. if (replyopts.opt.opt.optlen) {
  27. ipc.opt = &replyopts.opt;
  28.  
  29. if (replyopts.opt.opt.srr)
  30. daddr = replyopts.opt.opt.faddr;
  31. }
  32.  
  33. oif = arg->bound_dev_if; /*设置 输出接口 */
  34. if (!oif && netif_index_is_l3_master(net, skb->skb_iif))
  35. oif = skb->skb_iif;
  36. /* 查路由 根据目的地址 原地址 查找 */
  37. flowi4_init_output(&fl4, oif,
  38. IP4_REPLY_MARK(net, skb->mark),
  39. RT_TOS(arg->tos),
  40. RT_SCOPE_UNIVERSE, ip_hdr(skb)->protocol,
  41. ip_reply_arg_flowi_flags(arg),
  42. daddr, saddr,
  43. tcp_hdr(skb)->source, tcp_hdr(skb)->dest);
  44. security_skb_classify_flow(skb, flowi4_to_flowi(&fl4));
  45. rt = ip_route_output_key(net, &fl4);
  46. if (IS_ERR(rt))/*如果没有命中路由,终止*/
  47. return;
  48.  
  49. inet_sk(sk)->tos = arg->tos;
  50.  
  51. sk->sk_priority = skb->priority;
  52. sk->sk_protocol = ip_hdr(skb)->protocol;
  53. sk->sk_bound_dev_if = arg->bound_dev_if;
  54. sk->sk_sndbuf = sysctl_wmem_default;
  55. /*将数据报文添加到队列末尾中或者复制到新生成的
  56. skb中去 并添加到输出队列*/
  57. err = ip_append_data(sk, &fl4, ip_reply_glue_bits, arg->iov->iov_base,
  58. len, 0, &ipc, &rt, MSG_DONTWAIT);
  59. if (unlikely(err)) {
  60. ip_flush_pending_frames(sk);
  61. goto out;
  62. }
  63.  
  64. nskb = skb_peek(&sk->sk_write_queue);
  65. if (nskb) {/* 如果发送队列有skb,则计算校验和,发送 */
  66. if (arg->csumoffset >= 0)
  67. *((__sum16 *)skb_transport_header(nskb) +
  68. arg->csumoffset) = csum_fold(csum_add(nskb->csum,
  69. arg->csum));
  70. nskb->ip_summed = CHECKSUM_NONE;
  71. ip_push_pending_frames(sk, &fl4);// 发送数据
  72. }
  73. out:
  74. ip_rt_put(rt);
  75. }
  76. int ip_send_skb(struct net *net, struct sk_buff *skb)
  77. {
  78. int err;
  79.  
  80. err = ip_local_out(net, skb->sk, skb);
  81. if (err) {
  82. if (err > 0)
  83. err = net_xmit_errno(err);
  84. if (err)
  85. IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
  86. }
  87.  
  88. return err;
  89. }
  90.  
  91. int ip_push_pending_frames(struct sock *sk, struct flowi4 *fl4)
  92. {
  93. struct sk_buff *skb;
  94.  
  95. skb = ip_finish_skb(sk, fl4);
  96. if (!skb)
  97. return 0;
  98.  
  99. /* Netfilter gets whole the not fragmented skb. */
  100. return ip_send_skb(sock_net(sk), skb);
  101. }

ip_queue_xmit 流程图

IP 层收发报文简要剖析4--ip 报文发送的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

  7. TCP层的分段和IP层的分片之间的关系 & MTU和MSS之间的关系 (转载)

    首先说明:数据报的分段和分片确实发生,分段发生在传输层,分片发生在网络层.但是对于分段来说,这是经常发生在UDP传输层协议上的情况,对于传输层使用TCP协议的通道来说,这种事情很少发生. 1,MTU( ...

  8. 原 TCP层的分段和IP层的分片之间的关系 & MTU和MSS之间的关系

    首先说明:数据报的分段和分片确实发生,分段发生在传输层,分片发生在网络层.但是对于分段来说,这是经常发生在UDP传输层协议上的情况,对于传输层使用TCP协议的通道来说,这种事情很少发生. 1,MTU( ...

  9. Linux 网卡驱动学习(六)(应用层、tcp 层、ip 层、设备层和驱动层作用解析)

    本文将介绍网络连接建立的过程.收发包流程,以及当中应用层.tcp层.ip层.设备层和驱动层各层发挥的作用. 1.应用层 对于使用socket进行网络连接的server端程序.我们会先调用socket函 ...

随机推荐

  1. kali linux 换国内源

    输入命令 vim /etc/apt/sources.list 添加国内源 #中科大deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-f ...

  2. spring boot:actuator的安全配置:使用spring security做ip地址限制(spring boot 2.3.2)

    一,actuator有哪些环节要做安全配置? actuator是应用广泛的监控工具, 但在生产环境中使用时,需要做严格的安全保障, 避免造成信息泄露等严重的安全问题 actuator可以采取的安全措施 ...

  3. centos8平台:用fontconfig安装及管理字体(fc-list/fc-match/fc-cache)

    一,fc-list所属的rpm包 [root@blog ~]$ whereis fc-list fc-list: /usr/bin/fc-list /usr/share/man/man1/fc-lis ...

  4. Business Partner - 供应商与客户的集成 - S/4HANA(2)

    配置 BP配置 激活BP的PPO请求 Cross-Application Components->Master Data Synchronization->Master Data Sync ...

  5. JVM内存管理和垃圾回收

    无论对于Java程序员还是大数据研发人员,JVM是必须掌握的技能之一.既是面试中经常问的问题,也是在实际业务中对程序进行调优.排查类似于内存溢出.栈溢出.内存泄漏等问题的关键.笔者将按下图分多篇文章详 ...

  6. ubuntu18.04下的off-by-null:hitcon_2018_children_tcache

    又没做出来,先说说自己的思路 因为是off-by-null,所以准备构造重叠的chunk,但是发现程序里有memset,给构造prev size造成重大问题 所以来详细记录一下做题过程 先逆向,IDA ...

  7. 案例>>>用绝对值的方法打印出菱形

    import java.util.Scanner; public class Test { public static void main(String[] args) { Scanner sc = ...

  8. 深度学习中卷积层和pooling层的输出计算公式(转)

    原文链接:https://blog.csdn.net/yepeng_xinxian/article/details/82380707 1.卷积层的输出计算公式class torch.nn.Conv2d ...

  9. 手写webpack核心原理,再也不怕面试官问我webpack原理

    手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...

  10. Redis缓存雪崩和穿透的解决方法

    转载自: https://blog.csdn.net/qq_35433716/article/details/86375506 如何解决缓存雪崩?如何解决缓存穿透?如何保证缓存与数据库双写时一致的问题 ...