lx(1.14): SPEC 019 urltest mode + sticky load-balancing
Add a `mode` to the urltest group so it can distribute traffic instead of only
picking the lowest-delay node, with optional per-flow stickiness.
- mode: least_test (default, unchanged) | round_robin (rotate across live nodes)
| least_connection (reserved, phase 2 — rejected at config time).
- round_robin selects once per connection over the tag-sorted live set (nodes with
a fresh URL-test result supporting the network); UDP/QUIC sessions stay on one
node; first usable outbound is the fallback when nothing is live. The legacy
selectedOutbound* cache path is untouched — balancing is a separate branch in
DialContext/ListenPacket.
- sticky {mode, timeout, cap, hash}: binds one flow to one node. hash components
process|domain|source_ip|dest_ip|dest_port concatenate in order; absent -> "",
all-empty key -> one fixed node (keyless flows never rotate). mode jumphash
(default, stateless consistent hash — ~1/n remap on node-set change) or ttlmap
(key->node table, lazy + ticker eviction, 2000 LRU cap, 10m TTL, dead-node re-pin).
Reuses the existing urltest health ticker/history as the single liveness source;
no new probing. Now() reports the last-picked tag in balanced modes.
Tests (go test -race, 15 cases): distribution, dead-node skip, all-dead fallback,
jumphash stability + empty-key fixed node, ttlmap stick/expire/cap/dead-repick,
key building, validation. The race detector caught a real bug in the sticky
sweeper (read t.ticker unlocked while close() nilled it) — fixed by passing the
channels into the goroutine, mirroring URLTestGroup.loopCheck.
Also folds the SPEC 016 connections-map mutex (ebf9cc07) into the rc.11 changelog
section, which had not yet shipped in a release.
This commit is contained in:
@@ -14,7 +14,15 @@
|
||||
"interval": "",
|
||||
"tolerance": 0,
|
||||
"idle_timeout": "",
|
||||
"interrupt_exist_connections": false
|
||||
"interrupt_exist_connections": false,
|
||||
|
||||
"mode": "least_test",
|
||||
"sticky": {
|
||||
"mode": "jumphash",
|
||||
"timeout": "10m",
|
||||
"cap": 2000,
|
||||
"hash": ["process", "domain"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -47,3 +55,60 @@ The idle timeout. `30m` will be used if empty.
|
||||
Interrupt existing connections when the selected outbound has changed.
|
||||
|
||||
Only inbound connections are affected by this setting, internal connections will always be interrupted.
|
||||
|
||||
#### mode
|
||||
|
||||
!!! quote "sing-box-lx"
|
||||
|
||||
An `lx` extension (SPEC 019), not present in upstream sing-box.
|
||||
|
||||
Load-balancing mode — how a node is chosen per connection:
|
||||
|
||||
- `least_test` (default, also when empty): pick the lowest-delay node. This is upstream's
|
||||
behaviour; `sticky` is ignored.
|
||||
- `round_robin`: rotate across the live nodes (those with a fresh URL-test result that
|
||||
support the network). Selection happens once per connection; a UDP/QUIC session stays on
|
||||
one node.
|
||||
- `least_connection`: not implemented yet (planned); configuring it is an error.
|
||||
|
||||
When no node is live, the first usable outbound is used as a fallback.
|
||||
|
||||
#### sticky
|
||||
|
||||
!!! quote "sing-box-lx"
|
||||
|
||||
An `lx` extension (SPEC 019), not present in upstream sing-box.
|
||||
|
||||
Binds one flow to one node in `round_robin` / `least_connection` mode, so the same key
|
||||
(e.g. the same destination domain) always reaches the same node. Omit it (or leave `hash`
|
||||
empty) for no stickiness.
|
||||
|
||||
##### sticky.mode
|
||||
|
||||
The binding mechanism:
|
||||
|
||||
- `jumphash` (default): stateless consistent hash over the live nodes. No table; changing
|
||||
the live-node count remaps only ~1/n of keys. `timeout` / `cap` are ignored.
|
||||
- `ttlmap`: a `key → node` table. A key sticks to its node while that node is alive and the
|
||||
entry is younger than `timeout`; a dead node re-pins to a surviving one.
|
||||
|
||||
##### sticky.timeout
|
||||
|
||||
`ttlmap` entry TTL. `10m` will be used if empty. Ignored for `jumphash`.
|
||||
|
||||
##### sticky.cap
|
||||
|
||||
`ttlmap` maximum entries; the oldest are evicted past this. `2000` will be used if empty.
|
||||
Ignored for `jumphash`.
|
||||
|
||||
##### sticky.hash
|
||||
|
||||
Key components, concatenated in order. An absent component contributes an empty string;
|
||||
when all are empty the key is `""`, which maps to a single fixed node (so keyless flows do
|
||||
not rotate). Allowed values:
|
||||
|
||||
- `process`: the source process (Android package name, else executable path).
|
||||
- `domain`: the destination domain (empty for IP destinations).
|
||||
- `source_ip`: the source IP.
|
||||
- `dest_ip`: the destination IP.
|
||||
- `dest_port`: the destination port.
|
||||
|
||||
Reference in New Issue
Block a user