[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
This commit is contained in:
Joshua Duong 2020-03-31 08:39:24 -07:00
parent 504d393176
commit 13c639e0bb
6 changed files with 137 additions and 10 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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<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

View File

@ -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]);
}

View File

@ -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<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);
@ -263,6 +271,9 @@ class ResolvedService : public AsyncServiceRef {
int serviceVersion_;
};
// static
std::vector<ResolvedService*>* ResolvedService::sAdbTransportServices = NULL;
// static
std::vector<ResolvedService*>* ResolvedService::sAdbSecurePairingServices = NULL;
@ -270,7 +281,10 @@ std::vector<ResolvedService*>* ResolvedService::sAdbSecurePairingServices = NULL
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::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;
}

View File

@ -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."""