Understanding croc's Internal DNS Resolver: How the `--internal-dns` Flag Works

The --internal-dns flag forces croc to bypass the operating system's DNS resolver and use a built-in stub resolver that queries public DNS servers directly on port 53.

The schollz/croc secure file transfer tool provides an alternative DNS resolution mechanism for environments where system DNS is unreliable or restricted. When enabled via the --internal-dns flag, croc ignores the host's resolver configuration and uses its own implementation defined in src/models/constants.go to ensure reliable connectivity to relay servers.

How the Internal DNS Resolver Activates

According to the croc source code, the system checks for the --internal-dns flag in two ways before establishing connections.

Command-Line Flag Detection

In src/models/constants.go (lines 58-66), croc parses command-line arguments at startup. If --internal-dns is present, the tool sets the package-level variable INTERNAL_DNS to true for the duration of the session.

Persistent Configuration via --remember

If the flag is not supplied on the command line, croc checks for a file named internal-dns in the user's config directory (lines 49-55). When you run croc with the --remember flag alongside --internal-dns, it creates this marker file. On subsequent runs, the presence of this file automatically enables INTERNAL_DNS without requiring the command-line argument.

The Dual Resolution Logic in src/models/constants.go

The core resolution logic resides in the lookup function (lines 4-33), which conditionally routes DNS queries based on the INTERNAL_DNS state.

When INTERNAL_DNS is false: The function calls localLookupIP, which utilizes the standard net.Resolver with a strict 500ms timeout implemented via context.WithTimeout.

When INTERNAL_DNS is true: The function triggers the built-in stub resolver by calling remoteLookupIP.

Local Resolution (localLookupIP)

The local path relies entirely on the operating system's DNS configuration. It creates a context with a 500ms cancellation deadline and passes the query to the default Go resolver, which respects /etc/resolv.conf, systemd-resolved, or Windows DNS settings depending on the platform.

Built-in Stub Resolution (remoteLookupIP)

When internal DNS is enabled, croc spawns a goroutine for every server listed in the publicDNS slice (defined in lines 27-47). Each goroutine instantiates a net.Resolver configured with PreferGo: true and a custom Dial function that forces TCP/UDP connections directly to the specific DNS server on port 53, bypassing any system resolvers.

Results stream into a buffered channel, and the lookup function returns the first non-empty response it receives. If all goroutines fail to retrieve a valid record, the function returns an error.

Practical Usage Examples

Enable the built-in resolver for a single transfer:

croc send --internal-dns file.txt

Permanently enable the stub resolver by creating the config marker file:

croc send --remember --internal-dns file.txt

Verify which resolver is active using debug output:

croc --debug send --internal-dns file.txt

The first command activates the resolver only for that invocation. The second creates the internal-dns file in your config directory, making INTERNAL_DNS true on every subsequent execution unless explicitly overridden.

Key Implementation Files

  • src/models/constants.go: Contains the INTERNAL_DNS flag logic, the publicDNS server list (lines 27-47), the config file handling (lines 49-55), CLI argument parsing (lines 58-66), and the lookup, localLookupIP, and remoteLookupIP implementations (lines 4-33).
  • src/cli/cli.go: Declares the global --internal-dns and --remember flags and passes CLI arguments to the underlying resolution logic.

Summary

  • The --internal-dns flag forces croc to use a built-in stub resolver instead of the OS DNS.
  • When enabled, croc queries servers from the publicDNS list directly on port 53 using goroutines and returns the first successful result.
  • The internal-dns config file allows persistent activation without passing the flag on every run.
  • This mechanism bypasses system-wide DNS caching and restrictive resolver configurations, ensuring reliable relay host resolution.

Frequently Asked Questions

What is croc's internal DNS resolver?

Croc's internal DNS resolver is a built-in stub implementation located in src/models/constants.go that queries public DNS servers directly rather than using the operating system's resolver libraries. It activates when the --internal-dns flag is set or when an internal-dns marker file exists in the config directory.

How do I permanently enable internal DNS in croc?

Run any croc command with both --remember and --internal-dns flags, such as croc send --remember --internal-dns file.txt. This creates an internal-dns file in your config directory that automatically enables the built-in resolver on all future runs unless you delete the file or override with explicit flags.

What is the difference between local and internal DNS resolution in croc?

Local DNS uses the operating system's resolver via localLookupIP, respecting /etc/resolv.conf and local DNS caches with a 500ms timeout. Internal DNS uses remoteLookupIP to spawn parallel goroutines that query hardcoded public DNS servers directly on port 53, bypassing the host's resolver entirely.

Which public DNS servers does croc use when internal DNS is enabled?

The specific servers are defined in the publicDNS slice in src/models/constants.go (lines 27-47). The resolver initiates parallel queries to every server in this list simultaneously, returning the first successful response to minimize latency.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →