网络数据包收发流程:从驱动到协议栈

一、硬件环境

intel82546:PHY与MAC集成在一起的PCI网卡芯片,很强大
bcm5461:   PHY芯片,与之对应的MAC是TSEC
TSEC:      Three Speed Ethernet Controller,三速以太网控制器,PowerPc 架构CPU里面的MAC模块
            注意,TSEC内部有DMA子模块  

话说现在的CPU越来越牛叉了,什么功能都往里面加,最常见的如MAC功能。
TSEC只是MAC功能模块的一种,其他架构的cpu也有和TSEC类似的MAC功能模块。
这些集成到CPU芯片上的功能模块有个学名,叫平台设备,即 platform device。

二、网络收包原理

网络驱动收包大致有3种情况:

no NAPI:mac每收到一个以太网包,都会产生一个接收中断给cpu,即完全靠中断方式来收包
          缺点是当网络流量很大时,cpu大部分时间都耗在了处理mac的中断。

netpoll:在网络和I/O子系统尚不能完整可用时,模拟了来自指定设备的中断,即轮询收包。
         缺点是实时性差

NAPI: 采用 中断 + 轮询 的方式:mac收到一个包来后会产生接收中断,但是马上关闭。
       直到收够了netdev_max_backlog个包(默认300),或者收完mac上所有包后,才再打开接收中断
       通过sysctl来修改 net.core.netdev_max_backlog
       或者通过proc修改 /proc/sys/net/core/netdev_max_backlog


下面只写内核配置成使用NAPI的情况,只写TSEC驱动。(非NAPI的情况和PCI网卡驱动 以后再说)
内核版本 linux 2.6.24

三、NAPI 相关数据结构

每个网络设备(MAC层)都有自己的net_device数据结构,这个结构上有napi_struct。
每当收到数据包时,网络设备驱动会把自己的napi_struct挂到CPU私有变量上。
这样在软中断时,net_rx_action会遍历cpu私有变量的poll_list,
执行上面所挂的napi_struct结构的poll钩子函数,将数据包从驱动传到网络协议栈。

四、内核启动时的准备工作

4.1 初始化网络相关的全局数据结构,并挂载处理网络相关软中断的钩子函数
start_kernel()
    --> rest_init()
        --> do_basic_setup()
            --> do_initcall
               -->net_dev_init

__init net_dev_init()
{
    //每个CPU都有一个CPU私有变量 _get_cpu_var(softnet_data)
    //_get_cpu_var(softnet_data).poll_list很重要,软中断中需要遍历它的
    for_each_possible_cpu(i) {
        struct softnet_data *queue;
        queue = &per_cpu(softnet_data, i);
        skb_queue_head_init(&queue->input_pkt_queue);
        queue->completion_queue = NULL;
        INIT_LIST_HEAD(&queue->poll_list);
        queue->backlog.poll = process_backlog;
        queue->backlog.weight = weight_p;
    }
    open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL); //在软中断上挂网络发送handler
    open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL); //在软中断上挂网络接收handler
}
   
4.2 加载网络设备的驱动
NOTE:这里的网络设备是指MAC层的网络设备,即TSEC和PCI网卡(bcm5461是phy)
在网络设备驱动中创建net_device数据结构,并初始化其钩子函数 open(),close() 等
挂载TSEC的驱动的入口函数是 gfar_probe

// 平台设备 TSEC 的数据结构
static struct platform_driver gfar_driver = {
    .probe = gfar_probe,
    .remove = gfar_remove,
    .driver = {
        .name = "fsl-gianfar",
    },
};

int gfar_probe(struct platform_device *pdev)
{
    dev = alloc_etherdev(sizeof (*priv)); // 创建net_device数据结构

    dev->open = gfar_enet_open;
    dev->hard_start_xmit = gfar_start_xmit;
    dev->tx_timeout = gfar_timeout;
    dev->watchdog_timeo = TX_TIMEOUT;
#ifdef CONFIG_GFAR_NAPI
    netif_napi_add(dev, &priv->napi,gfar_poll,GFAR_DEV_WEIGHT); //软中断里会调用poll钩子函数
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
    dev->poll_controller = gfar_netpoll;
#endif
    dev->stop = gfar_close;
    dev->change_mtu = gfar_change_mtu;
    dev->mtu = 1500;
    dev->set_multicast_list = gfar_set_multi;
    dev->set_mac_address = gfar_set_mac_address;
    dev->ethtool_ops = &gfar_ethtool_ops;
}

五、启用网络设备
5.1 用户调用ifconfig等程序,然后通过ioctl系统调用进入内核
socket的ioctl()系统调用
    --> sock_ioctl()
        --> dev_ioctl()                              //判断SIOCSIFFLAGS
          --> __dev_get_by_name(net, ifr->ifr_name)  //根据名字选net_device
             --> dev_change_flags()                  //判断IFF_UP
                --> dev_open(net_device)             //调用open钩子函数 

 对于TSEC来说,挂的钩子函数是 gfar_enet_open(net_device)

