def run_exp(self, name, exp_config=None, schedule_name=None): if name[-3:] == ".py": name = name[:-3] if name not in self.experiments: logging.error("Experiment file %s not found! Skipping." % name) else: exp_class = self.experiments[name] results = {"meta": {}} try: logging.debug("Getting metadata for experiment...") meta = self.get_meta() results["meta"] = meta except Exception as exception: logging.exception("Error fetching metadata for " "%s: %s" % (name, exception)) results["meta_exception"] = str(exception) if schedule_name is not None: results["meta"]["schedule_name"] = schedule_name else: results["meta"]["schedule_name"] = name start_time = datetime.now() results["meta"]["client_time"] = start_time.isoformat() results["meta"]["centinel_version"] = centinel.__version__ # include vpn provider in metadata if self.vpn_provider: results["meta"]["vpn_provider"] = self.vpn_provider input_files = {} if exp_config is not None: if (('input_files' in exp_config) and (exp_config['input_files'] is not None)): for filename in exp_config['input_files']: file_handle = self.load_input_file(filename) if file_handle is not None: input_files[filename] = file_handle if (('params' in exp_config) and (exp_config['params'] is not None)): exp_class.params = exp_config['params'] # if the scheduler does not specify input files, but # the experiment class specifies a list of input file names, # load them. failing to load input files does not stop # experiment from running. if len(input_files) == 0: if exp_class.input_files is not None: for filename in exp_class.input_files: file_handle = self.load_input_file(filename) if file_handle is not None: input_files[filename] = file_handle # otherwise, fall back to [schedule name].txt (deprecated) else: filename = "%s.txt" % name file_handle = self.load_input_file(filename) if file_handle is not None: input_files[filename] = file_handle try: # instantiate the experiment logging.debug("Initializing the experiment class for %s" % name) # these constants can be useful for some experiments, but it is not # encouraged to use these directly global_constants = { 'experiments_dir': self.config['dirs']['experiments_dir'], 'results_dir': self.config['dirs']['results_dir'], 'data_dir': self.config['dirs']['data_dir'] } exp_class.global_constants = global_constants exp = exp_class(input_files) except Exception as exception: logging.exception("Error initializing %s: %s" % (name, exception)) results["init_exception"] = str(exception) return exp.global_constants = global_constants run_tcpdump = True if self.config['results']['record_pcaps'] is False: logging.info("Your configuration has disabled pcap " "recording, tcpdump will not start.") run_tcpdump = False # disable this on the experiment too exp.record_pcaps = False if run_tcpdump and os.geteuid() != 0: logging.info("Centinel is not running as root, " "tcpdump will not start.") run_tcpdump = False if run_tcpdump and exp_class.overrides_tcpdump: logging.info("Experiment overrides tcpdump recording.") run_tcpdump = False tcpdump_started = False try: if run_tcpdump: td = Tcpdump() tds.append(td) td.start() tcpdump_started = True logging.info("tcpdump started...") # wait for tcpdump to initialize time.sleep(2) except Exception as exp: logging.exception("Failed to run tcpdump: %s" % (exp, )) try: # run the experiment exp.run() except Exception as exception: logging.exception("Error running %s: %s" % (name, exception)) results["runtime_exception"] = str(exception) except KeyboardInterrupt: logging.warn( "Keyboard interrupt received, stopping experiment...") # save any external results that the experiment has generated # they could be anything that doesn't belong in the json file # (e.g. pcap files) # these should all be compressed with bzip2 # the experiment is responsible for giving these a name and # keeping a list of files in the json results results_dir = self.config['dirs']['results_dir'] if exp.external_results is not None: logging.debug("Writing external files for %s" % name) for fname, fcontents in exp.external_results.items(): external_file_name = ( "external_%s-%s-%s" ".bz2" % (name, start_time.strftime("%Y-%m-%dT%H%M%S.%f"), fname)) external_file_path = os.path.join(results_dir, external_file_name) try: with open(external_file_path, 'w:bz2') as file_p: data = bz2.compress(fcontents) file_p.write(data) logging.debug("External file " "%s written successfully" % fname) except Exception as exp: logging.exception("Failed to write external file:" "%s" % exp) logging.debug("Finished writing external files for %s" % name) if tcpdump_started: logging.info("Waiting for tcpdump to process packets...") # 5 seconds should be enough. this hasn't been tested on # a RaspberryPi or a Hummingboard i2 time.sleep(5) td.stop() logging.info("tcpdump stopped.") bz2_successful = False data = None try: pcap_file_name = ( "pcap_%s-%s.pcap" ".bz2" % (name, start_time.strftime("%Y-%m-%dT%H%M%S.%f"))) pcap_file_path = os.path.join(results_dir, pcap_file_name) with open(pcap_file_path, 'wb') as pcap_bz2, open(td.pcap_filename(), 'rb') as pcap: compressor = bz2.BZ2Compressor() compressed_size_so_far = 0 for pcap_data in iter(lambda: pcap.read(10 * 1024), b''): compressed_chunk = compressor.compress(pcap_data) pcap_bz2.write(compressed_chunk) if len(compressed_chunk): compressed_size_so_far += len(compressed_chunk) compressed_chunk = compressor.flush() pcap_bz2.write(compressed_chunk) if len(compressed_chunk): compressed_size_so_far += len(compressed_chunk) uncompressed_size = os.path.getsize(td.pcap_filename()) compression_ratio = 100 * ( float(compressed_size_so_far) / float(uncompressed_size)) logging.debug( "pcap BZ2 compression: compressed/uncompressed (ratio):" " %d/%d (%.1f%%)" % (compressed_size_so_far, uncompressed_size, compression_ratio)) logging.info("Saved pcap to " "%s." % pcap_file_path) bz2_successful = True except Exception as exception: logging.exception("Failed to compress and write " "pcap file: %s" % exception) if not bz2_successful: logging.info("Writing pcap file uncompressed") try: pcap_file_name = ( "pcap_%s-%s" ".pcap" % (name, start_time.strftime("%Y-%m-%dT%H%M%S.%f"))) pcap_file_path = os.path.join(results_dir, pcap_file_name) with open(pcap_file_path, 'wb') as pcap_out, open( td.pcap_filename(), 'rb') as pcap: for pcap_data in iter(lambda: pcap.read(10 * 1024), b''): pcap_out.write(pcap_data) logging.info("Saved pcap to " "%s." % pcap_file_path) except Exception as exception: logging.exception("Failed to write " "pcap file: %s" % exception) # delete pcap data to free up some memory logging.debug("Removing pcap data from memory") td.delete() del data del td # close input file handle(s) logging.debug("Closing input files for %s" % name) if type(input_files) is dict: for file_name, file_handle in input_files.items(): try: file_handle.close() except AttributeError: logging.warning("Closing %s failed" % file_name) logging.debug("Input files closed for %s" % name) logging.debug("Storing results for %s" % name) try: results[name] = exp.results except Exception as exception: logging.exception("Error storing results for " "%s: %s" % (name, exception)) if "results_exception" not in results: results["results_exception"] = {} results["results_exception"][name] = str(exception) end_time = datetime.now() time_taken = (end_time - start_time) results["meta"]["time_taken"] = time_taken.total_seconds() logging.info("%s took %s to finish." % (name, time_taken)) logging.debug("Saving %s results to file" % name) try: # pretty printing results will increase file size, but files are # compressed before sending. result_file_path = self\ .get_result_file(name, start_time.strftime("%Y-%m-%dT%H%M%S.%f")) result_file = bz2.BZ2File(result_file_path, "w") json.dump( results, result_file, indent=2, separators=(',', ': '), # ignore encoding errors, these will be dealt with on the server ensure_ascii=False) result_file.close() # free up memory by deleting results from memory del results del result_file except Exception as exception: logging.exception("Error saving results for " "%s to file: %s" % (name, exception)) logging.debug("Done saving %s results to file" % name)
def run_exp(self, name, exp_config=None, schedule_name=None): if name[-3:] == ".py": name = name[:-3] if name not in self.experiments: logging.error("Experiment file %s not found! Skipping." % name) else: exp_class = self.experiments[name] results = {"meta": {}} try: logging.debug("Getting metadata for experiment...") meta = self.get_meta() results["meta"] = meta except Exception as exception: logging.exception("Error fetching metadata for " "%s: %s" % (name, exception)) results["meta_exception"] = str(exception) if schedule_name is not None: results["meta"]["schedule_name"] = schedule_name else: results["meta"]["schedule_name"] = name start_time = datetime.now() results["meta"]["client_time"] = start_time.isoformat() results["meta"]["centinel_version"] = centinel.__version__ # include vpn provider in metadata if self.vpn_provider: results["meta"]["vpn_provider"] = self.vpn_provider input_files = {} if exp_config is not None: if (('input_files' in exp_config) and (exp_config['input_files'] is not None)): for filename in exp_config['input_files']: file_handle = self.load_input_file(filename) if file_handle is not None: input_files[filename] = file_handle if (('params' in exp_config) and (exp_config['params'] is not None)): exp_class.params = exp_config['params'] # if the scheduler does not specify input files, but # the experiment class specifies a list of input file names, # load them. failing to load input files does not stop # experiment from running. if len(input_files) == 0: if exp_class.input_files is not None: for filename in exp_class.input_files: file_handle = self.load_input_file(filename) if file_handle is not None: input_files[filename] = file_handle # otherwise, fall back to [schedule name].txt (deprecated) else: filename = "%s.txt" % name file_handle = self.load_input_file(filename) if file_handle is not None: input_files[filename] = file_handle try: # instantiate the experiment logging.debug("Initializing the experiment class for %s" % name) # these constants can be useful for some experiments, but it is not # encouraged to use these directly global_constants = {'experiments_dir': self.config['dirs']['experiments_dir'], 'results_dir': self.config['dirs']['results_dir'], 'data_dir': self.config['dirs']['data_dir']} exp_class.global_constants = global_constants exp = exp_class(input_files) except Exception as exception: logging.exception("Error initializing %s: %s" % (name, exception)) results["init_exception"] = str(exception) return exp.global_constants = global_constants run_tcpdump = True if self.config['results']['record_pcaps'] is False: logging.info("Your configuration has disabled pcap " "recording, tcpdump will not start.") run_tcpdump = False # disable this on the experiment too exp.record_pcaps = False if run_tcpdump and os.geteuid() != 0: logging.info("Centinel is not running as root, " "tcpdump will not start.") run_tcpdump = False if run_tcpdump and exp_class.overrides_tcpdump: logging.info("Experiment overrides tcpdump recording.") run_tcpdump = False tcpdump_started = False try: if run_tcpdump: td = Tcpdump() tds.append(td) td.start() tcpdump_started = True logging.info("tcpdump started...") # wait for tcpdump to initialize time.sleep(2) except Exception as exp: logging.exception("Failed to run tcpdump: %s" % (exp,)) try: # run the experiment exp.run() except Exception as exception: logging.exception("Error running %s: %s" % (name, exception)) results["runtime_exception"] = str(exception) except KeyboardInterrupt: logging.warn("Keyboard interrupt received, stopping experiment...") # save any external results that the experiment has generated # they could be anything that doesn't belong in the json file # (e.g. pcap files) # these should all be compressed with bzip2 # the experiment is responsible for giving these a name and # keeping a list of files in the json results results_dir = self.config['dirs']['results_dir'] if exp.external_results is not None: logging.debug("Writing external files for %s" % name) for fname, fcontents in exp.external_results.items(): external_file_name = ("external_%s-%s-%s" ".bz2" % (name, start_time.strftime("%Y-%m-%dT%H%M%S.%f"), fname)) external_file_path = os.path.join(results_dir, external_file_name) try: with open(external_file_path, 'w:bz2') as file_p: data = bz2.compress(fcontents) file_p.write(data) logging.debug("External file " "%s written successfully" % fname) except Exception as exp: logging.exception("Failed to write external file:" "%s" % exp) logging.debug("Finished writing external files for %s" % name) if tcpdump_started: logging.info("Waiting for tcpdump to process packets...") # 5 seconds should be enough. this hasn't been tested on # a RaspberryPi or a Hummingboard i2 time.sleep(5) td.stop() logging.info("tcpdump stopped.") bz2_successful = False data = None try: pcap_file_name = ("pcap_%s-%s.pcap" ".bz2" % (name, start_time.strftime("%Y-%m-%dT%H%M%S.%f"))) pcap_file_path = os.path.join(results_dir, pcap_file_name) with open(pcap_file_path, 'wb') as pcap_bz2, open(td.pcap_filename(), 'rb') as pcap: compressor = bz2.BZ2Compressor() compressed_size_so_far = 0 for pcap_data in iter(lambda: pcap.read(10 * 1024), b''): compressed_chunk = compressor.compress(pcap_data) pcap_bz2.write(compressed_chunk) if len(compressed_chunk): compressed_size_so_far += len(compressed_chunk) compressed_chunk = compressor.flush() pcap_bz2.write(compressed_chunk) if len(compressed_chunk): compressed_size_so_far += len(compressed_chunk) uncompressed_size = os.path.getsize(td.pcap_filename()) compression_ratio = 100 * (float(compressed_size_so_far) / float(uncompressed_size)) logging.debug("pcap BZ2 compression: compressed/uncompressed (ratio):" " %d/%d (%.1f%%)" % (compressed_size_so_far, uncompressed_size, compression_ratio)) logging.info("Saved pcap to " "%s." % pcap_file_path) bz2_successful = True except Exception as exception: logging.exception("Failed to compress and write " "pcap file: %s" % exception) if not bz2_successful: logging.info("Writing pcap file uncompressed") try: pcap_file_name = ("pcap_%s-%s" ".pcap" % (name, start_time.strftime("%Y-%m-%dT%H%M%S.%f"))) pcap_file_path = os.path.join(results_dir, pcap_file_name) with open(pcap_file_path, 'wb') as pcap_out, open(td.pcap_filename(), 'rb') as pcap: for pcap_data in iter(lambda: pcap.read(10 * 1024), b''): pcap_out.write(pcap_data) logging.info("Saved pcap to " "%s." % pcap_file_path) except Exception as exception: logging.exception("Failed to write " "pcap file: %s" % exception) # delete pcap data to free up some memory logging.debug("Removing pcap data from memory") td.delete() del data del td # close input file handle(s) logging.debug("Closing input files for %s" % name) if type(input_files) is dict: for file_name, file_handle in input_files.items(): try: file_handle.close() except AttributeError: logging.warning("Closing %s failed" % file_name) logging.debug("Input files closed for %s" % name) logging.debug("Storing results for %s" % name) try: results[name] = exp.results except Exception as exception: logging.exception("Error storing results for " "%s: %s" % (name, exception)) if "results_exception" not in results: results["results_exception"] = {} results["results_exception"][name] = str(exception) end_time = datetime.now() time_taken = (end_time - start_time) results["meta"]["time_taken"] = time_taken.total_seconds() logging.info("%s took %s to finish." % (name, time_taken)) logging.debug("Saving %s results to file" % name) try: # pretty printing results will increase file size, but files are # compressed before sending. result_file_path = self\ .get_result_file(name, start_time.strftime("%Y-%m-%dT%H%M%S.%f")) result_file = bz2.BZ2File(result_file_path, "w") json.dump(results, result_file, indent=2, separators=(',', ': '), # ignore encoding errors, these will be dealt with on the server ensure_ascii=False) result_file.close() # free up memory by deleting results from memory del results del result_file except Exception as exception: logging.exception("Error saving results for " "%s to file: %s" % (name, exception)) logging.debug("Done saving %s results to file" % name)