feat: socks5 proxy server (#3336)

- `SOCKS5_ENABLED=off`
- `SOCKS5_LISTENING_ADDRESS=":1080"`
- `SOCKS5_USER=`
- `SOCKS5_PASSWORD=`
This commit is contained in:
Quentin McGaw
2026-05-21 13:18:55 -04:00
committed by GitHub
parent 2210a0e9ad
commit eb9916f0ac
16 changed files with 1605 additions and 2 deletions
@@ -20,6 +20,7 @@ type Settings struct {
HTTPProxy HTTPProxy
Log Log
PublicIP PublicIP
Socks5 Socks5
Shadowsocks Shadowsocks
Storage Storage
System System
@@ -49,6 +50,7 @@ func (s *Settings) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Support
"http proxy": s.HTTPProxy.validate,
"log": s.Log.validate,
"public ip check": s.PublicIP.validate,
"socks5": s.Socks5.validate,
"shadowsocks": s.Shadowsocks.validate,
"storage": s.Storage.validate,
"system": s.System.validate,
@@ -81,6 +83,7 @@ func (s *Settings) copy() (copied Settings) {
HTTPProxy: s.HTTPProxy.copy(),
Log: s.Log.copy(),
PublicIP: s.PublicIP.copy(),
Socks5: s.Socks5.copy(),
Shadowsocks: s.Shadowsocks.copy(),
Storage: s.Storage.copy(),
System: s.System.copy(),
@@ -104,6 +107,7 @@ func (s *Settings) OverrideWith(other Settings,
patchedSettings.HTTPProxy.overrideWith(other.HTTPProxy)
patchedSettings.Log.overrideWith(other.Log)
patchedSettings.PublicIP.overrideWith(other.PublicIP)
patchedSettings.Socks5.overrideWith(other.Socks5)
patchedSettings.Shadowsocks.overrideWith(other.Shadowsocks)
patchedSettings.Storage.overrideWith(other.Storage)
patchedSettings.System.overrideWith(other.System)
@@ -131,6 +135,7 @@ func (s *Settings) SetDefaults() {
s.Log.setDefaults()
s.IPv6.setDefaults()
s.PublicIP.setDefaults()
s.Socks5.setDefaults()
s.Shadowsocks.setDefaults()
s.Storage.SetDefaults()
s.System.setDefaults()
@@ -154,6 +159,7 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
node.AppendNode(s.Log.toLinesNode())
node.AppendNode(s.IPv6.toLinesNode())
node.AppendNode(s.Health.toLinesNode())
node.AppendNode(s.Socks5.toLinesNode())
node.AppendNode(s.Shadowsocks.toLinesNode())
node.AppendNode(s.HTTPProxy.toLinesNode())
node.AppendNode(s.ControlServer.toLinesNode())
@@ -212,6 +218,7 @@ func (s *Settings) Read(r *reader.Reader, warner Warner) (err error) {
"public ip": func(r *reader.Reader) error {
return s.PublicIP.read(r, warner)
},
"socks5": s.Socks5.read,
"shadowsocks": s.Shadowsocks.read,
"storage": s.Storage.Read,
"system": s.System.read,
@@ -81,6 +81,8 @@ func Test_Settings_String(t *testing.T) {
| | 1.1.1.1
| | 8.8.8.8
| Restart VPN on healthcheck failure: yes
SOCKS5 proxy server settings:
| Enabled: no
Shadowsocks server settings:
| Enabled: no
HTTP proxy settings:
+91
View File
@@ -0,0 +1,91 @@
package settings
import (
"errors"
"fmt"
"os"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree"
)
// Socks5 contains settings to configure the Socks5 proxy server.
type Socks5 struct {
Enabled *bool
ListeningAddress string
Username *string
Password *string
}
func (s Socks5) validate() (err error) {
err = validate.ListeningAddress(s.ListeningAddress, os.Getuid())
if err != nil {
return fmt.Errorf("server listening address is not valid: %w", err)
}
switch {
case *s.Username != "" && *s.Password == "":
return errors.New("password must be set if username is set")
case *s.Username == "" && *s.Password != "":
return errors.New("username must be set if password is set")
}
return nil
}
func (s *Socks5) copy() (copied Socks5) {
return Socks5{
Enabled: gosettings.CopyPointer(s.Enabled),
ListeningAddress: s.ListeningAddress,
Username: gosettings.CopyPointer(s.Username),
Password: gosettings.CopyPointer(s.Password),
}
}
func (s *Socks5) overrideWith(other Socks5) {
s.Enabled = gosettings.OverrideWithPointer(s.Enabled, other.Enabled)
s.ListeningAddress = gosettings.OverrideWithComparable(s.ListeningAddress, other.ListeningAddress)
s.Username = gosettings.OverrideWithPointer(s.Username, other.Username)
s.Password = gosettings.OverrideWithPointer(s.Password, other.Password)
}
func (s *Socks5) setDefaults() {
s.Enabled = gosettings.DefaultPointer(s.Enabled, false)
s.ListeningAddress = gosettings.DefaultComparable(s.ListeningAddress, ":1080")
s.Username = gosettings.DefaultPointer(s.Username, "")
s.Password = gosettings.DefaultPointer(s.Password, "")
}
func (s Socks5) String() string {
return s.toLinesNode().String()
}
func (s Socks5) toLinesNode() (node *gotree.Node) {
node = gotree.New("SOCKS5 proxy server settings:")
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(s.Enabled))
if !*s.Enabled {
return node
}
node.Appendf("Listening address: %s", s.ListeningAddress)
if *s.Username != "" || *s.Password != "" {
node.Appendf("Username: %s", *s.Username)
node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Password))
}
return node
}
func (s *Socks5) read(r *reader.Reader) (err error) {
s.Enabled, err = r.BoolPtr("SOCKS5_ENABLED")
if err != nil {
return err
}
s.ListeningAddress = r.String("SOCKS5_LISTENING_ADDRESS")
s.Username = r.Get("SOCKS5_USER", reader.ForceLowercase(false))
s.Password = r.Get("SOCKS5_PASSWORD", reader.ForceLowercase(false))
return nil
}