Commit Graph
100 Commits
Author SHA1 Message Date
Cuong Manh Le a4cfd4e479 cmd/cli: discard upstream answers whose question mismatches the request
Defense in depth against cache poisoning: a compromised or misbehaving
upstream can return an answer for a different name than was asked (e.g.
records for attacker.example in response to a query for victim.example).
Such an answer would be cached under the legitimate request key and
served to subsequent queries.

Validate that the upstream answer echoes the request's question
(case-insensitive name plus Qtype/Qclass, per RFC 1035 section 4.1.2)
before serving or caching it. A mismatch is logged at debug level and
the upstream is skipped, failing safe to the next upstream or SERVFAIL.

Refs github.com/Control-D-Inc/ctrld/issues/322
2026-07-28 16:12:47 +07:00
Cuong Manh Le d29e7d131e fix: wrong DoQ resolver rewriting upstream responses with SetReply
The DoQ resolver called SetReply on the already-unpacked upstream
response. SetReply is meant to build a reply from a request, so it
forces the RCODE to NOERROR and overwrites the Question with the
request's question. This masked upstream failures from the proxy's
failover logic (a SERVFAIL looked like a successful empty response) and
corrupted the Question section of the response served to clients.

Restore only the downstream transaction ID instead (RFC 9250 section
4.2.1 puts the DNS Message ID at 0 on the wire), preserving the upstream
RCODE, Question, and answer sections untouched. This matches how the DoH
and DoT resolvers return unpacked upstream responses.

Refs github.com/Control-D-Inc/ctrld/issues/322
2026-07-28 16:12:23 +07:00
Cuong Manh Le 836c9ccf12 fix: treat 464XLAT CLAT source as local, not WAN
On networks using 464XLAT (common on IPv6-only cellular carriers and iPhone
hotspots), the local machine's DNS queries can reach ctrld's listener with a
source address in the RFC 7335 IPv4 Service Continuity Prefix (192.0.0.0/29,
e.g. 192.0.0.2 on the CLAT/host side). isWanClient classified 192.0.0.x as a
WAN client, so with allow_wan_clients unset (the default) the query was refused,
breaking DNS resolution entirely on the affected connection even though it
originated from the local host.

Recognize the IPv4 Service Continuity Prefix as a local range, mirroring the
existing CGNAT special case:

- Add ipv4ServiceContinuityPrefix (192.0.0.0/29) and an isServiceContinuityAddr
  helper (single definition, reused by both call sites).
- isWanClient excludes the range, so 464XLAT/CLAT queries are served normally.
- isPrivatePtrLookup treats the range as private so reverse lookups are handled
  consistently.

Scoped to 192.0.0.0/29 (the exact 464XLAT range); this does not weaken
allow_wan_clients since no globally routable remote client can appear from it.
2026-07-28 16:11:51 +07:00
Cuong Manh Le 53d3d3d44a cmd/cli: preserve fallback listener port across reload in DNS intercept
On macOS DNS-intercept mode, when mDNSResponder owns *:53 ctrld falls back
to listening on 127.0.0.1:5354, and the pf rdr rules correctly redirect DNS
to the bound port at startup. However, DNS resolution later breaks with an
endless watchdog "anchor intact but probe FAILED -> force reload" loop and
`dig @127.0.0.1` timeouts.

Root cause is config reload. In CD mode, apiConfigReload refetches the
generated config (which always declares port 53) every hour and the reload
merge in runWait only inherits the running port when the new port is 0. The
generated config explicitly says 53, so `*p.cfg = *newCfg` reverts p.cfg to
port 53. The DNS listener goroutines are started only when !reload, so they
are never re-bound and stay on 5354. Every subsequent pf rebuild reads p.cfg
and targets the dead port 53.

Fix: after applying the reloaded config in DNS-intercept mode on darwin,
restore the actual bound listener IP/Port into the in-memory config via the
new preserveBoundListeners helper, logging the configured-vs-actual
divergence. A reload cannot move the running listener anyway, so this keeps
p.cfg consistent with reality; all pf rdr rules and the watchdog probe then
target the live port. The on-disk generated config is intentionally left
unchanged (still 53), so no generated-config change is required.
2026-07-28 16:11:37 +07:00
Cuong Manh Le 0d8df38dc1 fix: back off unroutable IPv6 DoH upstream health-check spam
When IPv6 is available locally but the selected IPv6 DoH endpoint is
unroutable (e.g. dialing [2606:1a40::22]:443 returns "no route to host"
while IPv4 stays usable), ctrld re-bootstrapped and re-dialed the endpoint
every ~2s. A weekend soak produced ~46.7k "no route to host" lines, with
the dial/health-check loop dominating the log during bad windows.

Add bounded backoff/suppression for network-unreachable endpoints at two
levels:

