免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12345下一页
最近访问板块 发新帖
查看: 35493 | 回复: 40

Linux内核IP Queue机制的分析(三)——ip_queue内核模块的分析 [复制链接]

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
发表于 2010-01-04 21:54 |显示全部楼层
本文分析ip_queue的内核态源码。文中如有任何疏漏和差错,欢迎各位朋友指正。

        本文欢迎自由转载,但请标明出处,并保证本文的完整性。
        作者:Godbach
        Blog:http://Godbach.cublog.cn
        日期:2010/01/04
       
本系列的前两篇文章如下:
1. Linux内核IP Queue机制的分析(一)——用户态接收数据包
        http://blog.chinaunix.net/u/33048/showart_1678213.html
2. Linux内核IP Queue机制的分析(二)——用户态处理并回传数据包
http://blog.chinaunix.net/u/33048/showart_1839753.html
本文大纲如下:
一、IP Queue的生效
二、网络层中IP报文进入IP Queue的流程
三、ip_queue代码分析
(一)数据结构的定义
(二)ip_queue模块的加载和卸载
(三)ip_queue报文入队处理函数的注册
(四)入队函数ipq_enqueue_packet —— 发送数据包到用户空间
(五)接收和处理用户空间的配置—— 接收用户空间的数据包
(六)数据包的最终处理

[ 本帖最后由 Godbach 于 2010-1-5 15:08 编辑 ]

评分

参与人数 2可用积分 +30 收起 理由
T-Bagwell + 15 我很赞同
dreamice + 15 原创内容

查看全部评分

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
发表于 2010-01-04 21:56 |显示全部楼层
一、IP Queue的生效
数据包能够进入ip_queue模块,需要两个动作:
(1)模块的加载:modprobe ip_queue
(2)NF上对数据包执行NF_QUEUE的动作,这个可以通过用户态配置一条iptables规则实现:
        iptables -A INPUT -p tcp --dport 21 -j QUEUE
        这里假设对发往本机的TCP报文端口为21的进行QUEUE。
有了以上两个步骤, 所有匹配到(2)中的报文将会调用IP Queue模块的相关函数。

二、网络层中IP报文进入IP Queue的流程
        本文中分析的代码的内核版本为2.6.18.3.
这里我们以本地接收报文为例,如果是转发的报文,可比照着分析即可。
IP层接收报文的函数为:ip_rcv()(ip_input.c)。该函数对报文进行一些初步的检查后,就将报文交给PREROUTING Hook点注册的钩子函数处理:
  1.         return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
  2.                        ip_rcv_finish);
复制代码

由以上代码可见,报文经过钩子函数之后,由ip_rcv_finish()接着处理。该函数主要完成数据报文的路由查找。
如果是发往本地的报文,则会调用ip_local_deliver()函数:
  1.         return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
  2.                        ip_local_deliver_finish);
复制代码

该函数主要功能就是将报文交给NF_IP_LOCAL_IN hook点的钩子函数进行处理。我们在第一部分中添加的一条iptables规则就是对于经过INPUT链的TCP报文且目的端口21执行动作QUEUE。如果此时用户空间已经开启socket等待接收IP Queue报文的话,那么对应的报文就会进入用户空间,然后就可以参照我们之前提供的用户空间例程进行处理。
这里,我们简单列出在NF_IP_LOCAL_IN中NF_HOOK宏的调用函数过程:
NF_HOOK()->NF_HOOK_THRESH()->nf_hook_thresh()->nf_hook_slow()
当nf_hook_slow函数返回值为NF_QUEUE时,进一步调用nf_queue()。该函数对所有动作为QUEUE的报文进行处理,其中关键的一行代码如下:
  1.         status = queue_handler[pf]->outfn(*skb, info, queuenum,
  2.                                           queue_handler[pf]->data);
复制代码

这行代码就是将报文按照IP层的协议交给对应的queue handler。IPv4协议中注册的queue handler为ipq_enqueue_packet(),即我们要分写ip_queue模块的代码。
至此,需要QUEUE的报文已经走入了我们要分析ip_queue,我们下面就开始走入正题,分析ip_queue的代码。

--未完待续

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
发表于 2010-01-04 21:58 |显示全部楼层
三、ip_queue代码分析
        ip_queue模块的代码较为简单,包含ip_queue.h和ip_queue.c。我们将分别该两个源文件进行分析。
