aboutsummaryrefslogtreecommitdiffstats
path: root/vicn/resource/ns3
diff options
context:
space:
mode:
authorJordan Augé <jordan.auge+fdio@email.com>2017-02-24 14:58:01 +0100
committerJordan Augé <jordan.auge+fdio@cisco.com>2017-02-24 18:36:29 +0000
commit85a341d645b57b7cd88a26ed2ea0a314704240ea (patch)
treebdda2b35003aae20103a796f86daced160b8a730 /vicn/resource/ns3
parent9b30fc10fb1cbebe651e5a107e8ca5b24de54675 (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__.py0
-rw-r--r--vicn/resource/ns3/emulated_channel.py209
-rw-r--r--vicn/resource/ns3/emulated_lte_channel.py188
-rw-r--r--vicn/resource/ns3/emulated_wifi_channel.py148
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()])