IPv6 dataplane support + Decentralized IPv4 prefix attribution + Bug fix for attribut... 07/6707/1
authorMarcel Enguehard <[email protected]>
Mon, 15 May 2017 14:26:27 +0000 (16:26 +0200)
committerMarcel Enguehard <[email protected]>
Mon, 15 May 2017 14:26:27 +0000 (16:26 +0200)
Change-Id: I0a26eda064d9e22d9d55fd568748076148f49645
Signed-off-by: Marcel Enguehard <[email protected]>
20 files changed:
examples/tutorial/tutorial01.json
vicn/core/api.py
vicn/core/resource.py
vicn/core/resource_mgr.py
vicn/resource/central.py
vicn/resource/channel.py
vicn/resource/ip/prefix_tree.py [new file with mode: 0644]
vicn/resource/ip/route.py
vicn/resource/ip/routing_table.py
vicn/resource/ip_assignment.py [new file with mode: 0644]
vicn/resource/linux/link.py
vicn/resource/linux/net_device.py
vicn/resource/linux/package_manager.py
vicn/resource/linux/phy_interface.py
vicn/resource/linux/tap_device.py
vicn/resource/lxd/lxc_container.py
vicn/resource/ns3/emulated_lte_channel.py
vicn/resource/vpp/interface.py
vicn/resource/vpp/vpp_bridge.py
vicn/resource/vpp/vpp_commands.py

index d7c716c..edc0e6d 100644 (file)
@@ -3,7 +3,7 @@
         {
             "type": "Physical",
             "name": "server",
-            "hostname": "hostname"
+            "hostname": "localhost"
         },
         {
             "type": "NetDevice",
         },
         {
             "type": "CentralIP",
-            "ip_routing_strategy": "spt"
+            "ip_routing_strategy": "spt",
+           "ip6_data_prefix": "2001::/50",
+           "ip4_data_prefix": "192.168.128.0/24",
+           "ip4_control_prefix": "192.168.140.0/24"
         },
         {
             "type": "CentralICN",
             "face_protocol": "udp4"
         }
-    ],
-    "settings": {
-        "network": "192.168.29.0/24"
-    }
+    ]
 }
index 761177c..09167aa 100644 (file)
@@ -63,7 +63,7 @@ class API(metaclass = Singleton):
 
             # SETTING
             settings = DEFAULT_SETTINGS
-            settings.update(topology.get('settings'))
+            settings.update(topology.get('settings', dict()))
 
             # VICN process-related initializations
             nofile = settings.get('ulimit-n', None)
index 53ad218..9355cd0 100644 (file)
@@ -196,8 +196,14 @@ class BaseResource(BaseType, ABC, metaclass=ResourceMetaclass):
         if mandatory:
             raise VICNException('Mandatory attributes not set: %r' % (mandatory,))
 
-        # Check requirements
+        # Check requirements and default values
         for attr in self.iter_attributes():
+            if attr.name not in kwargs:
+                default = self.get_default_collection(attr) if attr.is_collection else \
+                        self.get_default(attr)
+                if vars(attr)['default'] != NEVER_SET:
+                    #import pdb; pdb.set_trace()
+                    self.set(attr.name, default, blocking=False)
             if issubclass(attr.type, Resource) and attr.requirements:
                 for req in attr.requirements:
                     instance = self.get(attr.name)
index b750f55..c6ce77a 100644 (file)
@@ -902,8 +902,8 @@ class ResourceManager(metaclass=Singleton):
                         new_state = AttributeState.INITIALIZED
 
             elif pending_state == AttributeState.PENDING_UPDATE:
+                attrs = resource._state.attr_change_value[attribute.name]
                 if resource._state.attr_change_success[attribute.name] == True:
-                    attrs = resource._state.attr_change_value[attribute.name]
                     self.attr_log(resource, attribute,
                             'UPDATE success. Value = {}. Attribute is CLEAN'.format(attrs))
                     if attrs != NEVER_SET:
@@ -934,6 +934,8 @@ class ResourceManager(metaclass=Singleton):
                                 resource.get_uuid(), attribute.name))
                         e = resource._state.attr_change_value[attribute.name]
                         new_state = AttributeState.ERROR
+                        import traceback; traceback.print_tb(e.__traceback__)
+                        import os; os._exit(1)
 
             else:
                 raise RuntimeError
@@ -954,7 +956,7 @@ class ResourceManager(metaclass=Singleton):
         return Query.from_dict(dic)
 
     def _monitor_netmon(self, resource):
-        ip = resource.node.host_interface.ip_address
+        ip = resource.node.host_interface.ip4_address
         if not ip:
             log.error('IP of monitored Node is None')
             import os; os._exit(1)
@@ -1007,7 +1009,7 @@ class ResourceManager(metaclass=Singleton):
 
     def _monitor_emulator(self, resource):
         ns = resource
