refactor: update custom STUN server settings (#1310)

* refactor: update global context STUN server initialization

Modified global context initialization to use a single StunInfoCollector
instance with properly configured IPv4 and IPv6 servers instead of
creating separate instances.

feat: add IPv6 STUN server configuration support

Added interface methods and config struct fields to support both IPv4
and IPv6 STUN server configuration. Modified getter and setter methods
to handle Option<Vec<String>> type for both server types.

feat: enhance StunInfoCollector with IPv6 support

Updated StunInfoCollector to support both IPv4 and IPv6 STUN servers.
Added new constructor that accepts both server types and methods to set
them independently.

feat: add CLI argument for IPv6 STUN servers

Added command line argument support for configuring IPv6 STUN servers.
Updated configuration setup to handle both IPv4 and IPv6 STUN server
settings.

docs: add localization for STUN server configuration

Added English and Chinese localization strings for the new STUN server
configuration options, including both IPv4 and IPv6 variants.
This commit is contained in:
fanyang
2025-09-02 21:46:37 +08:00
committed by GitHub
parent 754439f03c
commit b87a05b457
10 changed files with 95 additions and 50 deletions

View File

@@ -208,6 +208,12 @@ core_clap:
enable_relay_foreign_network_kcp: enable_relay_foreign_network_kcp:
en: "if true, allow relay kcp packets from foreign network. default is false (not forward foreign network kcp packets)" en: "if true, allow relay kcp packets from foreign network. default is false (not forward foreign network kcp packets)"
zh-CN: "如果为true则作为共享节点时也可以转发其他网络的 KCP 数据包。默认值为false不转发" zh-CN: "如果为true则作为共享节点时也可以转发其他网络的 KCP 数据包。默认值为false不转发"
stun_servers:
en: "Override default STUN servers; If configured but empty, STUN servers are not used"
zh-CN: "覆盖内置的默认 STUN server 列表;如果设置了但是为空,则不使用 STUN servers如果没设置则使用默认 STUN server 列表"
stun_servers_v6:
en: "Override default STUN servers, IPv6; If configured but empty, IPv6 STUN servers are not used"
zh-CN: "覆盖内置的默认 IPv6 STUN server 列表;如果设置了但是为空,则不使用 IPv6 STUN servers如果没设置则使用默认 IPv6 STUN server 列表"
core_app: core_app:
panic_backtrace_save: panic_backtrace_save:

View File

@@ -200,8 +200,11 @@ pub trait ConfigLoader: Send + Sync {
fn get_udp_whitelist(&self) -> Vec<String>; fn get_udp_whitelist(&self) -> Vec<String>;
fn set_udp_whitelist(&self, whitelist: Vec<String>); fn set_udp_whitelist(&self, whitelist: Vec<String>);
fn get_stun_servers(&self) -> Vec<String>; fn get_stun_servers(&self) -> Option<Vec<String>>;
fn set_stun_servers(&self, servers: Vec<String>); fn set_stun_servers(&self, servers: Option<Vec<String>>);
fn get_stun_servers_v6(&self) -> Option<Vec<String>>;
fn set_stun_servers_v6(&self, servers: Option<Vec<String>>);
fn dump(&self) -> String; fn dump(&self) -> String;
} }
@@ -374,7 +377,7 @@ impl From<PortForwardConfig> for PortForwardConfigPb {
} }
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
struct Config { struct Config {
netns: Option<String>, netns: Option<String>,
hostname: Option<String>, hostname: Option<String>,
@@ -412,6 +415,7 @@ struct Config {
tcp_whitelist: Option<Vec<String>>, tcp_whitelist: Option<Vec<String>>,
udp_whitelist: Option<Vec<String>>, udp_whitelist: Option<Vec<String>>,
stun_servers: Option<Vec<String>>, stun_servers: Option<Vec<String>>,
stun_servers_v6: Option<Vec<String>>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -791,17 +795,20 @@ impl ConfigLoader for TomlConfigLoader {
self.config.lock().unwrap().udp_whitelist = Some(whitelist); self.config.lock().unwrap().udp_whitelist = Some(whitelist);
} }
fn get_stun_servers(&self) -> Vec<String> { fn get_stun_servers(&self) -> Option<Vec<String>> {
self.config self.config.lock().unwrap().stun_servers.clone()
.lock()
.unwrap()
.stun_servers
.clone()
.unwrap_or_default()
} }
fn set_stun_servers(&self, servers: Vec<String>) { fn set_stun_servers(&self, servers: Option<Vec<String>>) {
self.config.lock().unwrap().stun_servers = Some(servers); self.config.lock().unwrap().stun_servers = servers;
}
fn get_stun_servers_v6(&self) -> Option<Vec<String>> {
self.config.lock().unwrap().stun_servers_v6.clone()
}
fn set_stun_servers_v6(&self, servers: Option<Vec<String>>) {
self.config.lock().unwrap().stun_servers_v6 = servers;
} }
fn dump(&self) -> String { fn dump(&self) -> String {
@@ -838,14 +845,14 @@ pub mod tests {
fn test_stun_servers_config() { fn test_stun_servers_config() {
let config = TomlConfigLoader::default(); let config = TomlConfigLoader::default();
let stun_servers = config.get_stun_servers(); let stun_servers = config.get_stun_servers();
assert!(stun_servers.is_empty()); assert!(stun_servers.is_none());
// Test setting custom stun servers // Test setting custom stun servers
let custom_servers = vec!["txt:stun.easytier.cn".to_string()]; let custom_servers = vec!["txt:stun.easytier.cn".to_string()];
config.set_stun_servers(custom_servers.clone()); config.set_stun_servers(Some(custom_servers.clone()));
let retrieved_servers = config.get_stun_servers(); let retrieved_servers = config.get_stun_servers();
assert_eq!(retrieved_servers, custom_servers); assert_eq!(retrieved_servers.unwrap(), custom_servers);
} }
#[test] #[test]
@@ -859,7 +866,7 @@ stun_servers = [
]"#; ]"#;
let config = TomlConfigLoader::new_from_str(config_str).unwrap(); let config = TomlConfigLoader::new_from_str(config_str).unwrap();
let stun_servers = config.get_stun_servers(); let stun_servers = config.get_stun_servers().unwrap();
assert_eq!(stun_servers.len(), 3); assert_eq!(stun_servers.len(), 3);
assert_eq!(stun_servers[0], "stun.l.google.com:19302"); assert_eq!(stun_servers[0], "stun.l.google.com:19302");

View File

@@ -114,12 +114,21 @@ impl GlobalCtx {
let (event_bus, _) = tokio::sync::broadcast::channel(8); let (event_bus, _) = tokio::sync::broadcast::channel(8);
let stun_servers = config_fs.get_stun_servers(); let stun_info_collector = StunInfoCollector::new_with_default_servers();
let stun_info_collection = Arc::new(if stun_servers.is_empty() {
StunInfoCollector::new_with_default_servers() if let Some(stun_servers) = config_fs.get_stun_servers() {
stun_info_collector.set_stun_servers(stun_servers);
} else { } else {
StunInfoCollector::new(stun_servers) stun_info_collector.set_stun_servers(Vec::new());
}); }
if let Some(stun_servers) = config_fs.get_stun_servers_v6() {
stun_info_collector.set_stun_servers_v6(stun_servers);
} else {
stun_info_collector.set_stun_servers_v6(Vec::new());
}
let stun_info_collector = Arc::new(stun_info_collector);
let enable_exit_node = config_fs.get_flags().enable_exit_node || cfg!(target_env = "ohos"); let enable_exit_node = config_fs.get_flags().enable_exit_node || cfg!(target_env = "ohos");
let proxy_forward_by_system = config_fs.get_flags().proxy_forward_by_system; let proxy_forward_by_system = config_fs.get_flags().proxy_forward_by_system;
@@ -145,12 +154,12 @@ impl GlobalCtx {
ip_collector: Mutex::new(Some(Arc::new(IPCollector::new( ip_collector: Mutex::new(Some(Arc::new(IPCollector::new(
net_ns, net_ns,
stun_info_collection.clone(), stun_info_collector.clone(),
)))), )))),
hostname: Mutex::new(hostname), hostname: Mutex::new(hostname),
stun_info_collection: Mutex::new(stun_info_collection), stun_info_collection: Mutex::new(stun_info_collector),
running_listeners: Mutex::new(Vec::new()), running_listeners: Mutex::new(Vec::new()),

View File

@@ -71,12 +71,7 @@ impl IfConfiguerTrait for MacIfConfiger {
run_shell_cmd(format!("ifconfig {} inet delete", name).as_str()).await run_shell_cmd(format!("ifconfig {} inet delete", name).as_str()).await
} else { } else {
run_shell_cmd( run_shell_cmd(
format!( format!("ifconfig {} inet {} delete", name, ip.unwrap().address()).as_str(),
"ifconfig {} inet {} delete",
name,
ip.unwrap().address().to_string()
)
.as_str(),
) )
.await .await
} }

View File

@@ -65,7 +65,7 @@ impl InterfaceFilter {
async fn is_interface_physical(&self) -> bool { async fn is_interface_physical(&self) -> bool {
let interface_name = &self.iface.name; let interface_name = &self.iface.name;
let output = tokio::process::Command::new("networksetup") let output = tokio::process::Command::new("networksetup")
.args(&["-listallhardwareports"]) .args(["-listallhardwareports"])
.output() .output()
.await .await
.expect("Failed to execute command"); .expect("Failed to execute command");
@@ -79,11 +79,7 @@ impl InterfaceFilter {
if line.contains("Device:") && line.contains(interface_name) { if line.contains("Device:") && line.contains(interface_name) {
let next_line = lines[i + 1]; let next_line = lines[i + 1];
if next_line.contains("Virtual Interface") { return !next_line.contains("Virtual Interface");
return false;
} else {
return true;
}
} }
} }

View File

@@ -718,10 +718,10 @@ impl StunInfoCollectorTrait for StunInfoCollector {
} }
impl StunInfoCollector { impl StunInfoCollector {
pub fn new(stun_servers: Vec<String>) -> Self { pub fn new(stun_servers: Vec<String>, stun_servers_v6: Vec<String>) -> Self {
Self { Self {
stun_servers: Arc::new(RwLock::new(stun_servers)), stun_servers: Arc::new(RwLock::new(stun_servers)),
stun_servers_v6: Arc::new(RwLock::new(Self::get_default_servers_v6())), stun_servers_v6: Arc::new(RwLock::new(stun_servers_v6)),
udp_nat_test_result: Arc::new(RwLock::new(None)), udp_nat_test_result: Arc::new(RwLock::new(None)),
public_ipv6: Arc::new(AtomicCell::new(None)), public_ipv6: Arc::new(AtomicCell::new(None)),
nat_test_result_time: Arc::new(AtomicCell::new(Local::now())), nat_test_result_time: Arc::new(AtomicCell::new(Local::now())),
@@ -732,7 +732,17 @@ impl StunInfoCollector {
} }
pub fn new_with_default_servers() -> Self { pub fn new_with_default_servers() -> Self {
Self::new(Self::get_default_servers()) Self::new(Self::get_default_servers(), Self::get_default_servers_v6())
}
pub fn set_stun_servers(&self, stun_servers: Vec<String>) {
let mut g = self.stun_servers.write().unwrap();
*g = stun_servers;
}
pub fn set_stun_servers_v6(&self, stun_servers_v6: Vec<String>) {
let mut g = self.stun_servers_v6.write().unwrap();
*g = stun_servers_v6;
} }
pub fn get_default_servers() -> Vec<String> { pub fn get_default_servers() -> Vec<String> {

View File

@@ -25,7 +25,7 @@ use easytier::{
constants::EASYTIER_VERSION, constants::EASYTIER_VERSION,
global_ctx::GlobalCtx, global_ctx::GlobalCtx,
set_default_machine_id, set_default_machine_id,
stun::MockStunInfoCollector, stun::{MockStunInfoCollector, StunInfoCollector},
}, },
connector::create_connector_by_url, connector::create_connector_by_url,
instance_manager::NetworkInstanceManager, instance_manager::NetworkInstanceManager,
@@ -572,6 +572,15 @@ struct NetworkOptions {
num_args = 0.. num_args = 0..
)] )]
stun_servers: Option<Vec<String>>, stun_servers: Option<Vec<String>>,
#[arg(
long,
env = "ET_STUN_SERVERS_V6",
value_delimiter = ',',
help = t!("core_clap.stun_servers_v6").to_string(),
num_args = 0..
)]
stun_servers_v6: Option<Vec<String>>,
} }
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@@ -943,7 +952,23 @@ impl NetworkOptions {
cfg.set_udp_whitelist(old_udp_whitelist); cfg.set_udp_whitelist(old_udp_whitelist);
if let Some(stun_servers) = &self.stun_servers { if let Some(stun_servers) = &self.stun_servers {
cfg.set_stun_servers(stun_servers.clone()); if stun_servers.is_empty() {
cfg.set_stun_servers(None);
} else {
cfg.set_stun_servers(Some(stun_servers.clone()));
}
} else {
cfg.set_stun_servers(Some(StunInfoCollector::get_default_servers()));
}
if let Some(stun_servers) = &self.stun_servers_v6 {
if stun_servers.is_empty() {
cfg.set_stun_servers_v6(None);
} else {
cfg.set_stun_servers_v6(Some(stun_servers.clone()));
}
} else {
cfg.set_stun_servers_v6(Some(StunInfoCollector::get_default_servers_v6()));
} }
Ok(()) Ok(())

View File

@@ -457,7 +457,7 @@ impl KcpProxyDst {
global_ctx: ArcGlobalCtx, global_ctx: ArcGlobalCtx,
proxy_entries: Arc<DashMap<ConnId, TcpProxyEntry>>, proxy_entries: Arc<DashMap<ConnId, TcpProxyEntry>>,
cidr_set: Arc<CidrSet>, cidr_set: Arc<CidrSet>,
route: Arc<(dyn crate::peers::route_trait::Route + Send + Sync + 'static)>, route: Arc<dyn crate::peers::route_trait::Route + Send + Sync + 'static>,
) -> Result<()> { ) -> Result<()> {
let mut conn_data = kcp_stream.conn_data().clone(); let mut conn_data = kcp_stream.conn_data().clone();
let parsed_conn_data = KcpConnData::decode(&mut conn_data) let parsed_conn_data = KcpConnData::decode(&mut conn_data)

View File

@@ -252,13 +252,13 @@ pub struct QUICProxyDst {
endpoint: Arc<quinn::Endpoint>, endpoint: Arc<quinn::Endpoint>,
proxy_entries: Arc<DashMap<SocketAddr, TcpProxyEntry>>, proxy_entries: Arc<DashMap<SocketAddr, TcpProxyEntry>>,
tasks: Arc<Mutex<JoinSet<()>>>, tasks: Arc<Mutex<JoinSet<()>>>,
route: Arc<(dyn crate::peers::route_trait::Route + Send + Sync + 'static)>, route: Arc<dyn crate::peers::route_trait::Route + Send + Sync + 'static>,
} }
impl QUICProxyDst { impl QUICProxyDst {
pub fn new( pub fn new(
global_ctx: ArcGlobalCtx, global_ctx: ArcGlobalCtx,
route: Arc<(dyn crate::peers::route_trait::Route + Send + Sync + 'static)>, route: Arc<dyn crate::peers::route_trait::Route + Send + Sync + 'static>,
) -> Result<Self> { ) -> Result<Self> {
let _g = global_ctx.net_ns.guard(); let _g = global_ctx.net_ns.guard();
let (endpoint, _) = make_server_endpoint("0.0.0.0:0".parse().unwrap()) let (endpoint, _) = make_server_endpoint("0.0.0.0:0".parse().unwrap())
@@ -324,7 +324,7 @@ impl QUICProxyDst {
ctx: Arc<GlobalCtx>, ctx: Arc<GlobalCtx>,
cidr_set: Arc<CidrSet>, cidr_set: Arc<CidrSet>,
proxy_entries: Arc<DashMap<SocketAddr, TcpProxyEntry>>, proxy_entries: Arc<DashMap<SocketAddr, TcpProxyEntry>>,
route: Arc<(dyn crate::peers::route_trait::Route + Send + Sync + 'static)>, route: Arc<dyn crate::peers::route_trait::Route + Send + Sync + 'static>,
) { ) {
let remote_addr = conn.remote_address(); let remote_addr = conn.remote_address();
defer!( defer!(
@@ -368,7 +368,7 @@ impl QUICProxyDst {
cidr_set: Arc<CidrSet>, cidr_set: Arc<CidrSet>,
proxy_entry_key: SocketAddr, proxy_entry_key: SocketAddr,
proxy_entries: Arc<DashMap<SocketAddr, TcpProxyEntry>>, proxy_entries: Arc<DashMap<SocketAddr, TcpProxyEntry>>,
route: Arc<(dyn crate::peers::route_trait::Route + Send + Sync + 'static)>, route: Arc<dyn crate::peers::route_trait::Route + Send + Sync + 'static>,
) -> Result<(QUICStream, TcpStream, ProxyAclHandler)> { ) -> Result<(QUICStream, TcpStream, ProxyAclHandler)> {
let conn = incoming.await.with_context(|| "accept failed")?; let conn = incoming.await.with_context(|| "accept failed")?;
let addr = conn.remote_address(); let addr = conn.remote_address();

View File

@@ -127,10 +127,7 @@ impl PacketProtocol {
match self { match self {
PacketProtocol::IPv4 => Ok(libc::PF_INET as u16), PacketProtocol::IPv4 => Ok(libc::PF_INET as u16),
PacketProtocol::IPv6 => Ok(libc::PF_INET6 as u16), PacketProtocol::IPv6 => Ok(libc::PF_INET6 as u16),
PacketProtocol::Other(_) => Err(io::Error::new( PacketProtocol::Other(_) => Err(io::Error::other("neither an IPv4 nor IPv6 packet")),
io::ErrorKind::Other,
"neither an IPv4 nor IPv6 packet",
)),
} }
} }
@@ -904,7 +901,7 @@ impl NicCtx {
// remove the 10.0.0.0/24 route (which is added by rust-tun by default) // remove the 10.0.0.0/24 route (which is added by rust-tun by default)
let _ = nic let _ = nic
.ifcfg .ifcfg
.remove_ipv4_route(&nic.ifname(), "10.0.0.0".parse().unwrap(), 24) .remove_ipv4_route(nic.ifname(), "10.0.0.0".parse().unwrap(), 24)
.await; .await;
} }