printer_browser.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. #!/usr/bin/env python3
  2. """
  3. Browse for printers on the local net, dump DNS records for inclusion
  4. in a DNS zone in master file format.
  5. """
  6. from argparse import ArgumentParser, FileType, ArgumentDefaultsHelpFormatter
  7. from zeroconf import Zeroconf, ServiceBrowser, ServiceInfo
  8. from socket import inet_ntop, AF_INET, AF_INET6
  9. from dns.rdataclass import IN
  10. from dns.rdatatype import PTR, SRV, TXT
  11. from dns.rrset import RRset
  12. class Listener:
  13. """
  14. mDNS browser, receives service advertisements, retrieves
  15. ServiceInfo records and drops them into a Queue for pickup.
  16. """
  17. try:
  18. from queue import Queue, Empty # Python 3
  19. except ImportError:
  20. from Queue import Queue, Empty # Python 2
  21. def __init__(self, args):
  22. self.q = self.Queue()
  23. self.browse_timeout = args.browse_timeout
  24. self.query_timeout = args.query_timeout * 1000
  25. def add_service(self, z, type, name):
  26. printer = z.get_service_info(type, name, timeout = self.query_timeout)
  27. if printer is not None:
  28. self.q.put(printer)
  29. def remove_service(self, z, type, name):
  30. pass
  31. def update_service(self, z, type, name):
  32. pass
  33. def get(self):
  34. "Iterator to retrieve ServiceInfo results"
  35. while True:
  36. try:
  37. yield self.q.get(timeout = self.browse_timeout)
  38. except self.Empty:
  39. return
  40. def dns_name(name, prefix = (), suffix = ()):
  41. "Strip .local, add optional prefix and suffix."
  42. from dns.name import Name, from_text
  43. name = from_text(str(name))
  44. assert name.labels[-2:] == (b"local", b"")
  45. return Name(prefix + name.labels[:-2] + suffix)
  46. def rr(name, rdata):
  47. r = RRset(name, rdata.rdclass, rdata.rdtype)
  48. r.add(rdata)
  49. return r
  50. def txt_rr(printer):
  51. "Regenerate ServiceInfo from property list and generate a TXT RR."
  52. from dns.rdata import from_wire
  53. si = ServiceInfo(printer.type, printer.name, properties = printer.properties)
  54. return rr(dns_name(printer.name), from_wire(IN, TXT, si.text, 0, len(si.text)))
  55. def srv_rr(printer):
  56. "Generate an SRV RR."
  57. from dns.rdtypes.IN.SRV import SRV as SRV_RR
  58. return rr(dns_name(printer.name), SRV_RR(IN, SRV, printer.priority, printer.weight, printer.port, dns_name(printer.server)))
  59. def ptr_rr(printer, *prefix):
  60. "Generate a PTR RR."
  61. from dns.rdtypes.ANY.PTR import PTR as PTR_RR
  62. return rr(dns_name(printer.type, prefix = prefix), PTR_RR(IN, PTR, dns_name(printer.name)))
  63. def main():
  64. ap = ArgumentParser(description = __doc__, formatter_class = ArgumentDefaultsHelpFormatter)
  65. ap.add_argument("-q", "--quiet", action = "store_true",
  66. help = "omit comments from generated master file text")
  67. ap.add_argument("-o", "--output", type = FileType("w"), default = "-",
  68. help = "where to write master file text")
  69. ap.add_argument("--browse-timeout", type = int, default = 5,
  70. help = "timeout in seconds while browsing for printers")
  71. ap.add_argument("--query-timeout", type = int, default = 3,
  72. help = "timeout in seconds to retrieve printer data")
  73. ap.add_argument("--keep-adminurl", action = "store_true",
  74. help = "keep adminurl property in generated DNS data")
  75. ap.add_argument("mdns_type", nargs = "*",
  76. default = ["_ipp._tcp.local.", "_universal._sub._ipp._tcp.local.", "_pdl-datastream._tcp.local."],
  77. help = "mDNS types for which to browse")
  78. args = ap.parse_args()
  79. def write(s = ""):
  80. args.output.write("{!s}\n".format(s))
  81. printers = {}
  82. z = Zeroconf()
  83. listener = Listener(args)
  84. for mdns_type in args.mdns_type:
  85. ServiceBrowser(z, mdns_type, listener)
  86. for printer in listener.get():
  87. try:
  88. printers[printer.name].append(printer)
  89. except KeyError:
  90. printers[printer.name] = [printer]
  91. z.close()
  92. for printer in printers.values():
  93. rrs = [ptr_rr(p) for p in printer]
  94. p = printer[0]
  95. if not args.keep_adminurl:
  96. p.properties.pop(b"adminurl", None)
  97. rrs.append(srv_rr(p))
  98. rrs.append(txt_rr(p))
  99. if not args.quiet:
  100. write(";; Name: {!r}".format(p.name))
  101. for a in p.addresses:
  102. write(";; Addr: {} ({})".format(inet_ntop({4:AF_INET, 16:AF_INET6}[len(a)], a), p.server))
  103. write(";;")
  104. for rr in rrs:
  105. write(rr)
  106. write()
  107. if __name__ == "__main__":
  108. main()