terom@634: # pvl-hosts terom@556: tero@443: DNS/DHCP hosts management/integration for ISC bind9 and dhcpd. terom@556: terom@634: ## Hosts tero@512: The `pvl.hosts-*` tools read hosts files as input, which have an ini format, using section names as hostnames to configure attributes for that host: tero@443: tero@443: [foo] tero@512: ip = 192.0.2.1 tero@452: ethernet = 00:11:22:33:44:55 tero@443: tero@443: [bar] tero@512: ip = 192.0.2.2 tero@452: ethernet = 01:23:45:67:89:ab tero@443: tero@520: The domain name for a host is determined from the basename of the config file, so this example file would generate something like the following output for use in a `zone "example.com" { ... }` zonefile: tero@512: tero@516: $ bin/pvl.hosts-forward etc/hosts/example.com tero@512: foo A 192.0.2.1 tero@512: bar A 192.0.2.2 tero@443: tero@520: And correspondingly, the reverse zone for `2.0.192.in-addr.arpa`: tero@443: tero@520: $ bin/pvl.hosts-reverse --zone-prefix=192.0.2.0/24 etc/hosts/example.com tero@512: 1 PTR foo.example.com. tero@512: 2 PTR bar.example.com. tero@443: tero@452: And the associated DHCP hosts: tero@452: tero@512: $ bin/pvl.hosts-dhcp etc/hosts/example.com tero@452: host foo { tero@480: option host-name foo; tero@480: hardware ethernet 00:11:22:33:44:55; tero@512: fixed-address 192.0.2.1; tero@452: } tero@452: tero@452: host bar { tero@480: option host-name bar; tero@480: hardware ethernet 01:23:45:67:89:ab; tero@512: fixed-address 192.0.2.2; tero@452: } tero@452: terom@634: ### Include directories tero@507: Host configs can be included: tero@507: tero@521: $ cat etc/hosts/test tero@514: include = test.d/ tero@507: tero@521: $ cat etc/hosts/test.d/foo tero@507: ip = 192.0.2.1 tero@507: tero@521: $ cat etc/hosts/test.d/bar tero@507: ip = 192.0.2.2 tero@507: tero@521: $ bin/pvl.hosts-forward etc/hosts/test tero@514: foo A 192.0.2.1 tero@514: bar A 192.0.2.2 tero@514: tero@514: Including a directory of files is equivalent to substituiting each file as a named section at the level of the include = statement. Note that this means that included files are treated directly as host definitions, IOW, you should NOT include a section name in an included host file unless you want to declare an additional subdomain: tero@514: tero@514: $ cat etc/hosts/wrong.test tero@514: include = wrong.d/ tero@514: tero@514: $ etc/hosts/wrong.d/host tero@514: [host] tero@514: ip = 192.0.2.6 tero@514: tero@516: Using the --root-zone option to generate the full FQDN for the host: tero@516: tero@516: $ bin/pvl.hosts-forward --root-zone etc/hosts/wrong.test tero@516: host.host.wrong.test A 192.0.2.6 tero@507: terom@634: ### Host aliases tero@484: Hosts can specify DNS aliases: tero@484: tero@484: [foo] tero@484: ip = 127.0.0.1 tero@484: alias = test1 terom@705: alias4 = test tero@484: tero@484: [bar] tero@484: ip = 127.0.0.2 tero@484: alias = test2 terom@705: alias4 = test tero@484: terom@705: $ bin/pvl.hosts-forward etc/hosts/alias.test tero@484: foo A 127.0.0.1 tero@484: test1 CNAME foo terom@705: test A 127.0.0.1 tero@484: bar A 127.0.0.2 tero@484: test2 CNAME bar terom@705: test A 127.0.0.2 terom@705: terom@705: Normal CNAME aliases cannot overlap with other hosts, but the IPv4/IPv6-only `alias4`/`alias6` may overlap. tero@484: terom@634: ### Generated hosts tero@447: The hosts file format supports something similar to bind9's $GENERATE directive for hosts: tero@447: terom@705: [dyn{1-8}] terom@705: ip = 10.1.16.$ tero@447: terom@705: $ bin/pvl.hosts-forward etc/hosts/dyn.test terom@705: dyn1 A 10.1.16.1 terom@705: dyn2 A 10.1.16.2 terom@705: dyn3 A 10.1.16.3 terom@705: dyn4 A 10.1.16.4 terom@705: dyn5 A 10.1.16.5 terom@705: dyn6 A 10.1.16.6 terom@705: dyn7 A 10.1.16.7 terom@705: dyn8 A 10.1.16.8 terom@705: tero@448: This feature can be used for generating reverse delegations: tero@448: [foo-{240-247}] tero@448: forward = tero@448: reverse = $.240/29.0.0.10.in-addr.arpa tero@448: ip = 10.0.0.$ terom@705: terom@705: $ bin/pvl.hosts-reverse --zone-prefix=10.0.0.0/16 etc/hosts/reverse.test terom@705: 240.0 CNAME 240.240/29.0.0.10.in-addr.arpa. terom@705: 241.0 CNAME 241.240/29.0.0.10.in-addr.arpa. terom@705: 242.0 CNAME 242.240/29.0.0.10.in-addr.arpa. terom@705: 243.0 CNAME 243.240/29.0.0.10.in-addr.arpa. terom@705: 244.0 CNAME 244.240/29.0.0.10.in-addr.arpa. terom@705: 245.0 CNAME 245.240/29.0.0.10.in-addr.arpa. terom@705: 246.0 CNAME 246.240/29.0.0.10.in-addr.arpa. terom@705: 247.0 CNAME 247.240/29.0.0.10.in-addr.arpa. tero@480: terom@634: ### DHCP Options tero@480: The hosts need not specify any fixed ip address, leaving IP address allocation to dhcpd: tero@480: tero@480: [foo] tero@480: ethernet = 00:11:22:33:44:55 tero@480: terom@705: $ bin/pvl.hosts-dhcp etc/hosts/dhcp.test tero@480: host foo { tero@480: option host-name foo; tero@480: hardware ethernet 00:11:22:33:44:55; tero@480: } tero@480: terom@634: ### DHCP Boot options tero@480: The hosts can specify DHCP boot server/file options: tero@480: terom@705: boot.next-server = boot.test terom@705: tero@480: [foo] terom@705: ethernet = 00:11:22:33:44:55 terom@705: boot = boot2.test:/debian/wheezy/pxelinux.0 tero@480: terom@705: [bar] terom@705: ethernet = 00:11:22:33:44:55 terom@705: boot.filename = /debian/jessie/pxelinux.0 terom@705: terom@705: $ bin/pvl.hosts-dhcp etc/hosts/boot.test tero@480: host foo { tero@480: option host-name foo; tero@480: hardware ethernet 00:11:22:33:44:55; terom@705: next-server boot2.test; terom@705: filename "/debian/wheezy/pxelinux.0"; terom@705: } terom@705: terom@705: host bar { terom@705: option host-name bar; terom@705: hardware ethernet 00:11:22:33:44:55; terom@705: next-server boot.test; terom@705: filename "/debian/jessie/pxelinux.0"; tero@480: } tero@480: terom@634: ### DHCP hosts in multiple subnets/domains tero@483: A host with different interfaces in multiple domains must specify unique interface names: tero@483: terom@705: [foo] tero@483: [[asdf]] tero@483: ip = 10.1.0.1 tero@483: ethernet.eth1 = 00:11:22:33:44:55 tero@483: terom@705: [bar] tero@483: [[asdf]] tero@483: ip = 10.2.0.1 tero@483: ethernet.eth2 = 55:44:33:22:11:00 tero@483: terom@705: $ bin/pvl.hosts-dhcp etc/hosts/dhcp-test tero@483: host asdf-eth1 { tero@483: option host-name asdf; tero@483: hardware ethernet 00:11:22:33:44:55; tero@483: fixed-address 10.1.0.1; tero@483: } tero@483: tero@483: host asdf-eth2 { tero@483: option host-name asdf; tero@483: hardware ethernet 55:44:33:22:11:00; tero@483: fixed-address 10.2.0.1; tero@483: } tero@483: terom@706: ### DHCP subgroups terom@706: Hosts can be assigned to DHCP subgroups by hardware ethernet: terom@706: terom@706: #### `dhcpd.conf` terom@706: class "test-hosts" { terom@706: match hardware; terom@706: } terom@706: terom@706: #### `etc/hosts/dhcp-classes.test` terom@706: [foo] terom@706: ethernet = 00:11:22:33:44:55 terom@706: dhcp:subclass = test-hosts terom@706: terom@706: #### `bin/pvl.hosts-dhcp etc/hosts/dhcp-classes.test` terom@706: host foo { terom@706: option host-name foo; terom@706: hardware ethernet 00:11:22:33:44:55; terom@706: } terom@706: terom@706: subclass "test-hosts" 1:00:11:22:33:44:55; terom@706: terom@634: # `update` tero@626: A script to drive the *pvl.hosts* tools for maintaing a set of zone/host files for a DNS/DHCP server. tero@626: terom@634: ## Source host files tero@521: tero@521: Creating a tree of symlinks for managing split zonefile domains can be useful: tero@521: tero@521: $ tree etc/zones/ tero@521: etc/zones/ tero@521: ├── forward tero@521: │   └── test tero@521: │   ├── asdf.test -> ../../../hosts/asdf.test tero@521: │   └── test -> ../../../hosts/test terom@705: ├── reverse terom@705: │   └── 192.0.2 terom@705: │   ├── asdf.test -> ../../../hosts/asdf.test terom@705: │   └── test -> ../../../hosts/test terom@705: └── test tero@521: tero@521: Given a structure like above, the `pvl.hosts-forward` can generate a single forward zone containing all sub-domains: tero@521: tero@521: $ bin/pvl.hosts-forward --hosts-include etc/hosts/ etc/zones/forward/test/ tero@521: foo A 192.0.2.1 tero@521: bar A 192.0.2.2 tero@521: quux.asdf A 192.0.2.5 tero@521: tero@521: Note that the directory name is treated separately as a zone origin; the file names within the domain are still treated as a flat namespace independent of the directory name (which is different than *pvl.hosts* would behave for `include = etc/zones/forward/test/`). tero@521: tero@521: The same trick also works for `pvl.hosts-reverse`: tero@521: tero@521: $ bin/pvl.hosts-reverse --hosts-include etc/hosts/ etc/zones/reverse/192.0.2/ tero@521: 1 PTR foo.test. tero@521: 2 PTR bar.test. tero@521: 5 PTR quux.asdf.test. tero@521: tero@645: ## Source zone files tero@645: tero@645: The zonefile header should be written out manually, using an `$INCLUDE` directive to reference the (generated) hosts zonefile: tero@645: tero@645: $ cat etc/zones/test tero@645: $TTL 3600 tero@645: tero@645: @ SOA foo.test. hostmaster.test. ( tero@645: 0 ; serial tero@645: 1d ; refresh tero@645: 5m ; retry tero@645: 10d ; expiry tero@645: 300 ; negative tero@645: ) tero@645: tero@645: NS foo tero@645: NS bar tero@645: tero@645: $INCLUDE "forward/test" tero@645: tero@646: ## Operation terom@557: tero@646: Use the *update* script to generate a complete set of output zonefiles: terom@557: tero@724: $ ./bin/update -C tero@724: var: apply dir tero@724: var/dhcp: apply dir tero@724: var/zones: apply dir tero@724: var/include-cache: apply dir tero@724: var/serials: apply dir tero@724: var/dhcp/hosts: apply dir tero@724: var/zones/includes: apply dir tero@724: var/zones/forward: apply dir tero@724: var/zones/reverse: apply dir tero@646: Commit... tero@724: Using commit timestamp: 1425379711 tero@646: Updating forward host zones... terom@705: var/zones/forward/test: Generating forward hosts zone: etc/zones/forward/test tero@646: Updating reverse host zones... terom@705: var/zones/reverse/192.0.2: Generating reverse hosts zone: etc/zones/reverse/192.0.2 tero@646: Updating DHCP hosts... tero@646: Copying zone includes... tero@646: Updating zones... tero@724: var/serials/test: Update serial: <- 1425379711 tero@724: var/zones/test: Generate zone: etc/zones/test @ 1425379711 tero@646: Updating DHCP confs... tero@646: Testing zones... tero@646: Reload zones... tero@646: Reload zones tero@724: * Reloading domain name service... bind9 [ OK ] tero@646: Testing DHCP... tero@646: Reload DHCP... tero@724: Reload DHCP tero@724: isc-dhcp-server stop/waiting tero@724: isc-dhcp-server start/running, process 32581 tero@724: tero@724: The update script tracks hostfile/zonefile dependencies, and only updates the necessary output files: tero@724: tero@724: $ touch etc/hosts/test.d/foo && ./bin/update -C tero@724: Commit... tero@724: Using commit timestamp: 1425379801 tero@724: Updating forward host zones... tero@724: var/zones/forward/test: Generating forward hosts zone: etc/zones/forward/test tero@724: Updating reverse host zones... tero@724: var/zones/reverse/192.0.2: Generating reverse hosts zone: etc/zones/reverse/192.0.2 tero@724: Updating DHCP hosts... tero@724: Copying zone includes... tero@724: Updating zones... tero@724: var/serials/test: Update serial: 1425379801 <- 1425379801 tero@724: var/zones/test: Generate zone: etc/zones/test @ 1425379801 tero@724: Updating DHCP confs... tero@724: Testing zones... tero@724: Reload zones... tero@724: Reload zones tero@724: * Reloading domain name service... bind9 ...done. tero@724: Testing DHCP... tero@724: Reload DHCP... tero@724: Reload DHCP tero@724: isc-dhcp-server stop/waiting tero@724: isc-dhcp-server start/running, process 775 terom@557: tero@726: Use `-n` to enable noop mode and preview changes before updating: tero@726: tero@726: sed -i s/quux/quux2/ etc/hosts/asdf.test && ./bin/update -C -n tero@726: Commit... tero@726: /home/tjmartti/pvl/pvl-hosts: skip commit tero@726: Using local unix time for uncommited changes: 1425380558 tero@726: Updating forward host zones... tero@726: var/zones/forward/test: Generating forward hosts zone: etc/zones/forward/test tero@726: --- var/zones/forward/test 2015-03-03 12:55:53.480735624 +0200 tero@726: +++ var/zones/forward/test.new 2015-03-03 13:02:38.708732551 +0200 tero@726: @@ -2,2 +2,2 @@ tero@726: bar A 192.0.2.2 tero@726: -quux.asdf A 192.0.2.5 tero@726: +quux22.asdf A 192.0.2.5 tero@726: Updating reverse host zones... tero@726: var/zones/reverse/192.0.2: Generating reverse hosts zone: etc/zones/reverse/192.0.2 tero@726: --- var/zones/reverse/192.0.2 2015-03-03 12:55:53.596735623 +0200 tero@726: +++ var/zones/reverse/192.0.2.new 2015-03-03 13:02:38.832732550 +0200 tero@726: @@ -2,2 +2,2 @@ tero@726: 2 PTR bar.test. tero@726: -5 PTR quux.asdf.test. tero@726: +5 PTR quux22.asdf.test. tero@726: Updating DHCP hosts... tero@726: Copying zone includes... tero@726: Updating zones... tero@726: Updating DHCP confs... tero@726: Testing zones... tero@726: Reload zones... tero@726: Skip reload zones tero@726: Testing DHCP... tero@726: Reload DHCP... tero@726: Skip reload DHCP tero@726: tero@726: Note that noop mode does not yet handle dependency chains, i.e. you will not see which zones get updated serials without also using `-F`, which force-updates all output files regardless of dependency states. tero@726: tero@727: Finally, the default operation mode of update is to commit any changes, and update the zones using the commit timestamp as a serial. Use the `-p` flag to show output diffs as with `-n`: tero@727: tero@727: $ sed -i s/quux/quux2/ etc/hosts/asdf.test && ./bin/update -m "rename quux to quux2" -p tero@727: Commit... tero@727: /home/tjmartti/pvl/pvl-hosts: commit: rename quux to quux2 tero@727: diff -r 8790e1e28661 etc/hosts/asdf.test tero@727: --- a/etc/hosts/asdf.test Tue Mar 03 13:05:13 2015 +0200 tero@727: +++ b/etc/hosts/asdf.test Tue Mar 03 13:06:03 2015 +0200 tero@727: @@ -1,2 +1,2 @@ tero@727: -[quux] tero@727: +[quux2] tero@727: ip = 192.0.2.5 tero@727: Using commit timestamp: 1425380763 tero@727: Updating forward host zones... tero@727: var/zones/forward/test: Generating forward hosts zone: etc/zones/forward/test tero@727: --- var/zones/forward/test 2015-03-03 13:04:09.556731909 +0200 tero@727: +++ var/zones/forward/test.new 2015-03-03 13:06:04.260731122 +0200 tero@727: @@ -2,2 +2,2 @@ tero@727: bar A 192.0.2.2 tero@727: -quux22.asdf A 192.0.2.5 tero@727: +quux2.asdf A 192.0.2.5 tero@727: Updating reverse host zones... tero@727: var/zones/reverse/192.0.2: Generating reverse hosts zone: etc/zones/reverse/192.0.2 tero@727: --- var/zones/reverse/192.0.2 2015-03-03 13:04:09.684731908 +0200 tero@727: +++ var/zones/reverse/192.0.2.new 2015-03-03 13:06:04.384731122 +0200 tero@727: @@ -2,2 +2,2 @@ tero@727: 2 PTR bar.test. tero@727: -5 PTR quux22.asdf.test. tero@727: +5 PTR quux2.asdf.test. tero@727: Updating DHCP hosts... tero@727: Copying zone includes... tero@727: Updating zones... tero@727: var/serials/test: Update serial: 1425380649 <- 1425380763 tero@727: var/zones/test: Generate zone: etc/zones/test @ 1425380763 tero@727: --- var/zones/test 2015-03-03 13:04:09.812731907 +0200 tero@727: +++ var/zones/test.new 2015-03-03 13:06:04.512731121 +0200 tero@727: @@ -1,3 +1,3 @@ tero@727: $TTL 3600 tero@727: -@ SOA foo.test. hostmaster.test. 1425380649 1d 5m 10d 300 tero@727: +@ SOA foo.test. hostmaster.test. 1425380763 1d 5m 10d 300 tero@727: NS foo tero@727: Updating DHCP confs... tero@727: Testing zones... tero@727: Reload zones... tero@727: Reload zones tero@727: * Reloading domain name service... bind9 [ OK ] tero@727: Testing DHCP... tero@727: Reload DHCP... tero@727: Reload DHCP tero@727: isc-dhcp-server stop/waiting tero@727: isc-dhcp-server start/running, process 2839 tero@727: terom@557: tero@646: ## Output zone files terom@557: tero@646: The generated zone files can then be loaded by bind: terom@556: tero@646: $ cat var/zones/test tero@646: $TTL 3600 tero@646: @ SOA foo.test. hostmaster.test. 1425049508 1d 5m 10d 300 tero@646: NS foo tero@646: NS bar tero@646: $INCLUDE "./var/zones/forward/test" terom@579: tero@646: $ cat var/zones/forward/test tero@646: foo A 192.0.2.1 tero@646: bar A 192.0.2.2 tero@646: quux.asdf A 192.0.2.5 terom@601: tero@644: # *pvl-dns* tero@644: Low-level zonefile utilities. tero@644: tero@644: ## `bin/pvl.dns-process` tero@644: Process a zonefile to modify: tero@644: tero@644: * `SOA` record serial tero@644: * `$INCLUDE` paths tero@644: tero@644: $ bin/pvl.dns-process --serial $(date +%s) --include-path var/zones etc/zones/test tero@644: $TTL 3600 tero@644: @ SOA foo.test. hostmaster.test. 1425049088 1d 5m 10d 300 tero@644: NS foo tero@644: NS bar tero@644: $INCLUDE "var/zones/forward/test" tero@644: tero@644: ## `bin/pvl.dns-zone` tero@644: Load a zonefile and output any ZoneRecords that it contains, including `$GENERATE`ed and `$INCLUDE`ed records: tero@644: tero@644: $ bin/pvl.dns-zone --zone=test var/zones/test tero@644: @ 3600 SOA foo.test. hostmaster.test. 1425049248 1d 5m 10d 300 tero@644: @ 3600 NS foo tero@644: @ 3600 NS bar tero@644: foo 3600 A 192.0.2.1 tero@644: bar 3600 A 192.0.2.2 tero@644: quux.asdf 3600 A 192.0.2.5 tero@644: tero@644: tero@644: Optionally `--check-hosts` for dupliates `A`/`AAAA` records. tero@644: tero@644: Use `--reverse-prefix=192.0.2` to generate a reverse-dns zone from `A`/`AAAA` records: tero@644: tero@644: $ bin/pvl.dns-zone --zone=test var/zones/test --reverse-prefix=192.0.2 tero@644: 1 PTR foo.test. tero@644: 2 PTR bar.test. tero@644: 5 PTR quux.asdf.test. tero@644: terom@634: # Experimental features terom@601: tero@522: Features that are still under development terom@601: tero@522: * DHCP host status tracking from syslog/dhcpd.leases into a database tero@522: * SNMP network topology discovery