class LinuxDevice(DesktopDevice): def __init__(self, config, connector): super().__init__(config, connector) self._connector_helper = ConnectorHelper(self) @staticmethod def local_ips(): raise XVEx("TODO: Local IPs for Linux") @staticmethod def open_app(binary_path, root=False): unused(root) if binary_path is None: L.debug('Application has no binary path; not opening') # TODO: open the application here @staticmethod def close_app(binary_path, root=False): unused(root) if binary_path is None: L.debug('Application has no binary path; not closing') # TODO: close the application here def os_name(self): return 'linux' def os_version(self): return " ".join(platform.linux_distribution()) def report_info(self): info = super().report_info() commands = [ ['uname', '-a'], ['lsb_release', '-a'], ['lscpu'], ] for command in commands: try: info += self._connector_helper.check_command(command)[0] except XVProcessException as ex: L.warning( "Couldn't get system info using command {}:\n{}".format( command, ex)) return info def kill_process(self, pid): L.debug("Killing process {}".format(pid)) return self._connector_helper.execute_scriptlet( 'remote_os_kill.py', [pid, int(signal.SIGKILL)], root=True) def pgrep(self, process_name): L.debug("pgrep-ing for {}".format(process_name)) return self._connector_helper.execute_scriptlet('pgrep.py', [process_name], root=True) def command_line_for_pid(self, pid): return self._connector_helper.execute_scriptlet( 'command_line_for_pid.py', [pid], root=True)
def setup(self): if not self._config.get("checkout", False): return # Update the device's git checkout to be the same as our branch and to be at latest revision branch = Git._git_branch() L.info("Updating git repo to branch {} on device {}".format( branch, self._device.device_id())) connector_helper = ConnectorHelper(self._device) git_root = self._device.config().get('git_root', tools_root()) # TODO: Potentially should clean as well? connector_helper.execute_command([ # This can possibly be done in fewer lines 'cd', git_root, '&&', 'git', 'checkout', branch, '&&', 'git', 'pull', '&&', 'git', 'submodule', 'update', '--init', '--recursive' ])
def __init__(self, device, config): super().__init__(device, config) self._connector_helper = ConnectorHelper(self._device) # Having a randomized test chain name is a cheap way of ensuring that two instances of this # class will never interfere with one another. Unlikely we'll ever need two but you never # know self._testing_chain_name = "xv_leak_testing_" + "".join( random.choice(string.ascii_uppercase) for _ in range(8)) self._create_testing_chain()
class TestCaseRemote(TestCase): def __init__(self, devices, parameters): super().__init__(devices, parameters) self.test_device = self.devices['test_device'] self.remote_test_config = self.parameters self.connector_helper = ConnectorHelper(self.test_device) def test(self): L.info("Running test {} completely on remote device".format( self.remote_test_config['name'])) remote_config_path = os.path.join(self.test_device.temp_directory(), 'test_config.py') self.connector_helper.write_remote_file_from_contents( remote_config_path, json.dumps([self.remote_test_config])) cmd = [ 'run_tests.py', self.test_device.output_directory(), '-c', remote_config_path ] # TODO: Use .requires_root to figure out if the test needs root. ret, stdout, stderr = self.connector_helper.execute_python(cmd, root=True) # TODO: Reconsider all this. Maybe we shouldn't display the output here as it screws up the # logs. Possibly better is to have ERROR log go to stderr then we just dump stderr here if stdout: # TODO: Make the logger send error and other to correct out file L.info('*' * 80) L.info('BEGIN stdout from remote machine:') L.info('*' * 80) for line in stdout.splitlines(): # TODO: Logger not satisfying my needs! print(line) L.info('*' * 80) L.info('END stdout from remote machine:') L.info('*' * 80) if stderr: L.error('*' * 80) L.error('BEGIN stderr from remote machine:') L.error('*' * 80) for line in stderr.splitlines(): # TODO: Logger not satisfying my needs! print(line) L.error('*' * 80) L.error('END stderr from remote machine:') L.error('*' * 80) if ret == 0: L.info('Remote test execution succeeded') return L.error('Remote test execution failed') self.failTest('Remote test run failed!')
class MacOSDevice(DesktopDevice): def __init__(self, config, connector): super().__init__(config, connector) # TODO: I think this should be part of DesktopDevice. Need to clarify what all these thigns # mean. I think we should move to DesktopDevice meaning anything with the tools. Maybe even # this becomes ToolsDevice. self._connector_helper = ConnectorHelper(self) # TODO: This needs to execute remotely in general. Let's make a scriptlet. Let's ensure that # nothing on the device classes themselves restricts the devices to being the localhost @staticmethod def local_ips(): ips = [] for iface in netifaces.interfaces(): if netifaces.AF_INET in netifaces.ifaddresses(iface): ips.append(netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']) return [ipaddress.ip_address(ip) for ip in ips] def open_app(self, bundle_path, root=False): # Quote the bundle path as some have spaces in self._connector_helper.execute_scriptlet( 'macos_open_app.py', ["'{}'".format(bundle_path)], root=root) def close_app(self, bundle_path, root=False): # Quit by sending quit signal to the window so the app shuts down how a user would shut it # down. In theory it's equivalent to a pkill but slightly more realistic this way self._connector_helper.execute_command( ['osascript', '-e', "'quit app \"{}\"'".format(bundle_path)], root=root) def os_name(self): return 'macos' def os_version(self): return self._connector_helper.execute_scriptlet('remote_mac_ver.py', [])[0] def report_info(self): info = super().report_info() try: info += self._connector_helper.check_command( ['system_profiler', 'SPSoftwareDataType'])[0] except XVProcessException as ex: L.warning("Couldn't get OS info from system_profiler:\n{}".format(ex)) return info def kill_process(self, pid): L.debug("Killing process {}".format(pid)) return self._connector_helper.execute_scriptlet( 'remote_os_kill.py', [pid, int(signal.SIGKILL)], root=True) def pgrep(self, process_name): '''Similar to the posix pgrep program, however it will return any process ids where process_name is a a substring of the whole process command line.''' L.debug("pgrep-ing for {}".format(process_name)) return self._connector_helper.execute_scriptlet('pgrep.py', [process_name], root=True) def command_line_for_pid(self, pid): return self._connector_helper.execute_scriptlet('command_line_for_pid.py', [pid], root=True)
class LinuxDevice(DesktopDevice): def __init__(self, config, connector): super().__init__(config, connector) self._connector_helper = ConnectorHelper(self) @staticmethod def local_ips(): raise XVEx("TODO: Local IPs for Linux") @staticmethod def open_app(binary_path, root=False): unused(root) if binary_path is None: L.debug('Application has no binary path; not opening') # TODO: open the application here @staticmethod def close_app(binary_path, root=False): unused(root) if binary_path is None: L.debug('Application has no binary path; not closing') # TODO: close the application here def os_name(self): return 'linux' def os_version(self): L.warning("TODO: Linux version") return 'TODO: Linux version' def report_info(self): # TODO: Add Linux-specific reporting here. info = super().report_info() return info def kill_process(self, pid): L.debug("Killing process {}".format(pid)) return self._connector_helper.execute_scriptlet( 'remote_os_kill.py', [pid, int(signal.SIGKILL)], root=True) def pgrep(self, process_name): L.debug("pgrep-ing for {}".format(process_name)) return self._connector_helper.execute_scriptlet('pgrep.py', [process_name], root=True) def command_line_for_pid(self, pid): return self._connector_helper.execute_scriptlet( 'command_line_for_pid.py', [pid], root=True)
class MacOSRoute(Route): PROG_ROW = re.compile( r"^([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*([^\s]*)$") def __init__(self, device, config): super().__init__(device, config) self._connector_helper = ConnectorHelper(self._device) # Internet: # Destination Gateway Flags Refs Use Netif Expire # default 192.168.56.1 UGSc 100 90 en0 # default 192.168.104.1 UGScI 1 0 en4 # default 192.168.216.1 UGScI 0 0 vlan0 def get_v4_routes(self): routes = [] lines = self._connector_helper.check_command(['netstat', '-rn'])[0].splitlines() for line in lines: if "Destination" in line: continue match = MacOSRoute.PROG_ROW.match(line) if not match: continue entry = RouteEntry( dest=match.group(1), gway=match.group(2), flags=match.group(3), refs=match.group(4), use=match.group(5), iface=match.group(6), expire=match.group(7) ) routes.append(entry) return routes
def main(argv=None): if argv is None: argv = sys.argv[1:] scriptlet_args = argv[1:] argv = argv[0:1] parser = argparse.ArgumentParser(description='Execute a scriptlet locally for testing') parser.add_argument( 'scriptlet', help='Name of the scriptlet in the xv_leak_tools/scriplets folder') args = parser.parse_args(argv) L.configure({ 'trace': { 'level': L.INFO, }, 'describe': { 'file_format': None, }, 'report': { 'file_format': None, }, }) try: L.info("Running scriptlet {} with args {}".format(args.scriptlet, scriptlet_args)) context = TestRunContext({'output_directory': 'output'}) localhost = LocalhostDiscoverer(context, []).discover_device({}) helper = ConnectorHelper(localhost) L.info("scriptlet returned {}".format(helper.execute_scriptlet( 'remote_mac_ver.py', scriptlet_args))) except BaseException as ex: L.info("scriptlet raised {}".format(ex)) return 0
class LinuxRoute(Route): PROG_ROW = re.compile( r"^([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$" ) def __init__(self, device, config): super().__init__(device, config) self._connector_helper = ConnectorHelper(self._device) # Kernel IP routing table # Destination Gateway Genmask Flags MSS Window irtt Iface # 0.0.0.0 172.16.49.2 0.0.0.0 UG 0 0 0 ens33 # 169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 ens33 # 172.16.49.0 0.0.0.0 255.255.255.0 U 0 0 0 ens33 def get_v4_routes(self): routes = [] lines = self._connector_helper.check_command(['netstat', '-rn'])[0].splitlines() for line in lines: if "Destination" in line: continue match = LinuxRoute.PROG_ROW.match(line) print(line) if not match: continue # Windows routes use IPs for the netmask rather than CIDR blocks. dest = "{}/{}".format( match.group(1), netaddr.IPAddress(match.group(3)).netmask_bits()) entry = RouteEntry( dest=dest, gway=match.group(2), flags=match.group(4), iface=match.group(8), ) routes.append(entry) return routes
class WindowsRoute(Route): PROG_ROW = re.compile( r"^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)$") def __init__(self, device, config): super().__init__(device, config) self._connector_helper = ConnectorHelper(self._device) # Network Destination Netmask Gateway Interface Metric # 0.0.0.0 0.0.0.0 172.16.49.2 172.16.49.174 2000 # 0.0.0.0 0.0.0.0 192.168.216.1 192.168.216.139 11000 # 0.0.0.0 0.0.0.0 172.16.49.2 172.16.49.173 2 # 127.0.0.0 255.0.0.0 On-link 127.0.0.1 331 # 127.0.0.1 255.255.255.255 On-link 127.0.0.1 331 # 127.255.255.255 255.255.255.255 On-link 127.0.0.1 331 # 169.254.0.0 255.255.0.0 On-link 169.254.25.248 262 def get_v4_routes(self): routes = [] lines = self._connector_helper.check_command(['netstat', '-rn'])[0].splitlines() for line in lines: if "Destination" in line: continue match = WindowsRoute.PROG_ROW.match(line) if not match: continue # Windows routes use IPs for the netmask rather than CIDR blocks. dest = "{}/{}".format( match.group(1), netaddr.IPAddress(match.group(2)).netmask_bits()) entry = RouteEntry(dest=dest, gway=match.group(3), flags="", iface=match.group(4)) routes.append(entry) return routes
class WindowsDNSTool(DNSLookupTool): def __init__(self, device, config): super().__init__(device, config) self._connector_helper = ConnectorHelper(self._device) def known_servers(self): output = self._connector_helper.check_command([ 'wmic.exe', 'nicconfig', 'where', '"IPEnabled = True"', 'get', 'DNSServerSearchOrder', '/format:rawxml' ])[0] L.verbose("Got raw wmic output: {}".format(output)) dns_servers = [] for nic in fromstring(output).findall("./RESULTS/CIM/INSTANCE"): for prop in nic: if prop.tag != 'PROPERTY.ARRAY': continue for val in prop.findall("./VALUE.ARRAY/VALUE"): ip = ipaddress.ip_address(val.text) dns_servers.append(ip) self._check_current_dns_server_is_known(dns_servers) return dns_servers
def __init__(self, device): self._connector_helper = ConnectorHelper(device)
def __init__(self, config, connector): super().__init__(config, connector) self._connector_helper = ConnectorHelper(self)
def __init__(self, devices, parameters): super().__init__(devices, parameters) self.test_device = self.devices['test_device'] self.remote_test_config = self.parameters self.connector_helper = ConnectorHelper(self.test_device)
def __init__(self, device, config): super().__init__(device, config) self._connector_helper = ConnectorHelper(self._device)
class IPToolCurl(LocalComponent): ACCEPTABLE_CURL_ERRORS = [ "Connection timed out", "Couldn't connect to server", "Could not resolve host", "Resolving timed out after", "Operation timed out" ] def __init__(self, device, config): super().__init__(device, config) # Separating out DynDNS allows us to easily use hosts to get the IPs if we wish. self._dyndns = ICanHazIP(self._curl_url) self._connector_helper = ConnectorHelper(device) self._max_time = None def _execute(self, cmd): return self._connector_helper.check_command(cmd) def _curl_url(self, url): L.debug("Curl-ing url {} to find ips".format(url)) # TODO: Can't use pycurl on cygwin. Need to figure out how to compile cleanly. # try: # L.debug("curl-ing {}".format(url)) # curl_instance = curl.Curl(url) # curl_instance.set_option(pycurl.TIMEOUT, 5) # response = curl_instance.get() # print response # except pycurl.error as ex: # errno, message = ex.args # if errno == pycurl.E_COULDNT_CONNECT: # L.warning( # "pycurl couldn't connect to {}. Assuming no public IP available.".format(url)) # return None # # Be cautious. Haven't assessed what other errors are acceptable so let's # # just raise up # # the error. # raise try: cmd = ['curl', url, '--connect-timeout', '5'] if self._max_time: cmd += ['-m', self._max_time] return self._execute(cmd) except XVProcessException as ex: for error in IPToolCurl.ACCEPTABLE_CURL_ERRORS: if error in ex.stderr: L.verbose( "curl couldn't connect to {}. Assuming no public IP available." .format(url)) return "", "" # Be cautious. Haven't assessed what other errors are acceptable so let's just raise up # the error. raise def public_ipv4_addresses(self, timeout=None): # TODO: Yuck! Redo this class. Hierarchy is bad. Just inherit things like DynDNS from # IPToolCurl self._max_time = timeout return self._dyndns.public_ipv4_addresses() def public_ipv6_addresses(self, timeout=None): self._max_time = timeout return self._dyndns.public_ipv6_addresses() def all_public_ip_addresses(self, timeout=None): self._max_time = timeout return self._dyndns.all_public_ip_addresses()
def __init__(self, device, config): super().__init__(device, config) # Separating out DynDNS allows us to easily use hosts to get the IPs if we wish. self._dyndns = ICanHazIP(self._curl_url) self._connector_helper = ConnectorHelper(device) self._max_time = None
class WindowsDevice(DesktopDevice): # pylint: disable=no-self-use PROG_OS_VERSION = re.compile(r'OS Version:\s*([^\s]+).*') def __init__(self, config, connector): super().__init__(config, connector) self._connector_helper = ConnectorHelper(self) def _pgrep_cygwin(self, process_name): # -W shows windows processes under cygwin lines = self._connector_helper.check_command(['ps', '-efW'])[0].splitlines() lines = [line for line in lines if process_name in line] return [int(line.split()[1]) for line in lines] def is_cygwin(self): ret = self._connector_helper.execute_command(['uname'])[0] # Don't even need to check the output. This will fail if DOS. return ret == 0 def os_name(self): return 'windows' def os_version(self): try: # Use systeminfo on Windows as platform.win32_ver doesn't work for cygwin. That way the # code is agnostic to the shell. output = self._connector_helper.check_command(['systeminfo'])[0] for line in output.splitlines(): match = WindowsDevice.PROG_OS_VERSION.match(line) if match: return match.group(1) raise XVEx( "Couldn't determine Windows Version from systeminfo output:\n{}" .format(output)) except XVProcessException as ex: raise XVEx( "Couldn't determine Windows Version as systeminfo failed:\n{}". format(ex)) # TODO: I think all desktop devices share this, so derive instead. @staticmethod def local_ips(): ips = [] for iface in netifaces.interfaces(): if netifaces.AF_INET in netifaces.ifaddresses(iface): ips.append( netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']) return [ipaddress.ip_address(ip) for ip in ips] # TODO: Not sure this will either work at all or work on cygwin def open_app(self, app_path, root=False): # pylint: disable=unused-argument cmd = ['run', "\"{}\"".format(windows_safe_path(app_path))] L.debug("Executing cmd '{}'".format(cmd)) self._connector_helper.check_command(cmd) def close_app(self, app_path, root=False): # pylint: disable=unused-argument pname = windows_path_split(app_path)[1] pids = [str(pid) for pid in self.pgrep(pname)] if len(pids) > 1: L.warning( "Closing all pids {} associated to application {}".format( ', '.join(pids), pname)) for pid in pids: self.kill_process(pid) def kill_process(self, pid): L.debug("Killing process {}".format(pid)) # taskkill is the most generic way to handle windows self._connector_helper.check_command( ['taskkill', '/PID', str(pid), '/F'], root=True) def pgrep(self, process_name): '''Similar to the posix pgrep program, however it will return any process ids where process_name is a a substring of the whole process command line.''' L.debug("pgrep-ing for {}".format(process_name)) if self.is_cygwin(): return self._pgrep_cygwin(process_name) return self._connector_helper.execute_scriptlet('pgrep.py', [process_name], root=True) # This is pretty sketchy. Do better! @staticmethod def _fix_quotes(cmd_line): args = [] next_arg = "" start_quote = False escaping = False for ichar, char in enumerate(cmd_line): if char not in ["\\", "\"", " "]: next_arg += char continue if char == "\"": if escaping: next_arg += char escaping = False continue elif start_quote: args.append(next_arg) start_quote = False next_arg = "" continue else: start_quote = True continue elif char == "\\": if cmd_line[ichar + 1] == "\"": escaping = True next_arg += char else: next_arg += char continue elif char == " ": if start_quote: next_arg += char continue else: if next_arg: args.append(next_arg) next_arg = "" continue if next_arg != "": args.append(next_arg) return args def command_line_for_pid(self, pid): if self.is_cygwin(): # The psutil module isn't supported on cygwin cmd = [ "wmic", "process", "where", "ProcessID='{}'".format(pid), "get", "CommandLine" ] args = self._connector_helper.check_command(cmd)[0] L.verbose("Raw wmic command line was: {}".format(args)) return WindowsDevice._fix_quotes(args) return self._connector_helper.execute_scriptlet( 'command_line_for_pid.py', [pid]) def report_info(self): info = super().report_info() try: info += self._connector_helper.check_command(['systeminfo'])[0] except XVProcessException as ex: L.warning("Couldn't get OS info from systeminfo:\n{}".format(ex)) return info
class PosixGoPacketCapturer: def __init__(self, device, interface): self._device = device self._connector_helper = ConnectorHelper(self._device) self._interface = interface self._capture_file = 'capture.{}.json'.format(self._interface) self._pid_file = None def _binary_location(self): here_relative = os.path.relpath( os.path.dirname(os.path.realpath(__file__)), tools_root()) return os.path.join(self._device.tools_root(), here_relative, '..', 'bin', "xv_packet_capture") def _get_packets(self): # TODO: This highlights a weakness in the framework. I can't pull to my local device # because I don't know where to pull to! Just pulling to a temp file for now src = os.path.join(self._device.temp_directory(), self._capture_file) file_, dst = tempfile.mkstemp(prefix="xv_leak_test_", suffix="_{}".format(self._capture_file)) os.close(file_) os.remove(dst) timeup = TimeUp(5) while not timeup: try: self._device.connector().pull(src, dst) break except XVEx: L.warning( "Waiting for capture file to be written: {}".format(src)) time.sleep(1) if not os.path.exists(dst): raise XVEx("Couldn't get capture file from capture device") packets = object_from_json_file(dst, 'attribute') return packets['data'] @staticmethod def _random_pid_file(): return "xv_packet_capture_{}.pid".format(''.join( random.choice(string.ascii_uppercase) for _ in range(10))) def start(self): L.debug("Starting packet capture on interface {}".format( self._interface)) if self._pid_file: raise XVEx("Packet capture already started!") # TODO: Use PID file self._pid_file = os.path.join(self._device.temp_directory(), PosixGoPacketCapturer._random_pid_file()) cmd = [ '/usr/local/bin/daemon', '-o', os.path.join(self._device.temp_directory(), 'daemon.out'), '--', self._binary_location(), '-i', self._interface, '-o', self._device.temp_directory(), '-f', 'capture.{}.json'.format(self._interface), '--preserve', '--debug' ] self._connector_helper.check_command(cmd, root=True) def stop(self): L.debug("Stopping packet capture on interface {} and getting packets". format(self._interface)) if not self._pid_file: raise XVEx("Packet capture not started!") # TODO: Switch to pid file kill cmd = ['killall', '-SIGINT', 'xv_packet_capture'] self._connector_helper.check_command(cmd, root=True) self._pid_file = None return self._get_packets()
def __init__(self, device, interface): self._device = device self._connector_helper = ConnectorHelper(self._device) self._interface = interface self._capture_file = 'capture.{}.json'.format(self._interface) self._pid_file = None
class LinuxFirewall(Firewall): def __init__(self, device, config): super().__init__(device, config) self._connector_helper = ConnectorHelper(self._device) # Having a randomized test chain name is a cheap way of ensuring that two instances of this # class will never interfere with one another. Unlikely we'll ever need two but you never # know self._testing_chain_name = "xv_leak_testing_" + "".join( random.choice(string.ascii_uppercase) for _ in range(8)) self._create_testing_chain() def __del__(self): self._delete_testing_chain() def _block_ip_args_rules(self, ip): return [ [self._testing_chain_name, "-s", ip, "-j", "DROP"], [self._testing_chain_name, "-d", ip, "-j", "DROP"], ] def _chain_exists(self, chain): ret, _, _ = self._connector_helper.execute_command( ["iptables", "-w", "--list", chain], root=True) return ret == 0 def _create_chain(self, chain): L.debug("Creating iptables chain {}".format(chain)) if self._chain_exists(chain): L.debug("iptables chain {} exists".format(chain)) return self._connector_helper.check_command(["iptables", "-w", "-N", chain], root=True) def _delete_chain(self, chain): L.debug("Deleting iptables chain {}".format(chain)) if not self._chain_exists(chain): L.debug("iptables chain {} doesn't exist".format(chain)) self._connector_helper.check_command(["iptables", "-w", "-X", chain], root=True) def _rule_exists(self, rule_args): ret, _, _ = self._connector_helper.execute_command( ["iptables", "-w", "-C"] + rule_args, root=True) return ret == 0 def _create_rule(self, rule_args): L.debug("Creating iptables rule {}".format(rule_args)) if self._rule_exists(rule_args): L.debug("iptables rule {} already exists".format(rule_args)) self._connector_helper.check_command(["iptables", "-w", "-A"] + rule_args, root=True) def _delete_rule(self, rule_args): L.debug("Deleting iptables rule {}".format(rule_args)) if not self._rule_exists(rule_args): L.debug("iptables rule {} doesn't exist".format(rule_args)) return self._connector_helper.check_command(["iptables", "-w", "-D"] + rule_args, root=True) def _jump_rule_args(self, chain): return [chain, "-j", self._testing_chain_name] def _delete_testing_chain(self): # Flush all rules in our chain self._connector_helper.check_command( ["iptables", "-w", "-F", self._testing_chain_name], root=True) # Remove all references (jumps) to our chain L.debug("Cleaning up iptables testing chain") for source_chain in ["INPUT", "OUTPUT"]: self._delete_rule(self._jump_rule_args(source_chain)) self._delete_chain(self._testing_chain_name) def _create_testing_chain(self): self._create_chain(self._testing_chain_name) for source_chain in ["INPUT", "OUTPUT"]: self._create_rule(self._jump_rule_args(source_chain)) def block_ip(self, ip): rules = self._block_ip_args_rules(ip) for rule in rules: self._create_rule(rule) def unblock_ip(self, ip): rules = self._block_ip_args_rules(ip) for rule in rules: self._delete_rule(rule)
def __init__(self, config, connector): super().__init__(config, connector) # TODO: I think this should be part of DesktopDevice. Need to clarify what all these thigns # mean. I think we should move to DesktopDevice meaning anything with the tools. Maybe even # this becomes ToolsDevice. self._connector_helper = ConnectorHelper(self)
class Dig: # pylint: disable=too-few-public-methods # Server line looks like: # ;; SERVER: 8.8.8.8#53(8.8.8.8) PROG_DIG_SERVER = re.compile( r";; SERVER:\s*({}).*".format(RE_IPV4_ADDRESS)) # Answers look like: # google.com. 39 IN A 216.58.200.14 PROG_DIG_ANSWER = re.compile( r"[^\s]+\s+\d+\s+[A-Z]+\s+[A-Z]+\s+({})".format(RE_IPV4_ADDRESS)) def __init__(self, device): self._connector_helper = ConnectorHelper(device) def _execute(self, cmd): return self._connector_helper.check_command(cmd) @staticmethod def _parse_answers(lines): ips = [] for line in lines: if line.strip() == "": break matches = Dig.PROG_DIG_ANSWER.match(line) if not matches: continue ips.append(ipaddress.ip_address(matches.group(1))) return ips @staticmethod def _parse_output(output): server = None lines = output.splitlines() for line in lines: matches = Dig.PROG_DIG_SERVER.match(line) if not matches: continue server = matches.group(1) break if server is None: raise XVEx("Couldn't parse dig output: {}".format(output)) ips = [] for iline, line in enumerate(lines): if 'ANSWER SECTION' not in line: continue ips = Dig._parse_answers(lines[iline + 1:]) break if len(ips) == 0: raise XVEx( "dig failed to return any IPs. Output was: {}".format(output)) return ipaddress.ip_address(server), ips # TODO: The timeout here isn't reliable. The process can lock. Let's wrap this with a Popen # and just hard kill the process if it time's out. Note that this will be fiddly for remote # execution! def lookup(self, hostname, timeout, server=None): # dig doesn't like floats for timeout timeout = int(math.ceil(timeout)) if server: cmd = [ 'dig', "+time={}".format(timeout), hostname, "@{}".format(server) ] else: cmd = ['dig', "+time={}".format(timeout), hostname] # Prevent the output from being empty stdout = None while not stdout: stdout = self._execute(cmd)[0] if not stdout: L.verbose("dig output was empty; doing another lookup.") L.verbose("dig output: {}".format(stdout)) return Dig._parse_output(stdout)