翻譯來自這里
介紹
NFQUEUE是一個iptables和ip6tables目標,它將數據包的決定委托給用戶空間軟件。 例如,以下規則將要求所有去往包的數據包都要聽取用戶安全計劃的決定:
iptables -A INPUT -j NFQUEUE --queue-num 0
在用戶空間中,一個軟件必須使用libnetfilter_queue連接到隊列0(默認的)并從內核獲取消息。 然后它必須對包進行判決。
內在工作
要理解NFQUEUE,最簡單的方法就是了解Linux內核的架構。 當一個數據包到達一個NFQUEUE目標時,它就會進入與--queue-num選項給定的編號對應的隊列。 數據包隊列被實現為鏈接列表,元素是數據包和元數據(Linux內核skb):
- 這是一個固定長度的隊列,實現為數據包的鏈接列表。
- 存儲由整數索引的數據包
- 當用戶空間向相應的索引整數發出判決時,數據包被釋放
- 當隊列滿時,沒有數據包可以入隊
這在用戶空間方面有一些含義:
- 用戶空間可以讀取多個數據包并等待判決。 如果隊列未滿,則不會有這種行為的影響。
- 數據包可以在沒有命令的情況下被判決。 用戶空間可以讀取數據包1,2,3,4并以4,2,3,1的順序依次判斷。
- 太慢的判決將導致一個完整的隊列。 然后內核將丟棄傳入的數據包,而不是將它們排入隊列。
關于內核和用戶空間之間的協議有幾句話
內核和用戶空間之間使用的協議是nfnetlink。 這是一個基于消息的協議,不涉及任何共享內存。 當一個數據包進入隊列時,內核發送一個包含數據包數據和相關信息的nfnetlink格式的消息到一個套接字,用戶空間讀取這個消息。 為了做出判定,用戶空間格式化一個包含索引號的nfnetlink消息并發送給通信套接字。
在C中使用libnetfilter_queue
libnetfiler_queue的主要信息來源是Doxygen生成的文檔。
庫的使用有三個步驟:
- 軟件庫連接到給定隊列并設置一些選項的庫設置。
- 消息接收階段調用每個數據包接收一個回調。
- 在關閉時候nfq_close被調用。
如果您想查看產品代碼,可以查看一下libnetfilter_queue的多線程實現suricata中的source-nfq.c。
示例軟件架構
最簡單的架構是由一個線程讀取數據包并發布判決。 下面的代碼不完整,但顯示了實現的邏輯。
/* Definition of callback function */
static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,
struct nfq_data *nfa, void *data)
{
int verdict;
u_int32_t id = treat_pkt(nfa, &verdict); /* Treat packet */
return nfq_set_verdict(qh, id, verdict, 0, NULL); /* Verdict packet */
}
/* Set callback function */
qh = nfq_create_queue(h, 0, &cb, NULL);
for (;;) {
if ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) {
nfq_handle_packet(h, buf, rv); /* send packet to callback */
continue;
}
}
讀線程和判決線程也是可能的:
PacketPool *ppool;
/* Definition of callback function */
static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,
struct nfq_data *nfa, void *data)
{
/* Simply copy packet date and send them to a packet pool */
return push_packet_to_pool(ppool, nfa);
}
int main() {
/* Set callback function */
qh = nfq_create_queue(h, 0, &cb, NULL);
/* create reading thread */
pthread_create(read_thread_id, NULL, read_thread, qh);
/* create verdict thread */
pthread_create(write_thread_id, NULL, verdict_thread, qh);
/* ... */
}
static void *read_thread(void *fd)
{
for (;;) {
if ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) {
nfq_handle_packet(h, buf, rv); /* send packet to callback */
continue;
}
}
}
static void *verdict_thread(void *fd)
{
for (;;) {
Packet p = fetch_packet_from_pool(ppool);
u_int32_t id = treat_pkt(nfa, &verdict); /* Treat packet */
nfq_set_verdict(qh, id, verdict, 0, NULL); /* Verdict packet */
}
}
其他語言
Pierre Chifflier(又名pollux)開發了libnetfilter_queue的綁定,可以用于大多數高級語言(python,perl,ruby,...):nfqueue-bindings。
高級功能
多隊列
--queue-balance是Florian Westphal添加的NFQUEUE選項,可以將通過相同iptables規則排隊的平衡分組加載到多個隊列中。 用法相當簡單。 例如,要將INPUT流量負載平衡到隊列0到3,可以使用以下規則。
iptables -A INPUT -j NFQUEUE --queue-balance 0:3
有一點需要提及的是,負載平衡是針對流而言的,并且流的所有分組都被發送到相同的隊列。
這個擴展是從Linux內核2.6.31和iptables v1.4.5開始的。
queue-bypass
--queue-bypass在其他NFQUEUE選項上設置了排隊旁路。 當沒有用戶空間軟件連接到隊列時,它改變了iptables規則的行為。 如果沒有軟件正在監聽隊列,則數據包將被授權,而不是丟棄數據包。
從Linux內核2.6.39和iptables v1.4.11開始擴展。
這個特性從內核3.10到3.12被破壞:當使用最近的iptables時,傳遞選項--queue-bypass對這些內核沒有影響。
fail-open
這個選項在Linux 3.6以后是可用的,并且允許接受數據包,而不是在隊列滿時丟棄數據包。 一個示例用法可以在suricata中找到。
批量判決
從Linux 3.1開始,可以使用批量判定。 不要為一個數據包發送一個判決,而是可以向一個id低于給定id的數據包發送判決。 為此,必須使用nfq_set_verdict_batch或nfq_set_verdict_batch2函數。
該系統具有性能優勢,因為消息的限制增加了分組速率。 但是它可以引起延遲,因為數據包一次被判決。 因此,用戶空間軟件有責任找到自適應技術,通過更快地發布判決來限制延遲,特別是在數據包較少的情況下。
雜項
/proc中的nfnetlink_queue條目
nfnetlink_queue在/ proc:/proc/net/netfilter/ nfnetlink_queue中有一個專用的入口
cat /proc/net/netfilter/nfnetlink_queue
40 23948 0 2 65531 0 0 106 1
695/5000
內容如下:
- 隊列號
- peer portid:很有可能是軟件偵聽隊列的進程ID
- 隊列總數:當前在隊列中等待的數據包數量
- 復制模式:0和1只有消息只提供元數據。 如果2個消息提供一個大小復制范圍的包的一部分。
- 復制范圍:要放入消息的分組數據的長度
- 隊列丟失:由于隊列已滿而丟棄的數據包數量
- 用戶丟棄:由于無法將netlink消息發送到用戶空間而丟棄的數據包數量。 如果此-
計數器不為零,請嘗試增加netlink緩沖區大小。 在應用程序方面,如果netlink消息丟失,您將看到數據包ID的差距。 - id序列:最后一個包的包ID
- 1
經常問的問題
libnetfilter_queue和多線程
libnetfilter_queue取決于發送到套接字的消息。 send / recv操作需要通過鎖保護,以避免并發寫入。 這意味著nfq_set_verdict2和nfq_handle_packet函數需要通過鎖定機制來保護。
接收消息和發送消息是完全獨立的操作,不共享任何內存。 特別是判決只使用包索引作為信息。 因此,只要鎖定不同,線程就可以為隊列中的任何數據包進行判定。
數據包重新排序
使用NFQUEUE可以輕松地進行數據包重新排序,因為可以對任何已排隊的數據包進行判定操作。 盡管有一點需要考慮的是排隊數據包的內核實現是通過鏈表來實現的。 所以判斷不在列表開頭的數據包是昂貴的(最老的數據包是第一個)。
libnetfilter_queue和零拷貝
由于內核和用戶空間之間的通信基于發送到netlink套接字的消息,因此不存在零拷貝等問題。 Patrick McHardy已經啟動了netlink的內存映射實現,因此將來可能會有零拷貝。