14 KiB
Welcome to darkproxy...
Production hardening defaults
The stack now uses safer defaults for production:
- Service admin/web passwords are loaded from Docker secrets instead of inline values.
- Admin/monitoring ports are bound to
127.0.0.1on the host. - 3proxy SOCKS auth is currently disabled (
auth none) for trusted/internal use. - Pi-hole ARP cache parsing is disabled in Docker (
FTLCONF_database_network_parseARPcache=false) to prevent recurring netlinkneigh/ARP errors.
Before starting in production, set these secret files:
printf 'YOUR_STRONG_PIHOLE_PASSWORD\n' > secrets/pihole_webpassword.txt
printf 'YOUR_STRONG_GRAFANA_PASSWORD\n' > secrets/grafana_admin_password.txt
SOCKS auth note:
- Current default is
auth nonein3proxy/first-instanse.cfg. secrets/3proxy_users.txtis not used in this mode.- Only re-add
PROXY_USERScompose wiring if you intentionally switch back toauth strong.
Pi-hole note: disabling ARP parsing avoids noisy Failed to read ARP cache
messages in containerized setups where neighbor-table netlink operations are not
supported. This only affects Pi-hole's network-table enrichment, not DNS
blocking/forwarding behavior.
This container runs the following vontainers in the stack.
dark3proxy - SOCKS5/http proxy with hostname-preserving router
- DNS
- pihole - ad blocking and metrics
- PopuraDNS - non-recursive DNS forwards requests to the following: Alfis - Blockchain based yggdrasil resolver EmerCoin - Resolves EMC domains UnBound - Recursive DNS for Clearnet domains
- .onion and .i2p domain names are now resolved via the stack (see DNS section below)
- I2PD over Yggdrasil container (
container_name: darki2p)- Yggdrasil IP network stack
- Socks5 server for I2P
- Tor container
SOCKS ports on host
Proxy access is exposed on a single host port:
<host-ip>:2000->dark3proxySOCKS (dark3proxy:1080), externally reachable.- Intended as the single client entrypoint for clearnet, onion, and Ygg/I2P routing rules.
Router internals for :2000:
- Front-end process:
3proxy/socks_router.py(SOCKS5), keeps destination hostnames intact. - Upstream dispatch:
*.onion-> Tor SOCKS (10.5.0.7:9050)*.i2p-> i2pd SOCKS (10.5.0.2:4447)*.ygg,*.meshname,*.meship, and200::/7-> local Ygg-guarded SOCKS (127.0.0.1:1085)- all other targets -> Tor SOCKS (
10.5.0.7:9050)
- Existing 3proxy primary instance still runs for admin/legacy rules, with SOCKS moved to internal port
1088to avoid collision.
Additional non-proxy endpoint:
127.0.0.1:2006/udp-> Tor DNSPort (darktor:9053).
Quick checks:
# Onion over the only published proxy port
curl --socks5-hostname <host-ip>:2000 -I http://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion
# Clearnet over the only published proxy port
curl --socks5-hostname <host-ip>:2000 -I https://example.com
The system offers a DNS that resolves many darknet and clearnet IP's. The stack now handles:
Local resolution model (important):
-
DNS queries are resolved inside this Docker stack on your host (
pihole -> CoreDNS -> local resolver services). -
The stack does not forward DNS queries to public DNS providers for supported darknet/blockchain TLDs.
-
Some namespaces still require blockchain RPC data sources for authoritative records (see limitations below).
-
*.onion– forwarded to Tor's DNSPort running in thedarktorcontainer at 10.5.0.7:9053. -
*.i2p– resolved by locali2pdnsbridge service (10.5.0.16:53).i2pdnssynthesizes deterministic private A records and writes IP<->hostname mappings.dark3proxyconsumes that map so synthetic IP requests are translated back to.i2phostnames and routed to i2pd SOCKS (10.5.0.2:4447).
You can query these directly from any container on the darkproxy network, for
example:
# Tor .onion domains
docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 facebookcorewwwi.onion
# I2P .i2p domains
docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 stats.i2p
# Ethereum Name Service (.eth) via local ensdns service
docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 vitalik.eth
# ENS address/content hash records (TXT)
docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 TXT vitalik.eth
# Other supported TLDs: .bit, .alt, .loki, .zil, .web3, .exit, .onion4, .onion6
docker run --rm --network darkproxy infoblox/dig dig @10.5.0.4 example.bit
Note: the CoreDNS service at
10.5.0.4no longer enforces an ACL for the root zone – it will happily perform recursive lookups for any client. Queries from your host should now work without the “refused to do a recursive query” error. If you later reintroduce an ACL, update this note accordingly.
Host-side quick tests (no extra images)
Run DNS queries from your host by executing dig in the existing darkpihole
container:
docker exec darkpihole dig @10.5.0.4 TXT vitalik.eth +short
docker exec darkpihole dig @10.5.0.4 A vitalik.eth +short
Expected behavior:
TXTincludesaddress=and (if present)contenthash=.Areturns a CNAME fallback like<name>.eth.limo..
Supported TLDs and resolvers
The stack now provides DNS resolution for a comprehensive set of alternative TLDs:
| TLD | Resolver | Purpose |
|---|---|---|
.onion, .exit, .onion4, .onion6 |
Tor DNSPort (10.5.0.7:9053) | Tor hidden services |
.i2p |
i2pdns bridge (10.5.0.16:53) + i2pd SOCKS (10.5.0.2:4447) | I2P eepsite names |
.eth |
ensdns (10.5.0.11) via CoreDNS | Ethereum Name Service (local resolver) |
.bit |
namecoindns (10.5.0.12) via CoreDNS | Namecoin names (local resolver service) |
.alt |
local unbound (10.5.0.5) via CoreDNS | Alternative DNS root (local-only fallback mode) |
.loki |
local lokinet (10.5.0.14) via CoreDNS | Lokinet privacy network (no Cloudflare/public DNS forwarder dependency) |
.zil |
zildns (10.5.0.15) via CoreDNS | Zilliqa blockchain (local resolver service; no Cloudflare DNS dependency) |
.web3 |
local unbound (10.5.0.5) via CoreDNS | General blockchain namespace (local-only fallback mode) |
| Alfis TLDs | Alfis (10.5.0.3) | .anon, .btn, .conf, .index, .merch, .mirror, .mob, .screen, .srv, .ygg |
| EmerCoin TLDs | EmerCoin (10.5.0.9) | .emc, .coin, .lib, .bazar, .enum |
| OpenNIC TLDs | local unbound (10.5.0.5) via CoreDNS | .bbs, .chan, .cyb, .dyn, .epic, .geek, .gopher, .indy, .libre, .neo, .null, .o, .oss, .oz, .parody, .pirate, .fur, .ku, .rm, .te, .ti, .uu, .ko (local-only fallback mode) |
| Clearnet | Unbound (10.5.0.5) | All other domains via recursive resolution |
Local-only DNS mode limitations
Darkproxy now avoids direct public DNS forwarders for .alt, .zil, .web3, OpenNIC TLDs, and Lokinet fallback DNS.
Resolution still happens locally in this stack. The remaining external dependency surface is blockchain RPC/data access for record lookup, not public DNS forwarding.
Important limitations still apply for authoritative data:
.ethrequires an Ethereum JSON-RPC backend. By default this stack useshttps://ethereum-rpc.publicnode.cominensdns..alt, OpenNIC TLDs, and.web3are not in the ICANN root. In local-only fallback mode they are sent to local Unbound, so they usually returnNXDOMAINunless you provide your own authoritative/local resolver backend for those namespaces..zilis resolved through localzildns, which queries ZNS usinghttps://api.zilliqa.comby default. This is a Zilliqa RPC lookup path (on-chain resolution), not a Cloudflare DNS forwarder path. You can override the RPC endpoint withZILDNS_ZNS_URLindocker-compose.yml.zildnssupportsZILDNS_CACHE_SECONDS(record cache duration) andZILDNS_PREWARM_DOMAIN(startup warm-up query) to reduce first-query latency.- For browser convenience,
Alookups may return a CNAME to a public IPFS gateway when a.zilcontent hash exists. That gateway alias is separate from DNS resolution itself. .lokiis resolved by local Lokinet, and Lokinet fallback DNS points to local Unbound (no Cloudflare/public DNS forwarder dependency).
Configuration validation and drift tests
A simple automation is included to help detect accidental changes to the compose and DNS configuration:
-
Local check – run
scripts/validate-config.sh(or its PowerShell equivalent).- The script requires
dockerto be installed; it will generate a normalized manifest and compare it againstdocker-compose.lock.yml. - If the baseline file does not exist it will be created; commit the file after review.
- CoreDNS syntax is also validated using either a local
corednsbinary or a container.
- The script requires
-
CI workflow –
.github/workflows/config-validation.ymlis triggered on pushes or pull requests affectingdocker-compose.ymlorPopuraDNS/. The job executes the samedocker compose configcomparison and runs the Corefile check inside a container. -
Updating the baseline – when intentional changes are made to the compose file, regenerate the baseline:
scripts/validate-config.sh # fails with drift cp generated.yml docker-compose.lock.yml git add docker-compose.lock.yml
These steps ensure that configuration drift is caught early and reviewers can see what has changed.
Local ENS (.eth) client
If you want to resolve Ethereum ENS names locally from the host, use the standalone client script:
python3 -m venv .venv-ens
source .venv-ens/bin/activate
pip install -r scripts/requirements-ens.txt
python3 scripts/resolve_ens.py vitalik.eth --pretty
Options:
--rpc <url>to set a custom Ethereum JSON-RPC endpoint.--include-textto include common ENS text records.
Example:
python3 scripts/resolve_ens.py ens.eth --include-text --pretty
Local Namecoin (.bit) resolver (true on-chain)
.bit lookups are routed through a local blockchain-backed resolver path:
- CoreDNS forwards
bit.:53to10.5.0.12:53(namecoindns). namecoindnsqueries localnamecoindJSON-RPC (10.5.0.13:8336).namecoindsyncs the Namecoin blockchain and serves on-chain records.
Quick test:
docker exec darkpihole dig @10.5.0.4 A id.bit
docker exec darkpihole dig @10.5.0.4 TXT id.bit
If your environment returns NXDOMAIN for .bit, configure Namecoin-capable
records in Namecoin itself and wait for local namecoind sync completion.
Important: initial blockchain sync can take significant time. During sync,
.bitresponses may be empty or incomplete.
Local Lokinet (.loki) resolver
.loki lookups are resolved by a local Lokinet daemon path:
- CoreDNS forwards
loki.:53to10.5.0.14:53(lokinet). lokinetresolves.lokinames through the Lokinet network.
Quick test:
docker exec darkpihole dig @10.5.0.4 A oxen.loki
Note: Lokinet may need a short bootstrap period after startup before
.lokinames resolve.
Monitoring the proxy
A lightweight Prometheus/Grafana stack is included to expose 3proxy metrics and give you visibility into traffic volumes, connected sessions, etc.
how it works
3proxyis configured withmonitor -p6800, which sets up a plain-text status socket. Samples look likePROXY CONNS 12orSOCKS IN 345.monitor/exporter.pypolls that socket every few seconds and exports the counters on HTTP port 9100 in Prometheus format.- The
docker-compose.ymlfile now defines three new services:3proxy_exporter,prometheusandgrafana. Prometheus scrapes the exporter and Grafana points at Prometheus as a data source.
building & running
The exporter lives in monitor/; build the image and start the stack:
# build everything including the new services
docker-compose build 3proxy_exporter prometheus grafana
docker-compose up -d
Prometheus will be accessible on port 9090, Grafana on 3000 (admin
password secret). Use Grafana to create or import dashboards – a simple
example JSON is provided in monitor/3proxy-dashboard.json. You can import
that file directly or build your own panels using metrics such as
proxy_conns and proxy_bytes_in.
tips
- Alerts can be added in Prometheus rules, e.g. fire when
proxy_connsexceeds a threshold for several minutes. - If you don’t want the full stack, you can still query the monitor port
directly with
nc; nothing in the proxy depends on the exporter. - The
scripts/validate-config.shscript warns if a 3proxy config lacks amonitorline.
Production readiness checklist
Make sure you never commit real secrets. The secrets/ directory is now
included in .gitignore; both timezone and Yggdrasil key files live there and
are mounted into containers at runtime.
Before deploying to a live environment, run the validation script and resolve any warnings or errors. The script covers:
- YAML syntax and drift against the committed baseline
- CoreDNS/PopuraDNS Corefile syntax
- Unbound configuration syntax
- Presence of required secret files
- Simple 3proxy configuration sanity (presence of
socks/proxyrules) - Detection of
:latestimage tags (pin to fixed versions) - Verification that restart policies exist
The GitHub Actions workflow also includes a smoke test job that spins up
all services in docker-compose.yml using a Docker‑in‑Docker service and
waits for each container’s healthcheck to report healthy. This gives an
additional layer of confidence that the stack can be built and started
successfully.
You can also extend the script with additional service-specific checks such as
running docker run ... commands to exercise the images, scanning the
3proxy config more deeply, or linting other mounted configuration files.
Run the script locally via ./scripts/validate-config.sh (or
.scripts/validate-config.ps1 on Windows) and review the output carefully.
CI will execute the same validations automatically on pull requests.