Protocols and transports
Every katana node runs one listener, and two things decide what it speaks. node_type in [node.api] picks the protocol family. The panel’s answer to katana’s node-info request fills in the rest: the port, the transport, whether TLS is on, and the protocol details such as the Shadowsocks cipher. katana then builds the listener from that answer, or refuses to build it when the answer asks for something it does not implement.
Read this page when you set up a node in the panel and want to know which settings katana will honour, or when a node does not start and the log says node requests kernel-unsupported feature. The panel pages (Xboard and V2board, SSPanel) cover the panel API itself.
At a glance
Section titled “At a glance”node_type |
Protocol | Transports | TLS | UDP relay | Multiplexing | Panels |
|---|---|---|---|---|---|---|
V2ray |
VMess, AEAD only (alter_id 0) |
TCP, WebSocket, gRPC | Optional | Yes | mux.cool and XUDP | UniProxy, SSPanel |
V2ray with enable_vless = true |
VLESS, no flow |
TCP, WebSocket, gRPC | Optional | Yes | mux.cool and XUDP | UniProxy, SSPanel |
Trojan |
Trojan, password = the user’s UUID | UniProxy: TCP. SSPanel: TCP, WebSocket, gRPC | Always | Yes | mux.cool and XUDP | UniProxy, SSPanel |
Shadowsocks |
AEAD (SIP004) or 2022 multi-user (SIP022) | TCP | Never | No | No | UniProxy only |
Hysteria2 |
Hysteria 2 | QUIC over UDP | Always, inside QUIC | When [node.hysteria].udp = true |
Not applicable | UniProxy, SSPanel (custom_config), or local settings |
“UniProxy” means the newV2board API that Xboard and V2board serve (panel_type = "NewV2board", or its alias "V2board"). Hysteria 2 has its own page, Hysteria 2 nodes; the rest of this page is about the four stream protocols.
How katana builds a node
Section titled “How katana builds a node”katana checks everything that can fail before it binds the port, so a refused node never half-starts:
flowchart TB P["Panel node info"] --> N["Parse for the node type"] N -->|"Shadowsocks obfs, SSPanel Shadowsocks"| E1["node_info failed"] N --> T["Build the transport: TCP, WebSocket, gRPC, TLS"] T -->|"REALITY, ACME, missing certificate, unknown transport"| E2["initial start failed"] T --> R["Build the protocol user table"] R -->|"XTLS flow, unknown cipher"| E2 R --> B["Bind the TCP port and serve"]
The certificate is read in the transport step, so a TLS node with a missing certificate is refused before its protocol settings are looked at.
Choosing the node type
Section titled “Choosing the node type”node_type is set in katana’s config file, not in the panel. The value is case-insensitive:
| You write | Node type | Notes |
|---|---|---|
V2ray, vmess, vless |
V2ray | Serves VMess, or VLESS when enable_vless = true. |
Trojan |
Trojan | |
Shadowsocks |
Shadowsocks | UniProxy panels only. |
Hysteria2, hysteria, hy2 |
Hysteria 2 | See Hysteria 2 nodes. katana sends hy2 to a UniProxy panel as written, and Xboard does not accept that type, so write Hysteria2 there. |
Any other value, including an empty one, is refused. katana --test prints:
configuration error: unknown node_type "Socks"At startup katana logs node 1: unknown node_type "Socks" and skips that node. The other nodes still start.
A hot reload of a file that contains such a node is refused as a whole: katana applies none of the new file and every node keeps running as it was. The log names the node by its panel, host, ID and, on a UniProxy panel, the type it is asked for:
reload: node newv2board@https://panel.example.com#1/socks: unknown node_type "Socks"; keeping current configThe same applies while a node that was skipped at startup is still in the file: no edit to the file takes effect until you correct that node’s node_type.
A VLESS node on a UniProxy panel looks like this:
[[node]]panel_type = "NewV2board"
[node.api]host = "https://panel.example.com"node_id = 1key = "replace-with-the-panel-key"node_type = "V2ray"enable_vless = true
[node.controller.cert]mode = "file"cert_file = "/etc/katana/fullchain.pem"key_file = "/etc/katana/privkey.pem"On a UniProxy panel the node type is also part of every request: katana sends node_type=vless for a V2ray node with enable_vless = true, and the lowercased node_type otherwise. Xboard finds a node by its ID and this type, so the same node_id asked as vless and as v2ray names two different panel nodes. An edit that changes the type katana asks for, such as turning on enable_vless for a V2ray node, therefore restarts the node on a hot reload, with empty traffic counters; see Node identity and reload.
Transports
Section titled “Transports”The panel’s network value picks the transport. katana lowercases it before matching:
Panel network |
Transport | Behaviour |
|---|---|---|
empty, tcp, raw |
TCP | The protocol runs directly on the TCP connection, or on TLS over it. |
ws, websocket |
WebSocket | Accepts the upgrade only on the configured path. An empty path means /; a path without a leading / gets one, and an ?ed= query is stripped because it configures early data, which katana accepts through Sec-WebSocket-Protocol (up to 16 KiB; more is answered with HTTP 413). When the panel gives a host, the request’s Host header must match it (case-insensitive, port ignored). A wrong path or host is answered with HTTP 404. |
grpc, gun |
gRPC | Serves the gun tunnel on /<serviceName>/Tun and the multi-hunk tunnel on /<serviceName>/TunMulti, with the service name used exactly as the panel gives it. A stream on any other path is reset. Each HTTP/2 stream is one proxied connection, so one TCP connection can carry many. |
httpupgrade |
Refused | node requests kernel-unsupported feature: httpupgrade transport |
splithttp, xhttp |
Refused | node requests kernel-unsupported feature: splithttp transport |
| anything else | Refused | node requests kernel-unsupported feature: transport "quic", naming the value |
Only V2ray nodes and SSPanel Trojan nodes read network. A UniProxy Trojan node is always TCP, and a Shadowsocks node is always TCP.
Whether a node uses TLS depends on its type and, for V2ray, on the panel:
| Node type | TLS |
|---|---|
| V2ray | On when the panel says so: UniProxy tls = 1, SSPanel security = "tls" or "xtls", or tls in the SSPanel legacy string. |
| Trojan | Always, whatever the panel says. |
| Shadowsocks | Never. |
| Hysteria 2 | Always; the TLS handshake is part of QUIC. |
The certificate never comes from the panel. katana reads it from [node.controller.cert], and the panel’s tls_settings, server_name and similar fields are not used:
| Key | Type | Required | Default | Description |
|---|---|---|---|---|
mode | string (enum) | depends | "none" | "none" (no certificate) or "file" (read cert_file and key_file). Case-sensitive. "file" is required for every node that uses TLS; without it a stream node fails with TLS node requires cert.mode = "file" and a Hysteria 2 node with hysteria2 node requires cert.mode = "file". The ACME modes "dns", "http" and "tls" are refused on every stream node, TLS or not: node requests kernel-unsupported feature: ACME cert mode "dns"; on a Hysteria 2 node they fail like any value other than "file". Any other value behaves like "none" on a stream node. |
cert_file | path | depends | "" | PEM certificate chain: the server certificate first, then any intermediates (a fullchain.pem). Required with mode = "file"; if it or key_file is empty, a TLS or Hysteria 2 node fails with TLS node requires cert.cert_file and cert.key_file. Read each time the listener is built, not when the file changes. A missing file fails with the bare OS error, such as No such file or directory (os error 2), which does not name the path. |
key_file | path | depends | "" | PEM private key matching cert_file. Required with mode = "file". Read at the same moments as cert_file; a key that does not match the certificate fails the listener build. |
reject_unknown_sni | bool | no | false | Not implemented, so true is refused rather than silently ignored: the node fails with node requests kernel-unsupported feature: cert.reject_unknown_sni. Leave it false. |
Details of the TLS server:
- Versions: TLS 1.2 and 1.3. Older versions are refused.
- Certificate: the first certificate in
cert_fileis the server certificate, and the rest are sent as the chain. - ALPN: a gRPC node selects
h2, which is what HTTP/2 clients offer. TCP and WebSocket nodes do not configure ALPN, so a client’s ALPN list is accepted without a protocol being selected. - SNI: not checked. Any server name gets the one certificate.
reject_unknown_sni = trueis refused, as the table says.
What katana reads from each panel
Section titled “What katana reads from each panel”Only the fields below shape a stream node’s listener (the Hysteria 2 fields are on the Hysteria 2 nodes page). Every other field in the panel’s answer is ignored, including Xboard’s tls_settings, multiplex, decryption, plugin and plugin_opts, so a setting that exists only there has no effect on a katana node.
| Field | Node types | Use |
|---|---|---|
server_port |
all | The port to listen on. 0 or a missing value fails with newV2board: server port must be > 0. |
network |
V2ray | The transport, from the table above. |
networkSettings |
V2ray, VMess | path (WebSocket), headers.Host (WebSocket host check), serviceName (gRPC), header (TCP, ignored). |
network_settings |
V2ray, VLESS | The same fields, read instead of networkSettings when enable_vless = true. |
tls |
V2ray | 0 or missing: no TLS. 1: TLS. 2: REALITY, refused. |
flow |
V2ray | Must be empty. Any value is refused as an XTLS flow. |
cipher |
Shadowsocks | The cipher, from the Shadowsocks table below. |
server_key |
Shadowsocks | The server PSK of a 2022 cipher, base64. |
obfs |
Shadowsocks | Empty, plain or none (exactly, in lowercase). Anything else is refused. |
A Trojan node reads only server_port here, and is served on TCP with TLS.
katana reads custom_config when the panel reports version 2021.11 or later and [node.api].disable_custom_config is false. Otherwise it reads the legacy server string. When katana should read custom_config but the panel sends none, the node fails with custom_config is empty, disable custom config.
custom_config field |
Node types | Use |
|---|---|---|
offset_port_node |
all | The port to listen on, as a JSON string such as "443". |
network |
V2ray, Trojan | The transport. For Trojan, empty means TCP. |
security |
V2ray | tls or xtls turns TLS on. Ignored for Trojan, which always uses TLS. |
path, host |
V2ray, Trojan | WebSocket path and host check. |
servicename |
V2ray, Trojan | gRPC service name. |
enable_vless |
V2ray | The string "1" serves VLESS. Either this or [node.api].enable_vless is enough. |
flow |
V2ray, Trojan | Must be empty. Any value is refused as an XTLS flow. |
enable_reality |
V2ray, Trojan | A JSON boolean. true is refused as REALITY. |
header |
V2ray | TCP header, ignored. |
The legacy string for a V2ray node is split on ;: the address, the port, the alter ID (ignored), then the network and tls in either order, then |-separated key=value extras (path, host, servicename, headerType). A string with fewer than six parts fails with malformed legacy v2ray server string. The legacy string has no VLESS or flow fields, so for a legacy node katana takes them from [node.api] (enable_vless and vless_flow), and a non-empty vless_flow refuses the node.
192.0.2.10;443;0;ws;tls;path=/ws|host=proxy.example.comFor a Trojan node, katana takes the port from port=: from port=443#8443 it takes the port after # (8443), from port=443 it takes 443. The transport is TCP unless the |-separated extras after the first ; contain a grpc key, whatever its value, which switches it to gRPC with servicename as the service name:
proxy.example.com;port=443#8443|host=proxy.example.com|grpc=1|servicename=tunnelA Shadowsocks node on SSPanel is refused with sspanel: Shadowsocks node type is not supported.
A V2ray node without enable_vless serves VMess.
- AEAD headers only. The legacy MD5 header that goes with
alterIdgreater than0is not implemented. katana gives every user an alter ID of0and ignores the value in the SSPanel legacy string, so clients must usealterId: 0. - Body security:
aes-128-gcmorchacha20-poly1305. A client set toautopicks one of these.noneandzeroare not accepted, and the connection is closed during the handshake. - Clock: a VMess header carries a timestamp, and katana accepts it only within 120 seconds of its own clock. Keep the server’s clock synchronised.
- Users: the account ID is the user’s UUID. A user whose UUID does not parse is skipped with
skipping user 7: uuid is not a valid UUID, and the others are served. - Commands: TCP, UDP, and mux (see Multiplexing).
The Etemenanki page on VMess describes the protocol implementation in more detail.
A V2ray node with enable_vless = true, or with enable_vless = "1" in SSPanel’s custom_config, serves VLESS.
- No flow. XTLS flows such as
xtls-rprx-visionare not implemented. A panelflow(or[node.api].vless_flowwhen SSPanel sends a legacy string) refuses the whole node withnode requests kernel-unsupported feature: VLESS XTLS flow. A client that sends a flow anyway fails its handshake. - Encryption: clients must use
encryption: "none". - Users: the ID is the user’s UUID. Users with an unparseable UUID are skipped, as for VMess.
- Commands: TCP, UDP, and mux.
See VLESS for the protocol implementation.
Trojan
Section titled “Trojan”- Password: the user’s UUID from the panel, exactly as the panel lists it. This is the XrayR convention, and it is the password Xboard gives Trojan clients.
- TLS: always on, so a Trojan node needs
[node.controller.cert]withmode = "file". - Transports: TCP on UniProxy panels; TCP, WebSocket or gRPC on SSPanel.
- UDP: served through Trojan’s UDP associate command.
- Mux: a client signals mux.cool by connecting to
v1.mux.cool, and katana treats that connection as a mux carrier.
See Trojan for the protocol implementation.
Shadowsocks
Section titled “Shadowsocks”A Shadowsocks node is available on UniProxy panels only. It is TCP only: katana binds no UDP socket for it, so a client’s UDP relay does not work through a katana Shadowsocks node.
The panel’s cipher decides between the two Shadowsocks generations:
cipher |
Family | User’s key |
|---|---|---|
aes-128-gcm, aes-256-gcm |
AEAD (SIP004) | The user’s UUID, as the password |
chacha20-ietf-poly1305 (also chacha20-poly1305) |
AEAD (SIP004) | The user’s UUID, as the password |
xchacha20-ietf-poly1305 (also xchacha20-poly1305) |
AEAD (SIP004) | The user’s UUID, as the password |
2022-blake3-aes-128-gcm |
2022 multi-user (SIP022) | The first 16 characters of the UUID |
2022-blake3-aes-256-gcm |
2022 multi-user (SIP022) | The first 32 characters of the UUID |
2022-blake3-chacha20-poly1305 |
Refused | The 2022 spec defines multiple users only for the AES ciphers |
| anything else | Refused | node requests kernel-unsupported feature: shadowsocks cipher "rc4-md5" |
The AEAD names are matched case-insensitively, and aead_aes_128_gcm, aead_aes_256_gcm and aead_chacha20_poly1305 are accepted as aliases. The 2022 names must be written exactly as shown, in lowercase.
Shadowsocks 2022 keys
Section titled “Shadowsocks 2022 keys”A 2022 node always runs in multi-user mode, with one port for every user. Each client connection carries an identity header that tells katana which user it is. Two keys are involved:
- The server key is the panel’s
server_key, a base64 PSK of 16 bytes for2022-blake3-aes-128-gcmand 32 bytes for2022-blake3-aes-256-gcm. A longer key is folded down to that length; a shorter one fails withshadowsocks-2022: PSK too short (0 < 32), and invalid base64 fails with adecode PSK:error. - The user key is the first 16 or 32 characters of the user’s UUID string, used as raw bytes.
This is the same derivation Xboard uses when it builds the client’s password: base64(first N characters of the UUID), joined to the server key with a colon. For the UUID 11111111-2222-3333-4444-555555555555 on a 2022-blake3-aes-128-gcm node:
user key bytes: 11111111-2222-33client password: <server_key>:MTExMTExMTEtMjIyMi0zMw==You do not configure any of this in katana. It only has to agree with the panel, and with Xboard it does.
Obfuscation and plugins
Section titled “Obfuscation and plugins”katana accepts obfs values of empty, plain and none. Any other value is refused when katana reads the node info, before anything is built:
node 1: node_info failed: newV2board: shadowsocks obfs "http" is not supportedkatana does not read plugin or plugin_opts. A SIP003 plugin configured in the panel is not applied, and clients that use it cannot connect.
See Shadowsocks for the protocol implementation.
Multiplexing
Section titled “Multiplexing”VMess, VLESS and Trojan nodes accept mux.cool connections from Xray-compatible clients. There is no setting to turn this on: katana recognises the mux command (VMess and VLESS) or the v1.mux.cool destination (Trojan) on every connection.
- Each sub-connection inside a mux carrier is its own flow: it is routed, audited, rate-limited and counted against the authenticated user exactly like a separate connection.
- UDP sub-connections are supported, including XUDP, where each packet carries its own address.
- A carrier holds up to 256 sub-connections at once. A client that opens another is refused that one sub-connection; the carrier and its other sub-connections continue.
- Only mux.cool is implemented. sing-box multiplexing (Xboard’s
multiplexsetting) is neither read nor supported.
Refused features
Section titled “Refused features”katana fails closed: when a panel asks for something it cannot deliver, the node does not start, rather than starting without the feature the clients expect. This table lists every refusal and the message katana logs:
| Feature | Where it comes from | Error |
|---|---|---|
| REALITY | UniProxy tls = 2, SSPanel enable_reality = true |
node requests kernel-unsupported feature: REALITY |
| XTLS flow | Panel flow; [node.api].vless_flow for an SSPanel legacy string |
node requests kernel-unsupported feature: VLESS XTLS flow |
| HTTPUpgrade | network = "httpupgrade" |
node requests kernel-unsupported feature: httpupgrade transport |
| SplitHTTP, XHTTP | network = "splithttp" or "xhttp" |
node requests kernel-unsupported feature: splithttp transport |
| Other transports | network values such as quic, kcp, h2 |
node requests kernel-unsupported feature: transport "quic" |
| PROXY protocol | A node that asks to accept it; no panel field sets this in katana 3.0.1 | node requests kernel-unsupported feature: PROXY protocol accept |
| ACME certificates | [node.controller.cert].mode of dns, http or tls |
node requests kernel-unsupported feature: ACME cert mode "dns" |
| SNI enforcement | [node.controller.cert].reject_unknown_sni = true |
node requests kernel-unsupported feature: cert.reject_unknown_sni |
| Unknown Shadowsocks cipher | Panel cipher |
node requests kernel-unsupported feature: shadowsocks cipher "rc4-md5" |
| 2022 ChaCha20 multi-user | cipher = "2022-blake3-chacha20-poly1305" |
shadowsocks-2022: multi-user requires an aes-gcm method, or PSK too short without a key |
| Shadowsocks obfs | Panel obfs other than plain or none |
newV2board: shadowsocks obfs "http" is not supported |
| Shadowsocks on SSPanel | node_type = "Shadowsocks" with panel_type = "SSPanel" |
sspanel: Shadowsocks node type is not supported |
| TLS without a file certificate | A TLS node whose mode is not "file", including the default "none" |
TLS node requires cert.mode = "file" |
| TLS without paths | mode = "file" with an empty cert_file or key_file |
TLS node requires cert.cert_file and cert.key_file |
When a node is refused
Section titled “When a node is refused”Most of these settings come from the panel, so katana --test cannot see them: it checks the config file, builds the outbounds, panel clients and routers, and validates [node.hysteria], but it does not contact the panel. A config for a REALITY node passes --test and fails at startup.
The error appears in the log with the node’s ID. A refusal found while building the listener looks like this:
ERROR katana::manager::node: node 1: initial start failed: node requests kernel-unsupported feature: REALITY; retrying in 1sA refusal found while parsing the panel’s answer, such as a Shadowsocks obfs, is logged at startup as node 1: node_info failed: followed by the message and the same ; retrying in note. If it appears later, on a poll, katana logs a warning (node 1: node_info:) and keeps serving the settings it already has. Other nodes in the same file are not affected.
A node that has not come up keeps trying. katana waits 1 second after the first failed attempt and doubles the wait after each further failure, up to 60 seconds, or up to update_periodic when that is shorter. Every attempt reads the node info and the user list from the panel afresh, so once you fix the node in the panel it comes up at the next attempt, without a restart of katana. Saving a change to the node’s [[node]] table in the config file starts the next attempt at once.
If a running node’s settings change in the panel to something refused, the node loses its listener and logs node 1: rebuild failed: followed by the reason. It tries again at every poll, so the node comes back once the panel is fixed. Troubleshooting covers the other startup errors.
Tested clients
Section titled “Tested clients”katana’s integration tests run the real katana binary against a fake panel and connect with independent client implementations. Every test sends a payload through the node and checks the echoed bytes. The Xray tests also check that the panel received a traffic report for the user.
| Client | Protocol | Transport | TLS | Mux |
|---|---|---|---|---|
| Xray-core | VMess | TCP, WebSocket, gRPC | No | TCP only |
| Xray-core | VMess | TCP | Yes | No |
| Xray-core | VLESS | WebSocket | Yes | Yes |
| Xray-core | VLESS | gRPC | Yes | No |
| Xray-core | Trojan | TCP | Yes | Yes |
| Official Hysteria 2 client | Hysteria 2 | QUIC | Yes | Not applicable |
The Xray clients use VMess security auto and VLESS encryption none, and verify katana’s certificate by pinning its hash. The Xray mux tests use a concurrency of 8. The Hysteria 2 client authenticates with nothing but the user’s UUID, skips certificate verification, and opens two streams on one connection; its node takes its port from [node.hysteria] rather than from the panel.
No test in katana drives a Shadowsocks node with an external client.