跳转到内容

Workspace 与 crate

源码文件:202 个 · 核对版本 Etemenanki 596916d · katana v3.0.1
  • Etemenanki/Cargo.toml
  • Etemenanki/Cargo.lock
  • Etemenanki/.gitmodules
  • Etemenanki/.cargo/config.toml
  • Etemenanki/.github/workflows/build.yml
  • Etemenanki/README.md
  • Etemenanki/concepts/Cargo.toml
  • Etemenanki/environment/Cargo.toml
  • Etemenanki/protocols/Cargo.toml
  • Etemenanki/app/Cargo.toml
  • Etemenanki/app/src/balancer.rs
  • Etemenanki/app/src/config.rs
  • Etemenanki/app/src/connector.rs
  • Etemenanki/app/src/flow.rs
  • Etemenanki/app/src/inbound/mod.rs
  • Etemenanki/app/src/inbound/tun.rs
  • Etemenanki/app/src/instance.rs
  • Etemenanki/app/src/main.rs
  • Etemenanki/app/src/outbound/freedom.rs
  • Etemenanki/app/src/outbound/mod.rs
  • Etemenanki/app/src/outbound/proxy.rs
  • Etemenanki/app/src/outbound/udp_fanout.rs
  • Etemenanki/app/src/router.rs
  • Etemenanki/app/src/serve.rs
  • Etemenanki/app/src/transport.rs
  • Etemenanki/concepts/src/buffer.rs
  • Etemenanki/concepts/src/client.rs
  • Etemenanki/concepts/src/core.rs
  • Etemenanki/concepts/src/lib.rs
  • Etemenanki/concepts/src/link.rs
  • Etemenanki/concepts/src/net.rs
  • Etemenanki/concepts/src/relay.rs
  • Etemenanki/concepts/src/runtime.rs
  • Etemenanki/concepts/src/sniff.rs
  • Etemenanki/concepts/src/wake.rs
  • Etemenanki/environment/src/dial/mod.rs
  • Etemenanki/environment/src/dial/quic.rs
  • Etemenanki/environment/src/dial/socket.rs
  • Etemenanki/environment/src/dial/tcp.rs
  • Etemenanki/environment/src/dial/udp.rs
  • Etemenanki/environment/src/lib.rs
  • Etemenanki/environment/src/routing.rs
  • Etemenanki/protocols/src/core/harness.rs
  • Etemenanki/protocols/src/core/mod.rs
  • Etemenanki/protocols/src/dns/message.rs
  • Etemenanki/protocols/src/dns/mod.rs
  • Etemenanki/protocols/src/error.rs
  • Etemenanki/protocols/src/flow.rs
  • Etemenanki/protocols/src/helpers/address.rs
  • Etemenanki/protocols/src/helpers/address_family.rs
  • Etemenanki/protocols/src/helpers/crypto.rs
  • Etemenanki/protocols/src/helpers/mod.rs
  • Etemenanki/protocols/src/helpers/parse.rs
  • Etemenanki/protocols/src/http/codec.rs
  • Etemenanki/protocols/src/http/config.rs
  • Etemenanki/protocols/src/http/core.rs
  • Etemenanki/protocols/src/http/mod.rs
  • Etemenanki/protocols/src/http/protocol.rs
  • Etemenanki/protocols/src/hysteria/auth.rs
  • Etemenanki/protocols/src/hysteria/config.rs
  • Etemenanki/protocols/src/hysteria/connection.rs
  • Etemenanki/protocols/src/hysteria/connector.rs
  • Etemenanki/protocols/src/hysteria/mod.rs
  • Etemenanki/protocols/src/hysteria/obfs.rs
  • Etemenanki/protocols/src/hysteria/protocol.rs
  • Etemenanki/protocols/src/hysteria/quic.rs
  • Etemenanki/protocols/src/hysteria/server/authenticator.rs
  • Etemenanki/protocols/src/hysteria/server/config.rs
  • Etemenanki/protocols/src/hysteria/server/datagrams.rs
  • Etemenanki/protocols/src/hysteria/server/endpoint.rs
  • Etemenanki/protocols/src/hysteria/server/inbound.rs
  • Etemenanki/protocols/src/hysteria/server/io.rs
  • Etemenanki/protocols/src/hysteria/server/masquerade.rs
  • Etemenanki/protocols/src/hysteria/server/mod.rs
  • Etemenanki/protocols/src/hysteria/server/shim.rs
  • Etemenanki/protocols/src/hysteria/slot.rs
  • Etemenanki/protocols/src/lib.rs
  • Etemenanki/protocols/src/macros.rs
  • Etemenanki/protocols/src/mux/demux.rs
  • Etemenanki/protocols/src/mux/frame.rs
  • Etemenanki/protocols/src/mux/mod.rs
  • Etemenanki/protocols/src/sniff/collector.rs
  • Etemenanki/protocols/src/sniff/http.rs
  • Etemenanki/protocols/src/sniff/mod.rs
  • Etemenanki/protocols/src/sniff/tls.rs
  • Etemenanki/protocols/src/socks/codec.rs
  • Etemenanki/protocols/src/socks/config.rs
  • Etemenanki/protocols/src/socks/handshake.rs
  • Etemenanki/protocols/src/socks/mod.rs
  • Etemenanki/protocols/src/socks/protocol.rs
  • Etemenanki/protocols/src/socks/server.rs
  • Etemenanki/protocols/src/socks/udp_link.rs
  • Etemenanki/protocols/src/ss_2022/codec.rs
  • Etemenanki/protocols/src/ss_2022/core.rs
  • Etemenanki/protocols/src/ss_2022/crypto.rs
  • Etemenanki/protocols/src/ss_2022/mod.rs
  • Etemenanki/protocols/src/ss_2022/protocol.rs
  • Etemenanki/protocols/src/ss_2022/users.rs
  • Etemenanki/protocols/src/ss_legacy/aead.rs
  • Etemenanki/protocols/src/ss_legacy/codec.rs
  • Etemenanki/protocols/src/ss_legacy/core.rs
  • Etemenanki/protocols/src/ss_legacy/mod.rs
  • Etemenanki/protocols/src/ss_legacy/protocol.rs
  • Etemenanki/protocols/src/ss_legacy/users.rs
  • Etemenanki/protocols/src/transports/accept.rs
  • Etemenanki/protocols/src/transports/connect.rs
  • Etemenanki/protocols/src/transports/grpc/framing.rs
  • Etemenanki/protocols/src/transports/grpc/liveness.rs
  • Etemenanki/protocols/src/transports/grpc/mod.rs
  • Etemenanki/protocols/src/transports/grpc/settings.rs
  • Etemenanki/protocols/src/transports/grpc/stream.rs
  • Etemenanki/protocols/src/transports/keepalive.rs
  • Etemenanki/protocols/src/transports/mod.rs
  • Etemenanki/protocols/src/transports/stream.rs
  • Etemenanki/protocols/src/transports/tls/config.rs
  • Etemenanki/protocols/src/transports/tls/mod.rs
  • Etemenanki/protocols/src/transports/tls/stream.rs
  • Etemenanki/protocols/src/transports/ws/endpoint.rs
  • Etemenanki/protocols/src/transports/ws/mod.rs
  • Etemenanki/protocols/src/transports/ws/stream.rs
  • Etemenanki/protocols/src/trojan/codec.rs
  • Etemenanki/protocols/src/trojan/core.rs
  • Etemenanki/protocols/src/trojan/mod.rs
  • Etemenanki/protocols/src/trojan/protocol.rs
  • Etemenanki/protocols/src/trojan/users.rs
  • Etemenanki/protocols/src/tun/config.rs
  • Etemenanki/protocols/src/tun/device.rs
  • Etemenanki/protocols/src/tun/inbound.rs
  • Etemenanki/protocols/src/tun/mod.rs
  • Etemenanki/protocols/src/tun/tracked.rs
  • Etemenanki/protocols/src/tun/udp.rs
  • Etemenanki/protocols/src/vless/codec.rs
  • Etemenanki/protocols/src/vless/config.rs
  • Etemenanki/protocols/src/vless/core.rs
  • Etemenanki/protocols/src/vless/mod.rs
  • Etemenanki/protocols/src/vless/protocol.rs
  • Etemenanki/protocols/src/vless/validator.rs
  • Etemenanki/protocols/src/vmess/accounts.rs
  • Etemenanki/protocols/src/vmess/aead.rs
  • Etemenanki/protocols/src/vmess/codec.rs
  • Etemenanki/protocols/src/vmess/core.rs
  • Etemenanki/protocols/src/vmess/framing.rs
  • Etemenanki/protocols/src/vmess/keys.rs
  • Etemenanki/protocols/src/vmess/mod.rs
  • Etemenanki/protocols/src/vmess/protocol.rs
  • Etemenanki/protocols/src/vmess/session.rs
  • Etemenanki/protocols/src/wireguard/config.rs
  • Etemenanki/protocols/src/wireguard/connector.rs
  • Etemenanki/protocols/src/wireguard/device.rs
  • Etemenanki/protocols/src/wireguard/mod.rs
  • Etemenanki/protocols/src/wireguard/slot.rs
  • Etemenanki/concepts/tests/runtime.rs
  • Etemenanki/concepts/tests/client.rs
  • Etemenanki/environment/tests/integration.rs
  • Etemenanki/environment/tests/integration/quic.rs
  • Etemenanki/environment/tests/unit/routing.rs
  • Etemenanki/protocols/tests/pipeline.rs
  • Etemenanki/protocols/tests/pipeline/hysteria.rs
  • Etemenanki/protocols/tests/pipeline/socks.rs
  • Etemenanki/protocols/tests/unit/socks/protocol.rs
  • Etemenanki/protocols/tests/unit/socks/server.rs
  • Etemenanki/protocols/tests/unit/vless/protocol.rs
  • Etemenanki/protocols/tests/unit/dns/message.rs
  • Etemenanki/protocols/tests/unit/hysteria/protocol.rs
  • Etemenanki/protocols/tests/unit/mux/frame.rs
  • Etemenanki/protocols/tests/unit/sniff/tls.rs
  • Etemenanki/app/tests/integration.rs
  • Etemenanki/app/tests/support/mod.rs
  • Etemenanki/app/tests/integration/e2e_tun.rs
  • Etemenanki/app/tests/integration/e2e_wg.rs
  • Etemenanki/app/tests/integration/e2e_xray_mux.rs
  • katana/Cargo.toml
  • katana/Cargo.lock
  • katana/.cargo/config.toml
  • katana/.gitmodules
  • katana/.github/workflows/build.yml
  • katana/.github/workflows/release.yml
  • katana/src/api/mod.rs
  • katana/src/api/newv2board.rs
  • katana/src/api/sspanel.rs
  • katana/src/config.rs
  • katana/src/connector.rs
  • katana/src/inbound.rs
  • katana/src/main.rs
  • katana/src/manager/mod.rs
  • katana/src/manager/node.rs
  • katana/src/manager/proxy.rs
  • katana/src/manager/transport.rs
  • katana/src/meter.rs
  • katana/src/outbound/freedom.rs
  • katana/src/outbound/mod.rs
  • katana/src/outbound/proxy.rs
  • katana/src/router.rs
  • katana/src/rule.rs
  • katana/src/runtime.rs
  • katana/src/serve.rs
  • katana/src/traffic.rs
  • katana/tests/integration.rs
  • katana/tests/integration/xray_interop.rs
  • katana/tests/integration/hysteria_interop.rs
  • katana/tests/integration/sniff.rs
  • katana/tests/support/mod.rs

