2016-09-27 13:03:14 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*-coding: utf-8 -*-
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
import logging
|
2016-09-26 16:11:22 +00:00
|
|
|
import time
|
|
|
|
import subprocess
|
|
|
|
import zmq
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
__author__ = "Sven Zehl, Anatolij Zubow"
|
|
|
|
__copyright__ = "Copyright (c) 2016, Technische Universität Berlin"
|
|
|
|
__version__ = "1.0.0"
|
|
|
|
__email__ = "{zehl, zubow}@tkn.tu-berlin.de"
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
"""
|
2016-09-27 11:45:20 +00:00
|
|
|
Class for controlling the hybrid TDMA/CSMA MAC.
|
2016-09-26 16:11:22 +00:00
|
|
|
"""
|
2016-09-27 11:45:20 +00:00
|
|
|
class HybridTDMACSMAMac(object):
|
2016-09-27 13:03:14 +00:00
|
|
|
def __init__(self, log, interface, no_slots_in_superframe, slot_duration_ns,
|
2016-09-27 11:45:20 +00:00
|
|
|
hmac_binary_path='hmac_userspace_daemon/hmac_userspace_daemon',
|
|
|
|
local_mac_processor_port=1217):
|
|
|
|
'''
|
|
|
|
The configuration of such a MAC is described by:
|
|
|
|
:param interface: the wireless interface on which we want to install the HMAC
|
|
|
|
:param no_slots_in_superframe: the total number of slots in a superframe
|
|
|
|
:param slot_duration_ns: the time duration of each slot (microseconds)
|
|
|
|
:param hmac_binary_path: path to the C++ userland HMAC daemon
|
|
|
|
:param local_mac_processor_port: ZeroMQ port used for communication with HMAC daemon
|
|
|
|
'''
|
2016-09-27 13:03:14 +00:00
|
|
|
self.log = log
|
2016-09-27 11:45:20 +00:00
|
|
|
|
|
|
|
self.interface = interface
|
2016-09-26 16:11:22 +00:00
|
|
|
self.mNo_slots_in_superframe = no_slots_in_superframe
|
|
|
|
self.mSlot_duration_ns = slot_duration_ns
|
|
|
|
self.acs = []
|
|
|
|
for ii in range(no_slots_in_superframe):
|
|
|
|
self.acs.append(None)
|
2016-09-27 11:45:20 +00:00
|
|
|
# path to the HMAC C++ userland daemon
|
|
|
|
self.hmac_binary_path = hmac_binary_path
|
|
|
|
self.local_mac_processor_port = local_mac_processor_port
|
|
|
|
self.state = MACState.NOT_RUNNING
|
|
|
|
|
|
|
|
|
|
|
|
def getInterface(self):
|
|
|
|
'''
|
|
|
|
Returns the wireless interface
|
|
|
|
'''
|
|
|
|
return self.interface
|
|
|
|
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
def getNumSlots(self):
|
2016-09-27 11:45:20 +00:00
|
|
|
'''
|
|
|
|
Get the total number of slots in superframe
|
|
|
|
'''
|
2016-09-26 16:11:22 +00:00
|
|
|
return self.mNo_slots_in_superframe
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
|
|
|
|
def setAccessPolicy(self, slot_nr, ac):
|
|
|
|
'''
|
|
|
|
Sets an access policy to a given slot in the superframe
|
|
|
|
:param slot_nr: the slot id to which the access policy to apply
|
|
|
|
:param ac: the access policy (AccessPolicy class)
|
|
|
|
:return: True if correct
|
|
|
|
'''
|
|
|
|
if slot_nr >= 0 and slot_nr < len(self.acs):
|
|
|
|
self.acs[slot_nr] = ac
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
def getAccessPolicy(self, slot_nr):
|
2016-09-27 11:45:20 +00:00
|
|
|
'''
|
|
|
|
Get the access policy assigned to given slot.
|
|
|
|
:param slot_nr: ID starting from 0.
|
|
|
|
:return: AccessPolicy object
|
|
|
|
'''
|
|
|
|
if slot_nr >= 0 and slot_nr < len(self.acs):
|
|
|
|
return self.acs[slot_nr]
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def removeAccessPolicy(self, slot_nr):
|
|
|
|
'''
|
|
|
|
Removes the access policy assigned to given slot.
|
|
|
|
:param slot_nr: ID starting from 0.
|
|
|
|
:return: True
|
|
|
|
'''
|
|
|
|
if slot_nr >= 0 and slot_nr < len(self.acs):
|
|
|
|
self.acs[slot_nr] = None
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
def getSlotDuration(self):
|
2016-09-27 11:45:20 +00:00
|
|
|
'''
|
|
|
|
Get time duration of a slot
|
|
|
|
'''
|
2016-09-26 16:11:22 +00:00
|
|
|
return self.mSlot_duration_ns
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
|
2016-09-26 16:11:22 +00:00
|
|
|
def printConfiguration(self):
|
2016-09-27 11:45:20 +00:00
|
|
|
'''
|
|
|
|
Return the MAC configuration serialized as string.
|
|
|
|
:return:
|
|
|
|
'''
|
2016-09-27 13:48:27 +00:00
|
|
|
self.log.info('[')
|
2016-09-26 16:11:22 +00:00
|
|
|
for ii in range(self.getNumSlots()):
|
2016-09-27 13:48:27 +00:00
|
|
|
nline = str(ii) + ': ' + self.getAccessPolicy(ii).printConfiguration()
|
|
|
|
self.log.info(nline)
|
|
|
|
self.log.info(']')
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
def install_mac_processor(self):
|
|
|
|
'''
|
|
|
|
Installs the given hybrid MAC configuration
|
|
|
|
:return: True if successful
|
|
|
|
'''
|
|
|
|
self.log.debug('install_mac_processor()')
|
2016-09-26 16:11:22 +00:00
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
if self.state == MACState.RUNNING:
|
|
|
|
self.log.warn('HMAC is already running; use update_mac_processor() to update at run-time')
|
|
|
|
return False
|
2016-09-26 16:11:22 +00:00
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
try:
|
|
|
|
# 1. create HMAC configuration string
|
|
|
|
conf_str = self._create_configuration_string()
|
2016-09-26 16:11:22 +00:00
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
# construct command argument for HMAC daemon
|
2016-09-27 13:03:14 +00:00
|
|
|
processArgs = str(self.hmac_binary_path) + " -d 0 " + " -i" + str(self.interface) \
|
2016-09-27 11:45:20 +00:00
|
|
|
+ " -f" + str(self.getSlotDuration()) + " -n" + str(self.getNumSlots()) + " -c" + conf_str
|
2016-09-26 16:11:22 +00:00
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
self.log.debug('Starting HMAC daemon with: %s' % processArgs)
|
|
|
|
|
|
|
|
# start HMAC daemon as a background process
|
|
|
|
subprocess.Popen(processArgs.split(), shell=False)
|
|
|
|
|
|
|
|
self.hmac_ctrl_socket = None
|
|
|
|
self.state = MACState.RUNNING
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
|
|
self.log.fatal("An error occurred while starting HMAC daemon, err_msg: %s" % str(e))
|
|
|
|
|
|
|
|
return False
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
def update_mac_processor(self):
|
|
|
|
'''
|
|
|
|
Updates the given hybrid MAC configuration at run-time with new configuration
|
|
|
|
:return: True if successful
|
|
|
|
'''
|
|
|
|
|
|
|
|
self.log.debug('update_mac_processor()')
|
|
|
|
|
|
|
|
if self.state == MACState.NOT_RUNNING:
|
|
|
|
self.log.info('HMAC is not yet running running; start it')
|
|
|
|
return self.install_mac_processor()
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
try:
|
2016-09-27 11:45:20 +00:00
|
|
|
# 1. create HMAC configuration string
|
|
|
|
conf_str = self._create_configuration_string()
|
|
|
|
|
|
|
|
if self.hmac_ctrl_socket is None:
|
|
|
|
context = zmq.Context()
|
|
|
|
self.hmac_ctrl_socket = context.socket(zmq.REQ)
|
|
|
|
self.hmac_ctrl_socket.connect("tcp://localhost:" + str(self.local_mac_processor_port))
|
|
|
|
|
|
|
|
# update MAC processor configuration
|
|
|
|
self.log.info("Send ctrl req message to HMAC: %s" % conf_str)
|
|
|
|
self.hmac_ctrl_socket.send(conf_str)
|
|
|
|
message = self.hmac_ctrl_socket.recv()
|
|
|
|
self.log.info("Received ctrl reply message from HMAC: %s" % message)
|
2016-09-26 16:11:22 +00:00
|
|
|
return True
|
2016-09-27 11:45:20 +00:00
|
|
|
except zmq.ZMQError as e:
|
|
|
|
self.log.fatal("Failed to update running HMAC daemon, err_msg: %s" % str(e))
|
2016-09-26 16:11:22 +00:00
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
return False
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
def uninstall_mac_processor(self):
|
|
|
|
'''
|
|
|
|
Uninstalls the running hybrid MAC
|
|
|
|
:return: True if successful
|
|
|
|
'''
|
2016-09-26 16:11:22 +00:00
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
self.log.debug('uninstall_mac_processor')
|
|
|
|
|
|
|
|
if self.state == MACState.NOT_RUNNING:
|
|
|
|
self.log.warn('HMAC is already stopped')
|
|
|
|
return True
|
|
|
|
|
|
|
|
try:
|
|
|
|
# set allow all configuration string
|
|
|
|
conf_str = self._create_allow_all_conf_string()
|
|
|
|
|
|
|
|
# command string
|
|
|
|
terminate_str = 'TERMINATE'
|
|
|
|
|
|
|
|
if self.hmac_ctrl_socket is None:
|
|
|
|
context = zmq.Context()
|
|
|
|
self.hmac_ctrl_socket = context.socket(zmq.REQ)
|
|
|
|
self.hmac_ctrl_socket.connect("tcp://localhost:" + str(self.local_mac_processor_port))
|
|
|
|
|
|
|
|
# update MAC processor configuration
|
|
|
|
self.log.info("Send ctrl req message to HMAC: %s" % conf_str)
|
|
|
|
self.hmac_ctrl_socket.send(conf_str)
|
|
|
|
message = self.hmac_ctrl_socket.recv()
|
|
|
|
self.log.info("Received ctrl reply from HMAC: %s" % message)
|
|
|
|
|
|
|
|
# give one second to settle down
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
# send termination signal to MAC
|
|
|
|
self.hmac_ctrl_socket.send(terminate_str)
|
|
|
|
message = self.hmac_ctrl_socket.recv()
|
|
|
|
self.log.info("Received ctrl reply from HMAC: %s" % message)
|
|
|
|
|
|
|
|
self.state = MACState.NOT_RUNNING
|
|
|
|
return True
|
|
|
|
except zmq.ZMQError as e:
|
|
|
|
self.log.fatal("Failed to uninstall MAC processor %s" % str(e))
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
''' Helper '''
|
|
|
|
def _create_configuration_string(self):
|
2016-09-26 16:11:22 +00:00
|
|
|
conf_str = None
|
2016-09-27 11:45:20 +00:00
|
|
|
for ii in range(self.getNumSlots()): # for each slot
|
|
|
|
ac = self.getAccessPolicy(ii)
|
2016-09-26 16:11:22 +00:00
|
|
|
entries = ac.getEntries()
|
|
|
|
|
|
|
|
for ll in range(len(entries)):
|
|
|
|
entry = entries[ll]
|
|
|
|
|
|
|
|
# slot_id, mac_addr, tid_mask
|
|
|
|
if conf_str is None:
|
|
|
|
conf_str = str(ii) + "," + str(entry[0]) + "," + str(entry[1])
|
|
|
|
else:
|
|
|
|
conf_str = conf_str + "#" + str(ii) + "," + str(entry[0]) + "," + str(entry[1])
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
return conf_str
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
''' Helper '''
|
|
|
|
def _create_allow_all_conf_string(self):
|
2016-09-26 16:11:22 +00:00
|
|
|
# generate configuration string
|
|
|
|
conf_str = None
|
2016-09-27 11:45:20 +00:00
|
|
|
for ii in range(self.getNumSlots()): # for each slot
|
2016-09-26 16:11:22 +00:00
|
|
|
# slot_id, mac_addr, tid_mask
|
|
|
|
if conf_str is None:
|
|
|
|
conf_str = str(ii) + "," + 'FF:FF:FF:FF:FF:FF' + "," + str(255)
|
|
|
|
else:
|
|
|
|
conf_str = conf_str + "#" + str(ii) + "," + 'FF:FF:FF:FF:FF:FF' + "," + str(255)
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
return conf_str
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
class MACState:
|
|
|
|
RUNNING, NOT_RUNNING = range(2)
|
2016-09-26 16:11:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
"""
|
2016-09-27 11:45:20 +00:00
|
|
|
Class for controlling the access policy of each time slot using the destination MAC address and IP ToS value.
|
2016-09-26 16:11:22 +00:00
|
|
|
"""
|
|
|
|
class AccessPolicy(object):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.entries = []
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
|
2016-09-26 16:11:22 +00:00
|
|
|
def disableAll(self):
|
2016-09-27 11:45:20 +00:00
|
|
|
'''
|
|
|
|
Block usage of time slot for all packets
|
|
|
|
'''
|
2016-09-26 16:11:22 +00:00
|
|
|
self.entries = []
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
|
2016-09-26 16:11:22 +00:00
|
|
|
def allowAll(self):
|
2016-09-27 11:45:20 +00:00
|
|
|
'''
|
|
|
|
Unblock usage of time slot for all packets
|
|
|
|
'''
|
2016-09-26 16:11:22 +00:00
|
|
|
self.entries = []
|
|
|
|
self.entries.append(('FF:FF:FF:FF:FF:FF', 255))
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
|
2016-09-26 16:11:22 +00:00
|
|
|
def addDestMacAndTosValues(self, dstHwAddr, *tosArgs):
|
2016-09-27 11:45:20 +00:00
|
|
|
"""Add destination mac address and list of ToS fields which is allowed to be transmitted in this time slot
|
2016-09-26 16:11:22 +00:00
|
|
|
:param dstHwAddr: destination mac address
|
|
|
|
:param tosArgs: list of ToS values to be allowed here
|
|
|
|
"""
|
|
|
|
tid_map = 0
|
|
|
|
for ii in range(len(tosArgs)):
|
|
|
|
# convert ToS into tid
|
|
|
|
tos = tosArgs[ii]
|
|
|
|
skb_prio = tos & 30 >> 1
|
|
|
|
tid =skb_prio & 7
|
|
|
|
tid_map = tid_map | 2**tid
|
|
|
|
|
|
|
|
self.entries.append((dstHwAddr, tid_map))
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
|
2016-09-26 16:11:22 +00:00
|
|
|
def getEntries(self):
|
2016-09-27 11:45:20 +00:00
|
|
|
'''
|
|
|
|
Get saved entries
|
|
|
|
'''
|
2016-09-26 16:11:22 +00:00
|
|
|
return self.entries
|
|
|
|
|
2016-09-27 11:45:20 +00:00
|
|
|
|
2016-09-26 16:11:22 +00:00
|
|
|
def printConfiguration(self):
|
2016-09-27 11:45:20 +00:00
|
|
|
'''
|
|
|
|
For debugging
|
|
|
|
'''
|
2016-09-26 16:11:22 +00:00
|
|
|
s = ''
|
|
|
|
for ii in range(len(self.entries)):
|
|
|
|
s = str(self.entries[ii][0]) + "/" + str(self.entries[ii][1]) + "," + s
|
|
|
|
return s
|