F35 is an end-to-end DNS resolver scanner for real tunnel testing.
It does not only ask a DNS question and call that a success. It actually:
- starts a tunnel client
- uses one resolver from your list
- waits for the tunnel to become usable
- sends a real HTTP request through the tunnel
- prints only resolvers that really pass traffic
This is useful when you want to find resolvers that still have outside connectivity during heavy filtering or shutdown conditions.
A resolver is the DNS server IP you want to test.
Examples:
1.1.1.1
8.8.8.8:53
10.10.34.1If you give only an IP, F35 uses port 53 automatically.
You need all of these:
- a file with resolver IPs
- a working tunnel domain
- one tunnel client:
dnstt-clientslipstream-clientvaydns-client
- the extra flags that your tunnel client needs, passed with
--args
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o f35 ./cmd/f35If you are new, start with the smallest common command:
f35 --resolvers resolvers.txt --domain t.example.com --args '-pubkey YOUR_PUBLIC_KEY'This uses the default engine, which is vaydns.
On Windows PowerShell, if vaydns-client.exe is not in PATH, use --client-path:
.\f35.exe --resolvers resolvers.txt --domain t.example.com --client-path .\vaydns-client.exe --args '-pubkey YOUR_PUBLIC_KEY'If you do not want to pass many flags every time, use a TOML config file.
Use flat keys with underscores, like download_timeout and start_port:
If you are new, this is the easiest way to use F35.
Write your settings once, then run f35 -c f35.toml.
For repeated scans, prefer a config file over a long flag list.
Use this as a starting point and remove the parts you do not need:
resolvers_file = "resolvers.txt"
engine = "vaydns"
client_path = "./vaydns-client"
domain = "t.example.com"
args = "-pubkey YOUR_PUBLIC_KEY"
probe = true
probe_url = "http://www.google.com/gen_204"
probe_timeout = 15
download = false
download_url = "https://speed.cloudflare.com/__down?bytes=100000"
download_timeout = 15
upload = false
upload_url = "https://speed.cloudflare.com/__up"
upload_bytes = 100000
upload_timeout = 15
whois = false
whois_timeout = 15
proxy = "socks5h"
proxy_user = ""
proxy_pass = ""
workers = 20
retries = 0
wait = 1000
start_port = 40000
json = false
short = false
quiet = falseWhat to edit first:
resolvers_fileyour resolver list filedomainyour tunnel domainargsyour tunnel client flags, usually including the public keyclient_pathset this if the client binary is not inPATH
Simple run:
f35 -c f35.tomlCLI flags override the file:
f35 -c f35.toml --workers 50 --downloadIf you are new, focus on --resolvers, --domain, --args, --config or -c, and sometimes --client-path.
Use --args for tunnel-client-specific flags like -pubkey, and always wrap the whole value in quotes.
Example: --args "-pubkey YOUR_PUBLIC_KEY".
| Flag | Default | Meaning |
|---|---|---|
--config, -c |
none | Load a TOML config file. File values become defaults and CLI flags override them. |
--resolvers |
required | Path to a file containing resolver IPs. |
--domain |
required | Tunnel domain to test against. |
--args |
none | Extra tunnel client flags. This is where engine-specific flags like -pubkey go. |
--engine |
vaydns |
Tunnel client engine: dnstt, slipstream, or vaydns. |
--client-path |
PATH lookup |
Explicit path to the tunnel client binary if it is not in PATH. Useful on Windows. |
| Flag | Default | Meaning |
|---|---|---|
--probe |
true |
Run a quick connectivity probe through the tunnel. |
--probe-url |
http://www.google.com/gen_204 |
HTTP URL used for the probe request. |
--probe-timeout |
15 |
Probe request timeout in seconds. |
--download |
false |
Run a real download test through the tunnel. |
--download-url |
https://speed.cloudflare.com/__down?bytes=100000 |
HTTP URL used for the download test. |
--download-timeout |
15 |
Download request timeout in seconds. |
--upload |
false |
Run a real upload test through the tunnel. |
--upload-url |
https://speed.cloudflare.com/__up |
HTTP URL used for the upload test. |
--upload-bytes |
100000 |
Number of bytes sent in the upload body. |
--upload-timeout |
15 |
Upload request timeout in seconds. |
--whois |
false |
Look up resolver organization and country. |
--whois-timeout |
15 |
Whois lookup timeout in seconds. |
| Flag | Default | Meaning |
|---|---|---|
--proxy |
socks5h |
Proxy protocol used for the HTTP request through the tunnel. Wrong values can make healthy resolvers look dead. |
--proxy-user |
none | Proxy username if the tunnel exit requires authentication. |
--proxy-pass |
none | Proxy password if the tunnel exit requires authentication. Requires --proxy-user. |
--workers |
20 |
Number of concurrent E2E scan workers. |
--wait |
1000 |
Milliseconds to wait for tunnel establishment before HTTP tests. |
--retries |
0 |
Retry count after the first failed E2E attempt. |
--start-port |
40000 |
First local listen port used for worker listener allocation. |
| Flag | Default | Meaning |
|---|---|---|
--json |
false |
Print one JSON object per result line instead of plain text. |
--short |
false |
Print only IP:PORT LATENCY in plain text output. |
--quiet |
false |
Suppress startup, progress, and completion logs. |
Use these as the main knobs:
--waitwait longer here if the tunnel starts too slowly--probe-timeoutraise this if the quick probe is timing out--download-timeoutraise this if the download test starts but does not finish in time--upload-timeoutraise this if the upload test starts but does not finish in time--whois-timeoutraise this if the whois lookup is too slow
Good starting values:
- very large resolver list: lower
--workersif the tunnel client starts struggling - slow tunnel startup: increase
--wait - weak or filtered path: increase
--download-timeout - weak upload path: increase
--upload-timeout - slow whois API: increase
--whois-timeout - only probe fails: increase
--probe-timeout
--args is only for tunnel client flags.
Put the same flags there that you normally pass when you run the tunnel client manually. F35 does not replace your client config. It only fills in the resolver, listen address, and domain for you.
Always wrap the whole --args value in quotes.
- Linux and macOS:
--args '-pubkey YOUR_PUBLIC_KEY' - Windows PowerShell:
--args '-pubkey YOUR_PUBLIC_KEY'--args "-pubkey YOUR_PUBLIC_KEY"also works - Windows
cmd.exe: use double quotes--args "-pubkey YOUR_PUBLIC_KEY"
Examples:
- DNSTT:
--args '-pubkey YOUR_PUBLIC_KEY' - VayDNS:
--args '-pubkey YOUR_PUBLIC_KEY -log-level info -udp-timeout 200ms' - Windows
cmd.exe:--args "-pubkey YOUR_PUBLIC_KEY" - Windows PowerShell:
--args '-pubkey YOUR_PUBLIC_KEY'
F35 automatically fills these parts for you:
- resolver address
- local listen address
- domain
For dnstt, F35 places --args before the positional domain and listen arguments.
If you are new, start with something like this:
f35 --resolvers resolvers.txt --engine dnstt --domain t.example.com --proxy socks5h --args '-pubkey YOUR_PUBLIC_KEY'What this means:
- read resolvers from
resolvers.txt - use
dnstt-client - connect to
t.example.com - send the HTTP test through the tunnel using the
socks5hprotocol - pass the public key to the client
f35 --resolvers resolvers.txt \
--engine dnstt \
--domain t.example.com \
--proxy socks5h \
--args '-pubkey YOUR_PUBLIC_KEY'f35 --resolvers resolvers.txt \
--engine vaydns \
--domain t.example.com \
--proxy socks5h \
--args '-pubkey YOUR_PUBLIC_KEY -record-type txt -clientid-size 1 -rps 300 -max-qname-len 99 -max-num-labels 2'f35 --resolvers resolvers.txt \
--engine slipstream \
--domain t.example.com \
--proxy socks5hUse this if the proxy exposed by your tunnel requires a username and password:
f35 --resolvers resolvers.txt \
--engine dnstt \
--domain t.example.com \
--proxy socks5h \
--proxy-user myuser \
--proxy-pass mypass \
--args '-pubkey YOUR_PUBLIC_KEY'--proxy-pass only works together with --proxy-user.
f35 --resolvers resolvers.txt --engine dnstt --domain t.example.com --proxy socks5h --args '-pubkey YOUR_PUBLIC_KEY' | tee healthy.txtLinux or macOS:
f35 --resolvers resolvers.txt --engine vaydns --domain t.example.com --proxy socks5h --client-path ./vaydns-client --args '-pubkey YOUR_PUBLIC_KEY'Windows PowerShell:
.\f35.exe --resolvers resolvers.txt --domain t.example.com --client-path .\vaydns-client.exe --args '-pubkey YOUR_PUBLIC_KEY'Windows full path example:
.\f35.exe --resolvers resolvers.txt --domain t.example.com --client-path C:\tools\vaydns-client.exe --args '-pubkey YOUR_PUBLIC_KEY'This is useful when resolvers are slow but still usable.
f35 --resolvers resolvers.txt --engine vaydns --domain t.example.com --proxy socks5h --workers 50 --wait 2000 --probe-timeout 8 --retries 2 --args '-pubkey YOUR_PUBLIC_KEY'Meaning:
- fewer concurrent workers
- longer tunnel warm-up wait
- longer HTTP timeout
- retry failed resolvers
f35 --resolvers resolvers.txt --engine vaydns --domain t.example.com --proxy socks5h --whois --args '-pubkey YOUR_PUBLIC_KEY'This keeps the enabled checks independent, and if --whois is enabled, plain output also includes org and country fields for that resolver IP.
This is most useful when the resolver IP itself belongs to the network you care about. If your tunnel goes into a more advanced upstream chain, this extra lookup can be less meaningful.
f35 --resolvers resolvers.txt --engine vaydns --domain t.example.com --proxy socks5h --download --upload --args '-pubkey YOUR_PUBLIC_KEY'This adds a real upload request to the scan and keeps it independent from the other checks.
By default it sends 100000 bytes with a POST, and you can change that with --upload-bytes.
f35 --resolvers resolvers.txt --engine vaydns --domain t.example.com --proxy socks5h --whois --upload --json --args '-pubkey YOUR_PUBLIC_KEY'Use this if you want to parse the output in another program.
F35 does not generate advanced proxy protocol packets by itself.
It only sends a normal HTTP request through the tunnel using the protocol selected with --proxy.
Examples:
- if your tunnel path expects SOCKS, use
--proxy socks5or--proxy socks5h - if your tunnel path expects HTTP proxy traffic, use
--proxy http
If you use something more advanced behind the tunnel, like vless+ws, F35 is not generating native vless+ws traffic.
It is only checking whether the tunnel path can move a request and return any response.
That means:
- the download request is the strongest signal
- upload is the next strongest signal after download
- whois and probe are weaker checks
- F35 does not require HTTP
200 - even
400or404can still prove that the tunnel is working --whoismay be less useful in those advanced chains- wrong
--proxycan ruin scan results
By default, F35 also prints colored status logs to stderr.
Use --quiet to silence those logs and keep only result lines on stdout.
On interactive terminals, the progress status updates in place on a single line so healthy resolver output stays visible above it.
Typical status logs look like this:
[INFO] starting | resolvers=5000 | workers=20 | engine=vaydns
[INFO] 50/5000 | healthy=11 | failed=39 | elapsed=28s
[INFO] completed | 5000/5000 | healthy=241 | failed=4759 | elapsed=2m14sIf no resolver passes, the final status line is printed as [WARN].
1.2.3.4:53 342ms download="off" upload="off" whois="off" probe="ok"
5.6.7.8:53 89ms download="off" upload="off" whois="off" probe="ok"Only usable resolvers are printed.
A resolver is considered usable if at least one enabled check succeeds. By default, probe is the primary signal.
When more than one enabled check succeeds, latency priority is download > upload > whois > probe.
F35 does not require HTTP 200.
Even a 400 or 404 can still prove that the tunnel is working.
Latency is colored on terminal output:
- green:
0-2000ms - yellow:
2000-6000ms - red:
6000ms+
If you pipe the output to a file or another command, colors are not printed.
1.2.3.4:53 342ms download="ok" upload="ok" whois="ok" probe="fail" org="Iran Information Technology Company PJSC" country="Iran"
5.6.7.8:53 2140ms download="ok" upload="fail" whois="fail" probe="ok" org="" country=""The output stays simple and the status fields always appear in the same order. When --whois is enabled, org and country are appended at the end.
1.2.3.4:53 342ms
5.6.7.8:53 89ms{"resolver":"1.2.3.4:53","latency_ms":342,"download":"off","upload":"off","whois":"off","probe":"ok"}
{"resolver":"5.6.7.8:53","latency_ms":2140,"download":"ok","upload":"fail","whois":"fail","probe":"ok"}If you do not know what to tune first, try this order:
- keep
--proxy socks5h - if output is empty, increase
--wait - if working resolvers are slow, increase
--probe-timeout - if results are unstable, lower
--workers - if some resolvers fail randomly, add
--retries 1or--retries 2
The selected tunnel client binary was not found.
Fix it with one of these:
- install the client
- add it to
PATH - use
--client-path /full/path/to/client - on Windows, a common fix is
--client-path .\vaydns-client.exe - on Windows, a full path also works, for example
--client-path C:\tools\vaydns-client.exe
Usually one of these is wrong:
- domain
- engine
- pubkey or other tunnel client flags inside
--args - wait time is too short
- probe timeout is too short
Try this:
--wait 2000 --probe-timeout 8 --retries 1If you set a proxy password, you must also set a proxy username.
Try:
- lower
--workers - increase
--wait - increase
--probe-timeout - add retries with
--retries
Put the same client flags you normally use when running your tunnel client manually.
F35 is not replacing your tunnel client config. It is only fuzzing resolvers and local listen ports around that client command.
- root package
github.com/nxdp/f35importable scanner library ./cmd/f35CLI entrypoint
package main
import (
"fmt"
"github.com/nxdp/f35"
)
func main() {
cfg := f35.DefaultConfig()
cfg.Domain = "t.example.com"
cfg.Resolvers = []string{"1.1.1.1:53", "8.8.8.8:53"}
cfg.Upload = true
cfg.ExtraArgs = []string{"-pubkey", "YOUR_PUBLIC_KEY"}
err := f35.Scan(cfg, f35.Hooks{
OnResult: func(result f35.Result) {
fmt.Println(result.Resolver, result.LatencyMS)
},
})
if err != nil {
panic(err)
}
}