From 504d393176a56763585490258aee70d3490e7a71 Mon Sep 17 00:00:00 2001 From: Joshua Duong Date: Tue, 31 Mar 2020 10:58:50 -0700 Subject: [PATCH 1/2] [adb client] Add "adb mdns check" command. This command will check if the mdns daemon is available on the host machine. Bug: 152510294 Test: pkill -9 mdnsd; adb mdns check; mdnsd; adb mdns check; Test: test_adb.py Change-Id: If644678a339763817a8a7adcbdc545626d161aba --- adb/adb.cpp | 18 ++++++++++++++++++ adb/adb_wifi.h | 2 ++ adb/client/commandline.cpp | 20 ++++++++++++++++++++ adb/client/transport_mdns.cpp | 14 ++++++++++++++ adb/test_adb.py | 11 +++++++++++ 5 files changed, 65 insertions(+) diff --git a/adb/adb.cpp b/adb/adb.cpp index 06fdb69a1..aced079ae 100644 --- a/adb/adb.cpp +++ b/adb/adb.cpp @@ -1076,6 +1076,20 @@ void adb_set_reject_kill_server(bool value) { g_reject_kill_server = value; } +static bool handle_mdns_request(std::string_view service, int reply_fd) { + if (!android::base::ConsumePrefix(&service, "mdns:")) { + return false; + } + + if (service == "check") { + std::string check = mdns_check(); + SendOkay(reply_fd, check); + return true; + } + + return false; +} + HostRequestResult handle_host_request(std::string_view service, TransportType type, const char* serial, TransportId transport_id, int reply_fd, asocket* s) { @@ -1320,6 +1334,10 @@ HostRequestResult handle_host_request(std::string_view service, TransportType ty return HostRequestResult::Handled; } + if (handle_mdns_request(service, reply_fd)) { + return HostRequestResult::Handled; + } + return HostRequestResult::Unhandled; } diff --git a/adb/adb_wifi.h b/adb/adb_wifi.h index 585748c91..49d3a9134 100644 --- a/adb/adb_wifi.h +++ b/adb/adb_wifi.h @@ -27,6 +27,8 @@ void adb_wifi_pair_device(const std::string& host, const std::string& password, std::string& response); bool adb_wifi_is_known_host(const std::string& host); +std::string mdns_check(); + #else // !ADB_HOST struct AdbdAuthContext; diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp index 29f9dc14c..7b8b2d770 100644 --- a/adb/client/commandline.cpp +++ b/adb/client/commandline.cpp @@ -127,6 +127,7 @@ static void help() { " localfilesystem:\n" " reverse --remove REMOTE remove specific reverse socket connection\n" " reverse --remove-all remove all reverse socket connections from device\n" + " mdns check check if mdns discovery is available\n" "\n" "file transfer:\n" " push [--sync] [-z ALGORITHM] [-Z] LOCAL... REMOTE\n" @@ -1910,6 +1911,25 @@ int adb_commandline(int argc, const char** argv) { ReadOrderlyShutdown(fd); return 0; + } else if (!strcmp(argv[0], "mdns")) { + --argc; + if (argc < 1) error_exit("mdns requires an argument"); + ++argv; + + std::string error; + if (!adb_check_server_version(&error)) { + error_exit("failed to check server version: %s", error.c_str()); + } + + std::string query = "host:mdns:"; + if (!strcmp(argv[0], "check")) { + if (argc != 1) error_exit("mdns %s doesn't take any arguments", argv[0]); + query += "check"; + } else { + error_exit("unknown mdns command [%s]", argv[0]); + } + + return adb_query_command(query); } /* do_sync_*() commands */ else if (!strcmp(argv[0], "ls")) { diff --git a/adb/client/transport_mdns.cpp b/adb/client/transport_mdns.cpp index 22b9b1808..38a760fcb 100644 --- a/adb/client/transport_mdns.cpp +++ b/adb/client/transport_mdns.cpp @@ -545,3 +545,17 @@ void init_mdns_transport_discovery(void) { ResolvedService::initAdbSecure(); std::thread(init_mdns_transport_discovery_thread).detach(); } + +std::string mdns_check() { + uint32_t daemon_version; + uint32_t sz = sizeof(daemon_version); + + auto dnserr = DNSServiceGetProperty(kDNSServiceProperty_DaemonVersion, &daemon_version, &sz); + std::string result = "ERROR: mdns daemon unavailable"; + if (dnserr != kDNSServiceErr_NoError) { + return result; + } + + result = android::base::StringPrintf("mdns daemon version [%u]", daemon_version); + return result; +} diff --git a/adb/test_adb.py b/adb/test_adb.py index c872fb0f7..6989e3ba9 100755 --- a/adb/test_adb.py +++ b/adb/test_adb.py @@ -576,6 +576,17 @@ class PowerTest(unittest.TestCase): # If the power event was detected, the adb shell command should be broken very quickly. self.assertLess(end - start, 2) +"""Use 'adb mdns check' to see if mdns discovery is available.""" +def is_adb_mdns_available(): + with adb_server() as server_port: + output = subprocess.check_output(["adb", "-P", str(server_port), + "mdns", "check"]).strip() + return output.startswith(b"mdns daemon version") + +@unittest.skipIf(not is_adb_mdns_available(), "mdns feature not available") +class MdnsTest(unittest.TestCase): + """Tests for adb mdns.""" + pass def main(): """Main entrypoint.""" From 13c639e0bb59cd77977dbc46f89c4a2a833edb1f Mon Sep 17 00:00:00 2001 From: Joshua Duong Date: Tue, 31 Mar 2020 08:39:24 -0700 Subject: [PATCH 2/2] [adb client] Add "mdns services" command. This command list all discovered mdns services, so we can connect via service name later on. Bug: 152521166 Test: 'adb mdns services' Test: test_adb.py Change-Id: I23d42a7933e67a65bd0c9924afd6abe5915c0a11 --- adb/adb.cpp | 5 +++ adb/adb_wifi.h | 1 + adb/client/adb_client.h | 5 ++- adb/client/commandline.cpp | 5 +++ adb/client/transport_mdns.cpp | 46 ++++++++++++++++--- adb/test_adb.py | 85 ++++++++++++++++++++++++++++++++++- 6 files changed, 137 insertions(+), 10 deletions(-) diff --git a/adb/adb.cpp b/adb/adb.cpp index aced079ae..dcec0ba11 100644 --- a/adb/adb.cpp +++ b/adb/adb.cpp @@ -1086,6 +1086,11 @@ static bool handle_mdns_request(std::string_view service, int reply_fd) { SendOkay(reply_fd, check); return true; } + if (service == "services") { + std::string services_list = mdns_list_discovered_services(); + SendOkay(reply_fd, services_list); + return true; + } return false; } diff --git a/adb/adb_wifi.h b/adb/adb_wifi.h index 49d3a9134..3a6b0b133 100644 --- a/adb/adb_wifi.h +++ b/adb/adb_wifi.h @@ -28,6 +28,7 @@ void adb_wifi_pair_device(const std::string& host, const std::string& password, bool adb_wifi_is_known_host(const std::string& host); std::string mdns_check(); +std::string mdns_list_discovered_services(); #else // !ADB_HOST diff --git a/adb/client/adb_client.h b/adb/client/adb_client.h index 27be28f35..caf4e86ac 100644 --- a/adb/client/adb_client.h +++ b/adb/client/adb_client.h @@ -90,8 +90,9 @@ extern const char* _Nullable * _Nullable __adb_envp; // ADB Secure DNS service interface. Used to query what ADB Secure DNS services have been // resolved, and to run some kind of callback for each one. -using adb_secure_foreach_service_callback = std::function; +using adb_secure_foreach_service_callback = + std::function; // Queries pairing/connect services that have been discovered and resolved. // If |host_name| is not null, run |cb| only for services diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp index 7b8b2d770..d565e0133 100644 --- a/adb/client/commandline.cpp +++ b/adb/client/commandline.cpp @@ -128,6 +128,7 @@ static void help() { " reverse --remove REMOTE remove specific reverse socket connection\n" " reverse --remove-all remove all reverse socket connections from device\n" " mdns check check if mdns discovery is available\n" + " mdns services list all discovered services\n" "\n" "file transfer:\n" " push [--sync] [-z ALGORITHM] [-Z] LOCAL... REMOTE\n" @@ -1925,6 +1926,10 @@ int adb_commandline(int argc, const char** argv) { if (!strcmp(argv[0], "check")) { if (argc != 1) error_exit("mdns %s doesn't take any arguments", argv[0]); query += "check"; + } else if (!strcmp(argv[0], "services")) { + if (argc != 1) error_exit("mdns %s doesn't take any arguments", argv[0]); + query += "services"; + printf("List of discovered mdns services\n"); } else { error_exit("unknown mdns command [%s]", argv[0]); } diff --git a/adb/client/transport_mdns.cpp b/adb/client/transport_mdns.cpp index 38a760fcb..2bf062f05 100644 --- a/adb/client/transport_mdns.cpp +++ b/adb/client/transport_mdns.cpp @@ -216,6 +216,9 @@ class ResolvedService : public AsyncServiceRef { int adbSecureServiceType = serviceIndex(); switch (adbSecureServiceType) { + case kADBTransportServiceRefIndex: + sAdbTransportServices->push_back(this); + break; case kADBSecurePairingServiceRefIndex: sAdbSecurePairingServices->push_back(this); break; @@ -233,16 +236,21 @@ class ResolvedService : public AsyncServiceRef { std::string serviceName() const { return serviceName_; } + std::string regType() const { return regType_; } + std::string ipAddress() const { return ip_addr_; } uint16_t port() const { return port_; } using ServiceRegistry = std::vector; + // unencrypted tcp connections + static ServiceRegistry* sAdbTransportServices; + static ServiceRegistry* sAdbSecurePairingServices; static ServiceRegistry* sAdbSecureConnectServices; - static void initAdbSecure(); + static void initAdbServiceRegistries(); static void forEachService(const ServiceRegistry& services, const std::string& hostname, adb_secure_foreach_service_callback cb); @@ -263,6 +271,9 @@ class ResolvedService : public AsyncServiceRef { int serviceVersion_; }; +// static +std::vector* ResolvedService::sAdbTransportServices = NULL; + // static std::vector* ResolvedService::sAdbSecurePairingServices = NULL; @@ -270,7 +281,10 @@ std::vector* ResolvedService::sAdbSecurePairingServices = NULL std::vector* ResolvedService::sAdbSecureConnectServices = NULL; // static -void ResolvedService::initAdbSecure() { +void ResolvedService::initAdbServiceRegistries() { + if (!sAdbTransportServices) { + sAdbTransportServices = new ServiceRegistry; + } if (!sAdbSecurePairingServices) { sAdbSecurePairingServices = new ServiceRegistry; } @@ -283,17 +297,18 @@ void ResolvedService::initAdbSecure() { void ResolvedService::forEachService(const ServiceRegistry& services, const std::string& wanted_service_name, adb_secure_foreach_service_callback cb) { - initAdbSecure(); + initAdbServiceRegistries(); for (auto service : services) { auto service_name = service->serviceName(); + auto reg_type = service->regType(); auto ip = service->ipAddress(); auto port = service->port(); if (wanted_service_name == "") { - cb(service_name.c_str(), ip.c_str(), port); + cb(service_name.c_str(), reg_type.c_str(), ip.c_str(), port); } else if (service_name == wanted_service_name) { - cb(service_name.c_str(), ip.c_str(), port); + cb(service_name.c_str(), reg_type.c_str(), ip.c_str(), port); } } } @@ -301,7 +316,7 @@ void ResolvedService::forEachService(const ServiceRegistry& services, // static bool ResolvedService::connectByServiceName(const ServiceRegistry& services, const std::string& service_name) { - initAdbSecure(); + initAdbServiceRegistries(); for (auto service : services) { if (service_name == service->serviceName()) { D("Got service_name match [%s]", service->serviceName().c_str()); @@ -398,6 +413,9 @@ static void adb_RemoveDNSService(const char* regType, const char* serviceName) { int index = adb_DNSServiceIndexByName(regType); ResolvedService::ServiceRegistry* services; switch (index) { + case kADBTransportServiceRefIndex: + services = ResolvedService::sAdbTransportServices; + break; case kADBSecurePairingServiceRefIndex: services = ResolvedService::sAdbSecurePairingServices; break; @@ -542,7 +560,7 @@ void init_mdns_transport_discovery_thread(void) { } void init_mdns_transport_discovery(void) { - ResolvedService::initAdbSecure(); + ResolvedService::initAdbServiceRegistries(); std::thread(init_mdns_transport_discovery_thread).detach(); } @@ -559,3 +577,17 @@ std::string mdns_check() { result = android::base::StringPrintf("mdns daemon version [%u]", daemon_version); return result; } + +std::string mdns_list_discovered_services() { + std::string result; + auto cb = [&](const char* service_name, const char* reg_type, const char* ip_addr, + uint16_t port) { + result += android::base::StringPrintf("%s\t%s\t%s:%u\n", service_name, reg_type, ip_addr, + port); + }; + + ResolvedService::forEachService(*ResolvedService::sAdbTransportServices, "", cb); + ResolvedService::forEachService(*ResolvedService::sAdbSecureConnectServices, "", cb); + ResolvedService::forEachService(*ResolvedService::sAdbSecurePairingServices, "", cb); + return result; +} diff --git a/adb/test_adb.py b/adb/test_adb.py index 6989e3ba9..03bdcbd8d 100755 --- a/adb/test_adb.py +++ b/adb/test_adb.py @@ -32,6 +32,8 @@ import threading import time import unittest import warnings +from importlib import util +from parameterized import parameterized_class def find_open_port(): # Find an open port. @@ -583,10 +585,91 @@ def is_adb_mdns_available(): "mdns", "check"]).strip() return output.startswith(b"mdns daemon version") +"""Check if we have zeroconf python library installed""" +def is_zeroconf_installed(): + zeroconf_spec = util.find_spec("zeroconf") + return zeroconf_spec is not None + +@contextlib.contextmanager +def zeroconf_context(ipversion): + from zeroconf import Zeroconf + """Context manager for a zeroconf instance + + This creates a zeroconf instance and returns it. + """ + + try: + zeroconf = Zeroconf(ip_version=ipversion) + yield zeroconf + finally: + zeroconf.close() + +@contextlib.contextmanager +def zeroconf_register_service(zeroconf_ctx, info): + """Context manager for a zeroconf service + + Registers a service and unregisters it on cleanup. Returns the ServiceInfo + supplied. + """ + + try: + zeroconf_ctx.register_service(info) + yield info + finally: + zeroconf_ctx.unregister_service(info) + +"""Should match the service names listed in adb_mdns.h""" +@parameterized_class(('service_name',), [ + ("adb",), + ("adb-tls-connect",), + ("adb-tls-pairing",), +]) @unittest.skipIf(not is_adb_mdns_available(), "mdns feature not available") class MdnsTest(unittest.TestCase): """Tests for adb mdns.""" - pass + + @unittest.skipIf(not is_zeroconf_installed(), "zeroconf library not installed") + def test_mdns_services_register_unregister(self): + """Ensure that `adb mdns services` correctly adds and removes a service + """ + from zeroconf import IPVersion, ServiceInfo + + def _mdns_services(port): + output = subprocess.check_output(["adb", "-P", str(port), "mdns", "services"]) + return [x.split("\t") for x in output.decode("utf8").strip().splitlines()[1:]] + + with adb_server() as server_port: + output = subprocess.check_output(["adb", "-P", str(server_port), + "mdns", "services"]).strip() + self.assertTrue(output.startswith(b"List of discovered mdns services")) + print(f"services={_mdns_services(server_port)}") + + """TODO(joshuaduong): Add ipv6 tests once we have it working in adb""" + """Register/Unregister a service""" + with zeroconf_context(IPVersion.V4Only) as zc: + serv_instance = "my_fake_test_service" + serv_type = "_" + self.service_name + "._tcp." + serv_ipaddr = socket.inet_aton("1.2.3.4") + serv_port = 12345 + service_info = ServiceInfo( + serv_type + "local.", + name=serv_instance + "." + serv_type + "local.", + addresses=[serv_ipaddr], + port=serv_port) + print(f"Registering {serv_instance}.{serv_type} ...") + with zeroconf_register_service(zc, service_info) as info: + """Give adb some time to register the service""" + time.sleep(0.25) + print(f"services={_mdns_services(server_port)}") + self.assertTrue(any((serv_instance in line and serv_type in line) + for line in _mdns_services(server_port))) + + """Give adb some time to unregister the service""" + print("Unregistering mdns service...") + time.sleep(0.25) + print(f"services={_mdns_services(server_port)}") + self.assertFalse(any((serv_instance in line and serv_type in line) + for line in _mdns_services(server_port))) def main(): """Main entrypoint."""