blob: 56f379296b9d8cef06e27d7991a596559eb5e8cf [file] [log] [blame]
#
# Copyright 2021 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import time
from acts import asserts
from acts.controllers.openwrt_ap import MOBLY_CONTROLLER_CONFIG_NAME as OPENWRT
from acts.test_decorators import test_tracker_info
from acts_contrib.test_utils.net import connectivity_const as cconst
from acts_contrib.test_utils.net import connectivity_test_utils as cutils
from acts_contrib.test_utils.net.net_test_utils import start_tcpdump
from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump
from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
from scapy.all import rdpcap
from scapy.all import Scapy_Exception
from scapy.all import TCP
from scapy.all import UDP
DEFAULT_DNS_TIMEOUT = 5
class DnsOverHttpsTest(WifiBaseTest):
"""Tests for DnsoverHttps feature."""
def setup_class(self):
"""Setup devices and OpenWrt for DnsoverHttps test and unpack params."""
self.dut = self.android_devices[0]
if len(self.android_devices) > 1:
self.dut_b = self.android_devices[1]
for ad in self.android_devices:
wutils.reset_wifi(ad)
ad.droid.setPrivateDnsMode(True)
req_params = ("ping_hosts",)
opt_params = ("wifi_network", "configure_OpenWrt",
"ipv4_only_network", "ipv4_ipv6_network")
self.unpack_userparams(req_param_names=req_params,
opt_param_names=opt_params)
if OPENWRT in self.user_params:
self.openwrt = self.access_points[0]
if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
self.dut.log.info("Skip configure Wifi interface due to config setup.")
else:
self.configure_openwrt_ap_and_start(wpa_network=True)
self.tcpdump_pid = None
self.default_dns = None
self.default_dns_v6 = None
self._setup_doh(self.dut, self.get_wifi_network())
def teardown_test(self):
wutils.reset_wifi(self.dut)
if OPENWRT in self.user_params:
self.openwrt.network_setting.del_default_dns(self.default_dns)
self.default_dns = None
self.default_dns_v6 = None
def teardown_class(self):
for ad in self.android_devices:
ad.droid.setPrivateDnsMode(True)
self._setup_doh(ad, self.get_wifi_network(), enable=False)
def on_fail(self, test_name, begin_time):
self.dut.take_bug_report(test_name, begin_time)
def get_wifi_network(self, ipv6_supported=False):
"""Return fit network for conditions.
Args:
ipv6_supported: Boolean for select network.
Returns:
A dict for network object for connect wifi.
"""
if OPENWRT in self.user_params:
if ipv6_supported:
self.openwrt.network_setting.enable_ipv6()
self.openwrt.network_setting.setup_ipv6_bridge()
else:
self.openwrt.network_setting.disable_ipv6()
self.openwrt.network_setting.remove_ipv6_bridge()
if self.default_dns:
self.openwrt.network_setting.add_default_dns(self.default_dns)
if self.default_dns_v6:
for ipv6_dns in self.default_dns_v6:
self.openwrt.network_setting.add_default_v6_dns(ipv6_dns)
if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip":
return self.wifi_network
return self.openwrt.get_wifi_network()
if ipv6_supported:
return self.ipv4_ipv6_network
return self.ipv4_only_network
def _verify_doh_queries(self, pcap_file, over_https):
"""Verify if DNS queries were over https or not.
Args:
pcap_file: tcpdump file
over_https: True if excepted all dns go through doh.
"""
try:
packets = rdpcap(pcap_file)
except Scapy_Exception:
asserts.fail("Not a valid pcap file")
for pkt in packets:
summary = "%s" % pkt.summary()
for host in self.ping_hosts:
host = host.split(".")[-2]
if UDP in pkt and pkt[UDP].sport == 53 and host in summary:
if over_https:
asserts.fail("Found query to port 53: %s" % summary)
else:
self.dut.log.info("Found query to port 53: %s" % summary)
if TCP in pkt and pkt[TCP].sport == 853:
asserts.fail("Found query to port 853: %s" % summary)
def _test_public_doh_mode(self, ad, net, dns_mode, hostname=None):
"""Test step for DoH.
Args:
ad: android device object.
net: wifi network to connect to, LTE network if None.
dns_mode: private DNS mode.
hostname: private DNS hostname to set to.
"""
# set private dns mode
if dns_mode:
cutils.set_private_dns(self.dut, dns_mode, hostname)
# connect to wifi
wutils.start_wifi_connection_scan_and_ensure_network_found(
self.dut, net[wutils.WifiEnums.SSID_KEY])
wutils.wifi_connect(self.dut, net)
self._verify_tls_completed()
# start tcpdump on the device
self.tcpdump_pid = start_tcpdump(self.dut, self.test_name)
# ping hosts should pass
for host in self.ping_hosts:
self.log.info("Pinging %s" % host)
status = wutils.validate_connection(self.dut, host)
asserts.assert_true(status, "Failed to ping host %s" % host)
self.log.info("Ping successful")
# stop tcpdump
pcap_file = stop_tcpdump(self.dut, self.tcpdump_pid, self.test_name)
# verify DNS queries
overhttps = dns_mode != cconst.PRIVATE_DNS_MODE_OFF
self._verify_doh_queries(pcap_file, overhttps)
# reset wifi
wutils.reset_wifi(self.dut)
def _verify_tls_completed(self, retry_count=5):
"""Verify tls finish verification process.
Expect all private dns server status, should be
"success", or "fail".
Args:
retry_count: int for retry times.
Raises:
TimeoutError: if TLS verification stuck in processing.
"""
for attempt in range(retry_count):
out = self.dut.adb.shell("dumpsys dnsresolver")
if "status{in_process}" in out:
if attempt + 1 < retry_count:
self.dut.log.info("DoT still validating, retrying...")
time.sleep(DEFAULT_DNS_TIMEOUT)
else:
return
raise TimeoutError("Fail to completed TLS verification.")
def _setup_doh(self, ad, net, enable=True):
"""Enable/Disable DoH option.
Args:
ad: android devies.
net: network as wifi.
enable: if True, sets the 'doh' experiment flag.
"""
if enable:
ad.adb.shell("setprop persist.device_config.netd_native.doh 1")
else:
ad.adb.shell("setprop persist.device_config.netd_native.doh 0")
wutils.wifi_connect(ad, net)
wutils.reset_wifi(ad)
out = ad.adb.shell("dumpsys dnsresolver |grep doh")
ad.log.debug(out)
def test_mix_server_ipv4_only_wifi_network_with_dns_strict_mode(self):
"""Test doh flag with below situation.
- Android device in strict mode
- DNS server supporting both Dns, DoT and DoH protocols
- IPv4-only network
"""
self._test_public_doh_mode(self.dut, self.get_wifi_network(),
cconst.PRIVATE_DNS_MODE_STRICT,
hostname=cconst.DNS_GOOGLE_HOSTNAME)
def test_mix_server_ipv4_ipv6_wifi_network_with_dns_strict_mode(self):
"""Test doh flag with below situation.
- Android device in strict mode
- DNS server supporting both Dns, DoT and DoH protocols
- IPv4-IPv6 network
"""
self._test_public_doh_mode(self.dut, self.get_wifi_network(),
cconst.PRIVATE_DNS_MODE_STRICT,
hostname=cconst.DNS_GOOGLE_HOSTNAME)
def test_pure_server_ipv4_only_wifi_network_with_dns_strict_mode(self):
"""Test doh flag with below situation.
- Android device in strict mode
- DNS server only supporting DoH protocols
- IPv4-only network
"""
self._test_public_doh_mode(self.dut, self.get_wifi_network(),
cconst.PRIVATE_DNS_MODE_STRICT,
hostname=cconst.DOH_CLOUDFLARE_HOSTNAME)
def test_pure_server_ipv4_ipv6_wifi_network_with_dns_strict_mode(self):
"""Test doh flag with below situation.
- Android device in strict mode
- DNS server only supporting DoH protocols
- IPv4-IPv6 network
"""
self._test_public_doh_mode(self.dut, self.get_wifi_network(),
cconst.PRIVATE_DNS_MODE_STRICT,
hostname=cconst.DOH_CLOUDFLARE_HOSTNAME)
def test_mix_server_ipv4_only_wifi_network_with_dns_opportunistic_mode(self):
"""Test doh flag with below situation.
- Android device in opportunistic mode
- DNS server supporting both Dns, DoT and DoH protocols
- IPv4-only network
"""
self.default_dns = cconst.DNS_GOOGLE_ADDR_V4
self._test_public_doh_mode(self.dut, self.get_wifi_network(),
cconst.PRIVATE_DNS_MODE_OPPORTUNISTIC)
def test_mix_server_ipv4_ipv6_wifi_network_with_dns_opportunistic_mode(self):
"""Test doh flag with below situation.
- Android device in opportunistic mode
- DNS server supporting both Dns, DoT and DoH protocols
- IPv4-IPv6 network
"""
self.default_dns = cconst.DNS_GOOGLE_ADDR_V4
self.default_dns_v6 = cconst.DNS_GOOGLE_ADDR_V6
self._test_public_doh_mode(self.dut, self.get_wifi_network(),
cconst.PRIVATE_DNS_MODE_OPPORTUNISTIC)
def test_mix_server_ipv4_only_wifi_network_with_dns_off_mode(self):
"""Test doh with below situation.
- Android device in dns off mode
- DNS server supporting both Dns, DoT and DoH protocols
- IPv4-only network
"""
self.default_dns = cconst.DNS_GOOGLE_ADDR_V4
self._test_public_doh_mode(self.dut, self.get_wifi_network(),
cconst.PRIVATE_DNS_MODE_OFF)
def test_mix_server_ipv4_ipv6_wifi_network_with_dns_off_mode(self):
"""Test doh with below situation.
- Android device in dns off mode
- DNS server supporting both Dns, DoT and DoH protocols
- IPv4-IPv6 network
"""
self.default_dns = cconst.DNS_GOOGLE_ADDR_V4
self.default_dns_v6 = cconst.DNS_GOOGLE_ADDR_V6
self._test_public_doh_mode(self.dut, self.get_wifi_network(),
cconst.PRIVATE_DNS_MODE_OFF)