Skip to content

hostsfile: add optional hostname checks and zone filtering - #700

Merged
semihalev merged 3 commits into
semihalev:mainfrom
mrpops2ko:hostsfile-validation-zones-pr
Oct 7, 2026
Merged

semihalev merged 3 commits into
semihalev:mainfrom
mrpops2ko:hostsfile-validation-zones-pr

Conversation

@mrpops2ko

Copy link
Copy Markdown
Contributor

Add hostsfilechecknames and hostsfilezones, both disabled by default, to control which names a hosts file publishes. Name checking skips invalid ASCII hostnames with a warning; zone filtering includes each zone’s apex and descendants. Both apply before forward and reverse records are built and on reload.

When either option is enabled, equivalent names are imported once per row. The first accepted normal name becomes primary, separate rows still aggregate addresses, and supported wildcards retain their row precedence. Defaults, documentation, and regression tests are updated.

Focused race tests, build, lint, allocation gates, and documentation drift checks pass. Full make test repeats one cache timing failure and three dnstap Unix-socket failures also reproduced on unchanged upstream.

Related to discussion #684.

@mrpops2ko
mrpops2ko marked this pull request as ready for review October 5, 2026 19:59
@mrpops2ko

Copy link
Copy Markdown
Contributor Author

Why hostsfilezones and hostsfilechecknames are useful

hostsfile lets SDNS turn entries from a hosts file into DNS records.

The problem is that, on many systems, /etc/hosts is not a DNS zone file. It is shared machine configuration used by:

  • the operating system
  • DHCP services
  • configuration management
  • containers and hypervisors
  • VPN or bootstrap configuration
  • locally defined aliases

That means:

present in /etc/hosts

does not necessarily mean:

should be served to every DNS client

PR #700 adds two controls that separate those concerns:

hostsfilechecknames = true
hostsfilezones = ["home.arpa."]
  • hostsfilechecknames validates imported hostnames and ignores malformed ones.
  • hostsfilezones limits which DNS namespaces SDNS imports from the hosts file.

Together they turn hosts-file support from:

/etc/hosts
    |
    +--> publish everything

into:

/etc/hosts
    |
    +--> validate names
    |
    +--> allow selected DNS zones
    |
    +--> publish only intended records

Entries rejected by either check are also excluded from generated PTR records. Names outside configured zones are not treated as local hosts-file answers; DNS queries for them continue through normal resolution.

Both options are disabled by default, so existing behaviour remains unchanged.

Common homelab example: DHCP + local DNS

A home router may build /etc/hosts from DHCP leases:

192.168.1.20   nas.home.arpa
192.168.1.21   printer.home.arpa
192.168.1.22   tv.home.arpa

The same file may also contain entries used only by the router itself:

127.0.0.1      localhost
127.0.1.1      router
192.168.1.1    management
45.90.28.0     dns1.example.net

Without filtering, configuring /etc/hosts as the SDNS hosts file means all suitable entries can become DNS answers.

With:

hostsfilechecknames = true
hostsfilezones = ["home.arpa."]

SDNS publishes:

nas.home.arpa
printer.home.arpa
tv.home.arpa

but does not publish unrelated local or bootstrap mappings.

This lets one hosts file continue serving operating-system purposes without making its entire contents part of LAN DNS.

Protect against bad DHCP hostnames

DHCP clients often control their own hostname.

A device could provide malformed or unexpected data such as:

192.168.1.40   camera.home.arpa
192.168.1.41   -.home.arpa
192.168.1.42   camera-.home.arpa

With:

hostsfilechecknames = true

valid hostnames are imported and malformed ones are skipped.

Important distinction:

This does not validate or modify DHCP itself.

It creates validation at the point where data crosses into DNS.

That means bad data can remain in the DHCP database or hosts file without SDNS automatically publishing it.

Prevent DHCP clients claiming unrelated domains

This becomes more important in company or branch-office networks.

Suppose DHCP normally creates:

10.20.0.101   reception-pc.branch23.corp.example
10.20.0.102   printer.branch23.corp.example

but a client supplies:

10.20.0.150   updates.vendor.com

If the hosts file is imported without namespace restrictions, an externally meaningful name can potentially become a local DNS answer.

With:

hostsfilechecknames = true
hostsfilezones = ["branch23.corp.example."]

SDNS accepts:

reception-pc.branch23.corp.example
printer.branch23.corp.example

and ignores:

updates.vendor.com

This creates a clear namespace boundary:

DHCP/client supplied hostname
          |
          v
branch23.corp.example ? ---- no ----> do not publish
          |
         yes
          |
          v
       SDNS

A DHCP client can therefore populate names inside the intended local namespace without being able to make SDNS answer for arbitrary external domains.

This also matches the concern discussed in discussion #684: /etc/hosts may contain useful local state without every entry being appropriate for DNS publication.

