fix ipv6 packet routing and avoid route looping

properly handle ipv6 link local address and exit node.
This commit is contained in:
sijie.sun
2025-08-03 16:54:03 +08:00
committed by Sijie.Sun
parent 84bfac144c
commit d0a6c93c2c
6 changed files with 49 additions and 27 deletions

6
Cargo.lock generated
View File

@@ -1979,7 +1979,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "easytier"
version = "2.4.0"
version = "2.4.1"
dependencies = [
"aes-gcm",
"anyhow",
@@ -2112,7 +2112,7 @@ dependencies = [
[[package]]
name = "easytier-gui"
version = "2.4.0"
version = "2.4.1"
dependencies = [
"anyhow",
"chrono",
@@ -2162,7 +2162,7 @@ dependencies = [
[[package]]
name = "easytier-web"
version = "2.4.0"
version = "2.4.1"
dependencies = [
"anyhow",
"async-trait",

View File

@@ -1,5 +1,5 @@
use std::{
net::{Ipv4Addr, SocketAddr},
net::{IpAddr, SocketAddr},
path::PathBuf,
sync::{Arc, Mutex},
u64,
@@ -107,8 +107,8 @@ pub trait ConfigLoader: Send + Sync {
fn get_flags(&self) -> Flags;
fn set_flags(&self, flags: Flags);
fn get_exit_nodes(&self) -> Vec<Ipv4Addr>;
fn set_exit_nodes(&self, nodes: Vec<Ipv4Addr>);
fn get_exit_nodes(&self) -> Vec<IpAddr>;
fn set_exit_nodes(&self, nodes: Vec<IpAddr>);
fn get_routes(&self) -> Option<Vec<cidr::Ipv4Cidr>>;
fn set_routes(&self, routes: Option<Vec<cidr::Ipv4Cidr>>);
@@ -283,7 +283,7 @@ struct Config {
network_identity: Option<NetworkIdentity>,
listeners: Option<Vec<url::Url>>,
mapped_listeners: Option<Vec<url::Url>>,
exit_nodes: Option<Vec<Ipv4Addr>>,
exit_nodes: Option<Vec<IpAddr>>,
peer: Option<Vec<PeerConfig>>,
proxy_network: Option<Vec<ProxyNetworkConfig>>,
@@ -624,7 +624,7 @@ impl ConfigLoader for TomlConfigLoader {
self.config.lock().unwrap().flags_struct = Some(flags);
}
fn get_exit_nodes(&self) -> Vec<Ipv4Addr> {
fn get_exit_nodes(&self) -> Vec<IpAddr> {
self.config
.lock()
.unwrap()
@@ -633,7 +633,7 @@ impl ConfigLoader for TomlConfigLoader {
.unwrap_or_default()
}
fn set_exit_nodes(&self, nodes: Vec<Ipv4Addr>) {
fn set_exit_nodes(&self, nodes: Vec<IpAddr>) {
self.config.lock().unwrap().exit_nodes = Some(nodes);
}

View File

@@ -4,7 +4,7 @@
extern crate rust_i18n;
use std::{
net::{Ipv4Addr, SocketAddr},
net::{IpAddr, SocketAddr},
path::PathBuf,
process::ExitCode,
sync::Arc,
@@ -333,7 +333,7 @@ struct NetworkOptions {
help = t!("core_clap.exit_nodes").to_string(),
num_args = 0..
)]
exit_nodes: Vec<Ipv4Addr>,
exit_nodes: Vec<IpAddr>,
#[arg(
long,

View File

@@ -711,12 +711,20 @@ impl NicCtx {
tracing::info!("[USER_PACKET] not ipv6 packet: {:?}", ipv6);
return;
}
let src_ipv6 = ipv6.get_source();
let dst_ipv6 = ipv6.get_destination();
tracing::trace!(
?ret,
"[USER_PACKET] recv new packet from tun device and forward to peers."
);
if src_ipv6.is_unicast_link_local()
&& Some(src_ipv6) != mgr.get_global_ctx().get_ipv6().map(|x| x.address())
{
// do not route link local packet to other nodes unless the address is assigned by user
return;
}
// TODO: use zero-copy
let send_ret = mgr.send_msg_by_ip(ret, IpAddr::V6(dst_ipv6)).await;
if send_ret.is_err() {

View File

@@ -627,7 +627,7 @@ impl NetworkConfig {
}
if self.exit_nodes.len() > 0 {
let mut exit_nodes = Vec::<std::net::Ipv4Addr>::with_capacity(self.exit_nodes.len());
let mut exit_nodes = Vec::<std::net::IpAddr>::with_capacity(self.exit_nodes.len());
for node in self.exit_nodes.iter() {
exit_nodes.push(
node.parse()
@@ -891,7 +891,7 @@ impl NetworkConfig {
mod tests {
use crate::common::config::ConfigLoader;
use rand::Rng;
use std::net::Ipv4Addr;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
fn gen_default_config() -> crate::common::config::TomlConfigLoader {
let config = crate::common::config::TomlConfigLoader::default();
@@ -1073,7 +1073,19 @@ mod tests {
rng.gen_range(0..255),
rng.gen_range(1..254),
);
nodes.push(ip);
nodes.push(IpAddr::V4(ip));
// gen ipv6
let ip = Ipv6Addr::new(
rng.gen_range(0..65535),
rng.gen_range(0..65535),
rng.gen_range(0..65535),
rng.gen_range(0..65535),
rng.gen_range(0..65535),
rng.gen_range(0..65535),
rng.gen_range(0..65535),
rng.gen_range(0..65535),
);
nodes.push(IpAddr::V6(ip));
}
config.set_exit_nodes(nodes);
}

View File

@@ -142,7 +142,7 @@ pub struct PeerManager {
encryptor: Arc<Box<dyn Encryptor>>,
data_compress_algo: CompressorAlgo,
exit_nodes: Vec<Ipv4Addr>,
exit_nodes: Vec<IpAddr>,
reserved_my_peer_id_map: DashMap<String, PeerId>,
@@ -948,6 +948,9 @@ impl PeerManager {
dst_peers.push(peer_id);
} else {
for exit_node in &self.exit_nodes {
let IpAddr::V4(exit_node) = exit_node else {
continue;
};
if let Some(peer_id) = self.peers.get_peer_id_by_ipv4(exit_node).await {
dst_peers.push(peer_id);
is_exit_node = true;
@@ -985,18 +988,17 @@ impl PeerManager {
);
} else if let Some(peer_id) = self.peers.get_peer_id_by_ipv6(&ipv6_addr).await {
dst_peers.push(peer_id);
} else {
// For IPv6, we'll need to implement exit node support later
// For now, just try to find any available peer for routing
if dst_peers.is_empty() {
dst_peers.extend(
self.peers
.list_routes()
.await
.iter()
.map(|x| x.key().clone()),
);
is_exit_node = true;
} else if !ipv6_addr.is_unicast_link_local() {
// NOTE: never route link local address to exit node.
for exit_node in &self.exit_nodes {
let IpAddr::V6(exit_node) = exit_node else {
continue;
};
if let Some(peer_id) = self.peers.get_peer_id_by_ipv6(exit_node).await {
dst_peers.push(peer_id);
is_exit_node = true;
break;
}
}
}