BeyondDimension/SteamTools · error · AggregateException
Unable to connect to {host}
Error message
Unable to connect to {host} What it means
Thrown by TunnelMiddleware when none of the resolved upstream endpoints for a host could be connected to within connectTimeout. Each per-endpoint failure is collected into innerExceptions (after honouring cancellation) and then aggregated, naming the host that could not be reached.
Source
Thrown at src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Services.Implementation/HttpServer/Middleware/TunnelMiddleware.cs:85
var innerExceptions = new List<Exception>();
await foreach (var endPoint in GetUpstreamEndPointsAsync(host, cancellationToken))
{
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
try
{
using var timeoutTokenSource = new CancellationTokenSource(connectTimeout);
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutTokenSource.Token);
await socket.ConnectAsync(endPoint, linkedTokenSource.Token);
return new NetworkStream(socket, ownsSocket: true);
}
catch (Exception ex)
{
socket.Dispose();
cancellationToken.ThrowIfCancellationRequested();
innerExceptions.Add(ex);
}
}
throw new AggregateException($"Unable to connect to {host}", innerExceptions);
}
/// <summary>
/// 获取目标终节点
/// </summary>
/// <param name="host"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
async IAsyncEnumerable<EndPoint> GetUpstreamEndPointsAsync(HostString host, [EnumeratorCancellation] CancellationToken cancellationToken)
{
const int HTTPS_PORT = 443;
var targetHost = host.Host;
var targetPort = host.Port ?? HTTPS_PORT;
if (IPAddress.TryParse(targetHost, out var address) == true)
{
yield return new IPEndPoint(address, targetPort);
}View on GitHub (pinned to c16ffa08e0)
Solutions
- Read innerExceptions to distinguish timeout vs connection-refused vs DNS failure.
- Confirm the host resolves and its service port (typically 443) is reachable from the host machine.
- Validate the Host header being forwarded is correct and not mangled by rewriting.
- Switch accelerator node / route, or disable the tunnel temporarily to confirm the target itself is up.
Example fix
// before
throw new AggregateException($"Unable to connect to {host}", innerExceptions);
// after: include attempt count and dominant error for faster diagnosis
var dominant = innerExceptions.GroupBy(e => e.GetType())
.OrderByDescending(g => g.Count()).First().Key.Name;
throw new AggregateException(
$"Unable to connect to {host} ({innerExceptions.Count} attempts, dominant: {dominant})",
innerExceptions); Defensive patterns
Strategy: retry
Validate before calling
// Validate the host and reachability before tunnelling.
if (!HostString.IsValid(host.Host) || host.Port is < 1 or > 65535)
throw new ArgumentException($"Invalid upstream host: {host}.");
async Task<bool> IsUpstreamReachableAsync(HostString h, CancellationToken ct)
{
await foreach (var ep in GetUpstreamEndPointsAsync(h, ct))
{
using var s = new TcpClient();
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(connectTimeout);
try { await s.ConnectAsync(ep, cts.Token); return true; } catch { }
}
return false;
} Try / catch
try { await ForwardTunnelAsync(host, ct); }
catch (AggregateException ex) when (ex.Message.StartsWith("Unable to connect to"))
{
if (attempt < maxAttempts && ex.InnerExceptions.Any(e => e is TimeoutException))
{ await Task.Delay(backoff, ct); goto retry; }
// Return 502 to the client with the host name; do not leak inner exceptions.
context.Response.StatusCode = 502;
await context.Response.WriteAsync($"Bad gateway: {host}", ct);
} Prevention
- Configure a short connectTimeout so dead upstreams fail fast and trigger failover.
- Maintain an allowlist of healthy upstream endpoints and refresh it periodically.
- Return a clean 502/503 to clients instead of letting the AggregateException surface.
- Monitor connect-failure rates per host to catch degraded upstreams early.
When it happens
Trigger: Forwarding a tunneled (e.g. HTTPS CONNECT-style) request where socket.ConnectAsync fails or times out for every endpoint returned by GetUpstreamEndPointsAsync for the HostString.
Common situations: Target service offline; DNS for the host resolves to unreachable IPs; port 443 (HTTPS_PORT) blocked by firewall/ISP; accelerator node behind the host is down; host header malformed so resolution yields bad endpoints.
Related errors
- Could not find any IP that can be successfully connected.
- Unable to connect to {endPoint.Host}:{endPoint.Port}.
- TCP port {httpProxyPort} is already occupied by other proces
- Failed to get available ports. There are no available ports.
AI-assisted analysis of BeyondDimension/SteamTools@c16ffa08e0 (2026-08-13).
Data as JSON: /api/errors/29bd45c14a0040ec.
Report an issue: GitHub.