diff --git a/database/model/model.go b/database/model/model.go index d71e0589..88d1548c 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -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". diff --git a/frontend/src/models/inbound.js b/frontend/src/models/inbound.js index 830dc9a9..a205bd1c 100644 --- a/frontend/src/models/inbound.js +++ b/frontend/src/models/inbound.js @@ -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, diff --git a/frontend/src/pages/inbounds/InboundFormModal.vue b/frontend/src/pages/inbounds/InboundFormModal.vue index 8d2019de..a68b38eb 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.vue +++ b/frontend/src/pages/inbounds/InboundFormModal.vue @@ -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')); - - + + - + @@ -993,7 +1012,7 @@ watch(() => inbound.value?.protocol, () => stampAdvancedTextFor('stream')); -