mirror of
https://github.com/Telecominfraproject/wlan-lanforge-scripts.git
synced 2025-11-01 11:18:03 +00:00
2453 lines
152 KiB
Python
Executable File
2453 lines
152 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
NAME: lf_roam_test.py
|
|
|
|
PURPOSE: lf_hard_rome_test.py works on both roaming methods i.e. hard/forced roaming and also attenuation based roaming
|
|
(soft roam) specific or purely based to 11r.
|
|
- By default, this script executes a hard roaming process and provides the results of the 11r roam test pdf,
|
|
as well as all the packet captures generated after the roam test. However, to perform a soft roam, the soft_roam
|
|
parameter must be set to true.
|
|
|
|
Hard Roam
|
|
EXAMPLE: For a single station and a single iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 1 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 1 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based
|
|
|
|
EXAMPLE: For a single station and multiple iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 1 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 10 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based
|
|
|
|
EXAMPLE: For multiple station and a single iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 10 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 1 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based
|
|
|
|
EXAMPLE: For multiple station and multiple iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 10 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 10 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based
|
|
|
|
EXAMPLE: For multiple station and multiple iteration with multicast traffic enable
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "10:f9:20:fd:f3:4b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 2 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 1 --channel "36" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based --sta_type normal --multicast True
|
|
|
|
Soft Roam
|
|
EXAMPLE: For a single station and a single iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 1 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 1 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based --soft_roam True
|
|
|
|
EXAMPLE: For a single station and multiple iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 1 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 10 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based --soft_roam True
|
|
|
|
EXAMPLE: For multiple station and a single iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 10 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 1 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based --soft_roam True
|
|
|
|
EXAMPLE: For multiple station and multiple iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 10 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 10 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based --soft_roam True
|
|
|
|
SCRIPT_CLASSIFICATION: Test
|
|
NOTES:
|
|
|
|
The primary focus of this script is to enable seamless roaming of clients/stations between two access points (APs).
|
|
The test can be conducted with a single or multiple stations, with single or multiple iterations.
|
|
|
|
The script will create stations/clients with advanced/802.1x and 11r key management. By default, it will create a
|
|
single station/client. Once the stations are created, the script will generate CX traffic between the upstream port and
|
|
the stations and run the traffic before roam.
|
|
|
|
Packet captures will be taken for each station/client in two scenarios:
|
|
|
|
(i) While the station/client is connected to an AP
|
|
(ii) While the station/client roams from one AP to another AP
|
|
|
|
These packet captures will be used to analyze the performance and stability of the roaming process.
|
|
|
|
Overall, this script is designed to provide a comprehensive test of the roaming functionality of the APs and the
|
|
stability of the network when clients move between APs.
|
|
|
|
The following are the criteria for PASS the test:
|
|
|
|
1. The BSSID of the station should change after roaming from one AP to another
|
|
2 The station should not experience any disconnections during/after the roaming process.
|
|
3. The duration of the roaming process should be less than 100 ms.
|
|
|
|
The following are the criteria for FAIL the test:
|
|
|
|
1. The BSSID of the station remains unchanged after roaming from one AP to another.
|
|
2. No roaming occurs, as all stations are connected to the same AP.
|
|
3. The captured packet does not contain a Reassociation Response Frame.
|
|
4. The station experiences disconnection during/after the roaming process.
|
|
5. The duration of the roaming process exceeds 100 ms.
|
|
|
|
STATUS: BETA RELEASE (MORE TESTING ONLY WITH MULTICAST)
|
|
|
|
VERIFIED_ON: 15-MAY-2023, Underdevelopment
|
|
|
|
LICENSE:
|
|
Free to distribute and modify. LANforge systems must be licensed.
|
|
Copyright 2022 Candela Technologies Inc
|
|
|
|
INCLUDE_IN_README: False
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import importlib
|
|
import logging
|
|
import time
|
|
import datetime
|
|
from datetime import datetime
|
|
import pandas as pd
|
|
import paramiko
|
|
from itertools import chain
|
|
import argparse
|
|
|
|
logger = logging.getLogger(__name__)
|
|
if sys.version_info[0] != 3:
|
|
logger.critical("This script requires Python 3")
|
|
exit(1)
|
|
|
|
sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../")))
|
|
lfcli_base = importlib.import_module("py-json.LANforge.lfcli_base")
|
|
LFCliBase = lfcli_base.LFCliBase
|
|
LFUtils = importlib.import_module("py-json.LANforge.LFUtils")
|
|
realm = importlib.import_module("py-json.realm")
|
|
Realm = realm.Realm
|
|
lf_logger_config = importlib.import_module("py-scripts.lf_logger_config")
|
|
cv_test_reports = importlib.import_module("py-json.cv_test_reports")
|
|
lf_report = cv_test_reports.lanforge_reports
|
|
lf_report_pdf = importlib.import_module("py-scripts.lf_report")
|
|
lf_csv = importlib.import_module("py-scripts.lf_csv")
|
|
lf_pcap = importlib.import_module("py-scripts.lf_pcap")
|
|
lf_graph = importlib.import_module("py-scripts.lf_graph")
|
|
sniff_radio = importlib.import_module("py-scripts.lf_sniff_radio")
|
|
sta_connect = importlib.import_module("py-scripts.sta_connect2")
|
|
lf_clean = importlib.import_module("py-scripts.lf_cleanup")
|
|
series = importlib.import_module("cc_module_9800_3504")
|
|
attenuator = importlib.import_module("py-scripts.attenuator_serial")
|
|
modify = importlib.import_module("py-scripts.lf_atten_mod_test")
|
|
multicast_profile = importlib.import_module("py-json.multicast_profile")
|
|
|
|
|
|
class HardRoam(Realm):
|
|
def __init__(self, lanforge_ip=None,
|
|
lanforge_port=None,
|
|
lanforge_ssh_port=None,
|
|
c1_bssid=None,
|
|
c2_bssid=None,
|
|
fiveg_radio=None,
|
|
twog_radio=None,
|
|
sixg_radio=None,
|
|
band=None,
|
|
sniff_radio_=None,
|
|
num_sta=None,
|
|
security=None,
|
|
security_key=None,
|
|
ssid=None,
|
|
upstream=None,
|
|
duration=None,
|
|
iteration=None,
|
|
channel=None,
|
|
option=None,
|
|
duration_based=None,
|
|
iteration_based=None,
|
|
dut_name=None,
|
|
traffic_type="lf_udp",
|
|
roaming_delay=None,
|
|
path="../",
|
|
scheme="ssh",
|
|
dest="localhost",
|
|
user="admin",
|
|
passwd="Cisco123",
|
|
prompt="WLC2",
|
|
series_cc="9800",
|
|
ap="AP687D.B45C.1D1C",
|
|
port="8888",
|
|
band_cc="5g",
|
|
timeout="10",
|
|
eap_method=None,
|
|
eap_identity=None,
|
|
eap_password=None,
|
|
pairwise_cipher=None,
|
|
groupwise_cipher=None,
|
|
private_key=None,
|
|
pk_passwd=None,
|
|
ca_cert=None,
|
|
eap_phase1=None,
|
|
eap_phase2=None,
|
|
log_file=False,
|
|
debug=False,
|
|
soft_roam=False,
|
|
sta_type=None,
|
|
ieee80211w=None,
|
|
multicast=None
|
|
):
|
|
super().__init__(lanforge_ip,
|
|
lanforge_port)
|
|
self.lanforge_ip = lanforge_ip
|
|
self.lanforge_port = lanforge_port
|
|
self.lanforge_ssh_port = lanforge_ssh_port
|
|
self.c1_bssid = c1_bssid
|
|
self.c2_bssid = c2_bssid
|
|
self.fiveg_radios = fiveg_radio
|
|
self.twog_radios = twog_radio
|
|
self.sixg_radios = sixg_radio
|
|
self.band = band
|
|
self.sniff_radio = sniff_radio_
|
|
self.num_sta = num_sta
|
|
self.ssid_name = ssid
|
|
self.security = security
|
|
self.security_key = security_key
|
|
self.upstream = upstream
|
|
self.duration = duration
|
|
self.iteration = iteration
|
|
self.channel = channel
|
|
self.option = option
|
|
self.iteration_based = iteration_based
|
|
self.duration_based = duration_based
|
|
self.local_realm = Realm(lfclient_host=self.lanforge_ip, lfclient_port=self.lanforge_port)
|
|
self.staConnect = sta_connect.StaConnect2(host=self.lanforge_ip, port=self.lanforge_port,
|
|
outfile="sta_connect2.csv")
|
|
self.final_bssid = []
|
|
self.pcap_obj_2 = None
|
|
self.pcap_name = None
|
|
self.test_duration = None
|
|
self.client_list = []
|
|
self.dut_name = dut_name
|
|
self.pcap_obj = lf_pcap.LfPcap(host=self.lanforge_ip, port=self.lanforge_port)
|
|
self.lf_csv_obj = lf_csv()
|
|
self.traffic_type = traffic_type
|
|
self.roam_delay = roaming_delay
|
|
self.sta_type = sta_type
|
|
self.cx_profile = self.local_realm.new_l3_cx_profile()
|
|
self.cc = None
|
|
self.cc = series.create_controller_series_object(
|
|
scheme=scheme,
|
|
dest=dest,
|
|
user=user,
|
|
passwd=passwd,
|
|
prompt=prompt,
|
|
series=series_cc,
|
|
ap=ap,
|
|
port=port,
|
|
band=band_cc,
|
|
timeout=timeout)
|
|
self.cc.pwd = path
|
|
self.start_time = None
|
|
self.end_time = None
|
|
self.eap_method = eap_method
|
|
self.eap_identity = eap_identity
|
|
self.eap_password = eap_password
|
|
self.pairwise_cipher = str(pairwise_cipher)
|
|
self.groupwise_cipher = groupwise_cipher
|
|
self.private_key = private_key
|
|
self.pk_passwd = pk_passwd
|
|
self.ca_cert = ca_cert
|
|
self.eap_phase1 = eap_phase1
|
|
self.eap_phase2 = eap_phase2
|
|
self.log_file = log_file
|
|
self.debug = debug
|
|
self.mac_data = None
|
|
self.soft_roam = soft_roam
|
|
self.ieee80211w = ieee80211w
|
|
self.multicast = multicast
|
|
print("Number of iteration : ", self.iteration)
|
|
# logging.basicConfig(filename='roam.log', filemode='w', level=logging.INFO, force=True)
|
|
self.multi_cast_profile = multicast_profile.MULTICASTProfile(self.lanforge_ip, self.lanforge_port,
|
|
local_realm=self)
|
|
|
|
# Start debugger of controller
|
|
def start_debug_(self, mac_list):
|
|
mac = mac_list
|
|
for i in mac:
|
|
y = self.cc.debug_wireless_mac_cc(mac=str(i))
|
|
print(y)
|
|
|
|
# Stop debugger of controller
|
|
def stop_debug_(self, mac_list):
|
|
mac = mac_list
|
|
for i in mac:
|
|
y = self.cc.no_debug_wireless_mac_cc(mac=str(i))
|
|
print(y)
|
|
|
|
# Get trace file names from controller
|
|
def get_ra_trace_file(self):
|
|
ra = self.cc.get_ra_trace_files__cc()
|
|
print(ra)
|
|
ele_list = [y for y in (x.strip() for x in ra.splitlines()) if y]
|
|
print(ele_list)
|
|
return ele_list
|
|
|
|
# Get trace file names from controller with respect to number of clients
|
|
def get_file_name(self, client):
|
|
file_name = []
|
|
if not self.debug:
|
|
for i in range(client):
|
|
file_name.append("debug disabled")
|
|
else:
|
|
file = self.get_ra_trace_file()
|
|
indices = [i for i, s in enumerate(file) if 'dir bootflash: | i ra_trace' in s]
|
|
# print(indices)
|
|
y = indices[-1]
|
|
if client == 1:
|
|
z = file[y + 1]
|
|
list_ = [z]
|
|
m = list_[0].split(" ")
|
|
print(m)
|
|
print(len(m))
|
|
print(m[-1])
|
|
if m[-1].isnumeric():
|
|
print("Log file not Available")
|
|
file_name.append("file not found")
|
|
file_name.append(m[-1])
|
|
else:
|
|
z = file[y + (int(0) + 1)]
|
|
list_ = [z]
|
|
m = list_[0].split(" ")
|
|
print(m)
|
|
print(len(m))
|
|
print(m[-1])
|
|
if m[-1].isnumeric():
|
|
print("Log file not Available")
|
|
for i in range(client):
|
|
file_name.append("file not found")
|
|
else:
|
|
for i in range(client):
|
|
z = file[y + (int(i) + 1)]
|
|
list_ = [z]
|
|
m = list_[0].split(" ")
|
|
print(m)
|
|
print(len(m))
|
|
print(m[-1])
|
|
if m[-1].isnumeric():
|
|
print("Log file not Available")
|
|
file_name.append("file not found")
|
|
file_name.append(m[-1])
|
|
print("File_name", file_name)
|
|
file_name.reverse()
|
|
return file_name
|
|
|
|
# delete trace file from controller
|
|
def delete_trace_file(self, file):
|
|
# file = self.get_file_name()
|
|
self.cc.del_ra_trace_file_cc(file=file)
|
|
|
|
# get station list from lf
|
|
def get_station_list(self):
|
|
sta = self.staConnect.station_list()
|
|
if sta == "no response":
|
|
return "no response"
|
|
sta_list = []
|
|
for i in sta:
|
|
for j in i:
|
|
sta_list.append(j)
|
|
return sta_list
|
|
|
|
# Create N - number of clients of advanced configuration on lf
|
|
def create_n_clients(self, start_id=0, sta_prefix=None, num_sta=None, dut_ssid=None,
|
|
dut_security=None, dut_passwd=None, radio=None):
|
|
|
|
local_realm = Realm(lfclient_host=self.lanforge_ip, lfclient_port=self.lanforge_port)
|
|
station_profile = local_realm.new_station_profile()
|
|
if self.band == "fiveg":
|
|
radio = self.fiveg_radios
|
|
if self.band == "twog":
|
|
radio = self.twog_radios
|
|
if self.band == "sixg":
|
|
radio = self.sixg_radios
|
|
sta_list = self.get_station_list()
|
|
print("Available list of stations on lanforge-GUI :", sta_list)
|
|
logging.info(str(sta_list))
|
|
if not sta_list:
|
|
print("No stations are available on lanforge-GUI")
|
|
logging.info("No stations are available on lanforge-GUI")
|
|
else:
|
|
station_profile.cleanup(sta_list, delay=1)
|
|
LFUtils.wait_until_ports_disappear(base_url=local_realm.lfclient_url,
|
|
port_list=sta_list,
|
|
debug=True)
|
|
print("Creating stations.")
|
|
logging.info("Creating stations.")
|
|
station_list = LFUtils.portNameSeries(prefix_=sta_prefix, start_id_=start_id,
|
|
end_id_=num_sta - 1, padding_number_=10000,
|
|
radio=radio)
|
|
if self.sta_type == "normal":
|
|
station_profile.set_command_flag("add_sta", "power_save_enable", 1)
|
|
if not self.soft_roam:
|
|
station_profile.set_command_flag("add_sta", "disable_roam", 1)
|
|
if self.soft_roam:
|
|
print("Soft roam true")
|
|
logging.info("Soft roam true")
|
|
if self.option == "otds":
|
|
print("OTDS present")
|
|
station_profile.set_command_flag("add_sta", "ft-roam-over-ds", 1)
|
|
|
|
if self.sta_type == "11r-sae-802.1x":
|
|
dut_passwd = "[BLANK]"
|
|
station_profile.use_security(dut_security, dut_ssid, dut_passwd)
|
|
station_profile.set_number_template("00")
|
|
|
|
station_profile.set_command_flag("add_sta", "create_admin_down", 1)
|
|
|
|
station_profile.set_command_param("set_port", "report_timer", 1500)
|
|
|
|
# connect station to particular bssid
|
|
# self.station_profile.set_command_param("add_sta", "ap", self.bssid[0])
|
|
|
|
station_profile.set_command_flag("set_port", "rpt_timer", 1)
|
|
if self.sta_type == "11r":
|
|
station_profile.set_command_flag("add_sta", "80211u_enable", 0)
|
|
station_profile.set_command_flag("add_sta", "8021x_radius", 1)
|
|
if not self.soft_roam:
|
|
# station_profile.ssid_pass = self.security_key
|
|
station_profile.set_command_flag("add_sta", "disable_roam", 1)
|
|
if self.soft_roam:
|
|
print("Soft roam true")
|
|
logging.info("Soft roam true")
|
|
if self.option == "otds":
|
|
print("OTDS present")
|
|
station_profile.set_command_flag("add_sta", "ft-roam-over-ds", 1)
|
|
station_profile.set_command_flag("add_sta", "power_save_enable", 1)
|
|
station_profile.set_wifi_extra(key_mgmt="FT-PSK ",
|
|
pairwise="",
|
|
group="",
|
|
psk="",
|
|
eap="",
|
|
identity="",
|
|
passwd="",
|
|
pin="",
|
|
phase1="NA",
|
|
phase2="NA",
|
|
pac_file="NA",
|
|
private_key="NA",
|
|
pk_password="NA",
|
|
hessid="00:00:00:00:00:01",
|
|
realm="localhost.localdomain",
|
|
client_cert="NA",
|
|
imsi="NA",
|
|
milenage="NA",
|
|
domain="localhost.localdomain",
|
|
roaming_consortium="NA",
|
|
venue_group="NA",
|
|
network_type="NA",
|
|
ipaddr_type_avail="NA",
|
|
network_auth_type="NA",
|
|
anqp_3gpp_cell_net="NA")
|
|
if self.sta_type == "11r-sae":
|
|
station_profile.set_command_flag("add_sta", "ieee80211w", 2)
|
|
station_profile.set_command_flag("add_sta", "80211u_enable", 0)
|
|
station_profile.set_command_flag("add_sta", "8021x_radius", 1)
|
|
# station_profile.set_command_flag("add_sta", "disable_roam", 1)
|
|
if not self.soft_roam:
|
|
station_profile.set_command_flag("add_sta", "disable_roam", 1)
|
|
if self.soft_roam:
|
|
if self.option == "otds":
|
|
station_profile.set_command_flag("add_sta", "ft-roam-over-ds", 1)
|
|
station_profile.set_command_flag("add_sta", "power_save_enable", 1)
|
|
station_profile.set_wifi_extra(key_mgmt="FT-SAE ",
|
|
pairwise="",
|
|
group="",
|
|
psk="",
|
|
eap="",
|
|
identity="",
|
|
passwd="",
|
|
pin="",
|
|
phase1="NA",
|
|
phase2="NA",
|
|
pac_file="NA",
|
|
private_key="NA",
|
|
pk_password="NA",
|
|
hessid="00:00:00:00:00:01",
|
|
realm="localhost.localdomain",
|
|
client_cert="NA",
|
|
imsi="NA",
|
|
milenage="NA",
|
|
domain="localhost.localdomain",
|
|
roaming_consortium="NA",
|
|
venue_group="NA",
|
|
network_type="NA",
|
|
ipaddr_type_avail="NA",
|
|
network_auth_type="NA",
|
|
anqp_3gpp_cell_net="NA")
|
|
if self.sta_type == "11r-eap": # wpa2 enterprise
|
|
station_profile.set_command_flag("set_port", "rpt_timer", 1)
|
|
# station_profile.set_command_param("add_sta", "ieee80211w", 2)
|
|
station_profile.set_command_flag("add_sta", "80211u_enable", 0)
|
|
station_profile.set_command_flag("add_sta", "8021x_radius", 1)
|
|
if not self.soft_roam:
|
|
station_profile.set_command_flag("add_sta", "disable_roam", 1)
|
|
if self.soft_roam:
|
|
if self.option == "otds":
|
|
station_profile.set_command_flag("add_sta", "ft-roam-over-ds", 1)
|
|
# station_profile.set_command_flag("add_sta", "disable_roam", 1)
|
|
station_profile.set_command_flag("add_sta", "power_save_enable", 1)
|
|
# station_profile.set_command_flag("add_sta", "ap", "68:7d:b4:5f:5c:3f")
|
|
station_profile.set_wifi_extra(key_mgmt="FT-EAP ",
|
|
pairwise=self.pairwise_cipher,
|
|
group=self.groupwise_cipher,
|
|
eap=self.eap_method,
|
|
identity=self.eap_identity,
|
|
passwd=self.eap_password,
|
|
ca_cert=self.ca_cert,
|
|
private_key=self.private_key,
|
|
pk_password=self.pk_passwd)
|
|
if self.sta_type == "11r-eap-sha384": # wpa3 enterprise
|
|
station_profile.set_command_flag("set_port", "rpt_timer", 1)
|
|
station_profile.set_command_flag("add_sta", "80211u_enable", 0)
|
|
station_profile.set_command_flag("add_sta", "8021x_radius", 1)
|
|
if not self.soft_roam:
|
|
station_profile.set_command_flag("add_sta", "disable_roam", 1)
|
|
if self.soft_roam:
|
|
if self.option == "otds":
|
|
station_profile.set_command_flag("add_sta", "ft-roam-over-ds", 1)
|
|
station_profile.set_command_flag("add_sta", "power_save_enable", 1)
|
|
station_profile.set_wifi_extra(key_mgmt="FT-EAP-SHA384 ",
|
|
pairwise=self.pairwise_cipher,
|
|
group=self.groupwise_cipher,
|
|
eap=self.eap_method,
|
|
identity=self.eap_identity,
|
|
passwd=self.eap_password,
|
|
ca_cert=self.ca_cert,
|
|
private_key=self.private_key,
|
|
pk_password=self.pk_passwd)
|
|
# enabling ieee80211w flag
|
|
if self.ieee80211w:
|
|
station_profile.set_command_param("add_sta", "ieee80211w", self.ieee80211w)
|
|
|
|
station_profile.create(radio=radio, sta_names_=station_list)
|
|
print("Waiting for ports to appear")
|
|
logging.info("Waiting for ports to appear")
|
|
local_realm.wait_until_ports_appear(sta_list=station_list)
|
|
|
|
if self.soft_roam:
|
|
for sta_name in station_list:
|
|
sta = sta_name.split(".")[2] # TODO: Use name_to_eid
|
|
# wpa_cmd = "roam " + str(checker2)
|
|
|
|
bgscan = {
|
|
"shelf": 1,
|
|
"resource": 1, # TODO: Do not hard-code resource, get it from radio eid I think.
|
|
"port": str(sta),
|
|
"type": 'NA',
|
|
"text": 'bgscan="simple:30:-65:300"'
|
|
}
|
|
|
|
print(bgscan)
|
|
logging.info(str(bgscan))
|
|
self.local_realm.json_post("/cli-json/set_wifi_custom", bgscan)
|
|
# time.sleep(2)
|
|
|
|
station_profile.admin_up()
|
|
print("Waiting for ports to admin up")
|
|
logging.info("Waiting for ports to admin up")
|
|
if local_realm.wait_for_ip(station_list):
|
|
print("All stations got IPs")
|
|
logging.info("All stations got IPs")
|
|
# exit()
|
|
return True
|
|
else:
|
|
print("Stations failed to get IPs")
|
|
logging.info("Stations failed to get IPs")
|
|
return False
|
|
|
|
# create a multicast profile
|
|
def mcast_tx(self):
|
|
# set 1mbps tx rate
|
|
self.multi_cast_profile.side_b_min_bps = 1000000
|
|
self.multi_cast_profile.create_mc_tx("mc_udp", self.upstream)
|
|
|
|
def mcast_rx(self, sta_list):
|
|
self.multi_cast_profile.side_a_min_bps = 0
|
|
print("Station List :", sta_list)
|
|
self.multi_cast_profile.create_mc_rx("mc_udp", sta_list)
|
|
|
|
def mcast_start(self):
|
|
self.multi_cast_profile.start_mc()
|
|
|
|
def mcast_stop(self):
|
|
self.multi_cast_profile.stop_mc()
|
|
|
|
# Create layer-3 traffic on clients
|
|
def create_layer3(self, side_a_min_rate, side_a_max_rate, side_b_min_rate, side_b_max_rate, side_a_min_pdu,
|
|
side_b_min_pdu, traffic_type, sta_list):
|
|
print("Station List :", sta_list)
|
|
logging.info(f"Station List : {str(sta_list)}")
|
|
print(type(sta_list))
|
|
print("Upstream port :", self.upstream)
|
|
logging.info(str(self.upstream))
|
|
self.cx_profile.host = self.lanforge_ip
|
|
self.cx_profile.port = self.lanforge_port
|
|
self.cx_profile.side_a_min_bps = side_a_min_rate
|
|
self.cx_profile.side_a_max_bps = side_a_max_rate
|
|
self.cx_profile.side_b_min_bps = side_b_min_rate
|
|
self.cx_profile.side_b_max_bps = side_b_max_rate
|
|
self.cx_profile.side_a_min_pdu = side_a_min_pdu,
|
|
self.cx_profile.side_b_min_pdu = side_b_min_pdu,
|
|
# Create layer3 end points & run traffic
|
|
print("Creating Endpoints")
|
|
logging.info("Creating Endpoints")
|
|
self.cx_profile.create(endp_type=traffic_type, side_a=sta_list, side_b=self.upstream, sleep_time=0)
|
|
self.cx_profile.start_cx()
|
|
|
|
# Get layer3 values
|
|
def get_layer3_values(self, cx_name=None, query=None):
|
|
url = f"/cx/{cx_name}"
|
|
response = self.json_get(_req_url=url)
|
|
result = response[str(cx_name)][str(query)]
|
|
return result
|
|
|
|
# Get cross-connect names
|
|
def get_cx_list(self):
|
|
layer3_result = self.local_realm.cx_list()
|
|
layer3_names = [item["name"] for item in layer3_result.values() if "_links" in item]
|
|
print("Layer-3 Names :", layer3_names)
|
|
return layer3_names
|
|
|
|
# Get Endpoint values
|
|
def get_endp_values(self, endp="A", cx_name="niki", query="tx bytes"):
|
|
# self.get_cx_list()
|
|
# self.json_get("http://192.168.100.131:8080/endp/Unsetwlan000-0-B?fields=rx%20rate")
|
|
url = f"/endp/{cx_name}-{endp}?fields={query}"
|
|
response = self.json_get(_req_url=url)
|
|
print(response)
|
|
if (response is None) or ("endpoint" not in response):
|
|
print("Incomplete response:")
|
|
exit(1)
|
|
final = response["endpoint"][query]
|
|
print(final)
|
|
return final
|
|
|
|
# Pre-Cleanup on lanforge
|
|
def precleanup(self):
|
|
obj = lf_clean.lf_clean(host=self.lanforge_ip, port=self.lanforge_port, clean_cxs=True, clean_endp=True)
|
|
obj.resource = "all"
|
|
obj.sta_clean()
|
|
obj.cxs_clean()
|
|
# obj.layer3_endp_clean()
|
|
|
|
# Get client data from lf
|
|
def station_data_query(self, station_name="wlan0", query="channel"):
|
|
url = f"/port/{1}/{1}/{station_name}?fields={query}"
|
|
# print("url//////", url)
|
|
response = self.local_realm.json_get(_req_url=url)
|
|
print("Response: ", response)
|
|
if (response is None) or ("interface" not in response):
|
|
print("Station_list: incomplete response:")
|
|
# pprint(response)
|
|
exit(1)
|
|
y = response["interface"][query]
|
|
return y
|
|
|
|
# Start packet capture on lf
|
|
# TODO: Check if other monitor ports exist on this radio already. If so, delete those
|
|
# before adding new monitor port (or) just use the existing monitor port without creating
|
|
# a new one. --Ben
|
|
def start_sniffer(self, radio_channel=None, radio=None, test_name="sniff_radio", duration=60):
|
|
self.pcap_name = test_name + str(datetime.now().strftime("%Y-%m-%d-%H-%M")).replace(':', '-') + ".pcap"
|
|
self.pcap_obj_2 = sniff_radio.SniffRadio(lfclient_host=self.lanforge_ip, lfclient_port=self.lanforge_port,
|
|
radio=radio, channel=radio_channel, monitor_name="monitor")
|
|
self.pcap_obj_2.setup(0, 0, 0)
|
|
time.sleep(5)
|
|
self.pcap_obj_2.monitor.admin_up()
|
|
time.sleep(5)
|
|
self.pcap_obj_2.monitor.start_sniff(capname=self.pcap_name, duration_sec=duration)
|
|
|
|
# Stop packet capture and get file name
|
|
def stop_sniffer(self):
|
|
print("In Stop Sniffer")
|
|
directory = None
|
|
directory_name = "pcap"
|
|
if directory_name:
|
|
directory = os.path.join("", str(directory_name))
|
|
try:
|
|
if not os.path.exists(directory):
|
|
os.mkdir(directory)
|
|
except Exception as x:
|
|
print(x)
|
|
|
|
self.pcap_obj_2.monitor.admin_down()
|
|
self.pcap_obj_2.cleanup()
|
|
lf_report.pull_reports(hostname=self.lanforge_ip, port=self.lanforge_ssh_port, username="lanforge",
|
|
password="lanforge", report_location="/home/lanforge/" + self.pcap_name,
|
|
report_dir="pcap")
|
|
return self.pcap_name
|
|
|
|
# Generate csv files at the beginning
|
|
def generate_csv(self):
|
|
file_name = []
|
|
for i in range(self.num_sta):
|
|
file = 'test_client_' + str(i) + '.csv'
|
|
if self.multicast == "True":
|
|
lf_csv_obj = lf_csv(_columns=['Iterations', 'bssid1', 'bssid2', "PASS/FAIL", "Remark"], _rows=[],
|
|
_filename=file)
|
|
else:
|
|
lf_csv_obj = lf_csv(_columns=['Iterations', 'bssid1', 'bssid2', "Roam Time(ms)", "PASS/FAIL",
|
|
"Pcap file Name", "Log File", "Remark"], _rows=[], _filename=file)
|
|
# "Packet loss",
|
|
file_name.append(file)
|
|
lf_csv_obj.generate_csv()
|
|
return file_name
|
|
|
|
# Get journal ctl logs/ kernel logs
|
|
def journal_ctl_logs(self, file):
|
|
jor_lst = []
|
|
name = "kernel_log" + file + ".txt"
|
|
jor_lst.append(name)
|
|
try:
|
|
ssh = paramiko.SSHClient()
|
|
command = "journalctl --since '5 minutes ago' > kernel_log" + file + ".txt"
|
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
ssh.connect(hostname=self.lanforge_ip, port=self.lanforge_ssh_port, username="lanforge",
|
|
password="lanforge", banner_timeout=600)
|
|
stdin, stdout, stderr = ssh.exec_command(str(command))
|
|
stdout.readlines()
|
|
ssh.close()
|
|
kernel_log = "/home/lanforge/kernel_log" + file + ".txt"
|
|
lf_report.pull_reports(hostname=self.lanforge_ip, port=self.lanforge_ssh_port, username="lanforge",
|
|
password="lanforge", report_location=kernel_log, report_dir=".")
|
|
except Exception as e:
|
|
print(e)
|
|
return jor_lst
|
|
|
|
# Gives wlan management status of pcap file
|
|
def get_wlan_mgt_status(self, file_name,
|
|
pyshark_filter="(wlan.fc.type_subtype eq 3 && wlan.fixed.status_code == 0x0000 && wlan.tag.number == 55)"):
|
|
query_reasso_response = self.pcap_obj.get_wlan_mgt_status_code(pcap_file=str(file_name), filter=pyshark_filter)
|
|
print("Query", query_reasso_response)
|
|
return query_reasso_response
|
|
|
|
# Get attenuator serial number
|
|
def attenuator_serial(self):
|
|
obj = attenuator.AttenuatorSerial(lfclient_host=self.lanforge_ip, lfclient_port=self.lanforge_port)
|
|
val = obj.show()
|
|
return val
|
|
|
|
# To modify the attenuators
|
|
def attenuator_modify(self, serno, idx, val):
|
|
atten_obj = modify.CreateAttenuator(self.lanforge_ip, self.lanforge_port, serno, idx, val)
|
|
atten_obj.build()
|
|
|
|
# This is where the main roaming functionality begins
|
|
def run(self, file_n=None):
|
|
try:
|
|
if self.soft_roam:
|
|
logging.info("Setting both attenuators to zero attenuation at the beginning.")
|
|
ser_no = self.attenuator_serial()
|
|
print("Available attenuators :", ser_no[0], ser_no[1])
|
|
logging.info("Available attenuators :" + str(ser_no[0]) + " , " + str(ser_no[1]))
|
|
ser_1 = ser_no[0].split(".")[2]
|
|
ser_2 = ser_no[1].split(".")[2]
|
|
self.attenuator_modify(ser_1, "all", 0)
|
|
self.attenuator_modify(ser_2, "all", 0)
|
|
except Exception as e:
|
|
logging.warning(str(e))
|
|
finally:
|
|
kernel_log = []
|
|
message = None, None
|
|
|
|
# Start Timer
|
|
test_time = datetime.now()
|
|
test_time = test_time.strftime("%b %d %H:%M:%S")
|
|
print("Test started at ", test_time)
|
|
logging.info("Test started at " + str(test_time))
|
|
self.start_time = test_time
|
|
|
|
# Getting two BSSID's for roam
|
|
self.final_bssid.extend([self.c1_bssid, self.c2_bssid])
|
|
print("Final BSSID's are :", self.final_bssid)
|
|
logging.info("Final BSSID's are :" + str(self.final_bssid))
|
|
|
|
# If 'Soft Roam' is selected, initially set the attenuator to zero.
|
|
if self.soft_roam:
|
|
print("Setting both attenuators to zero attenuation at the beginning for 'soft roam'")
|
|
logging.info("Setting both attenuators to zero attenuation at the beginning for 'soft roam'")
|
|
ser_no = self.attenuator_serial()
|
|
print("Available attenuators :", ser_no[0], ser_no[1])
|
|
logging.info("Available attenuators :" + str(ser_no[0]) + " , " + str(ser_no[1]))
|
|
ser_1 = ser_no[0].split(".")[2]
|
|
ser_2 = ser_no[1].split(".")[2]
|
|
self.attenuator_modify(ser_1, "all", 0)
|
|
self.attenuator_modify(ser_2, "all", 0)
|
|
|
|
# Start sniffer & Create clients with respect to bands
|
|
print("Begin sniffing to establish the initial connection.")
|
|
logging.info("Begin sniffing to establish the initial connection.")
|
|
self.start_sniffer(radio_channel=self.channel, radio=self.sniff_radio,
|
|
test_name="roam_" + str(self.sta_type) + "_" + str(self.option) + "start" + "_",
|
|
duration=3600)
|
|
if self.band == "twog":
|
|
self.local_realm.reset_port(self.twog_radios)
|
|
self.create_n_clients(sta_prefix="wlan1", num_sta=self.num_sta, dut_ssid=self.ssid_name,
|
|
dut_security=self.security, dut_passwd=self.security_key, radio=self.twog_radios)
|
|
if self.band == "fiveg":
|
|
self.local_realm.reset_port(self.fiveg_radios)
|
|
self.create_n_clients(sta_prefix="wlan", num_sta=self.num_sta, dut_ssid=self.ssid_name,
|
|
dut_security=self.security, dut_passwd=self.security_key, radio=self.fiveg_radios)
|
|
if self.band == "sixg":
|
|
self.local_realm.reset_port(self.sixg_radios)
|
|
self.create_n_clients(sta_prefix="wlan", num_sta=self.num_sta, dut_ssid=self.ssid_name,
|
|
dut_security=self.security, dut_passwd=self.security_key, radio=self.sixg_radios)
|
|
|
|
# Check if all stations have ip or not
|
|
sta_list = self.get_station_list()
|
|
print("Checking for IP and station list :", sta_list)
|
|
logging.info("Checking for IP and station list :" + str(sta_list))
|
|
if sta_list == "no response":
|
|
print("No response from station")
|
|
logging.info("No response from station")
|
|
else: # if all stations got ip check mac address for stations
|
|
val = self.wait_for_ip(sta_list)
|
|
mac_list = []
|
|
for sta_name in sta_list:
|
|
sta = sta_name.split(".")[2] # use name_to_eid
|
|
mac = self.station_data_query(station_name=str(sta), query="mac")
|
|
mac_list.append(mac)
|
|
print("List of MAC addresses for all stations :", mac_list)
|
|
logging.info("List of MAC addresses for all stations :" + str(mac_list))
|
|
self.mac_data = mac_list
|
|
# if self.debug:
|
|
# print("start debug")
|
|
# self.start_debug_(mac_list=mac_list)
|
|
# print("check for 30 min")
|
|
# time.sleep(1800)
|
|
print("Stop Sniffer")
|
|
logging.info("Stop Sniffer")
|
|
file_name_ = self.stop_sniffer()
|
|
file_name = "./pcap/" + str(file_name_)
|
|
print("pcap file name :", file_name)
|
|
logging.info("pcap file name : " + str(file_name))
|
|
# if self.debug:
|
|
# print("stop debugger")
|
|
# self.stop_debug_(mac_list=mac_list)
|
|
# # time.sleep(40)
|
|
# exit()
|
|
|
|
if val: # if all station got an ip, then check all station are connected to single AP
|
|
print("All stations got ip")
|
|
logging.info("All stations got ip")
|
|
print("Check if all stations are connected single ap")
|
|
logging.info("Check if all stations are connected single ap")
|
|
# get BSSID'S of all stations
|
|
print("Get BSSID's of all stations")
|
|
logging.info("Get BSSID's of all stations")
|
|
check = []
|
|
for sta_name in sta_list:
|
|
sta = sta_name.split(".")[2]
|
|
bssid = self.station_data_query(station_name=str(sta), query="ap")
|
|
logging.info(str(bssid))
|
|
check.append(bssid)
|
|
print("BSSID of the current connected stations : ", check)
|
|
logging.info(str(check))
|
|
|
|
# Check if all the stations in the BSSID list have the same BSSID.
|
|
print("Verifying whether all BSSID's are identical or not.")
|
|
logging.info("Verifying whether all BSSID's are identical or not.")
|
|
result = all(element == check[0] for element in check)
|
|
|
|
# if all BSSID's are identical / same, run layer3 traffic b/w station to upstream
|
|
if result:
|
|
if self.multicast == "True":
|
|
print("multicast is true")
|
|
self.mcast_tx()
|
|
self.mcast_rx(sta_list=sta_list)
|
|
self.mcast_start()
|
|
else:
|
|
self.create_layer3(side_a_min_rate=1000000, side_a_max_rate=0, side_b_min_rate=1000000,
|
|
side_b_max_rate=0, sta_list=sta_list, traffic_type=self.traffic_type,
|
|
side_a_min_pdu=1250, side_b_min_pdu=1250)
|
|
else:
|
|
# if BSSID's are not identical / same, try to move all clients to one ap
|
|
print("Attempt to ensure that all clients are connected to the same AP before "
|
|
"initiating a roaming process.")
|
|
logging.info("Attempt to ensure that all clients are connected to the same AP before "
|
|
"initiating a roaming process.")
|
|
count1 = check.count(self.c1_bssid.upper())
|
|
count2 = check.count(self.c2_bssid.upper())
|
|
checker, new_sta_list, checker2 = None, [], None
|
|
if count1 > count2:
|
|
print("Station connected mostly to ap1")
|
|
logging.info("Station connected mostly to ap1")
|
|
checker = self.c2_bssid.upper()
|
|
checker2 = self.c1_bssid.upper()
|
|
else:
|
|
checker = self.c1_bssid.upper()
|
|
checker2 = self.c2_bssid.upper()
|
|
index_count = [i for i, x in enumerate(check) if x == checker]
|
|
print(index_count)
|
|
logging.info(str(index_count))
|
|
for i in index_count:
|
|
new_sta_list.append(sta_list[i])
|
|
print("new_sta_list", new_sta_list)
|
|
logging.info("new_sta_list " + str(new_sta_list))
|
|
|
|
for sta_name in new_sta_list:
|
|
eid = self.name_to_eid(sta_name)
|
|
print("eid", eid)
|
|
# sta = sta_name.split(".")[2] # TODO: use name-to-eid
|
|
sta = eid[2]
|
|
print(sta)
|
|
logging.info(sta)
|
|
wpa_cmd = "roam " + str(checker2)
|
|
wifi_cli_cmd_data1 = {
|
|
"shelf": eid[0],
|
|
"resource": eid[1], # TODO: do not hard-code
|
|
"port": str(sta),
|
|
"wpa_cli_cmd": 'scan trigger freq 5180 5300'
|
|
}
|
|
wifi_cli_cmd_data = {
|
|
"shelf": eid[0],
|
|
"resource": eid[1],
|
|
"port": str(sta),
|
|
"wpa_cli_cmd": wpa_cmd
|
|
}
|
|
print(wifi_cli_cmd_data)
|
|
logging.info(str(wifi_cli_cmd_data))
|
|
self.local_realm.json_post("/cli-json/wifi_cli_cmd", wifi_cli_cmd_data1)
|
|
# TODO: LANforge sta on same radio will share scan results, so you only need to scan on one STA per
|
|
# radio, and then sleep should be 5 seconds, then roam every station that needs to roam.
|
|
# You do not need this sleep and scan for each STA.
|
|
self.local_realm.json_post("/cli-json/wifi_cli_cmd", wifi_cli_cmd_data)
|
|
if self.multicast == "True":
|
|
print("multicast is true")
|
|
self.mcast_tx()
|
|
self.mcast_rx(sta_list=sta_list)
|
|
self.mcast_start()
|
|
else:
|
|
self.create_layer3(side_a_min_rate=1000000, side_a_max_rate=0, side_b_min_rate=1000000,
|
|
side_b_max_rate=0, sta_list=sta_list, traffic_type=self.traffic_type,
|
|
side_a_min_pdu=1250, side_b_min_pdu=1250)
|
|
timeout, variable, iterable_var = None, None, None
|
|
if self.duration_based is True:
|
|
timeout = time.time() + 60 * float(self.duration)
|
|
# iteration_dur = 50000000
|
|
iterable_var = 50000000
|
|
variable = -1
|
|
if self.iteration_based:
|
|
variable = self.iteration
|
|
iterable_var = self.iteration
|
|
# post_bssid = None
|
|
|
|
while variable: # The iteration loop for roaming begins at this point.
|
|
if self.multicast == "True":
|
|
if variable == 1:
|
|
print("ignore")
|
|
else:
|
|
print("wait for 5 mins for next roam process")
|
|
time.sleep(120)
|
|
print("Value of the Variable : ", variable)
|
|
logging.info("Value of the Variable :" + str(variable))
|
|
iterations, number, ser_1, ser_2 = self.iteration, None, None, None
|
|
if variable != -1:
|
|
iterations = iterable_var - variable
|
|
variable = variable - 1
|
|
if variable == -1:
|
|
# need to write duration iteration logic
|
|
# iterations = iterable_var - iteration_dur
|
|
if self.duration is not None:
|
|
if time.time() > timeout:
|
|
break
|
|
if self.soft_roam:
|
|
# Get the serial number of attenuators from lf
|
|
ser_no = self.attenuator_serial()
|
|
print(ser_no[0])
|
|
logging.info(str(ser_no[0]))
|
|
ser_1 = ser_no[0].split(".")[2]
|
|
ser_2 = ser_no[1].split(".")[2]
|
|
if iterations % 2 == 0:
|
|
print("even set c1 to lowest and c2 to highest attenuation ")
|
|
logging.info("even set c1 to lowest and c2 to highest attenuation ")
|
|
number = "even"
|
|
print("number", number)
|
|
logging.info("number " + str(number))
|
|
|
|
# set attenuation to zero in first attenuator and high in second attenuator
|
|
self.attenuator_modify(ser_1, "all", 700)
|
|
self.attenuator_modify(ser_2, "all", 0)
|
|
else:
|
|
print("odd, c1 is already at highest and c2 is at lowest")
|
|
logging.info("odd, c1 is already at highest and c2 is at lowest")
|
|
self.attenuator_modify(ser_1, "all", 0)
|
|
self.attenuator_modify(ser_2, "all", 700) # 700 == 300/400 bgscan 15:-70:300
|
|
number = "odd"
|
|
print("number", number)
|
|
logging.info("number " + str(number))
|
|
try:
|
|
# Define row list per iteration
|
|
row_list = []
|
|
sta_list = self.get_station_list()
|
|
print("Station list : ", sta_list)
|
|
logging.info("Station list :" + str(sta_list))
|
|
if sta_list == "no response":
|
|
print("No response")
|
|
logging.info("No response")
|
|
pass
|
|
else:
|
|
station = self.wait_for_ip(sta_list)
|
|
if self.debug:
|
|
print("Start debug")
|
|
logging.info("Start debug")
|
|
self.start_debug_(mac_list=mac_list)
|
|
if station:
|
|
print("All stations got ip")
|
|
logging.info("All stations got ip")
|
|
# Get bssid's of all stations currently connected
|
|
bssid_list = []
|
|
for sta_name in sta_list:
|
|
sta = sta_name.split(".")[2]
|
|
bssid = self.station_data_query(station_name=str(sta), query="ap")
|
|
bssid_list.append(bssid)
|
|
print("BSSID of the current connected stations : ", bssid_list)
|
|
logging.info(str(bssid_list))
|
|
pass_fail_list = []
|
|
pcap_file_list = []
|
|
roam_time1 = []
|
|
# packet_loss_lst = []
|
|
remark = []
|
|
log_file = []
|
|
|
|
# Check if all element of bssid list has same bssid's
|
|
result = all(element == bssid_list[0] for element in bssid_list)
|
|
|
|
if not result:
|
|
# Attempt to connect the client to the same AP for each iteration
|
|
print("Giving a try to connect")
|
|
logging.info("Giving a try to connect")
|
|
print("Move all clients to one AP")
|
|
logging.info("Move all clients to one AP")
|
|
count3 = bssid_list.count(self.c1_bssid.upper())
|
|
count4 = bssid_list.count(self.c2_bssid.upper())
|
|
print("Count3", count3)
|
|
logging.info("Count3 " + str(count3))
|
|
print("Count4", count4)
|
|
logging.info("Count4 " + str(count4))
|
|
checker, new_sta_list, checker2 = None, [], None
|
|
if count3 > count4:
|
|
print("Station connected mostly to AP-1")
|
|
logging.info("Station connected mostly to AP-1")
|
|
checker = self.c2_bssid.upper()
|
|
checker2 = self.c1_bssid.upper()
|
|
else:
|
|
checker = self.c1_bssid.upper()
|
|
checker2 = self.c2_bssid.upper()
|
|
index_count = [i for i, x in enumerate(bssid_list) if x == checker]
|
|
print(index_count)
|
|
logging.info(str(index_count))
|
|
for i in index_count:
|
|
new_sta_list.append(sta_list[i])
|
|
print("new_sta_list", new_sta_list)
|
|
logging.info("new_sta_list " + str(new_sta_list))
|
|
# for i, x in zip(bssid_list, sta_list):
|
|
# if i == checker:
|
|
# index_count = bssid_list.index(checker)
|
|
# new_sta_list.append(sta_list[index_count])
|
|
# print("new_sta_list", new_sta_list)
|
|
|
|
for sta_name in new_sta_list:
|
|
# sta = sta_name.split(".")[2]
|
|
eid = self.name_to_eid(sta_name)
|
|
sta = eid[2]
|
|
print(sta)
|
|
logging.info(str(sta))
|
|
wpa_cmd = "roam " + str(checker2)
|
|
|
|
wifi_cli_cmd_data1 = {
|
|
"shelf": eid[0],
|
|
"resource": eid[1],
|
|
"port": str(sta),
|
|
"wpa_cli_cmd": 'scan trigger freq 5180 5300'
|
|
}
|
|
wifi_cli_cmd_data = {
|
|
"shelf": eid[0],
|
|
"resource": eid[1],
|
|
"port": str(sta),
|
|
"wpa_cli_cmd": wpa_cmd
|
|
}
|
|
print(wifi_cli_cmd_data)
|
|
logging.info(str(wifi_cli_cmd_data))
|
|
self.local_realm.json_post("/cli-json/wifi_cli_cmd", wifi_cli_cmd_data1)
|
|
self.local_realm.json_post("/cli-json/wifi_cli_cmd", wifi_cli_cmd_data)
|
|
|
|
# check bssid before
|
|
before_bssid = []
|
|
for sta_name in sta_list:
|
|
sta = sta_name.split(".")[2]
|
|
before_bss = self.station_data_query(station_name=str(sta), query="ap")
|
|
logging.info(str(before_bss))
|
|
before_bssid.append(before_bss)
|
|
print("BSSID of the current connected stations : ", before_bssid)
|
|
logging.info("BSSID of the current connected stations : " + str(before_bssid))
|
|
|
|
if before_bssid[0] == str(self.c1_bssid.upper()):
|
|
post_bssid = self.c2_bssid.upper()
|
|
else:
|
|
post_bssid = self.c1_bssid.upper()
|
|
print("After roaming, the stations will connect to %s the BSSID" % post_bssid)
|
|
logging.info(
|
|
"After roaming, the stations will connect to " + str(post_bssid) + "the BSSID")
|
|
result1 = all(element == before_bssid[0] for element in before_bssid)
|
|
|
|
if result1:
|
|
print("All stations connected to same AP")
|
|
logging.info("All stations connected to same AP")
|
|
for i in before_bssid:
|
|
local_row_list = [str(iterations + 1), i]
|
|
logging.info(str(local_row_list))
|
|
row_list.append(local_row_list)
|
|
print("Row list :", row_list)
|
|
logging.info(str(row_list))
|
|
# if all bssid are equal then do check to which ap it is connected
|
|
formated_bssid = before_bssid[0].lower()
|
|
station_before = ""
|
|
if formated_bssid == self.c1_bssid:
|
|
print("Station connected to chamber1 AP")
|
|
logging.info("Station connected to chamber1 AP")
|
|
station_before = formated_bssid
|
|
elif formated_bssid == self.c2_bssid:
|
|
print("Station connected to chamber 2 AP")
|
|
logging.info("Station connected to chamber 2 AP")
|
|
station_before = formated_bssid
|
|
print("Current connected stations BSSID", station_before)
|
|
logging.info(str(station_before))
|
|
# After checking all conditions start roam and start snifffer
|
|
print("Starting sniffer")
|
|
logging.info("Starting sniffer")
|
|
self.start_sniffer(radio_channel=self.channel, radio=self.sniff_radio,
|
|
test_name="roam_" + str(self.sta_type) + "_" + str(
|
|
self.option) + "_iteration_" + str(
|
|
iterations) + "_", duration=3600)
|
|
if self.soft_roam:
|
|
ser_num = None
|
|
ser_num2 = None
|
|
if number == "even":
|
|
ser_num = ser_1
|
|
ser_num2 = ser_2
|
|
print("even", ser_num)
|
|
logging.info("even " + str(ser_num))
|
|
elif number == "odd":
|
|
ser_num = ser_2
|
|
ser_num2 = ser_1
|
|
print("odd", ser_num)
|
|
logging.info("odd " + str(ser_num))
|
|
# logic to decrease c2 attenuation till 10 db using 1dbm steps
|
|
status = None
|
|
print("checking attenuation")
|
|
logging.info("checking attenuation")
|
|
print("ser num", ser_num)
|
|
logging.info("ser num " + str(ser_num))
|
|
for atten_val2 in range(700, -10, -10):
|
|
print(atten_val2)
|
|
self.attenuator_modify(int(ser_num), "all", atten_val2)
|
|
# TODO: You are changing in 1db increments. So, sleep for only 4 seconds
|
|
# should be enough.
|
|
print("wait for 4 secs")
|
|
logging.info("wait for 4 secs")
|
|
# query bssid's of all stations
|
|
bssid_check = []
|
|
for sta_name in sta_list:
|
|
sta = sta_name.split(".")[2]
|
|
bssid = self.station_data_query(station_name=str(sta), query="ap")
|
|
# if bssid == "NA":
|
|
# time.sleep(10)
|
|
bssid_check.append(bssid)
|
|
print(bssid_check)
|
|
logging.info(str(bssid_check))
|
|
|
|
# check if all are equal
|
|
resulta = all(element == bssid_check[0] for element in bssid_check)
|
|
if resulta:
|
|
station_after = bssid_check[0].lower()
|
|
if station_after == "N/A" or station_after == "na":
|
|
status = "station did not roamed"
|
|
print("station did not roamed")
|
|
logging.info("station did not roamed")
|
|
continue
|
|
if station_after == station_before:
|
|
status = "station did not roamed"
|
|
print("station did not roamed")
|
|
logging.info("station did not roamed")
|
|
continue
|
|
elif station_after != station_before:
|
|
print("client performed roam")
|
|
logging.info("client performed roam")
|
|
break
|
|
|
|
if status == "station did not roamed":
|
|
# set c1 to high
|
|
for atten_val1 in (range(0, 700, 10)):
|
|
print(atten_val1)
|
|
logging.info(str(atten_val1))
|
|
self.attenuator_modify(int(ser_num2), "all", atten_val1)
|
|
# TODO: You are changing in 1db increments. So, sleep for only 4 seconds
|
|
# should be enough.
|
|
# TODO: Add attenuation step to logs to make it more obvious what script is doing.
|
|
print("wait for 4 secs")
|
|
logging.info("wait for 4 secs")
|
|
bssid_check2 = []
|
|
for sta_name in sta_list:
|
|
sta = sta_name.split(".")[2]
|
|
bssid = self.station_data_query(station_name=str(sta),
|
|
query="ap")
|
|
# if bssid == "NA":
|
|
# time.sleep(10)
|
|
bssid_check2.append(bssid)
|
|
print(bssid_check2)
|
|
logging.info(str(bssid_check2))
|
|
# check if all are equal
|
|
result = all(element == bssid_check2[0] for element in bssid_check2)
|
|
if result:
|
|
station_after = bssid_check2[0].lower()
|
|
if station_after == "N/A" or station_after == "na":
|
|
# status = "station did not roamed"
|
|
print("station did not roamed")
|
|
logging.info("station did not roamed")
|
|
continue
|
|
if station_after == station_before:
|
|
# status = "station did not roamed"
|
|
print("station did not roamed")
|
|
logging.info("station did not roamed")
|
|
continue
|
|
else:
|
|
print('station roamed')
|
|
logging.info('station roamed')
|
|
break
|
|
else:
|
|
if station_before == self.final_bssid[0]:
|
|
print("Connected stations bssid is same to bssid list first element")
|
|
logging.info(
|
|
"Connected stations bssid is same to bssid list first element")
|
|
for sta_name in sta_list:
|
|
sta = sta_name.split(".")[2]
|
|
logging.info(str(sta))
|
|
wpa_cmd = ""
|
|
if self.option == "ota":
|
|
wpa_cmd = "roam " + str(self.final_bssid[1])
|
|
if self.option == "otds":
|
|
wpa_cmd = "ft_ds " + str(self.final_bssid[1])
|
|
# wpa_cmd = "roam " + str(self.final_bssid[1])
|
|
wifi_cli_cmd_data1 = {
|
|
"shelf": 1,
|
|
"resource": 1,
|
|
"port": str(sta),
|
|
"wpa_cli_cmd": 'scan trigger freq 5180 5300'
|
|
}
|
|
wifi_cli_cmd_data = {
|
|
"shelf": 1,
|
|
"resource": 1,
|
|
"port": str(sta),
|
|
"wpa_cli_cmd": wpa_cmd
|
|
}
|
|
print("Roam Command : ", wifi_cli_cmd_data)
|
|
logging.info("Roam Command : " + str(wifi_cli_cmd_data))
|
|
self.local_realm.json_post("/cli-json/wifi_cli_cmd",
|
|
wifi_cli_cmd_data1)
|
|
# TODO: See note in similar code above about only needing to scan once per radio
|
|
self.local_realm.json_post("/cli-json/wifi_cli_cmd",
|
|
wifi_cli_cmd_data)
|
|
else:
|
|
print("Connected stations bssid is same to bssid list second element")
|
|
logging.info(
|
|
"Connected stations bssid is same to bssid list second element")
|
|
for sta_name in sta_list:
|
|
sta = sta_name.split(".")[2]
|
|
wifi_cmd = ""
|
|
if self.option == "ota":
|
|
wifi_cmd = "roam " + str(self.final_bssid[0])
|
|
if self.option == "otds":
|
|
wifi_cmd = "ft_ds " + str(self.final_bssid[0])
|
|
logging.info(str(sta))
|
|
wifi_cli_cmd_data1 = {
|
|
"shelf": 1,
|
|
"resource": 1,
|
|
"port": str(sta),
|
|
"wpa_cli_cmd": 'scan trigger freq 5180 5300'
|
|
}
|
|
wifi_cli_cmd_data = {
|
|
"shelf": 1,
|
|
"resource": 1,
|
|
"port": str(sta),
|
|
"wpa_cli_cmd": wifi_cmd
|
|
}
|
|
print("Roam Command : ", wifi_cli_cmd_data)
|
|
logging.info("Roam Command : " + str(wifi_cli_cmd_data))
|
|
self.local_realm.json_post("/cli-json/wifi_cli_cmd",
|
|
wifi_cli_cmd_data1)
|
|
# TODO: See note in similar code above about only needing to scan once per radio
|
|
self.local_realm.json_post("/cli-json/wifi_cli_cmd",
|
|
wifi_cli_cmd_data)
|
|
# Kernel logs
|
|
kernel = self.journal_ctl_logs(file=str(iterations))
|
|
print("Name of the Kernel logs file :", kernel)
|
|
for i in kernel:
|
|
kernel_log.append(i)
|
|
# Stop sniff & Attach data
|
|
print("Stop sniffer")
|
|
logging.info("Stop sniffer")
|
|
file_name_ = self.stop_sniffer()
|
|
file_name = "./pcap/" + str(file_name_)
|
|
print("pcap file name", file_name)
|
|
logging.info("pcap file name " + str(file_name))
|
|
if self.debug:
|
|
print("Stop debugger")
|
|
logging.info("Stop debugger")
|
|
self.stop_debug_(mac_list=mac_list)
|
|
else:
|
|
print("Debug is disabled")
|
|
logging.info("Debug is disabled")
|
|
self.wait_for_ip(sta_list)
|
|
bssid_list_1 = []
|
|
for sta_name in sta_list:
|
|
sta = sta_name.split(".")[2]
|
|
bssid = self.station_data_query(station_name=str(sta), query="ap")
|
|
bssid_list_1.append(bssid)
|
|
print("The stations are romed to another AP (%s)" % bssid_list_1)
|
|
logging.info("The stations are romed to another AP " + str(bssid_list_1))
|
|
for i, x in zip(row_list, bssid_list_1):
|
|
i.append(x)
|
|
print("Row list, after roam :", row_list)
|
|
logging.info("Row list, after roam :" + str(row_list))
|
|
trace = self.get_file_name(client=self.num_sta)
|
|
print("Trace file :", trace)
|
|
log_file.append(trace)
|
|
print("Log file :", log_file)
|
|
|
|
# Check if all are equal
|
|
all(element == bssid_list_1[0] for element in bssid_list_1)
|
|
res = ""
|
|
station_before_ = before_bssid
|
|
print("The BSSID of the station before roamed :", station_before_)
|
|
logging.info("The BSSID of the station before roamed : " + str(station_before_))
|
|
# For each mac address query data from pcap
|
|
for i, x in zip(mac_list, range(len(station_before_))):
|
|
print("MAC address :", i)
|
|
logging.info("MAC address :" + str(i))
|
|
print("BSSID :", bssid_list_1)
|
|
logging.info(str(bssid_list_1))
|
|
query_action_frame_time, auth_time = None, None
|
|
station_after = bssid_list_1[x]
|
|
print("The connected BSSID for stations, after rome :", station_after)
|
|
logging.info(
|
|
"The connected BSSID for stations, after rome : " + str(station_after))
|
|
if station_after == station_before_[x] or station_after == "na":
|
|
print("Station did not roamed")
|
|
logging.info("Station did not roamed")
|
|
res = "FAIL"
|
|
elif station_after != station_before_[x]:
|
|
print("Client has performed a roaming operation.")
|
|
logging.info("Client has performed a roaming operation.")
|
|
res = "PASS"
|
|
if res == "FAIL":
|
|
res = "FAIL"
|
|
if self.multicast == "True":
|
|
print("multicast function")
|
|
if res == "PASS":
|
|
print("roam success")
|
|
print("check for multicast traffic resumed or not")
|
|
endp_list = self.json_get(
|
|
"endp?fields=name,eid,rx rate (last)",
|
|
debug_=False)
|
|
print("endpoint", endp_list)
|
|
local_list, local_list1, final_list = [], [], []
|
|
if "endpoint" in endp_list:
|
|
print(endp_list["endpoint"])
|
|
|
|
for i in range(1, len(endp_list["endpoint"])):
|
|
local_list.append(endp_list['endpoint'][i])
|
|
print(local_list)
|
|
new_lst = []
|
|
for i in range(len(local_list)):
|
|
local_list1 = list(local_list[i].keys())
|
|
new_lst.append(local_list1[0])
|
|
print(local_list1)
|
|
print(new_lst)
|
|
for i in range(len(new_lst)):
|
|
final_list.append(
|
|
endp_list['endpoint'][i + 1][new_lst[i]][
|
|
'rx rate (last)'])
|
|
print(final_list)
|
|
if 0 in final_list:
|
|
print("try to start multicast few times")
|
|
print("start multicast once again")
|
|
self.mcast_start()
|
|
time.sleep(60)
|
|
self.mcast_start()
|
|
print("check for multicast resumed or not ")
|
|
endp_list = self.json_get(
|
|
"endp?fields=name,eid,rx rate (last)",
|
|
debug_=False)
|
|
print("endpoint", endp_list)
|
|
local_list, local_list1, final_list = [], [], []
|
|
if "endpoint" in endp_list:
|
|
print(endp_list["endpoint"])
|
|
|
|
for i in range(1, len(endp_list["endpoint"])):
|
|
local_list.append(endp_list['endpoint'][i])
|
|
print(local_list)
|
|
new_lst = []
|
|
for i in range(len(local_list)):
|
|
local_list1 = list(local_list[i].keys())
|
|
new_lst.append(local_list1[0])
|
|
print(local_list1)
|
|
print(new_lst)
|
|
for i in range(len(new_lst)):
|
|
final_list.append(
|
|
endp_list['endpoint'][i + 1][new_lst[i]][
|
|
'rx rate (last)'])
|
|
print(final_list)
|
|
if 0 in final_list:
|
|
print("multicast did not resumed after few trials")
|
|
pass_fail_list.append("FAIL")
|
|
remark.append(
|
|
"bssid switched but multicast did not resumed after few trials")
|
|
else:
|
|
pass_fail_list.append("PASS")
|
|
remark.append(
|
|
"bssid switched and multicast resumed after few trials ")
|
|
else:
|
|
pass_fail_list.append("PASS")
|
|
remark.append("multicast resumed after roam")
|
|
else:
|
|
print("roaming failed")
|
|
pass_fail_list.append("FAIL")
|
|
remark.append("bssid does not switched")
|
|
else:
|
|
if res == "PASS":
|
|
if self.sta_type == "normal":
|
|
query_reasso_response = self.get_wlan_mgt_status(
|
|
file_name=file_name,
|
|
pyshark_filter="wlan.da eq %s and wlan.fc.type_subtype eq 3" % (
|
|
str(i)))
|
|
else:
|
|
query_reasso_response = self.get_wlan_mgt_status(
|
|
file_name=file_name,
|
|
pyshark_filter="(wlan.fc.type_subtype eq 3 && wlan.fixed.status_code == 0x0000 && wlan.tag.number == 55) && (wlan.da == %s)" % (
|
|
str(i)))
|
|
print(query_reasso_response)
|
|
logging.info(str(query_reasso_response))
|
|
if len(query_reasso_response) != 0 and query_reasso_response != "empty":
|
|
if query_reasso_response == "Successful":
|
|
print("Re-association status is successful")
|
|
logging.info("Re-association status is successful")
|
|
if self.sta_type == "normal":
|
|
reasso_t = self.pcap_obj.read_time(
|
|
pcap_file=str(file_name),
|
|
filter="wlan.da eq %s and wlan.fc.type_subtype eq 3" % (
|
|
str(i)))
|
|
else:
|
|
reasso_t = self.pcap_obj.read_time(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fc.type_subtype eq 3 && wlan.fixed.status_code == 0x0000 && wlan.tag.number == 55) && (wlan.da == %s)" % (
|
|
str(i)))
|
|
print("Re-association time is", reasso_t)
|
|
logging.info("Re-association time is " + str(reasso_t))
|
|
if self.option == "otds":
|
|
print("Checking for Action Frame")
|
|
logging.info("Checking for Action Frame")
|
|
|
|
# Action frame check
|
|
query_action_frame = self.pcap_obj.check_frame_present(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fixed.category_code == 6) && (wlan.sa == %s)" % (
|
|
str(i)))
|
|
print("Action Frame", query_action_frame)
|
|
if len(query_action_frame) != 0 and query_action_frame != "empty":
|
|
print("Action frame is present")
|
|
logging.info("Action frame is present")
|
|
query_action_frame_time = self.pcap_obj.read_time(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fixed.category_code == 6) && (wlan.sa == %s)" % (
|
|
str(i)))
|
|
print("Action frame time is",
|
|
query_action_frame_time)
|
|
logging.info(
|
|
"Action frame time is " + str(reasso_t))
|
|
else:
|
|
roam_time1.append("No Action frame")
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("No Action Frame")
|
|
print("Row list :", row_list)
|
|
logging.info("Row list " + str(row_list))
|
|
else:
|
|
print("Checking for Authentication Frame")
|
|
logging.info("Checking for Authentication Frame")
|
|
if self.sta_type == "normal":
|
|
query_auth_response = self.pcap_obj.get_wlan_mgt_status_code(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fixed.auth.alg == 0 && wlan.sa == %s)" % (
|
|
str(i)))
|
|
else:
|
|
query_auth_response = self.pcap_obj.get_wlan_mgt_status_code(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fixed.auth.alg == 2 && wlan.fixed.status_code == 0x0000 && wlan.fixed.auth_seq == 0x0001) && (wlan.sa == %s)" % (
|
|
str(i)))
|
|
print("Authentication Frames response is",
|
|
query_auth_response)
|
|
if len(query_auth_response) != 0 and query_auth_response != "empty":
|
|
if query_auth_response == "Successful":
|
|
print("Authentication Request Frame is present")
|
|
logging.info(
|
|
"Authentication Request Frame is present")
|
|
if self.sta_type == "normal":
|
|
auth_time = self.pcap_obj.read_time(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fixed.auth.alg == 0 && wlan.sa == %s)" % (
|
|
str(i)))
|
|
else:
|
|
auth_time = self.pcap_obj.read_time(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fixed.auth.alg == 2 && wlan.fixed.status_code == 0x0000 && wlan.fixed.auth_seq == 0x0001) && (wlan.sa == %s)" % (
|
|
str(i)))
|
|
print("Authentication Request Frame time is",
|
|
auth_time)
|
|
logging.info(
|
|
"Authentication Request Frame time is" + str(
|
|
auth_time))
|
|
else:
|
|
roam_time1.append('Auth Fail')
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append(" auth failure")
|
|
else:
|
|
roam_time1.append("No Auth frame")
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("No Auth frame")
|
|
print("Row list :", row_list)
|
|
logging.info("row list " + str(row_list))
|
|
# roam_time = None
|
|
if self.option == "otds":
|
|
roam_time = reasso_t - query_action_frame_time
|
|
else:
|
|
roam_time = reasso_t - auth_time
|
|
print("Roam Time (ms)", roam_time)
|
|
logging.info("Roam Time (ms)" + str(roam_time))
|
|
roam_time1.append(roam_time)
|
|
if self.option == "ota":
|
|
if roam_time < 100:
|
|
pass_fail_list.append("PASS")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("Passed all criteria")
|
|
else:
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("Roam time is greater then 100 ms")
|
|
else:
|
|
pass_fail_list.append("PASS")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("Passed all criteria")
|
|
else:
|
|
roam_time1.append('Reassociation Fail')
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("Reassociation failure")
|
|
print(
|
|
"pcap_file name for fail instance of iteration value ")
|
|
logging.info(
|
|
"pcap_file name for fail instance of iteration value ")
|
|
else:
|
|
roam_time1.append("No Reassociation")
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("No Reasso response")
|
|
print("Row list : ", row_list)
|
|
logging.info("row list " + str(row_list))
|
|
else:
|
|
query_reasso_response = self.get_wlan_mgt_status(
|
|
file_name=file_name,
|
|
pyshark_filter="(wlan.fc.type_subtype eq 3 && wlan.fixed.status_code == 0x0000 && wlan.tag.number == 55) && (wlan.da == %s)" % (
|
|
str(i)))
|
|
print("Query_reasso_response:", query_reasso_response)
|
|
logging.info(str(query_reasso_response))
|
|
if len(query_reasso_response) != 0 and query_reasso_response != 'empty':
|
|
if query_reasso_response == "Successful":
|
|
print("Re-Association status is successful")
|
|
logging.info("Re-Association status is successful")
|
|
reasso_t = self.pcap_obj.read_time(pcap_file=str(file_name),
|
|
filter="(wlan.fc.type_subtype eq 3 && wlan.fixed.status_code == 0x0000 && wlan.tag.number == 55) && (wlan.da == %s)" % (
|
|
str(i)))
|
|
print("Re-Association time is", reasso_t)
|
|
logging.info("Re-Association time is " + str(reasso_t))
|
|
if self.option == "otds":
|
|
print("Check for Action frame")
|
|
logging.info("Check for Action Frame")
|
|
|
|
# action frame check
|
|
query_action_frame = self.pcap_obj.check_frame_present(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fixed.category_code == 6) && (wlan.sa == %s)" % (
|
|
str(i)))
|
|
if len(query_action_frame) != 0 and query_action_frame != "empty":
|
|
print("Action Frame is present")
|
|
logging.info("Action Frame is present")
|
|
query_action_frame_time = self.pcap_obj.read_time(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fixed.category_code == 6) && (wlan.sa == %s)" % (
|
|
str(i)))
|
|
print("Action Frame time is",
|
|
query_action_frame_time)
|
|
logging.info(
|
|
"Action Frame) time is " + str(reasso_t))
|
|
else:
|
|
roam_time1.append("No Action frame")
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("bssid miNo Action Frame")
|
|
print("Row list :", row_list)
|
|
logging.info("Row list :" + str(row_list))
|
|
else:
|
|
print("Check for Authentication Frame")
|
|
logging.info("Check for Authentication Frame")
|
|
query_auth_response = self.pcap_obj.get_wlan_mgt_status_code(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fixed.auth.alg == 2 && wlan.fixed.status_code == 0x0000 && wlan.fixed.auth_seq == 0x0001) && (wlan.sa == %s)" % (
|
|
str(i)))
|
|
if len(query_auth_response) != 0 and query_auth_response != "empty":
|
|
if query_auth_response == "Successful":
|
|
print("Authentication Request is present")
|
|
logging.info(
|
|
"Authentication Request is present")
|
|
auth_time = self.pcap_obj.read_time(
|
|
pcap_file=str(file_name),
|
|
filter="(wlan.fixed.auth.alg == 2 && wlan.fixed.status_code == 0x0000 && wlan.fixed.auth_seq == 0x0001) && (wlan.sa == %s)" % (
|
|
str(i)))
|
|
print("Authentication time is", auth_time)
|
|
logging.info(
|
|
"Authentication time is " + str(auth_time))
|
|
else:
|
|
roam_time1.append('Auth Fail')
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("bssid mismatch auth failure")
|
|
else:
|
|
roam_time1.append("No Auth frame")
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("bssid mismatched No Auth frame")
|
|
print("Row list :", row_list)
|
|
logging.info("Row list :" + str(row_list))
|
|
# roam_time = None
|
|
if self.option == "otds":
|
|
roam_time = reasso_t - query_action_frame_time
|
|
else:
|
|
roam_time = reasso_t - auth_time
|
|
print("Roam time (ms)", roam_time)
|
|
logging.info("Roam time (ms) " + str(roam_time))
|
|
roam_time1.append(roam_time)
|
|
if self.option == "ota":
|
|
if roam_time < 50:
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append(
|
|
"(BSSID mismatched)Client disconnected after roaming")
|
|
else:
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append(
|
|
"(BSSID mismatched)Roam time is greater then 100 ms,")
|
|
else:
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("BSSID mismatched")
|
|
else:
|
|
roam_time1.append('Reassociation Fail')
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("BSSID mismatched Reassociation failure")
|
|
else:
|
|
roam_time1.append("No Reassociation")
|
|
pass_fail_list.append("FAIL")
|
|
pcap_file_list.append(str(file_name))
|
|
remark.append("BSSID mismatched , No Reasso response")
|
|
print("Row list :", row_list)
|
|
logging.info("row list " + str(row_list))
|
|
if self.multicast == "True":
|
|
print(row_list)
|
|
print(pass_fail_list)
|
|
print(remark)
|
|
for i, x in zip(row_list, pass_fail_list):
|
|
i.append(x)
|
|
for i, x in zip(row_list, remark):
|
|
i.append(x)
|
|
print("Row list :", row_list)
|
|
for i, x in zip(file_n, row_list):
|
|
self.lf_csv_obj.open_csv_append(fields=x, name=i)
|
|
|
|
else:
|
|
for i, x in zip(row_list, roam_time1):
|
|
i.append(x)
|
|
print("Row list :", row_list)
|
|
logging.info("Row list : " + str(row_list))
|
|
# for i, x in zip(row_list, packet_loss_lst):
|
|
# i.append(x)
|
|
for i, x in zip(row_list, pass_fail_list):
|
|
i.append(x)
|
|
print("Row list :", row_list)
|
|
logging.info("Row list : " + str(row_list))
|
|
for i, x in zip(row_list, pcap_file_list):
|
|
i.append(x)
|
|
print("Log file :", log_file)
|
|
logging.info("Log file : " + str(log_file))
|
|
my_unnested_list = list(chain(*log_file))
|
|
print(my_unnested_list)
|
|
logging.info(str(my_unnested_list))
|
|
for i, x in zip(row_list, my_unnested_list):
|
|
i.append(x)
|
|
print("Row list :", row_list)
|
|
for i, x in zip(row_list, remark):
|
|
i.append(x)
|
|
print("Row list :", row_list)
|
|
logging.info("row list " + str(row_list))
|
|
for i, x in zip(file_n, row_list):
|
|
self.lf_csv_obj.open_csv_append(fields=x, name=i)
|
|
else:
|
|
message = "all stations are not connected to same ap for iteration " + str(
|
|
iterations)
|
|
print("All stations are not connected to same ap")
|
|
logging.info("All stations are not connected to same ap")
|
|
print("Starting Sniffer")
|
|
logging.info("Starting Sniffer")
|
|
self.start_sniffer(radio_channel=self.channel, radio=self.sniff_radio,
|
|
test_name="roam_" + str(self.sta_type) + "_" + str(
|
|
self.option) + "_iteration_" + str(
|
|
iterations) + "_", duration=3600)
|
|
print("Stop Sniffer")
|
|
logging.info("Stop Sniffer")
|
|
self.stop_sniffer()
|
|
kernel = self.journal_ctl_logs(file=str(iterations))
|
|
for i in kernel:
|
|
kernel_log.append(i)
|
|
bssid_list2 = []
|
|
for sta_name in sta_list:
|
|
# local_row_list = [0, "68"]
|
|
local_row_list = [str(iterations)]
|
|
sta = sta_name.split(".")[2]
|
|
before_bssid_ = self.station_data_query(station_name=str(sta), query="ap")
|
|
print(before_bssid_)
|
|
logging.info(str(before_bssid_))
|
|
bssid_list2.append(before_bssid_)
|
|
local_row_list.append(before_bssid_)
|
|
print(local_row_list)
|
|
logging.info(str(local_row_list))
|
|
row_list.append(local_row_list)
|
|
print(row_list)
|
|
logging.info(str(row_list))
|
|
for i, x in zip(row_list, bssid_list2):
|
|
i.append(x)
|
|
print("Row list :", row_list)
|
|
logging.info("Row list : " + str(row_list))
|
|
if self.multicast == "True":
|
|
for a in row_list:
|
|
a.append("FAIL")
|
|
print("Row list :", row_list)
|
|
else:
|
|
for i in row_list:
|
|
i.append("No Roam Time")
|
|
print("Row list :", row_list)
|
|
logging.info("Row list : " + str(row_list))
|
|
for a in row_list:
|
|
a.append("FAIL")
|
|
print("Row list :", row_list)
|
|
logging.info("Row list : " + str(row_list))
|
|
# pcap
|
|
for i in row_list:
|
|
i.append("N/A")
|
|
print("Row list:", row_list)
|
|
logging.info("Row list : " + str(row_list))
|
|
if self.debug:
|
|
print("Stop Debugger")
|
|
logging.info("Stop Debugger")
|
|
self.stop_debug_(mac_list=mac_list)
|
|
else:
|
|
print("Debug is disabled")
|
|
logging.info("Debug is disabled")
|
|
|
|
trace = self.get_file_name(client=self.num_sta)
|
|
log_file.append(trace)
|
|
print("Log file :", log_file)
|
|
logging.info("Log file : " + str(log_file))
|
|
my_unnested_list = list(chain(*log_file))
|
|
print(my_unnested_list)
|
|
logging.info(str(my_unnested_list))
|
|
for i, x in zip(row_list, my_unnested_list):
|
|
i.append(x)
|
|
print("Row list:", row_list)
|
|
logging.info("Row list : " + str(row_list))
|
|
for i in row_list:
|
|
i.append("No roam performed all stations are not connected to same ap")
|
|
print("Row list:", row_list)
|
|
logging.info("Row list : " + str(row_list))
|
|
for i, x in zip(file_n, row_list):
|
|
self.lf_csv_obj.open_csv_append(fields=x, name=i)
|
|
else:
|
|
message = "station's failed to get ip after the test start"
|
|
print("Station's failed to get ip after test starts")
|
|
logging.info("Station's failed to get ip after test starts")
|
|
if self.duration_based is True:
|
|
if time.time() > timeout:
|
|
break
|
|
except Exception as e:
|
|
# print(e)
|
|
logging.warning(str(e))
|
|
pass
|
|
else:
|
|
message = "station's failed to get ip at the beginning"
|
|
print("##### Station's failed to get associate at the beginning")
|
|
logging.info("Station's failed to get associate at the beginning")
|
|
else:
|
|
print("Stations failed to get ip")
|
|
logging.info("Stations failed to get ip")
|
|
test_end = datetime.now()
|
|
test_end = test_end.strftime("%b %d %H:%M:%S")
|
|
print("Test Ended At ", test_end)
|
|
logging.info("Test Ended At " + str(test_end))
|
|
self.end_time = test_end
|
|
s1 = test_time
|
|
s2 = test_end # for example
|
|
fmt = '%b %d %H:%M:%S'
|
|
self.test_duration = datetime.strptime(s2, fmt) - datetime.strptime(s1, fmt)
|
|
return kernel_log, message
|
|
|
|
# except Exception as e:
|
|
# logging.warning(str(e))
|
|
|
|
# Graph generation function
|
|
def generate_client_pass_fail_graph(self, csv_list=None):
|
|
try:
|
|
print("CSV list", csv_list)
|
|
logging.info("CSV list " + str(csv_list))
|
|
x_axis_category = []
|
|
for i in range(self.num_sta):
|
|
x_axis_category.append(i + 1)
|
|
print(x_axis_category)
|
|
logging.info(str(x_axis_category))
|
|
pass_list = []
|
|
fail_list = []
|
|
dataset = []
|
|
for i in csv_list:
|
|
print("i", i)
|
|
logging.info("i, " + i)
|
|
lf_csv_obj = lf_csv()
|
|
h = lf_csv_obj.read_csv(file_name=i, column="PASS/FAIL")
|
|
count = h.count("PASS")
|
|
print(count)
|
|
logging.info(str(count))
|
|
count_ = h.count("FAIL")
|
|
print(count_)
|
|
logging.info(str(count_))
|
|
pass_list.append(count)
|
|
fail_list.append(count_)
|
|
dataset.append(pass_list)
|
|
dataset.append(fail_list)
|
|
print(dataset)
|
|
logging.info(str(dataset))
|
|
# It will contain per station pass and fail number eg [[9, 7], [3, 4]] here 9, 7 are pass number for clients 3 and 4 are fail number
|
|
# dataset = [[9, 7 , 4], [1, 3, 4]]
|
|
graph = lf_graph.lf_bar_graph(_data_set=dataset,
|
|
_xaxis_name="Total Number Of Stations = " + str(self.num_sta),
|
|
_yaxis_name="Total Number of iterations = " + str(self.iteration),
|
|
_xaxis_categories=x_axis_category, _label=["PASS", "FAIL"], _xticks_font=8,
|
|
_graph_image_name="11r roam client per iteration graph",
|
|
_color=['forestgreen', 'red', 'blueviolet'], _color_edge='black',
|
|
_figsize=(13, 5), _xaxis_step=1,
|
|
_graph_title="Client Performance Over %s Iterations" % (str(self.iteration)),
|
|
_show_bar_value=True, _text_font=12, _text_rotation=45, _enable_csv=True,
|
|
_legend_loc="upper right", _legend_fontsize=12, _legend_box=(1.12, 1.01),
|
|
_remove_border=['top', 'right', 'left'], _alignment={"left": 0.011}, )
|
|
graph_png = graph.build_bar_graph()
|
|
print("graph name {}".format(graph_png))
|
|
logging.info(str("graph name {}".format(graph_png)))
|
|
return graph_png
|
|
except Exception as e:
|
|
logging.info(str(e))
|
|
print(str(e))
|
|
|
|
# Report generation function
|
|
def generate_report(self, csv_list, kernel_lst, current_path=None):
|
|
try:
|
|
option, band_, station_, iteration__ = None, None, None, None
|
|
if self.option == 'ota':
|
|
option = "OTA"
|
|
else:
|
|
option = "OTD"
|
|
if self.band == "fiveg":
|
|
band_ = "5G"
|
|
elif self.band == "twog":
|
|
band_ = "2G"
|
|
elif self.band == "sixg":
|
|
band_ = "6G"
|
|
if int(self.num_sta) > 1:
|
|
station_ = "Multi"
|
|
else:
|
|
station_ = "Single"
|
|
|
|
if int(self.iteration) > 1:
|
|
iteration__ = "Multi"
|
|
else:
|
|
iteration__ = "Single"
|
|
if self.soft_roam:
|
|
dir_name = "Soft_Roam_Test_" + str(band_) + "_" + str(option) + "_" + str(station_) + "Client_" + str(
|
|
iteration__) + "_Iteration"
|
|
out_html = "soft_roam.html"
|
|
pdf_name = "soft_roam_test.pdf"
|
|
else:
|
|
dir_name = "Hard_Roam_Test_" + str(band_) + "_" + str(option) + "_" + str(station_) + "Client_" + str(
|
|
iteration__) + "_Iteration"
|
|
out_html = "hard_roam.html"
|
|
pdf_name = "Hard_roam_test.pdf"
|
|
report = lf_report_pdf.lf_report(_path="", _results_dir_name=dir_name, _output_html=out_html,
|
|
_output_pdf=pdf_name)
|
|
if current_path is not None:
|
|
report.current_path = os.path.dirname(os.path.abspath(current_path))
|
|
report_path = report.get_report_path()
|
|
report.build_x_directory(directory_name="csv_data")
|
|
report.build_x_directory(directory_name="kernel_log")
|
|
for i in kernel_lst:
|
|
report.move_data(directory="kernel_log", _file_name=str(i))
|
|
date = str(datetime.now()).split(",")[0].replace(" ", "-").split(".")[0]
|
|
test_setup_info = {
|
|
"DUT Name": self.dut_name,
|
|
"SSID": self.ssid_name,
|
|
"Test Duration": self.test_duration,
|
|
}
|
|
if self.soft_roam:
|
|
report.set_title("SOFT ROAM (11r) TEST")
|
|
else:
|
|
if self.sta_type == "normal":
|
|
report.set_title("HARD ROAM TEST")
|
|
else:
|
|
report.set_title("HARD ROAM (11r) TEST")
|
|
report.set_date(date)
|
|
report.build_banner_cover()
|
|
report.set_table_title("Test Setup Information")
|
|
report.build_table_title()
|
|
report.test_setup_table(value="Device under test", test_setup_data=test_setup_info)
|
|
report.set_obj_html("Objective",
|
|
"The Roaming test is a type of performance test that is performed on wireless Access Points (APs)"
|
|
" to evaluate their ability to support 802.11r (Fast BSS Transition) standard for fast and seamless"
|
|
" roaming of wireless clients between APs within the same network. This standard helps minimize the"
|
|
" handoff time when a client moves from one AP to another, resulting in a more stable and consistent wireless experience.<br>"
|
|
"<br>"
|
|
"<b>Hard Roaming:</b><br>"
|
|
"This happens when a wireless device completely disconnects from the current Access Point before "
|
|
"connecting to a new one. However, with the 802.11r standard, the authentication and key negotiation"
|
|
" process can be expedited, reducing the time it takes to connect to the new Access Point. This results"
|
|
" in a faster and more seamless handoff between Access Points.<br>"
|
|
"<br>"
|
|
"<b>Soft Roaming:</b><br>"
|
|
"This happens when a wireless device maintains a connection with both the current and new Access Points"
|
|
" during the transition. With 802.11r, the device can maintain its security context during the handoff,"
|
|
" allowing for a faster and more secure transition. Soft roaming with 11r is designed to be seamless,"
|
|
" allowing the device to move from one Access Point to another without any interruption in connectivity.")
|
|
report.build_objective()
|
|
report.set_obj_html("Client per iteration Graph",
|
|
"The below graph provides information about out of total iterations how many times each client got Pass or Fail")
|
|
report.build_objective()
|
|
graph = self.generate_client_pass_fail_graph(csv_list=csv_list)
|
|
report.set_graph_image(graph)
|
|
report.set_csv_filename(graph)
|
|
report.move_csv_file()
|
|
report.move_graph_image()
|
|
report.build_graph_without_border()
|
|
if self.multicast == "True":
|
|
report.set_obj_html("Pass/Fail Criteria:",
|
|
"<b>The following are the criteria for PASS the test:</b><br><br>"
|
|
"1. The BSSID of the station should change after roaming from one AP to another.<br>"
|
|
"2. multicast traffic should resume after the client roams.<br>"
|
|
"<br>"
|
|
"<b>The following are the criteria for FAIL the test:</b><br><br>"
|
|
"1. The BSSID of the station remains unchanged after roaming from one AP to another.<br>"
|
|
"2. No roaming occurs, as all stations are connected to the same AP.<br>")
|
|
else:
|
|
report.set_obj_html("Pass/Fail Criteria:",
|
|
"<b>The following are the criteria for PASS the test:</b><br><br>"
|
|
"1. The BSSID of the station should change after roaming from one AP to another.<br>"
|
|
"2. The station should not experience any disconnections during/after the roaming process.<br>"
|
|
"3. The duration of the roaming process should be less than 100 ms.<br>"
|
|
"<br>"
|
|
"<b>The following are the criteria for FAIL the test:</b><br><br>"
|
|
"1. The BSSID of the station remains unchanged after roaming from one AP to another.<br>"
|
|
"2. No roaming occurs, as all stations are connected to the same AP.<br>"
|
|
"3. The captured packet does not contain a Reassociation Response Frame.<br>"
|
|
"4. The station experiences disconnection during/after the roaming process.<br>"
|
|
"5. The duration of the roaming process exceeds 100 ms.<br>")
|
|
report.build_objective()
|
|
for i in csv_list:
|
|
report.move_data(directory="csv_data", _file_name=str(i))
|
|
report.move_data(directory_name="pcap")
|
|
for i, x in zip(range(self.num_sta), csv_list):
|
|
# report.set_table_title("Client information " + str(i))
|
|
# report.build_table_title()
|
|
if self.multicast == "True":
|
|
report.set_obj_html("Client " + str(i + 1) + " Information",
|
|
"The table below presents comprehensive information regarding Client " + str(
|
|
i + 1) +
|
|
", including its BSSID before and after roaming, PASS/FAIL criteria and Remark")
|
|
else:
|
|
report.set_obj_html("Client " + str(i + 1) + " Information",
|
|
"The table below presents comprehensive information regarding Client " + str(
|
|
i + 1) +
|
|
", including its BSSID before and after roaming, the time of roaming, the name of "
|
|
"the capture file, and any relevant remarks.")
|
|
report.build_objective()
|
|
lf_csv_obj = lf_csv()
|
|
if self.multicast == "True":
|
|
y = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="Iterations")
|
|
z = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="bssid1")
|
|
u = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="bssid2")
|
|
h = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="PASS/FAIL")
|
|
r = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="Remark")
|
|
else:
|
|
y = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="Iterations")
|
|
z = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="bssid1")
|
|
u = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="bssid2")
|
|
t = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="Roam Time(ms)")
|
|
# l = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="Packet loss")
|
|
h = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="PASS/FAIL")
|
|
p = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="Pcap file Name")
|
|
lf = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="Log File")
|
|
r = lf_csv_obj.read_csv(file_name=str(report_path) + "/csv_data/" + str(x), column="Remark")
|
|
if self.multicast == "True":
|
|
table = {
|
|
"iterations": y,
|
|
"Bssid before": z,
|
|
"Bssid After": u,
|
|
"PASS/FAIL": h,
|
|
"Remark": r
|
|
}
|
|
else:
|
|
table = {
|
|
"iterations": y,
|
|
"Bssid before": z,
|
|
"Bssid After": u,
|
|
"Roam Time(ms)": t,
|
|
"PASS/FAIL": h,
|
|
"pcap file name": p,
|
|
"Log File": lf,
|
|
"Remark": r
|
|
}
|
|
if self.multicast != "True":
|
|
if not self.log_file:
|
|
del table["Log File"]
|
|
print("Tabel Data :", table)
|
|
test_setup = pd.DataFrame(table)
|
|
report.set_table_dataframe(test_setup)
|
|
report.build_table()
|
|
if self.option == 'ota':
|
|
testname = 'over the air'
|
|
else:
|
|
testname = 'over the ds'
|
|
test_input_infor = {
|
|
"LANforge ip": self.lanforge_ip,
|
|
"LANforge port": self.lanforge_port,
|
|
"test start time": self.start_time,
|
|
"test end time": self.end_time,
|
|
"Bands": self.band,
|
|
"Upstream": self.upstream,
|
|
"Stations": self.num_sta,
|
|
"iterations": self.iteration,
|
|
"SSID": self.ssid_name,
|
|
"Security": self.security,
|
|
"Client mac": self.mac_data,
|
|
'Test': testname,
|
|
"Contact": "support@candelatech.com"
|
|
}
|
|
report.set_table_title("Test basic Information")
|
|
report.build_table_title()
|
|
report.test_setup_table(value="Information", test_setup_data=test_input_infor)
|
|
report.build_footer()
|
|
report.write_html()
|
|
report.write_pdf_with_timestamp(_page_size='A4', _orientation='Portrait')
|
|
return report_path
|
|
except Exception as e:
|
|
print(str(e))
|
|
logging.info(str(e))
|
|
|
|
|
|
def main():
|
|
help_summary = '''\
|
|
The script is designed to support both hard and soft roaming, ensuring a smooth transition for devices between
|
|
access points (APs). Additionally, the script captures packets in two scenarios: when a device is connected to
|
|
an AP and when it roams from one AP to another. These captured packets help analyze the performance and stability
|
|
of the roaming process. In essence, the script serves as a thorough test for assessing how well APs handle
|
|
roaming and the overall network stability when clients move between different access points.
|
|
|
|
The roaming test will create stations with advanced/802.1x and 11r key management, create CX traffic between upstream
|
|
port and stations, run traffic and generate a report.
|
|
'''
|
|
parser = argparse.ArgumentParser(
|
|
prog='lf_roam_test.py',
|
|
# formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
epilog='''\
|
|
lf_roam_test.py
|
|
''',
|
|
description='''\
|
|
lf_roam_test.py :
|
|
--------------------
|
|
|
|
Summary :
|
|
----------
|
|
The primary focus of this script is to enable seamless roaming of clients/stations between two access points (APs).
|
|
The test can be conducted with a single or multiple stations, with single or multiple iterations.
|
|
|
|
The script will create stations/clients with advanced/802.1x and 11r key management. By default, it will create a
|
|
single station/client. Once the stations are created, the script will generate CX traffic between the upstream port and
|
|
the stations and run the traffic before roam.
|
|
|
|
Packet captures will be taken for each station/client in two scenarios:
|
|
|
|
(i) While the station/client is connected to an AP
|
|
(ii) While the station/client roams from one AP to another AP
|
|
|
|
These packet captures will be used to analyze the performance and stability of the roaming process.
|
|
|
|
Overall, this script is designed to provide a comprehensive test of the roaming functionality of the APs and the
|
|
stability of the network when clients move between APs.
|
|
|
|
The following are the criteria for PASS the test:
|
|
|
|
1. The BSSID of the station should change after roaming from one AP to another
|
|
2 The station should not experience any disconnections during/after the roaming process.
|
|
3. The duration of the roaming process should be less than 100 ms.
|
|
|
|
The following are the criteria for FAIL the test:
|
|
|
|
1. The BSSID of the station remains unchanged after roaming from one AP to another.
|
|
2. No roaming occurs, as all stations are connected to the same AP.
|
|
3. The captured packet does not contain a Reassociation Response Frame.
|
|
4. The station experiences disconnection during/after the roaming process.
|
|
5. The duration of the roaming process exceeds 100 ms.
|
|
|
|
|
|
############################################
|
|
# Examples Commands for different scenarios
|
|
############################################
|
|
|
|
Hard Roam
|
|
|
|
EXAMPLE: For a single station and a single iteration
|
|
python3 lf__roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 1 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 1 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based
|
|
|
|
EXAMPLE: For a single station and multiple iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 1 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 10 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based
|
|
|
|
EXAMPLE: For multiple station and a single iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 10 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 1 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based
|
|
|
|
EXAMPLE: For multiple station and multiple iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 10 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 10 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based
|
|
|
|
EXAMPLE: For multiple station and multiple iteration with multicast traffic enable
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "10:f9:20:fd:f3:4b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 2 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 1 --channel "36" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based --sta_type normal --multicast True
|
|
|
|
|
|
Soft Roam
|
|
EXAMPLE: For a single station and a single iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 1 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 1 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based --soft_roam True
|
|
|
|
EXAMPLE: For a single station and multiple iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 1 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 10 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based --soft_roam True
|
|
|
|
EXAMPLE: For multiple station and a single iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 10 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 1 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based --soft_roam True
|
|
|
|
EXAMPLE: For multiple station and multiple iteration
|
|
python3 lf_roam_test.py --mgr 192.168.100.221 --ap1_bssid "68:7d:b4:5f:5c:3b" --ap2_bssid "14:16:9d:53:58:cb"
|
|
--fiveg_radios "1.1.wiphy1" --band "fiveg" --sniff_radio "wiphy2" --num_sta 10 --ssid_name "RoamAP5g" --security "wpa2"
|
|
--security_key "something" --duration None --upstream "eth2" --iteration 10 --channel "40" --option "ota"
|
|
--dut_name ["AP1","AP2"] --traffic_type "lf_udp" --log_file False --debug False --iteration_based --soft_roam True
|
|
|
|
NOTES:
|
|
* For enterprise authentication
|
|
--eap_method <eap_method>
|
|
Add this argument to specify the EAP method
|
|
|
|
example:
|
|
TLS, TTLS, PEAP
|
|
|
|
--pairwise_cipher [BLANK]
|
|
Add this argument to specify the type of pairwise cipher
|
|
|
|
DEFAULT
|
|
CCMP
|
|
TKIP
|
|
NONE
|
|
CCMP-TKIP
|
|
CCMP-256
|
|
GCMP
|
|
GCMP-256
|
|
CCMP/GCMP-256
|
|
|
|
--groupwise_cipher [BLANK]
|
|
Add this argument to specify the type of groupwise cipher
|
|
|
|
DEFAULT
|
|
CCMP
|
|
TKIP
|
|
WEP104
|
|
WEP40
|
|
GTK_NOT_USED
|
|
GCMP-256
|
|
CCMP-256
|
|
GCMP/CCMP-256
|
|
ALL
|
|
|
|
--eap_identity <eap_identity>
|
|
Add this argument to specify the username of radius server
|
|
|
|
--eap_password <eap_password>
|
|
Add this argument to specify the password of radius server
|
|
|
|
--pk_passwd <private_key_passsword>
|
|
Add this argument to specify the private key password
|
|
Required only for TLS
|
|
|
|
--ca_cert <path_to_certificate>
|
|
Add this argument to specify the certificate path
|
|
Required only for TLS
|
|
|
|
example:
|
|
/home/lanforge/ca.pem
|
|
|
|
--private_key <path_to_private_key>
|
|
Add this argument to specify the private key path
|
|
Required only for TLS
|
|
|
|
example:
|
|
/home/lanforge/client.p12
|
|
|
|
|
|
===============================================================================
|
|
|
|
''')
|
|
required = parser.add_argument_group('Required arguments')
|
|
|
|
required.add_argument('--mgr', help='lanforge ip', default="192.168.100.221")
|
|
required.add_argument('--lanforge_port', help='lanforge port', type=int, default=8080)
|
|
required.add_argument('--lanforge_ssh_port', help='lanforge ssh port', type=int, default=22)
|
|
required.add_argument('--ap1_bssid', type=str, help='AP1 bssid', default="68:7d:b4:5f:5c:3b")
|
|
required.add_argument('--ap2_bssid', type=str, help='AP2 bssid', default="14:16:9d:53:58:cb")
|
|
required.add_argument('--twog_radios', help='Twog radio', default=None)
|
|
required.add_argument('--fiveg_radios', help='Fiveg radio', default="1.1.wiphy1")
|
|
required.add_argument('--sixg_radios', help='Sixg radio', default=None)
|
|
required.add_argument('--band', help='eg. --band "twog" or sixg', default="fiveg")
|
|
required.add_argument('--sniff_radio', help='eg. --sniff_radio "wiphy2', default="wiphy2")
|
|
required.add_argument('--num_sta', help='eg. --num_sta 1', type=int, default=1)
|
|
required.add_argument('--ssid_name', help='eg. --ssid_name "ssid_5g"', default="RoamAP5g")
|
|
required.add_argument('--security', help='eg. --security "wpa2"', default="wpa2")
|
|
required.add_argument('--security_key', help='eg. --security_key "something"', default="something")
|
|
required.add_argument('--upstream', help='eg. --upstream "eth2"', default="eth2")
|
|
required.add_argument('--duration', help='duration', default=None)
|
|
required.add_argument('--iteration', help='Number of iterations', type=int, default=1)
|
|
required.add_argument('--channel', help='Channel', type=str, default="40")
|
|
required.add_argument('--option', help='eg. --option "ota', default="ota")
|
|
required.add_argument('--iteration_based', help='Iteration based', default=False, action='store_true')
|
|
required.add_argument('--duration_based', help='Duration based', default=False, action='store_true')
|
|
required.add_argument('--dut_name', help='', default=["AP1", "AP2"]) # ["AP687D.B45C.1D1C", "AP2C57.4152.385C"]
|
|
required.add_argument('--traffic_type', help='To chose the traffic type', default="lf_udp")
|
|
# eap authentication
|
|
required.add_argument('--eap_method', help='Enter EAP method e.g: TLS', default=None)
|
|
required.add_argument('--eap_identity', help='Radius server identity', default='[BLANK]')
|
|
required.add_argument('--eap_password', help='Radius Server password', default='[BLANK]')
|
|
required.add_argument('--pairwise_cipher',
|
|
help='Pairwise Ciphers\n'
|
|
'DEFAULT\n'
|
|
'CCMP\n'
|
|
'TKIP\n'
|
|
'NONE\n'
|
|
'CCMP-TKIP\n'
|
|
'CCMP-256\n'
|
|
'GCMP\n'
|
|
'GCMP-256\n'
|
|
'CCMP/GCMP-256',
|
|
default='[BLANK]')
|
|
required.add_argument('--groupwise_cipher', type=str,
|
|
help='Groupwise Ciphers\n'
|
|
'DEFAULT\n'
|
|
'CCMP\n'
|
|
'TKIP\n'
|
|
'WEP104\n'
|
|
'WEP40\n'
|
|
'GTK_NOT_USED\n'
|
|
'GCMP-256\n'
|
|
'CCMP-256\n'
|
|
'GCMP/CCMP-256\n'
|
|
'ALL',
|
|
default='[BLANK]')
|
|
required.add_argument('--private_key',
|
|
help='Enter private key path e.g: /home/lanforge/client.p12', default='[BLANK]')
|
|
required.add_argument('--pk_passwd', help='Enter the private key password', default='[BLANK]')
|
|
required.add_argument('--ca_cert', help='Enter path for certificate e.g: /home/lanforge/ca.pem',
|
|
default='[BLANK]')
|
|
|
|
required.add_argument("--eap_phase1", help="EAP Phase 1 (outer authentication, i.e. TLS tunnel) parameters.\n"
|
|
"For example, \"peapver=0\" or \"peapver=1 peaplabel=1\".\n"
|
|
"Some WPA Enterprise setups may require \"auth=MSCHAPV2\"",
|
|
default="[BLANK]")
|
|
required.add_argument("--eap_phase2", help="EAP Phase 2 (inner authentication) parameters.\n"
|
|
"For example, \"autheap=MSCHAPV2 autheap=MD5\" for EAP-TTLS.",
|
|
default="[BLANK]")
|
|
|
|
required.add_argument('--log_file', help='To get the log file, need to pass the True', default=False)
|
|
required.add_argument('--debug', help='To enable/disable debugger, need to pass the True/False', default=False)
|
|
required.add_argument('--soft_roam', help='To enable soft rome eg. --soft_rome True', default=False)
|
|
required.add_argument('--sta_type', type=str, help="provide the type of client you want to create "
|
|
"i.e 11r, 11r-sae, 11r-eap, 11r-eap-sha384, normal",
|
|
default="11r")
|
|
required.add_argument('--ieee80211w', help='--ieee80211w <disabled(0),optional(1),required(2)', default=None)
|
|
required.add_argument('--multicast', default=False, help="set to true only if we want multicast "
|
|
"traffic run along the hard roam process")
|
|
|
|
optional = parser.add_argument_group('Optional arguments')
|
|
|
|
optional.add_argument('--scheme', help='', default="ssh")
|
|
optional.add_argument('--dest', help='', default="localhost")
|
|
optional.add_argument('--user', help='', default="admin")
|
|
optional.add_argument('--passwd', help='', default="Cisco123")
|
|
optional.add_argument('--prompt', help='', default="WLC2")
|
|
optional.add_argument('--series_cc', help='', default="9800")
|
|
optional.add_argument('--ap', help='', default="AP687D.B45C.1D1C")
|
|
optional.add_argument('--ap_ssh_port', help='', default="8888")
|
|
optional.add_argument('--band_cc', help='', default="5g")
|
|
optional.add_argument('--timeout', help='', default="10")
|
|
|
|
parser.add_argument('--help_summary', help='Show summary of what this script does', default=None,
|
|
action="store_true")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# help summary
|
|
if args.help_summary:
|
|
print(help_summary)
|
|
exit(0)
|
|
|
|
# validating arguments if EAP method selected
|
|
if args.eap_method is not None:
|
|
if args.eap_identity == '[BLANK]':
|
|
print("--eap_identity required")
|
|
exit(1)
|
|
elif args.eap_password == '[BLANK]':
|
|
print("--eap_password required")
|
|
exit(1)
|
|
elif args.eap_method == 'TLS':
|
|
if args.pk_passwd == '[BLANK]':
|
|
print("--pk_passwd required")
|
|
exit(1)
|
|
elif args.ca_cert == '[BLANK]':
|
|
print('--ca_cert required')
|
|
exit(1)
|
|
elif args.private_key == '[BLANK]':
|
|
print('--private_key required')
|
|
exit(0)
|
|
# if security is wpa3, it is necessary to have a pairwise_cipher, groupwise_cipher values
|
|
if '11r-eap-sha384' in args.sta_type:
|
|
if args.pairwise_cipher == '[BLANK]':
|
|
print('--pairwise_cipher required')
|
|
exit(1)
|
|
elif args.groupwise_cipher == '[BLANK]':
|
|
print('--groupwise_cipher required')
|
|
exit(1)
|
|
obj = HardRoam(lanforge_ip=args.mgr,
|
|
lanforge_port=args.lanforge_port,
|
|
lanforge_ssh_port=args.lanforge_ssh_port,
|
|
c1_bssid=args.ap1_bssid,
|
|
c2_bssid=args.ap2_bssid,
|
|
fiveg_radio=args.fiveg_radios,
|
|
twog_radio=args.twog_radios,
|
|
sixg_radio=args.sixg_radios,
|
|
band=args.band,
|
|
sniff_radio_=args.sniff_radio,
|
|
num_sta=args.num_sta,
|
|
security=args.security,
|
|
security_key=args.security_key,
|
|
ssid=args.ssid_name,
|
|
upstream=args.upstream,
|
|
duration=args.duration,
|
|
iteration=args.iteration,
|
|
channel=args.channel,
|
|
option=args.option,
|
|
duration_based=args.duration_based,
|
|
iteration_based=args.iteration_based,
|
|
dut_name=args.dut_name,
|
|
traffic_type=args.traffic_type,
|
|
scheme="ssh",
|
|
dest="localhost",
|
|
user="admin",
|
|
passwd="Cisco123",
|
|
prompt="WLC2",
|
|
series_cc="9800",
|
|
ap="AP687D.B45C.1D1C",
|
|
port="8888",
|
|
band_cc="5g",
|
|
timeout="10",
|
|
eap_method=args.eap_method,
|
|
eap_identity=args.eap_identity,
|
|
eap_password=args.eap_password,
|
|
pairwise_cipher=args.pairwise_cipher,
|
|
groupwise_cipher=args.groupwise_cipher,
|
|
private_key=args.private_key,
|
|
pk_passwd=args.pk_passwd,
|
|
ca_cert=args.ca_cert,
|
|
eap_phase1=args.eap_phase1,
|
|
eap_phase2=args.eap_phase2,
|
|
soft_roam=args.soft_roam,
|
|
sta_type=args.sta_type,
|
|
ieee80211w=args.ieee80211w,
|
|
multicast=args.multicast
|
|
)
|
|
x = os.getcwd()
|
|
print("Current Working Directory :", x)
|
|
file = obj.generate_csv()
|
|
print("CSV File :", file)
|
|
# obj.precleanup()
|
|
kernel, message = obj.run(file_n=file)
|
|
report_dir_name = obj.generate_report(csv_list=file, kernel_lst=kernel, current_path=str(x) + "/tests")
|
|
print(report_dir_name)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|