fix(clients): set Internet Resource state on startup (#10509)

Building on top of #10507, setting the initial Internet Resource state
is a piece of cake. All we need to do is thread a boolean variable
through to all call-sites of `Session::connect`. Without the need for
the Internet Resource's ID, we can simply pass in the boolean that is
saved in the configuration of each client.

Resolves: #10255
This commit is contained in:
Thomas Eizinger
2025-10-07 07:13:52 +00:00
committed by GitHub
parent 36dfee2c42
commit 8fc2ef8ad1
17 changed files with 112 additions and 31 deletions

View File

@@ -296,6 +296,7 @@ class TunnelService : VpnService() {
osVersion = Build.VERSION.RELEASE,
logDir = getLogDir(),
logFilter = config.logFilter,
isInternetResourceActive = resourceState.isEnabled(),
protectSocket = protectSocket,
deviceInfo = gson.toJson(deviceInfo),
).use { session ->

View File

@@ -65,6 +65,7 @@ mod ffi {
os_version_override: Option<String>,
log_dir: String,
log_filter: String,
is_internet_resource_active: bool,
callback_handler: CallbackHandler,
device_info: String,
) -> Result<WrappedSession, String>;
@@ -250,6 +251,7 @@ impl WrappedSession {
os_version_override: Option<String>,
log_dir: String,
log_filter: String,
is_internet_resource_active: bool,
callback_handler: ffi::CallbackHandler,
device_info: String,
) -> Result<Self> {
@@ -302,6 +304,7 @@ impl WrappedSession {
Arc::new(socket_factory::tcp),
Arc::new(socket_factory::udp),
portal,
is_internet_resource_active,
runtime.handle().clone(),
);
session.set_tun(Box::new(Tun::new()?));

View File

@@ -94,6 +94,7 @@ impl Session {
log_dir: String,
log_filter: String,
device_info: String,
is_internet_resource_active: bool,
protect_socket: Arc<dyn ProtectSocket>,
) -> Result<Self, Error> {
let udp_socket_factory = Arc::new(protected_udp_socket_factory(protect_socket.clone()));
@@ -109,6 +110,7 @@ impl Session {
log_dir,
log_filter,
device_info,
is_internet_resource_active,
tcp_socket_factory,
udp_socket_factory,
)
@@ -227,6 +229,7 @@ fn connect(
log_dir: String,
log_filter: String,
device_info: String,
is_internet_resource_active: bool,
tcp_socket_factory: Arc<dyn SocketFactory<TcpSocket>>,
udp_socket_factory: Arc<dyn SocketFactory<UdpSocket>>,
) -> Result<Session, Error> {
@@ -278,6 +281,7 @@ fn connect(
tcp_socket_factory,
udp_socket_factory,
portal,
is_internet_resource_active,
runtime.handle().clone(),
);

View File

@@ -98,6 +98,7 @@ impl Eventloop {
pub(crate) fn new(
tcp_socket_factory: Arc<dyn SocketFactory<TcpSocket>>,
udp_socket_factory: Arc<dyn SocketFactory<UdpSocket>>,
is_internet_resource_active: bool,
mut portal: PhoenixChannel<(), IngressMessages, PublicKeyParam>,
cmd_rx: mpsc::UnboundedReceiver<Command>,
resource_list_sender: watch::Sender<Vec<ResourceView>>,
@@ -110,6 +111,7 @@ impl Eventloop {
tcp_socket_factory,
udp_socket_factory,
DNS_RESOURCE_RECORDS_CACHE.lock().clone(),
is_internet_resource_active,
);
portal.connect(PublicKeyParam(tunnel.public_key().to_bytes()));

View File

@@ -58,6 +58,7 @@ impl Session {
tcp_socket_factory: Arc<dyn SocketFactory<TcpSocket>>,
udp_socket_factory: Arc<dyn SocketFactory<UdpSocket>>,
portal: PhoenixChannel<(), IngressMessages, PublicKeyParam>,
is_internet_resource_active: bool,
handle: tokio::runtime::Handle,
) -> (Self, EventStream) {
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
@@ -70,6 +71,7 @@ impl Session {
Eventloop::new(
tcp_socket_factory,
udp_socket_factory,
is_internet_resource_active,
portal,
cmd_rx,
resource_list_sender,

View File

@@ -208,3 +208,5 @@ cc fdfbc47a6e26893cf317a3e2dd0a95a7d07a3fc1d7c19accbf20b5582e9453e4
cc 5ca1f7f615da306a4bbcd7f1300459288600ed1740c650fa75fdefade2d789d7
cc 71380222e92940bac1d7a6ab8b13eb7180da63481c08c7c0b168c0007e02906d
cc d2a87f2284b2ae5a2206bd7091aa31015599bd8301e19644a860e46c0113dafb
cc 3927e6609d6e4ec9562281e319e266d1df652b5f825ad3fe867146bab7ffa045
cc 5f9aab8caf96d39aae4869a153a16d32193ad91a9c0095233e3a145b85780e08

View File

@@ -38,7 +38,7 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::num::NonZeroUsize;
use std::ops::ControlFlow;
use std::time::{Duration, Instant};
use std::{io, iter, mem};
use std::{io, iter};
pub(crate) const IPV4_RESOURCES: Ipv4Network =
match Ipv4Network::new(Ipv4Addr::new(100, 96, 0, 0), 11) {
@@ -186,7 +186,12 @@ impl PendingFlow {
}
impl ClientState {
pub(crate) fn new(seed: [u8; 32], records: BTreeSet<DnsResourceRecord>, now: Instant) -> Self {
pub(crate) fn new(
seed: [u8; 32],
records: BTreeSet<DnsResourceRecord>,
is_internet_resource_active: bool,
now: Instant,
) -> Self {
Self {
resources_gateways: Default::default(),
active_cidr_resources: IpNetworkTable::new(),
@@ -203,7 +208,7 @@ impl ClientState {
udp_dns_sockets_by_upstream_and_query_id: Default::default(),
stub_resolver: StubResolver::new(records),
buffered_transmits: Default::default(),
is_internet_resource_active: false,
is_internet_resource_active,
recently_connected_gateways: LruCache::new(MAX_REMEMBERED_GATEWAYS),
upstream_dns: Default::default(),
buffered_dns_queries: Default::default(),
@@ -1696,7 +1701,7 @@ impl ClientState {
None => true,
}
}
Resource::Internet(_) => !mem::replace(&mut self.is_internet_resource_active, true),
Resource::Internet(_) => self.is_internet_resource_active,
};
if activated {
@@ -2167,7 +2172,7 @@ mod tests {
impl ClientState {
pub fn for_test() -> ClientState {
ClientState::new(rand::random(), Default::default(), Instant::now())
ClientState::new(rand::random(), Default::default(), false, Instant::now())
}
}

View File

@@ -114,6 +114,7 @@ impl ClientTunnel {
tcp_socket_factory: Arc<dyn SocketFactory<TcpSocket>>,
udp_socket_factory: Arc<dyn SocketFactory<UdpSocket>>,
records: BTreeSet<DnsResourceRecord>,
is_internet_resource_active: bool,
) -> Self {
Self {
io: Io::new(
@@ -121,7 +122,12 @@ impl ClientTunnel {
udp_socket_factory.clone(),
BTreeSet::default(),
),
role_state: ClientState::new(rand::random(), records, Instant::now()),
role_state: ClientState::new(
rand::random(),
records,
is_internet_resource_active,
Instant::now(),
),
buffers: Buffers::default(),
packet_counter: opentelemetry::global::meter("connlib")
.u64_counter("system.network.packets")

View File

@@ -105,7 +105,12 @@ impl SimClient {
}
}
pub(crate) fn restart(&mut self, key: PrivateKey, now: Instant) {
pub(crate) fn restart(
&mut self,
key: PrivateKey,
is_internet_resource_active: bool,
now: Instant,
) {
let dns_resource_records = self.dns_resource_record_cache.clone();
// Overwrite the ClientState with a new key.
@@ -113,7 +118,12 @@ impl SimClient {
//
// We keep all the state in `SimClient` which is equivalent to host system.
// That is where we cache resolved DNS names for example.
self.sut = ClientState::new(key.0, dns_resource_records, now);
self.sut = ClientState::new(
key.0,
dns_resource_records,
is_internet_resource_active,
now,
);
self.search_domain = None;
self.dns_by_sentinel.clear();
@@ -435,8 +445,7 @@ pub struct RefClient {
#[debug(skip)]
resources: Vec<Resource>,
#[debug(skip)]
internet_resource_active: bool,
pub(crate) internet_resource_active: bool,
/// The CIDR resources the client is aware of.
#[debug(skip)]
@@ -492,7 +501,12 @@ impl RefClient {
///
/// This simulates receiving the `init` message from the portal.
pub(crate) fn init(self, now: Instant) -> SimClient {
let mut client_state = ClientState::new(self.key.0, Default::default(), now); // Cheating a bit here by reusing the key as seed.
let mut client_state = ClientState::new(
self.key.0,
Default::default(),
self.internet_resource_active,
now,
); // Cheating a bit here by reusing the key as seed.
client_state.update_interface_config(Interface {
ipv4: self.tunnel_ip4,
ipv6: self.tunnel_ip6,
@@ -627,12 +641,13 @@ impl RefClient {
pub(crate) fn add_internet_resource(&mut self, resource: InternetResource) {
self.resources.push(Resource::Internet(resource.clone()));
self.internet_resource_active = true;
self.ipv4_routes
.insert(resource.id, Ipv4Network::DEFAULT_ROUTE);
self.ipv6_routes
.insert(resource.id, Ipv6Network::DEFAULT_ROUTE);
if self.internet_resource_active {
self.ipv4_routes
.insert(resource.id, Ipv4Network::DEFAULT_ROUTE);
self.ipv6_routes
.insert(resource.id, Ipv6Network::DEFAULT_ROUTE);
}
}
pub(crate) fn add_cidr_resource(&mut self, r: CidrResource) {
@@ -656,7 +671,6 @@ impl RefClient {
/// Re-adds all resources in the order they have been initially added.
pub(crate) fn readd_all_resources(&mut self) {
self.cidr_resources = IpNetworkTable::new();
self.internet_resource_active = false;
for resource in mem::take(&mut self.resources) {
match resource {
@@ -1256,6 +1270,7 @@ fn ref_client(
system_dns,
upstream_dns,
search_domain,
any::<bool>(),
client_id(),
private_key(),
)
@@ -1266,6 +1281,7 @@ fn ref_client(
system_dns_resolvers,
upstream_dns_resolvers,
search_domain,
internet_resource_active,
id,
key,
)| {
@@ -1277,7 +1293,7 @@ fn ref_client(
system_dns_resolvers,
upstream_dns_resolvers,
search_domain,
internet_resource_active: Default::default(),
internet_resource_active,
cidr_resources: IpNetworkTable::new(),
dns_records: Default::default(),
connected_cidr_resources: Default::default(),

View File

@@ -373,14 +373,12 @@ impl TunnelTest {
let system_dns = ref_state.client.inner().system_dns_resolvers();
let upstream_dns = ref_state.client.inner().upstream_dns_resolvers();
let all_resources = ref_state.client.inner().all_resources();
let internet_resource_state = ref_state.client.inner().active_internet_resource();
let internet_resource_state = ref_state.client.inner().internet_resource_active;
state.client.exec_mut(|c| {
c.restart(key, now);
c.restart(key, internet_resource_state, now);
// Apply to new instance.
c.sut
.set_internet_resource_state(internet_resource_state.is_some(), now);
c.sut.update_interface_config(Interface {
ipv4,
ipv6,

View File

@@ -332,6 +332,7 @@ impl<I: GuiIntegration> Controller<I> {
self.send_ipc(&service::ClientMsg::Connect {
api_url: api_url.to_string(),
token: token.expose_secret().clone(),
is_internet_resource_active: self.general_settings.internet_resource_enabled(),
})
.await?;

View File

@@ -53,6 +53,7 @@ pub enum ClientMsg {
Connect {
api_url: String,
token: String,
is_internet_resource_active: bool,
},
Disconnect,
ApplyLogFilter {
@@ -191,6 +192,7 @@ enum Session {
WaitingForNetwork {
api_url: String,
token: SecretString,
is_internet_resource_active: bool,
},
#[default]
None,
@@ -372,10 +374,18 @@ impl<'a> Handler<'a> {
Session::Connected { connlib, .. } => {
connlib.reset("network changed".to_owned());
}
Session::WaitingForNetwork { api_url, token } => {
Session::WaitingForNetwork {
api_url,
token,
is_internet_resource_active,
} => {
tracing::info!("Attempting to re-connect upon network change");
let result = self.try_connect(&api_url.clone(), token.clone());
let result = self.try_connect(
&api_url.clone(),
token.clone(),
*is_internet_resource_active,
);
if let Some(e) = result
.as_ref()
@@ -501,14 +511,18 @@ impl<'a> Handler<'a> {
self.send_ipc(ServerMsg::ClearedLogs(result.map_err(|e| e.to_string())))
.await?
}
ClientMsg::Connect { api_url, token } => {
ClientMsg::Connect {
api_url,
token,
is_internet_resource_active,
} => {
let token = SecretString::new(token);
if !self.session.is_none() {
tracing::debug!(session = ?self.session, "Connecting despite existing session");
}
let result = self.try_connect(&api_url, token.clone());
let result = self.try_connect(&api_url, token.clone(), is_internet_resource_active);
if let Some(e) = result
.as_ref()
@@ -518,7 +532,11 @@ impl<'a> Handler<'a> {
tracing::debug!(
"Encountered IO error when connecting to portal, most likely we don't have Internet: {e}"
);
self.session = Session::WaitingForNetwork { api_url, token };
self.session = Session::WaitingForNetwork {
api_url,
token,
is_internet_resource_active,
};
return Ok(());
}
@@ -598,7 +616,12 @@ impl<'a> Handler<'a> {
Ok(())
}
fn try_connect(&mut self, api_url: &str, token: SecretString) -> Result<Session> {
fn try_connect(
&mut self,
api_url: &str,
token: SecretString,
is_internet_resource_active: bool,
) -> Result<Session> {
let started_at = Instant::now();
let device_id = device_id::get_or_create().context("Failed to get-or-create device ID")?;
@@ -636,6 +659,7 @@ impl<'a> Handler<'a> {
Arc::new(tcp_socket_factory),
Arc::new(UdpSocketFactory::default()),
portal,
is_internet_resource_active,
tokio::runtime::Handle::current(),
);

View File

@@ -341,6 +341,7 @@ fn try_main() -> Result<()> {
Arc::new(tcp_socket_factory),
Arc::new(UdpSocketFactory::default()),
portal,
false,
rt.handle().clone(),
);

View File

@@ -209,6 +209,7 @@ class Adapter {
DeviceMetadata.getOSVersion(),
connlibLogFolderPath,
logFilter,
internetResourceEnabled,
callbackHandler,
String(data: jsonEncoder.encode(DeviceMetadata.deviceInfo()), encoding: .utf8)!
)

View File

@@ -20,7 +20,12 @@ export default function Android() {
return (
<Entries downloadLinks={downloadLinks} title="Android">
{/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This must not be done when the issue's PR merges. */}
<Unreleased></Unreleased>
<Unreleased>
<ChangeItem pull="10509">
Fixes an issue where the Internet Resource could be briefly active on
startup, despite it being disabled.
</ChangeItem>
</Unreleased>
<Entry version="1.5.4" date={new Date("2025-09-18")}>
<ChangeItem pull="10371">
Fixes a bug that could prevent sign-ins from completing successfully

View File

@@ -24,7 +24,12 @@ export default function Apple() {
return (
<Entries downloadLinks={downloadLinks} title="macOS / iOS">
{/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This must not be done when the issue's PR merges. */}
<Unreleased></Unreleased>
<Unreleased>
<ChangeItem pull="10509">
Fixes an issue where the Internet Resource could be briefly active on
startup, despite it being disabled.
</ChangeItem>
</Unreleased>
<Entry version="1.5.8" date={new Date("2025-09-10")}>
<ChangeItem pull="10313">
Fixes an issue where multiple concurrent Firezone macOS clients could

View File

@@ -10,7 +10,12 @@ export default function GUI({ os }: { os: OS }) {
return (
<Entries downloadLinks={downloadLinks(os)} title={title(os)}>
{/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This must not be done when the issue's PR merges. */}
<Unreleased></Unreleased>
<Unreleased>
<ChangeItem pull="10509">
Fixes an issue where the Internet Resource could be briefly active on
startup, despite it being disabled.
</ChangeItem>
</Unreleased>
<Entry version="1.5.7" date={new Date("2025-09-10")}>
<ChangeItem pull="10104">
Fixes an issue where DNS resources would resolve to a different IP