Commit Graph

628 Commits

Author SHA1 Message Date
Dev Scribe 3226c2d0e2 Port NRPT recovery limits to master
Also adapts the logging fields to the master branch logger API.
2026-07-13 20:18:26 +07:00
Cuong Manh Le eb8756bbe5 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-13 20:15:33 +07:00
Cuong Manh Le ff14fc8ac9 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-09 16:41:55 +07:00
Dev Scribe 8948fa402b fix: back off macOS pf watchdog exec storms 2026-07-09 16:25:47 +07:00
Codescribe 8330049b66 feat: add firewall mode DNS-resolved IP allowlist 2026-07-08 15:31:54 +07:00
Cuong Manh Le c1d3686f9a 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-22 17:04:08 +07:00
Dev Scribe 9399f4590b fix: refresh macOS VPN DNS after pf stabilization 2026-06-22 17:04:06 +07:00
Codescribe a4bc23d17e fix: allow intercept fallback for default listener 2026-06-22 17:00:57 +07:00
Codescribe 7a8450cc40 fix: flush pf states after forced DNS intercept reload 2026-06-22 17:00:47 +07:00
Cuong Manh Le 5ccbf63e58 test: isolate VPN DNS settling tests from host adapters 2026-06-22 17:00:38 +07:00
Codescribe 3c740b9693 fix: port Windows VPN DNS settling fix to master 2026-05-29 13:38:27 +07:00
Cuong Manh Le 01490434a6 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-05-12 12:42:07 +07:00
Cuong Manh Le a61677b6e4 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-05-12 12:41:56 +07:00
Codescribe 1735d3d55b cmd/cli: skip upstream.os healthcheck when WFP loopback protect enabled
When WFP loopback protect is active, the upstream.os healthcheck will
always fail because an external WFP block filter is interfering with
plain DNS. This demotes those expected failures to debug level and
returns errOsHealthcheckSuppressed so the recovery loop treats them
as non-fatal, eliminating the log spam described in #526.
2026-05-07 19:37:42 +07:00
Codescribe 81aa6b237b dns_intercept: add WFP loopback protect for VPN block-outside-dns
When third-party VPN software (e.g., OpenVPN) installs WFP block filters via
block-outside-dns, all DNS traffic to non-tunnel interfaces is blocked —
including DNS to 127.0.0.1 (ctrld's NRPT target). This breaks DNS mode
interception because the NRPT catch-all rule routes queries to loopback,
but WFP blocks the connection before it reaches ctrld's listener.

Fix: after exhausting all NRPT recovery attempts, activate a minimal WFP
session with "hard permit" filters (FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT)
for DNS to localhost in a max-priority sublayer (weight 0xFFFF). This
overrides the VPN's block for loopback DNS only, while preserving the
VPN's DNS leak protection for all other (non-loopback) DNS traffic.

The loopback protect is:
- Only activated when NRPT probes fail (not preemptively)
- Harmless when no conflicting WFP blocks exist (permit-only, no blocks)
- Persistent until ctrld shutdown (survives VPN reconnect cycles)
- Cleaned up by the existing cleanupWFPFilters path on shutdown
2026-04-30 19:30:43 +07:00
Codescribe 8abeeea4c3 log: persist internal runtime logs to disk
Add file-backed persistence to the internal logWriter so runtime logs
survive service restarts. When internal logging is enabled (CD mode,
no explicit log_path), writes are teed to both the existing in-memory
ring buffer and a rotated file on disk (ctrld.log in the home directory).

File rotation: 5MB max with 1 backup (ctrld.log.1), so max ~10MB on disk.
Log view/send now reads from the persisted files (including backup) to
provide complete history across restarts. Live tail continues to use
the in-memory subscriber mechanism unchanged.

Activation: same conditions as existing internal logging — CD mode only,
no log_path configured. No new config options or dependencies.
2026-04-30 19:19:19 +07:00
Codescribe b3c670b17e dns: fix recovery race condition during rapid network transitions
When multiple network changes fire in quick succession (e.g., VPN
disconnect + interface swap), the second handleRecovery() call cancels
the first but inherits stale DoH transports, causing DNS blackouts
of up to 30 seconds.

Three changes to reduce worst-case recovery from ~30s to <3s:

1. ForceReBootstrap() on recovery entry — closes dead connections and
   creates fresh transports synchronously before probing, replacing the
   lazy ReBootstrap() flag that left stale connections for probes to hit.

2. Debounce handleRecovery() for network changes (500ms window) — only
   the recovery flow is debounced; all other state updates (IP, pf
   anchor, VPN DNS, tunnel checks) still run immediately on every event.
   This eliminates the cancel-and-restart race without missing state.

3. Combined effect: ForceReBootstrap closes old in-flight connections
   (closeTransports) and builds new ones (SetupTransport) atomically,
   so recovery probes never inherit dead connections from a prior
   recovery attempt.
2026-04-30 19:19:19 +07:00
Codescribe a8821e6d00 fix(darwin): support non-standard listener port in intercept mode
When port 53 is taken (e.g. by mDNSResponder), ctrld failed with
'could not find available listen ip and port' instead of falling back
to port 5354. Root cause: tryUpdateListenerConfig() checked the
dnsIntercept bool, which is derived in prog.run() AFTER listener
config is resolved.

Fix: check interceptMode string directly (CLI flag + config fallback)
in a new tryUpdateListenerConfigIntercept() that tries 127.0.0.1:53
then 127.0.0.1:5354.

Also updates buildPFAnchorRules() to use the actual listener IP/port
from config instead of hardcoded 127.0.0.1:53, so pf rules redirect
to wherever ctrld is actually listening.
2026-04-30 19:19:19 +07:00
Codescribe a3880beec2 docs: port IPv6 learnings and comment fixes to master
- Update comment in ensurePFAnchorReference: pfctl -sn returns
  rdr-anchor only (nat-anchor not used by ctrld)
- Update nat-anchor table entry in pf-dns-intercept.md
- Add pf nuances 10-16 from investigation: cross-AF redirect,
  block return, sendmsg EINVAL, nat-on-lo0, raw sockets, DIOCNATLOOK,
  and the pragmatic IPv6 block solution
2026-04-30 19:19:19 +07:00
Codescribe d7124995d2 fix: bracket IPv6 addresses in VPN DNS upstream config
upstreamConfigFor() used strings.Contains(":") to decide whether to
append ":53", which always evaluates true for IPv6 addresses. This left
bare addresses like "2a0d:6fc0:9b0:3600::1" without brackets or port,
causing net.Dial to reject with "too many colons in address".

Use net.JoinHostPort() which handles IPv6 bracketing automatically,
producing "[2a0d:6fc0:9b0:3600::1]:53".
2026-04-30 19:19:19 +07:00
Codescribe 86dafc432d Add log tail command for live log streaming
This commit adds a new `ctrld log tail` subcommand that streams
runtime debug logs to the terminal in real-time, similar to `tail -f`.

Changes:
- log_writer.go: Add Subscribe/tailLastLines for fan-out to tail clients
- control_server.go: Add /log/tail endpoint with streaming response
  - Internal logging: subscribes to logWriter for live data
  - File-based logging: polls log file for new data (200ms interval)
  - Sends last N lines as initial context on connect
- commands.go: Add `log tail` cobra subcommand with --lines/-n flag
- control_client.go: Add postStream() with no timeout for long-lived connections

Usage:
  sudo ctrld log tail          # shows last 10 lines then follows
  sudo ctrld log tail -n 50    # shows last 50 lines then follows
  Ctrl+C to stop
2026-04-30 19:19:19 +07:00
Cuong Manh Le ca8d07d3f5 fix(darwin): correct pf rules tests 2026-04-30 19:19:19 +07:00
Cuong Manh Le 2aaa78ef48 fix(windows): make staticcheck happy 2026-04-30 19:19:19 +07:00
Codescribe 0f2a930cf8 feat: robust username detection and CI updates
Add platform-specific username detection for Control D metadata:
- macOS: directory services (dscl) with console user fallback
- Linux: systemd loginctl, utmp, /etc/passwd traversal
- Windows: WTS session enumeration, registry, token lookup
2026-04-30 19:19:19 +07:00
Codescribe 5a6163142c feat: add VPN DNS split routing 2026-04-30 19:19:19 +07:00
Codescribe 402771bed6 feat: add Windows NRPT and WFP DNS interception 2026-04-30 19:19:19 +07:00
Codescribe a99dcca288 feat: add macOS pf DNS interception 2026-04-30 19:19:19 +07:00
Codescribe 395335162f feat: introduce DNS intercept mode infrastructure 2026-04-30 19:19:19 +07:00
Cuong Manh Le 878b3d7920 fix(cli): avoid warning when HTTP log server is not yet available
Treat "socket missing" (ENOENT) and connection refused as expected when
probing the log server, and only log when the error indicates something
unexpected. This prevents noisy warnings when the log server has not
started yet.

Discover while doing captive portal tests.
2026-04-30 19:19:19 +07:00
Cuong Manh Le 6f331f19c8 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-04-30 19:19:19 +07:00
Cuong Manh Le e4ca728ef0 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-04-30 19:19:19 +07:00
Cuong Manh Le ba3dd3a4b0 Including system metadata when posting to utility API 2026-04-30 19:19:19 +07:00
Cuong Manh Le aacbcad133 cmd/cli: workaround TB.TemdDir path too long for Unix socket path
Discover while testing v2.0.0 Github MR.

See: https://github.com/golang/go/issues/62614

While at it, also fix staticcheck linter on Windows.
2026-04-30 19:19:19 +07:00
Cuong Manh Le 1489245f50 cmd/cli: ensure error message ends with newline 2026-04-30 19:19:19 +07:00
Cuong Manh Le 9b1f102315 refactor: remove unused StopOnFirstMatch field from MatchingConfig
Remove StopOnFirstMatch field that was defined but never used in the
actual matching logic.

The current implementation always evaluates all rule types and applies
a fixed precedence (Domain > MAC > Network), making the StopOnFirstMatch
field unnecessary.

Changes:
- Remove StopOnFirstMatch from MatchingConfig structs
- Update DefaultMatchingConfig() function
- Update all test cases and references
- Simplify configuration to only include Order field

This cleanup removes dead code and simplifies the configuration API
without changing any functional behavior.
2026-04-30 19:19:19 +07:00
Cuong Manh Le c365051732 feat: add configurable rule matching with improved code structure
Implement configurable DNS policy rule matching order and refactor
upstreamFor method for better maintainability.

New features:
- Add MatchingConfig to ListenerPolicyConfig for rule order configuration
- Support custom rule evaluation order (network, mac, domain)
- Add stop_on_first_match configuration option
- Hidden from config files (mapstructure:"-" toml:"-") for future release

Code improvements:
- Create upstreamForRequest struct to reduce method parameter count
- Refactor upstreamForWithConfig to use single struct parameter
- Improve code readability and maintainability
- Maintain full backward compatibility

Technical details:
- String-based configuration converted to RuleType enum internally
- Default behavior preserved (network → mac → domain order)
- Domain rules still override MAC/network rules regardless of order
- Comprehensive test coverage for configuration integration

The matching configuration is programmatically accessible but hidden
from user configuration files until ready for public release.
2026-04-30 19:19:19 +07:00
Cuong Manh Le 650e47a504 refactor: consolidate network interface detection logic
Move platform-specific network interface detection from cmd/cli/ to root package
as ValidInterfaces function. This eliminates code duplication and provides a
consistent interface for determining valid physical network interfaces across
all platforms.

- Remove duplicate validInterfacesMap functions from platform-specific files
- Add context parameter to virtualInterfaces for proper logging
- Update all callers to use ctrld.ValidInterfaces instead of local functions
- Improve error handling in virtual interface detection on Linux
2026-04-30 19:19:18 +07:00
Cuong Manh Le f24059885f 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.
2026-04-30 19:19:18 +07:00
Cuong Manh Le 52cfb4c302 Change download url for v2
While at it, also updating CI flow to reflect new path.
2026-04-30 19:19:18 +07:00
Cuong Manh Le 6cf754883d refactor: replace Unix socket log communication with HTTP-based system
Replace the legacy Unix socket log communication between `ctrld start` and
`ctrld run` with a modern HTTP-based system for better reliability and
maintainability.

Benefits:
- More reliable communication protocol using standard HTTP
- Better error handling and connection management
- Cleaner separation of concerns with dedicated endpoints
- Easier to test and debug with HTTP-based communication
- More maintainable code with proper abstraction layers

This change maintains backward compatibility while providing a more robust
foundation for inter-process communication between ctrld commands.
2026-04-30 19:19:18 +07:00
Cuong Manh Le 4be262156f feat: enhance log reading with ANSI color stripping and comprehensive documentation
- Add newLogReader function with optional ANSI color code stripping
- Implement logReaderNoColor() and logReaderRaw() methods for different use cases
- Add comprehensive documentation for logReader struct and all related methods
- Add extensive test coverage with 16+ test cases covering edge cases

The new functionality allows consumers to choose between raw log data
(with ANSI color codes) or stripped content (without color codes),
making logs more suitable for different processing pipelines and
display environments.
2026-04-30 19:19:18 +07:00
Cuong Manh Le d3b01dc7e8 feat: capitalize all log messages for better readability
Capitalize the first letter of all log messages throughout the codebase
to improve readability and consistency in logging output.

Key improvements:
- All log messages now start with capital letters
- Consistent formatting across all logging statements
- Improved readability for debugging and monitoring
- Enhanced user experience with better formatted messages

Files updated:
- CLI commands and service management
- Internal client information discovery
- Network operations and configuration
- DNS resolver and proxy operations
- Platform-specific implementations

This completes the final phase of the logging improvement project,
ensuring all log messages follow consistent capitalization standards
for better readability and professional appearance.
2026-04-30 19:19:18 +07:00
Cuong Manh Le 88a297ad43 feat: enhance CLI commands and service management logging
Add comprehensive logging to CLI utility functions and configuration
management operations to improve visibility into CLI command execution
and configuration processing.

Key improvements:
- Configuration file writing operations with detailed error tracking
- Base64 configuration processing with step-by-step logging
- No-config mode flag processing with endpoint transformation logging
- Enhanced error handling with context preservation
- Success confirmation logging for all operations

This provides complete visibility into CLI configuration operations,
helping identify configuration issues and processing problems during
CLI command execution.
2026-04-30 19:19:18 +07:00
Cuong Manh Le 7040c2024a feat: enhance configuration and network management logging
Add comprehensive logging to configuration management and network operations
across all supported platforms to improve visibility into system setup and
network configuration processes.

Key improvements:
- Configuration initialization and validation logging
- CLI flag processing visibility (listen, log, cache flags)
- IP allocation/deallocation tracking across platforms
- DNS configuration operations logging (Linux, macOS, FreeBSD)
- Upstream bootstrap and fallback operation tracking
- Listener configuration initialization logging

This provides complete visibility into configuration management and network
setup operations, helping identify configuration issues and network setup
problems across different platforms.
2026-04-30 19:19:18 +07:00
Cuong Manh Le 082f5a0fac feat: enhance DNS proxy logging with comprehensive flow tracking
Add detailed logging throughout DNS proxy operations to improve visibility
into query processing, cache operations, and upstream resolver performance.

Key improvements:
- DNS server setup and listener management logging
- Complete query processing pipeline visibility
- Cache hit/miss and stale response handling logs
- Upstream resolver iteration and failure tracking
- Resolver-specific logging (OS, DoH, DoT, DoQ, Legacy)
- All log messages capitalized for better readability

This provides comprehensive debugging capabilities for DNS proxy operations
and helps identify performance bottlenecks and failure points in the
resolution chain.
2026-04-30 19:19:18 +07:00
Cuong Manh Le 7778c96f38 fix: use background context for DNS listeners to survive reloads
Change DNS listener context from parent context to background context
so that listeners continue running during configuration reloads.
Listener configuration changes require a service restart, not reload,
so listeners must persist across reload operations.

This prevents DNS listeners from being terminated when the parent
context is cancelled during reload operations.
2026-04-30 19:19:18 +07:00
Cuong Manh Le 64393b7b6c feat: enhance logging in service commands with consistent logger usage
- Add entry/exit logging to all ServiceCommand methods (start, stop, status, reload, restart, uninstall)
- Replace mainLog.Load() calls with consistent logger variable usage throughout
- Capitalize all logging messages for better readability
- Add error context logging for service manager initialization failures
- Add debug logging for key operations (restart sequence, cleanup, validation)
- Improve error handling with proper error context in all service commands
- Add completion logging to track command execution flow

This improves debugging capabilities and provides better operational visibility
for service management operations while maintaining clean user-facing messages.
2026-04-30 19:19:18 +07:00
Ginder Singh c8477fe442 start mobile library with provision id and custom hostname. 2026-04-30 19:19:18 +07:00
Cuong Manh Le 36afb16e57 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
2026-04-30 19:19:18 +07:00
Cuong Manh Le 134561c85a 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
2026-04-30 19:19:18 +07:00