diff --git a/net-dhcp/network.py b/net-dhcp/network.py index 2abcdf1..515c144 100644 --- a/net-dhcp/network.py +++ b/net-dhcp/network.py @@ -27,6 +27,17 @@ client = docker.from_env() def close_docker(): client.close() +host_dhcp_clients = {} +container_dhcp_clients = {} +@atexit.register +def cleanup_dhcp(): + for endpoint, dhcp in host_dhcp_clients.items(): + logger.warning('cleaning up orphaned host DHCP client (endpoint "%s")', endpoint) + dhcp.finish(timeout=1) + for endpoint, dhcp in container_dhcp_clients.items(): + logger.warning('cleaning up orphaned container DHCP client (endpoint "%s")', endpoint) + dhcp.finish(timeout=1) + def veth_pair(e): return f'dh-{e[:12]}', f'{e[:12]}-dh' @@ -114,10 +125,10 @@ def create_endpoint(): if addr.ip == bridge_addr.ip: raise NetDhcpError(400, f'Address {addr} is already in use on bridge {bridge["ifname"]}') elif type_ == 'v4': - dhcp = udhcpc.DHCPClient(if_container['ifname'], once=True) - dhcp.finish() - addr = dhcp.ip + dhcp = udhcpc.DHCPClient(if_container['ifname']) + addr = dhcp.await_ip(timeout=10) res_iface['Address'] = str(addr) + host_dhcp_clients[req['EndpointID']] = dhcp else: raise NetDhcpError(400, f'DHCPv6 is currently unsupported') logger.info('Adding address %s to %s', addr, if_container['ifname']) @@ -180,6 +191,7 @@ def delete_endpoint(): @app.route('/NetworkDriver.Join', methods=['POST']) def join(): req = request.get_json(force=True) + endpoint = req['EndpointID'] bridge = net_bridge(req['NetworkID']) _if_host, if_container = veth_pair(req['EndpointID']) @@ -191,6 +203,13 @@ def join(): }, 'StaticRoutes': [] } + if endpoint in host_dhcp_clients: + dhcp = host_dhcp_clients[endpoint] + logger.info('Setting IPv4 gateway from DHCP (%s)', dhcp.gateway) + res['Gateway'] = str(dhcp.gateway) + dhcp.finish(timeout=1) + del host_dhcp_clients[endpoint] + ipv6 = ipv6_enabled(req['NetworkID']) for route in bridge.routes: if route['type'] != rtypes['RTN_UNICAST'] or (route['family'] == socket.AF_INET6 and not ipv6): diff --git a/net-dhcp/udhcpc.py b/net-dhcp/udhcpc.py index 6c99e6c..c1a4b55 100644 --- a/net-dhcp/udhcpc.py +++ b/net-dhcp/udhcpc.py @@ -1,6 +1,7 @@ from enum import Enum import ipaddress from os import path +import time import threading import subprocess import logging @@ -9,6 +10,7 @@ from pyroute2.netns.process.proxy import NSPopen INFO_PREFIX = '__info' HANDLER_SCRIPT = path.join(path.dirname(__file__), 'udhcpc_handler.py') +AWAIT_INTERVAL = 0.1 class EventType(Enum): BOUND = 'bound' @@ -62,12 +64,22 @@ class DHCPClient: logger.debug('[udhcp event] %s %s %s %s', event_type, self.ip, self.gateway, self.domain) self.event_listener(event_type, self.ip, self.gateway, self.domain) - def finish(self): + def await_ip(self, timeout=5): + # TODO: this bad + waited = 0 + while not self.ip: + if waited >= timeout: + raise DHCPClientError('Timed out waiting for dhcp lease') + time.sleep(AWAIT_INTERVAL) + waited += AWAIT_INTERVAL + return self.ip + + def finish(self, timeout=5): if not self.once: self.proc.terminate() - if self.proc.wait(timeout=10) != 0: + if self.proc.wait(timeout=timeout) != 0: raise DHCPClientError(f'udhcpc exited with non-zero exit code {self.proc.returncode}') self._event_thread.join() if self.once and not self.ip: - raise DHCPClientError(f'failed to obtain lease') + raise DHCPClientError(f'Timed out waiting for dhcp lease')