• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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