def _handle_hello(self, wtp, hello): """Handle an incoming HELLO message. Args: hello, a HELLO message Returns: None """ LOG.info("Hello from %s WTP %s seq %u", self.addr[0], wtp.addr, hello.seq) # New connection if not wtp.connection: # set pointer to pnfdev object self.wtp = wtp # set connection wtp.connection = self # Update WTP params wtp.period = hello.period wtp.last_seen = hello.seq wtp.last_seen_ts = time.time() # Upon connection to the controller, the WTP must be provided # with the list of shared VAP for tenant in RUNTIME.tenants.values(): # tenant does not use shared VAPs if tenant.bssid_type == T_TYPE_UNIQUE: continue # wtp not in this tenant if wtp.addr not in tenant.wtps: continue tenant_id = tenant.tenant_id tokens = [tenant_id.hex[0:12][i:i + 2] for i in range(0, 12, 2)] base_bssid = EtherAddress(':'.join(tokens)) for block in wtp.supports: net_bssid = generate_bssid(base_bssid, block.hwaddr) # vap has already been created if net_bssid in RUNTIME.tenants[tenant_id].vaps: continue vap = VAP(net_bssid, block, wtp, tenant) self.send_add_vap(vap) RUNTIME.tenants[tenant_id].vaps[net_bssid] = vap
def __assign_downlink(self, dl_block): """Set the downlink block. Set the downlink block. Notice how this is always called before assigning the uplink and that if the specified dl_block is already defined as uplink then the agent will automatically promote the block to downlink.""" # null operation if self.blocks and self.blocks[0] == dl_block: return # If LVAP is associated to a shared tenant, then reset LVAP 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, dl_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) return # otherwise reset lvap self._tenant = None self.association_state = False self.authentication_state = False self._assoc_id = 0 self._lvap_bssid = net_bssid # save target block self.target_block = dl_block # clear downlink blocks for block in list(self._downlink.keys()): # this will add a new id to pending del self._downlink[block] # reset target block self.target_block = None # assign default port policy to downlink resource block, this will # trigger a send_add_lvap and a set_port (radio) message # this will add a new id to pending self._downlink[dl_block] = RadioPort(self, dl_block)
def send_vaps(self): """Send VAPs configurations. Upon connection to the controller, the WTP must be provided with the list of shared VAPs Args: None Returns: None """ for tenant in RUNTIME.tenants.values(): # tenant does not use shared VAPs if tenant.bssid_type == T_TYPE_UNIQUE: continue # wtp not in this tenant if self.wtp.addr not in tenant.wtps: continue tenant_id = tenant.tenant_id tokens = [tenant_id.hex[0:12][i:i + 2] for i in range(0, 12, 2)] base_bssid = EtherAddress(':'.join(tokens)) for block in self.wtp.supports: net_bssid = generate_bssid(base_bssid, block.hwaddr) # vap has already been created if net_bssid in RUNTIME.tenants[tenant_id].vaps: continue vap = VAP(net_bssid, block, self.wtp, tenant) self.send_add_vap(vap) RUNTIME.tenants[tenant_id].vaps[net_bssid] = vap
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_hello(self, hello): """Handle an incoming HELLO message. Args: hello, a HELLO message Returns: None """ wtp_addr = EtherAddress(hello.wtp) try: wtp = RUNTIME.wtps[wtp_addr] except KeyError: LOG.info("Hello from unknown WTP (%s)", wtp_addr) return LOG.info("Hello from %s seq %u", self.addr[0], hello.seq) # compute delta if not new connection if wtp.connection: delta = time.time() - wtp.last_seen_ts # uplink ul_bytes = hello.uplink_bytes - wtp.uplink_bytes wtp.uplink_bytes_per_second = int(ul_bytes / delta) * 8 # downlink dl_bytes = hello.downlink_bytes - wtp.downlink_bytes wtp.downlink_bytes_per_second = int(dl_bytes / delta) * 8 # If this is a new connection, then send caps request if not wtp.connection: # set wtp before connection because it is used when the # connection attribute of the PNFDev object is set self.wtp = wtp wtp.connection = self self.send_caps_request() # Update WTP params wtp.period = hello.period wtp.last_seen = hello.seq wtp.uplink_bytes = hello.uplink_bytes wtp.downlink_bytes = hello.downlink_bytes wtp.last_seen_ts = time.time() # Upon connection to the controller, the WTP must be provided # with the list of shared VAP for tenant in RUNTIME.tenants.values(): # tenant does not use shared VAPs if tenant.bssid_type == T_TYPE_UNIQUE: continue # wtp not in this tenant if wtp_addr not in tenant.wtps: continue tenant_id = tenant.tenant_id tokens = [tenant_id.hex[0:12][i:i+2] for i in range(0, 12, 2)] base_bssid = EtherAddress(':'.join(tokens)) for block in wtp.supports: net_bssid = generate_bssid(base_bssid, block.hwaddr) # vap has already been created if net_bssid in RUNTIME.tenants[tenant_id].vaps: continue vap = VAP(net_bssid, block, wtp, tenant) self.send_add_vap(vap) RUNTIME.tenants[tenant_id].vaps[net_bssid] = vap
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.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 _handle_hello(self, hello): """Handle an incoming HELLO message. Args: hello, a HELLO message Returns: None """ wtp_addr = EtherAddress(hello.wtp) try: wtp = RUNTIME.wtps[wtp_addr] except KeyError: LOG.info("Hello from unknown WTP (%s)", wtp_addr) return LOG.info("Hello from %s seq %u", self.addr[0], hello.seq) # compute delta if not new connection if wtp.connection: delta = time.time() - wtp.last_seen_ts # uplink ul_bytes = hello.uplink_bytes - wtp.uplink_bytes wtp.uplink_bytes_per_second = int(ul_bytes / delta) * 8 # downlink dl_bytes = hello.downlink_bytes - wtp.downlink_bytes wtp.downlink_bytes_per_second = int(dl_bytes / delta) * 8 # If this is a new connection, then send caps request if not wtp.connection: # set wtp before connection because it is used when the # connection attribute of the PNFDev object is set self.wtp = wtp wtp.connection = self # Update WTP params wtp.period = hello.period wtp.last_seen = hello.seq wtp.uplink_bytes = hello.uplink_bytes wtp.downlink_bytes = hello.downlink_bytes wtp.last_seen_ts = time.time() # Upon connection to the controller, the WTP must be provided # with the list of shared VAP for tenant in RUNTIME.tenants.values(): # tenant does not use shared VAPs if tenant.bssid_type == T_TYPE_UNIQUE: continue # wtp not in this tenant if wtp_addr not in tenant.wtps: continue tenant_id = tenant.tenant_id tokens = [tenant_id.hex[0:12][i:i + 2] for i in range(0, 12, 2)] base_bssid = EtherAddress(':'.join(tokens)) for block in wtp.supports: net_bssid = generate_bssid(base_bssid, block.hwaddr) # vap has already been created if net_bssid in RUNTIME.tenants[tenant_id].vaps: continue vap = VAP(net_bssid, block, wtp, tenant) self.send_add_vap(vap) RUNTIME.tenants[tenant_id].vaps[net_bssid] = vap
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)) # set supported band lvap.supported_band = request.supported_band # Check if block is valid incoming = ResourceBlock(wtp, EtherAddress(request.hwaddr), request.channel, request.band) valid = [block for block in wtp.supports if block == incoming] if not valid: LOG.warning("No valid intersection found. Ignoring request.") return # This will trigger an LVAP ADD message (and REMOVE if necessary) lvap.blocks = valid[0] # save LVAP in the runtime RUNTIME.lvaps[sta] = lvap LOG.info("Sending probe response to %s", lvap.addr) self.send_probe_response(lvap, ssid)
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 _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 blocks(self, blocks): """Assign a list of block to the LVAP. Assign a list of blocks to the LVAP. Accepts as input either a list or a ResourceBlock. If the list has more than one ResourceBlocks, then the first one is assigned to the downlink and the remaining are assigned to the uplink. Args: blocks: A list of ResourceBlocks or a ResourceBlock """ if self.pending: raise ValueError("Handover in progress") if not blocks: return if isinstance(blocks, list): pool = blocks elif isinstance(blocks, ResourceBlock): pool = [] pool.append(blocks) else: raise TypeError("Invalid type: %s", type(blocks)) for block in pool: if not isinstance(block, ResourceBlock): raise TypeError("Invalid type: %s", type(block)) # If LVAP is associated to a shared tenant, then reset LVAP 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, pool[0].hwaddr) # if not ignore request if net_bssid not in self._tenant.vaps: return # otherwise reset lvap self._tenant = None self.association_state = False self.authentication_state = False self._assoc_id = 0 self._lvap_bssid = net_bssid # clear all blocks self.clear_blocks(target_block=pool[0]) # Set downlink block if different. self.__assign_downlink(pool[0]) # set uplink blocks self.__assign_uplink(pool[1:]) # delete all outgoing virtual link and then remove the entire port if self.ports: self.ports[0].clear() del self.ports[0] # Create a new port from scratch self.ports[0] = VirtualPort(virtual_port_id=0) for block in self.blocks: self.ports[0].poas.append(block.radio.port()) # set/update intent intent = { 'version': '1.0', 'dpid': self.blocks[0].radio.port().dpid, 'port': self.blocks[0].radio.port().port_id, 'hwaddr': self.addr } intent_server = RUNTIME.components[IntentServer.__module__] if self.poa_uuid: intent_server.update_poa(intent, self.poa_uuid) else: self.poa_uuid = intent_server.add_poa(intent)
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()