代码分布在两个私有仓库中。Etemenanki 是一个包含四个 crate 的 Cargo workspace:其中三个库 crate 共同构成代理内核,另一个是基于它们构建的独立二进制 etemenanki-app。katana 是一个单独的仓库,它的二进制是面板节点 agent,从私有 Cargo registry 获取这三个库 crate。第三个仓库 harranu 存放路由模型,内核现在带有它的一份副本。

动手修改任何代码之前,应先读这份地图。本页涵盖:每个 crate 负责什么,feature 和依赖如何在 crate 之间传递,每个源文件的用途,决定所有解析代码写法的 lint,2.0 版本如何走到单一流水线的设计,以及一次修改必须通过的验证门槛。Concepts、Environment、Protocols 和 App 下的页面再分别深入每个 crate。

  • 文件夹Etemenanki/
    • Cargo.toml workspace 清单和共享的依赖版本
    • Cargo.lock
    • .cargo/config.toml 声明私有 registry
    • .github/workflows/build.yml
    • 文件夹concepts/ etemenanki-concepts
      • …
    • 文件夹environment/ etemenanki-environment
      • …
    • 文件夹protocols/ etemenanki-protocols
      • …
    • 文件夹app/ etemenanki-app,独立二进制
      • …
    • 文件夹Xray-core/ git submodule,仅用于参考和互操作测试
      • …
    • 文件夹hysteria/ git submodule,仅用于参考和互操作测试
      • …
  • 文件夹katana/
    • Cargo.toml 按版本依赖三个库 crate
    • Cargo.lock
    • .cargo/config.toml 声明私有 registry
    • 文件夹.github/workflows/ build.yml 和 release.yml
      • …
    • 文件夹src/
      • …
    • 文件夹tests/
      • …
    • 文件夹Xboard/ 面板参考源码树,不参与构建
      • …
    • 文件夹V2bX/ 面板参考源码树,不参与构建
      • …
    • 文件夹XrayR/ 面板参考源码树,不参与构建
      • …
仓库 构建产物 依赖来源
Etemenanki etemenanki-concepts、etemenanki-environment 和 etemenanki-protocols(库,发布到私有 Cargo registry),以及 etemenanki-app(二进制) 只有 crates.io 和它自己的 path 依赖
katana katana 二进制 来自私有 registry 的三个库 crate:etemenanki-concepts 和 etemenanki-environment 为 2.0.0,etemenanki-protocols 为 2.0.1
harranu 一个独立的路由模型 crate 内核和 katana 都已不再依赖它

workspace 清单中列出的恰好是 members = ["app", "concepts", "environment", "protocols"]。检出目录中的其他内容,包括各个 submodule,对 Cargo 都不可见。

Xray-core/(上游为 XTLS/Xray-core)和 hysteria/(上游为 HyNetworks/hysteria)是在 .gitmodules 中声明的 git submodule,其中没有任何内容会被 Cargo 编译。它们有两个用途:

  • 移植参考。 大多数协议模块都是移植而来,模块文档会写明它参照的 Go 文件:trojan/protocol.rs 移植自 proxy/trojan/protocol.go,mux/frame.rs 移植自 common/mux/{frame.go,reader.go,writer.go},hysteria/obfs.rs 移植自 hysteria/extras/obfs/salamander.go,而 hysteria/PROTOCOL.md 是 Hysteria 2 两端实现共同依据的规范。Shadowsocks 2022 移植自 sing-shadowsocks/shadowaead_2022,该仓库没有 vendored 进来。
  • 互操作测试。 app/tests/support/mod.rs 在首次使用时构建上游二进制:在 Xray-core/ 中执行 go build -o $CARGO_TARGET_TMPDIR/xray ./main,在 hysteria/app 中执行 go build。构建结果保存在 LazyLock 静态变量 XRAY_BIN 和 HYSTERIA_BIN 中,因此每个测试二进制对每个上游最多只构建一次。如果没有 go 或构建失败,辅助函数会打印一行 SKIP: 并返回 None,所有需要该二进制的测试都会提前返回并判为通过。katana 的 tests/support/mod.rs 从同级检出目录 ../Etemenanki/Xray-core 和 ../Etemenanki/hysteria 构建同样的两个二进制,该目录不存在时同样跳过。

katana 的 Xboard/、V2bX/ 和 XrayR/ submodule 在面板兼容性方面起同样的参考作用,不会被构建。

路由模型(first-match 规则表,域名、CIDR 和端口匹配条件,GeoIP 和 GeoSite .dat 加载)迁移过两次:先从 app 和 katana 各自的本地副本迁入共享的 harranu crate,再从 harranu 迁入 etemenanki-environment:

步骤 仓库 Commit
app 删除本地副本,改为依赖已发布的 harranu crate Etemenanki 58ad56c Extract route model into shared harranu crate, drop patch hack
katana 在同一天做了同样的事 katana 90c5e35 Replace vendored route model with the shared harranu crate
harranu 0.3 原样 vendored 为 etemenanki_environment::routing Etemenanki fc40d1c add etemenanki-environment: host dialers and the vendored route model
katana 改用内核中的副本做路由 katana e9bc640 route with the kernel’s route model instead of harranu

最后一次迁移的原因记录在 fc40d1c 中:从私有 registry 依赖 harranu,会让 workspace 在无法访问该 registry 的地方无法构建。对贡献者而言,这意味着路由相关的修改要落在 environment/src/routing.rs。修改 harranu 既影响不到 app,也影响不到 katana。路由模型本身见路由模型。

依赖只有一个方向:从二进制指向 etemenanki-concepts。每个箭头都是清单中声明的一个依赖;标签是依赖方 crate 开启的 feature。

