【博客600】将iptables流量转到用户态进行特殊化处理

本文介绍如何利用iptables的NFQUEUE目标将匹配到的流量传递到用户态进行特殊处理。包括NFQUEUE的基本概念、工作原理及使用注意事项,并提供了一个Go语言示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

将iptables流量转到用户态进行特殊化处理

场景

当我们需要将iptables匹配到的流量送到用户态来进行特殊化处理

方法:借助 Iptables NFQUEUE

NFQUEUE介绍:

NFQUEUE
This target passes the packet to userspace using the nfnetlink_queue handler. The packet is put into the queue identified by its 16-bit queue number. Userspace can inspect and modify the packet if desired. Userspace must then drop or reinject the packet into the kernel. Please see libnetfilter_queue for details. nfnetlink_queue was added in Linux 2.6.14. The queue-balance option was added in Linux 2.6.31, queue-bypass in 2.6.39.

–queue-num value

This specifies the QUEUE number to use. Valid queue numbers are 0 to 65535. The default value is 0.

–queue-balance value**:***value*

This specifies a range of queues to use. Packets are then balanced across the given queues. This is useful for multicore systems: start multiple instances of the userspace program on queues x, x+1, .. x+n and use “–queue-balance x**:***x+n*”. Packets belonging to the same connection are put into the same nfqueue.

–queue-bypass

By default, if no userspace program is listening on an NFQUEUE, then all packets that are to be queued are dropped. When this option is used, the NFQUEUE rule behaves like ACCEPT instead, and the packet will move on to the next table.

–queue-cpu-fanout

Available starting Linux kernel 3.10. When used together with –queue-balance this will use the CPU ID as an index to map packets to the queues. The idea is that you can improve performance if there’s a queue per CPU. This requires –queue-balance to be specified.

NFQUEUE用途

NFQUEUE 是一种 iptables 和 ip6tables 的目标(an iptables and ip6tables target),将网络包处理决定委托给用户态软件。

比如,下面的规则将所有接收到的网络包(all packet going to the box)委托给监听中的用户态程序去决策:

iptables -A INPUT -j NFQUEUE --queue-num 0

用户态软件必须监听队列 0(connect to queue 0),并从内核获取消息;然后给每个网络包给出判决(verdict)。

NFQUEUE内部实现

当一个网络包触发 NFQUEUE 目标,它将被插入到 --queue-num 指定的队列里。网络包队列是一个链式列表,列表元素是网络包和元数据(Linux 内核 skb)

特性:

  • 固定长度的链式列表
  • 使用整数索引网络包
  • 当用户态程序对指定整数索引的网络包做出判决后,该网络包将被释放掉
  • 当队列满了,队列不再接收网络包

用户态程序会有如下影响:

  • 批量读取消息,并批量给出判决。当队列未满时,批量处理不会产生影响。
  • 可以无序地给网络包做出判决。比如,得到 1、2、3、4 网络包,以 4、2、3、1 顺序做出判决。
  • 过慢的判决会导致队列充满。此时,内核会丢掉新来的网络包,而不是插入队列。

原理:基于内核和用户态之间的通信协议

内核和用户态之间的通信协议使用的是 nfnetlink。这是一种不共享内存的基于消息的通信协议。
当一个网络包入队时,内核将包含了网络包数据和相关信息的 nfnetlink 消息发送到某个套接字(socket),
用户态程序就能够读取到这条消息。用户态程序进行判决的做法是,将包含了网络包整数索引的 nfnetlink 消息发送到那个套接字。

NFQUEUE 的使用注意事项

1、队列容量

NFQUEUE 是有容量限制的,默认是 1024;用户程序可以在监听队列的时候设置容量大小。如果队列满了,iptables 默认会丢包;在内核 3.6 之后,可以使用 --fail-open 选项将这默认丢包改为默认接收。

如何知道队列满了后发生丢包呢?有两种方式:

每次丢包都会打印一条系统日志
cat /proc/net/netfilter/nfnetlink_queue 里有丢包统计

无法通过程序的方式实时感知到队列满了、或者丢包了。

2、复制到用户程序的网络包的大小

将网络包复制到用户程序的时候,对网络包的大小是有限制的,默认是 65531;用户程序可以在监听队列的时候设置复制网络包的最大值。
如果只需要根据 IP 和端口进行判决,则可以将这个限制设置为 40(三层和四层的头部总大小),避免不必要的网络包内容复制。

3、没有响应判决结果

如果没有对网络包响应其判决结果,则该网络包会永远阻塞在 iptables,iptables 不会自动丢弃这样的网络包。

NFQUEUE 的工作机制

NFQUEUE 的工作机制可分上下两场:

上半场:

  • 接收 -j NFQUEUE 发送过来的网络包
  • 入队
  • 通过 nfnetlink 将网络包发给用户程序

