鸿蒙应用接入DoH

本文档介绍如何在 HarmonyOS 原生网络库、RCP 和 webiew 中通过DoH,接入HTTPDNS。

1. 前言

在 HarmonyOS 平台,推荐您优先通过HarmonyOS SDK接入。若您由于某些原因无法引入 SDK,您可以通过 DoH的接入,本文聚焦 HarmonyOS 下“网络库或组件”的 DoH接入方式,涵盖:

说明
  • 鸿蒙的网络组件只支持配置一个 DoH 链接,为提高业务稳定性,建议做好Local DNS降级

    • Network Kit 和 RCP 库中,不支持配置自动降级到 Local DNS 的模式,请做好应用侧降级,参考接入方案的示例代码。

    • ArkWeb 可以通过设置SecureDnsModeAUTO开启允许自动降级到 Local DNS。

  • Network Kit 和 RCP 的 DoH 配置会受addCustomDnsRule自定义解析规则的影响,如果您通过addCustomDnsRule配置了某些域名的解析,那么该域名将不会再通过DoH 解析。

2. 前提条件

通过 DoH 接入 HarmonyOS 下“网络库或组件”前,请您确保已经完成配置DoH服务

3. Network Kit(httpRequest)接入 DoH

通过 Network Kit 在单次请求上指定http.request(options: http.RequestOptions)中的options.dnsOverHttps: string为您的 DoH 接入点,即可开启 DoH,无需改造全局会话。示例代码如下:

import http from '@ohos.net.http'; 
const httpRequest: http.HttpRequest = http.createHttp();
 // 这里请替换为您自己的 DoH URL
const DOH_ENDPOINT = 'https://xxxxx.aliyunhttpdns.com/dns-query';

