C语言NIDS实战:从PCAP解析到告警的完整实现

发布时间:2026/9/28 11:58:00

C语言NIDS实战:从PCAP解析到告警的完整实现 简介这是一份面向高校计算机、网络安全相关专业学生的课程设计与期末大作业参考项目主题为基于PCAP的网络入侵检测系统采用C语言实现。项目已通过导师指导并获得97分高分评价下载后无需修改即可直接运行适合作为课程设计、期末大作业或网络编程与安全方向的实践素材。压缩包共17个文件约891KB包含6个C源文件、4个头文件、2个Makefile、1个Shell脚本、1份PDF报告、1个Markdown说明及Python辅助脚本等覆盖抓包嗅探、线程池调度、流量分析与告警等核心模块目录结构清晰便于按模块阅读与二次开发。目前已有142人学习下载。读者可获得完整可运行的源码工程、配套使用说明与课程报告PDF以及Makefile与测试脚本帮助快速理解PCAP抓包、入侵检测流程与多线程分析设计并对照报告梳理实现思路与排错方法。1. 从一份 PCAP 到告警这套 C 语言 NIDS 到底在做什么手里攥着一份几百 MB 的 PCAPWireshark 里翻得眼花却还是说不清刚才那波流量里到底有没有人扫端口、有没有人往内网打 webshell——这大概是很多做安全运维或课程设计的人共同的痛点。基于 PCAP 的网络入侵检测系统本质就是把这件「人肉翻包」的活交给程序读离线抓包文件或监听网卡按规则匹配流量特征命中就落一条告警。用 C 语言实现是因为它贴着 libpcap 这层抓包库最近零依赖、跑得快、内存自己管特别适合嵌入式网关、教学演示和需要极致性能的检测节点。这套「源码使用说明报告」的组合面向的是想真正搞懂 NIDS 内部机理的人不是调个 Suricata 规则就完事而是自己写解析器、自己定规则、自己算告警。读完你能拿到一条从 PCAP 解析到规则匹配再到告警输出的完整可复现路径也能看清哪些地方最容易翻车。2. 拆开一个包libpcap 抓包与协议解析的落地骨架2.1 为什么选 libpcap 而不是自己写 raw socket很多人第一反应是用AF_PACKET或SOCK_RAW直接抓觉得这样「更底层更可控」。但真写起来你会发现链路层类型判断、混杂模式设置、抓包过滤表达式编译、跨 Linux/BSD/macOS 的兼容全是体力活。libpcap 把这些都封装好了pcap_open_offline读文件、pcap_open_live抓网卡、pcap_compilepcap_setfilter下 BPF 过滤一套 API 通吃。对 NIDS 来说BPF 过滤尤其关键——你可以在内核层就把非目标流量丢掉只把 TCP/UDP 送进用户态解析性能差距是数量级的。常见做法是离线分析用pcap_open_offline实时检测用pcap_open_live配pcap_setnonblock避免阻塞主循环。2.2 从以太网帧到 TCP 载荷的解析链路一个包进来解析顺序是固定的以太网头14 字节注意可能有 VLAN 标签要偏移 4 字节→ 判断 EtherType 是不是 0x0800IPv4→ IP 头看 IHL 字段算实际长度别写死 20→ 判断协议号是不是 6TCP或 17UDP→ TCP 头看 data offset 算载荷偏移。每一步都要做长度校验否则畸形包直接让你段错误。下面是最小可跑的解析骨架#include pcap.h #include netinet/ip.h #include netinet/tcp.h #include netinet/ether.h void packet_handler(u_char *args, const struct pcap_pkthdr *hdr, const u_char *pkt) { // 以太网头固定 14 字节 if (hdr-caplen 14) return; const struct ether_header *eth (struct ether_header *)pkt; uint16_t eth_type ntohs(eth-ether_type); uint32_t offset 14; // 处理 VLAN 标签802.1Q偏移再加 4 if (eth_type 0x8100) { if (hdr-caplen 18) return; eth_type ntohs(*(uint16_t *)(pkt 16)); offset 4; } if (eth_type ! 0x0800) return; // 只处理 IPv4 if (hdr-caplen offset 20) return; const struct ip *iph (const struct ip *)(pkt offset); uint32_t ip_hlen iph-ip_hl * 4; // 头长度按 4 字节为单位 if (ip_hlen 20) return; // 非法头长丢弃 if (iph-ip_p ! IPPROTO_TCP) return; if (hdr-caplen offset ip_hlen 20) return; const struct tcphdr *tcph (const struct tcphdr *)(pkt offset ip_hlen); uint32_t tcp_hlen tcph-doff * 4; const u_char *payload pkt offset ip_hlen tcp_hlen; uint32_t payload_len ntohs(iph-ip_len) - ip_hlen - tcp_hlen; // 到这里 payload/payload_len 就是 TCP 载荷交给规则引擎 // 注意 payload_len 要用 caplen 再兜一次底防止越界 }逻辑说明ip_hl和doff都是「以 4 字节为单位」的字段忘了乘 4 是最经典的翻车点解析出来的载荷指针会偏。参数说明hdr-caplen是实际抓到的长度hdr-len是原始包长做载荷计算时两者都要考虑抓包时 snaplen 设太小会截断载荷导致漏检。我一般把 snaplen 设成 65535除非你明确只关心头部。2.3 主循环与离线/在线两种模式的切换主循环用pcap_loop或pcap_dispatch前者一直抓到 EOF 或出错后者抓一批就返回适合需要定期做超时检测的场景。离线模式传文件句柄在线模式传网卡句柄回调函数完全复用。下面这段把两种模式统一起来int main(int argc, char *argv[]) { char errbuf[PCAP_ERRBUF_SIZE]; pcap_t *handle; // 参数是文件就走离线是网卡名就走在线 if (argc 1 access(argv[1], F_OK) 0) { handle pcap_open_offline(argv[1], errbuf); } else { handle pcap_open_live(argv[1], 65535, 1, 1000, errbuf); } if (!handle) { fprintf(stderr, open failed: %s\n, errbuf); return 1; } // 只抓 TCP减少用户态负担 struct bpf_program fp; if (pcap_compile(handle, fp, tcp, 0, PCAP_NETMASK_UNKNOWN) 0) { pcap_setfilter(handle, fp); } pcap_loop(handle, 0, packet_handler, NULL); pcap_close(handle); return 0; }逻辑说明pcap_open_live的第三个参数 1 表示混杂模式第四个 1000 是超时毫秒。参数说明BPF 表达式tcp可以换成tcp port 80 or tcp port 443之类越精确内核丢得越多、用户态越轻松。注意pcap_compile的优化参数设 1 会生成更高效的过滤码但调试规则时设 0 更容易看懂。3. 规则引擎把「什么算入侵」翻译成 C 代码能跑的逻辑3.1 规则的数据结构设计从字符串到匹配树NIDS 的核心是规则。最简单的做法是每条规则一个结构体字段包括协议、源/目的 IP、端口、载荷关键字、动作。但规则一多逐条遍历就慢了。常见做法是先用协议和端口做一级索引把规则分桶再在桶内做字符串匹配。下面是一个够用的规则结构#define MAX_PAYLOAD_PATTERN 256 typedef struct { int proto; // IPPROTO_TCP / IPPROTO_UDP uint16_t dport; // 目的端口0 表示任意 char pattern[MAX_PAYLOAD_PATTERN]; // 载荷关键字 int pattern_len; char msg[128]; // 告警描述 int severity; // 1-3 } rule_t; rule_t rules[] { {IPPROTO_TCP, 80, /etc/passwd, 11, 疑似路径穿越, 3}, {IPPROTO_TCP, 80, union select, 12, 疑似 SQL 注入, 3}, {IPPROTO_TCP, 22, SSH-, 4, SSH 连接, 1}, }; int rule_count sizeof(rules) / sizeof(rules[0]);逻辑说明pattern用定长数组是为了避免动态内存嵌入式场景更稳。参数说明dport设 0 表示不限制端口适合做全局特征匹配severity用来分级报告里可以按级别统计。真实项目里规则通常从配置文件读但教学和快速验证阶段硬编码数组最省事。3.2 载荷匹配memmem 与大小写不敏感的取舍匹配载荷最直接的是memmem它在二进制数据里找子串比strstr安全不依赖\0结尾。但 HTTP 攻击特征经常大小写混写UNION SELECT和union select都得命中。这时候要么统一转小写再匹配要么用大小写不敏感的匹配函数。转小写会改动原始载荷如果后面还要做别的检测就得先拷贝。我一般对载荷做一份小写副本专门用于匹配// 在 packet_handler 里拿到 payload 之后 if (payload_len 0 payload_len 8192) { char lower[8192]; for (uint32_t i 0; i payload_len; i) lower[i] tolower(payload[i]); for (int i 0; i rule_count; i) { if (rules[i].proto ! IPPROTO_TCP) continue; if (rules[i].dport ! 0 rules[i].dport ! ntohs(tcph-th_dport)) continue; if (memmem(lower, payload_len, rules[i].pattern, rules[i].pattern_len)) { printf([ALERT] %s | %s:%u - %s:%u\n, rules[i].msg, inet_ntoa(iph-ip_src), ntohs(tcph-th_sport), inet_ntoa(iph-ip_dst), ntohs(tcph-th_dport)); } } }逻辑说明memmem是 GNU 扩展Linux 下直接用其他平台要自己实现一个。参数说明8192这个上限是经验值超过这个长度的载荷做全量小写转换性价比低可以只转前 N 字节或直接跳过。注意inet_ntoa不是线程安全的多线程场景要用inet_ntop。3.3 端口扫描与 SYN Flood 的统计型检测载荷匹配只能抓「内容里有特征」的攻击端口扫描和 SYN Flood 这类没有明显载荷的得靠统计。思路是维护一张源 IP 的计数表单位时间内目的端口数超过阈值就判扫描SYN 包数超过阈值就判 Flood。下面是一个极简的滑动窗口计数#define TABLE_SIZE 1024 typedef struct { uint32_t src_ip; uint16_t ports[64]; // 记录最近访问的端口 int port_cnt; int syn_cnt; time_t window_start; } stat_entry_t; stat_entry_t table[TABLE_SIZE]; void check_scan(uint32_t src_ip, uint16_t dport, int is_syn) { uint32_t idx src_ip % TABLE_SIZE; stat_entry_t *e table[idx]; time_t now time(NULL); if (now - e-window_start 10) { // 10 秒一个窗口 e-src_ip src_ip; e-port_cnt 0; e-syn_cnt 0; e-window_start now; } if (e-src_ip ! src_ip) return; // 哈希冲突简单丢弃 // 端口去重后计数 int found 0; for (int i 0; i e-port_cnt; i) if (e-ports[i] dport) { found 1; break; } if (!found e-port_cnt 64) e-ports[e-port_cnt] dport; if (is_syn) e-syn_cnt; if (e-port_cnt 20) printf([ALERT] 端口扫描 | 源 %u 在 10s 内访问 %d 个端口\n, src_ip, e-port_cnt); if (e-syn_cnt 100) printf([ALERT] SYN Flood | 源 %u 在 10s 内发送 %d 个 SYN\n, src_ip, e-syn_cnt); }逻辑说明哈希表用src_ip % TABLE_SIZE做索引冲突直接丢弃是简化处理真实场景要用链表或开放寻址。参数说明窗口 10 秒、端口阈值 20、SYN 阈值 100 都是可调参数报告里应该说明这些值的选取依据。注意time(NULL)精度是秒高流量场景要用gettimeofday做毫秒级窗口。4. 避坑与排查那些让 NIDS 漏报误报的细节4.1 现象明明有攻击流量程序一条告警都不出原因通常是 BPF 过滤器把流量提前丢了。比如你写了tcp port 80但攻击走的是 8080自然抓不到。另一个常见原因是 snaplen 设太小载荷被截断memmem匹配不到完整特征。解决先用tcpdump -r xxx.pcap -nn确认目标流量确实在文件里再把 BPF 表达式放宽到tcp甚至空逐步收窄定位。4.2 现象程序跑几分钟就段错误九成是解析时没做长度校验。畸形包、截断包、IP 头长度字段被恶意设成非法值都会让指针飞到非法内存。解决每一层解析前都检查caplen是否够ip_hl和doff是否在合法范围IP 头 20-60 字节TCP 头 20-60 字节载荷长度用caplen和ip_len取小值。血泪经验加断言不如加if return线上环境断言会直接崩。4.3 现象告警里 IP 和端口全是乱的多半是字节序问题。网络字节序是大端printf出来之前必须ntohs/ntohl。iph-ip_src是struct in_addr直接当整数打印会得到反的地址。解决IP 用inet_ntoa或inet_ntop端口用ntohs长度字段用ntohs。养成习惯凡是协议头里的多字节字段用之前先转。4.4 现象同一份 PCAP 跑两次告警数量不一样如果用了统计型检测时间窗口依赖time(NULL)两次运行的起始时间不同窗口边界就不同计数自然有差异。解决离线分析时不要用真实时间改成用包的时间戳hdr-ts做窗口基准这样同一份文件每次跑结果一致报告才可复现。4.5 现象规则一多处理速度断崖式下降逐条遍历规则是 O(规则数) 的复杂度规则上千条时每条流量都要跑上千次memmem。解决先按端口分桶只匹配该端口对应的规则再按协议过滤如果还慢上 Aho-Corasick 多模式匹配一次扫描命中所有关键字。教学项目里分桶通常就够了AC 自动机是进阶优化。5. 让检测结果可复现离线回放、告警归并与报告生成离线回放是验证 NIDS 最靠谱的手段。同一份 PCAP固定规则、固定窗口参数跑出来的告警应该完全一致。我一般会写一个回放脚本把告警输出重定向到文件再用sort | uniq -c做归并统计看看哪些规则命中最多、哪些源 IP 最活跃。下面这段把告警按「源 IP 规则」聚合# 假设程序输出格式为[ALERT] msg | src:sport - dst:dport ./nids test.pcap alerts.log # 提取源 IP 和告警类型做聚合 awk -F[|:] /ALERT/ {gsub(/ /,,$2); print $2, $1} alerts.log \ | sort | uniq -c | sort -rn | head -20逻辑说明awk按分隔符切出源 IP 和告警描述uniq -c计数sort -rn按次数倒序。参数说明分隔符要根据你实际的输出格式调整别照抄。这个统计结果直接可以放进报告说明「本次检测共命中 N 类告警其中端口扫描占比最高」。报告里还应该有一张参数表把关键阈值和选取理由写清楚评审或答辩时这是加分项参数取值选取理由snaplen65535避免载荷截断导致漏检BPF 过滤tcp只处理 TCP减少用户态负担扫描窗口10 秒兼顾检测灵敏度和误报率端口阈值20 个正常用户 10 秒内很少访问超 20 个端口SYN 阈值100 个正常建连远低于此值最后说个我自己的习惯每次改完规则或阈值一定拿同一份 PCAP 重跑一遍对比告警差异。没有基线对比的调参就是玄学今天调完觉得对了明天换个流量又翻车。把回放和归并脚本固化成流程比记住任何单个参数都管用。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/28 11:58:00

