本文檔的Copyleft歸yfydz所有,使用GPL發布,可以自由拷貝,轉載,轉載時請保持文檔的完整性,嚴禁用於任何商業用途。
msn: yfydz_no1@hotmail.com
來源:http://yfydz.cublog.cn1. 前言 ip_queue是netfilter提供的將網路資料包從核心傳遞到使用者空間的方法,核心中要提供ip_queue支援,在使用者層空間開啟一 個netlink的socket後就可以接受核心通過ip_queue所傳遞來的網路資料包,具體資料包類型可由iptables命令來確定,只要將規則 動作設定為“-j QUEUE”即可。 之所以要命名為ip_queue,是因為這是一個隊列處理過程,iptables規則把指定的包發給QUEUE是一個資料進入隊列的過程,而使用者空間程式通過netlink socket擷取資料包進行裁定,結果返回核心,進行出隊列的操作。 在iptables代碼中,提供了libipq庫,封裝了對ipq的一些操作,使用者層程式可以直接使用libipq庫函數處理資料。 2. 使用者層介面:libipq libipq主要是在iptables-<version>/libipq/libipq.c中實現,提供了以下函數: //建立ipq的handle:
struct ipq_handle *ipq_create_handle(u_int32_t flags, u_int32_t protocol); // 釋放ipq handle
int ipq_destroy_handle(struct ipq_handle *h); // 讀取資料到buf中
ssize_t ipq_read(const struct ipq_handle *h,
unsigned char *buf, size_t len, int timeout); // 設定ipq拷貝模式
int ipq_set_mode(const struct ipq_handle *h, u_int8_t mode, size_t len); // 從buf中解析資料包結構
ipq_packet_msg_t *ipq_get_packet(const unsigned char *buf); // 返回包的類型
int ipq_message_type(const unsigned char *buf); // 設定對資料包的裁決
int ipq_set_verdict(const struct ipq_handle *h,
ipq_id_t id,
unsigned int verdict,
size_t data_len,
unsigned char *buf); 有了libipq,使用者層程式就很簡單了,libipq.3中提供了一個執行個體,比較簡單,只列出,不再贅述。/*
* This code is GPL.
*/
#include <linux/netfilter.h>
#include <libipq.h>
#include <stdio.h>#define BUFSIZE 2048 static void die(struct ipq_handle *h)
{
ipq_perror("passer");
ipq_destroy_handle(h);
exit(1);
}int main(int argc, char **argv)
{
int status;
unsigned char buf[BUFSIZE];
struct ipq_handle *h;
h = ipq_create_handle(0, PF_INET);
if (!h)
die(h);
status = ipq_set_mode(h, IPQ_COPY_PACKET, BUFSIZE);
if (status < 0)
die(h);
do{
status = ipq_read(h, buf, BUFSIZE, 0);
if (status < 0)
die(h);
switch (ipq_message_type(buf)) {
case NLMSG_ERROR:
fprintf(stderr, "Received error message %d//n",
ipq_get_msgerr(buf));
break;
case IPQM_PACKET: {
ipq_packet_msg_t *m = ipq_get_packet(buf);
status = ipq_set_verdict(h, m->packet_id,
NF_ACCEPT, 0, NULL);
if (status < 0)
die(h);
break;
}
default:
fprintf(stderr, "Unknown message type!//n");
break;
}
} while (1);
ipq_destroy_handle(h);
return 0;
}3. 核心:資料包進入隊列 以下核心代碼版本為2.4.26。 在net/core/netfilter.c中的對於要進行動作NF_QUEUE的資料處理流程為: nf_hook_slow()->nf_queue->queue_handler[pf].outfn 如果ip_queue模組有效,這個queue_handler[pf].outfn函數實際上是對應ipq_enqueue_packet()函數(net/ipv4/netfilter/ip_queue.c),這是通過下面的函數進行登記的:/* net/ipv4/netfilter/ip_queue.c */
...
status = nf_register_queue_handler(PF_INET, ipq_enqueue_packet, NULL);
... ipq_enqueue_packet()函數: static int
ipq_enqueue_packet(struct sk_buff *skb, struct nf_info *info, void *data)
{
int status = -EINVAL;
struct sk_buff *nskb;
struct ipq_queue_entry *entry;// copy_mode是一個全域變數,IPQ_COPY_NONE表示還沒初始化,資料包會被丟棄
// 通常要初始化為IPQ_COPY_META(只拷貝META資訊到使用者層)或
// IPQ_COPY_PACKET(拷貝全部資訊到使用者層) if (copy_mode == IPQ_COPY_NONE)
return -EAGAIN; // 記錄資料包的相關資訊,包括其路由資訊
entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
if (entry == NULL) {
printk(KERN_ERR "ip_queue: OOM in ipq_enqueue_packet()/n");
return -ENOMEM;
} entry->info = info;
entry->skb = skb; if (entry->info->hook == NF_IP_LOCAL_OUT) {
// 在OUTPUT點進行QUEUE時記錄相關路由資訊:TOS,源、目的IP
struct iphdr *iph = skb->nh.iph; entry->rt_info.tos = iph->tos;
entry->rt_info.daddr = iph->daddr;
entry->rt_info.saddr = iph->saddr;
} // 產生一個新的skb包,該包中儲存關於entry的資訊,其資料部分是準備傳遞給使用者// 空間的資料結構也就是libipq所讀取的資料內容,如果拷貝模式是IPQ_COPY_META,// 只包含ipq資料頭資訊;如果是IPQ_COPY_PACKET,在ipq資料頭後拷貝整個skb包// IP資料資訊
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; /* netlink_unicast will either free the nskb or attach it to a socket */
// 將該skb附加到使用者層開啟的netlink socket上,放到其等待隊列中,如果不成功這丟棄該包
// ipqnl是ip_queue對應的netlink sock
// peer_pid使用者空間程式的pid
status = netlink_unicast(ipqnl, nskb, peer_pid, MSG_DONTWAIT);
if (status < 0)
goto err_out_unlock; // 將entry資訊入QUEUE隊列,等待使用者層的處理結果,如果隊列滿則丟棄該包
status = __ipq_enqueue_entry(entry);
if (status < 0)
goto err_out_unlock; 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;
} 所附加的META資料是這樣一個結構: /* include/linux/netlink.h */
struct nlmsghdr
{
__u32 nlmsg_len; /* Length of message including header */
__u16 nlmsg_type; /* Message content */
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process PID */
}; 一旦資料進入了netlink sock的輸入隊列中,使用者層對資料的讀取就由netlink sock來處理了,ip_queue就不再管了,ip_queue只需要處理從使用者層發來的資料,從使用者層看是對netlink socket的寫,從核心的ip_queue看是使用者層資料的資料讀取過程。4. 核心:讀取使用者層資料 ip_queue要讀取netlink socket中返回的處理資料結果,函數流程為: ipq_rcv_sk()
|
V
ipq_rcv_skb()
|
V
ipq_receive_peer()
|
|------------------------------+
V V
ipq_set_verdict() ipq_set_mode()
| |
V V
ipq_find_dequeue_entry() __ipq_set_mode()
ipq_issue_verdict()
|
V
nf_reinject()在模組初始化時建立netlink sock: ipqnl = netlink_kernel_create(NETLINK_FIREWALL, ipq_rcv_sk); 其接收資料函數為ipq_rcv_sk(): static void
ipq_rcv_sk(struct sock *sk, int len)
{
do {
struct sk_buff *skb; if (down_trylock(&ipqnl_sem))
return;
// 從sock的等待隊列中取出skb
while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
// 接收skb內容,skb中的資料格式和發送skb到ipq的格式是一樣的,前面是ipq的// 控制頭,即META部分, 後面才是真正的skb中的資料
ipq_rcv_skb(skb);
// 丟棄skb包,這個skb本來就不是正常的網路skb,而是ipq通訊的skb
kfree_skb(skb);
}
up(&ipqnl_sem); } while (ipqnl && ipqnl->receive_queue.qlen);
} ipq_rcv_skb()函數本身都是再為ipq_receive_peer()函數作準備,忽略;ipq_receive_peer函數: static int
ipq_receive_peer(struct ipq_peer_msg *pmsg,
unsigned char type, unsigned int len)
{
int status = 0; if (len < sizeof(*pmsg))
return -EINVAL; switch (type) {
// 設定IPQ的拷貝模式:IPQ_COPY_META or IPQ_COPY_PACKET
case IPQM_MODE:
status = ipq_set_mode(pmsg->msg.mode.value,
pmsg->msg.mode.range);
break;// 處理資料包的裁決
case IPQM_VERDICT:
if (pmsg->msg.verdict.value > NF_MAX_VERDICT)
status = -EINVAL;
else
status = ipq_set_verdict(&pmsg->msg.verdict,
len - sizeof(*pmsg));
break;
default:
status = -EINVAL;
}
return status;
} ipq_set_verdict()函數: static int
ipq_set_verdict(struct ipq_verdict_msg *vmsg, unsigned int len)
{
struct ipq_queue_entry *entry; if (vmsg->value > NF_MAX_VERDICT)
return -EINVAL;// 根據包的ID找出以前放入QUEUE隊列中的ipq_queue_entry結構,該結構儲存// 最初的skb包的地址
entry = ipq_find_dequeue_entry(id_cmp, vmsg->id);
if (entry == NULL)
return -ENOENT;
else {
int verdict = vmsg->value;
if (vmsg->data_len && vmsg->data_len == len)
// 如果資料被使用者層修改,將修改後的資訊替換skb中原來的資訊
if (ipq_mangle_ipv4(vmsg, entry) < 0)
verdict = NF_DROP;
// 最終進行裁定
ipq_issue_verdict(entry, verdict);
return 0;
}
} ipq_issue_verdict()函數: static void
ipq_issue_verdict(struct ipq_queue_entry *entry, int verdict)
{
// 所有QUEUE的包都要由該函數返回netfilter,在net/core/netfilter.c中定義
nf_reinject(entry->skb, entry->info, verdict);
kfree(entry);
} 5. 結論 ip_queue工具的提供使得很多在核心裡不太容易實現的功能可以放到使用者層空間內實現,處理安全性高,畢竟核心中的錯誤會導致系統崩潰,而使用者層程式的出錯不影響系統的整體運行,當然這是以效能降低為代價的。 ip_queue隊列實現是使用queue_handler的,queue_handler對於每個協議族只支援一個隊列,所以如果有兩個需要使用queue功能的應用就會發生衝突,如實現QoS的IMQ也使用這個隊列,因此兩者在核心中是不能共存的。