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_HOOK
inhdr_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分片的重组等信息,下章节分析路由相关信息。(学习过程,大神勿喷,多指正)