diff --git a/client/src/components/ui/Guide/Guide.tsx b/client/src/components/ui/Guide/Guide.tsx index c4db933f535..c96e2e5c352 100644 --- a/client/src/components/ui/Guide/Guide.tsx +++ b/client/src/components/ui/Guide/Guide.tsx @@ -352,9 +352,12 @@ export const Guide = ({ dnsAddresses }: GuideProps) => { const serverName = useSelector((state: RootState) => state.encryption?.server_name); const portHttps = useSelector((state: RootState) => state.encryption?.port_https); + const dnsPrivacyAvailable = useSelector((state: RootState) => state.dashboard?.dnsPrivacyAvailable); const tlsAddress = dnsAddresses?.filter((item: any) => item.includes('tls://')) ?? ''; const httpsAddress = dnsAddresses?.filter((item: any) => item.includes('https://')) ?? ''; - const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1; + // When we have resolved TLS/HTTPS addresses, the guide can show the details. + const hasDnsPrivacyAddresses = httpsAddress.length > 0 || tlsAddress.length > 0; + const showDnsPrivacyNotice = !hasDnsPrivacyAddresses && !dnsPrivacyAvailable; const [activeTabLabel, setActiveTabLabel] = useState('Router'); diff --git a/client/src/initialState.ts b/client/src/initialState.ts index 7cc2855fed5..bfd9f93ab94 100644 --- a/client/src/initialState.ts +++ b/client/src/initialState.ts @@ -127,6 +127,7 @@ export type DashboardData = { httpPort: number; dnsPort: number; dnsAddresses: string[]; + dnsPrivacyAvailable: boolean; dnsVersion: string; dnsStartTime: number | null; clients: Client[]; @@ -448,6 +449,7 @@ export const initialState: RootState = { httpPort: STANDARD_WEB_PORT, dnsPort: STANDARD_DNS_PORT, dnsAddresses: [], + dnsPrivacyAvailable: false, dnsVersion: '', dnsStartTime: null, clients: [], diff --git a/client/src/reducers/dashboard.ts b/client/src/reducers/dashboard.ts index b745f5f4209..81a6909719d 100644 --- a/client/src/reducers/dashboard.ts +++ b/client/src/reducers/dashboard.ts @@ -24,6 +24,7 @@ const dashboard = handleActions( start_time: dnsStartTime, dns_port: dnsPort, dns_addresses: dnsAddresses, + dns_privacy_available: dnsPrivacyAvailable, protection_enabled: protectionEnabled, protection_disabled_duration: protectionDisabledDuration, http_port: httpPort, @@ -37,6 +38,7 @@ const dashboard = handleActions( dnsStartTime, dnsPort, dnsAddresses, + dnsPrivacyAvailable: dnsPrivacyAvailable ?? state.dnsPrivacyAvailable, protectionEnabled, protectionDisabledDuration, language, diff --git a/internal/home/control.go b/internal/home/control.go index b60a7cf4a62..c54a1cca690 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -95,6 +95,21 @@ func collectDNSAddresses(tlsMgr *tlsManager) (addrs []string, err error) { return addrs, nil } +// isDNSPrivacyAvailable returns true if at least one DNS privacy protocol is +// configured to be available. tlsMgr may be nil. +func isDNSPrivacyAvailable(tlsMgr *tlsManager) (ok bool) { + if tlsMgr == nil { + return false + } + + tlsConf := tlsMgr.config() + dohAvailable := tlsConf.PortHTTPS != 0 && (tlsConf.Enabled || tlsConf.AllowUnencryptedDoH) + dotAvailable := tlsConf.Enabled && tlsConf.PortDNSOverTLS != 0 + doqAvailable := tlsConf.Enabled && tlsConf.PortDNSOverQUIC != 0 + + return dohAvailable || dotAvailable || doqAvailable +} + // statusResponse is a response for /control/status endpoint. type statusResponse struct { Version string `json:"version"` @@ -102,6 +117,8 @@ type statusResponse struct { DNSAddrs []string `json:"dns_addresses"` DNSPort uint16 `json:"dns_port"` HTTPPort uint16 `json:"http_port"` + // DNSPrivacyAvailable indicates whether DNS privacy features are available. + DNSPrivacyAvailable bool `json:"dns_privacy_available"` // ProtectionDisabledDuration is the duration of the protection pause in // milliseconds. @@ -160,6 +177,7 @@ func (web *webAPI) handleStatus(w http.ResponseWriter, r *http.Request) { DNSAddrs: dnsAddrs, DNSPort: config.DNS.Port, HTTPPort: config.HTTPConfig.Address.Port(), + DNSPrivacyAvailable: isDNSPrivacyAvailable(web.tlsManager), ProtectionDisabledDuration: protectionDisabledDuration, StartTime: aghhttp.JSONTime(web.startTime), ProtectionEnabled: protEnabled, diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 65ef2c5ef97..c1c376667d2 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1462,6 +1462,7 @@ - 'dns_addresses' - 'dns_port' - 'http_port' + - 'dns_privacy_available' - 'protection_enabled' - 'protection_disabled_until' - 'running' @@ -1485,6 +1486,9 @@ 'example': 80 'minimum': 1 'maximum': 65535 + 'dns_privacy_available': + 'type': 'boolean' + 'description': 'Whether at least one DNS privacy protocol is available.' 'protection_enabled': 'type': 'boolean' 'protection_disabled_duration':