diff options
author | Jordan Augé <jordan.auge+fdio@email.com> | 2017-02-24 14:58:01 +0100 |
---|---|---|
committer | Jordan Augé <jordan.auge+fdio@cisco.com> | 2017-02-24 18:36:29 +0000 |
commit | 85a341d645b57b7cd88a26ed2ea0a314704240ea (patch) | |
tree | bdda2b35003aae20103a796f86daced160b8a730 /vicn/resource/ns3 | |
parent | 9b30fc10fb1cbebe651e5a107e8ca5b24de54675 (diff) |
Initial commit: vICN
Change-Id: I7ce66c4e84a6a1921c63442f858b49e083adc7a7
Signed-off-by: Jordan Augé <jordan.auge+fdio@cisco.com>
Diffstat (limited to 'vicn/resource/ns3')
-rw-r--r-- | vicn/resource/ns3/__init__.py | 0 | ||||
-rw-r--r-- | vicn/resource/ns3/emulated_channel.py | 209 | ||||
-rw-r--r-- | vicn/resource/ns3/emulated_lte_channel.py | 188 | ||||
-rw-r--r-- | vicn/resource/ns3/emulated_wifi_channel.py | 148 |
4 files changed, 545 insertions, 0 deletions
diff --git a/vicn/resource/ns3/__init__.py b/vicn/resource/ns3/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/vicn/resource/ns3/__init__.py diff --git a/vicn/resource/ns3/emulated_channel.py b/vicn/resource/ns3/emulated_channel.py new file mode 100644 index 00000000..08d7a14b --- /dev/null +++ b/vicn/resource/ns3/emulated_channel.py @@ -0,0 +1,209 @@ +#!/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. +# + +import logging +import random + +from netmodel.model.type import Integer +from netmodel.util.socket import check_port +from vicn.core.address_mgr import AddressManager +from vicn.core.attribute import Attribute, Multiplicity +from vicn.core.exception import ResourceNotFound +from vicn.core.requirement import Requirement +from vicn.core.resource import BaseResource +from vicn.core.resource_mgr import wait_resources +from vicn.core.task import inline_task, async_task, task +from vicn.core.task import BashTask, run_task +from vicn.resource.channel import Channel +from vicn.resource.linux.application import LinuxApplication as Application +from vicn.resource.linux.net_device import NetDevice +from vicn.resource.linux.tap_device import TapDevice +from vicn.resource.linux.veth_pair import VethPair +from vicn.resource.lxd.lxc_container import LxcContainer +from vicn.resource.node import Node + +log = logging.getLogger(__name__) + +class EmulatedChannel(Channel, Application): + """EmulatedChannel resource + + This resources serves as a base class for wireless channels emulated by + means of ns3 simulation. + + Attributes: + ap (Reference[node]): Reference to the AP node + stations (Reference[node]): Reference to the list of stations. + control_port (int): Port used to communicate with the management + interface of the simulation. + + Implementation notes: + - Both AP and stations are allocated a separate VLAN to isolate broadcast + traffic and prevent loops on the bridge. + - We also need that all interfaces related to ap and stations are created + before we run the commandline (currently, dynamically adding/removing + AP and stations is not supported by the emulator). This is made + possible thanks to the key=True parameter, which makes sure the + attributes are processed before the __create__ is called. + + Todo: + - Retrieve the process PID to kill it during __delete__ + """ + + __resource_type__ = BaseResource + + ap = Attribute(Node, description = 'AP', key = True) + stations = Attribute(Node, description = 'List of stations', + multiplicity = Multiplicity.OneToMany, key = True) + control_port = Attribute(Integer, + description = 'Control port for the simulation') + + # Overloaded attributes + node = Attribute(requirements = [ + Requirement('bridge') + ]) + + #-------------------------------------------------------------------------- + # Constructor + #-------------------------------------------------------------------------- + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # AP (resp. stations) interfaces (for external connectivity) and + # tap_devices (for connection to emulator) + self._ap_if = None + self._ap_tap = None + self._sta_ifs = dict() + self._sta_taps = dict() + + # Device names to be attached to the bridge (differs according to the + # node type, Physical or LxcContainer, and eventually None for an + # unmanaged stations) + self._ap_bridged = None + self._sta_bridged = dict() + + #-------------------------------------------------------------------------- + # Resource lifecycle + #-------------------------------------------------------------------------- + + @inline_task + def __get__(self): + raise ResourceNotFound + + def __create__(self): + # NOTE: http://stackoverflow.com/questions/21141352/python-subprocess- + # calling-a-script-which-runs-a-background-process-hanging + # The output of the background scripts is still going to the same file + # descriptor as the child script, thus the parent script waits for it + # to finish. + cmd = '(' + self.__app_name__ + ' ' + self._get_cmdline_params() + \ + '>/dev/null 2>&1) &' + return BashTask(self.node, cmd) + + def __delete__(self): + raise NotImplementedError + + #-------------------------------------------------------------------------- + # Attribute handlers + #-------------------------------------------------------------------------- + + @async_task + async def _set_ap(self, ap=None): + if ap is None: + ap = self.ap + if ap is None: + log.info('Ignored setting ap to None...') + return + + # Add a WiFi interface for the AP... + interfaces = list() + if isinstance(ap, LxcContainer): + # Ideally, We need to create a VethPair for each station + # This should be monitored for the total channel bw + host = NetDevice(node = ap.node, + device_name='vhh-' + ap.name + '-' + self.name, + monitored = False, + managed = False) + self._ap_if = VethPair(node = self.ap, + name = 'vh-' + ap.name + '-' + self.name, + device_name = 'vh-' + ap.name + '-' + self.name, + host = host, + owner = self) + self._ap_bridged = self._ap_if.host + else: + raise NotImplementedError + self._state.manager.commit_resource(self._ap_if) + + interfaces.append(self._ap_if) + + # Add a tap interface for the AP... + self._ap_tap = TapDevice(node = self.node, + owner = self, + device_name = 'tap-' + ap.name + '-' + self.name, + up = True, + promiscuous = True, + monitored = False) + self._state.manager.commit_resource(self._ap_tap) + interfaces.append(self._ap_tap) + + # Wait for interfaces to be setup + await wait_resources(interfaces) + + # NOTE: only set channel after the resource is created or it might + # create loops which, at this time, are not handled + self._ap_if.set('channel', self) + + # Add interfaces to bridge + vlan = AddressManager().get('vlan', self, tag='ap') + + # AS the container has created the VethPair already without Vlan, we + # need to delete and recreate it + task = self.node.bridge._remove_interface(self._ap_bridged) + await run_task(task, self._state.manager) + task = self.node.bridge._add_interface(self._ap_bridged, vlan = vlan) + await run_task(task, self._state.manager) + + task = self.node.bridge._add_interface(self._ap_tap, vlan = vlan) + await run_task(task, self._state.manager) + + print('/!\ pass information to the running simulation') + + @inline_task + def _get_ap(self): + return {'ap': None} + + @inline_task + def _get_stations(self): + return {'stations': list()} + + @async_task + async def _set_stations(self, stations=None): + print('adding stations...') + if stations is None: + stations = self.stations + + for station in stations: + await self._add_station(station) + + def _add_stations(self, stations): + raise NotImplementedError + + @inline_task + def _remove_stations(self, station): + raise NotImplementedError + diff --git a/vicn/resource/ns3/emulated_lte_channel.py b/vicn/resource/ns3/emulated_lte_channel.py new file mode 100644 index 00000000..8c7382cb --- /dev/null +++ b/vicn/resource/ns3/emulated_lte_channel.py @@ -0,0 +1,188 @@ +#!/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.address_mgr import AddressManager +from vicn.core.resource_mgr import wait_resources +from vicn.core.task import run_task +from vicn.resource.ns3.emulated_channel import EmulatedChannel +from vicn.resource.linux.net_device import NetDevice + +DEFAULT_FADING_ENABLED = True +DEFAULT_TW_BUFFER = 800000 +DEFAULT_NETMASK = 24 + +class EmulatedLteChannel(EmulatedChannel): + """ + Resource: EmulatedLteChannel + + This resource uses ns3 based emulation to emulate a lte subnet with one pgw, + one enode B, and multiple UEs. + + NOTE: + This model needs to be extended with LTE specific features like "network + resource" in the future. Currently it works in same way as wifi emulator + with no lte specific features. + + Attributes: + ap (Reference[node]): Reference to the AP/pgw node + stations (Reference[node]): Reference to the list of UE. + control_port (int): Port used to communicate with the management + interface of the simulation. + """ + + __package_names__ = ['lte-emulator'] + __app_name__ = 'lte_emulator' + + #--------------------------------------------------------------------------- + # Attribute handlers + #--------------------------------------------------------------------------- + + async def _add_station(self, station): + from vicn.resource.lxd.lxc_container import LxcContainer + from vicn.resource.linux.veth_pair import VethPair + from vicn.resource.linux.tap_device import TapChannel + + interfaces = list() + # ... and each station + if not station.managed: + sta_if = None + else: + if isinstance(station, LxcContainer): + host = NetDevice(node = station.node, + device_name='vhh-' + station.name + '-' + self.name, + managed = False) + sta_if = VethPair(node = station, + name = 'vh-' + station.name + '-' + self.name, + device_name = 'vh-' + station.name + '-' + self.name, + host = host, + owner = self) + bridged_sta = sta_if.host + else: + raise NotImplementedError + + if sta_if: + self._sta_ifs[station] = sta_if + self._sta_bridged[station] = bridged_sta + interfaces.append(sta_if) + self._state.manager.commit_resource(sta_if) + + sta_tap = TapChannel(node = self.node, + owner = self, + device_name = 'tap-' + station.name + '-' + self.name, + up = True, + promiscuous = True, + station_name = station.name, + channel_name = self.name) + self._sta_taps[station] = sta_tap + interfaces.append(sta_tap) + self._state.manager.commit_resource(sta_tap) + + # Wait for interfaces to be setup + await wait_resources(interfaces) + + # Add interfaces to bridge + # One vlan per station is needed to avoid broadcast loops + vlan = AddressManager().get('vlan', sta_tap) + if sta_if: + sta_if.set('channel', self) + + task = self.node.bridge._remove_interface(bridged_sta) + await run_task(task, self._state.manager) + + task = self.node.bridge._add_interface(bridged_sta, + vlan = vlan) + await run_task(task, self._state.manager) + + task = self.node.bridge._add_interface(sta_tap, vlan = vlan) + await run_task(task, self._state.manager) + + def _get_cmdline_params(self): + + # IP have not been assign, use AddressManager for simplicity since it + # will remember the assignment + # NOTE: here the IP address passed to emulator program is hardcoded with + # a /24 mask(even if the associated IP with the station does not have a + # /24 mask). This is not a problem at all because the netmask passed to + # the emulator program has no impact on configuration in the emulator + # program. Indeed, the IP routing table in the emulator program are + # configured on a per address basis(one route per IP address) instead of + # on a per prefix basis(one route per prefix). This guarantees the IP + # routing will not change regardless of what netmask is. That is why we + # can always safely pass a hardcoded /24 mask to the emulator program. + + sta_list = list() # list of identifiers + sta_macs = list() # list of macs + sta_taps = list() + sta_ips = list() + for station in self.stations: + if not station.managed: + interface = [i for i in station.interfaces if i.channel == self] + assert len(interface) == 1 + interface = interface[0] + + sta_list.append(interface.name) + sta_macs.append(interface.mac_address) + sta_ips.append(interface.ip_address + '/24') + else: + identifier = self._sta_ifs[station]._state.uuid._uuid + sta_list.append(identifier) + + mac = self._sta_ifs[station].mac_address + sta_macs.append(mac) + + # Preallocate IP address + ip = AddressManager().get_ip(self._sta_ifs[station]) + '/24' + sta_ips.append(ip) + + tap = self._sta_taps[station].device_name + sta_taps.append(tap) + + params = { + # Name of the tap between NS3 and the base station + 'bs-tap' : self._ap_tap.device_name, + # Number of stations + 'n-sta' : len(self._sta_taps), + # List of the stations of the simulation + 'sta-list' : ','.join(sta_list), + # List of the taps between NS3 and the mobile stations + 'sta-taps' : ','.join(sta_taps), + # List of the macs of the mobile stations + 'sta-macs' : ','.join(sta_macs), + # X position of the Base Station + 'bs-x' : 0, #self.ap.x, + # Y position of the Base Station + 'bs-y' : 0, #self.ap.y, + # Experiment ID + 'experiment-id' : 'vicn', + # Index of the base station + 'bs-name' : self._ap_tap.device_name, + # Base station IP address + 'bs-mac' : self._ap_if.mac_address, + # Control port for dynamically managing the stations movement + 'control-port' : self.control_port, + # Coma-separated list of stations' IP/netmask len + 'sta-ips' : ','.join(sta_ips), + # Base station IP/netmask len + 'bs-ip' : AddressManager().get_ip(self._ap_if) + + DEFAULT_NETMASK, + 'txBuffer' : '800000', + 'isFading' : 'true' if DEFAULT_FADING_ENABLED else 'false', + } + + return ' '.join(['--{}={}'.format(k, v) for k, v in params.items()]) + diff --git a/vicn/resource/ns3/emulated_wifi_channel.py b/vicn/resource/ns3/emulated_wifi_channel.py new file mode 100644 index 00000000..088d4444 --- /dev/null +++ b/vicn/resource/ns3/emulated_wifi_channel.py @@ -0,0 +1,148 @@ +#!/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.resource.ns3.emulated_channel import EmulatedChannel +from vicn.resource.linux.net_device import NetDevice +from vicn.core.address_mgr import AddressManager +from vicn.core.resource_mgr import wait_resources +from vicn.core.task import async_task, run_task + +class EmulatedWiFiChannel(EmulatedChannel): + """EmulatedWiFiChannel Resource + + This resource uses the ns3 simulator to emulate an 80211n wireless channel. + It assume no interference between base stations, and thus the emulated + channel corresponds to a single base station. + """ + __package_names__ = ['wifi-emulator'] + __app_name__ = 'wifi_emulator' + + #--------------------------------------------------------------------------- + # Attribute handlers + #--------------------------------------------------------------------------- + + async def _add_station(self, station): + from vicn.resource.lxd.lxc_container import LxcContainer + from vicn.resource.linux.veth_pair import VethPair + from vicn.resource.linux.tap_device import TapChannel + from vicn.resource.linux.macvlan import MacVlan + + interfaces = list() + if not station.managed: + sta_if = None + else: + if isinstance(station, LxcContainer): + host = NetDevice(node = station.node, + device_name='vhh-' + station.name + '-' + self.name, + managed = False) + sta_if = VethPair(node = station, + name = 'vh-' + station.name + '-' + self.name, + device_name = 'vh-' + station.name + '-' + self.name, + host = host, + owner = self) + bridged_sta = sta_if.host + else: + raise NotImplementedError + + if sta_if: + self._sta_ifs[station] = sta_if + self._sta_bridged[station] = bridged_sta + interfaces.append(sta_if) + self._state.manager.commit_resource(sta_if) + + sta_tap = TapChannel(node = self.node, + owner = self, + device_name = 'tap-' + station.name + '-' + self.name, + up = True, + promiscuous = True, + station_name = station.name, + channel_name = self.name) + self._sta_taps[station] = sta_tap + interfaces.append(sta_tap) + self._state.manager.commit_resource(sta_tap) + + # Wait for interfaces to be setup + await wait_resources(interfaces) + + # Add interfaces to bridge + # One vlan per station is needed to avoid broadcast loops + vlan = AddressManager().get('vlan', sta_tap) + # sta_tap choosen because always there + if sta_if: + sta_if.set('channel', self) + + task = self.node.bridge._remove_interface(bridged_sta) + await run_task(task, self._state.manager) + task = self.node.bridge._add_interface(bridged_sta, + vlan = vlan) + await run_task(task, self._state.manager) + + task = self.node.bridge._add_interface(sta_tap, vlan = vlan) + await run_task(task, self._state.manager) + + + def _get_cmdline_params(self, ): + + # sta-macs and sta-list for unmanaged stations + sta_list = list() # list of identifiers + sta_macs = list() # list of macs + sta_taps = list() + for station in self.stations: + if not station.managed: + interface = [i for i in station.interfaces if i.channel == self] + assert len(interface) == 1 + interface = interface[0] + + sta_list.append(interface.name) + sta_macs.append(interface.mac_address) + else: + identifier = self._sta_ifs[station]._state.uuid._uuid + sta_list.append(identifier) + + mac = self._sta_ifs[station].mac_address + sta_macs.append(mac) + + tap = self._sta_taps[station].device_name + sta_taps.append(tap) + + params = { + # Name of the tap between NS3 and the base station + 'bs-tap' : self._ap_tap.device_name, + # Number of stations + 'n-sta' : len(self._sta_taps), + # List of the stations of the simulation # identifiers + 'sta-list' : ','.join(sta_list), + # List of the taps between NS3 and the mobile stations + 'sta-taps' : ','.join(sta_taps), + # List of the macs of the mobile stations + 'sta-macs' : ','.join(sta_macs), + # X position of the Base Station + 'bs-x' : 0, #self.ap.x, + # Y position of the Base Station + 'bs-y' : 0, #self.ap.y, + # Experiment ID + 'experiment-id' : 'vicn', + # Index of the base station + 'bs-name' : self._ap_tap.device_name, + # Base station MAC address + 'bs-mac' : self._ap_if.mac_address, + # Control port for dynamically managing the stations movement + 'control-port' : self.control_port, + } + + return ' '.join(['--{}={}'.format(k, v) for k, v in params.items()]) |