HBase与Hive整合实战:用SQL查询海量数据的存储与解析方案

在做大数据平台运维的这几年,我最常被问到的一句话是:HBase 里存了这么多数据,想做统计、join 一下,难道只能写 Java API 吗?不是。把 HBase 和 Hive 整合起来以后,HBase 里的海量数据也能用标准 SQL 查询&…

2026/9/28 11:58:00

基于YOLO的西红柿成熟度检测:1267张图像训练三分类模型实战

简介:这份资源是面向计算机视觉学习者与智能农业开发者的西红柿成熟度目标检测数据集,可直接用于YOLO系列算法的训练与验证,帮助解决果蔬成熟状态自动分类的识别难题。压缩包共2000个文件,以1267个xml标注文件和733个txt标签文件为…

2026/9/28 11:58:00

基于深度学习的图像修复系统实战:从CNN编解码到GAN对抗训练

简介:这是一套面向计算机相关专业毕业设计、课程设计及机器学习入门者的深度学习图像修复项目资料,核心是用卷积神经网络与对抗式训练策略实现图像缺失区域的智能补全,可处理划痕修复、噪点消除与局部遮挡还原等任务,适合需要完整…

2026/9/28 12:58:04

数据库表关系设计:一对一、一对多、多对多从理论到实战

做数据库也这么多年了,说实话,我见过太多线上系统出问题,最后排查来排查去,根子都在建表那一步——表关系没理清楚。要么是两张表耦合得乱七八糟,要么是该拆开的全塞进一张大表里,要么是多对多关系靠逗号分…

