Skip to content

Commit a2e6f4c

Browse files
committed
Fix dnstt-client not found: check current directory and improve error messages
1 parent f2d25f6 commit a2e6f4c

10 files changed

Lines changed: 175 additions & 34 deletions

File tree

GUIDE.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,51 @@ findns همه این‌ها را به صورت خودکار تست می‌کند
3333

3434
**خیر!** findns به تنهایی تمام تست‌های DNS را انجام می‌دهد. فقط اگر بخواهید تست واقعی تانل (e2e) انجام دهید، به dnstt-client یا slipstream-client نیاز دارید. بدون آن‌ها هم اسکنر کامل کار می‌کند.
3535

36+
### dnstt-client چیست و چطور نصبش کنم؟
37+
38+
`dnstt-client` برنامه کلاینت پروژه [DNSTT](https://www.bamsoftware.com/software/dnstt/) است. این برنامه یک تانل DNS-over-UDP یا DNS-over-HTTPS درست می‌کند. findns از این برنامه برای **تست واقعی تانل** (e2e) استفاده می‌کند — یعنی واقعاً یک تانل می‌سازد و بررسی می‌کند اتصال برقرار می‌شود یا نه.
39+
40+
**نصب با Go (ساده‌ترین روش):**
41+
42+
</div>
43+
44+
```bash
45+
go install www.bamsoftware.com/git/dnstt.git/dnstt-client@latest
46+
```
47+
48+
<div dir="rtl">
49+
50+
**دانلود دستی:**
51+
52+
از [صفحه پروژه DNSTT](https://www.bamsoftware.com/software/dnstt/) باینری آماده دانلود کنید.
53+
54+
**بعد از دانلود، حتماً فایل را در PATH قرار دهید:**
55+
56+
</div>
57+
58+
```bash
59+
# لینوکس/macOS:
60+
sudo mv dnstt-client /usr/local/bin/
61+
sudo chmod +x /usr/local/bin/dnstt-client
62+
63+
# یا PATH را به پوشه فعلی اضافه کنید:
64+
export PATH=$PATH:$(pwd)
65+
```
66+
67+
<div dir="rtl">
68+
69+
> **نکته مهم:** فقط گذاشتن فایل کنار findns روی لینوکس **کافی نیست** مگر اینکه پوشه فعلی در PATH باشد. روی ویندوز این مشکل وجود ندارد.
70+
71+
### کدام resolverها برای dnstt کار می‌کنند؟
72+
73+
بدون فلگ `--pubkey` هم findns بررسی می‌کند کدام resolverها **قابلیت** کار با تانل DNS را دارند:
74+
75+
- **resolve/tunnel**: بررسی می‌کند resolver می‌تواند NS record دامنه تانل شما را ببیند
76+
- **edns**: بررسی می‌کند سایز payload بزرگ (1232 بایت) پشتیبانی می‌شود
77+
- **nxdomain**: بررسی می‌کند resolver جواب جعلی نمی‌دهد
78+
79+
resolverهایی که همه این مراحل را پاس کنند، **با احتمال بالا** برای dnstt کار می‌کنند. فلگ `--pubkey` فقط تأیید نهایی (e2e) را اضافه می‌کند.
80+
3681
---
3782

3883
## 2. نصب و راه‌اندازی

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,13 @@ chmod +x findns-linux-amd64
8080
### Requirements
8181

8282
- **Go 1.24+** for building from source
83-
- **dnstt-client** in PATH (for e2e DNSTT tests)
84-
- **slipstream-client** in PATH (for e2e Slipstream tests)
85-
- **curl** in PATH (for e2e connectivity verification)
83+
- **dnstt-client** — only for e2e tunnel tests (`--pubkey`). Install: `go install www.bamsoftware.com/git/dnstt.git/dnstt-client@latest`
84+
- **slipstream-client** — only for e2e Slipstream tests (`--cert`)
85+
- **curl** — for e2e connectivity verification
86+
87+
> **Important:** On Linux, you must place `dnstt-client` in PATH (e.g. `/usr/local/bin/`). Just putting it next to the scanner is not enough. Run: `sudo mv dnstt-client /usr/local/bin/ && sudo chmod +x /usr/local/bin/dnstt-client`
88+
>
89+
> Without `--pubkey`, the scanner still finds resolvers compatible with DNS tunneling — it tests ping, resolve, NXDOMAIN, EDNS, and tunnel delegation without needing dnstt-client.
8690
8791
---
8892

@@ -634,9 +638,13 @@ go install github.com/SamNet-dev/findns/cmd@latest
634638
### پیش‌نیازها
635639

636640
- **Go 1.24+** برای بیلد از سورس
637-
- **dnstt-client** در PATH (برای تست e2e DNSTT)
638-
- **slipstream-client** در PATH (برای تست e2e Slipstream)
639-
- **curl** در PATH (برای تأیید اتصال e2e)
641+
- **dnstt-client** — فقط برای تست e2e تانل (`--pubkey`). نصب: `go install www.bamsoftware.com/git/dnstt.git/dnstt-client@latest`
642+
- **slipstream-client** — فقط برای تست e2e Slipstream (`--cert`)
643+
- **curl** — برای تأیید اتصال e2e
644+
645+
> **مهم:** در لینوکس باید `dnstt-client` را در PATH قرار دهید (مثلاً `/usr/local/bin/`). فقط گذاشتن کنار اسکنر کافی نیست. اجرا کنید: `sudo mv dnstt-client /usr/local/bin/ && sudo chmod +x /usr/local/bin/dnstt-client`
646+
>
647+
> بدون `--pubkey` هم اسکنر resolverهای سازگار با تانل DNS را پیدا می‌کند (ping, resolve, nxdomain, edns, tunnel delegation بدون نیاز به dnstt-client).
640648
641649
---
642650

cmd/chain.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"fmt"
5-
"os/exec"
65
"strconv"
76
"strings"
87
"time"
@@ -51,7 +50,7 @@ func parseStepFlag(raw string) (stepConfig, error) {
5150
return stepConfig{name: name, params: params}, nil
5251
}
5352

54-
func buildStep(cfg stepConfig, defaultTimeout, defaultCount int, ports chan int) (scanner.Step, error) {
53+
func buildStep(cfg stepConfig, defaultTimeout, defaultCount int, ports chan int, binPaths map[string]string) (scanner.Step, error) {
5554
stepTimeout := defaultTimeout
5655
if v, ok := cfg.params["timeout"]; ok {
5756
t, err := strconv.Atoi(v)
@@ -102,7 +101,7 @@ func buildStep(cfg stepConfig, defaultTimeout, defaultCount int, ports chan int)
102101
if v, ok := cfg.params["test-url"]; ok {
103102
testURL = v
104103
}
105-
return scanner.Step{Name: "e2e/dnstt", Timeout: dur, Check: scanner.DnsttCheck(domain, pubkey, testURL, ports), SortBy: "e2e_ms"}, nil
104+
return scanner.Step{Name: "e2e/dnstt", Timeout: dur, Check: scanner.DnsttCheckBin(binPaths["dnstt-client"], domain, pubkey, testURL, ports), SortBy: "e2e_ms"}, nil
106105

107106
case "e2e/slipstream":
108107
domain, ok := cfg.params["domain"]
@@ -114,7 +113,7 @@ func buildStep(cfg stepConfig, defaultTimeout, defaultCount int, ports chan int)
114113
if v, ok := cfg.params["test-url"]; ok {
115114
testURL = v
116115
}
117-
return scanner.Step{Name: "e2e/slipstream", Timeout: dur, Check: scanner.SlipstreamCheck(domain, cert, testURL, ports), SortBy: "e2e_ms"}, nil
116+
return scanner.Step{Name: "e2e/slipstream", Timeout: dur, Check: scanner.SlipstreamCheckBin(binPaths["slipstream-client"], domain, cert, testURL, ports), SortBy: "e2e_ms"}, nil
118117

119118
case "nxdomain":
120119
return scanner.Step{Name: "nxdomain", Timeout: dur, Check: scanner.NXDomainCheck(stepCount), SortBy: "hijack"}, nil
@@ -153,7 +152,7 @@ func buildStep(cfg stepConfig, defaultTimeout, defaultCount int, ports chan int)
153152
if v, ok := cfg.params["test-url"]; ok {
154153
testURL = v
155154
}
156-
return scanner.Step{Name: "doh/e2e", Timeout: dur, Check: scanner.DoHDnsttCheck(domain, pubkey, testURL, ports), SortBy: "e2e_ms"}, nil
155+
return scanner.Step{Name: "doh/e2e", Timeout: dur, Check: scanner.DoHDnsttCheckBin(binPaths["dnstt-client"], domain, pubkey, testURL, ports), SortBy: "e2e_ms"}, nil
157156

158157
default:
159158
return scanner.Step{}, fmt.Errorf("unknown step type %q", cfg.name)
@@ -175,20 +174,29 @@ func runChain(cmd *cobra.Command, args []string) error {
175174
}
176175

177176
// Pre-flight: check required binaries for e2e steps
177+
binPaths := make(map[string]string) // "dnstt-client" -> resolved path
178178
for _, cfg := range configs {
179179
switch cfg.name {
180180
case "e2e/dnstt", "doh/e2e":
181-
if _, err := exec.LookPath("dnstt-client"); err != nil {
182-
return fmt.Errorf("step %q requires dnstt-client in PATH (not found)", cfg.name)
181+
if _, ok := binPaths["dnstt-client"]; !ok {
182+
bin, err := findBinary("dnstt-client")
183+
if err != nil {
184+
return fmt.Errorf("step %q requires dnstt-client: %w", cfg.name, err)
185+
}
186+
binPaths["dnstt-client"] = bin
183187
}
184-
if _, err := exec.LookPath("curl"); err != nil {
188+
if _, err := findBinary("curl"); err != nil {
185189
return fmt.Errorf("step %q requires curl in PATH (not found)", cfg.name)
186190
}
187191
case "e2e/slipstream":
188-
if _, err := exec.LookPath("slipstream-client"); err != nil {
189-
return fmt.Errorf("step %q requires slipstream-client in PATH (not found)", cfg.name)
192+
if _, ok := binPaths["slipstream-client"]; !ok {
193+
bin, err := findBinary("slipstream-client")
194+
if err != nil {
195+
return fmt.Errorf("step %q requires slipstream-client: %w", cfg.name, err)
196+
}
197+
binPaths["slipstream-client"] = bin
190198
}
191-
if _, err := exec.LookPath("curl"); err != nil {
199+
if _, err := findBinary("curl"); err != nil {
192200
return fmt.Errorf("step %q requires curl in PATH (not found)", cfg.name)
193201
}
194202
}
@@ -200,7 +208,7 @@ func runChain(cmd *cobra.Command, args []string) error {
200208
// Build all steps
201209
steps := make([]scanner.Step, 0, len(configs))
202210
for _, cfg := range configs {
203-
s, err := buildStep(cfg, timeout, count, ports)
211+
s, err := buildStep(cfg, timeout, count, ports, binPaths)
204212
if err != nil {
205213
return err
206214
}

cmd/doh_e2e.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,19 @@ func runDoHE2E(cmd *cobra.Command, args []string) error {
2727
pubkey, _ := cmd.Flags().GetString("pubkey")
2828
testURL, _ := cmd.Flags().GetString("test-url")
2929

30+
bin, err := findBinary("dnstt-client")
31+
if err != nil {
32+
return err
33+
}
34+
3035
urls, err := loadInput()
3136
if err != nil {
3237
return err
3338
}
3439

3540
dur := time.Duration(e2eTimeout) * time.Second
3641
ports := scanner.PortPool(30000, workers)
37-
check := scanner.DoHDnsttCheck(domain, pubkey, testURL, ports)
42+
check := scanner.DoHDnsttCheckBin(bin, domain, pubkey, testURL, ports)
3843

3944
start := time.Now()
4045
results := scanner.RunPool(urls, workers, dur, check, newProgress("doh/e2e"))

cmd/e2e_dnstt.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,19 @@ func runE2EDnstt(cmd *cobra.Command, args []string) error {
2727
pubkey, _ := cmd.Flags().GetString("pubkey")
2828
testURL, _ := cmd.Flags().GetString("test-url")
2929

30+
bin, err := findBinary("dnstt-client")
31+
if err != nil {
32+
return err
33+
}
34+
3035
ips, err := loadInput()
3136
if err != nil {
3237
return err
3338
}
3439

3540
dur := time.Duration(e2eTimeout) * time.Second
3641
ports := scanner.PortPool(30000, workers)
37-
check := scanner.DnsttCheck(domain, pubkey, testURL, ports)
42+
check := scanner.DnsttCheckBin(bin, domain, pubkey, testURL, ports)
3843

3944
start := time.Now()
4045
results := scanner.RunPool(ips, workers, dur, check, newProgress("e2e/dnstt"))

cmd/e2e_slipstream.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,19 @@ func runE2ESlipstream(cmd *cobra.Command, args []string) error {
2626
certPath, _ := cmd.Flags().GetString("cert")
2727
testURL, _ := cmd.Flags().GetString("test-url")
2828

29+
bin, err := findBinary("slipstream-client")
30+
if err != nil {
31+
return err
32+
}
33+
2934
ips, err := loadInput()
3035
if err != nil {
3136
return err
3237
}
3338

3439
dur := time.Duration(e2eTimeout) * time.Second
3540
ports := scanner.PortPool(30000, workers)
36-
check := scanner.SlipstreamCheck(domain, certPath, testURL, ports)
41+
check := scanner.SlipstreamCheckBin(bin, domain, certPath, testURL, ports)
3742

3843
start := time.Now()
3944
results := scanner.RunPool(ips, workers, dur, check, newProgress("e2e/slipstream"))

cmd/root.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package main
33
import (
44
"fmt"
55
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"runtime"
69
"time"
710

811
"github.com/SamNet-dev/findns/internal/scanner"
@@ -84,6 +87,37 @@ func writeReport(mode string, results []scanner.Result, elapsed time.Duration, s
8487
return nil
8588
}
8689

90+
// findBinary looks for a binary in PATH first, then in the current directory.
91+
// On Linux, exec.LookPath does NOT check the current directory, so users placing
92+
// dnstt-client next to the scanner get "not found". This fixes that.
93+
func findBinary(name string) (string, error) {
94+
// Check PATH first
95+
if p, err := exec.LookPath(name); err == nil {
96+
return p, nil
97+
}
98+
99+
// Check current directory
100+
local := name
101+
if runtime.GOOS == "windows" && filepath.Ext(name) == "" {
102+
local = name + ".exe"
103+
}
104+
if abs, err := filepath.Abs(local); err == nil {
105+
if _, err := os.Stat(abs); err == nil {
106+
return abs, nil
107+
}
108+
}
109+
110+
hint := ""
111+
switch name {
112+
case "dnstt-client":
113+
hint = "\n\nTo install dnstt-client:\n go install www.bamsoftware.com/git/dnstt.git/dnstt-client@latest\n\nOr download from: https://www.bamsoftware.com/software/dnstt/"
114+
case "slipstream-client":
115+
hint = "\n\nDownload from: https://github.com/Mygod/slipstream-rust/releases"
116+
}
117+
118+
return "", fmt.Errorf("%s not found in PATH or current directory.%s\n\nIf already downloaded, either:\n 1. Move it to a folder in PATH: sudo mv %s /usr/local/bin/\n 2. Or add current directory to PATH: export PATH=$PATH:$(pwd)", name, hint, name)
119+
}
120+
87121
func isTTY() bool {
88122
fi, err := os.Stderr.Stat()
89123
if err != nil {

cmd/scan.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package main
33
import (
44
"fmt"
55
"os"
6-
"os/exec"
76
"sort"
87
"strings"
98
"time"
@@ -72,18 +71,23 @@ func runScan(cmd *cobra.Command, args []string) error {
7271
}
7372

7473
// Pre-flight: verify required binaries before wasting time scanning
74+
var dnsttBin, slipstreamBin string
7575
if pubkey != "" {
76-
if _, err := exec.LookPath("dnstt-client"); err != nil {
77-
return fmt.Errorf("--pubkey requires dnstt-client in PATH (not found)")
76+
bin, err := findBinary("dnstt-client")
77+
if err != nil {
78+
return fmt.Errorf("--pubkey requires dnstt-client: %w", err)
7879
}
80+
dnsttBin = bin
7981
}
8082
if certPath != "" {
81-
if _, err := exec.LookPath("slipstream-client"); err != nil {
82-
return fmt.Errorf("--cert requires slipstream-client in PATH (not found)")
83+
bin, err := findBinary("slipstream-client")
84+
if err != nil {
85+
return fmt.Errorf("--cert requires slipstream-client: %w", err)
8386
}
87+
slipstreamBin = bin
8488
}
85-
if (pubkey != "" || certPath != "") {
86-
if _, err := exec.LookPath("curl"); err != nil {
89+
if pubkey != "" || certPath != "" {
90+
if _, err := findBinary("curl"); err != nil {
8791
return fmt.Errorf("e2e tests require curl in PATH (not found)")
8892
}
8993
}
@@ -111,7 +115,7 @@ func runScan(cmd *cobra.Command, args []string) error {
111115
if domain != "" && pubkey != "" {
112116
steps = append(steps, scanner.Step{
113117
Name: "doh/e2e", Timeout: time.Duration(e2eTimeout) * time.Second,
114-
Check: scanner.DoHDnsttCheck(domain, pubkey, testURL, ports), SortBy: "e2e_ms",
118+
Check: scanner.DoHDnsttCheckBin(dnsttBin, domain, pubkey, testURL, ports), SortBy: "e2e_ms",
115119
})
116120
}
117121
} else {
@@ -144,13 +148,13 @@ func runScan(cmd *cobra.Command, args []string) error {
144148
if domain != "" && pubkey != "" {
145149
steps = append(steps, scanner.Step{
146150
Name: "e2e/dnstt", Timeout: time.Duration(e2eTimeout) * time.Second,
147-
Check: scanner.DnsttCheck(domain, pubkey, testURL, ports), SortBy: "e2e_ms",
151+
Check: scanner.DnsttCheckBin(dnsttBin, domain, pubkey, testURL, ports), SortBy: "e2e_ms",
148152
})
149153
}
150154
if domain != "" && certPath != "" {
151155
steps = append(steps, scanner.Step{
152156
Name: "e2e/slipstream", Timeout: time.Duration(e2eTimeout) * time.Second,
153-
Check: scanner.SlipstreamCheck(domain, certPath, testURL, ports), SortBy: "e2e_ms",
157+
Check: scanner.SlipstreamCheckBin(slipstreamBin, domain, certPath, testURL, ports), SortBy: "e2e_ms",
154158
})
155159
}
156160
}

internal/scanner/doh.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,17 @@ func DoHTunnelCheck(domain string, count int) CheckFunc {
174174
}
175175
}
176176

177+
// DoHDnsttCheckBin is like DoHDnsttCheck but uses an explicit binary path.
178+
func DoHDnsttCheckBin(bin, domain, pubkey, testURL string, ports chan int) CheckFunc {
179+
return dohDnsttCheck(bin, domain, pubkey, testURL, ports)
180+
}
181+
177182
// DoHDnsttCheck runs an e2e test using dnstt-client in DoH mode.
178183
func DoHDnsttCheck(domain, pubkey, testURL string, ports chan int) CheckFunc {
184+
return dohDnsttCheck("dnstt-client", domain, pubkey, testURL, ports)
185+
}
186+
187+
func dohDnsttCheck(bin, domain, pubkey, testURL string, ports chan int) CheckFunc {
179188
return func(url string, timeout time.Duration) (bool, Metrics) {
180189
ctx, cancel := context.WithTimeout(context.Background(), timeout)
181190
defer cancel()
@@ -190,7 +199,7 @@ func DoHDnsttCheck(domain, pubkey, testURL string, ports chan int) CheckFunc {
190199

191200
start := time.Now()
192201

193-
cmd := execCommandContext(ctx, "dnstt-client",
202+
cmd := execCommandContext(ctx, bin,
194203
"-doh", url,
195204
"-pubkey", pubkey,
196205
domain,

0 commit comments

Comments
 (0)