##// END OF EJS Templates
py3: hack around inconsistency of type of name passed to DNSQuestion...
Pulkit Goyal -
r42754:8306b6c2 default
parent child Browse files
Show More
@@ -1,785 +1,786 b''
1 1 test-abort-checkin.t
2 2 test-absorb-edit-lines.t
3 3 test-absorb-filefixupstate.py
4 4 test-absorb-phase.t
5 5 test-absorb-rename.t
6 6 test-absorb-strip.t
7 7 test-absorb-unfinished.t
8 8 test-absorb.t
9 9 test-acl.t
10 10 test-add.t
11 11 test-addremove-similar.t
12 12 test-addremove.t
13 13 test-alias.t
14 14 test-amend-subrepo.t
15 15 test-amend.t
16 16 test-ancestor.py
17 17 test-annotate.py
18 18 test-annotate.t
19 19 test-arbitraryfilectx.t
20 20 test-archive-symlinks.t
21 21 test-archive.t
22 22 test-atomictempfile.py
23 23 test-audit-path.t
24 24 test-audit-subrepo.t
25 25 test-automv.t
26 26 test-backout.t
27 27 test-backwards-remove.t
28 28 test-bad-extension.t
29 29 test-bad-pull.t
30 30 test-basic.t
31 31 test-batching.py
32 32 test-bdiff.py
33 33 test-bheads.t
34 34 test-bisect.t
35 35 test-bisect2.t
36 36 test-bisect3.t
37 37 test-blackbox.t
38 38 test-bookflow.t
39 39 test-bookmarks-corner-case.t
40 40 test-bookmarks-current.t
41 41 test-bookmarks-merge.t
42 42 test-bookmarks-pushpull.t
43 43 test-bookmarks-rebase.t
44 44 test-bookmarks-strip.t
45 45 test-bookmarks.t
46 46 test-branch-change.t
47 47 test-branch-option.t
48 48 test-branch-tag-confict.t
49 49 test-branches.t
50 50 test-bugzilla.t
51 51 test-bundle-phases.t
52 52 test-bundle-r.t
53 53 test-bundle-type.t
54 54 test-bundle-vs-outgoing.t
55 55 test-bundle.t
56 56 test-bundle2-exchange.t
57 57 test-bundle2-format.t
58 58 test-bundle2-multiple-changegroups.t
59 59 test-bundle2-pushback.t
60 60 test-bundle2-remote-changegroup.t
61 61 test-cache-abuse.t
62 62 test-cappedreader.py
63 63 test-casecollision-merge.t
64 64 test-casecollision.t
65 65 test-casefolding.t
66 66 test-cat.t
67 67 test-cbor.py
68 68 test-censor.t
69 69 test-changelog-exec.t
70 70 test-check-code.t
71 71 test-check-commit.t
72 72 test-check-config.t
73 73 test-check-execute.t
74 74 test-check-help.t
75 75 test-check-interfaces.py
76 76 test-check-module-imports.t
77 77 test-check-py3-compat.t
78 78 test-check-pyflakes.t
79 79 test-check-pylint.t
80 80 test-check-shbang.t
81 81 test-children.t
82 82 test-churn.t
83 83 test-clone-cgi.t
84 84 test-clone-pull-corruption.t
85 85 test-clone-r.t
86 86 test-clone-uncompressed.t
87 87 test-clone-update-order.t
88 88 test-clone.t
89 89 test-clonebundles.t
90 90 test-close-head.t
91 91 test-commandserver.t
92 92 test-commit-amend.t
93 93 test-commit-interactive-curses.t
94 94 test-commit-interactive.t
95 95 test-commit-multiple.t
96 96 test-commit-unresolved.t
97 97 test-commit.t
98 98 test-committer.t
99 99 test-completion.t
100 100 test-config-env.py
101 101 test-config.t
102 102 test-conflict.t
103 103 test-confused-revert.t
104 104 test-context-metadata.t
105 105 test-context.py
106 106 test-contrib-check-code.t
107 107 test-contrib-check-commit.t
108 108 test-contrib-dumprevlog.t
109 109 test-contrib-emacs.t
110 110 test-contrib-perf.t
111 111 test-contrib-relnotes.t
112 112 test-contrib-testparseutil.t
113 113 test-contrib.t
114 114 test-convert-authormap.t
115 115 test-convert-clonebranches.t
116 116 test-convert-cvs-branch.t
117 117 test-convert-cvs-detectmerge.t
118 118 test-convert-cvs-synthetic.t
119 119 test-convert-cvs.t
120 120 test-convert-cvsnt-mergepoints.t
121 121 test-convert-datesort.t
122 122 test-convert-filemap.t
123 123 test-convert-git.t
124 124 test-convert-hg-sink.t
125 125 test-convert-hg-source.t
126 126 test-convert-hg-startrev.t
127 127 test-convert-mtn.t
128 128 test-convert-splicemap.t
129 129 test-convert-svn-sink.t
130 130 test-convert-tagsbranch-topology.t
131 131 test-convert.t
132 132 test-copies-in-changeset.t
133 133 test-copies.t
134 134 test-copy-move-merge.t
135 135 test-copy.t
136 136 test-copytrace-heuristics.t
137 137 test-custom-filters.t
138 138 test-debugbuilddag.t
139 139 test-debugbundle.t
140 140 test-debugcommands.t
141 141 test-debugextensions.t
142 142 test-debugindexdot.t
143 143 test-debugrename.t
144 144 test-default-push.t
145 145 test-demandimport.py
146 146 test-devel-warnings.t
147 147 test-diff-antipatience.t
148 148 test-diff-binary-file.t
149 149 test-diff-change.t
150 150 test-diff-color.t
151 151 test-diff-copy-depth.t
152 152 test-diff-hashes.t
153 153 test-diff-ignore-whitespace.t
154 154 test-diff-indent-heuristic.t
155 155 test-diff-issue2761.t
156 156 test-diff-newlines.t
157 157 test-diff-reverse.t
158 158 test-diff-subdir.t
159 159 test-diff-unified.t
160 160 test-diff-upgrade.t
161 161 test-diffdir.t
162 162 test-diffstat.t
163 163 test-directaccess.t
164 164 test-dirstate-backup.t
165 165 test-dirstate-nonnormalset.t
166 166 test-dirstate-race.t
167 167 test-dirstate-race2.t
168 168 test-dirstate.t
169 169 test-dispatch.py
170 170 test-dispatch.t
171 171 test-doctest.py
172 172 test-double-merge.t
173 173 test-drawdag.t
174 174 test-duplicateoptions.py
175 175 test-editor-filename.t
176 176 test-empty-dir.t
177 177 test-empty-file.t
178 178 test-empty-group.t
179 179 test-empty.t
180 180 test-encode.t
181 181 test-encoding-align.t
182 182 test-encoding-func.py
183 183 test-encoding-textwrap.t
184 184 test-encoding.t
185 185 test-eol-add.t
186 186 test-eol-clone.t
187 187 test-eol-hook.t
188 188 test-eol-patch.t
189 189 test-eol-tag.t
190 190 test-eol-update.t
191 191 test-eol.t
192 192 test-eolfilename.t
193 193 test-excessive-merge.t
194 194 test-exchange-obsmarkers-case-A1.t
195 195 test-exchange-obsmarkers-case-A2.t
196 196 test-exchange-obsmarkers-case-A3.t
197 197 test-exchange-obsmarkers-case-A4.t
198 198 test-exchange-obsmarkers-case-A5.t
199 199 test-exchange-obsmarkers-case-A6.t
200 200 test-exchange-obsmarkers-case-A7.t
201 201 test-exchange-obsmarkers-case-B1.t
202 202 test-exchange-obsmarkers-case-B2.t
203 203 test-exchange-obsmarkers-case-B3.t
204 204 test-exchange-obsmarkers-case-B4.t
205 205 test-exchange-obsmarkers-case-B5.t
206 206 test-exchange-obsmarkers-case-B6.t
207 207 test-exchange-obsmarkers-case-B7.t
208 208 test-exchange-obsmarkers-case-C1.t
209 209 test-exchange-obsmarkers-case-C2.t
210 210 test-exchange-obsmarkers-case-C3.t
211 211 test-exchange-obsmarkers-case-C4.t
212 212 test-exchange-obsmarkers-case-D1.t
213 213 test-exchange-obsmarkers-case-D2.t
214 214 test-exchange-obsmarkers-case-D3.t
215 215 test-exchange-obsmarkers-case-D4.t
216 216 test-execute-bit.t
217 217 test-export.t
218 218 test-extdata.t
219 219 test-extdiff.t
220 220 test-extension-timing.t
221 221 test-extension.t
222 222 test-extensions-afterloaded.t
223 223 test-extensions-wrapfunction.py
224 224 test-extra-filelog-entry.t
225 225 test-fastannotate-corrupt.t
226 226 test-fastannotate-diffopts.t
227 227 test-fastannotate-hg.t
228 228 test-fastannotate-perfhack.t
229 229 test-fastannotate-protocol.t
230 230 test-fastannotate-renames.t
231 231 test-fastannotate-revmap.py
232 232 test-fastannotate.t
233 233 test-fetch.t
234 234 test-filebranch.t
235 235 test-filecache.py
236 236 test-filelog.py
237 237 test-fileset-generated.t
238 238 test-fileset.t
239 239 test-fix-metadata.t
240 240 test-fix-topology.t
241 241 test-fix.t
242 242 test-flagprocessor.t
243 243 test-flags.t
244 244 test-fncache.t
245 245 test-gendoc-da.t
246 246 test-gendoc-de.t
247 247 test-gendoc-el.t
248 248 test-gendoc-fr.t
249 249 test-gendoc-it.t
250 250 test-gendoc-ja.t
251 251 test-gendoc-pt_BR.t
252 252 test-gendoc-ro.t
253 253 test-gendoc-ru.t
254 254 test-gendoc-sv.t
255 255 test-gendoc-zh_CN.t
256 256 test-gendoc-zh_TW.t
257 257 test-gendoc.t
258 258 test-generaldelta.t
259 259 test-getbundle.t
260 260 test-git-export.t
261 261 test-githelp.t
262 262 test-globalopts.t
263 263 test-glog-beautifygraph.t
264 264 test-glog-topological.t
265 265 test-glog.t
266 266 test-gpg.t
267 267 test-graft.t
268 268 test-grep.t
269 269 test-hardlinks.t
270 270 test-help-hide.t
271 271 test-help.t
272 272 test-hg-parseurl.py
273 273 test-hghave.t
274 274 test-hgignore.t
275 275 test-hgk.t
276 276 test-hgrc.t
277 277 test-hgweb-annotate-whitespace.t
278 278 test-hgweb-auth.py
279 279 test-hgweb-bundle.t
280 280 test-hgweb-commands.t
281 281 test-hgweb-csp.t
282 282 test-hgweb-descend-empties.t
283 283 test-hgweb-diffs.t
284 284 test-hgweb-empty.t
285 285 test-hgweb-filelog.t
286 286 test-hgweb-json.t
287 287 test-hgweb-no-path-info.t
288 288 test-hgweb-no-request-uri.t
289 289 test-hgweb-non-interactive.t
290 290 test-hgweb-raw.t
291 291 test-hgweb-removed.t
292 292 test-hgweb-symrev.t
293 293 test-hgweb.t
294 294 test-hgwebdir-paths.py
295 295 test-hgwebdir.t
296 296 test-hgwebdirsym.t
297 297 test-histedit-arguments.t
298 298 test-histedit-base.t
299 299 test-histedit-bookmark-motion.t
300 300 test-histedit-commute.t
301 301 test-histedit-drop.t
302 302 test-histedit-edit.t
303 303 test-histedit-fold-non-commute.t
304 304 test-histedit-fold.t
305 305 test-histedit-merge-tools.t
306 306 test-histedit-no-backup.t
307 307 test-histedit-no-change.t
308 308 test-histedit-non-commute-abort.t
309 309 test-histedit-non-commute.t
310 310 test-histedit-obsolete.t
311 311 test-histedit-outgoing.t
312 312 test-histedit-templates.t
313 313 test-http-api-httpv2.t
314 314 test-http-api.t
315 315 test-http-bad-server.t
316 316 test-http-branchmap.t
317 317 test-http-bundle1.t
318 318 test-http-clone-r.t
319 319 test-http-permissions.t
320 320 test-http-protocol.t
321 321 test-http-proxy.t
322 322 test-http.t
323 323 test-https.t
324 324 test-hybridencode.py
325 325 test-i18n.t
326 326 test-identify.t
327 327 test-impexp-branch.t
328 328 test-import-bypass.t
329 329 test-import-context.t
330 330 test-import-eol.t
331 331 test-import-git.t
332 332 test-import-merge.t
333 333 test-import-unknown.t
334 334 test-import.t
335 335 test-imports-checker.t
336 336 test-incoming-outgoing.t
337 337 test-infinitepush-bundlestore.t
338 338 test-infinitepush-ci.t
339 339 test-infinitepush.t
340 340 test-inherit-mode.t
341 341 test-init.t
342 342 test-install.t
343 343 test-issue1089.t
344 344 test-issue1102.t
345 345 test-issue1175.t
346 346 test-issue1306.t
347 347 test-issue1438.t
348 348 test-issue1502.t
349 349 test-issue1802.t
350 350 test-issue1877.t
351 351 test-issue1993.t
352 352 test-issue2137.t
353 353 test-issue3084.t
354 354 test-issue4074.t
355 355 test-issue522.t
356 356 test-issue586.t
357 357 test-issue5979.t
358 358 test-issue612.t
359 359 test-issue619.t
360 360 test-issue660.t
361 361 test-issue672.t
362 362 test-issue842.t
363 363 test-journal-exists.t
364 364 test-journal-share.t
365 365 test-journal.t
366 366 test-keyword.t
367 367 test-known.t
368 368 test-largefiles-cache.t
369 369 test-largefiles-misc.t
370 370 test-largefiles-small-disk.t
371 371 test-largefiles-update.t
372 372 test-largefiles-wireproto.t
373 373 test-largefiles.t
374 374 test-lfconvert.t
375 375 test-lfs-bundle.t
376 376 test-lfs-largefiles.t
377 377 test-lfs-pointer.py
378 378 test-lfs-serve.t
379 379 test-lfs-test-server.t
380 380 test-lfs.t
381 381 test-linelog.py
382 382 test-linerange.py
383 383 test-locate.t
384 384 test-lock-badness.t
385 385 test-lock.py
386 386 test-log-exthook.t
387 387 test-log-linerange.t
388 388 test-log.t
389 389 test-logexchange.t
390 390 test-logtoprocess.t
391 391 test-lrucachedict.py
392 392 test-mactext.t
393 393 test-mailmap.t
394 394 test-manifest-merging.t
395 395 test-manifest.py
396 396 test-manifest.t
397 397 test-match.py
398 398 test-mdiff.py
399 399 test-merge-changedelete.t
400 400 test-merge-closedheads.t
401 401 test-merge-commit.t
402 402 test-merge-criss-cross.t
403 403 test-merge-default.t
404 404 test-merge-force.t
405 405 test-merge-halt.t
406 406 test-merge-internal-tools-pattern.t
407 407 test-merge-local.t
408 408 test-merge-no-file-change.t
409 409 test-merge-remove.t
410 410 test-merge-revert.t
411 411 test-merge-revert2.t
412 412 test-merge-subrepos.t
413 413 test-merge-symlinks.t
414 414 test-merge-tools.t
415 415 test-merge-types.t
416 416 test-merge1.t
417 417 test-merge10.t
418 418 test-merge2.t
419 419 test-merge4.t
420 420 test-merge5.t
421 421 test-merge6.t
422 422 test-merge7.t
423 423 test-merge8.t
424 424 test-merge9.t
425 425 test-minifileset.py
426 426 test-minirst.py
427 427 test-missing-capability.t
428 428 test-mq-eol.t
429 429 test-mq-git.t
430 430 test-mq-guards.t
431 431 test-mq-header-date.t
432 432 test-mq-header-from.t
433 433 test-mq-merge.t
434 434 test-mq-missingfiles.t
435 435 test-mq-pull-from-bundle.t
436 436 test-mq-qclone-http.t
437 437 test-mq-qdelete.t
438 438 test-mq-qdiff.t
439 439 test-mq-qfold.t
440 440 test-mq-qgoto.t
441 441 test-mq-qimport-fail-cleanup.t
442 442 test-mq-qimport.t
443 443 test-mq-qnew.t
444 444 test-mq-qpush-exact.t
445 445 test-mq-qpush-fail.t
446 446 test-mq-qqueue.t
447 447 test-mq-qrefresh-interactive.t
448 448 test-mq-qrefresh-replace-log-message.t
449 449 test-mq-qrefresh.t
450 450 test-mq-qrename.t
451 451 test-mq-qsave.t
452 452 test-mq-safety.t
453 453 test-mq-subrepo-svn.t
454 454 test-mq-subrepo.t
455 455 test-mq-symlinks.t
456 456 test-mq.t
457 457 test-mv-cp-st-diff.t
458 458 test-narrow-acl.t
459 459 test-narrow-archive.t
460 460 test-narrow-clone-no-ellipsis.t
461 461 test-narrow-clone-non-narrow-server.t
462 462 test-narrow-clone-nonlinear.t
463 463 test-narrow-clone-stream.t
464 464 test-narrow-clone.t
465 465 test-narrow-commit.t
466 466 test-narrow-copies.t
467 467 test-narrow-debugcommands.t
468 468 test-narrow-debugrebuilddirstate.t
469 469 test-narrow-exchange-merges.t
470 470 test-narrow-exchange.t
471 471 test-narrow-expanddirstate.t
472 472 test-narrow-merge.t
473 473 test-narrow-patch.t
474 474 test-narrow-patterns.t
475 475 test-narrow-pull.t
476 476 test-narrow-rebase.t
477 477 test-narrow-shallow-merges.t
478 478 test-narrow-shallow.t
479 479 test-narrow-share.t
480 480 test-narrow-sparse.t
481 481 test-narrow-strip.t
482 482 test-narrow-trackedcmd.t
483 483 test-narrow-update.t
484 484 test-narrow-widen-no-ellipsis.t
485 485 test-narrow-widen.t
486 486 test-narrow.t
487 487 test-nested-repo.t
488 488 test-newbranch.t
489 489 test-newcgi.t
490 490 test-newercgi.t
491 491 test-nointerrupt.t
492 492 test-notify-changegroup.t
493 493 test-obshistory.t
494 494 test-obsmarker-template.t
495 495 test-obsmarkers-effectflag.t
496 496 test-obsolete-bounds-checking.t
497 497 test-obsolete-bundle-strip.t
498 498 test-obsolete-changeset-exchange.t
499 499 test-obsolete-checkheads.t
500 500 test-obsolete-distributed.t
501 501 test-obsolete-divergent.t
502 502 test-obsolete-tag-cache.t
503 503 test-obsolete.t
504 504 test-oldcgi.t
505 505 test-origbackup-conflict.t
506 506 test-pager-legacy.t
507 507 test-pager.t
508 508 test-parents.t
509 509 test-parse-date.t
510 510 test-parseindex.t
511 511 test-parseindex2.py
512 512 test-patch-offset.t
513 513 test-patch.t
514 514 test-patchbomb-bookmark.t
515 515 test-patchbomb-tls.t
516 516 test-patchbomb.t
517 517 test-pathconflicts-basic.t
518 518 test-pathconflicts-merge.t
519 519 test-pathconflicts-update.t
520 520 test-pathencode.py
521 test-paths.t
521 522 test-pending.t
522 523 test-permissions.t
523 524 test-phabricator.t
524 525 test-phase-archived.t
525 526 test-phases-exchange.t
526 527 test-phases.t
527 528 test-profile.t
528 529 test-progress.t
529 530 test-propertycache.py
530 531 test-pull-branch.t
531 532 test-pull-bundle.t
532 533 test-pull-http.t
533 534 test-pull-permission.t
534 535 test-pull-pull-corruption.t
535 536 test-pull-r.t
536 537 test-pull-update.t
537 538 test-pull.t
538 539 test-purge.t
539 540 test-push-cgi.t
540 541 test-push-checkheads-partial-C1.t
541 542 test-push-checkheads-partial-C2.t
542 543 test-push-checkheads-partial-C3.t
543 544 test-push-checkheads-partial-C4.t
544 545 test-push-checkheads-pruned-B1.t
545 546 test-push-checkheads-pruned-B2.t
546 547 test-push-checkheads-pruned-B3.t
547 548 test-push-checkheads-pruned-B4.t
548 549 test-push-checkheads-pruned-B5.t
549 550 test-push-checkheads-pruned-B6.t
550 551 test-push-checkheads-pruned-B7.t
551 552 test-push-checkheads-pruned-B8.t
552 553 test-push-checkheads-superceed-A1.t
553 554 test-push-checkheads-superceed-A2.t
554 555 test-push-checkheads-superceed-A3.t
555 556 test-push-checkheads-superceed-A4.t
556 557 test-push-checkheads-superceed-A5.t
557 558 test-push-checkheads-superceed-A6.t
558 559 test-push-checkheads-superceed-A7.t
559 560 test-push-checkheads-superceed-A8.t
560 561 test-push-checkheads-unpushed-D1.t
561 562 test-push-checkheads-unpushed-D2.t
562 563 test-push-checkheads-unpushed-D3.t
563 564 test-push-checkheads-unpushed-D4.t
564 565 test-push-checkheads-unpushed-D5.t
565 566 test-push-checkheads-unpushed-D6.t
566 567 test-push-checkheads-unpushed-D7.t
567 568 test-push-http.t
568 569 test-push-race.t
569 570 test-push-warn.t
570 571 test-push.t
571 572 test-pushvars.t
572 573 test-qrecord.t
573 574 test-rebase-abort.t
574 575 test-rebase-backup.t
575 576 test-rebase-base-flag.t
576 577 test-rebase-bookmarks.t
577 578 test-rebase-brute-force.t
578 579 test-rebase-cache.t
579 580 test-rebase-check-restore.t
580 581 test-rebase-collapse.t
581 582 test-rebase-conflicts.t
582 583 test-rebase-dest.t
583 584 test-rebase-detach.t
584 585 test-rebase-emptycommit.t
585 586 test-rebase-inmemory.t
586 587 test-rebase-interruptions.t
587 588 test-rebase-issue-noparam-single-rev.t
588 589 test-rebase-legacy.t
589 590 test-rebase-mq-skip.t
590 591 test-rebase-mq.t
591 592 test-rebase-named-branches.t
592 593 test-rebase-newancestor.t
593 594 test-rebase-obsolete.t
594 595 test-rebase-parameters.t
595 596 test-rebase-partial.t
596 597 test-rebase-pull.t
597 598 test-rebase-rename.t
598 599 test-rebase-scenario-global.t
599 600 test-rebase-templates.t
600 601 test-rebase-transaction.t
601 602 test-rebuildstate.t
602 603 test-record.t
603 604 test-releasenotes-formatting.t
604 605 test-releasenotes-merging.t
605 606 test-releasenotes-parsing.t
606 607 test-relink.t
607 608 test-remote-hidden.t
608 609 test-remotefilelog-bad-configs.t
609 610 test-remotefilelog-bgprefetch.t
610 611 test-remotefilelog-blame.t
611 612 test-remotefilelog-bundle2.t
612 613 test-remotefilelog-bundles.t
613 614 test-remotefilelog-cacheprocess.t
614 615 test-remotefilelog-clone-tree.t
615 616 test-remotefilelog-clone.t
616 617 test-remotefilelog-corrupt-cache.t
617 618 test-remotefilelog-datapack.py
618 619 test-remotefilelog-gc.t
619 620 test-remotefilelog-gcrepack.t
620 621 test-remotefilelog-histpack.py
621 622 test-remotefilelog-http.t
622 623 test-remotefilelog-keepset.t
623 624 test-remotefilelog-linknodes.t
624 625 test-remotefilelog-local.t
625 626 test-remotefilelog-log.t
626 627 test-remotefilelog-partial-shallow.t
627 628 test-remotefilelog-permissions.t
628 629 test-remotefilelog-prefetch.t
629 630 test-remotefilelog-pull-noshallow.t
630 631 test-remotefilelog-push-pull.t
631 632 test-remotefilelog-repack-fast.t
632 633 test-remotefilelog-repack.t
633 634 test-remotefilelog-share.t
634 635 test-remotefilelog-sparse.t
635 636 test-remotefilelog-tags.t
636 637 test-remotefilelog-wireproto.t
637 638 test-remove.t
638 639 test-removeemptydirs.t
639 640 test-rename-after-merge.t
640 641 test-rename-dir-merge.t
641 642 test-rename-merge1.t
642 643 test-rename-merge2.t
643 644 test-rename.t
644 645 test-repair-strip.t
645 646 test-repo-compengines.t
646 647 test-requires.t
647 648 test-resolve.t
648 649 test-revert-flags.t
649 650 test-revert-interactive.t
650 651 test-revert-unknown.t
651 652 test-revert.t
652 653 test-revisions.t
653 654 test-revlog-ancestry.py
654 655 test-revlog-group-emptyiter.t
655 656 test-revlog-mmapindex.t
656 657 test-revlog-packentry.t
657 658 test-revlog-raw.py
658 659 test-revlog-v2.t
659 660 test-revlog.t
660 661 test-revset-dirstate-parents.t
661 662 test-revset-legacy-lookup.t
662 663 test-revset-outgoing.t
663 664 test-revset.t
664 665 test-revset2.t
665 666 test-rollback.t
666 667 test-run-tests.py
667 668 test-run-tests.t
668 669 test-rust-ancestor.py
669 670 test-rust-discovery.py
670 671 test-schemes.t
671 672 test-serve.t
672 673 test-server-view.t
673 674 test-setdiscovery.t
674 675 test-share-bookmarks.t
675 676 test-share.t
676 677 test-shelve.t
677 678 test-shelve2.t
678 679 test-show-stack.t
679 680 test-show-work.t
680 681 test-show.t
681 682 test-simple-update.t
682 683 test-simplekeyvaluefile.py
683 684 test-simplemerge.py
684 685 test-single-head.t
685 686 test-sparse-clear.t
686 687 test-sparse-clone.t
687 688 test-sparse-import.t
688 689 test-sparse-merges.t
689 690 test-sparse-profiles.t
690 691 test-sparse-requirement.t
691 692 test-sparse-verbose-json.t
692 693 test-sparse.t
693 694 test-split.t
694 695 test-ssh-bundle1.t
695 696 test-ssh-clone-r.t
696 697 test-ssh-proto-unbundle.t
697 698 test-ssh-proto.t
698 699 test-ssh-repoerror.t
699 700 test-ssh.t
700 701 test-sshserver.py
701 702 test-stack.t
702 703 test-static-http.t
703 704 test-status-color.t
704 705 test-status-inprocess.py
705 706 test-status-rev.t
706 707 test-status-terse.t
707 708 test-status.t
708 709 test-storage.py
709 710 test-stream-bundle-v2.t
710 711 test-strict.t
711 712 test-strip-cross.t
712 713 test-strip.t
713 714 test-subrepo-deep-nested-change.t
714 715 test-subrepo-git.t
715 716 test-subrepo-missing.t
716 717 test-subrepo-paths.t
717 718 test-subrepo-recursion.t
718 719 test-subrepo-relative-path.t
719 720 test-subrepo-svn.t
720 721 test-subrepo.t
721 722 test-symlink-os-yes-fs-no.py
722 723 test-symlink-placeholder.t
723 724 test-symlinks.t
724 725 test-tag.t
725 726 test-tags.t
726 727 test-template-basic.t
727 728 test-template-functions.t
728 729 test-template-keywords.t
729 730 test-template-map.t
730 731 test-tools.t
731 732 test-transplant.t
732 733 test-treediscovery-legacy.t
733 734 test-treediscovery.t
734 735 test-treemanifest.t
735 736 test-trusted.py
736 737 test-ui-color.py
737 738 test-ui-config.py
738 739 test-ui-verbosity.py
739 740 test-unamend.t
740 741 test-unbundlehash.t
741 742 test-uncommit.t
742 743 test-unified-test.t
743 744 test-unionrepo.t
744 745 test-unrelated-pull.t
745 746 test-up-local-change.t
746 747 test-update-atomic.t
747 748 test-update-branches.t
748 749 test-update-dest.t
749 750 test-update-issue1456.t
750 751 test-update-names.t
751 752 test-update-reverse.t
752 753 test-upgrade-repo.t
753 754 test-url-download.t
754 755 test-url-rev.t
755 756 test-url.py
756 757 test-username-newline.t
757 758 test-util.py
758 759 test-verify.t
759 760 test-walk.t
760 761 test-walkrepo.py
761 762 test-websub.t
762 763 test-win32text.t
763 764 test-wireproto-caching.t
764 765 test-wireproto-clientreactor.py
765 766 test-wireproto-command-branchmap.t
766 767 test-wireproto-command-capabilities.t
767 768 test-wireproto-command-changesetdata.t
768 769 test-wireproto-command-filedata.t
769 770 test-wireproto-command-filesdata.t
770 771 test-wireproto-command-heads.t
771 772 test-wireproto-command-known.t
772 773 test-wireproto-command-listkeys.t
773 774 test-wireproto-command-lookup.t
774 775 test-wireproto-command-manifestdata.t
775 776 test-wireproto-command-pushkey.t
776 777 test-wireproto-command-rawstorefiledata.t
777 778 test-wireproto-content-redirects.t
778 779 test-wireproto-exchangev2.t
779 780 test-wireproto-framing.py
780 781 test-wireproto-serverreactor.py
781 782 test-wireproto.py
782 783 test-wireproto.t
783 784 test-worker.t
784 785 test-wsgirequest.py
785 786 test-xdg.t
@@ -1,1688 +1,1692 b''
1 1 from __future__ import absolute_import, print_function
2 2
3 3 """ Multicast DNS Service Discovery for Python, v0.12
4 4 Copyright (C) 2003, Paul Scott-Murphy
5 5
6 6 This module provides a framework for the use of DNS Service Discovery
7 7 using IP multicast. It has been tested against the JRendezvous
8 8 implementation from <a href="http://strangeberry.com">StrangeBerry</a>,
9 9 and against the mDNSResponder from Mac OS X 10.3.8.
10 10
11 11 This library is free software; you can redistribute it and/or
12 12 modify it under the terms of the GNU Lesser General Public
13 13 License as published by the Free Software Foundation; either
14 14 version 2.1 of the License, or (at your option) any later version.
15 15
16 16 This library is distributed in the hope that it will be useful,
17 17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 19 Lesser General Public License for more details.
20 20
21 21 You should have received a copy of the GNU Lesser General Public
22 22 License along with this library; if not, see
23 23 <http://www.gnu.org/licenses/>.
24 24
25 25 """
26 26
27 27 """0.12 update - allow selection of binding interface
28 28 typo fix - Thanks A. M. Kuchlingi
29 29 removed all use of word 'Rendezvous' - this is an API change"""
30 30
31 31 """0.11 update - correction to comments for addListener method
32 32 support for new record types seen from OS X
33 33 - IPv6 address
34 34 - hostinfo
35 35 ignore unknown DNS record types
36 36 fixes to name decoding
37 37 works alongside other processes using port 5353 (e.g. Mac OS X)
38 38 tested against Mac OS X 10.3.2's mDNSResponder
39 39 corrections to removal of list entries for service browser"""
40 40
41 41 """0.10 update - Jonathon Paisley contributed these corrections:
42 42 always multicast replies, even when query is unicast
43 43 correct a pointer encoding problem
44 44 can now write records in any order
45 45 traceback shown on failure
46 46 better TXT record parsing
47 47 server is now separate from name
48 48 can cancel a service browser
49 49
50 50 modified some unit tests to accommodate these changes"""
51 51
52 52 """0.09 update - remove all records on service unregistration
53 53 fix DOS security problem with readName"""
54 54
55 55 """0.08 update - changed licensing to LGPL"""
56 56
57 57 """0.07 update - faster shutdown on engine
58 58 pointer encoding of outgoing names
59 59 ServiceBrowser now works
60 60 new unit tests"""
61 61
62 62 """0.06 update - small improvements with unit tests
63 63 added defined exception types
64 64 new style objects
65 65 fixed hostname/interface problem
66 66 fixed socket timeout problem
67 67 fixed addServiceListener() typo bug
68 68 using select() for socket reads
69 69 tested on Debian unstable with Python 2.2.2"""
70 70
71 71 """0.05 update - ensure case insensitivity on domain names
72 72 support for unicast DNS queries"""
73 73
74 74 """0.04 update - added some unit tests
75 75 added __ne__ adjuncts where required
76 76 ensure names end in '.local.'
77 77 timeout on receiving socket for clean shutdown"""
78 78
79 79 __author__ = "Paul Scott-Murphy"
80 80 __email__ = "paul at scott dash murphy dot com"
81 81 __version__ = "0.12"
82 82
83 83 import errno
84 84 import itertools
85 85 import select
86 86 import socket
87 87 import struct
88 88 import threading
89 89 import time
90 90 import traceback
91 91
92 from mercurial import pycompat
93
92 94 __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"]
93 95
94 96 # hook for threads
95 97
96 98 globals()['_GLOBAL_DONE'] = 0
97 99
98 100 # Some timing constants
99 101
100 102 _UNREGISTER_TIME = 125
101 103 _CHECK_TIME = 175
102 104 _REGISTER_TIME = 225
103 105 _LISTENER_TIME = 200
104 106 _BROWSER_TIME = 500
105 107
106 108 # Some DNS constants
107 109
108 110 _MDNS_ADDR = r'224.0.0.251'
109 111 _MDNS_PORT = 5353
110 112 _DNS_PORT = 53
111 113 _DNS_TTL = 60 * 60 # one hour default TTL
112 114
113 115 _MAX_MSG_TYPICAL = 1460 # unused
114 116 _MAX_MSG_ABSOLUTE = 8972
115 117
116 118 _FLAGS_QR_MASK = 0x8000 # query response mask
117 119 _FLAGS_QR_QUERY = 0x0000 # query
118 120 _FLAGS_QR_RESPONSE = 0x8000 # response
119 121
120 122 _FLAGS_AA = 0x0400 # Authoritative answer
121 123 _FLAGS_TC = 0x0200 # Truncated
122 124 _FLAGS_RD = 0x0100 # Recursion desired
123 125 _FLAGS_RA = 0x8000 # Recursion available
124 126
125 127 _FLAGS_Z = 0x0040 # Zero
126 128 _FLAGS_AD = 0x0020 # Authentic data
127 129 _FLAGS_CD = 0x0010 # Checking disabled
128 130
129 131 _CLASS_IN = 1
130 132 _CLASS_CS = 2
131 133 _CLASS_CH = 3
132 134 _CLASS_HS = 4
133 135 _CLASS_NONE = 254
134 136 _CLASS_ANY = 255
135 137 _CLASS_MASK = 0x7FFF
136 138 _CLASS_UNIQUE = 0x8000
137 139
138 140 _TYPE_A = 1
139 141 _TYPE_NS = 2
140 142 _TYPE_MD = 3
141 143 _TYPE_MF = 4
142 144 _TYPE_CNAME = 5
143 145 _TYPE_SOA = 6
144 146 _TYPE_MB = 7
145 147 _TYPE_MG = 8
146 148 _TYPE_MR = 9
147 149 _TYPE_NULL = 10
148 150 _TYPE_WKS = 11
149 151 _TYPE_PTR = 12
150 152 _TYPE_HINFO = 13
151 153 _TYPE_MINFO = 14
152 154 _TYPE_MX = 15
153 155 _TYPE_TXT = 16
154 156 _TYPE_AAAA = 28
155 157 _TYPE_SRV = 33
156 158 _TYPE_ANY = 255
157 159
158 160 # Mapping constants to names
159 161
160 162 _CLASSES = { _CLASS_IN : "in",
161 163 _CLASS_CS : "cs",
162 164 _CLASS_CH : "ch",
163 165 _CLASS_HS : "hs",
164 166 _CLASS_NONE : "none",
165 167 _CLASS_ANY : "any" }
166 168
167 169 _TYPES = { _TYPE_A : "a",
168 170 _TYPE_NS : "ns",
169 171 _TYPE_MD : "md",
170 172 _TYPE_MF : "mf",
171 173 _TYPE_CNAME : "cname",
172 174 _TYPE_SOA : "soa",
173 175 _TYPE_MB : "mb",
174 176 _TYPE_MG : "mg",
175 177 _TYPE_MR : "mr",
176 178 _TYPE_NULL : "null",
177 179 _TYPE_WKS : "wks",
178 180 _TYPE_PTR : "ptr",
179 181 _TYPE_HINFO : "hinfo",
180 182 _TYPE_MINFO : "minfo",
181 183 _TYPE_MX : "mx",
182 184 _TYPE_TXT : "txt",
183 185 _TYPE_AAAA : "quada",
184 186 _TYPE_SRV : "srv",
185 187 _TYPE_ANY : "any" }
186 188
187 189 # utility functions
188 190
189 191 def currentTimeMillis():
190 192 """Current system time in milliseconds"""
191 193 return time.time() * 1000
192 194
193 195 # Exceptions
194 196
195 197 class NonLocalNameException(Exception):
196 198 pass
197 199
198 200 class NonUniqueNameException(Exception):
199 201 pass
200 202
201 203 class NamePartTooLongException(Exception):
202 204 pass
203 205
204 206 class AbstractMethodException(Exception):
205 207 pass
206 208
207 209 class BadTypeInNameException(Exception):
208 210 pass
209 211
210 212 class BadDomainName(Exception):
211 213 def __init__(self, pos):
212 214 Exception.__init__(self, "at position %s" % pos)
213 215
214 216 class BadDomainNameCircular(BadDomainName):
215 217 pass
216 218
217 219 # implementation classes
218 220
219 221 class DNSEntry(object):
220 222 """A DNS entry"""
221 223
222 224 def __init__(self, name, type, clazz):
223 225 self.key = name.lower()
224 226 self.name = name
225 227 self.type = type
226 228 self.clazz = clazz & _CLASS_MASK
227 229 self.unique = (clazz & _CLASS_UNIQUE) != 0
228 230
229 231 def __eq__(self, other):
230 232 """Equality test on name, type, and class"""
231 233 if isinstance(other, DNSEntry):
232 234 return (self.name == other.name and self.type == other.type and
233 235 self.clazz == other.clazz)
234 236 return 0
235 237
236 238 def __ne__(self, other):
237 239 """Non-equality test"""
238 240 return not self.__eq__(other)
239 241
240 242 def getClazz(self, clazz):
241 243 """Class accessor"""
242 244 try:
243 245 return _CLASSES[clazz]
244 246 except KeyError:
245 247 return "?(%s)" % (clazz)
246 248
247 249 def getType(self, type):
248 250 """Type accessor"""
249 251 try:
250 252 return _TYPES[type]
251 253 except KeyError:
252 254 return "?(%s)" % (type)
253 255
254 256 def toString(self, hdr, other):
255 257 """String representation with additional information"""
256 258 result = ("%s[%s,%s" %
257 259 (hdr, self.getType(self.type), self.getClazz(self.clazz)))
258 260 if self.unique:
259 261 result += "-unique,"
260 262 else:
261 263 result += ","
262 264 result += self.name
263 265 if other is not None:
264 266 result += ",%s]" % (other)
265 267 else:
266 268 result += "]"
267 269 return result
268 270
269 271 class DNSQuestion(DNSEntry):
270 272 """A DNS question entry"""
271 273
272 274 def __init__(self, name, type, clazz):
275 if pycompat.ispy3 and isinstance(name, str):
276 name = name.encode('ascii')
273 277 if not name.endswith(".local."):
274 278 raise NonLocalNameException(name)
275 279 DNSEntry.__init__(self, name, type, clazz)
276 280
277 281 def answeredBy(self, rec):
278 282 """Returns true if the question is answered by the record"""
279 283 return (self.clazz == rec.clazz and
280 284 (self.type == rec.type or self.type == _TYPE_ANY) and
281 285 self.name == rec.name)
282 286
283 287 def __repr__(self):
284 288 """String representation"""
285 289 return DNSEntry.toString(self, "question", None)
286 290
287 291
288 292 class DNSRecord(DNSEntry):
289 293 """A DNS record - like a DNS entry, but has a TTL"""
290 294
291 295 def __init__(self, name, type, clazz, ttl):
292 296 DNSEntry.__init__(self, name, type, clazz)
293 297 self.ttl = ttl
294 298 self.created = currentTimeMillis()
295 299
296 300 def __eq__(self, other):
297 301 """Tests equality as per DNSRecord"""
298 302 if isinstance(other, DNSRecord):
299 303 return DNSEntry.__eq__(self, other)
300 304 return 0
301 305
302 306 def suppressedBy(self, msg):
303 307 """Returns true if any answer in a message can suffice for the
304 308 information held in this record."""
305 309 for record in msg.answers:
306 310 if self.suppressedByAnswer(record):
307 311 return 1
308 312 return 0
309 313
310 314 def suppressedByAnswer(self, other):
311 315 """Returns true if another record has same name, type and class,
312 316 and if its TTL is at least half of this record's."""
313 317 if self == other and other.ttl > (self.ttl / 2):
314 318 return 1
315 319 return 0
316 320
317 321 def getExpirationTime(self, percent):
318 322 """Returns the time at which this record will have expired
319 323 by a certain percentage."""
320 324 return self.created + (percent * self.ttl * 10)
321 325
322 326 def getRemainingTTL(self, now):
323 327 """Returns the remaining TTL in seconds."""
324 328 return max(0, (self.getExpirationTime(100) - now) / 1000)
325 329
326 330 def isExpired(self, now):
327 331 """Returns true if this record has expired."""
328 332 return self.getExpirationTime(100) <= now
329 333
330 334 def isStale(self, now):
331 335 """Returns true if this record is at least half way expired."""
332 336 return self.getExpirationTime(50) <= now
333 337
334 338 def resetTTL(self, other):
335 339 """Sets this record's TTL and created time to that of
336 340 another record."""
337 341 self.created = other.created
338 342 self.ttl = other.ttl
339 343
340 344 def write(self, out):
341 345 """Abstract method"""
342 346 raise AbstractMethodException
343 347
344 348 def toString(self, other):
345 349 """String representation with additional information"""
346 350 arg = ("%s/%s,%s" %
347 351 (self.ttl, self.getRemainingTTL(currentTimeMillis()), other))
348 352 return DNSEntry.toString(self, "record", arg)
349 353
350 354 class DNSAddress(DNSRecord):
351 355 """A DNS address record"""
352 356
353 357 def __init__(self, name, type, clazz, ttl, address):
354 358 DNSRecord.__init__(self, name, type, clazz, ttl)
355 359 self.address = address
356 360
357 361 def write(self, out):
358 362 """Used in constructing an outgoing packet"""
359 363 out.writeString(self.address, len(self.address))
360 364
361 365 def __eq__(self, other):
362 366 """Tests equality on address"""
363 367 if isinstance(other, DNSAddress):
364 368 return self.address == other.address
365 369 return 0
366 370
367 371 def __repr__(self):
368 372 """String representation"""
369 373 try:
370 374 return socket.inet_ntoa(self.address)
371 375 except Exception:
372 376 return self.address
373 377
374 378 class DNSHinfo(DNSRecord):
375 379 """A DNS host information record"""
376 380
377 381 def __init__(self, name, type, clazz, ttl, cpu, os):
378 382 DNSRecord.__init__(self, name, type, clazz, ttl)
379 383 self.cpu = cpu
380 384 self.os = os
381 385
382 386 def write(self, out):
383 387 """Used in constructing an outgoing packet"""
384 388 out.writeString(self.cpu, len(self.cpu))
385 389 out.writeString(self.os, len(self.os))
386 390
387 391 def __eq__(self, other):
388 392 """Tests equality on cpu and os"""
389 393 if isinstance(other, DNSHinfo):
390 394 return self.cpu == other.cpu and self.os == other.os
391 395 return 0
392 396
393 397 def __repr__(self):
394 398 """String representation"""
395 399 return self.cpu + " " + self.os
396 400
397 401 class DNSPointer(DNSRecord):
398 402 """A DNS pointer record"""
399 403
400 404 def __init__(self, name, type, clazz, ttl, alias):
401 405 DNSRecord.__init__(self, name, type, clazz, ttl)
402 406 self.alias = alias
403 407
404 408 def write(self, out):
405 409 """Used in constructing an outgoing packet"""
406 410 out.writeName(self.alias)
407 411
408 412 def __eq__(self, other):
409 413 """Tests equality on alias"""
410 414 if isinstance(other, DNSPointer):
411 415 return self.alias == other.alias
412 416 return 0
413 417
414 418 def __repr__(self):
415 419 """String representation"""
416 420 return self.toString(self.alias)
417 421
418 422 class DNSText(DNSRecord):
419 423 """A DNS text record"""
420 424
421 425 def __init__(self, name, type, clazz, ttl, text):
422 426 DNSRecord.__init__(self, name, type, clazz, ttl)
423 427 self.text = text
424 428
425 429 def write(self, out):
426 430 """Used in constructing an outgoing packet"""
427 431 out.writeString(self.text, len(self.text))
428 432
429 433 def __eq__(self, other):
430 434 """Tests equality on text"""
431 435 if isinstance(other, DNSText):
432 436 return self.text == other.text
433 437 return 0
434 438
435 439 def __repr__(self):
436 440 """String representation"""
437 441 if len(self.text) > 10:
438 442 return self.toString(self.text[:7] + "...")
439 443 else:
440 444 return self.toString(self.text)
441 445
442 446 class DNSService(DNSRecord):
443 447 """A DNS service record"""
444 448
445 449 def __init__(self, name, type, clazz, ttl, priority, weight, port, server):
446 450 DNSRecord.__init__(self, name, type, clazz, ttl)
447 451 self.priority = priority
448 452 self.weight = weight
449 453 self.port = port
450 454 self.server = server
451 455
452 456 def write(self, out):
453 457 """Used in constructing an outgoing packet"""
454 458 out.writeShort(self.priority)
455 459 out.writeShort(self.weight)
456 460 out.writeShort(self.port)
457 461 out.writeName(self.server)
458 462
459 463 def __eq__(self, other):
460 464 """Tests equality on priority, weight, port and server"""
461 465 if isinstance(other, DNSService):
462 466 return (self.priority == other.priority and
463 467 self.weight == other.weight and
464 468 self.port == other.port and
465 469 self.server == other.server)
466 470 return 0
467 471
468 472 def __repr__(self):
469 473 """String representation"""
470 474 return self.toString("%s:%s" % (self.server, self.port))
471 475
472 476 class DNSIncoming(object):
473 477 """Object representation of an incoming DNS packet"""
474 478
475 479 def __init__(self, data):
476 480 """Constructor from string holding bytes of packet"""
477 481 self.offset = 0
478 482 self.data = data
479 483 self.questions = []
480 484 self.answers = []
481 485 self.numquestions = 0
482 486 self.numanswers = 0
483 487 self.numauthorities = 0
484 488 self.numadditionals = 0
485 489
486 490 self.readHeader()
487 491 self.readQuestions()
488 492 self.readOthers()
489 493
490 494 def readHeader(self):
491 495 """Reads header portion of packet"""
492 496 format = '!HHHHHH'
493 497 length = struct.calcsize(format)
494 498 info = struct.unpack(format,
495 499 self.data[self.offset:self.offset + length])
496 500 self.offset += length
497 501
498 502 self.id = info[0]
499 503 self.flags = info[1]
500 504 self.numquestions = info[2]
501 505 self.numanswers = info[3]
502 506 self.numauthorities = info[4]
503 507 self.numadditionals = info[5]
504 508
505 509 def readQuestions(self):
506 510 """Reads questions section of packet"""
507 511 format = '!HH'
508 512 length = struct.calcsize(format)
509 513 for i in range(0, self.numquestions):
510 514 name = self.readName()
511 515 info = struct.unpack(format,
512 516 self.data[self.offset:self.offset + length])
513 517 self.offset += length
514 518
515 519 try:
516 520 question = DNSQuestion(name, info[0], info[1])
517 521 self.questions.append(question)
518 522 except NonLocalNameException:
519 523 pass
520 524
521 525 def readInt(self):
522 526 """Reads an integer from the packet"""
523 527 format = '!I'
524 528 length = struct.calcsize(format)
525 529 info = struct.unpack(format,
526 530 self.data[self.offset:self.offset + length])
527 531 self.offset += length
528 532 return info[0]
529 533
530 534 def readCharacterString(self):
531 535 """Reads a character string from the packet"""
532 536 length = ord(self.data[self.offset])
533 537 self.offset += 1
534 538 return self.readString(length)
535 539
536 540 def readString(self, len):
537 541 """Reads a string of a given length from the packet"""
538 542 format = '!%ds' % len
539 543 length = struct.calcsize(format)
540 544 info = struct.unpack(format,
541 545 self.data[self.offset:self.offset + length])
542 546 self.offset += length
543 547 return info[0]
544 548
545 549 def readUnsignedShort(self):
546 550 """Reads an unsigned short from the packet"""
547 551 format = '!H'
548 552 length = struct.calcsize(format)
549 553 info = struct.unpack(format,
550 554 self.data[self.offset:self.offset + length])
551 555 self.offset += length
552 556 return info[0]
553 557
554 558 def readOthers(self):
555 559 """Reads answers, authorities and additionals section of the packet"""
556 560 format = '!HHiH'
557 561 length = struct.calcsize(format)
558 562 n = self.numanswers + self.numauthorities + self.numadditionals
559 563 for i in range(0, n):
560 564 domain = self.readName()
561 565 info = struct.unpack(format,
562 566 self.data[self.offset:self.offset + length])
563 567 self.offset += length
564 568
565 569 rec = None
566 570 if info[0] == _TYPE_A:
567 571 rec = DNSAddress(domain, info[0], info[1], info[2],
568 572 self.readString(4))
569 573 elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR:
570 574 rec = DNSPointer(domain, info[0], info[1], info[2],
571 575 self.readName())
572 576 elif info[0] == _TYPE_TXT:
573 577 rec = DNSText(domain, info[0], info[1], info[2],
574 578 self.readString(info[3]))
575 579 elif info[0] == _TYPE_SRV:
576 580 rec = DNSService(domain, info[0], info[1], info[2],
577 581 self.readUnsignedShort(),
578 582 self.readUnsignedShort(),
579 583 self.readUnsignedShort(),
580 584 self.readName())
581 585 elif info[0] == _TYPE_HINFO:
582 586 rec = DNSHinfo(domain, info[0], info[1], info[2],
583 587 self.readCharacterString(),
584 588 self.readCharacterString())
585 589 elif info[0] == _TYPE_AAAA:
586 590 rec = DNSAddress(domain, info[0], info[1], info[2],
587 591 self.readString(16))
588 592 else:
589 593 # Try to ignore types we don't know about
590 594 # this may mean the rest of the name is
591 595 # unable to be parsed, and may show errors
592 596 # so this is left for debugging. New types
593 597 # encountered need to be parsed properly.
594 598 #
595 599 #print "UNKNOWN TYPE = " + str(info[0])
596 600 #raise BadTypeInNameException
597 601 self.offset += info[3]
598 602
599 603 if rec is not None:
600 604 self.answers.append(rec)
601 605
602 606 def isQuery(self):
603 607 """Returns true if this is a query"""
604 608 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY
605 609
606 610 def isResponse(self):
607 611 """Returns true if this is a response"""
608 612 return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE
609 613
610 614 def readUTF(self, offset, len):
611 615 """Reads a UTF-8 string of a given length from the packet"""
612 616 return self.data[offset:offset + len].decode('utf-8')
613 617
614 618 def readName(self):
615 619 """Reads a domain name from the packet"""
616 620 result = r''
617 621 off = self.offset
618 622 next = -1
619 623 first = off
620 624
621 625 while True:
622 626 len = ord(self.data[off:off + 1])
623 627 off += 1
624 628 if len == 0:
625 629 break
626 630 t = len & 0xC0
627 631 if t == 0x00:
628 632 result = r''.join((result, self.readUTF(off, len) + r'.'))
629 633 off += len
630 634 elif t == 0xC0:
631 635 if next < 0:
632 636 next = off + 1
633 637 off = ((len & 0x3F) << 8) | ord(self.data[off:off + 1])
634 638 if off >= first:
635 639 raise BadDomainNameCircular(off)
636 640 first = off
637 641 else:
638 642 raise BadDomainName(off)
639 643
640 644 if next >= 0:
641 645 self.offset = next
642 646 else:
643 647 self.offset = off
644 648
645 649 return result
646 650
647 651
648 652 class DNSOutgoing(object):
649 653 """Object representation of an outgoing packet"""
650 654
651 655 def __init__(self, flags, multicast=1):
652 656 self.finished = 0
653 657 self.id = 0
654 658 self.multicast = multicast
655 659 self.flags = flags
656 660 self.names = {}
657 661 self.data = []
658 662 self.size = 12
659 663
660 664 self.questions = []
661 665 self.answers = []
662 666 self.authorities = []
663 667 self.additionals = []
664 668
665 669 def addQuestion(self, record):
666 670 """Adds a question"""
667 671 self.questions.append(record)
668 672
669 673 def addAnswer(self, inp, record):
670 674 """Adds an answer"""
671 675 if not record.suppressedBy(inp):
672 676 self.addAnswerAtTime(record, 0)
673 677
674 678 def addAnswerAtTime(self, record, now):
675 679 """Adds an answer if if does not expire by a certain time"""
676 680 if record is not None:
677 681 if now == 0 or not record.isExpired(now):
678 682 self.answers.append((record, now))
679 683
680 684 def addAuthoritativeAnswer(self, record):
681 685 """Adds an authoritative answer"""
682 686 self.authorities.append(record)
683 687
684 688 def addAdditionalAnswer(self, record):
685 689 """Adds an additional answer"""
686 690 self.additionals.append(record)
687 691
688 692 def writeByte(self, value):
689 693 """Writes a single byte to the packet"""
690 694 format = '!c'
691 695 self.data.append(struct.pack(format, chr(value)))
692 696 self.size += 1
693 697
694 698 def insertShort(self, index, value):
695 699 """Inserts an unsigned short in a certain position in the packet"""
696 700 format = '!H'
697 701 self.data.insert(index, struct.pack(format, value))
698 702 self.size += 2
699 703
700 704 def writeShort(self, value):
701 705 """Writes an unsigned short to the packet"""
702 706 format = '!H'
703 707 self.data.append(struct.pack(format, value))
704 708 self.size += 2
705 709
706 710 def writeInt(self, value):
707 711 """Writes an unsigned integer to the packet"""
708 712 format = '!I'
709 713 self.data.append(struct.pack(format, int(value)))
710 714 self.size += 4
711 715
712 716 def writeString(self, value, length):
713 717 """Writes a string to the packet"""
714 718 format = '!' + str(length) + 's'
715 719 self.data.append(struct.pack(format, value))
716 720 self.size += length
717 721
718 722 def writeUTF(self, s):
719 723 """Writes a UTF-8 string of a given length to the packet"""
720 724 utfstr = s.encode('utf-8')
721 725 length = len(utfstr)
722 726 if length > 64:
723 727 raise NamePartTooLongException
724 728 self.writeByte(length)
725 729 self.writeString(utfstr, length)
726 730
727 731 def writeName(self, name):
728 732 """Writes a domain name to the packet"""
729 733
730 734 try:
731 735 # Find existing instance of this name in packet
732 736 #
733 737 index = self.names[name]
734 738 except KeyError:
735 739 # No record of this name already, so write it
736 740 # out as normal, recording the location of the name
737 741 # for future pointers to it.
738 742 #
739 743 self.names[name] = self.size
740 744 parts = name.split('.')
741 745 if parts[-1] == '':
742 746 parts = parts[:-1]
743 747 for part in parts:
744 748 self.writeUTF(part)
745 749 self.writeByte(0)
746 750 return
747 751
748 752 # An index was found, so write a pointer to it
749 753 #
750 754 self.writeByte((index >> 8) | 0xC0)
751 755 self.writeByte(index)
752 756
753 757 def writeQuestion(self, question):
754 758 """Writes a question to the packet"""
755 759 self.writeName(question.name)
756 760 self.writeShort(question.type)
757 761 self.writeShort(question.clazz)
758 762
759 763 def writeRecord(self, record, now):
760 764 """Writes a record (answer, authoritative answer, additional) to
761 765 the packet"""
762 766 self.writeName(record.name)
763 767 self.writeShort(record.type)
764 768 if record.unique and self.multicast:
765 769 self.writeShort(record.clazz | _CLASS_UNIQUE)
766 770 else:
767 771 self.writeShort(record.clazz)
768 772 if now == 0:
769 773 self.writeInt(record.ttl)
770 774 else:
771 775 self.writeInt(record.getRemainingTTL(now))
772 776 index = len(self.data)
773 777 # Adjust size for the short we will write before this record
774 778 #
775 779 self.size += 2
776 780 record.write(self)
777 781 self.size -= 2
778 782
779 783 length = len(''.join(self.data[index:]))
780 784 self.insertShort(index, length) # Here is the short we adjusted for
781 785
782 786 def packet(self):
783 787 """Returns a string containing the packet's bytes
784 788
785 789 No further parts should be added to the packet once this
786 790 is done."""
787 791 if not self.finished:
788 792 self.finished = 1
789 793 for question in self.questions:
790 794 self.writeQuestion(question)
791 795 for answer, time_ in self.answers:
792 796 self.writeRecord(answer, time_)
793 797 for authority in self.authorities:
794 798 self.writeRecord(authority, 0)
795 799 for additional in self.additionals:
796 800 self.writeRecord(additional, 0)
797 801
798 802 self.insertShort(0, len(self.additionals))
799 803 self.insertShort(0, len(self.authorities))
800 804 self.insertShort(0, len(self.answers))
801 805 self.insertShort(0, len(self.questions))
802 806 self.insertShort(0, self.flags)
803 807 if self.multicast:
804 808 self.insertShort(0, 0)
805 809 else:
806 810 self.insertShort(0, self.id)
807 811 return ''.join(self.data)
808 812
809 813
810 814 class DNSCache(object):
811 815 """A cache of DNS entries"""
812 816
813 817 def __init__(self):
814 818 self.cache = {}
815 819
816 820 def add(self, entry):
817 821 """Adds an entry"""
818 822 try:
819 823 list = self.cache[entry.key]
820 824 except KeyError:
821 825 list = self.cache[entry.key] = []
822 826 list.append(entry)
823 827
824 828 def remove(self, entry):
825 829 """Removes an entry"""
826 830 try:
827 831 list = self.cache[entry.key]
828 832 list.remove(entry)
829 833 except KeyError:
830 834 pass
831 835
832 836 def get(self, entry):
833 837 """Gets an entry by key. Will return None if there is no
834 838 matching entry."""
835 839 try:
836 840 list = self.cache[entry.key]
837 841 return list[list.index(entry)]
838 842 except (KeyError, ValueError):
839 843 return None
840 844
841 845 def getByDetails(self, name, type, clazz):
842 846 """Gets an entry by details. Will return None if there is
843 847 no matching entry."""
844 848 entry = DNSEntry(name, type, clazz)
845 849 return self.get(entry)
846 850
847 851 def entriesWithName(self, name):
848 852 """Returns a list of entries whose key matches the name."""
849 853 try:
850 854 return self.cache[name]
851 855 except KeyError:
852 856 return []
853 857
854 858 def entries(self):
855 859 """Returns a list of all entries"""
856 860 try:
857 861 return list(itertools.chain.from_iterable(self.cache.values()))
858 862 except Exception:
859 863 return []
860 864
861 865
862 866 class Engine(threading.Thread):
863 867 """An engine wraps read access to sockets, allowing objects that
864 868 need to receive data from sockets to be called back when the
865 869 sockets are ready.
866 870
867 871 A reader needs a handle_read() method, which is called when the socket
868 872 it is interested in is ready for reading.
869 873
870 874 Writers are not implemented here, because we only send short
871 875 packets.
872 876 """
873 877
874 878 def __init__(self, zeroconf):
875 879 threading.Thread.__init__(self)
876 880 self.zeroconf = zeroconf
877 881 self.readers = {} # maps socket to reader
878 882 self.timeout = 5
879 883 self.condition = threading.Condition()
880 884 self.start()
881 885
882 886 def run(self):
883 887 while not globals()['_GLOBAL_DONE']:
884 888 rs = self.getReaders()
885 889 if len(rs) == 0:
886 890 # No sockets to manage, but we wait for the timeout
887 891 # or addition of a socket
888 892 #
889 893 self.condition.acquire()
890 894 self.condition.wait(self.timeout)
891 895 self.condition.release()
892 896 else:
893 897 try:
894 898 rr, wr, er = select.select(rs, [], [], self.timeout)
895 899 for sock in rr:
896 900 try:
897 901 self.readers[sock].handle_read()
898 902 except Exception:
899 903 if not globals()['_GLOBAL_DONE']:
900 904 traceback.print_exc()
901 905 except Exception:
902 906 pass
903 907
904 908 def getReaders(self):
905 909 self.condition.acquire()
906 910 result = self.readers.keys()
907 911 self.condition.release()
908 912 return result
909 913
910 914 def addReader(self, reader, socket):
911 915 self.condition.acquire()
912 916 self.readers[socket] = reader
913 917 self.condition.notify()
914 918 self.condition.release()
915 919
916 920 def delReader(self, socket):
917 921 self.condition.acquire()
918 922 del self.readers[socket]
919 923 self.condition.notify()
920 924 self.condition.release()
921 925
922 926 def notify(self):
923 927 self.condition.acquire()
924 928 self.condition.notify()
925 929 self.condition.release()
926 930
927 931 class Listener(object):
928 932 """A Listener is used by this module to listen on the multicast
929 933 group to which DNS messages are sent, allowing the implementation
930 934 to cache information as it arrives.
931 935
932 936 It requires registration with an Engine object in order to have
933 937 the read() method called when a socket is available for reading."""
934 938
935 939 def __init__(self, zeroconf):
936 940 self.zeroconf = zeroconf
937 941 self.zeroconf.engine.addReader(self, self.zeroconf.socket)
938 942
939 943 def handle_read(self):
940 944 sock = self.zeroconf.socket
941 945 try:
942 946 data, (addr, port) = sock.recvfrom(_MAX_MSG_ABSOLUTE)
943 947 except socket.error as e:
944 948 if e.errno == errno.EBADF:
945 949 # some other thread may close the socket
946 950 return
947 951 else:
948 952 raise
949 953 self.data = data
950 954 msg = DNSIncoming(data)
951 955 if msg.isQuery():
952 956 # Always multicast responses
953 957 #
954 958 if port == _MDNS_PORT:
955 959 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
956 960 # If it's not a multicast query, reply via unicast
957 961 # and multicast
958 962 #
959 963 elif port == _DNS_PORT:
960 964 self.zeroconf.handleQuery(msg, addr, port)
961 965 self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT)
962 966 else:
963 967 self.zeroconf.handleResponse(msg)
964 968
965 969
966 970 class Reaper(threading.Thread):
967 971 """A Reaper is used by this module to remove cache entries that
968 972 have expired."""
969 973
970 974 def __init__(self, zeroconf):
971 975 threading.Thread.__init__(self)
972 976 self.zeroconf = zeroconf
973 977 self.start()
974 978
975 979 def run(self):
976 980 while True:
977 981 self.zeroconf.wait(10 * 1000)
978 982 if globals()['_GLOBAL_DONE']:
979 983 return
980 984 now = currentTimeMillis()
981 985 for record in self.zeroconf.cache.entries():
982 986 if record.isExpired(now):
983 987 self.zeroconf.updateRecord(now, record)
984 988 self.zeroconf.cache.remove(record)
985 989
986 990
987 991 class ServiceBrowser(threading.Thread):
988 992 """Used to browse for a service of a specific type.
989 993
990 994 The listener object will have its addService() and
991 995 removeService() methods called when this browser
992 996 discovers changes in the services availability."""
993 997
994 998 def __init__(self, zeroconf, type, listener):
995 999 """Creates a browser for a specific type"""
996 1000 threading.Thread.__init__(self)
997 1001 self.zeroconf = zeroconf
998 1002 self.type = type
999 1003 self.listener = listener
1000 1004 self.services = {}
1001 1005 self.nexttime = currentTimeMillis()
1002 1006 self.delay = _BROWSER_TIME
1003 1007 self.list = []
1004 1008
1005 1009 self.done = 0
1006 1010
1007 1011 self.zeroconf.addListener(self, DNSQuestion(self.type, _TYPE_PTR,
1008 1012 _CLASS_IN))
1009 1013 self.start()
1010 1014
1011 1015 def updateRecord(self, zeroconf, now, record):
1012 1016 """Callback invoked by Zeroconf when new information arrives.
1013 1017
1014 1018 Updates information required by browser in the Zeroconf cache."""
1015 1019 if record.type == _TYPE_PTR and record.name == self.type:
1016 1020 expired = record.isExpired(now)
1017 1021 try:
1018 1022 oldrecord = self.services[record.alias.lower()]
1019 1023 if not expired:
1020 1024 oldrecord.resetTTL(record)
1021 1025 else:
1022 1026 del self.services[record.alias.lower()]
1023 1027 callback = (lambda x:
1024 1028 self.listener.removeService(x, self.type, record.alias))
1025 1029 self.list.append(callback)
1026 1030 return
1027 1031 except Exception:
1028 1032 if not expired:
1029 1033 self.services[record.alias.lower()] = record
1030 1034 callback = (lambda x:
1031 1035 self.listener.addService(x, self.type, record.alias))
1032 1036 self.list.append(callback)
1033 1037
1034 1038 expires = record.getExpirationTime(75)
1035 1039 if expires < self.nexttime:
1036 1040 self.nexttime = expires
1037 1041
1038 1042 def cancel(self):
1039 1043 self.done = 1
1040 1044 self.zeroconf.notifyAll()
1041 1045
1042 1046 def run(self):
1043 1047 while True:
1044 1048 event = None
1045 1049 now = currentTimeMillis()
1046 1050 if len(self.list) == 0 and self.nexttime > now:
1047 1051 self.zeroconf.wait(self.nexttime - now)
1048 1052 if globals()['_GLOBAL_DONE'] or self.done:
1049 1053 return
1050 1054 now = currentTimeMillis()
1051 1055
1052 1056 if self.nexttime <= now:
1053 1057 out = DNSOutgoing(_FLAGS_QR_QUERY)
1054 1058 out.addQuestion(DNSQuestion(self.type, _TYPE_PTR, _CLASS_IN))
1055 1059 for record in self.services.values():
1056 1060 if not record.isExpired(now):
1057 1061 out.addAnswerAtTime(record, now)
1058 1062 self.zeroconf.send(out)
1059 1063 self.nexttime = now + self.delay
1060 1064 self.delay = min(20 * 1000, self.delay * 2)
1061 1065
1062 1066 if len(self.list) > 0:
1063 1067 event = self.list.pop(0)
1064 1068
1065 1069 if event is not None:
1066 1070 event(self.zeroconf)
1067 1071
1068 1072
1069 1073 class ServiceInfo(object):
1070 1074 """Service information"""
1071 1075
1072 1076 def __init__(self, type, name, address=None, port=None, weight=0,
1073 1077 priority=0, properties=None, server=None):
1074 1078 """Create a service description.
1075 1079
1076 1080 type: fully qualified service type name
1077 1081 name: fully qualified service name
1078 1082 address: IP address as unsigned short, network byte order
1079 1083 port: port that the service runs on
1080 1084 weight: weight of the service
1081 1085 priority: priority of the service
1082 1086 properties: dictionary of properties (or a string holding the bytes for
1083 1087 the text field)
1084 1088 server: fully qualified name for service host (defaults to name)"""
1085 1089
1086 1090 if not name.endswith(type):
1087 1091 raise BadTypeInNameException
1088 1092 self.type = type
1089 1093 self.name = name
1090 1094 self.address = address
1091 1095 self.port = port
1092 1096 self.weight = weight
1093 1097 self.priority = priority
1094 1098 if server:
1095 1099 self.server = server
1096 1100 else:
1097 1101 self.server = name
1098 1102 self.setProperties(properties)
1099 1103
1100 1104 def setProperties(self, properties):
1101 1105 """Sets properties and text of this info from a dictionary"""
1102 1106 if isinstance(properties, dict):
1103 1107 self.properties = properties
1104 1108 list = []
1105 1109 result = ''
1106 1110 for key in properties:
1107 1111 value = properties[key]
1108 1112 if value is None:
1109 1113 suffix = ''
1110 1114 elif isinstance(value, str):
1111 1115 suffix = value
1112 1116 elif isinstance(value, int):
1113 1117 if value:
1114 1118 suffix = 'true'
1115 1119 else:
1116 1120 suffix = 'false'
1117 1121 else:
1118 1122 suffix = ''
1119 1123 list.append('='.join((key, suffix)))
1120 1124 for item in list:
1121 1125 result = ''.join((result, struct.pack('!c', chr(len(item))),
1122 1126 item))
1123 1127 self.text = result
1124 1128 else:
1125 1129 self.text = properties
1126 1130
1127 1131 def setText(self, text):
1128 1132 """Sets properties and text given a text field"""
1129 1133 self.text = text
1130 1134 try:
1131 1135 result = {}
1132 1136 end = len(text)
1133 1137 index = 0
1134 1138 strs = []
1135 1139 while index < end:
1136 1140 length = ord(text[index])
1137 1141 index += 1
1138 1142 strs.append(text[index:index + length])
1139 1143 index += length
1140 1144
1141 1145 for s in strs:
1142 1146 eindex = s.find('=')
1143 1147 if eindex == -1:
1144 1148 # No equals sign at all
1145 1149 key = s
1146 1150 value = 0
1147 1151 else:
1148 1152 key = s[:eindex]
1149 1153 value = s[eindex + 1:]
1150 1154 if value == 'true':
1151 1155 value = 1
1152 1156 elif value == 'false' or not value:
1153 1157 value = 0
1154 1158
1155 1159 # Only update non-existent properties
1156 1160 if key and result.get(key) is None:
1157 1161 result[key] = value
1158 1162
1159 1163 self.properties = result
1160 1164 except Exception:
1161 1165 traceback.print_exc()
1162 1166 self.properties = None
1163 1167
1164 1168 def getType(self):
1165 1169 """Type accessor"""
1166 1170 return self.type
1167 1171
1168 1172 def getName(self):
1169 1173 """Name accessor"""
1170 1174 if self.type is not None and self.name.endswith("." + self.type):
1171 1175 return self.name[:len(self.name) - len(self.type) - 1]
1172 1176 return self.name
1173 1177
1174 1178 def getAddress(self):
1175 1179 """Address accessor"""
1176 1180 return self.address
1177 1181
1178 1182 def getPort(self):
1179 1183 """Port accessor"""
1180 1184 return self.port
1181 1185
1182 1186 def getPriority(self):
1183 1187 """Priority accessor"""
1184 1188 return self.priority
1185 1189
1186 1190 def getWeight(self):
1187 1191 """Weight accessor"""
1188 1192 return self.weight
1189 1193
1190 1194 def getProperties(self):
1191 1195 """Properties accessor"""
1192 1196 return self.properties
1193 1197
1194 1198 def getText(self):
1195 1199 """Text accessor"""
1196 1200 return self.text
1197 1201
1198 1202 def getServer(self):
1199 1203 """Server accessor"""
1200 1204 return self.server
1201 1205
1202 1206 def updateRecord(self, zeroconf, now, record):
1203 1207 """Updates service information from a DNS record"""
1204 1208 if record is not None and not record.isExpired(now):
1205 1209 if record.type == _TYPE_A:
1206 1210 #if record.name == self.name:
1207 1211 if record.name == self.server:
1208 1212 self.address = record.address
1209 1213 elif record.type == _TYPE_SRV:
1210 1214 if record.name == self.name:
1211 1215 self.server = record.server
1212 1216 self.port = record.port
1213 1217 self.weight = record.weight
1214 1218 self.priority = record.priority
1215 1219 #self.address = None
1216 1220 self.updateRecord(zeroconf, now,
1217 1221 zeroconf.cache.getByDetails(self.server,
1218 1222 _TYPE_A, _CLASS_IN))
1219 1223 elif record.type == _TYPE_TXT:
1220 1224 if record.name == self.name:
1221 1225 self.setText(record.text)
1222 1226
1223 1227 def request(self, zeroconf, timeout):
1224 1228 """Returns true if the service could be discovered on the
1225 1229 network, and updates this object with details discovered.
1226 1230 """
1227 1231 now = currentTimeMillis()
1228 1232 delay = _LISTENER_TIME
1229 1233 next = now + delay
1230 1234 last = now + timeout
1231 1235 try:
1232 1236 zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY,
1233 1237 _CLASS_IN))
1234 1238 while (self.server is None or self.address is None or
1235 1239 self.text is None):
1236 1240 if last <= now:
1237 1241 return 0
1238 1242 if next <= now:
1239 1243 out = DNSOutgoing(_FLAGS_QR_QUERY)
1240 1244 out.addQuestion(DNSQuestion(self.name, _TYPE_SRV,
1241 1245 _CLASS_IN))
1242 1246 out.addAnswerAtTime(
1243 1247 zeroconf.cache.getByDetails(self.name,
1244 1248 _TYPE_SRV,
1245 1249 _CLASS_IN),
1246 1250 now)
1247 1251 out.addQuestion(DNSQuestion(self.name, _TYPE_TXT,
1248 1252 _CLASS_IN))
1249 1253 out.addAnswerAtTime(
1250 1254 zeroconf.cache.getByDetails(self.name, _TYPE_TXT,
1251 1255 _CLASS_IN),
1252 1256 now)
1253 1257 if self.server is not None:
1254 1258 out.addQuestion(
1255 1259 DNSQuestion(self.server, _TYPE_A, _CLASS_IN))
1256 1260 out.addAnswerAtTime(
1257 1261 zeroconf.cache.getByDetails(self.server, _TYPE_A,
1258 1262 _CLASS_IN),
1259 1263 now)
1260 1264 zeroconf.send(out)
1261 1265 next = now + delay
1262 1266 delay = delay * 2
1263 1267
1264 1268 zeroconf.wait(min(next, last) - now)
1265 1269 now = currentTimeMillis()
1266 1270 result = 1
1267 1271 finally:
1268 1272 zeroconf.removeListener(self)
1269 1273
1270 1274 return result
1271 1275
1272 1276 def __eq__(self, other):
1273 1277 """Tests equality of service name"""
1274 1278 if isinstance(other, ServiceInfo):
1275 1279 return other.name == self.name
1276 1280 return 0
1277 1281
1278 1282 def __ne__(self, other):
1279 1283 """Non-equality test"""
1280 1284 return not self.__eq__(other)
1281 1285
1282 1286 def __repr__(self):
1283 1287 """String representation"""
1284 1288 result = ("service[%s,%s:%s," %
1285 1289 (self.name, socket.inet_ntoa(self.getAddress()), self.port))
1286 1290 if self.text is None:
1287 1291 result += "None"
1288 1292 else:
1289 1293 if len(self.text) < 20:
1290 1294 result += self.text
1291 1295 else:
1292 1296 result += self.text[:17] + "..."
1293 1297 result += "]"
1294 1298 return result
1295 1299
1296 1300
1297 1301 class Zeroconf(object):
1298 1302 """Implementation of Zeroconf Multicast DNS Service Discovery
1299 1303
1300 1304 Supports registration, unregistration, queries and browsing.
1301 1305 """
1302 1306 def __init__(self, bindaddress=None):
1303 1307 """Creates an instance of the Zeroconf class, establishing
1304 1308 multicast communications, listening and reaping threads."""
1305 1309 globals()['_GLOBAL_DONE'] = 0
1306 1310 if bindaddress is None:
1307 1311 self.intf = socket.gethostbyname(socket.gethostname())
1308 1312 else:
1309 1313 self.intf = bindaddress
1310 1314 self.group = ('', _MDNS_PORT)
1311 1315 self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1312 1316 try:
1313 1317 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1314 1318 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
1315 1319 except Exception:
1316 1320 # SO_REUSEADDR should be equivalent to SO_REUSEPORT for
1317 1321 # multicast UDP sockets (p 731, "TCP/IP Illustrated,
1318 1322 # Volume 2"), but some BSD-derived systems require
1319 1323 # SO_REUSEPORT to be specified explicitly. Also, not all
1320 1324 # versions of Python have SO_REUSEPORT available. So
1321 1325 # if you're on a BSD-based system, and haven't upgraded
1322 1326 # to Python 2.3 yet, you may find this library doesn't
1323 1327 # work as expected.
1324 1328 #
1325 1329 pass
1326 1330 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, "\xff")
1327 1331 self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, "\x01")
1328 1332 try:
1329 1333 self.socket.bind(self.group)
1330 1334 except Exception:
1331 1335 # Some versions of linux raise an exception even though
1332 1336 # SO_REUSEADDR and SO_REUSEPORT have been set, so ignore it
1333 1337 pass
1334 1338 self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP,
1335 1339 socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(r'0.0.0.0'))
1336 1340
1337 1341 self.listeners = []
1338 1342 self.browsers = []
1339 1343 self.services = {}
1340 1344 self.servicetypes = {}
1341 1345
1342 1346 self.cache = DNSCache()
1343 1347
1344 1348 self.condition = threading.Condition()
1345 1349
1346 1350 self.engine = Engine(self)
1347 1351 self.listener = Listener(self)
1348 1352 self.reaper = Reaper(self)
1349 1353
1350 1354 def isLoopback(self):
1351 1355 return self.intf.startswith("127.0.0.1")
1352 1356
1353 1357 def isLinklocal(self):
1354 1358 return self.intf.startswith("169.254.")
1355 1359
1356 1360 def wait(self, timeout):
1357 1361 """Calling thread waits for a given number of milliseconds or
1358 1362 until notified."""
1359 1363 self.condition.acquire()
1360 1364 self.condition.wait(timeout / 1000)
1361 1365 self.condition.release()
1362 1366
1363 1367 def notifyAll(self):
1364 1368 """Notifies all waiting threads"""
1365 1369 self.condition.acquire()
1366 1370 self.condition.notifyAll()
1367 1371 self.condition.release()
1368 1372
1369 1373 def getServiceInfo(self, type, name, timeout=3000):
1370 1374 """Returns network's service information for a particular
1371 1375 name and type, or None if no service matches by the timeout,
1372 1376 which defaults to 3 seconds."""
1373 1377 info = ServiceInfo(type, name)
1374 1378 if info.request(self, timeout):
1375 1379 return info
1376 1380 return None
1377 1381
1378 1382 def addServiceListener(self, type, listener):
1379 1383 """Adds a listener for a particular service type. This object
1380 1384 will then have its updateRecord method called when information
1381 1385 arrives for that type."""
1382 1386 self.removeServiceListener(listener)
1383 1387 self.browsers.append(ServiceBrowser(self, type, listener))
1384 1388
1385 1389 def removeServiceListener(self, listener):
1386 1390 """Removes a listener from the set that is currently listening."""
1387 1391 for browser in self.browsers:
1388 1392 if browser.listener == listener:
1389 1393 browser.cancel()
1390 1394 del browser
1391 1395
1392 1396 def registerService(self, info, ttl=_DNS_TTL):
1393 1397 """Registers service information to the network with a default TTL
1394 1398 of 60 seconds. Zeroconf will then respond to requests for
1395 1399 information for that service. The name of the service may be
1396 1400 changed if needed to make it unique on the network."""
1397 1401 self.checkService(info)
1398 1402 self.services[info.name.lower()] = info
1399 1403 if info.type in self.servicetypes:
1400 1404 self.servicetypes[info.type] += 1
1401 1405 else:
1402 1406 self.servicetypes[info.type] = 1
1403 1407 now = currentTimeMillis()
1404 1408 nexttime = now
1405 1409 i = 0
1406 1410 while i < 3:
1407 1411 if now < nexttime:
1408 1412 self.wait(nexttime - now)
1409 1413 now = currentTimeMillis()
1410 1414 continue
1411 1415 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1412 1416 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR,
1413 1417 _CLASS_IN, ttl, info.name), 0)
1414 1418 out.addAnswerAtTime(
1415 1419 DNSService(
1416 1420 info.name, _TYPE_SRV,
1417 1421 _CLASS_IN, ttl, info.priority, info.weight, info.port,
1418 1422 info.server),
1419 1423 0)
1420 1424 out.addAnswerAtTime(
1421 1425 DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text),
1422 1426 0)
1423 1427 if info.address:
1424 1428 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A,
1425 1429 _CLASS_IN, ttl, info.address), 0)
1426 1430 self.send(out)
1427 1431 i += 1
1428 1432 nexttime += _REGISTER_TIME
1429 1433
1430 1434 def unregisterService(self, info):
1431 1435 """Unregister a service."""
1432 1436 try:
1433 1437 del self.services[info.name.lower()]
1434 1438 if self.servicetypes[info.type] > 1:
1435 1439 self.servicetypes[info.type] -= 1
1436 1440 else:
1437 1441 del self.servicetypes[info.type]
1438 1442 except KeyError:
1439 1443 pass
1440 1444 now = currentTimeMillis()
1441 1445 nexttime = now
1442 1446 i = 0
1443 1447 while i < 3:
1444 1448 if now < nexttime:
1445 1449 self.wait(nexttime - now)
1446 1450 now = currentTimeMillis()
1447 1451 continue
1448 1452 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1449 1453 out.addAnswerAtTime(
1450 1454 DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0)
1451 1455 out.addAnswerAtTime(
1452 1456 DNSService(info.name, _TYPE_SRV,
1453 1457 _CLASS_IN, 0, info.priority, info.weight, info.port,
1454 1458 info.name),
1455 1459 0)
1456 1460 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT,
1457 1461 _CLASS_IN, 0, info.text), 0)
1458 1462 if info.address:
1459 1463 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A,
1460 1464 _CLASS_IN, 0, info.address), 0)
1461 1465 self.send(out)
1462 1466 i += 1
1463 1467 nexttime += _UNREGISTER_TIME
1464 1468
1465 1469 def unregisterAllServices(self):
1466 1470 """Unregister all registered services."""
1467 1471 if len(self.services) > 0:
1468 1472 now = currentTimeMillis()
1469 1473 nexttime = now
1470 1474 i = 0
1471 1475 while i < 3:
1472 1476 if now < nexttime:
1473 1477 self.wait(nexttime - now)
1474 1478 now = currentTimeMillis()
1475 1479 continue
1476 1480 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1477 1481 for info in self.services.values():
1478 1482 out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR,
1479 1483 _CLASS_IN, 0, info.name), 0)
1480 1484 out.addAnswerAtTime(
1481 1485 DNSService(info.name, _TYPE_SRV,
1482 1486 _CLASS_IN, 0, info.priority, info.weight,
1483 1487 info.port, info.server),
1484 1488 0)
1485 1489 out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT,
1486 1490 _CLASS_IN, 0, info.text), 0)
1487 1491 if info.address:
1488 1492 out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A,
1489 1493 _CLASS_IN, 0, info.address), 0)
1490 1494 self.send(out)
1491 1495 i += 1
1492 1496 nexttime += _UNREGISTER_TIME
1493 1497
1494 1498 def checkService(self, info):
1495 1499 """Checks the network for a unique service name, modifying the
1496 1500 ServiceInfo passed in if it is not unique."""
1497 1501 now = currentTimeMillis()
1498 1502 nexttime = now
1499 1503 i = 0
1500 1504 while i < 3:
1501 1505 for record in self.cache.entriesWithName(info.type):
1502 1506 if (record.type == _TYPE_PTR and not record.isExpired(now) and
1503 1507 record.alias == info.name):
1504 1508 if (info.name.find('.') < 0):
1505 1509 info.name = ("%w.[%s:%d].%s" %
1506 1510 (info.name, info.address, info.port, info.type))
1507 1511 self.checkService(info)
1508 1512 return
1509 1513 raise NonUniqueNameException
1510 1514 if now < nexttime:
1511 1515 self.wait(nexttime - now)
1512 1516 now = currentTimeMillis()
1513 1517 continue
1514 1518 out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA)
1515 1519 self.debug = out
1516 1520 out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN))
1517 1521 out.addAuthoritativeAnswer(DNSPointer(info.type, _TYPE_PTR,
1518 1522 _CLASS_IN, _DNS_TTL, info.name))
1519 1523 self.send(out)
1520 1524 i += 1
1521 1525 nexttime += _CHECK_TIME
1522 1526
1523 1527 def addListener(self, listener, question):
1524 1528 """Adds a listener for a given question. The listener will have
1525 1529 its updateRecord method called when information is available to
1526 1530 answer the question."""
1527 1531 now = currentTimeMillis()
1528 1532 self.listeners.append(listener)
1529 1533 if question is not None:
1530 1534 for record in self.cache.entriesWithName(question.name):
1531 1535 if question.answeredBy(record) and not record.isExpired(now):
1532 1536 listener.updateRecord(self, now, record)
1533 1537 self.notifyAll()
1534 1538
1535 1539 def removeListener(self, listener):
1536 1540 """Removes a listener."""
1537 1541 try:
1538 1542 self.listeners.remove(listener)
1539 1543 self.notifyAll()
1540 1544 except Exception:
1541 1545 pass
1542 1546
1543 1547 def updateRecord(self, now, rec):
1544 1548 """Used to notify listeners of new information that has updated
1545 1549 a record."""
1546 1550 for listener in self.listeners:
1547 1551 listener.updateRecord(self, now, rec)
1548 1552 self.notifyAll()
1549 1553
1550 1554 def handleResponse(self, msg):
1551 1555 """Deal with incoming response packets. All answers
1552 1556 are held in the cache, and listeners are notified."""
1553 1557 now = currentTimeMillis()
1554 1558 for record in msg.answers:
1555 1559 expired = record.isExpired(now)
1556 1560 if record in self.cache.entries():
1557 1561 if expired:
1558 1562 self.cache.remove(record)
1559 1563 else:
1560 1564 entry = self.cache.get(record)
1561 1565 if entry is not None:
1562 1566 entry.resetTTL(record)
1563 1567 record = entry
1564 1568 else:
1565 1569 self.cache.add(record)
1566 1570
1567 1571 self.updateRecord(now, record)
1568 1572
1569 1573 def handleQuery(self, msg, addr, port):
1570 1574 """Deal with incoming query packets. Provides a response if
1571 1575 possible."""
1572 1576 out = None
1573 1577
1574 1578 # Support unicast client responses
1575 1579 #
1576 1580 if port != _MDNS_PORT:
1577 1581 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0)
1578 1582 for question in msg.questions:
1579 1583 out.addQuestion(question)
1580 1584
1581 1585 for question in msg.questions:
1582 1586 if question.type == _TYPE_PTR:
1583 1587 if question.name == "_services._dns-sd._udp.local.":
1584 1588 for stype in self.servicetypes.keys():
1585 1589 if out is None:
1586 1590 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1587 1591 out.addAnswer(msg,
1588 1592 DNSPointer(
1589 1593 "_services._dns-sd._udp.local.",
1590 1594 _TYPE_PTR, _CLASS_IN,
1591 1595 _DNS_TTL, stype))
1592 1596 for service in self.services.values():
1593 1597 if question.name == service.type:
1594 1598 if out is None:
1595 1599 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1596 1600 out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR,
1597 1601 _CLASS_IN, _DNS_TTL, service.name))
1598 1602 else:
1599 1603 try:
1600 1604 if out is None:
1601 1605 out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA)
1602 1606
1603 1607 # Answer A record queries for any service addresses we know
1604 1608 if question.type == _TYPE_A or question.type == _TYPE_ANY:
1605 1609 for service in self.services.values():
1606 1610 if service.server == question.name.lower():
1607 1611 out.addAnswer(msg,
1608 1612 DNSAddress(question.name, _TYPE_A,
1609 1613 _CLASS_IN | _CLASS_UNIQUE,
1610 1614 _DNS_TTL, service.address))
1611 1615
1612 1616 service = self.services.get(question.name.lower(), None)
1613 1617 if not service:
1614 1618 continue
1615 1619
1616 1620 if (question.type == _TYPE_SRV or
1617 1621 question.type == _TYPE_ANY):
1618 1622 out.addAnswer(msg,
1619 1623 DNSService(question.name, _TYPE_SRV,
1620 1624 _CLASS_IN | _CLASS_UNIQUE,
1621 1625 _DNS_TTL, service.priority,
1622 1626 service.weight, service.port,
1623 1627 service.server))
1624 1628 if (question.type == _TYPE_TXT or
1625 1629 question.type == _TYPE_ANY):
1626 1630 out.addAnswer(msg, DNSText(question.name, _TYPE_TXT,
1627 1631 _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text))
1628 1632 if question.type == _TYPE_SRV:
1629 1633 out.addAdditionalAnswer(
1630 1634 DNSAddress(service.server, _TYPE_A,
1631 1635 _CLASS_IN | _CLASS_UNIQUE,
1632 1636 _DNS_TTL, service.address))
1633 1637 except Exception:
1634 1638 traceback.print_exc()
1635 1639
1636 1640 if out is not None and out.answers:
1637 1641 out.id = msg.id
1638 1642 self.send(out, addr, port)
1639 1643
1640 1644 def send(self, out, addr=_MDNS_ADDR, port=_MDNS_PORT):
1641 1645 """Sends an outgoing packet."""
1642 1646 # This is a quick test to see if we can parse the packets we generate
1643 1647 #temp = DNSIncoming(out.packet())
1644 1648 try:
1645 1649 self.socket.sendto(out.packet(), 0, (addr, port))
1646 1650 except Exception:
1647 1651 # Ignore this, it may be a temporary loss of network connection
1648 1652 pass
1649 1653
1650 1654 def close(self):
1651 1655 """Ends the background threads, and prevent this instance from
1652 1656 servicing further queries."""
1653 1657 if globals()['_GLOBAL_DONE'] == 0:
1654 1658 globals()['_GLOBAL_DONE'] = 1
1655 1659 self.notifyAll()
1656 1660 self.engine.notify()
1657 1661 self.unregisterAllServices()
1658 1662 self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP,
1659 1663 socket.inet_aton(_MDNS_ADDR) + socket.inet_aton(r'0.0.0.0'))
1660 1664 self.socket.close()
1661 1665
1662 1666 # Test a few module features, including service registration, service
1663 1667 # query (for Zoe), and service unregistration.
1664 1668
1665 1669 if __name__ == '__main__':
1666 1670 print("Multicast DNS Service Discovery for Python, version", __version__)
1667 1671 r = Zeroconf()
1668 1672 print("1. Testing registration of a service...")
1669 1673 desc = {'version':'0.10','a':'test value', 'b':'another value'}
1670 1674 info = ServiceInfo("_http._tcp.local.",
1671 1675 "My Service Name._http._tcp.local.",
1672 1676 socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc)
1673 1677 print(" Registering service...")
1674 1678 r.registerService(info)
1675 1679 print(" Registration done.")
1676 1680 print("2. Testing query of service information...")
1677 1681 print(" Getting ZOE service:",
1678 1682 str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local.")))
1679 1683 print(" Query done.")
1680 1684 print("3. Testing query of own service...")
1681 1685 print(" Getting self:",
1682 1686 str(r.getServiceInfo("_http._tcp.local.",
1683 1687 "My Service Name._http._tcp.local.")))
1684 1688 print(" Query done.")
1685 1689 print("4. Testing unregister of service information...")
1686 1690 r.unregisterService(info)
1687 1691 print(" Unregister done.")
1688 1692 r.close()
General Comments 0
You need to be logged in to leave comments. Login now