Commit Graph
932 Commits
Author SHA1 Message Date
riccardom 011294bc74 pqkem: concurrency tests (recovery + race)
- RecoversViaResignalAfterDataPathBreak: a data-path rotation that can no longer
  converge raises OnRekeyFailed, and re-bootstrapping over signalling resyncs both
  peers on a fresh PSK even while the data path stays broken.
- ConcurrentRekeysNoRace: hammers the single-lock state machine with concurrent
  rotation clocks from many goroutines (run with -race) and asserts no split-brain
  via a final deterministic bootstrap.
2026-08-07 10:24:01 +02:00
riccardom 07eb3030d2 pqkem: recover from persistent rekey failure by re-bootstrapping over signal
OnRekeyFailed now re-runs the KEM bootstrap over Signal (conn.RequestReoffer ->
handshaker.SendOffer) instead of only logging: a fresh signalling offer starts a new
exchange that overwrites the stalled PSK on both sides, resyncing after a persistent
data-path desync. Chosen over a responder-side awaitingAck revert (which fights the
confirm-less ack timing) and a full tunnel teardown (heavier). The tunnel stays up on
the previous PSK meanwhile since Signal is independent of the broken data path.
2026-08-07 10:24:01 +02:00
riccardom 1d510bad8e Discriminate initial from rekey failure 2026-08-07 10:24:01 +02:00
riccardom 8a203e7e4e pqkem: strict (fail-closed) mode + wire status Quantum resistance
Strict mode (NB_PQ_MLKEM_STRICT, default off) closes the initial PQ-vulnerable
window (NET-1408): when enabled, conn.presharedKey programs a per-conn random
sentinel PSK until the ML-KEM exchange derives the real one, so no session can form
on a non-PQ key (the real PSK is pushed via SetPresharedKey once it converges).
Default stays opportunistic.

Also surface PQ status: the peer 'Quantum resistance' flag (RosenpassEnabled) is now
true when an ML-KEM PSK has been derived for the peer, not only for Rosenpass.
2026-08-07 10:24:01 +02:00
riccardom 2625b3d5ee pqkem: rotate PSK in kernel mode instead of skipping
The idle-gate reads LastActivities, which only tracks per-peer data in userspace;
in kernel mode it is empty, so the gate treated every kernel peer as idle and
disabled data-path rotation entirely. Detect the bind via IsUserspaceBind and, in
kernel mode, report zero activity age (always 'active') so rotation runs on every
rekey. Lazy back-to-idle is already limited in kernel; the eBPF WG-activity
detection will later supply a real signal that excludes handshake/pqkem traffic.
2026-08-07 10:24:01 +02:00
riccardom 27ac3ca9f6 pqkem: derive PSK with HKDF-SHA256
Replace the raw SHA-256 concat combiner with HKDF-SHA256 (crypto/hkdf, Go 1.24):
IKM = ML-KEM_ss || X25519_ss (draft-ietf-tls-ecdhe-mlkem order), salt = the
domain-separation label, info = full transcript (offer || answer) || canonicalised
peer identities. Keeps the transcript + identity binding while using a proper KDF.
2026-08-07 10:24:01 +02:00
riccardom 3ad2989556 Don't rotate PQ keys if data path is idle for ~90s (less than a WG handhshake time 2026-08-07 10:24:01 +02:00
riccardom c9a66a7fbc Adds log tracepoints
- Add a trace slog level (NB_PQ_MLKEM_LOG_LEVEL=trace) and move the verbose
  per-exchange lifecycle logs (offer/answer/PSK/ack/rotation) to it, so debug
  stays quiet and troubleshooting is opt-in.
- Stop logging the raw preshared key; drop the temporary pqkem-dbg OnRemoteOffer/
  OnRemoteAnswer probes.
