mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-29 16:05:35 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a24cc0a1bb |
@@ -303,7 +303,14 @@ pub(crate) async fn bind_tcp_listener(
|
||||
let addr = bind_options.local_addr.ok_or_else(|| {
|
||||
TunnelError::InvalidAddr("tcp listener requires a local bind address".to_owned())
|
||||
})?;
|
||||
if bind_options.bind_device.is_none() && purpose == TcpListenPurpose::PortLease {
|
||||
if bind_options.bind_device.is_none()
|
||||
&& matches!(
|
||||
purpose,
|
||||
TcpListenPurpose::PortLease | TcpListenPurpose::PortForward
|
||||
)
|
||||
{
|
||||
// Port forwards follow the bind IP across interface recreation. An
|
||||
// implicit device binding would retain the deleted interface's index.
|
||||
bind_options.bind_device = Some(String::new());
|
||||
}
|
||||
let listener = create_tcp_socket(addr, &bind_options).await?.listen(1024)?;
|
||||
@@ -363,6 +370,22 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[rstest::rstest]
|
||||
#[tokio::test]
|
||||
async fn port_forward_listener_honors_device_binding(
|
||||
#[values(None, Some("lo"), Some(""))] device: Option<&str>,
|
||||
) {
|
||||
let mut options = TcpListenOptions::port_forward("127.0.0.1:0".parse().unwrap());
|
||||
options.bind.bind_device = device.map(str::to_owned);
|
||||
let listener = bind_tcp_listener(options).await.unwrap();
|
||||
let bound = SockRef::from(&listener.listener).device().unwrap();
|
||||
assert_eq!(
|
||||
bound.as_deref(),
|
||||
device.filter(|name| !name.is_empty()).map(str::as_bytes)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn runtime_tcp_owned_split_preserves_full_duplex_and_shutdown() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
|
||||
@@ -146,11 +146,11 @@ impl RuntimeUdpSocketFactory {
|
||||
return BindDev::from(bind_device.as_str());
|
||||
}
|
||||
|
||||
// Port forwards bind only their local address by default, so replacing
|
||||
// its interface does not strand the socket on the old interface index.
|
||||
if matches!(
|
||||
options.purpose,
|
||||
UdpSocketPurpose::DirectConnect
|
||||
| UdpSocketPurpose::PortBoundListener
|
||||
| UdpSocketPurpose::PortForward
|
||||
UdpSocketPurpose::DirectConnect | UdpSocketPurpose::PortBoundListener
|
||||
) {
|
||||
return BindDev::Auto;
|
||||
}
|
||||
@@ -438,7 +438,7 @@ mod tests {
|
||||
));
|
||||
assert!(matches!(
|
||||
factory.bind_device_for(&UdpBindOptions::port_forward(listener_addr)),
|
||||
BindDev::Auto
|
||||
BindDev::Disabled
|
||||
));
|
||||
assert!(matches!(
|
||||
factory.bind_device_for(&UdpBindOptions::port_lease(listener_addr)),
|
||||
@@ -450,12 +450,16 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_applies_listener_bind_device_option() {
|
||||
#[rstest::rstest]
|
||||
fn factory_applies_listener_bind_device_option(#[values(false, true)] port_forward: bool) {
|
||||
let listener_addr = SocketAddr::from(([0, 0, 0, 0], 11010));
|
||||
let factory = RuntimeUdpSocketFactory::new();
|
||||
let options = UdpBindOptions::port_bound_listener(listener_addr)
|
||||
.with_bind_device(Some("eth0".to_owned()));
|
||||
let options = if port_forward {
|
||||
UdpBindOptions::port_forward(listener_addr)
|
||||
} else {
|
||||
UdpBindOptions::port_bound_listener(listener_addr)
|
||||
}
|
||||
.with_bind_device(Some("eth0".to_owned()));
|
||||
|
||||
match factory.bind_device_for(&options) {
|
||||
BindDev::Custom(dev) => assert_eq!(dev, "eth0"),
|
||||
|
||||
@@ -2937,6 +2937,84 @@ pub async fn manual_reconnector(#[values(true, false)] is_foreign: bool) {
|
||||
drop_insts(vec![center_inst, inst1, inst2]).await;
|
||||
}
|
||||
|
||||
#[rstest::rstest]
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn port_forward_survives_interface_recreation(#[values("tcp", "udp")] proto: &str) {
|
||||
let network = prepare_linux_namespaces();
|
||||
let namespace = network.namespace("net_a");
|
||||
let create_tun = || {
|
||||
run_ip_in_ns(
|
||||
namespace,
|
||||
&["tuntap", "add", "dev", "pf_tun", "mode", "tun"],
|
||||
);
|
||||
run_ip_in_ns(namespace, &["addr", "add", "192.0.2.1/24", "dev", "pf_tun"]);
|
||||
run_ip_in_ns(namespace, &["link", "set", "pf_tun", "up"]);
|
||||
run_ip_in_ns_output(namespace, &["-o", "link", "show", "pf_tun"])
|
||||
.split(':')
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
};
|
||||
let original_index = create_tun();
|
||||
let insts = init_three_node_ex(
|
||||
&network,
|
||||
"udp",
|
||||
|cfg| {
|
||||
if cfg.get_inst_name() == "inst1" {
|
||||
cfg.set_port_forwards(vec![PortForwardConfig {
|
||||
bind_addr: "192.0.2.1:23456".parse().unwrap(),
|
||||
dst_addr: "10.144.144.3:23456".parse().unwrap(),
|
||||
proto: proto.to_owned(),
|
||||
}]);
|
||||
}
|
||||
cfg
|
||||
},
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
for recreated in [false, true] {
|
||||
if recreated {
|
||||
run_ip_in_ns(namespace, &["link", "del", "pf_tun"]);
|
||||
assert_ne!(create_tun(), original_index);
|
||||
}
|
||||
// Keep the forwarding instance and its listener alive across deletion.
|
||||
// Reusing the interface name and IP must not leave it bound to the old
|
||||
// ifindex. Check an application round trip, including the return path.
|
||||
let listener_url = format!("{proto}://0.0.0.0:23456").parse().unwrap();
|
||||
let connector_url = format!("{proto}://192.0.2.1:23456").parse().unwrap();
|
||||
let server_ns = NetNS::new(Some(network.namespace("net_c").into()));
|
||||
let client_ns = NetNS::new(Some(namespace.into()));
|
||||
let payload = b"port-forward interface recreation".to_vec();
|
||||
let timeout = Duration::from_secs(5);
|
||||
let result = if proto == "tcp" {
|
||||
_tunnel_pingpong_netns_with_timeout(
|
||||
core_tcp_listener(listener_url),
|
||||
core_tcp_dialer(connector_url),
|
||||
server_ns,
|
||||
client_ns,
|
||||
payload,
|
||||
timeout,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
_tunnel_pingpong_netns_with_timeout(
|
||||
core_udp_listener(listener_url),
|
||||
core_udp_dialer(connector_url),
|
||||
server_ns,
|
||||
client_ns,
|
||||
payload,
|
||||
timeout,
|
||||
)
|
||||
.await
|
||||
};
|
||||
assert!(result.is_ok(), "{proto}, recreated={recreated}: {result:?}");
|
||||
}
|
||||
|
||||
drop_insts(insts).await;
|
||||
}
|
||||
|
||||
#[rstest::rstest]
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
|
||||
Reference in New Issue
Block a user