(一)数据结构的定义
        ip_queue模块的数据结构定义在头文件ip_queue.h中,主要定义了用于在内核态和用户态传输数据的相关数据结构。其代码如下:
  1. /*
  2. * This is a module which is used for queueing IPv4 packets and
  3. * communicating with userspace via netlink.
  4. *
  5. * (C) 2000 James Morris, this code is GPL.
  6. */
  7. #ifndef _IP_QUEUE_H
  8. #define _IP_QUEUE_H

  9. #ifdef __KERNEL__
  10. #ifdef DEBUG_IPQ
  11. #define QDEBUG(x...) printk(KERN_DEBUG ## x)
  12. #else
  13. #define QDEBUG(x...)
  14. #endif  /* DEBUG_IPQ */
  15. #else
  16. #include <net/if.h>
  17. #endif        /* ! __KERNEL__ */

  18. /* 内核态发送到用户态的消息的数据结构*/
  19. typedef struct ipq_packet_msg {
  20.         unsigned long packet_id;        /* ID of queued packet */
  21.         unsigned long mark;                /* Netfilter mark value */
  22.         long timestamp_sec;                /* Packet arrival time (seconds) */
  23.         long timestamp_usec;                /* Packet arrvial time (+useconds) */
  24.         unsigned int hook;                /* Netfilter hook we rode in on */
  25.         char indev_name[IFNAMSIZ];        /* Name of incoming interface */
  26.         char outdev_name[IFNAMSIZ];        /* Name of outgoing interface */
  27.         unsigned short hw_protocol;        /* Hardware protocol (network order) */
  28.         unsigned short hw_type;                /* Hardware type */
  29.         unsigned char hw_addrlen;        /* Hardware address length */
  30.         unsigned char hw_addr[8];        /* Hardware address */
  31.         size_t data_len;                /* Length of packet data */
  32.         unsigned char payload[0];        /* Optional packet data */
  33. } ipq_packet_msg_t;

  34. /* 用户态发送到内核态的模式消息 */
  35. typedef struct ipq_mode_msg {
  36.         unsigned char value;                /* Requested mode */
  37.         size_t range;                        /* Optional range of packet requested */
  38. } ipq_mode_msg_t;

  39. /* 用户态发送到内核态的断言消息 */
  40. typedef struct ipq_verdict_msg {
  41.         unsigned int value;                /* Verdict to hand to netfilter */
  42.         unsigned long id;                /* Packet ID for this verdict */
  43.         size_t data_len;                /* Length of replacement data */
  44.         unsigned char payload[0];        /* Optional replacement packet */
  45. } ipq_verdict_msg_t;

  46. /*统一封装起来的用户态发内核态的信息的数据结构*/
  47. typedef struct ipq_peer_msg {
  48.         union {
  49.                 ipq_verdict_msg_t verdict;
  50.                 ipq_mode_msg_t mode;
  51.         } msg;
  52. } ipq_peer_msg_t;

  53. /* 报文传输的模式*/
  54. enum {
  55.         IPQ_COPY_NONE,                /* Initial mode, packets are dropped */
  56.         IPQ_COPY_META,                /* Copy metadata */
  57.         IPQ_COPY_PACKET                /* Copy metadata + packet (range) */
  58. };
  59. #define IPQ_COPY_MAX IPQ_COPY_PACKET

  60. /* IP Queue消息的类型 */
  61. #define IPQM_BASE        0x10        /* standard netlink messages below this */
  62. #define IPQM_MODE        (IPQM_BASE + 1)                /* Mode request from peer */
  63. #define IPQM_VERDICT        (IPQM_BASE + 2)                /* Verdict from peer */
  64. #define IPQM_PACKET        (IPQM_BASE + 3)                /* Packet from kernel */
  65. #define IPQM_MAX        (IPQM_BASE + 4)

  66. #endif /*_IP_QUEUE_H*/
复制代码

该头文件中定义的相关数据结构和宏应该和用户空间引用的头文件的内容是保持一致的。因此,对于该文件中的数据结构的详细解释,可以参考本系列文章的第一篇《Linux内核IP Queue机制的分析(一)——用户态接收数据包》中第二部分IP Queue编程接口。

(二)ip_queue模块的加载和卸载
分析一个内核模块的源代码,通常我们先看模块加载时做哪些工作,这样就可以了解到模块具体的功能会在什么条件下执行。而模块的卸载通常就是将模块加载时注册的函数和申请的资源进行释放等工作。因此,这里简单的分析一下init函数的实现,代码在ip_queue.c中。

  1. static int __init ip_queue_init(void)
  2. {
  3.         int status = -ENOMEM;
  4.         struct proc_dir_entry *proc;
  5.         /*注册netlink的通知链*/
  6.         netlink_register_notifier(&ipq_nl_notifier);
  7.         /*内核态创建netlink的socket,并注册ipq_rcv_sk函数实现接收用户空间下发的配置数据*/
  8.         ipqnl = netlink_kernel_create(NETLINK_FIREWALL, 0, ipq_rcv_sk,
  9.                                       THIS_MODULE);
  10.         if (ipqnl == NULL) {
  11.                 printk(KERN_ERR "ip_queue: failed to create netlink socket\n");
  12.                 goto cleanup_netlink_notifier;
  13.         }
  14.        
  15.         /*注册proc文件*/
  16.         proc = proc_net_create(IPQ_PROC_FS_NAME, 0, ipq_get_info);
  17.         if (proc)
  18.                 proc->owner = THIS_MODULE;
  19.         else {
  20.                 printk(KERN_ERR "ip_queue: failed to create proc entry\n");
  21.                 goto cleanup_ipqnl;
  22.         }
  23.         /*注册网络设备的通知链*/
  24.         register_netdevice_notifier(&ipq_dev_notifier);
  25.         ipq_sysctl_header = register_sysctl_table(ipq_root_table, 0);
  26.         /*注册IP Queue机制的报文处理结构,主要包含一个报文的入队处理函数,下面具体分析*/
  27.         status = nf_register_queue_handler(PF_INET, &nfqh);
  28.         if (status < 0) {
  29.                 printk(KERN_ERR "ip_queue: failed to register queue handler\n");
  30.                 goto cleanup_sysctl;
  31.         }
  32.         return status;

  33. cleanup_sysctl:
  34.         unregister_sysctl_table(ipq_sysctl_header);
  35.         unregister_netdevice_notifier(&ipq_dev_notifier);
  36.         proc_net_remove(IPQ_PROC_FS_NAME);
  37.        
  38. cleanup_ipqnl:
  39.         sock_release(ipqnl->sk_socket);
  40.         mutex_lock(&ipqnl_mutex);
  41.         mutex_unlock(&ipqnl_mutex);
  42.        
  43. cleanup_netlink_notifier:
  44.         netlink_unregister_notifier(&ipq_nl_notifier);
  45.         return status;
  46. }
复制代码

