Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4a4e441c1 | ||
|
|
e8526141be | ||
|
|
9abb630692 | ||
|
|
9b92ece5a1 | ||
|
|
87a3e54044 | ||
|
|
76b730e2a6 | ||
|
|
51af8d1ab0 | ||
|
|
002ffacd35 | ||
|
|
404cee9371 | ||
|
|
f89e7aa8dc | ||
|
|
a0312ec916 | ||
|
|
83cf59b93e | ||
|
|
ad5de13c25 |
51
README.md
51
README.md
@@ -1,7 +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 and NordVPN VPN servers, using Go, OpenVPN,
|
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN and NordVPN VPN servers, using Go, OpenVPN,
|
||||||
iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
||||||
|
|
||||||
**ANNOUNCEMENT**: *[Video of the Git history of Gluetun](https://youtu.be/khipOYJtGJ0)*
|
**ANNOUNCEMENT**: *[Video of the Git history of Gluetun](https://youtu.be/khipOYJtGJ0)*
|
||||||
@@ -35,8 +35,7 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.12 for a small Docker image of 52MB
|
- Based on Alpine 3.12 for a small Docker image of 52MB
|
||||||
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**,
|
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn** and **NordVPN** servers
|
||||||
**Surfshark**, **Cyberghost** and **NordVPN** servers
|
|
||||||
- DNS over TLS baked in with service provider(s) of your choice
|
- DNS over TLS baked in with service provider(s) of your choice
|
||||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||||
- Choose the vpn network protocol, `udp` or `tcp`
|
- Choose the vpn network protocol, `udp` or `tcp`
|
||||||
@@ -47,33 +46,15 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
|||||||
- [Connect LAN devices to it](https://github.com/qdm12/private-internet-access-docker#connect-to-it)
|
- [Connect LAN devices to it](https://github.com/qdm12/private-internet-access-docker#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 🎆
|
||||||
|
|
||||||
### Private Internet Access
|
### VPN provider specifics
|
||||||
|
|
||||||
- Pick the [region](https://www.privateinternetaccess.com/pages/network/)
|
- **Private Internet Access**: pick the [region](https://www.privateinternetaccess.com/pages/network/), the level of encryption and enable port forwarding
|
||||||
- Pick the level of encryption
|
- **Mullvad**: Pick the [country, city and ISP](https://mullvad.net/en/servers/#openvpn) and optionally a custom port to use (i.e. `53` (udp) or `80` (tcp))
|
||||||
- Enable port forwarding
|
- **Windscribe**: Pick the [region](https://windscribe.com/status), and optionally a custom port to use
|
||||||
|
- **Surfshark**: Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Surfshark) or a multi hop region name
|
||||||
### Mullvad
|
- **Cyberghost**: Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Cyberghost) and server group.
|
||||||
|
- **VyprVPN**: Pick the [region](https://www.vyprvpn.com/server-locations), port forwarding works by default
|
||||||
- Pick the [country, city and ISP](https://mullvad.net/en/servers/#openvpn)
|
- **NordVPN**: Pick the region and optionally the server number
|
||||||
- Pick the port to use (i.e. `53` (udp) or `80` (tcp))
|
|
||||||
|
|
||||||
### Windscribe
|
|
||||||
|
|
||||||
- Pick the [region](https://windscribe.com/status)
|
|
||||||
- Pick the port to use
|
|
||||||
|
|
||||||
### Surfshark
|
|
||||||
|
|
||||||
- Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Surfshark) or a multi hop region name
|
|
||||||
|
|
||||||
### Cyberghost
|
|
||||||
|
|
||||||
- Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Cyberghost)
|
|
||||||
|
|
||||||
### Vyprvpn
|
|
||||||
|
|
||||||
- Pick the [region](https://www.vyprvpn.com/server-locations)
|
|
||||||
|
|
||||||
### Extra niche features
|
### Extra niche features
|
||||||
|
|
||||||
@@ -212,14 +193,13 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
|
|||||||
|
|
||||||
And use the line produced as the value for the environment variable `CLIENT_KEY`.
|
And use the line produced as the value for the environment variable `CLIENT_KEY`.
|
||||||
|
|
||||||
- NordVPN
|
- Vyprvpn
|
||||||
|
|
||||||
| Variable | Default | Choices | Description |
|
| Variable | Default | Choices | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 🏁 `USER` | | | Your username |
|
| 🏁 `USER` | | | Your username |
|
||||||
| 🏁 `PASSWORD` | | | Your password |
|
| 🏁 `PASSWORD` | | | Your password |
|
||||||
| `REGION` | `Austria` | One of the [VyprVPN regions](https://www.vyprvpn.com/server-locations) | VPN server region |
|
| `REGION` | `Austria` | One of the [VyprVPN regions](https://www.vyprvpn.com/server-locations) | VPN server region |
|
||||||
| `SERVER_NUMBER` | | Server integer number | Optional server number. For example `251` for `Italy #251` |
|
|
||||||
|
|
||||||
- NordVPN
|
- NordVPN
|
||||||
|
|
||||||
@@ -227,7 +207,8 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 🏁 `USER` | | | Your username |
|
| 🏁 `USER` | | | Your username |
|
||||||
| 🏁 `PASSWORD` | | | Your password |
|
| 🏁 `PASSWORD` | | | Your password |
|
||||||
| 🏁 `REGION` | `Austria` (wrong) | One of the NordVPN server name, i.e. `Cyprus #12` | VPN server name |
|
| 🏁 `REGION` | `Austria` (wrong) | One of the NordVPN server country, i.e. `Switzerland` | VPN server country |
|
||||||
|
| `SERVER_NUMBER` | | Server integer number | Optional server number. For example `251` for `Italy #251` |
|
||||||
|
|
||||||
### DNS over TLS
|
### DNS over TLS
|
||||||
|
|
||||||
@@ -289,6 +270,12 @@ That one is important if you want to connect to the container from your LAN for
|
|||||||
| `UID` | `1000` | | User ID to run as non root and for ownership of files written |
|
| `UID` | `1000` | | User ID to run as non root and for ownership of files written |
|
||||||
| `GID` | `1000` | | Group ID to run as non root and for ownership of files written |
|
| `GID` | `1000` | | Group ID to run as non root and for ownership of files written |
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
| Variable | Default | Choices | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `PUBLICIP_PERIOD` | `12h` | Valid duration | Period to check for public IP address. Set to `0` to disable. |
|
||||||
|
|
||||||
## Connect to it
|
## Connect to it
|
||||||
|
|
||||||
There are various ways to achieve this, depending on your use case.
|
There are various ways to achieve this, depending on your use case.
|
||||||
|
|||||||
@@ -137,10 +137,12 @@ func _main(background context.Context, args []string) int {
|
|||||||
// wait for restartUnbound
|
// wait for restartUnbound
|
||||||
go unboundLooper.Run(ctx, wg)
|
go unboundLooper.Run(ctx, wg)
|
||||||
|
|
||||||
publicIPLooper := publicip.NewLooper(client, logger, fileManager, allSettings.System.IPStatusFilepath, uid, gid)
|
publicIPLooper := publicip.NewLooper(client, logger, fileManager, allSettings.System.IPStatusFilepath, allSettings.PublicIPPeriod, uid, gid)
|
||||||
restartPublicIP := publicIPLooper.Restart
|
restartPublicIP := publicIPLooper.Restart
|
||||||
|
setPublicIPPeriod := publicIPLooper.SetPeriod
|
||||||
go publicIPLooper.Run(ctx)
|
go publicIPLooper.Run(ctx)
|
||||||
go publicIPLooper.RunRestartTicker(ctx)
|
go publicIPLooper.RunRestartTicker(ctx)
|
||||||
|
setPublicIPPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
|
||||||
|
|
||||||
tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf, allSettings.TinyProxy, logger, streamMerger, uid, gid)
|
tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf, allSettings.TinyProxy, logger, streamMerger, uid, gid)
|
||||||
restartTinyproxy := tinyproxyLooper.Restart
|
restartTinyproxy := tinyproxyLooper.Restart
|
||||||
|
|||||||
@@ -16,16 +16,24 @@ type Looper interface {
|
|||||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||||
RunRestartTicker(ctx context.Context)
|
RunRestartTicker(ctx context.Context)
|
||||||
Restart()
|
Restart()
|
||||||
|
Start()
|
||||||
|
Stop()
|
||||||
|
GetSettings() (settings settings.DNS)
|
||||||
|
SetSettings(settings settings.DNS)
|
||||||
}
|
}
|
||||||
|
|
||||||
type looper struct {
|
type looper struct {
|
||||||
conf Configurator
|
conf Configurator
|
||||||
settings settings.DNS
|
settings settings.DNS
|
||||||
|
settingsMutex sync.RWMutex
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
streamMerger command.StreamMerger
|
streamMerger command.StreamMerger
|
||||||
uid int
|
uid int
|
||||||
gid int
|
gid int
|
||||||
restart chan struct{}
|
restart chan struct{}
|
||||||
|
start chan struct{}
|
||||||
|
stop chan struct{}
|
||||||
|
updateTicker chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
||||||
@@ -38,10 +46,44 @@ func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
|||||||
gid: gid,
|
gid: gid,
|
||||||
streamMerger: streamMerger,
|
streamMerger: streamMerger,
|
||||||
restart: make(chan struct{}),
|
restart: make(chan struct{}),
|
||||||
|
start: make(chan struct{}),
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
updateTicker: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||||
|
func (l *looper) Start() { l.start <- struct{}{} }
|
||||||
|
func (l *looper) Stop() { l.stop <- struct{}{} }
|
||||||
|
|
||||||
|
func (l *looper) GetSettings() (settings settings.DNS) {
|
||||||
|
l.settingsMutex.RLock()
|
||||||
|
defer l.settingsMutex.RUnlock()
|
||||||
|
return l.settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *looper) SetSettings(settings settings.DNS) {
|
||||||
|
l.settingsMutex.Lock()
|
||||||
|
defer l.settingsMutex.Unlock()
|
||||||
|
updatePeriodDiffers := l.settings.UpdatePeriod != settings.UpdatePeriod
|
||||||
|
l.settings = settings
|
||||||
|
l.settingsMutex.Unlock()
|
||||||
|
if updatePeriodDiffers {
|
||||||
|
l.updateTicker <- struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) logAndWait(ctx context.Context, err error) {
|
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||||
l.logger.Warn(err)
|
l.logger.Warn(err)
|
||||||
@@ -55,28 +97,44 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
l.fallbackToUnencryptedDNS()
|
l.fallbackToUnencryptedDNS()
|
||||||
|
waitForStart := true
|
||||||
|
for waitForStart {
|
||||||
select {
|
select {
|
||||||
|
case <-l.stop:
|
||||||
|
l.logger.Info("not started yet")
|
||||||
case <-l.restart:
|
case <-l.restart:
|
||||||
|
waitForStart = false
|
||||||
|
case <-l.start:
|
||||||
|
waitForStart = false
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
defer l.logger.Warn("loop exited")
|
defer l.logger.Warn("loop exited")
|
||||||
|
|
||||||
var unboundCtx context.Context
|
var unboundCtx context.Context
|
||||||
var unboundCancel context.CancelFunc = func() {}
|
var unboundCancel context.CancelFunc = func() {}
|
||||||
var waitError chan error
|
var waitError chan error
|
||||||
triggeredRestart := false
|
triggeredRestart := false
|
||||||
|
l.setEnabled(true)
|
||||||
for ctx.Err() == nil {
|
for ctx.Err() == nil {
|
||||||
if !l.settings.Enabled {
|
for !l.isEnabled() {
|
||||||
// wait for another restart signal to recheck if it is enabled
|
// wait for a signal to re-enable
|
||||||
select {
|
select {
|
||||||
|
case <-l.stop:
|
||||||
|
l.logger.Info("already disabled")
|
||||||
case <-l.restart:
|
case <-l.restart:
|
||||||
|
l.setEnabled(true)
|
||||||
|
case <-l.start:
|
||||||
|
l.setEnabled(true)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
unboundCancel()
|
unboundCancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings := l.GetSettings()
|
||||||
|
|
||||||
// Setup
|
// Setup
|
||||||
if err := l.conf.DownloadRootHints(l.uid, l.gid); err != nil {
|
if err := l.conf.DownloadRootHints(l.uid, l.gid); err != nil {
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
@@ -86,7 +144,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := l.conf.MakeUnboundConf(l.settings, l.uid, l.gid); err != nil {
|
if err := l.conf.MakeUnboundConf(settings, l.uid, l.gid); err != nil {
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -98,7 +156,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
close(waitError)
|
close(waitError)
|
||||||
}
|
}
|
||||||
unboundCtx, unboundCancel = context.WithCancel(context.Background())
|
unboundCtx, unboundCancel = context.WithCancel(context.Background())
|
||||||
stream, waitFn, err := l.conf.Start(unboundCtx, l.settings.VerbosityDetailsLevel)
|
stream, waitFn, err := l.conf.Start(unboundCtx, settings.VerbosityDetailsLevel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unboundCancel()
|
unboundCancel()
|
||||||
l.fallbackToUnencryptedDNS()
|
l.fallbackToUnencryptedDNS()
|
||||||
@@ -109,7 +167,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
// Started successfully
|
// Started successfully
|
||||||
go l.streamMerger.Merge(unboundCtx, stream, command.MergeName("unbound"))
|
go l.streamMerger.Merge(unboundCtx, stream, command.MergeName("unbound"))
|
||||||
l.conf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound
|
l.conf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound
|
||||||
if err := l.conf.UseDNSSystemWide(net.IP{127, 0, 0, 1}, l.settings.KeepNameserver); err != nil { // use Unbound
|
if err := l.conf.UseDNSSystemWide(net.IP{127, 0, 0, 1}, settings.KeepNameserver); err != nil { // use Unbound
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
}
|
}
|
||||||
if err := l.conf.WaitForUnbound(); err != nil {
|
if err := l.conf.WaitForUnbound(); err != nil {
|
||||||
@@ -124,7 +182,8 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
waitError <- err
|
waitError <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Wait for one of the three cases below
|
stayHere := true
|
||||||
|
for stayHere {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
l.logger.Warn("context canceled: exiting loop")
|
l.logger.Warn("context canceled: exiting loop")
|
||||||
@@ -136,36 +195,50 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
l.logger.Info("restarting")
|
l.logger.Info("restarting")
|
||||||
// unboundCancel occurs next loop run when the setup is complete
|
// unboundCancel occurs next loop run when the setup is complete
|
||||||
triggeredRestart = true
|
triggeredRestart = true
|
||||||
|
stayHere = false
|
||||||
|
case <-l.start:
|
||||||
|
l.logger.Info("already started")
|
||||||
|
case <-l.stop:
|
||||||
|
l.logger.Info("stopping")
|
||||||
|
unboundCancel()
|
||||||
|
<-waitError
|
||||||
|
close(waitError)
|
||||||
|
l.setEnabled(false)
|
||||||
|
stayHere = false
|
||||||
case err := <-waitError: // unexpected error
|
case err := <-waitError: // unexpected error
|
||||||
close(waitError)
|
close(waitError)
|
||||||
unboundCancel()
|
unboundCancel()
|
||||||
l.fallbackToUnencryptedDNS()
|
l.fallbackToUnencryptedDNS()
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
|
stayHere = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unboundCancel()
|
unboundCancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) fallbackToUnencryptedDNS() {
|
func (l *looper) fallbackToUnencryptedDNS() {
|
||||||
|
settings := l.GetSettings()
|
||||||
|
|
||||||
// Try with user provided plaintext ip address
|
// Try with user provided plaintext ip address
|
||||||
targetIP := l.settings.PlaintextAddress
|
targetIP := settings.PlaintextAddress
|
||||||
if targetIP != nil {
|
if targetIP != nil {
|
||||||
l.logger.Info("falling back on plaintext DNS at address %s", targetIP)
|
l.logger.Info("falling back on plaintext DNS at address %s", targetIP)
|
||||||
l.conf.UseDNSInternally(targetIP)
|
l.conf.UseDNSInternally(targetIP)
|
||||||
if err := l.conf.UseDNSSystemWide(targetIP, l.settings.KeepNameserver); err != nil {
|
if err := l.conf.UseDNSSystemWide(targetIP, settings.KeepNameserver); err != nil {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try with any IPv4 address from the providers chosen
|
// Try with any IPv4 address from the providers chosen
|
||||||
for _, provider := range l.settings.Providers {
|
for _, provider := range settings.Providers {
|
||||||
data := constants.DNSProviderMapping()[provider]
|
data := constants.DNSProviderMapping()[provider]
|
||||||
for _, targetIP = range data.IPs {
|
for _, targetIP = range data.IPs {
|
||||||
if targetIP.To4() != nil {
|
if targetIP.To4() != nil {
|
||||||
l.logger.Info("falling back on plaintext DNS at address %s", targetIP)
|
l.logger.Info("falling back on plaintext DNS at address %s", targetIP)
|
||||||
l.conf.UseDNSInternally(targetIP)
|
l.conf.UseDNSInternally(targetIP)
|
||||||
if err := l.conf.UseDNSSystemWide(targetIP, l.settings.KeepNameserver); err != nil {
|
if err := l.conf.UseDNSSystemWide(targetIP, settings.KeepNameserver); err != nil {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -174,14 +247,17 @@ func (l *looper) fallbackToUnencryptedDNS() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No IPv4 address found
|
// No IPv4 address found
|
||||||
l.logger.Error("no ipv4 DNS address found for providers %s", l.settings.Providers)
|
l.logger.Error("no ipv4 DNS address found for providers %s", settings.Providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) RunRestartTicker(ctx context.Context) {
|
func (l *looper) RunRestartTicker(ctx context.Context) {
|
||||||
if l.settings.UpdatePeriod == 0 {
|
ticker := time.NewTicker(time.Hour)
|
||||||
return
|
settings := l.GetSettings()
|
||||||
|
if settings.UpdatePeriod > 0 {
|
||||||
|
ticker = time.NewTicker(settings.UpdatePeriod)
|
||||||
|
} else {
|
||||||
|
ticker.Stop()
|
||||||
}
|
}
|
||||||
ticker := time.NewTicker(l.settings.UpdatePeriod)
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -189,6 +265,10 @@ func (l *looper) RunRestartTicker(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
l.restart <- struct{}{}
|
l.restart <- struct{}{}
|
||||||
|
case <-l.updateTicker:
|
||||||
|
ticker.Stop()
|
||||||
|
period := l.GetSettings().UpdatePeriod
|
||||||
|
ticker = time.NewTicker(period)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
||||||
)
|
)
|
||||||
@@ -26,8 +27,35 @@ var regularExpressions = struct { //nolint:gochecknoglobals
|
|||||||
func PostProcessLine(s string) (filtered string, level logging.Level) {
|
func PostProcessLine(s string) (filtered string, level logging.Level) {
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(s, "openvpn: "):
|
case strings.HasPrefix(s, "openvpn: "):
|
||||||
filtered = constants.ColorOpenvpn().Sprintf(s)
|
for _, ignored := range []string{
|
||||||
return filtered, logging.InfoLevel
|
"openvpn: WARNING: you are using user/group/chroot/setcon without persist-tun -- this may cause restarts to fail",
|
||||||
|
"openvpn: NOTE: UID/GID downgrade will be delayed because of --client, --pull, or --up-delay",
|
||||||
|
} {
|
||||||
|
if s == ignored {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(s, "openvpn: NOTE: "):
|
||||||
|
filtered = strings.TrimPrefix(s, "openvpn: NOTE: ")
|
||||||
|
filtered = "openvpn: " + filtered
|
||||||
|
level = logging.InfoLevel
|
||||||
|
case strings.HasPrefix(s, "openvpn: WARNING: "):
|
||||||
|
filtered = strings.TrimPrefix(s, "openvpn: WARNING: ")
|
||||||
|
filtered = "openvpn: " + filtered
|
||||||
|
level = logging.WarnLevel
|
||||||
|
case strings.HasPrefix(s, "openvpn: Options error: "):
|
||||||
|
filtered = strings.TrimPrefix(s, "openvpn: Options error: ")
|
||||||
|
filtered = "openvpn: " + filtered
|
||||||
|
level = logging.ErrorLevel
|
||||||
|
case s == "openvpn: Initialization Sequence Completed":
|
||||||
|
return color.HiGreenString(s), logging.InfoLevel
|
||||||
|
default:
|
||||||
|
filtered = s
|
||||||
|
level = logging.InfoLevel
|
||||||
|
}
|
||||||
|
filtered = constants.ColorOpenvpn().Sprintf(filtered)
|
||||||
|
return filtered, level
|
||||||
case strings.HasPrefix(s, "unbound: "):
|
case strings.HasPrefix(s, "unbound: "):
|
||||||
prefix := regularExpressions.unboundPrefix.FindString(s)
|
prefix := regularExpressions.unboundPrefix.FindString(s)
|
||||||
filtered = s[len(prefix):]
|
filtered = s[len(prefix):]
|
||||||
|
|||||||
@@ -80,6 +80,30 @@ func Test_PostProcessLine(t *testing.T) {
|
|||||||
"tinyproxy: BLABLA Jul 12 23:07:25 [32]: Reloading config file",
|
"tinyproxy: BLABLA Jul 12 23:07:25 [32]: Reloading config file",
|
||||||
"tinyproxy: Reloading config file",
|
"tinyproxy: Reloading config file",
|
||||||
logging.ErrorLevel},
|
logging.ErrorLevel},
|
||||||
|
"openvpn unknown": {
|
||||||
|
"openvpn: message",
|
||||||
|
"openvpn: message",
|
||||||
|
logging.InfoLevel},
|
||||||
|
"openvpn note": {
|
||||||
|
"openvpn: NOTE: message",
|
||||||
|
"openvpn: message",
|
||||||
|
logging.InfoLevel},
|
||||||
|
"openvpn warning": {
|
||||||
|
"openvpn: WARNING: message",
|
||||||
|
"openvpn: message",
|
||||||
|
logging.WarnLevel},
|
||||||
|
"openvpn options error": {
|
||||||
|
"openvpn: Options error: message",
|
||||||
|
"openvpn: message",
|
||||||
|
logging.ErrorLevel},
|
||||||
|
"openvpn ignored message": {
|
||||||
|
"openvpn: NOTE: UID/GID downgrade will be delayed because of --client, --pull, or --up-delay",
|
||||||
|
"",
|
||||||
|
""},
|
||||||
|
"openvpn success": {
|
||||||
|
"openvpn: Initialization Sequence Completed",
|
||||||
|
"openvpn: Initialization Sequence Completed",
|
||||||
|
logging.InfoLevel},
|
||||||
}
|
}
|
||||||
for name, tc := range tests {
|
for name, tc := range tests {
|
||||||
tc := tc
|
tc := tc
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ type Looper interface {
|
|||||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||||
Restart()
|
Restart()
|
||||||
PortForward()
|
PortForward()
|
||||||
|
GetSettings() (settings settings.OpenVPN)
|
||||||
|
SetSettings(settings settings.OpenVPN)
|
||||||
}
|
}
|
||||||
|
|
||||||
type looper struct {
|
type looper struct {
|
||||||
// Variable parameters
|
// Variable parameters
|
||||||
provider models.VPNProvider
|
provider models.VPNProvider
|
||||||
settings settings.OpenVPN
|
settings settings.OpenVPN
|
||||||
|
settingsMutex sync.RWMutex
|
||||||
// Fixed parameters
|
// Fixed parameters
|
||||||
uid int
|
uid int
|
||||||
gid int
|
gid int
|
||||||
@@ -69,6 +72,18 @@ func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
|
|||||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||||
func (l *looper) PortForward() { l.portForwardSignals <- struct{}{} }
|
func (l *looper) PortForward() { l.portForwardSignals <- struct{}{} }
|
||||||
|
|
||||||
|
func (l *looper) GetSettings() (settings settings.OpenVPN) {
|
||||||
|
l.settingsMutex.RLock()
|
||||||
|
defer l.settingsMutex.RUnlock()
|
||||||
|
return l.settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *looper) SetSettings(settings settings.OpenVPN) {
|
||||||
|
l.settingsMutex.Lock()
|
||||||
|
defer l.settingsMutex.Unlock()
|
||||||
|
l.settings = settings
|
||||||
|
}
|
||||||
|
|
||||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@@ -80,23 +95,24 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
defer l.logger.Warn("loop exited")
|
defer l.logger.Warn("loop exited")
|
||||||
|
|
||||||
for ctx.Err() == nil {
|
for ctx.Err() == nil {
|
||||||
|
settings := l.GetSettings()
|
||||||
providerConf := provider.New(l.provider)
|
providerConf := provider.New(l.provider)
|
||||||
connections, err := providerConf.GetOpenVPNConnections(l.settings.Provider.ServerSelection)
|
connections, err := providerConf.GetOpenVPNConnections(settings.Provider.ServerSelection)
|
||||||
l.fatalOnError(err)
|
l.fatalOnError(err)
|
||||||
lines := providerConf.BuildConf(
|
lines := providerConf.BuildConf(
|
||||||
connections,
|
connections,
|
||||||
l.settings.Verbosity,
|
settings.Verbosity,
|
||||||
l.uid,
|
l.uid,
|
||||||
l.gid,
|
l.gid,
|
||||||
l.settings.Root,
|
settings.Root,
|
||||||
l.settings.Cipher,
|
settings.Cipher,
|
||||||
l.settings.Auth,
|
settings.Auth,
|
||||||
l.settings.Provider.ExtraConfigOptions,
|
settings.Provider.ExtraConfigOptions,
|
||||||
)
|
)
|
||||||
err = l.fileManager.WriteLinesToFile(string(constants.OpenVPNConf), lines, files.Ownership(l.uid, l.gid), files.Permissions(0400))
|
err = l.fileManager.WriteLinesToFile(string(constants.OpenVPNConf), lines, files.Ownership(l.uid, l.gid), files.Permissions(0400))
|
||||||
l.fatalOnError(err)
|
l.fatalOnError(err)
|
||||||
|
|
||||||
err = l.conf.WriteAuthFile(l.settings.User, l.settings.Password, l.uid, l.gid)
|
err = l.conf.WriteAuthFile(settings.User, settings.Password, l.uid, l.gid)
|
||||||
l.fatalOnError(err)
|
l.fatalOnError(err)
|
||||||
|
|
||||||
if err := l.fw.SetVPNConnections(ctx, connections); err != nil {
|
if err := l.fw.SetVPNConnections(ctx, connections); err != nil {
|
||||||
@@ -158,7 +174,8 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) portForward(ctx context.Context, providerConf provider.Provider, client network.Client) {
|
func (l *looper) portForward(ctx context.Context, providerConf provider.Provider, client network.Client) {
|
||||||
if !l.settings.Provider.PortForwarding.Enabled {
|
settings := l.GetSettings()
|
||||||
|
if !settings.Provider.PortForwarding.Enabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var port uint16
|
var port uint16
|
||||||
@@ -175,7 +192,7 @@ func (l *looper) portForward(ctx context.Context, providerConf provider.Provider
|
|||||||
l.logger.Info("port forwarded is %d", port)
|
l.logger.Info("port forwarded is %d", port)
|
||||||
}
|
}
|
||||||
|
|
||||||
filepath := l.settings.Provider.PortForwarding.Filepath
|
filepath := settings.Provider.PortForwarding.Filepath
|
||||||
l.logger.Info("writing forwarded port to %s", filepath)
|
l.logger.Info("writing forwarded port to %s", filepath)
|
||||||
err = l.fileManager.WriteLinesToFile(
|
err = l.fileManager.WriteLinesToFile(
|
||||||
string(filepath), []string{fmt.Sprintf("%d", port)},
|
string(filepath), []string{fmt.Sprintf("%d", port)},
|
||||||
|
|||||||
@@ -99,6 +99,9 @@ type Reader interface {
|
|||||||
GetTinyProxyUser() (user string, err error)
|
GetTinyProxyUser() (user string, err error)
|
||||||
GetTinyProxyPassword() (password string, err error)
|
GetTinyProxyPassword() (password string, err error)
|
||||||
|
|
||||||
|
// Public IP getters
|
||||||
|
GetPublicIPPeriod() (period time.Duration, err error)
|
||||||
|
|
||||||
// Version getters
|
// Version getters
|
||||||
GetVersion() string
|
GetVersion() string
|
||||||
GetBuildDate() string
|
GetBuildDate() string
|
||||||
|
|||||||
17
internal/params/publicip.go
Normal file
17
internal/params/publicip.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package params
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
libparams "github.com/qdm12/golibs/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPublicIPPeriod obtains the period to fetch the IP address periodically.
|
||||||
|
// Set to 0 to disable
|
||||||
|
func (r *reader) GetPublicIPPeriod() (period time.Duration, err error) {
|
||||||
|
s, err := r.envParams.GetEnv("PUBLICIP_PERIOD", libparams.Default("12h"))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return time.ParseDuration(s)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package publicip
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/golibs/files"
|
"github.com/qdm12/golibs/files"
|
||||||
@@ -14,9 +15,14 @@ type Looper interface {
|
|||||||
Run(ctx context.Context)
|
Run(ctx context.Context)
|
||||||
RunRestartTicker(ctx context.Context)
|
RunRestartTicker(ctx context.Context)
|
||||||
Restart()
|
Restart()
|
||||||
|
Stop()
|
||||||
|
GetPeriod() (period time.Duration)
|
||||||
|
SetPeriod(period time.Duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
type looper struct {
|
type looper struct {
|
||||||
|
period time.Duration
|
||||||
|
periodMutex sync.RWMutex
|
||||||
getter IPGetter
|
getter IPGetter
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
fileManager files.FileManager
|
fileManager files.FileManager
|
||||||
@@ -24,11 +30,14 @@ type looper struct {
|
|||||||
uid int
|
uid int
|
||||||
gid int
|
gid int
|
||||||
restart chan struct{}
|
restart chan struct{}
|
||||||
|
stop chan struct{}
|
||||||
|
updateTicker chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLooper(client network.Client, logger logging.Logger, fileManager files.FileManager,
|
func NewLooper(client network.Client, logger logging.Logger, fileManager files.FileManager,
|
||||||
ipStatusFilepath models.Filepath, uid, gid int) Looper {
|
ipStatusFilepath models.Filepath, period time.Duration, uid, gid int) Looper {
|
||||||
return &looper{
|
return &looper{
|
||||||
|
period: period,
|
||||||
getter: NewIPGetter(client),
|
getter: NewIPGetter(client),
|
||||||
logger: logger.WithPrefix("ip getter: "),
|
logger: logger.WithPrefix("ip getter: "),
|
||||||
fileManager: fileManager,
|
fileManager: fileManager,
|
||||||
@@ -36,10 +45,26 @@ func NewLooper(client network.Client, logger logging.Logger, fileManager files.F
|
|||||||
uid: uid,
|
uid: uid,
|
||||||
gid: gid,
|
gid: gid,
|
||||||
restart: make(chan struct{}),
|
restart: make(chan struct{}),
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
updateTicker: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||||
|
func (l *looper) Stop() { l.stop <- struct{}{} }
|
||||||
|
|
||||||
|
func (l *looper) GetPeriod() (period time.Duration) {
|
||||||
|
l.periodMutex.RLock()
|
||||||
|
defer l.periodMutex.RUnlock()
|
||||||
|
return l.period
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *looper) SetPeriod(period time.Duration) {
|
||||||
|
l.periodMutex.Lock()
|
||||||
|
l.period = period
|
||||||
|
l.periodMutex.Unlock()
|
||||||
|
l.updateTicker <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
@@ -57,7 +82,23 @@ func (l *looper) Run(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
defer l.logger.Warn("loop exited")
|
defer l.logger.Warn("loop exited")
|
||||||
|
|
||||||
|
enabled := true
|
||||||
|
|
||||||
for ctx.Err() == nil {
|
for ctx.Err() == nil {
|
||||||
|
for !enabled {
|
||||||
|
// wait for a signal to re-enable
|
||||||
|
select {
|
||||||
|
case <-l.stop:
|
||||||
|
l.logger.Info("already disabled")
|
||||||
|
case <-l.restart:
|
||||||
|
enabled = true
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled and has a period set
|
||||||
|
|
||||||
ip, err := l.getter.Get()
|
ip, err := l.getter.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
@@ -75,8 +116,9 @@ func (l *looper) Run(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case <-l.restart: // triggered restart
|
case <-l.restart: // triggered restart
|
||||||
|
case <-l.stop:
|
||||||
|
enabled = false
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
l.logger.Warn("context canceled: exiting loop")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,6 +126,12 @@ func (l *looper) Run(ctx context.Context) {
|
|||||||
|
|
||||||
func (l *looper) RunRestartTicker(ctx context.Context) {
|
func (l *looper) RunRestartTicker(ctx context.Context) {
|
||||||
ticker := time.NewTicker(time.Hour)
|
ticker := time.NewTicker(time.Hour)
|
||||||
|
period := l.GetPeriod()
|
||||||
|
if period > 0 {
|
||||||
|
ticker = time.NewTicker(period)
|
||||||
|
} else {
|
||||||
|
ticker.Stop()
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -91,6 +139,9 @@ func (l *looper) RunRestartTicker(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
l.restart <- struct{}{}
|
l.restart <- struct{}{}
|
||||||
|
case <-l.updateTicker:
|
||||||
|
ticker.Stop()
|
||||||
|
ticker = time.NewTicker(l.GetPeriod())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/private-internet-access-docker/internal/models"
|
"github.com/qdm12/private-internet-access-docker/internal/models"
|
||||||
"github.com/qdm12/private-internet-access-docker/internal/params"
|
"github.com/qdm12/private-internet-access-docker/internal/params"
|
||||||
@@ -16,6 +17,7 @@ type Settings struct {
|
|||||||
Firewall Firewall
|
Firewall Firewall
|
||||||
TinyProxy TinyProxy
|
TinyProxy TinyProxy
|
||||||
ShadowSocks ShadowSocks
|
ShadowSocks ShadowSocks
|
||||||
|
PublicIPPeriod time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) String() string {
|
func (s *Settings) String() string {
|
||||||
@@ -27,6 +29,7 @@ func (s *Settings) String() string {
|
|||||||
s.Firewall.String(),
|
s.Firewall.String(),
|
||||||
s.TinyProxy.String(),
|
s.TinyProxy.String(),
|
||||||
s.ShadowSocks.String(),
|
s.ShadowSocks.String(),
|
||||||
|
"Public IP check period: " + s.PublicIPPeriod.String(),
|
||||||
"", // new line at the end
|
"", // new line at the end
|
||||||
}, "\n")
|
}, "\n")
|
||||||
}
|
}
|
||||||
@@ -62,5 +65,9 @@ func GetAllSettings(paramsReader params.Reader) (settings Settings, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
|
settings.PublicIPPeriod, err = paramsReader.GetPublicIPPeriod()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,18 +14,25 @@ import (
|
|||||||
type Looper interface {
|
type Looper interface {
|
||||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||||
Restart()
|
Restart()
|
||||||
|
Start()
|
||||||
|
Stop()
|
||||||
|
GetSettings() (settings settings.ShadowSocks)
|
||||||
|
SetSettings(settings settings.ShadowSocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
type looper struct {
|
type looper struct {
|
||||||
conf Configurator
|
conf Configurator
|
||||||
firewallConf firewall.Configurator
|
firewallConf firewall.Configurator
|
||||||
settings settings.ShadowSocks
|
settings settings.ShadowSocks
|
||||||
dnsSettings settings.DNS
|
settingsMutex sync.RWMutex
|
||||||
|
dnsSettings settings.DNS // TODO
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
streamMerger command.StreamMerger
|
streamMerger command.StreamMerger
|
||||||
uid int
|
uid int
|
||||||
gid int
|
gid int
|
||||||
restart chan struct{}
|
restart chan struct{}
|
||||||
|
start chan struct{}
|
||||||
|
stop chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||||
@@ -48,34 +55,81 @@ func NewLooper(conf Configurator, firewallConf firewall.Configurator, settings s
|
|||||||
uid: uid,
|
uid: uid,
|
||||||
gid: gid,
|
gid: gid,
|
||||||
restart: make(chan struct{}),
|
restart: make(chan struct{}),
|
||||||
|
start: make(chan struct{}),
|
||||||
|
stop: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||||
|
func (l *looper) Start() { l.start <- struct{}{} }
|
||||||
|
func (l *looper) Stop() { l.stop <- struct{}{} }
|
||||||
|
|
||||||
|
func (l *looper) GetSettings() (settings settings.ShadowSocks) {
|
||||||
|
l.settingsMutex.RLock()
|
||||||
|
defer l.settingsMutex.RUnlock()
|
||||||
|
return l.settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *looper) SetSettings(settings settings.ShadowSocks) {
|
||||||
|
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) Run(ctx context.Context, wg *sync.WaitGroup) {
|
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
waitForStart := true
|
||||||
|
for waitForStart {
|
||||||
select {
|
select {
|
||||||
|
case <-l.stop:
|
||||||
|
l.logger.Info("not started yet")
|
||||||
|
case <-l.start:
|
||||||
|
waitForStart = false
|
||||||
case <-l.restart:
|
case <-l.restart:
|
||||||
|
waitForStart = false
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
defer l.logger.Warn("loop exited")
|
defer l.logger.Warn("loop exited")
|
||||||
|
|
||||||
|
l.setEnabled(true)
|
||||||
|
|
||||||
var previousPort uint16
|
var previousPort uint16
|
||||||
for ctx.Err() == nil {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nameserver := l.dnsSettings.PlaintextAddress.String()
|
nameserver := l.dnsSettings.PlaintextAddress.String()
|
||||||
if l.dnsSettings.Enabled {
|
if l.dnsSettings.Enabled {
|
||||||
nameserver = "127.0.0.1"
|
nameserver = "127.0.0.1"
|
||||||
}
|
}
|
||||||
err := l.conf.MakeConf(
|
settings := l.GetSettings()
|
||||||
l.settings.Port,
|
err := l.conf.MakeConf(settings.Port, settings.Password, settings.Method, nameserver, l.uid, l.gid)
|
||||||
l.settings.Password,
|
|
||||||
l.settings.Method,
|
|
||||||
nameserver,
|
|
||||||
l.uid,
|
|
||||||
l.gid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
continue
|
continue
|
||||||
@@ -87,14 +141,14 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := l.firewallConf.SetAllowedPort(ctx, l.settings.Port); err != nil {
|
if err := l.firewallConf.SetAllowedPort(ctx, settings.Port); err != nil {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
previousPort = l.settings.Port
|
previousPort = settings.Port
|
||||||
|
|
||||||
shadowsocksCtx, shadowsocksCancel := context.WithCancel(context.Background())
|
shadowsocksCtx, shadowsocksCancel := context.WithCancel(context.Background())
|
||||||
stdout, stderr, waitFn, err := l.conf.Start(shadowsocksCtx, "0.0.0.0", l.settings.Port, l.settings.Password, l.settings.Log)
|
stdout, stderr, waitFn, err := l.conf.Start(shadowsocksCtx, "0.0.0.0", settings.Port, settings.Password, settings.Log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
shadowsocksCancel()
|
shadowsocksCancel()
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
@@ -107,6 +161,9 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
err := waitFn() // blocking
|
err := waitFn() // blocking
|
||||||
waitError <- err
|
waitError <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
stayHere := true
|
||||||
|
for stayHere {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
l.logger.Warn("context canceled: exiting loop")
|
l.logger.Warn("context canceled: exiting loop")
|
||||||
@@ -119,10 +176,22 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
shadowsocksCancel()
|
shadowsocksCancel()
|
||||||
<-waitError
|
<-waitError
|
||||||
close(waitError)
|
close(waitError)
|
||||||
|
stayHere = false
|
||||||
|
case <-l.start:
|
||||||
|
l.logger.Info("already started")
|
||||||
|
case <-l.stop:
|
||||||
|
l.logger.Info("stopping")
|
||||||
|
shadowsocksCancel()
|
||||||
|
<-waitError
|
||||||
|
close(waitError)
|
||||||
|
l.setEnabled(false)
|
||||||
|
stayHere = false
|
||||||
case err := <-waitError: // unexpected error
|
case err := <-waitError: // unexpected error
|
||||||
shadowsocksCancel()
|
shadowsocksCancel()
|
||||||
close(waitError)
|
close(waitError)
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
shadowsocksCancel() // repetition for linter only
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,24 @@ import (
|
|||||||
type Looper interface {
|
type Looper interface {
|
||||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||||
Restart()
|
Restart()
|
||||||
|
Start()
|
||||||
|
Stop()
|
||||||
|
GetSettings() (settings settings.TinyProxy)
|
||||||
|
SetSettings(settings settings.TinyProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
type looper struct {
|
type looper struct {
|
||||||
conf Configurator
|
conf Configurator
|
||||||
firewallConf firewall.Configurator
|
firewallConf firewall.Configurator
|
||||||
settings settings.TinyProxy
|
settings settings.TinyProxy
|
||||||
|
settingsMutex sync.RWMutex
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
streamMerger command.StreamMerger
|
streamMerger command.StreamMerger
|
||||||
uid int
|
uid int
|
||||||
gid int
|
gid int
|
||||||
restart chan struct{}
|
restart chan struct{}
|
||||||
|
start chan struct{}
|
||||||
|
stop chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||||
@@ -46,30 +53,75 @@ func NewLooper(conf Configurator, firewallConf firewall.Configurator, settings s
|
|||||||
uid: uid,
|
uid: uid,
|
||||||
gid: gid,
|
gid: gid,
|
||||||
restart: make(chan struct{}),
|
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) 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) {
|
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
waitForStart := true
|
||||||
|
for waitForStart {
|
||||||
select {
|
select {
|
||||||
|
case <-l.stop:
|
||||||
|
l.logger.Info("not started yet")
|
||||||
|
case <-l.start:
|
||||||
|
waitForStart = false
|
||||||
case <-l.restart:
|
case <-l.restart:
|
||||||
|
waitForStart = false
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
defer l.logger.Warn("loop exited")
|
defer l.logger.Warn("loop exited")
|
||||||
|
|
||||||
var previousPort uint16
|
var previousPort uint16
|
||||||
for ctx.Err() == nil {
|
for ctx.Err() == nil {
|
||||||
err := l.conf.MakeConf(
|
for !l.isEnabled() {
|
||||||
l.settings.LogLevel,
|
// wait for a signal to re-enable
|
||||||
l.settings.Port,
|
select {
|
||||||
l.settings.User,
|
case <-l.stop:
|
||||||
l.settings.Password,
|
l.logger.Info("already disabled")
|
||||||
l.uid,
|
case <-l.restart:
|
||||||
l.gid)
|
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 {
|
if err != nil {
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
continue
|
continue
|
||||||
@@ -81,11 +133,11 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := l.firewallConf.SetAllowedPort(ctx, l.settings.Port); err != nil {
|
if err := l.firewallConf.SetAllowedPort(ctx, settings.Port); err != nil {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
previousPort = l.settings.Port
|
previousPort = settings.Port
|
||||||
|
|
||||||
tinyproxyCtx, tinyproxyCancel := context.WithCancel(context.Background())
|
tinyproxyCtx, tinyproxyCancel := context.WithCancel(context.Background())
|
||||||
stream, waitFn, err := l.conf.Start(tinyproxyCtx)
|
stream, waitFn, err := l.conf.Start(tinyproxyCtx)
|
||||||
@@ -100,6 +152,8 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
err := waitFn() // blocking
|
err := waitFn() // blocking
|
||||||
waitError <- err
|
waitError <- err
|
||||||
}()
|
}()
|
||||||
|
stayHere := true
|
||||||
|
for stayHere {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
l.logger.Warn("context canceled: exiting loop")
|
l.logger.Warn("context canceled: exiting loop")
|
||||||
@@ -112,10 +166,22 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
tinyproxyCancel()
|
tinyproxyCancel()
|
||||||
<-waitError
|
<-waitError
|
||||||
close(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
|
case err := <-waitError: // unexpected error
|
||||||
tinyproxyCancel()
|
tinyproxyCancel()
|
||||||
close(waitError)
|
close(waitError)
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tinyproxyCancel() // repetition for linter only
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user