after #4083 the staleness window is 30 minutes, which still lets an ip
that stopped connecting a few minutes ago sit in the db blob and keep
the protected slot on the ascending sort. the ip that is actually
connecting right now gets classified as excess and sent to fail2ban,
and never lands in inbound_client_ips.ips so the panel doesnt show it
until you clear the log by hand.
only count ips observed in the current scan toward the limit. db-only
entries stay in the blob for display but dont participate in the ban
decision. live subset still uses the "protect oldest, ban newcomer"
rule.
closes#4091. followup to #4077.
* Fix Hysteria External Proxy + include Hysteria in Clash subscription (#4053)
Two related gaps on the Hysteria side of the subscription layer:
1) `genHysteriaLink` ignored `externalProxy` entirely, so an admin who
pointed a Hysteria inbound at an alternate endpoint (e.g. a CDN
hostname forwarding UDP back to the node) still got a link with the
original server address. Mirror what `genVlessLink` / `genTrojanLink`
already do: fan out one link per entry, substituting `dest` / `port`
and picking up the entry's remark suffix. As a bonus, the salamander
obfs password is now copied into the URL too — the panel-side link
generator already did this, so the subscription output was lagging
behind it.
2) `buildProxy` in `subClashService.go` had a protocol switch with cases
for VMESS / VLESS / Trojan / Shadowsocks and a `default: return nil`.
Hysteria inbounds fell into the default branch and silently vanished
from the Clash YAML. Route Hysteria to a dedicated
`buildHysteriaProxy` helper before the transport/security helpers run
(applyTransport / applySecurity model xray streams, which Hysteria
doesn't use).
`buildHysteriaProxy` reads `inbound.StreamSettings` directly instead
of going through `streamData` / `tlsData`, because those prune
fields (`allowInsecure`, the salamander `finalmask.udp` block) that
the mihomo Hysteria proxy wants preserved. Output shape matches
mihomo's expectations:
type: hysteria2 # or "hysteria" for v1
password / auth-str: <client auth>
sni, alpn, skip-cert-verify, client-fingerprint
obfs: salamander
obfs-password: <finalmask.udp[salamander].settings.password>
The existing `getProxies` fanout over `externalProxy` already plugs in
for Clash, so with Hysteria now recognised, External Proxy entries
also flow through to the Clash output for Hysteria inbounds.
Closes#4053
* gofmt: align map keys in buildHysteriaProxy
---------
Co-authored-by: pwnnex <eternxles@gmail.com>
`getXraySetting` builds its response as
{ "xraySetting": <db value>, "inboundTags": ..., "outboundTestUrl": ... }
and embeds the raw DB value as the `xraySetting` field without
checking whether the stored value already has that exact shape.
The frontend pulls the textarea content from `result.xraySetting`
and saves it back verbatim. If the DB ever ends up holding the
response-shaped wrapper instead of a real xray config (older
installs where this happened at least once, users who imported a
copy-pasted response into the textarea, a botched migration, etc.),
the next save nests another layer, the one after that nests a
third, and the Vue-side JSON.parse of the resulting blob silently
fails — the Xray Settings page goes blank.
Fix both ends of the round-trip:
* Add `service.UnwrapXrayTemplateConfig`. It peels off any number of
`xraySetting`-keyed layers, leaving a real xray config behind.
The check is conservative: if the outer object already contains
any top-level xray key (`inbounds`, `outbounds`, `routing`, `api`,
`dns`, `log`, `policy`, `stats`), it is returned unchanged, and
there is a depth cap to avoid pathological inputs.
* `SaveXraySetting` unwraps before validation so a round-tripped
wrapper from an already-corrupted page can no longer re-poison
the DB on save.
* `getXraySetting` unwraps on read and, when it finds a wrapper,
rewrites the DB with the corrected value. Existing broken installs
heal themselves on the next visit to the page.
Includes unit tests for the passthrough, single-wrap, multi-wrap,
string-encoded-inner, and false-positive cases.
Co-authored-by: pwnnex <eternxles@gmail.com>
* Fix: propagate xhttp xPadding settings into generated subscription links
The four `genXLink` helpers in `sub/subService.go` only copied `path`,
`host` and `mode` out of `xhttpSettings` when building vmess:// /
vless:// / trojan:// / ss:// URLs. Everything else — `xPaddingBytes`,
`xPaddingObfsMode`, `xPaddingKey`, `xPaddingHeader`,
`xPaddingPlacement`, `xPaddingMethod` — was silently dropped.
That meant an admin who set, say, `xPaddingBytes: "80-600"` plus obfs
mode with a custom `xPaddingKey` on the inbound had a server config
that no client could match from the copy-pasted link: the client kept
the xray/sing-box internal defaults (`100-1000`, `x_padding`,
`Referer`), hit the server, and was rejected by
invalid padding (queryInHeader=Referer, key=x_padding) length: 0
The user-visible symptom on OpenWRT / Podkop / sing-box was
"xhttp inbound just won't connect" — no obvious pointer to what was
actually wrong because the link itself *looks* complete.
Fix:
* New helper `applyXhttpPaddingParams(xhttp, params)` writes
`x_padding_bytes=<range>` (flat, sing-box family reads this) and
an `extra=<url-encoded-json>` blob carrying the full set of xhttp
settings (xray-core family reads this). Both encodings are emitted
side-by-side so every mainstream client can pick at least one up.
* All four link generators (`genVmessLink` via the obj map,
`genVlessLink`, `genTrojanLink`, `genShadowsocksLink`) now invoke
the copy.
* Obfs-only fields (`xPaddingKey`, `xPaddingHeader`,
`xPaddingPlacement`, `xPaddingMethod`) are only included when
`xPaddingObfsMode` is actually true and the admin filled them in.
An inbound with no custom padding produces exactly the same URL
as before — existing subscriptions are unaffected.
* Also propagate xhttp xPadding settings into the panel's own Info/QR links
The previous commit covered the subscription service
(sub/subService.go). The admin-panel side — the "Copy URL" / QR /
Info buttons inside inbound details — has four more
xhttp-emitting link generators in `web/assets/js/model/inbound.js`
(`genVmessLink`, `genVLESSLink`, `genTrojanLink`, `genSSLink`) that
had the exact same gap: only `path`, `host` and `mode` were copied.
Mirror the server-side fix on the client:
* Add two static helpers on `Inbound`:
- `Inbound.applyXhttpPaddingToParams(xhttp, params)` for
`vless://` / `trojan://` / `ss://` style URLs — writes
`x_padding_bytes=<range>` (sing-box family) and
`extra=<url-encoded-json>` (xray-core family).
- `Inbound.applyXhttpPaddingToObj(xhttp, obj)` for the VMess base64
JSON body — sets the same fields directly on the object.
* Call them from all four link generators so an admin who enables
obfs mode + a custom `xPaddingKey` / `xPaddingHeader` actually
gets a working URL from the panel.
* Only non-empty fields are emitted, so default inbounds produce
exactly the same URL as before.
Also fixes a latent positional-args bug in
`web/assets/js/model/outbound.js`: both VMess-JSON (L933) and
`fromParamLink` (L975) were calling
`new xHTTPStreamSettings(path, host, mode)` — but the 3rd positional
arg of the constructor is `headers`, not `mode`, so `mode` was
landing in the `headers` slot and the actual `mode` field stayed at
its default. Construct explicitly and set `mode` by name; while
here, also pick up `x_padding_bytes` and the `extra` JSON blob from
the imported URL so the symmetric case of importing a padded link
works too.
---------
Co-authored-by: pwnnex <eternxles@gmail.com>
`genHysteriaLink` was calling `.join(',')` on
`this.stream.tls.settings.echConfigList`, but that field is bound to an
`<a-input>` (single-line string) in `tls_settings.html` and defaults to
`''` in `TlsStreamSettings.Settings`. Calling `.join()` on a string
throws `TypeError: echConfigList.join is not a function`, which breaks
the Info / QR buttons for every hysteria / hysteria2 inbound.
All three sibling link generators (`genVmessLink`, `genVlessLink`,
`genTrojanLink`) already pass the value directly:
params.set("ech", this.stream.tls.settings.echConfigList)
`URLSearchParams.set` will stringify arrays with `,` on its own, so the
same one-liner works for both string and array inputs. Align
`genHysteriaLink` with the other three.
Fixes#4063
Co-authored-by: pwnnex <eternxles@gmail.com>