上半场的函数调用栈如下:

|-->NF_HOOK()                   // include/linux/netfilter.h
    |-->nf_hook()
    |-->nf_hook_slow()          // net/netfilter/core.c
        |-->nf_queue()          // net/netfilter/nf_queue.c
            |-->__nf_queue()
                |-->struct nf_queue_handler *qh->outfn()
               /
              /
             /
            |-->nfqnl_enqueue_packet()          // net/netfilter/nfnetlink_queue.c
                |-->__nfqnl_enqueue_packet()
                |-->nfnetlink_unicast()
                |-->__enqueue_entry()

下半场:

  • 接收用户程序通过 nfnetlink 发送过来的判决结果
  • 出队
  • 处理判决结果
  • 继续 iptables 后续的处理

下半场的函数调用栈如下:

|-->nfqnl_recv_verdict()            // net/netfilter/nfnetlink_queue.c
    |-->verdict_instance_lookup()
    |-->find_dequeue_entry()
        |-->__dequeue_entry()
    |-->nfqnl_reinject()
    |-->nf_reinject()               // net/netfilter/nf_queue.c
        |-->entry->state.okfn()
            |-->//继续 `iptables` 后续的处理

NFQUEUE的适用性

NFQUEUE 因为需要将网络包发送给用户程序,所以它的性能并不高;但相比于堆叠 iptables 规则,NFQUEUE 的处理方式更加灵活。而对于 XDP、xt_bpf 等拥有同等灵活性的新技术而言,NFQUEUE 的适用范围更广、能够适配很多老旧系统。

example:

iptables层:设置iptables规则将流量转到iptables nfqueue

使用的 iptables 规则如下:
iptables -t raw -I PREROUTING -p tcp --syn -j NFQUEUE --queue-num 1 --queue-bypass
--queue-num 指当前规则将对应的网络包放到 1 号队列
--queue-bypass 指当没有用户程序监听当前队列时,默认放行网络包,而不是丢包

用户态层的go示例:监听nfqueue,从而获取数据并做出裁决

参考: https://github.com/Asphaltt/learn-by-example/tree/main/nfqueue

example:使用 iptables NFQUEUE 监听新建 tcp 连接:

package main

import (
	"context"
	"encoding/binary"
	"fmt"
	"net"

	"github.com/florianl/go-nfqueue"
)

type packet []byte

func (p packet) srcIP() net.IP {
	return net.IP(p[12:16])
}

func (p packet) dstIP() net.IP {
	return net.IP(p[16:20])
}

func (p packet) srcPort() uint16 {
	tcphdr := p[20:]
	return binary.BigEndian.Uint16(tcphdr[:2])
}

func (p packet) dstPort() uint16 {
	tcphdr := p[20:]
	return binary.BigEndian.Uint16(tcphdr[2:4])
}

func handlePacket(q *nfqueue.Nfqueue, a nfqueue.Attribute) int {
	if a.Payload != nil && len(*a.Payload) != 0 {
		pkt := packet(*a.Payload)
		fmt.Printf("tcp connect: %s:%d -> %s:%d\n", pkt.srcIP(), pkt.srcPort(), pkt.dstIP(), pkt.dstPort())
	}
	_ = q.SetVerdict(*a.PacketID, nfqueue.NfAccept)
	return 0
}

func main() {
	cfg := nfqueue.Config{
		NfQueue:     1,
		MaxQueueLen: 2,
		Copymode:    nfqueue.NfQnlCopyPacket,
	}

	nfq, err := nfqueue.Open(&cfg)
	if err != nil {
		fmt.Println("failed to open nfqueue, err:", err)
		return
	}

	ctx, stop := context.WithCancel(context.Background())
	defer stop()
	if err := nfq.RegisterWithErrorFunc(ctx, func(a nfqueue.Attribute) int {
		return handlePacket(nfq, a)
	}, func(e error) int {
		return 0
	}); err != nil {
		fmt.Println("failed to register handlers, err:", err)
		return
	}

	select {}
}

效果:

./nfqueue-example
tcp connect: 113.81.xx.yy:19885 -> 10.7.xxx.yyy:22
tcp connect: 157.245.xx.yy:46131 -> 10.7.xxx.yyy:2060
tcp connect: 113.81.xx.yy:19907 -> 10.7.xxx.yyy:8080
tcp connect: 113.81.xx.yy:19918 -> 10.7.xxx.yyy:443
tcp connect: 113.81.xx.yy:19918 -> 10.7.xxx.yyy:443
tcp connect: 113.81.xx.yy:19918 -> 10.7.xxx.yyy:443
tcp connect: 46.101.xx.yy:48780 -> 10.7.xxx.yyy:5537
tcp connect: 89.248.xx.yy:54067 -> 10.7.xxx.yyy:309
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值