5.2 在网络设备的open钩子函数里,分配接收bd,挂中断ISR(包括rx、tx、err),对于TSEC来说
gfar_enet_open
    --> 给Rx Tx Bd 分配一致性DMA内存 
    --> 把Rx Bd的“EA地址”赋给数据结构,物理地址赋给TSEC寄存器
    --> 把Tx Bd的“EA地址”赋给数据结构,物理地址赋给TSEC寄存器
    --> 给 tx_skbuff 指针数组 分配内存,并初始化为NULL
    --> 给 rx_skbuff 指针数组 分配内存,并初始化为NULL

    --> 初始化Tx Bd
    --> 初始化Rx Bd,提前分配存储以太网包的skb,这里使用的是一次性dma映射
       (注意:#define DEFAULT_RX_BUFFER_SIZE  1536保证了skb能存一个以太网包)
        rxbdp = priv->rx_bd_base;
        for (i = 0; i < priv->rx_ring_size; i++) {
            struct sk_buff *skb = NULL;
            rxbdp->status = 0;
            //这里真正分配skb,并且初始化rxbpd->bufPtr, rxbdpd->length
            skb = gfar_new_skb(dev, rxbdp);    
            priv->rx_skbuff[i] = skb;
            rxbdp++;
        }
        rxbdp--;
        rxbdp->status |= RXBD_WRAP; // 给最后一个bd设置标记WRAP标记
        
    --> 注册TSEC相关的中断handler: 错误,接收,发送
        request_irq(priv->interruptError, gfar_error, 0, "enet_error", dev)
        request_irq(priv->interruptTransmit, gfar_transmit, 0, "enet_tx", dev)//包发送完
        request_irq(priv->interruptReceive, gfar_receive, 0, "enet_rx", dev)  //包接收完

    -->gfar_start(net_device)
        // 使能Rx、Tx
        // 开启TSEC的 DMA 寄存器
        // Mask 掉我们不关心的中断event

最终,TSEC相关的Bd等数据结构应该是下面这个样子的

六、中断里接收以太网包

TSEC的RX已经使能了,网络数据包进入内存的流程为:
    网线 --> Rj45网口 --> MDI 差分线
         --> bcm5461(PHY芯片进行数模转换) --> MII总线 
         --> TSEC的DMA Engine 会自动检查下一个可用的Rx bd 
         --> 把网络数据包 DMA 到 Rx bd 所指向的内存,即skb->data


接收到一个完整的以太网数据包后,TSEC会根据event mask触发一个 Rx 外部中断。
cpu保存现场,根据中断向量,开始执行外部中断处理函数do_IRQ()

do_IRQ 伪代码
{
   上半部处理硬中断
       查看中断源寄存器,得知是网络外设产生了外部中断
       执行网络设备的rx中断handler(设备不同,函数不同,但流程类似,TSEC是gfar_receive
          1. mask 掉 rx event,再来数据包就不会产生rx中断
          2. 给napi_struct.state加上 NAPI_STATE_SCHED 状态
          3. 挂网络设备自己的napi_struct结构到cpu私有变量_get_cpu_var(softnet_data).poll_list
          4. 触发网络接收软中断
    下半部处理软中断
        依次执行所有软中断handler,包括timer,tasklet等等
        执行网络接收的软中断handler  net_rx_action
          1. 遍历cpu私有变量_get_cpu_var(softnet_data).poll_list 
          2. 取出poll_list上面挂的napi_struct 结构,执行钩子函数napi_struct.poll()
             (设备不同,钩子函数不同,流程类似,TSEC是gfar_poll)
          3. 若poll钩子函数处理完所有包,则打开rx event mask,再来数据包的话会产生rx中断
          4. 调用napi_complete(napi_struct *n)
             把napi_struct 结构从_get_cpu_var(softnet_data).poll_list 上移走
             同时去掉 napi_struct.state 的 NAPI_STATE_SCHED 状态
}

6.1 TSEC的接收中断处理函数
gfar_receive
{
#ifdef CONFIG_GFAR_NAPI
    // test_and_set当前net_device的napi_struct.state 为 NAPI_STATE_SCHED
    // 在软中断里调用 net_rx_action 会检查状态 napi_struct.state
    if (netif_rx_schedule_prep(dev, &priv->napi)) {  
        tempval = gfar_read(&priv->regs->imask);            
        tempval &= IMASK_RX_DISABLED; //mask掉rx,不再产生rx中断
        gfar_write(&priv->regs->imask, tempval);    
        // 将当前net_device的 napi_struct.poll_list 挂到
        // CPU私有变量__get_cpu_var(softnet_data).poll_list 上,并触发软中断
        // 所以,在软中断中调用 net_rx_action 的时候,就会执行当前net_device的
        // napi_struct.poll()钩子函数,即 gfar_poll()
        __netif_rx_schedule(dev, &priv->napi);   
    } 
#else
    gfar_clean_rx_ring(dev, priv->rx_ring_size);
#endif
}

6.2 网络接收软中断net_rx_action
net_rx_action()
{
    struct list_head *list = &__get_cpu_var(softnet_data).poll_list;    
    //通过 napi_struct.poll_list, 将N多个 napi_struct 链接到一条链上 
    //通过 CPU私有变量,我们找到了链头,然后开始遍历这个链

    int budget = netdev_budget; //这个值就是 net.core.netdev_max_backlog,通过sysctl来修改

    while (!list_empty(list)) {
        struct napi_struct *n;
        int work, weight;
        local_irq_enable();
        //从链上取一个 napi_struct 结构(接收中断处理函数里加到链表上的,如gfar_receive)
        n = list_entry(list->next, struct napi_struct, poll_list);
        weight = n->weight;
        work = 0;
        if (test_bit(NAPI_STATE_SCHED, &n->state)) //检查状态标记,此标记在接收中断里加上的   
            work = n->poll(n, weight); //使用NAPI的话,使用的是网络设备自己的napi_struct.poll
                                       //对于TSEC是,是gfar_poll
        WARN_ON_ONCE(work > weight);
        budget -= work;
        local_irq_disable();

        if (unlikely(work == weight)) {
            if (unlikely(napi_disable_pending(n)))
                __napi_complete(n); //操作napi_struct,把去掉NAPI_STATE_SCHED状态,从链表中删去
            else
                list_move_tail(&n->poll_list, list);
        }
        netpoll_poll_unlock(have);
    }
out:
    local_irq_enable();
}

static int gfar_poll(struct napi_struct *napi, int budget)
{
    struct gfar_private *priv = container_of(napi, struct gfar_private, napi);
    struct net_device *dev = priv->dev;  //TSEC对应的网络设备
    int howmany;  
    //根据dev的rx bd,获取skb并送入协议栈,返回处理的skb的个数,即以太网包的个数
    howmany = gfar_clean_rx_ring(dev, budget);
    // 下面这个判断比较有讲究的
    // 收到的包的个数小于budget,代表我们在一个软中断里就全处理完了,所以打开 rx硬中断
    // 要是收到的包的个数大于budget,表示一个软中断里处理不完所有包,那就不打开 rx硬中断
    // 此次软中断的下一轮循环里再接着处理,直到包处理完(即howmany rx硬中断
    if (howmany < budget) {        
        netif_rx_complete(dev, napi);
        gfar_write(&priv->regs->rstat, RSTAT_CLEAR_RHALT);
        //打开 rx 硬中断,rx 硬中断是在gfar_receive()中被关闭的
        gfar_write(&priv->regs->imask, IMASK_DEFAULT); 
    }
    return howmany;
}          

gfar_clean_rx_ring(dev, budget)
{
    bdp = priv->cur_rx;
    while (!((bdp->status & RXBD_EMPTY) || (--rx_work_limit < 0))) {
        rmb();
        skb = priv->rx_skbuff[priv->skb_currx]; //从rx_skbuff[]中获取skb
        howmany++;
        dev->stats.rx_packets++;
        pkt_len = bdp->length - 4;  //从length中去掉以太网包的FCS长度
        gfar_process_frame(dev, skb, pkt_len);
        dev->stats.rx_bytes += pkt_len;
        dev->last_rx = jiffies;
        bdp->status &= ~RXBD_STATS;  //清rx bd的状态
    
        skb = gfar_new_skb(dev, bdp); // Add another skb for the future
        priv->rx_skbuff[priv->skb_currx] = skb;

        if (bdp->status & RXBD_WRAP)  //更新指向bd的指针
            bdp = priv->rx_bd_base;   //bd有WARP标记,说明是最后一个bd了,需要“绕回来”
        else
            bdp++;
        priv->skb_currx = (priv->skb_currx + 1) & RX_RING_MOD_MASK(priv->rx_ring_size);
    }
    priv->cur_rx = bdp; /* Update the current rxbd pointer to be the next one */
    return howmany;
}
        
gfar_process_frame()  
    -->skb->protocol = eth_type_trans(skb, dev); //确定网络层包类型,IP、ARP、VLAN等等
    -->RECEIVE(skb) //调用netif_receive_skb(skb)进入协议栈


#ifdef CONFIG_GFAR_NAPI
#define RECEIVE(x) netif_receive_skb(x)
#else
#define RECEIVE(x) netif_rx(x)
#endif

------------------------------------ 华丽的分割线 ---------------------------------------

呼,netif_receive_skb(skb) 可算到协议栈了,歇会儿....



以太网的FCS会在网卡中断(如gfar_clean_rx_ring)中忽略掉
    /* Remove the FCS from the packet length */
    pkt_len = bdp->length - 4;

至于填充数据,是在协议栈中被忽略掉的,比如ip协议ip_rcv()
    /* Our transport medium may have padded the buffer out. Now we know it
     * is IP we can trim to the true length of the frame.
     * Note this now means skb->len holds ntohs(iph->tot_len).
     */
    if (pskb_trim_rcsum(skb, len)) {
        IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
        goto drop;
    }

 

上一篇讲的是内核配置成NAPI的情况,那也是绝大多数内核使用的配置
现在讲讲内核不配置成NAPI时的情况


一、no NAPI 数据结构

不配置NAPI的时候,网络设备不使用自己的napi_struct结构,
所有网络设备驱动都使用同一个napi_struct,即cpu私有变量__get_cpu_var(softnet_data).backlog

每当收到数据包时,网络设备驱动会把__get_cpu_var(softnet_data).backlog挂到__get_cpu_var(softnet_data).poll_list上面。

所以软中断里net_rx_action遍历cpu私有变量__get_cpu_var(softnet_data).poll_list时,
上面挂的napi_struct只有一个

二、内核启动时的准备工作

也是在net_dev_init中,初始化了cpu私有变量的napi_struct,即所有网络设备驱动使用的napi_struct

__init net_dev_init()
{
    //每个CPU都有一个私有变量 _get_cpu_var(softnet_data)
    //
_get_cpu_var(softnet_data).poll_list很重要,软中断中需要遍历它的
    for_each_possible_cpu(i) {
        struct softnet_data *queue;
        queue = &per_cpu(softnet_data, i);
        skb_queue_head_init(&queue->input_pkt_queue); // 不配置NAPI时,才使用这个接收队列
        queue->completion_queue = NULL;
        INIT_LIST_HEAD(&queue->poll_list);
        queue->backlog.poll = process_backlog;        // poll钩子函数初始化
        queue->backlog.weight = weight_p;             //

    }
    open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL); //在软中断上挂网络接收handler
    open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL); //在软中断上挂网络发送handler

}