- ParallelDialer (internal/net): track dial addresses that fail with
  ENETUNREACH/EHOSTUNREACH and skip them for an exponentially growing,
  bounded window (5s -> 60s). A successful dial clears the entry
  immediately, so recovery is preserved when the route returns. When every
  candidate is suppressed the dial fails fast and quietly instead of
  hammering known-unroutable addresses.

- Upstream recovery loop (cmd/cli): demote unreachable check failures to
  debug and back off the retry cadence (2s -> 60s) for an unreachable
  streak; any other failure resets to the base cadence.

The new IsUnreachable classifier lives in internal/net and is reused by
cmd/cli's errNetworkError, so the unreachable-errno matching has a single
definition. Note the explicit winsock constants (10051/10065) are required
on Windows: syscall.ENETUNREACH/EHOSTUNREACH are Go's portable "invented"
values and never equal the raw WSA codes a failing connect surfaces.

Suppression and backoff are always bounded, so IPv6 is never disabled until
restart and recovers on its own once the route is back. Split-stack
selection and the #549 macOS intercept recovery work are untouched.

Adds unit tests for the classifier, the dialer's suppression tracker, and
the recovery backoff schedule.
2026-07-14 01:12:34 +07:00
Cuong Manh Le a5d536ab79 Upgrade quic-go to v0.59.1
For fixing CVE-2026-40898.
2026-06-16 15:17:11 +07:00
Cuong Manh Le 723c7827ba fix: stop self-upgrade tests from fork-bombing the windows test runner
The test:windows CI job intermittently failed to clean up .testbin with
"Access to the path '...cmd_cli.test.exe' is denied". This was previously
attributed to Windows Defender scanning the large unsigned test binaries,
and mitigated with Defender exclusions and cleanup retries. That was
treating a symptom.

Root cause: performUpgrade() self-upgrades by running
exec.Command(os.Executable(), "upgrade", "prod", "-vv") as a detached,
windowless child. In the real ctrld binary this re-execs ctrld and is
correct. Under `go test`, os.Executable() is the test binary itself, and
`go test` stops flag parsing at the first positional arg ("upgrade") and
ignores the rest -- so the child silently re-runs the entire test suite.
That child hits the upgrade tests again and spawns more detached children,
recursively: a fork bomb of hidden processes that pins the runner's
CPU/memory and keeps the test binary's image file locked. Windows refuses
to delete the image of a running process, hence the "Access is denied"
during after_script. Whether any children are still alive when cleanup
runs is a timing race, which is why the failure was flaky.

Two tests reached this path: Test_performUpgrade (directly) and
Test_selfUpgradeCheck (via selfUpgradeCheck -> performUpgrade on the
"upgrade allowed" case).

Fix:
- prog.go: extract the command construction into a package-level
  newUpgradeCmd var. Production behavior is unchanged.
- main_test.go: stub newUpgradeCmd once in TestMain so the whole test
  binary self-execs with `-test.run=^$` (matches no tests, exits
  immediately) instead of re-running the suite. This covers every test
  that reaches performUpgrade, present and future, while still exercising
  the cmd.Start() success path.
2026-06-16 14:52:45 +07:00
Cuong Manh Le da454db8ef docker: update Dockerfile to use bookworm
Stick to go1.25 for now, since using go1.26 causing a runtime panic when
building arm platforms.
2026-06-16 14:49:33 +07:00
Cuong Manh Le 3fe9b27fb4 fix(doh,doq): reject oversized upstream DNS responses
DoH, DoH3, and DoQ response paths previously used io.ReadAll on
attacker-controlled upstream responses before enforcing any protocol-level
size limit. A malicious or compromised upstream could return an oversized
body or stream and force ctrld to buffer unbounded data before eventually
failing DNS parsing.

Cap DoH/DoH3 response bodies at dns.MaxMsgSize and cap DoQ streams at the
2-byte length prefix plus dns.MaxMsgSize. Also limit non-200 DoH error
bodies so error formatting cannot consume large upstream responses.
2026-06-16 14:49:01 +07:00
Cuong Manh Le 35455eb0b9 fix(doq): share QUIC transport, close send side before read (RFC 9250)
DoQ pools now keep a single quic.Transport and UDP socket for all dials,
so parallel dial and reconnect churn no longer allocate a new socket per
attempt or leak the winner's UDP conn when the caller owns the packet
conn.

quicParallelDialer accepts an optional transport: when set, dials use
Transport.DialEarly on that socket; when nil, behavior matches the old
per-dial ListenUDP path (losers close their sockets).

Per RFC 9250 §4.2, close the query stream's send side before reading the
response so strict upstreams see STREAM FIN before answering.

CloseIdleConnections closes the shared transport and underlying UDP
conn so checked-out connections and the OS socket are torn down.

