IP Address Management
Every network needs a structured way of managing its IP addresses. IP address information has a tendency to scatter all over the gaff. So many files it ends up in. I want to store my IP information in one place, and from there generate all the extra files where it needs to go. That one place is the Ansible Inventory.
We will need the following:
- DNS - Domain Name Services. We will need to support multiple domains and route requests for external domains to the Great Outdoors.
- DHCP - Dynamic Host Configuration Protocol. This is the component that gives everybody their IP addresses.
Things we will need to support include:
- Hosts with multiple IP addresses such as the OKD load balancer.
- Host aliases.
- Guest hosts that we will not configure into our inventory.
- Multiple network segments, physical or virtual. (Virtual KVM nets discussed elsewhere)
Design decisions
Since this is a small company or home office on Linux, I will for now assume all IP subnets are 24 bits in size, with netmask 255.255.255.0. If and when I expand this architecture to include larger or smaller subnets, then I will need to organise a file somewhere listing all the IP subnets and their netmasks, and maybe routers as well. For the same reason, I will assume the first IP address in any network to be the gateway, and the last fifteen IP addresses (240-254) to be reserved for DHCP guests.
DNS configuration
While the lighter dnsmasq server would probably be fine for my small network, the whole world and their dog uses bind, so we will be using Bind 9.16.23 as currently in the CentOS repos. For now, we will only supply IPV4 addresses, though we will be wanting to support IPV6 in the future.
Bind works with zone files - two kinds: Forward and Reverse. This is an example of a forward resolution zone file that translates names to numbers.
; Zone file: /var/named/db.nerdhole.me.uk
;----------------------------------------------------------------------
$TTL 3D
@ IN SOA sypha.nerdhole.me.uk. admin.nerdhole.me.uk. (
2024053001 ; Serial number
8H ; refresh, seconds
2H ; retry, seconds
4W ; expire, seconds
1D ) ; minimum, seconds
;
NS sypha ; Inet Address of name server
;
nerdhole.me.uk. IN MX 10 sypha ; Record pointing at mail server(s)
;
gateway A 10.12.0.1
sypha A 10.12.0.2
okdlb A 10.12.0.70
...
... rest of the A records
...
; Aliases
ns CNAME sypha ; Master name server
bis CNAME sypha ; Boot/install server
kerberos CNAME sypha ; Kerberos domain controller
ldap CNAME sypha ; LDAP server
pxe CNAME sypha ; PXE boot server
www CNAME sypha ; Web server
git CNAME sypha ; Git server
fs CNAME sypha ; File server
An A record binds a hostname to an IP address. The next section contains CNAME records, where we can assign an extra name to a host. In the example above, we could refer to Sypha as bis.nerdhole.me.uk, and we would get the IP address of Sypha (10.12.0.2). We will have to maintain a list of aliases in the inventory and use a Jinja template and some scripting to generate our zone file.
This is an example of a reverse zone file that translates numbers to names. We will have one zone file for each 24-bit address range (So 10.12.0.0/24, 10.12.1.0/24 etc).
; Zone file: /var/named/db.10.12.0
;----------------------------------------------------------------------
@ IN SOA sypha.nerdhole.me.uk. admin.nerdhole.me.uk. (
2024053001 ; Serial number
8H ; Refresh
2H ; Retry
4W ; Expire
1D) ; Minimum TTL
NS ns.nerdhole.me.uk.
1 PTR gateway.nerdhole.me.uk.
2 PTR sypha.nerdhole.me.uk.
70 PTR okdlb.nerdhole.me.uk.
...
... rest of the PTR records
...
Both files start with an IN SOA (Start Of Authority) record. It has the following information:
- Full name of the main name server.
- Mangled email address of the administrator. (Here, admin@nerdhole.me.uk)
- Serial: A serial number for the map. Will use the output of
date +%s
for this. - Refresh: How often secondaries query the primary name server for changes.
- Retry: How long they wait to retry if the above fails.
- Expire: How long they keep trying to reach the primary server before giving up and forgetting the domain.
- Time To Live: How long any record will remain in cache or memory.
The NS record has the full name of the name server for this domain.
What follows are the PTR records. Starts with the last number in the IP address, the keyword PTR, and then the full name of the machine the IP address belongs to.
If we simply drop these files into /var/named
, named won't use them. To add them to the server we need to generate a file named /etc/named.conf.local
. This file is not included from /etc/named.conf
by default, so we need to add a line at the end:
include "/etc/named.conf.local";
I also need to comment out or delete the following line to make named listen on every interfece rather than just on the loopback interface:
# listen-on port 53 { 127.0.0.1; };
After which I can leave named.conf alone. This is part of /etc/named.conf.local
:
// Local zone definitions
//----------------------------------------------------------------------
zone "nerdhole.me.uk" {
type master;
notify no;
file "/var/named/db.nerdhole.me.uk";
};
zone "0.12.10.in-addr.arpa" {
type master;
notify no;
file "/var/named/db.10.12.0";
};
The "zone" line specifies which DNS domain the zone file applies to. For a forward zonefile, this is simply the domain name (nerdhole.me.uk). For a reverse name lookup zonefile, the zone name is the IP address of the zone (10.12.0) in reverse, followed by .in-addr.arpa
. For mostly hysterical reasons. The next line "type master" means that this is the server where the original file lives and that all others are copies. Notify no means that at present, this server does not tell anyone if the zone changes. Finally, the "file" line specifies the file where the zone information is.
There are a few additional files that need to be generated for a new name server.
DHCP configuration
DHCP servers listen on the network for requests for IP addresses and hand these addresses out, along with other information such as which IP address the name server has, routing information, boot information, and more. This is am example of a DHCPD configuration file:
# NSCHOOL DHCP CONFIGURATION FILE
#----------------------------------------------------------------------
option domain-name "nerdhole.me.uk.";
option domain-name-servers 10.12.0.65;
use-host-decl-names on;
default-lease-time 600;
max-lease-time 7200;
authoritative;
# Define what several numbered DHCP options mean.
option space pxelinux;
option pxelinux.magic code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;
option architecture-type code 93 = unsigned integer 16;
# Send PCs or VMs to PXE boot
class "PCs" {
match if substring(option vendor-class-identifier, 0, 9) = "PXEClient";
next-server 10.12.0.65;
if option architecture-type = 00:07 {
filename = "efi/shimx64.efi";
}
else {
filename = "pxelinux/pxelinux.0";
}
}
# Define the subnet(s) served by this DHCP server
subnet 10.12.0.0 netmask 255.255.255.0 {
option routers 10.12.0.1;
range 10.12.0.200 10.12.0.215; # Guest machines
}
host paya { hardware ethernet 1c:69:7a:66:cd:f4; fixed-address 10.12.0.65; }
host sypha { hardware ethernet d8:9e:f3:91:8a:d8; fixed-address 10.12.0.2; }
host algernon { hardware ethernet 74:56:3c:3c:bd:bd; fixed-address 10.12.0.60; }
host emerald { hardware ethernet 44:8a:5b:98:92:99; fixed-address 10.12.0.58; }
host labo101 { hardware ethernet 52:54:ba:be:02:65; fixed-address 10.12.0.101; }
host labo102 { hardware ethernet 52:54:ba:be:02:66; fixed-address 10.12.0.102; }
... rest of the entries.
I will incorporate the generation of this file into the mkdnsserver script because it uses the same data.
Generating DHCP and DNS configurations
The configuration files involved in DHCP and DNS are too complicated to generate directly from Ansible - reversing IP addresses for reverse IP address resolution, converting addresses from one format to another is much too complicated for a playbook. I will use a Perl script to generate the actual /var/named and /etc/dhcp files from the Ansible inventory files. The format is:
[all]
algernon.nerdhole.me.uk main_ip=10.12.0.60 local_pxefile=0A0C003C macaddress=74:56:3c:3c:bd:bd
ariciel.nerdhole.me.uk main_ip=10.12.0.63 local_pxefile=0A0C003F macaddress=52:54:ba:be:01:02
bannog.nerdhole.me.uk main_ip=10.12.0.62 local_pxefile=0A0C003E macaddress=52:54:ba:be:01:01
emerald.nerdhole.me.uk main_ip=10.12.0.58 local_pxefile=0A0C003A macaddress=44:8a:5b:98:92:99
This is only slightly complicated by quoted strings being supported. I will not do this in Python. This calls for the awesome power of Perl. This is a design for the data involved in generating DNS configurations.
The script will be called mkdnsserver
but it will also generate the /etc/dhcp/dhcpd.conf. The DNShostsource object suports host variables, and the DNSserver object supports aliases, which it will configure in CNAME records in the zone files. Zone files are made on demand whenever the script finds a DNS domain it doesn't have yet. The main server will only serve DHCP records to machines in its own subnet. I'll have to create a separate role for DHCP servers in other subnets.
So where do I get my host aliases from? Passing them all to mkdnsserver on the command line is sub-optimal. It would be nice if a host could get an alias based on its host groups, but that would involve combining Ansible variables from all over the place, that might or might not be there. Frankly, at some point I will have to make a proper CMDB and have Ansible use it. Tomorrow.
So what I will do now is to create an /etc/hosts file for the main server that contains IP addresses, FQDNs and aliases, which I will put in a separate section of the nschool Ansible variable in the nschool role. Once that is generated, I can feed /etc/hosts to mkdnsserver and get the host aliases from there. As a side benefit, the main server will be able to resolve hosts even if the name server is down.
Will I implement the /etc/hosts input filter as a hostsource, or as a separate object? I think it is worthwhile to have several hostsources that supplement each other, so I will add host aliases to DNShostsource
, and implement DNSetchostsource
to read any /etc/host.