diff options
Diffstat (limited to 'src/libosmo-mgcp/mgcp_protocol.c')
-rw-r--r-- | src/libosmo-mgcp/mgcp_protocol.c | 301 |
1 files changed, 254 insertions, 47 deletions
diff --git a/src/libosmo-mgcp/mgcp_protocol.c b/src/libosmo-mgcp/mgcp_protocol.c index ea86672..ea80907 100644 --- a/src/libosmo-mgcp/mgcp_protocol.c +++ b/src/libosmo-mgcp/mgcp_protocol.c @@ -40,6 +40,7 @@ #include <osmocom/mgcp/mgcp_msg.h> #include <osmocom/mgcp/mgcp_endp.h> #include <osmocom/mgcp/mgcp_sdp.h> +#include <osmocom/mgcp/mgcp_codec.h> struct mgcp_request { char *name; @@ -88,8 +89,7 @@ static int setup_rtp_processing(struct mgcp_endpoint *endp, } } - return cfg->setup_rtp_processing_cb(endp, &conn_dst->end, - &conn_src->end); + return cfg->setup_rtp_processing_cb(endp, conn_dst, conn_src); } /* array of function pointers for handling various @@ -356,13 +356,15 @@ static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *p) /* Try to find a free port by attempting to bind on it. Also handle the * counter that points on the next free port. Since we have a pointer - * to the next free port, binding should work on the first attempt, - * nevertheless, try at least the next 200 ports before giving up */ + * to the next free port, binding should in work on the first attempt in + * general. In case of failure the next port is tryed until the whole port + * range is tryed once. */ static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn) { int i; struct mgcp_rtp_end *end; struct mgcp_port_range *range; + unsigned int tries; OSMO_ASSERT(conn); end = &conn->end; @@ -371,7 +373,8 @@ static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn) range = &endp->cfg->net_ports; /* attempt to find a port */ - for (i = 0; i < 200; ++i) { + tries = (range->range_end - range->range_start) / 2; + for (i = 0; i < tries; ++i) { int rc; if (range->last_port >= range->range_end) @@ -387,8 +390,123 @@ static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn) } LOGP(DLMGCP, LOGL_ERROR, - "Allocating a RTP/RTCP port failed 200 times 0x%x.\n", - ENDPOINT_NUMBER(endp)); + "Allocating a RTP/RTCP port failed %u times 0x%x.\n", + tries, ENDPOINT_NUMBER(endp)); + return -1; +} + +/*! Helper function for check_local_cx_options() to get a pointer of the next + * lco option identifier + * \param[in] lco string + * \returns pointer to the beginning of the LCO identifier, NULL on failure */ +char *get_lco_identifier(const char *options) +{ + char *ptr; + unsigned int count = 0; + + /* Jump to the end of the lco identifier */ + ptr = strstr(options, ":"); + if (!ptr) + return NULL; + + /* Walk backwards until the pointer points to the beginning of the + * lco identifier. We know that we stand at the beginning when we + * are either at the beginning of the memory or see a space or + * comma. (this is tolerant, it will accept a:10, b:11 as well as + * a:10,b:11) */ + while (1) { + /* Endless loop protection */ + if (count > 10000) + return NULL; + else if (ptr < options || *ptr == ' ' || *ptr == ',') { + ptr++; + break; + } + ptr--; + count++; + } + + /* Check if we got any result */ + if (*ptr == ':') + return NULL; + + return ptr; +} + +/*! Check the LCO option. This function checks for multiple appearence of LCO + * options, which is illegal + * \param[in] ctx talloc context + * \param[in] lco string + * \returns 0 on success, -1 on failure */ +int check_local_cx_options(void *ctx, const char *options) +{ + int i; + char *options_copy; + char *lco_identifier; + char *lco_identifier_end; + char *next_lco_identifier; + + char **lco_seen; + unsigned int lco_seen_n = 0; + + if (!options) + return -1; + + lco_seen = + (char **)talloc_zero_size(ctx, strlen(options) * sizeof(char *)); + options_copy = talloc_strdup(ctx, options); + lco_identifier = options_copy; + + do { + /* Move the lco_identifier pointer to the beginning of the + * current lco option identifier */ + lco_identifier = get_lco_identifier(lco_identifier); + if (!lco_identifier) + goto error; + + /* Look ahead to the next LCO option early, since we + * will parse destructively */ + next_lco_identifier = strstr(lco_identifier + 1, ","); + + /* Pinch off the end of the lco field identifier name + * and see if we still got something, also check if + * there is some value after the colon. */ + lco_identifier_end = strstr(lco_identifier, ":"); + if (!lco_identifier_end) + goto error; + if (*(lco_identifier_end + 1) == ' ' + || *(lco_identifier_end + 1) == ',' + || *(lco_identifier_end + 1) == '\0') + goto error; + *lco_identifier_end = '\0'; + if (strlen(lco_identifier) == 0) + goto error; + + /* Check if we have already seen the current field identifier + * before. If yes, we must bail, an LCO must only appear once + * in the LCO string */ + for (i = 0; i < lco_seen_n; i++) { + if (strcmp(lco_seen[i], lco_identifier) == 0) + goto error; + } + lco_seen[lco_seen_n] = lco_identifier; + lco_seen_n++; + + /* The first identifier must always be found at the beginnning + * of the LCO string */ + if (lco_seen[0] != options_copy) + goto error; + + /* Go to the next lco option */ + lco_identifier = next_lco_identifier; + } while (lco_identifier); + + talloc_free(lco_seen); + talloc_free(options_copy); + return 0; +error: + talloc_free(lco_seen); + talloc_free(options_copy); return -1; } @@ -402,20 +520,34 @@ static int set_local_cx_options(void *ctx, struct mgcp_lco *lco, char *p_opt, *a_opt; char codec[9]; + if (!options) + return 0; + if (strlen(options) == 0) + return 0; + + /* Make sure the encoding of the LCO is consistant before we proceed */ + if (check_local_cx_options(ctx, options) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "local CX options: Internal inconsistency in Local Connection Options!\n"); + return 524; + } + talloc_free(lco->string); - talloc_free(lco->codec); - lco->codec = NULL; - lco->pkt_period_min = lco->pkt_period_max = 0; - lco->string = talloc_strdup(ctx, options ? options : ""); + lco->string = talloc_strdup(ctx, options); p_opt = strstr(lco->string, "p:"); if (p_opt && sscanf(p_opt, "p:%d-%d", &lco->pkt_period_min, &lco->pkt_period_max) == 1) lco->pkt_period_max = lco->pkt_period_min; + /* FIXME: LCO also supports the negotiation of more then one codec. + * (e.g. a:PCMU;G726-32) But this implementation only supports a single + * codec only. */ a_opt = strstr(lco->string, "a:"); - if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1) + if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1) { + talloc_free(lco->codec); lco->codec = talloc_strdup(ctx, codec); + } LOGP(DLMGCP, LOGL_DEBUG, "local CX options: lco->pkt_period_max: %i, lco->codec: %s\n", @@ -456,15 +588,15 @@ uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp, /* Get the number of frames per channel and packet */ if (rtp->frames_per_packet) f = rtp->frames_per_packet; - else if (rtp->packet_duration_ms && rtp->codec.frame_duration_num) { - int den = 1000 * rtp->codec.frame_duration_num; - f = (rtp->packet_duration_ms * rtp->codec.frame_duration_den + + else if (rtp->packet_duration_ms && rtp->codec->frame_duration_num) { + int den = 1000 * rtp->codec->frame_duration_num; + f = (rtp->packet_duration_ms * rtp->codec->frame_duration_den + den / 2) / den; } - return rtp->codec.rate * f * rtp->codec.frame_duration_num / - rtp->codec.frame_duration_den; + return rtp->codec->rate * f * rtp->codec->frame_duration_num / + rtp->codec->frame_duration_den; } static int mgcp_osmux_setup(struct mgcp_endpoint *endp, const char *line) @@ -480,6 +612,68 @@ static int mgcp_osmux_setup(struct mgcp_endpoint *endp, const char *line) return mgcp_parse_osmux_cid(line); } +/* Process codec information contained in CRCX/MDCX */ +static int handle_codec_info(struct mgcp_conn_rtp *conn, + struct mgcp_parse_data *p, int have_sdp, bool crcx) +{ + struct mgcp_endpoint *endp = p->endp; + int rc; + char *cmd; + + if (crcx) + cmd = "CRCX"; + else + cmd = "MDCX"; + + /* Collect codec information */ + if (have_sdp) { + /* If we have SDP, we ignore the local connection options and + * use only the SDP information. */ + mgcp_codec_reset_all(conn); + rc = mgcp_parse_sdp_data(endp, conn, p); + if (rc != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "%s: endpoint:%x sdp not parseable\n", cmd, + ENDPOINT_NUMBER(endp)); + + /* See also RFC 3661: Protocol error */ + return 510; + } + } else if (endp->local_options.codec) { + /* When no SDP is available, we use the codec information from + * the local connection options (if present) */ + mgcp_codec_reset_all(conn); + rc = mgcp_codec_add(conn, PTYPE_UNDEFINED, endp->local_options.codec); + if (rc != 0) + goto error; + } + + /* Make sure we always set a sane default codec */ + if (conn->end.codecs_assigned == 0) { + /* When SDP and/or LCO did not supply any codec information, + * than it makes sense to pick a sane default: (payload-type 0, + * PCMU), see also: OS#2658 */ + mgcp_codec_reset_all(conn); + rc = mgcp_codec_add(conn, 0, NULL); + if (rc != 0) + goto error; + } + + /* Make codec decision */ + if (mgcp_codec_decide(conn) != 0) + goto error; + + return 0; + +error: + LOGP(DLMGCP, LOGL_ERROR, + "%s: endpoint:0x%x codec negotiation failure\n", cmd, + ENDPOINT_NUMBER(endp)); + + /* See also RFC 3661: Codec negotiation failure */ + return 534; +} + /* CRCX command handler, processes the received command */ static struct msgb *handle_create_con(struct mgcp_parse_data *p) { @@ -597,17 +791,6 @@ mgcp_header_done: * connection ids) */ endp->callid = talloc_strdup(tcfg->endpoints, callid); - /* Extract audio codec information */ - rc = set_local_cx_options(endp->tcfg->endpoints, &endp->local_options, - local_options); - if (rc != 0) { - LOGP(DLMGCP, LOGL_ERROR, - "CRCX: endpoint:%x inavlid local connection options!\n", - ENDPOINT_NUMBER(endp)); - error_code = rc; - goto error2; - } - snprintf(conn_name, sizeof(conn_name), "%s", callid); _conn = mgcp_conn_alloc(NULL, endp, MGCP_CONN_TYPE_RTP, conn_name); if (!_conn) { @@ -638,12 +821,27 @@ mgcp_header_done: goto error2; } - /* set up RTP media parameters */ - if (have_sdp) - mgcp_parse_sdp_data(endp, conn, p); - else if (endp->local_options.codec) - mgcp_set_audio_info(p->cfg, &conn->end.codec, - PTYPE_UNDEFINED, endp->local_options.codec); + /* Set local connection options, if present */ + if (local_options) { + rc = set_local_cx_options(endp->tcfg->endpoints, + &endp->local_options, local_options); + if (rc != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "CRCX: endpoint:%x inavlid local connection options!\n", + ENDPOINT_NUMBER(endp)); + error_code = rc; + goto error2; + } + } + + /* Handle codec information and decide for a suitable codec */ + rc = handle_codec_info(conn, p, have_sdp, true); + mgcp_codec_summary(conn); + if (rc) { + error_code = rc; + goto error2; + } + conn->end.fmtp_extra = talloc_strdup(tcfg->endpoints, tcfg->audio_fmtp_extra); @@ -718,11 +916,15 @@ mgcp_header_done: error2: mgcp_endp_release(endp); LOGP(DLMGCP, LOGL_NOTICE, - "CRCX: endpoint:0x%x unable to create connection resource error\n", + "CRCX: endpoint:0x%x unable to create connection\n", ENDPOINT_NUMBER(endp)); return create_err_response(endp, error_code, "CRCX", p->trans); } + + + + /* MDCX command handler, processes the received command */ static struct msgb *handle_modify_con(struct mgcp_parse_data *p) { @@ -814,23 +1016,27 @@ mgcp_header_done: } else conn->conn->mode = conn->conn->mode_orig; - if (have_sdp) - mgcp_parse_sdp_data(endp, conn, p); + /* Set local connection options, if present */ + if (local_options) { + rc = set_local_cx_options(endp->tcfg->endpoints, + &endp->local_options, local_options); + if (rc != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "MDCX: endpoint:%x inavlid local connection options!\n", + ENDPOINT_NUMBER(endp)); + error_code = rc; + goto error3; + } + } - rc = set_local_cx_options(endp->tcfg->endpoints, &endp->local_options, - local_options); - if (rc != 0) { - LOGP(DLMGCP, LOGL_ERROR, - "MDCX: endpoint:%x inavlid local connection options!\n", - ENDPOINT_NUMBER(endp)); + /* Handle codec information and decide for a suitable codec */ + rc = handle_codec_info(conn, p, have_sdp, false); + mgcp_codec_summary(conn); + if (rc) { error_code = rc; goto error3; } - if (!have_sdp && endp->local_options.codec) - mgcp_set_audio_info(p->cfg, &conn->end.codec, - PTYPE_UNDEFINED, endp->local_options.codec); - /* check connection mode setting */ if (conn->conn->mode != MGCP_CONN_LOOPBACK && conn->conn->mode != MGCP_CONN_RECV_ONLY @@ -842,6 +1048,7 @@ mgcp_header_done: goto error3; } + if (setup_rtp_processing(endp, conn) != 0) goto error3; |