** **
**---------------------------------------------------------------------------**
** Copyright: Andreas Eversberg **
+** MNCC-Interface: Harald Welte **
** **
** mISDN gsm **
** **
\*****************************************************************************/
#include "main.h"
+#include "mncc.h"
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
}
#include <mISDN/mISDNcompat.h>
+#include <assert.h>
-struct lcr_gsm *gsm = NULL;
+#define SOCKET_RETRY_TIMER 5
+
+//struct lcr_gsm *gsm = NULL;
int new_callref = 1;
+/* names of MNCC-SAP */
+static const struct _value_string {
+ int msg_type;
+ const char *name;
+} mncc_names[] = {
+ { MNCC_SETUP_REQ, "MNCC_SETUP_REQ" },
+ { MNCC_SETUP_IND, "MNCC_SETUP_IND" },
+ { MNCC_SETUP_RSP, "MNCC_SETUP_RSP" },
+ { MNCC_SETUP_CNF, "MNCC_SETUP_CNF" },
+ { MNCC_SETUP_COMPL_REQ, "MNCC_SETUP_COMPL_REQ" },
+ { MNCC_SETUP_COMPL_IND, "MNCC_SETUP_COMPL_IND" },
+ { MNCC_CALL_CONF_IND, "MNCC_CALL_CONF_IND" },
+ { MNCC_CALL_PROC_REQ, "MNCC_CALL_PROC_REQ" },
+ { MNCC_PROGRESS_REQ, "MNCC_PROGRESS_REQ" },
+ { MNCC_ALERT_REQ, "MNCC_ALERT_REQ" },
+ { MNCC_ALERT_IND, "MNCC_ALERT_IND" },
+ { MNCC_NOTIFY_REQ, "MNCC_NOTIFY_REQ" },
+ { MNCC_NOTIFY_IND, "MNCC_NOTIFY_IND" },
+ { MNCC_DISC_REQ, "MNCC_DISC_REQ" },
+ { MNCC_DISC_IND, "MNCC_DISC_IND" },
+ { MNCC_REL_REQ, "MNCC_REL_REQ" },
+ { MNCC_REL_IND, "MNCC_REL_IND" },
+ { MNCC_REL_CNF, "MNCC_REL_CNF" },
+ { MNCC_FACILITY_REQ, "MNCC_FACILITY_REQ" },
+ { MNCC_FACILITY_IND, "MNCC_FACILITY_IND" },
+ { MNCC_START_DTMF_IND, "MNCC_START_DTMF_IND" },
+ { MNCC_START_DTMF_RSP, "MNCC_START_DTMF_RSP" },
+ { MNCC_START_DTMF_REJ, "MNCC_START_DTMF_REJ" },
+ { MNCC_STOP_DTMF_IND, "MNCC_STOP_DTMF_IND" },
+ { MNCC_STOP_DTMF_RSP, "MNCC_STOP_DTMF_RSP" },
+ { MNCC_MODIFY_REQ, "MNCC_MODIFY_REQ" },
+ { MNCC_MODIFY_IND, "MNCC_MODIFY_IND" },
+ { MNCC_MODIFY_RSP, "MNCC_MODIFY_RSP" },
+ { MNCC_MODIFY_CNF, "MNCC_MODIFY_CNF" },
+ { MNCC_MODIFY_REJ, "MNCC_MODIFY_REJ" },
+ { MNCC_HOLD_IND, "MNCC_HOLD_IND" },
+ { MNCC_HOLD_CNF, "MNCC_HOLD_CNF" },
+ { MNCC_HOLD_REJ, "MNCC_HOLD_REJ" },
+ { MNCC_RETRIEVE_IND, "MNCC_RETRIEVE_IND" },
+ { MNCC_RETRIEVE_CNF, "MNCC_RETRIEVE_CNF" },
+ { MNCC_RETRIEVE_REJ, "MNCC_RETRIEVE_REJ" },
+ { MNCC_USERINFO_REQ, "MNCC_USERINFO_REQ" },
+ { MNCC_USERINFO_IND, "MNCC_USERINFO_IND" },
+ { MNCC_REJ_REQ, "MNCC_REJ_REQ" },
+ { MNCC_REJ_IND, "MNCC_REJ_IND" },
+ { MNCC_PROGRESS_IND, "MNCC_PROGRESS_IND" },
+ { MNCC_CALL_PROC_IND, "MNCC_CALL_PROC_IND" },
+ { MNCC_CALL_CONF_REQ, "MNCC_CALL_CONF_REQ" },
+ { MNCC_START_DTMF_REQ, "MNCC_START_DTMF_REQ" },
+ { MNCC_STOP_DTMF_REQ, "MNCC_STOP_DTMF_REQ" },
+ { MNCC_HOLD_REQ, "MNCC_HOLD_REQ " },
+ { MNCC_RETRIEVE_REQ, "MNCC_RETRIEVE_REQ" },
+ { 0, NULL }
+};
+
+const char *mncc_name(int value)
+{
+ int i = 0;
+
+ while (mncc_names[i].name) {
+ if (mncc_names[i].msg_type == value)
+ return mncc_names[i].name;
+ i++;
+ }
+ return "unknown";
+}
+
+static int mncc_send(struct lcr_gsm *lcr_gsm, int msg_type, void *data);
+
/*
* create and send mncc message
*/
mncc->callref = callref;
return (mncc);
}
-int send_and_free_mncc(void *instance, unsigned int msg_type, void *data)
+int send_and_free_mncc(struct lcr_gsm *lcr_gsm, unsigned int msg_type, void *data)
{
- int ret;
+ int ret = 0;
-#ifdef WITH_GSM_BS
- ret = mncc_send((struct gsm_network *)instance, msg_type, data);
-#endif
-#ifdef WITH_GSM_MS
- ret = mncc_send((struct osmocom_ms *)instance, msg_type, data);
-#endif
+ if (lcr_gsm) {
+ ret = mncc_send(lcr_gsm, msg_type, data);
+ }
free(data);
return ret;
p_callerinfo.itype = (mISDNport->ifport->interface->extension)?INFO_ITYPE_ISDN_EXTENSION:INFO_ITYPE_ISDN;
memset(&p_m_g_delete, 0, sizeof(p_m_g_delete));
add_work(&p_m_g_delete, delete_event, this, 0);
- p_m_g_instance = NULL;
+ p_m_g_lcr_gsm = NULL;
p_m_g_callref = 0;
p_m_g_mode = 0;
p_m_g_gsm_b_sock = -1;
/* bind socket to bchannel */
addr.family = AF_ISDN;
- addr.dev = gsm->gsm_port;
+ addr.dev = mISDNloop.port;
addr.channel = index+1+(index>15);
ret = bind(p_m_g_gsm_b_sock, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
frame->msg_type = GSM_TCHF_FRAME;
frame->callref = p_m_g_callref;
memcpy(frame->data, _frame, 33);
-#ifdef WITH_GSM_BS
- mncc_send((struct gsm_network *)p_m_g_instance, frame->msg_type, frame);
-#endif
-#ifdef WITH_GSM_MS
- mncc_send((struct osmocom_ms *)p_m_g_instance, frame->msg_type, frame);
-#endif
+
+ if (p_m_g_lcr_gsm) {
+ mncc_send(p_m_g_lcr_gsm, frame->msg_type, frame);
+ }
}
/*
* create trace
- **/
+ */
void gsm_trace_header(struct mISDNport *mISDNport, class PmISDN *port, unsigned int msg_type, int direction)
{
char msgtext[64];
/* select message and primitive text */
- SCPY(msgtext, "gsmnet");
+ SCPY(msgtext, mncc_name(msg_type));
/* add direction */
if (port) {
switch(port->p_type) {
case PORT_TYPE_GSM_BS_OUT:
- SCAT(msgtext, " LCR->BSC");
- break;
case PORT_TYPE_GSM_BS_IN:
- SCAT(msgtext, " LCR<-BSC");
+ SCAT(msgtext, " LCR<->BSC");
break;
case PORT_TYPE_GSM_MS_OUT:
- SCAT(msgtext, " LCR->MS");
- break;
case PORT_TYPE_GSM_MS_IN:
- SCAT(msgtext, " LCR<-MS");
+ SCAT(msgtext, " LCR<->MS");
break;
}
} else
mode->lchan_mode = 0x01; /* GSM V1 */
add_trace("mode", NULL, "0x%02x", mode->lchan_mode);
end_trace();
- send_and_free_mncc(p_m_g_instance, mode->msg_type, mode);
+ send_and_free_mncc(p_m_g_lcr_gsm, mode->msg_type, mode);
}
gsm_trace_header(p_m_mISDNport, this, MNCC_FRAME_RECV, DIRECTION_OUT);
end_trace();
frame = create_mncc(MNCC_FRAME_RECV, p_m_g_callref);
- send_and_free_mncc(p_m_g_instance, frame->msg_type, frame);
+ send_and_free_mncc(p_m_g_lcr_gsm, frame->msg_type, frame);
p_m_g_tch_connected = 1;
}
}
gsm_trace_header(p_m_mISDNport, this, MNCC_FRAME_RECV, DIRECTION_OUT);
end_trace();
frame = create_mncc(MNCC_FRAME_RECV, p_m_g_callref);
- send_and_free_mncc(p_m_g_instance, frame->msg_type, frame);
+ send_and_free_mncc(p_m_g_lcr_gsm, frame->msg_type, frame);
p_m_g_tch_connected = 1;
}
}
gsm_trace_header(p_m_mISDNport, this, MNCC_SETUP_COMPL_REQ, DIRECTION_OUT);
resp = create_mncc(MNCC_SETUP_COMPL_REQ, p_m_g_callref);
end_trace();
- send_and_free_mncc(p_m_g_instance, resp->msg_type, resp);
+ send_and_free_mncc(p_m_g_lcr_gsm, resp->msg_type, resp);
message = message_create(p_serial, ACTIVE_EPOINT(p_epointlist), PORT_TO_EPOINT, MESSAGE_CONNECT);
memcpy(&message->param.connectinfo, &p_connectinfo, sizeof(struct connect_info));
gsm_trace_header(p_m_mISDNport, this, MNCC_FRAME_RECV, DIRECTION_OUT);
end_trace();
frame = create_mncc(MNCC_FRAME_RECV, p_m_g_callref);
- send_and_free_mncc(p_m_g_instance, frame->msg_type, frame);
+ send_and_free_mncc(p_m_g_lcr_gsm, frame->msg_type, frame);
p_m_g_tch_connected = 1;
}
}
gsm_trace_header(p_m_mISDNport, this, MNCC_FRAME_RECV, DIRECTION_OUT);
end_trace();
frame = create_mncc(MNCC_FRAME_RECV, p_m_g_callref);
- send_and_free_mncc(p_m_g_instance, frame->msg_type, frame);
+ send_and_free_mncc(p_m_g_lcr_gsm, frame->msg_type, frame);
p_m_g_tch_connected = 1;
}
}
add_trace("cause", "value", "%d", resp->cause.value);
#endif
end_trace();
- send_and_free_mncc(p_m_g_instance, resp->msg_type, resp);
+ send_and_free_mncc(p_m_g_lcr_gsm, resp->msg_type, resp);
/* sending release to endpoint */
while(p_epointlist) {
end_trace();
mncc = create_mncc(MNCC_NOTIFY_REQ, p_m_g_callref);
mncc->notify = notify;
- send_and_free_mncc(p_m_g_instance, mncc->msg_type, mncc);
+ send_and_free_mncc(p_m_g_lcr_gsm, mncc->msg_type, mncc);
}
}
}
add_trace("progress", "descr", "%d", mncc->progress.descr);
}
end_trace();
- send_and_free_mncc(p_m_g_instance, mncc->msg_type, mncc);
+ send_and_free_mncc(p_m_g_lcr_gsm, mncc->msg_type, mncc);
new_state(PORT_STATE_IN_ALERTING);
gsm_trace_header(p_m_mISDNport, this, MNCC_FRAME_RECV, DIRECTION_OUT);
end_trace();
mncc = create_mncc(MNCC_FRAME_RECV, p_m_g_callref);
- send_and_free_mncc(p_m_g_instance, mncc->msg_type, mncc);
+ send_and_free_mncc(p_m_g_lcr_gsm, mncc->msg_type, mncc);
p_m_g_tch_connected = 1;
}
}
add_trace("connected", "number", "%s", mncc->connected.number);
}
end_trace();
- send_and_free_mncc(p_m_g_instance, mncc->msg_type, mncc);
+ send_and_free_mncc(p_m_g_lcr_gsm, mncc->msg_type, mncc);
new_state(PORT_STATE_CONNECT_WAITING);
}
add_trace("cause", "location", "%d", mncc->cause.location);
add_trace("cause", "value", "%d", mncc->cause.value);
end_trace();
- send_and_free_mncc(p_m_g_instance, mncc->msg_type, mncc);
+ send_and_free_mncc(p_m_g_lcr_gsm, mncc->msg_type, mncc);
new_state(PORT_STATE_OUT_DISCONNECT);
gsm_trace_header(p_m_mISDNport, this, MNCC_FRAME_RECV, DIRECTION_OUT);
end_trace();
mncc = create_mncc(MNCC_FRAME_RECV, p_m_g_callref);
- send_and_free_mncc(p_m_g_instance, mncc->msg_type, mncc);
+ send_and_free_mncc(p_m_g_lcr_gsm, mncc->msg_type, mncc);
p_m_g_tch_connected = 1;
}
}
add_trace("cause", "location", "%d", mncc->cause.location);
add_trace("cause", "value", "%d", mncc->cause.value);
end_trace();
- send_and_free_mncc(p_m_g_instance, mncc->msg_type, mncc);
+ send_and_free_mncc(p_m_g_lcr_gsm, mncc->msg_type, mncc);
new_state(PORT_STATE_RELEASE);
trigger_work(&p_m_g_delete);
int gsm_exit(int rc)
{
- /* free gsm instance */
- if (gsm) {
- free(gsm);
- gsm = NULL;
- }
-
return(rc);
}
int gsm_init(void)
{
- char conf_error[256] = "";
-
/* seed the PRNG */
srand(time(NULL));
- /* create gsm instance */
- gsm = (struct lcr_gsm *)MALLOC(sizeof(struct lcr_gsm));
- gsm->gsm_sock = -1;
+ return 0;
+}
+
+/*
+ * MNCC interface
+ */
+
+static int mncc_q_enqueue(struct lcr_gsm *lcr_gsm, struct gsm_mncc *mncc, unsigned int len)
+{
+ struct mncc_q_entry *qe;
+
+ qe = (struct mncc_q_entry *) MALLOC(sizeof(*qe)+sizeof(*mncc)+len);
+ if (!qe)
+ return -ENOMEM;
+
+ qe->next = NULL;
+ qe->len = len;
+ memcpy(qe->data, mncc, len);
+
+ /* in case of empty list ... */
+ if (!lcr_gsm->mncc_q_hd && !lcr_gsm->mncc_q_tail) {
+ /* the list head and tail both point to the new qe */
+ lcr_gsm->mncc_q_hd = lcr_gsm->mncc_q_tail = qe;
+ } else {
+ /* append to tail of list */
+ lcr_gsm->mncc_q_tail->next = qe;
+ lcr_gsm->mncc_q_tail = qe;
+ }
+
+ lcr_gsm->mncc_lfd.when |= LCR_FD_WRITE;
+
+ return 0;
+}
+
+static struct mncc_q_entry *mncc_q_dequeue(struct lcr_gsm *lcr_gsm)
+{
+ struct mncc_q_entry *qe = lcr_gsm->mncc_q_hd;
+ if (!qe)
+ return NULL;
+
+ /* dequeue the successfully sent message */
+ lcr_gsm->mncc_q_hd = qe->next;
+ if (!qe)
+ return NULL;
+ if (qe == lcr_gsm->mncc_q_tail)
+ lcr_gsm->mncc_q_tail = NULL;
+
+ return qe;
+}
- /* parse options */
- if (!gsm_conf(&gsm->conf, conf_error)) {
- PERROR("%s", conf_error);
+/* routine called by LCR code if it wants to send a message to OpenBSC */
+static int mncc_send(struct lcr_gsm *lcr_gsm, int msg_type, void *data)
+{
+ int len = 0;
+
+ /* FIXME: the caller should provide this */
+ switch (msg_type) {
+ case GSM_TCHF_FRAME:
+ len = sizeof(struct gsm_data_frame) + 33;
+ break;
+ default:
+ len = sizeof(struct gsm_mncc);
+ break;
+ }
+
+ return mncc_q_enqueue(lcr_gsm, (struct gsm_mncc *)data, len);
+}
+
+/* close MNCC socket */
+static int mncc_fd_close(struct lcr_gsm *lcr_gsm, struct lcr_fd *lfd)
+{
+ class Port *port;
+ class Pgsm *pgsm = NULL;
+ struct lcr_msg *message;
+
+ PERROR("Lost MNCC socket, retrying in %u seconds\n", SOCKET_RETRY_TIMER);
+ close(lfd->fd);
+ unregister_fd(lfd);
+ lfd->fd = -1;
+
+ /* free all the calls that were running through the MNCC interface */
+ port = port_first;
+ while(port) {
+ if ((port->p_type & PORT_CLASS_mISDN_MASK) == PORT_CLASS_GSM) {
+ pgsm = (class Pgsm *)port;
+ if (pgsm->p_m_g_lcr_gsm == lcr_gsm) {
+ message = message_create(pgsm->p_serial, ACTIVE_EPOINT(pgsm->p_epointlist), PORT_TO_EPOINT, MESSAGE_RELEASE);
+ message->param.disconnectinfo.cause = 27; // temp. unavail.
+ message->param.disconnectinfo.location = LOCATION_PRIVATE_LOCAL;
+ message_put(message);
+ pgsm->new_state(PORT_STATE_RELEASE);
+ trigger_work(&pgsm->p_m_g_delete);
+ }
+ }
+ port = port->next;
+ }
+
+ /* flush the queue */
+ while (mncc_q_dequeue(lcr_gsm))
+ ;
+
+ /* start the re-connect timer */
+ schedule_timer(&lcr_gsm->socket_retry, SOCKET_RETRY_TIMER, 0);
+
+ return 0;
+}
+
+/* write to OpenBSC via MNCC socket */
+static int mncc_fd_write(struct lcr_fd *lfd, void *inst, int idx)
+{
+ struct lcr_gsm *lcr_gsm = (struct lcr_gsm *) inst;
+ struct mncc_q_entry *qe, *qe2;
+ int rc;
+
+ while (1) {
+ qe = lcr_gsm->mncc_q_hd;
+ if (!qe) {
+ lfd->when &= ~LCR_FD_WRITE;
+ break;
+ }
+ rc = write(lfd->fd, qe->data, qe->len);
+ if (rc == 0)
+ return mncc_fd_close(lcr_gsm, lfd);
+ if (rc < 0)
+ return rc;
+ if (rc < (int)qe->len)
+ return -1;
+ /* dequeue the successfully sent message */
+ qe2 = mncc_q_dequeue(lcr_gsm);
+ assert(qe == qe2);
+ free(qe);
+ }
+ return 0;
+}
+
+/* read from OpenBSC via MNCC socket */
+static int mncc_fd_read(struct lcr_fd *lfd, void *inst, int idx)
+{
+ struct lcr_gsm *lcr_gsm = (struct lcr_gsm *) inst;
+ int rc;
+ static char buf[sizeof(struct gsm_mncc)+1024];
+ struct gsm_mncc *mncc_prim = (struct gsm_mncc *) buf;
+
+ memset(buf, 0, sizeof(buf));
+ rc = recv(lfd->fd, buf, sizeof(buf), 0);
+ if (rc == 0)
+ return mncc_fd_close(lcr_gsm, lfd);
+ if (rc < 0)
+ return rc;
+
+ /* Hand the MNCC message into LCR */
+ switch (lcr_gsm->type) {
#ifdef WITH_GSM_BS
- gsm_bs_exit(-EINVAL);
+ case LCR_GSM_TYPE_NETWORK:
+ return message_bsc(lcr_gsm, mncc_prim->msg_type, mncc_prim);
#endif
#ifdef WITH_GSM_MS
- gsm_ms_exit(-EINVAL);
+ case LCR_GSM_TYPE_MS:
+ return message_ms(lcr_gsm, mncc_prim->msg_type, mncc_prim);
#endif
- return gsm_exit(-EINVAL);
+ default:
+ return 0;
}
+}
- /* open gsm loop interface */
- if (loopback_open()) {
- return gsm_exit(-1);
- }
+/* file descriptor callback if we can read or write form MNCC socket */
+static int mncc_fd_cb(struct lcr_fd *lfd, unsigned int what, void *inst, int idx)
+{
+ int rc = 0;
- return 0;
+ if (what & LCR_FD_READ)
+ rc = mncc_fd_read(lfd, inst, idx);
+ if (rc < 0)
+ return rc;
+
+ if (what & LCR_FD_WRITE)
+ rc = mncc_fd_write(lfd, inst, idx);
+
+ return rc;
}
-int handle_gsm(void)
+int mncc_socket_retry_cb(struct lcr_timer *timer, void *inst, int index)
{
+ struct lcr_gsm *lcr_gsm = (struct lcr_gsm *) inst;
+ int fd, rc;
+
+ lcr_gsm->mncc_lfd.fd = -1;
+
+ fd = socket(PF_UNIX, SOCK_SEQPACKET, 0);
+ if (fd < 0) {
+ PERROR("Cannot create SEQPACKET socket, giving up!\n");
+ return fd;
+ }
+
+ rc = connect(fd, (struct sockaddr *) &lcr_gsm->sun,
+ sizeof(lcr_gsm->sun));
+ if (rc < 0) {
+ PERROR("Could not connect to MNCC socket %s, "
+ "retrying in %u seconds\n", lcr_gsm->sun.sun_path,
+ SOCKET_RETRY_TIMER);
+ close(fd);
+ schedule_timer(&lcr_gsm->socket_retry, SOCKET_RETRY_TIMER, 0);
+ } else {
+ PDEBUG(DEBUG_GSM, "Connected to MNCC socket %s!\n", lcr_gsm->sun.sun_path);
+ lcr_gsm->mncc_lfd.fd = fd;
+ register_fd(&lcr_gsm->mncc_lfd, LCR_FD_READ, &mncc_fd_cb, lcr_gsm, 0);
+ }
+
return 0;
}