三、中断里接受以太网包

TSEC的接收中断处理函数

gfar_receive
{
    gfar_write(&priv->regs->ievent, IEVENT_RX_MASK);
#ifdef CONFIG_GFAR_NAPI
    // test_and_set当前net_device的napi_struct.state 为 NAPI_STATE_SCHED
    // 在软中断里调用 net_rx_action 会检查状态 napi_struct.state
    if (netif_rx_schedule_prep(dev, &priv->napi)) {  
        tempval = gfar_read(&priv->regs->imask);            
        tempval &= IMASK_RX_DISABLED;
        gfar_write(&priv->regs->imask, tempval);    

        // 将当前net_device的 napi_struct.poll_list 挂到
        // CPU私有变量 &__get_cpu_var(softnet_data).poll_list 上,并触发软中断
        // 所以,在软中断中调用 net_rx_action 的时候,就会执行当前net_device的
        // napi_struct.poll()钩子函数,即 gfar_poll()
        __netif_rx_schedule(dev, &priv->napi);   
    } 

#else
    gfar_clean_rx_ring(dev, priv->rx_ring_size);
#endif
}

gfar_clean_rx_ring   
   
-->gfar_process_frame  
      -->初始化了skb->dev,这样在软中断里才能判断这个数据包来自哪里
      -->RECEIVE(skb) // 调用netif_rx(skb)