Add a FIN-strict test server, coverage for bootstrap vs parallel-dial
paths, and a Linux-only FD churn regression test.
2026-06-16 14:48:43 +07:00
Cuong Manh Le f1309121ae doq: validate DNS-over-QUIC response framing
DoQ responses are length-prefixed per RFC 9250. The resolver previously
assumed the stream always contained at least two bytes and unpacked from
buf[2:], which could panic on truncated or malicious replies.

Validate the prefix against the bytes read, return a clear error, and
retire the connection from the pool on framing failure. Unpack only the
slice declared by the prefix so a short read cannot be misinterpreted as
a full message.

Add regression coverage with a small test server that returns malformed
raw payloads (empty, one byte, prefix-only, prefix larger than payload).
2026-06-16 14:48:33 +07:00
Cuong Manh Le 06668a2b6c cmd/cli: rate-limit PIN brute-force on control socket
Currently there is no limit on PIN attempts, allowing unlimited
brute force if an attacker gains socket access. While the socket is
root-only by default, rate limiting is cheap defense-in-depth.
2026-06-16 14:47:27 +07:00
Cuong Manh Le 97e5e99b8d cmd/cli: use os.CreateTemp for symlink-safe temp file creation
Current code writes to a predictable path, which on systems without
`fs.protected_symlinks` (e.g. embedded routers) could allow a local
attacker with API compromise to perform symlink attacks.
2026-06-16 14:47:19 +07:00
Cuong Manh Le c54ff701bd internal/router/dnsmasq: use text/template instead of html/template
Since this is a plain-text config, not html.
2026-06-16 14:47:08 +07:00
Cuong Manh Le 33682e2312 all: explicit TLS MinVersion in tls.Config
Go's default is already TLS 1.2+ (since Go 1.18), but making this
explicit satisfies RFC 7858/9250 recommendations and makes the security
intent clear for auditors.
2026-06-16 14:46:42 +07:00
Cuong Manh Le 87ddf03b90 .github/workflows: bump go and staticcheck version 2026-06-02 14:20:05 +07:00
Cuong Manh Le d49a4c67c9 Bump golang.org/x/net to v0.55.0
For GO-2026-5026 security fix.
2026-06-02 14:18:23 +07:00
Cuong Manh Le 75e8447c75 test: isolate VPN DNS settling tests from host adapters 2026-06-01 16:30:34 +07:00
Cuong Manh Le 5dd5846cca cmd/cli: skip upstream.os healthcheck when WFP loopback protect enabled
Since the check will always be failed in this case, causing unnecessary
log spamming.
2026-05-05 22:15:54 +07:00
Cuong Manh Le d1ea70d688 fix: prevent panic on network change during SetSelfIP
SetSelfIP unconditionally accessed t.dhcp, but t.dhcp is only
initialized when DHCP discovery is enabled. A network change event
can fire SetSelfIP regardless of the discovery configuration,
causing a nil pointer dereference.

Guard the t.dhcp access with a nil check so the self IP is still
updated on the Table even when DHCP discovery is disabled.
2026-04-22 15:30:59 +07:00
Cuong Manh Le ed98104384 doq: use OpenStreamSync and retry on StreamLimitReachedError
Replace conn.OpenStream (non-blocking) with conn.OpenStreamSync so that
the resolver waits for the server's MAX_STREAMS credit replenishment frame
instead of immediately failing when the stream limit is temporarily
exhausted. Also retry on StreamLimitReachedError as defense-in-depth for
servers that are slow or fail to send MAX_STREAMS updates.
2026-04-13 17:56:16 +07:00
Cuong Manh Le 839b8236e7 docs: add known issue for daemon crashing on Merlin 2026-04-07 11:34:07 +07:00
Cuong Manh Le fe08f00746 fix(darwin): correct pf rules tests 2026-03-03 15:36:46 +07:00
Cuong Manh Le 9be15aeec8 fix(windows): make staticcheck happy 2026-03-03 15:15:16 +07:00
Cuong Manh Le a4f0418811 fix(darwin): handle mDNSResponder on port 53 to avoid bind conflicts
When mDNSResponder is using port 53 on macOS, adjust listener config to
use 0.0.0.0:53, stop mDNSResponder before binding, and run cleanup on
install and uninstall so the DNS server can start reliably.
2026-03-03 14:25:25 +07:00
Cuong Manh Le 40c68a13a1 fix(metadata): detect login user via logname when running under sudo
On Darwin 26.2+, sudo no longer preserves SUDO_USER, LOGNAME, and USER
(CVE-2025-43416), so env-based detection fails. Use the logname(1)
command on Unix first, then fall back to environment variables and
user.Current() so the real login user is still reported correctly.
2026-03-03 14:25:11 +07:00
Cuong Manh Le 3f30ec30d8 refactor(doq): simplify DoQ connection pool implementation
Replace the map-based pool and refCount bookkeeping with a channel-based
pool. Drop the closed state, per-connection address tracking, and extra
mutexes so the pool relies on the channel for concurrency and lifecycle,
matching the approach used in the DoT pool.
2026-03-03 14:24:50 +07:00
Cuong Manh Le 4790eb2c88 refactor(dot): simplify DoT connection pool implementation
Replace the map-based pool and refCount bookkeeping with a channel-based
pool. Drop the closed state, per-connection address tracking, and
extra mutexes so the pool relies on the channel for concurrency and
lifecycle.
2026-03-03 14:24:39 +07:00
Cuong Manh Le da3ea05763 fix(dot): validate connections before reuse to prevent io.EOF errors
Add connection health check in getConn to validate TLS connections
before reusing them from the pool. This prevents io.EOF errors when
reusing connections that were closed by the server (e.g., due to idle
timeout).
2026-03-03 14:24:27 +07:00
Cuong Manh Le 209c9211b9 fix(dns): handle empty and invalid IP addresses gracefully
Add guard checks to prevent panics when processing client info with
empty IP addresses. Replace netip.MustParseAddr with ParseAddr to
handle invalid IP addresses gracefully instead of panicking.

