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