import dns.rdataclass
import dns.rdatatype
import dns.rdata
+import dns.rdtypes.ANY.SOA
import dns.rrset
import dns.tokenizer
import dns.ttl
@ivar tok: The tokenizer
@type tok: dns.tokenizer.Tokenizer object
- @ivar ttl: The default TTL
- @type ttl: int
+ @ivar last_ttl: The last seen explicit TTL for an RR
+ @type last_ttl: int
+ @ivar last_ttl_known: Has last TTL been detected
+ @type last_ttl_known: bool
+ @ivar default_ttl: The default TTL from a $TTL directive or SOA RR
+ @type default_ttl: int
+ @ivar default_ttl_known: Has default TTL been detected
+ @type default_ttl_known: bool
@ivar last_name: The last name read
@type last_name: dns.name.Name object
@ivar current_origin: The current origin
@ivar zone: the zone
@type zone: dns.zone.Zone object
@ivar saved_state: saved reader state (used when processing $INCLUDE)
- @type saved_state: list of (tokenizer, current_origin, last_name, file)
- tuples.
+ @type saved_state: list of (tokenizer, current_origin, last_name, file,
+ last_ttl, last_ttl_known, default_ttl, default_ttl_known) tuples.
@ivar current_file: the file object of the $INCLUDed file being parsed
(None if no $INCLUDE is active).
@ivar allow_include: is $INCLUDE allowed?
self.tok = tok
self.current_origin = origin
self.relativize = relativize
- self.ttl = 0
+ self.last_ttl = 0
+ self.last_ttl_known = False
+ self.default_ttl = 0
+ self.default_ttl_known = False
self.last_name = self.current_origin
self.zone = zone_factory(origin, rdclass, relativize=relativize)
self.saved_state = []
# TTL
try:
ttl = dns.ttl.from_text(token.value)
+ self.last_ttl = ttl
+ self.last_ttl_known = True
token = self.tok.get()
if not token.is_identifier():
raise dns.exception.SyntaxError
except dns.ttl.BadTTL:
- ttl = self.ttl
+ if not (self.last_ttl_known or self.default_ttl_known):
+ raise dns.exception.SyntaxError("Missing default TTL value")
+ if self.default_ttl_known:
+ ttl = self.default_ttl
+ else:
+ ttl = self.last_ttl
# Class
try:
rdclass = dns.rdataclass.from_text(token.value)
raise dns.exception.SyntaxError(
"caught exception %s: %s" % (str(ty), str(va)))
+ if not self.default_ttl_known and isinstance(rd, dns.rdtypes.ANY.SOA.SOA):
+ # The pre-RFC2308 and pre-BIND9 behavior inherits the zone default
+ # TTL from the SOA minttl if no $TTL statement is present before the
+ # SOA is parsed.
+ self.default_ttl = rd.minimum
+ self.default_ttl_known = True
+
rd.choose_relativity(self.zone.origin, self.relativize)
covers = rd.covers()
rds = n.find_rdataset(rdclass, rdtype, covers, True)
# TTL
try:
ttl = dns.ttl.from_text(token.value)
+ self.last_ttl = ttl
+ self.last_ttl_known = True
token = self.tok.get()
if not token.is_identifier():
raise dns.exception.SyntaxError
except dns.ttl.BadTTL:
- ttl = self.ttl
+ if not (self.last_ttl_known or self.default_ttl_known):
+ raise dns.exception.SyntaxError("Missing default TTL value")
+ if self.default_ttl_known:
+ ttl = self.default_ttl
+ else:
+ ttl = self.last_ttl
# Class
try:
rdclass = dns.rdataclass.from_text(token.value)
self.current_origin,
self.last_name,
self.current_file,
- self.ttl) = self.saved_state.pop(-1)
+ self.last_ttl,
+ self.last_ttl_known,
+ self.default_ttl,
+ self.default_ttl_known) = self.saved_state.pop(-1)
continue
break
elif token.is_eol():
token = self.tok.get()
if not token.is_identifier():
raise dns.exception.SyntaxError("bad $TTL")
- self.ttl = dns.ttl.from_text(token.value)
+ self.default_ttl = dns.ttl.from_text(token.value)
+ self.default_ttl_known = True
self.tok.get_eol()
elif c == u'$ORIGIN':
self.current_origin = self.tok.get_name()
self.current_origin,
self.last_name,
self.current_file,
- self.ttl))
+ self.last_ttl,
+ self.last_ttl_known,
+ self.default_ttl,
+ self.default_ttl_known))
self.current_file = open(filename, 'r')
self.tok = dns.tokenizer.Tokenizer(self.current_file,
filename)
ns2 1w1D1h1m1S a 10.0.0.2
"""
+# No $TTL so default TTL for RRs should be inherited from SOA minimum TTL (
+# not from the last explicit RR TTL).
+ttl_from_soa_text = """$ORIGIN example.
+@ 1h soa foo bar 1 2 3 4 5
+@ 1h ns ns1
+@ 1h ns ns2
+ns1 1w1D1h1m1S a 10.0.0.2
+ns2 a 10.0.0.1
+"""
+
+# No $TTL and no SOA, so default TTL for RRs should be inherited from last
+# explicit RR TTL.
+ttl_from_last_text = """$ORIGIN example.
+@ 1h ns ns1
+@ 1h ns ns2
+ns1 a 10.0.0.1
+ns2 1w1D1h1m1S a 10.0.0.2
+"""
+
+# No $TTL and no SOA should raise SyntaxError as no TTL can be determined.
+no_ttl_text = """$ORIGIN example.
+@ ns ns1
+@ ns ns2
+ns1 a 10.0.0.1
+ns2 a 10.0.0.2
+"""
+
no_soa_text = """$TTL 1h
$ORIGIN example.
@ ns ns1
rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A)
self.failUnless(rds.ttl == 694861)
+ def testTTLFromSOA(self):
+ z = dns.zone.from_text(ttl_from_soa_text, 'example.', relativize=True)
+ n = z['@']
+ rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA)
+ self.failUnless(rds.ttl == 3600)
+ soa_rd = rds[0]
+ n = z['ns1']
+ rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A)
+ self.failUnless(rds.ttl == 694861)
+ n = z['ns2']
+ rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A)
+ self.failUnless(rds.ttl == soa_rd.minimum)
+
+ def testTTLFromLast(self):
+ z = dns.zone.from_text(ttl_from_last_text, 'example.', check_origin=False)
+ n = z['@']
+ rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.NS)
+ self.failUnless(rds.ttl == 3600)
+ n = z['ns1']
+ rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A)
+ self.failUnless(rds.ttl == 3600)
+ n = z['ns2']
+ rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A)
+ self.failUnless(rds.ttl == 694861)
+
+ def testNoTTL(self):
+ def bad():
+ dns.zone.from_text(no_ttl_text, 'example.', check_origin=False)
+ self.failUnlessRaises(dns.exception.SyntaxError, bad)
+
def testNoSOA(self):
def bad():
dns.zone.from_text(no_soa_text, 'example.', relativize=True)
def testFirstRRStartsWithWhitespace(self):
# no name is specified, so default to the initial origin
- # no ttl is specified, so default to the initial TTL of 0
- z = dns.zone.from_text(' IN A 10.0.0.1', origin='example.',
+ z = dns.zone.from_text(' 300 IN A 10.0.0.1', origin='example.',
check_origin=False)
n = z['@']
rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A)
- self.failUnless(rds.ttl == 0)
+ self.failUnless(rds.ttl == 300)
def testZoneOrigin(self):
z = dns.zone.Zone('example.')