shadow1ng/fscan · error
LDAP dial: %w
Error message
LDAP dial: %w
What it means
connectToDomain in the Windows systeminfo plugin fails to open an LDAP connection (port 389) to the domain controller. It first tries ldap.DialURL with the DC hostname, and on failure retries once with the host's resolved IPv4; if both attempts fail, the underlying dial error is wrapped as "LDAP dial: %w". This means TCP to port 389 could not be established or the URL/handle was invalid.
Source
Thrown at plugins/local/systeminfo_dc_windows.go:91
func (p *SystemInfoPlugin) connectToDomain(domain string) (*domainInfo, error) {
dcHost, err := p.findDC(domain)
if err != nil {
return nil, err
}
client, err := gssapi.NewSSPIClient()
if err != nil {
return nil, fmt.Errorf("SSPI: %w", err)
}
defer func() { _ = client.Close() }()
conn, err := ldap.DialURL(ldapURL(dcHost, 389))
if err != nil {
if ipv4, resolveErr := resolveIPv4(dcHost); resolveErr == nil {
conn, err = ldap.DialURL(ldapURL(ipv4, 389))
}
if err != nil {
return nil, fmt.Errorf("LDAP dial: %w", err)
}
}
if err := conn.GSSAPIBind(client, fmt.Sprintf("ldap/%s", dcHost), ""); err != nil {
_ = conn.Close()
return nil, fmt.Errorf("GSSAPI bind: %w", err)
}
baseDN, err := p.getBaseDN(conn, domain)
if err != nil {
_ = conn.Close()
return nil, err
}
return &domainInfo{Domain: domain, BaseDN: baseDN, LDAPConn: conn}, nil
}
func (p *SystemInfoPlugin) findDC(domain string) (string, error) {View on GitHub (pinned to 95cc12e753)
Solutions
- Verify network reachability: from the scanning host run a TCP connect test to <dcHost>:389 (e.g. Test-NetConnection <dcHost> -Port 389 on Windows).
- Confirm DNS resolves dcHost; if not, add the DC's IP to hosts or fix DNS, since the resolveIPv4 fallback also depends on resolution.
- Check firewalls/ACLs between scanner and DC allow LDAP (TCP 389).
- Verify the DC host value passed into collectDomainInfo is correct and the DC is online (ping -n 1 <domain> is used later in findDC as a sanity check).
Example fix
// before (host unreachable)
conn, err := ldap.DialURL(ldapURL(dcHost, 389)) // fails: dial tcp 10.0.0.5:389: connectex: No connection could be made
// after (verify reachability first in caller or use correct host)
if !ldapReachable(dcHost, 389) {
return nil, fmt.Errorf("skipping: DC %s not reachable on 389", dcHost)
}
conn, err := ldap.DialURL(ldapURL(dcHost, 389)) Defensive patterns
Strategy: retry
Validate before calling
// Pre-check LDAP reachability before invoking the plugin
testConn, err := net.DialTimeout("tcp", net.JoinHostPort(dcHost, "389"), 5*time.Second)
if err != nil {
return fmt.Errorf("DC %s:389 unreachable: %w", dcHost, err)
}
testConn.Close() Try / catch
result := plugin.Scan(ctx, host, session)
if result != nil && !result.Success {
var dnsErr *net.DNSError
if errors.As(result.Error, &dnsErr) {
// fix DNS or resolve IPv4 manually, then retry once
} else if isTimeout(result.Error) {
// retry with backoff
}
} Prevention
- Run a TCP/389 reachability check (Test-NetConnection) before scanning a DC.
- Use DC FQDNs and keep DNS healthy so both hostname and IPv4 fallback can resolve.
- Document firewall rules allowing LDAP 389 from scanner subnets.
When it happens
Trigger: ldap.DialURL(ldapURL(dcHost, 389)) fails AND the fallback ldap.DialURL(ldapURL(ipv4, 389)) after resolveIPv4 also fails. Typical underlying causes: connection refused, timeout, DNS failure (also making resolveIPv4 fail), unreachable host, or malformed DC host value.
Common situations: Running the scanner from a machine that cannot reach the DC (firewall blocking LDAP 389), wrong or stale dcHost value, DNS not resolving the domain controller, DC offline, or scanning across a VPN/network segment where port 389 is filtered.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06).
Data as JSON: /api/errors/e89204ae4c875509.
Report an issue: GitHub.