Skip to content

Workspace and crates

Source files: 202 · checked against 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

The code lives in two private repositories. Etemenanki is a Cargo workspace of four crates: three libraries that together form the proxy kernel, and the standalone etemenanki-app binary built on them. katana is a separate repository whose binary, the panel node agent, takes the three library crates from a private Cargo registry. A third repository, harranu, holds the route model that the kernel now carries a copy of.

This page is the map to read before changing anything. It covers which crate owns what, how features and dependencies flow between crates, what every source file is for, the lints that shape all parsing code, how release 2.0 arrived at its one-pipeline design, and the gates a change has to pass. The pages under Concepts, Environment, Protocols and App then go into each crate.

  • DirectoryEtemenanki/
    • Cargo.toml workspace manifest and shared dependency versions
    • Cargo.lock
    • .cargo/config.toml declares the private registry
    • .github/workflows/build.yml
    • Directoryconcepts/ etemenanki-concepts
      • …
    • Directoryenvironment/ etemenanki-environment
      • …
    • Directoryprotocols/ etemenanki-protocols
      • …
    • Directoryapp/ etemenanki-app, the standalone binary
      • …
    • DirectoryXray-core/ git submodule, reference and interop tests only
      • …
    • Directoryhysteria/ git submodule, reference and interop tests only
      • …
  • Directorykatana/
    • Cargo.toml depends on the three library crates by version
    • Cargo.lock
    • .cargo/config.toml declares the private registry
    • Directory.github/workflows/ build.yml and release.yml
      • …
    • Directorysrc/
      • …
    • Directorytests/
      • …
    • DirectoryXboard/ panel reference tree, not built
      • …
    • DirectoryV2bX/ panel reference tree, not built
      • …
    • DirectoryXrayR/ panel reference tree, not built
      • …
Repository What it builds Consumes
Etemenanki etemenanki-concepts, etemenanki-environment and etemenanki-protocols (libraries, published to a private Cargo registry), and etemenanki-app (binary) Only crates.io and its own path dependencies
katana The katana binary The three library crates, from the private registry: etemenanki-concepts and etemenanki-environment at 2.0.0, etemenanki-protocols at 2.0.1
harranu A standalone route-model crate Nothing in the kernel or katana depends on it any more

The workspace manifest lists exactly members = ["app", "concepts", "environment", "protocols"]. Everything else in the checkout, including the submodules, is invisible to Cargo.

Xray-core/ (upstream XTLS/Xray-core) and hysteria/ (upstream HyNetworks/hysteria) are git submodules declared in .gitmodules. Nothing in them is compiled by Cargo. They serve two purposes:

  • Porting reference. Most protocol modules are ports, and their module docs name the Go file they follow: trojan/protocol.rs ports proxy/trojan/protocol.go, mux/frame.rs ports common/mux/{frame.go,reader.go,writer.go}, hysteria/obfs.rs ports hysteria/extras/obfs/salamander.go, and hysteria/PROTOCOL.md is the specification both Hysteria 2 ends are written against. Shadowsocks 2022 is ported from sing-shadowsocks/shadowaead_2022, which is not vendored.
  • Interoperability tests. app/tests/support/mod.rs builds the upstream binaries on first use: go build -o $CARGO_TARGET_TMPDIR/xray ./main inside Xray-core/, and go build inside hysteria/app. The results sit behind LazyLock statics, XRAY_BIN and HYSTERIA_BIN, so each test binary builds each upstream at most once. When go is missing or the build fails, the helper prints a SKIP: line and returns None, and every test that needs the binary returns early and passes. katana’s tests/support/mod.rs builds the same two binaries from the sibling checkout ../Etemenanki/Xray-core and ../Etemenanki/hysteria, and also skips when that directory does not exist.

katana’s Xboard/, V2bX/ and XrayR/ submodules play the same reference role for panel compatibility. Nothing builds them.

The route model (first-match rule table, domain, CIDR and port matchers, GeoIP and GeoSite .dat loading) has moved twice: from local copies in the app and in katana into the shared harranu crate, and from harranu into etemenanki-environment:

Step Repository Commit
The app drops its local copy and depends on the published harranu crate Etemenanki 58ad56c Extract route model into shared harranu crate, drop patch hack
katana does the same, the same day katana 90c5e35 Replace vendored route model with the shared harranu crate
harranu 0.3 is vendored, unchanged, as etemenanki_environment::routing Etemenanki fc40d1c add etemenanki-environment: host dialers and the vendored route model
katana routes with the kernel’s copy katana e9bc640 route with the kernel’s route model instead of harranu

The reason for the last move is in fc40d1c: depending on harranu from a private registry made the workspace unbuildable wherever that registry was unreachable. The consequence for contributors is that routing changes go into environment/src/routing.rs. A change to harranu reaches neither the app nor katana. The route model itself is described in Routing.

Dependencies point one way, from the binaries down to etemenanki-concepts. Each arrow is a dependency declared in a manifest; a label names the features the dependent crate turns on.

flowchart BT
  concepts["etemenanki-concepts"]
  environment["etemenanki-environment"]
  protocols["etemenanki-protocols"]
  app["etemenanki-app (binary)"]
  katana["katana (binary, own repository)"]
  environment --> concepts
  protocols --> concepts
  protocols --> environment
  app --> concepts
  app --> environment
  app -->|"hysteria, tun"| protocols
  katana --> concepts
  katana --> environment
  katana -->|"hysteria, vendored-openssl"| protocols

Inside the workspace, each internal dependency carries three keys: a path (for example ../concepts) so a workspace build uses the local source, and a version = "2.0.0" plus a registry naming the private registry, which is what cargo publish writes into the published manifest in place of the path. Every crate also sets publish to that registry alone, so none of them can reach crates.io by accident.

