mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-03 17:45:44 +00:00
feat: add interface address fallback for IPv6 prefix detection (DHCPv6 IA_NA / SLAAC) (#2334)
feat: add IPv6 prefix fallback and NDP proxy for SLAAC/IA_NA Add interface address fallback for IPv6 prefix detection when route-based detection fails (no delegated prefix on LAN). Covers DHCPv6 IA_NA / SLAAC where prefix is assigned to WAN without PD. Uses getifaddrs() and Ipv6Inet to handle host bits. Add NDP proxy sync for on-link prefixes. ISP router uses NDP to resolve MACs; EasyTier /128 addresses on tun0 won't answer on physical WAN. Periodic sync (30s) reads /128 routes via netlink and manages proxy neigh entries. Auto-enables proxy_ndp sysctl. Returns WAN ifindex from detection; static NDP_WAN_IFINDEX for sync task. Spawns from provider reconcile when auto-detection enabled. Add tests: fallback default route interface, on-link NDP proxy, interface prefix selection. Fixes #2333 Co-authored-by: ririyeye <200610237@qq.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: KKRainbow <443152178@qq.com>
This commit is contained in:
co-authored by
Claude
KKRainbow
parent
741460e1e4
commit
7756a15cbe
@@ -219,6 +219,7 @@ pub struct GlobalCtx {
|
|||||||
|
|
||||||
running_listeners: Mutex<Vec<url::Url>>,
|
running_listeners: Mutex<Vec<url::Url>>,
|
||||||
advertised_ipv6_public_addr_prefix: Mutex<Option<cidr::Ipv6Cidr>>,
|
advertised_ipv6_public_addr_prefix: Mutex<Option<cidr::Ipv6Cidr>>,
|
||||||
|
tun_device_name: Mutex<Option<String>>,
|
||||||
|
|
||||||
flags: ArcSwap<Flags>,
|
flags: ArcSwap<Flags>,
|
||||||
|
|
||||||
@@ -336,6 +337,7 @@ impl GlobalCtx {
|
|||||||
|
|
||||||
running_listeners: Mutex::new(Vec::new()),
|
running_listeners: Mutex::new(Vec::new()),
|
||||||
advertised_ipv6_public_addr_prefix: Mutex::new(None),
|
advertised_ipv6_public_addr_prefix: Mutex::new(None),
|
||||||
|
tun_device_name: Mutex::new(None),
|
||||||
|
|
||||||
flags: ArcSwap::new(Arc::new(flags)),
|
flags: ArcSwap::new(Arc::new(flags)),
|
||||||
|
|
||||||
@@ -370,6 +372,24 @@ impl GlobalCtx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_tun_device_name(&self, name: Option<String>) {
|
||||||
|
*self.tun_device_name.lock().unwrap() = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_tun_device_ready(&self, name: String) {
|
||||||
|
self.set_tun_device_name(Some(name.clone()));
|
||||||
|
self.issue_event(GlobalCtxEvent::TunDeviceReady(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_tun_device_error(&self, error: String) {
|
||||||
|
self.set_tun_device_name(None);
|
||||||
|
self.issue_event(GlobalCtxEvent::TunDeviceError(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tun_device_name(&self) -> Option<String> {
|
||||||
|
self.tun_device_name.lock().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_network_in_whitelist(&self, network_name: &str) -> Result<(), anyhow::Error> {
|
pub fn check_network_in_whitelist(&self, network_name: &str) -> Result<(), anyhow::Error> {
|
||||||
if self
|
if self
|
||||||
.get_flags()
|
.get_flags()
|
||||||
@@ -825,6 +845,36 @@ pub mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_tun_device_name_tracks_explicit_runtime_state() {
|
||||||
|
let config = TomlConfigLoader::default();
|
||||||
|
let global_ctx = GlobalCtx::new(config);
|
||||||
|
|
||||||
|
assert_eq!(global_ctx.get_tun_device_name(), None);
|
||||||
|
|
||||||
|
global_ctx.issue_event(GlobalCtxEvent::TunDeviceReady("ignored".to_string()));
|
||||||
|
assert_eq!(global_ctx.get_tun_device_name(), None);
|
||||||
|
|
||||||
|
let mut subscriber = global_ctx.subscribe();
|
||||||
|
|
||||||
|
global_ctx.set_tun_device_ready("easytier0".to_string());
|
||||||
|
assert_eq!(
|
||||||
|
global_ctx.get_tun_device_name(),
|
||||||
|
Some("easytier0".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
subscriber.recv().await.unwrap(),
|
||||||
|
GlobalCtxEvent::TunDeviceReady("easytier0".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
global_ctx.set_tun_device_error("closed".to_string());
|
||||||
|
assert_eq!(global_ctx.get_tun_device_name(), None);
|
||||||
|
assert_eq!(
|
||||||
|
subscriber.recv().await.unwrap(),
|
||||||
|
GlobalCtxEvent::TunDeviceError("closed".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn trusted_key_source_lookup_is_precise() {
|
async fn trusted_key_source_lookup_is_precise() {
|
||||||
let config = TomlConfigLoader::default();
|
let config = TomlConfigLoader::default();
|
||||||
|
|||||||
@@ -177,3 +177,20 @@ pub(crate) fn list_ipv6_route_messages()
|
|||||||
pub(crate) fn get_interface_index(name: &str) -> Result<u32, Error> {
|
pub(crate) fn get_interface_index(name: &str) -> Result<u32, Error> {
|
||||||
netlink::NetlinkIfConfiger::get_interface_index(name)
|
netlink::NetlinkIfConfiger::get_interface_index(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub(crate) fn add_ipv6_ndp_proxy(name: &str, address: Ipv6Addr) -> Result<(), Error> {
|
||||||
|
netlink::NetlinkIfConfiger::add_ipv6_ndp_proxy(name, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub(crate) fn remove_ipv6_ndp_proxy(name: &str, address: Ipv6Addr) -> Result<(), Error> {
|
||||||
|
netlink::NetlinkIfConfiger::remove_ipv6_ndp_proxy(name, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub(crate) fn list_ipv6_ndp_proxy(
|
||||||
|
name: &str,
|
||||||
|
) -> Result<std::collections::BTreeSet<Ipv6Addr>, Error> {
|
||||||
|
netlink::NetlinkIfConfiger::list_ipv6_ndp_proxy(name)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
collections::BTreeSet,
|
||||||
ffi::CString,
|
ffi::CString,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||||
@@ -16,6 +17,10 @@ use netlink_packet_core::{
|
|||||||
use netlink_packet_route::{
|
use netlink_packet_route::{
|
||||||
AddressFamily, RouteNetlinkMessage,
|
AddressFamily, RouteNetlinkMessage,
|
||||||
address::{AddressAttribute, AddressMessage},
|
address::{AddressAttribute, AddressMessage},
|
||||||
|
neighbour::{
|
||||||
|
NeighbourAddress, NeighbourAttribute, NeighbourFlags, NeighbourHeader, NeighbourMessage,
|
||||||
|
NeighbourState,
|
||||||
|
},
|
||||||
route::{
|
route::{
|
||||||
RouteAddress, RouteAttribute, RouteHeader, RouteMessage, RouteProtocol, RouteScope,
|
RouteAddress, RouteAttribute, RouteHeader, RouteMessage, RouteProtocol, RouteScope,
|
||||||
RouteType,
|
RouteType,
|
||||||
@@ -375,6 +380,105 @@ impl NetlinkIfConfiger {
|
|||||||
pub(crate) fn list_ipv6_route_messages() -> Result<Vec<RouteMessage>, Error> {
|
pub(crate) fn list_ipv6_route_messages() -> Result<Vec<RouteMessage>, Error> {
|
||||||
Self::list_route_messages(AddressFamily::Inet6)
|
Self::list_route_messages(AddressFamily::Inet6)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ipv6_ndp_proxy_message(name: &str, address: Ipv6Addr) -> Result<NeighbourMessage, Error> {
|
||||||
|
let mut message = NeighbourMessage::default();
|
||||||
|
message.header = NeighbourHeader {
|
||||||
|
family: AddressFamily::Inet6,
|
||||||
|
ifindex: Self::get_interface_index(name)?,
|
||||||
|
state: NeighbourState::Permanent,
|
||||||
|
flags: NeighbourFlags::Proxy,
|
||||||
|
kind: RouteType::Unicast,
|
||||||
|
};
|
||||||
|
message
|
||||||
|
.attributes
|
||||||
|
.push(NeighbourAttribute::Destination(NeighbourAddress::Inet6(
|
||||||
|
address,
|
||||||
|
)));
|
||||||
|
Ok(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_ipv6_ndp_proxy(name: &str, address: Ipv6Addr) -> Result<(), Error> {
|
||||||
|
send_netlink_req_and_wait_one_resp(
|
||||||
|
RouteNetlinkMessage::NewNeighbour(Self::ipv6_ndp_proxy_message(name, address)?),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn remove_ipv6_ndp_proxy(name: &str, address: Ipv6Addr) -> Result<(), Error> {
|
||||||
|
send_netlink_req_and_wait_one_resp(
|
||||||
|
RouteNetlinkMessage::DelNeighbour(Self::ipv6_ndp_proxy_message(name, address)?),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_neighbour_messages(
|
||||||
|
address_family: AddressFamily,
|
||||||
|
) -> Result<Vec<NeighbourMessage>, Error> {
|
||||||
|
let mut message = NeighbourMessage::default();
|
||||||
|
message.header.family = address_family;
|
||||||
|
message.header.flags = NeighbourFlags::Proxy;
|
||||||
|
|
||||||
|
let s = send_netlink_req(
|
||||||
|
RouteNetlinkMessage::GetNeighbour(message),
|
||||||
|
NLM_F_REQUEST | NLM_F_DUMP,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut ret_vec = vec![];
|
||||||
|
let mut resp = Vec::<u8>::new();
|
||||||
|
loop {
|
||||||
|
if resp.is_empty() {
|
||||||
|
let (new_resp, _) = s.recv_from_full()?;
|
||||||
|
resp = new_resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = NetlinkMessage::<RouteNetlinkMessage>::deserialize(&resp)
|
||||||
|
.with_context(|| "Failed to deserialize netlink neighbour message")?;
|
||||||
|
resp = resp.split_off(ret.buffer_len());
|
||||||
|
|
||||||
|
tracing::debug!("net link response <<< {:?}", ret);
|
||||||
|
|
||||||
|
match ret.payload {
|
||||||
|
NetlinkPayload::Error(e) => {
|
||||||
|
if e.code == NonZero::new(0) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return Err(e.to_io().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewNeighbour(m)) => {
|
||||||
|
ret_vec.push(m);
|
||||||
|
}
|
||||||
|
NetlinkPayload::Done(_) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p => {
|
||||||
|
tracing::error!("Unexpected netlink response: {:?}", p);
|
||||||
|
return Err(anyhow::anyhow!("Unexpected netlink response").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret_vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn list_ipv6_ndp_proxy(name: &str) -> Result<BTreeSet<Ipv6Addr>, Error> {
|
||||||
|
let ifindex = Self::get_interface_index(name)?;
|
||||||
|
|
||||||
|
Ok(Self::list_neighbour_messages(AddressFamily::Inet6)?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|message| {
|
||||||
|
message.header.ifindex == ifindex
|
||||||
|
&& message.header.flags.contains(NeighbourFlags::Proxy)
|
||||||
|
})
|
||||||
|
.filter_map(|message| {
|
||||||
|
message.attributes.into_iter().find_map(|attr| match attr {
|
||||||
|
NeighbourAttribute::Destination(NeighbourAddress::Inet6(addr)) => Some(addr),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ use crate::vpn_portal::{self, VpnPortal};
|
|||||||
use super::dns_server::{MAGIC_DNS_FAKE_IP, runner::DnsRunner};
|
use super::dns_server::{MAGIC_DNS_FAKE_IP, runner::DnsRunner};
|
||||||
use super::listeners::ListenerManager;
|
use super::listeners::ListenerManager;
|
||||||
use super::public_ipv6_provider::{
|
use super::public_ipv6_provider::{
|
||||||
reconcile_public_ipv6_provider_runtime, run_public_ipv6_provider_reconcile_task,
|
PublicIpv6ProviderReconcileTask, reconcile_public_ipv6_provider_runtime,
|
||||||
should_run_public_ipv6_provider_reconcile, validate_public_ipv6_config,
|
run_public_ipv6_provider_reconcile_task, should_run_public_ipv6_provider_reconcile,
|
||||||
validate_public_ipv6_config_values,
|
validate_public_ipv6_config, validate_public_ipv6_config_values,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "socks5")]
|
#[cfg(feature = "socks5")]
|
||||||
@@ -194,6 +194,44 @@ impl NicCtxContainer {
|
|||||||
|
|
||||||
#[cfg(feature = "tun")]
|
#[cfg(feature = "tun")]
|
||||||
type ArcNicCtx = Arc<Mutex<Option<NicCtxContainer>>>;
|
type ArcNicCtx = Arc<Mutex<Option<NicCtxContainer>>>;
|
||||||
|
type ArcPublicIpv6ProviderTaskSlot = Arc<PublicIpv6ProviderTaskSlot>;
|
||||||
|
|
||||||
|
struct PublicIpv6ProviderTaskSlot {
|
||||||
|
task: Mutex<Option<PublicIpv6ProviderReconcileTask>>,
|
||||||
|
closing: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PublicIpv6ProviderTaskSlot {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
task: Mutex::new(None),
|
||||||
|
closing: AtomicBool::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ensure_started(&self, global_ctx: &ArcGlobalCtx) {
|
||||||
|
let mut task = self.task.lock().await;
|
||||||
|
if self.closing.load(Ordering::Acquire) || task.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*task = run_public_ipv6_provider_reconcile_task(global_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn shutdown(&self) {
|
||||||
|
self.closing.store(true, Ordering::Release);
|
||||||
|
let task = self.task.lock().await.take();
|
||||||
|
if let Some(task) = task {
|
||||||
|
task.shutdown().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ensure_public_ipv6_provider_reconcile_task(
|
||||||
|
global_ctx: &ArcGlobalCtx,
|
||||||
|
task_slot: &ArcPublicIpv6ProviderTaskSlot,
|
||||||
|
) {
|
||||||
|
task_slot.ensure_started(global_ctx).await;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct InstanceRpcServerHook {
|
pub struct InstanceRpcServerHook {
|
||||||
rpc_portal_whitelist: Vec<IpCidr>,
|
rpc_portal_whitelist: Vec<IpCidr>,
|
||||||
@@ -254,6 +292,7 @@ pub struct InstanceConfigPatcher {
|
|||||||
socks5_server: Weak<Socks5Server>,
|
socks5_server: Weak<Socks5Server>,
|
||||||
peer_manager: Weak<PeerManager>,
|
peer_manager: Weak<PeerManager>,
|
||||||
conn_manager: Weak<ManualConnectorManager>,
|
conn_manager: Weak<ManualConnectorManager>,
|
||||||
|
public_ipv6_provider_task: ArcPublicIpv6ProviderTaskSlot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstanceConfigPatcher {
|
impl InstanceConfigPatcher {
|
||||||
@@ -324,7 +363,6 @@ impl InstanceConfigPatcher {
|
|||||||
self.patch_mapped_listeners(patch.mapped_listeners).await?;
|
self.patch_mapped_listeners(patch.mapped_listeners).await?;
|
||||||
self.patch_connector(patch.connectors).await?;
|
self.patch_connector(patch.connectors).await?;
|
||||||
|
|
||||||
let provider_reconcile_was_running = should_run_public_ipv6_provider_reconcile(&global_ctx);
|
|
||||||
let mut provider_config_changed = false;
|
let mut provider_config_changed = false;
|
||||||
if let Some(hostname) = patch.hostname {
|
if let Some(hostname) = patch.hostname {
|
||||||
global_ctx.set_hostname(hostname.clone());
|
global_ctx.set_hostname(hostname.clone());
|
||||||
@@ -362,10 +400,12 @@ impl InstanceConfigPatcher {
|
|||||||
if provider_config_changed {
|
if provider_config_changed {
|
||||||
reconcile_public_ipv6_provider_runtime(&global_ctx).await;
|
reconcile_public_ipv6_provider_runtime(&global_ctx).await;
|
||||||
|
|
||||||
let provider_reconcile_should_run =
|
if should_run_public_ipv6_provider_reconcile(&global_ctx) {
|
||||||
should_run_public_ipv6_provider_reconcile(&global_ctx);
|
ensure_public_ipv6_provider_reconcile_task(
|
||||||
if !provider_reconcile_was_running && provider_reconcile_should_run {
|
&global_ctx,
|
||||||
run_public_ipv6_provider_reconcile_task(&global_ctx);
|
&self.public_ipv6_provider_task,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,6 +687,7 @@ pub struct Instance {
|
|||||||
socks5_server: Arc<Socks5Server>,
|
socks5_server: Arc<Socks5Server>,
|
||||||
|
|
||||||
proxy_cidrs_monitor: Option<AbortOnDropHandle<()>>,
|
proxy_cidrs_monitor: Option<AbortOnDropHandle<()>>,
|
||||||
|
public_ipv6_provider_task: ArcPublicIpv6ProviderTaskSlot,
|
||||||
|
|
||||||
global_ctx: ArcGlobalCtx,
|
global_ctx: ArcGlobalCtx,
|
||||||
}
|
}
|
||||||
@@ -734,6 +775,7 @@ impl Instance {
|
|||||||
socks5_server,
|
socks5_server,
|
||||||
|
|
||||||
proxy_cidrs_monitor: None,
|
proxy_cidrs_monitor: None,
|
||||||
|
public_ipv6_provider_task: Arc::new(PublicIpv6ProviderTaskSlot::new()),
|
||||||
|
|
||||||
global_ctx,
|
global_ctx,
|
||||||
}
|
}
|
||||||
@@ -1034,7 +1076,11 @@ impl Instance {
|
|||||||
.await?;
|
.await?;
|
||||||
self.listener_manager.lock().await.run().await?;
|
self.listener_manager.lock().await.run().await?;
|
||||||
self.peer_manager.run().await?;
|
self.peer_manager.run().await?;
|
||||||
run_public_ipv6_provider_reconcile_task(&self.global_ctx);
|
ensure_public_ipv6_provider_reconcile_task(
|
||||||
|
&self.global_ctx,
|
||||||
|
&self.public_ipv6_provider_task,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
#[cfg(feature = "tun")]
|
#[cfg(feature = "tun")]
|
||||||
{
|
{
|
||||||
@@ -1347,6 +1393,7 @@ impl Instance {
|
|||||||
socks5_server: Arc::downgrade(&self.socks5_server),
|
socks5_server: Arc::downgrade(&self.socks5_server),
|
||||||
peer_manager: Arc::downgrade(&self.peer_manager),
|
peer_manager: Arc::downgrade(&self.peer_manager),
|
||||||
conn_manager: Arc::downgrade(&self.conn_manager),
|
conn_manager: Arc::downgrade(&self.conn_manager),
|
||||||
|
public_ipv6_provider_task: self.public_ipv6_provider_task.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1602,6 +1649,7 @@ impl Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clear_resources(&mut self) {
|
pub async fn clear_resources(&mut self) {
|
||||||
|
self.public_ipv6_provider_task.shutdown().await;
|
||||||
self.peer_manager.clear_resources().await;
|
self.peer_manager.clear_resources().await;
|
||||||
#[cfg(feature = "tun")]
|
#[cfg(feature = "tun")]
|
||||||
let _ = self.nic_ctx.lock().await.take();
|
let _ = self.nic_ctx.lock().await.take();
|
||||||
@@ -1787,6 +1835,21 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn public_ipv6_provider_task_slot_does_not_restart_after_shutdown() {
|
||||||
|
let global_ctx = get_mock_global_ctx();
|
||||||
|
let slot = std::sync::Arc::new(super::PublicIpv6ProviderTaskSlot::new());
|
||||||
|
global_ctx.config.set_ipv6_public_addr_provider(true);
|
||||||
|
global_ctx
|
||||||
|
.config
|
||||||
|
.set_ipv6_public_addr_prefix(Some("2001:db8::/48".parse().unwrap()));
|
||||||
|
|
||||||
|
slot.shutdown().await;
|
||||||
|
super::ensure_public_ipv6_provider_reconcile_task(&global_ctx, &slot).await;
|
||||||
|
|
||||||
|
assert!(slot.task.lock().await.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn validate_public_ipv6_patch_allows_enabling_auto_with_manual_ipv6() {
|
async fn validate_public_ipv6_patch_allows_enabling_auto_with_manual_ipv6() {
|
||||||
let global_ctx = get_mock_global_ctx();
|
let global_ctx = get_mock_global_ctx();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1361,12 +1361,11 @@ impl NicCtx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.global_ctx
|
self.global_ctx
|
||||||
.issue_event(GlobalCtxEvent::TunDeviceReady(nic.ifname().to_string()));
|
.set_tun_device_ready(nic.ifname().to_string());
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.global_ctx
|
self.global_ctx.set_tun_device_error(err.to_string());
|
||||||
.issue_event(GlobalCtxEvent::TunDeviceError(err.to_string()));
|
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1405,12 +1404,11 @@ impl NicCtx {
|
|||||||
match nic.create_dev_for_mobile(tun_fd).await {
|
match nic.create_dev_for_mobile(tun_fd).await {
|
||||||
Ok(ret) => {
|
Ok(ret) => {
|
||||||
self.global_ctx
|
self.global_ctx
|
||||||
.issue_event(GlobalCtxEvent::TunDeviceReady(nic.ifname().to_string()));
|
.set_tun_device_ready(nic.ifname().to_string());
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.global_ctx
|
self.global_ctx.set_tun_device_error(err.to_string());
|
||||||
.issue_event(GlobalCtxEvent::TunDeviceError(err.to_string()));
|
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,12 @@ impl EasyTierLauncher {
|
|||||||
#[cfg(mobile)]
|
#[cfg(mobile)]
|
||||||
Self::run_routine_for_mobile(&instance, &data, &mut tasks).await;
|
Self::run_routine_for_mobile(&instance, &data, &mut tasks).await;
|
||||||
|
|
||||||
instance.run().await?;
|
if let Err(err) = instance.run().await {
|
||||||
|
tasks.abort_all();
|
||||||
|
drop(tasks);
|
||||||
|
instance.clear_resources().await;
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ffi-dataplane")]
|
#[cfg(feature = "ffi-dataplane")]
|
||||||
data.data_plane
|
data.data_plane
|
||||||
|
|||||||
@@ -477,6 +477,12 @@ struct PublicIpv6Lab {
|
|||||||
extra_bridges: [&'static str; 2],
|
extra_bridges: [&'static str; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum PublicIpv6LabTopology {
|
||||||
|
DelegatedPrefix,
|
||||||
|
OnLinkPrefix,
|
||||||
|
}
|
||||||
|
|
||||||
impl PublicIpv6Lab {
|
impl PublicIpv6Lab {
|
||||||
const PROVIDER_NS: &'static str = "net_a";
|
const PROVIDER_NS: &'static str = "net_a";
|
||||||
const CLIENT_NS: &'static str = "net_b";
|
const CLIENT_NS: &'static str = "net_b";
|
||||||
@@ -490,11 +496,13 @@ impl PublicIpv6Lab {
|
|||||||
const PROVIDER_DEFAULT_FROM: &'static str = "2001:db8:100::/64";
|
const PROVIDER_DEFAULT_FROM: &'static str = "2001:db8:100::/64";
|
||||||
const PROVIDER_WAN_ADDR: &'static str = "2001:db8:ffff:1::2/64";
|
const PROVIDER_WAN_ADDR: &'static str = "2001:db8:ffff:1::2/64";
|
||||||
const UPSTREAM_WAN_ADDR: &'static str = "2001:db8:ffff:1::1/64";
|
const UPSTREAM_WAN_ADDR: &'static str = "2001:db8:ffff:1::1/64";
|
||||||
|
const ON_LINK_PROVIDER_WAN_ADDR: &'static str = "2001:db8:100::2/64";
|
||||||
|
const ON_LINK_UPSTREAM_WAN_ADDR: &'static str = "2001:db8:100::1/64";
|
||||||
const UPSTREAM_SERVER_ADDR: &'static str = "2001:db8:ffff:2::1/64";
|
const UPSTREAM_SERVER_ADDR: &'static str = "2001:db8:ffff:2::1/64";
|
||||||
const SERVER_ADDR: &'static str = "2001:db8:ffff:2::100/64";
|
const SERVER_ADDR: &'static str = "2001:db8:ffff:2::100/64";
|
||||||
const SERVER_IP: &'static str = "2001:db8:ffff:2::100";
|
const SERVER_IP: &'static str = "2001:db8:ffff:2::100";
|
||||||
|
|
||||||
fn setup() -> Self {
|
fn setup_with_topology(topology: PublicIpv6LabTopology) -> Self {
|
||||||
prepare_linux_namespaces();
|
prepare_linux_namespaces();
|
||||||
|
|
||||||
del_netns(Self::UPSTREAM_NS);
|
del_netns(Self::UPSTREAM_NS);
|
||||||
@@ -544,13 +552,23 @@ impl PublicIpv6Lab {
|
|||||||
Self::SERVER_BRIDGE,
|
Self::SERVER_BRIDGE,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let (provider_wan_addr, upstream_wan_addr) = match topology {
|
||||||
|
PublicIpv6LabTopology::DelegatedPrefix => {
|
||||||
|
(Self::PROVIDER_WAN_ADDR, Self::UPSTREAM_WAN_ADDR)
|
||||||
|
}
|
||||||
|
PublicIpv6LabTopology::OnLinkPrefix => (
|
||||||
|
Self::ON_LINK_PROVIDER_WAN_ADDR,
|
||||||
|
Self::ON_LINK_UPSTREAM_WAN_ADDR,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
run_ip_in_ns(
|
run_ip_in_ns(
|
||||||
Self::PROVIDER_NS,
|
Self::PROVIDER_NS,
|
||||||
&["addr", "add", Self::PROVIDER_WAN_ADDR, "dev", "pubwan0"],
|
&["addr", "add", provider_wan_addr, "dev", "pubwan0"],
|
||||||
);
|
);
|
||||||
run_ip_in_ns(
|
run_ip_in_ns(
|
||||||
Self::UPSTREAM_NS,
|
Self::UPSTREAM_NS,
|
||||||
&["addr", "add", Self::UPSTREAM_WAN_ADDR, "dev", "upwan0"],
|
&["addr", "add", upstream_wan_addr, "dev", "upwan0"],
|
||||||
);
|
);
|
||||||
run_ip_in_ns(
|
run_ip_in_ns(
|
||||||
Self::UPSTREAM_NS,
|
Self::UPSTREAM_NS,
|
||||||
@@ -561,6 +579,8 @@ impl PublicIpv6Lab {
|
|||||||
&["addr", "add", Self::SERVER_ADDR, "dev", "srv0"],
|
&["addr", "add", Self::SERVER_ADDR, "dev", "srv0"],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
match topology {
|
||||||
|
PublicIpv6LabTopology::DelegatedPrefix => {
|
||||||
run_ip_in_ns(
|
run_ip_in_ns(
|
||||||
Self::PROVIDER_NS,
|
Self::PROVIDER_NS,
|
||||||
&["link", "add", "pubprefix0", "type", "dummy"],
|
&["link", "add", "pubprefix0", "type", "dummy"],
|
||||||
@@ -592,6 +612,23 @@ impl PublicIpv6Lab {
|
|||||||
"pubwan0",
|
"pubwan0",
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
PublicIpv6LabTopology::OnLinkPrefix => {
|
||||||
|
run_ip_in_ns(
|
||||||
|
Self::PROVIDER_NS,
|
||||||
|
&[
|
||||||
|
"-6",
|
||||||
|
"route",
|
||||||
|
"add",
|
||||||
|
"default",
|
||||||
|
"via",
|
||||||
|
"2001:db8:100::1",
|
||||||
|
"dev",
|
||||||
|
"pubwan0",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
run_ip_in_ns(
|
run_ip_in_ns(
|
||||||
Self::SERVER_NS,
|
Self::SERVER_NS,
|
||||||
@@ -606,6 +643,7 @@ impl PublicIpv6Lab {
|
|||||||
"srv0",
|
"srv0",
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
if matches!(topology, PublicIpv6LabTopology::DelegatedPrefix) {
|
||||||
run_ip_in_ns(
|
run_ip_in_ns(
|
||||||
Self::UPSTREAM_NS,
|
Self::UPSTREAM_NS,
|
||||||
&[
|
&[
|
||||||
@@ -619,6 +657,7 @@ impl PublicIpv6Lab {
|
|||||||
"upwan0",
|
"upwan0",
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
run_sysctl_in_ns(Self::PROVIDER_NS, "net.ipv6.conf.all.forwarding=1");
|
run_sysctl_in_ns(Self::PROVIDER_NS, "net.ipv6.conf.all.forwarding=1");
|
||||||
run_sysctl_in_ns(Self::UPSTREAM_NS, "net.ipv6.conf.all.forwarding=1");
|
run_sysctl_in_ns(Self::UPSTREAM_NS, "net.ipv6.conf.all.forwarding=1");
|
||||||
@@ -672,7 +711,15 @@ fn get_public_ipv6_config(
|
|||||||
async fn init_public_ipv6_two_node(
|
async fn init_public_ipv6_two_node(
|
||||||
client_inst_id: uuid::Uuid,
|
client_inst_id: uuid::Uuid,
|
||||||
) -> (PublicIpv6Lab, Instance, Instance) {
|
) -> (PublicIpv6Lab, Instance, Instance) {
|
||||||
let lab = PublicIpv6Lab::setup();
|
init_public_ipv6_two_node_with_topology(client_inst_id, PublicIpv6LabTopology::DelegatedPrefix)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init_public_ipv6_two_node_with_topology(
|
||||||
|
client_inst_id: uuid::Uuid,
|
||||||
|
topology: PublicIpv6LabTopology,
|
||||||
|
) -> (PublicIpv6Lab, Instance, Instance) {
|
||||||
|
let lab = PublicIpv6Lab::setup_with_topology(topology);
|
||||||
|
|
||||||
let provider_cfg = get_public_ipv6_config(
|
let provider_cfg = get_public_ipv6_config(
|
||||||
"provider_public_ipv6",
|
"provider_public_ipv6",
|
||||||
@@ -756,6 +803,13 @@ fn addr_exists_in_ns(ns: &str, dev: &str, needle: &str) -> bool {
|
|||||||
run_ip_in_ns_output(ns, &["-6", "addr", "show", "dev", dev]).contains(needle)
|
run_ip_in_ns_output(ns, &["-6", "addr", "show", "dev", dev]).contains(needle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ndp_proxy_exists_in_ns(ns: &str, dev: &str, addr: std::net::Ipv6Addr) -> bool {
|
||||||
|
let addr = addr.to_string();
|
||||||
|
run_ip_in_ns_output(ns, &["-6", "neigh", "show", "proxy", "dev", dev])
|
||||||
|
.lines()
|
||||||
|
.any(|line| line.split_whitespace().next() == Some(addr.as_str()))
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial_test::serial]
|
#[serial_test::serial]
|
||||||
pub async fn public_ipv6_auto_addr_end_to_end() {
|
pub async fn public_ipv6_auto_addr_end_to_end() {
|
||||||
@@ -878,6 +932,67 @@ pub async fn public_ipv6_auto_addr_end_to_end() {
|
|||||||
drop_insts(vec![provider, client]).await;
|
drop_insts(vec![provider, client]).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial_test::serial]
|
||||||
|
pub async fn public_ipv6_auto_addr_on_link_ndp_proxy_end_to_end() {
|
||||||
|
let client_id = uuid::Uuid::parse_str("44444444-4444-4444-4444-444444444444").unwrap();
|
||||||
|
let (_lab, provider, client) =
|
||||||
|
init_public_ipv6_two_node_with_topology(client_id, PublicIpv6LabTopology::OnLinkPrefix)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| async {
|
||||||
|
provider
|
||||||
|
.get_global_ctx()
|
||||||
|
.get_advertised_ipv6_public_addr_prefix()
|
||||||
|
== Some(PublicIpv6Lab::PROVIDER_PREFIX.parse().unwrap())
|
||||||
|
},
|
||||||
|
Duration::from_secs(10),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let leased = wait_for_public_ipv6_addr(&client).await;
|
||||||
|
wait_for_public_ipv6_route(&provider, leased).await;
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| async {
|
||||||
|
addr_exists_in_ns(
|
||||||
|
PublicIpv6Lab::CLIENT_NS,
|
||||||
|
PublicIpv6Lab::CLIENT_TUN,
|
||||||
|
&leased.to_string(),
|
||||||
|
) && route_exists_in_ns(
|
||||||
|
PublicIpv6Lab::PROVIDER_NS,
|
||||||
|
&format!("{} dev {}", leased.address(), PublicIpv6Lab::PROVIDER_TUN),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Duration::from_secs(10),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| async {
|
||||||
|
ndp_proxy_exists_in_ns(PublicIpv6Lab::PROVIDER_NS, "pubwan0", leased.address())
|
||||||
|
},
|
||||||
|
Duration::from_secs(20),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| async {
|
||||||
|
ping6_test(
|
||||||
|
PublicIpv6Lab::SERVER_NS,
|
||||||
|
leased.address().to_string().as_str(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
},
|
||||||
|
Duration::from_secs(20),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
drop_insts(vec![provider, client]).await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial_test::serial]
|
#[serial_test::serial]
|
||||||
pub async fn public_ipv6_auto_addr_reconnect_reuses_same_address() {
|
pub async fn public_ipv6_auto_addr_reconnect_reuses_same_address() {
|
||||||
|
|||||||
Reference in New Issue
Block a user