mirror of
https://github.com/NexusOne23/noid-privacy.git
synced 2026-02-07 12:11:53 +01:00
722 lines
39 KiB
PowerShell
722 lines
39 KiB
PowerShell
function Get-CurrentDNSProvider {
|
|
<#
|
|
.SYNOPSIS
|
|
Detect the current DNS provider based on configured DNS servers
|
|
#>
|
|
[CmdletBinding()]
|
|
param()
|
|
|
|
try {
|
|
# Get DNS servers from active adapters
|
|
$adapters = Get-NetAdapter -Physical | Where-Object { $_.Status -eq 'Up' }
|
|
foreach ($adapter in $adapters) {
|
|
$dnsServers = Get-DnsClientServerAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue
|
|
if ($dnsServers -and $dnsServers.ServerAddresses) {
|
|
$primaryDNS = $dnsServers.ServerAddresses | Select-Object -First 1
|
|
|
|
# Match against known providers
|
|
switch -Regex ($primaryDNS) {
|
|
'^1\.1\.1\.' { return "Cloudflare" }
|
|
'^1\.0\.0\.' { return "Cloudflare" }
|
|
'^9\.9\.9\.' { return "Quad9" }
|
|
'^149\.112\.' { return "Quad9" }
|
|
'^94\.140\.14\.' { return "AdGuard" }
|
|
'^94\.140\.15\.' { return "AdGuard" }
|
|
}
|
|
}
|
|
}
|
|
return $null # Could not detect provider
|
|
}
|
|
catch {
|
|
Write-Log -Level WARNING -Message "Failed to detect current DNS provider: $_" -Module "DNS"
|
|
return $null
|
|
}
|
|
}
|
|
|
|
function Invoke-DNSConfiguration {
|
|
<#
|
|
.SYNOPSIS
|
|
Configure secure DNS with DNS over HTTPS (DoH)
|
|
|
|
.DESCRIPTION
|
|
Configures secure DNS on all physical network adapters with:
|
|
- DNS server addresses (IPv4 and IPv6)
|
|
- DNS over HTTPS (DoH) encryption
|
|
- Automatic backup for rollback
|
|
|
|
Supports three DNS providers:
|
|
- Quad9: Security-focused, Swiss privacy (default)
|
|
- Cloudflare: Fastest resolver, privacy-focused
|
|
- AdGuard: Ad/tracker blocking, EU jurisdiction
|
|
|
|
All providers perform server-side DNSSEC validation.
|
|
|
|
.PARAMETER Provider
|
|
DNS provider to use: Quad9, Cloudflare, or AdGuard (default: Quad9)
|
|
|
|
.PARAMETER DryRun
|
|
Show what would be configured without applying changes
|
|
|
|
.PARAMETER Force
|
|
Skip connectivity tests and apply configuration anyway
|
|
|
|
.EXAMPLE
|
|
Invoke-DNSConfiguration
|
|
Configure Quad9 DNS (default, security-focused) on all adapters
|
|
|
|
.EXAMPLE
|
|
Invoke-DNSConfiguration -Provider Cloudflare
|
|
Configure Cloudflare DNS (fastest) on all adapters
|
|
|
|
.EXAMPLE
|
|
Invoke-DNSConfiguration -Provider AdGuard -DryRun
|
|
Test AdGuard DNS (ad-blocking) configuration without applying
|
|
|
|
.OUTPUTS
|
|
PSCustomObject with configuration results
|
|
|
|
.NOTES
|
|
Requires Administrator privileges
|
|
Creates automatic backup for rollback
|
|
Uses PowerShell Best Practice cmdlets (not netsh)
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter()]
|
|
[ValidateSet('Cloudflare', 'Quad9', 'AdGuard', 'KEEP')]
|
|
[string]$Provider,
|
|
|
|
[Parameter()]
|
|
[switch]$DryRun,
|
|
|
|
[Parameter()]
|
|
[switch]$Force
|
|
)
|
|
|
|
begin {
|
|
$moduleName = "DNS"
|
|
$startTime = Get-Date
|
|
|
|
# Core/Rollback.ps1 is loaded by Framework.ps1 - DO NOT load again here
|
|
# Loading it twice would reset $script:BackupBasePath and break the backup system!
|
|
|
|
# Provider selection - NonInteractive or Interactive
|
|
if (-not $Provider) {
|
|
if (Test-NonInteractiveMode) {
|
|
# NonInteractive mode (GUI) - use config values
|
|
$Provider = Get-NonInteractiveValue -Module "DNS" -Key "provider" -Default "Quad9"
|
|
$script:DoHMode = Get-NonInteractiveValue -Module "DNS" -Key "dohMode" -Default "REQUIRE"
|
|
|
|
# Handle "KEEP" provider - detect current DNS and preserve it
|
|
if ($Provider -eq "KEEP") {
|
|
$detectedProvider = Get-CurrentDNSProvider
|
|
if ($detectedProvider) {
|
|
$Provider = $detectedProvider
|
|
Write-Log -Level INFO -Message "KEEP mode: Detected current provider as $Provider" -Module $moduleName
|
|
} else {
|
|
$Provider = "Quad9"
|
|
Write-Log -Level WARNING -Message "KEEP mode: Could not detect provider, defaulting to Quad9" -Module $moduleName
|
|
}
|
|
}
|
|
|
|
Write-NonInteractiveDecision -Module $moduleName -Decision "DNS Provider" -Value $Provider
|
|
Write-NonInteractiveDecision -Module $moduleName -Decision "DoH Mode" -Value $script:DoHMode
|
|
}
|
|
else {
|
|
# Interactive mode
|
|
Write-Host ""
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
Write-Host " DNS PROVIDER SELECTION" -ForegroundColor Cyan
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
Write-Host "[1] Quad9 (9.9.9.9) - RECOMMENDED FOR SECURITY" -ForegroundColor Green
|
|
Write-Host " PRIMARY STRENGTH: Security + Privacy (Malware Blocking)" -ForegroundColor Cyan
|
|
Write-Host " Speed 4/5 | Privacy 5/5 | Security 5/5 | Filtering 4/5" -ForegroundColor Gray
|
|
Write-Host " - Blocks malware/phishing domains automatically" -ForegroundColor Gray
|
|
Write-Host " - Swiss jurisdiction (strongest privacy laws)" -ForegroundColor Gray
|
|
Write-Host ""
|
|
|
|
Write-Host "[2] Cloudflare (1.1.1.1) - RECOMMENDED FOR SPEED" -ForegroundColor Yellow
|
|
Write-Host " PRIMARY STRENGTH: Speed (Fastest Resolver)" -ForegroundColor Cyan
|
|
Write-Host " Speed 5/5 | Privacy 4/5 | Security 4/5 | Filtering 2/5" -ForegroundColor Gray
|
|
Write-Host " - Global performance leader" -ForegroundColor Gray
|
|
Write-Host " - Minimal logging (25h anonymized)" -ForegroundColor Gray
|
|
Write-Host ""
|
|
|
|
Write-Host "[3] AdGuard DNS (94.140.14.14) - RECOMMENDED FOR AD-BLOCKING" -ForegroundColor Yellow
|
|
Write-Host " PRIMARY STRENGTH: Ad-Blocking + Tracker Protection" -ForegroundColor Cyan
|
|
Write-Host " Speed 4/5 | Privacy 4/5 | Security 4/5 | Filtering 5/5" -ForegroundColor Gray
|
|
Write-Host " - Blocks ads and trackers at DNS level" -ForegroundColor Gray
|
|
Write-Host " - Family-friendly options available" -ForegroundColor Gray
|
|
Write-Host ""
|
|
|
|
Write-Host "[0] Skip DNS configuration" -ForegroundColor Gray
|
|
Write-Host " Keep current system DNS" -ForegroundColor Gray
|
|
Write-Host ""
|
|
|
|
do {
|
|
$selection = Read-Host "Select provider [1-3, 0=Skip, default: 1]"
|
|
if ([string]::IsNullOrWhiteSpace($selection)) { $selection = "1" }
|
|
|
|
if ($selection -notin @('0', '1', '2', '3')) {
|
|
Write-Host ""
|
|
Write-Host "Invalid input. Please enter 0, 1, 2, or 3." -ForegroundColor Red
|
|
Write-Host ""
|
|
}
|
|
} while ($selection -notin @('0', '1', '2', '3'))
|
|
|
|
$Provider = switch ($selection) {
|
|
"1" { "Quad9" }
|
|
"2" { "Cloudflare" }
|
|
"3" { "AdGuard" }
|
|
"0" { $null }
|
|
}
|
|
|
|
if ($null -eq $Provider) {
|
|
Write-Host ""
|
|
Write-Host "DNS configuration skipped" -ForegroundColor Gray
|
|
Write-Host ""
|
|
return [PSCustomObject]@{
|
|
Success = $true
|
|
Provider = "Skipped"
|
|
AdaptersConfigured = 0
|
|
DoHEnabled = $false
|
|
BackupCreated = $false
|
|
Errors = @()
|
|
Warnings = @("DNS configuration skipped by user")
|
|
Duration = (Get-Date) - $startTime
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Selected: $Provider" -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Log -Level DEBUG -Message "User selected DNS provider: $Provider" -Module $moduleName
|
|
|
|
# DoH Mode Selection (REQUIRE vs ALLOW)
|
|
Write-Host ""
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
Write-Host " DNS-over-HTTPS (DoH) MODE" -ForegroundColor Cyan
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
Write-Host "Choose DoH encryption mode:" -ForegroundColor White
|
|
Write-Host ""
|
|
|
|
Write-Host "[1] REQUIRE Mode (Recommended)" -ForegroundColor Green
|
|
Write-Host " - Maximum security: NO unencrypted fallback" -ForegroundColor Gray
|
|
Write-Host " - Best for: Home networks, single-location systems" -ForegroundColor Gray
|
|
Write-Host " - Warning: May break in corporate networks or captive portals" -ForegroundColor Yellow
|
|
Write-Host ""
|
|
|
|
Write-Host "[2] ALLOW Mode (Mobile/Enterprise/VPN)" -ForegroundColor Yellow
|
|
Write-Host " - Balanced: Falls back to UDP if DoH unavailable" -ForegroundColor Gray
|
|
Write-Host " - Best for: VPN users, mobile devices, enterprise networks" -ForegroundColor Gray
|
|
Write-Host " - Warning: Less secure (unencrypted fallback possible)" -ForegroundColor Yellow
|
|
Write-Host ""
|
|
|
|
do {
|
|
$dohSelection = Read-Host "Select DoH mode [1/2, default: 1]"
|
|
if ([string]::IsNullOrWhiteSpace($dohSelection)) { $dohSelection = "1" }
|
|
|
|
if ($dohSelection -notin @('1', '2')) {
|
|
Write-Host ""
|
|
Write-Host "Invalid input. Please enter 1 or 2." -ForegroundColor Red
|
|
Write-Host ""
|
|
}
|
|
} while ($dohSelection -notin @('1', '2'))
|
|
|
|
$script:DoHMode = switch ($dohSelection) {
|
|
"1" { "REQUIRE" }
|
|
"2" { "ALLOW" }
|
|
}
|
|
|
|
Write-Host ""
|
|
if ($script:DoHMode -eq "REQUIRE") {
|
|
Write-Host "DoH Mode: REQUIRE (Maximum Security)" -ForegroundColor Green
|
|
}
|
|
else {
|
|
Write-Host "DoH Mode: ALLOW (Mobile/Enterprise Compatible)" -ForegroundColor Yellow
|
|
}
|
|
Write-Host ""
|
|
Write-Log -Level DEBUG -Message "User selected DoH mode: $script:DoHMode" -Module $moduleName
|
|
}
|
|
}
|
|
else {
|
|
# Provider specified via parameter - handle KEEP and get DoHMode
|
|
if ($Provider -eq "KEEP") {
|
|
# Detect current DNS provider and preserve it (only used by GUI)
|
|
$detectedProvider = Get-CurrentDNSProvider
|
|
if ($detectedProvider) {
|
|
$Provider = $detectedProvider
|
|
Write-Log -Level INFO -Message "KEEP mode: Detected current provider as $Provider" -Module $moduleName
|
|
} else {
|
|
$Provider = "Quad9"
|
|
Write-Log -Level WARNING -Message "KEEP mode: Could not detect provider, defaulting to Quad9" -Module $moduleName
|
|
}
|
|
}
|
|
|
|
if (Test-NonInteractiveMode) {
|
|
# NonInteractive mode (GUI) - read DoHMode from config
|
|
$script:DoHMode = Get-NonInteractiveValue -Module "DNS" -Key "dohMode" -Default "REQUIRE"
|
|
Write-NonInteractiveDecision -Module $moduleName -Decision "DNS Provider" -Value $Provider
|
|
Write-NonInteractiveDecision -Module $moduleName -Decision "DoH Mode" -Value $script:DoHMode
|
|
}
|
|
else {
|
|
# Interactive CLI - default to REQUIRE when Provider is passed directly
|
|
$script:DoHMode = "REQUIRE"
|
|
}
|
|
}
|
|
|
|
# Initialize Session-based backup system
|
|
$moduleBackupPath = $null
|
|
if (-not $DryRun) {
|
|
try {
|
|
Initialize-BackupSystem
|
|
$moduleBackupPath = Start-ModuleBackup -ModuleName "DNS"
|
|
Write-Log -Level INFO -Message "Session backup initialized: $moduleBackupPath" -Module $moduleName
|
|
}
|
|
catch {
|
|
Write-Log -Level WARNING -Message "Failed to initialize backup system: $_" -Module $moduleName
|
|
Write-Log -Level WARNING -Message "Continuing without backup (RISKY!)" -Module $moduleName
|
|
}
|
|
}
|
|
else {
|
|
Write-Log -Level INFO -Message "Skipping backup initialization (DryRun mode)" -Module $moduleName
|
|
}
|
|
|
|
# Initialize result object
|
|
$result = [PSCustomObject]@{
|
|
Success = $false
|
|
Provider = $Provider
|
|
AdaptersConfigured = 0
|
|
DoHEnabled = $false
|
|
BackupCreated = $false
|
|
VerificationPassed = $false
|
|
Errors = @()
|
|
Warnings = @()
|
|
Duration = $null
|
|
}
|
|
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
Write-Log -Level INFO -Message "========================================" -Module $moduleName
|
|
Write-Log -Level INFO -Message "DNS CONFIGURATION" -Module $moduleName
|
|
Write-Log -Level INFO -Message "========================================" -Module $moduleName
|
|
Write-Log -Level INFO -Message "Provider: $Provider" -Module $moduleName
|
|
Write-Log -Level INFO -Message "Mode: $(if ($DryRun) { 'DRY RUN' } else { 'APPLY' })" -Module $moduleName
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
}
|
|
|
|
process {
|
|
try {
|
|
# Load provider configuration
|
|
$configPath = Join-Path $PSScriptRoot "..\Config\Providers.json"
|
|
|
|
if (-not (Test-Path $configPath)) {
|
|
throw "Provider configuration file not found: $configPath"
|
|
}
|
|
|
|
$providersConfig = Get-Content -Path $configPath -Raw | ConvertFrom-Json
|
|
$providerKey = $Provider.ToLower()
|
|
$providerConfig = $providersConfig.providers.$providerKey
|
|
|
|
if (-not $providerConfig) {
|
|
throw "Provider configuration not found for: $Provider"
|
|
}
|
|
|
|
# Display provider information
|
|
Write-Log -Level INFO -Message "DNS PROVIDER DETAILS:" -Module $moduleName
|
|
Write-Log -Level INFO -Message " Name: $($providerConfig.name)" -Module $moduleName
|
|
Write-Log -Level INFO -Message " Description: $($providerConfig.description)" -Module $moduleName
|
|
Write-Log -Level INFO -Message " Best for: $($providerConfig.best_for)" -Module $moduleName
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
Write-Log -Level INFO -Message " RATINGS:" -Module $moduleName
|
|
Write-Log -Level INFO -Message " Speed: $($providerConfig.ratings.speed)/5" -Module $moduleName
|
|
Write-Log -Level INFO -Message " Privacy: $($providerConfig.ratings.privacy)/5" -Module $moduleName
|
|
Write-Log -Level INFO -Message " Security: $($providerConfig.ratings.security)/5" -Module $moduleName
|
|
Write-Log -Level INFO -Message " Filtering: $($providerConfig.ratings.filtering)/5" -Module $moduleName
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
Write-Log -Level INFO -Message " FEATURES:" -Module $moduleName
|
|
foreach ($feature in $providerConfig.features) {
|
|
Write-Log -Level INFO -Message " - $feature" -Module $moduleName
|
|
}
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
Write-Log -Level INFO -Message " Jurisdiction: $($providerConfig.jurisdiction)" -Module $moduleName
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
|
|
# Quick connectivity test (unless forced or dry-run)
|
|
if (-not $Force -and -not $DryRun) {
|
|
Write-Log -Level INFO -Message "Testing DNS connectivity (quick check)..." -Module $moduleName
|
|
|
|
$primaryTest = Test-DNSConnectivity -ServerAddress $providerConfig.ipv4.primary
|
|
|
|
if (-not $primaryTest.Reachable) {
|
|
# Non-fatal: TCP port 53 not reachable (may be firewall or VPN), but UDP DNS usually works
|
|
$result.Warnings += "DNS pre-check skipped (TCP 53 not reachable) - configuration will proceed"
|
|
Write-Log -Level INFO -Message "DNS connectivity pre-check failed (TCP 53 blocked or timeout) - this is often normal with firewalls/VPNs. DNS will be configured anyway." -Module $moduleName
|
|
}
|
|
elseif (-not $primaryTest.CanResolve) {
|
|
# Can reach DNS but cannot resolve - still non-fatal
|
|
Write-Log -Level INFO -Message "DNS server reachable, configuration will proceed" -Module $moduleName
|
|
}
|
|
else {
|
|
# All good
|
|
Write-Log -Level SUCCESS -Message "DNS connectivity verified" -Module $moduleName
|
|
}
|
|
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
}
|
|
|
|
# CRITICAL FIX: Create backup BEFORE cleaning DNS state
|
|
# Bug: Reset-DnsState deletes DoH/Policy entries, so backup must happen first
|
|
# to capture the actual pre-apply state (not the cleaned state)
|
|
if (-not $DryRun) {
|
|
Write-Log -Level INFO -Message "Creating backup of current DNS settings..." -Module $moduleName
|
|
|
|
$backupFile = Backup-DNSSettings
|
|
|
|
if ($backupFile) {
|
|
# Register backup in session manifest
|
|
Complete-ModuleBackup -ItemsBackedUp 1 -Status "Success"
|
|
|
|
$result.BackupCreated = $true
|
|
Write-Log -Level SUCCESS -Message "Backup created successfully" -Module $moduleName
|
|
}
|
|
else {
|
|
$result.Warnings += "Could not create backup"
|
|
Write-Log -Level WARNING -Message "Backup creation failed - continuing without backup" -Module $moduleName
|
|
}
|
|
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
}
|
|
|
|
# CRITICAL: Clean ALL previous DNS state (prevents interference from old providers/VPNs)
|
|
if (-not $DryRun) {
|
|
Write-Log -Level INFO -Message "Cleaning up previous DNS state..." -Module $moduleName
|
|
Reset-DnsState -KeepAdapterDns
|
|
|
|
# Wait for adapter state to stabilize after cleanup (prevents 0x80004005 errors)
|
|
Start-Sleep -Seconds 3
|
|
}
|
|
else {
|
|
Write-Log -Level INFO -Message "[DRY RUN] Skipping DNS state cleanup (Reset-DnsState)" -Module $moduleName
|
|
}
|
|
|
|
# Get physical adapters (aggressive VPN/VM filtering)
|
|
$adapters = @(Get-PhysicalAdapters) # Force array to ensure .Count works
|
|
|
|
if ($adapters.Count -eq 0) {
|
|
# No physical adapters found - skip gracefully (VM or unusual config)
|
|
Write-Host ""
|
|
Write-Host "========================================" -ForegroundColor Yellow
|
|
Write-Host " DNS Module Skipped" -ForegroundColor Yellow
|
|
Write-Host "========================================" -ForegroundColor Yellow
|
|
Write-Host ""
|
|
Write-Host "No physical network adapters found." -ForegroundColor Cyan
|
|
Write-Host ""
|
|
Write-Host "This can happen in:" -ForegroundColor Yellow
|
|
Write-Host " - Virtual machines with unusual network config" -ForegroundColor Gray
|
|
Write-Host " - Systems with all adapters disabled" -ForegroundColor Gray
|
|
Write-Host " - Enterprise environments with special setups" -ForegroundColor Gray
|
|
Write-Host ""
|
|
Write-Host "This is NOT an error - DNS configuration will be skipped." -ForegroundColor Green
|
|
Write-Host ""
|
|
|
|
Write-Log -Level WARNING -Message "DNS skipped: No physical network adapters found" -Module $moduleName
|
|
|
|
$result.Success = $true # Not an error - intentional skip
|
|
$result.Warnings += "DNS skipped: No physical network adapters found. Check adapter configuration if this is unexpected."
|
|
|
|
return $result
|
|
}
|
|
|
|
Write-Log -Level INFO -Message "Configuring $($adapters.Count) network adapter(s)" -Module $moduleName
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
|
|
# GLOBAL DOH ENFORCEMENT (before per-adapter config)
|
|
# This ensures Windows GUI shows "Encrypted" and forces DoH globally
|
|
# CRITICAL ORDER: netsh FIRST (may reset EnableAutoDoh), then Registry AFTER
|
|
if (-not $DryRun -and $providerConfig.doh.supported) {
|
|
Write-Log -Level INFO -Message "Enabling global DoH enforcement..." -Module $moduleName
|
|
|
|
try {
|
|
$dnsParamsPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters"
|
|
|
|
# 1. FIRST: Set global DoH via netsh (this may reset EnableAutoDoh!)
|
|
$netshResult = netsh dnsclient set global doh=yes 2>&1
|
|
if ($LASTEXITCODE -eq 0) {
|
|
Write-Log -Level DEBUG -Message " netsh global DoH enabled" -Module $moduleName
|
|
}
|
|
else {
|
|
Write-Log -Level DEBUG -Message " netsh global DoH: $netshResult (exit code: $LASTEXITCODE)" -Module $moduleName
|
|
}
|
|
|
|
# 2. SECOND: Set EnableAutoDoh = 2 AFTER netsh (so it persists!)
|
|
if (-not (Test-Path $dnsParamsPath)) {
|
|
New-Item -Path $dnsParamsPath -Force | Out-Null
|
|
}
|
|
|
|
$existing = Get-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -ErrorAction SilentlyContinue
|
|
if ($null -ne $existing) {
|
|
Set-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -Value 2 -Force | Out-Null
|
|
}
|
|
else {
|
|
New-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -Value 2 -PropertyType DWord -Force | Out-Null
|
|
}
|
|
Write-Log -Level DEBUG -Message " EnableAutoDoh = 2 (Force encrypted DNS)" -Module $moduleName
|
|
|
|
Write-Log -Level SUCCESS -Message "Global DoH enforcement enabled" -Module $moduleName
|
|
}
|
|
catch {
|
|
Write-Log -Level WARNING -Message "Could not enable global DoH enforcement: $_" -Module $moduleName
|
|
$result.Warnings += "Global DoH enforcement failed (non-critical)"
|
|
}
|
|
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
}
|
|
|
|
# Configure each adapter
|
|
Write-Log -Level INFO -Message "Configuring DNS servers..." -Module $moduleName
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
|
|
$configuredCount = 0
|
|
$dohSuccessCount = 0
|
|
|
|
foreach ($adapter in $adapters) {
|
|
Write-Log -Level INFO -Message "Configuring adapter: $($adapter.Name)" -Module $moduleName
|
|
|
|
# Set DNS servers (IPv4 + IPv6)
|
|
$dnsResult = Set-DNSServers -InterfaceIndex $adapter.InterfaceIndex `
|
|
-IPv4Primary $providerConfig.ipv4.primary `
|
|
-IPv4Secondary $providerConfig.ipv4.secondary `
|
|
-IPv6Primary $providerConfig.ipv6.primary `
|
|
-IPv6Secondary $providerConfig.ipv6.secondary `
|
|
-Validate:(-not $Force) `
|
|
-DryRun:$DryRun
|
|
|
|
if ($dnsResult) {
|
|
$configuredCount++
|
|
Write-Log -Level SUCCESS -Message "DNS servers configured on $($adapter.Name)" -Module $moduleName
|
|
|
|
# Enable DoH for IPv4 and IPv6 addresses so ALL queries are encrypted
|
|
if (-not $DryRun -and $providerConfig.doh.supported) {
|
|
Write-Log -Level DEBUG -Message "Enabling DoH (IPv4 + IPv6)..." -Module $moduleName
|
|
|
|
# Register DoH endpoints for IPv4 servers
|
|
$dohPrimaryV4 = Enable-DoH -ServerAddress $providerConfig.ipv4.primary `
|
|
-DohTemplate $providerConfig.doh.template
|
|
$dohSecondaryV4 = Enable-DoH -ServerAddress $providerConfig.ipv4.secondary `
|
|
-DohTemplate $providerConfig.doh.template
|
|
|
|
# Register DoH endpoints for IPv6 servers (Windows supports DoH for IPv6 as well)
|
|
$dohPrimaryV6 = Enable-DoH -ServerAddress $providerConfig.ipv6.primary `
|
|
-DohTemplate $providerConfig.doh.template
|
|
$dohSecondaryV6 = Enable-DoH -ServerAddress $providerConfig.ipv6.secondary `
|
|
-DohTemplate $providerConfig.doh.template
|
|
|
|
if ($dohPrimaryV4 -and $dohSecondaryV4 -and $dohPrimaryV6 -and $dohSecondaryV6) {
|
|
Write-Log -Level SUCCESS -Message "DoH enabled on $($adapter.Name) for IPv4 and IPv6" -Module $moduleName
|
|
|
|
# TRICK #4: IPv6-FIRST HACK to force Windows DoH validation
|
|
# Windows needs to see IPv6 servers first to recognize them as DoH-capable
|
|
try {
|
|
# Check if adapter has IPv6 enabled
|
|
$ipv6Binding = Get-NetAdapterBinding -InterfaceAlias $adapter.Name `
|
|
-ComponentID ms_tcpip6 -ErrorAction SilentlyContinue
|
|
$ipv6Enabled = $ipv6Binding -and $ipv6Binding.Enabled
|
|
|
|
if ($ipv6Enabled) {
|
|
Write-Log -Level DEBUG -Message "IPv6-First hack: Setting IPv6 servers first..." -Module $moduleName
|
|
|
|
# Temporarily set IPv6 FIRST
|
|
$ipv6Servers = @($providerConfig.ipv6.primary, $providerConfig.ipv6.secondary)
|
|
$ipv4Servers = @($providerConfig.ipv4.primary, $providerConfig.ipv4.secondary)
|
|
|
|
Set-DnsClientServerAddress -InterfaceAlias $adapter.Name `
|
|
-ServerAddresses ($ipv6Servers + $ipv4Servers) -ErrorAction Stop
|
|
|
|
# Wait for Windows to validate IPv6 DoH
|
|
Write-Log -Level DEBUG -Message "Waiting 5 seconds for IPv6 DoH validation..." -Module $moduleName
|
|
Start-Sleep -Seconds 5
|
|
|
|
# Reset to IPv4-first (faster for most users)
|
|
Write-Log -Level DEBUG -Message "Resetting DNS order (IPv4 first for speed)..." -Module $moduleName
|
|
Set-DnsClientServerAddress -InterfaceAlias $adapter.Name `
|
|
-ServerAddresses ($ipv4Servers + $ipv6Servers) -ErrorAction Stop
|
|
}
|
|
}
|
|
catch {
|
|
Write-Log -Level DEBUG -Message "IPv6-First hack failed (non-critical): $_" -Module $moduleName
|
|
}
|
|
|
|
# TRICK #5: Set DohFlags registry keys (ENCRYPTED ONLY!)
|
|
# This is what makes Windows GUI show "Encrypted" instead of "Unencrypted"
|
|
Write-Log -Level DEBUG -Message "Setting DoH encryption flags (DohFlags)..." -Module $moduleName
|
|
|
|
try {
|
|
$adapterGuid = $adapter.InterfaceGuid
|
|
|
|
# IPv4 Servers -> Doh branch
|
|
foreach ($ip in @($providerConfig.ipv4.primary, $providerConfig.ipv4.secondary)) {
|
|
try {
|
|
$regPath = "HKLM:\System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters\$adapterGuid\DohInterfaceSettings\Doh\$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
|
|
Write-Log -Level DEBUG -Message " DohFlags set: $ip (Encrypted Only)" -Module $moduleName
|
|
}
|
|
catch {
|
|
Write-Log -Level DEBUG -Message " Failed to set DohFlags for $ip : $_" -Module $moduleName
|
|
}
|
|
}
|
|
|
|
# IPv6 Servers -> Doh6 branch (DIFFERENT from IPv4!)
|
|
foreach ($ip in @($providerConfig.ipv6.primary, $providerConfig.ipv6.secondary)) {
|
|
try {
|
|
$basePath = "HKLM:\System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters\$adapterGuid\DohInterfaceSettings\Doh6"
|
|
$ipPath = "$basePath\$ip"
|
|
|
|
if (-not (Test-Path $basePath)) {
|
|
New-Item -Path $basePath -Force -ErrorAction Stop | Out-Null
|
|
}
|
|
|
|
if (-not (Test-Path $ipPath)) {
|
|
New-Item -Path $ipPath -Force -ErrorAction Stop | Out-Null
|
|
}
|
|
|
|
New-ItemProperty -Path $ipPath -Name 'DohFlags' -Value 1 -PropertyType QWord -Force -ErrorAction Stop | Out-Null
|
|
Write-Log -Level DEBUG -Message " DohFlags set: $ip (Encrypted Only, Doh6)" -Module $moduleName
|
|
}
|
|
catch {
|
|
Write-Log -Level DEBUG -Message " Failed to set DohFlags for $ip : $_" -Module $moduleName
|
|
}
|
|
}
|
|
|
|
$dohSuccessCount++
|
|
Write-Log -Level SUCCESS -Message "DoH encryption enforced on $($adapter.Name) (all servers, DohFlags set)" -Module $moduleName
|
|
}
|
|
catch {
|
|
$result.Warnings += "DoH flags could not be set on $($adapter.Name) - DNS may not show as encrypted in UI"
|
|
Write-Log -Level WARNING -Message "DoH flags configuration had issues on $($adapter.Name)" -Module $moduleName
|
|
}
|
|
|
|
# DHCP DNS Override Protection
|
|
if (-not $DryRun) {
|
|
$dhcpDisabled = Disable-DHCPDnsOverride -InterfaceIndex $adapter.InterfaceIndex -DryRun:$false
|
|
if (-not $dhcpDisabled) {
|
|
$result.Warnings += "Could not disable DHCP DNS override on $($adapter.Name)"
|
|
}
|
|
}
|
|
else {
|
|
Write-Log -Level INFO -Message "[DRY RUN] Skipping DHCP DNS override protection on $($adapter.Name)" -Module $moduleName
|
|
}
|
|
}
|
|
else {
|
|
$result.Warnings += "DoH could not be enabled on $($adapter.Name)"
|
|
Write-Log -Level WARNING -Message "DoH configuration had issues on $($adapter.Name)" -Module $moduleName
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$result.Errors += "Failed to configure $($adapter.Name)"
|
|
Write-Log -Level ERROR -Message "Failed to configure $($adapter.Name)" -Module $moduleName
|
|
}
|
|
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
}
|
|
|
|
# CRITICAL: Set global DoH policy according to selected mode (REQUIRE or ALLOW)
|
|
if (-not $DryRun -and $dohSuccessCount -gt 0) {
|
|
$modeDescription = if ($script:DoHMode -eq "ALLOW") { "ALLOW mode (fallback permitted)" } else { "REQUIRE mode (no fallback)" }
|
|
Write-Log -Level INFO -Message "Enforcing global DoH policy ($modeDescription)..." -Module $moduleName
|
|
$policySet = Set-DoHPolicy
|
|
if ($policySet) {
|
|
$successDesc = if ($script:DoHMode -eq "ALLOW") { "Global DoH policy: ALLOW mode active (fallback allowed by design)" } else { "Global DoH policy: REQUIRE mode active (no unencrypted fallback)" }
|
|
Write-Log -Level SUCCESS -Message $successDesc -Module $moduleName
|
|
}
|
|
else {
|
|
$result.Warnings += "Could not set global DoH policy - DoH may fall back to unencrypted"
|
|
}
|
|
}
|
|
|
|
$result.AdaptersConfigured = $configuredCount
|
|
$result.DoHEnabled = ($dohSuccessCount -gt 0)
|
|
|
|
# Final status
|
|
if ($configuredCount -eq $adapters.Count) {
|
|
$result.Success = $true
|
|
Write-Log -Level SUCCESS -Message "DNS configuration completed successfully" -Module $moduleName
|
|
Write-Log -Level INFO -Message "Configured $configuredCount of $($adapters.Count) adapter(s)" -Module $moduleName
|
|
|
|
if ($result.DoHEnabled) {
|
|
Write-Log -Level SUCCESS -Message "DNS over HTTPS (DoH) is active - DNS queries are encrypted" -Module $moduleName
|
|
}
|
|
}
|
|
else {
|
|
Write-Log -Level WARNING -Message "DNS configuration completed with errors" -Module $moduleName
|
|
Write-Log -Level WARNING -Message "Configured $configuredCount of $($adapters.Count) adapter(s)" -Module $moduleName
|
|
}
|
|
|
|
# Mark verification as passed (individual functions already verified)
|
|
if (-not $DryRun -and $result.Success) {
|
|
$result.VerificationPassed = $true
|
|
}
|
|
}
|
|
catch {
|
|
$result.Success = $false
|
|
$result.Errors += $_.Exception.Message
|
|
Write-ErrorLog -Message "DNS configuration failed" -Module $moduleName -ErrorRecord $_
|
|
}
|
|
}
|
|
|
|
end {
|
|
$result.Duration = (Get-Date) - $startTime
|
|
|
|
# Flush DNS cache for immediate effect (only on success)
|
|
if ($result.Success -and -not $DryRun) {
|
|
Write-Log -Level INFO -Message "Flushing DNS resolver cache for immediate effect..." -Module $moduleName
|
|
try {
|
|
Clear-DnsClientCache -ErrorAction Stop
|
|
Write-Log -Level SUCCESS -Message "DNS cache cleared successfully" -Module $moduleName
|
|
}
|
|
catch {
|
|
Write-Log -Level WARNING -Message "Could not flush DNS cache: $_" -Module $moduleName
|
|
# Non-critical: continue anyway
|
|
}
|
|
}
|
|
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
Write-Log -Level INFO -Message "========================================" -Module $moduleName
|
|
Write-Log -Level INFO -Message "CONFIGURATION SUMMARY" -Module $moduleName
|
|
Write-Log -Level INFO -Message "========================================" -Module $moduleName
|
|
Write-Log -Level INFO -Message "Provider: $($result.Provider)" -Module $moduleName
|
|
Write-Log -Level INFO -Message "Adapters configured: $($result.AdaptersConfigured)" -Module $moduleName
|
|
Write-Log -Level INFO -Message "DoH enabled: $(if ($result.DoHEnabled) { 'Yes' } else { 'No' })" -Module $moduleName
|
|
Write-Log -Level INFO -Message "Backup created: $(if ($result.BackupCreated) { 'Yes' } else { 'No' })" -Module $moduleName
|
|
Write-Log -Level INFO -Message "Duration: $([math]::Round($result.Duration.TotalSeconds, 2)) seconds" -Module $moduleName
|
|
|
|
if ($result.Warnings.Count -gt 0) {
|
|
Write-Log -Level INFO -Message "Warnings: $($result.Warnings.Count)" -Module $moduleName
|
|
foreach ($warn in $result.Warnings) {
|
|
Write-Log -Level INFO -Message " Warning: $warn" -Module $moduleName
|
|
}
|
|
}
|
|
|
|
if ($result.Errors.Count -gt 0) {
|
|
Write-Log -Level INFO -Message "Errors: $($result.Errors.Count)" -Module $moduleName
|
|
foreach ($err in $result.Errors) {
|
|
Write-Log -Level ERROR -Message " Error: $err" -Module $moduleName
|
|
}
|
|
}
|
|
|
|
Write-Log -Level INFO -Message "========================================" -Module $moduleName
|
|
Write-Log -Level INFO -Message " " -Module $moduleName
|
|
|
|
# GUI parsing marker for settings count
|
|
Write-Log -Level SUCCESS -Message "Applied 5 settings" -Module $moduleName
|
|
|
|
return $result
|
|
}
|
|
}
|