1# Copyright 2016 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import collections 16import copy 17import ipaddress 18 19_ROUTER_DNS = '8.8.8.8, 4.4.4.4' 20 21 22class Subnet(object): 23 """Configs for a subnet on the dhcp server. 24 25 Attributes: 26 network: ipaddress.IPv4Network, the network that this subnet is in. 27 start: ipaddress.IPv4Address, the start ip address. 28 end: ipaddress.IPv4Address, the end ip address. 29 router: The router to give to all hosts in this subnet. 30 lease_time: The lease time of all hosts in this subnet. 31 additional_parameters: A dictionary corresponding to DHCP parameters. 32 additional_options: A dictionary corresponding to DHCP options. 33 """ 34 35 def __init__(self, 36 subnet, 37 start=None, 38 end=None, 39 router=None, 40 lease_time=None, 41 additional_parameters={}, 42 additional_options={}): 43 """ 44 Args: 45 subnet: ipaddress.IPv4Network, The address space of the subnetwork 46 served by the DHCP server. 47 start: ipaddress.IPv4Address, The start of the address range to 48 give hosts in this subnet. If not given, the second ip in 49 the network is used, under the assumption that the first 50 address is the router. 51 end: ipaddress.IPv4Address, The end of the address range to give 52 hosts. If not given then the address prior to the broadcast 53 address (i.e. the second to last ip in the network) is used. 54 router: ipaddress.IPv4Address, The router hosts should use in this 55 subnet. If not given the first ip in the network is used. 56 lease_time: int, The amount of lease time in seconds 57 hosts in this subnet have. 58 additional_parameters: A dictionary corresponding to DHCP parameters. 59 additional_options: A dictionary corresponding to DHCP options. 60 """ 61 self.network = subnet 62 63 if start: 64 self.start = start 65 else: 66 self.start = self.network[2] 67 68 if not self.start in self.network: 69 raise ValueError('The start range is not in the subnet.') 70 if self.start.is_reserved: 71 raise ValueError('The start of the range cannot be reserved.') 72 73 if end: 74 self.end = end 75 else: 76 self.end = self.network[-2] 77 78 if not self.end in self.network: 79 raise ValueError('The end range is not in the subnet.') 80 if self.end.is_reserved: 81 raise ValueError('The end of the range cannot be reserved.') 82 if self.end < self.start: 83 raise ValueError( 84 'The end must be an address larger than the start.') 85 86 if router: 87 if router >= self.start and router <= self.end: 88 raise ValueError('Router must not be in pool range.') 89 if not router in self.network: 90 raise ValueError('Router must be in the given subnet.') 91 92 self.router = router 93 else: 94 # TODO: Use some more clever logic so that we don't have to search 95 # every host potentially. 96 # This is especially important if we support IPv6 networks in this 97 # configuration. The improved logic that we can use is: 98 # a) erroring out if start and end encompass the whole network, and 99 # b) picking any address before self.start or after self.end. 100 self.router = None 101 for host in self.network.hosts(): 102 if host < self.start or host > self.end: 103 self.router = host 104 break 105 106 if not self.router: 107 raise ValueError('No useable host found.') 108 109 self.lease_time = lease_time 110 self.additional_parameters = additional_parameters 111 self.additional_options = additional_options 112 if 'domain-name-servers' not in self.additional_options: 113 self.additional_options['domain-name-servers'] = _ROUTER_DNS 114 115 116class StaticMapping(object): 117 """Represents a static dhcp host. 118 119 Attributes: 120 identifier: How id of the host (usually the mac addres 121 e.g. 00:11:22:33:44:55). 122 address: ipaddress.IPv4Address, The ipv4 address to give the host. 123 lease_time: How long to give a lease to this host. 124 """ 125 126 def __init__(self, identifier, address, lease_time=None): 127 self.identifier = identifier 128 self.ipv4_address = address 129 self.lease_time = lease_time 130 131 132class DhcpConfig(object): 133 """The configs for a dhcp server. 134 135 Attributes: 136 subnets: A list of all subnets for the dhcp server to create. 137 static_mappings: A list of static host addresses. 138 default_lease_time: The default time for a lease. 139 max_lease_time: The max time to allow a lease. 140 """ 141 142 def __init__(self, 143 subnets=None, 144 static_mappings=None, 145 default_lease_time=600, 146 max_lease_time=7200): 147 self.subnets = copy.deepcopy(subnets) if subnets else [] 148 self.static_mappings = (copy.deepcopy(static_mappings) 149 if static_mappings else []) 150 self.default_lease_time = default_lease_time 151 self.max_lease_time = max_lease_time 152 153 def render_config_file(self): 154 """Renders the config parameters into a format compatible with 155 the ISC DHCP server (dhcpd). 156 """ 157 lines = [] 158 159 if self.default_lease_time: 160 lines.append('default-lease-time %d;' % self.default_lease_time) 161 if self.max_lease_time: 162 lines.append('max-lease-time %s;' % self.max_lease_time) 163 164 for subnet in self.subnets: 165 address = subnet.network.network_address 166 mask = subnet.network.netmask 167 router = subnet.router 168 start = subnet.start 169 end = subnet.end 170 lease_time = subnet.lease_time 171 additional_parameters = subnet.additional_parameters 172 additional_options = subnet.additional_options 173 174 lines.append('subnet %s netmask %s {' % (address, mask)) 175 lines.append('\tpool {') 176 lines.append('\t\toption subnet-mask %s;' % mask) 177 lines.append('\t\toption routers %s;' % router) 178 lines.append('\t\trange %s %s;' % (start, end)) 179 if lease_time: 180 lines.append('\t\tdefault-lease-time %d;' % lease_time) 181 lines.append('\t\tmax-lease-time %d;' % lease_time) 182 for param, value in additional_parameters.items(): 183 lines.append('\t\t%s %s;' % (param, value)) 184 for option, value in additional_options.items(): 185 lines.append('\t\toption %s %s;' % (option, value)) 186 lines.append('\t}') 187 lines.append('}') 188 189 for mapping in self.static_mappings: 190 identifier = mapping.identifier 191 fixed_address = mapping.ipv4_address 192 host_fake_name = 'host%s' % identifier.replace(':', '') 193 lease_time = mapping.lease_time 194 195 lines.append('host %s {' % host_fake_name) 196 lines.append('\thardware ethernet %s;' % identifier) 197 lines.append('\tfixed-address %s;' % fixed_address) 198 if lease_time: 199 lines.append('\tdefault-lease-time %d;' % lease_time) 200 lines.append('\tmax-lease-time %d;' % lease_time) 201 lines.append('}') 202 203 config_str = '\n'.join(lines) 204 205 return config_str 206