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, 云计算, 入侵检测系统, 安全数据湖, 实时监控大屏, 恶意活动检测, 数据包拦截, 流量管理, 深度包检测, 网络安全, 网络安全分析, 规则引擎, 防御绕过, 防火墙, 隐私保护