Wraps up the 'help wanted' backend items from the SOCKS5 scaffold PR
(#4452) so the dedicated socks inbound is a fully functional protocol
end-to-end, not just a model constant.
xray/api.go AddUser
-------------------
Live add-user via the gRPC HandlerService now handles 'socks' and
'http' as first-class protocols. Previously these fell through the
default branch and returned nil, so adding a new password-mode account
to a running socks/http inbound silently required a full xray restart.
- New 'socks' case constructs a proxy/socks.Account{Username, Password}
from the panel-side map keys 'user' and 'pass' (matching how
Inbound.SocksSettings.SocksAccount serialises in
frontend/src/models/inbound.js). Username is required, password is
optional so a no-pass account is still expressible if Xray ever
allows it on a specific build.
- New 'http' case mirrors the same shape via proxy/http.Account.
The dedicated HTTP inbound isn't surfaced standalone in the panel
UI yet, but the runtime API is symmetric with socks and several
follow-up plans (e.g. exposing HTTP as a separate inbound) become
one-line UI work instead of a backend refactor.
Both branches reuse the existing getRequiredUserString /
getOptionalUserString helpers, so a malformed userMap surfaces the
same typed error message as the vless / vmess paths above.
web/service/port_conflict.go
----------------------------
inboundTransports() now folds 'socks' into the same branch that already
handles 'mixed': settings.udp=true means the inbound holds both tcp and
udp on the listening port (socks5 UDP ASSOCIATE), settings.udp=false
keeps it tcp-only. Without this, a socks+udp inbound would silently be
classified as tcp-only and the validator would let a hysteria2 udp
inbound coexist with it on the same port — both processes would then
race for the udp socket at xray start, with one of them quietly failing.
The two protocols share the exact same settings JSON shape for this
field (it's the same proxy/socks server type under the hood), so the
sane thing is to merge the case clauses rather than copy/paste the
type-assertion. Comment updated to spell out why.
web/service/tgbot.go
--------------------
Add model.Socks to the excludedProtocols set in getInboundsAddClient
so the Telegram bot doesn't offer a dedicated SOCKS inbound when the
admin asks 'add a client to which inbound?'. SOCKS inbounds, like
Mixed/HTTP, don't produce a per-client subscription URL (see the
existing link-less branch in sub/subService.go::GetLink), so any
client attached via the bot would have no way to actually subscribe.
Added a header comment explaining the criterion so future protocols
fall into the right bucket without an audit.
Tests
-----
web/service/port_conflict_test.go gains four cases that pin the new
behaviour at the transport-bits level (TestInboundTransports):
- socks + udp=true -> tcp|udp (matches Mixed)
- socks + udp=false -> tcp only
- socks + missing settings -> tcp only
- socks + empty settings -> tcp only
…plus two end-to-end conflict checks that mirror the existing
shadowsocks/mixed coverage:
- TestCheckPortConflict_SocksUDPBlocksUDPNeighbour: a socks+udp
inbound on port N must clash with a hysteria2/udp on the same
port. Catches a regression where the Socks branch is dropped
from inboundTransports.
- TestCheckPortConflict_SocksTCPCoexistsWithUDPNeighbour: a
socks-tcp-only inbound must still let a hysteria2/udp neighbour
bind the same port. Mirrors the #4103 vless+hysteria2 coexistence
case.
Out-of-scope (still tracked in the PR description)
--------------------------------------------------
- Sub-link generation (sub/subService.go GetLink): SOCKS deliberately
stays link-less for the reasons documented in the previous commit;
no socks:// scheme is consistently understood across xray/v2ray
client ecosystems.
- Routing UI: routing rules in this fork already accept any inbound
tag, so SOCKS inbounds are routable as-is. A dedicated
'protocol == socks' helper in the routing rule editor is a UX
follow-up, not a correctness gap.
- Translations: protocol labels are rendered raw in this fork; no
per-locale label key exists for vmess/vless/mixed either, so adding
one only for socks would be inconsistent.
Different nodes are different machines, so same port + transport across
NodeIDs shouldn't conflict. resolveInboundTag now keeps a caller-supplied
unique tag verbatim so central and node panels stay in agreement instead
of regenerating into a UNIQUE constraint failure on sync.
the panel rejected configurations like vless reality on tcp/443 and
hysteria2 on udp/443 even though those are independent sockets in
linux. the old checkPortExist looked only at port + listen.
inboundTransports now classifies each inbound by L4 transport:
hysteria/hysteria2/wireguard are udp; streamSettings.network=kcp is
udp; shadowsocks reads settings.network ("tcp"/"udp"/"tcp,udp");
mixed (socks/http) adds udp when settings.udp is true; everything
else is tcp. checkPortConflict pulls every row on the same port and
only flags a conflict when transport masks overlap. the listen-
overlap rule (specific addr vs any-addr on the same port) is kept.
inbounds.tag has a unique DB constraint and the controller derives
tags from port ("inbound-443"). without disambiguation a second
inbound on the same port would still hit a unique-constraint error.
generateInboundTag keeps the historical "inbound-<port>" shape when
the base tag is free, so existing routing rules survive the upgrade
unchanged, and appends "-tcp"/"-udp" only when the base is already
taken.
closes#4103.