mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 10:14:35 +00:00
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:
@@ -64,6 +64,9 @@ pub trait ConfigLoader: Send + Sync {
|
||||
fn get_ipv4(&self) -> 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 set_dhcp(&self, dhcp: bool);
|
||||
|
||||
@@ -259,6 +262,7 @@ struct Config {
|
||||
instance_name: Option<String>,
|
||||
instance_id: Option<uuid::Uuid>,
|
||||
ipv4: Option<String>,
|
||||
ipv6: Option<String>,
|
||||
dhcp: Option<bool>,
|
||||
network_identity: Option<NetworkIdentity>,
|
||||
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 {
|
||||
self.config.lock().unwrap().dhcp.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ pub struct GlobalCtx {
|
||||
event_bus: EventBus,
|
||||
|
||||
cached_ipv4: AtomicCell<Option<cidr::Ipv4Inet>>,
|
||||
cached_ipv6: AtomicCell<Option<cidr::Ipv6Inet>>,
|
||||
cached_proxy_cidrs: AtomicCell<Option<Vec<ProxyNetworkConfig>>>,
|
||||
|
||||
ip_collector: Mutex<Option<Arc<IPCollector>>>,
|
||||
@@ -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<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 {
|
||||
self.config.get_id()
|
||||
}
|
||||
|
||||
@@ -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<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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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> {
|
||||
Ok(())
|
||||
}
|
||||
async fn remove_ip(&self, _name: &str, _ip: Option<Ipv4Addr>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn remove_ipv6(&self, _name: &str, _ip: Option<Ipv6Addr>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn wait_interface_show(&self, _name: &str) -> Result<(), Error> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -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>>(
|
||||
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>(
|
||||
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)]
|
||||
|
||||
@@ -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<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;
|
||||
|
||||
Reference in New Issue
Block a user