mirror of
https://github.com/lbl8603/vnt.git
synced 2025-09-26 20:21:20 +08:00
1.1.0
This commit is contained in:
63
README.md
63
README.md
@@ -1,19 +1,19 @@
|
|||||||
# switch
|
# switch
|
||||||
A virtual network tool (VPN)
|
|
||||||
|
A virtual network tool (VPN)
|
||||||
|
|
||||||
将不同网络下的多个设备虚拟到一个局域网下
|
将不同网络下的多个设备虚拟到一个局域网下
|
||||||
|
|
||||||
|
|
||||||
### 快速使用:
|
### 快速使用:
|
||||||
|
|
||||||
1. 指定一个token,在多台设备上运行该程序,例如:
|
1. 指定一个token,在多台设备上运行该程序,例如:
|
||||||
```shell
|
```shell
|
||||||
# linux上
|
# linux上
|
||||||
root@DESKTOP-0BCHNIO:/opt# ./switch-cmd start --token 123456
|
root@DESKTOP-0BCHNIO:/opt# ./switch-cmd -k 123456
|
||||||
# 在另一台linux上使用nohup后台运行
|
# 在另一台linux上使用nohup后台运行
|
||||||
root@izj6cemne76ykdzkataftfz switch# nohup ./switch-cmd start --token 123456 &
|
root@izj6cemne76ykdzkataftfz switch# nohup ./switch-cmd -k 123456 &
|
||||||
# windows上
|
# windows上
|
||||||
D:\switch\bin_v1>switch-cmd.exe start --token 123456
|
D:\switch\bin_v1>switch-cmd.exe -k 123456
|
||||||
```
|
```
|
||||||
2. 可以执行info命令查看当前设备的虚拟ip
|
2. 可以执行info命令查看当前设备的虚拟ip
|
||||||
```shell
|
```shell
|
||||||
@@ -36,11 +36,7 @@
|
|||||||
CentOS 7.9.2009 (Core) [64-bit] 10.26.0.4 p2p 35 Online
|
CentOS 7.9.2009 (Core) [64-bit] 10.26.0.4 p2p 35 Online
|
||||||
```
|
```
|
||||||
4. 最后可以用虚拟ip实现设备间相互访问
|
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">
|
<img width="506" alt="ssh" src="https://raw.githubusercontent.com/lbl8603/switch/dev/documents/img/ssh.jpg">
|
||||||
5. 帮助,使用-h命令查看
|
5. 帮助,使用-h命令查看
|
||||||
|
|
||||||
@@ -49,50 +45,61 @@
|
|||||||
1. 和远程桌面(如mstsc)搭配,超低延迟的体验
|
1. 和远程桌面(如mstsc)搭配,超低延迟的体验
|
||||||
2. 安装samba服务,共享磁盘
|
2. 安装samba服务,共享磁盘
|
||||||
3. 搭配公网服务器nginx反向代理,在公网访问本地文件
|
3. 搭配公网服务器nginx反向代理,在公网访问本地文件
|
||||||
4. 点对网(结合启动参数'--in-ip'和'--out-ip')
|
4. 点对网(结合启动参数'-i'和'-o')
|
||||||
|
|
||||||
|
|
||||||
### 使用须知
|
### 使用须知
|
||||||
|
|
||||||
- token的作用是标识一个虚拟局域网,当使用公共服务器时,建议使用一个唯一值当token(比如uuid),否则有可能连接到其他人创建的虚拟局域网中
|
- token的作用是标识一个虚拟局域网,当使用公共服务器时,建议使用一个唯一值当token(比如uuid),否则有可能连接到其他人创建的虚拟局域网中
|
||||||
- 建议指定deviceId,默认使用MAC地址,在某些环境下可能发生变化,**注意不要重复**
|
- 默认使用公共服务器,目前的配置是2核4G 4Mbps,有需要再扩展~
|
||||||
- 公共服务器目前的配置是2核4G 4Mbps,有需要再扩展~
|
|
||||||
- 需要root/管理员权限
|
- 需要root/管理员权限
|
||||||
- 使用命令行运行
|
- switch-cmd需要使用命令行运行
|
||||||
- Mac和Linux下需要加可执行权限(例如:chmod +x ./switch-macos)
|
- Mac和Linux下需要加可执行权限(例如:chmod +x ./switch-cmd)
|
||||||
- 自己搭注册和中继服务器(https://github.com/lbl8603/switch-server)
|
- 可以自己搭注册和中继服务器([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
|
|
||||||
|
到项目根目录下执行 cargo build -p switch-cmd
|
||||||
|
|
||||||
### 支持平台
|
### 支持平台
|
||||||
|
|
||||||
- Mac
|
- Mac
|
||||||
- Linux
|
- Linux
|
||||||
- Windows
|
- Windows
|
||||||
- 使用tun网卡 依赖wintun.dll(https://www.wintun.net/ )(将dll放到同目录下,建议使用版本0.14.1)
|
- 使用tun网卡 依赖wintun.dll([win-tun](https://www.wintun.net/))(将dll放到同目录下,建议使用版本0.14.1)
|
||||||
- 使用tap网卡 依赖tap-windows(https://build.openvpn.net/downloads/releases/ )(建议使用版本9.24.7)
|
- 使用tap网卡 依赖tap-windows([win-tap](https://build.openvpn.net/downloads/releases/))(建议使用版本9.24.7)
|
||||||
|
- Android
|
||||||
|
- [SwitchApp](https://github.com/lbl8603/SwitchApp)
|
||||||
|
|
||||||
### 特性
|
### 特性
|
||||||
|
|
||||||
- IP层数据转发
|
- IP层数据转发
|
||||||
- tun虚拟网卡
|
- tun虚拟网卡
|
||||||
- tap虚拟网卡
|
- tap虚拟网卡
|
||||||
- NAT穿透
|
- NAT穿透
|
||||||
- 点对点穿透
|
- 点对点穿透
|
||||||
- 服务端中继转发
|
- 服务端中继转发
|
||||||
- 客户端中继转发
|
- 客户端中继转发
|
||||||
- IP代理
|
- IP代理
|
||||||
- p2p组播/广播
|
- p2p组播/广播
|
||||||
- 客户端数据加密
|
- 客户端数据加密
|
||||||
|
|
||||||
### Todo
|
### Todo
|
||||||
- 支持安卓
|
|
||||||
- 服务端数据加密
|
- 服务端数据加密
|
||||||
|
- 支持Ipv6
|
||||||
|
|
||||||
### 常见问题
|
### 常见问题
|
||||||
|
|
||||||
#### 问题1: 设置网络地址失败
|
#### 问题1: 设置网络地址失败
|
||||||
|
|
||||||
##### 可能原因:
|
##### 可能原因:
|
||||||
|
|
||||||
switch默认使用10.26.0.0/24网段,和本地网络适配器的ip冲突
|
switch默认使用10.26.0.0/24网段,和本地网络适配器的ip冲突
|
||||||
|
|
||||||
##### 解决方法:
|
##### 解决方法:
|
||||||
|
|
||||||
1. 方法一:找到冲突的IP,将其改成别的
|
1. 方法一:找到冲突的IP,将其改成别的
|
||||||
2. 方法二:自建服务器,指定其他不会冲突的网段
|
2. 方法二:自建服务器,指定其他不会冲突的网段
|
||||||
3. 方法三:增加参数--device-id,设置不同的id会让switch-server分配不同的IP,从而绕开有冲突的IP
|
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 |
@@ -16,6 +16,12 @@ dirs = "4.0.0"
|
|||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0.94"
|
serde_json = "1.0.94"
|
||||||
log = "0.4.17"
|
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]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
mini = []
|
mini = []
|
@@ -10,6 +10,7 @@ use switch::handle::registration_handler::ReqEnum;
|
|||||||
|
|
||||||
mod command;
|
mod command;
|
||||||
mod console_out;
|
mod console_out;
|
||||||
|
mod root_check;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -25,8 +26,8 @@ async fn main() {
|
|||||||
opts.optopt("s", "", "注册和中继服务器地址", "<server>");
|
opts.optopt("s", "", "注册和中继服务器地址", "<server>");
|
||||||
opts.optopt("e", "", "NAT探测服务器地址,使用逗号分隔", "<addr1,addr2>");
|
opts.optopt("e", "", "NAT探测服务器地址,使用逗号分隔", "<addr1,addr2>");
|
||||||
opts.optflag("a", "", "使用tap模式,默认使用tun模式");
|
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("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", "", "配置点对网时使用,--out-ip 192.168.10.0/24,192.168.1.10,表示允许目标为192.168.10.0/24的数据从网卡192.168.1.10转发出去", "<out-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.optopt("w", "", "使用该密码生成的密钥对客户端数据进行加密,并且服务端无法解密,使用相同密码的客户端才能通信", "<password>");
|
||||||
opts.optflag("m", "", "模拟组播,默认情况下组播数据会被当作广播发送,开启后会模拟真实组播的数据发送");
|
opts.optflag("m", "", "模拟组播,默认情况下组播数据会被当作广播发送,开启后会模拟真实组播的数据发送");
|
||||||
opts.optopt("u", "", "虚拟网卡mtu值", "<mtu>");
|
opts.optopt("u", "", "虚拟网卡mtu值", "<mtu>");
|
||||||
@@ -45,6 +46,14 @@ async fn main() {
|
|||||||
return;
|
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") {
|
if matches.opt_present("list") {
|
||||||
command::command(command::CommandEnum::List);
|
command::command(command::CommandEnum::List);
|
||||||
return;
|
return;
|
||||||
@@ -61,10 +70,6 @@ async fn main() {
|
|||||||
command::command(command::CommandEnum::All);
|
command::command(command::CommandEnum::All);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if matches.opt_present("h") {
|
|
||||||
print_usage(&program, opts);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if !matches.opt_present("k") {
|
if !matches.opt_present("k") {
|
||||||
print_usage(&program, opts);
|
print_usage(&program, opts);
|
||||||
println!("parameter -k not found .");
|
println!("parameter -k not found .");
|
||||||
|
11
switch-cmd/src/root_check/mod.rs
Normal file
11
switch-cmd/src/root_check/mod.rs
Normal 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;
|
3
switch-cmd/src/root_check/unix.rs
Normal file
3
switch-cmd/src/root_check/unix.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub fn is_app_elevated() -> bool {
|
||||||
|
sudo::RunningAs::Root == sudo::check()
|
||||||
|
}
|
76
switch-cmd/src/root_check/windows.rs
Normal file
76
switch-cmd/src/root_check/windows.rs
Normal 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) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -58,7 +58,7 @@ async fn connect(config: ConnectConfig) -> Result<ConnectRegResponse, String> {
|
|||||||
return Err(format!("server err:{}", e));
|
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<_>>();
|
.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 = 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) {
|
let in_ips = match ips_parse(&in_ips) {
|
||||||
|
@@ -99,7 +99,7 @@
|
|||||||
|
|
||||||
#add-div-in {
|
#add-div-in {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
height: 380px;
|
height: 410px;
|
||||||
width: 580px;
|
width: 580px;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
@@ -364,7 +364,7 @@
|
|||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="natServer">NatServerAddress:</label>
|
<label for="natServer">NatServerAddress:</label>
|
||||||
<input type="text" id="natServer" name="natServer" required
|
<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>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="simulateMulticast">SimulateMulticast:</label>
|
<label for="simulateMulticast">SimulateMulticast:</label>
|
||||||
|
@@ -89,6 +89,8 @@ async fn start_(sender: ChannelSender,
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let len = device_reader.read(&mut buf[12..])? + 12;
|
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 {
|
match handle(&sender, &mut buf, len, device_writer, &igmp_server, current_device.load(), &ip_route, &ip_proxy_map, &cipher).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@@ -88,7 +88,7 @@ pub fn create_device(device_type: DeviceType,
|
|||||||
|
|
||||||
fn add_route(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> io::Result<()> {
|
fn add_route(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> io::Result<()> {
|
||||||
let route_add_str: String = format!(
|
let route_add_str: String = format!(
|
||||||
"sudo route -n add -net {:?}/{:?} -interface {}",
|
"route -n add {} -netmask {} -interface {}",
|
||||||
address, netmask, name
|
address, netmask, name
|
||||||
);
|
);
|
||||||
let route_add_out = Command::new("sh")
|
let route_add_out = Command::new("sh")
|
||||||
|
Reference in New Issue
Block a user