mirror of
https://github.com/qdm12/gluetun.git
synced 2026-07-25 03:46:22 +02:00
wire up everything with gluetun
This commit is contained in:
@@ -240,6 +240,11 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
SHADOWSOCKS_PASSWORD= \
|
SHADOWSOCKS_PASSWORD= \
|
||||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||||
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||||
|
# Socks5
|
||||||
|
SOCKS5_ENABLED=off \
|
||||||
|
SOCKS5_LISTENING_ADDRESS=":1080" \
|
||||||
|
SOCKS5_USER= \
|
||||||
|
SOCKS5_PASSWORD= \
|
||||||
# Control server
|
# Control server
|
||||||
HTTP_CONTROL_SERVER_LOG=on \
|
HTTP_CONTROL_SERVER_LOG=on \
|
||||||
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
||||||
|
|||||||
+15
-1
@@ -41,6 +41,7 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/routing"
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
"github.com/qdm12/gluetun/internal/server"
|
"github.com/qdm12/gluetun/internal/server"
|
||||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||||
|
"github.com/qdm12/gluetun/internal/socks5"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
updater "github.com/qdm12/gluetun/internal/updater/loop"
|
updater "github.com/qdm12/gluetun/internal/updater/loop"
|
||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
@@ -411,6 +412,17 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return fmt.Errorf("starting public ip loop: %w", err)
|
return fmt.Errorf("starting public ip loop: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
socks5Loop := socks5.NewLoop(socks5.Settings{
|
||||||
|
Username: *allSettings.Socks5.Username,
|
||||||
|
Password: *allSettings.Socks5.Password,
|
||||||
|
Address: allSettings.Socks5.ListeningAddress,
|
||||||
|
Logger: logger.New(log.SetComponent("socks5")),
|
||||||
|
})
|
||||||
|
socks5RunError, err := socks5Loop.Start(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("starting SOCKS5 server loop: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
healthLogger := logger.New(log.SetComponent("healthcheck"))
|
healthLogger := logger.New(log.SetComponent("healthcheck"))
|
||||||
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger)
|
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger)
|
||||||
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||||
@@ -506,7 +518,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
String() string
|
String() string
|
||||||
Stop() error
|
Stop() error
|
||||||
}{
|
}{
|
||||||
portForwardLooper, publicIPLooper,
|
portForwardLooper, publicIPLooper, socks5Loop,
|
||||||
}
|
}
|
||||||
for _, stopper := range stoppers {
|
for _, stopper := range stoppers {
|
||||||
err := stopper.Stop()
|
err := stopper.Stop()
|
||||||
@@ -518,6 +530,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
logger.Errorf("port forwarding loop crashed: %s", err)
|
logger.Errorf("port forwarding loop crashed: %s", err)
|
||||||
case err := <-publicIPRunError:
|
case err := <-publicIPRunError:
|
||||||
logger.Errorf("public IP loop crashed: %s", err)
|
logger.Errorf("public IP loop crashed: %s", err)
|
||||||
|
case err := <-socks5RunError:
|
||||||
|
logger.Errorf("SOCKS5 server loop crashed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return orderHandler.Shutdown(context.Background())
|
return orderHandler.Shutdown(context.Background())
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type Settings struct {
|
|||||||
HTTPProxy HTTPProxy
|
HTTPProxy HTTPProxy
|
||||||
Log Log
|
Log Log
|
||||||
PublicIP PublicIP
|
PublicIP PublicIP
|
||||||
|
Socks5 Socks5
|
||||||
Shadowsocks Shadowsocks
|
Shadowsocks Shadowsocks
|
||||||
Storage Storage
|
Storage Storage
|
||||||
System System
|
System System
|
||||||
@@ -49,6 +50,7 @@ func (s *Settings) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Support
|
|||||||
"http proxy": s.HTTPProxy.validate,
|
"http proxy": s.HTTPProxy.validate,
|
||||||
"log": s.Log.validate,
|
"log": s.Log.validate,
|
||||||
"public ip check": s.PublicIP.validate,
|
"public ip check": s.PublicIP.validate,
|
||||||
|
"socks5": s.Socks5.validate,
|
||||||
"shadowsocks": s.Shadowsocks.validate,
|
"shadowsocks": s.Shadowsocks.validate,
|
||||||
"storage": s.Storage.validate,
|
"storage": s.Storage.validate,
|
||||||
"system": s.System.validate,
|
"system": s.System.validate,
|
||||||
@@ -81,6 +83,7 @@ func (s *Settings) copy() (copied Settings) {
|
|||||||
HTTPProxy: s.HTTPProxy.copy(),
|
HTTPProxy: s.HTTPProxy.copy(),
|
||||||
Log: s.Log.copy(),
|
Log: s.Log.copy(),
|
||||||
PublicIP: s.PublicIP.copy(),
|
PublicIP: s.PublicIP.copy(),
|
||||||
|
Socks5: s.Socks5.copy(),
|
||||||
Shadowsocks: s.Shadowsocks.copy(),
|
Shadowsocks: s.Shadowsocks.copy(),
|
||||||
Storage: s.Storage.copy(),
|
Storage: s.Storage.copy(),
|
||||||
System: s.System.copy(),
|
System: s.System.copy(),
|
||||||
@@ -104,6 +107,7 @@ func (s *Settings) OverrideWith(other Settings,
|
|||||||
patchedSettings.HTTPProxy.overrideWith(other.HTTPProxy)
|
patchedSettings.HTTPProxy.overrideWith(other.HTTPProxy)
|
||||||
patchedSettings.Log.overrideWith(other.Log)
|
patchedSettings.Log.overrideWith(other.Log)
|
||||||
patchedSettings.PublicIP.overrideWith(other.PublicIP)
|
patchedSettings.PublicIP.overrideWith(other.PublicIP)
|
||||||
|
patchedSettings.Socks5.overrideWith(other.Socks5)
|
||||||
patchedSettings.Shadowsocks.overrideWith(other.Shadowsocks)
|
patchedSettings.Shadowsocks.overrideWith(other.Shadowsocks)
|
||||||
patchedSettings.Storage.overrideWith(other.Storage)
|
patchedSettings.Storage.overrideWith(other.Storage)
|
||||||
patchedSettings.System.overrideWith(other.System)
|
patchedSettings.System.overrideWith(other.System)
|
||||||
@@ -131,6 +135,7 @@ func (s *Settings) SetDefaults() {
|
|||||||
s.Log.setDefaults()
|
s.Log.setDefaults()
|
||||||
s.IPv6.setDefaults()
|
s.IPv6.setDefaults()
|
||||||
s.PublicIP.setDefaults()
|
s.PublicIP.setDefaults()
|
||||||
|
s.Socks5.setDefaults()
|
||||||
s.Shadowsocks.setDefaults()
|
s.Shadowsocks.setDefaults()
|
||||||
s.Storage.SetDefaults()
|
s.Storage.SetDefaults()
|
||||||
s.System.setDefaults()
|
s.System.setDefaults()
|
||||||
@@ -154,6 +159,7 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
|
|||||||
node.AppendNode(s.Log.toLinesNode())
|
node.AppendNode(s.Log.toLinesNode())
|
||||||
node.AppendNode(s.IPv6.toLinesNode())
|
node.AppendNode(s.IPv6.toLinesNode())
|
||||||
node.AppendNode(s.Health.toLinesNode())
|
node.AppendNode(s.Health.toLinesNode())
|
||||||
|
node.AppendNode(s.Socks5.toLinesNode())
|
||||||
node.AppendNode(s.Shadowsocks.toLinesNode())
|
node.AppendNode(s.Shadowsocks.toLinesNode())
|
||||||
node.AppendNode(s.HTTPProxy.toLinesNode())
|
node.AppendNode(s.HTTPProxy.toLinesNode())
|
||||||
node.AppendNode(s.ControlServer.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 {
|
"public ip": func(r *reader.Reader) error {
|
||||||
return s.PublicIP.read(r, warner)
|
return s.PublicIP.read(r, warner)
|
||||||
},
|
},
|
||||||
|
"socks5": s.Socks5.read,
|
||||||
"shadowsocks": s.Shadowsocks.read,
|
"shadowsocks": s.Shadowsocks.read,
|
||||||
"storage": s.Storage.Read,
|
"storage": s.Storage.Read,
|
||||||
"system": s.System.read,
|
"system": s.System.read,
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
| | ├── 1.1.1.1
|
| | ├── 1.1.1.1
|
||||||
| | └── 8.8.8.8
|
| | └── 8.8.8.8
|
||||||
| └── Restart VPN on healthcheck failure: yes
|
| └── Restart VPN on healthcheck failure: yes
|
||||||
|
├── SOCKS5 proxy settings:
|
||||||
|
| └── Enabled: no
|
||||||
├── Shadowsocks server settings:
|
├── Shadowsocks server settings:
|
||||||
| └── Enabled: no
|
| └── Enabled: no
|
||||||
├── HTTP proxy settings:
|
├── HTTP proxy settings:
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Loop struct {
|
||||||
|
settings Settings
|
||||||
|
|
||||||
|
mutex sync.Mutex
|
||||||
|
runCancel context.CancelFunc
|
||||||
|
runDone <-chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoop(settings Settings) *Loop {
|
||||||
|
return &Loop{
|
||||||
|
settings: settings,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loop) String() string {
|
||||||
|
return "SOCKS5 server loop"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loop) Start(_ context.Context) (runError <-chan error, err error) {
|
||||||
|
l.mutex.Lock()
|
||||||
|
defer l.mutex.Unlock()
|
||||||
|
|
||||||
|
var runCtx context.Context
|
||||||
|
runCtx, l.runCancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
runDone := make(chan error)
|
||||||
|
l.runDone = runDone
|
||||||
|
runErrorCh := make(chan error)
|
||||||
|
|
||||||
|
go run(runCtx, runDone, l.settings)
|
||||||
|
|
||||||
|
return runErrorCh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(ctx context.Context, done chan<- error, settings Settings) {
|
||||||
|
defer close(done)
|
||||||
|
logger := settings.Logger
|
||||||
|
|
||||||
|
for ctx.Err() == nil {
|
||||||
|
server := newServer(settings)
|
||||||
|
errorCh, err := server.Start(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warnf("failed starting SOCKS5 server: %s", err)
|
||||||
|
waitBeforeRetry(ctx)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
done <- server.Stop()
|
||||||
|
return
|
||||||
|
case err := <-errorCh:
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Warnf("SOCKS5 server crashed: %s", err)
|
||||||
|
waitBeforeRetry(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loop) Stop() (err error) {
|
||||||
|
l.mutex.Lock()
|
||||||
|
defer l.mutex.Unlock()
|
||||||
|
|
||||||
|
l.runCancel()
|
||||||
|
return <-l.runDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitBeforeRetry(ctx context.Context) {
|
||||||
|
const retryDelay = 10 * time.Second
|
||||||
|
timer := time.NewTimer(retryDelay)
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ type Server struct {
|
|||||||
stopping atomic.Bool
|
stopping atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(settings Settings) *Server {
|
func newServer(settings Settings) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
username: settings.Username,
|
username: settings.Username,
|
||||||
password: settings.Password,
|
password: settings.Password,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func TestServerProxy(t *testing.T) {
|
|||||||
backendConnCh <- conn
|
backendConnCh <- conn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
server := New(Settings{
|
server := newServer(Settings{
|
||||||
Username: testCase.username,
|
Username: testCase.username,
|
||||||
Password: testCase.password,
|
Password: testCase.password,
|
||||||
Address: "127.0.0.1:0",
|
Address: "127.0.0.1:0",
|
||||||
@@ -215,7 +215,7 @@ func TestNew(t *testing.T) {
|
|||||||
for name, testCase := range testCases {
|
for name, testCase := range testCases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
result := New(testCase.settings)
|
result := newServer(testCase.settings)
|
||||||
assert.Equal(t, testCase.expected.username, result.username)
|
assert.Equal(t, testCase.expected.username, result.username)
|
||||||
assert.Equal(t, testCase.expected.password, result.password)
|
assert.Equal(t, testCase.expected.password, result.password)
|
||||||
assert.Equal(t, testCase.expected.address, result.address)
|
assert.Equal(t, testCase.expected.address, result.address)
|
||||||
@@ -231,7 +231,7 @@ func TestStartStop(t *testing.T) {
|
|||||||
logger.EXPECT().Infof(gomock.Any(), gomock.Any()).Times(0)
|
logger.EXPECT().Infof(gomock.Any(), gomock.Any()).Times(0)
|
||||||
logger.EXPECT().Warnf(gomock.Any(), gomock.Any()).Times(0)
|
logger.EXPECT().Warnf(gomock.Any(), gomock.Any()).Times(0)
|
||||||
|
|
||||||
server := New(Settings{
|
server := newServer(Settings{
|
||||||
Address: "127.0.0.1:0",
|
Address: "127.0.0.1:0",
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user