NFQUEUE目标帮助信息如下。
# iptables -j NFQUEUE -h
NFQUEUE target options
--queue-num value Send packet to QUEUE number <value>.
Valid queue numbers are 0-65535
--queue-balance first:last Balance flows between queues <value> to <value>.
--queue-bypass Bypass Queueing if no queue instance exists.
--queue-cpu-fanout Use current CPU (no hashing)
如下配置策略。
# sudo iptables -A INPUT -j NFQUEUE --queue-bypass --queue-num 3
#
# sudo iptables -L -v -n
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
89 11767 NFQUEUE all -- * * 0.0.0.0/0 0.0.0.0/0 NFQUEUE num 3 bypass
编译libnetfilter_queue-1.0.5包以及其中的example实例。
# apt install libnfnetlink-dev
# apt install libmnl-dev
#
# cd libnetfilter_queue-1.0.5/
# ./configure
# make && make install
#
# cd examples
#
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
#
# gcc nf-queue.c -o nf-queue -lmnl /usr/local/lib/libnetfilter_queue.so
运行nf_queue实例程序,参数为以上策略中NFQUEUE的队列号,其将打印收到的部分报文信息。nf_queue默认动作是NF_ACCEPT。
# ./nf-queue 3
packet received (id=1 hw=0x0800 hook=1, payload len 40)
packet received (id=2 hw=0x0800 hook=1, payload len 40)
NFQUEUE目标
函数xt_register_targets注册三个版本的NFQUEUE目标,这里只关注下revision 3。
static struct xt_target nfqueue_tg_reg[] __read_mostly = {
{
.name = "NFQUEUE",
.revision = 3,
.family = NFPROTO_UNSPEC,
.checkentry = nfqueue_tg_check,
.target = nfqueue_tg_v3,
.targetsize = sizeof(struct xt_NFQ_info_v3),
.me = THIS_MODULE,
},
};
static int __init nfqueue_tg_init(void)
{
return xt_register_targets(nfqueue_tg_reg, ARRAY_SIZE(nfqueue_tg_reg));
策略检查函数,确保配置了合法的队列号。对于revision 2,仅支持命令选项queue-bypass,其标志为1(NFQ_FLAG_BYPASS),如果flags值大于1即为错误。对于版本3,标志位不能超过NFQ_FLAG_MASK定义的值。
static int nfqueue_tg_check(const struct xt_tgchk_param *par)
{
const struct xt_NFQ_info_v3 *info = par->targinfo;
u32 maxid;
init_hashrandom(&jhash_initval);
if (info->queues_total == 0) {
pr_info_ratelimited("number of total queues is 0\n");
return -EINVAL;
}
maxid = info->queues_total - 1 + info->queuenum;
if (maxid > 0xffff) {
pr_info_ratelimited("number of queues (%u) out of range (got %u)\n",
info->queues_total, maxid);
return -ERANGE;
}
if (par->target->revision == 2 && info->flags > 1)
return -EINVAL;
if (par->target->revision == 3 && info->flags & ~NFQ_FLAG_MASK)
return -EINVAL;
如下目标处理函数,如果队列数量大于1,在配置了命令选项queue-cpu-fanout的情况下,根据当前处理器ID选择队列号。否则,没有设置queue-cpu-fanout选项时,根据报文IPv4/IPv6头部源和目的地址等信息进行hash获得队列号。
static unsigned int
nfqueue_tg_v3(struct sk_buff *skb, const struct xt_action_param *par)
{
const struct xt_NFQ_info_v3 *info = par->targinfo;
u32 queue = info->queuenum;
if (info->queues_total > 1) {
if (info->flags & NFQ_FLAG_CPU_FANOUT) {
int cpu = smp_processor_id();
queue = info->queuenum + cpu % info->queues_total;
} else {
queue = nfqueue_hash(skb, queue, info->queues_total,
xt_family(par), jhash_initval);
宏NF_QUEUE_NR将队列号和NF_QUEUE一并封装到ret变量中,另外,对于命令行参数queue-bypass,变量ret中增加标志位NF_VERDICT_FLAG_QUEUE_BYPASS。
ret = NF_QUEUE_NR(queue);
if (info->flags & NFQ_FLAG_BYPASS)
ret |= NF_VERDICT_FLAG_QUEUE_BYPASS;
return ret;
以上函数返回值ret,由三部分组成,最高的16位存储队列号或者错误码,随后的8位保存额外标志(如BYPPASS),最低的8位保存判定结果,在判断结果为NF_DROP时,最高的16位保存的是错误码。
#define NF_VERDICT_MASK 0x000000ff
/* extra verdict flags have mask 0x0000ff00 */
#define NF_VERDICT_FLAG_QUEUE_BYPASS 0x00008000
/* queue number (NF_QUEUE) or errno (NF_DROP) */
#define NF_VERDICT_QMASK 0xffff0000
#define NF_VERDICT_QBITS 16
#define NF_QUEUE_NR(x) ((((x) << 16) & NF_VERDICT_QMASK) | NF_QUEUE)
#define NF_DROP_ERR(x) (((-x) << 16) | NF_DROP)
在hook点处理函数nf_hook_slow中,如果返回值的verdict(NF_VERDICT_MASK之后的值)为NF_QUEUE,将报文送入nf_queue处理。
int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,
const struct nf_hook_entries *e, unsigned int s)
{
for (; s < e->num_hook_entries; s++) {
verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);
switch (verdict & NF_VERDICT_MASK) {
case NF_QUEUE:
ret = nf_queue(skb, state, s, verdict);
if (ret == 1)
continue;
return ret;
内核版本 5.10