import ipaddress import random import flask import pyroute2 from docker_plugin_api.NetworkDriverEntities import * from docker_plugin_api.Plugin import Blueprint, InputValidationException from .NetworkDriverData import * app = Blueprint('NetworkDriver', __name__) def genid(size=8, chars='0123456789abcdef'): return ''.join([random.choice(chars) for _ in range(size)]) def create_interface(endpoint, network) -> str: ifname0 = 'veth{}'.format(genid()) ifname1 = 'veth{}'.format(genid()) with pyroute2.IPRoute() as ip: ip.link('add', ifname=ifname0, peer=ifname1, kind='veth') idx = ip.link_lookup(ifname=ifname0)[0] if endpoint.Interface.MacAddress: ip.link('set', index=idx, address=endpoint.Interface.MacAddress) ip.link('set', index=idx, state='up') if endpoint.Interface.Address: addr = ipaddress.ip_interface(endpoint.Interface.Address) ip.addr('add', index=idx, address=addr.ip.compressed, mask=addr.network.prefixlen) if endpoint.Interface.AddressIPv6: addr = ipaddress.ip_interface(endpoint.Interface.AddressIPv6) ip.addr('add', index=idx, address=addr.ip.compressed, mask=addr.network.prefixlen) endpoint.Interface.Name = ifname0 idx = ip.link_lookup(ifname=ifname1)[0] ip.link('set', index=idx, state='up') if 'parent' in network.Options: id_parent = ip.link_lookup(ifname=network.Options['parent'])[0] ip.link("set", index=idx, master=id_parent) endpoint.Interface.Peer = ifname1 return ifname0, ifname1 def delete_interface(interface): try: with pyroute2.IPRoute() as ip: idx = ip.link_lookup(ifname=interface)[0] ip.link("delete", index=idx) except: pass @app.route('/NetworkDriver.GetCapabilities', methods=['POST']) def GetCapabilities(): return { 'Scope': 'local', 'ConnectivityScope': 'global', 'GwAllocChecker': True, } @app.route('/NetworkDriver.GwAllocCheck', methods=['POST']) def GwAllocCheck(): request = GwAllocCheckEntity(**flask.request.get_json(force=True)) skip_ipv4 = skip_ipv6 = request.Options.get('com.docker.network.generic', {}).get('nogw') == '1' if request.Options.get('com.docker.network.generic', {}).get('nogw4') == '1': skip_ipv4 = True if request.Options.get('com.docker.network.generic', {}).get('nogw6') == '1': skip_ipv6 = True return { 'SkipIPv4': skip_ipv4, 'SkipIPv6': skip_ipv6, } @app.route('/NetworkDriver.CreateNetwork', methods=['POST']) def CreateNetwork(): network = NetworkCreateEntity(**flask.request.get_json(force=True)) try: for option, value in network.Options['com.docker.network.generic'].items(): if option not in network.Options: network.Options[option] = value except KeyError: pass networks[network.NetworkID] = network networks_sync() return {} @app.route('/NetworkDriver.DeleteNetwork', methods=['POST']) def DeleteNetwork(): network = NetworkDeleteEntity(**flask.request.get_json(force=True)) if network.NetworkID in networks: del networks[network.NetworkID] networks_sync() return {} @app.route('/NetworkDriver.CreateEndpoint', methods=['POST']) def CreateEndpoint(): endpoint = EndpointCreateEntity(**flask.request.get_json(force=True)) endpoints['{}-{}'.format(endpoint.NetworkID, endpoint.EndpointID)] = endpoint return { 'Interface': { } } @app.route('/NetworkDriver.EndpointOperInfo', methods=['POST']) def EndpointOperInfo(): endpoint = EndpointOperInfoEntity(**flask.request.get_json(force=True)) return { 'Value': { } } @app.route('/NetworkDriver.DeleteEndpoint', methods=['POST']) def DeleteEndpoint(): entity = EndpointDeleteEntity(**flask.request.get_json(force=True)) endpoint_id = '{}-{}'.format(entity.NetworkID, entity.EndpointID) if endpoint_id in endpoints: del endpoints['{}-{}'.format(entity.NetworkID, entity.EndpointID)] return {} @app.route('/NetworkDriver.Join', methods=['POST']) def Join(): join = JoinEntity(**flask.request.get_json(force=True)) network = networks[join.NetworkID] endpoint = endpoints['{}-{}'.format(join.NetworkID, join.EndpointID)] interface, interface_external = create_interface(endpoint, network) endpoint.internal_interface_name = interface endpoint.external_interface_name = interface_external gw4 = None for net4 in network.IPv4: try: gw4 = ipaddress.ip_interface(net4.Gateway) break except: pass gw6 = None for net6 in network.IPv6: try: gw6 = ipaddress.ip_interface(net6.Gateway) break except: pass result = { 'InterfaceName': { 'SrcName': interface, 'DstPrefix': 'eth', }, 'StaticRoutes': [], } if gw4 is not None: result['Gateway'] = gw4.ip.compressed if gw6 is not None: result['GatewayIPv6'] = gw6.ip.compressed gw4 = endpoint.Options.get("gw4", None) if gw4 is not None: result['StaticRoutes'].append({ 'Destination': gw4 + '/32', 'RouteType': 1, }) result['Gateway'] = gw4 gw6 = endpoint.Options.get("gw6", None) if gw6 is not None: result['StaticRoutes'].append({ 'Destination': gw6 + '/128', 'RouteType': 1, }) result['GatewayIPv6'] = gw6 return result @app.route('/NetworkDriver.Leave', methods=['POST']) def Leave(): leave = LeaveEntity(**flask.request.get_json(force=True)) endpoint = endpoints.get('{}-{}'.format(leave.NetworkID, leave.EndpointID), None) if endpoint is not None and endpoint.external_interface_name: delete_interface(endpoint.external_interface_name) return {} @app.route('/NetworkDriver.DiscoverNew', methods=['POST']) def DiscoverNew(): entity = DiscoverNewEntity(**flask.request.get_json(force=True)) return {} @app.route('/NetworkDriver.DiscoverDelete', methods=['POST']) def DiscoverDelete(): entity = DiscoverDeleteEntity(**flask.request.get_json(force=True)) return {} @app.route('/NetworkDriver.ProgramExternalConnectivity', methods=['POST']) def ProgramExternalConnectivity(): entity = ProgramExternalConnectivityEntity(**flask.request.get_json(force=True)) return {} @app.route('/NetworkDriver.RevokeExternalConnectivity', methods=['POST']) def RevokeExternalConnectivity(): entity = RevokeExternalConnectivityEntity(**flask.request.get_json(force=True)) return {}