2026/9/28 12:58:04

用Go从零实现以太坊JSON-RPC客户端:协议解析与交易实战

最近在做以太坊相关的东西,越做越觉得有意思。网上聊go语言实现以太坊客户端的教程不少,但大多直接给你一个go-ethereum的rpc包让你对着文档调,真正从零把JSON-RPC这一层手写一遍的人不多。这篇文章我准备完整复盘一下自己用go语言从零实现一…

2026/9/28 12:58:04

HCIA静态路由综合实验:全网可达与回程路由排错详解

做网络实验最怕的不是配错命令,而是配完之后不知道错在哪。HCIA的静态路由综合实验,我愿把它叫“全网可达的拼图”——每一台路由器手里都攥着一块路由表,只有把每个网段的“去程”“回程”都拼严实了,网络才真正通。很多初学者在…

2026/9/28 12:58:04

C盘爆满怎么办?十招系统盘清理与空间迁移方案

C盘爆红可能是电脑使用中最常见也最闹心的提示了。这几年前前后后帮同事、朋友处理过上百台C盘爆满的机器,有刚买一年就红的,也有用了五年才突然告急的,还有那种明明看着没装几个软件、C盘却神秘少了几十G的怪事。这篇文章把我这些年积攒的排…

2026/9/28 12:53:04

