IGN:Zeroconf better error handling

This commit is contained in:
Kovid Goyal 2009-10-21 08:17:11 -06:00
parent 51c08484ef
commit fb5902d7a0

View File

@ -19,7 +19,7 @@
You should have received a copy of the GNU Lesser General Public You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
''' '''
'''0.12 update - allow selection of binding interface '''0.12 update - allow selection of binding interface
@ -101,7 +101,7 @@ _LISTENER_TIME = 200
_BROWSER_TIME = 500 _BROWSER_TIME = 500
# Some DNS constants # Some DNS constants
_MDNS_ADDR = '224.0.0.251' _MDNS_ADDR = '224.0.0.251'
_MDNS_PORT = 5353; _MDNS_PORT = 5353;
_DNS_PORT = 53; _DNS_PORT = 53;
@ -208,7 +208,7 @@ class BadTypeInNameException(Exception):
class DNSEntry(object): class DNSEntry(object):
'''A DNS entry''' '''A DNS entry'''
def __init__(self, name, type, clazz): def __init__(self, name, type, clazz):
self.key = string.lower(name) self.key = string.lower(name)
self.name = name self.name = name
@ -256,10 +256,10 @@ class DNSEntry(object):
class DNSQuestion(DNSEntry): class DNSQuestion(DNSEntry):
'''A DNS question entry''' '''A DNS question entry'''
def __init__(self, name, type, clazz): def __init__(self, name, type, clazz):
if not name.endswith('.local.'): if not name.endswith('.local.'):
raise NonLocalNameException raise NonLocalNameException('DNSQuestion: Not a local name '+name)
DNSEntry.__init__(self, name, type, clazz) DNSEntry.__init__(self, name, type, clazz)
def answeredBy(self, rec): def answeredBy(self, rec):
@ -273,7 +273,7 @@ class DNSQuestion(DNSEntry):
class DNSRecord(DNSEntry): class DNSRecord(DNSEntry):
'''A DNS record - like a DNS entry, but has a TTL''' '''A DNS record - like a DNS entry, but has a TTL'''
def __init__(self, name, type, clazz, ttl): def __init__(self, name, type, clazz, ttl):
DNSEntry.__init__(self, name, type, clazz) DNSEntry.__init__(self, name, type, clazz)
self.ttl = ttl self.ttl = ttl
@ -334,7 +334,7 @@ class DNSRecord(DNSEntry):
class DNSAddress(DNSRecord): class DNSAddress(DNSRecord):
'''A DNS address record''' '''A DNS address record'''
def __init__(self, name, type, clazz, ttl, address): def __init__(self, name, type, clazz, ttl, address):
DNSRecord.__init__(self, name, type, clazz, ttl) DNSRecord.__init__(self, name, type, clazz, ttl)
self.address = address self.address = address
@ -378,10 +378,10 @@ class DNSHinfo(DNSRecord):
def __repr__(self): def __repr__(self):
'''String representation''' '''String representation'''
return self.cpu + ' ' + self.os return self.cpu + ' ' + self.os
class DNSPointer(DNSRecord): class DNSPointer(DNSRecord):
'''A DNS pointer record''' '''A DNS pointer record'''
def __init__(self, name, type, clazz, ttl, alias): def __init__(self, name, type, clazz, ttl, alias):
DNSRecord.__init__(self, name, type, clazz, ttl) DNSRecord.__init__(self, name, type, clazz, ttl)
self.alias = alias self.alias = alias
@ -402,7 +402,7 @@ class DNSPointer(DNSRecord):
class DNSText(DNSRecord): class DNSText(DNSRecord):
'''A DNS text record''' '''A DNS text record'''
def __init__(self, name, type, clazz, ttl, text): def __init__(self, name, type, clazz, ttl, text):
DNSRecord.__init__(self, name, type, clazz, ttl) DNSRecord.__init__(self, name, type, clazz, ttl)
self.text = text self.text = text
@ -426,7 +426,7 @@ class DNSText(DNSRecord):
class DNSService(DNSRecord): class DNSService(DNSRecord):
'''A DNS service record''' '''A DNS service record'''
def __init__(self, name, type, clazz, ttl, priority, weight, port, server): def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
DNSRecord.__init__(self, name, type, clazz, ttl) DNSRecord.__init__(self, name, type, clazz, ttl)
self.priority = priority self.priority = priority
@ -453,7 +453,7 @@ class DNSService(DNSRecord):
class DNSIncoming(object): class DNSIncoming(object):
'''Object representation of an incoming DNS packet''' '''Object representation of an incoming DNS packet'''
def __init__(self, data): def __init__(self, data):
'''Constructor from string holding bytes of packet''' '''Constructor from string holding bytes of packet'''
self.offset = 0 self.offset = 0
@ -464,7 +464,7 @@ class DNSIncoming(object):
self.numAnswers = 0 self.numAnswers = 0
self.numAuthorities = 0 self.numAuthorities = 0
self.numAdditionals = 0 self.numAdditionals = 0
self.readHeader() self.readHeader()
self.readQuestions() self.readQuestions()
self.readOthers() self.readOthers()
@ -491,7 +491,7 @@ class DNSIncoming(object):
name = self.readName() name = self.readName()
info = struct.unpack(format, self.data[self.offset:self.offset+length]) info = struct.unpack(format, self.data[self.offset:self.offset+length])
self.offset += length self.offset += length
question = DNSQuestion(name, info[0], info[1]) question = DNSQuestion(name, info[0], info[1])
self.questions.append(question) self.questions.append(question)
@ -561,7 +561,7 @@ class DNSIncoming(object):
if rec is not None: if rec is not None:
self.answers.append(rec) self.answers.append(rec)
def isQuery(self): def isQuery(self):
'''Returns true if this is a query''' '''Returns true if this is a query'''
return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
@ -574,7 +574,7 @@ class DNSIncoming(object):
'''Reads a UTF-8 string of a given length from the packet''' '''Reads a UTF-8 string of a given length from the packet'''
result = self.data[offset:offset+len].decode('utf-8') result = self.data[offset:offset+len].decode('utf-8')
return result return result
def readName(self): def readName(self):
'''Reads a domain name from the packet''' '''Reads a domain name from the packet'''
result = '' result = ''
@ -607,11 +607,11 @@ class DNSIncoming(object):
self.offset = off self.offset = off
return result return result
class DNSOutgoing(object): class DNSOutgoing(object):
'''Object representation of an outgoing packet''' '''Object representation of an outgoing packet'''
def __init__(self, flags, multicast = 1): def __init__(self, flags, multicast = 1):
self.finished = 0 self.finished = 0
self.id = 0 self.id = 0
@ -620,7 +620,7 @@ class DNSOutgoing(object):
self.names = {} self.names = {}
self.data = [] self.data = []
self.size = 12 self.size = 12
self.questions = [] self.questions = []
self.answers = [] self.answers = []
self.authorities = [] self.authorities = []
@ -660,7 +660,7 @@ class DNSOutgoing(object):
format = '!H' format = '!H'
self.data.insert(index, struct.pack(format, value)) self.data.insert(index, struct.pack(format, value))
self.size += 2 self.size += 2
def writeShort(self, value): def writeShort(self, value):
'''Writes an unsigned short to the packet''' '''Writes an unsigned short to the packet'''
format = '!H' format = '!H'
@ -739,7 +739,7 @@ class DNSOutgoing(object):
self.size += 2 self.size += 2
record.write(self) record.write(self)
self.size -= 2 self.size -= 2
length = len(''.join(self.data[index:])) length = len(''.join(self.data[index:]))
self.insertShort(index, length) # Here is the short we adjusted for self.insertShort(index, length) # Here is the short we adjusted for
@ -758,7 +758,7 @@ class DNSOutgoing(object):
self.writeRecord(authority, 0) self.writeRecord(authority, 0)
for additional in self.additionals: for additional in self.additionals:
self.writeRecord(additional, 0) self.writeRecord(additional, 0)
self.insertShort(0, len(self.additionals)) self.insertShort(0, len(self.additionals))
self.insertShort(0, len(self.authorities)) self.insertShort(0, len(self.authorities))
self.insertShort(0, len(self.answers)) self.insertShort(0, len(self.answers))
@ -773,7 +773,7 @@ class DNSOutgoing(object):
class DNSCache(object): class DNSCache(object):
'''A cache of DNS entries''' '''A cache of DNS entries'''
def __init__(self): def __init__(self):
self.cache = {} self.cache = {}
@ -856,11 +856,17 @@ class Engine(threading.Thread):
self.condition.wait(self.timeout) self.condition.wait(self.timeout)
self.condition.release() self.condition.release()
else: else:
from calibre.constants import DEBUG
try: try:
rr, wr, er = select.select(rs, [], [], self.timeout) rr, wr, er = select.select(rs, [], [], self.timeout)
for socket in rr: for socket in rr:
try: try:
self.readers[socket].handle_read() self.readers[socket].handle_read()
except NonLocalNameException, err:
print err
except UnicodeDecodeError:
if DEBUG:
traceback.print_exc()
except: except:
traceback.print_exc() traceback.print_exc()
except: except:
@ -872,7 +878,7 @@ class Engine(threading.Thread):
result = self.readers.keys() result = self.readers.keys()
self.condition.release() self.condition.release()
return result return result
def addReader(self, reader, socket): def addReader(self, reader, socket):
self.condition.acquire() self.condition.acquire()
self.readers[socket] = reader self.readers[socket] = reader
@ -897,7 +903,7 @@ class Listener(object):
It requires registration with an Engine object in order to have 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 availble for reading.'''
def __init__(self, zeroconf): def __init__(self, zeroconf):
self.zeroconf = zeroconf self.zeroconf = zeroconf
self.zeroconf.engine.addReader(self, self.zeroconf.socket) self.zeroconf.engine.addReader(self, self.zeroconf.socket)
@ -924,7 +930,7 @@ class Listener(object):
class Reaper(threading.Thread): class Reaper(threading.Thread):
'''A Reaper is used by this module to remove cache entries that '''A Reaper is used by this module to remove cache entries that
have expired.''' have expired.'''
def __init__(self, zeroconf): def __init__(self, zeroconf):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.zeroconf = zeroconf self.zeroconf = zeroconf
@ -953,7 +959,7 @@ class ServiceBrowser(threading.Thread):
The listener object will have its addService() and The listener object will have its addService() and
removeService() methods called when this browser removeService() methods called when this browser
discovers changes in the services availability.''' discovers changes in the services availability.'''
def __init__(self, zeroconf, type, listener): def __init__(self, zeroconf, type, listener):
'''Creates a browser for a specific type''' '''Creates a browser for a specific type'''
threading.Thread.__init__(self) threading.Thread.__init__(self)
@ -964,7 +970,7 @@ class ServiceBrowser(threading.Thread):
self.nextTime = currentTimeMillis() self.nextTime = currentTimeMillis()
self.delay = _BROWSER_TIME self.delay = _BROWSER_TIME
self.list = [] self.list = []
self.done = 0 self.done = 0
self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
@ -1024,12 +1030,12 @@ class ServiceBrowser(threading.Thread):
if event is not None: if event is not None:
event(self.zeroconf) event(self.zeroconf)
class ServiceInfo(object): class ServiceInfo(object):
'''Service information''' '''Service information'''
def __init__(self, type, name, address=None, port=None, weight=0, def __init__(self, type, name, address=None, port=None, weight=0,
priority=0, properties=None, server=None): priority=0, properties=None, server=None):
'''Create a service description. '''Create a service description.
@ -1095,7 +1101,7 @@ class ServiceInfo(object):
index += 1 index += 1
strs.append(text[index:index+length]) strs.append(text[index:index+length])
index += length index += length
for s in strs: for s in strs:
eindex = s.find('=') eindex = s.find('=')
if eindex == -1: if eindex == -1:
@ -1118,7 +1124,7 @@ class ServiceInfo(object):
except: except:
traceback.print_exc() traceback.print_exc()
self.properties = None self.properties = None
def getType(self): def getType(self):
'''Type accessor''' '''Type accessor'''
return self.type return self.type
@ -1208,7 +1214,7 @@ class ServiceInfo(object):
result = 1 result = 1
finally: finally:
zeroconf.removeListener(self) zeroconf.removeListener(self)
return result return result
def __eq__(self, other): def __eq__(self, other):
@ -1233,7 +1239,7 @@ class ServiceInfo(object):
result += self.text[:17] + '...' result += self.text[:17] + '...'
result += ']' result += ']'
return result return result
class Zeroconf(object): class Zeroconf(object):
'''Implementation of Zeroconf Multicast DNS Service Discovery '''Implementation of Zeroconf Multicast DNS Service Discovery
@ -1273,7 +1279,7 @@ class Zeroconf(object):
# the SO_REUSE* options have been set, so ignore it # the SO_REUSE* options have been set, so ignore it
# #
pass pass
#self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0')) #self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0'))
self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
self.listeners = [] self.listeners = []
@ -1284,7 +1290,7 @@ class Zeroconf(object):
self.cache = DNSCache() self.cache = DNSCache()
self.condition = threading.Condition() self.condition = threading.Condition()
self.engine = Engine(self) self.engine = Engine(self)
self.listener = Listener(self) self.listener = Listener(self)
self.reaper = Reaper(self) self.reaper = Reaper(self)
@ -1479,7 +1485,7 @@ class Zeroconf(object):
record = entry record = entry
else: else:
self.cache.add(record) self.cache.add(record)
self.updateRecord(now, record) self.updateRecord(now, record)
def handleQuery(self, msg, addr, port): def handleQuery(self, msg, addr, port):
@ -1493,14 +1499,14 @@ class Zeroconf(object):
out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0) out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
for question in msg.questions: for question in msg.questions:
out.addQuestion(question) out.addQuestion(question)
for question in msg.questions: for question in msg.questions:
if question.type == _TYPE_PTR: if question.type == _TYPE_PTR:
if question.name == '_services._dns-sd._udp.local.': if question.name == '_services._dns-sd._udp.local.':
for stype in self.servicetypes.keys(): for stype in self.servicetypes.keys():
if out is None: if out is None:
out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
out.addAnswer(msg, DNSPointer('_services._dns-sd._udp.local.', _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype)) out.addAnswer(msg, DNSPointer('_services._dns-sd._udp.local.', _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype))
for service in self.services.values(): for service in self.services.values():
if question.name == service.type: if question.name == service.type:
if out is None: if out is None:
@ -1510,16 +1516,16 @@ class Zeroconf(object):
try: try:
if out is None: if out is None:
out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
# Answer A record queries for any service addresses we know # Answer A record queries for any service addresses we know
if question.type == _TYPE_A or question.type == _TYPE_ANY: if question.type == _TYPE_A or question.type == _TYPE_ANY:
for service in self.services.values(): for service in self.services.values():
if service.server == question.name.lower(): if service.server == question.name.lower():
out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
service = self.services.get(question.name.lower(), None) service = self.services.get(question.name.lower(), None)
if not service: continue if not service: continue
if question.type == _TYPE_SRV or question.type == _TYPE_ANY: if question.type == _TYPE_SRV or question.type == _TYPE_ANY:
out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server)) out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server))
if question.type == _TYPE_TXT or question.type == _TYPE_ANY: if question.type == _TYPE_TXT or question.type == _TYPE_ANY:
@ -1528,7 +1534,7 @@ class Zeroconf(object):
out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address))
except: except:
traceback.print_exc() traceback.print_exc()
if out is not None and out.answers: if out is not None and out.answers:
out.id = msg.id out.id = msg.id
self.send(out, addr, port) self.send(out, addr, port)
@ -1553,11 +1559,11 @@ class Zeroconf(object):
self.unregisterAllServices() self.unregisterAllServices()
self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0'))
self.socket.close() self.socket.close()
# Test a few module features, including service registration, service # Test a few module features, including service registration, service
# query (for Zoe), and service unregistration. # query (for Zoe), and service unregistration.
if __name__ == '__main__': if __name__ == '__main__':
print 'Multicast DNS Service Discovery for Python, version', __version__ print 'Multicast DNS Service Discovery for Python, version', __version__
r = Zeroconf() r = Zeroconf()
print '1. Testing registration of a service...' print '1. Testing registration of a service...'