const isDoHFailure = (err: ErrorDetails): boolean => {
  const code = String(err.code ?? '');
  const msg = String(err.message ?? '');
  return /(couldn'?t\s+resolve\s+host\s+name|resolve\s+host\s+name|dns|resolve|name\s*not\s*resolved|EAI_AGAIN)/i.test(msg) || /DNS/i.test(code);
};

httpRequest.request(this.urlInput, {
  method: http.RequestMethod.GET,
  connectTimeout: 3000,
  readTimeout: 3000,
  dnsOverHttps: DOH_ENDPOINT,
}).then((res: http.HttpResponse) => {
  console.log('DoH request success:', res);
}).catch((err: ErrorDetails) => {
  if (isDoHFailure(err)) {
    console.error('DoH request error, falling back to local DNS:', err);
    httpRequest.request(this.urlInput, {
      method: http.RequestMethod.GET,
      connectTimeout: 3000,
      readTimeout: 3000,
    }).then((fallbackRes: http.HttpResponse) => {
      console.log('Fallback request success:', fallbackRes);
    }).catch((fallbackErr: ErrorDetails) => {
      console.error('Fallback request error:', fallbackErr);
    });
  } else {
    console.error('Request error:', err);
  }
});

4. Remote Communication Kit(RCP)接入 DoH

RCP 网络库支持在全局 Session 和 单个 Request 两个不同粒度下接入 DoH, 下面介绍对应的接入方式。

4.1 Session 接入 DoH

通过 RCP 在会话层面上指定SessionConfiguration.requestConfiguration.dns.dnsOverHttps为您的 DoH 接入点,即可开启 DoH,无需改造全局会话。示例代码如下:

import { rcp } from '@kit.RemoteCommunicationKit';
import type { BusinessError } from '@ohos.base';
private isDoHFailure(err: BusinessError): boolean {
    const code: string = err.code ? String(err.code) : '';
    const msg: string =  String(err.data);
    return /(couldn'?t\s+resolve\s+host\s+name|resolve\s+host\s+name|dns|resolve|name\s*not\s*resolved|EAI_AGAIN)/i.test(msg) || /DNS/i.test(code);
  }
  
 async sendRequest() {
    try {
      const dohConfig: rcp.DnsOverHttpsConfiguration = {
        url: 'https://xxxxx.aliyunhttpdns.com/dns-query',
        skipCertificatesValidation: false,
      };

      const dohSession = rcp.createSession({
        requestConfiguration: {
          dns: { dnsOverHttps: dohConfig },
          transfer: { timeout: { connectMs: 3000, transferMs: 8000 } },
        },
      });

      const resp = await dohSession.get(this.urlInput);
      console.info('DoH request success, status=', resp.statusCode);
      console.info('Response:', JSON.stringify(resp));
    } catch (err) {
      if (this.isDoHFailure(err)) {
        console.error('DoH request error, falling back to local DNS:', err);
        try {
          const localSession = rcp.createSession({
            requestConfiguration: { transfer: { timeout: { connectMs: 3000, transferMs: 8000 } } },
          });
          const fb = await localSession.get(this.urlInput);
          console.info('Fallback (local DNS) success, status=', fb.statusCode);
          console.info('Fallback response:', JSON.stringify(fb));
        } catch (fallbackErr) {
          console.error('Fallback (local DNS) request error:', fallbackErr);
        }
      } else {
        console.error('Request error (non-DoH):', err);
      }
    }
  }

4.2 Request 接入 DoH

通过 RCP 在单次请求上指定 request.configuration.dns.dnsOverHttps为您的 DoH 接入点,即可开启 DoH,无需改造全局会话。示例代码如下:

import { rcp } from '@kit.RemoteCommunicationKit';
import type { BusinessError } from '@ohos.base';


  async sendRequest() {
    try {
      const dohConfig: rcp.DnsOverHttpsConfiguration = {
        url: 'https://xxxxx.aliyunhttpdns.com/dns-query',
        skipCertificatesValidation: false,
      };

      const session = rcp.createSession({
        requestConfiguration: {
          transfer: { timeout: { connectMs: 3000, transferMs: 8000 } },
        },
      });

      const perReq = new rcp.Request(this.urlInput, 'GET', undefined, undefined, undefined, undefined, {
        dns: { dnsOverHttps: dohConfig },
      });

      const resp = await session.fetch(perReq);
      console.info('DoH request success, status=', resp.statusCode);
      console.info('Response:', JSON.stringify(resp));
    } catch (err) {
      if (this.isDoHFailure(err)) {
        console.error('DoH request error, falling back to local DNS:', err);
        try {
          const localSession = rcp.createSession({
            requestConfiguration: { transfer: { timeout: { connectMs: 3000, transferMs: 8000 } } },
          });
          const fb = await localSession.get(this.urlInput);
          console.info('Fallback (local DNS) success, status=', fb.statusCode);
          console.info('Fallback response:', JSON.stringify(fb));
        } catch (fallbackErr) {
          console.error('Fallback (local DNS) request error:', fallbackErr);
        }
      } else {
        console.error('Request error (non-DoH):', err);
      }
    }
  }

5. WebView 的 DoH 策略

通过 webview.WebviewController.setHttpDns指定您的 DoH URL,示例代码如下:

import { webview } from '@kit.ArkWeb';
webview.WebviewController.setHttpDns(webview.SecureDnsMode.AUTO, 'https://xxxxx.aliyunhttpdns.com/dns-query');
说明

鸿蒙的网络组件只支持配置一个 DoH 链接,为提高业务稳定性,建议做好Local DNS降级,ArkWeb 可以通过设置SecureDnsModeAUTO开启允许自动降级到 Local DNS。

6. 总结

通过本文档介绍的步骤,您可以成功在 鸿蒙网络库中集成DNS over HTTPS (DoH)功能,从而显著提升应用程序的安全性和用户隐私保护能力。配置后可通过将手机 WIFI 网络的 DNS 服务器设置为一个无效地址,观察业务请求是否依然能够正常发起,以此判断 DoH 是否接入成功。