-        ip = ns.node.bridge.ip_address # host_interface.ip_address
+        ip = ns.node.bridge.ip4_address # host_interface.ip_address
 
         ws_ns = self._router.add_interface('websocketclient', address = ip,
                 port = ns.control_port,
@@ -1356,8 +1358,10 @@ class ResourceManager(metaclass=Singleton):
                     new_state = ResourceState.INITIALIZED
                 else:
                     e = resource._state.change_value
+                    import traceback; traceback.print_tb(e.__traceback__)
                     log.error('Cannot setup resource {} : {}'.format(
                             resource.get_uuid(), e))
+                    import os; os._exit(1)
 
             elif pending_state == ResourceState.PENDING_GET:
                 if resource._state.change_success == True:
index 6f3c680..1013d1a 100644 (file)
@@ -22,7 +22,6 @@ import os
 
 from netmodel.model.type                import String
 from netmodel.util.misc                 import pairwise
-from vicn.core.address_mgr              import AddressManager
 from vicn.core.attribute                import Attribute
 from vicn.core.exception                import ResourceNotFound
 from vicn.core.resource                 import Resource
@@ -34,9 +33,9 @@ from vicn.resource.icn.forwarder        import Forwarder
 from vicn.resource.icn.face             import L2Face, L4Face, FaceProtocol
 from vicn.resource.icn.producer         import Producer
 from vicn.resource.icn.route            import Route as ICNRoute
-from vicn.resource.linux.file           import TextFile
 from vicn.resource.lxd.lxc_container    import LxcContainer
 from vicn.resource.node                 import Node
+from vicn.resource.ip_assignment        import Ipv4Assignment, Ipv6Assignment
 
 log = logging.getLogger(__name__)
 
@@ -47,7 +46,7 @@ CMD_CONTAINER_SET_DNS = 'echo "nameserver {ip_dns}" | ' \
 
 # For host
 CMD_NAT = '\n'.join([
-    'iptables -t nat -A POSTROUTING -o {bridge_name} -s {network} ' \
+    'iptables -t nat -A POSTROUTING -o {interface_name} -s {network} ' \
     '! -d {network} -j MASQUERADE',
     'echo 1 > /proc/sys/net/ipv4/ip_forward'
 ])
@@ -208,90 +207,6 @@ def _get_icn_graph(manager):
 
 #-------------------------------------------------------------------------------
 
-class IPAssignment(Resource):
-    """
-    Resource: IPAssignment
-    """
-
-    #--------------------------------------------------------------------------
-    # Resource lifecycle
-    #--------------------------------------------------------------------------
-
-    def __after__(self):
-        return ('Interface',)
-
-    @inline_task
-    def __get__(self):
-        raise ResourceNotFound
-
-    def __subresources__(self):
-        basedir = os.path.dirname(self._state.manager._base)
-        self.host_file = TextFile(node = None,
-                filename = os.path.join(basedir, HOST_FILE),
-                overwrite = True)
-        return self.host_file
-
-    def __create__(self):
-        """
-        IP assignment strategy /32: assign /32 IP address to each interface
-
-        We might use different subnets for resources involved in experiment,
-        and supporting resources, and to minimize routing tables.
-        """
-        log.info('Assigment of IP addresses')
-        tasks = EmptyTask()
-
-        # We sort nodes by names for IP assignment. This code ensures that
-        # interfaces on the same channel get consecutive IP addresses. That
-        # way, we can assign /31 on p2p channels.
-        channels = sorted(self._state.manager.by_type(Channel),
-                key = lambda x : x.get_sortable_name())
-        channels.extend(sorted(self._state.manager.by_type(Node),
-                    key = lambda node : node.name))
-
-        host_file_content = ""
-
-        # Dummy code to start IP addressing on an even number for /31
-        ip = AddressManager().get_ip(None)
-        if int(ip[-1]) % 2 == 0:
-            ip = AddressManager().get_ip("dummy object")
-
-        for channel in channels:
-            # Sort interfaces in a deterministic order to ensure consistent
-            # addressing across restarts of the tool
-            interfaces = sorted(channel.interfaces,
-                    key = lambda x : x.device_name)
-
-            for interface in interfaces:
-                if interface.ip_address is None:
-                    ip = AddressManager().get_ip(interface)
-
-                    @async_task
-                    async def set_ip(interface, ip):
-                        await interface.async_set('ip_address', ip)
-
-                    if interface.managed:
-                        tasks = tasks | set_ip(interface, ip)
-                else:
-                    ip = interface.ip_address
-
-                # Note: interface.ip_address should still be None at this stage
-                # since we have not made the assignment yet
-
-                if isinstance(channel, Node):
-                    host_file_content += '# {} {} {}\n'.format(
-                            interface.node.name, interface.device_name, ip)
-                    if interface == interface.node.host_interface:
-                        host_file_content += '{} {}\n'.format(ip,
-                                interface.node.name)
-        self.host_file.content = host_file_content
-
-        return tasks
-
-    __delete__ = None
-
-#-------------------------------------------------------------------------------
-
 class IPRoutes(Resource):
     """
     Resource: IPRoutes
@@ -331,7 +246,9 @@ class IPRoutes(Resource):
             if not node_uuid in origins:
                 origins[node_uuid] = list()
             for interface in node.interfaces:
-                origins[node_uuid].append(interface.ip_address)
+                origins[node_uuid].append(interface.ip4_address)
+                if interface.ip6_address: #Control interfaces have no v6 address
+                    origins[node_uuid].append(interface.ip6_address)
         return origins
 
     def _get_ip_routes(self):
@@ -370,37 +287,44 @@ class IPRoutes(Resource):
             if prefix in ip_routes[src_node]:
                 continue
 
-            if prefix == next_hop_ingress.ip_address:
+            #FIXME: should test for IP format
+            ip_version = 6 if ":" in prefix else 4
+            next_hop_ingress_ip = (next_hop_ingress.ip6_address if ip_version is 6 else
+                    next_hop_ingress.ip4_address)
+            if prefix == next_hop_ingress_ip:
                 # Direct route on src_node.name :
                 # route add [prefix] dev [next_hop_interface_.device_name]
-                route = IPRoute(node     = src_node,
+                route4 = IPRoute(node     = src_node,
                                 managed    = False,
                                 owner      = self,
                                 ip_address = prefix,
                                 mac_address = mac_addr,
+                                ip_version = ip_version,
                                 interface  = next_hop_interface)
             else:
                 # We need to be sure we have a route to the gw from the node
-                if not next_hop_ingress.ip_address in ip_routes[src_node]:
-                    pre_route = IPRoute(node = src_node,
-                                    managed    = False,
-                                    owner      = self,
-                                    ip_address = next_hop_ingress.ip_address,
+                if not next_hop_ingress_ip in ip_routes[src_node]:
+                    pre_route = IPRoute(node    = src_node,
+                                    managed     = False,
+                                    owner       = self,
+                                    ip_address  = next_hop_ingress_ip,
+                                    ip_version  = ip_version,
                                     mac_address = mac_addr,
-                                    interface  = next_hop_interface)
-                    ip_routes[src_node].append(next_hop_ingress.ip_address)
+                                    interface   = next_hop_interface)
+                    ip_routes[src_node].append(next_hop_ingress_ip)
                     pre_routes.append(pre_route)
 
                 # Route on src_node.name:
                 # route add [prefix] dev [next_hop_interface_.device_name]
                 #    via [next_hop_ingress.ip_address]
-                route = IPRoute(node     = src_node,
-                                managed    = False,
-                                owner      = self,
-                                ip_address = prefix,
-                                interface  = next_hop_interface,
+                route = IPRoute(node        = src_node,
+                                managed     = False,
+                                owner       = self,
+                                ip_address  = prefix,
+                                ip_version  = ip_version,
+                                interface   = next_hop_interface,
                                 mac_address = mac_addr,
-                                gateway    = next_hop_ingress.ip_address)
+                                gateway     = next_hop_ingress_ip)
             ip_routes[src_node].append(prefix)
             routes.append(route)
         return pre_routes, routes
@@ -420,16 +344,24 @@ class IPRoutes(Resource):
             dst = self._state.manager.by_uuid(map_[dst_node_uuid])
 
             log.debug('[IP ROUTE] NODES {}/{}/{} -> {}/{}/{}'.format(
-                        src_node.name, src.device_name, src.ip_address,
-                        dst_node.name, dst.device_name, dst.ip_address))
+                        src_node.name, src.device_name, src.ip4_address,
+                        dst_node.name, dst.device_name, dst.ip4_address))
             log.debug('[IP ROUTE] NODES {}/{}/{} -> {}/{}/{}'.format(
-                        dst_node.name, dst.device_name, dst.ip_address,
-                        src_node.name, src.device_name, src.ip_address))
+                        dst_node.name, dst.device_name, dst.ip4_address,
+                        src_node.name, src.device_name, src.ip4_address))
 
             route = IPRoute(node        = src_node,
                             managed     = False,
                             owner       = self,
-                            ip_address  = dst.ip_address,
+                            ip_address  = dst.ip4_address,
+                            mac_address = dst.mac_address,
+                            interface   = src)
+            routes.append(route)
+            route = IPRoute(node        = src_node,
+                            managed     = False,
+                            owner       = self,
+                            ip_address  = dst.ip6_address,
+                            ip_version  = 6,
                             mac_address = dst.mac_address,
                             interface   = src)
             routes.append(route)
@@ -437,7 +369,15 @@ class IPRoutes(Resource):
             route = IPRoute(node       = dst_node,
                             managed    = False,
                             owner      = self,
-                            ip_address = src.ip_address,
+                            ip_address = src.ip4_address,
+                            mac_address = src.mac_address,
+                            interface  = dst)
+            routes.append(route)
+            route = IPRoute(node       = dst_node,
+                            managed    = False,
+                            owner      = self,
+                            ip_address = src.ip6_address,
+                            ip_version = 6,
                             mac_address = src.mac_address,
                             interface  = dst)
             routes.append(route)
@@ -513,15 +453,15 @@ class ICNFaces(Resource):
                 src_face = L4Face(node        = src_node,
                                   owner      = self,
                                   protocol    = protocol,
-                                  src_ip      = src.ip_address,
-                                  dst_ip      = dst.ip_address,
+                                  src_ip      = src.ip4_address,
+                                  dst_ip      = dst.ip4_address,
                                   src_port    = TMP_DEFAULT_PORT,
                                   dst_port    = TMP_DEFAULT_PORT)
                 dst_face = L4Face(node        = dst_node,
                                   owner      = self,
                                   protocol    = protocol,
-                                  src_ip      = dst.ip_address,
-                                  dst_ip      = src.ip_address,
+                                  src_ip      = dst.ip4_address,
+                                  dst_ip      = src.ip4_address,
                                   src_port    = TMP_DEFAULT_PORT,
                                   dst_port    = TMP_DEFAULT_PORT)
             else:
@@ -649,38 +589,9 @@ class ContainerSetup(Resource):
 
     def __subresources__(self):
 
-        # a) routes: host -> container
-        #   . container interfaces
-        #   . container host (main) interface
-        # route add -host {ip_address} dev {bridge_name}
-        route = IPRoute(node       = self.container.node,
-                        managed    = False,
-                        owner      = self,
-                        ip_address = self.container.host_interface.ip_address, 
-                        interface  = self.container.node.bridge)
-        route.node.routing_table.routes << route
-
-        # b) route: container -> host
-        # route add {ip_gateway} dev {interface_name}
-        # route add default gw {ip_gateway} dev {interface_name}
-        route = IPRoute(node       = self.container,
-                        owner      = self,
-                        managed    = False,
-                        ip_address = self.container.node.bridge.ip_address,
-                        interface  = self.container.host_interface)
-        route.node.routing_table.routes << route
-        route_gw = IPRoute(node       = self.container,
-                           managed    = False,
-                           owner      = self,
-                           ip_address = 'default',
-                           interface  = self.container.host_interface,
-                           gateway    = self.container.node.bridge.ip_address)
-        route_gw.node.routing_table.routes << route_gw
-
-        # c) dns
         dns_server_entry = DnsServerEntry(node = self.container,
                 owner      = self,
-                ip_address = self.container.node.bridge.ip_address,
+                ip_address = self.container.node.bridge.ip4_address,
                 interface_name = self.container.host_interface.device_name)
 
         return dns_server_entry
@@ -690,6 +601,44 @@ class ContainerSetup(Resource):
         raise ResourceNotFound
 
     def __create__(self):
+        #If no IP has been given on the host interface (e.g., through DHCP)
+        #We need to assign one
+        if not self.container.host_interface.ip4_address:
+            # a) get the IP
+            assign=self._state.manager.by_type(Ipv4Assignment)[0]
+            ip4_addr = assign.get_control_address(self.container.host_interface)
+            self.container.host_interface.ip4_address = ip4_addr
+
+
+            # a) routes: host -> container
+            #   . container interfaces
+            #   . container host (main) interface
+            # route add -host {ip_address} dev {bridge_name}
+            route = IPRoute(node       = self.container.node,
+                            managed    = False,
+                            owner      = self,
+                            ip_address = ip4_addr,
+                            interface  = self.container.node.bridge)
+            route.node.routing_table.routes << route
+
+            # b) route: container -> host
+            # route add {ip_gateway} dev {interface_name}
+            # route add default gw {ip_gateway} dev {interface_name}
+            route = IPRoute(node       = self.container,
+                            owner      = self,
+                            managed    = False,
+                            ip_address = self.container.node.bridge.ip4_address,
+                            interface  = self.container.host_interface)
+            route.node.routing_table.routes << route
+            route_gw = IPRoute(node       = self.container,
+                               managed    = False,
+                               owner      = self,
+                               ip_address = 'default',
+                               interface  = self.container.host_interface,
+                               gateway    = self.container.node.bridge.ip4_address)
+            route_gw.node.routing_table.routes << route_gw
+
+
         return BashTask(self.container.node, CMD_IP_FORWARD)
 
 #------------------------------------------------------------------------------
@@ -729,21 +678,26 @@ class CentralIP(Resource):
 
     ip_routing_strategy = Attribute(String, description = 'IP routing strategy',
             default = 'pair') # spt, pair
+    ip6_data_prefix = Attribute(String, description="Prefix for IPv6 forwarding", mandatory=True)
+    ip4_data_prefix = Attribute(String, description="Prefix for IPv4 forwarding", mandatory=True)
+    ip4_control_prefix = Attribute(String, description="Prefix for IPv4 control", mandatory=True)
 
     #--------------------------------------------------------------------------
     # Resource lifecycle
     #--------------------------------------------------------------------------
 
-    def __after_init__(self):
-        return ('Node', 'Channel', 'Interface')
+    #def __after_init__(self):
+    #    return ('Node', 'Channel', 'Interface')
 
     def __subresources__(self):
-        ip_assign = IPAssignment(owner=self)
+        ip4_assign = Ipv4Assignment(prefix=self.ip4_data_prefix,
+                control_prefix=self.ip4_control_prefix)
+        ip6_assign = Ipv6Assignment(prefix=self.ip6_data_prefix)
         containers_setup = ContainersSetup(owner=self)
         ip_routes = IPRoutes(owner = self,
                 routing_strategy = self.ip_routing_strategy)
 
-        return ip_assign > (ip_routes | containers_setup)
+        return (ip4_assign | ip6_assign) > (ip_routes | containers_setup)
 
     @inline_task
     def __get__(self):
index cd64b64..d91bebc 100644 (file)
 # limitations under the License.
 #
 
-from netmodel.model.type   import String
-from vicn.core.resource    import Resource
-from vicn.core.attribute   import Attribute
+from netmodel.model.type    import String
+from vicn.core.resource     import Resource
+from vicn.core.attribute    import Attribute
+from vicn.core.task         import EmptyTask
+from vicn.resource.ip_assignment    import Ipv6Assignment, Ipv4Assignment
+
+from math import log, ceil
 
 class Channel(Resource):
     """
@@ -29,6 +33,9 @@ class Channel(Resource):
     # Public API
     #--------------------------------------------------------------------------
 
+    def __after_init__(self):
+        return ("IpAssignment",)
+
     def get_remote_name(self, name):
         if len(self._interfaces) != 2:
             return None
@@ -42,3 +49,26 @@ class Channel(Resource):
         ret = "{:03}".format(len(self.interfaces))
         ret = ret + ''.join(sorted(map(lambda x : x.node.name, self.interfaces)))
         return ret
+
+    def __create__(self):
+        interfaces = sorted(self.interfaces, key = lambda x : x.device_name)
+        if interfaces:
+            #IPv6
+            central6 = self._state.manager.by_type(Ipv6Assignment)[0]
+            prefix6_size = min(64, 128 - ceil(log(len(self.interfaces), 2)))
+            prefix6 = iter(central6.get_prefix(self, prefix6_size))
+
+            #IPv4
+            central4 = self._state.manager.by_type(Ipv4Assignment)[0]
+            prefix4_size = 32 - ceil(log(len(self.interfaces), 2))
+            prefix4 = iter(central4.get_prefix(self, prefix4_size))
+
+            for interface in interfaces:
+                try:
+                    interface.ip4_address = next(prefix4)
+                except StopIteration as e:
+                    import pdb; pdb.set_trace()
+                interface.ip6_address = next(prefix6)
+                interface.ip6_prefix = prefix6_size
+
+        return EmptyTask()
diff --git a/vicn/resource/ip/prefix_tree.py b/vicn/resource/ip/prefix_tree.py
new file mode 100644 (file)
index 0000000..d3a8139
--- /dev/null
@@ -0,0 +1,211 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Cisco and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from socket import inet_pton, inet_ntop, AF_INET6
+from struct import unpack, pack
+from abc    import ABCMeta
+
+class NotEnoughAddresses(Exception):
+    pass
+
+class Prefix(metaclass=ABCMeta):
+
+    def __init__(self, ip_address, prefix_size=None):
+        if not prefix_size:
+            ip_address, prefix_size = ip_address.split('/')
+            prefix_size = int(prefix_size)
+        if isinstance(ip_address, str):
+            ip_address = self.aton(ip_address)
+        self.ip_address = ip_address
+        self.prefix_size = prefix_size
+        self._range = self.limits()
+
+    def __contains__(self, obj):
+        #it can be an IP as a integer
+        if isinstance(obj, int):
+            obj = type(self)(obj, self.MAX_PREFIX_SIZE)
+        #Or it's an IP string
+        if isinstance(obj, str):
+            #It's a prefix as 'IP/prefix'
+            if '/' in obj:
+                split_obj = obj.split('/')
+                obj = type(self)(split_obj[0], int(split_obj[1]))
+            else:
+                obj = type(self)(obj, self.MAX_PREFIX_SIZE)
+
+        return self._contains_prefix(obj)
+
+    def _contains_prefix(self, prefix):
+        assert isinstance(prefix, type(self))
+        return (prefix.prefix_size >= self.prefix_size and
+            prefix.ip_address >= self.first_prefix_address() and
+            prefix.ip_address <= self.last_prefix_address())
+
+    #Returns the first address of a prefix
+    def first_prefix_address(self):
+        return self.ip_address & (self.MASK << (self.MAX_PREFIX_SIZE-self.prefix_size))
+
+    def last_prefix_address(self):
+        return self.ip_address | (self.MASK >> self.prefix_size)
+
+    def limits(self):
+        return self.first_prefix_address(), self.last_prefix_address()
+
+    def __str__(self):
+        return "{}/{}".format(self.ntoa(self.first_prefix_address()), self.prefix_size)
+
+    def __eq__(self, other):
+        return (self.first_prefix_address() == other.first_prefix_address() and
+                self.prefix_size == other.prefix_size)
+
+    def __hash__(self):
+        return hash(str(self))
+
+    def __iter__(self):
+        for i in range(self._range[0], self._range[1]+1):
+            yield self.ntoa(i)
+
+class Inet4Prefix(Prefix):
+
+    MASK = 0xffffffff
+    MAX_PREFIX_SIZE = 32
+
+    @classmethod
+    def aton(cls, address):
+        ret = 0
+        components = address.split('.')
+        for comp in components:
+            ret = (ret << 8) + int(comp)
+        return ret
+
+    @classmethod
+    def ntoa(cls, address):
+        components = []
+        for _ in range(0,4):
+            components.insert(0,'{}'.format(address % 256))
+            address = address >> 8
+        return '.'.join(components)
+
+class Inet6Prefix(Prefix):
+
+    MASK = 0xffffffffffffffff
+    MAX_PREFIX_SIZE = 64
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._range = self.limits(True)
+
+    @classmethod
+    def aton (cls, address, with_suffix=False):
+        ret, suffix = unpack(">QQ", inet_pton(AF_INET6, address))
+        if with_suffix:
+            ret = (ret << 64) | suffix
+        return ret
+
+    @classmethod
+    def ntoa (cls, address, with_suffix=False):
+        ret = None
+        if with_suffix:
+            ret = inet_ntop(AF_INET6, pack(">QQ", address >> 64, address & ((1 << 64) -1)))
+        else:
+            ret = inet_ntop(AF_INET6, pack(">QQ", address, 0))
+        return ret
+
+    def limits(self, with_suffix=False):
+        ret = super().limits()
+        if with_suffix:
+            ret = ret[0] << 64, ret[1] << 64 | self.MASK
+        return ret
+
+    def __iter__(self):
+        for i in range(*self._range):
+            yield self.ntoa(i, True)
+
+###### PREFIX TREE ######
+
+class PrefixTree:
+    def __init__(self, prefix):
+        self.prefix = prefix
+        self.prefix_cls = type(prefix)
+        self.left = None
+        self.right = None
+        #When the full prefix is assigned
+        self.full = False
+
+    def find_prefix(self, prefix_size):
+        ret, lret, rret = [None]*3
+        if prefix_size > self.prefix.prefix_size and not self.full:
+            if self.left is None:
+                lret = self.prefix_cls(self.prefix.first_prefix_address(), self.prefix.prefix_size+1)
+            else:
+                lret = self.left.find_prefix(prefix_size)
+
+            if self.right is None:
+                rret = self.prefix_cls(self.prefix.last_prefix_address(), self.prefix.prefix_size+1)
+            else:
+                rret = self.right.find_prefix(prefix_size)
+
+        #Now we look for the longer prefix to assign
+        if not lret or (rret and rret.prefix_size > lret.prefix_size):
+            ret = rret
+        else:
+            ret = lret
+        return ret
+
+
+    def assign_prefix(self, prefix):
+        assert prefix in self.prefix
+        if prefix.prefix_size > self.prefix.prefix_size:
+            #Existing prefix on the left
+            lp = self.prefix_cls(self.prefix.first_prefix_address(), self.prefix.prefix_size+1)
+            #It's on the left branch
+            if prefix in lp:
+                if not self.left:
+                    self.left = PrefixTree(lp)
+                self.left.assign_prefix(prefix)
+            #It's on the right branch
+            else:
+                rp = self.prefix_cls(self.prefix.last_prefix_address(), self.prefix.prefix_size+1)
+                if not self.right:
+                    self.right = PrefixTree(rp)
+                self.right.assign_prefix(prefix)
+        elif self.prefix == prefix:
+            self.full = True
+        else:
+            raise RuntimeError("And you may ask yourself, well, how did I get here?")
+
+    def get_prefix(self, prefix_size):
+        ret = self.find_prefix(prefix_size)
+        if not ret:
+            raise NotEnoughAddresses
+        #find_prefix returns the size of the largest available prefix in our space
+        #not necessarily the one we asked for
+        ret.prefix_size = prefix_size
+        self.assign_prefix(ret)
+        return ret
+
+    def get_assigned_prefixes(self):
+        ret = []
+        if not self.right and not self.left and self.full:
+            ret.append(self.prefix)
+        else:
+            if self.right:
+                ret.extend(self.right.get_assigned_prefixes())
+            if self.left:
+                ret.extend(self.left.get_assigned_prefixes())
+        return ret
index f073e42..b9f8296 100644 (file)
@@ -16,7 +16,7 @@
 # limitations under the License.
 #
 
-from netmodel.model.type        import String
+from netmodel.model.type        import String, Integer
 from vicn.resource.node         import Node
 from vicn.core.attribute        import Attribute
 from vicn.core.resource         import Resource
@@ -27,6 +27,7 @@ class IPRoute(Resource):
     ip_address = Attribute(String, mandatory = True)
     interface = Attribute(Interface, mandatory = True)
     gateway = Attribute(String)
+    ip_version = Attribute(Integer, default=4)
 
     # FIXME Temp hack for VPP, migrate this to an ARP table resource
     mac_address = Attribute(String)
index 52b8179..e45793c 100644 (file)
@@ -25,15 +25,17 @@ from vicn.resource.node             import Node
 from vicn.resource.vpp.vpp_commands import CMD_VPP_ADD_ROUTE
 from vicn.resource.vpp.vpp_commands import CMD_VPP_ADD_ROUTE_GW
 
-CMD_ADD_ROUTE = ('ip route add {route.ip_address} '
+CMD_ADD_ROUTE = ('ip -{route.ip_version} route add {route.ip_address} '
         'dev {route.interface.device_name} || true')
-CMD_ADD_ROUTE_GW = ('ip route add {route.ip_address} '
+CMD_ADD_ROUTE_GW = ('ip -{route.ip_version} route add {route.ip_address} '
         'dev {route.interface.device_name} via {route.gateway} || true')
-CMD_DEL_ROUTE = ('ip route del {route.ip_address} '
+CMD_DEL_ROUTE = ('ip -{route.ip_version} route del {route.ip_address} '
         'dev {route.interface.device_name}')
 CMD_SHOW_ROUTES = 'ip route show'
 
 CMD_ADD_ARP_ENTRY = 'arp -s {route.ip_address} {route.mac_address}'
+CMD_ADD_NDB_ENTRY = ('ip -6 neigh add {route.ip_address} lladr {route.mac_address} '
+        'dev {route.interface.device_name}')
 
 # Populate arp table too. The current configuration with one single bridge
 # connecting every container and vpp nodes seem to create loops that prevent
@@ -93,7 +95,7 @@ class RoutingTable(Resource):
     #--------------------------------------------------------------------------
 
     def __after__(self):
-        return ('CentralIP', 'VPPInterface')
+        return ('CentralIP', 'VPPInterface', 'ContainerSetup')
 
     def __get__(self):
         def cache(rv):
@@ -125,10 +127,10 @@ class RoutingTable(Resource):
             if route.ip_address in done:
                 continue
             done.add(route.ip_address)
-            
+
             # TODO VPP should provide its own implementation of routing table
             # on the node
-            if not route.interface.has_vpp_child: 
+            if not route.interface.has_vpp_child:
                 if route.gateway is None:
                     cmd = CMD_ADD_ROUTE.format(route = route)
                     routes_cmd.append(cmd)
@@ -148,7 +150,7 @@ class RoutingTable(Resource):
                     cmd = CMD_VPP_ADD_ROUTE_GW.format(route = route)
                     routes_via_cmd.append(cmd)
                     routes_via_lock = route.node.vpp.vppctl_lock
-                
+
         # TODO: checks
         clean_routes_task = EmptyTask()
 
diff --git a/vicn/resource/ip_assignment.py b/vicn/resource/ip_assignment.py
new file mode 100644 (file)
index 0000000..7553f4f
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 Cisco and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from vicn.core.resource                 import Resource
+from netmodel.model.type                import String
+from vicn.core.attribute                import Attribute
+from vicn.resource.ip.prefix_tree       import Inet6Prefix, PrefixTree, Inet4Prefix
+from vicn.core.task                     import inline_task
+from vicn.core.exception                import ResourceNotFound
+
+class IpAssignment(Resource):
+    prefix = Attribute(String, mandatory=True)
+    control_prefix = Attribute(String, description="prefix for control plane")
+
+    PrefixClass = None
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._prefix = self.PrefixClass(self.prefix)
+        self._prefix_tree = PrefixTree(self._prefix)
+        self._assigned_prefixes = {}
+        if self.control_prefix:
+            self._ctrl_prefix = self.PrefixClass(self.control_prefix)
+            self._ctrl_prefix_it = iter(self._ctrl_prefix)
+            next(self._ctrl_prefix_it) #Removes internet address
+            self._assigned_addresses = {}
+
+    def get_prefix(self, obj, prefix_size):
+        ret = None
+        if obj in self._assigned_prefixes:
+            ret = self._assigned_prefixes[obj]
+        else:
+            ret = self._prefix_tree.get_prefix(prefix_size)
+            self._assigned_prefixes[obj] = ret
+        return ret
+
+    def get_control_address(self, obj):
+        ret = None
+        if not self.control_prefix:
+            raise RuntimeError("No control prefix given")
+        try:
+            ret = self._assigned_addresses[obj]
+        except KeyError:
+            ret = next(self._ctrl_prefix_it)
+            self._assigned_addresses[obj] = ret
+        return ret
+
+class Ipv6Assignment(IpAssignment):
+    PrefixClass = Inet6Prefix
+
+
+class Ipv4Assignment(IpAssignment):
+    PrefixClass = Inet4Prefix
index a930b8a..ad77bfb 100644 (file)
@@ -120,14 +120,14 @@ class Link(Channel):
         if hasattr(self.src_node, 'vpp') and not self.src_node.vpp is None:
             vpp_src = VPPInterface(parent = self._src,
                     vpp = self.src_node.vpp,
-                    ip_address = Reference(self._src, 'ip_address'),
+                    ip4_address = Reference(self._src, 'ip4_address'),
                     device_name = 'vpp' + self._src.device_name)
             manager.commit_resource(vpp_src)
 
         if hasattr(self.dst_node, 'vpp') and not self.dst_node.vpp is None:
             vpp_dst = VPPInterface(parent = self._dst,
                     vpp = self.dst_node.vpp,
-                    ip_address = Reference(self._dst, 'ip_address'),
+                    ip4_address = Reference(self._dst, 'ip4_address'),
                     device_name = 'vpp' + self._dst.device_name)
             manager.commit_resource(vpp_dst)
 
index a6f7614..1ce7e4d 100644 (file)
@@ -38,7 +38,10 @@ LXD_FIX = lambda cmd: 'sleep 1 && {}'.format(cmd)
 
 MAX_DEVICE_NAME_SIZE = 15
 
-CMD_FLUSH_IP         = 'ip addr flush dev {device_name}'
+IPV4=4
+IPV6=6
+
+CMD_FLUSH_IP         = 'ip -{ip_version} addr flush dev {device_name}'
 
 CMD_INTERFACE_LIST = 'ip link show | grep -A 1 @{}'
 RX_INTERFACE_LIST  = '.*?(?P<ifname>[^ ]*)@{}:'
@@ -57,8 +60,10 @@ CMD_DELETE = 'ip link delete {netdevice.device_name}'
 CMD_SET_MAC_ADDRESS = 'ip link set dev {netdevice.device_name} ' \
                       'address {netdevice.mac_address}'
 CMD_GET_IP_ADDRESS = 'ip addr show {netdevice.device_name}'
-CMD_SET_IP_ADDRESS = 'ip addr add dev {netdevice.device_name} ' \
-                     '{netdevice.ip_address} brd + || true'
+CMD_SET_IP4_ADDRESS = 'ip addr add dev {netdevice.device_name} ' \
+                     '{netdevice.ip4_address} brd + || true'
+CMD_SET_IP6_ADDRESS = 'ip addr add dev {netdevice.device_name} ' \
+                     '{netdevice.ip6_address}/{netdevice.ip6_prefix} || true'
 CMD_SET_PROMISC = 'ip link set dev {netdevice.device_name} promisc {on_off}'
 CMD_SET_UP = 'ip link set {netdevice.device_name} {up_down}'
 CMD_SET_CAPACITY='\n'.join([
@@ -85,6 +90,11 @@ sysctl net.ipv4.conf.all.rp_filter
 sysctl net.ipv4.conf.{netdevice.device_name}.rp_filter
 '''
 
+CMD_UNSET_IP6_FWD = 'sysctl -w net.ipv6.conf.{netdevice.device_name}.forwarding=0'
+CMD_SET_IP6_FWD = 'sysctl -w net.ipv6.conf.{netdevice.device_name}.forwarding=1'
+CMD_GET_IP6_FWD = 'sysctl -n net.ipv6.conf.{netdevice.device_name}.forwarding'
+
+
 #-------------------------------------------------------------------------------
 
 # FIXME GPL code
@@ -269,7 +279,10 @@ class BaseNetDevice(Interface, Application):
     capacity = Attribute(Integer,
             description = 'Capacity for interface shaping (Mb/s)')
     mac_address = Attribute(String, description = 'Mac address of the device')
-    ip_address = Attribute(String, description = 'IP address of the device')
+    ip4_address = Attribute(String, description = 'IP address of the device')
+    ip6_address = Attribute(String, description = 'IPv6 address of the device')
+    ip6_prefix = Attribute(Integer, description = 'Prefix for the IPv6 link', default=64)
+    ip6_forwarding = Attribute(Bool, description = 'IPv6 forwarding', default = True)
     pci_address = Attribute(String,
             description = 'PCI bus address of the device',
             ro = True)
@@ -329,7 +342,7 @@ class BaseNetDevice(Interface, Application):
     def _set_mac_address(self):
         return BashTask(self.node, CMD_SET_MAC_ADDRESS, {'netdevice': self})
 
-    def _get_ip_address(self):
+    def _get_ip4_address(self):
         """
         NOTE: Incidently, this will also give the MAC address, as well as other
         attributes...
@@ -357,21 +370,79 @@ class BaseNetDevice(Interface, Application):
                 if len(ips) > 1:
                     log.warning('Keeping only first of many IP addresses...')
                 ip = ips[0]
-                attrs['ip_address'] = ip['ip-address']
+                attrs['ip4_address'] = ip['ip-address']
+            else:
+                attrs['ip4_address'] = None
+            return attrs
+
+        return BashTask(self.node, CMD_GET_IP_ADDRESS,
+                {'netdevice': self}, parse=parse)
+
+    def _set_ip4_address(self):
+        if self.ip4_address is None:
+            # Unset IP
+            return BashTask(self.node, CMD_FLUSH_IP,
+                    {'device_name': self.device_name, 'ip_version': IPV4})
+        return BashTask(self.node, CMD_SET_IP4_ADDRESS, {'netdevice': self})
+
+    def _get_ip6_address(self):
+        """
+        NOTE: Incidently, this will also give the MAC address, as well as other
+        attributes...
+        """
+        def parse(rv):
+            attrs = dict()
+
+            assert rv is not None
+
+            nds = list(parse_ip_addr(rv.stdout))
+
+            assert nds
+            assert len(nds) <= 1
+
+            nd = nds[0]
+
+            assert nd['name'] == self.device_name
+
+            attrs['mac_address'] = nd['hardware-address']
+
+            # We assume a single IPv4 address for now...
+            ips = [ip for ip in nd['ip-addresses']
+                    #We remove the link-local address that starts with fe80
+                    if ip['ip-address-type'] == 'ipv6' and ip['ip-address'][:4] != 'fe80']
+            if len(ips) >= 1:
+                if len(ips) > 1:
+                    log.warning('Keeping only first of many IPv6 addresses...')
+                ip = ips[0]
+                attrs['ip6_address'] = ip['ip-address']
+                attrs['ip6_prefix']  = ip['prefix']
             else:
-                attrs['ip_address'] = None
+                attrs['ip6_address'] = None
             return attrs
 
         return BashTask(self.node, CMD_GET_IP_ADDRESS,
                 {'netdevice': self}, parse=parse)
 
-    def _set_ip_address(self):
-        if self.ip_address is None:
+    _get_ip6_prefix = _get_ip6_address
+
+    def _set_ip6_address(self):
+        if self.ip6_address is None:
             # Unset IP
             return BashTask(self.node, CMD_FLUSH_IP,
-                    {'device_name': self.device_name})
-        return BashTask(self.node, CMD_SET_IP_ADDRESS,
-                {'netdevice': self})
+                    {'device_name': self.device_name, 'ip_version': IPV6})
+        return BashTask(self.node, CMD_SET_IP6_ADDRESS, {'netdevice': self})
+
+    def _get_ip6_forwarding(self):
+        def parse(rv):
+            ret = {"ip6_forwarding" : False}
+            if rv.stdout == "1":
+                ret["ip6_forwarding"] = True
+            return ret
+        return BashTask(self.node, CMD_GET_IP6_FWD, {'netdevice': self}, parse=parse)
+
+    def _set_ip6_forwarding(self):
+        cmd = CMD_SET_IP6_FWD if self.ip6_forwarding else CMD_UNSET_IP6_FWD
+        return BashTask(self.node, cmd, {'netdevice': self})
 
     @task
     def _get_promiscuous(self):
index cb149ac..86b7057 100644 (file)
@@ -96,9 +96,9 @@ class PackageManager(Resource):
     def __after__(self):
         if self.node.__class__.__name__ == 'Physical':
             # UGLY : This blocking code is currently needed
-            task = self.node.host_interface._get_ip_address()
+            task = self.node.host_interface._get_ip4_address()
             ip_dict = task.execute_blocking()
-            self.node.host_interface.ip_address = ip_dict['ip_address']
+            self.node.host_interface.ip4_address = ip_dict['ip4_address']
             return ('Repository',)
         else:
             return ('Repository', 'CentralIP', 'RoutingTable')
@@ -158,7 +158,7 @@ class PackageManager(Resource):
         return '/etc/apt/sources.list.d/{}.list'.format(repository.repo_name)
 
     def _get_deb_source(self, repository):
-        path = repository.node.host_interface.ip_address + '/'
+        path = repository.node.host_interface.ip4_address + '/'
         if repository.directory:
             path += repository.directory + '/'
         return 'deb http://{} {}/'.format(path, self.node.dist)
index c1aef27..81d2950 100644 (file)
@@ -36,7 +36,8 @@ class PhyInterface(Interface):
             mandatory = True)
     mac_address = Attribute(String, description = "Device's MAC address",
             mandatory=True)
-    ip_address = Attribute(String, description = "Device's IP address")
+    ip4_address = Attribute(String, description = "Device's IP address")
+    ip6_address = Attribute(String, description = "Device's IP address")
 
     #--------------------------------------------------------------------------
     # Constructor and Accessors
index 68c0b9c..b7c9f96 100644 (file)
 from netmodel.model.type            import String
 from vicn.core.attribute            import Attribute
 from vicn.core.task                 import BashTask
-from vicn.resource.linux.net_device import BaseNetDevice
+from vicn.resource.linux.net_device import BaseNetDevice, IPV4, IPV6, CMD_FLUSH_IP
 
 CMD_CREATE='ip tuntap add name {netdevice.device_name} mode tap'
-CMD_FLUSH_IP='ip addr flush dev {device_name}'
-CMD_SET_IP_ADDRESS='ip addr add dev {netdevice.device_name} 0.0.0.0'
+#CMD_SET_IP_ADDRESS='ip -{version} addr add dev {netdevice.device_name} 0.0.0.0'
 
 class TapDevice(BaseNetDevice):
 
@@ -35,13 +34,22 @@ class TapDevice(BaseNetDevice):
     def __create__(self):
         return BashTask(self.node, CMD_CREATE, {'netdevice': self})
 
-    def _set_ip_address(self):
-        if self.ip_address is None:
-            # Unset IP
-            return BashTask(self.node, CMD_FLUSH_IP, 
-                    {'device_name': self.device_name})
-        return BashTask(self.node, CMD_SET_IP_ADDRESS, 
-                {'netdevice': self})
+##mengueha: do we actually need that?
+#    def _set_ip4_address(self):
+#        if self.ip4_address is None:
+#            # Unset IP
+#            return BashTask(self.node, CMD_FLUSH_IP, 
+#                    {'device_name': self.device_name})
+#        return BashTask(self.node, CMD_SET_IP_ADDRESS, 
+#                {'netdevice': self})
+#
+#    def _set_ip6_address(self):
+#        if self.ip6_address is None:
+#            # Unset IP
+#            return BashTask(self.node, CMD_FLUSH_IP, 
+#                    {'ip_version': IPV6, 'device_name': self.device_name})
+#        return BashTask(self.node, CMD_SET_IP_ADDRESS, 
+#                {'netdevice': self})
 
 class TapChannel(TapDevice):
     station_name = Attribute(String)
index 7c678e5..9daaffb 100644 (file)
@@ -52,6 +52,10 @@ DEFAULT_SOURCE_PROTOCOL = 'simplestreams'
 # Commands used to interact with LXD (in addition to pylxd bindings)
 CMD_GET_PID='lxc info {container.name} | grep Pid | cut -d " " -f 2'
 
+CMD_UNSET_IP6_FWD = 'sysctl -w net.ipv6.conf.all.forwarding=0'
+CMD_SET_IP6_FWD = 'sysctl -w net.ipv6.conf.all.forwarding=1'
+CMD_GET_IP6_FWD = 'sysctl -n net.ipv6.conf.all.forwarding'
+
 # Type: ContainerName
 ContainerName = String(max_size = 64, ascii = True, 
         forbidden = ('/', ',', ':'))
@@ -94,6 +98,7 @@ class LxcContainer(Node):
     image = Attribute(String, description = 'image', default = None)
     is_image = Attribute(Bool, defaut = False)
     pid = Attribute(Integer, description = 'PID of the container')
+    ip6_forwarding = Attribute(Bool, default=True)
 
     #-------------------------------------------------------------------------- 
     # Constructor / Accessors
@@ -317,3 +322,15 @@ class LxcContainer(Node):
         if output:
             args += (ret.stdout, ret.stderr)
         return ReturnValue(*args)
+
+    def _get_ip6_forwarding(self):
+        def parse(rv):
+            ret = {"ip6_forwarding" : False}
+            if rv.stdout == "1":
+                ret["ip6_forwarding"] = True
+            return ret
+        return BashTask(self, CMD_GET_IP6_FWD, parse=parse)
+
+    def _set_ip6_forwarding(self):
+        cmd = CMD_SET_IP6_FWD if self.ip6_forwarding else CMD_UNSET_IP6_FWD
+        return BashTask(self, cmd)
index 3937022..bf0f709 100644 (file)
@@ -137,7 +137,7 @@ class EmulatedLteChannel(EmulatedChannel):
 
                 sta_list.append(interface.name)
                 sta_macs.append(interface.mac_address)
-                sta_ips.append(interface.ip_address + '/24')
+                sta_ips.append(interface.ip4_address + '/24')
             else:
                 identifier = self._sta_ifs[station]._state.uuid._uuid
                 sta_list.append(identifier)
index efe4fe5..5f8f501 100644 (file)
@@ -35,15 +35,16 @@ class VPPInterface(Resource):
     An interface representation in VPP
     """
 
-    vpp = Attribute(VPP, 
+    vpp = Attribute(VPP,
             description = 'Forwarder to which this interface belong to',
             mandatory = True,
             multiplicity = Multiplicity.ManyToOne,
             key = True,
-            reverse_name = 'interfaces') 
-    parent = Attribute(Interface, description = 'parent', 
-            mandatory = True, reverse_name = 'vppinterface') 
-    ip_address = Attribute(String)
+            reverse_name = 'interfaces')
+    parent = Attribute(Interface, description = 'parent',
+            mandatory = True, reverse_name = 'vppinterface')
+    ip4_address = Attribute(String)
+    ip6_address = Attribute(String)
     prefix_len = Attribute(Integer, default = 31)
     up = Attribute(Bool, description = 'Interface up/down status')
     monitored = Attribute(Bool, default = True)
@@ -73,7 +74,8 @@ class VPPInterface(Resource):
         # belongs to vpp
         self.parent.has_vpp_child = True
 
-        self.ip_address = self.parent.ip_address
+        self.ip4_address = self.parent.ip4_address
+        self.ip6_address = self.parent.ip6_address
         self.up = True
 
         if isinstance(self.parent,NonTapBaseNetDevice):
@@ -86,7 +88,8 @@ class VPPInterface(Resource):
                     {'vpp_interface': self},
                     lock = self.vpp.vppctl_lock)
 
-            self.parent.set('ip_address', None)
+            self.parent.set('ip4_address', None)
+            self.parent.set('ip6_address', None)
             self.parent.set('offload', False)
             self.parent.remote.set('offload', False)
 
@@ -107,8 +110,8 @@ class VPPInterface(Resource):
     # Attributes
     #--------------------------------------------------------------------------
 
-    def _set_ip_address(self):
-        if self.ip_address: 
+    def _set_ip4_address(self):
+        if self.ip4_address:
             return BashTask(self.vpp.node, CMD_VPP_SET_IP, {'netdevice': self},
                     lock = self.vpp.vppctl_lock)
 
@@ -121,5 +124,9 @@ class VPPInterface(Resource):
         return {'up' : False}
 
     @task
-    def _get_ip_address(self):
-        return {'ip_address' : None}
+    def _get_ip4_address(self):
+        return {'ip4_address' : None}
+
+    @task
+    def _get_ip6_address(self):
+        return {'ip6_address' : None}
index c7a70c0..612145d 100644 (file)
@@ -40,8 +40,8 @@ class VPPBridge(Channel, LinuxApplication):
     """
     Resource: VPPBridge
 
-    VPPBridge instantiate a vpp resource and set it as a vpp bridge. 
-    
+    VPPBridge instantiate a vpp resource and set it as a vpp bridge.
+
     This resource requires to be run within a LxcContainer which will have VPP.
     Every interface in the lxc_container (i.e., the ones contained in
     self.node.interfaces) will be added to the vpp bridge. To connect other vpp
@@ -64,7 +64,7 @@ class VPPBridge(Channel, LinuxApplication):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self._vpp_interfaces = list()
-        
+
     #--------------------------------------------------------------------------
     # Resource lifecycle
     #--------------------------------------------------------------------------
@@ -72,7 +72,7 @@ class VPPBridge(Channel, LinuxApplication):
     def __subresources__ (self):
         # We don't need any reference to the list of SymVethPair because each
         # side of a veth will be included in the node.interfaces list
-        self._veths = [SymVethPair(node1 = self.node, node2 = node, 
+        self._veths = [SymVethPair(node1 = self.node, node2 = node,
                 owner = self) for node in self.connected_nodes]
 
         return Resource.__concurrent__(*self._veths)
@@ -82,7 +82,7 @@ class VPPBridge(Channel, LinuxApplication):
         # Add the veth side on the connected_nodes to the set of interfaces of
         # the channel
         self.interfaces.extend([veth.side2 for veth in self._veths])
-    
+
     @task
     def __get__(self):
         # Forces creation
@@ -90,10 +90,10 @@ class VPPBridge(Channel, LinuxApplication):
 
     # Nothing to do
     __delete__ = None
-    
+
     def __create__(self):
         manager = self._state.manager
-        
+
         # Create a VPPInterface for each interface in the node. These will be
         # the interfaces we will connect to the vpp bridge process
         vpp_interfaces = list()
@@ -102,9 +102,9 @@ class VPPBridge(Channel, LinuxApplication):
             if interface.device_name == 'eth0':
                 continue
 
-            vpp_interface = VPPInterface(vpp = self.node.vpp, 
-                    parent = interface, 
-                    ip_address = Reference(interface, 'ip_address'), 
+            vpp_interface = VPPInterface(vpp = self.node.vpp,
+                    parent = interface,
+                    ip4_address = Reference(interface, 'ip4_address'),
                     device_name = 'host-' + interface.device_name)
             vpp_interfaces.append(vpp_interface)
             manager.commit_resource(vpp_interface)
@@ -112,17 +112,17 @@ class VPPBridge(Channel, LinuxApplication):
         tasks = EmptyTask()
 
         for vpp_interface in vpp_interfaces:
-            tasks = tasks > (wait_resource_task(vpp_interface) > 
+            tasks = tasks > (wait_resource_task(vpp_interface) >
                     self._add_interface(vpp_interface,0))
 
-        return wait_resource_task(self.node.vpp) > tasks 
+        return wait_resource_task(self.node.vpp) > tasks
 
     #--------------------------------------------------------------------------
     # Internal methods
     #--------------------------------------------------------------------------
 
     def _add_interface(self, interface, br_domain):
-        return BashTask(self.node, CMD_ADD_INTERFACE_TO_BR, 
+        return BashTask(self.node, CMD_ADD_INTERFACE_TO_BR,
                 {'interface': interface, 'br_domain': br_domain})
 
     def _del_interface(self,  interface, br_domain):
index 63c7480..40315c1 100644 (file)
@@ -21,7 +21,7 @@ CMD_VPP_CREATE_IFACE = '''
 vppctl create host-interface name {vpp_interface.parent.device_name} hw-addr {vpp_interface.parent.mac_address}
 vppctl set interface state {vpp_interface.device_name} up
 '''
-CMD_VPP_SET_IP = 'vppctl set int ip address {netdevice.device_name} {netdevice.ip_address}/{netdevice.prefix_len}'
+CMD_VPP_SET_IP = 'vppctl set int ip address {netdevice.device_name} {netdevice.ip4_address}/{netdevice.prefix_len}'
 CMD_VPP_SET_UP = 'vppctl set int state {netdevice.device_name} up'
 
 ##### VPP IP ROUTING #####