diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5297c288..e23a2bd7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -11,7 +11,7 @@ on: image_tag: description: 'Tag for this image build' type: string - default: 'v2.6.1' + default: 'v2.6.2' required: true mark_latest: description: 'Mark this image as latest' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e805b45f..039a27c7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ on: version: description: 'Version for this release' type: string - default: 'v2.6.1' + default: 'v2.6.2' required: true make_latest: description: 'Mark this release as latest' @@ -92,4 +92,4 @@ jobs: files: | ./zipped_assets/* token: ${{ secrets.GITHUB_TOKEN }} - tag_name: ${{ inputs.version }} \ No newline at end of file + tag_name: ${{ inputs.version }} diff --git a/Cargo.lock b/Cargo.lock index 3e78c8c5..156ccb62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2229,7 +2229,7 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "easytier" -version = "2.6.1" +version = "2.6.2" dependencies = [ "aes-gcm", "anyhow", @@ -2404,7 +2404,7 @@ dependencies = [ [[package]] name = "easytier-gui" -version = "2.6.1" +version = "2.6.2" dependencies = [ "anyhow", "async-trait", @@ -2484,7 +2484,7 @@ dependencies = [ [[package]] name = "easytier-web" -version = "2.6.1" +version = "2.6.2" dependencies = [ "anyhow", "async-trait", diff --git a/README.md b/README.md index 00df972f..e884bffa 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,9 @@ After successful execution, you can check the network status using `easytier-cli ```text | ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | version | | ------------ | -------------- | ----- | ------ | --------- | -------- | -------- | ------------ | -------- | ---------- | --------------- | -| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.6.1-70e69a38~ | -| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.6.1-70e69a38~ | -| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.6.1-70e69a38~ | +| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.6.2-70e69a38~ | +| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.6.2-70e69a38~ | +| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.6.2-70e69a38~ | ``` You can test connectivity between nodes: diff --git a/README_CN.md b/README_CN.md index 26142595..3d22be93 100644 --- a/README_CN.md +++ b/README_CN.md @@ -108,9 +108,9 @@ sudo easytier-core -d --network-name abc --network-secret abc -p tcp://<共享 ```text | ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | version | | ------------ | -------------- | ----- | ------ | --------- | -------- | -------- | ------------ | -------- | ---------- | --------------- | -| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.6.1-70e69a38~ | -| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.6.1-70e69a38~ | -| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.6.1-70e69a38~ | +| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.6.2-70e69a38~ | +| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.6.2-70e69a38~ | +| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.6.2-70e69a38~ | ``` 您可以测试节点之间的连通性: diff --git a/easytier-contrib/easytier-magisk/module.prop b/easytier-contrib/easytier-magisk/module.prop index 852e02fe..086597ca 100644 --- a/easytier-contrib/easytier-magisk/module.prop +++ b/easytier-contrib/easytier-magisk/module.prop @@ -1,6 +1,6 @@ id=easytier_magisk name=EasyTier_Magisk -version=v2.6.1 +version=v2.6.2 versionCode=1 author=EasyTier description=easytier magisk module @EasyTier(https://github.com/EasyTier/EasyTier) diff --git a/easytier-gui/package.json b/easytier-gui/package.json index db26ee2c..bf1ff7bc 100644 --- a/easytier-gui/package.json +++ b/easytier-gui/package.json @@ -1,7 +1,7 @@ { "name": "easytier-gui", "type": "module", - "version": "2.6.1", + "version": "2.6.2", "private": true, "packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4", "scripts": { @@ -59,4 +59,4 @@ "vue-i18n": "^10.0.0", "vue-tsc": "^2.1.10" } -} \ No newline at end of file +} diff --git a/easytier-gui/src-tauri/Cargo.toml b/easytier-gui/src-tauri/Cargo.toml index 38cb414f..f629ad1e 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.6.1" +version = "2.6.2" description = "EasyTier GUI" authors = ["you"] edition.workspace = true diff --git a/easytier-gui/src-tauri/tauri.conf.json b/easytier-gui/src-tauri/tauri.conf.json index 516457f0..b13b4806 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.6.1", + "version": "2.6.2", "identifier": "com.kkrainbow.easytier", "plugins": { "shell": { @@ -36,4 +36,4 @@ "csp": null } } -} \ No newline at end of file +} diff --git a/easytier-web/Cargo.toml b/easytier-web/Cargo.toml index 1d4ab306..1c1d9b7d 100644 --- a/easytier-web/Cargo.toml +++ b/easytier-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "easytier-web" -version = "2.6.1" +version = "2.6.2" edition.workspace = true description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server." diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index 7aa7d64b..99c07751 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.6.1" +version = "2.6.2" edition.workspace = true rust-version.workspace = true authors = ["kkrainbow"] diff --git a/easytier/src/common/upnp.rs b/easytier/src/common/upnp.rs index 61ec357f..d86bda8c 100644 --- a/easytier/src/common/upnp.rs +++ b/easytier/src/common/upnp.rs @@ -5,6 +5,9 @@ use std::{ time::Duration, }; +#[cfg(test)] +use std::sync::atomic::{AtomicUsize, Ordering}; + use anyhow::{Context, anyhow, bail}; use igd_next::{ AddAnyPortError, PortMappingProtocol, SearchOptions, @@ -35,6 +38,19 @@ const PORT_MAPPING_BACKEND_IGD: &str = "igd"; type TokioGateway = Gateway; +#[cfg(test)] +static UDP_PORT_MAPPING_ATTEMPTS: AtomicUsize = AtomicUsize::new(0); + +#[cfg(test)] +pub(crate) fn reset_udp_port_mapping_attempts_for_test() { + UDP_PORT_MAPPING_ATTEMPTS.store(0, Ordering::Relaxed); +} + +#[cfg(test)] +pub(crate) fn udp_port_mapping_attempts_for_test() -> usize { + UDP_PORT_MAPPING_ATTEMPTS.load(Ordering::Relaxed) +} + enum PortMappingBackend { NatPmp { gateway: Ipv4Addr }, Igd { gateway: TokioGateway }, @@ -262,6 +278,9 @@ async fn try_start_udp_port_mapping( return Ok(None); } + #[cfg(test)] + UDP_PORT_MAPPING_ATTEMPTS.fetch_add(1, Ordering::Relaxed); + let mapping = discover_udp_port_mapping(global_ctx.clone(), local_listener.clone()).await?; tracing::info!( %local_listener, diff --git a/easytier/src/connector/udp_hole_punch/cone.rs b/easytier/src/connector/udp_hole_punch/cone.rs index 5d75f981..fc36a322 100644 --- a/easytier/src/connector/udp_hole_punch/cone.rs +++ b/easytier/src/connector/udp_hole_punch/cone.rs @@ -113,25 +113,7 @@ impl PunchConeHoleClient { let global_ctx = self.peer_mgr.get_global_ctx(); let udp_array = UdpSocketArray::new(1, global_ctx.net_ns.clone()); - let local_socket = { - let _g = self.peer_mgr.get_global_ctx().net_ns.guard(); - Arc::new(UdpSocket::bind("0.0.0.0:0").await?) - }; - let local_addr = local_socket - .local_addr() - .with_context(|| "failed to get local addr from udp punch socket")?; - let local_listener: url::Url = format!("udp://0.0.0.0:{}", local_addr.port()) - .parse() - .unwrap(); - let (local_mapped_addr, _local_port_mapping_lease) = upnp::resolve_udp_public_addr( - global_ctx.clone(), - &local_listener, - local_socket.clone(), - ) - .await - .with_context(|| "failed to resolve udp public addr for cone hole punch")?; - // client -> server: tell server the mapped port, server will return the mapped address of listening port. let rpc_stub = self .peer_mgr .get_peer_rpc_mgr() @@ -158,6 +140,24 @@ impl PunchConeHoleClient { "select_punch_listener response missing listener_mapped_addr" ))?; + let local_socket = { + let _g = self.peer_mgr.get_global_ctx().net_ns.guard(); + Arc::new(UdpSocket::bind("0.0.0.0:0").await?) + }; + let local_addr = local_socket + .local_addr() + .with_context(|| "failed to get local addr from udp punch socket")?; + let local_listener: url::Url = format!("udp://0.0.0.0:{}", local_addr.port()) + .parse() + .unwrap(); + let (local_mapped_addr, _local_port_mapping_lease) = upnp::resolve_udp_public_addr( + global_ctx.clone(), + &local_listener, + local_socket.clone(), + ) + .await + .with_context(|| "failed to resolve udp public addr for cone hole punch")?; + tracing::debug!( ?local_mapped_addr, ?remote_mapped_addr, @@ -245,10 +245,15 @@ impl PunchConeHoleClient { #[cfg(test)] pub mod tests { + use std::sync::Arc; use crate::{ + common::upnp::{ + reset_udp_port_mapping_attempts_for_test, udp_port_mapping_attempts_for_test, + }, connector::udp_hole_punch::{ - UdpHolePunchConnector, tests::create_mock_peer_manager_with_mock_stun, + UdpHolePunchConnector, cone::PunchConeHoleClient, + tests::create_mock_peer_manager_with_mock_stun, }, peers::tests::{connect_peer_manager, wait_route_appear, wait_route_appear_with_cost}, proto::common::NatType, @@ -279,4 +284,27 @@ pub mod tests { .unwrap(); println!("{:?}", p_a.list_routes().await); } + + #[tokio::test] + async fn cone_hole_punch_does_not_create_upnp_mapping_before_listener_rpc_succeeds() { + let p_a = create_mock_peer_manager_with_mock_stun(NatType::Restricted).await; + let p_b = create_mock_peer_manager_with_mock_stun(NatType::PortRestricted).await; + let p_c = create_mock_peer_manager_with_mock_stun(NatType::Restricted).await; + connect_peer_manager(p_a.clone(), p_b.clone()).await; + connect_peer_manager(p_b.clone(), p_c.clone()).await; + wait_route_appear(p_a.clone(), p_c.clone()).await.unwrap(); + + let mut flags = p_a.get_global_ctx().get_flags(); + flags.disable_upnp = false; + p_a.get_global_ctx().set_flags(flags); + + reset_udp_port_mapping_attempts_for_test(); + + let ret = PunchConeHoleClient::new(p_a.clone(), Arc::new(timedmap::TimedMap::new())) + .do_hole_punching(p_c.my_peer_id()) + .await; + + assert!(ret.is_err()); + assert_eq!(udp_port_mapping_attempts_for_test(), 0); + } } diff --git a/script/install.ps1 b/script/install.ps1 index c909e04a..b6aa1d28 100644 --- a/script/install.ps1 +++ b/script/install.ps1 @@ -7,7 +7,7 @@ Copies binaries to the install directory and updates the system PATH. .PARAMETER Version - Target version: "latest", "stable", or a specific tag like "v2.6.1". + Target version: "latest", "stable", or a specific tag like "v2.6.2". Default: "latest" .PARAMETER InstallDir @@ -16,7 +16,7 @@ .EXAMPLE .\install.ps1 - .\install.ps1 -Version v2.6.1 + .\install.ps1 -Version v2.6.2 .\install.ps1 -InstallDir "C:\EasyTier" .NOTES