osmo-isdntap/src/isdntap.c

519 lines
14 KiB
C

#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <osmocom/core/isdnhdlc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/gsmtap.h>
#include <osmocom/core/hashtable.h>
#include <osmocom/gsm/tlv.h>
#include "isdntap.h"
#include "q931.h"
#include "log.h"
/***********************************************************************
* data structures
***********************************************************************/
struct isdntap_line *isdntap_line_find(struct isdntap_instance *itd, const char *name)
{
struct isdntap_line *line;
llist_for_each_entry(line, &itd->lines, list) {
if (!strcmp(name, line->name))
return line;
}
return NULL;
}
struct isdntap_line *isdntap_line_new(struct isdntap_instance *itd, const char *name)
{
struct isdntap_line *line = talloc_zero(itd, struct isdntap_line);
if (!line)
return NULL;
line->name = talloc_strdup(line, name);
hash_init(line->q931.call_state);
//line->ctrs = rate_ctr_group_alloc(line, &line_ctrg_desc, ctr_idx);
/* initialize timeslots */
for (unsigned int i = 0; i < ARRAY_SIZE(line->ts); i++) {
struct isdntap_ts *ts = &line->ts[i];
ts->line = line;
ts->num = i;
ts->mode = E1_TS_TRACE_MODE_NONE;
ts->gsmtap_subtype = GSMTAP_E1T1_LAPD;
osmo_isdnhdlc_rcv_init(&ts->hdlc.state[0].vars, 0);
osmo_isdnhdlc_rcv_init(&ts->hdlc.state[1].vars, 0);
ts->driver.dahdi.rx.fd = -1;
ts->driver.dahdi.tx.fd = -1;
ts->driver.dahdi.channo = -1;
}
llist_add_tail(&line->list, &itd->lines);
return line;
}
/***********************************************************************
* call state hash table
***********************************************************************/
/* one call / entry in the state table */
struct q931_call_state {
struct hlist_node node;
uint32_t call_ref; /* decoded call reference */
bool net2user; /* call established in Net->User direction? */
time_t create_time; /* time at which call was created */
struct q931_channel_id chan_id; /* channel identification of associated user plane */
struct q931_party_number calling_party;
struct q931_party_number called_party;
bool sending_complete; /* called party number is complete */
};
static struct q931_call_state *
calltbl_find_callref(struct isdntap_line *line, uint32_t callref)
{
struct q931_call_state *st;
hash_for_each_possible(line->q931.call_state, st, node, callref) {
if (st->call_ref == callref)
return st;
}
return NULL;
}
static struct q931_call_state *
calltbl_create_callref(struct isdntap_line *line, uint32_t callref, bool net2user)
{
struct q931_call_state *st = talloc_zero(line, struct q931_call_state);
if (!st)
return NULL;
st->call_ref = callref;
st->net2user = net2user;
st->create_time = time(NULL);
hash_add(line->q931.call_state, &st->node, callref);
return st;
}
static struct q931_call_state *
calltbl_find_or_create_callref(struct isdntap_line *line, uint32_t callref, bool net2user)
{
struct q931_call_state *cstate;
cstate = calltbl_find_callref(line, callref);
if (!cstate)
cstate = calltbl_create_callref(line, callref, false);
return cstate;
}
static void calltbl_delete_callref(struct q931_call_state *cstate)
{
hash_del(&cstate->node);
talloc_free(cstate);
}
static void calltbl_delete_all_callref(struct isdntap_line *line)
{
struct q931_call_state *cstate;
struct hlist_node *n;
int i;
hash_for_each_safe(line->q931.call_state, i, n, cstate, node) {
calltbl_delete_callref(cstate);
}
}
/* delete any call_state we have on chan_id */
static void calltbl_delete_callref_on_chan_id(struct isdntap_line *line, const struct q931_channel_id *chan_id)
{
struct q931_call_state *cstate;
struct hlist_node *n;
int i;
hash_for_each_safe(line->q931.call_state, i, n, cstate, node) {
if (cstate->chan_id.info_chan_type == chan_id->info_chan_type &&
cstate->chan_id.d_channel == chan_id->d_channel &&
cstate->chan_id.b_channel == chan_id->b_channel) {
calltbl_delete_callref(cstate);
}
}
}
/***********************************************************************
* Q.931 processing
***********************************************************************/
/* append additional digits received (overlap dialling) to called party number */
static void append_called_digits(struct q931_party_number *out, const uint8_t *buf, size_t len)
{
unsigned int idx = strlen(out->digits);
size_t i;
for (i = 0; i < len; i++) {
if (idx >= ARRAY_SIZE(out->digits))
return;
out->digits[idx] = buf[i];
idx++;
}
}
static int isdntap_q931_rx_global(struct isdntap_line *line, const struct q931_msg_parsed *parsed)
{
int rc;
switch (parsed->msg_type) {
case Q931_MSGT_RESTART:
if (TLVP_PRES_LEN(&parsed->ies, Q931_IEI_RESTART_IND, 1)) {
struct q931_channel_id chan_id;
uint8_t rest_ind = *TLVP_VAL(&parsed->ies, Q931_IEI_RESTART_IND) & 0x7;
switch (rest_ind) {
case 0: /* clear indicated channel[s] */
if (!TLVP_PRESENT(&parsed->ies, Q931_IEI_CHANNEL_ID))
return -EINVAL;
rc = q931_decode_channel_id(&chan_id,
TLVP_VAL(&parsed->ies, Q931_IEI_CHANNEL_ID),
TLVP_LEN(&parsed->ies, Q931_IEI_CHANNEL_ID));
if (rc < 0)
return rc;
/* clear any calls on selected channel */
calltbl_delete_callref_on_chan_id(line, &chan_id);
break;
case 6:
case 7: /* clear all channels */
calltbl_delete_all_callref(line);
break;
}
}
break;
}
return 0;
}
/* receive one Q.931 message for signaling analysis */
static int isdntap_q931_rx(struct isdntap_line *line, bool net2user, const uint8_t *buf, size_t len)
{
struct q931_msg_parsed parsed;
struct q931_call_state *cstate;
int rc;
rc = q931_msg_parse(&parsed, buf, len);
if (rc < 0)
return rc;
/* process special case of global call reference elsewhere */
if (parsed.call_ref == 0) {
return isdntap_q931_rx_global(line, &parsed);
}
/* Step 1: look-up (or create in case of SETUP) the call state record for call reference */
switch (parsed.msg_type) {
case Q931_MSGT_SETUP:
cstate = calltbl_find_or_create_callref(line, parsed.call_ref, false);
if (!cstate)
return -ENOMEM;
break;
default:
cstate = calltbl_find_callref(line, parsed.call_ref);
if (!cstate)
return -ENODEV;
break;
}
/* Step 2: Additional information gathering */
switch (parsed.msg_type) {
case Q931_MSGT_SETUP:
/* decode called/calling party (if any) for context */
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_CALLED_PARTY_NUM)) {
q931_decode_called_party(&cstate->called_party,
TLVP_VAL(&parsed.ies, Q931_IEI_CALLED_PARTY_NUM),
TLVP_LEN(&parsed.ies, Q931_IEI_CALLED_PARTY_NUM));
}
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_CALLING_PARTY_NUM)) {
q931_decode_calling_party(&cstate->calling_party,
TLVP_VAL(&parsed.ies, Q931_IEI_CALLING_PARTY_NUM),
TLVP_LEN(&parsed.ies, Q931_IEI_CALLING_PARTY_NUM));
}
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_SENDING_COMPLETE))
cstate->sending_complete = true;
break;
case Q931_MSGT_INFORMATION:
/* append any additional digits to called_party */
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_KEYPAD_FACILITY) &&
!cstate->sending_complete) {
append_called_digits(&cstate->called_party,
TLVP_VAL(&parsed.ies, Q931_IEI_KEYPAD_FACILITY),
TLVP_LEN(&parsed.ies, Q931_IEI_KEYPAD_FACILITY));
}
/* mark as complete, if we finally are */
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_SENDING_COMPLETE))
cstate->sending_complete = true;
break;
default:
break;
}
/* Step 3: dispatch by message type; look in those that contain a ChannelIndicator */
switch (parsed.msg_type) {
case Q931_MSGT_SETUP:
case Q931_MSGT_CALL_PROCEEDING:
case Q931_MSGT_SETUP_ACK:
#if 0
case Q931_MSGT_ALERTING:
case Q931_MSGT_CONNECT:
case Q931_MSGT_RESUME_ACK:
#endif
if (TLVP_PRESENT(&parsed.ies, Q931_IEI_CHANNEL_ID)) {
rc = q931_decode_channel_id(&cstate->chan_id,
TLVP_VAL(&parsed.ies, Q931_IEI_CHANNEL_ID),
TLVP_LEN(&parsed.ies, Q931_IEI_CHANNEL_ID));
if (rc < 0)
return rc;
if (cstate->chan_id.exclusive && !cstate->chan_id.d_channel) {
/* now we know the exact B channel used */
/* TODO: start capturing */
}
}
break;
case Q931_MSGT_RELEASE:
case Q931_MSGT_RELEASE_COMPLETE:
/* forget about the call */
calltbl_delete_callref(cstate);
cstate = NULL;
break;
default:
break;
}
return 0;
}
/* trace one Q.921 / LAPD frame for signaling analysis */
static void isdntap_q921_rx(struct isdntap_line *line, bool net2user, const uint8_t *buf, size_t len)
{
uint8_t sapi, tei;
/* Parse LAPD header; Ignore anything != I frames */
/* Q.921 header: 2 bytes address; 1-2 byte control; [optional] information */
if (len < 2)
return;
/* Address field: Figure 5 / Q.921 */
sapi = buf[0] >> 2;
tei = buf[1] >> 1;
/* SAPI value for circuit-switched call control (Table 2/Q.921) */
if (sapi != 0) {
/* skip unknown SAPI */
return;
}
if (len < 3)
return;
/* Control field: Table 4/Q.921 */
if (buf[2] & 0x01) {
/* skip frames != I-frame (which have 0 as lsb of 1st octet) */
return;
}
if (len < 4)
return;
/* skip N(R) / N(S) and go directly to Q.931 payload */
isdntap_q931_rx(line, net2user, buf + 4, len - 4);
}
/* main entry point for received D-channel data on a given TS */
int isdntap_ts_rx_dchan(struct isdntap_ts *ts, const uint8_t *buf, size_t len, bool net2user)
{
isdntap_q921_rx(ts->line, net2user, buf, len);
return 0;
}
/* main entry point for received B-channel data on a given TS */
int isdntap_ts_rx_bchan(struct isdntap_ts *ts, const uint8_t *buf, size_t len, bool is_rx)
{
struct msgb *msg = msgb_alloc_c(ts->line, len, "Bchan");
uint8_t *cur;
int rc;
if (!msg)
return -ENOMEM;
cur = msgb_put(msg, len);
memcpy(cur, buf, len);
if (is_rx)
rc = osmo_wqueue_enqueue(&ts->output.file.rx, msg);
else
rc = osmo_wqueue_enqueue(&ts->output.file.tx, msg);
if (rc)
msgb_free(msg);
return 0;
}
/* helper function for opening an output file */
static int isdntap_open_file(struct isdntap_ts *ts, const char *call_label, const char *suffix)
{
char buf[PATH_MAX];
int rc;
snprintf(buf, sizeof(buf), "%s/isdntap-%s-%u-%s.raw", ts->line->inst->cfg.output_path,
call_label, ts->num, suffix);
rc = open(buf, O_CREAT|O_WRONLY|O_TRUNC|O_NONBLOCK);
if (rc < 0) {
LOGP(DITAP, LOGL_ERROR, "Error opening file %s: %s\n", buf, strerror(errno));
return rc;
}
LOGP(DITAP, LOGL_INFO, "Opened file %s\n", buf);
return rc;
}
int isdntap_ts_start_bchan(struct isdntap_ts *ts, const char *call_label)
{
int fd_rx, fd_tx;
osmo_wqueue_init(&ts->output.file.tx, 100);
fd_tx = isdntap_open_file(ts, call_label, "tx");
if (fd_tx < 0)
return fd_tx;
ts->output.file.tx.bfd.fd = fd_tx;
osmo_fd_register(&ts->output.file.tx.bfd);
osmo_wqueue_init(&ts->output.file.rx, 100);
fd_rx = isdntap_open_file(ts, call_label, "rx");
if (!fd_rx)
goto out_cleanup_tx;
ts->output.file.rx.bfd.fd = fd_rx;
osmo_fd_register(&ts->output.file.rx.bfd);
return 0;
out_cleanup_tx:
osmo_fd_unregister(&ts->output.file.tx.bfd);
ts->output.file.tx.bfd.fd = -1;
close(fd_tx);
return -1;
}
void isdntap_ts_stop_bchan(struct isdntap_ts *ts)
{
/* FIXME: ideally we'd make sure that the write queues are both fully flushed first */
if (ts->output.file.tx.bfd.fd >= 0) {
osmo_wqueue_clear(&ts->output.file.tx);
osmo_fd_unregister(&ts->output.file.tx.bfd);
close(ts->output.file.tx.bfd.fd);
ts->output.file.tx.bfd.fd = -1;
}
if (ts->output.file.rx.bfd.fd >= 0) {
osmo_wqueue_clear(&ts->output.file.rx);
osmo_fd_unregister(&ts->output.file.rx.bfd);
close(ts->output.file.rx.bfd.fd);
ts->output.file.rx.bfd.fd = -1;
}
}
/***********************************************************************
* e1d integration
***********************************************************************/
# if 0
/* trace one timeslot of a line */
void e1tap_trace_ts(struct e1tap_ts *ts, const uint8_t *tsbuf, size_t frame_count, uint8_t hdlc_idx)
{
struct e1tap_line *line = ts->line;
int oi = 0;
int rc;
OSMO_ASSERT(hdlc_idx <= 0);
switch (ts->mode) {
case E1_TS_TRACE_MODE_HDLC:
case E1_TS_TRACE_MODE_ISDN_D:
while (oi < frame_count) {
int num_consumed;
/* feed the new bytes into the HDLC decoder */
rc = osmo_isdnhdlc_decode(&ts->hdlc.state[hdlc_idx].vars,
&tsbuf[oi], frame_count-oi,
&num_consumed, ts->hdlc.state[hdlc_idx].out,
sizeof(ts->hdlc.state[hdlc_idx].out));
if (rc > 0) {
/* if HDLC decoder produced output, send it via GSMTAP */
gsmtap_send_ex(line->gti, GSMTAP_TYPE_E1T1, flags,
ts->num, ts->gsmtap_subtype, 0, 0, 0, 0,
ts->hdlc.state[hdlc_idx].out, rc);
/* feed D-channel into higher level analysis */
if (ts->mode == E1_TS_TRACE_MODE_ISDN_D)
isdntap_q921_rx(line, hdlc_idx, ts->hdlc.state[hdlc_idx].out, rc);
} else if (rc < 0) {
/* FIXME: log error */
}
oi += num_consumed;
}
break;
default:
break;
}
}
/* trace an entire line (we pass in full 32byte frames */
void e1tap_trace_line(struct e1tap_line *line, bool mux_out, const uint8_t *buf, int frame_count)
{
uint8_t tsbuf[frame_count];
uint8_t hdlc_idx;
if (!line)
return;
if (mux_out)
hdlc_idx = 1;
else
hdlc_idx = 0;
for (unsigned int tn = 1; tn < ARRAY_SIZE(line->ts); tn++) {
struct e1tap_ts *ts = line->ts[i];
/* fast path */
if (ts->mode == E1_TS_TRACE_MODE_NONE)
continue;
/* demultiplex the bytes of the given TS */
for (unsigned int f = 0; f < frame_count; f++)
tsbuf[f] = buf[32*f + tn];
e1tap_trace_ts(ts, tsbuf, frame_count, hdlc_idx);
}
}
/* USB/trunkdev <- application/OCTOI */
void
e1_trace_mux_out(struct e1_line *line, const uint8_t *buf, int frame_count)
{
return _e1_trace(line, true, buf, frame_count);
}
/* USB/trunkdev -> application/OCTOI */
void
e1_trace_mux_in(struct e1_line *line, const uint8_t *buf, int frame_count)
{
return _e1_trace(line, false, buf, frame_count);
}
#endif