From 8181830902db37591c03d3ddc4c86edad35ca324 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:31:53 +0000 Subject: [PATCH] feat: add DHCP subnet (CIDR) configuration parameter Allow -d to optionally accept a CIDR subnet (e.g. -d 10.0.0.0/24) to fix the DHCP address range. When specified, the DHCP allocator uses the configured subnet instead of deriving it from peer IPs. Changes: - Add dhcp_cidr field to config struct and ConfigLoader trait - Modify -d CLI arg to accept optional CIDR string value - Update DHCP logic to use configured CIDR as default subnet - Add dhcp_cidr to NetworkConfig proto message - Update launcher to handle dhcp_cidr in gen_config/new_from_config - Update i18n translations --- easytier/locales/app.yml | 4 ++-- easytier/src/common/config.rs | 16 ++++++++++++++++ easytier/src/core.rs | 17 ++++++++++++++--- easytier/src/instance/instance.rs | 12 ++++++++++-- easytier/src/launcher.rs | 6 ++++++ easytier/src/proto/api_manage.proto | 1 + 6 files changed, 49 insertions(+), 7 deletions(-) diff --git a/easytier/locales/app.yml b/easytier/locales/app.yml index cb4256ee..896c1e33 100644 --- a/easytier/locales/app.yml +++ b/easytier/locales/app.yml @@ -49,8 +49,8 @@ core_clap: en: "manually specify the public IPv6 subnet to share, instead of auto-detecting from system routes" zh-CN: "手动指定要共享的公网 IPv6 子网,不自动从系统路由检测" 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将自动更改。" + 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. Optionally specify a CIDR subnet (e.g. -d 10.0.0.0/24) to fix the DHCP address range." + zh-CN: "由Easytier自动确定并设置IP地址,默认从10.0.0.1开始。警告:在使用DHCP时,如果网络中出现IP冲突,IP将自动更改。可选指定CIDR子网(如 -d 10.0.0.0/24)来固定DHCP地址范围。" peers: en: "peers to connect initially" zh-CN: "最初要连接的对等节点" diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index 57cb8f0c..9cb51a21 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -185,6 +185,9 @@ pub trait ConfigLoader: Send + Sync { fn get_dhcp(&self) -> bool; fn set_dhcp(&self, dhcp: bool); + fn get_dhcp_cidr(&self) -> Option; + fn set_dhcp_cidr(&self, cidr: Option); + fn add_proxy_cidr( &self, cidr: cidr::Ipv4Cidr, @@ -535,6 +538,7 @@ struct Config { ipv6_public_addr_auto: Option, ipv6_public_addr_prefix: Option, dhcp: Option, + dhcp_cidr: Option, network_identity: Option, listeners: Option>, mapped_listeners: Option>, @@ -768,6 +772,18 @@ impl ConfigLoader for TomlConfigLoader { self.config.lock().unwrap().dhcp = Some(dhcp); } + fn get_dhcp_cidr(&self) -> Option { + let locked_config = self.config.lock().unwrap(); + locked_config + .dhcp_cidr + .as_ref() + .and_then(|s| s.parse().ok()) + } + + fn set_dhcp_cidr(&self, cidr: Option) { + self.config.lock().unwrap().dhcp_cidr = cidr.map(|c| c.to_string()); + } + fn add_proxy_cidr( &self, cidr: cidr::Ipv4Cidr, diff --git a/easytier/src/core.rs b/easytier/src/core.rs index ed014faa..da851b17 100644 --- a/easytier/src/core.rs +++ b/easytier/src/core.rs @@ -204,7 +204,7 @@ struct NetworkOptions { num_args = 0..=1, default_missing_value = "true" )] - dhcp: Option, + dhcp: Option, #[arg( short, @@ -907,8 +907,19 @@ impl NetworkOptions { cfg.set_network_identity(NetworkIdentity::new_credential(network_name)); } - if let Some(dhcp) = self.dhcp { - cfg.set_dhcp(dhcp); + if let Some(ref dhcp) = self.dhcp { + if dhcp == "true" || dhcp == "1" { + cfg.set_dhcp(true); + } else if dhcp == "false" || dhcp == "0" { + cfg.set_dhcp(false); + } else { + // Treat as CIDR, e.g. "10.0.0.0/24" + cfg.set_dhcp(true); + let cidr: cidr::Ipv4Cidr = dhcp.parse().with_context(|| { + format!("failed to parse dhcp cidr: {}", dhcp) + })?; + cfg.set_dhcp_cidr(Some(cidr)); + } } if let Some(ipv4) = &self.ipv4 { diff --git a/easytier/src/instance/instance.rs b/easytier/src/instance/instance.rs index d6c2f156..b7e3dee9 100644 --- a/easytier/src/instance/instance.rs +++ b/easytier/src/instance/instance.rs @@ -829,7 +829,11 @@ impl Instance { let nic_ctx = self.nic_ctx.clone(); let _peer_packet_receiver = self.peer_packet_receiver.clone(); tokio::spawn(async move { - let default_ipv4_addr = Ipv4Inet::new(Ipv4Addr::new(10, 126, 126, 0), 24).unwrap(); + let default_ipv4_addr = if let Some(dhcp_cidr) = global_ctx_c.config.get_dhcp_cidr() { + Ipv4Inet::new(dhcp_cidr.first_address(), dhcp_cidr.network_length()).unwrap() + } else { + Ipv4Inet::new(Ipv4Addr::new(10, 126, 126, 0), 24).unwrap() + }; let mut current_dhcp_ip: Option = None; let mut next_sleep_time = 0; let nic_closed_notifier = Arc::new(Notify::new()); @@ -864,7 +868,11 @@ impl Instance { used_ipv4.insert(peer_ipv4_addr.into()); } - let dhcp_inet = used_ipv4.iter().next().unwrap_or(&default_ipv4_addr); + let dhcp_inet = if global_ctx_c.config.get_dhcp_cidr().is_some() { + &default_ipv4_addr + } else { + used_ipv4.iter().next().unwrap_or(&default_ipv4_addr) + }; // if old ip is already in this subnet and not conflicted, use it if let Some(ip) = current_dhcp_ip && ip.network() == dhcp_inet.network() diff --git a/easytier/src/launcher.rs b/easytier/src/launcher.rs index 9b7d4f4a..bfd1164b 100644 --- a/easytier/src/launcher.rs +++ b/easytier/src/launcher.rs @@ -637,6 +637,11 @@ impl NetworkConfig { ); cfg.set_hostname(self.hostname.clone()); cfg.set_dhcp(self.dhcp.unwrap_or_default()); + if let Some(ref dhcp_cidr) = self.dhcp_cidr { + if let Ok(cidr) = dhcp_cidr.parse::() { + cfg.set_dhcp_cidr(Some(cidr)); + } + } cfg.set_inst_name(self.network_name.clone().unwrap_or_default()); // The web UI does not expose credential inputs directly, but imported/saved @@ -1019,6 +1024,7 @@ impl NetworkConfig { } result.dhcp = Some(config.get_dhcp()); + result.dhcp_cidr = config.get_dhcp_cidr().map(|c| c.to_string()); let network_identity = config.get_network_identity(); result.network_name = Some(network_identity.network_name.clone()); diff --git a/easytier/src/proto/api_manage.proto b/easytier/src/proto/api_manage.proto index 32a5796a..542d337e 100644 --- a/easytier/src/proto/api_manage.proto +++ b/easytier/src/proto/api_manage.proto @@ -102,6 +102,7 @@ message NetworkConfig { optional bool disable_relay_data = 65; optional bool enable_udp_broadcast_relay = 66; optional uint32 socket_mark = 67; + optional string dhcp_cidr = 68; } message PortForwardConfig {