Linux内核IP层的报文处理流程(一)
本文主要讲解了Linux内核IP层的整体架构和对从网卡接受的报文处理流程,使用的内核的版本是2.6.32.27
为了方便理解,本文采用整体流程图加伪代码的方式对Linxu内核中IP整体实现架构和对网卡报文的处理流程进行了讲解,希望可以对大家有所帮助。阅读本文章假设大家对C语言有了一定的了解
IP层的整体实现架构
IP层接受底层数据报文的处理流程
- /*
- * 在NET_RX_SOFTIRQ软中后,由ETH_P_IP触发的ipv4协议入口函数
- */
- int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
- {
- /*
- * 过滤掉送往其他主机的数据包(这时网卡正在处于混杂模式)
- */
- if (skb->pkt_type == PACKET_OTHERHOST)
- goto drop;
- iph = ip_hdr(skb);
- /*头的长度是否至少是IP头长度(5); 是否是IPV4报文*/
- if (iph->ihl < 5 || iph->version != 4)
- goto inhdr_error;
- /*IP头长度是否正确,不是伪造的长度*/
- 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 inhdr_error;
- len = ntohs(iph->tot_len);
- if (skb->len < len) {
- IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
- goto drop;
- } else if (len < (iph->ihl*4))
- goto inhdr_error;
- /*实际尺寸不匹配套接字缓冲(skb->len)中维护的信息,则调用skb_trim调整数据包的长度*/
- if (pskb_trim_rcsum(skb, len)) {
- IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
- goto drop;
- }
- /*调用IP_PRE_ROUTING(NF_INET_PRE_ROUTING)上注册的钩子,
- *在调用钩子处理完之后,调用钩子处理完成之后,调用ip_rcv_finish
- * 后面讲防火墙的时候,我们会仔细梳理*/
- return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
- }
- /* NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish)*/
- #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \
- NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)
- {
- int __ret; \
- if ((__ret=nf_hook_thresh(pf, hook, (skb), indev, outdev, okfn, thresh, 1)) == 1)\
- __ret = (okfn)(skb); \
- __ret;
- }
- static inline int nf_hook_thresh(u_int8_t pf, unsigned int hook,
- struct sk_buff *skb,
- struct net_device *indev,
- struct net_device *outdev,
- int (*okfn)(struct sk_buff *), int thresh,
- int cond)
- {
- /*逐个调用注册的防火墙钩子*/
- return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh);
- }
- /*
- * 接收完数据包后的后续处理函数
- */
- static int ip_rcv_finish(struct sk_buff *skb)
- {
- const struct iphdr *iph = ip_hdr(skb);
- struct rtable *rt;
- /*
- * 激活ip_route_input,确定报文的路由,如果ip_route_input无法从FIB中找到路由
- * 则丢弃数据报文,ip_route_input将在IP路由中的专题中进行讲解
- */
- if (skb_dst(skb) == NULL) {
- int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);
- if (unlikely(err)) {
- goto drop;
- }
- }
- /*检查IP报头里面是否含有选项,如果含有建立ip_options*/
- if (iph->ihl > 5 && ip_rcv_options(skb))
- goto drop;
- /*根据dst_entry的结果,使用skb_dst(skb)->input(skb)进行IP的路由选择
- *传递给本地计算机的单播或多播,进入 ip_local_deliver();
- *单播转发的报文进入ip_forward()
- *多播转发进入ip_mr_input()
- */
- return dst_input(skb);
- {
- skb_dst(skb)->input(skb)
- }
- drop:
- kfree_skb(skb);
- return NET_RX_DROP;
- }
- /*目的地分发策略的注册*/
- static int __mkroute_input(struct sk_buff *skb,
- struct fib_result *res,
- struct in_device *in_dev,
- __be32 daddr, __be32 saddr, u32 tos,
- struct rtable **result)
- {
- //.......
- rth->u.dst.input = ip_forward;
- rth->u.dst.output = ip_output;
- //......
- }
- static int __mkroute_output(struct rtable **result,
- struct fib_result *res,
- const struct flowi *fl,
- const struct flowi *oldflp,
- struct net_device *dev_out,
- unsigned flags)
- {
- //......
- if (flags & RTCF_LOCAL) {
- rth->u.dst.input = ip_local_deliver;
- rth->rt_spec_dst = fl->fl4_dst;
- }
- if (flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
- rth->rt_spec_dst = fl->fl4_src;
- if (flags & RTCF_LOCAL &&
- !(dev_out->flags & IFF_LOOPBACK)) {
- rth->u.dst.output = ip_mc_output;
- RT_CACHE_STAT_INC(out_slow_mc);
- }
- #ifdef CONFIG_IP_MROUTE
- if (res->type == RTN_MULTICAST) {
- if (IN_DEV_MFORWARD(in_dev) &&
- !ipv4_is_local_multicast(oldflp->fl4_dst)) {
- rth->u.dst.input = ip_mr_input;
- rth->u.dst.output = ip_mc_output;
- }
- }
- #endif
- }
- //......
- }
如果IP报文需要转发,那么分析流程如下
- //-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
- /*单播转发处理,负责处理转发相关的所有动作*/
- int ip_forward(struct sk_buff *skb)
- {
- /*删除不是PACKET_HOST的数据包*/
- if (skb->pkt_type != PACKET_HOST)
- goto drop;
- /*TTL递减为1之间,丢弃该报,并返回ICMP_TIME_EXCEEDED*/
- if (ip_hdr(skb)->ttl <= 1)
- goto too_many_hops;
- /*如果skb->len大于MTU值,且Dont-Fragment被职位,则丢弃此报文,
- *并返回ICMP_FRAG_NEEDED*/
- if (unlikely(skb->len > dst_mtu(&rt->u.dst) && (ip_hdr(skb)->frag_off & htons(IP_DF)))) {
- icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(dst_mtu(&rt->u.dst)));
- goto drop;
- }
- /*检查是否有足够的空间用于输出网络设备中的MAC报头dst.header_len()
- *调用skb_cow来创建一个新的足够长的skb,并且拷贝原来的所有数据
- */
- if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))
- goto drop;
- iph = ip_hdr(skb);
- /*TTL减少1*/
- ip_decrease_ttl(iph);
- /*使用IP_FORWARD中注册的钩子函数,当防火墙中的钩子都与运行完成后,
- *进入ip_forward_finish*/
- return NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev, rt->u.dst.dev, ip_forward_finish);
- sr_failed:
- /*
- * Strict routing permits no gatewaying
- */
- icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
- goto drop;
- too_many_hops:
- /* Tell the sender its packet died... */
- IP_INC_STATS_BH(dev_net(skb_dst(skb)->dev), IPSTATS_MIB_INHDRERRORS);
- icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
- drop:
- kfree_skb(skb);
- return NET_RX_DROP;
- }
- /*
- * 该函数没有什么用途,除非启用了FASTROUTE,
- * 将处理后的函数报文送入output阶段
- */
- static int ip_forward_finish(struct sk_buff *skb)
- {
- struct ip_options * opt = &(IPCB(skb)->opt);
- /*使用ip_forward_options处理IP选项*/
- if (unlikely(opt->optlen))
- ip_forward_options(skb);
- /*送入到输出阶段*/
- return dst_output(skb);
- {
- skb_dst(skb)->output(skb);
- }
- }
- /*目的地分发策略的注册*/
- static int __mkroute_input(struct sk_buff *skb,
- struct fib_result *res,
- struct in_device *in_dev,
- __be32 daddr, __be32 saddr, u32 tos,
- struct rtable **result)
- {
- //.......
- rth->u.dst.input = ip_forward;
- rth->u.dst.output = ip_output;
- //......
- }
- int ip_output(struct sk_buff *skb)
- {
- struct net_device *dev = skb_dst(skb)->dev;
- /*将skb->dev指向输出设备的dev*/
- skb->dev = dev;
- /*设置2层包类型为ETH_P_IP*/
- skb->protocol = htons(ETH_P_IP);
- /*使用防火墙中的NF_IP_POST_ROUTING中注册的钩子函数进行处理,
- *处理完成之后进入ip_finish_output处理*/
- return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev,
- ip_finish_output,
- !(IPCB(skb)->flags & IPSKB_REROUTED));
- }
- /*判定是否进行IP分片*/
- static int ip_finish_output(struct sk_buff *skb)
- {
- /*如果报文尺寸大于MTU,则进行IP分片后送入ip_finish_output2
- *否则直接送入ip_finish_output2
- */
- if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb))
- return ip_fragment(skb, ip_finish_output2);
- else
- return ip_finish_output2(skb);
- }
- static const struct neigh_ops arp_generic_ops = {
- .family = AF_INET,
- .output = neigh_resolve_output,
- .hh_output = dev_queue_xmit,
- };
- static const struct neigh_ops arp_hh_ops = {
- .family = AF_INET,
- .output = neigh_resolve_output,
- .hh_output = dev_queue_xmit,
- };
- static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst, __be16 protocol)
- {
- struct hh_cache *hh;
- //......
- if (n->nud_state & NUD_CONNECTED)
- hh->hh_output = n->ops->hh_output; /*也就是dev_queue_xmit*/
- else
- hh->hh_output = n->ops->output;
- //......
- }
- static void neigh_suspect(struct neighbour *neigh)
- {
- //.....
- neigh->output = neigh->ops->output; /*也就是neigh_resolve_output*/
- }
- static inline int ip_finish_output2(struct sk_buff *skb)
- {
- /*如果2层头数据空间不够,则重新分配足够长度的SKB,并将数据复制到新的SKB后释放原来SKB*/
- if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
- struct sk_buff *skb2;
- skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
- if (skb->sk)
- skb_set_owner_w(skb2, skb->sk);
- kfree_skb(skb);
- skb = skb2;
- }
- /*如果路由出口项中已经含有2层包头缓存的引用(dst->hh),进入neigh_hh_output*/
- if (dst->hh)
- return neigh_hh_output(dst->hh, skb);
- /*如果没有dst->hh,有dst->neighbour,则启动地址解析协议,也就是neigh_resolve_output*/
- else if (dst->neighbour)
- return dst->neighbour->output(skb);
- }
- static inline int neigh_hh_output(struct hh_cache *hh, struct sk_buff *skb)
- {
- /*直接复制2层包头到套接字的包数据空间中*/
- memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);
- skb_push(skb, hh_len);
- /*调用hh->hh_output(skb),也就是dev_queue_xmit进行硬件发送*/
- return hh->hh_output(skb);
- }
如果IP是上送本地CPU的报文,处理流程如下
- //-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
- /*包的本地投递*/
- int ip_local_deliver(struct sk_buff *skb)
- {
- /*收集并组装IP分片,如果还没有收集完成,那么就等待IP分片组装完成*/
- if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
- if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
- return 0;
- }
- /*进入NF_IP_LOCAL_IN的过滤器处理,处理完成后进入ip_local_deliver_finish*/
- return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);
- }
- /*IP层处理完成后的协议分发函数*/
- static int ip_local_deliver_finish(struct sk_buff *skb)
- {
- resubmit:
- /*如果是RAW-IP报文,送往RAW-IP对应的处理???*/
- raw = raw_local_deliver(skb, protocol);
- /*MAX_INET_PROTOS-1 为IP报头中协议的模,
- *这里计算对应协议在ipprot中被散列的位置*/
- hash = protocol & (MAX_INET_PROTOS - 1);
- /*IP层上的ipprot负责管理所有的传输协议*/
- ipprot = rcu_dereference(inet_protos[hash]);
- /*如果找到相应的协议,那么调用对应的处理例程*/
- if (ipprot != NULL) {
- ret = ipprot->handler(skb);
- if (ret < 0) {
- protocol = -ret;
- goto resubmit;
- }
- }
- /*找不到相应的处理例程*/
- else {
- /*又是RAW-IP报文,会在RAW-IP处理例程???
- * 就丢弃,并想对端发送ICMP_DEST_UNREACH,ICMP_PROT_UNREACH*/
- if (!raw)
- {
- icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
- }
- kfree_skb(skb);
- }
- return 0;
- }
- static const struct net_protocol tcp_protocol = {
- .handler = tcp_v4_rcv, /*TCP*/
- };
- static const struct net_protocol udp_protocol = {
- .handler = udp_rcv, /*UDP*/
- };
- static const struct net_protocol icmp_protocol = {
- .handler = icmp_rcv, /*ICMP*/
- };
- static const struct net_protocol igmp_protocol = {
- .handler = igmp_rcv, /*IGMP*/
- };
通过上面的分析讲解,我们就可以很清楚的了解到IP是如何接受一个来自于网卡的数据包,并如何进行三层报文
关于二层数据报文的处理流程和是如何送到三层进行处理,请参考我前面的博客
希望大家批评指正
Linux内核IP层的报文处理流程(一)的更多相关文章
- IP 层收发报文简要剖析2--ip报文的输入ip_local_deliver
ip报文根据路由结果:如果发往本地则调用ip_local_deliver处理报文:如果是转发出去,则调用ip_forward 处理报文. 一.ip报文转发到本地: /* * Deliver IP Pa ...
- 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 层收发报文简要剖析6--ip报文输出3 ip_push_pending_frames
L4层的协议会把数据通过ip_append_data或ip_append_page把数据线放在缓冲区,然后再显示调用ip_push_pending_frames传送数据. 把数据放在缓冲区有两个优点, ...
- 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() * ...
- Linux 内核协议栈 学习资料
终极资料 1.<Understanding Linux Network Internals> 2.<TCP/IP Architecture, Design and Implement ...
- Linux内核网络报文简单流程
转:http://blog.csdn.net/adamska0104/article/details/45397177 Linux内核网络报文简单流程2014-08-12 10:05:09 分类: L ...
随机推荐
- [Unity3D]Unity3D发展偷看游戏初期阶段NGUI
朋友,大家晚上好. 我是秦培.欢迎关注我的博客,我的博客地址blog.csdn.net/qinyuanpei.近期博主開始研究NGUI了,由于NGUI是Unity3D中最为流行的界面插件,所以不管从学 ...
- win32 字体变换与窗口同大同小
#include <windows.h> #include "res/resource.h" LRESULT CALLBACK WinProc(HWND hwnd, U ...
- QEventLoop等待另外一个事件的停止,非常实用 good
void MyWidget::SendRequest(QString strUser) { network_manager = new QNetworkAccessManager(); connect ...
- js传真实地址 C:\fakepath
js给action传真是地址的时候,处于安全,传到action中 浏览器会改变路径变为C:\fakepath\ftp.txt,但是原始路径却是 C:\Documents and Settings\Ad ...
- 01-编写CMS注意事项
原文:01-编写CMS注意事项 1.将ThinkPHP核心文件放在项目目录,将下载的扩展包放在在ThinkPHP目录下的Extend文件夹中 2.设置整个项目的编码为utf-8 3.创建Public公 ...
- expect实现ssh自动登录
expect实现ssh自动登录 #!/usr/local/bin/expect set PASSWD [lindex $argv 1] set IP [lindex $argv 0] set CM ...
- 用代码定位硬盘上的文件(使用ShellExecute执行explorer /select命令,其它参数也很全)
问题:如何用代码控制资源浏览器,并定位到指定的文件? 答:使用ShellExecute,配合explorer即可 ShellExecute(Application.Handle, 'open', PC ...
- kill命令"-1"这个参数到底是杀进程还是reload?(转)
kill-1:重新读取一次参数的配置文件 (类似 reload) 这句话给我的感觉是把进程杀掉后重启进程,即 reload.而我查了下 man kill,-1 对应的 signal 是 SIGHUP, ...
- IOCP模型与网络编程
IOCP模型与网络编程 一.前言: 在老师分配任务(“尝试利用IOCP模型写出服务端和客户端的代码”)给我时,脑子一片空白,并不知道什么是IOCP模型,会不会是像软件设计模式里面的工厂模 ...
- HDU 4284 Travel
据说是TSP经典问题...可以用状态压缩做.但是看到数据量,就厚着脸皮上搜索了...先floyd预处理每对点间的最小消费,然后只考虑要去的城市就可以了,这样的话城市数最多16个...当时就暴搜了... ...