119 lines
3.8 KiB
Python
119 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
|
|
from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange
|
|
import sys
|
|
import time
|
|
from twisted.internet import reactor, defer, threads
|
|
from twisted.names import client, dns, error, server
|
|
from twisted.python import log
|
|
import socket
|
|
|
|
domain = sys.argv[1]
|
|
port = int(sys.argv[2])
|
|
ttl = 0 # TODO 120
|
|
timeout = 2
|
|
|
|
class DynamicResolver(object):
|
|
def __init__(self):
|
|
self.zeroconf = Zeroconf(ip_version=4)
|
|
|
|
def _dynamicResponseRequired(self, query):
|
|
if str(query.name).endswith(domain):
|
|
return True
|
|
|
|
return False
|
|
|
|
def _doDynamicResponse(self, query):
|
|
if query.type == dns.SOA:
|
|
return defer.succeed(([], [], []))
|
|
|
|
localname = str(query.name)[:-len(domain)] + "local."
|
|
|
|
def browse(localname):
|
|
services = []
|
|
def handler(zeroconf, service_type, name, state_change):
|
|
if state_change is ServiceStateChange.Added:
|
|
services.append(name)
|
|
|
|
sb = ServiceBrowser(self.zeroconf, localname, [handler])
|
|
time.sleep(timeout)
|
|
sb.cancel()
|
|
|
|
answers = []
|
|
for service in services:
|
|
answers.append(dns.RRHeader(name=query.name.name, ttl=ttl, type=dns.PTR, payload=dns.Record_PTR(
|
|
name=service[:-6] + domain
|
|
)))
|
|
return answers, [], []
|
|
|
|
def txt(localname):
|
|
info = self.zeroconf.get_service_info(localname, localname)
|
|
|
|
data = [b"%s=%s" % (p, info.properties[p]) for p in info.properties]
|
|
answers = [dns.RRHeader(name=query.name.name, ttl=ttl, type=dns.TXT, payload=dns.Record_TXT(
|
|
*data
|
|
))]
|
|
return answers, [], []
|
|
|
|
def srv(localname):
|
|
info = self.zeroconf.get_service_info(localname, localname)
|
|
|
|
answers = [dns.RRHeader(name=query.name.name, ttl=ttl, type=dns.SRV, payload=dns.Record_SRV(
|
|
info.priority, info.weight, info.port, info.server[:-6] + domain
|
|
))]
|
|
additional = [dns.RRHeader(name=info.server[:-6] + domain, ttl=ttl, type=dns.A, payload=dns.Record_A(
|
|
socket.inet_ntop(socket.AF_INET, addr)
|
|
)) for addr in info.addresses]
|
|
|
|
return answers, [], additional
|
|
|
|
def host(localname):
|
|
|
|
answers = [dns.RRHeader(name=query.name.name, ttl=ttl, type=dns.A, payload=dns.Record_A(
|
|
"192.0.2.1" # TODO
|
|
))]
|
|
|
|
return answers, [], []
|
|
|
|
d = defer.Deferred()
|
|
|
|
if query.type == dns.PTR:
|
|
d = threads.deferToThread(browse, localname)
|
|
return d
|
|
elif query.type == dns.TXT:
|
|
d = threads.deferToThread(txt, localname)
|
|
return d
|
|
elif query.type == dns.SRV:
|
|
d = threads.deferToThread(srv, localname)
|
|
return d
|
|
elif query.type == dns.A:
|
|
d = threads.deferToThread(host, localname)
|
|
return d
|
|
elif query.type != dns.AAAA:
|
|
print("Unsupported request", query)
|
|
d.callback(([], [], []))
|
|
return d
|
|
|
|
def query(self, query, timeout=None):
|
|
if self._dynamicResponseRequired(query):
|
|
return self._doDynamicResponse(query)
|
|
else:
|
|
return defer.fail(error.DomainError())
|
|
|
|
def main():
|
|
factory = server.DNSServerFactory(
|
|
clients=[DynamicResolver()],
|
|
verbose=0
|
|
)
|
|
|
|
protocol = dns.DNSDatagramProtocol(controller=factory)
|
|
|
|
reactor.listenUDP(port, protocol)
|
|
reactor.listenTCP(port, factory)
|
|
|
|
log.startLogging(sys.stdout)
|
|
reactor.run()
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|