#!/usr/bin/env python3

''' Check 'synth_record' query module synthetic responses. '''

from dnstest.test import Test
from dnstest.module import ModSynthRecord, ModOnlineSign
import random
import re

t = Test()

ModSynthRecord.check()

onlinesign = random.choice([True, False])
try:
    ModOnlineSign.check()
except:
    onlinesign = False

# Zone indexes
FWD      = 0
REV4     = 1
REV6     = 2
REV      = 3
REV4CIDR = 4

# Initialize server configuration
knot = t.server("knot")
zone = t.zone("forward.", storage=".") + \
       t.zone("1.168.192.in-addr.arpa.", storage=".") + \
       t.zone("1.6.b.0.0.0.0.0.0.2.6.2.ip6.arpa.", storage=".") + \
       t.zone("ip6.arpa.", storage=".") + \
       t.zone("0/25.2.0.10.in-addr.arpa.", storage=".", file_name="0-25.2.0.10.in-addr.arpa.zone")
t.link(zone, knot)

# Configure 'synth_record' modules for auto forward/reverse zones
knot.add_module(zone[FWD],      ModSynthRecord("forward", None,        None, "192.168.0.1"))
knot.add_module(zone[FWD],      ModSynthRecord("forward", "dynamic-", "900", "[ 192.168.1.0-192.168.1.127, 2620:0:b61::/52 ]"))
knot.add_module(zone[REV4],     ModSynthRecord("reverse", "dynamic-", "900", "[ 192.168.3.0/25, 192.168.1.0/25, 192.168.2.0/25 ]", "forward."))
knot.add_module(zone[REV6],     ModSynthRecord("reverse", "dynamic-", "900", "2620:0000:0b61::-2620:0000:0b61:0fff:ffff:ffff:ffff:ffff", "forward."))
knot.add_module(zone[REV],      ModSynthRecord("reverse", "",         "900", "::0/0", "forward."))
knot.add_module(zone[REV4CIDR], ModSynthRecord("reverse", "dynamic-", "900", "[ 10.0.2.0/25 ]", "forward."))

if onlinesign:
    for z in zone:
        knot.add_module(z, ModOnlineSign())

def check_rrsig(resp, expect):
    resp.check_count(expect if onlinesign else 0, rtype="RRSIG", section="answer")

def check_nsec(resp, expect):
    resp.check_count(expect if onlinesign else 0, rtype="NSEC", section="authority")
    resp.check_count(expect + 1 if onlinesign else 0, rtype="RRSIG", section="authority") # +1 for SOA

t.start()

# Static address mapping
static_map = [ ("192.168.1.42", "42." + zone[REV4].name, "static4-a.forward."),
               ("2620:0:b61::42", "2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0." + zone[REV6].name, "static6-a.forward.") ]

# Check static reverse records
for (_, reverse, forward) in static_map:
    resp = knot.dig(reverse, "PTR", dnssec=True)
    resp.check(forward, rcode="NOERROR", flags="QR AA", ttl=172800)
    check_rrsig(resp, 1)

# Check static forward records
for (addr, reverse, forward) in static_map:
    rrtype = "AAAA" if ":" in addr else "A"
    resp = knot.dig(forward, rrtype, dnssec=True)
    resp.check(addr, rcode="NOERROR", flags="QR AA", ttl=7200)
    check_rrsig(resp, 1)

