1# 2# Copyright 2021 - The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import time 17 18from acts import asserts 19from acts.controllers.openwrt_ap import MOBLY_CONTROLLER_CONFIG_NAME as OPENWRT 20from acts.test_decorators import test_tracker_info 21from acts_contrib.test_utils.net import connectivity_const as cconst 22from acts_contrib.test_utils.net import connectivity_test_utils as cutils 23from acts_contrib.test_utils.net.net_test_utils import start_tcpdump 24from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump 25from acts_contrib.test_utils.wifi import wifi_test_utils as wutils 26from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest 27from scapy.all import rdpcap 28from scapy.all import Scapy_Exception 29from scapy.all import TCP 30from scapy.all import UDP 31 32 33DEFAULT_DNS_TIMEOUT = 5 34 35 36class DnsOverHttpsTest(WifiBaseTest): 37 """Tests for DnsoverHttps feature.""" 38 39 def setup_class(self): 40 """Setup devices and OpenWrt for DnsoverHttps test and unpack params.""" 41 42 self.dut = self.android_devices[0] 43 if len(self.android_devices) > 1: 44 self.dut_b = self.android_devices[1] 45 for ad in self.android_devices: 46 wutils.reset_wifi(ad) 47 ad.droid.setPrivateDnsMode(True) 48 req_params = ("ping_hosts",) 49 opt_params = ("wifi_network", "configure_OpenWrt", 50 "ipv4_only_network", "ipv4_ipv6_network") 51 self.unpack_userparams(req_param_names=req_params, 52 opt_param_names=opt_params) 53 if OPENWRT in self.user_params: 54 self.openwrt = self.access_points[0] 55 if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip": 56 self.dut.log.info("Skip configure Wifi interface due to config setup.") 57 else: 58 self.configure_openwrt_ap_and_start(wpa_network=True) 59 self.tcpdump_pid = None 60 self.default_dns = None 61 self.default_dns_v6 = None 62 self._setup_doh(self.dut, self.get_wifi_network()) 63 64 def teardown_test(self): 65 wutils.reset_wifi(self.dut) 66 if OPENWRT in self.user_params: 67 self.openwrt.network_setting.del_default_dns(self.default_dns) 68 self.default_dns = None 69 self.default_dns_v6 = None 70 71 def teardown_class(self): 72 for ad in self.android_devices: 73 ad.droid.setPrivateDnsMode(True) 74 self._setup_doh(ad, self.get_wifi_network(), enable=False) 75 76 def on_fail(self, test_name, begin_time): 77 self.dut.take_bug_report(test_name, begin_time) 78 79 def get_wifi_network(self, ipv6_supported=False): 80 """Return fit network for conditions. 81 82 Args: 83 ipv6_supported: Boolean for select network. 84 Returns: 85 A dict for network object for connect wifi. 86 """ 87 if OPENWRT in self.user_params: 88 if ipv6_supported: 89 self.openwrt.network_setting.enable_ipv6() 90 self.openwrt.network_setting.setup_ipv6_bridge() 91 else: 92 self.openwrt.network_setting.disable_ipv6() 93 self.openwrt.network_setting.remove_ipv6_bridge() 94 if self.default_dns: 95 self.openwrt.network_setting.add_default_dns(self.default_dns) 96 if self.default_dns_v6: 97 for ipv6_dns in self.default_dns_v6: 98 self.openwrt.network_setting.add_default_v6_dns(ipv6_dns) 99 if hasattr(self, "configure_OpenWrt") and self.configure_OpenWrt == "skip": 100 return self.wifi_network 101 return self.openwrt.get_wifi_network() 102 if ipv6_supported: 103 return self.ipv4_ipv6_network 104 return self.ipv4_only_network 105 106 def _verify_doh_queries(self, pcap_file, over_https): 107 """Verify if DNS queries were over https or not. 108 109 Args: 110 pcap_file: tcpdump file 111 over_https: True if excepted all dns go through doh. 112 """ 113 try: 114 packets = rdpcap(pcap_file) 115 except Scapy_Exception: 116 asserts.fail("Not a valid pcap file") 117 118 for pkt in packets: 119 summary = "%s" % pkt.summary() 120 for host in self.ping_hosts: 121 host = host.split(".")[-2] 122 if UDP in pkt and pkt[UDP].sport == 53 and host in summary: 123 if over_https: 124 asserts.fail("Found query to port 53: %s" % summary) 125 else: 126 self.dut.log.info("Found query to port 53: %s" % summary) 127 if TCP in pkt and pkt[TCP].sport == 853: 128 asserts.fail("Found query to port 853: %s" % summary) 129 130 def _test_public_doh_mode(self, ad, net, dns_mode, hostname=None): 131 """Test step for DoH. 132 133 Args: 134 ad: android device object. 135 net: wifi network to connect to, LTE network if None. 136 dns_mode: private DNS mode. 137 hostname: private DNS hostname to set to. 138 """ 139 140 # set private dns mode 141 if dns_mode: 142 cutils.set_private_dns(self.dut, dns_mode, hostname) 143 # connect to wifi 144 wutils.start_wifi_connection_scan_and_ensure_network_found( 145 self.dut, net[wutils.WifiEnums.SSID_KEY]) 146 wutils.wifi_connect(self.dut, net) 147 self._verify_tls_completed() 148 149 # start tcpdump on the device 150 self.tcpdump_pid = start_tcpdump(self.dut, self.test_name) 151 152 # ping hosts should pass 153 for host in self.ping_hosts: 154 self.log.info("Pinging %s" % host) 155 status = wutils.validate_connection(self.dut, host) 156 asserts.assert_true(status, "Failed to ping host %s" % host) 157 self.log.info("Ping successful") 158 159 # stop tcpdump 160 pcap_file = stop_tcpdump(self.dut, self.tcpdump_pid, self.test_name) 161 162 # verify DNS queries 163 overhttps = dns_mode != cconst.PRIVATE_DNS_MODE_OFF 164 self._verify_doh_queries(pcap_file, overhttps) 165 166 # reset wifi 167 wutils.reset_wifi(self.dut) 168 169 def _verify_tls_completed(self, retry_count=5): 170 """Verify tls finish verification process. 171 172 Expect all private dns server status, should be 173 "success", or "fail". 174 175 Args: 176 retry_count: int for retry times. 177 Raises: 178 TimeoutError: if TLS verification stuck in processing. 179 """ 180 for attempt in range(retry_count): 181 out = self.dut.adb.shell("dumpsys dnsresolver") 182 if "status{in_process}" in out: 183 if attempt + 1 < retry_count: 184 self.dut.log.info("DoT still validating, retrying...") 185 time.sleep(DEFAULT_DNS_TIMEOUT) 186 else: 187 return 188 raise TimeoutError("Fail to completed TLS verification.") 189 190 def _setup_doh(self, ad, net, enable=True): 191 """Enable/Disable DoH option. 192 193 Args: 194 ad: android devies. 195 net: network as wifi. 196 enable: if True, sets the 'doh' experiment flag. 197 """ 198 if enable: 199 ad.adb.shell("setprop persist.device_config.netd_native.doh 1") 200 else: 201 ad.adb.shell("setprop persist.device_config.netd_native.doh 0") 202 wutils.wifi_connect(ad, net) 203 wutils.reset_wifi(ad) 204 out = ad.adb.shell("dumpsys dnsresolver |grep doh") 205 ad.log.debug(out) 206 207 def test_mix_server_ipv4_only_wifi_network_with_dns_strict_mode(self): 208 """Test doh flag with below situation. 209 210 - Android device in strict mode 211 - DNS server supporting both Dns, DoT and DoH protocols 212 - IPv4-only network 213 """ 214 self._test_public_doh_mode(self.dut, self.get_wifi_network(), 215 cconst.PRIVATE_DNS_MODE_STRICT, 216 hostname=cconst.DNS_GOOGLE_HOSTNAME) 217 218 def test_mix_server_ipv4_ipv6_wifi_network_with_dns_strict_mode(self): 219 """Test doh flag with below situation. 220 221 - Android device in strict mode 222 - DNS server supporting both Dns, DoT and DoH protocols 223 - IPv4-IPv6 network 224 """ 225 self._test_public_doh_mode(self.dut, self.get_wifi_network(), 226 cconst.PRIVATE_DNS_MODE_STRICT, 227 hostname=cconst.DNS_GOOGLE_HOSTNAME) 228 229 def test_pure_server_ipv4_only_wifi_network_with_dns_strict_mode(self): 230 """Test doh flag with below situation. 231 232 - Android device in strict mode 233 - DNS server only supporting DoH protocols 234 - IPv4-only network 235 """ 236 self._test_public_doh_mode(self.dut, self.get_wifi_network(), 237 cconst.PRIVATE_DNS_MODE_STRICT, 238 hostname=cconst.DOH_CLOUDFLARE_HOSTNAME) 239 240 def test_pure_server_ipv4_ipv6_wifi_network_with_dns_strict_mode(self): 241 """Test doh flag with below situation. 242 243 - Android device in strict mode 244 - DNS server only supporting DoH protocols 245 - IPv4-IPv6 network 246 """ 247 self._test_public_doh_mode(self.dut, self.get_wifi_network(), 248 cconst.PRIVATE_DNS_MODE_STRICT, 249 hostname=cconst.DOH_CLOUDFLARE_HOSTNAME) 250 251 def test_mix_server_ipv4_only_wifi_network_with_dns_opportunistic_mode(self): 252 """Test doh flag with below situation. 253 254 - Android device in opportunistic mode 255 - DNS server supporting both Dns, DoT and DoH protocols 256 - IPv4-only network 257 """ 258 self.default_dns = cconst.DNS_GOOGLE_ADDR_V4 259 self._test_public_doh_mode(self.dut, self.get_wifi_network(), 260 cconst.PRIVATE_DNS_MODE_OPPORTUNISTIC) 261 262 def test_mix_server_ipv4_ipv6_wifi_network_with_dns_opportunistic_mode(self): 263 """Test doh flag with below situation. 264 265 - Android device in opportunistic mode 266 - DNS server supporting both Dns, DoT and DoH protocols 267 - IPv4-IPv6 network 268 """ 269 self.default_dns = cconst.DNS_GOOGLE_ADDR_V4 270 self.default_dns_v6 = cconst.DNS_GOOGLE_ADDR_V6 271 self._test_public_doh_mode(self.dut, self.get_wifi_network(), 272 cconst.PRIVATE_DNS_MODE_OPPORTUNISTIC) 273 274 def test_mix_server_ipv4_only_wifi_network_with_dns_off_mode(self): 275 """Test doh with below situation. 276 277 - Android device in dns off mode 278 - DNS server supporting both Dns, DoT and DoH protocols 279 - IPv4-only network 280 """ 281 self.default_dns = cconst.DNS_GOOGLE_ADDR_V4 282 self._test_public_doh_mode(self.dut, self.get_wifi_network(), 283 cconst.PRIVATE_DNS_MODE_OFF) 284 285 def test_mix_server_ipv4_ipv6_wifi_network_with_dns_off_mode(self): 286 """Test doh with below situation. 287 288 - Android device in dns off mode 289 - DNS server supporting both Dns, DoT and DoH protocols 290 - IPv4-IPv6 network 291 """ 292 self.default_dns = cconst.DNS_GOOGLE_ADDR_V4 293 self.default_dns_v6 = cconst.DNS_GOOGLE_ADDR_V6 294 self._test_public_doh_mode(self.dut, self.get_wifi_network(), 295 cconst.PRIVATE_DNS_MODE_OFF) 296