#ifdef CONFIG_GFAR_NAPI
#define RECEIVE(x) netif_receive_skb(x)
#else
#define RECEIVE(x) netif_rx(x)
#endif

netif_rx(skb)
{
   queue = &__get_cpu_var(softnet_data);
   __skb_queue_tail(&queue->input_pkt_queue, skb); //将skb放到接收队列(在net_dev_init初始化)中
   napi_schedule(&queue->backlog); //将cpu私有变量的的napi_struct挂到cpu私有变量的poll_list上
                                   //test_and_set napi_struct.state为 NAPI_STATE_SCHED
                                   //触发网络接收软中断
}

软中断net_rx_action中调用poll钩子函数

虽说软中断里也遍历cpu私有变量的poll_list,事实上poll_list现在只挂一个napi_struct结构
即cpu私有变量的backlog成员(它在net_dev_init中初始化),所以现在调用的poll钩子函数就是process_backlog了

static int process_backlog(struct napi_struct *napi, int quota)
{
    struct softnet_data *queue = &__get_cpu_var(softnet_data);
    napi->weight = weight_p;
    do {
        struct sk_buff *skb;
        struct net_device *dev;

        local_irq_disable();
        skb = __skb_dequeue(&queue->input_pkt_queue); //从接收队列中取出skb,
        if (!skb) {                                   //这些skb是在netif_rx中进入队列的
            __napi_complete(napi);
            local_irq_enable();
            break;
        }
        local_irq_enable();
        dev = skb->dev;
        netif_receive_skb(skb);     //进入协议协议栈
        dev_put(dev);
    } while (++work < quota && jiffies == start_time);
    return work;
}

 

