commit 39cfbf435def2ecf18e7292f92ff34ef2da66561 Author: Harald Welte Date: Thu Jul 28 09:04:11 2016 +0200 initial import of incomplete project to record E1 lines diff --git a/README b/README new file mode 100644 index 0000000..149b547 --- /dev/null +++ b/README @@ -0,0 +1,27 @@ +Osmocom E1 recorder +=================== +(C) 2016 by Harald Welte + +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). diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..1430296 --- /dev/null +++ b/src/Makefile @@ -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 diff --git a/src/e1_recorder.c b/src/e1_recorder.c new file mode 100644 index 0000000..420834f --- /dev/null +++ b/src/e1_recorder.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include + +#include +#include + +#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 \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); + }; +} diff --git a/src/recorder.h b/src/recorder.h new file mode 100644 index 0000000..eaa5eab --- /dev/null +++ b/src/recorder.h @@ -0,0 +1,37 @@ +#pragma once +#include + +#include +#include +#include + +/* 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); diff --git a/src/storage.c b/src/storage.c new file mode 100644 index 0000000..571a043 --- /dev/null +++ b/src/storage.c @@ -0,0 +1,20 @@ +#include +#include + +#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; +} diff --git a/src/storage.h b/src/storage.h new file mode 100644 index 0000000..d128a34 --- /dev/null +++ b/src/storage.h @@ -0,0 +1,21 @@ +#pragma once +#include + +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); diff --git a/src/vty.c b/src/vty.c new file mode 100644 index 0000000..694dab1 --- /dev/null +++ b/src/vty.c @@ -0,0 +1,195 @@ + +#include +#include +#include + +#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); +}