allow loopback src address in listener (#1730)

This commit is contained in:
KKRainbow
2026-01-01 00:41:56 +08:00
committed by GitHub
parent 7c563153ae
commit 4e651a72f7
7 changed files with 99 additions and 52 deletions
+12
View File
@@ -303,6 +303,18 @@ impl GlobalCtx {
}
}
pub fn is_port_in_running_listeners(&self, port: u16, is_udp: bool) -> bool {
let check_proto = |listener_proto: &str| {
let listener_is_udp = matches!(listener_proto, "udp" | "wg");
listener_is_udp == is_udp
};
self.running_listeners
.lock()
.unwrap()
.iter()
.any(|x| x.port() == Some(port) && check_proto(x.scheme()))
}
pub fn get_vpn_portal_cidr(&self) -> Option<cidr::Ipv4Cidr> {
self.config.get_vpn_portal_config().map(|x| x.client_cidr)
}
+9
View File
@@ -507,6 +507,15 @@ impl KcpProxyDst {
Some(dst_socket.ip()) == global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address()));
if send_to_self && global_ctx.no_tun() {
if global_ctx.is_port_in_running_listeners(dst_socket.port(), false)
&& global_ctx.is_ip_in_same_network(&src_ip)
{
return Err(anyhow::anyhow!(
"dst socket {:?} is in running listeners, ignore it",
dst_socket
)
.into());
}
dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap();
}
+9
View File
@@ -416,6 +416,15 @@ impl QUICProxyDst {
let send_to_self = Some(*dst_socket.ip()) == ctx.get_ipv4().map(|ip| ip.address());
if send_to_self && ctx.no_tun() {
if ctx.is_port_in_running_listeners(dst_socket.port(), false)
&& ctx.is_ip_in_same_network(&src_ip)
{
return Err(anyhow::anyhow!(
"dst socket {:?} is in running listeners, ignore it",
dst_socket
)
.into());
}
dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap();
}
+12
View File
@@ -733,6 +733,18 @@ impl<C: NatDstConnector> TcpProxy<C> {
let nat_dst = if Some(nat_entry.real_dst.ip())
== global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address()))
{
if global_ctx.is_port_in_running_listeners(nat_entry.real_dst.port(), false)
&& global_ctx.is_ip_in_same_network(&nat_entry.src.ip())
{
tracing::error!(
?nat_entry,
"nat dst port {} is in running listeners, ignore it",
nat_entry.real_dst.port()
);
nat_entry.state.store(NatDstEntryState::Closed);
Self::remove_entry_from_all_conn_map(conn_map, addr_conn_map, nat_entry);
return;
}
format!("127.0.0.1:{}", nat_entry.real_dst.port())
.parse()
.unwrap()
+24 -11
View File
@@ -298,6 +298,30 @@ impl UdpProxy {
udp::UdpPacket::new(ipv4.payload())?
};
// TODO: should it be async.
let dst_socket = if Some(ipv4.get_destination())
== self.global_ctx.get_ipv4().as_ref().map(Ipv4Inet::address)
{
if self
.global_ctx
.is_port_in_running_listeners(udp_packet.get_destination(), true)
&& self
.global_ctx
.is_ip_in_same_network(&std::net::IpAddr::V4(ipv4.get_source()))
{
tracing::debug!(
dst_port = udp_packet.get_destination(),
"dst socket is in running listeners, ignore it"
);
return Some(());
}
format!("127.0.0.1:{}", udp_packet.get_destination())
.parse()
.unwrap()
} else {
SocketAddr::new(real_dst_ip.into(), udp_packet.get_destination())
};
tracing::trace!(
?packet,
?ipv4,
@@ -339,17 +363,6 @@ impl UdpProxy {
nat_entry.mark_active();
// TODO: should it be async.
let dst_socket = if Some(ipv4.get_destination())
== self.global_ctx.get_ipv4().as_ref().map(Ipv4Inet::address)
{
format!("127.0.0.1:{}", udp_packet.get_destination())
.parse()
.unwrap()
} else {
SocketAddr::new(real_dst_ip.into(), udp_packet.get_destination())
};
let send_ret = {
let _g = self.global_ctx.net_ns.guard();
nat_entry
+17 -39
View File
@@ -1,6 +1,6 @@
use std::{
fmt::Debug,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
net::{IpAddr, Ipv4Addr, Ipv6Addr},
sync::{atomic::AtomicBool, Arc, Weak},
time::{Instant, SystemTime},
};
@@ -416,47 +416,25 @@ impl PeerManager {
if src.scheme() == "ring" {
return Ok(());
}
let src_host = match src.socket_addrs(|| Some(1)) {
Ok(addrs) => addrs,
Err(_) => {
// if the tunnel is not rely on ip address, skip check
return Ok(());
}
let Ok(Some(addr)) = src.socket_addrs(|| Some(1)).map(|x| x.first().cloned()) else {
// if the tunnel is not rely on ip address, skip check
return Ok(());
};
let virtual_ipv4 = self.global_ctx.get_ipv4().map(|ip| ip.network());
let virtual_ipv6 = self.global_ctx.get_ipv6().map(|ip| ip.network());
tracing::info!(
?virtual_ipv4,
?virtual_ipv6,
"check remote addr not from virtual network"
);
for addr in src_host {
// if no-tun is enabled, the src ip of packet in virtual network is converted to loopback address
if addr.ip().is_loopback()
&& !self
.allow_loopback_tunnel
.load(std::sync::atomic::Ordering::Relaxed)
{
anyhow::bail!("tunnel src host is loopback address");
}
match addr {
SocketAddr::V4(addr) => {
if let Some(virtual_ipv4) = virtual_ipv4 {
if virtual_ipv4.contains(addr.ip()) {
anyhow::bail!("tunnel src host is from the virtual network (ignore this error please)");
}
}
}
SocketAddr::V6(addr) => {
if let Some(virtual_ipv6) = virtual_ipv6 {
if virtual_ipv6.contains(addr.ip()) {
anyhow::bail!("tunnel src host is from the virtual network (ignore this error please)");
}
}
}
}
// if no-tun is enabled, the src ip of packet in virtual network is converted to loopback address
// we already filter out the connection in tcp/quic/kcp proxy so no need check here.
if addr.ip().is_loopback() {
// allow other loopback address, good for conn from cdn/l4 connection
return Ok(());
}
if self.global_ctx.is_ip_in_same_network(&addr.ip()) {
anyhow::bail!(
"tunnel src {} is from the same network (ignore this error please)",
addr
);
}
Ok(())
}
+16 -2
View File
@@ -1481,9 +1481,23 @@ pub async fn relay_bps_limit_test(#[values(100, 200, 400, 800)] bps_limit: u64)
drop_insts(insts).await;
}
#[rstest::rstest]
#[serial_test::serial]
#[tokio::test]
async fn avoid_tunnel_loop_back_to_virtual_network() {
let insts = init_three_node("udp").await;
async fn avoid_tunnel_loop_back_to_virtual_network(#[values(true, false)] no_tun: bool) {
let insts = init_three_node_ex(
"udp",
|cfg| {
if matches!(cfg.get_inst_name().as_str(), "inst2" | "inst3") {
let mut flags = cfg.get_flags();
flags.no_tun = no_tun;
cfg.set_flags(flags);
}
cfg
},
false,
)
.await;
let tcp_connector = TcpTunnelConnector::new("tcp://10.144.144.2:11010".parse().unwrap());
insts[0]