Feat/pseudo dhcp (#109)

*  feat: pseudo dhcp
This commit is contained in:
m1m1sha
2024-05-17 23:16:56 +08:00
committed by GitHub
parent bad6a5946a
commit 0ead308392
14 changed files with 314 additions and 113 deletions
+19 -3
View File
@@ -24,7 +24,10 @@ pub trait ConfigLoader: Send + Sync {
fn set_netns(&self, ns: Option<String>);
fn get_ipv4(&self) -> Option<std::net::Ipv4Addr>;
fn set_ipv4(&self, addr: std::net::Ipv4Addr);
fn set_ipv4(&self, addr: Option<std::net::Ipv4Addr>);
fn get_dhcp(&self) -> bool;
fn set_dhcp(&self, dhcp: bool);
fn add_proxy_cidr(&self, cidr: cidr::IpCidr);
fn remove_proxy_cidr(&self, cidr: cidr::IpCidr);
@@ -161,6 +164,7 @@ struct Config {
instance_name: Option<String>,
instance_id: Option<uuid::Uuid>,
ipv4: Option<String>,
dhcp: Option<bool>,
network_identity: Option<NetworkIdentity>,
listeners: Option<Vec<url::Url>>,
@@ -280,8 +284,20 @@ impl ConfigLoader for TomlConfigLoader {
.flatten()
}
fn set_ipv4(&self, addr: std::net::Ipv4Addr) {
self.config.lock().unwrap().ipv4 = Some(addr.to_string());
fn set_ipv4(&self, addr: Option<std::net::Ipv4Addr>) {
self.config.lock().unwrap().ipv4 = if let Some(addr) = addr {
Some(addr.to_string())
} else {
None
};
}
fn get_dhcp(&self) -> bool {
self.config.lock().unwrap().dhcp.unwrap_or_default()
}
fn set_dhcp(&self, dhcp: bool) {
self.config.lock().unwrap().dhcp = Some(dhcp);
}
fn add_proxy_cidr(&self, cidr: cidr::IpCidr) {
+4 -1
View File
@@ -36,6 +36,9 @@ pub enum GlobalCtxEvent {
VpnPortalClientConnected(String, String), // (portal, client ip)
VpnPortalClientDisconnected(String, String), // (portal, client ip)
DhcpIpv4Changed(Option<std::net::Ipv4Addr>, Option<std::net::Ipv4Addr>), // (old, new)
DhcpIpv4Conflicted(Option<std::net::Ipv4Addr>),
}
type EventBus = tokio::sync::broadcast::Sender<GlobalCtxEvent>;
@@ -127,7 +130,7 @@ impl GlobalCtx {
return addr;
}
pub fn set_ipv4(&mut self, addr: std::net::Ipv4Addr) {
pub fn set_ipv4(&self, addr: Option<std::net::Ipv4Addr>) {
self.config.set_ipv4(addr);
self.cached_ipv4.store(None);
}
+25 -6
View File
@@ -71,6 +71,13 @@ struct Cli {
)]
ipv4: Option<String>,
#[arg(
short,
long,
help = "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."
)]
dhcp: bool,
#[arg(short, long, help = "peers to connect initially", num_args = 0..)]
peers: Vec<String>,
@@ -271,12 +278,16 @@ impl From<Cli> for TomlConfigLoader {
cli.network_secret.clone(),
));
if let Some(ipv4) = &cli.ipv4 {
cfg.set_ipv4(
ipv4.parse()
.with_context(|| format!("failed to parse ipv4 address: {}", ipv4))
.unwrap(),
)
cfg.set_dhcp(cli.dhcp);
if !cli.dhcp {
if let Some(ipv4) = &cli.ipv4 {
cfg.set_ipv4(Some(
ipv4.parse()
.with_context(|| format!("failed to parse ipv4 address: {}", ipv4))
.unwrap(),
))
}
}
cfg.set_peers(
@@ -503,6 +514,14 @@ pub async fn async_main(cli: Cli) {
portal, client_addr
));
}
GlobalCtxEvent::DhcpIpv4Changed(old, new) => {
print_event(format!("dhcp ip changed. old: {:?}, new: {:?}", old, new));
}
GlobalCtxEvent::DhcpIpv4Conflicted(ip) => {
print_event(format!("dhcp ip conflict. ip: {:?}", ip));
}
}
}
});
+117 -1
View File
@@ -1,10 +1,12 @@
use std::borrow::BorrowMut;
use std::collections::HashSet;
use std::net::Ipv4Addr;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Weak};
use anyhow::Context;
use cidr::Ipv4Inet;
use futures::{SinkExt, StreamExt};
use pnet::packet::ipv4::Ipv4Packet;
@@ -285,6 +287,116 @@ impl Instance {
Ok(())
}
// Warning, if there is an IP conflict in the network when using DHCP, the IP will be automatically changed.
fn check_dhcp_ip_conflict(&self) {
use rand::Rng;
let peer_manager_c = self.peer_manager.clone();
let global_ctx_c = self.get_global_ctx();
let nic_c = self.virtual_nic.as_ref().unwrap().clone();
tokio::spawn(async move {
let default_ipv4_addr = Ipv4Addr::new(10, 0, 0, 0);
let mut dhcp_ip: Option<Ipv4Inet> = None;
let mut tries = 6;
loop {
let mut ipv4_addr: Option<Ipv4Inet> = None;
let mut unique_ipv4 = HashSet::new();
for i in 0..tries {
if dhcp_ip.is_none() {
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
for route in peer_manager_c.list_routes().await {
if !route.ipv4_addr.is_empty() {
if let Ok(ip) = Ipv4Inet::new(
if let Ok(ipv4) = route.ipv4_addr.parse::<Ipv4Addr>() {
ipv4
} else {
default_ipv4_addr
},
24,
) {
unique_ipv4.insert(ip);
}
}
}
if i == tries - 1 && unique_ipv4.is_empty() {
unique_ipv4.insert(Ipv4Inet::new(default_ipv4_addr, 24).unwrap());
}
if let Some(ip) = dhcp_ip {
if !unique_ipv4.contains(&ip) {
ipv4_addr = dhcp_ip;
break;
}
}
for net in unique_ipv4.iter().map(|inet| inet.network()).take(1) {
if let Some(ip) = net.iter().find(|ip| {
ip.address() != net.first_address()
&& ip.address() != net.last_address()
&& !unique_ipv4.contains(ip)
}) {
ipv4_addr = Some(ip);
}
}
}
if dhcp_ip != ipv4_addr {
let last_ip = dhcp_ip.map(|p| p.address());
tracing::debug!("last_ip: {:?}", last_ip);
let _ = nic_c.remove_ip(last_ip).await;
#[cfg(target_os = "macos")]
if last_ip.is_some() {
let _g = global_ctx_c.net_ns.guard();
let ret = nic_c
.get_ifcfg()
.remove_ipv4_route(nic_c.ifname(), last_ip.unwrap(), 24)
.await;
if ret.is_err() {
tracing::trace!(
cidr = 24,
err = ?ret,
"remove route failed.",
);
}
}
if let Some(ip) = ipv4_addr {
let _ = nic_c.link_up().await;
dhcp_ip = Some(ip);
tries = 1;
if let Err(e) = nic_c.add_ip(ip.address(), 24).await {
tracing::error!("add ip failed: {:?}", e);
global_ctx_c.set_ipv4(None);
let sleep: u64 = rand::thread_rng().gen_range(200..500);
tokio::time::sleep(std::time::Duration::from_millis(sleep)).await;
continue;
}
#[cfg(target_os = "macos")]
let _ = nic_c.add_route(ip.address(), 24).await;
global_ctx_c.set_ipv4(Some(ip.address()));
global_ctx_c.issue_event(GlobalCtxEvent::DhcpIpv4Changed(
last_ip,
Some(ip.address()),
));
} else {
global_ctx_c.set_ipv4(None);
global_ctx_c.issue_event(GlobalCtxEvent::DhcpIpv4Conflicted(last_ip));
dhcp_ip = None;
tries = 6;
}
}
let sleep: u64 = rand::thread_rng().gen_range(5..10);
tokio::time::sleep(std::time::Duration::from_secs(sleep)).await;
}
});
}
pub async fn run(&mut self) -> Result<(), Error> {
self.listener_manager
.lock()
@@ -294,7 +406,11 @@ impl Instance {
self.listener_manager.lock().await.run().await?;
self.peer_manager.run().await?;
if let Some(ipv4_addr) = self.global_ctx.get_ipv4() {
if self.global_ctx.config.get_dhcp() {
self.prepare_tun_device().await?;
self.run_proxy_cidrs_route_updater();
self.check_dhcp_ip_conflict();
} else if let Some(ipv4_addr) = self.global_ctx.get_ipv4() {
self.prepare_tun_device().await?;
self.assign_ipv4_to_tun_device(ipv4_addr).await?;
self.run_proxy_cidrs_route_updater();
+1 -1
View File
@@ -48,7 +48,7 @@ pub fn get_inst_config(inst_name: &str, ns: Option<&str>, ipv4: &str) -> TomlCon
let config = TomlConfigLoader::default();
config.set_inst_name(inst_name.to_owned());
config.set_netns(ns.map(|s| s.to_owned()));
config.set_ipv4(ipv4.parse().unwrap());
config.set_ipv4(Some(ipv4.parse().unwrap()));
config.set_listeners(vec![
"tcp://0.0.0.0:11010".parse().unwrap(),
"udp://0.0.0.0:11010".parse().unwrap(),