mirror of
https://github.com/devplayer0/docker-net-dhcp
synced 2024-11-16 21:28:01 +00:00
Support DHCPv6
This commit is contained in:
parent
ec198aad67
commit
644731410b
@ -17,6 +17,7 @@ from . import NetDhcpError, udhcpc, app
|
|||||||
OPTS_KEY = 'com.docker.network.generic'
|
OPTS_KEY = 'com.docker.network.generic'
|
||||||
OPT_PREFIX = 'devplayer0.net-dhcp'
|
OPT_PREFIX = 'devplayer0.net-dhcp'
|
||||||
OPT_BRIDGE = f'{OPT_PREFIX}.bridge'
|
OPT_BRIDGE = f'{OPT_PREFIX}.bridge'
|
||||||
|
OPT_IPV6 = f'{OPT_PREFIX}.ipv6'
|
||||||
|
|
||||||
logger = logging.getLogger('gunicorn.error')
|
logger = logging.getLogger('gunicorn.error')
|
||||||
|
|
||||||
@ -57,7 +58,9 @@ def get_bridges():
|
|||||||
def net_bridge(n):
|
def net_bridge(n):
|
||||||
return ndb.interfaces[client.networks.get(n).attrs['Options'][OPT_BRIDGE]]
|
return ndb.interfaces[client.networks.get(n).attrs['Options'][OPT_BRIDGE]]
|
||||||
def ipv6_enabled(n):
|
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):
|
def endpoint_container_iface(n, e):
|
||||||
for cid, info in client.networks.get(n).attrs['Containers'].items():
|
for cid, info in client.networks.get(n).attrs['Containers'].items():
|
||||||
if info['EndpointID'] == e:
|
if info['EndpointID'] == e:
|
||||||
@ -99,10 +102,15 @@ def net_get_capabilities():
|
|||||||
@app.route('/NetworkDriver.CreateNetwork', methods=['POST'])
|
@app.route('/NetworkDriver.CreateNetwork', methods=['POST'])
|
||||||
def create_net():
|
def create_net():
|
||||||
req = request.get_json(force=True)
|
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
|
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()
|
bridges = get_bridges()
|
||||||
if desired not in bridges:
|
if desired not in bridges:
|
||||||
return jsonify({'Err': f'Bridge "{desired}" not found (or the specified bridge is already used by Docker)'}), 400
|
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:
|
for bridge_addr in bridge_addrs:
|
||||||
if addr.ip == bridge_addr.ip:
|
if addr.ip == bridge_addr.ip:
|
||||||
raise NetDhcpError(400, f'Address {addr} is already in use on bridge {bridge["ifname"]}')
|
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:
|
else:
|
||||||
raise NetDhcpError(400, f'DHCPv6 is currently unsupported')
|
dhcp = udhcpc.DHCPClient(if_container, v6=type_ == 'v6', once=True)
|
||||||
logger.info('Adding address %s to %s', addr, if_container['ifname'])
|
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')
|
try_addr('v4')
|
||||||
if ipv6_enabled(network_id):
|
if ipv6_enabled(network_id):
|
||||||
@ -236,7 +246,8 @@ def join():
|
|||||||
},
|
},
|
||||||
'StaticRoutes': []
|
'StaticRoutes': []
|
||||||
}
|
}
|
||||||
if endpoint in gateway_hints and gateway_hints[endpoint]:
|
|
||||||
|
if endpoint in gateway_hints:
|
||||||
gateway = gateway_hints[endpoint]
|
gateway = gateway_hints[endpoint]
|
||||||
logger.info('Setting IPv4 gateway from DHCP (%s)', gateway)
|
logger.info('Setting IPv4 gateway from DHCP (%s)', gateway)
|
||||||
res['Gateway'] = str(gateway)
|
res['Gateway'] = str(gateway)
|
||||||
@ -288,6 +299,7 @@ class ContainerDHCPManager:
|
|||||||
self.ipv6 = ipv6_enabled(network)
|
self.ipv6 = ipv6_enabled(network)
|
||||||
|
|
||||||
self.dhcp = None
|
self.dhcp = None
|
||||||
|
self.dhcp6 = None
|
||||||
self._thread = threading.Thread(target=self.run)
|
self._thread = threading.Thread(target=self.run)
|
||||||
self._thread.start()
|
self._thread.start()
|
||||||
|
|
||||||
@ -323,9 +335,15 @@ class ContainerDHCPManager:
|
|||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
iface = await_endpoint_container_iface(self.network, self.endpoint)
|
iface = await_endpoint_container_iface(self.network, self.endpoint)
|
||||||
|
|
||||||
self.dhcp = udhcpc.DHCPClient(iface, event_listener=self._on_event)
|
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)
|
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:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
@ -333,8 +351,14 @@ class ContainerDHCPManager:
|
|||||||
if not self.dhcp:
|
if not self.dhcp:
|
||||||
return
|
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.iface['ifname'], self.dhcp.netns)
|
||||||
self.dhcp.finish(timeout=1)
|
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)
|
ndb.sources.remove(self.dhcp.netns)
|
||||||
self._thread.join()
|
self._thread.join()
|
||||||
|
@ -29,8 +29,9 @@ class DHCPClientError(Exception):
|
|||||||
def _nspopen_wrapper(netns):
|
def _nspopen_wrapper(netns):
|
||||||
return lambda *args, **kwargs: NSPopen(netns, *args, **kwargs)
|
return lambda *args, **kwargs: NSPopen(netns, *args, **kwargs)
|
||||||
class DHCPClient:
|
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.iface = iface
|
||||||
|
self.v6 = v6
|
||||||
self.once = once
|
self.once = once
|
||||||
self.event_listeners = [DHCPClient._attr_listener]
|
self.event_listeners = [DHCPClient._attr_listener]
|
||||||
if event_listener:
|
if event_listener:
|
||||||
@ -42,10 +43,12 @@ class DHCPClient:
|
|||||||
logger.debug('udhcpc using netns %s', self.netns)
|
logger.debug('udhcpc using netns %s', self.netns)
|
||||||
|
|
||||||
Popen = _nspopen_wrapper(self.netns) if self.netns else subprocess.Popen
|
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')
|
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)
|
flags=os.O_CREAT | os.O_EXCL)
|
||||||
self.proc = Popen(cmdline, env={'EVENT_QUEUE': self._event_queue.name})
|
self.proc = Popen(cmdline, env={'EVENT_QUEUE': self._event_queue.name})
|
||||||
|
|
||||||
@ -84,10 +87,10 @@ class DHCPClient:
|
|||||||
try:
|
try:
|
||||||
event['type'] = EventType(event['type'])
|
event['type'] = EventType(event['type'])
|
||||||
except ValueError:
|
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
|
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:
|
for listener in self.event_listeners:
|
||||||
try:
|
try:
|
||||||
listener(self, event['type'], event)
|
listener(self, event['type'], event)
|
||||||
@ -96,7 +99,7 @@ class DHCPClient:
|
|||||||
|
|
||||||
def await_ip(self, timeout=10):
|
def await_ip(self, timeout=10):
|
||||||
if not self._has_lease.wait(timeout=timeout):
|
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
|
return self.ip
|
||||||
|
|
||||||
@ -105,14 +108,14 @@ class DHCPClient:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self.proc.returncode != None and (not self.once or self.proc.returncode != 0):
|
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:
|
if self.once:
|
||||||
self.await_ip()
|
self.await_ip()
|
||||||
else:
|
else:
|
||||||
self.proc.terminate()
|
self.proc.terminate()
|
||||||
|
|
||||||
if self.proc.wait(timeout=timeout) != 0:
|
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:
|
if self.netns:
|
||||||
self.proc.release()
|
self.proc.release()
|
||||||
|
|
||||||
|
@ -11,11 +11,14 @@ if __name__ != '__main__':
|
|||||||
|
|
||||||
event = {'type': sys.argv[1]}
|
event = {'type': sys.argv[1]}
|
||||||
if event['type'] in ('bound', 'renew'):
|
if event['type'] in ('bound', 'renew'):
|
||||||
event['ip'] = f'{env["ip"]}/{env["mask"]}'
|
if 'ipv6' in env:
|
||||||
if 'router' in env:
|
event['ip'] = env['ipv6']
|
||||||
event['gateway'] = env['router']
|
else:
|
||||||
if 'domain' in env:
|
event['ip'] = f'{env["ip"]}/{env["mask"]}'
|
||||||
event['domain'] = env['domain']
|
if 'router' in env:
|
||||||
|
event['gateway'] = env['router']
|
||||||
|
if 'domain' in env:
|
||||||
|
event['domain'] = env['domain']
|
||||||
elif event['type'] in ('deconfig', 'leasefail', 'nak'):
|
elif event['type'] in ('deconfig', 'leasefail', 'nak'):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
Loading…
Reference in New Issue
Block a user