initial import of incomplete project to record E1 lines

This commit is contained in:
Harald Welte 2016-07-28 09:04:11 +02:00
commit 39cfbf435d
7 changed files with 410 additions and 0 deletions

27
README Normal file
View File

@ -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).

13
src/Makefile Normal file
View File

@ -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

97
src/e1_recorder.c Normal file
View File

@ -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);
};
}

37
src/recorder.h Normal file
View File

@ -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);

20
src/storage.c Normal file
View File

@ -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;
}

21
src/storage.h Normal file
View File

@ -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);

195
src/vty.c Normal file
View File

@ -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);
}