623 lines
16 KiB
C
623 lines
16 KiB
C
/* mgcp_utils - common functions to setup an MGCP connection
|
|
*/
|
|
/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
|
* All Rights Reserved
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include <osmocom/core/linuxlist.h>
|
|
#include <osmocom/core/select.h>
|
|
#include <osmocom/core/write_queue.h>
|
|
#include <osmocom/core/msgb.h>
|
|
#include <osmocom/core/logging.h>
|
|
|
|
#include <osmocom/legacy_mgcp/mgcp.h>
|
|
#include <osmocom/legacy_mgcp/mgcp_internal.h>
|
|
#include <osmocom/mgcp_client/mgcpgw_client.h>
|
|
#include <osmocom/mgcp_client/mgcpgw_client_internal.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
void mgcpgw_client_conf_init(struct mgcpgw_client_conf *conf)
|
|
{
|
|
/* NULL and -1 default to MGCPGW_CLIENT_*_DEFAULT values */
|
|
*conf = (struct mgcpgw_client_conf){
|
|
.local_addr = NULL,
|
|
.local_port = -1,
|
|
.remote_addr = NULL,
|
|
.remote_port = -1,
|
|
.first_endpoint = 0,
|
|
.last_endpoint = 0,
|
|
.bts_base = 0,
|
|
};
|
|
}
|
|
|
|
/* Test if a given endpoint id is currently in use */
|
|
static bool endpoint_in_use(uint16_t id, struct mgcpgw_client *client)
|
|
{
|
|
struct mgcp_inuse_endpoint *endpoint;
|
|
llist_for_each_entry(endpoint, &client->inuse_endpoints, entry) {
|
|
if (endpoint->id == id)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Find and seize an unsused endpoint id */
|
|
int mgcpgw_client_next_endpoint(struct mgcpgw_client *client)
|
|
{
|
|
int i;
|
|
uint16_t first_endpoint = client->actual.first_endpoint;
|
|
uint16_t last_endpoint = client->actual.last_endpoint;
|
|
struct mgcp_inuse_endpoint *endpoint;
|
|
|
|
/* Use the maximum permitted range if the VTY
|
|
* configuration does not specify a range */
|
|
if (client->actual.last_endpoint == 0) {
|
|
first_endpoint = 1;
|
|
last_endpoint = 65534;
|
|
}
|
|
|
|
/* Test the permitted endpoint range for an endpoint
|
|
* number that is not in use. When a suitable endpoint
|
|
* number can be found, seize it by adding it to the
|
|
* inuse list. */
|
|
for (i=first_endpoint;i<last_endpoint;i++)
|
|
{
|
|
if (endpoint_in_use(i,client) == false) {
|
|
endpoint = talloc_zero(client, struct mgcp_inuse_endpoint);
|
|
endpoint->id = i;
|
|
llist_add_tail(&endpoint->entry, &client->inuse_endpoints);
|
|
return endpoint->id;
|
|
}
|
|
}
|
|
|
|
/* All endpoints are busy! */
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Release a seized endpoint id to make it available again for other calls */
|
|
void mgcpgw_client_release_endpoint(uint16_t id, struct mgcpgw_client *client)
|
|
{
|
|
struct mgcp_inuse_endpoint *endpoint;
|
|
struct mgcp_inuse_endpoint *endpoint_tmp;
|
|
llist_for_each_entry_safe(endpoint, endpoint_tmp, &client->inuse_endpoints, entry) {
|
|
if (endpoint->id == id) {
|
|
llist_del(&endpoint->entry);
|
|
talloc_free(endpoint);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mgcpgw_client_handle_response(struct mgcpgw_client *mgcp,
|
|
struct mgcp_response_pending *pending,
|
|
struct mgcp_response *response)
|
|
{
|
|
if (!pending) {
|
|
LOGP(DLMGCP, LOGL_ERROR,
|
|
"Cannot handle NULL response\n");
|
|
return;
|
|
}
|
|
if (pending->response_cb)
|
|
pending->response_cb(response, pending->priv);
|
|
else
|
|
LOGP(DLMGCP, LOGL_INFO, "MGCP response ignored (NULL cb)\n");
|
|
talloc_free(pending);
|
|
}
|
|
|
|
static int mgcp_response_parse_head(struct mgcp_response *r, struct msgb *msg)
|
|
{
|
|
int comment_pos;
|
|
char *end;
|
|
|
|
if (mgcp_msg_terminate_nul(msg))
|
|
goto response_parse_failure;
|
|
|
|
r->body = (char *)msg->data;
|
|
|
|
if (sscanf(r->body, "%3d %u %n",
|
|
&r->head.response_code, &r->head.trans_id,
|
|
&comment_pos) != 2)
|
|
goto response_parse_failure;
|
|
|
|
r->head.comment = r->body + comment_pos;
|
|
end = strchr(r->head.comment, '\r');
|
|
if (!end)
|
|
goto response_parse_failure;
|
|
/* Mark the end of the comment */
|
|
*end = '\0';
|
|
r->body = end + 1;
|
|
if (r->body[0] == '\n')
|
|
r->body ++;
|
|
return 0;
|
|
|
|
response_parse_failure:
|
|
LOGP(DLMGCP, LOGL_ERROR,
|
|
"Failed to parse MGCP response header\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* TODO undup against mgcp_protocol.c:mgcp_check_param() */
|
|
static bool mgcp_line_is_valid(const char *line)
|
|
{
|
|
const size_t line_len = strlen(line);
|
|
if (line[0] == '\0')
|
|
return true;
|
|
|
|
if (line_len < 2
|
|
|| line[1] != '=') {
|
|
LOGP(DLMGCP, LOGL_ERROR,
|
|
"Wrong MGCP option format: '%s'\n",
|
|
line);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Parse a line like "m=audio 16002 RTP/AVP 98" */
|
|
static int mgcp_parse_audio(struct mgcp_response *r, const char *line)
|
|
{
|
|
if (sscanf(line, "m=audio %hu",
|
|
&r->audio_port) != 1)
|
|
goto response_parse_failure;
|
|
|
|
return 0;
|
|
|
|
response_parse_failure:
|
|
LOGP(DLMGCP, LOGL_ERROR,
|
|
"Failed to parse MGCP response header\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
int mgcp_response_parse_params(struct mgcp_response *r)
|
|
{
|
|
char *line;
|
|
int rc;
|
|
OSMO_ASSERT(r->body);
|
|
char *data = strstr(r->body, "\n\n");
|
|
|
|
if (!data) {
|
|
LOGP(DLMGCP, LOGL_ERROR,
|
|
"MGCP response: cannot find start of parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Advance to after the \n\n, replace the second \n with \0. That's
|
|
* where the parameters start. */
|
|
data ++;
|
|
*data = '\0';
|
|
data ++;
|
|
|
|
for_each_line(line, data) {
|
|
if (!mgcp_line_is_valid(line))
|
|
return -EINVAL;
|
|
|
|
switch (line[0]) {
|
|
case 'm':
|
|
rc = mgcp_parse_audio(r, line);
|
|
if (rc)
|
|
return rc;
|
|
break;
|
|
default:
|
|
/* skip unhandled parameters */
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct mgcp_response_pending *mgcpgw_client_response_pending_get(
|
|
struct mgcpgw_client *mgcp,
|
|
struct mgcp_response *r)
|
|
{
|
|
struct mgcp_response_pending *pending;
|
|
if (!r)
|
|
return NULL;
|
|
llist_for_each_entry(pending, &mgcp->responses_pending, entry) {
|
|
if (pending->trans_id == r->head.trans_id) {
|
|
llist_del(&pending->entry);
|
|
return pending;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Feed an MGCP message into the receive processing.
|
|
* Parse the head and call any callback registered for the transaction id found
|
|
* in the MGCP message. This is normally called directly from the internal
|
|
* mgcp_do_read that reads from the socket connected to the MGCP gateway. This
|
|
* function is published mainly to be able to feed data from the test suite.
|
|
*/
|
|
int mgcpgw_client_rx(struct mgcpgw_client *mgcp, struct msgb *msg)
|
|
{
|
|
struct mgcp_response r = { 0 };
|
|
struct mgcp_response_pending *pending;
|
|
int rc;
|
|
|
|
rc = mgcp_response_parse_head(&r, msg);
|
|
if (rc) {
|
|
LOGP(DLMGCP, LOGL_ERROR, "Cannot parse MGCP response\n");
|
|
return -1;
|
|
}
|
|
|
|
pending = mgcpgw_client_response_pending_get(mgcp, &r);
|
|
if (!pending) {
|
|
LOGP(DLMGCP, LOGL_ERROR,
|
|
"Cannot find matching MGCP transaction for trans_id %d\n",
|
|
r.head.trans_id);
|
|
return -1;
|
|
}
|
|
|
|
mgcpgw_client_handle_response(mgcp, pending, &r);
|
|
return 0;
|
|
}
|
|
|
|
static int mgcp_do_read(struct osmo_fd *fd)
|
|
{
|
|
struct mgcpgw_client *mgcp = fd->data;
|
|
struct msgb *msg;
|
|
int ret;
|
|
|
|
msg = msgb_alloc_headroom(4096, 128, "mgcp_from_gw");
|
|
if (!msg) {
|
|
LOGP(DLMGCP, LOGL_ERROR, "Failed to allocate MGCP message.\n");
|
|
return -1;
|
|
}
|
|
|
|
ret = read(fd->fd, msg->data, 4096 - 128);
|
|
if (ret <= 0) {
|
|
LOGP(DLMGCP, LOGL_ERROR, "Failed to read: %d/%s\n", errno, strerror(errno));
|
|
msgb_free(msg);
|
|
return -1;
|
|
} else if (ret > 4096 - 128) {
|
|
LOGP(DLMGCP, LOGL_ERROR, "Too much data: %d\n", ret);
|
|
msgb_free(msg);
|
|
return -1;
|
|
}
|
|
|
|
msg->l2h = msgb_put(msg, ret);
|
|
ret = mgcpgw_client_rx(mgcp, msg);
|
|
talloc_free(msg);
|
|
return ret;
|
|
}
|
|
|
|
static int mgcp_do_write(struct osmo_fd *fd, struct msgb *msg)
|
|
{
|
|
int ret;
|
|
static char strbuf[4096];
|
|
unsigned int l = msg->len < sizeof(strbuf) ? msg->len : sizeof(strbuf);
|
|
unsigned int i;
|
|
|
|
strncpy(strbuf, (const char*)msg->data, l);
|
|
for (i = 0; i < sizeof(strbuf); i++) {
|
|
if (strbuf[i] == '\n' || strbuf[i] == '\r') {
|
|
strbuf[i] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
DEBUGP(DLMGCP, "Tx MGCP msg to MGCP GW: '%s'\n", strbuf);
|
|
|
|
LOGP(DLMGCP, LOGL_DEBUG, "Sending msg to MGCP GW size: %u\n", msg->len);
|
|
|
|
ret = write(fd->fd, msg->data, msg->len);
|
|
if (ret != msg->len)
|
|
LOGP(DLMGCP, LOGL_ERROR, "Failed to forward message to MGCP"
|
|
" GW: %s\n", strerror(errno));
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct mgcpgw_client *mgcpgw_client_init(void *ctx,
|
|
struct mgcpgw_client_conf *conf)
|
|
{
|
|
struct mgcpgw_client *mgcp;
|
|
|
|
mgcp = talloc_zero(ctx, struct mgcpgw_client);
|
|
|
|
INIT_LLIST_HEAD(&mgcp->responses_pending);
|
|
INIT_LLIST_HEAD(&mgcp->inuse_endpoints);
|
|
|
|
mgcp->next_trans_id = 1;
|
|
|
|
mgcp->actual.local_addr = conf->local_addr ? conf->local_addr :
|
|
MGCPGW_CLIENT_LOCAL_ADDR_DEFAULT;
|
|
mgcp->actual.local_port = conf->local_port >= 0 ? (uint16_t)conf->local_port :
|
|
MGCPGW_CLIENT_LOCAL_PORT_DEFAULT;
|
|
|
|
mgcp->actual.remote_addr = conf->remote_addr ? conf->remote_addr :
|
|
MGCPGW_CLIENT_REMOTE_ADDR_DEFAULT;
|
|
mgcp->actual.remote_port = conf->remote_port >= 0 ? (uint16_t)conf->remote_port :
|
|
MGCPGW_CLIENT_REMOTE_PORT_DEFAULT;
|
|
|
|
mgcp->actual.first_endpoint = conf->first_endpoint > 0 ? (uint16_t)conf->first_endpoint : 0;
|
|
mgcp->actual.last_endpoint = conf->last_endpoint > 0 ? (uint16_t)conf->last_endpoint : 0;
|
|
mgcp->actual.bts_base = conf->bts_base > 0 ? (uint16_t)conf->bts_base : 4000;
|
|
|
|
return mgcp;
|
|
}
|
|
|
|
int mgcpgw_client_connect(struct mgcpgw_client *mgcp)
|
|
{
|
|
int on;
|
|
struct sockaddr_in addr;
|
|
struct osmo_wqueue *wq;
|
|
int rc;
|
|
|
|
if (!mgcp) {
|
|
LOGP(DLMGCP, LOGL_FATAL, "MGCPGW client not initialized properly\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
wq = &mgcp->wq;
|
|
|
|
wq->bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (wq->bfd.fd < 0) {
|
|
LOGP(DLMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno);
|
|
return -errno;
|
|
}
|
|
|
|
on = 1;
|
|
if (setsockopt(wq->bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
|
|
LOGP(DLMGCP, LOGL_FATAL,
|
|
"Failed to initialize socket for MGCP GW: %s\n",
|
|
strerror(errno));
|
|
rc = -errno;
|
|
goto error_close_fd;
|
|
}
|
|
|
|
/* bind socket */
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
inet_aton(mgcp->actual.local_addr, &addr.sin_addr);
|
|
addr.sin_port = htons(mgcp->actual.local_port);
|
|
if (bind(wq->bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
|
|
LOGP(DLMGCP, LOGL_FATAL,
|
|
"Failed to bind for MGCP GW to %s %u\n",
|
|
mgcp->actual.local_addr, mgcp->actual.local_port);
|
|
rc = -errno;
|
|
goto error_close_fd;
|
|
}
|
|
|
|
/* connect to the remote */
|
|
inet_aton(mgcp->actual.remote_addr, &addr.sin_addr);
|
|
addr.sin_port = htons(mgcp->actual.remote_port);
|
|
if (connect(wq->bfd.fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
LOGP(DLMGCP, LOGL_FATAL,
|
|
"Failed to connect to MGCP GW at %s %u: %s\n",
|
|
mgcp->actual.remote_addr, mgcp->actual.remote_port,
|
|
strerror(errno));
|
|
rc = -errno;
|
|
goto error_close_fd;
|
|
}
|
|
|
|
mgcp->remote_addr = htonl(addr.sin_addr.s_addr);
|
|
|
|
osmo_wqueue_init(wq, 10);
|
|
wq->bfd.when = BSC_FD_READ;
|
|
wq->bfd.data = mgcp;
|
|
wq->read_cb = mgcp_do_read;
|
|
wq->write_cb = mgcp_do_write;
|
|
|
|
if (osmo_fd_register(&wq->bfd) != 0) {
|
|
LOGP(DLMGCP, LOGL_FATAL, "Failed to register BFD\n");
|
|
rc = -EIO;
|
|
goto error_close_fd;
|
|
}
|
|
LOGP(DLMGCP, LOGL_INFO, "MGCP GW connection: %s:%u -> %s:%u\n",
|
|
mgcp->actual.local_addr, mgcp->actual.local_port,
|
|
mgcp->actual.remote_addr, mgcp->actual.remote_port);
|
|
|
|
return 0;
|
|
error_close_fd:
|
|
close(wq->bfd.fd);
|
|
wq->bfd.fd = -1;
|
|
return rc;
|
|
}
|
|
|
|
const char *mgcpgw_client_remote_addr_str(struct mgcpgw_client *mgcp)
|
|
{
|
|
return mgcp->actual.remote_addr;
|
|
}
|
|
|
|
uint16_t mgcpgw_client_remote_port(struct mgcpgw_client *mgcp)
|
|
{
|
|
return mgcp->actual.remote_port;
|
|
}
|
|
|
|
/* Return the MGCP GW binary IPv4 address in network byte order. */
|
|
uint32_t mgcpgw_client_remote_addr_n(struct mgcpgw_client *mgcp)
|
|
{
|
|
return mgcp->remote_addr;
|
|
}
|
|
|
|
struct mgcp_response_pending * mgcpgw_client_pending_add(
|
|
struct mgcpgw_client *mgcp,
|
|
mgcp_trans_id_t trans_id,
|
|
mgcp_response_cb_t response_cb,
|
|
void *priv)
|
|
{
|
|
struct mgcp_response_pending *pending;
|
|
|
|
pending = talloc_zero(mgcp, struct mgcp_response_pending);
|
|
pending->trans_id = trans_id;
|
|
pending->response_cb = response_cb;
|
|
pending->priv = priv;
|
|
llist_add_tail(&pending->entry, &mgcp->responses_pending);
|
|
|
|
return pending;
|
|
}
|
|
|
|
/* Send the MGCP message in msg to the MGCP GW and handle a response with
|
|
* response_cb. NOTE: the response_cb still needs to call
|
|
* mgcp_response_parse_params(response) to get the parsed parameters -- to
|
|
* potentially save some CPU cycles, only the head line has been parsed when
|
|
* the response_cb is invoked. */
|
|
int mgcpgw_client_tx(struct mgcpgw_client *mgcp, struct msgb *msg,
|
|
mgcp_response_cb_t response_cb, void *priv)
|
|
{
|
|
struct mgcp_response_pending *pending;
|
|
mgcp_trans_id_t trans_id;
|
|
int rc;
|
|
|
|
trans_id = msg->cb[MSGB_CB_MGCP_TRANS_ID];
|
|
if (!trans_id) {
|
|
LOGP(DLMGCP, LOGL_ERROR,
|
|
"Unset transaction id in mgcp send request\n");
|
|
talloc_free(msg);
|
|
return -EINVAL;
|
|
}
|
|
|
|
pending = mgcpgw_client_pending_add(mgcp, trans_id, response_cb, priv);
|
|
|
|
if (msgb_l2len(msg) > 4096) {
|
|
LOGP(DLMGCP, LOGL_ERROR,
|
|
"Cannot send, MGCP message too large: %u\n",
|
|
msgb_l2len(msg));
|
|
msgb_free(msg);
|
|
rc = -EINVAL;
|
|
goto mgcp_tx_error;
|
|
}
|
|
|
|
rc = osmo_wqueue_enqueue(&mgcp->wq, msg);
|
|
if (rc) {
|
|
LOGP(DLMGCP, LOGL_FATAL, "Could not queue message to MGCP GW\n");
|
|
msgb_free(msg);
|
|
goto mgcp_tx_error;
|
|
} else
|
|
LOGP(DLMGCP, LOGL_INFO, "Queued %u bytes for MGCP GW\n",
|
|
msgb_l2len(msg));
|
|
return 0;
|
|
|
|
mgcp_tx_error:
|
|
/* Pass NULL to response cb to indicate an error */
|
|
mgcpgw_client_handle_response(mgcp, pending, NULL);
|
|
return -1;
|
|
}
|
|
|
|
static struct msgb *mgcp_msg_from_buf(mgcp_trans_id_t trans_id,
|
|
const char *buf, int len)
|
|
{
|
|
struct msgb *msg;
|
|
|
|
if (len > (4096 - 128)) {
|
|
LOGP(DLMGCP, LOGL_ERROR, "Cannot send to MGCP GW:"
|
|
" message too large: %d\n", len);
|
|
return NULL;
|
|
}
|
|
|
|
msg = msgb_alloc_headroom(4096, 128, "MGCP tx");
|
|
OSMO_ASSERT(msg);
|
|
|
|
char *dst = (char*)msgb_put(msg, len);
|
|
memcpy(dst, buf, len);
|
|
msg->l2h = msg->data;
|
|
msg->cb[MSGB_CB_MGCP_TRANS_ID] = trans_id;
|
|
|
|
return msg;
|
|
}
|
|
|
|
static struct msgb *mgcp_msg_from_str(mgcp_trans_id_t trans_id,
|
|
const char *fmt, ...)
|
|
{
|
|
static char compose[4096 - 128];
|
|
va_list ap;
|
|
int len;
|
|
OSMO_ASSERT(fmt);
|
|
|
|
va_start(ap, fmt);
|
|
len = vsnprintf(compose, sizeof(compose), fmt, ap);
|
|
va_end(ap);
|
|
if (len >= sizeof(compose)) {
|
|
LOGP(DLMGCP, LOGL_ERROR,
|
|
"Message too large: trans_id=%u len=%d\n",
|
|
trans_id, len);
|
|
return NULL;
|
|
}
|
|
if (len < 1) {
|
|
LOGP(DLMGCP, LOGL_ERROR,
|
|
"Failed to compose message: trans_id=%u len=%d\n",
|
|
trans_id, len);
|
|
return NULL;
|
|
}
|
|
return mgcp_msg_from_buf(trans_id, compose, len);
|
|
}
|
|
|
|
static mgcp_trans_id_t mgcpgw_client_next_trans_id(struct mgcpgw_client *mgcp)
|
|
{
|
|
/* avoid zero trans_id to distinguish from unset trans_id */
|
|
if (!mgcp->next_trans_id)
|
|
mgcp->next_trans_id ++;
|
|
return mgcp->next_trans_id ++;
|
|
}
|
|
|
|
struct msgb *mgcp_msg_crcx(struct mgcpgw_client *mgcp,
|
|
uint16_t rtp_endpoint, unsigned int call_id,
|
|
enum mgcp_connection_mode mode)
|
|
{
|
|
mgcp_trans_id_t trans_id = mgcpgw_client_next_trans_id(mgcp);
|
|
return mgcp_msg_from_str(trans_id,
|
|
"CRCX %u %x@mgw MGCP 1.0\r\n"
|
|
"C: %x\r\n"
|
|
"L: p:20, a:AMR, nt:IN\r\n"
|
|
"M: %s\r\n"
|
|
,
|
|
trans_id,
|
|
rtp_endpoint,
|
|
call_id,
|
|
mgcp_cmode_name(mode));
|
|
}
|
|
|
|
struct msgb *mgcp_msg_mdcx(struct mgcpgw_client *mgcp,
|
|
uint16_t rtp_endpoint, const char *rtp_conn_addr,
|
|
uint16_t rtp_port, enum mgcp_connection_mode mode)
|
|
|
|
{
|
|
mgcp_trans_id_t trans_id = mgcpgw_client_next_trans_id(mgcp);
|
|
return mgcp_msg_from_str(trans_id,
|
|
"MDCX %u %x@mgw MGCP 1.0\r\n"
|
|
"M: %s\r\n"
|
|
"\r\n"
|
|
"c=IN IP4 %s\r\n"
|
|
"m=audio %u RTP/AVP 255\r\n"
|
|
,
|
|
trans_id,
|
|
rtp_endpoint,
|
|
mgcp_cmode_name(mode),
|
|
rtp_conn_addr,
|
|
rtp_port);
|
|
}
|
|
|
|
struct msgb *mgcp_msg_dlcx(struct mgcpgw_client *mgcp, uint16_t rtp_endpoint,
|
|
unsigned int call_id)
|
|
{
|
|
mgcp_trans_id_t trans_id = mgcpgw_client_next_trans_id(mgcp);
|
|
return mgcp_msg_from_str(trans_id,
|
|
"DLCX %u %x@mgw MGCP 1.0\r\n"
|
|
"C: %x\r\n", trans_id, rtp_endpoint, call_id);
|
|
}
|
|
|
|
struct mgcpgw_client_conf *mgcpgw_client_conf_actual(struct mgcpgw_client *mgcp)
|
|
{
|
|
return &mgcp->actual;
|
|
}
|