[Python-modules-commits] [python-zeroconf] 01/05: Import python-zeroconf_0.17.6.orig.tar.gz
Ruben Undheim
rubund-guest at moszumanska.debian.org
Thu Oct 6 17:11:19 UTC 2016
This is an automated email from the git hooks/post-receive script.
rubund-guest pushed a commit to branch master
in repository python-zeroconf.
commit e694928afacaf099988e8b2c6f40df196df18245
Author: Ruben Undheim <ruben.undheim at gmail.com>
Date: Thu Oct 6 19:02:19 2016 +0200
Import python-zeroconf_0.17.6.orig.tar.gz
---
.gitignore | 10 +
.travis.yml | 1 +
README.rst | 24 +++
setup.cfg | 1 +
test_zeroconf.py | 226 ++++++++++++++++++--
zeroconf.py | 639 ++++++++++++++++++++++++++++++++++++-------------------
6 files changed, 661 insertions(+), 240 deletions(-)
diff --git a/.gitignore b/.gitignore
index dc84959..264bddc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,12 @@
build/
+*.pyc
+*.pyo
+Thumbs.db
+.DS_Store
+.project
+.pydevproject
+.settings
+.idea
+.vslick
+.cache
diff --git a/.travis.yml b/.travis.yml
index 3c09140..94f8d95 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ python:
- "2.7"
- "3.3"
- "3.4"
+ - "3.5"
- "pypy"
- "pypy3"
matrix:
diff --git a/README.rst b/README.rst
index 3e812c8..314dba4 100644
--- a/README.rst
+++ b/README.rst
@@ -110,11 +110,33 @@ Here's an example:
If you want to customize that you need to specify ``interfaces`` argument when
constructing ``Zeroconf`` object (see the code for details).
+If you don't know the name of the service you need to browse for, try:
+
+.. code-block:: python
+
+ from zeroconf import ZeroconfServiceTypes
+ print('\n'.join(ZeroconfServiceTypes.find()))
+
See examples directory for more.
Changelog
=========
+0.17.6
+------
+
+* Many improvements to address race conditions and exceptions during ZC()
+ startup and shutdown, thanks to: morpav, veawor, justingiorgi, herczy,
+ stephenrauch
+* Added more test coverage: strahlex, stephenrauch
+* Stephen Rauch contributed:
+
+ - Speed up browser startup
+ - Add ZeroconfServiceTypes() query class to discover all advertised service types
+ - Add full validation for service names, types and subtypes
+ - Fix for subtype browsing
+ - Fix DNSHInfo support
+
0.17.5
------
@@ -223,6 +245,7 @@ Changelog
----
* Jonathon Paisley contributed these corrections:
+
- always multicast replies, even when query is unicast
- correct a pointer encoding problem
- can now write records in any order
@@ -230,6 +253,7 @@ Changelog
- better TXT record parsing
- server is now separate from name
- can cancel a service browser
+
* modified some unit tests to accommodate these changes
0.09
diff --git a/setup.cfg b/setup.cfg
index 51017e1..24b129b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,3 +5,4 @@ universal = 1
show-source = 1
import-order-style=google
application-import-names=zeroconf
+max-line-length=110
diff --git a/test_zeroconf.py b/test_zeroconf.py
index 6ecf672..e0e368a 100644
--- a/test_zeroconf.py
+++ b/test_zeroconf.py
@@ -7,21 +7,22 @@
import logging
import socket
import struct
+import time
import unittest
from threading import Event
-from mock import Mock
from six import indexbytes
from six.moves import xrange
import zeroconf as r
from zeroconf import (
+ DNSHinfo,
DNSText,
- Listener,
ServiceBrowser,
ServiceInfo,
ServiceStateChange,
Zeroconf,
+ ZeroconfServiceTypes,
)
log = logging.getLogger('zeroconf')
@@ -66,6 +67,19 @@ class PacketGeneration(unittest.TestCase):
self.assertEqual(len(generated.questions), len(parsed.questions))
self.assertEqual(question, parsed.questions[0])
+ def test_dns_hinfo(self):
+ generated = r.DNSOutgoing(0)
+ generated.add_additional_answer(
+ DNSHinfo('irrelevant', r._TYPE_HINFO, 0, 0, 'cpu', 'os'))
+ parsed = r.DNSIncoming(generated.packet())
+ self.assertEqual(parsed.answers[0].cpu, u'cpu')
+ self.assertEqual(parsed.answers[0].os, u'os')
+
+ generated = r.DNSOutgoing(0)
+ generated.add_additional_answer(
+ DNSHinfo('irrelevant', r._TYPE_HINFO, 0, 0, 'cpu', 'x' * 257))
+ self.assertRaises(r.NamePartTooLongException, generated.packet)
+
class PacketForm(unittest.TestCase):
@@ -151,6 +165,198 @@ class Framework(unittest.TestCase):
rv.close()
+class Exceptions(unittest.TestCase):
+
+ browser = None
+
+ @classmethod
+ def setUpClass(cls):
+ cls.browser = Zeroconf()
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.browser.close()
+ cls.browser = None
+
+ def test_bad_service_info_name(self):
+ self.assertRaises(
+ r.BadTypeInNameException,
+ self.browser.get_service_info, "type", "type_not")
+
+ def test_bad_service_names(self):
+ bad_names_to_try = (
+ '',
+ 'local',
+ '_tcp.local.',
+ '_udp.local.',
+ '._udp.local.',
+ '_ at ._tcp.local.',
+ '_A at ._tcp.local.',
+ '_x--x._tcp.local.',
+ '_-x._udp.local.',
+ '_x-._tcp.local.',
+ '_22._udp.local.',
+ '_2-2._tcp.local.',
+ '_1234567890-abcde._udp.local.',
+ '._x._udp.local.',
+ )
+ for name in bad_names_to_try:
+ self.assertRaises(
+ r.BadTypeInNameException,
+ self.browser.get_service_info, name, 'x.' + name)
+
+ def test_bad_sub_types(self):
+ bad_names_to_try = (
+ '_sub._http._tcp.local.',
+ 'x.sub._http._tcp.local.',
+ 'a' * 64 + '._sub._http._tcp.local.',
+ 'a' * 62 + u'â._sub._http._tcp.local.',
+ )
+ for name in bad_names_to_try:
+ self.assertRaises(
+ r.BadTypeInNameException, r.service_type_name, name)
+
+ def test_good_service_names(self):
+ good_names_to_try = (
+ '_x._tcp.local.',
+ '_x._udp.local.',
+ '_12345-67890-abc._udp.local.',
+ 'x._sub._http._tcp.local.',
+ 'a' * 63 + '._sub._http._tcp.local.',
+ 'a' * 61 + u'â._sub._http._tcp.local.',
+ )
+ for name in good_names_to_try:
+ r.service_type_name(name)
+
+
+class ServiceTypesQuery(unittest.TestCase):
+
+ def test_integration_with_listener(self):
+
+ type_ = "_test-srvc-type._tcp.local."
+ name = "xxxyyy"
+ registration_name = "%s.%s" % (name, type_)
+
+ 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_types = ZeroconfServiceTypes.find(timeout=0.5)
+ assert type_ in service_types
+ service_types = ZeroconfServiceTypes.find(
+ zc=zeroconf_registrar, timeout=0.5)
+ assert type_ in service_types
+
+ finally:
+ zeroconf_registrar.close()
+
+ def test_integration_with_subtype_and_listener(self):
+ subtype_ = "_subtype._sub"
+ type_ = "_type._tcp.local."
+ name = "xxxyyy"
+ # Note: discovery returns only DNS-SD type not subtype
+ discovery_type = "%s.%s" % (subtype_, type_)
+ registration_name = "%s.%s" % (name, type_)
+
+ zeroconf_registrar = Zeroconf(interfaces=['127.0.0.1'])
+ desc = {'path': '/~paulsm/'}
+ info = ServiceInfo(
+ discovery_type, registration_name,
+ socket.inet_aton("10.0.1.2"), 80, 0, 0,
+ desc, "ash-2.local.")
+ zeroconf_registrar.register_service(info)
+
+ try:
+ service_types = ZeroconfServiceTypes.find(timeout=0.5)
+ print(service_types)
+ assert discovery_type in service_types
+ service_types = ZeroconfServiceTypes.find(
+ zc=zeroconf_registrar, timeout=0.5)
+ assert discovery_type in service_types
+
+ finally:
+ zeroconf_registrar.close()
+
+
+class ListenerTest(unittest.TestCase):
+
+ def test_integration_with_listener_class(self):
+
+ service_added = Event()
+ service_removed = Event()
+
+ subtype_name = "My special Subtype"
+ type_ = "_http._tcp.local."
+ subtype = subtype_name + "._sub." + type_
+ name = "xxxyyy"
+ registration_name = "%s.%s" % (name, type_)
+
+ class MyListener(object):
+ def add_service(self, zeroconf, type, name):
+ zeroconf.get_service_info(type, name)
+ service_added.set()
+
+ def remove_service(self, zeroconf, type, name):
+ service_removed.set()
+
+ zeroconf_browser = Zeroconf()
+ zeroconf_browser.add_service_listener(subtype, MyListener())
+
+ properties = dict(
+ prop_none=None,
+ prop_string=b'a_prop',
+ prop_float=1.0,
+ prop_blank=b'a blanked string',
+ prop_true=1,
+ prop_false=0,
+ )
+
+ zeroconf_registrar = Zeroconf()
+ desc = {'path': '/~paulsm/'}
+ desc.update(properties)
+ info_service = ServiceInfo(
+ subtype, registration_name,
+ socket.inet_aton("10.0.1.2"), 80, 0, 0,
+ desc, "ash-2.local.")
+ zeroconf_registrar.register_service(info_service)
+
+ try:
+ service_added.wait(1)
+ assert service_added.is_set()
+
+ # short pause to allow multicast timers to expire
+ time.sleep(2)
+
+ # clear the answer cache to force query
+ for record in zeroconf_browser.cache.entries():
+ zeroconf_browser.cache.remove(record)
+
+ # get service info without answer cache
+ info = zeroconf_browser.get_service_info(type_, registration_name)
+
+ assert info.properties[b'prop_none'] is False
+ assert info.properties[b'prop_string'] == properties['prop_string']
+ assert info.properties[b'prop_float'] is False
+ assert info.properties[b'prop_blank'] == properties['prop_blank']
+ assert info.properties[b'prop_true'] is True
+ assert info.properties[b'prop_false'] is False
+
+ info = zeroconf_browser.get_service_info(subtype, registration_name)
+ assert info.properties[b'prop_none'] is False
+
+ zeroconf_registrar.unregister_service(info_service)
+ service_removed.wait(1)
+ assert service_removed.is_set()
+ finally:
+ zeroconf_registrar.close()
+ zeroconf_browser.close()
+
+
def test_integration():
service_added = Event()
service_removed = Event()
@@ -179,26 +385,14 @@ def test_integration():
try:
service_added.wait(1)
assert service_added.is_set()
- zeroconf_registrar.unregister_service(info)
- service_removed.wait(1)
- assert service_removed.is_set()
+ # Don't remove service, allow close() to cleanup
+
finally:
zeroconf_registrar.close()
browser.cancel()
zeroconf_browser.close()
-def test_listener_handles_closed_socket_situation_gracefully():
- error = socket.error(socket.EBADF)
- error.errno = socket.EBADF
-
- zeroconf = Mock()
- zeroconf.socket.recvfrom.side_effect = error
-
- listener = Listener(zeroconf)
- listener.handle_read(zeroconf.socket)
-
-
def test_dnstext_repr_works():
# There was an issue on Python 3 that prevented DNSText's repr
# from working when the text was longer than 10 bytes
diff --git a/zeroconf.py b/zeroconf.py
index 8c00d2b..d1211f7 100644
--- a/zeroconf.py
+++ b/zeroconf.py
@@ -1,4 +1,5 @@
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import (
+ absolute_import, division, print_function, unicode_literals)
""" Multicast DNS Service Discovery for Python, v0.14-wmcbrine
Copyright 2003 Paul Scott-Murphy, 2014 William McBrine
@@ -25,6 +26,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import enum
import errno
import logging
+import re
import select
import socket
import struct
@@ -38,7 +40,7 @@ from six.moves import xrange
__author__ = 'Paul Scott-Murphy, William McBrine'
__maintainer__ = 'Jakub Stasiak <jakub at stasiak.at>'
-__version__ = '0.17.5'
+__version__ = '0.17.6'
__license__ = 'LGPL'
@@ -64,10 +66,6 @@ log.addHandler(NullHandler())
if log.level == logging.NOTSET:
log.setLevel(logging.WARN)
-# hook for threads
-
-_GLOBAL_DONE = False
-
# Some timing constants
_UNREGISTER_TIME = 125
@@ -157,6 +155,9 @@ _TYPES = {_TYPE_A: "a",
_TYPE_SRV: "srv",
_TYPE_ANY: "any"}
+_HAS_A_TO_Z = re.compile(r'[A-Za-z]')
+_HAS_ONLY_A_TO_Z_NUM_HYPHEN = re.compile(r'^[A-Za-z0-9\-]+$')
+
# utility functions
@@ -164,6 +165,94 @@ def current_time_millis():
"""Current system time in milliseconds"""
return time.time() * 1000
+
+def service_type_name(type_):
+ """
+ Validate a fully qualified service name, instance or subtype. [rfc6763]
+
+ Returns fully qualified service name.
+
+ Domain names used by mDNS-SD take the following forms:
+
+ <sn> . <_tcp|_udp> . local.
+ <Instance> . <sn> . <_tcp|_udp> . local.
+ <sub>._sub . <sn> . <_tcp|_udp> . local.
+
+ 1) must end with 'local.'
+
+ This is true because we are implementing mDNS and since the 'm' means
+ multi-cast, the 'local.' domain is mandatory.
+
+ 2) local is preceded with either '_udp.' or '_tcp.'
+
+ 3) service name <sn> precedes <_tcp|_udp>
+
+ The rules for Service Names [RFC6335] state that they may be no more
+ than fifteen characters long (not counting the mandatory underscore),
+ consisting of only letters, digits, and hyphens, must begin and end
+ with a letter or digit, must not contain consecutive hyphens, and
+ must contain at least one letter.
+
+ The instance name <Instance> and sub type <sub> may be up to 63 bytes.
+
+ :param type_: Type, SubType or service name to validate
+ :return: fully qualified service name (eg: _http._tcp.local.)
+ """
+ if not (type_.endswith('._tcp.local.') or type_.endswith('._udp.local.')):
+ raise BadTypeInNameException(
+ "Type must end with '._tcp.local.' or '._udp.local.'")
+
+ if type_.startswith('.'):
+ raise BadTypeInNameException("Type must not start with '.'")
+
+ remaining = type_[:-len('._tcp.local.')].split('.')
+ name = remaining.pop()
+ if not name:
+ raise BadTypeInNameException("No Service name found")
+
+ if name[0] != '_':
+ raise BadTypeInNameException("Service name must start with '_'")
+
+ # remove leading underscore
+ name = name[1:]
+
+ if len(name) > 15:
+ raise BadTypeInNameException("Service name must be <= 15 bytes")
+
+ if '--' in name:
+ raise BadTypeInNameException("Service name must not contain '--'")
+
+ if '-' in (name[0], name[-1]):
+ raise BadTypeInNameException(
+ "Service name may not start or end with '-'")
+
+ if not _HAS_A_TO_Z.search(name):
+ raise BadTypeInNameException(
+ "Service name must contain at least one letter (eg: 'A-Z')")
+
+ if not _HAS_ONLY_A_TO_Z_NUM_HYPHEN.search(name):
+ raise BadTypeInNameException(
+ "Service name must contain only these characters: "
+ "A-Z, a-z, 0-9, hyphen ('-')")
+
+ if remaining and remaining[-1] == '_sub':
+ remaining.pop()
+ if len(remaining) == 0:
+ raise BadTypeInNameException(
+ "_sub requires a subtype name")
+
+ if len(remaining) > 1:
+ raise BadTypeInNameException(
+ "Unexpected characters '%s.'" % '.'.join(remaining[1:]))
+
+ if remaining:
+ length = len(remaining[0].encode('utf-8'))
+ if length > 63:
+ raise BadTypeInNameException("Too long: '%s'" % remaining[0])
+
+ return '_' + name + type_[-len('._tcp.local.'):]
+
+
# Exceptions
@@ -197,10 +286,10 @@ class DNSEntry(object):
"""A DNS entry"""
- def __init__(self, name, type, class_):
+ def __init__(self, name, type_, class_):
self.key = name.lower()
self.name = name
- self.type = type
+ self.type = type_
self.class_ = class_ & _CLASS_MASK
self.unique = (class_ & _CLASS_UNIQUE) != 0
@@ -215,11 +304,13 @@ class DNSEntry(object):
"""Non-equality test"""
return not self.__eq__(other)
- def get_class_(self, class_):
+ @staticmethod
+ def get_class_(class_):
"""Class accessor"""
return _CLASSES.get(class_, "?(%s)" % class_)
- def get_type(self, t):
+ @staticmethod
+ def get_type(t):
"""Type accessor"""
return _TYPES.get(t, "?(%s)" % t)
@@ -233,7 +324,7 @@ class DNSEntry(object):
result += ","
result += self.name
if other is not None:
- result += ",%s]" % (other)
+ result += ",%s]" % other
else:
result += "]"
return result
@@ -243,10 +334,8 @@ class DNSQuestion(DNSEntry):
"""A DNS question entry"""
- def __init__(self, name, type, class_):
- # if not name.endswith(".local."):
- # raise NonLocalNameException
- DNSEntry.__init__(self, name, type, class_)
+ def __init__(self, name, type_, class_):
+ DNSEntry.__init__(self, name, type_, class_)
def answered_by(self, rec):
"""Returns true if the question is answered by the record"""
@@ -263,8 +352,8 @@ class DNSRecord(DNSEntry):
"""A DNS record - like a DNS entry, but has a TTL"""
- def __init__(self, name, type, class_, ttl):
- DNSEntry.__init__(self, name, type, class_)
+ def __init__(self, name, type_, class_, ttl):
+ DNSEntry.__init__(self, name, type_, class_)
self.ttl = ttl
self.created = current_time_millis()
@@ -292,7 +381,7 @@ class DNSRecord(DNSEntry):
def get_remaining_ttl(self, now):
"""Returns the remaining TTL in seconds."""
- return max(0, (self.get_expiration_time(100) - now) / 1000)
+ return max(0, (self.get_expiration_time(100) - now) / 1000.0)
def is_expired(self, now):
"""Returns true if this record has expired."""
@@ -313,9 +402,9 @@ class DNSRecord(DNSEntry):
raise AbstractMethodException
def to_string(self, other):
- """String representation with addtional information"""
- arg = "%s/%s,%s" % (self.ttl,
- self.get_remaining_ttl(current_time_millis()), other)
+ """String representation with additional information"""
+ arg = "%s/%s,%s" % (
+ self.ttl, self.get_remaining_ttl(current_time_millis()), other)
return DNSEntry.to_string(self, "record", arg)
@@ -323,8 +412,8 @@ class DNSAddress(DNSRecord):
"""A DNS address record"""
- def __init__(self, name, type, class_, ttl, address):
- DNSRecord.__init__(self, name, type, class_, ttl)
+ def __init__(self, name, type_, class_, ttl, address):
+ DNSRecord.__init__(self, name, type_, class_, ttl)
self.address = address
def write(self, out):
@@ -348,15 +437,21 @@ class DNSHinfo(DNSRecord):
"""A DNS host information record"""
- def __init__(self, name, type, class_, ttl, cpu, os):
- DNSRecord.__init__(self, name, type, class_, ttl)
- self.cpu = cpu
- self.os = os
+ def __init__(self, name, type_, class_, ttl, cpu, os):
+ DNSRecord.__init__(self, name, type_, class_, ttl)
+ try:
+ self.cpu = cpu.decode('utf-8')
+ except AttributeError:
+ self.cpu = cpu
+ try:
+ self.os = os.decode('utf-8')
+ except AttributeError:
+ self.os = os
def write(self, out):
"""Used in constructing an outgoing packet"""
- out.write_string(self.cpu)
- out.write_string(self.oso)
+ out.write_character_string(self.cpu.encode('utf-8'))
+ out.write_character_string(self.os.encode('utf-8'))
def __eq__(self, other):
"""Tests equality on cpu and os"""
@@ -372,8 +467,8 @@ class DNSPointer(DNSRecord):
"""A DNS pointer record"""
- def __init__(self, name, type, class_, ttl, alias):
- DNSRecord.__init__(self, name, type, class_, ttl)
+ def __init__(self, name, type_, class_, ttl, alias):
+ DNSRecord.__init__(self, name, type_, class_, ttl)
self.alias = alias
def write(self, out):
@@ -418,8 +513,9 @@ class DNSService(DNSRecord):
"""A DNS service record"""
- def __init__(self, name, type, class_, ttl, priority, weight, port, server):
- DNSRecord.__init__(self, name, type, class_, ttl)
+ def __init__(self, name, type_, class_, ttl,
+ priority, weight, port, server):
+ DNSRecord.__init__(self, name, type_, class_, ttl)
self.priority = priority
self.weight = weight
self.port = port
@@ -455,6 +551,8 @@ class DNSIncoming(object):
self.data = data
self.questions = []
self.answers = []
+ self.id = 0
+ self.flags = 0
self.num_questions = 0
self.num_answers = 0
self.num_authorities = 0
@@ -464,24 +562,25 @@ class DNSIncoming(object):
self.read_questions()
self.read_others()
- def unpack(self, format):
- length = struct.calcsize(format)
- info = struct.unpack(format, self.data[self.offset:self.offset + length])
+ def unpack(self, format_):
+ length = struct.calcsize(format_)
+ info = struct.unpack(
+ format_, self.data[self.offset:self.offset + length])
self.offset += length
return info
def read_header(self):
"""Reads header portion of packet"""
(self.id, self.flags, self.num_questions, self.num_answers,
- self.num_quthorities, self.num_additionals) = self.unpack(b'!6H')
+ self.num_authorities, self.num_additionals) = self.unpack(b'!6H')
def read_questions(self):
"""Reads questions section of packet"""
for i in xrange(self.num_questions):
name = self.read_name()
- type, class_ = self.unpack(b'!HH')
+ type_, class_ = self.unpack(b'!HH')
- question = DNSQuestion(name, type, class_)
+ question = DNSQuestion(name, type_, class_)
self.questions.append(question)
def read_int(self):
@@ -510,24 +609,30 @@ class DNSIncoming(object):
n = self.num_answers + self.num_authorities + self.num_additionals
for i in xrange(n):
domain = self.read_name()
- type, class_, ttl, length = self.unpack(b'!HHiH')
+ type_, class_, ttl, length = self.unpack(b'!HHiH')
rec = None
- if type == _TYPE_A:
- rec = DNSAddress(domain, type, class_, ttl, self.read_string(4))
- elif type == _TYPE_CNAME or type == _TYPE_PTR:
- rec = DNSPointer(domain, type, class_, ttl, self.read_name())
- elif type == _TYPE_TXT:
- rec = DNSText(domain, type, class_, ttl, self.read_string(length))
- elif type == _TYPE_SRV:
- rec = DNSService(domain, type, class_, ttl,
- self.read_unsigned_short(), self.read_unsigned_short(),
- self.read_unsigned_short(), self.read_name())
- elif type == _TYPE_HINFO:
- rec = DNSHinfo(domain, type, class_, ttl,
- self.read_character_string(), self.read_character_string())
- elif type == _TYPE_AAAA:
- rec = DNSAddress(domain, type, class_, ttl, self.read_string(16))
+ if type_ == _TYPE_A:
+ rec = DNSAddress(
+ domain, type_, class_, ttl, self.read_string(4))
+ elif type_ == _TYPE_CNAME or type_ == _TYPE_PTR:
+ rec = DNSPointer(
+ domain, type_, class_, ttl, self.read_name())
+ elif type_ == _TYPE_TXT:
+ rec = DNSText(
+ domain, type_, class_, ttl, self.read_string(length))
+ elif type_ == _TYPE_SRV:
+ rec = DNSService(
+ domain, type_, class_, ttl,
+ self.read_unsigned_short(), self.read_unsigned_short(),
+ self.read_unsigned_short(), self.read_name())
+ elif type_ == _TYPE_HINFO:
+ rec = DNSHinfo(
+ domain, type_, class_, ttl,
+ self.read_character_string(), self.read_character_string())
+ elif type_ == _TYPE_AAAA:
+ rec = DNSAddress(
+ domain, type_, class_, ttl, self.read_string(16))
else:
# Try to ignore types we don't know about
# Skip the payload for the resource record so the next
@@ -553,7 +658,7 @@ class DNSIncoming(object):
"""Reads a domain name from the packet"""
result = ''
off = self.offset
- next = -1
+ next_ = -1
first = off
while True:
@@ -566,8 +671,8 @@ class DNSIncoming(object):
result = ''.join((result, self.read_utf(off, length) + '.'))
off += length
elif t == 0xC0:
- if next < 0:
- next = off + 1
+ if next_ < 0:
+ next_ = off + 1
off = ((length & 0x3F) << 8) | indexbytes(self.data, off)
if off >= first:
# TODO raise more specific exception
@@ -577,8 +682,8 @@ class DNSIncoming(object):
# TODO raise more specific exception
raise Exception("Bad domain name at %s" % (off,))
- if next >= 0:
- self.offset = next
+ if next_ >= 0:
+ self.offset = next_
else:
self.offset = off
@@ -626,9 +731,9 @@ class DNSOutgoing(object):
"""Adds an additional answer"""
self.additionals.append(record)
- def pack(self, format, value):
- self.data.append(struct.pack(format, value))
- self.size += struct.calcsize(format)
+ def pack(self, format_, value):
+ self.data.append(struct.pack(format_, value))
+ self.size += struct.calcsize(format_)
def write_byte(self, value):
"""Writes a single byte to the packet"""
@@ -662,6 +767,14 @@ class DNSOutgoing(object):
self.write_byte(length)
self.write_string(utfstr)
+ def write_character_string(self, value):
+ assert isinstance(value, bytes)
+ length = len(value)
+ if length > 256:
+ raise NamePartTooLongException
+ self.write_byte(length)
+ self.write_string(value)
+
def write_name(self, name):
"""Writes a domain name to the packet"""
@@ -768,14 +881,16 @@ class DNSCache(object):
matching entry."""
try:
list_ = self.cache[entry.key]
- return list_[list_.index(entry)]
+ for cached_entry in list_:
+ if entry.__eq__(cached_entry):
+ return cached_entry
except (KeyError, ValueError):
return None
- def get_by_details(self, name, type, class_):
+ def get_by_details(self, name, type_, class_):
"""Gets an entry by details. Will return None if there is
no matching entry."""
- entry = DNSEntry(name, type, class_)
+ entry = DNSEntry(name, type_, class_)
return self.get(entry)
def entries_with_name(self, name):
@@ -790,7 +905,7 @@ class DNSCache(object):
if not self.cache:
return []
else:
- # copy the cache before running the reduce, to avoid size change during iteration
+ # avoid size change during iteration by copying the cache
values = list(self.cache.values())
return reduce(lambda a, b: a + b, values)
@@ -809,7 +924,7 @@ class Engine(threading.Thread):
"""
def __init__(self, zc):
- threading.Thread.__init__(self)
+ threading.Thread.__init__(self, name='zeroconf-Engine')
self.daemon = True
self.zc = zc
self.readers = {} # maps socket to reader
@@ -818,43 +933,37 @@ class Engine(threading.Thread):
self.start()
def run(self):
- while not _GLOBAL_DONE:
- rs = self.get_readers()
- if len(rs) == 0:
- # No sockets to manage, but we wait for the timeout
- # or addition of a socket
- #
- with self.condition:
+ while not self.zc.done:
+ with self.condition:
+ rs = self.readers.keys()
+ if len(rs) == 0:
+ # No sockets to manage, but we wait for the timeout
+ # or addition of a socket
self.condition.wait(self.timeout)
- else:
+
+ if len(rs) != 0:
try:
rr, wr, er = select.select(rs, [], [], self.timeout)
- for socket_ in rr:
- try:
- self.readers[socket_].handle_read(socket_)
- except Exception as e: # TODO stop catching all Exceptions
- log.exception('Unknown error, possibly benign: %r', e)
- except Exception as e: # TODO stop catching all Exceptions
- log.exception('Unknown error, possibly benign: %r', e)
-
- def get_readers(self):
- result = []
- with self.condition:
- result = self.readers.keys()
- return result
-
- def add_reader(self, reader, socket):
+ if not self.zc.done:
+ for socket_ in rr:
+ reader = self.readers.get(socket_)
+ if reader:
+ reader.handle_read(socket_)
+
+ except socket.error as e:
+ # If the socket was closed by another thread, during
+ # shutdown, ignore it and exit
+ if e.errno != socket.EBADF or not self.zc.done:
+ raise
+
+ def add_reader(self, reader, socket_):
with self.condition:
- self.readers[socket] = reader
+ self.readers[socket_] = reader
self.condition.notify()
- def del_reader(self, socket):
- with self.condition:
- del self.readers[socket]
- self.condition.notify()
-
- def notify(self):
+ def del_reader(self, socket_):
with self.condition:
+ del self.readers[socket_]
self.condition.notify()
@@ -865,24 +974,15 @@ class Listener(object):
to cache information as it arrives.
It requires registration with an Engine object in order to have
- the read() method called when a socket is availble for reading."""
+ the read() method called when a socket is available for reading."""
def __init__(self, zc):
self.zc = zc
+ self.data = None
def handle_read(self, socket_):
- try:
- data, (addr, port) = socket_.recvfrom(_MAX_MSG_ABSOLUTE)
- except socket.error as e:
- # If the socket was closed by another thread -- which happens
- # regularly on shutdown -- an EBADF exception is thrown here.
- # Ignore it.
- if e.errno == socket.EBADF:
- return
- else:
- raise e
- else:
- log.debug('Received %r from %r:%r', data, addr, port)
+ data, (addr, port) = socket_.recvfrom(_MAX_MSG_ABSOLUTE)
+ log.debug('Received %r from %r:%r', data, addr, port)
self.data = data
msg = DNSIncoming(data)
@@ -907,7 +1007,7 @@ class Reaper(threading.Thread):
have expired."""
def __init__(self, zc):
- threading.Thread.__init__(self)
+ threading.Thread.__init__(self, name='zeroconf-Reaper')
self.daemon = True
self.zc = zc
self.start()
@@ -915,7 +1015,7 @@ class Reaper(threading.Thread):
def run(self):
while True:
self.zc.wait(10 * 1000)
- if _GLOBAL_DONE:
+ if self.zc.done:
return
now = current_time_millis()
for record in self.zc.cache.entries():
@@ -962,7 +1062,10 @@ class ServiceBrowser(threading.Thread):
def __init__(self, zc, type_, handlers=None, listener=None):
"""Creates a browser for a specific type"""
assert handlers or listener, 'You need to specify at least one handler'
- threading.Thread.__init__(self)
+ if not type_.endswith(service_type_name(type_)):
+ raise BadTypeInNameException
+ threading.Thread.__init__(
+ self, name='zeroconf-ServiceBrowser_' + type_)
self.daemon = True
self.zc = zc
self.type = type_
@@ -971,12 +1074,10 @@ class ServiceBrowser(threading.Thread):
self.delay = _BROWSER_TIME
self._handlers_to_call = []
- self.done = False
-
- self.zc.add_listener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
-
self._service_state_changed = Signal()
+ self.done = False
+
if hasattr(handlers, 'add_service'):
listener = handlers
handlers = None
... 541 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-zeroconf.git
More information about the Python-modules-commits
mailing list