The four crates were released together at 2.0.0 in 054cf34 (“release etemenanki 2.0.0”), and etemenanki-environment was published for the first time in that release, at the same number. Since then only etemenanki-protocols has moved, in two patch releases listed under the 2.0 redesign: 2.0.1 (2f1f8cb, “release etemenanki-protocols 2.0.1”) with the WireGuard and mux fixes, and 2.0.2 (596916d, “release etemenanki-protocols 2.0.2”), which holds a SOCKS5 UDP association to its control connection’s client. 2.0.2 changes no public API: the source a UDP ASSOCIATE declares reaches SocksInbound through the crate-private handshake_with_udp_source, and the public handshake keeps its signature. etemenanki-concepts, etemenanki-environment and etemenanki-app stay at 2.0.0. The internal requirements still read version = "2.0.0", which 2.0.2 satisfies, and the workspace Cargo.lock records etemenanki-protocols at 2.0.2.

No crate declares a default feature set, so every feature below is opt-in.

Feature Crate What it turns on Enabled by
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; compiles protocols::hysteria etemenanki-app, katana
tun etemenanki-protocols dep:ipstack, dep:tun-rs, dep:rtnetlink (a Linux-only target dependency); compiles protocols::tun under #[cfg(all(feature = "tun", unix))] etemenanki-app
vendored-openssl etemenanki-protocols openssl/vendored: OpenSSL is built from source and linked statically katana
quic etemenanki-environment dep:quinn; compiles dial::quic (QuicDialer, SocketWrap) No crate; only --all-features or an explicit --features quic

The reasons are recorded in the manifests. hysteria is off by default because it “pulls in a whole second TLS stack (quinn -> rustls)”, and a downstream that only wants the classic protocols must not inherit it on its next version bump. tun is off for the same reason: an interface-management stack should not reach crates that never touch it. katana opts in to hysteria because it serves Hysteria 2 nodes, and to vendored-openssl because its release binaries must not link OpenSSL dynamically (see CI).

protocols::hysteria builds its QUIC endpoints on quinn directly. It does not enable etemenanki-environment/quic, so no production build at the verified revision compiles QuicDialer.

Dependency in katana’s Cargo.toml Requirement Features Resolved in Cargo.lock
etemenanki-concepts version = "2.0.0", private registry none 2.0.0, registry source and checksum recorded
etemenanki-environment version = "2.0.0", private registry none 2.0.0
etemenanki-protocols version = "2.0.0", private registry vendored-openssl, hysteria 2.0.1

A bare "2.0.0" is a caret requirement: any 2.x release at or above 2.0.0 satisfies it, which is why etemenanki-protocols resolves to 2.0.1 without a manifest change. katana v3.0.1 has not taken 2.0.2; its lockfile still records 2.0.1. The exact version is pinned by Cargo.lock, which records the registry source and a checksum for each crate, and both CI workflows build with --locked, so a build fails instead of resolving a different kernel. To move katana to a new kernel release, update one package at a time with cargo update -p <crate> --precise <version> and check that nothing else in the lockfile moved.

Both repositories declare the registry in .cargo/config.toml with the cargo:token credential provider. The token is never in the repository; CI passes it in through an environment variable.

Setting Value Where
Edition 2024 [workspace.package], inherited by every crate
Resolver 3 [workspace]
Shared versions One [workspace.dependencies] table; members write tokio.workspace = true Cargo.toml
Release profile lto = true Etemenanki Cargo.toml
Release profile (katana) lto = true, codegen-units = 4 katana Cargo.toml

Three traits from etemenanki-concepts are the seams every other crate plugs into. The details are on Server core, Server runtime and Links, connectors and net types.

concepts/src/link.rs → Connector and DatagramLink: what an outbound is, and how one is opened.

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: the server side of a protocol as a sans-I/O state machine.

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: the per-connection driver that ties a core, its transport and a connector together.

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 wraps a byte stream, over_datagrams a DatagramLink; both assert BUF_SIZE > Core::STAGING_RESERVE, because a smaller buffer would never have room to read. showing_progress turns the quiet Future into the Stream of Traffic reports described under tokio-stream below.

protocols/src/flow.rs → Flow: the Target of every server core in etemenanki-protocols, and therefore what the app’s and katana’s connectors receive.

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

Who implements what, at the verified revisions:

