diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 822a8d7..49f5dab 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ node_modules easytier-gui/src-tauri/*.dll /easytier-contrib/easytier-ohrs/dist/ + +.direnv diff --git a/Cargo.lock b/Cargo.lock index 29193e6..0c7b490 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8606,8 +8606,8 @@ dependencies = [ [[package]] name = "thunk-rs" -version = "0.3.3" -source = "git+https://github.com/easytier/thunk.git#5e8371a3100dbc18dda952a2036c6bd6fb0504db" +version = "0.3.4" +source = "git+https://github.com/easytier/thunk.git#403f0d26d3d5bcfdfd76c23e36e517f19fe891e0" [[package]] name = "tiff" diff --git a/easytier/locales/app.yml b/easytier/locales/app.yml index 84afdd6..69c1eca 100644 --- a/easytier/locales/app.yml +++ b/easytier/locales/app.yml @@ -27,6 +27,9 @@ core_clap: ipv4: 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设备" + ipv6: + en: "ipv6 address of this vpn node, can be used together with ipv4 for dual-stack operation" + zh-CN: "此VPN节点的IPv6地址,可与IPv4一起使用以进行双栈操作" 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." zh-CN: "由Easytier自动确定并设置IP地址,默认从10.0.0.1开始。警告:在使用DHCP时,如果网络中出现IP冲突,IP将自动更改。" diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index 41d6390..b49938f 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -64,6 +64,9 @@ pub trait ConfigLoader: Send + Sync { fn get_ipv4(&self) -> Option; fn set_ipv4(&self, addr: Option); + fn get_ipv6(&self) -> Option; + fn set_ipv6(&self, addr: Option); + fn get_dhcp(&self) -> bool; fn set_dhcp(&self, dhcp: bool); @@ -259,6 +262,7 @@ struct Config { instance_name: Option, instance_id: Option, ipv4: Option, + ipv6: Option, dhcp: Option, network_identity: Option, listeners: Option>, @@ -416,6 +420,23 @@ impl ConfigLoader for TomlConfigLoader { }; } + fn get_ipv6(&self) -> Option { + let locked_config = self.config.lock().unwrap(); + locked_config + .ipv6 + .as_ref() + .map(|s| s.parse().ok()) + .flatten() + } + + fn set_ipv6(&self, addr: Option) { + self.config.lock().unwrap().ipv6 = if let Some(addr) = addr { + Some(addr.to_string()) + } else { + None + }; + } + fn get_dhcp(&self) -> bool { self.config.lock().unwrap().dhcp.unwrap_or_default() } diff --git a/easytier/src/common/global_ctx.rs b/easytier/src/common/global_ctx.rs index 803df87..9fe50da 100644 --- a/easytier/src/common/global_ctx.rs +++ b/easytier/src/common/global_ctx.rs @@ -61,6 +61,7 @@ pub struct GlobalCtx { event_bus: EventBus, cached_ipv4: AtomicCell>, + cached_ipv6: AtomicCell>, cached_proxy_cidrs: AtomicCell>>, ip_collector: Mutex>>, @@ -124,6 +125,7 @@ impl GlobalCtx { event_bus, cached_ipv4: AtomicCell::new(None), + cached_ipv6: AtomicCell::new(None), cached_proxy_cidrs: AtomicCell::new(None), ip_collector: Mutex::new(Some(Arc::new(IPCollector::new( @@ -191,6 +193,20 @@ impl GlobalCtx { self.cached_ipv4.store(None); } + pub fn get_ipv6(&self) -> Option { + 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) { + self.config.set_ipv6(addr); + self.cached_ipv6.store(None); + } + pub fn get_id(&self) -> uuid::Uuid { self.config.get_id() } diff --git a/easytier/src/common/ifcfg/darwin.rs b/easytier/src/common/ifcfg/darwin.rs index 2cf13be..cfce7cc 100644 --- a/easytier/src/common/ifcfg/darwin.rs +++ b/easytier/src/common/ifcfg/darwin.rs @@ -80,4 +80,62 @@ impl IfConfiguerTrait for MacIfConfiger { async fn set_mtu(&self, name: &str, mtu: u32) -> Result<(), Error> { 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) -> 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, + ) -> 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 + } } diff --git a/easytier/src/common/ifcfg/mod.rs b/easytier/src/common/ifcfg/mod.rs index e779cac..a64f0a6 100644 --- a/easytier/src/common/ifcfg/mod.rs +++ b/easytier/src/common/ifcfg/mod.rs @@ -7,7 +7,7 @@ mod windows; mod route; -use std::net::Ipv4Addr; +use std::net::{Ipv4Addr, Ipv6Addr}; use async_trait::async_trait; use tokio::process::Command; @@ -41,12 +41,40 @@ pub trait IfConfiguerTrait: Send + Sync { ) -> Result<(), Error> { Ok(()) } + async fn add_ipv6_route( + &self, + _name: &str, + _address: Ipv6Addr, + _cidr_prefix: u8, + _cost: Option, + ) -> 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> { Ok(()) } async fn remove_ip(&self, _name: &str, _ip: Option) -> Result<(), Error> { Ok(()) } + async fn remove_ipv6(&self, _name: &str, _ip: Option) -> Result<(), Error> { + Ok(()) + } async fn wait_interface_show(&self, _name: &str) -> Result<(), Error> { return Ok(()); } diff --git a/easytier/src/common/ifcfg/netlink.rs b/easytier/src/common/ifcfg/netlink.rs index 80ecbd2..7b2c4e6 100644 --- a/easytier/src/common/ifcfg/netlink.rs +++ b/easytier/src/common/ifcfg/netlink.rs @@ -194,6 +194,32 @@ impl NetlinkIfConfiger { ) } + fn get_prefix_len_ipv6(name: &str, ip: Ipv6Addr) -> Result { + 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::DelAddress(message), + true, + ) + } + pub(crate) fn mtu_op>( name: &str, op: T, @@ -469,6 +495,106 @@ impl IfConfiguerTrait for NetlinkIfConfiger { 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::NewAddress(message), + false, + ) + } + + async fn remove_ipv6(&self, name: &str, ip: Option) -> 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, + ) -> 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)] diff --git a/easytier/src/common/ifcfg/windows.rs b/easytier/src/common/ifcfg/windows.rs index 5699104..fb4141b 100644 --- a/easytier/src/common/ifcfg/windows.rs +++ b/easytier/src/common/ifcfg/windows.rs @@ -169,6 +169,74 @@ impl IfConfiguerTrait for WindowsIfConfiger { ) .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) -> 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, + ) -> 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; diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index f67d7fa..c6b51ac 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -148,6 +148,13 @@ struct NetworkOptions { )] ipv4: Option, + #[arg( + long, + env = "ET_IPV6", + help = t!("core_clap.ipv6").to_string() + )] + ipv6: Option, + #[arg( short, 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() { let mut peers = cfg.get_peers(); peers.reserve(peers.len() + self.peers.len()); diff --git a/easytier/src/gateway/socks5.rs b/easytier/src/gateway/socks5.rs index 5c39583..c99d15d 100644 --- a/easytier/src/gateway/socks5.rs +++ b/easytier/src/gateway/socks5.rs @@ -301,7 +301,7 @@ impl Socks5ServerNet { let dst = ipv4.get_destination(); 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); } } diff --git a/easytier/src/gateway/tcp_proxy.rs b/easytier/src/gateway/tcp_proxy.rs index 6884c5e..234ec79 100644 --- a/easytier/src/gateway/tcp_proxy.rs +++ b/easytier/src/gateway/tcp_proxy.rs @@ -557,7 +557,7 @@ impl TcpProxy { let dst = ipv4.get_destination(); 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); } } diff --git a/easytier/src/instance/dns_server/tests.rs b/easytier/src/instance/dns_server/tests.rs index 6035cb4..19a4260 100644 --- a/easytier/src/instance/dns_server/tests.rs +++ b/easytier/src/instance/dns_server/tests.rs @@ -34,7 +34,7 @@ pub async fn prepare_env(dns_name: &str, tun_ip: Ipv4Inet) -> (Arc, let r = Arc::new(tokio::sync::Mutex::new(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) } diff --git a/easytier/src/instance/instance.rs b/easytier/src/instance/instance.rs index 2936773..0c6212e 100644 --- a/easytier/src/instance/instance.rs +++ b/easytier/src/instance/instance.rs @@ -484,7 +484,7 @@ impl Instance { &peer_manager_c, _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!( ?current_dhcp_ip, ?candidate_ipv4_addr, @@ -532,24 +532,29 @@ impl Instance { if !self.global_ctx.config.get_flags().no_tun { #[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( - self.global_ctx.clone(), - &self.peer_manager, - self.peer_packet_receiver.clone(), - ); - new_nic_ctx.run(ipv4_addr).await?; - let ifname = new_nic_ctx.ifname().await; - Self::use_new_nic_ctx( - self.nic_ctx.clone(), - new_nic_ctx, - Self::create_magic_dns_runner( - self.peer_manager.clone(), - ifname, - ipv4_addr.clone(), - ), - ) - .await; + { + let ipv4_addr = self.global_ctx.get_ipv4(); + let ipv6_addr = self.global_ctx.get_ipv6(); + + // Only run if we have at least one IP address (IPv4 or IPv6) + if ipv4_addr.is_some() || ipv6_addr.is_some() { + let mut new_nic_ctx = NicCtx::new( + self.global_ctx.clone(), + &self.peer_manager, + self.peer_packet_receiver.clone(), + ); + + new_nic_ctx.run(ipv4_addr, ipv6_addr).await?; + let ifname = new_nic_ctx.ifname().await; + + // Create Magic DNS runner only if we have IPv4 + let dns_runner = if let Some(ipv4) = ipv4_addr { + 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(); - while now.elapsed().as_secs() < 1 { + while now.elapsed().as_secs() < 10 { tokio::time::sleep(std::time::Duration::from_millis(50)).await; if pm.strong_count() == 0 { tracing::info!( diff --git a/easytier/src/instance/virtual_nic.rs b/easytier/src/instance/virtual_nic.rs index 6330a15..8d02265 100644 --- a/easytier/src/instance/virtual_nic.rs +++ b/easytier/src/instance/virtual_nic.rs @@ -1,7 +1,7 @@ use std::{ collections::BTreeSet, io, - net::Ipv4Addr, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, pin::Pin, sync::{Arc, Weak}, task::{Context, Poll}, @@ -25,7 +25,7 @@ use byteorder::WriteBytesExt as _; use bytes::{BufMut, BytesMut}; use futures::{lock::BiLock, ready, SinkExt, Stream, StreamExt}; use pin_project_lite::pin_project; -use pnet::packet::ipv4::Ipv4Packet; +use pnet::packet::{ipv4::Ipv4Packet, ipv6::Ipv6Packet}; use tokio::{ io::{AsyncRead, AsyncWrite, ReadBuf}, sync::Mutex, @@ -434,12 +434,26 @@ impl VirtualNic { 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) -> Result<(), Error> { let _g = self.global_ctx.net_ns.guard(); self.ifcfg.remove_ip(self.ifname(), ip).await?; Ok(()) } + pub async fn remove_ipv6(&self, ip: Option) -> 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> { let _g = self.global_ctx.net_ns.guard(); self.ifcfg @@ -448,6 +462,14 @@ impl VirtualNic { 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 { IfConfiger {} } @@ -496,6 +518,20 @@ impl NicCtx { 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) { if let Some(ipv4) = Ipv4Packet::new(ret.payload()) { if ipv4.get_version() != 4 { @@ -509,16 +545,53 @@ impl NicCtx { ); // 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() { - tracing::trace!(?send_ret, "[USER_PACKET] send_msg_ipv4 failed") + tracing::trace!(?send_ret, "[USER_PACKET] send_msg failed") } } else { 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 stream: Pin>, ) -> Result<(), Error> { @@ -532,7 +605,7 @@ impl NicCtx { tracing::error!("read from nic failed: {:?}", ret); 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"); }); @@ -647,7 +720,7 @@ impl NicCtx { Ok(()) } - pub async fn run(&mut self, ipv4_addr: cidr::Ipv4Inet) -> Result<(), Error> { + pub async fn run(&mut self, ipv4_addr: Option, ipv6_addr: Option) -> Result<(), Error> { let tunnel = { let mut nic = self.nic.lock().await; match nic.create_dev().await { @@ -681,10 +754,19 @@ impl NicCtx { 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.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?; Ok(()) @@ -710,7 +792,7 @@ impl NicCtx { 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); Ok(()) diff --git a/easytier/src/peers/peer_manager.rs b/easytier/src/peers/peer_manager.rs index de0c31c..746a722 100644 --- a/easytier/src/peers/peer_manager.rs +++ b/easytier/src/peers/peer_manager.rs @@ -1,6 +1,6 @@ use std::{ fmt::Debug, - net::Ipv4Addr, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, sync::{Arc, Weak}, time::{Instant, SystemTime}, }; @@ -873,6 +873,43 @@ impl PeerManager { (dst_peers, is_exit_node) } + pub async fn get_msg_dst_peer_ipv6(&self, ipv6_addr: &Ipv6Addr) -> (Vec, 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( compress_algo: CompressorAlgo, encryptor: &Box, @@ -887,11 +924,11 @@ impl PeerManager { 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!( - "do send_msg in peer manager, msg: {:?}, ipv4_addr: {}", + "do send_msg in peer manager, msg: {:?}, ip_addr: {}", msg, - ipv4_addr + ip_addr ); msg.fill_peer_manager_hdr( @@ -911,10 +948,13 @@ impl PeerManager { .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() { - tracing::info!("no peer id for ipv4: {}", ipv4_addr); + tracing::info!("no peer id for ip: {}", ip_addr); return Ok(()); } diff --git a/easytier/src/peers/peer_map.rs b/easytier/src/peers/peer_map.rs index 4aa0abc..9bd37bc 100644 --- a/easytier/src/peers/peer_map.rs +++ b/easytier/src/peers/peer_map.rs @@ -1,4 +1,4 @@ -use std::{net::Ipv4Addr, sync::Arc}; +use std::{net::{Ipv4Addr, Ipv6Addr}, sync::Arc}; use anyhow::Context; use dashmap::DashMap; @@ -194,6 +194,16 @@ impl PeerMap { None } + pub async fn get_peer_id_by_ipv6(&self, ipv6: &Ipv6Addr) -> Option { + 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 { for route in self.routes.read().await.iter() { if let Some(info) = route.get_peer_info(peer_id).await { diff --git a/easytier/src/peers/peer_ospf_route.rs b/easytier/src/peers/peer_ospf_route.rs index f9d3adf..d1d99f7 100644 --- a/easytier/src/peers/peer_ospf_route.rs +++ b/easytier/src/peers/peer_ospf_route.rs @@ -1,7 +1,7 @@ use std::{ collections::BTreeSet, fmt::Debug, - net::Ipv4Addr, + net::{Ipv4Addr, Ipv6Addr}, sync::{ atomic::{AtomicBool, AtomicU32, Ordering}, Arc, Weak, @@ -125,6 +125,7 @@ impl RoutePeerInfo { peer_route_id: 0, network_length: 24, quic_port: None, + ipv6_addr: None, } } @@ -165,6 +166,7 @@ impl RoutePeerInfo { .unwrap_or(24), 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)) = @@ -221,6 +223,8 @@ impl Into for RoutePeerInfo { next_hop_peer_id_latency_first: None, cost_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, next_hop_map: NextHopMap, ipv4_peer_id_map: DashMap, + ipv6_peer_id_map: DashMap, cidr_peer_id_map: DashMap, next_hop_map_version: AtomicVersion, } @@ -645,6 +650,7 @@ impl RouteTable { peer_infos: DashMap::new(), next_hop_map: DashMap::new(), ipv4_peer_id_map: DashMap::new(), + ipv6_peer_id_map: DashMap::new(), cidr_peer_id_map: DashMap::new(), next_hop_map_version: AtomicVersion::new(), } @@ -742,6 +748,10 @@ impl RouteTable { // remove ipv4 map for peers we cannot reach. 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| { // remove cidr map for peers we cannot reach. self.next_hop_map.contains_key(v) @@ -876,6 +886,17 @@ impl RouteTable { .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() { self.cidr_peer_id_map .entry(cidr.parse().unwrap()) @@ -2267,6 +2288,21 @@ impl Route for PeerRoute { None } + async fn get_peer_id_by_ipv6(&self, ipv6_addr: &Ipv6Addr) -> Option { + 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) { *self.service_impl.cost_calculator.write().unwrap() = Some(_cost_fn); self.service_impl.synced_route_info.version.inc(); diff --git a/easytier/src/peers/peer_rpc_service.rs b/easytier/src/peers/peer_rpc_service.rs index 8e3b5e7..13eba76 100644 --- a/easytier/src/peers/peer_rpc_service.rs +++ b/easytier/src/peers/peer_rpc_service.rs @@ -36,6 +36,11 @@ impl DirectConnectorRpc for DirectConnectorManagerRpcServer { .chain(self.global_ctx.get_running_listeners().into_iter()) .map(Into::into) .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!( "get_ip_list: public_ipv4: {:?}, public_ipv6: {:?}, listeners: {:?}", ret.public_ipv4, diff --git a/easytier/src/peers/route_trait.rs b/easytier/src/peers/route_trait.rs index 21f6083..d18e5ba 100644 --- a/easytier/src/peers/route_trait.rs +++ b/easytier/src/peers/route_trait.rs @@ -1,4 +1,4 @@ -use std::{net::Ipv4Addr, sync::Arc}; +use std::{net::{Ipv4Addr, Ipv6Addr}, sync::Arc}; use dashmap::DashMap; @@ -82,6 +82,10 @@ pub trait Route { None } + async fn get_peer_id_by_ipv6(&self, _ipv6: &Ipv6Addr) -> Option { + None + } + async fn list_peers_own_foreign_network( &self, _network_identity: &NetworkIdentity, diff --git a/easytier/src/proto/cli.proto b/easytier/src/proto/cli.proto index 0eafeb2..f79bb6d 100644 --- a/easytier/src/proto/cli.proto +++ b/easytier/src/proto/cli.proto @@ -65,6 +65,8 @@ message Route { optional uint32 next_hop_peer_id_latency_first = 12; optional int32 cost_latency_first = 13; optional int32 path_latency_latency_first = 14; + + common.Ipv6Inet ipv6_addr = 15; } message PeerRoutePair { diff --git a/easytier/src/proto/common.proto b/easytier/src/proto/common.proto index bd6cef0..c6378e4 100644 --- a/easytier/src/proto/common.proto +++ b/easytier/src/proto/common.proto @@ -139,6 +139,11 @@ message Ipv4Inet { uint32 network_length = 2; } +message Ipv6Inet { + Ipv6Addr address = 1; + uint32 network_length = 2; +} + message Url { string url = 1; } message SocketAddr { diff --git a/easytier/src/proto/common.rs b/easytier/src/proto/common.rs index b97c60a..07d3d1c 100644 --- a/easytier/src/proto/common.rs +++ b/easytier/src/proto/common.rs @@ -131,6 +131,41 @@ impl FromStr for Ipv4Inet { } } +impl From for Ipv6Inet { + fn from(value: cidr::Ipv6Inet) -> Self { + Ipv6Inet { + address: Some(value.address().into()), + network_length: value.network_length() as u32, + } + } +} + +impl From 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 { + Ok(Ipv6Inet::from( + cidr::Ipv6Inet::from_str(s).with_context(|| "Failed to parse Ipv6Inet")?, + )) + } +} + impl From for Url { fn from(value: url::Url) -> Self { Url { diff --git a/easytier/src/proto/peer_rpc.proto b/easytier/src/proto/peer_rpc.proto index 0ac05ca..0bfb432 100644 --- a/easytier/src/proto/peer_rpc.proto +++ b/easytier/src/proto/peer_rpc.proto @@ -24,6 +24,7 @@ message RoutePeerInfo { uint32 network_length = 13; optional uint32 quic_port = 14; + optional common.Ipv6Inet ipv6_addr = 15; } message PeerIdVersion { diff --git a/easytier/src/tests/ipv6_test.rs b/easytier/src/tests/ipv6_test.rs new file mode 100644 index 0000000..d71dc07 --- /dev/null +++ b/easytier/src/tests/ipv6_test.rs @@ -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()); +} diff --git a/easytier/src/tests/mod.rs b/easytier/src/tests/mod.rs index 271dfed..ad5bb5a 100644 --- a/easytier/src/tests/mod.rs +++ b/easytier/src/tests/mod.rs @@ -1,6 +1,8 @@ #[cfg(target_os = "linux")] mod three_node; +mod ipv6_test; + use crate::common::PeerId; use crate::peers::peer_manager::PeerManager; diff --git a/easytier/src/tests/three_node.rs b/easytier/src/tests/three_node.rs index 9284fbc..1d5d6f8 100644 --- a/easytier/src/tests/three_node.rs +++ b/easytier/src/tests/three_node.rs @@ -51,11 +51,17 @@ pub fn prepare_linux_namespaces() { 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(); config.set_inst_name(inst_name.to_owned()); config.set_netns(ns.map(|s| s.to_owned())); config.set_ipv4(Some(ipv4.parse().unwrap())); + config.set_ipv6(Some(ipv6.parse().unwrap())); config.set_listeners(vec![ "tcp://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 TomlConfigLoader>( "inst1", Some("net_a"), "10.144.144.1", + "fd00::1/64", ))); let mut inst2 = Instance::new(cfg_cb(get_inst_config( "inst2", Some("net_b"), "10.144.144.2", + "fd00::2/64", ))); let mut inst3 = Instance::new(cfg_cb(get_inst_config( "inst3", Some("net_c"), "10.144.144.3", + "fd00::3/64", ))); inst1.run().await.unwrap(); @@ -232,6 +241,30 @@ async fn ping_test(from_netns: &str, target_ip: &str, payload_size: Option) -> 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] #[tokio::test] #[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, ); + // Test IPv4 connectivity wait_for_condition( || async { ping_test("net_c", "10.144.144.1", None).await }, Duration::from_secs(5000), ) .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; } @@ -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 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" { inst4 .get_conn_manager() @@ -627,16 +679,7 @@ pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str .iter() .find(|r| **r == inst4.peer_id()) .is_none(); - if !ret { - println!( - "conn info: {:?}", - insts[2] - .get_peer_manager() - .get_peer_map() - .list_peer_conns(inst4.peer_id()) - .await - ); - } + ret }, // 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() { 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 .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); 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 inst2 = Instance::new(get_inst_config("inst2", Some("net_c"), "10.144.145.2")); + let mut inst1 = Instance::new(get_inst_config( + "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(); 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); 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 .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); 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 .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); 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![]); 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_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) { 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 { center_node_config .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); } 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![]); 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(); inst1.run().await.unwrap(); diff --git a/easytier/src/tunnel/common.rs b/easytier/src/tunnel/common.rs index 34e230f..f0ea3b8 100644 --- a/easytier/src/tunnel/common.rs +++ b/easytier/src/tunnel/common.rs @@ -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 { tracing::trace!(dev_name = ?dev_name, "bind device"); socket2_socket.bind_device(Some(dev_name.as_bytes()))?; diff --git a/easytier/src/vpn_portal/wireguard.rs b/easytier/src/vpn_portal/wireguard.rs index 780e516..dca3ed0 100644 --- a/easytier/src/vpn_portal/wireguard.rs +++ b/easytier/src/vpn_portal/wireguard.rs @@ -1,5 +1,5 @@ use std::{ - net::{Ipv4Addr, SocketAddr}, + net::{IpAddr, Ipv4Addr, SocketAddr}, sync::Arc, }; @@ -128,7 +128,7 @@ impl WireGuardImpl { tracing::trace!(?i, "Received from wg client"); let dst = i.get_destination(); 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; } diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ad1125c --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ceb754e --- /dev/null +++ b/flake.nix @@ -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"; + }; + }); +}