You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用nDPI库的程序返回错误结果,持续输出无效分类值求助

解决nDPI网络流检测程序中"Invalid category"错误的方案

你的程序持续输出Invalid category: -842150451,本质是ndpi_protocol结构体的category字段为未初始化的垃圾值,核心问题出在nDPI初始化、流结构体管理和数据包处理的逻辑错误上。以下是具体问题和修复方案:

关键错误点分析

  1. 重复执行nDPI初始化收尾
    ndpi_finalize_initialization是初始化阶段的收尾操作,应该只调用一次。你在read_packet函数中每个数据包都调用,会破坏nDPI的内部检测状态,导致协议检测逻辑失效。

  2. 未正确初始化ndpi_flow_struct
    直接用new创建ndpi_flow_struct,没有调用nDPI提供的ndpi_flow_malloc初始化函数,结构体内部成员都是随机垃圾值,后续检测时会读取无效数据。

  3. 每个数据包新建流结构体
    网络流是由多个数据包组成的连续会话,你为每个数据包创建独立的ndpi_flow_struct,导致nDPI无法累积流的上下文信息,根本无法完成协议检测,同时会造成严重内存泄漏。

  4. 不安全的类型转换
    强制将Tins::PDU转换为Tins::Packet是不安全的,PDU是抽象基类,并非所有PDU实例都是Packet,会导致未定义行为。

  5. 未处理协议查找失败的异常
    rfind_pdu<Tins::IP>()和rfind_pdu<Tins::TCP>()如果找不到对应层,会抛出std::runtime_error,导致程序崩溃。

修复后的完整代码

#include <iostream>
#include <tins/tins.h>
#include "ndpi_api.h"
#include <unordered_map>
#include <tuple>

// 流的五元组key,用于跟踪已存在的流
using FlowKey = std::tuple<uint32_t, uint32_t, uint16_t, uint16_t, uint8_t>;

// 自定义哈希函数,用于FlowKey的unordered_map
struct FlowKeyHash {
    std::size_t operator()(const FlowKey& k) const {
        return std::hash<uint32_t>()(std::get<0>(k)) ^
               std::hash<uint32_t>()(std::get<1>(k)) ^
               std::hash<uint16_t>()(std::get<2>(k)) ^
               std::hash<uint16_t>()(std::get<3>(k)) ^
               std::hash<uint8_t>()(std::get<4>(k));
    }
};

// 存储流信息的结构体
struct ndpi_flow_info {
    ndpi_protocol detected_protocol;
    struct ndpi_flow_struct *ndpi_flow;
};

// 全局nDPI检测模块
struct ndpi_detection_module_struct* ndpi_struct = nullptr;
// 流表,用于管理所有活跃流
std::unordered_map<FlowKey, ndpi_flow_info, FlowKeyHash> flow_table;

// 获取流的五元组key(自动按统一顺序排序,避免双向流重复)
FlowKey get_flow_key(const Tins::IP& ip, const Tins::Transport& transport) {
    uint8_t proto = ip.protocol();
    uint32_t src_ip = ip.src_addr();
    uint32_t dst_ip = ip.dst_addr();
    uint16_t src_port = transport.sport();
    uint16_t dst_port = transport.dport();

    // 统一五元组顺序,确保双向流对应同一个key
    if ((src_ip > dst_ip) || (src_ip == dst_ip && src_port > dst_port)) {
        std::swap(src_ip, dst_ip);
        std::swap(src_port, dst_port);
    }
    return std::make_tuple(src_ip, dst_ip, src_port, dst_port, proto);
}