Trait Implementations
ProxyCoreDecode TrojanCore, VlessCore, VMessCore, ShadowsocksCore, Ss2022Core, HttpCore, PassthroughCore, Hy2StreamCore, Hy2UdpCore, TunUdpCore. SOCKS is the exception: socks/server.rs → SocksInbound drives its own control connection.
ProxyCoreEncode (client codec) TrojanStream, VlessStream, VMessStream, SsStream, Ss2022Stream, SocksConnect, HttpConnect, and NoCodec
ProxyCoreEncodeDatagram TrojanDatagram, VlessDatagram, VMessDatagram, and NoCodec
Connector<Target> concepts: SocketConnector, ProxyClientConnector, every FnMut(Target) -> Fut closure; environment: Dialer (for DialTarget and 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
Dependency Crates Why it is there
tokio (full) all The runtime, sockets and timers. Every crate’s dev-dependencies add test-util for start_paused tests, because idle and keepalive deadlines are minutes long.
tokio-stream concepts, protocols, app The Stream trait: in ProxyShowsProgress mode ProxyServerRuntime is a Stream whose items are Result<Traffic, RuntimeError<_>> progress reports, rather than a Future.
tokio-util protocols, app CancellationToken: the app scopes every task of a generation under one token (serve.rs → spawn_scoped), and the Hysteria 2 and TUN inbounds stop their tasks with one. AbortOnDropHandle ties a background task to the value that owns it: the HTTP/2 driver of a dialed gRPC stream, and the HTTP/3 driver and datagram pump of a Hysteria 2 client connection.
futures protocols The Sink and Stream traits over a tokio-tungstenite socket (ws/stream.rs), and a Shared future so concurrent callers await one Hysteria 2 connection build (hysteria/slot.rs).
pin-project concepts Pinned projections in the hand-written futures of relay.rs.
compact_str, smallvec concepts and up Remote::Domain holds a CompactString; EffectList is a SmallVec with INLINE_EFFECTS = 4 inline slots, so a typical event allocates nothing.
parking_lot concepts, protocols, app Short, never-awaited locks: wake.rs → ReadyQueue, the user tables, the rebuildable connection slots.
arc-swap protocols, app User tables that a reload replaces under live listeners, for example vmess/accounts.rs and the Hysteria 2 authenticator.
rand protocols Salts, request padding, VMess session keys and DNS query IDs.
openssl, tokio-openssl protocols Every TLS session over TCP: the TLS transport (transports/tls/config.rs builds servers from SslAcceptor::mozilla_intermediate_v5 and sets a minimum of TLS 1.2 on both sides) and the DNS-over-TLS and DNS-over-HTTPS backends in dns/mod.rs.
quinn, h3, h3-quinn, rustls, rustls-native-certs, rustls-pemfile, rustls-pki-types protocols, feature hysteria; quinn alone also in environment, feature quic QUIC and HTTP/3 for Hysteria 2, and the QUIC dialer. rustls is there only because it is quinn’s one crypto backend.
blake2 protocols, feature hysteria The Salamander obfuscator’s hash.
boringtun, smoltcp protocols (always) WireGuard: boringtun’s Tunn runs the WireGuard protocol, smoltcp is the userspace TCP/IP stack inside the tunnel. Not behind a feature.
h2, http protocols h2 carries the gRPC transport over HTTP/2 (transports/grpc/, and serving one HTTP/2 connection as many streams in transports/accept.rs). The http types also serve the WebSocket upgrade headers and the Hysteria 2 HTTP/3 authentication and masquerade.
tokio-tungstenite protocols The WebSocket transport.
httparse protocols HTTP/1.x request and response heads in http/protocol.rs.
ipstack, tun-rs, rtnetlink protocols, feature tun The userspace IP stack, device creation, and route installation on Linux.
aes, aes-gcm, chacha20poly1305, hkdf, sha1, sha2, md-5, crc32fast, blake3, subtle protocols The ciphers, KDFs and hashes of VMess, Shadowsocks and Trojan: md-5 for the Shadowsocks EVP_BytesToKey, the VMess command key and the VMess ChaCha20-Poly1305 body key, HKDF-SHA1 for Shadowsocks AEAD subkeys, SHA-256 for the VMess KDF, SHA-224 for the Trojan password hash, CRC32 for the checksum inside a VMess auth id, BLAKE3 for the Shadowsocks 2022 session and identity subkeys.
socket2 environment, protocols Socket options that must be set between creation and bind or connect (SocketOptions), and TCP keepalive (transports/keepalive.rs).
cidr, regex, prost environment Route matchers; prost decodes the protobuf in geoip.dat and geosite.dat. The app and katana also use cidr directly.
moka (future) protocols The DNS answer cache in dns/mod.rs, a moka::future::Cache bounded at CACHE_CAPACITY = 8192 names.
notify app, katana The configuration-file watcher behind hot reload.
serde, toml, clap, tracing-subscriber app, katana Configuration, the command line and log output.
reqwest (native-tls-vendored), serde_json katana The panel HTTP clients and their JSON bodies. On Linux native-tls is OpenSSL, and the vendored flavour builds it statically, like the kernel’s vendored-openssl.
thiserror, anyhow protocols ProtocolError, and its opaque Other variant.
uuid concepts, protocols, app UserAuthorization::Uuid for VMess and VLESS users.

Dev-only dependencies: rcgen, rustls and rustls-pki-types in environment (certificates for the QUIC dialer test), etherparse in protocols (IP packets for the fake TUN device), and openssl, boringtun and smoltcp in the app (test certificates and an in-process WireGuard peer). Besides tokio with test-util, katana’s only dev-dependency is openssl with vendored, for the self-signed certificates of its Xray TLS tests; vendoring it matches the kernel’s vendored-openssl, so both resolve to one OpenSSL build.

OpenSSL terminates every TLS session that runs over TCP. rustls enters the build only with quinn: under the hysteria feature of etemenanki-protocols, or the quic feature of etemenanki-environment, which no crate enables. It is there because QUIC embeds TLS in its handshake state machine and quinn offers no other backend. Both rustls configurations the code builds, in protocols::hysteria, are built with an explicitly named provider:

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

That is hysteria/connection.rs → tls_config for the client and hysteria/server/endpoint.rs → tls_config for the server. The plain ClientConfig::builder() and ServerConfig::builder() panic when a downstream unifies a second crypto-provider feature into the build, so they must never be used. The server configuration also pins TLS 1.3: QuicServerConfig unwraps rustls::quic::ServerConnection::new on the first packet, so a configuration without TLS 1.3 would panic inside the accept loop rather than fail at build time.

Line counts are at the verified revision and include doc comments. Unit tests live outside src/ (see Tests), so the counts are code and documentation only, with one exception: three small inline mod tests blocks in etemenanki-concepts.

3,889 lines. The sans-I/O model: server cores, client codecs, the two runtimes, links and connectors. It contains no protocol code and depends on no other workspace crate.

File Lines Responsibility
lib.rs 44 Crate docs, including the one-task diagram, and the module list
core.rs 732 The protocol traits. Server side: ProxyCoreDecode, Event, Effect, Effects, EffectList. Client side: ProxyCoreEncodeHandshake, ProxyCoreEncode, ProxyCoreEncodeDatagram, Handshake, Reply, Opened, NoCodec
runtime.rs 1,394 ProxyServerRuntime, the Transport trait with StreamTransport and DatagramTransport, Traffic, RuntimeError, the ProxyRunsQuiet and ProxyShowsProgress modes, WORK_BUDGET
client.rs 654 ProxyClientRuntime (a codec over a dialed upstream, exposed as a byte stream or a DatagramLink), ProxyClientConnector, ProxyClientConnecting
link.rs 223 DatagramLink, Outbound, Connector and its closure impl, UdpOutbound, SocketConnector, SocketTarget, NoStream, NoDatagram
buffer.rs 269 ReadBuffer and WriteBuffer, fixed-size boxed arrays that never grow, and Staging, the append-only view of a WriteBuffer’s free tail that a core writes into
wake.rs 180 ReadyQueue and KeyWaker: per-key wake-ups, so the runtime polls only the outbounds that became ready
relay.rs 277 UnidirectionalConnection, BidirectionalConnection, Relayed: byte-copy futures for passthrough paths
net.rs 94 UserAuthorization, NetworkUser, DialNetwork, Remote, Destination
sniff.rs 22 SniffedProtocol, SniffedBehavior, the Sniffer trait

1,363 lines. Two halves that do not depend on each other: how this host opens a socket, and which outbound a flow is routed to.

File Lines Responsibility
lib.rs 40 Crate docs and the lint policy
dial/mod.rs 100 DialTarget, Dialer, and its Connector impls for DialTarget and SocketTarget
dial/socket.rs 247 SocketOptions (source address, Interface, packet mark, SocketHook), AddressFamily, per-OS support decided at compile time
dial/tcp.rs 109 TcpDialer, connect_any across addresses, DEFAULT_CONNECT_TIMEOUT = 10 s per attempt
dial/udp.rs 187 UdpDialer, bind_dual, DualStackUdp
dial/quic.rs 90 Feature quic: QuicDialer, SocketWrap
routing.rs 590 The vendored harranu route model: RouteTarget, RouteMatch, RouteTable, DomainRegex, GeoData, .dat protobuf messages

Dialers are covered in Dialers, routing in Routing.

23,105 lines. Every protocol is a server core plus a client codec on top of the traits above; transports turn sockets into byte streams; the shared pieces sit beside them.

Area Lines Page
Crate root, core/, helpers/ 1,487 Protocol foundations
sniff/ 360 Sniffing
dns/ 748 DNS
transports/ 2,356 TCP and TLS, WebSocket and gRPC
mux/ 929 mux.cool and XUDP
socks/, http/ 1,422 and 760 SOCKS, HTTP
trojan/, vless/ 928 and 1,000 Trojan, VLESS
vmess/ 2,876 VMess crypto, VMess wire
ss_legacy/, ss_2022/ 1,334 and 1,668 Shadowsocks, Shadowsocks 2022
hysteria/ (feature hysteria) 4,739 Hysteria 2 client, Hysteria 2 server
wireguard/ 1,555 WireGuard
tun/ (feature tun, Unix) 943 TUN
File Lines Responsibility
lib.rs 38 Lint policy and module list, with the hysteria and tun gates
flow.rs 70 Flow, the target of every core
error.rs 72 ProtocolError and its mapping onto io::ErrorKind
macros.rs 23 byte_newtype!, fixed-size byte newtypes for key material
core/mod.rs 464 FlowKey, SubKey, Phase, Timing, HANDSHAKE_TIMEOUT, RELAY_IDLE_TIMEOUT, SniffPrefix, Passthrough, PassthroughCore
core/harness.rs 125 CoreHarness: a hand-driven runtime stand-in for testing a core without sockets
helpers/address.rs 354 AddressCodec (the type-byte address format of SOCKS5, Trojan, Shadowsocks, VLESS and 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-free slice access (see Lints)
helpers/mod.rs 4 Module list
sniff/mod.rs 84 SNIFF_TIMEOUT, SNIFF_LIMIT, sniff, worth_sniffing, plausible_domain
sniff/collector.rs 77 Collector and Verdict: the clock-free accumulator of a flow’s first bytes
sniff/tls.rs 101 TlsSniffer: ClientHello SNI
sniff/http.rs 98 HttpSniffer: HTTP/1.x Host
dns/mod.rs 545 Resolver, Backend, ResolverSpec, the cache and its TTL bounds
dns/message.rs 203 The minimal A/AAAA wire codec: encode_query, decode_answer
File Lines Responsibility
transports/mod.rs 14 Module list
transports/accept.rs 175 InboundTransport, Accepted, TRANSPORT_HANDSHAKE_TIMEOUT, serving an HTTP/2 connection as many streams
transports/connect.rs 195 TransportKind, TransportConnector: resolve, connect, wrap an upstream
transports/stream.rs 71 TransportStream, the one byte-stream type every transport yields
transports/keepalive.rs 33 set_keepalive for every accepted or dialed socket
transports/tls/mod.rs 8 Module list
transports/tls/config.rs 201 ServerConfig, ClientConfig, Alpn, VerifyMode over OpenSSL
transports/tls/stream.rs 91 MaybeTlsStream, tcp_from_std, accept_optional_tcp, wrap_optional_tcp
transports/ws/mod.rs 7 Module list
transports/ws/endpoint.rs 254 WsRoute, WsTarget, path and early-data handling, MAX_EARLY_DATA, MAX_WS_MESSAGE_LEN
transports/ws/stream.rs 378 WsStream: WebSocket messages as bytes, WS_IDLE_TIMEOUT, WS_KEEPALIVE_INTERVAL
transports/grpc/mod.rs 10 Module list
transports/grpc/framing.rs 320 encode_hunk, encode_multi_hunk, HunkDecoder
transports/grpc/settings.rs 149 HTTP/2 settings, MAX_GRPC_MESSAGE_LEN, H2_IDLE_TIMEOUT, GrpcPaths, GrpcMode
transports/grpc/liveness.rs 145 Liveness: idle deadline and PING supervision of a served connection
transports/grpc/stream.rs 305 GrpcStream: one HTTP/2 stream as bytes
File Lines Responsibility
mux/mod.rs 52 MUX_ADDRESS, MUX_PORT, mux_destination, is_mux_destination
mux/frame.rs 407 The mux.cool frame codec, FrameMeta, SessionStatus, MAX_META_LEN, MAX_DATA_LEN
mux/demux.rs 470 Demux: the server-side demultiplexer shared by the Trojan, VLESS and VMess cores; feed for a plain carrier, feed_chunks for all of a VMess read’s chunks in one call
File Lines Responsibility
socks/mod.rs 14 Module list
socks/protocol.rs 249 SOCKS4, 4a and 5 wire constants and primitives; endpoint, the canonical (IpAddr, u16) both ends compare a relay datagram’s sender by
socks/handshake.rs 273 The server handshake up to the reply: handshake, handshake_with_udp_source (crate-private; also returns the source a UDP ASSOCIATE names), Request, Version, reply writers
socks/server.rs 445 SocksInbound: the bespoke driver that owns the control connection; ExpectedSender, the one client a UDP association hears
socks/codec.rs 154 SocksConnect: the SOCKS5 CONNECT client codec
socks/udp_link.rs 207 SocksUdpLink: a client-side UDP ASSOCIATE as a DatagramLink
socks/config.rs 80 SocksServerConfig, SocksAuth
http/mod.rs 11 Module list
http/protocol.rs 283 Request and response heads, fixed responses, forwarded-request rewriting
http/core.rs 322 HttpCore: CONNECT tunnels and absolute-form forwarding
http/codec.rs 96 HttpConnect: the CONNECT client codec
http/config.rs 48 HttpServerConfig
File Lines Responsibility
trojan/mod.rs 11 Module list
trojan/protocol.rs 332 Request header and UDP packet framing, password_hash
trojan/users.rs 89 TrojanServerConfig, Validator
trojan/core.rs 353 TrojanCore
trojan/codec.rs 143 TrojanStream, TrojanDatagram
vless/mod.rs 15 Module list
vless/protocol.rs 309 Request and response headers, length-prefixed UDP packets
vless/validator.rs 77 Validator, the user table
vless/config.rs 49 VlessServerConfig
vless/core.rs 365 VlessCore
vless/codec.rs 185 VlessStream, VlessDatagram
File Lines Responsibility
vmess/mod.rs 18 Module list
vmess/aead.rs 616 The HMAC-SHA256 KDF, the AES-128 auth-id codec, the sealed header envelope
vmess/keys.rs 211 Typed 16-byte key material, so a key cannot be passed where an IV is expected
vmess/accounts.rs 358 Account, AccountValidator: matching an auth id to a user
vmess/protocol.rs 419 Request and response header codecs, Security, RequestOptions
vmess/framing.rs 388 ChunkStream, ChunkDecoder: body-chunk framing
vmess/session.rs 108 OutboundSession: per-connection key and IV derivation
vmess/core.rs 563 VMessCore
vmess/codec.rs 195 VMessStream, VMessDatagram
File Lines Responsibility
ss_legacy/mod.rs 16 Module list; supported ciphers
ss_legacy/aead.rs 656 Method, Session, ChunkEncoder, ChunkDecoder, EncryptWriter, DecryptReader
ss_legacy/users.rs 97 ShadowsocksServerConfig, Resolved key table
ss_legacy/protocol.rs 9 The shared ADDR codec
ss_legacy/core.rs 433 ShadowsocksCore
ss_legacy/codec.rs 123 SsStream
ss_2022/mod.rs 13 Module list
ss_2022/crypto.rs 523 The 2022-blake3-* methods, session_key, identity_subkey, StreamAead, ChunkWriter, ChunkReader
ss_2022/protocol.rs 381 Request and response header framing, extended identity headers
ss_2022/users.rs 164 Ss2022ServerConfig, Validator, normalise_psk, decode_psk
ss_2022/core.rs 420 Ss2022Core
ss_2022/codec.rs 167 Ss2022Stream
File Lines Responsibility
hysteria/mod.rs 29 Module list and the shared-connection model
hysteria/protocol.rs 821 QUIC varints, TCPRequest and TCPResponse, padding, UDP messages and defragmentation
hysteria/auth.rs 177 The HTTP/3 authentication exchange
hysteria/obfs.rs 341 Salamander packet obfuscation
hysteria/quic.rs 66 Datagram sending shared by both ends: whole first, fragmented only when the peer refuses the whole message
hysteria/connection.rs 594 Hy2Conn: one authenticated client connection, the rustls client configuration
hysteria/slot.rs 235 ConnSlot: the lazily built, rebuildable shared connection
hysteria/connector.rs 216 Hy2Connector, Hy2Stream, Hy2DatagramLink
hysteria/config.rs 79 Hy2Config, Obfs
hysteria/server/mod.rs 12 Module list
hysteria/server/inbound.rs 682 Hy2Inbound, Hy2StreamCore
hysteria/server/shim.rs 525 Splitting one QUIC connection between HTTP/3 and proxy streams
hysteria/server/datagrams.rs 357 QuicDatagrams, Hy2UdpCore
hysteria/server/authenticator.rs 216 Authenticator: which user a credential belongs to
hysteria/server/endpoint.rs 131 The server QUIC endpoint and its rustls configuration
hysteria/server/io.rs 114 QuicIo: h3 stream types as AsyncRead and AsyncWrite
hysteria/server/masquerade.rs 82 Masquerade: the answer given to anyone who is not a client
hysteria/server/config.rs 62 ServerConfig, ListenerConfig
File Lines Responsibility
wireguard/mod.rs 41 Module docs: why WireGuard is an outbound only
wireguard/config.rs 97 WgConfig, parse_key
wireguard/device.rs 998 WgDevice: the single driver task owning boringtun’s Tunn, the smoltcp stack and the peer socket; at most one uplink item held per connection, CHANNEL_CAP = 256
wireguard/connector.rs 268 WgConnector, WgStream, WgDatagramLink
wireguard/slot.rs 151 DeviceSlot: the lazily built, rebuildable tunnel
tun/mod.rs 42 Module docs: what is dropped, keeping the outbound path off the device
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 TunInbound over ipstack
tun/udp.rs 286 TunUdpLink, TunUdpCore
tun/tracked.rs 93 TrackedTcp: counted streams, so shutdown can wait for them

4,302 lines. A binary crate: no public API, every module private to main.rs.

File Lines Responsibility
main.rs 149 CLI (-c/--config, --test), tracing setup, the notify watcher with a 200 ms debounce, SIGINT/SIGTERM
config.rs 666 The TOML schema (deny_unknown_fields throughout), load, parse_bytes, reload diffing
instance.rs 391 Instance, build, Built: generations and hot reload
serve.rs 413 spawn_scoped, StreamListener, accept loops, run_hysteria_inbound
connector.rs 39 AppConnector: routes a flow and opens it on the chosen outbound
flow.rs 22 Flow, FlowContext
router.rs 158 build_router, route_target: compiling [[route.rule]] into a RouteTable
transport.rs 156 tls_layer, resolve_stream, reject_stream_settings: stream-settings validation shared by both builders
balancer.rs 188 Balancer, Member, Strategy: health-probed outbound groups
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, the per-protocol client wiring
outbound/proxy.rs 215 ProxyClient, OutboundStream, OutboundDatagram, BlackholeLink
outbound/freedom.rs 163 FreedomConnector, ResolvingUdp
outbound/udp_fanout.rs 185 FanOutLink: per-packet UDP routing, MAX_SUBS

The app’s internals are on Build pipeline, Serving, Outbounds and Generations and reload.

6,460 lines, one binary crate. The details are on the katana overview.

File Lines Responsibility
main.rs 54 CLI (-c/--config, --test)
config.rs 324 The TOML schema
runtime.rs 439 The process root: outbound pool, one NodeManager per [[node]], config watching; a reload builds every added node before touching a running one
api/mod.rs 369 PanelClient over the two panel types, panel_node_type
api/sspanel.rs 576 The sspanel (mod_mu) client
api/newv2board.rs 452 The newV2board (UniProxy) client
manager/mod.rs 97 The NodeManager → TransportManager → ProxyManager tree
manager/node.rs 675 Per-node sync and change classification; bootstrap retried with a backoff from 1 s to 60 s; a panel client rebuilt for a [node.api] edit
manager/transport.rs 177 A bound listener and what serves it
manager/proxy.rs 239 A node’s user tables and admission
inbound.rs 548 Inbound construction per panel node type
serve.rs 447 Accept loop and one runtime per connection
connector.rs 361 KatanaConnector: admission, routing and audit for every flow
router.rs 120 Compiling [[route.rule]] against etemenanki_environment::routing
rule.rs 76 Destination-audit rules
traffic.rs 472 Per-user traffic counters and speed limits
meter.rs 141 Metering on the outbound side of a flow
outbound/mod.rs 533 The outbound pool
outbound/proxy.rs 179 Proxy clients for upstream outbounds
outbound/freedom.rs 181 The direct outbound

environment/src/lib.rs and protocols/src/lib.rs open with the same attribute:

#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)]
// Tests exercise known-good inputs and may use the panicking forms freely.
#![cfg_attr(
test,
allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)
)]

