- 論壇徽章:
- 36
|
(三)ip_queue報文入隊處理函數(shù)的注冊
在上面分析的模塊注冊代碼中,IP Queue的報文入隊處理函數(shù)的注冊是通過調(diào)用nf_register_queue_handler()來實現(xiàn)的。因此,有必要了解一下該函數(shù)的源碼,源碼位于nf_queue.c中:
- /* return EBUSY when somebody else is registered, return EEXIST if the
- * same handler is registered, return 0 in case of success. */
- int nf_register_queue_handler(int pf, struct nf_queue_handler *qh)
- {
- int ret;
- /*IP協(xié)議族的值必須在當前指定的范圍內(nèi)*/
- if (pf >= NPROTO)
- return -EINVAL;
- write_lock_bh(&queue_handler_lock);
- /*該queue handler已經(jīng)被注冊過了*/
- if (queue_handler[pf] == qh)
- ret = -EEXIST;
- /*該協(xié)議族已經(jīng)被注冊了handler了*/
- else if (queue_handler[pf])
- ret = -EBUSY;
- /*將該協(xié)議的queue hanler指向參數(shù)qh*/
- else {
- queue_handler[pf] = qh;
- ret = 0;
- }
- write_unlock_bh(&queue_handler_lock);
- return ret;
- }
復制代碼
該函數(shù)的代碼比較簡單,先來了解一下函數(shù)的兩個參數(shù):
(1)pf:IP協(xié)議族的值,PF_INET和PF_INET6分別代表IPv4和IPv6。
(2)qh:NF中對報文進行Queue的結構體,定義如下(netfilter.h):
- /* Packet queuing */
- struct nf_queue_handler {
- int (*outfn)(struct sk_buff *skb, struct nf_info *info,
- unsigned int queuenum, void *data);
- void *data;
- char *name;
- };
復制代碼
由此可見,該結構體主要包含一個函數(shù)指針,用于處理NF框架需要Queue的報文。data應該是用來保存一些私有數(shù)據(jù),name則是該queue handler的名稱。
代碼中已經(jīng)包含了該函數(shù)源碼的簡單注釋,這里再對該函數(shù)進行一下簡單的總結:
(1) 每個協(xié)議族只能注冊一個queue handler;
(2) 隨后報文的處理中,可以根據(jù)報文的協(xié)議族,就可以找到報文進行Queue時的處理函數(shù)queue_handler[pf]->outfn()。
在IP Queue模塊中,queue handler的注冊如下所示:
- status = nf_register_queue_handler(PF_INET, &nfqh);
復制代碼
可見,注冊的是IPv4協(xié)議族的報文處理函數(shù),而nfqh結構體的定義如下:
- static struct nf_queue_handler nfqh = {
- .name = "ip_queue",
- .outfn = &ipq_enqueue_packet,
- };
復制代碼
這里,IP Queue處理報文的函數(shù)終于閃亮登場了,我們前面啰嗦了半天,主要就是想理順一下思路,順理成章的引出該函數(shù)。
(四)入隊函數(shù)ipq_enqueue_packet —— 發(fā)送數(shù)據(jù)包到用戶空間
第二部分已經(jīng)分析過了該函數(shù)會在什么條件下被觸發(fā)。這里詳細分析該函數(shù)的實現(xiàn),代碼在ip_queue.c中。
分析代碼之前,我們先了解一下IP Queue對數(shù)據(jù)包隊列管理的核心數(shù)據(jù)結構:
- struct ipq_queue_entry {
- struct list_head list;
- struct nf_info *info;
- struct sk_buff *skb;
- };
復制代碼
所有被Queue到用戶空間的數(shù)據(jù)包都會有這樣一個結構體。其中,
該數(shù)據(jù)結構的第1個元素是個雙向鏈表結構,用于將所有queue的數(shù)據(jù)包用雙向鏈表連
接起來,實現(xiàn)隊列管理。
第2個元素同樣是一個結構體,其定義在netfilter.h中:
- /* Each queued (to userspace) skbuff has one of these. */
- struct nf_info
- {
- /* The ops struct which sent us to userspace. */
- struct nf_hook_ops *elem;
-
- /* If we're sent to userspace, this keeps housekeeping info */
- int pf;
- unsigned int hook;
- struct net_device *indev, *outdev;
- int (*okfn)(struct sk_buff *);
- };
復制代碼
這個結構體應該很容易看出他的作用,就是記錄下當數(shù)據(jù)包被Queue時所在的hook函數(shù)的相關信息,包括hook的操作結構、協(xié)議號、hook點等相關信息。當用戶空間下發(fā)了對數(shù)據(jù)包的處理結果時,內(nèi)核就需要參考這個結構對數(shù)據(jù)包進行進一步的處理。具體的我們會在后面數(shù)據(jù)包回注函數(shù)中分析。
第3個元素是skb本身,也就是回注函數(shù)中要處理的數(shù)據(jù)包。
OK,我們下面就開始如對函數(shù)的分析。
- static int
- ipq_enqueue_packet(struct sk_buff *skb, struct nf_info *info,
- unsigned int queuenum, void *data)
- {
- int status = -EINVAL;
- struct sk_buff *nskb;
- struct ipq_queue_entry *entry;
- /*判斷用戶配置的模式,可以為拷貝元數(shù)據(jù)或者整個數(shù)據(jù)包的信息*/
- if (copy_mode == IPQ_COPY_NONE)
- return -EAGAIN;
- /*為即將入隊的數(shù)據(jù)包分配一個struct ipq_queue_entry 結構體,用于實現(xiàn)對數(shù)據(jù)包的管理,我們稱之為queue管理結構體*/
- entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
- if (entry == NULL) {
- printk(KERN_ERR "ip_queue: OOM in ipq_enqueue_packet()\n");
- return -ENOMEM;
- }
- /*將該數(shù)據(jù)包對應的相關信息保存到管理結構體中*/
- entry->info = info;
- entry->skb = skb;
-
- /*構建用于發(fā)往用戶空間的消息*/
- nskb = ipq_build_packet_message(entry, &status);
- if (nskb == NULL)
- goto err_out_free;
-
- write_lock_bh(&queue_lock);
-
- /如果用戶空間沒有開啟進程,等待接收消息的話,就釋放該消息/
- if (!peer_pid)
- goto err_out_free_nskb;
- /*如果當前隊列中的數(shù)據(jù)包總數(shù)超過了設置的最大值,則是放該消息,并且增加丟棄數(shù)據(jù)包的統(tǒng)計計數(shù)*/
- if (queue_total >= queue_maxlen) {
- queue_dropped++;
- status = -ENOSPC;
- if (net_ratelimit())
- printk (KERN_WARNING "ip_queue: full at %d entries, "
- "dropping packets(s). Dropped: %d\n", queue_total,
- queue_dropped);
- goto err_out_free_nskb;
- }
- /* 將消息發(fā)送給用戶空間 */
- status = netlink_unicast(ipqnl, nskb, peer_pid, MSG_DONTWAIT);
- if (status < 0) {
- queue_user_dropped++;
- goto err_out_unlock;
- }
- /*成功發(fā)送到用戶空間之后,將該數(shù)據(jù)包的queue管理結構添加到全局隊列鏈表中*/
- __ipq_enqueue_entry(entry);
- write_unlock_bh(&queue_lock);
- return status;
- err_out_free_nskb:
- kfree_skb(nskb);
-
- err_out_unlock:
- write_unlock_bh(&queue_lock);
- err_out_free:
- kfree(entry);
- return status;
- }
復制代碼
該函數(shù)成功執(zhí)行之后,就將數(shù)據(jù)包相關的信息發(fā)送到用戶空間,同時將該數(shù)據(jù)包對應的queue管理結構加到全局鏈表中。
下面對于ipq_enqueue_packet函數(shù)中調(diào)用的幾個重要函數(shù)做一些分析。
首先是構建消息的函數(shù)ipq_build_packet_message。
- static struct sk_buff *
- ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp)
- {
- unsigned char *old_tail;
- size_t size = 0;
- size_t data_len = 0;
- struct sk_buff *skb;
- struct ipq_packet_msg *pmsg;
- struct nlmsghdr *nlh;
- read_lock_bh(&queue_lock);
- /*根據(jù)用戶配置的copy模式,確定發(fā)給用戶空間消息的長度*/
- switch (copy_mode) {
- /*對于初始模式和拷貝元數(shù)據(jù)的模式,消息應該包括netlink的消息頭和ipq的消息頭,長度為兩個消息頭長度之和,即sizeof(struct nlmsghdr) + sizeof(struct ipq_packet_msg),并考慮對齊的因素。這里直接調(diào)用netlink封裝的宏NLMSG_SPACE來計算長度。該宏本身已經(jīng)包含了netlink消息頭的長度*/
- case IPQ_COPY_META:
- case IPQ_COPY_NONE:
- /*消息長度為netlink的消息頭和ipq的消息頭的長度之和*/
- size = NLMSG_SPACE(sizeof(*pmsg));
- data_len = 0;
- break;
-
- case IPQ_COPY_PACKET:
- if (entry->skb->ip_summed == CHECKSUM_HW &&
- (*errp = skb_checksum_help(entry->skb,
- entry->info->outdev == NULL))) {
- read_unlock_bh(&queue_lock);
- return NULL;
- }
- /*如果用戶需要拷貝整個數(shù)據(jù)包的內(nèi)容,那么如果配置的長度為0或者超出數(shù)據(jù)包的實際長度,則以數(shù)據(jù)包的實際長度進行拷貝*/
- if (copy_range == 0 || copy_range > entry->skb->len)
- data_len = entry->skb->len;
- else
- data_len = copy_range;
- /*消息長度為netlink的消息頭、ipq的消息頭的長度以及要拷貝數(shù)據(jù)包的長度之和*/
- size = NLMSG_SPACE(sizeof(*pmsg) + data_len);
- break;
-
- default:
- *errp = -EINVAL;
- read_unlock_bh(&queue_lock);
- return NULL;
- }
- read_unlock_bh(&queue_lock);
- /*為構建的消息申請內(nèi)存,同樣適用skb結構體,消息的內(nèi)容應該在skb->data和skb->tail之間*/
- skb = alloc_skb(size, GFP_ATOMIC);
- if (!skb)
- goto nlmsg_failure;
- /*記錄一下新分配的skb中的tail指針的地址,此時應該skb->tail指向skb->data*/
- old_tail= skb->tail;
- /*填充netlink消息頭*/
- nlh = NLMSG_PUT(skb, 0, 0, IPQM_PACKET, size - sizeof(*nlh));
- /*獲取netlink消息體的起始地址,即ipq的消息頭*/
- pmsg = NLMSG_DATA(nlh);
- memset(pmsg, 0, sizeof(*pmsg));
- /*將相關的ipq消息記錄到消息頭中*/
- /*將skb對應的queue管理結構體的地址作為packet_id*/
- pmsg->packet_id = (unsigned long )entry;
- pmsg->data_len = data_len;
- pmsg->timestamp_sec = entry->skb->tstamp.off_sec;
- pmsg->timestamp_usec = entry->skb->tstamp.off_usec;
- pmsg->mark = entry->skb->nfmark;
- pmsg->hook = entry->info->hook;
- pmsg->hw_protocol = entry->skb->protocol;
-
- /*記錄被queue數(shù)據(jù)包對應的輸入設備名稱*/
- if (entry->info->indev)
- strcpy(pmsg->indev_name, entry->info->indev->name);
- else
- pmsg->indev_name[0] = '\0';
- /*記錄被queue數(shù)據(jù)包對應的輸出設備名稱*/
- if (entry->info->outdev)
- strcpy(pmsg->outdev_name, entry->info->outdev->name);
- else
- pmsg->outdev_name[0] = '\0';
- /*記錄被queue數(shù)據(jù)包對應的鏈路層的協(xié)議類型及地址長度*/
- if (entry->info->indev && entry->skb->dev) {
- pmsg->hw_type = entry->skb->dev->type;
- if (entry->skb->dev->hard_header_parse)
- pmsg->hw_addrlen =
- entry->skb->dev->hard_header_parse(entry->skb,
- pmsg->hw_addr);
- }
- /* data_len != 0 說明需要拷貝數(shù)據(jù)包的原始數(shù)據(jù)。skb_copy_bits 函數(shù)的實現(xiàn)不再分析,其主要功能就是將從skb->data+offset開始的數(shù)據(jù)拷貝data_len個字節(jié)到pmsg->payload中,即ipq消息的載荷。另外一個需要注意的問題,如果skb掛載的有分片包,則skb_copy_bits也會按照順序拷貝分片包中的數(shù)據(jù)*/
- if (data_len)
- if (skb_copy_bits(entry->skb, 0, pmsg->payload, data_len))
- BUG();
- /*設置netlink消息的長度*/
- nlh->nlmsg_len = skb->tail - old_tail;
- return skb;
- nlmsg_failure:
- if (skb)
- kfree_skb(skb);
- *errp = -EINVAL;
- printk(KERN_ERR "ip_queue: error creating packet message\n");
- return NULL;
- }
復制代碼
其次,對于netlink_unicast函數(shù),我們這里不具體分析,它的作用就是將內(nèi)核封裝好的netlink消息發(fā)送到用戶態(tài)。
最后分析一下__ipq_enqueue_entry 函數(shù)。
- static inline void
- __ipq_enqueue_entry(struct ipq_queue_entry *entry)
- {
- list_add(&entry->list, &queue_list);
- queue_total++;
- }
復制代碼
該函數(shù)的功能很簡答,就是將當前skb的queue管理結構添加到全局的queue管理鏈表queue_list中,并增加隊列的統(tǒng)計計數(shù)。該鏈表記錄了所有發(fā)往用戶空間,且并未收到用戶空間下發(fā)處理結果的數(shù)據(jù)包。
至此,數(shù)據(jù)包的入隊處理函數(shù)已經(jīng)分析完畢。一切順利執(zhí)行的話,現(xiàn)在用戶態(tài)已經(jīng)接收到關于該數(shù)據(jù)包的消息。
--未完待續(xù) |
|