diff --git a/config/dns.json b/config/dns.json index 6c2ffbbd..02499902 100644 --- a/config/dns.json +++ b/config/dns.json @@ -3,48 +3,56 @@ "Primary": "8.8.8.8", "Secondary": "8.8.4.4", "Primary6": "2001:4860:4860::8888", - "Secondary6": "2001:4860:4860::8844" + "Secondary6": "2001:4860:4860::8844", + "DohTemplate": "https://dns.google/dns-query" }, "Cloudflare":{ "Primary": "1.1.1.1", "Secondary": "1.0.0.1", "Primary6": "2606:4700:4700::1111", - "Secondary6": "2606:4700:4700::1001" + "Secondary6": "2606:4700:4700::1001", + "DohTemplate": "https://cloudflare-dns.com/dns-query" }, "Cloudflare_Malware":{ "Primary": "1.1.1.2", "Secondary": "1.0.0.2", "Primary6": "2606:4700:4700::1112", - "Secondary6": "2606:4700:4700::1002" + "Secondary6": "2606:4700:4700::1002", + "DohTemplate": "https://security.cloudflare-dns.com/dns-query" }, "Cloudflare_Malware_Adult":{ "Primary": "1.1.1.3", "Secondary": "1.0.0.3", "Primary6": "2606:4700:4700::1113", - "Secondary6": "2606:4700:4700::1003" + "Secondary6": "2606:4700:4700::1003", + "DohTemplate": "https://family.cloudflare-dns.com/dns-query" }, "Open_DNS":{ "Primary": "208.67.222.222", "Secondary": "208.67.220.220", "Primary6": "2620:119:35::35", - "Secondary6": "2620:119:53::53" + "Secondary6": "2620:119:53::53", + "DohTemplate": "https://doh.opendns.com/dns-query" }, "Quad9":{ "Primary": "9.9.9.9", "Secondary": "149.112.112.112", "Primary6": "2620:fe::fe", - "Secondary6": "2620:fe::9" + "Secondary6": "2620:fe::9", + "DohTemplate": "https://dns.quad9.net/dns-query" }, "AdGuard_Ads_Trackers":{ "Primary": "94.140.14.14", "Secondary": "94.140.15.15", "Primary6": "2a10:50c0::ad1:ff", - "Secondary6": "2a10:50c0::ad2:ff" + "Secondary6": "2a10:50c0::ad2:ff", + "DohTemplate": "https://dns.adguard-dns.com/dns-query" }, "AdGuard_Ads_Trackers_Malware_Adult":{ "Primary": "94.140.14.15", "Secondary": "94.140.15.16", "Primary6": "2a10:50c0::bad1:ff", - "Secondary6": "2a10:50c0::bad2:ff" + "Secondary6": "2a10:50c0::bad2:ff", + "DohTemplate": "https://family.adguard-dns.com/dns-query" } } diff --git a/functions/private/Set-WinUtilDNS.ps1 b/functions/private/Set-WinUtilDNS.ps1 index f12da27d..b7e5f86c 100644 --- a/functions/private/Set-WinUtilDNS.ps1 +++ b/functions/private/Set-WinUtilDNS.ps1 @@ -33,23 +33,71 @@ function Set-WinUtilDNS { } } + $dohSupported = [bool](Get-Command Add-DnsClientDohServerAddress -ErrorAction SilentlyContinue) + $dnscacheBase = "HKLM:\System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters" + Foreach ($Adapter in $Adapters) { + $interfaceParams = "$dnscacheBase\$($Adapter.InterfaceGuid)" + if($DNSProvider -eq "DHCP") { Write-WinUtilLog -Component "DNS" -Message "Resetting DNS to DHCP on adapter $($Adapter.Name) (ifIndex: $($Adapter.ifIndex))." Set-DnsClientServerAddress -InterfaceIndex $Adapter.ifIndex -ResetServerAddresses netsh interface ip set dnsservers name="$($Adapter.Name)" source=dhcp netsh interface ipv6 set dnsservers name="$($Adapter.Name)" source=dhcp + + $dohInterfaceSettings = "$interfaceParams\DohInterfaceSettings" + if (Test-Path $dohInterfaceSettings) { + if ($dohSupported) { + $dohServerAddresses = @( + Get-ChildItem -Path "$dohInterfaceSettings\Doh" -ErrorAction SilentlyContinue + Get-ChildItem -Path "$dohInterfaceSettings\Doh6" -ErrorAction SilentlyContinue + ) | Select-Object -ExpandProperty PSChildName -Unique + + foreach ($ip in $dohServerAddresses) { + if (Get-DnsClientDohServerAddress -ServerAddress $ip -ErrorAction SilentlyContinue) { + Write-WinUtilLog -Component "DNS" -Message "Removing DoH registration for $ip." + Remove-DnsClientDohServerAddress -ServerAddress $ip -Confirm:$false -ErrorAction Stop + } + } + } + + Remove-Item -Path $dohInterfaceSettings -Recurse -Force -ErrorAction SilentlyContinue + } } else { Write-WinUtilLog -Component "DNS" -Message "Setting IPv4 DNS on adapter $($Adapter.Name) (ifIndex: $($Adapter.ifIndex)) to $($dns.Primary), $($dns.Secondary)." Set-DnsClientServerAddress -InterfaceIndex $Adapter.ifIndex -ServerAddresses ($dns.Primary, $dns.Secondary) Write-WinUtilLog -Component "DNS" -Message "Setting IPv6 DNS on adapter $($Adapter.Name) (ifIndex: $($Adapter.ifIndex)) to $($dns.Primary6), $($dns.Secondary6)." Set-DnsClientServerAddress -InterfaceIndex $Adapter.ifIndex -ServerAddresses ($dns.Primary6, $dns.Secondary6) + + if ($dohSupported -and $dns.DohTemplate) { + $ips = @($dns.Primary, $dns.Secondary, $dns.Primary6, $dns.Secondary6) | Where-Object { $_ } + foreach ($ip in $ips) { + $existing = Get-DnsClientDohServerAddress -ServerAddress $ip -ErrorAction SilentlyContinue + if ($existing) { + Set-DnsClientDohServerAddress -ServerAddress $ip -DohTemplate $dns.DohTemplate -AllowFallbackToUdp $false -AutoUpgrade $true -ErrorAction Stop + } else { + Write-WinUtilLog -Component "DNS" -Message "Registering DoH template for $ip." + Add-DnsClientDohServerAddress -ServerAddress $ip -DohTemplate $dns.DohTemplate -AllowFallbackToUdp $false -AutoUpgrade $true -ErrorAction Stop + } + + $leaf = if ($ip.Contains(':')) { 'Doh6' } else { 'Doh' } + $regPath = "$interfaceParams\DohInterfaceSettings\$leaf\$ip" + + if (-not (Test-Path $regPath)) { + New-Item -Path $regPath -Force -ErrorAction Stop | Out-Null + } + New-ItemProperty -Path $regPath -Name "DohFlags" -Value 1 -PropertyType QWord -Force -ErrorAction Stop | Out-Null + } + } } } + if ($DNSProvider -ne "DHCP" -and $dohSupported -and $dns.DohTemplate) { + Clear-DnsClientCache + } Write-WinUtilLog -Component "DNS" -Message "DNS provider change completed: $DNSProvider" } catch { - Write-Warning "Unable to set DNS Provider due to an unhandled exception." - Write-Warning $psitem.Exception.StackTrace - Write-WinUtilLog -Level "ERROR" -Component "DNS" -Message "Unable to set DNS provider $DNSProvider`: $($psitem.Exception.Message)" + Write-Warning "DNS provider $DNSProvider was not completed because an error occurred." + Write-Warning $psitem.Exception.Message + Write-WinUtilLog -Level "ERROR" -Component "DNS" -Message "DNS provider $DNSProvider was not completed: $($psitem.Exception.Message)" } } diff --git a/pester/dns.Tests.ps1 b/pester/dns.Tests.ps1 index ff59e6d9..c7ab0896 100644 --- a/pester/dns.Tests.ps1 +++ b/pester/dns.Tests.ps1 @@ -23,6 +23,20 @@ BeforeAll { param($Message, $Level, $Component) } + function Add-DnsClientDohServerAddress { + param($ServerAddress, $DohTemplate, $AllowFallbackToUdp, $AutoUpgrade, $ErrorAction) + } + function Set-DnsClientDohServerAddress { + param($ServerAddress, $DohTemplate, $AllowFallbackToUdp, $AutoUpgrade, $ErrorAction) + } + function Get-DnsClientDohServerAddress { + param($ServerAddress, $ErrorAction) + } + function Remove-DnsClientDohServerAddress { + param($ServerAddress, $Confirm, $ErrorAction) + } + function Clear-DnsClientCache { } + . (Join-Path $script:repoRoot "functions\private\Set-WinUtilDNS.ps1") } @@ -36,6 +50,7 @@ Describe "Set-WinUtilDNS" { Secondary = "1.0.0.1" Primary6 = "2606:4700:4700::1111" Secondary6 = "2606:4700:4700::1001" + DohTemplate = "https://cloudflare-dns.com/dns-query" } } } @@ -46,6 +61,7 @@ Describe "Set-WinUtilDNS" { Name = "Ethernet" Status = "Up" ifIndex = 7 + InterfaceGuid = "{1234-5678-90AB-CDEF}" } } Mock Set-DnsClientServerAddress { } @@ -53,13 +69,25 @@ Describe "Set-WinUtilDNS" { Mock Write-WinUtilLog { } Mock Write-Warning { } Mock Write-Host { } + + Mock Get-Command { return $true } -ParameterFilter { $Name -eq "Add-DnsClientDohServerAddress" } + Mock Add-DnsClientDohServerAddress { } + Mock Set-DnsClientDohServerAddress { } + Mock Get-DnsClientDohServerAddress { return $null } + Mock Remove-DnsClientDohServerAddress { } + Mock Test-Path { return $false } + Mock Get-ChildItem { } + Mock New-Item { } + Mock New-ItemProperty { } + Mock Remove-Item { } + Mock Clear-DnsClientCache { } } AfterEach { Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue } - It "sets IPv4 and IPv6 DNS server addresses separately" { + It "sets IPv4 and IPv6 DNS server addresses separately and applies DoH templates" { Set-WinUtilDNS -DNSProvider "Cloudflare" Should -Invoke -CommandName Set-DnsClientServerAddress -Times 1 -Exactly -ParameterFilter { @@ -77,9 +105,51 @@ Describe "Set-WinUtilDNS" { Should -Invoke -CommandName Set-DnsClientServerAddress -Times 0 -Exactly -ParameterFilter { $ServerAddresses.Count -eq 4 } + Should -Invoke -CommandName Add-DnsClientDohServerAddress -Times 4 -Exactly + Should -Invoke -CommandName New-ItemProperty -Times 4 -ParameterFilter { + $Name -eq "DohFlags" -and $Value -eq 1 + } + Should -Invoke -CommandName Clear-DnsClientCache -Times 1 -Exactly } - It "resets DNS to DHCP for IPv4 and IPv6" { + It "updates an existing DoH entry with the selected provider settings" { + Mock Get-DnsClientDohServerAddress { + if ($ServerAddress -eq "1.1.1.1") { + return [pscustomobject]@{ ServerAddress = $ServerAddress } + } + return $null + } + + Set-WinUtilDNS -DNSProvider "Cloudflare" + + Should -Invoke -CommandName Set-DnsClientDohServerAddress -Times 1 -Exactly -ParameterFilter { + $ServerAddress -eq "1.1.1.1" -and + $DohTemplate -eq "https://cloudflare-dns.com/dns-query" -and + $AllowFallbackToUdp -eq $false -and + $AutoUpgrade -eq $true + } + Should -Invoke -CommandName Add-DnsClientDohServerAddress -Times 3 -Exactly + } + + It "resets DNS to DHCP and removes the applied DoH configuration" { + Mock Test-Path { return $true } -ParameterFilter { $Path -like "*DohInterfaceSettings*" } + Mock Get-ChildItem { + if ($Path -like "*\Doh6") { + return @( + [pscustomobject]@{ PSChildName = "2606:4700:4700::1111" } + [pscustomobject]@{ PSChildName = "2606:4700:4700::1001" } + ) + } + + return @( + [pscustomobject]@{ PSChildName = "1.1.1.1" } + [pscustomobject]@{ PSChildName = "1.0.0.1" } + ) + } + Mock Get-DnsClientDohServerAddress { + [pscustomobject]@{ ServerAddress = $ServerAddress } + } + Set-WinUtilDNS -DNSProvider "DHCP" Should -Invoke -CommandName Set-DnsClientServerAddress -Times 1 -Exactly -ParameterFilter { @@ -102,6 +172,10 @@ Describe "Set-WinUtilDNS" { $Arguments[4] -eq "name=Ethernet" -and $Arguments[5] -eq "source=dhcp" } + Should -Invoke -CommandName Remove-Item -Times 1 -Exactly -ParameterFilter { + $Path -like "*DohInterfaceSettings*" -and $Recurse -eq $true -and $Force -eq $true + } + Should -Invoke -CommandName Remove-DnsClientDohServerAddress -Times 4 -Exactly } It "catches DNS setter failures so the tweak runspace can continue" { @@ -112,7 +186,7 @@ Describe "Set-WinUtilDNS" { Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter { $Level -eq "ERROR" -and $Component -eq "DNS" -and - $Message -like "Unable to set DNS provider Cloudflare*" + $Message -like "DNS provider Cloudflare was not completed: *" } } }