IP Queue模块的初始化函数最重要的两个工作:
(1)创建用于IP Queue的netlink socket,实现接收用户态的数据的函数ipq_rcv_sk();
(2)注册IP Queue报文的入队处理函数,并将数据包按照用户的配置将相关信息发向用户空间。当NF框架的hook函数对报文的处理返回NF_QUEUE时,该函数作为报文下一步被处理的入口函数。
模块初始化中还有一些其他诸如注册通知链的工作,这里我们不作具体分析,想了解相关细节的朋友可以查阅具体的资料。至于通知链的相关知识,也可以参考论坛上scutan兄的精华帖《内核通知链 学习笔记》。链接:
http://linux.chinaunix.net/bbs/viewthread.php?tid=1051266

--未完待续

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
发表于 2010-01-04 22:01 |显示全部楼层
(三)ip_queue报文入队处理函数的注册
   在上面分析的模块注册代码中,IP Queue的报文入队处理函数的注册是通过调用nf_register_queue_handler()来实现的。因此,有必要了解一下该函数的源码,源码位于nf_queue.c中:
  1. /* return EBUSY when somebody else is registered, return EEXIST if the
  2. * same handler is registered, return 0 in case of success. */
  3. int nf_register_queue_handler(int pf, struct nf_queue_handler *qh)
  4. {      
  5.         int ret;
  6.         /*IP协议族的值必须在当前指定的范围内*/
  7.         if (pf >= NPROTO)
  8.                 return -EINVAL;

  9.         write_lock_bh(&queue_handler_lock);
  10.         /*该queue handler已经被注册过了*/
  11.         if (queue_handler[pf] == qh)
  12.                 ret = -EEXIST;
  13.         /*该协议族已经被注册了handler了*/
  14.         else if (queue_handler[pf])
  15.                 ret = -EBUSY;
  16.         /*将该协议的queue hanler指向参数qh*/
  17.         else {
  18.                 queue_handler[pf] = qh;
  19.                 ret = 0;
  20.         }
  21.         write_unlock_bh(&queue_handler_lock);

  22.         return ret;
  23. }
复制代码

该函数的代码比较简单,先来了解一下函数的两个参数:
(1)pf:IP协议族的值,PF_INET和PF_INET6分别代表IPv4和IPv6。
(2)qh:NF中对报文进行Queue的结构体,定义如下(netfilter.h):
  1. /* Packet queuing */
  2. struct nf_queue_handler {
  3.         int (*outfn)(struct sk_buff *skb, struct nf_info *info,
  4.                      unsigned int queuenum, void *data);
  5.         void *data;
  6.         char *name;
  7. };
复制代码

由此可见,该结构体主要包含一个函数指针,用于处理NF框架需要Queue的报文。data应该是用来保存一些私有数据,name则是该queue handler的名称。
代码中已经包含了该函数源码的简单注释,这里再对该函数进行一下简单的总结:
(1) 每个协议族只能注册一个queue handler;
(2) 随后报文的处理中,可以根据报文的协议族,就可以找到报文进行Queue时的处理函数queue_handler[pf]->outfn()。
        在IP Queue模块中,queue handler的注册如下所示:
  1. status = nf_register_queue_handler(PF_INET, &nfqh);
复制代码

        可见,注册的是IPv4协议族的报文处理函数,而nfqh结构体的定义如下:
  1. static struct nf_queue_handler nfqh = {
  2.         .name        = "ip_queue",
  3.         .outfn        = &ipq_enqueue_packet,
  4. };
复制代码

这里,IP Queue处理报文的函数终于闪亮登场了,我们前面啰嗦了半天,主要就是想理顺一下思路,顺理成章的引出该函数。
(四)入队函数ipq_enqueue_packet —— 发送数据包到用户空间
        第二部分已经分析过了该函数会在什么条件下被触发。这里详细分析该函数的实现,代码在ip_queue.c中。
        分析代码之前,我们先了解一下IP Queue对数据包队列管理的核心数据结构:
  1. struct ipq_queue_entry {
  2.         struct list_head list;
  3.         struct nf_info *info;
  4.         struct sk_buff *skb;
  5. };
复制代码

所有被Queue到用户空间的数据包都会有这样一个结构体。其中,
该数据结构的第1个元素是个双向链表结构,用于将所有queue的数据包用双向链表连
接起来,实现队列管理。
第2个元素同样是一个结构体,其定义在netfilter.h中:
  1. /* Each queued (to userspace) skbuff has one of these. */
  2. struct nf_info
  3. {
  4.         /* The ops struct which sent us to userspace. */
  5.         struct nf_hook_ops *elem;
  6.        
  7.         /* If we're sent to userspace, this keeps housekeeping info */
  8.         int pf;
  9.         unsigned int hook;
  10.         struct net_device *indev, *outdev;
  11.         int (*okfn)(struct sk_buff *);
  12. };
复制代码

这个结构体应该很容易看出他的作用,就是记录下当数据包被Queue时所在的hook函数的相关信息,包括hook的操作结构、协议号、hook点等相关信息。当用户空间下发了对数据包的处理结果时,内核就需要参考这个结构对数据包进行进一步的处理。具体的我们会在后面数据包回注函数中分析。
第3个元素是skb本身,也就是回注函数中要处理的数据包。

