2 from twisted.internet import reactor
3 from twisted.names import server
4 from twisted.names import dns
5 from twisted.names import common
6 from twisted.names import authority
7 from twisted.internet import defer
8 from twisted.python import failure
10 from invirt.config import structs as config
11 import invirt.database
16 class DatabaseAuthority(common.ResolverBase):
17 """An Authority that is loaded from a file."""
21 def __init__(self, domains=None, database=None):
22 common.ResolverBase.__init__(self)
23 if database is not None:
24 invirt.database.connect(database)
26 invirt.database.connect()
27 if domains is not None:
28 self.domains = domains
30 self.domains = config.dns.domains
31 ns = config.dns.nameservers[0]
32 self.soa = dns.Record_SOA(mname=ns.hostname,
33 rname=config.dns.contact.replace('@','.',1),
34 serial=1, refresh=3600, retry=900,
35 expire=3600000, minimum=21600, ttl=3600)
36 self.ns = dns.Record_NS(name=ns.hostname, ttl=3600)
37 record = dns.Record_A(address=ns.ip, ttl=3600)
38 self.ns1 = dns.RRHeader(ns.hostname, dns.A, dns.IN,
39 3600, record, auth=True)
42 def _lookup(self, name, cls, type, timeout = None):
45 value = self._lookup_unsafe(name, cls, type, timeout = None)
46 except (psycopg2.OperationalError, sqlalchemy.exceptions.SQLError):
49 print "Reloading database"
55 def _lookup_unsafe(self, name, cls, type, timeout):
56 invirt.database.clear_cache()
61 if name in self.domains:
64 # Look for the longest-matching domain. (This works because domain
65 # will remain bound after breaking out of the loop.)
67 for domain in self.domains:
68 if name.endswith('.'+domain) and len(domain) > len(best_domain):
71 return defer.fail(failure.Failure(dns.DomainError(name)))
75 additional = [self.ns1]
76 authority.append(dns.RRHeader(domain, dns.NS, dns.IN,
77 3600, self.ns, auth=True))
80 host = name[:-len(domain)-1]
81 if not host: # Request for the domain itself.
82 if type in (dns.A, dns.ALL_RECORDS):
83 record = dns.Record_A(config.dns.nameservers[0].ip, ttl)
84 results.append(dns.RRHeader(name, dns.A, dns.IN,
85 ttl, record, auth=True))
87 results.append(dns.RRHeader(domain, dns.NS, dns.IN,
88 ttl, self.ns, auth=True))
91 results.append(dns.RRHeader(domain, dns.SOA, dns.IN,
92 ttl, self.soa, auth=True))
93 else: # Request for a subdomain.
94 if 'passup' in dir(config.dns) and host in config.dns.passup:
95 record = dns.Record_CNAME('%s.%s' % (host, config.dns.parent), ttl)
96 return defer.succeed((
97 [dns.RRHeader(name, dns.CNAME, dns.IN, ttl, record, auth=True)],
100 value = invirt.database.Machine.query().filter_by(name=host).first()
101 if value is None or not value.nics:
102 return defer.fail(failure.Failure(dns.AuthoritativeDomainError(name)))
103 ip = value.nics[0].ip
104 if ip is None: #Deactivated?
105 return defer.fail(failure.Failure(dns.AuthoritativeDomainError(name)))
107 if type in (dns.A, dns.ALL_RECORDS):
108 record = dns.Record_A(ip, ttl)
109 results.append(dns.RRHeader(name, dns.A, dns.IN,
110 ttl, record, auth=True))
111 elif type == dns.SOA:
112 results.append(dns.RRHeader(domain, dns.SOA, dns.IN,
113 ttl, self.soa, auth=True))
114 if len(results) == 0:
117 return defer.succeed((results, authority, additional))
120 return defer.fail(failure.Failure(dns.AuthoritativeDomainError(name)))
122 if '__main__' == __name__:
124 for zone in config.dns.zone_files:
125 for origin in config.dns.domains:
126 r = authority.BindAuthority(zone)
127 # This sucks, but if I want a generic zone file, I have to
128 # reload the information by hand
130 lines = open(zone).readlines()
131 lines = r.collapseContinuations(r.stripComments(lines))
135 resolvers.append(DatabaseAuthority())
138 f = server.DNSServerFactory(authorities=resolvers, verbose=verbosity)
139 p = dns.DNSDatagramProtocol(f)
140 f.noisy = p.noisy = verbosity
142 reactor.listenUDP(53, p)
143 reactor.listenTCP(53, f)