From c23b544c34f35c7c6acaa5b1c9c52dd61b690625 Mon Sep 17 00:00:00 2001 From: "Sijie.Sun" Date: Tue, 14 Jan 2025 08:55:48 +0800 Subject: [PATCH] tcp accept should retry when encoutering some kinds of error (#565) * tcp accept should retry when encoutering some kinds of error bump version to v2.1.2 * persistent temporary machine id --- .github/workflows/release.yml | 2 +- Cargo.lock | 4 +- README.md | 15 +++---- README_CN.md | 16 +++---- easytier-gui/package.json | 2 +- easytier-gui/src-tauri/Cargo.toml | 2 +- easytier-gui/src-tauri/tauri.conf.json | 2 +- easytier/Cargo.toml | 2 +- easytier/src/common/mod.rs | 26 +++++++++-- easytier/src/instance/listeners.rs | 4 +- easytier/src/tunnel/tcp.rs | 60 +++++++++++++++++--------- 11 files changed, 87 insertions(+), 48 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d2894ba..52f32bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ on: version: description: 'Version for this release' type: string - default: 'v2.1.1' + default: 'v2.1.2' required: true make_latest: description: 'Mark this release as latest' diff --git a/Cargo.lock b/Cargo.lock index 88fbcdf..ef5ae06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1830,7 +1830,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "easytier" -version = "2.1.1" +version = "2.1.2" dependencies = [ "aes-gcm", "anyhow", @@ -1926,7 +1926,7 @@ dependencies = [ [[package]] name = "easytier-gui" -version = "2.1.1" +version = "2.1.2" dependencies = [ "anyhow", "chrono", diff --git a/README.md b/README.md index d4310e5..c1d7f28 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ EasyTier is a simple, safe and decentralized VPN networking solution implemented - **High Availability**: Supports multi-path and switches to healthy paths when high packet loss or network errors are detected. - **IPv6 Support**: Supports networking using IPv6. - **Multiple Protocol Types**: Supports communication between nodes using protocols such as WebSocket and QUIC. +- **Web Management Interface**: Provides a [web-based management](https://easytier.cn/web) interface for easy configuration and monitoring. ## Installation @@ -52,7 +53,7 @@ EasyTier is a simple, safe and decentralized VPN networking solution implemented 4. **Install by Docker Compose** - Please visit the [EasyTier Official Website](https://www.easytier.top/en/) to view the full documentation. + Please visit the [EasyTier Official Website](https://www.easytier.cn/en/) to view the full documentation. 5. **Install by script (For Linux Only)** @@ -200,20 +201,20 @@ Subnet proxy information will automatically sync to each node in the virtual net ### Networking without Public IP -EasyTier supports networking using shared public nodes. The currently deployed shared public node is ``tcp://public.easytier.top:11010``. +EasyTier supports networking using shared public nodes. The currently deployed shared public node is ``tcp://public.easytier.cn:11010``. When using shared nodes, each node entering the network needs to provide the same ``--network-name`` and ``--network-secret`` parameters as the unique identifier of the network. Taking two nodes as an example, Node A executes: ```sh -sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -e tcp://public.easytier.top:11010 +sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 ``` Node B executes ```sh -sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -e tcp://public.easytier.top:11010 +sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 ``` After the command is successfully executed, Node A can access Node B through the virtual IP 10.144.144.2. @@ -286,7 +287,7 @@ Run you own public server cluster is exactly same as running an virtual network, You can also join the official public server cluster with following command: ``` -sudo easytier-core --network-name easytier --network-secret easytier -p tcp://public.easytier.top:11010 +sudo easytier-core --network-name easytier --network-secret easytier -p tcp://public.easytier.cn:11010 ``` @@ -296,10 +297,8 @@ You can use ``easytier-core --help`` to view all configuration items ## Roadmap -- [ ] Improve documentation and user guides. -- [ ] Support features such as encryption, TCP hole punching, etc. +- [ ] Support features such TCP hole punching, KCP, FEC etc. - [ ] Support iOS. -- [ ] Support Web configuration management. ## Community and Contribution diff --git a/README_CN.md b/README_CN.md index 94c3be6..17f9b55 100644 --- a/README_CN.md +++ b/README_CN.md @@ -8,7 +8,7 @@ [简体中文](/README_CN.md) | [English](/README.md) -**请访问 [EasyTier 官网](https://www.easytier.top/) 以查看完整的文档。** +**请访问 [EasyTier 官网](https://www.easytier.cn/) 以查看完整的文档。** 一个简单、安全、去中心化的内网穿透 VPN 组网方案,使用 Rust 语言和 Tokio 框架实现。 @@ -31,6 +31,7 @@ - **高可用性**:支持多路径和在检测到高丢包率或网络错误时切换到健康路径。 - **IPV6 支持**:支持利用 IPV6 组网。 - **多协议类型**: 支持使用 WebSocket、QUIC 等协议进行节点间通信。 +- **Web 管理界面**:支持通过 [Web 界面](https://easytier.cn)管理节点。 ## 安装 @@ -52,7 +53,7 @@ 4. **通过Docker Compose安装** - 请访问 [EasyTier 官网](https://www.easytier.top/) 以查看完整的文档。 + 请访问 [EasyTier 官网](https://www.easytier.cn/) 以查看完整的文档。 5. **使用一键脚本安装 (仅适用于 Linux)** @@ -199,20 +200,20 @@ sudo easytier-core --ipv4 10.144.144.2 -n 10.1.1.0/24 ### 无公网IP组网 -EasyTier 支持共享公网节点进行组网。目前已部署共享的公网节点 ``tcp://public.easytier.top:11010``。 +EasyTier 支持共享公网节点进行组网。目前已部署共享的公网节点 ``tcp://public.easytier.cn:11010``。 使用共享节点时,需要每个入网节点提供相同的 ``--network-name`` 和 ``--network-secret`` 参数,作为网络的唯一标识。 以双节点为例,节点 A 执行: ```sh -sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -e tcp://public.easytier.top:11010 +sudo easytier-core -i 10.144.144.1 --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 ``` 节点 B 执行 ```sh -sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -e tcp://public.easytier.top:11010 +sudo easytier-core --ipv4 10.144.144.2 --network-name abc --network-secret abc -p tcp://public.easytier.cn:11010 ``` 命令执行成功后,节点 A 即可通过虚拟 IP 10.144.144.2 访问节点 B。 @@ -289,7 +290,7 @@ connected_clients: 也可以使用以下命令加入官方公共服务器集群,后续将实现公共服务器集群的节点间负载均衡: ``` -sudo easytier-core --network-name easytier --network-secret easytier -p tcp://public.easytier.top:11010 +sudo easytier-core --network-name easytier --network-secret easytier -p tcp://public.easytier.cn:11010 ``` ### 其他配置 @@ -299,9 +300,8 @@ sudo easytier-core --network-name easytier --network-secret easytier -p tcp://pu ## 路线图 - [ ] 完善文档和用户指南。 -- [ ] 支持 TCP 打洞等特性。 +- [ ] 支持 TCP 打洞、KCP、FEC 等特性。 - [ ] 支持 iOS。 -- [ ] 支持 Web 配置管理。 ## 社区和贡献 diff --git a/easytier-gui/package.json b/easytier-gui/package.json index 1910aa9..3994822 100644 --- a/easytier-gui/package.json +++ b/easytier-gui/package.json @@ -1,7 +1,7 @@ { "name": "easytier-gui", "type": "module", - "version": "2.1.1", + "version": "2.1.2", "private": true, "packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4", "scripts": { diff --git a/easytier-gui/src-tauri/Cargo.toml b/easytier-gui/src-tauri/Cargo.toml index 5db3f32..a2eb44f 100644 --- a/easytier-gui/src-tauri/Cargo.toml +++ b/easytier-gui/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "easytier-gui" -version = "2.1.1" +version = "2.1.2" description = "EasyTier GUI" authors = ["you"] edition = "2021" diff --git a/easytier-gui/src-tauri/tauri.conf.json b/easytier-gui/src-tauri/tauri.conf.json index 2ddf9a8..9bc09a1 100644 --- a/easytier-gui/src-tauri/tauri.conf.json +++ b/easytier-gui/src-tauri/tauri.conf.json @@ -17,7 +17,7 @@ "createUpdaterArtifacts": false }, "productName": "easytier-gui", - "version": "2.1.1", + "version": "2.1.2", "identifier": "com.kkrainbow.easytier", "plugins": {}, "app": { diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index 0072d09..0f1f0e8 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -3,7 +3,7 @@ name = "easytier" description = "A full meshed p2p VPN, connecting all your devices in one network with one command." homepage = "https://github.com/EasyTier/EasyTier" repository = "https://github.com/EasyTier/EasyTier" -version = "2.1.1" +version = "2.1.2" edition = "2021" authors = ["kkrainbow"] keywords = ["vpn", "p2p", "network", "easytier"] diff --git a/easytier/src/common/mod.rs b/easytier/src/common/mod.rs index 7c09493..7258bcc 100644 --- a/easytier/src/common/mod.rs +++ b/easytier/src/common/mod.rs @@ -1,6 +1,7 @@ use std::{ fmt::Debug, future, + io::Write as _, sync::{Arc, Mutex}, }; use tokio::task::JoinSet; @@ -81,7 +82,17 @@ pub fn join_joinset_background( } pub fn get_machine_id() -> uuid::Uuid { - // TODO: load from local file + // a path same as the binary + let machine_id_file = std::env::current_exe() + .map(|x| x.with_file_name("et_machine_id")) + .unwrap_or_else(|_| std::path::PathBuf::from("et_machine_id")); + + // try load from local file + if let Ok(mid) = std::fs::read_to_string(&machine_id_file) { + if let Ok(mid) = uuid::Uuid::parse_str(mid.trim()) { + return mid; + } + } #[cfg(any( target_os = "linux", @@ -95,7 +106,7 @@ pub fn get_machine_id() -> uuid::Uuid { crate::tunnel::generate_digest_from_str("", x.as_str(), &mut b); uuid::Uuid::from_bytes(b) }) - .unwrap_or(uuid::Uuid::new_v4()); + .ok(); #[cfg(not(any( target_os = "linux", @@ -103,9 +114,18 @@ pub fn get_machine_id() -> uuid::Uuid { target_os = "windows", target_os = "freebsd" )))] + let gen_mid = None; + + if gen_mid.is_some() { + return gen_mid.unwrap(); + } + let gen_mid = uuid::Uuid::new_v4(); - // TODO: save to local file + // try save to local file + if let Ok(mut file) = std::fs::File::create(machine_id_file) { + let _ = file.write_all(gen_mid.to_string().as_bytes()); + } gen_mid } diff --git a/easytier/src/instance/listeners.rs b/easytier/src/instance/listeners.rs index 5750f2b..91aceb2 100644 --- a/easytier/src/instance/listeners.rs +++ b/easytier/src/instance/listeners.rs @@ -163,7 +163,7 @@ impl ListenerManage Err(e) => { global_ctx.issue_event(GlobalCtxEvent::ListenerAddFailed( l.local_url(), - e.to_string(), + format!("error: {:?}, retry listen later...", e), )); tracing::error!(?e, ?l, "listener listen error"); tokio::time::sleep(std::time::Duration::from_secs(1)).await; @@ -176,7 +176,7 @@ impl ListenerManage Err(e) => { global_ctx.issue_event(GlobalCtxEvent::ListenerAcceptFailed( l.local_url(), - format!("error: {}, retry listen later...", e.to_string()), + format!("error: {:?}, retry listen later...", e), )); tracing::error!(?e, ?l, "listener accept error"); tokio::time::sleep(std::time::Duration::from_secs(1)).await; diff --git a/easytier/src/tunnel/tcp.rs b/easytier/src/tunnel/tcp.rs index 005e26c..d5efaf7 100644 --- a/easytier/src/tunnel/tcp.rs +++ b/easytier/src/tunnel/tcp.rs @@ -28,6 +28,30 @@ impl TcpTunnelListener { listener: None, } } + + async fn do_accept(&mut self) -> Result, std::io::Error> { + let listener = self.listener.as_ref().unwrap(); + let (stream, _) = listener.accept().await?; + + if let Err(e) = stream.set_nodelay(true) { + tracing::warn!(?e, "set_nodelay fail in accept"); + } + + let info = TunnelInfo { + tunnel_type: "tcp".to_owned(), + local_addr: Some(self.local_url().into()), + remote_addr: Some( + super::build_url_from_socket_addr(&stream.peer_addr()?.to_string(), "tcp").into(), + ), + }; + + let (r, w) = stream.into_split(); + Ok(Box::new(TunnelWrapper::new( + FramedReader::new(r, TCP_MTU_BYTES), + FramedWriter::new(w), + Some(info), + ))) + } } #[async_trait] @@ -57,27 +81,23 @@ impl TunnelListener for TcpTunnelListener { } async fn accept(&mut self) -> Result, super::TunnelError> { - let listener = self.listener.as_ref().unwrap(); - let (stream, _) = listener.accept().await?; - - if let Err(e) = stream.set_nodelay(true) { - tracing::warn!(?e, "set_nodelay fail in accept"); + loop { + match self.do_accept().await { + Ok(ret) => return Ok(ret), + Err(e) => { + use std::io::ErrorKind::*; + if matches!( + e.kind(), + NotConnected | ConnectionAborted | ConnectionRefused | ConnectionReset + ) { + tracing::warn!(?e, "accept fail with retryable error: {:?}", e); + continue; + } + tracing::warn!(?e, "accept fail"); + return Err(e.into()); + } + } } - - let info = TunnelInfo { - tunnel_type: "tcp".to_owned(), - local_addr: Some(self.local_url().into()), - remote_addr: Some( - super::build_url_from_socket_addr(&stream.peer_addr()?.to_string(), "tcp").into(), - ), - }; - - let (r, w) = stream.into_split(); - Ok(Box::new(TunnelWrapper::new( - FramedReader::new(r, TCP_MTU_BYTES), - FramedWriter::new(w), - Some(info), - ))) } fn local_url(&self) -> url::Url {