OK,我们下面就开始如对函数的分析。
  1. static int
  2. ipq_enqueue_packet(struct sk_buff *skb, struct nf_info *info,
  3.                    unsigned int queuenum, void *data)
  4. {
  5.         int status = -EINVAL;
  6.         struct sk_buff *nskb;
  7.         struct ipq_queue_entry *entry;

  8.         /*判断用户配置的模式,可以为拷贝元数据或者整个数据包的信息*/
  9.         if (copy_mode == IPQ_COPY_NONE)
  10.                 return -EAGAIN;
  11.         /*为即将入队的数据包分配一个struct ipq_queue_entry 结构体,用于实现对数据包的管理,我们称之为queue管理结构体*/
  12.         entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
  13.         if (entry == NULL) {
  14.                 printk(KERN_ERR "ip_queue: OOM in ipq_enqueue_packet()\n");
  15.                 return -ENOMEM;
  16.         }
  17.         /*将该数据包对应的相关信息保存到管理结构体中*/
  18.         entry->info = info;
  19.         entry->skb = skb;
  20.        
  21. /*构建用于发往用户空间的消息*/
  22.         nskb = ipq_build_packet_message(entry, &status);
  23.         if (nskb == NULL)
  24.                 goto err_out_free;
  25.                
  26.         write_lock_bh(&queue_lock);
  27.        
  28.         /如果用户空间没有开启进程,等待接收消息的话,就释放该消息/
  29.         if (!peer_pid)
  30.                 goto err_out_free_nskb;
  31.         /*如果当前队列中的数据包总数超过了设置的最大值,则是放该消息,并且增加丢弃数据包的统计计数*/
  32.         if (queue_total >= queue_maxlen) {
  33.                 queue_dropped++;
  34.                 status = -ENOSPC;
  35.                 if (net_ratelimit())
  36.                           printk (KERN_WARNING "ip_queue: full at %d entries, "
  37.                                   "dropping packets(s). Dropped: %d\n", queue_total,
  38.                                   queue_dropped);
  39.                 goto err_out_free_nskb;
  40.         }

  41.         /* 将消息发送给用户空间 */
  42.         status = netlink_unicast(ipqnl, nskb, peer_pid, MSG_DONTWAIT);
  43.         if (status < 0) {
  44.                 queue_user_dropped++;
  45.                 goto err_out_unlock;
  46.         }
  47.         /*成功发送到用户空间之后,将该数据包的queue管理结构添加到全局队列链表中*/
  48.         __ipq_enqueue_entry(entry);

  49.         write_unlock_bh(&queue_lock);
  50.         return status;

  51. err_out_free_nskb:
  52.         kfree_skb(nskb);
  53.        
  54. err_out_unlock:
  55.         write_unlock_bh(&queue_lock);

  56. err_out_free:
  57.         kfree(entry);
  58.         return status;
  59. }
复制代码

该函数成功执行之后,就将数据包相关的信息发送到用户空间,同时将该数据包对应的queue管理结构加到全局链表中。
下面对于ipq_enqueue_packet函数中调用的几个重要函数做一些分析。
首先是构建消息的函数ipq_build_packet_message。
  1. static struct sk_buff *
  2. ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp)
  3. {
  4.         unsigned char *old_tail;
  5.         size_t size = 0;
  6.         size_t data_len = 0;
  7.         struct sk_buff *skb;
  8.         struct ipq_packet_msg *pmsg;
  9.         struct nlmsghdr *nlh;

  10.         read_lock_bh(&queue_lock);
  11.         /*根据用户配置的copy模式,确定发给用户空间消息的长度*/
  12.         switch (copy_mode) {
  13.         /*对于初始模式和拷贝元数据的模式,消息应该包括netlink的消息头和ipq的消息头,长度为两个消息头长度之和,即sizeof(struct nlmsghdr) + sizeof(struct ipq_packet_msg),并考虑对齐的因素。这里直接调用netlink封装的宏NLMSG_SPACE来计算长度。该宏本身已经包含了netlink消息头的长度*/
  14.         case IPQ_COPY_META:
  15.         case IPQ_COPY_NONE:
  16.                 /*消息长度为netlink的消息头和ipq的消息头的长度之和*/
  17.                 size = NLMSG_SPACE(sizeof(*pmsg));
  18.                 data_len = 0;
  19.                 break;
  20.        
  21.         case IPQ_COPY_PACKET:
  22.                 if (entry->skb->ip_summed == CHECKSUM_HW &&
  23.                     (*errp = skb_checksum_help(entry->skb,
  24.                                                entry->info->outdev == NULL))) {
  25.                         read_unlock_bh(&queue_lock);
  26.                         return NULL;
  27.                 }
  28.                 /*如果用户需要拷贝整个数据包的内容,那么如果配置的长度为0或者超出数据包的实际长度,则以数据包的实际长度进行拷贝*/
  29.                 if (copy_range == 0 || copy_range > entry->skb->len)
  30.                         data_len = entry->skb->len;
  31.                 else
  32.                         data_len = copy_range;
  33.                 /*消息长度为netlink的消息头、ipq的消息头的长度以及要拷贝数据包的长度之和*/
  34.                 size = NLMSG_SPACE(sizeof(*pmsg) + data_len);
  35.                 break;
  36.        
  37.         default:
  38.                 *errp = -EINVAL;
  39.                 read_unlock_bh(&queue_lock);
  40.                 return NULL;
  41.         }

  42.         read_unlock_bh(&queue_lock);
  43.         /*为构建的消息申请内存,同样适用skb结构体,消息的内容应该在skb->data和skb->tail之间*/
  44.         skb = alloc_skb(size, GFP_ATOMIC);
  45.         if (!skb)
  46.                 goto nlmsg_failure;
  47.         /*记录一下新分配的skb中的tail指针的地址,此时应该skb->tail指向skb->data*/
  48.         old_tail= skb->tail;
  49.         /*填充netlink消息头*/
  50.         nlh = NLMSG_PUT(skb, 0, 0, IPQM_PACKET, size - sizeof(*nlh));
  51.         /*获取netlink消息体的起始地址,即ipq的消息头*/
  52.         pmsg = NLMSG_DATA(nlh);
  53.         memset(pmsg, 0, sizeof(*pmsg));

  54.         /*将相关的ipq消息记录到消息头中*/
  55.                 /*将skb对应的queue管理结构体的地址作为packet_id*/
  56.         pmsg->packet_id       = (unsigned long )entry;
  57.         pmsg->data_len        = data_len;
  58.         pmsg->timestamp_sec   = entry->skb->tstamp.off_sec;
  59.         pmsg->timestamp_usec  = entry->skb->tstamp.off_usec;
  60.         pmsg->mark            = entry->skb->nfmark;
  61.         pmsg->hook            = entry->info->hook;
  62.         pmsg->hw_protocol     = entry->skb->protocol;
  63.        
  64.         /*记录被queue数据包对应的输入设备名称*/
  65.         if (entry->info->indev)
  66.                 strcpy(pmsg->indev_name, entry->info->indev->name);
  67.         else
  68.                 pmsg->indev_name[0] = '\0';
  69.         /*记录被queue数据包对应的输出设备名称*/
  70.         if (entry->info->outdev)
  71.                 strcpy(pmsg->outdev_name, entry->info->outdev->name);
  72.         else
  73.                 pmsg->outdev_name[0] = '\0';

  74.         /*记录被queue数据包对应的链路层的协议类型及地址长度*/
  75.         if (entry->info->indev && entry->skb->dev) {
  76.                 pmsg->hw_type = entry->skb->dev->type;
  77.                 if (entry->skb->dev->hard_header_parse)
  78.                         pmsg->hw_addrlen =
  79.                                 entry->skb->dev->hard_header_parse(entry->skb,
  80.                                                                    pmsg->hw_addr);
  81.         }
  82.         /* data_len != 0 说明需要拷贝数据包的原始数据。skb_copy_bits 函数的实现不再分析,其主要功能就是将从skb->data+offset开始的数据拷贝data_len个字节到pmsg->payload中,即ipq消息的载荷。另外一个需要注意的问题,如果skb挂载的有分片包,则skb_copy_bits也会按照顺序拷贝分片包中的数据*/
  83.         if (data_len)
  84.                 if (skb_copy_bits(entry->skb, 0, pmsg->payload, data_len))
  85.                         BUG();
  86.         /*设置netlink消息的长度*/
  87.         nlh->nlmsg_len = skb->tail - old_tail;
  88.         return skb;

  89. nlmsg_failure:
  90.         if (skb)
  91.                 kfree_skb(skb);
  92.         *errp = -EINVAL;
  93.         printk(KERN_ERR "ip_queue: error creating packet message\n");
  94.         return NULL;
  95. }
