Open
Conversation
If can not determine version, this is still better than UNKNOWN
Avoid memory and file descriptor leak.
This has been causing crashes on recent OpenWRT Archer C7v2 builds.
options.c: refactor around opt->logfile
- Run github actions on Ubuntu 22.04 - Build with gcc-12 and clang-15 - clang-tidy exclude list updated from scratch - Using "-gdwarf-4" because of: llvm/llvm-project#56550 - Added (void) to ignore return values - Added void if no function parameter is used - Default version string updated - valgrind suppression file added - Timeouts raised in case of valgrind test: more important to run stable
Rewritten hostname extraction from resolver URL
Allow clang-tidy to be disabled during compilation
Remove unnecessary math.h import
logf was a nasty one: some c++ 13 header defined it and conflicted. CURLINFO_PROTOCOL is deprecated, CURLINFO_SCHEME was suggested instead.
Yearly modernization
In current commit the important part is to avoid process exit and recover from faulty state, even when every ongoing request has to be dropped.
To have debug logs dump before FLOG panic and when it is necessary when debugging a rare issue. In options_cleanup() according to valgrind not closing logfd if it is the default stdout.
A few curl version after 8.9.0 kept connections opened which did not count in CURLMOPT_MAX_TOTAL_CONNECTIONS limit. Therefore more IO events are necessary to handle more sockets than we expect. Details in: curl/curl#15857
It was not working at all before. Better to get rid of TCP keepalive since it was not necessary. Raising curl requirement level because of option. It has been made configurable via argument, because different DoH server providers have different timeouts configured.
When a large burst of DNS requests came in and the only HTTPS/2 connection was too many time in idle state, curl closes it and opens brand new connection for every single DNS request. Obviously later it will keep only 1 and close all the others. So it is better to wait for multiplexing. When testing this setting, the DNS query latency values did not raise.
If we already have a connection 5 second should be enough, else leave time for connecting to server. Goal is to fail faster if some reused HTTPS connection was idle for way too many time and it is actually not working anymore.
It was quite a mess and inconsistent. It will not be perfect with this change, but still better. In case of boolean options, default values are omitted.
- Moved argument parsing "decisions" into main() - getopt() handling changes, test OPR_PARSING_ERROR with "-j" and "-i" argument strings. - Minimal libcurl version raised to 7.66.0, so no need to check CURL_VERSION_HTTP3 availability. - Raised hardcoded version.
Fix `curl_result_code` log
* Added DNS server with TCP support * Added TCP client limit option, also can be used to disable TCP DNS server in case of issues * Readme and help pages updated * Combined API with UDP DNS server * Use minimally necessary public header * Added TCP only statistic counters * Added TCP functional tests * Basic querries with valgrind - fragmented TCP request processing - testing 4k large response support * Started to use stricter compiler warnings * UDP truncation feature * Enable more compiler warnings * Replace c-ares deprecated functions * Reset HTTPS client on timeouts * systemd service improvements * Version bump
* Warning fixes Compatibility with CMake < 3.10 will be removed from a future version of CMake. warning: call to '_curl_easy_setopt_err_long' declared with attribute warning: curl_easy_setopt expects a long argument * Avoid division by zero The operation of dividing by a fraction is equivalent to multiplying by its reciprocal. * clang tidy changes * Replace atoi with strtoul Safer, minus one clang tidy rule.
Signed-off-by: 林博仁(Buo-ren Lin) <buo.ren.lin@gmail.com>
…196) Motivation: ----------- Enable routing DNS-over-HTTPS traffic through different network paths per instance. Primary use case: running multiple `https_dns_proxy` instances on a router where each WiFi LAN gateway routes through a different WireGuard tunnel to different geographic locations. This allows DNS traffic from different WiFi networks to exit via different VPN endpoints. Implementation: --------------- - Added `source_addr` field to `struct Options` - New `-S` command-line flag to specify source IPv4/v6 address - Uses `CURLOPT_INTERFACE` to bind outbound HTTPS connections - Backward compatible: without -S, uses system default routing - Logs `Using source address: X` at `debug` level when configured Example Usage: -------------- ### Instance 1: WiFi LAN 1 gateway (routes via WireGuard to US) ```shell https_dns_proxy -a 192.168.1.1 -p 53 -S 192.168.1.1 \ -r https://security.cloudflare-dns.com/dns-query \ -b 1.1.1.2,1.0.0.2 ``` ### Instance 2: WiFi LAN 2 gateway (routes via WireGuard to EU) ```shell https_dns_proxy -a 192.168.2.1 -p 53 -S 192.168.2.1 \ -r https://security.cloudflare-dns.com/dns-query \ -b 1.1.1.2,1.0.0.2 ``` Each instance binds to its WiFi interface address for both listening and outbound HTTPS, ensuring traffic routes through the correct WireGuard tunnel configured for that interface. Verification: ------------- With `-S` flag, CURL binds to specified source address: ``` [D] https_client.c:260 F0C1: Requesting HTTP/2 [D] https_client.c:324 F0C1: Using source address: 192.168.1.1 [D] https_client.c:218 F0C1: * Added security.cloudflare-dns.com:443:1.0.0.2,1.1.1.2,... to DNS cache [D] https_client.c:218 F0C1: * Hostname security.cloudflare-dns.com was found in DNS cache [D] https_client.c:94 curl opened socket: 9 [D] https_client.c:218 F0C1: * Trying 1.0.0.2:443... [D] https_client.c:218 F0C1: * Name '192.168.1.1' family 2 resolved to '192.168.1.1' family 2 [D] https_client.c:218 F0C1: * Local port: 0 [D] https_client.c:639 Reserved new io event: 0xffffc0ed3568 [D] https_client.c:218 F0C1: * Connected to security.cloudflare-dns.com (1.0.0.2) port 443 (#0) ``` Without `-S` flag, no source binding (backward compatible): ``` [D] https_client.c:260 39BF: Requesting HTTP/2 [D] https_client.c:218 39BF: * Added security.cloudflare-dns.com:443:1.1.1.2,1.0.0.2,... to DNS cache [D] https_client.c:218 39BF: * Hostname security.cloudflare-dns.com was found in DNS cache [D] https_client.c:94 curl opened socket: 9 [D] https_client.c:218 39BF: * Trying 1.1.1.2:443... [D] https_client.c:639 Reserved new io event: 0xffffe69a0f18 [D] https_client.c:218 39BF: * Connected to security.cloudflare-dns.com (1.1.1.2) port 443 (#0) ``` Note the presence of `Using source address` and `Name '192.168.1.1' ... resolved` lines only when `-S` is specified. Files Modified: --------------- - `src/options.h`: Added source_addr field - `src/options.c`: Added -S flag parsing and help text - `src/https_client.c`: Implemented CURLOPT_INTERFACE binding - `tests/robot/functional_tests.robot`: Added test case - `README.md`: Updated documentation
- main.c: fix NULL pointer dereference in https_resp_cb() — req was used before NULL check, and FLOG also dereferenced NULL req->tx_id - main.c: fix potential crash in get_host_from_uri() — strlen(host) was called before checking curl_url_get() return code, host could be NULL - dns_server_tcp.c: fix data corruption on EAGAIN — sent += len was executed with len == -1 when EAGAIN/EWOULDBLOCK occurred, causing negative offset; added continue to skip the addition - dns_server_tcp.c: fix format string mismatches in FLOG() calls — format had 2 specifiers but 4 arguments were passed (ipstr, port, strerror, errno); changed format to include all 4 arguments - dns_server_tcp.c: fix typo 'listaning' -> 'listening' - https_client.c: remove deprecated CURLPIPE_HTTP1 (since libcurl 7.62.0), keep only CURLPIPE_MULTIPLEX Co-authored-by: afanasyev <aafanasev@rtk-soft.ru>
Change max response size from UINT16_MAX to UINT16_MAX - 2 to account for the 2-byte length prefix in TCP DNS framing.
Add comment explaining why no race condition exists: single-threaded event loop ensures no callbacks can execute during this function.
Add bounds check to prevent overflow when calculating needed_space. Reject TCP requests exceeding UINT16_MAX bytes.
Store client->next pointer before freeing to prevent potential UAF.
Reduce max retries from 255 to 50 iterations. Fix bug where sent counter was incremented on EAGAIN error.
Always free new_resp when non-NULL, regardless of size comparison.
Move NULL check before accessing req->tx_id. Include buflen in error message for debugging.
Add 8KB size limit to prevent unbounded memory growth. Add NULL check after malloc to prevent crash on allocation failure.
Move NULL check before accessing req->tx_id. Include buflen in error message for debugging. Add missing return statement to prevent dereferencing NULL after logging.
Defer flight recorder dump from SIGUSR2 handler to main loop via ev_async to ensure fprintf() is called outside signal context.
- ring_buffer.c: Add NULL check in ring_buffer_dump() to prevent segfault - logging.c: Add fdopen() failure check and NULL logfile after fclose - main.c: Remove unnecessary curl_global_init() in version display - main.c: Add bounds check before strncpy to prevent stack overflow
- dns_server.c: Close socket on bind failure to prevent FD leak - dns_server_tcp.c: Limit buffer growth to prevent memory exhaustion - https_client.c: Remove global state modification in HTTP version fallback - dns_poller.c: Add bounds check before writing comma separator
- https_client.c: Set CURLOPT_NOSIGNAL to 1 to disable curl's internal signal handling and prevent interference with libev's signal watchers.
- main.c: Add hostname_len > 0 check to prevent strncpy underflow - dns_server.c: Set new_resp = NULL after ares_free_string to prevent UAF - ring_buffer.c: Set *rbp = NULL on allocation failure paths - dns_poller.c: Fix type mismatch between size_t and ares_socklen_t - logging.c: Add fdopen failure check in _log()
Motivation: ----------- PR #196 added the `-S` flag to bind HTTPS connections to a source address, enabling policy-based routing. However, bootstrap DNS queries (used to resolve DoH server hostnames like "dns.google") were not bound to the source address. This caused two issues: 1. **Privacy leak**: Bootstrap DNS queries go via default route (local ISP), exposing which DoH server you're using 2. **Routing mismatch**: HTTPS connection routes via VPN but may fail if resolved IP is unreachable from VPN Implementation: --------------- - Bind bootstrap DNS queries using `ares_set_local_ip4()` and `ares_set_local_ip6()` from c-ares - Validate address family matches proxy mode (`-4`/`-6`), warn on mismatch - Warn on invalid address literals - Robot Framework tests for source binding and validation warnings - Docker-based test infrastructure for CI/CD and macOS development Example Usage: -------------- ```bash https_dns_proxy -S 192.168.12.1 -b 1.1.1.1,8.8.8.8 -r https://dns.google/dns-query ``` With PBR rules routing traffic from source 192.168.12.1 via VPN: ```text # Route DoH HTTPS (port 443) via VPN config policy option name 'DoH WA via wg_wa' option interface 'wg_wa' option chain 'output' option proto 'tcp' option src_addr '192.168.12.1' option dest_port '443' # Route bootstrap DNS (port 53) via VPN config policy option name 'Bootstrap DNS WA via wg_wa' option interface 'wg_wa' option chain 'output' option proto 'udp' option src_addr '192.168.12.1' option dest_port '53' option dest_addr '1.1.1.1 8.8.8.8' ``` Both rules now match because `-S` binds both HTTPS and bootstrap DNS to the same source address. Verification: ------------- Bootstrap DNS bound to source address: ``` [I] dns_poller.c:163 Using source address: 192.168.12.1 [I] dns_poller.c:208 Received new DNS server IP: 142.250.80.110 for dns.google ``` Warning on address family mismatch: ``` [W] dns_poller.c:133 Bootstrap source address '::1' is IPv6, but IPv4-only mode is set ``` Warning on invalid address: ``` [W] dns_poller.c:141 Bootstrap source address 'not-an-ip' is not a valid IP literal ``` Files Modified: --------------- - `src/dns_poller.c`: Added `set_bootstrap_source_addr()` function - `src/dns_poller.h`: Added source_addr parameter to poller init - `src/main.c`: Pass source_addr to dns_poller - `src/options.c`: Fix format string type - `tests/robot/functional_tests.robot`: Source binding and validation tests - `tests/docker/Dockerfile`: Test image with valgrind and ctest integration - `tests/docker/run_all_tests.sh`: Simplified test runner using Dockerfile CMD - `CMakeLists.txt`: Fix robot test WORKING_DIRECTORY, add distclean target - `README.md`: Update Docker test documentation - `.gitignore`: Add build/ directory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot]
Can you help keep this open source service alive? 💖 Please sponsor : )