Kafka按时间戳查询消息:原理、API与实战全解析

做Kafka排查的人,十有八九都对着这句话抓过狂:“我想看看昨晚23:30之后,这个topic到底消费了哪些消息”。以前要么按消息总量平均估算offset,要么干脆把消费组重置到最新再慢慢刷,效率低而且不精准。Kafka从0.10版本开…

2026/9/28 3:03:23

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/9/28 6:05:15

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/28 6:07:41

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/28 0:02:03

广州外贸网站建设推广:从零搭建全流程拆解与真实报价避坑

广州外贸网站建设推广:从零搭建全流程拆解与真实报价避坑 改个需求建站公司拖一周,后台改个文案还得再交一笔“技术维护费”。这种憋屈事儿,做外贸的朋友太熟悉了。很多老板在找广州外贸网站建设推广服务商时,光盯着首页好不好看,却忽略了从零搭建一个能…

2026/9/28 0:02:04

搞懂百度竞价推广价格,网站性能优化别掉链子

搞懂百度竞价推广价格,网站性能优化别掉链子 网站突然打不开,浏览器弹出红色警告“此网站存在安全风险”,后台一看全是乱码代码和奇怪的跳转链接。这种网站被黑挂马的绝望感,很多刚转行做网站的朋友都经历过,尤其是那些为了省几百块钱服务器费用的新手。…

2026/9/25 20:55:38

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/26 19:58:38

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/28 1:59:25

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