Noeiov0.2.0
功能介绍

子网路由

让一个节点把所在局域网的网段通告给整个网络,子网内的主机无需安装客户端

一个 noeio 节点可以充当子网路由器:把它所在局域网的一个或多个网段通告给整个网络,其他节点访问这些网段的流量就会经由它转发。局域网里的主机不需要安装任何客户端。这相当于 Tailscale 的 --advertise-routes / --accept-routes,或 WireGuard 的 AllowedIPs。

   Host-B(无客户端)                        Noeio E
   192.168.10.7                             accept_routes = true
        |                                        |
   [ 192.168.10.0/24 ]                           |
        |                                        |
   Noeio A (Linux)  ---- overlay (WG/UDP) -----+
   advertise_routes = ["192.168.10.0/24"]

   在 E 上:curl http://192.168.10.7  -> 经 A 转发,并 SNAT 成 A 的局域网地址

两种角色

角色作用支持平台
通告端(Advertiser)声明自己负责转发的网段,把流量转发并 SNAT 进局域网仅 Linux
消费端(Consumer)为其他节点通告的网段安装路由,流量走隧道过去Linux、macOS、Windows

角色是按网段划分的,不是按节点:一个 Linux 节点可以同时通告 192.168.10.0/24,并接受另一个节点通告的 10.0.0.0/8。

macOS 和 Windows 不能做通告端。 配置文件或命令行里给出非空的 advertise_routes 会让 daemon 在启动时直接退出,并打印平台名与被拒绝的网段;noeio route advertise 会返回 FAILED_PRECONDITION,daemon 继续运行。两种情况下该网段都不会进入广播——这是故意的设计:如果只是警告但仍然通告,所有消费端访问这个子网都会变成黑洞,而消费端一侧看不出任何原因。这两个平台仍然是完整的消费端。

开启方式

两端要做的事不同,需要分别配置。以上面的拓扑为例,Noeio A 是通告端,Noeio E 是消费端。

通告端:局域网里那台 Linux 节点

[router]
# 本节点负责转发的网段。前缀长度必须在 /8 到 /32 之间。
advertise_routes = ["192.168.10.0/24"]
# 自动管理 ip_forward 与 nftables 表。
auto_nat = true
# 局域网出口网卡;留空表示自动选取地址落在第一个通告网段内的网卡。
lan_interface = ""

等效的命令行参数:

noeio boot --advertise-routes 192.168.10.0/24

消费端:需要访问这个局域网的节点

[router]
# 安装其他节点通告的子网路由。
accept_routes = true

等效的命令行参数:

noeio boot --accept-routes

字段含义见配置文件。

因为角色是按网段划分的,同一个节点确实可以两个字段都填——比如一台 Linux 节点通告自己的局域网,同时又要访问别人通告的网段。但更常见的情况是这两件事发生在两台不同的机器上。

daemon 运行期间还可以用 noeio route 随时增删通告的网段,不需要重启,命令与参数见管理子网路由。

不能通告的网段

以下网段在通告端会被明确报错拒绝,在消费端则会被静默忽略不安装(两侧都做校验):

  • 前缀短于 /8 的网段,包括 0.0.0.0/0——出口节点(exit node)暂不在范围内
  • 包含本节点 overlay 地址的网段
  • 包含已配置的 derper 或 STUN 服务器地址的网段,否则会切断控制平面
  • 127.0.0.0/8、169.254.0.0/16

带主机位的 CIDR(如 192.168.10.7/24)会被规范化成网络地址,并打印一条警告。

消费端的冲突处理

消费端学到一个网段时,按以下优先级处理:

  1. 与消费端某张物理网卡所在的子网重叠 → 拒绝。本地局域网优先。
  2. 消费端自己也在通告这个网段 → 拒绝,因为它本身就是出口。
  3. 命中上面的黑名单 → 拒绝。
  4. 多个节点通告同一个网段 → peer_id 最小的那个为 active,其余为 standby。active 的节点消失后由下一个接替。
  5. 不同节点通告嵌套的网段(如 10.0.0.0/8 与 10.1.0.0/16)→ 两条都安装,按最长前缀匹配决定走哪条。

撤销是怎么生效的