Together these four lints forbid the usual ways a byte from the network could panic the process: .unwrap() and .expect(), buf[i] and buf[a..b], and any integer operation that can overflow or panic, + on a usize included. They do not cover every panic (an explicit assert! or unreachable! still compiles), so they are a floor, not a proof. etemenanki-concepts, etemenanki-app and katana do not carry the attribute.

The only local exceptions are eight functions in etemenanki-protocols that re-allow clippy::arithmetic_side_effects alone, each with a reason that states the bound:

Function File Stated reason
put_varint, read_varint transports/grpc/framing.rs A shift by a constant 7 on u64; a shift amount kept below 64
read_varint, read_varint_slice hysteria/protocol.rs The QUIC varint prefix caps the fold at 62 bits
decode_varint hysteria/server/shim.rs The same 62-bit cap
deadline transports/grpc/liveness.rs An Instant only overflows past the end of the monotonic clock
after transports/ws/stream.rs, socks/server.rs The same Instant argument

A new exception follows the same form: one small function, one lint, and a reason a reviewer can check.

The cfg_attr(test, …) half matters because of how unit tests are compiled: they are mounted into the module they test (see Tests), so they are part of the crate under cfg(test) and would otherwise inherit the ban.

A parser in etemenanki-protocols takes a byte slice that is possibly incomplete and certainly untrusted, and has three outcomes: a value with the number of bytes it used, “not enough bytes yet”, or an error. helpers/parse.rs supplies the building blocks:

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 is data.get(index) with a short read mapped to ProtocolError::Truncated(what).
  • take_array computes the end offset with checked_add (overflow becomes ProtocolError::Overflow("field offset")) and copies a fixed-size array, which is what u16::from_be_bytes and friends need.
  • need_more turns a truncation, which reaches io::Error as UnexpectedEof, into Ok(None), and leaves every other error an error. It is how a parser handed a growing buffer says “ask me again with more bytes”.