进入函数netif_receive_skb()后,skb正式开始协议栈之旅。
先上图,协议栈大致过程如下所示:


跟OSI七层模型不同,linux根据包结构对网络进行分层。
比如,arp头和ip头都是紧跟在以太网头后面的,所以在linux协议栈中arp和ip地位相同(如上图)
但是在OSI七层模型中,arp属于链路层,ip属于网络层..... 
这里就不死抠概念,我们就说arp,ip都属于第二层。下面是网络第二层的处理流程

一、相关数据结构
内核处理网络第二层,有下面2个重要list_head变量 (文件linux_2_6_24/net/core/dev.c)
list_head 链表上挂了很多packet_type数据结构

static struct list_head ptype_base[16] __read_mostly;   /* 16 way hashed list */
static struct list_head ptype_all __read_mostly;        /* Taps */

struct packet_type {
    __be16 type;                /* This is really htons(ether_type).*/
    struct net_device   *dev;   /* NULL is wildcarded here       */
    int     (*func) (struct sk_buff *,
                     struct net_device *,
                     struct packet_type *,
                     struct net_device *);
    struct sk_buff    *(*gso_segment)(struct sk_buff *skb, int features);
    int    (*gso_send_check)(struct sk_buff *skb);
    void   *af_packet_priv;
    struct list_head    list;
};

type 成员保存了二层协议类型,ETH_P_IP、ETH_P_ARP等等
func 成员就是钩子函数了,如 ip_rcv()、arp_rcv()等等

二、操作packet_type的API
//把packet_type结构挂在与type对应的list_head上面
void dev_add_pack(struct packet_type *pt){
    int hash;
    spin_lock_bh(&ptype_lock);
    if (pt->type == htons(ETH_P_ALL))        //type为ETH_P_ALL时,挂在ptype_all上面
        list_add_rcu(&pt->list, &ptype_all);
    else {
        hash = ntohs(pt->type) & 15;         //否则,挂在ptype_base[type&15]上面
        list_add_rcu(&pt->list, &ptype_base[hash]);
    }
    spin_unlock_bh(&ptype_lock);
}

//把packet_type从list_head上删除
void dev_remove_pack(struct packet_type *pt){
    __dev_remove_pack(pt);
    synchronize_net();
}
void __dev_remove_pack(struct packet_type *pt){
    struct list_head *head;
    struct packet_type *pt1;
    spin_lock_bh(&ptype_lock);
    if (pt->type == htons(ETH_P_ALL))
        head = &ptype_all;                        //找到链表头
    else
        head = &ptype_base[ntohs(pt->type) & 15]; //

    list_for_each_entry(pt1, head, list) {
        if (pt == pt1) {
            list_del_rcu(&pt->list);
            goto out;
        }
    }
    printk(KERN_WARNING "dev_remove_pack: %p not found.\n", pt);
out:
    spin_unlock_bh(&ptype_lock);
}