- Demote the per-handshake conn log to trace.
2026-08-07 10:24:01 +02:00
riccardom e447011dc3 Fixes second answer dropped (the one carrying the PQ KEM data)
Prevents dropping concurrent answer / offer carrying the PQ ML-KEM data
2026-08-07 10:24:01 +02:00
riccardom cec3ca229e Renames SetRemotePort to SetRemoteAddr 2026-08-07 10:24:01 +02:00
riccardom 7ceb319107 pqkem: clock data-path PSK rotation from WireGuard handshakes
Source OnDataPathRekeyed from the WGWatcher's per-handshake callback
(onWGCheckSuccess), which fires only on a fresh handshake, and OnDataPathDown
from the handshake-timeout path. A fresh handshake clocks the next chained
KEM exchange pushed over the data-path UDP transport.
2026-08-07 10:24:01 +02:00
riccardom 5f9c67cdaa pqkem: register data-path endpoint from signalling
Learn the peer's data-path endpoint from the signalling offer/answer: its WG
overlay IP combined with the advertised pq UDP port (SetRemotePort -> AddPeer).
Registering here is safe before the tunnel is up because sends only ever fire
once it is (clocked by OnDataPathRekeyed). RemovePeer is wired at peer teardown
(engine.removePeer), not on transient disconnect.
2026-08-07 10:24:01 +02:00
riccardom 8b7c105b5e pqkem: apply derived PSK at WG peer-config time (pull) + keep push for rekey 2026-08-07 10:24:01 +02:00
riccardom cfea741ab4 pqkem: carry KEM offer/answer over the signalling exchange 2026-08-07 10:24:01 +02:00
riccardom e6cc446877 pqkem: dedicated slog logger via NB_PQ_MLKEM_LOG_LEVEL 2026-08-07 10:24:01 +02:00
riccardom 09664e84aa Homogeneous logs prefix 2026-08-07 10:24:01 +02:00
riccardom 4d2037ccc9 Bit of renaming
peer -> peerAddrs
have types for remoteID and localID
t.Close log error
Manager SetTransport -> Start
2026-08-07 10:24:01 +02:00
riccardom f0eb275575 Typo 2026-08-07 10:24:01 +02:00
riccardom 341ed699d9 Race fix 2026-08-07 10:24:01 +02:00
riccardom 11d0ad13bb Makes Transport just a UDP socket.
Manager owns maps for remoteID <-> remote UDP addr
Engine talks to manager only
2026-08-07 10:24:01 +02:00
riccardom 5d8d87c050 Adds transport 2026-08-07 10:24:01 +02:00
riccardom b9d83de47c Communicate the port over the signal exchange 2026-08-07 10:24:01 +02:00
riccardom 5b808d1c2b Ensure iface is up and with overlay ip assigned to get a valid UDP port 2026-08-07 10:24:01 +02:00
riccardom c5759b086d Adds real callback setter for PSK on ready 2026-08-07 10:24:01 +02:00
riccardom f852f15e42 Initializes PQ ML-KEM manager 2026-08-07 10:24:01 +02:00
riccardom 6fc11f5fa6 Adds no-op Transports and callbacks 2026-08-07 10:24:01 +02:00
riccardom 8fa2a41888 Added enabled env var 2026-08-07 10:24:01 +02:00
riccardom 717c9297d9 Adds MLKEM Payload placeholder to client internals 2026-08-07 10:24:01 +02:00
riccardom 3059e7d141 Invert order of keys as per draft 2026-08-07 10:24:01 +02:00
riccardom 551145def6 Removes confirm. Uses next offer to deliver confirmation/ack of previous round
We clock the next Offer initiation to the OnDataPathRekeyed, so we have 2 minutes
ahead of us to do our attempts and stuff before to give up.
On failure, we will know because we will not receive a new answer.. but more importantly
the wg handshake will fail :D
2026-08-07 10:24:01 +02:00
riccardom 40016ae082 Leave signal offer/answer as a pull/push operation not as an actual transport 2026-08-07 10:24:01 +02:00
riccardom 9dbc7401c9 Assume two transports: initial "signal" (control plane) one (no data path established yet) + data path one
Define OnDataPathRekeyed event to transition from control plane path to data plane path over the WG tunnel.

Keep confirm ALWAYS on NEW established WG tunnel (posthandshake with rekeying). We keep an active method
irrelevant of the WG handshake (we might decide that the indirect wg handshake is sufficient in the future).

Optimistic commit on responder(when sending answer), while on initiator we set it on getting the answer
2026-08-07 10:24:01 +02:00
riccardom abe28c41ee Epurate wg refs 2026-08-07 10:24:01 +02:00
riccardom 56a8681b76 Collapse Driver and Manager in one.
- Have just one manager => one lock
 - Session state is needed in driver to => we have it available now.
 - Isomorphically align to rosenpass components and functionality

