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" 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)
class Application(ApplicationBase): # Counters cnt_burst_dropped_num = 0 cnt_burst_num = 0 cnt_frame_last = None cnt_frame_num = 0 # Internal variables lo_trigger = False def __init__(self): self.app_print_copyright(APP_CR_HOLDERS) self.argv = self.parse_argv() # 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): # Compose a list of permitted UDP ports rx_port_list = [ "port %d" % (port + 102) for port in self.argv.base_ports ] tx_port_list = [ "port %d" % (port + 2) for port in self.argv.base_ports ] # Arguments to be passed to scapy.all.sniff() sniff_args = { "filter": "udp and (%s)" % " or ".join(rx_port_list + tx_port_list), "prn": self.pkt_handler, "store": 0, } if self.argv.cap_file is not None: log.info("Reading packets from '%s'..." % self.argv.cap_file) sniff_args["offline"] = self.argv.cap_file else: log.info("Listening on interface '%s'..." % self.argv.sniff_if) sniff_args["iface"] = self.argv.sniff_if if self.argv.cap_filter is not None: log.info("Using additional capture filter '%s'" % self.argv.cap_filter) sniff_args["filter"] += " and (%s)" % self.argv.cap_filter # Start sniffing... scapy.all.sniff(**sniff_args) # Scapy registers its own signal handler self.shutdown() def pkt_handler(self, ether): # Prevent loopback packet duplication if self.argv.sniff_if == "lo" and self.argv.cap_file is None: self.lo_trigger = not self.lo_trigger if not self.lo_trigger: return # Extract a TRX payload ip = ether.payload udp = ip.payload trx = udp.payload # Convert to bytearray msg_raw = bytearray(trx.load) # Determine a burst direction (L1 <-> TRX) l12trx = udp.sport > udp.dport # Create an empty DATA message msg = DATAMSG_L12TRX() if l12trx else DATAMSG_TRX2L1() # Attempt to parse the payload as a DATA message try: msg.parse_msg(msg_raw) msg.validate() except ValueError as e: desc = msg.desc_hdr() if desc == "": desc = "parsing error" log.warning("Ignoring an incorrect message (%s): %s" % (desc, e)) self.cnt_burst_dropped_num += 1 return # Poke burst pass filter if not self.burst_pass_filter(msg): self.cnt_burst_dropped_num += 1 return # Debug print log.debug("%s burst: %s" \ % ("L1 -> TRX" if l12trx else "TRX -> L1", msg.desc_hdr())) # Poke message handler self.msg_handle(msg) # Poke burst counter rc = self.burst_count(msg.fn, msg.tn) if rc is True: self.shutdown() def burst_pass_filter(self, msg): # Direction filter if self.argv.direction is not None: if self.argv.direction == "TRX": # L1 -> TRX if not isinstance(msg, DATAMSG_L12TRX): return False elif self.argv.direction == "L1": # TRX -> L1 if not isinstance(msg, DATAMSG_TRX2L1): 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 # Message type specific filtering if isinstance(msg, DATAMSG_TRX2L1): # NOPE.ind filter if not self.argv.pf_nope_ind and msg.nope_ind: return False # RSSI filter if self.argv.pf_rssi_min is not None: if msg.rssi < self.argv.pf_rssi_min: return False if self.argv.pf_rssi_max is not None: if msg.rssi > self.argv.pf_rssi_max: return False # Burst passed ;) return True def msg_handle(self, msg): if self.argv.verbose: print(msg.burst) # Append a new message to the capture if self.argv.output_file is not None: self.ddf.append_msg(msg) def burst_count(self, fn, tn): # Update frame counter if self.cnt_frame_last is None: self.cnt_frame_last = fn self.cnt_frame_num += 1 else: if fn != self.cnt_frame_last: self.cnt_frame_num += 1 # Update burst counter self.cnt_burst_num += 1 # Stop sniffing after N bursts if self.argv.burst_count is not None: if self.cnt_burst_num == self.argv.burst_count: log.info("Collected required amount of bursts") return True # Stop sniffing after N frames if self.argv.frame_count is not None: if self.cnt_frame_num == self.argv.frame_count: log.info("Collected required amount of frames") return True return False def shutdown(self): log.info("Shutting down...") # Print statistics log.info("%u bursts handled, %u dropped" \ % (self.cnt_burst_num, self.cnt_burst_dropped_num)) # Exit sys.exit(0) def parse_argv(self): parser = argparse.ArgumentParser( prog="trx_sniff", description="Scapy-based TRX interface sniffer") parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Print burst bits to stdout") # Register common logging options self.app_reg_logging_options(parser) trx_group = parser.add_argument_group("TRX interface") trx_group.add_argument( "-p", "--base-port", "--base-ports", dest="base_ports", type=int, metavar="PORT", default=[5700, 6700], nargs="*", help="Set base port number (default %(default)s)") trx_group.add_argument("-o", "--output-file", metavar="FILE", dest="output_file", type=str, help="Write bursts to a capture file") input_group = trx_group.add_mutually_exclusive_group() input_group.add_argument( "-i", "--sniff-interface", dest="sniff_if", type=str, default="lo", metavar="IF", help="Set network interface (default '%(default)s')") input_group.add_argument("-r", "--capture-file", dest="cap_file", type=str, metavar="FILE", help="Read packets from a PCAP file") trx_group.add_argument( "-f", "--capture-filter", dest="cap_filter", type=str, metavar="FILTER", help="Set additional capture filter (e.g. 'host 192.168.1.2')") cnt_group = parser.add_argument_group("Count limitations (optional)") cnt_group.add_argument("--frame-count", metavar="N", dest="frame_count", type=int, help="Stop after sniffing N frames") cnt_group.add_argument("--burst-count", metavar="N", dest="burst_count", type=int, help="Stop after sniffing N bursts") pf_group = parser.add_argument_group("Filtering (optional)") pf_group.add_argument("--direction", dest="direction", type=str, choices=["TRX", "L1"], help="Burst direction") 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)") pf_group.add_argument("--no-nope-ind", dest="pf_nope_ind", action="store_false", help="Ignore NOPE.ind (NOPE / IDLE indications)") pf_group.add_argument("--rssi-min", metavar="RSSI", dest="pf_rssi_min", type=int, help="Minimum RSSI value (e.g. -75)") pf_group.add_argument("--rssi-max", metavar="RSSI", dest="pf_rssi_max", type=int, help="Maximum RSSI value (e.g. -50)") return parser.parse_args()
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 sniff_interface = "lo" sniff_base_port = 5700 print_bursts = False output_file = None # Counters cnt_burst_dropped_num = 0 cnt_burst_break = None cnt_burst_num = 0 cnt_frame_break = None cnt_frame_last = None cnt_frame_num = 0 # Burst direction fliter bf_dir_l12trx = None # Timeslot number filter bf_tn_val = None # Frame number fliter bf_fn_lt = None bf_fn_gt = None # Internal variables lo_trigger = False def __init__(self): print_copyright(CR_HOLDERS) self.parse_argv() # Open requested capture file if self.output_file is not None: self.ddf = DATADumpFile(self.output_file) def run(self): # Compose a packet filter pkt_filter = "udp and (port %d or port %d)" \ % (self.sniff_base_port + 2, self.sniff_base_port + 102) print("[i] Listening on interface '%s'..." % self.sniff_interface) # Start sniffing... scapy.all.sniff(iface = self.sniff_interface, store = 0, filter = pkt_filter, prn = self.pkt_handler) # Scapy registers its own signal handler self.shutdown() def pkt_handler(self, ether): # Prevent loopback packet duplication if self.sniff_interface == "lo": self.lo_trigger = not self.lo_trigger if not self.lo_trigger: return # Extract a TRX payload ip = ether.payload udp = ip.payload trx = udp.payload # Convert to bytearray msg_raw = bytearray(str(trx)) # Determine a burst direction (L1 <-> TRX) l12trx = udp.sport > udp.dport # Create an empty DATA message msg = DATAMSG_L12TRX() if l12trx else DATAMSG_TRX2L1() # Attempt to parse the payload as a DATA message try: msg.parse_msg(msg_raw) except: print("[!] Failed to parse message, dropping...") self.cnt_burst_dropped_num += 1 return # Poke burst pass filter rc = self.burst_pass_filter(l12trx, msg.fn, msg.tn) if rc is False: self.cnt_burst_dropped_num += 1 return # Debug print print("[i] %s burst: %s" \ % ("L1 -> TRX" if l12trx else "TRX -> L1", msg.desc_hdr())) # Poke message handler self.msg_handle(msg) # Poke burst counter rc = self.burst_count(msg.fn, msg.tn) if rc is True: self.shutdown() def burst_pass_filter(self, l12trx, fn, tn): # Direction filter if self.bf_dir_l12trx is not None: if l12trx != self.bf_dir_l12trx: return False # Timeslot filter if self.bf_tn_val is not None: if tn != self.bf_tn_val: return False # Frame number filter if self.bf_fn_lt is not None: if fn > self.bf_fn_lt: return False if self.bf_fn_gt is not None: if fn < self.bf_fn_gt: return False # Burst passed ;) return True def msg_handle(self, msg): if self.print_bursts: print(msg.burst) # Append a new message to the capture if self.output_file is not None: self.ddf.append_msg(msg) def burst_count(self, fn, tn): # Update frame counter if self.cnt_frame_last is None: self.cnt_frame_last = fn self.cnt_frame_num += 1 else: if fn != self.cnt_frame_last: self.cnt_frame_num += 1 # Update burst counter self.cnt_burst_num += 1 # Stop sniffing after N bursts if self.cnt_burst_break is not None: if self.cnt_burst_num == self.cnt_burst_break: print("[i] Collected required amount of bursts") return True # Stop sniffing after N frames if self.cnt_frame_break is not None: if self.cnt_frame_num == self.cnt_frame_break: print("[i] Collected required amount of frames") return True return False def shutdown(self): print("[i] Shutting down...") # Print statistics print("[i] %u bursts handled, %u dropped" \ % (self.cnt_burst_num, self.cnt_burst_dropped_num)) # Exit sys.exit(0) def print_help(self, msg = None): s = " Usage: " + sys.argv[0] + " [options]\n\n" \ " Some help...\n" \ " -h --help this text\n\n" s += " Sniffing options\n" \ " -i --sniff-interface Set network interface (default '%s')\n" \ " -p --sniff-base-port Set base port number (default %d)\n\n" s += " Processing (no processing by default)\n" \ " -o --output-file Write bursts to file\n" \ " -v --print-bits Print burst bits to stdout\n\n" \ s += " Count limitations (disabled by default)\n" \ " --frame-count NUM Stop after sniffing NUM frames\n" \ " --burst-count NUM Stop after sniffing NUM bursts\n\n" s += " Filtering (disabled by default)\n" \ " --direction DIR Burst direction: L12TRX or TRX2L1\n" \ " --timeslot NUM TDMA timeslot number [0..7]\n" \ " --frame-num-lt NUM TDMA frame number lower than NUM\n" \ " --burst-num-gt NUM TDMA frame number greater than NUM\n" print(s % (self.sniff_interface, self.sniff_base_port)) if msg is not None: print(msg) def parse_argv(self): try: opts, args = getopt.getopt(sys.argv[1:], "i:p:o:v:h", ["help", "sniff-interface=", "sniff-base-port=", "frame-count=", "burst-count=", "direction=", "timeslot=", "frame-num-lt=", "frame-num-gt=", "output-file=", "print-bits"]) 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 ("-i", "--sniff-interface"): self.sniff_interface = v elif o in ("-p", "--sniff-base-port"): self.sniff_base_port = int(v) elif o in ("-o", "--output-file"): self.output_file = v elif o in ("-v", "--print-bits"): self.print_bursts = True # Break counters elif o == "--frame-count": self.cnt_frame_break = int(v) elif o == "--burst-count": self.cnt_burst_break = int(v) # Direction filter elif o == "--direction": if v == "L12TRX": self.bf_dir_l12trx = True elif v == "TRX2L1": self.bf_dir_l12trx = False else: self.print_help("[!] Wrong direction argument") sys.exit(2) # Timeslot pass filter elif o == "--timeslot": self.bf_tn_val = int(v) if self.bf_tn_val < 0 or self.bf_tn_val > 7: self.print_help("[!] Wrong timeslot value") sys.exit(2) # Frame number pass filter elif o == "--frame-num-lt": self.bf_fn_lt = int(v) elif o == "--frame-num-gt": self.bf_fn_gt = int(v)
class Application(ApplicationBase): # Counters cnt_burst_dropped_num = 0 cnt_burst_num = 0 cnt_frame_last = None cnt_frame_num = 0 # Internal variables lo_trigger = False def __init__(self): self.app_print_copyright(APP_CR_HOLDERS) self.argv = self.parse_argv() # 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): # Compose a packet filter pkt_filter = "udp and (port %d or port %d)" \ % (self.argv.base_port + 2, self.argv.base_port + 102) log.info("Listening on interface '%s'..." % self.argv.sniff_if) # Start sniffing... scapy.all.sniff(iface=self.argv.sniff_if, store=0, filter=pkt_filter, prn=self.pkt_handler) # Scapy registers its own signal handler self.shutdown() def pkt_handler(self, ether): # Prevent loopback packet duplication if self.argv.sniff_if == "lo": self.lo_trigger = not self.lo_trigger if not self.lo_trigger: return # Extract a TRX payload ip = ether.payload udp = ip.payload trx = udp.payload # Convert to bytearray msg_raw = bytearray(str(trx)) # Determine a burst direction (L1 <-> TRX) l12trx = udp.sport > udp.dport # Create an empty DATA message msg = DATAMSG_L12TRX() if l12trx else DATAMSG_TRX2L1() # Attempt to parse the payload as a DATA message try: msg.parse_msg(msg_raw) except: log.warning("Failed to parse message, dropping...") self.cnt_burst_dropped_num += 1 return # Poke burst pass filter rc = self.burst_pass_filter(l12trx, msg.fn, msg.tn) if rc is False: self.cnt_burst_dropped_num += 1 return # Debug print log.debug("%s burst: %s" \ % ("L1 -> TRX" if l12trx else "TRX -> L1", msg.desc_hdr())) # Poke message handler self.msg_handle(msg) # Poke burst counter rc = self.burst_count(msg.fn, msg.tn) if rc is True: self.shutdown() def burst_pass_filter(self, l12trx, fn, tn): # Direction filter if self.argv.direction is not None: if self.argv.direction == "TRX" and not l12trx: return False elif self.argv.direction == "L1" and l12trx: return False # Timeslot filter if self.argv.pf_tn is not None: if tn != self.argv.pf_tn: return False # Frame number filter if self.argv.pf_fn_lt is not None: if fn > self.argv.pf_fn_lt: return False if self.argv.pf_fn_gt is not None: if fn < self.argv.pf_fn_gt: return False # Burst passed ;) return True def msg_handle(self, msg): if self.argv.verbose: print(msg.burst) # Append a new message to the capture if self.argv.output_file is not None: self.ddf.append_msg(msg) def burst_count(self, fn, tn): # Update frame counter if self.cnt_frame_last is None: self.cnt_frame_last = fn self.cnt_frame_num += 1 else: if fn != self.cnt_frame_last: self.cnt_frame_num += 1 # Update burst counter self.cnt_burst_num += 1 # Stop sniffing after N bursts if self.argv.burst_count is not None: if self.cnt_burst_num == self.argv.burst_count: log.info("Collected required amount of bursts") return True # Stop sniffing after N frames if self.argv.frame_count is not None: if self.cnt_frame_num == self.argv.frame_count: log.info("Collected required amount of frames") return True return False def shutdown(self): log.info("Shutting down...") # Print statistics log.info("%u bursts handled, %u dropped" \ % (self.cnt_burst_num, self.cnt_burst_dropped_num)) # Exit sys.exit(0) def parse_argv(self): parser = argparse.ArgumentParser( prog="trx_sniff", description="Scapy-based TRX interface sniffer") parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Print burst bits to stdout") # Register common logging options self.app_reg_logging_options(parser) trx_group = parser.add_argument_group("TRX interface") trx_group.add_argument( "-i", "--sniff-interface", dest="sniff_if", type=str, default="lo", metavar="IF", help="Set network interface (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("-o", "--output-file", metavar="FILE", dest="output_file", type=str, help="Write bursts to a capture file") cnt_group = parser.add_argument_group("Count limitations (optional)") cnt_group.add_argument("--frame-count", metavar="N", dest="frame_count", type=int, help="Stop after sniffing N frames") cnt_group.add_argument("--burst-count", metavar="N", dest="burst_count", type=int, help="Stop after sniffing N bursts") pf_group = parser.add_argument_group("Filtering (optional)") pf_group.add_argument("--direction", dest="direction", type=str, choices=["TRX", "L1"], help="Burst direction") 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()
class Application(ApplicationBase): # Counters cnt_burst_dropped_num = 0 cnt_burst_num = 0 cnt_frame_last = None cnt_frame_num = 0 # Internal variables lo_trigger = False def __init__(self): self.app_print_copyright(APP_CR_HOLDERS) self.argv = self.parse_argv() # 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): # Compose a packet filter pkt_filter = "udp and (port %d or port %d)" \ % (self.argv.base_port + 2, self.argv.base_port + 102) log.info("Listening on interface '%s'..." % self.argv.sniff_if) # Start sniffing... scapy.all.sniff(iface = self.argv.sniff_if, store = 0, filter = pkt_filter, prn = self.pkt_handler) # Scapy registers its own signal handler self.shutdown() def pkt_handler(self, ether): # Prevent loopback packet duplication if self.argv.sniff_if == "lo": self.lo_trigger = not self.lo_trigger if not self.lo_trigger: return # Extract a TRX payload ip = ether.payload udp = ip.payload trx = udp.payload # Convert to bytearray msg_raw = bytearray(str(trx)) # Determine a burst direction (L1 <-> TRX) l12trx = udp.sport > udp.dport # Create an empty DATA message msg = DATAMSG_L12TRX() if l12trx else DATAMSG_TRX2L1() # Attempt to parse the payload as a DATA message try: msg.parse_msg(msg_raw) except: log.warning("Failed to parse message, dropping...") self.cnt_burst_dropped_num += 1 return # Poke burst pass filter rc = self.burst_pass_filter(l12trx, msg.fn, msg.tn) if rc is False: self.cnt_burst_dropped_num += 1 return # Debug print log.debug("%s burst: %s" \ % ("L1 -> TRX" if l12trx else "TRX -> L1", msg.desc_hdr())) # Poke message handler self.msg_handle(msg) # Poke burst counter rc = self.burst_count(msg.fn, msg.tn) if rc is True: self.shutdown() def burst_pass_filter(self, l12trx, fn, tn): # Direction filter if self.argv.direction is not None: if self.argv.direction == "TRX" and not l12trx: return False elif self.argv.direction == "L1" and l12trx: return False # Timeslot filter if self.argv.pf_tn is not None: if tn != self.argv.pf_tn: return False # Frame number filter if self.argv.pf_fn_lt is not None: if fn > self.argv.pf_fn_lt: return False if self.argv.pf_fn_gt is not None: if fn < self.argv.pf_fn_gt: return False # Burst passed ;) return True def msg_handle(self, msg): if self.argv.verbose: print(msg.burst) # Append a new message to the capture if self.argv.output_file is not None: self.ddf.append_msg(msg) def burst_count(self, fn, tn): # Update frame counter if self.cnt_frame_last is None: self.cnt_frame_last = fn self.cnt_frame_num += 1 else: if fn != self.cnt_frame_last: self.cnt_frame_num += 1 # Update burst counter self.cnt_burst_num += 1 # Stop sniffing after N bursts if self.argv.burst_count is not None: if self.cnt_burst_num == self.argv.burst_count: log.info("Collected required amount of bursts") return True # Stop sniffing after N frames if self.argv.frame_count is not None: if self.cnt_frame_num == self.argv.frame_count: log.info("Collected required amount of frames") return True return False def shutdown(self): log.info("Shutting down...") # Print statistics log.info("%u bursts handled, %u dropped" \ % (self.cnt_burst_num, self.cnt_burst_dropped_num)) # Exit sys.exit(0) def parse_argv(self): parser = argparse.ArgumentParser(prog = "trx_sniff", description = "Scapy-based TRX interface sniffer") parser.add_argument("-v", "--verbose", dest = "verbose", action = "store_true", help = "Print burst bits to stdout") # Register common logging options self.app_reg_logging_options(parser) trx_group = parser.add_argument_group("TRX interface") trx_group.add_argument("-i", "--sniff-interface", dest = "sniff_if", type = str, default = "lo", metavar = "IF", help = "Set network interface (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("-o", "--output-file", metavar = "FILE", dest = "output_file", type = str, help = "Write bursts to a capture file") cnt_group = parser.add_argument_group("Count limitations (optional)") cnt_group.add_argument("--frame-count", metavar = "N", dest = "frame_count", type = int, help = "Stop after sniffing N frames") cnt_group.add_argument("--burst-count", metavar = "N", dest = "burst_count", type = int, help = "Stop after sniffing N bursts") pf_group = parser.add_argument_group("Filtering (optional)") pf_group.add_argument("--direction", dest = "direction", type = str, choices = ["TRX", "L1"], help = "Burst direction") 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()
class Application: # Application variables sniff_interface = "lo" sniff_base_port = 5700 print_bursts = False output_file = None # Counters cnt_burst_dropped_num = 0 cnt_burst_break = None cnt_burst_num = 0 cnt_frame_break = None cnt_frame_last = None cnt_frame_num = 0 # Burst direction fliter bf_dir_l12trx = None # Timeslot number filter bf_tn_val = None # Frame number fliter bf_fn_lt = None bf_fn_gt = None # Internal variables lo_trigger = False def __init__(self): print_copyright(CR_HOLDERS) self.parse_argv() # Open requested capture file if self.output_file is not None: self.ddf = DATADumpFile(self.output_file) def run(self): # Compose a packet filter pkt_filter = "udp and (port %d or port %d)" \ % (self.sniff_base_port + 2, self.sniff_base_port + 102) print("[i] Listening on interface '%s'..." % self.sniff_interface) # Start sniffing... scapy.all.sniff(iface=self.sniff_interface, store=1, filter=pkt_filter, prn=self.pkt_handler) # Scapy registers its own signal handler self.shutdown() def pkt_handler(self, ether): # Prevent loopback packet duplication if self.sniff_interface == "lo": self.lo_trigger = not self.lo_trigger if not self.lo_trigger: return # Extract a TRX payload ip = ether.payload udp = ip.payload trx = udp.payload # Convert to bytearray msg_raw = bytearray(str(trx)) # Determine a burst direction (L1 <-> TRX) l12trx = udp.sport > udp.dport # Create an empty DATA message msg = DATAMSG_L12TRX() if l12trx else DATAMSG_TRX2L1() # Attempt to parse the payload as a DATA message try: msg.parse_msg(msg_raw) except: print("[!] Failed to parse message, dropping...") self.cnt_burst_dropped_num += 1 return # Poke burst pass filter rc = self.burst_pass_filter(l12trx, msg.fn, msg.tn) if rc is False: self.cnt_burst_dropped_num += 1 return # Debug print print("[i] %s burst: %s" \ % ("L1 -> TRX" if l12trx else "TRX -> L1", msg.desc_hdr())) # Poke message handler self.msg_handle(msg) # Poke burst counter rc = self.burst_count(msg.fn, msg.tn) if rc is True: self.shutdown() def burst_pass_filter(self, l12trx, fn, tn): # Direction filter if self.bf_dir_l12trx is not None: if l12trx != self.bf_dir_l12trx: return False # Timeslot filter if self.bf_tn_val is not None: if tn != self.bf_tn_val: return False # Frame number filter if self.bf_fn_lt is not None: if fn > self.bf_fn_lt: return False if self.bf_fn_gt is not None: if fn < self.bf_fn_gt: return False # Burst passed ;) return True def msg_handle(self, msg): if self.print_bursts: print(msg.burst) # Append a new message to the capture if self.output_file is not None: self.ddf.append_msg(msg) def burst_count(self, fn, tn): # Update frame counter if self.cnt_frame_last is None: self.cnt_frame_last = fn self.cnt_frame_num += 1 else: if fn != self.cnt_frame_last: self.cnt_frame_num += 1 # Update burst counter self.cnt_burst_num += 1 # Stop sniffing after N bursts if self.cnt_burst_break is not None: if self.cnt_burst_num == self.cnt_burst_break: print("[i] Collected required amount of bursts") return True # Stop sniffing after N frames if self.cnt_frame_break is not None: if self.cnt_frame_num == self.cnt_frame_break: print("[i] Collected required amount of frames") return True return False def shutdown(self): print("[i] Shutting down...") # Print statistics print("[i] %u bursts handled, %u dropped" \ % (self.cnt_burst_num, self.cnt_burst_dropped_num)) # Exit sys.exit(0) def print_help(self, msg=None): s = " Usage: " + sys.argv[0] + " [options]\n\n" \ " Some help...\n" \ " -h --help this text\n\n" s += " Sniffing options\n" \ " -i --sniff-interface Set network interface (default '%s')\n" \ " -p --sniff-base-port Set base port number (default %d)\n\n" s += " Processing (no processing by default)\n" \ " -o --output-file Write bursts to file\n" \ " -v --print-bits Print burst bits to stdout\n\n" \ s += " Count limitations (disabled by default)\n" \ " --frame-count NUM Stop after sniffing NUM frames\n" \ " --burst-count NUM Stop after sniffing NUM bursts\n\n" s += " Filtering (disabled by default)\n" \ " --direction DIR Burst direction: L12TRX or TRX2L1\n" \ " --timeslot NUM TDMA timeslot number [0..7]\n" \ " --frame-num-lt NUM TDMA frame number lower than NUM\n" \ " --burst-num-gt NUM TDMA frame number greater than NUM\n" print(s % (self.sniff_interface, self.sniff_base_port)) if msg is not None: print(msg) def parse_argv(self): try: opts, args = getopt.getopt(sys.argv[1:], "i:p:o:v:h", [ "help", "sniff-interface=", "sniff-base-port=", "frame-count=", "burst-count=", "direction=", "timeslot=", "frame-num-lt=", "frame-num-gt=", "output-file=", "print-bits" ]) 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 ("-i", "--sniff-interface"): self.sniff_interface = v elif o in ("-p", "--sniff-base-port"): self.sniff_base_port = int(v) elif o in ("-o", "--output-file"): self.output_file = v elif o in ("-v", "--print-bits"): self.print_bursts = True # Break counters elif o == "--frame-count": self.cnt_frame_break = int(v) elif o == "--burst-count": self.cnt_burst_break = int(v) # Direction filter elif o == "--direction": if v == "L12TRX": self.bf_dir_l12trx = True elif v == "TRX2L1": self.bf_dir_l12trx = False else: self.print_help("[!] Wrong direction argument") sys.exit(2) # Timeslot pass filter elif o == "--timeslot": self.bf_tn_val = int(v) if self.bf_tn_val < 0 or self.bf_tn_val > 7: self.print_help("[!] Wrong timeslot value") sys.exit(2) # Frame number pass filter elif o == "--frame-num-lt": self.bf_fn_lt = int(v) elif o == "--frame-num-gt": self.bf_fn_gt = int(v)