无论是从本地输出的数据还是转发的数据报文,经过路由后都要输出到网络设备,而输出到网络设备的接口就是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)

int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
{
struct inet_sock *inet = inet_sk(sk);
struct net *net = sock_net(sk);
struct ip_options_rcu *inet_opt;
struct flowi4 *fl4;
struct rtable *rt;
struct iphdr *iph;
int res; /* Skip all of this if the packet is already routed,
* f.e. by something like SCTP.
*/
rcu_read_lock();
/*
* 如果待输出的数据包已准备好路由缓存,
* 则无需再查找路由,直接跳转到packet_routed
* 处作处理。
*/
inet_opt = rcu_dereference(inet->inet_opt);
fl4 = &fl->u.ip4;
rt = skb_rtable(skb);
if (rt)
goto packet_routed; /* Make sure we can route this packet. */
/*
* 如果输出该数据包的传输控制块中
* 缓存了输出路由缓存项,则需检测
* 该路由缓存项是否过期。
* 如果过期,重新通过输出网络设备、
* 目的地址、源地址等信息查找输出
* 路由缓存项。如果查找到对应的路
* 由缓存项,则将其缓存到传输控制
* 块中,否则丢弃该数据包。
* 如果未过期,则直接使用缓存在
* 传输控制块中的路由缓存项。
*/
rt = (struct rtable *)__sk_dst_check(sk, 0);
if (!rt) { /* 缓存过期 */
__be32 daddr; /* Use correct destination address if we have options. */
daddr = inet->inet_daddr; /* 目的地址 */
if (inet_opt && inet_opt->opt.srr)
daddr = inet_opt->opt.faddr; /* 严格路由选项 */ /* If this fails, retransmit mechanism of transport layer will
* keep trying until route appears or the connection times
* itself out.
*/ /* 查找路由缓存 */
rt = ip_route_output_ports(net, fl4, sk,
daddr, inet->inet_saddr,
inet->inet_dport,
inet->inet_sport,
sk->sk_protocol,
RT_CONN_FLAGS(sk),
sk->sk_bound_dev_if);
if (IS_ERR(rt))
goto no_route;
sk_setup_caps(sk, &rt->dst); /* 设置控制块的路由缓存 */
}
skb_dst_set_noref(skb, &rt->dst);/* 将路由设置到skb中 */ packet_routed:
/*
* 查找到输出路由后,先进行严格源路由
* 选项的处理。如果存在严格源路由选项,
* 并且数据包的下一跳地址和网关地址不
* 一致,则丢弃该数据包。
*/
if (inet_opt && inet_opt->opt.is_strictroute && rt->rt_uses_gateway)
goto no_route; /* OK, we know where to send it, allocate and build IP header. */
/*
* 设置IP首部中各字段的值。如果存在IP选项,
* 则在IP数据包首部中构建IP选项。
*/
skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));
skb_reset_network_header(skb);
iph = ip_hdr(skb);/* 构造ip头 */
*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
if (ip_dont_fragment(sk, &rt->dst) && !skb->ignore_df)
iph->frag_off = htons(IP_DF);
else
iph->frag_off = 0;
iph->ttl = ip_select_ttl(inet, &rt->dst);
iph->protocol = sk->sk_protocol;
ip_copy_addrs(iph, fl4); /* Transport layer set skb->h.foo itself. */
/* 构造ip选项 */
if (inet_opt && inet_opt->opt.optlen) {
iph->ihl += inet_opt->opt.optlen >> 2;
ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);
} ip_select_ident_segs(net, skb, sk,
skb_shinfo(skb)->gso_segs ?: 1); /* TODO : should we use skb->sk here instead of sk ? */
/*
* 设置输出数据包的QoS类型。
*/
skb->priority = sk->sk_priority;
skb->mark = sk->sk_mark; res = ip_local_out(net, sk, skb); /* 输出 */
rcu_read_unlock();
return res; no_route:
rcu_read_unlock();
/*
* 如果查找不到对应的路由缓存项,
* 在此处理,将该数据包丢弃。
*/
IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
kfree_skb(skb);
return -EHOSTUNREACH;
}

