IP 层收发报文简要剖析1-ip报文的输入
ip层数据包处理场景如下:

网络层处理数据包文时需要和路由表以及邻居系统打交道。输入数据时,提供输入接口给链路层调用,并调用传输层的输入接口将数据输入到传输层。
在输出数据时,提供输出接口给传输层,并调用链路层的输出接口将数据输出到链路层。在输入输出数据时,需要查找路由表 通过netfiler处理等操作。
一、ip数据报文输入 ip_rcv &ip_rcv_finish &ip_rcv_finish2
1、在inet_init中注册了类型为ETH_P_IP协议的数据包的回调ip_rcv
2、ip_rcv处理完成后交给 NETFILTER(PRE-ROUTING)过滤,再上递给 ip_rcv_finish(),
3、ip_rcv_finish根据 skb 包中的路由信息,决定这个数据包是转发还是上交给本机,由此产生两条路径,一为 ip_local_deliver(),另一个为ip_forward()
4、ip_local_deliver它首先检查这个包是否是一个分 片 包 , 如 果 是 , 它 要 调 动 ip_defrag() 将 分 片 重 装 , 然 后 再 次 将 包 将 给 NETFILTER(LOCAL_IN)过滤后,
再由 ip_local_deliver_finish()将数据上传到 L4 层,这样就完成了 IP 层的处理;它负责将数据上传
5、ip_forward(),它负责将数据转发,经由 NETFILTER(FORWARD)过滤后将给 ip_forward_finish(),然后调用 dst_output()将数据包发送出去。
/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
const struct iphdr *iph;
struct net *net;
u32 len; /* When the interface is in promisc. mode, drop all the crap
* that it receives, do not try to analyse it.
*/
/*数据包不是发给我们的,这里所说的“不属于”这个主机,
是指在这个包目标主机的MAC地址不是本机,而不是L3层的ip地址*/
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop; net = dev_net(dev);
__IP_UPD_PO_STATS(net, IPSTATS_MIB_IN, skb->len); skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb) {
__IP_INC_STATS(net, IPSTATS_MIB_INDISCARDS);
goto out;
} if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto inhdr_error; iph = ip_hdr(skb); /*
* RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
*
* Is the datagram acceptable?
*
* 1. Length at least the size of an ip header
* 2. Version of 4
* 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
* 4. Doesn't have a bogus length
*/ if (iph->ihl < 5 || iph->version != 4)
goto inhdr_error; BUILD_BUG_ON(IPSTATS_MIB_ECT1PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_1);
BUILD_BUG_ON(IPSTATS_MIB_ECT0PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_0);
BUILD_BUG_ON(IPSTATS_MIB_CEPKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_CE);
__IP_ADD_STATS(net,
IPSTATS_MIB_NOECTPKTS + (iph->tos & INET_ECN_MASK),
max_t(unsigned short, 1, skb_shinfo(skb)->gso_segs));
/*
对数据报的头长度进行检查
iph->ihl*4是20,是首部最长的长度,
此语句是说如果头部长度不能pull,则error
*/
if (!pskb_may_pull(skb, iph->ihl*4))
goto inhdr_error; iph = ip_hdr(skb);
/* 校验和错误 */
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
goto csum_error;
/* 取总长度 */
len = ntohs(iph->tot_len);
if (skb->len < len) {
__IP_INC_STATS(net, IPSTATS_MIB_INTRUNCATEDPKTS);
goto drop;
} else if (len < (iph->ihl*4))
goto inhdr_error; /* Our transport medium may have padded the buffer out. Now we know it
* is IP we can trim to the true length of the frame.
* Note this now means skb->len holds ntohs(iph->tot_len).
*/ /* 设置总长度为ip包的长度//根据ip包总长度,
重新计算skb的长度,去掉末尾的无用信息 */
if (pskb_trim_rcsum(skb, len)) {
__IP_INC_STATS(net, IPSTATS_MIB_INDISCARDS);
goto drop;
}
/*获取传输层的头部*/
skb->transport_header = skb->network_header + iph->ihl*4; /* Remove any debris in the socket control block */
//这里面后面会存ip填充信息
memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
IPCB(skb)->iif = skb->skb_iif; /* 保存输入设备信息 */ /* Must drop socket now because of tproxy. */
skb_orphan(skb);
/*
* 最后通过netfilter模块处理后,调用ip_rcv_finish()
* 完成IP数据包的输入。
*/
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
net, NULL, skb, dev, NULL,
ip_rcv_finish);//hook 在nf_register_hooks注册 csum_error:
__IP_INC_STATS(net, IPSTATS_MIB_CSUMERRORS);
inhdr_error:
__IP_INC_STATS(net, IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
out:
return NET_RX_DROP;
}
ip_rcv_finish()在ip_rcv()中当IP数据包经过netfilter模块 处理后被调用。完成的主要功能是,如 还没有为该数据包查找输入路由缓存,则 调用ip_route_input()为其查找输入路由缓存。
接着处理IP数据包首部中的选项,最后 根据输入路由缓存输入到本地或转发。
/*
* ip_rcv_finish()在ip_rcv()中当IP数据包经过netfilter模块
* 处理后被调用。完成的主要功能是,如果
* 还没有为该数据包查找输入路由缓存,则
* 调用ip_route_input()为其查找输入路由缓存。
* 接着处理IP数据包首部中的选项,最后
* 根据输入路由缓存输入到本地或转发发。
*/
static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
const struct iphdr *iph = ip_hdr(skb);
struct rtable *rt; /* if ingress device is enslaved to an L3 master device pass the
* skb to its handler for processing
*/
skb = l3mdev_ip_rcv(skb);
if (!skb)
return NET_RX_SUCCESS;
/*
启用了early_demux
skb路由缓存为空
skb的sock为空
不是分片包
*/
if (net->ipv4.sysctl_ip_early_demux &&
!skb_dst(skb) &&
!skb->sk &&
!ip_is_fragment(iph)) {
const struct net_protocol *ipprot;
int protocol = iph->protocol;
/* 找到上层协议 */
/* 获取协议对应的prot */
ipprot = rcu_dereference(inet_protos[protocol]);
/* 找到early_demux函数,如tcp_v4_early_demux */
if (ipprot && ipprot->early_demux) {
/* 调用该函数,将路由信息缓存到skb->refdst */
ipprot->early_demux(skb); /* must reload iph, skb->head might have changed */
/* 重新取ip头 */
iph = ip_hdr(skb);
}
} /*
* Initialise the virtual path cache for the packet. It describes
* how the packet travels inside Linux networking.
*/
/*
* 如果还没有为该数据包查找输入路由缓存,
* 则调用ip_route_input_noref()为其查找输入路由缓存。
* 若查找失败,则将该数据包丢弃。
*/
if (!skb_valid_dst(skb)) {
int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
iph->tos, skb->dev);
if (unlikely(err)) {
if (err == -EXDEV)
__NET_INC_STATS(net, LINUX_MIB_IPRPFILTER);
goto drop;
}
} #ifdef CONFIG_IP_ROUTE_CLASSID
if (unlikely(skb_dst(skb)->tclassid)) {
struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);
u32 idx = skb_dst(skb)->tclassid;
st[idx&0xFF].o_packets++;
st[idx&0xFF].o_bytes += skb->len;
st[(idx>>16)&0xFF].i_packets++;
st[(idx>>16)&0xFF].i_bytes += skb->len;
}
#endif
/*
* 根据长度判断IP首部中是否存在选项,如果有,
* 则调用ip_rcv_options()处理IP选项。
*/
if (iph->ihl > 5 && ip_rcv_options(skb))
goto drop;
/*
* 最后根据输入路由缓存决定输入到本地或
* 转发,最终前者调用ip_local_deliver(),后者调用
* ip_forward()。
* 对于输入到本地或转发的组播报文,在经过netfilter处理
* 之后会调用ip_rcv_finish()正式进入输入的处理。先调用
* ip_route_input()进行输入路由的查询,如果发现目的地址
* 为组播地址,就会按照组播地址的规则查找路由,查找
* 到组播的输入路由后,组播报文接收处理函数为ip_mr_input()。
*/
rt = skb_rtable(skb);
if (rt->rt_type == RTN_MULTICAST) {
__IP_UPD_PO_STATS(net, IPSTATS_MIB_INMCAST, skb->len);
} else if (rt->rt_type == RTN_BROADCAST) {
__IP_UPD_PO_STATS(net, IPSTATS_MIB_INBCAST, skb->len);
} else if (skb->pkt_type == PACKET_BROADCAST ||
skb->pkt_type == PACKET_MULTICAST) {
struct in_device *in_dev = __in_dev_get_rcu(skb->dev); /* RFC 1122 3.3.6:
*
* When a host sends a datagram to a link-layer broadcast
* address, the IP destination address MUST be a legal IP
* broadcast or IP multicast address.
*
* A host SHOULD silently discard a datagram that is received
* via a link-layer broadcast (see Section 2.4) but does not
* specify an IP multicast or broadcast destination address.
*
* This doesn't explicitly say L2 *broadcast*, but broadcast is
* in a way a form of multicast and the most common use case for
* this is 802.11 protecting against cross-station spoofing (the
* so-called "hole-196" attack) so do it for both.
*/
if (in_dev &&
IN_DEV_ORCONF(in_dev, DROP_UNICAST_IN_L2_MULTICAST))
goto drop;
}
/* 调用路由项的input函数,可能为ip_local_deliver或者ip_forward */
return dst_input(skb); drop:
kfree_skb(skb);
return NET_RX_DROP;
}
IP 层收发报文简要剖析1-ip报文的输入的更多相关文章
- 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 层收发报文简要剖析2--ip报文的输入ip_local_deliver
ip报文根据路由结果:如果发往本地则调用ip_local_deliver处理报文:如果是转发出去,则调用ip_forward 处理报文. 一.ip报文转发到本地: /* * Deliver IP Pa ...
- 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函 ...
随机推荐
- 分布式锁结合SpringCache
1.高并发缓存失效问题: 缓存穿透: 指查询一个一定不存在的数据,由于缓存不命中导致去查询数据库,但数据库也无此记录,我们没有将此次查询的null写入缓存,导致这个不存在的数据每次请求都要到存储层进行 ...
- CSP-S2020AFO记
2020-10.11 考初赛辣. 选择题考了一堆时间复杂度,一个不会(卒) 我寻思这01背包哪里能用贪心? 啊,这,这,这手写快排竟如此简单,手写取Max,手写队列,两个字符串颠来倒去,竟活到爆! 震 ...
- centos 开机启动服务 systemctl
systemctl 实现开机自启服务 转载起一个好听的名字 最后发布于2018-06-26 13:49:06 阅读数 13473 收藏 展开 systemctl是RHEL 7 的服务管理工具中主要的 ...
- deepin vue安装步骤
deepin安装node.js sudo wget https://nodejs.org/dist/v9.2.0/node-v9.2.0-linux-x64.tar.xz tar xJf node-v ...
- 【应用服务 App Service】发布到Azure上的应用显示时间不是本地时间的问题,修改应用服务的默认时区
问题情形 应用程序发布到App Service后,时间显示不是北京时间,默认情况为UTC时间,比中国时间晚 8 个小时. 详细日志 无 问题原因 Azure 上所有的服务时间都采用了 UTC 时间. ...
- PS编辑工具
3.1PS污点修复 (1)快捷键:J. (2)中括号可以改变笔触的大小,前中括号减小笔触,后中括号增加笔触. (3)可以用选区把需要修复的地方框选上,再进行修复,这样不会影响到未选区域. 3.2PS修 ...
- Qlik Sense学习笔记之插件开发
date: 2019-05-06 13:18:45 updated: 2019-08-09 15:18:45 Qlik Sense学习笔记之插件开发 1.开发前的基础工作 1.1 新建插件 dev-h ...
- selenium中的三种等待方式
1.强制等待,采用的time.sleep,然后后面加上要等待的时间: 2.隐性等待implicitly_wait,隐性等待设定后,会对之后的所有代码生效,会在设定的时间之内,不停的去查找元素,如果找到 ...
- Luogu P5072 [Ynoi2015]盼君勿忘
题意 给定一个长度为 \(n\) 的序列 \(a\) 和 \(m\) 次询问,第 \(i\) 次询问需要求出 \([l_i,r_i]\) 内所有子序列去重之后的和,对 \(p_i\) 取模. \(\t ...
- 通过Azure bot framework composer 设计一个AI对话机器人bot(查询天气)
本文介绍通过机器人框架设计器 (Bot framework composer)接近拖拉拽的方式设计一个聊天机器人,该聊天机器人的主要功能是发起http请求查询天气.当然,稍微变通下,可以用来查询几乎任 ...