class DiscoveryClientService: _SERVICE_TYPE = '_http._tcp.local.' _SERVICE_NAME = 'sound-system-server._http._tcp.local.' def __init__(self, loop, notify): self._zeroconf = Zeroconf() self._listener = self.ServiceListener(loop, notify) self._browser = None def start(self): self._browser = ServiceBrowser(self._zeroconf, self._SERVICE_TYPE, self._listener) def stop(self): if self._browser is not None: self._browser.cancel() self._browser = None class ServiceListener: def __init__(self, loop, notify): self._loop = loop self._notify = notify def add_service(self, zeroconf, t, name): if name == DiscoveryClientService._SERVICE_NAME: info = zeroconf.get_service_info(t, name) self._loop.call_soon_threadsafe(self._notify, name, info) def remove_service(self, zeroconf, t, name): pass
def discover_homekit_devices( max_seconds: int = 10, zeroconf_instance: "Zeroconf" = None) -> List[Any]: """ This method discovers all HomeKit Accessories. It browses for devices in the _hap._tcp.local. domain and checks if all required fields are set in the text record. It one field is missing, it will be excluded from the result list. :param max_seconds: the number of seconds we will wait for the devices to be discovered :return: a list of dicts containing all fields as described in table 5.7 page 69 """ zeroconf = zeroconf_instance or Zeroconf() listener = CollectingListener() service_browser = ServiceBrowser(zeroconf, HAP_TYPE, listener) sleep(max_seconds) tmp = [] try: for info in listener.get_data(): data = _build_data_from_service_info(info) if "c#" not in data or "md" not in data: continue logging.debug("found Homekit IP accessory %s", data) tmp.append(data) finally: service_browser.cancel() if not zeroconf_instance: zeroconf.close() return tmp
def _find_data_for_device_id( device_id: str, max_seconds: int = 10, zeroconf_instance: "Zeroconf" = None) -> Tuple[str, int]: """ Try to find a HomeKit Accessory via Bonjour. The process is time boxed by the second parameter which sets an upper limit of `max_seconds` before it times out. The runtime of the function may be longer because of the Bonjour handling code. """ zeroconf = zeroconf_instance or Zeroconf() found_device_event = threading.Event() listener = CollectingListener(device_id=device_id, found_device_event=found_device_event) service_browser = ServiceBrowser(zeroconf, HAP_TYPE, listener) found_device_event.wait(timeout=max_seconds) try: data = listener.get_data() for info in data: if info.properties[b"id"].decode() == device_id: logging.debug("Located Homekit IP accessory %s", info.properties) return _build_data_from_service_info(info) finally: service_browser.cancel() if not zeroconf_instance: zeroconf.close() raise AccessoryNotFoundError( "Device not found via Bonjour within 10 seconds")
def test_integration(): service_added = Event() service_removed = Event() type_ = "_http._tcp.local." registration_name = "xxxyyy.%s" % type_ def on_service_state_change(zeroconf, service_type, state_change, name): if name == registration_name: if state_change is ServiceStateChange.Added: service_added.set() elif state_change is ServiceStateChange.Removed: service_removed.set() zeroconf_browser = Zeroconf() browser = ServiceBrowser(zeroconf_browser, type_, [on_service_state_change]) zeroconf_registrar = Zeroconf() desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, socket.inet_aton("10.0.1.2"), 80, 0, 0, desc, "ash-2.local.") zeroconf_registrar.register_service(info) try: service_added.wait(1) assert service_added.is_set() # Don't remove service, allow close() to cleanup finally: zeroconf_registrar.close() browser.cancel() zeroconf_browser.close()
def discover_homekit_devices(): """ Search for HAP devices on the network. :returns: list of dicts containing service info """ zeroconf = Zeroconf() listener = CollectingListener() browser = ServiceBrowser(zeroconf, '_hap._tcp.local.', listener) sleep(1) devices = [] for info in listener.get_data(): device = { 'name': info.name, 'address': inet_ntoa(info.address), 'port': info.port, 'c#': int(info.properties[b'c#'].decode()), 'ff': int(info.properties[b'ff'].decode()), 'id': info.properties[b'id'].decode(), 'md': info.properties[b'md'].decode(), 'pv': info.properties[b'pv'].decode(), 's#': int(info.properties[b's#'].decode()), 'sf': int(info.properties[b'sf'].decode()), 'ci': int(info.properties[b'ci'].decode()), } devices.append(device) browser.cancel() zeroconf.close() return devices
def find_device_ip_and_port(device_id: str): """ Find a specific device on the network. :param device_id: ID of device to search for :returns: dict containing IP and port, if found, else None """ result = None zeroconf = Zeroconf() listener = CollectingListener() browser = ServiceBrowser(zeroconf, '_hap._tcp.local.', listener) counter = 0 while result is None and counter < 10: sleep(1) data = listener.get_data() for info in data: if info.properties[b'id'].decode() == device_id: result = {'ip': inet_ntoa(info.address), 'port': info.port} break counter += 1 browser.cancel() zeroconf.close() return result
def _main_loop(): """Main Zeroconf service loop that starts browsing for WoT services and processes the register tasks queue.""" browser = None try: browser = ServiceBrowser( zeroconf, DNSSDDiscoveryService.WOT_SERVICE_TYPE, handlers=[_on_service_change]) while not close_event.is_set(): try: register_task = register_queue.get_nowait() task_handler_map = { TASK_REGISTER: _register, TASK_UNREGISTER: _unregister } task_handler_map[register_task['type']](register_task) except queue.Empty: pass close_event.wait(ITER_WAIT) finally: if browser is not None: browser.cancel() with registered_lock: for serv_info in registered: zeroconf.unregister_service(serv_info) zeroconf.close()
def test_integration(): service_added = Event() service_removed = Event() type_ = "_http._tcp.local." registration_name = "xxxyyy.%s" % type_ def on_service_state_change(zeroconf, service_type, state_change, name): if name == registration_name: if state_change is ServiceStateChange.Added: service_added.set() elif state_change is ServiceStateChange.Removed: service_removed.set() zeroconf_browser = Zeroconf(interfaces=['127.0.0.1']) browser = ServiceBrowser(zeroconf_browser, type_, [on_service_state_change]) zeroconf_registrar = Zeroconf(interfaces=['127.0.0.1']) desc = {'path': '/~paulsm/'} info = ServiceInfo(type_, registration_name, socket.inet_aton("10.0.1.2"), 80, 0, 0, desc, "ash-2.local.") zeroconf_registrar.register_service(info) try: service_added.wait(1) assert service_added.is_set() # Don't remove service, allow close() to cleanup finally: zeroconf_registrar.close() browser.cancel() zeroconf_browser.close()
class Discoverer: ANNOUNCE_PERIOD_SECS = 1 MAXIMUM_WAIT_CYCLES = 10 SERVICE_TYPE = "_googlemdt._tcp.local." def __init__(self, listener=None): self.discoveries = {} self.listener = listener self.zeroconf = None def discover(self): self.zeroconf = Zeroconf() self.browser = ServiceBrowser(self.zeroconf, Discoverer.SERVICE_TYPE, self) self._heard_announcement = True cycle_count = 0 # Keep waiting until we stop hearing announcements for a full second, or until we've waited 10 seconds while self._heard_announcement and cycle_count < Discoverer.MAXIMUM_WAIT_CYCLES: cycle_count += 1 self._heard_announcement = False time.sleep(Discoverer.ANNOUNCE_PERIOD_SECS) self.browser.cancel() self.browser = None self.zeroconf = None def add_service(self, zeroconf, type, name): info = self.zeroconf.get_service_info(type, name) if info: hostname = info.server.split('.')[0] address = info.parsed_addresses()[0] # Prevent duplicate announcements from extending the discovery delay if hostname not in self.discoveries: self._heard_announcement = True self.discoveries[hostname] = address if self.listener and hasattr(self.listener, "add_device"): self.listener.add_device(hostname, address) def remove_service(self, zeroconf, type, name): info = self.zeroconf.get_service_info(type, name) if info: if self.listener and hasattr(self.listener, "remove_device"): self.listener.remove_device(info.server, self.discoveries[info.server]) if info.server in self.discoveries: self._heard_announcement = True del (self.discoveries[info.server]) def update_service(self, zeroconf, type, name): """TODO(jtgans): Add this method once zeroconf figures out what it's for.""" pass
class DashboardImportDiscovery: def __init__(self, zc: Zeroconf) -> None: self.zc = zc self.service_browser = ServiceBrowser( self.zc, ESPHOME_SERVICE_TYPE, [self._on_update] ) self.import_state = {} def _on_update( self, zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange, ) -> None: _LOGGER.debug( "service_update: type=%s name=%s state_change=%s", service_type, name, state_change, ) if service_type != ESPHOME_SERVICE_TYPE: return if state_change == ServiceStateChange.Removed: self.import_state.pop(name, None) info = zeroconf.get_service_info(service_type, name) _LOGGER.debug("-> resolved info: %s", info) if info is None: return node_name = name[: -len(ESPHOME_SERVICE_TYPE) - 1] required_keys = [ TXT_RECORD_PACKAGE_IMPORT_URL, TXT_RECORD_PROJECT_NAME, TXT_RECORD_PROJECT_VERSION, ] if any(key not in info.properties for key in required_keys): # Not a dashboard import device return import_url = info.properties[TXT_RECORD_PACKAGE_IMPORT_URL].decode() project_name = info.properties[TXT_RECORD_PROJECT_NAME].decode() project_version = info.properties[TXT_RECORD_PROJECT_VERSION].decode() self.import_state[name] = DiscoveredImport( device_name=node_name, package_import_url=import_url, project_name=project_name, project_version=project_version, ) def cancel(self) -> None: self.service_browser.cancel()
class DysonDiscovery: """Dyson device discovery.""" def __init__(self): """Initialize the instance.""" self._registered = {} self._discovered = {} self._lock = threading.Lock() self._browser = None def register_device(self, device: DysonDevice, callback: Callable[[str], None]) -> None: """Register a device.""" with self._lock: if device.serial in self._discovered: callback(self._discovered[device.serial]) else: self._registered[device.serial] = callback def device_discovered(self, info: ServiceInfo) -> None: """Call when a device is discovered.""" if info.type == TYPE_DYSON_360_EYE: serial = (info.name.split(".")[0]).split("-", 1)[1] else: # TYPE_DYSON_FAN serial = (info.name.split(".")[0]).split("_")[1] address = socket.inet_ntoa(info.addresses[0]) with self._lock: if serial in self._registered: callback = self._registered.pop(serial) callback(address) else: self._discovered[serial] = address def start_discovery(self, zeroconf_instance: Optional[Zeroconf] = None) -> None: """Start discovery.""" listener = DysonListener(self) zeroconf = zeroconf_instance or Zeroconf() self._browser = ServiceBrowser( zeroconf, [TYPE_DYSON_360_EYE, TYPE_DYSON_FAN], listener, ) def stop_discovery(self) -> None: """Stop discovery.""" try: self._browser.cancel() except RuntimeError: # Throws when called from callback # cannot join current thread pass self._browser.zc.close() self._browser = None
def run(self): zeroconf = Zeroconf() browser = ServiceBrowser(zeroconf, GOOGLE_CAST_IDENTIFIER, self) try: with self.run_condition: self.run_condition.wait() self.logger.debug("end of run-body (discovery)") finally: browser.cancel() zeroconf.close()
class AirDropBrowser: def __init__(self, config): self.ip_addr = AirDropUtil.get_ip_for_interface(config.interface, ipv6=True) if self.ip_addr is None: if config.interface == "awdl0": raise RuntimeError( f"Interface {config.interface} does not have an IPv6 address. Make sure that `owl` is running." ) else: raise RuntimeError( f"Interface {config.interface} does not have an IPv6 address" ) self.zeroconf = Zeroconf( interfaces=[str(self.ip_addr)], ip_version=IPVersion.V6Only, apple_p2p=platform.system() == "Darwin", ) self.callback_add = None self.callback_remove = None self.browser = None def start(self, callback_add=None, callback_remove=None): """ Start the AirDropBrowser to discover other AirDrop devices """ if self.browser is not None: return # already started self.callback_add = callback_add self.callback_remove = callback_remove self.browser = ServiceBrowser(self.zeroconf, "_airdrop._tcp.local.", self) def stop(self): self.browser.cancel() self.browser = None self.zeroconf.close() def add_service(self, zeroconf, service_type, name): info = zeroconf.get_service_info(service_type, name) logger.debug(f"Add service {name}") if self.callback_add is not None: self.callback_add(info) def remove_service(self, zeroconf, service_type, name): info = zeroconf.get_service_info(service_type, name) logger.debug(f"Remove service {name}") if self.callback_remove is not None: self.callback_remove(info)
def main(): # Try to query server status... success = False try: # Alert user... print(_("Searching LAN for Helios servers... (ctrl-c to cancel)")) # Initialize Zeroconf... zeroconf = Zeroconf() # Construct listener... listener = LocalNetworkServiceListener() # Begin listening... browser = ServiceBrowser(zc=zeroconf, type_="_http._tcp.local.", listener=listener) browser_tls = ServiceBrowser(zc=zeroconf, type_="_https._tcp.local.", listener=listener) # Keep blocking while scanning... try: while True: sleep(0.1) # Unless the user requests to abort... except KeyboardInterrupt: print(_(F"\rAborting, please wait...")) # Cleanup Zeroconf... finally: browser.cancel() browser_tls.cancel() zeroconf.close() # Set exit status... success = True # Some other kind of exception... except Exception as some_exception: print(_(F"An unknown exception occurred: {print(some_exception)}")) # If unsuccessful, bail... if not success: sys.exit(1) # Done... sys.exit(0)
def connect(self, num_retries): """ Connects to the drone :param num_retries: maximum number of retries :return: True if the connection succeeded and False otherwise """ if ("Mambo" not in self.drone_type): print("Setting up mDNS listener since this is not a Mambo") #parrot's latest mambo firmware (3.0.26 broke all of the mDNS services so this is (temporarily) commented #out but it is backwards compatible and will work with the hard-coded addresses for now. zeroconf = Zeroconf() listener = mDNSListener(self) print("Making a browser for %s" % self.mdns_address) browser = ServiceBrowser(zeroconf, self.mdns_address, listener) # basically have to sleep until the info comes through on the listener num_tries = 0 while (num_tries < num_retries and not self.is_connected): time.sleep(1) num_tries += 1 # if we didn't hear the listener, return False if (not self.is_connected): color_print( "connection failed: did you remember to connect your machine to the Drone's wifi network?", "ERROR") return False else: browser.cancel() # perform the handshake and get the UDP info handshake = self._handshake(num_retries) if (handshake): self._create_udp_connection() self.listener_thread = threading.Thread(target=self._listen_socket) self.listener_thread.start() color_print("Success in setting up the wifi network to the drone!", "SUCCESS") return True else: color_print("Error: TCP handshake failed.", "ERROR") return False
class RioFinder: SERVICE_NAME = '_ni._tcp.local.' def __init__(self): self.zeroconf = Zeroconf() self.listener = Listener(self) self.browser = ServiceBrowser(self.zeroconf, RioFinder.SERVICE_NAME, self.listener) self.current_list = [] def stop(self): self.browser.cancel() def on_new_rio(self, rio: dict): if json.dumps(rio, sort_keys=True) not in self.current_list: self.current_list.append(rio)
def wait_for_wireless(name, service_name, timeout=None): # return (addresses, port) expecting_name = f"perfcat_{name}._{service_name}._tcp.local." print(f"[WIRELESS] expecting {expecting_name}") done = threading.Event() ctx = {} class MyListener: def add_service(self, zeroconf, type, name): # info = zeroconf.get_service_info(type, name) _info = MyServiceInfo(type, name) info = None if _info.request(zeroconf, 5000): info = _info if not info: if _info.request(zeroconf, 2000, True): info = _info # _info = ServiceInfo(type, name, parsed_addresses=["10.3.3.230"]) # if _info.request(zeroconf, 1000): # info = _info # print(f"[Service] `{name}` added, service info: `{info}`") ctx['addresses'] = list(map(socket.inet_ntoa, info.addresses)) print(f"[Service] `{name}` found, `{ctx}`") if name == expecting_name: ctx['addresses'] = list(map(socket.inet_ntoa, info.addresses)) ctx['port'] = info.port print(f"[Service] `{name}` found, `{ctx}`") done.set() zero_conf = Zeroconf() # zero_conf._respond_sockets.pop(0) # zero_conf1 = zero_conf._respond_sockets # for index in zero_conf1: # print(index) listener = MyListener() browser = ServiceBrowser(zero_conf, f"_{service_name}._tcp.local.", listener) if not done.wait(timeout): ctx['addresses'] = [] ctx['port'] = 0 browser.cancel() zero_conf.close() return ctx['addresses'], ctx['port']
def find_cloudlets(self, seconds_to_wait=3): print 'Started looking for cloudlets' self.services = {} zeroconf = Zeroconf() browser = ServiceBrowser(zeroconf, CloudletFinder.CLOUDLET_SERVICE_DNS, listener=self) # Wait to find cloudlets. print 'Waiting for results' time.sleep(seconds_to_wait) # Stop looking for cloudlets. browser.cancel() # Return the list of cloudlets found. print 'Cloudlets found: ' print self.services return self.services
def Wait_for_privet_mdns_service(t_seconds, service, logger, wifi_interfaces=[]): """Listens for t_seconds and returns an information object for each service. This is the primary interface to discover mDNS services. It blocks for t_seconds while listening, and returns a list of information objects, one for each service discovered. Args: t_seconds: Time to listen for mDNS records, in seconds. Floating point ok. service: The service to wait for, if found, return early is_add: If True, wait for service to be added If False, wait for service to be removed wifi_interfaces: The interfaces to listen on as strings, if empty listen on all interfaces. For example: ['192.168.1.2']. Returns: If Add event observed, return the Zeroconf information class; otherwise, return None """ l = _Listener(logger) if not wifi_interfaces: z = Zeroconf() else: z = Zeroconf(wifi_interfaces) sb = ServiceBrowser(zc=z, type_='_privet._tcp.local.', listener=l) service_info = wait_for_service_add(t_seconds, service, l) sb.cancel() # Only method available to kill all threads pylint: disable=protected-access z._GLOBAL_DONE = True zeroconf_threads = _find_zeroconf_threads() # Wait up to 30 seconds for zeroconf to terminate its threads t_end = time.time() + 30 while len(zeroconf_threads) > 1 and time.time() < t_end: time.sleep(0.01) zeroconf_threads = _find_zeroconf_threads() z.close() if len(zeroconf_threads) > 1: logger.info('Zeroconf failed to terminate its threads in 30 seconds.') else: logger.info('All listeners have been stopped.') return service_info
class ZeroConfListener(): """ The ZeroConfListener handles the browse off all Zeptrion Air. It listen for all Zeptrion Air devices available on zeroconf. """ def __init__(self, add_service_handler): """ Init the ZeroConfListener. :param add_service_handler: -- that gets the new found devices """ self._add_service_handler = add_service_handler self._zeroconf = None self._browser = None self._service_type = "_zapp._tcp.local." def start_searching(self): """Start searching.""" self._zeroconf = Zeroconf() self._browser = ServiceBrowser(self._zeroconf, self._service_type, self) def stop_searching(self): """Stop searching.""" self._browser.cancel() self._zeroconf.close() @staticmethod def is_a_zapp_device(name): """Test id the device is a zeptrionAir device.""" return name.startswith('zapp-') and name[5:13].isdigit() def remove_service(self, zeroconf, _type, name): """Remove a service.""" pass def add_service(self, zeroconf, _type, name): """Add a zeptrion Service and call the add_service_handler.""" info = zeroconf.get_service_info(_type, name) if info: if self.is_a_zapp_device(info.name): _ip = socket.inet_ntoa(info.address) self._add_service_handler(name, _ip, info.port)
def discover_network() -> dict[str, Device]: """ Discover devices that expose the devolo device API via mDNS synchronous. :return: Devices accessible via serial number. """ devices: dict[str, Device] = {} def add(zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange) -> None: """React on state changes.""" _add(devices, zeroconf, service_type, name, state_change) browser = ServiceBrowser(Zeroconf(), SERVICE_TYPE, [add], question_type=DNSQuestionType.QM) time.sleep(3) browser.cancel() return devices
def connect(self, num_retries): """ Connects to the drone :param num_retries: maximum number of retries :return: True if the connection succeeded and False otherwise """ zeroconf = Zeroconf() listener = mDNSListener(self) browser = ServiceBrowser(zeroconf, self.mdns_address, listener) # basically have to sleep until the info comes through on the listener num_tries = 0 while (num_tries < num_retries and not self.is_connected): time.sleep(1) num_tries += 1 # if we didn't hear the listener, return False if (not self.is_connected): color_print( "connection failed: did you remember to connect your machine to the Drone's wifi network?", "ERROR") return False else: browser.cancel() # perform the handshake and get the UDP info handshake = self._handshake(num_retries) if (handshake): self._create_udp_connection() self.listener_thread = threading.Thread(target=self._listen_socket) self.listener_thread.start() color_print("Success in setting up the wifi network to the drone!", "SUCCESS") return True else: color_print("Error: TCP handshake failed.", "ERROR") return False
def discover_chromecasts(max_devices=None, timeout=DISCOVER_TIMEOUT): """ Discover chromecasts on the network. """ try: zconf = Zeroconf() listener = CastListener() browser = ServiceBrowser(zconf, "_googlecast._tcp.local.", listener) if max_devices is None: time.sleep(timeout) return listener.devices else: start = time.time() while (time.time() - start < timeout and listener.count < max_devices): time.sleep(.1) return listener.devices finally: browser.cancel() zconf.close()
def test_integration(): service_added = Event() service_removed = Event() type_ = "_http._tcp.local." registration_name = "xxxyyy.%s" % type_ class MyListener(object): def remove_service(self, zeroconf, type_, name): if name == registration_name: service_removed.set() def add_service(self, zeroconf, type_, name): if name == registration_name: service_added.set() zeroconf_browser = Zeroconf() listener = MyListener() browser = ServiceBrowser(zeroconf_browser, type_, listener) zeroconf_registrar = Zeroconf() desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, socket.inet_aton("10.0.1.2"), 80, 0, 0, desc, "ash-2.local.") zeroconf_registrar.register_service(info) try: service_added.wait(1) assert service_added.is_set() zeroconf_registrar.unregister_service(info) service_removed.wait(1) assert service_removed.is_set() finally: zeroconf_registrar.close() browser.cancel() zeroconf_browser.close()
def main(): # Catch CNTRL-C signel global SHUTDOWN signal.signal(signal.SIGINT, signal_cntrl_c) init_logging("", 2) context = zmq.Context.instance() zeroconf = Zeroconf() listener = ZeroConfListener(context) browser = ServiceBrowser(zeroconf, "_DashZMQ._tcp.local.", listener) b = zmq_tcpBridge(tcp_port=5001, context=context) while not SHUTDOWN: time.sleep(1) print("Goodbye") zeroconf.unregister_all_services() b.close() time.sleep(1) zeroconf.close() browser.cancel()
class YandexIOListener: add_handlerer = None browser = None def __init__(self, loop): self.loop = loop def start(self, handlerer: Callable, zeroconf: Zeroconf): self.add_handlerer = handlerer self.browser = ServiceBrowser(zeroconf, '_yandexio._tcp.local.', handlers=[self._zeroconf_handler]) def stop(self, *args): self.browser.cancel() self.browser.zc.close() def _zeroconf_handler(self, zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange): info = zeroconf.get_service_info(service_type, name) if not info: return properties = { k.decode(): v.decode() if isinstance(v, bytes) else v for k, v in info.properties.items() } coro = self.add_handlerer({ 'device_id': properties['deviceId'], 'platform': properties['platform'], 'host': str(ipaddress.ip_address(info.addresses[0])), 'port': info.port }) self.loop.create_task(coro)
def test_notify_listeners(): """Test adding and removing notify listeners.""" # instantiate a zeroconf instance zc = Zeroconf(interfaces=['127.0.0.1']) notify_called = 0 class TestNotifyListener(r.NotifyListener): def notify_all(self): nonlocal notify_called notify_called += 1 with pytest.raises(NotImplementedError): r.NotifyListener().notify_all() notify_listener = TestNotifyListener() zc.add_notify_listener(notify_listener) def on_service_state_change(zeroconf, service_type, state_change, name): """Dummy service callback.""" # start a browser browser = ServiceBrowser(zc, "_http._tcp.local.", [on_service_state_change]) browser.cancel() assert notify_called zc.remove_notify_listener(notify_listener) notify_called = 0 # start a browser browser = ServiceBrowser(zc, "_http._tcp.local.", [on_service_state_change]) browser.cancel() assert not notify_called zc.close()
def discover_chromecasts(max_devices=None, timeout=DISCOVER_TIMEOUT): try: zconf = Zeroconf() listener = CastListener() browser = ServiceBrowser(zconf, "_googlecast._tcp.local.", listener) t = 0 if max_devices is None: time.sleep(DISCOVER_TIMEOUT) return listener.devices else: while t < DISCOVER_TIMEOUT: time.sleep(.1) if listener.count >= max_devices: return listener.devices return listener.devices finally: browser.cancel() zconf.close()
def browse(localname): services = [] def handler(zeroconf, service_type, name, state_change): if state_change is ServiceStateChange.Added: services.append(name) sb = ServiceBrowser(self.zeroconf, localname, [handler]) time.sleep(timeout) sb.cancel() answers, additional = [], [] for service in services: answers.append( dns.RRHeader(name=localname[:-6] + domain, ttl=ttl, type=dns.PTR, payload=dns.Record_PTR(name=service[:-6] + domain))) #txt_ans, _, _ = txt(service) #srv_ans, _, a_ans = srv(service) #additional += a_ans + txt_ans + srv_ans return answers, [], additional
def test_integration(): service_added = Event() service_removed = Event() unexpected_ttl = Event() got_query = Event() type_ = "_http._tcp.local." registration_name = "xxxyyy.%s" % type_ def on_service_state_change(zeroconf, service_type, state_change, name): if name == registration_name: if state_change is ServiceStateChange.Added: service_added.set() elif state_change is ServiceStateChange.Removed: service_removed.set() zeroconf_browser = Zeroconf(interfaces=["127.0.0.1"]) # we are going to monkey patch the zeroconf send to check packet sizes old_send = zeroconf_browser.send time_offset = 0 def current_time_millis(): """Current system time in milliseconds""" return time.time() * 1000 + time_offset * 1000 expected_ttl = r._DNS_TTL # needs to be a list so that we can modify it in our phony send nbr_queries = [0, None] def send(out, addr=r._MDNS_ADDR, port=r._MDNS_PORT): """Sends an outgoing packet.""" pout = r.DNSIncoming(out.packet()) for answer in pout.answers: nbr_queries[0] += 1 if not answer.ttl > expected_ttl / 2: unexpected_ttl.set() got_query.set() old_send(out, addr=addr, port=port) # monkey patch the zeroconf send zeroconf_browser.send = send # monkey patch the zeroconf current_time_millis r.current_time_millis = current_time_millis service_added = Event() service_removed = Event() browser = ServiceBrowser(zeroconf_browser, type_, [on_service_state_change]) zeroconf_registrar = Zeroconf(interfaces=["127.0.0.1"]) desc = {"path": "/~paulsm/"} info = ServiceInfo( type_, registration_name, socket.inet_aton("10.0.1.2"), 80, 0, 0, desc, "ash-2.local.", ) zeroconf_registrar.register_service(info) try: service_added.wait(1) assert service_added.is_set() sleep_count = 0 while nbr_queries[0] < 50: time_offset += expected_ttl / 4 zeroconf_browser.notify_all() sleep_count += 1 got_query.wait(1) got_query.clear() assert not unexpected_ttl.is_set() # Don't remove service, allow close() to cleanup finally: zeroconf_registrar.close() service_removed.wait(1) assert service_removed.is_set() browser.cancel() zeroconf_browser.close()
def test_lots_of_names(self): # instantiate a zeroconf instance zc = Zeroconf(interfaces=["127.0.0.1"]) # create a bunch of servers type_ = "_my-service._tcp.local." name = "a wonderful service" server_count = 300 self.generate_many_hosts(zc, type_, name, server_count) # verify that name changing works self.verify_name_change(zc, type_, name, server_count) # we are going to monkey patch the zeroconf send to check packet sizes old_send = zc.send # needs to be a list so that we can modify it in our phony send longest_packet = [0, None] def send(out, addr=r._MDNS_ADDR, port=r._MDNS_PORT): """Sends an outgoing packet.""" packet = out.packet() if longest_packet[0] < len(packet): longest_packet[0] = len(packet) longest_packet[1] = out old_send(out, addr=addr, port=port) # monkey patch the zeroconf send zc.send = send # dummy service callback def on_service_state_change(zeroconf, service_type, state_change, name): pass # start a browser browser = ServiceBrowser(zc, type_, [on_service_state_change]) # wait until the browse request packet has maxed out in size sleep_count = 0 while sleep_count < 100 and longest_packet[ 0] < r._MAX_MSG_ABSOLUTE - 100: sleep_count += 1 time.sleep(0.1) browser.cancel() time.sleep(0.5) import zeroconf zeroconf.log.debug("sleep_count %d, sized %d", sleep_count, longest_packet[0]) # now the browser has sent at least one request, verify the size assert longest_packet[0] <= r._MAX_MSG_ABSOLUTE assert longest_packet[0] >= r._MAX_MSG_ABSOLUTE - 100 # mock zeroconf's logger warning() and debug() from mock import patch patch_warn = patch("zeroconf.log.warning") patch_debug = patch("zeroconf.log.debug") mocked_log_warn = patch_warn.start() mocked_log_debug = patch_debug.start() # now that we have a long packet in our possession, let's verify the # exception handling. out = longest_packet[1] out.data.append(b"\0" * 1000) # mock the zeroconf logger and check for the correct logging backoff call_counts = mocked_log_warn.call_count, mocked_log_debug.call_count # try to send an oversized packet zc.send(out) assert mocked_log_warn.call_count == call_counts[0] + 1 assert mocked_log_debug.call_count == call_counts[0] zc.send(out) assert mocked_log_warn.call_count == call_counts[0] + 1 assert mocked_log_debug.call_count == call_counts[0] + 1 # force a receive of an oversized packet packet = out.packet() s = zc._respond_sockets[0] # mock the zeroconf logger and check for the correct logging backoff call_counts = mocked_log_warn.call_count, mocked_log_debug.call_count # force receive on oversized packet s.sendto(packet, 0, (r._MDNS_ADDR, r._MDNS_PORT)) s.sendto(packet, 0, (r._MDNS_ADDR, r._MDNS_PORT)) time.sleep(2.0) zeroconf.log.debug( "warn %d debug %d was %s", mocked_log_warn.call_count, mocked_log_debug.call_count, call_counts, ) assert mocked_log_debug.call_count > call_counts[0] # close our zeroconf which will close the sockets zc.close() # pop the big chunk off the end of the data and send on a closed socket out.data.pop() zc._GLOBAL_DONE = False # mock the zeroconf logger and check for the correct logging backoff call_counts = mocked_log_warn.call_count, mocked_log_debug.call_count # send on a closed socket (force a socket error) zc.send(out) zeroconf.log.debug( "warn %d debug %d was %s", mocked_log_warn.call_count, mocked_log_debug.call_count, call_counts, ) assert mocked_log_warn.call_count > call_counts[0] assert mocked_log_debug.call_count > call_counts[0] zc.send(out) zeroconf.log.debug( "warn %d debug %d was %s", mocked_log_warn.call_count, mocked_log_debug.call_count, call_counts, ) assert mocked_log_debug.call_count > call_counts[0] + 2 mocked_log_warn.stop() mocked_log_debug.stop()
def test_integration(): service_added = Event() service_removed = Event() unexpected_ttl = Event() got_query = Event() type_ = "_http._tcp.local." registration_name = "xxxyyy.%s" % type_ def on_service_state_change(zeroconf, service_type, state_change, name): if name == registration_name: if state_change is ServiceStateChange.Added: service_added.set() elif state_change is ServiceStateChange.Removed: service_removed.set() zeroconf_browser = Zeroconf(interfaces=['127.0.0.1']) # we are going to monkey patch the zeroconf send to check packet sizes old_send = zeroconf_browser.send time_offset = 0 def current_time_millis(): """Current system time in milliseconds""" return time.time() * 1000 + time_offset * 1000 expected_ttl = r._DNS_TTL # needs to be a list so that we can modify it in our phony send nbr_queries = [0, None] def send(out, addr=r._MDNS_ADDR, port=r._MDNS_PORT): """Sends an outgoing packet.""" pout = r.DNSIncoming(out.packet()) for answer in pout.answers: nbr_queries[0] += 1 if not answer.ttl > expected_ttl / 2: unexpected_ttl.set() got_query.set() old_send(out, addr=addr, port=port) # monkey patch the zeroconf send zeroconf_browser.send = send # monkey patch the zeroconf current_time_millis r.current_time_millis = current_time_millis service_added = Event() service_removed = Event() browser = ServiceBrowser(zeroconf_browser, type_, [on_service_state_change]) zeroconf_registrar = Zeroconf(interfaces=['127.0.0.1']) desc = {'path': '/~paulsm/'} info = ServiceInfo( type_, registration_name, socket.inet_aton("10.0.1.2"), 80, 0, 0, desc, "ash-2.local.") zeroconf_registrar.register_service(info) try: service_added.wait(1) assert service_added.is_set() sleep_count = 0 while nbr_queries[0] < 50: time_offset += expected_ttl / 4 zeroconf_browser.notify_all() sleep_count += 1 got_query.wait(1) got_query.clear() assert not unexpected_ttl.is_set() # Don't remove service, allow close() to cleanup finally: zeroconf_registrar.close() browser.cancel() zeroconf_browser.close()
def test_lots_of_names(self): # instantiate a zeroconf instance zc = Zeroconf(interfaces=['127.0.0.1']) # create a bunch of servers type_ = "_my-service._tcp.local." name = 'a wonderful service' server_count = 300 self.generate_many_hosts(zc, type_, name, server_count) # verify that name changing works self.verify_name_change(zc, type_, name, server_count) # we are going to monkey patch the zeroconf send to check packet sizes old_send = zc.send longest_packet_len = 0 longest_packet = None # type: Optional[r.DNSOutgoing] def send(out, addr=r._MDNS_ADDR, port=r._MDNS_PORT): """Sends an outgoing packet.""" packet = out.packet() nonlocal longest_packet_len, longest_packet if longest_packet_len < len(packet): longest_packet_len = len(packet) longest_packet = out old_send(out, addr=addr, port=port) # monkey patch the zeroconf send setattr(zc, "send", send) # dummy service callback def on_service_state_change(zeroconf, service_type, state_change, name): pass # start a browser browser = ServiceBrowser(zc, type_, [on_service_state_change]) # wait until the browse request packet has maxed out in size sleep_count = 0 while sleep_count < 100 and longest_packet_len < r._MAX_MSG_ABSOLUTE - 100: sleep_count += 1 time.sleep(0.1) browser.cancel() time.sleep(0.5) import zeroconf zeroconf.log.debug('sleep_count %d, sized %d', sleep_count, longest_packet_len) # now the browser has sent at least one request, verify the size assert longest_packet_len <= r._MAX_MSG_ABSOLUTE assert longest_packet_len >= r._MAX_MSG_ABSOLUTE - 100 # mock zeroconf's logger warning() and debug() from unittest.mock import patch patch_warn = patch('zeroconf.log.warning') patch_debug = patch('zeroconf.log.debug') mocked_log_warn = patch_warn.start() mocked_log_debug = patch_debug.start() # now that we have a long packet in our possession, let's verify the # exception handling. out = longest_packet assert out is not None out.data.append(b'\0' * 1000) # mock the zeroconf logger and check for the correct logging backoff call_counts = mocked_log_warn.call_count, mocked_log_debug.call_count # try to send an oversized packet zc.send(out) assert mocked_log_warn.call_count == call_counts[0] + 1 assert mocked_log_debug.call_count == call_counts[0] zc.send(out) assert mocked_log_warn.call_count == call_counts[0] + 1 assert mocked_log_debug.call_count == call_counts[0] + 1 # force a receive of an oversized packet packet = out.packet() s = zc._respond_sockets[0] # mock the zeroconf logger and check for the correct logging backoff call_counts = mocked_log_warn.call_count, mocked_log_debug.call_count # force receive on oversized packet s.sendto(packet, 0, (r._MDNS_ADDR, r._MDNS_PORT)) s.sendto(packet, 0, (r._MDNS_ADDR, r._MDNS_PORT)) time.sleep(2.0) zeroconf.log.debug( 'warn %d debug %d was %s', mocked_log_warn.call_count, mocked_log_debug.call_count, call_counts ) assert mocked_log_debug.call_count > call_counts[0] # close our zeroconf which will close the sockets zc.close() # pop the big chunk off the end of the data and send on a closed socket out.data.pop() zc._GLOBAL_DONE = False # mock the zeroconf logger and check for the correct logging backoff call_counts = mocked_log_warn.call_count, mocked_log_debug.call_count # send on a closed socket (force a socket error) zc.send(out) zeroconf.log.debug( 'warn %d debug %d was %s', mocked_log_warn.call_count, mocked_log_debug.call_count, call_counts ) assert mocked_log_warn.call_count > call_counts[0] assert mocked_log_debug.call_count > call_counts[0] zc.send(out) zeroconf.log.debug( 'warn %d debug %d was %s', mocked_log_warn.call_count, mocked_log_debug.call_count, call_counts ) assert mocked_log_debug.call_count > call_counts[0] + 2 mocked_log_warn.stop() mocked_log_debug.stop()
def test_backoff(): got_query = Event() type_ = "_http._tcp.local." zeroconf_browser = Zeroconf(interfaces=['127.0.0.1']) # we are going to monkey patch the zeroconf send to check query transmission old_send = zeroconf_browser.send time_offset = 0.0 start_time = time.time() * 1000 initial_query_interval = r._BROWSER_TIME / 1000 def current_time_millis(): """Current system time in milliseconds""" return start_time + time_offset * 1000 def send(out, addr=r._MDNS_ADDR, port=r._MDNS_PORT): """Sends an outgoing packet.""" got_query.set() old_send(out, addr=addr, port=port) # monkey patch the zeroconf send setattr(zeroconf_browser, "send", send) # monkey patch the zeroconf current_time_millis r.current_time_millis = current_time_millis # monkey patch the backoff limit to prevent test running forever r._BROWSER_BACKOFF_LIMIT = 10 # seconds # dummy service callback def on_service_state_change(zeroconf, service_type, state_change, name): pass browser = ServiceBrowser(zeroconf_browser, type_, [on_service_state_change]) try: # Test that queries are sent at increasing intervals sleep_count = 0 next_query_interval = 0.0 expected_query_time = 0.0 while True: zeroconf_browser.notify_all() sleep_count += 1 got_query.wait(0.1) if time_offset == expected_query_time: assert got_query.is_set() got_query.clear() if next_query_interval == r._BROWSER_BACKOFF_LIMIT: # Only need to test up to the point where we've seen a query # after the backoff limit has been hit break elif next_query_interval == 0: next_query_interval = initial_query_interval expected_query_time = initial_query_interval else: next_query_interval = min(2 * next_query_interval, r._BROWSER_BACKOFF_LIMIT) expected_query_time += next_query_interval else: assert not got_query.is_set() time_offset += initial_query_interval finally: browser.cancel() zeroconf_browser.close()
def test_integration(): service_added = Event() service_removed = Event() unexpected_ttl = Event() got_query = Event() type_ = "_http._tcp.local." registration_name = "xxxyyy.%s" % type_ def on_service_state_change(zeroconf, service_type, state_change, name): if name == registration_name: if state_change is ServiceStateChange.Added: service_added.set() elif state_change is ServiceStateChange.Removed: service_removed.set() zeroconf_browser = Zeroconf(interfaces=['127.0.0.1']) # we are going to monkey patch the zeroconf send to check packet sizes old_send = zeroconf_browser.send time_offset = 0.0 def current_time_millis(): """Current system time in milliseconds""" return time.time() * 1000 + time_offset * 1000 expected_ttl = r._DNS_HOST_TTL nbr_answers = 0 def send(out, addr=r._MDNS_ADDR, port=r._MDNS_PORT): """Sends an outgoing packet.""" pout = r.DNSIncoming(out.packet()) nonlocal nbr_answers for answer in pout.answers: nbr_answers += 1 if not answer.ttl > expected_ttl / 2: unexpected_ttl.set() got_query.set() old_send(out, addr=addr, port=port) # monkey patch the zeroconf send setattr(zeroconf_browser, "send", send) # monkey patch the zeroconf current_time_millis r.current_time_millis = current_time_millis # monkey patch the backoff limit to ensure we always get one query every 1/4 of the DNS TTL r._BROWSER_BACKOFF_LIMIT = int(expected_ttl / 4) service_added = Event() service_removed = Event() browser = ServiceBrowser(zeroconf_browser, type_, [on_service_state_change]) zeroconf_registrar = Zeroconf(interfaces=['127.0.0.1']) desc = {'path': '/~paulsm/'} info = ServiceInfo(type_, registration_name, socket.inet_aton("10.0.1.2"), 80, 0, 0, desc, "ash-2.local.") zeroconf_registrar.register_service(info) try: service_added.wait(1) assert service_added.is_set() # Test that we receive queries containing answers only if the remaining TTL # is greater than half the original TTL sleep_count = 0 test_iterations = 50 while nbr_answers < test_iterations: # Increase simulated time shift by 1/4 of the TTL in seconds time_offset += expected_ttl / 4 zeroconf_browser.notify_all() sleep_count += 1 got_query.wait(0.1) got_query.clear() # Prevent the test running indefinitely in an error condition assert sleep_count < test_iterations * 4 assert not unexpected_ttl.is_set() # Don't remove service, allow close() to cleanup finally: zeroconf_registrar.close() service_removed.wait(1) assert service_removed.is_set() browser.cancel() zeroconf_browser.close()