链路层到网络层(eth->ip->udp)
来源:互联网 发布:淘宝限额5000怎么修改 编辑:程序博客网 时间:2024/06/07 06:32
0x01 缘由
上章节已经简单的学习了从硬件驱动层到链路层的相关过程,计划这章接讲解arp_rcv,但是考虑整个学习计划还是先从ip_rcv为主线展开学习,还是跟着上次的调试节奏进入网络层的源码跟踪和学习。
0x02 整体调用栈
0x03 源码跟踪
1.跟踪ip_rcv
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; /* 这个函数不会接收不是发给这个主机的数据包,如果主机是工作在混杂模式,这个数据包已经由 netif_receive_skb()去完成处理了。注意,这里所说的“不属于”这个主机,是指在这个包目标 主机的MAC地址不是本机,而不是L3层的ip地址。所以,它不包括路由的包。*/ if (skb->pkt_type == PACKET_OTHERHOST) goto drop; //相关统计 IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len); /* 共享的检查,如果是共享的数据包,因为它可能需要修改skb中的信息,所以要先复制一个副本, 再作进一步的处理。*/ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) { IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS); goto out; } /*下面主要做一些校验检查: 1、数据包长度至少有一个ip header的长度,大于等于20; 2、版本号为4; 3、校验和正确; 4、没有多余的长度; */ if (!pskb_may_pull(skb, sizeof(struct iphdr))) goto inhdr_error; /* 提取ip头结构,这个常在网络设备开发中要处理。*/ iph = ip_hdr(skb); /* ip头小于5 * 4 (20),版本号不等于4 */ if (iph->ihl < 5 || iph->version != 4) goto inhdr_error; /* 处理数据填充*/ if (!pskb_may_pull(skb, iph->ihl*4)) goto inhdr_error; /* 提取ip头结构*/ iph = ip_hdr(skb); /* 快速校验和 */ if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl))) goto inhdr_error; /* 数据包总长度 */ len = ntohs(iph->tot_len); /* 如果skb的总长度小于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; /* 我们的传输介质可能已经填充了缓冲区。现在我们知道此包为ip包, 我们可以处理侦到真实长度。 注意现在这个包skb->len = ntohs(iph->tot_len) */ 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); /* 此处NF_HOOK 宏会先检测是否使用netfilter,如果下了netfiler 钩子函数,则走netfilter处理流程,否则走ip_rcv_finish */ return NF_HOOK(PF_INET, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish); //此处调试不调试NF_HOOKinhdr_error: IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INHDRERRORS);drop: kfree_skb(skb);out: return NET_RX_DROP;}
2.跟踪ip_rcv_finish
static int ip_rcv_finish(struct sk_buff *skb){ const struct iphdr *iph = ip_hdr(skb); struct rtable *rt; /* * 初始化数据包的虚拟路径缓存。它表述数据包在Linux网络中遍历的路径。 * 刚进来没有路由信息,于是调用ip_route_input去初始化。 */ if (skb_dst(skb) == NULL) { int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev); //下次分析整个路由 if (unlikely(err)) { if (err == -EHOSTUNREACH) IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INADDRERRORS); else if (err == -ENETUNREACH) IP_INC_STATS_BH(dev_net(skb->dev), IPSTATS_MIB_INNOROUTES); goto drop; } } /* 略 */ /* 头部长度大于20,说明存在ip选项,对ip选项进行处理。*/ 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->u.dst.dev), IPSTATS_MIB_INMCAST, skb->len); } else if (rt->rt_type == RTN_BROADCAST) //广播 IP_UPD_PO_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INBCAST, skb->len); return dst_input(skb); //此处一个inline函数,进行下一步处理,如果是发往本地走ip_local_deliver,如果是转发到其他走ip_forward。drop: kfree_skb(skb); return NET_RX_DROP;}
3.跟踪ip_local_deliver
/* * 将包纵向转发到更高层的协议; */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; } /* 此处是netfilter处理,如果存在钩子函数则处理钩子函数,否则直接调用ip_local_deliver_finish*/ return NF_HOOK(PF_INET, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);}
4.跟踪ip_local_deliver_finish
static int ip_local_deliver_finish(struct sk_buff *skb){ struct net *net = dev_net(skb->dev); __skb_pull(skb, ip_hdrlen(skb)); /* Point into the IP datagram, just past the header. */ skb_reset_transport_header(skb); rcu_read_lock(); { int protocol = ip_hdr(skb)->protocol; int hash, raw; const struct net_protocol *ipprot; resubmit: /* 对每个报文要先检查是否是 RAW 报文,检查的方式是从 raw_v4_hash 表中查找是否有对应的 RAW, 在 inet_create 函数中 RAW socket 曾经调用了自己 hash 函数,将自己挂在了此 hash 表中。 然而不是 RAW 的 socket 不会往此 hash 表中加数据。向上层分发收到的目的地址是本机的包,首 先分发原始套接字,注意:由于 ping 程序本身是 raw 应用,所以会进入到 raw_v4_input 中,但 是实际并没有通过这个函数将报文收到应用层,为什么呢?请参考该函数的讲解,而对于其它 RAW IP 的应用,比如 OSPF 就会通过此函数把报文发送到用户层*/ raw = raw_local_deliver(skb, protocol); /*没有冲突的哈希表,直接定向到协议指针*/ hash = protocol & (MAX_INET_PROTOS - 1); ipprot = rcu_dereference(inet_protos[hash]); if (ipprot != NULL) { int ret; if (!net_eq(net, &init_net) && !ipprot->netns_ok) { if (net_ratelimit()) printk("%s: proto %d isn't netns-ready\n", __func__, protocol); kfree_skb(skb); goto out; } if (!ipprot->no_policy) { if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { kfree_skb(skb); goto out; } nf_reset(skb); } /* 对应的操作函数是 tcp_v4_rcv,icmp_rcv,igmp_rcv,udp_rcv,在本书目前的例子中,由于是 ping 应用程序,那么它应该是 icmp_rcv */ ret = ipprot->handler(skb); //此处已经将报文传到上层,离开网络层。 if (ret < 0) { protocol = -ret; goto resubmit; } IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS); } else { if (!raw) { /* 如果没有对应的高层协议处理此报文,要么发送目标不可达,要么释放该报文.*/ if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { IP_INC_STATS_BH(net, IPSTATS_MIB_INUNKNOWNPROTOS); icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0); } } else IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS); kfree_skb(skb); } } out: rcu_read_unlock(); return 0;}
0x04 总结
上面的分析,忽略了路由的查找、ip分片的重组等信息,下章节分析路由相关信息。(学习过程,大神勿喷,多指正)
阅读全文
0 0
- 链路层到网络层(eth->ip->udp)
- 网络协议结构体定义(eth,ip,tcp,udp,icmp...)
- 网络层ip
- TCP/IP网络层
- IP网络层
- 网络层ip
- TCP/IP网络层
- 【网络层】IP网际协议
- TCP/IP网络层
- 网络层-IP协议
- 网络层IP协议
- TCP/IP网络协议基础 02——传输层协议UDP
- 网络七层协议、TCP/IP、UDP、HTTP、Socket 个人理解
- 网络协议 TCP/IP UDP
- TCP/IP 详解网络层
- 网络层之IP协议
- TCP/IP网络层(转)
- 网络层IP地址划分
- Linux 命令
- 二分查找
- Redis使用认证密码登录
- 从零开始···maven创建web项目
- MySQL数据库多级关联删除失败
- 链路层到网络层(eth->ip->udp)
- ue4 delegate event
- unity对Xml文件进行读取
- 【安全牛学习笔记】WEP加密、RC4算法
- UNIX主机文件导入数据库方法以及sqlldr在AIX主机使用方法
- ActiveMQ系列—ActiveMQ安装
- 【求最小权值割边】HDU
- cache一致性入门级解答
- Crash-fix-2:org.springframework.http.converter.HttpMessageNotReadableException