三、进入二层协议处理函数
int netif_receive_skb(struct sk_buff *skb)
{
   //略去一些代码
    rcu_read_lock();
    //第一步:先处理 ptype_all 上所有的 packet_type->func()            
    //所有包都会调func,对性能影响严重!内核默认没挂任何钩子函数

    list_for_each_entry_rcu(ptype, &ptype_all, list) {  //遍历ptye_all链表
        if (!ptype->dev || ptype->dev == skb->dev) {    //上面的paket_type.type 为 ETH_P_ALL
            if (pt_prev)                                //对所有包调用paket_type.func()
                ret = deliver_skb(skb, pt_prev, orig_dev); //此函数最终调用paket_type.func()
            pt_prev = ptype;
        }
    }
    //第二步:若编译内核时选上BRIDGE,下面会执行网桥模块
    //调用函数指针 br_handle_frame_hook(skb), 在动态模块 linux_2_6_24/net/bridge/br.c中
    //br_handle_frame_hook = br_handle_frame;
    //所以实际函数 br_handle_frame。
    //注意:在此网桥模块里初始化 skb->pkt_type 为 PACKET_HOST、PACKET_OTHERHOST

    skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
    if (!skb) goto out;

    //第三步:编译内核时选上MAC_VLAN模块,下面才会执行
    //调用 macvlan_handle_frame_hook(skb), 在动态模块linux_2_6_24/drivers/net/macvlan.c中
    //macvlan_handle_frame_hook = macvlan_handle_frame; 
    //所以实际函数为 macvlan_handle_frame。 
    //注意:此函数里会初始化 skb->pkt_type 为 PACKET_BROADCAST、PACKET_MULTICAST、PACKET_HOST

    skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
    if (!skb)  goto out;

    //第四步:最后 type = skb->protocol; &ptype_base[ntohs(type)&15]
    //处理ptype_base[
ntohs(type)&15]上的所有的 packet_type->func()
    //根据第二层不同协议来进入不同的钩子函数,重要的有:ip_rcv() arp_rcv()
    type = skb->protocol;
    list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
        if (ptype->type == type &&                      //遍历包type所对应的链表
            (!ptype->dev || ptype->dev == skb->dev)) {  //调用链表上所有pakcet_type.func()
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev); //就这里!arp包会调arp_rcv()
            pt_prev = ptype;                               //        ip包会调ip_rcv()
        }
    }
    if (pt_prev) {
        ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
    } else {               //下面就是数据包从协议栈返回来了
        kfree_skb(skb);    //注意这句,若skb没进入socket的接收队列,则在这里被释放
        ret = NET_RX_DROP; //若skb进入接收队列,则系统调用取包时skb释放,这里skb引用数减一而已
    }
out:
    rcu_read_unlock();
    return ret;
}

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()等
}

这里只是将大致流程,arp_rcv(), ip_rcv() 什么的具体流程,以后再写。

四、网络抓包tcpdump
tcpdump也是在二层抓包的,用的是libpcap库,它的基本原理是
1.先创建socket,内核dev_add_packet()挂上自己的钩子函数
2.然后在钩子函数中,把skb放到自己的接收队列中,
3.接着系统调用recv取出skb来,把数据包skb->data拷贝到用户空间
4.最后关闭socket,内核dev_remove_packet()删除自己的钩子函数

下面是一些重要的数据结构,用到的钩子函数都在这里初始化好了
static const struct proto_ops packet_ops = {
    .family =    PF_PACKET,
    .owner =    THIS_MODULE,
    .release =    packet_release,    //关闭socket的时候调这个
    .bind =        packet_bind,
    .connect =    sock_no_connect,
    .socketpair =    sock_no_socketpair,
    .accept =    sock_no_accept,
    .getname =    packet_getname, 
    .poll =        packet_poll,
    .ioctl =    packet_ioctl,
    .listen =    sock_no_listen,
    .shutdown =    sock_no_shutdown,
    .setsockopt =    packet_setsockopt,
    .getsockopt =    packet_getsockopt,
    .sendmsg =    packet_sendmsg,
    .recvmsg =    packet_recvmsg,   //socket收包的时候调这个
    .mmap =        packet_mmap,
    .sendpage =    sock_no_sendpage,
};

static struct net_proto_family packet_family_ops = {
    .family =    PF_PACKET,
    .create =    packet_create,     //创建socket的时候调这个
    .owner    =    THIS_MODULE,
};

至于系统调用 socket、recv、close是如何调到这些内核钩子函数的,以后再讲。这里只关注packet_type

4.1 系统调用socket
libpcap系统调用socket,内核最终调用 packet_create
static int packet_create(struct net *net, struct socket *sock, int protocol){
    po->prot_hook.func = packet_rcv;   //初始化钩子函数指针
    po->prot_hook.af_packet_priv = sk;
    if (protocol) {
        po->prot_hook.type = protocol;  //类型是系统调用socket形参指定的
        dev_add_pack(&po->prot_hook);//关键!!
        sock_hold(sk);
        po->running = 1;
    }
    return(0);
}

4.2 钩子函数 packet_rcv 将skb放入到接收队列
文件 linux_2_6_24/net/packet/af_packet.c
简单来说,packet_rcv中,skb越过了整个协议栈,直接进入队列

4.3 系统调用recv
系统调用recv、read、recvmsg,内核最终会调用packet_recvmsg
从接收队列中取出skb,将数据包内容skb->data拷贝到用户空间

4.4 系统调用close
内核最终会调用packet_release
static int packet_release(struct socket *sock){
    struct sock *sk = sock->sk;
    struct packet_sock *po;
    if (!sk)  return 0;
    po = pkt_sk(sk);
    write_lock_bh(&packet_sklist_lock);
    sk_del_node_init(sk);
    write_unlock_bh(&packet_sklist_lock);
    // Unhook packet receive handler.
    if (po->running) {
        dev_remove_pack(&po->prot_hook);   //就是这句!!把packet_type从链表中删除
        po->running = 0;
        po->num = 0;
        __sock_put(sk);
    }
    packet_flush_mclist(sk);
     // Now the socket is dead. No more input will appear.
    sock_orphan(sk);
    sock->sk = NULL;
    /* Purge queues */
    skb_queue_purge(&sk->sk_receive_queue);
    sk_refcnt_debug_release(sk);
    sock_put(sk);
    return 0;
}