复制代码

其次,对于netlink_unicast函数,我们这里不具体分析,它的作用就是将内核封装好的netlink消息发送到用户态。
最后分析一下__ipq_enqueue_entry 函数。
  1. static inline void
  2. __ipq_enqueue_entry(struct ipq_queue_entry *entry)
  3. {
  4.        list_add(&entry->list, &queue_list);
  5.        queue_total++;
  6. }
复制代码

该函数的功能很简答,就是将当前skb的queue管理结构添加到全局的queue管理链表queue_list中,并增加队列的统计计数。该链表记录了所有发往用户空间,且并未收到用户空间下发处理结果的数据包。
至此,数据包的入队处理函数已经分析完毕。一切顺利执行的话,现在用户态已经接收到关于该数据包的消息。

--未完待续

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
发表于 2010-01-04 22:05 |显示全部楼层
(五)接收和处理用户空间的配置—— 接收用户空间的数据包
        ip_queue模块加载的代码中有这样一行代码:
  1.         ipqnl = netlink_kernel_create(NETLINK_FIREWALL, 0, ipq_rcv_sk,
  2.                                       THIS_MODULE);
复制代码

        上文也已经分析了,该行代码就是注册一个用于接收用户空间配置数据的函数ipq_rcv_sk。因此,接收和处理用户空间的配置应该从这个函数开始分析。
        ipq_rcv_sk的代码如下:
  1.         static void
  2. ipq_rcv_sk(struct sock *sk, int len)
  3. {
  4.         struct sk_buff *skb;
  5.         unsigned int qlen;

  6.         mutex_lock(&ipqnl_mutex);
  7.                        
  8.         for (qlen = skb_queue_len(&sk->sk_receive_queue); qlen; qlen--) {
  9.                 skb = skb_dequeue(&sk->sk_receive_queue);
  10.                 ipq_rcv_skb(skb);
  11.                 kfree_skb(skb);
  12.         }
  13.                
  14.         mutex_unlock(&ipqnl_mutex);
  15. }
复制代码

