diff --git a/include/openbsc/gsm_04_08.h b/include/openbsc/gsm_04_08.h index 6d6ead183..ca251b00b 100644 --- a/include/openbsc/gsm_04_08.h +++ b/include/openbsc/gsm_04_08.h @@ -80,4 +80,6 @@ void allocate_security_operation(struct gsm_subscriber_connection *conn); int gsm48_multirate_config(uint8_t *lv, const struct amr_multirate_conf *mr, const struct amr_mode *modes); +int gsm48_tch_rtp_create(struct gsm_trans *trans); + #endif diff --git a/include/openbsc/msc_ifaces.h b/include/openbsc/msc_ifaces.h index 620859b7c..a1071ae9b 100644 --- a/include/openbsc/msc_ifaces.h +++ b/include/openbsc/msc_ifaces.h @@ -39,3 +39,4 @@ int msc_tx_common_id(struct gsm_subscriber_connection *conn); int msc_call_assignment(struct gsm_trans *trans); int msc_call_bridge(struct gsm_trans *trans1, struct gsm_trans *trans2); void msc_call_release(struct gsm_trans *trans); +int msc_call_connect(struct gsm_trans *trans, uint16_t port, uint32_t ip); diff --git a/include/openbsc/transaction.h b/include/openbsc/transaction.h index 4ed1d3ad4..4930fbd32 100644 --- a/include/openbsc/transaction.h +++ b/include/openbsc/transaction.h @@ -49,6 +49,13 @@ struct gsm_trans { /* bearer capabilities (rate and codec) */ struct gsm_mncc_bearer_cap bearer_cap; + /* status of the assignment, true when done */ + bool assignment_done; + + /* if true, TCH_RTP_CREATE is sent after the + * assignment is done */ + bool tch_rtp_create; + union { struct { diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c index 0909a1b02..2070da9a8 100644 --- a/src/libmsc/gsm_04_08.c +++ b/src/libmsc/gsm_04_08.c @@ -1257,6 +1257,8 @@ static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans, struct msgb *msg; unsigned char *data; + DEBUGP(DMNCC, "transmit message %s\n", get_mncc_name(msg_type)); + #if BEFORE_MSCSPLIT /* Re-enable this log output once we can obtain this information via * A-interface, see OS#2391. */ @@ -1681,6 +1683,7 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); struct tlv_parsed tp; struct gsm_mncc call_conf; + int rc; gsm48_stop_cc_timer(trans); gsm48_start_cc_timer(trans, 0x310, GSM48_T310); @@ -1726,7 +1729,18 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF); - msc_call_assignment(trans); + /* Assign call (if not done yet) */ + if (trans->assignment_done == false) { + rc = msc_call_assignment(trans); + trans->assignment_done = true; + } + else + rc = 0; + + /* don't continue, if there were problems with + * the call assignment. */ + if (rc) + return rc; return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND, &call_conf); @@ -1757,7 +1771,15 @@ static int gsm48_cc_tx_call_proc_and_assign(struct gsm_trans *trans, void *arg) if (rc) return rc; - return msc_call_assignment(trans); + /* Assign call (if not done yet) */ + if (trans->assignment_done == false) { + rc = msc_call_assignment(trans); + trans->assignment_done = true; + } + else + rc = 0; + + return rc; } static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg) @@ -2605,6 +2627,139 @@ static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg) return mncc_recvmsg(trans->net, trans, MNCC_USERINFO_IND, &user); } +static void mncc_recv_rtp(struct gsm_network *net, uint32_t callref, + int cmd, uint32_t addr, uint16_t port, uint32_t payload_type, + uint32_t payload_msg_type) +{ + uint8_t data[sizeof(struct gsm_mncc)]; + struct gsm_mncc_rtp *rtp; + + memset(&data, 0, sizeof(data)); + rtp = (struct gsm_mncc_rtp *) &data[0]; + + rtp->callref = callref; + rtp->msg_type = cmd; + rtp->ip = addr; + rtp->port = port; + rtp->payload_type = payload_type; + rtp->payload_msg_type = payload_msg_type; + mncc_recvmsg(net, NULL, cmd, (struct gsm_mncc *)data); +} + +static void mncc_recv_rtp_sock(struct gsm_network *net, struct gsm_trans *trans, int cmd) +{ + int msg_type; + + /* FIXME This has to be set to some meaningful value. + * Possible options are: + * GSM_TCHF_FRAME, GSM_TCHF_FRAME_EFR, + * GSM_TCHH_FRAME, GSM_TCH_FRAME_AMR + * (0 if unknown) */ + msg_type = GSM_TCHF_FRAME; + + uint32_t addr = mgcpgw_client_remote_addr_n(net->mgcpgw.client); + uint16_t port = trans->conn->rtp.port_cn; + + /* FIXME: This has to be set to some meaningful value, + * before the MSC-Split, this value was pulled from + * lchan->abis_ip.rtp_payload */ + uint32_t payload_type = 0; + + return mncc_recv_rtp(net, trans->callref, cmd, + addr, + port, + payload_type, + msg_type); +} + +static void mncc_recv_rtp_err(struct gsm_network *net, uint32_t callref, int cmd) +{ + return mncc_recv_rtp(net, callref, cmd, 0, 0, 0, 0); +} + +static int tch_rtp_create(struct gsm_network *net, uint32_t callref) +{ + struct gsm_trans *trans; + int rc; + + /* Find callref */ + trans = trans_find_by_callref(net, callref); + if (!trans) { + LOGP(DMNCC, LOGL_ERROR, "RTP create for non-existing trans\n"); + mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE); + return -EIO; + } + log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub); + if (!trans->conn) { + LOGP(DMNCC, LOGL_NOTICE, "RTP create for trans without conn\n"); + mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE); + return 0; + } + + trans->conn->mncc_rtp_bridge = 1; + + /* When we call msc_call_assignment() we will trigger, depending + * on the RAN type the call assignment on the A or Iu interface. + * msc_call_assignment() also takes care about sending the CRCX + * command to the MGCP-GW. The CRCX will return the port number, + * where the PBX (e.g. Asterisk) will send its RTP stream to. We + * have to return this port number back to the MNCC by sending + * it back with the TCH_RTP_CREATE message. To make sure that + * this message is sent AFTER the response to CRCX from the + * MGCP-GW has arrived, we need will instruct msc_call_assignment() + * to take care of this by setting trans->tch_rtp_create to true. + * This will make sure that gsm48_tch_rtp_create() (below) is + * called as soon as the local port number has become known. */ + trans->tch_rtp_create = true; + + /* Assign call (if not done yet) */ + if (trans->assignment_done == false) { + rc = msc_call_assignment(trans); + trans->assignment_done = true; + } + else + rc = 0; + + return rc; +} + +/* Trigger TCH_RTP_CREATE acknowledgement */ +int gsm48_tch_rtp_create(struct gsm_trans *trans) +{ + /* This function is called as soon as the port, on which the + * mgcp-gw expects the incoming RTP stream from the remote + * end (e.g. Asterisk) is known. */ + + struct gsm_subscriber_connection *conn = trans->conn; + struct gsm_network *network = conn->network; + + mncc_recv_rtp_sock(network, trans, MNCC_RTP_CREATE); + return 0; +} + +static int tch_rtp_connect(struct gsm_network *net, void *arg) +{ + struct gsm_trans *trans; + struct gsm_mncc_rtp *rtp = arg; + + /* Find callref */ + trans = trans_find_by_callref(net, rtp->callref); + if (!trans) { + LOGP(DMNCC, LOGL_ERROR, "RTP connect for non-existing trans\n"); + mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT); + return -EIO; + } + log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub); + if (!trans->conn) { + LOGP(DMNCC, LOGL_ERROR, "RTP connect for trans without conn\n"); + mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT); + return 0; + } + + msc_call_connect(trans,rtp->port,rtp->ip); + return 0; +} + static struct downstate { uint32_t states; int type; @@ -2680,11 +2835,16 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg) if (rc < 0) disconnect_bridge(net, arg, -rc); return rc; + case MNCC_RTP_CREATE: + return tch_rtp_create(net, data->callref); + case MNCC_RTP_CONNECT: + return tch_rtp_connect(net, arg); + case MNCC_RTP_FREE: + /* unused right now */ + return -EIO; + case MNCC_FRAME_DROP: case MNCC_FRAME_RECV: - case MNCC_RTP_CREATE: - case MNCC_RTP_CONNECT: - case MNCC_RTP_FREE: case GSM_TCHF_FRAME: case GSM_TCHF_FRAME_EFR: case GSM_TCHH_FRAME: diff --git a/src/libmsc/msc_ifaces.c b/src/libmsc/msc_ifaces.c index 7059dd000..05435c7d1 100644 --- a/src/libmsc/msc_ifaces.c +++ b/src/libmsc/msc_ifaces.c @@ -203,6 +203,12 @@ static void mgcp_response_rab_act_cs_crcx(struct mgcp_response *r, void *priv) } else goto rab_act_cs_error; + /* Respond back to MNCC (if requested) */ + if (trans->tch_rtp_create) { + if (gsm48_tch_rtp_create(trans)) + goto rab_act_cs_error; + } + rab_act_cs_error: /* FIXME abort call, invalidate conn, ... */ return; @@ -360,6 +366,51 @@ static void mgcp_response_bridge_mdcx(struct mgcp_response *r, void *priv) } } +int msc_call_connect(struct gsm_trans *trans, uint16_t port, uint32_t ip) +{ + /* With this function we inform the MGCP-GW where (ip/port) it + * has to send its outgoing voic traffic. The receiving end will + * usually be a PBX (e.g. Asterisk). The IP-Address we tell, will + * not only be used to direct the traffic, it will also be used + * as a filter to make sure only RTP packets from the right + * remote end will reach the BSS. This is also the reason why + * inbound audio will not work until this step is performed */ + + /* NOTE: This function is used when msc_call_bridge(), is not + * applicable. This is usually the case when an external MNCC + * is in use */ + + struct gsm_subscriber_connection *conn; + struct mgcpgw_client *mgcp; + struct msgb *msg; + + if (!trans) + return -EINVAL; + if (!trans->conn) + return -EINVAL; + if (!trans->conn->network) + return -EINVAL; + if (!trans->conn->network->mgcpgw.client) + return -EINVAL; + + mgcp = trans->conn->network->mgcpgw.client; + + struct in_addr ip_addr; + ip_addr.s_addr = ntohl(ip); + + conn = trans->conn; + + msg = mgcp_msg_mdcx(mgcp, + conn->iu.mgcp_rtp_endpoint, + inet_ntoa(ip_addr), port, MGCP_CONN_RECV_SEND); + if (mgcpgw_client_tx(mgcp, msg, NULL, trans)) + LOGP(DMGCP, LOGL_ERROR, + "Failed to send MDCX message for %s\n", + vlr_subscr_name(trans->vsub)); + + return 0; +} + int msc_call_bridge(struct gsm_trans *trans1, struct gsm_trans *trans2) { if (!trans1)