From f83ec56212e22e60d9a3b04733ec4b1b96238cef Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Thu, 7 Sep 2017 19:18:40 +0200 Subject: [PATCH] create libosmo-mgcp and osmo-mgw by copying legacy code This a cosmetic commit, copying libosmo-legacy-mgcp to libosmo-mgcp and osmo-bsc_mgcp to osmo-mgw 1:1 at first, to provide a basis for next patches that highlight the changes from legacy to new code. Until osmo-msc and osmo-bsc are adjusted to operate with the new code, we will keep the legacy code alongside the new code. The legacy code might be dropped later. Change-Id: Idf54481754a1765bdb2d0d7033bc0d7dc2018024 --- doc/examples/osmo-mgw/osmo-mgw.cfg | 14 + include/osmocom/mgcp/Makefile.am | 4 + include/osmocom/mgcp/mgcp.h | 299 +++++ include/osmocom/mgcp/mgcp_internal.h | 337 +++++ include/osmocom/mgcp/mgcp_transcode.h | 90 ++ include/osmocom/mgcp/osmux.h | 41 + include/osmocom/mgcp/vty.h | 31 + src/libosmo-mgcp/Makefile.am | 50 + src/libosmo-mgcp/g711common.h | 187 +++ src/libosmo-mgcp/mgcp_common.c | 54 + src/libosmo-mgcp/mgcp_network.c | 1166 ++++++++++++++++++ src/libosmo-mgcp/mgcp_osmux.c | 583 +++++++++ src/libosmo-mgcp/mgcp_protocol.c | 1640 +++++++++++++++++++++++++ src/libosmo-mgcp/mgcp_sdp.c | 305 +++++ src/libosmo-mgcp/mgcp_transcode.c | 611 +++++++++ src/libosmo-mgcp/mgcp_vty.c | 1555 +++++++++++++++++++++++ src/osmo-mgw/Makefile.am | 31 + src/osmo-mgw/mgcp_main.c | 351 ++++++ tests/mgcp/Makefile.am | 61 + tests/mgcp/mgcp_test.c | 1235 +++++++++++++++++++ tests/mgcp/mgcp_test.ok | 480 ++++++++ tests/mgcp/mgcp_transcoding_test.c | 661 ++++++++++ tests/mgcp/mgcp_transcoding_test.ok | 539 ++++++++ 23 files changed, 10325 insertions(+) create mode 100644 doc/examples/osmo-mgw/osmo-mgw.cfg create mode 100644 include/osmocom/mgcp/Makefile.am create mode 100644 include/osmocom/mgcp/mgcp.h create mode 100644 include/osmocom/mgcp/mgcp_internal.h create mode 100644 include/osmocom/mgcp/mgcp_transcode.h create mode 100644 include/osmocom/mgcp/osmux.h create mode 100644 include/osmocom/mgcp/vty.h create mode 100644 src/libosmo-mgcp/Makefile.am create mode 100644 src/libosmo-mgcp/g711common.h create mode 100644 src/libosmo-mgcp/mgcp_common.c create mode 100644 src/libosmo-mgcp/mgcp_network.c create mode 100644 src/libosmo-mgcp/mgcp_osmux.c create mode 100644 src/libosmo-mgcp/mgcp_protocol.c create mode 100644 src/libosmo-mgcp/mgcp_sdp.c create mode 100644 src/libosmo-mgcp/mgcp_transcode.c create mode 100644 src/libosmo-mgcp/mgcp_vty.c create mode 100644 src/osmo-mgw/Makefile.am create mode 100644 src/osmo-mgw/mgcp_main.c create mode 100644 tests/mgcp/Makefile.am create mode 100644 tests/mgcp/mgcp_test.c create mode 100644 tests/mgcp/mgcp_test.ok create mode 100644 tests/mgcp/mgcp_transcoding_test.c create mode 100644 tests/mgcp/mgcp_transcoding_test.ok diff --git a/doc/examples/osmo-mgw/osmo-mgw.cfg b/doc/examples/osmo-mgw/osmo-mgw.cfg new file mode 100644 index 000000000..442162f94 --- /dev/null +++ b/doc/examples/osmo-mgw/osmo-mgw.cfg @@ -0,0 +1,14 @@ +! +! MGCP configuration example +! +mgcp + !local ip 10.23.24.2 + !bts ip 10.24.24.1 + !bind ip 10.23.24.1 + bind port 2427 + rtp base 4000 + rtp force-ptime 20 + sdp audio payload number 98 + sdp audio payload name AMR/8000 + number endpoints 31 + no rtcp-omit diff --git a/include/osmocom/mgcp/Makefile.am b/include/osmocom/mgcp/Makefile.am new file mode 100644 index 000000000..4a9550cb6 --- /dev/null +++ b/include/osmocom/mgcp/Makefile.am @@ -0,0 +1,4 @@ +noinst_HEADERS = \ + mgcp_transcode.h \ + vty.h \ + $(NULL) diff --git a/include/osmocom/mgcp/mgcp.h b/include/osmocom/mgcp/mgcp.h new file mode 100644 index 000000000..c017fafa3 --- /dev/null +++ b/include/osmocom/mgcp/mgcp.h @@ -0,0 +1,299 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther + * (C) 2009-2012 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 . + * + */ + +#ifndef OPENBSC_MGCP_H +#define OPENBSC_MGCP_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define RTP_PORT_DEFAULT 4000 +#define RTP_PORT_NET_DEFAULT 16000 + +/** + * Calculate the RTP audio port for the given multiplex + * and the direction. This allows a semi static endpoint + * to port calculation removing the need for the BSC + * and the MediaGateway to communicate. + * + * Port usage explained: + * base + (multiplex * 2) + 0 == local port to wait for network packets + * base + (multiplex * 2) + 1 == local port for rtcp + * + * The above port will receive packets from the BTS that need + * to be patched and forwarded to the network. + * The above port will receive packets from the network that + * need to be patched and forwarded to the BTS. + * + * We assume to have a static BTS IP address so we can differentiate + * network and BTS. + * + */ +static inline int rtp_calculate_port(int multiplex, int base) +{ + return base + (multiplex * 2); +} + + +/* + * Handling of MGCP Endpoints and the MGCP Config + */ +struct mgcp_endpoint; +struct mgcp_config; +struct mgcp_trunk_config; +struct mgcp_rtp_end; + +#define MGCP_ENDP_CRCX 1 +#define MGCP_ENDP_DLCX 2 +#define MGCP_ENDP_MDCX 3 + +/* + * what to do with the msg? + * - continue as usual? + * - reject and send a failure code? + * - defer? do not send anything + */ +#define MGCP_POLICY_CONT 4 +#define MGCP_POLICY_REJECT 5 +#define MGCP_POLICY_DEFER 6 + +typedef int (*mgcp_realloc)(struct mgcp_trunk_config *cfg, int endpoint); +typedef int (*mgcp_change)(struct mgcp_trunk_config *cfg, int endpoint, int state); +typedef int (*mgcp_policy)(struct mgcp_trunk_config *cfg, int endpoint, int state, const char *transactio_id); +typedef int (*mgcp_reset)(struct mgcp_trunk_config *cfg); +typedef int (*mgcp_rqnt)(struct mgcp_endpoint *endp, char tone); + +/** + * Return: + * < 0 in case no audio was processed + * >= 0 in case audio was processed. The remaining payload + * length will be returned. + */ +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); + +typedef void (*mgcp_get_format)(struct mgcp_endpoint *endp, + int *payload_type, + const char**subtype_name, + const char**fmtp_extra); + +#define PORT_ALLOC_STATIC 0 +#define PORT_ALLOC_DYNAMIC 1 + +/** + * This holds information on how to allocate ports + */ +struct mgcp_port_range { + int mode; + + /* addr or NULL to fall-back to default */ + char *bind_addr; + + /* pre-allocated from a base? */ + int base_port; + + /* dynamically allocated */ + int range_start; + int range_end; + int last_port; +}; + +#define MGCP_KEEPALIVE_ONCE (-1) + +struct mgcp_trunk_config { + struct llist_head entry; + + struct mgcp_config *cfg; + + int trunk_nr; + int trunk_type; + + char *audio_fmtp_extra; + char *audio_name; + int audio_payload; + int audio_send_ptime; + int audio_send_name; + int audio_loop; + + int no_audio_transcoding; + + int omit_rtcp; + int keepalive_interval; + + /* RTP patching */ + int force_constant_ssrc; /* 0: don't, 1: once */ + int force_aligned_timing; + + /* spec handling */ + int force_realloc; + + /* timer */ + struct osmo_timer_list keepalive_timer; + + unsigned int number_endpoints; + struct mgcp_endpoint *endpoints; +}; + +enum mgcp_role { + MGCP_BSC = 0, + MGCP_BSC_NAT, +}; + +enum mgcp_connection_mode { + MGCP_CONN_NONE = 0, + MGCP_CONN_RECV_ONLY = 1, + MGCP_CONN_SEND_ONLY = 2, + MGCP_CONN_RECV_SEND = MGCP_CONN_RECV_ONLY | MGCP_CONN_SEND_ONLY, + MGCP_CONN_LOOPBACK = 4 | MGCP_CONN_RECV_SEND, +}; + +extern const struct value_string mgcp_connection_mode_strs[]; + +static inline const char *mgcp_cmode_name(enum mgcp_connection_mode mode) +{ + return get_value_string(mgcp_connection_mode_strs, mode); +} + +struct mgcp_config { + int source_port; + char *local_ip; + char *source_addr; + char *bts_ip; + char *call_agent_addr; + + struct in_addr bts_in; + + /* transcoder handling */ + char *transcoder_ip; + struct in_addr transcoder_in; + int transcoder_remote_base; + + /* RTP processing */ + mgcp_processing rtp_processing_cb; + mgcp_processing_setup setup_rtp_processing_cb; + + mgcp_get_format get_net_downlink_format_cb; + + struct osmo_wqueue gw_fd; + + struct mgcp_port_range bts_ports; + struct mgcp_port_range net_ports; + struct mgcp_port_range transcoder_ports; + int endp_dscp; + + int bts_force_ptime; + + mgcp_change change_cb; + mgcp_policy policy_cb; + mgcp_reset reset_cb; + mgcp_realloc realloc_cb; + mgcp_rqnt rqnt_cb; + void *data; + + uint32_t last_call_id; + + /* trunk handling */ + struct mgcp_trunk_config trunk; + struct llist_head trunks; + + /* only used for start with a static configuration */ + int last_net_port; + int last_bts_port; + + enum mgcp_role role; + + /* osmux translator: 0 means disabled, 1 means enabled */ + int osmux; + /* addr to bind the server to */ + char *osmux_addr; + /* The BSC-NAT may ask for enabling osmux on demand. This tells us if + * the osmux socket is already initialized. + */ + int osmux_init; + /* osmux batch factor: from 1 to 4 maximum */ + int osmux_batch; + /* osmux batch size (in bytes) */ + int osmux_batch_size; + /* osmux port */ + uint16_t osmux_port; + /* Pad circuit with dummy messages until we see the first voice + * message. + */ + uint16_t osmux_dummy; +}; + +/* config management */ +struct mgcp_config *mgcp_config_alloc(void); +int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg, + enum mgcp_role role); +int mgcp_vty_init(void); +int mgcp_endpoints_allocate(struct mgcp_trunk_config *cfg); +void mgcp_release_endp(struct mgcp_endpoint *endp); +void mgcp_initialize_endp(struct mgcp_endpoint *endp); +int mgcp_reset_transcoder(struct mgcp_config *cfg); +void mgcp_format_stats(struct mgcp_endpoint *endp, char *stats, size_t size); +int mgcp_parse_stats(struct msgb *msg, uint32_t *ps, uint32_t *os, uint32_t *pr, uint32_t *_or, int *loss, uint32_t *jitter); + +void mgcp_trunk_set_keepalive(struct mgcp_trunk_config *tcfg, int interval); + +/* + * format helper functions + */ +struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg); + +/* adc helper */ +static inline int mgcp_timeslot_to_endpoint(int multiplex, int timeslot) +{ + if (timeslot == 0) { + LOGP(DLMGCP, LOGL_ERROR, "Timeslot should not be 0\n"); + timeslot = 255; + } + + return timeslot + (32 * multiplex); +} + +static inline void mgcp_endpoint_to_timeslot(int endpoint, int *multiplex, int *timeslot) +{ + *multiplex = endpoint / 32; + *timeslot = endpoint % 32; +} + +int mgcp_send_reset_ep(struct mgcp_endpoint *endp, int endpoint); +int mgcp_send_reset_all(struct mgcp_config *cfg); + + +int mgcp_create_bind(const char *source_addr, struct osmo_fd *fd, int port); +int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, struct sockaddr_in *addr, char *buf, int rc); +int mgcp_udp_send(int fd, struct in_addr *addr, int port, char *buf, int len); + +#endif diff --git a/include/osmocom/mgcp/mgcp_internal.h b/include/osmocom/mgcp/mgcp_internal.h new file mode 100644 index 000000000..dcc75f18e --- /dev/null +++ b/include/osmocom/mgcp/mgcp_internal.h @@ -0,0 +1,337 @@ +/* MGCP Private Data */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther + * (C) 2009-2012 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 . + * + */ + +#pragma once + +#include + +#include + +#define CI_UNUSED 0 + +enum mgcp_trunk_type { + MGCP_TRUNK_VIRTUAL, + MGCP_TRUNK_E1, +}; + +struct mgcp_rtp_stream_state { + uint32_t ssrc; + uint16_t last_seq; + uint32_t last_timestamp; + uint32_t err_ts_counter; + int32_t last_tsdelta; + uint32_t last_arrival_time; +}; + +struct mgcp_rtp_state { + int initialized; + int patch_ssrc; + + uint32_t orig_ssrc; + + int seq_offset; + + int32_t timestamp_offset; + uint32_t packet_duration; + + struct mgcp_rtp_stream_state in_stream; + struct mgcp_rtp_stream_state out_stream; + + /* jitter and packet loss calculation */ + int stats_initialized; + uint16_t stats_base_seq; + uint16_t stats_max_seq; + uint32_t stats_ssrc; + uint32_t stats_jitter; + int32_t stats_transit; + int stats_cycles; + bool patched_first_rtp_payload; /* FIXME: drop this, see OS#2459 */ +}; + +struct mgcp_rtp_codec { + uint32_t rate; + int channels; + uint32_t frame_duration_num; + uint32_t frame_duration_den; + + int payload_type; + char *audio_name; + char *subtype_name; +}; + +struct mgcp_rtp_end { + /* statistics */ + unsigned int packets; + unsigned int octets; + unsigned int dropped_packets; + 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 */ + + /* per endpoint data */ + int frames_per_packet; + uint32_t packet_duration_ms; + char *fmtp_extra; + int output_enabled; + int force_output_ptime; + + /* RTP patching */ + int force_constant_ssrc; /* -1: always, 0: don't, 1: once */ + int force_aligned_timing; + void *rtp_process_data; + + /* + * Each end has a socket... + */ + struct osmo_fd rtp; + struct osmo_fd rtcp; + + int local_port; + int local_alloc; +}; + +enum { + MGCP_TAP_BTS_IN, + MGCP_TAP_BTS_OUT, + MGCP_TAP_NET_IN, + MGCP_TAP_NET_OUT, + + /* last element */ + MGCP_TAP_COUNT +}; + +struct mgcp_rtp_tap { + int enabled; + struct sockaddr_in forward; +}; + +struct mgcp_lco { + char *string; + char *codec; + int pkt_period_min; /* time in ms */ + int pkt_period_max; /* time in ms */ +}; + +enum mgcp_type { + MGCP_RTP_DEFAULT = 0, + MGCP_RTP_TRANSCODED, + MGCP_OSMUX_BSC, + MGCP_OSMUX_BSC_NAT, +}; + +#include + +struct mgcp_endpoint { + int allocated; + uint32_t ci; + char *callid; + struct mgcp_lco local_options; + int conn_mode; + int orig_mode; + + /* backpointer */ + struct mgcp_config *cfg; + struct mgcp_trunk_config *tcfg; + + /* port status for bts/net */ + struct mgcp_rtp_end bts_end; + struct mgcp_rtp_end net_end; + + /* + * For transcoding we will send from the local_port + * of trans_bts and it will arrive at trans_net from + * where we will forward it to the network. + */ + struct mgcp_rtp_end trans_bts; + struct mgcp_rtp_end trans_net; + enum mgcp_type type; + + /* sequence bits */ + struct mgcp_rtp_state net_state; + struct mgcp_rtp_state bts_state; + + /* fields for re-transmission */ + char *last_trans; + char *last_response; + + /* tap for the endpoint */ + struct mgcp_rtp_tap taps[MGCP_TAP_COUNT]; + + struct { + /* Osmux state: disabled, activating, active */ + enum osmux_state state; + /* Allocated Osmux circuit ID for this endpoint */ + int allocated_cid; + /* Used Osmux circuit ID for this endpoint */ + uint8_t cid; + /* handle to batch messages */ + struct osmux_in_handle *in; + /* handle to unbatch messages */ + struct osmux_out_handle out; + /* statistics */ + struct { + uint32_t chunks; + uint32_t octets; + } stats; + } osmux; +}; + +#define for_each_line(line, save) \ + for (line = strline_r(NULL, &save); line;\ + line = strline_r(NULL, &save)) + +static inline char *strline_r(char *str, char **saveptr) +{ + char *result; + + if (str) + *saveptr = str; + + result = *saveptr; + + if (*saveptr != NULL) { + *saveptr = strpbrk(*saveptr, "\r\n"); + + if (*saveptr != NULL) { + char *eos = *saveptr; + + if ((*saveptr)[0] == '\r' && (*saveptr)[1] == '\n') + (*saveptr)++; + (*saveptr)++; + if ((*saveptr)[0] == '\0') + *saveptr = NULL; + + *eos = '\0'; + } + } + + return result; +} + + + +#define ENDPOINT_NUMBER(endp) abs((int)(endp - endp->tcfg->endpoints)) + +/** + * Internal structure while parsing a request + */ +struct mgcp_parse_data { + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + char *trans; + char *save; + int found; +}; + +int mgcp_send_dummy(struct mgcp_endpoint *endp); +int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port); +int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port); +int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *enp, int rtp_port); +int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *enp, int rtp_port); +int mgcp_free_rtp_port(struct mgcp_rtp_end *end); + +/* For transcoding we need to manage an in and an output that are connected */ +static inline int endp_back_channel(int endpoint) +{ + return endpoint + 60; +} + +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); + +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, + struct mgcp_rtp_end *rtp); + +void mgcp_state_calc_loss(struct mgcp_rtp_state *s, struct mgcp_rtp_end *, + uint32_t *expected, int *loss); +uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *); + +/* payload processing default functions */ +int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_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); + +void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp, + int *payload_type, + const char**subtype_name, + const char**fmtp_extra); + +/* internal RTP Annex A counting */ +void mgcp_rtp_annex_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, + const uint16_t seq, const int32_t transit, + const uint32_t ssrc); + +int mgcp_set_ip_tos(int fd, int tos); + +enum { + MGCP_DEST_NET = 0, + MGCP_DEST_BTS, +}; + + +#define MGCP_DUMMY_LOAD 0x23 + + +/** + * SDP related information + */ +/* Assume audio frame length of 20ms */ +#define DEFAULT_RTP_AUDIO_FRAME_DUR_NUM 20 +#define DEFAULT_RTP_AUDIO_FRAME_DUR_DEN 1000 +#define DEFAULT_RTP_AUDIO_PACKET_DURATION_MS 20 +#define DEFAULT_RTP_AUDIO_DEFAULT_RATE 8000 +#define DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS 1 + +#define PTYPE_UNDEFINED (-1) +int mgcp_parse_sdp_data(struct mgcp_endpoint *endp, struct mgcp_rtp_end *rtp, struct mgcp_parse_data *p); +int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec, + int payload_type, const char *audio_name); + + +/** + * Internal network related + */ +static inline const char *mgcp_net_src_addr(struct mgcp_endpoint *endp) +{ + if (endp->cfg->net_ports.bind_addr) + return endp->cfg->net_ports.bind_addr; + return endp->cfg->source_addr; +} + +static inline const char *mgcp_bts_src_addr(struct mgcp_endpoint *endp) +{ + if (endp->cfg->bts_ports.bind_addr) + return endp->cfg->bts_ports.bind_addr; + return endp->cfg->source_addr; +} + +int mgcp_msg_terminate_nul(struct msgb *msg); diff --git a/include/osmocom/mgcp/mgcp_transcode.h b/include/osmocom/mgcp/mgcp_transcode.h new file mode 100644 index 000000000..147e48bba --- /dev/null +++ b/include/osmocom/mgcp/mgcp_transcode.h @@ -0,0 +1,90 @@ +/* + * (C) 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 . + * + */ +#ifndef OPENBSC_MGCP_TRANSCODE_H +#define OPENBSC_MGCP_TRANSCODE_H + +#include "bscconfig.h" + +#include +#ifdef HAVE_BCG729 +#include +#include +#endif + +enum audio_format { + AF_INVALID, + AF_S16, + AF_L16, + AF_GSM, + AF_G729, + AF_PCMA, + AF_PCMU +}; + + +struct mgcp_process_rtp_state { + /* decoding */ + enum audio_format src_fmt; + union { + gsm gsm_handle; +#ifdef HAVE_BCG729 + bcg729DecoderChannelContextStruct *g729_dec; +#endif + } src; + size_t src_frame_size; + size_t src_samples_per_frame; + + /* processing */ + + /* encoding */ + enum audio_format dst_fmt; + union { + gsm gsm_handle; +#ifdef HAVE_BCG729 + bcg729EncoderChannelContextStruct *g729_enc; +#endif + } dst; + size_t dst_frame_size; + size_t dst_samples_per_frame; + int dst_packet_duration; + + int is_running; + uint16_t next_seq; + uint32_t next_time; + int16_t samples[10*160]; + size_t sample_cnt; + size_t sample_offs; +}; + + +int mgcp_transcoding_setup(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end); + +void mgcp_transcoding_net_downlink_format(struct mgcp_endpoint *endp, + int *payload_type, + const char**audio_name, + const char**fmtp_extra); + +int mgcp_transcoding_process_rtp(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size); + +int mgcp_transcoding_get_frame_size(void *state_, int nsamples, int dst); +#endif /* OPENBSC_MGCP_TRANSCODE_H */ diff --git a/include/osmocom/mgcp/osmux.h b/include/osmocom/mgcp/osmux.h new file mode 100644 index 000000000..f3ea72a85 --- /dev/null +++ b/include/osmocom/mgcp/osmux.h @@ -0,0 +1,41 @@ +#ifndef _OPENBSC_OSMUX_H_ +#define _OPENBSC_OSMUX_H_ + +#include + +#define OSMUX_PORT 1984 + +enum { + OSMUX_ROLE_BSC = 0, + OSMUX_ROLE_BSC_NAT, +}; + +int osmux_init(int role, struct mgcp_config *cfg); +int osmux_enable_endpoint(struct mgcp_endpoint *endp, struct in_addr *addr, uint16_t port); +void osmux_disable_endpoint(struct mgcp_endpoint *endp); +void osmux_allocate_cid(struct mgcp_endpoint *endp); +void osmux_release_cid(struct mgcp_endpoint *endp); + +int osmux_xfrm_to_rtp(struct mgcp_endpoint *endp, int type, char *buf, int rc); +int osmux_xfrm_to_osmux(int type, char *buf, int rc, struct mgcp_endpoint *endp); + +int osmux_send_dummy(struct mgcp_endpoint *endp); + +int osmux_get_cid(void); +void osmux_put_cid(uint8_t osmux_cid); +int osmux_used_cid(void); + +enum osmux_state { + OSMUX_STATE_DISABLED = 0, + OSMUX_STATE_NEGOTIATING, + OSMUX_STATE_ACTIVATING, + OSMUX_STATE_ENABLED, +}; + +enum osmux_usage { + OSMUX_USAGE_OFF = 0, + OSMUX_USAGE_ON = 1, + OSMUX_USAGE_ONLY = 2, +}; + +#endif diff --git a/include/osmocom/mgcp/vty.h b/include/osmocom/mgcp/vty.h new file mode 100644 index 000000000..c277c0aa9 --- /dev/null +++ b/include/osmocom/mgcp/vty.h @@ -0,0 +1,31 @@ +#ifndef OPENBSC_VTY_H +#define OPENBSC_VTY_H + +#include +#include +#include + +struct gsm_network; +struct vty; + +void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *); + +struct buffer *vty_argv_to_buffer(int argc, const char *argv[], int base); + +extern struct cmd_element cfg_description_cmd; +extern struct cmd_element cfg_no_description_cmd; + +enum mgcp_vty_node { + MGCP_NODE = _LAST_OSMOVTY_NODE + 1, + TRUNK_NODE, +}; + +struct log_info; +int bsc_vty_init(struct gsm_network *network); +int bsc_vty_init_extra(void); + +void msc_vty_init(struct gsm_network *msc_network); + +struct gsm_network *gsmnet_from_vty(struct vty *vty); + +#endif diff --git a/src/libosmo-mgcp/Makefile.am b/src/libosmo-mgcp/Makefile.am new file mode 100644 index 000000000..bcf624291 --- /dev/null +++ b/src/libosmo-mgcp/Makefile.am @@ -0,0 +1,50 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(LIBBCG729_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(COVERAGE_LDFLAGS) \ + $(LIBBCG729_LIBS) \ + $(NULL) + +# 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:0:0 + +lib_LTLIBRARIES = \ + libosmo-legacy-mgcp.la \ + $(NULL) + +noinst_HEADERS = \ + g711common.h \ + $(NULL) + +libosmo_legacy_mgcp_la_SOURCES = \ + mgcp_common.c \ + mgcp_protocol.c \ + mgcp_network.c \ + mgcp_vty.c \ + mgcp_osmux.c \ + mgcp_sdp.c \ + $(NULL) +if BUILD_MGCP_TRANSCODING +libosmo_legacy_mgcp_la_SOURCES += \ + mgcp_transcode.c \ + $(NULL) +endif + +libosmo_legacy_mgcp_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LEGACY_MGCP_LIBVERSION) diff --git a/src/libosmo-mgcp/g711common.h b/src/libosmo-mgcp/g711common.h new file mode 100644 index 000000000..cb35fc651 --- /dev/null +++ b/src/libosmo-mgcp/g711common.h @@ -0,0 +1,187 @@ +/* + * PCM - A-Law conversion + * Copyright (c) 2000 by Abramo Bagnara + * + * Wrapper for linphone Codec class by Simon Morlat + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +static inline int val_seg(int val) +{ + int r = 0; + val >>= 7; /*7 = 4 + 3*/ + if (val & 0xf0) { + val >>= 4; + r += 4; + } + if (val & 0x0c) { + val >>= 2; + r += 2; + } + if (val & 0x02) + r += 1; + return r; +} + +/* + * s16_to_alaw() - Convert a 16-bit linear PCM value to 8-bit A-law + * + * s16_to_alaw() accepts an 16-bit integer and encodes it as A-law data. + * + * Linear Input Code Compressed Code + * ------------------------ --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + * G711 is designed for 13 bits input signal, this function add extra shifting to take this into account. + */ + +static inline unsigned char s16_to_alaw(int pcm_val) +{ + int mask; + int seg; + unsigned char aval; + + if (pcm_val >= 0) { + mask = 0xD5; + } else { + mask = 0x55; + pcm_val = -pcm_val; + if (pcm_val > 0x7fff) + pcm_val = 0x7fff; + } + + if (pcm_val < 256) /*256 = 32 << 3*/ + aval = pcm_val >> 4; /*4 = 1 + 3*/ + else { + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + aval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f); + } + return aval ^ mask; +} + +/* + * alaw_to_s16() - Convert an A-law value to 16-bit linear PCM + * + */ +static inline int alaw_to_s16(unsigned char a_val) +{ + int t; + int seg; + + a_val ^= 0x55; + t = a_val & 0x7f; + if (t < 16) + t = (t << 4) + 8; + else { + seg = (t >> 4) & 0x07; + t = ((t & 0x0f) << 4) + 0x108; + t <<= seg -1; + } + return ((a_val & 0x80) ? t : -t); +} +/* + * s16_to_ulaw() - Convert a linear PCM value to u-law + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ + +static inline unsigned char s16_to_ulaw(int pcm_val) /* 2's complement (16-bit range) */ +{ + int mask; + int seg; + unsigned char uval; + + if (pcm_val < 0) { + pcm_val = 0x84 - pcm_val; + mask = 0x7f; + } else { + pcm_val += 0x84; + mask = 0xff; + } + if (pcm_val > 0x7fff) + pcm_val = 0x7fff; + + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0x0f); + return uval ^ mask; +} + +/* + * ulaw_to_s16() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +static inline int ulaw_to_s16(unsigned char u_val) +{ + int t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & 0x0f) << 3) + 0x84; + t <<= (u_val & 0x70) >> 4; + + return ((u_val & 0x80) ? (0x84 - t) : (t - 0x84)); +} diff --git a/src/libosmo-mgcp/mgcp_common.c b/src/libosmo-mgcp/mgcp_common.c new file mode 100644 index 000000000..444a06047 --- /dev/null +++ b/src/libosmo-mgcp/mgcp_common.c @@ -0,0 +1,54 @@ +/* Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* Implementations useful both for the MGCP GW as well as MGCP GW clients */ + +/* + * (C) 2016 by sysmocom s.m.f.c. GmbH + * 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 . + * + */ + +#include + +#include +#include + +const struct value_string mgcp_connection_mode_strs[] = { + { MGCP_CONN_NONE, "none" }, + { MGCP_CONN_RECV_SEND, "sendrecv" }, + { MGCP_CONN_SEND_ONLY, "sendonly" }, + { MGCP_CONN_RECV_ONLY, "recvonly" }, + { MGCP_CONN_LOOPBACK, "loopback" }, + { 0, NULL } +}; + +/* Ensure that the msg->l2h is NUL terminated. */ +int mgcp_msg_terminate_nul(struct msgb *msg) +{ + unsigned char *tail = msg->l2h + msgb_l2len(msg); /* char after l2 data */ + if (tail[-1] == '\0') + /* nothing to do */; + else if (msgb_tailroom(msg) > 0) + tail[0] = '\0'; + else if (tail[-1] == '\r' || tail[-1] == '\n') + tail[-1] = '\0'; + else { + LOGP(DLMGCP, LOGL_ERROR, "Cannot NUL terminate MGCP message: " + "Length: %d, Buffer size: %d\n", + msgb_l2len(msg), msg->data_len); + return -ENOTSUP; + } + return 0; +} diff --git a/src/libosmo-mgcp/mgcp_network.c b/src/libosmo-mgcp/mgcp_network.c new file mode 100644 index 000000000..8ccfb4269 --- /dev/null +++ b/src/libosmo-mgcp/mgcp_network.c @@ -0,0 +1,1166 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther + * (C) 2009-2012 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 . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +#warning "Make use of the rtp proxy code" + + +#define RTP_SEQ_MOD (1 << 16) +#define RTP_MAX_DROPOUT 3000 +#define RTP_MAX_MISORDER 100 +#define RTP_BUF_SIZE 4096 + +enum { + MGCP_PROTO_RTP, + MGCP_PROTO_RTCP, +}; + +/** + * This does not need to be a precision timestamp and + * is allowed to wrap quite fast. The returned value is + * 1/unit seconds. + */ +static uint32_t get_current_ts(unsigned unit) +{ + struct timespec tp; + uint64_t ret; + + if (!unit) + return 0; + + memset(&tp, 0, sizeof(tp)); + if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) + LOGP(DLMGCP, LOGL_NOTICE, + "Getting the clock failed.\n"); + + /* convert it to 1/unit seconds */ + ret = tp.tv_sec; + ret *= unit; + ret += (int64_t)tp.tv_nsec * unit / 1000 / 1000 / 1000; + + return ret; +} + +int mgcp_udp_send(int fd, struct in_addr *addr, int port, char *buf, int len) +{ + struct sockaddr_in out; + out.sin_family = AF_INET; + out.sin_port = port; + memcpy(&out.sin_addr, addr, sizeof(*addr)); + + return sendto(fd, buf, len, 0, (struct sockaddr *)&out, sizeof(out)); +} + +int mgcp_send_dummy(struct mgcp_endpoint *endp) +{ + static char buf[] = { MGCP_DUMMY_LOAD }; + int rc; + int was_rtcp = 0; + + rc = mgcp_udp_send(endp->net_end.rtp.fd, &endp->net_end.addr, + endp->net_end.rtp_port, buf, 1); + + if (rc == -1) + goto failed; + + if (endp->tcfg->omit_rtcp) + return rc; + + was_rtcp = 1; + rc = mgcp_udp_send(endp->net_end.rtcp.fd, &endp->net_end.addr, + endp->net_end.rtcp_port, buf, 1); + + if (rc >= 0) + return rc; + +failed: + LOGP(DLMGCP, LOGL_ERROR, + "Failed to send dummy %s packet: %s on: 0x%x to %s:%d\n", + was_rtcp ? "RTCP" : "RTP", + strerror(errno), ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), + was_rtcp ? endp->net_end.rtcp_port : endp->net_end.rtp_port); + + return -1; +} + +static int32_t compute_timestamp_aligment_error(struct mgcp_rtp_stream_state *sstate, + int ptime, uint32_t timestamp) +{ + int32_t timestamp_delta; + + if (ptime == 0) + return 0; + + /* Align according to: T - Tlast = k * Tptime */ + timestamp_delta = timestamp - sstate->last_timestamp; + + return timestamp_delta % ptime; +} + +static int check_rtp_timestamp(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_stream_state *sstate, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, + uint16_t seq, uint32_t timestamp, + const char *text, int32_t *tsdelta_out) +{ + int32_t tsdelta; + int32_t timestamp_error; + + /* Not fully intialized, skip */ + if (sstate->last_tsdelta == 0 && timestamp == sstate->last_timestamp) + return 0; + + if (seq == sstate->last_seq) { + if (timestamp != sstate->last_timestamp) { + sstate->err_ts_counter += 1; + LOGP(DLMGCP, LOGL_ERROR, + "The %s timestamp delta is != 0 but the sequence " + "number %d is the same, " + "TS offset: %d, SeqNo offset: %d " + "on 0x%x SSRC: %u timestamp: %u " + "from %s:%d in %d\n", + text, seq, + state->timestamp_offset, state->seq_offset, + ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + return 0; + } + + tsdelta = + (int32_t)(timestamp - sstate->last_timestamp) / + (int16_t)(seq - sstate->last_seq); + + if (tsdelta == 0) { + /* Don't update *tsdelta_out */ + LOGP(DLMGCP, LOGL_NOTICE, + "The %s timestamp delta is %d " + "on 0x%x SSRC: %u timestamp: %u " + "from %s:%d in %d\n", + text, tsdelta, + ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + + return 0; + } + + if (sstate->last_tsdelta != tsdelta) { + if (sstate->last_tsdelta) { + LOGP(DLMGCP, LOGL_INFO, + "The %s timestamp delta changes from %d to %d " + "on 0x%x SSRC: %u timestamp: %u from %s:%d in %d\n", + text, sstate->last_tsdelta, tsdelta, + ENDPOINT_NUMBER(endp), sstate->ssrc, timestamp, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + } + + if (tsdelta_out) + *tsdelta_out = tsdelta; + + timestamp_error = + compute_timestamp_aligment_error(sstate, state->packet_duration, + timestamp); + + if (timestamp_error) { + sstate->err_ts_counter += 1; + LOGP(DLMGCP, LOGL_NOTICE, + "The %s timestamp has an alignment error of %d " + "on 0x%x SSRC: %u " + "SeqNo delta: %d, TS delta: %d, dTS/dSeq: %d " + "from %s:%d in mode %d. ptime: %d\n", + text, timestamp_error, + ENDPOINT_NUMBER(endp), sstate->ssrc, + (int16_t)(seq - sstate->last_seq), + (int32_t)(timestamp - sstate->last_timestamp), + tsdelta, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode, state->packet_duration); + } + return 1; +} + +/* Set the timestamp offset according to the packet duration. */ +static int adjust_rtp_timestamp_offset(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, + int16_t delta_seq, uint32_t in_timestamp) +{ + int32_t tsdelta = state->packet_duration; + int timestamp_offset; + uint32_t out_timestamp; + + if (tsdelta == 0) { + tsdelta = state->out_stream.last_tsdelta; + if (tsdelta != 0) { + LOGP(DLMGCP, LOGL_NOTICE, + "A fixed packet duration is not available on 0x%x, " + "using last output timestamp delta instead: %d " + "from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), tsdelta, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } else { + tsdelta = rtp_end->codec.rate * 20 / 1000; + LOGP(DLMGCP, LOGL_NOTICE, + "Fixed packet duration and last timestamp delta " + "are not available on 0x%x, " + "using fixed 20ms instead: %d " + "from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), tsdelta, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + } + + out_timestamp = state->out_stream.last_timestamp + delta_seq * tsdelta; + timestamp_offset = out_timestamp - in_timestamp; + + if (state->timestamp_offset != timestamp_offset) { + state->timestamp_offset = timestamp_offset; + + LOGP(DLMGCP, LOGL_NOTICE, + "Timestamp offset change on 0x%x SSRC: %u " + "SeqNo delta: %d, TS offset: %d, " + "from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + delta_seq, state->timestamp_offset, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + + return timestamp_offset; +} + +/* Set the timestamp offset according to the packet duration. */ +static int align_rtp_timestamp_offset(struct mgcp_endpoint *endp, + struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, + struct sockaddr_in *addr, + uint32_t timestamp) +{ + int timestamp_error = 0; + int ptime = state->packet_duration; + + /* Align according to: T + Toffs - Tlast = k * Tptime */ + + timestamp_error = compute_timestamp_aligment_error( + &state->out_stream, ptime, + timestamp + state->timestamp_offset); + + if (timestamp_error) { + state->timestamp_offset += ptime - timestamp_error; + + LOGP(DLMGCP, LOGL_NOTICE, + "Corrected timestamp alignment error of %d on 0x%x SSRC: %u " + "new TS offset: %d, " + "from %s:%d in %d\n", + timestamp_error, + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + state->timestamp_offset, inet_ntoa(addr->sin_addr), + ntohs(addr->sin_port), endp->conn_mode); + } + + OSMO_ASSERT(compute_timestamp_aligment_error(&state->out_stream, ptime, + timestamp + state->timestamp_offset) == 0); + + return timestamp_error; +} + +int mgcp_rtp_processing_default(struct mgcp_endpoint *endp, struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size) +{ + return 0; +} + +int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end) +{ + return 0; +} + +void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp, + int *payload_type, + const char**audio_name, + const char**fmtp_extra) +{ + /* Use the BTS side parameters when passing the SDP data (for + * downlink) to the net peer. + */ + *payload_type = endp->bts_end.codec.payload_type; + *audio_name = endp->bts_end.codec.audio_name; + *fmtp_extra = endp->bts_end.fmtp_extra; +} + + +void mgcp_rtp_annex_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, + const uint16_t seq, const int32_t transit, + const uint32_t ssrc) +{ + int32_t d; + + /* initialize or re-initialize */ + if (!state->stats_initialized || state->stats_ssrc != ssrc) { + state->stats_initialized = 1; + state->stats_base_seq = seq; + state->stats_max_seq = seq - 1; + state->stats_ssrc = ssrc; + state->stats_jitter = 0; + state->stats_transit = transit; + state->stats_cycles = 0; + } else { + uint16_t udelta; + + /* + * The below takes the shape of the validation of + * Appendix A. Check if there is something weird with + * the sequence number, otherwise check for a wrap + * around in the sequence number. + * It can't wrap during the initialization so let's + * skip it here. The Appendix A probably doesn't have + * this issue because of the probation. + */ + udelta = seq - state->stats_max_seq; + if (udelta < RTP_MAX_DROPOUT) { + if (seq < state->stats_max_seq) + state->stats_cycles += RTP_SEQ_MOD; + } else if (udelta <= RTP_SEQ_MOD - RTP_MAX_MISORDER) { + LOGP(DLMGCP, LOGL_NOTICE, + "RTP seqno made a very large jump on 0x%x delta: %u\n", + ENDPOINT_NUMBER(endp), udelta); + } + } + + /* + * Calculate the jitter between the two packages. The TS should be + * taken closer to the read function. This was taken from the + * Appendix A of RFC 3550. Timestamp and arrival_time have a 1/rate + * resolution. + */ + d = transit - state->stats_transit; + state->stats_transit = transit; + if (d < 0) + d = -d; + state->stats_jitter += d - ((state->stats_jitter + 8) >> 4); + state->stats_max_seq = seq; +} + + + +/** + * The RFC 3550 Appendix A assumes there are multiple sources but + * some of the supported endpoints (e.g. the nanoBTS) can only handle + * one source and this code will patch RTP header to appear as if there + * is only one source. + * There is also no probation period for new sources. Every RTP header + * we receive will be seen as a switch in streams. + */ +void mgcp_patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, struct sockaddr_in *addr, + char *data, int len) +{ + uint32_t arrival_time; + int32_t transit; + uint16_t seq; + uint32_t timestamp, ssrc; + struct rtp_hdr *rtp_hdr; + int payload = rtp_end->codec.payload_type; + + if (len < sizeof(*rtp_hdr)) + return; + + 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); + ssrc = ntohl(rtp_hdr->ssrc); + transit = arrival_time - timestamp; + + mgcp_rtp_annex_count(endp, state, seq, transit, ssrc); + + if (!state->initialized) { + state->initialized = 1; + state->in_stream.last_seq = seq - 1; + state->in_stream.ssrc = state->orig_ssrc = ssrc; + 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_timestamp = timestamp; + state->out_stream.ssrc = ssrc - 1; /* force output SSRC change */ + LOGP(DLMGCP, LOGL_INFO, + "Initializing stream on 0x%x SSRC: %u timestamp: %u " + "pkt-duration: %d, from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + state->seq_offset, state->packet_duration, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + if (state->packet_duration == 0) { + state->packet_duration = rtp_end->codec.rate * 20 / 1000; + LOGP(DLMGCP, LOGL_NOTICE, + "Fixed packet duration is not available on 0x%x, " + "using fixed 20ms instead: %d from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), state->packet_duration, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + } else if (state->in_stream.ssrc != ssrc) { + LOGP(DLMGCP, LOGL_NOTICE, + "The SSRC changed on 0x%x: %u -> %u " + "from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), + state->in_stream.ssrc, rtp_hdr->ssrc, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + + state->in_stream.ssrc = ssrc; + if (rtp_end->force_constant_ssrc) { + int16_t delta_seq; + + /* Always increment seqno by 1 */ + state->seq_offset = + (state->out_stream.last_seq + 1) - seq; + + /* Estimate number of packets that would have been sent */ + delta_seq = + (arrival_time - state->in_stream.last_arrival_time + + state->packet_duration/2) / + state->packet_duration; + + adjust_rtp_timestamp_offset(endp, state, rtp_end, addr, + delta_seq, timestamp); + + state->patch_ssrc = 1; + ssrc = state->orig_ssrc; + if (rtp_end->force_constant_ssrc != -1) + rtp_end->force_constant_ssrc -= 1; + + LOGP(DLMGCP, LOGL_NOTICE, + "SSRC patching enabled on 0x%x SSRC: %u " + "SeqNo offset: %d, TS offset: %d " + "from %s:%d in %d\n", + ENDPOINT_NUMBER(endp), state->in_stream.ssrc, + state->seq_offset, state->timestamp_offset, + inet_ntoa(addr->sin_addr), ntohs(addr->sin_port), + endp->conn_mode); + } + + state->in_stream.last_tsdelta = 0; + } else { + /* Compute current per-packet timestamp delta */ + check_rtp_timestamp(endp, state, &state->in_stream, rtp_end, addr, + seq, timestamp, "input", + &state->in_stream.last_tsdelta); + + if (state->patch_ssrc) + ssrc = state->orig_ssrc; + } + + /* Save before patching */ + state->in_stream.last_timestamp = timestamp; + state->in_stream.last_seq = seq; + state->in_stream.last_arrival_time = arrival_time; + + if (rtp_end->force_aligned_timing && + state->out_stream.ssrc == ssrc && state->packet_duration) + /* Align the timestamp offset */ + align_rtp_timestamp_offset(endp, state, rtp_end, addr, timestamp); + + /* Store the updated SSRC back to the packet */ + if (state->patch_ssrc) + rtp_hdr->ssrc = htonl(ssrc); + + /* Apply the offset and store it back to the packet. + * This won't change anything if the offset is 0, so the conditional is + * omitted. */ + seq += state->seq_offset; + rtp_hdr->sequence = htons(seq); + timestamp += state->timestamp_offset; + rtp_hdr->timestamp = htonl(timestamp); + + /* Check again, whether the timestamps are still valid */ + if (state->out_stream.ssrc == ssrc) + check_rtp_timestamp(endp, state, &state->out_stream, rtp_end, + addr, seq, timestamp, "output", + &state->out_stream.last_tsdelta); + + /* Save output values */ + state->out_stream.last_seq = seq; + state->out_stream.last_timestamp = timestamp; + state->out_stream.ssrc = ssrc; + + if (payload < 0) + return; + +#if 0 + DEBUGP(DLMGCP, "Payload hdr payload %u -> endp payload %u\n", + rtp_hdr->payload_type, payload); + rtp_hdr->payload_type = payload; +#endif +} + +/* + * The below code is for dispatching. We have a dedicated port for + * the data coming from the net and one to discover the BTS. + */ +static int forward_data(int fd, struct mgcp_rtp_tap *tap, const char *buf, int len) +{ + if (!tap->enabled) + return 0; + + return sendto(fd, buf, len, 0, + (struct sockaddr *)&tap->forward, sizeof(tap->forward)); +} + +static int mgcp_send_transcoder(struct mgcp_rtp_end *end, + struct mgcp_config *cfg, int is_rtp, + const char *buf, int len) +{ + int rc; + int port; + struct sockaddr_in addr; + + port = is_rtp ? end->rtp_port : end->rtcp_port; + + addr.sin_family = AF_INET; + addr.sin_addr = cfg->transcoder_in; + addr.sin_port = port; + + rc = sendto(is_rtp ? + end->rtp.fd : + end->rtcp.fd, buf, len, 0, + (struct sockaddr *) &addr, sizeof(addr)); + + if (rc != len) + LOGP(DLMGCP, LOGL_ERROR, + "Failed to send data to the transcoder: %s\n", + strerror(errno)); + + return rc; +} + +int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, + struct sockaddr_in *addr, char *buf, int rc) +{ + struct mgcp_trunk_config *tcfg = endp->tcfg; + struct mgcp_rtp_end *rtp_end; + struct mgcp_rtp_state *rtp_state; + int tap_idx; + + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x dest %s tcfg->audio_loop %d endp->conn_mode %d (== loopback: %d)\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + tcfg->audio_loop, + endp->conn_mode, + endp->conn_mode == MGCP_CONN_LOOPBACK); + + /* For loop toggle the destination and then dispatch. */ + if (tcfg->audio_loop) + dest = !dest; + + /* Loop based on the conn_mode, maybe undoing the above */ + if (endp->conn_mode == MGCP_CONN_LOOPBACK) + dest = !dest; + + if (dest == MGCP_DEST_NET) { + rtp_end = &endp->net_end; + rtp_state = &endp->bts_state; + tap_idx = MGCP_TAP_NET_OUT; + } else { + rtp_end = &endp->bts_end; + rtp_state = &endp->net_state; + tap_idx = MGCP_TAP_BTS_OUT; + } + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x dest %s net_end %s %d %d bts_end %s %d %d rtp_end %s %d %d\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + + inet_ntoa(endp->net_end.addr), + ntohs(endp->net_end.rtp_port), + ntohs(endp->net_end.rtcp_port), + + inet_ntoa(endp->bts_end.addr), + ntohs(endp->bts_end.rtp_port), + ntohs(endp->bts_end.rtcp_port), + + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + + if (!rtp_end->output_enabled) { + rtp_end->dropped_packets += 1; + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x output disabled, drop to %s %s %d %d\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + } else if (is_rtp) { + int cont; + int nbytes = 0; + int len = rc; + do { + cont = endp->cfg->rtp_processing_cb(endp, rtp_end, + buf, &len, RTP_BUF_SIZE); + if (cont < 0) + break; + + mgcp_patch_and_count(endp, rtp_state, rtp_end, addr, buf, len); + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x process/send to %s %s %d %d\n", + ENDPOINT_NUMBER(endp), + (dest == MGCP_DEST_NET)? "net" : "bts", + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + forward_data(rtp_end->rtp.fd, &endp->taps[tap_idx], + buf, len); + + /* FIXME: HACK HACK HACK. See OS#2459. + * The ip.access nano3G needs the first RTP payload's first two bytes to read hex + * '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 (tap_idx == MGCP_TAP_BTS_OUT + && !rtp_state->patched_first_rtp_payload) { + uint8_t *data = (uint8_t*)&buf[12]; + data[0] = 0xe4; + data[1] = 0x00; + 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 (rc <= 0) + return rc; + nbytes += rc; + len = cont; + } while (len > 0); + return nbytes; + } else if (!tcfg->omit_rtcp) { + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x send to %s %s %d %d\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + + return mgcp_udp_send(rtp_end->rtcp.fd, + &rtp_end->addr, + rtp_end->rtcp_port, buf, rc); + } + + return 0; +} + +static int receive_from(struct mgcp_endpoint *endp, int fd, struct sockaddr_in *addr, + char *buf, int bufsize) +{ + int rc; + socklen_t slen = sizeof(*addr); + + rc = recvfrom(fd, buf, bufsize, 0, + (struct sockaddr *) addr, &slen); + if (rc < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to receive message on: 0x%x errno: %d/%s\n", + ENDPOINT_NUMBER(endp), errno, strerror(errno)); + return -1; + } + + /* do not forward aynthing... maybe there is a packet from the bts */ + if (!endp->allocated) + return -1; + + #warning "Slight spec violation. With connection mode recvonly we should attempt to forward." + + return rc; +} + +static int rtp_data_net(struct osmo_fd *fd, unsigned int what) +{ + char buf[RTP_BUF_SIZE]; + struct sockaddr_in addr; + struct mgcp_endpoint *endp; + int rc, proto; + + endp = (struct mgcp_endpoint *) fd->data; + + rc = receive_from(endp, fd->fd, &addr, buf, sizeof(buf)); + if (rc <= 0) + return -1; + + LOGP(DLMGCP, LOGL_DEBUG, + "endpoint %x", + ENDPOINT_NUMBER(endp)); + LOGPC(DLMGCP, LOGL_DEBUG, + " from net %s %d", + inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + LOGPC(DLMGCP, LOGL_DEBUG, + " net_end %s %d %d", + inet_ntoa(endp->net_end.addr), + ntohs(endp->net_end.rtp_port), + ntohs(endp->net_end.rtcp_port)); + LOGPC(DLMGCP, LOGL_DEBUG, + " bts_end %s %d %d\n", + inet_ntoa(endp->bts_end.addr), + ntohs(endp->bts_end.rtp_port), + ntohs(endp->bts_end.rtcp_port) + ); + + if (memcmp(&addr.sin_addr, &endp->net_end.addr, sizeof(addr.sin_addr)) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "rtp_data_net: Endpoint 0x%x data from wrong address %s vs. ", + ENDPOINT_NUMBER(endp), inet_ntoa(addr.sin_addr)); + LOGPC(DLMGCP, LOGL_ERROR, + "%s\n", inet_ntoa(endp->net_end.addr)); + return -1; + } + + switch(endp->type) { + case MGCP_RTP_DEFAULT: + case MGCP_RTP_TRANSCODED: + if (endp->net_end.rtp_port != addr.sin_port && + endp->net_end.rtcp_port != addr.sin_port) { + LOGP(DLMGCP, LOGL_ERROR, + "rtp_data_net: Data from wrong source port %d on 0x%x\n", + ntohs(addr.sin_port), ENDPOINT_NUMBER(endp)); + return -1; + } + break; + case MGCP_OSMUX_BSC: + case MGCP_OSMUX_BSC_NAT: + break; + } + + LOGP(DLMGCP, LOGL_DEBUG, + "rtp_data_net: Endpoint %x data from %s %d\n", + ENDPOINT_NUMBER(endp), + inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + + /* throw away the dummy message */ + if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) { + LOGP(DLMGCP, LOGL_NOTICE, "Filtered dummy from network on 0x%x\n", + ENDPOINT_NUMBER(endp)); + return 0; + } + + proto = fd == &endp->net_end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP; + endp->net_end.packets += 1; + endp->net_end.octets += rc; + + forward_data(fd->fd, &endp->taps[MGCP_TAP_NET_IN], buf, rc); + + switch (endp->type) { + case MGCP_RTP_DEFAULT: + return mgcp_send(endp, MGCP_DEST_BTS, proto == MGCP_PROTO_RTP, + &addr, buf, rc); + case MGCP_RTP_TRANSCODED: + return mgcp_send_transcoder(&endp->trans_net, endp->cfg, + proto == MGCP_PROTO_RTP, buf, rc); + case MGCP_OSMUX_BSC_NAT: + return osmux_xfrm_to_osmux(MGCP_DEST_BTS, buf, rc, endp); + case MGCP_OSMUX_BSC: /* Should not happen */ + break; + } + + LOGP(DLMGCP, LOGL_ERROR, "Bad MGCP type %u on endpoint %u\n", + endp->type, ENDPOINT_NUMBER(endp)); + return 0; +} + +static void discover_bts(struct mgcp_endpoint *endp, int proto, struct sockaddr_in *addr) +{ + struct mgcp_config *cfg = endp->cfg; + + if (proto == MGCP_PROTO_RTP && endp->bts_end.rtp_port == 0) { + if (!cfg->bts_ip || + memcmp(&addr->sin_addr, + &cfg->bts_in, sizeof(cfg->bts_in)) == 0 || + memcmp(&addr->sin_addr, + &endp->bts_end.addr, sizeof(endp->bts_end.addr)) == 0) { + + endp->bts_end.rtp_port = addr->sin_port; + endp->bts_end.addr = addr->sin_addr; + + LOGP(DLMGCP, LOGL_NOTICE, + "Found BTS for endpoint: 0x%x on port: %d/%d of %s\n", + ENDPOINT_NUMBER(endp), ntohs(endp->bts_end.rtp_port), + ntohs(endp->bts_end.rtcp_port), inet_ntoa(addr->sin_addr)); + } + } else if (proto == MGCP_PROTO_RTCP && endp->bts_end.rtcp_port == 0) { + if (memcmp(&endp->bts_end.addr, &addr->sin_addr, + sizeof(endp->bts_end.addr)) == 0) { + endp->bts_end.rtcp_port = addr->sin_port; + } + } +} + +static int rtp_data_bts(struct osmo_fd *fd, unsigned int what) +{ + char buf[RTP_BUF_SIZE]; + struct sockaddr_in addr; + struct mgcp_endpoint *endp; + int rc, proto; + + endp = (struct mgcp_endpoint *) fd->data; + + rc = receive_from(endp, fd->fd, &addr, buf, sizeof(buf)); + if (rc <= 0) + return -1; + + proto = fd == &endp->bts_end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP; + + /* We have no idea who called us, maybe it is the BTS. */ + /* it was the BTS... */ + discover_bts(endp, proto, &addr); + + if (memcmp(&endp->bts_end.addr, &addr.sin_addr, sizeof(addr.sin_addr)) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "rtp_data_bts: Data from wrong bts %s on 0x%x\n", + inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp)); + return -1; + } + + if (endp->bts_end.rtp_port != addr.sin_port && + endp->bts_end.rtcp_port != addr.sin_port) { + LOGP(DLMGCP, LOGL_ERROR, + "rtp_data_bts: ata from wrong bts source port %d on 0x%x\n", + ntohs(addr.sin_port), ENDPOINT_NUMBER(endp)); + return -1; + } + + LOGP(DLMGCP, LOGL_DEBUG, + "rtp_data_bts: Endpoint %x data from %s %d\n", + ENDPOINT_NUMBER(endp), + inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + + /* throw away the dummy message */ + if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) { + LOGP(DLMGCP, LOGL_NOTICE, "Filtered dummy from bts on 0x%x\n", + ENDPOINT_NUMBER(endp)); + return 0; + } + + /* do this before the loop handling */ + endp->bts_end.packets += 1; + endp->bts_end.octets += rc; + + forward_data(fd->fd, &endp->taps[MGCP_TAP_BTS_IN], buf, rc); + + switch (endp->type) { + case MGCP_RTP_DEFAULT: + LOGP(DLMGCP, LOGL_DEBUG, + "rtp_data_bts: Endpoint %x MGCP_RTP_DEFAULT\n", + ENDPOINT_NUMBER(endp)); + return mgcp_send(endp, MGCP_DEST_NET, proto == MGCP_PROTO_RTP, + &addr, buf, rc); + case MGCP_RTP_TRANSCODED: + return mgcp_send_transcoder(&endp->trans_bts, endp->cfg, + proto == MGCP_PROTO_RTP, buf, rc); + case MGCP_OSMUX_BSC: + /* OSMUX translation: BTS -> BSC */ + return osmux_xfrm_to_osmux(MGCP_DEST_NET, buf, rc, endp); + case MGCP_OSMUX_BSC_NAT: + break; /* Should not happen */ + } + + LOGP(DLMGCP, LOGL_ERROR, "Bad MGCP type %u on endpoint %u\n", + endp->type, ENDPOINT_NUMBER(endp)); + return 0; +} + +static int rtp_data_transcoder(struct mgcp_rtp_end *end, struct mgcp_endpoint *_endp, + int dest, struct osmo_fd *fd) +{ + char buf[RTP_BUF_SIZE]; + struct sockaddr_in addr; + struct mgcp_config *cfg; + int rc, proto; + + cfg = _endp->cfg; + rc = receive_from(_endp, fd->fd, &addr, buf, sizeof(buf)); + if (rc <= 0) + return -1; + + proto = fd == &end->rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP; + + if (memcmp(&addr.sin_addr, &cfg->transcoder_in, sizeof(addr.sin_addr)) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Data not coming from transcoder dest: %d %s on 0x%x\n", + dest, inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(_endp)); + return -1; + } + + if (end->rtp_port != addr.sin_port && + end->rtcp_port != addr.sin_port) { + LOGP(DLMGCP, LOGL_ERROR, + "Data from wrong transcoder dest %d source port %d on 0x%x\n", + dest, ntohs(addr.sin_port), ENDPOINT_NUMBER(_endp)); + return -1; + } + + /* throw away the dummy message */ + if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) { + LOGP(DLMGCP, LOGL_NOTICE, "Filtered dummy from transcoder dest %d on 0x%x\n", + dest, ENDPOINT_NUMBER(_endp)); + return 0; + } + + end->packets += 1; + return mgcp_send(_endp, dest, proto == MGCP_PROTO_RTP, &addr, buf, rc); +} + +static int rtp_data_trans_net(struct osmo_fd *fd, unsigned int what) +{ + struct mgcp_endpoint *endp; + endp = (struct mgcp_endpoint *) fd->data; + + return rtp_data_transcoder(&endp->trans_net, endp, MGCP_DEST_NET, fd); +} + +static int rtp_data_trans_bts(struct osmo_fd *fd, unsigned int what) +{ + struct mgcp_endpoint *endp; + endp = (struct mgcp_endpoint *) fd->data; + + return rtp_data_transcoder(&endp->trans_bts, endp, MGCP_DEST_BTS, fd); +} + +int mgcp_create_bind(const char *source_addr, struct osmo_fd *fd, int port) +{ + struct sockaddr_in addr; + int on = 1; + + fd->fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd->fd < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to create UDP port.\n"); + return -1; + } + + setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_aton(source_addr, &addr.sin_addr); + + if (bind(fd->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(fd->fd); + fd->fd = -1; + return -1; + } + + return 0; +} + +int mgcp_set_ip_tos(int fd, int tos) +{ + int ret; + ret = setsockopt(fd, IPPROTO_IP, IP_TOS, + &tos, sizeof(tos)); + return ret != 0; +} + +static int bind_rtp(struct mgcp_config *cfg, const char *source_addr, + struct mgcp_rtp_end *rtp_end, int endpno) +{ + if (mgcp_create_bind(source_addr, &rtp_end->rtp, + rtp_end->local_port) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to create RTP port: %s:%d on 0x%x\n", + source_addr, rtp_end->local_port, endpno); + goto cleanup0; + } + + if (mgcp_create_bind(source_addr, &rtp_end->rtcp, + rtp_end->local_port + 1) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to create RTCP port: %s:%d on 0x%x\n", + source_addr, rtp_end->local_port + 1, endpno); + goto cleanup1; + } + + mgcp_set_ip_tos(rtp_end->rtp.fd, cfg->endp_dscp); + mgcp_set_ip_tos(rtp_end->rtcp.fd, cfg->endp_dscp); + + rtp_end->rtp.when = BSC_FD_READ; + if (osmo_fd_register(&rtp_end->rtp) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to register RTP port %d on 0x%x\n", + rtp_end->local_port, endpno); + goto cleanup2; + } + + rtp_end->rtcp.when = BSC_FD_READ; + if (osmo_fd_register(&rtp_end->rtcp) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to register RTCP port %d on 0x%x\n", + rtp_end->local_port + 1, endpno); + goto cleanup3; + } + + return 0; + +cleanup3: + osmo_fd_unregister(&rtp_end->rtp); +cleanup2: + close(rtp_end->rtcp.fd); + rtp_end->rtcp.fd = -1; +cleanup1: + close(rtp_end->rtp.fd); + rtp_end->rtp.fd = -1; +cleanup0: + return -1; +} + +static int int_bind(const char *port, + struct mgcp_rtp_end *end, int (*cb)(struct osmo_fd *, unsigned), + struct mgcp_endpoint *_endp, + const char *source_addr, int rtp_port) +{ + if (end->rtp.fd != -1 || end->rtcp.fd != -1) { + LOGP(DLMGCP, LOGL_ERROR, "Previous %s was still bound on %d\n", + port, ENDPOINT_NUMBER(_endp)); + mgcp_free_rtp_port(end); + } + + end->local_port = rtp_port; + end->rtp.cb = cb; + end->rtp.data = _endp; + end->rtcp.data = _endp; + end->rtcp.cb = cb; + return bind_rtp(_endp->cfg, source_addr, end, ENDPOINT_NUMBER(_endp)); +} + +int mgcp_bind_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("bts-port", &endp->bts_end, + rtp_data_bts, endp, + mgcp_bts_src_addr(endp), rtp_port); +} + +int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("net-port", &endp->net_end, + rtp_data_net, endp, + mgcp_net_src_addr(endp), rtp_port); +} + +int mgcp_bind_trans_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("trans-net", &endp->trans_net, + rtp_data_trans_net, endp, + endp->cfg->source_addr, rtp_port); +} + +int mgcp_bind_trans_bts_rtp_port(struct mgcp_endpoint *endp, int rtp_port) +{ + return int_bind("trans-bts", &endp->trans_bts, + rtp_data_trans_bts, endp, + endp->cfg->source_addr, rtp_port); +} + +int mgcp_free_rtp_port(struct mgcp_rtp_end *end) +{ + if (end->rtp.fd != -1) { + close(end->rtp.fd); + end->rtp.fd = -1; + osmo_fd_unregister(&end->rtp); + } + + if (end->rtcp.fd != -1) { + close(end->rtcp.fd); + end->rtcp.fd = -1; + osmo_fd_unregister(&end->rtcp); + } + + return 0; +} + + +void mgcp_state_calc_loss(struct mgcp_rtp_state *state, + struct mgcp_rtp_end *end, uint32_t *expected, + int *loss) +{ + *expected = state->stats_cycles + state->stats_max_seq; + *expected = *expected - state->stats_base_seq + 1; + + if (!state->stats_initialized) { + *expected = 0; + *loss = 0; + return; + } + + /* + * Make sure the sign is correct and use the biggest + * positive/negative number that fits. + */ + *loss = *expected - end->packets; + if (*expected < end->packets) { + if (*loss > 0) + *loss = INT_MIN; + } else { + if (*loss < 0) + *loss = INT_MAX; + } +} + +uint32_t mgcp_state_calc_jitter(struct mgcp_rtp_state *state) +{ + if (!state->stats_initialized) + return 0; + return state->stats_jitter >> 4; +} diff --git a/src/libosmo-mgcp/mgcp_osmux.c b/src/libosmo-mgcp/mgcp_osmux.c new file mode 100644 index 000000000..743d3f9cc --- /dev/null +++ b/src/libosmo-mgcp/mgcp_osmux.c @@ -0,0 +1,583 @@ +/* + * (C) 2012-2013 by Pablo Neira Ayuso + * (C) 2012-2013 by On Waves ehf + * All rights not specifically granted under this license are 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. + */ + +#include /* for printf */ +#include /* for memcpy */ +#include /* for abs */ +#include /* for PRIu64 */ +#include +#include +#include + +#include +#include + +#include +#include +#include + +static struct osmo_fd osmux_fd; + +static LLIST_HEAD(osmux_handle_list); + +struct osmux_handle { + struct llist_head head; + struct osmux_in_handle *in; + struct in_addr rem_addr; + int rem_port; + int refcnt; +}; + +static void *osmux; + +static void osmux_deliver(struct msgb *batch_msg, void *data) +{ + struct osmux_handle *handle = data; + struct sockaddr_in out = { + .sin_family = AF_INET, + .sin_port = handle->rem_port, + }; + + memcpy(&out.sin_addr, &handle->rem_addr, sizeof(handle->rem_addr)); + sendto(osmux_fd.fd, batch_msg->data, batch_msg->len, 0, + (struct sockaddr *)&out, sizeof(out)); + msgb_free(batch_msg); +} + +static struct osmux_handle * +osmux_handle_find_get(struct in_addr *addr, int rem_port) +{ + struct osmux_handle *h; + + /* Lookup for existing OSMUX handle for this destination address. */ + llist_for_each_entry(h, &osmux_handle_list, head) { + if (memcmp(&h->rem_addr, addr, sizeof(struct in_addr)) == 0 && + h->rem_port == rem_port) { + LOGP(DLMGCP, LOGL_DEBUG, "using existing OSMUX handle " + "for addr=%s:%d\n", + inet_ntoa(*addr), ntohs(rem_port)); + h->refcnt++; + return h; + } + } + + return NULL; +} + +static void osmux_handle_put(struct osmux_in_handle *in) +{ + struct osmux_handle *h; + + /* Lookup for existing OSMUX handle for this destination address. */ + llist_for_each_entry(h, &osmux_handle_list, head) { + if (h->in == in) { + if (--h->refcnt == 0) { + LOGP(DLMGCP, LOGL_INFO, + "Releasing unused osmux handle for %s:%d\n", + inet_ntoa(h->rem_addr), + ntohs(h->rem_port)); + LOGP(DLMGCP, LOGL_INFO, "Stats: " + "input RTP msgs: %u bytes: %"PRIu64" " + "output osmux msgs: %u bytes: %"PRIu64"\n", + in->stats.input_rtp_msgs, + in->stats.input_rtp_bytes, + in->stats.output_osmux_msgs, + in->stats.output_osmux_bytes); + llist_del(&h->head); + osmux_xfrm_input_fini(h->in); + talloc_free(h); + } + return; + } + } + LOGP(DLMGCP, LOGL_ERROR, "cannot find Osmux input handle %p\n", in); +} + +static struct osmux_handle * +osmux_handle_alloc(struct mgcp_config *cfg, struct in_addr *addr, int rem_port) +{ + struct osmux_handle *h; + + h = talloc_zero(osmux, struct osmux_handle); + if (!h) + return NULL; + h->rem_addr = *addr; + h->rem_port = rem_port; + h->refcnt++; + + h->in = talloc_zero(h, struct osmux_in_handle); + if (!h->in) { + talloc_free(h); + return NULL; + } + + h->in->osmux_seq = 0; /* sequence number to start OSmux message from */ + h->in->batch_factor = cfg->osmux_batch; + /* If batch size is zero, the library defaults to 1470 bytes. */ + h->in->batch_size = cfg->osmux_batch_size; + h->in->deliver = osmux_deliver; + osmux_xfrm_input_init(h->in); + h->in->data = h; + + llist_add(&h->head, &osmux_handle_list); + + LOGP(DLMGCP, LOGL_DEBUG, "created new OSMUX handle for addr=%s:%d\n", + inet_ntoa(*addr), ntohs(rem_port)); + + return h; +} + +static struct osmux_in_handle * +osmux_handle_lookup(struct mgcp_config *cfg, struct in_addr *addr, int rem_port) +{ + struct osmux_handle *h; + + h = osmux_handle_find_get(addr, rem_port); + if (h != NULL) + return h->in; + + h = osmux_handle_alloc(cfg, addr, rem_port); + if (h == NULL) + return NULL; + + return h->in; +} + +int osmux_xfrm_to_osmux(int type, char *buf, int rc, struct mgcp_endpoint *endp) +{ + int ret; + struct msgb *msg; + + msg = msgb_alloc(4096, "RTP"); + if (!msg) + return 0; + + memcpy(msg->data, buf, rc); + msgb_put(msg, rc); + + while ((ret = osmux_xfrm_input(endp->osmux.in, msg, endp->osmux.cid)) > 0) { + /* batch full, build and deliver it */ + osmux_xfrm_input_deliver(endp->osmux.in); + } + return 0; +} + +static struct mgcp_endpoint * +endpoint_lookup(struct mgcp_config *cfg, int cid, + struct in_addr *from_addr, int type) +{ + struct mgcp_endpoint *tmp = NULL; + int i; + + /* Lookup for the endpoint that corresponds to this port */ + for (i=0; itrunk.number_endpoints; i++) { + struct in_addr *this; + + tmp = &cfg->trunk.endpoints[i]; + + if (!tmp->allocated) + continue; + + switch(type) { + case MGCP_DEST_NET: + this = &tmp->net_end.addr; + break; + case MGCP_DEST_BTS: + this = &tmp->bts_end.addr; + break; + default: + /* Should not ever happen */ + LOGP(DLMGCP, LOGL_ERROR, "Bad type %d. Fix your code.\n", type); + return NULL; + } + + if (tmp->osmux.cid == cid && this->s_addr == from_addr->s_addr) + return tmp; + } + + LOGP(DLMGCP, LOGL_ERROR, "Cannot find endpoint with cid=%d\n", cid); + + return NULL; +} + +static void scheduled_tx_net_cb(struct msgb *msg, void *data) +{ + struct mgcp_endpoint *endp = data; + struct sockaddr_in addr = { + .sin_addr = endp->net_end.addr, + .sin_port = endp->net_end.rtp_port, + }; + + endp->bts_end.octets += msg->len; + endp->bts_end.packets++; + + mgcp_send(endp, MGCP_DEST_NET, 1, &addr, (char *)msg->data, msg->len); + msgb_free(msg); +} + +static void scheduled_tx_bts_cb(struct msgb *msg, void *data) +{ + struct mgcp_endpoint *endp = data; + struct sockaddr_in addr = { + .sin_addr = endp->bts_end.addr, + .sin_port = endp->bts_end.rtp_port, + }; + + endp->net_end.octets += msg->len; + endp->net_end.packets++; + + mgcp_send(endp, MGCP_DEST_BTS, 1, &addr, (char *)msg->data, msg->len); + msgb_free(msg); +} + +static struct msgb *osmux_recv(struct osmo_fd *ofd, struct sockaddr_in *addr) +{ + struct msgb *msg; + socklen_t slen = sizeof(*addr); + int ret; + + msg = msgb_alloc(4096, "OSMUX"); + if (!msg) { + LOGP(DLMGCP, LOGL_ERROR, "cannot allocate message\n"); + return NULL; + } + ret = recvfrom(ofd->fd, msg->data, msg->data_len, 0, + (struct sockaddr *)addr, &slen); + if (ret <= 0) { + msgb_free(msg); + LOGP(DLMGCP, LOGL_ERROR, "cannot receive message\n"); + return NULL; + } + msgb_put(msg, ret); + + return msg; +} + +#define osmux_chunk_length(msg, rem) (rem - msg->len); + +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; + + msg = osmux_recv(ofd, &addr); + if (!msg) + return -1; + + /* not any further processing dummy messages */ + if (msg->data[0] == MGCP_DUMMY_LOAD) + goto out; + + rem = msg->len; + while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { + struct mgcp_endpoint *endp; + + /* Yes, we use MGCP_DEST_NET to locate the origin */ + endp = endpoint_lookup(cfg, osmuxh->circuit_id, + &addr.sin_addr, MGCP_DEST_NET); + if (!endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot find an endpoint for circuit_id=%d\n", + osmuxh->circuit_id); + goto out; + } + endp->osmux.stats.octets += osmux_chunk_length(msg, rem); + endp->osmux.stats.chunks++; + rem = msg->len; + + osmux_xfrm_output(osmuxh, &endp->osmux.out, &list); + osmux_tx_sched(&list, scheduled_tx_bts_cb, endp); + } +out: + msgb_free(msg); + return 0; +} + +/* This is called from the bsc-nat */ +static int osmux_handle_dummy(struct mgcp_config *cfg, struct sockaddr_in *addr, + struct msgb *msg) +{ + struct mgcp_endpoint *endp; + uint8_t osmux_cid; + + if (msg->len < 1 + sizeof(osmux_cid)) { + LOGP(DLMGCP, LOGL_ERROR, + "Discarding truncated Osmux dummy load\n"); + goto out; + } + + LOGP(DLMGCP, LOGL_DEBUG, "Received Osmux dummy load from %s\n", + inet_ntoa(addr->sin_addr)); + + if (!cfg->osmux) { + LOGP(DLMGCP, LOGL_ERROR, + "bsc wants to use Osmux but bsc-nat did not request it\n"); + goto out; + } + + /* extract the osmux CID from the dummy message */ + memcpy(&osmux_cid, &msg->data[1], sizeof(osmux_cid)); + + endp = endpoint_lookup(cfg, osmux_cid, &addr->sin_addr, MGCP_DEST_BTS); + if (!endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot find endpoint for Osmux CID %d\n", osmux_cid); + goto out; + } + + if (endp->osmux.state == OSMUX_STATE_ENABLED) + goto out; + + if (osmux_enable_endpoint(endp, &addr->sin_addr, addr->sin_port) < 0 ) { + LOGP(DLMGCP, LOGL_ERROR, + "Could not enable osmux in endpoint %d\n", + ENDPOINT_NUMBER(endp)); + goto out; + } + + LOGP(DLMGCP, LOGL_INFO, "Enabling osmux in endpoint %d for %s:%u\n", + ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr), + ntohs(addr->sin_port)); +out: + msgb_free(msg); + return 0; +} + +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; + + msg = osmux_recv(ofd, &addr); + if (!msg) + return -1; + + /* not any further processing dummy messages */ + if (msg->data[0] == MGCP_DUMMY_LOAD) + return osmux_handle_dummy(cfg, &addr, msg); + + rem = msg->len; + while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { + struct mgcp_endpoint *endp; + + /* Yes, we use MGCP_DEST_BTS to locate the origin */ + endp = endpoint_lookup(cfg, osmuxh->circuit_id, + &addr.sin_addr, MGCP_DEST_BTS); + if (!endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot find an endpoint for circuit_id=%d\n", + osmuxh->circuit_id); + goto out; + } + endp->osmux.stats.octets += osmux_chunk_length(msg, rem); + endp->osmux.stats.chunks++; + rem = msg->len; + + osmux_xfrm_output(osmuxh, &endp->osmux.out, &list); + osmux_tx_sched(&list, scheduled_tx_net_cb, endp); + } +out: + msgb_free(msg); + return 0; +} + +int osmux_init(int role, struct mgcp_config *cfg) +{ + int ret; + + switch(role) { + case OSMUX_ROLE_BSC: + osmux_fd.cb = osmux_read_from_bsc_nat_cb; + break; + case OSMUX_ROLE_BSC_NAT: + osmux_fd.cb = osmux_read_from_bsc_cb; + break; + default: + LOGP(DLMGCP, LOGL_ERROR, "wrong role for OSMUX\n"); + return -1; + } + osmux_fd.data = cfg; + + ret = mgcp_create_bind(cfg->osmux_addr, &osmux_fd, cfg->osmux_port); + if (ret < 0) { + LOGP(DLMGCP, LOGL_ERROR, "cannot bind OSMUX socket\n"); + return ret; + } + mgcp_set_ip_tos(osmux_fd.fd, cfg->endp_dscp); + osmux_fd.when |= BSC_FD_READ; + + ret = osmo_fd_register(&osmux_fd); + if (ret < 0) { + LOGP(DLMGCP, LOGL_ERROR, "cannot register OSMUX socket\n"); + return ret; + } + cfg->osmux_init = 1; + + return 0; +} + +int osmux_enable_endpoint(struct mgcp_endpoint *endp, struct in_addr *addr, uint16_t port) +{ + /* If osmux is enabled, initialize the output handler. This handler is + * used to reconstruct the RTP flow from osmux. The RTP SSRC is + * allocated based on the circuit ID (endp->osmux.cid), which is unique + * in the local scope to the BSC/BSC-NAT. We use it to divide the RTP + * SSRC space (2^32) by the 256 possible circuit IDs, then randomly + * select one value from that window. Thus, we have no chance to have + * overlapping RTP SSRC traveling to the BTSes behind the BSC, + * similarly, for flows traveling to the MSC. + */ + static const uint32_t rtp_ssrc_winlen = UINT32_MAX / 256; + + if (endp->osmux.state == OSMUX_STATE_DISABLED) { + LOGP(DLMGCP, LOGL_ERROR, "Endpoint %u didn't request Osmux\n", + ENDPOINT_NUMBER(endp)); + return -1; + } + + osmux_xfrm_output_init(&endp->osmux.out, + (endp->osmux.cid * rtp_ssrc_winlen) + + (random() % rtp_ssrc_winlen)); + + endp->osmux.in = osmux_handle_lookup(endp->cfg, addr, port); + if (!endp->osmux.in) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot allocate input osmux handle\n"); + return -1; + } + if (!osmux_xfrm_input_open_circuit(endp->osmux.in, endp->osmux.cid, + endp->cfg->osmux_dummy)) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot open osmux circuit %u\n", + endp->osmux.cid); + return -1; + } + + switch (endp->cfg->role) { + case MGCP_BSC_NAT: + endp->type = MGCP_OSMUX_BSC_NAT; + break; + case MGCP_BSC: + endp->type = MGCP_OSMUX_BSC; + break; + } + endp->osmux.state = OSMUX_STATE_ENABLED; + + return 0; +} + +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); + osmux_xfrm_input_close_circuit(endp->osmux.in, endp->osmux.cid); + endp->osmux.state = OSMUX_STATE_DISABLED; + endp->osmux.cid = -1; + osmux_handle_put(endp->osmux.in); +} + +void osmux_release_cid(struct mgcp_endpoint *endp) +{ + if (endp->osmux.allocated_cid >= 0) + osmux_put_cid(endp->osmux.allocated_cid); + endp->osmux.allocated_cid = -1; +} + +void osmux_allocate_cid(struct mgcp_endpoint *endp) +{ + osmux_release_cid(endp); + endp->osmux.allocated_cid = osmux_get_cid(); +} + +/* We don't need to send the dummy load for osmux so often as another endpoint + * may have already punched the hole in the firewall. This approach is simple + * though. + */ +int osmux_send_dummy(struct mgcp_endpoint *endp) +{ + char buf[1 + sizeof(uint8_t)]; + struct in_addr addr_unset = {}; + + buf[0] = MGCP_DUMMY_LOAD; + memcpy(&buf[1], &endp->osmux.cid, sizeof(endp->osmux.cid)); + + /* Wait until we have the connection information from MDCX */ + if (memcmp(&endp->net_end.addr, &addr_unset, sizeof(addr_unset)) == 0) + return 0; + + if (endp->osmux.state == OSMUX_STATE_ACTIVATING) { + if (osmux_enable_endpoint(endp, &endp->net_end.addr, + htons(endp->cfg->osmux_port)) < 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Could not activate osmux in endpoint %d\n", + ENDPOINT_NUMBER(endp)); + } + LOGP(DLMGCP, LOGL_ERROR, + "Osmux CID %u for %s:%u is now enabled\n", + endp->osmux.cid, inet_ntoa(endp->net_end.addr), + endp->cfg->osmux_port); + } + LOGP(DLMGCP, LOGL_DEBUG, + "sending OSMUX dummy load to %s CID %u\n", + inet_ntoa(endp->net_end.addr), endp->osmux.cid); + + return mgcp_udp_send(osmux_fd.fd, &endp->net_end.addr, + htons(endp->cfg->osmux_port), buf, sizeof(buf)); +} + +/* bsc-nat allocates/releases the Osmux circuit ID */ +static uint8_t osmux_cid_bitmap[(OSMUX_CID_MAX + 1) / 8]; + +int osmux_used_cid(void) +{ + int i, j, used = 0; + + for (i = 0; i < sizeof(osmux_cid_bitmap); i++) { + for (j = 0; j < 8; j++) { + if (osmux_cid_bitmap[i] & (1 << j)) + used += 1; + } + } + + return used; +} + +int osmux_get_cid(void) +{ + int i, j; + + for (i = 0; i < sizeof(osmux_cid_bitmap); i++) { + for (j = 0; j < 8; j++) { + if (osmux_cid_bitmap[i] & (1 << j)) + continue; + + osmux_cid_bitmap[i] |= (1 << j); + LOGP(DLMGCP, LOGL_DEBUG, + "Allocating Osmux CID %u from pool\n", (i * 8) + j); + return (i * 8) + j; + } + } + + LOGP(DLMGCP, LOGL_ERROR, "All Osmux circuits are in use!\n"); + return -1; +} + +void osmux_put_cid(uint8_t osmux_cid) +{ + LOGP(DLMGCP, LOGL_DEBUG, "Osmux CID %u is back to the pool\n", osmux_cid); + osmux_cid_bitmap[osmux_cid / 8] &= ~(1 << (osmux_cid % 8)); +} diff --git a/src/libosmo-mgcp/mgcp_protocol.c b/src/libosmo-mgcp/mgcp_protocol.c new file mode 100644 index 000000000..bc1ec0dc3 --- /dev/null +++ b/src/libosmo-mgcp/mgcp_protocol.c @@ -0,0 +1,1640 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2012 by Holger Hans Peter Freyther + * (C) 2009-2012 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define for_each_non_empty_line(line, save) \ + for (line = strtok_r(NULL, "\r\n", &save); line;\ + line = strtok_r(NULL, "\r\n", &save)) + + +static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end); + +struct mgcp_request { + char *name; + struct msgb *(*handle_request) (struct mgcp_parse_data *data); + char *debug_name; +}; + +#define MGCP_REQUEST(NAME, REQ, DEBUG_NAME) \ + { .name = NAME, .handle_request = REQ, .debug_name = DEBUG_NAME }, + +static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *data); +static struct msgb *handle_create_con(struct mgcp_parse_data *data); +static struct msgb *handle_delete_con(struct mgcp_parse_data *data); +static struct msgb *handle_modify_con(struct mgcp_parse_data *data); +static struct msgb *handle_rsip(struct mgcp_parse_data *data); +static struct msgb *handle_noti_req(struct mgcp_parse_data *data); + +static void create_transcoder(struct mgcp_endpoint *endp); +static void delete_transcoder(struct mgcp_endpoint *endp); + +static int setup_rtp_processing(struct mgcp_endpoint *endp); + +static int mgcp_analyze_header(struct mgcp_parse_data *parse, char *data); + +/* Display an mgcp message on the log output */ +void display_mgcp_message(unsigned char *message, unsigned int len, + char *preamble) +{ + unsigned char line[80]; + unsigned char *ptr; + unsigned int consumed = 0; + unsigned int consumed_line = 0; + unsigned int line_count = 0; + + if (!log_check_level(DLMGCP, LOGL_DEBUG)) + return; + + while (1) { + memset(line, 0, sizeof(line)); + ptr = line; + consumed_line = 0; + do { + if (*message != '\n' && *message != '\r') { + *ptr = *message; + ptr++; + } + message++; + consumed++; + consumed_line++; + } while (*message != '\n' && consumed < len + && consumed_line < sizeof(line)); + + if (strlen((const char *)line)) { + LOGP(DLMGCP, LOGL_DEBUG, "%s: line #%02u: %s\n", + preamble, line_count, line); + line_count++; + } + + if (consumed >= len) + return; + } +} + +static int mgcp_check_param(const struct mgcp_endpoint *endp, const char *line) +{ + const size_t line_len = strlen(line); + if (line[0] != '\0' && line_len < 2) { + LOGP(DLMGCP, LOGL_ERROR, + "Wrong MGCP option format: '%s' on 0x%x\n", + line, ENDPOINT_NUMBER(endp)); + return 0; + } + + return 1; +} + +static uint32_t generate_call_id(struct mgcp_config *cfg) +{ + int i; + + /* use the call id */ + ++cfg->last_call_id; + + /* handle wrap around */ + if (cfg->last_call_id == CI_UNUSED) + ++cfg->last_call_id; + + /* callstack can only be of size number_of_endpoints */ + /* verify that the call id is free, e.g. in case of overrun */ + for (i = 1; i < cfg->trunk.number_endpoints; ++i) + if (cfg->trunk.endpoints[i].ci == cfg->last_call_id) + return generate_call_id(cfg); + + return cfg->last_call_id; +} + +/* + * array of function pointers for handling various + * messages. In the future this might be binary sorted + * for performance reasons. + */ +static const struct mgcp_request mgcp_requests [] = { + MGCP_REQUEST("AUEP", handle_audit_endpoint, "AuditEndpoint") + MGCP_REQUEST("CRCX", handle_create_con, "CreateConnection") + MGCP_REQUEST("DLCX", handle_delete_con, "DeleteConnection") + MGCP_REQUEST("MDCX", handle_modify_con, "ModifiyConnection") + MGCP_REQUEST("RQNT", handle_noti_req, "NotificationRequest") + + /* SPEC extension */ + MGCP_REQUEST("RSIP", handle_rsip, "ReSetInProgress") +}; + +static struct msgb *mgcp_msgb_alloc(void) +{ + struct msgb *msg; + msg = msgb_alloc_headroom(4096, 128, "MGCP msg"); + if (!msg) + LOGP(DLMGCP, LOGL_ERROR, "Failed to msgb for MGCP data.\n"); + + return msg; +} + +static struct msgb *do_retransmission(const struct mgcp_endpoint *endp) +{ + struct msgb *msg = mgcp_msgb_alloc(); + if (!msg) + return NULL; + + msg->l2h = msgb_put(msg, strlen(endp->last_response)); + memcpy(msg->l2h, endp->last_response, msgb_l2len(msg)); + display_mgcp_message(msg->l2h, msgb_l2len(msg), "Retransmitted response"); + return msg; +} + +static struct msgb *create_resp(struct mgcp_endpoint *endp, int code, + const char *txt, const char *msg, + const char *trans, const char *param, + const char *sdp) +{ + int len; + struct msgb *res; + + res = mgcp_msgb_alloc(); + if (!res) + return NULL; + + len = snprintf((char *) res->data, 2048, "%d %s%s%s\r\n%s", + code, trans, txt, param ? param : "", sdp ? sdp : ""); + if (len < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to sprintf MGCP response.\n"); + msgb_free(res); + return NULL; + } + + res->l2h = msgb_put(res, len); + LOGP(DLMGCP, LOGL_DEBUG, "Generated response: code=%d\n", code); + display_mgcp_message(res->l2h, msgb_l2len(res), "Generated response"); + + /* + * Remember the last transmission per endpoint. + */ + if (endp) { + struct mgcp_trunk_config *tcfg = endp->tcfg; + talloc_free(endp->last_response); + talloc_free(endp->last_trans); + endp->last_trans = talloc_strdup(tcfg->endpoints, trans); + endp->last_response = talloc_strndup(tcfg->endpoints, + (const char *) res->l2h, + msgb_l2len(res)); + } + + return res; +} + +static struct msgb *create_ok_resp_with_param(struct mgcp_endpoint *endp, + int code, const char *msg, + const char *trans, const char *param) +{ + return create_resp(endp, code, " OK", msg, trans, param, NULL); +} + +static struct msgb *create_ok_response(struct mgcp_endpoint *endp, + int code, const char *msg, const char *trans) +{ + return create_ok_resp_with_param(endp, code, msg, trans, NULL); +} + +static struct msgb *create_err_response(struct mgcp_endpoint *endp, + int code, const char *msg, const char *trans) +{ + return create_resp(endp, code, " FAIL", msg, trans, NULL, NULL); +} + +static int write_response_sdp(struct mgcp_endpoint *endp, + char *sdp_record, size_t size, const char *addr) +{ + const char *fmtp_extra; + const char *audio_name; + int payload_type; + int len; + int nchars; + + endp->cfg->get_net_downlink_format_cb(endp, &payload_type, + &audio_name, &fmtp_extra); + + len = snprintf(sdp_record, size, + "v=0\r\n" + "o=- %u 23 IN IP4 %s\r\n" + "s=-\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n", + endp->ci, addr, addr); + + if (len < 0 || len >= size) + goto buffer_too_small; + + if (payload_type >= 0) { + nchars = snprintf(sdp_record + len, size - len, + "m=audio %d RTP/AVP %d\r\n", + endp->net_end.local_port, payload_type); + if (nchars < 0 || nchars >= size - len) + goto buffer_too_small; + + len += nchars; + + if (audio_name && endp->tcfg->audio_send_name) { + nchars = snprintf(sdp_record + len, size - len, + "a=rtpmap:%d %s\r\n", + payload_type, audio_name); + + if (nchars < 0 || nchars >= size - len) + goto buffer_too_small; + + len += nchars; + } + + if (fmtp_extra) { + nchars = snprintf(sdp_record + len, size - len, + "%s\r\n", fmtp_extra); + + if (nchars < 0 || nchars >= size - len) + goto buffer_too_small; + + len += nchars; + } + } + if (endp->bts_end.packet_duration_ms > 0 && endp->tcfg->audio_send_ptime) { + nchars = snprintf(sdp_record + len, size - len, + "a=ptime:%d\r\n", + endp->bts_end.packet_duration_ms); + if (nchars < 0 || nchars >= size - len) + goto buffer_too_small; + + len += nchars; + } + + return len; + +buffer_too_small: + LOGP(DLMGCP, LOGL_ERROR, "SDP buffer too small: %zu (needed %d)\n", + size, len); + return -1; +} + +static struct msgb *create_response_with_sdp(struct mgcp_endpoint *endp, + const char *msg, const char *trans_id) +{ + const char *addr = endp->cfg->local_ip; + char sdp_record[4096]; + int len; + int nchars; + char osmux_extension[strlen("\nX-Osmux: 255") + 1]; + + if (!addr) + addr = mgcp_net_src_addr(endp); + + if (endp->osmux.state == OSMUX_STATE_NEGOTIATING) { + sprintf(osmux_extension, "\nX-Osmux: %u", endp->osmux.cid); + endp->osmux.state = OSMUX_STATE_ACTIVATING; + } else { + osmux_extension[0] = '\0'; + } + + len = snprintf(sdp_record, sizeof(sdp_record), + "I: %u%s\n\n", endp->ci, osmux_extension); + if (len < 0) + return NULL; + + nchars = write_response_sdp(endp, sdp_record + len, + sizeof(sdp_record) - len - 1, addr); + if (nchars < 0) + return NULL; + + len += nchars; + + sdp_record[sizeof(sdp_record) - 1] = '\0'; + + return create_resp(endp, 200, " OK", msg, trans_id, NULL, sdp_record); +} + +static void send_dummy(struct mgcp_endpoint *endp) +{ + if (endp->osmux.state != OSMUX_STATE_DISABLED) + osmux_send_dummy(endp); + else + mgcp_send_dummy(endp); +} + +/* + * handle incoming messages: + * - this can be a command (four letters, space, transaction id) + * - or a response (three numbers, space, transaction id) + */ +struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg) +{ + struct mgcp_parse_data pdata; + int i, code, handled = 0; + struct msgb *resp = NULL; + char *data; + + if (msgb_l2len(msg) < 4) { + LOGP(DLMGCP, LOGL_ERROR, "msg too short: %d\n", msg->len); + return NULL; + } + + if (mgcp_msg_terminate_nul(msg)) + return NULL; + + display_mgcp_message(msg->l2h, msgb_l2len(msg), "Received message"); + + /* attempt to treat it as a response */ + if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) { + LOGP(DLMGCP, LOGL_DEBUG, "Response: Code: %d\n", code); + return NULL; + } + + msg->l3h = &msg->l2h[4]; + + + /* + * Check for a duplicate message and respond. + */ + memset(&pdata, 0, sizeof(pdata)); + pdata.cfg = cfg; + data = strline_r((char *) msg->l3h, &pdata.save); + pdata.found = mgcp_analyze_header(&pdata, data); + if (pdata.endp && pdata.trans + && pdata.endp->last_trans + && strcmp(pdata.endp->last_trans, pdata.trans) == 0) { + return do_retransmission(pdata.endp); + } + + for (i = 0; i < ARRAY_SIZE(mgcp_requests); ++i) { + if (strncmp(mgcp_requests[i].name, (const char *) &msg->l2h[0], 4) == 0) { + handled = 1; + resp = mgcp_requests[i].handle_request(&pdata); + break; + } + } + + if (!handled) + LOGP(DLMGCP, LOGL_NOTICE, "MSG with type: '%.4s' not handled\n", &msg->l2h[0]); + + return resp; +} + +/** + * We have a null terminated string with the endpoint name here. We only + * support two kinds. Simple ones as seen on the BSC level and the ones + * seen on the trunk side. + */ +static struct mgcp_endpoint *find_e1_endpoint(struct mgcp_config *cfg, + const char *mgcp) +{ + char *rest = NULL; + struct mgcp_trunk_config *tcfg; + int trunk, endp; + + trunk = strtoul(mgcp + 6, &rest, 10); + if (rest == NULL || rest[0] != '/' || trunk < 1) { + LOGP(DLMGCP, LOGL_ERROR, "Wrong trunk name '%s'\n", mgcp); + return NULL; + } + + endp = strtoul(rest + 1, &rest, 10); + if (rest == NULL || rest[0] != '@') { + LOGP(DLMGCP, LOGL_ERROR, "Wrong endpoint name '%s'\n", mgcp); + return NULL; + } + + /* signalling is on timeslot 1 */ + if (endp == 1) + return NULL; + + tcfg = mgcp_trunk_num(cfg, trunk); + if (!tcfg) { + LOGP(DLMGCP, LOGL_ERROR, "The trunk %d is not declared.\n", trunk); + return NULL; + } + + if (!tcfg->endpoints) { + LOGP(DLMGCP, LOGL_ERROR, "Endpoints of trunk %d not allocated.\n", trunk); + return NULL; + } + + if (endp < 1 || endp >= tcfg->number_endpoints) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to find endpoint '%s'\n", mgcp); + return NULL; + } + + return &tcfg->endpoints[endp]; +} + +static struct mgcp_endpoint *find_endpoint(struct mgcp_config *cfg, const char *mgcp) +{ + char *endptr = NULL; + unsigned int gw = INT_MAX; + + if (strncmp(mgcp, "ds/e1", 5) == 0) + return find_e1_endpoint(cfg, mgcp); + + gw = strtoul(mgcp, &endptr, 16); + if (gw > 0 && gw < cfg->trunk.number_endpoints && endptr[0] == '@') + return &cfg->trunk.endpoints[gw]; + + LOGP(DLMGCP, LOGL_ERROR, "Not able to find the endpoint: '%s'\n", mgcp); + return NULL; +} + +/** + * @returns 0 when the status line was complete and transaction_id and + * endp out parameters are set. + */ +static int mgcp_analyze_header(struct mgcp_parse_data *pdata, char *data) +{ + int i = 0; + char *elem, *save = NULL; + + OSMO_ASSERT(data); + pdata->trans = "000000"; + + for (elem = strtok_r(data, " ", &save); elem; + elem = strtok_r(NULL, " ", &save)) { + switch (i) { + case 0: + pdata->trans = elem; + break; + case 1: + pdata->endp = find_endpoint(pdata->cfg, elem); + if (!pdata->endp) { + LOGP(DLMGCP, LOGL_ERROR, + "Unable to find Endpoint `%s'\n", elem); + return -1; + } + break; + case 2: + if (strcmp("MGCP", elem)) { + LOGP(DLMGCP, LOGL_ERROR, + "MGCP header parsing error\n"); + return -1; + } + break; + case 3: + if (strcmp("1.0", elem)) { + LOGP(DLMGCP, LOGL_ERROR, "MGCP version `%s' " + "not supported\n", elem); + return -1; + } + break; + } + i++; + } + + if (i != 4) { + LOGP(DLMGCP, LOGL_ERROR, "MGCP status line too short.\n"); + pdata->trans = "000000"; + pdata->endp = NULL; + return -1; + } + + return 0; +} + +static int verify_call_id(const struct mgcp_endpoint *endp, + const char *callid) +{ + if (strcmp(endp->callid, callid) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "CallIDs does not match on 0x%x. '%s' != '%s'\n", + ENDPOINT_NUMBER(endp), endp->callid, callid); + return -1; + } + + return 0; +} + +static int verify_ci(const struct mgcp_endpoint *endp, + const char *_ci) +{ + uint32_t ci = strtoul(_ci, NULL, 10); + + if (ci != endp->ci) { + LOGP(DLMGCP, LOGL_ERROR, "ConnectionIdentifiers do not match on 0x%x. %u != %s\n", + ENDPOINT_NUMBER(endp), endp->ci, _ci); + return -1; + } + + return 0; +} + +static struct msgb *handle_audit_endpoint(struct mgcp_parse_data *p) +{ + if (p->found != 0) + return create_err_response(NULL, 500, "AUEP", p->trans); + else + return create_ok_response(p->endp, 200, "AUEP", p->trans); +} + +static int parse_conn_mode(const char *msg, struct mgcp_endpoint *endp) +{ + int ret = 0; + if (strcmp(msg, "recvonly") == 0) + endp->conn_mode = MGCP_CONN_RECV_ONLY; + else if (strcmp(msg, "sendrecv") == 0) + endp->conn_mode = MGCP_CONN_RECV_SEND; + else if (strcmp(msg, "sendonly") == 0) + endp->conn_mode = MGCP_CONN_SEND_ONLY; + else if (strcmp(msg, "loopback") == 0) + endp->conn_mode = MGCP_CONN_LOOPBACK; + else { + LOGP(DLMGCP, LOGL_ERROR, "Unknown connection mode: '%s'\n", msg); + ret = -1; + } + + endp->net_end.output_enabled = + endp->conn_mode & MGCP_CONN_SEND_ONLY ? 1 : 0; + endp->bts_end.output_enabled = + endp->conn_mode & MGCP_CONN_RECV_ONLY ? 1 : 0; + + LOGP(DLMGCP, LOGL_DEBUG, "endpoint %x connection mode '%s' %d output_enabled net %d bts %d\n", + ENDPOINT_NUMBER(endp), + msg, endp->conn_mode, endp->net_end.output_enabled, + endp->bts_end.output_enabled); + + return ret; +} + +static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_rtp_end *end, + struct mgcp_port_range *range, + int (*alloc)(struct mgcp_endpoint *endp, int port)) +{ + int i; + + if (range->mode == PORT_ALLOC_STATIC) { + end->local_alloc = PORT_ALLOC_STATIC; + return 0; + } + + /* attempt to find a port */ + for (i = 0; i < 200; ++i) { + int rc; + + if (range->last_port >= range->range_end) + range->last_port = range->range_start; + + rc = alloc(endp, range->last_port); + + range->last_port += 2; + if (rc == 0) { + end->local_alloc = PORT_ALLOC_DYNAMIC; + return 0; + } + + } + + LOGP(DLMGCP, LOGL_ERROR, "Allocating a RTP/RTCP port failed 200 times 0x%x.\n", + ENDPOINT_NUMBER(endp)); + return -1; +} + +static int allocate_ports(struct mgcp_endpoint *endp) +{ + if (allocate_port(endp, &endp->net_end, &endp->cfg->net_ports, + mgcp_bind_net_rtp_port) != 0) + return -1; + + if (allocate_port(endp, &endp->bts_end, &endp->cfg->bts_ports, + mgcp_bind_bts_rtp_port) != 0) { + mgcp_rtp_end_reset(&endp->net_end); + return -1; + } + + if (endp->cfg->transcoder_ip && endp->tcfg->trunk_type == MGCP_TRUNK_VIRTUAL) { + if (allocate_port(endp, &endp->trans_net, + &endp->cfg->transcoder_ports, + mgcp_bind_trans_net_rtp_port) != 0) { + mgcp_rtp_end_reset(&endp->net_end); + mgcp_rtp_end_reset(&endp->bts_end); + return -1; + } + + if (allocate_port(endp, &endp->trans_bts, + &endp->cfg->transcoder_ports, + mgcp_bind_trans_bts_rtp_port) != 0) { + mgcp_rtp_end_reset(&endp->net_end); + mgcp_rtp_end_reset(&endp->bts_end); + mgcp_rtp_end_reset(&endp->trans_net); + return -1; + } + + /* remember that we have set up transcoding */ + endp->type = MGCP_RTP_TRANSCODED; + } + + return 0; +} + +/* Set the LCO from a string (see RFC 3435). + * The string is stored in the 'string' field. A NULL string is handled excatly + * like an empty string, the 'string' field is never NULL after this function + * has been called. */ +static void set_local_cx_options(void *ctx, struct mgcp_lco *lco, + const char *options) +{ + char *p_opt, *a_opt; + char codec[9]; + + 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 : ""); + + 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; + + a_opt = strstr(lco->string, "a:"); + if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1) + lco->codec = talloc_strdup(ctx, codec); +} + +void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change, + struct mgcp_rtp_end *rtp) +{ + struct mgcp_trunk_config *tcfg = endp->tcfg; + + int patch_ssrc = expect_ssrc_change && tcfg->force_constant_ssrc; + + rtp->force_aligned_timing = tcfg->force_aligned_timing; + rtp->force_constant_ssrc = patch_ssrc ? 1 : 0; + + LOGP(DLMGCP, LOGL_DEBUG, + "Configuring RTP endpoint: local port %d%s%s\n", + ntohs(rtp->rtp_port), + rtp->force_aligned_timing ? ", force constant timing" : "", + rtp->force_constant_ssrc ? ", force constant ssrc" : ""); +} + +uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *rtp) +{ + int f = 0; + + /* 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 + den/2) + / den; + } + + return rtp->codec.rate * f * rtp->codec.frame_duration_num / rtp->codec.frame_duration_den; +} + +static int mgcp_parse_osmux_cid(const char *line) +{ + int osmux_cid; + + if (sscanf(line + 2, "Osmux: %u", &osmux_cid) != 1) + return -1; + + if (osmux_cid > OSMUX_CID_MAX) { + LOGP(DLMGCP, LOGL_ERROR, "Osmux ID too large: %u > %u\n", + osmux_cid, OSMUX_CID_MAX); + return -1; + } + LOGP(DLMGCP, LOGL_DEBUG, "bsc-nat offered Osmux CID %u\n", osmux_cid); + + return osmux_cid; +} + +static int mgcp_osmux_setup(struct mgcp_endpoint *endp, const char *line) +{ + if (!endp->cfg->osmux_init) { + if (osmux_init(OSMUX_ROLE_BSC, endp->cfg) < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Cannot init OSMUX\n"); + return -1; + } + LOGP(DLMGCP, LOGL_NOTICE, "OSMUX socket has been set up\n"); + } + + return mgcp_parse_osmux_cid(line); +} + +static struct msgb *handle_create_con(struct mgcp_parse_data *p) +{ + struct mgcp_trunk_config *tcfg; + struct mgcp_endpoint *endp = p->endp; + int error_code = 400; + + const char *local_options = NULL; + const char *callid = NULL; + const char *mode = NULL; + char *line; + int have_sdp = 0, osmux_cid = -1; + + if (p->found != 0) + return create_err_response(NULL, 510, "CRCX", p->trans); + + /* parse CallID C: and LocalParameters L: */ + for_each_line(line, p->save) { + if (!mgcp_check_param(endp, line)) + continue; + + switch (line[0]) { + case 'L': + local_options = (const char *) line + 3; + break; + case 'C': + callid = (const char *) line + 3; + break; + case 'M': + mode = (const char *) line + 3; + break; + case 'X': + /* Osmux is not enabled in this bsc, ignore it so the + * bsc-nat knows that we don't want to use Osmux. + */ + if (!p->endp->cfg->osmux) + break; + + if (strncmp("Osmux: ", line + 2, strlen("Osmux: ")) == 0) + osmux_cid = mgcp_osmux_setup(endp, line); + break; + case '\0': + have_sdp = 1; + goto mgcp_header_done; + default: + LOGP(DLMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n", + *line, *line, ENDPOINT_NUMBER(endp)); + break; + } + } + +mgcp_header_done: + tcfg = p->endp->tcfg; + + /* Check required data */ + if (!callid || !mode) { + LOGP(DLMGCP, LOGL_ERROR, "Missing callid and mode in CRCX on 0x%x\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "CRCX", p->trans); + } + + if (endp->allocated) { + if (tcfg->force_realloc) { + LOGP(DLMGCP, LOGL_NOTICE, "Endpoint 0x%x already allocated. Forcing realloc.\n", + ENDPOINT_NUMBER(endp)); + mgcp_release_endp(endp); + if (p->cfg->realloc_cb) + p->cfg->realloc_cb(tcfg, ENDPOINT_NUMBER(endp)); + } else { + LOGP(DLMGCP, LOGL_ERROR, "Endpoint is already used. 0x%x\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "CRCX", p->trans); + } + } + + /* copy some parameters */ + endp->callid = talloc_strdup(tcfg->endpoints, callid); + + set_local_cx_options(endp->tcfg->endpoints, &endp->local_options, + local_options); + + if (parse_conn_mode(mode, endp) != 0) { + error_code = 517; + goto error2; + } + + /* initialize */ + endp->net_end.rtp_port = endp->net_end.rtcp_port = endp->bts_end.rtp_port = endp->bts_end.rtcp_port = 0; + mgcp_rtp_end_config(endp, 0, &endp->net_end); + mgcp_rtp_end_config(endp, 0, &endp->bts_end); + + /* set to zero until we get the info */ + memset(&endp->net_end.addr, 0, sizeof(endp->net_end.addr)); + + /* bind to the port now */ + if (allocate_ports(endp) != 0) + goto error2; + + /* assign a local call identifier or fail */ + endp->ci = generate_call_id(p->cfg); + if (endp->ci == CI_UNUSED) + goto error2; + + /* Annotate Osmux circuit ID and set it to negotiating state until this + * is fully set up from the dummy load. + */ + endp->osmux.state = OSMUX_STATE_DISABLED; + if (osmux_cid >= 0) { + endp->osmux.cid = osmux_cid; + endp->osmux.state = OSMUX_STATE_NEGOTIATING; + } else if (endp->cfg->osmux == OSMUX_USAGE_ONLY) { + LOGP(DLMGCP, LOGL_ERROR, + "Osmux only and no osmux offered on 0x%x\n", ENDPOINT_NUMBER(endp)); + goto error2; + } + + endp->allocated = 1; + + /* set up RTP media parameters */ + mgcp_set_audio_info(p->cfg, &endp->bts_end.codec, tcfg->audio_payload, tcfg->audio_name); + endp->bts_end.fmtp_extra = talloc_strdup(tcfg->endpoints, + tcfg->audio_fmtp_extra); + if (have_sdp) + mgcp_parse_sdp_data(endp, &endp->net_end, p); + else if (endp->local_options.codec) + mgcp_set_audio_info(p->cfg, &endp->net_end.codec, + PTYPE_UNDEFINED, endp->local_options.codec); + + if (p->cfg->bts_force_ptime) { + endp->bts_end.packet_duration_ms = p->cfg->bts_force_ptime; + endp->bts_end.force_output_ptime = 1; + } + + if (setup_rtp_processing(endp) != 0) + goto error2; + + /* policy CB */ + if (p->cfg->policy_cb) { + int rc; + rc = p->cfg->policy_cb(tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_CRCX, p->trans); + switch (rc) { + case MGCP_POLICY_REJECT: + LOGP(DLMGCP, LOGL_NOTICE, "CRCX rejected by policy on 0x%x\n", + ENDPOINT_NUMBER(endp)); + mgcp_release_endp(endp); + return create_err_response(endp, 400, "CRCX", p->trans); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + create_transcoder(endp); + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + 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); + if (p->cfg->change_cb) + p->cfg->change_cb(tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_CRCX); + + if (endp->conn_mode & MGCP_CONN_RECV_ONLY && tcfg->keepalive_interval != 0) { + send_dummy(endp); + } + + create_transcoder(endp); + return create_response_with_sdp(endp, "CRCX", p->trans); +error2: + mgcp_release_endp(endp); + LOGP(DLMGCP, LOGL_NOTICE, "Resource error on 0x%x\n", ENDPOINT_NUMBER(endp)); + return create_err_response(endp, error_code, "CRCX", p->trans); +} + +static struct msgb *handle_modify_con(struct mgcp_parse_data *p) +{ + struct mgcp_endpoint *endp = p->endp; + int error_code = 500; + int silent = 0; + int have_sdp = 0; + char *line; + const char *local_options = NULL; + + if (p->found != 0) + return create_err_response(NULL, 510, "MDCX", p->trans); + + if (endp->ci == CI_UNUSED) { + LOGP(DLMGCP, LOGL_ERROR, "Endpoint is not " + "holding a connection. 0x%x\n", ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "MDCX", p->trans); + } + + for_each_line(line, p->save) { + if (!mgcp_check_param(endp, line)) + continue; + + switch (line[0]) { + case 'C': { + if (verify_call_id(endp, line + 3) != 0) + goto error3; + break; + } + case 'I': { + if (verify_ci(endp, line + 3) != 0) + goto error3; + break; + } + case 'L': + local_options = (const char *) line + 3; + break; + case 'M': + if (parse_conn_mode(line + 3, endp) != 0) { + error_code = 517; + goto error3; + } + endp->orig_mode = endp->conn_mode; + break; + case 'Z': + silent = strcmp("noanswer", line + 3) == 0; + break; + case '\0': + /* SDP file begins */ + have_sdp = 1; + mgcp_parse_sdp_data(endp, &endp->net_end, p); + /* This will exhaust p->save, so the loop will + * terminate next time. + */ + break; + default: + LOGP(DLMGCP, LOGL_NOTICE, "Unhandled MGCP option: '%c'/%d on 0x%x\n", + line[0], line[0], ENDPOINT_NUMBER(endp)); + break; + } + } + + set_local_cx_options(endp->tcfg->endpoints, &endp->local_options, + local_options); + + if (!have_sdp && endp->local_options.codec) + mgcp_set_audio_info(p->cfg, &endp->net_end.codec, + PTYPE_UNDEFINED, endp->local_options.codec); + + if (setup_rtp_processing(endp) != 0) + goto error3; + + /* policy CB */ + if (p->cfg->policy_cb) { + int rc; + rc = p->cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_MDCX, p->trans); + switch (rc) { + case MGCP_POLICY_REJECT: + LOGP(DLMGCP, LOGL_NOTICE, "MDCX rejected by policy on 0x%x\n", + ENDPOINT_NUMBER(endp)); + if (silent) + goto out_silent; + return create_err_response(endp, 400, "MDCX", p->trans); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + LOGP(DLMGCP, LOGL_DEBUG, "endp %x MDCX defer\n", + ENDPOINT_NUMBER(endp)); + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + mgcp_rtp_end_config(endp, 1, &endp->net_end); + mgcp_rtp_end_config(endp, 1, &endp->bts_end); + + /* modify */ + LOGP(DLMGCP, LOGL_DEBUG, "Modified endpoint on: 0x%x Server: %s:%u\n", + ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port)); + if (p->cfg->change_cb) + p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_MDCX); + + if (endp->conn_mode & MGCP_CONN_RECV_ONLY && + endp->tcfg->keepalive_interval != 0) + send_dummy(endp); + + if (silent) + goto out_silent; + + return create_response_with_sdp(endp, "MDCX", p->trans); + +error3: + return create_err_response(endp, error_code, "MDCX", p->trans); + + +out_silent: + LOGP(DLMGCP, LOGL_DEBUG, "endp %x Modify endpoint: silent exit\n", + ENDPOINT_NUMBER(endp)); + return NULL; +} + +static struct msgb *handle_delete_con(struct mgcp_parse_data *p) +{ + struct mgcp_endpoint *endp = p->endp; + int error_code = 400; + int silent = 0; + char *line; + char stats[1048]; + + if (p->found != 0) + return create_err_response(NULL, error_code, "DLCX", p->trans); + + if (!p->endp->allocated) { + LOGP(DLMGCP, LOGL_ERROR, "Endpoint is not used. 0x%x\n", + ENDPOINT_NUMBER(endp)); + return create_err_response(endp, 400, "DLCX", p->trans); + } + + for_each_line(line, p->save) { + if (!mgcp_check_param(endp, line)) + continue; + + switch (line[0]) { + case 'C': + if (verify_call_id(endp, line + 3) != 0) + goto error3; + break; + case 'I': + if (verify_ci(endp, line + 3) != 0) + goto error3; + break; + case 'Z': + silent = strcmp("noanswer", line + 3) == 0; + break; + default: + LOGP(DLMGCP, LOGL_NOTICE, "Unhandled option: '%c'/%d on 0x%x\n", + line[0], line[0], ENDPOINT_NUMBER(endp)); + break; + } + } + + /* policy CB */ + if (p->cfg->policy_cb) { + int rc; + rc = p->cfg->policy_cb(endp->tcfg, ENDPOINT_NUMBER(endp), + MGCP_ENDP_DLCX, p->trans); + switch (rc) { + case MGCP_POLICY_REJECT: + LOGP(DLMGCP, LOGL_NOTICE, "DLCX rejected by policy on 0x%x\n", + ENDPOINT_NUMBER(endp)); + if (silent) + goto out_silent; + return create_err_response(endp, 400, "DLCX", p->trans); + break; + case MGCP_POLICY_DEFER: + /* stop processing */ + delete_transcoder(endp); + return NULL; + break; + case MGCP_POLICY_CONT: + /* just continue */ + break; + } + } + + /* free the connection */ + LOGP(DLMGCP, LOGL_DEBUG, "Deleted endpoint on: 0x%x Server: %s:%u\n", + ENDPOINT_NUMBER(endp), inet_ntoa(endp->net_end.addr), ntohs(endp->net_end.rtp_port)); + + /* save the statistics of the current call */ + mgcp_format_stats(endp, stats, sizeof(stats)); + + delete_transcoder(endp); + mgcp_release_endp(endp); + if (p->cfg->change_cb) + p->cfg->change_cb(endp->tcfg, ENDPOINT_NUMBER(endp), MGCP_ENDP_DLCX); + + if (silent) + goto out_silent; + return create_ok_resp_with_param(endp, 250, "DLCX", p->trans, stats); + +error3: + return create_err_response(endp, error_code, "DLCX", p->trans); + +out_silent: + return NULL; +} + +static struct msgb *handle_rsip(struct mgcp_parse_data *p) +{ + if (p->found != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to find the endpoint.\n"); + return NULL; + } + + if (p->cfg->reset_cb) + p->cfg->reset_cb(p->endp->tcfg); + return NULL; +} + +static char extract_tone(const char *line) +{ + const char *str = strstr(line, "D/"); + if (!str) + return CHAR_MAX; + + return str[2]; +} + +/* + * This can request like DTMF detection and forward, fax detection... it + * can also request when the notification should be send and such. We don't + * do this right now. + */ +static struct msgb *handle_noti_req(struct mgcp_parse_data *p) +{ + int res = 0; + char *line; + char tone = CHAR_MAX; + + if (p->found != 0) + return create_err_response(NULL, 400, "RQNT", p->trans); + + for_each_line(line, p->save) { + switch (line[0]) { + case 'S': + tone = extract_tone(line); + break; + } + } + + /* we didn't see a signal request with a tone */ + if (tone == CHAR_MAX) + return create_ok_response(p->endp, 200, "RQNT", p->trans); + + if (p->cfg->rqnt_cb) + res = p->cfg->rqnt_cb(p->endp, tone); + + return res == 0 ? + create_ok_response(p->endp, 200, "RQNT", p->trans) : + create_err_response(p->endp, res, "RQNT", p->trans); +} + +static void mgcp_keepalive_timer_cb(void *_tcfg) +{ + struct mgcp_trunk_config *tcfg = _tcfg; + int i; + LOGP(DLMGCP, LOGL_DEBUG, "Triggered trunk %d keepalive timer.\n", + tcfg->trunk_nr); + + if (tcfg->keepalive_interval <= 0) + return; + + for (i = 1; i < tcfg->number_endpoints; ++i) { + struct mgcp_endpoint *endp = &tcfg->endpoints[i]; + if (endp->conn_mode == MGCP_CONN_RECV_ONLY) + send_dummy(endp); + } + + LOGP(DLMGCP, LOGL_DEBUG, "Rescheduling trunk %d keepalive timer.\n", + tcfg->trunk_nr); + osmo_timer_schedule(&tcfg->keepalive_timer, tcfg->keepalive_interval, 0); +} + +void mgcp_trunk_set_keepalive(struct mgcp_trunk_config *tcfg, int interval) +{ + tcfg->keepalive_interval = interval; + osmo_timer_setup(&tcfg->keepalive_timer, mgcp_keepalive_timer_cb, tcfg); + + if (interval <= 0) + osmo_timer_del(&tcfg->keepalive_timer); + else + osmo_timer_schedule(&tcfg->keepalive_timer, + tcfg->keepalive_interval, 0); +} + +struct mgcp_config *mgcp_config_alloc(void) +{ + struct mgcp_config *cfg; + + cfg = talloc_zero(NULL, struct mgcp_config); + if (!cfg) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to allocate config.\n"); + return NULL; + } + + cfg->source_port = 2427; + cfg->source_addr = talloc_strdup(cfg, "0.0.0.0"); + cfg->osmux_addr = talloc_strdup(cfg, "0.0.0.0"); + + cfg->transcoder_remote_base = 4000; + + cfg->bts_ports.base_port = RTP_PORT_DEFAULT; + cfg->net_ports.base_port = RTP_PORT_NET_DEFAULT; + + cfg->rtp_processing_cb = &mgcp_rtp_processing_default; + cfg->setup_rtp_processing_cb = &mgcp_setup_rtp_processing_default; + + cfg->get_net_downlink_format_cb = &mgcp_get_net_downlink_format_default; + + /* default trunk handling */ + cfg->trunk.cfg = cfg; + cfg->trunk.trunk_nr = 0; + cfg->trunk.trunk_type = MGCP_TRUNK_VIRTUAL; + cfg->trunk.audio_name = talloc_strdup(cfg, "AMR/8000"); + cfg->trunk.audio_payload = 126; + cfg->trunk.audio_send_ptime = 1; + cfg->trunk.audio_send_name = 1; + cfg->trunk.omit_rtcp = 0; + mgcp_trunk_set_keepalive(&cfg->trunk, MGCP_KEEPALIVE_ONCE); + + INIT_LLIST_HEAD(&cfg->trunks); + + return cfg; +} + +struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int nr) +{ + struct mgcp_trunk_config *trunk; + + trunk = talloc_zero(cfg, struct mgcp_trunk_config); + if (!trunk) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to allocate.\n"); + return NULL; + } + + trunk->cfg = cfg; + trunk->trunk_type = MGCP_TRUNK_E1; + trunk->trunk_nr = nr; + trunk->audio_name = talloc_strdup(cfg, "AMR/8000"); + trunk->audio_payload = 126; + trunk->audio_send_ptime = 1; + trunk->audio_send_name = 1; + trunk->number_endpoints = 33; + trunk->omit_rtcp = 0; + mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE); + llist_add_tail(&trunk->entry, &cfg->trunks); + return trunk; +} + +struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index) +{ + struct mgcp_trunk_config *trunk; + + llist_for_each_entry(trunk, &cfg->trunks, entry) + if (trunk->trunk_nr == index) + return trunk; + + return NULL; +} + +static void mgcp_rtp_codec_reset(struct mgcp_rtp_codec *codec) +{ + codec->payload_type = -1; + talloc_free(codec->subtype_name); + codec->subtype_name = NULL; + talloc_free(codec->audio_name); + 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; +} + +static void mgcp_rtp_end_reset(struct mgcp_rtp_end *end) +{ + if (end->local_alloc == PORT_ALLOC_DYNAMIC) { + mgcp_free_rtp_port(end); + end->local_port = 0; + } + + end->packets = 0; + end->octets = 0; + end->dropped_packets = 0; + memset(&end->addr, 0, sizeof(end->addr)); + end->rtp_port = end->rtcp_port = 0; + end->local_alloc = -1; + talloc_free(end->fmtp_extra); + end->fmtp_extra = NULL; + talloc_free(end->rtp_process_data); + end->rtp_process_data = NULL; + + /* Set default values */ + end->frames_per_packet = 0; /* unknown */ + end->packet_duration_ms = DEFAULT_RTP_AUDIO_PACKET_DURATION_MS; + end->output_enabled = 0; + + mgcp_rtp_codec_reset(&end->codec); + mgcp_rtp_codec_reset(&end->alt_codec); +} + +static void mgcp_rtp_end_init(struct mgcp_rtp_end *end) +{ + mgcp_rtp_end_reset(end); + end->rtp.fd = -1; + end->rtcp.fd = -1; +} + +int mgcp_endpoints_allocate(struct mgcp_trunk_config *tcfg) +{ + int i; + + /* Initialize all endpoints */ + tcfg->endpoints = _talloc_zero_array(tcfg->cfg, + sizeof(struct mgcp_endpoint), + tcfg->number_endpoints, "endpoints"); + if (!tcfg->endpoints) + return -1; + + for (i = 0; i < tcfg->number_endpoints; ++i) { + tcfg->endpoints[i].osmux.allocated_cid = -1; + tcfg->endpoints[i].ci = CI_UNUSED; + tcfg->endpoints[i].cfg = tcfg->cfg; + tcfg->endpoints[i].tcfg = tcfg; + mgcp_rtp_end_init(&tcfg->endpoints[i].net_end); + mgcp_rtp_end_init(&tcfg->endpoints[i].bts_end); + mgcp_rtp_end_init(&tcfg->endpoints[i].trans_net); + mgcp_rtp_end_init(&tcfg->endpoints[i].trans_bts); + } + + return 0; +} + +void mgcp_release_endp(struct mgcp_endpoint *endp) +{ + LOGP(DLMGCP, LOGL_DEBUG, "Releasing endpoint on: 0x%x\n", ENDPOINT_NUMBER(endp)); + endp->ci = CI_UNUSED; + endp->allocated = 0; + + talloc_free(endp->callid); + endp->callid = NULL; + + talloc_free(endp->local_options.string); + endp->local_options.string = NULL; + talloc_free(endp->local_options.codec); + endp->local_options.codec = NULL; + + mgcp_rtp_end_reset(&endp->bts_end); + mgcp_rtp_end_reset(&endp->net_end); + mgcp_rtp_end_reset(&endp->trans_net); + mgcp_rtp_end_reset(&endp->trans_bts); + endp->type = MGCP_RTP_DEFAULT; + + memset(&endp->net_state, 0, sizeof(endp->net_state)); + memset(&endp->bts_state, 0, sizeof(endp->bts_state)); + + endp->conn_mode = endp->orig_mode = MGCP_CONN_NONE; + + if (endp->osmux.state == OSMUX_STATE_ENABLED) + osmux_disable_endpoint(endp); + + /* release the circuit ID if it had been allocated */ + osmux_release_cid(endp); + + memset(&endp->taps, 0, sizeof(endp->taps)); +} + +void mgcp_initialize_endp(struct mgcp_endpoint *endp) +{ + return mgcp_release_endp(endp); +} + +static int send_trans(struct mgcp_config *cfg, const char *buf, int len) +{ + struct sockaddr_in addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr = cfg->transcoder_in; + addr.sin_port = htons(2427); + return sendto(cfg->gw_fd.bfd.fd, buf, len, 0, + (struct sockaddr *) &addr, sizeof(addr)); +} + +static void send_msg(struct mgcp_endpoint *endp, int endpoint, int port, + const char *msg, const char *mode) +{ + char buf[2096]; + int len; + int nchars; + + /* hardcoded to AMR right now, we do not know the real type at this point */ + len = snprintf(buf, sizeof(buf), + "%s 42 %x@mgw MGCP 1.0\r\n" + "C: 4256\r\n" + "M: %s\r\n" + "\r\n", + msg, endpoint, mode); + + if (len < 0) + return; + + nchars = write_response_sdp(endp, buf + len, sizeof(buf) + len - 1, NULL); + if (nchars < 0) + return; + + len += nchars; + + buf[sizeof(buf) - 1] = '\0'; + + send_trans(endp->cfg, buf, len); +} + +static void send_dlcx(struct mgcp_endpoint *endp, int endpoint) +{ + char buf[2096]; + int len; + + len = snprintf(buf, sizeof(buf), + "DLCX 43 %x@mgw MGCP 1.0\r\n" + "C: 4256\r\n" + , endpoint); + + if (len < 0) + return; + + buf[sizeof(buf) - 1] = '\0'; + + send_trans(endp->cfg, buf, len); +} + +static int send_agent(struct mgcp_config *cfg, const char *buf, int len) +{ + return write(cfg->gw_fd.bfd.fd, buf, len); +} + +int mgcp_send_reset_all(struct mgcp_config *cfg) +{ + static const char mgcp_reset[] = { + "RSIP 1 *@mgw MGCP 1.0\r\n" + }; + + return send_agent(cfg, mgcp_reset, sizeof mgcp_reset -1); +} + +int mgcp_send_reset_ep(struct mgcp_endpoint *endp, int endpoint) +{ + char buf[128]; + int len; + + len = snprintf(buf, sizeof(buf), + "RSIP 39 %x@mgw MGCP 1.0\r\n" + , endpoint); + if (len < 0) + return len; + + buf[sizeof(buf) - 1] = '\0'; + + return send_agent(endp->cfg, buf, len); +} + +static int setup_rtp_processing(struct mgcp_endpoint *endp) +{ + int rc = 0; + struct mgcp_config *cfg = endp->cfg; + + if (endp->type != MGCP_RTP_DEFAULT) + return 0; + + if (endp->conn_mode == MGCP_CONN_LOOPBACK) + return 0; + + if (endp->conn_mode & MGCP_CONN_SEND_ONLY) + rc |= cfg->setup_rtp_processing_cb(endp, &endp->net_end, &endp->bts_end); + else + rc |= cfg->setup_rtp_processing_cb(endp, &endp->net_end, NULL); + + if (endp->conn_mode & MGCP_CONN_RECV_ONLY) + rc |= cfg->setup_rtp_processing_cb(endp, &endp->bts_end, &endp->net_end); + else + rc |= cfg->setup_rtp_processing_cb(endp, &endp->bts_end, NULL); + return rc; +} + +static void create_transcoder(struct mgcp_endpoint *endp) +{ + int port; + int in_endp = ENDPOINT_NUMBER(endp); + int out_endp = endp_back_channel(in_endp); + + if (endp->type != MGCP_RTP_TRANSCODED) + return; + + send_msg(endp, in_endp, endp->trans_bts.local_port, "CRCX", "sendrecv"); + send_msg(endp, in_endp, endp->trans_bts.local_port, "MDCX", "sendrecv"); + send_msg(endp, out_endp, endp->trans_net.local_port, "CRCX", "sendrecv"); + send_msg(endp, out_endp, endp->trans_net.local_port, "MDCX", "sendrecv"); + + port = rtp_calculate_port(in_endp, endp->cfg->transcoder_remote_base); + endp->trans_bts.rtp_port = htons(port); + endp->trans_bts.rtcp_port = htons(port + 1); + + port = rtp_calculate_port(out_endp, endp->cfg->transcoder_remote_base); + endp->trans_net.rtp_port = htons(port); + endp->trans_net.rtcp_port = htons(port + 1); +} + +static void delete_transcoder(struct mgcp_endpoint *endp) +{ + int in_endp = ENDPOINT_NUMBER(endp); + int out_endp = endp_back_channel(in_endp); + + if (endp->type != MGCP_RTP_TRANSCODED) + return; + + send_dlcx(endp, in_endp); + send_dlcx(endp, out_endp); +} + +int mgcp_reset_transcoder(struct mgcp_config *cfg) +{ + if (!cfg->transcoder_ip) + return 0; + + static const char mgcp_reset[] = { + "RSIP 1 13@mgw MGCP 1.0\r\n" + }; + + return send_trans(cfg, mgcp_reset, sizeof mgcp_reset -1); +} + +void mgcp_format_stats(struct mgcp_endpoint *endp, char *msg, size_t size) +{ + uint32_t expected, jitter; + int ploss; + int nchars; + mgcp_state_calc_loss(&endp->net_state, &endp->net_end, + &expected, &ploss); + jitter = mgcp_state_calc_jitter(&endp->net_state); + + nchars = snprintf(msg, size, + "\r\nP: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u", + endp->bts_end.packets, endp->bts_end.octets, + endp->net_end.packets, endp->net_end.octets, + ploss, jitter); + if (nchars < 0 || nchars >= size) + goto truncate; + + msg += nchars; + size -= nchars; + + /* Error Counter */ + nchars = snprintf(msg, size, + "\r\nX-Osmo-CP: EC TIS=%u, TOS=%u, TIR=%u, TOR=%u", + endp->net_state.in_stream.err_ts_counter, + endp->net_state.out_stream.err_ts_counter, + endp->bts_state.in_stream.err_ts_counter, + endp->bts_state.out_stream.err_ts_counter); + if (nchars < 0 || nchars >= size) + goto truncate; + + msg += nchars; + size -= nchars; + + if (endp->osmux.state == OSMUX_STATE_ENABLED) { + snprintf(msg, size, + "\r\nX-Osmux-ST: CR=%u, BR=%u", + endp->osmux.stats.chunks, + endp->osmux.stats.octets); + } +truncate: + msg[size - 1] = '\0'; +} + +int mgcp_parse_stats(struct msgb *msg, uint32_t *ps, uint32_t *os, + uint32_t *pr, uint32_t *_or, int *loss, uint32_t *jitter) +{ + char *line, *save; + int rc; + + /* initialize with bad values */ + *ps = *os = *pr = *_or = *jitter = UINT_MAX; + *loss = INT_MAX; + + + line = strtok_r((char *) msg->l2h, "\r\n", &save); + if (!line) + return -1; + + /* this can only parse the message that is created above... */ + for_each_non_empty_line(line, save) { + switch (line[0]) { + case 'P': + rc = sscanf(line, "P: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u", + ps, os, pr, _or, loss, jitter); + return rc == 6 ? 0 : -1; + } + } + + return -1; +} diff --git a/src/libosmo-mgcp/mgcp_sdp.c b/src/libosmo-mgcp/mgcp_sdp.c new file mode 100644 index 000000000..055e1af6c --- /dev/null +++ b/src/libosmo-mgcp/mgcp_sdp.c @@ -0,0 +1,305 @@ +/* + * Some SDP file parsing... + * + * (C) 2009-2015 by Holger Hans Peter Freyther + * (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 . + * + */ + +#include +#include + +#include + +struct sdp_rtp_map { + /* the type */ + int payload_type; + /* null, static or later dynamic codec name */ + char *codec_name; + /* A pointer to the original line for later parsing */ + char *map_line; + + int rate; + int channels; +}; + +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; +} + +void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used) +{ + int i; + + for (i = 0; i < used; ++i) { + switch (codecs[i].payload_type) { + case 0: + codecs[i].codec_name = "PCMU"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + case 3: + codecs[i].codec_name = "GSM"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + case 8: + codecs[i].codec_name = "PCMA"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + case 18: + codecs[i].codec_name = "G729"; + codecs[i].rate = 8000; + codecs[i].channels = 1; + break; + } + } +} + +void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, int payload, char *audio_name) +{ + int i; + + for (i = 0; i < used; ++i) { + char audio_codec[64]; + int rate = -1; + int channels = -1; + 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", audio_name); + continue; + } + + codecs[i].map_line = talloc_strdup(ctx, audio_name); + codecs[i].codec_name = talloc_strdup(ctx, audio_codec); + codecs[i].rate = rate; + codecs[i].channels = channels; + return; + } + + LOGP(DLMGCP, LOGL_ERROR, "Unconfigured PT(%d) with %s\n", payload, audio_name); +} + +int is_codec_compatible(struct mgcp_endpoint *endp, struct sdp_rtp_map *codec) +{ + char *bts_codec; + char audio_codec[64]; + + if (!codec->codec_name) + return 0; + + /* + * GSM, GSM/8000 and GSM/8000/1 should all be compatible.. let's go + * by name first. + */ + bts_codec = endp->tcfg->audio_name; + if (sscanf(bts_codec, "%63[^/]/%*d/%*d", audio_codec) < 1) + return 0; + + return strcasecmp(audio_codec, codec->codec_name) == 0; +} + +int mgcp_parse_sdp_data(struct mgcp_endpoint *endp, struct mgcp_rtp_end *rtp, struct mgcp_parse_data *p) +{ + struct sdp_rtp_map codecs[10]; + int codecs_used = 0; + char *line; + int maxptime = -1; + int i; + int codecs_assigned = 0; + void *tmp_ctx = talloc_new(NULL); + + memset(&codecs, 0, sizeof(codecs)); + + for_each_line(line, p->save) { + switch (line[0]) { + case 'o': + case 's': + case 't': + case 'v': + /* skip these SDP attributes */ + break; + case 'a': { + int payload; + int ptime, ptime2 = 0; + char audio_name[64]; + + + if (sscanf(line, "a=rtpmap:%d %63s", + &payload, audio_name) == 2) { + codecs_update(tmp_ctx, codecs, codecs_used, payload, audio_name); + } else if (sscanf(line, "a=ptime:%d-%d", + &ptime, &ptime2) >= 1) { + if (ptime2 > 0 && ptime2 != ptime) + rtp->packet_duration_ms = 0; + else + rtp->packet_duration_ms = ptime; + } else if (sscanf(line, "a=maxptime:%d", &ptime2) == 1) { + maxptime = ptime2; + } + break; + } + case 'm': { + int port, rc; + + 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) { + rtp->rtp_port = htons(port); + rtp->rtcp_port = htons(port + 1); + codecs_used = rc - 1; + codecs_initialize(tmp_ctx, codecs, codecs_used); + } + break; + } + case 'c': { + char ipv4[16]; + + if (sscanf(line, "c=IN IP4 %15s", ipv4) == 1) { + inet_aton(ipv4, &rtp->addr); + } + break; + } + default: + if (p->endp) + LOGP(DLMGCP, LOGL_NOTICE, + "Unhandled SDP option: '%c'/%d on 0x%x\n", + line[0], line[0], ENDPOINT_NUMBER(p->endp)); + else + LOGP(DLMGCP, LOGL_NOTICE, + "Unhandled SDP option: '%c'/%d\n", + line[0], line[0]); + break; + } + } + + /* 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; + } + + mgcp_set_audio_info(p->cfg, codec, + codecs[i].payload_type, + codecs[i].map_line); + codecs_assigned += 1; + } + + 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; + } + + 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)); + } + + talloc_free(tmp_ctx); + return codecs_assigned > 0; +} + diff --git a/src/libosmo-mgcp/mgcp_transcode.c b/src/libosmo-mgcp/mgcp_transcode.c new file mode 100644 index 000000000..cbabdd1fa --- /dev/null +++ b/src/libosmo-mgcp/mgcp_transcode.c @@ -0,0 +1,611 @@ +/* + * (C) 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 . + * + */ + +#include +#include +#include + + +#include "g711common.h" + +#include +#include +#include + +#include +#include + +int mgcp_transcoding_get_frame_size(void *state_, int nsamples, int dst) +{ + struct mgcp_process_rtp_state *state = state_; + if (dst) + return (nsamples >= 0 ? + nsamples / state->dst_samples_per_frame : + 1) * state->dst_frame_size; + else + return (nsamples >= 0 ? + nsamples / state->src_samples_per_frame : + 1) * state->src_frame_size; +} + +static enum audio_format get_audio_format(const struct mgcp_rtp_codec *codec) +{ + if (codec->subtype_name) { + if (!strcasecmp("GSM", codec->subtype_name)) + return AF_GSM; + if (!strcasecmp("PCMA", codec->subtype_name)) + return AF_PCMA; + if (!strcasecmp("PCMU", codec->subtype_name)) + return AF_PCMU; +#ifdef HAVE_BCG729 + if (!strcasecmp("G729", codec->subtype_name)) + return AF_G729; +#endif + if (!strcasecmp("L16", codec->subtype_name)) + return AF_L16; + } + + switch (codec->payload_type) { + case 0 /* PCMU */: + return AF_PCMU; + case 3 /* GSM */: + return AF_GSM; + case 8 /* PCMA */: + return AF_PCMA; +#ifdef HAVE_BCG729 + case 18 /* G.729 */: + return AF_G729; +#endif + case 11 /* L16 */: + return AF_L16; + default: + return AF_INVALID; + } +} + +static void l16_encode(short *sample, unsigned char *buf, size_t n) +{ + for (; n > 0; --n, ++sample, buf += 2) { + buf[0] = sample[0] >> 8; + buf[1] = sample[0] & 0xff; + } +} + +static void l16_decode(unsigned char *buf, short *sample, size_t n) +{ + for (; n > 0; --n, ++sample, buf += 2) + sample[0] = ((short)buf[0] << 8) | buf[1]; +} + +static void alaw_encode(short *sample, unsigned char *buf, size_t n) +{ + for (; n > 0; --n) + *(buf++) = s16_to_alaw(*(sample++)); +} + +static void alaw_decode(unsigned char *buf, short *sample, size_t n) +{ + for (; n > 0; --n) + *(sample++) = alaw_to_s16(*(buf++)); +} + +static void ulaw_encode(short *sample, unsigned char *buf, size_t n) +{ + for (; n > 0; --n) + *(buf++) = s16_to_ulaw(*(sample++)); +} + +static void ulaw_decode(unsigned char *buf, short *sample, size_t n) +{ + for (; n > 0; --n) + *(sample++) = ulaw_to_s16(*(buf++)); +} + +static int processing_state_destructor(struct mgcp_process_rtp_state *state) +{ + switch (state->src_fmt) { + case AF_GSM: + if (state->src.gsm_handle) + gsm_destroy(state->src.gsm_handle); + break; +#ifdef HAVE_BCG729 + case AF_G729: + if (state->src.g729_dec) + closeBcg729DecoderChannel(state->src.g729_dec); + break; +#endif + default: + break; + } + switch (state->dst_fmt) { + case AF_GSM: + if (state->dst.gsm_handle) + gsm_destroy(state->dst.gsm_handle); + break; +#ifdef HAVE_BCG729 + case AF_G729: + if (state->dst.g729_enc) + closeBcg729EncoderChannel(state->dst.g729_enc); + break; +#endif + default: + break; + } + return 0; +} + +int mgcp_transcoding_setup(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct mgcp_rtp_end *src_end) +{ + struct mgcp_process_rtp_state *state; + enum audio_format src_fmt, dst_fmt; + const struct mgcp_rtp_codec *dst_codec = &dst_end->codec; + + /* cleanup first */ + if (dst_end->rtp_process_data) { + talloc_free(dst_end->rtp_process_data); + dst_end->rtp_process_data = NULL; + } + + if (!src_end) + return 0; + + const struct mgcp_rtp_codec *src_codec = &src_end->codec; + + if (endp->tcfg->no_audio_transcoding) { + LOGP(DLMGCP, LOGL_NOTICE, + "Transcoding disabled on endpoint 0x%x\n", + ENDPOINT_NUMBER(endp)); + return 0; + } + + src_fmt = get_audio_format(src_codec); + dst_fmt = get_audio_format(dst_codec); + + LOGP(DLMGCP, LOGL_ERROR, + "Checking transcoding: %s (%d) -> %s (%d)\n", + src_codec->subtype_name, src_codec->payload_type, + dst_codec->subtype_name, dst_codec->payload_type); + + if (src_fmt == AF_INVALID || dst_fmt == AF_INVALID) { + if (!src_codec->subtype_name || !dst_codec->subtype_name) + /* Not enough info, do nothing */ + return 0; + + if (strcasecmp(src_codec->subtype_name, dst_codec->subtype_name) == 0) + /* Nothing to do */ + return 0; + + LOGP(DLMGCP, LOGL_ERROR, + "Cannot transcode: %s codec not supported (%s -> %s).\n", + src_fmt != AF_INVALID ? "destination" : "source", + src_codec->audio_name, dst_codec->audio_name); + return -EINVAL; + } + + if (src_codec->rate && dst_codec->rate && src_codec->rate != dst_codec->rate) { + LOGP(DLMGCP, LOGL_ERROR, + "Cannot transcode: rate conversion (%d -> %d) not supported.\n", + src_codec->rate, dst_codec->rate); + return -EINVAL; + } + + state = talloc_zero(endp->tcfg->cfg, struct mgcp_process_rtp_state); + talloc_set_destructor(state, processing_state_destructor); + dst_end->rtp_process_data = state; + + state->src_fmt = src_fmt; + + switch (state->src_fmt) { + case AF_L16: + case AF_S16: + state->src_frame_size = 80 * sizeof(short); + state->src_samples_per_frame = 80; + break; + case AF_GSM: + state->src_frame_size = sizeof(gsm_frame); + state->src_samples_per_frame = 160; + state->src.gsm_handle = gsm_create(); + if (!state->src.gsm_handle) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize GSM decoder.\n"); + return -EINVAL; + } + break; +#ifdef HAVE_BCG729 + case AF_G729: + state->src_frame_size = 10; + state->src_samples_per_frame = 80; + state->src.g729_dec = initBcg729DecoderChannel(); + if (!state->src.g729_dec) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize G.729 decoder.\n"); + return -EINVAL; + } + break; +#endif + case AF_PCMU: + case AF_PCMA: + state->src_frame_size = 80; + state->src_samples_per_frame = 80; + break; + default: + break; + } + + state->dst_fmt = dst_fmt; + + switch (state->dst_fmt) { + case AF_L16: + case AF_S16: + state->dst_frame_size = 80*sizeof(short); + state->dst_samples_per_frame = 80; + break; + case AF_GSM: + state->dst_frame_size = sizeof(gsm_frame); + state->dst_samples_per_frame = 160; + state->dst.gsm_handle = gsm_create(); + if (!state->dst.gsm_handle) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize GSM encoder.\n"); + return -EINVAL; + } + break; +#ifdef HAVE_BCG729 + case AF_G729: + state->dst_frame_size = 10; + state->dst_samples_per_frame = 80; + state->dst.g729_enc = initBcg729EncoderChannel(); + if (!state->dst.g729_enc) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize G.729 decoder.\n"); + return -EINVAL; + } + break; +#endif + case AF_PCMU: + case AF_PCMA: + state->dst_frame_size = 80; + state->dst_samples_per_frame = 80; + break; + default: + break; + } + + if (dst_end->force_output_ptime) + state->dst_packet_duration = mgcp_rtp_packet_duration(endp, dst_end); + + LOGP(DLMGCP, LOGL_INFO, + "Initialized RTP processing on: 0x%x " + "conv: %d (%d, %d, %s) -> %d (%d, %d, %s)\n", + ENDPOINT_NUMBER(endp), + src_fmt, src_codec->payload_type, src_codec->rate, src_end->fmtp_extra, + dst_fmt, dst_codec->payload_type, dst_codec->rate, dst_end->fmtp_extra); + + return 0; +} + +void mgcp_transcoding_net_downlink_format(struct mgcp_endpoint *endp, + int *payload_type, + const char**audio_name, + const char**fmtp_extra) +{ + struct mgcp_process_rtp_state *state = endp->net_end.rtp_process_data; + struct mgcp_rtp_codec *net_codec = &endp->net_end.codec; + struct mgcp_rtp_codec *bts_codec = &endp->bts_end.codec; + + if (!state || net_codec->payload_type < 0) { + *payload_type = bts_codec->payload_type; + *audio_name = bts_codec->audio_name; + *fmtp_extra = endp->bts_end.fmtp_extra; + return; + } + + *payload_type = net_codec->payload_type; + *audio_name = net_codec->audio_name; + *fmtp_extra = endp->net_end.fmtp_extra; +} + +static int decode_audio(struct mgcp_process_rtp_state *state, + uint8_t **src, size_t *nbytes) +{ + while (*nbytes >= state->src_frame_size) { + if (state->sample_cnt + state->src_samples_per_frame > ARRAY_SIZE(state->samples)) { + LOGP(DLMGCP, LOGL_ERROR, + "Sample buffer too small: %zu > %zu.\n", + state->sample_cnt + state->src_samples_per_frame, + ARRAY_SIZE(state->samples)); + return -ENOSPC; + } + switch (state->src_fmt) { + case AF_GSM: + if (gsm_decode(state->src.gsm_handle, + (gsm_byte *)*src, state->samples + state->sample_cnt) < 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to decode GSM.\n"); + return -EINVAL; + } + break; +#ifdef HAVE_BCG729 + case AF_G729: + bcg729Decoder(state->src.g729_dec, *src, 0, state->samples + state->sample_cnt); + break; +#endif + case AF_PCMU: + ulaw_decode(*src, state->samples + state->sample_cnt, + state->src_samples_per_frame); + break; + case AF_PCMA: + alaw_decode(*src, state->samples + state->sample_cnt, + state->src_samples_per_frame); + break; + case AF_S16: + memmove(state->samples + state->sample_cnt, *src, + state->src_frame_size); + break; + case AF_L16: + l16_decode(*src, state->samples + state->sample_cnt, + state->src_samples_per_frame); + break; + default: + break; + } + *src += state->src_frame_size; + *nbytes -= state->src_frame_size; + state->sample_cnt += state->src_samples_per_frame; + } + return 0; +} + +static int encode_audio(struct mgcp_process_rtp_state *state, + uint8_t *dst, size_t buf_size, size_t max_samples) +{ + int nbytes = 0; + size_t nsamples = 0; + /* Encode samples into dst */ + while (nsamples + state->dst_samples_per_frame <= max_samples) { + if (nbytes + state->dst_frame_size > buf_size) { + if (nbytes > 0) + break; + + /* Not even one frame fits into the buffer */ + LOGP(DLMGCP, LOGL_INFO, + "Encoding (RTP) buffer too small: %zu > %zu.\n", + nbytes + state->dst_frame_size, buf_size); + return -ENOSPC; + } + switch (state->dst_fmt) { + case AF_GSM: + gsm_encode(state->dst.gsm_handle, + state->samples + state->sample_offs, dst); + break; +#ifdef HAVE_BCG729 + case AF_G729: + bcg729Encoder(state->dst.g729_enc, + state->samples + state->sample_offs, dst); + break; +#endif + case AF_PCMU: + ulaw_encode(state->samples + state->sample_offs, dst, + state->src_samples_per_frame); + break; + case AF_PCMA: + alaw_encode(state->samples + state->sample_offs, dst, + state->src_samples_per_frame); + break; + case AF_S16: + memmove(dst, state->samples + state->sample_offs, + state->dst_frame_size); + break; + case AF_L16: + l16_encode(state->samples + state->sample_offs, dst, + state->src_samples_per_frame); + break; + default: + break; + } + dst += state->dst_frame_size; + nbytes += state->dst_frame_size; + state->sample_offs += state->dst_samples_per_frame; + nsamples += state->dst_samples_per_frame; + } + state->sample_cnt -= nsamples; + return nbytes; +} + +static struct mgcp_rtp_end *source_for_dest(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end) +{ + if (&endp->bts_end == dst_end) + return &endp->net_end; + else if (&endp->net_end == dst_end) + return &endp->bts_end; + OSMO_ASSERT(0); +} + +/* + * With some modems we get offered multiple codecs + * and we have selected one of them. It might not + * be the right one and we need to detect this with + * the first audio packets. One difficulty is that + * we patch the rtp payload type in place, so we + * need to discuss this. + */ +struct mgcp_process_rtp_state *check_transcode_state( + struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + struct rtp_hdr *rtp_hdr) +{ + struct mgcp_rtp_end *src_end; + + /* Only deal with messages from net to bts */ + if (&endp->bts_end != dst_end) + goto done; + + src_end = source_for_dest(endp, dst_end); + + /* Already patched */ + if (rtp_hdr->payload_type == dst_end->codec.payload_type) + goto done; + /* The payload we expect */ + if (rtp_hdr->payload_type == src_end->codec.payload_type) + goto done; + /* The matching alternate payload type? Then switch */ + if (rtp_hdr->payload_type == src_end->alt_codec.payload_type) { + struct mgcp_config *cfg = endp->cfg; + struct mgcp_rtp_codec tmp_codec = src_end->alt_codec; + src_end->alt_codec = src_end->codec; + src_end->codec = tmp_codec; + cfg->setup_rtp_processing_cb(endp, &endp->net_end, &endp->bts_end); + cfg->setup_rtp_processing_cb(endp, &endp->bts_end, &endp->net_end); + } + +done: + return dst_end->rtp_process_data; +} + +int mgcp_transcoding_process_rtp(struct mgcp_endpoint *endp, + struct mgcp_rtp_end *dst_end, + char *data, int *len, int buf_size) +{ + struct mgcp_process_rtp_state *state; + const size_t rtp_hdr_size = sizeof(struct rtp_hdr); + struct rtp_hdr *rtp_hdr = (struct rtp_hdr *) data; + char *payload_data = (char *) &rtp_hdr->data[0]; + int payload_len = *len - rtp_hdr_size; + uint8_t *src = (uint8_t *)payload_data; + uint8_t *dst = (uint8_t *)payload_data; + size_t nbytes = payload_len; + size_t nsamples; + size_t max_samples; + uint32_t ts_no; + int rc; + + state = check_transcode_state(endp, dst_end, rtp_hdr); + if (!state) + return 0; + + if (state->src_fmt == state->dst_fmt) { + if (!state->dst_packet_duration) + return 0; + + /* TODO: repackage without transcoding */ + } + + /* If the remaining samples do not fit into a fixed ptime, + * a) discard them, if the next packet is much later + * b) add silence and * send it, if the current packet is not + * yet too late + * c) append the sample data, if the timestamp matches exactly + */ + + /* TODO: check payload type (-> G.711 comfort noise) */ + + if (payload_len > 0) { + ts_no = ntohl(rtp_hdr->timestamp); + if (!state->is_running) { + state->next_seq = ntohs(rtp_hdr->sequence); + state->next_time = ts_no; + state->is_running = 1; + } + + + if (state->sample_cnt > 0) { + int32_t delta = ts_no - state->next_time; + /* TODO: check sequence? reordering? packet loss? */ + + if (delta > state->sample_cnt) { + /* There is a time gap between the last packet + * and the current one. Just discard the + * partial data that is left in the buffer. + * TODO: This can be improved by adding silence + * instead if the delta is small enough. + */ + LOGP(DLMGCP, LOGL_NOTICE, + "0x%x dropping sample buffer due delta=%d sample_cnt=%zu\n", + ENDPOINT_NUMBER(endp), delta, state->sample_cnt); + state->sample_cnt = 0; + state->next_time = ts_no; + } else if (delta < 0) { + LOGP(DLMGCP, LOGL_NOTICE, + "RTP time jumps backwards, delta = %d, " + "discarding buffered samples\n", + delta); + state->sample_cnt = 0; + state->sample_offs = 0; + return -EAGAIN; + } + + /* Make sure the samples start without offset */ + if (state->sample_offs && state->sample_cnt) + memmove(&state->samples[0], + &state->samples[state->sample_offs], + state->sample_cnt * + sizeof(state->samples[0])); + } + + state->sample_offs = 0; + + /* Append decoded audio to samples */ + decode_audio(state, &src, &nbytes); + + if (nbytes > 0) + LOGP(DLMGCP, LOGL_NOTICE, + "Skipped audio frame in RTP packet: %zu octets\n", + nbytes); + } else + ts_no = state->next_time; + + if (state->sample_cnt < state->dst_packet_duration) + return -EAGAIN; + + max_samples = + state->dst_packet_duration ? + state->dst_packet_duration : state->sample_cnt; + + nsamples = state->sample_cnt; + + rc = encode_audio(state, dst, buf_size, max_samples); + /* + * There were no samples to encode? + * TODO: how does this work for comfort noise? + */ + if (rc == 0) + return -ENOMSG; + /* Any other error during the encoding */ + if (rc < 0) + return rc; + + nsamples -= state->sample_cnt; + + *len = rtp_hdr_size + rc; + rtp_hdr->sequence = htons(state->next_seq); + rtp_hdr->timestamp = htonl(ts_no); + + state->next_seq += 1; + state->next_time = ts_no + nsamples; + + /* + * XXX: At this point we should always have consumed + * samples. So doing OSMO_ASSERT(nsamples > 0) and returning + * rtp_hdr_size should be fine. + */ + return nsamples ? rtp_hdr_size : 0; +} diff --git a/src/libosmo-mgcp/mgcp_vty.c b/src/libosmo-mgcp/mgcp_vty.c new file mode 100644 index 000000000..a48e20508 --- /dev/null +++ b/src/libosmo-mgcp/mgcp_vty.c @@ -0,0 +1,1555 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The protocol implementation */ + +/* + * (C) 2009-2014 by Holger Hans Peter Freyther + * (C) 2009-2011 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 . + * + */ + + +#include + +#include +#include +#include + +#include + +#define RTCP_OMIT_STR "Drop RTCP packets in both directions\n" +#define RTP_PATCH_STR "Modify RTP packet header in both directions\n" +#define RTP_KEEPALIVE_STR "Send dummy UDP packet to net RTP destination\n" + +static struct mgcp_config *g_cfg = NULL; + +static struct mgcp_trunk_config *find_trunk(struct mgcp_config *cfg, int nr) +{ + struct mgcp_trunk_config *trunk; + + if (nr == 0) + trunk = &cfg->trunk; + else + trunk = mgcp_trunk_num(cfg, nr); + + return trunk; +} + +/* + * vty code for mgcp below + */ +struct cmd_node mgcp_node = { + MGCP_NODE, + "%s(config-mgcp)# ", + 1, +}; + +struct cmd_node trunk_node = { + TRUNK_NODE, + "%s(config-mgcp-trunk)# ", + 1, +}; + +static int config_write_mgcp(struct vty *vty) +{ + vty_out(vty, "mgcp%s", VTY_NEWLINE); + if (g_cfg->local_ip) + vty_out(vty, " local ip %s%s", g_cfg->local_ip, VTY_NEWLINE); + if (g_cfg->bts_ip && strlen(g_cfg->bts_ip) != 0) + vty_out(vty, " bts ip %s%s", g_cfg->bts_ip, VTY_NEWLINE); + vty_out(vty, " bind ip %s%s", g_cfg->source_addr, VTY_NEWLINE); + vty_out(vty, " bind port %u%s", g_cfg->source_port, VTY_NEWLINE); + + if (g_cfg->bts_ports.mode == PORT_ALLOC_STATIC) + vty_out(vty, " rtp bts-base %u%s", g_cfg->bts_ports.base_port, VTY_NEWLINE); + else + vty_out(vty, " rtp bts-range %u %u%s", + g_cfg->bts_ports.range_start, g_cfg->bts_ports.range_end, VTY_NEWLINE); + if (g_cfg->bts_ports.bind_addr) + vty_out(vty, " rtp bts-bind-ip %s%s", g_cfg->bts_ports.bind_addr, VTY_NEWLINE); + + if (g_cfg->net_ports.mode == PORT_ALLOC_STATIC) + vty_out(vty, " rtp net-base %u%s", g_cfg->net_ports.base_port, VTY_NEWLINE); + else + vty_out(vty, " rtp net-range %u %u%s", + g_cfg->net_ports.range_start, g_cfg->net_ports.range_end, VTY_NEWLINE); + if (g_cfg->net_ports.bind_addr) + vty_out(vty, " rtp net-bind-ip %s%s", g_cfg->net_ports.bind_addr, VTY_NEWLINE); + + vty_out(vty, " rtp ip-dscp %d%s", g_cfg->endp_dscp, VTY_NEWLINE); + if (g_cfg->trunk.keepalive_interval == MGCP_KEEPALIVE_ONCE) + vty_out(vty, " rtp keep-alive once%s", VTY_NEWLINE); + else if (g_cfg->trunk.keepalive_interval) + vty_out(vty, " rtp keep-alive %d%s", + g_cfg->trunk.keepalive_interval, VTY_NEWLINE); + else + vty_out(vty, " no rtp keep-alive%s", VTY_NEWLINE); + + if (g_cfg->trunk.omit_rtcp) + vty_out(vty, " rtcp-omit%s", VTY_NEWLINE); + else + vty_out(vty, " no rtcp-omit%s", VTY_NEWLINE); + if (g_cfg->trunk.force_constant_ssrc || g_cfg->trunk.force_aligned_timing) { + vty_out(vty, " %srtp-patch ssrc%s", + g_cfg->trunk.force_constant_ssrc ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %srtp-patch timestamp%s", + g_cfg->trunk.force_aligned_timing ? "" : "no ", VTY_NEWLINE); + } else + vty_out(vty, " no rtp-patch%s", VTY_NEWLINE); + if (g_cfg->trunk.audio_payload != -1) + vty_out(vty, " sdp audio-payload number %d%s", + g_cfg->trunk.audio_payload, VTY_NEWLINE); + if (g_cfg->trunk.audio_name) + vty_out(vty, " sdp audio-payload name %s%s", + g_cfg->trunk.audio_name, VTY_NEWLINE); + if (g_cfg->trunk.audio_fmtp_extra) + vty_out(vty, " sdp audio fmtp-extra %s%s", + g_cfg->trunk.audio_fmtp_extra, VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-ptime%s", + g_cfg->trunk.audio_send_ptime ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-name%s", + g_cfg->trunk.audio_send_name ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " loop %u%s", !!g_cfg->trunk.audio_loop, VTY_NEWLINE); + vty_out(vty, " number endpoints %u%s", g_cfg->trunk.number_endpoints - 1, VTY_NEWLINE); + vty_out(vty, " %sallow-transcoding%s", + g_cfg->trunk.no_audio_transcoding ? "no " : "", VTY_NEWLINE); + if (g_cfg->call_agent_addr) + vty_out(vty, " call-agent ip %s%s", g_cfg->call_agent_addr, VTY_NEWLINE); + if (g_cfg->transcoder_ip) + vty_out(vty, " transcoder-mgw %s%s", g_cfg->transcoder_ip, VTY_NEWLINE); + + if (g_cfg->transcoder_ports.mode == PORT_ALLOC_STATIC) + vty_out(vty, " rtp transcoder-base %u%s", g_cfg->transcoder_ports.base_port, VTY_NEWLINE); + else + vty_out(vty, " rtp transcoder-range %u %u%s", + g_cfg->transcoder_ports.range_start, g_cfg->transcoder_ports.range_end, VTY_NEWLINE); + if (g_cfg->bts_force_ptime > 0) + vty_out(vty, " rtp force-ptime %d%s", g_cfg->bts_force_ptime, VTY_NEWLINE); + vty_out(vty, " transcoder-remote-base %u%s", g_cfg->transcoder_remote_base, VTY_NEWLINE); + + switch (g_cfg->osmux) { + case OSMUX_USAGE_ON: + vty_out(vty, " osmux on%s", VTY_NEWLINE); + break; + case OSMUX_USAGE_ONLY: + vty_out(vty, " osmux only%s", VTY_NEWLINE); + break; + case OSMUX_USAGE_OFF: + default: + vty_out(vty, " osmux off%s", VTY_NEWLINE); + break; + } + if (g_cfg->osmux) { + vty_out(vty, " osmux bind-ip %s%s", + g_cfg->osmux_addr, VTY_NEWLINE); + vty_out(vty, " osmux batch-factor %d%s", + g_cfg->osmux_batch, VTY_NEWLINE); + vty_out(vty, " osmux batch-size %u%s", + g_cfg->osmux_batch_size, VTY_NEWLINE); + vty_out(vty, " osmux port %u%s", + g_cfg->osmux_port, VTY_NEWLINE); + vty_out(vty, " osmux dummy %s%s", + g_cfg->osmux_dummy ? "on" : "off", VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +static void dump_rtp_end(const char *end_name, struct vty *vty, + struct mgcp_rtp_state *state, struct mgcp_rtp_end *end) +{ + struct mgcp_rtp_codec *codec = &end->codec; + + vty_out(vty, + " %s%s" + " Timestamp Errs: %d->%d%s" + " Dropped Packets: %d%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", + end_name, VTY_NEWLINE, + state->in_stream.err_ts_counter, + state->out_stream.err_ts_counter, VTY_NEWLINE, + end->dropped_packets, 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, VTY_NEWLINE, + end->fmtp_extra, codec->audio_name, codec->subtype_name, VTY_NEWLINE, + end->output_enabled, end->force_output_ptime, VTY_NEWLINE); +} + +static void dump_trunk(struct vty *vty, struct mgcp_trunk_config *cfg, int verbose) +{ + int i; + + vty_out(vty, "%s trunk nr %d with %d endpoints:%s", + cfg->trunk_type == MGCP_TRUNK_VIRTUAL ? "Virtual" : "E1", + cfg->trunk_nr, cfg->number_endpoints - 1, VTY_NEWLINE); + + if (!cfg->endpoints) { + vty_out(vty, "No endpoints allocated yet.%s", VTY_NEWLINE); + return; + } + + for (i = 1; i < cfg->number_endpoints; ++i) { + struct mgcp_endpoint *endp = &cfg->endpoints[i]; + vty_out(vty, + " Endpoint 0x%.2x: CI: %d net: %u/%u bts: %u/%u on %s " + "traffic received bts: %u remote: %u transcoder: %u/%u%s", + i, endp->ci, + ntohs(endp->net_end.rtp_port), ntohs(endp->net_end.rtcp_port), + ntohs(endp->bts_end.rtp_port), ntohs(endp->bts_end.rtcp_port), + inet_ntoa(endp->bts_end.addr), + endp->bts_end.packets, endp->net_end.packets, + endp->trans_net.packets, endp->trans_bts.packets, + VTY_NEWLINE); + + if (verbose && endp->allocated) { + dump_rtp_end("Net->BTS", vty, &endp->bts_state, &endp->bts_end); + dump_rtp_end("BTS->Net", vty, &endp->net_state, &endp->net_end); + } + } +} + +DEFUN(show_mcgp, show_mgcp_cmd, + "show mgcp [stats]", + SHOW_STR + "Display information about the MGCP Media Gateway\n" + "Include Statistics\n") +{ + struct mgcp_trunk_config *trunk; + int show_stats = argc >= 1; + + dump_trunk(vty, &g_cfg->trunk, show_stats); + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) + dump_trunk(vty, trunk, show_stats); + + if (g_cfg->osmux) + vty_out(vty, "Osmux used CID: %d%s", osmux_used_cid(), VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp, + cfg_mgcp_cmd, + "mgcp", + "Configure the MGCP") +{ + vty->node = MGCP_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_local_ip, + cfg_mgcp_local_ip_cmd, + "local ip A.B.C.D", + "Local options for the SDP record\n" + IP_STR + "IPv4 Address to use in SDP record\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->local_ip, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bts_ip, + cfg_mgcp_bts_ip_cmd, + "bts ip A.B.C.D", + "BTS Audio source/destination options\n" + IP_STR + "IPv4 Address of the BTS\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->bts_ip, argv[0]); + inet_aton(g_cfg->bts_ip, &g_cfg->bts_in); + return CMD_SUCCESS; +} + +#define BIND_STR "Listen/Bind related socket option\n" +DEFUN(cfg_mgcp_bind_ip, + cfg_mgcp_bind_ip_cmd, + "bind ip A.B.C.D", + BIND_STR + IP_STR + "IPv4 Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->source_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bind_port, + cfg_mgcp_bind_port_cmd, + "bind port <0-65534>", + BIND_STR + "Port information\n" + "UDP port to listen for MGCP messages\n") +{ + unsigned int port = atoi(argv[0]); + g_cfg->source_port = port; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_bind_early, + cfg_mgcp_bind_early_cmd, + "bind early (0|1)", + BIND_STR + "Bind local ports on start up\n" + "Bind on demand\n" "Bind on startup\n") +{ + vty_out(vty, "bind early is deprecated, remove it from the config.\n"); + return CMD_WARNING; +} + +static void parse_base(struct mgcp_port_range *range, const char **argv) +{ + unsigned int port = atoi(argv[0]); + range->mode = PORT_ALLOC_STATIC; + range->base_port = port; +} + +static void parse_range(struct mgcp_port_range *range, const char **argv) +{ + range->mode = PORT_ALLOC_DYNAMIC; + range->range_start = atoi(argv[0]); + range->range_end = atoi(argv[1]); + range->last_port = g_cfg->bts_ports.range_start; +} + + +#define RTP_STR "RTP configuration\n" +#define BTS_START_STR "First UDP port allocated for the BTS side\n" +#define NET_START_STR "First UDP port allocated for the NET side\n" +#define UDP_PORT_STR "UDP Port number\n" +DEFUN(cfg_mgcp_rtp_bts_base_port, + cfg_mgcp_rtp_bts_base_port_cmd, + "rtp bts-base <0-65534>", + RTP_STR + BTS_START_STR + UDP_PORT_STR) +{ + parse_base(&g_cfg->bts_ports, argv); + return CMD_SUCCESS; +} + +#define RANGE_START_STR "Start of the range of ports\n" +#define RANGE_END_STR "End of the range of ports\n" +DEFUN(cfg_mgcp_rtp_bts_range, + cfg_mgcp_rtp_bts_range_cmd, + "rtp bts-range <0-65534> <0-65534>", + RTP_STR "Range of ports to use for the BTS side\n" + RANGE_START_STR RANGE_END_STR) +{ + parse_range(&g_cfg->bts_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_net_range, + cfg_mgcp_rtp_net_range_cmd, + "rtp net-range <0-65534> <0-65534>", + 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); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_net_base_port, + cfg_mgcp_rtp_net_base_port_cmd, + "rtp net-base <0-65534>", + RTP_STR NET_START_STR UDP_PORT_STR) +{ + parse_base(&g_cfg->net_ports, argv); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_rtp_bts_base_port, cfg_mgcp_rtp_base_port_cmd, + "rtp base <0-65534>", + RTP_STR BTS_START_STR UDP_PORT_STR) + +DEFUN(cfg_mgcp_rtp_transcoder_range, + cfg_mgcp_rtp_transcoder_range_cmd, + "rtp transcoder-range <0-65534> <0-65534>", + RTP_STR "Range of ports to use for the Transcoder\n" + RANGE_START_STR RANGE_END_STR) +{ + parse_range(&g_cfg->transcoder_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_transcoder_base, + cfg_mgcp_rtp_transcoder_base_cmd, + "rtp transcoder-base <0-65534>", + RTP_STR "First UDP port allocated for the Transcoder side\n" + UDP_PORT_STR) +{ + parse_base(&g_cfg->transcoder_ports, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_bts_bind_ip, + cfg_mgcp_rtp_bts_bind_ip_cmd, + "rtp bts-bind-ip A.B.C.D", + RTP_STR "Bind endpoints facing the BTS\n" "Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->bts_ports.bind_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_no_bts_bind_ip, + cfg_mgcp_rtp_no_bts_bind_ip_cmd, + "no rtp bts-bind-ip", + NO_STR RTP_STR "Bind endpoints facing the BTS\n" "Address to bind to\n") +{ + talloc_free(g_cfg->bts_ports.bind_addr); + g_cfg->bts_ports.bind_addr = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_net_bind_ip, + cfg_mgcp_rtp_net_bind_ip_cmd, + "rtp net-bind-ip A.B.C.D", + RTP_STR "Bind endpoints facing the Network\n" "Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->net_ports.bind_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_no_net_bind_ip, + cfg_mgcp_rtp_no_net_bind_ip_cmd, + "no rtp net-bind-ip", + NO_STR RTP_STR "Bind endpoints facing the Network\n" "Address to bind to\n") +{ + talloc_free(g_cfg->net_ports.bind_addr); + g_cfg->net_ports.bind_addr = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_ip_dscp, + cfg_mgcp_rtp_ip_dscp_cmd, + "rtp ip-dscp <0-255>", + RTP_STR + "Apply IP_TOS to the audio stream (including Osmux)\n" "The DSCP value\n") +{ + int dscp = atoi(argv[0]); + g_cfg->endp_dscp = dscp; + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_rtp_ip_dscp, cfg_mgcp_rtp_ip_tos_cmd, + "rtp ip-tos <0-255>", + RTP_STR + "Apply IP_TOS to the audio stream\n" "The DSCP value\n") + +#define FORCE_PTIME_STR "Force a fixed ptime for packets sent to the BTS" +DEFUN(cfg_mgcp_rtp_force_ptime, + cfg_mgcp_rtp_force_ptime_cmd, + "rtp force-ptime (10|20|40)", + RTP_STR FORCE_PTIME_STR + "The required ptime (packet duration) in ms\n" + "10 ms\n20 ms\n40 ms\n") +{ + g_cfg->bts_force_ptime = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_rtp_force_ptime, + cfg_mgcp_no_rtp_force_ptime_cmd, + "no rtp force-ptime", + NO_STR RTP_STR FORCE_PTIME_STR) +{ + g_cfg->bts_force_ptime = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_sdp_fmtp_extra, + cfg_mgcp_sdp_fmtp_extra_cmd, + "sdp audio fmtp-extra .NAME", + "Add extra fmtp for the SDP file\n" "Audio\n" "Fmtp-extra\n" + "Extra Information\n") +{ + char *txt = argv_concat(argv, argc, 0); + if (!txt) + return CMD_WARNING; + + osmo_talloc_replace_string(g_cfg, &g_cfg->trunk.audio_fmtp_extra, txt); + talloc_free(txt); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_allow_transcoding, + cfg_mgcp_allow_transcoding_cmd, + "allow-transcoding", + "Allow transcoding\n") +{ + g_cfg->trunk.no_audio_transcoding = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_allow_transcoding, + cfg_mgcp_no_allow_transcoding_cmd, + "no allow-transcoding", + NO_STR "Allow transcoding\n") +{ + g_cfg->trunk.no_audio_transcoding = 1; + return CMD_SUCCESS; +} + +#define SDP_STR "SDP File related options\n" +#define AUDIO_STR "Audio payload options\n" +DEFUN(cfg_mgcp_sdp_payload_number, + cfg_mgcp_sdp_payload_number_cmd, + "sdp audio-payload number <0-255>", + SDP_STR AUDIO_STR + "Number\n" "Payload number\n") +{ + unsigned int payload = atoi(argv[0]); + g_cfg->trunk.audio_payload = payload; + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_sdp_payload_number, cfg_mgcp_sdp_payload_number_cmd_old, + "sdp audio payload number <0-255>", + SDP_STR AUDIO_STR AUDIO_STR "Number\n" "Payload number\n") + + +DEFUN(cfg_mgcp_sdp_payload_name, + cfg_mgcp_sdp_payload_name_cmd, + "sdp audio-payload name NAME", + SDP_STR AUDIO_STR "Name\n" "Payload name\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->trunk.audio_name, argv[0]); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_sdp_payload_name, cfg_mgcp_sdp_payload_name_cmd_old, + "sdp audio payload name NAME", + SDP_STR AUDIO_STR AUDIO_STR "Name\n" "Payload name\n") + +DEFUN(cfg_mgcp_sdp_payload_send_ptime, + cfg_mgcp_sdp_payload_send_ptime_cmd, + "sdp audio-payload send-ptime", + SDP_STR AUDIO_STR + "Send SDP ptime (packet duration) attribute\n") +{ + g_cfg->trunk.audio_send_ptime = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_sdp_payload_send_ptime, + cfg_mgcp_no_sdp_payload_send_ptime_cmd, + "no sdp audio-payload send-ptime", + NO_STR SDP_STR AUDIO_STR + "Send SDP ptime (packet duration) attribute\n") +{ + g_cfg->trunk.audio_send_ptime = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_sdp_payload_send_name, + cfg_mgcp_sdp_payload_send_name_cmd, + "sdp audio-payload send-name", + SDP_STR AUDIO_STR + "Send SDP rtpmap with the audio name\n") +{ + g_cfg->trunk.audio_send_name = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_sdp_payload_send_name, + cfg_mgcp_no_sdp_payload_send_name_cmd, + "no sdp audio-payload send-name", + NO_STR SDP_STR AUDIO_STR + "Send SDP rtpmap with the audio name\n") +{ + g_cfg->trunk.audio_send_name = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_loop, + cfg_mgcp_loop_cmd, + "loop (0|1)", + "Loop audio for all endpoints on main trunk\n" + "Don't Loop\n" "Loop\n") +{ + if (g_cfg->osmux) { + vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + g_cfg->trunk.audio_loop = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_force_realloc, + cfg_mgcp_force_realloc_cmd, + "force-realloc (0|1)", + "Force endpoint reallocation when the endpoint is still seized\n" + "Don't force reallocation\n" "force reallocation\n") +{ + g_cfg->trunk.force_realloc = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_number_endp, + cfg_mgcp_number_endp_cmd, + "number endpoints <0-65534>", + "Number options\n" "Endpoints available\n" "Number endpoints\n") +{ + /* + 1 as we start counting at one */ + g_cfg->trunk.number_endpoints = atoi(argv[0]) + 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_omit_rtcp, + cfg_mgcp_omit_rtcp_cmd, + "rtcp-omit", + RTCP_OMIT_STR) +{ + g_cfg->trunk.omit_rtcp = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_omit_rtcp, + cfg_mgcp_no_omit_rtcp_cmd, + "no rtcp-omit", + NO_STR RTCP_OMIT_STR) +{ + g_cfg->trunk.omit_rtcp = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_patch_rtp_ssrc, + cfg_mgcp_patch_rtp_ssrc_cmd, + "rtp-patch ssrc", + RTP_PATCH_STR + "Force a fixed SSRC\n" + ) +{ + g_cfg->trunk.force_constant_ssrc = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_patch_rtp_ssrc, + cfg_mgcp_no_patch_rtp_ssrc_cmd, + "no rtp-patch ssrc", + NO_STR RTP_PATCH_STR + "Force a fixed SSRC\n" + ) +{ + g_cfg->trunk.force_constant_ssrc = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_patch_rtp_ts, + cfg_mgcp_patch_rtp_ts_cmd, + "rtp-patch timestamp", + RTP_PATCH_STR + "Adjust RTP timestamp\n" + ) +{ + g_cfg->trunk.force_aligned_timing = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_patch_rtp_ts, + cfg_mgcp_no_patch_rtp_ts_cmd, + "no rtp-patch timestamp", + NO_STR RTP_PATCH_STR + "Adjust RTP timestamp\n" + ) +{ + g_cfg->trunk.force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_patch_rtp, + cfg_mgcp_no_patch_rtp_cmd, + "no rtp-patch", + NO_STR RTP_PATCH_STR) +{ + g_cfg->trunk.force_constant_ssrc = 0; + g_cfg->trunk.force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_keepalive, + cfg_mgcp_rtp_keepalive_cmd, + "rtp keep-alive <1-120>", + RTP_STR RTP_KEEPALIVE_STR + "Keep alive interval in secs\n" + ) +{ + mgcp_trunk_set_keepalive(&g_cfg->trunk, atoi(argv[0])); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_rtp_keepalive_once, + cfg_mgcp_rtp_keepalive_once_cmd, + "rtp keep-alive once", + RTP_STR RTP_KEEPALIVE_STR + "Send dummy packet only once after CRCX/MDCX\n" + ) +{ + mgcp_trunk_set_keepalive(&g_cfg->trunk, MGCP_KEEPALIVE_ONCE); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_rtp_keepalive, + cfg_mgcp_no_rtp_keepalive_cmd, + "no rtp keep-alive", + NO_STR RTP_STR RTP_KEEPALIVE_STR + ) +{ + mgcp_trunk_set_keepalive(&g_cfg->trunk, 0); + return CMD_SUCCESS; +} + + + +#define CALL_AGENT_STR "Callagent information\n" +DEFUN(cfg_mgcp_agent_addr, + cfg_mgcp_agent_addr_cmd, + "call-agent ip A.B.C.D", + CALL_AGENT_STR IP_STR + "IPv4 Address of the callagent\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->call_agent_addr, argv[0]); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_mgcp_agent_addr, cfg_mgcp_agent_addr_cmd_old, + "call agent ip A.B.C.D", + CALL_AGENT_STR CALL_AGENT_STR IP_STR + "IPv4 Address of the callagent\n") + + +DEFUN(cfg_mgcp_transcoder, + cfg_mgcp_transcoder_cmd, + "transcoder-mgw A.B.C.D", + "Use a MGW to detranscoder RTP\n" + "The IP address of the MGW") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->transcoder_ip, argv[0]); + inet_aton(g_cfg->transcoder_ip, &g_cfg->transcoder_in); + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_no_transcoder, + cfg_mgcp_no_transcoder_cmd, + "no transcoder-mgw", + NO_STR "Disable the transcoding\n") +{ + if (g_cfg->transcoder_ip) { + LOGP(DLMGCP, LOGL_NOTICE, "Disabling transcoding on future calls.\n"); + talloc_free(g_cfg->transcoder_ip); + g_cfg->transcoder_ip = NULL; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_transcoder_remote_base, + cfg_mgcp_transcoder_remote_base_cmd, + "transcoder-remote-base <0-65534>", + "Set the base port for the transcoder\n" "The RTP base port on the transcoder") +{ + g_cfg->transcoder_remote_base = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_trunk, cfg_mgcp_trunk_cmd, + "trunk <1-64>", + "Configure a SS7 trunk\n" "Trunk Nr\n") +{ + struct mgcp_trunk_config *trunk; + int index = atoi(argv[0]); + + trunk = mgcp_trunk_num(g_cfg, index); + if (!trunk) + trunk = mgcp_trunk_alloc(g_cfg, index); + + if (!trunk) { + vty_out(vty, "%%Unable to allocate trunk %u.%s", + index, VTY_NEWLINE); + return CMD_WARNING; + } + + vty->node = TRUNK_NODE; + vty->index = trunk; + return CMD_SUCCESS; +} + +static int config_write_trunk(struct vty *vty) +{ + struct mgcp_trunk_config *trunk; + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) { + vty_out(vty, " trunk %d%s", trunk->trunk_nr, VTY_NEWLINE); + vty_out(vty, " sdp audio-payload number %d%s", + trunk->audio_payload, VTY_NEWLINE); + vty_out(vty, " sdp audio-payload name %s%s", + trunk->audio_name, VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-ptime%s", + trunk->audio_send_ptime ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %ssdp audio-payload send-name%s", + trunk->audio_send_name ? "" : "no ", VTY_NEWLINE); + + if (trunk->keepalive_interval == MGCP_KEEPALIVE_ONCE) + vty_out(vty, " rtp keep-alive once%s", VTY_NEWLINE); + else if (trunk->keepalive_interval) + vty_out(vty, " rtp keep-alive %d%s", + trunk->keepalive_interval, VTY_NEWLINE); + else + vty_out(vty, " no rtp keep-alive%s", VTY_NEWLINE); + vty_out(vty, " loop %d%s", + trunk->audio_loop, VTY_NEWLINE); + vty_out(vty, " force-realloc %d%s", + trunk->force_realloc, VTY_NEWLINE); + if (trunk->omit_rtcp) + vty_out(vty, " rtcp-omit%s", VTY_NEWLINE); + else + vty_out(vty, " no rtcp-omit%s", VTY_NEWLINE); + if (trunk->force_constant_ssrc || trunk->force_aligned_timing) { + vty_out(vty, " %srtp-patch ssrc%s", + trunk->force_constant_ssrc ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %srtp-patch timestamp%s", + trunk->force_aligned_timing ? "" : "no ", VTY_NEWLINE); + } else + vty_out(vty, " no rtp-patch%s", VTY_NEWLINE); + if (trunk->audio_fmtp_extra) + vty_out(vty, " sdp audio fmtp-extra %s%s", + trunk->audio_fmtp_extra, VTY_NEWLINE); + vty_out(vty, " %sallow-transcoding%s", + trunk->no_audio_transcoding ? "no " : "", VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_sdp_fmtp_extra, + cfg_trunk_sdp_fmtp_extra_cmd, + "sdp audio fmtp-extra .NAME", + "Add extra fmtp for the SDP file\n" "Audio\n" "Fmtp-extra\n" + "Extra Information\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + char *txt = argv_concat(argv, argc, 0); + if (!txt) + return CMD_WARNING; + + osmo_talloc_replace_string(g_cfg, &trunk->audio_fmtp_extra, txt); + talloc_free(txt); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_payload_number, + cfg_trunk_payload_number_cmd, + "sdp audio-payload number <0-255>", + SDP_STR AUDIO_STR "Number\n" "Payload Number\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + unsigned int payload = atoi(argv[0]); + + trunk->audio_payload = payload; + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_trunk_payload_number, cfg_trunk_payload_number_cmd_old, + "sdp audio payload number <0-255>", + SDP_STR AUDIO_STR AUDIO_STR "Number\n" "Payload Number\n") + +DEFUN(cfg_trunk_payload_name, + cfg_trunk_payload_name_cmd, + "sdp audio-payload name NAME", + SDP_STR AUDIO_STR "Payload\n" "Payload Name\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + + osmo_talloc_replace_string(g_cfg, &trunk->audio_name, argv[0]); + return CMD_SUCCESS; +} + +ALIAS_DEPRECATED(cfg_trunk_payload_name, cfg_trunk_payload_name_cmd_old, + "sdp audio payload name NAME", + SDP_STR AUDIO_STR AUDIO_STR "Payload\n" "Payload Name\n") + + +DEFUN(cfg_trunk_loop, + cfg_trunk_loop_cmd, + "loop (0|1)", + "Loop audio for all endpoints on this trunk\n" + "Don't Loop\n" "Loop\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + + if (g_cfg->osmux) { + vty_out(vty, "Cannot use `loop' with `osmux'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + trunk->audio_loop = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_sdp_payload_send_ptime, + cfg_trunk_sdp_payload_send_ptime_cmd, + "sdp audio-payload send-ptime", + SDP_STR AUDIO_STR + "Send SDP ptime (packet duration) attribute\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_ptime = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_sdp_payload_send_ptime, + cfg_trunk_no_sdp_payload_send_ptime_cmd, + "no sdp audio-payload send-ptime", + NO_STR SDP_STR AUDIO_STR + "Send SDP ptime (packet duration) attribute\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_ptime = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_sdp_payload_send_name, + cfg_trunk_sdp_payload_send_name_cmd, + "sdp audio-payload send-name", + SDP_STR AUDIO_STR + "Send SDP rtpmap with the audio name\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_name = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_sdp_payload_send_name, + cfg_trunk_no_sdp_payload_send_name_cmd, + "no sdp audio-payload send-name", + NO_STR SDP_STR AUDIO_STR + "Send SDP rtpmap with the audio name\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->audio_send_name = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_omit_rtcp, + cfg_trunk_omit_rtcp_cmd, + "rtcp-omit", + RTCP_OMIT_STR) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->omit_rtcp = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_omit_rtcp, + cfg_trunk_no_omit_rtcp_cmd, + "no rtcp-omit", + NO_STR RTCP_OMIT_STR) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->omit_rtcp = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_patch_rtp_ssrc, + cfg_trunk_patch_rtp_ssrc_cmd, + "rtp-patch ssrc", + RTP_PATCH_STR + "Force a fixed SSRC\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_constant_ssrc = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_patch_rtp_ssrc, + cfg_trunk_no_patch_rtp_ssrc_cmd, + "no rtp-patch ssrc", + NO_STR RTP_PATCH_STR + "Force a fixed SSRC\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_constant_ssrc = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_patch_rtp_ts, + cfg_trunk_patch_rtp_ts_cmd, + "rtp-patch timestamp", + RTP_PATCH_STR + "Adjust RTP timestamp\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_aligned_timing = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_patch_rtp_ts, + cfg_trunk_no_patch_rtp_ts_cmd, + "no rtp-patch timestamp", + NO_STR RTP_PATCH_STR + "Adjust RTP timestamp\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_patch_rtp, + cfg_trunk_no_patch_rtp_cmd, + "no rtp-patch", + NO_STR RTP_PATCH_STR) +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->force_constant_ssrc = 0; + trunk->force_aligned_timing = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_rtp_keepalive, + cfg_trunk_rtp_keepalive_cmd, + "rtp keep-alive <1-120>", + RTP_STR RTP_KEEPALIVE_STR + "Keep-alive interval in secs\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + mgcp_trunk_set_keepalive(trunk, atoi(argv[0])); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_rtp_keepalive_once, + cfg_trunk_rtp_keepalive_once_cmd, + "rtp keep-alive once", + RTP_STR RTP_KEEPALIVE_STR + "Send dummy packet only once after CRCX/MDCX\n" + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_rtp_keepalive, + cfg_trunk_no_rtp_keepalive_cmd, + "no rtp keep-alive", + NO_STR RTP_STR RTP_KEEPALIVE_STR + ) +{ + struct mgcp_trunk_config *trunk = vty->index; + mgcp_trunk_set_keepalive(trunk, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_allow_transcoding, + cfg_trunk_allow_transcoding_cmd, + "allow-transcoding", + "Allow transcoding\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->no_audio_transcoding = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_trunk_no_allow_transcoding, + cfg_trunk_no_allow_transcoding_cmd, + "no allow-transcoding", + NO_STR "Allow transcoding\n") +{ + struct mgcp_trunk_config *trunk = vty->index; + trunk->no_audio_transcoding = 1; + return CMD_SUCCESS; +} + +DEFUN(loop_endp, + loop_endp_cmd, + "loop-endpoint <0-64> NAME (0|1)", + "Loop a given endpoint\n" "Trunk number\n" + "The name in hex of the endpoint\n" "Disable the loop\n" "Enable the loop\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Loopback number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + + endp = &trunk->endpoints[endp_no]; + int loop = atoi(argv[2]); + + if (loop) + endp->conn_mode = MGCP_CONN_LOOPBACK; + else + endp->conn_mode = endp->orig_mode; + + /* Handle it like a MDCX, switch on SSRC patching if enabled */ + mgcp_rtp_end_config(endp, 1, &endp->bts_end); + mgcp_rtp_end_config(endp, 1, &endp->net_end); + + return CMD_SUCCESS; +} + +DEFUN(tap_call, + tap_call_cmd, + "tap-call <0-64> ENDPOINT (bts-in|bts-out|net-in|net-out) A.B.C.D <0-65534>", + "Forward data on endpoint to a different system\n" "Trunk number\n" + "The endpoint in hex\n" + "Forward the data coming from the bts\n" + "Forward the data coming from the bts leaving to the network\n" + "Forward the data coming from the net\n" + "Forward the data coming from the net leaving to the bts\n" + "destination IP of the data\n" "destination port\n") +{ + struct mgcp_rtp_tap *tap; + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + int port = 0; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + + if (strcmp(argv[2], "bts-in") == 0) { + port = MGCP_TAP_BTS_IN; + } else if (strcmp(argv[2], "bts-out") == 0) { + port = MGCP_TAP_BTS_OUT; + } else if (strcmp(argv[2], "net-in") == 0) { + port = MGCP_TAP_NET_IN; + } else if (strcmp(argv[2], "net-out") == 0) { + port = MGCP_TAP_NET_OUT; + } else { + vty_out(vty, "Unknown mode... tricked vty?%s", VTY_NEWLINE); + return CMD_WARNING; + } + + tap = &endp->taps[port]; + memset(&tap->forward, 0, sizeof(tap->forward)); + inet_aton(argv[3], &tap->forward.sin_addr); + tap->forward.sin_port = htons(atoi(argv[4])); + tap->enabled = 1; + return CMD_SUCCESS; +} + +DEFUN(free_endp, free_endp_cmd, + "free-endpoint <0-64> NUMBER", + "Free the given endpoint\n" "Trunk number\n" + "Endpoint number in hex.\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + int endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + mgcp_release_endp(endp); + return CMD_SUCCESS; +} + +DEFUN(reset_endp, reset_endp_cmd, + "reset-endpoint <0-64> NUMBER", + "Reset the given endpoint\n" "Trunk number\n" + "Endpoint number in hex.\n") +{ + struct mgcp_trunk_config *trunk; + struct mgcp_endpoint *endp; + int endp_no, rc; + + trunk = find_trunk(g_cfg, atoi(argv[0])); + if (!trunk) { + vty_out(vty, "%%Trunk %d not found in the config.%s", + atoi(argv[0]), VTY_NEWLINE); + return CMD_WARNING; + } + + if (!trunk->endpoints) { + vty_out(vty, "%%Trunk %d has no endpoints allocated.%s", + trunk->trunk_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + endp_no = strtoul(argv[1], NULL, 16); + if (endp_no < 1 || endp_no >= trunk->number_endpoints) { + vty_out(vty, "Endpoint number %s/%d is invalid.%s", + argv[1], endp_no, VTY_NEWLINE); + return CMD_WARNING; + } + + endp = &trunk->endpoints[endp_no]; + rc = mgcp_send_reset_ep(endp, ENDPOINT_NUMBER(endp)); + if (rc < 0) { + vty_out(vty, "Error %d sending reset.%s", rc, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(reset_all_endp, reset_all_endp_cmd, + "reset-all-endpoints", + "Reset all endpoints\n") +{ + int rc; + + rc = mgcp_send_reset_all(g_cfg); + if (rc < 0) { + vty_out(vty, "Error %d during endpoint reset.%s", + rc, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +#define OSMUX_STR "RTP multiplexing\n" +DEFUN(cfg_mgcp_osmux, + cfg_mgcp_osmux_cmd, + "osmux (on|off|only)", + OSMUX_STR "Enable OSMUX\n" "Disable OSMUX\n" "Only use OSMUX\n") +{ + if (strcmp(argv[0], "off") == 0) { + g_cfg->osmux = OSMUX_USAGE_OFF; + return CMD_SUCCESS; + } + + if (strcmp(argv[0], "on") == 0) + g_cfg->osmux = OSMUX_USAGE_ON; + else if (strcmp(argv[0], "only") == 0) + g_cfg->osmux = OSMUX_USAGE_ONLY; + + if (g_cfg->trunk.audio_loop) { + vty_out(vty, "Cannot use `loop' with `osmux'.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_ip, + cfg_mgcp_osmux_ip_cmd, + "osmux bind-ip A.B.C.D", + OSMUX_STR IP_STR "IPv4 Address to bind to\n") +{ + osmo_talloc_replace_string(g_cfg, &g_cfg->osmux_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_batch_factor, + cfg_mgcp_osmux_batch_factor_cmd, + "osmux batch-factor <1-8>", + OSMUX_STR "Batching factor\n" "Number of messages in the batch\n") +{ + g_cfg->osmux_batch = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_batch_size, + cfg_mgcp_osmux_batch_size_cmd, + "osmux batch-size <1-65535>", + OSMUX_STR "batch size\n" "Batch size in bytes\n") +{ + g_cfg->osmux_batch_size = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_port, + cfg_mgcp_osmux_port_cmd, + "osmux port <1-65535>", + OSMUX_STR "port\n" "UDP port\n") +{ + g_cfg->osmux_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcp_osmux_dummy, + cfg_mgcp_osmux_dummy_cmd, + "osmux dummy (on|off)", + OSMUX_STR "Dummy padding\n" "Enable dummy padding\n" "Disable dummy padding\n") +{ + if (strcmp(argv[0], "on") == 0) + g_cfg->osmux_dummy = 1; + else if (strcmp(argv[0], "off") == 0) + g_cfg->osmux_dummy = 0; + + return CMD_SUCCESS; +} + +int mgcp_vty_init(void) +{ + install_element_ve(&show_mgcp_cmd); + install_element(ENABLE_NODE, &loop_endp_cmd); + install_element(ENABLE_NODE, &tap_call_cmd); + install_element(ENABLE_NODE, &free_endp_cmd); + install_element(ENABLE_NODE, &reset_endp_cmd); + install_element(ENABLE_NODE, &reset_all_endp_cmd); + + install_element(CONFIG_NODE, &cfg_mgcp_cmd); + install_node(&mgcp_node, config_write_mgcp); + + vty_install_default(MGCP_NODE); + install_element(MGCP_NODE, &cfg_mgcp_local_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bts_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_bind_early_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_base_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_base_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_base_port_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_range_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_bts_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_no_bts_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_range_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_net_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_no_net_bind_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_range_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_transcoder_base_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_dscp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_ip_tos_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_force_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_rtp_force_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_cmd); + install_element(MGCP_NODE, &cfg_mgcp_rtp_keepalive_once_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_rtp_keepalive_cmd); + install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd); + install_element(MGCP_NODE, &cfg_mgcp_agent_addr_cmd_old); + install_element(MGCP_NODE, &cfg_mgcp_transcoder_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_transcoder_cmd); + install_element(MGCP_NODE, &cfg_mgcp_transcoder_remote_base_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_number_cmd_old); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_name_cmd_old); + install_element(MGCP_NODE, &cfg_mgcp_loop_cmd); + install_element(MGCP_NODE, &cfg_mgcp_force_realloc_cmd); + install_element(MGCP_NODE, &cfg_mgcp_number_endp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_omit_rtcp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_omit_rtcp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_patch_rtp_ssrc_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_ssrc_cmd); + install_element(MGCP_NODE, &cfg_mgcp_patch_rtp_ts_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_ts_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_patch_rtp_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_fmtp_extra_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_send_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_sdp_payload_send_ptime_cmd); + install_element(MGCP_NODE, &cfg_mgcp_sdp_payload_send_name_cmd); + install_element(MGCP_NODE, &cfg_mgcp_no_sdp_payload_send_name_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_ip_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_batch_factor_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_batch_size_cmd); + install_element(MGCP_NODE, &cfg_mgcp_osmux_port_cmd); + 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_trunk_cmd); + install_node(&trunk_node, config_write_trunk); + vty_install_default(TRUNK_NODE); + install_element(TRUNK_NODE, &cfg_trunk_rtp_keepalive_cmd); + install_element(TRUNK_NODE, &cfg_trunk_rtp_keepalive_once_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_rtp_keepalive_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd); + install_element(TRUNK_NODE, &cfg_trunk_payload_number_cmd_old); + install_element(TRUNK_NODE, &cfg_trunk_payload_name_cmd_old); + install_element(TRUNK_NODE, &cfg_trunk_loop_cmd); + install_element(TRUNK_NODE, &cfg_trunk_omit_rtcp_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_omit_rtcp_cmd); + install_element(TRUNK_NODE, &cfg_trunk_patch_rtp_ssrc_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_ssrc_cmd); + install_element(TRUNK_NODE, &cfg_trunk_patch_rtp_ts_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_ts_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_patch_rtp_cmd); + install_element(TRUNK_NODE, &cfg_trunk_sdp_fmtp_extra_cmd); + install_element(TRUNK_NODE, &cfg_trunk_sdp_payload_send_ptime_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_sdp_payload_send_ptime_cmd); + install_element(TRUNK_NODE, &cfg_trunk_sdp_payload_send_name_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_sdp_payload_send_name_cmd); + install_element(TRUNK_NODE, &cfg_trunk_allow_transcoding_cmd); + install_element(TRUNK_NODE, &cfg_trunk_no_allow_transcoding_cmd); + + return 0; +} + +static int allocate_trunk(struct mgcp_trunk_config *trunk) +{ + int i; + struct mgcp_config *cfg = trunk->cfg; + + if (mgcp_endpoints_allocate(trunk) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to allocate %d endpoints on trunk %d.\n", + trunk->number_endpoints, trunk->trunk_nr); + return -1; + } + + /* early bind */ + for (i = 1; i < trunk->number_endpoints; ++i) { + struct mgcp_endpoint *endp = &trunk->endpoints[i]; + + if (cfg->bts_ports.mode == PORT_ALLOC_STATIC) { + cfg->last_bts_port += 2; + if (mgcp_bind_bts_rtp_port(endp, cfg->last_bts_port) != 0) { + LOGP(DLMGCP, LOGL_FATAL, + "Failed to bind: %d\n", cfg->last_bts_port); + return -1; + } + endp->bts_end.local_alloc = PORT_ALLOC_STATIC; + } + + if (cfg->net_ports.mode == PORT_ALLOC_STATIC) { + cfg->last_net_port += 2; + if (mgcp_bind_net_rtp_port(endp, cfg->last_net_port) != 0) { + LOGP(DLMGCP, LOGL_FATAL, + "Failed to bind: %d\n", cfg->last_net_port); + return -1; + } + endp->net_end.local_alloc = PORT_ALLOC_STATIC; + } + + if (trunk->trunk_type == MGCP_TRUNK_VIRTUAL && + cfg->transcoder_ip && cfg->transcoder_ports.mode == PORT_ALLOC_STATIC) { + int rtp_port; + + /* network side */ + rtp_port = rtp_calculate_port(ENDPOINT_NUMBER(endp), + cfg->transcoder_ports.base_port); + if (mgcp_bind_trans_net_rtp_port(endp, rtp_port) != 0) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port); + return -1; + } + endp->trans_net.local_alloc = PORT_ALLOC_STATIC; + + /* bts side */ + rtp_port = rtp_calculate_port(endp_back_channel(ENDPOINT_NUMBER(endp)), + cfg->transcoder_ports.base_port); + if (mgcp_bind_trans_bts_rtp_port(endp, rtp_port) != 0) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to bind: %d\n", rtp_port); + return -1; + } + endp->trans_bts.local_alloc = PORT_ALLOC_STATIC; + } + } + + return 0; +} + +int mgcp_parse_config(const char *config_file, struct mgcp_config *cfg, + enum mgcp_role role) +{ + int rc; + struct mgcp_trunk_config *trunk; + + cfg->osmux_port = OSMUX_PORT; + cfg->osmux_batch = 4; + cfg->osmux_batch_size = OSMUX_BATCH_DEFAULT_MAX; + + g_cfg = cfg; + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); + return rc; + } + + + if (!g_cfg->bts_ip) + fprintf(stderr, "No BTS ip address specified. This will allow everyone to connect.\n"); + + if (!g_cfg->source_addr) { + fprintf(stderr, "You need to specify a bind address.\n"); + return -1; + } + + /* initialize the last ports */ + g_cfg->last_bts_port = rtp_calculate_port(0, g_cfg->bts_ports.base_port); + g_cfg->last_net_port = rtp_calculate_port(0, g_cfg->net_ports.base_port); + + if (allocate_trunk(&g_cfg->trunk) != 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to initialize the virtual trunk.\n"); + return -1; + } + + llist_for_each_entry(trunk, &g_cfg->trunks, entry) { + if (allocate_trunk(trunk) != 0) { + LOGP(DLMGCP, LOGL_ERROR, + "Failed to initialize E1 trunk %d.\n", trunk->trunk_nr); + return -1; + } + } + cfg->role = role; + + return 0; +} + diff --git a/src/osmo-mgw/Makefile.am b/src/osmo-mgw/Makefile.am new file mode 100644 index 000000000..3e3511df9 --- /dev/null +++ b/src/osmo-mgw/Makefile.am @@ -0,0 +1,31 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBBCG729_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +bin_PROGRAMS = \ + osmo-bsc_mgcp \ + $(NULL) + +osmo_bsc_mgcp_SOURCES = \ + mgcp_main.c \ + $(NULL) + +osmo_bsc_mgcp_LDADD = \ + $(top_builddir)/src/libosmo-legacy-mgcp/libosmo-legacy-mgcp.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBBCG729_LIBS) \ + $(LIBRARY_GSM) \ + $(NULL) diff --git a/src/osmo-mgw/mgcp_main.c b/src/osmo-mgw/mgcp_main.c new file mode 100644 index 000000000..48241a642 --- /dev/null +++ b/src/osmo-mgw/mgcp_main.c @@ -0,0 +1,351 @@ +/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* The main method to drive it as a standalone process */ + +/* + * (C) 2009-2011 by Holger Hans Peter Freyther + * (C) 2009-2011 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../../bscconfig.h" + +#ifdef BUILD_MGCP_TRANSCODING +#include +#endif + +#define _GNU_SOURCE +#include + +#warning "Make use of the rtp proxy code" + +static struct mgcp_config *cfg; +static struct mgcp_trunk_config *reset_trunk; +static int reset_endpoints = 0; +static int daemonize = 0; + +const char *openbsc_copyright = + "Copyright (C) 2009-2010 Holger Freyther and On-Waves\r\n" + "Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n" + "Dieter Spaar, Andreas Eversberg, Harald Welte\r\n\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static char *config_file = "mgcp.cfg"; + +/* used by msgb and mgcp */ +void *tall_bsc_ctx = NULL; + +static void print_help() +{ + printf("Some useful help...\n"); + printf(" -h --help is printing this text.\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -s --disable-color\n"); + printf(" -D --daemonize Fork the process into a background daemon\n"); + printf(" -V --version Print the version number\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"config-file", 1, 0, 'c'}, + {"daemonize", 0, 0, 'D'}, + {"version", 0, 0, 'V'}, + {"disable-color", 0, 0, 's'}, + {0, 0, 0, 0}, + }; + + c = getopt_long(argc, argv, "hc:VD", long_options, &option_index); + + if (c == -1) + break; + + switch(c) { + case 'h': + print_help(); + exit(0); + break; + case 'c': + config_file = talloc_strdup(tall_bsc_ctx, optarg); + break; + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'V': + print_version(1); + exit(0); + break; + case 'D': + daemonize = 1; + break; + default: + /* ignore */ + break; + }; + } +} + +/* simply remember this */ +static int mgcp_rsip_cb(struct mgcp_trunk_config *tcfg) +{ + reset_endpoints = 1; + reset_trunk = tcfg; + + return 0; +} + +static int read_call_agent(struct osmo_fd *fd, unsigned int what) +{ + struct sockaddr_in addr; + socklen_t slen = sizeof(addr); + struct msgb *msg; + struct msgb *resp; + int i; + + msg = (struct msgb *) fd->data; + + /* read one less so we can use it as a \0 */ + int rc = recvfrom(cfg->gw_fd.bfd.fd, msg->data, msg->data_len - 1, 0, + (struct sockaddr *) &addr, &slen); + if (rc < 0) { + perror("Gateway failed to read"); + return -1; + } else if (slen > sizeof(addr)) { + fprintf(stderr, "Gateway received message from outerspace: %zu %zu\n", + (size_t) slen, sizeof(addr)); + return -1; + } + + /* handle message now */ + msg->l2h = msgb_put(msg, rc); + resp = mgcp_handle_message(cfg, msg); + msgb_reset(msg); + + if (resp) { + sendto(cfg->gw_fd.bfd.fd, resp->l2h, msgb_l2len(resp), 0, (struct sockaddr *) &addr, sizeof(addr)); + msgb_free(resp); + } + + if (reset_endpoints) { + LOGP(DLMGCP, LOGL_NOTICE, + "Asked to reset endpoints: %d/%d\n", + reset_trunk->trunk_nr, reset_trunk->trunk_type); + reset_endpoints = 0; + + /* is checking in_addr.s_addr == INADDR_LOOPBACK making it more secure? */ + for (i = 1; i < reset_trunk->number_endpoints; ++i) + mgcp_release_endp(&reset_trunk->endpoints[i]); + } + + return 0; +} + +int mgcp_vty_is_config_node(struct vty *vty, int node) +{ + switch (node) { + case CONFIG_NODE: + return 0; + + default: + return 1; + } +} + +int mgcp_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case TRUNK_NODE: + vty->node = MGCP_NODE; + vty->index = NULL; + break; + case MGCP_NODE: + default: + if (mgcp_vty_is_config_node(vty, vty->node)) + vty->node = CONFIG_NODE; + else + vty->node = ENABLE_NODE; + + vty->index = NULL; + } + + return vty->node; +} + + +static struct vty_app_info vty_info = { + .name = "OpenBSC MGCP", + .version = PACKAGE_VERSION, + .go_parent_cb = mgcp_vty_go_parent, + .is_config_node = mgcp_vty_is_config_node, +}; + +static const struct log_info_cat log_categories[] = { + /* DLMGCP is provided by the MGCP library */ +}; + +const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + +int main(int argc, char **argv) +{ + struct sockaddr_in addr; + int on = 1, rc; + + tall_bsc_ctx = talloc_named_const(NULL, 1, "mgcp-callagent"); + msgb_talloc_ctx_init(tall_bsc_ctx, 0); + + osmo_init_ignore_signals(); + osmo_init_logging(&log_info); + + cfg = mgcp_config_alloc(); + if (!cfg) + return -1; + +#ifdef BUILD_MGCP_TRANSCODING + cfg->setup_rtp_processing_cb = &mgcp_transcoding_setup; + cfg->rtp_processing_cb = &mgcp_transcoding_process_rtp; + cfg->get_net_downlink_format_cb = &mgcp_transcoding_net_downlink_format; +#endif + + cfg->trunk.force_realloc = 1; + + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + logging_vty_add_cmds(NULL); + osmo_stats_vty_add_cmds(&log_info); + mgcp_vty_init(); + + handle_options(argc, argv); + + rate_ctr_init(tall_bsc_ctx); + osmo_stats_init(tall_bsc_ctx); + + rc = mgcp_parse_config(config_file, cfg, MGCP_BSC); + if (rc < 0) + return rc; + + /* start telnet after reading config for vty_get_bind_addr() */ + rc = telnet_init_dynif(tall_bsc_ctx, NULL, + vty_get_bind_addr(), OSMO_VTY_PORT_BSC_MGCP); + if (rc < 0) + return rc; + + /* set some callbacks */ + cfg->reset_cb = mgcp_rsip_cb; + + /* we need to bind a socket */ + if (rc == 0) { + cfg->gw_fd.bfd.when = BSC_FD_READ; + cfg->gw_fd.bfd.cb = read_call_agent; + cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (cfg->gw_fd.bfd.fd < 0) { + perror("Gateway failed to listen"); + return -1; + } + + setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(cfg->source_port); + inet_aton(cfg->source_addr, &addr.sin_addr); + + if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Gateway failed to bind"); + return -1; + } + + cfg->gw_fd.bfd.data = msgb_alloc(4096, "mgcp-msg"); + if (!cfg->gw_fd.bfd.data) { + fprintf(stderr, "Gateway memory error.\n"); + return -1; + } + + if (cfg->call_agent_addr) { + addr.sin_port = htons(2727); + inet_aton(cfg->call_agent_addr, &addr.sin_addr); + if (connect(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DLMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n", + cfg->call_agent_addr, errno); + close(cfg->gw_fd.bfd.fd); + cfg->gw_fd.bfd.fd = -1; + return -1; + } + } + + if (osmo_fd_register(&cfg->gw_fd.bfd) != 0) { + LOGP(DLMGCP, LOGL_FATAL, "Failed to register the fd\n"); + return -1; + } + + LOGP(DLMGCP, LOGL_NOTICE, "Configured for MGCP.\n"); + } + + /* initialisation */ + srand(time(NULL)); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + /* main loop */ + while (1) { + osmo_select_main(0); + } + + + return 0; +} diff --git a/tests/mgcp/Makefile.am b/tests/mgcp/Makefile.am new file mode 100644 index 000000000..f04312486 --- /dev/null +++ b/tests/mgcp/Makefile.am @@ -0,0 +1,61 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_srcdir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBBCG729_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +EXTRA_DIST = \ + mgcp_test.ok \ + mgcp_transcoding_test.ok \ + $(NULL) + +noinst_PROGRAMS = \ + mgcp_test \ + $(NULL) +if BUILD_MGCP_TRANSCODING +noinst_PROGRAMS += \ + mgcp_transcoding_test \ + $(NULL) +endif + +mgcp_test_SOURCES = \ + mgcp_test.c \ + $(NULL) + +mgcp_test_LDADD = \ + $(top_builddir)/src/libosmo-legacy-mgcp/libosmo-legacy-mgcp.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBRARY_DL) \ + $(LIBOSMONETIF_LIBS) \ + $(LIBRARY_GSM) \ + -lm \ + $(NULL) + +mgcp_transcoding_test_SOURCES = \ + mgcp_transcoding_test.c \ + $(NULL) + +mgcp_transcoding_test_LDADD = \ + $(top_builddir)/src/libosmo-legacy-mgcp/libosmo-legacy-mgcp.la \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBBCG729_LIBS) \ + $(LIBRARY_DL) \ + $(LIBOSMONETIF_LIBS) \ + $(LIBRARY_GSM) \ + -lm \ + $(NULL) diff --git a/tests/mgcp/mgcp_test.c b/tests/mgcp/mgcp_test.c new file mode 100644 index 000000000..bbb0db255 --- /dev/null +++ b/tests/mgcp/mgcp_test.c @@ -0,0 +1,1235 @@ +/* + * (C) 2011-2012,2014 by Holger Hans Peter Freyther + * (C) 2011-2012,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 . + */ +#undef _GNU_SOURCE +#define _GNU_SOURCE + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +char *strline_r(char *str, char **saveptr); + +const char *strline_test_data = + "one CR\r" + "two CR\r" + "\r" + "one CRLF\r\n" + "two CRLF\r\n" + "\r\n" + "one LF\n" + "two LF\n" + "\n" + "mixed (4 lines)\r\r\n\n\r\n"; + +#define EXPECTED_NUMBER_OF_LINES 13 + +static void test_strline(void) +{ + char *save = NULL; + char *line; + char buf[2048]; + int counter = 0; + + osmo_strlcpy(buf, strline_test_data, sizeof(buf)); + + for (line = strline_r(buf, &save); line; + line = strline_r(NULL, &save)) { + printf("line: '%s'\n", line); + counter++; + } + + OSMO_ASSERT(counter == EXPECTED_NUMBER_OF_LINES); +} + +#define AUEP1 "AUEP 158663169 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n" +#define AUEP1_RET "200 158663169 OK\r\n" +#define AUEP2 "AUEP 18983213 ds/e1-2/1@172.16.6.66 MGCP 1.0\r\n" +#define AUEP2_RET "500 18983213 FAIL\r\n" +#define EMPTY "\r\n" +#define EMPTY_RET NULL +#define SHORT "CRCX \r\n" +#define SHORT_RET "510 000000 FAIL\r\n" + +#define MDCX_WRONG_EP "MDCX 18983213 ds/e1-3/1@172.16.6.66 MGCP 1.0\r\n" +#define MDCX_ERR_RET "510 18983213 FAIL\r\n" +#define MDCX_UNALLOCATED "MDCX 18983214 ds/e1-1/2@172.16.6.66 MGCP 1.0\r\n" +#define MDCX_RET "400 18983214 FAIL\r\n" +#define MDCX3 "MDCX 18983215 1@mgw MGCP 1.0\r\n" +#define MDCX3_RET "200 18983215 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 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 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=ptime:20\r\n" +#define MDCX3_FMTP_RET "200 18983215 OK\r\n" \ + "I: 3\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 3 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 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=fmtp:126 0/1/2\r\n" \ + "a=ptime:20\r\n" +#define MDCX4 "MDCX 18983216 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" +#define MDCX4_RET(Ident) "200 " Ident " OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 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 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=ptime:20\r\n" + +#define MDCX4_PT1 "MDCX 18983217 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20-40, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_PT2 "MDCX 18983218 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20-20, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_PT3 "MDCX 18983219 1@mgw MGCP 1.0\r\n" \ + "M: sendrecv\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_SO "MDCX 18983220 1@mgw MGCP 1.0\r\n" \ + "M: sendonly\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20, a:AMR, nt:IN\r\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 23 IN IP4 0.0.0.0\r\n" \ + "c=IN IP4 0.0.0.0\r\n" \ + "t=0 0\r\n" \ + "m=audio 4441 RTP/AVP 99\r\n" \ + "a=rtpmap:99 AMR/8000\r\n" \ + "a=ptime:40\r\n" + +#define MDCX4_RO "MDCX 18983221 1@mgw MGCP 1.0\r\n" \ + "M: recvonly\r" \ + "C: 2\r\n" \ + "I: 1\r\n" \ + "L: p:20, a:AMR, nt:IN\r\n" + +#define SHORT2 "CRCX 1" +#define SHORT2_RET "510 000000 FAIL\r\n" +#define SHORT3 "CRCX 1 1@mgw" +#define SHORT4 "CRCX 1 1@mgw MGCP" +#define SHORT5 "CRCX 1 1@mgw MGCP 1.0" + +#define CRCX "CRCX 2 1@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 97\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_RET "200 2 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 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 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=ptime:20\r\n" + +#define CRCX_RET_NO_RTPMAP "200 2 OK\r\n" \ + "I: 1\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 1 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 0 RTP/AVP 126\r\n" \ + "a=ptime:20\r\n" + +#define CRCX_FMTP_RET "200 2 OK\r\n" \ + "I: 3\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 3 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 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=fmtp:126 0/1/2\r\n" \ + "a=ptime:20\r\n" + +#define CRCX_ZYN "CRCX 2 1@mgw MGCP 1.0\r" \ + "M: recvonly\r" \ + "C: 2\r\r" \ + "v=0\r" \ + "c=IN IP4 123.12.12.123\r" \ + "m=audio 5904 RTP/AVP 97\r" \ + "a=rtpmap:97 GSM-EFR/8000\r" + +#define CRCX_ZYN_RET "200 2 OK\r\n" \ + "I: 2\n" \ + "\n" \ + "v=0\r\n" \ + "o=- 2 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 0 RTP/AVP 126\r\n" \ + "a=rtpmap:126 AMR/8000\r\n" \ + "a=ptime:20\r\n" + +#define DLCX "DLCX 7 1@mgw MGCP 1.0\r\n" \ + "C: 2\r\n" + +#define DLCX_RET "250 7 OK\r\n" \ + "P: PS=0, OS=0, PR=0, OR=0, PL=0, JI=0\r\n" \ + "X-Osmo-CP: EC TIS=0, TOS=0, TIR=0, TOR=0\r\n" + +#define RQNT "RQNT 186908780 1@mgw MGCP 1.0\r\n" \ + "X: B244F267488\r\n" \ + "S: D/9\r\n" + +#define RQNT2 "RQNT 186908781 1@mgw MGCP 1.0\r\n" \ + "X: ADD4F26746F\r\n" \ + "R: D/[0-9#*](N), G/ft, fxr/t38\r\n" + +#define RQNT1_RET "200 186908780 OK\r\n" +#define RQNT2_RET "200 186908781 OK\r\n" + +#define PTYPE_IGNORE 0 /* == default initializer */ +#define PTYPE_NONE 128 +#define PTYPE_NYI PTYPE_NONE + +#define CRCX_MULT_1 "CRCX 2 1@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 18 97\r\n"\ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_2 "CRCX 2 2@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 18 97 101\r\n"\ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=rtpmap:101 FOO/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_3 "CRCX 2 3@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=rtpmap:101 FOO/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_4 "CRCX 2 4@mgw MGCP 1.0\r\n" \ + "M: recvonly\r\n" \ + "C: 2\r\n" \ + "X\r\n" \ + "L: p:20\r\n" \ + "\r\n" \ + "v=0\r\n" \ + "c=IN IP4 123.12.12.123\r\n" \ + "m=audio 5904 RTP/AVP 18\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=rtpmap:97 GSM-EFR/8000\r\n" \ + "a=rtpmap:101 FOO/8000\r\n" \ + "a=ptime:40\r\n" + +#define CRCX_MULT_GSM_EXACT \ + "CRCX 259260421 5@mgw MGCP 1.0\r\n" \ + "C: 1355c6041e\r\n" \ + "I: 3\r\n" \ + "L: p:20, a:GSM, nt:IN\r\n" \ + "M: recvonly\r\n" \ + "\r\n" \ + "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" \ + "a=rtpmap:0 PCMU/8000\r\n" \ + "a=rtpmap:8 PCMA/8000\r\n" \ + "a=rtpmap:3 gsm/8000\r\n" \ + "a=rtpmap:18 G729/8000\r\n" \ + "a=fmtp:18 annexb=no\r\n" \ + "a=rtpmap:4 G723/8000\r\n" \ + "a=rtpmap:96 iLBC/8000\r\n" \ + "a=fmtp:96 mode=20\r\n" \ + "a=rtpmap:97 iLBC/8000\r\n" \ + "a=fmtp:97 mode=30\r\n" \ + "a=rtpmap:101 telephone-event/8000\r\n" \ + "a=fmtp:101 0-15\r\n" \ + "a=recvonly\r\n" +#define MDCX_NAT_DUMMY \ + "MDCX 23 5@mgw MGCP 1.0\r\n" \ + "C: 1355c6041e\r\n" \ + "\r\n" \ + "c=IN IP4 8.8.8.8\r\n" \ + "m=audio 16434 RTP/AVP 255\r\n" + + +struct mgcp_test { + const char *name; + const char *req; + const char *exp_resp; + int exp_net_ptype; + int exp_bts_ptype; + + const char *extra_fmtp; +}; + +static const struct mgcp_test tests[] = { + { "AUEP1", AUEP1, AUEP1_RET }, + { "AUEP2", AUEP2, AUEP2_RET }, + { "MDCX1", MDCX_WRONG_EP, MDCX_ERR_RET }, + { "MDCX2", MDCX_UNALLOCATED, MDCX_RET }, + { "CRCX", CRCX, CRCX_RET, 97, 126 }, + { "MDCX3", MDCX3, MDCX3_RET, PTYPE_NONE, 126 }, + { "MDCX4", MDCX4, MDCX4_RET("18983216"), 99, 126 }, + { "MDCX4_PT1", MDCX4_PT1, MDCX4_RET("18983217"), 99, 126 }, + { "MDCX4_PT2", MDCX4_PT2, MDCX4_RET("18983218"), 99, 126 }, + { "MDCX4_PT3", MDCX4_PT3, MDCX4_RET("18983219"), 99, 126 }, + { "MDCX4_SO", MDCX4_SO, MDCX4_RET("18983220"), 99, 126 }, + { "MDCX4_RO", MDCX4_RO, MDCX4_RET("18983221"), PTYPE_IGNORE, 126 }, + { "DLCX", DLCX, DLCX_RET, -1, -1 }, + { "CRCX_ZYN", CRCX_ZYN, CRCX_ZYN_RET, 97, 126 }, + { "EMPTY", EMPTY, EMPTY_RET }, + { "SHORT1", SHORT, SHORT_RET }, + { "SHORT2", SHORT2, SHORT2_RET }, + { "SHORT3", SHORT3, SHORT2_RET }, + { "SHORT4", SHORT4, SHORT2_RET }, + { "RQNT1", RQNT, RQNT1_RET }, + { "RQNT2", RQNT2, RQNT2_RET }, + { "DLCX", DLCX, DLCX_RET, -1, -1 }, + { "CRCX", CRCX, CRCX_FMTP_RET, 97, 126, .extra_fmtp = "a=fmtp:126 0/1/2" }, + { "MDCX3", MDCX3, MDCX3_FMTP_RET, PTYPE_NONE, 126 , .extra_fmtp = "a=fmtp:126 0/1/2" }, + { "DLCX", DLCX, DLCX_RET, -1, -1 , .extra_fmtp = "a=fmtp:126 0/1/2" }, +}; + +static const struct mgcp_test retransmit[] = { + { "CRCX", CRCX, CRCX_RET }, + { "RQNT1", RQNT, RQNT1_RET }, + { "RQNT2", RQNT2, RQNT2_RET }, + { "MDCX3", MDCX3, MDCX3_RET }, + { "DLCX", DLCX, DLCX_RET }, +}; + +static struct msgb *create_msg(const char *str) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "MGCP msg"); + int len = sprintf((char *)msg->data, "%s", str); + msg->l2h = msgb_put(msg, len); + return msg; +} + +static int last_endpoint = -1; + +static int mgcp_test_policy_cb(struct mgcp_trunk_config *cfg, int endpoint, + int state, const char *transactio_id) +{ + fprintf(stderr, "Policy CB got state %d on endpoint %d\n", + state, endpoint); + last_endpoint = endpoint; + return MGCP_POLICY_CONT; +} + +#define MGCP_DUMMY_LOAD 0x23 +static int dummy_packets = 0; +/* override and forward */ +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + typedef ssize_t (*sendto_t)(int, const void *, size_t, int, + const struct sockaddr *, socklen_t); + static sendto_t real_sendto = NULL; + uint32_t dest_host = htonl(((struct sockaddr_in *)dest_addr)->sin_addr.s_addr); + int dest_port = htons(((struct sockaddr_in *)dest_addr)->sin_port); + + if (!real_sendto) + real_sendto = dlsym(RTLD_NEXT, "sendto"); + + if (len == 1 && ((const char *)buf)[0] == MGCP_DUMMY_LOAD ) { + fprintf(stderr, "Dummy packet to 0x%08x:%d, msg length %zu\n%s\n\n", + dest_host, dest_port, + len, osmo_hexdump(buf, len)); + dummy_packets += 1; + } + + return real_sendto(sockfd, buf, len, flags, dest_addr, addrlen); +} + +static int64_t force_monotonic_time_us = -1; +/* override and forward */ +int clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + typedef int (*clock_gettime_t)(clockid_t clk_id, struct timespec *tp); + static clock_gettime_t real_clock_gettime = NULL; + + if (!real_clock_gettime) + real_clock_gettime = dlsym(RTLD_NEXT, "clock_gettime"); + + if (clk_id == CLOCK_MONOTONIC && force_monotonic_time_us >= 0) { + tp->tv_sec = force_monotonic_time_us / 1000000; + tp->tv_nsec = (force_monotonic_time_us % 1000000) * 1000; + return 0; + } + + return real_clock_gettime(clk_id, tp); +} + +#define CONN_UNMODIFIED (0x1000) + +static void test_values(void) +{ + /* Check that NONE disables all output */ + OSMO_ASSERT((MGCP_CONN_NONE & MGCP_CONN_RECV_SEND) == 0) + + /* Check that LOOPBACK enables all output */ + OSMO_ASSERT((MGCP_CONN_LOOPBACK & MGCP_CONN_RECV_SEND) == + MGCP_CONN_RECV_SEND) +} + + +static void test_messages(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + int i; + + cfg = mgcp_config_alloc(); + + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + cfg->policy_cb = mgcp_test_policy_cb; + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + /* reset endpoints */ + for (i = 0; i < cfg->trunk.number_endpoints; i++) { + endp = &cfg->trunk.endpoints[i]; + endp->net_end.codec.payload_type = PTYPE_NONE; + endp->net_end.packet_duration_ms = -1; + + OSMO_ASSERT(endp->conn_mode == MGCP_CONN_NONE); + endp->conn_mode |= CONN_UNMODIFIED; + } + + for (i = 0; i < ARRAY_SIZE(tests); i++) { + const struct mgcp_test *t = &tests[i]; + struct msgb *inp; + struct msgb *msg; + + printf("Testing %s\n", t->name); + + last_endpoint = -1; + dummy_packets = 0; + + osmo_talloc_replace_string(cfg, &cfg->trunk.audio_fmtp_extra, t->extra_fmtp); + + inp = create_msg(t->req); + msg = mgcp_handle_message(cfg, inp); + msgb_free(inp); + if (!t->exp_resp) { + if (msg) + printf("%s failed '%s'\n", t->name, (char *) msg->data); + } else if (strcmp((char *) msg->data, t->exp_resp) != 0) + printf("%s failed '%s'\n", t->name, (char *) msg->data); + msgb_free(msg); + + if (dummy_packets) + printf("Dummy packets: %d\n", dummy_packets); + + if (last_endpoint != -1) { + endp = &cfg->trunk.endpoints[last_endpoint]; + + if (endp->net_end.packet_duration_ms != -1) + printf("Detected packet duration: %d\n", + endp->net_end.packet_duration_ms); + else + printf("Packet duration not set\n"); + if (endp->local_options.pkt_period_min || + endp->local_options.pkt_period_max) + printf("Requested packetetization period: " + "%d-%d\n", + endp->local_options.pkt_period_min, + endp->local_options.pkt_period_max); + else + printf("Requested packetization period not set\n"); + + if ((endp->conn_mode & CONN_UNMODIFIED) == 0) { + printf("Connection mode: %d:%s%s%s%s\n", + endp->conn_mode, + !endp->conn_mode ? " NONE" : "", + endp->conn_mode & MGCP_CONN_SEND_ONLY ? + " SEND" : "", + endp->conn_mode & MGCP_CONN_RECV_ONLY ? + " RECV" : "", + endp->conn_mode & MGCP_CONN_LOOPBACK & + ~MGCP_CONN_RECV_SEND ? + " LOOP" : ""); + fprintf(stderr, + "BTS output %sabled, NET output %sabled\n", + endp->bts_end.output_enabled ? "en" : "dis", + endp->net_end.output_enabled ? "en" : "dis"); + } else + printf("Connection mode not set\n"); + + OSMO_ASSERT(endp->net_end.output_enabled == + (endp->conn_mode & MGCP_CONN_SEND_ONLY ? 1 : 0)); + OSMO_ASSERT(endp->bts_end.output_enabled == + (endp->conn_mode & MGCP_CONN_RECV_ONLY ? 1 : 0)); + + endp->net_end.packet_duration_ms = -1; + endp->local_options.pkt_period_min = 0; + endp->local_options.pkt_period_max = 0; + endp->conn_mode |= CONN_UNMODIFIED; + } + + + /* Check detected payload type */ + if (t->exp_net_ptype != PTYPE_IGNORE || + t->exp_bts_ptype != PTYPE_IGNORE) { + OSMO_ASSERT(last_endpoint != -1); + endp = &cfg->trunk.endpoints[last_endpoint]; + + fprintf(stderr, "endpoint %d: " + "payload type BTS %d (exp %d), NET %d (exp %d)\n", + last_endpoint, + endp->bts_end.codec.payload_type, t->exp_bts_ptype, + endp->net_end.codec.payload_type, t->exp_net_ptype); + + if (t->exp_bts_ptype != PTYPE_IGNORE) + OSMO_ASSERT(endp->bts_end.codec.payload_type == + t->exp_bts_ptype); + if (t->exp_net_ptype != PTYPE_IGNORE) + OSMO_ASSERT(endp->net_end.codec.payload_type == + t->exp_net_ptype); + + /* Reset them again for next test */ + endp->net_end.codec.payload_type = PTYPE_NONE; + } + } + + talloc_free(cfg); +} + +static void test_retransmission(void) +{ + struct mgcp_config *cfg; + int i; + + cfg = mgcp_config_alloc(); + + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + /* reset endpoints */ + for (i = 0; i < cfg->trunk.number_endpoints; i++) { + struct mgcp_endpoint *endp; + endp = &cfg->trunk.endpoints[i]; + endp->bts_end.packet_duration_ms = 20; + } + + for (i = 0; i < ARRAY_SIZE(retransmit); i++) { + const struct mgcp_test *t = &retransmit[i]; + struct msgb *inp; + struct msgb *msg; + + printf("Testing %s\n", t->name); + + inp = create_msg(t->req); + msg = mgcp_handle_message(cfg, inp); + msgb_free(inp); + if (strcmp((char *) msg->data, t->exp_resp) != 0) + printf("%s failed '%s'\n", t->name, (char *) msg->data); + msgb_free(msg); + + /* Retransmit... */ + printf("Re-transmitting %s\n", t->name); + inp = create_msg(t->req); + msg = mgcp_handle_message(cfg, inp); + msgb_free(inp); + if (strcmp((char *) msg->data, t->exp_resp) != 0) + printf("%s failed '%s'\n", t->name, (char *) msg->data); + msgb_free(msg); + } + + talloc_free(cfg); +} + +static int rqnt_cb(struct mgcp_endpoint *endp, char _tone) +{ + ptrdiff_t tone = _tone; + endp->cfg->data = (void *) tone; + return 0; +} + +static void test_rqnt_cb(void) +{ + struct mgcp_config *cfg; + struct msgb *inp, *msg; + + cfg = mgcp_config_alloc(); + cfg->rqnt_cb = rqnt_cb; + + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + inp = create_msg(CRCX); + msgb_free(mgcp_handle_message(cfg, inp)); + msgb_free(inp); + + /* send the RQNT and check for the CB */ + inp = create_msg(RQNT); + msg = mgcp_handle_message(cfg, inp); + if (strncmp((const char *) msg->l2h, "200", 3) != 0) { + printf("FAILED: message is not 200. '%s'\n", msg->l2h); + abort(); + } + + if (cfg->data != (void *) '9') { + printf("FAILED: callback not called: %p\n", cfg->data); + abort(); + } + + msgb_free(msg); + msgb_free(inp); + + inp = create_msg(DLCX); + msgb_free(mgcp_handle_message(cfg, inp)); + msgb_free(inp); + talloc_free(cfg); +} + +struct pl_test { + int cycles; + uint16_t base_seq; + uint16_t max_seq; + uint32_t packets; + + uint32_t expected; + int loss; +}; + +static const struct pl_test pl_test_dat[] = { + /* basic.. just one package */ + { .cycles = 0, .base_seq = 0, .max_seq = 0, .packets = 1, .expected = 1, .loss = 0}, + /* some packages and a bit of loss */ + { .cycles = 0, .base_seq = 0, .max_seq = 100, .packets = 100, .expected = 101, .loss = 1}, + /* wrap around */ + { .cycles = 1<<16, .base_seq = 0xffff, .max_seq = 2, .packets = 4, .expected = 4, .loss = 0}, + /* min loss */ + { .cycles = 0, .base_seq = 0, .max_seq = 0, .packets = UINT_MAX, .expected = 1, .loss = INT_MIN }, + /* max loss, with wrap around on expected max */ + { .cycles = INT_MAX, .base_seq = 0, .max_seq = UINT16_MAX, .packets = 0, .expected = ((uint32_t)(INT_MAX) + UINT16_MAX + 1), .loss = INT_MAX }, +}; + +static void test_packet_loss_calc(void) +{ + int i; + printf("Testing packet loss calculation.\n"); + + 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; + + rtp.packets = pl_test_dat[i].packets; + mgcp_state_calc_loss(&state, &rtp, &expected, &loss); + + if (loss != pl_test_dat[i].loss || expected != pl_test_dat[i].expected) { + printf("FAIL: Wrong exp/loss at idx(%d) Loss(%d vs. %d) Exp(%u vs. %u)\n", + i, loss, pl_test_dat[i].loss, + expected, pl_test_dat[i].expected); + } + } +} + +static void test_mgcp_stats(void) +{ + printf("Testing stat parsing\n"); + + uint32_t bps, bos, pr, _or, jitter; + struct msgb *msg; + int loss; + int rc; + + msg = create_msg(DLCX_RET); + rc = mgcp_parse_stats(msg, &bps, &bos, &pr, &_or, &loss, &jitter); + printf("Parsing result: %d\n", rc); + if (bps != 0 || bos != 0 || pr != 0 || _or != 0 || loss != 0 || jitter != 0) + printf("FAIL: Parsing failed1.\n"); + msgb_free(msg); + + msg = create_msg("250 7 OK\r\nP: PS=10, OS=20, PR=30, OR=40, PL=-3, JI=40\r\n"); + rc = mgcp_parse_stats(msg, &bps, &bos, &pr, &_or, &loss, &jitter); + printf("Parsing result: %d\n", rc); + if (bps != 10 || bos != 20 || pr != 30 || _or != 40 || loss != -3 || jitter != 40) + printf("FAIL: Parsing failed2.\n"); + msgb_free(msg); +} + +struct rtp_packet_info { + float txtime; + int len; + char *data; +}; + +struct rtp_packet_info test_rtp_packets1[] = { + /* RTP: SeqNo=0, TS=0 */ + {0.000000, 20, "\x80\x62\x00\x00\x00\x00\x00\x00\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 20, "\x80\x62\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=2, TS=320 */ + {0.040000, 20, "\x80\x62\x00\x02\x00\x00\x01\x40\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Repeat RTP timestamp: */ + /* RTP: SeqNo=3, TS=320 */ + {0.060000, 20, "\x80\x62\x00\x03\x00\x00\x01\x40\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=4, TS=480 */ + {0.080000, 20, "\x80\x62\x00\x04\x00\x00\x01\xE0\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=5, TS=640 */ + {0.100000, 20, "\x80\x62\x00\x05\x00\x00\x02\x80\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Double skip RTP timestamp (delta = 2*160): */ + /* RTP: SeqNo=6, TS=960 */ + {0.120000, 20, "\x80\x62\x00\x06\x00\x00\x03\xC0\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=7, TS=1120 */ + {0.140000, 20, "\x80\x62\x00\x07\x00\x00\x04\x60\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=8, TS=1280 */ + {0.160000, 20, "\x80\x62\x00\x08\x00\x00\x05\x00\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Non 20ms RTP timestamp (delta = 120): */ + /* RTP: SeqNo=9, TS=1400 */ + {0.180000, 20, "\x80\x62\x00\x09\x00\x00\x05\x78\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=10, TS=1560 */ + {0.200000, 20, "\x80\x62\x00\x0A\x00\x00\x06\x18\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=11, TS=1720 */ + {0.220000, 20, "\x80\x62\x00\x0B\x00\x00\x06\xB8\x11\x22\x33\x44" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* SSRC changed to 0x10203040, RTP timestamp jump */ + /* RTP: SeqNo=12, TS=34688 */ + {0.240000, 20, "\x80\x62\x00\x0C\x00\x00\x87\x80\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=13, TS=34848 */ + {0.260000, 20, "\x80\x62\x00\x0D\x00\x00\x88\x20\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=14, TS=35008 */ + {0.280000, 20, "\x80\x62\x00\x0E\x00\x00\x88\xC0\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Non 20ms RTP timestamp (delta = 120): */ + /* RTP: SeqNo=15, TS=35128 */ + {0.300000, 20, "\x80\x62\x00\x0F\x00\x00\x89\x38\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=16, TS=35288 */ + {0.320000, 20, "\x80\x62\x00\x10\x00\x00\x89\xD8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=17, TS=35448 */ + {0.340000, 20, "\x80\x62\x00\x11\x00\x00\x8A\x78\x10\x20\x30\x40" + "\x01\x23\x45\x67\x8A\xAB\xCD\xEF"}, + /* SeqNo increment by 2, RTP timestamp delta = 320: */ + /* RTP: SeqNo=19, TS=35768 */ + {0.360000, 20, "\x80\x62\x00\x13\x00\x00\x8B\xB8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=20, TS=35928 */ + {0.380000, 20, "\x80\x62\x00\x14\x00\x00\x8C\x58\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=21, TS=36088 */ + {0.380000, 20, "\x80\x62\x00\x15\x00\x00\x8C\xF8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Repeat last packet */ + /* RTP: SeqNo=21, TS=36088 */ + {0.400000, 20, "\x80\x62\x00\x15\x00\x00\x8C\xF8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=22, TS=36248 */ + {0.420000, 20, "\x80\x62\x00\x16\x00\x00\x8D\x98\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=23, TS=36408 */ + {0.440000, 20, "\x80\x62\x00\x17\x00\x00\x8E\x38\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* Don't increment SeqNo but increment timestamp by 160 */ + /* RTP: SeqNo=23, TS=36568 */ + {0.460000, 20, "\x80\x62\x00\x17\x00\x00\x8E\xD8\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=24, TS=36728 */ + {0.480000, 20, "\x80\x62\x00\x18\x00\x00\x8F\x78\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=25, TS=36888 */ + {0.500000, 20, "\x80\x62\x00\x19\x00\x00\x90\x18\x10\x20\x30\x40" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* SSRC changed to 0x50607080, RTP timestamp jump, Delay of 1.5s, + * SeqNo jump */ + /* RTP: SeqNo=1000, TS=160000 */ + {2.000000, 20, "\x80\x62\x03\xE8\x00\x02\x71\x00\x50\x60\x70\x80" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=1001, TS=160160 */ + {2.020000, 20, "\x80\x62\x03\xE9\x00\x02\x71\xA0\x50\x60\x70\x80" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, + /* RTP: SeqNo=1002, TS=160320 */ + {2.040000, 20, "\x80\x62\x03\xEA\x00\x02\x72\x40\x50\x60\x70\x80" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF"}, +}; + +void mgcp_patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *state, + struct mgcp_rtp_end *rtp_end, struct sockaddr_in *addr, + char *data, int len); + +static void test_packet_error_detection(int patch_ssrc, int patch_ts) +{ + int i; + + struct mgcp_trunk_config trunk; + struct mgcp_endpoint endp; + struct mgcp_rtp_state state; + struct mgcp_rtp_end *rtp = &endp.net_end; + struct sockaddr_in addr = {0}; + char buffer[4096]; + 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; + + printf("Testing packet error detection%s%s.\n", + patch_ssrc ? ", patch SSRC" : "", + patch_ts ? ", patch timestamps" : ""); + + memset(&trunk, 0, sizeof(trunk)); + memset(&endp, 0, sizeof(endp)); + memset(&state, 0, sizeof(state)); + + trunk.number_endpoints = 1; + trunk.endpoints = &endp; + trunk.force_constant_ssrc = patch_ssrc; + trunk.force_aligned_timing = patch_ts; + + endp.tcfg = &trunk; + + mgcp_initialize_endp(&endp); + + rtp->codec.payload_type = 98; + + for (i = 0; i < ARRAY_SIZE(test_rtp_packets1); ++i) { + struct rtp_packet_info *info = test_rtp_packets1 + i; + + force_monotonic_time_us = round(1000000.0 * info->txtime); + + OSMO_ASSERT(info->len <= sizeof(buffer)); + OSMO_ASSERT(info->len >= 0); + memmove(buffer, info->data, info->len); + + mgcp_rtp_end_config(&endp, 1, rtp); + + mgcp_patch_and_count(&endp, &state, rtp, &addr, + buffer, info->len); + + if (state.out_stream.ssrc != last_ssrc) { + printf("Output SSRC changed to %08x\n", + state.out_stream.ssrc); + last_ssrc = state.out_stream.ssrc; + } + + printf("In TS: %d, dTS: %d, Seq: %d\n", + state.in_stream.last_timestamp, + 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", + 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); + + printf("Stats: Jitter = %u, Transit = %d\n", + mgcp_state_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_timestamp = state.out_stream.last_timestamp; + last_seqno = state.out_stream.last_seq; + } + + force_monotonic_time_us = -1; +} + +static void test_multilple_codec(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + struct msgb *inp, *resp; + struct in_addr addr; + + printf("Testing multiple payload types\n"); + + cfg = mgcp_config_alloc(); + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + cfg->policy_cb = mgcp_test_policy_cb; + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + /* Allocate endpoint 1@mgw with two codecs */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_1); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 1); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 18); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 97); + + /* Allocate 2@mgw with three codecs, last one ignored */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_2); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 2); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 18); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 97); + + /* Allocate 3@mgw with no codecs, check for PT == -1 */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_3); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 3); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == -1); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1); + + /* Allocate 4@mgw with a single codec */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_4); + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 4); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 18); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1); + + /* Allocate 5@mgw at select GSM.. */ + last_endpoint = -1; + inp = create_msg(CRCX_MULT_GSM_EXACT); + talloc_free(cfg->trunk.audio_name); + cfg->trunk.audio_name = "GSM/8000"; + cfg->trunk.no_audio_transcoding = 1; + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 5); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 3); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1); + + inp = create_msg(MDCX_NAT_DUMMY); + last_endpoint = -1; + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + OSMO_ASSERT(last_endpoint == 5); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 3); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == -1); + OSMO_ASSERT(endp->net_end.rtp_port == htons(16434)); + memset(&addr, 0, sizeof(addr)); + inet_aton("8.8.8.8", &addr); + OSMO_ASSERT(endp->net_end.addr.s_addr == addr.s_addr); + + /* Check what happens without that flag */ + + /* Free the previous endpoint and the data ... */ + mgcp_release_endp(endp); + talloc_free(endp->last_response); + talloc_free(endp->last_trans); + endp->last_response = endp->last_trans = NULL; + + last_endpoint = -1; + inp = create_msg(CRCX_MULT_GSM_EXACT); + cfg->trunk.no_audio_transcoding = 0; + resp = mgcp_handle_message(cfg, inp); + msgb_free(inp); + msgb_free(resp); + + OSMO_ASSERT(last_endpoint == 5); + endp = &cfg->trunk.endpoints[last_endpoint]; + OSMO_ASSERT(endp->net_end.codec.payload_type == 255); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 0); + + talloc_free(cfg); +} + +static void test_no_cycle(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + + printf("Testing no sequence flow on initial packet\n"); + + cfg = mgcp_config_alloc(); + cfg->trunk.number_endpoints = 64; + mgcp_endpoints_allocate(&cfg->trunk); + + endp = &cfg->trunk.endpoints[1]; + OSMO_ASSERT(endp->net_state.stats_initialized == 0); + + mgcp_rtp_annex_count(endp, &endp->net_state, 0, 0, 2342); + OSMO_ASSERT(endp->net_state.stats_initialized == 1); + OSMO_ASSERT(endp->net_state.stats_cycles == 0); + OSMO_ASSERT(endp->net_state.stats_max_seq == 0); + + mgcp_rtp_annex_count(endp, &endp->net_state, 1, 0, 2342); + OSMO_ASSERT(endp->net_state.stats_initialized == 1); + OSMO_ASSERT(endp->net_state.stats_cycles == 0); + OSMO_ASSERT(endp->net_state.stats_max_seq == 1); + + /* now jump.. */ + mgcp_rtp_annex_count(endp, &endp->net_state, UINT16_MAX, 0, 2342); + OSMO_ASSERT(endp->net_state.stats_initialized == 1); + OSMO_ASSERT(endp->net_state.stats_cycles == 0); + OSMO_ASSERT(endp->net_state.stats_max_seq == UINT16_MAX); + + /* and wrap */ + mgcp_rtp_annex_count(endp, &endp->net_state, 0, 0, 2342); + OSMO_ASSERT(endp->net_state.stats_initialized == 1); + OSMO_ASSERT(endp->net_state.stats_cycles == UINT16_MAX + 1); + OSMO_ASSERT(endp->net_state.stats_max_seq == 0); + + talloc_free(cfg); +} + +static void test_no_name(void) +{ + struct mgcp_config *cfg; + struct mgcp_endpoint *endp; + struct msgb *inp, *msg; + int i; + + printf("Testing no rtpmap name\n"); + cfg = mgcp_config_alloc(); + + cfg->trunk.number_endpoints = 64; + cfg->trunk.audio_send_name = 0; + mgcp_endpoints_allocate(&cfg->trunk); + + cfg->policy_cb = mgcp_test_policy_cb; + + mgcp_endpoints_allocate(mgcp_trunk_alloc(cfg, 1)); + + /* reset endpoints */ + for (i = 0; i < cfg->trunk.number_endpoints; i++) { + endp = &cfg->trunk.endpoints[i]; + endp->net_end.codec.payload_type = PTYPE_NONE; + endp->net_end.packet_duration_ms = -1; + + OSMO_ASSERT(endp->conn_mode == MGCP_CONN_NONE); + endp->conn_mode |= CONN_UNMODIFIED; + } + + inp = create_msg(CRCX); + msg = mgcp_handle_message(cfg, inp); + if (strcmp((char *) msg->data, CRCX_RET_NO_RTPMAP) != 0) + printf("FAILED: there should not be a RTPMAP: %s\n", + (char *) msg->data); + msgb_free(inp); + msgb_free(msg); + + mgcp_release_endp(&cfg->trunk.endpoints[1]); + talloc_free(cfg); +} + +static void test_osmux_cid(void) +{ + int id, i; + + OSMO_ASSERT(osmux_used_cid() == 0); + id = osmux_get_cid(); + OSMO_ASSERT(id == 0); + OSMO_ASSERT(osmux_used_cid() == 1); + osmux_put_cid(id); + OSMO_ASSERT(osmux_used_cid() == 0); + + for (i = 0; i < 256; ++i) { + id = osmux_get_cid(); + OSMO_ASSERT(id == i); + OSMO_ASSERT(osmux_used_cid() == i + 1); + } + + id = osmux_get_cid(); + OSMO_ASSERT(id == -1); + + for (i = 0; i < 256; ++i) + osmux_put_cid(i); + OSMO_ASSERT(osmux_used_cid() == 0); +} + +static const struct log_info_cat log_categories[] = { +}; + +const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + +int main(int argc, char **argv) +{ + msgb_talloc_ctx_init(NULL, 0); + osmo_init_logging(&log_info); + + test_strline(); + test_values(); + test_messages(); + test_retransmission(); + test_packet_loss_calc(); + test_rqnt_cb(); + test_mgcp_stats(); + test_packet_error_detection(1, 0); + test_packet_error_detection(0, 0); + test_packet_error_detection(0, 1); + test_packet_error_detection(1, 1); + test_multilple_codec(); + test_no_cycle(); + test_no_name(); + test_osmux_cid(); + + printf("Done\n"); + return EXIT_SUCCESS; +} diff --git a/tests/mgcp/mgcp_test.ok b/tests/mgcp/mgcp_test.ok new file mode 100644 index 000000000..4e27282ee --- /dev/null +++ b/tests/mgcp/mgcp_test.ok @@ -0,0 +1,480 @@ +line: 'one CR' +line: 'two CR' +line: '' +line: 'one CRLF' +line: 'two CRLF' +line: '' +line: 'one LF' +line: 'two LF' +line: '' +line: 'mixed (4 lines)' +line: '' +line: '' +line: '' +Testing AUEP1 +Testing AUEP2 +Testing MDCX1 +Testing MDCX2 +Testing CRCX +Dummy packets: 1 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 1: RECV +Testing MDCX3 +Dummy packets: 1 +Packet duration not set +Requested packetization period not set +Connection mode not set +Testing MDCX4 +Dummy packets: 1 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 3: SEND RECV +Testing MDCX4_PT1 +Dummy packets: 1 +Detected packet duration: 40 +Requested packetetization period: 20-40 +Connection mode: 3: SEND RECV +Testing MDCX4_PT2 +Dummy packets: 1 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 3: SEND RECV +Testing MDCX4_PT3 +Dummy packets: 1 +Detected packet duration: 40 +Requested packetization period not set +Connection mode: 3: SEND RECV +Testing MDCX4_SO +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 2: SEND +Testing MDCX4_RO +Dummy packets: 1 +Packet duration not set +Requested packetetization period: 20-20 +Connection mode: 1: RECV +Testing DLCX +Detected packet duration: 20 +Requested packetization period not set +Connection mode: 0: NONE +Testing CRCX_ZYN +Dummy packets: 1 +Packet duration not set +Requested packetization period not set +Connection mode: 1: RECV +Testing EMPTY +Testing SHORT1 +Testing SHORT2 +Testing SHORT3 +Testing SHORT4 +Testing RQNT1 +Testing RQNT2 +Testing DLCX +Detected packet duration: 20 +Requested packetization period not set +Connection mode: 0: NONE +Testing CRCX +Dummy packets: 1 +Detected packet duration: 40 +Requested packetetization period: 20-20 +Connection mode: 1: RECV +Testing MDCX3 +Dummy packets: 1 +Packet duration not set +Requested packetization period not set +Connection mode not set +Testing DLCX +Detected packet duration: 20 +Requested packetization period not set +Connection mode: 0: NONE +Testing CRCX +Re-transmitting CRCX +Testing RQNT1 +Re-transmitting RQNT1 +Testing RQNT2 +Re-transmitting RQNT2 +Testing MDCX3 +Re-transmitting MDCX3 +Testing DLCX +Re-transmitting DLCX +Testing packet loss calculation. +Testing stat parsing +Parsing result: 0 +Parsing result: 0 +Testing packet error detection, patch SSRC. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 12000, dTS: 12000, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing packet error detection. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +Output SSRC changed to 10203040 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 32968, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 120, dTS: 120, Seq change: 1, TS Err change: in +1, out +1 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +Output SSRC changed to 50607080 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 123112, dTS: 160, Seq change: 975, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing packet error detection, patch timestamps. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +Output SSRC changed to 10203040 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 32968, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +Output SSRC changed to 50607080 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 123112, dTS: 160, Seq change: 975, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing packet error detection, patch SSRC, patch timestamps. +Output SSRC changed to 11223344 +In TS: 0, dTS: 0, Seq: 0 +Out TS change: 0, dTS: 0, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 160, dTS: 160, Seq: 1 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 2 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = 0 +In TS: 320, dTS: 160, Seq: 3 +Out TS change: 0, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 10, Transit = 160 +In TS: 480, dTS: 160, Seq: 4 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 9, Transit = 160 +In TS: 640, dTS: 160, Seq: 5 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 8, Transit = 160 +In TS: 960, dTS: 320, Seq: 6 +Out TS change: 320, dTS: 320, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 18, Transit = 0 +In TS: 1120, dTS: 160, Seq: 7 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 17, Transit = 0 +In TS: 1280, dTS: 160, Seq: 8 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 0 +In TS: 1400, dTS: 120, Seq: 9 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 17, Transit = 40 +In TS: 1560, dTS: 160, Seq: 10 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 16, Transit = 40 +In TS: 1720, dTS: 160, Seq: 11 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 15, Transit = 40 +In TS: 34688, dTS: 0, Seq: 12 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 34848, dTS: 160, Seq: 13 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35008, dTS: 160, Seq: 14 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -32768 +In TS: 35128, dTS: 120, Seq: 15 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +1, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35288, dTS: 160, Seq: 16 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35448, dTS: 160, Seq: 17 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 2, Transit = -32728 +In TS: 35768, dTS: 160, Seq: 19 +Out TS change: 320, dTS: 160, Seq change: 2, TS Err change: in +0, out +0 +Stats: Jitter = 12, Transit = -32888 +In TS: 35928, dTS: 160, Seq: 20 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 11, Transit = -32888 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 20, Transit = -33048 +In TS: 36088, dTS: 160, Seq: 21 +Out TS change: 0, dTS: 160, Seq change: 0, TS Err change: in +0, out +0 +Stats: Jitter = 29, Transit = -32888 +In TS: 36248, dTS: 160, Seq: 22 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 27, Transit = -32888 +In TS: 36408, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 25, Transit = -32888 +In TS: 36568, dTS: 160, Seq: 23 +Out TS change: 160, dTS: 160, Seq change: 0, TS Err change: in +1, out +1 +Stats: Jitter = 24, Transit = -32888 +In TS: 36728, dTS: 160, Seq: 24 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 22, Transit = -32888 +In TS: 36888, dTS: 160, Seq: 25 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 21, Transit = -32888 +In TS: 160000, dTS: 0, Seq: 1000 +Out TS change: 12000, dTS: 12000, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160160, dTS: 160, Seq: 1001 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +In TS: 160320, dTS: 160, Seq: 1002 +Out TS change: 160, dTS: 160, Seq change: 1, TS Err change: in +0, out +0 +Stats: Jitter = 0, Transit = -144000 +Testing multiple payload types +Testing no sequence flow on initial packet +Testing no rtpmap name +Done diff --git a/tests/mgcp/mgcp_transcoding_test.c b/tests/mgcp/mgcp_transcoding_test.c new file mode 100644 index 000000000..61de25f4f --- /dev/null +++ b/tests/mgcp/mgcp_transcoding_test.c @@ -0,0 +1,661 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include "bscconfig.h" +#ifndef BUILD_MGCP_TRANSCODING +#error "Requires MGCP transcoding enabled (see --enable-mgcp-transcoding)" +#endif + +#include + +uint8_t *audio_frame_l16[] = { +}; + +struct rtp_packets { + float t; + int len; + char *data; +}; + +struct rtp_packets audio_packets_l16[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 332, + "\x80\x0B\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + "\x00\x00\x40\x13\x5A\x9E\x40\x13\x00\x00\xBF\xED\xA5\x62\xBF\xED" + }, +}; + +struct rtp_packets audio_packets_gsm[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 45, + "\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B" + "\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A" + "\xDE" + }, +}; + +struct rtp_packets audio_packets_gsm_invalid_size[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 41, + "\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B" + "\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A" + "\xDE" + }, +}; + +struct rtp_packets audio_packets_gsm_invalid_data[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 45, + "\x80\x03\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE" + "\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE" + "\xEE" + }, +}; + +struct rtp_packets audio_packets_gsm_invalid_ptype[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 45, + "\x80\x08\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xD4\x7C\xE3\xE9\x62\x50\x39\xF0\xF8\xB4\x68\xEA\x6C\x0E\x81\x1B" + "\x56\x2A\xD5\xBC\x69\x9C\xD1\xF0\x66\x7A\xEC\x49\x7A\x33\x3D\x0A" + "\xDE" + }, +}; + +struct rtp_packets audio_packets_g729[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 32, + "\x80\x12\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xAF\xC2\x81\x40\x00\xFA\xCE\xA4\x21\x7C\xC5\xC3\x4F\xA5\x98\xF5" + "\xB2\x95\xC4\xAD" + }, +}; + +struct rtp_packets audio_packets_pcma[] = { + /* RTP: SeqNo=1, TS=160 */ + {0.020000, 172, + "\x80\x08\x00\x01\x00\x00\x00\xA0\x11\x22\x33\x44" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + "\xD5\xA5\xA3\xA5\xD5\x25\x23\x25\xD5\xA5\xA3\xA5\xD5\x25\x23\x25" + }, + /* RTP: SeqNo=26527, TS=232640 */ + {0.020000, 92, + "\x80\x08\x67\x9f\x00\x03\x8c\xc0\x04\xaa\x67\x9f\xd5\xd5\xd5\xd5" + "\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5" + "\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5" + "\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5" + "\xd5\xd5\xd5\xd5\xd5\xd5\x55\x55\xd5\xd5\x55\x55\xd5\xd5\x55\x55" + "\xd5\xd5\xd5\x55\x55\xd5\xd5\xd5\x55\x55\xd5\xd5" + }, + /* RTP: SeqNo=26528, TS=232720 */ + {0.020000, 92, + "\x80\x08\x67\xa0\x00\x03\x8d\x10\x04\xaa\x67\x9f\x55\xd5\xd5\x55" + "\xd5\x55\xd5\xd5\xd5\x55\xd5\x55\xd5\xd5\x55\xd5\x55\xd5\x55\xd5" + "\x55\x55\xd5\x55\xd5\xd5\x55\x55\x55\x55\x55\xd5\xd5\x55\xd5\xd5" + "\xd5\x55\xd5\xd5\xd5\x55\x54\x55\xd5\xd5\x55\xd5\xd5\xd5\xd5\x55" + "\x54\x55\xd5\x55\xd5\x55\x55\x55\x55\x55\xd5\xd5\xd5\xd5\xd5\xd4" + "\xd5\x54\x55\xd5\xd4\xd5\x54\xd5\x55\xd5\xd5\xd5" + }, +}; + + + +static int audio_name_to_type(const char *name) +{ + if (!strcasecmp(name, "gsm")) + return 3; +#ifdef HAVE_BCG729 + else if (!strcasecmp(name, "g729")) + return 18; +#endif + else if (!strcasecmp(name, "pcma")) + return 8; + else if (!strcasecmp(name, "l16")) + return 11; + return -1; +} + +int mgcp_get_trans_frame_size(void *state_, int nsamples, int dst); + +static int given_configured_endpoint(int in_samples, int out_samples, + const char *srcfmt, const char *dstfmt, + void **out_ctx, struct mgcp_endpoint **out_endp) +{ + int rc; + struct mgcp_rtp_end *dst_end; + struct mgcp_rtp_end *src_end; + struct mgcp_config *cfg; + struct mgcp_trunk_config *tcfg; + struct mgcp_endpoint *endp; + + cfg = mgcp_config_alloc(); + tcfg = talloc_zero(cfg, struct mgcp_trunk_config); + endp = talloc_zero(tcfg, struct mgcp_endpoint); + + cfg->setup_rtp_processing_cb = mgcp_transcoding_setup; + cfg->rtp_processing_cb = mgcp_transcoding_process_rtp; + cfg->get_net_downlink_format_cb = mgcp_transcoding_net_downlink_format; + + tcfg->endpoints = endp; + tcfg->number_endpoints = 1; + tcfg->cfg = cfg; + endp->tcfg = tcfg; + endp->cfg = cfg; + mgcp_initialize_endp(endp); + + dst_end = &endp->bts_end; + dst_end->codec.payload_type = audio_name_to_type(dstfmt); + + src_end = &endp->net_end; + src_end->codec.payload_type = audio_name_to_type(srcfmt); + + if (out_samples) { + dst_end->codec.frame_duration_den = dst_end->codec.rate; + dst_end->codec.frame_duration_num = out_samples; + dst_end->frames_per_packet = 1; + dst_end->force_output_ptime = 1; + } + + rc = mgcp_transcoding_setup(endp, dst_end, src_end); + if (rc < 0) { + printf("setup failed: %s", strerror(-rc)); + abort(); + } + + *out_ctx = cfg; + *out_endp = endp; + return 0; +} + + +static int transcode_test(const char *srcfmt, const char *dstfmt, + uint8_t *src_pkts, size_t src_pkt_size) +{ + char buf[4096] = {0x80, 0}; + void *ctx; + + struct mgcp_rtp_end *dst_end; + struct mgcp_process_rtp_state *state; + struct mgcp_endpoint *endp; + int in_size; + const int in_samples = 160; + int len, cont; + + printf("== Transcoding test ==\n"); + printf("converting %s -> %s\n", srcfmt, dstfmt); + + given_configured_endpoint(in_samples, 0, srcfmt, dstfmt, &ctx, &endp); + + dst_end = &endp->bts_end; + state = dst_end->rtp_process_data; + OSMO_ASSERT(state != NULL); + + in_size = mgcp_transcoding_get_frame_size(state, in_samples, 0); + OSMO_ASSERT(sizeof(buf) >= in_size + 12); + + memcpy(buf, src_pkts, src_pkt_size); + + len = src_pkt_size; + + cont = mgcp_transcoding_process_rtp(endp, dst_end, + buf, &len, sizeof(buf)); + if (cont < 0) { + printf("Nothing encoded due: %s\n", strerror(-cont)); + talloc_free(ctx); + return -1; + } + + if (len < 24) { + printf("encoded: %s\n", osmo_hexdump((unsigned char *)buf, len)); + } else { + const char *str = osmo_hexdump((unsigned char *)buf, len); + int i = 0; + const int prefix = 4; + const int cutlen = 48; + int nchars = 0; + + printf("encoded:\n"); + do { + nchars = printf("%*s%-.*s", prefix, "", cutlen, str + i); + i += nchars - prefix; + printf("\n"); + } while (nchars - prefix >= cutlen); + } + printf("counted: %d\n", cont); + talloc_free(ctx); + return 0; +} + +static void test_rtp_seq_state(void) +{ + char buf[4096]; + int len; + int cont; + void *ctx; + struct mgcp_endpoint *endp; + struct mgcp_process_rtp_state *state; + struct rtp_hdr *hdr; + uint32_t ts_no; + uint16_t seq_no; + + given_configured_endpoint(160, 0, "pcma", "l16", &ctx, &endp); + state = endp->bts_end.rtp_process_data; + OSMO_ASSERT(!state->is_running); + OSMO_ASSERT(state->next_seq == 0); + OSMO_ASSERT(state->next_time == 0); + + /* initialize packet */ + len = audio_packets_pcma[0].len; + memcpy(buf, audio_packets_pcma[0].data, len); + cont = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, len); + OSMO_ASSERT(cont >= 0); + OSMO_ASSERT(state->is_running); + OSMO_ASSERT(state->next_seq == 2); + OSMO_ASSERT(state->next_time == 240); + + /* verify that the right timestamp was written */ + OSMO_ASSERT(len == audio_packets_pcma[0].len); + hdr = (struct rtp_hdr *) &buf[0]; + + memcpy(&ts_no, &hdr->timestamp, sizeof(ts_no)); + OSMO_ASSERT(htonl(ts_no) == 160); + memcpy(&seq_no, &hdr->sequence, sizeof(seq_no)); + OSMO_ASSERT(htons(seq_no) == 1); + /* Check the right sequence number is written */ + state->next_seq = 1234; + len = audio_packets_pcma[0].len; + memcpy(buf, audio_packets_pcma[0].data, len); + cont = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, len); + OSMO_ASSERT(cont >= 0); + OSMO_ASSERT(len == audio_packets_pcma[0].len); + hdr = (struct rtp_hdr *) &buf[0]; + + memcpy(&seq_no, &hdr->sequence, sizeof(seq_no)); + OSMO_ASSERT(htons(seq_no) == 1234); + + talloc_free(ctx); +} + +static void test_transcode_result(void) +{ + char buf[4096]; + int len, res; + void *ctx; + struct mgcp_endpoint *endp; + struct mgcp_process_rtp_state *state; + + { + /* from GSM to PCMA and same ptime */ + given_configured_endpoint(160, 0, "gsm", "pcma", &ctx, &endp); + state = endp->bts_end.rtp_process_data; + + /* result */ + len = audio_packets_gsm[0].len; + memcpy(buf, audio_packets_gsm[0].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == sizeof(struct rtp_hdr)); + OSMO_ASSERT(state->sample_cnt == 0); + + len = res; + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == -ENOMSG); + + talloc_free(ctx); + } + + { + /* from GSM to PCMA and same ptime */ + given_configured_endpoint(160, 160, "gsm", "pcma", &ctx, &endp); + state = endp->bts_end.rtp_process_data; + + /* result */ + len = audio_packets_gsm[0].len; + memcpy(buf, audio_packets_gsm[0].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == sizeof(struct rtp_hdr)); + OSMO_ASSERT(state->sample_cnt == 0); + + len = res; + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == -EAGAIN); + + talloc_free(ctx); + } + + { + /* from PCMA to GSM and wrong different ptime */ + given_configured_endpoint(80, 160, "pcma", "gsm", &ctx, &endp); + state = endp->bts_end.rtp_process_data; + + /* Add the first sample */ + len = audio_packets_pcma[1].len; + memcpy(buf, audio_packets_pcma[1].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(state->sample_cnt == 80); + OSMO_ASSERT(state->next_time == 232640); + OSMO_ASSERT(res < 0); + + /* Add the second sample and it should be consumable */ + len = audio_packets_pcma[2].len; + memcpy(buf, audio_packets_pcma[2].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(state->sample_cnt == 0); + OSMO_ASSERT(state->next_time == 232640 + 80 + 160); + OSMO_ASSERT(res == sizeof(struct rtp_hdr)); + + talloc_free(ctx); + } + + { + /* from PCMA to GSM with a big time jump */ + struct rtp_hdr *hdr; + uint32_t ts; + + given_configured_endpoint(80, 160, "pcma", "gsm", &ctx, &endp); + state = endp->bts_end.rtp_process_data; + + /* Add the first sample */ + len = audio_packets_pcma[1].len; + memcpy(buf, audio_packets_pcma[1].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(state->sample_cnt == 80); + OSMO_ASSERT(state->next_time == 232640); + OSMO_ASSERT(state->next_seq == 26527); + OSMO_ASSERT(res < 0); + + /* Add a skip to the packet to force a 'resync' */ + len = audio_packets_pcma[2].len; + memcpy(buf, audio_packets_pcma[2].data, len); + hdr = (struct rtp_hdr *) &buf[0]; + /* jump the time and add alignment error */ + ts = ntohl(hdr->timestamp) + 123 * 80 + 2; + hdr->timestamp = htonl(ts); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res < 0); + OSMO_ASSERT(state->sample_cnt == 80); + OSMO_ASSERT(state->next_time == ts); + OSMO_ASSERT(state->next_seq == 26527); + /* TODO: this can create alignment errors */ + + + /* Now attempt to consume 160 samples */ + len = audio_packets_pcma[2].len; + memcpy(buf, audio_packets_pcma[2].data, len); + hdr = (struct rtp_hdr *) &buf[0]; + ts += 80; + hdr->timestamp = htonl(ts); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == 12); + OSMO_ASSERT(state->sample_cnt == 0); + OSMO_ASSERT(state->next_time == ts + 160); + OSMO_ASSERT(state->next_seq == 26528); + + talloc_free(ctx); + } +} + +static void test_transcode_change(void) +{ + char buf[4096] = {0x80, 0}; + void *ctx; + + struct mgcp_endpoint *endp; + struct mgcp_process_rtp_state *state; + struct rtp_hdr *hdr; + + int len, res; + + { + /* from GSM to PCMA and same ptime */ + printf("Testing Initial L16->GSM, PCMA->GSM\n"); + given_configured_endpoint(160, 0, "l16", "gsm", &ctx, &endp); + endp->net_end.alt_codec = endp->net_end.codec; + endp->net_end.alt_codec.payload_type = audio_name_to_type("pcma"); + state = endp->bts_end.rtp_process_data; + + /* initial transcoding work */ + OSMO_ASSERT(state->src_fmt == AF_L16); + OSMO_ASSERT(state->dst_fmt == AF_GSM); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 8); + OSMO_ASSERT(endp->net_end.codec.payload_type == 11); + + /* result */ + len = audio_packets_pcma[0].len; + memcpy(buf, audio_packets_pcma[0].data, len); + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + state = endp->bts_end.rtp_process_data; + OSMO_ASSERT(res == sizeof(struct rtp_hdr)); + OSMO_ASSERT(state->sample_cnt == 0); + OSMO_ASSERT(state->src_fmt == AF_PCMA); + OSMO_ASSERT(state->dst_fmt == AF_GSM); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 11); + OSMO_ASSERT(endp->net_end.codec.payload_type == 8); + + len = res; + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(res == -ENOMSG); + OSMO_ASSERT(state == endp->bts_end.rtp_process_data); + + + /* now check that comfort noise doesn't change anything */ + len = audio_packets_pcma[1].len; + memcpy(buf, audio_packets_pcma[1].data, len); + hdr = (struct rtp_hdr *) buf; + hdr->payload_type = 12; + res = mgcp_transcoding_process_rtp(endp, &endp->bts_end, buf, &len, ARRAY_SIZE(buf)); + OSMO_ASSERT(state == endp->bts_end.rtp_process_data); + OSMO_ASSERT(state->sample_cnt == 80); + OSMO_ASSERT(state->src_fmt == AF_PCMA); + OSMO_ASSERT(state->dst_fmt == AF_GSM); + OSMO_ASSERT(endp->net_end.alt_codec.payload_type == 11); + OSMO_ASSERT(endp->net_end.codec.payload_type == 8); + + talloc_free(ctx); + } +} + +static int test_repacking(int in_samples, int out_samples, int no_transcode) +{ + char buf[4096] = {0x80, 0}; + int cc; + struct mgcp_endpoint *endp; + void *ctx; + + struct mgcp_process_rtp_state *state; + int in_cnt; + int out_size; + int in_size; + uint32_t ts = 0; + uint16_t seq = 0; + const char *srcfmt = "pcma"; + const char *dstfmt = no_transcode ? "pcma" : "l16"; + + printf("== Transcoding test ==\n"); + printf("converting %s -> %s\n", srcfmt, dstfmt); + + given_configured_endpoint(in_samples, out_samples, srcfmt, dstfmt, &ctx, &endp); + + state = endp->bts_end.rtp_process_data; + OSMO_ASSERT(state != NULL); + + in_size = mgcp_transcoding_get_frame_size(state, in_samples, 0); + OSMO_ASSERT(sizeof(buf) >= in_size + 12); + + out_size = mgcp_transcoding_get_frame_size(state, -1, 1); + OSMO_ASSERT(sizeof(buf) >= out_size + 12); + + buf[1] = endp->net_end.codec.payload_type; + *(uint16_t*)(buf+2) = htons(1); + *(uint32_t*)(buf+4) = htonl(0); + *(uint32_t*)(buf+8) = htonl(0xaabbccdd); + + for (in_cnt = 0; in_cnt < 16; in_cnt++) { + int cont; + int len; + + /* fake PCMA data */ + printf("generating %d %s input samples\n", in_samples, srcfmt); + for (cc = 0; cc < in_samples; cc++) + buf[12+cc] = cc; + + *(uint16_t*)(buf+2) = htonl(seq); + *(uint32_t*)(buf+4) = htonl(ts); + + seq += 1; + ts += in_samples; + + cc += 12; /* include RTP header */ + + len = cc; + + do { + cont = mgcp_transcoding_process_rtp(endp, &endp->bts_end, + buf, &len, sizeof(buf)); + if (cont == -EAGAIN) { + fprintf(stderr, "Got EAGAIN\n"); + break; + } + + if (cont < 0) { + printf("processing failed: %s", strerror(-cont)); + abort(); + } + + len -= 12; /* ignore RTP header */ + + printf("got %d %s output frames (%d octets) count=%d\n", + len / out_size, dstfmt, len, cont); + + len = cont; + } while (len > 0); + } + + talloc_free(ctx); + return 0; +} + +static const struct log_info_cat log_categories[] = { +}; + +const struct log_info log_info = { + .cat = log_categories, + .num_cat = ARRAY_SIZE(log_categories), +}; + +int main(int argc, char **argv) +{ + int rc; + osmo_init_logging(&log_info); + + printf("=== Transcoding Good Cases ===\n"); + + transcode_test("l16", "l16", + (uint8_t *)audio_packets_l16[0].data, + audio_packets_l16[0].len); + transcode_test("l16", "gsm", + (uint8_t *)audio_packets_l16[0].data, + audio_packets_l16[0].len); + transcode_test("l16", "pcma", + (uint8_t *)audio_packets_l16[0].data, + audio_packets_l16[0].len); + transcode_test("gsm", "l16", + (uint8_t *)audio_packets_gsm[0].data, + audio_packets_gsm[0].len); + transcode_test("gsm", "gsm", + (uint8_t *)audio_packets_gsm[0].data, + audio_packets_gsm[0].len); + transcode_test("gsm", "pcma", + (uint8_t *)audio_packets_gsm[0].data, + audio_packets_gsm[0].len); + transcode_test("pcma", "l16", + (uint8_t *)audio_packets_pcma[0].data, + audio_packets_pcma[0].len); + transcode_test("pcma", "gsm", + (uint8_t *)audio_packets_pcma[0].data, + audio_packets_pcma[0].len); + transcode_test("pcma", "pcma", + (uint8_t *)audio_packets_pcma[0].data, + audio_packets_pcma[0].len); + + printf("=== Transcoding Bad Cases ===\n"); + + printf("Invalid size:\n"); + rc = transcode_test("gsm", "pcma", + (uint8_t *)audio_packets_gsm_invalid_size[0].data, + audio_packets_gsm_invalid_size[0].len); + OSMO_ASSERT(rc < 0); + + printf("Invalid data:\n"); + rc = transcode_test("gsm", "pcma", + (uint8_t *)audio_packets_gsm_invalid_data[0].data, + audio_packets_gsm_invalid_data[0].len); + OSMO_ASSERT(rc < 0); + + printf("Invalid payload type:\n"); + rc = transcode_test("gsm", "pcma", + (uint8_t *)audio_packets_gsm_invalid_ptype[0].data, + audio_packets_gsm_invalid_ptype[0].len); + OSMO_ASSERT(rc == 0); + + printf("=== Repacking ===\n"); + + test_repacking(160, 160, 0); + test_repacking(160, 160, 1); + test_repacking(160, 80, 0); + test_repacking(160, 80, 1); + test_repacking(160, 320, 0); + test_repacking(160, 320, 1); + test_repacking(160, 240, 0); + test_repacking(160, 240, 1); + test_repacking(160, 100, 0); + test_repacking(160, 100, 1); + test_rtp_seq_state(); + test_transcode_result(); + test_transcode_change(); + + return 0; +} + diff --git a/tests/mgcp/mgcp_transcoding_test.ok b/tests/mgcp/mgcp_transcoding_test.ok new file mode 100644 index 000000000..387cfd275 --- /dev/null +++ b/tests/mgcp/mgcp_transcoding_test.ok @@ -0,0 +1,539 @@ +=== Transcoding Good Cases === +== Transcoding test == +converting l16 -> l16 +encoded: + 80 0b 00 01 00 00 00 a0 11 22 33 44 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed 00 00 40 13 + 5a 9e 40 13 00 00 bf ed a5 62 bf ed +counted: 0 +== Transcoding test == +converting l16 -> gsm +encoded: + 80 0b 00 01 00 00 00 a0 11 22 33 44 d4 7c e3 e9 + 62 50 39 f0 f8 b4 68 ea 6c 0e 81 1b 56 2a d5 bc + 69 9c d1 f0 66 7a ec 49 7a 33 3d 0a de +counted: 12 +== Transcoding test == +converting l16 -> pcma +encoded: + 80 0b 00 01 00 00 00 a0 11 22 33 44 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 +counted: 12 +== Transcoding test == +converting gsm -> l16 +encoded: + 80 03 00 01 00 00 00 a0 11 22 33 44 00 00 54 00 + 59 f0 34 20 c4 c8 b9 f8 e2 18 f1 e8 f2 28 f0 e0 + 46 08 4f 80 2c a0 a9 c8 80 00 c0 58 3f 80 63 c0 + 24 b8 fa b8 f6 88 0b a0 c8 70 a8 b0 c8 c0 3b a8 + 66 a0 2e 38 d1 f8 98 98 aa 18 e8 30 26 a0 37 40 + 37 e8 17 00 ee 50 b7 80 b1 88 de 28 18 40 45 b0 + 4f 48 21 d8 df 78 ae 68 ab 98 d6 b8 18 18 48 90 + 4e 70 27 40 e8 10 b5 b0 ac 80 d4 60 14 50 48 48 + 50 10 2a 00 ec 08 ba 00 af 58 d1 c0 10 60 45 c8 + 54 10 30 78 f1 a8 bb 18 ad 48 ce 30 0a e8 3f 30 + 4f 10 32 18 f6 18 bf 20 ac 30 cd 80 0b d0 43 d8 + 55 e0 34 a0 f5 78 bc 98 ad 98 cd c8 0a 80 40 58 + 51 c0 35 40 f9 60 c1 68 ac c8 cb 38 08 00 40 98 + 51 e0 34 d8 fa 28 c2 f0 ae 40 c7 70 02 d0 3c a8 + 54 78 38 a0 fc 68 c2 08 ad 50 c7 78 01 60 39 c0 + 51 38 3a e8 00 e8 c6 38 ab d8 c4 00 fe 08 39 18 + 50 30 39 50 01 d8 ca 70 b1 80 c4 c8 fc 58 36 40 + 51 d8 3b 08 02 80 c8 58 b0 60 c5 a8 fb d0 33 e8 + 4e 80 3c e0 06 10 cb 90 ae 48 c2 60 f9 58 34 08 + 4d a0 3a a8 06 48 cf 80 b4 60 c3 e8 f7 90 30 18 + 4d a0 3b 98 07 90 cf 18 b4 68 c4 88 +counted: 12 +== Transcoding test == +converting gsm -> gsm +encoded: + 80 03 00 01 00 00 00 a0 11 22 33 44 d4 7c e3 e9 + 62 50 39 f0 f8 b4 68 ea 6c 0e 81 1b 56 2a d5 bc + 69 9c d1 f0 66 7a ec 49 7a 33 3d 0a de +counted: 0 +== Transcoding test == +converting gsm -> pcma +encoded: + 80 03 00 01 00 00 00 a0 11 22 33 44 d5 a0 a3 bf + 38 24 08 19 1e 1b a4 a6 b3 20 2a 3a ba ad b7 60 + 17 92 3e 20 3e b8 ac b2 32 2c 20 02 b6 be be 82 + 04 27 26 35 8d a4 a6 b5 35 21 20 31 8d a7 a6 b6 + 02 27 21 30 81 a7 a1 b0 06 24 21 32 85 a4 a0 bd + 19 24 21 3d 90 ba a6 bc 16 25 21 3c 92 a5 a0 bf + 10 25 21 3c 90 a5 a1 bf 6f 3a 21 3f 95 a5 a1 bf + 62 3b 21 39 f3 bb a0 b9 79 3b 21 39 c3 b9 a1 b8 + db 39 20 3b 4a b9 a1 b9 c8 3f 26 38 78 be a1 b8 + f1 3e 26 38 65 bc a6 bb ed 3f 21 3b 6f bf a6 b8 + ec 3d 27 3b 15 bd a6 b8 eb 3d 27 38 +counted: 12 +== Transcoding test == +converting pcma -> l16 +encoded: + 80 08 00 01 00 00 00 a0 11 22 33 44 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 00 08 42 00 + 5a 00 42 00 00 08 be 00 a6 00 be 00 +counted: 12 +== Transcoding test == +converting pcma -> gsm +encoded: + 80 08 00 01 00 00 00 a0 11 22 33 44 d4 b9 f4 5d + d9 50 5a e1 a0 cd 76 ea 52 0e 87 53 ad d4 ea a2 + 0a 63 ca e9 60 79 e2 2a 25 d2 c0 f3 39 +counted: 12 +== Transcoding test == +converting pcma -> pcma +encoded: + 80 08 00 01 00 00 00 a0 11 22 33 44 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 d5 a5 a3 a5 + d5 25 23 25 d5 a5 a3 a5 d5 25 23 25 +counted: 0 +=== Transcoding Bad Cases === +Invalid size: +== Transcoding test == +converting gsm -> pcma +Nothing encoded due: No message of desired type +Invalid data: +== Transcoding test == +converting gsm -> pcma +Nothing encoded due: No message of desired type +Invalid payload type: +== Transcoding test == +converting gsm -> pcma +encoded: + 80 08 00 01 00 00 00 a0 11 22 33 44 d5 a0 a3 bf + 38 24 08 19 1e 1b a4 a6 b3 20 2a 3a ba ad b7 60 + 17 92 3e 20 3e b8 ac b2 32 2c 20 02 b6 be be 82 + 04 27 26 35 8d a4 a6 b5 35 21 20 31 8d a7 a6 b6 + 02 27 21 30 81 a7 a1 b0 06 24 21 32 85 a4 a0 bd + 19 24 21 3d 90 ba a6 bc 16 25 21 3c 92 a5 a0 bf + 10 25 21 3c 90 a5 a1 bf 6f 3a 21 3f 95 a5 a1 bf + 62 3b 21 39 f3 bb a0 b9 79 3b 21 39 c3 b9 a1 b8 + db 39 20 3b 4a b9 a1 b9 c8 3f 26 38 78 be a1 b8 + f1 3e 26 38 65 bc a6 bb ed 3f 21 3b 6f bf a6 b8 + ec 3d 27 3b 15 bd a6 b8 eb 3d 27 38 +counted: 12 +=== Repacking === +== Transcoding test == +converting pcma -> l16 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +generating 160 pcma input samples +got 2 l16 output frames (320 octets) count=12 +== Transcoding test == +converting pcma -> pcma +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +generating 160 pcma input samples +got 2 pcma output frames (160 octets) count=12 +== Transcoding test == +converting pcma -> l16 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +== Transcoding test == +converting pcma -> pcma +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +== Transcoding test == +converting pcma -> l16 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 l16 output frames (640 octets) count=12 +== Transcoding test == +converting pcma -> pcma +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 4 pcma output frames (320 octets) count=12 +== Transcoding test == +converting pcma -> l16 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 l16 output frames (480 octets) count=12 +== Transcoding test == +converting pcma -> pcma +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +generating 160 pcma input samples +generating 160 pcma input samples +got 3 pcma output frames (240 octets) count=12 +== Transcoding test == +converting pcma -> l16 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +generating 160 pcma input samples +got 1 l16 output frames (160 octets) count=12 +got 1 l16 output frames (160 octets) count=12 +== Transcoding test == +converting pcma -> pcma +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +generating 160 pcma input samples +got 1 pcma output frames (80 octets) count=12 +got 1 pcma output frames (80 octets) count=12 +Testing Initial L16->GSM, PCMA->GSM