flowchart BT
  concepts["etemenanki-concepts"]
  environment["etemenanki-environment"]
  protocols["etemenanki-protocols"]
  app["etemenanki-app(二进制)"]
  katana["katana(二进制,独立仓库)"]
  environment --> concepts
  protocols --> concepts
  protocols --> environment
  app --> concepts
  app --> environment
  app -->|"hysteria, tun"| protocols
  katana --> concepts
  katana --> environment
  katana -->|"hysteria, vendored-openssl"| protocols

在 workspace 内部,每个内部依赖都带有三个键:一个 path(例如 ../concepts),使 workspace 构建使用本地源码;以及 version = "2.0.0" 和指向私有 registry 的 registry,cargo publish 会用它们替换已发布清单中的 path。每个 crate 还把 publish 设为只允许该 registry,因此任何 crate 都不会意外发布到 crates.io。

四个 crate 在 054cf34(“release etemenanki 2.0.0”)中以 2.0.0 一起发布,etemenanki-environment 也在这次发布中首次发布,版本号相同。此后只有 etemenanki-protocols 有过变动,发布了两个 patch 版本,都列在 2.0 重构一节中:2.0.1(2f1f8cb,“release etemenanki-protocols 2.0.1”)包含 WireGuard 和 mux 修复;2.0.2(596916d,“release etemenanki-protocols 2.0.2”)把 SOCKS5 UDP 关联限定在其控制连接的客户端上。2.0.2 没有改变任何公开 API:UDP ASSOCIATE 声明的来源通过 crate 私有的 handshake_with_udp_source 传给 SocksInbound,公开的 handshake 签名保持不变。etemenanki-concepts、etemenanki-environment 和 etemenanki-app 仍为 2.0.0。内部依赖要求仍写作 version = "2.0.0",2.0.2 满足这一要求,workspace 的 Cargo.lock 中记录的 etemenanki-protocols 为 2.0.2。

没有任何 crate 声明 default feature 集,因此下面每个 feature 都需要显式开启。

Feature Crate 开启的内容 由谁开启
hysteria etemenanki-protocols dep:quinn、dep:h3、dep:h3-quinn、dep:rustls、dep:rustls-native-certs、dep:rustls-pemfile、dep:rustls-pki-types、dep:blake2;编译 protocols::hysteria etemenanki-app、katana
tun etemenanki-protocols dep:ipstack、dep:tun-rs、dep:rtnetlink(仅限 Linux 的 target 依赖);在 #[cfg(all(feature = "tun", unix))] 下编译 protocols::tun etemenanki-app
vendored-openssl etemenanki-protocols openssl/vendored:从源码构建 OpenSSL 并静态链接 katana
quic etemenanki-environment dep:quinn;编译 dial::quic(QuicDialer、SocketWrap) 没有 crate 开启;只有 --all-features 或显式的 --features quic

原因记录在各个清单中。hysteria 默认关闭,因为它“pulls in a whole second TLS stack (quinn -> rustls)”,只需要经典协议的 downstream 不应在下一次版本升级时被动继承它。tun 关闭的理由相同:网络接口管理的依赖栈不应进入从不使用它的 crate。katana 开启 hysteria 是因为它要服务 Hysteria 2 节点,开启 vendored-openssl 是因为它的 release 二进制不能动态链接 OpenSSL(见 CI)。

protocols::hysteria 直接在 quinn 之上构建 QUIC endpoint,并不开启 etemenanki-environment/quic,因此在已核对的版本中,没有任何生产构建会编译 QuicDialer。

katana Cargo.toml 中的依赖 版本要求 Feature Cargo.lock 中的解析结果
etemenanki-concepts version = "2.0.0",私有 registry 无 2.0.0,记录了 registry 来源和校验和
etemenanki-environment version = "2.0.0",私有 registry 无 2.0.0
etemenanki-protocols version = "2.0.0",私有 registry vendored-openssl、hysteria 2.0.1

裸写的 "2.0.0" 是 caret 版本要求:任何不低于 2.0.0 的 2.x 版本都满足它,因此 etemenanki-protocols 无需修改清单就解析到了 2.0.1。katana v3.0.1 尚未采用 2.0.2,它的 lockfile 仍记录 2.0.1。确切版本由 Cargo.lock 锁定,其中记录了每个 crate 的 registry 来源和校验和;两个 CI workflow 都使用 --locked 构建,因此构建会直接失败,而不会解析到另一个内核版本。要把 katana 升级到新的内核版本,用 cargo update -p <crate> --precise <version> 一次更新一个 package,并确认 lockfile 中没有其他内容发生变化。

两个仓库都在 .cargo/config.toml 中声明该 registry,并使用 cargo:token 凭据提供方。token 从不出现在仓库中,CI 通过环境变量传入。

设置 值 位置
Edition 2024 [workspace.package],被每个 crate 继承
Resolver 3 [workspace]
共享版本 一张 [workspace.dependencies] 表;成员写 tokio.workspace = true Cargo.toml
Release profile lto = true Etemenanki Cargo.toml
Release profile(katana) lto = true、codegen-units = 4 katana Cargo.toml

etemenanki-concepts 中的三个 trait 是其他所有 crate 接入的接缝。详见服务端协议核心、服务端运行时和Link、connector 与网络类型。

concepts/src/link.rs → Connector 和 DatagramLink:出站是什么,以及如何打开一个出站。