该函数的功能非常明确,就从当前套接字的接收数据包队列中取出数据包,然后交给ipq_rcv_skb进一步分析和处理,然后就释放掉该数据包的缓存。
下面接着分析ipq_rcv_skb函数,其源码如下:
  1. #define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err)); return; } while (0)

  2. static inline void
  3. ipq_rcv_skb(struct sk_buff *skb)
  4. {
  5.         int status, type, pid, flags, nlmsglen, skblen;
  6.         struct nlmsghdr *nlh;
  7.         /*获取并检查数据包的长度*/
  8.         skblen = skb->len;
  9.         if (skblen < sizeof(*nlh))
  10.                 return;
  11.         /*获取netlink消息头部的结构体,并检查头部中保存的数据长度字段*/
  12.         nlh = (struct nlmsghdr *)skb->data;
  13.         nlmsglen = nlh->nlmsg_len;
  14.         if (nlmsglen < sizeof(*nlh) || skblen < nlmsglen)
  15.                 return;
  16.         /*取到用户态的进程ID,以及用户空间配置的标记为*/
  17.         pid = nlh->nlmsg_pid;
  18.         flags = nlh->nlmsg_flags;
  19.         /*判断用户进程的ID是否合法,以及该数据包是否是netlink的请求包*/
  20.         if(pid <= 0 || !(flags & NLM_F_REQUEST) || flags & NLM_F_MULTI)
  21.                 RCV_SKB_FAIL(-EINVAL);
  22.                
  23.         if (flags & MSG_TRUNC)
  24.                 RCV_SKB_FAIL(-ECOMM);
  25.         /*获取消息的类型并检查*/       
  26.         type = nlh->nlmsg_type;
  27.         if (type < NLMSG_NOOP || type >= IPQM_MAX)
  28.                 RCV_SKB_FAIL(-EINVAL);
  29.         /*如果不是ip_queue消息,则返回*/       
  30.         if (type <= IPQM_BASE)
  31.                 return;
  32.                
  33.         if (security_netlink_recv(skb, CAP_NET_ADMIN))
  34.                 RCV_SKB_FAIL(-EPERM);
  35.        
  36.         write_lock_bh(&queue_lock);
  37.         /*如果内核态记录的用户空间进程ID非0,且当前数据包的进程ID并不等于内核态记录的值,则意味着接收数据包失败*/
  38.         if (peer_pid) {
  39.                 if (peer_pid != pid) {
  40.                         write_unlock_bh(&queue_lock);
  41.                         RCV_SKB_FAIL(-EBUSY);
  42.                 }
  43.         } else {
  44.                 net_enable_timestamp();
  45. /*如果内核态记录的peer_pid 为0,则将当前消息中的进程ID记录到全局变量peer_pid 中。此种情形应该是用户态初次向内核发送ip_queue的消息*/
  46.                 peer_pid = pid;
  47.         }
  48.                
  49.         write_unlock_bh(&queue_lock);
  50.         /*处理用户空间下发的ip_queue配置消息*/
  51.         status = ipq_receive_peer(NLMSG_DATA(nlh), type,
  52.                                   nlmsglen - NLMSG_LENGTH(0));
  53.         if (status < 0)
  54.                 RCV_SKB_FAIL(status);
  55.                
  56.         if (flags & NLM_F_ACK)
  57.                 netlink_ack(skb, nlh, 0);
  58.         return;
  59. }
复制代码

该函数对于接收到的用户空间下发的消息,检查其多个参数的合法性,并判断是否是ip_queue的消息。如果是,则交由ipq_receive_peer函数进行消息的解析和处理。
下面我们分析一下ipq_receive_peer函数的实现,代码如下:
  1. static int
  2. ipq_receive_peer(struct ipq_peer_msg *pmsg,
  3.                  unsigned char type, unsigned int len)
  4. {
  5.         int status = 0;

  6.         if (len < sizeof(*pmsg))
  7.                 return -EINVAL;
  8.         /*根据用户态的消息类型进行处理*/
  9.         switch (type) {
  10.         case IPQM_MODE:
  11.                 status = ipq_set_mode(pmsg->msg.mode.value,
  12.                                       pmsg->msg.mode.range);
  13.                 break;
  14.                
  15.         case IPQM_VERDICT:
  16.                 if (pmsg->msg.verdict.value > NF_MAX_VERDICT)
  17.                         status = -EINVAL;
  18.                 else
  19.                         status = ipq_set_verdict(&pmsg->msg.verdict,
  20.                                                  len - sizeof(*pmsg));
  21.                         break;
  22.         default:
  23.                 status = -EINVAL;
  24.         }
  25.         return status;
  26. }
复制代码

该函数主要是根据用户态设置的消息的类型,进行不同的处理。我们在IP Queue分析的第一篇文章《Linux内核IP Queue机制的分析(一)¬——用户态接收数据包》(以下简称文一)中已经提到,用户态下发到内核态的消息分为“模式设置消息(IPQM_MODE)”和“断言消息(IPQM_VERDICT)”两个子类。
这里我们先分析消息类型为IPQM_MODE即模式设置消息时内核态的处理。对于断言消息IPQM_VERDICT的处理部分,我们将在后面第六节中分析。
模式设置消息对应的处理函数为ipq_set_mode,其代码如下:
  1. static int
  2. ipq_set_mode(unsigned char mode, unsigned int range)
  3. {
  4.         int status;

  5.         write_lock_bh(&queue_lock);
  6.         status = __ipq_set_mode(mode, range);
  7.         write_unlock_bh(&queue_lock);
  8.         return status;
  9. }
复制代码

该函数主要就是调用__ipq_set_mode函数,其代码如下:
  1. static inline int
  2. __ipq_set_mode(unsigned char mode, unsigned int range)
  3. {
  4.         int status = 0;
  5.        
  6.         switch(mode) {
  7.         case IPQ_COPY_NONE:
  8.         case IPQ_COPY_META:
  9.                 copy_mode = mode;
  10.                 copy_range = 0;
  11.                 break;
  12.                
  13.         case IPQ_COPY_PACKET:
  14.                 copy_mode = mode;
  15.                 copy_range = range;
  16.                 if (copy_range > 0xFFFF)
  17.                         copy_range = 0xFFFF;
  18.                 break;
  19.                
  20.         default:
  21.                 status = -EINVAL;

  22.         }
  23.         return status;
  24. }
复制代码

同样,我们在文一中分析过了模式设置消息的三种请求模式:IPQ_COPY_NONE、IPQ_COPY_META和IPQ_COPY_PACKET。参考文一中的具体解释,可以很轻松的理解该函数的实现:
(1)当请求模式为IPQ_COPY_NONE或IPQ_COPY_META时,记录下所设置的模式保存到全局变量copy_mode,并且将全局变量copy_range置0。因为这两种模式都不会要求读取数据包的原始内容;
(2)当请求模式为IPQ_COPY_PACKET时,记录下所设置的模式保存到全局变量copy_mode,并将用户空间设置的读取数据包的长度值保存到全局变量copy_range中。如果用户设置的长度大于0xFFFF,则将copy_range设置为0xFFFF。
        这里copy_mode和copy_range,以及前面提到的peer_pid都使用全局变量类型,因为内核态发送相关消息到用户空间时要读取这几个参数,并根据各参数值的不同,发送不同类型的消息到用户空间。
        通过模式设置消息,用户空间成功的告诉内核态,我需要数据包的哪些部分,只是摘要信息,还是连数据包的原始内容也要。如果需要数据包的原始内容,长度是多少等等。而内核态也会根据用户态下发的需求,将数据包的相关信息打包发给用户空间的接收进程。

