fix(ios/android): Pass device name and os version as overrides over connect (#3036)

Fixes #3035 
Fixes #3037 

# Before

<img width="738" alt="Screenshot 2023-12-28 at 8 05 31 AM"
src="https://github.com/firezone/firezone/assets/167144/c7ab4d74-672c-4536-97fe-f75d8d158bfb">

<img width="546" alt="Screenshot 2023-12-28 at 6 12 30 PM"
src="https://github.com/firezone/firezone/assets/167144/1bd4ba98-d11d-4277-bd14-b0afcdf78119">

# After

<img width="742" alt="Screenshot 2023-12-28 at 10 48 31 AM"
src="https://github.com/firezone/firezone/assets/167144/96054f82-069f-47f7-862c-986455ef76c0">
<img width="744" alt="Screenshot 2023-12-28 at 6 29 37 PM"
src="https://github.com/firezone/firezone/assets/167144/4ffc19b6-7c87-4ccb-bcfe-cb0e76fe95b7">
This commit is contained in:
Jamil
2024-01-03 12:08:33 -08:00
committed by GitHub
parent 7ff2c22316
commit 1251397651
11 changed files with 108 additions and 14 deletions

View File

@@ -9,6 +9,7 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.VpnService
import android.os.Build
import android.system.OsConstants
import android.util.Log
import androidx.core.app.NotificationCompat
@@ -185,6 +186,8 @@ class TunnelService : VpnService() {
apiUrl = config.apiUrl,
token = config.token,
deviceId = deviceId(),
deviceName = Build.MODEL,
osVersion = Build.VERSION.RELEASE,
logDir = getLogDir(),
logFilter = config.logFilter,
callback = callback,

View File

@@ -6,6 +6,8 @@ object TunnelSession {
apiUrl: String,
token: String,
deviceId: String,
deviceName: String,
osVersion: String,
logDir: String,
logFilter: String,
callback: Any,

View File

@@ -382,11 +382,16 @@ macro_rules! string_from_jstring {
};
}
// TODO: Refactor this when we refactor PhoenixChannel.
// See https://github.com/firezone/firezone/issues/2158
#[allow(clippy::too_many_arguments)]
fn connect(
env: &mut JNIEnv,
api_url: JString,
token: JString,
device_id: JString,
device_name: JString,
os_version: JString,
log_dir: JString,
log_filter: JString,
callback_handler: GlobalRef,
@@ -394,6 +399,8 @@ fn connect(
let api_url = string_from_jstring!(env, api_url);
let secret = SecretString::from(string_from_jstring!(env, token));
let device_id = string_from_jstring!(env, device_id);
let device_name = string_from_jstring!(env, device_name);
let os_version = string_from_jstring!(env, os_version);
let log_dir = string_from_jstring!(env, log_dir);
let log_filter = string_from_jstring!(env, log_filter);
@@ -409,6 +416,8 @@ fn connect(
api_url.as_str(),
secret,
device_id,
Some(device_name),
Some(os_version),
callback_handler,
Duration::from_secs(5 * 60),
)?;
@@ -427,6 +436,8 @@ pub unsafe extern "system" fn Java_dev_firezone_android_tunnel_TunnelSession_con
api_url: JString,
token: JString,
device_id: JString,
device_name: JString,
os_version: JString,
log_dir: JString,
log_filter: JString,
callback_handler: JObject,
@@ -441,6 +452,8 @@ pub unsafe extern "system" fn Java_dev_firezone_android_tunnel_TunnelSession_con
api_url,
token,
device_id,
device_name,
os_version,
log_dir,
log_filter,
callback_handler,

View File

@@ -24,6 +24,8 @@ mod ffi {
api_url: String,
token: String,
device_id: String,
device_name_override: Option<String>,
os_version_override: Option<String>,
log_dir: String,
log_filter: String,
callback_handler: CallbackHandler,
@@ -174,10 +176,15 @@ fn init_logging(log_dir: PathBuf, log_filter: String) -> file_logger::Handle {
}
impl WrappedSession {
// TODO: Refactor this when we refactor PhoenixChannel.
// See https://github.com/firezone/firezone/issues/2158
#[allow(clippy::too_many_arguments)]
fn connect(
api_url: String,
token: String,
device_id: String,
device_name_override: Option<String>,
os_version_override: Option<String>,
log_dir: String,
log_filter: String,
callback_handler: ffi::CallbackHandler,
@@ -188,6 +195,8 @@ impl WrappedSession {
api_url.as_str(),
secret,
device_id,
device_name_override,
os_version_override,
CallbackHandler {
inner: Arc::new(callback_handler),
handle: init_logging(log_dir.into(), log_filter),

View File

@@ -65,6 +65,8 @@ where
api_url: impl TryInto<Url>,
token: SecretString,
device_id: String,
device_name_override: Option<String>,
os_version_override: Option<String>,
callbacks: CB,
max_partition_time: Duration,
) -> Result<Self> {
@@ -111,6 +113,8 @@ where
api_url.try_into().map_err(|_| Error::UriError)?,
token,
device_id,
device_name_override,
os_version_override,
this.callbacks.clone(),
max_partition_time,
);
@@ -122,18 +126,23 @@ where
Ok(this)
}
// TODO: Refactor this when we refactor PhoenixChannel.
// See https://github.com/firezone/firezone/issues/2158
#[allow(clippy::too_many_arguments)]
fn connect_inner(
runtime: &Runtime,
runtime_stopper: tokio::sync::mpsc::Sender<StopRuntime>,
api_url: Url,
token: SecretString,
device_id: String,
device_name_override: Option<String>,
os_version_override: Option<String>,
callbacks: CallbackErrorFacade<CB>,
max_partition_time: Duration,
) {
runtime.spawn(async move {
let (connect_url, private_key) = fatal_error!(
login_url(Mode::Client, api_url, token, device_id, None),
login_url(Mode::Client, api_url, token, device_id, device_name_override),
runtime_stopper,
&callbacks
);
@@ -143,7 +152,7 @@ where
// to force queue ordering.
let (control_plane_sender, mut control_plane_receiver) = tokio::sync::mpsc::channel(1);
let mut connection = PhoenixChannel::<_, IngressMessages, ReplyMessages, Messages>::new(Secret::new(SecureUrl::from_url(connect_url)), move |msg, reference, topic| {
let mut connection = PhoenixChannel::<_, IngressMessages, ReplyMessages, Messages>::new(Secret::new(SecureUrl::from_url(connect_url)), os_version_override, move |msg, reference, topic| {
let control_plane_sender = control_plane_sender.clone();
async move {
tracing::trace!(?msg);

View File

@@ -64,6 +64,7 @@ impl secrecy::Zeroize for SecureUrl {
/// `await` it, it will block until you use `close` in a [PhoenixSender], the portal close the connection or something goes wrong.
pub struct PhoenixChannel<F, I, R, M> {
secret_url: Secret<SecureUrl>,
os_version_override: Option<String>,
handler: F,
sender: Sender<Message>,
receiver: Receiver<Message>,
@@ -71,7 +72,10 @@ pub struct PhoenixChannel<F, I, R, M> {
}
// This is basically the same as tungstenite does but we add some new headers (namely user-agent)
fn make_request(secret_url: &Secret<SecureUrl>) -> Result<Request> {
fn make_request(
secret_url: &Secret<SecureUrl>,
os_version_override: Option<String>,
) -> Result<Request> {
use secrecy::ExposeSecret;
let host = secret_url
@@ -96,7 +100,7 @@ fn make_request(secret_url: &Secret<SecureUrl>) -> Result<Request> {
.header("Upgrade", "websocket")
.header("Sec-WebSocket-Version", "13")
.header("Sec-WebSocket-Key", key)
.header("User-Agent", get_user_agent())
.header("User-Agent", get_user_agent(os_version_override))
.uri(secret_url.expose_secret().inner.as_str())
.body(())?;
Ok(req)
@@ -127,7 +131,11 @@ where
) -> Result<()> {
tracing::trace!("Trying to connect to portal...");
let (ws_stream, _) = connect_async(make_request(&self.secret_url)?).await?;
let (ws_stream, _) = connect_async(make_request(
&self.secret_url,
self.os_version_override.clone(),
)?)
.await?;
tracing::trace!("Successfully connected to portal");
@@ -269,13 +277,18 @@ where
/// - `handler`: The handle that will be called for each received message.
///
/// For more info see [struct-level docs][PhoenixChannel].
pub fn new(secret_url: Secret<SecureUrl>, handler: F) -> Self {
pub fn new(
secret_url: Secret<SecureUrl>,
os_version_override: Option<String>,
handler: F,
) -> Self {
let (sender, receiver) = channel(CHANNEL_SIZE);
Self {
sender,
receiver,
secret_url,
os_version_override,
handler,
_phantom: PhantomData,
}

View File

@@ -75,15 +75,21 @@ pub enum Mode {
Gateway,
}
pub fn get_user_agent() -> String {
pub fn get_user_agent(os_version_override: Option<String>) -> String {
// Note: we could switch to sys-info and get the hostname
// but we lose the arch
// and neither of the libraries provide the kernel version.
// so I rather keep os_info which seems like the most popular
// and keep implementing things that we are missing on top
let info = os_info::get();
// iOS returns "Unknown", but we already know we're on iOS here
#[cfg(target_os = "ios")]
let os_type = "iOS";
#[cfg(not(target_os = "ios"))]
let os_type = info.os_type();
let os_version = info.version();
let os_version = os_version_override.unwrap_or(info.version().to_string());
let additional_info = additional_info();
let lib_version = VERSION;
let lib_name = LIB_NAME;

View File

@@ -173,7 +173,7 @@ async fn connect_to_portal(
loop {
let result = phoenix_channel::init::<InitGateway, _, _>(
Secret::new(SecureUrl::from_url(connect_url.clone())),
get_user_agent(),
get_user_agent(None),
PHOENIX_TOPIC,
(),
)

View File

@@ -15,6 +15,8 @@ fn main() -> Result<()> {
cli.common.api_url,
SecretString::from(cli.common.token),
cli.firezone_id,
None,
None,
CallbackHandler { handle },
cli.max_partition_time.into(),
)

View File

@@ -395,6 +395,8 @@ impl Controller {
self.advanced_settings.api_url.clone(),
auth_info.token.clone(),
self.device_id.clone(),
None, // TODO: Send device name here (windows computer name)
None,
callback_handler.clone(),
Duration::from_secs(5 * 60),
)?;

View File

@@ -143,8 +143,15 @@ class Adapter {
do {
self.state = .startingTunnel(
session: try WrappedSession.connect(
self.controlPlaneURLString, self.token, self.getDeviceId(), self.connlibLogFolderPath,
self.logFilter, self.callbackHandler),
self.controlPlaneURLString,
self.token,
self.getDeviceId(),
self.getDeviceName(),
self.getOSVersion(),
self.connlibLogFolderPath,
self.logFilter,
self.callbackHandler
),
onStarted: completionHandler
)
} catch let error {
@@ -219,9 +226,31 @@ class Adapter {
}
}
// MARK: Device unique identifiers
// MARK: Device metadata
extension Adapter {
func getDeviceName() -> String? {
// Returns a generic device name on iOS 16 and higher
// See https://github.com/firezone/firezone/issues/3034
#if os(iOS)
return UIDevice.current.name
#else
// Fallback to connlib's gethostname()
return nil
#endif
}
func getOSVersion() -> String? {
// Returns the OS version
// See https://github.com/firezone/firezone/issues/3034
#if os(iOS)
return UIDevice.current.systemVersion
#else
// Fallback to connlib's osinfo
return nil
#endif
}
// uuidString and copyMACAddress() *should* reliably return valid Strings, but if
// for whatever reason they're nil, return a random UUID instead to prevent
// upsert collisions in the portal.
@@ -305,9 +334,15 @@ extension Adapter {
do {
self.state = .startingTunnel(
session: try WrappedSession.connect(
controlPlaneURLString, token, self.getDeviceId(), self.connlibLogFolderPath,
controlPlaneURLString,
token,
self.getDeviceId(),
self.getDeviceName(),
self.getOSVersion(),
self.connlibLogFolderPath,
self.logFilter,
self.callbackHandler),
self.callbackHandler
),
onStarted: { error in
if let error = error {
self.logger.error(