Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
daa0d39
Large improvement pack (#192)
baranyaib90 Sep 1, 2025
7b27ecd
Fixes 16 (#193)
baranyaib90 Oct 7, 2025
7de8af2
Fix code block formatting in README.md (#195)
brlin-tw Nov 23, 2025
b499aa9
Remove redundant include from ares.
aarond10 Dec 24, 2025
67ecae0
Add -S flag for configurable source address on outbound connections (…
karl82 Dec 30, 2025
caad1eb
fix: critical bugs in DNS proxy (#201)
toxeh Feb 10, 2026
abbb5c4
Limit TCP DNS response payload to prevent overflow
aarond10 Feb 8, 2026
c97476c
Document TOCTOU race safety in TCP client lookup
aarond10 Feb 8, 2026
08d7d2e
Prevent integer overflow in TCP buffer size calculation
aarond10 Feb 8, 2026
442815e
Prevent use-after-free in TCP client removal
aarond10 Feb 8, 2026
13f5169
Fix infinite loop in TCP response sending
aarond10 Feb 10, 2026
5fd40ad
Fix memory leak in DNS response truncation
aarond10 Feb 8, 2026
87f3271
Fix NULL pointer dereference in HTTPS response callback
aarond10 Feb 10, 2026
5b95a64
Add size limit and NULL check to flight recorder
aarond10 Feb 9, 2026
95fe77c
Fix NULL pointer dereference in HTTPS response callback
aarond10 Feb 8, 2026
0ae04eb
Use ev_async for signal-safe flight recorder dump
aarond10 Feb 10, 2026
e298263
Fix critical security and stability bugs
aarond10 Feb 10, 2026
3c7fd07
Fix HIGH severity stability and resource bugs
aarond10 Feb 10, 2026
75b3200
Fix curl signal handling conflict
aarond10 Feb 10, 2026
2f1e6ed
Fix several bugs:
aarond10 Feb 10, 2026
8018812
Bind bootstrap DNS lookups to -S source address (#1) (#202)
karl82 Mar 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
run: sudo apt-get update

- name: Setup Dependencies
run: sudo apt-get install cmake libc-ares-dev libcurl4-openssl-dev libev-dev build-essential clang-tidy dnsutils python3-pip python3-venv valgrind ${{ matrix.compiler }}
run: sudo apt-get install cmake libc-ares-dev libcurl4-openssl-dev libev-dev libsystemd-dev build-essential clang-tidy dnsutils python3-pip python3-venv valgrind ${{ matrix.compiler }}

- name: Setup Python Virtual Environment
run: python3 -m venv ${{github.workspace}}/venv
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
build/
CMakeCache.txt
CTestTestfile.cmake
CMakeFiles/
Expand All @@ -18,3 +19,4 @@ output.xml
report.html
custom_curl/
valgrind-*.log
tests/robot/__pycache__
42 changes: 35 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.7)
cmake_minimum_required(VERSION 3.10)
project(HttpsDnsProxy C)

include(CheckIncludeFile)

# FUNCTIONS

# source: https://stackoverflow.com/a/27990434
Expand All @@ -25,15 +27,19 @@ if (NOT CMAKE_INSTALL_BINDIR)
set(CMAKE_INSTALL_BINDIR bin)
endif()

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra --pedantic -Wno-strict-aliasing -Wno-variadic-macros")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -Wstrict-aliasing -Wformat=2 -Wunused -Wno-variadic-macros -Wnull-dereference -Wshadow -Wconversion -Wsign-conversion -Wfloat-conversion -Wimplicit-fallthrough")
set(CMAKE_C_FLAGS_DEBUG "-gdwarf-4 -DDEBUG")
set(CMAKE_C_FLAGS_RELEASE "-O2")

if ((CMAKE_C_COMPILER_ID MATCHES GNU AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 9) OR
(CMAKE_C_COMPILER_ID MATCHES Clang AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10))
if (((CMAKE_C_COMPILER_ID MATCHES GNU AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 9) AND
(CMAKE_C_COMPILER_ID MATCHES GNU AND CMAKE_C_COMPILER_VERSION VERSION_LESS 14)) OR
( CMAKE_C_COMPILER_ID MATCHES Clang AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 10))
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-gnu-zero-variadic-macro-arguments -Wno-gnu-folding-constant")
endif()

set(SERVICE_EXTRA_OPTIONS "")
set(SERVICE_TYPE "simple")

# VERSION
# It is possible to define external default value like: cmake -DSW_VERSION=1.2-custom

Expand Down Expand Up @@ -65,6 +71,12 @@ endif()

# LIBRARY DEPENDENCIES

# Add Homebrew paths for macOS
if(APPLE)
list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
link_directories("/opt/homebrew/lib")
endif()

find_path(LIBCARES_INCLUDE_DIR ares.h)
find_path(LIBEV_INCLUDE_DIR ev.h)

Expand All @@ -81,6 +93,15 @@ include_directories(
${LIBCARES_INCLUDE_DIR} ${LIBCURL_INCLUDE_DIR}
${LIBEV_INCLUDE_DIR} src)

check_include_file("systemd/sd-daemon.h" HAVE_SD_DAEMON_H)

if(HAVE_SD_DAEMON_H)
message(STATUS "Using libsystemd")
add_definitions(-DHAS_LIBSYSTEMD=1)
set(LIBS ${LIBS} systemd)
set(SERVICE_TYPE "notify")
endif()

# CLANG TIDY

option(USE_CLANG_TIDY "Use clang-tidy during compilation" ON)
Expand All @@ -95,7 +116,7 @@ if(USE_CLANG_TIDY)
message(STATUS "clang-tidy not found.")
else()
message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}")
set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" "-fix" "-fix-errors" "-checks=*,-cert-err34-c,-readability-identifier-length,-altera-unroll-loops,-bugprone-easily-swappable-parameters,-concurrency-mt-unsafe,-*magic-numbers,-hicpp-signed-bitwise,-readability-function-cognitive-complexity,-altera-id-dependent-backward-branch,-google-readability-todo,-misc-include-cleaner")
set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" "-fix" "-fix-errors" "-checks=*,-readability-identifier-length,-altera-unroll-loops,-bugprone-easily-swappable-parameters,-concurrency-mt-unsafe,-*magic-numbers,-hicpp-signed-bitwise,-readability-function-cognitive-complexity,-altera-id-dependent-backward-branch,-misc-include-cleaner,-llvmlibc-restrict-system-libc-headers,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling")
endif()
else()
message(STATUS "Not using clang-tidy.")
Expand Down Expand Up @@ -132,7 +153,6 @@ endif()

install(TARGETS ${TARGET_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})

set(SERVICE_EXTRA_OPTIONS "")
if(IS_DIRECTORY "/etc/munin/plugins" AND
IS_DIRECTORY "/etc/munin/plugin-conf.d")
set(SERVICE_EXTRA_OPTIONS "-s 300")
Expand Down Expand Up @@ -162,6 +182,14 @@ else()
message(STATUS "python3 found: ${PYTHON3_EXE}")

enable_testing()

# Robot framework tests
add_test(NAME robot COMMAND ${PYTHON3_EXE} -m robot.run functional_tests.robot
WORKING_DIRECTORY tests/robot)
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests/robot)
endif()

# Clean target (removes entire build directory)
add_custom_target(distclean
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}
COMMENT "Removing build directory"
)
40 changes: 29 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

`https_dns_proxy` is a light-weight DNS<-->HTTPS, non-caching translation
proxy for the [RFC 8484][rfc-8484] DNS-over-HTTPS standard. It receives
regular (UDP) DNS requests and issues them via DoH.
regular (UDP or TCP) DNS requests and issues them via DoH.

[Google's DNS-over-HTTPS][google-doh] service is default, but
[Cloudflare's service][cloudflare-doh] also works with trivial commandline flag
Expand Down Expand Up @@ -48,6 +48,7 @@ Depends on `c-ares (>=1.11.0)`, `libcurl (>=7.66.0)`, `libev (>=4.25)`.
On Debian-derived systems those are libc-ares-dev,
libcurl4-{openssl,nss,gnutls}-dev and libev-dev respectively.
On Redhat-derived systems those are c-ares-devel, libcurl-devel and libev-devel.
On systems with systemd it is recommended to have libsystemd development package installed.

On MacOS, you may run into issues with curl headers. Others have had success when first installing curl with brew.
```
Expand All @@ -57,7 +58,7 @@ brew link curl --force

On Ubuntu
```
apt-get install cmake libc-ares-dev libcurl4-openssl-dev libev-dev build-essential
apt-get install cmake libc-ares-dev libcurl4-openssl-dev libev-dev libsystemd-dev build-essential
```

If all pre-requisites are met, you should be able to build with:
Expand All @@ -71,14 +72,14 @@ $ make
* If system libcurl supports it by default nothing else has to be done

* If a custom build of libcurl supports HTTP/3 which is installed in a different location, that can be set when running cmake:
```
$ cmake -D CUSTOM_LIBCURL_INSTALL_PATH=/absolute/path/to/custom/libcurl/install .
```
```
$ cmake -D CUSTOM_LIBCURL_INSTALL_PATH=/absolute/path/to/custom/libcurl/install .
```

* Just to test HTTP/3 support for development purpose, simply run the following command and wait for a long time:
```
$ ./development_build_with_http3.sh
```
```
$ ./development_build_with_http3.sh
```

## INSTALL

Expand Down Expand Up @@ -158,15 +159,17 @@ docker run --name "https-dns-proxy" -p 5053:5053/udp \
Just run it as a daemon and point traffic at it. Commandline flags are:

```
Usage: ./https_dns_proxy [-a <listen_addr>] [-p <listen_port>]
Usage: ./https_dns_proxy [-a <listen_addr>] [-p <listen_port>] [-T <tcp_client_limit>]
[-b <dns_servers>] [-i <polling_interval>] [-4]
[-r <resolver_url>] [-t <proxy_server>] [-x] [-q] [-C <ca_path>] [-c <dscp_codepoint>]
[-r <resolver_url>] [-t <proxy_server>] [-S <source_addr>] [-x] [-q] [-C <ca_path>] [-c <dscp_codepoint>]
[-d] [-u <user>] [-g <group>]
[-v]+ [-l <logfile>] [-s <statistic_interval>] [-F <log_limit>] [-V] [-h]

DNS server
-a listen_addr Local IPv4/v6 address to bind to. (Default: 127.0.0.1)
-p listen_port Local port to bind to. (Default: 5053)
-T tcp_client_limit Number of TCP clients to serve.
(Default: 20, Disabled: 0, Min: 1, Max: 200)

DNS client
-b dns_servers Comma-separated IPv4/v6 addresses and ports (addr:port)
Expand All @@ -184,11 +187,16 @@ Usage: ./https_dns_proxy [-a <listen_addr>] [-p <listen_port>]
supports it (http, https, socks4a, socks5h), otherwise
initial DNS resolution will still be done via the
bootstrap DNS servers.
-S source_addr Source IPv4/v6 address for outbound HTTPS and bootstrap DNS.
(Default: system default)
-x Use HTTP/1.1 instead of HTTP/2. Useful with broken
or limited builds of libcurl.
-q Use HTTP/3 (QUIC) only.
-m max_idle_time Maximum idle time in seconds allowed for reusing a HTTPS connection.
(Default: 118, Min: 0, Max: 3600)
-L conn_loss_time Time in seconds to tolerate connection timeouts of reused connections.
This option mitigates half-open TCP connection issue (e.g. WAN IP change).
(Default: 15, Min: 5, Max: 60)
-C ca_path Optional file containing CA certificates.
-c dscp_codepoint Optional DSCP codepoint to set on upstream HTTPS server
connections. (Min: 0, Max: 63)
Expand Down Expand Up @@ -223,6 +231,17 @@ pip3 install robotframework
python3 -m robot.run tests/robot/functional_tests.robot
```

## Docker bootstrap DNS test

There is a repeatable Docker-based test suite for validating proxy behavior
including `-S` source address binding:

```
tests/docker/run_all_tests.sh
```

If your Docker CLI is not on `PATH`, you can set `DOCKER_BIN` to its full path.

## TODO

* Add some tests.
Expand All @@ -233,4 +252,3 @@ python3 -m robot.run tests/robot/functional_tests.robot
* Aaron Drew (aarond10@gmail.com): Original https_dns_proxy.
* Soumya ([github.com/soumya92](https://github.com/soumya92)): RFC 8484 implementation.
* baranyaib90 ([github.com/baranyaib90](https://github.com/baranyaib90)): fixes and improvements.

6 changes: 4 additions & 2 deletions https_dns_proxy.service.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ Before=nss-lookup.target
After=network.target

[Service]
Type=simple
Type=${SERVICE_TYPE}
DynamicUser=yes
Restart=on-failure
ExecStart=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/https_dns_proxy \
-v -v ${SERVICE_EXTRA_OPTIONS}
Restart=on-failure
RestartSec=5
TimeoutStartSec=20
TimeoutStopSec=10

[Install]
Expand Down
17 changes: 14 additions & 3 deletions munin/https_dns_proxy.plugin
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ graph_scale no
graph_args --base 1000 --lower-limit 0
requests.label Requests
responses.label Responses
tcprequests.label TcpRequests
tcpresponses.label TcpResponses

multigraph https_dns_proxy_latency
graph_title HTTPS DNS proxy - latency
Expand All @@ -19,6 +21,7 @@ graph_category network
graph_scale no
graph_args --base 1000 --lower-limit 0
latency.label Latency
tcplatency.label TcpLatency

multigraph https_dns_proxy_connections
graph_title HTTPS DNS proxy - connections
Expand All @@ -40,7 +43,7 @@ EOM
esac

log_lines=$(journalctl --unit https_dns_proxy.service --output cat --since '6 minutes ago')
pattern='stat\.c:[0-9]+ ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)$'
pattern='stat\.c:[0-9]+ ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)$'

# match log lines with pattern (last match will be used)
IFS='
Expand All @@ -53,18 +56,26 @@ for line in $log_lines; do
fi
done

latency='U'
if [ -n "${stat[3]}" ] && \
[ -n "${stat[2]}" ] && \
[ "${stat[2]}" -gt "0" ]; then
latency=$((${stat[3]} / ${stat[2]}))
fi

if [ -n "${stat[11]}" ] && \
[ -n "${stat[10]}" ] && \
[ "${stat[10]}" -gt "0" ]; then
tcplatency=$((${stat[11]} / ${stat[10]}))
fi

echo "multigraph https_dns_proxy_count"
echo "requests.value ${stat[1]:-U}"
echo "responses.value ${stat[2]:-U}"
echo "tcprequests.value ${stat[9]:-U}"
echo "tcpresponses.value ${stat[10]:-U}"
echo "multigraph https_dns_proxy_latency"
echo "latency.value ${latency}"
echo "latency.value ${latency:-0}"
echo "tcplatency.value ${tcplatency:-0}"
echo "multigraph https_dns_proxy_connections"
echo "opened.value ${stat[6]:-U}"
echo "closed.value ${stat[7]:-U}"
Expand Down
Loading