iptables目标NFQUEUE

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

from netfilterqueue import NetfilterQueue from scapy.all import IP, TCP import subprocess import logging # 配置日志 logging.basicConfig(level=logging.DEBUG) # 数据包数组 packet_queue = [] # 存储拦截的数据包 queue_length = 5 # 队列长度阈值 # 定义数据包处理函数 def process_packet(packet): global packet_queue global queue_length logging.debug(f"拦截到一个数据包,大小:{len(packet.get_payload())}") scapy_packet = IP(packet.get_payload()) # 将数据包转换为 Scapy 对象 # 检查 TCP 标志 if scapy_packet.haslayer(TCP): tcp_flags = scapy_packet[TCP].flags if tcp_flags & 0x02: # SYN 标志为 1 logging.debug("SYN 标志为 1,正常发送数据包") packet.accept() elif tcp_flags & 0x01: # FIN 标志为 1 logging.debug("FIN 标志为 1,正常发送数据包") packet.accept() else: # 将数据包存入队列 packet_queue.append(packet) # 如果队列中有足够的数据包,重新排序并发送 if len(packet_queue) >= queue_length: # 假设我们每次处理 5 个数据包 print("发送数据包") sorted_packets = sorted(packet_queue, key=lambda p: p.get_timestamp()) # 按时间戳排序 for p in sorted_packets: p.accept() # 释放数据包,发送到下一个处理阶段 packet_queue = [] # 清空队列 # 如果队列未达到阈值,直接返回 return else: packet.accept() # 如果不是 TCP 数据包,则直接通过 # 设置 iptables 规则 def setup_iptables(port, queue_num): logging.debug(f"设置 iptables 规则,将端口 {port} 的数据包转发到队列 {queue_num}...") try: subprocess.run([ "sudo", "iptables", "-I", "OUTPUT", "-p", "tcp", "--sport", str(port), "-j", "NFQUEUE", "--queue-num", str(queue_num) ], check=True) except subprocess.CalledProcessError as e: logging.error(f"Error setting iptables rules: {e}") # 清理 iptables 规则 def cleanup_iptables(): logging.debug("清理 iptables 规则...") try: subprocess.run(["sudo", "iptables", "-F"], check=True) except subprocess.CalledProcessError as e: logging.error(f"Error cleaning up iptables rules: {e}") # 主函数 def main(): port = 8080 # 监听的端口 queue_num = 0 # NetfilterQueue 队列编号 try: # 设置 iptables 规则 setup_iptables(port, queue_num) # 创建 NetfilterQueue 对象并绑定到队列编号 nfqueue = NetfilterQueue() nfqueue.bind(queue_num, process_packet) logging.debug(f"开始监听端口 {port} 的数据包...") try: nfqueue.run() except KeyboardInterrupt: logging.debug("停止监听...") except Exception as e: logging.error(f"Error in NetfilterQueue: {e}") finally: nfqueue.unbind() cleanup_iptables() except Exception as e: logging.error(f"Error in main: {e}") if __name__ == "__main__": main() 该代码是否能够达到拦截并存储数据包,然后顺序发送的目的
04-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值