1、2 ip_build_and_send_pkt

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

int ip_build_and_send_pkt(struct sk_buff *skb, const struct sock *sk,
__be32 saddr, __be32 daddr, struct ip_options_rcu *opt)
{
struct inet_sock *inet = inet_sk(sk);
struct rtable *rt = skb_rtable(skb);
struct net *net = sock_net(sk);
struct iphdr *iph; /* Build the IP header. 构造ip首部*/
skb_push(skb, sizeof(struct iphdr) + (opt ? opt->opt.optlen : 0));
skb_reset_network_header(skb);
iph = ip_hdr(skb);
iph->version = 4;
iph->ihl = 5;
iph->tos = inet->tos;
iph->ttl = ip_select_ttl(inet, &rt->dst);
iph->daddr = (opt && opt->opt.srr ? opt->opt.faddr : daddr);
iph->saddr = saddr;
iph->protocol = sk->sk_protocol;
/* 分片与否 */
if (ip_dont_fragment(sk, &rt->dst)) {
iph->frag_off = htons(IP_DF);
iph->id = 0;
} else {
iph->frag_off = 0;
__ip_select_ident(net, iph, 1);
}
/*处理ip选项字段*/
if (opt && opt->opt.optlen) {
iph->ihl += opt->opt.optlen>>2;
ip_options_build(skb, &opt->opt, daddr, rt, 0);
}
/* QOS优先级 */
skb->priority = sk->sk_priority;
skb->mark = sk->sk_mark; /* Send it out. */
return ip_local_out(net, skb->sk, skb);
}

1.3 ip_send_reply

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

