blob: d84f0cbaed759dc593a337b6b380e166e2ba5952 [file] [log] [blame]
Wei-Yu Chenbd495ba2021-08-31 19:46:35 +08001#!/usr/bin/env python3
2
3# SPDX-FileCopyrightText: © 2021 Open Networking Foundation <support@opennetworking.org>
4# SPDX-License-Identifier: Apache-2.0
5
6# service.py
7#
8
9import re
10import netaddr
11
12from .utils import logger, AttrDict
13from .container import ServiceInfoContainer
14
15
16def getIPaddress(addressWithMask):
17 return str(netaddr.IPNetwork(addressWithMask).ip)
18
19
20def dhcpSubnetConfigGenerator():
21 dhcpSubnetConfigs = list()
22
23 serviceInfoContainer = ServiceInfoContainer()
24 serviceInfoContainer.initialize()
25
26 for domainName, domain in serviceInfoContainer.all():
27 subnetConfig = {
28 "dns_search": [domainName],
29 "dns_servers": [domain["dnsServer"]["address"]],
30 "ntp_servers": [domain["ntpServer"]["address"]],
31 "tftp_servers": domain["dhcpServer"]["address"],
32 "range": domain["dhcprange"],
33 "subnet": domain["subnet"],
34 "routers": [{"ip": domain["router"]}],
35 "hosts": list(),
36 }
37
38 for address, host in domain["hosts"].items():
39 subnetConfig["hosts"].append(
40 {
41 "ip_addr": getIPaddress(address),
42 "mac_addr": host["macaddr"],
43 "name": host["hostname"],
44 }
45 )
46
47 subnetConfig["hosts"] = sorted(
48 subnetConfig["hosts"], key=lambda x: int(x["ip_addr"].split(".")[-1])
49 )
50 dhcpSubnetConfigs.append(subnetConfig)
51
52 return dhcpSubnetConfigs
53
54
55def dnsFowardZoneConfigGenerator():
56 def getDomainNameByIP(ip_address):
57 """
58 getDomainNameByIP will return the corresponding domain name of an IP address
59 In the ntpServer, dhcpServer, dnsServer we only have the IP addresses of them,
60 But we can use the dhcp subnet configuration to find the FQDN
61 """
62 dhcpSubnetConfigs = dhcpSubnetConfigGenerator()
63
64 for domain in dhcpSubnetConfigs:
65 domainName = domain["dns_search"][0]
66 for host in domain["hosts"]:
67 if ip_address == host["ip_addr"]:
68 return f"{host['name']}.{domainName}."
69
70
71 dnsForwardZoneConfigs = dict()
72
73 serviceInfoContainer = ServiceInfoContainer()
74 serviceInfoContainer.initialize()
75
76 for domainName, domain in serviceInfoContainer.all():
77 forwardZoneConfig = {
78 "cname": dict(),
79 "a": dict(),
80 "ns": list(),
81 "srv": dict(),
82 "txt": dict(),
83 }
84
85 # Get the services set to this Tenant network
86 ntpServer = domain["ntpServer"] or None
87 dhcpServer = domain["dhcpServer"] or None
88 dnsServer = domain["dnsServer"] or None
89
90 # If service exists, set the FQDN to CNAME records
91 if ntpServer:
92 forwardZoneConfig["cname"]["dns"] = getDomainNameByIP(ntpServer["address"])
93 if dhcpServer:
94 forwardZoneConfig["cname"]["tftp"] = getDomainNameByIP(dhcpServer["address"])
95 if dnsServer:
96 forwardZoneConfig["cname"]["dns"] = getDomainNameByIP(dnsServer["address"])
97 forwardZoneConfig["ns"].append(getDomainNameByIP(dnsServer["address"]))
98
99 for address, host in domain["hosts"].items():
100 # Add exist IP address into dnsReverseZoneConfigs,
101 hostname = host["hostname"]
102 forwardZoneConfig["a"][hostname] = address
103
104 for address in netaddr.IPSet(domain["dhcprange"]):
105 # If address exists in ServiceInfoContainer's host dictionary,
106 # Use the pre-generated hostname as A record's name
107 hostname = "dhcp%03d" % address.words[-1]
108 forwardZoneConfig["a"][hostname] = str(address)
109
110 dnsForwardZoneConfigs[domainName] = forwardZoneConfig
111
112 return dnsForwardZoneConfigs
113
114
115def dnsReverseZoneConfigGenerator():
116 def canonicalize_rfc1918_prefix(prefix):
117 """
118 RFC1918 prefixes need to be expanded to their widest canonical range to
119 group all reverse lookup domains together for reverse DNS with NSD/Unbound.
120 """
121
122 pnet = netaddr.IPNetwork(prefix)
123 (o1, o2, o3, o4) = pnet.network.words # Split ipv4 octets
124 cidr_plen = pnet.prefixlen
125
126 if o1 == 10:
127 o2 = o3 = o4 = 0
128 cidr_plen = 8
129 elif (o1 == 172 and o2 >= 16 and o2 <= 31) or (o1 == 192 and o2 == 168):
130 o3 = o4 = 0
131 cidr_plen = 16
132
133 return "%s/%d" % (".".join(map(str, [o1, o2, o3, o4])), cidr_plen)
134
135 serviceInfoContainer = ServiceInfoContainer()
136 serviceInfoContainer.initialize()
137
138 # dnsReverseZoneConfigs contains all reverse zone records.
139 dnsReverseZoneConfigs = dict()
140 widedomain = None
141
142 for domainName, domain in serviceInfoContainer.all():
143
144 # Expand network range to group all tenant domains
145 widedomain = widedomain or canonicalize_rfc1918_prefix(domain["subnet"])
146
147 # Create the basic structure of reverse zone config
148 # {"10.0.0.0/8": {"ns": list(), "ptr": dict()}}
149 dnsReverseZoneConfigs.setdefault(widedomain, dict())
150 dnsReverseZoneConfigs[widedomain].setdefault("ns", list())
151 dnsReverseZoneConfigs[widedomain].setdefault("ptr", dict())
152
153 # Get the DNS services set to this Tenant network
154 dnsServer = domain["dnsServer"]["name"] if domain["dnsServer"] else None
155
156 # If service exists, set the FQDN to CNAME records
157 if dnsServer:
158 dnsReverseZoneConfigs[widedomain]["ns"].append(f"{domainName}.{dnsServer}.")
159
160 for address, host in domain["hosts"].items():
161 # Add exist IP address into dnsReverseZoneConfigs,
162 hostname = host["hostname"]
163 dnsReverseZoneConfigs[widedomain]["ptr"][
164 address
165 ] = f"{hostname}.{domainName}."
166
167 for address in netaddr.IPSet(domain["dhcprange"]):
168 # Add DHCP range IP address into dnsReverseZoneConfigs,
169 # Use the pre-generated hostname as A record's name
170 hostname = "dhcp%03d" % address.words[3]
171 dnsReverseZoneConfigs[widedomain]["ptr"][
172 str(address)
173 ] = f"{hostname}.{domainName}."
174
175 dnsReverseZoneConfigs[widedomain]["ptr"] = dict(
176 sorted(
177 dnsReverseZoneConfigs[widedomain]["ptr"].items(),
178 key=lambda x: (int(x[0].split(".")[-2]), int(x[0].split(".")[-1])),
179 )
180 )
181
182 return dnsReverseZoneConfigs