Skip to content

Commit

Permalink
HTTP proxy written in Go to replace Tinyproxy (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
qdm12 committed Nov 1, 2020
1 parent 58da55d commit 0c9f74f
Show file tree
Hide file tree
Showing 29 changed files with 623 additions and 680 deletions.
17 changes: 8 additions & 9 deletions Dockerfile
Expand Up @@ -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 \
Expand All @@ -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
Expand Down
29 changes: 15 additions & 14 deletions 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*

Expand Down Expand Up @@ -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 🎆
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

</p></details>
- <details><summary>Connect LAN devices through the built-in HTTP proxy *Tinyproxy* (i.e. with Chrome, Kodi, etc.)</summary><p>
- <details><summary>Connect LAN devices through the built-in HTTP proxy (i.e. with Chrome, Kodi, etc.)</summary><p>

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

</p></details>
- <details><summary>Connect LAN devices through the built-in *Shadowsocks* proxy (per app, system wide, etc.)</summary><p>
Expand Down
28 changes: 10 additions & 18 deletions cmd/gluetun/main.go
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Expand Up @@ -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=
Expand Down
4 changes: 0 additions & 4 deletions internal/constants/colors.go
Expand Up @@ -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)
}
2 changes: 0 additions & 2 deletions internal/constants/paths.go
Expand Up @@ -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.
Expand Down
20 changes: 0 additions & 20 deletions internal/constants/tinyproxy.go

This file was deleted.

33 changes: 33 additions & 0 deletions 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
}
62 changes: 62 additions & 0 deletions 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",
}

0 comments on commit 0c9f74f

Please sign in to comment.