linux网络实现分析(2)——数据包的接收(从链路层到ip层)

简介:

linux网络实现分析(2)——数据包的接收(从链路层到ip层)

——lvyilong316

任何数据包在由驱动接收进入协议栈都会经过netif_receive_skb函数,可以说这个函数是协议栈的入口。在分析这个函数前,首先介绍下三层协议在内核中的组织方式。

    在Linux内核中,有两种不同目的的3层协议:

(1) ptype_all管理的协议主要用于分析目的,它接收所有到达第3层协议的数据包。

(2) ptype_base管理正常的3层协议,仅接收具有正确协议标志符的数据包,例如,Internet的0x0800。

    它们的组织关系如下图:



下面开始分析netif_receive_skb函数。


netif_receive_skb


int netif_receive_skb(struct sk_buff *skb)

{

    struct packet_type *ptype, *pt_prev;

    struct net_device *orig_dev;

    struct net_device *null_or_orig;

    int ret = NET_RX_DROP;

    __be16 type;

 

    if (skb->vlan_tci && vlan_hwaccel_do_receive(skb)) //vlan tag,则vlan处理

        return NET_RX_SUCCESS;

 

    null_or_orig = NULL;

    orig_dev = skb->dev;   //接受数据包的原始设备

    if (orig_dev->master) {

        if (skb_bond_should_drop(skb))

            null_or_orig = orig_dev; /* deliver only exact match */

        else

            skb->dev = orig_dev->master;

    }

    pt_prev = NULL;

rcu_read_lock();

    //AF_PACKETETH_P_ALL网络协议会被放在ptype_all链表中(tcpdump)

list_for_each_entry_rcu(ptype, &ptype_all, list) { 

    if (ptype->dev == null_or_orig || ptype->dev == skb->dev ||

       ptype->dev == orig_dev) {

        if (pt_prev)

            ret = deliver_skb(skb, pt_prev, orig_dev);

        pt_prev = ptype;

    }

 }

 

    skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);//bridge逻辑处理

    if (!skb)

        goto out;

 

    type = skb->protocol; //获取协议类型

    //根据协议类型在hashptype_base中找到相关的注册协议(IPARP)

    list_for_each_entry_rcu(ptype,

            &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {

        if (ptype->type == type &&

            (ptype->dev == null_or_orig || ptype->dev == skb->dev ||

             ptype->dev == orig_dev)) {

            if (pt_prev)

                ret = deliver_skb(skb, pt_prev, orig_dev);//调用协议处理函数

            pt_prev = ptype;

        }

    }

    if (pt_prev) {

        ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);//???

    } else {

        kfree_skb(skb);

        /* Jamal, now you will not able to escape explaining

         * me how you were going to use this. :-)

         */

        ret = NET_RX_DROP;

    }

 

out:

    rcu_read_unlock();

    return ret;

}



deliver_skb

    int deliver_skb(struct sk_buff *skb,struct packet_type *pt_prev, struct net_device *orig_dev){

atomic_inc(&skb->users); //这句不容忽视,与后面流程的kfree_skb()相呼

return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);//调函数ip_rcv() arp_rcv()

}

 

如果是IP数据包,则调用ip_rcv()函数,这是因为ip协议再引导时间初始化协议处理函数为ip_rcv(),具体如下:

net/ipv4/af_inet.c,

static struct packet_type ip_packet_type __read_mostly = {

    .type = cpu_to_be16(ETH_P_IP),

    .func = ip_rcv,

    .gso_send_check = inet_gso_send_check,

    .gso_segment = inet_gso_segment,

    .gro_receive = inet_gro_receive,

    .gro_complete = inet_gro_complete,

};

static int __init inet_init(void){

    …

    dev_add_pack(&ip_packet_type);

    …

}

 

下面来看ip_rcv()函数:


ip_rcv()

net/ipv4/ip_input.c

/*

参数说明:

@dev:即skb->dev,但并不一定是最开始接收skbdev,如经过bridge逻辑又发回本机协议栈时,此时dev即为刚经过的bridge对应的虚拟dev,具体见bridge逻辑。

@pt: 指向协议的指针,这里即ip_packet_type

@orig_dev: 这是最开始接收数据包的设备dev,再netif_receive_skb中被设置。

*/

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;

    //如果skbshare的(skb->users不为1),则clone一个skb_buffskb指向这个新的skb->users1skb_buff

    if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {

        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);

        goto out;

    }

    //进入ip_rcv之前,skb->date已经指向链路头后面,链路头已被解析。

    if (!pskb_may_pull(skb, sizeof(struct iphdr)))

        goto inhdr_error;

 

    iph = ip_hdr(skb);

 

    if (iph->ihl < 5 || iph->version != 4) //丢掉ip头长小于4*5=20的,或者ip version不为4的包(头部长度是以4字节为单位的)

        goto inhdr_error;

 

    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); //len为数据包的真实长度

  

   //由于链路层传输时,由于对齐或最小mtu等原因,可能会对数据包填充,所以这里要将数据包截断为其真实长度(len),len包括线性和非线性数据两部分。另外pskb_trim_rcsum会让L4校验和失效,以免接受的NIC计算过此值,但是计算的不正确。

    if (pskb_trim_rcsum(skb, len)) {

        IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);

        goto drop;

    }

    skb_orphan(skb);//使包成为不属于任何套接字的孤包(skb->sk = NULL)

    //IP PRE_ROUTING

    return NF_HOOK(PF_INET, 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;

}

 

在经过IP协议的PRE_ROUTING hook点后,将调用ip_rcv_finish函数。


  ip_rcv_finish