Add test to verify queryFromSelf handles IP addresses safely.
2026-03-03 14:24:07 +07:00
Cuong Manh Le acbebcf7c2 perf(dot): implement connection pooling for improved performance
Implement TCP/TLS connection pooling for DoT resolver to match DoQ
performance. Previously, DoT created a new TCP/TLS connection for every
DNS query, incurring significant TLS handshake overhead. Now connections are
reused across queries, eliminating this overhead for subsequent requests.

The implementation follows the same pattern as DoQ, using parallel dialing
and connection pooling to achieve comparable performance characteristics.
2026-03-03 14:22:55 +07:00
Cuong Manh Le 2e8a0f00a0 fix(config): use three-state atomic for rebootstrap to prevent data race
Replace boolean rebootstrap flag with a three-state atomic integer to
prevent concurrent SetupTransport calls during rebootstrap. The atomic
state machine ensures only one goroutine can proceed from "started" to
"in progress", eliminating the need for a mutex while maintaining
thread safety.

States: NotStarted -> Started -> InProgress -> NotStarted

Note that the race condition is still acceptable because any additional
transports created during the race are functional. Once the connection
is established, the unused transports are safely handled by the garbage
collector.
2026-03-03 14:22:43 +07:00
Cuong Manh Le 1f4c47318e refactor(config): consolidate transport setup and eliminate duplication
Consolidate DoH/DoH3/DoQ transport initialization into a single
SetupTransport method and introduce generic helper functions to eliminate
duplicated IP stack selection logic across transport getters.

This reduces code duplication by ~77 lines while maintaining the same
functionality.
2026-03-03 14:22:32 +07:00
Cuong Manh Le e8d1a4604e perf(doq): implement connection pooling for improved performance
Implement QUIC connection pooling for DoQ resolver to match DoH3
performance. Previously, DoQ created a new QUIC connection for every
DNS query, incurring significant handshake overhead. Now connections are
reused across queries, eliminating this overhead for subsequent requests.

The implementation follows the same pattern as DoH3, using parallel dialing
and connection pooling to achieve comparable performance characteristics.
2026-03-03 14:22:16 +07:00
Cuong Manh Le 8d63a755ba Removing outdated netlink codes 2026-03-03 14:21:46 +07:00
Cuong Manh Le f05519d1c8 refactor(network): consolidate network change monitoring
Remove separate watchLinkState function and integrate link state change
handling directly into monitorNetworkChanges. This consolidates network
monitoring logic into a single place and simplifies the codebase.

Update netlink dependency from v1.2.1-beta.2 to v1.3.1 and netns from
v0.0.4 to v0.0.5 to use stable versions.
2026-03-03 14:21:27 +07:00
Cuong Manh Le 1804e6db67 fix(windows): improve DNS server discovery for domain-joined machines
Add DNS suffix matching for non-physical adapters when domain-joined.
This allows interfaces with matching DNS suffix to be considered valid
even if not in validInterfacesMap, improving DNS server discovery for
remote VPN scenarios.

While at it, also replacing context.Background() with proper ctx
parameter throughout the function for consistent context propagation.
2026-03-03 14:20:14 +07:00
Cuong Manh Le 27c5be43c2 fix(system): disable ghw warnings to reduce log noise
Disable warnings from ghw library when retrieving chassis information.
These warnings are undesirable but recoverable errors that emit unnecessary
log messages. Using WithDisableWarnings() suppresses them while maintaining
functionality.
2026-01-09 15:10:29 +07:00
Cuong Manh Le 3beffd0dc8 .github/workflows: temporary use actions/setup-go
Since WillAbides/setup-go-faster failed with macOS-latest.

