Skip to content

Commit 18552b7

Browse files
hhfrancoisclaude
andcommitted
fix(cli): copy wintun.dll beside an auto-downloaded version on Windows
WinTUN loads wintun.dll from the executable's own directory (not PATH), so a version fetched into ~/.plug/versions/<v>/ failed with "Error loading wintun.dll: module could not be found" — the DLL was only beside the launcher in Programs/plug. The launcher now copies it next to each downloaded version. Also refreshes a stale route_windows.go comment (the NRPT rule is the .plug suffix now, not a catch-all). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent abbecde commit 18552b7

2 files changed

Lines changed: 34 additions & 6 deletions

File tree

cli/internal/tun/route_windows.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ func configure(dev any, _, cidr, dnsIP string, log logfn) ([]string, string, fun
6969
return nil, "", cleanup, nil
7070
}
7171

72-
// setSystemNRPT installs a catch-all Name Resolution Policy Table rule (namespace
73-
// ".") sending every DNS query to dnsIP. This is what makes single-label cluster
74-
// names resolve on Windows — the equivalent of scutil on macOS / resolv.conf on
75-
// Linux. Driven through the DnsClient cmdlets, which encode the DnsPolicyConfig
76-
// registry correctly (the same table WireGuard-Windows writes by hand), and flush
77-
// the resolver cache so a prior negative answer ("Could not resolve") doesn't stick.
72+
// setSystemNRPT installs a Name Resolution Policy Table rule routing the ".plug"
73+
// search suffix to dnsIP. Paired with that suffix on plug0 (SetDNS above), it makes
74+
// single-label cluster names resolve on Windows: getaddrinfo appends the suffix (a
75+
// real DNS query at last), NRPT sends ".plug" here, answerDNS strips it back. It is
76+
// the Windows equivalent of scutil on macOS / resolv.conf on Linux, and the same
77+
// suffix+NRPT mechanism Tailscale/WireGuard use. Driven through the DnsClient cmdlets
78+
// (they encode the DnsPolicyConfig registry correctly), flushing the resolver cache
79+
// so a prior "Could not resolve" negative doesn't stick.
7880
func setSystemNRPT(dnsIP string) error {
7981
clearSystemNRPT(dnsIP) // drop a stale rule a crashed run may have left
8082
return psRun("Add-DnsClientNrptRule -Namespace '." + searchSuffix + "' -NameServers '" + dnsIP + "'; Clear-DnsClientCache")

cli/main.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ func ensureVersion(v string, cfg config) (string, error) {
302302
own := func() { chownToUser(versionsDir()); chownToUser(dir); chownToUser(bin) }
303303
if fi, err := os.Stat(bin); err == nil && fi.Size() > 1<<20 {
304304
own()
305+
ensureWintunBeside(bin)
305306
return bin, nil
306307
}
307308
data, err := getDownload(cfg, fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH), "v"+v)
@@ -331,9 +332,34 @@ func ensureVersion(v string, cfg config) (string, error) {
331332
return "", err
332333
}
333334
own()
335+
ensureWintunBeside(bin)
334336
return bin, nil
335337
}
336338

339+
// ensureWintunBeside copies wintun.dll next to a versioned binary on Windows.
340+
// WinTUN's loader looks only in the executable's OWN directory (a hardening choice,
341+
// not the PATH), so a binary run from ~/.plug/versions/<v>/ can't find the wintun.dll
342+
// the installer dropped in the launcher dir — "Error loading wintun.dll ... module
343+
// could not be found". Best-effort: copy it from beside the launcher; no-op elsewhere.
344+
func ensureWintunBeside(bin string) {
345+
if runtime.GOOS != "windows" {
346+
return
347+
}
348+
dst := filepath.Join(filepath.Dir(bin), "wintun.dll")
349+
if _, err := os.Stat(dst); err == nil {
350+
return // already there
351+
}
352+
self, err := os.Executable()
353+
if err != nil {
354+
return
355+
}
356+
data, err := os.ReadFile(filepath.Join(filepath.Dir(self), "wintun.dll"))
357+
if err != nil {
358+
return // launcher has none beside it — nothing to copy
359+
}
360+
_ = os.WriteFile(dst, data, 0o644)
361+
}
362+
337363
func listVersions() {
338364
fmt.Printf("launcher: v%s\n", version)
339365
entries, err := os.ReadDir(versionsDir())

0 commit comments

Comments
 (0)