pub enum Outbound<S, D> {
Stream(S),
Datagram(D),
}
pub trait Connector<Target> {
type Stream: AsyncRead + AsyncWrite + Unpin;
type Datagram: DatagramLink;
type Future: Future<Output = io::Result<Outbound<Self::Stream, Self::Datagram>>>;
fn connect(&mut self, target: Target) -> Self::Future;
}
pub trait DatagramLink: Unpin {
type Addr;
fn poll_send_to(
&mut self,
cx: &mut Context<'_>,
buf: &[u8],
to: &Self::Addr,
) -> Poll<io::Result<usize>>;
fn poll_recv_from(
&mut self,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<Self::Addr>>;
}

concepts/src/core.rs → ProxyCoreDecode:协议的服务端,实现为一个 sans-I/O 状态机。

pub trait ProxyCoreDecode {
type Key: Copy + Ord + Send + Sync + 'static;
type Target;
type Error;
type TransportAddr: Clone + Send + Sync + 'static;
const STAGING_RESERVE: usize;
const MAX_DATAGRAM: usize = 4096;
fn handle(
&mut self,
event: Event<'_, Self>,
effects: &mut Effects<'_, Self>,
) -> Result<usize, Self::Error>;
fn held(&self) -> &[u8] { .. }
}

concepts/src/runtime.rs → ProxyServerRuntime:每条连接的驱动器,把协议核心、它的传输层和一个 connector 连在一起。

pub struct ProxyServerRuntime<const BUF_SIZE: usize, Core, Trans, Conn, Mode = ProxyRunsQuiet>
where
Core: ProxyCoreDecode,
Conn: Connector<Core::Target>,
{ .. }
impl<const BUF_SIZE: usize, Core, T, Conn>
ProxyServerRuntime<BUF_SIZE, Core, StreamTransport<T>, Conn, ProxyRunsQuiet>
where
Core: ProxyCoreDecode,
T: AsyncRead + AsyncWrite + Unpin,
Conn: Connector<Core::Target>,
{
pub fn new(transport: T, core: Core, connector: Conn) -> Self { .. }
}
impl<const BUF_SIZE: usize, Core, D, Conn>
ProxyServerRuntime<BUF_SIZE, Core, DatagramTransport<D>, Conn, ProxyRunsQuiet>
where
Core: ProxyCoreDecode,
D: DatagramLink<Addr = Core::TransportAddr>,
Conn: Connector<Core::Target>,
{
pub fn over_datagrams(link: D, core: Core, connector: Conn) -> Self { .. }
}

new 包装一条字节流,over_datagrams 包装一个 DatagramLink;两者都断言 BUF_SIZE > Core::STAGING_RESERVE,因为缓冲区再小就永远腾不出读取的空间。showing_progress 把安静的 Future 变成下文 tokio-stream 一行所述、产出 Traffic 报告的 Stream。

protocols/src/flow.rs → Flow:etemenanki-protocols 中每个服务端协议核心的 Target,因而也是 app 和 katana 的 connector 收到的东西。

pub struct Flow<T> {
pub destination: Destination,
pub user: NetworkUser<T>,
pub sniffed: Option<SniffedBehavior>,
pub source: Option<IpAddr>,
}

在已核对的版本中,各 trait 的实现者如下:

Trait 实现
ProxyCoreDecode TrojanCore、VlessCore、VMessCore、ShadowsocksCore、Ss2022Core、HttpCore、PassthroughCore、Hy2StreamCore、Hy2UdpCore、TunUdpCore。SOCKS 是例外:socks/server.rs → SocksInbound 自己驱动控制连接。
ProxyCoreEncode(客户端 codec) TrojanStream、VlessStream、VMessStream、SsStream、Ss2022Stream、SocksConnect、HttpConnect 和 NoCodec
ProxyCoreEncodeDatagram TrojanDatagram、VlessDatagram、VMessDatagram 和 NoCodec
Connector<Target> concepts:SocketConnector、ProxyClientConnector、所有 FnMut(Target) -> Fut 闭包;environment:Dialer(针对 DialTarget 和 SocketTarget);protocols:TransportConnector、WgConnector、Hy2Connector;app:AppConnector、FreedomConnector;katana:KatanaConnector
DatagramLink concepts:UdpSocket、UdpOutbound、NoDatagram、ProxyClientRuntime;environment:DualStackUdp;protocols:SocksUdpLink、WgDatagramLink、Hy2DatagramLink、QuicDatagrams、TunUdpLink;app:FanOutLink、ResolvingUdp、OutboundDatagram、BlackholeLink;katana:FanOut、ResolvingUdp、OutboundDatagram
依赖 Crate 用途
tokio(full) 全部 运行时、socket 和定时器。每个 crate 的 dev-dependencies 都加上了 test-util,用于 start_paused 测试,因为空闲和 keepalive 的截止时间长达数分钟。
tokio-stream concepts、protocols、app Stream trait:在 ProxyShowsProgress 模式下,ProxyServerRuntime 不是 Future,而是一个 Stream,其元素是 Result<Traffic, RuntimeError<_>> 形式的进度报告。
tokio-util protocols、app CancellationToken:app 把一个 generation 的所有任务都挂在同一个 token 下(serve.rs → spawn_scoped),Hysteria 2 和 TUN 入站也用 token 停止各自的任务。AbortOnDropHandle 把一个后台任务绑定到拥有它的值上:拨号得到的 gRPC stream 的 HTTP/2 驱动,以及 Hysteria 2 客户端连接的 HTTP/3 驱动和数据报泵。
futures protocols tokio-tungstenite socket 之上的 Sink 和 Stream trait(ws/stream.rs),以及一个 Shared future,让并发调用方等待同一次 Hysteria 2 连接构建(hysteria/slot.rs)。
pin-project concepts relay.rs 中手写 future 的 pin 投影。
compact_str、smallvec concepts 及以上 Remote::Domain 持有一个 CompactString;EffectList 是带 INLINE_EFFECTS = 4 个内联槽位的 SmallVec,因此典型的事件不做任何分配。
parking_lot concepts、protocols、app 持有时间短、从不跨 await 的锁:wake.rs → ReadyQueue、用户表、可重建的连接槽。
arc-swap protocols、app 重载时在运行中的监听器下替换的用户表,例如 vmess/accounts.rs 和 Hysteria 2 的 authenticator。
rand protocols salt、请求填充、VMess 会话密钥和 DNS 查询 ID。
openssl、tokio-openssl protocols 所有基于 TCP 的 TLS 会话:TLS 传输层(transports/tls/config.rs 用 SslAcceptor::mozilla_intermediate_v5 构建服务端,并在两端都把最低版本设为 TLS 1.2),以及 dns/mod.rs 中的 DNS-over-TLS 和 DNS-over-HTTPS 后端。
quinn、h3、h3-quinn、rustls、rustls-native-certs、rustls-pemfile、rustls-pki-types protocols,feature hysteria;quinn 单独也出现在 environment 中,feature quic Hysteria 2 的 QUIC 和 HTTP/3,以及 QUIC 拨号器。引入 rustls 只是因为它是 quinn 唯一的加密后端。
blake2 protocols,feature hysteria Salamander 混淆器使用的哈希。
boringtun、smoltcp protocols(始终引入) WireGuard:boringtun 的 Tunn 运行 WireGuard 协议,smoltcp 是隧道内的用户态 TCP/IP 协议栈。不受 feature 控制。
h2、http protocols h2 承载基于 HTTP/2 的 gRPC 传输(transports/grpc/,以及 transports/accept.rs 中把一条 HTTP/2 连接作为多条 stream 来服务)。http 类型还用于 WebSocket 升级头,以及 Hysteria 2 的 HTTP/3 认证和伪装。
tokio-tungstenite protocols WebSocket 传输层。
httparse protocols http/protocol.rs 中的 HTTP/1.x 请求头和响应头。
ipstack、tun-rs、rtnetlink protocols,feature tun 用户态 IP 协议栈、设备创建,以及 Linux 上的路由安装。
aes、aes-gcm、chacha20poly1305、hkdf、sha1、sha2、md-5、crc32fast、blake3、subtle protocols VMess、Shadowsocks 和 Trojan 的加密算法、KDF 和哈希:md-5 用于 Shadowsocks 的 EVP_BytesToKey、VMess 命令密钥和 VMess ChaCha20-Poly1305 正文密钥;HKDF-SHA1 用于 Shadowsocks AEAD 子密钥;SHA-256 用于 VMess KDF;SHA-224 用于 Trojan 密码哈希;CRC32 用于 VMess auth id 中的校验和;BLAKE3 用于 Shadowsocks 2022 的会话子密钥和身份子密钥。
socket2 environment、protocols 必须在创建之后、bind 或 connect 之前设置的 socket 选项(SocketOptions),以及 TCP keepalive(transports/keepalive.rs)。
cidr、regex、prost environment 路由匹配条件;prost 解码 geoip.dat 和 geosite.dat 中的 protobuf。app 和 katana 也直接使用 cidr。
moka(future) protocols dns/mod.rs 中的 DNS 应答缓存,一个上限为 CACHE_CAPACITY = 8192 个域名的 moka::future::Cache。
notify app、katana 热重载背后的配置文件监视器。
serde、toml、clap、tracing-subscriber app、katana 配置、命令行和日志输出。
reqwest(native-tls-vendored)、serde_json katana 面板 HTTP 客户端及其 JSON 请求体和响应体。在 Linux 上 native-tls 就是 OpenSSL,vendored 变体会静态构建它,与内核的 vendored-openssl 一样。
thiserror、anyhow protocols ProtocolError,以及它不透明的 Other 变体。
uuid concepts、protocols、app VMess 和 VLESS 用户的 UserAuthorization::Uuid。

仅用于开发的依赖:environment 中的 rcgen、rustls 和 rustls-pki-types(为 QUIC 拨号器测试生成证书),protocols 中的 etherparse(为假 TUN 设备构造 IP 数据包),以及 app 中的 openssl、boringtun 和 smoltcp(测试证书和进程内的 WireGuard 对端)。除了带 test-util 的 tokio,katana 唯一的 dev-dependency 是带 vendored 的 openssl,用于为它的 Xray TLS 测试生成自签名证书;vendored 与内核的 vendored-openssl 保持一致,因此两者解析到同一份 OpenSSL 构建。

所有基于 TCP 的 TLS 会话都由 OpenSSL 终结。rustls 只随 quinn 进入构建:要么在 etemenanki-protocols 的 hysteria feature 下,要么在 etemenanki-environment 的 quic feature 下,而后者没有 crate 开启。之所以引入它,是因为 QUIC 把 TLS 嵌入了自己的握手状态机,而 quinn 不提供其他后端。代码构建的两份 rustls 配置都在 protocols::hysteria 中,并且都显式指定了 provider:

let provider = Arc::new(rustls::crypto::ring::default_provider());
let builder = rustls::ClientConfig::builder_with_provider(provider)

即客户端的 hysteria/connection.rs → tls_config 和服务端的 hysteria/server/endpoint.rs → tls_config。当 downstream 在构建中统一进第二个 crypto provider feature 时,普通的 ClientConfig::builder() 和 ServerConfig::builder() 会 panic,因此绝不能使用。服务端配置还固定使用 TLS 1.3:QuicServerConfig 在收到第一个数据包时会对 rustls::quic::ServerConnection::new 调用 unwrap,所以不含 TLS 1.3 的配置会在 accept 循环里 panic,而不是在构建配置时报错。

行数取自已核对的版本,包含文档注释。单元测试放在 src/ 之外(见测试),所以行数只包括代码和文档,唯一的例外是 etemenanki-concepts 中三个小的内联 mod tests 块。

3,889 行。sans-I/O 模型:服务端协议核心、客户端 codec、两种运行时、link 和 connector。它不含任何协议代码,也不依赖任何其他 workspace crate。

文件 行数 职责
lib.rs 44 crate 文档(包括单任务示意图)和模块列表
core.rs 732 协议 trait。服务端:ProxyCoreDecode、Event、Effect、Effects、EffectList。客户端:ProxyCoreEncodeHandshake、ProxyCoreEncode、ProxyCoreEncodeDatagram、Handshake、Reply、Opened、NoCodec
runtime.rs 1,394 ProxyServerRuntime,Transport trait 及 StreamTransport 和 DatagramTransport,Traffic、RuntimeError,ProxyRunsQuiet 和 ProxyShowsProgress 两种模式,WORK_BUDGET
client.rs 654 ProxyClientRuntime(拨号得到的上游之上的一个 codec,以字节流或 DatagramLink 的形式暴露)、ProxyClientConnector、ProxyClientConnecting
link.rs 223 DatagramLink、Outbound、Connector 及其闭包实现、UdpOutbound、SocketConnector、SocketTarget、NoStream、NoDatagram
buffer.rs 269 ReadBuffer 和 WriteBuffer:从不增长的定长装箱数组;以及 Staging:WriteBuffer 空闲尾部的只追加视图,供协议核心写入
wake.rs 180 ReadyQueue 和 KeyWaker:按 key 唤醒,使运行时只轮询已就绪的出站
relay.rs 277 UnidirectionalConnection、BidirectionalConnection、Relayed:直通路径上复制字节的 future
net.rs 94 UserAuthorization、NetworkUser、DialNetwork、Remote、Destination
sniff.rs 22 SniffedProtocol、SniffedBehavior、Sniffer trait

1,363 行。互不依赖的两半:本机如何打开 socket,以及一条流被路由到哪个出站。

文件 行数 职责
lib.rs 40 crate 文档和 lint 策略
dial/mod.rs 100 DialTarget、Dialer,以及它针对 DialTarget 和 SocketTarget 的 Connector 实现
dial/socket.rs 247 SocketOptions(源地址、Interface、packet mark、SocketHook)、AddressFamily,以及在编译期确定的各操作系统支持情况
dial/tcp.rs 109 TcpDialer,在多个地址间尝试的 connect_any,每次尝试的 DEFAULT_CONNECT_TIMEOUT = 10 秒
dial/udp.rs 187 UdpDialer、bind_dual、DualStackUdp
dial/quic.rs 90 feature quic:QuicDialer、SocketWrap
routing.rs 590 vendored 进来的 harranu 路由模型:RouteTarget、RouteMatch、RouteTable、DomainRegex、GeoData、.dat 的 protobuf 消息

拨号器见拨号器与 socket 策略,路由见路由模型。

23,105 行。每个协议都是在上述 trait 之上的一个服务端协议核心加一个客户端 codec;传输层把 socket 变成字节流;共享部分与它们并列。

区域 行数 页面
crate 根、core/、helpers/ 1,487 协议 crate 基础
sniff/ 360 嗅探
dns/ 748 DNS
transports/ 2,356 TCP 与 TLS、WebSocket 与 gRPC
mux/ 929 mux.cool 与 XUDP
socks/、http/ 1,422 和 760 SOCKS、HTTP
trojan/、vless/ 928 和 1,000 Trojan、VLESS
vmess/ 2,876 VMess 加密、VMess 线格式
ss_legacy/、ss_2022/ 1,334 和 1,668 Shadowsocks、Shadowsocks 2022
hysteria/(feature hysteria) 4,739 Hysteria 2 客户端、Hysteria 2 服务端
wireguard/ 1,555 WireGuard
tun/(feature tun,Unix) 943 TUN
文件 行数 职责
lib.rs 38 lint 策略和模块列表,带有 hysteria 和 tun 两个开关
flow.rs 70 Flow,每个协议核心的目标类型
error.rs 72 ProtocolError 及其到 io::ErrorKind 的映射
macros.rs 23 byte_newtype!:用于密钥材料的定长字节 newtype
core/mod.rs 464 FlowKey、SubKey、Phase、Timing、HANDSHAKE_TIMEOUT、RELAY_IDLE_TIMEOUT、SniffPrefix、Passthrough、PassthroughCore
core/harness.rs 125 CoreHarness:手动驱动的运行时替身,用于在没有 socket 的情况下测试协议核心
helpers/address.rs 354 AddressCodec(SOCKS5、Trojan、Shadowsocks、VLESS 和 VMess 使用的带类型字节的地址格式)、parse_authority、format_authority
helpers/address_family.rs 230 AddressFamilyStrategy、FamilySupport、resolve_candidates、destination_to_socketaddrs
helpers/crypto.rs 54 evp_bytes_to_key、hkdf_sha1_ss_subkey、increment_le、ct_eq
helpers/parse.rs 53 take、take_array、need_more:不会 panic 的切片访问(见 Lint)
helpers/mod.rs 4 模块列表
sniff/mod.rs 84 SNIFF_TIMEOUT、SNIFF_LIMIT、sniff、worth_sniffing、plausible_domain
sniff/collector.rs 77 Collector 和 Verdict:累积一条流开头字节的累加器,与时钟无关
sniff/tls.rs 101 TlsSniffer:ClientHello 中的 SNI
sniff/http.rs 98 HttpSniffer:HTTP/1.x 的 Host
dns/mod.rs 545 Resolver、Backend、ResolverSpec,缓存及其 TTL 上下限
dns/message.rs 203 最小化的 A/AAAA 线格式编解码:encode_query、decode_answer
文件 行数 职责
transports/mod.rs 14 模块列表
transports/accept.rs 175 InboundTransport、Accepted、TRANSPORT_HANDSHAKE_TIMEOUT,把一条 HTTP/2 连接作为多条 stream 来服务
transports/connect.rs 195 TransportKind、TransportConnector:解析、连接并包装上游
transports/stream.rs 71 TransportStream:所有传输层产出的唯一字节流类型
transports/keepalive.rs 33 set_keepalive,作用于每个 accept 或拨号得到的 socket
transports/tls/mod.rs 8 模块列表
transports/tls/config.rs 201 基于 OpenSSL 的 ServerConfig、ClientConfig、Alpn、VerifyMode
transports/tls/stream.rs 91 MaybeTlsStream、tcp_from_std、accept_optional_tcp、wrap_optional_tcp
transports/ws/mod.rs 7 模块列表
transports/ws/endpoint.rs 254 WsRoute、WsTarget,path 和 early data 的处理,MAX_EARLY_DATA、MAX_WS_MESSAGE_LEN
transports/ws/stream.rs 378 WsStream:把 WebSocket 消息当作字节流,WS_IDLE_TIMEOUT、WS_KEEPALIVE_INTERVAL
transports/grpc/mod.rs 10 模块列表
transports/grpc/framing.rs 320 encode_hunk、encode_multi_hunk、HunkDecoder
transports/grpc/settings.rs 149 HTTP/2 设置、MAX_GRPC_MESSAGE_LEN、H2_IDLE_TIMEOUT、GrpcPaths、GrpcMode
transports/grpc/liveness.rs 145 Liveness:被服务连接的空闲截止时间和 PING 监督
transports/grpc/stream.rs 305 GrpcStream:把一条 HTTP/2 stream 当作字节流
文件 行数 职责
mux/mod.rs 52 MUX_ADDRESS、MUX_PORT、mux_destination、is_mux_destination
mux/frame.rs 407 mux.cool 帧编解码、FrameMeta、SessionStatus、MAX_META_LEN、MAX_DATA_LEN
mux/demux.rs 470 Demux:Trojan、VLESS 和 VMess 协议核心共用的服务端解复用器;普通承载者用 feed,一次 VMess 读取得到的所有分块用 feed_chunks 一次调用送入
文件 行数 职责
socks/mod.rs 14 模块列表
socks/protocol.rs 249 SOCKS4、4a 和 5 的线格式常量与基础操作;endpoint,即两端比较中继数据报发送方时所用的规范形式 (IpAddr, u16)
socks/handshake.rs 273 直到回复为止的服务端握手:handshake、handshake_with_udp_source(crate 私有;还会返回 UDP ASSOCIATE 指明的来源)、Request、Version、回复写入函数
socks/server.rs 445 SocksInbound:拥有控制连接的专用驱动;ExpectedSender,即一个 UDP 关联唯一接收的那个客户端
socks/codec.rs 154 SocksConnect:SOCKS5 CONNECT 客户端 codec
socks/udp_link.rs 207 SocksUdpLink:把客户端侧的 UDP ASSOCIATE 作为一个 DatagramLink
socks/config.rs 80 SocksServerConfig、SocksAuth
http/mod.rs 11 模块列表
http/protocol.rs 283 请求头和响应头、固定响应、转发请求的改写
http/core.rs 322 HttpCore:CONNECT 隧道和 absolute-form 转发
http/codec.rs 96 HttpConnect:CONNECT 客户端 codec
http/config.rs 48 HttpServerConfig
文件 行数 职责
trojan/mod.rs 11 模块列表
trojan/protocol.rs 332 请求头和 UDP 数据包的分帧、password_hash
trojan/users.rs 89 TrojanServerConfig、Validator
trojan/core.rs 353 TrojanCore
trojan/codec.rs 143 TrojanStream、TrojanDatagram
vless/mod.rs 15 模块列表
vless/protocol.rs 309 请求头和响应头、带长度前缀的 UDP 数据包
vless/validator.rs 77 Validator,即用户表
vless/config.rs 49 VlessServerConfig
vless/core.rs 365 VlessCore
vless/codec.rs 185 VlessStream、VlessDatagram
文件 行数 职责
vmess/mod.rs 18 模块列表
vmess/aead.rs 616 HMAC-SHA256 KDF、AES-128 auth id 编解码、密封的头部信封
vmess/keys.rs 211 带类型的 16 字节密钥材料,使密钥不会被误传到需要 IV 的位置
vmess/accounts.rs 358 Account、AccountValidator:把 auth id 匹配到用户
vmess/protocol.rs 419 请求头和响应头编解码、Security、RequestOptions
vmess/framing.rs 388 ChunkStream、ChunkDecoder:正文分块的分帧
vmess/session.rs 108 OutboundSession:每条连接的密钥和 IV 派生
vmess/core.rs 563 VMessCore
vmess/codec.rs 195 VMessStream、VMessDatagram
文件 行数 职责
ss_legacy/mod.rs 16 模块列表;支持的加密方法
ss_legacy/aead.rs 656 Method、Session、ChunkEncoder、ChunkDecoder、EncryptWriter、DecryptReader
ss_legacy/users.rs 97 ShadowsocksServerConfig、Resolved 密钥表
ss_legacy/protocol.rs 9 共享的 ADDR 编解码
ss_legacy/core.rs 433 ShadowsocksCore
ss_legacy/codec.rs 123 SsStream
ss_2022/mod.rs 13 模块列表
ss_2022/crypto.rs 523 2022-blake3-* 加密方法、session_key、identity_subkey、StreamAead、ChunkWriter、ChunkReader
ss_2022/protocol.rs 381 请求头和响应头的分帧、扩展身份头
ss_2022/users.rs 164 Ss2022ServerConfig、Validator、normalise_psk、decode_psk
ss_2022/core.rs 420 Ss2022Core
ss_2022/codec.rs 167 Ss2022Stream
文件 行数 职责
hysteria/mod.rs 29 模块列表和共享连接模型
hysteria/protocol.rs 821 QUIC varint、TCPRequest 和 TCPResponse、填充、UDP 消息及分片重组
hysteria/auth.rs 177 HTTP/3 认证交互
hysteria/obfs.rs 341 Salamander 数据包混淆
hysteria/quic.rs 66 两端共用的数据报发送:先整条发送,只有对端拒收整条消息时才分片
hysteria/connection.rs 594 Hy2Conn:一条已认证的客户端连接,以及 rustls 客户端配置
hysteria/slot.rs 235 ConnSlot:延迟构建、可重建的共享连接
hysteria/connector.rs 216 Hy2Connector、Hy2Stream、Hy2DatagramLink
hysteria/config.rs 79 Hy2Config、Obfs
hysteria/server/mod.rs 12 模块列表
hysteria/server/inbound.rs 682 Hy2Inbound、Hy2StreamCore
hysteria/server/shim.rs 525 在 HTTP/3 和代理 stream 之间拆分同一条 QUIC 连接
hysteria/server/datagrams.rs 357 QuicDatagrams、Hy2UdpCore
hysteria/server/authenticator.rs 216 Authenticator:判断一个凭据属于哪个用户
hysteria/server/endpoint.rs 131 服务端 QUIC endpoint 及其 rustls 配置
hysteria/server/io.rs 114 QuicIo:把 h3 的 stream 类型包装为 AsyncRead 和 AsyncWrite
hysteria/server/masquerade.rs 82 Masquerade:对非客户端访问者给出的应答
hysteria/server/config.rs 62 ServerConfig、ListenerConfig
文件 行数 职责
wireguard/mod.rs 41 模块文档:为什么 WireGuard 只作为出站
wireguard/config.rs 97 WgConfig、parse_key
wireguard/device.rs 998 WgDevice:唯一的驱动任务,拥有 boringtun 的 Tunn、smoltcp 协议栈和对端 socket;每条连接最多持有一个上行项,CHANNEL_CAP = 256
wireguard/connector.rs 268 WgConnector、WgStream、WgDatagramLink
wireguard/slot.rs 151 DeviceSlot:延迟构建、可重建的隧道
tun/mod.rs 42 模块文档:哪些流量会被丢弃,以及如何让出站路径绕开该设备
tun/config.rs 23 TunConfig、DEFAULT_MTU、DEFAULT_UDP_IDLE_TIMEOUT、DEFAULT_MAX_FLOWS
tun/device.rs 196 DeviceSpec、open、TunDevice
tun/inbound.rs 303 基于 ipstack 的 TunInbound
tun/udp.rs 286 TunUdpLink、TunUdpCore
tun/tracked.rs 93 TrackedTcp:被计数的 stream,使关闭流程可以等待它们结束

4,302 行。一个二进制 crate:没有公共 API,所有模块都是 main.rs 的私有模块。

文件 行数 职责
main.rs 149 CLI(-c/--config、--test)、tracing 设置、带 200 ms 防抖的 notify 监视器、SIGINT/SIGTERM
config.rs 666 TOML schema(处处使用 deny_unknown_fields)、load、parse_bytes、重载时的差异比较
instance.rs 391 Instance、build、Built:generation 与热重载
serve.rs 413 spawn_scoped、StreamListener、accept 循环、run_hysteria_inbound
connector.rs 39 AppConnector:为一条流选路,并在选中的出站上打开它
flow.rs 22 Flow、FlowContext
router.rs 158 build_router、route_target:把 [[route.rule]] 编译成 RouteTable
transport.rs 156 tls_layer、resolve_stream、reject_stream_settings:两个构建器共用的 stream 设置校验
balancer.rs 188 Balancer、Member、Strategy:带健康探测的出站组
inbound/mod.rs 608 BindSpec、StreamInbound、InboundKind、build_inbound
inbound/tun.rs 105 build_tun_inbound、run_tun_inbound
outbound/mod.rs 844 Outbound、build_outbound,各协议客户端的装配
outbound/proxy.rs 215 ProxyClient、OutboundStream、OutboundDatagram、BlackholeLink
outbound/freedom.rs 163 FreedomConnector、ResolvingUdp
outbound/udp_fanout.rs 185 FanOutLink:逐包的 UDP 路由,MAX_SUBS

app 的内部实现见从配置到运行的流水线、入站服务、出站、扇出与负载均衡和Generation 与热重载。

6,460 行,一个二进制 crate。详见 katana 内部结构。

文件 行数 职责
main.rs 54 CLI(-c/--config、--test)
config.rs 324 TOML schema
runtime.rs 439 进程根:出站池、每个 [[node]] 一个 NodeManager、配置监视;重载时先构建所有新增节点,再改动正在运行的节点
api/mod.rs 369 覆盖两种面板类型的 PanelClient、panel_node_type
api/sspanel.rs 576 sspanel(mod_mu)客户端
api/newv2board.rs 452 newV2board(UniProxy)客户端
manager/mod.rs 97 NodeManager → TransportManager → ProxyManager 层级
manager/node.rs 675 每个节点的同步与变化分类;启动引导按 1 秒到 60 秒的退避重试;[node.api] 修改时重建面板客户端
manager/transport.rs 177 一个已绑定的监听器及服务它的组件
manager/proxy.rs 239 一个节点的用户表与准入
inbound.rs 548 按面板节点类型构造入站
serve.rs 447 accept 循环,每条连接一个运行时
connector.rs 361 KatanaConnector:对每条流做准入、路由和审计
router.rs 120 基于 etemenanki_environment::routing 编译 [[route.rule]]
rule.rs 76 目的地审计规则
traffic.rs 472 按用户的流量计数器和限速
meter.rs 141 在流的出站一侧计量
outbound/mod.rs 533 出站池
outbound/proxy.rs 179 通往上游出站的代理客户端
outbound/freedom.rs 181 直连出站

environment/src/lib.rs 和 protocols/src/lib.rs 开头是同一个属性:

#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)]
// 测试使用已知合法的输入,可以随意使用会 panic 的写法。
#![cfg_attr(
test,
allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)
)]