// 处理单个数据包
int process_packet(const Tins::Packet& packet) {
    try {
        const Tins::IP& ip = packet.pdu()->rfind_pdu<Tins::IP>();
        const Tins::Transport& transport = packet.pdu()->rfind_pdu<Tins::Transport>();

        // 获取流key
        FlowKey key = get_flow_key(ip, transport);
        auto it = flow_table.find(key);
        ndpi_flow_info* flow_info;

        // 如果流不存在,创建并初始化新流
        if (it == flow_table.end()) {
            ndpi_flow_info new_flow;
            new_flow.ndpi_flow = ndpi_flow_malloc(ndpi_struct);
            if (!new_flow.ndpi_flow) {
                std::cout << "Memory allocation failed for flow" << std::endl;
                return -1;
            }
            ndpi_init_flow(ndpi_struct, new_flow.ndpi_flow);
            new_flow.detected_protocol = ndpi_protocol::ndpi_protocol_unknown;
            it = flow_table.insert({key, new_flow}).first;
        }
        flow_info = &it->second;

        // 序列化整个数据包(从链路层开始)
        std::vector<uint8_t> packet_data = packet.pdu()->serialize();
        uint64_t time_ms = packet.timestamp().seconds() * 1000 + packet.timestamp().microseconds() / 1000;

        // 准备流输入信息
        ndpi_flow_input_info input;
        memset(&input, 0, sizeof(input));
        input.flow = flow_info->ndpi_flow;
        input.flow_id = (void*)&key;
        input.seen_flow_beginning = (it->second.ndpi_flow->num_processed_pkts == 0) ? 1 : 0;

        // 检测协议
        flow_info->detected_protocol = ndpi_detection_process_packet(ndpi_struct, flow_info->ndpi_flow,
                                                                    packet_data.data(), packet_data.size(),
                                                                    time_ms, &input);

        // 打印检测结果(仅当协议确定时)
        if (flow_info->detected_protocol.protocol_id != NDPI_PROTOCOL_UNKNOWN &&
            flow_info->detected_protocol.category >= 0 && flow_info->detected_protocol.category < NDPI_PROTOCOL_NUM_CATEGORIES) {
            const char* category_name = ndpi_category_get_name(ndpi_struct, flow_info->detected_protocol.category);
            std::cout << "Flow between " << ip.src_addr() << ":" << transport.sport()
                      << " and " << ip.dst_addr() << ":" << transport.dport()
                      << " -> Category: " << category_name
                      << ", Protocol: " << ndpi_get_proto_name(ndpi_struct, &flow_info->detected_protocol) << std::endl;
        }

        return 0;
    } catch (const std::runtime_error& e) {
        // 忽略不包含IP/传输层的数据包
        return 0;
    }
}

int main() {
    // 初始化nDPI
    ndpi_struct = ndpi_init_detection_module(ndpi_no_prefs);
    if (!ndpi_struct) {
        std::cout << "nDPI initialization failed" << std::endl;
        return -1;
    }
    ndpi_finalize_initialization(ndpi_struct);

    // 配置嗅探器
    Tins::SnifferConfiguration config;
    config.set_promisc_mode(true);
    config.set_filter("ip"); // 捕获所有IP数据包
    Tins::NetworkInterface iface = Tins::NetworkInterface::default_interface();
    std::cout << "Sniffing on interface: " << iface.name() << " (" << iface.friendly_name() << ")" << std::endl;

    // 开始嗅探
    try {
        Tins::Sniffer sniffer(iface.name(), config);
        sniffer.sniff_loop([&](const Tins::Packet& packet) -> bool {
            process_packet(packet);
            return true;
        });
    } catch (const std::exception& e) {
        std::cout << "Sniffer error: " << e.what() << std::endl;
    }

    // 清理资源
    for (auto& entry : flow_table) {
        ndpi_flow_free(ndpi_struct, entry.second.ndpi_flow);
    }
    ndpi_exit_detection_module(ndpi_struct);
    return 0;
}

关键修复说明

  1. 正确的nDPI初始化流程
    将ndpi_finalize_initialization移至main函数中,仅在初始化阶段调用一次,确保nDPI内部状态稳定。

  2. 流的生命周期管理
    使用std::unordered_map维护流表,通过五元组跟踪活跃流,每个流只创建一个ndpi_flow_struct,并使用ndpi_flow_malloc和ndpi_init_flow正确初始化,程序退出时统一释放资源。

  3. 安全的数据包处理
    使用try-catch捕获协议层查找失败的异常,避免程序崩溃;直接使用Tins::Packet获取时间戳,避免不安全的类型转换。

  4. 正确的流输入参数
    设置seen_flow_beginning标识流的第一个数据包,帮助nDPI正确初始化流检测上下文。

排查思路总结

遇到此类问题时,按以下顺序排查:

  • 检查nDPI初始化流程是否正确,确保初始化函数只调用一次
  • 验证ndpi_flow_struct是否通过nDPI提供的函数正确初始化,避免直接使用原始内存分配
  • 确认流的生命周期管理是否正确,同一个流必须复用同一个ndpi_flow_struct
  • 检查参数传递是否符合nDPI API要求,尤其是时间戳、数据包长度等关键参数
  • 排查是否存在未初始化的内存或无效的类型转换

内容的提问来源于stack exchange,提问作者Ostrale

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 09:37:06