@@ -6,16 +6,19 @@ import (
6
6
"fmt"
7
7
"net"
8
8
"net/netip"
9
+ "os"
9
10
"strings"
11
+ "time"
10
12
11
13
"golang.org/x/sys/windows"
14
+ "golang.org/x/sys/windows/registry"
12
15
"golang.zx2c4.com/wireguard/tun"
13
16
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
14
17
18
+ "github.com/datawire/dlib/dcontext"
15
19
"github.com/datawire/dlib/derror"
16
20
"github.com/datawire/dlib/dlog"
17
21
"github.com/telepresenceio/telepresence/v2/pkg/proc"
18
- "github.com/telepresenceio/telepresence/v2/pkg/shellquote"
19
22
"github.com/telepresenceio/telepresence/v2/pkg/vif/buffer"
20
23
)
21
24
@@ -115,7 +118,17 @@ func (t *nativeDevice) removeSubnet(_ context.Context, subnet *net.IPNet) error
115
118
return t .getLUID ().DeleteIPAddress (prefixFromIPNet (subnet ))
116
119
}
117
120
118
- func (t * nativeDevice ) setDNS (ctx context.Context , server net.IP , domains []string ) (err error ) {
121
+ func (t * nativeDevice ) setDNS (ctx context.Context , clusterDomain string , server net.IP , searchList []string ) (err error ) {
122
+ // This function must not be interrupted by a context cancellation, so we give it a timeout instead.
123
+ parentCtx := ctx
124
+ ctx , cancel := context .WithCancel (dcontext .WithoutCancel (ctx ))
125
+ go func () {
126
+ <- parentCtx .Done ()
127
+ // Give this function some time to complete its task. Configuring DSN on windows is slow.
128
+ time .Sleep (10 * time .Second )
129
+ cancel ()
130
+ }()
131
+
119
132
ipFamily := func (ip net.IP ) winipcfg.AddressFamily {
120
133
f := winipcfg .AddressFamily (windows .AF_INET6 )
121
134
if ip4 := ip .To4 (); ip4 != nil {
@@ -130,46 +143,103 @@ func (t *nativeDevice) setDNS(ctx context.Context, server net.IP, domains []stri
130
143
_ = luid .FlushDNS (oldFamily )
131
144
}
132
145
}
133
- if err = luid .SetDNS (family , []netip.Addr {addrFromIP (server )}, domains ); err != nil {
146
+ serverStr := server .String ()
147
+ servers16 , err := windows .UTF16PtrFromString (serverStr )
148
+ if err != nil {
134
149
return err
135
150
}
151
+ searchList16 , err := windows .UTF16PtrFromString (strings .Join (searchList , "," ))
152
+ if err != nil {
153
+ return err
154
+ }
155
+ guid , err := luid .GUID ()
156
+ if err != nil {
157
+ return err
158
+ }
159
+ dnsInterfaceSettings := & winipcfg.DnsInterfaceSettings {
160
+ Version : winipcfg .DnsInterfaceSettingsVersion1 ,
161
+ Flags : winipcfg .DnsInterfaceSettingsFlagNameserver | winipcfg .DnsInterfaceSettingsFlagSearchList ,
162
+ NameServer : servers16 ,
163
+ SearchList : searchList16 ,
164
+ }
165
+ if family == windows .AF_INET6 {
166
+ dnsInterfaceSettings .Flags |= winipcfg .DnsInterfaceSettingsFlagIPv6
167
+ }
168
+ if err = winipcfg .SetInterfaceDnsSettings (* guid , dnsInterfaceSettings ); err != nil {
169
+ return err
170
+ }
171
+
172
+ // Unless we also update the global DNS search path, the one for the device doesn't work on some platforms.
173
+ // This behavior is mainly observed on Windows Server editions.
136
174
137
- // On some systems (e.g. CircleCI but not josecv's windows box), SetDNS isn't enough to allow the domains to be resolved,
138
- // and the network adapter's domain has to be set explicitly.
139
- // It's actually way easier to do this via powershell than any system calls that can be run from go code
140
- domain := ""
141
- if len (domains ) > 0 {
142
- // Quote the domain to prevent powershell injection
143
- domain = shellquote .ShellArgsString ([]string {strings .TrimSuffix (domains [0 ], "." )})
144
- }
145
- // It's apparently well known that WMI queries can hang under various conditions, so we add a timeout here to prevent hanging the daemon
146
- // Fun fact: terminating the context that powershell is running in will not stop a hanging WMI call (!) perhaps because it is considered uninterruptible
147
- // For more on WMI queries hanging, see:
148
- // * http://www.yusufozturk.info/windows-powershell/how-to-avoid-wmi-query-hangs-in-powershell.html
149
- // * https://theolddogscriptingblog.wordpress.com/2012/05/11/wmi-hangs-and-how-to-avoid-them/
150
- // * https://stackoverflow.com/questions/24294408/gwmi-query-hangs-powershell-script
151
- // * http://use-powershell.blogspot.com/2018/03/get-wmiobject-hangs.html
152
- pshScript := fmt .Sprintf (`
153
- $job = Get-WmiObject Win32_NetworkAdapterConfiguration -filter "interfaceindex='%d'" -AsJob | Wait-Job -Timeout 30
154
- if ($job.State -ne 'Completed') {
155
- throw "timed out getting network adapter after 30 seconds."
156
- }
157
- $obj = $job | Receive-Job
158
- $job = Invoke-WmiMethod -InputObject $obj -Name SetDNSDomain -ArgumentList "%s" -AsJob | Wait-Job -Timeout 30
159
- if ($job.State -ne 'Completed') {
160
- throw "timed out setting network adapter DNS Domain after 30 seconds."
161
- }
162
- $job | Receive-Job
163
- ` , t .interfaceIndex , domain )
164
- cmd := proc .CommandContext (ctx , "powershell.exe" , "-NoProfile" , "-NonInteractive" , pshScript )
165
- cmd .DisableLogging = true // disable chatty logging
166
- dlog .Debugf (ctx , "Calling powershell's SetDNSDomain %q" , domain )
167
- if err := cmd .Run (); err != nil {
168
- // Log the error, but don't actually fail on it: This is all just a fallback for SetDNS, so the domains might actually be working
169
- dlog .Errorf (ctx , "Failed to set NetworkAdapterConfiguration DNS Domain: %v. Will proceed, but namespace mapping might not be functional." , err )
175
+ // Retrieve the current global search paths so that paths that aren't related to
176
+ // the cluster domain (i.e. not managed by us) can be retained.
177
+ gss , err := getGlobalSearchList ()
178
+ if err != nil {
179
+ return err
180
+ }
181
+ if oldLen := len (gss ); oldLen > 0 {
182
+ // Windows does not use a dot suffix in the search path.
183
+ clusterDomain = strings .TrimSuffix (clusterDomain , "." )
184
+
185
+ // Put our new search path in front of other entries. Then include those
186
+ // that don't end with our cluster domain (these are entries that aren't
187
+ // managed by Telepresence).
188
+ newGss := make ([]string , len (searchList ), oldLen )
189
+ copy (newGss , searchList )
190
+ for _ , gs := range gss {
191
+ if ! strings .HasSuffix (gs , clusterDomain ) {
192
+ newGss = append (newGss , gs )
193
+ }
194
+ }
195
+ gss = newGss
196
+ } else {
197
+ gss = searchList
170
198
}
171
199
t .dns = server
172
- return nil
200
+ return setGlobalSearchList (ctx , gss )
201
+ }
202
+
203
+ func psList (values []string ) string {
204
+ var sb strings.Builder
205
+ sb .WriteString ("@(" )
206
+ for i , gs := range values {
207
+ if i > 0 {
208
+ sb .WriteByte (',' )
209
+ }
210
+ sb .WriteByte ('"' )
211
+ sb .WriteString (strings .TrimSuffix (gs , "." ))
212
+ sb .WriteByte ('"' )
213
+ }
214
+ sb .WriteByte (')' )
215
+ return sb .String ()
216
+ }
217
+
218
+ func getGlobalSearchList () ([]string , error ) {
219
+ rk , err := registry .OpenKey (registry .LOCAL_MACHINE , `System\CurrentControlSet\Services\Tcpip\Parameters` , registry .QUERY_VALUE )
220
+ if err != nil {
221
+ if os .IsNotExist (err ) {
222
+ err = nil
223
+ }
224
+ return nil , err
225
+ }
226
+ csv , _ , err := rk .GetStringValue ("SearchList" )
227
+ if err != nil {
228
+ if os .IsNotExist (err ) {
229
+ err = nil
230
+ }
231
+ return nil , err
232
+ }
233
+ if csv == "" {
234
+ return nil , nil
235
+ }
236
+ return strings .Split (csv , "," ), nil
237
+ }
238
+
239
+ func setGlobalSearchList (ctx context.Context , gss []string ) error {
240
+ cmd := proc .CommandContext (ctx , "powershell.exe" , "Set-DnsClientGlobalSetting" , "-SuffixSearchList" , psList (gss ))
241
+ _ , err := proc .CaptureErr (ctx , cmd )
242
+ return err
173
243
}
174
244
175
245
func (t * nativeDevice ) setMTU (int ) error {
0 commit comments