From ce24aa0d717e8f1c4834290e32f948be03314a1f Mon Sep 17 00:00:00 2001 From: Rolando Islas Date: Wed, 15 Mar 2017 05:21:45 -0700 Subject: [PATCH] Route unhandled errors to logger (+3 squashed commits) Add GUI - only has run server option - removed drc-sim-helper.py Added get_key GUI Update setup.py --- drc-sim-backend.py | 74 ++- drc-sim-helper.py | 689 -------------------- resources/image/clover.gif | Bin 0 -> 1871 bytes resources/image/diamond.gif | Bin 0 -> 1541 bytes resources/image/heart.gif | Bin 0 -> 1668 bytes resources/image/spade.gif | Bin 0 -> 1708 bytes setup.py | 12 +- src/server/control/gamepad.py | 89 ++- src/server/data/args.py | 2 + src/server/data/config_server.py | 8 + src/server/data/constants.py | 8 +- src/server/data/resource.py | 16 + src/server/net/sockets.py | 28 + src/server/ui/__init__.py | 0 src/server/ui/cli/__init__.py | 0 src/server/ui/cli/cli_main.py | 9 + src/server/ui/gui/__init__.py | 0 src/server/ui/gui/frame/__init__.py | 0 src/server/ui/gui/frame/frame_get_key.py | 165 +++++ src/server/ui/gui/frame/frame_run_server.py | 155 +++++ src/server/ui/gui/frame/frame_tab.py | 12 + src/server/ui/gui/gui_main.py | 68 ++ src/server/util/interface_util.py | 85 +++ src/server/util/logging/logger.py | 66 +- src/server/util/logging/logger_backend.py | 1 + src/server/util/logging/logger_cli.py | 9 + src/server/util/logging/logger_gui.py | 9 + src/server/util/logging/logger_wpa.py | 9 + src/server/util/os_util.py | 31 + src/server/util/process_util.py | 47 ++ src/server/util/wpa_supplicant.py | 261 ++++++++ 31 files changed, 1095 insertions(+), 758 deletions(-) delete mode 100644 drc-sim-helper.py create mode 100644 resources/image/clover.gif create mode 100644 resources/image/diamond.gif create mode 100644 resources/image/heart.gif create mode 100644 resources/image/spade.gif create mode 100644 src/server/data/resource.py create mode 100644 src/server/ui/__init__.py create mode 100644 src/server/ui/cli/__init__.py create mode 100644 src/server/ui/cli/cli_main.py create mode 100644 src/server/ui/gui/__init__.py create mode 100644 src/server/ui/gui/frame/__init__.py create mode 100644 src/server/ui/gui/frame/frame_get_key.py create mode 100644 src/server/ui/gui/frame/frame_run_server.py create mode 100644 src/server/ui/gui/frame/frame_tab.py create mode 100644 src/server/ui/gui/gui_main.py create mode 100644 src/server/util/interface_util.py create mode 100644 src/server/util/logging/logger_cli.py create mode 100644 src/server/util/logging/logger_gui.py create mode 100644 src/server/util/logging/logger_wpa.py create mode 100644 src/server/util/os_util.py create mode 100644 src/server/util/process_util.py create mode 100644 src/server/util/wpa_supplicant.py diff --git a/drc-sim-backend.py b/drc-sim-backend.py index aeeb102..8bf7e39 100644 --- a/drc-sim-backend.py +++ b/drc-sim-backend.py @@ -1,12 +1,72 @@ -import sys +from src.server.data import constants +from src.server.data.config_server import ConfigServer +from src.server.util.logging.logger_wpa import LoggerWpa +from src.server.data.args import Args +from src.server.ui.cli.cli_main import CliMain +from src.server.ui.gui.gui_main import GuiMain +from src.server.util.logging.logger import Logger +from src.server.util.logging.logger_backend import LoggerBackend +from src.server.util.logging.logger_cli import LoggerCli +from src.server.util.logging.logger_gui import LoggerGui +from src.server.util.os_util import OsUtil -from src.server.control.gamepad import Gamepad -gamepad = Gamepad() -while True: +def init_loggers(): + loggers = (Logger, LoggerBackend, LoggerGui, LoggerCli, LoggerWpa) + for logger in loggers: + if Args.args.debug: + logger.set_level(Logger.DEBUG) + elif Args.args.extra: + logger.set_level(Logger.EXTRA) + elif Args.args.finer: + logger.set_level(Logger.FINER) + elif Args.args.verbose: + logger.set_level(Logger.VERBOSE) + else: + logger.set_level(Logger.INFO) + + +def start(): + ui = None try: - gamepad.update() + if Args.args.cli: + Logger.info("Enabling CLI") + ui = CliMain() + else: + Logger.info("Enabling GUI") + ui = GuiMain() + ui.start() except KeyboardInterrupt: - gamepad.close() - sys.exit() + if ui: + ui.stop() + except Exception, e: + if ui: + ui.stop() + Logger.throw(e) + Logger.info("Exiting") + + +def log_level(): + # Logger info + Logger.debug("Debug logging enabled") + Logger.extra("Extra debug logging enabled") + Logger.finer("Finer debug logging enabled") + Logger.verbose("Verbose logging enabled") + if LoggerWpa.get_level() <= Logger.FINER: + LoggerWpa.warn("At this log level SSIDs are logged!") + + +def main(): + Args.parse_args() + ConfigServer.load() + ConfigServer.save() + init_loggers() + Logger.info("Initializing drc-sim-backend") + Logger.info("Using \"%s\" as home folder.", constants.PATH_ROOT) + log_level() + OsUtil.log_info(Logger) + start() + +if __name__ == '__main__': + main() diff --git a/drc-sim-helper.py b/drc-sim-helper.py deleted file mode 100644 index ed11278..0000000 --- a/drc-sim-helper.py +++ /dev/null @@ -1,689 +0,0 @@ -# coding=utf-8 -import argparse -import curses -import curses.textpad -import locale -import multiprocessing -import os -import re -import shutil -import subprocess -import sys -import time -import traceback -from distutils import spawn - -import netifaces as netifaces -import pkg_resources - - -class DrcSimHelper: - def __init__(self): - if os.getuid() != 0: - raise SystemExit("This script needs to be run as root.") - self.height = None - self.args = None - self.active_command = None - self.textbox = None - self.window_main = None - self.input_thread = None - self.input_queue = multiprocessing.Queue() - self.title = "drc-sim-helper" - self.log_path = os.path.expanduser("~/.drc-sim/") - locale.setlocale(locale.LC_ALL, "") - self.parse_args() - curses.wrapper(self.start) - - def parse_args(self): - arg_parser = argparse.ArgumentParser(description="Helper script for connecting to the Wii U.", - prefix_chars="- ") - arg_parser.add_argument("--all-interfaces", action="store_const", - const=True, default=False, help="show all interfaces instead of only the Wii U " - "compatible") - arg_parser.add_argument("--log", action="store_const", const=True, default=False, - help="log output of wpa_supplicant_drc and the backend") - if "run_server" in sys.argv: - subparsers = arg_parser.add_subparsers() - # run_server - run_server = subparsers.add_parser("run_server") - run_server.add_argument("wiiu_interface", type=str) - run_server.add_argument("normal_interface", type=str) - # add to input queue to trigger command parsing - self.input_queue.put("") - else: - arg_parser.add_argument(" run_server", dest="...", help="connects to the wii u and runs drc-sim-backend (" - "needs auth details)") - self.args = arg_parser.parse_args() - self.args.run_server = "run_server" in sys.argv - - def start(self, screen): - height, width = screen.getmaxyx() - self.height = height - # title - screen.addstr(0, width / 2 - len(self.title) / 2, self.title) - # command prompt symbol - screen.addstr(height - 1, 0, "$ ") - screen.refresh() - # main display - self.window_main = curses.newwin(height - 2, width, 1, 0) - # input - window_input = curses.newwin(1, width - 2, height - 1, 2) - self.textbox = curses.textpad.Textbox(window_input) - - self.input_thread = InputThread(self.input_queue, self.textbox) - self.input_thread.start() - - self.set_command(CommandHelp) - try: - while True: - self.parse_queue() - self.active_command.update() - time.sleep(0.01) - except Exception as e: - self.stop(e) - - def parse_queue(self): - if self.input_queue.empty(): - return - input_text = self.input_queue.get(1).strip() - self.input_queue.empty() - # global - if input_text == "quit" or input_text == "exit": - self.stop() - # active command - new_command = self.active_command.parse_command(input_text) - if new_command: - self.set_command(new_command) - - def stop(self, message=None): - self.active_command.stop() - self.input_thread.terminate() - if isinstance(message, Exception): - type_, value_, traceback_ = sys.exc_info() - raise SystemExit("".join(traceback.format_tb(traceback_)) + type_.__name__ + "\n" + str(value_)) - else: - raise SystemExit(message) - - def set_command(self, command): - if self.active_command: - self.active_command.stop() - self.active_command = command(self, self.window_main, self.textbox) - - -class InputThread(multiprocessing.Process): - def __init__(self, queue, textbox): - super(InputThread, self).__init__() - self.queue = queue - self.textbox = textbox - - def run(self): - while True: - try: - text = self.textbox.edit() - except KeyboardInterrupt: - break - self.queue.put(text) - self.textbox.win.erase() - - -class Command: - def __init__(self, parent, window_main, textbox): - self.parent = parent - self.window_main = window_main - self.textbox = textbox - self.show_main() - - def parse_command(self, command): - pass - - def show_main(self): - pass - - def stop(self): - pass - - def update(self): - pass - - def clear(self): - self.window_main.clear() - self.window_main.refresh() - self.textbox.win.refresh() - - def write(self, y, x, text): - self.window_main.addstr(y, x, text) - self.window_main.refresh() - self.textbox.win.refresh() - - def prompt_user_input_choice(self, choices, prompt): - self.clear() - for y in range(0, len(prompt)): - self.write(y, 0, prompt[y]) - for y in range(0, len(choices)): - self.write(y + len(prompt) + 1, 0, str(y + 1) + ") " + str(choices[y])) - - def prompt_user_input(self, prompt): - self.clear() - for y in range(0, len(prompt)): - self.write(y, 0, prompt[y]) - - -class CommandHelp(Command): - def parse_command(self, command): - if command == "help": - self.parent.set_command(CommandHelp) - elif command == "get_key": - self.parent.set_command(CommandGetKey) - elif command == "run_server" or self.parent.args.run_server: - self.parent.set_command(CommandRunServer) - elif command == "route": - self.parent.set_command(CommandRoute) - - def show_main(self): - self.clear() - self.write(0, 0, "Help") - self.write(2, 0, "Commands:") - - self.write(3, 0, "help show this help screen") - self.write(4, 0, "quit exit the program") - self.write(5, 0, "get_key obtain the ssid, bssid, and psk from the wii u") - self.write(6, 0, "run_server connects to the wii u and runs drc-sim-backend (needs auth details)") - self.write(7, 0, "route adds network routes (dev)") - - -class NetworkCommand(Command): - def __init__(self, parent, window_main, textbox, forward_method): - Command.__init__(self, parent, window_main, textbox) - self.forward_method = forward_method - # process - self.wpa_supplicant_process = None - # input bool - self.requesting_interface_wii_input = False - self.requesting_networkmanager_unmanage_input = False - self.requesting_interface_normal_input = False - # interfaces - self.interfaces_wiiu = None - self.interface_wiiu = None - self.interfaces_normal = None - self.interface_normal = None - # static - self.nm_conf = "/etc/NetworkManager/NetworkManager.conf" - self.tmp_conf_psk = "/tmp/drc-sim/get_psk.conf" - self.conf_psk = os.path.expanduser("~/.drc-sim/connect_to_wii_u.conf") - # start - self.prompt_wiiu_interface() - - def parse_command(self, command): - # check which wiiu interface should be used - if self.requesting_interface_wii_input: - try: - index = int(command) - if 1 <= index <= len(self.interfaces_wiiu): - self.requesting_interface_wii_input = False - self.interface_wiiu = self.interfaces_wiiu[index - 1] - self.prompt_networkmanager_unmanage() - except ValueError: - pass - # check which normal interface should be used - elif self.requesting_interface_normal_input: - try: - index = int(command) - if 1 <= index <= len(self.interfaces_normal): - self.requesting_interface_normal_input = False - self.interface_normal = self.interfaces_normal[index - 1] - self.forward_method() - except ValueError: - pass - # check if interface should be set to unmanaged - elif self.requesting_networkmanager_unmanage_input: - try: - index = int(command) - if index == 1: # yes - self.requesting_networkmanager_unmanage_input = False - self.set_interface_unmanaged() - self.forward_method() - elif index == 2: # no - self.requesting_networkmanager_unmanage_input = False - self.forward_method() - except ValueError: - pass - - def prompt_wiiu_interface(self): - # Get interfaces - self.interfaces_wiiu = InterfaceUtil.get_wiiu_compatible_interfaces() if not self.parent.args.all_interfaces \ - else InterfaceUtil.get_all_interfaces() - if len(self.interfaces_wiiu) == 0 and not self.parent.args.run_server: - self.parent.stop("No Wii U compatible wireless interfaces found. Add --all-interfaces to show all " - "interfaces.") - # Check if arg passed in cli - if hasattr(self.parent.args, "wiiu_interface"): - for interface in InterfaceUtil.get_all_interfaces(): - if interface[0] == self.parent.args.wiiu_interface: - self.interface_wiiu = interface - self.forward_method() - return - # Prompt - self.prompt_user_input_choice(self.interfaces_wiiu, - ["Select a wireless interface that will be used for connections to a Wii U."]) - self.requesting_interface_wii_input = True - - def prompt_normal_interface(self): - # Get interfaces - self.interfaces_normal = InterfaceUtil.get_all_interfaces() - if self.interface_wiiu in self.interfaces_normal: - self.interfaces_normal.remove(self.interface_wiiu) - if len(self.interfaces_normal) == 0: - self.parent.stop("No interfaces found.") - # Check cli arg - if hasattr(self.parent.args, "normal_interface"): - for interface in self.interfaces_normal: - if interface[0] == self.parent.args.normal_interface: - self.interface_normal = interface - self.forward_method() - return - # Prompt - self.prompt_user_input_choice(self.interfaces_normal, - ["Select an interface that will be used for a standard network " - "connection."]) - self.requesting_interface_normal_input = True - - def prompt_networkmanager_unmanage(self): - # do not prompt user if they do not have network manager - if not os.path.isfile(self.nm_conf): - self.requesting_networkmanager_unmanage_input = False - self.forward_method() - return - # do not prompt if device is already unmanaged - conf = open(self.nm_conf) - conf_data = conf.read() - conf.close() - if "mac:" + self.interface_wiiu[1] in conf_data: - self.requesting_networkmanager_unmanage_input = False - self.forward_method() - return - # prompt - self.prompt_user_input_choice(["yes", "no"], ["Network Manager configuration found.", - "Would you like to set the interface '" + self.interface_wiiu[0] + - "' to unmanaged in network-manager's configuration file?", - "(You should only do this if the interface is not your primary " - "networking interface.)"]) - self.requesting_networkmanager_unmanage_input = True - - def set_interface_unmanaged(self): - conf = open(self.nm_conf, "a") - conf.writelines(["[keyfile]", "\nunmanaged-devices=mac:" + self.interface_wiiu[1]]) - conf.close() - subprocess.call(["service", "network-manager", "restart"], stdout=open(os.devnull, "w"), - stderr=subprocess.STDOUT) - - def start_wpa_supplicant(self, conf): - subprocess.call(["rfkill", "unblock", "wlan"], stdout=open(os.devnull, "w"), stderr=subprocess.STDOUT) - log = open(os.path.join(self.parent.log_path, "wpa_supplicant_drc.log"), "w") if self.parent.args.log else \ - open(os.devnull, "w") - log.write("-" * 80 + "\nStarted wpa_supplicant_drc\n") - self.wpa_supplicant_process = subprocess.Popen(["wpa_supplicant_drc", "-Dnl80211", "-i", - self.interface_wiiu[0], "-c", conf], - stdout=log, stderr=subprocess.STDOUT) - - def stop(self): - if self.wpa_supplicant_process and not self.wpa_supplicant_process.poll(): - self.wpa_supplicant_process.terminate() - self.kill_wpa() - - @staticmethod - def kill_wpa(): - subprocess.call(["killall", "wpa_supplicant_drc"], stdout=open(os.devnull), stderr=subprocess.STDOUT) - - @staticmethod - def wpa_cli(command): - if isinstance(command, str): - command = [command] - try: - process = subprocess.check_output(["wpa_cli_drc", "-p", "/var/run/wpa_supplicant_drc"] + command, - stderr=subprocess.STDOUT) - return process - except subprocess.CalledProcessError: - return "" - - -class CommandRunServer(NetworkCommand): - def __init__(self, parent, window_main, textbox): - # process - self.drc_sim_backend_queue = multiprocessing.Queue() - self.status_output_time = 0 - self.dead_process_time = None - self.drc_sim_backend_process = None - # network details - self.ip = None - self.subnet = None - self.gateway = None - - NetworkCommand.__init__(self, parent, window_main, textbox, self.check_conf) - - def parse_command(self, command): - NetworkCommand.parse_command(self, command) - if command == "pypy": - sys.executable = "pypy" - self.drc_sim_backend_process.terminate() - self.start_processes(True) - - def stop(self): - NetworkCommand.stop(self) - if self.drc_sim_backend_process and not self.drc_sim_backend_process.poll(): - self.drc_sim_backend_process.terminate() - - def update(self): - # restart a terminated process - if self.dead_process_time: - # show status - is_alive_wpa = self.wpa_supplicant_process.poll() is None - is_alive_drc = self.drc_sim_backend_process.poll() is None - if time.time() - self.status_output_time >= 1: - self.status_output_time = time.time() - self.window_main.addstr(0, 0, "Server status") - wpa_status = self.wpa_cli("status") - if "wpa_state=COMPLETED" in wpa_status: - wpa_status = "connected" - elif "wpa_state=SCANNING" in wpa_status: - wpa_status = "connecting" - else: - wpa_status = "unknown" - self.window_main.addstr(2, 0, "Wii U Connection: " + - (wpa_status if is_alive_wpa else "stopped") + " " * 20) - self.window_main.addstr(3, 0, "Server: " + - ("running" if is_alive_drc else "stopped") + " " * 20) - self.window_main.addstr(5, 0, "Interface Wii U: " + self.interface_wiiu[0] + ", " + - self.interface_wiiu[1]) - self.window_main.addstr(6, 0, "Interface Normal: " + self.interface_normal[0] + ", " + - self.interface_normal[1] + ", " + self.ip) - self.window_main.addstr(8, 0, "Logging: " + str(self.parent.args.log)) - if self.parent.args.log: - self.window_main.addstr(9, 0, "Log Path: " + self.parent.log_path) - self.window_main.addstr(self.parent.height - 3, 0, "> ") - self.window_main.refresh() - # update time if running - if is_alive_wpa and is_alive_drc: - self.dead_process_time = time.time() - # restart if process has been dead for a while - if time.time() - self.dead_process_time >= 10: - self.start_processes(True) - - def start_processes(self, restart=False): - if not restart: - self.stop() - self.dead_process_time = time.time() - self.clear() - if self.wpa_supplicant_process is None or self.wpa_supplicant_process.poll(): - self.start_wpa_supplicant(self.conf_psk) - self.add_route() - if self.drc_sim_backend_process is None or self.drc_sim_backend_process.poll(): - self.start_drc_sim_backend() - - def start_drc_sim_backend(self): - drc_sim_path = os.path.join(os.path.dirname(__file__), "drc-sim-backend.py") - if not os.path.exists(drc_sim_path): - drc_sim_path = os.path.abspath(spawn.find_executable("drc-sim-backend.py")) - log = open(os.path.join(self.parent.log_path, "drc-sim-backend.log"), "w") if self.parent.args.log else \ - open(os.devnull, "w") - log.write("-" * 80 + "\nStarted drc-sim-backend\n") - command = [sys.executable, drc_sim_path] - if self.parent.args.log: - command.append("--debug") - self.drc_sim_backend_process = subprocess.Popen(command, stdout=log, stderr=subprocess.STDOUT) - - def add_route(self): - # Setup dhcp - try: - subprocess.check_call(["dhclient"], stdout=open(os.devnull), stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - self.parent.stop("Failed setup dhcp.") - # Prioritize normal interface - try: - subprocess.check_call(["ifmetric", self.interface_normal[0], "0"], stdout=open(os.devnull), - stderr=subprocess.STDOUT) - subprocess.check_call(["ifmetric", self.interface_wiiu[0], "1"], stdout=open(os.devnull), - stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - self.parent.stop("Failed to prioritize normal network interface.") - - def check_conf(self): - # User needs auth details first - if not os.path.isfile(self.conf_psk): - self.parent.stop("No Wii U wireless authentication found at $dir. Try running \"get_key\"." - .replace("$dir", str(self.conf_psk))) - # Prompt for normal NIC - if not self.interface_normal: - self.prompt_normal_interface() - return - # Calculate network details - self.calulate_network_details() - # start - self.start_processes() - - def calulate_network_details(self): - # Get interface ip - self.ip = InterfaceUtil.get_ip(self.interface_normal[0]) - # Check if there is an ip - if not self.ip: - self.parent.stop("Selected normal interface is not connected to a network.") - # Subnet and gateway - ip_split = self.ip.split(".") - ip_prefix = ".".join([ip_split[0], ip_split[1], ip_split[2]]) - self.subnet = ip_prefix + ".0/24" - self.gateway = ip_prefix + ".1" - - -class CommandRoute(CommandRunServer): - def stop(self): - pass - - def start_processes(self, restart=False): - self.add_route() - self.parent.stop("Added route") - - -class CommandGetKey(NetworkCommand): - def __init__(self, parent, window_main, textbox): - NetworkCommand.__init__(self, parent, window_main, textbox, self.prompt_wps_pin) - # wpa - self.wpa_supplicant_process = None - self.wps_pin = None - self.bssid = None - self.bssids = [] - # input bool - self.requesting_wps_pin_input = False - self.requesting_recan_input = False - self.requesting_newpin_input = False - - def parse_command(self, command): - NetworkCommand.parse_command(self, command) - # get the wps pin from user - if self.requesting_wps_pin_input: - try: - if len(command) == 4: - for char in command: - if int(char) < 0 or int(char) > 3: - return - self.requesting_wps_pin_input = False - self.wps_pin = command - self.get_key() - except ValueError: - pass - # check if another scan should start - elif self.requesting_recan_input: - try: - index = int(command) - if index == 1: # yes - self.get_key() - elif index == 2: # no - self.parent.stop("Failed to get Wii U authentication.") - except ValueError: - pass - # check if user wants to enter new pin - elif self.requesting_newpin_input: - try: - index = int(command) - if index == 1: # newpin - self.prompt_wps_pin() - elif index == 2: # exit - self.parent.stop("Failed to get Wii U authentication.") - except ValueError: - pass - - def prompt_wps_pin(self): - self.prompt_user_input(["Input the Wii U's WPS key.", - "", - "Start gamepad sync mode on the Wii U.", - "The screen will show a spade/heart/diamond/clover in a certain order.", - "", - "Enter the key as a four numbers.", - "", - u"♠ (spade) = 0 ♥ (heart) = 1 ♦ (diamond) = 2 ♣ (clover) = 3" - .encode("utf-8"), - "", - u"Example: ♣♠♥♦ (clover, spade, heart, diamond) would equal 3012".encode("utf-8")]) - self.requesting_wps_pin_input = True - - def get_key(self): - # Create temp config - self.prompt_user_input(["Attempting to pair with the Wii U.", "This might take a few minutes."]) - tmp_dir = "/tmp/drc-sim/" - conf_name = "get_psk.conf" - if not os.path.exists(tmp_dir): - os.mkdir(tmp_dir) - orig_conf = os.path.join(os.path.dirname(__file__), "resources/config/get_psk.conf") - if not os.path.exists(orig_conf): - orig_conf = pkg_resources.resource_string(pkg_resources.Requirement.parse("drcsim"), - "resources/config/get_psk.conf") - conf_tmp = open(self.tmp_conf_psk, "w") - conf_tmp.write(orig_conf) - conf_tmp.close() - else: - shutil.copyfile(orig_conf, tmp_dir + conf_name) - # Start wpa_supplicant - self.stop() - self.start_wpa_supplicant(tmp_dir + conf_name) - # Start scan - self.prompt_user_input(["Scanning WiFi networks."]) - tries = 0 - while "OK" not in self.wpa_cli("scan") and tries <= 10: - tries += 1 - time.sleep(1) - if tries > 10: - self.parent.stop("Could not start AP scan.") - # Parse scan results - self.prompt_user_input(["Sorting networks."]) - tries = 0 - wiiu_ap = re.compile('^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})(\s*\d*\s*-*\d*\s*)(\[WPA2-PSK-CCMP\])?' - '(\[ESS\])(\s*)(WiiU|\\\\x00)(.+)$') # \x00 is escaped (\\x00) - while tries <= 10: - for line in self.wpa_cli("scan_results").split("\n"): - if wiiu_ap.match(line): - self.bssids.append(line.split()[0]) - if len(self.bssids) > 0: - break - tries += 1 - time.sleep(1) - if tries > 10: - self.prompt_user_input_choice(["yes", "no"], - ["Wii U network not found. Scan again?", "", - "- Your Wii U could be too far away.", - "- Restart the Wii U and enter gampad pairing (sync) mode."]) - self.requesting_recan_input = True - return - # Try to authenticate - self.prompt_user_input(["Attempting to connect."]) - tries = 0 - auth = False - for bssid in self.bssids: - self.bssid = bssid - self.prompt_user_input(["Trying network " + str(self.bssids.index(bssid) + 1) + " of " + - str(len(self.bssids))]) - self.wpa_cli(["wps_pin", bssid, self.wps_pin + "5678"]) - while tries <= 60: - conf = open(tmp_dir + conf_name) - lines = conf.readlines() - conf.close() - for line in lines: - if "network={" in line: - auth = True - if auth: - break - tries += 1 - time.sleep(1) - if auth: - break - if tries > 60: - self.prompt_user_input_choice(["Re-enter WPS PIN", "exit"], - ["Could not connect to Wii U.", "", - "- WPS PIN could be incorrect.", - "- Restart the Wii U and enter gampad pairing (sync) mode."]) - self.requesting_newpin_input = True - return - # Save details - self.save_key() - - def save_key(self): - # Check the config path - if not os.path.exists(os.path.expanduser("~/.drc-sim")): - os.mkdir(os.path.expanduser("~/.drc-sim")) - # add additional connect information to config - conf = open(self.tmp_conf_psk, "r") - lines = conf.readlines() - conf.close() - for line in lines: - if "update_config=1" in line: - lines.insert(lines.index(line) + 1, "ap_scan=1\n") - break - for line in lines: - if "network={" in line: - lines.insert(lines.index(line) + 1, " " * 8 + "scan_ssid=1\n") - lines.insert(lines.index(line) + 2, " " * 8 + "bssid=" + self.bssid + "\n") - break - save_conf = open(self.conf_psk, "w") - save_conf.writelines(lines) - save_conf.close() - self.parent.stop("Successfully saved Wii U authentication data.") - - -class InterfaceUtil: - def __init__(self): - pass - - @classmethod - def get_wiiu_compatible_interfaces(cls): - all_interfaces = cls.get_all_interfaces() - compatible_interfaces = [] - for interface in all_interfaces: - if cls.is_interface_wiiu_compatible(interface[0]): - compatible_interfaces.append(interface) - return compatible_interfaces - - @classmethod - def get_all_interfaces(cls): - interfaces = [] - for interface in netifaces.interfaces(): - interfaces.append([interface, cls.get_mac(interface)]) - return interfaces - - @classmethod - def is_interface_wiiu_compatible(cls, interface): - try: - return "5." in subprocess.check_output(["iwlist", interface, "frequency"]) - except subprocess.CalledProcessError: - return False - - @classmethod - def get_ip(cls, interface): - return netifaces.ifaddresses(interface)[netifaces.AF_INET][0]["addr"] - - @classmethod - def get_mac(cls, interface): - return netifaces.ifaddresses(interface)[netifaces.AF_LINK][0]["addr"] - - -if __name__ == '__main__': - try: - helper = DrcSimHelper() - except KeyboardInterrupt: - pass diff --git a/resources/image/clover.gif b/resources/image/clover.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc2f2c28581963aa019a2766d7830ed350dd11c4 GIT binary patch literal 1871 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 XX+LC9EfBFOfBZITb4ddV0rvd^W72Jq literal 0 HcmV?d00001 diff --git a/resources/image/spade.gif b/resources/image/spade.gif new file mode 100644 index 0000000000000000000000000000000000000000..7bddcfee169001da5f8848003b38eebf45c24c5c GIT binary patch literal 1708 zcmdthi9gc|008jcW@9$T%&l#X=0)C13Q4KiW(S`#naH{ImXNDjIa1jghGa20%I7m9 zk!xNU2o)6-RaI3rH8pj0btDq0p`n37p|rKNb#!!eb#?Xh^z`-h z4Gav>Xfy_c!D6w-#>OTlCZ?vQJ9g|aH#fJiu&}hW+`W6Zm6g?=J$tOJt!->lq=?d{{^U%7JS>eZ`UE|zo#*Os!bfHj~k&%&^nJE&9va+(Wv$Jz^ zbMx}@^7He>VsSx1fkYxHEG#T4Dk?56E-5J~EiEl8D=RNAuc)Y~tgNi6s**~j)z#HC zH8r)hwRLrM_4V})4GoQrjSn9_Y;JCT`t+$xCX>tMEiEmrt*vcsZ5OT{Aw%ueg>C6Ze^kP0C|qWS*}jltc#FF(siy&SEX{o! z0l0+BhMGA8fO7psxYIN=-t90kQ*CHjZWfPr&VrwVGx7IIh$rY!A!P0-#Rr6=f2#j{ zT?!n%b)iEk%){l3+rZ*S9sKns)3RP0XC=#aCxsO-GqTT;;<*j7&EagPluF<`~8; z$r=aMBz#TZNm=x;>K)NP1|r$IKU~D0FlYMh=96p2wnX2aM1f4+gn)(Fp-k6PR&x&O zf{7?y(OR*BIQe!N?8+cRl+~&JbNxB=cr)5fe3ofe?}9?uQ^?0$Nz=YS1gtMNpYT&**`_p>e`TT#Q3J>4j+0QQAql%%u6VFkxe-qGIEUT5|g97c{s*22-sRQeE&6 zAA7|3Uc|XL#`F9J?ZumhI``XH9Bjq7J6CH6z^hM;dCG4Y2754#QUcGm^8C0s3fFa> z2L_ynY6x5*pN&vjlgA@0F@Q|La_c@$XH>;>QzHOY0qY$^fIU$1SVbl;Lt`=~G6i3v zcUO+j+!)B$H-FQ^yMW8tLpLEq=L_-a3?@cHC|Pn=eLjOkIELGK%;F2LGtsEBC;U{+ zN*gJ;3H+wh)3Jth78eL9G4910P;KMY3(+E;`+j|`WJFFfFhZ|?v{34!XT6|IsM!Z*vOWNgslSk2`11yM6m~Rcz$R|re-*uzNu@&h^Vm&u};Cz~{>CX5_Fj@VkCCTbyp1OEblk$tBC literal 0 HcmV?d00001 diff --git a/setup.py b/setup.py index d8eb34b..b77912b 100644 --- a/setup.py +++ b/setup.py @@ -7,9 +7,15 @@ setup(name='drcsim', version='1.1', description='Wii U gamepad simulator.', - install_requires=['construct<2.8', 'Pillow==3.4.2', 'cffi==1.9.1', 'netifaces==0.10.5'], + install_requires=['construct<2.8', 'Pillow==3.4.2', 'cffi==1.9.1', 'netifaces==0.10.5', 'pexpect==4.2.1'], packages=find_packages(), include_package_data=True, - data_files=[('resources/config', ['resources/config/get_psk.conf'])], - scripts=['drc-sim-backend.py', 'drc-sim-helper.py'] + data_files=[('resources/config', ['resources/config/get_psk.conf']), + ('resources/image', [ + 'resources/image/clover.gif', + 'resources/image/diamond.gif', + 'resources/image/heart.gif', + 'resources/image/spade.gif' + ])], + scripts=['drc-sim-backend.py'] ) diff --git a/src/server/control/gamepad.py b/src/server/control/gamepad.py index 23362d9..070f92a 100644 --- a/src/server/control/gamepad.py +++ b/src/server/control/gamepad.py @@ -1,11 +1,10 @@ -import logging import select import socket import time +from threading import Thread from src.server.control.server import Server from src.server.control.util.controller import Controller -from src.server.data.args import Args from src.server.data.config_server import ConfigServer from src.server.net import socket_handlers from src.server.net import sockets @@ -13,24 +12,34 @@ class Gamepad: + NO_PACKETS = "NO_PACKETS" + STOPPED = "STOPPED" + RUNNING = "RUNNING" + WAITING_FOR_PACKET = "WAITING_FOR_PACKET" + def __init__(self): - Args.parse_args() - self.set_logging_level() + self.backend_thread = None + self.status = self.STOPPED + self.status_change_listeners = [] + self.running = False + self.wii_packet_time = time.time() + self.has_received_packet = False + self.server = Server() + + def start(self): ConfigServer.load() ConfigServer.save() self.print_init() - self.server = Server() sockets.Sockets.connect() socket_handlers.SocketHandlers.create() - self.has_received_packet = False - self.wii_packet_time = time.time() + self.running = True + LoggerBackend.debug("Starting backend thread") + self.backend_thread = Thread(target=self.update) + self.backend_thread.start() + LoggerBackend.debug("Post backend thread") def print_init(self): LoggerBackend.info("Started drc-sim-backend") - LoggerBackend.debug("Debug logging enabled") - LoggerBackend.extra("Extra debug logging enabled") - LoggerBackend.finer("Finer debug logging enabled") - LoggerBackend.verbose("Verbose logging enabled") self.print_config() LoggerBackend.info("Waiting for Wii U packets") @@ -67,31 +76,44 @@ def handle_sockets(self): self.server.handle_client_command_packet(sock) def update(self): - self.check_last_packet_time() - self.handle_sockets() - Controller.update() + while self.running: + try: + self.check_last_packet_time() + self.handle_sockets() + Controller.update() + except Exception, e: + LoggerBackend.throw(e) - @staticmethod - def close(): - for s in socket_handlers.SocketHandlers.wii_handlers.itervalues(): - s.close() + def close(self): + if not self.running: + LoggerBackend.debug("Ignored stop request: already stopped") + return + LoggerBackend.debug("Stopping backend") + self.running = False + try: + self.backend_thread.join() + except RuntimeError, e: + LoggerBackend.exception(e) + LoggerBackend.debug("Closing handlers") + if socket_handlers.SocketHandlers.wii_handlers: + for s in socket_handlers.SocketHandlers.wii_handlers.itervalues(): + s.close() + LoggerBackend.debug("Closing sockets") + sockets.Sockets.close() + self.status_change_listeners = [] + LoggerBackend.debug("Backend closed") def check_last_packet_time(self): - if time.time() - self.wii_packet_time >= 60: - LoggerBackend.throw("No Wii U packets received in the last minute. Shutting down.") - - @staticmethod - def set_logging_level(): - if Args.args.debug: - LoggerBackend.set_level(logging.DEBUG) - elif Args.args.extra: - LoggerBackend.set_level(LoggerBackend.EXTRA) - elif Args.args.finer: - LoggerBackend.set_level(LoggerBackend.FINER) - elif Args.args.verbose: - LoggerBackend.set_level(LoggerBackend.VERBOSE) + if not self.has_received_packet: + status = self.WAITING_FOR_PACKET + elif time.time() - self.wii_packet_time >= 60: + status = Gamepad.NO_PACKETS else: - LoggerBackend.set_level(logging.INFO) + status = Gamepad.RUNNING + if self.status != status: + self.status = status + for listener in self.status_change_listeners: + listener(status) @staticmethod def print_config(): @@ -99,3 +121,6 @@ def print_config(): LoggerBackend.info("Config: Input Delay %f", ConfigServer.input_delay) LoggerBackend.info("Config: Image Quality %d", ConfigServer.quality) LoggerBackend.info("Config: Stream Audio %s", ConfigServer.stream_audio) + + def add_status_change_listener(self, callback): + self.status_change_listeners.append(callback) diff --git a/src/server/data/args.py b/src/server/data/args.py index e550e28..864f619 100644 --- a/src/server/data/args.py +++ b/src/server/data/args.py @@ -18,4 +18,6 @@ def parse_args(): help="finer debug output") arg_parser.add_argument("-v", "--verbose", action="store_const", const=True, default=False, help="verbose debug output") + arg_parser.add_argument("-c", "--cli", action="store_const", const=True, default=False, + help="disable gui") Args.args = arg_parser.parse_args() diff --git a/src/server/data/config_server.py b/src/server/data/config_server.py index 5ebf36b..83d7da2 100644 --- a/src/server/data/config_server.py +++ b/src/server/data/config_server.py @@ -2,6 +2,7 @@ class ConfigServer: + scan_timeout = None config = Config() stream_audio = None input_delay = None @@ -14,13 +15,20 @@ def __init__(self): @classmethod def load(cls): cls.config.load("~/.drc-sim/server.conf") + # Audio cls.stream_audio = cls.config.get_boolean("AUDIO", "stream", True, "Stream audio to clients") + # Input cls.input_delay = cls.config.get_float("INPUT", "delay", 0, 1, 0.1, "Amount of time to send input to Wii") + # Video cls.quality = cls.config.get_int("VIDEO", "quality", 1, 100, 75, "Quality of video stream. Sends uncompressed " "data at 100\n" "5/10/15 low - 75 lan - 100 loopback") cls.fps = cls.config.get_int("VIDEO", "fps", 1, 60, 30, "FPS of video stream. No limit if set to 60\n" "10 low - 30 lan - 60 loopback") + # General + cls.scan_timeout = cls.config.get_int("GENERAL", "scan_timeout", 0, 60 * 5, 60 * 2, "Sets the time they server " + "is allowed to scan for the" + "Wii U network") @classmethod def save(cls): diff --git a/src/server/data/constants.py b/src/server/data/constants.py index 3434140..fd5fbeb 100644 --- a/src/server/data/constants.py +++ b/src/server/data/constants.py @@ -27,4 +27,10 @@ COMMAND_PONG = "PONG" # Paths -LOG_PATH = os.path.expanduser("~/.drc-sim/log/") +PATH_ROOT = os.path.expanduser("~/.drc-sim/") +PATH_LOG_DIR = os.path.join(PATH_ROOT, "log/") +PATH_CONF_CONNECT = os.path.join(PATH_ROOT, "connect_to_wii_u.conf") +PATH_LOG_WPA = os.path.join(PATH_LOG_DIR, "wpa_supplicant_drc.log") +PATH_CONF_NETWORK_MANAGER = "/etc/NetworkManager/NetworkManager.conf" +PATH_TMP = "/tmp/drc-sim/" +PATH_CONF_CONNECT_TMP = os.path.join(PATH_TMP, "get_psk.conf") diff --git a/src/server/data/resource.py b/src/server/data/resource.py new file mode 100644 index 0000000..365fce9 --- /dev/null +++ b/src/server/data/resource.py @@ -0,0 +1,16 @@ +import os + +import pkg_resources + +from src.server.util.logging.logger import Logger + + +def join(*args): + return os.path.join(*args) + + +class Resource: + def __init__(self, in_path): + pre = "resources/" + Logger.debug("Loading resource \"%s\"", join(pre, in_path)) + self.resource = pkg_resources.resource_string(pkg_resources.Requirement.parse("drcsim"), join(pre, in_path)) diff --git a/src/server/net/sockets.py b/src/server/net/sockets.py index a99e884..eb47e28 100644 --- a/src/server/net/sockets.py +++ b/src/server/net/sockets.py @@ -60,6 +60,34 @@ def connect(self): self.SERVER_AUD_S = self.tcp_server(self.SERVER_IP, constants.PORT_SERVER_AUD) self.SERVER_CMD_S = self.udp_service(self.SERVER_IP, constants.PORT_SERVER_CMD) + def close(self): + LoggerBackend.debug("Closing sockets") + if self.WII_MSG_S: + # self.WII_MSG_S.shutdown(socket.SHUT_RDWR) + self.WII_MSG_S.close() + if self.WII_VID_S: + # self.WII_VID_S.shutdown(socket.SHUT_RDWR) + self.WII_VID_S.close() + if self.WII_AUD_S: + # self.WII_AUD_S.shutdown(socket.SHUT_RDWR) + self.WII_AUD_S.close() + if self.WII_CMD_S: + # self.WII_CMD_S.shutdown(socket.SHUT_RDWR) + self.WII_CMD_S.close() + if self.WII_HID_S: + # self.WII_HID_S.shutdown(socket.SHUT_RDWR) + self.WII_HID_S.close() + if self.SERVER_VID_S: + # self.SERVER_VID_S.shutdown(socket.SHUT_RDWR) + self.SERVER_VID_S.close() + if self.SERVER_AUD_S: + # self.SERVER_AUD_S.shutdown(socket.SHUT_RDWR) + self.SERVER_AUD_S.close() + if self.SERVER_CMD_S: + # self.SERVER_CMD_S.shutdown(socket.SHUT_RDWR) + self.SERVER_CMD_S.close() + LoggerBackend.debug("Closed sockets") + @classmethod def remove_client_socket(cls, address): ip = address[0] diff --git a/src/server/ui/__init__.py b/src/server/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/ui/cli/__init__.py b/src/server/ui/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/ui/cli/cli_main.py b/src/server/ui/cli/cli_main.py new file mode 100644 index 0000000..d491c29 --- /dev/null +++ b/src/server/ui/cli/cli_main.py @@ -0,0 +1,9 @@ +from src.server.util.logging.logger_cli import LoggerCli + + +class CliMain: + def __init__(self): + LoggerCli.throw(NotImplementedError("CLI not implemented")) + + def start(self): + LoggerCli.throw(NotImplementedError("CLI not implemented")) diff --git a/src/server/ui/gui/__init__.py b/src/server/ui/gui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/ui/gui/frame/__init__.py b/src/server/ui/gui/frame/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/server/ui/gui/frame/frame_get_key.py b/src/server/ui/gui/frame/frame_get_key.py new file mode 100644 index 0000000..b7a5e2a --- /dev/null +++ b/src/server/ui/gui/frame/frame_get_key.py @@ -0,0 +1,165 @@ +import os +import time +import tkMessageBox +from Tkinter import PhotoImage, Button, END +from threading import Thread +from ttk import Entry, Progressbar, Combobox + +from src.server.util.interface_util import InterfaceUtil +from src.server.data import constants +from src.server.data.resource import Resource +from src.server.ui.gui.frame.frame_tab import FrameTab +from src.server.util.logging.logger_gui import LoggerGui +from src.server.util.wpa_supplicant import WpaSupplicant + + +class FrameGetKey(FrameTab): + def __init__(self, master=None, **kw): + FrameTab.__init__(self, master, **kw) + self.wpa_supplicant = None + self.getting_psk = False + # Widgets + button_size = 50 + # Spade + self.button_spade = Button(self, width=button_size, height=button_size) + self.button_spade.image = self.get_image("image/spade.gif", button_size, button_size) + self.button_spade.config(image=self.button_spade.image) + self.button_spade.number = 0 + # Heart + self.button_heart = Button(self, width=button_size, height=button_size) + self.button_heart.image = self.get_image("image/heart.gif", button_size, button_size) + self.button_heart.config(image=self.button_heart.image) + self.button_heart.number = 1 + # Diamond + self.button_diamond = Button(self, width=button_size, height=button_size) + self.button_diamond.image = self.get_image("image/diamond.gif", button_size, button_size) + self.button_diamond.config(image=self.button_diamond.image) + self.button_diamond.number = 2 + # Clover + self.button_clover = Button(self, width=button_size, height=button_size) + self.button_clover.image = self.get_image("image/clover.gif", button_size, button_size) + self.button_clover.config(image=self.button_clover.image) + self.button_clover.number = 3 + # Delete + self.button_delete = Button(self, text="Delete") + # Code + self.entry_pair_code = Entry(self, state="readonly") + # Progress bar + self.progress_bar = Progressbar(self, mode="indeterminate") + # interface dropdown + self.dropdown_wii_u = Combobox(self, state="readonly") + # Events + self.button_spade.bind("", self.button_clicked) + self.button_heart.bind("", self.button_clicked) + self.button_diamond.bind("", self.button_clicked) + self.button_clover.bind("", self.button_clicked) + self.button_delete.bind("", self.button_delete_clicked) + # Grid + self.button_spade.grid(column=0, row=0) + self.button_heart.grid(column=1, row=0) + self.button_diamond.grid(column=2, row=0) + self.button_clover.grid(column=3, row=0) + self.button_delete.grid(column=4, row=0) + self.entry_pair_code.grid(column=0, row=1, columnspan=5) + self.progress_bar.grid(column=0, row=3, columnspan=5) + self.dropdown_wii_u.grid(column=0, row=2, columnspan=5) + + def button_delete_clicked(self, event): + self.set_code_text(self.entry_pair_code.get()[:len(self.entry_pair_code.get()) - 1]) + + def button_clicked(self, event): + if self.getting_psk: + tkMessageBox.showerror("Running", "A pairing attempt is already im progress.") + return + number = str(event.widget.number) + LoggerGui.debug("A suit button was clicked") # Don't log numbers as the code can be derived from that + code = self.entry_pair_code.get() + code += number + self.set_code_text(code) + wii_u_interface = self.dropdown_wii_u.get() + if not wii_u_interface: + tkMessageBox.showerror("No Interface", "An interface must be selected.") + self.activate() + return + try: + InterfaceUtil.get_mac(wii_u_interface) + except ValueError: + tkMessageBox.showerror("Interface Error", "The selected Interface is no longer available.") + self.activate() + return + if InterfaceUtil.is_managed_by_network_manager(wii_u_interface): + set_unmanaged = tkMessageBox.askokcancel( + "Managed Interface", "This interface is managed by Network Manager. To use it with DRC Sim it needs " + "to be set to unmanaged. Network Manager will not be able to control the interface" + " after this.\nSet %s to unmanaged?" % wii_u_interface) + if set_unmanaged: + InterfaceUtil.set_unmanaged_by_network_manager(wii_u_interface) + else: + tkMessageBox.showerror("Managed Interface", "Selected Wii U interface is managed by Network Manager.") + self.activate() + return + if len(code) == 4: + self.getting_psk = True + self.set_code_text("") + self.progress_bar.grid() + self.get_psk(code, wii_u_interface) + + def get_psk(self, code, interface): + LoggerGui.debug("Attempting to get PSK") # Don't log code + if not os.path.exists(constants.PATH_TMP): + os.mkdir(constants.PATH_TMP) + tmp_conf = open(constants.PATH_CONF_CONNECT_TMP, "w") + tmp_conf.write(Resource("config/get_psk.conf").resource) + tmp_conf.close() + self.wpa_supplicant = WpaSupplicant() + self.wpa_supplicant.add_status_change_listener(self.wpa_status_changed) + self.wpa_supplicant.get_psk(constants.PATH_CONF_CONNECT_TMP, interface, code) + + def wpa_status_changed(self, status): + LoggerGui.debug("Wpa status changed to %s", status) + if status == WpaSupplicant.NOT_FOUND: + self.deactivate() + self.activate() + tkMessageBox.showerror("Scan", "No Wii U found.") + elif status == WpaSupplicant.TERMINATED: + self.deactivate() + self.activate() + tkMessageBox.showerror("Auth Fail", "Could not authenticate. Check the entered PIN.") + elif status == WpaSupplicant.FAILED_START: + self.deactivate() + self.activate() + tkMessageBox.showerror("Error", "An unexpected error occurred.") + elif status == WpaSupplicant.DISCONNECTED: + self.deactivate() + self.activate() + tkMessageBox.showerror("Auth Saved", "Successfully paired with Wii U.") + + def activate(self): + LoggerGui.debug("FrameTab activate called") + self.getting_psk = False + self.set_code_text("") + self.progress_bar.start() + self.progress_bar.grid_remove() + self.dropdown_wii_u["values"] = InterfaceUtil.get_wiiu_compatible_interfaces() + + def deactivate(self): + LoggerGui.debug("FrameTab deactivate called") + self.progress_bar.stop() + self.getting_psk = False + if self.wpa_supplicant: + self.wpa_supplicant.stop() + + @staticmethod + def get_image(location, width, height): + image = PhotoImage(data=Resource(location).resource) + orig_width = image.width() + orig_height = image.height() + image = image.zoom(width, height) + image = image.subsample(orig_width, orig_height) + return image + + def set_code_text(self, text): + self.entry_pair_code.config(state="normal") + self.entry_pair_code.delete(0, END) + self.entry_pair_code.insert(0, text) + self.entry_pair_code.config(state="readonly") diff --git a/src/server/ui/gui/frame/frame_run_server.py b/src/server/ui/gui/frame/frame_run_server.py new file mode 100644 index 0000000..3b9f062 --- /dev/null +++ b/src/server/ui/gui/frame/frame_run_server.py @@ -0,0 +1,155 @@ +import os +import tkMessageBox +from ttk import Label, Button, Combobox + +from src.server.control.gamepad import Gamepad +from src.server.util.wpa_supplicant import WpaSupplicant +from src.server.data import constants +from src.server.ui.gui.frame.frame_tab import FrameTab +from src.server.util.interface_util import InterfaceUtil +from src.server.util.logging.logger_gui import LoggerGui + + +class FrameRunServer(FrameTab): + def __init__(self, master=None, **kw): + FrameTab.__init__(self, master, **kw) + self.wii_u_interface = None + self.normal_interface = None + self.gamepad = None + self.wpa_supplicant = None + LoggerGui.extra("Initializing FrameRunServer") + # Create Widgets + self.label_wpa = Label(self, text="Wii U Connection:") + self.label_backend = Label(self, text="Server Status:") + self.label_wpa_status = Label(self) + self.label_backend_status = Label(self) + self.button_start = Button(self, text="Start") + self.button_stop = Button(self, text="Stop") + self.label_wiiu_interface = Label(self, text="Wii U Interface") + self.label_normal_interface = Label(self, text="Normal Interface") + self.dropdown_wiiu_interface = Combobox(self, state="readonly") + self.dropdown_normal_interface = Combobox(self, state="readonly") + self.label_interface_info = Label(self) + # Events + self.button_start.bind("", self.start_server) + self.button_stop.bind("", self.stop_server) + # Position widgets + self.label_wpa.grid(column=0, row=0, sticky="e") + self.label_backend.grid(column=0, row=1, sticky="e") + self.label_wpa_status.grid(column=1, row=0, sticky="w") + self.label_backend_status.grid(column=1, row=1, sticky="w") + self.label_wiiu_interface.grid(column=0, row=2) + self.label_normal_interface.grid(column=0, row=3) + self.dropdown_wiiu_interface.grid(column=1, row=2, columnspan=2) + self.dropdown_normal_interface.grid(column=1, row=3, columnspan=2) + self.button_start.grid(column=1, row=4) + self.button_stop.grid(column=2, row=4) + self.label_interface_info.grid(column=0, row=5, columnspan=3) + LoggerGui.extra("Initialized FrameRunServer") + + def start_server(self, event=None): + if event: + LoggerGui.debug("User clicked start server button") + LoggerGui.debug("Start server called") + if self.label_backend_status["text"] != Gamepad.STOPPED and \ + (self.label_wpa_status["text"] not in (WpaSupplicant.DISCONNECTED, WpaSupplicant.TERMINATED)): + tkMessageBox.showerror("Running", "Server is already running") + return + if not os.path.exists(constants.PATH_CONF_CONNECT): + tkMessageBox.showerror("Auth Error", + "No auth details found. Use the \"Get Key\" tab to pair with a Wii U.") + self.activate() + return + self.normal_interface = self.dropdown_normal_interface.get() + self.wii_u_interface = self.dropdown_wiiu_interface.get() + if not self.normal_interface or not self.wii_u_interface: + tkMessageBox.showerror("Interface Error", "Two interfaces need to be selected.") + self.activate() + return + if self.normal_interface == self.wii_u_interface: + tkMessageBox.showerror("Interface Error", "The selected normal and Wii U interfaces must be different.") + self.activate() + return + try: + InterfaceUtil.get_mac(self.normal_interface) + InterfaceUtil.get_mac(self.wii_u_interface) + except ValueError: + tkMessageBox.showerror("Interface Error", "The selected Interface is no longer available.") + self.activate() + return + if InterfaceUtil.is_managed_by_network_manager(self.wii_u_interface): + set_unmanaged = tkMessageBox.askokcancel( + "Managed Interface", "This interface is managed by Network Manager. To use it with DRC Sim it needs " + "to be set to unmanaged. Network Manager will not be able to control the interface" + " after this.\nSet %s to unmanaged?" % self.wii_u_interface) + if set_unmanaged: + InterfaceUtil.set_unmanaged_by_network_manager(self.wii_u_interface) + else: + tkMessageBox.showerror("Managed Interface", "Selected Wii U interface is managed by Network Manager.") + self.activate() + return + LoggerGui.debug("Starting wpa supplicant") + self.wpa_supplicant = WpaSupplicant() + self.wpa_supplicant.add_status_change_listener(self.wpa_status_changed) + self.wpa_supplicant.connect(constants.PATH_CONF_CONNECT, self.wii_u_interface) + self.label_backend_status.config(text="WAITING") + + def wpa_status_changed(self, status): + LoggerGui.debug("Wpa changed status to %s", status) + self.label_wpa_status.config(text=status) + if status == WpaSupplicant.CONNECTED: + LoggerGui.debug("Routing") + InterfaceUtil.dhclient() + InterfaceUtil.set_metric(self.normal_interface, 0) + InterfaceUtil.set_metric(self.wii_u_interface, 1) + LoggerGui.debug("Starting backend") + self.gamepad = Gamepad() + self.gamepad.add_status_change_listener(self.backend_status_changed) + self.gamepad.start() + self.label_interface_info.config(text="Server IP: " + InterfaceUtil.get_ip(self.normal_interface)) + elif status in (WpaSupplicant.DISCONNECTED, WpaSupplicant.TERMINATED): + self.stop_server() + elif status == WpaSupplicant.NOT_FOUND: + self.stop_server() + tkMessageBox.showerror("Scan Error", "No Wii U found.") + elif status == WpaSupplicant.FAILED_START: + self.stop_server() + tkMessageBox.showerror("Cannot Connect", "Failed to start wpa_supplicant_drc. This could mean there is a " + "configuration error or wpa_supplicant_drc is not installed. " + "Check %s for details." % constants.PATH_LOG_WPA) + + def backend_status_changed(self, status): + LoggerGui.debug("Backend status changed to %s", status) + self.label_backend_status.config(text=status) + if status == Gamepad.NO_PACKETS: + self.stop_server() + + def stop_server(self, event=None): + if event: + LoggerGui.debug("User clicked stop server button") + LoggerGui.debug("Stop server called") + if event and (self.label_wpa_status["text"] in (WpaSupplicant.DISCONNECTED, WpaSupplicant.TERMINATED) + and self.label_backend_status["text"] == Gamepad.STOPPED): + tkMessageBox.showerror("Stop", "Server is not running.") + return + if self.gamepad: + self.gamepad.close() + if self.wpa_supplicant: + self.wpa_supplicant.stop() + self.activate() + + def activate(self): + LoggerGui.debug("FrameRunServer activated") + self.dropdown_wiiu_interface["values"] = InterfaceUtil.get_wiiu_compatible_interfaces() + self.dropdown_normal_interface["values"] = InterfaceUtil.get_all_interfaces() + self.dropdown_wiiu_interface.set("") + self.dropdown_normal_interface.set("") + self.label_wpa_status["text"] = WpaSupplicant.DISCONNECTED + self.label_backend_status["text"] = Gamepad.STOPPED + self.button_start.config(state="normal") + self.button_stop.config(state="normal") + self.label_interface_info.config(text="") + + def deactivate(self): + LoggerGui.debug("FrameRunServer deactivated") + self.stop_server() diff --git a/src/server/ui/gui/frame/frame_tab.py b/src/server/ui/gui/frame/frame_tab.py new file mode 100644 index 0000000..79f75d3 --- /dev/null +++ b/src/server/ui/gui/frame/frame_tab.py @@ -0,0 +1,12 @@ +from ttk import Frame + + +class FrameTab(Frame): + def __init__(self, master=None, **kw): + Frame.__init__(self, master, **kw) + + def activate(self): + raise NotImplementedError() + + def deactivate(self): + raise NotImplementedError() diff --git a/src/server/ui/gui/gui_main.py b/src/server/ui/gui/gui_main.py new file mode 100644 index 0000000..f67495a --- /dev/null +++ b/src/server/ui/gui/gui_main.py @@ -0,0 +1,68 @@ +import Tkinter +from ttk import Notebook + +from src.server.ui.gui.frame.frame_get_key import FrameGetKey +from src.server.ui.gui.frame.frame_run_server import FrameRunServer +from src.server.util.logging.logger_gui import LoggerGui + + +class GuiMain: + def __init__(self): + Tkinter.Tk.report_callback_exception = self.throw + # Main window + self.destroyed = False + LoggerGui.info("Initializing GUI") + self.main_window = Tkinter.Tk() + self.main_window.wm_title("DRC Sim Server") + self.main_window.protocol("WM_DELETE_WINDOW", self.on_closing) + # Notebook + self.tab_id = None + self.notebook = Notebook(self.main_window, width=300, height=150) + self.notebook.grid(column=0, row=0) + self.notebook.bind("<>", self.on_tab_changed) + # Run Server Frame + self.frame_run_server = FrameRunServer(self.notebook) + self.notebook.add(self.frame_run_server, text="Run Server") + # Get Key Frame + self.frame_get_key = FrameGetKey(self.notebook) + self.notebook.add(self.frame_get_key, text="Get Key") + + @staticmethod + def throw(*args): + for arg in args: + if isinstance(arg, Exception): + LoggerGui.throw(arg) + + def after(self): + self.main_window.after(1000, self.after) + + def start(self): + LoggerGui.info("Opening GUI") + self.after() + self.main_window.mainloop() + LoggerGui.info("GUI Closed") + + def stop(self): + self.on_closing() + + def on_closing(self): + if self.destroyed: + return + self.destroyed = True + LoggerGui.info("Closing GUI") + if self.tab_id in self.notebook.children: + self.notebook.children[self.tab_id].deactivate() + try: + self.main_window.destroy() + except Exception, e: + LoggerGui.exception(e) + + def on_tab_changed(self, event): + tab_id = self.notebook.select() + tab_index = self.notebook.index(tab_id) + tab_name = self.notebook.tab(tab_index, "text") + LoggerGui.debug("Notebook tab changed to \"%s\" with id %d", tab_name, tab_index) + if self.tab_id: + self.notebook.children[self.tab_id].deactivate() + self.tab_id = tab_id.split(".")[len(tab_id.split(".")) - 1] + self.notebook.children[self.tab_id].activate() diff --git a/src/server/util/interface_util.py b/src/server/util/interface_util.py new file mode 100644 index 0000000..cfb8250 --- /dev/null +++ b/src/server/util/interface_util.py @@ -0,0 +1,85 @@ +import netifaces +import os + +from src.server.data import constants +from src.server.util.logging.logger import Logger +from src.server.util.os_util import OsUtil +from src.server.util.process_util import ProcessUtil + + +class InterfaceUtil: + def __init__(self): + pass + + @classmethod + def get_wiiu_compatible_interfaces(cls): + """ + Returns a list of interfaces that can operate on the 5GHz spectrum + :return: array of interface names + """ + all_interfaces = cls.get_all_interfaces() + compatible_interfaces = [] + for interface in all_interfaces: + if cls.is_interface_wiiu_compatible(interface): + compatible_interfaces.append(interface) + return compatible_interfaces + + @classmethod + def get_all_interfaces(cls): + """ + Gets a list of all system network interfaces + :return: array of interface names + """ + interfaces = [] + for interface in netifaces.interfaces(): + interfaces.append(interface) + return interfaces + + @classmethod + def is_interface_wiiu_compatible(cls, interface): + if not OsUtil.is_linux(): + Logger.extra("Ignoring interface compatibility check for %s", interface) + return False + frequency_info = ProcessUtil.get_output(["iwlist", interface, "frequency"]) + return "5." in frequency_info + # TODO check for 802.11n compliance + + @classmethod + def get_ip(cls, interface): + return netifaces.ifaddresses(interface)[netifaces.AF_INET][0]["addr"] + + @classmethod + def get_mac(cls, interface): + return netifaces.ifaddresses(interface)[netifaces.AF_LINK][0]["addr"] + + @classmethod + def set_metric(cls, interface, metric): + ProcessUtil.call(["ifmetric", interface, str(metric)]) + + @classmethod + def dhclient(cls): + ProcessUtil.call(["killall", "dhclient"]) + ProcessUtil.call(["dhclient"]) + + @classmethod + def is_managed_by_network_manager(cls, interface): + if not os.path.exists(constants.PATH_CONF_NETWORK_MANAGER): + Logger.debug("Network manager config not found.") + return False + conf = open(constants.PATH_CONF_NETWORK_MANAGER) + conf_data = conf.readlines() + conf.close() + for line in conf_data: + if line.startswith("unmanaged") and "mac:" + cls.get_mac(interface) in line: + Logger.debug("Interface \"%s\" is unmanaged by network manager.", interface) + return False + return True + + @classmethod + def set_unmanaged_by_network_manager(cls, interface): + Logger.debug("Adding interface \"%s-%s\" as an unmanaged interface to network manager", interface, + cls.get_mac(interface)) + conf = open(constants.PATH_CONF_NETWORK_MANAGER, "a") + conf.writelines(["[keyfile]\n", "unmanaged-devices=mac:" + cls.get_mac(interface) + "\n"]) + conf.close() + ProcessUtil.call(["service", "network-manager", "restart"]) diff --git a/src/server/util/logging/logger.py b/src/server/util/logging/logger.py index 7419be4..714feb2 100644 --- a/src/server/util/logging/logger.py +++ b/src/server/util/logging/logger.py @@ -1,40 +1,24 @@ import logging import os import shutil -import sys from src.server.data import constants class Logger: level = logging.INFO + INFO = logging.INFO + DEBUG = logging.DEBUG EXTRA = logging.DEBUG - 1 FINER = logging.DEBUG - 2 VERBOSE = logging.DEBUG - 3 logger = logging.getLogger("drcsim") - consoleHandler = None - fileHandler = None + console_handler = None + file_handler = None def __init__(self, name=None): - if not Logger.logger.handlers: - # Logger - if name: - Logger.logger = logging.getLogger(name) - # Console output - Logger.consoleHandler = logging.StreamHandler() - Logger.consoleHandler.setFormatter( - logging.Formatter("%(asctime)s %(levelname)s:%(name)s %(message)s")) - Logger.logger.addHandler(Logger.consoleHandler) - # File output - log_path = os.path.join(constants.LOG_PATH, Logger.logger.name + ".log") - if not os.path.exists(constants.LOG_PATH): - os.mkdir(constants.LOG_PATH) - if os.path.exists(log_path): - shutil.copyfile(log_path, log_path.replace(".log", "-1.log")) - os.remove(log_path) - Logger.fileHandler = logging.FileHandler(log_path) - Logger.fileHandler.setFormatter(Logger.consoleHandler.formatter) - Logger.logger.addHandler(Logger.fileHandler) + if not Logger.console_handler or not Logger.file_handler: + Logger.logger, Logger.console_handler, Logger.file_handler = self.create_logger(name) # Level names logging.addLevelName(Logger.EXTRA, "EXTRA") logging.addLevelName(Logger.FINER, "FINER") @@ -60,7 +44,8 @@ def verbose(cls, message, *args): def set_level(cls, level): cls.level = level cls.logger.setLevel(cls.level) - cls.consoleHandler.setLevel(cls.level) + cls.console_handler.setLevel(cls.level) + cls.file_handler.setLevel(cls.level) @classmethod def warn(cls, message, *args): @@ -68,15 +53,16 @@ def warn(cls, message, *args): @classmethod def throw(cls, exception, message=None, *args): - cls.logger.error("=" * 50 + "[Crash]" + "=" * 50) + cls.logger.error(str("=" * 50 + " [ CRASH ] " + "=" * 50)) if message: cls.logger.error(message, *args) if isinstance(exception, Exception): cls.logger.exception(exception) else: cls.logger.error(exception) - cls.logger.error("=" * 50 + "[Crash]" + "=" * 50) - sys.exit(1) + cls.logger.error(str("=" * 50 + " [ CRASH ] " + "=" * 50)) + if cls != Logger: + raise exception @classmethod def finer(cls, message, *args): @@ -85,3 +71,31 @@ def finer(cls, message, *args): @classmethod def exception(cls, exception, *args): cls.logger.log(cls.EXTRA, exception, *args, exc_info=1) + + @classmethod + def get_level(cls): + return cls.level + + @staticmethod + def create_logger(name): + logger = logging.getLogger(name) + format_str = "%(asctime)s %(levelname)s:%(name)s %(message)s" + # Console output + console_handler = logging.StreamHandler() + console_handler.setFormatter(logging.Formatter(format_str)) + logger.addHandler(console_handler) + # File output + log_path = os.path.join(constants.PATH_LOG_DIR, logger.name + ".log") + if not os.path.exists(constants.PATH_ROOT): + os.mkdir(constants.PATH_ROOT) + if not os.path.exists(constants.PATH_LOG_DIR): + os.mkdir(constants.PATH_LOG_DIR) + if os.path.exists(log_path): + shutil.copyfile(log_path, log_path.replace(".log", "-1.log")) + os.remove(log_path) + file_handler = logging.FileHandler(log_path) + file_handler.setFormatter(logging.Formatter(format_str)) + logger.addHandler(file_handler) + return logger, console_handler, file_handler + +Logger("drcsim") diff --git a/src/server/util/logging/logger_backend.py b/src/server/util/logging/logger_backend.py index 6df6759..6a27703 100644 --- a/src/server/util/logging/logger_backend.py +++ b/src/server/util/logging/logger_backend.py @@ -4,6 +4,7 @@ class LoggerBackend(Logger): def __init__(self, name=None): Logger.__init__(self, name) + LoggerBackend.logger, LoggerBackend.console_handler, LoggerBackend.file_handler = self.create_logger(name) LoggerBackend("backend") diff --git a/src/server/util/logging/logger_cli.py b/src/server/util/logging/logger_cli.py new file mode 100644 index 0000000..dd5a702 --- /dev/null +++ b/src/server/util/logging/logger_cli.py @@ -0,0 +1,9 @@ +from src.server.util.logging.logger import Logger + + +class LoggerCli(Logger): + def __init__(self, name=None): + Logger.__init__(self, name) + LoggerCli.logger, LoggerCli.console_handler, LoggerCli.file_handler = self.create_logger(name) + +LoggerCli("cli") diff --git a/src/server/util/logging/logger_gui.py b/src/server/util/logging/logger_gui.py new file mode 100644 index 0000000..e50be9f --- /dev/null +++ b/src/server/util/logging/logger_gui.py @@ -0,0 +1,9 @@ +from src.server.util.logging.logger import Logger + + +class LoggerGui(Logger): + def __init__(self, name=None): + Logger.__init__(self, name) + LoggerGui.logger, LoggerGui.console_handler, LoggerGui.file_handler = self.create_logger(name) + +LoggerGui("gui") diff --git a/src/server/util/logging/logger_wpa.py b/src/server/util/logging/logger_wpa.py new file mode 100644 index 0000000..0db4daf --- /dev/null +++ b/src/server/util/logging/logger_wpa.py @@ -0,0 +1,9 @@ +from src.server.util.logging.logger import Logger + + +class LoggerWpa(Logger): + def __init__(self, name=None): + Logger.__init__(self, name) + LoggerWpa.logger, LoggerWpa.console_handler, LoggerWpa.file_handler = self.create_logger(name) + +LoggerWpa("wpa") diff --git a/src/server/util/os_util.py b/src/server/util/os_util.py new file mode 100644 index 0000000..3e614f3 --- /dev/null +++ b/src/server/util/os_util.py @@ -0,0 +1,31 @@ +import os +import platform as _platform + + +class OsUtil: + platform = os.name + name = _platform.system() + release = _platform.release() + distro = _platform.linux_distribution() + + def __init__(self): + pass + + @classmethod + def is_windows(cls): + return "windows" in cls.name.lower() + + @classmethod + def log_info(cls, logger): + if not cls.is_linux(): + logger.warn("This OS is not supported") + # Log info + logger.debug("OS platform: %s", cls.platform) + logger.debug("OS name: %s", cls.name) + if cls.is_linux(): + logger.debug("OS distro: %s", cls.distro) + logger.debug("OS release: %s", cls.release) + + @classmethod + def is_linux(cls): + return "linux" in cls.name.lower() diff --git a/src/server/util/process_util.py b/src/server/util/process_util.py new file mode 100644 index 0000000..4babd0a --- /dev/null +++ b/src/server/util/process_util.py @@ -0,0 +1,47 @@ +import subprocess + +import errno + +from src.server.util.logging.logger import Logger + + +class ProcessUtil: + def __init__(self): + pass + + @classmethod + def get_output(cls, command, silent=False): + """ + Wraps a subprocess.check_output call. Checks for process errors and command not found errors. + :param command: array of strings - same as check_output + :return: string of output, error, or None if the command is not found + """ + try: + if not silent: + Logger.extra("Attempting to execute command %s", command) + output = subprocess.check_output(command, stderr=subprocess.STDOUT) + except OSError, e: + output = "" + if e.errno == errno.ENOENT: + Logger.warn("\"%s\" may not be installed", command[0]) + else: + Logger.exception(e) + cls.log_failed_command(command) + except subprocess.CalledProcessError, e: + output = e.output + cls.log_failed_command(command, output.strip()) + Logger.verbose("Command \"%s\" output %s", command, output) + return output + + @classmethod + def call(cls, command): + """ + Same as subprocess.call but outputs to logger + :param command: string array - same as call + :return: None + """ + cls.get_output(command) + + @classmethod + def log_failed_command(cls, command, output=None): + Logger.extra("Failed to execute command \"%s\" and got output \"%s\"", command, output) diff --git a/src/server/util/wpa_supplicant.py b/src/server/util/wpa_supplicant.py new file mode 100644 index 0000000..e64d841 --- /dev/null +++ b/src/server/util/wpa_supplicant.py @@ -0,0 +1,261 @@ +import re +import subprocess +import time +from threading import Thread + +import pexpect + +from src.server.data import constants +from src.server.data.config_server import ConfigServer +from src.server.util.logging.logger_wpa import LoggerWpa +from src.server.util.process_util import ProcessUtil + + +class WpaSupplicant: + UNKNOWN = "UNKNOWN" + CONNECTING = "CONNECTING" + CONNECTED = "CONNECTED" + TERMINATED = "TERMINATED" + DISCONNECTED = "DISCONNECTED" + SCANNING = "SCANNING" + NOT_FOUND = "NOT_FOUND" + FAILED_START = "FAILED_START" + + def __init__(self): + self.time_scan = 0 + self.time_start = 0 + self.mac_addr_regex = re.compile('^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})') + self.wiiu_ap_regex = re.compile('^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})(\s*\d*\s*-*\d*\s*)' + '(\[WPA2-PSK-CCMP\])?' + '(\[ESS\])(\s*)(WiiU|\\\\x00)(.+)$') # \x00 is escaped (\\x00) + self.running = False + self.status = self.UNKNOWN + self.status_check_thread = None + self.status_changed_listeners = [] + self.wpa_supplicant_process = None + self.psk_thread = None + self.psk_thread_cli = None + + def connect(self, conf_path, interface, status_check=True): + LoggerWpa.debug("Connect called") + self.running = True + self.unblock_wlan() + self.kill_wpa() + command = ["wpa_supplicant_drc", "-Dnl80211", "-i", interface, "-c", conf_path] + if LoggerWpa.get_level() == LoggerWpa.FINER: + command.append("-d") + elif LoggerWpa.get_level() == LoggerWpa.VERBOSE: + command.append("-dd") + LoggerWpa.debug("Starting wpa supplicant") + self.wpa_supplicant_process = subprocess.Popen(command, stdout=open(constants.PATH_LOG_WPA, "w"), + stderr=subprocess.STDOUT) + LoggerWpa.debug("Started wpa supplicant") + if status_check: + LoggerWpa.debug("Starting status check thread") + self.status_check_thread = Thread(target=self.check_status) + self.status_check_thread.start() + + def check_status(self): + while self.running: + wpa_status = self.wpa_cli("status") + scan_results = self.wpa_cli("scan_results") + not_started_message = "Failed to connect to non-global ctrl_ifname" + LoggerWpa.finer("Scan Results: %s", scan_results) + LoggerWpa.finer("Status: %s", wpa_status) + # process is dead or wpa_supplicant has not started + if self.wpa_supplicant_process.poll() or not_started_message in scan_results: + LoggerWpa.finer("%d seconds until start timeout", 30 - self.time_start) + # wait for wpa_supplicant to initialize + if self.time_start >= 5: + status = self.FAILED_START + else: + status = self.status + self.time_start += 1 + # scanning + elif not self.scan_contains_wii_u(scan_results) or "wpa_state=SCANNING" in wpa_status: + LoggerWpa.finer("%d seconds until scan timeout", ConfigServer.scan_timeout - self.time_scan) + # timeout scan + if self.time_scan >= ConfigServer.scan_timeout: + status = self.NOT_FOUND + else: + status = self.SCANNING + self.time_scan += 1 + elif "wpa_state=COMPLETED" in wpa_status: + status = self.CONNECTED + self.time_scan = 0 # forces a disconnect - might need to be handled better + elif "wpa_state=AUTHENTICATING" in wpa_status or "wpa_state=ASSOCIATING" in wpa_status: + status = self.CONNECTING + elif "wpa_state=DISCONNECTED" in wpa_status: + status = self.DISCONNECTED + else: + LoggerWpa.extra("WPA status: %s", wpa_status) + status = self.UNKNOWN + if status != self.status: + self.status = status + for callback in self.status_changed_listeners: + callback(self.status) + time.sleep(1) + + @staticmethod + def kill_wpa(): + ProcessUtil.call(["killall", "wpa_supplicant_drc"]) + + @staticmethod + def unblock_wlan(): + ProcessUtil.call(["rfkill", "unblock", "wlan"]) + + @staticmethod + def wpa_cli(command): + if isinstance(command, str): + command = [command] + return ProcessUtil.get_output(["wpa_cli_drc", "-p", "/var/run/wpa_supplicant_drc"] + command, silent=True) + + def stop(self): + if not self.running: + LoggerWpa.debug("Ignored stop request: already stopped") + return + self.running = False + if self.status_check_thread: + LoggerWpa.debug("Stopping wpa status check") + try: + self.status_check_thread.join() + except RuntimeError, e: + LoggerWpa.exception(e) + if self.psk_thread_cli and self.psk_thread_cli.isalive(): + LoggerWpa.debug("Stopping psk pexpect spawn") + self.psk_thread_cli.sendline("quit") + self.psk_thread_cli.close(True) + LoggerWpa.debug("Stopping wpa process") + if self.wpa_supplicant_process and self.wpa_supplicant_process.poll() is None: + self.wpa_supplicant_process.terminate() + self.kill_wpa() + # reset + self.status_changed_listeners = [] + self.time_start = 0 + self.time_scan = 0 + LoggerWpa.debug("Wpa stopped") + + def add_status_change_listener(self, callback): + """ + Calls passed method when status changed. + If listening to a "connect" call: + FAILED_START: wpa_supplicant_drc did not initialize + SCANNING: wpa_supplicant_drc is scanning + CONNECTED: wpa_supplicant_drc is connected to an AP + CONNECTING: wpa_supplicant_drc is authenticating + TERMINATED: wpa_supplicant_drc was found by the T-1000 Cyberdyne Systems Model 101 + NOT_FOUND: wpa_supplicant_drc could not find a Wii U AP + UNKNOWN: wpa_supplicant_drc is in a state that is unhandled - it will be logged + If listening to a "get_psk" call: + FAILED_START: there was an error attempting to parse CLI output - exception is logged + NOT_FOUND: wpa_supplicant_drc did not find any Wii U APs + TERMINATED: wpa_supplicant_drc could not authenticate with any SSIDs + DISCONNECTED: auth details were saved + :param callback: method to call on status change + :return: None + """ + self.status_changed_listeners.append(callback) + + def scan_contains_wii_u(self, scan_results): + for line in scan_results.split("\n"): + if self.wiiu_ap_regex.match(line): + return True + return False + + def scan_is_empty(self, scan_results): + for line in scan_results.split("\n"): + if self.mac_addr_regex.match(line): + return False + return True + + def get_psk(self, conf_path, interface, code): + self.connect(conf_path, interface, status_check=False) + self.psk_thread = Thread(target=self.get_psk_thread, kwargs={"code": code}, name="PSK Thread") + self.psk_thread.start() + + def get_psk_thread(self, code): + try: + LoggerWpa.debug("CLI expect starting") + self.psk_thread_cli = pexpect.spawn("wpa_cli_drc -p /var/run/wpa_supplicant_drc") + LoggerWpa.debug("CLI expect Waiting for init") + self.psk_thread_cli.expect("Interactive mode") + # Scan for Wii U SSIDs + scan_tries = 5 + wii_u_bssids = [] + while self.running and scan_tries > 0: + self.psk_thread_cli.sendline("scan") + LoggerWpa.debug("CLI expect waiting for scan start") + self.psk_thread_cli.expect("OK") + LoggerWpa.debug("CLI expect waiting for scan results available event") + self.psk_thread_cli.expect("<3>CTRL-EVENT-SCAN-RESULTS", timeout=60) + self.psk_thread_cli.sendline("scan_results") + LoggerWpa.debug("CLI expect waiting for scan results") + self.psk_thread_cli.expect("bssid / frequency / signal level / flags / ssid") + for line in range(0, 100): # up to 100 APs + try: + self.psk_thread_cli.expect(self.mac_addr_regex.pattern, timeout=1) + except pexpect.TIMEOUT: + break + scan_results = self.psk_thread_cli.before + LoggerWpa.finer("CLI expect - scan results: %s", scan_results) + for line in scan_results.split("\n"): + if self.wiiu_ap_regex.match(line): + wii_u_bssids.append(line.split()[0]) + if len(wii_u_bssids) == 0: + scan_tries -= 1 + else: + scan_tries = 0 + # Check for found Wii U ssids + if len(wii_u_bssids) == 0: + LoggerWpa.debug("No Wii U SSIDs found") + for callback in self.status_changed_listeners: + callback(self.NOT_FOUND) + return + # attempt to pair with any wii u bssid + for bssid in wii_u_bssids: + self.psk_thread_cli.sendline("wps_pin %s %s" % (bssid, code + "5678")) + LoggerWpa.debug("CLI expect waiting for wps_pin input confirmation") + self.psk_thread_cli.expect(code + "5678") + LoggerWpa.debug("CLI expect waiting for authentication") + try: + self.psk_thread_cli.expect("<3>WPS-CRED-RECEIVED", timeout=60) + # save conf + LoggerWpa.debug("PSK obtained") + self.save_connect_conf(bssid) + for callback in self.status_changed_listeners: + callback(self.DISCONNECTED) + return + except pexpect.TIMEOUT: + LoggerWpa.debug("CLI expect BSSID auth failed") + self.psk_thread_cli.sendline("reconnect") + self.psk_thread_cli.expect("OK") + # Timed out + except pexpect.TIMEOUT, e: + LoggerWpa.debug("PSK get attempt ended with an error.") + LoggerWpa.exception(e) + for callback in self.status_changed_listeners: + callback(self.FAILED_START) + # Failed to authenticate + LoggerWpa.debug("Could not authenticate with any SSIDs") + for callback in self.status_changed_listeners: + callback(self.TERMINATED) + + @staticmethod + def save_connect_conf(bssid): + LoggerWpa.debug("Saving connection config") + # add additional connect information to config + conf = open(constants.PATH_CONF_CONNECT_TMP, "r") + lines = conf.readlines() + conf.close() + for line in lines: + if "update_config=1" in line: + lines.insert(lines.index(line) + 1, "ap_scan=1\n") + break + for line in lines: + if "network={" in line: + lines.insert(lines.index(line) + 1, "\tscan_ssid=1\n") + lines.insert(lines.index(line) + 2, "\tbssid=" + bssid + "\n") + break + save_conf = open(constants.PATH_CONF_CONNECT, "w") + save_conf.writelines(lines) + save_conf.close()