diff options
38 files changed, 2081 insertions, 499 deletions
@@ -38,6 +38,7 @@ missing stamp-h1 libtool ltmain.sh +m4/*.m4 # git-version-gen magic .tarball-version diff --git a/configure.ac b/configure.ac index 0ded288..29390c6 100644 --- a/configure.ac +++ b/configure.ac @@ -39,10 +39,10 @@ AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DL="$LIBS";LIBS=""]) AC_SUBST(LIBRARY_DL) -PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.11.0) -PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.11.0) -PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.11.0) -PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.2.0) +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.12.0) +PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.12.0) +PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.12.0) +PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.3.0) AC_ARG_ENABLE(sanitize, [AS_HELP_STRING( diff --git a/git-version-gen b/git-version-gen index 8e59c5a..42cf3d2 100755 --- a/git-version-gen +++ b/git-version-gen @@ -92,8 +92,8 @@ fi if test -n "$v" then : # use $v -elif test -d ./.git \ - && v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ +elif + v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ || git describe --abbrev=4 HEAD 2>/dev/null` \ && case $v in [0-9]*) ;; diff --git a/include/osmocom/legacy_mgcp/mgcp.h b/include/osmocom/legacy_mgcp/mgcp.h index 147a0d5..7490e37 100644 --- a/include/osmocom/legacy_mgcp/mgcp.h +++ b/include/osmocom/legacy_mgcp/mgcp.h @@ -243,6 +243,12 @@ struct mgcp_config { * message. */ uint16_t osmux_dummy; + + /* Use a jitterbuffer on the bts-side receiver */ + bool bts_use_jibuf; + /* Minimum and maximum buffer size for the jitter buffer, in ms */ + uint32_t bts_jitter_delay_min; + uint32_t bts_jitter_delay_max; }; /* config management */ diff --git a/include/osmocom/legacy_mgcp/mgcp_internal.h b/include/osmocom/legacy_mgcp/mgcp_internal.h index dcc75f1..956bee0 100644 --- a/include/osmocom/legacy_mgcp/mgcp_internal.h +++ b/include/osmocom/legacy_mgcp/mgcp_internal.h @@ -25,6 +25,7 @@ #include <string.h> #include <osmocom/core/select.h> +#include <osmocom/netif/jibuf.h> #define CI_UNUSED 0 @@ -198,6 +199,14 @@ struct mgcp_endpoint { uint32_t octets; } stats; } osmux; + + /* Jitter buffer */ + struct osmo_jibuf* bts_jb; + /* Use a jitterbuffer on the bts-side receiver */ + bool bts_use_jibuf; + /* Minimum and maximum buffer size for the jitter buffer, in ms */ + uint32_t bts_jitter_delay_min; + uint32_t bts_jitter_delay_max; }; #define for_each_line(line, save) \ @@ -335,3 +344,8 @@ static inline const char *mgcp_bts_src_addr(struct mgcp_endpoint *endp) } int mgcp_msg_terminate_nul(struct msgb *msg); + +/** + * Internal jitter buffer related + */ +void mgcp_dejitter_udp_send(struct msgb *msg, void *data); diff --git a/include/osmocom/mgcp/Makefile.am b/include/osmocom/mgcp/Makefile.am index 7e297e4..65ca670 100644 --- a/include/osmocom/mgcp/Makefile.am +++ b/include/osmocom/mgcp/Makefile.am @@ -5,5 +5,6 @@ noinst_HEADERS = \ mgcp_stat.h \ mgcp_endp.h \ mgcp_sdp.h \ + mgcp_codec.h \ debug.h \ $(NULL) diff --git a/include/osmocom/mgcp/mgcp.h b/include/osmocom/mgcp/mgcp.h index bdc8f87..bdc86fc 100644 --- a/include/osmocom/mgcp/mgcp.h +++ b/include/osmocom/mgcp/mgcp.h @@ -74,11 +74,13 @@ typedef int (*mgcp_rqnt)(struct mgcp_endpoint *endp, char tone); typedef int (*mgcp_processing)(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end, char *data, int *len, int buf_size); -typedef int (*mgcp_processing_setup)(struct mgcp_endpoint *endp, - struct mgcp_rtp_end *dst_end, - struct mgcp_rtp_end *src_end); struct mgcp_conn_rtp; + +typedef int (*mgcp_processing_setup)(struct mgcp_endpoint *endp, + struct mgcp_conn_rtp *conn_dst, + struct mgcp_conn_rtp *conn_src); + typedef void (*mgcp_get_format)(struct mgcp_endpoint *endp, int *payload_type, const char**subtype_name, diff --git a/include/osmocom/mgcp/mgcp_codec.h b/include/osmocom/mgcp/mgcp_codec.h new file mode 100644 index 0000000..f8d5e70 --- /dev/null +++ b/include/osmocom/mgcp/mgcp_codec.h @@ -0,0 +1,6 @@ +#pragma once + +void mgcp_codec_summary(struct mgcp_conn_rtp *conn); +void mgcp_codec_reset_all(struct mgcp_conn_rtp *conn); +int mgcp_codec_add(struct mgcp_conn_rtp *conn, int payload_type, const char *audio_name); +int mgcp_codec_decide(struct mgcp_conn_rtp *conn); diff --git a/include/osmocom/mgcp/mgcp_common.h b/include/osmocom/mgcp/mgcp_common.h index d23339f..eb6564f 100644 --- a/include/osmocom/mgcp/mgcp_common.h +++ b/include/osmocom/mgcp/mgcp_common.h @@ -82,4 +82,8 @@ static inline int mgcp_msg_terminate_nul(struct msgb *msg) /* A prefix to denote the virtual trunk (RTP on both ends) */ #define MGCP_ENDPOINT_PREFIX_VIRTUAL_TRUNK "rtpbridge/" +/* Maximal number of payload types / codecs that can be negotiated via SDP at + * at once. */ +#define MGCP_MAX_CODECS 10 + #endif diff --git a/include/osmocom/mgcp/mgcp_conn.h b/include/osmocom/mgcp/mgcp_conn.h index e2a423f..3da7334 100644 --- a/include/osmocom/mgcp/mgcp_conn.h +++ b/include/osmocom/mgcp/mgcp_conn.h @@ -27,6 +27,17 @@ #include <osmocom/core/linuxlist.h> #include <inttypes.h> +/* RTP connection related counters */ +enum { + IN_STREAM_ERR_TSTMP_CTR, + OUT_STREAM_ERR_TSTMP_CTR, + RTP_PACKETS_RX_CTR, + RTP_OCTETS_RX_CTR, + RTP_PACKETS_TX_CTR, + RTP_OCTETS_TX_CTR, + RTP_DROPPED_PACKETS_CTR +}; + struct mgcp_conn *mgcp_conn_alloc(void *ctx, struct mgcp_endpoint *endp, enum mgcp_conn_type type, char *name); struct mgcp_conn *mgcp_conn_get(struct mgcp_endpoint *endp, const char *id); diff --git a/include/osmocom/mgcp/mgcp_endp.h b/include/osmocom/mgcp/mgcp_endp.h index a486dcd..3876794 100644 --- a/include/osmocom/mgcp/mgcp_endp.h +++ b/include/osmocom/mgcp/mgcp_endp.h @@ -42,13 +42,13 @@ typedef void (*mgcp_cleanup_cp) (struct mgcp_endpoint *endp, /*! MGCP endpoint properties */ struct mgcp_endpoint_type { - /*!< maximum number of connections */ + /*! maximum number of connections */ int max_conns; - /*!< callback that defines how to dispatch incoming RTP data */ + /*! callback that defines how to dispatch incoming RTP data */ mgcp_dispatch_rtp_cb dispatch_rtp_cb; - /*!< callback that implements endpoint specific cleanup actions */ + /*! callback that implements endpoint specific cleanup actions */ mgcp_cleanup_cp cleanup_cb; }; @@ -63,31 +63,31 @@ extern const struct mgcp_endpoint_typeset ep_typeset; /*! MGCP endpoint model */ struct mgcp_endpoint { - /*!< Call identifier string (as supplied by the call agant) */ + /*! Call identifier string (as supplied by the call agant) */ char *callid; - /*!< Local connection options (see mgcp_intermal.h) */ + /*! Local connection options (see mgcp_internal.h) */ struct mgcp_lco local_options; - /*!< List with connections active on this endpoint */ + /*! List with connections active on this endpoint */ struct llist_head conns; - /*!< Backpointer to the MGW configuration */ + /*! Backpointer to the MGW configuration */ struct mgcp_config *cfg; - /*!< Backpointer to the Trunk specific configuration */ + /*! Backpointer to the Trunk specific configuration */ struct mgcp_trunk_config *tcfg; - /*!< Endpoint properties (see above) */ + /*! Endpoint properties (see above) */ const struct mgcp_endpoint_type *type; - /*!< Last MGCP transmission (in case re-transmission is required) */ + /*! Last MGCP transmission (in case re-transmission is required) */ char *last_trans; - /*!< Last MGCP response (in case re-transmission is required) */ + /*! Last MGCP response (in case re-transmission is required) */ char *last_response; - /*!< Memorize if this endpoint was choosen by the MGW (wildcarded, true) + /*! Memorize if this endpoint was choosen by the MGW (wildcarded, true) * or if the user has choosen the particular endpoint explicitly. */ bool wildcarded_req; }; diff --git a/include/osmocom/mgcp/mgcp_internal.h b/include/osmocom/mgcp/mgcp_internal.h index 0da2c56..06883dd 100644 --- a/include/osmocom/mgcp/mgcp_internal.h +++ b/include/osmocom/mgcp/mgcp_internal.h @@ -28,6 +28,7 @@ #include <osmocom/mgcp/mgcp.h> #include <osmocom/core/linuxlist.h> #include <osmocom/core/counter.h> +#include <osmocom/core/rate_ctr.h> #define CI_UNUSED 0 @@ -45,7 +46,7 @@ struct mgcp_rtp_stream_state { uint32_t ssrc; uint16_t last_seq; uint32_t last_timestamp; - uint32_t err_ts_counter; + struct rate_ctr *err_ts_ctr; int32_t last_tsdelta; uint32_t last_arrival_time; }; @@ -98,28 +99,25 @@ struct mgcp_rtp_codec { /* 'mgcp_rtp_end': basically a wrapper around the RTP+RTCP ports */ struct mgcp_rtp_end { - /* statistics */ - struct { - unsigned int packets_rx; - unsigned int octets_rx; - unsigned int packets_tx; - unsigned int octets_tx; - unsigned int dropped_packets; - } stats; - /* local IP address of the RTP socket */ struct in_addr addr; /* in network byte order */ int rtp_port, rtcp_port; - /* audio codec information */ - struct mgcp_rtp_codec codec; - struct mgcp_rtp_codec alt_codec; /* TODO/XXX: make it generic */ + /* currently selected audio codec */ + struct mgcp_rtp_codec *codec; + + /* array with assigned audio codecs to choose from (SDP) */ + struct mgcp_rtp_codec codecs[MGCP_MAX_CODECS]; + + /* number of assigned audio codecs (SDP) */ + unsigned int codecs_assigned; /* per endpoint data */ int frames_per_packet; uint32_t packet_duration_ms; + int maximum_packet_time; /* -1: not set */ char *fmtp_extra; /* are we transmitting packets (1) or dropping (0) outbound packets */ int output_enabled; @@ -130,8 +128,6 @@ struct mgcp_rtp_end { int force_constant_ssrc; /* -1: always, 0: don't, 1: once */ /* should we perform align_rtp_timestamp_offset() (1) or not (0) */ int force_aligned_timing; - /* FIXME: not used anymore, used to be [external] transcoding related */ - void *rtp_process_data; /* Each end has a separate socket for RTP and RTCP */ struct osmo_fd rtp; @@ -202,6 +198,8 @@ struct mgcp_conn_rtp { uint32_t octets; } stats; } osmux; + + struct rate_ctr_group *rate_ctr_group; }; /*! Connection type, specifies which member of the union "u" in mgcp_conn @@ -212,33 +210,33 @@ enum mgcp_conn_type { /*! MGCP connection (untyped) */ struct mgcp_conn { - /*!< list head */ + /*! list head */ struct llist_head entry; - /*!< Backpointer to the endpoint where the conn belongs to */ + /*! Backpointer to the endpoint where the conn belongs to */ struct mgcp_endpoint *endp; - /*!< type of the connection (union) */ + /*! type of the connection (union) */ enum mgcp_conn_type type; - /*!< mode of the connection */ + /*! mode of the connection */ enum mgcp_connection_mode mode; - /*!< copy of the mode to restore the original setting (VTY) */ + /*! copy of the mode to restore the original setting (VTY) */ enum mgcp_connection_mode mode_orig; - /*!< connection id to identify the connection */ + /*! connection id to identify the connection */ char id[MGCP_CONN_ID_LENGTH]; - /*!< human readable name (vty, logging) */ + /*! human readable name (vty, logging) */ char name[256]; - /*!< union with connection description */ + /*! union with connection description */ union { struct mgcp_conn_rtp rtp; } u; - /*!< pointer to optional private data */ + /*! pointer to optional private data */ void *priv; }; @@ -280,6 +278,8 @@ static inline int endp_back_channel(int endpoint) struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int index); struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index); +char *get_lco_identifier(const char *options); +int check_local_cx_options(void *ctx, const char *options); void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change, struct mgcp_rtp_end *rtp); uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp, @@ -290,8 +290,8 @@ int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end char *data, int *len, int buf_size); int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp, - struct mgcp_rtp_end *dst_end, - struct mgcp_rtp_end *src_end); + struct mgcp_conn_rtp *conn_dst, + struct mgcp_conn_rtp *conn_src); void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp, int *payload_type, diff --git a/include/osmocom/mgcp/mgcp_sdp.h b/include/osmocom/mgcp/mgcp_sdp.h index da23cba..950092e 100644 --- a/include/osmocom/mgcp/mgcp_sdp.h +++ b/include/osmocom/mgcp/mgcp_sdp.h @@ -21,15 +21,11 @@ */ #pragma once -#include <osmocom/mgcp/mgcp_sdp.h> int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn, struct mgcp_parse_data *p); -int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec, - int payload_type, const char *audio_name); - int mgcp_write_response_sdp(const struct mgcp_endpoint *endp, const struct mgcp_conn_rtp *conn, struct msgb *sdp, const char *addr); diff --git a/include/osmocom/mgcp/mgcp_stat.h b/include/osmocom/mgcp/mgcp_stat.h index b6c73fa..0bde8cf 100644 --- a/include/osmocom/mgcp/mgcp_stat.h +++ b/include/osmocom/mgcp/mgcp_stat.h @@ -30,8 +30,7 @@ void mgcp_format_stats(char *str, size_t str_len, struct mgcp_conn *conn); /* Exposed for test purposes only, do not use actively */ -void calc_loss(struct mgcp_rtp_state *s, struct mgcp_rtp_end *, - uint32_t *expected, int *loss); +void calc_loss(struct mgcp_conn_rtp *conn, uint32_t *expected, int *loss); /* Exposed for test purposes only, do not use actively */ uint32_t calc_jitter(struct mgcp_rtp_state *); diff --git a/include/osmocom/mgcp_client/mgcp_client.h b/include/osmocom/mgcp_client/mgcp_client.h index 73f3bba..19c684d 100644 --- a/include/osmocom/mgcp_client/mgcp_client.h +++ b/include/osmocom/mgcp_client/mgcp_client.h @@ -26,6 +26,33 @@ struct mgcp_client_conf { typedef unsigned int mgcp_trans_id_t; +/*! Enumeration of the codec types that mgcp_client is able to handle. */ +enum mgcp_codecs { + CODEC_PCMU_8000_1 = 0, + CODEC_GSM_8000_1 = 3, + CODEC_PCMA_8000_1 = 8, + CODEC_G729_8000_1 = 18, + CODEC_GSMEFR_8000_1 = 110, + CODEC_GSMHR_8000_1 = 111, + CODEC_AMR_8000_1 = 112, + CODEC_AMRWB_16000_1 = 113, +}; +/* Note: when new codec types are added, the corresponding value strings + * in mgcp_client.c (codec_table) must be updated as well. Enumerations + * in enum mgcp_codecs must correspond to a valid payload type. However, + * this is an internal assumption that is made to avoid lookup tables. + * The API-User should not rely on this coincidence! */ + +/*! Structure to build a payload type map to allow the defiition custom payload + * types. */ +struct ptmap { + /*! codec for which a payload type number should be defined */ + enum mgcp_codecs codec; + + /*! payload type number (96-127) */ + unsigned int pt; +}; + struct mgcp_response_head { int response_code; mgcp_trans_id_t trans_id; @@ -39,6 +66,11 @@ struct mgcp_response { struct mgcp_response_head head; uint16_t audio_port; char audio_ip[INET_ADDRSTRLEN]; + unsigned int ptime; + enum mgcp_codecs codecs[MGCP_MAX_CODECS]; + unsigned int codecs_len; + struct ptmap ptmap[MGCP_MAX_CODECS]; + unsigned int ptmap_len; }; enum mgcp_verb { @@ -66,6 +98,11 @@ struct mgcp_msg { uint16_t audio_port; char *audio_ip; enum mgcp_connection_mode conn_mode; + unsigned int ptime; + enum mgcp_codecs codecs[MGCP_MAX_CODECS]; + unsigned int codecs_len; + struct ptmap ptmap[MGCP_MAX_CODECS]; + unsigned int ptmap_len; }; void mgcp_client_conf_init(struct mgcp_client_conf *conf); @@ -117,3 +154,9 @@ static inline const char *mgcp_client_cmode_name(enum mgcp_connection_mode mode) { return get_value_string(mgcp_client_connection_mode_strs, mode); } + +enum mgcp_codecs map_str_to_codec(const char *str); +unsigned int map_codec_to_pt(struct ptmap *ptmap, unsigned int ptmap_len, + enum mgcp_codecs codec); +enum mgcp_codecs map_pt_to_codec(struct ptmap *ptmap, unsigned int ptmap_len, + unsigned int pt); diff --git a/include/osmocom/mgcp_client/mgcp_client_fsm.h b/include/osmocom/mgcp_client/mgcp_client_fsm.h index 7d06178..353baff 100644 --- a/include/osmocom/mgcp_client/mgcp_client_fsm.h +++ b/include/osmocom/mgcp_client/mgcp_client_fsm.h @@ -14,20 +14,31 @@ * identifier is supplied it is checked against the internal state to make * sure it is correct. */ struct mgcp_conn_peer { - /*!< RTP connection IP-Address (optional, string e.g. "127.0.0.1") */ + /*! RTP connection IP-Address (optional, string e.g. "127.0.0.1") */ char addr[INET_ADDRSTRLEN]; - /*!< RTP connection IP-Port (optional) */ + /*! RTP connection IP-Port (optional) */ uint16_t port; - /*!< RTP endpoint */ + /*! RTP endpoint */ char endpoint[MGCP_ENDPOINT_MAXLEN]; - /*!< CALL ID (unique per connection) */ + /*! CALL ID (unique per connection) */ unsigned int call_id; + + /*! RTP packetization interval (optional) */ + unsigned int ptime; + + /*! RTP codec list (optional) */ + enum mgcp_codecs codecs[MGCP_MAX_CODECS]; + + /*! Number of codecs in RTP codec list (optional) */ + unsigned int codecs_len; }; struct osmo_fsm_inst *mgcp_conn_create(struct mgcp_client *mgcp, struct osmo_fsm_inst *parent_fi, uint32_t parent_term_evt, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer); int mgcp_conn_modify(struct osmo_fsm_inst *fi, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer); void mgcp_conn_delete(struct osmo_fsm_inst *fi); + +const char *mgcp_conn_get_ci(struct osmo_fsm_inst *fi); diff --git a/src/libosmo-legacy-mgcp/Makefile.am b/src/libosmo-legacy-mgcp/Makefile.am index 4ee4e6a..2d6b6dd 100644 --- a/src/libosmo-legacy-mgcp/Makefile.am +++ b/src/libosmo-legacy-mgcp/Makefile.am @@ -24,7 +24,7 @@ AM_LDFLAGS = \ # This is not at all related to the release version, but a range of supported # API versions. Read TODO_RELEASE in the source tree's root! -LEGACY_MGCP_LIBVERSION=0:1:0 +LEGACY_MGCP_LIBVERSION=1:0:1 lib_LTLIBRARIES = \ libosmo-legacy-mgcp.la \ diff --git a/src/libosmo-legacy-mgcp/mgcp_network.c b/src/libosmo-legacy-mgcp/mgcp_network.c index 8ccfb42..7b161d2 100644 --- a/src/libosmo-legacy-mgcp/mgcp_network.c +++ b/src/libosmo-legacy-mgcp/mgcp_network.c @@ -584,6 +584,36 @@ static int mgcp_send_transcoder(struct mgcp_rtp_end *end, return rc; } +void mgcp_dejitter_udp_send(struct msgb *msg, void *data) +{ + struct mgcp_rtp_end *rtp_end = (struct mgcp_rtp_end *) data; + + int rc = mgcp_udp_send(rtp_end->rtp.fd, &rtp_end->addr, + rtp_end->rtp_port, (char*) msg->data, msg->len); + if (rc != msg->len) + LOGP(DLMGCP, LOGL_ERROR, + "Failed to send data after jitter buffer: %d\n", rc); + msgb_free(msg); +} + +static int enqueue_dejitter(struct osmo_jibuf *jb, struct mgcp_rtp_end *rtp_end, char *buf, int len) +{ + struct msgb *msg; + msg = msgb_alloc(len, "mgcp-jibuf"); + if (!msg) + return -1; + + memcpy(msg->data, buf, len); + msgb_put(msg, len); + + if (osmo_jibuf_enqueue(jb, msg) < 0) { + rtp_end->dropped_packets += 1; + msgb_free(msg); + } + + return len; +} + int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, struct sockaddr_in *addr, char *buf, int rc) { @@ -591,6 +621,7 @@ int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, struct mgcp_rtp_end *rtp_end; struct mgcp_rtp_state *rtp_state; int tap_idx; + struct osmo_jibuf *jb; LOGP(DLMGCP, LOGL_DEBUG, "endpoint %x dest %s tcfg->audio_loop %d endp->conn_mode %d (== loopback: %d)\n", @@ -612,10 +643,12 @@ int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, rtp_end = &endp->net_end; rtp_state = &endp->bts_state; tap_idx = MGCP_TAP_NET_OUT; + jb = endp->bts_jb; } else { rtp_end = &endp->bts_end; rtp_state = &endp->net_state; tap_idx = MGCP_TAP_BTS_OUT; + jb = NULL; } LOGP(DLMGCP, LOGL_DEBUG, "endpoint %x dest %s net_end %s %d %d bts_end %s %d %d rtp_end %s %d %d\n", @@ -680,9 +713,12 @@ int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, rtp_state->patched_first_rtp_payload = true; } - rc = mgcp_udp_send(rtp_end->rtp.fd, - &rtp_end->addr, - rtp_end->rtp_port, buf, len); + if (jb) + rc = enqueue_dejitter(jb, rtp_end, buf, len); + else + rc = mgcp_udp_send(rtp_end->rtp.fd, + &rtp_end->addr, + rtp_end->rtp_port, buf, len); if (rc <= 0) return rc; diff --git a/src/libosmo-legacy-mgcp/mgcp_osmux.c b/src/libosmo-legacy-mgcp/mgcp_osmux.c index 743d3f9..b53e2b5 100644 --- a/src/libosmo-legacy-mgcp/mgcp_osmux.c +++ b/src/libosmo-legacy-mgcp/mgcp_osmux.c @@ -267,7 +267,6 @@ int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what) { struct msgb *msg; struct osmux_hdr *osmuxh; - struct llist_head list; struct sockaddr_in addr; struct mgcp_config *cfg = ofd->data; uint32_t rem; @@ -297,8 +296,7 @@ int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what) endp->osmux.stats.chunks++; rem = msg->len; - osmux_xfrm_output(osmuxh, &endp->osmux.out, &list); - osmux_tx_sched(&list, scheduled_tx_bts_cb, endp); + osmux_xfrm_output_sched(&endp->osmux.out, osmuxh); } out: msgb_free(msg); @@ -359,7 +357,6 @@ int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what) { struct msgb *msg; struct osmux_hdr *osmuxh; - struct llist_head list; struct sockaddr_in addr; struct mgcp_config *cfg = ofd->data; uint32_t rem; @@ -389,8 +386,7 @@ int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what) endp->osmux.stats.chunks++; rem = msg->len; - osmux_xfrm_output(osmuxh, &endp->osmux.out, &list); - osmux_tx_sched(&list, scheduled_tx_net_cb, endp); + osmux_xfrm_output_sched(&endp->osmux.out, osmuxh); } out: msgb_free(msg); @@ -470,9 +466,13 @@ int osmux_enable_endpoint(struct mgcp_endpoint *endp, struct in_addr *addr, uint switch (endp->cfg->role) { case MGCP_BSC_NAT: endp->type = MGCP_OSMUX_BSC_NAT; + osmux_xfrm_output_set_tx_cb(&endp->osmux.out, + scheduled_tx_net_cb, endp); break; case MGCP_BSC: endp->type = MGCP_OSMUX_BSC; + osmux_xfrm_output_set_tx_cb(&endp->osmux.out, + scheduled_tx_bts_cb, endp); break; } endp->osmux.state = OSMUX_STATE_ENABLED; @@ -484,6 +484,11 @@ void osmux_disable_endpoint(struct mgcp_endpoint *endp) { LOGP(DLMGCP, LOGL_INFO, "Releasing endpoint %u using Osmux CID %u\n", ENDPOINT_NUMBER(endp), endp->osmux.cid); + + /* We are closing, we don't need pending RTP packets to be transmitted */ + osmux_xfrm_output_set_tx_cb(&endp->osmux.out, NULL, NULL); + osmux_xfrm_output_flush(&endp->osmux.out); + osmux_xfrm_input_close_circuit(endp->osmux.in, endp->osmux.cid); endp->osmux.state = OSMUX_STATE_DISABLED; endp->osmux.cid = -1; diff --git a/src/libosmo-legacy-mgcp/mgcp_protocol.c b/src/libosmo-legacy-mgcp/mgcp_protocol.c index 4e82233..854b4ba 100644 --- a/src/libosmo-legacy-mgcp/mgcp_protocol.c +++ b/src/libosmo-legacy-mgcp/mgcp_protocol.c @@ -863,6 +863,11 @@ mgcp_header_done: goto error2; } + /* Apply Jiter buffer settings for this endpoint, they can be overriden by CRCX policy later */ + endp->bts_use_jibuf = endp->cfg->bts_use_jibuf; + endp->bts_jitter_delay_min = endp->cfg->bts_jitter_delay_min; + endp->bts_jitter_delay_max = endp->cfg->bts_jitter_delay_max; + endp->allocated = 1; /* set up RTP media parameters */ @@ -898,6 +903,13 @@ mgcp_header_done: case MGCP_POLICY_DEFER: /* stop processing */ create_transcoder(endp); + /* Set up jitter buffer if required after policy has updated jibuf endp values */ + if (endp->bts_use_jibuf) { + endp->bts_jb = osmo_jibuf_alloc(tcfg->endpoints); + osmo_jibuf_set_min_delay(endp->bts_jb, endp->bts_jitter_delay_min); + osmo_jibuf_set_max_delay(endp->bts_jb, endp->bts_jitter_delay_max); + osmo_jibuf_set_dequeue_cb(endp->bts_jb, mgcp_dejitter_udp_send, &endp->net_end); + } return NULL; break; case MGCP_POLICY_CONT: @@ -906,6 +918,14 @@ mgcp_header_done: } } + /* Set up jitter buffer if required after policy has updated jibuf endp values */ + if (endp->bts_use_jibuf) { + endp->bts_jb = osmo_jibuf_alloc(tcfg->endpoints); + osmo_jibuf_set_min_delay(endp->bts_jb, endp->bts_jitter_delay_min); + osmo_jibuf_set_max_delay(endp->bts_jb, endp->bts_jitter_delay_max); + osmo_jibuf_set_dequeue_cb(endp->bts_jb, mgcp_dejitter_udp_send, &endp->net_end); + } + LOGP(DLMGCP, LOGL_DEBUG, "Creating endpoint on: 0x%x CI: %u port: %u/%u\n", ENDPOINT_NUMBER(endp), endp->ci, endp->net_end.local_port, endp->bts_end.local_port); @@ -1373,6 +1393,9 @@ int mgcp_endpoints_allocate(struct mgcp_trunk_config *tcfg) void mgcp_release_endp(struct mgcp_endpoint *endp) { LOGP(DLMGCP, LOGL_DEBUG, "Releasing endpoint on: 0x%x\n", ENDPOINT_NUMBER(endp)); + if (endp->bts_jb) + osmo_jibuf_delete(endp->bts_jb); + endp->bts_jb = NULL; endp->ci = CI_UNUSED; endp->allocated = 0; diff --git a/src/libosmo-legacy-mgcp/mgcp_vty.c b/src/libosmo-legacy-mgcp/mgcp_vty.c index 7b11422..be884cb 100644 --- a/src/libosmo-legacy-mgcp/mgcp_vty.c +++ b/src/libosmo-legacy-mgcp/mgcp_vty.c @@ -29,6 +29,7 @@ #include <osmocom/legacy_mgcp/vty.h> #include <string.h> +#include <inttypes.h> #define RTCP_OMIT_STR "Drop RTCP packets in both directions\n" #define RTP_PATCH_STR "Modify RTP packet header in both directions\n" @@ -164,6 +165,13 @@ static int config_write_mgcp(struct vty *vty) vty_out(vty, " osmux dummy %s%s", g_cfg->osmux_dummy ? "on" : "off", VTY_NEWLINE); } + if (g_cfg->bts_use_jibuf) + vty_out(vty, " bts-jitter-buffer%s", VTY_NEWLINE); + if (g_cfg->bts_jitter_delay_min) + vty_out(vty, " bts-jitter-delay-min %"PRIu32"%s", g_cfg->bts_jitter_delay_min, VTY_NEWLINE); + if (g_cfg->bts_jitter_delay_max) + vty_out(vty, " bts-jitter-delay-max %"PRIu32"%s", g_cfg->bts_jitter_delay_max, VTY_NEWLINE); + return CMD_SUCCESS; } @@ -241,6 +249,11 @@ DEFUN(show_mcgp, show_mgcp_cmd, if (g_cfg->osmux) vty_out(vty, "Osmux used CID: %d%s", osmux_used_cid(), VTY_NEWLINE); + vty_out(vty, "Jitter Buffer by default on Uplink : %s%s", + g_cfg->bts_use_jibuf ? "on" : "off", VTY_NEWLINE); + if (g_cfg->bts_use_jibuf) + vty_out(vty, "Jitter Buffer delays: min=%"PRIu32" max=%"PRIu32"%s", + g_cfg->bts_jitter_delay_min, g_cfg->bts_jitter_delay_max, VTY_NEWLINE); return CMD_SUCCESS; } @@ -1344,6 +1357,63 @@ DEFUN(cfg_mgcp_osmux_dummy, return CMD_SUCCESS; } +#define DEJITTER_STR "Uplink Jitter Buffer" +DEFUN(cfg_mgcp_bts_use_jibuf, + cfg_mgcp_bts_use_jibuf_cmd, + "bts-jitter-buffer", + DEJITTER_STR "\n") +{ + g_cfg->bts_use_jibuf = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_bts_use_jibuf, + cfg_mgcp_no_bts_use_jibuf_cmd, + "no bts-jitter-buffer", + NO_STR DEJITTER_STR "\n") +{ + g_cfg->bts_use_jibuf = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bts_jitter_delay_min, + cfg_mgcp_bts_jitter_delay_min_cmd, + "bts-jitter-buffer-delay-min <1-65535>", + DEJITTER_STR " Minimum Delay in ms\n" "Minimum Delay in ms\n") +{ + g_cfg->bts_jitter_delay_min = atoi(argv[0]); + if (!g_cfg->bts_jitter_delay_min) { + vty_out(vty, "bts-jitter-buffer-delay-min cannot be zero.%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (g_cfg->bts_jitter_delay_min && g_cfg->bts_jitter_delay_max && + g_cfg->bts_jitter_delay_min > g_cfg->bts_jitter_delay_max) { + vty_out(vty, "bts-jitter-buffer-delay-min cannot be bigger than " \ + "bts-jitter-buffer-delay-max.%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bts_jitter_delay_max, + cfg_mgcp_bts_jitter_delay_max_cmd, + "bts-jitter-buffer-delay-max <1-65535>", + DEJITTER_STR " Maximum Delay in ms\n" "Maximum Delay in ms\n") +{ + g_cfg->bts_jitter_delay_max = atoi(argv[0]); + if (!g_cfg->bts_jitter_delay_max) { + vty_out(vty, "bts-jitter-buffer-delay-max cannot be zero.%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (g_cfg->bts_jitter_delay_min && g_cfg->bts_jitter_delay_max && + g_cfg->bts_jitter_delay_min > g_cfg->bts_jitter_delay_max) { + vty_out(vty, "bts-jitter-buffer-delay-max cannot be smaller than " \ + "bts-jitter-buffer-delay-min.%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + int mgcp_vty_init(void) { install_element_ve(&show_mgcp_cmd); @@ -1411,6 +1481,10 @@ int mgcp_vty_init(void) install_element(MGCP_NODE, &cfg_mgcp_osmux_dummy_cmd); install_element(MGCP_NODE, &cfg_mgcp_allow_transcoding_cmd); install_element(MGCP_NODE, &cfg_mgcp_no_allow_transcoding_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bts_use_jibuf_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_bts_use_jibuf_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bts_jitter_delay_min_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bts_jitter_delay_max_cmd); install_element(MGCP_NODE, &cfg_mgcp_trunk_cmd); diff --git a/src/libosmo-mgcp-client/Makefile.am b/src/libosmo-mgcp-client/Makefile.am index 01371d7..d3f8fff 100644 --- a/src/libosmo-mgcp-client/Makefile.am +++ b/src/libosmo-mgcp-client/Makefile.am @@ -20,7 +20,7 @@ AM_LDFLAGS = \ # This is not at all related to the release version, but a range of supported # API versions. Read TODO_RELEASE in the source tree's root! -MGCP_CLIENT_LIBVERSION=3:0:0 +MGCP_CLIENT_LIBVERSION=4:0:1 lib_LTLIBRARIES = \ libosmo-mgcp-client.la \ diff --git a/src/libosmo-mgcp-client/mgcp_client.c b/src/libosmo-mgcp-client/mgcp_client.c index e054593..00b52f8 100644 --- a/src/libosmo-mgcp-client/mgcp_client.c +++ b/src/libosmo-mgcp-client/mgcp_client.c @@ -36,6 +36,150 @@ #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) @@ -178,24 +322,116 @@ static bool mgcp_line_is_valid(const char *line) return true; } -/* Parse a line like "m=audio 16002 RTP/AVP 98" */ -static int mgcp_parse_audio_port(struct mgcp_response *r, const char *line) +/* 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) { - if (sscanf(line, "m=audio %hu", - &r->audio_port) != 1) - goto response_parse_failure; + 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; + 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: +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 MGCP response header (audio port)\n"); + "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) { @@ -253,6 +489,7 @@ int mgcp_response_parse_params(struct mgcp_response *r) 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 */ @@ -277,8 +514,13 @@ int mgcp_response_parse_params(struct mgcp_response *r) 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(r, line); + rc = mgcp_parse_audio_port_pt(r, line); if (rc) goto exit; break; @@ -293,6 +535,10 @@ int mgcp_response_parse_params(struct mgcp_response *r) } } + /* 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); @@ -813,6 +1059,119 @@ struct msgb *mgcp_msg_dlcx(struct mgcp_client *mgcp, uint16_t rtp_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 @@ -823,7 +1182,8 @@ struct msgb *mgcp_msg_gen(struct mgcp_client *mgcp, struct mgcp_msg *mgcp_msg) uint32_t mandatory_mask; struct msgb *msg = msgb_alloc_headroom(4096, 128, "MGCP tx"); int rc = 0; - char local_ip[INET_ADDRSTRLEN]; + int rc_sdp; + bool use_sdp = false; msg->l2h = msg->data; msg->cb[MSGB_CB_MGCP_TRANS_ID] = trans_id; @@ -902,9 +1262,17 @@ struct msgb *mgcp_msg_gen(struct mgcp_client *mgcp, struct mgcp_msg *mgcp_msg) rc += msgb_printf(msg, "I: %s\r\n", mgcp_msg->conn_id); } - /* Add local connection options */ - if (mgcp_msg->verb == MGCP_VERB_CRCX) - rc += msgb_printf(msg, "L: p:20, a:AMR, nt:IN\r\n"); + /* 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) @@ -912,52 +1280,15 @@ struct msgb *mgcp_msg_gen(struct mgcp_client *mgcp, struct mgcp_msg *mgcp_msg) msgb_printf(msg, "M: %s\r\n", mgcp_client_cmode_name(mgcp_msg->conn_mode)); - /* Add SDP body */ - if (mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_IP - && mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_PORT) { - - /* 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 NULL; - } - - /* 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); + /* 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; - } - if (strlen(mgcp_msg->audio_ip) <= 0) { - LOGP(DLMGCP, LOGL_ERROR, - "Empty ip address, can not generate MGCP message\n"); - msgb_free(msg); - return NULL; - } - 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 255\r\n", - mgcp_msg->audio_port); + else + rc += rc_sdp; } if (rc != 0) { diff --git a/src/libosmo-mgcp-client/mgcp_client_fsm.c b/src/libosmo-mgcp-client/mgcp_client_fsm.c index 3c755be..fe1590f 100644 --- a/src/libosmo-mgcp-client/mgcp_client_fsm.c +++ b/src/libosmo-mgcp-client/mgcp_client_fsm.c @@ -112,9 +112,12 @@ static struct msgb *make_crcx_msg_bind(struct mgcp_ctx *mgcp_ctx) .verb = MGCP_VERB_CRCX, .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE), .call_id = mgcp_ctx->conn_peer_local.call_id, - .conn_mode = MGCP_CONN_LOOPBACK, + .conn_mode = MGCP_CONN_RECV_ONLY, + .ptime = mgcp_ctx->conn_peer_local.ptime, + .codecs_len = mgcp_ctx->conn_peer_local.codecs_len }; osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_local.endpoint, MGCP_ENDPOINT_MAXLEN); + memcpy(mgcp_msg.codecs, mgcp_ctx->conn_peer_local.codecs, sizeof(mgcp_msg.codecs)); return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg); } @@ -124,15 +127,19 @@ static struct msgb *make_crcx_msg_bind_connect(struct mgcp_ctx *mgcp_ctx) struct mgcp_msg mgcp_msg; mgcp_msg = (struct mgcp_msg) { - .verb = MGCP_VERB_CRCX,.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | - MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | - MGCP_MSG_PRESENCE_AUDIO_PORT), + .verb = MGCP_VERB_CRCX, + .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | + MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | + MGCP_MSG_PRESENCE_AUDIO_PORT), .call_id = mgcp_ctx->conn_peer_local.call_id, .conn_mode = MGCP_CONN_RECV_SEND, .audio_ip = mgcp_ctx->conn_peer_local.addr, .audio_port = mgcp_ctx->conn_peer_local.port, + .ptime = mgcp_ctx->conn_peer_local.ptime, + .codecs_len = mgcp_ctx->conn_peer_local.codecs_len }; osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_local.endpoint, MGCP_ENDPOINT_MAXLEN); + memcpy(mgcp_msg.codecs, mgcp_ctx->conn_peer_local.codecs, sizeof(mgcp_msg.codecs)); return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg); } @@ -150,8 +157,11 @@ static struct msgb *make_mdcx_msg(struct mgcp_ctx *mgcp_ctx) .conn_mode = MGCP_CONN_RECV_SEND, .audio_ip = mgcp_ctx->conn_peer_local.addr, .audio_port = mgcp_ctx->conn_peer_local.port, + .ptime = mgcp_ctx->conn_peer_local.ptime, + .codecs_len = mgcp_ctx->conn_peer_local.codecs_len }; osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_remote.endpoint, MGCP_ENDPOINT_MAXLEN); + memcpy(mgcp_msg.codecs, mgcp_ctx->conn_peer_local.codecs, sizeof(mgcp_msg.codecs)); /* Note: We take the endpoint and the call_id from the remote * connection info, because we can be confident that the @@ -215,6 +225,14 @@ static void fsm_crcx_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) } } +/* Return the CI that the MGW allocated during CRCX response. This is purely informational for logging + * and identity tracking; the mgcp_conn_*() functions take care of using the right CI internally. */ +const char *mgcp_conn_get_ci(struct osmo_fsm_inst *fi) +{ + struct mgcp_ctx *mgcp_ctx = fi->priv; + return mgcp_ctx->conn_id; +} + static void mgw_crcx_resp_cb(struct mgcp_response *r, void *priv) { struct osmo_fsm_inst *fi = priv; @@ -476,7 +494,7 @@ static void fsm_cleanup_cb(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause ca * mgcp_conn_delete() to instruct the FSM to perform a graceful exit */ if (strlen(mgcp_ctx->conn_id)) { LOGPFSML(fi, LOGL_ERROR, - "MGW/DLCX: aprupt FSM termination with connections still present, sending unconditional DLCX...\n"); + "MGW/DLCX: abrupt FSM termination with connections still present, sending unconditional DLCX...\n"); msg = make_dlcx_msg(mgcp_ctx); OSMO_ASSERT(msg); mgcp_client_tx(mgcp, msg, NULL, NULL); @@ -548,7 +566,7 @@ static struct osmo_fsm fsm_mgcp_client = { /*! allocate FSM, and create a new connection on the MGW. * \param[in] mgcp MGCP client descriptor. - * \param[in] mgcpparent_fi Parent FSM instance. + * \param[in] parent_fi Parent FSM instance. * \param[in] parent_term_evt Event to be sent to parent when terminating. * \param[in] parent_evt Event to be sent to parent when operation is done. * \param[in] conn_peer Connection parameters (ip, port...). @@ -565,7 +583,7 @@ struct osmo_fsm_inst *mgcp_conn_create(struct mgcp_client *mgcp, struct osmo_fsm OSMO_ASSERT(mgcp); OSMO_ASSERT(conn_peer); - /* Check if IP/Port informstaion in conn info makes sense */ + /* Check if IP/Port information in conn info makes sense */ if (conn_peer->port && inet_aton(conn_peer->addr, &ip_test) == 0) return NULL; @@ -622,17 +640,24 @@ int mgcp_conn_modify(struct osmo_fsm_inst *fi, uint32_t parent_evt, struct mgcp_ OSMO_ASSERT(fi->state != ST_DLCX_RESP); /* Check if IP/Port parameters make sense */ - if (conn_peer->port == 0) + if (conn_peer->port == 0) { + LOGPFSML(fi, LOGL_ERROR, "Cannot MDCX, port == 0\n"); return -EINVAL; - if (inet_aton(conn_peer->addr, &ip_test) == 0) + } + if (inet_aton(conn_peer->addr, &ip_test) == 0) { + LOGPFSML(fi, LOGL_ERROR, "Cannot MDCX, IP address == 0.0.0.0\n"); return -EINVAL; + } /*! The user may supply an endpoint identifier in conn_peer. The * identifier is then checked. This check is optional. Later steps do * not depend on the endpoint identifier supplied here because it is * already implicitly known from the CRCX phase. */ - if (strlen(conn_peer->endpoint) && strcmp(conn_peer->endpoint, mgcp_ctx->conn_peer_remote.endpoint)) + if (strlen(conn_peer->endpoint) && strcmp(conn_peer->endpoint, mgcp_ctx->conn_peer_remote.endpoint)) { + LOGPFSML(fi, LOGL_ERROR, "Cannot MDCX, endpoint mismatches: requested %s, should be %s\n", + conn_peer->endpoint, mgcp_ctx->conn_peer_remote.endpoint); return -EINVAL; + } /*! Note: The call-id is implicitly known from the previous CRCX and * will not be checked even when it is set in conn_peer. */ diff --git a/src/libosmo-mgcp/Makefile.am b/src/libosmo-mgcp/Makefile.am index a98c391..587bdd4 100644 --- a/src/libosmo-mgcp/Makefile.am +++ b/src/libosmo-mgcp/Makefile.am @@ -35,6 +35,7 @@ libosmo_mgcp_a_SOURCES = \ mgcp_vty.c \ mgcp_osmux.c \ mgcp_sdp.c \ + mgcp_codec.c \ mgcp_msg.c \ mgcp_conn.c \ mgcp_stat.c \ diff --git a/src/libosmo-mgcp/mgcp_codec.c b/src/libosmo-mgcp/mgcp_codec.c new file mode 100644 index 0000000..2ce90dd --- /dev/null +++ b/src/libosmo-mgcp/mgcp_codec.c @@ -0,0 +1,343 @@ +/* + * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2014 by On-Waves + * 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/mgcp/mgcp_internal.h> +#include <osmocom/mgcp/mgcp_endp.h> +#include <errno.h> + +/* Helper function to dump codec information of a specified codec to a printable + * string, used by dump_codec_summary() */ +static char *dump_codec(struct mgcp_rtp_codec *codec) +{ + static char str[256]; + char *pt_str; + + if (codec->payload_type > 76) + pt_str = "DYNAMIC"; + else if (codec->payload_type > 72) + pt_str = "RESERVED <!>"; + else if (codec->payload_type != PTYPE_UNDEFINED) + pt_str = codec->subtype_name; + else + pt_str = "INVALID <!>"; + + snprintf(str, sizeof(str), "(pt:%i=%s, audio:%s subt=%s, rate=%u, ch=%i, t=%u/%u)", codec->payload_type, pt_str, + codec->audio_name, codec->subtype_name, codec->rate, codec->channels, codec->frame_duration_num, + codec->frame_duration_den); + return str; +} + +/*! Dump a summary of all negotiated codecs to debug log + * \param[in] conn related rtp-connection. */ +void mgcp_codec_summary(struct mgcp_conn_rtp *conn) +{ + struct mgcp_rtp_end *rtp; + unsigned int i; + struct mgcp_rtp_codec *codec; + struct mgcp_endpoint *endp; + + rtp = &conn->end; + endp = conn->conn->endp; + + if (rtp->codecs_assigned == 0) { + LOGP(DLMGCP, LOGL_ERROR, "endpoint:0x%x conn:%s no codecs available\n", ENDPOINT_NUMBER(endp), + mgcp_conn_dump(conn->conn)); + return; + } + + /* Store parsed codec information */ + for (i = 0; i < rtp->codecs_assigned; i++) { + codec = &rtp->codecs[i]; + + LOGP(DLMGCP, LOGL_DEBUG, "endpoint:0x%x conn:%s codecs[%u]:%s", ENDPOINT_NUMBER(endp), + mgcp_conn_dump(conn->conn), i, dump_codec(codec)); + + if (codec == rtp->codec) + LOGPC(DLMGCP, LOGL_DEBUG, " [selected]"); + + LOGPC(DLMGCP, LOGL_DEBUG, "\n"); + } +} + +/* Initalize or reset codec information with default data. */ +void codec_init(struct mgcp_rtp_codec *codec) +{ + if (codec->subtype_name) + talloc_free(codec->subtype_name); + if (codec->audio_name) + talloc_free(codec->audio_name); + memset(codec, 0, sizeof(*codec)); + codec->payload_type = -1; + codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; + codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; + codec->rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE; + codec->channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS; +} + +/*! Initalize or reset codec information with default data. + * \param[out] conn related rtp-connection. */ +void mgcp_codec_reset_all(struct mgcp_conn_rtp *conn) +{ + memset(conn->end.codecs, 0, sizeof(conn->end.codecs)); + conn->end.codecs_assigned = 0; + conn->end.codec = NULL; +} + +/* Set members of struct mgcp_rtp_codec, extrapolate in missing information */ +static int codec_set(void *ctx, struct mgcp_rtp_codec *codec, + int payload_type, const char *audio_name, unsigned int pt_offset) +{ + int rate; + int channels; + char audio_codec[64]; + + /* Initalize the codec struct with some default data to begin with */ + codec_init(codec); + + if (payload_type != PTYPE_UNDEFINED) { + /* Make sure we do not get any reserved or undefined type numbers */ + /* See also: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */ + if (payload_type == 1 || payload_type == 2 || payload_type == 19) + goto error; + if (payload_type >= 72 && payload_type <= 76) + goto error; + if (payload_type >= 127) + goto error; + + codec->payload_type = payload_type; + } + + /* When no audio name is given, we are forced to use the payload + * type to generate the audio name. This is only possible for + * non dynamic payload types, which are statically defined */ + if (!audio_name) { + switch (payload_type) { + case 0: + audio_name = talloc_strdup(ctx, "PCMU/8000/1"); + break; + case 3: + audio_name = talloc_strdup(ctx, "GSM/8000/1"); + break; + case 8: + audio_name = talloc_strdup(ctx, "PCMA/8000/1"); + break; + case 18: + audio_name = talloc_strdup(ctx, "G729/8000/1"); + break; + default: + /* The given payload type is not known to us, or it + * it is a dynamic payload type for which we do not + * know the audio name. We must give up here */ + goto error; + } + } + + /* Now we extract the codec subtype name, rate and channels. The latter + * two are optional. If they are not present we use the safe defaults + * above. */ + if (strlen(audio_name) > sizeof(audio_codec)) + goto error; + channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS; + rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE; + if (sscanf(audio_name, "%63[^/]/%d/%d", audio_codec, &rate, &channels) < 1) + goto error; + + /* Note: We only accept configurations with one audio channel! */ + if (channels != 1) + goto error; + + codec->rate = rate; + codec->channels = channels; + codec->subtype_name = talloc_strdup(ctx, audio_codec); + codec->audio_name = talloc_strdup(ctx, audio_name); + codec->payload_type = payload_type; + + if (!strcmp(audio_codec, "G729")) { + codec->frame_duration_num = 10; + codec->frame_duration_den = 1000; + } else { + codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; + codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; + } + + /* Derive the payload type if it is unknown */ + if (codec->payload_type == PTYPE_UNDEFINED) { + + /* For the known codecs from the static range we restore + * the IANA or 3GPP assigned payload type number */ + if (codec->rate == 8000 && codec->channels == 1) { + /* See also: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */ + if (!strcmp(codec->subtype_name, "GSM")) + codec->payload_type = 3; + else if (!strcmp(codec->subtype_name, "PCMA")) + codec->payload_type = 8; + else if (!strcmp(codec->subtype_name, "PCMU")) + codec->payload_type = 0; + else if (!strcmp(codec->subtype_name, "G729")) + codec->payload_type = 18; + + /* See also: 3GPP TS 48.103, chapter 5.4.2.2 RTP Payload + * Note: These are not fixed payload types as the IANA + * defined once, they still remain dymanic payload + * types, but with a payload type number preference. */ + else if (!strcmp(codec->subtype_name, "GSM-EFR")) + codec->payload_type = 110; + else if (!strcmp(codec->subtype_name, "GSM-HR-08")) + codec->payload_type = 111; + else if (!strcmp(codec->subtype_name, "AMR")) + codec->payload_type = 112; + else if (!strcmp(codec->subtype_name, "AMR-WB")) + codec->payload_type = 113; + } + + /* If we could not determine a payload type we assume that + * we are dealing with a codec from the dynamic range. We + * choose a fixed identifier from 96-109. (Note: normally, + * the dynamic payload type rante is from 96-127, but from + * 110 onwards 3gpp defines prefered codec types, which are + * also fixed, see above) */ + if (codec->payload_type < 0) { + codec->payload_type = 96 + pt_offset; + if (codec->payload_type > 109) + goto error; + } + } + + return 0; +error: + /* Make sure we leave a clean codec entry on error. */ + codec_init(codec); + memset(codec, 0, sizeof(*codec)); + return -EINVAL; +} + +/*! Add codec configuration depending on payload type and/or codec name. This + * function uses the input parameters to extrapolate the full codec information. + * \param[out] codec configuration (caller provided memory). + * \param[out] conn related rtp-connection. + * \param[in] payload_type codec type id (e.g. 3 for GSM, -1 when undefined). + * \param[in] audio_name audio codec name (e.g. "GSM/8000/1"). + * \returns 0 on success, -EINVAL on failure. */ +int mgcp_codec_add(struct mgcp_conn_rtp *conn, int payload_type, const char *audio_name) +{ + int rc; + + /* The amount of codecs we can store is limited, make sure we do not + * overrun this limit. */ + if (conn->end.codecs_assigned >= MGCP_MAX_CODECS) + return -EINVAL; + + rc = codec_set(conn->conn, &conn->end.codecs[conn->end.codecs_assigned], payload_type, audio_name, + conn->end.codecs_assigned); + if (rc != 0) + return -EINVAL; + + conn->end.codecs_assigned++; + + return 0; +} + +/* Check if the given codec is applicable on the specified endpoint + * Helper function for mgcp_codec_decide() */ +static bool is_codec_compatible(const struct mgcp_endpoint *endp, const struct mgcp_rtp_codec *codec) +{ + char codec_name[64]; + + /* A codec name must be set, if not, this might mean that the codec + * (payload type) that was assigned is unknown to us so we must stop + * here. */ + if (!codec->subtype_name) + return false; + + /* We now extract the codec_name (letters before the /, e.g. "GSM" + * from the audio name that is stored in the trunk configuration. + * We do not compare to the full audio_name because we expect that + * "GSM", "GSM/8000" and "GSM/8000/1" are all compatible when the + * audio name of the codec is set to "GSM" */ + if (sscanf(endp->tcfg->audio_name, "%63[^/]/%*d/%*d", codec_name) < 1) + return false; + + /* Finally we check if the subtype_name we have generated from the + * audio_name in the trunc struct patches the codec_name of the + * given codec */ + if (strcasecmp(codec_name, codec->subtype_name) == 0) + return true; + + /* FIXME: It is questinable that the method to pick a compatible + * codec can work properly. Since this useses tcfg->audio_name, as + * a reference, which is set to "AMR/8000" permanently. + * tcfg->audio_name must be updated by the first connection that + * has been made on an endpoint, so that the second connection + * can make a meaningful decision here */ + + return false; +} + +/*! Decide for one suitable codec + * \param[in] conn related rtp-connection. + * \returns 0 on success, -EINVAL on failure. */ +int mgcp_codec_decide(struct mgcp_conn_rtp *conn) +{ + struct mgcp_rtp_end *rtp; + unsigned int i; + struct mgcp_endpoint *endp; + bool codec_assigned = false; + + endp = conn->conn->endp; + rtp = &conn->end; + + /* This function works on the results the SDP/LCO parser has extracted + * from the MGCP message. The goal is to select a suitable codec for + * the given connection. When transcoding is available, the first codec + * from the codec list is taken without further checking. When + * transcoding is not available, then the choice must be made more + * carefully. Each codec in the list is checked until one is found that + * is rated compatible. The rating is done by the helper function + * is_codec_compatible(), which does the actual checking. */ + for (i = 0; i < rtp->codecs_assigned; i++) { + /* When no transcoding is available, avoid codecs that would + * require transcoding. */ + if (endp->tcfg->no_audio_transcoding && !is_codec_compatible(endp, &rtp->codecs[i])) { + LOGP(DLMGCP, LOGL_NOTICE, "transcoding not available, skipping codec: %d/%s\n", + rtp->codecs[i].payload_type, rtp->codecs[i].subtype_name); + continue; + } + + rtp->codec = &rtp->codecs[i]; + codec_assigned = true; + break; + } + + /* FIXME: To the reviewes: This is problematic. I do not get why we + * need to reset the packet_duration_ms depending on the codec + * selection. I thought it were all 20ms? Is this to address some + * cornercase. (This piece of code was in the code path before, + * together with the note: "TODO/XXX: Store this per codec and derive + * it on use" */ + if (codec_assigned) { + if (rtp->maximum_packet_time >= 0 + && rtp->maximum_packet_time * rtp->codec->frame_duration_den > + rtp->codec->frame_duration_num * 1500) + rtp->packet_duration_ms = 0; + + return 0; + } + + return -EINVAL; +} diff --git a/src/libosmo-mgcp/mgcp_conn.c b/src/libosmo-mgcp/mgcp_conn.c index 998dbc5..3a5db0f 100644 --- a/src/libosmo-mgcp/mgcp_conn.c +++ b/src/libosmo-mgcp/mgcp_conn.c @@ -25,9 +25,31 @@ #include <osmocom/mgcp/mgcp_internal.h> #include <osmocom/mgcp/mgcp_common.h> #include <osmocom/mgcp/mgcp_endp.h> +#include <osmocom/mgcp/mgcp_sdp.h> +#include <osmocom/mgcp/mgcp_codec.h> #include <osmocom/gsm/gsm_utils.h> +#include <osmocom/core/rate_ctr.h> #include <ctype.h> +static const struct rate_ctr_desc rate_ctr_desc[] = { + [IN_STREAM_ERR_TSTMP_CTR] = {"stream_err_tstmp:in", "Inbound rtp-stream timestamp errors."}, + [OUT_STREAM_ERR_TSTMP_CTR] = {"stream_err_tstmp:out", "Outbound rtp-stream timestamp errors."}, + [RTP_PACKETS_RX_CTR] = {"rtp:packets_rx", "Inbound rtp packets."}, + [RTP_OCTETS_RX_CTR] = {"rtp:octets_rx", "Inbound rtp octets."}, + [RTP_PACKETS_TX_CTR] = {"rtp:packets_tx", "Outbound rtp packets."}, + [RTP_OCTETS_TX_CTR] = {"rtp:octets_rx", "Outbound rtp octets."}, + [RTP_DROPPED_PACKETS_CTR] = {"rtp:dropped", "dropped rtp packets."} +}; + +const static struct rate_ctr_group_desc rate_ctr_group_desc = { + .group_name_prefix = "conn_rtp", + .group_description = "rtp connection statistics", + .class_id = 1, + .num_ctr = ARRAY_SIZE(rate_ctr_desc), + .ctr_desc = rate_ctr_desc +}; + + /* Allocate a new connection identifier. According to RFC3435, they must * be unique only within the scope of the endpoint. (Caller must provide * memory for id) */ @@ -67,26 +89,14 @@ static int mgcp_alloc_id(struct mgcp_endpoint *endp, char *id) return -1; } -/* Reset codec state and free memory */ -static void mgcp_rtp_codec_init(struct mgcp_rtp_codec *codec) -{ - codec->payload_type = -1; - codec->subtype_name = NULL; - codec->audio_name = NULL; - codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; - codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; - codec->rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE; - codec->channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS; - - /* see also mgcp_sdp.c, mgcp_set_audio_info() */ - talloc_free(codec->subtype_name); - talloc_free(codec->audio_name); -} - /* Initialize rtp connection struct with default values */ static void mgcp_rtp_conn_init(struct mgcp_conn_rtp *conn_rtp, struct mgcp_conn *conn) { struct mgcp_rtp_end *end = &conn_rtp->end; + /* FIXME: Each new rate counter group requires an unique index. At the + * moment we generate this index using this counter, but perhaps there + * is a more concious way to assign the indexes. */ + static unsigned int rate_ctr_index = 0; conn_rtp->type = MGCP_RTP_DEFAULT; conn_rtp->osmux.allocated_cid = -1; @@ -96,7 +106,6 @@ static void mgcp_rtp_conn_init(struct mgcp_conn_rtp *conn_rtp, struct mgcp_conn end->rtp.fd = -1; end->rtcp.fd = -1; - memset(&end->stats, 0, sizeof(end->stats)); end->rtp_port = end->rtcp_port = 0; talloc_free(end->fmtp_extra); end->fmtp_extra = NULL; @@ -105,9 +114,15 @@ static void mgcp_rtp_conn_init(struct mgcp_conn_rtp *conn_rtp, struct mgcp_conn end->frames_per_packet = 0; /* unknown */ end->packet_duration_ms = DEFAULT_RTP_AUDIO_PACKET_DURATION_MS; end->output_enabled = 0; + end->maximum_packet_time = -1; + + conn_rtp->rate_ctr_group = rate_ctr_group_alloc(conn, &rate_ctr_group_desc, rate_ctr_index); + conn_rtp->state.in_stream.err_ts_ctr = &conn_rtp->rate_ctr_group->ctr[IN_STREAM_ERR_TSTMP_CTR]; + conn_rtp->state.out_stream.err_ts_ctr = &conn_rtp->rate_ctr_group->ctr[OUT_STREAM_ERR_TSTMP_CTR]; + rate_ctr_index++; - mgcp_rtp_codec_init(&end->codec); - mgcp_rtp_codec_init(&end->alt_codec); + /* Make sure codec table is reset */ + mgcp_codec_reset_all(conn_rtp); } /* Cleanup rtp connection struct */ @@ -116,6 +131,7 @@ static void mgcp_rtp_conn_cleanup(struct mgcp_conn_rtp *conn_rtp) osmux_disable_conn(conn_rtp); osmux_release_cid(conn_rtp); mgcp_free_rtp_port(&conn_rtp->end); + rate_ctr_group_free(conn_rtp->rate_ctr_group); } /*! allocate a new connection list entry. diff --git a/src/libosmo-mgcp/mgcp_network.c b/src/libosmo-mgcp/mgcp_network.c index 6923b97..3ac93be 100644 --- a/src/libosmo-mgcp/mgcp_network.c +++ b/src/libosmo-mgcp/mgcp_network.c @@ -222,7 +222,7 @@ static int check_rtp_timestamp(struct mgcp_endpoint *endp, if (seq == sstate->last_seq) { if (timestamp != sstate->last_timestamp) { - sstate->err_ts_counter += 1; + rate_ctr_inc(sstate->err_ts_ctr); LOGP(DRTP, LOGL_ERROR, "The %s timestamp delta is != 0 but the sequence " "number %d is the same, " @@ -272,7 +272,7 @@ static int check_rtp_timestamp(struct mgcp_endpoint *endp, ts_alignment_error(sstate, state->packet_duration, timestamp); if (timestamp_error) { - sstate->err_ts_counter += 1; + rate_ctr_inc(sstate->err_ts_ctr); LOGP(DRTP, LOGL_NOTICE, "The %s timestamp has an alignment error of %d " "on 0x%x SSRC: %u " @@ -310,7 +310,7 @@ static int adjust_rtp_timestamp_offset(struct mgcp_endpoint *endp, ENDPOINT_NUMBER(endp), tsdelta, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); } else { - tsdelta = rtp_end->codec.rate * 20 / 1000; + tsdelta = rtp_end->codec->rate * 20 / 1000; LOGP(DRTP, LOGL_NOTICE, "Fixed packet duration and last timestamp delta " "are not available on 0x%x, " @@ -399,12 +399,12 @@ int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, /*! dummy callback to disable transcoding (see also cfg->setup_rtp_processing_cb). * \param[in] associated endpoint - * \param[in] destination RTP end - * \param[in] source RTP end + * \param[in] destination RTP connnection + * \param[in] source RTP connection * \returns ignores input parameters, return always 0 */ int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp, - struct mgcp_rtp_end *dst_end, - struct mgcp_rtp_end *src_end) + struct mgcp_conn_rtp *conn_dst, + struct mgcp_conn_rtp *conn_src) { LOGP(DRTP, LOGL_DEBUG, "endpoint:0x%x transcoding disabled\n", ENDPOINT_NUMBER(endp)); @@ -421,8 +421,8 @@ void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp, "endpoint:0x%x conn:%s using format defaults\n", ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn->conn)); - *payload_type = conn->end.codec.payload_type; - *audio_name = conn->end.codec.audio_name; + *payload_type = conn->end.codec->payload_type; + *audio_name = conn->end.codec->audio_name; *fmtp_extra = conn->end.fmtp_extra; } @@ -490,7 +490,7 @@ void mgcp_patch_and_count(struct mgcp_endpoint *endp, uint16_t seq; uint32_t timestamp, ssrc; struct rtp_hdr *rtp_hdr; - int payload = rtp_end->codec.payload_type; + int payload = rtp_end->codec->payload_type; if (len < sizeof(*rtp_hdr)) return; @@ -498,7 +498,7 @@ void mgcp_patch_and_count(struct mgcp_endpoint *endp, rtp_hdr = (struct rtp_hdr *)data; seq = ntohs(rtp_hdr->sequence); timestamp = ntohl(rtp_hdr->timestamp); - arrival_time = get_current_ts(rtp_end->codec.rate); + arrival_time = get_current_ts(rtp_end->codec->rate); ssrc = ntohl(rtp_hdr->ssrc); transit = arrival_time - timestamp; @@ -511,7 +511,9 @@ void mgcp_patch_and_count(struct mgcp_endpoint *endp, state->in_stream.last_tsdelta = 0; state->packet_duration = mgcp_rtp_packet_duration(endp, rtp_end); - state->out_stream = state->in_stream; + state->out_stream.last_seq = seq - 1; + state->out_stream.ssrc = state->patch.orig_ssrc = ssrc; + state->out_stream.last_tsdelta = 0; state->out_stream.last_timestamp = timestamp; state->out_stream.ssrc = ssrc - 1; /* force output SSRC change */ LOGP(DRTP, LOGL_INFO, @@ -522,7 +524,7 @@ void mgcp_patch_and_count(struct mgcp_endpoint *endp, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); if (state->packet_duration == 0) { state->packet_duration = - rtp_end->codec.rate * 20 / 1000; + rtp_end->codec->rate * 20 / 1000; LOGP(DRTP, LOGL_NOTICE, "endpoint:0x%x fixed packet duration is not available, " "using fixed 20ms instead: %d from %s:%d\n", @@ -678,12 +680,9 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr, } LOGP(DRTP, LOGL_DEBUG, - "endpoint:0x%x loop:%d, mode:%d ", - ENDPOINT_NUMBER(endp), tcfg->audio_loop, conn_src->conn->mode); - if (conn_src->conn->mode == MGCP_CONN_LOOPBACK) - LOGPC(DRTP, LOGL_DEBUG, "(loopback)\n"); - else - LOGPC(DRTP, LOGL_DEBUG, "\n"); + "endpoint:0x%x loop:%d, mode:%d%s\n", + ENDPOINT_NUMBER(endp), tcfg->audio_loop, conn_src->conn->mode, + conn_src->conn->mode == MGCP_CONN_LOOPBACK ? " (loopback)" : ""); /* Note: In case of loopback configuration, both, the source and the * destination will point to the same connection. */ @@ -692,7 +691,7 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr, dest_name = conn_dst->conn->name; if (!rtp_end->output_enabled) { - rtp_end->stats.dropped_packets += 1; + rate_ctr_inc(&conn_dst->rate_ctr_group->ctr[RTP_DROPPED_PACKETS_CTR]); LOGP(DRTP, LOGL_DEBUG, "endpoint:0x%x output disabled, drop to %s %s " "rtp_port:%u rtcp_port:%u\n", @@ -733,11 +732,18 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr, * 'e400', or it will reject the RAB assignment. It seems to not harm other femto * cells (as long as we patch only the first RTP payload in each stream). */ - if (!rtp_state->patched_first_rtp_payload) { + if (!rtp_state->patched_first_rtp_payload + && conn_src->conn->mode == MGCP_CONN_LOOPBACK) { uint8_t *data = (uint8_t *) & buf[12]; - data[0] = 0xe4; - data[1] = 0x00; - rtp_state->patched_first_rtp_payload = true; + if (data[0] == 0xe0) { + data[0] = 0xe4; + data[1] = 0x00; + rtp_state->patched_first_rtp_payload = true; + LOGP(DRTP, LOGL_DEBUG, + "endpoint:0x%x Patching over first two bytes" + " to fake an IuUP Initialization Ack\n", + ENDPOINT_NUMBER(endp)); + } } len = mgcp_udp_send(rtp_end->rtp.fd, @@ -747,8 +753,8 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr, if (len <= 0) return len; - conn_dst->end.stats.packets_tx += 1; - conn_dst->end.stats.octets_tx += len; + rate_ctr_inc(&conn_dst->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]); + rate_ctr_add(&conn_dst->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], len); nbytes += len; buflen = cont; @@ -767,8 +773,8 @@ int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr, &rtp_end->addr, rtp_end->rtcp_port, buf, len); - conn_dst->end.stats.packets_tx += 1; - conn_dst->end.stats.octets_tx += len; + rate_ctr_inc(&conn_dst->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]); + rate_ctr_add(&conn_dst->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], len); return len; } @@ -865,6 +871,15 @@ static int check_rtp_destin(struct mgcp_conn_rtp *conn) struct mgcp_endpoint *endp; endp = conn->conn->endp; + /* Note: it is legal to create a connection but never setting a port + * and IP-address for outgoing data. */ + if (strcmp(inet_ntoa(conn->end.addr), "0.0.0.0") == 0 && conn->end.rtp_port == 0) { + LOGP(DRTP, LOGL_DEBUG, + "endpoint:0x%x destination IP-address and rtp port is (not yet) known\n", + ENDPOINT_NUMBER(endp)); + return -1; + } + if (strcmp(inet_ntoa(conn->end.addr), "0.0.0.0") == 0) { LOGP(DRTP, LOGL_ERROR, "endpoint:0x%x destination IP-address is invalid\n", @@ -928,8 +943,8 @@ static int mgcp_recv(int *proto, struct sockaddr_in *addr, char *buf, } /* Increment RX statistics */ - conn->end.stats.packets_rx += 1; - conn->end.stats.octets_rx += rc; + rate_ctr_inc(&conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]); + rate_ctr_add(&conn->rate_ctr_group->ctr[RTP_OCTETS_RX_CTR], rc); /* Forward a copy of the RTP data to a debug ip/port */ forward_data(fd->fd, &conn->tap_in, buf, rc); diff --git a/src/libosmo-mgcp/mgcp_osmux.c b/src/libosmo-mgcp/mgcp_osmux.c index 1d3cab3..26817c8 100644 --- a/src/libosmo-mgcp/mgcp_osmux.c +++ b/src/libosmo-mgcp/mgcp_osmux.c @@ -256,8 +256,8 @@ static void scheduled_tx_net_cb(struct msgb *msg, void *data) .sin_port = conn_net->end.rtp_port, }; - conn_bts->end.stats.octets_tx += msg->len; - conn_bts->end.stats.packets_tx++; + rate_ctr_inc(&conn_bts->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]); + rate_ctr_add(&conn_bts->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], msg->len); /* Send RTP data to NET */ /* FIXME: Get rid of conn_bts and conn_net! */ @@ -283,8 +283,8 @@ static void scheduled_tx_bts_cb(struct msgb *msg, void *data) .sin_port = conn_bts->end.rtp_port, }; - conn_net->end.stats.octets_tx += msg->len; - conn_net->end.stats.packets_tx++; + rate_ctr_inc(&conn_net->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]); + rate_ctr_add(&conn_net->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], msg->len); /* Send RTP data to BTS */ /* FIXME: Get rid of conn_bts and conn_net! */ @@ -322,11 +322,10 @@ int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what) { struct msgb *msg; struct osmux_hdr *osmuxh; - struct llist_head list; struct sockaddr_in addr; struct mgcp_config *cfg = ofd->data; uint32_t rem; - struct mgcp_conn_rtp *conn_net = NULL; + struct mgcp_conn_rtp *conn_bts = NULL; msg = osmux_recv(ofd, &addr); if (!msg) @@ -345,8 +344,8 @@ int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what) &addr.sin_addr, MGCP_DEST_NET); /* FIXME: Get rid of CONN_ID_XXX! */ - conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); - if (!conn_net) + conn_bts = mgcp_conn_get_rtp(endp, CONN_ID_BTS); + if (!conn_bts) goto out; if (!endp) { @@ -355,12 +354,11 @@ int osmux_read_from_bsc_nat_cb(struct osmo_fd *ofd, unsigned int what) osmuxh->circuit_id); goto out; } - conn_net->osmux.stats.octets += osmux_chunk_length(msg, rem); - conn_net->osmux.stats.chunks++; + conn_bts->osmux.stats.octets += osmux_chunk_length(msg, rem); + conn_bts->osmux.stats.chunks++; rem = msg->len; - osmux_xfrm_output(osmuxh, &conn_net->osmux.out, &list); - osmux_tx_sched(&list, scheduled_tx_bts_cb, endp); + osmux_xfrm_output_sched(&conn_bts->osmux.out, osmuxh); } out: msgb_free(msg); @@ -426,7 +424,6 @@ int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what) { struct msgb *msg; struct osmux_hdr *osmuxh; - struct llist_head list; struct sockaddr_in addr; struct mgcp_config *cfg = ofd->data; uint32_t rem; @@ -463,8 +460,7 @@ int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what) conn_net->osmux.stats.chunks++; rem = msg->len; - osmux_xfrm_output(osmuxh, &conn_net->osmux.out, &list); - osmux_tx_sched(&list, scheduled_tx_net_cb, endp); + osmux_xfrm_output_sched(&conn_net->osmux.out, osmuxh); } out: msgb_free(msg); @@ -553,9 +549,13 @@ int osmux_enable_conn(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn, switch (endp->cfg->role) { case MGCP_BSC_NAT: conn->type = MGCP_OSMUX_BSC_NAT; + osmux_xfrm_output_set_tx_cb(&conn->osmux.out, + scheduled_tx_net_cb, endp); break; case MGCP_BSC: conn->type = MGCP_OSMUX_BSC; + osmux_xfrm_output_set_tx_cb(&conn->osmux.out, + scheduled_tx_bts_cb, endp); break; } @@ -576,6 +576,11 @@ void osmux_disable_conn(struct mgcp_conn_rtp *conn) LOGP(DLMGCP, LOGL_INFO, "Releasing connection %s using Osmux CID %u\n", conn->conn->id, conn->osmux.cid); + + /* We are closing, we don't need pending RTP packets to be transmitted */ + osmux_xfrm_output_set_tx_cb(&conn->osmux.out, NULL, NULL); + osmux_xfrm_output_flush(&conn->osmux.out); + osmux_xfrm_input_close_circuit(conn->osmux.in, conn->osmux.cid); conn->osmux.state = OSMUX_STATE_DISABLED; conn->osmux.cid = -1; 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; diff --git a/src/libosmo-mgcp/mgcp_sdp.c b/src/libosmo-mgcp/mgcp_sdp.c index 52b4df4..102c8c3 100644 --- a/src/libosmo-mgcp/mgcp_sdp.c +++ b/src/libosmo-mgcp/mgcp_sdp.c @@ -25,9 +25,13 @@ #include <osmocom/mgcp/mgcp_internal.h> #include <osmocom/mgcp/mgcp_msg.h> #include <osmocom/mgcp/mgcp_endp.h> +#include <osmocom/mgcp/mgcp_codec.h> #include <errno.h> +/* A struct to store intermediate parsing results. The function + * mgcp_parse_sdp_data() is using it as temporary storage for parsing the SDP + * codec information. */ struct sdp_rtp_map { /* the type */ int payload_type; @@ -40,89 +44,8 @@ struct sdp_rtp_map { int channels; }; -/*! Set codec configuration depending on payload type and codec name. - * \param[in] ctx talloc context. - * \param[out] codec configuration (caller provided memory). - * \param[in] payload_type codec type id (e.g. 3 for GSM, -1 when undefined). - * \param[in] audio_name audio codec name (e.g. "GSM/8000/1"). - * \returns 0 on success, -1 on failure. */ -int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec, - int payload_type, const char *audio_name) -{ - int rate = codec->rate; - int channels = codec->channels; - char audio_codec[64]; - - talloc_free(codec->subtype_name); - codec->subtype_name = NULL; - talloc_free(codec->audio_name); - codec->audio_name = NULL; - - if (payload_type != PTYPE_UNDEFINED) - codec->payload_type = payload_type; - - if (!audio_name) { - switch (payload_type) { - case 0: - audio_name = "PCMU/8000/1"; - break; - case 3: - audio_name = "GSM/8000/1"; - break; - case 8: - audio_name = "PCMA/8000/1"; - break; - case 18: - audio_name = "G729/8000/1"; - break; - default: - /* Payload type is unknown, don't change rate and - * channels. */ - /* TODO: return value? */ - return 0; - } - } - - if (sscanf(audio_name, "%63[^/]/%d/%d", - audio_codec, &rate, &channels) < 1) - return -EINVAL; - - codec->rate = rate; - codec->channels = channels; - codec->subtype_name = talloc_strdup(ctx, audio_codec); - codec->audio_name = talloc_strdup(ctx, audio_name); - - if (!strcmp(audio_codec, "G729")) { - codec->frame_duration_num = 10; - codec->frame_duration_den = 1000; - } else { - codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; - codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; - } - - if (payload_type < 0) { - payload_type = 96; - if (rate == 8000 && channels == 1) { - if (!strcmp(audio_codec, "GSM")) - payload_type = 3; - else if (!strcmp(audio_codec, "PCMA")) - payload_type = 8; - else if (!strcmp(audio_codec, "PCMU")) - payload_type = 0; - else if (!strcmp(audio_codec, "G729")) - payload_type = 18; - } - - codec->payload_type = payload_type; - } - - if (channels != 1) - LOGP(DLMGCP, LOGL_NOTICE, - "Channels != 1 in SDP: '%s'\n", audio_name); - - return 0; -} - +/* Helper function to extrapolate missing codec parameters in a codec mao from + * an already filled in payload_type, called from: mgcp_parse_sdp_data() */ static void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used) { int i; @@ -149,10 +72,16 @@ static void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used) codecs[i].rate = 8000; codecs[i].channels = 1; break; + default: + codecs[i].codec_name = NULL; + codecs[i].rate = 0; + codecs[i].channels = 0; } } } +/* Helper function to update codec map information with additional data from + * SDP, called from: mgcp_parse_sdp_data() */ static void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, int payload, const char *audio_name) { @@ -162,8 +91,13 @@ static void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, char audio_codec[64]; int rate = -1; int channels = -1; + + /* Note: We can only update payload codecs that already exist + * in our codec list. If we get an unexpected payload type, + * we just drop it */ if (codecs[i].payload_type != payload) continue; + if (sscanf(audio_name, "%63[^/]/%d/%d", audio_codec, &rate, &channels) < 1) { LOGP(DLMGCP, LOGL_ERROR, "Failed to parse '%s'\n", @@ -182,43 +116,72 @@ static void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, audio_name); } -/* Check if the codec matches what is set up in the trunk config */ -static int is_codec_compatible(const struct mgcp_endpoint *endp, - const struct sdp_rtp_map *codec) +/* Extract payload types from SDP, also check for duplicates */ +static int pt_from_sdp(void *ctx, struct sdp_rtp_map *codecs, + unsigned int codecs_len, char *sdp) { - char *codec_str; - char audio_codec[64]; + char *str; + char *str_ptr; + char *pt_str; + unsigned int pt; + unsigned int count = 0; + unsigned int i; + + str = talloc_zero_size(ctx, strlen(sdp) + 1); + str_ptr = str; + strcpy(str_ptr, sdp); + + str_ptr = strstr(str_ptr, "RTP/AVP "); + if (!str_ptr) + goto exit; + + pt_str = strtok(str_ptr, " "); + if (!pt_str) + goto exit; + + while (1) { + /* Do not allow excessive payload types */ + if (count > codecs_len) + goto error; + + pt_str = strtok(NULL, " "); + if (!pt_str) + break; - if (!codec->codec_name) - return 0; + pt = atoi(pt_str); - /* GSM, GSM/8000 and GSM/8000/1 should all be compatible... - * let's go by name first. */ - codec_str = endp->tcfg->audio_name; - if (sscanf(codec_str, "%63[^/]/%*d/%*d", audio_codec) < 1) - return 0; + /* Do not allow duplicate payload types */ + for (i = 0; i < count; i++) + if (codecs[i].payload_type == pt) + goto error; + + codecs[count].payload_type = pt; + count++; + } - return strcasecmp(audio_codec, codec->codec_name) == 0; +exit: + talloc_free(str); + return count; +error: + talloc_free(str); + return -EINVAL; } /*! Analyze SDP input string. * \param[in] endp trunk endpoint. * \param[out] conn associated rtp connection. * \param[out] caller provided memory to store the parsing results. - * \returns 0 on success, -1 on failure. * * Note: In conn (conn->end) the function returns the packet duration, - * the rtp port and the rtcp port */ + * rtp port, rtcp port and the codec information. + * \returns 0 on success, -1 on failure. */ int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp, - struct mgcp_conn_rtp *conn, - struct mgcp_parse_data *p) + struct mgcp_conn_rtp *conn, struct mgcp_parse_data *p) { - struct sdp_rtp_map codecs[10]; - int codecs_used = 0; + struct sdp_rtp_map codecs[MGCP_MAX_CODECS]; + unsigned int codecs_used = 0; char *line; - int maxptime = -1; - int i; - int codecs_assigned = 0; + unsigned int i; void *tmp_ctx = talloc_new(NULL); struct mgcp_rtp_end *rtp; @@ -255,30 +218,21 @@ int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp, rtp->packet_duration_ms = 0; else rtp->packet_duration_ms = ptime; - } else if (sscanf(line, "a=maxptime:%d", &ptime2) - == 1) { - maxptime = ptime2; + } else if (sscanf(line, "a=maxptime:%d", &ptime2) == 1) { + rtp->maximum_packet_time = ptime2; } break; case 'm': - rc = sscanf(line, - "m=audio %d RTP/AVP %d %d %d %d %d %d %d %d %d %d", - &port, &codecs[0].payload_type, - &codecs[1].payload_type, - &codecs[2].payload_type, - &codecs[3].payload_type, - &codecs[4].payload_type, - &codecs[5].payload_type, - &codecs[6].payload_type, - &codecs[7].payload_type, - &codecs[8].payload_type, - &codecs[9].payload_type); - if (rc >= 2) { + rc = sscanf(line, "m=audio %d RTP/AVP", &port); + if (rc == 1) { rtp->rtp_port = htons(port); rtp->rtcp_port = htons(port + 1); - codecs_used = rc - 1; - codecs_initialize(tmp_ctx, codecs, codecs_used); } + + rc = pt_from_sdp(conn->conn, codecs, + ARRAY_SIZE(codecs), line); + if (rc > 0) + codecs_used = rc; break; case 'c': @@ -299,43 +253,37 @@ int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp, break; } } + OSMO_ASSERT(codecs_used <= MGCP_MAX_CODECS); - /* Now select the primary and alt_codec */ - for (i = 0; i < codecs_used && codecs_assigned < 2; ++i) { - struct mgcp_rtp_codec *codec = codecs_assigned == 0 ? - &rtp->codec : &rtp->alt_codec; - - if (endp->tcfg->no_audio_transcoding && - !is_codec_compatible(endp, &codecs[i])) { - LOGP(DLMGCP, LOGL_NOTICE, "Skipping codec %s\n", - codecs[i].codec_name); - continue; - } + /* So far we have only set the payload type in the codec struct. Now we + * fill up the remaining fields of the codec description with some default + * information */ + codecs_initialize(tmp_ctx, codecs, codecs_used); - mgcp_set_audio_info(p->cfg, codec, - codecs[i].payload_type, codecs[i].map_line); - codecs_assigned += 1; + /* Store parsed codec information */ + for (i = 0; i < codecs_used; i++) { + rc = mgcp_codec_add(conn, codecs[i].payload_type, codecs[i].map_line); + if (rc < 0) + LOGP(DLMGCP, LOGL_NOTICE, "endpoint:0x%x, failed to add codec\n", ENDPOINT_NUMBER(p->endp)); } - if (codecs_assigned > 0) { - /* TODO/XXX: Store this per codec and derive it on use */ - if (maxptime >= 0 && maxptime * rtp->codec.frame_duration_den > - rtp->codec.frame_duration_num * 1500) { - /* more than 1 frame */ - rtp->packet_duration_ms = 0; - } + talloc_free(tmp_ctx); - LOGP(DLMGCP, LOGL_NOTICE, - "Got media info via SDP: port %d, payload %d (%s), " - "duration %d, addr %s\n", - ntohs(rtp->rtp_port), rtp->codec.payload_type, - rtp->codec.subtype_name ? rtp-> - codec.subtype_name : "unknown", rtp->packet_duration_ms, - inet_ntoa(rtp->addr)); + LOGP(DLMGCP, LOGL_NOTICE, + "Got media info via SDP: port:%d, addr:%s, duration:%d, payload-types:", + ntohs(rtp->rtp_port), inet_ntoa(rtp->addr), + rtp->packet_duration_ms); + if (codecs_used == 0) + LOGPC(DLMGCP, LOGL_NOTICE, "none"); + for (i = 0; i < codecs_used; i++) { + LOGPC(DLMGCP, LOGL_NOTICE, "%d=%s", + rtp->codecs[i].payload_type, + rtp->codecs[i].subtype_name ? rtp-> codecs[i].subtype_name : "unknown"); + LOGPC(DLMGCP, LOGL_NOTICE, " "); } + LOGPC(DLMGCP, LOGL_NOTICE, "\n"); - talloc_free(tmp_ctx); - return codecs_assigned > 0; + return 0; } /*! Generate SDP response string. @@ -380,7 +328,9 @@ int mgcp_write_response_sdp(const struct mgcp_endpoint *endp, if (rc < 0) goto buffer_too_small; - if (audio_name && endp->tcfg->audio_send_name) { + /* FIXME: Check if the payload type is from the static range, + * if yes, omitthe a=rtpmap since it is unnecessary */ + if (audio_name && endp->tcfg->audio_send_name && (payload_type >= 96 && payload_type <= 127)) { rc = msgb_printf(sdp, "a=rtpmap:%d %s\r\n", payload_type, audio_name); diff --git a/src/libosmo-mgcp/mgcp_stat.c b/src/libosmo-mgcp/mgcp_stat.c index 581130c..3685cfe 100644 --- a/src/libosmo-mgcp/mgcp_stat.c +++ b/src/libosmo-mgcp/mgcp_stat.c @@ -25,12 +25,14 @@ #include <osmocom/mgcp/mgcp_stat.h> #include <osmocom/mgcp/mgcp_endp.h> #include <limits.h> +#include <inttypes.h> /* Helper function for mgcp_format_stats_rtp() to calculate packet loss */ -void calc_loss(struct mgcp_rtp_state *state, - struct mgcp_rtp_end *end, uint32_t *expected, - int *loss) +void calc_loss(struct mgcp_conn_rtp *conn, uint32_t *expected, int *loss) { + struct mgcp_rtp_state *state = &conn->state; + struct rate_ctr *packets_rx = &conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]; + *expected = state->stats.cycles + state->stats.max_seq; *expected = *expected - state->stats.base_seq + 1; @@ -44,8 +46,8 @@ void calc_loss(struct mgcp_rtp_state *state, * Make sure the sign is correct and use the biggest * positive/negative number that fits. */ - *loss = *expected - end->stats.packets_rx; - if (*expected < end->stats.packets_rx) { + *loss = *expected - packets_rx->current; + if (*expected < packets_rx->current) { if (*loss > 0) *loss = INT_MIN; } else { @@ -70,13 +72,18 @@ static void mgcp_format_stats_rtp(char *str, size_t str_len, int ploss; int nchars; - calc_loss(&conn->state, &conn->end, &expected, &ploss); + struct rate_ctr *packets_rx = &conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]; + struct rate_ctr *octets_rx = &conn->rate_ctr_group->ctr[RTP_OCTETS_RX_CTR]; + struct rate_ctr *packets_tx = &conn->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]; + struct rate_ctr *octets_tx = &conn->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR]; + + calc_loss(conn, &expected, &ploss); jitter = calc_jitter(&conn->state); nchars = snprintf(str, str_len, - "\r\nP: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u", - conn->end.stats.packets_tx, conn->end.stats.octets_tx, - conn->end.stats.packets_rx, conn->end.stats.octets_rx, + "\r\nP: PS=%" PRIu64 ", OS=%" PRIu64 ", PR=%" PRIu64 ", OR=%" PRIu64 ", PL=%d, JI=%u", + packets_tx->current, octets_tx->current, + packets_rx->current, octets_rx->current, ploss, jitter); if (nchars < 0 || nchars >= str_len) goto truncate; @@ -87,9 +94,9 @@ static void mgcp_format_stats_rtp(char *str, size_t str_len, if (conn->conn->endp->cfg->osmux != OSMUX_USAGE_OFF) { /* Error Counter */ nchars = snprintf(str, str_len, - "\r\nX-Osmo-CP: EC TI=%u, TO=%u", - conn->state.in_stream.err_ts_counter, - conn->state.out_stream.err_ts_counter); + "\r\nX-Osmo-CP: EC TI=%" PRIu64 ", TO=%" PRIu64, + conn->state.in_stream.err_ts_ctr->current, + conn->state.out_stream.err_ts_ctr->current); if (nchars < 0 || nchars >= str_len) goto truncate; diff --git a/src/libosmo-mgcp/mgcp_vty.c b/src/libosmo-mgcp/mgcp_vty.c index 14ecd17..ad462b7 100644 --- a/src/libosmo-mgcp/mgcp_vty.c +++ b/src/libosmo-mgcp/mgcp_vty.c @@ -30,6 +30,7 @@ #include <osmocom/mgcp/mgcp_endp.h> #include <string.h> +#include <inttypes.h> #define RTCP_OMIT_STR "Drop RTCP packets in both directions\n" #define RTP_PATCH_STR "Modify RTP packet header in both directions\n" @@ -154,22 +155,27 @@ static int config_write_mgcp(struct vty *vty) return CMD_SUCCESS; } -static void dump_rtp_end(struct vty *vty, struct mgcp_rtp_state *state, - struct mgcp_rtp_end *end) +static void dump_rtp_end(struct vty *vty, struct mgcp_conn_rtp *conn) { - struct mgcp_rtp_codec *codec = &end->codec; + struct mgcp_rtp_state *state = &conn->state; + struct mgcp_rtp_end *end = &conn->end; + struct mgcp_rtp_codec *codec = end->codec; + struct rate_ctr *dropped_packets; + + dropped_packets = &conn->rate_ctr_group->ctr[RTP_DROPPED_PACKETS_CTR]; vty_out(vty, - " Timestamp Errs: %d->%d%s" - " Dropped Packets: %d%s" + " Timestamp Errs: %" PRIu64 "->%" PRIu64 "%s" + " Dropped Packets: %" PRIu64 "%s" " Payload Type: %d Rate: %u Channels: %d %s" " Frame Duration: %u Frame Denominator: %u%s" " FPP: %d Packet Duration: %u%s" " FMTP-Extra: %s Audio-Name: %s Sub-Type: %s%s" " Output-Enabled: %d Force-PTIME: %d%s", - state->in_stream.err_ts_counter, - state->out_stream.err_ts_counter, VTY_NEWLINE, - end->stats.dropped_packets, VTY_NEWLINE, + state->in_stream.err_ts_ctr->current, + state->out_stream.err_ts_ctr->current, + VTY_NEWLINE, + dropped_packets->current, VTY_NEWLINE, codec->payload_type, codec->rate, codec->channels, VTY_NEWLINE, codec->frame_duration_num, codec->frame_duration_den, VTY_NEWLINE, end->frames_per_packet, end->packet_duration_ms, @@ -207,8 +213,7 @@ static void dump_trunk(struct vty *vty, struct mgcp_trunk_config *cfg, * connection types (E1) as soon as * the implementation is available */ if (conn->type == MGCP_CONN_TYPE_RTP) { - dump_rtp_end(vty, &conn->u.rtp.state, - &conn->u.rtp.end); + dump_rtp_end(vty, &conn->u.rtp); } } } @@ -281,13 +286,6 @@ DEFUN(cfg_mgcp_bind_early, return CMD_WARNING; } -static void parse_range(struct mgcp_port_range *range, const char **argv) -{ - range->range_start = atoi(argv[0]); - range->range_end = atoi(argv[1]); - range->last_port = g_cfg->net_ports.range_start; -} - #define RTP_STR "RTP configuration\n" #define UDP_PORT_STR "UDP Port number\n" #define NET_START_STR "First UDP port allocated\n" @@ -296,11 +294,38 @@ static void parse_range(struct mgcp_port_range *range, const char **argv) DEFUN(cfg_mgcp_rtp_port_range, cfg_mgcp_rtp_port_range_cmd, - "rtp port-range <0-65534> <0-65534>", + "rtp port-range <1024-65534> <1025-65535>", RTP_STR "Range of ports to use for the NET side\n" RANGE_START_STR RANGE_END_STR) { - parse_range(&g_cfg->net_ports, argv); + int start; + int end; + + start = atoi(argv[0]); + end = atoi(argv[1]); + + if (end < start) { + vty_out(vty, "range end port (%i) must be greater than the range start port (%i)!%s", + end, start, VTY_NEWLINE); + return CMD_WARNING; + } + + if (start & 1) { + vty_out(vty, "range must begin at an even port number, autocorrecting port (%i) to: %i%s", + start, start & 0xFFFE, VTY_NEWLINE); + start &= 0xFFFE; + } + + if ((end & 1) == 0) { + vty_out(vty, "range must end at an odd port number, autocorrecting port (%i) to: %i%s", + end, end | 1, VTY_NEWLINE); + end |= 1; + } + + g_cfg->net_ports.range_start = start; + g_cfg->net_ports.range_end = end; + g_cfg->net_ports.last_port = g_cfg->net_ports.range_start; + return CMD_SUCCESS; } ALIAS_DEPRECATED(cfg_mgcp_rtp_port_range, diff --git a/tests/mgcp/mgcp_test.c b/tests/mgcp/mgcp_test.c index f6c421a..56d0cee 100644 --- a/tests/mgcp/mgcp_test.c +++ b/tests/mgcp/mgcp_test.c @@ -26,6 +26,8 @@ #include <osmocom/mgcp/mgcp_stat.h> #include <osmocom/mgcp/mgcp_msg.h> #include <osmocom/mgcp/mgcp_endp.h> +#include <osmocom/mgcp/mgcp_sdp.h> +#include <osmocom/mgcp/mgcp_codec.h> #include <osmocom/core/application.h> #include <osmocom/core/talloc.h> @@ -156,8 +158,8 @@ static void test_strline(void) "s=-\r\n" \ "c=IN IP4 0.0.0.0\r\n" \ "t=0 0\r\n" \ - "m=audio 16002 RTP/AVP 96\r\n" \ - "a=rtpmap:96 AMR\r\n" \ + "m=audio 16002 RTP/AVP 112\r\n" \ + "a=rtpmap:112 AMR\r\n" \ "a=ptime:40\r\n" #define MDCX4_PT1 \ @@ -404,7 +406,7 @@ static void test_strline(void) "v=0\r\n" \ "o=- 1439038275 1439038275 IN IP4 192.168.181.247\r\n" \ "s=-\r\nc=IN IP4 192.168.181.247\r\n" \ - "t=0 0\r\nm=audio 29084 RTP/AVP 255 0 8 3 18 4 96 97 101\r\n" \ + "t=0 0\r\nm=audio 29084 RTP/AVP 0 8 3 18 4 96 97 101\r\n" \ "a=rtpmap:0 PCMU/8000\r\n" \ "a=rtpmap:8 PCMA/8000\r\n" \ "a=rtpmap:3 gsm/8000\r\n" \ @@ -425,7 +427,24 @@ static void test_strline(void) "I: %s\r\n" \ "\r\n" \ "c=IN IP4 8.8.8.8\r\n" \ - "m=audio 16434 RTP/AVP 255\r\n" + "m=audio 16434 RTP/AVP 3\r\n" + +#define CRCX_NO_LCO_NO_SDP \ + "CRCX 2 6@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" + +#define CRCX_NO_LCO_NO_SDP_RET \ + "200 2 OK\r\n" \ + "I: %s\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "o=- %s 23 IN IP4 0.0.0.0\r\n" \ + "s=-\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 16008 RTP/AVP 0\r\n" \ + "a=ptime:20\r\n" struct mgcp_test { const char *name; @@ -462,6 +481,7 @@ static const struct mgcp_test tests[] = { {"MDCX3", MDCX3, MDCX3_FMTP_RET, PTYPE_NONE,.extra_fmtp = "a=fmtp:126 0/1/2"}, {"DLCX", DLCX, DLCX_RET, PTYPE_IGNORE,.extra_fmtp = "a=fmtp:126 0/1/2"}, + {"CRCX", CRCX_NO_LCO_NO_SDP, CRCX_NO_LCO_NO_SDP_RET, 97}, }; static const struct mgcp_test retransmit[] = { @@ -764,14 +784,14 @@ static void test_messages(void) fprintf(stderr, "endpoint %d: " "payload type %d (expected %d)\n", last_endpoint, - conn->end.codec.payload_type, t->ptype); + conn->end.codec->payload_type, t->ptype); if (t->ptype != PTYPE_IGNORE) - OSMO_ASSERT(conn->end.codec.payload_type == + OSMO_ASSERT(conn->end.codec->payload_type == t->ptype); /* Reset them again for next test */ - conn->end.codec.payload_type = PTYPE_NONE; + conn->end.codec->payload_type = PTYPE_NONE; } } @@ -917,23 +937,43 @@ static const struct pl_test pl_test_dat[] = { static void test_packet_loss_calc(void) { int i; + struct mgcp_endpoint endp; + struct mgcp_trunk_config trunk; + printf("Testing packet loss calculation.\n"); + memset(&endp, 0, sizeof(endp)); + memset(&trunk, 0, sizeof(trunk)); + + endp.type = &ep_typeset.rtp; + trunk.vty_number_endpoints = 1; + trunk.endpoints = &endp; + endp.tcfg = &trunk; + INIT_LLIST_HEAD(&endp.conns); + for (i = 0; i < ARRAY_SIZE(pl_test_dat); ++i) { uint32_t expected; int loss; - struct mgcp_rtp_state state; - struct mgcp_rtp_end rtp; - memset(&state, 0, sizeof(state)); - memset(&rtp, 0, sizeof(rtp)); - state.stats.initialized = 1; - state.stats.base_seq = pl_test_dat[i].base_seq; - state.stats.max_seq = pl_test_dat[i].max_seq; - state.stats.cycles = pl_test_dat[i].cycles; + struct mgcp_conn_rtp *conn = NULL; + struct mgcp_conn *_conn = NULL; + struct mgcp_rtp_state *state; + struct rate_ctr *packets_rx; - rtp.stats.packets_rx = pl_test_dat[i].packets; - calc_loss(&state, &rtp, &expected, &loss); + _conn = + mgcp_conn_alloc(NULL, &endp, MGCP_CONN_TYPE_RTP, + "test-connection"); + conn = mgcp_conn_get_rtp(&endp, _conn->id); + state = &conn->state; + packets_rx = &conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]; + + state->stats.initialized = 1; + state->stats.base_seq = pl_test_dat[i].base_seq; + state->stats.max_seq = pl_test_dat[i].max_seq; + state->stats.cycles = pl_test_dat[i].cycles; + + packets_rx->current = pl_test_dat[i].packets; + calc_loss(conn, &expected, &loss); if (loss != pl_test_dat[i].loss || expected != pl_test_dat[i].expected) { @@ -942,7 +982,10 @@ static void test_packet_loss_calc(void) i, loss, pl_test_dat[i].loss, expected, pl_test_dat[i].expected); } + + mgcp_conn_free_all(&endp); } + } int mgcp_parse_stats(struct msgb *msg, uint32_t *ps, uint32_t *os, @@ -1129,10 +1172,12 @@ static void test_packet_error_detection(int patch_ssrc, int patch_ts) uint32_t last_ssrc = 0; uint32_t last_timestamp = 0; uint32_t last_seqno = 0; - int last_in_ts_err_cnt = 0; - int last_out_ts_err_cnt = 0; + uint64_t last_in_ts_err_cnt = 0; + uint64_t last_out_ts_err_cnt = 0; struct mgcp_conn_rtp *conn = NULL; struct mgcp_conn *_conn = NULL; + struct rate_ctr test_ctr_in; + struct rate_ctr test_ctr_out; printf("Testing packet error detection%s%s.\n", patch_ssrc ? ", patch SSRC" : "", @@ -1142,6 +1187,11 @@ static void test_packet_error_detection(int patch_ssrc, int patch_ts) memset(&endp, 0, sizeof(endp)); memset(&state, 0, sizeof(state)); + memset(&test_ctr_in, 0, sizeof(test_ctr_in)); + memset(&test_ctr_out, 0, sizeof(test_ctr_out)); + state.in_stream.err_ts_ctr = &test_ctr_in; + state.out_stream.err_ts_ctr = &test_ctr_out; + endp.type = &ep_typeset.rtp; trunk.vty_number_endpoints = 1; @@ -1160,7 +1210,8 @@ static void test_packet_error_detection(int patch_ssrc, int patch_ts) rtp = &conn->end; - rtp->codec.payload_type = 98; + OSMO_ASSERT(mgcp_codec_add(conn, PTYPE_UNDEFINED, "AMR/8000/1") == 0); + rtp->codec = &rtp->codecs[0]; for (i = 0; i < ARRAY_SIZE(test_rtp_packets1); ++i) { struct rtp_packet_info *info = test_rtp_packets1 + i; @@ -1186,18 +1237,18 @@ static void test_packet_error_detection(int patch_ssrc, int patch_ts) state.in_stream.last_tsdelta, state.in_stream.last_seq); printf("Out TS change: %d, dTS: %d, Seq change: %d, " - "TS Err change: in %+d, out %+d\n", + "TS Err change: in +%u, out +%u\n", state.out_stream.last_timestamp - last_timestamp, state.out_stream.last_tsdelta, state.out_stream.last_seq - last_seqno, - state.in_stream.err_ts_counter - last_in_ts_err_cnt, - state.out_stream.err_ts_counter - last_out_ts_err_cnt); + (unsigned int) (state.in_stream.err_ts_ctr->current - last_in_ts_err_cnt), + (unsigned int) (state.out_stream.err_ts_ctr->current - last_out_ts_err_cnt)); printf("Stats: Jitter = %u, Transit = %d\n", calc_jitter(&state), state.stats.transit); - last_in_ts_err_cnt = state.in_stream.err_ts_counter; - last_out_ts_err_cnt = state.out_stream.err_ts_counter; + last_in_ts_err_cnt = state.in_stream.err_ts_ctr->current; + last_out_ts_err_cnt = state.out_stream.err_ts_ctr->current; last_timestamp = state.out_stream.last_timestamp; last_seqno = state.out_stream.last_seq; } @@ -1236,8 +1287,7 @@ static void test_multilple_codec(void) endp = &cfg->trunk.endpoints[last_endpoint]; conn = mgcp_conn_get_rtp(endp, conn_id); OSMO_ASSERT(conn); - OSMO_ASSERT(conn->end.codec.payload_type == 18); - OSMO_ASSERT(conn->end.alt_codec.payload_type == 97); + OSMO_ASSERT(conn->end.codec->payload_type == 18); /* Allocate 2@mgw with three codecs, last one ignored */ last_endpoint = -1; @@ -1252,10 +1302,14 @@ static void test_multilple_codec(void) endp = &cfg->trunk.endpoints[last_endpoint]; conn = mgcp_conn_get_rtp(endp, conn_id); OSMO_ASSERT(conn); - OSMO_ASSERT(conn->end.codec.payload_type == 18); - OSMO_ASSERT(conn->end.alt_codec.payload_type == 97); - - /* Allocate 3@mgw with no codecs, check for PT == -1 */ + OSMO_ASSERT(conn->end.codec->payload_type == 18); + + /* Allocate 3@mgw with no codecs, check for PT == 0 */ + /* Note: It usually makes no sense to leave the payload type list + * out. However RFC 2327 does not clearly forbid this case and + * it makes and since we already decided in OS#2658 that a missing + * LCO should pick a sane default codec, it makes sense to expect + * the same behaviour if SDP lacks proper payload type information */ last_endpoint = -1; inp = create_msg(CRCX_MULT_3, NULL); resp = mgcp_handle_message(cfg, inp); @@ -1268,8 +1322,7 @@ static void test_multilple_codec(void) endp = &cfg->trunk.endpoints[last_endpoint]; conn = mgcp_conn_get_rtp(endp, conn_id); OSMO_ASSERT(conn); - OSMO_ASSERT(conn->end.codec.payload_type == -1); - OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); + OSMO_ASSERT(conn->end.codec->payload_type == 0); /* Allocate 4@mgw with a single codec */ last_endpoint = -1; @@ -1284,8 +1337,7 @@ static void test_multilple_codec(void) endp = &cfg->trunk.endpoints[last_endpoint]; conn = mgcp_conn_get_rtp(endp, conn_id); OSMO_ASSERT(conn); - OSMO_ASSERT(conn->end.codec.payload_type == 18); - OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); + OSMO_ASSERT(conn->end.codec->payload_type == 18); /* Allocate 5@mgw at select GSM.. */ last_endpoint = -1; @@ -1303,8 +1355,7 @@ static void test_multilple_codec(void) endp = &cfg->trunk.endpoints[last_endpoint]; conn = mgcp_conn_get_rtp(endp, conn_id); OSMO_ASSERT(conn); - OSMO_ASSERT(conn->end.codec.payload_type == 3); - OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); + OSMO_ASSERT(conn->end.codec->payload_type == 3); inp = create_msg(MDCX_NAT_DUMMY, conn_id); last_endpoint = -1; @@ -1315,8 +1366,7 @@ static void test_multilple_codec(void) endp = &cfg->trunk.endpoints[last_endpoint]; conn = mgcp_conn_get_rtp(endp, conn_id); OSMO_ASSERT(conn); - OSMO_ASSERT(conn->end.codec.payload_type == 3); - OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); + OSMO_ASSERT(conn->end.codec->payload_type == 3); OSMO_ASSERT(conn->end.rtp_port == htons(16434)); memset(&addr, 0, sizeof(addr)); inet_aton("8.8.8.8", &addr); @@ -1346,8 +1396,7 @@ static void test_multilple_codec(void) endp = &cfg->trunk.endpoints[last_endpoint]; conn = mgcp_conn_get_rtp(endp, conn_id); OSMO_ASSERT(conn); - OSMO_ASSERT(conn->end.codec.payload_type == 255); - OSMO_ASSERT(conn->end.alt_codec.payload_type == 0); + OSMO_ASSERT(conn->end.codec->payload_type == 0); talloc_free(cfg); } @@ -1465,6 +1514,108 @@ const struct log_info log_info = { .num_cat = ARRAY_SIZE(log_categories), }; +static void test_get_lco_identifier(void) +{ + char *test; + printf("Testing get_lco_identifier()\n"); + + /* Normal case at the beginning */ + test = "p:10, a:PCMU"; + printf("%s -> %s\n", test, get_lco_identifier(test)); + + test = "p:10, a:PCMU"; + printf("%s -> %s\n", test, get_lco_identifier(test)); + + /* Begin parsing in the middle of the value part of + * the previous LCO option value */ + test = "XXXX, p:10, a:PCMU"; + printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); + + test = "XXXX,p:10,a:PCMU"; + printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); + + test = "10,a:PCMU"; + printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); + + test = "10, a:PCMU"; + printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); + + test = "10,a: PCMU"; + printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); + + test = "10 ,a: PCMU"; + printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); + + /* Begin parsing right at the end of the previous LCO + * option value */ + test = ", a:PCMU"; + printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); + + test = " a:PCMU"; + printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); + + /* Empty string, result should be (null) */ + test = ""; + printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); + + /* Missing colons, result should be (null) */ + test = "p10, aPCMU"; + printf("%s -> %s\n", test, get_lco_identifier(test)); + + /* Colon separated from the identifier, result should be (null) */ + test = "10,a :PCMU"; + printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); +} + +static void test_check_local_cx_options(void *ctx) +{ + /* Legal cases */ + OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:PCMU") == 0); + OSMO_ASSERT(check_local_cx_options(ctx, "a:PCMU") == 0); + OSMO_ASSERT(check_local_cx_options(ctx, "a:PCMU, p:10, IN:10") == 0); + OSMO_ASSERT(check_local_cx_options(ctx, "one:AAA, two:BB, tree:C") == + 0); + OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:PCMU") == 0); + OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:G726-32") == 0); + OSMO_ASSERT(check_local_cx_options(ctx, "p:10-20, b:64") == 0); + OSMO_ASSERT(check_local_cx_options(ctx, "b:32-64, e:off") == 0); + OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:PCMU;G726-32") == 0); + + /* Illegal spaces before and after colon */ + OSMO_ASSERT(check_local_cx_options(ctx, "a:PCMU, p :10") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "a :PCMU, p:10") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "p: 10, a:PCMU") == -1); + + /* Check if multiple appearances of LCOs are rejected */ + OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:PCMU, p:10") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:PCMU, a:PCMU, p:10") == + -1); + OSMO_ASSERT(check_local_cx_options(ctx, "p:10, p:10") == -1); + + /* Check if empty LCO are rejected */ + OSMO_ASSERT(check_local_cx_options(ctx, "p: , a:PCMU") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "p: , a: PCMU") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a: PCMU") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "p:, a:PCMU") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:") == -1); + + /* Garbeled beginning and ends */ + OSMO_ASSERT(check_local_cx_options(ctx, ":10, a:10") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "10, a:PCMU") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, ", a:PCMU") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, " a:PCMU") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "a:PCMU,") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "a:PCMU, ") == -1); + + /* Illegal strings */ + OSMO_ASSERT(check_local_cx_options(ctx, " ") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, "AAA") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, ":,") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, ",:") == -1); + OSMO_ASSERT(check_local_cx_options(ctx, ",,,") == -1); +} + int main(int argc, char **argv) { void *ctx = talloc_named_const(NULL, 0, "mgcp_test"); @@ -1486,6 +1637,8 @@ int main(int argc, char **argv) test_no_cycle(); test_no_name(); test_osmux_cid(); + test_get_lco_identifier(); + test_check_local_cx_options(ctx); OSMO_ASSERT(talloc_total_size(msgb_ctx) == 0); OSMO_ASSERT(talloc_total_blocks(msgb_ctx) == 1); diff --git a/tests/mgcp/mgcp_test.ok b/tests/mgcp/mgcp_test.ok index d2879ad..9838f4d 100644 --- a/tests/mgcp/mgcp_test.ok +++ b/tests/mgcp/mgcp_test.ok @@ -408,6 +408,21 @@ Response matches our expectations. Testing CRCX creating message from statically defined input: ---------8<--------- +CRCX 2 6@mgw MGCP 1.0
+M: recvonly
+C: 2
+ +---------8<--------- +checking response: +using message with patched conn_id for comparison +Response matches our expectations. +(response does not contain a connection id) +Dummy packets: 2 + +================================================ +Testing CRCX +creating message from statically defined input: +---------8<--------- CRCX 2 1@mgw MGCP 1.0
M: recvonly
C: 2
@@ -1031,7 +1046,7 @@ o=- 1439038275 1439038275 IN IP4 192.168.181.247 s=-
c=IN IP4 192.168.181.247
t=0 0
-m=audio 29084 RTP/AVP 255 0 8 3 18 4 96 97 101
+m=audio 29084 RTP/AVP 0 8 3 18 4 96 97 101
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:3 gsm/8000
@@ -1054,7 +1069,7 @@ C: 1355c6041e I: %s
c=IN IP4 8.8.8.8
-m=audio 16434 RTP/AVP 255
+m=audio 16434 RTP/AVP 3
---------8<--------- creating message from statically defined input: @@ -1069,7 +1084,7 @@ o=- 1439038275 1439038275 IN IP4 192.168.181.247 s=-
c=IN IP4 192.168.181.247
t=0 0
-m=audio 29084 RTP/AVP 255 0 8 3 18 4 96 97 101
+m=audio 29084 RTP/AVP 0 8 3 18 4 96 97 101
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:3 gsm/8000
@@ -1104,4 +1119,18 @@ a=ptime:40 checking response: using message with patched conn_id for comparison Response matches our expectations. +Testing get_lco_identifier() +p:10, a:PCMU -> p:10, a:PCMU +p:10, a:PCMU -> p:10, a:PCMU +'XXXX, p:10, a:PCMU' -> 'p:10, a:PCMU' +'XXXX,p:10,a:PCMU' -> 'p:10,a:PCMU' +'10,a:PCMU' -> 'a:PCMU' +'10, a:PCMU' -> 'a:PCMU' +'10,a: PCMU' -> 'a: PCMU' +'10 ,a: PCMU' -> 'a: PCMU' +', a:PCMU' -> 'a:PCMU' +' a:PCMU' -> 'a:PCMU' +'' -> '(null)' +p10, aPCMU -> (null) +'10,a :PCMU' -> '(null)' Done diff --git a/tests/mgcp_client/mgcp_client_test.c b/tests/mgcp_client/mgcp_client_test.c index 007b90c..9978f79 100644 --- a/tests/mgcp_client/mgcp_client_test.c +++ b/tests/mgcp_client/mgcp_client_test.c @@ -95,21 +95,26 @@ static int reply_to(mgcp_trans_id_t trans_id, int code, const char *comment, void test_response_cb(struct mgcp_response *response, void *priv) { + unsigned int i; OSMO_ASSERT(priv == mgcp); mgcp_response_parse_params(response); - printf("response cb received:\n" - " head.response_code = %d\n" - " head.trans_id = %u\n" - " head.comment = %s\n" - " audio_port = %u\n" - " audio_ip = %s\n", - response->head.response_code, - response->head.trans_id, - response->head.comment, - response->audio_port, - response->audio_ip - ); + printf("response cb received:\n"); + printf(" head.response_code = %d\n", response->head.response_code); + printf(" head.trans_id = %u\n", response->head.trans_id); + printf(" head.comment = %s\n", response->head.comment); + printf(" audio_port = %u\n", response->audio_port); + printf(" audio_ip = %s\n", response->audio_ip); + printf(" ptime = %u\n", response->ptime); + printf(" codecs_len = %u\n", response->codecs_len); + for(i=0;i<response->codecs_len;i++) + printf(" codecs[%u] = %u\n", i, response->codecs[i]); + printf(" ptmap_len = %u\n", response->ptmap_len); + for(i=0;i<response->ptmap_len;i++) { + printf(" ptmap[%u].codec = %u\n", i, response->ptmap[i].codec); + printf(" ptmap[%u].pt = %u\n", i, response->ptmap[i].pt); + } + } mgcp_trans_id_t dummy_mgcp_send(struct msgb *msg) @@ -149,8 +154,9 @@ void test_crcx(void) "s=-\r\n" "c=IN IP4 10.9.1.120\r\n" "t=0 0\r\n" - "m=audio 16002 RTP/AVP 98\r\n" - "a=rtpmap:98 AMR/8000\r\n" + "m=audio 16002 RTP/AVP 110 96\r\n" + "a=rtpmap:110 AMR/8000\r\n" + "a=rtpmap:96 GSM-EFR/8000\r\n" "a=ptime:20\r\n"); } @@ -166,7 +172,15 @@ void test_mgcp_msg(void) .audio_port = 1234, .call_id = 47, .conn_id = "11", - .conn_mode = MGCP_CONN_RECV_SEND + .conn_mode = MGCP_CONN_RECV_SEND, + .ptime = 20, + .codecs[0] = CODEC_GSM_8000_1, + .codecs[1] = CODEC_AMR_8000_1, + .codecs[2] = CODEC_GSMEFR_8000_1, + .codecs_len = 1, + .ptmap[0].codec = CODEC_GSMEFR_8000_1, + .ptmap[0].pt = 96, + .ptmap_len = 1 }; if (mgcp) @@ -183,6 +197,26 @@ void test_mgcp_msg(void) msg = mgcp_msg_gen(mgcp, &mgcp_msg); printf("%s\n", (char *)msg->data); + printf("Generated CRCX message (two codecs):\n"); + mgcp_msg.verb = MGCP_VERB_CRCX; + mgcp_msg.presence = + (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | + MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE); + mgcp_msg.codecs_len = 2; + msg = mgcp_msg_gen(mgcp, &mgcp_msg); + mgcp_msg.codecs_len = 1; + printf("%s\n", (char *)msg->data); + + printf("Generated CRCX message (three codecs, one with custom pt):\n"); + mgcp_msg.verb = MGCP_VERB_CRCX; + mgcp_msg.presence = + (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | + MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE); + mgcp_msg.codecs_len = 3; + msg = mgcp_msg_gen(mgcp, &mgcp_msg); + mgcp_msg.codecs_len = 1; + printf("%s\n", (char *)msg->data); + printf("Generated MDCX message:\n"); mgcp_msg.verb = MGCP_VERB_MDCX; mgcp_msg.presence = @@ -192,6 +226,28 @@ void test_mgcp_msg(void) msg = mgcp_msg_gen(mgcp, &mgcp_msg); printf("%s\n", (char *)msg->data); + printf("Generated MDCX message (two codecs):\n"); + mgcp_msg.verb = MGCP_VERB_MDCX; + mgcp_msg.presence = + (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | + MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE | + MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT); + mgcp_msg.codecs_len = 2; + msg = mgcp_msg_gen(mgcp, &mgcp_msg); + mgcp_msg.codecs_len = 1; + printf("%s\n", (char *)msg->data); + + printf("Generated MDCX message (three codecs, one with custom pt):\n"); + mgcp_msg.verb = MGCP_VERB_MDCX; + mgcp_msg.presence = + (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | + MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE | + MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT); + mgcp_msg.codecs_len = 3; + msg = mgcp_msg_gen(mgcp, &mgcp_msg); + mgcp_msg.codecs_len = 1; + printf("%s\n", (char *)msg->data); + printf("Generated DLCX message:\n"); mgcp_msg.verb = MGCP_VERB_DLCX; mgcp_msg.presence = @@ -242,6 +298,9 @@ void test_mgcp_client_cancel() .conn_mode = MGCP_CONN_RECV_SEND, .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE), + .ptime = 20, + .codecs[0] = CODEC_AMR_8000_1, + .codecs_len = 1 }; printf("\n%s():\n", __func__); @@ -376,6 +435,99 @@ void test_sdp_section_start() OSMO_ASSERT(!failures); } +static void test_map_pt_to_codec(void) +{ + /* Full form */ + OSMO_ASSERT(map_str_to_codec("PCMU/8000/1") == CODEC_PCMU_8000_1); + OSMO_ASSERT(map_str_to_codec("GSM/8000/1") == CODEC_GSM_8000_1); + OSMO_ASSERT(map_str_to_codec("PCMA/8000/1") == CODEC_PCMA_8000_1); + OSMO_ASSERT(map_str_to_codec("G729/8000/1") == CODEC_G729_8000_1); + OSMO_ASSERT(map_str_to_codec("GSM-EFR/8000/1") == CODEC_GSMEFR_8000_1); + OSMO_ASSERT(map_str_to_codec("GSM-HR-08/8000/1") == CODEC_GSMHR_8000_1); + OSMO_ASSERT(map_str_to_codec("AMR/8000/1") == CODEC_AMR_8000_1); + OSMO_ASSERT(map_str_to_codec("AMR-WB/16000/1") == CODEC_AMRWB_16000_1); + + /* Short form */ + OSMO_ASSERT(map_str_to_codec("GSM-EFR") == CODEC_GSMEFR_8000_1); + OSMO_ASSERT(map_str_to_codec("G729") == CODEC_G729_8000_1); + OSMO_ASSERT(map_str_to_codec("GSM-HR-08") == CODEC_GSMHR_8000_1); + + /* We do not care about what is after the first delimiter */ + OSMO_ASSERT(map_str_to_codec("AMR-WB/123///456") == CODEC_AMRWB_16000_1); + OSMO_ASSERT(map_str_to_codec("PCMA/asdf") == CODEC_PCMA_8000_1); + OSMO_ASSERT(map_str_to_codec("GSM/qwertz") == CODEC_GSM_8000_1); + + /* A trailing delimiter should not hurt */ + OSMO_ASSERT(map_str_to_codec("AMR/") == CODEC_AMR_8000_1); + OSMO_ASSERT(map_str_to_codec("G729/") == CODEC_G729_8000_1); + OSMO_ASSERT(map_str_to_codec("GSM/") == CODEC_GSM_8000_1); + + /* This is expected to fail */ + OSMO_ASSERT(map_str_to_codec("INVALID/1234/7") == -1); + OSMO_ASSERT(map_str_to_codec(NULL) == -1); + OSMO_ASSERT(map_str_to_codec("") == -1); + OSMO_ASSERT(map_str_to_codec("/////") == -1); + + /* The buffers are 64 bytes long, check what happens with overlong + * strings as input (This schould still work.) */ + OSMO_ASSERT(map_str_to_codec("AMR-WB/16000/1############################################################################################################") == CODEC_AMRWB_16000_1); + + /* This should not work, as there is no delimiter after the codec + * name */ + OSMO_ASSERT(map_str_to_codec("AMR-WB####################################################################################################################") == -1); +} + +static void test_map_codec_to_pt_and_map_pt_to_codec(void) +{ + struct ptmap ptmap[10]; + unsigned int ptmap_len; + unsigned int i; + + ptmap[0].codec = CODEC_GSMEFR_8000_1; + ptmap[0].pt = 96; + ptmap[1].codec = CODEC_GSMHR_8000_1; + ptmap[1].pt = 97; + ptmap[2].codec = CODEC_AMR_8000_1; + ptmap[2].pt = 98; + ptmap[3].codec = CODEC_AMRWB_16000_1; + ptmap[3].pt = 99; + ptmap_len = 4; + + /* Mappings that are covered by the table */ + for (i = 0; i < ptmap_len; i++) + printf(" %u => %u\n", ptmap[i].codec, map_codec_to_pt(ptmap, ptmap_len, ptmap[i].codec)); + for (i = 0; i < ptmap_len; i++) + printf(" %u <= %u\n", ptmap[i].pt, map_pt_to_codec(ptmap, ptmap_len, ptmap[i].pt)); + printf("\n"); + + /* Map some codecs/payload types from the static range, result must + * always be a 1:1 mapping */ + printf(" %u => %u\n", CODEC_PCMU_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_PCMU_8000_1)); + printf(" %u => %u\n", CODEC_GSM_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_GSM_8000_1)); + printf(" %u => %u\n", CODEC_PCMA_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_PCMA_8000_1)); + printf(" %u => %u\n", CODEC_G729_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_G729_8000_1)); + printf(" %u <= %u\n", CODEC_PCMU_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_PCMU_8000_1)); + printf(" %u <= %u\n", CODEC_GSM_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_GSM_8000_1)); + printf(" %u <= %u\n", CODEC_PCMA_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_PCMA_8000_1)); + printf(" %u <= %u\n", CODEC_G729_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_G729_8000_1)); + printf("\n"); + + /* Try to do mappings from statically defined range to danymic range and vice versa. This + * is illegal and should result into a 1:1 mapping */ + ptmap[3].codec = CODEC_AMRWB_16000_1; + ptmap[3].pt = 2; + ptmap[4].codec = CODEC_PCMU_8000_1; + ptmap[4].pt = 100; + ptmap_len = 5; + + /* Apply all mappings again, the illegal ones we defined should result into 1:1 mappings */ + for (i = 0; i < ptmap_len; i++) + printf(" %u => %u\n", ptmap[i].codec, map_codec_to_pt(ptmap, ptmap_len, ptmap[i].codec)); + for (i = 0; i < ptmap_len; i++) + printf(" %u <= %u\n", ptmap[i].pt, map_pt_to_codec(ptmap, ptmap_len, ptmap[i].pt)); + printf("\n"); +} + static const struct log_info_cat log_categories[] = { }; @@ -403,6 +555,8 @@ int main(int argc, char **argv) test_mgcp_msg(); test_mgcp_client_cancel(); test_sdp_section_start(); + test_map_codec_to_pt_and_map_pt_to_codec(); + test_map_pt_to_codec(); printf("Done\n"); fprintf(stderr, "Done\n"); diff --git a/tests/mgcp_client/mgcp_client_test.err b/tests/mgcp_client/mgcp_client_test.err index 7309242..1d5a1a0 100644 --- a/tests/mgcp_client/mgcp_client_test.err +++ b/tests/mgcp_client/mgcp_client_test.err @@ -62,4 +62,8 @@ test_sdp_section_start() test [9]: body: "some mgcp header data\r\nand header params\n\r\rm=audio 23\r\n" DLMGCP MGCP response: cannot find start of SDP parameters got rc=-22 +DLMGCP ptmap contains illegal mapping: codec=113 maps to pt=2 +DLMGCP ptmap contains illegal mapping: codec=0 maps to pt=100 +DLMGCP ptmap contains illegal mapping: codec=113 maps to pt=2 +DLMGCP ptmap contains illegal mapping: codec=0 maps to pt=100 Done diff --git a/tests/mgcp_client/mgcp_client_test.ok b/tests/mgcp_client/mgcp_client_test.ok index fc6db30..454ee3d 100644 --- a/tests/mgcp_client/mgcp_client_test.ok +++ b/tests/mgcp_client/mgcp_client_test.ok @@ -18,8 +18,9 @@ o=- 1 23 IN IP4 10.9.1.120 s=-
c=IN IP4 10.9.1.120
t=0 0
-m=audio 16002 RTP/AVP 98
-a=rtpmap:98 AMR/8000
+m=audio 16002 RTP/AVP 110 96
+a=rtpmap:110 AMR/8000
+a=rtpmap:96 GSM-EFR/8000
a=ptime:20
----- @@ -29,16 +30,39 @@ response cb received: head.comment = OK audio_port = 16002 audio_ip = 10.9.1.120 + ptime = 20 + codecs_len = 2 + codecs[0] = 112 + codecs[1] = 110 + ptmap_len = 2 + ptmap[0].codec = 112 + ptmap[0].pt = 110 + ptmap[1].codec = 110 + ptmap[1].pt = 96 Generated CRCX message: CRCX 1 23@mgw MGCP 1.0
C: 2f
I: 11
-L: p:20, a:AMR, nt:IN
+L: p:20, a:GSM, nt:IN
+M: sendrecv
+ +Generated CRCX message (two codecs): +CRCX 2 23@mgw MGCP 1.0
+C: 2f
+I: 11
+L: p:20, a:GSM;AMR, nt:IN
+M: sendrecv
+ +Generated CRCX message (three codecs, one with custom pt): +CRCX 3 23@mgw MGCP 1.0
+C: 2f
+I: 11
+L: p:20, a:GSM;AMR;GSM-EFR, nt:IN
M: sendrecv
Generated MDCX message: -MDCX 2 23@mgw MGCP 1.0
+MDCX 4 23@mgw MGCP 1.0
C: 2f
I: 11
M: sendrecv
@@ -48,18 +72,50 @@ o=- 2f 23 IN IP4 127.0.0.1 s=-
c=IN IP4 192.168.100.23
t=0 0
-m=audio 1234 RTP/AVP 255
+m=audio 1234 RTP/AVP 3
+a=ptime:20
+ +Generated MDCX message (two codecs): +MDCX 5 23@mgw MGCP 1.0
+C: 2f
+I: 11
+M: sendrecv
+
+v=0
+o=- 2f 23 IN IP4 127.0.0.1
+s=-
+c=IN IP4 192.168.100.23
+t=0 0
+m=audio 1234 RTP/AVP 3 112
+a=rtpmap:112 AMR/8000/1
+a=ptime:20
+ +Generated MDCX message (three codecs, one with custom pt): +MDCX 6 23@mgw MGCP 1.0
+C: 2f
+I: 11
+M: sendrecv
+
+v=0
+o=- 2f 23 IN IP4 127.0.0.1
+s=-
+c=IN IP4 192.168.100.23
+t=0 0
+m=audio 1234 RTP/AVP 3 112 96
+a=rtpmap:112 AMR/8000/1
+a=rtpmap:96 GSM-EFR/8000/1
+a=ptime:20
Generated DLCX message: -DLCX 3 23@mgw MGCP 1.0
+DLCX 7 23@mgw MGCP 1.0
C: 2f
I: 11
Generated AUEP message: -AUEP 4 23@mgw MGCP 1.0
+AUEP 8 23@mgw MGCP 1.0
Generated RSIP message: -RSIP 5 23@mgw MGCP 1.0
+RSIP 9 23@mgw MGCP 1.0
Overfolow test: @@ -102,4 +158,33 @@ test_sdp_section_start() test [7]: test_sdp_section_start() test [8]: test_sdp_section_start() test [9]: + 110 => 96 + 111 => 97 + 112 => 98 + 113 => 99 + 96 <= 110 + 97 <= 111 + 98 <= 112 + 99 <= 113 + + 0 => 0 + 3 => 3 + 8 => 8 + 18 => 18 + 0 <= 0 + 3 <= 3 + 8 <= 8 + 18 <= 18 + + 110 => 96 + 111 => 97 + 112 => 98 + 113 => 113 + 0 => 0 + 96 <= 110 + 97 <= 111 + 98 <= 112 + 2 <= 2 + 100 <= 100 + Done |