- `HEALTH_TARGET_ADDRESS` to replace `HEALTH_ADDRESS_TO_PING` - Remove `github.com/go-ping/ping` dependency - Dial TCP the target address, appending `:443` if port is not set
104 lines
2.3 KiB
Go
104 lines
2.3 KiB
Go
// Package healthcheck defines the client and server side of the built in healthcheck.
|
|
package healthcheck
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"time"
|
|
)
|
|
|
|
func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
|
|
defer close(done)
|
|
|
|
s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait)
|
|
|
|
for {
|
|
previousErr := s.handler.getErr()
|
|
|
|
const healthcheckTimeout = 3 * time.Second
|
|
healthcheckCtx, healthcheckCancel := context.WithTimeout(
|
|
ctx, healthcheckTimeout)
|
|
err := s.healthCheck(healthcheckCtx)
|
|
healthcheckCancel()
|
|
|
|
s.handler.setErr(err)
|
|
|
|
if previousErr != nil && err == nil {
|
|
s.logger.Info("healthy!")
|
|
s.vpn.healthyTimer.Stop()
|
|
s.vpn.healthyWait = *s.config.VPN.Initial
|
|
} else if previousErr == nil && err != nil {
|
|
s.logger.Info("unhealthy: " + err.Error())
|
|
s.vpn.healthyTimer.Stop()
|
|
s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait)
|
|
}
|
|
|
|
if err != nil { // try again after 1 second
|
|
timer := time.NewTimer(time.Second)
|
|
select {
|
|
case <-ctx.Done():
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
return
|
|
case <-timer.C:
|
|
case <-s.vpn.healthyTimer.C:
|
|
s.onUnhealthyVPN(ctx)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Success, check again in 5 seconds
|
|
const period = 5 * time.Second
|
|
timer := time.NewTimer(period)
|
|
select {
|
|
case <-ctx.Done():
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
return
|
|
case <-timer.C:
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) healthCheck(ctx context.Context) (err error) {
|
|
// TODO use mullvad API if current provider is Mullvad
|
|
|
|
address, err := makeAddressToDial(s.config.TargetAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
const dialNetwork = "tcp4"
|
|
connection, err := s.dialer.DialContext(ctx, dialNetwork, address)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot dial: %w", err)
|
|
}
|
|
|
|
err = connection.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("cannot close connection: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func makeAddressToDial(address string) (addressToDial string, err error) {
|
|
host, port, err := net.SplitHostPort(address)
|
|
if err != nil {
|
|
addrErr := new(net.AddrError)
|
|
ok := errors.As(err, &addrErr)
|
|
if !ok || addrErr.Err != "missing port in address" {
|
|
return "", fmt.Errorf("cannot split host and port from address: %w", err)
|
|
}
|
|
host = address
|
|
const defaultPort = "443"
|
|
port = defaultPort
|
|
}
|
|
address = net.JoinHostPort(host, port)
|
|
return address, nil
|
|
}
|