See: https://github.com/WillAbides/setup-go-faster/issues/37
2025-12-18 17:10:43 +07:00
Cuong Manh Le 1f9c586444 docs: add documentation for runtime internal logging 2025-12-18 17:10:43 +07:00
Cuong Manh Le a92e1ca024 Upgrade quic-go to v0.57.1 2025-12-18 17:10:43 +07:00
Cuong Manh Le 705df72110 fix: remove incorrect transport close on DoH3 error
Remove the transport Close() call from DoH3 error handling path.
The transport is shared and reused across requests, and closing it
on error would break subsequent requests. The transport lifecycle
is already properly managed by the http.Client and the finalizer
set in newDOH3Transport().
2025-12-18 17:10:43 +07:00
Cuong Manh Le 22122c45b2 Including system metadata when posting to utility API 2025-12-18 17:10:39 +07:00
Cuong Manh Le 78ea2d6361 .github/workflows: upgrade staticcheck-action to v1.4.0
While at it, also bump go version to 1.24
2025-11-12 15:22:01 +07:00
Cuong Manh Le df3cf7ef62 Upgrade quic-go to v0.56.0 2025-11-12 15:15:16 +07:00
Cuong Manh Le 80e652b8d9 fix: ensure log and cache flags are processed during reload
During reload operations, log and cache flags were not being processed,
which prevented runtime internal logs from working correctly. To fix this,
processLogAndCacheFlags was refactored to accept explicit viper and config
parameters instead of relying on global state, enabling it to be called
during reload with the new configuration. This ensures that log and cache
settings are properly applied when the service reloads its configuration.
2025-11-12 15:15:05 +07:00
Cuong Manh Le 091c7edb19 Fix: Filter root domain from search domains on Linux
Remove empty and root domain (".") entries from search domains list
to prevent systemd-resolved errors. This addresses the issue where
systemd doesn't allow root domain in search domains configuration.

The filtering ensures only valid search domains are passed to
systemd-resolved, preventing DNS operation failures.
2025-11-12 15:14:40 +07:00
Cuong Manh Le 6c550b1d74 Upgrade quic-go to v0.55.0
While at it, also bump required go version to 1.24
2025-11-12 15:14:26 +07:00
Cuong Manh Le 0e3f764299 feat: add --rfc1918 flag for explicit LAN client support
Make RFC1918 listener spawning opt-in via --rfc1918 flag instead of automatic behavior.
This allows users to explicitly control when ctrld listens on private network addresses
to receive DNS queries from LAN clients, improving security and configurability.

Refactor network interface detection to better distinguish between physical and virtual
interfaces, ensuring only real hardware interfaces are used for RFC1918 address binding.
2025-09-25 16:45:56 +07:00
Cuong Manh Le e52402eb0c Upgrade quic-go to v0.54.0 2025-09-25 16:45:05 +07:00
Cuong Manh Le 2133f31854 docs: add known issues documentation for Darwin 15.5 upgrade issue
Documents the self-upgrade issue on macOS Darwin 15.5 affecting
ctrld v1.4.2+ and provides workarounds for affected users.
2025-09-25 16:44:54 +07:00
Cuong Manh Le ce1a165348 .github/workflows: bump go version to 1.24.x 2025-08-15 23:33:23 +07:00
Cuong Manh Le fd48e6d795 fix: ensure upstream health checks can handle large DNS responses
- Add UpstreamConfig.VerifyMsg() method with proper EDNS0 support
- Replace hardcoded DNS messages in health checks with standardized verification method
- Set EDNS0 buffer size to 4096 bytes to handle large DNS responses
- Add test case for legacy resolver with extensive extra sections
2025-08-15 22:55:47 +07:00
Cuong Manh Le d71d1341b6 refactor(prog): move network monitoring outside listener loop
Move the network monitoring goroutine initialization outside the listener
loop to prevent it from being started multiple times. Previously, the
network monitoring was started once per listener during first run, which
was unnecessary and could lead to multiple monitoring instances.

The change ensures network monitoring is started only once per program
execution cycle, improving efficiency and preventing potential resource
waste from duplicate monitoring goroutines.

- Extract network monitoring goroutine from listener loop
- Start network monitoring once per run cycle instead of per listener
- Maintain same functionality while improving resource usage
2025-08-12 16:49:05 +07:00
Cuong Manh Le 21855df4af fix: correct Windows API constants to fix domain join detection
The function was incorrectly identifying domain-joined status due to wrong
constant values, potentially causing false negatives for domain-joined machines.
2025-08-12 16:48:10 +07:00
Cuong Manh Le 66e2d3a40a refactor: move network monitoring to separate goroutine
- Move network monitoring initialization out of serveDNS() function
- Start network monitoring in a separate goroutine during program startup
- Remove context parameter from monitorNetworkChanges() as it's not used
- Simplify serveDNS() function signature by removing unused context parameter
- Ensure network monitoring starts only once during initial run, not on reload

