mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-24 16:41:30 +02:00
111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
// Package netstate tracks OS-reported network availability for the client.
|
|
//
|
|
// A State instance is owned by the platform integration (e.g. the Android or
|
|
// iOS bindings, fed from ConnectivityManager callbacks or NWPathMonitor) and
|
|
// is injected into the connection retry loops (management, signal, relay,
|
|
// peer guards and the top-level connect loop), which consult it to avoid
|
|
// burning CPU and battery on reconnect attempts while the device has no
|
|
// network at all (e.g. airplane mode), and to reset their backoff as soon as
|
|
// the network returns.
|
|
//
|
|
// Consumers hold a *State that may be nil — every non-mobile platform leaves
|
|
// it unset. The read methods are safe on a nil receiver: they report online
|
|
// and never block, so consumers behave as if this package did not exist.
|
|
package netstate
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// State holds the OS-reported network availability. The zero value is not
|
|
// usable; create instances with New.
|
|
type State struct {
|
|
mu sync.Mutex
|
|
online bool
|
|
changed chan struct{}
|
|
}
|
|
|
|
// New creates a State that starts online. Platforms without network tracking
|
|
// pass a nil *State instead: the read methods treat nil as always online and
|
|
// never block, so consumers need no nil guards.
|
|
func New() *State {
|
|
return &State{
|
|
online: true,
|
|
changed: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// Set records whether the OS reports any usable network. Transitions wake up
|
|
// all Wait callers immediately. Unlike the read methods, Set is not nil-safe:
|
|
// it is only for the platform owner that created the State with New.
|
|
func (s *State) Set(online bool) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if s.online == online {
|
|
return
|
|
}
|
|
s.online = online
|
|
close(s.changed)
|
|
s.changed = make(chan struct{})
|
|
log.Infof("OS network availability changed: online=%t", online)
|
|
}
|
|
|
|
// IsOnline reports whether the OS reports at least one usable network. On a
|
|
// nil receiver — no State injected — it reports online.
|
|
func (s *State) IsOnline() bool {
|
|
if s == nil {
|
|
return true
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.online
|
|
}
|
|
|
|
// Changed returns a channel closed on the next availability transition, for
|
|
// callers that already own a select loop and cannot block in Wait. Re-read it
|
|
// after every fire: each transition installs a fresh channel. On a nil
|
|
// receiver — no State injected — it returns nil, which blocks forever in a
|
|
// select, so the caller simply never observes a transition.
|
|
func (s *State) Changed() <-chan struct{} {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.changed
|
|
}
|
|
|
|
// Wait blocks while the network is offline. It reports whether it had to
|
|
// wait, so callers can reset their backoff after an outage. It returns early
|
|
// with the context error when ctx is done. On a nil receiver — no State
|
|
// injected — it returns immediately.
|
|
func (s *State) Wait(ctx context.Context) (bool, error) {
|
|
if s == nil {
|
|
return false, nil
|
|
}
|
|
waited := false
|
|
for {
|
|
s.mu.Lock()
|
|
if s.online {
|
|
s.mu.Unlock()
|
|
return waited, nil
|
|
}
|
|
ch := s.changed
|
|
s.mu.Unlock()
|
|
|
|
if !waited {
|
|
waited = true
|
|
log.Debugf("network is offline, pausing connection attempts")
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return waited, ctx.Err()
|
|
case <-ch:
|
|
}
|
|
}
|
|
}
|