Commit Graph

7230 Commits

Author SHA1 Message Date
Thomas Eizinger
407a67cb40 docs: add changelog entries for several issues (#9113)
As part of going through the changes since the last Client and Gateway
relies, I noticed that for several of the things we fixed, it might be
worth adding changelog entries.
2025-05-13 13:35:02 +00:00
Thomas Eizinger
45924eb90b fix(connlib): ignore scopes for IPv6 link-local addresses (#9115)
To send UDP DNS queries to upstream DNS servers, we have a
`UdpSocket::handshake` function that turns a UDP socket into a
single-use object where exactly one datagram is expected from the
address we send a message to. The way this is enforced is via an
equality check.

It appears that this equality check fails if users run an upstream DNS
server on a link-local IPv6 address within a setup that utilises IPv6
scopes. At the time when we receive the response, the packet has already
been successfully routed back to us so we should accept it, even if we
didn't specify a scope as the destination address.
2025-05-13 13:33:28 +00:00
Thomas Eizinger
b8738448df refactor(connlib): forward error from source IP resolver (#9116)
In order to avoid routing loops on Windows, our UDP and TCP sockets in
`connlib` embed a "source IP resolver" that finds the "next best"
interface after our TUN device according to Windows' routing metrics.
This ensures that packets don't get routed back into our TUN device.

Currently, errors during this process are only logged on TRACE and
therefore not visible in Sentry. We fix this by moving around some of
the function interfaces and forward the error from the source IP
resolver together with some context of the destination IP.
2025-05-13 13:33:15 +00:00
Jamil
ae224384be feat(apple): Disable advanced settings if overridden (#9108)
If `authURL`, `apiURL`, or `logFilter` are set in the managed
configuration, we disable each of these fields respectively from user
editing.

If all of them are overridden, we disable the `Apply` and `Reset to
Defaults` buttons.

Related #4505
2025-05-13 04:06:05 +00:00
Thomas Eizinger
c93a3d710a fix(gui-client): don't panic during setup hook (#9112)
As part of launching the Tauri GUI client, we need to observe a specific
initialisation order. In particular, we need to wait until Tauri sends
us a `RunEvent::Ready` before we can initialise things like the tray
menu.

To make this more convenient, Tauri offers a so-called "setup hook" that
can be set on the app builder. Unfortunately, Tauri internally panics if
this provided setup-hook returns an `Err`. Removing this is tracked
upstream: https://github.com/tauri-apps/tauri/issues/12815.

Until this is fixed, we stop using this "setup hook" and instead spawn
our own task that performs this work. This task needs to wait until
Tauri is ready. To achieve that, we introduce an additional mpsc channel
that sends a notification every time we receive a `RunEvent::Ready`.
That should only happen once. We only read from the receiver once, which
is why we ignore the error on the sending side in case the receiver has
already been dropped.

Resolves: #9101
2025-05-13 04:02:42 +00:00
Thomas Eizinger
b0e20d8a41 fix(gui-client): initialise logger one layer up (#9111)
Initialising the logger as part of the `open` function causes a flaky
test in case there is already another logger initialised.

Fixes: #9096
2025-05-13 03:55:59 +00:00
Thomas Eizinger
b93c28240e chore(rust): fix features in bin-shared (#9094)
When this crate is compiled by itself, these features are required. This
doesn't show up in CI because there we compile the entire workspace and
some crate somewhere already activates these features then.
2025-05-13 03:12:59 +00:00
Jamil
14892d0f27 fix(apple): Save settings before signing out (#9104)
This fixes a small bug where settings wouldn't be saved if signed in,
and also allows errors during saving to bubble up to the user.

---------

Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-13 02:37:09 +00:00
Thomas Eizinger
87b3ed9857 ci: test new lychee action (#9107)
Helping the lychee team test some new changes.

Related: https://github.com/lycheeverse/lychee-action/pull/292
2025-05-13 01:57:55 +00:00
Jamil
7d738bc192 refactor(apple): Fix var/func scope in SettingsView (#9106)
These are incorrectly scoped. No functionality is changing.
2025-05-13 01:35:28 +00:00
Jamil
a849711525 refactor(apple): Prepare to support MDM-shadowed config (#9084)
One simple way we can tell the GUI app which configuration fields have
been overridden by MDM is to specify an `overriddenKeys` string array.
If provided, this will disable the relevant configuration from being set
/ editable in the GUI app and communicate to the user as such.

Related: #4505

---------

Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-13 00:12:17 +00:00
Jamil
4cc5963af2 fix(apple): Don't double-log errors in IPCClient (#9102)
See
https://github.com/firezone/firezone/pull/9056#discussion_r2083836491
2025-05-12 22:17:53 +00:00
Thomas Eizinger
ea0ad9d089 chore(gateway): log CLI args we got invoked with (#9089) 2025-05-12 22:10:37 +00:00
Jamil
08dee37d09 feat(apple): Poll tunnel for new configuration every 1s (#9083)
Similar to how we fetch new resources, we add a Configuration poller
that fetches new configuration every 1s. If the configuration is
unchanged, we respond to the caller with a cached copy to avoid needing
to serialize the data over IPC.

Related: #4505
2025-05-12 14:27:47 +00:00
dependabot[bot]
909e438328 build(deps): bump once_cell from 1.20.2 to 1.21.3 in /rust (#9098)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.20.2 to
1.21.3.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/matklad/once_cell/blob/master/CHANGELOG.md">once_cell's
changelog</a>.</em></p>
<blockquote>
<h2>1.21.3</h2>
<ul>
<li>Outline more initialization in <code>race</code>: <a
href="https://redirect.github.com/matklad/once_cell/pull/284">#284</a>,
<a
href="https://redirect.github.com/matklad/once_cell/pull/285">#285</a>.</li>
</ul>
<h2>1.21.2</h2>
<ul>
<li>Relax success ordering from AcqRel to Release in <code>race</code>:
<a
href="https://redirect.github.com/matklad/once_cell/pull/278">#278</a>.</li>
</ul>
<h2>1.21.1</h2>
<ul>
<li>Reduce MSRV to 1.65: <a
href="https://redirect.github.com/matklad/once_cell/pull/277">#277</a>.</li>
</ul>
<h2>1.21.0</h2>
<ul>
<li>Outline initialization in <code>race</code>: <a
href="https://redirect.github.com/matklad/once_cell/pull/273">#273</a>.</li>
<li>Add <code>OnceNonZereUsize::get_unchecked</code>: <a
href="https://redirect.github.com/matklad/once_cell/pull/274">#274</a>.</li>
<li>Add <code>OnceBox::clone</code> and
<code>OnceBox::with_value</code>: <a
href="https://redirect.github.com/matklad/once_cell/pull/275">#275</a>.</li>
<li>Increase MSRV to 1.70</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="29e3d93cf0"><code>29e3d93</code></a>
Merge pull request <a
href="https://redirect.github.com/matklad/once_cell/issues/286">#286</a>
from briansmith/b/once-ref-dry</li>
<li><a
href="a206950676"><code>a206950</code></a>
Prepare for 1.21.3 release.</li>
<li><a
href="17d4a9b433"><code>17d4a9b</code></a>
DRY <code>race::OnceRef::{get_or_try_init, set}</code>.</li>
<li><a
href="01b1d560f4"><code>01b1d56</code></a>
Merge pull request <a
href="https://redirect.github.com/matklad/once_cell/issues/285">#285</a>
from briansmith/b/once-ref-cold</li>
<li><a
href="a851cc4788"><code>a851cc4</code></a>
Mark initialization of <code>OnceRef::get_or_try_init</code> cold.</li>
<li><a
href="2447a93fbe"><code>2447a93</code></a>
Merge pull request <a
href="https://redirect.github.com/matklad/once_cell/issues/281">#281</a>
from briansmith/b/init-inner</li>
<li><a
href="c294d64f9c"><code>c294d64</code></a>
Merge pull request <a
href="https://redirect.github.com/matklad/once_cell/issues/280">#280</a>
from briansmith/b/self</li>
<li><a
href="5f0fdd427a"><code>5f0fdd4</code></a>
Merge pull request <a
href="https://redirect.github.com/matklad/once_cell/issues/283">#283</a>
from briansmith/b/cast_mut</li>
<li><a
href="899e319523"><code>899e319</code></a>
Merge pull request <a
href="https://redirect.github.com/matklad/once_cell/issues/284">#284</a>
from briansmith/b/dry-get</li>
<li><a
href="90da60ba44"><code>90da60b</code></a>
Mark initialization of <code>OnceBox::get_or_try_init</code> cold.</li>
<li>Additional commits viewable in <a
href="https://github.com/matklad/once_cell/compare/v1.20.2...v1.21.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=once_cell&package-manager=cargo&previous-version=1.20.2&new-version=1.21.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 13:31:48 +00:00
dependabot[bot]
8a8fa59330 build(deps): bump divan from 0.1.17 to 0.1.21 in /rust (#9099)
Bumps [divan](https://github.com/nvzqz/divan) from 0.1.17 to 0.1.21.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/nvzqz/divan/blob/main/CHANGELOG.md">divan's
changelog</a>.</em></p>
<blockquote>
<h2>[0.1.21] - 2025-04-09</h2>
<h3>Fixed</h3>
<ul>
<li><code>Divan::skip_exact</code> behaved incorrectly in
<code>v0.1.19</code>.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Improved handling of internal code around filters and those
responsible for
sacking the people who have just been sacked have been sacked.</li>
</ul>
<h2>[0.1.20] - 2025-04-09</h2>
<h3>Fixed</h3>
<ul>
<li><code>Divan::skip_regex</code> accidentally dropped
<a
href="https://docs.rs/regex-lite/latest/regex_lite/struct.Regex.html"><code>regex_lite::Regex</code></a>
and behaved incorrectly in <code>v0.1.19</code>.</li>
</ul>
<h2>[0.1.19] - 2025-04-09</h2>
<h3>Fixed</h3>
<ul>
<li>[<code>cargo-nextest</code>] no longer skips benchmarks with
argument parameters (<a
href="https://redirect.github.com/nvzqz/divan/issues/75">#75</a>).</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Organized positive and negative filters into a split buffer.</li>
</ul>
<h2>[0.1.18] - 2025-04-05</h2>
<h3>Added</h3>
<ul>
<li>
<p>Support for [<code>cargo-nextest</code>] running benchmarks as
tests.</p>
</li>
<li>
<p>[<code>prelude</code>] module for simplifying imports of
[<code>#[bench]</code>][bench_attr],
[<code>#[bench_group]</code>][bench_group_attr],
[<code>black_box</code>], [<code>black_box_drop</code>],
[<code>AllocProfiler</code>], [<code>Bencher</code>], and
[<code>Divan</code>].</p>
</li>
<li>
<p>Support <code>wasi</code> and <code>emscripten</code> targets.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="52f9d4983e"><code>52f9d49</code></a>
Release v0.1.21</li>
<li><a
href="5afb095486"><code>5afb095</code></a>
Fix broken <code>Divan::skip_exact</code></li>
<li><a
href="e4d112ccbc"><code>e4d112c</code></a>
Release v0.1.20</li>
<li><a
href="1d74108bbe"><code>1d74108</code></a>
Fix broken <code>Divan::skip_regex</code></li>
<li><a
href="58988fc304"><code>58988fc</code></a>
Release v0.1.19</li>
<li><a
href="f43a742d0f"><code>f43a742</code></a>
docs: Change &quot;changes&quot; to &quot;changed&quot; in
changelog</li>
<li><a
href="f348769fb4"><code>f348769</code></a>
docs: List most recent changes in changelog</li>
<li><a
href="07111fb6f8"><code>07111fb</code></a>
Abstract filters into <code>FilterSet</code></li>
<li><a
href="2c865cdf24"><code>2c865cd</code></a>
docs: Add link to <code>prelude</code> module in changelog</li>
<li><a
href="9075b9e0ed"><code>9075b9e</code></a>
fix nextest support for parameterized benches</li>
<li>Additional commits viewable in <a
href="https://github.com/nvzqz/divan/compare/v0.1.17...v0.1.21">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=divan&package-manager=cargo&previous-version=0.1.17&new-version=0.1.21)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 13:31:01 +00:00
dependabot[bot]
bce1f17753 build(deps): bump sha2 from 0.10.8 to 0.10.9 in /rust (#9100)
Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.10.8 to
0.10.9.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="82c36a428f"><code>82c36a4</code></a>
sha2: add soft-compact backend (backport of <a
href="https://redirect.github.com/RustCrypto/hashes/issues/686">#686</a>)
(<a
href="https://redirect.github.com/RustCrypto/hashes/issues/687">#687</a>)</li>
<li><a
href="c1e85ae6d5"><code>c1e85ae</code></a>
ci: remove pre-1.56 jobs and other fixes (<a
href="https://redirect.github.com/RustCrypto/hashes/issues/688">#688</a>)</li>
<li><a
href="a667dd94f9"><code>a667dd9</code></a>
skein: fix implementation for output sizes not multiple of 8 (<a
href="https://redirect.github.com/RustCrypto/hashes/issues/682">#682</a>)</li>
<li><a
href="62b6ff1c59"><code>62b6ff1</code></a>
Fix blake2 and ascon-hash CI (<a
href="https://redirect.github.com/RustCrypto/hashes/issues/521">#521</a>)</li>
<li><a
href="f3b657cfac"><code>f3b657c</code></a>
Expose belt_compress to public (<a
href="https://redirect.github.com/RustCrypto/hashes/issues/520">#520</a>)</li>
<li><a
href="9f66cb810c"><code>9f66cb8</code></a>
md4: Optimize compress to improve hash performance (<a
href="https://redirect.github.com/RustCrypto/hashes/issues/519">#519</a>)</li>
<li><a
href="70a2b62c50"><code>70a2b62</code></a>
Add simpler code snippet to the documentation (<a
href="https://redirect.github.com/RustCrypto/hashes/issues/515">#515</a>)</li>
<li><a
href="e3ab2579a0"><code>e3ab257</code></a>
Update Cargo.lock</li>
<li><a
href="c640781bc1"><code>c640781</code></a>
ascon-hash: Add Zeroize feature (<a
href="https://redirect.github.com/RustCrypto/hashes/issues/480">#480</a>)</li>
<li><a
href="cfda39f5cf"><code>cfda39f</code></a>
Update Cargo.lock</li>
<li>See full diff in <a
href="https://github.com/RustCrypto/hashes/compare/sha2-v0.10.8...sha2-v0.10.9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sha2&package-manager=cargo&previous-version=0.10.8&new-version=0.10.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 13:30:29 +00:00
Thomas Eizinger
f2e9ae51de build(nix): add GUI smoke test tools (#9086) 2025-05-12 13:07:11 +00:00
Thomas Eizinger
f965487739 chore(connlib): turn down logs for non-fatal IO errors (#9091)
Signed-off-by: Thomas Eizinger <thomas@eizinger.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-12 11:48:40 +00:00
Thomas Eizinger
4097ee0cdf chore(gui-client): only read is_finished once (#9095)
For at least 1 user, the threads shut down correctly, but we didn't seem
to have exited the loop. In
https://firezone-inc.sentry.io/issues/6335839279/events/c11596de18924ee3a1b64ced89b1fba2/?project=4508008945549312,
we can see that both flags are marked as `true` yet we still emitted the
message.

The only way how I can explain this is that the thread shut down in
between the two times we've called the `is_finished` function. To ensure
this doesn't happen, we now only read it once.

This however also shows that 5s may not be enough time for WinTUN to
shutdown. Therefore, we increase the grace period to 10s.
2025-05-12 11:47:42 +00:00
Thomas Eizinger
275e29bb07 chore(rust): silence more error logs from WinTUN (#9090) 2025-05-12 11:42:34 +00:00
Thomas Eizinger
945fed8e9d chore(phoenix-channel): downgrade log about dropped messages (#9092)
This can easily happen if we are briefly disconnected from the portal.
It is not the end of the world and not worth creating Sentry alerts for.

Originally, this was intended to be a way of detecting "bad
connectivity" but that didn't really work.
2025-05-12 11:40:40 +00:00
Thomas Eizinger
f01fd4ddf6 fix(connlib): clear pending sockets on DNS server re-creation (#9093)
Our DNS over TCP implementation uses `smoltcp` which requires us to
manage sockets individually, i.e. there is no such thing as a listening
socket. Instead, we have to create multiple sockets and rotate through
them.

Whenever we receive new DNS servers from the host app, we throw away all
of those sockets and create new ones.

The way we refer to these sockets internally is via `smoltcp`'s
`SocketHandle`. These are just indices into a `Vec` and this access can
panic when it is out of range. Normally that doesn't happen because such
a `SocketHandle` is only created when the socket is created and
therefore, each `SocketHandle` in existence should be valid.

What we overlooked is that these sockets get destroyed and re-created
when we call `set_listen_addresses` which happens when the host app
tells us about new DNS servers. In that case, sockets that we had just
received a query on and are waiting for a response have their handles
stored in a temporary `HashMap`. Attempting to send back a response for
one of those queries will then either fail with an error that the socket
is not in the right state or - worse - panic with an out of bounds error
if the previously had more listen addresses than we have now.

To fix this, we need to clear this map of pending queries every time we
call `set_listen_addresses`.
2025-05-12 11:39:59 +00:00
Thomas Eizinger
7e4fe68485 fix(connlib): take into account header overhead for GSO (#9088)
When calculating the maximum size of the UDP payload we can send in a
single syscall, we need to take into account the overhead of the IP and
UDP headers.
2025-05-12 11:36:10 +00:00
Thomas Eizinger
df463296a0 test: add context for failing dump_syms command (#9087) 2025-05-12 11:32:04 +00:00
Thomas Eizinger
656f5db1d0 ci(rust): add timeout to GUI smoke tests (#9085)
These don't have an inherent timeout so the CI job gets stuck forever.
They typically finish in about a minute.
2025-05-12 11:30:52 +00:00
Jamil
1ceccc0da0 refactor(apple): Consolidate app configuration to UserDefaults (#9056)
We are currently storing app configuration across three places:

- UserDefaults (favorite resources)
- VPN configuration (Settings)
- Disk (firezone id)

These can be consolidated to UserDefaults, which is the standard way to
store app configuration like this.

UserDefaults is the umbrella persistence store for regular app
configuration (`plist` files which are just XML dictionaries),
iCloud-synced app configuration across a user's devices, and managed app
configuration (MDM). They provide a cached, thread-safe, and
interprocess-supported mechanism for handling app configuration. We can
also subscribe to changes on this app configuration to react to changes.

Unfortunately, the System Extension ruins some of our fun because it
runs as root, and is confined to a different group container, meaning we
cannot share configuration directly between GUI and tunnel procs.

To address this, we use the tunnel process to store all vital
configuration and introduce IPC calls to set and fetch these.

Commit-by-commit review recommended, but things got a little crazy
towards the end when I realized that we can't share a single
UserDefaults between both procs.
2025-05-12 05:27:49 +00:00
Thomas Eizinger
575e974547 ci: limit docker compose parallelism (#9082) 2025-05-12 02:44:34 +00:00
Thomas Eizinger
117881adfe ci: reduce link checker concurrency (#9076)
Our link checker `lychee` doesn't appear to de-duplicate requests to the
same URL which causes 429 errors with GitHub. To workaround this, we
reduce the concurrency to 1 and activate `lychee`'s cache. This cache is
just a file on disk. We don't need to actually save this in GitHub
actions' cache because all we want is for lychee to not make a request
to same URL again in the same session.

Related: https://github.com/lycheeverse/lychee-action/issues/289
2025-05-12 02:42:37 +00:00
Jamil
537295d8a3 fix(rust): Downgrade fastest nameserver to DEBUG (#9071)
These run every minute and add a lot of noise to the logs.

```
May 11 18:21:14 gateway-z1w4 firezone-gateway[2007]: 2025-05-11T18:21:14.154Z  INFO firezone_tunnel::io::nameserver_set: Evaluating fastest nameserver ips={127.0.0.53}
May 11 18:21:14 gateway-z1w4 firezone-gateway[2007]: 2025-05-11T18:21:14.155Z  INFO firezone_tunnel::io::nameserver_set: Evaluated fastest nameserver fastest=127.0.0.53
May 11 18:22:14 gateway-z1w4 firezone-gateway[2007]: 2025-05-11T18:22:14.154Z  INFO firezone_tunnel::io::nameserver_set: Evaluating fastest nameserver ips={127.0.0.53}
May 11 18:22:14 gateway-z1w4 firezone-gateway[2007]: 2025-05-11T18:22:14.155Z  INFO firezone_tunnel::io::nameserver_set: Evaluated fastest nameserver fastest=127.0.0.53
May 11 18:23:14 gateway-z1w4 firezone-gateway[2007]: 2025-05-11T18:23:14.153Z  INFO firezone_tunnel::io::nameserver_set: Evaluating fastest nameserver ips={127.0.0.53}
May 11 18:23:14 gateway-z1w4 firezone-gateway[2007]: 2025-05-11T18:23:14.155Z  INFO firezone_tunnel::io::nameserver_set: Evaluated fastest nameserver fastest=127.0.0.53
May 11 18:24:14 gateway-z1w4 firezone-gateway[2007]: 2025-05-11T18:24:14.154Z  INFO firezone_tunnel::io::nameserver_set: Evaluating fastest nameserver ips={127.0.0.53}
May 11 18:24:14 gateway-z1w4 firezone-gateway[2007]: 2025-05-11T18:24:14.155Z  INFO firezone_tunnel::io::nameserver_set: Evaluated fastest nameserver fastest=127.0.0.53
May 11 18:25:14 gateway-z1w4 firezone-gateway[2007]: 2025-05-11T18:25:14.153Z  INFO firezone_tunnel::io::nameserver_set: Evaluating fastest nameserver ips={127.0.0.53}
```
2025-05-12 01:58:17 +00:00
Thomas Eizinger
5566f1847f refactor(rust): move crates into a more sensical hierarchy (#9066)
The current `rust/` directory is a bit of a wild-west in terms of how
the crates are organised. Most of them are simply at the top-level when
in reality, they are all `connlib`-related. The Apple and Android FFI
crates - which are entrypoints in the Rust code are defined several
layers deep.

To improve the situation, we move around and rename several crates. The
end result is that all top-level crates / directories are:

- Either entrypoints into the Rust code, i.e. applications such as
Gateway, Relay or a Client
- Or crates shared across all those entrypoints, such as `telemetry` or
`logging`
2025-05-12 01:04:17 +00:00
Jamil
283879eeb0 docs: Remove note on disabling SIP (#9075)
When developing system extensions, Apple's
[documentation](https://developer.apple.com/documentation/DriverKit/debugging-and-testing-system-extensions)
instructs developers to disable SIP and turn on system extension
developer mode to disable certain runtime checks that allow the
extension to run.

It turns out this is completely unnecessary - any properly set up Xcode
toolchain can build a functioning macOS debug client.
2025-05-12 00:55:21 +00:00
Jamil
4e61ba9582 fix(apple): set new project version on each build (#9072)
When developing the macOS app, we always build the exact same version
and build code for each build. ~~This _may_ be one reason why we
constantly have to deactivate the extension before the new one will
launch.~~ Edit: Just tested, and I can verify that this does fix the
issue on dev builds, so no more having to uninstall the sysex between
builds.

Even if that's not the reason, this is a cleaner approach than building
it in our prod-only scripts.

---------

Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
2025-05-12 00:55:14 +00:00
Jamil
091b52ef07 ci: Prevent having to manually update provisioning profile UUIDs (#9074)
When updating the provisioning profiles (i.e. when changing anything the
Apple Developer Portal), we needed to manually update these build
scripts to point to the new UUIDs.

This can be made simpler to automatically pull it out of the profiles in
CI.
2025-05-11 22:54:14 +00:00
Jamil
07fa91f713 chore(apple): Add file header template for xcode (#9068)
When creating new files in Xcode, it's helpful if the file header is
already formatted properly.
2025-05-10 12:26:37 +00:00
Thomas Eizinger
3f4e004a48 fix(connlib): don't recreate DNS resource NAT for failed domains (#9064)
Before a Client can send packets to a DNS resource, the Gateway must
first setup a NAT table between the IPs assigned by the Client and the
IPs the domain actually resolves to. This is what we call the DNS
resource NAT.

The communication for this process happens over IP through the tunnel
which is an unreliable transport. To ensure that this works reliably
even in the presence of packet loss on the wire, the Client uses an
idempotent algorithm where it tracks the state of the NAT for each
domain that is has ever assigned IPs for (i.e. received an A or AAAA
query from an application). This algorithm ensures that if we don't hear
anything back from the Gateway within 2s, another packet for setting up
the NAT is sent as soon as we receive _any_ DNS query.

This design balances efficiency (we don't try forever) with reliability
(we always check all of them).

In case a domain does not resolve at all or there are resolution errors,
the Gateway replies with `NatStatus::Inactive`. At present, the Client
doesn't handle this in any particular way other than logging that it was
not able to successfully setup the NAT.

The combination of the above results in an undesirable behaviour: If an
application queries a domain without A and AAAA records once, we will
keep retrying forever to resolve it upon every other DNS query issued to
the system. To fix this, we introduce `dns_resource_nat::State::Failed`.
Entries in this state are ignored as part of the above algorithm and
only recreated when explicitly told to do so which we only do when we
receive another DNS query for this domain.

To handle the increased complexity around this system, we extract it
into its own component and add a fleet of unit tests for its behaviour.
2025-05-09 15:04:21 +00:00
Thomas Eizinger
d80765b290 ci: explicitly bundle the tunnel service (#9065)
Currently, the Tauri build is broken on `main` because #9045
accidentally merged a bit too soon. In that PR, the two binaries that
the `gui-client` crate is composed of are now both defined in `src/bin`.

For some reason, this breaks Tauri's bundler and now on aarch64, it
stops including the `firezone-client-ipc` binary in the bundle. I don't
fully understand why and how that even works for x64 in the first place.
Nowhere in our repository can I find a configuration for the bundler as
to why it should even include that binary in the first place.

To fix this, we now explicitly copy this binary into the correct path
and also rebuild the `data` archive in addition to the `control`
archive.
2025-05-09 14:49:01 +00:00
Thomas Eizinger
fa790b231a fix(gateway): respond with SERVFAIL for missing nameserver (#9061)
When we implemented #8350, we chose an error handling strategy that
would shutdown the Gateway in case we didn't have a nameserver selected
for handling those SRV and TXT queries. At the time, this was deemed to
be sufficiently rare to be an adequate strategy. We have since learned
that this can indeed happen when the Gateway starts without network
connectivity which is quite common when using tools such as terraform to
provision infrastructure.

In #9060, we fix this by re-evaluating the fastest nameserver on a
timer. This however doesn't change the error handling strategy when we
don't have a working nameserver at all. It is practically impossible to
have a working Gateway yet us being unable to select a nameserver. We
read them from `/etc/resolv.conf` which is what `libc` uses to also
resolve the domain we connect to for the WebSocket. A working WebSocket
connection is required for us to establish connections to Clients, which
in turn is a precursor to us receiving DNS queries from a Client.

It causes unnecessary complexity to have a code path that can
potentially terminate the Gateway, yet is practically unreachable. To
fix this situation, we remove this code path and instead reply with a
DNS SERVFAIL error.

---------

Signed-off-by: Thomas Eizinger <thomas@eizinger.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-09 05:55:48 +00:00
Brian Manifold
a8bea13591 fix(portal): Remove redundant index on actor_group_memberships (#9063)
Why:

* It was pointed out that the way Postgresql does compound indexes there
is no need to have an individual index on the first column of the
compound index. This commit removes the redundant index on the
`actor_id` for the `actor_group_membership` table.
2025-05-09 05:32:45 +00:00
Thomas Eizinger
f70ddc9ee6 chore(connlib): filter noisy log from opentelemetry_sdk (#9062)
Opentelemetry logs a DEBUG log every time it creates a new meter. That
happens fairly often now in our codebase which spams the logs on the
DEBUG level.

```
2025-05-09T02:31:51.147Z DEBUG opentelemetry_sdk:  name="MeterProvider.ExistingMeterReturned" meter_name="connlib"
```

We fix that be setting `opentelemetry_sdk` to default to `INFO` if it is
not specified explicitly.
2025-05-09 04:18:29 +00:00
Thomas Eizinger
ac339ff63b fix(gateway): evaluate fastest nameserver every 60s (#9060)
Currently, the Gateway reads all nameservers from `/etc/resolv.conf` on
startup and evaluates the fastest one to use for SRV and TXT DNS queries
that are forwarded by the Client. If the machine just booted and we do
not have Internet connectivity just yet, this fails which leaves the
Gateway in state where it cannot fulfill those queries.

In order to ensure we always use the fastest one and to self-heal from
such situations, we add a 60s timer that refreshes this state.
Currently, this will **not** re-read the nameservers from
`/etc/resolv.conf` but still use the same IPs read on startup.
2025-05-09 03:38:35 +00:00
Brian Manifold
20d8246ce8 fix(portal): Add indexes to actor_group_memberships (#9058)
Why:

* As we move towards hard deleting data one issue we've run into is with
cascading deletes on the actor_group_memberships table. In order to
solve this problem indexes have been created on the `actor_id` and
`group_id` columns of the actor_group_memberships.
2025-05-09 01:47:24 +00:00
Thomas Eizinger
33d5c32f35 fix(gateway): truncate payload of ICMP errors (#9059)
When the Gateway is handed an IP packet for a DNS resource that it
cannot route, it sends back an ICMP unreachable error. According to RFC
792 [0] (for ICMPv4) and RFC 4443 [1] (for ICMPv6), parts of the
original packet should be included in the ICMP error payload to allow
the sending party to correlate, what could not be sent.

For ICMPv4, the RFC says:

```
Internet Header + 64 bits of Data Datagram

The internet header plus the first 64 bits of the original
datagram's data.  This data is used by the host to match the
message to the appropriate process.  If a higher level protocol
uses port numbers, they are assumed to be in the first 64 data
bits of the original datagram's data.
```

For ICMPv6, the RFC says:

```
As much of invoking packet as possible without the ICMPv6 packet exceeding the minimum IPv6 MTU
```

[0]: https://datatracker.ietf.org/doc/html/rfc792
[1]: https://datatracker.ietf.org/doc/html/rfc4443#section-3.1
2025-05-09 01:38:31 +00:00
Thomas Eizinger
18ec6c6860 refactor(rust): move service implementation to GUI client (#9045)
The module and crate structure around the GUI client and its background
service are currently a mess of circular dependencies. Most of the
service implementation actually sits in `firezone-headless-client`
because the headless-client and the service share certain modules. We
have recently moved most of these to `firezone-bin-shared` which is the
correct place for these modules.

In order to move the background service to `firezone-gui-client`, we
need to untangle a few more things in the GUI client. Those are done
commit-by-commit in this PR. With that out the way, we can finally move
the service module to the GUI client; where is should actually live
given that it has nothing to do with the headless client.

As a result, the headless-client is - as one would expect - really just
a thin wrapper around connlib itself and is reduced down to 4 files with
this PR.

To make things more consistent in the GUI client, we move the `main.rs`
file also into `bin/`. By convention `bin/` is where you define binaries
if a crate has more than one. cargo will then build all of them.

Eventually, we can optimise the compile-times for `firezone-gui-client`
by splitting it into multiple crates:

- Shared structs like IPC messages
- Background service
- GUI client

This will be useful because it allows only re-compiling of the GUI
client alone if nothing in `connlib` changes and vice versa.

Resolves: #6913
Resolves: #5754
2025-05-08 13:22:09 +00:00
Thomas Eizinger
e96fbde493 build(rust): bump stun_codec (#9044)
This brings in new versions of `stun_codec` and `bytecodec` which end up
removing a duplicate dependency from our dependency tree.

Related: https://github.com/sile/bytecodec/pull/8
Related: https://github.com/sile/stun_codec
2025-05-06 23:10:23 +00:00
Thomas Eizinger
f2b1fbe718 refactor(rust): move device_id to bin-shared (#9040)
Both `device_id` and `device_info` are used by the headless-client and
the GUI client / IPC service. They should therefore be defined in the
`bin-shared` crate.
2025-05-06 04:52:37 +00:00
Thomas Eizinger
5ac5fc45e4 chore(gui-client): delete unused code (#9041)
Something seems to have gone wrong while rebasing #9022 and we have left
some dead code behind.
2025-05-06 04:44:59 +00:00
Jamil
2ef49b2e75 chore: remove unused DATABASE_REPLICATION vars (#9039)
These were added in #8909 and never removed and are unused.
2025-05-06 04:01:16 +00:00
Jamil
3de8a1e405 fix(apple/iOS): Use pointer directly for libresolv API calls (#9038)
Somewhere between Xcode 16.0 and Xcode 16.3, the API for the libresolv
functions we call changed slightly, and we can now pass the return value
of `__res_9_state()` directly to the `res_9_ninit`, `res_9_ndestroy` and
`res_9_getservers` functions.
2025-05-06 03:32:35 +00:00
Thomas Eizinger
c20cc779ac refactor(gui-client): inline -common crate (#9022)
In order to experiment with alternative GUI libraries, we extracted a
`gui-client-common` crate that would hold GUI-library agnostic code.
We've since upgraded to Tauri v2 and settled on that as the GUI
framework for the Windows and Linux Firezone Clients. Therefore this
abstraction is unnecessary and can be removed again.

This makes it easier to work on the GUI client and also allows the
compiler to flag unused code more easily.
2025-05-06 02:28:03 +00:00