net/ipv4/ip_input.c,

static int ip_rcv_finish(struct sk_buff *skb)

{

    const struct iphdr *iph = ip_hdr(skb);

    struct rtable *rt;

 

   // skb->_skb_dst 指向dst_entry

    if (skb_dst(skb) == NULL) {//如果 skb->_skb_dst为空

        int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,

skb->dev);  //查找路由表

        …

        }

    }

  //ip头部超过20字节时,表示有一些选项要处理,此时skb_cow(命名来自于copy on write)会被调用,如果skb和别人有share,就拷贝一个副本,因为处理选项有可能修改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);

 

drop:

    kfree_skb(skb);

    return NET_RX_DROP;

}

 

  dst_input

static inline int dst_input(struct sk_buff *skb)

{

    return skb_dst(skb)->input(skb);

}

即调用skb->_skb_dst->input(skb),skb->_skb_dst在ip_route_input中被初始化为ip_local_deliver或ip_forward,这取决于封包的目的地址。

目录
相关文章
|
19天前
|
机器学习/深度学习 数据采集 存储
时间序列预测新突破:深入解析循环神经网络(RNN)在金融数据分析中的应用
【10月更文挑战第7天】时间序列预测是数据科学领域的一个重要课题,特别是在金融行业中。准确的时间序列预测能够帮助投资者做出更明智的决策,比如股票价格预测、汇率变动预测等。近年来,随着深度学习技术的发展,尤其是循环神经网络(Recurrent Neural Networks, RNNs)及其变体如长短期记忆网络(LSTM)和门控循环单元(GRU),在处理时间序列数据方面展现出了巨大的潜力。本文将探讨RNN的基本概念,并通过具体的代码示例展示如何使用这些模型来进行金融数据分析。
119 2
|
5天前
|
运维 监控 网络协议
|
1天前
|
网络协议 安全 算法
网络空间安全之一个WH的超前沿全栈技术深入学习之路(9):WireShark 简介和抓包原理及实战过程一条龙全线分析——就怕你学成黑客啦!
实战:WireShark 抓包及快速定位数据包技巧、使用 WireShark 对常用协议抓包并分析原理 、WireShark 抓包解决服务器被黑上不了网等具体操作详解步骤;精典图示举例说明、注意点及常见报错问题所对应的解决方法IKUN和I原们你这要是学不会我直接退出江湖;好吧!!!
网络空间安全之一个WH的超前沿全栈技术深入学习之路(9):WireShark 简介和抓包原理及实战过程一条龙全线分析——就怕你学成黑客啦!
|
8天前
|
Ubuntu Linux 虚拟化
Linux虚拟机网络配置
【10月更文挑战第25天】在 Linux 虚拟机中,网络配置是实现虚拟机与外部网络通信的关键步骤。本文介绍了四种常见的网络配置方式:桥接模式、NAT 模式、仅主机模式和自定义网络模式,每种模式都详细说明了其原理和配置步骤。通过这些配置,用户可以根据实际需求选择合适的网络模式,确保虚拟机能够顺利地进行网络通信。
|
19天前
|
存储 安全 网络安全
云端盾牌:云计算时代的网络安全守护在数字化浪潮中,云计算以其高效、灵活的特性成为企业转型的加速器。然而,伴随其迅猛发展,网络安全问题亦如影随形,成为悬在每个组织头顶的达摩克利斯之剑。本文旨在探讨云计算服务中的网络安全挑战,分析信息安全的重要性,并提出相应对策,以期为企业构建一道坚实的云端防护网。
在当今这个数据驱动的时代,云计算已成为推动创新与效率的关键力量。它允许用户随时随地访问强大的计算资源,降低了企业的运营成本,加速了产品上市时间。但随之而来的网络威胁也日益猖獗,尤其是对于依赖云服务的企业而言,数据泄露、身份盗用等安全事件频发,不仅造成经济损失,更严重损害品牌信誉。本文深入剖析云计算环境中的安全风险,强调建立健全的信息安全管理机制的重要性,并分享一系列有效策略,旨在帮助企业和个人用户在享受云服务带来的便利的同时,也能构筑起强有力的网络防线。
|
1天前
|
网络协议 安全 算法
网络空间安全之一个WH的超前沿全栈技术深入学习之路(9-2):WireShark 简介和抓包原理及实战过程一条龙全线分析——就怕你学成黑客啦!
实战:WireShark 抓包及快速定位数据包技巧、使用 WireShark 对常用协议抓包并分析原理 、WireShark 抓包解决服务器被黑上不了网等具体操作详解步骤;精典图示举例说明、注意点及常见报错问题所对应的解决方法IKUN和I原们你这要是学不会我直接退出江湖;好吧!!!
|
15天前
|
安全 网络协议 物联网
物联网僵尸网络和 DDoS 攻击的 CERT 分析
物联网僵尸网络和 DDoS 攻击的 CERT 分析
|
18天前
|
存储 算法 数据可视化
单细胞分析 | Cicero+Signac 寻找顺式共可及网络
单细胞分析 | Cicero+Signac 寻找顺式共可及网络
23 0
|
8天前
|
运维 安全 Linux
Linux中传输文件文件夹的10个scp命令
【10月更文挑战第18天】本文详细介绍了10种利用scp命令在Linux系统中进行文件传输的方法,涵盖基础文件传输、使用密钥认证、复制整个目录、从远程主机复制文件、同时传输多个文件和目录、保持文件权限、跨多台远程主机传输、指定端口及显示传输进度等场景,旨在帮助用户在不同情况下高效安全地完成文件传输任务。
83 5