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