Hypervisor, Docker, or Proxmox hosts

Hypervisors commonly have mixed-purpose hosts files:

127.0.1.1      pve01
10.10.0.10     pve01.lab.home.arpa
10.10.0.20     nas.lab.home.arpa
172.17.0.1     docker0-host
100.64.0.5     tailscale-gateway

Only lab names may be intended for LAN DNS.

hostsfilezones = ["lab.home.arpa."]

Result:

pve01.lab.home.arpa    -> published
nas.lab.home.arpa      -> published

pve01                  -> local only
docker0-host           -> local only
tailscale-gateway      -> local only

No need to maintain separate /etc/hosts and sdns-hosts files purely to prevent unrelated machine configuration becoming DNS data.

Configuration-managed servers

Companies often generate /etc/hosts through Ansible, Puppet, Salt, cloud-init, or similar tooling.

Example:

127.0.0.1        localhost
127.0.1.1        resolver01

10.20.0.5        config-controller
10.20.0.6        vault-bootstrap

10.20.10.10      printer01.office.corp.example
10.20.10.11      nas01.office.corp.example

Some entries exist for local bootstrap or service dependencies. Others are intended to be visible through DNS.

Instead of maintaining two generated files:

/etc/hosts
/etc/sdns-hosts

SDNS can consume the same source:

hostsfilezones = ["office.corp.example."]

while publishing only the intended namespace.

This is particularly useful where /etc/hosts already acts as a source of truth for several services.

Resolver bootstrap

A resolver itself may need local hosts-file mappings before DNS is available:

45.90.28.0   dns1.example.net

That mapping may be required for bootstrap or special local routing.

But that does not mean LAN clients should receive the same override when they ask SDNS for:

dns1.example.net

With zone filtering, the resolver can still use /etc/hosts locally while SDNS publishes only explicitly approved zones.

This demonstrates the main distinction clearly:

A locally useful hostname mapping is not automatically DNS data intended for publication.

Shared rows and aliases

Hosts files can also contain multiple names on one line:

10.30.0.20 server01 server01.internal server01.office.corp.example

The machine may need short aliases locally, while DNS should expose only the fully qualified corporate name.

With:

hostsfilezones = ["office.corp.example."]

SDNS can accept the matching name without publishing every alias on the same hosts-file row.

What this solves

Before this change, using a shared hosts file effectively asks one question:

Should SDNS trust this file?

In real deployments, that is too broad.

Better questions are:

Are these names valid hostnames?

and:

Are these names inside namespaces SDNS is supposed to publish?

PR #700 adds those two boundaries.

                /etc/hosts
                    |
                    v
          +-------------------+
          | hostname valid?   |
          +-------------------+
                    |
                    v
          +-------------------+
          | allowed DNS zone? |
          +-------------------+
                    |
                    v
              publish in SDNS

This is useful anywhere hosts-file contents come from broader system state rather than a dedicated DNS-only file.

Typical examples:

  • home routers publishing DHCP leases under home.arpa.
  • branch resolvers limiting DHCP-generated names to a company namespace
  • Proxmox/Docker hosts sharing /etc/hosts with LAN DNS
  • configuration-managed servers using one generated hosts file
  • resolvers needing private bootstrap mappings that should not be exposed to clients
  • networks wanting malformed DHCP names blocked before they enter forward and reverse DNS

Core benefit

SDNS can consume a shared hosts file without assuming every entry in that file is valid and intended for DNS publication.

Related links

@semihalev semihalev left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed e12b608; no actionable findings. Checked independent filter behavior, per-row DNS identity deduplication, primary/alias promotion, PTR admission, wildcard precedence and label boundaries, reload behavior, and compatibility with both settings disabled. go test -race ./middleware/hostsfile ./internal/dnsname ./config -count=1 and the documentation drift check passed. An additional temporary test exercised all non-wildcard byte values in escaped owner labels through A, CNAME and PTR import/query paths using independently packed/unpacked names. Platform CI and release builds are still running at the time of this review; this is not a claim that the complete CI run has passed. No source files changed.

@semihalev

Copy link
Copy Markdown
Owner

Can you please fix the conflict issue?

@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.10714% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 88.78%. Comparing base (d6d3bce) to head (f1410b0).

Files with missing lines Patch % Lines
middleware/hostsfile/hostsfile.go 98.46% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #700      +/-   ##
==========================================
+ Coverage   88.70%   88.78%   +0.08%     
==========================================
  Files         196      196              
  Lines       25508    25615     +107     
==========================================
+ Hits        22627    22743     +116     
+ Misses       2879     2870       -9     
  Partials        2        2              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@semihalev
semihalev merged commit a3737e2 into semihalev:main Oct 7, 2026
21 of 22 checks passed
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