forked from tailscale/tailscale
-
Notifications
You must be signed in to change notification settings - Fork 0
/
manager_windows.go
543 lines (490 loc) · 15.4 KB
/
manager_windows.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package dns
import (
"bufio"
"bytes"
"errors"
"fmt"
"net/netip"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"syscall"
"time"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/atomicfile"
"tailscale.com/envknob"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname"
"tailscale.com/util/winutil"
)
const (
versionKey = `SOFTWARE\Microsoft\Windows NT\CurrentVersion`
)
var configureWSL = envknob.RegisterBool("TS_DEBUG_CONFIGURE_WSL")
type windowsManager struct {
logf logger.Logf
guid string
nrptDB *nrptRuleDatabase
wslManager *wslManager
}
func NewOSConfigurator(logf logger.Logf, interfaceName string) (OSConfigurator, error) {
ret := &windowsManager{
logf: logf,
guid: interfaceName,
wslManager: newWSLManager(logf),
}
if isWindows10OrBetter() {
ret.nrptDB = newNRPTRuleDatabase(logf)
}
go func() {
// Log WSL status once at startup.
if distros, err := wslDistros(); err != nil {
logf("WSL: could not list distributions: %v", err)
} else {
logf("WSL: found %d distributions", len(distros))
}
}()
return ret, nil
}
func (m *windowsManager) openInterfaceKey(pfx winutil.RegistryPathPrefix) (registry.Key, error) {
path := pfx.WithSuffix(m.guid)
key, err := winutil.OpenKeyWait(registry.LOCAL_MACHINE, path, registry.SET_VALUE)
if err != nil {
return 0, fmt.Errorf("opening %s: %w", path, err)
}
return key, nil
}
func delValue(key registry.Key, name string) error {
if err := key.DeleteValue(name); err != nil && err != registry.ErrNotExist {
return err
}
return nil
}
// setSplitDNS configures one or more NRPT (Name Resolution Policy Table) rules
// to resolve queries for domains using resolvers, rather than the
// system's "primary" resolver.
//
// If no resolvers are provided, the Tailscale NRPT rules are deleted.
func (m *windowsManager) setSplitDNS(resolvers []netip.Addr, domains []dnsname.FQDN) error {
if m.nrptDB == nil {
if resolvers == nil {
// Just a no-op in this case.
return nil
}
return fmt.Errorf("Split DNS unsupported on this Windows version")
}
defer m.nrptDB.Refresh()
if len(resolvers) == 0 {
return m.nrptDB.DelAllRuleKeys()
}
servers := make([]string, 0, len(resolvers))
for _, resolver := range resolvers {
servers = append(servers, resolver.String())
}
return m.nrptDB.WriteSplitDNSConfig(servers, domains)
}
func setTailscaleHosts(prevHostsFile []byte, hosts []*HostEntry) ([]byte, error) {
b := bytes.ReplaceAll(prevHostsFile, []byte("\r\n"), []byte("\n"))
sc := bufio.NewScanner(bytes.NewReader(b))
const (
header = "# TailscaleHostsSectionStart"
footer = "# TailscaleHostsSectionEnd"
)
var comments = []string{
"# This section contains MagicDNS entries for Tailscale.",
"# Do not edit this section manually.",
}
var out bytes.Buffer
var inSection bool
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == header {
inSection = true
continue
}
if line == footer {
inSection = false
continue
}
if inSection {
continue
}
fmt.Fprintln(&out, line)
}
if err := sc.Err(); err != nil {
return nil, err
}
if len(hosts) > 0 {
fmt.Fprintln(&out, header)
for _, c := range comments {
fmt.Fprintln(&out, c)
}
fmt.Fprintln(&out)
for _, he := range hosts {
fmt.Fprintf(&out, "%s %s\n", he.Addr, strings.Join(he.Hosts, " "))
}
fmt.Fprintln(&out)
fmt.Fprintln(&out, footer)
}
return bytes.ReplaceAll(out.Bytes(), []byte("\n"), []byte("\r\n")), nil
}
// setHosts sets the hosts file to contain the given host entries.
func (m *windowsManager) setHosts(hosts []*HostEntry) error {
systemDir, err := windows.GetSystemDirectory()
if err != nil {
return err
}
hostsFile := filepath.Join(systemDir, "drivers", "etc", "hosts")
b, err := os.ReadFile(hostsFile)
if err != nil {
return err
}
outB, err := setTailscaleHosts(b, hosts)
if err != nil {
return err
}
const fileMode = 0 // ignored on windows.
// This can fail spuriously with an access denied error, so retry it a
// few times.
for i := 0; i < 5; i++ {
if err = atomicfile.WriteFile(hostsFile, outB, fileMode); err == nil {
return nil
}
time.Sleep(10 * time.Millisecond)
}
return err
}
// setPrimaryDNS sets the given resolvers and domains as the Tailscale
// interface's DNS configuration.
// If resolvers is non-empty, those resolvers become the system's
// "primary" resolvers.
// domains can be set without resolvers, which just contributes new
// paths to the global DNS search list.
func (m *windowsManager) setPrimaryDNS(resolvers []netip.Addr, domains []dnsname.FQDN) error {
var ipsv4 []string
var ipsv6 []string
for _, ip := range resolvers {
if ip.Is4() {
ipsv4 = append(ipsv4, ip.String())
} else {
ipsv6 = append(ipsv6, ip.String())
}
}
domStrs := make([]string, 0, len(domains))
for _, dom := range domains {
domStrs = append(domStrs, dom.WithoutTrailingDot())
}
key4, err := m.openInterfaceKey(winutil.IPv4TCPIPInterfacePrefix)
if err != nil {
return err
}
defer key4.Close()
if len(ipsv4) == 0 {
if err := delValue(key4, "NameServer"); err != nil {
return err
}
} else if err := key4.SetStringValue("NameServer", strings.Join(ipsv4, ",")); err != nil {
return err
}
if len(domains) == 0 {
if err := delValue(key4, "SearchList"); err != nil {
return err
}
} else if err := key4.SetStringValue("SearchList", strings.Join(domStrs, ",")); err != nil {
return err
}
key6, err := m.openInterfaceKey(winutil.IPv6TCPIPInterfacePrefix)
if err != nil {
return err
}
defer key6.Close()
if len(ipsv6) == 0 {
if err := delValue(key6, "NameServer"); err != nil {
return err
}
} else if err := key6.SetStringValue("NameServer", strings.Join(ipsv6, ",")); err != nil {
return err
}
if len(domains) == 0 {
if err := delValue(key6, "SearchList"); err != nil {
return err
}
} else if err := key6.SetStringValue("SearchList", strings.Join(domStrs, ",")); err != nil {
return err
}
// Disable LLMNR on the Tailscale interface. We don't do multicast, and we
// certainly don't do LLMNR, so it's pointless to make Windows try it. It is
// being deprecated.
if err := key4.SetDWordValue("EnableMulticast", 0); err != nil {
return err
}
if err := key6.SetDWordValue("EnableMulticast", 0); err != nil {
return err
}
return nil
}
func (m *windowsManager) SetDNS(cfg OSConfig) error {
// We can configure Windows DNS in one of two ways:
//
// - In primary DNS mode, we set the NameServer and SearchList
// registry keys on our interface. Because our interface metric
// is very low, this turns us into the one and only "primary"
// resolver for the OS, i.e. all queries flow to the
// resolver(s) we specify.
// - In split DNS mode, we set the Domain registry key on our
// interface (which adds that domain to the global search list,
// but does not contribute other DNS configuration from the
// interface), and configure an NRPT (Name Resolution Policy
// Table) rule to route queries for our suffixes to the
// provided resolver.
//
// When switching modes, we delete all the configuration related
// to the other mode, so these two are an XOR.
//
// Windows actually supports much more advanced configurations as
// well, with arbitrary routing of hosts and suffixes to arbitrary
// resolvers. However, we use it in a "simple" split domain
// configuration only, routing one set of things to the "split"
// resolver and the rest to the primary.
// Unconditionally disable dynamic DNS updates and NetBIOS on our
// interfaces.
if err := m.disableDynamicUpdates(); err != nil {
m.logf("disableDynamicUpdates error: %v\n", err)
}
if err := m.disableNetBIOS(); err != nil {
m.logf("disableNetBIOS error: %v\n", err)
}
if len(cfg.MatchDomains) == 0 {
if err := m.setSplitDNS(nil, nil); err != nil {
return err
}
if err := m.setHosts(nil); err != nil {
return err
}
if err := m.setPrimaryDNS(cfg.Nameservers, cfg.SearchDomains); err != nil {
return err
}
} else if m.nrptDB == nil {
return errors.New("cannot set per-domain resolvers on Windows 7")
} else {
if err := m.setSplitDNS(cfg.Nameservers, cfg.MatchDomains); err != nil {
return err
}
// Unset the resolver on the interface to ensure that we do not become
// the primary resolver. Although this is what we want, at the moment
// (2022-08-13) it causes single label resolutions from the OS resolver
// to wait for a MDNS response from the Tailscale interface.
// See #1659 and #5366 for more details.
//
// Still set search domains on the interface, since NRPT only handles
// query routing and not search domain expansion.
if err := m.setPrimaryDNS(nil, cfg.SearchDomains); err != nil {
return err
}
// As we are not the primary resolver in this setup, we need to
// explicitly set some single name hosts to ensure that we can resolve
// them quickly and get around the 2.3s delay that otherwise occurs due
// to multicast timeouts.
if err := m.setHosts(cfg.Hosts); err != nil {
return err
}
}
// Force DNS re-registration in Active Directory. What we actually
// care about is that this command invokes the undocumented hidden
// function that forces Windows to notice that adapter settings
// have changed, which makes the DNS settings actually take
// effect.
//
// This command can take a few seconds to run, so run it async, best effort.
//
// After re-registering DNS, also flush the DNS cache to clear out
// any cached split-horizon queries that are no longer the correct
// answer.
go func() {
t0 := time.Now()
m.logf("running ipconfig /registerdns ...")
cmd := exec.Command("ipconfig", "/registerdns")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
err := cmd.Run()
d := time.Since(t0).Round(time.Millisecond)
if err != nil {
m.logf("error running ipconfig /registerdns after %v: %v", d, err)
} else {
m.logf("ran ipconfig /registerdns in %v", d)
}
t0 = time.Now()
m.logf("running ipconfig /flushdns ...")
cmd = exec.Command("ipconfig", "/flushdns")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
err = cmd.Run()
d = time.Since(t0).Round(time.Millisecond)
if err != nil {
m.logf("error running ipconfig /flushdns after %v: %v", d, err)
} else {
m.logf("ran ipconfig /flushdns in %v", d)
}
}()
// On initial setup of WSL, the restart caused by --shutdown is slow,
// so we do it out-of-line.
if configureWSL() {
go func() {
if err := m.wslManager.SetDNS(cfg); err != nil {
m.logf("WSL SetDNS: %v", err) // continue
} else {
m.logf("WSL SetDNS: success")
}
}()
}
return nil
}
func (m *windowsManager) SupportsSplitDNS() bool {
return m.nrptDB != nil
}
func (m *windowsManager) Close() error {
err := m.SetDNS(OSConfig{})
if m.nrptDB != nil {
m.nrptDB.Close()
m.nrptDB = nil
}
return err
}
// disableDynamicUpdates sets the appropriate registry values to prevent the
// Windows DHCP client from sending dynamic DNS updates for our interface to
// AD domain controllers.
func (m *windowsManager) disableDynamicUpdates() error {
if err := m.setSingleDWORD(winutil.IPv4TCPIPInterfacePrefix, "DisableDynamicUpdate", 1); err != nil {
return err
}
if err := m.setSingleDWORD(winutil.IPv6TCPIPInterfacePrefix, "DisableDynamicUpdate", 1); err != nil {
return err
}
return nil
}
// setSingleDWORD opens the Registry Key in HKLM for the interface associated
// with the windowsManager and sets the "keyPrefix\value" to data.
func (m *windowsManager) setSingleDWORD(prefix winutil.RegistryPathPrefix, value string, data uint32) error {
k, err := m.openInterfaceKey(prefix)
if err != nil {
return err
}
defer k.Close()
return k.SetDWordValue(value, data)
}
// disableNetBIOS sets the appropriate registry values to prevent Windows from
// sending NetBIOS name resolution requests for our interface which we do not
// handle nor want to. By leaving it enabled and not handling it we introduce
// short-name resolution delays in certain conditions as Windows waits for
// NetBIOS responses from our interface (#1659).
//
// Further, LLMNR and NetBIOS are being deprecated anyway in favor of MDNS.
// https://techcommunity.microsoft.com/t5/networking-blog/aligning-on-mdns-ramping-down-netbios-name-resolution-and-llmnr/ba-p/3290816
func (m *windowsManager) disableNetBIOS() error {
return m.setSingleDWORD(winutil.NetBTInterfacePrefix, "NetbiosOptions", 2)
}
func (m *windowsManager) GetBaseConfig() (OSConfig, error) {
resolvers, err := m.getBasePrimaryResolver()
if err != nil {
return OSConfig{}, err
}
return OSConfig{
Nameservers: resolvers,
// Don't return any search domains here, because even Windows
// 7 correctly handles blending search domains from multiple
// sources, and any search domains we add here will get tacked
// onto the Tailscale config unnecessarily.
}, nil
}
// getBasePrimaryResolver returns a guess of the non-Tailscale primary
// resolver on the system.
// It's used on Windows 7 to emulate split DNS by trying to figure out
// what the "previous" primary resolver was. It might be wrong, or
// incomplete.
func (m *windowsManager) getBasePrimaryResolver() (resolvers []netip.Addr, err error) {
tsGUID, err := windows.GUIDFromString(m.guid)
if err != nil {
return nil, err
}
tsLUID, err := winipcfg.LUIDFromGUID(&tsGUID)
if err != nil {
return nil, err
}
ifrows, err := winipcfg.GetIPInterfaceTable(windows.AF_INET)
if err == windows.ERROR_NOT_FOUND {
// IPv4 seems disabled, try to get interface metrics from IPv6 instead.
ifrows, err = winipcfg.GetIPInterfaceTable(windows.AF_INET6)
}
if err != nil {
return nil, err
}
type candidate struct {
id winipcfg.LUID
metric uint32
}
var candidates []candidate
for _, row := range ifrows {
if !row.Connected {
continue
}
if row.InterfaceLUID == tsLUID {
continue
}
candidates = append(candidates, candidate{row.InterfaceLUID, row.Metric})
}
if len(candidates) == 0 {
// No resolvers set outside of Tailscale.
return nil, nil
}
sort.Slice(candidates, func(i, j int) bool { return candidates[i].metric < candidates[j].metric })
for _, candidate := range candidates {
ips, err := candidate.id.DNS()
if err != nil {
return nil, err
}
ipLoop:
for _, ip := range ips {
ip = ip.Unmap()
// Skip IPv6 site-local resolvers. These are an ancient
// and obsolete IPv6 RFC, which Windows still faithfully
// implements. The net result is that some low-metric
// interfaces can "have" DNS resolvers, but they're just
// site-local resolver IPs that don't go anywhere. So, we
// skip the site-local resolvers in order to find the
// first interface that has real DNS servers configured.
for _, sl := range siteLocalResolvers {
if ip.WithZone("") == sl {
continue ipLoop
}
}
resolvers = append(resolvers, ip)
}
if len(resolvers) > 0 {
// Found some resolvers, we're done.
break
}
}
return resolvers, nil
}
var siteLocalResolvers = []netip.Addr{
netip.MustParseAddr("fec0:0:0:ffff::1"),
netip.MustParseAddr("fec0:0:0:ffff::2"),
netip.MustParseAddr("fec0:0:0:ffff::3"),
}
func isWindows10OrBetter() bool {
key, err := registry.OpenKey(registry.LOCAL_MACHINE, versionKey, registry.READ)
if err != nil {
// Fail safe, assume old Windows.
return false
}
// This key above only exists in Windows 10 and above. Its mere
// presence is good enough.
if _, _, err := key.GetIntegerValue("CurrentMajorVersionNumber"); err != nil {
return false
}
return true
}