File	Role	                                  rosenpass equivalent
kem.go	primitive pure X25519MLKEM768	          crypto.go/handshake
message.go	Offer/Answer/Confirm + Encode/Decode  messages.go
manager.go	Manager stateful, single lock	      server logic
callbacks.go	WGCallbackHandler (seam output)	  Handler
Transport (interfaccia)	seam trasporto pluggable  Conn
2026-08-07 10:24:01 +02:00
riccardom 9c6b00125b [squash] isInitial and answered can be inferred without state variables 2026-08-07 10:24:01 +02:00
riccardom 090f97d3c4 Manages convergence 2026-08-07 10:24:01 +02:00
riccardom b0f5731699 Models reattempts 2026-08-07 10:24:01 +02:00
riccardom e69b9ccc18 Reuse answer, don't calculate again 2026-08-07 10:24:01 +02:00
riccardom cb3285a79b Adds driver to glue together manager and outside world 2026-08-07 10:24:01 +02:00
riccardom 14df9d75a3 Defines event callbacks 2026-08-07 10:24:01 +02:00
riccardom b23b757380 Admits possible errors on Encode 2026-08-07 10:24:01 +02:00
riccardom 744508f742 Bench key material boilerplate time/allocs
CGO_ENABLED=1 go test ./client/internal/pqkem/ -run '^$' -bench . -benchmem 2>&1 | grep -E "Benchmark|ns/op|PASS|ok" | head -20

