Skip to content

AddSqlServer connection string ignores TargetHost: EndpointProperty.IPV4Host is hard-coded to 127.0.0.1 in the localhost network context #19972

Description

Summary

Setting TargetHost on the SQL Server resource's tcp endpoint changes the DCP port binding and the allocated endpoint, but the connection string produced by SqlServerServerResource (and therefore the built-in sql_check / <db>_check health checks and the AddDatabase creation step) still uses 127.0.0.1. The cause is in EndpointReferenceExpression.GetValueAsync: EndpointProperty.IPV4Host returns the literal "127.0.0.1" whenever the network context is LocalhostNetwork, regardless of the endpoint's configured or allocated address.

This part of the report reproduces on any machine; no broken Docker is needed. The motivation (why anyone would set TargetHost to [::1]) is docker/desktop-feedback#622 (Windows IPv4-loopback defect, still present in Docker Desktop 4.90.0), but the bug itself is independent of it.

Versions

  • Aspire.Hosting 13.4.6 (+87fe259e), Aspire.Hosting.SqlServer 13.4.6, Aspire.Hosting.Testing 13.4.6, AppHost SDK 13.4.6 (also reproduced on 13.5.3 earlier)
  • .NET SDK 10.0.400 / runtime 10.0.11, Microsoft.Data.SqlClient 7.0.2
  • Windows 11 Pro 25H2 (build 26200), Docker Desktop 4.90.0 (238679), WSL 2 backend, Linux containers

Reproduction (works on any OS)

var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddSqlServer("sql")
                .WithLifetime(ContainerLifetime.Session)
                .WithEndpoint("tcp", e => { e.TargetHost = "[::1]"; e.IsProxied = false; })
                .AddDatabase("bookstore-db");
builder.Build().Run();

From a test using DistributedApplicationTestingBuilder, after StartAsync() and waiting for sql to reach Running:

What Observed
sql.GetEndpoint("tcp").Host / .Port [::1] / 1433
docker ps port binding created by DCP [::1]:1433->1433/tcp
await app.GetConnectionStringAsync("bookstore-db") data source 127.0.0.1,1433
sql_check health check Unhealthy (connection refused, nothing listens on 127.0.0.1:1433)
AddDatabase creation on ResourceReadyEvent fails the same way; the failure surfaces from WaitForResourceHealthyAsync("sql") as the SqlException

Expected: the connection string data source is [::1],1433 (the endpoint's allocated address), so the health check and the database creation use the address that DCP actually published.

Same result for every TargetHost value

Eight variants on the unchanged AppHost, all yielding a 127.0.0.1 data source:

# TargetHost IsProxied DCP binding Connection-string host Direct connection to the endpoint host
A (default localhost) false 127.0.0.1:1433 127.0.0.1 n/a
B [::1] false [::1]:1433 127.0.0.1 login OK
C [::1], Port=51433 false [::1]:51433 127.0.0.1 login OK
D ::1 false [::1]:1433 127.0.0.1 login OK
E localhost true 127.0.0.1:<random> (proxy localhost:<port>) 127.0.0.1 n/a
F LAN IPv4 true <LAN>:<random> (proxy <LAN>:<port>) 127.0.0.1 login OK (through the proxy)
G LAN IPv4 false <LAN>:1433 127.0.0.1 login OK
H 0.0.0.0 false 0.0.0.0:1433 127.0.0.1 n/a

"login OK" means PRELOGIN, TLS and SQL authentication all succeeded when connecting to EndpointReference.Host:Port directly (SqlException 4060 "Cannot open database" only because the database had not been created yet).

Where it comes from

  • src/Aspire.Hosting.SqlServer/SqlServerServerResource.cs: the private ConnectionString expression is Server={PrimaryEndpoint.Property(EndpointProperty.IPV4Host)},{PrimaryEndpoint.Property(EndpointProperty.Port)};….
  • src/Aspire.Hosting/ApplicationModel/EndpointReference.cs (EndpointReferenceExpression.GetValueAsync):
EndpointProperty.IPV4Host when networkContext == KnownNetworkIdentifiers.LocalhostNetwork => "127.0.0.1",

so for the localhost network the allocated address is never consulted.

Proof that honouring TargetHost is sufficient

On a Windows machine where 127.0.0.1 to a published SQL Server port stalls in the TDS pre-login handshake (docker/desktop-feedback#622), a test-side experiment that (1) rewrote the connection string's data source to EndpointReference.Host:Port, (2) replaced sql_check / bookstore-db_check with a SELECT 1 check over that rewritten string, and (3) waited with WaitForResourceAsync("sql", e => e.Snapshot.HealthStatus == HealthStatus.Healthy) instead of WaitForResourceHealthyAsync (to avoid awaiting the faulted AddDatabase creation task) made the full 44-test suite pass (28.6 s). Steps 2 and 3 exist only because the health check and the creation step reuse the same 127.0.0.1 string; if IPV4Host resolved to the allocated address, none of the three steps would be needed. I am not proposing that experiment as a workaround — it depends on internal registration names and on the ResourceReadyEvent wait semantics.

Ask

Resolve IPV4Host from the allocated endpoint address (or fall back to EndpointProperty.Host) when the endpoint's TargetHost has been customised, so that WithEndpoint("tcp", e => e.TargetHost = "[::1]") produces a usable SQL Server connection string. Alternatively, let SqlServerServerResource use Host instead of IPV4Host when TargetHost is not the default.

A side observation while diagnosing: when the AddDatabase creation step fails, the SqlException surfaces from WaitForResourceHealthyAsync although the resource's own health check is Healthy; release/13.4 SqlServerBuilderExtensions looks like it only logs creation failures, so this may be worth a look on its own.

Related: #19803 (bind-address knob request), docker/desktop-feedback#622 (the Windows IPv4 loopback defect that makes this matter).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-app-modelIssues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions