feat(dns): Add real-time DNS speed benchmark and 'Fastest' auto-selection option (#4935)

* feat(dns): Add real-time DNS speed benchmark and 'Fastest' auto-selection option

* fix(dns): Address CodeRabbit & Codex PR review feedback

* fix(ci): Remove .biomeignore to keep repository root clean

* Fix Fastest DNS eligibility and failed probe handling

---------

Co-authored-by: Chris Titus <contact@christitus.com>
This commit is contained in:
KEERTHIVASAN K
2026-09-19 18:16:16 -05:00
committed by GitHub
co-authored by Chris Titus
parent f65d2631a0
commit 8e3998d9fd
8 changed files with 213 additions and 1 deletions
+1
View File
@@ -81,6 +81,7 @@ Because the final script is concatenated, code cannot rely on runtime module imp
## Configuration Contract
- Config files must remain valid JSON and compile cleanly through `ConvertFrom-Json`.
- `config/dns.json` opts unfiltered providers into Fastest selection with `BenchmarkEligible: true`; missing or false values exclude a provider from the TCP latency benchmark.
- `config/applications.json` defines installable applications; each entry includes the fields expected by tests and UI code, such as package manager IDs, category, display content, description, and link.
- `config/tweaks.json` defines Windows tweaks; registry and service changes include original values or original states when applicable so undo workflows can restore user systems.
- Preset and navigation files reference valid config keys. Renaming a config key requires updating all presets, UI references, docs, and code paths together.
+2
View File
@@ -1,5 +1,6 @@
{
"Google":{
"BenchmarkEligible": true,
"Primary": "8.8.8.8",
"Secondary": "8.8.4.4",
"Primary6": "2001:4860:4860::8888",
@@ -7,6 +8,7 @@
"DohTemplate": "https://dns.google/dns-query"
},
"Cloudflare":{
"BenchmarkEligible": true,
"Primary": "1.1.1.1",
"Secondary": "1.0.0.1",
"Primary6": "2606:4700:4700::1111",
+1 -1
View File
@@ -1885,7 +1885,7 @@
"category": "z__Advanced Tweaks - CAUTION",
"panel": "1",
"Type": "Combobox",
"ComboItems": "Default DHCP Google Cloudflare Cloudflare_Malware Cloudflare_Malware_Adult Open_DNS Quad9 AdGuard_Ads_Trackers AdGuard_Ads_Trackers_Malware_Adult",
"ComboItems": "Default DHCP Fastest Google Cloudflare Cloudflare_Malware Cloudflare_Malware_Adult Open_DNS Quad9 AdGuard_Ads_Trackers AdGuard_Ads_Trackers_Malware_Adult",
"link": "https://winutil.christitus.com/code-reference/tweaks/z--advanced-tweaks---caution/changedns"
},
"WPFAddUltPerf": {
+1
View File
@@ -65,6 +65,7 @@ Use the DNS section to switch both IPv4 and IPv6 DNS providers without editing a
* **Default**: Uses the default DNS settings configured by your ISP or network.
* **DHCP**: Automatically acquires DNS settings from the DHCP server.
* **Fastest**: Compares TCP port 53 connection times to Google and Cloudflare, then applies the quicker provider. Filtering providers are excluded. If both probes fail, existing DNS settings are preserved. The scan takes up to about three seconds; connection time is only a rough latency estimate, not a DNS lookup or DNS-over-HTTPS performance test.
* [**Google**](https://developers.google.com/speed/public-dns?hl=en): A reliable and fast DNS service provided by Google.
* [**Cloudflare**](https://developers.cloudflare.com/1.1.1.1/): Known for speed and privacy, Cloudflare DNS is a popular choice for enhancing internet performance.
* [**Cloudflare_Malware**](https://developers.cloudflare.com/1.1.1.1/setup/#:~:text=Use%20the%20following%20DNS%20resolvers%20to%20block%20malicious%20content%3A): Provides additional protection by blocking malware sites.
@@ -0,0 +1,84 @@
function Get-WinUtilDNSBenchmark {
<#
.SYNOPSIS
Benchmarks neutral DNS providers by measuring TCP port 53 latency (RTT in ms) to determine the fastest DNS server.
.PARAMETER TimeoutMs
Maximum timeout in milliseconds for each connection test. Default is 1500ms.
.OUTPUTS
Array of PSCustomObjects containing Provider, PrimaryIP, and LatencyMs sorted by lowest latency.
.EXAMPLE
$results = Get-WinUtilDNSBenchmark
$fastest = $results[0]
#>
[CmdletBinding()]
param(
[ValidateRange(1, 9998)]
[int]$TimeoutMs = 1500
)
Write-WinUtilLog -Component "DNS" -Message "Starting DNS latency benchmark scan (TCP port 53)..."
$dnsConfigs = $sync.configs.dns
if ($null -eq $dnsConfigs) {
Write-Warning "DNS configurations not found in `$sync.configs.dns."
Write-WinUtilLog -Level "ERROR" -Component "DNS" -Message "DNS configurations not found in `$sync.configs.dns."
return @()
}
$results = [System.Collections.Generic.List[PSObject]]::new()
foreach ($prop in $dnsConfigs.PSObject.Properties) {
$providerName = $prop.Name
$primaryIp = $prop.Value.Primary
if (-not $primaryIp) { continue }
# Providers must explicitly opt in so new filtering services are never auto-selected.
if ($prop.Value.BenchmarkEligible -ne $true) {
continue
}
$latency = 9999
$client = $null
try {
$client = New-Object System.Net.Sockets.TcpClient
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$asyncResult = $client.BeginConnect($primaryIp, 53, $null, $null)
$success = $asyncResult.AsyncWaitHandle.WaitOne($TimeoutMs, $false)
$stopwatch.Stop()
if ($success) {
$client.EndConnect($asyncResult)
$latency = [int]$stopwatch.ElapsedMilliseconds
} else {
$latency = 9999
}
} catch {
$latency = 9999
} finally {
if ($null -ne $client) {
$client.Dispose()
}
}
$results.Add([PSCustomObject]@{
Provider = $providerName
PrimaryIP = $primaryIp
LatencyMs = $latency
})
}
$sortedResults = @($results | Sort-Object LatencyMs)
if ($sortedResults.Count -gt 0 -and $sortedResults[0].LatencyMs -lt 9999) {
$fastest = $sortedResults[0]
Write-WinUtilLog -Component "DNS" -Message "DNS Benchmark completed. Fastest neutral provider: $($fastest.Provider) ($($fastest.LatencyMs) ms)"
} else {
Write-WinUtilLog -Component "DNS" -Message "DNS Benchmark completed. Could not determine latency for providers."
}
return $sortedResults
}
+15
View File
@@ -18,6 +18,21 @@ function Set-WinUtilDNS {
return $true
}
if($DNSProvider -eq "Fastest") {
Write-WinUtilLog -Component "DNS" -Message "Auto-detecting fastest DNS provider via latency benchmark..."
$benchmark = Get-WinUtilDNSBenchmark
$validFastest = $benchmark | Where-Object { $_.LatencyMs -lt 9999 } | Select-Object -First 1
if ($validFastest) {
$DNSProvider = $validFastest.Provider
Write-Host "Auto-selected fastest DNS provider: $DNSProvider ($($validFastest.LatencyMs) ms)"
Write-WinUtilLog -Component "DNS" -Message "Auto-selected fastest DNS provider: $DNSProvider ($($validFastest.LatencyMs) ms)"
} else {
Write-Warning "Could not measure DNS latency to any provider; keeping current network adapter DNS settings."
Write-WinUtilLog -Component "DNS" -Message "Benchmark timeout or all probes failed; aborting DNS change to preserve existing settings."
return $false
}
}
try {
$Adapters = Get-NetAdapter | Where-Object {$_.Status -eq "Up"}
Write-Host "Ensuring DNS is set to $DNSProvider on the following interfaces:"
+79
View File
@@ -0,0 +1,79 @@
BeforeAll {
. "$PSScriptRoot/../functions/private/Get-WinUtilDNSBenchmark.ps1"
function Write-WinUtilLog { param($Message, $Level, $Component) }
}
Describe 'Get-WinUtilDNSBenchmark' {
BeforeEach {
$script:sync = @{ configs = @{ dns = Get-Content "$PSScriptRoot/../config/dns.json" -Raw | ConvertFrom-Json } }
$script:probeCompleted = $true
$script:connectFails = $false
$script:disposed = 0
$script:ended = 0
$script:probed = @()
Mock Write-WinUtilLog { }
Mock New-Object {
$client = [pscustomobject]@{}
$client | Add-Member ScriptMethod BeginConnect {
param($Address, $Port, $Callback, $State)
$script:probed += $Address
$handle = [pscustomobject]@{}
$handle | Add-Member ScriptMethod WaitOne { param($Timeout, $ExitContext) return $script:probeCompleted }
return [pscustomobject]@{ AsyncWaitHandle = $handle }
}
$client | Add-Member ScriptMethod EndConnect {
param($Result)
$script:ended++
if ($script:connectFails) { throw 'Connection refused' }
}
$client | Add-Member ScriptMethod Dispose { $script:disposed++ }
return $client
} -ParameterFilter { $TypeName -eq 'System.Net.Sockets.TcpClient' }
}
AfterEach {
Remove-Variable sync -Scope Script
}
It 'only probes explicitly eligible unfiltered providers, including when an unknown provider is added' {
$script:sync.configs.dns | Add-Member NoteProperty UnknownProvider ([pscustomobject]@{ Primary = '192.0.2.1' })
$results = @(Get-WinUtilDNSBenchmark)
$results.Count | Should -Be 2
$results.Provider | Should -Contain 'Google'
$results.Provider | Should -Contain 'Cloudflare'
$script:probed.Count | Should -Be 2
$script:probed | Should -Contain '8.8.8.8'
$script:probed | Should -Contain '1.1.1.1'
@($results | Where-Object LatencyMs -ge 9999).Count | Should -Be 0
$script:ended | Should -Be 2
$script:disposed | Should -Be 2
}
It 'disposes timed out probes and marks them unavailable' {
$script:probeCompleted = $false
$results = @(Get-WinUtilDNSBenchmark)
@($results | Where-Object LatencyMs -eq 9999).Count | Should -Be 2
$script:disposed | Should -Be 2
$script:ended | Should -Be 0
}
It 'does not treat EndConnect failures as successful latency measurements' {
$script:connectFails = $true
$results = @(Get-WinUtilDNSBenchmark)
@($results | Where-Object LatencyMs -eq 9999).Count | Should -Be 2
$script:disposed | Should -Be 2
$script:ended | Should -Be 2
}
It 'returns no results when no providers are eligible' {
$script:sync.configs.dns.Google.BenchmarkEligible = $false
$script:sync.configs.dns.Cloudflare.BenchmarkEligible = $false
@(Get-WinUtilDNSBenchmark).Count | Should -Be 0
Should -Invoke New-Object -Times 0 -Exactly
}
It 'rejects infinite or out-of-range timeouts' {
{ Get-WinUtilDNSBenchmark -TimeoutMs -1 } | Should -Throw
{ Get-WinUtilDNSBenchmark -TimeoutMs 10000 } | Should -Throw
}
}
+30
View File
@@ -39,6 +39,7 @@ BeforeAll {
function Clear-DnsClientCache { }
. (Join-Path $script:repoRoot "functions\private\Set-WinUtilDNS.ps1")
. (Join-Path $script:repoRoot "functions\private\Get-WinUtilDNSBenchmark.ps1")
}
Describe "Set-WinUtilDNS" {
@@ -131,6 +132,35 @@ Describe "Set-WinUtilDNS" {
Should -Invoke -CommandName Clear-DnsClientCache -Times 1 -Exactly
}
It "preserves DNS when Fastest has no successful probes" {
Mock Get-WinUtilDNSBenchmark {
[pscustomobject]@{ Provider = 'Cloudflare'; LatencyMs = 9999 }
}
Set-WinUtilDNS -DNSProvider 'Fastest' | Should -BeFalse
Should -Invoke Get-NetAdapter -Times 0 -Exactly
Should -Invoke Set-DnsClientServerAddress -Times 0 -Exactly
Should -Invoke New-ItemProperty -Times 0 -Exactly
}
It "preserves DNS when Fastest returns no results" {
Mock Get-WinUtilDNSBenchmark { }
Set-WinUtilDNS -DNSProvider 'Fastest' | Should -BeFalse
Should -Invoke Get-NetAdapter -Times 0 -Exactly
Should -Invoke Set-DnsClientServerAddress -Times 0 -Exactly
}
It "applies the successful Fastest provider through the existing DNS and DoH path" {
Mock Get-WinUtilDNSBenchmark {
[pscustomobject]@{ Provider = 'Cloudflare'; LatencyMs = 12 }
[pscustomobject]@{ Provider = 'Google'; LatencyMs = 9999 }
}
Set-WinUtilDNS -DNSProvider 'Fastest' | Should -BeTrue
Should -Invoke Set-DnsClientServerAddress -Times 1 -Exactly -ParameterFilter {
$ServerAddresses[0] -eq '1.1.1.1' -and $ServerAddresses[1] -eq '1.0.0.1'
}
Should -Invoke Add-DnsClientDohServerAddress -Times 4 -Exactly
}
It "updates an existing DoH entry with the selected provider settings" {
Mock Get-DnsClientDohServerAddress {
if ($ServerAddress -eq "1.1.1.1") {