[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
diff --git a/adb/adb.cpp b/adb/adb.cpp
index aced079..dcec0ba 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -1086,6 +1086,11 @@
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 49d3a91..3a6b0b1 100644
--- a/adb/adb_wifi.h
+++ b/adb/adb_wifi.h
@@ -28,6 +28,7 @@
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 27be28f..caf4e86 100644
--- a/adb/client/adb_client.h
+++ b/adb/client/adb_client.h
@@ -90,8 +90,9 @@
// 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<void(
- const char* _Nonnull service_name, const char* _Nonnull ip_address, uint16_t port)>;
+using adb_secure_foreach_service_callback =
+ std::function<void(const char* _Nonnull service_name, const char* _Nonnull reg_type,
+ const char* _Nonnull ip_address, uint16_t port)>;
// 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 7b8b2d7..d565e01 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -128,6 +128,7 @@
" 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 @@
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 38a760f..2bf062f 100644
--- a/adb/client/transport_mdns.cpp
+++ b/adb/client/transport_mdns.cpp
@@ -216,6 +216,9 @@
int adbSecureServiceType = serviceIndex();
switch (adbSecureServiceType) {
+ case kADBTransportServiceRefIndex:
+ sAdbTransportServices->push_back(this);
+ break;
case kADBSecurePairingServiceRefIndex:
sAdbSecurePairingServices->push_back(this);
break;
@@ -233,16 +236,21 @@
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<ResolvedService*>;
+ // 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);
@@ -264,13 +272,19 @@
};
// static
+std::vector<ResolvedService*>* ResolvedService::sAdbTransportServices = NULL;
+
+// static
std::vector<ResolvedService*>* ResolvedService::sAdbSecurePairingServices = NULL;
// static
std::vector<ResolvedService*>* 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::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 @@
// 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 @@
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(void) {
- ResolvedService::initAdbSecure();
+ ResolvedService::initAdbServiceRegistries();
std::thread(init_mdns_transport_discovery_thread).detach();
}
@@ -559,3 +577,17 @@
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 6989e3b..03bdcbd 100755
--- a/adb/test_adb.py
+++ b/adb/test_adb.py
@@ -32,6 +32,8 @@
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 @@
"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."""