BenchmarkX25519Keygen-14    	   33795	     34966 ns/op	     224 B/op	       5 allocs/op
BenchmarkX25519ECDH-14      	   33855	     33973 ns/op	      32 B/op	       1 allocs/op
BenchmarkMLKEMKeygen-14     	   21817	     67778 ns/op	    8200 B/op	       2 allocs/op
BenchmarkMLKEMEncaps-14     	   29918	     43235 ns/op	    1216 B/op	       2 allocs/op
BenchmarkMLKEMDecaps-14     	   26048	     56291 ns/op	      64 B/op	       2 allocs/op
PASS
ok  	github.com/netbirdio/netbird/client/internal/pqkem	9.751s
Shell cwd was reset to /home/riccardo/Desktop/Personal/netbirdio/netbird
2026-08-07 10:24:01 +02:00
riccardom d808ecf8dd Pure mechanics of manager 2026-08-07 10:24:01 +02:00
riccardom f5b350a812 Messages definition 2026-08-07 10:24:01 +02:00
riccardom acb8970346 ML-KEM encapsulate/decapsulate module 2026-08-07 10:24:01 +02:00
riccardom 465e977ce1 Without the comment 2026-08-07 10:02:24 +02:00
riccardom 4daf7da383 [client] peer: re-arm the WireGuard watcher after a lazy wake
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.
2026-08-07 09:42:27 +02:00
Zoltan Papp 075b319fb3 [client, android] Pull fresh TUN settings on Android rebuild (#6991)
## Describe your changes

Pull fresh TUN settings on Android rebuild instead of push

The Android TUN rebuild consumed state pushed through notifications and
a Java-side snapshot, and both sources were unreliable. The DNS
search-domain notifier fired OnNetworkChanged with an empty string,
which the rebuild handler treated as the new route list, so any search
domain change rebuilt the TUN with zero routes and cut all tunnel
traffic. The rebuild also reused the search domains cached at the last
establish, so search domain updates never reached the TUN at runtime.

Make the notification a pure trigger and let the Java side pull a fresh
snapshot instead. Expose GetTunSettings on the Android SDK client: it
returns the current TUN route ranges, derived on demand by the route
manager from the client routes, the exit-node selection and the fake IP
blocks, together with the DNS search domains. The route notifier keeps
only its last-announced baseline to suppress triggers for unchanged
syncs; the TUN route state is owned by the route manager. SearchDomains
now locks the DNS server mutex since the pull arrives from a Java
thread.

Requires the matching android-client change that switches recreateTUN to
the pull API.

## Issue ticket number and link

## 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)
- [ ] This change does **not** modify the public API, gRPC protocols,
functionality behavior, CLI / service flags, or introduce a new feature
— **OR** I have discussed it with the NetBird team beforehand (link the
issue / Slack thread in the description). See
[CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first).

> 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

- **New Features**
  - Added access to current TUN route ranges and DNS search domains.
- TUN settings are returned in a mobile-friendly format for easier
integration.

- **Improvements**
  - Route changes are detected and synchronized more reliably.
- Current routing information now reflects active routes, including
supported fake-IP ranges.
- Simplified network initialization for more consistent startup
behavior.

- **API Changes**
- Removed the obsolete network-map retrieval method from the management
client interface.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 18:23:02 +02:00
Riccardo Manfrin d29bc23bb7 [client] launch macOS GUI as the logged-in user after install/update (#6962)
## Describe your changes

In unattended installs/updates there may be no logged-in user, so
there's no context to start the GUI (nor anyone to see it).

The bug is the GUI being started in the wrong user context / inheriting
the wrong `$HOME` (OS mechanics aside).

Today the GUI is started by a per-user LaunchAgent, i.e. on behalf of
the user who logs in — no login ⇒ no GUI.

The patch aligns to this: it launches the GUI on behalf of the logged-in
console user if one exists, otherwise it delegates the launch to the
per-user LaunchAgent at next login.

Additionally it logs when default UI settings are applied.

Note (small caveat): the LaunchAgent auto-starts the GUI at login only
once it's been registered — which happens on the first GUI launch in the
user's context. On an MDM/unattended fresh install done with no user
logged in (where the user has never run the GUI before), they may need
to start it manually once; it self-registers from then on.

## Issue ticket number and link

No public issue — reported internally (community report on Slack: macOS
advanced-view + onboarding reset on every update, esp. via MDM/Munki).
Buggy line on main:
https://github.com/netbirdio/netbird/blob/dd2bdc0de3aa14dd14dc90611c69c420bb3d3eb2/release_files/darwin_pkg/postinstall#L33

## 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)

> 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 macOS installer / GUI-launch behavior. No public API, CLI, or
configuration change: the fix only changes the user context the desktop
GUI is launched in after a pkg install/update.

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

N/A

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/netbirdio/codesmith/netbird/pr/6962"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img
alt="View with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a>
<a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1787923973&installation_model_id=427504&pr_number=6962&repository=netbirdio%2Fnetbird&return_to=https%3A%2F%2Fgithub.com%2Fnetbirdio%2Fnetbird%2Fpull%2F6962&signature=5226bbc7986fc60eb8b4ab77e98ac17a6534864763fc6407048bcbbb1550394b"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you
need. Autofix is disabled.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->

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

- **Bug Fixes**
- Improved macOS installation and updater UI launching to occur only
when an active, valid GUI console session is detected.
- Prevented UI launches during unattended/system, root, or
login-window-related installs.
- Ensured the app is launched in the correct console-user context, and
skips cleanly when username/UID resolution fails.

- **Improvements**
- Added clearer informational logging when the UI preferences file is
not found and default preferences are used.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-03 16:28:10 +02:00
Zoltan Papp 6f42636514 [client] Android - Serialize Android tunnel reconfiguration callbacks (#6990)
## Describe your changes

The Android route notifier and the DNS search-domain notifier both
delivered OnNetworkChanged from a fire-and-forget goroutine per update.
Two updates in quick succession could reach the Java side reordered: the
TUN rebuild handler applies them in arrival order and compares against
the last applied parameters, so a stale route set delivered last won as
the final TUN state. This is the same reordering hazard fixed for iOS in
#6454.

Wrap the Android network change listener into the shared tunnelnotifier
FIFO introduced in #6870, the same way RunOniOS does, and deliver both
notifiers synchronously into it. Enqueueing is non-blocking, a single
delivery goroutine preserves order, and calls into Java never overlap.

Also stop hasRouteDiff from sorting the notifier's shared route slices
in place; compare sorted copies instead.

## Issue ticket number and link

## 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)
- [ ] This change does **not** modify the public API, gRPC protocols,
functionality behavior, CLI / service flags, or introduce a new feature
— **OR** I have discussed it with the NetBird team beforehand (link the
issue / Slack thread in the description). See
[CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first).

> 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/__
2026-08-03 15:03:15 +02:00