--未完待续

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
发表于 2010-01-04 22:09 |显示全部楼层
(六)数据包的最终处理
        IP Queue机制提供了一种将数据包交给用户态处理的方法。那么,最后最关键的一步就是对数据包的处理。经过我们上面的分析,用户空间对数据包的处理,就是通过下发IP Queue消息,告诉内核态数据包是丢弃还是接受等。而内核态会根据具体的消息对数据包做真正的丢弃或接受等处理。
        那么,我们就从用户态下发的消息开始,一直分析到数据包最终被处理。
        我们上面分析了用户态下发的消息最终由__ipq_set_mode函数处理。该函数会根据消息类型调用对应的处理函数。用户态下发的数据包处理意见的消息,我们称之为“断言消息”,对应消息类型的宏定义为IPQM_VERDICT。该消息对应的处理函数为ipq_set_verdict。该函数代码如下:
  1. static int
  2. ipq_set_verdict(struct ipq_verdict_msg *vmsg, unsigned int len)
  3. {
  4.         struct ipq_queue_entry *entry;
  5.         /*检查对数据包处理意见的值的合法性*/
  6.         if (vmsg->value > NF_MAX_VERDICT)
  7.                 return -EINVAL;
  8.         /*根据断言消息中保存的id找到对应的数据包queue管理结构体*/
  9.         entry = ipq_find_dequeue_entry(id_cmp, vmsg->id);
  10.         if (entry == NULL)
  11.                 return -ENOENT;
  12.         else {
  13.                 /*记录断言消息的处理结果*/
  14.                 int verdict = vmsg->value;
  15.                 /*如果用户空间拷贝了数据包的原始内容,则要根据数据包被修改的情况调用函数ipq_mangle_ipv4对queue管理结构体中保存的原始skb进行修改*/
  16.                 if (vmsg->data_len && vmsg->data_len == len)
  17.                         if (ipq_mangle_ipv4(vmsg, entry) < 0)
  18.                                 verdict = NF_DROP;
  19.                 /*将数据包重新注入NF框架*/
  20.                 ipq_issue_verdict(entry, verdict);
  21.                 return 0;
  22.         }
  23. }
复制代码

        该函数首先根据断言消息的id,从全局链表queue_list中找到当前消息对应的queue管理结构体。然后,根据用户空间对数据包的修改和处理意见,对当前queue管理结构体中的skb进行修改。最后,调用函数ipq_issue_verdict将数据包重新注入NF框架。
        下面将逐一分析上面三个阶段的代码。
1. 获取断言消息对应的queue管理结构体
        ipq_set_verdict函数中实现代码如下:
  1. entry = ipq_find_dequeue_entry(id_cmp, vmsg->id);
复制代码

可见这里调用了ipq_find_dequeue_entry函数获取queue管理结构体的。传进去参数一个是函数指针id_cmp, 一个是断言消息的id。我们在文一中已经提到过,断言消息的id即内核态发送给用户空间消息中的packet_id。通过ipq_build_packet_message函数我们可以知道,packet_id就是当前数据包对应的queue管理结构体的地址。因此,找到queue管理结构体的方法,就是遍历queue_list全局链表,找到地址等于vmsg->id的queue管理结构体。
因此,不难想象,id_cmp函数的实现代码:
  1. static inline int
  2. id_cmp(struct ipq_queue_entry *e, unsigned long id)
  3. {
  4.         return (id == (unsigned long )e);
  5. }
复制代码

那么,函数ipq_find_dequeue_entry的代码实现的方法也就很明确了:
  1. static struct ipq_queue_entry *
  2. ipq_find_dequeue_entry(ipq_cmpfn cmpfn, unsigned long data)
  3. {
  4.         struct ipq_queue_entry *entry;
  5.        
  6.         write_lock_bh(&queue_lock);
  7.         entry = __ipq_find_dequeue_entry(cmpfn, data);
  8.         write_unlock_bh(&queue_lock);
  9.         return entry;
  10. }
复制代码

该函数进一步调用函数__ipq_find_dequeue_entry,代码如下:
  1. static inline struct ipq_queue_entry *
  2. __ipq_find_dequeue_entry(ipq_cmpfn cmpfn, unsigned long data)
  3. {
  4.         struct ipq_queue_entry *entry;

  5.         entry = __ipq_find_entry(cmpfn, data);
  6.         if (entry == NULL)
  7.                 return NULL;

  8.         __ipq_dequeue_entry(entry);
  9.         return entry;
  10. }
复制代码

该函数首先调用__ipq_find_entry,遍历全局链表queue_list,找到当前断言消息对应数据包的queue管理结构体:
  1. /*
  2. * Find and return a queued entry matched by cmpfn, or return the last
  3. * entry if cmpfn is NULL.
  4. */
  5. static inline struct ipq_queue_entry *
  6. __ipq_find_entry(ipq_cmpfn cmpfn, unsigned long data)
  7. {
  8.         struct list_head *p;

  9.         list_for_each_prev(p, &queue_list) {
  10.                 struct ipq_queue_entry *entry = (struct ipq_queue_entry *)p;
  11.                
  12.                 if (!cmpfn || cmpfn(entry, data))
  13.                         return entry;
  14.         }
  15.         return NULL;
  16. }
复制代码

