def handle_response(self, message): """ Handle an incoming RSSI_TRIGGER message. Args: message, a RSSI_TRIGGER message Returns: None """ wtp_addr = EtherAddress(message.wtp) if wtp_addr not in RUNTIME.wtps: return wtp = RUNTIME.wtps[wtp_addr] if wtp_addr not in RUNTIME.tenants[self.tenant_id].wtps: return incoming = ResourcePool() hwaddr = EtherAddress(message.hwaddr) channel = message.channel band = message.band incoming.add(ResourceBlock(wtp, hwaddr, channel, band)) matches = wtp.supports & incoming self.event = \ {'block': matches.pop(), 'timestamp': datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ"), 'current': message.current} self.handle_callback(self)
def handle_poller_response(self, message): """Handle an incoming poller response message. Args: message, a poller response message Returns: None """ if message.poller_id not in self.modules: return wtp_addr = EtherAddress(message.wtp) if wtp_addr not in RUNTIME.wtps: return wtp = RUNTIME.wtps[wtp_addr] hwaddr = EtherAddress(message.hwaddr) incoming = ResourcePool() incoming.add(ResourceBlock(wtp, hwaddr, message.channel, message.band)) matching = (wtp.supports & incoming).pop() LOG.info("Received %s response from %s (id=%u)", self.MODULE_NAME, matching, message.poller_id) # find poller object poller = self.modules[message.poller_id] # handle the message map_type = self.MODULE_NAME setattr(poller.block, map_type, CQM()) map_entry = getattr(poller.block, map_type) for entry in message.img_entries: addr = EtherAddress(entry[0]) if not addr.match(poller.addrs): continue value = {'addr': addr, 'last_rssi_std': entry[1] / 1000.0, 'last_rssi_avg': entry[2] / 1000.0, 'last_packets': entry[3], 'hist_packets': entry[4], 'ewma_rssi': entry[5] / 1000.0, 'sma_rssi': entry[6] / 1000.0} map_entry[addr] = value # call callback handle_callback(poller, poller)
def blocks(self, lvap=None, limit=None): """Return all blocks in this Tenant.""" # Initialize the Resource Pool pool = ResourcePool() # Update the Resource Pool with all # the available Resourse Blocks for wtp in self.wtps(): for block in wtp.supports: pool.add(block) return pool
def handle_response(self, response): """Handle an incoming poller response message. Args: message, a poller response message Returns: None """ wtp_addr = EtherAddress(response.wtp) if wtp_addr not in RUNTIME.wtps: return wtp = RUNTIME.wtps[wtp_addr] hwaddr = EtherAddress(response.hwaddr) block = ResourceBlock(wtp, hwaddr, response.channel, response.band) incoming = ResourcePool() incoming.add(block) matching = (wtp.supports & incoming).pop() if not matching: return # handle the message setattr(self.block, self.MODULE_NAME, CQM()) map_entry = getattr(self.block, self.MODULE_NAME) for entry in response.img_entries: addr = EtherAddress(entry[0]) if not addr.match(self.addrs): continue value = {'addr': addr, 'last_rssi_std': entry[1] / 1000.0, 'last_rssi_avg': entry[2] / 1000.0, 'last_packets': entry[3], 'hist_packets': entry[4], 'ewma_rssi': entry[5] / 1000.0, 'sma_rssi': entry[6] / 1000.0} map_entry[addr] = value # call callback self.handle_callback(self)
def handover(lvap, wtps): """ Handover the LVAP to a WTP with an RSSI higher that -65dB. """ # Initialize the Resource Pool pool = ResourcePool() # Update the Resource Pool with all # the available Resourse Blocks for wtp in wtps: pool = pool | wtp.supports # Select matching Resource Blocks matches = pool & lvap.scheduled_on # Filter Resource Blocks by RSSI valid = [block for block in matches if block.ucqm[lvap.addr]['ewma_rssi'] >= -55] # Perform the handover new_block = valid.pop() if valid else None LOG.info("LVAP %s setting new block %s" % (lvap.addr, new_block)) lvap.scheduled_on = new_block # Set port for block in lvap.scheduled_on: port = lvap.scheduled_on[block] port.no_ack = True port.tx_power = 20 port.rts_cts = 3500 port.mcs = [6, 12]
def wtp(self, wtp): """Assigns LVAP to new wtp.""" # Initialize the Resource Pool pool = ResourcePool() # Update the pool with all the available ResourseBlocks for block in wtp.blocks.values(): pool.append(block) # Filter blocks so we are sure to go to the same interface and band blocks = pool.filter_by_channel(self.blocks[0].channel) \ .filter_by_band(self.blocks[0].band) \ .first() self.blocks = blocks
def _on_disconnect(self): """ Handle WTP disconnection """ if not self.wtp: return LOG.info("WTP disconnected: %s", self.wtp.addr) # remove hosted lvaps for addr in list(RUNTIME.lvaps.keys()): lvap = RUNTIME.lvaps[addr] dl_wtps = [block.radio for block in lvap.downlink.keys()] ul_wtps = [block.radio for block in lvap.uplink.keys()] # in case the downlink went down, the remove also the uplinks if self.wtp in dl_wtps: RUNTIME.remove_lvap(lvap.addr) elif self.wtp in ul_wtps: LOG.info("Deleting LVAP (UL): %s", lvap.addr) lvap.clear_uplink() # remove hosted vaps for tenant_id in RUNTIME.tenants.keys(): for vap_id in list(RUNTIME.tenants[tenant_id].vaps.keys()): vap = RUNTIME.tenants[tenant_id].vaps[vap_id] if vap.wtp == self.wtp: LOG.info("Deleting VAP: %s", vap.net_bssid) del RUNTIME.tenants[tenant_id].vaps[vap.net_bssid] # reset state self.wtp.last_seen = 0 self.wtp.connection = None self.wtp.ports = {} self.wtp.supports = ResourcePool() self.wtp = None
def _handle_status_port(cls, status): """Handle an incoming PORT message. Args: status, a STATUS_PORT message Returns: None """ wtp_addr = EtherAddress(status.wtp) try: wtp = RUNTIME.wtps[wtp_addr] except KeyError: LOG.info("Status from unknown WTP %s", wtp_addr) return if not wtp.connection: LOG.info("Status from disconnected WTP %s", wtp_addr) return sta_addr = EtherAddress(status.sta) hwaddr = EtherAddress(status.hwaddr) block = ResourceBlock(wtp, hwaddr, status.channel, status.band) # incoming block pool = ResourcePool() pool.add(block) match = wtp.supports & pool if not match: LOG.error("Incoming block %s is invalid", block) return block = match.pop() LOG.info("Port status from %s, station %s", wtp_addr, sta_addr) tx_policy = block.tx_policies[sta_addr] tx_policy._mcs = set([float(x) / 2 for x in status.mcs]) tx_policy._rts_cts = int(status.rts_cts) tx_policy._mcast = int(status.tx_mcast) tx_policy._ur_count = int(status.ur_mcast_count) tx_policy._no_ack = bool(status.flags.no_ack) LOG.info("Port status %s", tx_policy)
def _handle_status_port(cls, status): """Handle an incoming PORT message. Args: status, a STATUS_PORT message Returns: None """ wtp_addr = EtherAddress(status.wtp) try: wtp = RUNTIME.wtps[wtp_addr] except KeyError: LOG.info("Status from unknown WTP %s", wtp_addr) return if not wtp.connection: LOG.info("Status from disconnected WTP %s", wtp_addr) return sta_addr = EtherAddress(status.sta) hwaddr = EtherAddress(status.hwaddr) block = ResourceBlock(wtp, hwaddr, status.channel, status.band) # incoming block pool = ResourcePool() pool.add(block) match = wtp.supports & pool if not match: LOG.error("Incoming block %s is invalid", block) return block = match.pop() LOG.info("Port status from %s, station %s", wtp_addr, sta_addr) tx_policy = block.tx_policies[sta_addr] tx_policy._mcs = set([float(x)/2 for x in status.mcs]) tx_policy._rts_cts = int(status.rts_cts) tx_policy._mcast = int(status.tx_mcast) tx_policy._ur_count = int(status.ur_mcast_count) tx_policy._no_ack = bool(status.flags.no_ack) LOG.info("Port status %s", tx_policy)
def __init__(self, lvap, block): self._lvap = lvap self._block = block self._no_ack = False self._rts_cts = 2346 match = (lvap.supports & ResourcePool([block])).pop() self._mcs = block.supports & match.supports
def put(self, *args, **kwargs): """Modify the LVAP Args: [0], the project id (mandatory) [1]: the lvap address (mandatory) Example URLs: PUT /api/v1/projects/52313ecb-9d00-4b7d-b873-b55d3d9ada26/lvaps/ 60:F4:45:D0:3B:FC { "version": "1.0", "wtp": "04:F0:21:09:F9:AA" } """ project_id = uuid.UUID(args[0]) project = self.service.projects[project_id] lvap = project.lvaps[EtherAddress(args[0])] if "blocks" in kwargs: addr = EtherAddress(kwargs['wtp']) wtp = self.service.devices[addr] pool = ResourcePool() for block_id in kwargs["blocks"]: pool.append(wtp.blocks[block_id]) lvap.blocks = pool elif "wtp" in kwargs: wtp = self.service.devices[EtherAddress(kwargs['wtp'])] lvap.wtp = wtp if "encap" in kwargs: encap = EtherAddress(kwargs["encap"]) lvap.encap = encap
def block(self, value): if not isinstance(value, ResourceBlock): raise ValueError("Expected ResourceBlock, got %s", type(value)) wtp = RUNTIME.wtps[value.radio.addr] requested = ResourcePool() requested.add(value) match = wtp.supports & requested if len(match) > 1: raise ValueError("More than one block specified") if not match: raise ValueError("No block specified") self._block = match.pop()
def uplink(self, blocks): """Set downlink block. Set the downlink block. Must receive as input a single resource block. """ if isinstance(blocks, ResourcePool): pool = blocks elif isinstance(blocks, ResourceBlock): pool = ResourcePool() pool.add(blocks) else: raise TypeError("Invalid type: %s", type(blocks)) if len(pool) > 5: raise ValueError("Downlink, too many blocks (%u)", len(blocks)) current = ResourcePool(list(self._uplink.keys())) # Null operation, just return if current == pool: return # make sure we are not overwriting a downlink block downlink = ResourcePool(list(self._downlink.keys())) pool = pool - downlink # clear uplink blocks for block in list(self._uplink.keys()): del self._uplink[block] # assign uplink blocks for block in pool: self._uplink[block] = RadioPort(self, block)
def put(self, *args, **kwargs): """Modify the LVAP Args: [0]: the lvap address (mandatory) Example URLs: PUT /api/v1/lvaps/60:F4:45:D0:3B:FC { "version": "1.0", "wtp": "04:F0:21:09:F9:AA" } """ lvap = self.service.lvaps[EtherAddress(args[0])] if "blocks" in kwargs: addr = EtherAddress(kwargs['wtp']) wtp = self.service.devices[addr] pool = ResourcePool() for block_id in kwargs["blocks"]: pool.append(wtp.blocks[block_id]) lvap.blocks = pool elif "wtp" in kwargs: wtp = self.service.devices[EtherAddress(kwargs['wtp'])] lvap.wtp = wtp if "encap" in kwargs: encap = EtherAddress(kwargs["encap"]) lvap.encap = encap
def __init__(self, addr, net_bssid_addr, lvap_bssid_addr): # read only params self.addr = addr self.net_bssid = net_bssid_addr # lvap bssid, this is the bssid to which the client is currently # attached, it can only be updated as a result of an auth request # message self._lvap_bssid = lvap_bssid_addr # the following parameters are only updated upon RX of a lvap status # update message from an agent self.authentication_state = False self.association_state = False # the following parameters are only updated by the controller, which # will then dispatch an add lvap message in order to propagate the # change to the agent self._ssids = [] self._encap = None self._group = 6000 # the following parameters can be updated by both agent and # controller. The controller sets them when a client successfully # associate. The agent sets them upon disassociation. self._assoc_id = 0 self._tenant = None # only one block supported, default block points to this self._downlink = DownlinkPort() # multiple blocks supported, no port configuration supported self._uplink = UplinkPort() # counters self.tx_samples = [] self.rx_samples = [] # rates statistics self.rates = {} # virtual ports (VNFs) self.__ports = {} # downlink intent uuid self.poa_uuid = None # supported resource blocks self.supported = ResourcePool()
def block(self, value): """Set block.""" if isinstance(value, ResourceBlock): self._block = value elif isinstance(value, dict): wtp = RUNTIME.wtps[EtherAddress(value['wtp'])] if 'hwaddr' not in value: raise ValueError("Missing field: hwaddr") if 'channel' not in value: raise ValueError("Missing field: channel") if 'band' not in value: raise ValueError("Missing field: band") if 'wtp' not in value: raise ValueError("Missing field: wtp") incoming = ResourcePool() block = ResourceBlock(wtp, EtherAddress(value['hwaddr']), int(value['channel']), int(value['band'])) incoming.add(block) match = wtp.supports & incoming if not match: raise ValueError("No block specified") if len(match) > 1: raise ValueError("More than one block specified") self._block = match.pop()
def block(self, value): """Set block.""" if isinstance(value, ResourceBlock): self._block = value elif isinstance(value, dict): if 'hwaddr' not in value: raise ValueError("Missing field: hwaddr") if 'channel' not in value: raise ValueError("Missing field: channel") if 'band' not in value: raise ValueError("Missing field: band") if 'wtp' not in value: raise ValueError("Missing field: wtp") wtp = RUNTIME.wtps[EtherAddress(value['wtp'])] incoming = ResourcePool() block = ResourceBlock(wtp, EtherAddress(value['hwaddr']), int(value['channel']), int(value['band'])) incoming.add(block) match = wtp.supports & incoming if not match: raise ValueError("No block specified") if len(match) > 1: raise ValueError("More than one block specified") self._block = match.pop()
def handover(self, lvap): """ Handover the LVAP to a WTP with an RSSI higher that -65dB. """ self.log.info("Running handover...") # Initialize the Resource Pool pool = ResourcePool() # Update the Resource Pool with all # the available Resourse Blocks for wtp in self.wtps(): pool = pool | wtp.supports # Select matching Resource Blocks matches = pool & lvap.scheduled_on # Filter Resource Blocks by RSSI valid = [ block for block in matches if block.ucqm[lvap.addr]['ewma_rssi'] >= -85 ] if not valid: return new_block = max(valid, key=lambda x: x.ucqm[lvap.addr]['ewma_rssi']) self.log.info("LVAP %s setting new block %s" % (lvap.addr, new_block)) lvap.scheduled_on = new_block # Set port for block in lvap.scheduled_on: port = lvap.scheduled_on[block] port.no_ack = True port.rts_cts = 3500 port.mcs = [6, 12, 54]
def _on_disconnect(self): """ Handle WTP disconnection """ if not self.wtp: return LOG.info("WTP disconnected: %s", self.wtp.addr) # reset state self.wtp.last_seen = 0 self.wtp.connection = None self.wtp.ports = {} self.wtp.supports = ResourcePool() # remove hosted LVAPs to_be_removed = [] for lvap in RUNTIME.lvaps.values(): if lvap.wtp == self.wtp: to_be_removed.append(lvap) for lvap in to_be_removed: LOG.info("Deleting LVAP: %s", lvap.addr) for handler in self.server.pt_types_handlers[PT_LVAP_LEAVE]: handler(lvap) lvap.clear_ports() del RUNTIME.lvaps[lvap.addr] # remove hosted vaps to_be_removed = [] for tenant in RUNTIME.tenants.values(): for vap in tenant.vaps.values(): if vap.wtp == self.wtp: to_be_removed.append(vap) for vap in to_be_removed: LOG.info("Deleting VAP: %s", vap.net_bssid) del RUNTIME.tenants[vap.tenant_id].vaps[vap.net_bssid]
def _handle_probe_request(self, wtp, request): """Handle an incoming PROBE_REQUEST message. Args: request, a PROBE_REQUEST message Returns: None """ if not wtp.connection: LOG.info("Probe request from disconnected WTP %s", wtp.addr) self.stream.close() return if not wtp.port(): LOG.info("WTP %s not ready", wtp.addr) return sta = EtherAddress(request.sta) if sta in RUNTIME.lvaps: return if not RUNTIME.is_allowed(sta): return if RUNTIME.is_denied(sta): return ssid = SSID(request.ssid) if request.ssid == b'': LOG.info("Probe request from %s ssid %s", sta, "Broadcast") else: LOG.info("Probe request from %s ssid %s", sta, ssid) # generate list of available SSIDs ssids = set() for tenant in RUNTIME.tenants.values(): if tenant.bssid_type == T_TYPE_SHARED: continue for wtp_in_tenant in tenant.wtps.values(): if wtp.addr == wtp_in_tenant.addr: ssids.add(tenant.tenant_name) if not ssids: LOG.info("No SSIDs available at this WTP") return # spawn new LVAP LOG.info("Spawning new LVAP %s on %s", sta, wtp.addr) net_bssid = generate_bssid(BASE_MAC, sta) lvap = LVAP(sta, net_bssid, net_bssid) lvap.set_ssids(list(ssids)) RUNTIME.lvaps[sta] = lvap # This will trigger an LVAP ADD message (and REMOVE if necessary) lvap.supported = ResourcePool() hwaddr = EtherAddress(request.hwaddr) channel = request.channel band = request.band lvap.supported.add(ResourceBlock(lvap, hwaddr, channel, band)) lvap.scheduled_on = wtp.supports & lvap.supported LOG.info("Sending probe response to %s", lvap.addr) self.send_probe_response(lvap)
def _handle_probe_request(self, request): """Handle an incoming PROBE_REQUEST message. Args: request, a PROBE_REQUEST message Returns: None """ wtp_addr = EtherAddress(request.wtp) try: wtp = RUNTIME.wtps[wtp_addr] except KeyError: LOG.info("Probe request from unknown WTP (%s)", wtp_addr) return if not wtp.connection: LOG.info("Probe request from disconnected WTP %s", wtp_addr) return sta = EtherAddress(request.sta) if sta in RUNTIME.lvaps: return if not RUNTIME.is_allowed(sta): return if RUNTIME.is_denied(sta): return ssid = SSID(request.ssid) if request.ssid == b'': LOG.info("Probe request from %s ssid %s", sta, "Broadcast") else: LOG.info("Probe request from %s ssid %s", sta, ssid) # generate list of available SSIDs ssids = set() for tenant in RUNTIME.tenants.values(): if tenant.bssid_type == T_TYPE_SHARED: continue for wtp_in_tenant in tenant.wtps.values(): if wtp_addr == wtp_in_tenant.addr: ssids.add(tenant.tenant_name) if not ssids: LOG.info("No SSIDs available at this WTP") return # spawn new LVAP LOG.info("Spawning new LVAP %s on %s", sta, wtp.addr) net_bssid = generate_bssid(BASE_MAC, sta) lvap = LVAP(sta, net_bssid, net_bssid) lvap.set_ssids(ssids) RUNTIME.lvaps[sta] = lvap # TODO: This should be built starting from the probe request lvap.supports.add(ResourceBlock(lvap, sta, 1, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 2, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 3, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 4, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 5, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 6, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 7, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 8, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 9, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 10, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 11, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 36, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 48, BT_L20)) # This will trigger an LVAP ADD message (and REMOVE if necessary) requested = ResourcePool() hwaddr = EtherAddress(request.hwaddr) channel = request.channel band = request.band requested.add(ResourceBlock(wtp, hwaddr, channel, band)) lvap.scheduled_on = wtp.supports & requested LOG.info("Sending probe response to %s", lvap.addr) self.send_probe_response(lvap)
def wtp(self, wtp): """Assigns LVAP to new wtp.""" default_block = next(iter(self.scheduled_on.keys())) matching = wtp.supports & ResourcePool([default_block]) self.scheduled_on = matching.pop() if matching else None
def put(self, *args, **kwargs): """ Set the WTP for a given LVAP, effectivelly hands-over the LVAP to another WTP Args: pool_id: the pool address lvap_id: the lvap address Request: version: the protocol version (1.0) wtp: the new wtp address Example URLs: PUT /api/v1/pools/EmPOWER/lvaps/11:22:33:44:55:66 """ try: if len(args) != 2: raise ValueError("Invalid URL") request = tornado.escape.json_decode(self.request.body) if "version" not in request: raise ValueError("missing version element") if "wtp" not in request and "scheduled_on" not in request and \ "encap" not in request: raise ValueError("missing wtp/scheduled_on element") tenant_id = uuid.UUID(args[0]) lvap_addr = EtherAddress(args[1]) tenant = RUNTIME.tenants[tenant_id] lvap = tenant.lvaps[lvap_addr] if "wtp" in request: wtp_addr = EtherAddress(request['wtp']) wtp = tenant.wtps[wtp_addr] lvap.wtp = wtp if "scheduled_on" in request: pool = ResourcePool() for block in request["scheduled_on"]: wtp_addr = EtherAddress(block['wtp']) wtp = RUNTIME.wtps[wtp_addr] channel = int(block['channel']) band = int(block['band']) rb = ResourceBlock(wtp, channel, band) pool.add(rb) lvap.scheduled_on = pool if "encap" in request: encap = EtherAddress(request["encap"]) lvap.encap = encap except KeyError as ex: self.send_error(404, message=ex) except ValueError as ex: self.send_error(400, message=ex) self.set_status(204, None)
def downlink(self, downlink): """Assign default resource block to LVAP. Assign default resource block to LVAP. Accepts as input either a ResourcePool or a ResourceBlock. If the resource pool has more than one resource block then one random resource block is assigned as both downlink and default uplink. The remaining resource blocks are assigned as uplink only. Args: downlink: A ResourcePool or a ResourceBlock """ # Null operation, just return if not downlink: return if isinstance(downlink, ResourcePool): pool = downlink elif isinstance(downlink, ResourceBlock): pool = ResourcePool() pool.add(downlink) else: raise TypeError("Expected ResourcePool, ResourceBlock, got %s", type(downlink)) current = ResourcePool(list(self.downlink.keys()) + list(self.uplink.keys())) # Null operation, just return, but before re-send configuration # commands (ports and of tables) if current == pool: self.set_ports() return # clear downlink blocks for block in list(self.downlink.keys()): del self.downlink[block] # clear uplink blocks for block in list(self.uplink.keys()): del self.uplink[block] # pick default resource block default_block = pool.pop() # assign default port policy to downlink resource block, this will # trigger a send_add_lvap and a set_port (radio) message self.downlink[default_block] = RadioPort(self, default_block) # assign remaining blocks (if any) to the uplink, this could # trigger one or more send_add_lvap and a set_port (radio) messages for block in pool: self.uplink[block] = RadioPort(self, block) # set ports self.set_ports()
def scheduled_on(self, downlink): """Assign default resource block to LVAP. Assign default resource block to LVAP. Accepts as input either a ResourcePool or a ResourceBlock. If the resource pool has more than one resource block then one random resource block is assigned as both downlink and default uplink. The remaining resource blocks are assigned as uplink only. Args: downlink: A ResourcePool or a ResourceBlock """ # Null operation, just return if not downlink: return if isinstance(downlink, ResourcePool): pool = downlink elif isinstance(downlink, ResourceBlock): pool = ResourcePool() pool.add(downlink) else: raise TypeError("Expected ResourcePool, ResourceBlock, got %s", type(downlink)) current = ResourcePool( list(self.downlink.keys()) + list(self.uplink.keys())) # Null operation, just return, but before re-send configuration # commands (ports and of tables) if current == pool: self.set_ports() return # clear downlink blocks for block in list(self.downlink.keys()): del self.downlink[block] # clear uplink blocks for block in list(self.uplink.keys()): del self.uplink[block] # pick default resource block default_block = pool.pop() # If lvap is associated to a shared tenant. I need to reset the lvap # before moving it. if self._tenant and self._tenant.bssid_type == T_TYPE_SHARED: # check if tenant is available at target block base_bssid = self._tenant.get_prefix() net_bssid = generate_bssid(base_bssid, default_block.hwaddr) # if not ignore request if net_bssid not in self._tenant.vaps: LOG.error("VAP %s not found on tenant %s", net_bssid, self._tenant.tenant_name) self.set_ports() return # check if vap is available at target block if net_bssid != self._tenant.vaps[net_bssid].net_bssid: LOG.error("VAP %s not available at target block %s", net_bssid, default_block) self.set_ports() return # otherwise reset lvap self._tenant = None self.association_state = False self.authentication_state = False self._assoc_id = 0 self._lvap_bssid = net_bssid else: self._lvap_bssid = self.net_bssid # assign default port policy to downlink resource block, this will # trigger a send_add_lvap and a set_port (radio) message self.downlink[default_block] = RadioPort(self, default_block) # assign remaining blocks (if any) to the uplink, this could # trigger one or more send_add_lvap and a set_port (radio) messages for block in pool: self.uplink[block] = RadioPort(self, block) # set ports self.set_ports()
def _handle_probe_request(self, request): """Handle an incoming PROBE_REQUEST message. Args: request, a PROBE_REQUEST message Returns: None """ wtp_addr = EtherAddress(request.wtp) try: wtp = RUNTIME.wtps[wtp_addr] except KeyError: LOG.info("Probe request from unknown WTP (%s)", wtp_addr) return if not wtp.connection: LOG.info("Probe request from disconnected WTP %s", wtp_addr) return sta = EtherAddress(request.sta) if sta in RUNTIME.lvaps: return if not RUNTIME.is_allowed(sta): return if RUNTIME.is_denied(sta): return ssid = SSID(request.ssid) if request.ssid == b'': LOG.info("Probe request from %s ssid %s", sta, "Broadcast") else: LOG.info("Probe request from %s ssid %s", sta, ssid) # generate list of available SSIDs ssids = set() for tenant in RUNTIME.tenants.values(): if tenant.bssid_type == T_TYPE_SHARED: continue for wtp_in_tenant in tenant.wtps.values(): if wtp_addr == wtp_in_tenant.addr: ssids.add(tenant.tenant_name) if not ssids: LOG.info("No SSIDs available at this WTP") return # spawn new LVAP LOG.info("Spawning new LVAP %s on %s", sta, wtp.addr) net_bssid = generate_bssid(BASE_MAC, sta) lvap = LVAP(sta, net_bssid, net_bssid) lvap._ssids = ssids RUNTIME.lvaps[sta] = lvap # TODO: This should be built starting from the probe request lvap.supports.add(ResourceBlock(lvap, sta, 1, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 2, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 3, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 4, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 5, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 6, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 7, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 8, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 9, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 10, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 11, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 36, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta, 48, BT_L20)) # This will trigger an LVAP ADD message (and REMOVE if necessary) requested = ResourcePool() hwaddr = EtherAddress(request.hwaddr) channel = request.channel band = request.band requested.add(ResourceBlock(wtp, hwaddr, channel, band)) lvap.scheduled_on = wtp.supports & requested LOG.info("Sending probe response to %s", lvap.addr) self.send_probe_response(lvap)
def _handle_status_lvap(self, wtp, status): """Handle an incoming STATUS_LVAP message. Args: status, a STATUS_LVAP message Returns: None """ if not wtp.connection: LOG.info("Status from disconnected WTP %s", wtp.addr) return sta_addr = EtherAddress(status.sta) set_mask = bool(status.flags.set_mask) lvap = None LOG.info("LVAP status update from %s", sta_addr) # If the LVAP does not exists, then create a new one if sta_addr not in RUNTIME.lvaps: net_bssid_addr = EtherAddress(status.net_bssid) lvap_bssid_addr = EtherAddress(status.lvap_bssid) lvap = LVAP(sta_addr, net_bssid_addr, lvap_bssid_addr) RUNTIME.lvaps[sta_addr] = lvap lvap = RUNTIME.lvaps[sta_addr] # incoming block lvap.supported = ResourcePool() hwaddr = EtherAddress(status.hwaddr) channel = status.channel band = status.band lvap.supported.add(ResourceBlock(lvap, hwaddr, channel, band)) match = wtp.supports & lvap.supported if not match: LOG.error("Incoming block %s is invalid", block) return block = match.pop() # this will try to updated the lvap object with the resource block # coming in this status update message. try: if set_mask: # set downlink+uplink block lvap._downlink.setitem(block, RadioPort(lvap, block)) else: # set uplink only blocks lvap._uplink.setitem(block, RadioPort(lvap, block)) except ValueError: LOG.error("Error while importing block %s, removing.", block) block.radio.connection.send_del_lvap(lvap) return lvap.authentication_state = bool(status.flags.authenticated) lvap.association_state = bool(status.flags.associated) lvap._assoc_id = status.assoc_id lvap._encap = EtherAddress(status.encap) ssids = [SSID(x.ssid) for x in status.ssids] if lvap.ssid: # Raise LVAP leave event self.server.send_lvap_leave_message_to_self(lvap) # removing LVAP from tenant, need first to look for right tenant if lvap.addr in lvap.tenant.lvaps: LOG.info("Removing %s from tenant %s", lvap.addr, lvap.ssid) del lvap.tenant.lvaps[lvap.addr] lvap._tenant = None if ssids[0]: tenant = RUNTIME.load_tenant(ssids[0]) if not tenant: LOG.info("LVAP %s from unknown tenant %s", lvap.addr, ssids[0]) RUNTIME.remove_lvap(lvap.addr) return # setting tenant without seding out add lvap message lvap._tenant = tenant # adding LVAP to tenant LOG.info("Adding %s to tenant %s", lvap.addr, ssids[0]) lvap.tenant.lvaps[lvap.addr] = lvap # Raise LVAP join event self.server.send_lvap_join_message_to_self(lvap) # update remaining ssids lvap._ssids = ssids[1:] # set ports lvap.set_ports() LOG.info("LVAP status %s", lvap)
def __init__(self, addr, label): super().__init__(addr, label) self.supports = ResourcePool()
def downlink(self, blocks): """Set downlink block. Set the downlink block. Must receive as input a single resource block. """ if isinstance(blocks, ResourcePool): pool = blocks elif isinstance(blocks, ResourceBlock): pool = ResourcePool() pool.add(blocks) else: raise TypeError("Invalid type: %s", type(blocks)) if len(pool) > 1: raise ValueError("Downlink, too many blocks (%u)", len(blocks)) current = ResourcePool(list(self._downlink.keys())) # Null operation, just return, but before re-send intent configuration if current == pool: self.set_ports() return # clear downlink blocks for block in list(self._downlink.keys()): del self._downlink[block] # downlink block default_block = pool.pop() # check if block is also in the uplink, if so remove it if default_block in self._uplink: del self._uplink[default_block] # If lvap is associated to a shared tenant. I need to reset the lvap # before moving it. if self._tenant and self._tenant.bssid_type == T_TYPE_SHARED: # check if tenant is available at target block base_bssid = self._tenant.get_prefix() net_bssid = generate_bssid(base_bssid, default_block.hwaddr) # if not ignore request if net_bssid not in self._tenant.vaps: LOG.error("VAP %s not found on tenant %s", net_bssid, self._tenant.tenant_name) self.set_ports() return # check if vap is available at target block if net_bssid != self._tenant.vaps[net_bssid].net_bssid: LOG.error("VAP %s not available at target block %s", net_bssid, default_block) self.set_ports() return # otherwise reset lvap self._tenant = None self.association_state = False self.authentication_state = False self._assoc_id = 0 self._lvap_bssid = net_bssid # assign default port policy to downlink resource block, this will # trigger a send_add_lvap and a set_port (radio) message self._downlink[default_block] = RadioPort(self, default_block) # set ports self.set_ports()
def put(self, *args, **kwargs): """ Set the WTP for a given LVAP, effectivelly hands-over the LVAP to another WTP Args: lvap_id: the lvap address Request: version: the protocol version (1.0) Example URLs: PUT /api/v1/lvaps/11:22:33:44:55:66 """ try: if len(args) != 1: raise ValueError("Invalid URL") request = tornado.escape.json_decode(self.request.body) if "version" not in request: raise ValueError("missing version element") lvap_addr = EtherAddress(args[0]) lvap = RUNTIME.lvaps[lvap_addr] if "wtp" in request: wtp_addr = EtherAddress(request['wtp']) wtp = RUNTIME.wtps[wtp_addr] lvap.wtp = wtp elif "scheduled_on" in request: pool = ResourcePool() for block in request["scheduled_on"]: wtp_addr = EtherAddress(block['wtp']) wtp = RUNTIME.wtps[wtp_addr] hwaddr = EtherAddress(block['hwaddr']) channel = int(block['channel']) band = int(block['band']) r_block = ResourceBlock(wtp, hwaddr, channel, band) pool.add(r_block) lvap.scheduled_on = pool elif "downlink" in request: pool = ResourcePool() for block in request["downlink"]: wtp_addr = EtherAddress(block['wtp']) wtp = RUNTIME.wtps[wtp_addr] hwaddr = EtherAddress(block['hwaddr']) channel = int(block['channel']) band = int(block['band']) r_block = ResourceBlock(wtp, hwaddr, channel, band) pool.add(r_block) lvap.downlink = pool elif "uplink" in request: pool = ResourcePool() for block in request["uplink"]: wtp_addr = EtherAddress(block['wtp']) wtp = RUNTIME.wtps[wtp_addr] hwaddr = EtherAddress(block['hwaddr']) channel = int(block['channel']) band = int(block['band']) r_block = ResourceBlock(wtp, hwaddr, channel, band) pool.add(r_block) lvap.uplink = pool if "encap" in request: encap = EtherAddress(request["encap"]) lvap.encap = encap except KeyError as ex: self.send_error(404, message=ex) except ValueError as ex: self.send_error(400, message=ex) self.set_status(204, None)
def scheduled_on(self): """ Get the resource blocks assigned to this LVAP in the uplink. """ return ResourcePool( list(self._downlink.keys()) + list(self._uplink.keys()))
def scheduled_on(self, downlink): """Assign default resource block to LVAP. Assign default resource block to LVAP. Accepts as input either a ResourcePool or a ResourceBlock. If the resource pool has more than one resource block then one random resource block is assigned as both downlink and default uplink. The remaining resource blocks are assigned as uplink only. Args: downlink: A ResourcePool or a ResourceBlock """ # Null operation, just return if not downlink: return if isinstance(downlink, ResourcePool): pool = downlink elif isinstance(downlink, ResourceBlock): pool = ResourcePool() pool.add(downlink) else: raise TypeError("Expected ResourcePool, ResourceBlock, got %s", type(downlink)) current = ResourcePool(list(self.downlink.keys()) + list(self.uplink.keys())) # Null operation, just return, but before re-send configuration # commands (ports and of tables) if current == pool: self.set_ports() return # clear downlink blocks for block in list(self.downlink.keys()): del self.downlink[block] # clear uplink blocks for block in list(self.uplink.keys()): del self.uplink[block] # pick default resource block default_block = pool.pop() # If lvap is associated to a shared tenant. I need to reset the lvap # before moving it. if self._tenant and self._tenant.bssid_type == T_TYPE_SHARED: # check if tenant is available at target block base_bssid = self._tenant.get_prefix() net_bssid = generate_bssid(base_bssid, default_block.hwaddr) # if not ignore request if net_bssid not in self._tenant.vaps: LOG.error("VAP %s not found on tenant %s", net_bssid, self._tenant.tenant_name) self.set_ports() return # check if vap is available at target block if net_bssid != self._tenant.vaps[net_bssid].net_bssid: LOG.error("VAP %s not available at target block %s", net_bssid, default_block) self.set_ports() return # otherwise reset lvap self._tenant = None self.association_state = False self.authentication_state = False self._assoc_id = 0 self._lvap_bssid = net_bssid else: self._lvap_bssid = self.net_bssid # assign default port policy to downlink resource block, this will # trigger a send_add_lvap and a set_port (radio) message self.downlink[default_block] = RadioPort(self, default_block) # assign remaining blocks (if any) to the uplink, this could # trigger one or more send_add_lvap and a set_port (radio) messages for block in pool: self.uplink[block] = RadioPort(self, block) # set ports self.set_ports()
def _handle_status_lvap(self, status): """Handle an incoming STATUS_LVAP message. Args: status, a STATUS_LVAP message Returns: None """ wtp_addr = EtherAddress(status.wtp) try: wtp = RUNTIME.wtps[wtp_addr] except KeyError: LOG.info("Status from unknown WTP %s", wtp_addr) return if not wtp.connection: LOG.info("Status from disconnected WTP %s", wtp_addr) return sta_addr = EtherAddress(status.sta) set_mask = bool(status.flags.set_mask) lvap = None hwaddr = EtherAddress(status.hwaddr) block = ResourceBlock(wtp, hwaddr, status.channel, status.band) LOG.info("LVAP status update from %s", sta_addr) # If the LVAP does not exists, then create a new one if sta_addr not in RUNTIME.lvaps: net_bssid_addr = EtherAddress(status.net_bssid) lvap_bssid_addr = EtherAddress(status.lvap_bssid) lvap = LVAP(sta_addr, net_bssid_addr, lvap_bssid_addr) # TODO: This should be built starting from the status message lvap.supports.add(ResourceBlock(lvap, sta_addr, 1, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 2, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 3, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 4, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 5, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 6, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 7, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 8, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 9, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 10, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 11, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 36, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 48, BT_L20)) RUNTIME.lvaps[sta_addr] = lvap lvap = RUNTIME.lvaps[sta_addr] # incoming block pool = ResourcePool() pool.add(block) match = wtp.supports & pool if not match: LOG.error("Incoming block %s is invalid", block) return block = match.pop() # this will try to updated the lvap object with the resource block # coming in this status update message. try: if set_mask: # set downlink+uplink block lvap._downlink.setitem(block, RadioPort(lvap, block)) else: # set uplink only blocks lvap._uplink.setitem(block, RadioPort(lvap, block)) except ValueError: LOG.error("Error while importing block %s, removing.", block) block.radio.connection.send_del_lvap(lvap) return lvap.authentication_state = bool(status.flags.authenticated) lvap.association_state = bool(status.flags.associated) lvap._assoc_id = status.assoc_id lvap._encap = EtherAddress(status.encap) ssids = [SSID(x.ssid) for x in status.ssids] if lvap.ssid: # Raise LVAP leave event LOG.info("LVAP LEAVE %s (%s)", lvap.addr, lvap.ssid) for handler in self.server.pt_types_handlers[PT_LVAP_LEAVE]: handler(lvap) # removing LVAP from tenant, need first to look for right tenant if lvap.addr in lvap.tenant.lvaps: LOG.info("Removing %s from tenant %s", lvap.addr, lvap.ssid) del lvap.tenant.lvaps[lvap.addr] lvap._tenant = None if ssids[0]: # setting tenant without seding out add lvap message lvap._tenant = RUNTIME.load_tenant(ssids[0]) # adding LVAP to tenant LOG.info("Adding %s to tenant %s", lvap.addr, ssids[0]) lvap.tenant.lvaps[lvap.addr] = lvap # Raise LVAP join event LOG.info("LVAP JOIN %s (%s)", lvap.addr, lvap.ssid) for handler in self.server.pt_types_handlers[PT_LVAP_JOIN]: handler(lvap) # update remaining ssids lvap._ssids = ssids[1:] # set ports lvap.set_ports() LOG.info("LVAP status %s", lvap)
def _handle_status_lvap(self, status): """Handle an incoming STATUS_LVAP message. Args: status, a STATUS_LVAP message Returns: None """ wtp_addr = EtherAddress(status.wtp) try: wtp = RUNTIME.wtps[wtp_addr] except KeyError: LOG.info("Status from unknown WTP %s", wtp_addr) return if not wtp.connection: LOG.info("Status from disconnected WTP %s", wtp_addr) return sta_addr = EtherAddress(status.sta) set_mask = bool(status.flags.set_mask) lvap = None hwaddr = EtherAddress(status.hwaddr) block = ResourceBlock(wtp, hwaddr, status.channel, status.band) LOG.info("LVAP status update from %s", sta_addr) # If the LVAP does not exists, then create a new one if sta_addr not in RUNTIME.lvaps: net_bssid_addr = EtherAddress(status.net_bssid) lvap_bssid_addr = EtherAddress(status.lvap_bssid) lvap = LVAP(sta_addr, net_bssid_addr, lvap_bssid_addr) # TODO: This should be built starting from the status message lvap.supports.add(ResourceBlock(lvap, sta_addr, 1, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 2, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 3, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 4, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 5, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 6, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 7, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 8, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 9, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 10, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 11, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 36, BT_L20)) lvap.supports.add(ResourceBlock(lvap, sta_addr, 48, BT_L20)) RUNTIME.lvaps[sta_addr] = lvap lvap = RUNTIME.lvaps[sta_addr] # incoming block pool = ResourcePool() pool.add(block) match = wtp.supports & pool if not match: LOG.error("Incoming block %s is invalid", block) return # this will try to updated the lvap object with the resource block # coming in this status update message. try: if set_mask: # set downlink+uplink block lvap._downlink.setitem(block, RadioPort(lvap, block)) else: # set uplink only blocks lvap._uplink.setitem(block, RadioPort(lvap, block)) except ValueError: LOG.error("Error while importing block %s, removing.", block) block.radio.connection.send_del_lvap(lvap) return lvap.authentication_state = bool(status.flags.authenticated) lvap.association_state = bool(status.flags.associated) lvap._assoc_id = status.assoc_id lvap._encap = EtherAddress(status.encap) ssids = [SSID(x.ssid) for x in status.ssids] if lvap.ssid: # Raise LVAP leave event LOG.info("LVAP LEAVE %s (%s)", lvap.addr, lvap.ssid) for handler in self.server.pt_types_handlers[PT_LVAP_LEAVE]: handler(lvap) # removing LVAP from tenant, need first to look for right tenant if lvap.addr in lvap.tenant.lvaps: LOG.info("Removing %s from tenant %s", lvap.addr, lvap.ssid) del lvap.tenant.lvaps[lvap.addr] lvap._tenant = None if ssids[0]: # setting tenant without seding out add lvap message lvap._tenant = RUNTIME.load_tenant(ssids[0]) # adding LVAP to tenant LOG.info("Adding %s to tenant %s", lvap.addr, ssids[0]) lvap.tenant.lvaps[lvap.addr] = lvap # Raise LVAP join event LOG.info("LVAP JOIN %s (%s)", lvap.addr, lvap.ssid) for handler in self.server.pt_types_handlers[PT_LVAP_JOIN]: handler(lvap) # update remaining ssids lvap._ssids = ssids[1:] # set ports lvap.set_ports() LOG.info("LVAP status %s", lvap)
def _handle_assoc_request(self, wtp, request): """Handle an incoming ASSOC_REQUEST message. Args: request, a ASSOC_REQUEST message Returns: None """ if not wtp.connection: LOG.info("Assoc request from disconnected WTP %s", wtp.addr) return sta = EtherAddress(request.sta) if sta not in RUNTIME.lvaps: LOG.info("Assoc request from unknown LVAP %s", sta) return lvap = RUNTIME.lvaps[sta] if not RUNTIME.is_allowed(sta): LOG.info("Assoc request from %s ignored (white list)", sta) return if RUNTIME.is_denied(sta): LOG.info("Assoc request from %s ignored (black list)", sta) return ssid = SSID(request.ssid.decode('UTF-8')) bssid = EtherAddress(request.bssid) tenant_name = None # look for ssid in shared tenants for tenant_id in RUNTIME.tenants: tenant = RUNTIME.tenants[tenant_id] if tenant.bssid_type == T_TYPE_UNIQUE: continue if bssid in tenant.vaps and ssid == tenant.tenant_name: tenant_name = tenant.tenant_name # otherwise this must be the lvap unique bssid if lvap.net_bssid == bssid and ssid in lvap.ssids: tenant_name = ssid if not tenant_name: LOG.info("Assoc request sta %s for ssid %s bssid %s, ignoring", lvap.addr, lvap.ssid, lvap.lvap_bssid) return # this will trigger an add lvap message to update the ssid lvap.tenant = RUNTIME.load_tenant(tenant_name) # update supported blocks field lvap.supported = ResourcePool() hwaddr = EtherAddress(request.hwaddr) channel = request.channel band = request.band lvap.supported.add(ResourceBlock(lvap, hwaddr, channel, band)) # this will trigger an add lvap message to update the assoc id lvap.assoc_id = self.server.assoc_id LOG.info("Assoc request sta %s ssid %s bssid %s assoc id %u, replying", lvap.addr, lvap.ssid, lvap.lvap_bssid, lvap.assoc_id) self.send_assoc_response(lvap)