mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 10:14:35 +00:00
fix(connector): skip self-connection when peer shares local interface IPs (#1941)
When two EasyTier instances run on the same machine and share the same network, the direct connector would expand a remote peer's 0.0.0.0 listener into local interface IPs and then attempt to connect to itself, causing an infinite loop of failed connection attempts. The existing `peer_id != my_peer_id` guard does not cover this case because the two instances have different peer IDs despite sharing the same physical network interfaces. Fix by adding a self-connection check in `spawn_direct_connect_task`: before spawning a connect task, compare the candidate (scheme, IP, port) against the local running listeners. If a local listener matches on all three dimensions — accounting for 0.0.0.0/:: wildcards by checking membership in the local interface IP sets — the candidate is silently dropped with a DEBUG log message. The fix covers all four code paths: - IPv4 unspecified (0.0.0.0) expansion loop - IPv4 specific-address branch - IPv6 unspecified (::) expansion loop - IPv6 specific-address branch The TESTING flag logic is untouched so existing unit tests are unaffected. * refactor(connector): replace is_self_connect closure with GlobalCtx::should_deny_proxy (#1954) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
net::{Ipv6Addr, SocketAddr},
|
net::{IpAddr, Ipv6Addr, SocketAddr},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
@@ -294,14 +294,42 @@ impl DirectConnectorManagerData {
|
|||||||
};
|
};
|
||||||
let listener_host = addrs.pop();
|
let listener_host = addrs.pop();
|
||||||
tracing::info!(?listener_host, ?listener, "try direct connect to peer");
|
tracing::info!(?listener_host, ?listener, "try direct connect to peer");
|
||||||
|
|
||||||
|
let is_udp = matches!(listener.scheme(), "udp" | "wg");
|
||||||
|
// Snapshot running listeners once; used for cheap port pre-checks before the
|
||||||
|
// expensive should_deny_proxy call (which binds a socket per IP) in the
|
||||||
|
// unspecified-address expansion loops below.
|
||||||
|
let local_listeners = self.global_ctx.get_running_listeners();
|
||||||
|
let port_has_local_listener = |port: u16| -> bool {
|
||||||
|
local_listeners
|
||||||
|
.iter()
|
||||||
|
.any(|l| l.port() == Some(port) && (matches!(l.scheme(), "udp" | "wg") == is_udp))
|
||||||
|
};
|
||||||
|
|
||||||
match listener_host {
|
match listener_host {
|
||||||
Some(SocketAddr::V4(s_addr)) => {
|
Some(SocketAddr::V4(s_addr)) => {
|
||||||
if s_addr.ip().is_unspecified() {
|
if s_addr.ip().is_unspecified() {
|
||||||
|
// Only pay the should_deny_proxy cost (bind per IP) when a local
|
||||||
|
// listener actually uses this port+protocol; otherwise the check
|
||||||
|
// can never return true.
|
||||||
|
let check_self = port_has_local_listener(s_addr.port());
|
||||||
ip_list
|
ip_list
|
||||||
.interface_ipv4s
|
.interface_ipv4s
|
||||||
.iter()
|
.iter()
|
||||||
.chain(ip_list.public_ipv4.iter())
|
.chain(ip_list.public_ipv4.iter())
|
||||||
.for_each(|ip| {
|
.for_each(|ip| {
|
||||||
|
let sock_addr = SocketAddr::new(
|
||||||
|
IpAddr::V4(std::net::Ipv4Addr::from(ip.addr)),
|
||||||
|
s_addr.port(),
|
||||||
|
);
|
||||||
|
if check_self && self.global_ctx.should_deny_proxy(&sock_addr, is_udp) {
|
||||||
|
tracing::debug!(
|
||||||
|
?ip,
|
||||||
|
?listener,
|
||||||
|
"skip self-connection (0.0.0.0 expansion)"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let mut addr = (*listener).clone();
|
let mut addr = (*listener).clone();
|
||||||
if addr.set_host(Some(ip.to_string().as_str())).is_ok() {
|
if addr.set_host(Some(ip.to_string().as_str())).is_ok() {
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
@@ -319,16 +347,26 @@ impl DirectConnectorManagerData {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
if self
|
||||||
self.clone(),
|
.global_ctx
|
||||||
dst_peer_id,
|
.should_deny_proxy(&SocketAddr::from(s_addr), is_udp)
|
||||||
listener.to_string(),
|
{
|
||||||
));
|
tracing::debug!(?listener, "skip self-connection (specific IPv4)");
|
||||||
|
} else {
|
||||||
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
|
self.clone(),
|
||||||
|
dst_peer_id,
|
||||||
|
listener.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(SocketAddr::V6(s_addr)) => {
|
Some(SocketAddr::V6(s_addr)) => {
|
||||||
if s_addr.ip().is_unspecified() {
|
if s_addr.ip().is_unspecified() {
|
||||||
// for ipv6, only try public ip
|
// for ipv6, only try public ip
|
||||||
|
// Same port pre-check as IPv4: avoid binding per IP when no local
|
||||||
|
// listener uses this port+protocol.
|
||||||
|
let check_self = port_has_local_listener(s_addr.port());
|
||||||
ip_list
|
ip_list
|
||||||
.interface_ipv6s
|
.interface_ipv6s
|
||||||
.iter()
|
.iter()
|
||||||
@@ -345,6 +383,15 @@ impl DirectConnectorManagerData {
|
|||||||
.collect::<HashSet<_>>()
|
.collect::<HashSet<_>>()
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|ip| {
|
.for_each(|ip| {
|
||||||
|
let sock_addr = SocketAddr::new(IpAddr::V6(*ip), s_addr.port());
|
||||||
|
if check_self && self.global_ctx.should_deny_proxy(&sock_addr, is_udp) {
|
||||||
|
tracing::debug!(
|
||||||
|
?ip,
|
||||||
|
?listener,
|
||||||
|
"skip self-connection (:: expansion)"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let mut addr = (*listener).clone();
|
let mut addr = (*listener).clone();
|
||||||
if addr.set_host(Some(format!("[{}]", ip).as_str())).is_ok() {
|
if addr.set_host(Some(format!("[{}]", ip).as_str())).is_ok() {
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
@@ -362,11 +409,18 @@ impl DirectConnectorManagerData {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
} else if !s_addr.ip().is_loopback() || TESTING.load(Ordering::Relaxed) {
|
||||||
tasks.spawn(Self::try_connect_to_ip(
|
if self
|
||||||
self.clone(),
|
.global_ctx
|
||||||
dst_peer_id,
|
.should_deny_proxy(&SocketAddr::from(s_addr), is_udp)
|
||||||
listener.to_string(),
|
{
|
||||||
));
|
tracing::debug!(?listener, "skip self-connection (specific IPv6)");
|
||||||
|
} else {
|
||||||
|
tasks.spawn(Self::try_connect_to_ip(
|
||||||
|
self.clone(),
|
||||||
|
dst_peer_id,
|
||||||
|
listener.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p => {
|
p => {
|
||||||
|
|||||||
Reference in New Issue
Block a user