然后将调用函数__ipq_dequeue_entry将找到的queue管理结构体从全局链表queue_list中删除,并减小被queue的数据包的统计计数。
  1. static inline void
  2. __ipq_dequeue_entry(struct ipq_queue_entry *entry)
  3. {
  4.         list_del(&entry->list);
  5.         queue_total--;
  6. }
复制代码

至此,我们已经找到了断言消息对应数据包的queue管理结构体。
2. 根据用户的配置修改数据包
        ipq_set_verdict函数中实现代码如下:
  1. int verdict = vmsg->value;

  2. if (vmsg->data_len && vmsg->data_len == len)
  3.         if (ipq_mangle_ipv4(vmsg, entry) < 0)
  4.                 verdict = NF_DROP;
复制代码

        这里,首先记录下对数据包的处理结果vmsg->value。然后,比较关键的一步,就是如果断言消息中数据长度部分不为0,并且消息中记录的载荷长度等于当前计算出来的载荷长度(如果正常进行设置和通信的话,两者应该是相等的),则调用ipq_mangle_ipv4对原始的数据包进行处理。
        函数ipq_mangle_ipv4的代码如下:
  1. static int
  2. ipq_mangle_ipv4(ipq_verdict_msg_t *v, struct ipq_queue_entry *e)
  3. {
  4.         int diff;
  5.         /*获得用户空间处理后的数据包的ip头部*/
  6.         struct iphdr *user_iph = (struct iphdr *)v->payload;
  7.         /*判断消息中记录的载荷长度是否小于头部长度。如果是,则就不再修改queue中保存的skb*/
  8.         if (v->data_len < sizeof(*user_iph))
  9.                 return 0;
  10.         /*判断数据包的长度是否发生变化,即获取用户修改后的数据包和原始数据包的长度的关系*/
  11.         diff = v->data_len - e->skb->len;
  12.         /*用户减小了数据包的长度*/
  13.         if (diff < 0)
  14.                 skb_trim(e->skb, v->data_len);
  15.         /*用户增大了数据包的长度*/
  16.         else if (diff > 0) {
  17.                 /*非法的数据包长度值*/
  18.                 if (v->data_len > 0xFFFF)
  19.                         return -EINVAL;
  20.                 /*数据包被扩充的长度大于了skb的tailroom,即skb的tail:end之间的长度已经不能容纳数据包增加的数据,这是需要新分配一个skb*/
  21.                 if (diff > skb_tailroom(e->skb)) {
  22.                         struct sk_buff *newskb;
  23.                         /*将原先的skb进行扩充,并将原始skb的数据包拷贝到新的skb中*/
  24.                         newskb = skb_copy_expand(e->skb,
  25.                                                  skb_headroom(e->skb),
  26.                                                  diff,
  27.                                                  GFP_ATOMIC);
  28.                         if (newskb == NULL) {
  29.                                 printk(KERN_WARNING "ip_queue: OOM "
  30.                                       "in mangle, dropping packet\n");
  31.                                 return -ENOMEM;
  32.                         }
  33.                         if (e->skb->sk)
  34.                                 skb_set_owner_w(newskb, e->skb->sk);
  35.                         kfree_skb(e->skb);
  36.                         e->skb = newskb;
  37.                 }
  38.                 /*将skb的tail指针增加tail+diff*/
  39.                 skb_put(e->skb, diff);
  40.         }
  41.         if (!skb_make_writable(&e->skb, v->data_len))
  42.                 return -ENOMEM;
  43.         /*将断言消息中全部的载荷拷贝到skb对应的数据区中。*/
  44.         memcpy(e->skb->data, v->payload, v->data_len);
  45.         e->skb->ip_summed = CHECKSUM_NONE;

  46.         return 0;
  47. }
复制代码

该函数主要就是断言消息中的对数据包做的任何修改都反应到原始的skb中。
3. 将修改后的数据包重新注入NF框架
ipq_set_verdict函数实现的代码如下:
  1.         ipq_issue_verdict(entry, verdict);
复制代码

        即调用ipq_issue_verdict函数将数据包重新注入NF中。该函数的代码如下:
  1. static void
  2. ipq_issue_verdict(struct ipq_queue_entry *entry, int verdict)
  3. {
  4.         /* TCP input path (and probably other bits) assume to be called
  5.          * from softirq context, not from syscall, like ipq_issue_verdict is
  6.          * called.  TCP input path deadlocks with locks taken from timer
  7.          * softirq, e.g.  We therefore emulate this by local_bh_disable() */

  8.         local_bh_disable();
  9.         nf_reinject(entry->skb, entry->info, verdict);
  10.         local_bh_enable();

  11.         kfree(entry);
  12. }
复制代码

该函数将queue管理结构体的两个个成员skb、info,以及用户对数据包的处理结果verdict作为入参,直接调用nf_reinject函数。nf_reinject函数会根据三个参数所保存的相关信息,对数据包进行真正的处理,比入交给下一个hook函数处理(NF_ACCEPT),或者丢弃(NF_DROP),或者重新交给当前再次交给当前的hook函数处理(NF_REPEAT)。
至此,整个ip_queue模块的核心代码已经分析完毕。

--完

论坛徽章:
0
发表于 2010-01-05 02:46 |显示全部楼层
支持下.

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
发表于 2010-01-05 12:47 |显示全部楼层
好文

论坛徽章:
0
发表于 2010-01-05 13:06 |显示全部楼层
好文好文  再加上前面两篇  ip queue就全了^_^

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
发表于 2010-01-05 13:10 |显示全部楼层

回复 #9 ubuntuer 的帖子

是啊,IP  Queue已经系统的的分析完了。从用户空间的使用到内核空间的源码。

惭愧的是,完成这三篇文章竟然拖了这么长时间。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP