diff --git a/Dockerfile b/Dockerfile index 2b3448ec..da330849 100644 --- a/Dockerfile +++ b/Dockerfile @@ -93,12 +93,12 @@ ENV VPNSP=pia \ FIREWALL_INPUT_PORTS= \ FIREWALL_OUTBOUND_SUBNETS= \ FIREWALL_DEBUG=off \ - # Tinyproxy - TINYPROXY=off \ - TINYPROXY_LOG=Info \ - TINYPROXY_PORT=8888 \ - TINYPROXY_USER= \ - TINYPROXY_PASSWORD= \ + # HTTP proxy + HTTPPROXY= \ + HTTPPROXY_LOG=off \ + HTTPPROXY_PORT=8888 \ + HTTPPROXY_USER= \ + HTTPPROXY_PASSWORD= \ # Shadowsocks SHADOWSOCKS=off \ SHADOWSOCKS_LOG=off \ @@ -109,10 +109,9 @@ ENV VPNSP=pia \ ENTRYPOINT ["/entrypoint"] EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp HEALTHCHECK --interval=10m --timeout=10s --start-period=30s --retries=2 CMD /entrypoint healthcheck -RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tinyproxy tzdata && \ - rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/tinyproxy/tinyproxy.conf && \ +RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \ + rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* && \ deluser openvpn && \ - deluser tinyproxy && \ deluser unbound && \ mkdir /gluetun # TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10 diff --git a/README.md b/README.md index b9168390..4c78a382 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # Gluetun VPN client *Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access, -Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN and PureVPN VPN servers, using Go, OpenVPN, -iptables, DNS over TLS, ShadowSocks and Tinyproxy* +Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN and PureVPN VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* **ANNOUNCEMENT**: *Github Wiki reworked* @@ -36,7 +35,7 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy* - Choose the vpn network protocol, `udp` or `tcp` - Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices - Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP) -- Built in HTTP proxy (Tinyproxy, tunnels TCP) +- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP) - [Connect other containers to it](https://github.com/qdm12/gluetun#connect-to-it) - [Connect LAN devices to it](https://github.com/qdm12/gluetun#connect-to-it) - Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7 🎆 @@ -243,15 +242,16 @@ None of the following values are required. | `SHADOWSOCKS_PASSWORD` | | | Password to use to connect to Shadowsocks | | `SHADOWSOCKS_METHOD` | `chacha20-ietf-poly1305` | `chacha20-ietf-poly1305`, `aes-128-gcm`, `aes-256-gcm` | Method to use for Shadowsocks | -### Tinyproxy +### HTTP proxy | Variable | Default | Choices | Description | | --- | --- | --- | --- | -| `TINYPROXY` | `off` | `on`, `off` | Enable the internal HTTP proxy tinyproxy | -| `TINYPROXY_LOG` | `Info` | `Info`, `Connect`, `Notice`, `Warning`, `Error`, `Critical` | Tinyproxy log level | -| `TINYPROXY_PORT` | `8888` | `1024` to `65535` | Internal port number for Tinyproxy to listen on | -| `TINYPROXY_USER` | | | Username to use to connect to Tinyproxy | -| `TINYPROXY_PASSWORD` | | | Password to use to connect to Tinyproxy | +| `HTTPPROXY` | `off` | `on`, `off` | Enable the internal HTTP proxy | +| `HTTPPROXY_LOG` | `off` | `on` or `off` | Logs every tunnel requests | +| `HTTPPROXY_PORT` | `8888` | `1024` to `65535` | Internal port number for the HTTP proxy to listen on | +| `HTTPPROXY_USER` | | | Username to use to connect to the HTTP proxy | +| `HTTPPROXY_PASSWORD` | | | Password to use to connect to the HTTP proxy | +| `HTTPPROXY_STEALTH` | `off` | `on` or `off` | Stealth mode means HTTP proxy headers are not added to your requests | ### System @@ -295,15 +295,16 @@ There are various ways to achieve this, depending on your use case. Add `network_mode: "container:gluetun"` to your *docker-compose.yml*, provided Gluetun is already running
--
+-
- You might want to use Shadowsocks instead which tunnels UDP as well as TCP, whereas Tinyproxy only tunnels TCP.
+ ⚠️ You might want to use Shadowsocks instead which tunnels UDP as well as TCP and does not leak your credentials.
+ The HTTP proxy will not encrypt your username and password every time you send a request to the HTTP proxy server.
- 1. Setup a HTTP proxy client, such as [SwitchyOmega for Chrome](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en)
+ 1. Setup an HTTP proxy client, such as [SwitchyOmega for Chrome](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en)
1. Ensure the Gluetun container is launched with:
- port `8888` published `-p 8888:8888/tcp`
- 1. With your HTTP proxy client, connect to the Docker host (i.e. `192.168.1.10`) on port `8888`. You need to enter your credentials if you set them with `TINYPROXY_USER` and `TINYPROXY_PASSWORD`.
- 1. If you set `TINYPROXY_LOG` to `Info`, more information will be logged in the Docker logs
+ 1. With your HTTP proxy client, connect to the Docker host (i.e. `192.168.1.10`) on port `8888`. You need to enter your credentials if you set them with `HTTPPROXY_USER` and `HTTPPROXY_PASSWORD`. Note that Chrome does not support authentication.
+ 1. If you set `HTTPPROXY_LOG` to `on`, more information will be logged in the Docker logs
diff --git a/cmd/gluetun/main.go b/cmd/gluetun/main.go
index a4bc8920..2cde8f56 100644
--- a/cmd/gluetun/main.go
+++ b/cmd/gluetun/main.go
@@ -18,6 +18,7 @@ import (
"github.com/qdm12/gluetun/internal/dns"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/healthcheck"
+ "github.com/qdm12/gluetun/internal/httpproxy"
gluetunLogging "github.com/qdm12/gluetun/internal/logging"
"github.com/qdm12/gluetun/internal/openvpn"
"github.com/qdm12/gluetun/internal/params"
@@ -27,7 +28,6 @@ import (
"github.com/qdm12/gluetun/internal/settings"
"github.com/qdm12/gluetun/internal/shadowsocks"
"github.com/qdm12/gluetun/internal/storage"
- "github.com/qdm12/gluetun/internal/tinyproxy"
"github.com/qdm12/gluetun/internal/updater"
versionpkg "github.com/qdm12/gluetun/internal/version"
"github.com/qdm12/golibs/command"
@@ -83,17 +83,15 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
dnsConf := dns.NewConfigurator(logger, client, fileManager)
routingConf := routing.NewRouting(logger)
firewallConf := firewall.NewConfigurator(logger, routingConf, fileManager)
- tinyProxyConf := tinyproxy.NewConfigurator(fileManager, logger)
streamMerger := command.NewStreamMerger()
paramsReader := params.NewReader(logger, fileManager)
fmt.Println(gluetunLogging.Splash(version, commit, buildDate))
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
- "OpenVPN": ovpnConf.Version,
- "Unbound": dnsConf.Version,
- "IPtables": firewallConf.Version,
- "TinyProxy": tinyProxyConf.Version,
+ "OpenVPN": ovpnConf.Version,
+ "Unbound": dnsConf.Version,
+ "IPtables": firewallConf.Version,
})
allSettings, err := settings.GetAllSettings(paramsReader)
@@ -125,11 +123,6 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
logger.Error(err)
return 1
}
- err = fileManager.SetOwnership("/etc/tinyproxy", uid, gid)
- if err != nil {
- logger.Error(err)
- return 1
- }
if allSettings.Firewall.Debug {
firewallConf.SetDebug()
@@ -161,6 +154,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
return 1
}
defer func() {
+ routingConf.SetVerbose(false)
if err := routingConf.TearDown(); err != nil {
logger.Error(err)
}
@@ -244,19 +238,17 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
go publicIPLooper.RunRestartTicker(ctx, wg)
publicIPLooper.SetPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
- tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf,
- allSettings.TinyProxy, logger, streamMerger, uid, gid, defaultInterface)
- restartTinyproxy := tinyproxyLooper.Restart
+ httpProxyLooper := httpproxy.NewLooper(httpClient, logger, allSettings.HTTPProxy)
wg.Add(1)
- go tinyproxyLooper.Run(ctx, wg)
+ go httpProxyLooper.Run(ctx, wg)
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger, defaultInterface)
restartShadowsocks := shadowsocksLooper.Restart
wg.Add(1)
go shadowsocksLooper.Run(ctx, wg)
- if allSettings.TinyProxy.Enabled {
- restartTinyproxy()
+ if allSettings.HTTPProxy.Enabled {
+ httpProxyLooper.Restart()
}
if allSettings.ShadowSocks.Enabled {
restartShadowsocks()
@@ -356,7 +348,7 @@ func printVersions(ctx context.Context, logger logging.Logger,
//nolint:lll
func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
logger logging.Logger, signalTunnelReady func()) {
- // Blocking line merging paramsReader for all programs: openvpn, tinyproxy, unbound and shadowsocks
+ // Blocking line merging paramsReader for openvpn and unbound
logger.Info("Launching standard output merger")
streamMerger.CollectLines(ctx, func(line string) {
line, level := gluetunLogging.PostProcessLine(line)
diff --git a/docker-compose.yml b/docker-compose.yml
index eee84d0f..263039f4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,7 +7,7 @@ services:
- NET_ADMIN
network_mode: bridge
ports:
- - 8888:8888/tcp # Tinyproxy
+ - 8888:8888/tcp # HTTP proxy
- 8388:8388/tcp # Shadowsocks
- 8388:8388/udp # Shadowsocks
- 8000:8000/tcp # Built-in HTTP control server
diff --git a/go.sum b/go.sum
index 8aaed7db..811c1ec7 100644
--- a/go.sum
+++ b/go.sum
@@ -72,8 +72,6 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/qdm12/golibs v0.0.0-20201024185935-092412448c2c h1:9EQyDXbeapnPeMeO8Yq7PE6zqYPGkHp/qijNBBTU74c=
-github.com/qdm12/golibs v0.0.0-20201024185935-092412448c2c/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a h1:v0zUA1FWeVkTEd9KyxfehbRVJeFGOqyMY6FHO/Q9ITU=
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
github.com/qdm12/ss-server v0.0.0-20200819124651-6428e626ee83 h1:b7sNsgsKxH0mbl9L1hdUp5KSDkZ/1kOQ+iHiBVgFElM=
diff --git a/internal/constants/colors.go b/internal/constants/colors.go
index 8ab1b7fe..de8af916 100644
--- a/internal/constants/colors.go
+++ b/internal/constants/colors.go
@@ -6,10 +6,6 @@ func ColorUnbound() *color.Color {
return color.New(color.FgCyan)
}
-func ColorTinyproxy() *color.Color {
- return color.New(color.FgHiGreen)
-}
-
func ColorOpenvpn() *color.Color {
return color.New(color.FgHiMagenta)
}
diff --git a/internal/constants/paths.go b/internal/constants/paths.go
index 67e469b2..49f6153c 100644
--- a/internal/constants/paths.go
+++ b/internal/constants/paths.go
@@ -21,8 +21,6 @@ const (
TunnelDevice models.Filepath = "/dev/net/tun"
// NetRoute is the path to the file containing information on the network route.
NetRoute models.Filepath = "/proc/net/route"
- // TinyProxyConf is the filepath to the tinyproxy configuration file.
- TinyProxyConf models.Filepath = "/etc/tinyproxy/tinyproxy.conf"
// RootHints is the filepath to the root.hints file used by Unbound.
RootHints models.Filepath = "/etc/unbound/root.hints"
// RootKey is the filepath to the root.key file used by Unbound.
diff --git a/internal/constants/tinyproxy.go b/internal/constants/tinyproxy.go
deleted file mode 100644
index 53cf643f..00000000
--- a/internal/constants/tinyproxy.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package constants
-
-import (
- "github.com/qdm12/gluetun/internal/models"
-)
-
-const (
- // TinyProxyInfoLevel is the info log level for TinyProxy.
- TinyProxyInfoLevel models.TinyProxyLogLevel = "Info"
- // TinyProxyConnectLevel is the info log level for TinyProxy.
- TinyProxyConnectLevel models.TinyProxyLogLevel = "Connect"
- // TinyProxyNoticeLevel is the info log level for TinyProxy.
- TinyProxyNoticeLevel models.TinyProxyLogLevel = "Notice"
- // TinyProxyWarnLevel is the warning log level for TinyProxy.
- TinyProxyWarnLevel models.TinyProxyLogLevel = "Warning"
- // TinyProxyErrorLevel is the error log level for TinyProxy.
- TinyProxyErrorLevel models.TinyProxyLogLevel = "Error"
- // TinyProxyCriticalLevel is the critical log level for TinyProxy.
- TinyProxyCriticalLevel models.TinyProxyLogLevel = "Critical"
-)
diff --git a/internal/httpproxy/auth.go b/internal/httpproxy/auth.go
new file mode 100644
index 00000000..94096637
--- /dev/null
+++ b/internal/httpproxy/auth.go
@@ -0,0 +1,33 @@
+package httpproxy
+
+import (
+ "encoding/base64"
+ "net/http"
+ "strings"
+)
+
+func isAuthorized(responseWriter http.ResponseWriter, request *http.Request,
+ username, password string) (authorized bool) {
+ basicAuth := request.Header.Get("Proxy-Authorization")
+ if len(basicAuth) == 0 {
+ responseWriter.WriteHeader(http.StatusProxyAuthRequired)
+ return false
+ }
+ b64UsernamePassword := strings.TrimPrefix(basicAuth, "Basic ")
+ b, err := base64.StdEncoding.DecodeString(b64UsernamePassword)
+ if err != nil {
+ responseWriter.WriteHeader(http.StatusUnauthorized)
+ return false
+ }
+ usernamePassword := strings.Split(string(b), ":")
+ const expectedFields = 2
+ if len(usernamePassword) != expectedFields {
+ responseWriter.WriteHeader(http.StatusBadRequest)
+ return false
+ }
+ if username != usernamePassword[0] && password != usernamePassword[1] {
+ responseWriter.WriteHeader(http.StatusUnauthorized)
+ return false
+ }
+ return true
+}
diff --git a/internal/httpproxy/handler.go b/internal/httpproxy/handler.go
new file mode 100644
index 00000000..5c206739
--- /dev/null
+++ b/internal/httpproxy/handler.go
@@ -0,0 +1,62 @@
+package httpproxy
+
+import (
+ "context"
+ "net/http"
+ "sync"
+ "time"
+
+ "github.com/qdm12/golibs/logging"
+)
+
+func newHandler(ctx context.Context, wg *sync.WaitGroup,
+ client *http.Client, logger logging.Logger,
+ stealth, verbose bool, username, password string) http.Handler {
+ const relayTimeout = 10 * time.Second
+ return &handler{
+ ctx: ctx,
+ wg: wg,
+ client: client,
+ logger: logger,
+ relayTimeout: relayTimeout,
+ verbose: verbose,
+ stealth: stealth,
+ username: username,
+ password: password,
+ }
+}
+
+type handler struct {
+ ctx context.Context
+ wg *sync.WaitGroup
+ client *http.Client
+ logger logging.Logger
+ relayTimeout time.Duration
+ verbose, stealth bool
+ username, password string
+}
+
+func (h *handler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) {
+ if len(h.username) > 0 && !isAuthorized(responseWriter, request, h.username, h.password) {
+ h.logger.Info("%s unauthorized", request.RemoteAddr)
+ return
+ }
+ switch request.Method {
+ case http.MethodConnect:
+ h.handleHTTPS(responseWriter, request)
+ default:
+ h.handleHTTP(responseWriter, request)
+ }
+}
+
+// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
+var hopHeaders = [...]string{ //nolint:gochecknoglobals
+ "Connection",
+ "Keep-Alive",
+ "Proxy-Authenticate",
+ "Proxy-Authorization",
+ "Te", // canonicalized version of "TE"
+ "Trailers",
+ "Transfer-Encoding",
+ "Upgrade",
+}
diff --git a/internal/httpproxy/http.go b/internal/httpproxy/http.go
new file mode 100644
index 00000000..438872dc
--- /dev/null
+++ b/internal/httpproxy/http.go
@@ -0,0 +1,74 @@
+package httpproxy
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "strings"
+)
+
+func (h *handler) handleHTTP(responseWriter http.ResponseWriter, request *http.Request) {
+ switch request.URL.Scheme {
+ case "http", "https":
+ default:
+ h.logger.Warn("Unsupported scheme %q", request.URL.Scheme)
+ http.Error(responseWriter, "unsupported scheme", http.StatusBadRequest)
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(h.ctx, h.relayTimeout)
+ defer cancel()
+ request = request.WithContext(ctx)
+
+ request.RequestURI = ""
+
+ for _, key := range hopHeaders {
+ request.Header.Del(key)
+ }
+
+ if !h.stealth {
+ setForwardedHeaders(request)
+ }
+
+ response, err := h.client.Do(request)
+ if err != nil {
+ http.Error(responseWriter, "server error", http.StatusInternalServerError)
+ h.logger.Warn("cannot request %s for client %q: %s",
+ request.URL, request.RemoteAddr, err)
+ return
+ }
+ defer response.Body.Close()
+ if h.verbose {
+ h.logger.Info("%s %s %s %s", request.RemoteAddr, response.Status, request.Method, request.URL)
+ }
+
+ for _, key := range hopHeaders {
+ response.Header.Del(key)
+ }
+
+ targetHeaderPtr := responseWriter.Header()
+ for key, values := range response.Header {
+ for _, value := range values {
+ targetHeaderPtr.Add(key, value)
+ }
+ }
+
+ responseWriter.WriteHeader(response.StatusCode)
+ if _, err := io.Copy(responseWriter, response.Body); err != nil {
+ h.logger.Error("%s %s: body copy error: %s", request.RemoteAddr, request.URL, err)
+ }
+}
+
+func setForwardedHeaders(request *http.Request) {
+ clientIP, _, err := net.SplitHostPort(request.RemoteAddr)
+ if err != nil {
+ return
+ }
+ // keep existing proxy headers
+ if prior, ok := request.Header["X-Forwarded-For"]; ok {
+ clientIP = fmt.Sprintf("%s,%s", strings.Join(prior, ", "), clientIP)
+ }
+ request.Header.Set("X-Forwarded-For", clientIP)
+}
diff --git a/internal/httpproxy/https.go b/internal/httpproxy/https.go
new file mode 100644
index 00000000..fa00a34e
--- /dev/null
+++ b/internal/httpproxy/https.go
@@ -0,0 +1,64 @@
+package httpproxy
+
+import (
+ "context"
+ "io"
+ "net"
+ "net/http"
+ "sync"
+)
+
+func (h *handler) handleHTTPS(responseWriter http.ResponseWriter, request *http.Request) {
+ dialer := net.Dialer{Timeout: h.relayTimeout}
+ destinationConn, err := dialer.DialContext(h.ctx, "tcp", request.Host)
+ if err != nil {
+ http.Error(responseWriter, err.Error(), http.StatusServiceUnavailable)
+ return
+ }
+
+ responseWriter.WriteHeader(http.StatusOK)
+
+ hijacker, ok := responseWriter.(http.Hijacker)
+ if !ok {
+ http.Error(responseWriter, "Hijacking not supported", http.StatusInternalServerError)
+ return
+ }
+ clientConnection, _, err := hijacker.Hijack()
+ if err != nil {
+ h.logger.Warn(err)
+ http.Error(responseWriter, err.Error(), http.StatusServiceUnavailable)
+ if err := destinationConn.Close(); err != nil {
+ h.logger.Error("closing destination connection: %s", err)
+ }
+ return
+ }
+
+ if h.verbose {
+ h.logger.Info("%s <-> %s", request.RemoteAddr, request.Host)
+ }
+
+ h.wg.Add(1)
+ ctx, cancel := context.WithCancel(h.ctx)
+ const transferGoroutines = 2
+ wg := &sync.WaitGroup{}
+ wg.Add(transferGoroutines)
+ go func() { // trigger cleanup when done
+ wg.Wait()
+ cancel()
+ }()
+ go func() { // cleanup
+ <-ctx.Done()
+ destinationConn.Close()
+ clientConnection.Close()
+ h.wg.Done()
+ }()
+ go transfer(destinationConn, clientConnection, wg)
+ go transfer(clientConnection, destinationConn, wg)
+}
+
+func transfer(destination io.WriteCloser, source io.ReadCloser, wg *sync.WaitGroup) {
+ _, _ = io.Copy(destination, source)
+ _ = source.Close()
+ _ = destination.Close()
+ wg.Done()
+}
diff --git a/internal/httpproxy/loop.go b/internal/httpproxy/loop.go
new file mode 100644
index 00000000..e36ad109
--- /dev/null
+++ b/internal/httpproxy/loop.go
@@ -0,0 +1,139 @@
+package httpproxy
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "sync"
+
+ "github.com/qdm12/gluetun/internal/settings"
+ "github.com/qdm12/golibs/logging"
+)
+
+type Looper interface {
+ Run(ctx context.Context, wg *sync.WaitGroup)
+ Restart()
+ Start()
+ Stop()
+ GetSettings() (settings settings.HTTPProxy)
+ SetSettings(settings settings.HTTPProxy)
+}
+
+type looper struct {
+ client *http.Client
+ settings settings.HTTPProxy
+ settingsMutex sync.RWMutex
+ logger logging.Logger
+ restart chan struct{}
+ start chan struct{}
+ stop chan struct{}
+}
+
+func NewLooper(client *http.Client, logger logging.Logger,
+ settings settings.HTTPProxy) Looper {
+ return &looper{
+ client: client,
+ settings: settings,
+ logger: logger.WithPrefix("http proxy: "),
+ restart: make(chan struct{}),
+ start: make(chan struct{}),
+ stop: make(chan struct{}),
+ }
+}
+
+func (l *looper) GetSettings() (settings settings.HTTPProxy) {
+ l.settingsMutex.RLock()
+ defer l.settingsMutex.RUnlock()
+ return l.settings
+}
+
+func (l *looper) SetSettings(settings settings.HTTPProxy) {
+ l.settingsMutex.Lock()
+ defer l.settingsMutex.Unlock()
+ l.settings = settings
+}
+
+func (l *looper) isEnabled() bool {
+ l.settingsMutex.RLock()
+ defer l.settingsMutex.RUnlock()
+ return l.settings.Enabled
+}
+
+func (l *looper) setEnabled(enabled bool) {
+ l.settingsMutex.Lock()
+ defer l.settingsMutex.Unlock()
+ l.settings.Enabled = enabled
+}
+
+func (l *looper) Restart() { l.restart <- struct{}{} }
+func (l *looper) Start() { l.start <- struct{}{} }
+func (l *looper) Stop() { l.stop <- struct{}{} }
+
+func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
+ defer wg.Done()
+ waitForStart := true
+ for waitForStart {
+ select {
+ case <-l.stop:
+ l.logger.Info("not started yet")
+ case <-l.start:
+ waitForStart = false
+ case <-l.restart:
+ waitForStart = false
+ case <-ctx.Done():
+ return
+ }
+ }
+ defer l.logger.Warn("loop exited")
+
+ for ctx.Err() == nil {
+ for !l.isEnabled() {
+ // wait for a signal to re-enable
+ select {
+ case <-l.stop:
+ l.logger.Info("already disabled")
+ case <-l.restart:
+ l.setEnabled(true)
+ case <-l.start:
+ l.setEnabled(true)
+ case <-ctx.Done():
+ return
+ }
+ }
+
+ settings := l.GetSettings()
+ address := fmt.Sprintf("0.0.0.0:%d", settings.Port)
+
+ server := New(ctx, address, l.logger, l.client, settings.Stealth, settings.Log, settings.User, settings.Password)
+
+ runCtx, runCancel := context.WithCancel(context.Background())
+ runWg := &sync.WaitGroup{}
+ runWg.Add(1)
+ go server.Run(runCtx, runWg)
+
+ stayHere := true
+ for stayHere {
+ select {
+ case <-ctx.Done():
+ l.logger.Warn("context canceled: exiting loop")
+ runCancel()
+ runWg.Wait()
+ return
+ case <-l.restart: // triggered restart
+ l.logger.Info("restarting")
+ runCancel()
+ runWg.Wait()
+ stayHere = false
+ case <-l.start:
+ l.logger.Info("already started")
+ case <-l.stop:
+ l.logger.Info("stopping")
+ runCancel()
+ runWg.Wait()
+ l.setEnabled(false)
+ stayHere = false
+ }
+ }
+ runCancel() // repetition for linter only
+ }
+}
diff --git a/internal/httpproxy/server.go b/internal/httpproxy/server.go
new file mode 100644
index 00000000..3da4bf0a
--- /dev/null
+++ b/internal/httpproxy/server.go
@@ -0,0 +1,55 @@
+package httpproxy
+
+import (
+ "context"
+ "net/http"
+ "sync"
+ "time"
+
+ "github.com/qdm12/golibs/logging"
+)
+
+type Server interface {
+ Run(ctx context.Context, wg *sync.WaitGroup)
+}
+
+type server struct {
+ address string
+ handler http.Handler
+ logger logging.Logger
+ internalWG *sync.WaitGroup
+}
+
+func New(ctx context.Context, address string,
+ logger logging.Logger, client *http.Client,
+ stealth, verbose bool, username, password string) Server {
+ wg := &sync.WaitGroup{}
+ return &server{
+ address: address,
+ handler: newHandler(ctx, wg, client, logger, stealth, verbose, username, password),
+ logger: logger,
+ internalWG: wg,
+ }
+}
+
+func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
+ defer wg.Done()
+ server := http.Server{Addr: s.address, Handler: s.handler}
+ go func() {
+ <-ctx.Done()
+ s.logger.Warn("context canceled: exiting loop")
+ defer s.logger.Warn("loop exited")
+ const shutdownGraceDuration = 2 * time.Second
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
+ defer cancel()
+ if err := server.Shutdown(shutdownCtx); err != nil {
+ s.logger.Error("failed shutting down: %s", err)
+ }
+ }()
+ s.logger.Info("listening on %s", s.address)
+ err := server.ListenAndServe()
+ if err != nil && ctx.Err() != context.Canceled {
+ s.logger.Error(err)
+ }
+ s.internalWG.Wait()
+}
diff --git a/internal/logging/line.go b/internal/logging/line.go
index 031c386a..4820e7e0 100644
--- a/internal/logging/line.go
+++ b/internal/logging/line.go
@@ -12,13 +12,9 @@ import (
//nolint:lll
var regularExpressions = struct { //nolint:gochecknoglobals
- unboundPrefix *regexp.Regexp
- tinyproxyLoglevel *regexp.Regexp
- tinyproxyPrefix *regexp.Regexp
+ unboundPrefix *regexp.Regexp
}{
- unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:0\] `),
- tinyproxyLoglevel: regexp.MustCompile(`INFO|CONNECT|NOTICE|WARNING|ERROR|CRITICAL`),
- tinyproxyPrefix: regexp.MustCompile(`tinyproxy: .+[ ]+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9] \[[0-9]+\]: `),
+ unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:0\] `),
}
func PostProcessLine(s string) (filtered string, level logging.Level) {
@@ -78,21 +74,6 @@ func PostProcessLine(s string) (filtered string, level logging.Level) {
filtered = fmt.Sprintf("unbound: %s", filtered)
filtered = constants.ColorUnbound().Sprintf(filtered)
return filtered, level
- case strings.HasPrefix(s, "tinyproxy: "):
- logLevel := regularExpressions.tinyproxyLoglevel.FindString(s)
- prefix := regularExpressions.tinyproxyPrefix.FindString(s)
- filtered = fmt.Sprintf("tinyproxy: %s", s[len(prefix):])
- filtered = constants.ColorTinyproxy().Sprintf(filtered)
- switch logLevel {
- case "INFO", "CONNECT", "NOTICE":
- return filtered, logging.InfoLevel
- case "WARNING":
- return filtered, logging.WarnLevel
- case "ERROR", "CRITICAL":
- return filtered, logging.ErrorLevel
- default:
- return filtered, logging.ErrorLevel
- }
}
return s, logging.InfoLevel
}
diff --git a/internal/logging/line_test.go b/internal/logging/line_test.go
index 6f6fb1cc..c8001a30 100644
--- a/internal/logging/line_test.go
+++ b/internal/logging/line_test.go
@@ -36,34 +36,6 @@ func Test_PostProcessLine(t *testing.T) {
"unbound: [1594595249] unbound[75:0] BLA: init module 0: validator",
"unbound: BLA: init module 0: validator",
logging.ErrorLevel},
- "tinyproxy info": {
- "tinyproxy: INFO Jul 12 23:07:25 [32]: Reloading config file",
- "tinyproxy: Reloading config file",
- logging.InfoLevel},
- "tinyproxy connect": {
- "tinyproxy: CONNECT Jul 12 23:07:25 [32]: Reloading config file",
- "tinyproxy: Reloading config file",
- logging.InfoLevel},
- "tinyproxy notice": {
- "tinyproxy: NOTICE Jul 12 23:07:25 [32]: Reloading config file",
- "tinyproxy: Reloading config file",
- logging.InfoLevel},
- "tinyproxy warning": {
- "tinyproxy: WARNING Jul 12 23:07:25 [32]: Reloading config file",
- "tinyproxy: Reloading config file",
- logging.WarnLevel},
- "tinyproxy error": {
- "tinyproxy: ERROR Jul 12 23:07:25 [32]: Reloading config file",
- "tinyproxy: Reloading config file",
- logging.ErrorLevel},
- "tinyproxy critical": {
- "tinyproxy: CRITICAL Jul 12 23:07:25 [32]: Reloading config file",
- "tinyproxy: Reloading config file",
- logging.ErrorLevel},
- "tinyproxy unknown": {
- "tinyproxy: BLABLA Jul 12 23:07:25 [32]: Reloading config file",
- "tinyproxy: Reloading config file",
- logging.ErrorLevel},
"openvpn unknown": {
"openvpn: message",
"openvpn: message",
diff --git a/internal/logging/splash.go b/internal/logging/splash.go
index 4ebbf0d4..7bf616fe 100644
--- a/internal/logging/splash.go
+++ b/internal/logging/splash.go
@@ -27,7 +27,7 @@ func title() []string {
"================ Gluetun ================",
"=========================================",
"==== A mix of OpenVPN, DNS over TLS, ====",
- "======= Shadowsocks and Tinyproxy =======",
+ "======= Shadowsocks and HTTP proxy ======",
"========= all glued up with Go ==========",
"=========================================",
"=========== For tunneling to ============",
diff --git a/internal/models/alias.go b/internal/models/alias.go
index 6dc7b6ae..b789ab7c 100644
--- a/internal/models/alias.go
+++ b/internal/models/alias.go
@@ -16,8 +16,6 @@ type (
URL string
// Filepath is a local filesytem file path.
Filepath string
- // TinyProxyLogLevel is the log level for TinyProxy.
- TinyProxyLogLevel string
// VPNProvider is the name of the VPN provider to be used.
VPNProvider string
// NetworkProtocol contains the network protocol to be used to communicate with the VPN servers.
diff --git a/internal/params/httpproxy.go b/internal/params/httpproxy.go
new file mode 100644
index 00000000..0b2b6740
--- /dev/null
+++ b/internal/params/httpproxy.go
@@ -0,0 +1,78 @@
+package params
+
+import (
+ "strings"
+
+ libparams "github.com/qdm12/golibs/params"
+)
+
+// GetHTTPProxy obtains if the HTTP proxy is on from the environment variable
+// HTTPPROXY, and using PROXY and TINYPROXY as retro-compatibility names.
+func (r *reader) GetHTTPProxy() (enabled bool, err error) {
+ retroKeysOption := libparams.RetroKeys(
+ []string{"TINYPROXY", "PROXY"},
+ r.onRetroActive,
+ )
+ return r.envParams.GetOnOff("HTTPPROXY", retroKeysOption, libparams.Default("off"))
+}
+
+// GetHTTPProxyLog obtains the if http proxy requests should be logged from
+// the environment variable HTTPPROXY_LOG, and using PROXY_LOG_LEVEL and
+// TINYPROXY_LOG as retro-compatibility names.
+func (r *reader) GetHTTPProxyLog() (log bool, err error) {
+ s, _ := r.envParams.GetEnv("HTTPPROXY_LOG")
+ if len(s) == 0 {
+ s, _ = r.envParams.GetEnv("PROXY_LOG_LEVEL")
+ if len(s) == 0 {
+ s, _ = r.envParams.GetEnv("TINYPROXY_LOG")
+ if len(s) == 0 {
+ return false, nil // default log disabled
+ }
+ }
+ switch strings.ToLower(s) {
+ case "info", "connect", "notice":
+ return true, nil
+ default:
+ return false, nil
+ }
+ }
+ return r.envParams.GetOnOff("HTTPPROXY_LOG", libparams.Default("off"))
+}
+
+// GetHTTPProxyPort obtains the HTTP proxy listening port from the environment variable
+// HTTPPROXY_PORT, and using PROXY_PORT and TINYPROXY_PORT as retro-compatibility names.
+func (r *reader) GetHTTPProxyPort() (port uint16, err error) {
+ retroKeysOption := libparams.RetroKeys(
+ []string{"TINYPROXY_PORT", "PROXY_PORT"},
+ r.onRetroActive,
+ )
+ return r.envParams.GetPort("HTTPPROXY_PORT", retroKeysOption, libparams.Default("8888"))
+}
+
+// GetHTTPProxyUser obtains the HTTP proxy server user from the environment variable
+// HTTPPROXY_USER, and using TINYPROXY_USER and PROXY_USER as retro-compatibility names.
+func (r *reader) GetHTTPProxyUser() (user string, err error) {
+ retroKeysOption := libparams.RetroKeys(
+ []string{"TINYPROXY_USER", "PROXY_USER"},
+ r.onRetroActive,
+ )
+ return r.envParams.GetEnv("HTTPPROXY_USER",
+ retroKeysOption, libparams.CaseSensitiveValue(), libparams.Unset())
+}
+
+// GetHTTPProxyPassword obtains the HTTP proxy server password from the environment variable
+// HTTPPROXY_PASSWORD, and using TINYPROXY_PASSWORD and PROXY_PASSWORD as retro-compatibility names.
+func (r *reader) GetHTTPProxyPassword() (password string, err error) {
+ retroKeysOption := libparams.RetroKeys(
+ []string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"},
+ r.onRetroActive,
+ )
+ return r.envParams.GetEnv("HTTPPROXY_PASSWORD",
+ retroKeysOption, libparams.CaseSensitiveValue(), libparams.Unset())
+}
+
+// GetHTTPProxyStealth obtains the HTTP proxy server stealth mode
+// from the environment variable HTTPPROXY_STEALTH.
+func (r *reader) GetHTTPProxyStealth() (stealth bool, err error) {
+ return r.envParams.GetOnOff("HTTPPROXY_STEALTH", libparams.Default("off"))
+}
diff --git a/internal/params/params.go b/internal/params/params.go
index 345d25a7..0021b67f 100644
--- a/internal/params/params.go
+++ b/internal/params/params.go
@@ -102,12 +102,13 @@ type Reader interface {
GetShadowSocksPassword() (password string, err error)
GetShadowSocksMethod() (method string, err error)
- // Tinyproxy getters
- GetTinyProxy() (activated bool, err error)
- GetTinyProxyLog() (models.TinyProxyLogLevel, error)
- GetTinyProxyPort() (port uint16, err error)
- GetTinyProxyUser() (user string, err error)
- GetTinyProxyPassword() (password string, err error)
+ // HTTP proxy getters
+ GetHTTPProxy() (activated bool, err error)
+ GetHTTPProxyLog() (log bool, err error)
+ GetHTTPProxyPort() (port uint16, err error)
+ GetHTTPProxyUser() (user string, err error)
+ GetHTTPProxyPassword() (password string, err error)
+ GetHTTPProxyStealth() (stealth bool, err error)
// Public IP getters
GetPublicIPPeriod() (period time.Duration, err error)
diff --git a/internal/params/tinyproxy.go b/internal/params/tinyproxy.go
deleted file mode 100644
index f61ac183..00000000
--- a/internal/params/tinyproxy.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package params
-
-import (
- "strconv"
-
- "github.com/qdm12/gluetun/internal/models"
- libparams "github.com/qdm12/golibs/params"
-)
-
-// GetTinyProxy obtains if TinyProxy is on from the environment variable
-// TINYPROXY, and using PROXY as a retro-compatibility name.
-func (r *reader) GetTinyProxy() (activated bool, err error) {
- // Retro-compatibility
- s, err := r.envParams.GetEnv("PROXY")
- if err != nil {
- return false, err
- } else if len(s) != 0 {
- r.logger.Warn("You are using the old environment variable PROXY, please consider changing it to TINYPROXY")
- return r.envParams.GetOnOff("PROXY", libparams.Compulsory())
- }
- return r.envParams.GetOnOff("TINYPROXY", libparams.Default("off"))
-}
-
-// GetTinyProxyLog obtains the TinyProxy log level from the environment variable
-// TINYPROXY_LOG, and using PROXY_LOG_LEVEL as a retro-compatibility name.
-func (r *reader) GetTinyProxyLog() (models.TinyProxyLogLevel, error) {
- // Retro-compatibility
- s, err := r.envParams.GetEnv("PROXY_LOG_LEVEL")
- if err != nil {
- return models.TinyProxyLogLevel(s), err
- } else if len(s) != 0 {
- r.logger.Warn("You are using the old environment variable PROXY_LOG_LEVEL, please consider changing it to TINYPROXY_LOG") //nolint:lll
- s, err = r.envParams.GetValueIfInside("PROXY_LOG_LEVEL",
- []string{"Info", "Connect", "Notice", "Warning", "Error", "Critical"},
- libparams.Compulsory())
- return models.TinyProxyLogLevel(s), err
- }
- s, err = r.envParams.GetValueIfInside("TINYPROXY_LOG",
- []string{"Info", "Connect", "Notice", "Warning", "Error", "Critical"},
- libparams.Default("Connect"))
- return models.TinyProxyLogLevel(s), err
-}
-
-// GetTinyProxyPort obtains the TinyProxy listening port from the environment variable
-// TINYPROXY_PORT, and using PROXY_PORT as a retro-compatibility name.
-func (r *reader) GetTinyProxyPort() (port uint16, err error) {
- // Retro-compatibility
- portStr, err := r.envParams.GetEnv("PROXY_PORT")
- switch {
- case err != nil:
- return 0, err
- case len(portStr) != 0:
- r.logger.Warn("You are using the old environment variable PROXY_PORT, please consider changing it to TINYPROXY_PORT")
- default:
- portStr, err = r.envParams.GetEnv("TINYPROXY_PORT", libparams.Default("8888"))
- if err != nil {
- return 0, err
- }
- }
- if err := r.verifier.VerifyPort(portStr); err != nil {
- return 0, err
- }
- portUint64, err := strconv.ParseUint(portStr, 10, 16)
- return uint16(portUint64), err
-}
-
-// GetTinyProxyUser obtains the TinyProxy server user from the environment variable
-// TINYPROXY_USER, and using PROXY_USER as a retro-compatibility name.
-func (r *reader) GetTinyProxyUser() (user string, err error) {
- defer func() {
- unsetErr := r.unsetEnv("PROXY_USER")
- if err == nil {
- err = unsetErr
- }
- }()
- defer func() {
- unsetErr := r.unsetEnv("TINYPROXY_USER")
- if err == nil {
- err = unsetErr
- }
- }()
- // Retro-compatibility
- user, err = r.envParams.GetEnv("PROXY_USER", libparams.CaseSensitiveValue())
- if err != nil {
- return user, err
- }
- if len(user) != 0 {
- r.logger.Warn("You are using the old environment variable PROXY_USER, please consider changing it to TINYPROXY_USER")
- return user, nil
- }
- return r.envParams.GetEnv("TINYPROXY_USER", libparams.CaseSensitiveValue())
-}
-
-// GetTinyProxyPassword obtains the TinyProxy server password from the environment variable
-// TINYPROXY_PASSWORD, and using PROXY_PASSWORD as a retro-compatibility name.
-func (r *reader) GetTinyProxyPassword() (password string, err error) {
- defer func() {
- unsetErr := r.unsetEnv("PROXY_PASSWORD")
- if err == nil {
- err = unsetErr
- }
- }()
- defer func() {
- unsetErr := r.unsetEnv("TINYPROXY_PASSWORD")
- if err == nil {
- err = unsetErr
- }
- }()
-
- // Retro-compatibility
- password, err = r.envParams.GetEnv("PROXY_PASSWORD", libparams.CaseSensitiveValue())
- if err != nil {
- return password, err
- }
- if len(password) != 0 {
- r.logger.Warn("You are using the old environment variable PROXY_PASSWORD, please consider changing it to TINYPROXY_PASSWORD") //nolint:lll
- return password, nil
- }
- return r.envParams.GetEnv("TINYPROXY_PASSWORD", libparams.CaseSensitiveValue())
-}
diff --git a/internal/settings/httpproxy.go b/internal/settings/httpproxy.go
new file mode 100644
index 00000000..de7be358
--- /dev/null
+++ b/internal/settings/httpproxy.go
@@ -0,0 +1,71 @@
+package settings
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/qdm12/gluetun/internal/params"
+)
+
+// HTTPProxy contains settings to configure the HTTP proxy.
+type HTTPProxy struct { //nolint:maligned
+ Enabled bool
+ Port uint16
+ User string
+ Password string
+ Stealth bool
+ Log bool
+}
+
+func (h *HTTPProxy) String() string {
+ if !h.Enabled {
+ return "HTTP Proxy settings: disabled"
+ }
+ auth, log, stealth := disabled, disabled, disabled
+ if h.User != "" {
+ auth = enabled
+ }
+ if h.Log {
+ log = enabled
+ }
+ if h.Stealth {
+ stealth = enabled
+ }
+ settingsList := []string{
+ "HTTP proxy settings:",
+ fmt.Sprintf("Port: %d", h.Port),
+ "Authentication: " + auth,
+ "Stealth: " + stealth,
+ "Log: " + log,
+ }
+ return strings.Join(settingsList, "\n |--")
+}
+
+// GetHTTPProxySettings obtains HTTPProxy settings from environment variables using the params package.
+func GetHTTPProxySettings(paramsReader params.Reader) (settings HTTPProxy, err error) {
+ settings.Enabled, err = paramsReader.GetHTTPProxy()
+ if err != nil || !settings.Enabled {
+ return settings, err
+ }
+ settings.Port, err = paramsReader.GetHTTPProxyPort()
+ if err != nil {
+ return settings, err
+ }
+ settings.User, err = paramsReader.GetHTTPProxyUser()
+ if err != nil {
+ return settings, err
+ }
+ settings.Password, err = paramsReader.GetHTTPProxyPassword()
+ if err != nil {
+ return settings, err
+ }
+ settings.Stealth, err = paramsReader.GetHTTPProxyStealth()
+ if err != nil {
+ return settings, err
+ }
+ settings.Log, err = paramsReader.GetHTTPProxyLog()
+ if err != nil {
+ return settings, err
+ }
+ return settings, nil
+}
diff --git a/internal/settings/settings.go b/internal/settings/settings.go
index 065459a6..9042062c 100644
--- a/internal/settings/settings.go
+++ b/internal/settings/settings.go
@@ -21,7 +21,7 @@ type Settings struct {
System System
DNS DNS
Firewall Firewall
- TinyProxy TinyProxy
+ HTTPProxy HTTPProxy
ShadowSocks ShadowSocks
PublicIPPeriod time.Duration
UpdaterPeriod time.Duration
@@ -44,7 +44,7 @@ func (s *Settings) String() string {
s.System.String(),
s.DNS.String(),
s.Firewall.String(),
- s.TinyProxy.String(),
+ s.HTTPProxy.String(),
s.ShadowSocks.String(),
s.ControlServer.String(),
"Public IP check period: " + s.PublicIPPeriod.String(), // TODO print disabled if 0
@@ -73,7 +73,7 @@ func GetAllSettings(paramsReader params.Reader) (settings Settings, err error) {
if err != nil {
return settings, err
}
- settings.TinyProxy, err = GetTinyProxySettings(paramsReader)
+ settings.HTTPProxy, err = GetHTTPProxySettings(paramsReader)
if err != nil {
return settings, err
}
diff --git a/internal/settings/tinyproxy.go b/internal/settings/tinyproxy.go
deleted file mode 100644
index d6481df7..00000000
--- a/internal/settings/tinyproxy.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package settings
-
-import (
- "fmt"
- "strings"
-
- "github.com/qdm12/gluetun/internal/models"
- "github.com/qdm12/gluetun/internal/params"
-)
-
-// TinyProxy contains settings to configure TinyProxy.
-type TinyProxy struct {
- User string
- Password string
- LogLevel models.TinyProxyLogLevel
- Port uint16
- Enabled bool
-}
-
-func (t *TinyProxy) String() string {
- if !t.Enabled {
- return "TinyProxy settings: disabled"
- }
- auth := disabled
- if t.User != "" {
- auth = enabled
- }
- settingsList := []string{
- fmt.Sprintf("Port: %d", t.Port),
- "Authentication: " + auth,
- "Log level: " + string(t.LogLevel),
- }
- return "TinyProxy settings:\n" + strings.Join(settingsList, "\n |--")
-}
-
-// GetTinyProxySettings obtains TinyProxy settings from environment variables using the params package.
-func GetTinyProxySettings(paramsReader params.Reader) (settings TinyProxy, err error) {
- settings.Enabled, err = paramsReader.GetTinyProxy()
- if err != nil || !settings.Enabled {
- return settings, err
- }
- settings.User, err = paramsReader.GetTinyProxyUser()
- if err != nil {
- return settings, err
- }
- settings.Password, err = paramsReader.GetTinyProxyPassword()
- if err != nil {
- return settings, err
- }
- settings.Port, err = paramsReader.GetTinyProxyPort()
- if err != nil {
- return settings, err
- }
- settings.LogLevel, err = paramsReader.GetTinyProxyLog()
- if err != nil {
- return settings, err
- }
- return settings, nil
-}
diff --git a/internal/tinyproxy/command.go b/internal/tinyproxy/command.go
deleted file mode 100644
index c850250b..00000000
--- a/internal/tinyproxy/command.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package tinyproxy
-
-import (
- "context"
- "fmt"
- "io"
- "strings"
-)
-
-func (c *configurator) Start(ctx context.Context) (stdout io.ReadCloser, waitFn func() error, err error) {
- c.logger.Info("starting tinyproxy server")
- stdout, _, waitFn, err = c.commander.Start(ctx, "tinyproxy", "-d")
- return stdout, waitFn, err
-}
-
-// Version obtains the version of the installed Tinyproxy server.
-func (c *configurator) Version(ctx context.Context) (string, error) {
- output, err := c.commander.Run(ctx, "tinyproxy", "-v")
- if err != nil {
- return "", err
- }
- words := strings.Fields(output)
- const minWords = 2
- if len(words) < minWords {
- return "", fmt.Errorf("tinyproxy -v: output is too short: %q", output)
- }
- return words[1], nil
-}
diff --git a/internal/tinyproxy/conf.go b/internal/tinyproxy/conf.go
deleted file mode 100644
index bf4efe57..00000000
--- a/internal/tinyproxy/conf.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package tinyproxy
-
-import (
- "fmt"
- "sort"
-
- "github.com/qdm12/gluetun/internal/constants"
- "github.com/qdm12/gluetun/internal/models"
- "github.com/qdm12/golibs/files"
-)
-
-func (c *configurator) MakeConf(logLevel models.TinyProxyLogLevel,
- port uint16, user, password string, uid, gid int) error {
- c.logger.Info("generating tinyproxy configuration file")
- lines := generateConf(logLevel, port, user, password, uid, gid)
- return c.fileManager.WriteLinesToFile(string(constants.TinyProxyConf),
- lines,
- files.Ownership(uid, gid),
- files.Permissions(constants.UserReadPermission))
-}
-
-func generateConf(logLevel models.TinyProxyLogLevel, port uint16, user, password string, uid, gid int) (
- lines []string) {
- confMapping := map[string]string{
- "User": fmt.Sprintf("%d", uid),
- "Group": fmt.Sprintf("%d", gid),
- "Port": fmt.Sprintf("%d", port),
- "Timeout": "600",
- "DefaultErrorFile": "\"/usr/share/tinyproxy/default.html\"",
- "MaxClients": "100",
- "MinSpareServers": "5",
- "MaxSpareServers": "20",
- "StartServers": "10",
- "MaxRequestsPerChild": "0",
- "DisableViaHeader": "Yes",
- "LogLevel": string(logLevel),
- // "StatFile": "\"/usr/share/tinyproxy/stats.html\"",
- }
- if len(user) > 0 {
- confMapping["BasicAuth"] = fmt.Sprintf("%s %s", user, password)
- }
- for k, v := range confMapping {
- line := fmt.Sprintf("%s %s", k, v)
- lines = append(lines, line)
- }
- sort.Slice(lines, func(i, j int) bool {
- return lines[i] < lines[j]
- })
- return lines
-}
diff --git a/internal/tinyproxy/conf_test.go b/internal/tinyproxy/conf_test.go
deleted file mode 100644
index 56ebb07e..00000000
--- a/internal/tinyproxy/conf_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package tinyproxy
-
-import (
- "testing"
-
- "github.com/qdm12/gluetun/internal/constants"
- "github.com/qdm12/gluetun/internal/models"
- "github.com/stretchr/testify/assert"
-)
-
-func Test_generateConf(t *testing.T) {
- t.Parallel()
- tests := map[string]struct {
- logLevel models.TinyProxyLogLevel
- port uint16
- user string
- password string
- lines []string
- }{
- "No credentials": {
- logLevel: constants.TinyProxyInfoLevel,
- port: 2000,
- lines: []string{
- "DefaultErrorFile \"/usr/share/tinyproxy/default.html\"",
- "DisableViaHeader Yes",
- "Group 1001",
- "LogLevel Info",
- "MaxClients 100",
- "MaxRequestsPerChild 0",
- "MaxSpareServers 20",
- "MinSpareServers 5",
- "Port 2000",
- "StartServers 10",
- "Timeout 600",
- "User 1000",
- },
- },
- "With credentials": {
- logLevel: constants.TinyProxyErrorLevel,
- port: 2000,
- user: "abc",
- password: "def",
- lines: []string{
- "BasicAuth abc def",
- "DefaultErrorFile \"/usr/share/tinyproxy/default.html\"",
- "DisableViaHeader Yes",
- "Group 1001",
- "LogLevel Error",
- "MaxClients 100",
- "MaxRequestsPerChild 0",
- "MaxSpareServers 20",
- "MinSpareServers 5",
- "Port 2000",
- "StartServers 10",
- "Timeout 600",
- "User 1000",
- },
- },
- }
- for name, tc := range tests {
- tc := tc
- t.Run(name, func(t *testing.T) {
- t.Parallel()
- lines := generateConf(tc.logLevel, tc.port, tc.user, tc.password, 1000, 1001)
- assert.Equal(t, tc.lines, lines)
- })
- }
-}
diff --git a/internal/tinyproxy/loop.go b/internal/tinyproxy/loop.go
deleted file mode 100644
index 7267072d..00000000
--- a/internal/tinyproxy/loop.go
+++ /dev/null
@@ -1,194 +0,0 @@
-package tinyproxy
-
-import (
- "context"
- "sync"
- "time"
-
- "github.com/qdm12/gluetun/internal/firewall"
- "github.com/qdm12/gluetun/internal/settings"
- "github.com/qdm12/golibs/command"
- "github.com/qdm12/golibs/logging"
-)
-
-type Looper interface {
- Run(ctx context.Context, wg *sync.WaitGroup)
- Restart()
- Start()
- Stop()
- GetSettings() (settings settings.TinyProxy)
- SetSettings(settings settings.TinyProxy)
-}
-
-type looper struct {
- conf Configurator
- firewallConf firewall.Configurator
- settings settings.TinyProxy
- settingsMutex sync.RWMutex
- logger logging.Logger
- streamMerger command.StreamMerger
- uid int
- gid int
- defaultInterface string
- restart chan struct{}
- start chan struct{}
- stop chan struct{}
-}
-
-func (l *looper) logAndWait(ctx context.Context, err error) {
- l.logger.Error(err)
- const waitTime = time.Minute
- l.logger.Info("retrying in %s", waitTime)
- timer := time.NewTimer(waitTime)
- select {
- case <-timer.C:
- case <-ctx.Done():
- if !timer.Stop() {
- <-timer.C
- }
- }
-}
-
-func NewLooper(conf Configurator, firewallConf firewall.Configurator, settings settings.TinyProxy,
- logger logging.Logger, streamMerger command.StreamMerger, uid, gid int, defaultInterface string) Looper {
- return &looper{
- conf: conf,
- firewallConf: firewallConf,
- settings: settings,
- logger: logger.WithPrefix("tinyproxy: "),
- streamMerger: streamMerger,
- uid: uid,
- gid: gid,
- defaultInterface: defaultInterface,
- restart: make(chan struct{}),
- start: make(chan struct{}),
- stop: make(chan struct{}),
- }
-}
-
-func (l *looper) GetSettings() (settings settings.TinyProxy) {
- l.settingsMutex.RLock()
- defer l.settingsMutex.RUnlock()
- return l.settings
-}
-
-func (l *looper) SetSettings(settings settings.TinyProxy) {
- l.settingsMutex.Lock()
- defer l.settingsMutex.Unlock()
- l.settings = settings
-}
-
-func (l *looper) isEnabled() bool {
- l.settingsMutex.RLock()
- defer l.settingsMutex.RUnlock()
- return l.settings.Enabled
-}
-
-func (l *looper) setEnabled(enabled bool) {
- l.settingsMutex.Lock()
- defer l.settingsMutex.Unlock()
- l.settings.Enabled = enabled
-}
-
-func (l *looper) Restart() { l.restart <- struct{}{} }
-func (l *looper) Start() { l.start <- struct{}{} }
-func (l *looper) Stop() { l.stop <- struct{}{} }
-
-func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
- defer wg.Done()
- waitForStart := true
- for waitForStart {
- select {
- case <-l.stop:
- l.logger.Info("not started yet")
- case <-l.start:
- waitForStart = false
- case <-l.restart:
- waitForStart = false
- case <-ctx.Done():
- return
- }
- }
- defer l.logger.Warn("loop exited")
-
- var previousPort uint16
- for ctx.Err() == nil {
- for !l.isEnabled() {
- // wait for a signal to re-enable
- select {
- case <-l.stop:
- l.logger.Info("already disabled")
- case <-l.restart:
- l.setEnabled(true)
- case <-l.start:
- l.setEnabled(true)
- case <-ctx.Done():
- return
- }
- }
-
- settings := l.GetSettings()
- err := l.conf.MakeConf(settings.LogLevel, settings.Port, settings.User, settings.Password, l.uid, l.gid)
- if err != nil {
- l.logAndWait(ctx, err)
- continue
- }
-
- if previousPort > 0 {
- if err := l.firewallConf.RemoveAllowedPort(ctx, previousPort); err != nil {
- l.logger.Error(err)
- continue
- }
- }
- if err := l.firewallConf.SetAllowedPort(ctx, settings.Port, l.defaultInterface); err != nil {
- l.logger.Error(err)
- continue
- }
- previousPort = settings.Port
-
- tinyproxyCtx, tinyproxyCancel := context.WithCancel(context.Background())
- stream, waitFn, err := l.conf.Start(tinyproxyCtx)
- if err != nil {
- tinyproxyCancel()
- l.logAndWait(ctx, err)
- continue
- }
- go l.streamMerger.Merge(tinyproxyCtx, stream, command.MergeName("tinyproxy"))
- waitError := make(chan error)
- go func() {
- err := waitFn() // blocking
- waitError <- err
- }()
- stayHere := true
- for stayHere {
- select {
- case <-ctx.Done():
- l.logger.Warn("context canceled: exiting loop")
- tinyproxyCancel()
- <-waitError
- close(waitError)
- return
- case <-l.restart: // triggered restart
- l.logger.Info("restarting")
- tinyproxyCancel()
- <-waitError
- close(waitError)
- stayHere = false
- case <-l.start:
- l.logger.Info("already started")
- case <-l.stop:
- l.logger.Info("stopping")
- tinyproxyCancel()
- <-waitError
- close(waitError)
- l.setEnabled(false)
- stayHere = false
- case err := <-waitError: // unexpected error
- tinyproxyCancel()
- close(waitError)
- l.logAndWait(ctx, err)
- }
- }
- tinyproxyCancel() // repetition for linter only
- }
-}
diff --git a/internal/tinyproxy/tinyproxy.go b/internal/tinyproxy/tinyproxy.go
deleted file mode 100644
index c5f1c1a6..00000000
--- a/internal/tinyproxy/tinyproxy.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package tinyproxy
-
-import (
- "context"
- "io"
-
- "github.com/qdm12/gluetun/internal/models"
- "github.com/qdm12/golibs/command"
- "github.com/qdm12/golibs/files"
- "github.com/qdm12/golibs/logging"
-)
-
-type Configurator interface {
- Version(ctx context.Context) (string, error)
- MakeConf(logLevel models.TinyProxyLogLevel, port uint16, user, password string, uid, gid int) error
- Start(ctx context.Context) (stdout io.ReadCloser, waitFn func() error, err error)
-}
-
-type configurator struct {
- fileManager files.FileManager
- logger logging.Logger
- commander command.Commander
-}
-
-func NewConfigurator(fileManager files.FileManager, logger logging.Logger) Configurator {
- return &configurator{
- fileManager: fileManager,
- logger: logger.WithPrefix("tinyproxy configurator: "),
- commander: command.NewCommander()}
-}
Connect LAN devices through the built-in HTTP proxy (i.e. with Chrome, Kodi, etc.)
Connect LAN devices through the built-in *Shadowsocks* proxy (per app, system wide, etc.)