From 13c639e0bb59cd77977dbc46f89c4a2a833edb1f Mon Sep 17 00:00:00 2001 From: Joshua Duong Date: Tue, 31 Mar 2020 08:39:24 -0700 Subject: [PATCH] [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."""