diff --git a/README.md b/README.md index ffedc95..5829a42 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # switch - A virtual network tool (VPN) + +A virtual network tool (VPN) 将不同网络下的多个设备虚拟到一个局域网下 - ### 快速使用: 1. 指定一个token,在多台设备上运行该程序,例如: ```shell # linux上 - root@DESKTOP-0BCHNIO:/opt# ./switch-cmd start --token 123456 + root@DESKTOP-0BCHNIO:/opt# ./switch-cmd -k 123456 # 在另一台linux上使用nohup后台运行 - root@izj6cemne76ykdzkataftfz switch# nohup ./switch-cmd start --token 123456 & + root@izj6cemne76ykdzkataftfz switch# nohup ./switch-cmd -k 123456 & # windows上 - D:\switch\bin_v1>switch-cmd.exe start --token 123456 + D:\switch\bin_v1>switch-cmd.exe -k 123456 ``` 2. 可以执行info命令查看当前设备的虚拟ip ```shell @@ -36,11 +36,7 @@ CentOS 7.9.2009 (Core) [64-bit] 10.26.0.4 p2p 35 Online ``` 4. 最后可以用虚拟ip实现设备间相互访问 - 1. ping - ping - 2. ssh - ssh 5. 帮助,使用-h命令查看 @@ -49,50 +45,61 @@ 1. 和远程桌面(如mstsc)搭配,超低延迟的体验 2. 安装samba服务,共享磁盘 3. 搭配公网服务器nginx反向代理,在公网访问本地文件 -4. 点对网(结合启动参数'--in-ip'和'--out-ip') - +4. 点对网(结合启动参数'-i'和'-o') ### 使用须知 + - token的作用是标识一个虚拟局域网,当使用公共服务器时,建议使用一个唯一值当token(比如uuid),否则有可能连接到其他人创建的虚拟局域网中 -- 建议指定deviceId,默认使用MAC地址,在某些环境下可能发生变化,**注意不要重复** -- 公共服务器目前的配置是2核4G 4Mbps,有需要再扩展~ +- 默认使用公共服务器,目前的配置是2核4G 4Mbps,有需要再扩展~ - 需要root/管理员权限 -- 使用命令行运行 -- Mac和Linux下需要加可执行权限(例如:chmod +x ./switch-macos) -- 自己搭注册和中继服务器(https://github.com/lbl8603/switch-server) +- switch-cmd需要使用命令行运行 +- Mac和Linux下需要加可执行权限(例如:chmod +x ./switch-cmd) +- 可以自己搭注册和中继服务器([server](https://github.com/lbl8603/switch-server)) + ### 编译 - 前提条件:安装rust编译环境(https://www.rust-lang.org/zh-CN/tools/install) - - 到项目根目录下执行 cargo build -p switch-cmd - + +前提条件:安装rust编译环境([install rust](https://www.rust-lang.org/zh-CN/tools/install)) + +到项目根目录下执行 cargo build -p switch-cmd + ### 支持平台 + - Mac - Linux - Windows - - 使用tun网卡 依赖wintun.dll(https://www.wintun.net/ )(将dll放到同目录下,建议使用版本0.14.1) - - 使用tap网卡 依赖tap-windows(https://build.openvpn.net/downloads/releases/ )(建议使用版本9.24.7) + - 使用tun网卡 依赖wintun.dll([win-tun](https://www.wintun.net/))(将dll放到同目录下,建议使用版本0.14.1) + - 使用tap网卡 依赖tap-windows([win-tap](https://build.openvpn.net/downloads/releases/))(建议使用版本9.24.7) +- Android + - [SwitchApp](https://github.com/lbl8603/SwitchApp) ### 特性 + - IP层数据转发 - - tun虚拟网卡 - - tap虚拟网卡 + - tun虚拟网卡 + - tap虚拟网卡 - NAT穿透 - - 点对点穿透 - - 服务端中继转发 - - 客户端中继转发 + - 点对点穿透 + - 服务端中继转发 + - 客户端中继转发 - IP代理 - p2p组播/广播 - 客户端数据加密 ### Todo -- 支持安卓 + - 服务端数据加密 +- 支持Ipv6 ### 常见问题 + #### 问题1: 设置网络地址失败 + ##### 可能原因: + switch默认使用10.26.0.0/24网段,和本地网络适配器的ip冲突 + ##### 解决方法: + 1. 方法一:找到冲突的IP,将其改成别的 2. 方法二:自建服务器,指定其他不会冲突的网段 3. 方法三:增加参数--device-id,设置不同的id会让switch-server分配不同的IP,从而绕开有冲突的IP diff --git a/documents/img/cmd_list.jpg b/documents/img/cmd_list.jpg deleted file mode 100644 index dcdc038..0000000 Binary files a/documents/img/cmd_list.jpg and /dev/null differ diff --git a/documents/img/cmd_status.jpg b/documents/img/cmd_status.jpg deleted file mode 100644 index 81f8351..0000000 Binary files a/documents/img/cmd_status.jpg and /dev/null differ diff --git a/documents/img/ping.jpg b/documents/img/ping.jpg deleted file mode 100644 index a4c1caa..0000000 Binary files a/documents/img/ping.jpg and /dev/null differ diff --git a/switch-cmd/Cargo.toml b/switch-cmd/Cargo.toml index dbe4559..50f1ef5 100644 --- a/switch-cmd/Cargo.toml +++ b/switch-cmd/Cargo.toml @@ -16,6 +16,12 @@ dirs = "4.0.0" serde = "1.0" serde_json = "1.0.94" log = "0.4.17" + +[target.'cfg(any(target_os = "linux",target_os = "macos"))'.dependencies] +sudo = "0.6.0" + +[target.'cfg(target_os = "windows")'.dependencies] +winapi = { version = "0.3.9", features = ["handleapi", "processthreadsapi", "winnt", "securitybaseapi", "impl-default"] } [features] default = [] mini = [] \ No newline at end of file diff --git a/switch-cmd/src/main.rs b/switch-cmd/src/main.rs index 01e812c..a6fea52 100644 --- a/switch-cmd/src/main.rs +++ b/switch-cmd/src/main.rs @@ -10,6 +10,7 @@ use switch::handle::registration_handler::ReqEnum; mod command; mod console_out; +mod root_check; #[tokio::main] async fn main() { @@ -25,8 +26,8 @@ async fn main() { opts.optopt("s", "", "注册和中继服务器地址", ""); opts.optopt("e", "", "NAT探测服务器地址,使用逗号分隔", ""); opts.optflag("a", "", "使用tap模式,默认使用tun模式"); - opts.optmulti("i", "", "配置点对网(IP代理)时使用,--in-ip 192.168.10.0/24,10.26.0.3,表示允许接收网段192.168.10.0/24的数据并转发到10.26.0.3", ""); - opts.optmulti("o", "", "配置点对网时使用,--out-ip 192.168.10.0/24,192.168.1.10,表示允许目标为192.168.10.0/24的数据从网卡192.168.1.10转发出去", ""); + opts.optmulti("i", "", "配置点对网(IP代理)时使用,-i 192.168.10.0/24,10.26.0.3,表示允许接收网段192.168.10.0/24的数据并转发到10.26.0.3", ""); + opts.optmulti("o", "", "配置点对网时使用,-o 192.168.10.0/24,192.168.1.10,表示允许目标为192.168.10.0/24的数据从网卡192.168.1.10转发出去", ""); opts.optopt("w", "", "使用该密码生成的密钥对客户端数据进行加密,并且服务端无法解密,使用相同密码的客户端才能通信", ""); opts.optflag("m", "", "模拟组播,默认情况下组播数据会被当作广播发送,开启后会模拟真实组播的数据发送"); opts.optopt("u", "", "虚拟网卡mtu值", ""); @@ -45,6 +46,14 @@ async fn main() { return; } }; + if matches.opt_present("h") || args.len() == 1 { + print_usage(&program, opts); + return; + } + if !root_check::is_app_elevated() { + println!("Please run it with administrator or root privileges"); + return; + } if matches.opt_present("list") { command::command(command::CommandEnum::List); return; @@ -61,10 +70,6 @@ async fn main() { command::command(command::CommandEnum::All); return; } - if matches.opt_present("h") { - print_usage(&program, opts); - return; - } if !matches.opt_present("k") { print_usage(&program, opts); println!("parameter -k not found ."); diff --git a/switch-cmd/src/root_check/mod.rs b/switch-cmd/src/root_check/mod.rs new file mode 100644 index 0000000..8c52835 --- /dev/null +++ b/switch-cmd/src/root_check/mod.rs @@ -0,0 +1,11 @@ +#[cfg(target_os = "windows")] +mod windows; + +#[cfg(target_os = "windows")] +pub use windows::is_app_elevated; + +#[cfg(any(target_os = "linux", target_os = "macos"))] +mod unix; + +#[cfg(any(target_os = "linux", target_os = "macos"))] +pub use unix::is_app_elevated; \ No newline at end of file diff --git a/switch-cmd/src/root_check/unix.rs b/switch-cmd/src/root_check/unix.rs new file mode 100644 index 0000000..516ccea --- /dev/null +++ b/switch-cmd/src/root_check/unix.rs @@ -0,0 +1,3 @@ +pub fn is_app_elevated() -> bool { + sudo::RunningAs::Root == sudo::check() +} \ No newline at end of file diff --git a/switch-cmd/src/root_check/windows.rs b/switch-cmd/src/root_check/windows.rs new file mode 100644 index 0000000..c3531bf --- /dev/null +++ b/switch-cmd/src/root_check/windows.rs @@ -0,0 +1,76 @@ +/// 使用 https://github.com/spa5k/is_sudo/blob/main/src/window.rs +use std::io::Error; +use std::ptr; + +use winapi::um::handleapi::CloseHandle; +use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken}; +use winapi::um::securitybaseapi::GetTokenInformation; +use winapi::um::winnt::{TokenElevation, HANDLE, TOKEN_ELEVATION, TOKEN_QUERY}; + +// Use std::io::Error::last_os_error for errors. +// NOTE: For this example I'm simple passing on the OS error. +// However, customising the error could provide more context + +/// Returns true if the current process has admin rights, otherwise false. +pub fn is_app_elevated() -> bool { + _is_app_elevated().unwrap_or(false) +} + +/// On success returns a bool indicating if the current process has admin rights. +/// Otherwise returns an OS error. +/// +/// This is unlikely to fail but if it does it's even more unlikely that you have admin permissions anyway. +/// Therefore the public function above simply eats the error and returns a bool. +fn _is_app_elevated() -> Result { + let token = QueryAccessToken::from_current_process()?; + token.is_elevated() +} + +/// A safe wrapper around querying Windows access tokens. +pub struct QueryAccessToken(HANDLE); + +impl QueryAccessToken { + pub fn from_current_process() -> Result { + unsafe { + let mut handle: HANDLE = ptr::null_mut(); + let result = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle); + + if result != 0 { + Ok(Self(handle)) + } else { + Err(Error::last_os_error()) + } + } + } + + /// On success returns a bool indicating if the access token has elevated privilidges. + /// Otherwise returns an OS error. + pub fn is_elevated(&self) -> Result { + unsafe { + let mut elevation = TOKEN_ELEVATION::default(); + let size = std::mem::size_of::() as u32; + let mut ret_size = size; + // The weird looking repetition of `as *mut _` is casting the reference to a c_void pointer. + if GetTokenInformation( + self.0, + TokenElevation, + &mut elevation as *mut _ as *mut _, + size, + &mut ret_size, + ) != 0 + { + Ok(elevation.TokenIsElevated != 0) + } else { + Err(Error::last_os_error()) + } + } + } +} + +impl Drop for QueryAccessToken { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { CloseHandle(self.0) }; + } + } +} diff --git a/switch-desktop/src/main.rs b/switch-desktop/src/main.rs index ecbda23..c4ccd07 100644 --- a/switch-desktop/src/main.rs +++ b/switch-desktop/src/main.rs @@ -58,7 +58,7 @@ async fn connect(config: ConnectConfig) -> Result { return Err(format!("server err:{}", e)); } }; - let nat_test_server = config.nat_test_server.split(" ").flat_map(|a| a.to_socket_addrs()).flatten() + let nat_test_server = config.nat_test_server.split(&[',', ' '][..]).flat_map(|a| a.to_socket_addrs()).flatten() .collect::>(); let in_ips = config.in_ips.split(" ").into_iter().filter(|e| !e.is_empty()).map(|e| e.to_string()).collect(); let in_ips = match ips_parse(&in_ips) { diff --git a/switch-desktop/ui/index.html b/switch-desktop/ui/index.html index 341702e..1086c31 100644 --- a/switch-desktop/ui/index.html +++ b/switch-desktop/ui/index.html @@ -99,7 +99,7 @@ #add-div-in { padding: 30px; - height: 380px; + height: 410px; width: 580px; position: relative; background-color: #fff; @@ -364,7 +364,7 @@
+ value="nat1.wherewego.top:35061,nat1.wherewego.top:35062,nat2.wherewego.top:35061,nat2.wherewego.top:35062">
diff --git a/switch/src/handle/tun_tap/tun_handler.rs b/switch/src/handle/tun_tap/tun_handler.rs index 03bfaa1..7b5abcf 100644 --- a/switch/src/handle/tun_tap/tun_handler.rs +++ b/switch/src/handle/tun_tap/tun_handler.rs @@ -89,6 +89,8 @@ async fn start_(sender: ChannelSender, return Ok(()); } let len = device_reader.read(&mut buf[12..])? + 12; + #[cfg(any(target_os = "macos"))] + let mut buf = &mut buf; match handle(&sender, &mut buf, len, device_writer, &igmp_server, current_device.load(), &ip_route, &ip_proxy_map, &cipher).await { Ok(_) => {} Err(e) => { diff --git a/switch/src/tun_tap_device/mac.rs b/switch/src/tun_tap_device/mac.rs index a9874fc..8fd9b07 100644 --- a/switch/src/tun_tap_device/mac.rs +++ b/switch/src/tun_tap_device/mac.rs @@ -88,7 +88,7 @@ pub fn create_device(device_type: DeviceType, fn add_route(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> io::Result<()> { let route_add_str: String = format!( - "sudo route -n add -net {:?}/{:?} -interface {}", + "route -n add {} -netmask {} -interface {}", address, netmask, name ); let route_add_out = Command::new("sh")