Skip to content

WSL2 SQL Server Connection Hang — Root Cause and Fix #40245

@innohead

Description

@innohead

Date: 2026-04-20
Environment: WSL2 (Ubuntu, kernel 6.6.87.2-microsoft-standard-WSL2), Cisco AnyConnect VPN, networkingMode=mirrored


Symptom

Any attempt to connect to SQL Server from WSL2 via pyodbc fails with:

[HYT00] Login timeout expired (SQLDriverConnect)

after several seconds (multiple connection attempts × ODBC login timeout + retries).
For example:

import pyodbc
conn = pyodbc.connect(
    "DRIVER=ODBC Driver 17 for SQL Server;"
    "SERVER=myserver.example.local;"
    "DATABASE=mydb;"
    "Trusted_Connection=yes;"
)

The same connection from a native Windows process (e.g. sqlcmd) completes in ~1 second.


Root Cause

The Microsoft ODBC Driver 17 and 18 for SQL Server (Linux) call bind(sock, 0.0.0.0:0) on
a freshly created TCP socket before calling connect(). This is a redundant but
standard pre-connection step — binding to the wildcard address on port 0 asks the
kernel to assign an ephemeral source port, which connect() would do automatically
anyway if bind() is skipped.

On this system, that bind() call returns ENOSYS ("Function not implemented"):

bind(3, {sa_family=AF_INET, sin_port=0, sin_addr=0.0.0.0}) = -1 ENOSYS

Confirmed via strace. Also reproducible directly:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setblocking(False)
s.bind(('0.0.0.0', 0))
# → OSError: [Errno 38] Function not implemented

When bind() fails, the ODBC driver closes the socket and retries with exponential
backoff (100 ms → 200 ms → 400 ms → 800 ms → 1 s intervals) until the login timeout
expires. TCP connectivity to the server is otherwise fine — connect() without a
prior bind() reaches the SQL Server almost immediately.

Why does bind() fail?

In WSL2 networkingMode=mirrored, WSL processes share the Windows host's network
stack directly. Cisco AnyConnect installs a kernel-level socket filter that,
in this configuration, blocks bind() on AF_INET sockets.

The failure is consistent whenever AnyConnect is active in mirrored mode,
regardless of the underlying physical network (WiFi or ethernet). The observed
pattern — connections succeeding 1–2 times after a fresh WSL session before all
subsequent attempts fail — is consistent with
AnyConnect's socket filter initializing fully a few seconds after VPN connection
is established. Once the filter is active, bind() fails for every subsequent call
and accumulated socket errors eventually degrade WSL's networking state.

Switching WSL2 to networkingMode=nat restores bind() but breaks internet
access entirely under AnyConnect, so that is not a viable workaround.


Fix

An LD_PRELOAD shim that intercepts bind(AF_INET, 0.0.0.0:0) and returns
success without calling the kernel. All other bind() calls (any non-zero
address or port) are passed through to the real implementation unchanged.

Source (fix_bind.c)

#define _GNU_SOURCE
#include <dlfcn.h>
#include <sys/socket.h>
#include <netinet/in.h>

int bind(int fd, const struct sockaddr *addr, socklen_t len) {
    if (addr && addr->sa_family == AF_INET) {
        const struct sockaddr_in *a = (const struct sockaddr_in *)addr;
        if (a->sin_addr.s_addr == 0 && a->sin_port == 0)
            return 0;  /* redundant bind — connect() auto-assigns source port */
    }
    static int (*real_bind)(int, const struct sockaddr *, socklen_t) = NULL;
    if (!real_bind) real_bind = dlsym(RTLD_NEXT, "bind");
    return real_bind(fd, addr, len);
}

Build

gcc -shared -fPIC -o /usr/local/lib/fix_bind.so fix_bind.c -ldl

Install (system-wide, survives WSL restart)

sudo bash -c 'echo "/usr/local/lib/fix_bind.so" >> /etc/ld.so.preload'

Result

With the shim in place, pyodbc.connect() completes almost immediately instead of
timing out after several seconds.


Safety

The shim intercepts only bind(AF_INET, 0.0.0.0, port=0). This call is
semantically a no-op — skipping it has exactly the same observable effect as
letting it succeed, because connect() auto-assigns a source address and port
in both cases. No other bind() call is affected.


Notes

If others hit this issue in WSL2 with AnyConnect and networkingMode=mirrored,
the shim above is the practical workaround. A cleaner fix would be for the
Microsoft ODBC Driver to make the pre-connection bind() call conditional on
the platform — skipping it on Linux when the address is wildcard, since
connect() handles source port assignment automatically.


Root cause diagnosed with assistance from Claude Code (Anthropic).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions