[PR #2588] [CLOSED] feat: add MasterDnsVPN protocol (native C++ engine) #2853

Closed
opened 2026-10-14 21:05:45 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/amnezia-vpn/amnezia-client/pull/2588
Author: @coffeegrind123
Created: 5/11/2026
Status: ❌ Closed

Base: dev ← Head: feature/masterdnsvpn-native


📝 Commits (10+)

  • 9008f4c masterdnsvpn: add enum entries + protocol constants
  • 813d891 masterdnsvpn: add protocol-config model + variant wiring
  • 6501c60 masterdnsvpn: QML page + ConfigModel + controller wiring
  • dd962da masterdnsvpn: unit tests for protocol-config model
  • 2dd5491 masterdnsvpn: switch model to pure structured JSON
  • 0cc95e2 masterdnsvpn: engine sibling dir + spec doc + crypto + SOCKS5
  • b92e416 masterdnsvpn: wire framing + DNS framing layers
  • 5b5816f masterdnsvpn: per-stream ARQ reliability layer
  • f51fb87 masterdnsvpn: resolver pool + 8 balancing strategies
  • 5d9011d masterdnsvpn: session/dispatcher — full stack assembly

📊 Changes

74 files changed (+14585 additions, -3 deletions)

View changed files

📝 client/CMakeLists.txt (+10 -0)
📝 client/android/AndroidManifest.xml (+13 -0)
📝 client/android/build.gradle.kts (+1 -0)
➕ client/android/master_dns_vpn/build.gradle.kts (+22 -0)
➕ client/android/master_dns_vpn/src/main/kotlin/MasterDnsVpn.kt (+209 -0)
➕ client/android/master_dns_vpn/src/main/kotlin/MasterDnsVpnConfig.kt (+58 -0)
➕ client/android/master_dns_vpn/src/main/kotlin/MasterDnsVpnNative.kt (+55 -0)
📝 client/android/settings.gradle.kts (+1 -0)
➕ client/android/src/org/amnezia/vpn/MasterDnsVpnService.kt (+13 -0)
📝 client/android/src/org/amnezia/vpn/VpnProto.kt (+9 -0)
📝 client/cmake/android.cmake (+5 -0)
📝 client/cmake/sources.cmake (+37 -0)
📝 client/core/controllers/coreController.cpp (+5 -1)
📝 client/core/controllers/coreController.h (+2 -0)
📝 client/core/models/containerConfig.cpp (+10 -0)
📝 client/core/models/containerConfig.h (+4 -1)
📝 client/core/models/protocolConfig.cpp (+31 -0)
📝 client/core/models/protocolConfig.h (+2 -0)
➕ client/core/models/protocols/masterDnsVpnProtocolConfig.cpp (+198 -0)
➕ client/core/models/protocols/masterDnsVpnProtocolConfig.h (+131 -0)

...and 54 more files

📄 Description

Adds MasterDnsVPN as a third SOCKS5-proxy-mode transport, alongside the existing Xray and SSXray entries. The four L3 transports (OpenVPN, WireGuard, AmneziaWG, IKEv2) are unaffected. MasterDnsVPN tunnels traffic inside DNS queries against operator-controlled tunnel domains. It's useful where standard VPN ports are blocked but DNS still resolves.

The reference implementation is masterking32/MasterDnsVPN, written in Go. Instead of bundling the Go binary, this PR ports the client engine to native C++/Qt6. The port integrates with the existing service/IPC architecture, ships statically with the rest of the client, and works on every platform amnezia-client already supports, without adding a per-platform Go toolchain to the build matrix.

What landed

74 files, ~14.5k LOC, across three layers.

UI and config model

QML page PageProtocolMasterDnsVpnSettings.qml for tunnel domains, resolvers, encryption method, SOCKS5 listen port and credentials. Backed by MasterDnsVpnConfigModel (structured JSON, no QSettings legacy strings). Plugs into the existing protocol dropdown via protocolsModel. installUiController is extended for SSH-installed servers.

Native engine

Under client/masterdnsvpn/, 13 modules totaling ~8.3k LOC:

  • wireframing: inner-packet binary codec, packed-control-blocks, SESSION_ACCEPT payload with the 13-byte client-policy tail.
  • crypto: None, XOR, ChaCha20, AES-128/192/256-GCM cipher pair.
  • dnsframing: base32/base36 codecs, RFC 1035 query builder, chunked TXT-RR response parser, exported DNS qtype/rcode/class constants.
  • compression: ZSTD, LZ4, ZLIB-raw with a 10 MiB decompression-bomb cap.
  • arq: per-stream reliability. Sliding-window, bounded NACK with frontier sampling, Karn-eligible adaptive RTO on both data and control planes, control-plane RTO retransmits, initial-NACK-delay, receive-window cap at 2x send window.
  • resolverpool: one QUdpSocket per resolver, 8 balancing strategies (RoundRobin through LossThenLatency to LeastLossTopRandom/RoundRobin), reportSend/reportSuccess/reportTimeout feedback API, per-resolver MTU and rolling loss/RTT EWMA with half-life decay.
  • mtuprober: binary-search upload and download MTU discovery.
  • pingpacer: tiered ping-pacing FSM (aggressive/lazy/cooldown/cold).
  • socks5server: local SOCKS5 listener for CONNECT (TCP) and UDP_ASSOCIATE (DNS-only, port 53). Standalone parseTargetPayload and UDP datagram codecs.
  • dnsmsg: lite RFC 1035 parser. Extracts transaction-ID and the first question for cache keying. Tolerates pointer compression with a loop guard.
  • dnscache: local DNS cache (Miss/Pending/Ready tri-state, configurable TTL) plus a DNS_QUERY_RES fragment reassembly store.
  • session: dispatcher, SESSION_INIT/ACCEPT handshake, applyServerPolicy clamping, per-stream ArqStream lifecycle, DNS_QUERY_REQ/RES dispatch and cache integration.
  • engine: façade for the desktop service host and Android JNI bridge.

Service and IPC integration

Service-side master_dns_vpn_service.{cpp,h} host. IPC interface extended via ipc_interface.rep with masterDnsVpnStart, masterDnsVpnStop, and masterDnsVpnSocksPort slots. conanfile.py adds zstd/1.5.6 and lz4/1.10.0 for the compression codecs (zlib is already a project dep). No new platform-specific toolchain deps.

Spec and tests

The wire spec is docs/masterdnsvpn-wire-spec.md (~800 lines). It walks the on-the-wire behavior section by section against upstream's Go source, with §13 citing the relevant internal/arq, internal/vpnproto, and internal/client files. §11 covers the local DNS service; §11.1a documents the deliberate divergence from upstream described below.

Tests are in client/tests/testMasterDnsVpnEngine.cpp (~3.3k LOC). Most entries are direct translations of upstream's Go test suite (internal/arq/arq_test.go, internal/vpnproto/*_test.go, internal/basecodec/*_test.go, internal/security/codec_test.go, internal/client/balancer_test.go, internal/fragmentstore/store_test.go, etc.) into QTest. Two real engine semantic bugs in the C++ port (HybridScore and LeastLossTop* round-robin fallback) turned up during the parity sweep and were fixed in flight.

One deliberate divergence: SOCKS5 cache-miss

Wire protocol matches upstream Go byte for byte. Server interop is preserved. One client-internal behavior diverges, documented in §11.1a of the spec:

Upstream's pattern on a SOCKS5-UDP DNS cache miss is: dispatch the query to the tunnel, then close the SOCKS5 UDP association so the local app retries, and let the retry hit the now-populated cache. This port instead dispatches, tracks the in-flight query by wire seq, and sends the tunnel response back when it arrives. No association teardown.

The reason: closing the listening socket on cache miss is unique to MasterDnsVPN. Normal DNS resolvers (dnsmasq, systemd-resolved, unbound) don't do that. The socket-churn pattern is observable on the local machine and is enough to fingerprint the resolver. The C++ port behaves like a standard cache-then-forward resolver and is indistinguishable from dnsmasq at the loopback layer. On-wire OPSEC is unchanged either way; local-loopback observability improves.

Out of scope

Client-only. Operators run the existing Go server on the VPS. No DNS-response builders, no server-side fragment store, no UDP server-side bind paths.

Pool reconfiguration after start() isn't supported, matching the other protocol implementations in this client. Resetting means tearing down and starting a fresh session.

Compatibility

No changes to existing protocols, models, or wire formats. MasterDnsVPN shows up as a new entry in the proxy-transport dropdown. Existing configurations are unaffected. The only new runtime dependencies are zstd and lz4 via Conan.

If splitting this into smaller PRs (UI + model, engine, service, spec, tests) would be easier to review, that's fine.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/amnezia-vpn/amnezia-client/pull/2588 **Author:** [@coffeegrind123](https://github.com/coffeegrind123) **Created:** 5/11/2026 **Status:** ❌ Closed **Base:** `dev` ← **Head:** `feature/masterdnsvpn-native` --- ### 📝 Commits (10+) - [`9008f4c`](https://github.com/amnezia-vpn/amnezia-client/commit/9008f4cd713a4c4cc0ecb0165af8f493e14767cd) masterdnsvpn: add enum entries + protocol constants - [`813d891`](https://github.com/amnezia-vpn/amnezia-client/commit/813d891bc2f801be37d12bea2f1bc412cf17c9b8) masterdnsvpn: add protocol-config model + variant wiring - [`6501c60`](https://github.com/amnezia-vpn/amnezia-client/commit/6501c60f62145abc0b81280718d69f466feac217) masterdnsvpn: QML page + ConfigModel + controller wiring - [`dd962da`](https://github.com/amnezia-vpn/amnezia-client/commit/dd962da27e91874632d7a7e97e472541f337fd77) masterdnsvpn: unit tests for protocol-config model - [`2dd5491`](https://github.com/amnezia-vpn/amnezia-client/commit/2dd5491d28681381b7b2121b5b3f2c490673113b) masterdnsvpn: switch model to pure structured JSON - [`0cc95e2`](https://github.com/amnezia-vpn/amnezia-client/commit/0cc95e2966c9f9fc7d3a90f3c8ef0f77ad693a74) masterdnsvpn: engine sibling dir + spec doc + crypto + SOCKS5 - [`b92e416`](https://github.com/amnezia-vpn/amnezia-client/commit/b92e416287fd7d19d1716872654ba1c9879b62fa) masterdnsvpn: wire framing + DNS framing layers - [`5b5816f`](https://github.com/amnezia-vpn/amnezia-client/commit/5b5816fe6164c57cff9ea6d93a71895b7997315a) masterdnsvpn: per-stream ARQ reliability layer - [`f51fb87`](https://github.com/amnezia-vpn/amnezia-client/commit/f51fb87614e82da8cfc80cb09dac7ea2f148c1d2) masterdnsvpn: resolver pool + 8 balancing strategies - [`5d9011d`](https://github.com/amnezia-vpn/amnezia-client/commit/5d9011d41a03dd0993a913234076b7622857ed2e) masterdnsvpn: session/dispatcher — full stack assembly ### 📊 Changes **74 files changed** (+14585 additions, -3 deletions) <details> <summary>View changed files</summary> 📝 `client/CMakeLists.txt` (+10 -0) 📝 `client/android/AndroidManifest.xml` (+13 -0) 📝 `client/android/build.gradle.kts` (+1 -0) ➕ `client/android/master_dns_vpn/build.gradle.kts` (+22 -0) ➕ `client/android/master_dns_vpn/src/main/kotlin/MasterDnsVpn.kt` (+209 -0) ➕ `client/android/master_dns_vpn/src/main/kotlin/MasterDnsVpnConfig.kt` (+58 -0) ➕ `client/android/master_dns_vpn/src/main/kotlin/MasterDnsVpnNative.kt` (+55 -0) 📝 `client/android/settings.gradle.kts` (+1 -0) ➕ `client/android/src/org/amnezia/vpn/MasterDnsVpnService.kt` (+13 -0) 📝 `client/android/src/org/amnezia/vpn/VpnProto.kt` (+9 -0) 📝 `client/cmake/android.cmake` (+5 -0) 📝 `client/cmake/sources.cmake` (+37 -0) 📝 `client/core/controllers/coreController.cpp` (+5 -1) 📝 `client/core/controllers/coreController.h` (+2 -0) 📝 `client/core/models/containerConfig.cpp` (+10 -0) 📝 `client/core/models/containerConfig.h` (+4 -1) 📝 `client/core/models/protocolConfig.cpp` (+31 -0) 📝 `client/core/models/protocolConfig.h` (+2 -0) ➕ `client/core/models/protocols/masterDnsVpnProtocolConfig.cpp` (+198 -0) ➕ `client/core/models/protocols/masterDnsVpnProtocolConfig.h` (+131 -0) _...and 54 more files_ </details> ### 📄 Description Adds MasterDnsVPN as a third SOCKS5-proxy-mode transport, alongside the existing Xray and SSXray entries. The four L3 transports (OpenVPN, WireGuard, AmneziaWG, IKEv2) are unaffected. MasterDnsVPN tunnels traffic inside DNS queries against operator-controlled tunnel domains. It's useful where standard VPN ports are blocked but DNS still resolves. The reference implementation is masterking32/MasterDnsVPN, written in Go. Instead of bundling the Go binary, this PR ports the client engine to native C++/Qt6. The port integrates with the existing service/IPC architecture, ships statically with the rest of the client, and works on every platform amnezia-client already supports, without adding a per-platform Go toolchain to the build matrix. ## What landed 74 files, ~14.5k LOC, across three layers. ### UI and config model QML page `PageProtocolMasterDnsVpnSettings.qml` for tunnel domains, resolvers, encryption method, SOCKS5 listen port and credentials. Backed by `MasterDnsVpnConfigModel` (structured JSON, no QSettings legacy strings). Plugs into the existing protocol dropdown via `protocolsModel`. `installUiController` is extended for SSH-installed servers. ### Native engine Under `client/masterdnsvpn/`, 13 modules totaling ~8.3k LOC: - `wireframing`: inner-packet binary codec, packed-control-blocks, SESSION_ACCEPT payload with the 13-byte client-policy tail. - `crypto`: None, XOR, ChaCha20, AES-128/192/256-GCM cipher pair. - `dnsframing`: base32/base36 codecs, RFC 1035 query builder, chunked TXT-RR response parser, exported DNS qtype/rcode/class constants. - `compression`: ZSTD, LZ4, ZLIB-raw with a 10 MiB decompression-bomb cap. - `arq`: per-stream reliability. Sliding-window, bounded NACK with frontier sampling, Karn-eligible adaptive RTO on both data and control planes, control-plane RTO retransmits, initial-NACK-delay, receive-window cap at 2x send window. - `resolverpool`: one QUdpSocket per resolver, 8 balancing strategies (RoundRobin through LossThenLatency to LeastLossTopRandom/RoundRobin), `reportSend`/`reportSuccess`/`reportTimeout` feedback API, per-resolver MTU and rolling loss/RTT EWMA with half-life decay. - `mtuprober`: binary-search upload and download MTU discovery. - `pingpacer`: tiered ping-pacing FSM (aggressive/lazy/cooldown/cold). - `socks5server`: local SOCKS5 listener for CONNECT (TCP) and UDP_ASSOCIATE (DNS-only, port 53). Standalone `parseTargetPayload` and UDP datagram codecs. - `dnsmsg`: lite RFC 1035 parser. Extracts transaction-ID and the first question for cache keying. Tolerates pointer compression with a loop guard. - `dnscache`: local DNS cache (Miss/Pending/Ready tri-state, configurable TTL) plus a DNS_QUERY_RES fragment reassembly store. - `session`: dispatcher, SESSION_INIT/ACCEPT handshake, `applyServerPolicy` clamping, per-stream ArqStream lifecycle, DNS_QUERY_REQ/RES dispatch and cache integration. - `engine`: façade for the desktop service host and Android JNI bridge. ### Service and IPC integration Service-side `master_dns_vpn_service.{cpp,h}` host. IPC interface extended via `ipc_interface.rep` with `masterDnsVpnStart`, `masterDnsVpnStop`, and `masterDnsVpnSocksPort` slots. `conanfile.py` adds `zstd/1.5.6` and `lz4/1.10.0` for the compression codecs (zlib is already a project dep). No new platform-specific toolchain deps. ## Spec and tests The wire spec is `docs/masterdnsvpn-wire-spec.md` (~800 lines). It walks the on-the-wire behavior section by section against upstream's Go source, with §13 citing the relevant `internal/arq`, `internal/vpnproto`, and `internal/client` files. §11 covers the local DNS service; §11.1a documents the deliberate divergence from upstream described below. Tests are in `client/tests/testMasterDnsVpnEngine.cpp` (~3.3k LOC). Most entries are direct translations of upstream's Go test suite (`internal/arq/arq_test.go`, `internal/vpnproto/*_test.go`, `internal/basecodec/*_test.go`, `internal/security/codec_test.go`, `internal/client/balancer_test.go`, `internal/fragmentstore/store_test.go`, etc.) into QTest. Two real engine semantic bugs in the C++ port (HybridScore and LeastLossTop\* round-robin fallback) turned up during the parity sweep and were fixed in flight. ## One deliberate divergence: SOCKS5 cache-miss Wire protocol matches upstream Go byte for byte. Server interop is preserved. One client-internal behavior diverges, documented in §11.1a of the spec: Upstream's pattern on a SOCKS5-UDP DNS cache miss is: dispatch the query to the tunnel, then close the SOCKS5 UDP association so the local app retries, and let the retry hit the now-populated cache. This port instead dispatches, tracks the in-flight query by wire seq, and sends the tunnel response back when it arrives. No association teardown. The reason: closing the listening socket on cache miss is unique to MasterDnsVPN. Normal DNS resolvers (dnsmasq, systemd-resolved, unbound) don't do that. The socket-churn pattern is observable on the local machine and is enough to fingerprint the resolver. The C++ port behaves like a standard cache-then-forward resolver and is indistinguishable from dnsmasq at the loopback layer. On-wire OPSEC is unchanged either way; local-loopback observability improves. ## Out of scope Client-only. Operators run the existing Go server on the VPS. No DNS-response builders, no server-side fragment store, no UDP server-side bind paths. Pool reconfiguration after `start()` isn't supported, matching the other protocol implementations in this client. Resetting means tearing down and starting a fresh session. ## Compatibility No changes to existing protocols, models, or wire formats. MasterDnsVPN shows up as a new entry in the proxy-transport dropdown. Existing configurations are unaffected. The only new runtime dependencies are zstd and lz4 via Conan. If splitting this into smaller PRs (UI + model, engine, service, spec, tests) would be easier to review, that's fine. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-14 21:05:45 +00:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amnezia-client#2853
No description provided.