void ip_send_unicast_reply(struct sock *sk, struct sk_buff *skb,
const struct ip_options *sopt,
__be32 daddr, __be32 saddr,
const struct ip_reply_arg *arg,
unsigned int len)
{
struct ip_options_data replyopts;
struct ipcm_cookie ipc;
struct flowi4 fl4;
struct rtable *rt = skb_rtable(skb);
struct net *net = sock_net(sk);
struct sk_buff *nskb;
int err;
int oif;
/* 获取ip选项用于处理原路由选项 */
if (__ip_options_echo(&replyopts.opt.opt, skb, sopt))
return; ipc.addr = daddr;
ipc.opt = NULL;
ipc.tx_flags = 0;
ipc.ttl = 0;
ipc.tos = -1;
/* 如果输入的ip 数据包文启用了路由选项
将得到的下一跳地址作为目的ip地址*/
if (replyopts.opt.opt.optlen) {
ipc.opt = &replyopts.opt; if (replyopts.opt.opt.srr)
daddr = replyopts.opt.opt.faddr;
} oif = arg->bound_dev_if; /*设置 输出接口 */
if (!oif && netif_index_is_l3_master(net, skb->skb_iif))
oif = skb->skb_iif;
/* 查路由 根据目的地址 原地址 查找 */
flowi4_init_output(&fl4, oif,
IP4_REPLY_MARK(net, skb->mark),
RT_TOS(arg->tos),
RT_SCOPE_UNIVERSE, ip_hdr(skb)->protocol,
ip_reply_arg_flowi_flags(arg),
daddr, saddr,
tcp_hdr(skb)->source, tcp_hdr(skb)->dest);
security_skb_classify_flow(skb, flowi4_to_flowi(&fl4));
rt = ip_route_output_key(net, &fl4);
if (IS_ERR(rt))/*如果没有命中路由,终止*/
return; inet_sk(sk)->tos = arg->tos; sk->sk_priority = skb->priority;
sk->sk_protocol = ip_hdr(skb)->protocol;
sk->sk_bound_dev_if = arg->bound_dev_if;
sk->sk_sndbuf = sysctl_wmem_default;
/*将数据报文添加到队列末尾中或者复制到新生成的
skb中去 并添加到输出队列*/
err = ip_append_data(sk, &fl4, ip_reply_glue_bits, arg->iov->iov_base,
len, 0, &ipc, &rt, MSG_DONTWAIT);
if (unlikely(err)) {
ip_flush_pending_frames(sk);
goto out;
} nskb = skb_peek(&sk->sk_write_queue);
if (nskb) {/* 如果发送队列有skb,则计算校验和,发送 */
if (arg->csumoffset >= 0)
*((__sum16 *)skb_transport_header(nskb) +
arg->csumoffset) = csum_fold(csum_add(nskb->csum,
arg->csum));
nskb->ip_summed = CHECKSUM_NONE;
ip_push_pending_frames(sk, &fl4);// 发送数据
}
out:
ip_rt_put(rt);
}
int ip_send_skb(struct net *net, struct sk_buff *skb)
{
int err; err = ip_local_out(net, skb->sk, skb);
if (err) {
if (err > 0)
err = net_xmit_errno(err);
if (err)
IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
} return err;
} int ip_push_pending_frames(struct sock *sk, struct flowi4 *fl4)
{
struct sk_buff *skb; skb = ip_finish_skb(sk, fl4);
if (!skb)
return 0; /* Netfilter gets whole the not fragmented skb. */
return ip_send_skb(sock_net(sk), skb);
}

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. 多测师讲解自动化测试 _RF自定义关键字_高级讲师肖sir

    RF自定义关键字 在rf中叫关键字 在python中就叫做函数 或实例方法 我们自己可以写自定义关键字 自己创建一个库===库里面去创建模块===模块里面创建类和实例方法==>rf导入和引用 库 ...

  2. 多测师讲解html _表格标签007_高级讲师肖sir

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>表 ...

  3. MeteoInfoLab脚本示例:图形版面、点标注

    在MeteoInfoLab界面中,图形的大小会随着它所在的窗口的大小改变而改变,在需要精确控制图中一些要素的位置的时候会比较困难,这时可以用figure函数的一些参数来控制图形版面大小.figure函 ...

  4. 使用docker安装E

     环境 虚拟机软件VmWare15.5 Centos7.0 安装docker yum install docker

  5. spring boot:配置druid数据库连接池(开启sql防火墙/使用log4j2做异步日志/spring boot 2.3.2)

    一,druid数据库连接池的功能? 1,Druid是阿里巴巴开发的号称为监控而生的数据库连接池 它的优点包括: 可以监控数据库访问性能 SQL执行日志 SQL防火墙 2,druid的官方站: http ...

  6. linux(centos8):禁用selinux(临时关闭/永久关闭)

    一,selinux的用途 1,什么是selinux SELinux:即安全增强型 Linux(Security-Enhanced Linux) 它是一个 Linux 内核模块,也是 Linux 的一个 ...

  7. php长时间的脚本,报502

    php-fpm超时时间设置request_terminate_timeout分析原创loophome 最后发布于2017-11-22 16:17:59 阅读数 21201 收藏展开今天发现了一个很神奇 ...

  8. 1-1Java概述

    001_Java语言发展史 Sun公司:Stanford University Network  002Java跨平台原理 平台:指的是操作系统Windows,Mac,Linux等. 总结:在需要运行 ...

  9. Vue中封装axios组件实例

    首先要创建一个网络模块network文件夹  里面要写封装好的几个组件 在config.js里面这样写 在index.js要这样写 core.js文件里面内容如下 然后要在main.js文件里面要设置 ...

  10. Sentinel流控规则

    流控规则 注:Sentinel的监控页面一开始是没有东西,需要对监控的服务发起请求后才会出现 资源名:唯一名称,默认请求路径 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,指定对哪个 ...