Compare commits

..

3 Commits

Author SHA1 Message Date
Quentin McGaw b00279bc61 hotfix(portforwarding): allow setting ports when no custom code is running 2026-07-29 06:06:09 +00:00
Matt Van Horn df3c9fa668 fix(healthcheck): prevent race condition on the healthchecker (#3400)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Quentin McGaw <quentin.mcgaw@gmail.com>

Thanks also to @arturhoo for also trying to resolve this issue in #3410
2026-07-29 04:22:20 +02:00
Kobi Hikri 257787ce15 feat(ci): attach provenance and SBOM attestations to the published image (#3415) 2026-07-28 21:51:19 -04:00
6 changed files with 28 additions and 14 deletions
+6 -4
View File
@@ -85,13 +85,13 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v7
- uses: actions/setup-go@v6
with:
go-version-file: ./devrun/go.mod
- run: go test ./...
working-directory: ./devrun
- uses: actions/setup-go@v7
- uses: actions/setup-go@v6
with:
go-version-file: ./ci/go.mod
- run: go test ./...
@@ -114,7 +114,7 @@ jobs:
- run: docker build -t qmcgaw/gluetun .
- name: Setup Go for CI utility
uses: actions/setup-go@v7
uses: actions/setup-go@v6
with:
go-version-file: ci/go.mod
@@ -162,7 +162,7 @@ jobs:
security-events: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v7
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
- uses: github/codeql-action/init@v4
@@ -241,3 +241,5 @@ jobs:
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
tags: ${{ steps.meta.outputs.tags }}
push: true
provenance: mode=max
sbom: true
-6
View File
@@ -8,7 +8,6 @@ import (
"net"
"net/netip"
"strings"
"sync"
"time"
"github.com/qdm12/gluetun/internal/healthcheck/dns"
@@ -24,7 +23,6 @@ type Checker struct {
icmpTargetIPs []netip.Addr
smallCheckType string
startupOnFail bool
configMutex sync.Mutex
icmpNotPermitted *bool
@@ -55,8 +53,6 @@ func NewChecker(logger Logger) *Checker {
func (c *Checker) SetConfig(tlsDialAddrs []string, icmpTargets []netip.Addr,
smallCheckType string, startupOnFail bool,
) {
c.configMutex.Lock()
defer c.configMutex.Unlock()
c.tlsDialAddrs = tlsDialAddrs
c.icmpTargetIPs = icmpTargets
c.smallCheckType = smallCheckType
@@ -166,10 +162,8 @@ func (c *Checker) Stop() error {
}
func (c *Checker) smallPeriodicCheck(ctx context.Context) error {
c.configMutex.Lock()
icmpTargetIPs := make([]netip.Addr, len(c.icmpTargetIPs))
copy(icmpTargetIPs, c.icmpTargetIPs)
c.configMutex.Unlock()
tryTimeouts := []time.Duration{
5 * time.Second,
5 * time.Second,
+3 -1
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"slices"
"strings"
"sync"
)
@@ -60,7 +61,8 @@ func (s *Service) SetPortsForwarded(ctx context.Context, ports []uint16) (err er
s.portMutex.Lock()
defer s.portMutex.Unlock()
if s.settings.PortForwarder != nil {
hasPortForwardingCodeRunning := !strings.HasSuffix(s.settings.PortForwarder.Name(), "[not supported]")
if hasPortForwardingCodeRunning {
return errors.New("setting port forwarded at runtime is not supported with internally running port forwarding code")
}
+7
View File
@@ -45,6 +45,7 @@ type Loop struct {
start <-chan struct{}
running chan<- models.LoopStatus
userTrigger bool
healthDone <-chan struct{}
// Internal constant values
backoffTime time.Duration
}
@@ -70,6 +71,11 @@ func NewLoop(vpnSettings settings.VPN, ipv6SupportLevel netlink.IPv6SupportLevel
statusManager := loopstate.New(constants.Stopped, start, running, stop, stopped)
state := state.New(statusManager, vpnSettings)
// Initialize healthDone channel to a closed channel so that the first time
// we call <-l.healthDone it does not block
healthDone := make(chan struct{})
close(healthDone)
return &Loop{
statusManager: statusManager,
state: state,
@@ -98,6 +104,7 @@ func NewLoop(vpnSettings settings.VPN, ipv6SupportLevel netlink.IPv6SupportLevel
stop: stop,
stopped: stopped,
userTrigger: true,
healthDone: healthDone,
backoffTime: defaultBackoffTime,
}
}
+1 -1
View File
@@ -55,7 +55,7 @@ func newNoPortForwarder(providerName string) *noPortForwarder {
}
func (n *noPortForwarder) Name() string {
return n.providerName
return n.providerName + " [not supported]"
}
func (n *noPortForwarder) PortForward(context.Context, pfutils.PortForwardObjects) (
+11 -2
View File
@@ -81,6 +81,7 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
_, _ = l.dnsLooper.ApplyStatus(ctx, constants.Running)
<-l.healthDone // make sure the health checker is stopped before restarting it
icmpTargetIPs := l.healthSettings.ICMPTargetIPs
if len(icmpTargetIPs) == 1 && icmpTargetIPs[0].IsUnspecified() {
icmpTargetIPs = []netip.Addr{data.serverIP}
@@ -104,7 +105,12 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
// Start collecting health errors asynchronously, since
// we should not wait for the code below to complete
// to start monitoring health and auto-healing.
go l.collectHealthErrors(ctx, loopCtx, healthErrCh)
// We keep track of when this goroutine is done with the healthDone
// channel to avoid a race condition where the health checker is stopped
// after being reconfigured and restarted, instead of before.
healthDone := make(chan struct{})
l.healthDone = healthDone
go l.collectHealthErrors(ctx, loopCtx, healthDone, healthErrCh)
err = l.publicip.RunOnce(ctx)
if err != nil {
@@ -140,7 +146,10 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
}
}
func (l *Loop) collectHealthErrors(ctx, loopCtx context.Context, healthErrCh <-chan error) {
func (l *Loop) collectHealthErrors(ctx, loopCtx context.Context,
done chan<- struct{}, healthErrCh <-chan error,
) {
defer close(done)
var previousHealthErr error
for {
select {