update: 更新支持socket 5端口监听

This commit is contained in:
xiamuceer-j
2026-03-06 15:17:49 +08:00
parent 20e5402854
commit a31ac4f51a
14 changed files with 166 additions and 39 deletions
+2 -1
View File
@@ -101,7 +101,8 @@ cp ./config.example.yaml ./config.yaml
配置模板见 `config.example.yaml`,重点关注: 配置模板见 `config.example.yaml`,重点关注:
- `mode`: `pool` / `multi-port` / `hybrid` - `mode`: `pool` / `multi-port` / `hybrid`
- `listener`: 代理入口监听与认证 - `listener`: 代理入口监听与认证(新增 `listener.protocol`: `http` / `socks5` / `mixed`)
- `multi_port`: 多端口入口参数(新增 `multi_port.protocol`: `http` / `socks5` / `mixed`)
- `management.listen`: Web 管理面板地址(默认 `0.0.0.0:9888`) - `management.listen`: Web 管理面板地址(默认 `0.0.0.0:9888`)
- `management.password`: 面板登录密码(为空则不需要登录) - `management.password`: 面板登录密码(为空则不需要登录)
- `subscriptions` / `nodes_file` / `nodes`: 节点来源(三选一或混用) - `subscriptions` / `nodes_file` / `nodes`: 节点来源(三选一或混用)
+2
View File
@@ -29,6 +29,7 @@ skip_cert_verify: false
listener: listener:
address: 0.0.0.0 # 监听地址 address: 0.0.0.0 # 监听地址
port: 2323 # 监听端口 port: 2323 # 监听端口
protocol: http # 监听协议: http | socks5 | mixed
username: username # 代理认证用户名 username: username # 代理认证用户名
password: password # 代理认证密码 password: password # 代理认证密码
@@ -49,6 +50,7 @@ pool:
multi_port: multi_port:
address: 0.0.0.0 # 监听地址 address: 0.0.0.0 # 监听地址
base_port: 24000 # 起始端口号 base_port: 24000 # 起始端口号
protocol: http # 监听协议: http | socks5 | mixed
username: mpuser # 默认认证用户名 username: mpuser # 默认认证用户名
password: mppass # 默认认证密码 password: mppass # 默认认证密码
+30
View File
@@ -10,11 +10,13 @@ const defaultSettings: SettingsData = {
listener_address: '0.0.0.0', listener_address: '0.0.0.0',
listener_port: 2323, listener_port: 2323,
listener_protocol: 'http',
listener_username: '', listener_username: '',
listener_password: '', listener_password: '',
multi_port_address: '0.0.0.0', multi_port_address: '0.0.0.0',
multi_port_base_port: 24000, multi_port_base_port: 24000,
multi_port_protocol: 'http',
multi_port_username: '', multi_port_username: '',
multi_port_password: '', multi_port_password: '',
@@ -387,6 +389,20 @@ export default function SettingsPanel() {
</fieldset> </fieldset>
</div> </div>
<fieldset className="fieldset">
<legend className="fieldset-legend font-semibold text-base-content/80">监听协议</legend>
<select
className="select select-md w-full bg-base-200/50 focus:bg-base-100 transition-colors focus:border-primary/50"
value={settings.listener_protocol}
onChange={(e) => updateField('listener_protocol', e.target.value)}
>
<option value="http">http</option>
<option value="socks5">socks5</option>
<option value="mixed">mixed (HTTP + SOCKS5)</option>
</select>
<p className="label text-base-content/50 mt-1">mixed 表示同端口同时支持 HTTP 与 SOCKS5</p>
</fieldset>
<div className="grid grid-cols-2 gap-4 pt-2"> <div className="grid grid-cols-2 gap-4 pt-2">
<fieldset className="fieldset"> <fieldset className="fieldset">
<legend className="fieldset-legend font-semibold text-base-content/80">代理用户名</legend> <legend className="fieldset-legend font-semibold text-base-content/80">代理用户名</legend>
@@ -448,6 +464,20 @@ export default function SettingsPanel() {
</fieldset> </fieldset>
</div> </div>
<fieldset className="fieldset">
<legend className="fieldset-legend font-semibold text-base-content/80">监听协议</legend>
<select
className="select select-md w-full bg-base-200/50 focus:bg-base-100 transition-colors focus:border-primary/50"
value={settings.multi_port_protocol}
onChange={(e) => updateField('multi_port_protocol', e.target.value)}
>
<option value="http">http</option>
<option value="socks5">socks5</option>
<option value="mixed">mixed (HTTP + SOCKS5)</option>
</select>
<p className="label text-base-content/50 mt-1">应用于 multi-port / hybrid 的每个节点入口</p>
</fieldset>
<div className="grid grid-cols-2 gap-4 pt-2"> <div className="grid grid-cols-2 gap-4 pt-2">
<fieldset className="fieldset"> <fieldset className="fieldset">
<legend className="fieldset-legend font-semibold text-base-content/80">默认用户名</legend> <legend className="fieldset-legend font-semibold text-base-content/80">默认用户名</legend>
+2
View File
@@ -86,12 +86,14 @@ export interface SettingsData {
// Listener // Listener
listener_address: string listener_address: string
listener_port: number listener_port: number
listener_protocol: string
listener_username: string listener_username: string
listener_password: string listener_password: string
// Multi-port // Multi-port
multi_port_address: string multi_port_address: string
multi_port_base_port: number multi_port_base_port: number
multi_port_protocol: string
multi_port_username: string multi_port_username: string
multi_port_password: string multi_port_password: string
+63 -32
View File
@@ -199,23 +199,19 @@ func Build(cfg *config.Config) (option.Options, error) {
Options: &perOptions, Options: &perOptions,
} }
outbounds = append(outbounds, perPool) outbounds = append(outbounds, perPool)
inboundOptions := &option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: addr,
ListenPort: meta.Port,
},
}
username := cfg.MultiPort.Username
password := cfg.MultiPort.Password
if username != "" {
inboundOptions.Users = []auth.User{{Username: username, Password: password}}
}
inboundTag := fmt.Sprintf("in-%s", tag) inboundTag := fmt.Sprintf("in-%s", tag)
inbounds = append(inbounds, option.Inbound{ inbound, err := buildInboundByProtocol(
Type: C.TypeHTTP, cfg.MultiPort.Protocol,
Tag: inboundTag, addr,
Options: inboundOptions, meta.Port,
}) cfg.MultiPort.Username,
cfg.MultiPort.Password,
inboundTag,
)
if err != nil {
return option.Options{}, fmt.Errorf("build multi-port inbound for %s: %w", tag, err)
}
inbounds = append(inbounds, inbound)
route.Rules = append(route.Rules, option.Rule{ route.Rules = append(route.Rules, option.Rule{
Type: C.RuleTypeDefault, Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{ DefaultOptions: option.DefaultRule{
@@ -292,24 +288,59 @@ func buildPoolInbound(cfg *config.Config) (option.Inbound, error) {
if err != nil { if err != nil {
return option.Inbound{}, fmt.Errorf("parse listener address: %w", err) return option.Inbound{}, fmt.Errorf("parse listener address: %w", err)
} }
inboundOptions := &option.HTTPMixedInboundOptions{ return buildInboundByProtocol(
ListenOptions: option.ListenOptions{ cfg.Listener.Protocol,
Listen: listenAddr, listenAddr,
ListenPort: cfg.Listener.Port, cfg.Listener.Port,
}, cfg.Listener.Username,
cfg.Listener.Password,
"http-in",
)
}
func buildInboundByProtocol(protocol string, listenAddr *badoption.Addr, port uint16, username, password, tag string) (option.Inbound, error) {
users := []auth.User(nil)
if username != "" {
users = []auth.User{{Username: username, Password: password}}
} }
if cfg.Listener.Username != "" {
inboundOptions.Users = []auth.User{{ switch protocol {
Username: cfg.Listener.Username, case config.InboundProtocolHTTP:
Password: cfg.Listener.Password, opts := &option.HTTPMixedInboundOptions{
}} ListenOptions: option.ListenOptions{
Listen: listenAddr,
ListenPort: port,
},
}
if len(users) > 0 {
opts.Users = users
}
return option.Inbound{Type: C.TypeHTTP, Tag: tag, Options: opts}, nil
case config.InboundProtocolSOCKS5:
opts := &option.SocksInboundOptions{
ListenOptions: option.ListenOptions{
Listen: listenAddr,
ListenPort: port,
},
}
if len(users) > 0 {
opts.Users = users
}
return option.Inbound{Type: C.TypeSOCKS, Tag: tag, Options: opts}, nil
case config.InboundProtocolMixed:
opts := &option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: listenAddr,
ListenPort: port,
},
}
if len(users) > 0 {
opts.Users = users
}
return option.Inbound{Type: C.TypeMixed, Tag: tag, Options: opts}, nil
default:
return option.Inbound{}, fmt.Errorf("unsupported inbound protocol %q", protocol)
} }
inbound := option.Inbound{
Type: C.TypeHTTP,
Tag: "http-in",
Options: inboundOptions,
}
return inbound, nil
} }
func buildNodeOutbound(tag, rawURI string, skipCertVerify bool) (option.Outbound, error) { func buildNodeOutbound(tag, rawURI string, skipCertVerify bool) (option.Outbound, error) {
+49 -1
View File
@@ -50,10 +50,11 @@ type GeoIPConfig struct {
AutoUpdateInterval time.Duration `yaml:"auto_update_interval"` // 自动更新间隔,默认 24 小时 AutoUpdateInterval time.Duration `yaml:"auto_update_interval"` // 自动更新间隔,默认 24 小时
} }
// ListenerConfig defines how the HTTP proxy should listen for clients. // ListenerConfig defines how the proxy should listen for clients.
type ListenerConfig struct { type ListenerConfig struct {
Address string `yaml:"address"` Address string `yaml:"address"`
Port uint16 `yaml:"port"` Port uint16 `yaml:"port"`
Protocol string `yaml:"protocol"` // http, socks5, mixed
Username string `yaml:"username"` Username string `yaml:"username"`
Password string `yaml:"password"` Password string `yaml:"password"`
} }
@@ -69,6 +70,7 @@ type PoolConfig struct {
type MultiPortConfig struct { type MultiPortConfig struct {
Address string `yaml:"address"` Address string `yaml:"address"`
BasePort uint16 `yaml:"base_port"` BasePort uint16 `yaml:"base_port"`
Protocol string `yaml:"protocol"` // http, socks5, mixed
Username string `yaml:"username"` Username string `yaml:"username"`
Password string `yaml:"password"` Password string `yaml:"password"`
} }
@@ -101,6 +103,27 @@ const (
NodeSourceManual NodeSource = "manual" // Added manually via WebUI NodeSourceManual NodeSource = "manual" // Added manually via WebUI
) )
const (
InboundProtocolHTTP = "http"
InboundProtocolSOCKS5 = "socks5"
InboundProtocolMixed = "mixed"
)
// NormalizeInboundProtocol normalizes inbound protocol aliases and validates the value.
func NormalizeInboundProtocol(value string) (string, error) {
protocol := strings.ToLower(strings.TrimSpace(value))
switch protocol {
case "socks":
protocol = InboundProtocolSOCKS5
}
switch protocol {
case InboundProtocolHTTP, InboundProtocolSOCKS5, InboundProtocolMixed:
return protocol, nil
default:
return "", fmt.Errorf("不支持的监听协议: %q(仅支持 http/socks5/mixed)", value)
}
}
// NodeConfig describes a single upstream proxy endpoint expressed as URI. // NodeConfig describes a single upstream proxy endpoint expressed as URI.
type NodeConfig struct { type NodeConfig struct {
Name string `yaml:"name" json:"name"` Name string `yaml:"name" json:"name"`
@@ -202,6 +225,14 @@ func (c *Config) applyDefaults() error {
if c.Listener.Port == 0 { if c.Listener.Port == 0 {
c.Listener.Port = 2323 c.Listener.Port = 2323
} }
if c.Listener.Protocol == "" {
c.Listener.Protocol = InboundProtocolHTTP
}
listenerProtocol, err := NormalizeInboundProtocol(c.Listener.Protocol)
if err != nil {
return err
}
c.Listener.Protocol = listenerProtocol
if c.Pool.Mode == "" { if c.Pool.Mode == "" {
c.Pool.Mode = "sequential" c.Pool.Mode = "sequential"
} }
@@ -217,6 +248,14 @@ func (c *Config) applyDefaults() error {
if c.MultiPort.BasePort == 0 { if c.MultiPort.BasePort == 0 {
c.MultiPort.BasePort = 28000 c.MultiPort.BasePort = 28000
} }
if c.MultiPort.Protocol == "" {
c.MultiPort.Protocol = InboundProtocolHTTP
}
multiPortProtocol, err := NormalizeInboundProtocol(c.MultiPort.Protocol)
if err != nil {
return err
}
c.MultiPort.Protocol = multiPortProtocol
if c.Management.Listen == "" { if c.Management.Listen == "" {
c.Management.Listen = "127.0.0.1:9090" c.Management.Listen = "127.0.0.1:9090"
} }
@@ -1002,6 +1041,7 @@ func (c *Config) SaveSettings() error {
// ValidateSettingsRequest validates the settings request fields and returns // ValidateSettingsRequest validates the settings request fields and returns
// a descriptive error for any invalid values, instead of silently ignoring them. // a descriptive error for any invalid values, instead of silently ignoring them.
func ValidateSettingsRequest(mode string, listenerPort, multiPortBasePort uint16, func ValidateSettingsRequest(mode string, listenerPort, multiPortBasePort uint16,
listenerProtocol, multiPortProtocol,
poolBlacklistDuration, subRefreshInterval, subRefreshTimeout, poolBlacklistDuration, subRefreshInterval, subRefreshTimeout,
subRefreshHealthCheckTimeout, subRefreshDrainTimeout, subRefreshHealthCheckTimeout, subRefreshDrainTimeout,
geoIPAutoUpdateInterval string) error { geoIPAutoUpdateInterval string) error {
@@ -1021,6 +1061,14 @@ func ValidateSettingsRequest(mode string, listenerPort, multiPortBasePort uint16
return fmt.Errorf("多端口起始端口不能为 0") return fmt.Errorf("多端口起始端口不能为 0")
} }
// Validate inbound protocols
if _, err := NormalizeInboundProtocol(listenerProtocol); err != nil {
return fmt.Errorf("监听入口协议无效: %w", err)
}
if _, err := NormalizeInboundProtocol(multiPortProtocol); err != nil {
return fmt.Errorf("多端口入口协议无效: %w", err)
}
// Validate duration fields // Validate duration fields
durationFields := []struct { durationFields := []struct {
name string name string
Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Easy Proxies</title> <title>Easy Proxies</title>
<script type="module" crossorigin src="/assets/index-BkdNNtGR.js"></script> <script type="module" crossorigin src="/assets/index-DXv5BU-I.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CZdhzSdd.css"> <link rel="stylesheet" crossorigin href="/assets/index-CZdhzSdd.css">
</head> </head>
<body> <body>
+13
View File
@@ -196,12 +196,14 @@ type allSettingsResponse struct {
// Listener // Listener
ListenerAddress string `json:"listener_address"` ListenerAddress string `json:"listener_address"`
ListenerPort uint16 `json:"listener_port"` ListenerPort uint16 `json:"listener_port"`
ListenerProtocol string `json:"listener_protocol"`
ListenerUsername string `json:"listener_username"` ListenerUsername string `json:"listener_username"`
ListenerPassword string `json:"listener_password"` ListenerPassword string `json:"listener_password"`
// Multi-port // Multi-port
MultiPortAddress string `json:"multi_port_address"` MultiPortAddress string `json:"multi_port_address"`
MultiPortBasePort uint16 `json:"multi_port_base_port"` MultiPortBasePort uint16 `json:"multi_port_base_port"`
MultiPortProtocol string `json:"multi_port_protocol"`
MultiPortUsername string `json:"multi_port_username"` MultiPortUsername string `json:"multi_port_username"`
MultiPortPassword string `json:"multi_port_password"` MultiPortPassword string `json:"multi_port_password"`
@@ -245,12 +247,14 @@ type allSettingsRequest struct {
// Listener // Listener
ListenerAddress string `json:"listener_address"` ListenerAddress string `json:"listener_address"`
ListenerPort uint16 `json:"listener_port"` ListenerPort uint16 `json:"listener_port"`
ListenerProtocol string `json:"listener_protocol"`
ListenerUsername string `json:"listener_username"` ListenerUsername string `json:"listener_username"`
ListenerPassword string `json:"listener_password"` ListenerPassword string `json:"listener_password"`
// Multi-port // Multi-port
MultiPortAddress string `json:"multi_port_address"` MultiPortAddress string `json:"multi_port_address"`
MultiPortBasePort uint16 `json:"multi_port_base_port"` MultiPortBasePort uint16 `json:"multi_port_base_port"`
MultiPortProtocol string `json:"multi_port_protocol"`
MultiPortUsername string `json:"multi_port_username"` MultiPortUsername string `json:"multi_port_username"`
MultiPortPassword string `json:"multi_port_password"` MultiPortPassword string `json:"multi_port_password"`
@@ -308,11 +312,13 @@ func (s *Server) getAllSettings() allSettingsResponse {
ListenerAddress: c.Listener.Address, ListenerAddress: c.Listener.Address,
ListenerPort: c.Listener.Port, ListenerPort: c.Listener.Port,
ListenerProtocol: c.Listener.Protocol,
ListenerUsername: c.Listener.Username, ListenerUsername: c.Listener.Username,
ListenerPassword: c.Listener.Password, ListenerPassword: c.Listener.Password,
MultiPortAddress: c.MultiPort.Address, MultiPortAddress: c.MultiPort.Address,
MultiPortBasePort: c.MultiPort.BasePort, MultiPortBasePort: c.MultiPort.BasePort,
MultiPortProtocol: c.MultiPort.Protocol,
MultiPortUsername: c.MultiPort.Username, MultiPortUsername: c.MultiPort.Username,
MultiPortPassword: c.MultiPort.Password, MultiPortPassword: c.MultiPort.Password,
@@ -346,6 +352,7 @@ func (s *Server) updateAllSettings(req allSettingsRequest) error {
// Validate request before applying // Validate request before applying
if err := config.ValidateSettingsRequest( if err := config.ValidateSettingsRequest(
req.Mode, req.ListenerPort, req.MultiPortBasePort, req.Mode, req.ListenerPort, req.MultiPortBasePort,
req.ListenerProtocol, req.MultiPortProtocol,
req.PoolBlacklistDuration, req.SubRefreshInterval, req.SubRefreshTimeout, req.PoolBlacklistDuration, req.SubRefreshInterval, req.SubRefreshTimeout,
req.SubRefreshHealthCheckTimeout, req.SubRefreshDrainTimeout, req.SubRefreshHealthCheckTimeout, req.SubRefreshDrainTimeout,
req.GeoIPAutoUpdateInterval, req.GeoIPAutoUpdateInterval,
@@ -374,12 +381,18 @@ func (s *Server) updateAllSettings(req allSettingsRequest) error {
// Listener // Listener
c.Listener.Address = req.ListenerAddress c.Listener.Address = req.ListenerAddress
c.Listener.Port = req.ListenerPort c.Listener.Port = req.ListenerPort
if p, err := config.NormalizeInboundProtocol(req.ListenerProtocol); err == nil {
c.Listener.Protocol = p
}
c.Listener.Username = req.ListenerUsername c.Listener.Username = req.ListenerUsername
c.Listener.Password = req.ListenerPassword c.Listener.Password = req.ListenerPassword
// Multi-port // Multi-port
c.MultiPort.Address = req.MultiPortAddress c.MultiPort.Address = req.MultiPortAddress
c.MultiPort.BasePort = req.MultiPortBasePort c.MultiPort.BasePort = req.MultiPortBasePort
if p, err := config.NormalizeInboundProtocol(req.MultiPortProtocol); err == nil {
c.MultiPort.Protocol = p
}
c.MultiPort.Username = req.MultiPortUsername c.MultiPort.Username = req.MultiPortUsername
c.MultiPort.Password = req.MultiPortPassword c.MultiPort.Password = req.MultiPortPassword