update: 更新支持socket 5端口监听
This commit is contained in:
@@ -101,7 +101,8 @@ cp ./config.example.yaml ./config.yaml
|
||||
配置模板见 `config.example.yaml`,重点关注:
|
||||
|
||||
- `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.password`: 面板登录密码(为空则不需要登录)
|
||||
- `subscriptions` / `nodes_file` / `nodes`: 节点来源(三选一或混用)
|
||||
|
||||
@@ -29,6 +29,7 @@ skip_cert_verify: false
|
||||
listener:
|
||||
address: 0.0.0.0 # 监听地址
|
||||
port: 2323 # 监听端口
|
||||
protocol: http # 监听协议: http | socks5 | mixed
|
||||
username: username # 代理认证用户名
|
||||
password: password # 代理认证密码
|
||||
|
||||
@@ -49,6 +50,7 @@ pool:
|
||||
multi_port:
|
||||
address: 0.0.0.0 # 监听地址
|
||||
base_port: 24000 # 起始端口号
|
||||
protocol: http # 监听协议: http | socks5 | mixed
|
||||
username: mpuser # 默认认证用户名
|
||||
password: mppass # 默认认证密码
|
||||
|
||||
|
||||
@@ -10,11 +10,13 @@ const defaultSettings: SettingsData = {
|
||||
|
||||
listener_address: '0.0.0.0',
|
||||
listener_port: 2323,
|
||||
listener_protocol: 'http',
|
||||
listener_username: '',
|
||||
listener_password: '',
|
||||
|
||||
multi_port_address: '0.0.0.0',
|
||||
multi_port_base_port: 24000,
|
||||
multi_port_protocol: 'http',
|
||||
multi_port_username: '',
|
||||
multi_port_password: '',
|
||||
|
||||
@@ -387,6 +389,20 @@ export default function SettingsPanel() {
|
||||
</fieldset>
|
||||
</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">
|
||||
<fieldset className="fieldset">
|
||||
<legend className="fieldset-legend font-semibold text-base-content/80">代理用户名</legend>
|
||||
@@ -448,6 +464,20 @@ export default function SettingsPanel() {
|
||||
</fieldset>
|
||||
</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">
|
||||
<fieldset className="fieldset">
|
||||
<legend className="fieldset-legend font-semibold text-base-content/80">默认用户名</legend>
|
||||
|
||||
@@ -86,12 +86,14 @@ export interface SettingsData {
|
||||
// Listener
|
||||
listener_address: string
|
||||
listener_port: number
|
||||
listener_protocol: string
|
||||
listener_username: string
|
||||
listener_password: string
|
||||
|
||||
// Multi-port
|
||||
multi_port_address: string
|
||||
multi_port_base_port: number
|
||||
multi_port_protocol: string
|
||||
multi_port_username: string
|
||||
multi_port_password: string
|
||||
|
||||
|
||||
+59
-28
@@ -199,23 +199,19 @@ func Build(cfg *config.Config) (option.Options, error) {
|
||||
Options: &perOptions,
|
||||
}
|
||||
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)
|
||||
inbounds = append(inbounds, option.Inbound{
|
||||
Type: C.TypeHTTP,
|
||||
Tag: inboundTag,
|
||||
Options: inboundOptions,
|
||||
})
|
||||
inbound, err := buildInboundByProtocol(
|
||||
cfg.MultiPort.Protocol,
|
||||
addr,
|
||||
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{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
@@ -292,24 +288,59 @@ func buildPoolInbound(cfg *config.Config) (option.Inbound, error) {
|
||||
if err != nil {
|
||||
return option.Inbound{}, fmt.Errorf("parse listener address: %w", err)
|
||||
}
|
||||
inboundOptions := &option.HTTPMixedInboundOptions{
|
||||
return buildInboundByProtocol(
|
||||
cfg.Listener.Protocol,
|
||||
listenAddr,
|
||||
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}}
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case config.InboundProtocolHTTP:
|
||||
opts := &option.HTTPMixedInboundOptions{
|
||||
ListenOptions: option.ListenOptions{
|
||||
Listen: listenAddr,
|
||||
ListenPort: cfg.Listener.Port,
|
||||
ListenPort: port,
|
||||
},
|
||||
}
|
||||
if cfg.Listener.Username != "" {
|
||||
inboundOptions.Users = []auth.User{{
|
||||
Username: cfg.Listener.Username,
|
||||
Password: cfg.Listener.Password,
|
||||
}}
|
||||
if len(users) > 0 {
|
||||
opts.Users = users
|
||||
}
|
||||
inbound := option.Inbound{
|
||||
Type: C.TypeHTTP,
|
||||
Tag: "http-in",
|
||||
Options: inboundOptions,
|
||||
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)
|
||||
}
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
func buildNodeOutbound(tag, rawURI string, skipCertVerify bool) (option.Outbound, error) {
|
||||
|
||||
@@ -50,10 +50,11 @@ type GeoIPConfig struct {
|
||||
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 {
|
||||
Address string `yaml:"address"`
|
||||
Port uint16 `yaml:"port"`
|
||||
Protocol string `yaml:"protocol"` // http, socks5, mixed
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
}
|
||||
@@ -69,6 +70,7 @@ type PoolConfig struct {
|
||||
type MultiPortConfig struct {
|
||||
Address string `yaml:"address"`
|
||||
BasePort uint16 `yaml:"base_port"`
|
||||
Protocol string `yaml:"protocol"` // http, socks5, mixed
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
}
|
||||
@@ -101,6 +103,27 @@ const (
|
||||
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.
|
||||
type NodeConfig struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
@@ -202,6 +225,14 @@ func (c *Config) applyDefaults() error {
|
||||
if c.Listener.Port == 0 {
|
||||
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 == "" {
|
||||
c.Pool.Mode = "sequential"
|
||||
}
|
||||
@@ -217,6 +248,14 @@ func (c *Config) applyDefaults() error {
|
||||
if c.MultiPort.BasePort == 0 {
|
||||
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 == "" {
|
||||
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
|
||||
// a descriptive error for any invalid values, instead of silently ignoring them.
|
||||
func ValidateSettingsRequest(mode string, listenerPort, multiPortBasePort uint16,
|
||||
listenerProtocol, multiPortProtocol,
|
||||
poolBlacklistDuration, subRefreshInterval, subRefreshTimeout,
|
||||
subRefreshHealthCheckTimeout, subRefreshDrainTimeout,
|
||||
geoIPAutoUpdateInterval string) error {
|
||||
@@ -1021,6 +1061,14 @@ func ValidateSettingsRequest(mode string, listenerPort, multiPortBasePort uint16
|
||||
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
|
||||
durationFields := []struct {
|
||||
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 |
+4
-4
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -196,12 +196,14 @@ type allSettingsResponse struct {
|
||||
// Listener
|
||||
ListenerAddress string `json:"listener_address"`
|
||||
ListenerPort uint16 `json:"listener_port"`
|
||||
ListenerProtocol string `json:"listener_protocol"`
|
||||
ListenerUsername string `json:"listener_username"`
|
||||
ListenerPassword string `json:"listener_password"`
|
||||
|
||||
// Multi-port
|
||||
MultiPortAddress string `json:"multi_port_address"`
|
||||
MultiPortBasePort uint16 `json:"multi_port_base_port"`
|
||||
MultiPortProtocol string `json:"multi_port_protocol"`
|
||||
MultiPortUsername string `json:"multi_port_username"`
|
||||
MultiPortPassword string `json:"multi_port_password"`
|
||||
|
||||
@@ -245,12 +247,14 @@ type allSettingsRequest struct {
|
||||
// Listener
|
||||
ListenerAddress string `json:"listener_address"`
|
||||
ListenerPort uint16 `json:"listener_port"`
|
||||
ListenerProtocol string `json:"listener_protocol"`
|
||||
ListenerUsername string `json:"listener_username"`
|
||||
ListenerPassword string `json:"listener_password"`
|
||||
|
||||
// Multi-port
|
||||
MultiPortAddress string `json:"multi_port_address"`
|
||||
MultiPortBasePort uint16 `json:"multi_port_base_port"`
|
||||
MultiPortProtocol string `json:"multi_port_protocol"`
|
||||
MultiPortUsername string `json:"multi_port_username"`
|
||||
MultiPortPassword string `json:"multi_port_password"`
|
||||
|
||||
@@ -308,11 +312,13 @@ func (s *Server) getAllSettings() allSettingsResponse {
|
||||
|
||||
ListenerAddress: c.Listener.Address,
|
||||
ListenerPort: c.Listener.Port,
|
||||
ListenerProtocol: c.Listener.Protocol,
|
||||
ListenerUsername: c.Listener.Username,
|
||||
ListenerPassword: c.Listener.Password,
|
||||
|
||||
MultiPortAddress: c.MultiPort.Address,
|
||||
MultiPortBasePort: c.MultiPort.BasePort,
|
||||
MultiPortProtocol: c.MultiPort.Protocol,
|
||||
MultiPortUsername: c.MultiPort.Username,
|
||||
MultiPortPassword: c.MultiPort.Password,
|
||||
|
||||
@@ -346,6 +352,7 @@ func (s *Server) updateAllSettings(req allSettingsRequest) error {
|
||||
// Validate request before applying
|
||||
if err := config.ValidateSettingsRequest(
|
||||
req.Mode, req.ListenerPort, req.MultiPortBasePort,
|
||||
req.ListenerProtocol, req.MultiPortProtocol,
|
||||
req.PoolBlacklistDuration, req.SubRefreshInterval, req.SubRefreshTimeout,
|
||||
req.SubRefreshHealthCheckTimeout, req.SubRefreshDrainTimeout,
|
||||
req.GeoIPAutoUpdateInterval,
|
||||
@@ -374,12 +381,18 @@ func (s *Server) updateAllSettings(req allSettingsRequest) error {
|
||||
// Listener
|
||||
c.Listener.Address = req.ListenerAddress
|
||||
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.Password = req.ListenerPassword
|
||||
|
||||
// Multi-port
|
||||
c.MultiPort.Address = req.MultiPortAddress
|
||||
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.Password = req.MultiPortPassword
|
||||
|
||||
|
||||
Reference in New Issue
Block a user