aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2016-07-28 09:04:11 +0200
committerHarald Welte <laforge@gnumonks.org>2016-07-28 09:04:11 +0200
commit39cfbf435def2ecf18e7292f92ff34ef2da66561 (patch)
treede22e3787645146d23cf1648eefbffb3743567ac
initial import of incomplete project to record E1 lines
-rw-r--r--README27
-rw-r--r--src/Makefile13
-rw-r--r--src/e1_recorder.c97
-rw-r--r--src/recorder.h37
-rw-r--r--src/storage.c20
-rw-r--r--src/storage.h21
-rw-r--r--src/vty.c195
7 files changed, 410 insertions, 0 deletions
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 <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).
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 <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);
+ };
+}
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 <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);
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 <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;
+}
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 <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);
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 <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);
+}