Proxy Groups & Naming Convention¶
When a user calls updateSubscription(clientId, ...), the panel generates a full Leaf .conf document and the SDK renders it into <data>/leaf-api/cache/current_temp.conf. This page explains the naming convention the panel uses for proxies and groups so your UI can parse the output of the Runtime HTTP API and build meaningful country / node pickers without hard-coding anything.
The reference implementation is generated by the Panel — every sample project (Android, Tauri) parses its output the same way.
1. Anatomy of a rendered config¶
[Proxy]
RU_MOSCOW_1_2_3_4_AMUX_TROJAN_WEBSOCKET_PLAIN_443_FRAGMENT = trojan, ...
RU_MOSCOW_1_2_3_4_AMUX_TROJAN_WEBSOCKET_PLAIN_443 = trojan, ...
RU_MOSCOW_1_2_3_4_AMUX_TROJAN_WEBSOCKET_PLAIN_443_DIRECT = trojan, ...
...
[Proxy Group]
RU_MOSCOW_1_2_3_4_CDN = failover, RU_MOSCOW_1_2_3_4_AMUX_TROJAN_WEBSOCKET_PLAIN_443_FRAGMENT, ..., health-check=true, ...
RU_MOSCOW_1_2_3_4_DIRECT = failover, RU_MOSCOW_1_2_3_4_AMUX_TROJAN_WEBSOCKET_PLAIN_443_DIRECT, ..., health-check=true, ...
RU_MOSCOW_1_2_3_4_MPTP = mptp, RU_MOSCOW_1_2_3_4_CDN, RU_MOSCOW_1_2_3_4_DIRECT, mptp-io-write-size=4096, ...
RU_MOSCOW_1_2_3_4 = failover, RU_MOSCOW_1_2_3_4_CDN, RU_MOSCOW_1_2_3_4_DIRECT, RU_MOSCOW_1_2_3_4_MPTP, ...
...
RU_AUTO = failover, RU_MOSCOW_1_2_3_4, RU_SPB_5_6_7_8, ...
RU = select, RU_AUTO, RU_MOSCOW_1_2_3_4, RU_SPB_5_6_7_8, ...
AUTO = failover, RU, DE, NL, ...
OUT = select, AUTO, RU, DE, NL, ...
This produces a clean three-level hierarchy:
OUT (select: AUTO + every country)
├── AUTO (failover across all countries)
└── <COUNTRY> (select: <COUNTRY>_AUTO + each node)
├── <COUNTRY>_AUTO (failover across nodes)
└── <NODE> (failover / speedtest / tryall per user preference)
├── <NODE>_CDN (failover across CDN-reachable proxies)
├── <NODE>_DIRECT (failover across direct-IP proxies)
└── <NODE>_MPTP (mptp bonding the above)
2. Naming rules¶
Node tag¶
Example: RU_MOSCOW_1_2_3_4.
Proxy tag (individual actor)¶
Qualifiers emitted by the Panel:
| Qualifier | Meaning |
|---|---|
| (none) | Baseline proxy, uses the node's external domain + the public TLS port. |
_FRAGMENT |
Same as baseline but with TLS / host-fragmentation enabled. |
_CDN |
Targets a CDN-fronted entry (external domain + CDN port). |
_DIRECT |
Connects to the node IP directly, bypassing the CDN. |
_ADDITIONAL_<port> |
Baseline proxy on an alternate public port. |
_FRAGMENT_ADDITIONAL_<port> |
Fragmented version on an alternate public port. |
_CDN_ADDITIONAL_<port> |
CDN version on an alternate public port. |
Inbound types correspond to protocol+transport combinations. A non-exhaustive list (versions noted indicate the minimum Leaf core version that supports the type):
INBOUND_TYPE |
Protocol | Transport |
|---|---|---|
AMUX_TROJAN_HTTPUPGRADE_PLAIN |
Trojan | HTTPUpgrade + AMux |
AMUX_TROJAN_WEBSOCKET_PLAIN |
Trojan | WebSocket + AMux |
AMUX_TROJAN_WEBSOCKET_ED_PLAIN |
Trojan | WebSocket early-data + AMux (≥ 0.3.5) |
AMUX_VMESS_HTTPUPGRADE_PLAIN |
VMess | HTTPUpgrade + AMux (≥ 0.4.3) |
AMUX_VMESS_WEBSOCKET_PLAIN |
VMess | WebSocket + AMux (≥ 0.4.3) |
AMUX_VMESS_WEBSOCKET_ED_PLAIN |
VMess | WebSocket early-data + AMux (≥ 0.4.3) |
H2_TROJAN_PLAIN |
Trojan | HTTP/2 |
H2_VMESS_PLAIN |
VMess | HTTP/2 (≥ 0.4.3) |
GRPC_TROJAN_PLAIN |
Trojan | gRPC (≥ 0.2.8) |
GRPC_VMESS_PLAIN |
VMess | gRPC (≥ 0.4.3) |
XHTTP_TROJAN_PLAIN |
Trojan | xHTTP (≥ 0.2.9) |
XHTTP_VMESS_PLAIN |
VMess | xHTTP (≥ 0.4.3) |
REALITY_TROJAN |
Trojan | REALITY |
REALITY_VLESS_TLS |
VLESS | REALITY |
REALITY_XHTTP |
VLESS | REALITY + xHTTP |
FAKETCP_TROJAN_TLS |
Trojan | FakeTCP |
QUIC_TROJAN_TLS |
Trojan | QUIC |
Group tags¶
| Pattern | Strategy | Description |
|---|---|---|
<NODE>_CDN |
failover |
All CDN-reachable proxies for the node. |
<NODE>_DIRECT |
failover |
All direct-IP proxies for the node. |
<NODE>_MPTP |
mptp |
Multi-path bonding of CDN + DIRECT sub-groups (when isMptpSupported). |
<NODE> |
failover / speedtest / tryall |
Top-level node group; strategy depends on useTryAll and useSpeedtest. |
<COUNTRY>_AUTO |
failover |
All nodes in a country auto-sorted by health. |
<COUNTRY> |
select |
Country level — <COUNTRY>_AUTO + each node inside. |
AUTO |
failover |
Global fallback across all countries. |
OUT |
select |
Root group — AUTO + every country. This is what the routing FINAL rule targets. |
3. Parsing recipe (client-side)¶
Given those conventions, a typical UI only needs to query three endpoints to build a full outbound picker:
GET /api/v1/app/outbound/list → high-level groups & types
GET /api/v1/app/outbound/selects?outbound=OUT → list of countries (+ AUTO)
GET /api/v1/app/outbound/selects?outbound=<COUNTRY> → nodes in the country (+ <COUNTRY>_AUTO)
TypeScript example¶
const api = new ApiClient(port);
// 1. Ask for the currently-selected country/subgroup.
const { selected: currentCountry } = await api.getCurrentSelectOutboundItem('OUT');
// 2. Pull the top list (countries).
const countries = (await api.getSelectOutboundItems('OUT')).outbounds
.filter((o) => o.name !== 'AUTO');
// 3. Pull nodes for the selected country.
const { outbounds: nodes } = await api.getSelectOutboundItems(currentCountry);
// 4. Let the user pick.
await api.setSelectOutboundItem(currentCountry, 'RU_MOSCOW_1_2_3_4');
// Also reflect the change at the top level so OUT points at the new country.
await api.setSelectOutboundItem('OUT', currentCountry);
Kotlin example¶
fun changeSelectedOutbound(outbound: String) = viewModelScope.launch(Dispatchers.IO) {
val subgroup = _selectedSubgroupTag.value ?: "OUT"
api.setSelectOutboundItem(subgroup, outbound)
if (subgroup != "OUT") {
// Bubble the selection up so routing still uses the current country group.
api.setSelectOutboundItem("OUT", subgroup)
}
}
Displaying country flags¶
Because node tags start with the two-letter ISO country code, you can map them to flags:
const code = nodeTag.split('_')[0].toLowerCase(); // "RU_MOSCOW_..." -> "ru"
const flag = `https://flagcdn.com/24x18/${code}.png`;
Pinging all candidates in parallel¶
val pings = currentOutbounds.map { outbound ->
async(Dispatchers.IO) {
val h = api.getOutboundHealth(outbound.name)
outbound.name to (h.tcpMs ?: h.udpMs)
}
}.awaitAll().toMap()
See the open-source samples for complete implementations — leaf-desktop/src/store/outbounds.ts and leaf-android/.../LeafViewModel.kt.
4. What the subscription-flag switches change¶
updateSubscription(tls, fragment, clientId, enable_speedtest, enable_try_all, cb) re-runs the panel generator. The enable_speedtest / enable_try_all flags alter only the <NODE> group strategy:
| Flags | Group strategy for <NODE> |
|---|---|
Both false |
failover, <CDN>, <DIRECT>, <MPTP>, … |
enable_speedtest = true |
speedtest, …, check-interval=1800, test-url=https://surfshield.org/25mb_file.bin, test-timeout=5 |
enable_try_all = true |
tryall, … |
<COUNTRY>_AUTO, AUTO, OUT are unchanged — so you can always count on OUT existing as a select group with countries as members.
5. Priority tagging (per region)¶
The Panel's generator re-orders actors inside every group based on the user's country so that the most resilient protocols for that market are tried first. You do not need to reproduce this on the client — it is baked into the generated config. For reference:
| Country | Preferred order (first → last) |
|---|---|
RU |
FAKETCP_TROJAN_TLS, QUIC_TROJAN_TLS, REALITY_VLESS_TLS, REALITY_TROJAN, REALITY_XHTTP, XHTTP_TROJAN_PLAIN.*FRAGMENT, GRPC_TROJAN_PLAIN.*FRAGMENT, GRPC_VMESS_PLAIN.*FRAGMENT |
IR |
FAKETCP_TROJAN_TLS, QUIC_TROJAN_TLS, REALITY_VLESS_TLS, REALITY_TROJAN, REALITY_XHTTP, XHTTP_TROJAN_PLAIN.*FRAGMENT, AMUX_TROJAN_HTTPUPGRADE_PLAIN.*FRAGMENT, AMUX_TROJAN_WEBSOCKET_PLAIN.*FRAGMENT |
| default | FAKETCP_TROJAN_TLS, QUIC_TROJAN_TLS, REALITY_VLESS_TLS, REALITY_TROJAN, REALITY, XHTTP_TROJAN_PLAIN.*DIRECT, XHTTP_VMESS_PLAIN.*DIRECT, AMUX_TROJAN_HTTPUPGRADE_PLAIN.*DIRECT |
6. Minimal safety rules for client code¶
- Treat every tag as opaque except for the
OUTandAUTOconstants — do not parse internals to drive business logic. - The only guaranteed group is
OUT(select). Everything else depends on what the user's country produced. selectOutboundItemmay return404if the group was renamed by a subscription refresh; handle it by re-fetchingoutbound/list.- When the user picks a node inside a country, also set
OUT → <COUNTRY>so the top-levelFINALrule uses the right country path. Both samples do this.
7. Related documentation¶
- Leaf Core & Protocols — what each inbound type means.
- Runtime HTTP API — endpoint reference.
- Android SDK / Desktop (Rust) — consuming these endpoints from a real app.