This change improves separation of concerns by isolating network monitoring
from DNS serving logic, and prevents potential issues with multiple
monitoring goroutines if starting multiple listeners.
2025-08-12 16:46:57 +07:00
Cuong Manh Le 36a7423634 refactor: extract empty string filtering to reusable function
- Add filterEmptyStrings utility function for consistent string filtering
- Replace inline slices.DeleteFunc calls with filterEmptyStrings
- Apply filtering to osArgs in addition to command args
- Improves code readability and reduces duplication
- Uses slices.DeleteFunc internally for efficient filtering
2025-07-15 23:09:54 +07:00
Cuong Manh Le e616091249 cmd/cli: ignore empty positional argument for start command
The validation was added during v1.4.0 release, but causing one-liner
install failed unexpectedly.
2025-07-15 21:57:36 +07:00
Cuong Manh Le 0948161529 Avoiding Windows runners file locking issue 2025-07-15 20:59:57 +07:00
Cuong Manh Le ce29b5d217 refactor: split selfUpgradeCheck into version check and upgrade execution
- Move version checking logic to shouldUpgrade for testability
- Move upgrade command execution to performUpgrade
- selfUpgradeCheck now composes these two for clarity
- Update and expand tests: focus on logic, not side effects
- Improves maintainability, testability, and separation of concerns
2025-07-15 19:12:23 +07:00
Cuong Manh Le de24fa293e internal/router: support Ubios 4.3+
This change improves compatibility with newer UniFi OS versions while
maintaining backward compatibility with UniFi OS 4.2 and earlier.
The refactoring also reduces code duplication and improves maintainability
by centralizing dnsmasq configuration path logic.
2025-07-15 19:11:13 +07:00
Cuong Manh Le 6663925c4d internal/router: support Merlin Guest Network Pro VLAN
By looking for any additional dnsmasq configuration files under
/tmp/etc, and handling them like default one.
2025-07-15 19:10:10 +07:00
Cuong Manh Le c4efa1ab97 Initializing default os resolver during upstream bootstrap
Since calling defaultNameservers may block the whole bootstrap process
if there's no valid DNS servers available.
2025-06-12 16:22:52 +07:00
Cuong Manh Le 7cea5305e1 all: fix a regression causing invalid reloading timeout
In v1.4.3, ControlD bootstrap DNS is used again for bootstrapping
process. When this happened, the default system nameservers will be
retrieved first, then ControlD DNS will be used if none available.

However, getting default system nameservers process may take longer than
reloading command timeout, causing invalid error message printed.

To fix this, ensuring default system nameservers is retrieved once.
2025-06-10 19:42:26 +07:00
Cuong Manh Le a20fbf95de all: enhanced TLS certificate verification error messages
Added more descriptive error messages for TLS certificate verification
failures across DoH, DoT, DoQ, and DoH3 protocols. The error messages
now include:

- Certificate subject information
- Issuer organization details
- Common name of the certificate

This helps users and developers better understand certificate validation
failures by providing specific details about the untrusted certificate,
rather than just a generic "unknown authority" message.

Example error message change:
Before: "certificate signed by unknown authority"
After: "certificate signed by unknown authority: TestCA, TestOrg, TestIssuerOrg"
2025-06-10 19:42:00 +07:00
Cuong Manh Le 628c4302aa cmd/cli: preserve search domains when reverting resolv.conf
Fixes search domains not being preserved when the resolv.conf file is
reverted to its previous state. This ensures that important domain
search configuration is maintained during DNS configuration changes.

The search domains handling was missing in setResolvConf function,
which is responsible for restoring DNS settings.
2025-06-04 18:36:51 +07:00
Cuong Manh Le 8dc34f8bf5 internal/net: improve IPv6 support detection with multiple common ports
Changed the IPv6 support detection to try multiple common ports (HTTP/HTTPS) instead of
just testing against a DNS port. The function now returns both the IPv6 support status
and the successful port that confirmed the connectivity. This makes the IPv6 detection
more reliable by not depending solely on DNS port availability.

