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

    Drone 安装教程 一. CentOS设置 1. 更换阿里源 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/ ...

  2. Java8中Stream 的一些用法

    数据结构和数据准备 @Data @AllArgsConstructor @NoArgsConstructor static class StreamItem { Integer id; String ...

  3. leaflet平台添加天地图方法

    leaflet平台添加天地图得方法具体如下操作 var map = L.map('map',  {   crs: L.CRS.EPSG4326,   zoomControl: true,   edit ...

  4. 【多次实践】win10+ubuntu18.04lts双系统安装葵花宝典(安装篇)

    这个教程诞生的缘由很简单,吃的太饱,硬是要折腾,结果,这一折腾便是20余小时,故写此文,帮助后来者少走弯路! 在本文开始,请先允许我对网上很多类似的教程嗤之以鼻,很成功地让我走了很多的弯路,一些有效简 ...

  5. oracle oracle sqldeveloper 12505 创建连接失败

    ref:http://blog.csdn.net/yangwenxue_admin/article/details/45062557

  6. jenkins自动拉取git分支构建项目

    一,创建jenkins项目 new item ->freestyle project, 自定义一个项目名称 二,配置项目 1,Source Code Management 选择 git,输入gi ...

  7. 一些免费API接口

    转载自:https://www.cnblogs.com/haimishasha/p/6351403.html 天气接口 聚合数据: http://op.juhe.cn/onebox/weather/q ...

  8. CentOS换yum源为国内源

    CentOS换源 yum源 备份原来的文件. mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup ...

  9. 【论文阅读】DGCNN:Dynamic Graph CNN for Learning on Point Clouds

    毕设进了图网络的坑,感觉有点难,一点点慢慢学吧,本文方法是<Rethinking Table Recognition using Graph Neural Networks>中关系建模环节 ...

  10. sqlsugar入门(3)-DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff")源码修改

    1.注释SqlSugar\ExpressionsToSql\ResolveItems\MethodCallExpressionResolve文件下的GetMethodValue方法 case &quo ...