Skip to content

Commit 02833ca

Browse files
revert(agent-tunnel): back out dual-stack QUIC bind from this PR
Maintainer asked for the dual-stack listener change to ship as its own PR (#1741 review on `service.rs`). Restore the original v4-only bind on both ends so this PR is back to "transparent routing" scope only: * gateway listener — `Ipv4Addr::UNSPECIFIED` instead of `Ipv6Addr::UNSPECIFIED` * agent client — `0.0.0.0:0` instead of the `[::]:0` + IPv4 fallback * remove the `bind_dual_stack_endpoint` / `build_dual_stack_v6_socket` helpers and the `socket2` dependency from `agent-tunnel` The dual-stack work is being re-submitted on a separate branch.
1 parent 2c2a148 commit 02833ca

5 files changed

Lines changed: 6 additions & 73 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/agent-tunnel/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ time = { version = "0.3", default-features = false, features = ["std"] }
4040

4141
# QUIC
4242
quinn = "0.11"
43-
socket2 = "0.5"
4443

4544
[dev-dependencies]
4645
base64 = "0.22"

crates/agent-tunnel/src/listener.rs

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
//! creates proxy streams on demand.
66
77
use std::collections::HashMap;
8-
use std::net::{Ipv4Addr, SocketAddr, UdpSocket};
8+
use std::net::SocketAddr;
99
use std::sync::Arc;
1010
use std::time::Duration;
1111

@@ -136,11 +136,10 @@ impl AgentTunnelListener {
136136

137137
server_config.transport_config(Arc::new(transport));
138138

139-
let endpoint = bind_dual_stack_endpoint(server_config, listen_addr)
139+
let endpoint = quinn::Endpoint::server(server_config, listen_addr)
140140
.with_context(|| format!("bind QUIC endpoint on {listen_addr}"))?;
141141

142-
let bound_addr = endpoint.local_addr().unwrap_or(listen_addr);
143-
info!(listen_addr = %bound_addr, "Agent tunnel QUIC endpoint bound");
142+
info!(%listen_addr, "Agent tunnel QUIC endpoint bound");
144143

145144
let registry = Arc::new(AgentRegistry::new());
146145
let agent_connections: Arc<RwLock<HashMap<Uuid, quinn::Connection>>> = Arc::new(RwLock::new(HashMap::new()));
@@ -395,51 +394,3 @@ async fn handle_control_message<S: tokio::io::AsyncWrite + Unpin, R: tokio::io::
395394
}
396395
}
397396
}
398-
399-
/// Bind a QUIC endpoint, preferring a dual-stack IPv6 socket so the listener
400-
/// accepts agents whose DNS resolution returns either IPv4 or IPv6.
401-
///
402-
/// `quinn::Endpoint::server` would otherwise honor the OS default for
403-
/// `IPV6_V6ONLY`, which is `0` (dual-stack) on Windows but `1` (v6-only) on
404-
/// Linux per RFC 3493. We explicitly clear the flag with `socket2`, then hand
405-
/// the socket to `quinn::Endpoint::new`. If the v6 bind fails entirely
406-
/// (e.g. IPv6 disabled on the host), we fall back to plain IPv4.
407-
fn bind_dual_stack_endpoint(
408-
server_config: quinn::ServerConfig,
409-
listen_addr: SocketAddr,
410-
) -> anyhow::Result<quinn::Endpoint> {
411-
if !listen_addr.is_ipv6() {
412-
return quinn::Endpoint::server(server_config, listen_addr).map_err(Into::into);
413-
}
414-
415-
let socket = match build_dual_stack_v6_socket(listen_addr) {
416-
Ok(socket) => socket,
417-
Err(error) if listen_addr.ip().is_unspecified() => {
418-
let v4_addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, listen_addr.port()));
419-
warn!(%error, fallback = %v4_addr, "IPv6 dual-stack bind failed; falling back to IPv4");
420-
return quinn::Endpoint::server(server_config, v4_addr).map_err(Into::into);
421-
}
422-
Err(error) => return Err(error),
423-
};
424-
425-
let runtime = quinn::default_runtime().context("no quinn-compatible async runtime found")?;
426-
quinn::Endpoint::new(quinn::EndpointConfig::default(), Some(server_config), socket, runtime).map_err(Into::into)
427-
}
428-
429-
fn build_dual_stack_v6_socket(listen_addr: SocketAddr) -> anyhow::Result<UdpSocket> {
430-
let socket = socket2::Socket::new(
431-
socket2::Domain::IPV6,
432-
socket2::Type::DGRAM,
433-
Some(socket2::Protocol::UDP),
434-
)
435-
.context("create IPv6 UDP socket")?;
436-
437-
if let Err(error) = socket.set_only_v6(false) {
438-
warn!(%error, "set_only_v6(false) failed; listener may be IPv6-only");
439-
}
440-
441-
socket.set_nonblocking(true).context("set socket non-blocking")?;
442-
socket.bind(&listen_addr.into()).context("bind v6 UDP socket")?;
443-
444-
Ok(socket.into())
445-
}

devolutions-agent/src/tunnel.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -337,19 +337,8 @@ async fn run_single_connection(
337337

338338
// -- Connect --
339339

340-
// Prefer a dual-stack IPv6 client socket so we can reach gateway endpoints
341-
// resolved as IPv6 (default for `localhost` and any AAAA-bearing name on
342-
// modern systems). If the host has IPv6 disabled (common in stripped-down
343-
// Linux containers) the v6 bind fails outright; fall back to plain IPv4
344-
// rather than crash the agent with no route to take.
345-
let mut endpoint = match quinn::Endpoint::client("[::]:0".parse().expect("static [::]:0 parses")) {
346-
Ok(endpoint) => endpoint,
347-
Err(error) => {
348-
warn!(%error, "IPv6 client bind failed; falling back to IPv4");
349-
quinn::Endpoint::client("0.0.0.0:0".parse().expect("static 0.0.0.0:0 parses"))
350-
.context("create QUIC endpoint (IPv4 fallback)")?
351-
}
352-
};
340+
let mut endpoint =
341+
quinn::Endpoint::client("0.0.0.0:0".parse().context("parse bind address")?).context("create QUIC endpoint")?;
353342
endpoint.set_default_client_config(client_config);
354343

355344
let connection = endpoint

devolutions-gateway/src/service.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -283,12 +283,7 @@ async fn spawn_tasks(conf_handle: ConfHandle) -> anyhow::Result<Tasks> {
283283
let ca_manager = agent_tunnel::cert::CaManager::load_or_generate(&data_dir)
284284
.context("failed to initialize agent tunnel CA")?;
285285

286-
// Bind to the IPv6 unspecified address so the listener is dual-stack and
287-
// accepts both IPv4 and IPv6 agent connections (matters when an agent's DNS
288-
// resolution returns an IPv6 address for the configured gateway endpoint).
289-
// The listener crate explicitly clears `IPV6_V6ONLY` for portability across
290-
// OSes, and falls back to IPv4 if the host has IPv6 disabled.
291-
let listen_addr = std::net::SocketAddr::from((std::net::Ipv6Addr::UNSPECIFIED, conf.agent_tunnel.listen_port));
286+
let listen_addr = std::net::SocketAddr::from((std::net::Ipv4Addr::UNSPECIFIED, conf.agent_tunnel.listen_port));
292287

293288
let (listener, handle) =
294289
agent_tunnel::AgentTunnelListener::bind(listen_addr, Arc::clone(&ca_manager), hostname)

0 commit comments

Comments
 (0)