mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
refactor(client/windows): de-dupe wintun.dll (#6020)
Closes #5977 Refactored some other stuff to make this work Also removed a redundant impl of `ensure_dll` in a benchmark
This commit is contained in:
8
rust/Cargo.lock
generated
8
rust/Cargo.lock
generated
@@ -1078,7 +1078,6 @@ dependencies = [
|
||||
"ip-packet",
|
||||
"ip_network",
|
||||
"itertools 0.13.0",
|
||||
"known-folders",
|
||||
"libc",
|
||||
"os_info",
|
||||
"phoenix-channel",
|
||||
@@ -1095,8 +1094,6 @@ dependencies = [
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"windows 0.57.0",
|
||||
"wintun",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1790,9 +1787,11 @@ dependencies = [
|
||||
"futures",
|
||||
"ip-packet",
|
||||
"ip_network",
|
||||
"known-folders",
|
||||
"libc",
|
||||
"netlink-packet-core",
|
||||
"netlink-packet-route",
|
||||
"ring",
|
||||
"rtnetlink",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -1851,9 +1850,9 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"connlib-client-shared",
|
||||
"connlib-shared",
|
||||
"crash-handler",
|
||||
"dirs",
|
||||
"firezone-bin-shared",
|
||||
"firezone-headless-client",
|
||||
"futures",
|
||||
"git-version",
|
||||
@@ -1917,7 +1916,6 @@ dependencies = [
|
||||
"nix 0.28.0",
|
||||
"phoenix-channel",
|
||||
"resolv-conf",
|
||||
"ring",
|
||||
"rtnetlink",
|
||||
"sd-notify",
|
||||
"secrecy",
|
||||
|
||||
@@ -29,8 +29,10 @@ rtnetlink = { workspace = true }
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
wintun = "0.4.0"
|
||||
known-folders = "1.1.0"
|
||||
ring = "0.17"
|
||||
uuid = { version = "1.10.0", features = ["v4"] }
|
||||
wintun = "0.4.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.57.0"
|
||||
|
||||
@@ -40,15 +40,6 @@ mod platform {
|
||||
use tun::Tun as _;
|
||||
|
||||
pub(crate) async fn perf() -> Result<()> {
|
||||
// Install wintun so the test can run
|
||||
let wintun_path = connlib_shared::windows::wintun_dll_path().unwrap();
|
||||
tokio::fs::create_dir_all(wintun_path.parent().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
tokio::fs::write(&wintun_path, connlib_shared::windows::wintun_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
const MTU: usize = 1_280;
|
||||
const NUM_REQUESTS: u64 = 1_000;
|
||||
const REQ_CODE: u8 = 42;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
mod tun_device_manager;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
|
||||
use clap::Args;
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_subscriber::{
|
||||
@@ -7,6 +10,21 @@ use tracing_subscriber::{
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
/// Bundle ID / App ID that the client uses to distinguish itself from other programs on the system
|
||||
///
|
||||
/// e.g. In ProgramData and AppData we use this to name our subdirectories for configs and data,
|
||||
/// and Windows may use it to track things like the MSI installer, notification titles,
|
||||
/// deep link registration, etc.
|
||||
///
|
||||
/// This should be identical to the `tauri.bundle.identifier` over in `tauri.conf.json`,
|
||||
/// but sometimes I need to use this before Tauri has booted up, or in a place where
|
||||
/// getting the Tauri app handle would be awkward.
|
||||
///
|
||||
/// Luckily this is also the AppUserModelId that Windows uses to label notifications,
|
||||
/// so if your dev system has Firezone installed by MSI, the notifications will look right.
|
||||
/// <https://learn.microsoft.com/en-us/windows/configuration/find-the-application-user-model-id-of-an-installed-app>
|
||||
pub const BUNDLE_ID: &str = "dev.firezone.client";
|
||||
|
||||
/// Mark for Firezone sockets to prevent routing loops on Linux.
|
||||
pub const FIREZONE_MARK: u32 = 0xfd002021;
|
||||
|
||||
|
||||
@@ -27,18 +27,6 @@ mod tests {
|
||||
.with_test_writer()
|
||||
.try_init();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Install wintun so the test can run
|
||||
let wintun_path = connlib_shared::windows::wintun_dll_path().unwrap();
|
||||
tokio::fs::create_dir_all(wintun_path.parent().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
tokio::fs::write(&wintun_path, connlib_shared::windows::wintun_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Run these tests in series since they would fight over the tunnel interface
|
||||
// if they ran concurrently
|
||||
create_tun();
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use crate::windows::CREATE_NO_WINDOW;
|
||||
use anyhow::{Context as _, Result};
|
||||
use connlib_shared::{
|
||||
windows::{CREATE_NO_WINDOW, TUNNEL_NAME},
|
||||
DEFAULT_MTU,
|
||||
};
|
||||
use connlib_shared::DEFAULT_MTU;
|
||||
use ip_network::{IpNetwork, Ipv4Network, Ipv6Network};
|
||||
use ring::digest;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
io,
|
||||
io::{self, Read as _},
|
||||
net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6},
|
||||
os::windows::process::CommandExt,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
@@ -27,8 +27,9 @@ use windows::Win32::{
|
||||
};
|
||||
use wintun::Adapter;
|
||||
|
||||
// Not sure how this and `TUNNEL_NAME` differ
|
||||
const ADAPTER_NAME: &str = "Firezone";
|
||||
// wintun automatically append " Tunnel" to this
|
||||
pub(crate) const TUNNEL_NAME: &str = "Firezone";
|
||||
|
||||
/// The ring buffer size used for Wintun.
|
||||
///
|
||||
/// Must be a power of two within a certain range <https://docs.rs/wintun/latest/wintun/struct.Adapter.html#method.start_session>
|
||||
@@ -211,16 +212,15 @@ impl Tun {
|
||||
pub fn new() -> Result<Self> {
|
||||
const TUNNEL_UUID: &str = "e9245bc1-b8c1-44ca-ab1d-c6aad4f13b9c";
|
||||
|
||||
// SAFETY: we're loading a DLL from disk and it has arbitrary C code in it.
|
||||
// The Windows client, in `wintun_install` hashes the DLL at startup, before calling connlib, so it's unlikely for the DLL to be accidentally corrupted by the time we get here.
|
||||
let path = connlib_shared::windows::wintun_dll_path()?;
|
||||
let path = ensure_dll()?;
|
||||
// SAFETY: we're loading a DLL from disk and it has arbitrary C code in it. There's no perfect way to prove it's safe.
|
||||
let wintun = unsafe { wintun::load_from_path(path) }?;
|
||||
|
||||
// Create wintun adapter
|
||||
let uuid = uuid::Uuid::from_str(TUNNEL_UUID)
|
||||
.expect("static UUID should always parse correctly")
|
||||
.as_u128();
|
||||
let adapter = &Adapter::create(&wintun, ADAPTER_NAME, TUNNEL_NAME, Some(uuid))?;
|
||||
let adapter = &Adapter::create(&wintun, TUNNEL_NAME, TUNNEL_NAME, Some(uuid))?;
|
||||
let iface_idx = adapter.get_adapter_index()?;
|
||||
|
||||
set_iface_config(adapter.get_luid(), DEFAULT_MTU as u32)?;
|
||||
@@ -394,3 +394,82 @@ fn set_iface_config(luid: wintun::NET_LUID_LH, mtu: u32) -> Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Installs the DLL in %LOCALAPPDATA% and returns the DLL's absolute path
|
||||
///
|
||||
/// e.g. `C:\Users\User\AppData\Local\dev.firezone.client\data\wintun.dll`
|
||||
/// Also verifies the SHA256 of the DLL on-disk with the expected bytes packed into the exe
|
||||
fn ensure_dll() -> Result<PathBuf> {
|
||||
let dll_bytes = wintun_bytes();
|
||||
|
||||
let path = wintun_dll_path().context("Can't compute wintun.dll path")?;
|
||||
// The DLL path should always have a parent
|
||||
let dir = path.parent().context("wintun.dll path invalid")?;
|
||||
std::fs::create_dir_all(dir).context("Can't create dirs for wintun.dll")?;
|
||||
|
||||
tracing::debug!(?path, "wintun.dll path");
|
||||
|
||||
// This hash check is not meant to protect against attacks. It only lets us skip redundant disk writes, and it updates the DLL if needed.
|
||||
// `tun_windows.rs` in connlib, and `elevation.rs`, rely on thia.
|
||||
if dll_already_exists(&path, &dll_bytes) {
|
||||
return Ok(path);
|
||||
}
|
||||
std::fs::write(&path, dll_bytes.bytes).context("Failed to write wintun.dll")?;
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn dll_already_exists(path: &Path, dll_bytes: &DllBytes) -> bool {
|
||||
let mut f = match std::fs::File::open(path) {
|
||||
Err(_) => return false,
|
||||
Ok(x) => x,
|
||||
};
|
||||
|
||||
let actual_len = usize::try_from(f.metadata().unwrap().len()).unwrap();
|
||||
let expected_len = dll_bytes.bytes.len();
|
||||
// If the dll is 100 MB instead of 0.5 MB, this allows us to skip a 100 MB read
|
||||
if actual_len != expected_len {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut buf = vec![0u8; expected_len];
|
||||
if f.read_exact(&mut buf).is_err() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let expected = ring::test::from_hex(dll_bytes.expected_sha256).unwrap();
|
||||
let actual = digest::digest(&digest::SHA256, &buf);
|
||||
expected == actual.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the absolute path for installing and loading `wintun.dll`
|
||||
///
|
||||
/// e.g. `C:\Users\User\AppData\Local\dev.firezone.client\data\wintun.dll`
|
||||
fn wintun_dll_path() -> Result<PathBuf> {
|
||||
let path = crate::windows::app_local_data_dir()?
|
||||
.join("data")
|
||||
.join("wintun.dll");
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
struct DllBytes {
|
||||
/// Bytes embedded in the client with `include_bytes`
|
||||
pub bytes: &'static [u8],
|
||||
/// Expected SHA256 hash
|
||||
pub expected_sha256: &'static str,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn wintun_bytes() -> DllBytes {
|
||||
DllBytes {
|
||||
bytes: include_bytes!("../wintun/bin/amd64/wintun.dll"),
|
||||
expected_sha256: "e5da8447dc2c320edc0fc52fa01885c103de8c118481f683643cacc3220dafce",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn wintun_bytes() -> DllBytes {
|
||||
DllBytes {
|
||||
bytes: include_bytes!("../wintun/bin/arm64/wintun.dll"),
|
||||
expected_sha256: "f7ba89005544be9d85231a9e0d5f23b2d15b3311667e2dad0debd344918a3f80",
|
||||
}
|
||||
}
|
||||
|
||||
21
rust/bin-shared/src/windows.rs
Normal file
21
rust/bin-shared/src/windows.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use known_folders::{get_known_folder_path, KnownFolder};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Hides Powershell's console on Windows
|
||||
///
|
||||
/// <https://stackoverflow.com/questions/59692146/is-it-possible-to-use-the-standard-library-to-spawn-a-process-without-showing-th#60958956>
|
||||
/// Also used for self-elevation
|
||||
pub const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
|
||||
/// Returns e.g. `C:/Users/User/AppData/Local/dev.firezone.client
|
||||
///
|
||||
/// This is where we can save config, logs, crash dumps, etc.
|
||||
/// It's per-user and doesn't roam across different PCs in the same domain.
|
||||
/// It's read-write for non-elevated processes.
|
||||
pub fn app_local_data_dir() -> Result<PathBuf> {
|
||||
let path = get_known_folder_path(KnownFolder::LocalAppData)
|
||||
.context("Can't find %LOCALAPPDATA% dir")?
|
||||
.join(crate::BUNDLE_ID);
|
||||
Ok(path)
|
||||
}
|
||||
@@ -41,17 +41,5 @@ tokio = { version = "1.38", features = ["macros", "rt"] }
|
||||
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
|
||||
swift-bridge = { workspace = true }
|
||||
|
||||
# Windows tunnel dependencies
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
wintun = "0.4.0"
|
||||
known-folders = "1.1.0"
|
||||
|
||||
# Windows Win32 API
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.57.0"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -52,12 +52,6 @@ pub enum ConnlibError {
|
||||
#[error("failed packet translation")]
|
||||
FailedTranslation,
|
||||
#[cfg(target_os = "windows")]
|
||||
#[error("Windows error: {0}")]
|
||||
WindowsError(#[from] windows::core::Error),
|
||||
#[cfg(target_os = "windows")]
|
||||
#[error(transparent)]
|
||||
Wintun(#[from] wintun::Error),
|
||||
#[cfg(target_os = "windows")]
|
||||
#[error("Can't compute path for wintun.dll")]
|
||||
WintunDllPath,
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
@@ -7,9 +7,6 @@ pub mod callbacks;
|
||||
pub mod error;
|
||||
pub mod messages;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(feature = "proptest")]
|
||||
pub mod proptest;
|
||||
|
||||
@@ -24,21 +21,6 @@ use rand_core::OsRng;
|
||||
|
||||
pub type DomainName = domain::base::Name<Vec<u8>>;
|
||||
|
||||
/// Bundle ID / App ID that the client uses to distinguish itself from other programs on the system
|
||||
///
|
||||
/// e.g. In ProgramData and AppData we use this to name our subdirectories for configs and data,
|
||||
/// and Windows may use it to track things like the MSI installer, notification titles,
|
||||
/// deep link registration, etc.
|
||||
///
|
||||
/// This should be identical to the `tauri.bundle.identifier` over in `tauri.conf.json`,
|
||||
/// but sometimes I need to use this before Tauri has booted up, or in a place where
|
||||
/// getting the Tauri app handle would be awkward.
|
||||
///
|
||||
/// Luckily this is also the AppUserModelId that Windows uses to label notifications,
|
||||
/// so if your dev system has Firezone installed by MSI, the notifications will look right.
|
||||
/// <https://learn.microsoft.com/en-us/windows/configuration/find-the-application-user-model-id-of-an-installed-app>
|
||||
pub const BUNDLE_ID: &str = "dev.firezone.client";
|
||||
|
||||
pub const DEFAULT_MTU: usize = 1280;
|
||||
|
||||
const LIB_NAME: &str = "connlib";
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
//! Windows-specific things like the well-known appdata path, bundle ID, etc.
|
||||
|
||||
use crate::Error;
|
||||
use known_folders::{get_known_folder_path, KnownFolder};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Hides Powershell's console on Windows
|
||||
///
|
||||
/// <https://stackoverflow.com/questions/59692146/is-it-possible-to-use-the-standard-library-to-spawn-a-process-without-showing-th#60958956>
|
||||
/// Also used for self-elevation
|
||||
pub const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
|
||||
// wintun automatically append " Tunnel" to this
|
||||
pub const TUNNEL_NAME: &str = "Firezone";
|
||||
|
||||
/// Returns e.g. `C:/Users/User/AppData/Local/dev.firezone.client
|
||||
///
|
||||
/// This is where we can save config, logs, crash dumps, etc.
|
||||
/// It's per-user and doesn't roam across different PCs in the same domain.
|
||||
/// It's read-write for non-elevated processes.
|
||||
pub fn app_local_data_dir() -> Result<PathBuf, Error> {
|
||||
let path = get_known_folder_path(KnownFolder::LocalAppData)
|
||||
.ok_or(Error::CantFindLocalAppDataFolder)?
|
||||
.join(crate::BUNDLE_ID);
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Returns the absolute path for installing and loading `wintun.dll`
|
||||
///
|
||||
/// e.g. `C:\Users\User\AppData\Local\dev.firezone.client\data\wintun.dll`
|
||||
pub fn wintun_dll_path() -> Result<PathBuf, Error> {
|
||||
let path = app_local_data_dir()?.join("data").join("wintun.dll");
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn wintun_bytes() -> &'static [u8] {
|
||||
include_bytes!("../../../headless-client/src/windows/wintun/bin/arm64/wintun.dll")
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn wintun_bytes() -> &'static [u8] {
|
||||
include_bytes!("../../../headless-client/src/windows/wintun/bin/amd64/wintun.dll")
|
||||
}
|
||||
@@ -18,8 +18,8 @@ atomicwrites = "0.4.3"
|
||||
chrono = { workspace = true }
|
||||
clap = { version = "4.5", features = ["derive", "env"] }
|
||||
connlib-client-shared = { workspace = true }
|
||||
connlib-shared = { workspace = true }
|
||||
crash-handler = "0.6.2"
|
||||
firezone-bin-shared = { workspace = true }
|
||||
firezone-headless-client = { path = "../../headless-client" }
|
||||
futures = { version = "0.3", default-features = false }
|
||||
git-version = "0.3.9"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use super::FZ_SCHEME;
|
||||
use anyhow::{Context, Result};
|
||||
use connlib_shared::BUNDLE_ID;
|
||||
use firezone_bin_shared::BUNDLE_ID;
|
||||
use secrecy::Secret;
|
||||
use std::{io, path::Path, time::Duration};
|
||||
use tokio::{io::AsyncReadExt, io::AsyncWriteExt, net::windows::named_pipe};
|
||||
|
||||
@@ -185,7 +185,7 @@ pub(crate) fn run(
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
connlib_shared::BUNDLE_ID,
|
||||
firezone_bin_shared::BUNDLE_ID,
|
||||
app.handle().config().tauri.bundle.identifier,
|
||||
"BUNDLE_ID should match bundle ID in tauri.conf.json"
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use firezone_bin_shared::BUNDLE_ID;
|
||||
use tauri::api::notification::Notification;
|
||||
|
||||
pub(crate) async fn set_autostart(enabled: bool) -> Result<()> {
|
||||
@@ -44,7 +45,7 @@ pub(crate) fn show_update_notification(
|
||||
|
||||
/// Show a notification in the bottom right of the screen
|
||||
pub(crate) fn show_notification(title: &str, body: &str) -> Result<()> {
|
||||
Notification::new(connlib_shared::BUNDLE_ID)
|
||||
Notification::new(BUNDLE_ID)
|
||||
.title(title)
|
||||
.body(body)
|
||||
.show()?;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{ControllerRequest, CtlrTx};
|
||||
use anyhow::{Context, Result};
|
||||
use connlib_shared::BUNDLE_ID;
|
||||
use firezone_bin_shared::BUNDLE_ID;
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
pub(crate) async fn set_autostart(_enabled: bool) -> Result<()> {
|
||||
|
||||
@@ -54,7 +54,6 @@ dirs = "5.0.1"
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
ipconfig = "0.3.2"
|
||||
known-folders = "1.1.0"
|
||||
ring = "0.17"
|
||||
thiserror = { version = "1.0", default-features = false }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
windows-service = "0.7.0"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
//! <https://superuser.com/a/1752670>
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use connlib_shared::windows::CREATE_NO_WINDOW;
|
||||
use firezone_bin_shared::windows::CREATE_NO_WINDOW;
|
||||
use std::{net::IpAddr, os::windows::process::CommandExt, path::Path, process::Command};
|
||||
|
||||
pub fn system_resolvers_for_gui() -> Result<Vec<IpAddr>> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::{Error, ServiceId};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use firezone_bin_shared::BUNDLE_ID;
|
||||
use std::{io::ErrorKind, os::unix::fs::PermissionsExt, path::PathBuf};
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
|
||||
@@ -88,9 +89,7 @@ impl Server {
|
||||
/// Test sockets live in e.g. `/run/user/1000/dev.firezone.client/data/`
|
||||
fn ipc_path(id: ServiceId) -> PathBuf {
|
||||
match id {
|
||||
ServiceId::Prod => PathBuf::from("/run")
|
||||
.join(connlib_shared::BUNDLE_ID)
|
||||
.join("ipc.sock"),
|
||||
ServiceId::Prod => PathBuf::from("/run").join(BUNDLE_ID).join("ipc.sock"),
|
||||
ServiceId::Test(id) => crate::known_dirs::runtime()
|
||||
.expect("`known_dirs::runtime()` should always work")
|
||||
.join(format!("ipc_test_{id}.sock")),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{Error, ServiceId};
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use connlib_shared::BUNDLE_ID;
|
||||
use firezone_bin_shared::BUNDLE_ID;
|
||||
use std::{ffi::c_void, io::ErrorKind, os::windows::io::AsRawHandle, time::Duration};
|
||||
use tokio::net::windows::named_pipe;
|
||||
use windows::Win32::{
|
||||
@@ -49,7 +49,6 @@ impl Server {
|
||||
/// This is async on Linux
|
||||
#[allow(clippy::unused_async)]
|
||||
pub(crate) async fn new(id: ServiceId) -> Result<Self> {
|
||||
crate::platform::setup_before_connlib()?;
|
||||
let pipe_path = ipc_path(id);
|
||||
Ok(Self { pipe_path })
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use connlib_shared::BUNDLE_ID;
|
||||
use firezone_bin_shared::BUNDLE_ID;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Path for IPC service config that either the IPC service or GUI can write
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use connlib_shared::BUNDLE_ID;
|
||||
use firezone_bin_shared::{windows::app_local_data_dir, BUNDLE_ID};
|
||||
use known_folders::{get_known_folder_path, KnownFolder};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::path::PathBuf;
|
||||
pub fn ipc_service_config() -> Option<PathBuf> {
|
||||
Some(
|
||||
get_known_folder_path(KnownFolder::ProgramData)?
|
||||
.join(connlib_shared::BUNDLE_ID)
|
||||
.join(BUNDLE_ID)
|
||||
.join("config"),
|
||||
)
|
||||
}
|
||||
@@ -31,43 +31,26 @@ pub fn ipc_service_logs() -> Option<PathBuf> {
|
||||
///
|
||||
/// See connlib docs for details
|
||||
pub fn logs() -> Option<PathBuf> {
|
||||
Some(
|
||||
connlib_shared::windows::app_local_data_dir()
|
||||
.ok()?
|
||||
.join("data")
|
||||
.join("logs"),
|
||||
)
|
||||
Some(app_local_data_dir().ok()?.join("data").join("logs"))
|
||||
}
|
||||
|
||||
/// e.g. `C:\Users\Alice\AppData\Local\dev.firezone.client\data`
|
||||
///
|
||||
/// Crash handler socket and other temp files go here
|
||||
pub fn runtime() -> Option<PathBuf> {
|
||||
Some(
|
||||
connlib_shared::windows::app_local_data_dir()
|
||||
.ok()?
|
||||
.join("data"),
|
||||
)
|
||||
Some(app_local_data_dir().ok()?.join("data"))
|
||||
}
|
||||
|
||||
/// e.g. `C:\Users\Alice\AppData\Local\dev.firezone.client\data`
|
||||
///
|
||||
/// Things like actor name go here
|
||||
pub fn session() -> Option<PathBuf> {
|
||||
Some(
|
||||
connlib_shared::windows::app_local_data_dir()
|
||||
.ok()?
|
||||
.join("data"),
|
||||
)
|
||||
Some(app_local_data_dir().ok()?.join("data"))
|
||||
}
|
||||
|
||||
/// e.g. `C:\Users\Alice\AppData\Local\dev.firezone.client\config`
|
||||
///
|
||||
/// See connlib docs for details
|
||||
pub fn settings() -> Option<PathBuf> {
|
||||
Some(
|
||||
connlib_shared::windows::app_local_data_dir()
|
||||
.ok()?
|
||||
.join("config"),
|
||||
)
|
||||
Some(app_local_data_dir().ok()?.join("config"))
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use super::TOKEN_ENV_KEY;
|
||||
use anyhow::{bail, Result};
|
||||
use firezone_bin_shared::FIREZONE_MARK;
|
||||
use firezone_bin_shared::{BUNDLE_ID, FIREZONE_MARK};
|
||||
use nix::sys::socket::{setsockopt, sockopt};
|
||||
use socket_factory::{TcpSocket, UdpSocket};
|
||||
use std::{
|
||||
@@ -29,9 +29,7 @@ pub(crate) fn udp_socket_factory(socket_addr: &SocketAddr) -> io::Result<UdpSock
|
||||
}
|
||||
|
||||
pub(crate) fn default_token_path() -> PathBuf {
|
||||
PathBuf::from("/etc")
|
||||
.join(connlib_shared::BUNDLE_ID)
|
||||
.join("token")
|
||||
PathBuf::from("/etc").join(BUNDLE_ID).join("token")
|
||||
}
|
||||
|
||||
pub(crate) fn check_token_permissions(path: &Path) -> Result<()> {
|
||||
@@ -68,11 +66,3 @@ pub(crate) fn check_token_permissions(path: &Path) -> Result<()> {
|
||||
pub(crate) fn notify_service_controller() -> Result<()> {
|
||||
Ok(sd_notify::notify(true, &[sd_notify::NotifyState::Ready])?)
|
||||
}
|
||||
|
||||
/// Platform-specific setup needed for connlib
|
||||
///
|
||||
/// On Linux this does nothing
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn setup_before_connlib() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -164,7 +164,6 @@ pub fn run_only_headless_client() -> Result<()> {
|
||||
|
||||
// The name matches that in `ipc_service.rs`
|
||||
let mut last_connlib_start_instant = Some(Instant::now());
|
||||
platform::setup_before_connlib()?;
|
||||
let args = ConnectArgs {
|
||||
udp_socket_factory: Arc::new(crate::udp_socket_factory),
|
||||
tcp_socket_factory: Arc::new(crate::tcp_socket_factory),
|
||||
|
||||
@@ -10,9 +10,6 @@ use std::path::{Path, PathBuf};
|
||||
pub(crate) use socket_factory::tcp as tcp_socket_factory;
|
||||
pub(crate) use socket_factory::udp as udp_socket_factory;
|
||||
|
||||
#[path = "windows/wintun_install.rs"]
|
||||
mod wintun_install;
|
||||
|
||||
// The return value is useful on Linux
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn check_token_permissions(_path: &Path) -> Result<()> {
|
||||
@@ -32,8 +29,3 @@ pub(crate) fn default_token_path() -> std::path::PathBuf {
|
||||
pub(crate) fn notify_service_controller() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn setup_before_connlib() -> Result<()> {
|
||||
wintun_install::ensure_dll()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
//! "Installs" wintun.dll at runtime by copying it into whatever folder the exe is in
|
||||
|
||||
use connlib_shared::windows::wintun_dll_path;
|
||||
use ring::digest;
|
||||
use std::{
|
||||
fs,
|
||||
io::{self, Read},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
struct DllBytes {
|
||||
/// Bytes embedded in the client with `include_bytes`
|
||||
bytes: &'static [u8],
|
||||
/// Expected SHA256 hash
|
||||
expected_sha256: &'static str,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub(crate) enum Error {
|
||||
#[error("Can't compute path where wintun.dll should be installed")]
|
||||
CantComputeWintunPath,
|
||||
#[error("create_dir_all failed")]
|
||||
CreateDirAll,
|
||||
#[error("Computed DLL path is invalid")]
|
||||
DllPathInvalid,
|
||||
#[error("permission denied")]
|
||||
PermissionDenied,
|
||||
#[error("write failed: `{0:?}`")]
|
||||
WriteFailed(io::Error),
|
||||
}
|
||||
|
||||
/// Installs the DLL in %LOCALAPPDATA% and returns the DLL's absolute path
|
||||
///
|
||||
/// e.g. `C:\Users\User\AppData\Local\dev.firezone.client\data\wintun.dll`
|
||||
/// Also verifies the SHA256 of the DLL on-disk with the expected bytes packed into the exe
|
||||
pub(crate) fn ensure_dll() -> Result<PathBuf, Error> {
|
||||
let dll_bytes = get_dll_bytes();
|
||||
|
||||
let path = wintun_dll_path().map_err(|_| Error::CantComputeWintunPath)?;
|
||||
// The DLL path should always have a parent
|
||||
let dir = path.parent().ok_or(Error::DllPathInvalid)?;
|
||||
std::fs::create_dir_all(dir).map_err(|_| Error::CreateDirAll)?;
|
||||
|
||||
tracing::debug!(?path, "wintun.dll path");
|
||||
|
||||
// This hash check is not meant to protect against attacks. It only lets us skip redundant disk writes, and it updates the DLL if needed.
|
||||
// `tun_windows.rs` in connlib, and `elevation.rs`, rely on thia.
|
||||
if !dll_already_exists(&path, &dll_bytes) {
|
||||
fs::write(&path, dll_bytes.bytes).map_err(|e| {
|
||||
#[allow(clippy::wildcard_enum_match_arm)]
|
||||
match e.kind() {
|
||||
io::ErrorKind::PermissionDenied => Error::PermissionDenied,
|
||||
_ => Error::WriteFailed(e),
|
||||
}
|
||||
})?;
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn dll_already_exists(path: &Path, dll_bytes: &DllBytes) -> bool {
|
||||
let mut f = match fs::File::open(path) {
|
||||
Err(_) => return false,
|
||||
Ok(x) => x,
|
||||
};
|
||||
|
||||
let actual_len = usize::try_from(f.metadata().unwrap().len()).unwrap();
|
||||
let expected_len = dll_bytes.bytes.len();
|
||||
// If the dll is 100 MB instead of 0.5 MB, this allows us to skip a 100 MB read
|
||||
if actual_len != expected_len {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut buf = vec![0u8; expected_len];
|
||||
if f.read_exact(&mut buf).is_err() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let expected = ring::test::from_hex(dll_bytes.expected_sha256).unwrap();
|
||||
let actual = digest::digest(&digest::SHA256, &buf);
|
||||
expected == actual.as_ref()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn get_dll_bytes() -> DllBytes {
|
||||
DllBytes {
|
||||
bytes: include_bytes!("wintun/bin/amd64/wintun.dll"),
|
||||
expected_sha256: "e5da8447dc2c320edc0fc52fa01885c103de8c118481f683643cacc3220dafce",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn get_dll_bytes() -> DllBytes {
|
||||
DllBytes {
|
||||
bytes: include_bytes!("wintun/bin/arm64/wintun.dll"),
|
||||
expected_sha256: "f7ba89005544be9d85231a9e0d5f23b2d15b3311667e2dad0debd344918a3f80",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user