Commit Graph

1402 Commits

Author SHA1 Message Date
Zoltan Papp
89c6e84a41 [client, ios] Fix context cancellation during restart (#7329)
* fix(mobile): stop the client synchronously so a restart cannot inherit a cancelled context

Original finding
----------------
A user reported that leaving home and switching from wifi to cellular killed
all Internet traffic until NetBird was turned off. A debug bundle captured the
failure (iOS, CLI 0.75.0, self-hosted management, generated 2026-08-18 01:17;
the incident is at 2026-08-17 22:37:38-51 UTC).

The bundle shows the whole sequence:

  22:37:38.255  management sync stream drops (keepalive ACK timeout)
  22:37:43.670  Swift: "Network type changed: wifi -> cellular" -> schedules a
                restart with a 1s debounce
  22:37:44.737  Go: "ensuring wg interface is removed, Netbird engine context
                cancelled" - engineCtx dies, every peer gets context canceled
  22:37:49.910  iface.go:238 "failed to remove WireGuard interface utun6:
                timeout when waiting for interface utun6 to be removed"
                -> the teardown stretches out for ~5s
  22:37:50.710  Swift: "restartClient: starting client", needsLogin=false
                (so this is NOT a login expiry)
  22:37:51.013  Go: connect.go:476 "exiting client retry loop due to
                unrecoverable error: context canceled" - the OLD run dies here
  22:37:51.333  Go: grpc.go:135 "failed creating connection to Management
                Service: context canceled" - the NEW start, 2ms after the old
                run finally exited
  22:37:51.334  Swift: "restartClient: start failed" -> widget disconnected
  then nothing for 15 minutes

The tunnel stayed installed with no engine behind it, so every packet was
black-holed. status.txt, generated ~14 hours later, still reads Management:
Disconnected / Signal: Disconnected / Peers count: 0/0 - the client never
recovered on its own.

Root cause
----------
Client.Stop() cancelled a shared ctxCancel field and returned immediately,
without waiting for the run loop to exit. The Swift stop{} completion handler
therefore fired while the Go teardown was still running (stretched out by the
utun6 removal timeout), and the start that followed landed on a context that
the outgoing run was about to cancel.

Two further paths wrote the same shared field. IsLoginRequired() and
LoginForMobile() each overwrote c.ctxCancel, so any call to them during a live
session discarded the running engine's cancel function. restartClient() calls
needsLoginCached() on exactly this path.

Changes
-------
- Stop() now drives the stored ConnectClient: ConnectClient.Stop() cancels the
  run context and blocks on runExited, so the caller's completion handler only
  fires once the run loop has really finished. The ctxCancel path stays as a
  fallback for when no ConnectClient exists yet (e.g. during LoginForMobile).
- Run() owns its cancel in a local variable, so a concurrent call that
  overwrites the shared field can no longer cancel this run's context through
  the deferred cleanup.
- IsLoginRequired() and LoginForMobile() use local cancels and leave the shared
  field alone. LoginForMobile's cancel moves into the deferred cleanup of the
  goroutine that outlives the call, so the OAuth token wait is not cut short.
- The Android SDK gets the same treatment. The structural defect is identical
  there, but the trigger is absent: Android has no automatic engine restart on
  a network type change, and no interface-removal timeout to stretch the
  teardown. This part is preventive, not a fix for an observed failure.

* fix(mobile): do not let a superseded startup publish its client

Review found a window the previous commit left open. Run stored its cancel
function and only published the ConnectClient later, after loading config and
constructing the client. A Stop landing inside that window found no
ConnectClient, cancelled the run and returned immediately. A new Run could then
publish its own client, and the cancelled older run — still executing — would
overwrite it with a client that was already being torn down. The next Stop
stopped that stale client and left the live one running with nothing tracking
it.

Runs now carry a generation. Run claims one before doing any work and publishes
its client only while the generation is still current; a superseded run returns
without touching the shared state. Stop bumps the generation, so any startup
still in flight is invalidated, then cancels it and waits for the run to exit
before returning (20s cap so a wedged teardown cannot block the caller
forever).

setState is gone: publishState replaces it at both call sites on each platform.

* fix(ios): add a non-waiting Stop for callers on a deadline

Stop now waits for the run loop to exit, which is what a restart needs but
wrong for stopTunnel: iOS gives NEPacketTunnelProvider only a few seconds
there before it kills the extension, and the wait can run to its 20s cap.
Waiting past the deadline earns a SIGKILL, so the next start inherits a dirty
state instead of the orderly shutdown the wait was meant to buy.

StopWithoutWait tears the client down and returns. ConnectClient.Stop blocks on
runExited with no cap of its own, so the non-waiting path runs it detached
rather than only skipping the runDone wait.

Android keeps a single blocking Stop: it has no equivalent deadline.

* fix(mobile): guard the run lifecycle with a single lock

Stop and beginRun each touched the same lifecycle state across two locks in
sequence: take stateMu, release it, then take ctxCancelLock. A run starting in
that gap installed its own cancel before Stop reached it, so Stop cancelled the
fresh run and left its own target running — the same class of defect this branch
exists to fix, this time in the locking rather than the state.

ctxCancel moves into the stateMu group, and both sides take their snapshot in
one critical section. ctxCancelLock then guarded nothing and is gone.

* fix(mobile): drop the run-generation machinery for a serialized lifecycle

The platform callers (Swift/Kotlin) always stop before starting and coalesce
restarts, so the generation counter guarded against call patterns that cannot
occur. Replace it with a single-run contract:

- startRun refuses a second Run while the previous one has not exited
- finishRun clears the published state on every exit path, including errors
- Stop cancels and waits for the run loop with a bounded timeout; it no
  longer calls ConnectClient.Stop, whose wait is unbounded
- concurrent Stops wait on the same exit channel instead of returning early
- a superseded startup no longer reports a clean nil exit

* revert(android): drop the run lifecycle changes

Android does not have the defect this PR fixes. On ux/ios-style-redesign the
EngineRestarter is gone: network changes are handled as events instead of an
engine restart, so nothing stops the client and starts it again.

The remaining stop() callers are all final teardowns on the main thread with a
framework deadline - the stop-engine broadcast receiver, onDestroy, onRevoke and
the binder's stopEngine. A Stop that waits for the run loop would risk an ANR
there for a race that cannot occur, so the fix stays iOS-only.

* fix(ios): make loginComplete race-free

The OAuth goroutine spawned by LoginForMobile sets loginComplete after the
call has returned to Swift, while the Swift side polls IsLoginComplete and
later calls ClearLoginComplete from its own thread. The plain bool made all
three unsynchronized: the store may never become visible to the poller, and
a Clear racing the store can be lost, leaving a stale true that makes the
next login look already complete.

Switch the field to atomic.Bool. It is a standalone flag rather than part of
the run lifecycle that stateMu guards, and it has to stay readable while the
login goroutine is still in flight.
2026-08-28 09:33:08 +02:00
Riccardo Manfrin
6620219939 [client] Add catch-all NRPT rule when NetBird is the primary DNS resolver (#7071)
* [client] Add catch-all NRPT rule when NetBird is the primary DNS resolver

* Remove obvious comments

* Install the catch-all rule where the adapter's DNS is set

addDNSSetupForAll makes us the peer's main DNS forwarder, and the catch-all NRPT
rule is the other half of that same job: without it the adapter's NameServer only
adds one more resolver to the set Windows queries in parallel. Having the two in
one place says that, where a separate block at the end of applyDNSConfig read as
an afterthought.

The block could not simply move up: removeDNSMatchPolicies deletes the catch-all
key too, so installing the rule before it ran would have had the rule deleted
moments later. The cleanup now runs first, which is what it was always for - it
clears what the previous apply installed before this one installs anything - and
keeps being unconditional, so a leftover rule from an earlier run cannot survive
into a config that no longer wants it.

* Name the escape hatch after the behaviour it restores

NB_DISABLE_DNS_CATCHALL_NRPT described the mechanism it switches off. What an
operator reaching for it wants is the behaviour they had before, so name it that:
NB_USE_LEGACY_DNS_RESOLUTION, matching NB_USE_LEGACY_ROUTING, the only other
legacy switch in the client.

Not NB_WIN_LEGACY_FULL_TUNNEL_DNS_RESOLVE, as first suggested: the rule follows a
primary nameserver group, not a full tunnel, and putting FULL_TUNNEL in a public
variable name would carry that confusion for as long as the variable lives. No
OS prefix either, since nothing else in the client has one and this switch is
inert anywhere but Windows by construction.

Behaviour and default are unchanged: the catch-all rule is on unless the variable
says otherwise.

* Exempt .local from the catch-all rule

RFC 6762 reserves .local for multicast DNS and says unicast resolvers must not
answer for it. The catch-all rule hands it to us anyway, we forward it to
whatever upstream the primary nameserver group points at, and the answer comes
back NXDOMAIN for hosts that do exist - printers, NAS boxes, anything
announcing itself on the link. Confirmed on a Win11Pro VM: laptop.local resolves
with the client down and returns "Nome DNS inesistente" with it up, and the
client log shows the query arriving on the catch-all handler and being forwarded
to 1.1.1.1.

An NRPT rule that names a namespace and lists no servers is an exemption: the
DNS client resolves those names as it would with no rule at all. What that looks
like in the registry is not what it sounds like. Writing no server value and
clearing ConfigOptions produces a rule Windows treats as a no-op - it never
appears in Get-DnsClientNrptPolicy -Effective and the catch-all keeps the query.
The value has to be present and empty, with ConfigOptions still 0x8: the flag
says the server list is the meaningful part of the rule, and an empty list then
means "no server, resolve normally". Verified both encodings on the VM.

Installed together with the catch-all, since without one nothing captures .local
in the first place, and removed with it.

Exclusivity is unaffected elsewhere, and a more specific rule still wins - a
match domain under .local keeps resolving through NetBird, which is what a
legacy Active Directory domain named corp.local needs. Verified separately that
a match domain does take precedence over the catch-all: declaring fritz.box
against the local router restored laptop.fritz.box while the catch-all was in
force.

* Treat the root namespace as a match domain, not a special case

The catch-all had a function, a registry key and a call site of its own, which
made it look like a different mechanism. It is not: "." is an NRPT namespace like
any other, it just happens to match every name. So it goes into the match domain
list, and addDNSMatchPolicy writes it along with the rest — batching, GPO
variant, volatile keys and cleanup all come for free.

The .local exemption stays a rule of its own, and not for symmetry: it is the one
rule with a different server list, an empty one. Putting it in the same Name
value would give it our resolver and exempt nothing.

Windows expands a rule's Name value into one effective namespace each, so a rule
carrying {.example.com, .} still shows both as separate rows in
Get-DnsClientNrptPolicy -Effective. Nothing is lost for diagnosis by dropping the
dedicated key.

Suggested by Vik in review.

* Do not report a failed NRPT cleanup as success

removeRegistryKeyFromDNSPolicyConfig returned nil for every OpenKey error, so a
permission or registry failure was indistinguishable from a key that was never
there. Cleanup then reported success while the rule stayed in force — which is
how a rule outlives the interface it points at and keeps sending every query to
an address that no longer answers.

Distinguish the two, the way listNRPTRuleKeys already does for the policy store
root: a missing key is nothing to do, anything else reaches the caller.

restoreHostDNS now propagates that error instead of logging it. applyDNSConfig
keeps logging on purpose: there we are about to write fresh rules over whatever
survived, while restore is the path where a rule left behind is the whole
problem.

Also addresses review nits on the tests: doc comments on the two added cases,
reported Close and DeleteKey errors so a failed cleanup cannot contaminate the
next registry test, and a context message on the exemption's namespace assertion.
2026-08-27 16:18:49 +02:00
Viktor Liu
63c26be72f [client] Add local Prometheus metrics endpoint (#6689) 2026-08-27 13:53:06 +02:00
Pascal Fischer
e06c17cf59 [management] network map from nmap data type (#6919)
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
Co-authored-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-27 11:28:05 +02:00
Viktor Liu
473392a935 [client] Tolerate a still-locked updater binary when cleaning up after an update (#7286) 2026-08-26 20:03:56 +02:00
Viktor Liu
0bd1147ff0 [client] Keep NetBird traffic out of third-party fwmark rules (#7314) 2026-08-26 20:03:40 +02:00
dmitri-netbird
0a9ce7f797 [client] fix a flake in TestResolver_ConcurrentStaleHitsCollapseRefresh test (#7326)
* fix a flake in TestResolver_ConcurrentStaleHitsCollapseRefresh test

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* use testify's eventually asserts

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

---------

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-26 12:51:19 +02:00
Viktor Liu
7e8b4e1417 [client, proxy] Remove lazy connection exclusions and run Rosenpass on the embedded proxy (#6763)
* Run lazy connection manager for rosenpass peers

* Treat forward-target peers as normal lazy connections

* Run Rosenpass in permissive mode on the embedded proxy
2026-08-26 12:43:48 +02:00
Zoltan Papp
ed7d4de999 [client, ios] Migrate switft profile manager to go (#6528)
* [client] Add iOS NetBirdSDK profile manager binding

Mirror the Android profile manager in the iOS gomobile binding so the
core's ID-based profilemanager.ServiceManager owns profile state on iOS
too, instead of a parallel Swift reimplementation.

Adds client/ios/NetBirdSDK/profile_manager.go (//go:build ios): an
ID-based ProfileManager wrapping ServiceManager with iOS-specific path
handling (default profile at the container-root netbird.cfg, others as
profiles/<id>.json) and a gomobile-friendly API: List/Add/Switch/Rename/
Logout/Remove plus active config/state path accessors. The default
profile keeps the reserved "default" id and is never assigned a hex id.

* fix(ios): preserve profile name when saving config during auth

NewAuth built a fresh in-memory config from only the management URL, so
the SSO/setup-key save (DirectWriteOutConfig) overwrote the profile config
file the profile manager had just written, wiping the display name to ""
and forcing the UI to fall back to the profile ID. Load the existing config
when present and override only the management URL, keeping the name and keys.

* [client] Extract the mobile profile manager into client/mobile

The Android and iOS gomobile bindings carried two near-identical copies of
the profile manager. Move the shared implementation into a new client/mobile
package and reduce both bindings to thin adapters that only translate to
gomobile-friendly types (gomobile binds per package, so the Profile /
ProfileArray wrappers have to stay platform-side).

Also bring the account-email layer over to the shared package: an SSO login
records the account under <stem>.account.json so the next login can pass it
as an OIDC login_hint. Logout keeps it, profile removal drops it. The suffix
deliberately differs from .state.json, which the engine's state manager owns
in the same directory on mobile.

Adds profilemanager.Prefs (namespaced per-profile preference store) and its
cleanup in ServiceManager.RemoveProfile, exposed through the shared manager
as ProfilePrefs.
2026-08-26 09:42:50 +02:00
Viktor Liu
51095cb986 [client, management] Support per-peer lazy connection state and default proxy peers to lazy (#6762)
* Support per-peer lazy connection state and default proxy peers to lazy

* Classify forward targets from incoming config in lazy exclusion

* Set IsUserspaceBind mock so lazy manager starts in engine test

* Skip lazy exclude reconciliation when the set is unchanged

* Keep cached lazy flag when a sync carries no peer config
2026-08-26 09:33:51 +02:00
Viktor Liu
ccf8f43cb1 [client] Ask the OS for privileges when a guarded SSH setting is changed (#7066) 2026-08-25 20:15:16 +02:00
Zoltan Papp
15fff4c164 [client] Sweep connections on network loss via a shared netevents manager (#7254)
Losing the last network only flipped the availability state: the dead management, signal and relay sockets stayed silently connected until their own timeouts, so the client kept reporting Connected with no network at all.

Introduce client/netevents with a Manager that ties the availability state, the connection sweeper and the status recorder together, and move the netstate and netsweep packages under it (netsweep renamed to sweep). SetNetworkAvailable(false) now also sweeps the registered connections so their owners redial and the listener reaches the NoNetwork state.

The Android and iOS bindings own a Manager instance and inject it through the constructors; consumers hold the concrete *Manager whose nil zero value reports always-online and never sweeps, with interfaces kept only as parameter contracts. The relay guard settle wait moved into the Manager as WaitSettled, removing the netevents import from the relay package.
2026-08-25 18:43:19 +02:00
Zoltan Papp
a08f7f63f4 [client] Create GUI windows on demand and destroy them on close (#7096)
The main and Settings windows were created at startup and kept alive hidden on close, so an idle tray held two webview processes for surfaces the user may never open. Both are now built on first show and destroyed on close, which takes the idle footprint on macOS from ~160 MB to ~74 MB.

The WindowManager owns creation: it rebuilds the main window on the next show and hands out live pointers, since a stored one goes stale. Every show is deferred until the frontend reports it has rendered, so a freshly created window is never on screen empty, with a timeout so a frontend that never reports cannot strand a window hidden.
2026-08-25 11:40:31 +02:00
Viktor Liu
5fc191167d [client] Revert declaring multi-buffer support for the loopback XDP program (#7303) 2026-08-24 13:47:41 +02:00
Viktor Liu
7f03a2e86f [client] Hold a peer offer or answer that arrives before the handshaker starts listening (#7255) 2026-08-24 10:54:11 +02:00
Viktor Liu
335adfe9c3 [client] Move the PCP implementation to the go-nat fork (#7282) 2026-08-21 14:15:10 +02:00
Viktor Liu
79a06720b6 [client] Add a lazy-connection override and device name reporting to the WASM client (#7276) 2026-08-21 10:55:52 +02:00
Viktor Liu
00243b28bc [client] Add missing anonymization and SSH privilege translations (#7269) 2026-08-21 10:05:33 +02:00
Viktor Liu
e4b8bf39d2 [client] Fix staticcheck findings from the updated golangci-lint (#7266)
* Fix staticcheck findings reported by the updated golangci-lint

* Skip the receive error log when the local context is done
2026-08-20 16:50:50 +02:00
Viktor Liu
917ad880e3 [client] Rename TURN-specific wg proxy naming to relayed connections (#7231) 2026-08-20 15:07:51 +02:00
dmitri-netbird
a144e8c144 [client, management] switch to go.uber.org/mock (#7253)
* switch to go.uber.org/mock/gomock

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* updated go:generate commands + regenerated mocks

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* update go:generate mockgen commands

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* removed duplicate import

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

* fix go:generate

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>

---------

Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io>
2026-08-20 11:53:19 +02:00
Zoltan Papp
9efa3c6579 [client] Start the restarted UI with the user's environment block (#7245)
The updater runs as LocalSystem and started netbird-ui via
CreateProcessAsUser with a nil environment, so the UI inherited the
SYSTEM environment (USERPROFILE, APPDATA pointing at systemprofile)
while running under the user's token. The WebView2-based UI exits
immediately in that state, so the UI never came back after an update.

Build the environment from the user's token with CreateEnvironmentBlock
and pass it to CreateProcessAsUser.
2026-08-19 12:19:10 +02:00
Zoltan Papp
77791b5858 [client] Report network addresses on Android for posture checks (#7235)
Android never reported its local network interfaces, so PeerNetworkRange posture checks could not be evaluated: NetworkAddresses always arrived empty.

net.Interfaces() is unusable on Android 11+ (SELinux blocks netlink), so the addresses are parsed from the interface description the host app already provides via stdnet.ExternalIFaceDiscover. The MAC filter is skipped, mirroring #5906
for iOS, since Android does not expose MACs either and nothing reads Mac server side.
2026-08-19 11:47:57 +02:00
Zoltan Papp
ad98b99fc5 [client] Stop the UI before a silent Windows update and suppress the installer reboot (#7209)
Stop the UI before a silent Windows update and suppress the installer reboot

On silent MSI updates msiexec could reboot the machine on its own. The running UI holds a lock on its own exe, and since msiexec runs as LocalSystem it cannot close the interactive user's UI via Restart Manager, so the MSI scheduled the
file replacement for the next reboot and marked the install restart-required.

Terminate netbird-ui.exe before launching the installer and wait until its image file is released; the existing deferred restart brings it back after the install on every exit path
Run msiexec with /norestart REBOOT=ReallySuppress so it never reboots on its own
Treat exit codes 3010/1641 as success with a warning instead of a failure

---------

Co-authored-by: Viktor Liu <viktor@netbird.io>
2026-08-19 11:41:24 +02:00
Zoltan Papp
6d223042eb [client] Clear stale installer result before starting update (#7204)
* [client] Clear stale installer result before starting update

The installer result file could survive a previous update attempt (e.g.
when the updater wrote it after the restarted daemon already ran its
startup check). A new install attempt left the old file in place, so the
GUI progress window's first GetInstallerResult poll read the outdated
result: a stale success made the GUI quit mid-install, which cancelled
the TriggerUpdate context and aborted the artifact verification; a stale
error surfaced a bogus failure dialog for a succeeding update.

Remove any leftover result file at the start of RunInstallation, before
the download begins, so result watchers only see the current attempt's
outcome.

* [client] Align stale-result warning with log message style
2026-08-19 10:27:06 +02:00
Zoltan Papp
070a0a7bf1 [client, android] Handle network changes without restarting the engine (#7144)
On network changes the client restarted the whole engine. That is heavy-handed and slow: it tears down working state to recover from a transition the engine could handle itself. This replaces the restart with proper network event handling.

Suspend the retry loops while no network is available. Instead of burning through backoff intervals against an unreachable network, the reconnection loops park until the OS reports a usable network again.

Reconnect immediately on a network switch. When the OS hands us a new network, connections bound to the old one are swept and re-dialed right away, rather than waiting for a timeout to notice they are dead.
2026-08-19 10:05:12 +02:00
Zoltan Papp
ecfbd686b8 [client, android] Expose ssh functionality for Android (#7156)
Adds an SSHClient gomobile binding so the Android app can run an SSH session over the tunnel with a PTY, exposed through a listener interface for the in-app terminal.

Server type is auto-detected from the SSH banner, which selects the auth path: JWT device-code flow, NetBird key, or a regular server (NetBird key first, then password). Host keys are verified against the peer registry for NetBird servers and trust-on-first-use for regular ones.
2026-08-18 18:49:13 +02:00
Viktor Liu
6210399e65 [client] Declare multi-buffer support for the loopback XDP program (#7230) 2026-08-17 13:10:12 +02:00
Viktor Liu
939b686d05 [client] Delete NRPT rules by enumerating the registry instead of a rule count (#7195) 2026-08-17 12:52:17 +02:00
Zoltan Papp
16544dbc58 [client] Pass stored email as login hint from UI and keep it on logout (#7199)
* [client] Pass stored email as login hint from UI and keep it on logout

Follow the CLI pattern: the Wails UI now reads the account email from the
user-owned profile state file and passes it as the OIDC login_hint on login
and session extend, since the daemon-side fallback runs as root and cannot
see the user's state file. Logout no longer deletes the stored email, so a
later login preselects the account at the IdP; profile removal remains the
operation that deletes it.

* [client] Log ignored profile lookup errors in extend-session hint fallback
2026-08-15 11:21:57 +02:00
Zoltan Papp
f458c1f265 [client] Skip IPv6 route tests when the default nexthop is unusable (#7212)
* [client] Skip IPv6 route tests when the default nexthop is unusable

ensureIPv6DefaultRoute treated a successful netlink RouteAdd as proof that
a usable IPv6 nexthop exists. Installing ::/0 via loopback can succeed while
the kernel still rejects that nexthop for a concrete prefix, which surfaced
on ubuntu22/20260810.260 runners as:

    add route to table: netlink add route: invalid argument

Probe the resolved nexthop by installing and removing a discard-prefix route
through the same code path the tests use, and skip when it fails. EEXIST
means the nexthop already carries a route, so it counts as usable.

* [client] Probe the IPv6 nexthop through raw netlink

addRoute swallows EAFNOSUPPORT and EOPNOTSUPP via isOpErr, so a nil return
did not prove the probe route was installed. Call netlink directly so an
unsupported operation skips the test instead of passing as usable.
2026-08-15 10:13:06 +02:00
Viktor Liu
ec6f1b8c27 [client] Rank Windows route candidates by combined route and interface metric (#7210) 2026-08-15 09:06:22 +02:00
Zoltan Papp
2cfe14d7ec [client] Keep account email on Android logout, drop it on profile removal (#7200)
Align Android logout semantics with the desktop UI and CLI: logging out no
longer deletes the stored account email, so the next login passes it as the
OIDC login_hint and the IdP preselects the account. Removing the profile is
now the operation that deletes the email; previously RemoveProfile left the
account file behind, which the fixed-name default profile would have
inherited on recreation.
2026-08-14 18:13:52 +02:00
Eduard Gert
85dd335836 [client] Add CI check for translation key parity (#6852)
English (en) is the source of truth for UI translation keys; the other
nine locales rely on runtime English fallback for any missing key, so a
gap never surfaces in CI. Add a dependency-free Node check that fails
when any locale declared in _index.json does not carry the exact same
key set as en (missing or orphaned keys), wired into a dedicated
UI Translations workflow that runs on locale changes.

Also close the one existing gap the check found: ja was missing
daemon.outdated.download ("Download Latest").

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-14 10:57:11 +02:00
Viktor Liu
5544761b47 [client] Add Windows DNS configuration to the debug bundle (#7196) 2026-08-13 20:07:37 +02:00
Viktor Liu
e290769df1 [client] Take the graphical session answer from the caller instead of the daemon environment (#7187) 2026-08-13 10:28:34 +02:00
Zoltan Papp
6b69f5c05d [client] Remove installer registry handlers for autostart Run keys (#7183)
The NSIS installer deleted HKLM/HKCU CurrentVersion\Run values it never
writes, which matches common AV heuristics for unwanted Run-key
manipulation and is suspected to contribute to Windows Defender and
third-party antivirus false positives on the installer.

Drop all autostart registry deletions from both the install and
uninstall sections so the installer only touches keys it creates
itself. Cleanup of the legacy machine-wide entry written by old
installers is left to documentation.

Extends the approach of the closed PR #6735, which only removed the
per-user deletion on uninstall.
2026-08-12 17:35:01 +02:00
Viktor Liu
db9fcf39ef [client] Gate IPv6 forwarding on overlay v6 and preserve host RA acceptance (#6221) 2026-08-12 16:07:00 +02:00
Lamera
52faa202b2 [client] fall back to per-IP ACL rules when ipset is unavailable (#6332) 2026-08-12 14:37:48 +02:00
Viktor Liu
f5ce0bc65a [client] Fix macOS DNS panic on malformed scutil output (#7180) 2026-08-12 13:25:12 +02:00
Viktor Liu
052cf5a748 [client] Derive Windows SSH privilege checks from the token and group membership (#6966) 2026-08-11 18:16:37 +02:00
Viktor Liu
95a458801c [doc] Point translation contributions at Crowdin (#7161) 2026-08-11 16:02:09 +02:00
Viktor Liu
f9abe2727f [client] Do not misroute WireGuard packets to the STUN handler (#7059) 2026-08-10 13:41:09 +02:00
Viktor Liu
5584f8ef0a [client] Add strict anonymization level and MAC anonymization to debug bundles (#7102) 2026-08-10 11:27:20 +02:00
Riccardo Manfrin
eb619fc7e3 [client] disambiguate the connection_type metric tag (#7043)
## Describe your changes

`recordConnectionMetrics` mapped only `conntype.Relay` to `relay` and
let a `default` branch
record everything else as `ice`. That silently included `ICETurn` — an
ICE connection through
a TURN server, which
[`conn.isRelayed`](https://github.com/netbirdio/netbird/blob/main/client/internal/peer/conn.go#L788-L795)
itself counts as relayed — and `None`, the transient state set when the
relay drops

([conn.go:632](https://github.com/netbirdio/netbird/blob/main/client/internal/peer/conn.go#L632))
or the peer state is reset
([conn.go:757](https://github.com/netbirdio/netbird/blob/main/client/internal/peer/conn.go#L757)).
Both were reported as direct peer-to-peer, so the `ice` share overstated
direct connections on
every platform.

The mapping now lists every priority explicitly and emits `ice_p2p`,
`ice_turn`, `relay` or
`unknown`. The new values deliberately do not reuse `ice` to avoid
ambuguity.


## Issue ticket number and link

No public issue. Found while reviewing the first production sample of
client metrics: 38% of iOS
connection events were tagged `ice` on a platform that forces relay by
default, which traced back
to the `default` branch at
[client/internal/peer/conn.go#L963-L968](https://github.com/netbirdio/netbird/blob/main/client/internal/peer/conn.go#L963-L968).

## Stack

<!-- branch-stack -->

### Checklist
- [x] Is it a bug fix
- [ ] Is a typo/documentation fix
- [ ] Is a feature enhancement
- [ ] It is a refactor
- [x] Created tests that fail without the change (if possible)

> By submitting this pull request, you confirm that you have read and
agree to the terms of the [Contributor License
Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md).

## Documentation
Select exactly one:

- [] I added/updated documentation for this change
- [x] Documentation is **not needed** for this change (explain why)

Internal metrics documentation only, in
`client/internal/metrics/infra/README.md`: the four
`connection_type` values with their derivation, and a note that pre-fix
`ice` samples are not
comparable with `ice_p2p`. No public API, CLI or configuration change,
so no netbirdio/docs PR.

### Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:

N/A

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

* **New Features**
* Connection metrics now distinguish direct peer-to-peer, TURN-assisted,
relay, and unknown connection types.
  * Metrics include clearer connection and peer identification details.

* **Documentation**
* Updated connection timing metric values, traffic semantics, priority
behavior, and historical data guidance.

* **Bug Fixes**
* Unset or unrecognized connection priorities are no longer incorrectly
classified as peer-to-peer.
* Unknown-transport metrics are skipped to prevent misleading connection
data.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-07 17:11:05 +02:00
Riccardo Manfrin
f63fd21e0c [client] peer: re-arm the WireGuard watcher after a lazy wake (#7091)
## Describe your changes

The Conn struct is reused across lazy-connection deactivate/activate.
Close
cancels the WireGuard watcher (via wgWatcherCancel, and ctxCancel also
tears
down its context) but left conn.wgWatcher pointing at the stopped
instance.
enableWgWatcherIfNeeded skips while conn.wgWatcher is non-nil, so the
next Open
never started a fresh watcher: once a lazy connection had idled and
woken, the
peer ran with no watcher at all — no WireGuard handshake-timeout
detection and
none of the escalation that depends on it.

Clear conn.wgWatcher and conn.wgWatcherCancel in Close so the next Open
re-arms
a fresh watcher.

## Issue ticket number and link

<!--
Required for anything that changes behavior. Link the issue (or the
validated
discussion it came from) that the NetBird team already agreed on. See

https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#ticket-first-pr-second
-->

## Stack

<!-- branch-stack -->

### Checklist
- [X] Is it a bug fix
- [ ] Is a typo/documentation fix
- [ ] Is a feature enhancement
- [ ] It is a refactor
- [ ] Created tests that fail without the change (if possible)
- [ ] I ran and tested this change locally — I did not rely on CI to
find out whether it works
- [ ] This PR has a single purpose (not a fix + refactor + feature in
one)
- [ ] This change is a trivial fix, **OR** it links an issue the NetBird
team agreed on beforehand. Changes to the public API, gRPC protocols,
functionality behavior, CLI / service flags, or new features always need
that agreement first. See
[CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#ticket-first-pr-second).

> By submitting this pull request, you confirm that you have read and
agree to the terms of the [Contributor License
Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md).

## Documentation
Select exactly one:

- [ ] I added/updated documentation for this change
- [X] Documentation is **not needed** for this change (explain why)

### Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved connection cleanup by fully releasing WireGuard watcher
resources when a connection closes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-07 17:01:08 +02:00
Viktor Liu
9a05a1c698 [client] Reword the firewalld package comment (#7081) 2026-08-06 20:36:13 +02:00
Zoltan Papp
1e2a7aa571 [client] Add a UI setting to stay connected after quitting (#7078)
Quitting the GUI from the tray always sent a Down RPC, dropping the VPN
connection with it. Some users want the connection to survive the UI.
2026-08-06 12:56:27 +02:00
Zoltan Papp
98f8a24108 [client] Ship a legacy GTK3 UI package for distros without WebKitGTK 6.0 (#7040)
Ship a legacy GTK3 UI package for distros without WebKitGTK 6.0
2026-08-06 12:13:20 +02:00
Zoltan Papp
2a61eac047 [client] Fix Linux tray right-click opening the main window (#7039)
## Describe your changes

On SNI hosts that report icon clicks via Activate (KDE Plasma, Waybar),
Wails fired the left-click handler on every dbusmenu 'opened' event, so
a right click opened the tray menu and immediately raised the main
window, which stole focus and closed the menu.

	Host                           Left click              Right click
KDE Plasma, Waybar main window (Activate) menu (host-rendered)
	GNOME Shell + AppIndicator     menu only               menu only
	Minimal WMs via XEmbed host    main window (Activate)  XEmbed GTK popup

Point the wails/v3 replace at the netbirdio fork (v3.0.0-beta.3 plus the
fix): once the host has sent Activate/SecondaryActivate, a menu open no
longer fires the click handler, while AppIndicator-only hosts that
signal clicks solely via 'opened' keep the old behavior.


## Issue ticket number and link

<!--
Required for anything that changes behavior. Link the issue (or the
validated
discussion it came from) that the NetBird team already agreed on. See

https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#ticket-first-pr-second
-->

## Stack

<!-- branch-stack -->

### Checklist
- [x] Is it a bug fix
- [ ] Is a typo/documentation fix
- [ ] Is a feature enhancement
- [ ] It is a refactor
- [ ] Created tests that fail without the change (if possible)
- [ ] I ran and tested this change locally — I did not rely on CI to
find out whether it works
- [ ] This PR has a single purpose (not a fix + refactor + feature in
one)
- [ ] This change is a trivial fix, **OR** it links an issue the NetBird
team agreed on beforehand. Changes to the public API, gRPC protocols,
functionality behavior, CLI / service flags, or new features always need
that agreement first. See
[CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#ticket-first-pr-second).

> By submitting this pull request, you confirm that you have read and
agree to the terms of the [Contributor License
Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md).

## Documentation
Select exactly one:

- [ ] I added/updated documentation for this change
- [x] Documentation is **not needed** for this change (explain why)

### Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Chores

* Updated underlying application components to support compatibility and
ongoing maintenance.
* Clarified Linux tray interaction documentation, including
platform-specific left- and right-click behavior and menu activation.
* No user-facing features, workflow changes, or visual updates are
included.
* Existing Linux tray behavior remains unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-04 17:36:04 +02:00