This commit is contained in:
lubeilin
2023-07-17 22:56:37 +08:00
parent 8d44934382
commit 6b988e0612
13 changed files with 148 additions and 38 deletions

View File

@@ -1,19 +1,19 @@
# switch
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,10 +36,6 @@
CentOS 7.9.2009 (Core) [64-bit] 10.26.0.4 p2p 35 Online
```
4. 最后可以用虚拟ip实现设备间相互访问
1. ping
<img width="506" alt="ping" src="https://raw.githubusercontent.com/lbl8603/switch/dev/documents/img/ping.jpg">
2. ssh
<img width="506" alt="ssh" src="https://raw.githubusercontent.com/lbl8603/switch/dev/documents/img/ssh.jpg">
5. 帮助,使用-h命令查看
@@ -49,30 +45,35 @@
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)
前提条件:安装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虚拟网卡
@@ -85,14 +86,20 @@
- 客户端数据加密
### Todo
- 支持安卓
- 服务端数据加密
- 支持Ipv6
### 常见问题
#### 问题1: 设置网络地址失败
##### 可能原因:
switch默认使用10.26.0.0/24网段和本地网络适配器的ip冲突
##### 解决方法:
1. 方法一找到冲突的IP将其改成别的
2. 方法二:自建服务器,指定其他不会冲突的网段
3. 方法三:增加参数--device-id设置不同的id会让switch-server分配不同的IP从而绕开有冲突的IP

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -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 = []

View File

@@ -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", "", "注册和中继服务器地址", "<server>");
opts.optopt("e", "", "NAT探测服务器地址,使用逗号分隔", "<addr1,addr2>");
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", "<in-ip>");
opts.optmulti("o", "", "配置点对网时使用,--out-ip 192.168.10.0/24,192.168.1.10表示允许目标为192.168.10.0/24的数据从网卡192.168.1.10转发出去", "<out-ip>");
opts.optmulti("i", "", "配置点对网(IP代理)时使用,-i 192.168.10.0/24,10.26.0.3表示允许接收网段192.168.10.0/24的数据并转发到10.26.0.3", "<in-ip>");
opts.optmulti("o", "", "配置点对网时使用,-o 192.168.10.0/24,192.168.1.10表示允许目标为192.168.10.0/24的数据从网卡192.168.1.10转发出去", "<out-ip>");
opts.optopt("w", "", "使用该密码生成的密钥对客户端数据进行加密,并且服务端无法解密,使用相同密码的客户端才能通信", "<password>");
opts.optflag("m", "", "模拟组播,默认情况下组播数据会被当作广播发送,开启后会模拟真实组播的数据发送");
opts.optopt("u", "", "虚拟网卡mtu值", "<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 .");

View File

@@ -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;

View File

@@ -0,0 +1,3 @@
pub fn is_app_elevated() -> bool {
sudo::RunningAs::Root == sudo::check()
}

View File

@@ -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<bool, Error> {
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<Self, Error> {
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<bool, Error> {
unsafe {
let mut elevation = TOKEN_ELEVATION::default();
let size = std::mem::size_of::<TOKEN_ELEVATION>() 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) };
}
}
}

View File

@@ -58,7 +58,7 @@ async fn connect(config: ConnectConfig) -> Result<ConnectRegResponse, String> {
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::<Vec<_>>();
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) {

View File

@@ -99,7 +99,7 @@
#add-div-in {
padding: 30px;
height: 380px;
height: 410px;
width: 580px;
position: relative;
background-color: #fff;
@@ -364,7 +364,7 @@
<div class="form-row">
<label for="natServer">NatServerAddress:</label>
<input type="text" id="natServer" name="natServer" required
value="nat1.wherewego.top:35061 nat1.wherewego.top:35062 nat2.wherewego.top:35061 nat2.wherewego.top:35062">
value="nat1.wherewego.top:35061,nat1.wherewego.top:35062,nat2.wherewego.top:35061,nat2.wherewego.top:35062">
</div>
<div class="form-row">
<label for="simulateMulticast">SimulateMulticast:</label>

View File

@@ -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) => {

View File

@@ -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")