refactor(connlib): move TunDeviceManager into firezone-bin-shared (#5843)

The `TunDeviceManager` is a component that the leaf-nodes of our
dependency tree need: the binaries. Thus, it is misplaced in the
`connlib-shared` crate which is at the very bottom of the dependency
tree.

This is necessary to allow the `TunDeviceManager` to actually construct
a `Tun` (which currently lives in `firezone-tunnel`).

Related: #5839.

---------

Signed-off-by: Thomas Eizinger <thomas@eizinger.io>
Co-authored-by: Reactor Scram <ReactorScram@users.noreply.github.com>
This commit is contained in:
Thomas Eizinger
2024-07-12 09:42:33 +10:00
committed by GitHub
parent 2013d6a2bf
commit 960ce80680
21 changed files with 88 additions and 84 deletions

22
rust/Cargo.lock generated
View File

@@ -1136,15 +1136,12 @@ dependencies = [
"known-folders",
"libc",
"log",
"netlink-packet-core",
"netlink-packet-route",
"os_info",
"phoenix-channel",
"proptest",
"rand 0.8.5",
"rand_core 0.6.4",
"ring",
"rtnetlink",
"secrecy",
"serde",
"serde_json",
@@ -1831,14 +1828,23 @@ dependencies = [
]
[[package]]
name = "firezone-cli-utils"
name = "firezone-bin-shared"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"connlib-shared",
"futures",
"ip_network",
"netlink-packet-core",
"netlink-packet-route",
"rtnetlink",
"tokio",
"tracing",
"tracing-log",
"tracing-subscriber",
"url",
"windows 0.57.0",
]
[[package]]
@@ -1855,7 +1861,7 @@ dependencies = [
"dns-lookup",
"domain",
"either",
"firezone-cli-utils",
"firezone-bin-shared",
"firezone-tunnel",
"futures",
"futures-bounded",
@@ -1939,7 +1945,7 @@ dependencies = [
"connlib-client-shared",
"connlib-shared",
"dirs",
"firezone-cli-utils",
"firezone-bin-shared",
"futures",
"git-version",
"humantime",
@@ -2024,6 +2030,7 @@ dependencies = [
"connlib-shared",
"derivative",
"domain",
"firezone-bin-shared",
"firezone-relay",
"futures",
"futures-bounded",
@@ -2037,8 +2044,6 @@ dependencies = [
"itertools 0.13.0",
"libc",
"log",
"netlink-packet-core",
"netlink-packet-route",
"pretty_assertions",
"proptest",
"proptest-state-machine",
@@ -2046,7 +2051,6 @@ dependencies = [
"rand 0.8.5",
"rand_core 0.6.4",
"rangemap",
"rtnetlink",
"secrecy",
"serde",
"serde_json",

View File

@@ -7,7 +7,7 @@ members = [
"connlib/tunnel",
"connlib/snownet",
"gateway",
"firezone-cli-utils",
"bin-shared",
"gui-smoke-test",
"headless-client",
"snownet-tests",
@@ -45,7 +45,7 @@ connlib-client-shared = { path = "connlib/clients/shared"}
firezone-gateway = { path = "gateway"}
firezone-headless-client = { path = "headless-client"}
firezone-gui-client = { path = "gui-client/src-tauri"}
firezone-cli-utils = { path = "firezone-cli-utils"}
firezone-bin-shared = { path = "bin-shared"}
snownet = { path = "connlib/snownet"}
firezone-relay = { path = "relay"}
connlib-shared = { path = "connlib/shared"}

View File

@@ -0,0 +1,33 @@
[package]
name = "firezone-bin-shared"
version = "0.1.0"
edition = "2021"
description = "Firezone-specific modules shared between binaries."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.82"
clap = { version = "4.5", features = ["derive", "env"] }
connlib-shared = { workspace = true }
futures = "0.3"
ip_network = { version = "0.4", default-features = false, features = ["serde"] }
tokio = { workspace = true, features = ["rt"] }
tracing = { workspace = true }
tracing-log = "0.2"
tracing-subscriber = { workspace = true, features = ["env-filter"] }
url = { version = "2.3.1", default-features = false }
[target.'cfg(target_os = "linux")'.dependencies]
netlink-packet-core = { version = "0.7", default-features = false }
netlink-packet-route = { version = "0.19", default-features = false }
rtnetlink = { workspace = true }
[target.'cfg(windows)'.dependencies.windows]
version = "0.57.0"
features = [
"Win32_Foundation",
]
[lints]
workspace = true

View File

@@ -1,3 +1,5 @@
mod tun_device_manager;
use clap::Args;
use tracing_log::LogTracer;
use tracing_subscriber::{
@@ -5,6 +7,9 @@ use tracing_subscriber::{
};
use url::Url;
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub use tun_device_manager::TunDeviceManager;
pub fn setup_global_subscriber<L>(additional_layer: L)
where
L: Layer<Registry> + Send + Sync,

View File

@@ -1,7 +1,7 @@
//! Virtual network interface
use crate::DEFAULT_MTU;
use anyhow::{anyhow, Context as _, Result};
use connlib_shared::DEFAULT_MTU;
use futures::TryStreamExt;
use ip_network::{IpNetwork, Ipv4Network, Ipv6Network};
use netlink_packet_route::route::{RouteProtocol, RouteScope};
@@ -12,8 +12,7 @@ use std::{
net::{Ipv4Addr, Ipv6Addr},
};
pub const FIREZONE_MARK: u32 = 0xfd002021;
pub const IFACE_NAME: &str = "tun-firezone";
const FIREZONE_MARK: u32 = 0xfd002021; // Keep this synced with `Sockets` until #5797.
const FILE_ALREADY_EXISTS: i32 = -17;
const FIREZONE_TABLE: u32 = 0x2021_fd00;
@@ -35,6 +34,8 @@ impl Drop for TunDeviceManager {
}
impl TunDeviceManager {
pub const IFACE_NAME: &'static str = "tun-firezone"; // Keep this synced with `Tun` until we fix the module dependencies (i.e. move `Tun` out of `firezone-tunnel`).
/// Creates a new managed tunnel device.
///
/// Panics if called without a Tokio runtime.
@@ -51,15 +52,17 @@ impl TunDeviceManager {
#[tracing::instrument(level = "trace", skip(self))]
pub async fn set_ips(&mut self, ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Result<()> {
let name = Self::IFACE_NAME;
let handle = &self.connection.handle;
let index = handle
.link()
.get()
.match_name(IFACE_NAME.to_string())
.match_name(name.to_string())
.execute()
.try_next()
.await?
.ok_or_else(|| anyhow!("Interface '{IFACE_NAME}' does not exist"))?
.ok_or_else(|| anyhow!("Interface '{name}' does not exist"))?
.header
.index;
@@ -139,6 +142,7 @@ impl TunDeviceManager {
.map(IpNetwork::from)
.chain(ipv6.into_iter().map(IpNetwork::from))
.collect();
if new_routes == self.routes {
tracing::debug!("Routes are unchanged");
@@ -152,7 +156,7 @@ impl TunDeviceManager {
let index = handle
.link()
.get()
.match_name(IFACE_NAME.to_string())
.match_name(Self::IFACE_NAME.to_string())
.execute()
.try_next()
.await?

View File

@@ -1,5 +1,5 @@
use crate::windows::{CREATE_NO_WINDOW, TUNNEL_NAME};
use anyhow::Result;
use connlib_shared::windows::{CREATE_NO_WINDOW, TUNNEL_NAME};
use ip_network::{Ipv4Network, Ipv6Network};
use std::{
net::{Ipv4Addr, Ipv6Addr},

View File

@@ -44,11 +44,6 @@ tokio = { version = "1.38", features = ["macros", "rt"] }
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
swift-bridge = { workspace = true }
[target.'cfg(target_os = "linux")'.dependencies]
netlink-packet-route = { version = "0.19", default-features = false }
netlink-packet-core = { version = "0.7", default-features = false }
rtnetlink = { workspace = true }
# Windows tunnel dependencies
[target.'cfg(target_os = "windows")'.dependencies]
wintun = "0.4.0"

View File

@@ -27,14 +27,6 @@ pub enum ConnlibError {
/// Glob for errors without a type.
#[error("Other error: {0}")]
Other(&'static str),
#[cfg(target_os = "linux")]
#[error(transparent)]
NetlinkError(rtnetlink::Error),
/// Io translation of netlink error
/// The IO version is easier to interpret
/// We maintain a different variant from the standard IO for this to keep more context
#[error("IO netlink error: {0}")]
NetlinkErrorIo(std::io::Error),
/// No iface found
#[error("No iface found")]
NoIface,
@@ -89,14 +81,3 @@ pub enum ConnlibError {
#[error("connection to the portal failed: {0}")]
PortalConnectionFailed(phoenix_channel::Error),
}
#[cfg(target_os = "linux")]
impl From<rtnetlink::Error> for ConnlibError {
fn from(err: rtnetlink::Error) -> Self {
#[allow(clippy::wildcard_enum_match_arm)]
match err {
rtnetlink::Error::NetlinkError(err) => Self::NetlinkErrorIo(err.to_io()),
err => Self::NetlinkError(err),
}
}
}

View File

@@ -6,7 +6,6 @@
pub mod callbacks;
pub mod error;
pub mod messages;
pub mod tun_device_manager;
#[cfg(target_os = "windows")]
pub mod windows;

View File

@@ -50,6 +50,9 @@ hickory-proto = { workspace = true }
derivative = "2.2.0"
ip-packet = { workspace = true, features = ["proptest"] }
[target.'cfg(target_os = "windows")'.dev-dependencies]
firezone-bin-shared = { workspace = true } # Required for benchmark.
[[bench]]
name = "tunnel"
harness = false
@@ -57,12 +60,6 @@ harness = false
[features]
proptest = ["dep:proptest", "connlib-shared/proptest"]
# Linux tunnel dependencies
[target.'cfg(target_os = "linux")'.dependencies]
netlink-packet-route = { version = "0.19", default-features = false }
netlink-packet-core = { version = "0.7", default-features = false }
rtnetlink = { workspace = true }
# Windows tunnel dependencies
[target.'cfg(target_os = "windows")'.dependencies]
tokio = { workspace = true, features = ["sync"] }

View File

@@ -26,6 +26,7 @@ mod platform {
#[cfg(target_os = "windows")]
mod platform {
use anyhow::Result;
use firezone_bin_shared::TunDeviceManager;
use firezone_tunnel::Tun;
use ip_packet::{IpPacket, Packet as _};
use std::{
@@ -59,8 +60,7 @@ mod platform {
let ipv4 = Ipv4Addr::from([100, 90, 215, 97]);
let ipv6 = Ipv6Addr::from([0xfd00, 0x2021, 0x1111, 0x0, 0x0, 0x0, 0x0016, 0x588f]);
let mut device_manager =
connlib_shared::tun_device_manager::platform::TunDeviceManager::new()?;
let mut device_manager = TunDeviceManager::new()?;
device_manager.set_ips(ipv4, ipv6).await?;
tun.add_route(ipv4.into())?;

View File

@@ -1,6 +1,6 @@
use super::utils;
use crate::device_channel::ioctl;
use connlib_shared::{tun_device_manager::platform::IFACE_NAME, Callbacks, Error, Result};
use connlib_shared::{Callbacks, Error, Result};
use ip_network::IpNetwork;
use libc::{
close, fcntl, makedev, mknod, open, F_GETFL, F_SETFL, IFF_NO_PI, IFF_TUN, O_NONBLOCK, O_RDWR,
@@ -22,6 +22,7 @@ use tokio::io::unix::AsyncFd;
const TUNSETIFF: libc::c_ulong = 0x4004_54ca;
const TUN_DEV_MAJOR: u32 = 10;
const TUN_DEV_MINOR: u32 = 200;
const IFACE_NAME: &str = "tun-firezone"; // Keep this synced with `TunDeviceManager` until we fix the module dependencies (i.e. move `Tun` out of `firezone-tunnel`).
// Safety: We know that this is a valid C string.
const TUN_FILE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"/dev/net/tun\0") };

View File

@@ -339,7 +339,9 @@ fn make_socket(addr: impl Into<SocketAddr>) -> Result<std::net::UdpSocket> {
#[cfg(target_os = "linux")]
{
socket.set_mark(connlib_shared::tun_device_manager::platform::FIREZONE_MARK)?;
const FIREZONE_MARK: u32 = 0xfd002021; // Keep this synced with `TunDeviceManager` until #5797.
socket.set_mark(FIREZONE_MARK)?;
}
// Note: for AF_INET sockets IPV6_V6ONLY is not a valid flag

View File

@@ -1,16 +0,0 @@
[package]
name = "firezone-cli-utils"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
url = { version = "2.3.1", default-features = false }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
tracing = { workspace = true }
tracing-log = "0.2"
clap = { version = "4.5", features = ["derive", "env"] }
[lints]
workspace = true

View File

@@ -17,7 +17,7 @@ connlib-shared = { workspace = true }
firezone-tunnel = { workspace = true }
futures = "0.3.29"
futures-bounded = { workspace = true }
firezone-cli-utils = { workspace = true }
firezone-bin-shared = { workspace = true }
phoenix-channel = { workspace = true }
secrecy = { workspace = true }
serde = { version = "1.0", default-features = false, features = ["std", "derive"] }

View File

@@ -2,10 +2,10 @@ use crate::eventloop::{Eventloop, PHOENIX_TOPIC};
use anyhow::{Context, Result};
use backoff::ExponentialBackoffBuilder;
use clap::Parser;
use connlib_shared::messages::Interface;
use connlib_shared::tun_device_manager::TunDeviceManager;
use connlib_shared::{get_user_agent, keypair, Callbacks, LoginUrl, StaticSecret};
use firezone_cli_utils::{setup_global_subscriber, CommonArgs};
use connlib_shared::{
get_user_agent, keypair, messages::Interface, Callbacks, LoginUrl, StaticSecret,
};
use firezone_bin_shared::{setup_global_subscriber, CommonArgs, TunDeviceManager};
use firezone_tunnel::{GatewayTunnel, Sockets};
use futures::channel::mpsc;
use futures::{future, StreamExt, TryFutureExt};

View File

@@ -13,7 +13,7 @@ atomicwrites = "0.4.3" # Needed to safely backup `/etc/resolv.conf` and write th
clap = { version = "4.5", features = ["derive", "env", "string"] }
connlib-client-shared = { workspace = true }
connlib-shared = { workspace = true }
firezone-cli-utils = { workspace = true }
firezone-bin-shared = { workspace = true }
futures = "0.3.30"
git-version = "0.3.9"
humantime = "2.1"

View File

@@ -1,5 +1,5 @@
use anyhow::{bail, Context as _, Result};
use connlib_shared::tun_device_manager::linux::IFACE_NAME;
use firezone_bin_shared::TunDeviceManager;
use std::{net::IpAddr, process::Command, str::FromStr};
mod etc_resolv_conf;
@@ -95,7 +95,7 @@ fn configure_network_manager(_dns_config: &[IpAddr]) -> Result<()> {
async fn configure_systemd_resolved(dns_config: &[IpAddr]) -> Result<()> {
let status = tokio::process::Command::new("resolvectl")
.arg("dns")
.arg(IFACE_NAME)
.arg(TunDeviceManager::IFACE_NAME)
.args(dns_config.iter().map(ToString::to_string))
.status()
.await
@@ -106,7 +106,7 @@ async fn configure_systemd_resolved(dns_config: &[IpAddr]) -> Result<()> {
let status = tokio::process::Command::new("resolvectl")
.arg("domain")
.arg(IFACE_NAME)
.arg(TunDeviceManager::IFACE_NAME)
.arg("~.")
.status()
.await

View File

@@ -7,7 +7,6 @@ use crate::{
use anyhow::{Context as _, Result};
use clap::Parser;
use connlib_client_shared::{file_logger, keypair, ConnectArgs, LoginUrl, Session, Sockets};
use connlib_shared::tun_device_manager;
use futures::{future, SinkExt as _, StreamExt as _};
use std::{net::IpAddr, path::PathBuf, pin::pin, time::Duration};
use tokio::{sync::mpsc, time::Instant};
@@ -16,6 +15,7 @@ use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Layer, Registry};
use url::Url;
pub mod ipc;
use firezone_bin_shared::TunDeviceManager;
use ipc::{Server as IpcServer, ServiceId};
#[cfg(target_os = "linux")]
@@ -162,7 +162,7 @@ struct Handler {
ipc_rx: ipc::ServerRead,
ipc_tx: ipc::ServerWrite,
last_connlib_start_instant: Option<Instant>,
tun_device: tun_device_manager::TunDeviceManager,
tun_device: TunDeviceManager,
}
enum Event {
@@ -178,7 +178,7 @@ impl Handler {
.await
.context("Failed to wait for incoming IPC connection from a GUI")?;
let (cb_tx, cb_rx) = mpsc::channel(10);
let tun_device = tun_device_manager::TunDeviceManager::new()?;
let tun_device = TunDeviceManager::new()?;
Ok(Self {
callback_handler: CallbackHandler { cb_tx },

View File

@@ -7,8 +7,7 @@ use crate::{
use anyhow::{anyhow, Context as _, Result};
use clap::Parser;
use connlib_client_shared::{file_logger, keypair, ConnectArgs, LoginUrl, Session, Sockets};
use connlib_shared::tun_device_manager;
use firezone_cli_utils::setup_global_subscriber;
use firezone_bin_shared::{setup_global_subscriber, TunDeviceManager};
use futures::{FutureExt as _, StreamExt as _};
use secrecy::SecretString;
use std::{
@@ -175,7 +174,7 @@ pub fn run_only_headless_client() -> Result<()> {
let mut terminate = pin!(terminate.recv().fuse());
let mut hangup = pin!(hangup.recv().fuse());
let mut dns_controller = DnsController::default();
let mut tun_device = tun_device_manager::TunDeviceManager::new()?;
let mut tun_device = TunDeviceManager::new()?;
let mut cb_rx = ReceiverStream::new(cb_rx).fuse();
loop {