Previously, the function only tested connectivity to a DNS port (53) over IPv6.
Now it tries to connect to commonly available ports like HTTP (80) and HTTPS (443)
until it finds a working one, making the detection more robust in environments where
certain ports might be blocked.
2025-06-04 16:29:28 +07:00
Cuong Manh Le b4faf82f76 all: set edns0 cookie for shared message
For cached or singleflight messages, the edns0 cookie is currently
shared among all of them, causing mismatch cookie warning from clients.
The ctrld proxy should re-set client cookies for each request
separately, even though they use the same shared answer.
2025-05-27 18:09:16 +07:00
Cuong Manh Le a983dfaee2 all: optimizing multiple queries to upstreams
To guard ctrld from possible DoS to remote upstreams, this commit
implements following things:

 - Optimizing multiple queries with the same domain and qtype to use
   singleflight group, so there's only 1 query to remote upstreams at
   any time.
 - Adding a hot cache with 1 second TTL, so repeated queries will re-use
   the result from cache if existed, preventing unnecessary requests to
   remote upstreams.
2025-05-23 21:09:15 +07:00
Cuong Manh Le 62f73bcaa2 all: preserve search domains settings
So bare hostname will be resolved as expected when ctrld is running.
2025-05-15 17:00:59 +07:00
Cuong Manh Le 00e9d2bdd3 all: do not listen on 0.0.0.0 on desktop clients
Since this may create security vulnerabilities such as DNS amplification
or abusing because the listener was exposed to the entire local network.
2025-05-15 16:59:24 +07:00
Cuong Manh Le d1ea1ba08c Disable parallel test for TestUpstreamConfig_SetupBootstrapIP
There's a bug in wmi library which causes race condition when getting
wmi instance manager concurrently. The new tests for setup bootstrap ip
concurrently thus failed unexpectedly.

There's going to be a fix sent to the upstream, in the meantime, disable
the parallel test temporary.

See: https://github.com/microsoft/wmi/issues/165
2025-04-18 00:36:58 +07:00
Cuong Manh Le c06c8aa859 Unifying DNS from /etc/resolv.conf function
As part of v1.4.0 release, reading DNS from /etc/resolv.conf file is
only available for Macos. However, there's no reason to prevent this
function from working on other *nix systems.

This commit unify the function to *nix, so it could be added as DNS
source for Linux and Freebsd.
2025-04-17 17:19:47 +07:00
Cuong Manh Le 0c2cc00c4f Using ControlD bootstrap DNS again
So on system where there's no available DNS, non-ControlD upstreams
could be bootstrapped like before.

While at it, also improving lookupIP to not initializing OS resolver
anymore, removing the un-necessary contention for accquiring/releasing
OS resolver mutex.
2025-04-17 17:15:15 +07:00
Cuong Manh Le 8d6ea91f35 Allowing bootstrap IPs for ControlD sub-domains
So protocol which uses sub-domain like doq/dot could be bootstrap in
case of no DNS available.
2025-04-17 17:13:10 +07:00
Cuong Manh Le 7dfb77228f cmd/cli: handle ipc warning message more precisely
If the socket file does not exist, it means that "ctrld start" was never
run. In this case, the warning message should not be printed to avoid
needless confusion.
2025-04-17 17:12:06 +07:00
Cuong Manh Le 3937e885f0 Bump golang.org/x/net to v0.38.0
Fixes CVE-2025-22872
2025-04-01 23:20:12 +07:00
Cuong Manh Le c651003cc4 Support direct ip in lookupIP function
So users can supply ip directly in config, avoiding unnecessary domain
lookup while bootstrapping.
2025-03-31 23:02:59 +07:00
Cuong Manh Le b7ccfcb8b4 Do not include commit hash when releasing tag 2025-03-27 20:11:57 +07:00
Cuong Manh Le a9ed70200b internal/router: change dnsmasq config manipulation on Merlin
Generally, using /jffs/scripts/dnsmasq.postconf is the right way to add
custom configuration to dnsmasq on Merlin. However, we have seen many
reports that the postconf does not work on their devices.

This commit changes how dnsmasq config manipulation is done on Merlin,
so it's expected to work on all Merlin devices:

 - Writing /jffs/scripts/dnsmasq.postconf script
 - Copy current dnsmasq.conf to /jffs/configs/dnsmasq.conf
 - Run postconf script directly on /jffs/configs/dnsmasq.conf
 - Restart dnsmasq

This way, the /jffs/configs/dnsmasq.conf will contain both current
dnsmasq config, and also custom config added by ctrld, without worrying
about conflicting, because configuration was added by postconf.

See (1) for more details about custom config files on Merlin.