路由不是被命令式地删除的。daemon 根据 peer 表维护一份期望路由集合,由一个 reconciler 在每次变更时和每 30 秒的定时 tick 上把内核路由表收敛到这份集合——缺什么补什么,多什么删什么。所以撤销有三条路径:

  1. 显式撤销:通告端发出的新 PeerInfo 里不再包含该网段(改配置或执行 noeio route withdraw)。做完整 diff,路由随之消失。
  2. 节点消失:derper 发现某个节点停止上报(1 分钟 TTL),广播一条 tombstone(withdrawn = true)。消费端丢弃该节点及它通告的一切。这覆盖了崩溃和断电——这类情况消费端自己无法察觉,因为一个配置稳定的健康节点同样不会发送任何更新(上报是去重的)。
  3. 定时 tick:周期性的 reconcile 会修复偏移,比如有人手动执行了 ip route del。

reconciler 从不把“距上次更新的时间”当作存活信号。

崩溃后残留什么

状态kill -9 之后清理方式
经由 TUN 的路由随网卡一起被内核回收(TUN 非持久化)另在 /var/run/noeio/routes 留一份镜像兜底,正常退出时删除
net.ipv4.ip_forward(通告端)保持被修改的状态原值存在 /var/run/noeio/ip_forward.orig,下次启动和正常退出时恢复
nftables 表 ip noeio(通告端)保留下次启动和正常退出时删除;整表替换保证幂等

/var/run 是 tmpfs,所以重启后是干净状态。第一行成立的前提是 TUN 必须保持非持久化——不要用 ip tuntap add 创建它,也不要设置 IFF_PERSIST。

通告端的 Linux 要求

通告端通过 netlink 安装 nftables 规则(不需要 nft 或 iptables 命令),并开启 net.ipv4.ip_forward。回包由 conntrack 解 NAT,沿着已有的指向消费端 overlay 地址的 /32 路由返回。

要求 Linux ≥ 4.18、daemon 所在网络命名空间具备 CAP_NET_ADMIN,以及内核选项 NF_TABLES、NF_TABLES_IPV4、NF_TABLES_NAT、NFT_MASQ、NFT_CT。容器里无法自动加载 NAT 模块,需要先在宿主机执行 modprobe nft_masq nf_nat nft_ct。报错信息会区分这两种情况。

ip_forward 是主机全局的。 打开它意味着这台主机会在它所有网卡之间转发 IPv4 流量,不只是 noeio 的流量。daemon 在修改时会打印一条警告。想自己管理就把 auto_nat 设为 false。

TUN 的 MTU 是 1411。经过通告端的 TCP SYN 会把 MSS 钳制到路径 MTU,这样局域网里 MTU 1500 的主机就不会发出隧道装不下的报文——正是这一步避免了“ping 通但大文件下载卡死”,这种问题在 ICMP fragmentation-needed 被中途过滤掉时很常见。大于 1411 字节的非 TCP 流量则依赖分片(DF 未置位)或依赖 ICMP 能回到发送方。

与 iptables 共存

nftables 和 iptables-legacy 共用同一套 hook、conntrack 和 NAT 引擎,按优先级顺序执行,互不覆盖。两点需要注意:

  • NAT 是有状态的。 一旦 conntrack 为某条流绑定了 NAT 映射,后面的 NAT 链不会再次转换它。如果已有的 iptables-legacy MASQUERADE 规则先匹配上,noeio 的 masquerade 对这条流就是静默空操作(反之亦然)。这种情况没有日志;SNAT“什么都没做”时,用 conntrack -L / nft list ruleset 检查。
  • 同时跑着 iptables-legacy 和 iptables-nft 的系统最难排查:每个工具只显示自己那一半规则。

noeio 只操作自己的表 ip noeio,从不修改 nat、filter、mangle。

信任模型

  • RPC socket(/var/run/noeio.sock,权限 0600,root)没有认证。任何能连上它的人都可以改写路由表,在 Linux 上还能开启转发与 NAT。文件权限就是边界,权限不是 0600 时 daemon 拒绝提供服务。
  • 路由通告经由 derper 传递。信任一个 derper 就意味着把路由表的写权限交给它——恶意中继可以注入一个网段,把流量拉到它指定的节点。这与已经交给它的 overlay IP 分配权是同一份信任。通告端和消费端都会执行上面的黑名单,所以 derper 无法借此切断某个节点与自己控制平面或局域网的连接。
  • 入站报文按 WireGuard 风格的 AllowedIPs 校验:一个节点只能以自己的 overlay 地址、或它所通告网段内的地址作为源地址。违规会按节点计数并限速打日志。

暂不支持

IPv6 子网;出口节点(0.0.0.0/0);同一网段多个通告端之间的负载均衡(只有主备);从局域网侧主动发起到 overlay 节点的连接(目前只打通 overlay → 局域网,回包靠 conntrack);macOS 作为通告端(需要 pf)。

On this page