commit
39cfbf435d
7 changed files with 410 additions and 0 deletions
@ -0,0 +1,27 @@
|
||||
Osmocom E1 recorder |
||||
=================== |
||||
(C) 2016 by Harald Welte <laforge@gnumonks.org> |
||||
|
||||
The idea of this program is to be able to passively record E1/T1 based |
||||
communications for purposes of data analysis. |
||||
|
||||
Recording of a single E1 link always requires two E1 interface cards, |
||||
one for each direction. |
||||
|
||||
Recording can be performed either |
||||
* passively, using a E1 Tap aapter |
||||
* asa proxy / man-in-the-middle |
||||
|
||||
All timeslots will be opened in "raw" mode, making sure the recording |
||||
will work wheter or not there is HLDC-based signalling (MTP or LAPD), |
||||
PCM voice, TRAU frames or anything else on the line. |
||||
|
||||
Recording will be done on a per-timeslot basis, dumping the raw bytes |
||||
read for this timeslot into a file. |
||||
|
||||
New files are started regularly, after reaching a pre-determined file |
||||
size limit. File names contain RTC time stamping and timeslot number. |
||||
|
||||
Later possible extensions could include automatic detection of the |
||||
payload and a more intelligent storage format (e.g. in case of HDLC |
||||
bsaed signalling). |
@ -0,0 +1,13 @@
|
||||
CFLAGS=-g -Wall
|
||||
LDFLAGS=-losmocore -losmogsm -losmovty -losmoabis -ltalloc
|
||||
|
||||
all: osmo-e1-recorder |
||||
|
||||
osmo-e1-recorder: e1_recorder.o storage.o vty.o |
||||
$(CC) $(LDFLAGS) -o$@ $^
|
||||
|
||||
%.o: %.c |
||||
$(CC) $(CFLAGS) -o $@ -c $^
|
||||
|
||||
clean: |
||||
@rm *.o osmo-e1-recorder
|
@ -0,0 +1,97 @@
|
||||
#include <osmocom/core/signal.h> |
||||
#include <osmocom/core/logging.h> |
||||
#include <osmocom/core/application.h> |
||||
#include <osmocom/vty/vty.h> |
||||
#include <osmocom/vty/telnet_interface.h> |
||||
|
||||
#include <osmocom/abis/abis.h> |
||||
#include <osmocom/abis/e1_input.h> |
||||
|
||||
#include "storage.h" |
||||
#include "recorder.h" |
||||
|
||||
static enum osmo_e1cap_capture_mode ts2cap_mode(struct e1inp_ts *ts) |
||||
{ |
||||
switch (ts->type) { |
||||
case E1INP_TS_TYPE_RAW: |
||||
return OSMO_E1CAP_MODE_RAW; |
||||
case E1INP_TS_TYPE_SIGN: |
||||
return OSMO_E1CAP_MODE_HDLC; |
||||
case E1INP_TS_TYPE_TRAU: |
||||
return OSMO_E1CAP_MODE_TRAU; |
||||
default: |
||||
OSMO_ASSERT(0); |
||||
} |
||||
} |
||||
|
||||
/* receive a raw message frome the E1 timeslot */ |
||||
void e1ts_raw_recv(struct e1inp_ts *ts, struct msgb *msg) |
||||
{ |
||||
struct e1_recorder_line *rline = &g_recorder.line[ts->line->nr]; |
||||
enum osmo_e1cap_capture_mode cap_mode = ts2cap_mode(ts); |
||||
|
||||
/* FIXME: special processing of TFP and PGSL */ |
||||
|
||||
e1frame_store(ts, msg, cap_mode); |
||||
|
||||
if (rline.mirror.enabled) { |
||||
/* forward data to destination line */ |
||||
} |
||||
} |
||||
|
||||
static int inp_sig_cb(unsigned int subsys, unsigned int signal, |
||||
void *handler_data, void *signal_data) |
||||
{ |
||||
OSMO_ASSERT(subsys == SS_L_INPUT); |
||||
|
||||
/* FIXME */ |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
// e1inp_ts_config_raw(ts, line, &e1ts_raw_recv);
|
||||
|
||||
static const struct log_info_cat recorder_categories[] = { |
||||
[DMAIN] = { |
||||
.name = "MAIN", |
||||
.enabled = 1, .loglevel = LOGL_DEBUG, |
||||
}, |
||||
}; |
||||
static struct log_info info = { |
||||
.cat = recorder_categories, |
||||
.num_cat = ARRAY_SIZE(recorder_categories), |
||||
}; |
||||
|
||||
struct vty_app_info vty_info = { |
||||
.name = "osmo-e1-recorder", |
||||
.version = "0", |
||||
.copyright = "(C) 2016 by Harald Welte <laforge@gnumonks.org>\n", |
||||
}; |
||||
|
||||
static void *rec_tall_ctx; |
||||
struct e1_recorder g_recorder; |
||||
|
||||
int main(int argc, char **argv) |
||||
{ |
||||
int rc; |
||||
|
||||
rec_tall_ctx = talloc_named_const(NULL, 0, "recorder"); |
||||
|
||||
osmo_init_logging(&info); |
||||
vty_init(&vty_info); |
||||
osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL); |
||||
libosmo_abis_init(rec_tall_ctx); |
||||
e1inp_vty_init(); |
||||
recorder_vty_init(); |
||||
|
||||
rc = vty_read_config_file("osmo-e1-recorder.cfg", NULL); |
||||
if (rc < 0) |
||||
exit(1); |
||||
|
||||
/* start telne tafte reading config for vty_get_bind_adr() */ |
||||
telnet_init_dynif(rec_tall_ctx, NULL, vty_get_bind_addr(), 4444); |
||||
|
||||
while (1) { |
||||
osmo_select_main(0); |
||||
}; |
||||
} |
@ -0,0 +1,37 @@
|
||||
#pragma once |
||||
#include <stdbool.h> |
||||
|
||||
#include <osmocom/core/msgb.h> |
||||
#include <osmocom/vty/command.h> |
||||
#include <osmocom/abis/e1_input.h> |
||||
|
||||
/* logging */ |
||||
enum { |
||||
DMAIN, |
||||
}; |
||||
|
||||
/* vty */ |
||||
enum rec_vty_node { |
||||
RECORDER_NODE = _LAST_OSMOVTY_NODE + 1, |
||||
}; |
||||
|
||||
struct e1_recorder_line { |
||||
struct { |
||||
bool enabled; |
||||
uint8_t line_nr; |
||||
} mirror; |
||||
}; |
||||
|
||||
struct e1_recorder { |
||||
char *storage_path; |
||||
unsigned int max_file_size_mb; |
||||
struct e1_recorder_line line[256]; |
||||
}; |
||||
|
||||
extern struct e1_recorder g_recorder; |
||||
|
||||
/* e1_recorder.c */ |
||||
void e1ts_raw_recv(struct e1inp_ts *ts, struct msgb *msg); |
||||
|
||||
/* vty.c */ |
||||
void recorder_vty_init(void); |
@ -0,0 +1,20 @@
|
||||
#include <osmocom/core/msgb.h> |
||||
#include <osmocom/abis/e1_input.h> |
||||
|
||||
#include "storage.h" |
||||
|
||||
int e1frame_store(struct e1inp_ts *ts, struct msgb *msg, enum osmo_e1cap_capture_mode mode) |
||||
{ |
||||
struct osmo_e1cap_pkthdr *h; |
||||
uint32_t len = msg->len; |
||||
|
||||
h = (struct osmo_e1cap_pkthdr *) msgb_push(msg, sizeof(*h)); |
||||
h->len = htonl(len); |
||||
h->line_nr = ts->line->num; |
||||
h->ts_nr = ts->num; |
||||
h->capture_mode = mode; |
||||
h->flags = 0; |
||||
|
||||
/* FIXME: Write */ |
||||
return 0; |
||||
} |
@ -0,0 +1,21 @@
|
||||
#pragma once |
||||
#include <stdint.h> |
||||
|
||||
enum osmo_e1cap_capture_mode { |
||||
OSMO_E1CAP_MODE_RAW, |
||||
OSMO_E1CAP_MODE_HDLC, |
||||
OSMO_E1CAP_MODE_TRAU, |
||||
OSMO_E1CAP_MODE_PGSL, |
||||
}; |
||||
|
||||
/* header for each frame we store */ |
||||
struct osmo_e1cap_pkthdr { |
||||
struct timeval ts; |
||||
uint32_t len; |
||||
uint8_t line_nr; |
||||
uint8_t ts_nr; |
||||
uint8_t capture_mode; |
||||
uint8_t flags; |
||||
} __attribute__((aligned)); |
||||
|
||||
int e1frame_store(struct e1inp_ts *ts, struct msgb *msg, enum osmo_e1cap_capture_mode mode); |
@ -0,0 +1,195 @@
|
||||
|
||||
#include <osmocom/abis/e1_input.h> |
||||
#include <osmocom/vty/command.h> |
||||
#include <osmocom/vty/vty.h> |
||||
|
||||
#include "recorder.h" |
||||
|
||||
#define LINE_STR "Configure Recording for given Line\nE1/T1 Line Number\n" |
||||
|
||||
DEFUN(cfg_recorder, cfg_recorder_cmd, |
||||
"recorder", |
||||
"Configuration of E1 Recorder\n") |
||||
{ |
||||
vty->node = RECORDER_NODE; |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(cfg_rec_line_ts_mode, cfg_rec_line_ts_mode_cmd, |
||||
"line <0-255> ts <0-31> mode (none|signalling|trau|raw)", |
||||
LINE_STR |
||||
"E1/T1 Timeslot Number\n" |
||||
"E1/T1 Timeslot Number\n" |
||||
"Recording Mode\n" |
||||
"No recording\n" |
||||
"Signalling Data (HDLC)\n" |
||||
"TRAU Frames\n" |
||||
"Raw Data\n") |
||||
{ |
||||
int line_nr = atoi(argv[0]); |
||||
int ts_nr = atoi(argv[1]); |
||||
int mode = get_string_value(e1inp_ts_type_names, argv[2]); |
||||
struct e1inp_line *line; |
||||
struct e1inp_ts *ts; |
||||
|
||||
if (mode < 0) { |
||||
vty_out(vty, "Cannot parse mode %s%s", argv[2], VTY_NEWLINE); |
||||
return CMD_WARNING; |
||||
} |
||||
|
||||
line = e1inp_line_find(line_nr); |
||||
if (!line) { |
||||
vty_out(vty, "Cannot find line %d%s", line_nr, VTY_NEWLINE); |
||||
return CMD_WARNING; |
||||
} |
||||
if (ts_nr >= line->num_ts) { |
||||
vty_out(vty, "Timeslot %d is too large%s", ts_nr, VTY_NEWLINE); |
||||
return CMD_WARNING; |
||||
} |
||||
ts = &line->ts[ts_nr]; |
||||
|
||||
switch (mode) { |
||||
case E1INP_TS_TYPE_NONE: |
||||
/* TOOD: have eqinp_ts_config_none ? */ |
||||
ts->type = E1INP_TS_TYPE_NONE; |
||||
break; |
||||
case E1INP_TS_TYPE_SIGN: |
||||
e1inp_ts_config_sign(ts, line); |
||||
break; |
||||
case E1INP_TS_TYPE_RAW: |
||||
e1inp_ts_config_raw(ts, line, &e1ts_raw_recv); |
||||
break; |
||||
} |
||||
|
||||
/* notify driver of change */ |
||||
e1inp_line_update(line); |
||||
|
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(cfg_rec_line_mirror, cfg_rec_line_mirror_cmd, |
||||
"line <0-255> mirror <0-255>", |
||||
LINE_STR "Mirror this line to another line\n" |
||||
"E1/T1 Line Number\n") |
||||
{ |
||||
uint8_t line_nr = atoi(argv[0]); |
||||
uint8_t peer_nr = atoi(argv[1]); |
||||
struct e1_recorder_line *line = &g_recorder.line[line_nr]; |
||||
struct e1_recorder_line *peer = &g_recorder.line[peer_nr]; |
||||
/* look up morror peer and enable mirror flag on peer */ |
||||
if (peer->mirror.enabled && |
||||
peer->mirror.line_nr != line_nr) { |
||||
vty_out(vty, "Peer line %u already part of another mirror%s", |
||||
peer_nr, VTY_NEWLINE); |
||||
return CMD_WARNING; |
||||
} |
||||
peer->mirror.enabled = true; |
||||
peer->mirror.line_nr = line_nr; |
||||
/* enable mirror flag of current line */ |
||||
if (line->mirror.enabled && |
||||
line->mirror.line_nr != peer_nr) { |
||||
vty_out(vty, "Line %u already part of another mirror%s", |
||||
line_nr, VTY_NEWLINE); |
||||
return CMD_WARNING; |
||||
} |
||||
line->mirror.enabled = true; |
||||
line->mirror.line_nr = peer_nr; |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(cfg_rec_no_line_mirror, cfg_rec_no_line_mirror_cmd, |
||||
"no line <0-255> mirror", |
||||
LINE_STR "Mirror this line to another line\n" |
||||
"E1/T1 Line Number\n") |
||||
{ |
||||
uint8_t line_nr = atoi(argv[0]); |
||||
struct e1_recorder_line *line = &g_recorder.line[line_nr]; |
||||
struct e1_recorder_line *peer; |
||||
|
||||
if (!line->mirror.enabled) |
||||
return CMD_WARNING; |
||||
/* look up morror peer (if any) and disable mirror flag on peer */ |
||||
peer = &g_recorder.line[line->mirror.line_nr]; |
||||
if (peer->mirror.enabled) { |
||||
peer->mirror.enabled = false; |
||||
peer->mirror.line_nr = 0; |
||||
} |
||||
/* dsiable mirror flag of current line */ |
||||
if (line->mirror.enabled){ |
||||
line->mirror.enabled = false; |
||||
line->mirror.line_nr = 0; |
||||
} |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
|
||||
DEFUN(cfg_rec_save_path, cfg_rec_save_path_cmd, |
||||
"storage-path PATH", |
||||
"Configure the directory for storing recordings\n" |
||||
"Directory to which recordings are stored\n") |
||||
{ |
||||
osmo_talloc_replace_string(NULL, &g_recorder.storage_path, argv[0]); |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
DEFUN(cfg_rec_file_size, cfg_rec_file_size_cmd, |
||||
"file-size-mb <1-9999999>", |
||||
"Configure the maximum file size before starting new file\n" |
||||
"Megabytes\n") |
||||
{ |
||||
g_recorder.max_file_size_mb = atoi(argv[0]); |
||||
return CMD_SUCCESS; |
||||
} |
||||
|
||||
static void config_write_recorder_line(struct vty *vty, unsigned int lnr) |
||||
{ |
||||
struct e1inp_line *line = e1inp_line_find(lnr); |
||||
struct e1_recorder_line *rline = &g_recorder.line[lnr]; |
||||
unsigned int i; |
||||
|
||||
if (rline->mirror.enabled) { |
||||
vty_out(vty, " line %u mirror %u%s", |
||||
lnr, rline->mirror.line_nr, VTY_NEWLINE); |
||||
} |
||||
|
||||
if (!line) |
||||
return; |
||||
|
||||
for (i = 0; i < line->num_ts; i++) { |
||||
struct e1inp_ts *ts = &line->ts[i]; |
||||
} |
||||
} |
||||
|
||||
static int config_write_recorder(struct vty *vty) |
||||
{ |
||||
unsigned int i; |
||||
|
||||
vty_out(vty, "recorder%s", VTY_NEWLINE); |
||||
vty_out(vty, " file-size-mb %u%s", g_recorder.max_file_size_mb, |
||||
VTY_NEWLINE); |
||||
vty_out(vty, " storage-path %s%s", g_recorder.storage_path, |
||||
VTY_NEWLINE); |
||||
for (i = 0; i < 255; i++) { |
||||
config_write_recorder_line(vty, i); |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static struct cmd_node cfg_recorder_node = { |
||||
RECORDER_NODE, |
||||
"%s(config-recorder)# ", |
||||
1, |
||||
}; |
||||
|
||||
void recorder_vty_init(void) |
||||
{ |
||||
install_element(CONFIG_NODE, &cfg_recorder_cmd); |
||||
|
||||
install_node(&cfg_recorder_node, config_write_recorder); |
||||
install_element(RECORDER_NODE, &cfg_rec_line_ts_mode_cmd); |
||||
install_element(RECORDER_NODE, &cfg_rec_line_mirror_cmd); |
||||
install_element(RECORDER_NODE, &cfg_rec_no_line_mirror_cmd); |
||||
install_element(RECORDER_NODE, &cfg_rec_save_path_cmd); |
||||
install_element(RECORDER_NODE, &cfg_rec_file_size_cmd); |
||||
} |
Loading…
Reference in new issue