概述

ip_queue_xmit是ip层提供给tcp层发送回调,大多数tcp发送都会使用这个回调,tcp层使用tcp_transmit_skb封装了tcp头之后,调用该函数,该函数提供了路由查找校验、封装ip头和ip选项的功能,封装完成之后调用ip_local_out发送数据包;

ip_build_and_send_pkt函数是服务器端在给客户端回复syn+ack时调用的,该函数在构造ip头之后,调用ip_local_out发送数据包;

ip_send_unicast_reply函数目前只用于发送ACK和RST,该函数根据对端发过来的skb构造ip头,然后调用ip_append_data向发送队列中附加/新增数据,最后调用ip_push_pending_frames发送数据包;

源码分析
 /* Note: skb->sk can be different from sk, in case of tunnels */
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();
inet_opt = rcu_dereference(inet->inet_opt);
fl4 = &fl->u.ip4; /* 获取skb中的路由缓存 */
rt = skb_rtable(skb); /* skb中有缓存则跳转处理 */
if (rt)
goto packet_routed; /* Make sure we can route this packet. */
/* 检查控制块中的路由缓存 */
rt = (struct rtable *)__sk_dst_check(sk, );
/* 缓存过期 */
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中 */
skb_dst_set_noref(skb, &rt->dst); 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头 */
skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : ));
skb_reset_network_header(skb); /* 构造ip头 */
iph = ip_hdr(skb);
*((__be16 *)iph) = htons(( << ) | ( << ) | (inet->tos & 0xff));
if (ip_dont_fragment(sk, &rt->dst) && !skb->ignore_df)
iph->frag_off = htons(IP_DF);
else
iph->frag_off = ;
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 >> ;
ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, );
} /* 设置id */
ip_select_ident_segs(net, skb, sk,
skb_shinfo(skb)->gso_segs ?: ); /* 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;
}
 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 : ));
skb_reset_network_header(skb);
iph = ip_hdr(skb);
iph->version = ;
iph->ihl = ;
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 = ;
} else {
iph->frag_off = ;
__ip_select_ident(net, iph, );
} /* 选项 */
if (opt && opt->opt.optlen) {
iph->ihl += opt->opt.optlen>>;
ip_options_build(skb, &opt->opt, daddr, rt, );
} /* QOS优先级 */
skb->priority = sk->sk_priority;
skb->mark = sk->sk_mark; /* Send it out. */
/* 输出 */
return ip_local_out(net, skb->sk, skb);
}
 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 = ;
