Skip to content

dns: preserve TTL values and enable systemd-resolved caching#14572

Open
syed-dawood wants to merge 2 commits intomicrosoft:masterfrom
syed-dawood:fix/dns-cache-ttl-preservation
Open

dns: preserve TTL values and enable systemd-resolved caching#14572
syed-dawood wants to merge 2 commits intomicrosoft:masterfrom
syed-dawood:fix/dns-cache-ttl-preservation

Conversation

@syed-dawood
Copy link
Copy Markdown

@syed-dawood syed-dawood commented Mar 28, 2026

Summary of the Pull Request

DnsQueryRaw internally injects DNS_QUERY_BYPASS_CACHE, causing all DNS responses tunneled through WSL to bypass the Windows DNS cache. This results in:

  1. TTL values reset to their original values, preventing downstream caches from working correctly
  2. Combined with systemd-resolved's default Cache=no-negative, negative responses (NODATA/NXDOMAIN) are never cached, causing repeated wire queries for unsupported record types (e.g. AAAA on IPv4-only networks)

This PR adds DNS_QUERY_DONT_RESET_TTL_VALUES to preserve real TTL values, and enables full DNS caching in systemd-resolved via a drop-in config.

PR Checklist

Detailed Description of the Pull Request / Additional comments

Root Cause

DnsQueryRaw (dnsapi.dll) always adds DNS_QUERY_BYPASS_CACHE to queryOptions internally. This was confirmed empirically by calling DnsQueryRaw via P/Invoke and inspecting the returned queryOptions in DNS_QUERY_RAW_RESULT — the 0x8 flag is always present regardless of input flags.

This means:

  • Every DNS query goes to wire (no Windows DNS cache hit)
  • TTL values in responses are reset to their original values (not decremented)
  • DNS_PROTOCOL_NO_WIRE is never returned

Changes

1. src/windows/common/DnsResolver.cpp — Add DNS_QUERY_DONT_RESET_TTL_VALUES

Sets the DNS_QUERY_DONT_RESET_TTL_VALUES (0x00100000) flag alongside the existing DNS_QUERY_NO_MULTICAST. This tells the Windows DNS client to preserve real decremented TTL values from upstream resolvers, enabling systemd-resolved to cache responses with meaningful lifetimes.

This is safe because HandleDnsQueryCompletion performs a raw CopyMemory of response bytes — no WSL code inspects or modifies TTL values in the DNS response.

2. src/linux/init/init.cpp — Enable Cache=yes in systemd-resolved

Writes a drop-in config to /run/systemd/resolved.conf.d/wsl-dns-cache.conf changing systemd-resolved from Cache=no-negative (default) to Cache=yes. This enables caching of both positive and negative DNS responses.

  • Written to /run/ (tmpfs) so it's regenerated each boot and user overrides in /etc/ take precedence
  • Harmless no-op on distros without systemd-resolved
  • Uses existing UtilMkdirPath()/WriteToFile() pattern from the same function

Related Issues

Validation Steps Performed

Empirical DnsQueryRaw testing (Windows)

A C# test program calling DnsQueryRaw via P/Invoke confirmed:

Test Flags Result
NO_MULTICAST only returned 0x80000800 TTL=60 (reset to original)
+ DONT_RESET_TTL returned 0x80100800 TTL=58→55 (decremented = real)
NO_WIRE_QUERY ERROR_INVALID_PARAMETER Not supported by DnsQueryRaw
AAAA NODATA (ipv4only.arpa) every query hits wire 31 bytes, 0 answers, proto=UDP

DNS benchmarking (WSL)

Dual-stack DNS benchmark (dig A + dig AAAA for 7 domains):

Metric Before (Cache=no-negative) After (Cache=yes)
Average per-query latency ~108ms ~13ms
AAAA NODATA queries 51-219ms (wire every time) 0ms (cached)
Cache misses 14/14 2/14

@syed-dawood syed-dawood requested a review from a team as a code owner March 28, 2026 17:29
@syed-dawood syed-dawood changed the title Fix DNS performance: preserve TTLs and enable systemd-resolved caching dns: preserve TTL values and enable systemd-resolved caching Mar 28, 2026
@syed-dawood
Copy link
Copy Markdown
Author

cc @benhillis @OneBlue @craigloewen-msft @jstarks @damanm24 — requesting review on this DNS performance fix.

This addresses the DnsQueryRaw cache bypass behavior that causes all DNS responses tunneled through WSL to hit the wire on every query. The fix is two complementary changes:

  1. DNS_QUERY_DONT_RESET_TTL_VALUES in DnsResolver.cpp — preserves real TTL values so downstream caching works correctly
  2. Cache=yes drop-in in init.cpp — enables full DNS response caching in systemd-resolved (default no-negative discards NODATA/NXDOMAIN responses)

Both changes were empirically validated. See the PR description for detailed test results and safety analysis.

Copy link
Copy Markdown
Contributor

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

This PR improves DNS performance in WSL’s DNS tunneling path by (1) preserving upstream TTL values in tunneled DNS responses and (2) enabling systemd-resolved to cache negative responses, reducing repeated high-latency lookups (notably AAAA→NODATA on IPv4-only networks).

Changes:

  • Set DNS_QUERY_DONT_RESET_TTL_VALUES on DnsQueryRaw requests so returned DNS responses carry decremented TTLs.
  • Generate a /run/systemd/resolved.conf.d/ drop-in to set systemd-resolved Cache=yes during systemd boot.

Reviewed changes

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

File Description
src/windows/common/DnsResolver.cpp Adds a DNS query option to preserve real TTLs in tunneled DNS responses and updates the related comment.
src/linux/init/init.cpp Writes a systemd-resolved drop-in under /run/ to enable positive + negative DNS caching.

DnsQueryRaw internally bypasses the Windows DNS cache, causing all DNS
responses tunneled to WSL to carry reset TTL values. This breaks
downstream caching in systemd-resolved, leading to repeated wire queries
for every DNS lookup — including negative (NODATA) responses for
unsupported record types like AAAA on IPv4-only networks.

This commit applies two complementary fixes:

1. Add DNS_QUERY_DONT_RESET_TTL_VALUES flag to DnsQueryRaw requests
   (DnsResolver.cpp) so that responses carry real decremented TTL values
   from upstream resolvers instead of reset-to-original values.

2. Write a systemd-resolved drop-in config enabling Cache=yes
   (init.cpp) to cache both positive and negative DNS responses. The
   default Cache=no-negative discards NODATA/NXDOMAIN answers, causing
   repeated wire queries for record types that will never resolve.

Together these changes restore effective DNS caching for WSL, reducing
redundant network round-trips and improving DNS resolution latency.

Fixes: microsoft#14568
Related: microsoft#9423, microsoft#13415
@syed-dawood syed-dawood force-pushed the fix/dns-cache-ttl-preservation branch from 9f6ea31 to ab7c069 Compare March 28, 2026 17:50
@syed-dawood
Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@syed-dawood syed-dawood requested a review from Copilot March 31, 2026 22:12
Copy link
Copy Markdown
Contributor

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 2 out of 2 changed files in this pull request and generated no new comments.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants