Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Jamming Avoidance code #338

Merged
merged 13 commits into from
Dec 5, 2023
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import socket
import sys
import threading
Expand All @@ -13,7 +14,7 @@
from channel_quality_est import ChannelQualityEstimator
from options import Options
from preprocessor import Preprocessor
from util import get_frequency_quality, get_mesh_freq, get_ipv6_addr, switch_frequency, kill_process_by_pid
from util import get_frequency_quality, get_mesh_freq, get_ipv6_addr, switch_frequency
from wireless_scanner import WirelessScanner
from log_config import logger

Expand Down Expand Up @@ -370,7 +371,9 @@ def switch_frequency(self, trigger_event) -> None:
:param trigger_event: ClientEvent that triggered the execution of this function.
"""
try:
switch_frequency(str(self.target_frequency))
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(switch_frequency(str(self.target_frequency)))

# Validate outcome of switch frequency process
self.current_frequency = get_mesh_freq()
Expand All @@ -385,6 +388,8 @@ def switch_frequency(self, trigger_event) -> None:
except Exception as e:
logger.error(f"Switching frequency error occurred: {str(e)}")
self.fsm.trigger(ClientEvent.SWITCH_UNSUCCESSFUL)
finally:
loop.close()

def recovering_switch_error(self, trigger_event) -> None:
"""
Expand Down Expand Up @@ -443,7 +448,7 @@ def main():

host: str = args.jamming_osf_orchestrator
port: int = args.port
node_id: str = get_ipv6_addr('tun0')
node_id: str = get_ipv6_addr(args.osf_interface)

client = JammingDetectionClient(node_id, host, port)
client.start()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
import netifaces
import sys
import time
import re
import os
import asyncio
import numpy as np
import util
import options
from options import Options
from log_config import logger

Expand All @@ -21,9 +25,9 @@ def is_interface_up(interface) -> bool:
return interface in available_interfaces


def switch_frequency(args) -> None:
async def switch_frequency(args) -> None:
dania-tii marked this conversation as resolved.
Show resolved Hide resolved
"""
Change the mesh frequency to the starting frequency.
Change to starting frequency.
"""
# Set max number of reties to perform the switch frequency method in case it fails
max_retries: int = 3
Expand All @@ -36,10 +40,11 @@ def switch_frequency(args) -> None:
while not switch_successful:
try:
# Execute switch frequency function
util.switch_frequency(starting_frequency)
await util.switch_frequency(starting_frequency)

# Validate outcome of switch frequency process
mesh_freq = util.get_mesh_freq()

if mesh_freq != int(starting_frequency):
if num_switch_freq_retries < max_retries:
logger.info("Frequency switch unsuccessful... retrying")
Expand All @@ -64,7 +69,7 @@ def switch_frequency(args) -> None:
sys.exit(1)


def check_osf_connectivity(args) -> None:
def check_client_osf_connectivity(args) -> None:
"""
Check IPv6 connectivity with a remote server.

Expand All @@ -86,19 +91,22 @@ def check_osf_connectivity(args) -> None:
sys.exit(1)


def setup_osf_interface(args: Options) -> None:
def check_osf_interface(args: Options) -> None:
"""
Set up the OSF interface using a tun0 tunnel.
Check if OSF interface is up.

param args: Options object containing configuration options.
"""
num_retries: int = 0
max_retries: int = 3
interface_status: bool = is_interface_up(args.osf_interface)
if not interface_status:
try:
setup_osf_interface_command = ["start_tunslip6.sh", "/dev/nrf0", f"{args.osf_interface}"]
util.run_command(setup_osf_interface_command, "Failed to set up OSF interface")
except Exception as e:
logger.error(f"OSF interface failed: {e}")

while not interface_status and num_retries < max_retries:
time.sleep(10)
interface_status = is_interface_up(args.osf_interface)
num_retries += 1
if num_retries == max_retries:
logger.error(f"OSF interface failed")
sys.exit(1)


Expand Down Expand Up @@ -163,34 +171,129 @@ def start_jamming_scripts(args, osf_ipv6_address) -> None:
logger.info(f"Error starting jamming server/client scripts: {e}")
raise Exception(e)

def main():

def get_device_identity(args) -> None:
"""
Request device identity
"""
# Store the current working directory
current_directory = os.getcwd()

try:
# Change the directory to the scripts_path
os.chdir(args.nats_scripts_path)

# Define the commands to execute
command = ["python", "_cli_command_get_identity.py"]
dania-tii marked this conversation as resolved.
Show resolved Hide resolved

# Execute the command
max_retries = 3

for retry_count in range(max_retries):
try:
subprocess.run(command, check=True)
break # Break if successful
except subprocess.CalledProcessError as e:
if retry_count < max_retries - 1:
logger.error(f"Retrying _cli_command_get_identity.py...")
time.sleep(2)
else:
logger.error(f"Error executing command {command}: {e}")
exit(0)

finally:
# Change back to the original directory after executing the commands
os.chdir(current_directory)


def validate_configuration(args) -> bool:
"""
Validate that scan interface and scan channels configurations are valid for jamming detection feature.

:return: A boolean to denote whether the device and parameter configurations are valid.
"""
valid = True

# Check that scan interface is set to 20mhz
try:
iw_output = os.popen('iw dev').read()
iw_output = re.sub(r'\s+', ' ', iw_output).split(' ')

# Extract interface sections from iw_output
idx_list = [idx - 1 for idx, val in enumerate(iw_output) if val == "Interface"]
if len(idx_list) > 1:
idx_list.pop(0)

# Calculate the start and end indices for interface sections
start_indices = [0] + idx_list
end_indices = idx_list + ([len(iw_output)] if idx_list[-1] != len(iw_output) else [])

# Use zip to create pairs of start and end indices, and extract interface sections
iw_interfaces = [iw_output[start:end] for start, end in zip(start_indices, end_indices)]

# Get scan interface channel width
for interface_list in iw_interfaces:
if args.scan_interface in interface_list:
channel_width_index = interface_list.index("width:") + 1
channel_width = re.sub("[^0-9]", "", interface_list[channel_width_index]).split()[0]
if channel_width != "20":
logger.info(f"{args.scan_interface} interface channel width must be set to 20 MHz.")
valid = False
break
else:
logger.info(f"No {args.scan_interface} interface")
except Exception as e:
logger.error(f"Exception: {e}")

# Check that list of channels to scan does not include any DFS channels
if not all(channel in options.VALID_CHANNELS for channel in args.channels5):
logger.info("5 GHz channels must be of the following: (36,40,44,48,149,153,157,161)")
valid = False

# Return validity after all checks performed
return valid


async def main():
# Create Options instance
args = Options()
dania-tii marked this conversation as resolved.
Show resolved Hide resolved

# Set up tun0 interface for OSF
logger.info("1. Set up tun0 osf interface")
setup_osf_interface(args)
# Get device identity
logger.info("1. Get device identity")
get_device_identity(args)
await asyncio.sleep(3)

# Switch to starting frequency
logger.info("2. Switch to starting frequency")
mesh_freq = util.get_mesh_freq()
if mesh_freq == np.nan or util.map_channel_to_freq(args.starting_channel) != mesh_freq:
switch_frequency(args)
await switch_frequency(args)

# Validate configuration parameters
logger.info("3. Validate configuration parameters")
if not validate_configuration(args):
raise Exception("Configuration validation failed. Please adjust the configurations according to the above to run the jamming avoidance feature.")

# Check interface for OSF
logger.info("4. Check if osf interface is up")
check_osf_interface(args)

# Get the IPv6 address of the tun0 interface
logger.info("3. Get ipv6 of tun0")
# Get IPv6 address of osf interface
logger.info("5. Get ipv6 of osf interface")
osf_ipv6_address = util.get_ipv6_addr(args.osf_interface)
if osf_ipv6_address is None:
raise ValueError("IPv6 address of the tun0 interface is not available.")

# If the current node is a client, check ipv6 connectivity with server
logger.info("4. Check connectivity")
if args.jamming_osf_orchestrator != osf_ipv6_address:
check_osf_connectivity(args)
logger.info("6. Check client osf connectivity with server")
check_client_osf_connectivity(args)

# Start jamming-related scripts
start_jamming_scripts(args, osf_ipv6_address)


if __name__ == "__main__":
main()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from logging.handlers import RotatingFileHandler

# Add a rotating file handler for log rotation (max 1 MB, keep 1 backup)
log_handler = RotatingFileHandler("jamming_avoidance.log", maxBytes=1e6, backupCount=1)
log_handler = RotatingFileHandler("/var/log/jamming_avoidance.log", maxBytes=1e6, backupCount=1)
log_handler.setLevel(logging.INFO)

# Create a formatter for the log messages
Expand Down
Original file line number Diff line number Diff line change
@@ -1,85 +1,24 @@
import os
import re
from typing import List
from log_config import logger

VALID_CHANNELS = [36, 40, 44, 48, 149, 153, 157, 161]


class Options:
def __init__(self):
self.jamming_osf_orchestrator: str = 'fd01::1'
self.jamming_osf_orchestrator: str = 'fd04::1'
dania-tii marked this conversation as resolved.
Show resolved Hide resolved
self.port: int = 8080
self.radio_index: str = "0"
self.starting_channel: int = 36
self.channels5: List[int] = [36, 40, 44, 48, 149, 153, 157, 161]
self.osf_interface: str = 'tun0'
self.scan_interface: str = 'wlp1s0'
self.mesh_interface: str = 'wlp1s0'
self.debug: bool = False
self.debug: bool = True
self.min_rows: int = 16
self.periodic_scan: float = 60
self.periodic_recovery_switch: float = 20
self.periodic_target_freq_broadcast: float = 10
self.data_gathering_wait_time: float = len(self.channels5) + 5
self.log_file: str = '/opt/mesh_com/modules/sc-mesh-secure-deployment/src/2_0/features/jamming/jamming_avoidance.log'
self.log_file: str = '/var/log/jamming_avoidance.log'
# Path to JSON file containing mean and std for spectral scan data normalization
self.col_mean_std_path: str = '/opt/mesh_com/modules/sc-mesh-secure-deployment/src/2_0/features/jamming/normalization_data/cols_mean_std.json'
self.traced_model_path: str = "/opt/mesh_com/modules/sc-mesh-secure-deployment/src/2_0/features/jamming/my_traced_model.pt"

def validate_configuration(self) -> bool:
"""
Validate that device and options file configurations are compatible for jamming detection.

:return: A boolean to denote whether the device and parameter configurations are valid.
"""
valid = True

# Check that mesh is set to 20mhz
try:
iw_output = os.popen('iw dev').read()
iw_output = re.sub(r'\s+', ' ', iw_output).split(' ')

# Extract interface sections from iw_output
idx_list = [idx - 1 for idx, val in enumerate(iw_output) if val == "Interface"]
if len(idx_list) > 1:
idx_list.pop(0)

# Calculate the start and end indices for interface sections
start_indices = [0] + idx_list
end_indices = idx_list + ([len(iw_output)] if idx_list[-1] != len(iw_output) else [])

# Use zip to create pairs of start and end indices, and extract interface sections
iw_interfaces = [iw_output[start:end] for start, end in zip(start_indices, end_indices)]

# Get mesh interface channel width
for interface_list in iw_interfaces:
if "mesh" in interface_list:
channel_width_index = interface_list.index("width:") + 1
channel_width = re.sub("[^0-9]", "", interface_list[channel_width_index]).split()[0]
if channel_width != "20":
logger.info("Mesh interface channel width must be set to 20 MHz.")
valid = False
break
else:
logger.info("No mesh interface")
except Exception as e:
logger.error(f"Exception: {e}")

# Check that list of channels does not include any DFS channels
if not all(channel in VALID_CHANNELS for channel in self.channels5):
logger.info("5 GHz channels must be of the following: (36,40,44,48,149,153,157,161)")
valid = False

# Return validity after all checks performed
return valid


def main():
args = Options()
# Validate user input parameter
if not args.validate_configuration():
raise Exception("Please adjust the jamming detection configuration according to the above.")


if __name__ == "__main__":
main()
self.nats_scripts_path: str = '/opt/mesh_com/modules/sc-mesh-secure-deployment/src/nats/scripts'
Loading