diff --git a/examples/browser.py b/examples/browser.py index 633ea0e1c..c3ef2f0d5 100755 --- a/examples/browser.py +++ b/examples/browser.py @@ -17,13 +17,14 @@ def on_service_state_change(zeroconf, service_type, name, state_change): if state_change is ServiceStateChange.Added: info = zeroconf.get_service_info(service_type, name) if info: - print(" Address: %s:%d" % (socket.inet_ntoa(info.address), info.port)) + addresses = ["%s:%d" % (socket.inet_ntoa(addr), info.port) for addr in info.addresses] + print(" Addresses: %s" % ", ".join(addresses)) print(" Weight: %d, priority: %d" % (info.weight, info.priority)) print(" Server: %s" % (info.server,)) if info.properties: print(" Properties are:") for key, value in info.properties.items(): - print(" %s: %s" % (key, value)) + print(" %s: %s" % (key.decode("utf-8"), value.decode("utf-8"))) else: print(" No properties") else: diff --git a/test_zeroconf.py b/test_zeroconf.py index 6ecf6728a..21e58f37a 100644 --- a/test_zeroconf.py +++ b/test_zeroconf.py @@ -188,6 +188,38 @@ def on_service_state_change(zeroconf, service_type, state_change, name): zeroconf_browser.close() +def test_multiple_addresses(): + type_ = "_http._tcp.local." + registration_name = "xxxyyy.%s" % type_ + desc = {'path': '/~paulsm/'} + address = socket.inet_aton("10.0.1.2") + + # Old way + info = ServiceInfo( + type_, registration_name, + address, 80, 0, 0, + desc, "ash-2.local.") + + assert not hasattr(info, "address") + assert info.addresses == [address] + + # Compatibility way + info = ServiceInfo( + type_, registration_name, + [address, address], 80, 0, 0, + desc, "ash-2.local.") + + assert info.addresses == [address, address] + + # New kwarg way + info = ServiceInfo( + type_, registration_name, + None, 80, 0, 0, + desc, "ash-2.local.", addresses=[address, address]) + + assert info.addresses == [address, address] + + def test_listener_handles_closed_socket_situation_gracefully(): error = socket.error(socket.EBADF) error.errno = socket.EBADF diff --git a/zeroconf.py b/zeroconf.py index e636a41fc..084b1b546 100644 --- a/zeroconf.py +++ b/zeroconf.py @@ -1068,24 +1068,42 @@ class ServiceInfo(object): """Service information""" def __init__(self, type, name, address=None, port=None, weight=0, - priority=0, properties=None, server=None): + priority=0, properties=None, server=None, addresses=None): """Create a service description. + For backwards compatibility/migration, address will also accept a + list of addresses. + type: fully qualified service type name name: fully qualified service name - address: IP address as unsigned short, network byte order + address: IP address as unsigned short, network byte order (legacy) port: port that the service runs on weight: weight of the service priority: priority of the service properties: dictionary of properties (or a string holding the bytes for the text field) - server: fully qualified name for service host (defaults to name)""" + server: fully qualified name for service host (defaults to name) + addresses: List of IP addresses as unsigned short, network byte + order""" + + # Accept both none, or one, but not both. + assert (address is None and addresses is None) or \ + (address is None and addresses) or \ + (address and addresses is None) if not name.endswith(type): raise BadTypeInNameException self.type = type self.name = name - self.address = address + if addresses is not None: + self.addresses = addresses + elif address is not None: + if isinstance(address, list): + self.addresses = address + else: + self.addresses = [address] + else: + self.addresses = [] self.port = port self.weight = weight self.priority = priority @@ -1174,7 +1192,8 @@ def update_record(self, zc, now, record): if record.type == _TYPE_A: # if record.name == self.name: if record.name == self.server: - self.address = record.address + if record.address not in self.addresses: + self.addresses.append(record.address) elif record.type == _TYPE_SRV: if record.name == self.name: self.server = record.server @@ -1199,8 +1218,7 @@ def request(self, zc, timeout): result = False try: zc.add_listener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN)) - while (self.server is None or self.address is None or - self.text is None): + while (self.server is None or self.text is None or not self.addresses): if last <= now: return False if next <= now: @@ -1440,9 +1458,9 @@ def register_service(self, info, ttl=_DNS_TTL): info.server), 0) out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0) - if info.address: + for address in info.addresses: out.add_answer_at_time(DNSAddress(info.server, _TYPE_A, - _CLASS_IN, ttl, info.address), 0) + _CLASS_IN, ttl, address), 0) self.send(out) i += 1 next_time += _REGISTER_TIME @@ -1473,9 +1491,9 @@ def unregister_service(self, info): info.name), 0) out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) - if info.address: + for address in info.addresses: out.add_answer_at_time(DNSAddress(info.server, _TYPE_A, - _CLASS_IN, 0, info.address), 0) + _CLASS_IN, 0, address), 0) self.send(out) i += 1 next_time += _UNREGISTER_TIME @@ -1500,9 +1518,9 @@ def unregister_all_services(self): info.port, info.server), 0) out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) - if info.address: + for address in info.addresses: out.add_answer_at_time(DNSAddress(info.server, - _TYPE_A, _CLASS_IN, 0, info.address), 0) + _TYPE_A, _CLASS_IN, 0, address), 0) self.send(out) i += 1 next_time += _UNREGISTER_TIME @@ -1621,9 +1639,10 @@ def handle_query(self, msg, addr, port): if question.type in (_TYPE_A, _TYPE_ANY): for service in self.services.values(): if service.server == question.name.lower(): - out.add_answer(msg, DNSAddress(question.name, - _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, - _DNS_TTL, service.address)) + for address in service.addresses: + out.add_answer(msg, DNSAddress(question.name, + _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, + _DNS_TTL, address)) service = self.services.get(question.name.lower(), None) if not service: @@ -1639,9 +1658,10 @@ def handle_query(self, msg, addr, port): _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text)) if question.type == _TYPE_SRV: - out.add_additional_answer(DNSAddress(service.server, - _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, - _DNS_TTL, service.address)) + for address in service.addresses: + out.add_additional_answer(DNSAddress(service.server, + _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, + _DNS_TTL, address)) except Exception as e: # TODO stop catching all Exceptions log.exception('Unknown error, possibly benign: %r', e)