17 Lesser General Public License for more details. |
17 Lesser General Public License for more details. |
18 |
18 |
19 You should have received a copy of the GNU Lesser General Public |
19 You should have received a copy of the GNU Lesser General Public |
20 License along with this library; if not, write to the Free Software |
20 License along with this library; if not, write to the Free Software |
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
22 |
22 |
23 """ |
23 """ |
24 |
24 |
25 """0.12 update - allow selection of binding interface |
25 """0.12 update - allow selection of binding interface |
26 typo fix - Thanks A. M. Kuchlingi |
26 typo fix - Thanks A. M. Kuchlingi |
27 removed all use of word 'Rendezvous' - this is an API change""" |
27 removed all use of word 'Rendezvous' - this is an API change""" |
254 result += "]" |
254 result += "]" |
255 return result |
255 return result |
256 |
256 |
257 class DNSQuestion(DNSEntry): |
257 class DNSQuestion(DNSEntry): |
258 """A DNS question entry""" |
258 """A DNS question entry""" |
259 |
259 |
260 def __init__(self, name, type, clazz): |
260 def __init__(self, name, type, clazz): |
261 if not name.endswith(".local."): |
261 if not name.endswith(".local."): |
262 raise NonLocalNameException |
262 raise NonLocalNameException |
263 DNSEntry.__init__(self, name, type, clazz) |
263 DNSEntry.__init__(self, name, type, clazz) |
264 |
264 |
271 return DNSEntry.toString(self, "question", None) |
271 return DNSEntry.toString(self, "question", None) |
272 |
272 |
273 |
273 |
274 class DNSRecord(DNSEntry): |
274 class DNSRecord(DNSEntry): |
275 """A DNS record - like a DNS entry, but has a TTL""" |
275 """A DNS record - like a DNS entry, but has a TTL""" |
276 |
276 |
277 def __init__(self, name, type, clazz, ttl): |
277 def __init__(self, name, type, clazz, ttl): |
278 DNSEntry.__init__(self, name, type, clazz) |
278 DNSEntry.__init__(self, name, type, clazz) |
279 self.ttl = ttl |
279 self.ttl = ttl |
280 self.created = currentTimeMillis() |
280 self.created = currentTimeMillis() |
281 |
281 |
332 arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other) |
332 arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other) |
333 return DNSEntry.toString(self, "record", arg) |
333 return DNSEntry.toString(self, "record", arg) |
334 |
334 |
335 class DNSAddress(DNSRecord): |
335 class DNSAddress(DNSRecord): |
336 """A DNS address record""" |
336 """A DNS address record""" |
337 |
337 |
338 def __init__(self, name, type, clazz, ttl, address): |
338 def __init__(self, name, type, clazz, ttl, address): |
339 DNSRecord.__init__(self, name, type, clazz, ttl) |
339 DNSRecord.__init__(self, name, type, clazz, ttl) |
340 self.address = address |
340 self.address = address |
341 |
341 |
342 def write(self, out): |
342 def write(self, out): |
376 return 0 |
376 return 0 |
377 |
377 |
378 def __repr__(self): |
378 def __repr__(self): |
379 """String representation""" |
379 """String representation""" |
380 return self.cpu + " " + self.os |
380 return self.cpu + " " + self.os |
381 |
381 |
382 class DNSPointer(DNSRecord): |
382 class DNSPointer(DNSRecord): |
383 """A DNS pointer record""" |
383 """A DNS pointer record""" |
384 |
384 |
385 def __init__(self, name, type, clazz, ttl, alias): |
385 def __init__(self, name, type, clazz, ttl, alias): |
386 DNSRecord.__init__(self, name, type, clazz, ttl) |
386 DNSRecord.__init__(self, name, type, clazz, ttl) |
387 self.alias = alias |
387 self.alias = alias |
388 |
388 |
389 def write(self, out): |
389 def write(self, out): |
400 """String representation""" |
400 """String representation""" |
401 return self.toString(self.alias) |
401 return self.toString(self.alias) |
402 |
402 |
403 class DNSText(DNSRecord): |
403 class DNSText(DNSRecord): |
404 """A DNS text record""" |
404 """A DNS text record""" |
405 |
405 |
406 def __init__(self, name, type, clazz, ttl, text): |
406 def __init__(self, name, type, clazz, ttl, text): |
407 DNSRecord.__init__(self, name, type, clazz, ttl) |
407 DNSRecord.__init__(self, name, type, clazz, ttl) |
408 self.text = text |
408 self.text = text |
409 |
409 |
410 def write(self, out): |
410 def write(self, out): |
424 else: |
424 else: |
425 return self.toString(self.text) |
425 return self.toString(self.text) |
426 |
426 |
427 class DNSService(DNSRecord): |
427 class DNSService(DNSRecord): |
428 """A DNS service record""" |
428 """A DNS service record""" |
429 |
429 |
430 def __init__(self, name, type, clazz, ttl, priority, weight, port, server): |
430 def __init__(self, name, type, clazz, ttl, priority, weight, port, server): |
431 DNSRecord.__init__(self, name, type, clazz, ttl) |
431 DNSRecord.__init__(self, name, type, clazz, ttl) |
432 self.priority = priority |
432 self.priority = priority |
433 self.weight = weight |
433 self.weight = weight |
434 self.port = port |
434 self.port = port |
451 """String representation""" |
451 """String representation""" |
452 return self.toString("%s:%s" % (self.server, self.port)) |
452 return self.toString("%s:%s" % (self.server, self.port)) |
453 |
453 |
454 class DNSIncoming(object): |
454 class DNSIncoming(object): |
455 """Object representation of an incoming DNS packet""" |
455 """Object representation of an incoming DNS packet""" |
456 |
456 |
457 def __init__(self, data): |
457 def __init__(self, data): |
458 """Constructor from string holding bytes of packet""" |
458 """Constructor from string holding bytes of packet""" |
459 self.offset = 0 |
459 self.offset = 0 |
460 self.data = data |
460 self.data = data |
461 self.questions = [] |
461 self.questions = [] |
462 self.answers = [] |
462 self.answers = [] |
463 self.numQuestions = 0 |
463 self.numQuestions = 0 |
464 self.numAnswers = 0 |
464 self.numAnswers = 0 |
465 self.numAuthorities = 0 |
465 self.numAuthorities = 0 |
466 self.numAdditionals = 0 |
466 self.numAdditionals = 0 |
467 |
467 |
468 self.readHeader() |
468 self.readHeader() |
469 self.readQuestions() |
469 self.readQuestions() |
470 self.readOthers() |
470 self.readOthers() |
471 |
471 |
472 def readHeader(self): |
472 def readHeader(self): |
489 length = struct.calcsize(format) |
489 length = struct.calcsize(format) |
490 for i in range(0, self.numQuestions): |
490 for i in range(0, self.numQuestions): |
491 name = self.readName() |
491 name = self.readName() |
492 info = struct.unpack(format, self.data[self.offset:self.offset+length]) |
492 info = struct.unpack(format, self.data[self.offset:self.offset+length]) |
493 self.offset += length |
493 self.offset += length |
494 |
494 |
495 question = DNSQuestion(name, info[0], info[1]) |
495 question = DNSQuestion(name, info[0], info[1]) |
496 self.questions.append(question) |
496 self.questions.append(question) |
497 |
497 |
498 def readInt(self): |
498 def readInt(self): |
499 """Reads an integer from the packet""" |
499 """Reads an integer from the packet""" |
658 def insertShort(self, index, value): |
658 def insertShort(self, index, value): |
659 """Inserts an unsigned short in a certain position in the packet""" |
659 """Inserts an unsigned short in a certain position in the packet""" |
660 format = '!H' |
660 format = '!H' |
661 self.data.insert(index, struct.pack(format, value)) |
661 self.data.insert(index, struct.pack(format, value)) |
662 self.size += 2 |
662 self.size += 2 |
663 |
663 |
664 def writeShort(self, value): |
664 def writeShort(self, value): |
665 """Writes an unsigned short to the packet""" |
665 """Writes an unsigned short to the packet""" |
666 format = '!H' |
666 format = '!H' |
667 self.data.append(struct.pack(format, value)) |
667 self.data.append(struct.pack(format, value)) |
668 self.size += 2 |
668 self.size += 2 |
756 self.writeRecord(answer, time) |
756 self.writeRecord(answer, time) |
757 for authority in self.authorities: |
757 for authority in self.authorities: |
758 self.writeRecord(authority, 0) |
758 self.writeRecord(authority, 0) |
759 for additional in self.additionals: |
759 for additional in self.additionals: |
760 self.writeRecord(additional, 0) |
760 self.writeRecord(additional, 0) |
761 |
761 |
762 self.insertShort(0, len(self.additionals)) |
762 self.insertShort(0, len(self.additionals)) |
763 self.insertShort(0, len(self.authorities)) |
763 self.insertShort(0, len(self.authorities)) |
764 self.insertShort(0, len(self.answers)) |
764 self.insertShort(0, len(self.answers)) |
765 self.insertShort(0, len(self.questions)) |
765 self.insertShort(0, len(self.questions)) |
766 self.insertShort(0, self.flags) |
766 self.insertShort(0, self.flags) |
870 result = [] |
870 result = [] |
871 self.condition.acquire() |
871 self.condition.acquire() |
872 result = self.readers.keys() |
872 result = self.readers.keys() |
873 self.condition.release() |
873 self.condition.release() |
874 return result |
874 return result |
875 |
875 |
876 def addReader(self, reader, socket): |
876 def addReader(self, reader, socket): |
877 self.condition.acquire() |
877 self.condition.acquire() |
878 self.readers[socket] = reader |
878 self.readers[socket] = reader |
879 self.condition.notify() |
879 self.condition.notify() |
880 self.condition.release() |
880 self.condition.release() |
895 group to which DNS messages are sent, allowing the implementation |
895 group to which DNS messages are sent, allowing the implementation |
896 to cache information as it arrives. |
896 to cache information as it arrives. |
897 |
897 |
898 It requires registration with an Engine object in order to have |
898 It requires registration with an Engine object in order to have |
899 the read() method called when a socket is availble for reading.""" |
899 the read() method called when a socket is availble for reading.""" |
900 |
900 |
901 def __init__(self, zeroconf): |
901 def __init__(self, zeroconf): |
902 self.zeroconf = zeroconf |
902 self.zeroconf = zeroconf |
903 self.zeroconf.engine.addReader(self, self.zeroconf.socket) |
903 self.zeroconf.engine.addReader(self, self.zeroconf.socket) |
904 |
904 |
905 def handle_read(self): |
905 def handle_read(self): |
946 """Used to browse for a service of a specific type. |
946 """Used to browse for a service of a specific type. |
947 |
947 |
948 The listener object will have its addService() and |
948 The listener object will have its addService() and |
949 removeService() methods called when this browser |
949 removeService() methods called when this browser |
950 discovers changes in the services availability.""" |
950 discovers changes in the services availability.""" |
951 |
951 |
952 def __init__(self, zeroconf, type, listener): |
952 def __init__(self, zeroconf, type, listener): |
953 """Creates a browser for a specific type""" |
953 """Creates a browser for a specific type""" |
954 threading.Thread.__init__(self) |
954 threading.Thread.__init__(self) |
955 self.zeroconf = zeroconf |
955 self.zeroconf = zeroconf |
956 self.type = type |
956 self.type = type |
957 self.listener = listener |
957 self.listener = listener |
958 self.services = {} |
958 self.services = {} |
959 self.nextTime = currentTimeMillis() |
959 self.nextTime = currentTimeMillis() |
960 self.delay = _BROWSER_TIME |
960 self.delay = _BROWSER_TIME |
961 self.list = [] |
961 self.list = [] |
962 |
962 |
963 self.done = 0 |
963 self.done = 0 |
964 |
964 |
965 self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) |
965 self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN)) |
966 self.start() |
966 self.start() |
967 |
967 |
1017 if len(self.list) > 0: |
1017 if len(self.list) > 0: |
1018 event = self.list.pop(0) |
1018 event = self.list.pop(0) |
1019 |
1019 |
1020 if event is not None: |
1020 if event is not None: |
1021 event(self.zeroconf) |
1021 event(self.zeroconf) |
1022 |
1022 |
1023 |
1023 |
1024 class ServiceInfo(object): |
1024 class ServiceInfo(object): |
1025 """Service information""" |
1025 """Service information""" |
1026 |
1026 |
1027 def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None): |
1027 def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None): |
1028 """Create a service description. |
1028 """Create a service description. |
1029 |
1029 |
1030 type: fully qualified service type name |
1030 type: fully qualified service type name |
1031 name: fully qualified service name |
1031 name: fully qualified service name |
1463 if entry is not None: |
1463 if entry is not None: |
1464 entry.resetTTL(record) |
1464 entry.resetTTL(record) |
1465 record = entry |
1465 record = entry |
1466 else: |
1466 else: |
1467 self.cache.add(record) |
1467 self.cache.add(record) |
1468 |
1468 |
1469 self.updateRecord(now, record) |
1469 self.updateRecord(now, record) |
1470 |
1470 |
1471 def handleQuery(self, msg, addr, port): |
1471 def handleQuery(self, msg, addr, port): |
1472 """Deal with incoming query packets. Provides a response if |
1472 """Deal with incoming query packets. Provides a response if |
1473 possible.""" |
1473 possible.""" |
1477 # |
1477 # |
1478 if port != _MDNS_PORT: |
1478 if port != _MDNS_PORT: |
1479 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0) |
1479 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0) |
1480 for question in msg.questions: |
1480 for question in msg.questions: |
1481 out.addQuestion(question) |
1481 out.addQuestion(question) |
1482 |
1482 |
1483 for question in msg.questions: |
1483 for question in msg.questions: |
1484 if question.type == _TYPE_PTR: |
1484 if question.type == _TYPE_PTR: |
1485 for service in self.services.values(): |
1485 for service in self.services.values(): |
1486 if question.name == service.type: |
1486 if question.name == service.type: |
1487 if out is None: |
1487 if out is None: |
1489 out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name)) |
1489 out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name)) |
1490 else: |
1490 else: |
1491 try: |
1491 try: |
1492 if out is None: |
1492 if out is None: |
1493 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) |
1493 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) |
1494 |
1494 |
1495 # Answer A record queries for any service addresses we know |
1495 # Answer A record queries for any service addresses we know |
1496 if question.type == _TYPE_A or question.type == _TYPE_ANY: |
1496 if question.type == _TYPE_A or question.type == _TYPE_ANY: |
1497 for service in self.services.values(): |
1497 for service in self.services.values(): |
1498 if service.server == question.name.lower(): |
1498 if service.server == question.name.lower(): |
1499 out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) |
1499 out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) |
1500 |
1500 |
1501 service = self.services.get(question.name.lower(), None) |
1501 service = self.services.get(question.name.lower(), None) |
1502 if not service: continue |
1502 if not service: continue |
1503 |
1503 |
1504 if question.type == _TYPE_SRV or question.type == _TYPE_ANY: |
1504 if question.type == _TYPE_SRV or question.type == _TYPE_ANY: |
1505 out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server)) |
1505 out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server)) |
1506 if question.type == _TYPE_TXT or question.type == _TYPE_ANY: |
1506 if question.type == _TYPE_TXT or question.type == _TYPE_ANY: |
1507 out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text)) |
1507 out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text)) |
1508 if question.type == _TYPE_SRV: |
1508 if question.type == _TYPE_SRV: |
1509 out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) |
1509 out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) |
1510 except: |
1510 except: |
1511 traceback.print_exc() |
1511 traceback.print_exc() |
1512 |
1512 |
1513 if out is not None and out.answers: |
1513 if out is not None and out.answers: |
1514 out.id = msg.id |
1514 out.id = msg.id |
1515 self.send(out, addr, port) |
1515 self.send(out, addr, port) |
1516 |
1516 |
1517 def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT): |
1517 def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT): |
1532 self.notifyAll() |
1532 self.notifyAll() |
1533 self.engine.notify() |
1533 self.engine.notify() |
1534 self.unregisterAllServices() |
1534 self.unregisterAllServices() |
1535 self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) |
1535 self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) |
1536 self.socket.close() |
1536 self.socket.close() |
1537 |
1537 |
1538 # Test a few module features, including service registration, service |
1538 # Test a few module features, including service registration, service |
1539 # query (for Zoe), and service unregistration. |
1539 # query (for Zoe), and service unregistration. |
1540 |
1540 |
1541 if __name__ == '__main__': |
1541 if __name__ == '__main__': |
1542 print "Multicast DNS Service Discovery for Python, version", __version__ |
1542 print "Multicast DNS Service Discovery for Python, version", __version__ |
1543 r = Zeroconf() |
1543 r = Zeroconf() |
1544 print "1. Testing registration of a service..." |
1544 print "1. Testing registration of a service..." |
1545 desc = {'version':'0.10','a':'test value', 'b':'another value'} |
1545 desc = {'version':'0.10','a':'test value', 'b':'another value'} |
1546 info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc) |
1546 info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc) |