Vibe Coding 时代,我用 Rust 写了个流量哨兵

  • King
  • 发布于 8小时前
  • 阅读 27

前阵子有个朋友找到我,说他搞了个公益服务放在网上,免费给大家用的。本来挺好的事儿,结果没过几天服务器就扛不住了——有人疯狂刷接口、暴力破解、搞DDoS。他问我:有没有什么简单的办法能挡一挡?我说,这不是有防火墙吗?他说云服务商的防火墙太粗糙,只能按IP段封,而且按流量计费,攻击流量进来之

前阵子有个朋友找到我,说他搞了个公益服务放在网上,免费给大家用的。本来挺好的事儿,结果没过几天服务器就扛不住了 —— 有人疯狂刷接口、暴力破解、搞DDoS。

他问我:有没有什么简单的办法能挡一挡?

我说,这不是有防火墙吗?他说云服务商的防火墙太粗糙,只能按IP段封,而且按流量计费,攻击流量进来之前已经产生了费用。他想要一个更灵活的东西,能在更底层把恶意流量截住。

于是就有了这个项目 —— net-sentry

先说说想做什么

需求其实很简单:

  1. 在网络层直接拦截恶意流量,不让他走到应用层
  2. 支持灵活的规则,比如"封掉某个IP"、"只允许某个端口"、"阻断TCP到某IP的443端口"
  3. 轻量、高效,毕竟公益服务资源有限

Linux有个东西叫TUN设备,是个虚拟网卡。它能拦截到的数据是IP层及以上,正好符合需求。用户空间程序读取TUN设备拿到原始IP包,自己决定是放行还是丢弃。

Rust 的选择

为什么用Rust?说实话,一开始也想用Go,毕竟写网络程序Go生态成熟。但后来想了想:

  • 这是跑在系统底层的东西,性能很关键
  • Go有GC,高包量的时候GC停顿会影响延迟
  • Rust的零成本抽象,能让代码既干净又快

而且现在有AI帮忙写代码,Rust那套所有权、生命周期的门槛被大大降低了。我问Claude:"帮我写一个解析IP头的结构体",它直接给我生成好,我只需要检查一下对不对。

这就是Vibe Coding时代 —— 你不用纠结语法细节,专注于解决问题本身就行。

架构设计

整体架构比较直接:

TUN设备 → 读取原始包 → 解析协议头 → 匹配规则 → 放行或丢弃
                ↓
            定期统计日志

代码分了几个模块:

  • io.rs:TUN设备的读写封装
  • parser.rs:IP/TCP/UDP/ICMP协议解析
  • rules.rs:规则定义和匹配
  • engine.rs:规则引擎,决定放行还是阻断
  • stats.rs:统计上报

用Trait把组件解耦

Rust的trait很适合这种场景。我把每个组件都抽象成trait:

// 数据包来源
pub trait PacketSource {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
}

// 数据包出口
pub trait PacketSink {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize>;
}

// 协议解析器
pub trait PacketParser {
    fn parse(&self, buf: &[u8]) -> Option<PacketMeta>;
}

// 规则引擎
pub trait RuleEngine {
    fn should_drop(&self, meta: &PacketMeta) -> bool;
}

这样做的好处是,核心循环完全不依赖具体实现:

fn run_loop<S, P, E, R>(device: &mut S, parser: &P, engine: &E, mut stats: R, buf_size: usize)
where
    S: PacketSource + PacketSink,
    P: PacketParser,
    E: RuleEngine,
    R: StatsReporter,
{
    let mut buf = vec![0u8; buf_size];

    loop {
        let n = device.read(&mut buf).unwrap();
        if let Some(meta) = parser.parse(&buf[..n]) {
            if !engine.should_drop(&meta) {
                device.write(&buf[..n]);
            }
        }
    }
}

想换成不同的解析库?实现一个PacketParser就行。想换规则引擎?实现RuleEngine。测试的时候,可以注入mock实现,不需要真的创建TUN设备。

协议解析

解析网络包这种事,千万别自己手写。各种边界条件、大小端、可选字段,写一个错一个。

直接用现成的库etherparse,它把IP、TCP、UDP、ICMP的解析都做好了:

fn parse_ipv4(buf: &[u8]) -> Option<PacketMeta> {
    let ip = Ipv4HeaderSlice::from_slice(buf).ok()?;
    let src_ip = IpAddr::V4(ip.source_addr());
    let dst_ip = IpAddr::V4(ip.destination_addr());
    let proto = ip.protocol();
    // 继续解析传输层...
}

