diff --git a/final/interface.py b/final/interface.py index 8fdb836..1adaa30 100644 --- a/final/interface.py +++ b/final/interface.py @@ -238,4 +238,3 @@ def main(): if __name__ == "__main__": main() - diff --git a/final/main.py b/final/main.py index 21d360d..2e31c02 100644 --- a/final/main.py +++ b/final/main.py @@ -1,24 +1,362 @@ #!/usr/bin/env python3 +import math +import heapq +import random import argparse -from utils.distance import apply_distance -from utils.insert import (login, insert_ue) -from utils.generate_upf_configs import ( - generate_upf_config, - generate_docker_compose, - generate_smf_config, - generate_uerouting_config -) import os import yaml import subprocess import time import docker +from collections import defaultdict +from pathlib import Path from datetime import datetime import sys +# Import utility functions +try: + from utils.upf_path_updater import update_upf_path + from utils.distance import apply_distance + from utils.insert import (login, insert_ue) + from utils.generate_upf_configs import ( + generate_upf_config, + generate_docker_compose, + generate_smf_config, + generate_uerouting_config + ) +except ImportError as e: + print(f"Warning: Could not import utility functions: {e}") + print("Make sure all utility modules are available in the utils/ directory") + + +class UPFNetwork: + def __init__(self): + self.graph = defaultdict(dict) + self.upf_loads = defaultdict(int) + self.upf_positions = {} + self.psa_position = None + self.psa_upf = "psa" + self.edge_upfs = set() + + def add_upf(self, upf_id, position): + self.upf_positions[upf_id] = position + + def set_psa(self, position): + self.psa_position = position + self.upf_positions[self.psa_upf] = position + + def connect_upfs(self, upf1, upf2): + distance = math.dist(self.upf_positions[upf1], self.upf_positions[upf2]) + self.graph[upf1][upf2] = distance + self.graph[upf2][upf1] = distance + + def get_path_cost(self, path, alpha=1.0, beta=0.5): + total_cost = 0 + for i in range(len(path)-1): + current = path[i] + next_node = path[i+1] + distance = self.graph[current][next_node] + load_cost = self.upf_loads[next_node] + total_cost += alpha * distance + beta * load_cost + return total_cost + + def constrained_dijkstra(self, start, end, exact_hops, alpha=1.0, beta=0.5): + heap = [] + heapq.heappush(heap, (0, 1, start, [start])) + + while heap: + current_cost, current_len, current_node, path = heapq.heappop(heap) + + if current_len == exact_hops: + if current_node == end: + return path, current_cost + continue + + for neighbor, distance in self.graph[current_node].items(): + if neighbor in path: + continue + if neighbor in self.edge_upfs and neighbor != end: + continue + new_path = path + [neighbor] + step_cost = alpha * distance + beta * self.upf_loads[neighbor] + new_cost = current_cost + step_cost + heapq.heappush(heap, (new_cost, current_len + 1, neighbor, new_path)) + + raise ValueError(f"No valid path of exactly {exact_hops} nodes from {start} to {end}") + + +def rename_upfs(network, edge_upfs): + """Rename UPFs according to their roles (edge, intermediate, PSA)""" + renamed_positions = {} + renamed_loads = {} + renamed_graph = defaultdict(dict) + old_to_new = {} + new_to_old = {} + i = 1 + j = 1 + + for upf in network.upf_positions: + if upf == network.psa_upf: + new_name = network.psa_upf + elif upf in edge_upfs: + new_name = f"edge-upf{i}" + i += 1 + else: + new_name = f"i-upf{j}" + j += 1 + old_to_new[upf] = new_name + new_to_old[new_name] = upf + + # Update positions and loads + for old, new in old_to_new.items(): + renamed_positions[new] = network.upf_positions[old] + renamed_loads[new] = network.upf_loads[old] + + # Update graph + for old_src, neighbors in network.graph.items(): + new_src = old_to_new[old_src] + for old_dst, dist in neighbors.items(): + new_dst = old_to_new[old_dst] + renamed_graph[new_src][new_dst] = dist + + # Apply changes to network + network.upf_positions = renamed_positions + network.upf_loads = renamed_loads + network.graph = renamed_graph + network.edge_upfs = {old_to_new[u] for u in edge_upfs} + return old_to_new, new_to_old + + +def get_coordinates(prompt, default=None, random_range=10): + """Get coordinates from user input or generate random ones""" + if default == "random": + return (random.uniform(0, random_range), random.uniform(0, random_range)) + while True: + coords = input(prompt).strip() + if not coords and default is not None: + return default + try: + x, y = map(float, coords.split()) + return (x, y) + except ValueError: + print("Invalid input. Enter two numbers separated by space.") + + +def generate_network(num_ue, num_upfs, m, skip=False): + """Generate network topology with UPFs and gNB""" + print("\n🔧 Configuring network...") + max_e = num_upfs - m + 1 + print(f"📈 Maximum edge UPFs allowed: {max_e}") + + network = UPFNetwork() + + # Single gNB for all UEs (interface.py logic) + print("\n📡 Configure single gNB for all UEs:") + gnb_pos = (0.0, 0.0) if skip else get_coordinates("gNB (x y): ", (0.0, 0.0)) + gnbs = {"gnb1": gnb_pos} + print(f" ➤ gNB: {gnb_pos}") + + print(f"\n🖧 {'Generating' if skip else 'Enter'} coordinates for {num_upfs} UPFs:") + for i in range(1, num_upfs): + pos = (random.uniform(0, 10), random.uniform(0, 10)) if skip else get_coordinates(f"UPF{i} (x y): ") + network.add_upf(f"upf{i}", pos) + print(f" ➤ UPF{i}: {pos}") + + print("\n🛡 PSA configuration:") + psa_pos = (random.uniform(0, 10), random.uniform(0, 10)) if skip else get_coordinates("Enter PSA coordinates (x y): ") + network.set_psa(psa_pos) + print(f" ➤ PSA: {psa_pos}") + + # Connect all UPFs to each other + all_upfs = list(network.upf_positions.keys()) + for i in range(len(all_upfs)): + for j in range(i+1, len(all_upfs)): + network.connect_upfs(all_upfs[i], all_upfs[j]) + + return network, gnbs, max_e + + +def find_best_path(network, gnb_pos, num_ue, m, alpha=1.0, beta=0.5): + """Find best path using interface.py logic: single edge UPF closest to gNB""" + # Find the closest UPF to the gNB to serve as the edge UPF + min_distance = float('inf') + edge_upf = None + + for upf_id, upf_pos in network.upf_positions.items(): + if upf_id == network.psa_upf: + continue + distance = math.dist(gnb_pos, upf_pos) + if distance < min_distance: + min_distance = distance + edge_upf = upf_id + + # Add load to the edge UPF (all UEs are connected to this edge UPF) + network.upf_loads[edge_upf] += num_ue + network.edge_upfs = {edge_upf} + + # Rename UPFs for clarity and get both mappings + old_to_new, new_to_old = rename_upfs(network, {edge_upf}) + renamed_edge_upf = old_to_new[edge_upf] + + # Calculate the best path from edge UPF to PSA + try: + path, cost = network.constrained_dijkstra(renamed_edge_upf, network.psa_upf, m, alpha, beta) + + # Update loads for UPFs in the path + for upf in path[1:-1]: + network.upf_loads[upf] += num_ue + + return path, cost, renamed_edge_upf, new_to_old + except ValueError as e: + return [], 0, renamed_edge_upf, {} + + +def convert_path_to_original_names(path, new_to_old): + """Convert path with renamed UPFs back to original UPF names""" + original_path = [] + for upf in path: + if upf == "psa": + original_path.append("PSA") + elif upf in new_to_old: + original_name = new_to_old[upf].upper() + original_path.append(original_name) + else: + original_path.append(upf) + return original_path + + +def generate_configuration_files(num_upfs, edge_upfs=1): + """Generate all configuration files for the UPF topology""" + print("\n🔧 Generating configuration files...") + + # Set up config output directory + custom_config_dir = "./config/custom" + os.makedirs(custom_config_dir, exist_ok=True) + + try: + # Generate UPF configuration files + for i in range(1, num_upfs): + hostname = f"i-upf{i}" if i > 1 else "i-upf" + is_edge = i <= edge_upfs + upf_config = generate_upf_config(hostname, is_edge=is_edge) + + with open(os.path.join(custom_config_dir, f"upfcfg-{hostname}.yaml"), "w") as f: + yaml.dump(upf_config, f, default_flow_style=False) + + # Generate PSA-UPF configuration + psa_config = generate_upf_config("remote-surgery", is_psa=True) + with open(os.path.join(custom_config_dir, "upfcfg-psa-upf.yaml"), "w") as f: + yaml.dump(psa_config, f, default_flow_style=False) + + # Generate SMF configuration + smf_config = generate_smf_config(num_upfs, edge_upfs) + with open(os.path.join(custom_config_dir, "smfcfg.yaml"), "w") as f: + yaml.dump(smf_config, f, default_flow_style=False) + + # Generate UE routing configuration + uerouting_config = generate_uerouting_config(num_upfs, edge_upfs) + with open(os.path.join(custom_config_dir, "uerouting.yaml"), "w") as f: + yaml.dump(uerouting_config, f, default_flow_style=False) + + # Generate Docker Compose configuration in the current directory + docker_compose = generate_docker_compose(num_upfs, edge_upfs) + with open("docker-compose-custom.yaml", "w") as f: + yaml.dump(docker_compose, f, default_flow_style=False) + + print(f"✅ Configuration files generated:") + print(f" - {num_upfs - 1} intermediate UPF configs in {custom_config_dir}") + print(f" - 1 PSA UPF config in {custom_config_dir}") + print(f" - SMF config with UPF topology in {custom_config_dir}") + print(f" - UE routing config in {custom_config_dir}") + print(f" - Docker Compose file: ./docker-compose-custom.yaml") + + return True + except Exception as e: + print(f"❌ Error generating configuration files: {e}") + return False + + +def start_containers(): + """Start Docker containers""" + print("\n🐳 Starting containers with docker-compose...") + try: + subprocess.run(["docker", "compose", "-f", "docker-compose-custom.yaml", "up", "-d"], + check=True, capture_output=True, text=True) + print("✅ Containers started in detached mode") + return True + except subprocess.CalledProcessError as e: + print(f"❌ Failed to start containers: {e}") + print(f"Error output: {e.stderr}") + return False + except FileNotFoundError: + print("❌ Docker or docker-compose not found. Please install Docker.") + return False + + +def check_upf_containers_running(num_upfs, timeout=60): + """Check if all UPF containers are running""" + try: + client = docker.from_env() + expected_upfs = num_upfs + + start_time = time.time() + while time.time() - start_time < timeout: + running_upfs = 0 + try: + containers = client.containers.list() + for container in containers: + if 'upf' in container.name.lower(): + if container.status == 'running': + running_upfs += 1 + if running_upfs >= expected_upfs: + return True + except Exception as e: + print(f"Error checking containers: {e}") + + time.sleep(5) + print(f"Waiting for UPF containers to start... ({running_upfs}/{expected_upfs} running)") + + return False + except Exception as e: + print(f"Docker not available or error: {e}") + return True + + +def update_path_configuration(upf_path): + """Update the UPF path in configuration files""" + print(f"\n🔄 Updating UPF path configuration...") + + # Configuration file paths + smf_config_path = "./config/custom/smfcfg.yaml" + uerouting_config_path = "./config/custom/uerouting.yaml" + + # Verify that config files exist + if not Path(smf_config_path).exists(): + print(f"❌ Error: SMF configuration file not found at {smf_config_path}") + return False + + if not Path(uerouting_config_path).exists(): + print(f"❌ Error: UE routing configuration file not found at {uerouting_config_path}") + return False + + try: + # Call the update function + print(f" Updating path to: {' -> '.join(upf_path)}") + result = update_upf_path(upf_path, smf_config_path, uerouting_config_path) + + if result: + print("✅ Configuration updated successfully!") + return True + else: + print("❌ Failed to update configuration.") + return False + except Exception as e: + print(f"❌ Error updating path configuration: {e}") + return False def generate_ue_configs(total_ues): + """Generate UE configuration files (from main.py)""" # Chemins des répertoires scripts_dir = os.path.dirname(os.path.abspath(__file__)) config_dir = os.path.join(scripts_dir, 'config/custom/ue') @@ -38,9 +376,6 @@ def generate_ue_configs(total_ues): print(f"Il existe déjà {existing_count} fichiers UE (le total demandé est {total_ues})") return - # Tenant ID (peut être personnalisé) - tenant_id = "6b8d30f1-c2a4-47e3-989b-72511aef87d8" - # Charger le modèle de configuration template_path = os.path.join(config_dir, 'uecfg1.yaml') if not os.path.exists(template_path): @@ -52,7 +387,6 @@ def generate_ue_configs(total_ues): # Générer les nouveaux fichiers for i in range(existing_count + 1, total_ues + 1): - # Générer le nom du fichier new_filename = f"uecfg{i}.yaml" new_filepath = os.path.join(config_dir, new_filename) @@ -67,32 +401,9 @@ def generate_ue_configs(total_ues): insert_ue(i) print(f"Données MongoDB insérées pour ue {i}") -def check_upf_containers_running(num_upfs, timeout=60): - """Check if all UPF containers are running""" - client = docker.from_env() - expected_upfs = num_upfs # includes all UPFs (intermediate + PSA) - - start_time = time.time() - while time.time() - start_time < timeout: - running_upfs = 0 - try: - containers = client.containers.list() - for container in containers: - if 'upf' in container.name.lower(): - if container.status == 'running': - running_upfs += 1 - if running_upfs >= expected_upfs: - return True - except Exception as e: - print(f"Error checking containers: {e}") - - time.sleep(5) - print(f"Waiting for UPF containers to start... ({running_upfs}/{expected_upfs} running)") - - return False - + def connect_ues(): - """Executes nr-ue inside the UERANSIM container for each UE config file""" + """Execute nr-ue inside the UERANSIM container for each UE config file""" try: client = docker.from_env() container = next((c for c in client.containers.list() if 'ueransim' in c.name.lower()), None) @@ -119,33 +430,215 @@ def connect_ues(): print(f"Error executing UE commands in UERANSIM: {e}") +def handle_ue_generation(num_ues): + """Handle UE generation and connection""" + if num_ues <= 0: + print("Number of UEs must be greater than 0.") + return False + + print(f"\n👥 Generating {num_ues} UE configurations...") + generate_ue_configs(num_ues) + + try: + client = docker.from_env() + ueransim_container = next( + (c for c in client.containers.list(all=True) if 'ueransim' in c.name.lower()), None) + + if ueransim_container: + print(f"Redémarrage du conteneur {ueransim_container.name}...") + ueransim_container.restart() + print("UERANSIM redémarré avec succès.") + time.sleep(5) + connect_ues() + else: + print("Conteneur UERANSIM introuvable.") + except docker.errors.DockerException as e: + print(f"Erreur lors du redémarrage de UERANSIM : {e}") + + print(f"✅ Generated {num_ues} UE configuration files") + return True + + +def collect_coordinates(upf_path, skip=False): + """Collect coordinates for distance-based shaping""" + if skip: + return {} + + print("\n📍 Enter geographic coordinates for distance-based bandwidth shaping:") + upf_coords = {} + + for upf_name in upf_path: + if upf_name.lower() != "psa": + try: + x = float(input(f" Enter latitude for {upf_name}: ")) + y = float(input(f" Enter longitude for {upf_name}: ")) + upf_coords[upf_name.lower()] = {"x": x, "y": y} + except ValueError: + print(f" Invalid coordinates for {upf_name}, skipping...") + + return upf_coords + + +def apply_distance_shaping(upf_coords): + """Apply distance-based bandwidth shaping""" + if not upf_coords: + return + + print("\n🌐 Applying distance-based bandwidth limits using tc...") + try: + apply_distance(upf_coords) + print("✅ Bandwidth shaping complete.") + except Exception as e: + print(f"❌ Error applying distance shaping: {e}") + + def main(): - """Main function to parse arguments and generate configuration files""" - parser = argparse.ArgumentParser(description="Generate Free5GC configuration files") + """Main function combining both programs' functionality""" + parser = argparse.ArgumentParser(description="Integrated 5G Network Configuration System") - # UPF topology arguments + # Network topology arguments parser.add_argument("--num_upfs", type=int, help="Total number of UPFs (including PSA-UPF)") parser.add_argument("--edge_upfs", type=int, default=1, help="Number of edge UPFs with N3 interfaces") # UE configuration arguments parser.add_argument("--ue", type=int, help="Number of UE config files to generate") + + # Interactive mode arguments + parser.add_argument("--interactive", action="store_true", help="Run in interactive mode for optimal path calculation") + parser.add_argument("--skip", action="store_true", help="Skip coordinate input and generate random network") + parser.add_argument("--no-docker", action="store_true", help="Skip Docker container startup") + parser.add_argument("--no-shaping", action="store_true", help="Skip distance-based bandwidth shaping") args = parser.parse_args() - # Handle UPF topology generation if num_upfs is provided + # Interactive mode (interface.py logic) + if args.interactive: + return run_interactive_mode(args) + + # Legacy mode (main.py logic) if args.num_upfs is not None: - handle_topology_generation(args) - - # Handle UE generation if ue is provided + return handle_topology_generation(args) + if args.ue is not None: - handle_ue_generation(args) + return handle_ue_generation(args.ue) # If no arguments provided, show help - if args.num_upfs is None and args.ue is None: - parser.print_help() + parser.print_help() + return 0 + + +def run_interactive_mode(args): + """Run the interactive mode with optimal path calculation""" + print("🏗️ Integrated 5G Network Configuration System") + print("=" * 50) + print("This system will:") + print("1. Calculate optimal UPF paths") + print("2. Generate configuration files") + print("3. Start Docker containers") + print("4. Update paths with optimal configuration") + print("5. Generate UE configurations") + print("6. Apply distance-based bandwidth shaping") + print("=" * 50) + + # Step 1: Collect network parameters + print("\n📊 STEP 1: Network Parameter Collection") + try: + num_ue = int(input("👥 Enter number of UEs: ")) + num_upfs = int(input("🔢 Enter number of UPFs (n): ")) + m = int(input("🔗 Enter number of UPFs each UE passes by (m): ")) + + # Validate inputs + if num_upfs < 2: + raise ValueError("Number of UPFs must be at least 2") + if m < 2 or m > num_upfs: + raise ValueError(f"m must be between 2 and {num_upfs}") + if num_ue < 1: + raise ValueError("Number of UEs must be at least 1") + + except ValueError as e: + print(f"❌ Invalid input: {e}") + return 1 + + # Step 2: Generate network topology and find optimal path + print("\n🗺️ STEP 2: Network Topology Generation and Path Calculation") + try: + network, gnbs, max_e = generate_network(num_ue, num_upfs, m, args.skip) + + alpha = 1.0 + beta = 0.5 + + gnb_pos = list(gnbs.values())[0] + path, cost, edge_upf, new_to_old = find_best_path(network, gnb_pos, num_ue, m, alpha, beta) + + print(f"\n📊 Network Configuration Summary:") + print(f" ➤ Single gNB for all {num_ue} UEs") + + # Show edge UPF with original name + if edge_upf in new_to_old: + original_edge_name = new_to_old[edge_upf].upper() + print(f" ➤ Edge UPF: {original_edge_name} (renamed to {edge_upf})") + else: + print(f" ➤ Edge UPF: {edge_upf}") + + if not path: + print(f"❌ No valid path found with exactly {m} UPFs from {edge_upf} to {network.psa_upf}") + return 1 + + # Convert path to original names for display + original_path = convert_path_to_original_names(path, new_to_old) + + print(f"\n🚚 Optimal path found ({len(path)-1} hops):") + print(f" ➤ Original UPF names: {' -> '.join(original_path)} (cost: {cost:.2f})") + print(f" ➤ Internal names: {' -> '.join(path)}") + + except Exception as e: + print(f"❌ Error in network generation or path calculation: {e}") + return 1 + + # Step 3: Generate configuration files + print("\n📝 STEP 3: Configuration File Generation") + edge_upfs = 1 + if not generate_configuration_files(num_upfs, edge_upfs): + return 1 + + # Step 4: Start Docker containers (optional) + if not args.no_docker: + print("\n🐳 STEP 4: Docker Container Startup") + if not start_containers(): + print("⚠️ Container startup failed, but continuing with configuration updates...") + else: + print("⏳ Waiting for containers to be ready...") + if not check_upf_containers_running(num_upfs): + print("⚠️ Timeout waiting for containers, but continuing...") + + # Step 5: Update path configuration + print("\n🔄 STEP 5: Path Configuration Update") + if not update_path_configuration(path): + return 1 + + # Step 6: Generate UE configurations + print("\n👥 STEP 6: UE Configuration Generation") + if not handle_ue_generation(num_ue): + return 1 + + # Step 7: Apply distance-based shaping (optional) + if not args.no_shaping: + print("\n🌐 STEP 7: Distance-based Bandwidth Shaping") + upf_coords = collect_coordinates(path, args.skip) + apply_distance_shaping(upf_coords) + + print("\n🎉 SUCCESS: Network configuration completed!") + print(f" Original path: {' -> '.join(original_path)}") + print(f" Internal path: {' -> '.join(path)}") + print(f" Path cost: {cost:.2f}") + print(f" UEs configured: {num_ue}") + print(" All configuration files are ready and updated.") + + return 0 + def handle_topology_generation(args): - """Handle the topology generation""" + """Handle the topology generation (legacy main.py mode)""" # Validate arguments if args.num_upfs < 2: raise ValueError("Number of UPFs must be at least 2 (one PSA-UPF and at least one intermediate UPF)") @@ -153,46 +646,9 @@ def handle_topology_generation(args): if args.edge_upfs < 1 or args.edge_upfs >= args.num_upfs: raise ValueError("Number of edge UPFs must be at least 1 and less than total UPFs") - # Set up config output directory - - custom_config_dir = "./config/custom" - os.makedirs(custom_config_dir, exist_ok=True) - - # Generate UPF configuration files - for i in range(1, args.num_upfs): - hostname = f"i-upf{i}" if i > 1 else "i-upf" - is_edge = i <= args.edge_upfs - upf_config = generate_upf_config(hostname, is_edge=is_edge) - - with open(os.path.join(custom_config_dir, f"upfcfg-{hostname}.yaml"), "w") as f: - yaml.dump(upf_config, f, default_flow_style=False) - - # Generate PSA-UPF configuration - psa_config = generate_upf_config("remote-surgery", is_psa=True) - with open(os.path.join(custom_config_dir, "upfcfg-psa-upf.yaml"), "w") as f: - yaml.dump(psa_config, f, default_flow_style=False) - - # Generate SMF configuration - smf_config = generate_smf_config(args.num_upfs, args.edge_upfs) - with open(os.path.join(custom_config_dir, "smfcfg.yaml"), "w") as f: - yaml.dump(smf_config, f, default_flow_style=False) - - # Generate UE routing configuration - uerouting_config = generate_uerouting_config(args.num_upfs, args.edge_upfs) - with open(os.path.join(custom_config_dir, "uerouting.yaml"), "w") as f: - yaml.dump(uerouting_config, f, default_flow_style=False) - - # Generate Docker Compose configuration in the current directory - docker_compose = generate_docker_compose(args.num_upfs, args.edge_upfs) - with open("docker-compose-custom.yaml", "w") as f: - yaml.dump(docker_compose, f, default_flow_style=False) - - print(f"Configuration files generated:") - print(f"- {args.num_upfs - 1} intermediate UPF configs in {custom_config_dir}") - print(f"- 1 PSA UPF config in {custom_config_dir}") - print(f"- SMF config with UPF topology in {custom_config_dir}") - print(f"- UE routing config in {custom_config_dir}") - print(f"- Docker Compose file: ./docker-compose-custom.yaml") + # Generate configuration files + if not generate_configuration_files(args.num_upfs, args.edge_upfs): + return 1 # Start the containers print("\nStarting containers with docker-compose...") @@ -201,60 +657,17 @@ def handle_topology_generation(args): print("Containers started in detached mode") except subprocess.CalledProcessError as e: print(f"Failed to start containers: {e}") - return - - # Wait for UPF containers to start - print("\nWaiting for all UPF containers to start...") - #if not check_upf_containers_running(args.num_upfs): - # print("Timeout waiting for UPF containers to start") - # return - #print("All UPF containers are running") - - # Prompt for coordinates and apply distance-based shaping - print("\nNow, enter the geographic coordinates (x, y) for each UPF (in decimal degrees):") - upf_coords = {} - for i in range(1, args.num_upfs): - name = f"i-upf{i}" if i > 1 else "i-upf" - x = float(input(f"Enter x (latitude) for {name}: ")) - y = float(input(f"Enter y (longitude) for {name}: ")) - upf_coords[name] = {"x": x, "y": y} - - # PSA-UPF - x = float(input("Enter x (latitude) for psa-upf: ")) - y = float(input("Enter y (longitude) for psa-upf: ")) - upf_coords["psa-upf"] = {"x": x, "y": y} - - print("\nApplying distance-based bandwidth limits using tc...") - #apply_distance(upf_coords) - #print("Bandwidth shaping complete.") - -def handle_ue_generation(args): - if args.ue <= 0: - print("Number of UEs must be greater than 0.") - exit(1) + return 1 - generate_ue_configs(args.ue) - - try: - client = docker.from_env() - ueransim_container = next( - (c for c in client.containers.list(all=True) if 'ueransim' in c.name.lower()), None) - - if ueransim_container: - print(f"Redémarrage du conteneur {ueransim_container.name}...") - ueransim_container.restart() - print("UERANSIM redémarré avec succès.") - time.sleep(5) # Give it time to fully restart - connect_ues() # <-- Launch UEs inside the container - else: - print("Conteneur UERANSIM introuvable.") - except docker.errors.DockerException as e: - print(f"Erreur lors du redémarrage de UERANSIM : {e}") + print(f"Configuration files generated:") + print(f"- {args.num_upfs - 1} intermediate UPF configs") + print(f"- 1 PSA UPF config") + print(f"- SMF config with UPF topology") + print(f"- UE routing config") + print(f"- Docker Compose file: ./docker-compose-custom.yaml") - print(f"Generated {args.ue} UE configuration files") + return 0 if __name__ == "__main__": - - main() - + exit(main()) \ No newline at end of file diff --git a/final/utils/__pycache__/distance.cpython-312.pyc b/final/utils/__pycache__/distance.cpython-312.pyc index a61ba18..b1c38fc 100644 Binary files a/final/utils/__pycache__/distance.cpython-312.pyc and b/final/utils/__pycache__/distance.cpython-312.pyc differ diff --git a/final/utils/insert.py b/final/utils/insert.py new file mode 100644 index 0000000..fbc3583 --- /dev/null +++ b/final/utils/insert.py @@ -0,0 +1,261 @@ +import requests +import sys + +BASE_URL = "http://127.0.0.1:5000" + +def login(username, password): + url = f"{BASE_URL}/api/login" + resp = requests.post(url, json={"username": username, "password": password}) + if resp.status_code == 200: + data = resp.json() + token = data.get("access_token") + if not token: + print("Login succeeded but no token found:", data) + return None + return token + else: + print(f"Login failed: {resp.status_code}, {resp.text}") + return None + +BASE_URL = "http://127.0.0.1:5000" + +def insert_ue(number): + ue_id = f"imsi-20893000000000{number}" + plmn = "20893" + url = f"{BASE_URL}/api/subscriber/{ue_id}/{plmn}" + + # Fixed token from the curl command + token = login("admin","free5gc") + + payload = { + "userNumber": 1, + "ueId": ue_id, + "plmnID": plmn, + "AuthenticationSubscription": { + "authenticationMethod": "5G_AKA", + "permanentKey": { + "permanentKeyValue": "8baf473f2f8fd09487cccbd7097c6862", + "encryptionKey": 0, + "encryptionAlgorithm": 0 + }, + "sequenceNumber": "000000000023", + "authenticationManagementField": "8000", + "milenage": { + "op": { + "opValue": "", + "encryptionKey": 0, + "encryptionAlgorithm": 0 + } + }, + "opc": { + "opcValue": "8e27b6af0e692e750f32667a3b14605d", + "encryptionKey": 0, + "encryptionAlgorithm": 0 + } + }, + "AccessAndMobilitySubscriptionData": { + "gpsis": ["msisdn-"], + "subscribedUeAmbr": { + "uplink": "1 Gbps", + "downlink": "2 Gbps" + }, + "nssai": { + "defaultSingleNssais": [{"sst": 1, "sd": "010203"}], + "singleNssais": [{"sst": 1, "sd": "112233"}] + } + }, + "SessionManagementSubscriptionData": [ + { + "singleNssai": {"sst": 1, "sd": "010203"}, + "dnnConfigurations": { + "internet": { + "pduSessionTypes": { + "defaultSessionType": "IPV4", + "allowedSessionTypes": ["IPV4"] + }, + "sscModes": { + "defaultSscMode": "SSC_MODE_1", + "allowedSscModes": ["SSC_MODE_2", "SSC_MODE_3"] + }, + "5gQosProfile": { + "5qi": 9, + "arp": { + "priorityLevel": 8, + "preemptCap": "", + "preemptVuln": "" + }, + "priorityLevel": 8 + }, + "sessionAmbr": { + "uplink": "1000 Mbps", + "downlink": "1000 Mbps" + }, + "staticIpAddress": [] + } + } + }, + { + "singleNssai": {"sst": 1, "sd": "112233"}, + "dnnConfigurations": { + "internet": { + "pduSessionTypes": { + "defaultSessionType": "IPV4", + "allowedSessionTypes": ["IPV4"] + }, + "sscModes": { + "defaultSscMode": "SSC_MODE_1", + "allowedSscModes": ["SSC_MODE_2", "SSC_MODE_3"] + }, + "5gQosProfile": { + "5qi": 8, + "arp": { + "priorityLevel": 8, + "preemptCap": "", + "preemptVuln": "" + }, + "priorityLevel": 8 + }, + "sessionAmbr": { + "uplink": "1000 Mbps", + "downlink": "1000 Mbps" + }, + "staticIpAddress": [] + } + } + } + ], + "SmfSelectionSubscriptionData": { + "subscribedSnssaiInfos": { + "01010203": { + "dnnInfos": [{"dnn": "internet"}] + }, + "01112233": { + "dnnInfos": [{"dnn": "internet"}] + } + } + }, + "AmPolicyData": { + "subscCats": ["free5gc"] + }, + "SmPolicyData": { + "smPolicySnssaiData": { + "01010203": { + "snssai": {"sst": 1, "sd": "010203"}, + "smPolicyDnnData": { + "internet": {"dnn": "internet"} + } + }, + "01112233": { + "snssai": {"sst": 1, "sd": "112233"}, + "smPolicyDnnData": { + "internet": {"dnn": "internet"} + } + } + } + }, + "FlowRules": [ + { + "filter": "1.1.1.1/32", + "precedence": 128, + "snssai": "01010203", + "dnn": "internet", + "qosRef": 1 + }, + { + "filter": "1.1.1.1/32", + "precedence": 127, + "snssai": "01112233", + "dnn": "internet", + "qosRef": 2 + } + ], + "QosFlows": [ + { + "snssai": "01010203", + "dnn": "internet", + "qosRef": 1, + "5qi": 8, + "mbrUL": "208 Mbps", + "mbrDL": "208 Mbps", + "gbrUL": "108 Mbps", + "gbrDL": "108 Mbps" + }, + { + "snssai": "01112233", + "dnn": "internet", + "qosRef": 2, + "5qi": 7, + "mbrUL": "407 Mbps", + "mbrDL": "407 Mbps", + "gbrUL": "207 Mbps", + "gbrDL": "207 Mbps" + } + ], + "ChargingDatas": [ + { + "chargingMethod": "Offline", + "quota": "100000", + "unitCost": "1", + "snssai": "01010203", + "dnn": "", + "filter": "" + }, + { + "chargingMethod": "Offline", + "quota": "100000", + "unitCost": "1", + "snssai": "01010203", + "dnn": "internet", + "filter": "1.1.1.1/32", + "qosRef": 1 + }, + { + "chargingMethod": "Online", + "quota": "100000", + "unitCost": "1", + "snssai": "01112233", + "dnn": "", + "filter": "" + }, + { + "chargingMethod": "Online", + "quota": "5000", + "unitCost": "1", + "snssai": "01112233", + "dnn": "internet", + "filter": "1.1.1.1/32", + "qosRef": 2 + } + ] + } + + headers = { + "Token": token, + "Content-Type": "application/json", + "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0", + "Accept": "application/json, text/plain, */*", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate, br, zstd", + "Origin": "http://127.0.0.1:5000", + "Connection": "keep-alive", + "Referer": "http://127.0.0.1:5000/subscriber/create", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "Priority": "u=0" + } + + response = requests.post(url, json=payload, headers=headers) + print(f"Inserted UE {ue_id}: Status Code {response.status_code}") + if response.status_code != 200: + print("Response:", response.text) + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python script.py ") + print("Where X is the last digit of the IMSI (imsi-20893000000000X)") + sys.exit(1) + + number = sys.argv[1] + insert_ue(number) + diff --git a/final/utils/upf_path_updater.py b/final/utils/upf_path_updater.py new file mode 100644 index 0000000..f9185ec --- /dev/null +++ b/final/utils/upf_path_updater.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +""" +Free5GC UPF Path Updater +------------------------ +This script updates existing Free5GC configuration files to modify the UPF path +according to a specified list of UPF names. + +It updates: +1. SMF configuration - links section +2. UE routing configuration - topology and specificPath sections +""" + +import yaml +import os +import sys +from pathlib import Path + + +def update_upf_path(upf_path_list, smf_config_path, uerouting_config_path): + """ + Update SMF and UE routing configurations with a new UPF path. + + Parameters: + - upf_path_list: List of UPF names in order (e.g., ["I-UPF", "I-UPF3", "I-UPF4", "PSA-UPF"]) + - smf_config_path: Path to SMF configuration file + - uerouting_config_path: Path to UE routing configuration file + """ + # Validate input + if not upf_path_list or len(upf_path_list) < 2: + print("Error: UPF path list must contain at least two UPFs") + return False + + if "psa" not in upf_path_list: + print("Warning: UPF path should typically include PSA-UPF") + + # Load existing configurations + try: + with open(smf_config_path, 'r') as file: + smf_config = yaml.safe_load(file) + + with open(uerouting_config_path, 'r') as file: + uerouting_config = yaml.safe_load(file) + except FileNotFoundError as e: + print(f"Error: Configuration file not found - {e}") + return False + except yaml.YAMLError as e: + print(f"Error: Invalid YAML in configuration file - {e}") + return False + + # Update SMF links section + # First, find 'gNB1' in node list to include it in the path + gnb_found = "gNB1" in smf_config["configuration"]["userplaneInformation"]["upNodes"] + + # Create new links array + new_links = [] + + # Add link from gNB to first UPF if gNB exists + if gnb_found: + new_links.append({ + "A": "gNB1", + "B": upf_path_list[0] + }) + + # Create links between UPFs in the specified order + for i in range(len(upf_path_list) - 1): + new_links.append({ + "A": upf_path_list[i], + "B": upf_path_list[i + 1] + }) + + # Update SMF links + smf_config["configuration"]["userplaneInformation"]["links"] = new_links + + # Update UE routing topology and specificPath for all UEs + for ue_key, ue_info in uerouting_config["ueRoutingInfo"].items(): + # Create new topology based on UPF path + new_topology = [] + + # Add gNB to first UPF link if applicable + if gnb_found: + new_topology.append({ + "A": "gNB1", + "B": upf_path_list[0] + }) + + # Add UPF to UPF links + for i in range(len(upf_path_list) - 1): + new_topology.append({ + "A": upf_path_list[i], + "B": upf_path_list[i + 1] + }) + + # Update topology for this UE + ue_info["topology"] = new_topology + + # Update specific paths for this UE if they exist + if "specificPath" in ue_info: + for path_entry in ue_info["specificPath"]: + path_entry["path"] = upf_path_list + + # Save updated configurations + try: + with open(smf_config_path, 'w') as file: + yaml.dump(smf_config, file, default_flow_style=False) + + with open(uerouting_config_path, 'w') as file: + yaml.dump(uerouting_config, file, default_flow_style=False) + + print(f"Successfully updated UPF path to: {' -> '.join(upf_path_list)}") + if gnb_found: + print(f"Path including gNB: gNB1 -> {' -> '.join(upf_path_list)}") + return True + + except Exception as e: + print(f"Error saving configuration files: {e}") + return False + + +