Skip to content

Conversation

@jimmystridh
Copy link

@jimmystridh jimmystridh commented Jan 29, 2026

Problem

When connecting to Azure SQL through an SSH tunnel or proxy (e.g., 127.0.0.1:11433), connections fail because SQL Server validates the hostname in the TDS LOGIN7 packet. The server receives 127.0.0.1 as the server name and rejects it with "Cannot open server '127.0.0.1' requested by the login".

Current workaround requires adding /etc/hosts entries mapping the real server name to 127.0.0.1.

Fixes #576

Solution

Add a --server-name flag that specifies the server name to send in the login packet, separate from the dial address (-S).

# Connect via tunnel on localhost:11433, authenticate as the real server
sqlcmd -S 127.0.0.1,11433 --server-name myserver.database.windows.net \
    -U myuser -P mypass -N -C

Implementation

  • Add ServerNameOverride field to ConnectSettings
  • Build the connection string using the override host/instance (so LOGIN7 uses the override)
  • Use a proxy dialer that rewrites the network dial target back to the original -S host/port
  • Skip override for named pipes

Allows specifying the server name sent in the TDS LOGIN7 packet
separately from the dial address. Fixes connections through SSH
tunnels or proxies to Azure SQL where the server validates hostname.

Refs: microsoft#576
@jimmystridh
Copy link
Author

@microsoft-github-policy-service agree

@dlevy-msft-sql dlevy-msft-sql added enhancement New feature or request Size: S Small issue (less than one week effort) labels Jan 30, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for overriding the server name used during TDS login (and related connection metadata) so sqlcmd can connect through tunnels/proxies (e.g., 127.0.0.1:11433) while authenticating as the real Azure SQL hostname.

Changes:

  • Introduces ConnectSettings.ServerNameOverride and applies it when building the connection string.
  • Adds a proxyDialer to rewrite the actual dial target while keeping the overridden server name for login.
  • Adds unit tests covering the new connection string behavior and dialer behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/sqlcmd/connect.go Adds ServerNameOverride and applies it during connection string construction.
pkg/sqlcmd/sqlcmd.go Attempts to attach a custom dialer when ServerNameOverride is set.
pkg/sqlcmd/dialer.go Implements a dialer that rewrites dial host/port while reporting an overridden hostname.
pkg/sqlcmd/dialer_test.go Adds unit tests for proxyDialer.
pkg/sqlcmd/sqlcmd_test.go Extends connection string tests to cover ServerNameOverride.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

serverName: overrideName,
targetHost: serverName,
targetPort: targetPort,
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When ServerNameOverride is set, the dial rewrite is only applied if connector can be type-asserted to *mssql.Connector. If that assertion ever fails (e.g., for non-mssql.Connector implementations returned by token/AAD connectors), ServerNameOverride will still change the connection string host but the dial target won’t be rewritten back to the original -S host/port, which breaks tunneled/proxy scenarios.

Consider either (1) ensuring GetTokenBasedConnection returns a *mssql.Connector (so this always works), (2) supporting dialer injection for the token connector type, or (3) returning an explicit error when the dialer can’t be applied while ServerNameOverride is set.

Suggested change
}
}
} else {
// When ServerNameOverride is set we must be able to inject a dialer;
// otherwise the connection string host is changed without rewriting
// the actual dial target, which breaks tunneled/proxy scenarios.
return localizer.Errorf("Server name override is not supported with the current authentication method")

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +25
dialer *net.Dialer
}

func (d *proxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
if d.dialer == nil {
d.dialer = &net.Dialer{}
}
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proxyDialer lazily initializes d.dialer with an unsynchronized write. If DialContext is called concurrently on the same proxyDialer instance (which is plausible when a driver.Connector is used by database/sql), this introduces a data race.

Safer options: initialize dialer eagerly (remove the nil check), store it as a non-pointer net.Dialer value, or guard initialization with sync.Once/a mutex.

Suggested change
dialer *net.Dialer
}
func (d *proxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
if d.dialer == nil {
d.dialer = &net.Dialer{}
}
dialer net.Dialer
}
func (d *proxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {

Copilot uses AI. Check for mistakes.
Comment on lines +199 to +206
func (connect ConnectSettings) useServerNameOverride(protocol string, serverName string) bool {
if connect.ServerNameOverride == "" {
return false
}
if protocol == "np" || strings.HasPrefix(serverName, `\\`) {
return false
}
return true
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useServerNameOverride introduces logic to skip applying the override for named pipes (protocol == "np" or \\...), but there’s no test exercising the override+named-pipe combination. A regression here would be easy to miss since ConnectionString() behavior changes based on both ServerName and ServerNameOverride.

Add a unit test in TestConnectionStringFromSqlCmd (or a new test) that sets ServerName to a named-pipe value and also sets ServerNameOverride, asserting that the resulting connection string is unchanged (override ignored).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

connectivity enhancement New feature or request Size: S Small issue (less than one week effort)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provide a way to specify target server hostname or specify username when authenticating with Azure AD CLI

2 participants