在网络运维和调试场景中,能直观看到服务器上的TCP连接状态,还能按需重置异常连接,是排查网络问题的重要能力。本文要聊的就是一款基于ncurses界面的工具------它能实时展示网卡捕获的TCP连接,还能通过注入RST数据包主动重置连接,我们从功能含义、实现原理、核心知识点和设计思路等方面,把这个工具讲明白。
一、工具核心功能:大白话讲清楚
简单来说,这个工具就做两件核心事:
- 可视化展示TCP连接:通过ncurses库打造的终端交互界面,实时显示指定网卡(或默认第一个可用网卡)上的所有TCP连接;哪怕是工具启动前就已建立的连接,也能检测到并标记为"旧连接"(如果不想显示旧连接,也能通过参数关闭这个功能)。
- 主动重置TCP连接:检测到异常或不需要的TCP连接时,工具能往连接的数据包流里注入RST数据包,强制断开这个TCP连接。
额外还有些实用的辅助功能:比如自定义界面的背景、边框、文字颜色;指定日志文件记录状态;关闭域名解析加快显示速度;关闭网卡混杂模式;通过参数控制输出详细程度等。
二、核心实现原理:从捕获到重置的全流程
1. 先搞懂:TCP连接怎么捕获?
要看到TCP连接,核心是抓包和解析,流程如下:
- 网卡数据捕获:工具会让指定网卡进入抓包模式(默认开启混杂模式,能捕获更多数据包,也可通过参数关闭),借助libpcap这类抓包库,抓取网卡上流经的所有网络数据包。
- TCP数据包解析 :从抓取的数据包里筛选出TCP协议的包,解析包的头部信息------比如源IP、目的IP、源端口、目的端口、TCP标志位(SYN、ACK、RST等)。
- 对于工具启动后新建立的连接:能捕获到客户端发的SYN包,从而完整识别连接的发起方和接收方;
- 对于工具启动前已存在的连接:因为没抓到初始SYN包,只能识别连接的IP和端口,标记为"旧连接"。
- 连接状态维护:把解析出的有效TCP连接信息(IP、端口、连接状态、是否旧连接等)存入内存中的数据结构(比如哈希表、链表),方便后续展示和操作。
2. 再搞懂:ncurses怎么把连接显示出来?
ncurses是Linux下的终端界面开发库,能让命令行工具拥有交互界面,核心步骤:
- 界面初始化:调用ncurses的初始化函数,创建终端界面窗口,设置界面的基本属性(比如背景色、边框色、文字色,对应工具的-b/-B/-f参数)。
- 连接信息渲染:把内存中维护的TCP连接列表,按一定格式(比如每行显示一个连接的IP、端口、状态)绘制到终端窗口中;还会定时刷新界面,保证显示的是最新的连接状态。
- 交互处理:ncurses支持键盘输入,工具可以监听用户的按键操作(比如选中某个连接、执行重置操作),实现"可视化操作"。
3. 最后搞懂:怎么注入RST包重置连接?
TCP连接的重置核心是发送RST标志位的数据包,流程如下:
- 构造RST数据包:根据要重置的TCP连接信息(源IP、目的IP、源端口、目的端口),构造符合TCP协议规范的RST数据包------要正确填写IP头部、TCP头部,把TCP标志位的RST位置1,同时保证序列号、确认号等字段符合TCP协议的交互规则(否则目标主机不会识别这个RST包)。
- 发送RST数据包:借助原始套接字(raw socket)把构造好的RST包发送到网络中;这个包会被发送到目标连接的两端,两端收到RST包后,会立即断开该TCP连接。
4. 流程原理图
c
...
void
usage(char *pname)
{
printf("Usage: %s [Option(s)]\n", pname);
printf("\n Options:\n");
printf(" -b color - Background color\n");
printf(" -B color - Border color\n");
printf(" -f color - Foreground color\n");
printf(" -h - This help\n");
printf(" -i iface - Network interface\n");
printf(" -l file - Log status information to file\n");
printf(" -n - Do not attempt to resolve hostnames\n");
printf(" -p - Do not put the interface in promiscuous mode\n");
printf(" -s - Require the initial SYN packet to display a connection\n");
printf(" -v - Verbose output, repeat to increase\n");
printf(" -V - Print \"%s\" and exit\n", VERSION);
printf("\n");
}
int
get_color(char *str)
{
if (!strcmp(str, "black"))
return(COLOR_BLACK);
else if (!strcmp(str, "red"))
return(COLOR_RED);
else if (!strcmp(str, "green"))
return(COLOR_GREEN);
else if (!strcmp(str, "yellow"))
return(COLOR_YELLOW);
else if (!strcmp(str, "blue"))
return(COLOR_BLUE);
else if (!strcmp(str, "magenta"))
return(COLOR_MAGENTA);
else if (!strcmp(str, "cyan"))
return(COLOR_CYAN);
else if (!strcmp(str, "white"))
return(COLOR_WHITE);
return(-1);
}
int
main(int argc, char *argv[])
{
...
while ( (flag = getopt(argc, argv, "f:b:B:l:i:nvVhps")) != -1) {
switch(flag) {
case 'b': opt.bgc = get_color(optarg); break;
case 'B': opt.boc = get_color(optarg); break;
case 'f': opt.fgc = get_color(optarg); break;
case 'l':
if (log_open(optarg) < 0)
exit(EXIT_FAILURE);
opt.logfile = optarg;
break;
case 's':
opt.grab = 0;
break;
case 'h':
usage(argv[0]);
exit(EXIT_SUCCESS);
break;
case 'i':
if (opt.iface != NULL)
free(opt.iface);
opt.iface = (u_char *)strdup(optarg);
break;
case 'n':
opt.resolve = 0;
break;
case 'p':
opt.promisc = 0;
break;
case 'v':
opt.verbose++;
break;
case 'V':
printf("%s\n", VERSION);
exit(EXIT_SUCCESS);
break;
default:
exit(EXIT_FAILURE);
}
}
if (opt.bgc < 0 || opt.boc < 0 || opt.fgc < 0)
opt.usec = 0;
if (opt.verbose > 5)
opt.verbose = 5;
if ( (opt.sock_raw = socket(PF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
perror("Error opening raw socket");
exit(EXIT_FAILURE);
}
if ( (cap = cap_open(opt.iface, opt.promisc)) == NULL)
exit(EXIT_FAILURE);
if (cap_setfilter(cap, "tcp") < 0)
exit(EXIT_FAILURE);
seteuid(getuid());
setuid(getuid());
pthread_mutex_init(&writestat, NULL);
pthread_mutex_init(&conn_tree, NULL);
if (initscreen() < 0) {
fprintf(stderr, "** Error: Failed to initialize screen\n");
exit(EXIT_FAILURE);
}
drawscreen();
writestatus(0, "Opened interface %s in %spromiscuous mode",
opt.iface, (opt.promisc == 1) ? "" : "non-");
if (opt.logfile)
writestatus(0, "Opened logfile %s", opt.logfile);
writestatus(0, "Verbose level set to %d", opt.verbose);
if (pthread_create(&sniff_thread, NULL, sniff, cap) != 0) {
endwin();
fprintf(stderr, "Error: Failed to create sniff thread\n");
exit(EXIT_FAILURE);
}
atexit(cleanup);
iact();
endwin();
exit(EXIT_SUCCESS);
}
If you need the complete source code, please add the WeChat number (c17865354792)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 网卡抓包 │ │ TCP包解析 │ │ 维护连接列表 │ │ ncurses显示 │
│ (libpcap) │───>│ (解析IP/端口/│───>│ (哈希表/链表)│───>│ (终端界面) │
└─────────────┘ │ TCP标志位) │ └─────────────┘ └──────┬──────┘
└─────────────┘ │
│
用户操作(选中连接重置) │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ 构造RST包 │<───│ 获取选中连接 │<───│ 监听按键输入 │<───────────┘
│ (raw socket)│ │ 信息 │ │ (ncurses) │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ 发送RST包 │
│ 重置TCP连接 │
└─────────────┘
三、涉及的核心领域知识点
1. 网络协议层:TCP协议基础
- TCP连接的建立(三次握手:SYN→SYN+ACK→ACK)、断开(四次挥手)和重置(RST包的作用)是核心;
- TCP头部结构:源/目的端口、序列号、确认号、标志位(SYN/ACK/RST/FIN等)、窗口大小等字段的含义,是解析和构造数据包的基础;
- 网络字节序(大端序):编程时要注意主机字节序和网络字节序的转换(比如htons、htonl函数)。
2. 抓包与发包技术
- libpcap库:Linux下最常用的抓包库,能实现网卡数据包的捕获、过滤;
- 原始套接字(Raw Socket):普通套接字只能收发应用层数据,原始套接字能直接操作IP层、TCP/UDP层的数据包,是构造和发送RST包的关键。
3. 终端界面开发:ncurses库
- 终端窗口管理:创建窗口、子窗口、边框绘制;
- 字符属性设置:颜色(前景色、背景色)、高亮等;
- 输入处理:非阻塞式键盘输入监听,实现交互操作;
- 界面刷新:定时更新界面内容,保证数据实时性。
4. 系统编程基础
- 进程/线程:抓包是持续的过程,通常会开独立线程抓包,避免阻塞界面渲染;
- 内存管理:维护连接列表时的内存申请/释放,避免内存泄漏;
- 命令行参数解析:处理工具的各种参数(-i/-h/-s等),比如用getopt函数解析参数。
四、工具的设计思路:从需求到落地
1. 核心设计目标
- 易用性:不用记复杂的命令行参数组合,通过可视化界面就能看连接、操作重置;
- 实时性:能快速捕获并展示最新的TCP连接状态;
- 灵活性:支持自定义网卡、界面样式、日志、解析规则(比如是否显示旧连接);
- 有效性:注入的RST包能可靠重置目标TCP连接。
2. 模块化设计思路
把工具拆成几个独立模块,降低耦合:
- 抓包解析模块:负责网卡抓包、TCP包解析、连接状态维护,对外提供"获取最新连接列表"的接口;
- 界面展示模块:基于ncurses,负责界面绘制、刷新、用户输入监听,调用抓包模块的接口获取数据;
- 连接操作模块:负责构造RST包、发送数据包,接收界面模块的"重置连接"指令,执行具体的重置操作;
- 配置解析模块:解析命令行参数,把参数传递给其他模块(比如指定网卡给抓包模块,指定颜色给界面模块)。
3. 性能与兼容性考量
- 抓包过滤:只抓取TCP包,减少不必要的数据包解析,提升性能;
- 混杂模式开关:默认开启能抓更多包,也支持关闭,适配不同网络环境;
- 域名解析开关:关闭域名解析(-n参数)能避免DNS查询的耗时,加快连接显示速度;
- 跨网卡适配:支持指定任意网卡(-i参数),适配多网卡服务器环境。
五、测试方法+实战案例
测试准备
环境要求 :Linux系统(CentOS/Ubuntu/Debian)、安装libpcap-dev、ncurses-dev、gcc、make、netcat(测试连接用)。
测试1:-i 指定监控网卡(核心必测)
测试命令
bash
sudo ./tcpview -i eth0
(eth0 换成你的网卡:ens33/enp5s0等,用 ip addr 查看)
预期结果
工具只监控指定网卡的TCP连接,界面正常显示。
测试2:-n 不解析主机名(加速显示)
测试命令
bash
sudo ./tcpview -i eth0 -n
预期结果
- 不进行DNS域名解析;
- 界面只显示IP,不显示主机名,显示速度更快。
测试3:-s 仅显示新连接(过滤旧连接)
测试步骤
- 先建立一个TCP连接(nc 127.0.0.1 8888)
- 启动工具加
-s参数
bash
sudo ./tcpview -i eth0 -s
预期结果
工具启动前的旧连接完全不显示,只捕获带SYN包的新连接。
测试4:-p 不开启混杂模式
测试命令
bash
sudo ./tcpview -i eth0 -p
预期结果
网卡不进入混杂模式,只捕获与本机相关的数据包,不监听全网流量。
测试5:-l 日志记录到文件
测试命令
bash
sudo ./tcpview -i eth0 -l log.txt
预期结果
- 工具运行状态自动写入
log.txt; - 退出后可查看文件内容。
测试6:-v 详细输出(可叠加)
测试命令
bash
sudo ./tcpview -i eth0 -v
sudo ./tcpview -i eth0 -vv
预期结果
-v 越多,界面/终端输出调试信息、抓包详情越详细。
测试7:颜色配置(-b 背景 / -B 边框 / -f 前景)
测试命令
bash
sudo ./tcpview -i eth0 -b black -B blue -f white
参数说明:
-b:界面背景色-B:窗口边框色-f:文字颜色
预期结果
界面颜色按设置生效, ncurses 窗口正常渲染无乱码。
测试8:组合参数
bash
sudo ./tcpview -i eth0 -n -p -s -l status.log -vv -b black -B green -f yellow
功能全开:
- 指定网卡
- 不解析域名
- 不开启混杂模式
- 仅显示新连接
- 记录日志
- 超详细输出
- 自定义绿边框、黑底黄字
五、核心功能测试:重置TCP连接
测试步骤
- 开两个终端:
bash
# 终端1:服务端
nc -l 9999
# 终端2:客户端
nc 127.0.0.1 9999
- 启动工具:
bash
sudo ./tcpview -i lo -n
- 在界面中选中这条连接
- 执行重置操作
预期结果
nc 连接立即断开,连接被RST包强制重置,工具界面刷新删除该连接。
总结
这款工具本质是"网络抓包+协议解析+终端可视化+数据包注入"的综合应用,核心是把底层的TCP协议操作、抓包发包技术,通过ncurses封装成友好的可视化界面,让不懂底层命令的运维人员也能轻松查看和重置TCP连接。
从技术角度看,它串联了网络协议、系统编程、终端界面开发三大核心领域:理解TCP协议是"能解析、能重置"的基础,抓包/发包技术是"能获取、能操作"的手段,ncurses是"能可视化、能交互"的载体。这类工具的设计思路,也适用于其他网络调试工具------把复杂的底层操作封装成简单的交互界面,兼顾功能性和易用性。
Welcome to follow WeChat official account【程序猿编码】