Skip to content

Commit 0c9f74f

Browse files
authoredNov 1, 2020
HTTP proxy written in Go to replace Tinyproxy (#269)
·
v3.40.0v3.6.0
1 parent 58da55d commit 0c9f74f

29 files changed

+623
-680
lines changed
 

‎Dockerfile

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ ENV VPNSP=pia \
9393
FIREWALL_INPUT_PORTS= \
9494
FIREWALL_OUTBOUND_SUBNETS= \
9595
FIREWALL_DEBUG=off \
96-
# Tinyproxy
97-
TINYPROXY=off \
98-
TINYPROXY_LOG=Info \
99-
TINYPROXY_PORT=8888 \
100-
TINYPROXY_USER= \
101-
TINYPROXY_PASSWORD= \
96+
# HTTP proxy
97+
HTTPPROXY= \
98+
HTTPPROXY_LOG=off \
99+
HTTPPROXY_PORT=8888 \
100+
HTTPPROXY_USER= \
101+
HTTPPROXY_PASSWORD= \
102102
# Shadowsocks
103103
SHADOWSOCKS=off \
104104
SHADOWSOCKS_LOG=off \
@@ -109,10 +109,9 @@ ENV VPNSP=pia \
109109
ENTRYPOINT ["/entrypoint"]
110110
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
111111
HEALTHCHECK --interval=10m --timeout=10s --start-period=30s --retries=2 CMD /entrypoint healthcheck
112-
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tinyproxy tzdata && \
113-
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/tinyproxy/tinyproxy.conf && \
112+
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
113+
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* && \
114114
deluser openvpn && \
115-
deluser tinyproxy && \
116115
deluser unbound && \
117116
mkdir /gluetun
118117
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10

‎README.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Gluetun VPN client
22

33
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
4-
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN and PureVPN VPN servers, using Go, OpenVPN,
5-
iptables, DNS over TLS, ShadowSocks and Tinyproxy*
4+
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN and PureVPN VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
65

76
**ANNOUNCEMENT**: *Github Wiki reworked*
87

@@ -36,7 +35,7 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
3635
- Choose the vpn network protocol, `udp` or `tcp`
3736
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
3837
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
39-
- Built in HTTP proxy (Tinyproxy, tunnels TCP)
38+
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
4039
- [Connect other containers to it](https://github.com/qdm12/gluetun#connect-to-it)
4140
- [Connect LAN devices to it](https://github.com/qdm12/gluetun#connect-to-it)
4241
- 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.
243242
| `SHADOWSOCKS_PASSWORD` | | | Password to use to connect to Shadowsocks |
244243
| `SHADOWSOCKS_METHOD` | `chacha20-ietf-poly1305` | `chacha20-ietf-poly1305`, `aes-128-gcm`, `aes-256-gcm` | Method to use for Shadowsocks |
245244
246-
### Tinyproxy
245+
### HTTP proxy
247246
248247
| Variable | Default | Choices | Description |
249248
| --- | --- | --- | --- |
250-
| `TINYPROXY` | `off` | `on`, `off` | Enable the internal HTTP proxy tinyproxy |
251-
| `TINYPROXY_LOG` | `Info` | `Info`, `Connect`, `Notice`, `Warning`, `Error`, `Critical` | Tinyproxy log level |
252-
| `TINYPROXY_PORT` | `8888` | `1024` to `65535` | Internal port number for Tinyproxy to listen on |
253-
| `TINYPROXY_USER` | | | Username to use to connect to Tinyproxy |
254-
| `TINYPROXY_PASSWORD` | | | Password to use to connect to Tinyproxy |
249+
| `HTTPPROXY` | `off` | `on`, `off` | Enable the internal HTTP proxy |
250+
| `HTTPPROXY_LOG` | `off` | `on` or `off` | Logs every tunnel requests |
251+
| `HTTPPROXY_PORT` | `8888` | `1024` to `65535` | Internal port number for the HTTP proxy to listen on |
252+
| `HTTPPROXY_USER` | | | Username to use to connect to the HTTP proxy |
253+
| `HTTPPROXY_PASSWORD` | | | Password to use to connect to the HTTP proxy |
254+
| `HTTPPROXY_STEALTH` | `off` | `on` or `off` | Stealth mode means HTTP proxy headers are not added to your requests |
255255
256256
### System
257257
@@ -295,15 +295,16 @@ There are various ways to achieve this, depending on your use case.
295295
Add `network_mode: "container:gluetun"` to your *docker-compose.yml*, provided Gluetun is already running
296296
297297
</p></details>
298-
- <details><summary>Connect LAN devices through the built-in HTTP proxy *Tinyproxy* (i.e. with Chrome, Kodi, etc.)</summary><p>
298+
- <details><summary>Connect LAN devices through the built-in HTTP proxy (i.e. with Chrome, Kodi, etc.)</summary><p>
299299
300-
You might want to use Shadowsocks instead which tunnels UDP as well as TCP, whereas Tinyproxy only tunnels TCP.
300+
⚠️ You might want to use Shadowsocks instead which tunnels UDP as well as TCP and does not leak your credentials.
301+
The HTTP proxy will not encrypt your username and password every time you send a request to the HTTP proxy server.
301302
302-
1. Setup a HTTP proxy client, such as [SwitchyOmega for Chrome](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en)
303+
1. Setup an HTTP proxy client, such as [SwitchyOmega for Chrome](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en)
303304
1. Ensure the Gluetun container is launched with:
304305
- port `8888` published `-p 8888:8888/tcp`
305-
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`.
306-
1. If you set `TINYPROXY_LOG` to `Info`, more information will be logged in the Docker logs
306+
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.
307+
1. If you set `HTTPPROXY_LOG` to `on`, more information will be logged in the Docker logs
307308
308309
</p></details>
309310
- <details><summary>Connect LAN devices through the built-in *Shadowsocks* proxy (per app, system wide, etc.)</summary><p>

‎cmd/gluetun/main.go

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/qdm12/gluetun/internal/dns"
1919
"github.com/qdm12/gluetun/internal/firewall"
2020
"github.com/qdm12/gluetun/internal/healthcheck"
21+
"github.com/qdm12/gluetun/internal/httpproxy"
2122
gluetunLogging "github.com/qdm12/gluetun/internal/logging"
2223
"github.com/qdm12/gluetun/internal/openvpn"
2324
"github.com/qdm12/gluetun/internal/params"
@@ -27,7 +28,6 @@ import (
2728
"github.com/qdm12/gluetun/internal/settings"
2829
"github.com/qdm12/gluetun/internal/shadowsocks"
2930
"github.com/qdm12/gluetun/internal/storage"
30-
"github.com/qdm12/gluetun/internal/tinyproxy"
3131
"github.com/qdm12/gluetun/internal/updater"
3232
versionpkg "github.com/qdm12/gluetun/internal/version"
3333
"github.com/qdm12/golibs/command"
@@ -83,17 +83,15 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
8383
dnsConf := dns.NewConfigurator(logger, client, fileManager)
8484
routingConf := routing.NewRouting(logger)
8585
firewallConf := firewall.NewConfigurator(logger, routingConf, fileManager)
86-
tinyProxyConf := tinyproxy.NewConfigurator(fileManager, logger)
8786
streamMerger := command.NewStreamMerger()
8887

8988
paramsReader := params.NewReader(logger, fileManager)
9089
fmt.Println(gluetunLogging.Splash(version, commit, buildDate))
9190

9291
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
93-
"OpenVPN": ovpnConf.Version,
94-
"Unbound": dnsConf.Version,
95-
"IPtables": firewallConf.Version,
96-
"TinyProxy": tinyProxyConf.Version,
92+
"OpenVPN": ovpnConf.Version,
93+
"Unbound": dnsConf.Version,
94+
"IPtables": firewallConf.Version,
9795
})
9896

9997
allSettings, err := settings.GetAllSettings(paramsReader)
@@ -125,11 +123,6 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
125123
logger.Error(err)
126124
return 1
127125
}
128-
err = fileManager.SetOwnership("/etc/tinyproxy", uid, gid)
129-
if err != nil {
130-
logger.Error(err)
131-
return 1
132-
}
133126

134127
if allSettings.Firewall.Debug {
135128
firewallConf.SetDebug()
@@ -161,6 +154,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
161154
return 1
162155
}
163156
defer func() {
157+
routingConf.SetVerbose(false)
164158
if err := routingConf.TearDown(); err != nil {
165159
logger.Error(err)
166160
}
@@ -244,19 +238,17 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
244238
go publicIPLooper.RunRestartTicker(ctx, wg)
245239
publicIPLooper.SetPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
246240

247-
tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf,
248-
allSettings.TinyProxy, logger, streamMerger, uid, gid, defaultInterface)
249-
restartTinyproxy := tinyproxyLooper.Restart
241+
httpProxyLooper := httpproxy.NewLooper(httpClient, logger, allSettings.HTTPProxy)
250242
wg.Add(1)
251-
go tinyproxyLooper.Run(ctx, wg)
243+
go httpProxyLooper.Run(ctx, wg)
252244

253245
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger, defaultInterface)
254246
restartShadowsocks := shadowsocksLooper.Restart
255247
wg.Add(1)
256248
go shadowsocksLooper.Run(ctx, wg)
257249

258-
if allSettings.TinyProxy.Enabled {
259-
restartTinyproxy()
250+
if allSettings.HTTPProxy.Enabled {
251+
httpProxyLooper.Restart()
260252
}
261253
if allSettings.ShadowSocks.Enabled {
262254
restartShadowsocks()
@@ -356,7 +348,7 @@ func printVersions(ctx context.Context, logger logging.Logger,
356348
//nolint:lll
357349
func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
358350
logger logging.Logger, signalTunnelReady func()) {
359-
// Blocking line merging paramsReader for all programs: openvpn, tinyproxy, unbound and shadowsocks
351+
// Blocking line merging paramsReader for openvpn and unbound
360352
logger.Info("Launching standard output merger")
361353
streamMerger.CollectLines(ctx, func(line string) {
362354
line, level := gluetunLogging.PostProcessLine(line)

‎docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ services:
77
- NET_ADMIN
88
network_mode: bridge
99
ports:
10-
- 8888:8888/tcp # Tinyproxy
10+
- 8888:8888/tcp # HTTP proxy
1111
- 8388:8388/tcp # Shadowsocks
1212
- 8388:8388/udp # Shadowsocks
1313
- 8000:8000/tcp # Built-in HTTP control server

‎go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
7272
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
7373
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7474
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
75-
github.com/qdm12/golibs v0.0.0-20201024185935-092412448c2c h1:9EQyDXbeapnPeMeO8Yq7PE6zqYPGkHp/qijNBBTU74c=
76-
github.com/qdm12/golibs v0.0.0-20201024185935-092412448c2c/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
7775
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a h1:v0zUA1FWeVkTEd9KyxfehbRVJeFGOqyMY6FHO/Q9ITU=
7876
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
7977
github.com/qdm12/ss-server v0.0.0-20200819124651-6428e626ee83 h1:b7sNsgsKxH0mbl9L1hdUp5KSDkZ/1kOQ+iHiBVgFElM=

‎internal/constants/colors.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ func ColorUnbound() *color.Color {
66
return color.New(color.FgCyan)
77
}
88

9-
func ColorTinyproxy() *color.Color {
10-
return color.New(color.FgHiGreen)
11-
}
12-
139
func ColorOpenvpn() *color.Color {
1410
return color.New(color.FgHiMagenta)
1511
}

‎internal/constants/paths.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ const (
2121
TunnelDevice models.Filepath = "/dev/net/tun"
2222
// NetRoute is the path to the file containing information on the network route.
2323
NetRoute models.Filepath = "/proc/net/route"
24-
// TinyProxyConf is the filepath to the tinyproxy configuration file.
25-
TinyProxyConf models.Filepath = "/etc/tinyproxy/tinyproxy.conf"
2624
// RootHints is the filepath to the root.hints file used by Unbound.
2725
RootHints models.Filepath = "/etc/unbound/root.hints"
2826
// RootKey is the filepath to the root.key file used by Unbound.

‎internal/constants/tinyproxy.go

Lines changed: 0 additions & 20 deletions
This file was deleted.

‎internal/httpproxy/auth.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package httpproxy
2+
3+
import (
4+
"encoding/base64"
5+
"net/http"
6+
"strings"
7+
)
8+
9+
func isAuthorized(responseWriter http.ResponseWriter, request *http.Request,
10+
username, password string) (authorized bool) {
11+
basicAuth := request.Header.Get("Proxy-Authorization")
12+
if len(basicAuth) == 0 {
13+
responseWriter.WriteHeader(http.StatusProxyAuthRequired)
14+
return false
15+
}
16+
b64UsernamePassword := strings.TrimPrefix(basicAuth, "Basic ")
17+
b, err := base64.StdEncoding.DecodeString(b64UsernamePassword)
18+
if err != nil {
19+
responseWriter.WriteHeader(http.StatusUnauthorized)
20+
return false
21+
}
22+
usernamePassword := strings.Split(string(b), ":")
23+
const expectedFields = 2
24+
if len(usernamePassword) != expectedFields {
25+
responseWriter.WriteHeader(http.StatusBadRequest)
26+
return false
27+
}
28+
if username != usernamePassword[0] && password != usernamePassword[1] {
29+
responseWriter.WriteHeader(http.StatusUnauthorized)
30+
return false
31+
}
32+
return true
33+
}

‎internal/httpproxy/handler.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package httpproxy
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"sync"
7+
"time"
8+
9+
"github.com/qdm12/golibs/logging"
10+
)
11+
12+
func newHandler(ctx context.Context, wg *sync.WaitGroup,
13+
client *http.Client, logger logging.Logger,
14+
stealth, verbose bool, username, password string) http.Handler {
15+
const relayTimeout = 10 * time.Second
16+
return &handler{
17+
ctx: ctx,
18+
wg: wg,
19+
client: client,
20+
logger: logger,
21+
relayTimeout: relayTimeout,
22+
verbose: verbose,
23+
stealth: stealth,
24+
username: username,
25+
password: password,
26+
}
27+
}
28+
29+
type handler struct {
30+
ctx context.Context
31+
wg *sync.WaitGroup
32+
client *http.Client
33+
logger logging.Logger
34+
relayTimeout time.Duration
35+
verbose, stealth bool
36+
username, password string
37+
}
38+
39+
func (h *handler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) {
40+
if len(h.username) > 0 && !isAuthorized(responseWriter, request, h.username, h.password) {
41+
h.logger.Info("%s unauthorized", request.RemoteAddr)
42+
return
43+
}
44+
switch request.Method {
45+
case http.MethodConnect:
46+
h.handleHTTPS(responseWriter, request)
47+
default:
48+
h.handleHTTP(responseWriter, request)
49+
}
50+
}
51+
52+
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
53+
var hopHeaders = [...]string{ //nolint:gochecknoglobals
54+
"Connection",
55+
"Keep-Alive",
56+
"Proxy-Authenticate",
57+
"Proxy-Authorization",
58+
"Te", // canonicalized version of "TE"
59+
"Trailers",
60+
"Transfer-Encoding",
61+
"Upgrade",
62+
}

‎internal/httpproxy/http.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package httpproxy
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net"
8+
"net/http"
9+
"strings"
10+
)
11+
12+
func (h *handler) handleHTTP(responseWriter http.ResponseWriter, request *http.Request) {
13+
switch request.URL.Scheme {
14+
case "http", "https":
15+
default:
16+
h.logger.Warn("Unsupported scheme %q", request.URL.Scheme)
17+
http.Error(responseWriter, "unsupported scheme", http.StatusBadRequest)
18+
return
19+
}
20+
21+
ctx, cancel := context.WithTimeout(h.ctx, h.relayTimeout)
22+
defer cancel()
23+
request = request.WithContext(ctx)
24+
25+
request.RequestURI = ""
26+
27+
for _, key := range hopHeaders {
28+
request.Header.Del(key)
29+
}
30+
31+
if !h.stealth {
32+
setForwardedHeaders(request)
33+
}
34+
35+
response, err := h.client.Do(request)
36+
if err != nil {
37+
http.Error(responseWriter, "server error", http.StatusInternalServerError)
38+
h.logger.Warn("cannot request %s for client %q: %s",
39+
request.URL, request.RemoteAddr, err)
40+
return
41+
}
42+
defer response.Body.Close()
43+
if h.verbose {
44+
h.logger.Info("%s %s %s %s", request.RemoteAddr, response.Status, request.Method, request.URL)
45+
}
46+
47+
for _, key := range hopHeaders {
48+
response.Header.Del(key)
49+
}
50+
51+
targetHeaderPtr := responseWriter.Header()
52+
for key, values := range response.Header {
53+
for _, value := range values {
54+
targetHeaderPtr.Add(key, value)
55+
}
56+
}
57+
58+
responseWriter.WriteHeader(response.StatusCode)
59+
if _, err := io.Copy(responseWriter, response.Body); err != nil {
60+
h.logger.Error("%s %s: body copy error: %s", request.RemoteAddr, request.URL, err)
61+
}
62+
}
63+
64+
func setForwardedHeaders(request *http.Request) {
65+
clientIP, _, err := net.SplitHostPort(request.RemoteAddr)
66+
if err != nil {
67+
return
68+
}
69+
// keep existing proxy headers
70+
if prior, ok := request.Header["X-Forwarded-For"]; ok {
71+
clientIP = fmt.Sprintf("%s,%s", strings.Join(prior, ", "), clientIP)
72+
}
73+
request.Header.Set("X-Forwarded-For", clientIP)
74+
}

‎internal/httpproxy/https.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package httpproxy
2+
3+
import (
4+
"context"
5+
"io"
6+
"net"
7+
"net/http"
8+
"sync"
9+
)
10+
11+
func (h *handler) handleHTTPS(responseWriter http.ResponseWriter, request *http.Request) {
12+
dialer := net.Dialer{Timeout: h.relayTimeout}
13+
destinationConn, err := dialer.DialContext(h.ctx, "tcp", request.Host)
14+
if err != nil {
15+
http.Error(responseWriter, err.Error(), http.StatusServiceUnavailable)
16+
return
17+
}
18+
19+
responseWriter.WriteHeader(http.StatusOK)
20+
21+
hijacker, ok := responseWriter.(http.Hijacker)
22+
if !ok {
23+
http.Error(responseWriter, "Hijacking not supported", http.StatusInternalServerError)
24+
return
25+
}
26+
clientConnection, _, err := hijacker.Hijack()
27+
if err != nil {
28+
h.logger.Warn(err)
29+
http.Error(responseWriter, err.Error(), http.StatusServiceUnavailable)
30+
if err := destinationConn.Close(); err != nil {
31+
h.logger.Error("closing destination connection: %s", err)
32+
}
33+
return
34+
}
35+
36+
if h.verbose {
37+
h.logger.Info("%s <-> %s", request.RemoteAddr, request.Host)
38+
}
39+
40+
h.wg.Add(1)
41+
ctx, cancel := context.WithCancel(h.ctx)
42+
const transferGoroutines = 2
43+
wg := &sync.WaitGroup{}
44+
wg.Add(transferGoroutines)
45+
go func() { // trigger cleanup when done
46+
wg.Wait()
47+
cancel()
48+
}()
49+
go func() { // cleanup
50+
<-ctx.Done()
51+
destinationConn.Close()
52+
clientConnection.Close()
53+
h.wg.Done()
54+
}()
55+
go transfer(destinationConn, clientConnection, wg)
56+
go transfer(clientConnection, destinationConn, wg)
57+
}
58+
59+
func transfer(destination io.WriteCloser, source io.ReadCloser, wg *sync.WaitGroup) {
60+
_, _ = io.Copy(destination, source)
61+
_ = source.Close()
62+
_ = destination.Close()
63+
wg.Done()
64+
}

‎internal/httpproxy/loop.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package httpproxy
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"sync"
8+
9+
"github.com/qdm12/gluetun/internal/settings"
10+
"github.com/qdm12/golibs/logging"
11+
)
12+
13+
type Looper interface {
14+
Run(ctx context.Context, wg *sync.WaitGroup)
15+
Restart()
16+
Start()
17+
Stop()
18+
GetSettings() (settings settings.HTTPProxy)
19+
SetSettings(settings settings.HTTPProxy)
20+
}
21+
22+
type looper struct {
23+
client *http.Client
24+
settings settings.HTTPProxy
25+
settingsMutex sync.RWMutex
26+
logger logging.Logger
27+
restart chan struct{}
28+
start chan struct{}
29+
stop chan struct{}
30+
}
31+
32+
func NewLooper(client *http.Client, logger logging.Logger,
33+
settings settings.HTTPProxy) Looper {
34+
return &looper{
35+
client: client,
36+
settings: settings,
37+
logger: logger.WithPrefix("http proxy: "),
38+
restart: make(chan struct{}),
39+
start: make(chan struct{}),
40+
stop: make(chan struct{}),
41+
}
42+
}
43+
44+
func (l *looper) GetSettings() (settings settings.HTTPProxy) {
45+
l.settingsMutex.RLock()
46+
defer l.settingsMutex.RUnlock()
47+
return l.settings
48+
}
49+
50+
func (l *looper) SetSettings(settings settings.HTTPProxy) {
51+
l.settingsMutex.Lock()
52+
defer l.settingsMutex.Unlock()
53+
l.settings = settings
54+
}
55+
56+
func (l *looper) isEnabled() bool {
57+
l.settingsMutex.RLock()
58+
defer l.settingsMutex.RUnlock()
59+
return l.settings.Enabled
60+
}
61+
62+
func (l *looper) setEnabled(enabled bool) {
63+
l.settingsMutex.Lock()
64+
defer l.settingsMutex.Unlock()
65+
l.settings.Enabled = enabled
66+
}
67+
68+
func (l *looper) Restart() { l.restart <- struct{}{} }
69+
func (l *looper) Start() { l.start <- struct{}{} }
70+
func (l *looper) Stop() { l.stop <- struct{}{} }
71+
72+
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
73+
defer wg.Done()
74+
waitForStart := true
75+
for waitForStart {
76+
select {
77+
case <-l.stop:
78+
l.logger.Info("not started yet")
79+
case <-l.start:
80+
waitForStart = false
81+
case <-l.restart:
82+
waitForStart = false
83+
case <-ctx.Done():
84+
return
85+
}
86+
}
87+
defer l.logger.Warn("loop exited")
88+
89+
for ctx.Err() == nil {
90+
for !l.isEnabled() {
91+
// wait for a signal to re-enable
92+
select {
93+
case <-l.stop:
94+
l.logger.Info("already disabled")
95+
case <-l.restart:
96+
l.setEnabled(true)
97+
case <-l.start:
98+
l.setEnabled(true)
99+
case <-ctx.Done():
100+
return
101+
}
102+
}
103+
104+
settings := l.GetSettings()
105+
address := fmt.Sprintf("0.0.0.0:%d", settings.Port)
106+
107+
server := New(ctx, address, l.logger, l.client, settings.Stealth, settings.Log, settings.User, settings.Password)
108+
109+
runCtx, runCancel := context.WithCancel(context.Background())
110+
runWg := &sync.WaitGroup{}
111+
runWg.Add(1)
112+
go server.Run(runCtx, runWg)
113+
114+
stayHere := true
115+
for stayHere {
116+
select {
117+
case <-ctx.Done():
118+
l.logger.Warn("context canceled: exiting loop")
119+
runCancel()
120+
runWg.Wait()
121+
return
122+
case <-l.restart: // triggered restart
123+
l.logger.Info("restarting")
124+
runCancel()
125+
runWg.Wait()
126+
stayHere = false
127+
case <-l.start:
128+
l.logger.Info("already started")
129+
case <-l.stop:
130+
l.logger.Info("stopping")
131+
runCancel()
132+
runWg.Wait()
133+
l.setEnabled(false)
134+
stayHere = false
135+
}
136+
}
137+
runCancel() // repetition for linter only
138+
}
139+
}

‎internal/httpproxy/server.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package httpproxy
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"sync"
7+
"time"
8+
9+
"github.com/qdm12/golibs/logging"
10+
)
11+
12+
type Server interface {
13+
Run(ctx context.Context, wg *sync.WaitGroup)
14+
}
15+
16+
type server struct {
17+
address string
18+
handler http.Handler
19+
logger logging.Logger
20+
internalWG *sync.WaitGroup
21+
}
22+
23+
func New(ctx context.Context, address string,
24+
logger logging.Logger, client *http.Client,
25+
stealth, verbose bool, username, password string) Server {
26+
wg := &sync.WaitGroup{}
27+
return &server{
28+
address: address,
29+
handler: newHandler(ctx, wg, client, logger, stealth, verbose, username, password),
30+
logger: logger,
31+
internalWG: wg,
32+
}
33+
}
34+
35+
func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
36+
defer wg.Done()
37+
server := http.Server{Addr: s.address, Handler: s.handler}
38+
go func() {
39+
<-ctx.Done()
40+
s.logger.Warn("context canceled: exiting loop")
41+
defer s.logger.Warn("loop exited")
42+
const shutdownGraceDuration = 2 * time.Second
43+
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
44+
defer cancel()
45+
if err := server.Shutdown(shutdownCtx); err != nil {
46+
s.logger.Error("failed shutting down: %s", err)
47+
}
48+
}()
49+
s.logger.Info("listening on %s", s.address)
50+
err := server.ListenAndServe()
51+
if err != nil && ctx.Err() != context.Canceled {
52+
s.logger.Error(err)
53+
}
54+
s.internalWG.Wait()
55+
}

‎internal/logging/line.go

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,9 @@ import (
1212

1313
//nolint:lll
1414
var regularExpressions = struct { //nolint:gochecknoglobals
15-
unboundPrefix *regexp.Regexp
16-
tinyproxyLoglevel *regexp.Regexp
17-
tinyproxyPrefix *regexp.Regexp
15+
unboundPrefix *regexp.Regexp
1816
}{
19-
unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:0\] `),
20-
tinyproxyLoglevel: regexp.MustCompile(`INFO|CONNECT|NOTICE|WARNING|ERROR|CRITICAL`),
21-
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]+\]: `),
17+
unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:0\] `),
2218
}
2319

