Commit Graph

11 Commits

Author SHA1 Message Date
Thomas Eizinger
17a18fdfbb feat(connlib): always use candidates in order of priority (#10063)
To make things easier to debug, we enforce the order that candidates are
processed in. We want candidates to be processed in the order of their
inverse priority as higher priorities are better. For example, a host
candidate has a higher priority than a relay candidate.

This will make our logs more consistent because a `0-0` candidate pair
is always a `host-host` pair.

We enforce this with our own `IceCandidate` type which implements
`PartialOrd` and `Ord`. This now moves the deserialisation for the
portal messages to a `Deserialise` impl on this type. In order to ensure
that a single faulty candidate doesn't invalidate the entire list, we
use `serde_with` to skip over those elements that cannot be
deserialised.
2025-08-01 01:57:29 +00:00
Thomas Eizinger
02638582fe feat(connlib): allow controlling IP stack per DNS resource (#9300)
With this patch, `connlib` exposes a new, optional field `ip_stack`
within the resource description of each DNS resource that controls the
supported IP stack.

By default, the IP stack is set to `Dual` to preserve the current
behaviour. When set to `IPv4Only` or `IPv6Only`, `connlib` will not
assign any IPv4 or IPv6 addresses when receiving DNS queries for such a
resource. The DNS query will still respond successfully with NOERROR
(and not NXDOMAIN) but the list of IPs will be empty.

This is useful to e.g. allow sys-admins to disable IPv6 for resources
with buggy clients such as the MongoDB atlas driver. The MongoDB driver
does not correctly handle happy-eyeballs and instead fails the
connection early on any connection error.

Additionally, customers operating in IPv6-exclusive networks can disable
IPv4 addresses with this setting.

Related: https://jira.mongodb.org/browse/NODE-4678
Related: #9042
Related: #8892
2025-05-31 00:27:59 +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
39e272cfd1 refactor(rust): introduce dns-types crate (#8380)
A sizeable chunk of Firezone's Rust components deal with parsing,
manipulating and emitting DNS queries and responses. The API surface of
DNS is quite large and to make handling of all corner-cases easier, we
depend on the `domain` library to do the heavy-lifting for us.

For better or worse, `domain` follows a lazy-parsing approach. Thus,
creating a new DNS message doesn't actually verify that it is in fact
valid. Within Firezone, we make several assumptions around DNS messages,
such as that they will only ever contain a single question.
Historically, DNS allows for multiple questions per query but in
practise, nobody uses that.

Due to how we handle DNS in Firezone, manipulating these messages
happens in multiple places. That combined with the lazy-parsing approach
from `domain` warrants having our own `dns-types` library that wraps
`domain` and provides us with types that offer the interface we need in
the rest of the codebase.

Resolves: #7019
2025-03-10 04:33:10 +00:00
Thomas Eizinger
943dbf9712 test(connlib): assert resource status as part of tunnel_test (#7772)
In order to ensure that the "site status" in the UIs is always
up-to-date, we model the resource status as part of `tunnel_test`. This
should cover even the most bizarre combinations of adding, removing,
disabling and enabling resources interleaved with sending packets,
resetting connections etc.

Fixes: #7761.
2025-01-21 04:35:22 +00:00
Thomas Eizinger
dd6b52b236 chore(rust): share edition key via workspace table (#7451) 2024-12-03 00:28:06 +00:00
Thomas Eizinger
2c26fc9c0e ci: lint Rust dependencies using cargo deny (#7390)
One of Rust's promises is "if it compiles, it works". However, there are
certain situations in which this isn't true. In particular, when using
dynamic typing patterns where trait objects are downcast to concrete
types, having two versions of the same dependency can silently break
things.

This happened in #7379 where I forgot to patch a certain Sentry
dependency. A similar problem exists with our `tracing-stackdriver`
dependency (see #7241).

Lastly, duplicate dependencies increase the compile-times of a project,
so we should aim for having as few duplicate versions of a particular
dependency as possible in our dependency graph.

This PR introduces `cargo deny`, a linter for Rust dependencies. In
addition to linting for duplicate dependencies, it also enforces that
all dependencies are compatible with an allow-list of licenses and it
warns when a dependency is referred to from multiple crates without
introducing a workspace dependency. Thanks to existing tooling
(https://github.com/mainmatter/cargo-autoinherit), transitioning all
dependencies to workspace dependencies was quite easy.

Resolves: #7241.
2024-11-22 00:17:28 +00:00
Thomas Eizinger
48ba2869a8 chore(rust): ban the use of .unwrap except in tests (#7319)
Using the clippy lint `unwrap_used`, we can automatically lint against
all uses of `.unwrap()` on `Result` and `Option`. This turns up quite a
few results actually. In most cases, they are invariants that can't
actually be hit. For these, we change them to `Option`. In other cases,
they can actually be hit. For example, if the user supplies an invalid
log-filter.

Activating this lint ensures the compiler will yell at us every time we
use `.unwrap` to double-check whether we do indeed want to panic here.

Resolves: #7292.
2024-11-13 03:59:22 +00:00
Gabi
dc97b9040d fix(connlib): large upstream dns message (#7183)
If edns0 doesn't work correctly DNS servers might respond with messages
bigger than our maximum udp size.

In that case we need to truncate those messages when forwarding the
respond back to the interface and expect the OS to retry with TCP.

Otherwise we aren't able to allocate a packet big enough for this.

Fixes #7121

---------

Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
2024-10-30 04:02:14 +00:00
Thomas Eizinger
2ca91a3b1a chore(connlib): remove old mock feature (#7142)
This is so stale, it definitely needs to go in the bin.
2024-10-23 16:30:15 +00:00
Thomas Eizinger
be250f1e00 refactor(connlib): repurpose connlib-shared as connlib-model (#6919)
The `connlib-shared` crate has become a bit of a dependency magnet
without a clear purpose. It hosts utilities like `get_user_agent`,
messages for the client and gateway to communicate with the portal and
domain types like `ResourceId`.

To create a better dependency structure in our workspace, we repurpose
`connlib-shared` as a `connlib-model` crate. Its purpose is to host
domain-specific model types that multiple crates may want to use. For
that purpose, we rename the `callbacks::ResourceDescription` type to
`ResourceView`, designating that this is a _view_ onto a resource as
seen by `connlib`. The message types which currently double up as
connlib-internal model thus become an implementation detail of
`firezone-tunnel` and shouldn't be used for anything else.

---------

Signed-off-by: Reactor Scram <ReactorScram@users.noreply.github.com>
Co-authored-by: Reactor Scram <ReactorScram@users.noreply.github.com>
2024-10-03 14:47:58 +00:00