mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-14 08:26:14 -08:00
refactor(tun): consolidate NIC setup and DHCP host logic (#2691)
Select dedicated or shared NICs through one constructor, removing redundant construction wrappers and unreachable setup error handling. Share desktop and mobile DHCP cancellation, publication, and permit handling while retaining their platform-specific NIC setup. Build mobile VPN configuration once after member selection or attach, and use CIDR formatting that preserves host prefixes. Cover DHCP publication locking and cancelled address updates, and retain explicit IPv4 /32 and IPv6 /128 projection assertions.
This commit is contained in:
1 parent
0da58e68df
commit
4d4b5ad8dc
6 files changed
+259
-376
No files matched your search
@@ -74,7 +74,11 @@ pub async fn prepare_env_with_tld_dns_zone(
|
||||
core_instance.packet_plane(),
|
||||
host_packet_rx,
|
||||
Arc::new(Notify::new()),
|
||||
);
|
||||
Arc::new(tokio::sync::Mutex::new(
|
||||
crate::instance::shared_virtual_nic::SharedVirtualNicRegistry::new(),
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
virtual_nic.run(Some(tun_ip), None).await.unwrap();
|
||||
|
||||
(ctx, core_instance, virtual_nic)
|
||||
|
||||
@@ -58,7 +58,7 @@ fn comparable_claims(mut claims: SharedIfConfigClaims) -> SharedIfConfigClaims {
|
||||
fn select_members(
|
||||
mut members: Vec<MobileTunMember>,
|
||||
mut rejected: Vec<MobileTunMemberError>,
|
||||
) -> (Vec<MobileTunMember>, MobileTunConfig) {
|
||||
) -> (Vec<MobileTunMember>, Vec<MobileTunMemberError>) {
|
||||
// Keep unchanged attached members ahead of new or reconfigured members.
|
||||
members.sort_by_key(|member| {
|
||||
!member.current_claims.as_ref().is_some_and(|current| {
|
||||
@@ -82,8 +82,7 @@ fn select_members(
|
||||
ifcfg.apply_member_claims(member.instance_id, claims);
|
||||
accepted.push(member);
|
||||
}
|
||||
let config = config_for_members(&accepted, rejected);
|
||||
(accepted, config)
|
||||
(accepted, rejected)
|
||||
}
|
||||
|
||||
#[cfg(any(mobile, test))]
|
||||
@@ -100,45 +99,21 @@ fn config_for_members(
|
||||
for member in members {
|
||||
config.instance_ids.push(member.instance_id);
|
||||
for address in &member.claims.ipv4_addresses {
|
||||
addresses.insert(format!(
|
||||
"{}/{}",
|
||||
address.address(),
|
||||
address.network_length()
|
||||
));
|
||||
routes.insert(format!(
|
||||
"{}/{}",
|
||||
address.network().first_address(),
|
||||
address.network_length()
|
||||
));
|
||||
addresses.insert(format!("{address:#}"));
|
||||
routes.insert(format!("{:#}", address.network()));
|
||||
}
|
||||
for address in &member.claims.ipv6_addresses {
|
||||
addresses.insert(format!(
|
||||
"{}/{}",
|
||||
address.address(),
|
||||
address.network_length()
|
||||
));
|
||||
routes.insert(format!(
|
||||
"{}/{}",
|
||||
address.network().first_address(),
|
||||
address.network_length()
|
||||
));
|
||||
addresses.insert(format!("{address:#}"));
|
||||
routes.insert(format!("{:#}", address.network()));
|
||||
}
|
||||
for route in &member.claims.ipv4_routes {
|
||||
if let Ok(route) = cidr::Ipv4Inet::new(route.address, route.prefix) {
|
||||
routes.insert(format!(
|
||||
"{}/{}",
|
||||
route.network().first_address(),
|
||||
route.network_length()
|
||||
));
|
||||
routes.insert(format!("{:#}", route.network()));
|
||||
}
|
||||
}
|
||||
for route in &member.claims.ipv6_routes {
|
||||
if let Ok(route) = cidr::Ipv6Inet::new(route.address, route.prefix) {
|
||||
routes.insert(format!(
|
||||
"{}/{}",
|
||||
route.network().first_address(),
|
||||
route.network_length()
|
||||
));
|
||||
routes.insert(format!("{:#}", route.network()));
|
||||
}
|
||||
}
|
||||
if member.dns {
|
||||
@@ -199,7 +174,8 @@ pub async fn get_mobile_tun_config(
|
||||
instance_ids: &[uuid::Uuid],
|
||||
) -> MobileTunConfig {
|
||||
let (members, rejected) = collect_members(manager, instance_ids).await;
|
||||
select_members(members, rejected).1
|
||||
let (members, rejected) = select_members(members, rejected);
|
||||
config_for_members(&members, rejected)
|
||||
}
|
||||
|
||||
#[cfg(any(mobile, test))]
|
||||
@@ -243,7 +219,7 @@ pub async fn attach_mobile_tun_fd(
|
||||
return Ok(MobileTunConfig::default());
|
||||
}
|
||||
let (members, rejected) = collect_members(manager, instance_ids.unwrap_or(&all_ids)).await;
|
||||
let (members, mut config) = select_members(members, rejected);
|
||||
let (members, mut rejected) = select_members(members, rejected);
|
||||
let mut attached = Vec::new();
|
||||
// Open once; a rejected member never replaces or tears down this shared fd.
|
||||
for member in members {
|
||||
@@ -259,7 +235,7 @@ pub async fn attach_mobile_tun_fd(
|
||||
Ok(()) => attached.push(member),
|
||||
Err(error) if is_claim_conflict(&error) => {
|
||||
attach_member(manager, member.instance_id, 0, false, None).await?;
|
||||
config.rejected.push(MobileTunMemberError {
|
||||
rejected.push(MobileTunMemberError {
|
||||
instance_id: member.instance_id,
|
||||
error: error.to_string(),
|
||||
});
|
||||
@@ -272,7 +248,7 @@ pub async fn attach_mobile_tun_fd(
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(config_for_members(&attached, config.rejected))
|
||||
Ok(config_for_members(&attached, rejected))
|
||||
}
|
||||
|
||||
#[cfg(mobile)]
|
||||
@@ -315,7 +291,9 @@ mod tests {
|
||||
fn keeps_attached_member_when_bootstrap_subnets_overlap() {
|
||||
let mut existing = member(2, "10.126.126.2/24");
|
||||
existing.current_claims = Some(existing.claims.clone());
|
||||
let (_, config) = select_members(vec![member(1, "10.126.126.1/24"), existing], Vec::new());
|
||||
let (members, rejected) =
|
||||
select_members(vec![member(1, "10.126.126.1/24"), existing], Vec::new());
|
||||
let config = config_for_members(&members, rejected);
|
||||
assert_eq!(config.instance_ids, vec![uuid::Uuid::from_u128(2)]);
|
||||
assert_eq!(config.addresses, vec!["10.126.126.2/24"]);
|
||||
assert_eq!(config.routes, vec!["10.126.126.0/24"]);
|
||||
@@ -329,7 +307,8 @@ mod tests {
|
||||
let mut unchanged = member(2, "10.0.2.2/24");
|
||||
unchanged.current_claims = Some(unchanged.claims.clone());
|
||||
unchanged.current_claims.as_mut().unwrap().mtu = Some(1300);
|
||||
let (_, config) = select_members(vec![changed, unchanged], Vec::new());
|
||||
let (members, rejected) = select_members(vec![changed, unchanged], Vec::new());
|
||||
let config = config_for_members(&members, rejected);
|
||||
assert_eq!(config.instance_ids, vec![uuid::Uuid::from_u128(2)]);
|
||||
assert_eq!(config.rejected[0].instance_id, uuid::Uuid::from_u128(1));
|
||||
}
|
||||
@@ -338,6 +317,10 @@ mod tests {
|
||||
fn projects_ipv6_and_shared_dns_and_rejects_overlapping_proxy_routes() {
|
||||
let mut first = member(1, "10.0.1.1/24");
|
||||
first.dns = true;
|
||||
first
|
||||
.claims
|
||||
.ipv4_addresses
|
||||
.insert("10.0.4.1/32".parse().unwrap());
|
||||
first
|
||||
.claims
|
||||
.ipv6_addresses
|
||||
@@ -348,12 +331,16 @@ mod tests {
|
||||
.insert(SharedIpv6Route::new("::".parse().unwrap(), 0, Some(5)));
|
||||
let mut second = member(2, "10.0.2.1/24");
|
||||
second.dns = true;
|
||||
let (_, config) = select_members(
|
||||
let (members, rejected) = select_members(
|
||||
vec![first, second, member(3, "100.100.100.101/32")],
|
||||
Vec::new(),
|
||||
);
|
||||
let config = config_for_members(&members, rejected);
|
||||
assert_eq!(config.instance_ids.len(), 2);
|
||||
assert!(config.addresses.contains(&"10.0.4.1/32".to_owned()));
|
||||
assert!(config.routes.contains(&"10.0.4.1/32".to_owned()));
|
||||
assert!(config.addresses.contains(&"2001:db8::1/128".to_owned()));
|
||||
assert!(config.routes.contains(&"2001:db8::1/128".to_owned()));
|
||||
assert!(config.routes.contains(&"::/0".to_owned()));
|
||||
assert_eq!(
|
||||
config
|
||||
@@ -371,7 +358,9 @@ mod tests {
|
||||
24,
|
||||
None,
|
||||
));
|
||||
let (_, config) = select_members(vec![member(1, "10.0.1.1/24"), route_owner], Vec::new());
|
||||
let (members, rejected) =
|
||||
select_members(vec![member(1, "10.0.1.1/24"), route_owner], Vec::new());
|
||||
let config = config_for_members(&members, rejected);
|
||||
assert_eq!(config.instance_ids.len(), 1);
|
||||
assert_eq!(config.rejected[0].instance_id, uuid::Uuid::from_u128(2));
|
||||
}
|
||||
|
||||
@@ -3,44 +3,28 @@ use std::{
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
||||
use easytier_core::{host::packet::HostPacketReceiver, instance::CorePacketPlane};
|
||||
use tokio::{sync::Mutex, task::JoinSet};
|
||||
use cidr::Ipv4Inet;
|
||||
#[cfg(not(mobile))]
|
||||
use easytier_core::instance::CorePacketPlane;
|
||||
use easytier_core::{
|
||||
gateway::dhcp::{DhcpIpv4ApplyOutcome, DhcpIpv4ApplyPermit, DhcpIpv4Host},
|
||||
host::packet::HostPacketReceiver,
|
||||
};
|
||||
use futures::FutureExt as _;
|
||||
use tokio::{
|
||||
sync::{Mutex, Notify},
|
||||
task::JoinSet,
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use super::MagicDnsRuntime;
|
||||
#[cfg(not(mobile))]
|
||||
use crate::instance::shared_virtual_nic::ArcSharedVirtualNicRegistry;
|
||||
use crate::{
|
||||
common::{error::Error, global_ctx::ArcGlobalCtx},
|
||||
instance::{shared_virtual_nic::ArcSharedVirtualNicRegistry, virtual_nic::NicCtx},
|
||||
common::global_ctx::{ArcGlobalCtx, GlobalCtxEvent},
|
||||
instance::virtual_nic::NicCtx,
|
||||
};
|
||||
|
||||
pub(super) async fn create_nic_ctx(
|
||||
global_ctx: ArcGlobalCtx,
|
||||
packet_plane: Arc<CorePacketPlane>,
|
||||
receiver: Arc<Mutex<HostPacketReceiver>>,
|
||||
close_notifier: Arc<tokio::sync::Notify>,
|
||||
registry: ArcSharedVirtualNicRegistry,
|
||||
) -> Result<NicCtx, Error> {
|
||||
#[cfg(not(mobile))]
|
||||
if global_ctx.get_flags().dev_name.is_empty() {
|
||||
return Ok(NicCtx::new(
|
||||
global_ctx,
|
||||
packet_plane,
|
||||
receiver,
|
||||
close_notifier,
|
||||
));
|
||||
}
|
||||
|
||||
let member_id = global_ctx.get_id();
|
||||
NicCtx::new_shared(
|
||||
global_ctx,
|
||||
packet_plane,
|
||||
receiver,
|
||||
close_notifier,
|
||||
registry,
|
||||
member_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
struct NicCtxContainer {
|
||||
_nic_ctx: Option<Box<dyn Any + Send>>,
|
||||
magic_dns: MagicDnsRuntime,
|
||||
@@ -130,3 +114,161 @@ impl TunNicState {
|
||||
.replace(NicCtxContainer::new(nic, magic_dns));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct NativeDhcpIpv4Host {
|
||||
pub(super) global_ctx: ArcGlobalCtx,
|
||||
pub(super) operation: Arc<Mutex<()>>,
|
||||
pub(super) cancel: CancellationToken,
|
||||
pub(super) nic: TunNicState,
|
||||
pub(super) closed: Arc<Notify>,
|
||||
#[cfg(not(mobile))]
|
||||
pub(super) packet_plane: Arc<CorePacketPlane>,
|
||||
#[cfg(not(mobile))]
|
||||
pub(super) shared_virtual_nic_registry: ArcSharedVirtualNicRegistry,
|
||||
}
|
||||
|
||||
impl NativeDhcpIpv4Host {
|
||||
fn ensure_open(&self) -> anyhow::Result<()> {
|
||||
if self.cancel.is_cancelled() {
|
||||
anyhow::bail!("instance is closing; DHCP update cancelled");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn apply(&self, next: Option<Ipv4Inet>) -> anyhow::Result<Option<Ipv4Inet>> {
|
||||
self.ensure_open()?;
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => anyhow::bail!("instance is closing; DHCP update cancelled"),
|
||||
_ = self.nic.drain() => {}
|
||||
}
|
||||
self.ensure_open()?;
|
||||
|
||||
#[cfg(not(mobile))]
|
||||
if let Some(ip) = next
|
||||
&& !self.global_ctx.no_tun()
|
||||
{
|
||||
let mut nic = NicCtx::new(
|
||||
self.global_ctx.clone(),
|
||||
self.packet_plane.clone(),
|
||||
self.nic.receiver(),
|
||||
self.closed.clone(),
|
||||
self.shared_virtual_nic_registry.clone(),
|
||||
)
|
||||
.await;
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => anyhow::bail!("instance is closing; DHCP update cancelled"),
|
||||
result = nic.run(Some(ip), self.global_ctx.get_ipv6()) => result?,
|
||||
}
|
||||
let shared_route_backend = nic.shared_route_backend_for_dns();
|
||||
let magic_dns = MagicDnsRuntime::start(
|
||||
self.global_ctx.clone(),
|
||||
self.packet_plane.clone(),
|
||||
nic.ifname().await,
|
||||
ip,
|
||||
shared_route_backend,
|
||||
);
|
||||
self.nic.install(nic, magic_dns).await;
|
||||
}
|
||||
self.global_ctx.set_ipv4(next);
|
||||
Ok(next)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DhcpIpv4Host for NativeDhcpIpv4Host {
|
||||
fn take_interface_closed(&self) -> bool {
|
||||
self.closed.notified().now_or_never().is_some()
|
||||
}
|
||||
|
||||
async fn apply_dhcp_ipv4(
|
||||
&self,
|
||||
_previous: Option<Ipv4Inet>,
|
||||
next: Option<Ipv4Inet>,
|
||||
) -> DhcpIpv4ApplyOutcome {
|
||||
let permit = self.operation.clone().lock_owned().await;
|
||||
let outcome = match self.apply(next).await {
|
||||
Ok(actual) => DhcpIpv4ApplyOutcome::applied(actual),
|
||||
Err(error) => DhcpIpv4ApplyOutcome::failed(self.global_ctx.get_ipv4(), error),
|
||||
};
|
||||
outcome.with_permit(DhcpIpv4ApplyPermit::new(permit))
|
||||
}
|
||||
|
||||
fn publish_dhcp_ipv4(
|
||||
&self,
|
||||
previous: Option<Ipv4Inet>,
|
||||
requested: Option<Ipv4Inet>,
|
||||
actual: Option<Ipv4Inet>,
|
||||
) {
|
||||
let event = if requested.is_none() {
|
||||
GlobalCtxEvent::DhcpIpv4Conflicted(previous)
|
||||
} else {
|
||||
GlobalCtxEvent::DhcpIpv4Changed(previous, actual)
|
||||
};
|
||||
self.global_ctx.issue_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use easytier_core::{config::toml::TomlConfig, process_runtime::CoreProcessRuntime};
|
||||
|
||||
use super::*;
|
||||
use crate::instance::test_instance::TestInstance;
|
||||
|
||||
#[tokio::test]
|
||||
async fn dhcp_preserves_publication_permit_and_cancelled_address() {
|
||||
let instance = TestInstance::new_with_process_runtime(
|
||||
TomlConfig::default(),
|
||||
CoreProcessRuntime::new(),
|
||||
);
|
||||
let global_ctx = instance.get_global_ctx();
|
||||
let mut flags = global_ctx.get_flags();
|
||||
flags.no_tun = true;
|
||||
global_ctx.set_flags(flags);
|
||||
let operation = Arc::new(Mutex::new(()));
|
||||
let cancel = CancellationToken::new();
|
||||
let runtime = super::super::NativeTunRuntime::new(
|
||||
global_ctx.clone(),
|
||||
cancel.clone(),
|
||||
TestInstance::new_shared_virtual_nic_registry(),
|
||||
);
|
||||
let (_sender, receiver) = tokio::sync::mpsc::channel(1);
|
||||
runtime
|
||||
.install_packet_receiver(HostPacketReceiver::new(receiver))
|
||||
.unwrap();
|
||||
let host = runtime.dhcp_host(
|
||||
operation.clone(),
|
||||
instance.get_core_instance().packet_plane(),
|
||||
);
|
||||
let mut events = global_ctx.subscribe();
|
||||
let address = "10.20.0.7/24".parse().unwrap();
|
||||
let mut previous = None;
|
||||
for next in [Some(address), None] {
|
||||
let outcome = host.apply_dhcp_ipv4(previous, next).await;
|
||||
assert!(outcome.result.is_ok());
|
||||
assert_eq!(outcome.actual, next);
|
||||
assert_eq!(global_ctx.get_ipv4(), next);
|
||||
assert!(operation.try_lock().is_err());
|
||||
host.publish_dhcp_ipv4(previous, next, outcome.actual);
|
||||
assert_eq!(
|
||||
events.try_recv().unwrap(),
|
||||
if next.is_none() {
|
||||
GlobalCtxEvent::DhcpIpv4Conflicted(previous)
|
||||
} else {
|
||||
GlobalCtxEvent::DhcpIpv4Changed(previous, next)
|
||||
}
|
||||
);
|
||||
drop(outcome);
|
||||
assert!(operation.try_lock().is_ok());
|
||||
previous = next;
|
||||
}
|
||||
|
||||
global_ctx.set_ipv4(Some(address));
|
||||
cancel.cancel();
|
||||
let outcome = host.apply_dhcp_ipv4(Some(address), None).await;
|
||||
assert!(outcome.result.is_err());
|
||||
assert_eq!(outcome.actual, Some(address));
|
||||
assert_eq!(global_ctx.get_ipv4(), Some(address));
|
||||
runtime.shutdown().await;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::Context as _;
|
||||
use cidr::Ipv4Inet;
|
||||
use easytier_core::{
|
||||
gateway::dhcp::{DhcpIpv4ApplyOutcome, DhcpIpv4ApplyPermit, DhcpIpv4Host},
|
||||
host::packet::HostPacketReceiver,
|
||||
instance::CorePacketPlane,
|
||||
gateway::dhcp::DhcpIpv4Host, host::packet::HostPacketReceiver, instance::CorePacketPlane,
|
||||
};
|
||||
use futures::FutureExt as _;
|
||||
use tokio::{
|
||||
sync::{Mutex, Notify, oneshot},
|
||||
task::JoinHandle,
|
||||
@@ -16,14 +12,11 @@ use tokio_util::sync::CancellationToken;
|
||||
|
||||
use super::{
|
||||
MagicDnsRuntime,
|
||||
tun_common::{TunNicState, create_nic_ctx},
|
||||
tun_common::{NativeDhcpIpv4Host, TunNicState},
|
||||
};
|
||||
use crate::{
|
||||
common::{
|
||||
error::Error,
|
||||
global_ctx::{ArcGlobalCtx, GlobalCtxEvent},
|
||||
},
|
||||
instance::shared_virtual_nic::ArcSharedVirtualNicRegistry,
|
||||
common::{error::Error, global_ctx::ArcGlobalCtx},
|
||||
instance::{shared_virtual_nic::ArcSharedVirtualNicRegistry, virtual_nic::NicCtx},
|
||||
};
|
||||
|
||||
pub(super) struct NativeTunRuntime {
|
||||
@@ -86,29 +79,14 @@ impl NativeTunRuntime {
|
||||
return;
|
||||
}
|
||||
let closed = Arc::new(Notify::new());
|
||||
let mut nic = match create_nic_ctx(
|
||||
let mut nic = NicCtx::new(
|
||||
global_ctx.clone(),
|
||||
packet_plane.clone(),
|
||||
receiver.clone(),
|
||||
closed.clone(),
|
||||
shared_virtual_nic_registry.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(nic) => nic,
|
||||
Err(error) => {
|
||||
if let Some(output) = output.take() {
|
||||
let _ = output.send(Err(error));
|
||||
return;
|
||||
}
|
||||
tracing::error!(?error, "failed to create native interface context");
|
||||
tokio::select! {
|
||||
_ = cancel.cancelled() => return,
|
||||
_ = tokio::time::sleep(Duration::from_secs(1)) => {}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
.await;
|
||||
let result = tokio::select! {
|
||||
biased;
|
||||
_ = cancel.cancelled() => {
|
||||
@@ -194,98 +172,3 @@ impl NativeTunRuntime {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct NativeDhcpIpv4Host {
|
||||
global_ctx: ArcGlobalCtx,
|
||||
operation: Arc<Mutex<()>>,
|
||||
cancel: CancellationToken,
|
||||
nic: TunNicState,
|
||||
closed: Arc<Notify>,
|
||||
packet_plane: Arc<CorePacketPlane>,
|
||||
shared_virtual_nic_registry: ArcSharedVirtualNicRegistry,
|
||||
}
|
||||
|
||||
impl NativeDhcpIpv4Host {
|
||||
fn ensure_open(&self) -> anyhow::Result<()> {
|
||||
if self.cancel.is_cancelled() {
|
||||
anyhow::bail!("instance is closing; DHCP update cancelled");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn apply(&self, next: Option<Ipv4Inet>) -> anyhow::Result<Option<Ipv4Inet>> {
|
||||
self.ensure_open()?;
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => anyhow::bail!("instance is closing; DHCP update cancelled"),
|
||||
_ = self.nic.drain() => {}
|
||||
}
|
||||
self.ensure_open()?;
|
||||
|
||||
let Some(ip) = next else {
|
||||
self.global_ctx.set_ipv4(None);
|
||||
return Ok(None);
|
||||
};
|
||||
if self.global_ctx.no_tun() {
|
||||
self.global_ctx.set_ipv4(Some(ip));
|
||||
return Ok(Some(ip));
|
||||
}
|
||||
|
||||
let mut nic = create_nic_ctx(
|
||||
self.global_ctx.clone(),
|
||||
self.packet_plane.clone(),
|
||||
self.nic.receiver(),
|
||||
self.closed.clone(),
|
||||
self.shared_virtual_nic_registry.clone(),
|
||||
)
|
||||
.await?;
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => anyhow::bail!("instance is closing; DHCP update cancelled"),
|
||||
result = nic.run(Some(ip), self.global_ctx.get_ipv6()) => result?,
|
||||
}
|
||||
let shared_route_backend = nic.shared_route_backend_for_dns();
|
||||
let magic_dns = MagicDnsRuntime::start(
|
||||
self.global_ctx.clone(),
|
||||
self.packet_plane.clone(),
|
||||
nic.ifname().await,
|
||||
ip,
|
||||
shared_route_backend,
|
||||
);
|
||||
self.nic.install(nic, magic_dns).await;
|
||||
self.global_ctx.set_ipv4(Some(ip));
|
||||
Ok(Some(ip))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DhcpIpv4Host for NativeDhcpIpv4Host {
|
||||
fn take_interface_closed(&self) -> bool {
|
||||
self.closed.notified().now_or_never().is_some()
|
||||
}
|
||||
|
||||
async fn apply_dhcp_ipv4(
|
||||
&self,
|
||||
_previous: Option<Ipv4Inet>,
|
||||
next: Option<Ipv4Inet>,
|
||||
) -> DhcpIpv4ApplyOutcome {
|
||||
let permit = self.operation.clone().lock_owned().await;
|
||||
let outcome = match self.apply(next).await {
|
||||
Ok(actual) => DhcpIpv4ApplyOutcome::applied(actual),
|
||||
Err(error) => DhcpIpv4ApplyOutcome::failed(self.global_ctx.get_ipv4(), error),
|
||||
};
|
||||
outcome.with_permit(DhcpIpv4ApplyPermit::new(permit))
|
||||
}
|
||||
|
||||
fn publish_dhcp_ipv4(
|
||||
&self,
|
||||
previous: Option<Ipv4Inet>,
|
||||
requested: Option<Ipv4Inet>,
|
||||
actual: Option<Ipv4Inet>,
|
||||
) {
|
||||
let event = if requested.is_none() {
|
||||
GlobalCtxEvent::DhcpIpv4Conflicted(previous)
|
||||
} else {
|
||||
GlobalCtxEvent::DhcpIpv4Changed(previous, actual)
|
||||
};
|
||||
self.global_ctx.issue_event(event);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,19 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use cidr::Ipv4Inet;
|
||||
use easytier_core::{
|
||||
gateway::dhcp::{DhcpIpv4ApplyOutcome, DhcpIpv4ApplyPermit, DhcpIpv4Host},
|
||||
host::packet::HostPacketReceiver,
|
||||
instance::CorePacketPlane,
|
||||
gateway::dhcp::DhcpIpv4Host, host::packet::HostPacketReceiver, instance::CorePacketPlane,
|
||||
};
|
||||
use futures::FutureExt as _;
|
||||
use tokio::sync::{Mutex, Notify, mpsc, oneshot};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use super::{
|
||||
MagicDnsRuntime,
|
||||
tun_common::{TunNicState, create_nic_ctx},
|
||||
tun_common::{NativeDhcpIpv4Host, TunNicState},
|
||||
};
|
||||
use crate::{
|
||||
common::global_ctx::{ArcGlobalCtx, GlobalCtxEvent},
|
||||
instance::shared_virtual_nic::ArcSharedVirtualNicRegistry,
|
||||
common::global_ctx::ArcGlobalCtx,
|
||||
instance::{shared_virtual_nic::ArcSharedVirtualNicRegistry, virtual_nic::NicCtx},
|
||||
};
|
||||
|
||||
struct MobileTunAttachment {
|
||||
@@ -76,20 +72,17 @@ impl NativeTunRuntime {
|
||||
return Ok(());
|
||||
}
|
||||
let closed = Arc::new(Notify::new());
|
||||
let mut nic = create_nic_ctx(
|
||||
let mut nic = NicCtx::new(
|
||||
global_ctx.clone(),
|
||||
packet_plane.clone(),
|
||||
nic_state.receiver(),
|
||||
closed,
|
||||
shared_virtual_nic_registry,
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
let claims = match claims {
|
||||
Some(claims) => claims,
|
||||
None => {
|
||||
crate::instance::virtual_nic::NicCtx::mobile_tun_claims(&global_ctx, &packet_plane)
|
||||
.await
|
||||
}
|
||||
None => NicCtx::mobile_tun_claims(&global_ctx, &packet_plane).await,
|
||||
};
|
||||
nic.run_for_mobile(fd, replace_tun_fd, claims)
|
||||
.await
|
||||
@@ -122,21 +115,16 @@ impl NativeTunRuntime {
|
||||
None => return,
|
||||
},
|
||||
};
|
||||
let result = if attachment.fd <= 0 {
|
||||
nic_state.drain().await;
|
||||
Ok(())
|
||||
} else {
|
||||
Self::install_mobile_tun(
|
||||
nic_state.clone(),
|
||||
global_ctx.clone(),
|
||||
packet_plane.clone(),
|
||||
attachment.fd,
|
||||
attachment.replace_tun_fd,
|
||||
shared_virtual_nic_registry.clone(),
|
||||
attachment.claims,
|
||||
)
|
||||
.await
|
||||
};
|
||||
let result = Self::install_mobile_tun(
|
||||
nic_state.clone(),
|
||||
global_ctx.clone(),
|
||||
packet_plane.clone(),
|
||||
attachment.fd,
|
||||
attachment.replace_tun_fd,
|
||||
shared_virtual_nic_registry.clone(),
|
||||
attachment.claims,
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = &result {
|
||||
tracing::error!(?error, "failed to attach mobile TUN fd");
|
||||
}
|
||||
@@ -215,70 +203,3 @@ impl NativeTunRuntime {
|
||||
self.nic.mobile_tun_claims().await
|
||||
}
|
||||
}
|
||||
|
||||
struct NativeDhcpIpv4Host {
|
||||
global_ctx: ArcGlobalCtx,
|
||||
operation: Arc<Mutex<()>>,
|
||||
cancel: CancellationToken,
|
||||
nic: TunNicState,
|
||||
closed: Arc<Notify>,
|
||||
}
|
||||
|
||||
impl NativeDhcpIpv4Host {
|
||||
fn ensure_open(&self) -> anyhow::Result<()> {
|
||||
if self.cancel.is_cancelled() {
|
||||
anyhow::bail!("instance is closing; DHCP update cancelled");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn apply(&self, next: Option<Ipv4Inet>) -> anyhow::Result<Option<Ipv4Inet>> {
|
||||
self.ensure_open()?;
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => anyhow::bail!("instance is closing; DHCP update cancelled"),
|
||||
_ = self.nic.drain() => {}
|
||||
}
|
||||
self.ensure_open()?;
|
||||
|
||||
let Some(ip) = next else {
|
||||
self.global_ctx.set_ipv4(None);
|
||||
return Ok(None);
|
||||
};
|
||||
self.global_ctx.set_ipv4(Some(ip));
|
||||
Ok(Some(ip))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DhcpIpv4Host for NativeDhcpIpv4Host {
|
||||
fn take_interface_closed(&self) -> bool {
|
||||
self.closed.notified().now_or_never().is_some()
|
||||
}
|
||||
|
||||
async fn apply_dhcp_ipv4(
|
||||
&self,
|
||||
_previous: Option<Ipv4Inet>,
|
||||
next: Option<Ipv4Inet>,
|
||||
) -> DhcpIpv4ApplyOutcome {
|
||||
let permit = self.operation.clone().lock_owned().await;
|
||||
let outcome = match self.apply(next).await {
|
||||
Ok(actual) => DhcpIpv4ApplyOutcome::applied(actual),
|
||||
Err(error) => DhcpIpv4ApplyOutcome::failed(self.global_ctx.get_ipv4(), error),
|
||||
};
|
||||
outcome.with_permit(DhcpIpv4ApplyPermit::new(permit))
|
||||
}
|
||||
|
||||
fn publish_dhcp_ipv4(
|
||||
&self,
|
||||
previous: Option<Ipv4Inet>,
|
||||
requested: Option<Ipv4Inet>,
|
||||
actual: Option<Ipv4Inet>,
|
||||
) {
|
||||
let event = if requested.is_none() {
|
||||
GlobalCtxEvent::DhcpIpv4Conflicted(previous)
|
||||
} else {
|
||||
GlobalCtxEvent::DhcpIpv4Changed(previous, actual)
|
||||
};
|
||||
self.global_ctx.issue_event(event);
|
||||
}
|
||||
}
|
||||
@@ -44,9 +44,7 @@ use zerocopy::{NativeEndian, NetworkEndian};
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::common::ifcfg::RegistryManager;
|
||||
|
||||
use super::shared_virtual_nic::{
|
||||
ArcSharedVirtualNicRegistry, SharedVirtualNicMember, SharedVirtualNicMemberId,
|
||||
};
|
||||
use super::shared_virtual_nic::{ArcSharedVirtualNicRegistry, SharedVirtualNicMember};
|
||||
|
||||
pin_project! {
|
||||
pub struct TunStream {
|
||||
@@ -905,14 +903,6 @@ pub enum NicBackend {
|
||||
}
|
||||
|
||||
impl NicBackend {
|
||||
pub fn dedicated(nic: Arc<Mutex<VirtualNic>>) -> Self {
|
||||
Self::Dedicated(nic)
|
||||
}
|
||||
|
||||
pub fn shared(member: SharedVirtualNicMember) -> Self {
|
||||
Self::Shared(member)
|
||||
}
|
||||
|
||||
pub async fn create_dev(&self) -> Result<Box<dyn Tunnel>, Error> {
|
||||
match self {
|
||||
Self::Dedicated(nic) => nic.lock().await.create_dev().await,
|
||||
@@ -1083,27 +1073,14 @@ pub struct NicCtx {
|
||||
}
|
||||
|
||||
impl NicCtx {
|
||||
fn virtual_nic_config_from_parts(
|
||||
dev_name: String,
|
||||
mut mtu: u32,
|
||||
enable_encryption: bool,
|
||||
net_ns: NetNS,
|
||||
) -> VirtualNicConfig {
|
||||
if enable_encryption {
|
||||
fn virtual_nic_config(global_ctx: &ArcGlobalCtx) -> VirtualNicConfig {
|
||||
let flags = global_ctx.get_flags();
|
||||
let mut mtu = flags.mtu;
|
||||
if flags.enable_encryption {
|
||||
mtu -= 20;
|
||||
}
|
||||
|
||||
VirtualNicConfig::new(dev_name, mtu, net_ns)
|
||||
}
|
||||
|
||||
fn virtual_nic_config(global_ctx: &ArcGlobalCtx) -> VirtualNicConfig {
|
||||
let flags = global_ctx.get_flags();
|
||||
Self::virtual_nic_config_from_parts(
|
||||
flags.dev_name,
|
||||
flags.mtu,
|
||||
flags.enable_encryption,
|
||||
global_ctx.net_ns.clone(),
|
||||
)
|
||||
VirtualNicConfig::new(flags.dev_name, mtu, global_ctx.net_ns.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn shared_route_backend_for_dns(&self) -> Option<NicBackend> {
|
||||
@@ -1113,11 +1090,6 @@ impl NicCtx {
|
||||
}
|
||||
}
|
||||
|
||||
fn dedicated_backend(global_ctx: &ArcGlobalCtx) -> NicBackend {
|
||||
let nic_config = Self::virtual_nic_config(global_ctx);
|
||||
NicBackend::dedicated(Arc::new(Mutex::new(VirtualNic::new(nic_config))))
|
||||
}
|
||||
|
||||
fn new_with_backend(
|
||||
global_ctx: ArcGlobalCtx,
|
||||
packet_plane: Arc<CorePacketPlane>,
|
||||
@@ -1140,13 +1112,30 @@ impl NicCtx {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
pub(crate) async fn new(
|
||||
global_ctx: ArcGlobalCtx,
|
||||
packet_plane: Arc<CorePacketPlane>,
|
||||
peer_packet_receiver: Arc<Mutex<HostPacketReceiver>>,
|
||||
close_notifier: Arc<Notify>,
|
||||
registry: ArcSharedVirtualNicRegistry,
|
||||
) -> Self {
|
||||
let backend = Self::dedicated_backend(&global_ctx);
|
||||
let nic_config = Self::virtual_nic_config(&global_ctx);
|
||||
#[cfg(mobile)]
|
||||
let nic_config = VirtualNicConfig {
|
||||
dev_name: String::new(),
|
||||
net_ns: NetNS::new(None),
|
||||
..nic_config
|
||||
};
|
||||
let backend = if cfg!(mobile) || !nic_config.dev_name.is_empty() {
|
||||
NicBackend::Shared(registry.lock().await.create_member(
|
||||
nic_config.dev_name.clone(),
|
||||
nic_config,
|
||||
global_ctx.get_id(),
|
||||
close_notifier.clone(),
|
||||
))
|
||||
} else {
|
||||
NicBackend::Dedicated(Arc::new(Mutex::new(VirtualNic::new(nic_config))))
|
||||
};
|
||||
|
||||
Self::new_with_backend(
|
||||
global_ctx,
|
||||
@@ -1157,51 +1146,6 @@ impl NicCtx {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) async fn new_shared(
|
||||
global_ctx: ArcGlobalCtx,
|
||||
packet_plane: Arc<CorePacketPlane>,
|
||||
peer_packet_receiver: Arc<Mutex<HostPacketReceiver>>,
|
||||
close_notifier: Arc<Notify>,
|
||||
registry: ArcSharedVirtualNicRegistry,
|
||||
member_id: SharedVirtualNicMemberId,
|
||||
) -> Result<Self, Error> {
|
||||
let flags = global_ctx.get_flags();
|
||||
#[cfg(mobile)]
|
||||
let dev_name = String::new();
|
||||
#[cfg(not(mobile))]
|
||||
let dev_name = flags.dev_name.clone();
|
||||
#[cfg(not(mobile))]
|
||||
if dev_name.is_empty() {
|
||||
return Err(anyhow::anyhow!("shared virtual nic requires dev_name").into());
|
||||
}
|
||||
#[cfg(mobile)]
|
||||
let net_ns = NetNS::new(None);
|
||||
#[cfg(not(mobile))]
|
||||
let net_ns = global_ctx.net_ns.clone();
|
||||
let nic_config = Self::virtual_nic_config_from_parts(
|
||||
dev_name.clone(),
|
||||
flags.mtu,
|
||||
flags.enable_encryption,
|
||||
net_ns,
|
||||
);
|
||||
|
||||
let member = registry.lock().await.create_member(
|
||||
dev_name,
|
||||
nic_config,
|
||||
member_id,
|
||||
close_notifier.clone(),
|
||||
);
|
||||
let backend = NicBackend::shared(member);
|
||||
|
||||
Ok(Self::new_with_backend(
|
||||
global_ctx,
|
||||
packet_plane,
|
||||
peer_packet_receiver,
|
||||
close_notifier,
|
||||
backend,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn ifname(&self) -> Option<String> {
|
||||
self.backend.ifname().await
|
||||
}
|
||||
@@ -1845,7 +1789,7 @@ mod tests {
|
||||
core.packet_plane(),
|
||||
Arc::new(Mutex::new(HostPacketReceiver::new(packet_receiver))),
|
||||
Arc::new(Notify::new()),
|
||||
NicBackend::shared(members[1].clone()),
|
||||
NicBackend::Shared(members[1].clone()),
|
||||
);
|
||||
|
||||
nic.configure_mobile_tun().await.unwrap();
|
||||
|
||||
Reference in new issue
Block a user