Add support for IPv6 within VPN (#1061)

* add flake.nix with nix based dev shell
* add support for IPv6
* update thunk

---------

Co-authored-by: sijie.sun <sijie.sun@smartx.com>
This commit is contained in:
DavHau
2025-07-04 22:43:30 +07:00
committed by GitHub
parent 01e491ec07
commit d0cfc49806
32 changed files with 893 additions and 70 deletions
+1
View File
@@ -0,0 +1 @@
use flake
+2
View File
@@ -38,3 +38,5 @@ node_modules
easytier-gui/src-tauri/*.dll easytier-gui/src-tauri/*.dll
/easytier-contrib/easytier-ohrs/dist/ /easytier-contrib/easytier-ohrs/dist/
.direnv
Generated
+2 -2
View File
@@ -8606,8 +8606,8 @@ dependencies = [
[[package]] [[package]]
name = "thunk-rs" name = "thunk-rs"
version = "0.3.3" version = "0.3.4"
source = "git+https://github.com/easytier/thunk.git#5e8371a3100dbc18dda952a2036c6bd6fb0504db" source = "git+https://github.com/easytier/thunk.git#403f0d26d3d5bcfdfd76c23e36e517f19fe891e0"
[[package]] [[package]]
name = "tiff" name = "tiff"
+3
View File
@@ -27,6 +27,9 @@ core_clap:
ipv4: ipv4:
en: "ipv4 address of this vpn node, if empty, this node will only forward packets and no TUN device will be created" en: "ipv4 address of this vpn node, if empty, this node will only forward packets and no TUN device will be created"
zh-CN: "此VPN节点的IPv4地址,如果为空,则此节点将仅转发数据包,不会创建TUN设备" zh-CN: "此VPN节点的IPv4地址,如果为空,则此节点将仅转发数据包,不会创建TUN设备"
ipv6:
en: "ipv6 address of this vpn node, can be used together with ipv4 for dual-stack operation"
zh-CN: "此VPN节点的IPv6地址,可与IPv4一起使用以进行双栈操作"
dhcp: dhcp:
en: "automatically determine and set IP address by Easytier, and the IP address starts from 10.0.0.1 by default. Warning, if there is an IP conflict in the network when using DHCP, the IP will be automatically changed." en: "automatically determine and set IP address by Easytier, and the IP address starts from 10.0.0.1 by default. Warning, if there is an IP conflict in the network when using DHCP, the IP will be automatically changed."
zh-CN: "由Easytier自动确定并设置IP地址,默认从10.0.0.1开始。警告:在使用DHCP时,如果网络中出现IP冲突,IP将自动更改。" zh-CN: "由Easytier自动确定并设置IP地址,默认从10.0.0.1开始。警告:在使用DHCP时,如果网络中出现IP冲突,IP将自动更改。"
+21
View File
@@ -64,6 +64,9 @@ pub trait ConfigLoader: Send + Sync {
fn get_ipv4(&self) -> Option<cidr::Ipv4Inet>; fn get_ipv4(&self) -> Option<cidr::Ipv4Inet>;
fn set_ipv4(&self, addr: Option<cidr::Ipv4Inet>); fn set_ipv4(&self, addr: Option<cidr::Ipv4Inet>);
fn get_ipv6(&self) -> Option<cidr::Ipv6Inet>;
fn set_ipv6(&self, addr: Option<cidr::Ipv6Inet>);
fn get_dhcp(&self) -> bool; fn get_dhcp(&self) -> bool;
fn set_dhcp(&self, dhcp: bool); fn set_dhcp(&self, dhcp: bool);
@@ -259,6 +262,7 @@ struct Config {
instance_name: Option<String>, instance_name: Option<String>,
instance_id: Option<uuid::Uuid>, instance_id: Option<uuid::Uuid>,
ipv4: Option<String>, ipv4: Option<String>,
ipv6: Option<String>,
dhcp: Option<bool>, dhcp: Option<bool>,
network_identity: Option<NetworkIdentity>, network_identity: Option<NetworkIdentity>,
listeners: Option<Vec<url::Url>>, listeners: Option<Vec<url::Url>>,
@@ -416,6 +420,23 @@ impl ConfigLoader for TomlConfigLoader {
}; };
} }
fn get_ipv6(&self) -> Option<cidr::Ipv6Inet> {
let locked_config = self.config.lock().unwrap();
locked_config
.ipv6
.as_ref()
.map(|s| s.parse().ok())
.flatten()
}
fn set_ipv6(&self, addr: Option<cidr::Ipv6Inet>) {
self.config.lock().unwrap().ipv6 = if let Some(addr) = addr {
Some(addr.to_string())
} else {
None
};
}
fn get_dhcp(&self) -> bool { fn get_dhcp(&self) -> bool {
self.config.lock().unwrap().dhcp.unwrap_or_default() self.config.lock().unwrap().dhcp.unwrap_or_default()
} }
+16
View File
@@ -61,6 +61,7 @@ pub struct GlobalCtx {
event_bus: EventBus, event_bus: EventBus,
cached_ipv4: AtomicCell<Option<cidr::Ipv4Inet>>, cached_ipv4: AtomicCell<Option<cidr::Ipv4Inet>>,
cached_ipv6: AtomicCell<Option<cidr::Ipv6Inet>>,
cached_proxy_cidrs: AtomicCell<Option<Vec<ProxyNetworkConfig>>>, cached_proxy_cidrs: AtomicCell<Option<Vec<ProxyNetworkConfig>>>,
ip_collector: Mutex<Option<Arc<IPCollector>>>, ip_collector: Mutex<Option<Arc<IPCollector>>>,
@@ -124,6 +125,7 @@ impl GlobalCtx {
event_bus, event_bus,
cached_ipv4: AtomicCell::new(None), cached_ipv4: AtomicCell::new(None),
cached_ipv6: AtomicCell::new(None),
cached_proxy_cidrs: AtomicCell::new(None), cached_proxy_cidrs: AtomicCell::new(None),
ip_collector: Mutex::new(Some(Arc::new(IPCollector::new( ip_collector: Mutex::new(Some(Arc::new(IPCollector::new(
@@ -191,6 +193,20 @@ impl GlobalCtx {
self.cached_ipv4.store(None); self.cached_ipv4.store(None);
} }
pub fn get_ipv6(&self) -> Option<cidr::Ipv6Inet> {
if let Some(ret) = self.cached_ipv6.load() {
return Some(ret);
}
let addr = self.config.get_ipv6();
self.cached_ipv6.store(addr.clone());
return addr;
}
pub fn set_ipv6(&self, addr: Option<cidr::Ipv6Inet>) {
self.config.set_ipv6(addr);
self.cached_ipv6.store(None);
}
pub fn get_id(&self) -> uuid::Uuid { pub fn get_id(&self) -> uuid::Uuid {
self.config.get_id() self.config.get_id()
} }
+58
View File
@@ -80,4 +80,62 @@ impl IfConfiguerTrait for MacIfConfiger {
async fn set_mtu(&self, name: &str, mtu: u32) -> Result<(), Error> { async fn set_mtu(&self, name: &str, mtu: u32) -> Result<(), Error> {
run_shell_cmd(format!("ifconfig {} mtu {}", name, mtu).as_str()).await run_shell_cmd(format!("ifconfig {} mtu {}", name, mtu).as_str()).await
} }
async fn add_ipv6_ip(
&self,
name: &str,
address: std::net::Ipv6Addr,
cidr_prefix: u8,
) -> Result<(), Error> {
run_shell_cmd(
format!("ifconfig {} inet6 {}/{} add", name, address, cidr_prefix).as_str(),
)
.await
}
async fn remove_ipv6(&self, name: &str, ip: Option<std::net::Ipv6Addr>) -> Result<(), Error> {
if let Some(ip) = ip {
run_shell_cmd(format!("ifconfig {} inet6 {} delete", name, ip).as_str()).await
} else {
// Remove all IPv6 addresses is more complex on macOS, just succeed
Ok(())
}
}
async fn add_ipv6_route(
&self,
name: &str,
address: std::net::Ipv6Addr,
cidr_prefix: u8,
cost: Option<i32>,
) -> Result<(), Error> {
let cmd = if let Some(cost) = cost {
format!(
"route -n add -inet6 {}/{} -interface {} -hopcount {}",
address, cidr_prefix, name, cost
)
} else {
format!(
"route -n add -inet6 {}/{} -interface {}",
address, cidr_prefix, name
)
};
run_shell_cmd(cmd.as_str()).await
}
async fn remove_ipv6_route(
&self,
name: &str,
address: std::net::Ipv6Addr,
cidr_prefix: u8,
) -> Result<(), Error> {
run_shell_cmd(
format!(
"route -n delete -inet6 {}/{} -interface {}",
address, cidr_prefix, name
)
.as_str(),
)
.await
}
} }
+29 -1
View File
@@ -7,7 +7,7 @@ mod windows;
mod route; mod route;
use std::net::Ipv4Addr; use std::net::{Ipv4Addr, Ipv6Addr};
use async_trait::async_trait; use async_trait::async_trait;
use tokio::process::Command; use tokio::process::Command;
@@ -41,12 +41,40 @@ pub trait IfConfiguerTrait: Send + Sync {
) -> Result<(), Error> { ) -> Result<(), Error> {
Ok(()) Ok(())
} }
async fn add_ipv6_route(
&self,
_name: &str,
_address: Ipv6Addr,
_cidr_prefix: u8,
_cost: Option<i32>,
) -> Result<(), Error> {
Ok(())
}
async fn remove_ipv6_route(
&self,
_name: &str,
_address: Ipv6Addr,
_cidr_prefix: u8,
) -> Result<(), Error> {
Ok(())
}
async fn add_ipv6_ip(
&self,
_name: &str,
_address: Ipv6Addr,
_cidr_prefix: u8,
) -> Result<(), Error> {
Ok(())
}
async fn set_link_status(&self, _name: &str, _up: bool) -> Result<(), Error> { async fn set_link_status(&self, _name: &str, _up: bool) -> Result<(), Error> {
Ok(()) Ok(())
} }
async fn remove_ip(&self, _name: &str, _ip: Option<Ipv4Addr>) -> Result<(), Error> { async fn remove_ip(&self, _name: &str, _ip: Option<Ipv4Addr>) -> Result<(), Error> {
Ok(()) Ok(())
} }
async fn remove_ipv6(&self, _name: &str, _ip: Option<Ipv6Addr>) -> Result<(), Error> {
Ok(())
}
async fn wait_interface_show(&self, _name: &str) -> Result<(), Error> { async fn wait_interface_show(&self, _name: &str) -> Result<(), Error> {
return Ok(()); return Ok(());
} }
+126
View File
@@ -194,6 +194,32 @@ impl NetlinkIfConfiger {
) )
} }
fn get_prefix_len_ipv6(name: &str, ip: Ipv6Addr) -> Result<u8, Error> {
let addrs = Self::list_addresses(name)?;
for addr in addrs {
if addr.address() == IpAddr::V6(ip) {
return Ok(addr.network_length());
}
}
Err(Error::NotFound)
}
fn remove_one_ipv6(name: &str, ip: Ipv6Addr, prefix_len: u8) -> Result<(), Error> {
let mut message = AddressMessage::default();
message.header.prefix_len = prefix_len;
message.header.index = NetlinkIfConfiger::get_interface_index(name)?;
message.header.family = AddressFamily::Inet6;
message
.attributes
.push(AddressAttribute::Address(std::net::IpAddr::V6(ip)));
send_netlink_req_and_wait_one_resp::<RouteNetlinkMessage>(
RouteNetlinkMessage::DelAddress(message),
true,
)
}
pub(crate) fn mtu_op<T: TryInto<Ioctl>>( pub(crate) fn mtu_op<T: TryInto<Ioctl>>(
name: &str, name: &str,
op: T, op: T,
@@ -469,6 +495,106 @@ impl IfConfiguerTrait for NetlinkIfConfiger {
Ok(()) Ok(())
} }
async fn add_ipv6_ip(
&self,
name: &str,
address: std::net::Ipv6Addr,
cidr_prefix: u8,
) -> Result<(), Error> {
let mut message = AddressMessage::default();
message.header.prefix_len = cidr_prefix;
message.header.index = NetlinkIfConfiger::get_interface_index(name)?;
message.header.family = AddressFamily::Inet6;
message
.attributes
.push(AddressAttribute::Address(std::net::IpAddr::V6(address)));
// For IPv6, we don't need IFA_LOCAL or IFA_BROADCAST
send_netlink_req_and_wait_one_resp::<RouteNetlinkMessage>(
RouteNetlinkMessage::NewAddress(message),
false,
)
}
async fn remove_ipv6(&self, name: &str, ip: Option<std::net::Ipv6Addr>) -> Result<(), Error> {
if ip.is_none() {
let addrs = Self::list_addresses(name)?;
for addr in addrs {
if let IpAddr::V6(ipv6) = addr.address() {
let prefix_len = addr.network_length();
Self::remove_one_ipv6(name, ipv6, prefix_len)?;
}
}
} else {
let ipv6 = ip.unwrap();
let prefix_len = Self::get_prefix_len_ipv6(name, ipv6)?;
Self::remove_one_ipv6(name, ipv6, prefix_len)?;
}
Ok(())
}
async fn add_ipv6_route(
&self,
name: &str,
address: std::net::Ipv6Addr,
cidr_prefix: u8,
cost: Option<i32>,
) -> Result<(), Error> {
let mut message = RouteMessage::default();
message.header.address_family = AddressFamily::Inet6;
message.header.destination_prefix_length = cidr_prefix;
message.header.table = RouteHeader::RT_TABLE_MAIN;
message.header.protocol = RouteProtocol::Static;
message.header.scope = RouteScope::Universe;
message.header.kind = RouteType::Unicast;
// Add metric (cost) if specified
if let Some(cost) = cost {
message
.attributes
.push(RouteAttribute::Priority(cost as u32));
}
message
.attributes
.push(RouteAttribute::Oif(NetlinkIfConfiger::get_interface_index(
name,
)?));
message
.attributes
.push(RouteAttribute::Destination(RouteAddress::Inet6(address)));
send_netlink_req_and_wait_one_resp(RouteNetlinkMessage::NewRoute(message), false)
}
async fn remove_ipv6_route(
&self,
name: &str,
address: std::net::Ipv6Addr,
cidr_prefix: u8,
) -> Result<(), Error> {
let routes = Self::list_routes()?;
let ifidx = NetlinkIfConfiger::get_interface_index(name)?;
for msg in routes {
let other_route: Route = msg.clone().into();
if other_route.destination == std::net::IpAddr::V6(address)
&& other_route.prefix == cidr_prefix
&& other_route.ifindex == Some(ifidx)
{
send_netlink_req_and_wait_one_resp(RouteNetlinkMessage::DelRoute(msg), true)?;
return Ok(());
}
}
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]
+68
View File
@@ -169,6 +169,74 @@ impl IfConfiguerTrait for WindowsIfConfiger {
) )
.await .await
} }
async fn add_ipv6_ip(
&self,
name: &str,
address: std::net::Ipv6Addr,
cidr_prefix: u8,
) -> Result<(), Error> {
run_shell_cmd(
format!(
"netsh interface ipv6 add address {} {}/{}",
name, address, cidr_prefix
)
.as_str(),
)
.await
}
async fn remove_ipv6(&self, name: &str, ip: Option<std::net::Ipv6Addr>) -> Result<(), Error> {
if let Some(ip) = ip {
run_shell_cmd(
format!("netsh interface ipv6 delete address {} {}", name, ip).as_str(),
)
.await
} else {
// Remove all IPv6 addresses
run_shell_cmd(
format!("netsh interface ipv6 delete address {} all", name).as_str(),
)
.await
}
}
async fn add_ipv6_route(
&self,
name: &str,
address: std::net::Ipv6Addr,
cidr_prefix: u8,
cost: Option<i32>,
) -> Result<(), Error> {
let cmd = if let Some(cost) = cost {
format!(
"netsh interface ipv6 add route {}/{} {} metric={}",
address, cidr_prefix, name, cost
)
} else {
format!(
"netsh interface ipv6 add route {}/{} {}",
address, cidr_prefix, name
)
};
run_shell_cmd(cmd.as_str()).await
}
async fn remove_ipv6_route(
&self,
name: &str,
address: std::net::Ipv6Addr,
cidr_prefix: u8,
) -> Result<(), Error> {
run_shell_cmd(
format!(
"netsh interface ipv6 delete route {}/{} {}",
address, cidr_prefix, name
)
.as_str(),
)
.await
}
} }
pub struct RegistryManager; pub struct RegistryManager;
+13
View File
@@ -148,6 +148,13 @@ struct NetworkOptions {
)] )]
ipv4: Option<String>, ipv4: Option<String>,
#[arg(
long,
env = "ET_IPV6",
help = t!("core_clap.ipv6").to_string()
)]
ipv6: Option<String>,
#[arg( #[arg(
short, short,
long, long,
@@ -615,6 +622,12 @@ impl NetworkOptions {
})?)) })?))
} }
if let Some(ipv6) = &self.ipv6 {
cfg.set_ipv6(Some(ipv6.parse().with_context(|| {
format!("failed to parse ipv6 address: {}", ipv6)
})?))
}
if !self.peers.is_empty() { if !self.peers.is_empty() {
let mut peers = cfg.get_peers(); let mut peers = cfg.get_peers();
peers.reserve(peers.len() + self.peers.len()); peers.reserve(peers.len() + self.peers.len());
+1 -1
View File
@@ -301,7 +301,7 @@ impl Socks5ServerNet {
let dst = ipv4.get_destination(); let dst = ipv4.get_destination();
let packet = ZCPacket::new_with_payload(&data); let packet = ZCPacket::new_with_payload(&data);
if let Err(e) = peer_manager.send_msg_ipv4(packet, dst).await { if let Err(e) = peer_manager.send_msg_by_ip(packet, IpAddr::V4(dst)).await {
tracing::error!("send to peer failed in smoltcp sender: {:?}", e); tracing::error!("send to peer failed in smoltcp sender: {:?}", e);
} }
} }
+1 -1
View File
@@ -557,7 +557,7 @@ impl<C: NatDstConnector> TcpProxy<C> {
let dst = ipv4.get_destination(); let dst = ipv4.get_destination();
let packet = ZCPacket::new_with_payload(&data); let packet = ZCPacket::new_with_payload(&data);
if let Err(e) = peer_mgr.send_msg_ipv4(packet, dst).await { if let Err(e) = peer_mgr.send_msg_by_ip(packet, IpAddr::V4(dst)).await {
tracing::error!("send to peer failed in smoltcp sender: {:?}", e); tracing::error!("send to peer failed in smoltcp sender: {:?}", e);
} }
} }
+1 -1
View File
@@ -34,7 +34,7 @@ pub async fn prepare_env(dns_name: &str, tun_ip: Ipv4Inet) -> (Arc<PeerManager>,
let r = Arc::new(tokio::sync::Mutex::new(r)); let r = Arc::new(tokio::sync::Mutex::new(r));
let mut virtual_nic = NicCtx::new(peer_mgr.get_global_ctx(), &peer_mgr, r); let mut virtual_nic = NicCtx::new(peer_mgr.get_global_ctx(), &peer_mgr, r);
virtual_nic.run(tun_ip).await.unwrap(); virtual_nic.run(Some(tun_ip), None).await.unwrap();
(peer_mgr, virtual_nic) (peer_mgr, virtual_nic)
} }
+25 -20
View File
@@ -484,7 +484,7 @@ impl Instance {
&peer_manager_c, &peer_manager_c,
_peer_packet_receiver.clone(), _peer_packet_receiver.clone(),
); );
if let Err(e) = new_nic_ctx.run(ip).await { if let Err(e) = new_nic_ctx.run(Some(ip), global_ctx_c.get_ipv6()).await {
tracing::error!( tracing::error!(
?current_dhcp_ip, ?current_dhcp_ip,
?candidate_ipv4_addr, ?candidate_ipv4_addr,
@@ -532,24 +532,29 @@ impl Instance {
if !self.global_ctx.config.get_flags().no_tun { if !self.global_ctx.config.get_flags().no_tun {
#[cfg(not(any(target_os = "android", target_env = "ohos")))] #[cfg(not(any(target_os = "android", target_env = "ohos")))]
if let Some(ipv4_addr) = self.global_ctx.get_ipv4() { {
let mut new_nic_ctx = NicCtx::new( let ipv4_addr = self.global_ctx.get_ipv4();
self.global_ctx.clone(), let ipv6_addr = self.global_ctx.get_ipv6();
&self.peer_manager,
self.peer_packet_receiver.clone(), // Only run if we have at least one IP address (IPv4 or IPv6)
); if ipv4_addr.is_some() || ipv6_addr.is_some() {
new_nic_ctx.run(ipv4_addr).await?; let mut new_nic_ctx = NicCtx::new(
let ifname = new_nic_ctx.ifname().await; self.global_ctx.clone(),
Self::use_new_nic_ctx( &self.peer_manager,
self.nic_ctx.clone(), self.peer_packet_receiver.clone(),
new_nic_ctx, );
Self::create_magic_dns_runner(
self.peer_manager.clone(), new_nic_ctx.run(ipv4_addr, ipv6_addr).await?;
ifname, let ifname = new_nic_ctx.ifname().await;
ipv4_addr.clone(),
), // Create Magic DNS runner only if we have IPv4
) let dns_runner = if let Some(ipv4) = ipv4_addr {
.await; Self::create_magic_dns_runner(self.peer_manager.clone(), ifname, ipv4)
} else {
None
};
Self::use_new_nic_ctx(self.nic_ctx.clone(), new_nic_ctx, dns_runner).await;
}
} }
} }
@@ -852,7 +857,7 @@ impl Drop for Instance {
}; };
let now = std::time::Instant::now(); let now = std::time::Instant::now();
while now.elapsed().as_secs() < 1 { while now.elapsed().as_secs() < 10 {
tokio::time::sleep(std::time::Duration::from_millis(50)).await; tokio::time::sleep(std::time::Duration::from_millis(50)).await;
if pm.strong_count() == 0 { if pm.strong_count() == 0 {
tracing::info!( tracing::info!(
+92 -10
View File
@@ -1,7 +1,7 @@
use std::{ use std::{
collections::BTreeSet, collections::BTreeSet,
io, io,
net::Ipv4Addr, net::{IpAddr, Ipv4Addr, Ipv6Addr},
pin::Pin, pin::Pin,
sync::{Arc, Weak}, sync::{Arc, Weak},
task::{Context, Poll}, task::{Context, Poll},
@@ -25,7 +25,7 @@ use byteorder::WriteBytesExt as _;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use futures::{lock::BiLock, ready, SinkExt, Stream, StreamExt}; use futures::{lock::BiLock, ready, SinkExt, Stream, StreamExt};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use pnet::packet::ipv4::Ipv4Packet; use pnet::packet::{ipv4::Ipv4Packet, ipv6::Ipv6Packet};
use tokio::{ use tokio::{
io::{AsyncRead, AsyncWrite, ReadBuf}, io::{AsyncRead, AsyncWrite, ReadBuf},
sync::Mutex, sync::Mutex,
@@ -434,12 +434,26 @@ impl VirtualNic {
Ok(()) Ok(())
} }
pub async fn add_ipv6_route(&self, address: Ipv6Addr, cidr: u8) -> Result<(), Error> {
let _g = self.global_ctx.net_ns.guard();
self.ifcfg
.add_ipv6_route(self.ifname(), address, cidr, None)
.await?;
Ok(())
}
pub async fn remove_ip(&self, ip: Option<Ipv4Addr>) -> Result<(), Error> { pub async fn remove_ip(&self, ip: Option<Ipv4Addr>) -> Result<(), Error> {
let _g = self.global_ctx.net_ns.guard(); let _g = self.global_ctx.net_ns.guard();
self.ifcfg.remove_ip(self.ifname(), ip).await?; self.ifcfg.remove_ip(self.ifname(), ip).await?;
Ok(()) Ok(())
} }
pub async fn remove_ipv6(&self, ip: Option<Ipv6Addr>) -> Result<(), Error> {
let _g = self.global_ctx.net_ns.guard();
self.ifcfg.remove_ipv6(self.ifname(), ip).await?;
Ok(())
}
pub async fn add_ip(&self, ip: Ipv4Addr, cidr: i32) -> Result<(), Error> { pub async fn add_ip(&self, ip: Ipv4Addr, cidr: i32) -> Result<(), Error> {
let _g = self.global_ctx.net_ns.guard(); let _g = self.global_ctx.net_ns.guard();
self.ifcfg self.ifcfg
@@ -448,6 +462,14 @@ impl VirtualNic {
Ok(()) Ok(())
} }
pub async fn add_ipv6(&self, ip: Ipv6Addr, cidr: i32) -> Result<(), Error> {
let _g = self.global_ctx.net_ns.guard();
self.ifcfg
.add_ipv6_ip(self.ifname(), ip, cidr as u8)
.await?;
Ok(())
}
pub fn get_ifcfg(&self) -> impl IfConfiguerTrait { pub fn get_ifcfg(&self) -> impl IfConfiguerTrait {
IfConfiger {} IfConfiger {}
} }
@@ -496,6 +518,20 @@ impl NicCtx {
Ok(()) Ok(())
} }
pub async fn assign_ipv6_to_tun_device(&self, ipv6_addr: cidr::Ipv6Inet) -> Result<(), Error> {
let nic = self.nic.lock().await;
nic.link_up().await?;
nic.remove_ipv6(None).await?;
nic.add_ipv6(ipv6_addr.address(), ipv6_addr.network_length() as i32)
.await?;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
{
nic.add_ipv6_route(ipv6_addr.first_address(), ipv6_addr.network_length())
.await?;
}
Ok(())
}
async fn do_forward_nic_to_peers_ipv4(ret: ZCPacket, mgr: &PeerManager) { async fn do_forward_nic_to_peers_ipv4(ret: ZCPacket, mgr: &PeerManager) {
if let Some(ipv4) = Ipv4Packet::new(ret.payload()) { if let Some(ipv4) = Ipv4Packet::new(ret.payload()) {
if ipv4.get_version() != 4 { if ipv4.get_version() != 4 {
@@ -509,16 +545,53 @@ impl NicCtx {
); );
// TODO: use zero-copy // TODO: use zero-copy
let send_ret = mgr.send_msg_ipv4(ret, dst_ipv4).await; let send_ret = mgr.send_msg_by_ip(ret, IpAddr::V4(dst_ipv4)).await;
if send_ret.is_err() { if send_ret.is_err() {
tracing::trace!(?send_ret, "[USER_PACKET] send_msg_ipv4 failed") tracing::trace!(?send_ret, "[USER_PACKET] send_msg failed")
} }
} else { } else {
tracing::warn!(?ret, "[USER_PACKET] not ipv4 packet"); tracing::warn!(?ret, "[USER_PACKET] not ipv4 packet");
} }
} }
fn do_forward_nic_to_peers( async fn do_forward_nic_to_peers_ipv6(ret: ZCPacket, mgr: &PeerManager) {
if let Some(ipv6) = Ipv6Packet::new(ret.payload()) {
if ipv6.get_version() != 6 {
tracing::info!("[USER_PACKET] not ipv6 packet: {:?}", ipv6);
return;
}
let dst_ipv6 = ipv6.get_destination();
tracing::trace!(
?ret,
"[USER_PACKET] recv new packet from tun device and forward to peers."
);
// TODO: use zero-copy
let send_ret = mgr.send_msg_by_ip(ret, IpAddr::V6(dst_ipv6)).await;
if send_ret.is_err() {
tracing::trace!(?send_ret, "[USER_PACKET] send_msg failed")
}
} else {
tracing::warn!(?ret, "[USER_PACKET] not ipv6 packet");
}
}
async fn do_forward_nic_to_peers(ret: ZCPacket, mgr: &PeerManager) {
let payload = ret.payload();
if payload.is_empty() {
return;
}
match payload[0] >> 4 {
4 => Self::do_forward_nic_to_peers_ipv4(ret, mgr).await,
6 => Self::do_forward_nic_to_peers_ipv6(ret, mgr).await,
_ => {
tracing::warn!(?ret, "[USER_PACKET] unknown IP version");
}
}
}
fn do_forward_nic_to_peers_task(
&mut self, &mut self,
mut stream: Pin<Box<dyn ZCPacketStream>>, mut stream: Pin<Box<dyn ZCPacketStream>>,
) -> Result<(), Error> { ) -> Result<(), Error> {
@@ -532,7 +605,7 @@ impl NicCtx {
tracing::error!("read from nic failed: {:?}", ret); tracing::error!("read from nic failed: {:?}", ret);
break; break;
} }
Self::do_forward_nic_to_peers_ipv4(ret.unwrap(), mgr.as_ref()).await; Self::do_forward_nic_to_peers(ret.unwrap(), mgr.as_ref()).await;
} }
panic!("nic stream closed"); panic!("nic stream closed");
}); });
@@ -647,7 +720,7 @@ impl NicCtx {
Ok(()) Ok(())
} }
pub async fn run(&mut self, ipv4_addr: cidr::Ipv4Inet) -> Result<(), Error> { pub async fn run(&mut self, ipv4_addr: Option<cidr::Ipv4Inet>, ipv6_addr: Option<cidr::Ipv6Inet>) -> Result<(), Error> {
let tunnel = { let tunnel = {
let mut nic = self.nic.lock().await; let mut nic = self.nic.lock().await;
match nic.create_dev().await { match nic.create_dev().await {
@@ -681,10 +754,19 @@ impl NicCtx {
let (stream, sink) = tunnel.split(); let (stream, sink) = tunnel.split();
self.do_forward_nic_to_peers(stream)?; self.do_forward_nic_to_peers_task(stream)?;
self.do_forward_peers_to_nic(sink); self.do_forward_peers_to_nic(sink);
self.assign_ipv4_to_tun_device(ipv4_addr).await?; // Assign IPv4 address if provided
if let Some(ipv4_addr) = ipv4_addr {
self.assign_ipv4_to_tun_device(ipv4_addr).await?;
}
// Assign IPv6 address if provided
if let Some(ipv6_addr) = ipv6_addr {
self.assign_ipv6_to_tun_device(ipv6_addr).await?;
}
self.run_proxy_cidrs_route_updater().await?; self.run_proxy_cidrs_route_updater().await?;
Ok(()) Ok(())
@@ -710,7 +792,7 @@ impl NicCtx {
let (stream, sink) = tunnel.split(); let (stream, sink) = tunnel.split();
self.do_forward_nic_to_peers(stream)?; self.do_forward_nic_to_peers_task(stream)?;
self.do_forward_peers_to_nic(sink); self.do_forward_peers_to_nic(sink);
Ok(()) Ok(())
+46 -6
View File
@@ -1,6 +1,6 @@
use std::{ use std::{
fmt::Debug, fmt::Debug,
net::Ipv4Addr, net::{IpAddr, Ipv4Addr, Ipv6Addr},
sync::{Arc, Weak}, sync::{Arc, Weak},
time::{Instant, SystemTime}, time::{Instant, SystemTime},
}; };
@@ -873,6 +873,43 @@ impl PeerManager {
(dst_peers, is_exit_node) (dst_peers, is_exit_node)
} }
pub async fn get_msg_dst_peer_ipv6(&self, ipv6_addr: &Ipv6Addr) -> (Vec<PeerId>, bool) {
let mut is_exit_node = false;
let mut dst_peers = vec![];
let network_length = self
.global_ctx
.get_ipv6()
.map(|x| x.network_length())
.unwrap_or(64);
let ipv6_inet = cidr::Ipv6Inet::new(*ipv6_addr, network_length).unwrap();
if ipv6_addr.is_multicast() || *ipv6_addr == ipv6_inet.last_address() {
dst_peers.extend(
self.peers
.list_routes()
.await
.iter()
.map(|x| x.key().clone()),
);
} 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;
}
}
(dst_peers, is_exit_node)
}
pub async fn try_compress_and_encrypt( pub async fn try_compress_and_encrypt(
compress_algo: CompressorAlgo, compress_algo: CompressorAlgo,
encryptor: &Box<dyn Encryptor>, encryptor: &Box<dyn Encryptor>,
@@ -887,11 +924,11 @@ impl PeerManager {
Ok(()) Ok(())
} }
pub async fn send_msg_ipv4(&self, mut msg: ZCPacket, ipv4_addr: Ipv4Addr) -> Result<(), Error> { pub async fn send_msg_by_ip(&self, mut msg: ZCPacket, ip_addr: IpAddr) -> Result<(), Error> {
tracing::trace!( tracing::trace!(
"do send_msg in peer manager, msg: {:?}, ipv4_addr: {}", "do send_msg in peer manager, msg: {:?}, ip_addr: {}",
msg, msg,
ipv4_addr ip_addr
); );
msg.fill_peer_manager_hdr( msg.fill_peer_manager_hdr(
@@ -911,10 +948,13 @@ impl PeerManager {
.await; .await;
} }
let (dst_peers, is_exit_node) = self.get_msg_dst_peer(&ipv4_addr).await; let (dst_peers, is_exit_node) = match ip_addr {
IpAddr::V4(ipv4_addr) => self.get_msg_dst_peer(&ipv4_addr).await,
IpAddr::V6(ipv6_addr) => self.get_msg_dst_peer_ipv6(&ipv6_addr).await,
};
if dst_peers.is_empty() { if dst_peers.is_empty() {
tracing::info!("no peer id for ipv4: {}", ipv4_addr); tracing::info!("no peer id for ip: {}", ip_addr);
return Ok(()); return Ok(());
} }
+11 -1
View File
@@ -1,4 +1,4 @@
use std::{net::Ipv4Addr, sync::Arc}; use std::{net::{Ipv4Addr, Ipv6Addr}, sync::Arc};
use anyhow::Context; use anyhow::Context;
use dashmap::DashMap; use dashmap::DashMap;
@@ -194,6 +194,16 @@ impl PeerMap {
None None
} }
pub async fn get_peer_id_by_ipv6(&self, ipv6: &Ipv6Addr) -> Option<PeerId> {
for route in self.routes.read().await.iter() {
let peer_id = route.get_peer_id_by_ipv6(ipv6).await;
if peer_id.is_some() {
return peer_id;
}
}
None
}
pub async fn get_route_peer_info(&self, peer_id: PeerId) -> Option<RoutePeerInfo> { pub async fn get_route_peer_info(&self, peer_id: PeerId) -> Option<RoutePeerInfo> {
for route in self.routes.read().await.iter() { for route in self.routes.read().await.iter() {
if let Some(info) = route.get_peer_info(peer_id).await { if let Some(info) = route.get_peer_info(peer_id).await {
+37 -1
View File
@@ -1,7 +1,7 @@
use std::{ use std::{
collections::BTreeSet, collections::BTreeSet,
fmt::Debug, fmt::Debug,
net::Ipv4Addr, net::{Ipv4Addr, Ipv6Addr},
sync::{ sync::{
atomic::{AtomicBool, AtomicU32, Ordering}, atomic::{AtomicBool, AtomicU32, Ordering},
Arc, Weak, Arc, Weak,
@@ -125,6 +125,7 @@ impl RoutePeerInfo {
peer_route_id: 0, peer_route_id: 0,
network_length: 24, network_length: 24,
quic_port: None, quic_port: None,
ipv6_addr: None,
} }
} }
@@ -165,6 +166,7 @@ impl RoutePeerInfo {
.unwrap_or(24), .unwrap_or(24),
quic_port: global_ctx.get_quic_proxy_port().map(|x| x as u32), quic_port: global_ctx.get_quic_proxy_port().map(|x| x as u32),
ipv6_addr: global_ctx.get_ipv6().map(|x| x.into()),
}; };
let need_update_periodically = if let Ok(Ok(d)) = let need_update_periodically = if let Ok(Ok(d)) =
@@ -221,6 +223,8 @@ impl Into<crate::proto::cli::Route> for RoutePeerInfo {
next_hop_peer_id_latency_first: None, next_hop_peer_id_latency_first: None,
cost_latency_first: None, cost_latency_first: None,
path_latency_latency_first: None, path_latency_latency_first: None,
ipv6_addr: self.ipv6_addr.map(Into::into),
} }
} }
} }
@@ -635,6 +639,7 @@ struct RouteTable {
peer_infos: DashMap<PeerId, RoutePeerInfo>, peer_infos: DashMap<PeerId, RoutePeerInfo>,
next_hop_map: NextHopMap, next_hop_map: NextHopMap,
ipv4_peer_id_map: DashMap<Ipv4Addr, PeerId>, ipv4_peer_id_map: DashMap<Ipv4Addr, PeerId>,
ipv6_peer_id_map: DashMap<Ipv6Addr, PeerId>,
cidr_peer_id_map: DashMap<cidr::IpCidr, PeerId>, cidr_peer_id_map: DashMap<cidr::IpCidr, PeerId>,
next_hop_map_version: AtomicVersion, next_hop_map_version: AtomicVersion,
} }
@@ -645,6 +650,7 @@ impl RouteTable {
peer_infos: DashMap::new(), peer_infos: DashMap::new(),
next_hop_map: DashMap::new(), next_hop_map: DashMap::new(),
ipv4_peer_id_map: DashMap::new(), ipv4_peer_id_map: DashMap::new(),
ipv6_peer_id_map: DashMap::new(),
cidr_peer_id_map: DashMap::new(), cidr_peer_id_map: DashMap::new(),
next_hop_map_version: AtomicVersion::new(), next_hop_map_version: AtomicVersion::new(),
} }
@@ -742,6 +748,10 @@ impl RouteTable {
// remove ipv4 map for peers we cannot reach. // remove ipv4 map for peers we cannot reach.
self.next_hop_map.contains_key(v) self.next_hop_map.contains_key(v)
}); });
self.ipv6_peer_id_map.retain(|_, v| {
// remove ipv6 map for peers we cannot reach.
self.next_hop_map.contains_key(v)
});
self.cidr_peer_id_map.retain(|_, v| { self.cidr_peer_id_map.retain(|_, v| {
// remove cidr map for peers we cannot reach. // remove cidr map for peers we cannot reach.
self.next_hop_map.contains_key(v) self.next_hop_map.contains_key(v)
@@ -876,6 +886,17 @@ impl RouteTable {
.or_insert(*peer_id); .or_insert(*peer_id);
} }
if let Some(ipv6_addr) = info.ipv6_addr.and_then(|x| x.address) {
self.ipv6_peer_id_map
.entry(ipv6_addr.into())
.and_modify(|v| {
if *v != *peer_id && is_new_peer_better(*v) {
*v = *peer_id;
}
})
.or_insert(*peer_id);
}
for cidr in info.proxy_cidrs.iter() { for cidr in info.proxy_cidrs.iter() {
self.cidr_peer_id_map self.cidr_peer_id_map
.entry(cidr.parse().unwrap()) .entry(cidr.parse().unwrap())
@@ -2267,6 +2288,21 @@ impl Route for PeerRoute {
None None
} }
async fn get_peer_id_by_ipv6(&self, ipv6_addr: &Ipv6Addr) -> Option<PeerId> {
let route_table = &self.service_impl.route_table;
if let Some(peer_id) = route_table.ipv6_peer_id_map.get(ipv6_addr) {
return Some(*peer_id);
}
// TODO: Add proxy support for IPv6 similar to IPv4
// if let Some(peer_id) = route_table.get_peer_id_for_proxy_ipv6(ipv6_addr) {
// return Some(peer_id);
// }
tracing::debug!(?ipv6_addr, "no peer id for ipv6");
None
}
async fn set_route_cost_fn(&self, _cost_fn: RouteCostCalculator) { async fn set_route_cost_fn(&self, _cost_fn: RouteCostCalculator) {
*self.service_impl.cost_calculator.write().unwrap() = Some(_cost_fn); *self.service_impl.cost_calculator.write().unwrap() = Some(_cost_fn);
self.service_impl.synced_route_info.version.inc(); self.service_impl.synced_route_info.version.inc();
+5
View File
@@ -36,6 +36,11 @@ impl DirectConnectorRpc for DirectConnectorManagerRpcServer {
.chain(self.global_ctx.get_running_listeners().into_iter()) .chain(self.global_ctx.get_running_listeners().into_iter())
.map(Into::into) .map(Into::into)
.collect(); .collect();
// remove et ipv6 from the interface ipv6 list
if let Some(et_ipv6) = self.global_ctx.get_ipv6() {
let et_ipv6: crate::proto::common::Ipv6Addr = et_ipv6.address().into();
ret.interface_ipv6s.retain(|x| *x != et_ipv6);
}
tracing::trace!( tracing::trace!(
"get_ip_list: public_ipv4: {:?}, public_ipv6: {:?}, listeners: {:?}", "get_ip_list: public_ipv4: {:?}, public_ipv6: {:?}, listeners: {:?}",
ret.public_ipv4, ret.public_ipv4,
+5 -1
View File
@@ -1,4 +1,4 @@
use std::{net::Ipv4Addr, sync::Arc}; use std::{net::{Ipv4Addr, Ipv6Addr}, sync::Arc};
use dashmap::DashMap; use dashmap::DashMap;
@@ -82,6 +82,10 @@ pub trait Route {
None None
} }
async fn get_peer_id_by_ipv6(&self, _ipv6: &Ipv6Addr) -> Option<PeerId> {
None
}
async fn list_peers_own_foreign_network( async fn list_peers_own_foreign_network(
&self, &self,
_network_identity: &NetworkIdentity, _network_identity: &NetworkIdentity,
+2
View File
@@ -65,6 +65,8 @@ message Route {
optional uint32 next_hop_peer_id_latency_first = 12; optional uint32 next_hop_peer_id_latency_first = 12;
optional int32 cost_latency_first = 13; optional int32 cost_latency_first = 13;
optional int32 path_latency_latency_first = 14; optional int32 path_latency_latency_first = 14;
common.Ipv6Inet ipv6_addr = 15;
} }
message PeerRoutePair { message PeerRoutePair {
+5
View File
@@ -139,6 +139,11 @@ message Ipv4Inet {
uint32 network_length = 2; uint32 network_length = 2;
} }
message Ipv6Inet {
Ipv6Addr address = 1;
uint32 network_length = 2;
}
message Url { string url = 1; } message Url { string url = 1; }
message SocketAddr { message SocketAddr {
+35
View File
@@ -131,6 +131,41 @@ impl FromStr for Ipv4Inet {
} }
} }
impl From<cidr::Ipv6Inet> for Ipv6Inet {
fn from(value: cidr::Ipv6Inet) -> Self {
Ipv6Inet {
address: Some(value.address().into()),
network_length: value.network_length() as u32,
}
}
}
impl From<Ipv6Inet> for cidr::Ipv6Inet {
fn from(value: Ipv6Inet) -> Self {
cidr::Ipv6Inet::new(
value.address.unwrap_or_default().into(),
value.network_length as u8,
)
.unwrap()
}
}
impl fmt::Display for Ipv6Inet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", cidr::Ipv6Inet::from(self.clone()))
}
}
impl FromStr for Ipv6Inet {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Ipv6Inet::from(
cidr::Ipv6Inet::from_str(s).with_context(|| "Failed to parse Ipv6Inet")?,
))
}
}
impl From<url::Url> for Url { impl From<url::Url> for Url {
fn from(value: url::Url) -> Self { fn from(value: url::Url) -> Self {
Url { Url {
+1
View File
@@ -24,6 +24,7 @@ message RoutePeerInfo {
uint32 network_length = 13; uint32 network_length = 13;
optional uint32 quic_port = 14; optional uint32 quic_port = 14;
optional common.Ipv6Inet ipv6_addr = 15;
} }
message PeerIdVersion { message PeerIdVersion {
+66
View File
@@ -0,0 +1,66 @@
use std::net::Ipv6Addr;
use crate::{
common::config::{ConfigLoader, TomlConfigLoader},
common::global_ctx::tests::get_mock_global_ctx,
peers::peer_manager::RouteAlgoType,
proto::peer_rpc::RoutePeerInfo,
};
#[tokio::test]
async fn test_ipv6_config_support() {
let config = TomlConfigLoader::default();
// Test IPv6 configuration setting and getting
let ipv6_cidr = "fd00::1/64".parse().unwrap();
config.set_ipv6(Some(ipv6_cidr));
assert_eq!(config.get_ipv6(), Some(ipv6_cidr));
}
#[tokio::test]
async fn test_global_ctx_ipv6() {
let global_ctx = get_mock_global_ctx();
// Test setting and getting IPv6 from global context
let ipv6_cidr = "fd00::1/64".parse().unwrap();
global_ctx.set_ipv6(Some(ipv6_cidr));
assert_eq!(global_ctx.get_ipv6(), Some(ipv6_cidr));
}
#[tokio::test]
async fn test_route_peer_info_ipv6() {
let global_ctx = get_mock_global_ctx();
// Set IPv6 address in global context
let ipv6_cidr = "fd00::1/64".parse().unwrap();
global_ctx.set_ipv6(Some(ipv6_cidr));
// Create RoutePeerInfo with IPv6 support
let peer_info = RoutePeerInfo::new();
let updated_info = peer_info.update_self(123, 456, &global_ctx);
// Verify IPv6 address is included
assert!(updated_info.ipv6_addr.is_some());
let ipv6_addr: Ipv6Addr = updated_info.ipv6_addr.unwrap().address.unwrap().into();
assert_eq!(ipv6_addr, ipv6_cidr.address());
}
#[tokio::test]
async fn test_peer_manager_ipv6() {
let global_ctx = get_mock_global_ctx();
let (packet_sender, _packet_receiver) = tokio::sync::mpsc::channel(100);
let peer_mgr = crate::peers::peer_manager::PeerManager::new(
RouteAlgoType::Ospf,
global_ctx.clone(),
packet_sender,
);
// Test IPv6 address lookup for unknown address
let ipv6_addr = Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 2);
let (peers, _is_self) = peer_mgr.get_msg_dst_peer_ipv6(&ipv6_addr).await;
// Should return empty peers list for unknown IPv6
assert!(peers.is_empty());
}
+2
View File
@@ -1,6 +1,8 @@
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
mod three_node; mod three_node;
mod ipv6_test;
use crate::common::PeerId; use crate::common::PeerId;
use crate::peers::peer_manager::PeerManager; use crate::peers::peer_manager::PeerManager;
+85 -22
View File
@@ -51,11 +51,17 @@ pub fn prepare_linux_namespaces() {
add_ns_to_bridge("br_b", "net_d"); add_ns_to_bridge("br_b", "net_d");
} }
pub fn get_inst_config(inst_name: &str, ns: Option<&str>, ipv4: &str) -> TomlConfigLoader { pub fn get_inst_config(
inst_name: &str,
ns: Option<&str>,
ipv4: &str,
ipv6: &str,
) -> TomlConfigLoader {
let config = TomlConfigLoader::default(); let config = TomlConfigLoader::default();
config.set_inst_name(inst_name.to_owned()); config.set_inst_name(inst_name.to_owned());
config.set_netns(ns.map(|s| s.to_owned())); config.set_netns(ns.map(|s| s.to_owned()));
config.set_ipv4(Some(ipv4.parse().unwrap())); config.set_ipv4(Some(ipv4.parse().unwrap()));
config.set_ipv6(Some(ipv6.parse().unwrap()));
config.set_listeners(vec![ config.set_listeners(vec![
"tcp://0.0.0.0:11010".parse().unwrap(), "tcp://0.0.0.0:11010".parse().unwrap(),
"udp://0.0.0.0:11010".parse().unwrap(), "udp://0.0.0.0:11010".parse().unwrap(),
@@ -82,16 +88,19 @@ pub async fn init_three_node_ex<F: Fn(TomlConfigLoader) -> TomlConfigLoader>(
"inst1", "inst1",
Some("net_a"), Some("net_a"),
"10.144.144.1", "10.144.144.1",
"fd00::1/64",
))); )));
let mut inst2 = Instance::new(cfg_cb(get_inst_config( let mut inst2 = Instance::new(cfg_cb(get_inst_config(
"inst2", "inst2",
Some("net_b"), Some("net_b"),
"10.144.144.2", "10.144.144.2",
"fd00::2/64",
))); )));
let mut inst3 = Instance::new(cfg_cb(get_inst_config( let mut inst3 = Instance::new(cfg_cb(get_inst_config(
"inst3", "inst3",
Some("net_c"), Some("net_c"),
"10.144.144.3", "10.144.144.3",
"fd00::3/64",
))); )));
inst1.run().await.unwrap(); inst1.run().await.unwrap();
@@ -232,6 +241,30 @@ async fn ping_test(from_netns: &str, target_ip: &str, payload_size: Option<usize
code.code().unwrap() == 0 code.code().unwrap() == 0
} }
async fn ping6_test(from_netns: &str, target_ip: &str, payload_size: Option<usize>) -> bool {
let _g = NetNS::new(Some(ROOT_NETNS_NAME.to_owned())).guard();
let code = tokio::process::Command::new("ip")
.args(&[
"netns",
"exec",
from_netns,
"ping6",
"-c",
"1",
"-s",
payload_size.unwrap_or(56).to_string().as_str(),
"-W",
"1",
target_ip.to_string().as_str(),
])
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.await
.unwrap();
code.code().unwrap() == 0
}
#[rstest::rstest] #[rstest::rstest]
#[tokio::test] #[tokio::test]
#[serial_test::serial] #[serial_test::serial]
@@ -250,12 +283,26 @@ pub async fn basic_three_node_test(#[values("tcp", "udp", "wg", "ws", "wss")] pr
insts[0].get_peer_manager().list_routes().await, insts[0].get_peer_manager().list_routes().await,
); );
// Test IPv4 connectivity
wait_for_condition( wait_for_condition(
|| async { ping_test("net_c", "10.144.144.1", None).await }, || async { ping_test("net_c", "10.144.144.1", None).await },
Duration::from_secs(5000), Duration::from_secs(5000),
) )
.await; .await;
// Test IPv6 connectivity
wait_for_condition(
|| async { ping6_test("net_c", "fd00::1", None).await },
Duration::from_secs(5),
)
.await;
wait_for_condition(
|| async { ping6_test("net_a", "fd00::3", None).await },
Duration::from_secs(5),
)
.await;
drop_insts(insts).await; drop_insts(insts).await;
} }
@@ -562,7 +609,12 @@ pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str
}; };
let insts = init_three_node(proto).await; let insts = init_three_node(proto).await;
let mut inst4 = Instance::new(get_inst_config("inst4", Some("net_d"), "10.144.144.4")); let mut inst4 = Instance::new(get_inst_config(
"inst4",
Some("net_d"),
"10.144.144.4",
"fd00::4/64",
));
if proto == "tcp" { if proto == "tcp" {
inst4 inst4
.get_conn_manager() .get_conn_manager()
@@ -627,16 +679,7 @@ pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str
.iter() .iter()
.find(|r| **r == inst4.peer_id()) .find(|r| **r == inst4.peer_id())
.is_none(); .is_none();
if !ret {
println!(
"conn info: {:?}",
insts[2]
.get_peer_manager()
.get_peer_map()
.list_peer_conns(inst4.peer_id())
.await
);
}
ret ret
}, },
// 0 down, assume last packet is recv in -0.01 // 0 down, assume last packet is recv in -0.01
@@ -726,13 +769,23 @@ pub async fn udp_broadcast_test() {
pub async fn foreign_network_forward_nic_data() { pub async fn foreign_network_forward_nic_data() {
prepare_linux_namespaces(); prepare_linux_namespaces();
let center_node_config = get_inst_config("inst1", Some("net_a"), "10.144.144.1"); let center_node_config = get_inst_config("inst1", Some("net_a"), "10.144.144.1", "fd00::1/64");
center_node_config center_node_config
.set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string()));
let mut center_inst = Instance::new(center_node_config); let mut center_inst = Instance::new(center_node_config);
let mut inst1 = Instance::new(get_inst_config("inst1", Some("net_b"), "10.144.145.1")); let mut inst1 = Instance::new(get_inst_config(
let mut inst2 = Instance::new(get_inst_config("inst2", Some("net_c"), "10.144.145.2")); "inst1",
Some("net_b"),
"10.144.145.1",
"fd00:1::1/64",
));
let mut inst2 = Instance::new(get_inst_config(
"inst2",
Some("net_c"),
"10.144.145.2",
"fd00:1::2/64",
));
center_inst.run().await.unwrap(); center_inst.run().await.unwrap();
inst1.run().await.unwrap(); inst1.run().await.unwrap();
@@ -940,21 +993,26 @@ pub async fn foreign_network_functional_cluster() {
crate::set_global_var!(OSPF_UPDATE_MY_GLOBAL_FOREIGN_NETWORK_INTERVAL_SEC, 1); crate::set_global_var!(OSPF_UPDATE_MY_GLOBAL_FOREIGN_NETWORK_INTERVAL_SEC, 1);
prepare_linux_namespaces(); prepare_linux_namespaces();
let center_node_config1 = get_inst_config("inst1", Some("net_a"), "10.144.144.1"); let center_node_config1 = get_inst_config("inst1", Some("net_a"), "10.144.144.1", "fd00::1/64");
center_node_config1 center_node_config1
.set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string()));
let mut center_inst1 = Instance::new(center_node_config1); let mut center_inst1 = Instance::new(center_node_config1);
let center_node_config2 = get_inst_config("inst2", Some("net_b"), "10.144.144.2"); let center_node_config2 = get_inst_config("inst2", Some("net_b"), "10.144.144.2", "fd00::2/64");
center_node_config2 center_node_config2
.set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string()));
let mut center_inst2 = Instance::new(center_node_config2); let mut center_inst2 = Instance::new(center_node_config2);
let inst1_config = get_inst_config("inst1", Some("net_c"), "10.144.145.1"); let inst1_config = get_inst_config("inst1", Some("net_c"), "10.144.145.1", "fd00:2::1/64");
inst1_config.set_listeners(vec![]); inst1_config.set_listeners(vec![]);
let mut inst1 = Instance::new(inst1_config); let mut inst1 = Instance::new(inst1_config);
let mut inst2 = Instance::new(get_inst_config("inst2", Some("net_d"), "10.144.145.2")); let mut inst2 = Instance::new(get_inst_config(
"inst2",
Some("net_d"),
"10.144.145.2",
"fd00:2::2/64",
));
center_inst1.run().await.unwrap(); center_inst1.run().await.unwrap();
center_inst2.run().await.unwrap(); center_inst2.run().await.unwrap();
@@ -1011,18 +1069,23 @@ pub async fn foreign_network_functional_cluster() {
pub async fn manual_reconnector(#[values(true, false)] is_foreign: bool) { pub async fn manual_reconnector(#[values(true, false)] is_foreign: bool) {
prepare_linux_namespaces(); prepare_linux_namespaces();
let center_node_config = get_inst_config("inst1", Some("net_a"), "10.144.144.1"); let center_node_config = get_inst_config("inst1", Some("net_a"), "10.144.144.1", "fd00::1/64");
if is_foreign { if is_foreign {
center_node_config center_node_config
.set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string()));
} }
let mut center_inst = Instance::new(center_node_config); let mut center_inst = Instance::new(center_node_config);
let inst1_config = get_inst_config("inst1", Some("net_b"), "10.144.145.1"); let inst1_config = get_inst_config("inst1", Some("net_b"), "10.144.145.1", "fd00:1::1/64");
inst1_config.set_listeners(vec![]); inst1_config.set_listeners(vec![]);
let mut inst1 = Instance::new(inst1_config); let mut inst1 = Instance::new(inst1_config);
let mut inst2 = Instance::new(get_inst_config("inst2", Some("net_c"), "10.144.145.2")); let mut inst2 = Instance::new(get_inst_config(
"inst2",
Some("net_c"),
"10.144.145.2",
"fd00:1::2/64",
));
center_inst.run().await.unwrap(); center_inst.run().await.unwrap();
inst1.run().await.unwrap(); inst1.run().await.unwrap();
+6 -1
View File
@@ -388,7 +388,12 @@ pub(crate) fn setup_sokcet2_ext(
} }
} }
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux", target_env = "ohos"))] #[cfg(any(
target_os = "android",
target_os = "fuchsia",
target_os = "linux",
target_env = "ohos"
))]
if let Some(dev_name) = bind_dev { if let Some(dev_name) = bind_dev {
tracing::trace!(dev_name = ?dev_name, "bind device"); tracing::trace!(dev_name = ?dev_name, "bind device");
socket2_socket.bind_device(Some(dev_name.as_bytes()))?; socket2_socket.bind_device(Some(dev_name.as_bytes()))?;
+2 -2
View File
@@ -1,5 +1,5 @@
use std::{ use std::{
net::{Ipv4Addr, SocketAddr}, net::{IpAddr, Ipv4Addr, SocketAddr},
sync::Arc, sync::Arc,
}; };
@@ -128,7 +128,7 @@ impl WireGuardImpl {
tracing::trace!(?i, "Received from wg client"); tracing::trace!(?i, "Received from wg client");
let dst = i.get_destination(); let dst = i.get_destination();
let _ = peer_mgr let _ = peer_mgr
.send_msg_ipv4(ZCPacket::new_with_payload(inner.as_ref()), dst) .send_msg_by_ip(ZCPacket::new_with_payload(inner.as_ref()), IpAddr::V4(dst))
.await; .await;
} }
Generated
+82
View File
@@ -0,0 +1,82 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1750741721,
"narHash": "sha256-Z0djmTa1YmnGMfE9jEe05oO4zggjDmxOGKwt844bUhE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4b1164c3215f018c4442463a27689d973cffd750",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1750905536,
"narHash": "sha256-Mo7yXM5IvMGNvJPiNkFsVT2UERmnvjsKgnY6UyDdySQ=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "2fa7c0aabd15fa0ccc1dc7e675a4fcf0272ad9a1",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+44
View File
@@ -0,0 +1,44 @@
{
description = "EasyTier development environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs {
inherit system overlays;
};
lib = nixpkgs.lib;
rust = pkgs.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" "rust-analyzer" ];
};
in
{
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
rust
pkg-config
protobuf
];
buildInputs = with pkgs; [
zstd
];
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ZSTD_SYS_USE_PKG_CONFIG = true;
KCP_SYS_EXTRA_HEADER_PATH = "${pkgs.libclang.lib}/lib/clang/19/include:${pkgs.glibc.dev}/include";
};
});
}