osmo-mgw/src/libosmo-mgcp-client/mgcp_client.c

1361 lines
37 KiB
C
Raw Normal View History

/* 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 General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU 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/core/byteswap.h>
#include <osmocom/core/socket.h>
#include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/mgcp_client/mgcp_client_internal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
/* Codec descripton for dynamic payload types (SDP) */
static const struct value_string codec_table[] = {
{ CODEC_PCMU_8000_1, "PCMU/8000/1" },
{ CODEC_GSM_8000_1, "GSM/8000/1" },
{ CODEC_PCMA_8000_1, "PCMA/8000/1" },
{ CODEC_G729_8000_1, "G729/8000/1" },
{ CODEC_GSMEFR_8000_1, "GSM-EFR/8000/1" },
{ CODEC_GSMHR_8000_1, "GSM-HR-08/8000/1" },
{ CODEC_AMR_8000_1, "AMR/8000/1" },
{ CODEC_AMRWB_16000_1, "AMR-WB/16000/1" },
{ 0, NULL },
};
/* Get encoding name from a full codec string e,g.
* ("CODEC/8000/2" => returns "CODEC") */
static char *extract_codec_name(const char *str)
{
static char buf[64];
unsigned int i;
if (!str)
return NULL;
/* FIXME osmo_strlcpy */
osmo_strlcpy(buf, str, sizeof(buf));
for (i = 0; i < strlen(buf); i++) {
if (buf[i] == '/')
buf[i] = '\0';
}
return buf;
}
/*! Map a string to a codec.
* \ptmap[in] str input string (e.g "GSM/8000/1", "GSM/8000" or "GSM")
* \returns codec that corresponds to the given string representation. */
enum mgcp_codecs map_str_to_codec(const char *str)
{
unsigned int i;
char *codec_name;
char str_buf[64];
osmo_strlcpy(str_buf, extract_codec_name(str), sizeof(str_buf));
for (i = 0; i < ARRAY_SIZE(codec_table); i++) {
codec_name = extract_codec_name(codec_table[i].str);
if (!codec_name)
continue;
if (strcmp(codec_name, str_buf) == 0)
return codec_table[i].value;
}
return -1;
}
/* Check the ptmap for illegal mappings */
static int check_ptmap(struct ptmap *ptmap)
{
/* Check if there are mappings that leave the IANA assigned dynamic
* payload type range. Under normal conditions such mappings should
* not occur */
/* Its ok to have a 1:1 mapping in the statically defined
* range, this won't hurt */
if (ptmap->codec == ptmap->pt)
return 0;
if (ptmap->codec < 96 || ptmap->codec > 127)
goto error;
if (ptmap->pt < 96 || ptmap->pt > 127)
goto error;
return 0;
error:
LOGP(DLMGCP, LOGL_ERROR,
"ptmap contains illegal mapping: codec=%u maps to pt=%u\n",
ptmap->codec, ptmap->pt);
return -1;
}
/*! Map a codec to a payload type.
* \ptmap[in] payload pointer to payload type map with specified payload types.
* \ptmap[in] ptmap_len length of the payload type map.
* \ptmap[in] codec the codec for which the payload type should be looked up.
* \returns assigned payload type */
unsigned int map_codec_to_pt(struct ptmap *ptmap, unsigned int ptmap_len,
enum mgcp_codecs codec)
{
unsigned int i;
/*! Note: If the payload type map is empty or the codec is not found
* in the map, then a 1:1 mapping is performed. If the codec falls
* into the statically defined range or if the mapping table isself
* tries to map to the statically defined range, then the mapping
* is also ignored and a 1:1 mapping is performed instead. */
/* we may return the codec directly since enum mgcp_codecs directly
* corresponds to the statićally assigned payload types */
if (codec < 96 || codec > 127)
return codec;
for (i = 0; i < ptmap_len; i++) {
/* Skip illegal map entries */
if (check_ptmap(ptmap) == 0 && ptmap->codec == codec)
return ptmap->pt;
ptmap++;
}
/* If nothing is found, do not perform any mapping */
return codec;
}
/*! Map a payload type to a codec.
* \ptmap[in] payload pointer to payload type map with specified payload types.
* \ptmap[in] ptmap_len length of the payload type map.
* \ptmap[in] payload type for which the codec should be looked up.
* \returns codec that corresponds to the specified payload type */
enum mgcp_codecs map_pt_to_codec(struct ptmap *ptmap, unsigned int ptmap_len,
unsigned int pt)
{
unsigned int i;
/*! Note: If the payload type map is empty or the payload type is not
* found in the map, then a 1:1 mapping is performed. If the payload
* type falls into the statically defined range or if the mapping
* table isself tries to map to the statically defined range, then
* the mapping is also ignored and a 1:1 mapping is performed
* instead. */
/* See also note in map_codec_to_pt() */
if (pt < 96 || pt > 127)
return pt;
for (i = 0; i < ptmap_len; i++) {
if (check_ptmap(ptmap) == 0 && ptmap->pt == pt)
return ptmap->codec;
ptmap++;
}
/* If nothing is found, do not perform any mapping */
return pt;
}
/*! Initalize MGCP client configuration struct with default values.
* \param[out] conf Client configuration.*/
void mgcp_client_conf_init(struct mgcp_client_conf *conf)
{
/* NULL and -1 default to MGCP_CLIENT_*_DEFAULT values */
*conf = (struct mgcp_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 mgcp_client *client)
{
struct mgcp_inuse_endpoint *endpoint;
llist_for_each_entry(endpoint, &client->inuse_endpoints, entry) {
if (endpoint->id == id)
return true;
}
return false;
}
/*! Pick next free endpoint ID.
* \param[in,out] client MGCP client descriptor.
* \returns 0 on success, -EINVAL on error. */
int mgcp_client_next_endpoint(struct mgcp_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.
* \param[in] id Endpoint ID
* \param[in,out] client MGCP client descriptor. */
/* Release a seized endpoint id to make it available again for other calls */
void mgcp_client_release_endpoint(uint16_t id, struct mgcp_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 mgcp_client_handle_response(struct mgcp_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;
osmo_strlcpy(r->head.comment, r->body + comment_pos, sizeof(r->head.comment));
end = strchr(r->head.comment, '\r');
if (!end)
goto response_parse_failure;
/* Mark the end of the comment */
*end = '\0';
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", extract port and payload types */
static int mgcp_parse_audio_port_pt(struct mgcp_response *r, char *line)
{
char *pt_str;
unsigned int pt;
unsigned int count = 0;
unsigned int i;
/* Extract port information */
if (sscanf(line, "m=audio %hu", &r->audio_port) != 1)
goto response_parse_failure_port;
if (r->audio_port == 0)
goto response_parse_failure_port;
/* Extract payload types */
line = strstr(line, "RTP/AVP ");
if (!line)
goto exit;
pt_str = strtok(line, " ");
while (1) {
/* Do not allow excessive payload types */
if (count > ARRAY_SIZE(r->codecs))
goto response_parse_failure_pt;
pt_str = strtok(NULL, " ");
if (!pt_str)
break;
pt = atoi(pt_str);
/* Do not allow duplicate payload types */
for (i = 0; i < count; i++)
if (r->codecs[i] == pt)
goto response_parse_failure_pt;
/* Note: The payload type we store may not necessarly match
* the codec types we have defined in enum mgcp_codecs. To
* ensure that the end result only contains codec types which
* match enum mgcp_codecs, we will go through afterwards and
* remap the affected entries with the inrofmation we learn
* from rtpmap */
r->codecs[count] = pt;
count++;
}
r->codecs_len = count;
exit:
return 0;
response_parse_failure_port:
LOGP(DLMGCP, LOGL_ERROR,
"Failed to parse SDP parameter port (%s)\n", line);
return -EINVAL;
response_parse_failure_pt:
LOGP(DLMGCP, LOGL_ERROR,
"Failed to parse SDP parameter payload types (%s)\n", line);
return -EINVAL;
}
/* Parse a line like "m=audio 16002 RTP/AVP 98", extract port and payload types */
static int mgcp_parse_audio_ptime_rtpmap(struct mgcp_response *r, const char *line)
{
unsigned int pt;
char codec_resp[64];
unsigned int codec;
if (strstr(line, "ptime")) {
if (sscanf(line, "a=ptime:%u", &r->ptime) != 1)
goto response_parse_failure_ptime;
} else if (strstr(line, "rtpmap")) {
if (sscanf(line, "a=rtpmap:%d %63s", &pt, codec_resp) == 2) {
/* The MGW may assign an own payload type in the
* response if the choosen codec falls into the IANA
* assigned dynamic payload type range (96-127).
* Normally the MGW should obey the 3gpp payload type
* assignments, which are fixed, so we likely wont see
* anything unexpected here. In order to be sure that
* we will now check the codec string and if the result
* does not match to what is IANA / 3gpp assigned, we
* will create an entry in the ptmap table so we can
* lookup later what has been assigned. */
codec = map_str_to_codec(codec_resp);
if (codec != pt) {
if (r->ptmap_len < ARRAY_SIZE(r->ptmap)) {
r->ptmap[r->ptmap_len].pt = pt;
r->ptmap[r->ptmap_len].codec = codec;
r->ptmap_len++;
} else
goto response_parse_failure_rtpmap;
}
} else
goto response_parse_failure_rtpmap;
}
return 0;
response_parse_failure_ptime:
LOGP(DLMGCP, LOGL_ERROR,
"Failed to parse SDP parameter, invalid ptime (%s)\n", line);
return -EINVAL;
response_parse_failure_rtpmap:
LOGP(DLMGCP, LOGL_ERROR,
"Failed to parse SDP parameter, invalid rtpmap (%s)\n", line);
return -EINVAL;
}
/* Parse a line like "c=IN IP4 10.11.12.13" */
static int mgcp_parse_audio_ip(struct mgcp_response *r, const char *line)
{
struct in_addr ip_test;
if (strlen(line) < 16)
goto response_parse_failure;
/* The current implementation strictly supports IPV4 only ! */
if (memcmp("c=IN IP4 ", line, 9) != 0)
goto response_parse_failure;
/* Extract IP-Address */
osmo_strlcpy(r->audio_ip, line + 9, sizeof(r->audio_ip));
/* Check IP-Address */
if (inet_aton(r->audio_ip, &ip_test) == 0)
goto response_parse_failure;
return 0;
response_parse_failure:
LOGP(DLMGCP, LOGL_ERROR,
"Failed to parse MGCP response header (audio ip)\n");
return -EINVAL;
}
/* A new section is marked by a double line break, check a few more
* patterns as there may be variants */
static char *mgcp_find_section_end(char *string)
{
char *rc;
rc = strstr(string, "\n\n");
if (rc)
return rc;
rc = strstr(string, "\n\r\n\r");
if (rc)
return rc;
rc = strstr(string, "\r\n\r\n");
if (rc)
return rc;
return NULL;
}
/*! Parse body (SDP) parameters of the MGCP response
* \param[in,out] r Response data
* \returns 0 on success, -EINVAL on error. */
int mgcp_response_parse_params(struct mgcp_response *r)
{
char *line;
int rc;
char *data;
char *data_ptr;
int i;
/* Since this functions performs a destructive parsing, we create a
* local copy of the body data */
OSMO_ASSERT(r->body);
data = talloc_strdup(r, r->body);
OSMO_ASSERT(data);
/* Find beginning of the parameter (SDP) section */
data_ptr = mgcp_find_section_end(data);
if (!data_ptr) {
LOGP(DLMGCP, LOGL_ERROR,
"MGCP response: cannot find start of SDP parameters\n");
rc = -EINVAL;
goto exit;
}
/* data_ptr now points to the beginning of the section-end-marker; for_each_non_empty_line()
* skips any \r and \n characters for free, so we don't need to skip the marker. */
for_each_non_empty_line(line, data_ptr) {
if (!mgcp_line_is_valid(line))
return -EINVAL;
switch (line[0]) {
case 'a':
rc = mgcp_parse_audio_ptime_rtpmap(r, line);
if (rc)
goto exit;
break;
case 'm':
rc = mgcp_parse_audio_port_pt(r, line);
if (rc)
goto exit;
break;
case 'c':
rc = mgcp_parse_audio_ip(r, line);
if (rc)
goto exit;
break;
default:
/* skip unhandled parameters */
break;
}
}
/* See also note in mgcp_parse_audio_port_pt() */
for (i = 0; i < r->codecs_len; i++)
r->codecs[i] = map_pt_to_codec(r->ptmap, r->ptmap_len, r->codecs[i]);
rc = 0;
exit:
talloc_free(data);
return rc;
}
/* Parse a line like "X: something" */
static int mgcp_parse_head_param(char *result, unsigned int result_len,
char label, const char *line)
{
char label_string[4];
/* Detect empty parameters */
if (strlen(line) < 4)
goto response_parse_failure;
/* Check if the label matches */
snprintf(label_string, sizeof(label_string), "%c: ", label);
if (memcmp(label_string, line, 3) != 0)
goto response_parse_failure;
/* Copy payload part of the string to destinations (the label string
* is always 3 chars long) */
osmo_strlcpy(result, line + 3, result_len);
return 0;
response_parse_failure:
LOGP(DLMGCP, LOGL_ERROR,
"Failed to parse MGCP response (parameter label: %c)\n", label);
return -EINVAL;
}
/* Parse MGCP parameters of the response */
static int parse_head_params(struct mgcp_response *r)
{
char *line;
int rc = 0;
OSMO_ASSERT(r->body);
char *data;
char *data_ptr;
char *data_end;
/* Since this functions performs a destructive parsing, we create a
* local copy of the body data */
data = talloc_zero_size(r, strlen(r->body)+1);
OSMO_ASSERT(data);
data_ptr = data;
osmo_strlcpy(data, r->body, strlen(r->body));
/* If there is an SDP body attached, prevent for_each_non_empty_line()
* into running in there, we are not yet interested in the parameters
* stored there. */
data_end = mgcp_find_section_end(data);
if (data_end)
*data_end = '\0';
for_each_non_empty_line(line, data_ptr) {
switch (line[0]) {
case 'Z':
rc = mgcp_parse_head_param(r->head.endpoint,
sizeof(r->head.endpoint),
'Z', line);
if (rc)
goto exit;
/* A specific endpoint identifier returned by the MGW
* must not contain any wildcard characters */
if (strstr(r->head.endpoint, "*") != NULL) {
rc = -EINVAL;
goto exit;
}
/* A specific endpoint identifier returned by the MGW
* must contain an @ character */
if (strstr(r->head.endpoint, "@") == NULL) {
rc = -EINVAL;
goto exit;
}
break;
case 'I':
rc = mgcp_parse_head_param(r->head.conn_id,
sizeof(r->head.conn_id),
'I', line);
if (rc)
goto exit;
break;
default:
/* skip unhandled parameters */
break;
}
}
exit:
talloc_free(data);
return rc;
}
static struct mgcp_response_pending *mgcp_client_response_pending_get(
struct mgcp_client *mgcp,
mgcp_trans_id_t trans_id)
{
struct mgcp_response_pending *pending;
llist_for_each_entry(pending, &mgcp->responses_pending, entry) {
if (pending->trans_id == 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 mgcp_client_rx(struct mgcp_client *mgcp, struct msgb *msg)
{
struct mgcp_response *r;
struct mgcp_response_pending *pending;
int rc;
r = talloc_zero(mgcp, struct mgcp_response);
OSMO_ASSERT(r);
rc = mgcp_response_parse_head(r, msg);
if (rc) {
LOGP(DLMGCP, LOGL_ERROR, "Cannot parse MGCP response (head)\n");
rc = 1;
goto error;
}
rc = parse_head_params(r);
if (rc) {
LOGP(DLMGCP, LOGL_ERROR, "Cannot parse MGCP response (head parameters)\n");
rc = 1;
goto error;
}
pending = mgcp_client_response_pending_get(mgcp, r->head.trans_id);
if (!pending) {
LOGP(DLMGCP, LOGL_ERROR,
"Cannot find matching MGCP transaction for trans_id %d\n",
r->head.trans_id);
rc = -ENOENT;
goto error;
}
mgcp_client_handle_response(mgcp, pending, r);
rc = 0;
error:
talloc_free(r);
return rc;
}
static int mgcp_do_read(struct osmo_fd *fd)
{
struct mgcp_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 = mgcp_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;
osmo_strlcpy(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 mgcp_client *mgcp_client_init(void *ctx,
struct mgcp_client_conf *conf)
{
struct mgcp_client *mgcp;
mgcp = talloc_zero(ctx, struct mgcp_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 :
MGCP_CLIENT_LOCAL_ADDR_DEFAULT;
mgcp->actual.local_port = conf->local_port >= 0 ? (uint16_t)conf->local_port :
MGCP_CLIENT_LOCAL_PORT_DEFAULT;
mgcp->actual.remote_addr = conf->remote_addr ? conf->remote_addr :
MGCP_CLIENT_REMOTE_ADDR_DEFAULT;
mgcp->actual.remote_port = conf->remote_port >= 0 ? (uint16_t)conf->remote_port :
MGCP_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;
}
static int init_socket(struct mgcp_client *mgcp)
{
int rc;
struct osmo_wqueue *wq;
int i;
wq = &mgcp->wq;
for (i = 0; i < 100; i++) {
/* Initalize socket with the currently configured port
* number */
rc = osmo_sock_init2_ofd(&wq->bfd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, mgcp->actual.local_addr,
mgcp->actual.local_port, mgcp->actual.remote_addr, mgcp->actual.remote_port,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
if (rc > 0)
return rc;
/* If there is a different port than the default port
* configured then we assume that the user has choosen
* that port conciously and we will not try to resolve
* this by silently choosing a different port. */
if (mgcp->actual.local_port != MGCP_CLIENT_LOCAL_PORT_DEFAULT && i == 0)
return -EINVAL;
/* Choose a new port number to try next */
LOGP(DLMGCP, LOGL_NOTICE,
"MGCPGW faild to bind to port %u, retrying with port %u -- check configuration!\n",
mgcp->actual.local_port, mgcp->actual.local_port + 1);
mgcp->actual.local_port++;
}
LOGP(DLMGCP, LOGL_FATAL, "MGCPGW faild to find a port to bind on %i times.\n", i);
return -EINVAL;
}
/*! Initalize client connection (opens socket only, no request is sent yet)
* \param[in,out] mgcp MGCP client descriptor.
* \returns 0 on success, -EINVAL on error. */
int mgcp_client_connect(struct mgcp_client *mgcp)
{
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;
rc = init_socket(mgcp);
if (rc < 0) {
LOGP(DLMGCP, LOGL_FATAL,
"Failed to initialize socket %s:%u -> %s:%u for MGCP GW: %s\n",
mgcp->actual.local_addr, mgcp->actual.local_port,
mgcp->actual.remote_addr, mgcp->actual.remote_port, strerror(errno));
goto error_close_fd;
}
inet_aton(mgcp->actual.remote_addr, &addr.sin_addr);
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;
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;
}
/*! Get the IP-Aaddress of the associated MGW as string.
* \param[in] mgcp MGCP client descriptor.
* \returns a pointer to the address string. */
const char *mgcp_client_remote_addr_str(struct mgcp_client *mgcp)
{
return mgcp->actual.remote_addr;
}
/*! Get the IP-Port of the associated MGW.
* \param[in] mgcp MGCP client descriptor.
* \returns port number. */
uint16_t mgcp_client_remote_port(struct mgcp_client *mgcp)
{
return mgcp->actual.remote_port;
}
/*! Get the IP-Aaddress of the associated MGW as its numeric representation.
* \param[in] mgcp MGCP client descriptor.
* \returns IP-Address as 32 bit integer (network byte order) */
uint32_t mgcp_client_remote_addr_n(struct mgcp_client *mgcp)
{
return mgcp->remote_addr;
}
struct mgcp_response_pending * mgcp_client_pending_add(
struct mgcp_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.
* Before the priv pointer becomes invalid, e.g. due to transaction timeout,
* mgcp_client_cancel() needs to be called for this transaction.
*/
int mgcp_client_tx(struct mgcp_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 = mgcp_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 */
mgcp_client_handle_response(mgcp, pending, NULL);
return -1;
}
/*! Cancel a pending transaction.
* \param[in] mgcp MGCP client descriptor.
* \param[in,out] trans_id Transaction id.
* \returns 0 on success, -ENOENT on error.
*
* Should a priv pointer passed to mgcp_client_tx() become invalid, this function must be called. In
* practical terms, if the caller of mgcp_client_tx() wishes to tear down a transaction without having
* received a response this function must be called. The trans_id can be obtained by calling
* mgcp_msg_trans_id() on the msgb produced by mgcp_msg_gen(). */
int mgcp_client_cancel(struct mgcp_client *mgcp, mgcp_trans_id_t trans_id)
{
struct mgcp_response_pending *pending = mgcp_client_response_pending_get(mgcp, trans_id);
if (!pending) {
/*! Note: it is not harmful to cancel a transaction twice. */
LOGP(DLMGCP, LOGL_INFO, "Cannot cancel, no such transaction: %u\n", trans_id);
return -ENOENT;
}
LOGP(DLMGCP, LOGL_INFO, "Canceled transaction %u\n", trans_id);
talloc_free(pending);
return 0;
/*! We don't really need to clean up the wqueue: In all sane cases, the msgb has already been sent
* out and is no longer in the wqueue. If it still is in the wqueue, then sending MGCP messages
* per se is broken and the program should notice so by a full wqueue. Even if this was called
* before we had a chance to send out the message and it is still going to be sent, we will just
* ignore the reply to it later. Removing a msgb from the wqueue here would just introduce more
* bug surface in terms of failing to update wqueue API's counters or some such.
*/
}
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 mgcp_client_next_trans_id(struct mgcp_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 mgcp_client *mgcp,
uint16_t rtp_endpoint, unsigned int call_id,
enum mgcp_connection_mode mode)
{
mgcp_trans_id_t trans_id = mgcp_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,
libosmo-mgcp-client: fix debian, make self-contained Add mgcp_common.h to libosmo-mgcp-client, to not need to share a header file with libosmo-legacy-mgcp (nor the upcoming libosmo-mgcp). Both libraries use the enum mgcp_connection_mode (and a for-loop macro and a string mangling function), so far declared in mgcp.h, and both mgcp-dev and mgcp-client-dev debian packages would require this header file to be installed. So far the mgcp-client-dev lacks this header file, which causes the osmo-msc debian package to fail building. Ways to solve: - If both -dev debian packages installed the same header file in the same place, they would conflict if ever installed at the same time. - mgcp-client-dev can depend on mgcp-dev, but it seems bad to keep such a large dependency for just one enum and two helpers. - Instead, this patch solves this by copying the few definitions to libosmo-mgcp-client. Once libosmo-mgcp-client has its own copy of those definitions, it is fully self-contained and depending builds (osmo-msc deb) will succeed. Copy the few actually common definitions to new header <osmocom/mgcp_client/mgcp_common.h>. The nature of this .h is that it may be shared with future libosmo-mgcp without causing linking problems. Remove libosmo-legacy-mgcp/mgcp_common.c file from the build of libosmo-mgcp-client, no longer needed. Add to new mgcp_common.h: - enum mgcp_connection_mode; - for_each_non_empty_line() macro. - mgcp_msg_terminate_nul() as static function. Its complexity is just above your average static inline, but being inline is a way to use it in both mgcp and mgcp_client without linking problems. Replace for_each_line() use in mgcp_client with for_each_non_empty_line() (for_each_non_empty_line() replaces for_each_line() and uses strtok_r() instead of a local reinvention). mgcp_connection_mode_strs are actually only used in libosmo-mgcp-client, so rename to mgcp_client_ prefix and move to mgcp_client.c. BTW, the future plan for upcoming libosmo-mgcp is to use the identical header file, and keep only one copy in the git source tree. The second copy may be generated during 'make', to avoid code dup while having two distinct headers. Related: I8e3359bedf973077c0a038aa04f5371a00c48fa0 (fix osmo-msc after this), I7a5d3b9a2eb90be7e34b95efa529429f2e6c3ed8 (mgcp_common.h) Change-Id: Ifb8f3fc2b399662a9dbba174e942352a1a21df3f
2017-09-21 22:52:54 +00:00
mgcp_client_cmode_name(mode));
}
struct msgb *mgcp_msg_mdcx(struct mgcp_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 = mgcp_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,
libosmo-mgcp-client: fix debian, make self-contained Add mgcp_common.h to libosmo-mgcp-client, to not need to share a header file with libosmo-legacy-mgcp (nor the upcoming libosmo-mgcp). Both libraries use the enum mgcp_connection_mode (and a for-loop macro and a string mangling function), so far declared in mgcp.h, and both mgcp-dev and mgcp-client-dev debian packages would require this header file to be installed. So far the mgcp-client-dev lacks this header file, which causes the osmo-msc debian package to fail building. Ways to solve: - If both -dev debian packages installed the same header file in the same place, they would conflict if ever installed at the same time. - mgcp-client-dev can depend on mgcp-dev, but it seems bad to keep such a large dependency for just one enum and two helpers. - Instead, this patch solves this by copying the few definitions to libosmo-mgcp-client. Once libosmo-mgcp-client has its own copy of those definitions, it is fully self-contained and depending builds (osmo-msc deb) will succeed. Copy the few actually common definitions to new header <osmocom/mgcp_client/mgcp_common.h>. The nature of this .h is that it may be shared with future libosmo-mgcp without causing linking problems. Remove libosmo-legacy-mgcp/mgcp_common.c file from the build of libosmo-mgcp-client, no longer needed. Add to new mgcp_common.h: - enum mgcp_connection_mode; - for_each_non_empty_line() macro. - mgcp_msg_terminate_nul() as static function. Its complexity is just above your average static inline, but being inline is a way to use it in both mgcp and mgcp_client without linking problems. Replace for_each_line() use in mgcp_client with for_each_non_empty_line() (for_each_non_empty_line() replaces for_each_line() and uses strtok_r() instead of a local reinvention). mgcp_connection_mode_strs are actually only used in libosmo-mgcp-client, so rename to mgcp_client_ prefix and move to mgcp_client.c. BTW, the future plan for upcoming libosmo-mgcp is to use the identical header file, and keep only one copy in the git source tree. The second copy may be generated during 'make', to avoid code dup while having two distinct headers. Related: I8e3359bedf973077c0a038aa04f5371a00c48fa0 (fix osmo-msc after this), I7a5d3b9a2eb90be7e34b95efa529429f2e6c3ed8 (mgcp_common.h) Change-Id: Ifb8f3fc2b399662a9dbba174e942352a1a21df3f
2017-09-21 22:52:54 +00:00
mgcp_client_cmode_name(mode),
rtp_conn_addr,
rtp_port);
}
struct msgb *mgcp_msg_dlcx(struct mgcp_client *mgcp, uint16_t rtp_endpoint,
unsigned int call_id)
{
mgcp_trans_id_t trans_id = mgcp_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);
}
#define MGCP_CRCX_MANDATORY (MGCP_MSG_PRESENCE_ENDPOINT | \
MGCP_MSG_PRESENCE_CALL_ID | \
MGCP_MSG_PRESENCE_CONN_MODE)
#define MGCP_MDCX_MANDATORY (MGCP_MSG_PRESENCE_ENDPOINT | \
MGCP_MSG_PRESENCE_CALL_ID | \
MGCP_MSG_PRESENCE_CONN_ID)
#define MGCP_DLCX_MANDATORY (MGCP_MSG_PRESENCE_ENDPOINT)
#define MGCP_AUEP_MANDATORY (MGCP_MSG_PRESENCE_ENDPOINT)
#define MGCP_RSIP_MANDATORY 0 /* none */
/* Helper function for mgcp_msg_gen(): Add LCO information to MGCP message */
static int add_lco(struct msgb *msg, struct mgcp_msg *mgcp_msg)
{
unsigned int i;
int rc = 0;
const char *codec;
unsigned int pt;
rc += msgb_printf(msg, "L:");
if (mgcp_msg->ptime)
rc += msgb_printf(msg, " p:%u,", mgcp_msg->ptime);
if (mgcp_msg->codecs_len) {
rc += msgb_printf(msg, " a:");
for (i = 0; i < mgcp_msg->codecs_len; i++) {
pt = mgcp_msg->codecs[i];
codec = get_value_string_or_null(codec_table, pt);
/* Note: Use codec descriptors from enum mgcp_codecs
* in mgcp_client only! */
OSMO_ASSERT(codec);
rc += msgb_printf(msg, "%s", extract_codec_name(codec));
if (i < mgcp_msg->codecs_len - 1)
rc += msgb_printf(msg, ";");
}
rc += msgb_printf(msg, ",");
}
rc += msgb_printf(msg, " nt:IN\r\n");
return rc;
}
/* Helper function for mgcp_msg_gen(): Add SDP information to MGCP message */
static int add_sdp(struct msgb *msg, struct mgcp_msg *mgcp_msg, struct mgcp_client *mgcp)
{
unsigned int i;
int rc = 0;
char local_ip[INET_ADDRSTRLEN];
const char *codec;
unsigned int pt;
/* Add separator to mark the beginning of the SDP block */
rc += msgb_printf(msg, "\r\n");
/* Add SDP protocol version */
rc += msgb_printf(msg, "v=0\r\n");
/* Determine local IP-Address */
if (osmo_sock_local_ip(local_ip, mgcp->actual.remote_addr) < 0) {
LOGP(DLMGCP, LOGL_ERROR,
"Could not determine local IP-Address!\n");
msgb_free(msg);
return -2;
}
/* Add owner/creator (SDP) */
rc += msgb_printf(msg, "o=- %x 23 IN IP4 %s\r\n",
mgcp_msg->call_id, local_ip);
/* Add session name (none) */
rc += msgb_printf(msg, "s=-\r\n");
/* Add RTP address and port */
if (mgcp_msg->audio_port == 0) {
LOGP(DLMGCP, LOGL_ERROR,
"Invalid port number, can not generate MGCP message\n");
msgb_free(msg);
return -2;
}
if (strlen(mgcp_msg->audio_ip) <= 0) {
LOGP(DLMGCP, LOGL_ERROR,
"Empty ip address, can not generate MGCP message\n");
msgb_free(msg);
return -2;
}
rc += msgb_printf(msg, "c=IN IP4 %s\r\n", mgcp_msg->audio_ip);
/* Add time description, active time (SDP) */
rc += msgb_printf(msg, "t=0 0\r\n");
rc += msgb_printf(msg, "m=audio %u RTP/AVP", mgcp_msg->audio_port);
for (i = 0; i < mgcp_msg->codecs_len; i++) {
pt = map_codec_to_pt(mgcp_msg->ptmap, mgcp_msg->ptmap_len, mgcp_msg->codecs[i]);
rc += msgb_printf(msg, " %u", pt);
}
rc += msgb_printf(msg, "\r\n");
for (i = 0; i < mgcp_msg->codecs_len; i++) {
pt = map_codec_to_pt(mgcp_msg->ptmap, mgcp_msg->ptmap_len, mgcp_msg->codecs[i]);
/* Note: Only dynamic payload type from the range 96-127
* require to be explained further via rtpmap. All others
* are implcitly definedby the number in m=audio */
if (pt >= 96 && pt <= 127) {
codec = get_value_string_or_null(codec_table, mgcp_msg->codecs[i]);
/* Note: Use codec descriptors from enum mgcp_codecs
* in mgcp_client only! */
OSMO_ASSERT(codec);
rc += msgb_printf(msg, "a=rtpmap:%u %s\r\n", pt, codec);
}
}
if (mgcp_msg->ptime)
rc += msgb_printf(msg, "a=ptime:%u\r\n", mgcp_msg->ptime);
return rc;
}
/*! Generate an MGCP message
* \param[in] mgcp MGCP client descriptor.
* \param[in] mgcp_msg Message description
* \returns message buffer on success, NULL on error. */
struct msgb *mgcp_msg_gen(struct mgcp_client *mgcp, struct mgcp_msg *mgcp_msg)
{
mgcp_trans_id_t trans_id = mgcp_client_next_trans_id(mgcp);
uint32_t mandatory_mask;
struct msgb *msg = msgb_alloc_headroom(4096, 128, "MGCP tx");
int rc = 0;
int rc_sdp;
bool use_sdp = false;
msg->l2h = msg->data;
msg->cb[MSGB_CB_MGCP_TRANS_ID] = trans_id;
/* Add command verb */
switch (mgcp_msg->verb) {
case MGCP_VERB_CRCX:
mandatory_mask = MGCP_CRCX_MANDATORY;
rc += msgb_printf(msg, "CRCX %u", trans_id);
break;
case MGCP_VERB_MDCX:
mandatory_mask = MGCP_MDCX_MANDATORY;
rc += msgb_printf(msg, "MDCX %u", trans_id);
break;
case MGCP_VERB_DLCX:
mandatory_mask = MGCP_DLCX_MANDATORY;
rc += msgb_printf(msg, "DLCX %u", trans_id);
break;
case MGCP_VERB_AUEP:
mandatory_mask = MGCP_AUEP_MANDATORY;
rc += msgb_printf(msg, "AUEP %u", trans_id);
break;
case MGCP_VERB_RSIP:
mandatory_mask = MGCP_RSIP_MANDATORY;
rc += msgb_printf(msg, "RSIP %u", trans_id);
break;
default:
LOGP(DLMGCP, LOGL_ERROR,
"Invalid command verb, can not generate MGCP message\n");
msgb_free(msg);
return NULL;
}
/* Check if mandatory fields are missing */
if (!((mgcp_msg->presence & mandatory_mask) == mandatory_mask)) {
LOGP(DLMGCP, LOGL_ERROR,
"One or more missing mandatory fields, can not generate MGCP message\n");
msgb_free(msg);
return NULL;
}
/* Add endpoint name */
if (mgcp_msg->presence & MGCP_MSG_PRESENCE_ENDPOINT) {
if (strlen(mgcp_msg->endpoint) <= 0) {
LOGP(DLMGCP, LOGL_ERROR,
"Empty endpoint name, can not generate MGCP message\n");
msgb_free(msg);
return NULL;
}
if (strstr(mgcp_msg->endpoint, "@") == NULL) {
LOGP(DLMGCP, LOGL_ERROR,
"Endpoint name (%s) lacks separator (@), can not generate MGCP message\n",
mgcp_msg->endpoint);
msgb_free(msg);
}
rc += msgb_printf(msg, " %s", mgcp_msg->endpoint);
}
/* Add protocol version */
rc += msgb_printf(msg, " MGCP 1.0\r\n");
/* Add call id */
if (mgcp_msg->presence & MGCP_MSG_PRESENCE_CALL_ID)
rc += msgb_printf(msg, "C: %x\r\n", mgcp_msg->call_id);
/* Add connection id */
if (mgcp_msg->presence & MGCP_MSG_PRESENCE_CONN_ID) {
if (strlen(mgcp_msg->conn_id) <= 0) {
LOGP(DLMGCP, LOGL_ERROR,
"Empty connection id, can not generate MGCP message\n");
msgb_free(msg);
return NULL;
}
rc += msgb_printf(msg, "I: %s\r\n", mgcp_msg->conn_id);
}
/* Using SDP makes sense when a valid IP/Port combination is specifiec,
* if we do not know this information yet, we fall back to LCO */
if (mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_IP
&& mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_PORT)
use_sdp = true;
/* Add local connection options (LCO) */
if (!use_sdp
&& (mgcp_msg->verb == MGCP_VERB_CRCX
|| mgcp_msg->verb == MGCP_VERB_MDCX))
rc += add_lco(msg, mgcp_msg);
/* Add mode */
if (mgcp_msg->presence & MGCP_MSG_PRESENCE_CONN_MODE)
rc +=
msgb_printf(msg, "M: %s\r\n",
mgcp_client_cmode_name(mgcp_msg->conn_mode));
/* Add session description protocol (SDP) */
if (use_sdp
&& (mgcp_msg->verb == MGCP_VERB_CRCX
|| mgcp_msg->verb == MGCP_VERB_MDCX)) {
rc_sdp = add_sdp(msg, mgcp_msg, mgcp);
if (rc_sdp == -2)
return NULL;
else
rc += rc_sdp;
}
if (rc != 0) {
LOGP(DLMGCP, LOGL_ERROR,
"message buffer to small, can not generate MGCP message\n");
msgb_free(msg);
msg = NULL;
}
return msg;
}
/*! Retrieve the MGCP transaction ID from a msgb generated by mgcp_msg_gen()
* \param[in] msg message buffer
* \returns Transaction id. */
mgcp_trans_id_t mgcp_msg_trans_id(struct msgb *msg)
{
return (mgcp_trans_id_t)msg->cb[MSGB_CB_MGCP_TRANS_ID];
}
/*! Get the configuration parameters a given MGCP client instance
* \param[in] mgcp MGCP client descriptor.
* \returns configuration */
struct mgcp_client_conf *mgcp_client_conf_actual(struct mgcp_client *mgcp)
{
return &mgcp->actual;
}
libosmo-mgcp-client: fix debian, make self-contained Add mgcp_common.h to libosmo-mgcp-client, to not need to share a header file with libosmo-legacy-mgcp (nor the upcoming libosmo-mgcp). Both libraries use the enum mgcp_connection_mode (and a for-loop macro and a string mangling function), so far declared in mgcp.h, and both mgcp-dev and mgcp-client-dev debian packages would require this header file to be installed. So far the mgcp-client-dev lacks this header file, which causes the osmo-msc debian package to fail building. Ways to solve: - If both -dev debian packages installed the same header file in the same place, they would conflict if ever installed at the same time. - mgcp-client-dev can depend on mgcp-dev, but it seems bad to keep such a large dependency for just one enum and two helpers. - Instead, this patch solves this by copying the few definitions to libosmo-mgcp-client. Once libosmo-mgcp-client has its own copy of those definitions, it is fully self-contained and depending builds (osmo-msc deb) will succeed. Copy the few actually common definitions to new header <osmocom/mgcp_client/mgcp_common.h>. The nature of this .h is that it may be shared with future libosmo-mgcp without causing linking problems. Remove libosmo-legacy-mgcp/mgcp_common.c file from the build of libosmo-mgcp-client, no longer needed. Add to new mgcp_common.h: - enum mgcp_connection_mode; - for_each_non_empty_line() macro. - mgcp_msg_terminate_nul() as static function. Its complexity is just above your average static inline, but being inline is a way to use it in both mgcp and mgcp_client without linking problems. Replace for_each_line() use in mgcp_client with for_each_non_empty_line() (for_each_non_empty_line() replaces for_each_line() and uses strtok_r() instead of a local reinvention). mgcp_connection_mode_strs are actually only used in libosmo-mgcp-client, so rename to mgcp_client_ prefix and move to mgcp_client.c. BTW, the future plan for upcoming libosmo-mgcp is to use the identical header file, and keep only one copy in the git source tree. The second copy may be generated during 'make', to avoid code dup while having two distinct headers. Related: I8e3359bedf973077c0a038aa04f5371a00c48fa0 (fix osmo-msc after this), I7a5d3b9a2eb90be7e34b95efa529429f2e6c3ed8 (mgcp_common.h) Change-Id: Ifb8f3fc2b399662a9dbba174e942352a1a21df3f
2017-09-21 22:52:54 +00:00
const struct value_string mgcp_client_connection_mode_strs[] = {
{ MGCP_CONN_NONE, "none" },
{ MGCP_CONN_RECV_SEND, "sendrecv" },
{ MGCP_CONN_SEND_ONLY, "sendonly" },
{ MGCP_CONN_RECV_ONLY, "recvonly" },
{ MGCP_CONN_LOOPBACK, "loopback" },
{ 0, NULL }
};