-
-
Notifications
You must be signed in to change notification settings - Fork 388
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(publicip): rework run loop and fix restarts
- Clearing IP data on VPN disconnection clears file - More efficient partial updates - Fix loop exit - Validate settings before updating
- Loading branch information
Showing
21 changed files
with
307 additions
and
379 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package publicip | ||
|
||
import "github.com/qdm12/gluetun/internal/models" | ||
|
||
// GetData returns the public IP data obtained from the last | ||
// fetch. It is notably used by the HTTP control server. | ||
func (l *Loop) GetData() (data models.PublicIP) { | ||
l.ipDataMutex.RLock() | ||
defer l.ipDataMutex.RUnlock() | ||
return l.ipData | ||
} | ||
|
||
// ClearData is used when the VPN connection goes down | ||
// and the public IP is not known anymore. | ||
func (l *Loop) ClearData() (err error) { | ||
l.ipDataMutex.Lock() | ||
defer l.ipDataMutex.Unlock() | ||
l.ipData = models.PublicIP{} | ||
|
||
l.settingsMutex.RLock() | ||
filepath := *l.settings.IPFilepath | ||
l.settingsMutex.RUnlock() | ||
return persistPublicIP(filepath, "", l.puid, l.pgid) | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,172 @@ | ||
package publicip | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/netip" | ||
"sync" | ||
"time" | ||
|
||
"github.com/qdm12/gluetun/internal/configuration/settings" | ||
"github.com/qdm12/gluetun/internal/constants" | ||
"github.com/qdm12/gluetun/internal/loopstate" | ||
"github.com/qdm12/gluetun/internal/models" | ||
"github.com/qdm12/gluetun/internal/publicip/state" | ||
"github.com/qdm12/gluetun/internal/publicip/ipinfo" | ||
) | ||
|
||
type Loop struct { | ||
statusManager *loopstate.State | ||
state *state.State | ||
// Objects | ||
// State | ||
settings settings.PublicIP | ||
settingsMutex sync.RWMutex | ||
ipData models.PublicIP | ||
ipDataMutex sync.RWMutex | ||
// Fixed injected objets | ||
fetcher Fetcher | ||
logger Logger | ||
// Fixed settings | ||
// Fixed parameters | ||
puid int | ||
pgid int | ||
// Internal channels and locks | ||
start chan struct{} | ||
running chan models.LoopStatus | ||
stop chan struct{} | ||
stopped chan struct{} | ||
updateTicker chan struct{} | ||
backoffTime time.Duration | ||
userTrigger bool | ||
// runCtx is used to detect when the loop has exited | ||
// when performing an update | ||
runCtx context.Context //nolint:containedctx | ||
runCancel context.CancelFunc | ||
runTrigger chan<- struct{} | ||
updateTrigger chan<- settings.PublicIP | ||
updatedResult <-chan error | ||
runDone <-chan struct{} | ||
// Mock functions | ||
timeNow func() time.Time | ||
} | ||
|
||
const defaultBackoffTime = 5 * time.Second | ||
|
||
func NewLoop(fetcher Fetcher, logger Logger, | ||
settings settings.PublicIP, puid, pgid int) *Loop { | ||
start := make(chan struct{}) | ||
running := make(chan models.LoopStatus) | ||
stop := make(chan struct{}) | ||
stopped := make(chan struct{}) | ||
updateTicker := make(chan struct{}) | ||
return &Loop{ | ||
settings: settings, | ||
fetcher: fetcher, | ||
logger: logger, | ||
puid: puid, | ||
pgid: pgid, | ||
timeNow: time.Now, | ||
} | ||
} | ||
|
||
statusManager := loopstate.New(constants.Stopped, start, running, stop, stopped) | ||
state := state.New(statusManager, settings, updateTicker) | ||
func (l *Loop) String() string { | ||
return "public ip loop" | ||
} | ||
|
||
return &Loop{ | ||
statusManager: statusManager, | ||
state: state, | ||
// Objects | ||
fetcher: fetcher, | ||
logger: logger, | ||
puid: puid, | ||
pgid: pgid, | ||
start: start, | ||
running: running, | ||
stop: stop, | ||
stopped: stopped, | ||
updateTicker: updateTicker, | ||
userTrigger: true, | ||
backoffTime: defaultBackoffTime, | ||
timeNow: time.Now, | ||
func (l *Loop) Start(_ context.Context) (_ <-chan error, err error) { | ||
l.runCtx, l.runCancel = context.WithCancel(context.Background()) | ||
runDone := make(chan struct{}) | ||
l.runDone = runDone | ||
runTrigger := make(chan struct{}) | ||
l.runTrigger = runTrigger | ||
updateTrigger := make(chan settings.PublicIP) | ||
l.updateTrigger = updateTrigger | ||
updatedResult := make(chan error) | ||
l.updatedResult = updatedResult | ||
|
||
go l.run(l.runCtx, runDone, runTrigger, updateTrigger, updatedResult) | ||
|
||
return nil, nil //nolint:nilnil | ||
} | ||
|
||
func (l *Loop) run(runCtx context.Context, runDone chan<- struct{}, | ||
runTrigger <-chan struct{}, updateTrigger <-chan settings.PublicIP, | ||
updatedResult chan<- error) { | ||
defer close(runDone) | ||
|
||
timer := time.NewTimer(time.Hour) | ||
defer timer.Stop() | ||
_ = timer.Stop() | ||
timerIsReadyToReset := true | ||
lastFetch := time.Unix(0, 0) | ||
|
||
for { | ||
select { | ||
case <-runCtx.Done(): | ||
return | ||
case <-runTrigger: | ||
case <-timer.C: | ||
timerIsReadyToReset = true | ||
case partialUpdate := <-updateTrigger: | ||
var err error | ||
timerIsReadyToReset, err = l.update(partialUpdate, lastFetch, timer, timerIsReadyToReset) | ||
updatedResult <- err | ||
continue | ||
} | ||
|
||
result, exit := l.fetchIPData(runCtx) | ||
if exit { | ||
return | ||
} | ||
|
||
message := "Public IP address is " + result.IP.String() | ||
message += " (" + result.Country + ", " + result.Region + ", " + result.City + ")" | ||
l.logger.Info(message) | ||
|
||
l.ipDataMutex.Lock() | ||
l.ipData = result.ToPublicIPModel() | ||
l.ipDataMutex.Unlock() | ||
|
||
filepath := *l.settings.IPFilepath | ||
err := persistPublicIP(filepath, result.IP.String(), l.puid, l.pgid) | ||
if err != nil { // non critical error, which can be fixed with settings updates. | ||
l.logger.Error(err.Error()) | ||
} | ||
|
||
lastFetch = l.timeNow() | ||
timerIsReadyToReset = l.updateTimer(*l.settings.Period, lastFetch, timer, timerIsReadyToReset) | ||
} | ||
} | ||
|
||
func (l *Loop) fetchIPData(ctx context.Context) (result ipinfo.Response, exit bool) { | ||
// keep retrying since settings updates won't change the | ||
// behavior of the following code. | ||
const defaultBackoffTime = 5 * time.Second | ||
backoffTime := defaultBackoffTime | ||
for { | ||
var err error | ||
result, err = l.fetcher.FetchInfo(ctx, netip.Addr{}) | ||
if err == nil { | ||
return result, false | ||
} | ||
|
||
exit = ctx.Err() != nil | ||
if exit { | ||
return result, true | ||
} | ||
|
||
l.logger.Error(fmt.Sprintf("%s - retrying in %s", err, backoffTime)) | ||
select { | ||
case <-ctx.Done(): | ||
return result, true | ||
case <-time.After(backoffTime): | ||
} | ||
const backoffTimeMultipler = 2 | ||
backoffTime *= backoffTimeMultipler | ||
} | ||
} | ||
|
||
func (l *Loop) StartSingleRun() { | ||
l.runTrigger <- struct{}{} | ||
} | ||
|
||
func (l *Loop) UpdateWith(partialUpdate settings.PublicIP) (err error) { | ||
select { | ||
case l.updateTrigger <- partialUpdate: | ||
select { | ||
case err = <-l.updatedResult: | ||
return err | ||
case <-l.runCtx.Done(): | ||
return l.runCtx.Err() | ||
} | ||
case <-l.runCtx.Done(): | ||
// loop has been stopped, no update can be done | ||
return l.runCtx.Err() | ||
} | ||
} | ||
|
||
func (l *Loop) Stop() (err error) { | ||
l.runCancel() | ||
<-l.runDone | ||
return l.ClearData() | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.