尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

纯C实现的轻量级网络入侵检测系统

纯C实现的轻量级网络入侵检测系统 简介这是一套基于PCAP数据包捕获与分析的轻量级网络入侵检测系统NIDS源码实现面向计算机、电子信息及网络安全方向的本科生课程设计、毕业设计与实践学习者聚焦于底层网络协议解析与异常流量识别核心能力训练。压缩包共17个文件含6个C源文件与4个头文件构成核心嗅探与分析模块2个Makefile支持跨平台编译1个Python脚本arp-poison.py提供典型攻击模拟另含项目说明PDF、README.md文档及测试脚本整体889KB结构紧凑、依赖少、开箱即用。已有354人学习下载适合希望深入理解Sniffer机制、线程池调度、ARP欺骗检测等关键技术的学习者。读者可直接编译运行结合CS241课程作业PDF理解设计逻辑并通过test.sh快速验证系统对常见攻击流量的响应能力。1. 这不是又一个“抓包打印”的玩具项目它用纯 C 实现了基于 PCAP 的实时流量解析、协议识别与异常行为标记能跑在树莓派上检测 ARP 欺骗、SYN Flood 和 HTTP 异常请求——适合课程设计快速落地也经得起毕设答辩追问你可能已经下载过几十个标着“网络入侵检测”的 GitHub 项目解压后发现只是tcpdump -r traffic.pcap | grep GET加个 Python 脚本循环匹配字符串。但这个code_20105目录下的东西不一样它没有依赖 Scapy 或 Suricata不调用任何 Python 解析库所有协议解析Ethernet/IP/TCP/UDP/ICMP/ARP/HTTP全由sniff.c和analysis.c用指针偏移 位运算硬啃出来线程池调度逻辑写在dispatch.c里Makefile里甚至带-O2 -marcharmv7-a针对树莓派的编译优化配套的arp-poison.py不是拿来演示攻击的而是作为验证用例——你用它发包C 程序必须实时捕获并标记为ALERT: ARP SPOOF DETECTED。这不是教学 Demo是 CS241 课程作业的真实交付物PDF 里明确写了评分项“能否在 100Mbps 流量下维持 3% CPU 占用”、“是否能区分合法 ARP 请求与恶意重复响应”。如果你正卡在毕设选题——既不想抄 Snort 又怕自己从零写解析器翻车这个项目就是那个“刚好够深、刚好能跑、刚好能讲清楚”的临界点。2. 从 Makefile 编译到线程池调度看清它怎么把原始 PCAP 数据流变成可标记的告警事件2.1 编译链路为什么这个 Makefile 比你写的更“懂”嵌入式项目根目录的Makefile是整个构建逻辑的中枢它不只负责gcc -c更关键的是做了三件事自动探测平台特性通过$(shell uname -m)判断是x86_64还是armv7l动态启用-marcharmv7-a -mfpuvfp3 -mfloat-abihard见第 12 行ARM_FLAGS分离编译与链接阶段.o文件全部生成在build/目录下避免污染源码树第 28 行$(OBJDIR)/%.o: %.c强制符号可见性控制-fvisibilityhidden__attribute__((visibility(default)))仅暴露main()和dispatch_init()见dispatch.h第 15 行防止第三方库符号冲突。# Makefile 关键片段第 10–15 行 ifeq ($(shell uname -m), armv7l) ARCH_FLAGS -marcharmv7-a -mfpuvfp3 -mfloat-abihard else ARCH_FLAGS -marchnative endif CFLAGS -Wall -Wextra -O2 $(ARCH_FLAGS) -fvisibilityhidden提示-fvisibilityhidden是血泪经验——某次我在树莓派上链接libpcap.so时因未加此参数导致pcap_open_live符号被意外覆盖程序启动即SIGSEGV。加了之后nm -D build/main.o显示只有main和dispatch_init两个全局符号。2.2 主干流程main.c如何串联嗅探、分发与分析三大模块整个系统采用“生产者-消费者”模型main()启动sniff_start()创建捕获线程生产者将原始数据包送入dispatch_queuedispatch_worker()从队列取包按协议类型分发给analysis_tcp(),analysis_arp()等函数消费者。关键不在代码行数而在状态隔离设计每个分析函数接收const uint8_t *pkt和size_t len绝不修改原始内存所有中间状态如 TCP 序列号窗口、ARP 缓存表都存在analysis_context_t结构体里由dispatch.c统一分配/回收。// main.c 第 47 行初始化核心组件 if (dispatch_init(4) ! 0) { // 启动 4 个工作线程 fprintf(stderr, Dispatch init failed\n); return 1; } sniff_start(eth0); // 绑定网卡启动捕获循环sniff_start()内部调用pcap_open_live()时传入100作为snaplen第 3 行#define SNAP_LEN 100这意味着它只截取每个包的前 100 字节——足够解析 Ethernet 头14B、IP 头20B、TCP 头20B和部分 payload但故意丢弃大文件传输的完整 HTTP body。这是性能与检测精度的权衡课程要求检测“异常连接模式”而非“传输内容”所以analysis_http()只检查GET /admin或POST /login这类路径特征见analysis.c第 218 行if (memcmp(payload, GET /admin, 10) 0)不解析 Cookie 或 Referer。2.3 线程池实现dispatch.c里藏着的无锁队列与唤醒机制dispatch.c的dispatch_queue_t并非简单链表而是环形缓冲区ring buffer 原子计数器实现的无锁队列。dispatch_enqueue()使用__atomic_fetch_add(q-tail, 1, __ATOMIC_SEQ_CST)更新尾指针dispatch_dequeue()用__atomic_load_n(q-head, __ATOMIC_ACQUIRE)读头指针——避免 pthread_mutex_t 在高并发下的上下文切换开销。更关键的是唤醒策略当队列从空变为非空时head tail→head ! taildispatch_worker()会调用pthread_cond_signal(q-not_empty)唤醒休眠线程而不是轮询usleep(1000)。// dispatch.c 第 89 行无锁入队核心逻辑 bool dispatch_enqueue(dispatch_queue_t *q, const packet_t *pkt) { size_t tail __atomic_fetch_add(q-tail, 1, __ATOMIC_SEQ_CST); if ((tail - __atomic_load_n(q-head, __ATOMIC_ACQUIRE)) q-capacity) { return false; // 队列满丢包 } q-buffer[tail % q-capacity] *pkt; if (tail __atomic_load_n(q-head, __ATOMIC_ACQUIRE)) { pthread_cond_signal(q-not_empty); // 仅当队列从空变非空时唤醒 } return true; }这个设计直接决定了它能在树莓派 4B4GB RAM上处理 80Mbps 流量而不丢包——我实测过当dispatch_enqueue()返回false时sniff.c里的pcap_dispatch()会立即fprintf(stderr, DROP: queue full\n)这比让线程死等更符合课程设计“可观测性”要求。3. 协议解析实战手撕 Ethernet/IP/TCP/ARP 四层结构看它如何从字节流中揪出异常3.1 Ethernet 层用memcpy和ntohs定位协议类型拒绝 magic number 陷阱sniff.c的process_packet()函数第一件事是校验 Ethernet 头完整性if (len sizeof(struct ethhdr)) return;。接着用memcpy(eth, pkt, sizeof(struct ethhdr))拷贝头结构再用ntohs(eth.ether_type)转换网络字节序。这里有个易错点ether_type为0x0800表示 IPv40x0806表示 ARP但不能直接switch(eth.ether_type)——因为ntohs()返回uint16_t而0x0800在小端机器上存储为0x0008若忘记转换会永远匹配不到。项目在sniff.h第 22 行定义了宏// sniff.h 第 22 行 #define ETH_P_IP 0x0800 #define ETH_P_ARP 0x0806 // sniff.c 第 45 行正确用法 switch (ntohs(eth.ether_type)) { case ETH_P_IP: process_ip(pkt sizeof(struct ethhdr), len - sizeof(struct ethhdr)); break; case ETH_P_ARP: process_arp(pkt sizeof(struct ethhdr), len - sizeof(struct ethhdr)); break; }注意struct ethhdr在不同内核版本中字段名可能不同如h_protovsether_type该项目用#include linux/if_ether.h而非net/ethernet.h确保与pcap底层一致。3.2 IP 层校验和验证与分片重组的取舍analysis.c的parse_ip_header()对 IP 头做两件事校验和验证调用ip_checksum((uint16_t*)ip_hdr, sizeof(struct iphdr))计算头校验和见analysis.c第 32 行若ip_hdr.check ! 0则直接return NULL分片处理检查ip_hdr.frag_off htons(IP_MF)是否为真若为真则跳过该包第 58 行// Fragmented packets skipped per coursework spec。这个取舍很务实——课程 PDF 明确要求“忽略分片包”因为重组逻辑会极大增加复杂度而真实入侵场景中 SYN Flood 或 ARP 欺骗几乎不用分片。// analysis.c 第 32 行IP 头校验和计算 static uint16_t ip_checksum(const uint16_t *data, size_t len) { uint32_t sum 0; for (size_t i 0; i len; i 2) { sum (i 1 len) ? *(uint16_t*)(data i) : *(uint8_t*)(data i); } sum (sum 16) (sum 0xFFFF); return ~((sum 16) sum) 0xFFFF; }3.3 TCP 层三次握手状态机与 SYN Flood 检测逻辑analysis_tcp()的核心是维护一个tcp_state_t状态机定义在analysis.h第 45 行TCP_STATE_LISTEN收到 SYN → 记录src_ip:src_port到syn_tableTCP_STATE_SYN_SENT收到 SYNACK → 校验 ACK numberTCP_STATE_ESTABLISHED收到 ACK → 清除syn_table条目。SYN Flood 检测就藏在syn_table的容量控制里#define MAX_SYN_TRACK 1024analysis.h第 38 行当syn_count 500 time_since_last_clear 10秒触发ALERT: POSSIBLE SYN FLOOD。注意它不依赖时间戳而是用gettimeofday()记录last_clear_time避免 NTP 调整导致误报。// analysis.c 第 135 行SYN Flood 检测入口 if (tcp_hdr-syn !tcp_hdr-ack) { if (add_to_syn_table(src_ip, src_port) -1) { if (syn_count 500 time_diff(last_clear_time, now) 10) { log_alert(POSSIBLE SYN FLOOD from %s:%d, inet_ntoa(*(struct in_addr*)src_ip), ntohs(src_port)); } } }3.4 ARP 层用哈希表检测 IP-MAC 绑定异常analysis_arp()的检测逻辑基于“同一 IP 地址在短时间内映射到不同 MAC 地址”将arp-arp_spa源 IP作为 keyarp-arp_sha源 MAC作为 value 存入arp_cache哈希表若查表发现existing_mac ! current_mac且time_diff(cache-last_seen, now) 30秒则标记ALERT: ARP SPOOF DETECTED。哈希表实现用开放寻址法analysis.c第 288 行arp_cache_t cache[ARP_CACHE_SIZE]ARP_CACHE_SIZE 256负载因子控制在 0.75 以内避免冲突链过长。4. 避坑指南那些让你编译失败、运行崩溃、检测漏报的 5 个真实踩坑记录4.1 现象make报错error: ‘PCAP_NETMASK_UNKNOWN’ undeclared原因系统libpcap-dev版本过低1.9.0PCAP_NETMASK_UNKNOWN宏在旧版中不存在。解决升级 libpcapsudo apt-get install libpcap-devUbuntu 20.04 默认满足若仍报错在sniff.c第 22 行手动定义#ifndef PCAP_NETMASK_UNKNOWN #define PCAP_NETMASK_UNKNOWN 0x00000000 #endif。4.2 现象程序启动后pcap_loop()无响应CPU 占用 0%原因pcap_open_live()的promisc参数设为0非混杂模式而目标网卡未配置为混杂模式。解决启动时加sudo或在sniff.c第 63 行改为pcap_open_live(dev, SNAP_LEN, 1, 1000, errbuf)第三个参数1启用混杂。4.3 现象arp-poison.py发包后C 程序未触发ARP SPOOF告警原因arp-poison.py默认发单播 ARP Reply而analysis_arp()只处理arp_op htons(ARPOP_REPLY)且arp_tpa local_ip的包但未校验arp_tha目标 MAC是否为广播地址ff:ff:ff:ff:ff:ff。解决在analysis_arp()第 312 行添加校验if (arp_hdr-arp_op htons(ARPOP_REPLY) memcmp(arp_hdr-arp_tha, \xff\xff\xff\xff\xff\xff, 6) 0)。4.4 现象test.sh运行./main -r test/normal.pcap时 segmentation fault原因test/normal.pcap是 Wireshark 导出的 pcapng 格式而libpcap的pcap_open_offline()无法解析 pcapng。解决用tshark -F libpcap -w normal_fixed.pcap test/normal.pcapng转换格式或直接用项目自带的test/capture.pcap已确认为 libpcap 格式。4.5 现象树莓派上编译成功但运行时报Illegal instruction原因Makefile中ARM_FLAGS启用了vfp3协处理器指令但旧版 Raspberry Pi OS 内核未启用 VFP 支持。解决注释掉Makefile第 13 行ARCH_FLAGS改用ARCH_FLAGS -marcharmv6zk -mfpuvfpPi 1/Zero 兼容或升级系统sudo apt update sudo apt upgrade。5. 毕设级改造给原始项目加 HTTP 异常检测、日志持久化与 Web 控制台三步可落地5.1 扩展 HTTP 检测从路径匹配到 User-Agent 异常识别原始analysis_http()只检查GET /admin但毕设需要更细粒度。我在analysis.c新增http_check_user_agent()函数规则如下若User-Agent包含sqlmap、nmap、dirb等工具名大小写不敏感若User-Agent长度 10 或 200 字符规避畸形 UA若连续 5 个包User-Agent完全相同且无 Cookie疑似扫描器。实现用strcasestr()替代strstr()并引入http_context_t结构体记录最近 10 个 UA 的哈希值djb2_hash()避免重复告警。// analysis.c 新增函数第 420 行 bool http_check_user_agent(const char *ua, size_t ua_len) { if (ua_len 10 || ua_len 200) return true; if (strcasestr(ua, sqlmap) || strcasestr(ua, nmap)) return true; uint32_t hash djb2_hash(ua, ua_len); if (http_ctx.ua_count 10) { memmove(http_ctx.ua_hashes, http_ctx.ua_hashes 1, 9 * sizeof(uint32_t)); http_ctx.ua_hashes[9] hash; } else { http_ctx.ua_hashes[http_ctx.ua_count] hash; } // 检查最近 5 个是否相同 int same_count 1; for (int i http_ctx.ua_count - 2; i 0 i http_ctx.ua_count - 5; i--) { if (http_ctx.ua_hashes[i] hash) same_count; } return same_count 5; }5.2 日志持久化用 ring buffer mmap 实现零拷贝日志写入为避免fprintf(stderr, ...)在高流量下阻塞主线程我替换log_alert()为ring_log_write()创建 1MB 内存映射文件/dev/shm/alert_log用__atomic_fetch_add()更新写指针写入格式为timestamp|src_ip|alert_type|payload单独线程每 5 秒fsync()一次保证断电不丢最近 5 秒日志。这样dispatch_worker()调用log_alert()时实际只是 memcpy 到共享内存耗时 100ns。5.3 Web 控制台用嵌入式 HTTP Server 暴露实时统计不引入外部框架直接用mongoose库轻量 C HTTP Server在main.c初始化后调用mg_http_listen(mgr, http://0.0.0.0:8080, ev_handler, NULL)ev_handler()响应/stats返回 JSON{total_packets:12345,alerts:{arp_spoof:12,syn_flood:3}}/live返回 SSE 流实时推送新告警data: {alert:ARP SPOOF,src:192.168.1.100}\n\n。编译时加-lmghttpmongoose.c直接加入src/目录无需额外依赖。从那以后我每次做网络安全部署都强制走一遍pcap_compile()编译 BPF 过滤器哪怕只用host 192.168.1.100因为某次在 IDC 机房没加过滤器的pcap_open_live()把交换机镜像流量全收进来dispatch_queue3 秒就满sniff.c的丢包日志刷屏到串口根本看不清。现在我的习惯是先tcpdump -i eth0 -c 10 host 192.168.1.100 -w test.pcap验证过滤语法再贴进代码——这招救过我三次答辩现场。希望帮到你。本文还有配套的精品资源点击获取
返回列表