解析出来的元信息放在一个简单的结构体里:

pub struct PacketMeta {
    pub proto: Proto,           // TCP/UDP/ICMP/其他
    pub src_ip: IpAddr,         // 源IP
    pub dst_ip: IpAddr,         // 目标IP
    pub src_port: Option<u16>,  // 源端口
    pub dst_port: Option<u16>,  // 目标端口
}

规则语法

规则设计成简单的DSL,命令行直接传:

# 封掉某个IP的所有流量
--block "dst=192.168.1.100:*"

# 只允许访问80和443端口
--allow "tcp,dst=*:80" --allow "tcp,dst=*:443" --default-policy deny

# 封掉某个IP段的TCP 443端口
--block "tcp,dst=10.0.0.0/8:443"

规则解析就是把字符串切分,识别关键词:

pub fn parse_rule(input: &str) -> Result<Rule, String> {
    let mut proto = None;
    let mut src = None;
    let mut dst = None;

    for part in input.split(',') {
        if part == "tcp" { proto = Some(ProtoMatch::Tcp); }
        else if part.starts_with("src=") { 
            src = Some(parse_netport(&part[4..])?); 
        }
        // ...
    }
    Ok(Rule { proto, src, dst })
}

规则匹配就是逐条检查:

impl RuleEngine for SimpleRuleEngine {
    fn should_drop(&self, meta: &PacketMeta) -> bool {
        // 先检查允许规则
        if self.allow.iter().any(|r| r.matches(meta)) {
            return false;
        }
        // 再检查阻断规则
        if self.block.iter().any(|r| r.matches(meta)) {
            return true;
        }
        // 都没匹配,按默认策略
        self.default_policy == DefaultPolicy::Deny
    }
}

AI辅助开发体验

说实话,这个项目大部分代码都是我描述需求,Claude生成的。我的角色更像是在做代码审查和架构设计。

比如我说:"写一个TUN设备的封装,支持read和write",它给我生成好。我说"解析IP头",它生成好。我说"实现一个规则引擎,支持allow和block列表",它又生成好。

我需要做的是:

  1. 把需求拆解成清晰的描述
  2. 检查生成的代码是否正确
  3. 思考模块之间的接口设计
  4. 决定整体架构

这就是Vibe Coding——你不再是一个字一个字敲代码的打字员,而是一个用自然语言指挥AI的建筑师。AI是你的pair programmer,而且是那种永不疲倦、随时响应的搭档。

当然,AI也会犯错。有次生成的规则解析代码,端口范围解析的逻辑有问题,我检查的时候发现了。还有一次,trait的泛型约束写漏了,编译器报错我才意识到。

但总的来说,开发效率至少提升了3-5倍。

用起来

编译运行需要root权限(创建TUN设备需要):

cargo build --release
sudo ./target/release/net-sentry \
    --tun-name tun0 \
    --block "tcp,dst=恶意IP:443" \
    --block "dst=另一个恶意IP:*"

运行后会定期打印统计:

INFO stats total=10000 dropped=328 forwarded=9672

后续想做的事

目前这个版本已经能用,但还有不少可以完善的地方:

  1. 持久化IP黑名单:现在规则是启动时传入的,动态封禁的IP重启后就丢了
  2. 连接跟踪:TCP是有状态的,可以只阻断新连接,不影响已建立的连接
  3. 流量速率限制:对某个IP的请求频率做限制,防止单点刷量
  4. 日志持久化:把拦截日志写到文件,方便事后分析攻击模式
  5. Web管理界面:提供一个简单的UI来管理规则,不用敲命令行

不过这些都是"锦上添花"。对于朋友的公益服务来说,现在这个版本已经能挡住大部分恶意流量了。

写在最后

这个项目大概花了一个晚上的时间。如果是以前,用C写这类东西,光是调库、处理边界条件就得折腾好久。现在有了Rust的生态和AI的加持,从想法到可用的原型,速度比以前快太多了。

技术的进步,本质上是让开发者能把更多精力放在解决问题上,而不是在工具本身的折腾上。Rust解决了内存安全的问题,AI解决了语法和样板代码的问题。

这就是Vibe Coding时代——你只需要说"我要做什么",剩下的"怎么做",让AI和工具来帮你。

如果你也遇到类似的流量攻击问题,可以参考这个思路实现自己的版本。核心逻辑其实不复杂,关键是理解TUN设备的原理和网络包的结构。

代码量不多,几百行Rust,但解决问题刚刚好。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
King
King
0x56af...a0dd
擅长Rust/Solidity/FunC/Move开发