2420
func PostProcessLine(s string) (filtered string, level logging.Level) {
@@ -78,21 +74,6 @@ func PostProcessLine(s string) (filtered string, level logging.Level) {
7874
filtered = fmt.Sprintf("unbound: %s", filtered)
7975
filtered = constants.ColorUnbound().Sprintf(filtered)
8076
return filtered, level
81-
case strings.HasPrefix(s, "tinyproxy: "):
82-
logLevel := regularExpressions.tinyproxyLoglevel.FindString(s)
83-
prefix := regularExpressions.tinyproxyPrefix.FindString(s)
84-
filtered = fmt.Sprintf("tinyproxy: %s", s[len(prefix):])
85-
filtered = constants.ColorTinyproxy().Sprintf(filtered)
86-
switch logLevel {
87-
case "INFO", "CONNECT", "NOTICE":
88-
return filtered, logging.InfoLevel
89-
case "WARNING":
90-
return filtered, logging.WarnLevel
91-
case "ERROR", "CRITICAL":
92-
return filtered, logging.ErrorLevel
93-
default:
94-
return filtered, logging.ErrorLevel
95-
}
9677
}
9778
return s, logging.InfoLevel
9879
}

‎internal/logging/line_test.go

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,6 @@ func Test_PostProcessLine(t *testing.T) {
3636
"unbound: [1594595249] unbound[75:0] BLA: init module 0: validator",
3737
"unbound: BLA: init module 0: validator",
3838
logging.ErrorLevel},
39-
"tinyproxy info": {
40-
"tinyproxy: INFO Jul 12 23:07:25 [32]: Reloading config file",
41-
"tinyproxy: Reloading config file",
42-
logging.InfoLevel},
43-
"tinyproxy connect": {
44-
"tinyproxy: CONNECT Jul 12 23:07:25 [32]: Reloading config file",
45-
"tinyproxy: Reloading config file",
46-
logging.InfoLevel},
47-
"tinyproxy notice": {
48-
"tinyproxy: NOTICE Jul 12 23:07:25 [32]: Reloading config file",
49-
"tinyproxy: Reloading config file",
50-
logging.InfoLevel},
51-
"tinyproxy warning": {
52-
"tinyproxy: WARNING Jul 12 23:07:25 [32]: Reloading config file",
53-
"tinyproxy: Reloading config file",
54-
logging.WarnLevel},
55-
"tinyproxy error": {
56-
"tinyproxy: ERROR Jul 12 23:07:25 [32]: Reloading config file",
57-
"tinyproxy: Reloading config file",
58-
logging.ErrorLevel},
59-
"tinyproxy critical": {
60-
"tinyproxy: CRITICAL Jul 12 23:07:25 [32]: Reloading config file",
61-
"tinyproxy: Reloading config file",
62-
logging.ErrorLevel},
63-
"tinyproxy unknown": {
64-
"tinyproxy: BLABLA Jul 12 23:07:25 [32]: Reloading config file",
65-
"tinyproxy: Reloading config file",
66-
logging.ErrorLevel},
6739
"openvpn unknown": {
6840
"openvpn: message",
6941
"openvpn: message",

‎internal/logging/splash.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func title() []string {
2727
"================ Gluetun ================",
2828
"=========================================",
2929
"==== A mix of OpenVPN, DNS over TLS, ====",
30-
"======= Shadowsocks and Tinyproxy =======",
30+
"======= Shadowsocks and HTTP proxy ======",
3131
"========= all glued up with Go ==========",
3232
"=========================================",
3333
"=========== For tunneling to ============",

‎internal/models/alias.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ type (
1616
URL string
1717
// Filepath is a local filesytem file path.
1818
Filepath string
19-
// TinyProxyLogLevel is the log level for TinyProxy.
20-
TinyProxyLogLevel string
2119
// VPNProvider is the name of the VPN provider to be used.
2220
VPNProvider string
2321
// NetworkProtocol contains the network protocol to be used to communicate with the VPN servers.

‎internal/params/httpproxy.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package params
2+
3+
import (
4+
"strings"
5+
6+
libparams "github.com/qdm12/golibs/params"
7+
)
8+
9+
// GetHTTPProxy obtains if the HTTP proxy is on from the environment variable
10+
// HTTPPROXY, and using PROXY and TINYPROXY as retro-compatibility names.
11+
func (r *reader) GetHTTPProxy() (enabled bool, err error) {
12+
retroKeysOption := libparams.RetroKeys(
13+
[]string{"TINYPROXY", "PROXY"},
14+
r.onRetroActive,
15+
)
16+
return r.envParams.GetOnOff("HTTPPROXY", retroKeysOption, libparams.Default("off"))
17+
}
18+
19+
// GetHTTPProxyLog obtains the if http proxy requests should be logged from
20+
// the environment variable HTTPPROXY_LOG, and using PROXY_LOG_LEVEL and
21+
// TINYPROXY_LOG as retro-compatibility names.
22+
func (r *reader) GetHTTPProxyLog() (log bool, err error) {
23+
s, _ := r.envParams.GetEnv("HTTPPROXY_LOG")
24+
if len(s) == 0 {
25+
s, _ = r.envParams.GetEnv("PROXY_LOG_LEVEL")
26+
if len(s) == 0 {
27+
s, _ = r.envParams.GetEnv("TINYPROXY_LOG")
28+
if len(s) == 0 {
29+
return false, nil // default log disabled
30+
}
31+
}
32+
switch strings.ToLower(s) {
33+
case "info", "connect", "notice":
34+
return true, nil
35+
default:
36+
return false, nil
37+
}
38+
}
39+
return r.envParams.GetOnOff("HTTPPROXY_LOG", libparams.Default("off"))
40+
}
41+
42+
// GetHTTPProxyPort obtains the HTTP proxy listening port from the environment variable
43+
// HTTPPROXY_PORT, and using PROXY_PORT and TINYPROXY_PORT as retro-compatibility names.
44+
func (r *reader) GetHTTPProxyPort() (port uint16, err error) {
45+
retroKeysOption := libparams.RetroKeys(
46+
[]string{"TINYPROXY_PORT", "PROXY_PORT"},
47+
r.onRetroActive,
48+
)
49+
return r.envParams.GetPort("HTTPPROXY_PORT", retroKeysOption, libparams.Default("8888"))
50+
}
51+
52+
// GetHTTPProxyUser obtains the HTTP proxy server user from the environment variable
53+
// HTTPPROXY_USER, and using TINYPROXY_USER and PROXY_USER as retro-compatibility names.
54+
func (r *reader) GetHTTPProxyUser() (user string, err error) {
55+
retroKeysOption := libparams.RetroKeys(
56+
[]string{"TINYPROXY_USER", "PROXY_USER"},
57+
r.onRetroActive,
58+
)
59+
return r.envParams.GetEnv("HTTPPROXY_USER",
60+
retroKeysOption, libparams.CaseSensitiveValue(), libparams.Unset())
61+
}
62+
63+
// GetHTTPProxyPassword obtains the HTTP proxy server password from the environment variable
64+
// HTTPPROXY_PASSWORD, and using TINYPROXY_PASSWORD and PROXY_PASSWORD as retro-compatibility names.
65+
func (r *reader) GetHTTPProxyPassword() (password string, err error) {
66+
retroKeysOption := libparams.RetroKeys(
67+
[]string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"},
68+
r.onRetroActive,
69+
)
70+
return r.envParams.GetEnv("HTTPPROXY_PASSWORD",
71+
retroKeysOption, libparams.CaseSensitiveValue(), libparams.Unset())
72+
}
73+
74+
// GetHTTPProxyStealth obtains the HTTP proxy server stealth mode
75+
// from the environment variable HTTPPROXY_STEALTH.
76+
func (r *reader) GetHTTPProxyStealth() (stealth bool, err error) {
77+
return r.envParams.GetOnOff("HTTPPROXY_STEALTH", libparams.Default("off"))
78+
}

‎internal/params/params.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,13 @@ type Reader interface {
102102
GetShadowSocksPassword() (password string, err error)
103103
GetShadowSocksMethod() (method string, err error)
104104

105-
// Tinyproxy getters
106-
GetTinyProxy() (activated bool, err error)
107-
GetTinyProxyLog() (models.TinyProxyLogLevel, error)
108-
GetTinyProxyPort() (port uint16, err error)
109-
GetTinyProxyUser() (user string, err error)
110-
GetTinyProxyPassword() (password string, err error)
105+
// HTTP proxy getters
106+
GetHTTPProxy() (activated bool, err error)
107+
GetHTTPProxyLog() (log bool, err error)
108+
GetHTTPProxyPort() (port uint16, err error)
109+
GetHTTPProxyUser() (user string, err error)
110+
GetHTTPProxyPassword() (password string, err error)
111+
GetHTTPProxyStealth() (stealth bool, err error)
111112

112113
// Public IP getters
113114
GetPublicIPPeriod() (period time.Duration, err error)

‎internal/params/tinyproxy.go

Lines changed: 0 additions & 120 deletions
This file was deleted.

‎internal/settings/httpproxy.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package settings
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/qdm12/gluetun/internal/params"
8+
)
9+
10+
// HTTPProxy contains settings to configure the HTTP proxy.
11+
type HTTPProxy struct { //nolint:maligned
12+
Enabled bool
13+
Port uint16
14+
User string
15+
Password string
16+
Stealth bool
17+
Log bool
18+
}
19+
20+
func (h *HTTPProxy) String() string {
21+
if !h.Enabled {
22+
return "HTTP Proxy settings: disabled"
23+
}
24+
auth, log, stealth := disabled, disabled, disabled
25+
if h.User != "" {
26+
auth = enabled
27+
}
28+
if h.Log {
29+
log = enabled
30+
}
31+
if h.Stealth {
32+
stealth = enabled
33+
}
34+
settingsList := []string{
35+
"HTTP proxy settings:",
36+
fmt.Sprintf("Port: %d", h.Port),
37+
"Authentication: " + auth,
38+
"Stealth: " + stealth,
39+
"Log: " + log,
40+
}
41+
return strings.Join(settingsList, "\n |--")
42+
}
43+
44+
// GetHTTPProxySettings obtains HTTPProxy settings from environment variables using the params package.
45+
func GetHTTPProxySettings(paramsReader params.Reader) (settings HTTPProxy, err error) {
46+
settings.Enabled, err = paramsReader.GetHTTPProxy()
47+
if err != nil || !settings.Enabled {
48+
return settings, err
49+
}
50+
settings.Port, err = paramsReader.GetHTTPProxyPort()
51+
if err != nil {
52+
return settings, err
53+
}
54+
settings.User, err = paramsReader.GetHTTPProxyUser()
55+
if err != nil {
56+
return settings, err
57+
}
58+
settings.Password, err = paramsReader.GetHTTPProxyPassword()
59+
if err != nil {
60+
return settings, err
61+
}
62+
settings.Stealth, err = paramsReader.GetHTTPProxyStealth()
63+
if err != nil {
64+
return settings, err
65+
}
66+
settings.Log, err = paramsReader.GetHTTPProxyLog()
67+
if err != nil {
68+
return settings, err
69+
}
70+
return settings, nil
71+
}

‎internal/settings/settings.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type Settings struct {
2121
System System
2222
DNS DNS
2323
Firewall Firewall
24-
TinyProxy TinyProxy
24+
HTTPProxy HTTPProxy
2525
ShadowSocks ShadowSocks
2626
PublicIPPeriod time.Duration
2727
UpdaterPeriod time.Duration
@@ -44,7 +44,7 @@ func (s *Settings) String() string {
4444
s.System.String(),
4545
s.DNS.String(),
4646
s.Firewall.String(),
47-
s.TinyProxy.String(),
47+
s.HTTPProxy.String(),
4848
s.ShadowSocks.String(),
4949
s.ControlServer.String(),
5050
"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) {
7373
if err != nil {
7474
return settings, err
7575
}
76-
settings.TinyProxy, err = GetTinyProxySettings(paramsReader)
76+
settings.HTTPProxy, err = GetHTTPProxySettings(paramsReader)
7777
if err != nil {
7878
return settings, err
7979
}

‎internal/settings/tinyproxy.go

Lines changed: 0 additions & 59 deletions
This file was deleted.

‎internal/tinyproxy/command.go

Lines changed: 0 additions & 28 deletions
This file was deleted.

‎internal/tinyproxy/conf.go

Lines changed: 0 additions & 50 deletions
This file was deleted.

‎internal/tinyproxy/conf_test.go

Lines changed: 0 additions & 68 deletions
This file was deleted.

‎internal/tinyproxy/loop.go

Lines changed: 0 additions & 194 deletions
This file was deleted.

‎internal/tinyproxy/tinyproxy.go

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.