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