Support DHCPv6

pull/8/head
Jack O'Sullivan 5 years ago
parent ec198aad67
commit 644731410b

@ -17,6 +17,7 @@ from . import NetDhcpError, udhcpc, app
OPTS_KEY = 'com.docker.network.generic'
OPT_PREFIX = 'devplayer0.net-dhcp'
OPT_BRIDGE = f'{OPT_PREFIX}.bridge'
OPT_IPV6 = f'{OPT_PREFIX}.ipv6'
logger = logging.getLogger('gunicorn.error')
@ -57,7 +58,9 @@ def get_bridges():
def net_bridge(n):
return ndb.interfaces[client.networks.get(n).attrs['Options'][OPT_BRIDGE]]
def ipv6_enabled(n):
return client.networks.get(n).attrs['EnableIPv6']
options = client.networks.get(n).attrs['Options']
return OPT_IPV6 in options and options[OPT_IPV6] == 'true'
def endpoint_container_iface(n, e):
for cid, info in client.networks.get(n).attrs['Containers'].items():
if info['EndpointID'] == e:
@ -99,10 +102,15 @@ def net_get_capabilities():
@app.route('/NetworkDriver.CreateNetwork', methods=['POST'])
def create_net():
req = request.get_json(force=True)
if OPT_BRIDGE not in req['Options'][OPTS_KEY]:
options = req['Options'][OPTS_KEY]
if OPT_BRIDGE not in options:
return jsonify({'Err': 'No bridge provided'}), 400
# We have to use a custom "enable IPv6" option because Docker's null IPAM driver doesn't support IPv6 and a plugin
# IPAM driver isn't allowed to return an empty address
if OPT_IPV6 in options and options[OPT_IPV6] not in ('', 'true', 'false'):
return jsonify({'Err': 'Invalid boolean value for ipv6'}), 400
desired = req['Options'][OPTS_KEY][OPT_BRIDGE]
desired = options[OPT_BRIDGE]
bridges = get_bridges()
if desired not in bridges:
return jsonify({'Err': f'Bridge "{desired}" not found (or the specified bridge is already used by Docker)'}), 400
@ -156,14 +164,16 @@ def create_endpoint():
for bridge_addr in bridge_addrs:
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, once=True)
addr = dhcp.finish()
res_iface['Address'] = str(addr)
gateway_hints[endpoint_id] = dhcp.gateway
else:
raise NetDhcpError(400, f'DHCPv6 is currently unsupported')
logger.info('Adding address %s to %s', addr, if_container['ifname'])
dhcp = udhcpc.DHCPClient(if_container, v6=type_ == 'v6', once=True)
addr = dhcp.finish()
if not addr:
return
res_iface[k] = str(addr)
if dhcp.gateway:
gateway_hints[endpoint_id] = dhcp.gateway
logger.info('Adding IP%s address %s to %s', type_, addr, if_container['ifname'])
try_addr('v4')
if ipv6_enabled(network_id):
@ -236,7 +246,8 @@ def join():
},
'StaticRoutes': []
}
if endpoint in gateway_hints and gateway_hints[endpoint]:
if endpoint in gateway_hints:
gateway = gateway_hints[endpoint]
logger.info('Setting IPv4 gateway from DHCP (%s)', gateway)
res['Gateway'] = str(gateway)
@ -288,6 +299,7 @@ class ContainerDHCPManager:
self.ipv6 = ipv6_enabled(network)
self.dhcp = None
self.dhcp6 = None
self._thread = threading.Thread(target=self.run)
self._thread.start()
@ -323,9 +335,15 @@ class ContainerDHCPManager:
def run(self):
try:
iface = await_endpoint_container_iface(self.network, self.endpoint)
self.dhcp = udhcpc.DHCPClient(iface, event_listener=self._on_event)
logger.info('Starting DHCP client on %s in container namespace %s', iface['ifname'], \
logger.info('Starting DHCPv4 client on %s in container namespace %s', iface['ifname'], \
self.dhcp.netns)
if self.ipv6:
self.dhcp6 = udhcpc.DHCPClient(iface, v6=True, event_listener=self._on_event)
logger.info('Starting DHCPv6 client on %s in container namespace %s', iface['ifname'], \
self.dhcp6.netns)
except Exception as e:
logger.exception(e)
@ -333,8 +351,14 @@ class ContainerDHCPManager:
if not self.dhcp:
return
logger.info('Shutting down DHCP client on %s in container namespace %s', \
logger.info('Shutting down DHCPv4 client on %s in container namespace %s', \
self.dhcp.iface['ifname'], self.dhcp.netns)
self.dhcp.finish(timeout=1)
if self.ipv6:
logger.info('Shutting down DHCPv6 client on %s in container namespace %s', \
self.dhcp6.iface['ifname'], self.dhcp.netns)
self.dhcp6.finish(timeout=1)
ndb.sources.remove(self.dhcp.netns)
self._thread.join()

@ -29,8 +29,9 @@ class DHCPClientError(Exception):
def _nspopen_wrapper(netns):
return lambda *args, **kwargs: NSPopen(netns, *args, **kwargs)
class DHCPClient:
def __init__(self, iface, once=False, event_listener=None):
def __init__(self, iface, v6=False, once=False, event_listener=None):
self.iface = iface
self.v6 = v6
self.once = once
self.event_listeners = [DHCPClient._attr_listener]
if event_listener:
@ -42,10 +43,12 @@ class DHCPClient:
logger.debug('udhcpc using netns %s', self.netns)
Popen = _nspopen_wrapper(self.netns) if self.netns else subprocess.Popen
cmdline = ['/sbin/udhcpc', '-s', HANDLER_SCRIPT, '-i', iface['ifname'], '-f']
bin_path = '/usr/bin/udhcpc6' if v6 else '/sbin/udhcpc'
cmdline = [bin_path, '-s', HANDLER_SCRIPT, '-i', iface['ifname'], '-f']
cmdline.append('-q' if once else '-R')
self._event_queue = posix_ipc.MessageQueue(f'/udhcpc_{iface["address"].replace(":", "_")}', \
self._suffix = '6' if v6 else ''
self._event_queue = posix_ipc.MessageQueue(f'/udhcpc{self._suffix}_{iface["address"].replace(":", "_")}', \
flags=os.O_CREAT | os.O_EXCL)
self.proc = Popen(cmdline, env={'EVENT_QUEUE': self._event_queue.name})
@ -84,10 +87,10 @@ class DHCPClient:
try:
event['type'] = EventType(event['type'])
except ValueError:
logger.warning('udhcpc#%d unknown event "%s"', self.proc.pid, event)
logger.warning('udhcpc%s#%d unknown event "%s"', self._suffix, self.proc.pid, event)
continue
logger.debug('[udhcp#%d event] %s', self.proc.pid, event)
logger.debug('[udhcp%s#%d event] %s', self._suffix, self.proc.pid, event)
for listener in self.event_listeners:
try:
listener(self, event['type'], event)
@ -96,7 +99,7 @@ class DHCPClient:
def await_ip(self, timeout=10):
if not self._has_lease.wait(timeout=timeout):
raise DHCPClientError('Timed out waiting for dhcp lease')
raise DHCPClientError(f'Timed out waiting for lease from udhcpc{self._suffix}')
return self.ip
@ -105,14 +108,14 @@ class DHCPClient:
return
if self.proc.returncode != None and (not self.once or self.proc.returncode != 0):
raise DHCPClientError(f'udhcpc exited early with code {self.proc.returncode}')
raise DHCPClientError(f'udhcpc{self._suffix} exited early with code {self.proc.returncode}')
if self.once:
self.await_ip()
else:
self.proc.terminate()
if self.proc.wait(timeout=timeout) != 0:
raise DHCPClientError(f'udhcpc exited with non-zero exit code {self.proc.returncode}')
raise DHCPClientError(f'udhcpc{self._suffix} exited with non-zero exit code {self.proc.returncode}')
if self.netns:
self.proc.release()

@ -11,11 +11,14 @@ if __name__ != '__main__':
event = {'type': sys.argv[1]}
if event['type'] in ('bound', 'renew'):
event['ip'] = f'{env["ip"]}/{env["mask"]}'
if 'router' in env:
event['gateway'] = env['router']
if 'domain' in env:
event['domain'] = env['domain']
if 'ipv6' in env:
event['ip'] = env['ipv6']
else:
event['ip'] = f'{env["ip"]}/{env["mask"]}'
if 'router' in env:
event['gateway'] = env['router']
if 'domain' in env:
event['domain'] = env['domain']
elif event['type'] in ('deconfig', 'leasefail', 'nak'):
pass
else:

Loading…
Cancel
Save