mirror of
https://github.com/EasyTier/EasyTier.git
synced 2024-11-16 11:42:27 +08:00
add client gui for easytier (#50)
This commit is contained in:
parent
4eb7efe5fc
commit
727ef37ae4
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
|
@ -104,6 +104,7 @@ jobs:
|
||||||
TAG=$GITHUB_SHA
|
TAG=$GITHUB_SHA
|
||||||
fi
|
fi
|
||||||
mv ./target/$TARGET/release/easytier-core"$SUFFIX" ./artifacts/objects/
|
mv ./target/$TARGET/release/easytier-core"$SUFFIX" ./artifacts/objects/
|
||||||
|
mv ./target/$TARGET/release/easytier-gui"$SUFFIX" ./artifacts/objects/
|
||||||
mv ./target/$TARGET/release/easytier-cli"$SUFFIX" ./artifacts/objects/
|
mv ./target/$TARGET/release/easytier-cli"$SUFFIX" ./artifacts/objects/
|
||||||
tar -cvf ./artifacts/$NAME-$TARGET-$TAG.tar -C ./artifacts/objects/ .
|
tar -cvf ./artifacts/$NAME-$TARGET-$TAG.tar -C ./artifacts/objects/ .
|
||||||
rm -rf ./artifacts/objects/
|
rm -rf ./artifacts/objects/
|
||||||
|
@ -124,7 +125,6 @@ jobs:
|
||||||
remote-path: /easytier-releases/${{ github.sha }}/
|
remote-path: /easytier-releases/${{ github.sha }}/
|
||||||
no-delete-remote-files: true
|
no-delete-remote-files: true
|
||||||
retry: 5
|
retry: 5
|
||||||
increment: true
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["easytier"]
|
members = ["easytier", "easytier-gui"]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
panic = "unwind"
|
panic = "unwind"
|
||||||
|
|
48
easytier-gui/Cargo.toml
Normal file
48
easytier-gui/Cargo.toml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
[package]
|
||||||
|
name = "easytier-gui"
|
||||||
|
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
||||||
|
homepage = "https://github.com/KKRainbow/EasyTier"
|
||||||
|
repository = "https://github.com/KKRainbow/EasyTier"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["kkrainbow"]
|
||||||
|
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||||
|
categories = ["network-programming"]
|
||||||
|
rust-version = "1.75"
|
||||||
|
license-file = "LICENSE"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
easytier = { path = "../easytier" }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
anyhow = "1.0"
|
||||||
|
chrono = "0.4.37"
|
||||||
|
|
||||||
|
once_cell = "1.18.0"
|
||||||
|
dashmap = "5.5.3"
|
||||||
|
egui = { version = "0.27.2" }
|
||||||
|
egui-modal = "0.3.6"
|
||||||
|
|
||||||
|
humansize = "2.1.3"
|
||||||
|
|
||||||
|
eframe = { version = "0.27.2", features = [
|
||||||
|
"default",
|
||||||
|
"serde",
|
||||||
|
"persistence",
|
||||||
|
"wgpu"
|
||||||
|
] }
|
||||||
|
wgpu = { version = "0.19.3", features = [ "webgpu", "webgl"] }
|
||||||
|
|
||||||
|
# For image support:
|
||||||
|
egui_extras = { version = "0.27.2", features = ["default", "image"] }
|
||||||
|
|
||||||
|
env_logger = { version = "0.10", default-features = false, features = [
|
||||||
|
"auto-color",
|
||||||
|
"humantime",
|
||||||
|
] }
|
||||||
|
|
||||||
|
egui_tiles = "0.8.0"
|
||||||
|
|
||||||
|
derivative = "2.2.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
elevated-command = "1.1.2"
|
1
easytier-gui/LICENSE
Symbolic link
1
easytier-gui/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../LICENSE
|
0
easytier-gui/README.md
Normal file
0
easytier-gui/README.md
Normal file
BIN
easytier-gui/assets/msyh.ttc
Normal file
BIN
easytier-gui/assets/msyh.ttc
Normal file
Binary file not shown.
217
easytier-gui/src/launcher.rs
Normal file
217
easytier-gui/src/launcher.rs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
sync::{atomic::AtomicBool, Arc, RwLock},
|
||||||
|
};
|
||||||
|
|
||||||
|
use chrono::{DateTime, Local};
|
||||||
|
use easytier::{
|
||||||
|
common::{
|
||||||
|
config::{ConfigLoader, TomlConfigLoader},
|
||||||
|
global_ctx::GlobalCtxEvent,
|
||||||
|
stun::StunInfoCollectorTrait,
|
||||||
|
},
|
||||||
|
instance::instance::Instance,
|
||||||
|
peers::rpc_service::PeerManagerRpcService,
|
||||||
|
rpc::{
|
||||||
|
cli::{PeerInfo, Route, StunInfo},
|
||||||
|
peer::GetIpListResponse,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct MyNodeInfo {
|
||||||
|
pub virtual_ipv4: String,
|
||||||
|
pub ips: GetIpListResponse,
|
||||||
|
pub stun_info: StunInfo,
|
||||||
|
pub listeners: Vec<String>,
|
||||||
|
pub vpn_portal_cfg: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
struct EasyTierData {
|
||||||
|
events: Arc<RwLock<VecDeque<(DateTime<Local>, GlobalCtxEvent)>>>,
|
||||||
|
node_info: Arc<RwLock<MyNodeInfo>>,
|
||||||
|
routes: Arc<RwLock<Vec<Route>>>,
|
||||||
|
peers: Arc<RwLock<Vec<PeerInfo>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EasyTierLauncher {
|
||||||
|
instance_alive: Arc<AtomicBool>,
|
||||||
|
stop_flag: Arc<AtomicBool>,
|
||||||
|
thread_handle: Option<std::thread::JoinHandle<()>>,
|
||||||
|
running_cfg: String,
|
||||||
|
|
||||||
|
error_msg: Arc<RwLock<Option<String>>>,
|
||||||
|
data: EasyTierData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EasyTierLauncher {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let instance_alive = Arc::new(AtomicBool::new(false));
|
||||||
|
Self {
|
||||||
|
instance_alive,
|
||||||
|
thread_handle: None,
|
||||||
|
error_msg: Arc::new(RwLock::new(None)),
|
||||||
|
running_cfg: String::new(),
|
||||||
|
|
||||||
|
stop_flag: Arc::new(AtomicBool::new(false)),
|
||||||
|
data: EasyTierData::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_easytier_event(event: GlobalCtxEvent, data: EasyTierData) {
|
||||||
|
let mut events = data.events.write().unwrap();
|
||||||
|
events.push_back((chrono::Local::now(), event));
|
||||||
|
if events.len() > 100 {
|
||||||
|
events.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn easytier_routine(
|
||||||
|
cfg: TomlConfigLoader,
|
||||||
|
stop_signal: Arc<tokio::sync::Notify>,
|
||||||
|
data: EasyTierData,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
let mut instance = Instance::new(cfg);
|
||||||
|
let peer_mgr = instance.get_peer_manager();
|
||||||
|
|
||||||
|
// Subscribe to global context events
|
||||||
|
let global_ctx = instance.get_global_ctx();
|
||||||
|
let data_c = data.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut receiver = global_ctx.subscribe();
|
||||||
|
while let Ok(event) = receiver.recv().await {
|
||||||
|
Self::handle_easytier_event(event, data_c.clone()).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// update my node info
|
||||||
|
let data_c = data.clone();
|
||||||
|
let global_ctx_c = instance.get_global_ctx();
|
||||||
|
let peer_mgr_c = peer_mgr.clone();
|
||||||
|
let vpn_portal = instance.get_vpn_portal_inst();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
let node_info = MyNodeInfo {
|
||||||
|
virtual_ipv4: global_ctx_c
|
||||||
|
.get_ipv4()
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
ips: global_ctx_c.get_ip_collector().collect_ip_addrs().await,
|
||||||
|
stun_info: global_ctx_c.get_stun_info_collector().get_stun_info(),
|
||||||
|
listeners: global_ctx_c
|
||||||
|
.get_running_listeners()
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect(),
|
||||||
|
vpn_portal_cfg: Some(
|
||||||
|
vpn_portal
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.dump_client_config(peer_mgr_c.clone())
|
||||||
|
.await,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
*data_c.node_info.write().unwrap() = node_info.clone();
|
||||||
|
*data_c.routes.write().unwrap() = peer_mgr_c.list_routes().await;
|
||||||
|
*data_c.peers.write().unwrap() = PeerManagerRpcService::new(peer_mgr_c.clone())
|
||||||
|
.list_peers()
|
||||||
|
.await;
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.run().await?;
|
||||||
|
stop_signal.notified().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start<F>(&mut self, cfg_generator: F)
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Result<TomlConfigLoader, anyhow::Error> + Send + Sync,
|
||||||
|
{
|
||||||
|
let error_msg = self.error_msg.clone();
|
||||||
|
let cfg = cfg_generator();
|
||||||
|
if let Err(e) = cfg {
|
||||||
|
error_msg.write().unwrap().replace(e.to_string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.running_cfg = cfg.as_ref().unwrap().dump();
|
||||||
|
|
||||||
|
let stop_flag = self.stop_flag.clone();
|
||||||
|
|
||||||
|
let instance_alive = self.instance_alive.clone();
|
||||||
|
instance_alive.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
|
let data = self.data.clone();
|
||||||
|
|
||||||
|
self.thread_handle = Some(std::thread::spawn(move || {
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let stop_notifier = Arc::new(tokio::sync::Notify::new());
|
||||||
|
|
||||||
|
let stop_notifier_clone = stop_notifier.clone();
|
||||||
|
rt.spawn(async move {
|
||||||
|
while !stop_flag.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
stop_notifier_clone.notify_one();
|
||||||
|
});
|
||||||
|
|
||||||
|
let ret = rt.block_on(Self::easytier_routine(
|
||||||
|
cfg.unwrap(),
|
||||||
|
stop_notifier.clone(),
|
||||||
|
data,
|
||||||
|
));
|
||||||
|
if let Err(e) = ret {
|
||||||
|
error_msg.write().unwrap().replace(e.to_string());
|
||||||
|
}
|
||||||
|
instance_alive.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error_msg(&self) -> Option<String> {
|
||||||
|
self.error_msg.read().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn running(&self) -> bool {
|
||||||
|
self.instance_alive
|
||||||
|
.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_events(&self) -> Vec<(DateTime<Local>, GlobalCtxEvent)> {
|
||||||
|
let events = self.data.events.read().unwrap();
|
||||||
|
events.iter().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_node_info(&self) -> MyNodeInfo {
|
||||||
|
self.data.node_info.read().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_routes(&self) -> Vec<Route> {
|
||||||
|
self.data.routes.read().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_peers(&self) -> Vec<PeerInfo> {
|
||||||
|
self.data.peers.read().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn running_cfg(&self) -> String {
|
||||||
|
self.running_cfg.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for EasyTierLauncher {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.stop_flag
|
||||||
|
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
if let Some(handle) = self.thread_handle.take() {
|
||||||
|
if let Err(e) = handle.join() {
|
||||||
|
println!("Error when joining thread: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1095
easytier-gui/src/main.rs
Normal file
1095
easytier-gui/src/main.rs
Normal file
File diff suppressed because it is too large
Load Diff
57
easytier-gui/src/text_list.rs
Normal file
57
easytier-gui/src/text_list.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TextListOption {
|
||||||
|
pub hint: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text_list_ui(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
id: &str,
|
||||||
|
texts: &mut Vec<String>,
|
||||||
|
option: Option<TextListOption>,
|
||||||
|
) {
|
||||||
|
let option = option.unwrap_or_default();
|
||||||
|
// convert text vec to (index, text) vec
|
||||||
|
let mut add_new_item = false;
|
||||||
|
let mut remove_idxs = vec![];
|
||||||
|
|
||||||
|
egui::Grid::new(id).max_col_width(200.0).show(ui, |ui| {
|
||||||
|
for i in 0..texts.len() {
|
||||||
|
egui::TextEdit::singleline(&mut texts[i])
|
||||||
|
.hint_text(&option.hint)
|
||||||
|
.show(ui);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("➖").clicked() {
|
||||||
|
remove_idxs.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == texts.len() - 1 {
|
||||||
|
if ui.button("➕").clicked() {
|
||||||
|
add_new_item = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
|
||||||
|
if texts.len() == 0 {
|
||||||
|
if ui.button("➕").clicked() {
|
||||||
|
add_new_item = true;
|
||||||
|
}
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let new_texts = texts
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(i, _)| !remove_idxs.contains(i))
|
||||||
|
.map(|(_, t)| t.clone())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
*texts = new_texts;
|
||||||
|
|
||||||
|
if add_new_item && texts.last().map(|t| !t.is_empty()).unwrap_or(true) {
|
||||||
|
texts.push("".to_string());
|
||||||
|
}
|
||||||
|
}
|
107
easytier-gui/src/toggle_switch.rs
Normal file
107
easytier-gui/src/toggle_switch.rs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
//! Source code example of how to create your own widget.
|
||||||
|
//! This is meant to be read as a tutorial, hence the plethora of comments.
|
||||||
|
|
||||||
|
/// iOS-style toggle switch:
|
||||||
|
///
|
||||||
|
/// ``` text
|
||||||
|
/// _____________
|
||||||
|
/// / /.....\
|
||||||
|
/// | |.......|
|
||||||
|
/// \_______\_____/
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Example:
|
||||||
|
/// ``` ignore
|
||||||
|
/// toggle_ui(ui, &mut my_bool);
|
||||||
|
/// ```
|
||||||
|
pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||||
|
// Widget code can be broken up in four steps:
|
||||||
|
// 1. Decide a size for the widget
|
||||||
|
// 2. Allocate space for it
|
||||||
|
// 3. Handle interactions with the widget (if any)
|
||||||
|
// 4. Paint the widget
|
||||||
|
|
||||||
|
// 1. Deciding widget size:
|
||||||
|
// You can query the `ui` how much space is available,
|
||||||
|
// but in this example we have a fixed size widget based on the height of a standard button:
|
||||||
|
let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0);
|
||||||
|
|
||||||
|
// 2. Allocating space:
|
||||||
|
// This is where we get a region of the screen assigned.
|
||||||
|
// We also tell the Ui to sense clicks in the allocated region.
|
||||||
|
let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
||||||
|
|
||||||
|
// 3. Interact: Time to check for clicks!
|
||||||
|
if response.clicked() {
|
||||||
|
*on = !*on;
|
||||||
|
response.mark_changed(); // report back that the value changed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach some meta-data to the response which can be used by screen readers:
|
||||||
|
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));
|
||||||
|
|
||||||
|
// 4. Paint!
|
||||||
|
// Make sure we need to paint:
|
||||||
|
if ui.is_rect_visible(rect) {
|
||||||
|
// Let's ask for a simple animation from egui.
|
||||||
|
// egui keeps track of changes in the boolean associated with the id and
|
||||||
|
// returns an animated value in the 0-1 range for how much "on" we are.
|
||||||
|
let how_on = ui.ctx().animate_bool(response.id, *on);
|
||||||
|
// We will follow the current style by asking
|
||||||
|
// "how should something that is being interacted with be painted?".
|
||||||
|
// This will, for instance, give us different colors when the widget is hovered or clicked.
|
||||||
|
let visuals = ui.style().interact_selectable(&response, *on);
|
||||||
|
// All coordinates are in absolute screen coordinates so we use `rect` to place the elements.
|
||||||
|
let rect = rect.expand(visuals.expansion);
|
||||||
|
let radius = 0.5 * rect.height();
|
||||||
|
ui.painter()
|
||||||
|
.rect(rect, radius, visuals.bg_fill, visuals.bg_stroke);
|
||||||
|
// Paint the circle, animating it from left to right with `how_on`:
|
||||||
|
let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
|
||||||
|
let center = egui::pos2(circle_x, rect.center().y);
|
||||||
|
ui.painter()
|
||||||
|
.circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All done! Return the interaction response so the user can check what happened
|
||||||
|
// (hovered, clicked, ...) and maybe show a tooltip:
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Here is the same code again, but a bit more compact:
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||||
|
let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0);
|
||||||
|
let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
||||||
|
if response.clicked() {
|
||||||
|
*on = !*on;
|
||||||
|
response.mark_changed();
|
||||||
|
}
|
||||||
|
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));
|
||||||
|
|
||||||
|
if ui.is_rect_visible(rect) {
|
||||||
|
let how_on = ui.ctx().animate_bool(response.id, *on);
|
||||||
|
let visuals = ui.style().interact_selectable(&response, *on);
|
||||||
|
let rect = rect.expand(visuals.expansion);
|
||||||
|
let radius = 0.5 * rect.height();
|
||||||
|
ui.painter()
|
||||||
|
.rect(rect, radius, visuals.bg_fill, visuals.bg_stroke);
|
||||||
|
let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
|
||||||
|
let center = egui::pos2(circle_x, rect.center().y);
|
||||||
|
ui.painter()
|
||||||
|
.circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper that allows the more idiomatic usage pattern: `ui.add(toggle(&mut my_bool))`
|
||||||
|
/// iOS-style toggle switch.
|
||||||
|
///
|
||||||
|
/// ## Example:
|
||||||
|
/// ``` ignore
|
||||||
|
/// ui.add(toggle(&mut my_bool));
|
||||||
|
/// ```
|
||||||
|
pub fn toggle(on: &mut bool) -> impl egui::Widget + '_ {
|
||||||
|
move |ui: &mut egui::Ui| toggle_ui(ui, on)
|
||||||
|
}
|
|
@ -23,6 +23,11 @@ name = "easytier-cli"
|
||||||
path = "src/easytier-cli.rs"
|
path = "src/easytier-cli.rs"
|
||||||
test = false
|
test = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "easytier"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
test = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = { version = "0.1", features = ["log"] }
|
tracing = { version = "0.1", features = ["log"] }
|
||||||
tracing-subscriber = { version = "0.3", features = [
|
tracing-subscriber = { version = "0.3", features = [
|
||||||
|
|
1
easytier/LICENSE
Symbolic link
1
easytier/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../LICENSE
|
|
@ -4,11 +4,13 @@ use std::{net::SocketAddr, vec};
|
||||||
|
|
||||||
use clap::{command, Args, Parser, Subcommand};
|
use clap::{command, Args, Parser, Subcommand};
|
||||||
use rpc::vpn_portal_rpc_client::VpnPortalRpcClient;
|
use rpc::vpn_portal_rpc_client::VpnPortalRpcClient;
|
||||||
|
use utils::{list_peer_route_pair, PeerRoutePair};
|
||||||
|
|
||||||
mod arch;
|
mod arch;
|
||||||
mod common;
|
mod common;
|
||||||
mod rpc;
|
mod rpc;
|
||||||
mod tunnels;
|
mod tunnels;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
common::stun::{StunInfoCollector, UdpNatTypeDetector},
|
common::stun::{StunInfoCollector, UdpNatTypeDetector},
|
||||||
|
@ -17,6 +19,7 @@ use crate::{
|
||||||
peer_center_rpc_client::PeerCenterRpcClient, peer_manage_rpc_client::PeerManageRpcClient,
|
peer_center_rpc_client::PeerCenterRpcClient, peer_manage_rpc_client::PeerManageRpcClient,
|
||||||
*,
|
*,
|
||||||
},
|
},
|
||||||
|
utils::{cost_to_str, float_to_str},
|
||||||
};
|
};
|
||||||
use humansize::format_size;
|
use humansize::format_size;
|
||||||
use tabled::settings::Style;
|
use tabled::settings::Style;
|
||||||
|
@ -94,107 +97,6 @@ enum Error {
|
||||||
TonicRpcError(#[from] tonic::Status),
|
TonicRpcError(#[from] tonic::Status),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct PeerRoutePair {
|
|
||||||
route: Route,
|
|
||||||
peer: Option<PeerInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PeerRoutePair {
|
|
||||||
fn get_latency_ms(&self) -> Option<f64> {
|
|
||||||
let mut ret = u64::MAX;
|
|
||||||
let p = self.peer.as_ref()?;
|
|
||||||
for conn in p.conns.iter() {
|
|
||||||
let Some(stats) = &conn.stats else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
ret = ret.min(stats.latency_us);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ret == u64::MAX {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(f64::from(ret as u32) / 1000.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_rx_bytes(&self) -> Option<u64> {
|
|
||||||
let mut ret = 0;
|
|
||||||
let p = self.peer.as_ref()?;
|
|
||||||
for conn in p.conns.iter() {
|
|
||||||
let Some(stats) = &conn.stats else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
ret += stats.rx_bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ret == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_tx_bytes(&self) -> Option<u64> {
|
|
||||||
let mut ret = 0;
|
|
||||||
let p = self.peer.as_ref()?;
|
|
||||||
for conn in p.conns.iter() {
|
|
||||||
let Some(stats) = &conn.stats else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
ret += stats.tx_bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ret == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_loss_rate(&self) -> Option<f64> {
|
|
||||||
let mut ret = 0.0;
|
|
||||||
let p = self.peer.as_ref()?;
|
|
||||||
for conn in p.conns.iter() {
|
|
||||||
ret += conn.loss_rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ret == 0.0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ret as f64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_conn_protos(&self) -> Option<Vec<String>> {
|
|
||||||
let mut ret = vec![];
|
|
||||||
let p = self.peer.as_ref()?;
|
|
||||||
for conn in p.conns.iter() {
|
|
||||||
let Some(tunnel_info) = &conn.tunnel else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
// insert if not exists
|
|
||||||
if !ret.contains(&tunnel_info.tunnel_type) {
|
|
||||||
ret.push(tunnel_info.tunnel_type.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ret.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_udp_nat_type(self: &Self) -> String {
|
|
||||||
let mut ret = NatType::Unknown;
|
|
||||||
if let Some(r) = &self.route.stun_info {
|
|
||||||
ret = NatType::try_from(r.udp_nat_type).unwrap();
|
|
||||||
}
|
|
||||||
format!("{:?}", ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CommandHandler {
|
struct CommandHandler {
|
||||||
addr: String,
|
addr: String,
|
||||||
}
|
}
|
||||||
|
@ -239,19 +141,9 @@ impl CommandHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_peer_route_pair(&self) -> Result<Vec<PeerRoutePair>, Error> {
|
async fn list_peer_route_pair(&self) -> Result<Vec<PeerRoutePair>, Error> {
|
||||||
let mut peers = self.list_peers().await?.peer_infos;
|
let peers = self.list_peers().await?.peer_infos;
|
||||||
let mut routes = self.list_routes().await?.routes;
|
let routes = self.list_routes().await?.routes;
|
||||||
let mut pairs: Vec<PeerRoutePair> = vec![];
|
Ok(list_peer_route_pair(peers, routes))
|
||||||
|
|
||||||
for route in routes.iter_mut() {
|
|
||||||
let peer = peers.iter_mut().find(|peer| peer.peer_id == route.peer_id);
|
|
||||||
pairs.push(PeerRoutePair {
|
|
||||||
route: route.clone(),
|
|
||||||
peer: peer.cloned(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(pairs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -279,18 +171,6 @@ impl CommandHandler {
|
||||||
id: String,
|
id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cost_to_str(cost: i32) -> String {
|
|
||||||
if cost == 1 {
|
|
||||||
"p2p".to_string()
|
|
||||||
} else {
|
|
||||||
format!("relay({})", cost)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn float_to_str(f: f64, precision: usize) -> String {
|
|
||||||
format!("{:.1$}", f, precision)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PeerRoutePair> for PeerTableItem {
|
impl From<PeerRoutePair> for PeerTableItem {
|
||||||
fn from(p: PeerRoutePair) -> Self {
|
fn from(p: PeerRoutePair) -> Self {
|
||||||
PeerTableItem {
|
PeerTableItem {
|
||||||
|
|
|
@ -35,6 +35,35 @@ use tokio_stream::wrappers::ReceiverStream;
|
||||||
use super::listeners::ListenerManager;
|
use super::listeners::ListenerManager;
|
||||||
use super::virtual_nic;
|
use super::virtual_nic;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct IpProxy {
|
||||||
|
tcp_proxy: Arc<TcpProxy>,
|
||||||
|
icmp_proxy: Arc<IcmpProxy>,
|
||||||
|
udp_proxy: Arc<UdpProxy>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IpProxy {
|
||||||
|
fn new(global_ctx: ArcGlobalCtx, peer_manager: Arc<PeerManager>) -> Result<Self, Error> {
|
||||||
|
let tcp_proxy = TcpProxy::new(global_ctx.clone(), peer_manager.clone());
|
||||||
|
let icmp_proxy = IcmpProxy::new(global_ctx.clone(), peer_manager.clone())
|
||||||
|
.with_context(|| "create icmp proxy failed")?;
|
||||||
|
let udp_proxy = UdpProxy::new(global_ctx.clone(), peer_manager.clone())
|
||||||
|
.with_context(|| "create udp proxy failed")?;
|
||||||
|
Ok(IpProxy {
|
||||||
|
tcp_proxy,
|
||||||
|
icmp_proxy,
|
||||||
|
udp_proxy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start(&self) -> Result<(), Error> {
|
||||||
|
self.tcp_proxy.start().await?;
|
||||||
|
self.icmp_proxy.start().await?;
|
||||||
|
self.udp_proxy.start().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
inst_name: String,
|
inst_name: String,
|
||||||
|
|
||||||
|
@ -51,9 +80,7 @@ pub struct Instance {
|
||||||
direct_conn_manager: Arc<DirectConnectorManager>,
|
direct_conn_manager: Arc<DirectConnectorManager>,
|
||||||
udp_hole_puncher: Arc<Mutex<UdpHolePunchConnector>>,
|
udp_hole_puncher: Arc<Mutex<UdpHolePunchConnector>>,
|
||||||
|
|
||||||
tcp_proxy: Arc<TcpProxy>,
|
ip_proxy: Option<IpProxy>,
|
||||||
icmp_proxy: Arc<IcmpProxy>,
|
|
||||||
udp_proxy: Arc<UdpProxy>,
|
|
||||||
|
|
||||||
peer_center: Arc<PeerCenterInstance>,
|
peer_center: Arc<PeerCenterInstance>,
|
||||||
|
|
||||||
|
@ -97,14 +124,6 @@ impl Instance {
|
||||||
|
|
||||||
let udp_hole_puncher = UdpHolePunchConnector::new(global_ctx.clone(), peer_manager.clone());
|
let udp_hole_puncher = UdpHolePunchConnector::new(global_ctx.clone(), peer_manager.clone());
|
||||||
|
|
||||||
let arc_tcp_proxy = TcpProxy::new(global_ctx.clone(), peer_manager.clone());
|
|
||||||
let arc_icmp_proxy = IcmpProxy::new(global_ctx.clone(), peer_manager.clone())
|
|
||||||
.with_context(|| "create icmp proxy failed")
|
|
||||||
.unwrap();
|
|
||||||
let arc_udp_proxy = UdpProxy::new(global_ctx.clone(), peer_manager.clone())
|
|
||||||
.with_context(|| "create udp proxy failed")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let peer_center = Arc::new(PeerCenterInstance::new(peer_manager.clone()));
|
let peer_center = Arc::new(PeerCenterInstance::new(peer_manager.clone()));
|
||||||
|
|
||||||
let vpn_portal_inst = vpn_portal::wireguard::WireGuard::default();
|
let vpn_portal_inst = vpn_portal::wireguard::WireGuard::default();
|
||||||
|
@ -123,9 +142,7 @@ impl Instance {
|
||||||
direct_conn_manager: Arc::new(direct_conn_manager),
|
direct_conn_manager: Arc::new(direct_conn_manager),
|
||||||
udp_hole_puncher: Arc::new(Mutex::new(udp_hole_puncher)),
|
udp_hole_puncher: Arc::new(Mutex::new(udp_hole_puncher)),
|
||||||
|
|
||||||
tcp_proxy: arc_tcp_proxy,
|
ip_proxy: None,
|
||||||
icmp_proxy: arc_icmp_proxy,
|
|
||||||
udp_proxy: arc_udp_proxy,
|
|
||||||
|
|
||||||
peer_center,
|
peer_center,
|
||||||
|
|
||||||
|
@ -269,9 +286,12 @@ impl Instance {
|
||||||
|
|
||||||
self.run_rpc_server().unwrap();
|
self.run_rpc_server().unwrap();
|
||||||
|
|
||||||
self.tcp_proxy.start().await.unwrap();
|
self.ip_proxy = Some(IpProxy::new(
|
||||||
self.icmp_proxy.start().await.unwrap();
|
self.get_global_ctx(),
|
||||||
self.udp_proxy.start().await.unwrap();
|
self.get_peer_manager(),
|
||||||
|
)?);
|
||||||
|
self.ip_proxy.as_ref().unwrap().start().await?;
|
||||||
|
|
||||||
self.run_proxy_cidrs_route_updater();
|
self.run_proxy_cidrs_route_updater();
|
||||||
|
|
||||||
self.udp_hole_puncher.lock().await.run().await?;
|
self.udp_hole_puncher.lock().await.run().await?;
|
||||||
|
@ -478,4 +498,8 @@ impl Instance {
|
||||||
pub fn get_global_ctx(&self) -> ArcGlobalCtx {
|
pub fn get_global_ctx(&self) -> ArcGlobalCtx {
|
||||||
self.global_ctx.clone()
|
self.global_ctx.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_vpn_portal_inst(&self) -> Arc<Mutex<Box<dyn VpnPortal>>> {
|
||||||
|
self.vpn_portal.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
13
easytier/src/lib.rs
Normal file
13
easytier/src/lib.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
pub mod arch;
|
||||||
|
pub mod common;
|
||||||
|
pub mod connector;
|
||||||
|
pub mod gateway;
|
||||||
|
pub mod instance;
|
||||||
|
pub mod peer_center;
|
||||||
|
pub mod peers;
|
||||||
|
pub mod rpc;
|
||||||
|
pub mod tunnels;
|
||||||
|
pub mod utils;
|
||||||
|
pub mod vpn_portal;
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Default)]
|
||||||
pub struct GetIpListResponse {
|
pub struct GetIpListResponse {
|
||||||
pub public_ipv4: String,
|
pub public_ipv4: String,
|
||||||
pub interface_ipv4s: Vec<String>,
|
pub interface_ipv4s: Vec<String>,
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_stream::stream;
|
use async_stream::stream;
|
||||||
use futures::{Future, FutureExt, Sink, SinkExt, Stream, StreamExt};
|
use futures::{stream::FuturesUnordered, Future, FutureExt, Sink, SinkExt, Stream, StreamExt};
|
||||||
use network_interface::NetworkInterfaceConfig;
|
use network_interface::NetworkInterfaceConfig;
|
||||||
use tokio::{sync::Mutex, time::error::Elapsed};
|
use tokio::{sync::Mutex, time::error::Elapsed};
|
||||||
|
|
||||||
|
@ -319,6 +319,29 @@ pub(crate) fn setup_sokcet2_ext(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn wait_for_connect_futures<Fut, Ret, E>(
|
||||||
|
mut futures: FuturesUnordered<Fut>,
|
||||||
|
) -> Result<Ret, super::TunnelError>
|
||||||
|
where
|
||||||
|
Fut: Future<Output = Result<Ret, E>> + Send + Sync,
|
||||||
|
E: std::error::Error + Into<super::TunnelError> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
// return last error
|
||||||
|
let mut last_err = None;
|
||||||
|
|
||||||
|
while let Some(ret) = futures.next().await {
|
||||||
|
if let Err(e) = ret {
|
||||||
|
last_err = Some(e.into());
|
||||||
|
} else {
|
||||||
|
return ret.map_err(|e| e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(last_err.unwrap_or(super::TunnelError::CommonError(
|
||||||
|
"no connect futures".to_string(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn setup_sokcet2(
|
pub(crate) fn setup_sokcet2(
|
||||||
socket2_socket: &socket2::Socket,
|
socket2_socket: &socket2::Socket,
|
||||||
bind_addr: &SocketAddr,
|
bind_addr: &SocketAddr,
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub enum TunnelError {
|
||||||
CommonError(String),
|
CommonError(String),
|
||||||
#[error("io error")]
|
#[error("io error")]
|
||||||
IOError(#[from] std::io::Error),
|
IOError(#[from] std::io::Error),
|
||||||
#[error("wait resp error")]
|
#[error("wait resp error {0}")]
|
||||||
WaitRespError(String),
|
WaitRespError(String),
|
||||||
#[error("Connect Error: {0}")]
|
#[error("Connect Error: {0}")]
|
||||||
ConnectError(String),
|
ConnectError(String),
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::{stream::FuturesUnordered, StreamExt};
|
use futures::stream::FuturesUnordered;
|
||||||
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
||||||
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
|
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
|
||||||
|
|
||||||
use crate::tunnels::common::setup_sokcet2;
|
use crate::tunnels::common::setup_sokcet2;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
check_scheme_and_get_socket_addr, common::FramedTunnel, Tunnel, TunnelInfo, TunnelListener,
|
check_scheme_and_get_socket_addr,
|
||||||
|
common::{wait_for_connect_futures, FramedTunnel},
|
||||||
|
Tunnel, TunnelInfo, TunnelListener,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -115,7 +117,7 @@ impl TcpTunnelConnector {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_with_custom_bind(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
async fn connect_with_custom_bind(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
||||||
let mut futures = FuturesUnordered::new();
|
let futures = FuturesUnordered::new();
|
||||||
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "tcp")?;
|
let dst_addr = check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "tcp")?;
|
||||||
|
|
||||||
for bind_addr in self.bind_addrs.iter() {
|
for bind_addr in self.bind_addrs.iter() {
|
||||||
|
@ -132,12 +134,7 @@ impl TcpTunnelConnector {
|
||||||
futures.push(socket.connect(dst_addr.clone()));
|
futures.push(socket.connect(dst_addr.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(ret) = futures.next().await else {
|
let ret = wait_for_connect_futures(futures).await;
|
||||||
return Err(super::TunnelError::CommonError(
|
|
||||||
"join connect futures failed".to_owned(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
return get_tunnel_with_tcp_stream(ret?, self.addr.clone().into());
|
return get_tunnel_with_tcp_stream(ret?, self.addr.clone().into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,7 +159,7 @@ impl super::TunnelConnector for TcpTunnelConnector {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use futures::SinkExt;
|
use futures::{SinkExt, StreamExt};
|
||||||
|
|
||||||
use crate::tunnels::{
|
use crate::tunnels::{
|
||||||
common::tests::{_tunnel_bench, _tunnel_pingpong},
|
common::tests::{_tunnel_bench, _tunnel_pingpong},
|
||||||
|
|
|
@ -23,7 +23,10 @@ use crate::{
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
codec::BytesCodec,
|
codec::BytesCodec,
|
||||||
common::{setup_sokcet2, setup_sokcet2_ext, FramedTunnel, TunnelWithCustomInfo},
|
common::{
|
||||||
|
setup_sokcet2, setup_sokcet2_ext, wait_for_connect_futures, FramedTunnel,
|
||||||
|
TunnelWithCustomInfo,
|
||||||
|
},
|
||||||
ring_tunnel::create_ring_tunnel_pair,
|
ring_tunnel::create_ring_tunnel_pair,
|
||||||
DatagramSink, DatagramStream, Tunnel, TunnelListener, TunnelUrl,
|
DatagramSink, DatagramStream, Tunnel, TunnelListener, TunnelUrl,
|
||||||
};
|
};
|
||||||
|
@ -555,7 +558,7 @@ impl UdpTunnelConnector {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_with_custom_bind(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
async fn connect_with_custom_bind(&mut self) -> Result<Box<dyn Tunnel>, super::TunnelError> {
|
||||||
let mut futures = FuturesUnordered::new();
|
let futures = FuturesUnordered::new();
|
||||||
|
|
||||||
for bind_addr in self.bind_addrs.iter() {
|
for bind_addr in self.bind_addrs.iter() {
|
||||||
let socket2_socket = socket2::Socket::new(
|
let socket2_socket = socket2::Socket::new(
|
||||||
|
@ -567,14 +570,7 @@ impl UdpTunnelConnector {
|
||||||
let socket = UdpSocket::from_std(socket2_socket.into())?;
|
let socket = UdpSocket::from_std(socket2_socket.into())?;
|
||||||
futures.push(self.try_connect_with_socket(socket));
|
futures.push(self.try_connect_with_socket(socket));
|
||||||
}
|
}
|
||||||
|
wait_for_connect_futures(futures).await
|
||||||
let Some(ret) = futures.next().await else {
|
|
||||||
return Err(super::TunnelError::CommonError(
|
|
||||||
"join connect futures failed".to_owned(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ use crate::{
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
check_scheme_and_get_socket_addr,
|
check_scheme_and_get_socket_addr,
|
||||||
common::{setup_sokcet2, setup_sokcet2_ext},
|
common::{setup_sokcet2, setup_sokcet2_ext, wait_for_connect_futures},
|
||||||
ring_tunnel::create_ring_tunnel_pair,
|
ring_tunnel::create_ring_tunnel_pair,
|
||||||
DatagramSink, DatagramStream, Tunnel, TunnelError, TunnelListener, TunnelUrl,
|
DatagramSink, DatagramStream, Tunnel, TunnelError, TunnelListener, TunnelUrl,
|
||||||
};
|
};
|
||||||
|
@ -689,7 +689,7 @@ impl super::TunnelConnector for WgTunnelConnector {
|
||||||
} else {
|
} else {
|
||||||
self.bind_addrs.clone()
|
self.bind_addrs.clone()
|
||||||
};
|
};
|
||||||
let mut futures = FuturesUnordered::new();
|
let futures = FuturesUnordered::new();
|
||||||
|
|
||||||
for bind_addr in bind_addrs.into_iter() {
|
for bind_addr in bind_addrs.into_iter() {
|
||||||
let socket2_socket = socket2::Socket::new(
|
let socket2_socket = socket2::Socket::new(
|
||||||
|
@ -707,13 +707,7 @@ impl super::TunnelConnector for WgTunnelConnector {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(ret) = futures.next().await else {
|
wait_for_connect_futures(futures).await
|
||||||
return Err(super::TunnelError::CommonError(
|
|
||||||
"join connect futures failed".to_owned(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remote_url(&self) -> url::Url {
|
fn remote_url(&self) -> url::Url {
|
||||||
|
|
128
easytier/src/utils.rs
Normal file
128
easytier/src/utils.rs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
use crate::rpc::cli::{NatType, PeerInfo, Route};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PeerRoutePair {
|
||||||
|
pub route: Route,
|
||||||
|
pub peer: Option<PeerInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeerRoutePair {
|
||||||
|
pub fn get_latency_ms(&self) -> Option<f64> {
|
||||||
|
let mut ret = u64::MAX;
|
||||||
|
let p = self.peer.as_ref()?;
|
||||||
|
for conn in p.conns.iter() {
|
||||||
|
let Some(stats) = &conn.stats else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
ret = ret.min(stats.latency_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret == u64::MAX {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(f64::from(ret as u32) / 1000.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_rx_bytes(&self) -> Option<u64> {
|
||||||
|
let mut ret = 0;
|
||||||
|
let p = self.peer.as_ref()?;
|
||||||
|
for conn in p.conns.iter() {
|
||||||
|
let Some(stats) = &conn.stats else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
ret += stats.rx_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tx_bytes(&self) -> Option<u64> {
|
||||||
|
let mut ret = 0;
|
||||||
|
let p = self.peer.as_ref()?;
|
||||||
|
for conn in p.conns.iter() {
|
||||||
|
let Some(stats) = &conn.stats else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
ret += stats.tx_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_loss_rate(&self) -> Option<f64> {
|
||||||
|
let mut ret = 0.0;
|
||||||
|
let p = self.peer.as_ref()?;
|
||||||
|
for conn in p.conns.iter() {
|
||||||
|
ret += conn.loss_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret == 0.0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ret as f64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_conn_protos(&self) -> Option<Vec<String>> {
|
||||||
|
let mut ret = vec![];
|
||||||
|
let p = self.peer.as_ref()?;
|
||||||
|
for conn in p.conns.iter() {
|
||||||
|
let Some(tunnel_info) = &conn.tunnel else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
// insert if not exists
|
||||||
|
if !ret.contains(&tunnel_info.tunnel_type) {
|
||||||
|
ret.push(tunnel_info.tunnel_type.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_udp_nat_type(self: &Self) -> String {
|
||||||
|
let mut ret = NatType::Unknown;
|
||||||
|
if let Some(r) = &self.route.stun_info {
|
||||||
|
ret = NatType::try_from(r.udp_nat_type).unwrap();
|
||||||
|
}
|
||||||
|
format!("{:?}", ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_peer_route_pair(peers: Vec<PeerInfo>, routes: Vec<Route>) -> Vec<PeerRoutePair> {
|
||||||
|
let mut pairs: Vec<PeerRoutePair> = vec![];
|
||||||
|
|
||||||
|
for route in routes.iter() {
|
||||||
|
let peer = peers.iter().find(|peer| peer.peer_id == route.peer_id);
|
||||||
|
pairs.push(PeerRoutePair {
|
||||||
|
route: route.clone(),
|
||||||
|
peer: peer.cloned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cost_to_str(cost: i32) -> String {
|
||||||
|
if cost == 1 {
|
||||||
|
"p2p".to_string()
|
||||||
|
} else {
|
||||||
|
format!("relay({})", cost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn float_to_str(f: f64, precision: usize) -> String {
|
||||||
|
format!("{:.1$}", f, precision)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user