链路层到网络层(eth->ip->udp)

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分片的重组等信息,下章节分析路由相关信息。(学习过程,大神勿喷,多指正)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值