From 7e196683a1e186989e4ae1cd7d09e275697fe6c3 Mon Sep 17 00:00:00 2001 From: Jamil Date: Thu, 13 Mar 2025 23:54:44 -0500 Subject: [PATCH] feat(android): set search-domain on VPN configuration (#8436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Android, we can use [`addSearchDomain`](https://developer.android.com/reference/android/net/VpnService.Builder#addSearchDomain(java.lang.String)) to configure the search domain list for our VPN tunnel. Thankfully, this gets applied to the system resolver without any other hackery involved (unlike for Apple in #8421), and most apps use the system resolver for queries. The one exception to this are some network utilities like AndroDNS and Fing. Tested to work fine in Termux using `github.io` as the search domain, which responds to ICMP echoes to any subdomain: Screenshot 2025-03-13 at 10 19 41 PM Related #8410 --------- Co-authored-by: Thomas Eizinger --- .../firezone/android/tunnel/TunnelService.kt | 7 ++++++ .../tunnel/callback/ConnlibCallback.kt | 1 + rust/connlib/clients/android/src/lib.rs | 25 +++++++++++++++++-- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt index 0b87dc4a3..62b61293c 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/TunnelService.kt @@ -58,6 +58,7 @@ class TunnelService : VpnService() { var tunnelIpv4Address: String? = null var tunnelIpv6Address: String? = null private var tunnelDnsAddresses: MutableList = mutableListOf() + private var tunnelSearchDomain: String? = null private var tunnelRoutes: MutableList = mutableListOf() private var _tunnelResources: List = emptyList() private var _tunnelState: State = State.DOWN @@ -116,6 +117,7 @@ class TunnelService : VpnService() { addressIPv4: String, addressIPv6: String, dnsAddresses: String, + searchDomain: String?, routes4JSON: String, routes6JSON: String, ) { @@ -124,6 +126,7 @@ class TunnelService : VpnService() { val routes4 = moshi.adapter>().fromJson(routes4JSON)!! val routes6 = moshi.adapter>().fromJson(routes6JSON)!! + tunnelSearchDomain = searchDomain tunnelIpv4Address = addressIPv4 tunnelIpv6Address = addressIPv6 tunnelRoutes.clear() @@ -207,6 +210,10 @@ class TunnelService : VpnService() { addDnsServer(dns) } + tunnelSearchDomain?.let { + addSearchDomain(it) + } + addAddress(tunnelIpv4Address!!, 32) addAddress(tunnelIpv6Address!!, 128) }.establish() diff --git a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/callback/ConnlibCallback.kt b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/callback/ConnlibCallback.kt index 1a3be84f7..6faaa0a61 100644 --- a/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/callback/ConnlibCallback.kt +++ b/kotlin/android/app/src/main/java/dev/firezone/android/tunnel/callback/ConnlibCallback.kt @@ -6,6 +6,7 @@ interface ConnlibCallback { addressIPv4: String, addressIPv6: String, dnsAddresses: String, + searchDomain: String?, routes4JSON: String, routes6JSON: String, ) diff --git a/rust/connlib/clients/android/src/lib.rs b/rust/connlib/clients/android/src/lib.rs index 4865487cd..1dcd036e0 100644 --- a/rust/connlib/clients/android/src/lib.rs +++ b/rust/connlib/clients/android/src/lib.rs @@ -171,7 +171,7 @@ impl Callbacks for CallbackHandler { tunnel_address_v4: Ipv4Addr, tunnel_address_v6: Ipv6Addr, dns_addresses: Vec, - _search_domain: Option, + search_domain: Option, route_list_4: Vec, route_list_6: Vec, ) { @@ -194,6 +194,16 @@ impl Callbacks for CallbackHandler { name: "dns_addresses", source, })?; + let search_domain = search_domain + .map(|domain| { + env.new_string(domain.to_string()) + .map_err(|source| CallbackError::NewStringFailed { + name: "search_domain", + source, + }) + }) + .transpose()? + .unwrap_or_default(); let route_list_4 = env .new_string(serde_json::to_string(&V4RouteList::new(route_list_4))?) .map_err(|source| CallbackError::NewStringFailed { @@ -211,11 +221,12 @@ impl Callbacks for CallbackHandler { env.call_method( &self.callback_handler, name, - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", &[ JValue::from(&tunnel_address_v4), JValue::from(&tunnel_address_v6), JValue::from(&dns_addresses), + JValue::from(&search_domain), JValue::from(&route_list_4), JValue::from(&route_list_6), ], @@ -566,3 +577,13 @@ fn install_rustls_crypto_provider() { tracing::debug!("Skipping install of crypto provider because we already have one."); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default_jstring_is_null() { + assert!(JString::default().is_null()) + } +}