# Check positive dynamic reverse records
dynamic_map = [ ("192.168.1.1", "1." + zone[REV4].name, "dynamic-192-168-1-1." + zone[FWD].name),
                ("2620:0:b61::1", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0." + zone[REV6].name, "dynamic-2620-0-b61--1." + zone[FWD].name) ]
#                       <     > <     > <     > <     > <     > <     > <     > <     >
reverse_extra = [ ("", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0." + zone[REV].name, "0-0--0." + zone[FWD].name),
                  ("", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0." + zone[REV].name, "0-0--1." + zone[FWD].name),
                  ("", "0.0.0.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0." + zone[REV].name, "0-0--1-0." + zone[FWD].name),
                  ("", "0.0.0.0.0.0.0.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0." + zone[REV].name, "0-0--1-0-0." + zone[FWD].name),
                  ("", "0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0." + zone[REV].name, "0-0--1-0-0-0." + zone[FWD].name),
                  ("", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0." + zone[REV].name, "0-0-0-1--0." + zone[FWD].name),
                  ("", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.0.0.0.0.0.0.0.0." + zone[REV].name, "0-0-1--0." + zone[FWD].name),
                  ("", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.0.0.0.0." + zone[REV].name, "0-1--0." + zone[FWD].name),
                  ("", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0." + zone[REV].name, "1-0--0." + zone[FWD].name),
                  ("", "0.0.0.1.0.0.2.0.0.3.0.0.4.0.0.0.a.b.c.d.0.e.f.0.a.a.0.0.0.0.b.b." + zone[REV].name, "bb00-aa-fe0-dcba-4-30-200-1000." + zone[FWD].name) ]

reverse_cidr = [ ("", "126." + zone[REV4CIDR].name, "dynamic-10-0-2-126." + zone[FWD].name) ]

for (_, reverse, forward) in dynamic_map + reverse_extra + reverse_cidr:
    resp = knot.dig(reverse, "PTR", dnssec=True)
    resp.check(forward, rcode="NOERROR", flags="QR AA", ttl=900)
    check_rrsig(resp, 1)

# Check positive dynamic forward records (default TTL and prefix)
resp = knot.dig("192-168-0-1.forward", "A", dnssec=True)
resp.check("192.168.0.1", rcode="NOERROR", ttl=3600)
check_rrsig(resp, 1)

# Check positive dynamic forward records
forward_extra = [ ("2620:0:b61::", "", "dynamic-2620-0-b61--." + zone[FWD].name),
                  ("2620:0:b61::1", "", "dynamic-2620-0-b61--1." + zone[FWD].name),
                  ("2620:0:b61::10", "", "dynamic-2620-0-b61--10." + zone[FWD].name),
                  ("2620:0:b61::100", "", "dynamic-2620-0-b61--100." + zone[FWD].name),
                  ("2620:0:b61::1000", "", "dynamic-2620-0-b61--1000." + zone[FWD].name),
                  ("2620:0:b61::", "", "dynamic-2620-0-b61-0-0-0-0-0." + zone[FWD].name) ]
for (addr, reverse, forward) in dynamic_map + forward_extra:
    rrtype = "AAAA" if ":" in addr else "A"
    resp = knot.dig(forward, rrtype, dnssec=True)
    resp.check(addr, rcode="NOERROR", flags="QR AA", ttl=900)
    check_rrsig(resp, 1)

# Check NODATA answer for all records
for (addr, reverse, forward) in dynamic_map:
    resp = knot.dig(reverse, "TXT", dnssec=True)
    resp.check(nordata=forward, rcode="NOERROR", flags="QR AA", ttl=172800)
    check_nsec(resp, 1)
    resp = knot.dig(forward, "TXT", dnssec=True)
    resp.check(nordata=addr, rcode="NOERROR", flags="QR AA", ttl=172800)
    check_nsec(resp, 1)

# Check NODATA on resulting empty-non-terminals
for (_, reverse, forward) in dynamic_map + reverse_extra:
    while knot.dig(reverse, "SOA").count("SOA") < 1: # until we hit zone apex
        reverse = re.sub(r'^[^.]*\.', '', reverse) # cut out the leftmost label
        resp = knot.dig(reverse, "PTR", dnssec=True)
        resp.check(nordata=forward, rcode="NOERROR", flags="QR AA", ttl=172800)
        check_nsec(resp, 1)

# Check "out of subnet range" query response
nxdomain_map = [ ("192.168.1.128", "128." + zone[REV4].name, "dynamic-192-168-1-128." + zone[FWD].name),
                 ("2620:0:b61:1000::", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1." + zone[REV6].name,
                  "dynamic-2620-0000-0b61-1000-0000-0000-0000-0000." + zone[FWD].name) ]
for (addr, reverse, forward) in nxdomain_map:
    rrtype = "AAAA" if ":" in addr else "A"
    exp_rcode = "NXDOMAIN" if not onlinesign else "NOERROR" # Onlinesign promotes NXDOMAIN to NODATA
    resp = knot.dig(reverse, "PTR", dnssec=True)
    resp.check(rcode=exp_rcode, flags="QR AA")
    check_nsec(resp, 1)
    resp = knot.dig(forward, rrtype, dnssec=True)
    resp.check(rcode=exp_rcode, flags="QR AA")
    check_nsec(resp, 1)

# Check alias leading to synthetic name
alias_map = [ ("192.168.1.1", None, "cname4." + zone[FWD].name),
              ("2620:0:b61::1", None, "cname6." + zone[FWD].name) ]
for (addr, _, forward) in alias_map:
    rrtype = "AAAA" if ":" in addr else "A"
    resp = knot.dig(forward, rrtype, dnssec=True)
    resp.check(addr, rcode="NOERROR", flags="QR AA", ttl=900)
    check_rrsig(resp, 2)

# Check ANY type question
for (addr, reverse, forward) in dynamic_map:
    resp = knot.dig(forward, "ANY", dnssec=True)
    resp.check(rcode="NOERROR", flags="QR AA")
    check_rrsig(resp, 1)

t.end()
