IP 层收发报文简要剖析2--ip报文的输入ip_local_deliver
ip报文根据路由结果:如果发往本地则调用ip_local_deliver处理报文;如果是转发出去,则调用ip_forward
处理报文。
一、ip报文转发到本地:
/*
* Deliver IP Packets to the higher protocol layers.
*/
/*
* 在ip_route_input_noref进行路由选择后,如果接收的包
* 是发送给本机,则调用ip_local_deliver来传递给上层协议
*/
int ip_local_deliver(struct sk_buff *skb)
{
/*
* Reassemble IP fragments.
*/
struct net *net = dev_net(skb->dev);
/*
* frag_off是16位,其中高3位用作标志位,
* 低13位才是真正的偏移量.
* 内核可通过设置的分片标识位或非0
* 的分片偏移量识别分片的分组。偏移
* 量字段为0,表明这是分组的最后一个分片。
*
* 如果接收到的IP数据包时分片,则调用
* ip_defrag()进行重组,其标志位IP_DEFRAG_LOCAL_DELIVER。
*/
if (ip_is_fragment(ip_hdr(skb))) {
/*
* 重新组合分片分组的各个部分。
*
* 如果ip_defrag()返回非0,则表示IP数据包分片
* 尚未到齐,重组没有完成,或者出错,直接
* 返回。为0,则表示已完成IP数据包的重组,
* 需要传递到传输层进行处理。
*/
if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
return 0;
}
/*
* 经过netfilter处理后,调用ip_local_deliver_finish(),
* 将组装完成的IP数据包传送到传输层处理
*/
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
net, NULL, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
//从这里进入L4传输层
/*
* ip_local_deliver_finish()将输入数据包从网络层传递
* 到传输层。过程如下:
* 1)首先,在数据包传递给传输层之前,去掉IP首部
* 2)接着,如果是RAW套接字接收数据包,则需要
* 复制一份副本,输入到接收该数据包的套接字。
* 3)最后,通过传输层的接收例程,将数据包传递
* 到传输层,由传输层进行处理。
*/
/*
ip 层处理报文过程中,回复制一份报文到raw_socket中去;有的是IPPROTO_TCP/IPPROTO_RAW
当 socket(AF_INET, SOCK_RAW, IPPROTO_RAW)时,它会接收所有协议的数据包,并且
IP_HDRINCL 是默认打开的,即是说应用层要提供 L3 和 L4 层的头。再如,如果是
IPPROTO_TCP 时,它只接收到 TCP 包。而 IP_HDRINCL 是默认不打开的,即系统会处理 L3
的头部
*/
static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
/*
* 在数据包传递给传输层之前,先去掉
* IP首部。
*/
__skb_pull(skb, skb_network_header_len(skb)); rcu_read_lock();
{
int protocol = ip_hdr(skb)->protocol;
const struct net_protocol *ipprot;
int raw;
/*
* 处理RAW套接字,先根据传输层协议号
* 得到哈希值,然后查看raw_v4_htable散列表
* 中以该值为关键字的哈希桶是否为空,
* 如果不为空,则说明创建了RAW套接字,
* 复制该数据包的副本输入到注册到
* 该桶中的所有套接字。
*/
/*
ip_local_deliver_finish函数会先检查哈希表raw_v4_htable。
因为在创建 socket时,inet_create会把协议号IPPROTO_ICMP的值赋给socket的成员num,
并以num为键值,把socket存入哈 项表raw_v4_htable?瑀aw_v4_htable[IPPROTO_ICMP&(MAX_INET_PROTOS-1)]上即存放了 这个socket,实际上是一个socket的链表,
如果其它还有socket要处理这个回显应答,也会被放到这里,组成一个链 表,
ip_local_deliver_finish收到数据报后,取出这个socket链表(目前实际上只有一项),
调用raw_v4_input,把 skb交给每一个socket进行处理。
然后,还需要把数据报交给inet_protos[IPPROTO_ICMP& (MAX_INET_PROTOS-1)],即icmp_rcv处理,
因为对于icmp报文,每一个都是需要经过协议栈处理的,
但对回显应 答,icmp_rcv只是简单丢弃,并未实际处理。
*/
resubmit:
//之前开巨帧的时候,icmp不通就是在这里面的函数中sock_queue_rcv_skb丢的
raw = raw_local_deliver(skb, protocol);
//如果是raw套接字,则则该函数里面会复制一份skb,然后送到 ,例如用ping 1.2.2.2的时候,会走这里面,不会走icmp_recv*/
ipprot = rcu_dereference(inet_protos[protocol]);
if (ipprot) {
int ret;
/*
* 通过查找inet_portos数组,确定是否
* 注册了与IP首部中传输层协议号
* 一致的传输层协议。若查找命中,
* 则执行对应的传输层协议例程。
*/
if (!ipprot->no_policy) {
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
goto out;
}
nf_reset(skb);
}
ret = ipprot->handler(skb);//这里面会进入udp tcp传输层
if (ret < 0) {
protocol = -ret;
goto resubmit;
}
__IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
} else {
if (!raw) {
/*
* 如果没有响应的协议传输层接收该数据包,
* 则释放该数据包。在释放前,如果是RAW
* 套接字没有接收或接收异常,则还需产生
* 一个目的不可达ICMP报文给发送方。表示该包raw没有接收并且inet_protos中没有注册该协议
*/
if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
__IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS);
icmp_send(skb, ICMP_DEST_UNREACH,
ICMP_PROT_UNREACH, 0);
}
kfree_skb(skb);
} else {
__IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
consume_skb(skb);
}
}
}
out:
rcu_read_unlock(); return 0;
}
ipprot = rcu_dereference(inet_protos[protocol]);
if (ipprot){.........}
它首先查找 inet_protos 数组,看有没有相关的注册的协议,如果有,则执行它的处理例程
在 inet_init()的时候,系统会注册几个常用的 L4 层协议:
if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");
if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");
if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0);
printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");
其中,协议的结构如下:
/* This is used to register protocols. */
struct net_protocol {
void (*early_demux)(struct sk_buff *skb);
/* 分组将传递到该函数进行进一步处理*/
/*
* 传输层协议数据包接收处理函数指针,当网络层接收IP数据包
* 之后,根据IP数据包所指示传输层协议,调用对应传输层
* net_protocol结构的该例程接收报文。
* TCP协议的接收函数为tcp_v4_rcv(),UDP协议的接收函数为
* udp_rcv(),IGMP协议为igmp_rcv(),ICMP协议为icmp_rcv()。
*/
int (*handler)(struct sk_buff *skb);
/*
* 在接收到ICMP错误信息并需要传递到更高层时,
* 调用该函数
*/
/*
* 在ICMP模块中接收到差错报文后,会解析差错报文,并根据
* 差错报文中原始的IP首部,调用对应传输层的异常处理
* 函数err_handler。TCP协议为tcp_v4_err(),UDP为
* udp_err(),IGMP则无。
*/
void (*err_handler)(struct sk_buff *skb, u32 info);
/*
* no_policy标识在路由时是否进行策略路由。TCP和UDP默认不进行
* 策略路由。
*/
unsigned int no_policy:1,
netns_ok:1,
/* does the protocol do more stringent
* icmp tag validation than simple
* socket lookup?
*/
icmp_strict_tag_validation:1;
};
关键之处在于 handler 域,它用于将数据包上传给 L4 层处理
以tcp协议为为例:
/*ipv4_specific是TCP传输层到网络层数据发送以及TCP建立过程的真正OPS,
在tcp_prot->init中被赋值给inet_connection_sock->icsk_af_ops
这里面有每种协议传输层的接收函数,后面的inetsw_array那几行是套接口层的相关函数
在函数中执行handler,见函数ip_local_deliver_finish
family协议族通过sock_register注册 传输层接口tcp_prot udp_prot netlink_prot等通过proto_register注册
IP层接口通过inet_add_protocol(&icmp_protocol等注册 ,这些组成过程参考inet_init函数
IP层处理完后(包括ip_local_deliver_finish和icmp_unreach),走到这里,
这是IP层和传输层的邻借口,然后在由这里走到tcp_prot udp_prot raw_prot
这些是传输层的接收处理过程,传输层和套接口层的处理过程需
要使用udp_prot tcp_prot raw_prot过渡到socket层,处理过程参考inetsw_array
*/static const struct net_protocol tcp_protocol = {
.early_demux = tcp_v4_early_demux,
.handler = tcp_v4_rcv,/*当接收到报文后,ip层处理完后
在ip_local_deliver_finish 函数中ret = ipprot->handler(skb);走到这里
从这里面跳转到tcp_prot*/
.err_handler = tcp_v4_err,/*icmp_unreach当收到ICMP差错报文后,
如果引起差错的是TCP包就走到该函数*/
.no_policy = 1,
.netns_ok = 1,
.icmp_strict_tag_validation = 1,
};
IP 层收发报文简要剖析2--ip报文的输入ip_local_deliver的更多相关文章
- IP 层收发报文简要剖析6--ip报文输出3 ip_push_pending_frames
L4层的协议会把数据通过ip_append_data或ip_append_page把数据线放在缓冲区,然后再显示调用ip_push_pending_frames传送数据. 把数据放在缓冲区有两个优点, ...
- IP 层收发报文简要剖析5--ip报文发送2
udp 发送ip段报文接口ip_append_data ip_append_data 函数主要用来udp 套接字以及raw套接字发送报文的接口.在tcp中发送ack 以及rest段的ip_send_u ...
- IP 层收发报文简要剖析3--ip输入报文分片重组
在ip_local_deliver中,如果检测到是分片包,则需要将报文进行重组.其所有的分片被重新组合后才能提交到上层协议,每一个被重新组合的数据包文用ipq结构实例来表示 struct ipq { ...
- IP 层收发报文简要剖析1-ip报文的输入
ip层数据包处理场景如下: 网络层处理数据包文时需要和路由表以及邻居系统打交道.输入数据时,提供输入接口给链路层调用,并调用传输层的输入接口将数据输入到传输层. 在输出数据时,提供输出接口给传输层,并 ...
- IP 层收发报文简要剖析4--ip 报文发送
无论是从本地输出的数据还是转发的数据报文,经过路由后都要输出到网络设备,而输出到网络设备的接口就是dst_output(output)函数 路由的时候,dst_output函数设置为ip_output ...
- IP 层收发报文简要剖析6--ip_forward 报文转发
//在函数ip_route_input_slow->ip_mkroute_input注册, /* * IP数据包的转发是由ip_forward()处理,该函数在ip_rcv_finish() * ...
- TCP层的分段和IP层的分片之间的关系 & MTU和MSS之间的关系 (转载)
首先说明:数据报的分段和分片确实发生,分段发生在传输层,分片发生在网络层.但是对于分段来说,这是经常发生在UDP传输层协议上的情况,对于传输层使用TCP协议的通道来说,这种事情很少发生. 1,MTU( ...
- 原 TCP层的分段和IP层的分片之间的关系 & MTU和MSS之间的关系
首先说明:数据报的分段和分片确实发生,分段发生在传输层,分片发生在网络层.但是对于分段来说,这是经常发生在UDP传输层协议上的情况,对于传输层使用TCP协议的通道来说,这种事情很少发生. 1,MTU( ...
- Linux 网卡驱动学习(六)(应用层、tcp 层、ip 层、设备层和驱动层作用解析)
本文将介绍网络连接建立的过程.收发包流程,以及当中应用层.tcp层.ip层.设备层和驱动层各层发挥的作用. 1.应用层 对于使用socket进行网络连接的server端程序.我们会先调用socket函 ...
随机推荐
- 空间视频和GIS
摘要. GIS的空间数据基本单位表示通常是根据 点,线和面.但是,另一种类型的空间数据正在变得越来越频繁 捕获的是视频,但在GIS中却被很大程度上忽略了.数字录像时 是现代社会中常见的一种媒介,包含多 ...
- 实验三 HTML表格和表单的制作
实验三 HTML表格和表单的制作 [实验目的] 1.掌握表格的创建.结构调整与美化方法: 2.熟悉表格与单元格的主要属性及其设置方法: 3.掌握通过表格来进行网页页面的布局方法. [实验环境] 连接互 ...
- 多测师讲解_ 高级自动化测试selenium_001基本学习
高级自动化测试python+selenium教程手册 --高级讲师肖sir 第 1 章webdriver 环境搭建好了,我们正式学习 selenium 的 webdriver 框架,它不像 QTP 之 ...
- MeteoInfoLab脚本示例:中尺度气旋散点图
全球长时间序列中尺度气旋数据(http://cioss.coas.oregonstate.edu/eddies/)有netCDF格式,散点数据类型,只有一个很大的维Nobs = 2590938.尝试读 ...
- 如何使用 Gin 和 Gorm 搭建一个简单的 API 服务 (二)
创建 API 我们之前已经跑过 Gin 框架的代码,现在是时候加些功能进去了. 读取全部信息 我们先从"增删改查"中的"查"入手,查询我们之前添加的信息.我接下来要删除几行代码,并把 Gin ...
- asp.net 获取网站根目录总结 Global.asax文件里获取获取网站根目录
1: 获取网站根目录的方法有几种如: Server.MapPath(Request.ServerVariables["PATH_INFO"])Server.MapPath(&qu ...
- mysql优化篇(基于索引)
在上一篇文章:Mysql索引(一篇就够le) 中介绍了索引的基本使用,分类和原理,也强烈建议先读Mysql索引(一篇就够le),然后继续本文的阅读 我们也知道mysql的优化可以从很多的方面进行,比如 ...
- 习题解答chapter04
题目: 实验:利用IDE的debug功能给例6.4和例6.6的new语句设置断点,使用单步调试(step into/step over)跟踪子类对象实例化(初始化)的执行顺序,并总结该过程.(教材:J ...
- 开源!一款功能强大的高性能二进制序列化器Bssom.Net
好久没更新博客了,我开源了一款高性能的二进制序列化器Bssom.Net和新颖的二进制协议Bssom,欢迎大家Star,欢迎参与项目贡献! Net开源技术交流群 976304396,禁止水,只能讨论技术 ...
- Java安全框架(一)Spring Security
Java安全框架(一)Spring Security 文章主要分三部分 1.Spring Security的架构及核心组件:(1)认证:(2)权限拦截:(3)数据库管理:(4)权限缓存:(5)自定 ...