def _create(self): """ create DySKT and member processes """ # read in and validate the conf file self._readconf() # intialize shared objects self._halt = mp.Event() # our stop event self._ic = mp.Queue() # comms for children self._pConns = {} # dict of connections to children # initialize children # Each child is expected to initialize in the _init_ function and throw # a RuntimeError failure logging.info("Initializing subprocess...") try: # start RTO first logging.info("Starting RTO") (conn1, conn2) = mp.Pipe() self._pConns['rto'] = conn1 self._rto = RTO(self._ic, conn2, self._conf) # set the region? if so, do it prior to starting the RadioController rd = self._conf['local']['region'] if rd: logging.info("Setting regulatory domain to %s", rd) self._rd = iw.regget() iw.regset(rd) if iw.regget() != rd: logging.warn("Regulatory domain %s may not have been set", rd) else: logging.info("Regulatory domain set to %s", rd) # recon radio is mandatory logging.info("Starting Reconnaissance Radio") (conn1, conn2) = mp.Pipe() self._pConns['recon'] = conn1 self._rr = RadioController(self._ic, conn2, self._conf['recon']) # collection if present if self._conf['collection']: try: logging.info("Starting Collection Radio") (conn1, conn2) = mp.Pipe() self._pConns['collection'] = conn1 self._cr = RadioController(self._ic, conn2, self._conf['collection']) except RuntimeError as e: # continue without collector, but log it logging.warning( "Collection Radio (%s), continuing without", e) except RuntimeError as e: # e should have the form "Major:Minor:Description" ms = e.message.split(':') logging.error("%s (%s) %s", ms[0], ms[1], ms[2]) self._state = DYSKT_INVALID except Exception as e: # catchall logging.error(e) self._state = DYSKT_INVALID else: # start children execution self._state = DYSKT_CREATED self._rr.start() if self._cr: self._cr.start() self._rto.start()
class DySKT(object): """ DySKT - primary process of the Wraith sensor """ def __init__(self, conf=None): """ initialize variables """ # get parameters self._cpath = conf if conf else os.path.join(GPATH, 'dyskt.conf') # internal variables self._state = DYSKT_INVALID # current state self._conf = {} # dyskt configuration dict self._halt = None # the stop event self._pConns = None # token pipes for children self._ic = None # internal comms queue self._rto = None # data collation/forwarding self._rr = None # recon radio self._cr = None # collection radio self._rd = None # regulatory domain def _create(self): """ create DySKT and member processes """ # read in and validate the conf file self._readconf() # intialize shared objects self._halt = mp.Event() # our stop event self._ic = mp.Queue() # comms for children self._pConns = {} # dict of connections to children # initialize children # Each child is expected to initialize in the _init_ function and throw # a RuntimeError failure logging.info("Initializing subprocess...") try: # start RTO first logging.info("Starting RTO") (conn1, conn2) = mp.Pipe() self._pConns['rto'] = conn1 self._rto = RTO(self._ic, conn2, self._conf) # set the region? if so, do it prior to starting the RadioController rd = self._conf['local']['region'] if rd: logging.info("Setting regulatory domain to %s", rd) self._rd = iw.regget() iw.regset(rd) if iw.regget() != rd: logging.warn("Regulatory domain %s may not have been set", rd) else: logging.info("Regulatory domain set to %s", rd) # recon radio is mandatory logging.info("Starting Reconnaissance Radio") (conn1, conn2) = mp.Pipe() self._pConns['recon'] = conn1 self._rr = RadioController(self._ic, conn2, self._conf['recon']) # collection if present if self._conf['collection']: try: logging.info("Starting Collection Radio") (conn1, conn2) = mp.Pipe() self._pConns['collection'] = conn1 self._cr = RadioController(self._ic, conn2, self._conf['collection']) except RuntimeError as e: # continue without collector, but log it logging.warning( "Collection Radio (%s), continuing without", e) except RuntimeError as e: # e should have the form "Major:Minor:Description" ms = e.message.split(':') logging.error("%s (%s) %s", ms[0], ms[1], ms[2]) self._state = DYSKT_INVALID except Exception as e: # catchall logging.error(e) self._state = DYSKT_INVALID else: # start children execution self._state = DYSKT_CREATED self._rr.start() if self._cr: self._cr.start() self._rto.start() def _destroy(self): """ destroy DySKT cleanly """ # change our state self._state = DYSKT_EXITING # reset regulatory domain if necessary if self._rd: try: logging.info("Resetting regulatory domain") iw.regset(self._rd) if iw.regget() != self._rd: raise RuntimeError except: logging.warn("Failed to reset regulatory domain") # halt main execution loop & send out poison pills # put a token on the internal comms from us to break the RTO out of # any holding for data block logging.info("Stopping Sub-processes") self._halt.set() self._ic.put(('dyskt', time.time(), '!CHECK!', [])) for key in self._pConns: try: self._pConns[key].send('!STOP!') except IOError: # ignore any broken pipe errors pass self._pConns[key].close() while mp.active_children(): time.sleep(0.5) # change our state self._state = DYSKT_DESTROYED @property def state(self): return self._state def start(self): """ start execution """ # setup signal handlers for pause(s),resume(s),stop signal.signal(signal.SIGINT, self.stop) # CTRL-C and kill -INT stop signal.signal(signal.SIGTERM, self.stop) # kill -TERM stop # initialize, quit on failure logging.info("**** Starting DySKT %s ****" % dyskt.__version__) self._create() if self.state == DYSKT_INVALID: # make sure we do not leave system in corrupt state (i.e. no wireless nics) self._destroy() raise DySKTRuntimeException( "DySKT failed to initialize, shutting down") # set state to running self._state = DYSKT_RUNNING # execution loop while not self._halt.is_set(): # get message a tuple: (level,originator,type,message) for key in self._pConns: try: if self._pConns[key].poll(): (l, o, t, m) = self._pConns[key].recv() if l == "err": # only process errors involved during execution if DYSKT_CREATED < self.state < DYSKT_EXITING: if o == 'collection': # allow collection radio to fail and still continue logging.warning( "Collection radio dropped. Continuing..." ) self._pConns['collection'].send('!STOP!') self._pConns['collection'].close() del self._pConns['collection'] mp.active_children() else: logging.error("%s failed. (%s) %s", o, t, m) self.stop() elif l == "warn": logging.warning("%s: (%s) %s", o, t, m) elif l == "info": logging.info("%s: (%s) %s", o, t, m) except Exception as e: # blanke exception logging.error("DySKT failed. (Unknown) %s", e) time.sleep(1) # noinspection PyUnusedLocal def stop(self, signum=None, stack=None): """ stop execution """ if DYSKT_RUNNING <= self.state < DYSKT_EXITING: logging.info("**** Stopping DySKT ****") self._destroy() def _readconf(self): """ read in config file at cpath """ logging.info("Reading configuration file...") conf = ConfigParser.RawConfigParser() if not conf.read(self._cpath): raise DySKTConfException('%s is invalid' % self._cpath) # intialize conf to empty dict self._conf = {} try: # read in the recon radio configuration self._conf['recon'] = self._readradio(conf, 'Recon') try: # catch any collection exceptions and log a warning if conf.has_section('Collection'): self._conf['collection'] = self._readradio( conf, 'Collection') else: self._conf['collection'] = None logging.info("No collection radio specified") except (ConfigParser.NoSectionError, ConfigParser.NoOptionError, RuntimeError, ValueError): logging.warning( "Invalid collection specification. Continuing without...") # GPS section self._conf['gps'] = {} self._conf['gps']['fixed'] = conf.getboolean('GPS', 'fixed') if self._conf['gps']['fixed']: self._conf['gps']['lat'] = conf.getfloat('GPS', 'lat') self._conf['gps']['lon'] = conf.getfloat('GPS', 'lon') self._conf['gps']['alt'] = conf.getfloat('GPS', 'alt') self._conf['gps']['dir'] = conf.getfloat('GPS', 'heading') else: self._conf['gps']['port'] = conf.getint('GPS', 'port') self._conf['gps']['id'] = conf.get('GPS', 'devid') self._conf['gps']['poll'] = conf.getfloat('GPS', 'poll') self._conf['gps']['epx'] = conf.getfloat('GPS', 'epx') self._conf['gps']['epy'] = conf.getfloat('GPS', 'epy') # Storage section self._conf['store'] = { 'host': conf.get('Storage', 'host'), 'port': conf.getint('Storage', 'port') } # Local section self._conf['local'] = {'region': None, 'c2c': None} if conf.has_option('Lcoal', 'C2C'): self._conf['local']['c2c'] = conf.getint('Local', 'C2C') if conf.has_option('Local', 'region'): reg = conf.get('Local', 'region') if len(reg) != 2: logging.warn("Regulatory domain %s is invalid" % reg) else: self._conf['local']['region'] = conf.get('Local', 'region') except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e: raise DySKTConfException("%s" % e) except (RuntimeError, ValueError) as e: raise DySKTConfException("%s" % e) def _readradio(self, conf, rtype='Recon'): """ read in the rtype radio configuration from conf and return parsed dict """ # don't bother if specified radio is not present if not conf.get(rtype, 'nic') in wifaces(): raise RuntimeError("Radio %s not present/not wireless" % conf.get(rtype, 'nic')) # get nic and set role setting default antenna config r = { 'nic': conf.get(rtype, 'nic'), 'spoofed': None, 'ant_gain': 0.0, 'ant_loss': 0.0, 'ant_offset': 0.0, 'ant_type': 0.0, 'desc': "unknown", 'scan_start': None, 'role': rtype.lower(), 'antennas': {} } # get optional properties if conf.has_option(rtype, 'spoof'): r['spoofed'] = conf.get(rtype, 'spoof') if conf.has_option(rtype, 'desc'): r['desc'] = conf.get(rtype, 'desc') # process antennas - get the number first try: nA = conf.getint(rtype, 'antennas') if conf.has_option( rtype, 'antennas') else 0 except ValueError: nA = 0 if nA: # antennas has been specified, force correct/valid specifications try: gain = map(float, conf.get(rtype, 'antenna_gain').split(',')) if len(gain) != nA: raise RuntimeError('gain') atype = conf.get(rtype, 'antenna_type').split(',') if len(atype) != nA: raise RuntimeError('type') loss = map(float, conf.get(rtype, 'antenna_loss').split(',')) if len(loss) != nA: raise RuntimeError('loss') rs = conf.get(rtype, 'antenna_xyz').split(',') xyz = [] for t in rs: xyz.append(tuple(map(int, t.split(':')))) if len(xyz) != nA: raise RuntimeError('xyz') except ConfigParser.NoOptionError as e: logging.warn("Antenna %s not specified" % e) #raise DySKTConfException("%s" % e) except ValueError as e: logging.warn("Invalid type for %s antenna configuration - %s") except RuntimeError as e: logging.warn( "Antenna %s has invalid number of specifications" % e) else: r['antennas']['num'] = nA r['antennas']['gain'] = gain r['antennas']['type'] = atype r['antennas']['loss'] = loss r['antennas']['xyz'] = xyz else: # none, set all empty r['antennas']['num'] = 0 r['antennas']['gain'] = [] r['antennas']['type'] = [] r['antennas']['loss'] = [] r['antennas']['xyz'] = [] # get scan pattern r['dwell'] = conf.getfloat(rtype, 'dwell') if r['dwell'] <= 0: raise ValueError("dwell must be > 0") r['scan'] = parsechlist(conf.get(rtype, 'scan'), 'scan') r['pass'] = parsechlist(conf.get(rtype, 'pass'), 'pass') if conf.has_option(rtype, 'scan_start'): try: scanspec = conf.get(rtype, 'scan_start') if ':' in scanspec: (ch, chw) = scanspec.split(':') else: ch = scanspec chw = None ch = int(ch) if ch else r['scan'][0][0] if not chw in iw.IW_CHWS: chw = r['scan'][0][1] r['scan_start'] = (ch, chw) if (ch, chw) in r['scan'] else r['scan'][0] except ValueError: # use default r['scan_start'] = r['scan'][0] else: r['scan_start'] = r['scan'][0] return r
def _create(self): """ create DySKT and member processes """ # read in and validate the conf file self._readconf() # intialize shared objects self._halt = mp.Event() # our stop event self._ic = mp.Queue() # comms for children self._pConns = {} # dict of connections to children # initialize children # Each child is expected to initialize in the _init_ function and throw # a RuntimeError failure logging.info("Initializing subprocess...") try: # start RTO first logging.info("Starting RTO") (conn1,conn2) = mp.Pipe() self._pConns['rto'] = conn1 self._rto = RTO(self._ic,conn2,self._conf) # set the region? if so, do it prior to starting the RadioController rd = self._conf['local']['region'] if rd: logging.info("Setting regulatory domain to %s",rd) self._rd = iw.regget() iw.regset(rd) if iw.regget() != rd: logging.warn("Regulatory domain %s may not have been set",rd) else: logging.info("Regulatory domain set to %s",rd) # recon radio is mandatory logging.info("Starting Reconnaissance Radio") (conn1,conn2) = mp.Pipe() self._pConns['recon'] = conn1 self._rr = RadioController(self._ic,conn2,self._conf['recon']) # collection if present if self._conf['collection']: try: logging.info("Starting Collection Radio") (conn1,conn2) = mp.Pipe() self._pConns['collection'] = conn1 self._cr = RadioController(self._ic,conn2,self._conf['collection']) except RuntimeError as e: # continue without collector, but log it logging.warning("Collection Radio (%s), continuing without",e) except RuntimeError as e: # e should have the form "Major:Minor:Description" ms = e.message.split(':') logging.error("%s (%s) %s",ms[0],ms[1],ms[2]) self._state = DYSKT_INVALID except Exception as e: # catchall logging.error(e) self._state = DYSKT_INVALID else: # start children execution self._state = DYSKT_CREATED self._rr.start() if self._cr: self._cr.start() self._rto.start()
class DySKT(object): """ DySKT - primary process of the Wraith sensor """ def __init__(self,conf=None): """ initialize variables """ # get parameters self._cpath = conf if conf else os.path.join(GPATH,'dyskt.conf') # internal variables self._state = DYSKT_INVALID # current state self._conf = {} # dyskt configuration dict self._halt = None # the stop event self._pConns = None # token pipes for children self._ic = None # internal comms queue self._rto = None # data collation/forwarding self._rr = None # recon radio self._cr = None # collection radio self._rd = None # regulatory domain def _create(self): """ create DySKT and member processes """ # read in and validate the conf file self._readconf() # intialize shared objects self._halt = mp.Event() # our stop event self._ic = mp.Queue() # comms for children self._pConns = {} # dict of connections to children # initialize children # Each child is expected to initialize in the _init_ function and throw # a RuntimeError failure logging.info("Initializing subprocess...") try: # start RTO first logging.info("Starting RTO") (conn1,conn2) = mp.Pipe() self._pConns['rto'] = conn1 self._rto = RTO(self._ic,conn2,self._conf) # set the region? if so, do it prior to starting the RadioController rd = self._conf['local']['region'] if rd: logging.info("Setting regulatory domain to %s",rd) self._rd = iw.regget() iw.regset(rd) if iw.regget() != rd: logging.warn("Regulatory domain %s may not have been set",rd) else: logging.info("Regulatory domain set to %s",rd) # recon radio is mandatory logging.info("Starting Reconnaissance Radio") (conn1,conn2) = mp.Pipe() self._pConns['recon'] = conn1 self._rr = RadioController(self._ic,conn2,self._conf['recon']) # collection if present if self._conf['collection']: try: logging.info("Starting Collection Radio") (conn1,conn2) = mp.Pipe() self._pConns['collection'] = conn1 self._cr = RadioController(self._ic,conn2,self._conf['collection']) except RuntimeError as e: # continue without collector, but log it logging.warning("Collection Radio (%s), continuing without",e) except RuntimeError as e: # e should have the form "Major:Minor:Description" ms = e.message.split(':') logging.error("%s (%s) %s",ms[0],ms[1],ms[2]) self._state = DYSKT_INVALID except Exception as e: # catchall logging.error(e) self._state = DYSKT_INVALID else: # start children execution self._state = DYSKT_CREATED self._rr.start() if self._cr: self._cr.start() self._rto.start() def _destroy(self): """ destroy DySKT cleanly """ # change our state self._state = DYSKT_EXITING # reset regulatory domain if necessary if self._rd: try: logging.info("Resetting regulatory domain") iw.regset(self._rd) if iw.regget() != self._rd: raise RuntimeError except: logging.warn("Failed to reset regulatory domain") # halt main execution loop & send out poison pills # put a token on the internal comms from us to break the RTO out of # any holding for data block logging.info("Stopping Sub-processes") self._halt.set() self._ic.put(('dyskt',time.time(),'!CHECK!',[])) for key in self._pConns: try: self._pConns[key].send('!STOP!') except IOError: # ignore any broken pipe errors pass self._pConns[key].close() while mp.active_children(): time.sleep(0.5) # change our state self._state = DYSKT_DESTROYED @property def state(self): return self._state def start(self): """ start execution """ # setup signal handlers for pause(s),resume(s),stop signal.signal(signal.SIGINT,self.stop) # CTRL-C and kill -INT stop signal.signal(signal.SIGTERM,self.stop) # kill -TERM stop # initialize, quit on failure logging.info("**** Starting DySKT %s ****" % dyskt.__version__) self._create() if self.state == DYSKT_INVALID: # make sure we do not leave system in corrupt state (i.e. no wireless nics) self._destroy() raise DySKTRuntimeException("DySKT failed to initialize, shutting down") # set state to running self._state = DYSKT_RUNNING # execution loop while not self._halt.is_set(): # get message a tuple: (level,originator,type,message) for key in self._pConns: try: if self._pConns[key].poll(): (l,o,t,m) = self._pConns[key].recv() if l == "err": # only process errors involved during execution if DYSKT_CREATED < self.state < DYSKT_EXITING: if o == 'collection': # allow collection radio to fail and still continue logging.warning("Collection radio dropped. Continuing...") self._pConns['collection'].send('!STOP!') self._pConns['collection'].close() del self._pConns['collection'] mp.active_children() else: logging.error("%s failed. (%s) %s",o,t,m) self.stop() elif l == "warn": logging.warning("%s: (%s) %s",o,t,m) elif l == "info": logging.info("%s: (%s) %s",o,t,m) except Exception as e: # blanke exception logging.error("DySKT failed. (Unknown) %s",e) time.sleep(1) # noinspection PyUnusedLocal def stop(self,signum=None,stack=None): """ stop execution """ if DYSKT_RUNNING <= self.state < DYSKT_EXITING: logging.info("**** Stopping DySKT ****") self._destroy() def _readconf(self): """ read in config file at cpath """ logging.info("Reading configuration file...") conf = ConfigParser.RawConfigParser() if not conf.read(self._cpath): raise DySKTConfException('%s is invalid' % self._cpath) # intialize conf to empty dict self._conf = {} try: # read in the recon radio configuration self._conf['recon'] = self._readradio(conf,'Recon') try: # catch any collection exceptions and log a warning if conf.has_section('Collection'): self._conf['collection'] = self._readradio(conf,'Collection') else: self._conf['collection'] = None logging.info("No collection radio specified") except (ConfigParser.NoSectionError,ConfigParser.NoOptionError, RuntimeError,ValueError): logging.warning("Invalid collection specification. Continuing without...") # GPS section self._conf['gps'] = {} self._conf['gps']['fixed'] = conf.getboolean('GPS','fixed') if self._conf['gps']['fixed']: self._conf['gps']['lat'] = conf.getfloat('GPS','lat') self._conf['gps']['lon'] = conf.getfloat('GPS','lon') self._conf['gps']['alt'] = conf.getfloat('GPS','alt') self._conf['gps']['dir'] = conf.getfloat('GPS','heading') else: self._conf['gps']['port'] = conf.getint('GPS','port') self._conf['gps']['id'] = conf.get('GPS','devid') self._conf['gps']['poll'] = conf.getfloat('GPS','poll') self._conf['gps']['epx'] = conf.getfloat('GPS','epx') self._conf['gps']['epy'] = conf.getfloat('GPS','epy') # Storage section self._conf['store'] = {'host':conf.get('Storage','host'), 'port':conf.getint('Storage','port')} # Local section self._conf['local'] = {'region':None,'c2c':None} if conf.has_option('Lcoal','C2C'): self._conf['local']['c2c'] = conf.getint('Local','C2C') if conf.has_option('Local','region'): reg = conf.get('Local','region') if len(reg) != 2: logging.warn("Regulatory domain %s is invalid" % reg) else: self._conf['local']['region'] = conf.get('Local','region') except (ConfigParser.NoSectionError,ConfigParser.NoOptionError) as e: raise DySKTConfException("%s" % e) except (RuntimeError,ValueError) as e: raise DySKTConfException("%s" % e) def _readradio(self,conf,rtype='Recon'): """ read in the rtype radio configuration from conf and return parsed dict """ # don't bother if specified radio is not present if not conf.get(rtype,'nic') in wifaces(): raise RuntimeError("Radio %s not present/not wireless" % conf.get(rtype,'nic')) # get nic and set role setting default antenna config r = {'nic':conf.get(rtype,'nic'), 'spoofed':None, 'ant_gain':0.0, 'ant_loss':0.0, 'ant_offset':0.0, 'ant_type':0.0, 'desc':"unknown", 'scan_start':None, 'role':rtype.lower(), 'antennas':{}} # get optional properties if conf.has_option(rtype,'spoof'): r['spoofed'] = conf.get(rtype,'spoof') if conf.has_option(rtype,'desc'): r['desc'] = conf.get(rtype,'desc') # process antennas - get the number first try: nA = conf.getint(rtype,'antennas') if conf.has_option(rtype,'antennas') else 0 except ValueError: nA = 0 if nA: # antennas has been specified, force correct/valid specifications try: gain = map(float,conf.get(rtype,'antenna_gain').split(',')) if len(gain) != nA: raise RuntimeError('gain') atype = conf.get(rtype,'antenna_type').split(',') if len(atype) != nA: raise RuntimeError('type') loss = map(float,conf.get(rtype,'antenna_loss').split(',')) if len(loss) != nA: raise RuntimeError('loss') rs = conf.get(rtype,'antenna_xyz').split(',') xyz = [] for t in rs: xyz.append(tuple(map(int,t.split(':')))) if len(xyz) != nA: raise RuntimeError('xyz') except ConfigParser.NoOptionError as e: logging.warn("Antenna %s not specified" % e) #raise DySKTConfException("%s" % e) except ValueError as e: logging.warn("Invalid type for %s antenna configuration - %s") except RuntimeError as e: logging.warn("Antenna %s has invalid number of specifications" % e) else: r['antennas']['num'] = nA r['antennas']['gain'] = gain r['antennas']['type'] = atype r['antennas']['loss'] = loss r['antennas']['xyz'] = xyz else: # none, set all empty r['antennas']['num'] = 0 r['antennas']['gain'] = [] r['antennas']['type'] = [] r['antennas']['loss'] = [] r['antennas']['xyz'] = [] # get scan pattern r['dwell'] = conf.getfloat(rtype,'dwell') if r['dwell'] <= 0: raise ValueError("dwell must be > 0") r['scan'] = parsechlist(conf.get(rtype,'scan'),'scan') r['pass'] = parsechlist(conf.get(rtype,'pass'),'pass') if conf.has_option(rtype,'scan_start'): try: scanspec = conf.get(rtype,'scan_start') if ':' in scanspec: (ch,chw) = scanspec.split(':') else: ch = scanspec chw = None ch = int(ch) if ch else r['scan'][0][0] if not chw in iw.IW_CHWS: chw = r['scan'][0][1] r['scan_start'] = (ch,chw) if (ch,chw) in r['scan'] else r['scan'][0] except ValueError: # use default r['scan_start'] = r['scan'][0] else: r['scan_start'] = r['scan'][0] return r
def _create(self): """ create DySKT and member processes """ # read in and validate the conf file self._readconf() # intialize shared objects self._halt = mp.Event() # our stop event self._ic = mp.Queue() # comms for children self._pConns = {} # dict of connections to children # initialize children # Each child is expected to initialize in the _init_ function and throw # a RuntimeError on failure. For non-mandatory components, all exceptions # are caught in in inner exceptions, mandatory are caught in the the outer # exception and result in shutting down logging.info("Initializing subprocess...") try: # start RTO first (IOT accept comms from the radios) logging.info("Starting RTO...") (conn1,conn2) = mp.Pipe() self._pConns['rto'] = conn1 self._rto = RTO(self._ic,conn2,self._conf) logging.info("RTO started") # set the region? if so, do it prior to starting the radio(s) rd = self._conf['local']['region'] if rd: logging.info("Setting regulatory domain to %s...",rd) self._rd = iw.regget() iw.regset(rd) if iw.regget() != rd: logging.warning("Regulatory domain %s may not have been set",rd) else: logging.info("Regulatory domain set to %s",rd) # recon radio is mandatory mode = 'paused' if self._conf['recon']['paused'] else 'scan' logging.info("Starting Reconnaissance Radio...") (conn1,conn2) = mp.Pipe() self._pConns['recon'] = conn1 self._rr = RadioController(self._ic,conn2,self._conf['recon']) logging.info("Reconnaissance Radio started in %s mode",mode) # collection if present if self._conf['collection']: try: mode = 'paused' if self._conf['collection']['paused'] else 'scan' logging.info("Starting Collection Radio...") (conn1,conn2) = mp.Pipe() self._pConns['collection'] = conn1 self._cr = RadioController(self._ic,conn2,self._conf['collection']) except RuntimeError as e: # continue without collector, but log it logging.warning("Collection Radio failed: %s, continuing w/out",e) else: logging.info("Collection Radio started in %s mode",mode) # c2c socket -> on failure log it and keep going logging.info("Starting C2C...") try: (conn1,conn2) = mp.Pipe() self._pConns['c2c'] = conn1 self._c2c = C2C(conn2,self._conf['local']['c2c']) self._c2c.start() except Exception as e: logging.warn("C2C failed: %s, continuing without" % e) else: logging.info("C2C listening on port %d" % self._conf['local']['c2c']) except RuntimeError as e: # e should have the form "Major:Minor:Description" ms = e.message.split(':') logging.error("%s (%s) %s",ms[0],ms[1],ms[2]) self._state = DYSKT_INVALID except Exception as e: # catchall logging.error(e) self._state = DYSKT_INVALID else: # start children execution self._state = DYSKT_CREATED self._rr.start() if self._cr: self._cr.start() self._rto.start()
class DySKT(object): """ DySKT - primary process of the Wraith sensor """ def __init__(self,conf=None): """ initialize variables """ # get parameters self._cpath = conf if conf else os.path.join(GPATH,'dyskt.conf') # internal variables self._state = DYSKT_INVALID # current state self._conf = {} # dyskt configuration dict self._halt = None # the stop event self._pConns = None # token pipes for children (& c2c) self._ic = None # internal comms queue self._rto = None # data collation/forwarding self._rr = None # recon radio self._cr = None # collection radio self._rd = None # regulatory domain self._c2c = None # our c2c @property def state(self): return self._state def run(self): """ start execution """ # setup signal handlers for pause(s),resume(s),stop signal.signal(signal.SIGINT,self.stop) # CTRL-C and kill -INT stop signal.signal(signal.SIGTERM,self.stop) # kill -TERM stop # initialize, quit on failure logging.info("**** Starting DySKT %s ****",dyskt.__version__) self._create() if self.state == DYSKT_INVALID: # make sure we do not leave system in corrupt state (i.e. w/o nics) self._destroy() raise DySKTRuntimeException("DySKT failed to initialize, shutting down") # set state to running self._state = DYSKT_RUNNING # execution loop while not self._halt.is_set(): # get message(s) a tuple: (level,originator,type,message) from children rs = [] # hide pycharm unreferenced variable warning try: rs,_,_ = select.select(self._pConns.values(),[],[],0.5) except select.error as e: # hide (4,'Interupted system call') errors if e[0] == 4: continue for r in rs: try: l,o,t,m = r.recv() if l == 'err': # only process errors invovled during execution if DYSKT_CREATED < self.state < DYSKT_EXITING: # continue w/out collection if it fails if o == 'collection': logging.warning("Collection radio dropped: %s",m) self._pConns['collection'].send('!STOP!') self._pConns['collection'].close() del self._pConns['collection'] mp.active_children() self._cr = None else: logging.error("%s failed (%) %s",o,t,m) else: logging.warning("Uninitiated error (%) %s from %s",t,m,o) elif l == 'warn': logging.warning("%s: (%s) %s",o,t,m) elif l == 'info': logging.info("%s: (%s) %s",o,t,m) elif l == 'cmd': cid,cmd,rdos,ps = self._processcmd(m) if cid is None: continue for rdo in rdos: logging.info("Client sent %s to %s",cmd,rdo) self._pConns[rdo].send('%s:%d:%s' % (cmd,cid,'-'.join(ps))) elif l == 'cmderr': self._pConns['c2c'].send("ERR %d \001%s\001\n" % (t,m)) elif l == 'cmdack': self._pConns['c2c'].send("OK %d \001%s\001\n" % (t,m)) except Exception as e: # blanket catch all logging.error("DySKT failed. (Unknown) %s->%s", type(e),e) # noinspection PyUnusedLocal def stop(self,signum=None,stack=None): """ stop execution """ if DYSKT_RUNNING <= self.state < DYSKT_EXITING: logging.info("**** Stopping DySKT ****") self._destroy() #### PRIVATE HELPERS def _create(self): """ create DySKT and member processes """ # read in and validate the conf file self._readconf() # intialize shared objects self._halt = mp.Event() # our stop event self._ic = mp.Queue() # comms for children self._pConns = {} # dict of connections to children # initialize children # Each child is expected to initialize in the _init_ function and throw # a RuntimeError on failure. For non-mandatory components, all exceptions # are caught in in inner exceptions, mandatory are caught in the the outer # exception and result in shutting down logging.info("Initializing subprocess...") try: # start RTO first (IOT accept comms from the radios) logging.info("Starting RTO...") (conn1,conn2) = mp.Pipe() self._pConns['rto'] = conn1 self._rto = RTO(self._ic,conn2,self._conf) logging.info("RTO started") # set the region? if so, do it prior to starting the radio(s) rd = self._conf['local']['region'] if rd: logging.info("Setting regulatory domain to %s...",rd) self._rd = iw.regget() iw.regset(rd) if iw.regget() != rd: logging.warning("Regulatory domain %s may not have been set",rd) else: logging.info("Regulatory domain set to %s",rd) # recon radio is mandatory mode = 'paused' if self._conf['recon']['paused'] else 'scan' logging.info("Starting Reconnaissance Radio...") (conn1,conn2) = mp.Pipe() self._pConns['recon'] = conn1 self._rr = RadioController(self._ic,conn2,self._conf['recon']) logging.info("Reconnaissance Radio started in %s mode",mode) # collection if present if self._conf['collection']: try: mode = 'paused' if self._conf['collection']['paused'] else 'scan' logging.info("Starting Collection Radio...") (conn1,conn2) = mp.Pipe() self._pConns['collection'] = conn1 self._cr = RadioController(self._ic,conn2,self._conf['collection']) except RuntimeError as e: # continue without collector, but log it logging.warning("Collection Radio failed: %s, continuing w/out",e) else: logging.info("Collection Radio started in %s mode",mode) # c2c socket -> on failure log it and keep going logging.info("Starting C2C...") try: (conn1,conn2) = mp.Pipe() self._pConns['c2c'] = conn1 self._c2c = C2C(conn2,self._conf['local']['c2c']) self._c2c.start() except Exception as e: logging.warn("C2C failed: %s, continuing without" % e) else: logging.info("C2C listening on port %d" % self._conf['local']['c2c']) except RuntimeError as e: # e should have the form "Major:Minor:Description" ms = e.message.split(':') logging.error("%s (%s) %s",ms[0],ms[1],ms[2]) self._state = DYSKT_INVALID except Exception as e: # catchall logging.error(e) self._state = DYSKT_INVALID else: # start children execution self._state = DYSKT_CREATED self._rr.start() if self._cr: self._cr.start() self._rto.start() def _destroy(self): """ destroy DySKT cleanly """ # change our state self._state = DYSKT_EXITING # reset regulatory domain if necessary if self._rd: try: logging.info("Resetting regulatory domain...") iw.regset(self._rd) if iw.regget() != self._rd: raise RuntimeError except: logging.warning("Failed to reset regulatory domain") else: logging.info("Regulatory domain reset") # halt main execution loop & send out poison pills logging.info("Stopping Sub-processes...") self._halt.set() for key in self._pConns: try: self._pConns[key].send('!STOP!') self._pConns[key].close() except IOError: # ignore any broken pipe errors pass # active_children has the side effect of joining the processes while mp.active_children(): time.sleep(0.5) while self._c2c.is_alive(): self._c2c.join(0.5) logging.info("Sub-processes stopped") # change our state self._state = DYSKT_DESTROYED def _readconf(self): """ read in config file at cpath """ logging.info("Reading configuration file...") conf = ConfigParser.RawConfigParser() if not conf.read(self._cpath): raise DySKTConfException('%s is invalid' % self._cpath) # intialize conf to empty dict self._conf = {} try: # read in the recon radio configuration self._conf['recon'] = self._readradio(conf,'Recon') try: # catch any collection exceptions and log a warning if conf.has_section('Collection'): self._conf['collection'] = self._readradio(conf,'Collection') else: self._conf['collection'] = None logging.info("No collection radio specified") except (ConfigParser.NoSectionError,ConfigParser.NoOptionError, RuntimeError,ValueError): logging.warning("Invalid collection specification. Continuing without...") # GPS section self._conf['gps'] = {} self._conf['gps']['fixed'] = conf.getboolean('GPS','fixed') if self._conf['gps']['fixed']: self._conf['gps']['lat'] = conf.getfloat('GPS','lat') self._conf['gps']['lon'] = conf.getfloat('GPS','lon') self._conf['gps']['alt'] = conf.getfloat('GPS','alt') self._conf['gps']['dir'] = conf.getfloat('GPS','heading') else: self._conf['gps']['port'] = conf.getint('GPS','port') self._conf['gps']['id'] = conf.get('GPS','devid') self._conf['gps']['poll'] = conf.getfloat('GPS','poll') self._conf['gps']['epx'] = conf.getfloat('GPS','epx') self._conf['gps']['epy'] = conf.getfloat('GPS','epy') # Storage section self._conf['store'] = {'host':conf.get('Storage','host'), 'port':conf.getint('Storage','port')} # Local section self._conf['local'] = {'region':None,'c2c':2526} # c2c if conf.has_option('Local','C2C'): try: self._conf['local']['c2c'] = conf.getint('Local','C2C') except: logging.warning("Invalid C2C port specification. Using default") # region if conf.has_option('Local','region'): reg = conf.get('Local','region') if len(reg) != 2: logging.warning("Regulatory domain %s is invalid",reg) else: self._conf['local']['region'] = conf.get('Local','region') except (ConfigParser.NoSectionError,ConfigParser.NoOptionError) as e: raise DySKTConfException("%s" % e) except (RuntimeError,ValueError) as e: raise DySKTConfException("%s" % e) # noinspection PyMethodMayBeStatic def _readradio(self,conf,rtype='Recon'): """ read in the rtype radio configuration from conf and return parsed dict """ # don't bother if specified radio is not present if not conf.get(rtype,'nic') in wifaces(): raise RuntimeError("Radio %s not present/not wireless" % conf.get(rtype,'nic')) # get nic and set role setting default antenna config r = {'nic':conf.get(rtype,'nic'), 'paused':False, 'spoofed':None, 'ant_gain':0.0, 'ant_loss':0.0, 'ant_offset':0.0, 'ant_type':0.0, 'desc':"unknown", 'scan_start':None, 'role':rtype.lower(), 'antennas':{}} # get optional properties if conf.has_option(rtype,'spoof'): spoof = conf.get(rtype,'spoof').upper() if re.match(MACADDR,spoof) is None: logging.warn("Invalid spoofed MAC addr %s specified",spoof) else: r['spoofed'] = spoof if conf.has_option(rtype,'desc'): r['desc'] = conf.get(rtype,'desc') if conf.has_option(rtype,'paused'): r['paused'] = conf.getboolean(rtype,'paused') # process antennas - get the number first try: nA = conf.getint(rtype,'antennas') if conf.has_option(rtype,'antennas') else 0 except ValueError: nA = 0 if nA: # antennas has been specified, warn (and ignore) invalid specifications try: gain = map(float,conf.get(rtype,'antenna_gain').split(',')) if len(gain) != nA: raise RuntimeError('gain') atype = conf.get(rtype,'antenna_type').split(',') if len(atype) != nA: raise RuntimeError('type') loss = map(float,conf.get(rtype,'antenna_loss').split(',')) if len(loss) != nA: raise RuntimeError('loss') rs = conf.get(rtype,'antenna_xyz').split(',') xyz = [] for t in rs: xyz.append(tuple(map(int,t.split(':')))) if len(xyz) != nA: raise RuntimeError('xyz') except ConfigParser.NoOptionError as e: logging.warning("Antenna %s not specified",e) except ValueError as e: logging.warning("Invalid type %s for %s antenna configuration",e,rtype) except RuntimeError as e: logging.warning("Antenna %s has invalid number of specifications",e) else: r['antennas']['num'] = nA r['antennas']['gain'] = gain r['antennas']['type'] = atype r['antennas']['loss'] = loss r['antennas']['xyz'] = xyz else: # none, set all empty r['antennas']['num'] = 0 r['antennas']['gain'] = [] r['antennas']['type'] = [] r['antennas']['loss'] = [] r['antennas']['xyz'] = [] # get scan pattern r['dwell'] = conf.getfloat(rtype,'dwell') if r['dwell'] <= 0: raise ValueError("dwell must be > 0") r['scan'] = parsechlist(conf.get(rtype,'scan'),'scan') r['pass'] = parsechlist(conf.get(rtype,'pass'),'pass') if conf.has_option(rtype,'scan_start'): try: scanspec = conf.get(rtype,'scan_start') if ':' in scanspec: (ch,chw) = scanspec.split(':') else: ch = scanspec chw = None ch = int(ch) if ch else r['scan'][0][0] if not chw in iw.IW_CHWS: chw = r['scan'][0][1] r['scan_start'] = (ch,chw) if (ch,chw) in r['scan'] else r['scan'][0] except ValueError: # use default r['scan_start'] = r['scan'][0] else: r['scan_start'] = r['scan'][0] return r def _processcmd(self,msg): """ parse and process command from c2c socket """ # ensure there are at least 3 tokens tkns = None try: tkns = msg.split(' ') except: return None,None,None,None if len(tkns) < 3: self._pConns['c2c'].send("ERR ? \001invalid command format\001\n") return None,None,None,None # and a cmdid is present cmdid = None try: cmdid = int(tkns[0].replace('!','')) except: self._pConns['c2c'].send("ERR ? \001invalid command format\001\n") return None,None,None,None # ATT we can error/ack with associated cmd id cmd = tkns[1].strip().lower() if cmd not in ['state','scan','hold','listen','pause','txpwr','spoof']: self._pConns['c2c'].send("ERR %d \001invalid command\001\n" % cmdid) return None,None,None,None if cmd == 'txpwr' or cmd == 'spoof': self._pConns['c2c'].send("ERR %d \001%s not implemented\001\n" % (cmdid,cmd)) return None,None,None,None radios = [] radio = tkns[2].strip().lower() if radio not in ['both','all','recon','collection']: self._pConns['c2c'].send("ERR %d \001invalid radio specification: %s \001\n" % (cmdid,radio)) return None,None,None,None if radio == 'all': radios = ['recon'] if self._cr is not None: radios.append('collection') elif radio == 'both': radios = ['recon','collection'] else: radios = [radio] if 'collection' in radios and self._cr is None: self._pConns['c2c'].send("ERR %d \001collection radio not present\001\n" % cmdid) return None,None,None,None # ensure any params are valid ps = [] if cmd in ['listen','txpwr','spoof']: try: ps = tkns[3].strip().split(':') if len(ps) not in [1,2,6]: raise RuntimeError if cmd == 'listen': # listen will be ch:chw or ch only ps[0] = int(ps[0]) # ensure chwidth is correct or not specified set to None if len(ps) == 2: if ps[1] not in ['HT20','HT40+','HT40-']: raise RuntimeError else: ps.append(None) elif cmd == 'txpwr': # txpwr will be pwr:opt ps[0] = int(ps[0]) if ps[1] not in ['fixed','auto','limit']: raise RuntimeError elif cmd == 'spoof': # spoof will be macaddr if len(ps) != 6: raise RuntimeError ps = [ps.join(':')] # rejoin the mac if re.match(MACADDR,ps[0].upper()) is None: raise RuntimeError except: self._pConns['c2c'].send("ERR %d \001invalid params\001\n" % cmdid) return None,None,None,None # all good return cmdid,cmd,radios,ps