diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..c8535d9
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "switch/p2p_channel"]
+ path = switch/p2p_channel
+ url = git@github.com:lbl8603/p2p_channel.git
diff --git a/README.md b/README.md
index 3bb61de..e3a7589 100644
--- a/README.md
+++ b/README.md
@@ -6,31 +6,59 @@
### 示例:
-- 在一台mac设备上运行,获取到ip 10.13.0.2:
+1. 指定一个token,在多台设备上运行该程序,例如:
+ ```shell
+ # linux上
+ root@DESKTOP-0BCHNIO:/opt# ./switch-desktop start --token 123456
+ # 在另一台linux上使用nohup后台运行,不在命令行指定配置时,将在home/.switch/config文件中读取配置
+ [root@izj6cemne76ykdzkataftfz switch]# nohup ./switch-desktop start &
+ # windows上
+ D:\switch\bin_v1>switch-desktop.exe start --token 123456
+ ```
+2. 可以执行status命令查看当前设备的虚拟ip
+ ```shell
+ root@DESKTOP-0BCHNIO:/opt# ./switch-desktop status
+ Name: Ubuntu 18.04 (bionic) [64-bit]
+ Virtual ip: 10.26.0.2
+ Virtual gateway: 10.26.0.1
+ Virtual netmask: 255.255.255.0
+ Connection status: Connected
+ NAT type: Cone
+ Relay server: 43.139.56.10:29871
+ Public ips: 120.228.76.75
+ Local ip: 172.25.165.58
+ ```
+3. 也可以执行list命令查看其他设备的虚拟ip
+ ```shell
+ root@DESKTOP-0BCHNIO:/opt# ./switch-desktop list
+ Name Virtual Ip P2P/Relay Rt Status
+ Windows 10.0.22621 (Windows 11 Professional) [64-bit] 10.26.0.3 p2p 2 Online
+ CentOS 7.9.2009 (Core) [64-bit] 10.26.0.4 p2p 35 Online
+ ```
+4. 最后可以用虚拟ip实现设备间相互访问
+ 1. ping
-
+
+ 2. ssh
+
+
-- 在另一台windows上运行,获取到ip 10.13.0.3:
+### 更多玩法
- 
+1. 和远程桌面(如mstsc)搭配,超低延迟的体验
+2. 安装samba服务,共享磁盘
+3. 搭配公网服务器nginx反向代理,在公网访问本地文件
-- 此时这两个不同局域网的设备之间就能用ip相互访问了
-
-
-
-- 输入"list"查看其他已连接的设备(p2p表示NAT打洞成功,relay表示使用服务器中继转发),"status"查看当前设备状态
-
-
-
-- token的作用是标识一个虚拟局域网,当使用公共服务器时,建议使用一个唯一值当token(比如uuid),否则有可能连接到其他人创建的虚拟局域网中
-- 公共服务器目前的配置是2核4G 4Mbps,有需要再扩展~
### 使用须知
+- token的作用是标识一个虚拟局域网,当使用公共服务器时,建议使用一个唯一值当token(比如uuid),否则有可能连接到其他人创建的虚拟局域网中
+- 建议指定deviceId,默认使用MAC地址,在某些环境下可能发生变化
+- 公共服务器目前的配置是2核4G 4Mbps,有需要再扩展~
- 需要root/管理员权限
- 使用命令行运行
- Mac和Linux下需要加可执行权限(例如:chmod +x ./switch-macos)
-- 暂时固定使用的10.13.0.1/24网段,需要避免和已有的路由冲突
-## 编译
+- 自己搭注册和中继服务器(https://github.com/lbl8603/switch-server)
+### 编译
前提条件:安装rust编译环境(https://www.rust-lang.org/zh-CN/tools/install)
到项目根目录下执行 cargo build -p switch-desktop
@@ -47,9 +75,9 @@
- NAT穿透
- 点对点穿透
- 服务端中继转发
-- Windows后台服务
+ - 客户端中继转发
### Todo
- 支持安卓
- 数据加密
-- 客户端中继转发
+
diff --git a/documents/img/cmd_list.jpg b/documents/img/cmd_list.jpg
new file mode 100644
index 0000000..dcdc038
Binary files /dev/null and b/documents/img/cmd_list.jpg differ
diff --git a/documents/img/cmd_status.jpg b/documents/img/cmd_status.jpg
new file mode 100644
index 0000000..81f8351
Binary files /dev/null and b/documents/img/cmd_status.jpg differ
diff --git a/documents/img/ping.jpg b/documents/img/ping.jpg
new file mode 100644
index 0000000..a4c1caa
Binary files /dev/null and b/documents/img/ping.jpg differ
diff --git a/documents/img/ssh.jpg b/documents/img/ssh.jpg
new file mode 100644
index 0000000..ea3dde1
Binary files /dev/null and b/documents/img/ssh.jpg differ
diff --git a/switch-desktop/Cargo.toml b/switch-desktop/Cargo.toml
index 5ceebd1..bf154e5 100644
--- a/switch-desktop/Cargo.toml
+++ b/switch-desktop/Cargo.toml
@@ -13,17 +13,19 @@ console = "0.15.2"
dirs = "4.0.0"
log = "0.4.17"
log4rs = "1.2.0"
-tokio = { version = "1.24.1", features = ["full"] }
+#tokio = { version = "1.24.1", features = ["full"] }
chrono = "0.4.23"
serde = "1.0"
serde_yaml = "0.9"
+serde_json = "1.0.94"
crossbeam = "0.8.2"
lazy_static = "1.4.0"
parking_lot = "0.12.1"
-#parity-tokio-ipc = "0.9.0"
-futures = "0.3"
+fs2 = "0.4.3"
+
+os_info = "3.5.1"
[target.'cfg(any(target_os = "linux",target_os = "macos"))'.dependencies]
sudo = "0.6.0"
diff --git a/switch-desktop/src/command/client.rs b/switch-desktop/src/command/client.rs
index b570b7a..765588c 100644
--- a/switch-desktop/src/command/client.rs
+++ b/switch-desktop/src/command/client.rs
@@ -2,15 +2,17 @@ use std::io;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
use std::time::Duration;
+use crate::command::entity::{DeviceItem, RouteItem, Status};
+
pub struct CommandClient {
udp: UdpSocket,
}
impl CommandClient {
pub fn new() -> io::Result {
- let port = crate::config::read_command_port().unwrap();
+ let port = crate::config::read_command_port()?;
let udp = UdpSocket::bind("127.0.0.1:0")?;
- udp.set_read_timeout(Some(Duration::from_secs(5)))?;
+ udp.set_read_timeout(Some(Duration::from_secs(2)))?;
udp.connect(SocketAddr::V4(SocketAddrV4::new(
Ipv4Addr::new(127, 0, 0, 1),
port,
@@ -20,16 +22,53 @@ impl CommandClient {
}
impl CommandClient {
- pub fn list(&self) -> io::Result {
+ pub fn list(&self) -> io::Result> {
self.udp.send(b"list")?;
let mut buf = [0; 10240];
let len = self.udp.recv(&mut buf)?;
- Ok(String::from_utf8(buf[..len].to_vec()).unwrap())
+ match serde_json::from_slice::>(&buf[..len]) {
+ Ok(val) => {
+ Ok(val)
+ }
+ Err(e) => {
+ log::error!("{:?}",e);
+ Err(io::Error::new(io::ErrorKind::Other, "data error"))
+ }
+ }
}
- pub fn status(&self) -> io::Result {
+ pub fn route(&self) -> io::Result> {
+ self.udp.send(b"route")?;
+ let mut buf = [0; 10240];
+ let len = self.udp.recv(&mut buf)?;
+ match serde_json::from_slice::>(&buf[..len]) {
+ Ok(val) => {
+ Ok(val)
+ }
+ Err(e) => {
+ log::error!("{:?}",e);
+ Err(io::Error::new(io::ErrorKind::Other, "data error"))
+ }
+ }
+ }
+ pub fn status(&self) -> io::Result {
self.udp.send(b"status")?;
let mut buf = [0; 10240];
let len = self.udp.recv(&mut buf)?;
+ match serde_json::from_slice::(&buf[..len]) {
+ Ok(val) => {
+ Ok(val)
+ }
+ Err(e) => {
+ log::error!("{:?},{:?}",&buf[..len],e);
+ Err(io::Error::new(io::ErrorKind::Other, "data error"))
+ }
+ }
+ }
+ #[cfg(any(unix))]
+ pub fn stop(&self) -> io::Result {
+ self.udp.send(b"stop")?;
+ let mut buf = [0; 10240];
+ let len = self.udp.recv(&mut buf)?;
Ok(String::from_utf8(buf[..len].to_vec()).unwrap())
}
}
diff --git a/switch-desktop/src/command/entity.rs b/switch-desktop/src/command/entity.rs
new file mode 100644
index 0000000..2f7c373
--- /dev/null
+++ b/switch-desktop/src/command/entity.rs
@@ -0,0 +1,34 @@
+use serde::{Deserialize, Serialize};
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Status {
+ pub name: String,
+ pub virtual_ip: String,
+ pub virtual_gateway: String,
+ pub virtual_netmask: String,
+ pub connect_status: String,
+ pub relay_server: String,
+ pub nat_type: String,
+ pub public_ips: String,
+ pub local_ip: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct RouteItem {
+ pub destination: String,
+ pub next_hop: String,
+ pub metric: String,
+ pub rt: String,
+ pub interface: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct DeviceItem {
+ pub name: String,
+ pub virtual_ip: String,
+ pub nat_type: String,
+ pub public_ips: String,
+ pub local_ip: String,
+ pub nat_traversal_type: String,
+ pub rt: String,
+ pub status: String,
+}
\ No newline at end of file
diff --git a/switch-desktop/src/command/mod.rs b/switch-desktop/src/command/mod.rs
index c07f47e..ab99755 100644
--- a/switch-desktop/src/command/mod.rs
+++ b/switch-desktop/src/command/mod.rs
@@ -1,2 +1,59 @@
+use std::io;
+use console::style;
+use crate::console_out;
+
pub mod client;
pub mod server;
+pub mod entity;
+
+pub enum CommandEnum {
+ Route,
+ List,
+ ListAll,
+ Status,
+ #[cfg(any(unix))]
+ Stop,
+}
+
+pub fn command(cmd: CommandEnum) {
+ if let Err(e) = command_(cmd) {
+ println!("{}:{:?}", style("连接后台服务错误(Connection background service error)").red(), e);
+ }
+}
+
+fn command_(cmd: CommandEnum) -> io::Result<()> {
+ match client::CommandClient::new() {
+ Ok(command_client) => {
+ match cmd {
+ CommandEnum::Route => {
+ let list = command_client.route()?;
+ console_out::console_route_table(list);
+ }
+ CommandEnum::List => {
+ let list = command_client.list()?;
+ console_out::console_device_list(list);
+ }
+ CommandEnum::ListAll => {
+ let list = command_client.list()?;
+ console_out::console_device_list_all(list);
+ }
+ CommandEnum::Status => {
+ let status = command_client.status()?;
+ console_out::console_status(status);
+ }
+ #[cfg(any(unix))]
+ CommandEnum::Stop => {
+ command_client.stop()?;
+ }
+ }
+ }
+ Err(e) => {
+ log::error!("{:?}",e);
+ println!(
+ "{}:{:?}",
+ style("连接后台服务错误(Connection background service error)").red(), e
+ );
+ }
+ };
+ Ok(())
+}
diff --git a/switch-desktop/src/command/server.rs b/switch-desktop/src/command/server.rs
index 0be90a0..3791585 100644
--- a/switch-desktop/src/command/server.rs
+++ b/switch-desktop/src/command/server.rs
@@ -2,10 +2,9 @@ use std::io;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket};
use std::sync::Arc;
-use console::style;
+use switch::core::Switch;
+use crate::command::entity::{DeviceItem, RouteItem, Status};
-use switch::handle::{PeerDeviceStatus, RouteType};
-use switch::Switch;
pub struct CommandServer {}
@@ -54,111 +53,137 @@ impl CommandServer {
}
}
+pub fn command_route(switch: &Switch) -> Vec {
+ let route_table = switch.route_table();
+ let mut route_list = Vec::with_capacity(route_table.len());
+ for (destination, route) in route_table {
+ let next_hop = switch.route_key(&route.route_key()).map_or(String::new(), |v| v.to_string());
+ let metric = route.metric.to_string();
+ let rt = if route.rt < 0 {
+ "".to_string()
+ } else {
+ route.rt.to_string()
+ };
+ let interface = route.addr.to_string();
+ let item = RouteItem {
+ destination: destination.to_string(),
+ next_hop,
+ metric,
+ rt,
+ interface,
+ };
+ route_list.push(item);
+ }
+ route_list
+}
+
+pub fn command_list(switch: &Switch) -> Vec {
+ let device_list = switch.device_list();
+ let mut list = Vec::new();
+ for peer in device_list {
+ let name = peer.name;
+ let virtual_ip = peer.virtual_ip.to_string();
+ let (nat_type, public_ips, local_ip) = if let Some(nat_info) = switch.peer_nat_info(&peer.virtual_ip) {
+ let nat_type = format!("{:?}", nat_info.nat_type);
+ let public_ips: Vec = nat_info.public_ips.iter().map(|v| v.to_string()).collect();
+ let public_ips = public_ips.join(",");
+ let local_ip = nat_info.local_ip.to_string();
+ (nat_type, public_ips, local_ip)
+ } else {
+ ("".to_string(), "".to_string(), "".to_string())
+ };
+ let (nat_traversal_type, rt) = if let Some(route) = switch.route(&peer.virtual_ip) {
+ let nat_traversal_type = if route.metric == 1 { "p2p" } else { "relay" }.to_string();
+ let rt = if route.rt < 0 {
+ "".to_string()
+ } else {
+ route.rt.to_string()
+ };
+ (nat_traversal_type, rt)
+ } else {
+ ("relay".to_string(), "".to_string())
+ };
+ let status = format!("{:?}", peer.status);
+ let item = DeviceItem {
+ name,
+ virtual_ip,
+ nat_type,
+ public_ips,
+ local_ip,
+ nat_traversal_type,
+ rt,
+ status,
+ };
+ list.push(item);
+ }
+ list
+}
+
+pub fn command_status(switch: &Switch) -> Status {
+ let current_device = switch.current_device();
+ let nat_info = switch.nat_info();
+ let name = switch.name().to_string();
+ let virtual_ip = current_device.virtual_ip().to_string();
+ let virtual_gateway = current_device.virtual_gateway().to_string();
+ let virtual_netmask = current_device.virtual_netmask.to_string();
+ let connect_status = format!("{:?}", switch.connection_status());
+ let relay_server = current_device.connect_server.to_string();
+ let nat_type = format!("{:?}", nat_info.nat_type);
+ let public_ips: Vec = nat_info.public_ips.iter().map(|v| v.to_string()).collect();
+ let public_ips = public_ips.join(",");
+ let local_ip = nat_info.local_ip.to_string();
+ Status {
+ name,
+ virtual_ip,
+ virtual_gateway,
+ virtual_netmask,
+ connect_status,
+ relay_server,
+ nat_type,
+ public_ips,
+ local_ip,
+ }
+}
+
fn command(cmd: &str, switch: &Switch) -> io::Result {
- let mut out_str = String::new();
- match cmd {
- "list" => {
- let server_rt = switch.server_rt();
- let device_list = switch.device_list();
- if device_list.is_empty() {
- return Ok("No other devices found\n".to_string());
+ let out_str = match cmd {
+ "route" => {
+ match serde_json::to_string(&command_route(switch)) {
+ Ok(str) => {
+ str
+ }
+ Err(e) => {
+ format!("{:?}", e)
+ }
}
- for peer_device_info in device_list {
- let route = switch.route(&peer_device_info.virtual_ip);
- let str = if peer_device_info.status == PeerDeviceStatus::Online {
- if route.route_type == RouteType::P2P {
- let str = if route.rt >= 0 {
- format!(
- "[{}] {}(p2p delay:{}ms)\n",
- peer_device_info.name, peer_device_info.virtual_ip, route.rt
- )
- } else {
- format!(
- "[{}] {}(p2p)",
- peer_device_info.name, peer_device_info.virtual_ip
- )
- };
- style(str).green().to_string()
- } else {
- let str = if server_rt >= 0 {
- format!(
- "[{}] {}(relay delay:{}ms)\n",
- peer_device_info.name,
- peer_device_info.virtual_ip,
- server_rt * 2
- )
- } else {
- format!(
- "[{}] {}(relay)\n",
- peer_device_info.name, peer_device_info.virtual_ip
- )
- };
- style(str).blue().to_string()
- }
- } else {
- let str = format!(
- "[{}] {}(Offline)\n",
- peer_device_info.name, peer_device_info.virtual_ip
- );
- style(str).red().to_string()
- };
- out_str.push_str(&str);
+ }
+ "list" => {
+ match serde_json::to_string(&command_list(switch)) {
+ Ok(str) => {
+ str
+ }
+ Err(e) => {
+ format!("{:?}", e)
+ }
}
}
"status" => {
- let server_rt = switch.server_rt();
- let current_device = switch.current_device();
- let str = format!("Virtual ip:{}\n", style(current_device.virtual_ip).green());
- out_str.push_str(&str);
- let str = format!(
- "Virtual gateway:{}\n",
- style(current_device.virtual_gateway).green()
- );
- out_str.push_str(&str);
- let str = format!(
- "Connection status :{}\n",
- style(format!("{:?}", switch.connection_status())).green()
- );
- out_str.push_str(&str);
- let str = format!(
- "Relay server :{}\n",
- style(current_device.connect_server).green()
- );
- out_str.push_str(&str);
- if server_rt >= 0 {
- let str = format!("Delay of relay server :{}ms\n", style(server_rt).green());
- out_str.push_str(&str);
- }
- if let Some(nat_info) = switch.nat_info() {
- let str = format!(
- "NAT type :{}",
- style(format!("{:?}", nat_info.nat_type)).green()
- );
- out_str.push_str(&str);
+ match serde_json::to_string(&command_status(switch)) {
+ Ok(str) => {
+ str
+ }
+ Err(e) => {
+ format!("{:?}", e)
+ }
}
}
- "help" | "h" => {
- let str = format!("Options: \n");
- out_str.push_str(&str);
- let str = format!(
- "{} , Query the virtual IP of other devices\n",
- style("list").green()
- );
- out_str.push_str(&str);
- let str = format!("{} , View current device status\n", style("status").green());
- out_str.push_str(&str);
- let str = format!("{} , Exit the program\n", style("exit").green());
- out_str.push_str(&str);
- }
- "exit" => {
- switch.stop_async();
+ "stop" => {
+ switch.stop()?;
+ "stopping".to_string()
}
_ => {
- let str = format!("command '{}' not fount. \n", style(cmd).red());
- out_str.push_str(&str);
- let str = format!("Try to enter: '{}'\n", style("help").green());
- out_str.push_str(&str);
+ format!("command '{}' not fount. \n Try to enter: 'help'\n", cmd)
}
- }
+ };
Ok(out_str)
}
diff --git a/switch-desktop/src/config/log_config.rs b/switch-desktop/src/config/log_config.rs
new file mode 100644
index 0000000..579e966
--- /dev/null
+++ b/switch-desktop/src/config/log_config.rs
@@ -0,0 +1,50 @@
+use std::io;
+use crate::config::SWITCH_HOME_PATH;
+#[cfg(target_os = "windows")]
+pub fn log_service_init() -> io::Result<()> {
+ log_init_("switch-service.log")
+}
+pub fn log_init() -> io::Result<()> {
+ log_init_("switch.log")
+}
+pub fn log_init_(file_name:&str) -> io::Result<()> {
+ let home = SWITCH_HOME_PATH.lock().clone();
+ let home = if let Some(home) = home {
+ home
+ } else {
+ return Err(io::Error::new(io::ErrorKind::Other, "not found"));
+ };
+ if !home.exists() {
+ std::fs::create_dir(&home)?;
+ }
+ let stderr = log4rs::append::console::ConsoleAppender::builder()
+ .target(log4rs::append::console::Target::Stderr)
+ .build();
+ let logfile = log4rs::append::file::FileAppender::builder()
+ // Pattern: https://docs.rs/log4rs/*/log4rs/encode/pattern/index.html
+ .encoder(Box::new(log4rs::encode::pattern::PatternEncoder::new(
+ "{d(%+)(utc)} [{f}:{L}] {h({l})} {M}:{m}{n}\n",
+ )))
+ .build(home.join(file_name))?;
+ match log4rs::Config::builder()
+ .appender(log4rs::config::Appender::builder().build("logfile", Box::new(logfile)))
+ .appender(
+ log4rs::config::Appender::builder()
+ .filter(Box::new(log4rs::filter::threshold::ThresholdFilter::new(
+ log::LevelFilter::Error,
+ )))
+ .build("stderr", Box::new(stderr)),
+ )
+ .build(
+ log4rs::config::Root::builder()
+ .appender("logfile")
+ .appender("stderr")
+ .build(log::LevelFilter::Info),
+ ) {
+ Ok(config) => {
+ let _ = log4rs::init_config(config);
+ }
+ Err(_) => {}
+ }
+ Ok(())
+}
\ No newline at end of file
diff --git a/switch-desktop/src/config/mod.rs b/switch-desktop/src/config/mod.rs
index 9c2e758..17c6271 100644
--- a/switch-desktop/src/config/mod.rs
+++ b/switch-desktop/src/config/mod.rs
@@ -1,45 +1,199 @@
-use std::fs::File;
+use std::fs::{File, OpenOptions};
use std::io;
use std::io::{Read, Write};
+use std::net::{SocketAddr, ToSocketAddrs};
use std::path::PathBuf;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
+use crate::StartArgs;
+
+pub mod log_config;
+
+pub struct StartConfig {
+ pub name: String,
+ pub token: String,
+ pub server: SocketAddr,
+ pub nat_test_server: Vec,
+ pub device_id: String,
+}
+
+pub fn default_config(start_args: StartArgs) -> Result {
+ let args_config = read_config();
+ if args_config.is_none() && start_args.token.is_none() {
+ return Err("找不到token(Token not found)".to_string());
+ }
+ let token = start_args.token.unwrap_or_else(|| args_config.as_ref().unwrap().token.clone()).trim().to_string();
+ if token.is_empty() {
+ return Err("token不能为空(Token cannot be empty)".to_string());
+ }
+ if token.len() > 64 {
+ return Err("token不能超过64字符(Token cannot exceed 64 characters)".to_string());
+ }
+ let name = start_args.name.unwrap_or_else(|| {
+ if let Some(c) = &args_config {
+ if !c.name.is_empty() {
+ return c.name.clone();
+ }
+ }
+ os_info::get().to_string()
+ });
+ let name = name.trim();
+ let name = if name.len() > 64 {
+ name[..64].to_string()
+ } else {
+ name.to_string()
+ };
+ let device_id = start_args.device_id.unwrap_or_else(|| {
+ if let Some(c) = &args_config {
+ if !c.device_id.is_empty() {
+ return c.device_id.clone();
+ }
+ }
+ if let Ok(Some(mac_address)) = mac_address::get_mac_address() {
+ mac_address.to_string()
+ } else {
+ "".to_string()
+ }
+ });
+ if device_id.is_empty() || device_id.len() > 64 {
+ return Err("设备id不能为空并且长度不能大于64字符(The device id cannot be empty and the length cannot be greater than 64 characters)".to_string());
+ }
+ let server = match start_args.server.unwrap_or_else(|| {
+ if let Some(c) = &args_config {
+ if !c.server.is_empty() {
+ return c.server.clone();
+ }
+ }
+ "nat1.wherewego.top:29871".to_string()
+ }).to_socket_addrs() {
+ Ok(mut server) => {
+ if let Some(addr) = server.next() {
+ addr
+ } else {
+ return Err("中继服务器地址错误( Relay server address error)".to_string());
+ }
+ }
+ Err(e) => {
+ return Err(format!("中继服务器地址错误( Relay server address error) :{:?}", e));
+ }
+ };
+ let nat_test_server = start_args.nat_test_server.unwrap_or_else(|| {
+ if let Some(c) = &args_config {
+ if !c.nat_test_server.is_empty() {
+ return c.nat_test_server.join(",");
+ }
+ }
+ "nat1.wherewego.top:35061,nat1.wherewego.top:35062,nat2.wherewego.top:35061,nat2.wherewego.top:35062".to_string()
+ }).split(",").flat_map(|a| a.to_socket_addrs()).flatten()
+ .collect::>();
+ if nat_test_server.is_empty() {
+ return Err("NAT检测服务地址错误(NAT detection service address error)".to_string());
+ }
+ let base_config = StartConfig {
+ name,
+ token,
+ server,
+ nat_test_server,
+ device_id,
+ };
+ Ok(base_config)
+}
+
lazy_static! {
static ref CONFIG: Mutex