74 """0.04 update - added some unit tests |
74 """0.04 update - added some unit tests |
75 added __ne__ adjuncts where required |
75 added __ne__ adjuncts where required |
76 ensure names end in '.local.' |
76 ensure names end in '.local.' |
77 timeout on receiving socket for clean shutdown""" |
77 timeout on receiving socket for clean shutdown""" |
78 |
78 |
79 __author__ = "Paul Scott-Murphy" |
79 __author__ = b"Paul Scott-Murphy" |
80 __email__ = "paul at scott dash murphy dot com" |
80 __email__ = b"paul at scott dash murphy dot com" |
81 __version__ = "0.12" |
81 __version__ = b"0.12" |
82 |
82 |
83 import errno |
83 import errno |
84 import itertools |
84 import itertools |
85 import select |
85 import select |
86 import socket |
86 import socket |
158 _TYPE_ANY = 255 |
158 _TYPE_ANY = 255 |
159 |
159 |
160 # Mapping constants to names |
160 # Mapping constants to names |
161 |
161 |
162 _CLASSES = { |
162 _CLASSES = { |
163 _CLASS_IN: "in", |
163 _CLASS_IN: b"in", |
164 _CLASS_CS: "cs", |
164 _CLASS_CS: b"cs", |
165 _CLASS_CH: "ch", |
165 _CLASS_CH: b"ch", |
166 _CLASS_HS: "hs", |
166 _CLASS_HS: b"hs", |
167 _CLASS_NONE: "none", |
167 _CLASS_NONE: b"none", |
168 _CLASS_ANY: "any", |
168 _CLASS_ANY: b"any", |
169 } |
169 } |
170 |
170 |
171 _TYPES = { |
171 _TYPES = { |
172 _TYPE_A: "a", |
172 _TYPE_A: b"a", |
173 _TYPE_NS: "ns", |
173 _TYPE_NS: b"ns", |
174 _TYPE_MD: "md", |
174 _TYPE_MD: b"md", |
175 _TYPE_MF: "mf", |
175 _TYPE_MF: b"mf", |
176 _TYPE_CNAME: "cname", |
176 _TYPE_CNAME: b"cname", |
177 _TYPE_SOA: "soa", |
177 _TYPE_SOA: b"soa", |
178 _TYPE_MB: "mb", |
178 _TYPE_MB: b"mb", |
179 _TYPE_MG: "mg", |
179 _TYPE_MG: b"mg", |
180 _TYPE_MR: "mr", |
180 _TYPE_MR: b"mr", |
181 _TYPE_NULL: "null", |
181 _TYPE_NULL: b"null", |
182 _TYPE_WKS: "wks", |
182 _TYPE_WKS: b"wks", |
183 _TYPE_PTR: "ptr", |
183 _TYPE_PTR: b"ptr", |
184 _TYPE_HINFO: "hinfo", |
184 _TYPE_HINFO: b"hinfo", |
185 _TYPE_MINFO: "minfo", |
185 _TYPE_MINFO: b"minfo", |
186 _TYPE_MX: "mx", |
186 _TYPE_MX: b"mx", |
187 _TYPE_TXT: "txt", |
187 _TYPE_TXT: b"txt", |
188 _TYPE_AAAA: "quada", |
188 _TYPE_AAAA: b"quada", |
189 _TYPE_SRV: "srv", |
189 _TYPE_SRV: b"srv", |
190 _TYPE_ANY: "any", |
190 _TYPE_ANY: b"any", |
191 } |
191 } |
192 |
192 |
193 # utility functions |
193 # utility functions |
194 |
194 |
195 |
195 |
260 def getClazz(self, clazz): |
260 def getClazz(self, clazz): |
261 """Class accessor""" |
261 """Class accessor""" |
262 try: |
262 try: |
263 return _CLASSES[clazz] |
263 return _CLASSES[clazz] |
264 except KeyError: |
264 except KeyError: |
265 return "?(%s)" % clazz |
265 return b"?(%s)" % clazz |
266 |
266 |
267 def getType(self, type): |
267 def getType(self, type): |
268 """Type accessor""" |
268 """Type accessor""" |
269 try: |
269 try: |
270 return _TYPES[type] |
270 return _TYPES[type] |
271 except KeyError: |
271 except KeyError: |
272 return "?(%s)" % type |
272 return b"?(%s)" % type |
273 |
273 |
274 def toString(self, hdr, other): |
274 def toString(self, hdr, other): |
275 """String representation with additional information""" |
275 """String representation with additional information""" |
276 result = "%s[%s,%s" % ( |
276 result = b"%s[%s,%s" % ( |
277 hdr, |
277 hdr, |
278 self.getType(self.type), |
278 self.getType(self.type), |
279 self.getClazz(self.clazz), |
279 self.getClazz(self.clazz), |
280 ) |
280 ) |
281 if self.unique: |
281 if self.unique: |
282 result += "-unique," |
282 result += b"-unique," |
283 else: |
283 else: |
284 result += "," |
284 result += b"," |
285 result += self.name |
285 result += self.name |
286 if other is not None: |
286 if other is not None: |
287 result += ",%s]" % other |
287 result += b",%s]" % other |
288 else: |
288 else: |
289 result += "]" |
289 result += b"]" |
290 return result |
290 return result |
291 |
291 |
292 |
292 |
293 class DNSQuestion(DNSEntry): |
293 class DNSQuestion(DNSEntry): |
294 """A DNS question entry""" |
294 """A DNS question entry""" |
295 |
295 |
296 def __init__(self, name, type, clazz): |
296 def __init__(self, name, type, clazz): |
297 if pycompat.ispy3 and isinstance(name, str): |
297 if pycompat.ispy3 and isinstance(name, str): |
298 name = name.encode('ascii') |
298 name = name.encode('ascii') |
299 if not name.endswith(".local."): |
299 if not name.endswith(b".local."): |
300 raise NonLocalNameException(name) |
300 raise NonLocalNameException(name) |
301 DNSEntry.__init__(self, name, type, clazz) |
301 DNSEntry.__init__(self, name, type, clazz) |
302 |
302 |
303 def answeredBy(self, rec): |
303 def answeredBy(self, rec): |
304 """Returns true if the question is answered by the record""" |
304 """Returns true if the question is answered by the record""" |
308 and self.name == rec.name |
308 and self.name == rec.name |
309 ) |
309 ) |
310 |
310 |
311 def __repr__(self): |
311 def __repr__(self): |
312 """String representation""" |
312 """String representation""" |
313 return DNSEntry.toString(self, "question", None) |
313 return DNSEntry.toString(self, b"question", None) |
314 |
314 |
315 |
315 |
316 class DNSRecord(DNSEntry): |
316 class DNSRecord(DNSEntry): |
317 """A DNS record - like a DNS entry, but has a TTL""" |
317 """A DNS record - like a DNS entry, but has a TTL""" |
318 |
318 |
369 """Abstract method""" |
369 """Abstract method""" |
370 raise AbstractMethodException |
370 raise AbstractMethodException |
371 |
371 |
372 def toString(self, other): |
372 def toString(self, other): |
373 """String representation with additional information""" |
373 """String representation with additional information""" |
374 arg = "%s/%s,%s" % ( |
374 arg = b"%s/%s,%s" % ( |
375 self.ttl, |
375 self.ttl, |
376 self.getRemainingTTL(currentTimeMillis()), |
376 self.getRemainingTTL(currentTimeMillis()), |
377 other, |
377 other, |
378 ) |
378 ) |
379 return DNSEntry.toString(self, "record", arg) |
379 return DNSEntry.toString(self, b"record", arg) |
380 |
380 |
381 |
381 |
382 class DNSAddress(DNSRecord): |
382 class DNSAddress(DNSRecord): |
383 """A DNS address record""" |
383 """A DNS address record""" |
384 |
384 |
526 self.readQuestions() |
526 self.readQuestions() |
527 self.readOthers() |
527 self.readOthers() |
528 |
528 |
529 def readHeader(self): |
529 def readHeader(self): |
530 """Reads header portion of packet""" |
530 """Reads header portion of packet""" |
531 format = '!HHHHHH' |
531 format = b'!HHHHHH' |
532 length = struct.calcsize(format) |
532 length = struct.calcsize(format) |
533 info = struct.unpack( |
533 info = struct.unpack( |
534 format, self.data[self.offset : self.offset + length] |
534 format, self.data[self.offset : self.offset + length] |
535 ) |
535 ) |
536 self.offset += length |
536 self.offset += length |
542 self.numauthorities = info[4] |
542 self.numauthorities = info[4] |
543 self.numadditionals = info[5] |
543 self.numadditionals = info[5] |
544 |
544 |
545 def readQuestions(self): |
545 def readQuestions(self): |
546 """Reads questions section of packet""" |
546 """Reads questions section of packet""" |
547 format = '!HH' |
547 format = b'!HH' |
548 length = struct.calcsize(format) |
548 length = struct.calcsize(format) |
549 for i in range(0, self.numquestions): |
549 for i in range(0, self.numquestions): |
550 name = self.readName() |
550 name = self.readName() |
551 info = struct.unpack( |
551 info = struct.unpack( |
552 format, self.data[self.offset : self.offset + length] |
552 format, self.data[self.offset : self.offset + length] |
559 except NonLocalNameException: |
559 except NonLocalNameException: |
560 pass |
560 pass |
561 |
561 |
562 def readInt(self): |
562 def readInt(self): |
563 """Reads an integer from the packet""" |
563 """Reads an integer from the packet""" |
564 format = '!I' |
564 format = b'!I' |
565 length = struct.calcsize(format) |
565 length = struct.calcsize(format) |
566 info = struct.unpack( |
566 info = struct.unpack( |
567 format, self.data[self.offset : self.offset + length] |
567 format, self.data[self.offset : self.offset + length] |
568 ) |
568 ) |
569 self.offset += length |
569 self.offset += length |
575 self.offset += 1 |
575 self.offset += 1 |
576 return self.readString(length) |
576 return self.readString(length) |
577 |
577 |
578 def readString(self, len): |
578 def readString(self, len): |
579 """Reads a string of a given length from the packet""" |
579 """Reads a string of a given length from the packet""" |
580 format = '!%ds' % len |
580 format = b'!%ds' % len |
581 length = struct.calcsize(format) |
581 length = struct.calcsize(format) |
582 info = struct.unpack( |
582 info = struct.unpack( |
583 format, self.data[self.offset : self.offset + length] |
583 format, self.data[self.offset : self.offset + length] |
584 ) |
584 ) |
585 self.offset += length |
585 self.offset += length |
586 return info[0] |
586 return info[0] |
587 |
587 |
588 def readUnsignedShort(self): |
588 def readUnsignedShort(self): |
589 """Reads an unsigned short from the packet""" |
589 """Reads an unsigned short from the packet""" |
590 format = '!H' |
590 format = b'!H' |
591 length = struct.calcsize(format) |
591 length = struct.calcsize(format) |
592 info = struct.unpack( |
592 info = struct.unpack( |
593 format, self.data[self.offset : self.offset + length] |
593 format, self.data[self.offset : self.offset + length] |
594 ) |
594 ) |
595 self.offset += length |
595 self.offset += length |
596 return info[0] |
596 return info[0] |
597 |
597 |
598 def readOthers(self): |
598 def readOthers(self): |
599 """Reads answers, authorities and additionals section of the packet""" |
599 """Reads answers, authorities and additionals section of the packet""" |
600 format = '!HHiH' |
600 format = b'!HHiH' |
601 length = struct.calcsize(format) |
601 length = struct.calcsize(format) |
602 n = self.numanswers + self.numauthorities + self.numadditionals |
602 n = self.numanswers + self.numauthorities + self.numadditionals |
603 for i in range(0, n): |
603 for i in range(0, n): |
604 domain = self.readName() |
604 domain = self.readName() |
605 info = struct.unpack( |
605 info = struct.unpack( |
744 """Adds an additional answer""" |
744 """Adds an additional answer""" |
745 self.additionals.append(record) |
745 self.additionals.append(record) |
746 |
746 |
747 def writeByte(self, value): |
747 def writeByte(self, value): |
748 """Writes a single byte to the packet""" |
748 """Writes a single byte to the packet""" |
749 format = '!c' |
749 format = b'!c' |
750 self.data.append(struct.pack(format, chr(value))) |
750 self.data.append(struct.pack(format, chr(value))) |
751 self.size += 1 |
751 self.size += 1 |
752 |
752 |
753 def insertShort(self, index, value): |
753 def insertShort(self, index, value): |
754 """Inserts an unsigned short in a certain position in the packet""" |
754 """Inserts an unsigned short in a certain position in the packet""" |
755 format = '!H' |
755 format = b'!H' |
756 self.data.insert(index, struct.pack(format, value)) |
756 self.data.insert(index, struct.pack(format, value)) |
757 self.size += 2 |
757 self.size += 2 |
758 |
758 |
759 def writeShort(self, value): |
759 def writeShort(self, value): |
760 """Writes an unsigned short to the packet""" |
760 """Writes an unsigned short to the packet""" |
761 format = '!H' |
761 format = b'!H' |
762 self.data.append(struct.pack(format, value)) |
762 self.data.append(struct.pack(format, value)) |
763 self.size += 2 |
763 self.size += 2 |
764 |
764 |
765 def writeInt(self, value): |
765 def writeInt(self, value): |
766 """Writes an unsigned integer to the packet""" |
766 """Writes an unsigned integer to the packet""" |
767 format = '!I' |
767 format = b'!I' |
768 self.data.append(struct.pack(format, int(value))) |
768 self.data.append(struct.pack(format, int(value))) |
769 self.size += 4 |
769 self.size += 4 |
770 |
770 |
771 def writeString(self, value, length): |
771 def writeString(self, value, length): |
772 """Writes a string to the packet""" |
772 """Writes a string to the packet""" |
773 format = '!' + str(length) + 's' |
773 format = b'!' + str(length) + b's' |
774 self.data.append(struct.pack(format, value)) |
774 self.data.append(struct.pack(format, value)) |
775 self.size += length |
775 self.size += length |
776 |
776 |
777 def writeUTF(self, s): |
777 def writeUTF(self, s): |
778 """Writes a UTF-8 string of a given length to the packet""" |
778 """Writes a UTF-8 string of a given length to the packet""" |
794 # No record of this name already, so write it |
794 # No record of this name already, so write it |
795 # out as normal, recording the location of the name |
795 # out as normal, recording the location of the name |
796 # for future pointers to it. |
796 # for future pointers to it. |
797 # |
797 # |
798 self.names[name] = self.size |
798 self.names[name] = self.size |
799 parts = name.split('.') |
799 parts = name.split(b'.') |
800 if parts[-1] == '': |
800 if parts[-1] == b'': |
801 parts = parts[:-1] |
801 parts = parts[:-1] |
802 for part in parts: |
802 for part in parts: |
803 self.writeUTF(part) |
803 self.writeUTF(part) |
804 self.writeByte(0) |
804 self.writeByte(0) |
805 return |
805 return |
861 self.insertShort(0, self.flags) |
861 self.insertShort(0, self.flags) |
862 if self.multicast: |
862 if self.multicast: |
863 self.insertShort(0, 0) |
863 self.insertShort(0, 0) |
864 else: |
864 else: |
865 self.insertShort(0, self.id) |
865 self.insertShort(0, self.id) |
866 return ''.join(self.data) |
866 return b''.join(self.data) |
867 |
867 |
868 |
868 |
869 class DNSCache(object): |
869 class DNSCache(object): |
870 """A cache of DNS entries""" |
870 """A cache of DNS entries""" |
871 |
871 |
953 rr, wr, er = select.select(rs, [], [], self.timeout) |
953 rr, wr, er = select.select(rs, [], [], self.timeout) |
954 for sock in rr: |
954 for sock in rr: |
955 try: |
955 try: |
956 self.readers[sock].handle_read() |
956 self.readers[sock].handle_read() |
957 except Exception: |
957 except Exception: |
958 if not globals()['_GLOBAL_DONE']: |
958 if not globals()[b'_GLOBAL_DONE']: |
959 traceback.print_exc() |
959 traceback.print_exc() |
960 except Exception: |
960 except Exception: |
961 pass |
961 pass |
962 |
962 |
963 def getReaders(self): |
963 def getReaders(self): |
1033 self.start() |
1033 self.start() |
1034 |
1034 |
1035 def run(self): |
1035 def run(self): |
1036 while True: |
1036 while True: |
1037 self.zeroconf.wait(10 * 1000) |
1037 self.zeroconf.wait(10 * 1000) |
1038 if globals()['_GLOBAL_DONE']: |
1038 if globals()[b'_GLOBAL_DONE']: |
1039 return |
1039 return |
1040 now = currentTimeMillis() |
1040 now = currentTimeMillis() |
1041 for record in self.zeroconf.cache.entries(): |
1041 for record in self.zeroconf.cache.entries(): |
1042 if record.isExpired(now): |
1042 if record.isExpired(now): |
1043 self.zeroconf.updateRecord(now, record) |
1043 self.zeroconf.updateRecord(now, record) |
1106 while True: |
1106 while True: |
1107 event = None |
1107 event = None |
1108 now = currentTimeMillis() |
1108 now = currentTimeMillis() |
1109 if len(self.list) == 0 and self.nexttime > now: |
1109 if len(self.list) == 0 and self.nexttime > now: |
1110 self.zeroconf.wait(self.nexttime - now) |
1110 self.zeroconf.wait(self.nexttime - now) |
1111 if globals()['_GLOBAL_DONE'] or self.done: |
1111 if globals()[b'_GLOBAL_DONE'] or self.done: |
1112 return |
1112 return |
1113 now = currentTimeMillis() |
1113 now = currentTimeMillis() |
1114 |
1114 |
1115 if self.nexttime <= now: |
1115 if self.nexttime <= now: |
1116 out = DNSOutgoing(_FLAGS_QR_QUERY) |
1116 out = DNSOutgoing(_FLAGS_QR_QUERY) |
1172 def setProperties(self, properties): |
1172 def setProperties(self, properties): |
1173 """Sets properties and text of this info from a dictionary""" |
1173 """Sets properties and text of this info from a dictionary""" |
1174 if isinstance(properties, dict): |
1174 if isinstance(properties, dict): |
1175 self.properties = properties |
1175 self.properties = properties |
1176 list = [] |
1176 list = [] |
1177 result = '' |
1177 result = b'' |
1178 for key in properties: |
1178 for key in properties: |
1179 value = properties[key] |
1179 value = properties[key] |
1180 if value is None: |
1180 if value is None: |
1181 suffix = '' |
1181 suffix = b'' |
1182 elif isinstance(value, str): |
1182 elif isinstance(value, str): |
1183 suffix = value |
1183 suffix = value |
1184 elif isinstance(value, int): |
1184 elif isinstance(value, int): |
1185 if value: |
1185 if value: |
1186 suffix = 'true' |
1186 suffix = b'true' |
1187 else: |
1187 else: |
1188 suffix = 'false' |
1188 suffix = b'false' |
1189 else: |
1189 else: |
1190 suffix = '' |
1190 suffix = b'' |
1191 list.append('='.join((key, suffix))) |
1191 list.append(b'='.join((key, suffix))) |
1192 for item in list: |
1192 for item in list: |
1193 result = ''.join( |
1193 result = b''.join( |
1194 (result, struct.pack('!c', chr(len(item))), item) |
1194 (result, struct.pack(b'!c', chr(len(item))), item) |
1195 ) |
1195 ) |
1196 self.text = result |
1196 self.text = result |
1197 else: |
1197 else: |
1198 self.text = properties |
1198 self.text = properties |
1199 |
1199 |
1210 index += 1 |
1210 index += 1 |
1211 strs.append(text[index : index + length]) |
1211 strs.append(text[index : index + length]) |
1212 index += length |
1212 index += length |
1213 |
1213 |
1214 for s in strs: |
1214 for s in strs: |
1215 eindex = s.find('=') |
1215 eindex = s.find(b'=') |
1216 if eindex == -1: |
1216 if eindex == -1: |
1217 # No equals sign at all |
1217 # No equals sign at all |
1218 key = s |
1218 key = s |
1219 value = 0 |
1219 value = 0 |
1220 else: |
1220 else: |
1221 key = s[:eindex] |
1221 key = s[:eindex] |
1222 value = s[eindex + 1 :] |
1222 value = s[eindex + 1 :] |
1223 if value == 'true': |
1223 if value == b'true': |
1224 value = 1 |
1224 value = 1 |
1225 elif value == 'false' or not value: |
1225 elif value == b'false' or not value: |
1226 value = 0 |
1226 value = 0 |
1227 |
1227 |
1228 # Only update non-existent properties |
1228 # Only update non-existent properties |
1229 if key and result.get(key) is None: |
1229 if key and result.get(key) is None: |
1230 result[key] = value |
1230 result[key] = value |
1366 """Non-equality test""" |
1366 """Non-equality test""" |
1367 return not self.__eq__(other) |
1367 return not self.__eq__(other) |
1368 |
1368 |
1369 def __repr__(self): |
1369 def __repr__(self): |
1370 """String representation""" |
1370 """String representation""" |
1371 result = "service[%s,%s:%s," % ( |
1371 result = b"service[%s,%s:%s," % ( |
1372 self.name, |
1372 self.name, |
1373 socket.inet_ntoa(self.getAddress()), |
1373 socket.inet_ntoa(self.getAddress()), |
1374 self.port, |
1374 self.port, |
1375 ) |
1375 ) |
1376 if self.text is None: |
1376 if self.text is None: |
1377 result += "None" |
1377 result += b"None" |
1378 else: |
1378 else: |
1379 if len(self.text) < 20: |
1379 if len(self.text) < 20: |
1380 result += self.text |
1380 result += self.text |
1381 else: |
1381 else: |
1382 result += self.text[:17] + "..." |
1382 result += self.text[:17] + b"..." |
1383 result += "]" |
1383 result += b"]" |
1384 return result |
1384 return result |
1385 |
1385 |
1386 |
1386 |
1387 class Zeroconf(object): |
1387 class Zeroconf(object): |
1388 """Implementation of Zeroconf Multicast DNS Service Discovery |
1388 """Implementation of Zeroconf Multicast DNS Service Discovery |
1391 """ |
1391 """ |
1392 |
1392 |
1393 def __init__(self, bindaddress=None): |
1393 def __init__(self, bindaddress=None): |
1394 """Creates an instance of the Zeroconf class, establishing |
1394 """Creates an instance of the Zeroconf class, establishing |
1395 multicast communications, listening and reaping threads.""" |
1395 multicast communications, listening and reaping threads.""" |
1396 globals()['_GLOBAL_DONE'] = 0 |
1396 globals()[b'_GLOBAL_DONE'] = 0 |
1397 if bindaddress is None: |
1397 if bindaddress is None: |
1398 self.intf = socket.gethostbyname(socket.gethostname()) |
1398 self.intf = socket.gethostbyname(socket.gethostname()) |
1399 else: |
1399 else: |
1400 self.intf = bindaddress |
1400 self.intf = bindaddress |
1401 self.group = ('', _MDNS_PORT) |
1401 self.group = (b'', _MDNS_PORT) |
1402 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
1402 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
1403 try: |
1403 try: |
1404 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
1404 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
1405 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) |
1405 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) |
1406 except Exception: |
1406 except Exception: |
1412 # if you're on a BSD-based system, and haven't upgraded |
1412 # if you're on a BSD-based system, and haven't upgraded |
1413 # to Python 2.3 yet, you may find this library doesn't |
1413 # to Python 2.3 yet, you may find this library doesn't |
1414 # work as expected. |
1414 # work as expected. |
1415 # |
1415 # |
1416 pass |
1416 pass |
1417 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, "\xff") |
1417 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, b"\xff") |
1418 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, "\x01") |
1418 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, b"\x01") |
1419 try: |
1419 try: |
1420 self.socket.bind(self.group) |
1420 self.socket.bind(self.group) |
1421 except Exception: |
1421 except Exception: |
1422 # Some versions of linux raise an exception even though |
1422 # Some versions of linux raise an exception even though |
1423 # SO_REUSEADDR and SO_REUSEPORT have been set, so ignore it |
1423 # SO_REUSEADDR and SO_REUSEPORT have been set, so ignore it |
1440 self.engine = Engine(self) |
1440 self.engine = Engine(self) |
1441 self.listener = Listener(self) |
1441 self.listener = Listener(self) |
1442 self.reaper = Reaper(self) |
1442 self.reaper = Reaper(self) |
1443 |
1443 |
1444 def isLoopback(self): |
1444 def isLoopback(self): |
1445 return self.intf.startswith("127.0.0.1") |
1445 return self.intf.startswith(b"127.0.0.1") |
1446 |
1446 |
1447 def isLinklocal(self): |
1447 def isLinklocal(self): |
1448 return self.intf.startswith("169.254.") |
1448 return self.intf.startswith(b"169.254.") |
1449 |
1449 |
1450 def wait(self, timeout): |
1450 def wait(self, timeout): |
1451 """Calling thread waits for a given number of milliseconds or |
1451 """Calling thread waits for a given number of milliseconds or |
1452 until notified.""" |
1452 until notified.""" |
1453 self.condition.acquire() |
1453 self.condition.acquire() |
1724 for question in msg.questions: |
1724 for question in msg.questions: |
1725 out.addQuestion(question) |
1725 out.addQuestion(question) |
1726 |
1726 |
1727 for question in msg.questions: |
1727 for question in msg.questions: |
1728 if question.type == _TYPE_PTR: |
1728 if question.type == _TYPE_PTR: |
1729 if question.name == "_services._dns-sd._udp.local.": |
1729 if question.name == b"_services._dns-sd._udp.local.": |
1730 for stype in self.servicetypes.keys(): |
1730 for stype in self.servicetypes.keys(): |
1731 if out is None: |
1731 if out is None: |
1732 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) |
1732 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) |
1733 out.addAnswer( |
1733 out.addAnswer( |
1734 msg, |
1734 msg, |
1735 DNSPointer( |
1735 DNSPointer( |
1736 "_services._dns-sd._udp.local.", |
1736 b"_services._dns-sd._udp.local.", |
1737 _TYPE_PTR, |
1737 _TYPE_PTR, |
1738 _CLASS_IN, |
1738 _CLASS_IN, |
1739 _DNS_TTL, |
1739 _DNS_TTL, |
1740 stype, |
1740 stype, |
1741 ), |
1741 ), |
1831 pass |
1831 pass |
1832 |
1832 |
1833 def close(self): |
1833 def close(self): |
1834 """Ends the background threads, and prevent this instance from |
1834 """Ends the background threads, and prevent this instance from |
1835 servicing further queries.""" |
1835 servicing further queries.""" |
1836 if globals()['_GLOBAL_DONE'] == 0: |
1836 if globals()[b'_GLOBAL_DONE'] == 0: |
1837 globals()['_GLOBAL_DONE'] = 1 |
1837 globals()[b'_GLOBAL_DONE'] = 1 |
1838 self.notifyAll() |
1838 self.notifyAll() |
1839 self.engine.notify() |
1839 self.engine.notify() |
1840 self.unregisterAllServices() |
1840 self.unregisterAllServices() |
1841 self.socket.setsockopt( |
1841 self.socket.setsockopt( |
1842 socket.SOL_IP, |
1842 socket.SOL_IP, |
1848 |
1848 |
1849 # Test a few module features, including service registration, service |
1849 # Test a few module features, including service registration, service |
1850 # query (for Zoe), and service unregistration. |
1850 # query (for Zoe), and service unregistration. |
1851 |
1851 |
1852 if __name__ == '__main__': |
1852 if __name__ == '__main__': |
1853 print("Multicast DNS Service Discovery for Python, version", __version__) |
1853 print(b"Multicast DNS Service Discovery for Python, version", __version__) |
1854 r = Zeroconf() |
1854 r = Zeroconf() |
1855 print("1. Testing registration of a service...") |
1855 print(b"1. Testing registration of a service...") |
1856 desc = {'version': '0.10', 'a': 'test value', 'b': 'another value'} |
1856 desc = {b'version': b'0.10', b'a': b'test value', b'b': b'another value'} |
1857 info = ServiceInfo( |
1857 info = ServiceInfo( |
1858 "_http._tcp.local.", |
1858 b"_http._tcp.local.", |
1859 "My Service Name._http._tcp.local.", |
1859 b"My Service Name._http._tcp.local.", |
1860 socket.inet_aton("127.0.0.1"), |
1860 socket.inet_aton(b"127.0.0.1"), |
1861 1234, |
1861 1234, |
1862 0, |
1862 0, |
1863 0, |
1863 0, |
1864 desc, |
1864 desc, |
1865 ) |
1865 ) |
1866 print(" Registering service...") |
1866 print(b" Registering service...") |
1867 r.registerService(info) |
1867 r.registerService(info) |
1868 print(" Registration done.") |
1868 print(b" Registration done.") |
1869 print("2. Testing query of service information...") |
1869 print(b"2. Testing query of service information...") |
1870 print( |
1870 print( |
1871 " Getting ZOE service:", |
1871 b" Getting ZOE service:", |
1872 str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local.")), |
1872 str(r.getServiceInfo(b"_http._tcp.local.", b"ZOE._http._tcp.local.")), |
1873 ) |
1873 ) |
1874 print(" Query done.") |
1874 print(b" Query done.") |
1875 print("3. Testing query of own service...") |
1875 print(b"3. Testing query of own service...") |
1876 print( |
1876 print( |
1877 " Getting self:", |
1877 b" Getting self:", |
1878 str( |
1878 str( |
1879 r.getServiceInfo( |
1879 r.getServiceInfo( |
1880 "_http._tcp.local.", "My Service Name._http._tcp.local." |
1880 b"_http._tcp.local.", b"My Service Name._http._tcp.local." |
1881 ) |
1881 ) |
1882 ), |
1882 ), |
1883 ) |
1883 ) |
1884 print(" Query done.") |
1884 print(b" Query done.") |
1885 print("4. Testing unregister of service information...") |
1885 print(b"4. Testing unregister of service information...") |
1886 r.unregisterService(info) |
1886 r.unregisterService(info) |
1887 print(" Unregister done.") |
1887 print(b" Unregister done.") |
1888 r.close() |
1888 r.close() |