{
"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"
- }
+ ]
}
# 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)
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)
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:
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
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)
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,
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:
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
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__)
# 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'
])
#-------------------------------------------------------------------------------
-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
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):
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
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)
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)
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:
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
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)
#------------------------------------------------------------------------------
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):
# 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):
"""
# Public API
#--------------------------------------------------------------------------
+ def __after_init__(self):
+ return ("IpAssignment",)
+
def get_remote_name(self, name):
if len(self._interfaces) != 2:
return None
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()
--- /dev/null
+#!/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
# 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
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)
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
#--------------------------------------------------------------------------
def __after__(self):
- return ('CentralIP', 'VPPInterface')
+ return ('CentralIP', 'VPPInterface', 'ContainerSetup')
def __get__(self):
def cache(rv):
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)
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()
--- /dev/null
+#!/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
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)
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>[^ ]*)@{}:'
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([
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
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)
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...
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):
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')
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)
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
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):
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)
# 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 = ('/', ',', ':'))
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
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)
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)
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)
# 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):
{'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)
# 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)
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}
"""
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
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._vpp_interfaces = list()
-
+
#--------------------------------------------------------------------------
# Resource lifecycle
#--------------------------------------------------------------------------
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)
# 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
# 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()
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)
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):
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 #####