Commit Graph

2046 Commits

Author SHA1 Message Date
Jamil
0bc3895c3e ci: Bump Apple clients to 1.4.4 (#8245)
These have been released / published. Need to merge this to get website
links and changelog updated.
2025-02-24 09:01:45 -08:00
Thomas Eizinger
a0f079f1cd feat(gui-client): send Linux GUI logs to journald (#8236)
This configures the GUI client to log to journald in addition to files
as well. For better or worse, this logs all events such that structured
information is preserved, e.g. all additional fields next to the message
are also saved as fields in the journal. By default, when viewing the
logs via `journalctl`, those fields are not displayed. This makes the
default output of `journalctl` for the FIrezone GUI not as useful as it
could be. Fixing that is left to a later stage.

Related: #8173
2025-02-24 04:28:56 +00:00
Thomas Eizinger
4cb2b01c26 build(nix): manage Rust installation via rustup (#8235)
Using `rustup` - even on NixOS - is easier to manage the Rust toolchain
as some tools rely on being able to use the `rustup` shims such as
`+nightly` to run a nightly toolchain.
2025-02-24 01:33:13 +00:00
Thomas Eizinger
57ce0ee469 feat(gateway): cache DNS queries for resources (#8225)
With the addition of the Firezone Control Protocol, we are now issuing a
lot more DNS queries on the Gateway. Specifically, every DNS query for a
DNS resource name always triggers a DNS query on the Gateway. This
ensures that changes to DNS entries for resources are picked up without
having to build any sort of "stale detection" in the Gateway itself. As
a result though, a Gateway has to issue a lot of DNS queries to upstream
resolvers which in 99% or more cases will return the same result.

To reduce the load on these upstream, we cache successful results of DNS
queries for 5 minutes.

---------

Signed-off-by: Thomas Eizinger <thomas@eizinger.io>
2025-02-23 04:27:09 +00:00
Thomas Eizinger
f882edb3bd feat(gui-client): configure IPC service to log to stdout (#8219)
On Linux, logs sent to stdout from a systemd-service are automatically
captured by `journald`. This is where most admins expect logs to be and
frankly, doing any kind of debugging of Firezone is much easier if you
can do `journalctl -efu firezone-client-ipc.service` in a terminal and
check what the IPC service is doing.

On Windows, stdout from a service is (unfortunately) ignored.

To achieve this and also allow dynamically changing the log-filter, I
had to introduce a (long-overdue) abstraction over tracing's "reload"
layer that allows us to combine multiple reload-handles into one.
Unfortunately, neither the `reload::Layer` nor the `reload::Handle`
implement `Clone`, which makes this unnecessarily difficult.

Related: #8173
2025-02-23 00:23:29 +00:00
Thomas Eizinger
ea9796e346 feat(gateway): apply filter engine to inbound packets (#7702)
The Gateway keeps some state for each client connection. Part of this
state are filters which can be controlled via the Firezone portal. Even
if no filters are set in the portal, the Gateway uses this data
structure to ensure only packets to allowed resources are forwarded. If
a resource is not allowed, its IP won't exist in the `IpNetworkTable` of
filters and thus won't be allowed.

When a Client disconnects, the Gateway cleans up this data structure and
thus all filters etc are gone. As soon as a Client reconnects, default
filters are installed (which don't allow anything) under the same IP
(the portal always assigns the same IP to Clients).

These filters are only applied on _outbound_ traffic (i.e. from the
Client towards Resources). As a result, packets arriving from Resources
to a Client will still be routed back, causing "Source not allowed"
errors on the client (which has lost all of its state when restarting).

To fix this, we apply the Gateway's filters also on the reverse path of
packets from Resources to Clients.

Resolves: #5568
Resolves: #7521
Resolves: #6091
2025-02-21 05:59:36 +00:00
Thomas Eizinger
f22a285678 feat(phoenix-channel): don't try to detect missing heartbeats (#8220)
At present our Rust implementation of the Phoenix Channel client tries
to detect missing heartbeat responses from the portal. This is
unnecessary and causes brittleness in production.

The WebSocket connection runs over TCP, meaning any kind of actual
network problem / partition will be detected by TCP itself and cause an
IO error further up the stack. In order to keep NAT bindings alive, we
only need to send _some_ traffic every so often, meaning sending a
heartbeat is good enough. We don't need to actually handle the response
in any particular way.

Lastly, by just using an interval, I realised that we can very easily
implement an optimisation from the Phoenix spec: Only send heartbeats if
you haven't sent anything else.

In theory, WebSocket ping/pong frames could be used for this keep-alive
mechanism. Unfortunately, as I understand the Phoenix spec, it requires
its own heartbeat to be sent, otherwise it will disconnect the
WebSocket.
2025-02-21 05:42:49 +00:00
Thomas Eizinger
9bc23732f3 chore(apple): downgrade warning about installed crypto provider (#8226)
With the introduction of system extensions, the memory is no longer
free'd after the tunnel disconnects meaning this can easily happen.
2025-02-21 05:27:12 +00:00
Thomas Eizinger
273d723729 fix(gui-client): use "Firezone" as the application name on Linux (#8223)
The current `.desktop` file uses the `firezone-client-gui` name from the
Tauri config. This looks ugly and unprofessional. Instead, we should
just call this "Firezone".


![image](https://github.com/user-attachments/assets/3c4705fb-3611-4da9-9254-eaee06a8d749)

Resolves: #8205
2025-02-21 05:26:34 +00:00
Thomas Eizinger
deb47d956e chore(gateway): remove log around "No NAT session" (#8227)
This is pretty confusing when reading logs. For inbound packets, we
assume that if we don't have a NAT session, they belong to the Internet
Resource or a CIDR resource, meaning this log shows up for all packets
for those resources and even for packets that don't belong to any
resource at all.
2025-02-21 05:24:59 +00:00
Thomas Eizinger
b10b6e75ea fix(gui-client): hide the .desktop entry for deep-links (#8224)
On Linux desktops, we install a dedicated `.desktop` file that is
responsible for handling our deep-links for sign-in. This desktop entry
is not meant to be launched manually and therefore should be hidden from
the application menus.
2025-02-21 05:19:19 +00:00
Thomas Eizinger
6f68b97558 chore(gui-client): release v1.4.6 (#8211) 2025-02-20 04:25:38 +00:00
Thomas Eizinger
d5fdb5fda8 test(connlib): remove assertion around idle packets / sec (#8210)
This has been flaky recently but it isn't a priority right now.
2025-02-20 01:33:18 +00:00
Thomas Eizinger
81da120c17 fix(phoenix-channel): report connection hiccups to upper layer (#8203)
The WebSocket connection to the portal from within the Clients, Gateways
and Relays may be temporarily interrupted by IO errors. In such cases we
simply reconnect to it. This isn't as much of a problem for Clients and
Gateways. For Relays however, a disconnect can be disruptive for
customers because the portal will send `relays_presence` events to all
Clients and Gateways. Any relayed connection will therefore be
interrupted. See #8177.

Relays run on our own infrastructure and we want to be notified if their
connection flaps.

In order to differentiate between these scenarios, we remove the logging
from within `phoenix-channel` and report these connection hiccups one
layer up. This allows Clients and Gateways to log them on DEBUG whereas
the Relay can log them on WARN.

Related: #8177 
Related: #7004
2025-02-20 00:54:43 +00:00
Thomas Eizinger
cad84922db fix(apple): don't panic in FFI functions (#8202)
Now that we have error reporting via Sentry in Swift-land as well, we
can handle errors in the FFI layer more gracefully and return them to
Swift.

---------

Signed-off-by: Jamil <jamilbk@users.noreply.github.com>
Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
2025-02-20 00:51:56 +00:00
Thomas Eizinger
3e4976e4ab fix(relay): don't starve items further down in the event-loop (#8177)
At present, the relay uses a priority in the event-loop that favors
routing traffic. Whenever a task further up in the loop is
`Poll::Ready`, we loop back to the top to continue processing. The issue
with that is that in very busy times, this can lead to starvation in
processing timers and messages from the portal. If we then finally get
to process portal messages, we think that the portal hasn't replied in
some time and proactively cut the connection and reconnect.

As a result, the portal will send `relays_presence` messages to the
clients and gateways which in turn will locally remove the relay. This
breaks relayed connections.

To fix this, instead of immediately traversing to the top of the
event-loop with `continue`, we only set a boolean. This gives each
element of the event-loop a chance to execute, even when a certain
component is very busy.

Related: #8165
Related: #8176
2025-02-18 12:00:32 +00:00
Thomas Eizinger
2e43523f75 fix(snownet): servers should not initiate WireGuard sessions (#8169)
Whilst ICE for a connection is in progress, it might happen that packets
for a particular client are arriving at the Gateway's TUN device. I
assume that these might be from a previous session?

We can only negotiate a WireGuard session once we have a nominated
socket. Thus, the very first packet sent on a session will always
trigger a new handshake. We don't want Gateway's to start handshakes
though, those should always be initiated by the Clients.

To avoid this, we add a conditional to `snownet::Node` that drops
packets iff the current node is a `ServerNode` and we haven't nominated
a socket yet.

The following log output from a Gateway motivated this change:

```
2025-02-17T15:36:45.372Z  INFO snownet::node: Connection failed (ICE timeout) cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5

// Here the previous connection failed.

2025-02-17T15:36:45.989Z DEBUG firezone_tunnel::gateway: Unknown client, perhaps already disconnected? dst=100.64.69.110
2025-02-17T15:36:45.989Z DEBUG firezone_tunnel::gateway: Unknown client, perhaps already disconnected? dst=100.64.69.110
2025-02-17T15:36:45.989Z DEBUG firezone_tunnel::gateway: Unknown client, perhaps already disconnected? dst=100.64.69.110
2025-02-17T15:36:46.213Z DEBUG firezone_tunnel::gateway: Unknown client, perhaps already disconnected? dst=100.64.69.110

// Until here, packets for this client got dropped but now a new connection (for the same IP!) is being created.

2025-02-17T15:36:46.474Z DEBUG snownet::node: Sampled relay rid=b7198983-0cf6-48ba-a459-e7d27ef7d6c9 client_id=8b106344-ba59-4050-8f9a-e2f0bab6e9e5 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.474Z  INFO str0m::ice_::agent: Set local credentials: IceCreds { ufrag: "ipcg", pass: "eyy6s27emu2joisw7aqc7q" } client_id=8b106344-ba59-4050-8f9a-e2f0bab6e9e5 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.474Z  INFO str0m::ice_::agent: Set remote credentials: IceCreds { ufrag: "up5k", pass: "4q6uvhawhcbnhbqrddvy5x" } client_id=8b106344-ba59-4050-8f9a-e2f0bab6e9e5 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.474Z  INFO str0m::ice_::agent: Add local candidate: Candidate(host=10.0.0.4:38621/udp prio=2130706175) client_id=8b106344-ba59-4050-8f9a-e2f0bab6e9e5 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.474Z  INFO str0m::ice_::agent: Add local candidate: Candidate(relay=34.16.221.134:62250/udp prio=37748479) client_id=8b106344-ba59-4050-8f9a-e2f0bab6e9e5 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.474Z  INFO str0m::ice_::agent: Add local candidate: Candidate(relay=[2600:1900:4180:ee3:0:78::]:62250/udp prio=37748735) client_id=8b106344-ba59-4050-8f9a-e2f0bab6e9e5 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.474Z  INFO str0m::ice_::agent: State change (new connection): New -> Checking client_id=8b106344-ba59-4050-8f9a-e2f0bab6e9e5 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.474Z  INFO snownet::node: Created new connection client_id=8b106344-ba59-4050-8f9a-e2f0bab6e9e5 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.475Z  INFO firezone_tunnel::peer: Allowing access to resource client=8b106344-ba59-4050-8f9a-e2f0bab6e9e5 resource=dca3fcc6-b5e0-470a-bc7b-6446cdd03bb3 expires=Some("2025-02-24T15:09:11+00:00") client_id=8b106344-ba59-4050-8f9a-e2f0bab6e9e5

// The connection has been created and very likely another packet has arrived at the TUN interface. This time though, we have an entry in our connection map for this IP and try to route it.

2025-02-17T15:36:46.546Z DEBUG boringtun::noise: Sending handshake_initiation cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.546Z DEBUG snownet::node: ICE is still in progress, buffering WG handshake num_buffered=1 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5

// We buffered the handshake packet. This is only meant to be done by clients.

2025-02-17T15:36:46.572Z  INFO str0m::ice_::agent: Created peer reflexive remote candidate from STUN request: Candidate(prflx=107.197.104.68:49376/udp prio=1862270719) cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.572Z DEBUG str0m::ice_::agent: Created new pair for STUN request: CandidatePair(1-0 prio=162128486503284223 state=Waiting attempts=0 unanswered=0 remote=0 last=None nom=None) cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.574Z  INFO str0m::ice_::agent: Created peer reflexive remote candidate from STUN request: Candidate(prflx=[2600:1700:3ecb:2410:7499:175a:5c9:9bc5]:57622/udp prio=1862270975) cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.574Z DEBUG str0m::ice_::agent: Created new pair for STUN request: CandidatePair(2-1 prio=162129586014912511 state=Waiting attempts=0 unanswered=0 remote=0 last=None nom=None) cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.611Z DEBUG str0m::ice_::pair: Nominated pair: CandidatePair(2-1 prio=162129586014912511 state=Succeeded attempts=1 unanswered=0 remote=2 last=Some(Instant { tv_sec: 286264, tv_nsec: 840170135 }) nom=Nominated) cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.612Z  INFO str0m::ice_::agent: State change (got nomination, still trying others): Checking -> Connected cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.612Z DEBUG snownet::node: Flushing packets buffered during ICE num_buffered=1 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.612Z  INFO snownet::node: Updating remote socket old=None new=Relay { relay: b7198983-0cf6-48ba-a459-e7d27ef7d6c9, dest: [2600:1700:3ecb:2410:7499:175a:5c9:9bc5]:57622 } duration_since_intent=137.48517ms cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5

// The connection has been established and we receive the (forced) handshake initiation by the client. However, we also flushed a handshake initiation.

2025-02-17T15:36:46.612Z DEBUG boringtun::noise: Received handshake_initiation remote_idx=731337473 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.613Z DEBUG boringtun::noise: Sending handshake_response local_idx=185230594 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.613Z DEBUG boringtun::noise: Sending handshake_initiation cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.629Z DEBUG snownet::node: Unknown connection or socket has already been nominated ignored_candidate=candidate:fffeff021b36b51d6f7abdc3 1 udp 50331391 34.94.63.38 55487 typ relay cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.629Z DEBUG snownet::node: Unknown connection or socket has already been nominated ignored_candidate=candidate:fffeff64a52b02479dab9c4 1 udp 1694498559 107.197.104.68 49376 typ srflx cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.629Z DEBUG snownet::node: Unknown connection or socket has already been nominated ignored_candidate=candidate:fffeff7ec9b7a7db40ec1c44 1 udp 2130706175 192.168.1.150 49376 typ host cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.630Z DEBUG snownet::node: Unknown connection or socket has already been nominated ignored_candidate=candidate:ffffff026d81f5c8a4d5600e 1 udp 50331647 2600:1900:4120:521c:0:78:: 55487 typ relay cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.630Z DEBUG snownet::node: Unknown connection or socket has already been nominated ignored_candidate=candidate:ffffff64e2c91c4ff6f343f5 1 udp 1694498815 2600:1700:3ecb:2410:7499:175a:5c9:9bc5 57622 typ srflx cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.630Z DEBUG snownet::node: Unknown connection or socket has already been nominated ignored_candidate=candidate:ffffff7ed64262b110d1f279 1 udp 2130706431 2600:1700:3ecb:2410:7499:175a:5c9:9bc5 57622 typ host cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5

// We are receiving a response for our handshake initiation. Let the fight begin!

2025-02-17T15:36:46.651Z DEBUG boringtun::noise: Received handshake_response local_idx=185230593 remote_idx=731337474 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.651Z DEBUG firezone_gateway::eventloop: Tunnel error: Failed to decapsulate: Failed to decapsulate: UnexpectedPacket
2025-02-17T15:36:46.651Z DEBUG boringtun::noise: Received handshake_initiation remote_idx=731337475 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.652Z DEBUG boringtun::noise: Sending handshake_response local_idx=185230596 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.652Z DEBUG boringtun::noise: Sending handshake_initiation cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.652Z DEBUG boringtun::noise: Received handshake_response local_idx=185230595 remote_idx=731337476 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.652Z DEBUG firezone_gateway::eventloop: Tunnel error: Failed to decapsulate: Failed to decapsulate: UnexpectedPacket
2025-02-17T15:36:46.652Z DEBUG boringtun::noise: Received handshake_initiation remote_idx=731337477 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.653Z DEBUG boringtun::noise: Sending handshake_response local_idx=185230598 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.653Z DEBUG boringtun::noise: Sending handshake_initiation cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.691Z DEBUG boringtun::noise: Received handshake_response local_idx=185230597 remote_idx=731337478 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.691Z DEBUG firezone_gateway::eventloop: Tunnel error: Failed to decapsulate: Failed to decapsulate: UnexpectedPacket
2025-02-17T15:36:46.691Z DEBUG boringtun::noise: Received handshake_initiation remote_idx=731337479 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.692Z DEBUG boringtun::noise: Sending handshake_response local_idx=185230600 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
2025-02-17T15:36:46.692Z  INFO snownet::node: Completed wireguard handshake cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5 duration_since_intent=217.247362ms
2025-02-17T15:36:46.692Z DEBUG firezone_gateway::eventloop: Tunnel error: Failed to decapsulate: Failed to decapsulate: NoCurrentSession
2025-02-17T15:36:46.692Z DEBUG firezone_gateway::eventloop: Tunnel error: Failed to decapsulate: Failed to decapsulate: NoCurrentSession
2025-02-17T15:36:46.692Z DEBUG firezone_gateway::eventloop: Tunnel error: Failed to decapsulate: Failed to decapsulate: NoCurrentSession
2025-02-17T15:36:46.692Z DEBUG firezone_gateway::eventloop: Tunnel error: Failed to decapsulate: Failed to decapsulate: NoCurrentSession
2025-02-17T15:36:46.708Z DEBUG firezone_gateway::eventloop: Tunnel error: Failed to decapsulate: Failed to decapsulate: NoCurrentSession
2025-02-17T15:36:46.731Z DEBUG boringtun::noise: New session session=185230600 cid=8b106344-ba59-4050-8f9a-e2f0bab6e9e5
```

As you can see, with both parties initiating handshakes, they end up
fighting over who should initiate the session.
2025-02-18 08:25:45 +00:00
Thomas Eizinger
2d37cfa264 refactor(snownet): make kind of connection more descriptive (#8167)
When `snownet` establishes a connection to another peer, we may end up
in one of four different connection types:

- `PeerToPeer`
- `PeerToRelay`
- `RelayToPeer`
- `RelayToRelay`

From the perspective of the local node, it only matters whether or not
we are sending data from our local socket or a relay's socket because in
the latter case, we have to encapsulate it in a channel data message.
Hence, at present, we often see logs that say "Direct" but really, we
are talking to a port allocated by the remote on a relay.

We know whether or not the remote candidate is a relay by looking at the
candidates they sent us.

To make our logs more descriptive, we now model out all 4 possibilities
here.
2025-02-18 07:35:50 +00:00
Thomas Eizinger
287068396f chore(snownet): advance backoff after accessing interval (#8175)
When we detect timed-out request to a relay, we print the duration we
were waiting for. Currently, this is offset by one "backoff tick"
because we advance the backoff too early.

Here is a log-output of a test prior to the change:

```
snownet::allocation: Sending BINDING requests to pick active socket relay_socket=V4(127.0.0.1:3478)
handle_timeout{active_socket=None}: snownet::allocation: Request timed out after 1.5s, re-sending id=TransactionId(0x0BFA13E983FEF36EE4877719) method=binding dst=127.0.0.1:3478
handle_timeout{active_socket=None}: snownet::allocation: Request timed out after 2.25s, re-sending id=TransactionId(0x0BFA13E983FEF36EE4877719) method=binding dst=127.0.0.1:3478
handle_timeout{active_socket=None}: snownet::allocation: Request timed out after 3.375s, re-sending id=TransactionId(0x0BFA13E983FEF36EE4877719) method=binding dst=127.0.0.1:3478
handle_timeout{active_socket=None}: snownet::allocation: Request timed out after 3.375s, re-sending id=TransactionId(0x0BFA13E983FEF36EE4877719) method=binding dst=127.0.0.1:3478
handle_timeout{active_socket=None}: snownet::allocation: Backoff expired, giving up id=TransactionId(0x0BFA13E983FEF36EE4877719) method=binding dst=127.0.0.1:3478
```

and with this change:

```
snownet::allocation: Sending BINDING requests to pick active socket relay_socket=V4(127.0.0.1:3478)
handle_timeout{active_socket=None}: snownet::allocation: Request timed out after 1s, re-sending id=TransactionId(0x6C79DD3607DF96806C4A7D8C) method=binding dst=127.0.0.1:3478
handle_timeout{active_socket=None}: snownet::allocation: Request timed out after 1.5s, re-sending id=TransactionId(0x6C79DD3607DF96806C4A7D8C) method=binding dst=127.0.0.1:3478
handle_timeout{active_socket=None}: snownet::allocation: Request timed out after 2.25s, re-sending id=TransactionId(0x6C79DD3607DF96806C4A7D8C) method=binding dst=127.0.0.1:3478
handle_timeout{active_socket=None}: snownet::allocation: Request timed out after 3.375s, re-sending id=TransactionId(0x6C79DD3607DF96806C4A7D8C) method=binding dst=127.0.0.1:3478
handle_timeout{active_socket=None}: snownet::allocation: Backoff expired, giving up id=TransactionId(0x6C79DD3607DF96806C4A7D8C) method=binding dst=127.0.0.1:3478
t
```

There is no functional difference, we were just logging the wrong
duration.
2025-02-18 06:12:52 +00:00
Thomas Eizinger
28f00089b9 test(connlib): increase threshold for idle packets (#8174)
Same as the other day. Currently no bandwidth to look into this but need
to ensure stable CI.
2025-02-18 04:49:07 +00:00
Thomas Eizinger
643347ba0e test(windows): reduce expected BPS of WinTUN benchmark (#8171)
This appears to have regressed in #8159. It is low-priority right now
and we need to unblock a flaky CI so lower the expected BPS and
investigate later.
2025-02-18 03:34:14 +00:00
Thomas Eizinger
33c707dbf6 feat(windows): introduce dedicated "TUN send" thread (#8159)
Same as done for unix-based operation systems in #8117, we introduce a
dedicated "TUN send" thread for Windows in this PR. Not only does this
move the syscalls and copying of sending packets away from `connlib`'s
main thread but it also establishes backpressure between those threads
properly.

WinTUN does not have any ability to signal that it has space in its send
buffer. If it fails to allocate a packet for sending, it will return
`ERROR_BUFFER_OVERFLOW` [0]. We now handle this case gracefully by
suspending the send thread for 10ms and then try again. This isn't a
great way of establishing back-pressure but at least we don't have any
packet loss.

To test this, I temporarily lowered the ring buffer size and ran a speed
test. In that, I could confirm that `ERROR_BUFFER_OVERFLOW` is indeed
emitted and handled as intended.

[0]: https://git.zx2c4.com/wintun/tree/api/session.c#n267
2025-02-17 20:33:45 +00:00
Thomas Eizinger
2d70a8ed31 test(connlib): create dedicated Internet site (#8153)
To ensure that our test suite represents production as much as possible,
we introduce a dedicated "Internet" site into the `StubPortal` that only
hosts the Internet resource. All other creates resources are assigned to
other sites.
2025-02-17 19:46:32 +00:00
Thomas Eizinger
7ea17c144a refactor(gui-client): de-duplicate logging of IPC message errors (#8157) 2025-02-17 14:21:52 +00:00
dependabot[bot]
8c7c0a9e8e build(deps): bump os_info from 3.9.2 to 3.10.0 in /rust (#8161)
Bumps [os_info](https://github.com/stanislav-tkach/os_info) from 3.9.2
to 3.10.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/stanislav-tkach/os_info/releases">os_info's
releases</a>.</em></p>
<blockquote>
<h2>os_info 3.10.0</h2>
<ul>
<li>Bluefin Linux support has been added. (<a
href="https://redirect.github.com/stanislav-tkach/os_info/issues/394">#394</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/stanislav-tkach/os_info/blob/master/CHANGELOG.md">os_info's
changelog</a>.</em></p>
<blockquote>
<h2>[3.10.0] (2025-02-09)</h2>
<ul>
<li>Bluefin Linux support has been added. (<a
href="https://redirect.github.com/stanislav-tkach/os_info/issues/394">#394</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="0554ec580d"><code>0554ec5</code></a>
Merge pull request <a
href="https://redirect.github.com/stanislav-tkach/os_info/issues/396">#396</a>
from stanislav-tkach/release-3-10-0</li>
<li><a
href="9a0980c375"><code>9a0980c</code></a>
Fix markdown indent</li>
<li><a
href="d189e7de3f"><code>d189e7d</code></a>
Release the 3.10.0 version</li>
<li><a
href="6d7ea4f231"><code>6d7ea4f</code></a>
Merge pull request <a
href="https://redirect.github.com/stanislav-tkach/os_info/issues/395">#395</a>
from stanislav-tkach/fix-spellcheck</li>
<li><a
href="e81339bd5d"><code>e81339b</code></a>
Fix spellcheck</li>
<li><a
href="9c6d24ead9"><code>9c6d24e</code></a>
Merge pull request <a
href="https://redirect.github.com/stanislav-tkach/os_info/issues/394">#394</a>
from sargunv/feature/add-bluefin-support</li>
<li><a
href="a41a664650"><code>a41a664</code></a>
feat: Add support for Bluefin Linux</li>
<li>See full diff in <a
href="https://github.com/stanislav-tkach/os_info/compare/v3.9.2...v3.10.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=os_info&package-manager=cargo&previous-version=3.9.2&new-version=3.10.0)](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-02-17 11:11:38 +00:00
dependabot[bot]
3b78821944 build(deps): bump tracing-subscriber from 0.3.18 to 0.3.19 in /rust (#8162)
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from
0.3.18 to 0.3.19.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/tracing/releases">tracing-subscriber's
releases</a>.</em></p>
<blockquote>
<h2>tracing-subscriber 0.3.19</h2>
<p>[ [crates.io][crate-0.3.19] ] | [ [docs.rs][docs-0.3.19] ]</p>
<p>This release updates the <code>tracing</code> dependency to
[v0.1.41][tracing-0.1.41] and
the <code>tracing-serde</code> dependency to
[v0.2.0][tracing-serde-0.2.0].</p>
<h3>Added</h3>
<ul>
<li>Add <code>set_span_events</code> to <code>fmt::Subscriber</code> (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/2962">#2962</a>)</li>
<li><strong>tracing</strong>: Allow <code>&amp;[u8]</code> to be
recorded as event/span field (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/2954">#2954</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Set <code>log</code> max level when reloading (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/1270">#1270</a>)</li>
<li>Bump MSRV to 1.63 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/2793">#2793</a>)</li>
<li>Use const <code>thread_local</code>s when possible (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/2838">#2838</a>)</li>
<li>Don't gate <code>with_ansi()</code> on the &quot;ansi&quot; feature
(<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3020">#3020</a>)</li>
<li>Updated tracing-serde to 0.2.0 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3160">#3160</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/tokio-rs/tracing/issues/1270">#1270</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/1270">tokio-rs/tracing#1270</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/2793">#2793</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/2793">tokio-rs/tracing#2793</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/2838">#2838</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/2838">tokio-rs/tracing#2838</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/2954">#2954</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/2954">tokio-rs/tracing#2954</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/2962">#2962</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/2962">tokio-rs/tracing#2962</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3020">#3020</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/3020">tokio-rs/tracing#3020</a>
<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3160">#3160</a>:
<a
href="https://redirect.github.com/tokio-rs/tracing/pull/3160">tokio-rs/tracing#3160</a>
[tracing-0.1.41]:
<a
href="https://github.com/tokio-rs/tracing/releases/tag/tracing-0.1.41">https://github.com/tokio-rs/tracing/releases/tag/tracing-0.1.41</a>
[tracing-serde-0.2.0]:
<a
href="https://github.com/tokio-rs/tracing/releases/tag/tracing-serde-0.2.0">https://github.com/tokio-rs/tracing/releases/tag/tracing-serde-0.2.0</a>
[docs-0.3.19]: <a
href="https://docs.rs/tracing-subscriber/0.3.19/tracing_subscriber/">https://docs.rs/tracing-subscriber/0.3.19/tracing_subscriber/</a>
[crate-0.3.19]: <a
href="https://crates.io/crates/tracing-subscriber/0.3.19">https://crates.io/crates/tracing-subscriber/0.3.19</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="311c313216"><code>311c313</code></a>
chore: prepare tracing-subscriber 0.3.19 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3162">#3162</a>)</li>
<li><a
href="35f360a192"><code>35f360a</code></a>
chore: fix new Clippy lints in Rust 1.83.0 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3165">#3165</a>)</li>
<li><a
href="c66a692e67"><code>c66a692</code></a>
chore: prepare tracing-serde 0.2.0 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3160">#3160</a>)</li>
<li><a
href="0ca7887081"><code>0ca7887</code></a>
chore: prepare tracing 0.1.41 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3159">#3159</a>)</li>
<li><a
href="504a287abb"><code>504a287</code></a>
tracing: update core to v0.1.33 and attributes to v0.1.28 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3156">#3156</a>)</li>
<li><a
href="baa5489406"><code>baa5489</code></a>
chore: prepare tracing-attributes 0.1.28 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3155">#3155</a>)</li>
<li><a
href="cb0f0e71dd"><code>cb0f0e7</code></a>
chore: prepare tracing-core 0.1.33 (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3153">#3153</a>)</li>
<li><a
href="11c8273035"><code>11c8273</code></a>
subscriber: don't gate <code>with_ansi()</code> on the &quot;ansi&quot;
feature (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/3020">#3020</a>)</li>
<li><a
href="8a25a16873"><code>8a25a16</code></a>
core: fix missed <code>register_callsite</code> error (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/2938">#2938</a>)</li>
<li><a
href="6f08af07f2"><code>6f08af0</code></a>
subscriber: set <code>log</code> max level when reloading (<a
href="https://redirect.github.com/tokio-rs/tracing/issues/1270">#1270</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.18...tracing-subscriber-0.3.19">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tracing-subscriber&package-manager=cargo&previous-version=0.3.18&new-version=0.3.19)](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-02-17 11:11:06 +00:00
Thomas Eizinger
a3c0321020 fix(android): init Sentry layer as part of logging (#8154)
Resolves: #8050.
2025-02-17 05:38:29 +00:00
Thomas Eizinger
af9fc49b18 fix(windows): don't double shutdown session (#8156)
The `wintun` crate will already shutdown the session for us when the
last instance of `Session` gets dropped. Shutting down the session prior
to that already results in an attempt to close an adapter that is no
longer present, causing WinTUN to log (unactionable) errors.
2025-02-17 05:38:11 +00:00
Thomas Eizinger
9de467483f fix(apple): init Sentry layer as part of logging (#8155) 2025-02-17 04:19:22 +00:00
Thomas Eizinger
72782b8389 fix(gui-client): update telemetry context on new session (#8152)
Every time we start a new session, our telemetry context potentially
changes, i.e. the user may sign into a new account. This should ensure
that both the IPC service and the GUI always use the most up-to-date
`account_slug` as part of Sentry events. In addition, this will also set
the `account_slug` for clients that just signed in. Previously, the
`account_slug` would only get populated on the next start of the client.
2025-02-17 03:29:08 +00:00
Jamil
e487272a1b chore(apple): Release Apple clients 1.4.3 (#8144) 2025-02-16 12:59:38 -08:00
Jamil
d38ec466b9 chore(android): Release Android 1.4.2 (#8145) 2025-02-16 12:59:12 -08:00
Jamil
80aa9e76c1 build(phoenix-channel): add cfg to enable system CAs (#8137)
By setting the `system_certs` cfg at compile-time, any TLS connections
from `phoenix-channel` will use the system-provided CA store instead of
the embedded one.

Resolves: #8065

Co-authored-by: oddlama <oddlama@oddlama.org>
Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
2025-02-15 00:23:25 +00:00
Thomas Eizinger
bc37e0140b fix(gui-client): allow sign-in without saving token to keyring (#8129)
Alternative to #8128. If the user dismissed the unlock prompt or has
their keyring otherwise misconfigured, it is still useful to allow them
to sign-in. They just won't stay signed-in across reboots of the device.
2025-02-14 15:17:26 +00:00
Thomas Eizinger
9cce4fd637 fix(gateway): don't route packets from expired NAT sessions (#8124)
When we receive an inbound packet from the TUN device on the Gateway, we
make a lookup in the NAT table to see if it needs to be translated back
to a DNS proxy IP.

At present, non-existence of such a NAT entry results in the packet
being sent entirely unmodified because that is what needs to happen for
CIDR resources. Whilst that is important, the same code path is
currently being executed for DNS resources whose NAT session expired!
Those packets should be dropped instead which is what we do with this
PR.

To differentiate between not having a NAT session at all or whether a
previous one existed but is expired now, we keep around all previous
"outside" tuples of NAT sessions around. Those are only very small in
their memory-footprint. The entire NAT table is scoped to a connection
to the given peer and will thus eventually freed once the peer
disconnects. This allows us to reliably and cheaply detect, whether a
packet is using an expired NAT session. This check must be cheap because
all traffic of CIDR resources and the Internet resource needs to perform
this check such that we know that they don't have to be translated.

This might be the source of some of the "Source not allowed" errors we
have been seeing in client logs.
2025-02-14 08:21:23 +00:00
Thomas Eizinger
8f0db6ad47 fix(connlib): run all callbacks on a separate thread (#8126)
At present, `connlib` communicates with its host app via callbacks.
These callbacks are executed synchronously as part of `connlib`s
event-loop, meaning `connlib` cannot do anything else whilst the
callback is executing in the host app. Additionally, this callback runs
within the `Future` that represents `connlib` and thus runs on a `tokio`
worker thread.

Attempting to interact with the session from within the callback can
lead to panics, for example when `Session::disconnect` is called which
uses `Runtime::block_on`. This isn't allowed by `tokio`: You cannot
block on the execution of an async task from within one of the worker
threads.

To solve both of these problems, we introduce a thread-pool of size 1
that is responsible for executing `connlib` callbacks. Not only does
this allow `connlib` to perform more work such as routing packets or
process portal messages, it also means that it is not possible for the
host app to cause these panics within the `tokio` runtime because the
callbacks run on a different thread.
2025-02-14 06:54:35 +00:00
Thomas Eizinger
10ba02e341 fix(connlib): split TUN send & recv into separate threads (#8117)
We appear to have caused a pretty big performance regression (~40%) in
037a2e64b6 (identified through
`git-bisect`). Specifically, the regression appears to have been caused
by [`aef411a`
(#7605)](aef411abf5).
Weirdly enough, undoing just that on top of `main` doesn't fix the
regression.

My hypothesis is that using the same file descriptor for read AND write
interests on the same runtime causes issues because those interests are
occasionally cleared (i.e. on false-positive wake-ups).

In this PR, we spawn a dedicated thread each for the sending and
receiving operations of the TUN device. On unix-based systems, a TUN
device is just a file descriptor and can therefore simply be copied and
read & written to from different threads. Most importantly, we only
construct the `AsyncFd` _within_ the newly spawned thread and runtime
because constructing an `AsyncFd` implicitly registers with the runtime
active on the current thread.

As a nice benefit, this allows us to get rid of a `future::select`.
Those are always kind of nasty because they cancel the future that
wasn't ready. My original intuition was that we drop packets due to
cancelled futures there but that could not be confirmed in experiments.
2025-02-14 05:32:51 +00:00
Jamil
39cbf60ec8 ci: Bump Apple clients to 1.4.2 (#8109)
Fixes a slew of memory leaks, crashes, and other papercuts.
2025-02-13 22:08:45 +00:00
Jamil
5afeb30f6f ci: Bump GUI clients to 1.4.5 (#8113) 2025-02-12 20:56:27 +00:00
Thomas Eizinger
5a12dcb5b3 fix(gui-client): migrate to tailwind v4 (#8105)
With the dependency bump in #7995, we introduced a visual regression
that made all windows lose their styling:


![image](https://github.com/user-attachments/assets/9c9921a7-cab0-4adc-9868-cd7ddec40c64)

The changelog to the v4 bump actually mentions some breaking changes and
an automated upgrade tool but both the reviewer and the author of the PR
missed that.
2025-02-12 19:19:18 +00:00
Jamil
393436a4aa ci: Release Gateway 1.4.4 (#8096) 2025-02-11 07:22:27 -08:00
Thomas Eizinger
1847e8407a chore: release Headless Client v1.4.3 (#8093) 2025-02-11 14:10:13 +00:00
Thomas Eizinger
6093199ee3 chore: release GUI Client v1.4.4 (#8092) 2025-02-11 14:09:34 +00:00
Thomas Eizinger
fc925af6c8 chore(phoenix-channel): log the portal's IP address on connect (#8088) 2025-02-11 07:11:08 +00:00
Thomas Eizinger
6c93ce76bf chore(phoenix-channel): log all errors when connection fails (#8089)
Currently, we are only logging the last error when we fail to connect to
any of the addresses from the portal. This is often not useful because
the last one is likely to be an IPv6 address which may not be supported
on the system so all we learn is "The requested address is not valid in
its context.".
2025-02-11 05:58:32 +00:00
Thomas Eizinger
7dcda1dc74 fix(windows): silence 0x800706D9 when DNS deactivation fails (#8085)
The error code we see here means "There are no more endpoints available
from the endpoint mapper." This has something to do with Windows'
internal RPC communication between components. DNS deactivation is on a
best-effort basis and it appears that everything else is working just
fine, despite this error.

It appears to happen when we shut down our own service, so perhaps it is
just a race condition.
2025-02-11 05:38:37 +00:00
Thomas Eizinger
d7ebd07183 fix(linux): check for correct sign of netlink error code (#8087)
We've previously tried to handle the "No such process" error from
netlink when it tries to remove a route that no longer exists. What we
failed to do is use the correct sign for the error code as netlink
errors are always negative, yet when printed, the are positive numbers.
2025-02-11 04:47:51 +00:00
Thomas Eizinger
b193dd91f6 fix(windows): don't warn on disabled IP stack (#8086)
When an IP stack is programmatically disabled, such as with:

> reg add
"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"
/v DisabledComponents /t REG_DWORD /d 255 /f

Attempting to interact with this IP stack will yield "NOT_FOUND" errors.
These aren't worth reporting to Sentry because there isn't much we can
do about it.
2025-02-11 04:37:17 +00:00
Thomas Eizinger
c9b9fb0e6c feat(relay): add SOFTWARE attribute (#8076)
Adding a `SOFTWARE` attribute is recommended by the spec and will allow
us to identify from client logs, which version of the relay we are
talking to.
2025-02-11 03:34:38 +00:00
Jamil
feb1ec5e17 chore: Update client URLs & redirects for consistency (#8056)
Whenever changing a URL we care about, we add an entry in
`website/redirects.js` to avoid breaking links to the old page. Most
search engines reindex these after 1 year, but other websites and places
won't, so we should generally keep them indefinitely since they don't
cost us much to keep around.
2025-02-11 03:30:41 +00:00