Network Analysis Using Wireshark 2 Cookbook 学习:邮件协议分析详解
一、邮件系统是什么?
电子邮件是互联网上最早也是最重要的应用之一。每个用户有一个唯一的邮件地址,格式为:
username@domainname.com
比如:zhangsan@gmail.com
邮件系统由三个核心协议组成,它们各司其职:
发件人 收件人
[张三的邮件客户端] [李四的邮件客户端]
| |
| SMTP(发送) POP3/IMAP(接收)
| |
[张三的邮件服务器] ---SMTP(服务器间传输)---> [李四的邮件服务器]
二、三大协议概览
2.1 协议对比
| 协议 | 全名 | 作用 | 默认端口 | 加密端口(TLS) |
|---|---|---|---|---|
| POP3 | Post Office Protocol 3 | 客户端从服务器取邮件 | TCP 110 | TCP 995 |
| IMAP | Internet Mail Access Protocol | 客户端从服务器读邮件 | TCP 143 | TCP 993 |
| SMTP | Simple Mail Transfer Protocol | 客户端发邮件,服务器间转邮件 | TCP 25 | TCP 465 |
2.2 POP3 和 IMAP 的核心区别
打个比方:
- POP3 像去邮局取信,取走之后邮局里就没有了(邮件下载到本地,服务器删除)
- IMAP 像在邮局里看信,信始终放在邮局(邮件留在服务器,多设备同步)
| 特性 | POP3 | IMAP |
|---|---|---|
| 邮件存储位置 | 下载到本地,服务器可选删除 | 始终在服务器 |
| 多设备访问 | 不方便 | 完全支持 |
| 离线访问 | 支持(已下载) | 需要同步 |
| 适用场景 | 单设备、低带宽 | 多设备、随时访问 |
三、POP3 协议详解
3.1 POP3 工作流程
3.2 POP3 常用命令
| 命令 | 作用 | 示例 |
|---|---|---|
| USER | 发送用户名 | USER zhangsan |
| PASS | 发送密码 | PASS mypass |
| STAT | 查询邮箱状态 | 返回邮件数和总大小 |
| LIST | 列出所有邮件 | 返回每封邮件编号和大小 |
| RETR | 取回指定邮件 | RETR 1 取第1封 |
| DELE | 删除指定邮件 | DELE 1 删第1封 |
| NOOP | 保持连接(心跳) | 无操作,防超时断开 |
| QUIT | 退出,提交所有删除 | 断开连接 |
3.3 POP3 响应格式
POP3 只有两种响应:
+OK 具体信息 ← 成功
-ERR 错误原因 ← 失败
失败示例(认证失败):
客户端: USER zhangsan
服务器: +OK
客户端: PASS wrongpassword
服务器: -ERR Login failed: invalid username or password
(随后服务器主动关闭TCP连接)
3.4 Wireshark 过滤 POP3
# 过滤所有POP3报文
pop
# 过滤POP3请求命令(客户端发出的)
pop.request
# 过滤用户名命令
pop.request.command == "USER"
# 过滤密码命令
pop.request.command == "PASS"
# 过滤POP3响应(服务器发出的)
pop.response
# 过滤成功响应
pop.response.indicator == "+OK"
# 过滤错误响应
pop.response.indicator == "-ERR"
# 加密POP3(TLS)
tcp.port == 995
四、IMAP 协议详解
4.1 IMAP 工作流程
4.2 IMAP 响应状态码
IMAP 响应分为标记响应(带命令编号)和无标记响应(以 * 开头):
| 状态 | 含义 | 示例场景 |
|---|---|---|
| OK | 命令成功 | 登录成功、邮件获取成功 |
| NO | 命令失败(可重试) | 用户名密码错误、邮件不存在 |
| BAD | 命令语法错误 | 格式不对的命令 |
| BYE | 服务器断开连接 | 超时或LOGOUT后 |
| PREAUTH | 已预认证 | 某些安全连接方式 |
4.3 Wireshark 过滤 IMAP
# 过滤所有IMAP请求(客户端→服务器)
imap.request
# 过滤特定IMAP命令
imap.request.command == "LOGIN"
imap.request.command == "FETCH"
imap.request.command == "DELETE"
# 过滤所有IMAP响应(服务器→客户端)
imap.response
# 过滤特定响应状态
imap.response.status == "OK"
imap.response.status == "NO"
imap.response.status == "BAD"
# 加密IMAP(TLS)
tcp.port == 993
五、SMTP 协议详解
5.1 SMTP 工作流程(发送邮件)
5.2 SMTP 状态码结构
SMTP 状态码的格式是:
状态码=类别. 主题. 细节\text{状态码} = \text{类别}.\ \text{主题}.\ \text{细节}状态码=类别. 主题. 细节
例如状态码 450:
- 类别
4→ 临时性问题(可重试) - 主题
5→ 邮件投递状态 - 细节
0→ 未定义的错误
类别说明:
| 类别 | 代码 | 含义 | 说明 |
|---|---|---|---|
| 成功 | 2.x.x | 操作成功 | 邮件已正常处理 |
| 临时失败 | 4.x.x | 暂时性问题 | 稍后重试可能成功,如服务器繁忙 |
| 永久失败 | 5.x.x | 永久性问题 | 无法投递,如地址不存在 |
主题说明:
| 主题代码 | 含义 |
|---|---|
| x.0.x | 其他或未定义状态 |
| x.1.x | 地址相关问题 |
| x.2.x | 邮箱状态问题 |
| x.3.x | 邮件系统问题 |
| x.4.x | 网络和路由问题 |
| x.5.x | 邮件投递协议状态 |
| x.6.x | 邮件内容或媒体问题 |
| x.7.x | 安全或策略问题 |
5.3 常见 SMTP 状态码速查
| 状态码 | 含义 | 可能原因 |
|---|---|---|
| 220 | 服务就绪 | 服务器正常运行 |
| 221 | 服务关闭传输通道 | 正常关闭,无问题 |
| 235 | 认证成功 | 用户名密码正确 |
| 250 | 请求成功 | 邮件发送/操作成功 |
| 251 | 非本地用户,将转发 | 邮件将被中继转发 |
| 354 | 开始输入邮件内容 | 服务器等待邮件正文 |
| 421 | 服务不可用 | 服务未运行或超负荷 |
| 422 | 邮件大小超限 | 收件方邮箱配额已满 |
| 431 | 内存或磁盘已满 | 服务器存储问题 |
| 442 | 连接问题 | 到目标服务器的连接有问题 |
| 450 | 请求操作未执行 | 远程邮件服务问题 |
| 451 | 无效命令 | 服务器繁忙导致操作中止 |
| 452 | 存储不足 | 接收服务器存储空间不足 |
| 500 | 语法错误 | 命令不被识别 |
| 512 | DNS错误 | 目标服务器找不到 |
| 530 | 认证问题 | 需要认证或服务器被拉黑 |
| 542 | 收件人地址被拒绝 | 被反垃圾邮件系统拦截 |
5.4 Wireshark 过滤 SMTP
# 过滤所有SMTP响应报文
smtp.response
# 过滤特定状态码
smtp.response.code == 250
smtp.response.code == 535
# 过滤状态码范围(大于400的都是错误)
smtp.response.code > 400
# 过滤响应参数(如AUTH LOGIN)
smtp.rsp.parameter == "AUTH LOGIN"
# 加密SMTP(TLS)
tcp.port == 465
六、邮件协议完整工作架构
七、故障排查方法
7.1 故障排查思路
用户报告邮件问题
|
+---> 发不出去? --> 检查 SMTP (端口25/465)
|
+---> 收不到? --> 检查 POP3(端口110/995)
或 IMAP(端口143/993)
第一步:确认TCP连接是否建立
# 如果TCP连接失败(SYN包无响应),是网络/防火墙问题
# 如果TCP连接成功,但登录失败,是用户名密码问题
# 如果登录成功,但邮件操作失败,是协议/服务器问题
第二步:检查认证
常见认证失败错误:
| 协议 | 错误响应 | 含义 |
|---|---|---|
| POP3 | -ERR Login failed |
用户名或密码错误 |
| IMAP | NO LOGIN failed |
登录失败 |
| SMTP | 535 Authentication failed |
认证失败 |
第三步:检查TCP层问题
如果能登录但速度很慢,检查:
重传(Retransmission) → 链路丢包
重复ACK(Duplicate ACK) → 链路质量差
零窗口(Zero Window) → 服务器/客户端处理太慢
窗口满(Window Full) → 接收缓冲区满
7.2 服务器间通信慢的排查
八、垃圾邮件和恶意邮件分析
8.1 分析步骤
第一步:过滤出带数据的邮件报文
# POP3协议中含有数据的报文
pop || data-text-lines
第二步:通过数据量初步判断
数据量很小(几KB) → 纯文本邮件,通常无附件
数据量很大(几百KB以上)→ 可能含有附件,需重点分析
第三步:追踪TCP流
在 Wireshark 中右键报文 → Follow TCP Stream,可以看到完整的邮件内容:
From: spam@malicious.com
To: victim@company.com
Subject: 您中奖了!
Content-Type: application/pdf; name="prize.pdf"
...
(PDF文件的Base64编码内容)
第四步:识别垃圾邮件特征
检查发件人地址:是否来自可疑域名
检查附件类型:.exe .pdf .doc 等可执行或宏文件
检查主题行:含有"中奖"、"紧急"等诱导词
检查相似邮件:多封邮件来自同一IP或域名
第五步:设置过滤规则
在邮件服务器上,根据分析结果设置规则:
发件人域名黑名单
发件人IP黑名单
附件类型过滤
主题关键词过滤
九、SSL/TLS 加密与解密
9.1 为什么需要加密?
不加密的邮件传输,用 Wireshark 就能看到明文的用户名和密码,非常危险:
不加密:
客户端 → USER zhangsan ← Wireshark 可见
客户端 → PASS mypassword123 ← Wireshark 可见!
加密后(TLS):
客户端 → [加密数据,无法读取]
9.2 各协议加密端口
| 协议 | 明文端口 | 加密端口(TLS) |
|---|---|---|
| POP3 | TCP 110 | TCP 995 |
| IMAP | TCP 143 | TCP 993 |
| SMTP | TCP 25 | TCP 465 |
9.3 Wireshark 解密 TLS(需要私钥)
获取 SSL 私钥的方式:
- macOS:钥匙串访问(Keychain Access)→ 找到对应证书并导出
- Windows:MMC(微软管理控制台)→ 证书 → 找到对应证书并导出
在 Wireshark 中配置:
菜单:编辑(Edit) → 首选项(Preferences)
→ Protocols → TLS
→ RSA keys list → 添加私钥文件
十、完整 C++ 代码:SMTP 状态码解析器
下面的代码演示如何解析 SMTP 状态码的三段结构,并给出详细的含义说明。
#include <iostream>
#include <string>
#include <map>
#include <sstream>
// SMTP 状态码解析器
// 状态码格式:三位数字,例如 250、421、535
// 结构:类别(第1位) + 主题(第2位) + 细节(第3位)
// 获取状态码类别含义(第一位数字)
std::string getClassMeaning(int cls) {
switch (cls) {
case 2: return "成功(Success)- 操作已成功完成";
case 4: return "临时失败(Transient Failure)- 暂时性问题,稍后可重试";
case 5: return "永久失败(Permanent Failure)- 永久性错误,重试也无用";
default: return "未知类别";
}
}
// 获取状态码主题含义(第二位数字)
std::string getSubjectMeaning(int subj) {
switch (subj) {
case 0: return "其他/未定义状态";
case 1: return "地址相关状态(Addressing)";
case 2: return "邮箱状态(Mailbox Status)";
case 3: return "邮件系统状态(Mail System)";
case 4: return "网络和路由状态(Network/Routing)";
case 5: return "邮件投递协议状态(Delivery Protocol)";
case 6: return "邮件内容或媒体状态(Content/Media)";
case 7: return "安全或策略状态(Security/Policy)";
default: return "未知主题";
}
}
// 常见 SMTP 状态码数据库
// key: 状态码, value: 说明
std::map<int, std::string> buildSmtpCodeMap() {
std::map<int, std::string> codes;
// 2xx 成功类
codes[220] = "服务就绪(Service Ready)- 服务器等待命令";
codes[221] = "服务关闭传输通道(Closing Channel)- 正常断开";
codes[235] = "认证成功(Authentication Successful)";
codes[250] = "请求操作完成(Requested Mail Action OK)- 邮件成功";
codes[251] = "非本地用户,将转发(User Not Local, Will Forward)";
codes[252] = "无法验证用户,仍将尝试投递";
codes[354] = "开始输入邮件内容,以单独一行的.结束";
// 4xx 临时失败类
codes[421] = "服务不可用(Service Not Available)- 服务器过载或未运行";
codes[422] = "邮件大小超过配额(Mailbox Full)";
codes[431] = "磁盘已满或内存不足(Out of Disk/Memory)";
codes[432] = "邮件队列已停止(Queue Stopped)";
codes[441] = "接收服务器无响应(Receiving Server Not Responding)";
codes[442] = "连接中断(Bad Connection)";
codes[444] = "无法路由(Unable to Route)";
codes[445] = "邮件系统拥塞(Mail System Congestion)";
codes[447] = "投递超时(Delivery Timeout)";
codes[450] = "请求操作未执行(Action Not Taken)- 远程邮件服务问题";
codes[451] = "本地错误,操作中止(Local Error in Processing)";
codes[452] = "存储空间不足(Insufficient Storage)";
// 5xx 永久失败类
codes[500] = "语法错误,命令无法识别(Syntax Error)";
codes[501] = "参数或参数中的语法错误";
codes[503] = "命令顺序错误(Bad Sequence of Commands)";
codes[512] = "DNS错误(Host Not Found)- 找不到目标服务器";
codes[530] = "需要认证(Authentication Required)或服务器被黑名单";
codes[535] = "认证失败(Authentication Failed)- 用户名/密码错误";
codes[542] = "收件人地址被拒绝(Recipient Address Rejected)- 被反垃圾系统拦截";
codes[550] = "邮件被拒绝,邮箱不可用";
codes[552] = "超出存储分配(Exceeded Storage Allocation)";
codes[554] = "事务失败(Transaction Failed)";
return codes;
}
// 解析三位 SMTP 状态码
void parseSmtpCode(int code) {
// 提取三段数字
int cls = code / 100; // 第一位:类别
int subj = (code / 10) % 10; // 第二位:主题
int detail = code % 10; // 第三位:细节
std::cout << "==============================" << std::endl;
std::cout << " SMTP 状态码解析:" << code << std::endl;
std::cout << "==============================" << std::endl;
// 检查状态码范围
if (cls < 2 || cls > 5) {
std::cout << " 无效的 SMTP 状态码(应为 2xx/4xx/5xx)" << std::endl;
return;
}
std::cout << " 类别(第1位 = " << cls << "): "
<< getClassMeaning(cls) << std::endl;
std::cout << " 主题(第2位 = " << subj << "): "
<< getSubjectMeaning(subj) << std::endl;
std::cout << " 细节(第3位 = " << detail << "): "
<< detail << std::endl;
// 查表获取详细说明
auto codeMap = buildSmtpCodeMap();
auto it = codeMap.find(code);
if (it != codeMap.end()) {
std::cout << " 详细说明: " << it->second << std::endl;
} else {
std::cout << " 详细说明: (未在常用列表中,请查阅 RFC 3463)" << std::endl;
}
// 给出操作建议
std::cout << " 处理建议: ";
if (cls == 2) {
std::cout << "正常,无需处理。" << std::endl;
} else if (cls == 4) {
std::cout << "临时性问题,建议等待后重试,或检查服务器状态。" << std::endl;
} else if (cls == 5) {
std::cout << "永久性错误,重试无效,需检查配置或联系管理员。" << std::endl;
}
std::cout << std::endl;
}
int main() {
std::cout << "====================================" << std::endl;
std::cout << " SMTP 状态码解析演示程序" << std::endl;
std::cout << "====================================" << std::endl << std::endl;
// 演示常见状态码解析
int testCodes[] = {220, 250, 354, 421, 451, 500, 535, 512};
for (int code : testCodes) {
parseSmtpCode(code);
}
// 交互模式:让用户输入状态码
std::cout << "------------------------------" << std::endl;
std::cout << "请输入要查询的 SMTP 状态码(输入0退出):" << std::endl;
int userCode = 0;
while (true) {
std::cout << "> ";
std::cin >> userCode;
if (userCode == 0) {
std::cout << "退出程序。" << std::endl;
break;
}
if (userCode < 100 || userCode > 599) {
std::cout << "无效输入,请输入100-599之间的整数。" << std::endl;
continue;
}
parseSmtpCode(userCode);
}
return 0;
}
编译和运行:
g++ -std=c++17 -o smtp_parser smtp_parser.cpp
./smtp_parser
部分输出示例:
==============================
SMTP 状态码解析:250
==============================
类别(第1位 = 2): 成功(Success)- 操作已成功完成
主题(第2位 = 5): 邮件投递协议状态(Delivery Protocol)
细节(第3位 = 0): 0
详细说明: 请求操作完成(Requested Mail Action OK)- 邮件成功
处理建议: 正常,无需处理。
==============================
SMTP 状态码解析:535
==============================
类别(第1位 = 5): 永久失败(Permanent Failure)- 永久性错误,重试也无用
主题(第2位 = 3): 邮件系统状态(Mail System)
细节(第3位 = 5): 5
详细说明: 认证失败(Authentication Failed)- 用户名/密码错误
处理建议: 永久性错误,重试无效,需检查配置或联系管理员。
十一、知识树总结
邮件协议体系
|
+-- POP3(接收邮件)
| |-- 端口: TCP 110 / TLS 995
| |-- 特点: 邮件下载到本地,服务器可删除
| |-- 响应: +OK / -ERR
| +-- 常用命令: USER PASS STAT LIST RETR DELE NOOP QUIT
|
+-- IMAP(读取邮件)
| |-- 端口: TCP 143 / TLS 993
| |-- 特点: 邮件留在服务器,多设备同步
| |-- 响应: OK / NO / BAD / BYE
| +-- 常用命令: LOGIN SELECT FETCH DELETE LOGOUT
|
+-- SMTP(发送邮件)
| |-- 端口: TCP 25 / TLS 465
| |-- 特点: 客户端→服务器、服务器→服务器
| |-- 状态码结构: 类别.主题.细节
| | |-- 2xx 成功
| | |-- 4xx 临时失败(可重试)
| | +-- 5xx 永久失败(无需重试)
| +-- 常用命令: EHLO AUTH MAIL FROM RCPT TO DATA QUIT
|
+-- 安全加密 TLS/SSL
| |-- 全部协议均支持 TLS
| +-- Wireshark 需要私钥才能解密
|
+-- 故障排查思路
|-- 发不出去 → 检查 SMTP
|-- 收不到 → 检查 POP3/IMAP
|-- TCP连接失败 → 网络/防火墙
|-- 认证失败 → 用户名/密码
+-- 速度慢 → 检查TCP重传/带宽
NetBIOS 与 SMB 协议分析详解(从零开始)
一、为什么需要 NetBIOS 和 SMB?
在企业内网中,员工经常需要:
- 访问共享文件夹(“网络驱动器”)
- 使用共享打印机
- 远程执行文件
这些功能在 Windows 网络里,都依赖 NetBIOS 和 SMB 这两套协议来实现。
打个比方:NetBIOS 是"楼道"(提供基础通道),SMB 是"快递员"(在楼道里搬运具体的文件和数据)。
二、NetBIOS 协议详解
2.1 什么是 NetBIOS?
NetBIOS(Network Basic Input/Output System,网络基本输入输出系统)是 1980 年代初为局域网通信开发的一套协议,工作在 OSI 模型的第五层(会话层)。
后来微软将其迁移到 TCP/IP 网络上运行,称为 NBT(NetBIOS over TCP/IP),规范见 RFC 1001 和 RFC 1002。
2.2 NetBIOS 的三大服务
NetBIOS 协议族
|
+-- NetBIOS-NS 名称服务 UDP 137 (负责"查电话本":名字→IP)
|
+-- NetBIOS-DGM 数据报服务 UDP 138 (负责"广播通告":我提供哪些服务)
|
+-- NetBIOS-SSN 会话服务 TCP 139 (负责"打电话":建立连接访问文件)
用 mermaid 展示三大服务的职责:
2.3 三大服务详细说明
名称服务(NBNS,UDP 137)
类似 DNS,但用于局域网内的 NetBIOS 名称解析:
数据报服务(NBDS,UDP 138)
设备定期广播自己提供哪些服务,所有人都能收到:
172.16.100.10 (FILE-SRV) 广播:我是 工作站 + 文件服务器 + SQL服务器
172.16.100.204 (GOLF) 广播:我是 工作站 + 文件服务器 + 打印队列服务器
会话服务(NBSS,TCP 139)
用于实际的文件操作,SMB 协议就跑在这一层上。
三、SMB 协议详解
3.1 什么是 SMB?
SMB(Server Message Block,服务器消息块)是在 NetBIOS 会话层之上运行的协议,主要用于:
- 浏览和访问共享目录
- 复制/读写文件
- 访问网络打印机
- 进程间通信(IPC)
CIFS(Common Internet File System)是 SMB 的一个变体/别名,本质相同。
SMB 可以跑在两个地方:
- TCP 139(通过 NetBIOS 会话层)
- TCP 445(直接运行,不需要 NetBIOS,更现代)
3.2 SMB 版本演进
SMB 1.0 (早期版本,命令繁多,效率低)
|
v
SMB 2.0 (2006年,Windows Vista,简化命令,微软后开放标准)
|
v
SMB 2.1 (Windows 7 / Server 2008 R2,性能提升)
|
v
SMB 3.0 (Windows 8 / Server 2012,大幅性能提升,支持虚拟化环境)
3.3 SMB 工作模型
SMB 是客户端-服务器模型:
四、NetBIOS/SMB 故障分析
4.1 通用排查步骤
第一步:先看全局
用 Wireshark 接入网络,检查以下几点:
1. NBNS 广播是否正常(nbns.flags.response == 0 过滤请求包)
2. 广播频率是否过高(正常:每设备 5~10 次/分钟)
3. 是否有 169.254.x.x 地址(APIPA地址,说明该PC没拿到DHCP地址)
4. 是否有异常的扫描行为(某些蠕虫病毒会大量发 NBNS 查询)
第二步:过滤 SMB 错误
# 过滤所有 SMB 错误(状态码不为0即为错误)
smb.nt_status != 0x0
# 过滤 SMB2 错误
smb2.nt_status != 0x0
常见 SMB 错误码:
| 错误码 | 名称 | 含义 | 常见原因 |
|---|---|---|---|
| 0x0 | STATUS_OK | 一切正常 | 正常操作 |
| 0xC0000022 | STATUS_ACCESS_DENIED | 访问被拒绝 | 权限不足,账户问题 |
| 0xC0000034 | STATUS_OBJECT_NAME_NOT_FOUND | 文件不存在 | 路径错误 |
| 0xC000006D | STATUS_LOGON_FAILURE | 登录失败 | 密码错误 |
| 0x00000016 | STATUS_MORE_PROCESSING_REQUIRED | 需要更多处理 | 可能是凭据问题 |
4.2 Wireshark 过滤命令汇总
# NetBIOS 通用
netbios
# 名称服务(端口137)
nbns
nbns.flags.response == 0 过滤所有NBNS查询请求
nbns.flags.response == 1 过滤所有NBNS响应
# 数据报服务(端口138)
nbds
# 会话服务(端口139)
nbss
# SMB 1.x
smb
smb.nt_status != 0x0 所有SMB错误
# SMB 2.x/3.x
smb2
smb2.nt_status != 0x0 所有SMB2错误
五、真实案例分析
案例一:应用程序卡死(Freeze)
现象: 用户打开某个网络应用,程序卡住不动。
Wireshark 分析流程:
根本原因: 账户没有访问该共享目录的权限,但程序没有给用户友好的提示,只是一直等待,表现为"卡死"。
解决方法: 检查并修复 Windows 文件共享权限(文件夹权限 + 共享权限都要检查)。
案例二:广播风暴导致网络中断
现象: 某分公司突然完全断网,连本地网关都 ping 不通。
网络结构:
分公司子网: 172.30.121.0/24
网关: 172.30.121.254
|
| MPLS L3 VPN
|
总部子网: 172.30.0.0/24
排查经过:
步骤1: ping 总部服务器 → 无响应
步骤2: 联系运营商 → 运营商监控显示线路正常(没有流量负载)
步骤3: ping 本地网关 172.30.121.254 → 无响应!
→ 说明问题在本地局域网,不是WAN
步骤4: Wireshark 接到网关端口 → 发现异常!
Wireshark 发现的问题:
源IP: 172.30.121.1
目标: 广播地址(255.255.255.255)
协议: SMB Mailslot(SMB邮槽协议)
操作: Write Mail Slot(写邮槽)
速率: 5000 包/秒 = 10 Mbps ← 直接打满了路由器端口!
流量计算:
设每个广播包约 200 字节,速率为 5000 包/秒:
带宽消耗=5000×200×8≈8,000,000 bps=8 Mbps\text{带宽消耗} = 5000 \times 200 \times 8 \approx 8,000,000 \text{ bps} = 8 \text{ Mbps}带宽消耗=5000×200×8≈8,000,000 bps=8 Mbps
一个 10 Mbps 的路由器端口就此被打满,其他所有流量(包括 ping、DNS、正常上网)全部被挤出去。
根因: 一个叫 LS3Bcast.exe 的服务程序出现 bug,疯狂发送 SMB 广播包。
解决方法: 找到并停止 LS3Bcast.exe 服务,防止其自动重启。
广播风暴 ASCII 示意:
正常状态(广播很少):
时间轴 ──────────────────────────────────>
广播包 . . . . . . .
(稀少,每设备约5~10次/分钟)
广播风暴状态:
时间轴 ──────────────────────────────────>
广播包 ||||||||||||||||||||||||||||||||||||
(5000包/秒,路由器/交换机被打爆)
所有正常流量 → 全部丢弃!
六、数据库流量与网络延迟分析
6.1 问题背景
当用户抱怨"数据库查询很慢"时,可能有三种原因:
6.2 延迟对数据库性能的影响
假设客户端和数据库服务器之间的往返延迟(RTT)为 35ms,完成一次查询需要 371 个来回包:
总耗时≈371×35ms=12,985ms≈13秒\text{总耗时} \approx 371 \times 35\text{ms} = 12,985\text{ms} \approx 13\text{秒}总耗时≈371×35ms=12,985ms≈13秒
加上可能的重传和低效操作,用户实际等待时间可能达到 10~15 秒甚至更长。
这说明:即使网络本身没问题,高延迟链路上"来回次数太多"就会导致严重的性能问题。
6.3 排查问题的提问清单
在开始抓包之前,先问清楚以下问题:
| 问题 | 目的 |
|---|---|
| 问题是全局还是局部?(所有人还是某些人) | 判断是WAN带宽还是特定用户/应用问题 |
| 所有应用都慢,还是只有这个数据库应用? | 判断是网络问题还是特定应用问题 |
| 问题总是有,还是特定时间出现? | 判断是否和高峰时段、特定操作相关 |
| 客户端和服务器在同一地点吗? | 判断是LAN还是WAN链路问题 |
6.4 用 Wireshark 定位数据库延迟
步骤一: 用 TCP 流追踪(Follow TCP Stream)看整个会话
步骤二: 观察每个请求-响应之间的时间间隔
包编号 时间 说明
------ ----- ------
100 0.000 客户端发出DB查询
101 0.035 服务器响应(35ms后)
102 0.035 客户端发出下一个请求
103 0.070 服务器响应
...
(如此重复371次)
步骤三: 用 TCP 对话统计找出高重传率的连接
正常内网重传率:0.1%0.1\%0.1% 到 0.5%0.5\%0.5%
超过此范围说明有网络问题,需要先解决网络层问题再分析应用层。
6.5 数据库依赖链问题
用户
|
| (看起来正常)
v
数据库服务器
|
| (这里才是瓶颈!)
v
文件服务器(共享存储)
重要提醒:客户端连数据库服务器可能很快,但数据库服务器去读共享文件服务器很慢,这时瓶颈在"数据库服务器到文件服务器"这段,必须把 Wireshark 接到这段链路才能发现。
6.6 解决方案
| 问题类型 | 解决方案 |
|---|---|
| WAN 延迟高(35ms以上) | 减少数据库来回包次数(DBA优化查询),或改用终端服务/Web访问 |
| WAN 带宽不足 | 升级带宽,或压缩传输数据 |
| 重传率过高 | 检查链路质量,修复网络层问题 |
| 应用本身效率低 | DBA 优化 SQL 查询,减少 N+1 查询问题 |
七、导出 SMB 对象(Export SMB Objects)
7.1 功能说明
Wireshark 可以从抓包文件中提取通过 SMB 传输的文件(图片、文档、可执行文件等),这对于:
- 网络取证(查看传输了什么文件)
- 备份验证
- 安全审计
非常有用。
7.2 操作步骤
Wireshark 菜单:
File(文件)
└─ Export Objects(导出对象)
└─ SMB/SMB2
└─ 弹出对象列表窗口
对象列表中的字段含义:
| 字段 | 含义 |
|---|---|
| Packet | 该对象对应的报文编号 |
| Hostname | 被访问的服务器名称 |
| Content Type | FILE(文件)或 PIPE(命名管道/IPC通信) |
| Size | 文件大小 |
| Filename | 文件路径和名称 |
Content Type 说明:
FILE → 实际的共享文件(文档、图片、可执行文件等)
PIPE → 命名管道,用于IPC(进程间通信)
例如:微软 RPC over SMB 就使用命名管道
7.3 前提条件
在使用此功能前,需要在 TCP 首选项里开启流重组:
Wireshark 菜单:
Edit(编辑)→ Preferences(首选项)
└─ Protocols(协议)
└─ TCP
└─ 勾选 "Allow subdissector to reassemble TCP streams"
(允许子解析器重组 TCP 流)
八、完整 C++ 代码:SMB 错误码查询工具
#include <iostream>
#include <string>
#include <map>
#include <iomanip>
#include <sstream>
// SMB NT 状态码查询工具
// NT 状态码是 32 位整数,高2位表示严重程度
// 格式:SSFF xxxx xxxx xxxx xxxx xxxx xxxx xxxx
// SS: 严重程度(00=成功, 01=信息, 10=警告, 11=错误)
// FF: Facility(设施码)
// 后28位: 具体错误编号
// 获取严重程度(取最高2位)
std::string getSeverity(uint32_t code) {
// 右移30位取最高2位
uint32_t sev = code >> 30;
switch (sev) {
case 0: return "成功(SUCCESS)";
case 1: return "信息(INFORMATIONAL)";
case 2: return "警告(WARNING)";
case 3: return "错误(ERROR)";
default: return "未知";
}
}
// 建立常见 SMB/NT 状态码数据库
std::map<uint32_t, std::string> buildNtStatusMap() {
std::map<uint32_t, std::string> m;
// 成功码(0x0开头)
m[0x00000000] = "STATUS_SUCCESS / STATUS_OK - 操作成功";
m[0x00000016] = "STATUS_MORE_PROCESSING_REQUIRED - 需要更多处理(常见于NTLM认证过程,可能是凭据问题)";
m[0x0000007B] = "STATUS_OBJECT_NAME_INVALID - 对象名称无效";
// 警告码(0x4/0x8开头)
m[0x40000006] = "STATUS_WORKING_SET_QUOTA - 工作集配额不足";
m[0x80000005] = "STATUS_BUFFER_OVERFLOW - 缓冲区溢出(数据被截断,不一定是错误)";
m[0x80000006] = "STATUS_NO_MORE_FILES - 没有更多文件(目录枚举结束,正常)";
// 错误码(0xC开头)
m[0xC0000001] = "STATUS_UNSUCCESSFUL - 操作未成功";
m[0xC0000002] = "STATUS_NOT_IMPLEMENTED - 功能未实现";
m[0xC0000005] = "STATUS_ACCESS_VIOLATION - 访问违规";
m[0xC0000008] = "STATUS_INVALID_HANDLE - 无效句柄";
m[0xC0000017] = "STATUS_NO_MEMORY - 内存不足";
m[0xC000001C] = "STATUS_INVALID_DEVICE_REQUEST - 无效设备请求";
m[0xC0000022] = "STATUS_ACCESS_DENIED - 访问被拒绝(权限不足,检查共享和NTFS权限)";
m[0xC0000023] = "STATUS_BUFFER_TOO_SMALL - 缓冲区太小";
m[0xC0000034] = "STATUS_OBJECT_NAME_NOT_FOUND - 对象/文件不存在(检查路径是否正确)";
m[0xC0000035] = "STATUS_OBJECT_NAME_COLLISION - 对象名称冲突(文件已存在)";
m[0xC000003A] = "STATUS_OBJECT_PATH_NOT_FOUND - 路径不存在";
m[0xC000006D] = "STATUS_LOGON_FAILURE - 登录失败(用户名或密码错误)";
m[0xC000006E] = "STATUS_ACCOUNT_RESTRICTION - 账户受限制";
m[0xC0000070] = "STATUS_INVALID_WORKSTATION - 工作站无效";
m[0xC0000071] = "STATUS_INVALID_LOGON_HOURS - 登录时间段无效";
m[0xC0000072] = "STATUS_ACCOUNT_DISABLED - 账户已禁用(联系管理员启用账户)";
m[0xC000007F] = "STATUS_DISK_FULL - 磁盘已满(服务器存储空间不足)";
m[0xC0000101] = "STATUS_DIRECTORY_NOT_EMPTY - 目录非空(无法删除非空目录)";
m[0xC0000103] = "STATUS_NOT_A_DIRECTORY - 不是目录";
m[0xC000013B] = "STATUS_UNEXPECTED_NETWORK_ERROR - 意外的网络错误";
m[0xC00000BB] = "STATUS_NOT_SUPPORTED - 操作不受支持";
m[0xC00000CC] = "STATUS_BAD_NETWORK_NAME - 网络名称错误(共享路径不存在)";
m[0xC00000D0] = "STATUS_REQUEST_NOT_ACCEPTED - 请求未被接受(服务器繁忙)";
return m;
}
// 将 uint32_t 格式化为十六进制字符串
std::string toHex(uint32_t val) {
std::stringstream ss;
ss << "0x" << std::uppercase << std::hex << std::setw(8)
<< std::setfill('0') << val;
return ss.str();
}
// 解析并显示 NT 状态码
void parseNtStatus(uint32_t code) {
auto statusMap = buildNtStatusMap();
std::cout << "==============================" << std::endl;
std::cout << " NT 状态码解析:" << toHex(code) << std::endl;
std::cout << "==============================" << std::endl;
// 严重程度(最高2位)
std::cout << " 严重程度: " << getSeverity(code) << std::endl;
// 设施码(第16~28位)
uint32_t facility = (code >> 16) & 0x0FFF;
std::cout << " 设施码 (Facility): " << facility << std::endl;
// 错误编号(低16位)
uint32_t errCode = code & 0xFFFF;
std::cout << " 错误编号: " << errCode << " (0x"
<< std::hex << errCode << std::dec << ")" << std::endl;
// 查表
auto it = statusMap.find(code);
if (it != statusMap.end()) {
std::cout << " 含义说明: " << it->second << std::endl;
} else {
std::cout << " 含义说明: (未在常用列表中)" << std::endl;
std::cout << " 参考: https://msdn.microsoft.com/en-us/library/ee441884.aspx"
<< std::endl;
}
// 操作建议
std::cout << " 处理建议: ";
uint32_t sev = code >> 30;
if (sev == 0) {
std::cout << "操作成功,无需处理。" << std::endl;
} else if (sev == 1) {
std::cout << "信息性状态,通常无需处理。" << std::endl;
} else if (sev == 2) {
std::cout << "警告,可能需要关注但不影响操作。" << std::endl;
} else {
std::cout << "错误!需要排查。检查权限、路径、账户状态等。" << std::endl;
}
std::cout << std::endl;
}
int main() {
std::cout << "========================================" << std::endl;
std::cout << " SMB NT 状态码解析工具" << std::endl;
std::cout << "========================================" << std::endl << std::endl;
// 演示常见错误码
uint32_t demoCodes[] = {
0x00000000, // STATUS_SUCCESS
0x00000016, // STATUS_MORE_PROCESSING_REQUIRED
0xC0000022, // STATUS_ACCESS_DENIED
0xC000006D, // STATUS_LOGON_FAILURE
0xC00000CC, // STATUS_BAD_NETWORK_NAME
0xC0000034 // STATUS_OBJECT_NAME_NOT_FOUND
};
for (uint32_t code : demoCodes) {
parseNtStatus(code);
}
// 交互模式
std::cout << "------------------------------" << std::endl;
std::cout << "输入十六进制状态码查询(例如 C0000022),输入 0 退出:" << std::endl;
std::string input;
while (true) {
std::cout << "> ";
std::cin >> input;
if (input == "0") {
std::cout << "退出程序。" << std::endl;
break;
}
// 解析十六进制输入(允许带或不带0x前缀)
try {
// 去掉可能的 0x 前缀
if (input.size() > 2 &&
(input.substr(0,2) == "0x" || input.substr(0,2) == "0X")) {
input = input.substr(2);
}
uint32_t code = static_cast<uint32_t>(std::stoul(input, nullptr, 16));
parseNtStatus(code);
} catch (...) {
std::cout << "输入无效,请输入十六进制数(如 C0000022)。" << std::endl;
}
}
return 0;
}
编译和运行:
g++ -std=c++17 -o smb_status smb_status.cpp
./smb_status
输出示例:
==============================
NT 状态码解析:0xC0000022
==============================
严重程度: 错误(ERROR)
设施码 (Facility): 0
错误编号: 34 (0x22)
含义说明: STATUS_ACCESS_DENIED - 访问被拒绝(权限不足,检查共享和NTFS权限)
处理建议: 错误!需要排查。检查权限、路径、账户状态等。
九、综合知识树
NetBIOS 与 SMB 协议体系
|
+-- NetBIOS 三大服务
| |
| +-- NBNS(UDP 137)名称服务
| | |-- 注册:客户端告诉域控"我叫XXX"
| | +-- 查询:谁叫XXX,它的IP是什么?
| |
| +-- NBDS(UDP 138)数据报服务
| | +-- 广播通告自己提供的服务(文件、打印等)
| |
| +-- NBSS(TCP 139)会话服务
| +-- SMB 在此之上运行(文件读写等)
|
+-- SMB 协议
| |
| +-- 版本:1.0 → 2.0 → 2.1 → 3.0
| |
| +-- 运行端口:TCP 139(经由NetBIOS)/ TCP 445(直连)
| |
| +-- 主要功能
| | |-- 共享目录访问
| | |-- 文件读写复制
| | |-- 打印机访问
| | +-- IPC(命名管道通信)
| |
| +-- 错误码(NT Status)
| |-- 0x00000000 成功
| |-- 0xC0000022 访问拒绝
| |-- 0xC000006D 登录失败
| +-- 0xC00000CC 网络名称错误
|
+-- 常见故障
| |
| +-- 应用程序卡死
| | 原因:权限拒绝后程序无限等待
| |
| +-- 广播风暴
| | 原因:程序bug疯狂发SMB广播
| | 影响:打满带宽,整网瘫痪
| |
| +-- 数据库查询慢
| 原因:WAN延迟 × 大量来回包 = 极长等待时间
| 公式:总耗时 ≈ 包次数 × 单次RTT
|
+-- 排查工具
|-- Wireshark 过滤器
|-- SMB 对象导出(File→Export Objects→SMB)
+-- TCP 流追踪(Follow TCP Stream)
企业应用行为分析详解(从零开始)
一、为什么要分析企业应用?
在企业网络中,应用变慢的原因通常有四种:
应用变慢
|
+-- LAN(局域网)问题 → 有线LAN中极少见
|
+-- WAN(广域网)问题 → 最常见,带宽不足或延迟高
|
+-- 服务器/客户端性能差 → CPU、内存、磁盘不足
|
+-- 应用本身的问题 → 程序设计低效、Bug
Wireshark 是定位这些问题的核心工具。本章涉及四类企业应用:
- 网络协议识别
- 终端服务(RDP / Citrix)
- 数据库流量
- SNMP 网络管理协议
二、识别网络上运行的应用
2.1 为什么要先识别应用?
接手一个新网络时,首先要搞清楚"线上跑的是什么"。不同应用会相互影响,带宽竞争会导致彼此都变慢。
几个重要概念:
| 概念 | 含义 | 位置 |
|---|---|---|
| VLAN | 虚拟局域网,OSI 1-2 层隔离 | 交换机上配置 |
| VRF | 虚拟路由转发,多个独立路由表实例 | 路由器上配置 |
| Blade Server | 刀片服务器,多块服务器板卡 + 后置交换机的机箱 | 数据中心 |
VRF 类比:一栋楼里有多家公司,各自有独立的门禁系统(路由表),互相看不见对方,但共用同一栋楼(同一台路由器)。这在运营商 MPLS 网络中很常见。
2.2 Wireshark 工具:协议层级(Protocol Hierarchy)
菜单路径:Statistics → Protocol Hierarchy
这个功能会展示当前捕获中所有协议的流量占比,像一棵树一样展开:
2.3 Wireshark 工具:对话统计(Conversations)
菜单路径:Statistics → Conversations
列出所有"谁在和谁说话",按协议分类(Ethernet / IP / TCP / UDP),可以看到:
- 源 IP、目标 IP
- 数据量
- 使用的端口(即什么应用)
2.4 抓包位置选择
| 要监控的对象 | Wireshark 接入位置 |
|---|---|
| 某台服务器 | 端口镜像(Port Mirror)到该服务器的交换机端口 |
| 远程分公司 | 端口镜像到连接 WAN 的路由器端口 |
| 互联网出口 | 端口镜像到互联网出口链路 |
三、终端服务分析(RDP / Citrix)
3.1 什么是终端服务?
终端服务让用户的电脑(或瘦客户端)连接到远程服务器,在服务器上运行应用,本地屏幕只显示"画面变化"。
两种主要实现:
| 产品 | 协议 | 端口 |
|---|---|---|
| Microsoft Terminal Server | RDP(远程桌面协议) | TCP 3389 |
| Citrix Metaframe | ICA(独立计算架构) | TCP 1494 或 2598 |
3.2 终端服务的流量特征
终端服务传输的是屏幕变化,所以流量高度不对称:
下行(服务器→客户端):几十 Kbps ~ 几 Mbps(屏幕内容)
上行(客户端→服务器):最多几 Kbps(鼠标点击、键盘输入)
不同场景的带宽消耗:
| 使用场景 | 下行带宽需求 |
|---|---|
| 纯文字 ERP 界面 | 几十~几百 Kbps |
| 普通 Word 文档 | 几十~几百 Kbps |
| PowerPoint 编辑 | 几百 Kbps ~ 几 Mbps |
| PowerPoint 全屏播放(F5) | 可达 8~10 Mbps! |
| 网页浏览 | 几百 Kbps ~ 几 Mbps |
| 视频播放 | 极高,不建议在终端服务上用 |
3.3 故障排查思路
用户反映"慢"时,先问问题,区分根因:
高延迟链路的特殊问题:
当用户通过高延迟线路(如卫星链路、国际专线)使用终端服务时,快速打字会出现字符延迟——因为每次按键都要等服务器返回屏幕更新,延迟越高越明显。
3.4 用 Wireshark 监控终端服务带宽
在 I/O Graphs 中配置:
- Y 轴:Bits/Tick(比特/时间片)
- 用过滤器分别监控上行和下行流量
正常模式下,下行远大于上行。当下行触顶时,用户开始感受到屏幕卡顿。
四、数据库流量分析
4.1 网络工程师和数据库的关系
用户说:数据库好慢!→ 找网络工程师
|
+-----------+-----------+
| |
真的是网络问题 其实是应用/DB问题
(丢包/延迟/带宽不足) (SQL低效/服务器性能差)
| |
我们来解决 转给DBA处理
我们的任务:证明网络没问题,或者找到网络层面的证据帮 DBA 定位。
4.2 排查前先问清楚
| 问题 | 判断依据 |
|---|---|
| 所有人慢还是个别人慢? | 个别人 → 特定客户端/应用问题 |
| 所有应用慢还是只有这个数据库? | 只有这个 → 数据库或其服务器问题 |
| 远程办公室也慢还是总部也慢? | 都慢 → 不是 WAN 带宽问题 |
| 客户端和服务器之间的链路满了吗? | 是 → 带宽不足 |
4.3 网络延迟对数据库的影响
这是最关键的部分。假设:
- 客户端和数据库服务器的往返延迟(RTT)= 35ms
- 完成一次完整查询需要 371 个数据包来回
则总等待时间约为:
总等待时间≈371×35ms=12,985ms≈13秒\text{总等待时间} \approx 371 \times 35\text{ms} = 12{,}985\text{ms} \approx 13\text{秒}总等待时间≈371×35ms=12,985ms≈13秒
加上偶发的重传和低效操作,用户实际感受可能是 10~15 秒甚至更久。
这说明:网络延迟本身没问题(35ms也不算差),但数据库应用"来回包次数"太多,把延迟放大了几百倍。
4.4 TCP 重传率参考
| 网络类型 | 正常重传率 |
|---|---|
| 内部企业网络 | 0.1%0.1\%0.1% ~ 0.5%0.5\%0.5% |
| 互联网连接 | 高得多,属正常 |
超出内网正常范围则需要先解决网络层问题。
4.5 特殊情况:软件问题被误判为网络问题
有时候 Wireshark 里看到大量重传,但仔细分析会发现:
- 重传只发生在某个特定软件窗口/操作时
- 随后客户端主动重新建立了 TCP 连接
这通常说明是软件 Bug:某个处理流程卡死,导致 TCP 长时间没有响应,触发了超时重传,最终客户端断开重连。
4.6 数据库架构依赖链(重要!)
用户客户端
|
| 看起来这段很正常
v
数据库服务器
|
| 这段才是瓶颈!(但容易被忽视)
v
共享文件服务器(NAS)
(数据库的数据文件存在这里)
必须确认所有依赖关系再开始测试,否则会测错位置。
4.7 解决方案
| 根因 | 解决方案 |
|---|---|
| WAN 延迟高 × 包次数多 | 1. DBA 优化查询减少来回包 2. 改用终端服务/Web访问 |
| 带宽不足 | 升级 WAN 链路 |
| 数据库服务器到文件服务器慢 | 优化存储网络,或将数据库迁移到本地存储 |
| 软件 Bug 导致 TCP 超时 | 联系软件开发商修复 |
五、SNMP 协议分析
5.1 什么是 SNMP?
SNMP(Simple Network Management Protocol,简单网络管理协议)是用来监控和管理网络设备的协议。
打个比方:SNMP 就像是工厂里的"传感器+报警系统"——每台设备(交换机、路由器、服务器)上都有一个小探针,定期把自己的运行状态报告给中央监控室。
5.2 SNMP 三大组件
5.3 SNMP 三个版本对比
| 版本 | 特点 | 安全性 |
|---|---|---|
| SNMPv1 | 最早版本,功能简单 | 无加密,仅社区字符串(明文) |
| SNMPv2c | 性能改进,增加批量查询 | 无加密,仍用社区字符串 |
| SNMPv3 | 当前推荐版本 | 支持认证 + 加密,安全性高 |
5.4 MIB 和 OID 概念
MIB(管理信息库):定义了设备可以被查询的所有信息的"目录"。
OID(对象标识符):MIB 中每个数据项的唯一编号,用数字串表示,像文件路径一样层级展开。
举例:
.1.3.6.1.2.1.2.2.1.16
|___________|___|__|__|
固定前缀 接口表 出字节数
.1.3.6.1.2.1.2.2.1.16.1 → 第1个接口的出字节数(ifOutOctets接口1)
.1.3.6.1.2.1.2.2.1.16.2 → 第2个接口的出字节数(ifOutOctets接口2)
5.5 SNMP 工作流程
5.6 SNMP 故障排查思路
第一步:先问清楚问题性质
问题一:这是新设备还是以前正常工作过的设备?
|
+-- 新设备 → 检查配置
| |-- 社区字符串(Community String)是否填写正确?
| |-- NMS 的 IP 地址是否配置对?
| |-- SNMPv3 的加密参数是否匹配?
| +-- 网络是否互通?(先 ping 一下)
|
+-- 以前正常,现在不行 → 检查变化
|-- 设备自身有没有故障(控制平面/管理平面问题)?
|-- 网络中间是否发生了中断(如 STP 收敛导致短暂断流)?
+-- 是单个设备不报还是多个设备都不报?
注意:SNMP 是低优先级协议
大多数网络设备把 SNMP 处理放在低优先级队列。当设备繁忙时(高 CPU、大量路由计算等),SNMP 响应会被延迟甚至丢弃。这会导致 NMS 误判设备"失联"。
5.7 常见 SNMP 故障类型
| 故障类型 | 现象 | 原因 | 解决方法 |
|---|---|---|---|
| 版本不匹配 | NMS 无响应 | NMS 用 v2c 查,设备只支持 v1 | 统一 SNMP 版本 |
| 社区字符串错误 | NMS 无响应 | 密码填错 | 核对社区字符串 |
| OID 不存在 | 返回 noSuchInstance | 查询了不存在的接口/对象 | 确认设备支持的 OID |
| 网络不通 | 完全无响应 | 防火墙拦截 UDP 161 | 放通 UDP 161 端口 |
| 设备繁忙 | 偶发丢失 | SNMP 被高优先级任务挤占 | 降低轮询频率 |
| STP 收敛 | 短暂丢失 | 链路重新收敛期间流量中断 | 正常现象,等待恢复 |
六、完整 C++ 代码:SNMP OID 解析与 MIB 查询工具
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <sstream>
// SNMP OID 解析工具
// OID(Object Identifier)是由数字和点组成的层次路径
// 例如:.1.3.6.1.2.1.2.2.1.16.1
// |___________________|_|
// 标准MIB前缀 实例索引(第几个接口)
// 将 OID 字符串拆分为数字数组
// 例如 "1.3.6.1.2.1" → {1, 3, 6, 1, 2, 1}
std::vector<int> parseOID(const std::string& oid) {
std::vector<int> parts;
std::stringstream ss(oid);
std::string token;
while (std::getline(ss, token, '.')) {
if (!token.empty()) {
try {
parts.push_back(std::stoi(token));
} catch (...) {
// 忽略非数字部分
}
}
}
return parts;
}
// 将数字数组还原为 OID 字符串
std::string formatOID(const std::vector<int>& parts) {
std::string result;
for (size_t i = 0; i < parts.size(); i++) {
if (i > 0) result += ".";
result += std::to_string(parts[i]);
}
return result;
}
// 建立常见 OID 含义数据库
// key: OID前缀字符串, value: 说明
std::map<std::string, std::string> buildOIDMap() {
std::map<std::string, std::string> m;
// 系统信息(sysDescr等)
m["1.3.6.1.2.1.1.1"] = "sysDescr - 系统描述(设备型号、OS版本等)";
m["1.3.6.1.2.1.1.2"] = "sysObjectID - 设备厂商OID标识";
m["1.3.6.1.2.1.1.3"] = "sysUpTime - 系统运行时间(单位:百分之一秒)";
m["1.3.6.1.2.1.1.4"] = "sysContact - 联系人信息";
m["1.3.6.1.2.1.1.5"] = "sysName - 设备名称(hostname)";
m["1.3.6.1.2.1.1.6"] = "sysLocation - 设备物理位置";
// 接口信息表(ifTable)
m["1.3.6.1.2.1.2.1"] = "ifNumber - 接口数量";
m["1.3.6.1.2.1.2.2.1.1"] = "ifIndex - 接口索引编号";
m["1.3.6.1.2.1.2.2.1.2"] = "ifDescr - 接口描述(如 GigabitEthernet0/1)";
m["1.3.6.1.2.1.2.2.1.3"] = "ifType - 接口类型(以太网=6,串口=22等)";
m["1.3.6.1.2.1.2.2.1.5"] = "ifSpeed - 接口速率(bps)";
m["1.3.6.1.2.1.2.2.1.7"] = "ifAdminStatus - 管理状态(1=up, 2=down)";
m["1.3.6.1.2.1.2.2.1.8"] = "ifOperStatus - 实际运行状态(1=up, 2=down)";
m["1.3.6.1.2.1.2.2.1.10"] = "ifInOctets - 接口接收字节总数(入流量)";
m["1.3.6.1.2.1.2.2.1.16"] = "ifOutOctets - 接口发送字节总数(出流量)";
m["1.3.6.1.2.1.2.2.1.13"] = "ifInDiscards - 接口入方向丢弃包数";
m["1.3.6.1.2.1.2.2.1.14"] = "ifInErrors - 接口入方向错误包数";
m["1.3.6.1.2.1.2.2.1.19"] = "ifOutDiscards - 接口出方向丢弃包数";
m["1.3.6.1.2.1.2.2.1.20"] = "ifOutErrors - 接口出方向错误包数";
// IP 信息
m["1.3.6.1.2.1.4.1"] = "ipForwarding - 是否开启IP转发(路由功能)";
m["1.3.6.1.2.1.4.3"] = "ipInReceives - 收到的IP包总数";
m["1.3.6.1.2.1.4.5"] = "ipInDiscards - 丢弃的IP入包数";
// TCP 统计
m["1.3.6.1.2.1.6.9"] = "tcpCurrEstab - 当前已建立的TCP连接数";
m["1.3.6.1.2.1.6.10"] = "tcpInSegs - 接收的TCP段总数";
m["1.3.6.1.2.1.6.11"] = "tcpOutSegs - 发送的TCP段总数";
m["1.3.6.1.2.1.6.12"] = "tcpRetransSegs - 重传的TCP段总数(重要!过高说明网络有问题)";
// UDP 统计
m["1.3.6.1.2.1.7.1"] = "udpInDatagrams - 接收的UDP数据报总数";
m["1.3.6.1.2.1.7.4"] = "udpOutDatagrams - 发送的UDP数据报总数";
return m;
}
// 查询 OID 含义(支持带实例索引的OID)
// 例如 1.3.6.1.2.1.2.2.1.16.1 → 先精确匹配,再去掉最后一段做前缀匹配
std::string lookupOID(const std::string& oid,
const std::map<std::string, std::string>& oidMap) {
// 精确匹配
auto it = oidMap.find(oid);
if (it != oidMap.end()) {
return it->second;
}
// 去掉最后一个数字(实例索引)再查
size_t lastDot = oid.rfind('.');
if (lastDot != std::string::npos) {
std::string prefix = oid.substr(0, lastDot);
std::string instance = oid.substr(lastDot + 1);
auto it2 = oidMap.find(prefix);
if (it2 != oidMap.end()) {
return it2->second + " [实例索引=" + instance + "]";
}
}
return "(未在常用MIB列表中,可能是厂商私有OID)";
}
// 模拟 SNMP 轮询场景演示
void demonstratePolling() {
std::cout << "\n===== 模拟 SNMP 轮询场景 =====" << std::endl;
struct PollResult {
std::string oid;
std::string value;
bool success;
};
// 模拟 NMS 对设备的轮询结果(假设设备有2个接口)
std::vector<PollResult> results = {
{"1.3.6.1.2.1.1.5.0", "Router-HQ-01", true},
{"1.3.6.1.2.1.1.3.0", "1234500", true},
{"1.3.6.1.2.1.2.2.1.16.1", "98234567890", true},
{"1.3.6.1.2.1.2.2.1.16.2", "12345678", true},
{"1.3.6.1.2.1.2.2.1.16.3", "noSuchInstance", false}, // 第3个接口不存在
{"1.3.6.1.2.1.6.12.0", "1523", true},
};
auto oidMap = buildOIDMap();
std::cout << std::string(70, '-') << std::endl;
std::cout << " OID | 结果 | 含义" << std::endl;
std::cout << std::string(70, '-') << std::endl;
for (const auto& r : results) {
std::string meaning = lookupOID(r.oid, oidMap);
// 截断过长的含义字符串用于显示
std::string shortMeaning = meaning.substr(0, 30);
std::cout << " " << r.oid;
// 对齐
for (int i = r.oid.size(); i < 35; i++) std::cout << " ";
std::cout << "| ";
if (!r.success) {
std::cout << "noSuchInstance ← 错误!" << std::endl;
std::cout << " > 原因:查询了不存在的OID(如设备只有2个接口,却查第3个)"
<< std::endl;
} else {
std::cout << r.value << std::endl;
std::cout << " > " << meaning << std::endl;
}
}
std::cout << std::string(70, '-') << std::endl;
}
int main() {
std::cout << "========================================" << std::endl;
std::cout << " SNMP OID 查询与解析工具" << std::endl;
std::cout << "========================================" << std::endl;
auto oidMap = buildOIDMap();
// 演示查询几个常用OID
std::cout << "\n===== 常用 OID 含义速查 =====" << std::endl;
std::vector<std::string> demoOIDs = {
"1.3.6.1.2.1.1.1",
"1.3.6.1.2.1.2.2.1.16",
"1.3.6.1.2.1.2.2.1.16.1", // 带实例索引
"1.3.6.1.2.1.6.12",
"1.3.6.1.2.1.2.2.1.8",
};
for (const auto& oid : demoOIDs) {
std::string meaning = lookupOID(oid, oidMap);
std::cout << "\n OID: " << oid << std::endl;
std::cout << " => " << meaning << std::endl;
}
// 模拟轮询演示
demonstratePolling();
// 交互查询
std::cout << "\n===== 交互查询模式 =====" << std::endl;
std::cout << "输入 OID 查询含义(输入 exit 退出):" << std::endl;
std::string input;
while (true) {
std::cout << "> ";
std::cin >> input;
if (input == "exit") {
std::cout << "退出程序。" << std::endl;
break;
}
// 去掉开头的点
if (!input.empty() && input[0] == '.') {
input = input.substr(1);
}
std::string result = lookupOID(input, oidMap);
std::cout << " => " << result << std::endl;
}
return 0;
}
编译和运行:
g++ -std=c++17 -o snmp_tool snmp_tool.cpp
./snmp_tool
输出示例(部分):
===== 常用 OID 含义速查 =====
OID: 1.3.6.1.2.1.1.1
=> sysDescr - 系统描述(设备型号、OS版本等)
OID: 1.3.6.1.2.1.2.2.1.16.1
=> ifOutOctets - 接口发送字节总数(出流量) [实例索引=1]
OID: 1.3.6.1.2.1.6.12
=> tcpRetransSegs - 重传的TCP段总数(重要!过高说明网络有问题)
===== 模拟 SNMP 轮询场景 =====
1.3.6.1.2.1.2.2.1.16.3 | noSuchInstance ← 错误!
> 原因:查询了不存在的OID(如设备只有2个接口,却查第3个)
七、综合知识树
企业应用行为分析
|
+-- 识别网络流量
| |-- Protocol Hierarchy(协议层级分布)
| +-- Conversations(对话统计,看谁在和谁通信)
|
+-- 终端服务(RDP / Citrix ICA)
| |-- 特点:传输屏幕变化,流量高度不对称
| | 下行多(屏幕内容)上行少(鼠标键盘)
| |-- 端口:RDP=TCP 3389 ICA=TCP 1494/2598
| |-- 常见问题
| | |-- 带宽打满 → 屏幕卡顿
| | |-- 高延迟 → 打字延迟
| | +-- 服务器内存不足 → 整体慢
| +-- 排查:先问用户"窗口切换慢还是数据处理慢"
|
+-- 数据库流量
| |-- 核心公式:总耗时 ≈ 包次数 × 单次RTT
| |-- 内网重传率正常值:0.1% ~ 0.5%
| |-- 常见问题
| | |-- 延迟×包次数 → 等待时间放大
| | |-- 软件Bug → 假冒TCP问题
| | +-- 依赖链隐藏瓶颈(DB服务器→文件服务器)
| +-- 解决:DBA减少来回包,或改用终端/Web访问
|
+-- SNMP
|-- 三组件:被管设备 + Agent + NMS服务器
|-- 三版本:v1(弱)→ v2c(中)→ v3(强,加密)
|-- 端口:UDP 161(查询)UDP 162(Trap告警)
|-- MIB/OID:设备数据的"目录"和"地址"
+-- 常见故障
|-- 版本不匹配 → 无响应
|-- 社区字符串错误 → 无响应
|-- OID不存在 → noSuchInstance
+-- 网络中断 → 短暂失联(STP收敛等)
第17章:SIP、多媒体与IP电话故障排查 — 从零详细解析
本文从零开始,把"IP电话是怎么打通的"以及"出了问题怎么用Wireshark查"讲得清清楚楚。
一、整体架构:两件事、两套协议
打一个网络电话,需要同时做两件事:
事情一:媒体传输(你说的话/你的视频)
→ 用 RTP 协议传输
事情二:信令控制(拨号、接听、挂断)
→ 用 SIP 协议控制
历史上有两套方案竞争,现在 IETF 方案赢了:
二、IP电话的基本原理
2.1 是什么
IP电话把模拟语音信号转换成IP数据包,通过网络传输。
你说话
|
| 模拟声音
↓
话筒/麦克风(模数转换)
|
| 数字音频
↓
编解码器(Codec,如G.711、G.729)
|
| 压缩后的音频数据
↓
RTP 协议打包
|
| RTP包 → UDP包 → IP包
↓
网络传输
|
↓
对方收到、解包、播放声音
Wireshark 只能抓 IP 部分的包,模拟信号部分抓不到。
2.2 RTP 包结构
RTP 包头的字段含义(从高位到低位):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Synchronization Source (SSRC) identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Contributing source (CSRC) identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 音频/视频数据 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 字段 | 大小 | 作用 |
|---|---|---|
| V(Version) | 2位 | RTP版本号,当前为2 |
| P(Padding) | 1位 | 包尾是否有填充字节 |
| X(Extension) | 1位 | 是否有扩展头 |
| CC(CSRC Count) | 4位 | 贡献源数量 |
| M(Marker) | 1位 | 标记应用事件(如视频帧边界) |
| PT(Payload Type) | 7位 | 负载类型(标识使用哪种编解码器) |
| Sequence Number | 16位 | 序列号,每包加1,用于检测丢包 |
| Timestamp | 32位 | 时间戳,反映采样时刻 |
| SSRC | 32位 | 同步源标识符,唯一标识一路RTP流 |
| CSRC | 32位×CC | 贡献源列表 |
2.3 序列号和时间戳的关系
序列号每包加1,时间戳按采样间隔增加:
包编号: 1 2 3 4 5
序列号: 1 2 3 4 5 ← 每包 +1
时间戳: 1 12 23 34 45 ← 按音频采样率增加
例如:G.711编解码器,采样率8000Hz,每包160个采样点
则时间戳每包增加 160
接收端用序列号判断包的顺序和是否丢包,用时间戳控制播放节奏(消除抖动)。
2.4 RTP 使用的端口范围
RTP 使用 UDP 作为传输层,端口范围为:
16384∼32767 16384 \sim 32767 16384∼32767
RTCP(控制协议)用 RTP 端口号加1:
RTCP 端口=RTP 端口+1 \text{RTCP 端口} = \text{RTP 端口} + 1 RTCP 端口=RTP 端口+1
例如 RTP 用 24950,则 RTCP 用 24951。
三、RTCP — 质量监控协议
3.1 作用
RTCP 与 RTP 配套工作,用于监控端到端通话质量,并把统计信息反馈给发送端,让发送端可以调整传输速率。
3.2 RTCP 报文类型
| 类型编号 | 名称 | 用途 |
|---|---|---|
| 200 | Sender Report(SR) | 发送端汇报:发了多少包、多少字节、时间戳 |
| 201 | Receiver Report(RR) | 接收端汇报:收到多少、丢了多少、抖动多少 |
| 202 | Source Description(SDES) | 描述源的信息(如用户名、邮件等) |
| 203 | BYE | 会话结束,通知对端此SSRC退出 |
| 204 | Application Specific(APP) | 应用自定义报文 |
3.3 RTCP Sender Report 关键字段
在 Wireshark 中看 RTCP 发送者报告时,需要重点关注:
| 字段 | 正常值 | 含义 |
|---|---|---|
| Fraction Lost | 0 或极小值 | 当前周期内丢包比例(0=无丢包) |
| Cumulative Packets Lost | 0 或极小值 | 整个会话累计丢包数 |
| Inter-arrival Jitter | 越小越好 | 包到达时间的抖动量(毫秒) |
这三个值任何一个异常,都说明通话质量有问题。
四、SIP — 信令控制协议
4.1 SIP 是什么
SIP(Session Initiation Protocol,会话发起协议)负责:
- 建立通话(拨号)
- 修改通话(切换编解码器)
- 终止通话(挂断)
SIP 定义了两类角色:
| 角色 | 说明 |
|---|---|
| UA(User Agent,用户代理) | 终端设备,如IP电话、软件客户端、摄像头 |
| SIP 服务器 | 提供注册、定位、转发等服务的网络设备 |
4.2 SDP — 会话描述协议
SIP 消息里的**正文(Body)**用 SDP(Session Description Protocol)格式写,告诉对方:
- 用哪个 UDP 端口接收 RTP 音频
- 用哪个 UDP 端口接收 RTP 视频
- 支持哪些编解码器(G.711、G.729、H.264……)
SDP 的核心作用就是两端"协商":我能发什么、你能收什么,最终选出双方都支持的方案。
五、SIP 完整通话流程
5.1 基本通话建立流程(两端直连)
5.2 经过中间节点的完整流程(实际网络场景)
本章示例有三个节点:
IP电话端 SBC/CUBE 对端
172.18.110.200 → 172.18.110.203 → 172.18.110.206
5.3 SDP 协商过程(Offer/Answer 模型)
第一步:主叫发 SDP Offer(在 INVITE 里)
"我支持 G.711 和 G.729,
请把音频发到我的 UDP 端口 8260"
第二步:被叫发 SDP Answer(在 200 OK 里)
"我也支持 G.711,就用它吧,
请把音频发到我的 UDP 端口 25944"
第三步:ACK 确认
双方开始用 G.711,互相往对方端口发 RTP 包
六、SIP 响应码大全
响应码结构和 HTTP 完全一样,分六大类:
1xx → 进行中(还没完成)
2xx → 成功
3xx → 重定向(去别的地方)
4xx → 客户端错误(请求本身有问题)
5xx → 服务器错误(服务器自己的问题)
6xx → 全局失败(任何服务器都无法处理)
6.1 1xx — 临时/信息性响应
| 代码 | 名称 | 含义(大白话) |
|---|---|---|
| 100 | Trying | “收到了,正在处理” |
| 180 | Ringing | “对方电话在响铃” |
| 181 | Call Forward | “呼叫被转接到别处了” |
| 182 | Queued | “对方暂时没空,排队等待” |
| 183 | Session Progress | “会话正在建立中,有进展” |
6.2 2xx — 成功
| 代码 | 名称 | 含义 |
|---|---|---|
| 200 | OK | “成功!请求已被接受并处理完毕” |
| 202 | Accepted | “已接受,但还没处理完(异步处理)” |
6.3 3xx — 重定向
| 代码 | 名称 | 含义 |
|---|---|---|
| 300 | Multiple Choices | “这个地址有多个目的地,你选一个” |
| 301 | Moved Permanently | “用户已永久搬到新地址了” |
| 302 | Moved Temporarily | “用户临时在另一个地址” |
| 305 | Use Proxy | “必须通过指定的代理访问” |
| 380 | Alternative Service | “本次呼叫失败,但有替代服务可用” |
6.4 4xx — 客户端错误
| 代码 | 名称 | 含义 |
|---|---|---|
| 400 | Bad Request | 请求格式/语法有错误 |
| 401 | Unauthorized | 需要认证,客户端还没提供凭据 |
| 403 | Forbidden | 服务器拒绝,即使认证了也不行 |
| 404 | Not Found | 被叫用户不存在 |
| 405 | Method Not Allowed | 这个请求方法不被允许 |
| 407 | Proxy Auth Required | 需要先向代理服务器认证 |
| 408 | Request Timeout | 服务器等了太久,没收到响应 |
| 480 | Temporarily Unavailable | 对方暂时不在线/不可达 |
| 481 | Call Does Not Exist | 收到一个不认识的会话请求 |
| 482 | Loop Detected | 检测到请求在网络里绕圈 |
| 483 | Too Many Hops | Max-Forwards 已经减到0,包走太多跳了 |
| 486 | Busy Here | 对方忙,不接这个电话 |
| 487 | Request Terminated | 请求被 BYE 或 CANCEL 取消了 |
6.5 5xx — 服务器错误
| 代码 | 名称 | 含义 |
|---|---|---|
| 500 | Server Internal Error | 服务器内部错误(相当于服务器崩了) |
| 501 | Not Implemented | 服务器不支持这个功能 |
| 502 | Bad Gateway | 网关从下游收到了无效响应 |
| 503 | Service Unavailable | 服务器过载或维护中,暂时无法处理 |
| 504 | Server Timeout | 上游服务器没有及时响应 |
| 505 | Version Not Supported | 不支持请求中使用的SIP协议版本 |
| 513 | Message Too Large | 消息体太长,服务器无法处理 |
6.6 6xx — 全局失败
| 代码 | 名称 | 含义 |
|---|---|---|
| 600 | Busy Everywhere | 用户在任何地方都忙,不接 |
| 603 | Decline | 用户明确拒绝接听 |
| 604 | Does Not Exist Anywhere | 该用户在任何地方都不存在 |
| 606 | Not Acceptable | 会话参数(SDP内容)完全无法接受 |
七、视频通话与 RTSP
7.1 SIP 视频通话
视频通话和语音通话的信令完全一样,区别在于 SDP 消息里会同时描述音频流和视频流:
SDP 消息示例(简化):
m=audio 23978 RTP/AVP 0 ← 音频:UDP端口23978,编解码器G.711
a=rtpmap:0 PCMU/8000
m=video 30290 RTP/AVP 96 ← 视频:UDP端口30290
a=rtpmap:96 H264/90000 ← 使用H.264编解码器
流程:
音频 RTP 流 → UDP 端口 23978
视频 RTP 流 → UDP 端口 30290
7.2 RTSP — 实时流媒体协议
RTSP 用于点播/流媒体场景(比如看视频),和 SIP 用于实时双向通话不同。
RTSP 端口:TCP 554(和 HTTP 非常相似)
RTSP 常用命令
| 命令 | 方向 | 作用 |
|---|---|---|
| OPTIONS | C → S 或 S → C | 查询服务器/客户端支持哪些方法 |
| DESCRIBE | C → S | 获取媒体流描述(返回SDP) |
| SETUP | C → S | 建立媒体会话,协商RTP端口 |
| PLAY | C → S | 开始播放 |
| PAUSE | C → S | 暂停播放 |
| TEARDOWN | S → C | 立即关闭会话 |
| RECORD | C → S | 开始录制 |
| REDIRECT | S → C | 重定向到其他服务器 |
RTSP 与 SIP 的关键区别
| 对比点 | SIP | RTSP |
|---|---|---|
| 场景 | 实时双向通话 | 点播/流媒体 |
| 端口协商方式 | SDP 消息 | SETUP 命令 |
| 典型端口 | 动态(5060为信令) | TCP 554 |
| 媒体传输 | RTP(UDP) | RTP(UDP) |
| 连接方式 | UDP | TCP |
RTSP 完整会话流程
RTSP 端口分配方式:
客户端在 SETUP 请求里告诉服务器我的端口:
RTP 音频 → UDP 50960
RTCP 音频 → UDP 50961 (= RTP端口 + 1)
视频流同理,用另一对端口
八、用 Wireshark 分析 RTP 流
8.1 查看所有 RTP 流
菜单路径:
Telephony → RTP → RTP Streams
列出的每条 RTP 流信息:
| 字段 | 含义 |
|---|---|
| Source Address | 发送方 IP |
| Source Port | 发送方 UDP 端口 |
| Destination Address | 接收方 IP |
| Destination Port | 接收方 UDP 端口(范围 16384~32767) |
| SSRC | 同步源标识符(唯一标识这路流) |
| Payload | 编解码器类型(如G.711) |
| 数据包数 / 丢包数 / 抖动 | 质量统计 |
8.2 分析流质量
在 RTP Streams 窗口中:
1. 选中要分析的流
2. 点 "Find Reverse" → 找到反向流(双向通话各有一路)
3. 点 "Prepare Filter" → 过滤这对双向流的所有包
4. 点 "Analyze" → 弹出质量分析窗口
分析窗口显示:
- 正向流/反向流的抖动(Jitter)
- 延迟(Delay)
- 丢包率(Packet Loss)
- 以图形方式展示随时间变化的趋势
8.3 过滤 RTCP 包
如果 RTP 流的端口是 P,则过滤 RTCP:
udp.port == P+1
例如 RTP 用 23978,则:
udp.port == 23979
在 RTCP 包里验证 SSRC 是否与 RTP 流一致(0x252eb528)。
8.4 VoIP 通话回放
Wireshark 可以直接播放捕获到的语音通话:
步骤:
1. Telephony → RTP → RTP Streams
2. 选中音频流 + 反向流(两个方向都选)
3. 点 Analyze(合并双向流)
4. 点 Play Streams → 听到实际对话
注意事项:
- 目前只支持 G.711 编解码器的回放,保存为
.au格式 - G.729 不支持直接回放,需要手动转换
- 视频流不支持回放(只有音频)
九、常见故障排查思路
故障排查速查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| INVITE 发出无响应 | 网络不通,UDP丢包 | ping/traceroute,检查防火墙 |
| 收到 404 | 被叫号码不存在 | 检查号码配置,DNS解析 |
| 收到 486 Busy | 对方正在通话或拒接 | 正常现象 |
| 收到 488 Not Acceptable Here | SDP协商失败,编解码器不兼容 | 检查双方支持的Codec列表 |
| 通话有杂音 | RTP丢包/抖动 | 查RTCP丢包率和Jitter |
| 单通(只能听不能说) | 某方向RTP流缺失 | Wireshark看双向流是否都存在 |
| 通话建立后立即中断 | ACK未到达 | 查SIP ACK是否成功 |
十、协议栈层次总览
应用层 SIP(信令) SDP(会话描述) RTP(媒体) RTCP(质量控制) RTSP(流媒体信令)
↕ ↕ ↕ ↕ ↕
传输层 TCP/UDP (内嵌在SIP) UDP UDP TCP(554)
↕ ↕ ↕
网络层 IP IP
↕
链路层 以太网/Wi-Fi/...
一次完整的视频通话,所有协议同时工作:
时间线:
t=0 SIP INVITE(SDP Offer) → 信令,协商参数
t=1 SIP 200 OK(SDP Answer) → 信令,确认参数
t=2 SIP ACK → 信令,三次握手完成
t=3 RTP 音频流开始(UDP) → 媒体,持续传输
t=3 RTP 视频流开始(UDP) → 媒体,持续传输
t=3 RTCP 报告开始(每5秒) → 质量监控
...
t=n SIP BYE → 信令,挂断
t=n+1 RTP/RTCP 停止 → 媒体停止
十一、关键知识点总结
| 知识点 | 核心要点 |
|---|---|
| RTP | 媒体传输协议,运行在UDP上,每包有序列号+时间戳 |
| RTCP | RTP配套,监控质量,端口=RTP端口+1 |
| SSRC | 每路RTP流的唯一ID,32位随机数 |
| SIP | 信令协议,负责建立/修改/终止会话 |
| SDP | 嵌在SIP消息里,描述RTP端口和编解码器 |
| Offer/Answer | SIP建话的协商模型,INVITE带Offer,200 OK带Answer |
| RTP端口范围 | UDP 16384∼3276716384 \sim 3276716384∼32767 |
| RTSP | 流媒体信令,TCP 554,用SETUP协商端口(不用SDP) |
| Wireshark分析 | Telephony→RTP→RTP Streams,可分析抖动/丢包/延迟 |
| VoIP回放 | 支持G.711,保存为.au,不支持视频和G.729 |
第18-19章:带宽延迟测量 + 网络安全取证 — 从零详细解析
两章合并讲解:第18章教你测量网络性能,第19章教你识别和排查网络攻击。
第18章:带宽与延迟问题排查
一、三个容易混淆的概念
网络性能有三个层面,必须分清楚:
| 概念 | 英文 | 含义 | 例子 |
|---|---|---|---|
| 速率 | Speed | 链路物理上能跑多快(上限) | 千兆网卡 = 1 Gbps |
| 带宽 | Bandwidth | 被允许使用的最大速率 | 运营商限速 10 Mbps |
| 吞吐量 | Throughput | 实际应用层传输了多少数据 | FTP 实际下载速度 8 Mbps |
三者关系:
吞吐量≤带宽≤速率 \text{吞吐量} \leq \text{带宽} \leq \text{速率} 吞吐量≤带宽≤速率
现实中常见的情况:
WAN-Router1
|
| 物理接口:1 Gbps(速率)
|
运营商设备
|
| CAR限速:10 Mbps(带宽)
|
→ 超出10 Mbps的流量直接丢弃
CAR(Committed Access Rate,承诺接入速率) 是运营商在合同里承诺的带宽上限,超出部分会被丢弃或限速。
二、WAN 带宽测量的位置选择
在哪里抓包,测量结果就代表哪个位置的流量。位置不对,数据会不准。
流量整形(Traffic Shaping):企业主动控制发出流量不超过CAR,像一个水桶,超速的包先缓存、慢慢发,不丢包。
流量限速(Traffic Policing/CAR):运营商强制执行,超出的包直接丢弃。
三、用 Wireshark 测量带宽
3.1 查看平均带宽(Statistics → Summary)
输出内容:
- 总包数
- 平均速率(Avg. MBit/sec)
- 平均包大小
可以先用显示过滤器筛选某条流,再看 Summary,得到单条流的带宽。
3.2 用 I/O Graph 看带宽随时间变化
菜单:Statistics → IO Graphs
两个轴的含义:
| 轴 | 配置项 | 含义 |
|---|---|---|
| X 轴 | Tick Interval | 采样间隔(1ms ~ 10min可调) |
| Y 轴 | 单位 | 每个采样间隔内的包数/字节数/比特数 |
常用配置:X轴 = 1秒,Y轴 = Bytes/Tick → 得到 字节/秒 的带宽曲线。
"View as time of day"选项:显示实际时刻(几点几分),方便找流量高峰时段。
可以叠加多条曲线,对比不同流/协议的带宽占用:
带宽图示意(多条曲线):
Mbps
10 | HTTP ████████ ████
8 | ████████ ████
6 | FTP ██████
4 | ██████████
2 |
0 +──────────────────────────► 时间
0s 10s 20s 30s 40s
3.3 找出占用最多带宽的终端
菜单:Statistics → Endpoints
列出所有端点(IP地址)及其发送/接收的包数和字节数,按字节排序即可找到"流量大户"。
3.4 查看具体连接(会话)统计
菜单:Statistics → Conversations
显示每条流的:源IP、目的IP、包数、字节数、持续时间。
四、延迟与抖动的测量
4.1 概念区分
延迟(Delay / RTT):一个包从发出到收到回应的时间,即往返时间(Round Trip Time)。
RTT=T收到ACK−T发出包 \text{RTT} = T_{\text{收到ACK}} - T_{\text{发出包}} RTT=T收到ACK−T发出包
抖动(Jitter):延迟的变化量(波动程度)。
抖动=∣最大延迟−最小延迟∣平均延迟×100% \text{抖动} = \frac{|\text{最大延迟} - \text{最小延迟}|}{\text{平均延迟}} \times 100\% 抖动=平均延迟∣最大延迟−最小延迟∣×100%
举例:平均延迟100ms,最大120ms,最小80ms:
抖动=120−80100×100%=40% \text{抖动} = \frac{120 - 80}{100} \times 100\% = 40\% 抖动=100120−80×100%=40%
帧间延迟(Interframe Delay):相邻两个数据包到达之间的时间差。
| 参数 | 对什么应用影响最大 | 原因 |
|---|---|---|
| 带宽/丢包 | FTP、文件传输 | 大文件传输需要持续稳定的通道 |
| 延迟/抖动 | VoIP、视频通话 | 声音/画面必须实时,延迟超过150ms人耳可感知 |
4.2 用 Wireshark 测量帧间延迟
步骤:
1. 点击某个 TCP/UDP 包
2. 右键 → Follow TCP/UDP Stream
3. 复制过滤字符串(如 tcp.stream eq 5)
4. Statistics → IO Graphs
5. Y轴选 Advanced...
6. 过滤器填入 tcp.stream eq 5
7. 统计方式选 AVG(*)
8. 字段填 frame.time_delta_displayed
9. 图中显示的就是帧间时间差(毫秒)
frame.time_delta_displayed 含义:当前包与上一个显示的包之间的时间差。
4.3 用 RTT 图测量往返延迟
菜单:Statistics → TCP Stream Graph → Round Trip Time Graph
图中显示每个 TCP 包的 ACK 往返时间,可直观看出延迟是否稳定。
4.4 Layer 4 延迟过滤
tcp.analysis.ack_rtt
此过滤器显示每个包被确认所花的时间,专门用于测量 TCP 层的延迟。
4.5 延迟数值参考
| 延迟范围 | 评价 |
|---|---|
| < 10ms | 优秀,局域网正常范围 |
| 10ms ~ 100ms | 良好,WAN链路可接受 |
| 100ms ~ 300ms | 较差,VoIP质量明显下降 |
| > 300ms | 很差,通话中断 |
| 秒级 | 严重异常,必须立即排查 |
五、网络瓶颈排查流程
常见瓶颈场景速查
| 现象 | 可能原因 | 排查工具 |
|---|---|---|
| 所有用户都慢 | WAN带宽打满 | IO Graph |
| 特定时段慢 | 带宽高峰 | IO Graph + Time of Day |
| DNS解析慢 | DNS服务器过载 | DNS过滤 + IO Graph |
| 链路延迟秒级 | 物理故障(光纤/端口) | RTT Graph |
| 某台服务器慢 | 并发连接过多 | Endpoints统计 |
| 低优先级流量抢占 | 缺少QoS | Conversations分析流量类型 |
QoS 的作用
默认队列机制是 FIFO(先进先出)——谁先到谁先发,不分优先级。
FIFO 问题(没有QoS):
队列:[视频流][垃圾广告][FTP][网页][P2P][P2P][P2P][P2P]...
↑
P2P占满带宽,视频卡顿
QoS 优先级队列(部署后):
高优先级:[视频流][VoIP] ← 最先发送
中优先级:[网页][FTP]
低优先级:[P2P][广告] ← 有剩余带宽才发
第19章:网络安全与取证
六、安全分析的基本思路
网络取证和刑事侦查一样——先建立"正常基线",再找"异常"。
在开始分析之前,必须了解网络的"正常面貌":
需要掌握的背景信息:
- 所有服务器的 IP 地址和网段
- 所有路由器、交换机的 IP 和拓扑
- 防火墙、IDS/IPS、WAF 等安全设备的位置
- 网络上运行的应用及其端口号
Wireshark 的部署位置
互联网
|
[1] | [2]
─────防火墙─────
|
[3] [4]路由器
|
核心交换
/ \
[7]远程办公室 [8]可疑 PC/服务器
[5/6]路由器
| 编号 | 位置 | 适合场景 |
|---|---|---|
| 1 | 防火墙外(互联网侧) | 怀疑外部攻击穿过防火墙前 |
| 2 | 防火墙内(内网侧) | 怀疑攻击已穿过防火墙 |
| 3/4 | WAN路由器前后 | 怀疑远程办公室有问题 |
| 7 | 远程分支内部 | 隔离具体分支的攻击源 |
| 8 | 可疑PC/服务器端口 | 精准抓取特定设备流量 |
七、正常流量 vs 可疑流量对照
7.1 IP 地址
| 类型 | 特征 |
|---|---|
| 正常 | 来自已知的内部 IP 段(如 192.168.x.x) |
| 可疑 | 来自不认识的地址,或目的是不该被访问的地址 |
7.2 端口号
| 类型 | 特征 |
|---|---|
| 正常 | 80(HTTP)、443(HTTPS)、53(DNS)、25(SMTP)、21(FTP)、3389(RDP)等标准端口 |
| 可疑 | Web服务器上出现RDP流量;内网向外部随机高端口发包 |
7.3 TCP 标志组合
| 标志组合 | 含义 | 正常/可疑 |
|---|---|---|
| SYN | 建立连接请求 | 正常 |
| SYN-ACK | 连接确认 | 正常 |
| ACK | 数据确认 | 正常 |
| FIN / FIN-ACK | 正常断开连接 | 正常 |
| RST | 快速重置/拒绝 | 正常(少量) |
| 大量 SYN 无回应 | 端口扫描 / DoS | 可疑 |
| RST+FIN 同时 | 非标准组合 | 可疑 |
| URG 标志 | 极少正常使用 | 可疑 |
| 所有标志为0 | Null Scan | 攻击 |
| FIN+PSH+URG | Xmas Scan | 攻击 |
7.4 流量模式
| 类型 | 特征 |
|---|---|
| 正常 | 流量有起伏,用户操作驱动(开文件大、关文件小) |
| 可疑 | 固定带宽的持续流量(可能是远程控制、挖矿或数据泄露) |
固定带宽模式示意:
Kbps
150 |████████████████████████████ ← 持续固定,不正常
100 |
50 |
0 +──────────────────────────► 时间
对比正常流量:
150 | ██ ██
100 | ████ ████ ██
50 | ██████ ███████████
0 +──────────────────────────► 时间(有起伏,正常)
7.5 广播流量
| 类型 | 特征 |
|---|---|
| 正常 | NetBIOS、少量ARP、少量DHCP,几秒一次 |
| 可疑 | 每秒数百上千个广播包,来自同一设备 |
7.6 DNS 流量
| 类型 | 特征 |
|---|---|
| 正常 | 每个查询都有对应的响应,每客户端每秒几十个以内 |
| 可疑 | 大量无对应查询的响应;或大量查询无响应(域名爆破) |
八、常见网络攻击类型
九、ARP 与 MAC 层攻击
9.1 ARP 工作原理(正常情况)
主机A 想和 192.168.1.10 通信:
主机A → 广播:谁是 192.168.1.10?请告诉我你的MAC
主机B → 单播:我是 192.168.1.10,我的MAC是 AA:BB:CC:DD:EE:FF
主机A 记录:IP 192.168.1.10 → MAC AA:BB:CC:DD:EE:FF
9.2 ARP 扫描特征
ARP 扫描是攻击者探测整个网段存活主机的手段:
ARP扫描模式(在Wireshark里看到的):
时间 源IP 目的IP 内容
0.001 192.168.1.5 192.168.1.1 Who has 192.168.1.1?
0.002 192.168.1.5 192.168.1.2 Who has 192.168.1.2?
0.003 192.168.1.5 192.168.1.3 Who has 192.168.1.3?
0.004 192.168.1.5 192.168.1.4 Who has 192.168.1.4?
...
← 顺序递增,速度极快 = 扫描
正常 ARP:随机目标,间隔较长。
扫描 ARP:顺序目标,间隔极短(毫秒级)。
9.3 ARP 欺骗 / 中间人攻击
正常情况:
主机A ──────────────────────────► 服务器
192.168.1.1 → MAC_Server
ARP 欺骗后:
主机A ──► 攻击者(伪装成服务器)──► 服务器
192.168.1.1 → MAC_Attacker
攻击者告诉主机A:“我就是服务器”,同时告诉服务器:“我就是主机A”,流量全部经过攻击者中转。
Wireshark 检测方法:看到同一个 MAC 地址对应两个不同 IP,或同一个 IP 地址对应两个不同 MAC,就要警惕。
十、扫描攻击详解
10.1 扫描的一般步骤
10.2 ICMP 扫描(Ping 扫描)
正常 ping:少量,有目的性。
扫描 ping:大量,目标递增,时间间隔极短。
ICMP 扫描模式示意:
目标IP:
192.168.110.1 ← ICMP Request
192.168.110.2 ← ICMP Request
192.168.110.3 ← ICMP Request
192.168.110.4 ← ICMP Request(无回应,说明不在线)
192.168.110.5 ← ICMP Request + Reply(在线!→ 进入下一步扫描)
...
蠕虫病毒 ICMP 扫描的传播机制:
感染主机1
→ 扫描整个网段
→ 找到主机2
→ 感染主机2
→ 主机2也开始扫描
→ 找到主机3
→ ...
→ 几分钟后,所有主机都在扫描
→ 网络带宽被 ICMP 包打满
10.3 TCP SYN 扫描
攻击者向多个端口发 SYN,根据响应判断端口状态:
端口状态判断:
攻击者 → SYN → 目标:80
目标 → SYN-ACK → 攻击者 ← 端口开放!(有服务在监听)
攻击者 → SYN → 目标:8080
目标 → RST → 攻击者 ← 端口关闭(有防火墙或无服务)
攻击者 → SYN → 目标:9999
(无响应) ← 被防火墙过滤/主机不在线
TCP SYN 扫描的危害:
半开连接(Half-Open Connection):
攻击者发 SYN → 服务器回 SYN-ACK → 攻击者不回 ACK
服务器维护着大量"等待ACK"的半开连接,消耗内存和CPU
→ 合法用户无法连接 → 变成 DoS 攻击
十一、DoS 与 DDoS 攻击
11.1 区别
| 类型 | 攻击源 | 特征 |
|---|---|---|
| DoS(拒绝服务) | 单一来源 | 一台机器发起大量请求 |
| DDoS(分布式拒绝服务) | 多个来源 | 数百上千台被控机器同时攻击 |
11.2 典型 DDoS 特征(Wireshark 中)
DoS 特征示意:
时间(μs) 源IP 目的IP 标志
0 192.168.1.1 94.23.71.12 SYN
11 192.168.1.2 94.23.71.12 SYN
22 192.168.1.3 94.23.71.12 SYN
33 192.168.1.4 94.23.71.12 SYN
...
← 源IP递增(伪造) 目的IP固定 时间间隔仅11微秒
关键线索:所有不同源IP竟然来自同一个MAC地址!
→ 说明是同一台机器在伪造源IP发包(IP spoofing)
IP 欺骗(IP Spoofing):攻击者伪造源IP地址,让服务器无法追踪真实攻击者,同时把回包发向被伪造的无辜IP。
11.3 难以识别的 DoS 伪装
| 伪装形式 | 说明 |
|---|---|
| Ping Flood | 大量ICMP包,可能伪装成网管软件探测 |
| HTTP GET Flood | 大量正常HTTP请求,每个单独看都合法 |
| SNMP GET Flood | 伪装成网络监控流量 |
识别关键:看数量和来源,而不只是看类型。
十二、智能 TCP 攻击
12.1 ACK 扫描
大量 ACK 包发往多个端口,目的是打断已有的 TCP 连接:
攻击者 → ACK(无对应SYN) → 目标:80
目标 → RST(连接不存在)→ 攻击者 ← 连接被重置
大量ACK → 大量RST → 所有连接被终止
12.2 异常标志组合
| 攻击名称 | 标志组合 | 目的 |
|---|---|---|
| Xmas Scan | FIN + PSH + URG | 让目标操作系统困惑,泄露端口状态 |
| Null Scan | 所有标志=0 | 绕过某些防火墙规则检测 |
| FIN Scan | 只有FIN | 绕过只检查SYN的防火墙 |
| FIN-ACK Flood | FIN + ACK | 大量发送,关闭连接或泛洪网络 |
Xmas Scan 的名字来源:像圣诞树一样"所有灯都亮了"(所有异常标志都置1)。
正常TCP标志:只有其中一两个为1
SYN=1, ACK=0, FIN=0, PSH=0, URG=0, RST=0
Xmas Scan:
SYN=0, ACK=0, FIN=1, PSH=1, URG=1, RST=0 ← 三个同时亮
Null Scan:
SYN=0, ACK=0, FIN=0, PSH=0, URG=0, RST=0 ← 全灭
十三、暴力破解攻击
13.1 什么是暴力破解
暴力破解是"反复试"——枚举所有可能的用户名、密码、文件名,直到找到一个有效的。
13.2 DNS 暴力破解
攻击者枚举子域名,探测组织的服务器资产:
DNS 暴力破解模式(Wireshark中):
查询:dns.icomm.co → 有回应(服务器存在)
查询:mail.icomm.co → 无回应
查询:intranet.icomm.co → 无回应
查询:vpn.icomm.co → 无回应
查询:admin.icomm.co → 无回应
查询:ftp.icomm.co → 有回应(发现FTP服务器!)
...
特征:大量查询,大部分无回应,来源IP固定
Wireshark 过滤器:dns → 看哪些查询没有对应响应。
13.3 HTTP 暴力破解
攻击者枚举Web服务器上的目录和文件:
HTTP 暴力破解模式:
GET /admin/ → 404 Not Found
GET /backup/ → 404 Not Found
GET /config.php → 404 Not Found
GET /wp-admin/ → 200 OK ← 找到了!
GET /phpMyAdmin/ → 200 OK ← 又找到了!
特征:大量 4xx 错误码,来自同一IP
Wireshark 过滤器:
http.response.code >= 400
也可以在 Statistics → HTTP → Packet Counter 看错误码统计。
找常见扫描工具特征:Edit → Find Packet → "nmap" 在 Packet Bytes 里搜索。
13.4 FTP 密码暴力破解
攻击者尝试登录 FTP 的流程:
第一步:尝试匿名登录
攻击者 → USER anonymous
服务器 → 230 Login successful(匿名允许登录)
第二步:尝试特权账户
攻击者 → USER root
服务器 → 530 Login incorrect(拒绝)
攻击者 → USER admin
服务器 → 530 Login incorrect(拒绝)
攻击者 → USER administrator
服务器 → 530 Login incorrect(拒绝)
...最终服务器发送 TCP Zero-Window 限制连接
TCP Zero-Window:服务器告诉攻击者"我的接收缓冲区已满,停止发送",是服务器的一种自保机制。
十四、安全防护体系
| 安全组件 | 作用 | 工作层次 |
|---|---|---|
| 防火墙 Firewall | 基于规则过滤流量,阻止未授权访问 | L3/L4 |
| NAC 网络访问控制 | 只允许授权设备连接网络(未授权的MAC被拒绝) | L2 |
| IDS(检测系统) | 发现入侵模式,告警 | L3-L7 |
| IPS(防御系统) | 发现入侵模式,自动阻断 | L3-L7 |
| WAF 应用防火墙 | 深度检测HTTP/HTTPS,防止SQL注入、XSS等 | L7 |
| 邮件过滤 / Web过滤 | 过滤恶意邮件和网站内容 | L7 |
IDS/IPS 的两种工作方式:
- 基于NetFlow/Jflow:分析流量统计,发现大流量异常(DDoS等)
- 基于内容分析:深度包检测,识别攻击特征字符串
十五、综合排查思路总图
十六、关键知识点总结
| 知识点 | 核心要点 |
|---|---|
| 速率/带宽/吞吐量 | 三者关系:吞吐量 ≤ 带宽 ≤ 速率 |
| CAR | 运营商承诺速率,超出丢弃 |
| 流量整形 | 企业主动控速,超量缓存不丢包 |
| IO Graph | 带宽随时间变化,可多流对比 |
| 延迟 RTT | 一去一回的总时间 |
| 抖动 Jitter | 延迟的变化幅度,对VoIP影响大 |
| frame.time_delta_displayed | Wireshark测帧间延迟的字段 |
| tcp.analysis.ack_rtt | 测TCP层ACK往返时间 |
| ARP扫描 | 顺序递增目标、毫秒级间隔 |
| ICMP扫描 | 大量ping,目标递增,蠕虫传播方式 |
| TCP SYN扫描 | 大量SYN,根据回应判断端口状态 |
| IP欺骗 | 不同源IP来自同一MAC → 伪造 |
| Xmas Scan | FIN+PSH+URG同时置1 |
| Null Scan | 所有标志为0 |
| 暴力破解特征 | 大量请求大部分失败,来源IP固定 |
| HTTP错误码过滤 | http.response.code >= 400 |
| QoS | 优先级队列,保障关键业务带宽 |
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)