(1) https://github.com/RMerl/asuswrt-merlin.ng/wiki/Custom-config-files
2025-03-26 23:18:53 +07:00
Cuong Manh Le c6365e6b74 cmd/cli: handle stop signal from service manager
So using "ctrld stop" or service manager to stop ctrld will end up with
the same result, stopped ctrld with a working DNS, and deactivation pin
code will always have effects if set.
2025-03-26 23:18:36 +07:00
Cuong Manh Le dacc67e50f Using LAN servers from OS resolver for private resolver
So heavy functions are only called once and could be re-used in
subsequent calls to NewPrivateResolver.
2025-03-26 23:18:21 +07:00
Cuong Manh Le c60cf33af3 all: implement self-upgrade flag from API
So upgrading don't have to be initiated manually, helping large
deployments to upgrade to latest ctrld version easily.
2025-03-26 23:18:04 +07:00
Cuong Manh Le f27cbe3525 all: fallback to use direct IPs for ControlD assets 2025-03-26 23:17:50 +07:00
Cuong Manh Le 2de1b9929a Do not send legacy DNS queries to bootstrap DNS 2025-03-26 23:17:26 +07:00
Cuong Manh Le 8bf654aece Bump golang.org/x/net to v0.36.0
Fixing https://pkg.go.dev/vuln/GO-2025-3503
2025-03-26 23:17:18 +07:00
Cuong Manh Le 84376ed719 cmd/cli: add missing pre-run setup for start command
Otherwise, ctrld won't be able to reset DNS correctly if problems
happened during self-check process.
2025-03-26 23:17:06 +07:00
Cuong Manh Le 7a136b8874 all: disable client discover on desktop platforms
Since requests are mostly originated from the machine itself, so all
necessary metadata is local to it.

Currently, the desktop platforms are Windows desktop and darwin.
2025-03-26 23:16:57 +07:00
Cuong Manh Le 58c0e4f15a all: remove ipv6 check polling
netmon provides ipv6 availability during network event changes, so use
this metadata instead of wasting on polling check.

Further, repeated network errors will force marking ipv6 as disable if
were being enabled, catching a rare case when ipv6 were disabled from
cli or system settings.
2025-03-26 23:16:38 +07:00
Cuong Manh Le 3b2e48761e Upgrade dominikh/staticcheck-action to v1.3.1
To upgrade actions/cache dependency, since v1-v2 was deprecated.
2025-03-06 18:42:06 +07:00
Cuong Manh Le b27064008e cmd/cli: do not validate if custom config is empty
Avoiding useless warnings when doing rules validation.
2025-03-06 18:17:48 +07:00
Cuong Manh Le 1ad63827e1 cmd/cli: do not validate invalid syntax config
If the remote custom config is an invalid syntax config, we should not
do rules validation, prevent unnecessary error messages printed.
2025-03-01 00:24:59 +07:00
Cuong Manh Le 20e61550c2 cmd/cli: set default value for remote config before validating
Since empty network will now have a default value, we need to set it
after sytax validation, prevent false positive when validating rules.
2025-03-01 00:24:55 +07:00
Cuong Manh Le 020b814402 cmd/cli: fix validating remote custom config
Currently, custom config is only validated against invalid syntax, not
the validating rules for each configuration value. It causes ctrld
process fatal instead of disregarding as expected.

To fix this, force the validating rule after fetching remote config.
While at it, also add the default network value if non-existed.
2025-02-28 20:08:26 +07:00
Cuong Manh Le e578867118 internal/router: fix fresh tomato config path
When ctrld performs upgrading tasks, the current binary would be moved
to different file, thus the executable will return this new file name,
instead of the old "/path/to/ctrld".

The config path on FreshTomato is located in the same directory with
ctrld binary, with ".startup" suffix. So when the binary was moved
during upgrading, the config path is located wrongly.

To fix it, read the binary path from service config first, then only
fallback to the current executable if the path is empty (this is the
same way ctrld is doing for other router platforms).
2025-02-27 23:47:46 +07:00
Cuong Manh Le cc9e27de5f Add some more mDNS services
Import from https://github.com/Control-D-Inc/ctrld/pull/145

Thanks @jaydeethree for contributing.
2025-02-27 18:52:50 +07:00
Cuong Manh Le 6ab3ab9faf cmd/cli: add DNS as ctrld service dependency
So on Windows system where there's local DNS running, ctrld could set
DNS forwarders correctly after DNS service started.
2025-02-26 00:44:13 +07:00
Cuong Manh Le e60a92e93e cmd/cli: improving IPC when try listening failed
So the "ctrld start" should know earlier that "ctrld run" failed to
listen on certain port, and terminate earlier instead of waiting for
timeout happened.
2025-02-25 03:33:00 +07:00
Cuong Manh Le b05056423a docs: add documentation for LAN queries 2025-02-21 20:44:34 +07:00
Cuong Manh Le c7168739c7 cmd/cli: use OS resolver as default upstream for SRV lan hostname
Since application may need SRV record for public domains, which could be
blocked by OS resolver, but not with remote upstreams.

This was reported by a Minecraft user, who seeing thing is broken after
upgrading to v1.4.0 release.
2025-02-21 20:44:34 +07:00