def run(self): # Init DATA interface with TRX or L1 if self.argv.conn_mode == "TRX": self.data_if = DATAInterface( self.argv.remote_addr, self.argv.base_port + 2, self.argv.bind_addr, self.argv.base_port + 102) elif self.argv.conn_mode == "L1": self.data_if = DATAInterface( self.argv.remote_addr, self.argv.base_port + 102, self.argv.bind_addr, self.argv.base_port + 2) # Read messages from the capture messages = self.ddf.parse_all( skip = self.argv.cnt_skip, count = self.argv.cnt_count) if messages is False: log.error("Parsing failed, nothing to send") sys.exit(1) for msg in messages: # Pass filter if not self.msg_pass_filter(msg): continue log.info("Sending a burst %s to %s..." % (msg.desc_hdr(), self.argv.conn_mode)) # Send message self.data_if.send_msg(msg)
def run(self): # Init DATA interface with TRX or L1 if self.conn_mode == "TRX": self.data_if = DATAInterface(self.remote_addr, self.base_port + 2, self.bind_addr, self.base_port + 102) l12trx = True elif self.conn_mode == "L1": self.data_if = DATAInterface(self.remote_addr, self.base_port + 102, self.bind_addr, self.base_port + 2) l12trx = False else: self.print_help("[!] Unknown connection type") sys.exit(2) # Read messages from the capture messages = self.ddf.parse_all( skip = self.msg_skip, count = self.msg_count) if messages is False: pass # FIXME!!! for msg in messages: # Pass filter if not self.msg_pass_filter(l12trx, msg): continue print("[i] Sending a burst %s to %s..." % (msg.desc_hdr(), self.conn_mode)) # Send message self.data_if.send_msg(msg)
def __init__(self, bind_addr, remote_addr, base_port, name=None, child_idx=0, clck_gen=None, pwr_meas=None): # Connection info self.remote_addr = remote_addr self.bind_addr = bind_addr self.base_port = base_port self.child_idx = child_idx # Meta info self.name = name log.info("Init transceiver '%s'" % self) # Child transceiver cannot have its own clock if clck_gen is not None and child_idx > 0: raise TypeError("Child transceiver cannot have its own clock") # Init DATA interface self.data_if = DATAInterface(remote_addr, base_port + child_idx * 2 + 102, bind_addr, base_port + child_idx * 2 + 2) # Init CTRL interface self.ctrl_if = CTRLInterfaceTRX(self, remote_addr, base_port + child_idx * 2 + 101, bind_addr, base_port + child_idx * 2 + 1) # Init optional CLCK interface self.clck_gen = clck_gen if clck_gen is not None: self.clck_if = UDPLink(remote_addr, base_port + 100, bind_addr, base_port) # Optional Power Measurement interface self.pwr_meas = pwr_meas # Internal state self.running = False # Actual RX / TX frequencies self._rx_freq = None self._tx_freq = None # Frequency hopping parameters (set by CTRL) self.fh = None # List of active (configured) timeslots self.ts_list = [] # List of child transceivers self.child_trx_list = TRXList()
def __init__(self, bind_addr, remote_addr, base_port, clck_gen=None, pwr_meas=None): # Connection info self.remote_addr = remote_addr self.bind_addr = bind_addr self.base_port = base_port # Init DATA interface self.data_if = DATAInterface(remote_addr, base_port + 102, bind_addr, base_port + 2) # Init CTRL interface self.ctrl_if = CTRLInterfaceTRX(self, remote_addr, base_port + 101, bind_addr, base_port + 1) # Init optional CLCK interface self.clck_gen = clck_gen if clck_gen is not None: self.clck_if = UDPLink(remote_addr, base_port + 100, bind_addr, base_port) # Optional Power Measurement interface self.pwr_meas = pwr_meas # Internal state self.running = False # Actual RX / TX frequencies self.rx_freq = None self.tx_freq = None # List of active (configured) timeslots self.ts_list = []
def __init__(self, bind_addr, remote_addr, base_port, name = None, child_idx = 0, clck_gen = None, pwr_meas = None): # Connection info self.remote_addr = remote_addr self.bind_addr = bind_addr self.base_port = base_port self.child_idx = child_idx # Meta info self.name = name log.info("Init transceiver '%s'" % self) # Child transceiver cannot have its own clock if clck_gen is not None and child_idx > 0: raise TypeError("Child transceiver cannot have its own clock") # Init DATA interface self.data_if = DATAInterface( remote_addr, base_port + child_idx * 2 + 102, bind_addr, base_port + child_idx * 2 + 2) # Init CTRL interface self.ctrl_if = CTRLInterfaceTRX(self, remote_addr, base_port + child_idx * 2 + 101, bind_addr, base_port + child_idx * 2 + 1) # Init optional CLCK interface self.clck_gen = clck_gen if clck_gen is not None: self.clck_if = UDPLink( remote_addr, base_port + 100, bind_addr, base_port) # Optional Power Measurement interface self.pwr_meas = pwr_meas # Internal state self.running = False # Actual RX / TX frequencies self.rx_freq = None self.tx_freq = None # List of active (configured) timeslots self.ts_list = [] # List of child transceivers self.child_trx_list = TRXList()
def __init__(self, bind_addr, remote_addr, base_port, clck_gen = None, pwr_meas = None): # Connection info self.remote_addr = remote_addr self.bind_addr = bind_addr self.base_port = base_port # Init DATA interface self.data_if = DATAInterface( remote_addr, base_port + 102, bind_addr, base_port + 2) # Init CTRL interface self.ctrl_if = CTRLInterfaceTRX(self, remote_addr, base_port + 101, bind_addr, base_port + 1) # Init optional CLCK interface self.clck_gen = clck_gen if clck_gen is not None: self.clck_if = UDPLink( remote_addr, base_port + 100, bind_addr, base_port) # Optional Power Measurement interface self.pwr_meas = pwr_meas # Internal state self.running = False # Actual RX / TX frequencies self.rx_freq = None self.tx_freq = None # List of active (configured) timeslots self.ts_list = []
class Transceiver: """ Base transceiver implementation. Represents a single transceiver, that can be used as for the BTS side, as for the MS side. Each individual instance of Transceiver unifies three basic interfaces built on three independent UDP connections: - CLCK (base port + 100/0) - clock indications from TRX to L1, - CTRL (base port + 101/1) - control interface for L1, - DATA (base port + 102/2) - bidirectional data interface for bursts. A transceiver can be either in active (i.e. working), or in idle mode. The active mode should ensure that both RX/TX frequencies are set. NOTE: CLCK is not required for some L1 implementations, so it is optional. == Timeslot configuration Transceiver has a list of active (i.e. configured) TDMA timeslots. The L1 should configure a timeslot before sending or expecting any data on it. This is done by SETSLOT control command, which also indicates an associated channel combination (see GSM TS 05.02). NOTE: we don't store the associated channel combinations, as they are only useful for burst detection and demodulation. == Clock distribution (optional) The clock indications are not expected by L1 when transceiver is not running, so we monitor both POWERON / POWEROFF events from the control interface, and keep the list of CLCK links in a given CLCKGen instance updated. The clock generator is started and stopped automatically. NOTE: a single instance of CLCKGen can be shared between multiple transceivers, as well as multiple transceivers may use individual CLCKGen instances. == Power Measurement (optional) Transceiver may have an optional power measurement interface, that shall provide at least one method: measure(freq). This is required for the MS side (i.e. OsmocomBB). """ def __init__(self, bind_addr, remote_addr, base_port, clck_gen = None, pwr_meas = None): # Connection info self.remote_addr = remote_addr self.bind_addr = bind_addr self.base_port = base_port # Init DATA interface self.data_if = DATAInterface( remote_addr, base_port + 102, bind_addr, base_port + 2) # Init CTRL interface self.ctrl_if = CTRLInterfaceTRX(self, remote_addr, base_port + 101, bind_addr, base_port + 1) # Init optional CLCK interface self.clck_gen = clck_gen if clck_gen is not None: self.clck_if = UDPLink( remote_addr, base_port + 100, bind_addr, base_port) # Optional Power Measurement interface self.pwr_meas = pwr_meas # Internal state self.running = False # Actual RX / TX frequencies self.rx_freq = None self.tx_freq = None # List of active (configured) timeslots self.ts_list = [] # To be overwritten if required, # no custom command handlers by default def ctrl_cmd_handler(self, request): return None def power_event_handler(self, event): # Trigger clock generator if required if self.clck_gen is not None: clck_links = self.clck_gen.clck_links if not self.running and (self.clck_if in clck_links): # Transceiver was stopped clck_links.remove(self.clck_if) elif self.running and (self.clck_if not in clck_links): # Transceiver was started clck_links.append(self.clck_if) if not self.clck_gen.timer and len(clck_links) > 0: log.info("Starting clock generator") self.clck_gen.start() elif self.clck_gen.timer and not clck_links: log.info("Stopping clock generator") self.clck_gen.stop() def recv_data_msg(self): # Read and parse data from socket msg = self.data_if.recv_l12trx_msg() if not msg: return None # Make sure that transceiver is configured and running if not self.running: log.warning("RX DATA message (%s), but transceiver " "is not running => dropping..." % msg.desc_hdr()) return None # Make sure that indicated timeslot is configured if msg.tn not in self.ts_list: log.warning("RX DATA message (%s), but timeslot " "is not configured => dropping..." % msg.desc_hdr()) return None return msg
class Application: # Application variables remote_addr = "127.0.0.1" bind_addr = "0.0.0.0" base_port = 5700 conn_mode = "TRX" output_file = None burst_type = None burst_count = 1 # Common header fields fn = None tn = None # Message specific header fields toa256 = None rssi = None pwr = None def __init__(self): print_copyright(CR_HOLDERS) self.parse_argv() self.check_argv() # Set up signal handlers signal.signal(signal.SIGINT, self.sig_handler) # Open requested capture file if self.output_file is not None: self.ddf = DATADumpFile(self.output_file) def run(self): # Init DATA interface with TRX or L1 if self.conn_mode == "TRX": self.data_if = DATAInterface(self.remote_addr, self.base_port + 2, self.bind_addr, self.base_port + 102) elif self.conn_mode == "L1": self.data_if = DATAInterface(self.remote_addr, self.base_port + 102, self.bind_addr, self.base_port + 2) # Init random burst generator burst_gen = RandBurstGen() # Init an empty DATA message if self.conn_mode == "TRX": msg = DATAMSG_L12TRX() elif self.conn_mode == "L1": msg = DATAMSG_TRX2L1() # Generate a random frame number or use provided one fn_init = msg.rand_fn() if self.fn is None else self.fn # Send as much bursts as required for i in range(self.burst_count): # Randomize the message header msg.rand_hdr() # Increase and set frame number msg.fn = (fn_init + i) % GSM_HYPERFRAME # Set timeslot number if self.tn is not None: msg.tn = self.tn # Set transmit power level if self.pwr is not None: msg.pwr = self.pwr # Set time of arrival if self.toa256 is not None: msg.toa256 = self.toa256 # Set RSSI if self.rssi is not None: msg.rssi = self.rssi # Generate a random burst if self.burst_type == "NB": burst = burst_gen.gen_nb() elif self.burst_type == "FB": burst = burst_gen.gen_fb() elif self.burst_type == "SB": burst = burst_gen.gen_sb() elif self.burst_type == "AB": burst = burst_gen.gen_ab() # Convert to soft-bits in case of TRX -> L1 message if self.conn_mode == "L1": burst = msg.ubit2sbit(burst) # Set burst msg.burst = burst print("[i] Sending %d/%d %s burst %s to %s..." % (i + 1, self.burst_count, self.burst_type, msg.desc_hdr(), self.conn_mode)) # Send message self.data_if.send_msg(msg) # Append a new message to the capture if self.output_file is not None: self.ddf.append_msg(msg) def print_help(self, msg = None): s = " Usage: " + sys.argv[0] + " [options]\n\n" \ " Some help...\n" \ " -h --help this text\n\n" s += " TRX interface specific\n" \ " -o --output-file Write bursts to a capture file\n" \ " -m --conn-mode Send bursts to: TRX (default) / L1\n" \ " -r --remote-addr Set remote address (default %s)\n" \ " -b --bind-addr Set local address (default %s)\n" \ " -p --base-port Set base port number (default %d)\n\n" s += " Burst generation\n" \ " -b --burst-type Random burst type (NB, FB, SB, AB)\n" \ " -c --burst-count How much bursts to send (default 1)\n" \ " -f --frame-number Set frame number (default random)\n" \ " -t --timeslot Set timeslot index (default random)\n" \ " --pwr Set power level (default random)\n" \ " --rssi Set RSSI (default random)\n" \ " --toa Set ToA in symbols (default random)\n" \ " --toa256 Set ToA in 1/256 symbol periods\n" print(s % (self.remote_addr, self.bind_addr, self.base_port)) if msg is not None: print(msg) def parse_argv(self): try: opts, args = getopt.getopt(sys.argv[1:], "o:m:r:b:p:b:c:f:t:h", [ "help", "output-file=" "conn-mode=", "remote-addr=", "bind-addr=", "base-port=", "burst-type=", "burst-count=", "frame-number=", "timeslot=", "rssi=", "toa=", "toa256=", "pwr=", ]) except getopt.GetoptError as err: self.print_help("[!] " + str(err)) sys.exit(2) for o, v in opts: if o in ("-h", "--help"): self.print_help() sys.exit(2) elif o in ("-o", "--output-file"): self.output_file = v elif o in ("-m", "--conn-mode"): self.conn_mode = v elif o in ("-r", "--remote-addr"): self.remote_addr = v elif o in ("-b", "--bind-addr"): self.bind_addr = v elif o in ("-p", "--base-port"): self.base_port = int(v) elif o in ("-b", "--burst-type"): self.burst_type = v elif o in ("-c", "--burst-count"): self.burst_count = int(v) elif o in ("-f", "--frame-number"): self.fn = int(v) elif o in ("-t", "--timeslot"): self.tn = int(v) # Message specific header fields elif o == "--pwr": self.pwr = int(v) elif o == "--rssi": self.rssi = int(v) elif o == "--toa256": self.toa256 = int(v) elif o == "--toa": self.toa256 = int(float(v) * 256.0 + 0.5) def check_argv(self): # Check connection mode if self.conn_mode not in ("TRX", "L1"): self.print_help("[!] Unknown connection type") sys.exit(2) # Check connection mode if self.burst_type not in ("NB", "FB", "SB", "AB"): self.print_help("[!] Unknown burst type") sys.exit(2) def sig_handler(self, signum, frame): print("Signal %d received" % signum) if signum is signal.SIGINT: sys.exit(0)
def run(self): # Init DATA interface with TRX or L1 if self.argv.conn_mode == "TRX": self.data_if = DATAInterface(self.argv.remote_addr, self.argv.base_port + 2, self.argv.bind_addr, self.argv.base_port + 102) elif self.argv.conn_mode == "L1": self.data_if = DATAInterface(self.argv.remote_addr, self.argv.base_port + 102, self.argv.bind_addr, self.argv.base_port + 2) # Init random burst generator burst_gen = RandBurstGen() # Init an empty DATA message if self.argv.conn_mode == "TRX": msg = DATAMSG_L12TRX() elif self.argv.conn_mode == "L1": msg = DATAMSG_TRX2L1() # Generate a random frame number or use provided one fn_init = msg.rand_fn() if self.argv.tdma_fn is None \ else self.argv.tdma_fn # Send as much bursts as required for i in range(self.argv.burst_count): # Randomize the message header msg.rand_hdr() # Increase and set frame number msg.fn = (fn_init + i) % GSM_HYPERFRAME # Set timeslot number if self.argv.tdma_tn is not None: msg.tn = self.argv.tdma_tn # Set transmit power level if self.argv.pwr is not None: msg.pwr = self.argv.pwr # Set time of arrival if self.argv.toa is not None: msg.toa256 = int(float(self.argv.toa) * 256.0 + 0.5) elif self.argv.toa256 is not None: msg.toa256 = self.argv.toa256 # Set RSSI if self.argv.rssi is not None: msg.rssi = self.argv.rssi # Generate a random burst if self.argv.burst_type == "NB": burst = burst_gen.gen_nb() elif self.argv.burst_type == "FB": burst = burst_gen.gen_fb() elif self.argv.burst_type == "SB": burst = burst_gen.gen_sb() elif self.argv.burst_type == "AB": burst = burst_gen.gen_ab() # Convert to soft-bits in case of TRX -> L1 message if self.argv.conn_mode == "L1": burst = msg.ubit2sbit(burst) # Set burst msg.burst = burst log.info("Sending %d/%d %s burst %s to %s..." % (i + 1, self.argv.burst_count, self.argv.burst_type, msg.desc_hdr(), self.argv.conn_mode)) # Send message self.data_if.send_msg(msg) # Append a new message to the capture if self.argv.output_file is not None: self.ddf.append_msg(msg)
class Transceiver: """ Base transceiver implementation. Represents a single transceiver, that can be used as for the BTS side, as for the MS side. Each individual instance of Transceiver unifies three basic interfaces built on three independent UDP connections: - CLCK (base port + 100/0) - clock indications from TRX to L1, - CTRL (base port + 101/1) - control interface for L1, - DATA (base port + 102/2) - bidirectional data interface for bursts. A transceiver can be either in active (i.e. working), or in idle mode. The active mode should ensure that both RX/TX frequencies are set. NOTE: CLCK is not required for some L1 implementations, so it is optional. == Timeslot configuration Transceiver has a list of active (i.e. configured) TDMA timeslots. The L1 should configure a timeslot before sending or expecting any data on it. This is done by SETSLOT control command, which also indicates an associated channel combination (see GSM TS 05.02). NOTE: we don't store the associated channel combinations, as they are only useful for burst detection and demodulation. == Child transceivers A BTS can (optionally) have more than one transceiver. In this case additional (let's say child) transceivers basically share the same clock source of the first transceiver, so UDP port mapping is a bit different, for example: (trx_0) clck=5700, ctrl=5701, data=5702, (trx_1) ctrl=5703, data=5704, (trx_2) ctrl=5705, data=5706. ... As soon as the first transceiver is powered on / off, all child transceivers are also powered on / off. == Clock distribution (optional) The clock indications are not expected by L1 when transceiver is not running, so we monitor both POWERON / POWEROFF events from the control interface, and keep the list of CLCK links in a given CLCKGen instance updated. The clock generator is started and stopped automatically. NOTE: a single instance of CLCKGen can be shared between multiple transceivers, as well as multiple transceivers may use individual CLCKGen instances. == Power Measurement (optional) Transceiver may have an optional power measurement interface, that shall provide at least one method: measure(freq). This is required for the MS side (i.e. OsmocomBB). """ def __init__(self, bind_addr, remote_addr, base_port, name=None, child_idx=0, clck_gen=None, pwr_meas=None): # Connection info self.remote_addr = remote_addr self.bind_addr = bind_addr self.base_port = base_port self.child_idx = child_idx # Meta info self.name = name log.info("Init transceiver '%s'" % self) # Child transceiver cannot have its own clock if clck_gen is not None and child_idx > 0: raise TypeError("Child transceiver cannot have its own clock") # Init DATA interface self.data_if = DATAInterface(remote_addr, base_port + child_idx * 2 + 102, bind_addr, base_port + child_idx * 2 + 2) # Init CTRL interface self.ctrl_if = CTRLInterfaceTRX(self, remote_addr, base_port + child_idx * 2 + 101, bind_addr, base_port + child_idx * 2 + 1) # Init optional CLCK interface self.clck_gen = clck_gen if clck_gen is not None: self.clck_if = UDPLink(remote_addr, base_port + 100, bind_addr, base_port) # Optional Power Measurement interface self.pwr_meas = pwr_meas # Internal state self.running = False # Actual RX / TX frequencies self.rx_freq = None self.tx_freq = None # List of active (configured) timeslots self.ts_list = [] # List of child transceivers self.child_trx_list = TRXList() def __str__(self): desc = "%s:%d" % (self.remote_addr, self.base_port) if self.child_idx > 0: desc += "/%d" % self.child_idx if self.name is not None: desc = "%s@%s" % (self.name, desc) return desc # To be overwritten if required, # no custom command handlers by default def ctrl_cmd_handler(self, request): return None def power_event_handler(self, event): # Update child transceivers for trx in self.child_trx_list.trx_list: if event == "POWERON": trx.running = True else: trx.running = False # Trigger clock generator if required if self.clck_gen is not None: clck_links = self.clck_gen.clck_links if not self.running and (self.clck_if in clck_links): # Transceiver was stopped clck_links.remove(self.clck_if) elif self.running and (self.clck_if not in clck_links): # Transceiver was started clck_links.append(self.clck_if) if not self.clck_gen.running and len(clck_links) > 0: log.info("Starting clock generator") self.clck_gen.start() elif self.clck_gen.running and not clck_links: log.info("Stopping clock generator") self.clck_gen.stop() def recv_data_msg(self): # Read and parse data from socket msg = self.data_if.recv_l12trx_msg() if not msg: return None # Make sure that transceiver is configured and running if not self.running: log.warning("(%s) RX TRXD message (%s), but transceiver " "is not running => dropping..." % (self, msg.desc_hdr())) return None # Make sure that indicated timeslot is configured if msg.tn not in self.ts_list: log.warning("(%s) RX TRXD message (%s), but timeslot is not " "configured => dropping..." % (self, msg.desc_hdr())) return None return msg
class Transceiver: """ Base transceiver implementation. Represents a single transceiver, that can be used as for the BTS side, as for the MS side. Each individual instance of Transceiver unifies three basic interfaces built on three independent UDP connections: - CLCK (base port + 100/0) - clock indications from TRX to L1, - CTRL (base port + 101/1) - control interface for L1, - DATA (base port + 102/2) - bidirectional data interface for bursts. A transceiver can be either in active (i.e. working), or in idle mode. The active mode should ensure that both RX/TX frequencies are set. NOTE: CLCK is not required for some L1 implementations, so it is optional. == Timeslot configuration Transceiver has a list of active (i.e. configured) TDMA timeslots. The L1 should configure a timeslot before sending or expecting any data on it. This is done by SETSLOT control command, which also indicates an associated channel combination (see GSM TS 05.02). NOTE: we don't store the associated channel combinations, as they are only useful for burst detection and demodulation. == Clock distribution (optional) The clock indications are not expected by L1 when transceiver is not running, so we monitor both POWERON / POWEROFF events from the control interface, and keep the list of CLCK links in a given CLCKGen instance updated. The clock generator is started and stopped automatically. NOTE: a single instance of CLCKGen can be shared between multiple transceivers, as well as multiple transceivers may use individual CLCKGen instances. == Power Measurement (optional) Transceiver may have an optional power measurement interface, that shall provide at least one method: measure(freq). This is required for the MS side (i.e. OsmocomBB). """ def __init__(self, bind_addr, remote_addr, base_port, clck_gen=None, pwr_meas=None): # Connection info self.remote_addr = remote_addr self.bind_addr = bind_addr self.base_port = base_port # Init DATA interface self.data_if = DATAInterface(remote_addr, base_port + 102, bind_addr, base_port + 2) # Init CTRL interface self.ctrl_if = CTRLInterfaceTRX(self, remote_addr, base_port + 101, bind_addr, base_port + 1) # Init optional CLCK interface self.clck_gen = clck_gen if clck_gen is not None: self.clck_if = UDPLink(remote_addr, base_port + 100, bind_addr, base_port) # Optional Power Measurement interface self.pwr_meas = pwr_meas # Internal state self.running = False # Actual RX / TX frequencies self.rx_freq = None self.tx_freq = None # List of active (configured) timeslots self.ts_list = [] # To be overwritten if required, # no custom command handlers by default def ctrl_cmd_handler(self, request): return None def power_event_handler(self, event): # Trigger clock generator if required if self.clck_gen is not None: clck_links = self.clck_gen.clck_links if not self.running and (self.clck_if in clck_links): # Transceiver was stopped clck_links.remove(self.clck_if) elif self.running and (self.clck_if not in clck_links): # Transceiver was started clck_links.append(self.clck_if) if not self.clck_gen.timer and len(clck_links) > 0: log.info("Starting clock generator") self.clck_gen.start() elif self.clck_gen.timer and not clck_links: log.info("Stopping clock generator") self.clck_gen.stop() def recv_data_msg(self): # Read and parse data from socket msg = self.data_if.recv_l12trx_msg() if not msg: return None # Make sure that transceiver is configured and running if not self.running: log.warning("RX DATA message (%s), but transceiver " "is not running => dropping..." % msg.desc_hdr()) return None # Make sure that indicated timeslot is configured if msg.tn not in self.ts_list: log.warning("RX DATA message (%s), but timeslot " "is not configured => dropping..." % msg.desc_hdr()) return None return msg
class Application(ApplicationBase): def __init__(self): self.app_print_copyright(APP_CR_HOLDERS) self.argv = self.parse_argv() # Set up signal handlers signal.signal(signal.SIGINT, self.sig_handler) # Configure logging self.app_init_logging(self.argv) # Open requested capture file if self.argv.output_file is not None: self.ddf = DATADumpFile(self.argv.output_file) def run(self): # Init DATA interface with TRX or L1 if self.argv.conn_mode == "TRX": self.data_if = DATAInterface(self.argv.remote_addr, self.argv.base_port + 2, self.argv.bind_addr, self.argv.base_port + 102) elif self.argv.conn_mode == "L1": self.data_if = DATAInterface(self.argv.remote_addr, self.argv.base_port + 102, self.argv.bind_addr, self.argv.base_port + 2) # Init random burst generator burst_gen = RandBurstGen() # Init an empty DATA message if self.argv.conn_mode == "TRX": msg = DATAMSG_L12TRX() elif self.argv.conn_mode == "L1": msg = DATAMSG_TRX2L1() # Generate a random frame number or use provided one fn_init = msg.rand_fn() if self.argv.tdma_fn is None \ else self.argv.tdma_fn # Send as much bursts as required for i in range(self.argv.burst_count): # Randomize the message header msg.rand_hdr() # Increase and set frame number msg.fn = (fn_init + i) % GSM_HYPERFRAME # Set timeslot number if self.argv.tdma_tn is not None: msg.tn = self.argv.tdma_tn # Set transmit power level if self.argv.pwr is not None: msg.pwr = self.argv.pwr # Set time of arrival if self.argv.toa is not None: msg.toa256 = int(float(self.argv.toa) * 256.0 + 0.5) elif self.argv.toa256 is not None: msg.toa256 = self.argv.toa256 # Set RSSI if self.argv.rssi is not None: msg.rssi = self.argv.rssi # Generate a random burst if self.argv.burst_type == "NB": burst = burst_gen.gen_nb() elif self.argv.burst_type == "FB": burst = burst_gen.gen_fb() elif self.argv.burst_type == "SB": burst = burst_gen.gen_sb() elif self.argv.burst_type == "AB": burst = burst_gen.gen_ab() # Convert to soft-bits in case of TRX -> L1 message if self.argv.conn_mode == "L1": burst = msg.ubit2sbit(burst) # Set burst msg.burst = burst log.info("Sending %d/%d %s burst %s to %s..." % (i + 1, self.argv.burst_count, self.argv.burst_type, msg.desc_hdr(), self.argv.conn_mode)) # Send message self.data_if.send_msg(msg) # Append a new message to the capture if self.argv.output_file is not None: self.ddf.append_msg(msg) def parse_argv(self): parser = argparse.ArgumentParser( prog="burst_gen", description="Auxiliary tool to generate and send random bursts") # Register common logging options self.app_reg_logging_options(parser) trx_group = parser.add_argument_group("TRX interface") trx_group.add_argument("-r", "--remote-addr", dest="remote_addr", type=str, default="127.0.0.1", help="Set remote address (default %(default)s)") trx_group.add_argument("-b", "--bind-addr", dest="bind_addr", type=str, default="0.0.0.0", help="Set bind address (default %(default)s)") trx_group.add_argument( "-p", "--base-port", dest="base_port", type=int, default=6700, help="Set base port number (default %(default)s)") trx_group.add_argument( "-m", "--conn-mode", dest="conn_mode", type=str, choices=["TRX", "L1"], default="TRX", help="Where to send bursts (default %(default)s)") trx_group.add_argument("-o", "--output-file", dest="output_file", type=str, help="Write bursts to a capture file") bg_group = parser.add_argument_group("Burst generation") bg_group.add_argument("-B", "--burst-type", dest="burst_type", type=str, choices=["NB", "FB", "SB", "AB"], default="NB", help="Random burst type (default %(default)s)") bg_group.add_argument( "-c", "--burst-count", metavar="N", dest="burst_count", type=int, default=1, help="How many bursts to send (default %(default)s)") bg_group.add_argument("-f", "--frame-number", metavar="FN", dest="tdma_fn", type=int, help="Set TDMA frame number (default random)") bg_group.add_argument("-t", "--timeslot", metavar="TN", dest="tdma_tn", type=int, choices=range(0, 8), help="Set TDMA timeslot (default random)") bg_pwr_group = bg_group.add_mutually_exclusive_group() bg_pwr_group.add_argument("--pwr", metavar="dBm", dest="pwr", type=int, help="Set power level (default random)") bg_pwr_group.add_argument("--rssi", metavar="dBm", dest="rssi", type=int, help="Set RSSI (default random)") bg_toa_group = bg_group.add_mutually_exclusive_group() bg_toa_group.add_argument( "--toa", dest="toa", type=int, help="Set Timing of Arrival in symbols (default random)") bg_toa_group.add_argument( "--toa256", dest="toa256", type=int, help="Set Timing of Arrival in 1/256 symbol periods") return parser.parse_args() def sig_handler(self, signum, frame): log.info("Signal %d received" % signum) if signum is signal.SIGINT: sys.exit(0)
class Application(ApplicationBase): def __init__(self): self.app_print_copyright(APP_CR_HOLDERS) self.argv = self.parse_argv() # Set up signal handlers signal.signal(signal.SIGINT, self.sig_handler) # Configure logging self.app_init_logging(self.argv) # Open requested capture file self.ddf = DATADumpFile(self.argv.capture_file) def run(self): # Init DATA interface with TRX or L1 if self.argv.conn_mode == "TRX": self.data_if = DATAInterface( self.argv.remote_addr, self.argv.base_port + 2, self.argv.bind_addr, self.argv.base_port + 102) elif self.argv.conn_mode == "L1": self.data_if = DATAInterface( self.argv.remote_addr, self.argv.base_port + 102, self.argv.bind_addr, self.argv.base_port + 2) # Read messages from the capture messages = self.ddf.parse_all( skip = self.argv.cnt_skip, count = self.argv.cnt_count) if messages is False: log.error("Parsing failed, nothing to send") sys.exit(1) for msg in messages: # Pass filter if not self.msg_pass_filter(msg): continue log.info("Sending a burst %s to %s..." % (msg.desc_hdr(), self.argv.conn_mode)) # Send message self.data_if.send_msg(msg) def msg_pass_filter(self, msg): # Direction filter l12trx = self.argv.conn_mode == "TRX" if isinstance(msg, DATAMSG_L12TRX) and not l12trx: return False elif isinstance(msg, DATAMSG_TRX2L1) and l12trx: return False # Timeslot filter if self.argv.pf_tn is not None: if msg.tn != self.argv.pf_tn: return False # Frame number filter if self.argv.pf_fn_lt is not None: if msg.fn > self.argv.pf_fn_lt: return False if self.argv.pf_fn_gt is not None: if msg.fn < self.argv.pf_fn_gt: return False # Burst passed ;) return True def parse_argv(self): parser = argparse.ArgumentParser(prog = "burst_send", description = "Auxiliary tool to send (reply) captured bursts") # Register common logging options self.app_reg_logging_options(parser) trx_group = parser.add_argument_group("TRX interface") trx_group.add_argument("-r", "--remote-addr", dest = "remote_addr", type = str, default = "127.0.0.1", help = "Set remote address (default %(default)s)") trx_group.add_argument("-b", "--bind-addr", dest = "bind_addr", type = str, default = "0.0.0.0", help = "Set bind address (default %(default)s)") trx_group.add_argument("-p", "--base-port", dest = "base_port", type = int, default = 6700, help = "Set base port number (default %(default)s)") trx_group.add_argument("-m", "--conn-mode", dest = "conn_mode", type = str, choices = ["TRX", "L1"], default = "TRX", help = "Where to send bursts (default %(default)s)") trx_group.add_argument("-i", "--capture-file", metavar = "FILE", dest = "capture_file", type = str, required = True, help = "Capture file to read bursts from") cnt_group = parser.add_argument_group("Count limitations (optional)") cnt_group.add_argument("--skip", metavar = "N", dest = "cnt_skip", type = int, help = "Skip N messages before sending") cnt_group.add_argument("--count", metavar = "N", dest = "cnt_count", type = int, help = "Stop after sending N messages") pf_group = parser.add_argument_group("Filtering (optional)") pf_group.add_argument("--timeslot", metavar = "TN", dest = "pf_tn", type = int, choices = range(0, 8), help = "TDMA timeslot number (equal TN)") pf_group.add_argument("--frame-num-lt", metavar = "FN", dest = "pf_fn_lt", type = int, help = "TDMA frame number (lower than FN)") pf_group.add_argument("--frame-num-gt", metavar = "FN", dest = "pf_fn_gt", type = int, help = "TDMA frame number (greater than FN)") return parser.parse_args() def sig_handler(self, signum, frame): log.info("Signal %d received" % signum) if signum is signal.SIGINT: sys.exit(0)
class Transceiver: """ Base transceiver implementation. Represents a single transceiver, that can be used as for the BTS side, as for the MS side. Each individual instance of Transceiver unifies three basic interfaces built on three independent UDP connections: - CLCK (base port + 100/0) - clock indications from TRX to L1, - CTRL (base port + 101/1) - control interface for L1, - DATA (base port + 102/2) - bidirectional data interface for bursts. A transceiver can be either in active (i.e. working), or in idle mode. The active mode should ensure that both RX/TX frequencies are set. NOTE: CLCK is not required for some L1 implementations, so it is optional. == Timeslot configuration Transceiver has a list of active (i.e. configured) TDMA timeslots. The L1 should configure a timeslot before sending or expecting any data on it. This is done by SETSLOT control command, which also indicates an associated channel combination (see GSM TS 05.02). NOTE: we don't store the associated channel combinations, as they are only useful for burst detection and demodulation. == Child transceivers A BTS can (optionally) have more than one transceiver. In this case additional (let's say child) transceivers basically share the same clock source of the first transceiver, so UDP port mapping is a bit different, for example: (trx_0) clck=5700, ctrl=5701, data=5702, (trx_1) ctrl=5703, data=5704, (trx_2) ctrl=5705, data=5706. ... As soon as the first transceiver is powered on / off, all child transceivers are also powered on / off. == Clock distribution (optional) The clock indications are not expected by L1 when transceiver is not running, so we monitor both POWERON / POWEROFF events from the control interface, and keep the list of CLCK links in a given CLCKGen instance updated. The clock generator is started and stopped automatically. NOTE: a single instance of CLCKGen can be shared between multiple transceivers, as well as multiple transceivers may use individual CLCKGen instances. == Power Measurement (optional) Transceiver may have an optional power measurement interface, that shall provide at least one method: measure(freq). This is required for the MS side (i.e. OsmocomBB). == Frequency hopping (optional) There are two ways to implement frequency hopping: a) The Transceiver is configured with the hopping parameters, in particular HSN, MAIO, and the list of ARFCNs (channels), so the actual Rx/Tx frequencies are changed by the Transceiver itself depending on the current TDMA frame number. b) The L1 maintains several Transceivers (two or more), so each instance is assigned one dedicated RF carrier frequency, and hence the number of available hopping frequencies is equal to the number of Transceivers. In this case, it's the task of the L1 to commutate bursts between Transceivers (frequencies). Variant a) is commonly known as "synthesizer frequency hopping" whereas b) is known as "baseband frequency hopping". For the MS side, a) is preferred, because a phone usually has only one Transceiver (per RAT). On the other hand, b) is more suitable for the BTS side, because it's relatively easy to implement and there is no technical limitation on the amount of Transceivers. FakeTRX obviously does support b) since multi-TRX feature has been implemented, as well as a) by resolving UL/DL frequencies using a preconfigured (by the L1) set of the hopping parameters. The later can be enabled using the SETFH control command. NOTE: in the current implementation, mode a) applies to the whole Transceiver and all its timeslots, so using in for the BTS side does not make any sense (imagine BCCH hopping together with DCCH). """ def __init__(self, bind_addr, remote_addr, base_port, name=None, child_idx=0, clck_gen=None, pwr_meas=None): # Connection info self.remote_addr = remote_addr self.bind_addr = bind_addr self.base_port = base_port self.child_idx = child_idx # Meta info self.name = name log.info("Init transceiver '%s'" % self) # Child transceiver cannot have its own clock if clck_gen is not None and child_idx > 0: raise TypeError("Child transceiver cannot have its own clock") # Init DATA interface self.data_if = DATAInterface(remote_addr, base_port + child_idx * 2 + 102, bind_addr, base_port + child_idx * 2 + 2) # Init CTRL interface self.ctrl_if = CTRLInterfaceTRX(self, remote_addr, base_port + child_idx * 2 + 101, bind_addr, base_port + child_idx * 2 + 1) # Init optional CLCK interface self.clck_gen = clck_gen if clck_gen is not None: self.clck_if = UDPLink(remote_addr, base_port + 100, bind_addr, base_port) # Optional Power Measurement interface self.pwr_meas = pwr_meas # Internal state self.running = False # Actual RX / TX frequencies self._rx_freq = None self._tx_freq = None # Frequency hopping parameters (set by CTRL) self.fh = None # List of active (configured) timeslots self.ts_list = [] # List of child transceivers self.child_trx_list = TRXList() def __str__(self): desc = "%s:%d" % (self.remote_addr, self.base_port) if self.child_idx > 0: desc += "/%d" % self.child_idx if self.name is not None: desc = "%s@%s" % (self.name, desc) return desc @property def ready(self): # Make sure that either both Rx/Tx frequencies are set if self._rx_freq is None or self._tx_freq is None: # ... or frequency hopping is in use if self.fh is None: return False return True def get_rx_freq(self, fn): if self.fh is None: return self._rx_freq # Frequency hopping in use, resolve by TDMA fn (rx_freq, _) = self.fh.resolve(fn) return rx_freq def get_tx_freq(self, fn): if self.fh is None: return self._tx_freq # Frequency hopping in use, resolve by TDMA fn (_, tx_freq) = self.fh.resolve(fn) return tx_freq def enable_fh(self, *args): self.fh = HoppingParams(*args) log.info("(%s) Frequency hopping configured: %s" % (self, self.fh)) def disable_fh(self): if self.fh is not None: log.info("(%s) Frequency hopping disabled" % self) self.fh = None # To be overwritten if required, # no custom command handlers by default def ctrl_cmd_handler(self, request): return None def power_event_handler(self, event): # Update child transceivers for trx in self.child_trx_list.trx_list: if event == "POWERON": trx.running = True elif event == "POWEROFF": trx.running = False trx.disable_fh() # Reset frequency hopping parameters if event == "POWEROFF": self.disable_fh() # Trigger clock generator if required if self.clck_gen is not None: clck_links = self.clck_gen.clck_links if not self.running and (self.clck_if in clck_links): # Transceiver was stopped clck_links.remove(self.clck_if) elif self.running and (self.clck_if not in clck_links): # Transceiver was started clck_links.append(self.clck_if) if not self.clck_gen.running and len(clck_links) > 0: log.info("Starting clock generator") self.clck_gen.start() elif self.clck_gen.running and not clck_links: log.info("Stopping clock generator") self.clck_gen.stop() def recv_data_msg(self): # Read and parse data from socket msg = self.data_if.recv_tx_msg() if not msg: return None # Make sure that transceiver is configured and running if not self.running: log.warning("(%s) RX TRXD message (%s), but transceiver " "is not running => dropping..." % (self, msg.desc_hdr())) return None # Make sure that indicated timeslot is configured # Pass PDUs without burst bits, they will be sent as NOPE.ind if msg.tn not in self.ts_list and msg.burst: log.warning("(%s) RX TRXD message (%s), but timeslot is not " "configured => dropping..." % (self, msg.desc_hdr())) return None return msg def handle_data_msg(self, msg): # TODO: make legacy mode configurable (via argv?) self.data_if.send_msg(msg, legacy=True)
class Application(ApplicationBase): def __init__(self): self.app_print_copyright(APP_CR_HOLDERS) self.argv = self.parse_argv() # Set up signal handlers signal.signal(signal.SIGINT, self.sig_handler) # Configure logging self.app_init_logging(self.argv) # Open requested capture file self.ddf = DATADumpFile(self.argv.capture_file) def run(self): # Init DATA interface with TRX or L1 if self.argv.conn_mode == "TRX": self.data_if = DATAInterface( self.argv.remote_addr, self.argv.base_port + 2, self.argv.bind_addr, self.argv.base_port + 102) elif self.argv.conn_mode == "L1": self.data_if = DATAInterface( self.argv.remote_addr, self.argv.base_port + 102, self.argv.bind_addr, self.argv.base_port + 2) # Read messages from the capture messages = self.ddf.parse_all( skip = self.argv.cnt_skip, count = self.argv.cnt_count) if messages is False: log.error("Parsing failed, nothing to send") sys.exit(1) for msg in messages: # Pass filter if not self.msg_pass_filter(msg): continue log.info("Sending a burst %s to %s..." % (msg.desc_hdr(), self.argv.conn_mode)) # Send message self.data_if.send_msg(msg) def msg_pass_filter(self, msg): # Direction filter if isinstance(msg, RxMsg) and self.argv.conn_mode == "TRX": return False # cannot send RxMsg to TRX if isinstance(msg, TxMsg) and self.argv.conn_mode == "L1": return False # cannot send TxMsg to L1 # Timeslot filter if self.argv.pf_tn is not None: if msg.tn != self.argv.pf_tn: return False # Frame number filter if self.argv.pf_fn_lt is not None: if msg.fn > self.argv.pf_fn_lt: return False if self.argv.pf_fn_gt is not None: if msg.fn < self.argv.pf_fn_gt: return False # Burst passed ;) return True def parse_argv(self): parser = argparse.ArgumentParser(prog = "burst_send", description = "Auxiliary tool to send (reply) captured bursts") # Register common logging options self.app_reg_logging_options(parser) trx_group = parser.add_argument_group("TRX interface") trx_group.add_argument("-r", "--remote-addr", dest = "remote_addr", type = str, default = "127.0.0.1", help = "Set remote address (default %(default)s)") trx_group.add_argument("-b", "--bind-addr", dest = "bind_addr", type = str, default = "0.0.0.0", help = "Set bind address (default %(default)s)") trx_group.add_argument("-p", "--base-port", dest = "base_port", type = int, default = 6700, help = "Set base port number (default %(default)s)") trx_group.add_argument("-m", "--conn-mode", dest = "conn_mode", type = str, choices = ["TRX", "L1"], default = "TRX", help = "Where to send bursts (default %(default)s)") trx_group.add_argument("-i", "--capture-file", metavar = "FILE", dest = "capture_file", type = str, required = True, help = "Capture file to read bursts from") cnt_group = parser.add_argument_group("Count limitations (optional)") cnt_group.add_argument("--skip", metavar = "N", dest = "cnt_skip", type = int, help = "Skip N messages before sending") cnt_group.add_argument("--count", metavar = "N", dest = "cnt_count", type = int, help = "Stop after sending N messages") pf_group = parser.add_argument_group("Filtering (optional)") pf_group.add_argument("--timeslot", metavar = "TN", dest = "pf_tn", type = int, choices = range(0, 8), help = "TDMA timeslot number (equal TN)") pf_group.add_argument("--frame-num-lt", metavar = "FN", dest = "pf_fn_lt", type = int, help = "TDMA frame number (lower than FN)") pf_group.add_argument("--frame-num-gt", metavar = "FN", dest = "pf_fn_gt", type = int, help = "TDMA frame number (greater than FN)") return parser.parse_args() def sig_handler(self, signum, frame): log.info("Signal %d received" % signum) if signum == signal.SIGINT: sys.exit(0)
class Application(ApplicationBase): def __init__(self): self.app_print_copyright(APP_CR_HOLDERS) self.argv = self.parse_argv() # Set up signal handlers signal.signal(signal.SIGINT, self.sig_handler) # Configure logging self.app_init_logging(self.argv) # Open requested capture file if self.argv.output_file is not None: self.ddf = DATADumpFile(self.argv.output_file) def run(self): # Init DATA interface with TRX or L1 if self.argv.conn_mode == "TRX": self.data_if = DATAInterface( self.argv.remote_addr, self.argv.base_port + 2, self.argv.bind_addr, self.argv.base_port + 102) elif self.argv.conn_mode == "L1": self.data_if = DATAInterface( self.argv.remote_addr, self.argv.base_port + 102, self.argv.bind_addr, self.argv.base_port + 2) # Init random burst generator burst_gen = RandBurstGen() # Init an empty DATA message if self.argv.conn_mode == "TRX": msg = DATAMSG_L12TRX() elif self.argv.conn_mode == "L1": msg = DATAMSG_TRX2L1() # Generate a random frame number or use provided one fn_init = msg.rand_fn() if self.argv.tdma_fn is None \ else self.argv.tdma_fn # Send as much bursts as required for i in range(self.argv.burst_count): # Randomize the message header msg.rand_hdr() # Increase and set frame number msg.fn = (fn_init + i) % GSM_HYPERFRAME # Set timeslot number if self.argv.tdma_tn is not None: msg.tn = self.argv.tdma_tn # Set transmit power level if self.argv.pwr is not None: msg.pwr = self.argv.pwr # Set time of arrival if self.argv.toa is not None: msg.toa256 = int(float(self.argv.toa) * 256.0 + 0.5) elif self.argv.toa256 is not None: msg.toa256 = self.argv.toa256 # Set RSSI if self.argv.rssi is not None: msg.rssi = self.argv.rssi # Generate a random burst if self.argv.burst_type == "NB": burst = burst_gen.gen_nb() elif self.argv.burst_type == "FB": burst = burst_gen.gen_fb() elif self.argv.burst_type == "SB": burst = burst_gen.gen_sb() elif self.argv.burst_type == "AB": burst = burst_gen.gen_ab() # Convert to soft-bits in case of TRX -> L1 message if self.argv.conn_mode == "L1": burst = msg.ubit2sbit(burst) # Set burst msg.burst = burst log.info("Sending %d/%d %s burst %s to %s..." % (i + 1, self.argv.burst_count, self.argv.burst_type, msg.desc_hdr(), self.argv.conn_mode)) # Send message self.data_if.send_msg(msg) # Append a new message to the capture if self.argv.output_file is not None: self.ddf.append_msg(msg) def parse_argv(self): parser = argparse.ArgumentParser(prog = "burst_gen", description = "Auxiliary tool to generate and send random bursts") # Register common logging options self.app_reg_logging_options(parser) trx_group = parser.add_argument_group("TRX interface") trx_group.add_argument("-r", "--remote-addr", dest = "remote_addr", type = str, default = "127.0.0.1", help = "Set remote address (default %(default)s)") trx_group.add_argument("-b", "--bind-addr", dest = "bind_addr", type = str, default = "0.0.0.0", help = "Set bind address (default %(default)s)") trx_group.add_argument("-p", "--base-port", dest = "base_port", type = int, default = 6700, help = "Set base port number (default %(default)s)") trx_group.add_argument("-m", "--conn-mode", dest = "conn_mode", type = str, choices = ["TRX", "L1"], default = "TRX", help = "Where to send bursts (default %(default)s)") trx_group.add_argument("-o", "--output-file", dest = "output_file", type = str, help = "Write bursts to a capture file") bg_group = parser.add_argument_group("Burst generation") bg_group.add_argument("-B", "--burst-type", dest = "burst_type", type = str, choices = ["NB", "FB", "SB", "AB"], default = "NB", help = "Random burst type (default %(default)s)") bg_group.add_argument("-c", "--burst-count", metavar = "N", dest = "burst_count", type = int, default = 1, help = "How many bursts to send (default %(default)s)") bg_group.add_argument("-f", "--frame-number", metavar = "FN", dest = "tdma_fn", type = int, help = "Set TDMA frame number (default random)") bg_group.add_argument("-t", "--timeslot", metavar = "TN", dest = "tdma_tn", type = int, choices = range(0, 8), help = "Set TDMA timeslot (default random)") bg_pwr_group = bg_group.add_mutually_exclusive_group() bg_pwr_group.add_argument("--pwr", metavar = "dBm", dest = "pwr", type = int, help = "Set power level (default random)") bg_pwr_group.add_argument("--rssi", metavar = "dBm", dest = "rssi", type = int, help = "Set RSSI (default random)") bg_toa_group = bg_group.add_mutually_exclusive_group() bg_toa_group.add_argument("--toa", dest = "toa", type = int, help = "Set Timing of Arrival in symbols (default random)") bg_toa_group.add_argument("--toa256", dest = "toa256", type = int, help = "Set Timing of Arrival in 1/256 symbol periods") return parser.parse_args() def sig_handler(self, signum, frame): log.info("Signal %d received" % signum) if signum is signal.SIGINT: sys.exit(0)
class Application: # Application variables remote_addr = "127.0.0.1" bind_addr = "0.0.0.0" base_port = 5700 conn_mode = "TRX" # Burst source capture_file = None # Count limitations msg_skip = None msg_count = None # Pass filtering pf_fn_lt = None pf_fn_gt = None pf_tn = None def __init__(self): print_copyright(CR_HOLDERS) self.parse_argv() # Set up signal handlers signal.signal(signal.SIGINT, self.sig_handler) # Open requested capture file self.ddf = DATADumpFile(self.capture_file) def run(self): # Init DATA interface with TRX or L1 if self.conn_mode == "TRX": self.data_if = DATAInterface(self.remote_addr, self.base_port + 2, self.bind_addr, self.base_port + 102) l12trx = True elif self.conn_mode == "L1": self.data_if = DATAInterface(self.remote_addr, self.base_port + 102, self.bind_addr, self.base_port + 2) l12trx = False else: self.print_help("[!] Unknown connection type") sys.exit(2) # Read messages from the capture messages = self.ddf.parse_all( skip = self.msg_skip, count = self.msg_count) if messages is False: pass # FIXME!!! for msg in messages: # Pass filter if not self.msg_pass_filter(l12trx, msg): continue print("[i] Sending a burst %s to %s..." % (msg.desc_hdr(), self.conn_mode)) # Send message self.data_if.send_msg(msg) def msg_pass_filter(self, l12trx, msg): # Direction filter if isinstance(msg, DATAMSG_L12TRX) and not l12trx: return False elif isinstance(msg, DATAMSG_TRX2L1) and l12trx: return False # Timeslot filter if self.pf_tn is not None: if msg.tn != self.pf_tn: return False # Frame number filter if self.pf_fn_lt is not None: if msg.fn > self.pf_fn_lt: return False if self.pf_fn_gt is not None: if msg.fn < self.pf_fn_gt: return False # Burst passed ;) return True def print_help(self, msg = None): s = " Usage: " + sys.argv[0] + " [options]\n\n" \ " Some help...\n" \ " -h --help this text\n\n" s += " TRX interface specific\n" \ " -m --conn-mode Send bursts to: TRX (default) / L1\n" \ " -r --remote-addr Set remote address (default %s)\n" \ " -b --bind-addr Set bind address (default %s)\n" \ " -p --base-port Set base port number (default %d)\n\n" s += " Burst source\n" \ " -i --capture-file Read bursts from capture file\n\n" \ s += " Count limitations (disabled by default)\n" \ " --msg-skip NUM Skip NUM messages before sending\n" \ " --msg-count NUM Stop after sending NUM messages\n\n" \ s += " Filtering (disabled by default)\n" \ " --timeslot NUM TDMA timeslot number [0..7]\n" \ " --frame-num-lt NUM TDMA frame number lower than NUM\n" \ " --frame-num-gt NUM TDMA frame number greater than NUM\n" print(s % (self.remote_addr, self.bind_addr, self.base_port)) if msg is not None: print(msg) def parse_argv(self): try: opts, args = getopt.getopt(sys.argv[1:], "m:r:b:p:i:h", [ "help", "conn-mode=", "remote-addr=", "bind-addr=", "base-port=", "capture-file=", "msg-skip=", "msg-count=", "timeslot=", "frame-num-lt=", "frame-num-gt=", ]) except getopt.GetoptError as err: self.print_help("[!] " + str(err)) sys.exit(2) for o, v in opts: if o in ("-h", "--help"): self.print_help() sys.exit(2) # Capture file elif o in ("-i", "--capture-file"): self.capture_file = v # TRX interface specific elif o in ("-m", "--conn-mode"): self.conn_mode = v elif o in ("-r", "--remote-addr"): self.remote_addr = v elif o in ("-b", "--bind-addr"): self.bind_addr = v elif o in ("-p", "--base-port"): self.base_port = int(v) # Count limitations elif o == "--msg-skip": self.msg_skip = int(v) elif o == "--msg-count": self.msg_count = int(v) # Timeslot pass filter elif o == "--timeslot": self.pf_tn = int(v) if self.pf_tn < 0 or self.pf_tn > 7: self.print_help("[!] Wrong timeslot value") sys.exit(2) # Frame number pass filter elif o == "--frame-num-lt": self.pf_fn_lt = int(v) elif o == "--frame-num-gt": self.pf_fn_gt = int(v) if self.capture_file is None: self.print_help("[!] Please specify a capture file") sys.exit(2) def sig_handler(self, signum, frame): print("Signal %d received" % signum) if signum is signal.SIGINT: sys.exit(0)
def run(self): # Init DATA interface with TRX or L1 if self.conn_mode == "TRX": self.data_if = DATAInterface(self.remote_addr, self.base_port + 2, self.bind_addr, self.base_port + 102) elif self.conn_mode == "L1": self.data_if = DATAInterface(self.remote_addr, self.base_port + 102, self.bind_addr, self.base_port + 2) # Init random burst generator burst_gen = RandBurstGen() # Init an empty DATA message if self.conn_mode == "TRX": msg = DATAMSG_L12TRX() elif self.conn_mode == "L1": msg = DATAMSG_TRX2L1() # Generate a random frame number or use provided one fn_init = msg.rand_fn() if self.fn is None else self.fn # Send as much bursts as required for i in range(self.burst_count): # Randomize the message header msg.rand_hdr() # Increase and set frame number msg.fn = (fn_init + i) % GSM_HYPERFRAME # Set timeslot number if self.tn is not None: msg.tn = self.tn # Set transmit power level if self.pwr is not None: msg.pwr = self.pwr # Set time of arrival if self.toa256 is not None: msg.toa256 = self.toa256 # Set RSSI if self.rssi is not None: msg.rssi = self.rssi # Generate a random burst if self.burst_type == "NB": burst = burst_gen.gen_nb() elif self.burst_type == "FB": burst = burst_gen.gen_fb() elif self.burst_type == "SB": burst = burst_gen.gen_sb() elif self.burst_type == "AB": burst = burst_gen.gen_ab() # Convert to soft-bits in case of TRX -> L1 message if self.conn_mode == "L1": burst = msg.ubit2sbit(burst) # Set burst msg.burst = burst print("[i] Sending %d/%d %s burst %s to %s..." % (i + 1, self.burst_count, self.burst_type, msg.desc_hdr(), self.conn_mode)) # Send message self.data_if.send_msg(msg) # Append a new message to the capture if self.output_file is not None: self.ddf.append_msg(msg)
class Transceiver: """ Base transceiver implementation. Represents a single transceiver, that can be used as for the BTS side, as for the MS side. Each individual instance of Transceiver unifies three basic interfaces built on three independent UDP connections: - CLCK (base port + 100/0) - clock indications from TRX to L1, - CTRL (base port + 101/1) - control interface for L1, - DATA (base port + 102/2) - bidirectional data interface for bursts. A transceiver can be either in active (i.e. working), or in idle mode. The active mode should ensure that both RX/TX frequencies are set. NOTE: CLCK is not required for some L1 implementations, so it is optional. == Timeslot configuration Transceiver has a list of active (i.e. configured) TDMA timeslots. The L1 should configure a timeslot before sending or expecting any data on it. This is done by SETSLOT control command, which also indicates an associated channel combination (see GSM TS 05.02). NOTE: we don't store the associated channel combinations, as they are only useful for burst detection and demodulation. == Child transceivers A BTS can (optionally) have more than one transceiver. In this case additional (let's say child) transceivers basically share the same clock source of the first transceiver, so UDP port mapping is a bit different, for example: (trx_0) clck=5700, ctrl=5701, data=5702, (trx_1) ctrl=5703, data=5704, (trx_2) ctrl=5705, data=5706. ... As soon as the first transceiver is powered on / off, all child transceivers are also powered on / off. == Clock distribution (optional) The clock indications are not expected by L1 when transceiver is not running, so we monitor both POWERON / POWEROFF events from the control interface, and keep the list of CLCK links in a given CLCKGen instance updated. The clock generator is started and stopped automatically. NOTE: a single instance of CLCKGen can be shared between multiple transceivers, as well as multiple transceivers may use individual CLCKGen instances. == Power Measurement (optional) Transceiver may have an optional power measurement interface, that shall provide at least one method: measure(freq). This is required for the MS side (i.e. OsmocomBB). """ def __init__(self, bind_addr, remote_addr, base_port, name = None, child_idx = 0, clck_gen = None, pwr_meas = None): # Connection info self.remote_addr = remote_addr self.bind_addr = bind_addr self.base_port = base_port self.child_idx = child_idx # Meta info self.name = name log.info("Init transceiver '%s'" % self) # Child transceiver cannot have its own clock if clck_gen is not None and child_idx > 0: raise TypeError("Child transceiver cannot have its own clock") # Init DATA interface self.data_if = DATAInterface( remote_addr, base_port + child_idx * 2 + 102, bind_addr, base_port + child_idx * 2 + 2) # Init CTRL interface self.ctrl_if = CTRLInterfaceTRX(self, remote_addr, base_port + child_idx * 2 + 101, bind_addr, base_port + child_idx * 2 + 1) # Init optional CLCK interface self.clck_gen = clck_gen if clck_gen is not None: self.clck_if = UDPLink( remote_addr, base_port + 100, bind_addr, base_port) # Optional Power Measurement interface self.pwr_meas = pwr_meas # Internal state self.running = False # Actual RX / TX frequencies self.rx_freq = None self.tx_freq = None # List of active (configured) timeslots self.ts_list = [] # List of child transceivers self.child_trx_list = TRXList() def __str__(self): desc = "%s:%d" % (self.remote_addr, self.base_port) if self.child_idx > 0: desc += "/%d" % self.child_idx if self.name is not None: desc = "%s@%s" % (self.name, desc) return desc # To be overwritten if required, # no custom command handlers by default def ctrl_cmd_handler(self, request): return None def power_event_handler(self, event): # Update child transceivers for trx in self.child_trx_list.trx_list: if event == "POWERON": trx.running = True else: trx.running = False # Trigger clock generator if required if self.clck_gen is not None: clck_links = self.clck_gen.clck_links if not self.running and (self.clck_if in clck_links): # Transceiver was stopped clck_links.remove(self.clck_if) elif self.running and (self.clck_if not in clck_links): # Transceiver was started clck_links.append(self.clck_if) if not self.clck_gen.timer and len(clck_links) > 0: log.info("Starting clock generator") self.clck_gen.start() elif self.clck_gen.timer and not clck_links: log.info("Stopping clock generator") self.clck_gen.stop() def recv_data_msg(self): # Read and parse data from socket msg = self.data_if.recv_l12trx_msg() if not msg: return None # Make sure that transceiver is configured and running if not self.running: log.warning("(%s) RX TRXD message (%s), but transceiver " "is not running => dropping..." % (self, msg.desc_hdr())) return None # Make sure that indicated timeslot is configured if msg.tn not in self.ts_list: log.warning("(%s) RX TRXD message (%s), but timeslot is not " "configured => dropping..." % (self, msg.desc_hdr())) return None return msg