这四个 lint 合起来,禁止了来自网络的一个字节让进程 panic 的常见途径:.unwrap() 和 .expect(),buf[i] 和 buf[a..b],以及任何可能溢出或 panic 的整数运算,包括 usize 上的 +。它们并不覆盖所有 panic(显式的 assert! 或 unreachable! 仍能编译通过),所以只是底线,而不是证明。etemenanki-concepts、etemenanki-app 和 katana 没有这个属性。

仅有的局部例外是 etemenanki-protocols 中的八个函数,它们只重新允许 clippy::arithmetic_side_effects 这一项,每个都附带一个说明边界的 reason:

函数 文件 给出的理由
put_varint、read_varint transports/grpc/framing.rs 对 u64 按常量 7 移位;移位量保持在 64 以下
read_varint、read_varint_slice hysteria/protocol.rs QUIC varint 前缀把累加结果限制在 62 位以内
decode_varint hysteria/server/shim.rs 同样的 62 位上限
deadline transports/grpc/liveness.rs Instant 只有超过单调时钟的终点才会溢出
after transports/ws/stream.rs、socks/server.rs 同样的 Instant 论证

新增例外时沿用同样的形式:一个小函数、一个 lint,以及一个审阅者可以核实的 reason。

cfg_attr(test, …) 这一半很重要,原因在于单元测试的编译方式:它们被挂载到所测试的模块中(见测试),因此在 cfg(test) 下属于 crate 的一部分,否则也会继承这些禁令。

