Repository navigation
Conversation
Add `ports.freeBind` (default false) which, on Linux, sets the IP_FREEBIND / IPV6_FREEBIND socket option on the DNS and DoT listeners. This lets blocky bind to addresses that are not yet assigned to an interface (e.g. Tailscale, WireGuard, Keepalived/VRRP floating IPs) instead of failing at startup with EADDRNOTAVAIL. - new build-tagged `server/freebind` package: Control hook on Linux, no-op elsewhere (Supported=false) - when enabled and supported, DNS/TCP/DoT listeners are pre-created with the freebind ListenConfig.Control hook and served via ActivateAndServe; the default path is unchanged (ListenAndServe) - non-Linux builds log a warning and bind normally - has no effect on wildcard binds; requires no extra capability Closes #2026
There was a problem hiding this comment.
Pull request overview
Adds an opt-in ports.freeBind configuration that enables the Linux IP_FREEBIND/IPV6_FREEBIND socket option on the DNS (UDP/TCP) and DoT listeners, allowing Blocky to bind to IPs that aren't yet assigned to an interface (e.g. Tailscale/WireGuard/VRRP). When the flag is off (or on non-Linux), the existing ListenAndServe path is unchanged; when on, sockets are pre-created with a net.ListenConfig.Control hook and started via ActivateAndServe.
Changes:
- New build-tagged
server/freebindpackage providing theControlhook andSupportedflag (Linux real, others no-op). server/server.go: pre-creates UDP/TCP/DoT listeners with the freebind hook when enabled and switches the start path toActivateAndServe; warns on non-Linux.- Config additions (
Ports.FreeBind, schema, docs, log output) and tests for both the freebind package and server wiring.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| server/server.go | Threads context+freeBind into server creation, attaches pre-bound listeners, switches between ListenAndServe/ActivateAndServe. |
| server/freebind/freebind_linux.go | Implements the Linux Control hook setting IP_FREEBIND/IPV6_FREEBIND. |
| server/freebind/freebind_others.go | No-op stub for non-Linux platforms. |
| server/freebind/freebind_linux_test.go | Verifies bind to TEST-NET-1 works with the hook. |
| server/freebind/freebind_suite_test.go | Ginkgo suite registration for the freebind package. |
| server/server_freebind_linux_test.go | Wiring test that pre-binding occurs only when FreeBind=true. |
| config/config.go | Adds Ports.FreeBind field and updated LogConfig output. |
| config/ports_test.go, config/config_test.go | Tests for default value and LogConfig output. |
| docs/configuration.md, docs/config.yml, docs/config.schema.json | Documents and schemas the new option. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2078 +/- ##
==========================================
- Coverage 86.30% 86.16% -0.15%
==========================================
Files 110 111 +1
Lines 8214 8260 +46
==========================================
+ Hits 7089 7117 +28
- Misses 916 929 +13
- Partials 209 214 +5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/0xerr0r/blocky](https://github.com/0xERR0R/blocky) | minor | `v0.30.0` → `v0.31.0` | --- ### Release Notes <details> <summary>0xERR0R/blocky (ghcr.io/0xerr0r/blocky)</summary> ### [`v0.31.0`](https://github.com/0xERR0R/blocky/releases/tag/v0.31.0) [Compare Source](0xERR0R/blocky@v0.30.0...v0.31.0) #### Changelog ##### Features - [`132c42f`](0xERR0R/blocky@132c42f): feat(config): validate and log ports.dohPath ([#​2072](0xERR0R/blocky#2072)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`e43b5e5`](0xERR0R/blocky@e43b5e5): feat(querylog): add SQLite query log target ([#​2080](0xERR0R/blocky#2080)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`5d806b6`](0xERR0R/blocky@5d806b6): feat(server): opt-in IP\_FREEBIND for DNS listeners ([#​2078](0xERR0R/blocky#2078)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`f18e52b`](0xERR0R/blocky@f18e52b): feat: capability-aware image that runs under drop:ALL ([#​2071](0xERR0R/blocky#2071)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`bf19d6d`](0xERR0R/blocky@bf19d6d): feat: config JSON schema generation and schema-driven validation ([#​2066](0xERR0R/blocky#2066)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`99ae703`](0xERR0R/blocky@99ae703): feat: filter ipv6hint in HTTPS/SVCB RR when AAAA is filtered ([#​2070](0xERR0R/blocky#2070)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`c44017a`](0xERR0R/blocky@c44017a): feat: load sensitive config values from files ([#​2077](0xERR0R/blocky#2077)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`e6b41db`](0xERR0R/blocky@e6b41db): feat: per-client DNS rate limiting ([#​2063](0xERR0R/blocky#2063)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`f457ec9`](0xERR0R/blocky@f457ec9): feat: read bootstrap nameservers from a resolv.conf file (resolvFile) ([#​2068](0xERR0R/blocky#2068)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`fdcf351`](0xERR0R/blocky@fdcf351): feat: resolve client names from hosts file and custom DNS ([#​1964](0xERR0R/blocky#1964)) ([#​2069](0xERR0R/blocky#2069)) ([@​0xERR0R](https://github.com/0xERR0R)) ##### Bug fixes - [`93cc238`](0xERR0R/blocky@93cc238): fix(caching): sync prefetched entries to Redis ([#​2079](0xERR0R/blocky#2079)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`69b0663`](0xERR0R/blocky@69b0663): fix(config): handle DNS stamp port on hostname field and bootstrap IPs ([#​2065](0xERR0R/blocky#2065)) ([@​0xERR0R](https://github.com/0xERR0R)) - [`31f8df3`](0xERR0R/blocky@31f8df3): fix(querylog): build SQLite target only on modernc-supported platforms ([#​2082](0xERR0R/blocky#2082)) ([@​0xERR0R](https://github.com/0xERR0R)) ##### Build and dependencies - [`a5cdeb2`](0xERR0R/blocky@a5cdeb2): build(deps): bump github.com/go-chi/chi/v5 from 5.2.5 to 5.3.0 ([#​2074](0xERR0R/blocky#2074)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`5a25f67`](0xERR0R/blocky@5a25f67): build(deps): bump github.com/mattn/go-colorable from 0.1.14 to 0.1.15 ([#​2075](0xERR0R/blocky#2075)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`1da222a`](0xERR0R/blocky@1da222a): build(deps): bump github.com/oapi-codegen/runtime from 1.4.0 to 1.4.1 ([#​2064](0xERR0R/blocky#2064)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`6ff1d13`](0xERR0R/blocky@6ff1d13): build(deps): bump golang.org/x/net from 0.54.0 to 0.55.0 ([#​2067](0xERR0R/blocky#2067)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`63f8d9a`](0xERR0R/blocky@63f8d9a): build: upgrade Go to 1.26.2 and golangci-lint to v2.12.2 ([#​2062](0xERR0R/blocky#2062)) ([@​0xERR0R](https://github.com/0xERR0R)) ##### Misc - [`e2de6ae`](0xERR0R/blocky@e2de6ae): docs(redis): document unix socket support and add e2e test ([#​2076](0xERR0R/blocky#2076)) ([@​0xERR0R](https://github.com/0xERR0R)) *** #### ❤️ Support Blocky Blocky is free and open source, developed in my spare time — no telemetry, no ads, no hidden filtering. If it's useful to you, please consider supporting its continued development: [GitHub Sponsors](https://github.com/sponsors/0xERR0R) · [thanks.dev](https://thanks.dev/u/gh/0xERR0R) · [Liberapay](https://liberapay.com/spx01) · [Ko-fi](https://ko-fi.com/0xerr0r) · [PayPal](https://paypal.me/spx01) Thank you to everyone supporting Blocky! 🙏 </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMDUuMyIsInVwZGF0ZWRJblZlciI6IjQzLjIwNS4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Resolves #2026.
What & why
Blocky exits at startup with
EADDRNOTAVAILwhen configured to listen on an IP that isn't assigned to an interface yet (Tailscale/WireGuard tunnel IPs, Keepalived/VRRP floating IPs, overlay networks brought up after boot):This adds an opt-in
ports.freeBindflag that sets the LinuxIP_FREEBIND(andIPV6_FREEBIND) socket option on the DNS and DoT listeners, so the bind succeeds immediately and traffic flows once the address appears — no restart, and no system-wideip_nonlocal_bindsysctl.Design
false→ existing deployments are unchanged. No effect on wildcard binds; requires no extra capability (unlikeIP_TRANSPARENT).freeBindis off (or non-Linux), DNS servers start viaListenAndServeexactly as before. When on + Linux, the UDP/TCP/DoT sockets are pre-created with anet.ListenConfig{Control}hook and served viaActivateAndServe. Parity with miekg/dns (UDPSize, UDP sockopts, DoT TLS wrap,Shutdown) was verified against v1.1.72.server/freebindpackage —freebind_linux.gosets the sockopt;freebind_others.gois a no-op withSupported = false. Non-Linux logs a warning and binds normally.Prior art checked: dnsmasq solves this via a netlink-watching
bind-dynamicstrategy (heavier); AdGuard Home has no in-app option (users resort to the system-wide sysctl). A targeted opt-in setsockopt is lighter and more precise.Testing
server/freebindunit test (Linux): binds to192.0.2.1(TEST-NET-1, never assigned) — fails without the hook, succeeds with it, for TCP and UDP. Hermetic, no privilege, no container. Self-skips if the environment already permits non-local binds.serverwiring test (Linux):freeBind:truepre-binds DNS to a not-yet-available address;freeBind:falsedoes not pre-create listeners.false) +LogConfigoutput.Verified: Config + Server (71) + Freebind (3) suites pass; cross-compiles on darwin & windows;
golangci-lint0 issues;gofmt/go vetclean;config.schema.jsonregenerated (idempotent).