add client gui for easytier (#50)

This commit is contained in:
Sijie.Sun
2024-04-06 22:44:30 +08:00
committed by GitHub
parent 4eb7efe5fc
commit 727ef37ae4
22 changed files with 1763 additions and 177 deletions

View File

@@ -4,11 +4,13 @@ use std::{net::SocketAddr, vec};
use clap::{command, Args, Parser, Subcommand};
use rpc::vpn_portal_rpc_client::VpnPortalRpcClient;
use utils::{list_peer_route_pair, PeerRoutePair};
mod arch;
mod common;
mod rpc;
mod tunnels;
mod utils;
use crate::{
common::stun::{StunInfoCollector, UdpNatTypeDetector},
@@ -17,6 +19,7 @@ use crate::{
peer_center_rpc_client::PeerCenterRpcClient, peer_manage_rpc_client::PeerManageRpcClient,
*,
},
utils::{cost_to_str, float_to_str},
};
use humansize::format_size;
use tabled::settings::Style;
@@ -94,107 +97,6 @@ enum Error {
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 {
addr: String,
}
@@ -239,19 +141,9 @@ impl CommandHandler {
}
async fn list_peer_route_pair(&self) -> Result<Vec<PeerRoutePair>, Error> {
let mut peers = self.list_peers().await?.peer_infos;
let mut routes = self.list_routes().await?.routes;
let mut pairs: Vec<PeerRoutePair> = vec![];
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)
let peers = self.list_peers().await?.peer_infos;
let routes = self.list_routes().await?.routes;
Ok(list_peer_route_pair(peers, routes))
}
#[allow(dead_code)]
@@ -279,18 +171,6 @@ impl CommandHandler {
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 {
fn from(p: PeerRoutePair) -> Self {
PeerTableItem {

View File

@@ -35,6 +35,35 @@ use tokio_stream::wrappers::ReceiverStream;
use super::listeners::ListenerManager;
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 {
inst_name: String,
@@ -51,9 +80,7 @@ pub struct Instance {
direct_conn_manager: Arc<DirectConnectorManager>,
udp_hole_puncher: Arc<Mutex<UdpHolePunchConnector>>,
tcp_proxy: Arc<TcpProxy>,
icmp_proxy: Arc<IcmpProxy>,
udp_proxy: Arc<UdpProxy>,
ip_proxy: Option<IpProxy>,
peer_center: Arc<PeerCenterInstance>,
@@ -97,14 +124,6 @@ impl Instance {
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 vpn_portal_inst = vpn_portal::wireguard::WireGuard::default();
@@ -123,9 +142,7 @@ impl Instance {
direct_conn_manager: Arc::new(direct_conn_manager),
udp_hole_puncher: Arc::new(Mutex::new(udp_hole_puncher)),
tcp_proxy: arc_tcp_proxy,
icmp_proxy: arc_icmp_proxy,
udp_proxy: arc_udp_proxy,
ip_proxy: None,
peer_center,
@@ -269,9 +286,12 @@ impl Instance {
self.run_rpc_server().unwrap();
self.tcp_proxy.start().await.unwrap();
self.icmp_proxy.start().await.unwrap();
self.udp_proxy.start().await.unwrap();
self.ip_proxy = Some(IpProxy::new(
self.get_global_ctx(),
self.get_peer_manager(),
)?);
self.ip_proxy.as_ref().unwrap().start().await?;
self.run_proxy_cidrs_route_updater();
self.udp_hole_puncher.lock().await.run().await?;
@@ -478,4 +498,8 @@ impl Instance {
pub fn get_global_ctx(&self) -> ArcGlobalCtx {
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
View 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;

View File

@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Default)]
pub struct GetIpListResponse {
pub public_ipv4: String,
pub interface_ipv4s: Vec<String>,

View File

@@ -6,7 +6,7 @@ use std::{
};
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 tokio::{sync::Mutex, time::error::Elapsed};
@@ -319,6 +319,29 @@ pub(crate) fn setup_sokcet2_ext(
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(
socket2_socket: &socket2::Socket,
bind_addr: &SocketAddr,

View File

@@ -22,7 +22,7 @@ pub enum TunnelError {
CommonError(String),
#[error("io error")]
IOError(#[from] std::io::Error),
#[error("wait resp error")]
#[error("wait resp error {0}")]
WaitRespError(String),
#[error("Connect Error: {0}")]
ConnectError(String),

View File

@@ -1,14 +1,16 @@
use std::net::SocketAddr;
use async_trait::async_trait;
use futures::{stream::FuturesUnordered, StreamExt};
use futures::stream::FuturesUnordered;
use tokio::net::{TcpListener, TcpSocket, TcpStream};
use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
use crate::tunnels::common::setup_sokcet2;
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)]
@@ -115,7 +117,7 @@ impl TcpTunnelConnector {
}
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")?;
for bind_addr in self.bind_addrs.iter() {
@@ -132,12 +134,7 @@ impl TcpTunnelConnector {
futures.push(socket.connect(dst_addr.clone()));
}
let Some(ret) = futures.next().await else {
return Err(super::TunnelError::CommonError(
"join connect futures failed".to_owned(),
));
};
let ret = wait_for_connect_futures(futures).await;
return get_tunnel_with_tcp_stream(ret?, self.addr.clone().into());
}
}
@@ -162,7 +159,7 @@ impl super::TunnelConnector for TcpTunnelConnector {
#[cfg(test)]
mod tests {
use futures::SinkExt;
use futures::{SinkExt, StreamExt};
use crate::tunnels::{
common::tests::{_tunnel_bench, _tunnel_pingpong},

View File

@@ -23,7 +23,10 @@ use crate::{
use super::{
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,
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> {
let mut futures = FuturesUnordered::new();
let futures = FuturesUnordered::new();
for bind_addr in self.bind_addrs.iter() {
let socket2_socket = socket2::Socket::new(
@@ -567,14 +570,7 @@ impl UdpTunnelConnector {
let socket = UdpSocket::from_std(socket2_socket.into())?;
futures.push(self.try_connect_with_socket(socket));
}
let Some(ret) = futures.next().await else {
return Err(super::TunnelError::CommonError(
"join connect futures failed".to_owned(),
));
};
return ret;
wait_for_connect_futures(futures).await
}
}

View File

@@ -27,7 +27,7 @@ use crate::{
use super::{
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,
DatagramSink, DatagramStream, Tunnel, TunnelError, TunnelListener, TunnelUrl,
};
@@ -689,7 +689,7 @@ impl super::TunnelConnector for WgTunnelConnector {
} else {
self.bind_addrs.clone()
};
let mut futures = FuturesUnordered::new();
let futures = FuturesUnordered::new();
for bind_addr in bind_addrs.into_iter() {
let socket2_socket = socket2::Socket::new(
@@ -707,13 +707,7 @@ impl super::TunnelConnector for WgTunnelConnector {
));
}
let Some(ret) = futures.next().await else {
return Err(super::TunnelError::CommonError(
"join connect futures failed".to_owned(),
));
};
return ret;
wait_for_connect_futures(futures).await
}
fn remote_url(&self) -> url::Url {

128
easytier/src/utils.rs Normal file
View 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)
}