mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 20:54:14 +00:00
feat(inbound): scaffold dedicated SOCKS5 inbound protocol
Adds an initial, intentionally minimal scaffold for a dedicated SOCKS5
inbound (Xray 'socks' protocol, RFC 1928). The existing 'mixed' inbound
already supports SOCKS+HTTP on the same port, but several deployments
need a pure SOCKS5 inbound — for example chained outbounds and clients
that don't tolerate HTTP CONNECT on the same listener.
This PR is purposely a scaffold so other contributors can finish the
full integration (sub link generation, routing UI helpers, Xray runtime
AddUser hooks, translations, docs, tests). It does NOT claim feature
completeness.
What's included
---------------
Backend (Go):
* database/model/model.go: add Socks model.Protocol constant ('socks')
next to Mixed, with a doc comment explaining the difference vs Mixed.
Frontend (JS/Vue):
* frontend/src/models/inbound.js
- Add Protocols.SOCKS ('socks') to the protocol enum, so the
existing 'Object.values(Protocols)' selector picks it up
automatically in the inbound create/edit modal.
- New Inbound.SocksSettings class (auth, accounts, udp, ip)
that serialises in the exact shape Xray's socks inbound expects
(https://xtls.github.io/config/inbounds/socks.html).
- New Inbound.SocksSettings.SocksAccount for password-mode users.
- Wire SOCKS into Inbound.Settings.getSettings() and fromJson()
dispatchers so create/edit/restore round-trips work.
* frontend/src/pages/inbounds/InboundFormModal.vue
- Extend the existing 'HTTP / Mixed accounts' form so SOCKS shares
the same accounts editor + auth/UDP/UDP-IP fields. SOCKS reuses
the MIXED shape because Xray's socks and mixed inbounds accept
the same settings keys.
- New addAccountByProtocol() helper dispatches to the correct
Account class per protocol (Http / Mixed / Socks) so each
protocol stores accounts in the right namespace.
What's intentionally NOT done (help wanted)
-------------------------------------------
1. sub/subService.go GetLink: SOCKS currently returns '' (same as
MIXED/HTTP/Tunnel). If we want a 'socks://' subscription link, that
needs its own gen* function. The existing comment on GetLink already
lists socks/http/mixed as link-less, so this is consistent for now.
2. Xray runtime AddUser / UpdateInbound hooks in web/service/inbound.go
— accounts are persisted in settings JSON and Xray picks them up on
restart, but live add-user without restart is not wired.
3. Routing UI: routing pages already accept any inbound tag, so SOCKS
inbounds are routable as-is; a dedicated 'protocol == socks' helper
in routing rule editors would be a nice follow-up.
4. Translations (web/translation/*.json): the protocol name is rendered
as the raw string 'socks' in the dropdown today; we don't yet have
a localised label.
5. Tests: xray/process_test.go has no SOCKS-specific case yet.
Why scaffold instead of full feature?
-------------------------------------
The protocol surface area touches db model, settings serialisation,
form UI, sub link generation, routing rules, and translations for 13
locales. Landing a vetted scaffold first lets multiple contributors
parallelise the remaining work without merge churn on a giant PR.
Refs
----
- Xray socks inbound spec: https://xtls.github.io/config/inbounds/socks.html
- RFC 1928 (SOCKS Protocol Version 5)
Co-developed-by: 3x-ui community (call for contributions)
This commit is contained in:
parent
f9ae0347c6
commit
4f8b0b90c8
3 changed files with 118 additions and 10 deletions
|
|
@ -20,9 +20,14 @@ const (
|
|||
Trojan Protocol = "trojan"
|
||||
Shadowsocks Protocol = "shadowsocks"
|
||||
Mixed Protocol = "mixed"
|
||||
WireGuard Protocol = "wireguard"
|
||||
Hysteria Protocol = "hysteria"
|
||||
Hysteria2 Protocol = "hysteria2"
|
||||
// Socks is a dedicated SOCKS5 inbound (Xray "socks" protocol).
|
||||
// Unlike Mixed (HTTP+SOCKS), this is a pure SOCKS5 inbound and is
|
||||
// intended for tunneling clients that only speak SOCKS5.
|
||||
// See: https://xtls.github.io/config/inbounds/socks.html
|
||||
Socks Protocol = "socks"
|
||||
WireGuard Protocol = "wireguard"
|
||||
Hysteria Protocol = "hysteria"
|
||||
Hysteria2 Protocol = "hysteria2"
|
||||
)
|
||||
|
||||
// IsHysteria returns true for both "hysteria" and "hysteria2".
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ export const Protocols = {
|
|||
WIREGUARD: 'wireguard',
|
||||
HYSTERIA: 'hysteria',
|
||||
MIXED: 'mixed',
|
||||
// Dedicated SOCKS5 inbound. Use MIXED if you also need HTTP support
|
||||
// on the same port; SOCKS is pure SOCKS5 (RFC 1928) only.
|
||||
SOCKS: 'socks',
|
||||
HTTP: 'http',
|
||||
TUNNEL: 'tunnel',
|
||||
TUN: 'tun',
|
||||
|
|
@ -2448,6 +2451,7 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
|
||||
case Protocols.TUNNEL: return new Inbound.TunnelSettings(protocol);
|
||||
case Protocols.MIXED: return new Inbound.MixedSettings(protocol);
|
||||
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
||||
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
||||
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
|
||||
case Protocols.TUN: return new Inbound.TunSettings(protocol);
|
||||
|
|
@ -2464,6 +2468,7 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||
case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json);
|
||||
case Protocols.TUNNEL: return Inbound.TunnelSettings.fromJson(json);
|
||||
case Protocols.MIXED: return Inbound.MixedSettings.fromJson(json);
|
||||
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
||||
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
||||
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
|
||||
case Protocols.TUN: return Inbound.TunSettings.fromJson(json);
|
||||
|
|
@ -3074,6 +3079,85 @@ Inbound.MixedSettings.SocksAccount = class extends XrayCommonClass {
|
|||
}
|
||||
};
|
||||
|
||||
// Dedicated SOCKS5 inbound settings.
|
||||
//
|
||||
// This mirrors Xray's `socks` inbound (RFC 1928), see:
|
||||
// https://xtls.github.io/config/inbounds/socks.html
|
||||
//
|
||||
// Unlike MixedSettings (which exposes HTTP+SOCKS on the same port),
|
||||
// SocksSettings configures a pure SOCKS5 inbound. The accepted fields
|
||||
// are the same shape Xray expects:
|
||||
// - auth: "noauth" | "password"
|
||||
// - accounts: [{ user, pass }] (only used when auth === "password")
|
||||
// - udp: bool (enables UDP ASSOCIATE)
|
||||
// - ip: string (address sent to client for UDP replies,
|
||||
// defaults to 127.0.0.1)
|
||||
//
|
||||
// NOTE: SOCKS inbounds are tunnel-style and do not produce a subscription
|
||||
// link (mirrors how MIXED/HTTP are treated in sub/subService.go::GetLink).
|
||||
Inbound.SocksSettings = class extends Inbound.Settings {
|
||||
constructor(
|
||||
protocol,
|
||||
auth = 'password',
|
||||
accounts = [new Inbound.SocksSettings.SocksAccount()],
|
||||
udp = false,
|
||||
ip = '127.0.0.1',
|
||||
) {
|
||||
super(protocol);
|
||||
this.auth = auth;
|
||||
this.accounts = accounts;
|
||||
this.udp = udp;
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
addAccount(account) {
|
||||
this.accounts.push(account);
|
||||
}
|
||||
|
||||
delAccount(index) {
|
||||
this.accounts.splice(index, 1);
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
let accounts;
|
||||
if (json.auth === 'password') {
|
||||
accounts = (json.accounts || []).map(
|
||||
account => Inbound.SocksSettings.SocksAccount.fromJson(account)
|
||||
);
|
||||
}
|
||||
return new Inbound.SocksSettings(
|
||||
Protocols.SOCKS,
|
||||
json.auth,
|
||||
accounts,
|
||||
json.udp,
|
||||
json.ip,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
auth: this.auth,
|
||||
accounts: this.auth === 'password'
|
||||
? this.accounts.map(account => account.toJson())
|
||||
: undefined,
|
||||
udp: this.udp,
|
||||
ip: this.ip,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Inbound.SocksSettings.SocksAccount = class extends XrayCommonClass {
|
||||
constructor(user = RandomUtil.randomSeq(10), pass = RandomUtil.randomSeq(10)) {
|
||||
super();
|
||||
this.user = user;
|
||||
this.pass = pass;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Inbound.SocksSettings.SocksAccount(json.user, json.pass);
|
||||
}
|
||||
};
|
||||
|
||||
Inbound.HttpSettings = class extends Inbound.Settings {
|
||||
constructor(
|
||||
protocol,
|
||||
|
|
|
|||
|
|
@ -174,6 +174,26 @@ function delFallback(idx) {
|
|||
inbound.value?.settings?.delFallback?.(idx);
|
||||
}
|
||||
|
||||
// Helper for the shared HTTP / MIXED / SOCKS accounts editor: dispatches
|
||||
// to the right Account class so each protocol stores accounts in the
|
||||
// shape Xray expects on the wire.
|
||||
function addAccountByProtocol() {
|
||||
const settings = inbound.value?.settings;
|
||||
if (!settings) return;
|
||||
switch (inbound.value.protocol) {
|
||||
case Protocols.HTTP:
|
||||
settings.addAccount(new Inbound.HttpSettings.HttpAccount());
|
||||
break;
|
||||
case Protocols.SOCKS:
|
||||
settings.addAccount(new Inbound.SocksSettings.SocksAccount());
|
||||
break;
|
||||
case Protocols.MIXED:
|
||||
default:
|
||||
settings.addAccount(new Inbound.MixedSettings.SocksAccount());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Date / GB bridges (legacy used moment via _expiryTime; we go direct).
|
||||
const expiryDate = computed({
|
||||
get: () => (dbForm.value?.expiryTime > 0 ? dayjs(dbForm.value.expiryTime) : null),
|
||||
|
|
@ -964,13 +984,12 @@ watch(() => inbound.value?.protocol, () => stampAdvancedTextFor('stream'));
|
|||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- HTTP / Mixed accounts -->
|
||||
<a-form v-if="protocol === Protocols.HTTP || protocol === Protocols.MIXED" :colon="false"
|
||||
:label-col="{ sm: { span: 8 } }" :wrapper-col="{ sm: { span: 14 } }" class="mt-12">
|
||||
<!-- HTTP / Mixed / SOCKS accounts -->
|
||||
<a-form
|
||||
v-if="protocol === Protocols.HTTP || protocol === Protocols.MIXED || protocol === Protocols.SOCKS"
|
||||
:colon="false" :label-col="{ sm: { span: 8 } }" :wrapper-col="{ sm: { span: 14 } }" class="mt-12">
|
||||
<a-form-item label="Accounts">
|
||||
<a-button size="small" @click="protocol === Protocols.HTTP
|
||||
? inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())
|
||||
: inbound.settings.addAccount(new Inbound.MixedSettings.SocksAccount())">
|
||||
<a-button size="small" @click="addAccountByProtocol()">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
|
|
@ -993,7 +1012,7 @@ watch(() => inbound.value?.protocol, () => stampAdvancedTextFor('stream'));
|
|||
<a-form-item v-if="protocol === Protocols.HTTP" label="Allow transparent">
|
||||
<a-switch v-model:checked="inbound.settings.allowTransparent" />
|
||||
</a-form-item>
|
||||
<template v-if="protocol === Protocols.MIXED">
|
||||
<template v-if="protocol === Protocols.MIXED || protocol === Protocols.SOCKS">
|
||||
<a-form-item label="Auth">
|
||||
<a-select v-model:value="inbound.settings.auth">
|
||||
<a-select-option value="noauth">noauth</a-select-option>
|
||||
|
|
|
|||
Loading…
Reference in a new issue