github.com/apernet/sing-tun@v0.2.6-0.20240323130332-b9f6511036ad/internal/winipcfg/netsh.go (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  package winipcfg
     7  
     8  import (
     9  	"bytes"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net/netip"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"strings"
    17  	"syscall"
    18  
    19  	"golang.org/x/sys/windows"
    20  	"golang.org/x/sys/windows/registry"
    21  )
    22  
    23  func runNetsh(cmds []string) error {
    24  	system32, err := windows.GetSystemDirectory()
    25  	if err != nil {
    26  		return err
    27  	}
    28  	cmd := exec.Command(filepath.Join(system32, "netsh.exe"))
    29  	cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
    30  
    31  	stdin, err := cmd.StdinPipe()
    32  	if err != nil {
    33  		return fmt.Errorf("runNetsh stdin pipe - %w", err)
    34  	}
    35  	go func() {
    36  		defer stdin.Close()
    37  		io.WriteString(stdin, strings.Join(append(cmds, "exit\r\n"), "\r\n"))
    38  	}()
    39  	output, err := cmd.CombinedOutput()
    40  	// Horrible kludges, sorry.
    41  	cleaned := bytes.ReplaceAll(output, []byte{'\r', '\n'}, []byte{'\n'})
    42  	cleaned = bytes.ReplaceAll(cleaned, []byte("netsh>"), []byte{})
    43  	cleaned = bytes.ReplaceAll(cleaned, []byte("There are no Domain Name Servers (DNS) configured on this computer."), []byte{})
    44  	cleaned = bytes.TrimSpace(cleaned)
    45  	if len(cleaned) != 0 && err == nil {
    46  		return fmt.Errorf("netsh: %#q", string(cleaned))
    47  	} else if err != nil {
    48  		return fmt.Errorf("netsh: %v: %#q", err, string(cleaned))
    49  	}
    50  	return nil
    51  }
    52  
    53  const (
    54  	netshCmdTemplateFlush4              = "interface ipv4 set dnsservers name=%d source=static address=none validate=no"
    55  	netshCmdTemplateFlush6              = "interface ipv6 set dnsservers name=%d source=static address=none validate=no"
    56  	netshCmdTemplateAdd4                = "interface ipv4 add dnsservers name=%d address=%s validate=no"
    57  	netshCmdTemplateAdd6                = "interface ipv6 add dnsservers name=%d address=%s validate=no"
    58  	netshCmdTemplateDisableRegistration = "interface ipv6 set dnsservers name=%d register=none"
    59  )
    60  
    61  func (luid LUID) fallbackSetDNSForFamily(family AddressFamily, dnses []netip.Addr) error {
    62  	var templateFlush string
    63  	if family == windows.AF_INET {
    64  		templateFlush = netshCmdTemplateFlush4
    65  	} else if family == windows.AF_INET6 {
    66  		templateFlush = netshCmdTemplateFlush6
    67  	}
    68  
    69  	cmds := make([]string, 0, 1+len(dnses))
    70  	ipif, err := luid.IPInterface(family)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	cmds = append(cmds, fmt.Sprintf(templateFlush, ipif.InterfaceIndex))
    75  	for i := 0; i < len(dnses); i++ {
    76  		if dnses[i].Is4() && family == windows.AF_INET {
    77  			cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd4, ipif.InterfaceIndex, dnses[i].String()))
    78  		} else if dnses[i].Is6() && family == windows.AF_INET6 {
    79  			cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd6, ipif.InterfaceIndex, dnses[i].String()))
    80  		}
    81  	}
    82  	return runNetsh(cmds)
    83  }
    84  
    85  func (luid LUID) fallbackSetDNSDomain(domain string) error {
    86  	guid, err := luid.GUID()
    87  	if err != nil {
    88  		return fmt.Errorf("Error converting luid to guid: %w", err)
    89  	}
    90  	key, err := registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Adapters\\%v", guid), registry.QUERY_VALUE)
    91  	if err != nil {
    92  		return fmt.Errorf("Error opening adapter-specific TCP/IP network registry key: %w", err)
    93  	}
    94  	paths, _, err := key.GetStringsValue("IpConfig")
    95  	key.Close()
    96  	if err != nil {
    97  		return fmt.Errorf("Error reading IpConfig registry key: %w", err)
    98  	}
    99  	if len(paths) == 0 {
   100  		return errors.New("No TCP/IP interfaces found on adapter")
   101  	}
   102  	key, err = registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Services\\%s", paths[0]), registry.SET_VALUE)
   103  	if err != nil {
   104  		return fmt.Errorf("Unable to open TCP/IP network registry key: %w", err)
   105  	}
   106  	err = key.SetStringValue("Domain", domain)
   107  	key.Close()
   108  	return err
   109  }
   110  
   111  func (luid LUID) fallbackDisableDNSRegistration() error {
   112  	// the DNS registration setting is shared for both IPv4 and IPv6
   113  	ipif, err := luid.IPInterface(windows.AF_INET)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	cmd := fmt.Sprintf(netshCmdTemplateDisableRegistration, ipif.InterfaceIndex)
   118  	return runNetsh([]string{cmd})
   119  }