gauravcodes-in/DPI

GitHub: gauravcodes-in/DPI

一个基于 C++17 的多线程深度包检测引擎,通过解析网络数据包并提取 TLS 握手中的 SNI 信息,实现对加密流量的应用识别、连接跟踪和规则过滤。

Stars: 2 | Forks: 1

# DPI 引擎 - 深度包检测系统 本文档解释了关于该项目的**所有内容** - 从基础网络概念到完整的代码架构。阅读本文后,您应该能准确了解数据包是如何在系统中流动的,而无需阅读源代码。 ## 目录 1. [什么是 DPI?](#1-what-is-dpi) 2. [网络背景知识](#2-networking-background) 3. [项目概述](#3-project-overview) 4. [文件结构](#4-file-structure) 5. [数据包的旅程(简单版)](#5-the-journey-of-a-packet-simple-version) 6. [数据包的旅程(多线程版)](#6-the-journey-of-a-packet-multi-threaded-version) 7. [深入探讨:各组件详解](#7-deep-dive-each-component) 8. [SNI 提取的工作原理](#8-how-sni-extraction-works) 9. [拦截机制的工作原理](#9-how-blocking-works) 10. [构建与运行](#10-building-and-running) 11. [理解输出结果](#11-understanding-the-output) ## 1. 什么是 DPI? **深度包检测 (DPI)** 是一种用于在网络数据包通过检查点时检查其内容的技术。与仅查看数据包头部(源/目标 IP)的简单防火墙不同,DPI 会查看数据包有效负载的*内部*。 ### 实际应用场景: - **互联网服务提供商 (ISP)**:限速或拦截特定应用(例如 BitTorrent) - **企业**:在办公网络中屏蔽社交媒体 - **家长控制**:拦截不当网站 - **安全防护**:检测恶意软件或入侵企图 ### 我们的 DPI 引擎功能: ``` User Traffic (PCAP) → [DPI Engine] → Filtered Traffic (PCAP) ↓ - Identifies apps (YouTube, Facebook, etc.) - Blocks based on rules - Generates reports ``` ## 2. 网络背景知识 ### 网络协议栈(层) 当您访问网站时,数据会穿过多个“层”: ``` ┌─────────────────────────────────────────────────────────┐ │ Layer 7: Application │ HTTP, TLS, DNS │ ├─────────────────────────────────────────────────────────┤ │ Layer 4: Transport │ TCP (reliable), UDP (fast) │ ├─────────────────────────────────────────────────────────┤ │ Layer 3: Network │ IP addresses (routing) │ ├─────────────────────────────────────────────────────────┤ │ Layer 2: Data Link │ MAC addresses (local network)│ └─────────────────────────────────────────────────────────┘ ``` ### 数据包结构 每个网络数据包就像一个**俄罗斯套娃** - 报头中包裹着报头: ``` ┌──────────────────────────────────────────────────────────────────┐ │ Ethernet Header (14 bytes) │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ IP Header (20 bytes) │ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ TCP Header (20 bytes) │ │ │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ Payload (Application Data) │ │ │ │ │ │ │ │ e.g., TLS Client Hello with SNI │ │ │ │ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────┘ ``` ### 五元组 一个**连接**(或“流”)由 5 个值唯一标识: | 字段 | 示例 | 用途 | |-------|---------|---------| | 源 IP | 192.168.1.100 | 发送方是谁 | | 目标 IP | 172.217.14.206 | 去往何处 | | 源端口 | 54321 | 发送方的应用程序标识符 | | 目标端口 | 443 | 正在访问的服务 (443 = HTTPS) | | 协议 | TCP (6) | TCP 或 UDP | **为什么这很重要?** - 具有相同五元组的所有数据包都属于同一个连接 - 如果我们拦截某个连接的一个数据包,我们应该拦截该连接的所有数据包 - 这就是我们“跟踪”计算机之间对话的方式 ### 什么是 SNI? **服务器名称指示 (SNI)** 是 TLS/HTTPS 握手的一部分。当您访问 `https://www.youtube.com` 时: 1. 您的浏览器发送一个“Client Hello”消息 2. 此消息以**明文**形式包含域名(尚未加密!) 3. 服务器使用此信息来知道该发送哪个证书 ``` TLS Client Hello: ├── Version: TLS 1.2 ├── Random: [32 bytes] ├── Cipher Suites: [list] └── Extensions: └── SNI Extension: └── Server Name: "www.youtube.com" ← We extract THIS! ``` **这是 DPI 的关键**:即使 HTTPS 是加密的,域名在第一个数据包中仍然是可见的! ## 3. 项目概述 ### 本项目的作用 ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Wireshark │ │ DPI Engine │ │ Output │ │ Capture │ ──► │ │ ──► │ PCAP │ │ (input.pcap)│ │ - Parse │ │ (filtered) │ └─────────────┘ │ - Classify │ └─────────────┘ │ - Block │ │ - Report │ └─────────────┘ ``` ### 两个版本 | 版本 | 文件 | 用例 | |---------|------|----------| | 简单版(单线程) | `src/main_working.cpp` | 学习,小型抓包 | | 多线程版 | `src/dpi_mt.cpp` | 生产环境,大型抓包 | ## 4. 文件结构 ``` DPI/ ├── include/ # Header files (declarations) │ ├── pcap_reader.h # PCAP file reading │ ├── packet_parser.h # Network protocol parsing │ ├── sni_extractor.h # TLS/HTTP inspection │ ├── types.h # Data structures (FiveTuple, AppType, etc.) │ ├── rule_manager.h # Blocking rules (multi-threaded version) │ ├── connection_tracker.h # Flow tracking (multi-threaded version) │ ├── load_balancer.h # LB thread (multi-threaded version) │ ├── fast_path.h # FP thread (multi-threaded version) │ ├── thread_safe_queue.h # Thread-safe queue │ └── dpi_engine.h # Main orchestrator │ ├── src/ # Implementation files │ ├── pcap_reader.cpp # PCAP file handling │ ├── packet_parser.cpp # Protocol parsing │ ├── sni_extractor.cpp # SNI/Host extraction │ ├── types.cpp # Helper functions │ ├── main_working.cpp # ★ SIMPLE VERSION ★ │ ├── dpi_mt.cpp # ★ MULTI-THREADED VERSION ★ │ └── [other files] # Supporting code │ ├── generate_test_pcap.py # Creates test data ├── test_dpi.pcap # Sample capture with various traffic └── README.md # This file! ``` ## 5. 数据包的旅程(简单版) 让我们通过 `main_working.cpp` 追踪单个数据包: ### 步骤 1:读取 PCAP 文件 ``` PcapReader reader; reader.open("capture.pcap"); ``` **发生的事情:** 1. 以二进制模式打开文件 2. 读取 24 字节的全局头(魔数、版本等) 3. 验证其是否为有效的 PCAP 文件 **PCAP 文件格式:** ``` ┌────────────────────────────┐ │ Global Header (24 bytes) │ ← Read once at start ├────────────────────────────┤ │ Packet Header (16 bytes) │ ← Timestamp, length │ Packet Data (variable) │ ← Actual network bytes ├────────────────────────────┤ │ Packet Header (16 bytes) │ │ Packet Data (variable) │ ├────────────────────────────┤ │ ... more packets ... │ └────────────────────────────┘ ``` ### 步骤 2:读取每个数据包 ``` while (reader.readNextPacket(raw)) { // raw.data contains the packet bytes // raw.header contains timestamp and length } ``` **发生的事情:** 1. 读取 16 字节的数据包头 2. 读取 N 字节的数据包数据 (N = header.incl_len) 3. 没有更多数据包时返回 false ### 步骤 3:解析协议头 ``` PacketParser::parse(raw, parsed); ``` **发生的事情(在 packet_parser.cpp 中):** ``` raw.data bytes: [0-13] Ethernet Header [14-33] IP Header [34-53] TCP Header [54+] Payload After parsing: parsed.src_mac = "00:11:22:33:44:55" parsed.dest_mac = "aa:bb:cc:dd:ee:ff" parsed.src_ip = "192.168.1.100" parsed.dest_ip = "172.217.14.206" parsed.src_port = 54321 parsed.dest_port = 443 parsed.protocol = 6 (TCP) parsed.has_tcp = true ``` **解析以太网头(14 字节):** ``` Bytes 0-5: Destination MAC Bytes 6-11: Source MAC Bytes 12-13: EtherType (0x0800 = IPv4) ``` **解析 IP 头(20+ 字节):** ``` Byte 0: Version (4 bits) + Header Length (4 bits) Byte 8: TTL (Time To Live) Byte 9: Protocol (6=TCP, 17=UDP) Bytes 12-15: Source IP Bytes 16-19: Destination IP ``` **解析 TCP 头(20+ 字节):** ``` Bytes 0-1: Source Port Bytes 2-3: Destination Port Bytes 4-7: Sequence Number Bytes 8-11: Acknowledgment Number Byte 12: Data Offset (header length) Byte 13: Flags (SYN, ACK, FIN, etc.) ``` ### 步骤 4:创建五元组并查找流 ``` FiveTuple tuple; tuple.src_ip = parseIP(parsed.src_ip); tuple.dst_ip = parseIP(parsed.dest_ip); tuple.src_port = parsed.src_port; tuple.dst_port = parsed.dest_port; tuple.protocol = parsed.protocol; Flow& flow = flows[tuple]; // Get or create ``` **发生的事情:** - 流表是一个哈希映射:`FiveTuple → Flow` - 如果此五元组存在,我们将获取现有的流 - 如果不存在,则创建一个新流 - 具有相同五元组的所有数据包共享同一个流 ### 步骤 5:提取 SNI(深度包检测) ``` // For HTTPS traffic (port 443) if (pkt.tuple.dst_port == 443 && pkt.payload_length > 5) { auto sni = SNIExtractor::extract(payload, payload_length); if (sni) { flow.sni = *sni; // "www.youtube.com" flow.app_type = sniToAppType(*sni); // AppType::YOUTUBE } } ``` **发生的事情(在 sni_extractor.cpp 中):** 1. **检查是否为 TLS Client Hello:** 字节 0:内容类型 = 0x16(握手) ✓ 字节 5:握手类型 = 0x01(Client Hello) ✓ 2. **导航到扩展:** 跳过:版本、随机数、会话 ID、密码套件、压缩方法 3. **查找 SNI 扩展(类型 0x0000):** 扩展类型:0x0000(SNI) 扩展长度:N SNI 列表长度:M SNI 类型:0x00(主机名) SNI 长度:L SNI 值:"www.youtube.com" ← 找到了! 4. **将 SNI 映射到应用类型:** // 在 types.cpp 中 if (sni.find("youtube") != std::string::npos) { return AppType::YOUTUBE; } ### 步骤 6:检查拦截规则 ``` if (rules.isBlocked(tuple.src_ip, flow.app_type, flow.sni)) { flow.blocked = true; } ``` **发生的事情:** ``` // Check IP blacklist if (blocked_ips.count(src_ip)) return true; // Check app blacklist if (blocked_apps.count(app)) return true; // Check domain blacklist (substring match) for (const auto& dom : blocked_domains) { if (sni.find(dom) != std::string::npos) return true; } return false; ``` ### 步骤 7:转发或丢弃 ``` if (flow.blocked) { dropped++; // Don't write to output } else { forwarded++; // Write packet to output file output.write(packet_header); output.write(packet_data); } ``` ### 步骤 8:生成报告 处理完所有数据包后: ``` // Count apps for (const auto& [tuple, flow] : flows) { app_stats[flow.app_type]++; } // Print report "YouTube: 150 packets (15%)" "Facebook: 80 packets (8%)" ... ``` ## 6. 数据包的旅程(多线程版) 多线程版本 (`dpi_mt.cpp`) 增加了**并行性**以实现高性能: ### 架构概述 ``` ┌─────────────────┐ │ Reader Thread │ │ (reads PCAP) │ └────────┬────────┘ │ ┌──────────────┴──────────────┐ │ hash(5-tuple) % 2 │ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ LB0 Thread │ │ LB1 Thread │ │ (Load Balancer)│ │ (Load Balancer)│ └────────┬────────┘ └────────┬────────┘ │ │ ┌──────┴──────┐ ┌──────┴──────┐ │hash % 2 │ │hash % 2 │ ▼ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │FP0 Thread│ │FP1 Thread│ │FP2 Thread│ │FP3 Thread│ │(Fast Path)│ │(Fast Path)│ │(Fast Path)│ │(Fast Path)│ └─────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬────┘ │ │ │ │ └────────────┴──────────────┴────────────┘ │ ▼ ┌───────────────────────┐ │ Output Queue │ └───────────┬───────────┘ │ ▼ ┌───────────────────────┐ │ Output Writer Thread │ │ (writes to PCAP) │ └───────────────────────┘ ``` ### 为什么采用这种设计? 1. **负载均衡器 (LB):** 将工作分配到各个 FP 2. **快速路径 (FP):** 执行实际的 DPI 处理 3. **一致性哈希:** 相同的五元组总是发送到相同的 FP **为什么一致性哈希很重要:** ``` Connection: 192.168.1.100:54321 → 142.250.185.206:443 Packet 1 (SYN): hash → FP2 Packet 2 (SYN-ACK): hash → FP2 (same FP!) Packet 3 (Client Hello): hash → FP2 (same FP!) Packet 4 (Data): hash → FP2 (same FP!) All packets of this connection go to FP2. FP2 can track the flow state correctly. ``` ### 详细流程 #### 步骤 1:读取器线程 ``` // Main thread reads PCAP while (reader.readNextPacket(raw)) { Packet pkt = createPacket(raw); // Hash to select Load Balancer size_t lb_idx = hash(pkt.tuple) % num_lbs; // Push to LB's queue lbs_[lb_idx]->queue().push(pkt); } ``` #### 步骤 2:负载均衡器线程 ``` void LoadBalancer::run() { while (running_) { // Pop from my input queue auto pkt = input_queue_.pop(); // Hash to select Fast Path size_t fp_idx = hash(pkt.tuple) % num_fps_; // Push to FP's queue fps_[fp_idx]->queue().push(pkt); } } ``` #### 步骤 3:快速路径线程 ``` void FastPath::run() { while (running_) { // Pop from my input queue auto pkt = input_queue_.pop(); // Look up flow (each FP has its own flow table) Flow& flow = flows_[pkt.tuple]; // Classify (SNI extraction) classifyFlow(pkt, flow); // Check rules if (rules_->isBlocked(pkt.tuple.src_ip, flow.app_type, flow.sni)) { stats_->dropped++; } else { // Forward: push to output queue output_queue_->push(pkt); } } } ``` #### 步骤 4:输出写入器线程 ``` void outputThread() { while (running_ || output_queue_.size() > 0) { auto pkt = output_queue_.pop(); // Write to output file output_file.write(packet_header); output_file.write(pkt.data); } } ``` ### 线程安全队列 使多线程成为可能的魔力所在: ``` template class TSQueue { std::queue queue_; std::mutex mutex_; std::condition_variable not_empty_; std::condition_variable not_full_; void push(T item) { std::lock_guard lock(mutex_); queue_.push(item); not_empty_.notify_one(); // Wake up waiting consumer } T pop() { std::unique_lock lock(mutex_); not_empty_.wait(lock, [&]{ return !queue_.empty(); }); T item = queue_.front(); queue_.pop(); return item; } }; ``` **工作原理:** - `push()`:生产者添加项目,向等待的消费者发出信号 - `pop()`:消费者等待直到项目可用,然后将其取出 - `mutex`:一次只能有一个线程访问 - `condition_variable`:高效等待(无忙循环) ## 7. 深入探讨:各组件详解 ### pcap_reader.h / pcap_reader.cpp **目的:** 读取由 Wireshark 保存的网络抓包 **关键结构:** ``` struct PcapGlobalHeader { uint32_t magic_number; // 0xa1b2c3d4 identifies PCAP uint16_t version_major; // Usually 2 uint16_t version_minor; // Usually 4 uint32_t snaplen; // Max packet size captured uint32_t network; // 1 = Ethernet }; struct PcapPacketHeader { uint32_t ts_sec; // Timestamp (seconds) uint32_t ts_usec; // Timestamp (microseconds) uint32_t incl_len; // Bytes saved in file uint32_t orig_len; // Original packet size }; ``` **关键函数:** - `open(filename)`:打开 PCAP,验证文件头 - `readNextPacket(raw)`:将下一个数据包读入缓冲区 - `close()`:清理资源 ### packet_parser.h / packet_parser.cpp **目的:** 从原始字节中提取协议字段 **关键函数:** ``` bool PacketParser::parse(const RawPacket& raw, ParsedPacket& parsed) { parseEthernet(...); // Extract MACs, EtherType parseIPv4(...); // Extract IPs, protocol, TTL parseTCP(...); // Extract ports, flags, seq numbers // OR parseUDP(...); // Extract ports } ``` **重要概念:** *网络字节序:* 网络协议使用大端序(最高有效字节在前)。您的计算机可能使用小端序。我们使用 `ntohs()` 和 `ntohl()` 进行转换: ``` // ntohs = Network TO Host Short (16-bit) uint16_t port = ntohs(*(uint16_t*)(data + offset)); // ntohl = Network TO Host Long (32-bit) uint32_t seq = ntohl(*(uint32_t*)(data + offset)); ``` ### sni_extractor.h / sni_extractor.cpp **目的:** 从 TLS 和 HTTP 中提取域名 **对于 TLS (HTTPS):** ``` std::optional SNIExtractor::extract( const uint8_t* payload, size_t length ) { // 1. Verify TLS record header // 2. Verify Client Hello handshake // 3. Skip to extensions // 4. Find SNI extension (type 0x0000) // 5. Extract hostname string } ``` **对于 HTTP:** ``` std::optional HTTPHostExtractor::extract( const uint8_t* payload, size_t length ) { // 1. Verify HTTP request (GET, POST, etc.) // 2. Search for "Host: " header // 3. Extract value until newline } ``` ### types.h / types.cpp **目的:** 定义全局使用的数据结构 **FiveTuple:** ``` struct FiveTuple { uint32_t src_ip; uint32_t dst_ip; uint16_t src_port; uint16_t dst_port; uint8_t protocol; bool operator==(const FiveTuple& other) const; }; ``` **AppType:** ``` enum class AppType { UNKNOWN, HTTP, HTTPS, DNS, GOOGLE, YOUTUBE, FACEBOOK, // ... more apps }; ``` **sniToAppType 函数:** ``` AppType sniToAppType(const std::string& sni) { if (sni.find("youtube") != std::string::npos) return AppType::YOUTUBE; if (sni.find("facebook") != std::string::npos) return AppType::FACEBOOK; // ... more patterns } ``` ## 8. SNI 提取的工作原理 ### TLS 握手 当您访问 `https://www.youtube.com` 时: ``` ┌──────────┐ ┌──────────┐ │ Browser │ │ Server │ └────┬─────┘ └────┬─────┘ │ │ │ ──── Client Hello ─────────────────────►│ │ (includes SNI: www.youtube.com) │ │ │ │ ◄─── Server Hello ───────────────────── │ │ (includes certificate) │ │ │ │ ──── Key Exchange ─────────────────────►│ │ │ │ ◄═══ Encrypted Data ══════════════════► │ │ (from here on, everything is │ │ encrypted - we can't see it) │ ``` **我们只能从 Client Hello 中提取 SNI!** ### TLS Client Hello 结构 ``` Byte 0: Content Type = 0x16 (Handshake) Bytes 1-2: Version = 0x0301 (TLS 1.0) Bytes 3-4: Record Length -- Handshake Layer -- Byte 5: Handshake Type = 0x01 (Client Hello) Bytes 6-8: Handshake Length -- Client Hello Body -- Bytes 9-10: Client Version Bytes 11-42: Random (32 bytes) Byte 43: Session ID Length (N) Bytes 44 to 44+N: Session ID ... Cipher Suites ... ... Compression Methods ... -- Extensions -- Bytes X-X+1: Extensions Length For each extension: Bytes: Extension Type (2) Bytes: Extension Length (2) Bytes: Extension Data -- SNI Extension (Type 0x0000) -- Extension Type: 0x0000 Extension Length: L SNI List Length: M SNI Type: 0x00 (hostname) SNI Length: K SNI Value: "www.youtube.com" ← THE GOAL! ``` ### 我们的提取代码(简化版) ``` std::optional SNIExtractor::extract( const uint8_t* payload, size_t length ) { // Check TLS record header if (payload[0] != 0x16) return std::nullopt; // Not handshake if (payload[5] != 0x01) return std::nullopt; // Not Client Hello size_t offset = 43; // Skip to session ID // Skip Session ID uint8_t session_len = payload[offset]; offset += 1 + session_len; // Skip Cipher Suites uint16_t cipher_len = readUint16BE(payload + offset); offset += 2 + cipher_len; // Skip Compression Methods uint8_t comp_len = payload[offset]; offset += 1 + comp_len; // Read Extensions Length uint16_t ext_len = readUint16BE(payload + offset); offset += 2; // Search for SNI extension size_t ext_end = offset + ext_len; while (offset + 4 <= ext_end) { uint16_t ext_type = readUint16BE(payload + offset); uint16_t ext_data_len = readUint16BE(payload + offset + 2); offset += 4; if (ext_type == 0x0000) { // SNI! // Parse SNI structure uint16_t sni_len = readUint16BE(payload + offset + 3); return std::string( (char*)(payload + offset + 5), sni_len ); } offset += ext_data_len; } return std::nullopt; // SNI not found } ``` ## 9. 拦截机制的工作原理 ### 规则类型 | 规则类型 | 示例 | 拦截目标 | |-----------|---------|----------------| | IP | `192.168.1.50` | 来自此源的所有流量 | | 应用 | `YouTube` | 所有 YouTube 连接 | | 域名 | `tiktok` | 任何包含 "tiktok" 的 SNI | ### 拦截流程 ``` Packet arrives │ ▼ ┌─────────────────────────────────┐ │ Is source IP in blocked list? │──Yes──► DROP └───────────────┬─────────────────┘ │No ▼ ┌─────────────────────────────────┐ │ Is app type in blocked list? │──Yes──► DROP └───────────────┬─────────────────┘ │No ▼ ┌─────────────────────────────────┐ │ Does SNI match blocked domain? │──Yes──► DROP └───────────────┬─────────────────┘ │No ▼ FORWARD ``` ### 基于流的拦截 **重要提示:** 我们是在*流*级别进行拦截,而不是数据包级别。 ``` Connection to YouTube: Packet 1 (SYN) → No SNI yet, FORWARD Packet 2 (SYN-ACK) → No SNI yet, FORWARD Packet 3 (ACK) → No SNI yet, FORWARD Packet 4 (Client Hello) → SNI: www.youtube.com → App: YOUTUBE (blocked!) → Mark flow as BLOCKED → DROP this packet Packet 5 (Data) → Flow is BLOCKED → DROP Packet 6 (Data) → Flow is BLOCKED → DROP ...all subsequent packets → DROP ``` **为什么采用这种方法?** - 在看到 Client Hello 之前,我们无法识别应用 - 一旦识别,我们将拦截该流的后续所有数据包 - 客户端上的连接将会失败/超时 ## 10. 构建与运行 ### 前置条件 - 带有 C++17 编译器的 **macOS/Linux** - **g++** 或 **clang++** - 无需外部库! ### 构建命令 **简单版:** ``` g++ -std=c++17 -O2 -I include -o dpi_simple \ src/main_working.cpp \ src/pcap_reader.cpp \ src/packet_parser.cpp \ src/sni_extractor.cpp \ src/types.cpp ``` **多线程版:** ``` g++ -std=c++17 -pthread -O2 -I include -o dpi_engine \ src/dpi_mt.cpp \ src/pcap_reader.cpp \ src/packet_parser.cpp \ src/sni_extractor.cpp \ src/types.cpp ``` ### 运行 **基本用法:** ``` ./build/dpi_engine test_dpi.pcap output.pcap ``` **带拦截规则运行:** ``` ./build/dpi_engine test_dpi.pcap output.pcap \ --block-app YouTube \ --block-app TikTok \ --block-ip 192.168.1.50 \ --block-domain facebook ``` **配置线程(仅限多线程版):** ``` ./dpi_engine input.pcap output.pcap --lbs 4 --fps 4 # 创建 4 个 LB 线程 × 4 个 FP 线程 = 16 个处理线程 ``` ### 创建测试数据 ``` python3 generate_test_pcap.py # 创建包含示例流量的 test_dpi.pcap ``` ## 11. 理解输出结果 ### 示例输出 ``` ╔══════════════════════════════════════════════════════════════╗ ║ DPI ENGINE v2.0 (Multi-threaded) ║ ╠══════════════════════════════════════════════════════════════╣ ║ Load Balancers: 2 FPs per LB: 2 Total FPs: 4 ║ ╚══════════════════════════════════════════════════════════════╝ [Rules] Blocked app: YouTube [Rules] Blocked IP: 192.168.1.50 [Reader] Processing packets... [Reader] Done reading 77 packets ╔══════════════════════════════════════════════════════════════╗ ║ PROCESSING REPORT ║ ╠══════════════════════════════════════════════════════════════╣ ║ Total Packets: 77 ║ ║ Total Bytes: 5738 ║ ║ TCP Packets: 73 ║ ║ UDP Packets: 4 ║ ╠══════════════════════════════════════════════════════════════╣ ║ Forwarded: 69 ║ ║ Dropped: 8 ║ ╠══════════════════════════════════════════════════════════════╣ ║ THREAD STATISTICS ║ ║ LB0 dispatched: 53 ║ ║ LB1 dispatched: 24 ║ ║ FP0 processed: 53 ║ ║ FP1 processed: 0 ║ ║ FP2 processed: 0 ║ ║ FP3 processed: 24 ║ ╠══════════════════════════════════════════════════════════════╣ ║ APPLICATION BREAKDOWN ║ ╠══════════════════════════════════════════════════════════════╣ ║ HTTPS 39 50.6% ########## ║ ║ Unknown 16 20.8% #### ║ ║ YouTube 4 5.2% # (BLOCKED) ║ ║ DNS 4 5.2% # ║ ║ Facebook 3 3.9% ║ ║ ... ║ ╚══════════════════════════════════════════════════════════════╝ [Detected Domains/SNIs] - www.youtube.com -> YouTube - www.facebook.com -> Facebook - www.google.com -> Google - github.com -> GitHub ... ``` ### 各部分含义 | 部分 | 含义 | |---------|---------| | 配置 | 创建的线程数量 | | 规则 | 激活的拦截规则 | | 总数据包 | 从输入文件中读取的数据包 | | 已转发 | 写入输出文件的数据包 | | 已丢弃 | 被拦截的数据包(未写入) | | 线程统计 | 跨线程的工作分布 | | 应用分类 | 流量分类结果 | | 检测到的 SNI | 实际发现的域名 | ## 12. 扩展项目 ### 改进建议 1. **添加更多应用签名** // 在 types.cpp 中 if (sni.find("twitch") != std::string::npos) return AppType::TWITCH; 2. **添加带宽限速** // 不是丢弃 (DROP),而是延迟数据包 if (shouldThrottle(flow)) { std::this_thread::sleep_for(10ms); } 3. **添加实时统计仪表盘** // 独立线程每秒打印一次统计信息 void statsThread() { while (running) { printStats(); sleep(1); } } 4. **添加 QUIC/HTTP3 支持** - QUIC 在端口 443 上使用 UDP - SNI 位于 Initial 数据包中(加密方式不同) 5. **添加持久化规则** - 将规则保存到文件 - 启动时加载 ## 总结 此 DPI 引擎演示了: 1. **网络协议解析** - 理解数据包结构 2. **深度包检测** - 查看加密连接内部 3. **流跟踪** - 管理有状态的连接 4. **多线程架构** - 通过线程池进行扩展 5. **生产者-消费者模式** - 线程安全队列 核心洞见在于,即使 HTTPS 流量也会在 TLS 握手过程中泄露目标域名,从而允许网络运营商识别并控制应用的使用情况。 ## 有疑问? 如果您对项目的任何部分有疑问,代码注释非常详尽,并且遵循与本文档描述相同的流程。请从简单版 (`main_working.cpp`) 开始理解概念,然后再转到多线程版 (`dpi_mt.cpp`) 了解如何增加并行性。 祝学习愉快!🚀
标签:AWS, C/C++, DPI, IP 地址批量处理, PCAP, SNI提取, 事务性I/O, 云计算, 入侵检测系统, 安全数据湖, 实时监控大屏, 恶意活动检测, 数据包拦截, 流量管理, 深度包检测, 网络安全, 网络安全分析, 规则引擎, 防御绕过, 防火墙, 隐私保护