ipc.ttl = ;
ipc.tos = -; /* 选项存在 */
if (replyopts.opt.opt.optlen) {
ipc.opt = &replyopts.opt; /* 源路由存在,设置下一跳ip地址为目的地址 */
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,
arg->uid);
security_skb_classify_flow(skb, flowi4_to_flowi(&fl4));
rt = ip_route_output_key(net, &fl4);
if (IS_ERR(rt))
return; /* 根据skb更新sk的属性 */
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;
sk->sk_mark = fl4.flowi4_mark;
/* 数据追加到前一个skb或者新建skb后添加到发送队列 */
err = ip_append_data(sk, &fl4, ip_reply_glue_bits, arg->iov->iov_base,
len, , &ipc, &rt, MSG_DONTWAIT);
if (unlikely(err)) {
ip_flush_pending_frames(sk);
goto out;
} /* 如果发送队列有skb,则计算校验和,发送 */
nskb = skb_peek(&sk->sk_write_queue);
if (nskb) {
if (arg->csumoffset >= )
*((__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);
}

TCP->IP输出 之 ip_queue_xmit、ip_build_and_send_pkt、ip_send_unicast_reply的更多相关文章

  1. TCP/IP协议栈在Linux内核中的运行时序分析

    网络程序设计调研报告 TCP/IP协议栈在Linux内核中的运行时序分析 姓名:柴浩宇 学号:SA20225105 班级:软设1班 2021年1月 调研要求 在深入理解Linux内核任务调度(中断处理 ...

  2. TCP/IP协议学习(五) 基于C# Socket的C/S模型

    TCP/IP协议作为现代网络通讯的基石,内容包罗万象,直接去理解理论是比较困难的:然而通过实践先理解网络通讯的理解,在反过来理解学习TCP/IP协议栈就相对简单很多.C#通过提供的Socket API ...

  3. TCP/IP详解 (转)

    TCP/IP详解学习笔记(1)-基本概念 为什么会有TCP/IP协议 在世界上各地,各种各样的电脑运行着各自不同的操作系统为大家服务,这些电脑在表达同一种信息的时候所使用的方法是千差万别.就好像圣经中 ...

  4. 『TCP/IP详解——卷一:协议』读书笔记——18

    2013-08-27 15:44:52 第7章 Ping程序 7.1 引言 “ping”这个名字来源于声纳定为操作.Ping程序由Mike Muuss编写,目的是为了测试另一台主机是否可达.该程序发送 ...

  5. 『TCP/IP详解——卷一:协议』读书笔记——14

    2013-08-25 11:32:06 第5章 RARP:逆地址解析协议 5.1 引言 具有本地磁盘的系统引导时,一般是从磁盘上的配置文件中读取IP地址.但是无盘机,如X终端或无盘工作站,则需要采用其 ...

  6. TCP/IP详解 笔记十四

    TCP/IP协议(二)  连接的建立与终止 tcpdump -S输出TCP报文的格式 格式: 源>目的:标志 (标志就是tcp头部).标识首字符意义如下: 例如:telnet 某服务的输出(包括 ...

  7. TCP/IP详解

    第一篇 TCPIP协议详解 第1章 TCPIP协议族 第2章 IP协议详解 第3章 TCP协议详解 第4章 TCP/IP通信案例:访问Internet上的Web服务器 一.TCP/IP协议族 TCP/ ...

  8. OSI七层&TCP&IP协议

    OSI七层: OSI七层与ICP/IP概念层的对应: ICP/IP概念层上的网络设备: IP(Internet Protocol网际协议):计算机之间的通信 IP(网络协议)位于网络层,作用是把各种数 ...

  9. TCP IP详解(转)

    大学学习网络基础的时候老师讲过,网络由下往上分为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. 网络七层协议简称OSI.TCP/IP刨除了物理层,并把上三层(会话层.表示层和应用层)统称 ...

  10. TCP/IP协议学习(四) 协议概述

    生活中有舒适区,借口成为懒惰的护身符,学习也有舒适区,逃避便是阻止进步的最大障碍. 经过半年多嵌入式方面的工作和学习,我提高了很多,但同时我也对自己所面临的问题逐渐清晰: 1. 偏于实践,理论基础不牢 ...

随机推荐

  1. shell 中的 set -e 和 set +e的区别

    区别: set -e : 执行的时候如果出现了返回值为非零,整个脚本 就会立即退出 set +e: 执行的时候如果出现了返回值为非零将会继续执行下面的脚本 set -e 命令用法总结如下:1. 当命令 ...

  2. beego中获取url以及参数的方式

    以下都全默认在controller下执行 获取当前请求的referer fmt.Println(this.Ctx.Request.Referer()) 输出:http://localhost:8080 ...

  3. 在已有lnmp环境的基础上安装PHP7

    Centos7.6系统 已经安装lnmp一键环境 想装个apache跑php7, apache安装在这 https://www.cnblogs.com/lz0925/p/11227063.html 要 ...

  4. SQL语句复习【专题六】

    SQL语句复习[专题六] 用户 + 创建表  --创建一个新的用户,然后给新的用户创建一张表,然后给表中添加一些数据.查询表中的数据 --创建用户需要比较大的权限 DBA create user di ...

  5. Toast的基本用法 吐司打印

    //Toast.makeText(上下文,内容,显示时间);Toast toast =Toast.makeText(this,"位置="+position+"内容=&qu ...

  6. Nginx常见错误及处理方法(转)

    404 bad request 一般原因:请求的Header过大 解决方法:配置nginx.conf相关设置 client_header_buffer_size 16k; large_client_h ...

  7. Solr初步

    Solr是一个独立的,基于Lucene的全文搜索服务器企业级搜索应用服务器,它对外提供API接口.用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引(solr生成倒排索引,数 ...

  8. UVa540 Team Queue(队列queue)

    队列 STL队列定义在头文件<queue>中, 用“ queue<int>s ” 方式定义, 用push()和pop()进行元素的入队和出队操作, front()取队首元素(但 ...

  9. 使用python获得屏幕截图并保存为位图文件

    直接上代码: import win32gui import win32ui from ctypes import windll import Image hwnd = win32gui.FindWin ...

  10. NTP服务及时间同步

    环境: centos7 server   192.168.2.171 client    192.168.2.173.192.168.2.174 整体思路:173.174同步171的时间,171定时同 ...