Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b04677f8f | ||
|
|
b5fb2b849a | ||
|
|
0c9f74ffa4 |
17
Dockerfile
17
Dockerfile
@@ -93,12 +93,12 @@ ENV VPNSP=pia \
|
|||||||
FIREWALL_INPUT_PORTS= \
|
FIREWALL_INPUT_PORTS= \
|
||||||
FIREWALL_OUTBOUND_SUBNETS= \
|
FIREWALL_OUTBOUND_SUBNETS= \
|
||||||
FIREWALL_DEBUG=off \
|
FIREWALL_DEBUG=off \
|
||||||
# Tinyproxy
|
# HTTP proxy
|
||||||
TINYPROXY=off \
|
HTTPPROXY= \
|
||||||
TINYPROXY_LOG=Info \
|
HTTPPROXY_LOG=off \
|
||||||
TINYPROXY_PORT=8888 \
|
HTTPPROXY_PORT=8888 \
|
||||||
TINYPROXY_USER= \
|
HTTPPROXY_USER= \
|
||||||
TINYPROXY_PASSWORD= \
|
HTTPPROXY_PASSWORD= \
|
||||||
# Shadowsocks
|
# Shadowsocks
|
||||||
SHADOWSOCKS=off \
|
SHADOWSOCKS=off \
|
||||||
SHADOWSOCKS_LOG=off \
|
SHADOWSOCKS_LOG=off \
|
||||||
@@ -109,10 +109,9 @@ ENV VPNSP=pia \
|
|||||||
ENTRYPOINT ["/entrypoint"]
|
ENTRYPOINT ["/entrypoint"]
|
||||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||||
HEALTHCHECK --interval=10m --timeout=10s --start-period=30s --retries=2 CMD /entrypoint healthcheck
|
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 && \
|
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-* /etc/tinyproxy/tinyproxy.conf && \
|
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* && \
|
||||||
deluser openvpn && \
|
deluser openvpn && \
|
||||||
deluser tinyproxy && \
|
|
||||||
deluser unbound && \
|
deluser unbound && \
|
||||||
mkdir /gluetun
|
mkdir /gluetun
|
||||||
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
|
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
|
||||||
|
|||||||
29
README.md
29
README.md
@@ -1,8 +1,7 @@
|
|||||||
# Gluetun VPN client
|
# Gluetun VPN client
|
||||||
|
|
||||||
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
|
*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,
|
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN and PureVPN VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||||
iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
|
||||||
|
|
||||||
**ANNOUNCEMENT**: *Github Wiki reworked*
|
**ANNOUNCEMENT**: *Github Wiki reworked*
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
|||||||
- Choose the vpn network protocol, `udp` or `tcp`
|
- 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 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 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 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)
|
- [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 🎆
|
- 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_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 |
|
| `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 |
|
| Variable | Default | Choices | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `TINYPROXY` | `off` | `on`, `off` | Enable the internal HTTP proxy tinyproxy |
|
| `HTTPPROXY` | `off` | `on`, `off` | Enable the internal HTTP proxy |
|
||||||
| `TINYPROXY_LOG` | `Info` | `Info`, `Connect`, `Notice`, `Warning`, `Error`, `Critical` | Tinyproxy log level |
|
| `HTTPPROXY_LOG` | `off` | `on` or `off` | Logs every tunnel requests |
|
||||||
| `TINYPROXY_PORT` | `8888` | `1024` to `65535` | Internal port number for Tinyproxy to listen on |
|
| `HTTPPROXY_PORT` | `8888` | `1024` to `65535` | Internal port number for the HTTP proxy to listen on |
|
||||||
| `TINYPROXY_USER` | | | Username to use to connect to Tinyproxy |
|
| `HTTPPROXY_USER` | | | Username to use to connect to the HTTP proxy |
|
||||||
| `TINYPROXY_PASSWORD` | | | Password to use to connect to Tinyproxy |
|
| `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
|
### 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
|
Add `network_mode: "container:gluetun"` to your *docker-compose.yml*, provided Gluetun is already running
|
||||||
|
|
||||||
</p></details>
|
</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:
|
1. Ensure the Gluetun container is launched with:
|
||||||
- port `8888` published `-p 8888:8888/tcp`
|
- 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. 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 `TINYPROXY_LOG` to `Info`, more information will be logged in the Docker logs
|
1. If you set `HTTPPROXY_LOG` to `on`, more information will be logged in the Docker logs
|
||||||
|
|
||||||
</p></details>
|
</p></details>
|
||||||
- <details><summary>Connect LAN devices through the built-in *Shadowsocks* proxy (per app, system wide, etc.)</summary><p>
|
- <details><summary>Connect LAN devices through the built-in *Shadowsocks* proxy (per app, system wide, etc.)</summary><p>
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/dns"
|
"github.com/qdm12/gluetun/internal/dns"
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||||
|
"github.com/qdm12/gluetun/internal/httpproxy"
|
||||||
gluetunLogging "github.com/qdm12/gluetun/internal/logging"
|
gluetunLogging "github.com/qdm12/gluetun/internal/logging"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn"
|
"github.com/qdm12/gluetun/internal/openvpn"
|
||||||
"github.com/qdm12/gluetun/internal/params"
|
"github.com/qdm12/gluetun/internal/params"
|
||||||
"github.com/qdm12/gluetun/internal/publicip"
|
"github.com/qdm12/gluetun/internal/publicip"
|
||||||
@@ -27,7 +29,6 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/settings"
|
"github.com/qdm12/gluetun/internal/settings"
|
||||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/tinyproxy"
|
|
||||||
"github.com/qdm12/gluetun/internal/updater"
|
"github.com/qdm12/gluetun/internal/updater"
|
||||||
versionpkg "github.com/qdm12/gluetun/internal/version"
|
versionpkg "github.com/qdm12/gluetun/internal/version"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
@@ -37,11 +38,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gochecknoglobals
|
//nolint:gochecknoglobals
|
||||||
var (
|
var buildInfo = models.BuildInformation{
|
||||||
version = "unknown"
|
Version: "unknown",
|
||||||
commit = "unknown"
|
Commit: "unknown",
|
||||||
buildDate = "an unknown date"
|
BuildDate: "an unknown date",
|
||||||
)
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -83,17 +84,15 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
dnsConf := dns.NewConfigurator(logger, client, fileManager)
|
dnsConf := dns.NewConfigurator(logger, client, fileManager)
|
||||||
routingConf := routing.NewRouting(logger)
|
routingConf := routing.NewRouting(logger)
|
||||||
firewallConf := firewall.NewConfigurator(logger, routingConf, fileManager)
|
firewallConf := firewall.NewConfigurator(logger, routingConf, fileManager)
|
||||||
tinyProxyConf := tinyproxy.NewConfigurator(fileManager, logger)
|
|
||||||
streamMerger := command.NewStreamMerger()
|
streamMerger := command.NewStreamMerger()
|
||||||
|
|
||||||
paramsReader := params.NewReader(logger, fileManager)
|
paramsReader := params.NewReader(logger, fileManager)
|
||||||
fmt.Println(gluetunLogging.Splash(version, commit, buildDate))
|
fmt.Println(gluetunLogging.Splash(buildInfo))
|
||||||
|
|
||||||
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
|
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
|
||||||
"OpenVPN": ovpnConf.Version,
|
"OpenVPN": ovpnConf.Version,
|
||||||
"Unbound": dnsConf.Version,
|
"Unbound": dnsConf.Version,
|
||||||
"IPtables": firewallConf.Version,
|
"IPtables": firewallConf.Version,
|
||||||
"TinyProxy": tinyProxyConf.Version,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
allSettings, err := settings.GetAllSettings(paramsReader)
|
allSettings, err := settings.GetAllSettings(paramsReader)
|
||||||
@@ -125,11 +124,6 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
err = fileManager.SetOwnership("/etc/tinyproxy", uid, gid)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if allSettings.Firewall.Debug {
|
if allSettings.Firewall.Debug {
|
||||||
firewallConf.SetDebug()
|
firewallConf.SetDebug()
|
||||||
@@ -161,6 +155,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
|
routingConf.SetVerbose(false)
|
||||||
if err := routingConf.TearDown(); err != nil {
|
if err := routingConf.TearDown(); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
@@ -244,19 +239,17 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
go publicIPLooper.RunRestartTicker(ctx, wg)
|
go publicIPLooper.RunRestartTicker(ctx, wg)
|
||||||
publicIPLooper.SetPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
|
publicIPLooper.SetPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
|
||||||
|
|
||||||
tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf,
|
httpProxyLooper := httpproxy.NewLooper(httpClient, logger, allSettings.HTTPProxy)
|
||||||
allSettings.TinyProxy, logger, streamMerger, uid, gid, defaultInterface)
|
|
||||||
restartTinyproxy := tinyproxyLooper.Restart
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go tinyproxyLooper.Run(ctx, wg)
|
go httpProxyLooper.Run(ctx, wg)
|
||||||
|
|
||||||
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger, defaultInterface)
|
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger, defaultInterface)
|
||||||
restartShadowsocks := shadowsocksLooper.Restart
|
restartShadowsocks := shadowsocksLooper.Restart
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go shadowsocksLooper.Run(ctx, wg)
|
go shadowsocksLooper.Run(ctx, wg)
|
||||||
|
|
||||||
if allSettings.TinyProxy.Enabled {
|
if allSettings.HTTPProxy.Enabled {
|
||||||
restartTinyproxy()
|
httpProxyLooper.Restart()
|
||||||
}
|
}
|
||||||
if allSettings.ShadowSocks.Enabled {
|
if allSettings.ShadowSocks.Enabled {
|
||||||
restartShadowsocks()
|
restartShadowsocks()
|
||||||
@@ -270,7 +263,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
controlServerAddress := fmt.Sprintf("0.0.0.0:%d", allSettings.ControlServer.Port)
|
controlServerAddress := fmt.Sprintf("0.0.0.0:%d", allSettings.ControlServer.Port)
|
||||||
controlServerLogging := allSettings.ControlServer.Log
|
controlServerLogging := allSettings.ControlServer.Log
|
||||||
httpServer := server.New(controlServerAddress, controlServerLogging,
|
httpServer := server.New(controlServerAddress, controlServerLogging,
|
||||||
logger, openvpnLooper, unboundLooper, updaterLooper)
|
logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go httpServer.Run(ctx, wg)
|
go httpServer.Run(ctx, wg)
|
||||||
|
|
||||||
@@ -356,7 +349,7 @@ func printVersions(ctx context.Context, logger logging.Logger,
|
|||||||
//nolint:lll
|
//nolint:lll
|
||||||
func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
||||||
logger logging.Logger, signalTunnelReady func()) {
|
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")
|
logger.Info("Launching standard output merger")
|
||||||
streamMerger.CollectLines(ctx, func(line string) {
|
streamMerger.CollectLines(ctx, func(line string) {
|
||||||
line, level := gluetunLogging.PostProcessLine(line)
|
line, level := gluetunLogging.PostProcessLine(line)
|
||||||
@@ -442,7 +435,7 @@ func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dn
|
|||||||
if !versionInformation {
|
if !versionInformation {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
message, err := versionpkg.GetMessage(ctx, version, commit, httpClient)
|
message, err := versionpkg.GetMessage(ctx, buildInfo, httpClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
network_mode: bridge
|
network_mode: bridge
|
||||||
ports:
|
ports:
|
||||||
- 8888:8888/tcp # Tinyproxy
|
- 8888:8888/tcp # HTTP proxy
|
||||||
- 8388:8388/tcp # Shadowsocks
|
- 8388:8388/tcp # Shadowsocks
|
||||||
- 8388:8388/udp # Shadowsocks
|
- 8388:8388/udp # Shadowsocks
|
||||||
- 8000:8000/tcp # Built-in HTTP control server
|
- 8000:8000/tcp # Built-in HTTP control server
|
||||||
|
|||||||
2
go.sum
2
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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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 h1:v0zUA1FWeVkTEd9KyxfehbRVJeFGOqyMY6FHO/Q9ITU=
|
||||||
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
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=
|
github.com/qdm12/ss-server v0.0.0-20200819124651-6428e626ee83 h1:b7sNsgsKxH0mbl9L1hdUp5KSDkZ/1kOQ+iHiBVgFElM=
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ func ColorUnbound() *color.Color {
|
|||||||
return color.New(color.FgCyan)
|
return color.New(color.FgCyan)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ColorTinyproxy() *color.Color {
|
|
||||||
return color.New(color.FgHiGreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ColorOpenvpn() *color.Color {
|
func ColorOpenvpn() *color.Color {
|
||||||
return color.New(color.FgHiMagenta)
|
return color.New(color.FgHiMagenta)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ const (
|
|||||||
TunnelDevice models.Filepath = "/dev/net/tun"
|
TunnelDevice models.Filepath = "/dev/net/tun"
|
||||||
// NetRoute is the path to the file containing information on the network route.
|
// NetRoute is the path to the file containing information on the network route.
|
||||||
NetRoute models.Filepath = "/proc/net/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 is the filepath to the root.hints file used by Unbound.
|
||||||
RootHints models.Filepath = "/etc/unbound/root.hints"
|
RootHints models.Filepath = "/etc/unbound/root.hints"
|
||||||
// RootKey is the filepath to the root.key file used by Unbound.
|
// RootKey is the filepath to the root.key file used by Unbound.
|
||||||
|
|||||||
@@ -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"
|
|
||||||
)
|
|
||||||
@@ -66,7 +66,7 @@ func generateUnboundConf(ctx context.Context, settings settings.DNS,
|
|||||||
// Network
|
// Network
|
||||||
"do-ip4": "yes",
|
"do-ip4": "yes",
|
||||||
"do-ip6": doIPv6,
|
"do-ip6": doIPv6,
|
||||||
"interface": "127.0.0.1",
|
"interface": "0.0.0.0",
|
||||||
"port": "53",
|
"port": "53",
|
||||||
// Other
|
// Other
|
||||||
"username": "\"nonrootuser\"",
|
"username": "\"nonrootuser\"",
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ server:
|
|||||||
harden-referral-path: yes
|
harden-referral-path: yes
|
||||||
hide-identity: yes
|
hide-identity: yes
|
||||||
hide-version: yes
|
hide-version: yes
|
||||||
interface: 127.0.0.1
|
interface: 0.0.0.0
|
||||||
key-cache-size: 16m
|
key-cache-size: 16m
|
||||||
key-cache-slabs: 4
|
key-cache-slabs: 4
|
||||||
msg-cache-size: 4m
|
msg-cache-size: 4m
|
||||||
|
|||||||
33
internal/httpproxy/auth.go
Normal file
33
internal/httpproxy/auth.go
Normal file
@@ -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
internal/httpproxy/handler.go
Normal file
62
internal/httpproxy/handler.go
Normal file
@@ -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",
|
||||||
|
}
|
||||||
74
internal/httpproxy/http.go
Normal file
74
internal/httpproxy/http.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
64
internal/httpproxy/https.go
Normal file
64
internal/httpproxy/https.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
139
internal/httpproxy/loop.go
Normal file
139
internal/httpproxy/loop.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
55
internal/httpproxy/server.go
Normal file
55
internal/httpproxy/server.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
@@ -12,13 +12,9 @@ import (
|
|||||||
|
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
var regularExpressions = struct { //nolint:gochecknoglobals
|
var regularExpressions = struct { //nolint:gochecknoglobals
|
||||||
unboundPrefix *regexp.Regexp
|
unboundPrefix *regexp.Regexp
|
||||||
tinyproxyLoglevel *regexp.Regexp
|
|
||||||
tinyproxyPrefix *regexp.Regexp
|
|
||||||
}{
|
}{
|
||||||
unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:0\] `),
|
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]+\]: `),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostProcessLine(s string) (filtered string, level logging.Level) {
|
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 = fmt.Sprintf("unbound: %s", filtered)
|
||||||
filtered = constants.ColorUnbound().Sprintf(filtered)
|
filtered = constants.ColorUnbound().Sprintf(filtered)
|
||||||
return filtered, level
|
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
|
return s, logging.InfoLevel
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,34 +36,6 @@ func Test_PostProcessLine(t *testing.T) {
|
|||||||
"unbound: [1594595249] unbound[75:0] BLA: init module 0: validator",
|
"unbound: [1594595249] unbound[75:0] BLA: init module 0: validator",
|
||||||
"unbound: BLA: init module 0: validator",
|
"unbound: BLA: init module 0: validator",
|
||||||
logging.ErrorLevel},
|
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 unknown": {
|
||||||
"openvpn: message",
|
"openvpn: message",
|
||||||
"openvpn: message",
|
"openvpn: message",
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ import (
|
|||||||
|
|
||||||
"github.com/kyokomi/emoji"
|
"github.com/kyokomi/emoji"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Splash returns the welcome spash message.
|
// Splash returns the welcome spash message.
|
||||||
func Splash(version, commit, buildDate string) string {
|
func Splash(buildInfo models.BuildInformation) string {
|
||||||
lines := title()
|
lines := title()
|
||||||
lines = append(lines, "")
|
lines = append(lines, "")
|
||||||
lines = append(lines, fmt.Sprintf("Running version %s built on %s (commit %s)", version, buildDate, commit))
|
lines = append(lines, fmt.Sprintf("Running version %s built on %s (commit %s)",
|
||||||
|
buildInfo.Version, buildInfo.BuildDate, buildInfo.Commit))
|
||||||
lines = append(lines, "")
|
lines = append(lines, "")
|
||||||
lines = append(lines, announcement()...)
|
lines = append(lines, announcement()...)
|
||||||
lines = append(lines, "")
|
lines = append(lines, "")
|
||||||
@@ -27,7 +29,7 @@ func title() []string {
|
|||||||
"================ Gluetun ================",
|
"================ Gluetun ================",
|
||||||
"=========================================",
|
"=========================================",
|
||||||
"==== A mix of OpenVPN, DNS over TLS, ====",
|
"==== A mix of OpenVPN, DNS over TLS, ====",
|
||||||
"======= Shadowsocks and Tinyproxy =======",
|
"======= Shadowsocks and HTTP proxy ======",
|
||||||
"========= all glued up with Go ==========",
|
"========= all glued up with Go ==========",
|
||||||
"=========================================",
|
"=========================================",
|
||||||
"=========== For tunneling to ============",
|
"=========== For tunneling to ============",
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ type (
|
|||||||
URL string
|
URL string
|
||||||
// Filepath is a local filesytem file path.
|
// Filepath is a local filesytem file path.
|
||||||
Filepath string
|
Filepath string
|
||||||
// TinyProxyLogLevel is the log level for TinyProxy.
|
|
||||||
TinyProxyLogLevel string
|
|
||||||
// VPNProvider is the name of the VPN provider to be used.
|
// VPNProvider is the name of the VPN provider to be used.
|
||||||
VPNProvider string
|
VPNProvider string
|
||||||
// NetworkProtocol contains the network protocol to be used to communicate with the VPN servers.
|
// NetworkProtocol contains the network protocol to be used to communicate with the VPN servers.
|
||||||
|
|||||||
7
internal/models/build.go
Normal file
7
internal/models/build.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type BuildInformation struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Commit string `json:"commit"`
|
||||||
|
BuildDate string `json:"buildDate"`
|
||||||
|
}
|
||||||
78
internal/params/httpproxy.go
Normal file
78
internal/params/httpproxy.go
Normal file
@@ -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"))
|
||||||
|
}
|
||||||
@@ -102,12 +102,13 @@ type Reader interface {
|
|||||||
GetShadowSocksPassword() (password string, err error)
|
GetShadowSocksPassword() (password string, err error)
|
||||||
GetShadowSocksMethod() (method string, err error)
|
GetShadowSocksMethod() (method string, err error)
|
||||||
|
|
||||||
// Tinyproxy getters
|
// HTTP proxy getters
|
||||||
GetTinyProxy() (activated bool, err error)
|
GetHTTPProxy() (activated bool, err error)
|
||||||
GetTinyProxyLog() (models.TinyProxyLogLevel, error)
|
GetHTTPProxyLog() (log bool, err error)
|
||||||
GetTinyProxyPort() (port uint16, err error)
|
GetHTTPProxyPort() (port uint16, err error)
|
||||||
GetTinyProxyUser() (user string, err error)
|
GetHTTPProxyUser() (user string, err error)
|
||||||
GetTinyProxyPassword() (password string, err error)
|
GetHTTPProxyPassword() (password string, err error)
|
||||||
|
GetHTTPProxyStealth() (stealth bool, err error)
|
||||||
|
|
||||||
// Public IP getters
|
// Public IP getters
|
||||||
GetPublicIPPeriod() (period time.Duration, err error)
|
GetPublicIPPeriod() (period time.Duration, err error)
|
||||||
|
|||||||
@@ -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())
|
|
||||||
}
|
|
||||||
@@ -128,7 +128,7 @@ func (r *routing) VPNDestinationIP() (ip net.IP, err error) {
|
|||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
if route.LinkIndex == defaultLinkIndex &&
|
if route.LinkIndex == defaultLinkIndex &&
|
||||||
route.Dst != nil &&
|
route.Dst != nil &&
|
||||||
!ipIsPrivate(route.Dst.IP) &&
|
!IPIsPrivate(route.Dst.IP) &&
|
||||||
bytes.Equal(route.Dst.Mask, net.IPMask{255, 255, 255, 255}) {
|
bytes.Equal(route.Dst.Mask, net.IPMask{255, 255, 255, 255}) {
|
||||||
return route.Dst.IP, nil
|
return route.Dst.IP, nil
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ func (r *routing) VPNLocalGatewayIP() (ip net.IP, err error) {
|
|||||||
return nil, fmt.Errorf("cannot find VPN local gateway IP address from ip routes")
|
return nil, fmt.Errorf("cannot find VPN local gateway IP address from ip routes")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ipIsPrivate(ip net.IP) bool {
|
func IPIsPrivate(ip net.IP) bool {
|
||||||
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/dns"
|
"github.com/qdm12/gluetun/internal/dns"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/openvpn"
|
"github.com/qdm12/gluetun/internal/openvpn"
|
||||||
"github.com/qdm12/gluetun/internal/updater"
|
"github.com/qdm12/gluetun/internal/updater"
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
@@ -22,22 +22,22 @@ type server struct {
|
|||||||
address string
|
address string
|
||||||
logging bool
|
logging bool
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
|
buildInfo models.BuildInformation
|
||||||
openvpnLooper openvpn.Looper
|
openvpnLooper openvpn.Looper
|
||||||
unboundLooper dns.Looper
|
unboundLooper dns.Looper
|
||||||
updaterLooper updater.Looper
|
updaterLooper updater.Looper
|
||||||
lookupIP func(host string) ([]net.IP, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(address string, logging bool, logger logging.Logger,
|
func New(address string, logging bool, logger logging.Logger, buildInfo models.BuildInformation,
|
||||||
openvpnLooper openvpn.Looper, unboundLooper dns.Looper, updaterLooper updater.Looper) Server {
|
openvpnLooper openvpn.Looper, unboundLooper dns.Looper, updaterLooper updater.Looper) Server {
|
||||||
return &server{
|
return &server{
|
||||||
address: address,
|
address: address,
|
||||||
logging: logging,
|
logging: logging,
|
||||||
logger: logger.WithPrefix("http server: "),
|
logger: logger.WithPrefix("http server: "),
|
||||||
|
buildInfo: buildInfo,
|
||||||
openvpnLooper: openvpnLooper,
|
openvpnLooper: openvpnLooper,
|
||||||
unboundLooper: unboundLooper,
|
unboundLooper: unboundLooper,
|
||||||
updaterLooper: updaterLooper,
|
updaterLooper: updaterLooper,
|
||||||
lookupIP: net.LookupIP,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +68,9 @@ func (s *server) makeHandler() http.HandlerFunc {
|
|||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
switch r.RequestURI {
|
switch r.RequestURI {
|
||||||
|
case "/version":
|
||||||
|
s.handleGetVersion(w)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
case "/openvpn/actions/restart":
|
case "/openvpn/actions/restart":
|
||||||
s.openvpnLooper.Restart()
|
s.openvpnLooper.Restart()
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|||||||
19
internal/server/version.go
Normal file
19
internal/server/version.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *server) handleGetVersion(w http.ResponseWriter) {
|
||||||
|
data, err := json.Marshal(s.buildInfo)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := w.Write(data); err != nil {
|
||||||
|
s.logger.Warn(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
71
internal/settings/httpproxy.go
Normal file
71
internal/settings/httpproxy.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ type Settings struct {
|
|||||||
System System
|
System System
|
||||||
DNS DNS
|
DNS DNS
|
||||||
Firewall Firewall
|
Firewall Firewall
|
||||||
TinyProxy TinyProxy
|
HTTPProxy HTTPProxy
|
||||||
ShadowSocks ShadowSocks
|
ShadowSocks ShadowSocks
|
||||||
PublicIPPeriod time.Duration
|
PublicIPPeriod time.Duration
|
||||||
UpdaterPeriod time.Duration
|
UpdaterPeriod time.Duration
|
||||||
@@ -44,7 +44,7 @@ func (s *Settings) String() string {
|
|||||||
s.System.String(),
|
s.System.String(),
|
||||||
s.DNS.String(),
|
s.DNS.String(),
|
||||||
s.Firewall.String(),
|
s.Firewall.String(),
|
||||||
s.TinyProxy.String(),
|
s.HTTPProxy.String(),
|
||||||
s.ShadowSocks.String(),
|
s.ShadowSocks.String(),
|
||||||
s.ControlServer.String(),
|
s.ControlServer.String(),
|
||||||
"Public IP check period: " + s.PublicIPPeriod.String(), // TODO print disabled if 0
|
"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 {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
settings.TinyProxy, err = GetTinyProxySettings(paramsReader)
|
settings.HTTPProxy, err = GetHTTPProxySettings(paramsReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()}
|
|
||||||
}
|
|
||||||
@@ -7,31 +7,33 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/logging"
|
"github.com/qdm12/gluetun/internal/logging"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetMessage returns a message for the user describing if there is a newer version
|
// GetMessage returns a message for the user describing if there is a newer version
|
||||||
// available. It should only be called once the tunnel is established.
|
// available. It should only be called once the tunnel is established.
|
||||||
func GetMessage(ctx context.Context, version, commitShort string, client *http.Client) (message string, err error) {
|
func GetMessage(ctx context.Context, buildInfo models.BuildInformation,
|
||||||
if version == "latest" {
|
client *http.Client) (message string, err error) {
|
||||||
|
if buildInfo.Version == "latest" {
|
||||||
// Find # of commits between current commit and latest commit
|
// Find # of commits between current commit and latest commit
|
||||||
commitsSince, err := getCommitsSince(ctx, client, commitShort)
|
commitsSince, err := getCommitsSince(ctx, client, buildInfo.Commit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot get version information: %w", err)
|
return "", fmt.Errorf("cannot get version information: %w", err)
|
||||||
} else if commitsSince == 0 {
|
} else if commitsSince == 0 {
|
||||||
return fmt.Sprintf("You are running on the bleeding edge of %s!", version), nil
|
return fmt.Sprintf("You are running on the bleeding edge of %s!", buildInfo.Version), nil
|
||||||
}
|
}
|
||||||
commits := "commits"
|
commits := "commits"
|
||||||
if commitsSince == 1 {
|
if commitsSince == 1 {
|
||||||
commits = "commit"
|
commits = "commit"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("You are running %d %s behind the most recent %s", commitsSince, commits, version), nil
|
return fmt.Sprintf("You are running %d %s behind the most recent %s", commitsSince, commits, buildInfo.Version), nil
|
||||||
}
|
}
|
||||||
tagName, name, releaseTime, err := getLatestRelease(ctx, client)
|
tagName, name, releaseTime, err := getLatestRelease(ctx, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot get version information: %w", err)
|
return "", fmt.Errorf("cannot get version information: %w", err)
|
||||||
}
|
}
|
||||||
if tagName == version {
|
if tagName == buildInfo.Version {
|
||||||
return fmt.Sprintf("You are running the latest release %s", version), nil
|
return fmt.Sprintf("You are running the latest release %s", buildInfo.Version), nil
|
||||||
}
|
}
|
||||||
timeSinceRelease := logging.FormatDuration(time.Since(releaseTime))
|
timeSinceRelease := logging.FormatDuration(time.Since(releaseTime))
|
||||||
return fmt.Sprintf("There is a new release %s (%s) created %s ago",
|
return fmt.Sprintf("There is a new release %s (%s) created %s ago",
|
||||||
|
|||||||
Reference in New Issue
Block a user