etemenanki-protocols 中的解析器接收一个可能不完整、而且肯定不可信的字节切片,结果只有三种:一个值加上它用掉的字节数,“字节还不够”,或者一个错误。helpers/parse.rs 提供了基本构件:

pub fn take<'a, I>(data: &'a [u8], index: I, what: &'static str) -> Result<&'a [u8], ProtocolError>
where
I: SliceIndex<[u8], Output = [u8]>;
pub fn take_array<const N: usize>(data: &[u8], at: usize) -> Result<[u8; N], ProtocolError>;
pub fn need_more<T>(result: std::io::Result<T>) -> std::io::Result<Option<T>>;
  • take 就是 data.get(index),只是把读取不足映射为 ProtocolError::Truncated(what)。
  • take_array 用 checked_add 计算结束偏移(溢出变为 ProtocolError::Overflow("field offset")),并复制出一个定长数组,这正是 u16::from_be_bytes 一类函数需要的。
  • need_more 把截断(到达 io::Error 时表现为 UnexpectedEof)变为 Ok(None),其他错误仍保持为错误。拿到一个不断增长的缓冲区的解析器,就是靠它表达“等字节更多了再来问我”。

vless/protocol.rs → parse_request_header 展示了由此形成的风格。单个字节通过 first() 和 get() 获取,固定字段一旦可用就立即校验,偏移用 saturating_add 或 checked_add 计算:

