RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).
日本語の概要は準備中です。原文の説明を表示しています。
Reference for the ripdpi-dns-resolver crate: DoH/DoT/DNSCrypt/DoQ/ODoH, health scoring, bootstrap IPs, tamper diagnostics. Use when touching encrypted DNS resolution, failover, or tamper detection.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
| Protocol | RFC/Spec | Transport | Default port | TLS required |
|---|---|---|---|---|
| DoH | RFC 8484 | HTTPS POST with application/dns-message | 443 | Yes (HTTPS) |
| DoT | RFC 7858 | TCP + TLS, length-prefixed DNS wire format | 853 | Yes |
| DNSCrypt | dnscrypt.info spec | TCP, XSalsa20Poly1305 (ChaChaBox) | 443 | No (own crypto) |
| DoQ | RFC 9250 | QUIC bidirectional streams, length-prefixed | 853 | Yes (QUIC TLS) |
| ODoH | RFC 9230 | HTTPS POST with application/oblivious-dns-message, HPKE-encrypted payload | 443 | Yes (HTTPS + HPKE) |
All protocols use standard DNS wire format (RFC 1035) for the query and response payloads. The protocol layer only differs in how the wire bytes are transported and encrypted.
DoQ does not support SOCKS5 transport because SOCKS5 is TCP-only; the
resolver returns EncryptedDnsError::Request if you try.
The crate (native/rust/crates/ripdpi-dns-resolver) is organized by concern, one
top-level <name>.rs module file plus a <name>/ directory of submodules for
each area. List the current layout rather than trusting a snapshot:
find native/rust/crates/ripdpi-dns-resolver/src -name '*.rs' | sort
At a high level:
pool.rs + pool/{builder,exchange,fallback_order,health_updates,ranking,tests}.rs
-- ResolverPool/ResolverPoolBuilder, the LRU fallback cache, and health-based rotation.resolver.rs + resolver/{connection,dispatch,dnscrypt_transport,doh,doq,doq_tests,dot,state,tcp}.rs
-- EncryptedDnsResolver, protocol dispatch (dispatch.rs), and per-protocol exchange logic;
resolver/doh/ further splits the manual HTTP/1.1 DoH path into request/response/chunked-body helpers.health.rs + health/{oracle_policy,registry,score,snapshot,tests}.rs -- HealthRegistry, EWMA scoring.transport.rs + transport/{client,framing,normalize,util,wire}.rs -- shared TLS config, endpoint
normalization (normalize.rs), DNS wire helpers, and extract_ip_answers().dnscrypt.rs + dnscrypt/{certificate,cipher,framing,identity,padding}.rs -- DNSCrypt crypto.doh_pipeline.rs + doh_pipeline/{builder,cache,https_bindings,ip_candidates,lookup,types}.rs --
higher-level DoH batch-lookup pipeline (DohResolverPipeline) used by the diagnostics/oracle side.https_service_binding.rs + https_service_binding/{cursor,dto,ech,records}.rs -- HTTPS/SVCB record
parsing (used by ODoH config discovery and ECH).odoh.rs -- ODoH client-side config resolution and HPKE query/response handling (no submodule split yet).hickory_backend.rs -- optional feature-gated hickory-resolver backend for DoH/DoT.types/ -- EncryptedDnsProtocol, EncryptedDnsEndpoint, EncryptedDnsError, EncryptedDnsConnectHooks,
OdohEndpointConfig, and related public types, re-exported from lib.rs.tests/{mod,pipeline,pool,https_service_binding}.rs -- integration tests (see Testing patterns below).HealthRegistry tracks per-endpoint and per-bootstrap-IP health using
exponentially weighted moving averages (EWMA).
Score model (health/score.rs:HealthScore):
ewma_success_rate: decays toward 0.5 (neutral prior) with configurable
half-life (default 60s via DEFAULT_HEALTH_HALF_LIFE in pool.rs)ewma_latency_ms: decays toward 200ms initial priorsuccess_rate * 0.7 + (1 - latency/2000) * 0.3How scores influence pool selection (pool/ranking.rs:try_order()):
HealthRegistry::rank_indices() sorts endpoints by composite score (best first)FallbackCache (LRU) promotes the most recently successful endpoint to rank 0SNI blocking penalty: record_sni_blocked() records failure with a 4000ms
latency penalty, rapidly deprioritizing endpoints whose TLS handshake is being
RST-injected by middlebox DPI equipment.
Bootstrap IP ranking: rank_bootstrap_ips() uses the same EWMA model on
per-IP health data. Both build_doh_client() and connect_direct_tcp_with()
consult this ranking to try the healthiest bootstrap IP first.
Sharing across pool recreations: ResolverPoolBuilder::health_registry()
accepts a pre-existing registry so that dropping and rebuilding a pool preserves
all accumulated health data.
DNSCrypt uses its own crypto layer instead of TLS. The crypto implementation
lives in dnscrypt.rs/dnscrypt/, and the exchange logic in
resolver/dnscrypt_transport.rs:exchange_dnscrypt().
Certificate lifecycle (fetch_dnscrypt_certificate() / current_dnscrypt_certificate()):
2.dnscrypt-cert.<provider_name> TXT recordsDNSCRYPT_CERT_SIZE):
DNSC (DNSCRYPT_CERT_MAGIC)ring::signature)Mutex<Option<DnsCryptCachedCertificate>>, reuse until 60s before expiryQuery encryption (exchange_dnscrypt_with_session()):
crypto_box::SecretKey)ChaChaBox from resolver public key + client secretDNSCRYPT_QUERY_NONCE_HALF)dnscrypt_pad(): append 0x80 then 0x00s)client_magic || client_public_key || nonce_half || ciphertextResponse decryption (decrypt_dnscrypt_response()):
DNSCRYPT_RESPONSE_MAGIC)dnscrypt_unpad(): find last 0x80)Implementation lives in src/odoh.rs (no submodule split) and uses the
odoh-rs crate for the RFC 9230 HPKE encrypt/decrypt/message framing
(ObliviousDoHMessage, encrypt_query(), decrypt_response(), compose(),
parse()). Dispatch is EncryptedDnsResolver::exchange_odoh(), wired from
resolver/dispatch.rs; default port is 443 like DoH.
Config discovery (OdohConfigSource): Bundled and CustomBytes variants
carry pre-fetched config bytes with a retrieval time and TTL; HttpsSvcb
parses the target's HTTPS/SVCB record (key 32769, ODOHCONFIG_SVCB_KEY) via
parse_https_service_bindings() from https_service_binding.rs. HttpsSvcb
config lookup carries an OdohConfigLookupSecurity tag and must be
EncryptedDns; a PlainDns tag returns OdohError::PlaintextConfigLookup
before use, so ODoH config material is never trusted from a plaintext lookup.
When system DNS is poisoned, you cannot resolve the hostname of your encrypted
DNS server (e.g., dns.google) via the poisoned system resolver. The solution
is hardcoded bootstrap IPs in EncryptedDnsEndpoint::bootstrap_ips.
How it works:
normalize_endpoint() enforces MissingBootstrapIps error for Direct
transport if no bootstrap IPs are providedbuild_doh_client() calls reqwest::ClientBuilder::resolve_to_addrs()
to pin the hostname to bootstrap IPs, bypassing system DNS entirelyconnect_direct_tcp_with() iterates bootstrap IPs
directly, connecting to SocketAddr::new(ip, port) without any DNS lookupsocks5h:// scheme),
bypassing the bootstrap problem entirelyEncryptedDnsConnectHooks: allows the Android VPN layer to inject a custom
DirectTcpConnector (for protect-socket bypass) and DirectUdpBinder (for
DoQ). When a TCP connector hook is set, DoH falls back to manual HTTP/1.1
over the hooked TCP stream (exchange_doh_manually()) instead of reqwest.
The hickory-backend feature flag (Cargo.toml) enables an alternative code
path that routes DoH and DoT through hickory-resolver instead of the manual
reqwest/tokio-rustls implementations.
Fallback conditions (can_use_hickory() in resolver/dispatch.rs):
DNSCrypt always uses the manual path because hickory-resolver has no DNSCrypt support. DoQ is not routed through hickory either.
The diagnostics runner uses encrypted DNS as a ground-truth oracle to detect DNS tampering by the ISP/middlebox.
Detection flow (native/rust/crates/ripdpi-diagnostics-runner/src/strategy.rs:detect_strategy_probe_dns_tampering()):
resolve_addresses())resolve_via_encrypted_dns())DnsTamperingDnsTamperingis_dns_injection_suspected() heuristicdiagnosis.rs which emits dns_tampering diagnostic codesDiagnostics DNS helper (ripdpi-diagnostics-dns/src/dns.rs, consumed through ripdpi-diagnostics-runner):
resolve_via_encrypted_dns() creates a one-shot EncryptedDnsResolver with
a preconfigured endpoint and calls exchange_blocking()extract_ip_answers() (re-exported from ripdpi-dns-resolver) parses A/AAAA
records from the response bytesbuild_fallback_encrypted_dns_endpoints() in dns.rs provides 3-4 fallback
DoH resolvers when the primary fails. Used in both
detect_strategy_probe_dns_tampering() and run_dns_probe().Default encrypted DNS is AdGuard (changed from Cloudflare). Priority:
AdGuard > DNS.SB > Mullvad > Google IP > Cloudflare IP > Google > Quad9 > Cloudflare.
Defined solely in BuiltInDnsProviders (DnsResolverConfig.kt); the Rust crate
has no equivalent default-ordering constant, it just resolves whatever endpoint
list it is given. Do not silently reorder the built-in providers or change the
default provider unless the user explicitly asks for it -- ordering changes
propagate to CriticalResolverChain and EncryptedDnsPathSelection and are
compatibility-sensitive for existing remembered-policy state.
tlsServerName for DNS.SB is "dns.sb" (not "doh.dns.sb"). The DoT cert
at port 853 is only valid for dns.sb; doh.dns.sb causes handshake failures.
VpnEncryptedDnsFailoverController triggers immediately on catastrophic errors
(connection reset, refused, "operation not permitted") via isCatastrophicDnsError(),
skipping the 2-failure threshold for bootstrap-phase queries (<=3 queries attempted).
The native tunnel counts bootstrap connection failures in dnsFailuresTotal.
Previously only DNS query failures (after successful connection) were counted;
connection-level failures during bootstrap were silently dropped.
End-to-end walkthrough for adding a hypothetical "DoX" protocol (module paths
reflect the current per-concern split; re-run the find command above if the
layout has moved further since):
types/endpoint.rs: Add a Doq-style variant to EncryptedDnsProtocol,
implement as_str() and default_port(). Add any protocol-specific fields
to EncryptedDnsEndpoint. Add error variants to EncryptedDnsError
(types/error.rs) and classify them in kind().
transport/normalize.rs: Add a normalization branch in
normalize_endpoint() to validate required fields and fill defaults. If
the protocol needs TLS, reuse build_client_config() from transport.rs.
resolver/dispatch.rs + resolver.rs: Add an exchange_dox() method
on EncryptedDnsResolver and wire it into the protocol dispatch match. If
the protocol uses connection reuse, add a variant to PooledConnection and
implement take_dox_session() / connect_dox_session() following the
DoT/DNSCrypt pattern (see resolver/dot.rs, resolver/dnscrypt_transport.rs).
If it needs a persistent client (like quinn::Endpoint for DoQ), add it to
ResolverInner in resolver/state.rs and initialize in with_health().
lib.rs: No changes needed unless you add new public types -- the protocol enum variant propagates through existing re-exports.
pool.rs: No changes needed -- ResolverPool is protocol-agnostic. It
delegates to EncryptedDnsResolver::exchange_with_metadata().
hickory_backend.rs (optional): If hickory-resolver gains support for
the protocol, add exchange_dox() and wire can_use_hickory() fallback.
tests/: Add integration tests following the existing patterns in
tests/pool.rs / tests/pipeline.rs:
DnsCryptTestServer, TLS listener patterns)127.0.0.1 with the local portIntegration tests (src/tests/{mod,pipeline,pool,https_service_binding}.rs):
rcgen self-signed certs + rustls::ServerConfigturmoil crate for deterministic network simulation (used for TcpClientStream trait)local-network-fixture dev-dependency for network test infrastructureUnit tests (inline in each module, plus dedicated <module>/tests.rs files
for health/ and pool/):
health/tests.rs: fake clock (advancing_fake_clock()) for deterministic EWMA testingdnscrypt.rs: Ed25519 keypair generation for certificate round-trip testspool/tests.rs: manual fallback cache seeding to test cold-start behaviorMock resolvers: Tests create EncryptedDnsResolver::with_extra_tls_roots()
pointing to localhost with self-signed certificate DER bytes passed as extra
TLS roots. The EncryptedDnsConnectHooks mechanism allows injecting custom
TCP connectors for test isolation.
Key test helpers:
build_query(name) -> DNS wire-format query bytesbuild_response(query, answer_ip) -> DNS wire-format response bytesDnsCryptTestServer -> full DNSCrypt server with cert generation + cryptoまだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).
日本語の概要は準備中です。原文の説明を表示しています。
ADB-based RIPDPI device/emulator debugging: build/install/launch, logcat filtering, fixture port-forwarding, instrumented tests, crash/ANR triage. Use when debugging on a real device or emulator.
日本語の概要は準備中です。原文の説明を表示しています。
RIPDPI Appium launch-contract reference: start routes and permission/service/data presets. Use when a test launches to the wrong screen or a new automation route is added. General flakiness: appium-test-debug.
日本語の概要は準備中です。原文の説明を表示しています。
RIPDPI Appium test authoring: page objects, resource-id locators, assertions, wait tiers. Use when writing a new Appium test or page object, or adding coverage for a new screen.
日本語の概要は準備中です。原文の説明を表示しています。
RIPDPI Appium failure triage: flaky tests, locator/session/wait issues, screenshot and element-tree debugging. Use when an Appium test fails or is flaky.
日本語の概要は準備中です。原文の説明を表示しています。
Add or modify a GitHub Actions job. Use when editing a file under .github/workflows/. Not for running a workflow locally (local-ci-act) or release signing (release-signing).
日本語の概要は準備中です。原文の説明を表示しています。