----------------------------------------------------------------------------------------------

搜一下内核源代码,二层协议还真是多。。。
drivers/net/wan/hdlc.c: dev_add_pack(&hdlc_packet_type);  //ETH_P_HDLC    hdlc_rcv
drivers/net/wan/lapbether.c:
            dev_add_pack(&lapbeth_packet_type);         //ETH_P_DEC       lapbeth_rcv
drivers/net/wan/syncppp.c:
            dev_add_pack(&sppp_packet_type);            //ETH_P_WAN_PPP   sppp_rcv
drivers/net/bonding/bond_alb.c:  dev_add_pack(pk_type); //ETH_P_ARP       rlb_arp_recv
drivers/net/bonding/bond_main.c:dev_add_pack(pk_type);  //PKT_TYPE_LACPDU bond_3ad_lacpdu_recv
drivers/net/bonding/bond_main.c:dev_add_pack(pt);       //ETH_P_ARP       bond_arp_rcv
drivers/net/pppoe.c: dev_add_pack(&pppoes_ptype);       //ETH_P_PPP_SES   pppoe_rcv
drivers/net/pppoe.c: dev_add_pack(&pppoed_ptype);       //ETH_P_PPP_DISC  pppoe_disc_rcv
drivers/net/hamradio/bpqether.c:
                    dev_add_pack(&bpq_packet_type);     //ETH_P_BPQ       bpq_rcv
net/ipv4/af_inet.c:  dev_add_pack(&ip_packet_type);     //ETH_P_IP       ip_rcv
net/ipv4/arp.c:    dev_add_pack(&arp_packet_type);      //ETH_P_ARP       arp_rcv
net/ipv4/ipconfig.c:  dev_add_pack(&rarp_packet_type);  //ETH_P_RARP      ic_rarp_recv
net/ipv4/ipconfig.c:  dev_add_pack(&bootp_packet_type); //ETH_P_IP        ic_bootp_recv
net/llc/llc_core.c: dev_add_pack(&llc_packet_type);     //ETH_P_802_2     llc_rcv
net/llc/llc_core.c: dev_add_pack(&llc_tr_packet_type);  //ETH_P_TR_802_2  llc_rcv
net/x25/af_x25.c:  dev_add_pack(&x25_packet_type);    //ETH_P_X25      x25_lapb_receive_frame
net/8021q/vlan.c:  dev_add_pack(&vlan_packet_type);     //ETH_P_8021Q     vlan_skb_recv

