Fix container DHCP without gateway

This commit is contained in:
Jack O'Sullivan 2019-08-26 10:21:32 +01:00
parent 9887cfab30
commit ab8336a5cb

View File

@ -3,6 +3,7 @@ import ipaddress
import logging
import atexit
import socket
import time
import threading
import pyroute2
@ -61,12 +62,31 @@ def endpoint_container_iface(n, e):
if info['EndpointID'] == e:
container = client.containers.get(cid)
netns = f'/proc/{container.attrs["State"]["Pid"]}/ns/net'
ndb.sources.add(netns=netns)
already = False
for source in ndb.sources:
if source == netns:
already = True
break
if not already:
ndb.sources.add(netns=netns)
for i in ndb.interfaces:
if i['address'] == info['MacAddress']:
return i
break
return None
def await_endpoint_container_iface(n, e, timeout=5):
start = time.time()
iface = None
while time.time() - start < timeout:
try:
iface = endpoint_container_iface(n, e)
except docker.errors.NotFound:
time.sleep(0.5)
if not iface:
raise NetDhcpError('Timed out waiting for container to become availabile')
return iface
@app.route('/NetworkDriver.GetCapabilities', methods=['POST'])
def net_get_capabilities():
@ -202,6 +222,7 @@ def delete_endpoint():
@app.route('/NetworkDriver.Join', methods=['POST'])
def join():
req = request.get_json(force=True)
network = req['NetworkID']
endpoint = req['EndpointID']
bridge = net_bridge(req['NetworkID'])
@ -220,7 +241,7 @@ def join():
res['Gateway'] = str(gateway)
del gateway_hints[endpoint]
ipv6 = ipv6_enabled(req['NetworkID'])
ipv6 = ipv6_enabled(network)
for route in bridge.routes:
if route['type'] != rtypes['RTN_UNICAST'] or \
(route['family'] == socket.AF_INET6 and not ipv6):
@ -242,24 +263,34 @@ def join():
'NextHop': route['gateway']
})
container_dhcp_clients[endpoint] = ContainerDHCPManager(network, endpoint)
return jsonify(res)
@app.route('/NetworkDriver.Leave', methods=['POST'])
def leave():
req = request.get_json(force=True)
endpoint = req['EndpointID']
if endpoint in container_dhcp_clients:
container_dhcp_clients[endpoint].stop()
del container_dhcp_clients[endpoint]
return jsonify({})
# Trying to grab the container's attributes (to get the network namespace)
# will deadlock, so we must defer starting the DHCP client
# will deadlock (since Docker is waiting on us), so we must defer starting
# the DHCP client
class ContainerDHCPManager:
def __init__(self, network, endpoint):
self.network = network
self.endpoint = endpoint
self.ipv6 = ipv6_enabled(network)
self.dhcp = None
self._thread = threading.Thread(target=self.run)
self._thread.start()
def _on_event(self, dhcp, event_type, _args):
def _on_event(self, dhcp, event_type, _event):
if event_type != udhcpc.EventType.RENEW:
return
@ -281,36 +312,20 @@ class ContainerDHCPManager:
.commit())
def run(self):
iface = endpoint_container_iface(self.network, self.endpoint)
self.dhcp = udhcpc.DHCPClient(iface)
logger.info('Starting DHCP client on %s in container namespace %s', iface['ifname'], \
self.dhcp.netns)
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'], \
self.dhcp.netns)
except Exception as e:
logger.exception(e)
def stop(self):
if not self.dhcp:
return
logger.info('Shutting down DHCP client on %s in container namespace %s', \
self.dhcp.iface['ifname'], self.dhcp.netns)
self.dhcp.finish(timeout=1)
ndb.sources.remove(self.dhcp.netns)
self._thread.join()
# ProgramExternalActivity is supposed to be used for port forwarding etc.,
# but we can use it to start the DHCP client in the container's network namespace
# since the interface will have been moved inside at this point.
@app.route('/NetworkDriver.ProgramExternalConnectivity', methods=['POST'])
def start_container_dhcp():
req = request.get_json(force=True)
endpoint = req['EndpointID']
container_dhcp_clients[endpoint] = ContainerDHCPManager(req['NetworkID'], endpoint)
return jsonify({})
@app.route('/NetworkDriver.RevokeExternalConnectivity', methods=['POST'])
def stop_container_dhcp():
req = request.get_json(force=True)
endpoint = req['EndpointID']
if endpoint in container_dhcp_clients:
container_dhcp_clients[endpoint].stop()
del container_dhcp_clients[endpoint]
return jsonify({})