数据包接收系列 — IP协议处理流程(一)
本文主要内容:在接收数据包时,IP协议的处理流程。
内核版本:2.6.37
Author:zhangskd @ csdn blog
IP报头
IP报头:
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4,
version:4;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u8 version:4, /* 协议版本,IPv4为4 */
ihl:4; /* 首部长度,不包括选项为5,表示20字节 */
#else
#error "Please fix <asm/byteorder.h>"
#endif __u8 tos; /* TOS服务类型,6位DSCP,2为ECN */
__be16 tot_len; /* IP包总长度,最大为65535 */
__be16 id; /* 标识符,同一个IP包的不同分片具有相同的标识符 */
__be16 frag_off; /* 3个标志位,13位偏移 */
__u8 ttl; /* 存活时间,一般为64跳 */
__u8 protocol; /* L4协议值 */
__sum16 check; /* 报头校验和,不包含载荷 */
__be32 saddr; /* 源IP */
__be32 daddr; /* 目的IP */
};
ip_rcv
调用ip_rcv()时skb中的一些变量:
ip_rcv()是IP层的入口,主要做了:
丢弃L2目的地址不是本机的数据包(这说明网卡处于混杂模式,嗅探器会处理这些包)。
检查skb的引用计数,如果大于1,说明其它地方也在使用此skb,则克隆一个skb返回;否则直接返回原来的skb。
数据包合法性检查:
data room必须大于IP报头长度。
IP报头长度至少是20,类型为IPv4。
data room至少能容纳IP报头(包括IP选项)。
检查IP报头校验和是否正确。
数据包没被截断(skb->len >= 报总长),报总长不小于20。
如果L2有进行填充(以太网帧最小长度为64),则把IP包裁剪成原大小,去除填充。此时如果接收的NIC
已计算出校验和,则让其失效,让L4自己重新计算。
最后,调用netfilter的NF_INET_PRE_ROUTING的钩子函数,如果此数据包被钩子函数放行,则调用
ip_rcv_finish()继续处理。
/* Main IP Receive routinue. */ int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
struct net_device *orig_dev)
{
struct iphdr *iph;
u32 len; /* When the interface is in promisc mode, drop all the crap that it receives,
* do not try to analyse it.
* 当数据帧的L2目的地址和接收接口的地址不同时,skb->pkt_type就被设成PACKET_OTHERHOST。
* 网卡本身会丢弃这些包,除非设成混杂模式。嗅探器自会处理这种包,IP层无需理会。
*/
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop; IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len); /* 如果此skb的引用计数大于1,说明在其它地方也被使用,则克隆一个skb返回。
* 否则直接返回原来的skb。
*/
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
goto out;
} /* 确保data room >= IP报头 */
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
*/ /* IP报头长度至少是20,类型为IPv4 */
if (iph->ihl < 5 || iph->version != 4)
goto inhdr_error; /* data room至少能容纳IP报头(包括IP选项) */
if (! pskb_may_pull(skb, iph->ihl * 4))
goto inhdr_error; iph = ip_hdr(skb); /* 检查IP报头校验和是否正确 */
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
goto inhdr_error; len = ntohs(iph->tot_len); /* IP报文总长度 */ /* L2为了满足最小帧的长度可能会进行填充,所以skb->len >= len。
* Ethernet数据帧的最小帧长度为64字节。
*/
if (skb->len < len) { /* 数据包被截断了 */
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
} 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).
*/ /* 如果L2有进行填充,则把IP包裁剪成原大小。
* 如果接收的NIC已计算出校验和,则让其失效,让L4自己重新计算。
*/
if (pskb_trim_rcsum(skb, len)) {
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
goto drop;
} /* Remove any debris in the socket control block */
memset(IPCB(skb), 0, sizeof(struct inet_skb_parm)); /* Must drop socket now because of tproxy. */
skb_orphan(skb); /* 调用netfilter的NF_INET_PRE_ROUTING钩子 */
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish); inhdr_error:
IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS); drop:
kfree_skb(skb); out:
return NET_RX_DROP;
}
如果skb的引用计数大于1,说明在其它地方也被使用,则克隆一个skb返回,否则直接返回原来的skb。
/**
* skb_shard_check - check if buffer is shard and if so clone it
* @skb: buffer to check
* @pri: priority for memory allocation
*
* If the buffer is shared the buffer is cloned and the old copy drops a
* reference. A new clone with a single reference is returned.
* If the buffer is not shared the original buffer is returned. When being called
* from interrupt status or with spinlocks held pri must be GFP_ATOMIC.
* NULL is returned on a memory allocation failure.
*/ static inline struct sk_buff *skb_shared_check(struct sk_buff *skb, gfp_t pri)
{
/* 不能睡眠,否则调用might_sleep()打印栈的回溯信息 */
might_sleep_if(pri & __GFP_WAIT); if (skb_shared(skb)) { /* skb->users是否为1 */
struct sk_buff *nskb = skb_clone(skb, pri);
kfree_skb(skb);
skb = nskb;
} return skb;
}
/**
* skb_orphan - orphan a buffer
* @skb: buffer to orphan
* If a buffer currently has an owner then we call the owner's destructor
* function and make the @skb unowned. The buffer continues to exist
* but is no longer charged to its former owner.
*/
static inline void skb_orphan(struct sk_buff *skb)
{
if (skb->destructor)
skb->destructor(skb); skb->destructor = NULL;
skb->sk = NULL;
}
ip_rcv_finish
ip_rcv_finish()主要做了:
查找路由,决定要把数据包发送到哪,赋值skb_dst()->input(),发往本地为ip_local_deliver,转发为ip_forward()。
更新Traffic Control (Qos)层的统计数据。
处理IP选项,检查选项是否正确,然后将选项存储在IPCB(skb)->opt中。
最后执行skb_dst()->input(),要么发往四层,要么进行转发,取决于IP的目的地址。
static int ip_rcv_finish(struct sk_buff *skb)
{
const struct iphdr *iph = ip_hdr(skb);
struct rtable *rt; /*
* Initialise the virtual path cache for the packet.
* It describes how the packet travels inside linux networking.
*/
if (skb_dst(skb) == NULL) { /* 查找路由,决定要把包送往哪里 */
int err = ip_route_input_noref(skb, iph->daddr, iph->saddr, iph->tos, skb->dev); if (unlikely(err)) {
if (err == -EHOSTUNREACH) /* no route to host,主机不可达 */
IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INADDRERRORS);
else if (err == -ENETUNREACH) /* Network is unreachable,网络不可达 */
IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INNOROUTES);
else if (err == -EXDEV) /* Cross-device link */
NET_INC_STATS_BH(dev_net(skb->dev), LINUX_MIB_IPRPFILTER); goto drop; /* 目的地不可达,丢弃 */
}
} /* 更新Traffic Control (Qos)层的统计数据 */
#ifdef CONFIG_NET_CLS_ROUTE
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_options_compile()来检查选项是否正确,然后将选项存储
* 在IPCB(skb)->opt中。
*/
if (iph->ihl > 5 && ip_rcv_options(skb))
goto drop; rt = skb_rtable(skb);
if (rt->rt_type == RTN_MULTICAST) {
IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST, skb->len);
} else if (rt->rt_type == RTN_BROADCAST)
IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST, skb->len); /* skb_dst(skb)->input()在ip_route_input_noref()中被赋值,要么是ip_local_deliver(),
* 要么是ip_forward(),取决于数据包的目的地址。
*/
return dst_input(skb); drop:
kfree_skb(skb);
return NET_RX_DROP;
} /* Input packet from network to transport. */
static inline int dst_input(struct sk_buff *skb)
{
return skb_dst(skb)->input(skb);
}
数据包接收系列 — IP协议处理流程(一)的更多相关文章
- 数据包接收系列 — IP协议处理流程(二)
本文主要内容:在接收数据包时,IP协议的处理流程. 内核版本:2.6.37 Author:zhangskd @ csdn blog 我们接着来看数据包如何发往本地的四层协议. ip_local_del ...
- 数据包接收系列 — NAPI的原理和实现
本文主要内容:简单分析NAPI的原理和实现. 内核版本:2.6.37 Author:zhangskd @ csdn 概述 NAPI是linux新的网卡数据处理API,据说是由于找不到更好的名字,所以就 ...
- Linux内核二层数据包接收流程
本文主要讲解了Linux内核二层数据包接收流程,使用的内核的版本是2.6.32.27 为了方便理解,本文采用整体流程图加伪代码的方式从内核高层面上梳理了二层数据包接收的流程,希望可以对大家有所帮助.阅 ...
- 从网卡发送数据再谈TCP/IP协议—网络传输速度计算-网卡构造
在<在深谈TCP/IP三步握手&四步挥手原理及衍生问题—长文解剖IP>里面提到 单个TCP包每次打包1448字节的数据进行发送(以太网Ethernet最大的数据帧是1518字节,以 ...
- wireshark抓包分析——TCP/IP协议
本文来自网易云社区 当我们需要跟踪网络有关的信息时,经常会说"抓包".这里抓包究竟是什么?抓到的包又能分析出什么?在本文中以TCP/IP协议为例,简单介绍TCP/IP协议以及如何通 ...
- linux 内核网络数据包接收流程
转:https://segmentfault.com/a/1190000008836467 本文将介绍在Linux系统中,数据包是如何一步一步从网卡传到进程手中的. 如果英文没有问题,强烈建议阅读后面 ...
- [转帖]IP /TCP协议及握手过程和数据包格式中级详解
IP /TCP协议及握手过程和数据包格式中级详解 https://www.toutiao.com/a6665292902458982926/ 写的挺好的 其实 一直没闹明白 网络好 广播地址 还有 网 ...
- 协议系列之TCP/IP协议
根据前面介绍的几种协议,将IP协议.TCP协议.UDP协议组合起来,于是便有了TCP/IP协议.现在很多的应用的通信都是建立在TCP/IP协议的基础上,运用非常广泛,很有必要对其学习一下. 打个不太恰 ...
- WireShark——IP协议包分析(Ping分析IP协议包)
互联网协议 IP 是 Internet Protocol 的缩写,中文缩写为“网协”.IP 协议是位于 OSI 模型中第三层的协议,其主要目的就是使得网络间能够互联通信.前面介绍了 ARP 协议, 该 ...
随机推荐
- Github上的Android项目介绍之ListViewAnimation(针对listView item的侧滑菜单)(1)
demo源码,需要可以下载 1.这是一个github开源项目,先去github上面下载,github下载地址. 2.将SwipeMenuListView项目,导入,然后新建项目如果要引用,要设置为相应 ...
- x264源代码简单分析:熵编码(Entropy Encoding)部分
===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...
- python+OpenCV 特征点检测
1.Harris角点检测 Harris角点检测算法是一个极为简单的角点检测算法,该算法在1988年就被发明了,算法的主要思想是如果像素周围显示存在多于一个方向的边,我们认为该点为兴趣点.基本原理是根据 ...
- Cocos2D在新版Swift中常量枚举值引用代码的修改
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 我们知道在SpriteBuilder中是无法直接给一个CCB文 ...
- iOS开发之Xcode常用调试(Debug)技巧总结
一.Xcode调试技巧之:NSLog 上面也提到了,在我们日常的开发过程中最常见的Debug方式就是打Log.而在OC语言中,打Log是采用NSLog方法.但是NSLog效率低下,具体原因可以看这篇博 ...
- iOS7 CookBook精彩瞬间(一)property、selector细节、__unused
1.我们常常使用nonatomic,很多人只知道它的效率较高,却不知道其含义,其含义是非线程安全的,也就是说多线程修改时不加锁,可能出现多个线程先后修改而成为脏数据的情况. 2.unsafe_unre ...
- JVM学习之-栈
JVM栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;JVM堆解决的是数据存储的问题,即数据怎么放.放在哪儿,另外JVM堆中存的是对象.JVM栈中存的是基本数据类型和JVM堆中对象的引用. ...
- Android多点触摸缩放图片-android学习之旅(四)
获取多触摸点 核心代码: 获取触摸点的个数和位置 public boolean onTouch(View v, MotionEvent event) { switch (event.getAction ...
- Volley,小并发网络请求的好帮手
不得不说,当不了解一件事情的时候,就会像当然的认为,其很神秘.但是当真正的接触到了这些神秘的item,就不会有这种感觉了.作为一个android开发新手的我,刚接触到了Volley这个开源的网络请求框 ...
- C++之多态性与虚函数
面向对象程序设计中的多态性是指向不同的对象发送同一个消息,不同对象对应同一消息产生不同行为.在程序中消息就是调用函数,不同的行为就是指不同的实现方法,即执行不同的函数体.也可以这样说就是实现了&quo ...