pub fn parse_request_header(buf: &[u8]) -> io::Result<Option<(RequestHeader, usize)>> {
let Some(&version) = buf.first() else {
return Ok(None);
};
if version != VERSION {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid vless request version: {version}"),
));
}
let Some(uuid) = need_more(take_array::<16>(buf, 1).map_err(io::Error::from))? else {
return Ok(None);
};
// ……附加信息长度、命令,然后通过 `take(buf, 19.., "vless address")` 读取地址
}

错误来自 error.rs → ProtocolError,它对出错的原因做了分类。它的 From<ProtocolError> for io::Error 实现确定了协议层以上的调用方看到的 io::ErrorKind:

变体 含义 io::ErrorKind
Truncated(&'static str) 输入比协议要求的短 UnexpectedEof
Overflow(&'static str) 对不可信长度或偏移的算术运算溢出 InvalidData
Malformed(&'static str) 某个字段的值是协议不允许的 InvalidData
Unsupported(&'static str) 本实现不提供的功能 InvalidData
Unauthenticated(&'static str) 无法认证对端 PermissionDenied
Crypto(&'static str) 某个 AEAD 或密钥派生步骤失败 Other
Io(io::Error) 底层 I/O 失败 原样透传
Other(anyhow::Error) 任何无法分类的错误 Other

UnexpectedEof 这一映射是承重的:need_more 依靠它区分“等待”和“拒绝”。

解析器测试遵循一个共同模式:编码一条合法消息,在许多位置截断它,并断言不会 panic,且每个前缀要么是“需要更多字节”,要么是错误。例如:

测试 文件 固定的行为
request_header_is_parsed_from_a_slice_once_whole protocols/tests/unit/vless/protocol.rs VLESS 请求头在字段边界处、地址内部以及差一个字节时被截断,结果都是 None;完整的请求头能解析,并报告其确切长度,不动后面的载荷
truncated_and_malformed_input_never_panics protocols/tests/unit/dns/message.rs DNS 应答的每个前缀都能解码而不 panic;超出末尾的 rdlength 是错误
varint_truncated_at_every_boundary、tcp_response_truncated_at_every_boundary protocols/tests/unit/hysteria/protocol.rs Hysteria 2 的 varint 和响应在每个截断点上的行为
rejects_truncated_metadata protocols/tests/unit/mux/frame.rs 元数据被截断的 mux.cool 帧
a_truncated_client_hello_yields_nothing_rather_than_garbage protocols/tests/unit/sniff/tls.rs 在多处被截断的 ClientHello 要么得到真实的 SNI,要么什么也得不到,绝不会得到另一个域名

1.x 版本通过任务和通道搬运字节。2.0 版本用单个任务驱动每条连接,该任务拥有连接的传输层、缓冲区、协议核心以及它打开的每个出站。只有被许多连接共享的承载者才保留自己的驱动任务:承载 gRPC stream 的 HTTP/2 连接、WireGuard 设备,以及 Hysteria 2 的 QUIC 连接。旧流水线被直接删除,没有并存保留,这也是所有 crate 一起升到 2.0.0 的原因。

flowchart LR
  sock["原始 socket"] --> trans["TcpInboundTransport"]
  trans --> vc["VcLink:两条传输 Bytes 的 mpsc 通道"]
  vc --> server["ProxyProtocolServer(tower Service)"]
  server --> circuit["ServerCircuit"]
  circuit --> router["Router"]
  router --> client["ProxyProtocolClient"]
  client --> out["OutboundTransport 拨号"]

传输层把 socket 变成一个 VcLink,即一对传输 Bytes 帧的有界 mpsc 通道。tower 风格的 ProxyProtocolServer 把握手解码成一个 ServerCircuit,OneOrManyTask 在 JoinSet 中承载派生出来的复制循环,多路复用连接则使用一个无界的接收端 stream。每一跳都是一个任务或一个通道。

Etemenanki 历史中迁移的主要步骤,以及此后发布的修复:

Commit 步骤
20d118b rewrite concepts 新的 core、runtime、buffer、wake、link 和 relay 模块;旧模块移入 concepts::legacy
9b7be19 split the proxy server and client traits 服务端使用 ProxyCoreDecode;客户端使用 ProxyCoreEncode 和 ProxyClientRuntime
fc40d1c add etemenanki-environment 作为 Connector 的主机拨号器,以及 vendored 进来的路由模型
a5c225b protocols: move the task-and-channel pipeline under legacy 旧的服务端、客户端和传输层移入 protocols::legacy;共享的编解码代码保持原位
8bad424 … 6b33b3c 逐个协议改成基于切片的解析器和编码器,即新协议核心需要的形态
8653d8f pipeline foundations Flow、Timing、SniffPrefix、PassthroughCore、TransportStream、InboundTransport、TransportConnector
95ea530 … 8300195 每个协议一个 sans-I/O 协议核心和客户端 codec,然后是 SOCKS、Hysteria 2、WireGuard 和 TUN
5c826f4 app: serve every inbound and outbound on the new pipeline app 完成切换;配置 schema、路由和热重载保持不变
44c5109、e79a357 drop the legacy pipeline 删除 protocols::legacy 和 concepts::legacy;tower 从所有 Etemenanki 清单中移除
054cf34 release etemenanki 2.0.0 四个 crate 全部为 2.0.0
6c727ee wireguard: bound the driver’s per-connection uplink WireGuard 驱动对每条连接最多持有一个上行项,并且只在不持有时才读取该连接的通道,因此一条停止消费的连接只会阻塞它自己的生产者,不影响其他连接
53eed3b mux: send each downlink frame once, keep a read’s held frames 每个 mux.cool 下行帧只暂存一次;Demux::feed_chunks 取代 feed_whole,一次调用接收一次 VMess 读取的全部分块
2f1f8cb release etemenanki-protocols 2.0.1 etemenanki-protocols 为 2.0.1;其他三个 crate 仍为 2.0.0
351abcc socks: hold a UDP association to its control connection’s client UDP 关联只接收来自控制连接 IP 的数据报,按规范形式比较;端口由第一个转发的数据报,或由一个指明该 IP 及端口的请求固定下来。没有指明确切来源的 Unix socket 客户端,以及收不到其客户端数据报的中继,都会以 0x02 拒绝。SocksUdpLink 也按同样的规范形式比较回复的来源。由 protocols/tests/pipeline/socks.rs 中的 udp_association_* 和 udp_link_* 测试、protocols/tests/unit/socks/server.rs 中的 ExpectedSender 测试,以及 protocols/tests/unit/socks/protocol.rs 中的 endpoint_sees_through_ipv4_mapping_and_ignores_flow_info 锁定
596916d release etemenanki-protocols 2.0.2 etemenanki-protocols 为 2.0.2;其他三个 crate 仍为 2.0.0

至今仍影响代码形态的后果:

  • 没有兼容层。 ServerCircuit、VcLink、RoutingKey、OneOrManyTask 以及 Service 形态的服务端和客户端都已删除。1.x 的使用方必须迁移到协议核心和 connector,katana v3.0.0 就是这样做的。新代码不要再加回适配层。
  • 测试让协议核心与它的 codec 相互对照。 1.x 的对照测试(*_vs_legacy_*)随旧流水线一起删除。协议测试现在让每个服务端协议核心对接它自己的客户端 codec,既通过 CoreHarness 手动驱动,也在真实 socket 上运行;与上游的一致性则交给 Xray 和 Hysteria 互操作测试套件。
  • 回复要等拨号完成。 代理请求要等它的出站真正连上后才得到应答,连不上时则带着原因被拒绝。唯一的例外是指定了 IP 且需要嗅探的请求:客户端在收到回复前不会发送任何数据,因此 HTTP CONNECT、SOCKS 和 Hysteria 2 会立即应答这类请求。
  • 部分文档注释仍写着 “new pipeline”。 它们指的就是唯一的这条流水线。
不变量 保证机制 由谁固定
依赖只有一个方向:concepts ← environment ← protocols ← app 和 katana 各个清单;Cargo 拒绝依赖环 构建本身
etemenanki-concepts 不含协议代码,其协议核心中没有 I/O concepts/Cargo.toml 中没有 workspace 依赖;协议核心以 Event 的形式接收字节,并以 Effect 作答 core_is_driven_without_any_io(concepts/tests/runtime.rs)、codec_is_driven_without_any_io(concepts/tests/client.rs)
一条被代理的连接在一个任务中运行,即使经由用客户端 codec 访问的上游代理也是如此 ProxyClientRuntime 是一个 AsyncRead + AsyncWrite 值,由服务端运行时就地轮询。共享承载者(拨号得到的 gRPC 连接、WireGuard 设备、Hysteria 2 连接)运行独立的驱动任务 server_runtime_relays_through_a_client_runtime_in_one_task(concepts/tests/client.rs)
没有要求 Hysteria 2 或 TUN 的 crate 两者都得不到 可选依赖放在 hysteria 和 tun 之后;没有 default feature 可在 katana 的 Cargo.lock 中看到:其中有 quinn(它要求了 hysteria),但没有 ipstack 或 tun-rs
rustls 从不隐式选择 crypto provider 两个 tls_config 函数都用 builder_with_provider 指定 ring provider;服务端还固定使用 TLS 1.3 没有专门的测试。每个 Hysteria 2 握手测试,例如 protocols/tests/pipeline/hysteria.rs 中的 new_server_vs_new_client_tcp,都会构建这两份配置,但没有测试统一进第二个 provider
协议解析不会因不可信输入而 panic crate 级的 clippy deny 和 helpers/parse.rs 对解析器意味着什么一节列出的截断测试
vendored 进来的路由模型保持 harranu 的语义 routing.rs 原样 vendored first_matching_rule_wins、any_matcher_within_a_rule_matches、port_parser_rejects_an_inverted_range 以及 environment/tests/unit/routing.rs 中的其余测试

两个仓库的 CI 都不运行测试、格式检查或 clippy。workflow 只构建 release 二进制;下面的验证门槛在修改合入前和每次发布前运行。

Workflow 触发条件 做什么
Etemenanki build.yml push 到 main、pull request、手动触发 安装 pkg-config 和 libssl-dev,然后执行 cargo build --release --package etemenanki-app --target x86_64-unknown-linux-gnu,并上传二进制
katana build.yml push 到 main 或 master、pull request、手动触发 cargo build --release --locked,上传 target/release/katana
katana release.yml v* tag、手动触发 用 --locked 构建 x86_64-unknown-linux-gnu 和 x86_64-unknown-linux-musl;如果 readelf 显示动态链接了 libssl 或 libcrypto 则失败,如果 musl 二进制带有程序解释器或任何 NEEDED 条目也失败;在 tag push 时,把两个二进制连同 .sha256 文件发布为 GitHub release

release.yml 中的动态 OpenSSL 检查,正是 katana 开启 vendored-openssl 的原因:无论主机上的 OpenSSL 是什么版本,二进制都必须能运行。

终端窗口
cargo fmt --all -- --check
cargo test --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
# 发布前还要运行:
cargo check --workspace --locked

这些确切命令带来的几点后果:

  • clippy 门槛中的 --all-features 会一次开启所有 feature:etemenanki-environment/quic,因此 dial/quic.rs 及其测试会被 lint;tun,它的 rtnetlink 依赖只存在于 Linux target 上;以及 vendored-openssl,它从源码构建 OpenSSL,需要 perl、make 和 C 编译器。
  • cargo test --workspace 会在成员之间统一 feature。 app 在 etemenanki-protocols 上开启了 hysteria 和 tun,所以 workspace 级的运行也会编译并运行 protocols/tests/pipeline.rs 中的 hysteria 和 tun 模块。单独运行 cargo test -p etemenanki-protocols 则不会;需要加上 --features hysteria,tun。
  • workspace 测试运行中没有任何东西开启 quic,因为没有成员要求它。QUIC 拨号器测试(environment/tests/integration/quic.rs 中的 dials_a_quic_server_over_loopback、a_socket_wrap_sees_the_endpoint_socket)只有在 cargo test -p etemenanki-environment --features quic 时才会运行。
  • 互操作测试需要 Go 和 submodule。 没有 go,或者 submodule 没有初始化时,Xray 和 Hysteria 测试会打印 SKIP: 并判为通过。在这样的机器上全绿并不代表这些测试被执行过;先运行 git submodule update --init 并安装 Go,再相信结果。
  • 有些测试不只需要 Go。 a_routed_connect_is_answered_while_the_app_runs(app/tests/integration/e2e_tun.rs)需要创建 TUN 设备的权限(实际上就是 CAP_NET_ADMIN);遇到 PermissionDenied 时它会打印 SKIP: 并返回。wireguard_outbound_live_env_tcp(app/tests/integration/e2e_wg.rs)标记为 #[ignore],需要 ETEMENANKI_WG_* 环境变量和网络访问。

单元测试是 <crate>/tests/unit/… 下的文件,被挂载到它们所测试的模块中。etemenanki-environment、etemenanki-protocols、etemenanki-app 和 katana 都采用这种布局;etemenanki-concepts 则把七个小测试内联在 buffer.rs、relay.rs 和 wake.rs 中,并通过公共 API 测试它的运行时。

#[cfg(test)]
#[path = "../../tests/unit/trojan/core.rs"]
mod tests;

它们作为子模块编译,因此可以访问私有项,并且受上文 cfg_attr(test, allow(…)) 的覆盖。集成测试是普通的 Cargo 测试 target:

Crate 集成测试 target 覆盖内容 测试函数数
concepts tests/runtime.rs、tests/client.rs 用玩具协议核心和 codec(TinyMux、TinyUdp、TinyCodec 等)测试运行时 42,其中 7 个内联在 src/ 中
environment tests/integration.rs(tcp、udp,以及受 feature 控制的 quic) 回环地址上的拨号器 32,其中 24 个是单元测试
protocols tests/pipeline.rs(hysteria 和 tun 受各自 feature 控制) 在真实 socket 上让每个服务端协议核心对接它的客户端 codec;传输层;DoT 和 DoH 416,其中 360 个是单元测试
app tests/integration.rs(13 个 e2e_* 模块) 真实的 etemenanki-app 二进制对接 Xray、Hysteria、WireGuard 对端和 TUN 设备 123,其中 61 个是单元测试
katana tests/integration.rs(xray_interop、hysteria_interop、sniff) 在假面板后运行的真实 katana 二进制,由 Xray 和 Hysteria 客户端驱动;端到端的 disable_sniffing 109,其中 96 个是单元测试

计数是已核对版本中 #[test] 和 #[tokio::test] 属性的数量。如何编写和运行各类测试见测试。