vless/protocol.rs → parse_request_header shows the resulting style. Single bytes come from first() and get(), fixed fields are checked as soon as they are available, and offsets are computed with saturating_add or 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);
};
// ... addons length, command, then the address via `take(buf, 19.., "vless address")`
}

The errors come from error.rs → ProtocolError, which classifies what went wrong. Its From<ProtocolError> for io::Error impl fixes the io::ErrorKind that callers above the protocol layer see:

Variant Meaning io::ErrorKind
Truncated(&'static str) Input shorter than the protocol requires UnexpectedEof
Overflow(&'static str) Arithmetic over an untrusted length or offset overflowed InvalidData
Malformed(&'static str) A field held a value the protocol does not permit InvalidData
Unsupported(&'static str) A feature this implementation does not provide InvalidData
Unauthenticated(&'static str) The peer could not be authenticated PermissionDenied
Crypto(&'static str) An AEAD or key-derivation step failed Other
Io(io::Error) A lower-level I/O failure passed through unchanged
Other(anyhow::Error) Anything that cannot be classified Other

The UnexpectedEof mapping is load-bearing: need_more relies on it to tell “wait” from “reject”.

Parser tests follow a common pattern: encode a valid message, cut it short at many points, and assert that nothing panics and that each prefix is either “need more” or an error. Examples:

Test File Pins
request_header_is_parsed_from_a_slice_once_whole protocols/tests/unit/vless/protocol.rs A VLESS header cut at its field boundaries, inside the address, and one byte short is None; the whole header parses and reports its exact length, leaving trailing payload alone
truncated_and_malformed_input_never_panics protocols/tests/unit/dns/message.rs Every prefix of a DNS answer decodes without panicking; an rdlength past the end is an error
varint_truncated_at_every_boundary, tcp_response_truncated_at_every_boundary protocols/tests/unit/hysteria/protocol.rs Hysteria 2 varints and responses at every truncation point
rejects_truncated_metadata protocols/tests/unit/mux/frame.rs A mux.cool frame whose metadata is cut short
a_truncated_client_hello_yields_nothing_rather_than_garbage protocols/tests/unit/sniff/tls.rs A ClientHello cut at several points yields the real SNI or nothing, never a different name

Release 1.x moved bytes through tasks and channels. Release 2.0 drives each connection from a single task that owns the connection’s transport, buffers, protocol core and every outbound it opens. Only carriers shared by many connections keep driver tasks of their own: an HTTP/2 connection carrying gRPC streams, a WireGuard device, and a Hysteria 2 QUIC connection. The old pipeline was removed, not kept alongside, which is why every crate went to 2.0.0 together.

flowchart LR
  sock["raw socket"] --> trans["TcpInboundTransport"]
  trans --> vc["VcLink: two mpsc channels of Bytes"]
  vc --> server["ProxyProtocolServer (tower Service)"]
  server --> circuit["ServerCircuit"]
  circuit --> router["Router"]
  router --> client["ProxyProtocolClient"]
  client --> out["OutboundTransport dial"]

A transport turned the socket into a VcLink, a pair of bounded mpsc channels carrying Bytes frames. A tower-style ProxyProtocolServer decoded the handshake into a ServerCircuit, and OneOrManyTask carried the spawned copy loops in JoinSets, with an unbounded receiver stream for multiplexed connections. Every hop was a task or a channel.

The main steps of the migration, and the fixes released since, in the Etemenanki history:

Commit Step
20d118b rewrite concepts The new core, runtime, buffer, wake, link and relay modules; the old ones move to concepts::legacy
9b7be19 split the proxy server and client traits ProxyCoreDecode for servers; ProxyCoreEncode and ProxyClientRuntime for clients
fc40d1c add etemenanki-environment Host dialers as Connectors, and the vendored route model
a5c225b protocols: move the task-and-channel pipeline under legacy Old servers, clients and transports move to protocols::legacy; shared codec code stays in place
8bad424 … 6b33b3c Slice parsers and encoders per protocol, the shape the new cores need
8653d8f pipeline foundations Flow, Timing, SniffPrefix, PassthroughCore, TransportStream, InboundTransport, TransportConnector
95ea530 … 8300195 A sans-I/O core and client codec per protocol, then SOCKS, Hysteria 2, WireGuard and TUN
5c826f4 app: serve every inbound and outbound on the new pipeline The app switches over; configuration schema, routing and hot reload are unchanged
44c5109, e79a357 drop the legacy pipeline protocols::legacy and concepts::legacy are deleted; tower leaves every Etemenanki manifest
054cf34 release etemenanki 2.0.0 All four crates at 2.0.0
6c727ee wireguard: bound the driver’s per-connection uplink The WireGuard driver holds at most one uplink item per connection and reads a connection’s channel only while it holds none, so a connection that stops draining blocks its own producer and no other
53eed3b mux: send each downlink frame once, keep a read’s held frames Each mux.cool downlink frame is staged once; Demux::feed_chunks replaces feed_whole and takes every chunk of a VMess read in one call
2f1f8cb release etemenanki-protocols 2.0.1 etemenanki-protocols at 2.0.1; the other three crates stay at 2.0.0
351abcc socks: hold a UDP association to its control connection’s client A UDP association hears only the control connection’s IP, compared in canonical form, and is pinned to one port by its first forwarded datagram or by a request naming that IP with a port. A Unix-socket client that does not name its exact source, and a relay that cannot hear its client, are refused with 0x02. SocksUdpLink compares reply sources in the same canonical form. Pinned by the udp_association_* and udp_link_* tests in protocols/tests/pipeline/socks.rs, the ExpectedSender tests in protocols/tests/unit/socks/server.rs, and endpoint_sees_through_ipv4_mapping_and_ignores_flow_info in protocols/tests/unit/socks/protocol.rs
596916d release etemenanki-protocols 2.0.2 etemenanki-protocols at 2.0.2; the other three crates stay at 2.0.0

Consequences that still shape the code:

  • No compatibility layer. ServerCircuit, VcLink, RoutingKey, OneOrManyTask and the Service-shaped servers and clients are gone. A 1.x consumer had to move to cores and connectors, which is what katana v3.0.0 did. New code does not add adapters back.
  • Tests compare a core with its codec. The 1.x oracle tests (*_vs_legacy_*) went with the old pipeline. The protocol tests now run each server core against its own client codec, both by hand through CoreHarness and over real sockets, and leave agreement with upstream to the Xray and Hysteria interop suites.
  • Replies wait for the dial. A proxy request is answered once its outbound is actually connected, or refused with a reason when it is not. The one exception is a request that names an IP and is sniffed: the client sends nothing before the reply, so HTTP CONNECT, SOCKS and Hysteria 2 answer such a request at once.
  • Some doc comments still say “new pipeline”. They mean the only pipeline.
Invariant Enforced by Pinned by
Dependencies point one way: concepts ← environment ← protocols ← app and katana The manifests; Cargo rejects a dependency cycle The build itself
etemenanki-concepts has no protocol code and no I/O in its cores No workspace dependency in concepts/Cargo.toml; cores receive bytes as Events and answer with Effects core_is_driven_without_any_io (concepts/tests/runtime.rs), codec_is_driven_without_any_io (concepts/tests/client.rs)
A proxied connection runs in one task, even through an upstream proxy spoken by a client codec ProxyClientRuntime is an AsyncRead + AsyncWrite value the server runtime polls in place. Shared carriers (a dialed gRPC connection, the WireGuard device, a Hysteria 2 connection) run separate driver tasks server_runtime_relays_through_a_client_runtime_in_one_task (concepts/tests/client.rs)
A crate that does not ask for Hysteria 2 or TUN gets neither Optional dependencies behind hysteria and tun; no default features Visible in katana’s Cargo.lock, which has quinn (it asks for hysteria) but no ipstack or tun-rs
rustls never picks a crypto provider implicitly builder_with_provider with the ring provider in both tls_config functions; the server also pins TLS 1.3 No dedicated test. Every Hysteria 2 handshake test, such as new_server_vs_new_client_tcp in protocols/tests/pipeline/hysteria.rs, builds both configurations, but no test unifies a second provider
Protocol parsing cannot panic on untrusted input The crate-wide clippy denies and helpers/parse.rs The truncation tests listed under Lints
The vendored route model keeps harranu’s semantics routing.rs is vendored unchanged first_matching_rule_wins, any_matcher_within_a_rule_matches, port_parser_rejects_an_inverted_range and the rest of environment/tests/unit/routing.rs

Neither repository’s CI runs tests, formatting or clippy. The workflows build release binaries; the validation gates below are run before a change lands and before every release.

Workflow Trigger What it does
Etemenanki build.yml Push to main, pull requests, manual Installs pkg-config and libssl-dev, then cargo build --release --package etemenanki-app --target x86_64-unknown-linux-gnu, and uploads the binary
katana build.yml Push to main or master, pull requests, manual cargo build --release --locked, uploads target/release/katana
katana release.yml Tags v*, manual Builds x86_64-unknown-linux-gnu and x86_64-unknown-linux-musl with --locked; fails if readelf shows a dynamic libssl or libcrypto, and fails if the musl binary has a program interpreter or any NEEDED entry; on a tag push, publishes both binaries with .sha256 files as a GitHub release

The dynamic-OpenSSL check in release.yml is why katana enables vendored-openssl: the binaries must run on hosts whatever their OpenSSL version.

Terminal window
cargo fmt --all -- --check
cargo test --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
# before a release, additionally:
cargo check --workspace --locked

A few consequences of these exact commands:

  • --all-features in the clippy gate turns on everything at once: etemenanki-environment/quic, so dial/quic.rs and its test are linted; tun, whose rtnetlink dependency exists only on Linux targets; and vendored-openssl, which builds OpenSSL from source and needs perl, make and a C compiler.
  • cargo test --workspace unifies features across members. The app enables hysteria and tun on etemenanki-protocols, so the workspace run also compiles and runs the hysteria and tun modules of protocols/tests/pipeline.rs. cargo test -p etemenanki-protocols alone does not; add --features hysteria,tun.
  • Nothing enables quic in a workspace test run, because no member asks for it. The QUIC dialer tests (dials_a_quic_server_over_loopback, a_socket_wrap_sees_the_endpoint_socket in environment/tests/integration/quic.rs) run only with cargo test -p etemenanki-environment --features quic.
  • Interop tests need Go and the submodules. Without go, or with an uninitialised submodule, the Xray and Hysteria tests print SKIP: and pass. A green run on such a machine has not exercised them; run git submodule update --init and install Go before trusting it.
  • Some tests need more than Go. a_routed_connect_is_answered_while_the_app_runs (app/tests/integration/e2e_tun.rs) needs permission to create a TUN device (in practice CAP_NET_ADMIN); on PermissionDenied it prints SKIP: and returns. wireguard_outbound_live_env_tcp (app/tests/integration/e2e_wg.rs) is #[ignore] and needs ETEMENANKI_WG_* environment variables and network access.

Unit tests are files under <crate>/tests/unit/…, mounted into the module they test. etemenanki-environment, etemenanki-protocols, etemenanki-app and katana all follow this layout; etemenanki-concepts instead keeps seven small tests inline in buffer.rs, relay.rs and wake.rs, and tests its runtimes through their public API.

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

They compile as a child module, so they can reach private items, and they are covered by the cfg_attr(test, allow(…)) above. Integration tests are ordinary Cargo test targets:

Crate Integration target Covers Test functions
concepts tests/runtime.rs, tests/client.rs The runtimes with toy cores and codecs (TinyMux, TinyUdp, TinyCodec and others) 42, of which 7 inline in src/
environment tests/integration.rs (tcp, udp, and quic behind the feature) Dialers on loopback 32, of which 24 unit
protocols tests/pipeline.rs (hysteria and tun behind their features) Each server core against its client codec over real sockets; transports; DoT and DoH 416, of which 360 unit
app tests/integration.rs (13 e2e_* modules) The real etemenanki-app binary against Xray, Hysteria, a WireGuard peer and a TUN device 123, of which 61 unit
katana tests/integration.rs (xray_interop, hysteria_interop, sniff) The real katana binary behind a fake panel, driven by Xray and Hysteria clients; disable_sniffing end to end 109, of which 96 unit

Counts are #[test] and #[tokio::test] attributes at the verified revision. How to write and run each kind is on Testing.