[AETHER-1000]

Allow reverse DNS lookups to work

NOTE: requires change in syntax used to define a zone - dns_zones no
longer works, must use dns_forward_zones/dns_reverse_zones

Fix multiplatform support

Change-Id: Ibd416b2da8853bc0b25ddec1774ddf9a9e1bf898
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f61049b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org>
+# SPDX-License-Identifier: Apache-2.0
+
+*.pyc
+__pycache__
diff --git a/README.md b/README.md
index 8af9627..84896ba 100644
--- a/README.md
+++ b/README.md
@@ -8,14 +8,15 @@
 
 [NSD Documentation](https://nlnetlabs.nl/documentation/nsd/)
 
-By default, listens on 127.0.0.1 and assumes that `unbound` or a similar
-recursive resolver or a proxy like `dnscurve` is sending it requests.
+By default NSD will listen on 127.0.0.1 and assumes that `unbound` or a similar
+recursive resolver or a proxy like `dnscurve` is on the same host sending it
+requests.
 
-Both forward and reverse zones are created.
+Both forward and reverse zones can be specified.
 
-TODO:
-
-- Handle creating forward-only zones, where the IP address isn't within the subnet
+Note: Previous revisions of this role allowed for a single forward definition
+and inferred the reverse zone, but this does not work well with multiple
+RFC1918 subnets defined.
 
 ## Requirements
 
@@ -23,10 +24,10 @@
 
 ## Defaults
 
-See the `dns_zones` structure:
+See the `dns_forward_zones` and `dns_reverse_zones` structures:
 
 ```yaml
-dns_zones:
+dns_forward_zones:
   example.com:
     ip_range: 192.168.1.1/24
     ns:
@@ -40,15 +41,28 @@
       lpr: printer.example.com.
     srv: {}
     txt: {}
+
+dns_reverse_zones:
+  192.168.0.0/16:
+    ns:
+      - gw.example.com.
+    ptr:
+      192.168.1.1: gw.example.com.
+      192.168.1.2: host1.example.com.
+      192.168.1.3: host2.example.com.
+      192.168.1.4: printer1.example.com.
+
 ```
 
 Note: In the molecule tests, `<zone>.serial` is used for the zonefile serial,
-and is set to a static value to guarantee idempotency. In production, this
-must be changed every time the zonefile changes, or can be omitted and the
-current timestamp is used to generate the serial.  Other DNS roles tried
-`complicated solutions
+and is set to a static value to guarantee idempotency. In production, this must
+be changed every time the zonefile changes, or can be omitted and the current
+timestamp is used to generate the serial.  Other DNS roles tried `complicated
+solutions
 <https://github.com/bertvv/ansible-role-bind/blob/master/templates/bind_zone.j2>`_
-to guarantee the serial changes, but that seems like a lot of trouble, and is only useful for zone transfers which are silly/antiquated 
+to guarantee the serial changes, but that seems like a lot of trouble, and is
+only useful for facilitating zone transfers which are
+silly/antiquated/security-problematic.
 
 ## Example Playbook
 
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
index e88b105..1b9266f 100644
--- a/molecule/default/converge.yml
+++ b/molecule/default/converge.yml
@@ -7,7 +7,7 @@
 - name: Converge
   hosts: all
   vars:
-    dns_zones:
+    dns_forward_zones:
       example.com:
         serial: 20201102
         ip_range: 10.0.0.1/24
@@ -26,12 +26,25 @@
         serial: 20201102
         ip_range: 10.0.10.1/24
         ns:
-          - gw2.example.com.
+          - gw2.example.org.
         a:
           gw2: 10.0.10.1
         cname: {}
         srv: {}
         txt: {}
+    dns_reverse_zones:
+      10.0.0.0/8:
+        serial: 20210315
+        ns:
+          - gw.example.com.
+          - gw2.example.org.
+        ptr:
+          10.0.0.1: gw.example.com.
+          10.0.0.2: host1.example.com.
+          10.0.0.3: host2.example.com.
+          10.0.0.4: printer1.example.com.
+          10.0.10.1: gw2.example.org.
+
   tasks:
     - name: "Include nsd"
       include_role:
diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml
index 43cba44..596330b 100644
--- a/molecule/default/verify.yml
+++ b/molecule/default/verify.yml
@@ -21,12 +21,24 @@
 
     - name: Check for NS record
       command:  # noqa 301
-        cmd: "dig ns example.com @127.0.0.1"
+        cmd: "dig ns +short example.com @127.0.0.1"
       register: ns_dig
-      failed_when: "'example.com.\t\t3600\tIN\tNS\tgw.example.com.' not in ns_dig.stdout"
+      failed_when: "'gw.example.com.' not in ns_dig.stdout"
 
     - name: Check for CNAME record
       command:  # noqa 301
         cmd: "dig lpr.example.com @127.0.0.1"
       register: cn_dig
       failed_when: "'lpr.example.com.\t3600\tIN\tCNAME\tprinter.example.com.' not in cn_dig.stdout"
+
+    - name: Check for reverse IP lookup
+      command:  # noqa 301
+        cmd: "dig -x {{ item.key }} @127.0.0.1"
+      register: rip_dig
+      failed_when: "item.value not in rip_dig.stdout"
+      with_dict:
+        10.0.0.1: gw.example.com.
+        10.0.0.2: host1.example.com.
+        10.0.0.3: host2.example.com.
+        10.0.0.4: printer1.example.com.
+        10.0.10.1: gw2.example.org.
diff --git a/tasks/OpenBSD.yml b/tasks/OpenBSD.yml
index e64f33f..e067aa9 100644
--- a/tasks/OpenBSD.yml
+++ b/tasks/OpenBSD.yml
@@ -4,4 +4,8 @@
 # SPDX-FileCopyrightText: © 2020 Open Networking Foundation <support@opennetworking.org>
 # SPDX-License-Identifier: Apache-2.0
 
-# nothing to do here, OpenBSD already has NSD installed in base
+#  NSD installed in base
+
+- name: Set unbound arguments for use with service module
+  set_fact:
+    nsd_arguments: "-c /var/nsd/etc/nsd.conf"
diff --git a/tasks/main.yml b/tasks/main.yml
index d6a9af6..2d28754 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -38,21 +38,28 @@
     owner: root
     group: "{{ nsd_groupname }}"
     validate: "nsd-checkzone {{ item.key }} %s"
-  with_dict: "{{ dns_zones }}"
+  with_dict: "{{ dns_forward_zones }}"
   notify:
     - reload-nsd
 
-# - name: Create DNS reverse zonefiles from template
-#   template:
-#     src: zone.reverse.j2
-#     dest: "{{ nsd_zones_dir }}/{{ item.key }}.reverse"
-#     mode: 0644
-#     owner: root
-#     group: "{{ nsd_groupname }}"
-#     validate: "nsd-checkzone {{ item.value.ip_range | unbound_revdns }} %s"
-#   with_dict: "{{ dns_zones }}"
-#   notify:
-#     - reload-nsd
+- name: Create DNS reverse zonefiles from template
+  template:
+    src: zone.reverse.j2
+    dest: "{{ nsd_zones_dir }}/{{ item.key | ipaddr('network') }}.reverse"
+    mode: 0644
+    owner: root
+    group: "{{ nsd_groupname }}"
+    validate: "nsd-checkzone {{ item.key | unbound_revdns }} %s"
+  with_dict: "{{ dns_reverse_zones }}"
+  notify:
+    - reload-nsd
+
+- name: Enable and start nsd
+  service:
+    name: "{{ nsd_service }}"
+    enabled: true
+    state: started
+    arguments: "{{ nsd_arguments | default(omit) }}"
 
 - name: Flush handlers as listen addresses can conflict with unbound
   meta: flush_handlers
diff --git a/templates/nsd.conf.j2 b/templates/nsd.conf.j2
index 93c2a14..b079d13 100644
--- a/templates/nsd.conf.j2
+++ b/templates/nsd.conf.j2
@@ -18,14 +18,17 @@
 remote-control:
   control-enable: yes
 
-# zonefiles to load
-{% for key, value in dns_zones.items() %}
+# forward zones
+{% for key, value in dns_forward_zones.items() %}
 zone:
   name: {{ key }}
   zonefile: {{ key }}.forward
+{% endfor %}
 
-# zone:
-#   name: {{ value.ip_range | unbound_revdns }}
-#   zonefile: {{ key }}.reverse
+# reverse zones
+{% for key, value in dns_reverse_zones.items() %}
+zone:
+  name: {{ key | unbound_revdns }}
+  zonefile: {{ key | ipaddr('network') }}.reverse
 
 {% endfor %}
diff --git a/templates/zone.reverse.j2 b/templates/zone.reverse.j2
index a0e5ecc..512f34f 100644
--- a/templates/zone.reverse.j2
+++ b/templates/zone.reverse.j2
@@ -7,17 +7,20 @@
 $ORIGIN {{ item.key }}. ; default zone domain
 $TTL {{ item.value.ttl | default(nsd_default_ttl) }} ; default time to live
 
-{{ item.value.ip_range | unbound_revdns }} IN SOA {{ item.value.ns | first }} admin.{{ item.key }}. (
+{{ item.key| unbound_revdns }} IN SOA {{ item.value.ns | first }} admin.{{ item.key }}. (
          {{ item.value.serial | default(ansible_date_time.epoch) }}   ; Serial, must be incremented every time you change this file
          3600        ; Refresh [1hr]
          600         ; Retry [10m]
          3600        ; Expire [1hr]
          60          ; Min TTL [1m]
          )
-{% if item.value.a is defined %}
+
+; NameServers
+{% for ns in item.value.ns %}
+ IN NS {{ ns }}
+{% endfor %}
 
 ; PTR records
-{% for name, ip4 in item.value.a.items() %}
+{% for ip4, name in item.value.ptr.items() %}
 {{ ip4 | ipaddr('revdns')}} IN PTR {{ name }}
 {% endfor %}
-{% endif %}