这些不同协议的packet_type,有些是linux系统启动时挂上去的
比如处理ip协议的pakcet_type,就是在 inet_init()时挂上去的
还有些驱动模块加载的时候才加上去的。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【项目介绍】 课程作业基于c++实现收发并解析TCPIP协议栈中的数据包源码+项目说明.zip课程作业基于c++实现收发并解析TCPIP协议栈中的数据包源码+项目说明.zip 介绍 - 实现监听主机上的网络适配器 - 使用选定的网络适配器收发数据报 - 截获并分析TCP/IP网络协议栈中的各层协议 最终,本项目实现的功能有: - 获取主机上的所有适配器 - 监听某个网络适配器,可以指定过滤规则 - 截获并分析TCP/IP网络协议栈中的各层协议数据包(包括以太网MAC帧、ARP请求分组、IP数据报、TCP报文段、UDP报文段、ICMP报文段) - 将统计信息和数据输出到文件 ### 题目 项目2:发送和接收TCP数据包 (a) TCP数据包结构设计; (b) TCP数据包发送和接收过程。 TCP是一种面向连接的、可靠的传输层协议。TCP协议工作在网络层IP协议的基础上。本项目的目的是设计一个发送和接收TCP数据包的程序,其功能是填充一个TCP数据包,发送给目的主机,并在目的主机接收此TCP数据包,将数据字段显示显示在标准输出上。 ### 软件架构 - TcpSender下是发送方源码 - TcpReceiver下是接收方源码 - com-headers下是公用头文件和API ### 开发环境 - Visual Studio 2022 - Npcap 1.73 - Npcap 1.13 SDK ## 使用说明 自行安装和配置Npcap,Linux环境下libpcap不够完善,不过UNIX环境直接使用POSIX标准下的socket就行 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通。 2、项目适用人群:计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等计算机相关专业的在校学生、专业老师、行业内企业员工下载使用。 3、项目可用于:项目本身具有较高的学习借鉴价值,不仅适用于小白学习进阶,也可用于专业人员二次开发。当然也可作为毕设项目、课程设计、课程大作业、初期项目立项演示等。 4、如果基础还行,或者热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载使用,相互学习,共同进步!
### 回答1: 腾讯的开源协议栈f-stack.pdf是在DPDK技术峰会上推出的一项重要技术创新。DPDK技术峰会是一个技术交流平台,旨在推动数据平面开发工具包(DPDK)技术的发展和应用。f-stack.pdf是腾讯开源的一套协议栈,它通过与DPDK集成,提供了高性能的网络数据包处理能力。 f-stack.pdf具有以下几个特点:高性能、轻量级和易使用。它基于零拷贝技术,能够以卓越的性能处理数据包。同时,f-stack.pdf采用了轻量级设计,只依赖于DPDK的核心功能,避免了不必要的复杂性,使得使用起来更加简单和灵活。 f-stack.pdf在应用层提供了一系列网络协议的支持,例如TCP、UDP和IP等。它提供了高度可扩展和定制化的接口,使得开发者可以根据具体需求进行定制和优化。另外,f-stack.pdf还提供了丰富的功能和工具,方便开发者进行网络应用的开发和调试。 该协议栈不仅适用于云服务器、网络设备和高速存储等领域,还可以广泛应用于物联网、5G通信和金融等行业。它的高性能和低延迟使得在大规模并发场景下的数据处理更加高效和稳定。 腾讯的开源协议栈f-stack.pdf在DPDK技术峰会上的推出,标志着中国企业在高性能网络技术领域的一次重要突破。它的出现将有助于促进我国网络技术的创新和发展,并提升我国在全球高性能网络领域的竞争力。 ### 回答2: 腾讯的开源协议栈f-stack是DPDK技术峰会上推出的一项重要的技术成果。该技术是基于DPDK(Data Plane Development Kit)开发的一套网络数据包处理框架,旨在提供高性能的网络数据包处理和协议栈功能。 f-stack的特点可以从以下几个方面来描述。首先,它采用了用户态的网络协议栈设计,将协议处理功能从内核态迁移到用户态,避免了内核态和用户态的频繁切换,提高了数据包处理的效率。其次,f-stack提供了一套完整的网络协议栈功能,包括TCP/IP协议栈、套接字接口、事件驱动机制等,可以支持常见的网络应用开发。此外,f-stack还提供了网络收发模块、队列管理、内存池等高性能的数据结构和算法,以进一步提升数据包处理的吞吐量和延迟。 通过使用f-stack,开发者可以轻松地构建高性能的网络应用,无论是在云计算、大数据分析还是边缘计算等场景中,都能够获得更好的性能和响应速度。与传统的网络协议栈相比,f-stack具有更低的延迟、更高的吞吐量和更好的可扩展性,能够更好地满足现代网络应用对于高性能数据处理的需求。 最后,开源协议栈f-stack的推出也体现了腾讯在网络技术领域的创新能力和开放合作的精神。通过开源的方式,腾讯可以与其他行业的开发者和研究机构共同探索和推进网络技术的发展,形成合力并推动整个行业的进步。 总的来说,腾讯的开源协议栈f-stack在DPDK技术峰会上的推出,为高性能网络数据包处理和协议栈开发提供了一个有力的工具和平台,对于促进网络技术的创新和发展具有重要意义。 ### 回答3: 腾讯的开源协议栈f-stack.pdf是关于dpdk技术峰会的一个重要文档。DPDK技术峰会是一个旨在促进DPDK技术发展和交流的会议,汇集了众多DPDK技术领域的专家和爱好者。 在f-stack.pdf中,腾讯团队分享了他们开发的开源协议栈f-stack的技术细节和应用场景。f-stack是一个高性能、可扩展的协议栈,基于DPDK技术开发,旨在提供更好的网络性能和更高的数据包处理能力。 f-stack的设计目标主要包括:轻量级、高性能、可扩展和易用性。它采用了一系列的优化技术,如零拷贝、多队列和事件驱动等,以提高数据包的处理效率。f-stack支持多种协议,如TCP、UDP和IP等,可以满足不同应用场景的需求。 此外,f-stack还提供了一些额外的功能,如高效的内存管理、负载均衡和安全性等。这些功能使得f-stack在网络应用程序的开发中更加方便和灵活。 f-stack已经在腾讯内部得到广泛的应用和验证,取得了良好的性能和稳定性。它已经成为了腾讯云和腾讯游戏等业务的核心组件。 总的来说,f-stack的出现充分展示了腾讯在DPDK技术领域的创新能力和技术实力。它为广大开发者提供了一个高效、可扩展的协议栈解决方案,有助于进一步推动DPDK技术的应用和发展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值