网络工程毕设选题新颖简单:基于轻量级协议栈的校园网流量可视化系统设计与实现
把“老掉牙”的 SNMP 轮询换成 eBPF,把“只能抓包”的 Wireshark 换成 Prometheus+Grafana,一周就能跑出一套能写进简历的“生产级”毕设原型——这是我今年带 12 位同学做完后的真实体感。下面把踩过的坑、测过的数据、贴过的代码一次性摊开,供下一届抄作业。
1. 先吐槽:传统毕设的三大老毛病
SNMP 轮询像老牛拉破车
五分钟一轮的 ifHCInOctets 不仅粒度粗,而且交换机默认关闭 v2c,开起来还得改 ACL,老师一句“不安全”直接打回。Wireshark + TAP 镜像成了“人肉运维”
抓 10 Gbps 流量不到 30 秒,笔记本硬盘先报警;更尴尬的是,只能看单包,给不出“近 5 分钟每个宿舍楼的 TCP 重传率”这种指标。NetFlow/sFlow 看似高级,实则“重装骑兵”
需要厂商授权、独立采集器、MongoDB 存模板,配置一多就“劝退”。答辩现场一紧张,忘记开 ip flow-export,数据直接空白,场面极度舒适。
2. 技术选型:eBPF 为什么能“降维打击”
| 维度 | eBPF(本文方案) | NetFlow v9 | sFlow |
|---|---|---|---|
| 部署成本 | 零硬件,内核≥4.14 即可 | 需三层交换机授权 | 同上 |
| 时间精度 | 毫秒级 | 分钟级 | 秒级 |
| 字段灵活 | 可自定义结构体 | 固定模板 | 固定模板 |
| 资源占用 | ≈1% CPU(见第 4 节) | 5–8% | 3–5% |
| 安全 | 普通用户 + CAP_BPF | 需 SNMP 写权限 | 同上 |
一句话:eBPF 把“流量镜像+特征统计”下沉到内核,既不用改交换机配置,也避开了“镜像口 1G 瓶颈”。
3. 系统架构:eBPF+Go+Prometheus+Grafana 四件套
3.1 内核态:eBPF 程序(C)
// tracer.c #include <linux/bpf.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <bpf/bpf_helpers.h> struct flow_key { __u32 src_ip; __u32 dst_ip; __u16 src_port; __u16 dst_port; __u8 proto; }; struct flow_stat { __u64 bytes; __u64 packets; }; struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 10240); __type(key, struct flow_key); __type(value, struct flow_stat); } flow_stats SEC(".maps"); SEC("xdp") int xdp_flow_mon(struct xdp_md *ctx) { void *data0 = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; struct ethhdr *eth = data0; if ((void *)(eth + 1) > data_end) return XDP_PASS; struct iphdr *ip = (struct iphdr *)(eth + 1); if ((void *)(ip + 1) > data_end) return XDP_PASS; struct flow_key k = {}; struct flow_stat *v, init = {0, 1}; k.src_ip = ip->saddr; k.dst_ip = ip->daddr; k.proto = ip->protocol; if (ip->protocol == IPPROTO_TCP || ip->protocol == IPPROTO_UDP) { struct tcphdr *tcp = (struct tcphdr *)((void *)ip + ip->ihl * 4); if ((void *)(tcp + 1) > data_end) return XDP_PASS; k.src_port = tcp->source; k.dst_port = tcp->dest; } v = bpf_map_lookup_elem(&flow_stats, &k); if (!v) { bpf_map_update_elem(&flow_stats, &k, &init, BPF_ANY); } else { __sync_fetch_and_add(&v->packets, 1); __sync_fetch_and_add(&v->bytes, ctx->data_end - ctx->data); } return XDP_PASS; } char _license[] SEC("license") = "GPL";3.2 用户态:Go Exporter(节选)
// main.go package main import ( "log" "time" "github.com/cilium/ebpf" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "net/http" ) var ( bytesGauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "campus_flow_bytes", Help: "Per-5s bytes aggregated by eBPF", }, []string{"src_ip", "dst_ip", "proto"}) ) func main() { spec, err := ebpf.LoadCollectionSpec("tracer.o") if err != nil { log.Fatal(err) } coll, err := ebpf.NewCollection(spec) if err != nil { log.Fatal(err) } defer coll.Close() flowMap := coll.Maps["flow_stats"] prometheus.MustRegister(bytesGauge) go func() { for { var key, nextKey FlowKey var val FlowStat iter := flowMap.Iterate() for iter.Next(&key, &val) { bytesGauge.WithLabelValues( int2ip(key.SrcIp), int2ip(key.DstIp), strconv.Itoa(int(key.Proto)), ).Set(float64(val.Bytes)) } time.Sleep(5 * time.Second) } }() http.Handle("/metrics", promhttp.Handler()) log.Fatal(http.ListenAndServe(":9100", nil)) }3.3 Grafana 面板 JSON 核心片段
{ "panels": [ { "title": "Top 10 Talkers", "targets": [ { "expr": "topk(10, sum(rate(campus_flow_bytes[5m])) by (src_ip))", "legendFormat": "{{src_ip}}" } ], "type": "graph" } ] }把 JSON 导入 Grafana,再配好 Prometheus 数据源,就能看到“谁在下 4K 蓝光”。
4. 性能与安全:能跑多快?会不会把内核搞崩?
测试机:i5-8400T + 8 G + Intel X550-T2,内核 5.15,流量 1 Gbps UDP 背景流。
| 指标 | 结果 |
|---|---|
| CPU 占用 | 0.9%(单核) |
| 吞吐延迟 | +0.04 ms(对比空转) |
| 内存 | 40 KB(map 条目上限 10k) |
| 丢包 | 0(xdp_drv 模式) |
安全边界:
- 权限最小化:只需
CAP_BPF+CAP_NET_ADMIN,无需 root 常驻。 - 内核兼容性:代码里用了
BPF_MAP_TYPE_HASH与XDP,≥4.14 即可;若学校机房老内核 3.10,可改用tc BPF钩子,API 几乎一致。 - 拒绝 panic:eBPF 验证器会拒绝未初始化指针访问,比手写内核模块稳得多。
5. 生产环境避坑指南
内核符号缺失
编译时报linux/bpf.h not found八成是开发头没装。Ubuntu 跑apt install linux-headers-$(uname -r);CentOS 跑yum install kernel-devel。探针稳定性
热升级网卡驱动后,XDP 钩子会被卸载。解决:把加载命令写进 systemd-service 的ExecStartPre,重启驱动后自动重挂。采样率失真
默认 5 秒导出一次 Prometheus,突发小包容易“抹平”。可在 eBPF 里加BPF_MAP_TYPE_PERCPU_ARRAY做 1 秒暂存,用户态再聚合,误差从 12% 降到 2%。map 打满
10k 条目对校园网够用,但遇到扫描器会瞬间爆炸。加 LRU 尾部淘汰,或把max_entries调大到 50k,内存仅增 200 KB。
6. 还能怎么玩?两个现成的延伸方向
- DDoS 检测:在 eBPF 里维护“源 IP 每秒 SYN 计数”map,超过阈值直接
XDP_DROP,用户态只负责告警。 - 带宽配额管理:把 3.2 节的 Gauge 换成 Counter,再写一条 PromQL
increase(campus_flow_bytes[1d]) > 5*1024*1024*1024,触发 webhook 调用宿舍网关 API 限速,轻松变身“网管小助手”。
7. 写在最后
这套方案从开题到答辩只花了三周,最耗时的环节居然是跟辅导员解释“eBPF 不是挖矿”。代码全部放 GitHub 公有库,配好 CI 后,每次 push 自动编出.o和 rpm 包,老师看完直接给了优秀。
如果你也在为“选题新颖+工作量饱满”掉头发,不妨把本文的仓库 fork 下来,换张校园网拓扑图,加点自己的仪表盘,就能跑出另一篇故事。
下一步想不想把 D Dream 的 10 Tbps 攻击流量重放进来,看看你的小探针会不会被冲垮?动手比看十篇论文更有意思,祝调试愉快!