Merge tag 'upstream/1.4.0'
Upstream version 1.4.0
Ruben Undheim
5 years ago
37 | 37 | stamp-h1 |
38 | 38 | libtool |
39 | 39 | ltmain.sh |
40 | m4/*.m4 | |
40 | 41 | |
41 | 42 | # git-version-gen magic |
42 | 43 | .tarball-version |
38 | 38 | AC_SUBST(LIBRARY_DL) |
39 | 39 | |
40 | 40 | |
41 | PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.11.0) | |
42 | PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.11.0) | |
43 | PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.11.0) | |
44 | PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.2.0) | |
41 | PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.12.0) | |
42 | PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.12.0) | |
43 | PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.12.0) | |
44 | PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.3.0) | |
45 | 45 | |
46 | 46 | AC_ARG_ENABLE(sanitize, |
47 | 47 | [AS_HELP_STRING( |
91 | 91 | if test -n "$v" |
92 | 92 | then |
93 | 93 | : # use $v |
94 | elif test -d ./.git \ | |
95 | && v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ | |
94 | elif | |
95 | v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ | |
96 | 96 | || git describe --abbrev=4 HEAD 2>/dev/null` \ |
97 | 97 | && case $v in |
98 | 98 | [0-9]*) ;; |
242 | 242 | * message. |
243 | 243 | */ |
244 | 244 | uint16_t osmux_dummy; |
245 | ||
246 | /* Use a jitterbuffer on the bts-side receiver */ | |
247 | bool bts_use_jibuf; | |
248 | /* Minimum and maximum buffer size for the jitter buffer, in ms */ | |
249 | uint32_t bts_jitter_delay_min; | |
250 | uint32_t bts_jitter_delay_max; | |
245 | 251 | }; |
246 | 252 | |
247 | 253 | /* config management */ |
24 | 24 | #include <string.h> |
25 | 25 | |
26 | 26 | #include <osmocom/core/select.h> |
27 | #include <osmocom/netif/jibuf.h> | |
27 | 28 | |
28 | 29 | #define CI_UNUSED 0 |
29 | 30 | |
197 | 198 | uint32_t octets; |
198 | 199 | } stats; |
199 | 200 | } osmux; |
201 | ||
202 | /* Jitter buffer */ | |
203 | struct osmo_jibuf* bts_jb; | |
204 | /* Use a jitterbuffer on the bts-side receiver */ | |
205 | bool bts_use_jibuf; | |
206 | /* Minimum and maximum buffer size for the jitter buffer, in ms */ | |
207 | uint32_t bts_jitter_delay_min; | |
208 | uint32_t bts_jitter_delay_max; | |
200 | 209 | }; |
201 | 210 | |
202 | 211 | #define for_each_line(line, save) \ |
334 | 343 | } |
335 | 344 | |
336 | 345 | int mgcp_msg_terminate_nul(struct msgb *msg); |
346 | ||
347 | /** | |
348 | * Internal jitter buffer related | |
349 | */ | |
350 | void mgcp_dejitter_udp_send(struct msgb *msg, void *data); |
73 | 73 | typedef int (*mgcp_processing)(struct mgcp_endpoint *endp, |
74 | 74 | struct mgcp_rtp_end *dst_end, |
75 | 75 | char *data, int *len, int buf_size); |
76 | ||
77 | struct mgcp_conn_rtp; | |
78 | ||
76 | 79 | typedef int (*mgcp_processing_setup)(struct mgcp_endpoint *endp, |
77 | struct mgcp_rtp_end *dst_end, | |
78 | struct mgcp_rtp_end *src_end); | |
79 | ||
80 | struct mgcp_conn_rtp; | |
80 | struct mgcp_conn_rtp *conn_dst, | |
81 | struct mgcp_conn_rtp *conn_src); | |
82 | ||
81 | 83 | typedef void (*mgcp_get_format)(struct mgcp_endpoint *endp, |
82 | 84 | int *payload_type, |
83 | 85 | const char**subtype_name, |
0 | #pragma once | |
1 | ||
2 | void mgcp_codec_summary(struct mgcp_conn_rtp *conn); | |
3 | void mgcp_codec_reset_all(struct mgcp_conn_rtp *conn); | |
4 | int mgcp_codec_add(struct mgcp_conn_rtp *conn, int payload_type, const char *audio_name); | |
5 | int mgcp_codec_decide(struct mgcp_conn_rtp *conn); |
81 | 81 | /* A prefix to denote the virtual trunk (RTP on both ends) */ |
82 | 82 | #define MGCP_ENDPOINT_PREFIX_VIRTUAL_TRUNK "rtpbridge/" |
83 | 83 | |
84 | /* Maximal number of payload types / codecs that can be negotiated via SDP at | |
85 | * at once. */ | |
86 | #define MGCP_MAX_CODECS 10 | |
87 | ||
84 | 88 | #endif |
26 | 26 | #include <osmocom/core/linuxlist.h> |
27 | 27 | #include <inttypes.h> |
28 | 28 | |
29 | /* RTP connection related counters */ | |
30 | enum { | |
31 | IN_STREAM_ERR_TSTMP_CTR, | |
32 | OUT_STREAM_ERR_TSTMP_CTR, | |
33 | RTP_PACKETS_RX_CTR, | |
34 | RTP_OCTETS_RX_CTR, | |
35 | RTP_PACKETS_TX_CTR, | |
36 | RTP_OCTETS_TX_CTR, | |
37 | RTP_DROPPED_PACKETS_CTR | |
38 | }; | |
39 | ||
29 | 40 | struct mgcp_conn *mgcp_conn_alloc(void *ctx, struct mgcp_endpoint *endp, |
30 | 41 | enum mgcp_conn_type type, char *name); |
31 | 42 | struct mgcp_conn *mgcp_conn_get(struct mgcp_endpoint *endp, const char *id); |
41 | 41 | |
42 | 42 | /*! MGCP endpoint properties */ |
43 | 43 | struct mgcp_endpoint_type { |
44 | /*!< maximum number of connections */ | |
44 | /*! maximum number of connections */ | |
45 | 45 | int max_conns; |
46 | 46 | |
47 | /*!< callback that defines how to dispatch incoming RTP data */ | |
47 | /*! callback that defines how to dispatch incoming RTP data */ | |
48 | 48 | mgcp_dispatch_rtp_cb dispatch_rtp_cb; |
49 | 49 | |
50 | /*!< callback that implements endpoint specific cleanup actions */ | |
50 | /*! callback that implements endpoint specific cleanup actions */ | |
51 | 51 | mgcp_cleanup_cp cleanup_cb; |
52 | 52 | }; |
53 | 53 | |
62 | 62 | /*! MGCP endpoint model */ |
63 | 63 | struct mgcp_endpoint { |
64 | 64 | |
65 | /*!< Call identifier string (as supplied by the call agant) */ | |
65 | /*! Call identifier string (as supplied by the call agant) */ | |
66 | 66 | char *callid; |
67 | 67 | |
68 | /*!< Local connection options (see mgcp_intermal.h) */ | |
68 | /*! Local connection options (see mgcp_internal.h) */ | |
69 | 69 | struct mgcp_lco local_options; |
70 | 70 | |
71 | /*!< List with connections active on this endpoint */ | |
71 | /*! List with connections active on this endpoint */ | |
72 | 72 | struct llist_head conns; |
73 | 73 | |
74 | /*!< Backpointer to the MGW configuration */ | |
74 | /*! Backpointer to the MGW configuration */ | |
75 | 75 | struct mgcp_config *cfg; |
76 | 76 | |
77 | /*!< Backpointer to the Trunk specific configuration */ | |
77 | /*! Backpointer to the Trunk specific configuration */ | |
78 | 78 | struct mgcp_trunk_config *tcfg; |
79 | 79 | |
80 | /*!< Endpoint properties (see above) */ | |
80 | /*! Endpoint properties (see above) */ | |
81 | 81 | const struct mgcp_endpoint_type *type; |
82 | 82 | |
83 | /*!< Last MGCP transmission (in case re-transmission is required) */ | |
83 | /*! Last MGCP transmission (in case re-transmission is required) */ | |
84 | 84 | char *last_trans; |
85 | 85 | |
86 | /*!< Last MGCP response (in case re-transmission is required) */ | |
86 | /*! Last MGCP response (in case re-transmission is required) */ | |
87 | 87 | char *last_response; |
88 | 88 | |
89 | /*!< Memorize if this endpoint was choosen by the MGW (wildcarded, true) | |
89 | /*! Memorize if this endpoint was choosen by the MGW (wildcarded, true) | |
90 | 90 | * or if the user has choosen the particular endpoint explicitly. */ |
91 | 91 | bool wildcarded_req; |
92 | 92 | }; |
27 | 27 | #include <osmocom/mgcp/mgcp.h> |
28 | 28 | #include <osmocom/core/linuxlist.h> |
29 | 29 | #include <osmocom/core/counter.h> |
30 | #include <osmocom/core/rate_ctr.h> | |
30 | 31 | |
31 | 32 | #define CI_UNUSED 0 |
32 | 33 | |
44 | 45 | uint32_t ssrc; |
45 | 46 | uint16_t last_seq; |
46 | 47 | uint32_t last_timestamp; |
47 | uint32_t err_ts_counter; | |
48 | struct rate_ctr *err_ts_ctr; | |
48 | 49 | int32_t last_tsdelta; |
49 | 50 | uint32_t last_arrival_time; |
50 | 51 | }; |
97 | 98 | |
98 | 99 | /* 'mgcp_rtp_end': basically a wrapper around the RTP+RTCP ports */ |
99 | 100 | struct mgcp_rtp_end { |
100 | /* statistics */ | |
101 | struct { | |
102 | unsigned int packets_rx; | |
103 | unsigned int octets_rx; | |
104 | unsigned int packets_tx; | |
105 | unsigned int octets_tx; | |
106 | unsigned int dropped_packets; | |
107 | } stats; | |
108 | ||
109 | 101 | /* local IP address of the RTP socket */ |
110 | 102 | struct in_addr addr; |
111 | 103 | |
112 | 104 | /* in network byte order */ |
113 | 105 | int rtp_port, rtcp_port; |
114 | 106 | |
115 | /* audio codec information */ | |
116 | struct mgcp_rtp_codec codec; | |
117 | struct mgcp_rtp_codec alt_codec; /* TODO/XXX: make it generic */ | |
107 | /* currently selected audio codec */ | |
108 | struct mgcp_rtp_codec *codec; | |
109 | ||
110 | /* array with assigned audio codecs to choose from (SDP) */ | |
111 | struct mgcp_rtp_codec codecs[MGCP_MAX_CODECS]; | |
112 | ||
113 | /* number of assigned audio codecs (SDP) */ | |
114 | unsigned int codecs_assigned; | |
118 | 115 | |
119 | 116 | /* per endpoint data */ |
120 | 117 | int frames_per_packet; |
121 | 118 | uint32_t packet_duration_ms; |
119 | int maximum_packet_time; /* -1: not set */ | |
122 | 120 | char *fmtp_extra; |
123 | 121 | /* are we transmitting packets (1) or dropping (0) outbound packets */ |
124 | 122 | int output_enabled; |
129 | 127 | int force_constant_ssrc; /* -1: always, 0: don't, 1: once */ |
130 | 128 | /* should we perform align_rtp_timestamp_offset() (1) or not (0) */ |
131 | 129 | int force_aligned_timing; |
132 | /* FIXME: not used anymore, used to be [external] transcoding related */ | |
133 | void *rtp_process_data; | |
134 | 130 | |
135 | 131 | /* Each end has a separate socket for RTP and RTCP */ |
136 | 132 | struct osmo_fd rtp; |
201 | 197 | uint32_t octets; |
202 | 198 | } stats; |
203 | 199 | } osmux; |
200 | ||
201 | struct rate_ctr_group *rate_ctr_group; | |
204 | 202 | }; |
205 | 203 | |
206 | 204 | /*! Connection type, specifies which member of the union "u" in mgcp_conn |
211 | 209 | |
212 | 210 | /*! MGCP connection (untyped) */ |
213 | 211 | struct mgcp_conn { |
214 | /*!< list head */ | |
212 | /*! list head */ | |
215 | 213 | struct llist_head entry; |
216 | 214 | |
217 | /*!< Backpointer to the endpoint where the conn belongs to */ | |
215 | /*! Backpointer to the endpoint where the conn belongs to */ | |
218 | 216 | struct mgcp_endpoint *endp; |
219 | 217 | |
220 | /*!< type of the connection (union) */ | |
218 | /*! type of the connection (union) */ | |
221 | 219 | enum mgcp_conn_type type; |
222 | 220 | |
223 | /*!< mode of the connection */ | |
221 | /*! mode of the connection */ | |
224 | 222 | enum mgcp_connection_mode mode; |
225 | 223 | |
226 | /*!< copy of the mode to restore the original setting (VTY) */ | |
224 | /*! copy of the mode to restore the original setting (VTY) */ | |
227 | 225 | enum mgcp_connection_mode mode_orig; |
228 | 226 | |
229 | /*!< connection id to identify the connection */ | |
227 | /*! connection id to identify the connection */ | |
230 | 228 | char id[MGCP_CONN_ID_LENGTH]; |
231 | 229 | |
232 | /*!< human readable name (vty, logging) */ | |
230 | /*! human readable name (vty, logging) */ | |
233 | 231 | char name[256]; |
234 | 232 | |
235 | /*!< union with connection description */ | |
233 | /*! union with connection description */ | |
236 | 234 | union { |
237 | 235 | struct mgcp_conn_rtp rtp; |
238 | 236 | } u; |
239 | 237 | |
240 | /*!< pointer to optional private data */ | |
238 | /*! pointer to optional private data */ | |
241 | 239 | void *priv; |
242 | 240 | }; |
243 | 241 | |
279 | 277 | struct mgcp_trunk_config *mgcp_trunk_alloc(struct mgcp_config *cfg, int index); |
280 | 278 | struct mgcp_trunk_config *mgcp_trunk_num(struct mgcp_config *cfg, int index); |
281 | 279 | |
280 | char *get_lco_identifier(const char *options); | |
281 | int check_local_cx_options(void *ctx, const char *options); | |
282 | 282 | void mgcp_rtp_end_config(struct mgcp_endpoint *endp, int expect_ssrc_change, |
283 | 283 | struct mgcp_rtp_end *rtp); |
284 | 284 | uint32_t mgcp_rtp_packet_duration(struct mgcp_endpoint *endp, |
289 | 289 | char *data, int *len, int buf_size); |
290 | 290 | |
291 | 291 | int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp, |
292 | struct mgcp_rtp_end *dst_end, | |
293 | struct mgcp_rtp_end *src_end); | |
292 | struct mgcp_conn_rtp *conn_dst, | |
293 | struct mgcp_conn_rtp *conn_src); | |
294 | 294 | |
295 | 295 | void mgcp_get_net_downlink_format_default(struct mgcp_endpoint *endp, |
296 | 296 | int *payload_type, |
20 | 20 | */ |
21 | 21 | |
22 | 22 | #pragma once |
23 | #include <osmocom/mgcp/mgcp_sdp.h> | |
24 | 23 | |
25 | 24 | int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp, |
26 | 25 | struct mgcp_conn_rtp *conn, |
27 | 26 | struct mgcp_parse_data *p); |
28 | 27 | |
29 | int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec, | |
30 | int payload_type, const char *audio_name); | |
31 | ||
32 | 28 | int mgcp_write_response_sdp(const struct mgcp_endpoint *endp, |
33 | 29 | const struct mgcp_conn_rtp *conn, struct msgb *sdp, |
34 | 30 | const char *addr); |
29 | 29 | void mgcp_format_stats(char *str, size_t str_len, struct mgcp_conn *conn); |
30 | 30 | |
31 | 31 | /* Exposed for test purposes only, do not use actively */ |
32 | void calc_loss(struct mgcp_rtp_state *s, struct mgcp_rtp_end *, | |
33 | uint32_t *expected, int *loss); | |
32 | void calc_loss(struct mgcp_conn_rtp *conn, uint32_t *expected, int *loss); | |
34 | 33 | |
35 | 34 | /* Exposed for test purposes only, do not use actively */ |
36 | 35 | uint32_t calc_jitter(struct mgcp_rtp_state *); |
25 | 25 | |
26 | 26 | typedef unsigned int mgcp_trans_id_t; |
27 | 27 | |
28 | /*! Enumeration of the codec types that mgcp_client is able to handle. */ | |
29 | enum mgcp_codecs { | |
30 | CODEC_PCMU_8000_1 = 0, | |
31 | CODEC_GSM_8000_1 = 3, | |
32 | CODEC_PCMA_8000_1 = 8, | |
33 | CODEC_G729_8000_1 = 18, | |
34 | CODEC_GSMEFR_8000_1 = 110, | |
35 | CODEC_GSMHR_8000_1 = 111, | |
36 | CODEC_AMR_8000_1 = 112, | |
37 | CODEC_AMRWB_16000_1 = 113, | |
38 | }; | |
39 | /* Note: when new codec types are added, the corresponding value strings | |
40 | * in mgcp_client.c (codec_table) must be updated as well. Enumerations | |
41 | * in enum mgcp_codecs must correspond to a valid payload type. However, | |
42 | * this is an internal assumption that is made to avoid lookup tables. | |
43 | * The API-User should not rely on this coincidence! */ | |
44 | ||
45 | /*! Structure to build a payload type map to allow the defiition custom payload | |
46 | * types. */ | |
47 | struct ptmap { | |
48 | /*! codec for which a payload type number should be defined */ | |
49 | enum mgcp_codecs codec; | |
50 | ||
51 | /*! payload type number (96-127) */ | |
52 | unsigned int pt; | |
53 | }; | |
54 | ||
28 | 55 | struct mgcp_response_head { |
29 | 56 | int response_code; |
30 | 57 | mgcp_trans_id_t trans_id; |
38 | 65 | struct mgcp_response_head head; |
39 | 66 | uint16_t audio_port; |
40 | 67 | char audio_ip[INET_ADDRSTRLEN]; |
68 | unsigned int ptime; | |
69 | enum mgcp_codecs codecs[MGCP_MAX_CODECS]; | |
70 | unsigned int codecs_len; | |
71 | struct ptmap ptmap[MGCP_MAX_CODECS]; | |
72 | unsigned int ptmap_len; | |
41 | 73 | }; |
42 | 74 | |
43 | 75 | enum mgcp_verb { |
65 | 97 | uint16_t audio_port; |
66 | 98 | char *audio_ip; |
67 | 99 | enum mgcp_connection_mode conn_mode; |
100 | unsigned int ptime; | |
101 | enum mgcp_codecs codecs[MGCP_MAX_CODECS]; | |
102 | unsigned int codecs_len; | |
103 | struct ptmap ptmap[MGCP_MAX_CODECS]; | |
104 | unsigned int ptmap_len; | |
68 | 105 | }; |
69 | 106 | |
70 | 107 | void mgcp_client_conf_init(struct mgcp_client_conf *conf); |
116 | 153 | { |
117 | 154 | return get_value_string(mgcp_client_connection_mode_strs, mode); |
118 | 155 | } |
156 | ||
157 | enum mgcp_codecs map_str_to_codec(const char *str); | |
158 | unsigned int map_codec_to_pt(struct ptmap *ptmap, unsigned int ptmap_len, | |
159 | enum mgcp_codecs codec); | |
160 | enum mgcp_codecs map_pt_to_codec(struct ptmap *ptmap, unsigned int ptmap_len, | |
161 | unsigned int pt); |
13 | 13 | * identifier is supplied it is checked against the internal state to make |
14 | 14 | * sure it is correct. */ |
15 | 15 | struct mgcp_conn_peer { |
16 | /*!< RTP connection IP-Address (optional, string e.g. "127.0.0.1") */ | |
16 | /*! RTP connection IP-Address (optional, string e.g. "127.0.0.1") */ | |
17 | 17 | char addr[INET_ADDRSTRLEN]; |
18 | 18 | |
19 | /*!< RTP connection IP-Port (optional) */ | |
19 | /*! RTP connection IP-Port (optional) */ | |
20 | 20 | uint16_t port; |
21 | 21 | |
22 | /*!< RTP endpoint */ | |
22 | /*! RTP endpoint */ | |
23 | 23 | char endpoint[MGCP_ENDPOINT_MAXLEN]; |
24 | 24 | |
25 | /*!< CALL ID (unique per connection) */ | |
25 | /*! CALL ID (unique per connection) */ | |
26 | 26 | unsigned int call_id; |
27 | ||
28 | /*! RTP packetization interval (optional) */ | |
29 | unsigned int ptime; | |
30 | ||
31 | /*! RTP codec list (optional) */ | |
32 | enum mgcp_codecs codecs[MGCP_MAX_CODECS]; | |
33 | ||
34 | /*! Number of codecs in RTP codec list (optional) */ | |
35 | unsigned int codecs_len; | |
27 | 36 | }; |
28 | 37 | |
29 | 38 | struct osmo_fsm_inst *mgcp_conn_create(struct mgcp_client *mgcp, struct osmo_fsm_inst *parent_fi, uint32_t parent_term_evt, |
30 | 39 | uint32_t parent_evt, struct mgcp_conn_peer *conn_peer); |
31 | 40 | int mgcp_conn_modify(struct osmo_fsm_inst *fi, uint32_t parent_evt, struct mgcp_conn_peer *conn_peer); |
32 | 41 | void mgcp_conn_delete(struct osmo_fsm_inst *fi); |
42 | ||
43 | const char *mgcp_conn_get_ci(struct osmo_fsm_inst *fi); |
23 | 23 | |
24 | 24 | # This is not at all related to the release version, but a range of supported |
25 | 25 | # API versions. Read TODO_RELEASE in the source tree's root! |
26 | LEGACY_MGCP_LIBVERSION=0:1:0 | |
26 | LEGACY_MGCP_LIBVERSION=1:0:1 | |
27 | 27 | |
28 | 28 | lib_LTLIBRARIES = \ |
29 | 29 | libosmo-legacy-mgcp.la \ |
583 | 583 | return rc; |
584 | 584 | } |
585 | 585 | |
586 | void mgcp_dejitter_udp_send(struct msgb *msg, void *data) | |
587 | { | |
588 | struct mgcp_rtp_end *rtp_end = (struct mgcp_rtp_end *) data; | |
589 | ||
590 | int rc = mgcp_udp_send(rtp_end->rtp.fd, &rtp_end->addr, | |
591 | rtp_end->rtp_port, (char*) msg->data, msg->len); | |
592 | if (rc != msg->len) | |
593 | LOGP(DLMGCP, LOGL_ERROR, | |
594 | "Failed to send data after jitter buffer: %d\n", rc); | |
595 | msgb_free(msg); | |
596 | } | |
597 | ||
598 | static int enqueue_dejitter(struct osmo_jibuf *jb, struct mgcp_rtp_end *rtp_end, char *buf, int len) | |
599 | { | |
600 | struct msgb *msg; | |
601 | msg = msgb_alloc(len, "mgcp-jibuf"); | |
602 | if (!msg) | |
603 | return -1; | |
604 | ||
605 | memcpy(msg->data, buf, len); | |
606 | msgb_put(msg, len); | |
607 | ||
608 | if (osmo_jibuf_enqueue(jb, msg) < 0) { | |
609 | rtp_end->dropped_packets += 1; | |
610 | msgb_free(msg); | |
611 | } | |
612 | ||
613 | return len; | |
614 | } | |
615 | ||
586 | 616 | int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, |
587 | 617 | struct sockaddr_in *addr, char *buf, int rc) |
588 | 618 | { |
590 | 620 | struct mgcp_rtp_end *rtp_end; |
591 | 621 | struct mgcp_rtp_state *rtp_state; |
592 | 622 | int tap_idx; |
623 | struct osmo_jibuf *jb; | |
593 | 624 | |
594 | 625 | LOGP(DLMGCP, LOGL_DEBUG, |
595 | 626 | "endpoint %x dest %s tcfg->audio_loop %d endp->conn_mode %d (== loopback: %d)\n", |
611 | 642 | rtp_end = &endp->net_end; |
612 | 643 | rtp_state = &endp->bts_state; |
613 | 644 | tap_idx = MGCP_TAP_NET_OUT; |
645 | jb = endp->bts_jb; | |
614 | 646 | } else { |
615 | 647 | rtp_end = &endp->bts_end; |
616 | 648 | rtp_state = &endp->net_state; |
617 | 649 | tap_idx = MGCP_TAP_BTS_OUT; |
650 | jb = NULL; | |
618 | 651 | } |
619 | 652 | LOGP(DLMGCP, LOGL_DEBUG, |
620 | 653 | "endpoint %x dest %s net_end %s %d %d bts_end %s %d %d rtp_end %s %d %d\n", |
679 | 712 | rtp_state->patched_first_rtp_payload = true; |
680 | 713 | } |
681 | 714 | |
682 | rc = mgcp_udp_send(rtp_end->rtp.fd, | |
683 | &rtp_end->addr, | |
684 | rtp_end->rtp_port, buf, len); | |
715 | if (jb) | |
716 | rc = enqueue_dejitter(jb, rtp_end, buf, len); | |
717 | else | |
718 | rc = mgcp_udp_send(rtp_end->rtp.fd, | |
719 | &rtp_end->addr, | |
720 | rtp_end->rtp_port, buf, len); | |
685 | 721 | |
686 | 722 | if (rc <= 0) |
687 | 723 | return rc; |
266 | 266 | { |
267 | 267 | struct msgb *msg; |
268 | 268 | struct osmux_hdr *osmuxh; |
269 | struct llist_head list; | |
270 | 269 | struct sockaddr_in addr; |
271 | 270 | struct mgcp_config *cfg = ofd->data; |
272 | 271 | uint32_t rem; |
296 | 295 | endp->osmux.stats.chunks++; |
297 | 296 | rem = msg->len; |
298 | 297 | |
299 | osmux_xfrm_output(osmuxh, &endp->osmux.out, &list); | |
300 | osmux_tx_sched(&list, scheduled_tx_bts_cb, endp); | |
298 | osmux_xfrm_output_sched(&endp->osmux.out, osmuxh); | |
301 | 299 | } |
302 | 300 | out: |
303 | 301 | msgb_free(msg); |
358 | 356 | { |
359 | 357 | struct msgb *msg; |
360 | 358 | struct osmux_hdr *osmuxh; |
361 | struct llist_head list; | |
362 | 359 | struct sockaddr_in addr; |
363 | 360 | struct mgcp_config *cfg = ofd->data; |
364 | 361 | uint32_t rem; |
388 | 385 | endp->osmux.stats.chunks++; |
389 | 386 | rem = msg->len; |
390 | 387 | |
391 | osmux_xfrm_output(osmuxh, &endp->osmux.out, &list); | |
392 | osmux_tx_sched(&list, scheduled_tx_net_cb, endp); | |
388 | osmux_xfrm_output_sched(&endp->osmux.out, osmuxh); | |
393 | 389 | } |
394 | 390 | out: |
395 | 391 | msgb_free(msg); |
469 | 465 | switch (endp->cfg->role) { |
470 | 466 | case MGCP_BSC_NAT: |
471 | 467 | endp->type = MGCP_OSMUX_BSC_NAT; |
468 | osmux_xfrm_output_set_tx_cb(&endp->osmux.out, | |
469 | scheduled_tx_net_cb, endp); | |
472 | 470 | break; |
473 | 471 | case MGCP_BSC: |
474 | 472 | endp->type = MGCP_OSMUX_BSC; |
473 | osmux_xfrm_output_set_tx_cb(&endp->osmux.out, | |
474 | scheduled_tx_bts_cb, endp); | |
475 | 475 | break; |
476 | 476 | } |
477 | 477 | endp->osmux.state = OSMUX_STATE_ENABLED; |
483 | 483 | { |
484 | 484 | LOGP(DLMGCP, LOGL_INFO, "Releasing endpoint %u using Osmux CID %u\n", |
485 | 485 | ENDPOINT_NUMBER(endp), endp->osmux.cid); |
486 | ||
487 | /* We are closing, we don't need pending RTP packets to be transmitted */ | |
488 | osmux_xfrm_output_set_tx_cb(&endp->osmux.out, NULL, NULL); | |
489 | osmux_xfrm_output_flush(&endp->osmux.out); | |
490 | ||
486 | 491 | osmux_xfrm_input_close_circuit(endp->osmux.in, endp->osmux.cid); |
487 | 492 | endp->osmux.state = OSMUX_STATE_DISABLED; |
488 | 493 | endp->osmux.cid = -1; |
862 | 862 | goto error2; |
863 | 863 | } |
864 | 864 | |
865 | /* Apply Jiter buffer settings for this endpoint, they can be overriden by CRCX policy later */ | |
866 | endp->bts_use_jibuf = endp->cfg->bts_use_jibuf; | |
867 | endp->bts_jitter_delay_min = endp->cfg->bts_jitter_delay_min; | |
868 | endp->bts_jitter_delay_max = endp->cfg->bts_jitter_delay_max; | |
869 | ||
865 | 870 | endp->allocated = 1; |
866 | 871 | |
867 | 872 | /* set up RTP media parameters */ |
897 | 902 | case MGCP_POLICY_DEFER: |
898 | 903 | /* stop processing */ |
899 | 904 | create_transcoder(endp); |
905 | /* Set up jitter buffer if required after policy has updated jibuf endp values */ | |
906 | if (endp->bts_use_jibuf) { | |
907 | endp->bts_jb = osmo_jibuf_alloc(tcfg->endpoints); | |
908 | osmo_jibuf_set_min_delay(endp->bts_jb, endp->bts_jitter_delay_min); | |
909 | osmo_jibuf_set_max_delay(endp->bts_jb, endp->bts_jitter_delay_max); | |
910 | osmo_jibuf_set_dequeue_cb(endp->bts_jb, mgcp_dejitter_udp_send, &endp->net_end); | |
911 | } | |
900 | 912 | return NULL; |
901 | 913 | break; |
902 | 914 | case MGCP_POLICY_CONT: |
903 | 915 | /* just continue */ |
904 | 916 | break; |
905 | 917 | } |
918 | } | |
919 | ||
920 | /* Set up jitter buffer if required after policy has updated jibuf endp values */ | |
921 | if (endp->bts_use_jibuf) { | |
922 | endp->bts_jb = osmo_jibuf_alloc(tcfg->endpoints); | |
923 | osmo_jibuf_set_min_delay(endp->bts_jb, endp->bts_jitter_delay_min); | |
924 | osmo_jibuf_set_max_delay(endp->bts_jb, endp->bts_jitter_delay_max); | |
925 | osmo_jibuf_set_dequeue_cb(endp->bts_jb, mgcp_dejitter_udp_send, &endp->net_end); | |
906 | 926 | } |
907 | 927 | |
908 | 928 | LOGP(DLMGCP, LOGL_DEBUG, "Creating endpoint on: 0x%x CI: %u port: %u/%u\n", |
1372 | 1392 | void mgcp_release_endp(struct mgcp_endpoint *endp) |
1373 | 1393 | { |
1374 | 1394 | LOGP(DLMGCP, LOGL_DEBUG, "Releasing endpoint on: 0x%x\n", ENDPOINT_NUMBER(endp)); |
1395 | if (endp->bts_jb) | |
1396 | osmo_jibuf_delete(endp->bts_jb); | |
1397 | endp->bts_jb = NULL; | |
1375 | 1398 | endp->ci = CI_UNUSED; |
1376 | 1399 | endp->allocated = 0; |
1377 | 1400 |
28 | 28 | #include <osmocom/legacy_mgcp/vty.h> |
29 | 29 | |
30 | 30 | #include <string.h> |
31 | #include <inttypes.h> | |
31 | 32 | |
32 | 33 | #define RTCP_OMIT_STR "Drop RTCP packets in both directions\n" |
33 | 34 | #define RTP_PATCH_STR "Modify RTP packet header in both directions\n" |
163 | 164 | vty_out(vty, " osmux dummy %s%s", |
164 | 165 | g_cfg->osmux_dummy ? "on" : "off", VTY_NEWLINE); |
165 | 166 | } |
167 | if (g_cfg->bts_use_jibuf) | |
168 | vty_out(vty, " bts-jitter-buffer%s", VTY_NEWLINE); | |
169 | if (g_cfg->bts_jitter_delay_min) | |
170 | vty_out(vty, " bts-jitter-delay-min %"PRIu32"%s", g_cfg->bts_jitter_delay_min, VTY_NEWLINE); | |
171 | if (g_cfg->bts_jitter_delay_max) | |
172 | vty_out(vty, " bts-jitter-delay-max %"PRIu32"%s", g_cfg->bts_jitter_delay_max, VTY_NEWLINE); | |
173 | ||
166 | 174 | return CMD_SUCCESS; |
167 | 175 | } |
168 | 176 | |
240 | 248 | |
241 | 249 | if (g_cfg->osmux) |
242 | 250 | vty_out(vty, "Osmux used CID: %d%s", osmux_used_cid(), VTY_NEWLINE); |
251 | vty_out(vty, "Jitter Buffer by default on Uplink : %s%s", | |
252 | g_cfg->bts_use_jibuf ? "on" : "off", VTY_NEWLINE); | |
253 | if (g_cfg->bts_use_jibuf) | |
254 | vty_out(vty, "Jitter Buffer delays: min=%"PRIu32" max=%"PRIu32"%s", | |
255 | g_cfg->bts_jitter_delay_min, g_cfg->bts_jitter_delay_max, VTY_NEWLINE); | |
243 | 256 | |
244 | 257 | return CMD_SUCCESS; |
245 | 258 | } |
1340 | 1353 | else if (strcmp(argv[0], "off") == 0) |
1341 | 1354 | g_cfg->osmux_dummy = 0; |
1342 | 1355 | |
1356 | return CMD_SUCCESS; | |
1357 | } | |
1358 | ||
1359 | #define DEJITTER_STR "Uplink Jitter Buffer" | |
1360 | DEFUN(cfg_mgcp_bts_use_jibuf, | |
1361 | cfg_mgcp_bts_use_jibuf_cmd, | |
1362 | "bts-jitter-buffer", | |
1363 | DEJITTER_STR "\n") | |
1364 | { | |
1365 | g_cfg->bts_use_jibuf = true; | |
1366 | return CMD_SUCCESS; | |
1367 | } | |
1368 | ||
1369 | DEFUN(cfg_mgcp_no_bts_use_jibuf, | |
1370 | cfg_mgcp_no_bts_use_jibuf_cmd, | |
1371 | "no bts-jitter-buffer", | |
1372 | NO_STR DEJITTER_STR "\n") | |
1373 | { | |
1374 | g_cfg->bts_use_jibuf = false; | |
1375 | return CMD_SUCCESS; | |
1376 | } | |
1377 | ||
1378 | DEFUN(cfg_mgcp_bts_jitter_delay_min, | |
1379 | cfg_mgcp_bts_jitter_delay_min_cmd, | |
1380 | "bts-jitter-buffer-delay-min <1-65535>", | |
1381 | DEJITTER_STR " Minimum Delay in ms\n" "Minimum Delay in ms\n") | |
1382 | { | |
1383 | g_cfg->bts_jitter_delay_min = atoi(argv[0]); | |
1384 | if (!g_cfg->bts_jitter_delay_min) { | |
1385 | vty_out(vty, "bts-jitter-buffer-delay-min cannot be zero.%s", VTY_NEWLINE); | |
1386 | return CMD_WARNING; | |
1387 | } | |
1388 | if (g_cfg->bts_jitter_delay_min && g_cfg->bts_jitter_delay_max && | |
1389 | g_cfg->bts_jitter_delay_min > g_cfg->bts_jitter_delay_max) { | |
1390 | vty_out(vty, "bts-jitter-buffer-delay-min cannot be bigger than " \ | |
1391 | "bts-jitter-buffer-delay-max.%s", VTY_NEWLINE); | |
1392 | return CMD_WARNING; | |
1393 | } | |
1394 | return CMD_SUCCESS; | |
1395 | } | |
1396 | ||
1397 | DEFUN(cfg_mgcp_bts_jitter_delay_max, | |
1398 | cfg_mgcp_bts_jitter_delay_max_cmd, | |
1399 | "bts-jitter-buffer-delay-max <1-65535>", | |
1400 | DEJITTER_STR " Maximum Delay in ms\n" "Maximum Delay in ms\n") | |
1401 | { | |
1402 | g_cfg->bts_jitter_delay_max = atoi(argv[0]); | |
1403 | if (!g_cfg->bts_jitter_delay_max) { | |
1404 | vty_out(vty, "bts-jitter-buffer-delay-max cannot be zero.%s", VTY_NEWLINE); | |
1405 | return CMD_WARNING; | |
1406 | } | |
1407 | if (g_cfg->bts_jitter_delay_min && g_cfg->bts_jitter_delay_max && | |
1408 | g_cfg->bts_jitter_delay_min > g_cfg->bts_jitter_delay_max) { | |
1409 | vty_out(vty, "bts-jitter-buffer-delay-max cannot be smaller than " \ | |
1410 | "bts-jitter-buffer-delay-min.%s", VTY_NEWLINE); | |
1411 | return CMD_WARNING; | |
1412 | } | |
1343 | 1413 | return CMD_SUCCESS; |
1344 | 1414 | } |
1345 | 1415 | |
1410 | 1480 | install_element(MGCP_NODE, &cfg_mgcp_osmux_dummy_cmd); |
1411 | 1481 | install_element(MGCP_NODE, &cfg_mgcp_allow_transcoding_cmd); |
1412 | 1482 | install_element(MGCP_NODE, &cfg_mgcp_no_allow_transcoding_cmd); |
1483 | install_element(MGCP_NODE, &cfg_mgcp_bts_use_jibuf_cmd); | |
1484 | install_element(MGCP_NODE, &cfg_mgcp_no_bts_use_jibuf_cmd); | |
1485 | install_element(MGCP_NODE, &cfg_mgcp_bts_jitter_delay_min_cmd); | |
1486 | install_element(MGCP_NODE, &cfg_mgcp_bts_jitter_delay_max_cmd); | |
1413 | 1487 | |
1414 | 1488 | |
1415 | 1489 | install_element(MGCP_NODE, &cfg_mgcp_trunk_cmd); |
34 | 34 | mgcp_vty.c \ |
35 | 35 | mgcp_osmux.c \ |
36 | 36 | mgcp_sdp.c \ |
37 | mgcp_codec.c \ | |
37 | 38 | mgcp_msg.c \ |
38 | 39 | mgcp_conn.c \ |
39 | 40 | mgcp_stat.c \ |
0 | /* | |
1 | * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org> | |
2 | * (C) 2009-2014 by On-Waves | |
3 | * All Rights Reserved | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU Affero General Public License as published by | |
7 | * the Free Software Foundation; either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU Affero General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU Affero General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | * | |
18 | */ | |
19 | #include <osmocom/mgcp/mgcp_internal.h> | |
20 | #include <osmocom/mgcp/mgcp_endp.h> | |
21 | #include <errno.h> | |
22 | ||
23 | /* Helper function to dump codec information of a specified codec to a printable | |
24 | * string, used by dump_codec_summary() */ | |
25 | static char *dump_codec(struct mgcp_rtp_codec *codec) | |
26 | { | |
27 | static char str[256]; | |
28 | char *pt_str; | |
29 | ||
30 | if (codec->payload_type > 76) | |
31 | pt_str = "DYNAMIC"; | |
32 | else if (codec->payload_type > 72) | |
33 | pt_str = "RESERVED <!>"; | |
34 | else if (codec->payload_type != PTYPE_UNDEFINED) | |
35 | pt_str = codec->subtype_name; | |
36 | else | |
37 | pt_str = "INVALID <!>"; | |
38 | ||
39 | snprintf(str, sizeof(str), "(pt:%i=%s, audio:%s subt=%s, rate=%u, ch=%i, t=%u/%u)", codec->payload_type, pt_str, | |
40 | codec->audio_name, codec->subtype_name, codec->rate, codec->channels, codec->frame_duration_num, | |
41 | codec->frame_duration_den); | |
42 | return str; | |
43 | } | |
44 | ||
45 | /*! Dump a summary of all negotiated codecs to debug log | |
46 | * \param[in] conn related rtp-connection. */ | |
47 | void mgcp_codec_summary(struct mgcp_conn_rtp *conn) | |
48 | { | |
49 | struct mgcp_rtp_end *rtp; | |
50 | unsigned int i; | |
51 | struct mgcp_rtp_codec *codec; | |
52 | struct mgcp_endpoint *endp; | |
53 | ||
54 | rtp = &conn->end; | |
55 | endp = conn->conn->endp; | |
56 | ||
57 | if (rtp->codecs_assigned == 0) { | |
58 | LOGP(DLMGCP, LOGL_ERROR, "endpoint:0x%x conn:%s no codecs available\n", ENDPOINT_NUMBER(endp), | |
59 | mgcp_conn_dump(conn->conn)); | |
60 | return; | |
61 | } | |
62 | ||
63 | /* Store parsed codec information */ | |
64 | for (i = 0; i < rtp->codecs_assigned; i++) { | |
65 | codec = &rtp->codecs[i]; | |
66 | ||
67 | LOGP(DLMGCP, LOGL_DEBUG, "endpoint:0x%x conn:%s codecs[%u]:%s", ENDPOINT_NUMBER(endp), | |
68 | mgcp_conn_dump(conn->conn), i, dump_codec(codec)); | |
69 | ||
70 | if (codec == rtp->codec) | |
71 | LOGPC(DLMGCP, LOGL_DEBUG, " [selected]"); | |
72 | ||
73 | LOGPC(DLMGCP, LOGL_DEBUG, "\n"); | |
74 | } | |
75 | } | |
76 | ||
77 | /* Initalize or reset codec information with default data. */ | |
78 | void codec_init(struct mgcp_rtp_codec *codec) | |
79 | { | |
80 | if (codec->subtype_name) | |
81 | talloc_free(codec->subtype_name); | |
82 | if (codec->audio_name) | |
83 | talloc_free(codec->audio_name); | |
84 | memset(codec, 0, sizeof(*codec)); | |
85 | codec->payload_type = -1; | |
86 | codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; | |
87 | codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; | |
88 | codec->rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE; | |
89 | codec->channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS; | |
90 | } | |
91 | ||
92 | /*! Initalize or reset codec information with default data. | |
93 | * \param[out] conn related rtp-connection. */ | |
94 | void mgcp_codec_reset_all(struct mgcp_conn_rtp *conn) | |
95 | { | |
96 | memset(conn->end.codecs, 0, sizeof(conn->end.codecs)); | |
97 | conn->end.codecs_assigned = 0; | |
98 | conn->end.codec = NULL; | |
99 | } | |
100 | ||
101 | /* Set members of struct mgcp_rtp_codec, extrapolate in missing information */ | |
102 | static int codec_set(void *ctx, struct mgcp_rtp_codec *codec, | |
103 | int payload_type, const char *audio_name, unsigned int pt_offset) | |
104 | { | |
105 | int rate; | |
106 | int channels; | |
107 | char audio_codec[64]; | |
108 | ||
109 | /* Initalize the codec struct with some default data to begin with */ | |
110 | codec_init(codec); | |
111 | ||
112 | if (payload_type != PTYPE_UNDEFINED) { | |
113 | /* Make sure we do not get any reserved or undefined type numbers */ | |
114 | /* See also: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */ | |
115 | if (payload_type == 1 || payload_type == 2 || payload_type == 19) | |
116 | goto error; | |
117 | if (payload_type >= 72 && payload_type <= 76) | |
118 | goto error; | |
119 | if (payload_type >= 127) | |
120 | goto error; | |
121 | ||
122 | codec->payload_type = payload_type; | |
123 | } | |
124 | ||
125 | /* When no audio name is given, we are forced to use the payload | |
126 | * type to generate the audio name. This is only possible for | |
127 | * non dynamic payload types, which are statically defined */ | |
128 | if (!audio_name) { | |
129 | switch (payload_type) { | |
130 | case 0: | |
131 | audio_name = talloc_strdup(ctx, "PCMU/8000/1"); | |
132 | break; | |
133 | case 3: | |
134 | audio_name = talloc_strdup(ctx, "GSM/8000/1"); | |
135 | break; | |
136 | case 8: | |
137 | audio_name = talloc_strdup(ctx, "PCMA/8000/1"); | |
138 | break; | |
139 | case 18: | |
140 | audio_name = talloc_strdup(ctx, "G729/8000/1"); | |
141 | break; | |
142 | default: | |
143 | /* The given payload type is not known to us, or it | |
144 | * it is a dynamic payload type for which we do not | |
145 | * know the audio name. We must give up here */ | |
146 | goto error; | |
147 | } | |
148 | } | |
149 | ||
150 | /* Now we extract the codec subtype name, rate and channels. The latter | |
151 | * two are optional. If they are not present we use the safe defaults | |
152 | * above. */ | |
153 | if (strlen(audio_name) > sizeof(audio_codec)) | |
154 | goto error; | |
155 | channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS; | |
156 | rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE; | |
157 | if (sscanf(audio_name, "%63[^/]/%d/%d", audio_codec, &rate, &channels) < 1) | |
158 | goto error; | |
159 | ||
160 | /* Note: We only accept configurations with one audio channel! */ | |
161 | if (channels != 1) | |
162 | goto error; | |
163 | ||
164 | codec->rate = rate; | |
165 | codec->channels = channels; | |
166 | codec->subtype_name = talloc_strdup(ctx, audio_codec); | |
167 | codec->audio_name = talloc_strdup(ctx, audio_name); | |
168 | codec->payload_type = payload_type; | |
169 | ||
170 | if (!strcmp(audio_codec, "G729")) { | |
171 | codec->frame_duration_num = 10; | |
172 | codec->frame_duration_den = 1000; | |
173 | } else { | |
174 | codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; | |
175 | codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; | |
176 | } | |
177 | ||
178 | /* Derive the payload type if it is unknown */ | |
179 | if (codec->payload_type == PTYPE_UNDEFINED) { | |
180 | ||
181 | /* For the known codecs from the static range we restore | |
182 | * the IANA or 3GPP assigned payload type number */ | |
183 | if (codec->rate == 8000 && codec->channels == 1) { | |
184 | /* See also: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */ | |
185 | if (!strcmp(codec->subtype_name, "GSM")) | |
186 | codec->payload_type = 3; | |
187 | else if (!strcmp(codec->subtype_name, "PCMA")) | |
188 | codec->payload_type = 8; | |
189 | else if (!strcmp(codec->subtype_name, "PCMU")) | |
190 | codec->payload_type = 0; | |
191 | else if (!strcmp(codec->subtype_name, "G729")) | |
192 | codec->payload_type = 18; | |
193 | ||
194 | /* See also: 3GPP TS 48.103, chapter 5.4.2.2 RTP Payload | |
195 | * Note: These are not fixed payload types as the IANA | |
196 | * defined once, they still remain dymanic payload | |
197 | * types, but with a payload type number preference. */ | |
198 | else if (!strcmp(codec->subtype_name, "GSM-EFR")) | |
199 | codec->payload_type = 110; | |
200 | else if (!strcmp(codec->subtype_name, "GSM-HR-08")) | |
201 | codec->payload_type = 111; | |
202 | else if (!strcmp(codec->subtype_name, "AMR")) | |
203 | codec->payload_type = 112; | |
204 | else if (!strcmp(codec->subtype_name, "AMR-WB")) | |
205 | codec->payload_type = 113; | |
206 | } | |
207 | ||
208 | /* If we could not determine a payload type we assume that | |
209 | * we are dealing with a codec from the dynamic range. We | |
210 | * choose a fixed identifier from 96-109. (Note: normally, | |
211 | * the dynamic payload type rante is from 96-127, but from | |
212 | * 110 onwards 3gpp defines prefered codec types, which are | |
213 | * also fixed, see above) */ | |
214 | if (codec->payload_type < 0) { | |
215 | codec->payload_type = 96 + pt_offset; | |
216 | if (codec->payload_type > 109) | |
217 | goto error; | |
218 | } | |
219 | } | |
220 | ||
221 | return 0; | |
222 | error: | |
223 | /* Make sure we leave a clean codec entry on error. */ | |
224 | codec_init(codec); | |
225 | memset(codec, 0, sizeof(*codec)); | |
226 | return -EINVAL; | |
227 | } | |
228 | ||
229 | /*! Add codec configuration depending on payload type and/or codec name. This | |
230 | * function uses the input parameters to extrapolate the full codec information. | |
231 | * \param[out] codec configuration (caller provided memory). | |
232 | * \param[out] conn related rtp-connection. | |
233 | * \param[in] payload_type codec type id (e.g. 3 for GSM, -1 when undefined). | |
234 | * \param[in] audio_name audio codec name (e.g. "GSM/8000/1"). | |
235 | * \returns 0 on success, -EINVAL on failure. */ | |
236 | int mgcp_codec_add(struct mgcp_conn_rtp *conn, int payload_type, const char *audio_name) | |
237 | { | |
238 | int rc; | |
239 | ||
240 | /* The amount of codecs we can store is limited, make sure we do not | |
241 | * overrun this limit. */ | |
242 | if (conn->end.codecs_assigned >= MGCP_MAX_CODECS) | |
243 | return -EINVAL; | |
244 | ||
245 | rc = codec_set(conn->conn, &conn->end.codecs[conn->end.codecs_assigned], payload_type, audio_name, | |
246 | conn->end.codecs_assigned); | |
247 | if (rc != 0) | |
248 | return -EINVAL; | |
249 | ||
250 | conn->end.codecs_assigned++; | |
251 | ||
252 | return 0; | |
253 | } | |
254 | ||
255 | /* Check if the given codec is applicable on the specified endpoint | |
256 | * Helper function for mgcp_codec_decide() */ | |
257 | static bool is_codec_compatible(const struct mgcp_endpoint *endp, const struct mgcp_rtp_codec *codec) | |
258 | { | |
259 | char codec_name[64]; | |
260 | ||
261 | /* A codec name must be set, if not, this might mean that the codec | |
262 | * (payload type) that was assigned is unknown to us so we must stop | |
263 | * here. */ | |
264 | if (!codec->subtype_name) | |
265 | return false; | |
266 | ||
267 | /* We now extract the codec_name (letters before the /, e.g. "GSM" | |
268 | * from the audio name that is stored in the trunk configuration. | |
269 | * We do not compare to the full audio_name because we expect that | |
270 | * "GSM", "GSM/8000" and "GSM/8000/1" are all compatible when the | |
271 | * audio name of the codec is set to "GSM" */ | |
272 | if (sscanf(endp->tcfg->audio_name, "%63[^/]/%*d/%*d", codec_name) < 1) | |
273 | return false; | |
274 | ||
275 | /* Finally we check if the subtype_name we have generated from the | |
276 | * audio_name in the trunc struct patches the codec_name of the | |
277 | * given codec */ | |
278 | if (strcasecmp(codec_name, codec->subtype_name) == 0) | |
279 | return true; | |
280 | ||
281 | /* FIXME: It is questinable that the method to pick a compatible | |
282 | * codec can work properly. Since this useses tcfg->audio_name, as | |
283 | * a reference, which is set to "AMR/8000" permanently. | |
284 | * tcfg->audio_name must be updated by the first connection that | |
285 | * has been made on an endpoint, so that the second connection | |
286 | * can make a meaningful decision here */ | |
287 | ||
288 | return false; | |
289 | } | |
290 | ||
291 | /*! Decide for one suitable codec | |
292 | * \param[in] conn related rtp-connection. | |
293 | * \returns 0 on success, -EINVAL on failure. */ | |
294 | int mgcp_codec_decide(struct mgcp_conn_rtp *conn) | |
295 | { | |
296 | struct mgcp_rtp_end *rtp; | |
297 | unsigned int i; | |
298 | struct mgcp_endpoint *endp; | |
299 | bool codec_assigned = false; | |
300 | ||
301 | endp = conn->conn->endp; | |
302 | rtp = &conn->end; | |
303 | ||
304 | /* This function works on the results the SDP/LCO parser has extracted | |
305 | * from the MGCP message. The goal is to select a suitable codec for | |
306 | * the given connection. When transcoding is available, the first codec | |
307 | * from the codec list is taken without further checking. When | |
308 | * transcoding is not available, then the choice must be made more | |
309 | * carefully. Each codec in the list is checked until one is found that | |
310 | * is rated compatible. The rating is done by the helper function | |
311 | * is_codec_compatible(), which does the actual checking. */ | |
312 | for (i = 0; i < rtp->codecs_assigned; i++) { | |
313 | /* When no transcoding is available, avoid codecs that would | |
314 | * require transcoding. */ | |
315 | if (endp->tcfg->no_audio_transcoding && !is_codec_compatible(endp, &rtp->codecs[i])) { | |
316 | LOGP(DLMGCP, LOGL_NOTICE, "transcoding not available, skipping codec: %d/%s\n", | |
317 | rtp->codecs[i].payload_type, rtp->codecs[i].subtype_name); | |
318 | continue; | |
319 | } | |
320 | ||
321 | rtp->codec = &rtp->codecs[i]; | |
322 | codec_assigned = true; | |
323 | break; | |
324 | } | |
325 | ||
326 | /* FIXME: To the reviewes: This is problematic. I do not get why we | |
327 | * need to reset the packet_duration_ms depending on the codec | |
328 | * selection. I thought it were all 20ms? Is this to address some | |
329 | * cornercase. (This piece of code was in the code path before, | |
330 | * together with the note: "TODO/XXX: Store this per codec and derive | |
331 | * it on use" */ | |
332 | if (codec_assigned) { | |
333 | if (rtp->maximum_packet_time >= 0 | |
334 | && rtp->maximum_packet_time * rtp->codec->frame_duration_den > | |
335 | rtp->codec->frame_duration_num * 1500) | |
336 | rtp->packet_duration_ms = 0; | |
337 | ||
338 | return 0; | |
339 | } | |
340 | ||
341 | return -EINVAL; | |
342 | } |
24 | 24 | #include <osmocom/mgcp/mgcp_internal.h> |
25 | 25 | #include <osmocom/mgcp/mgcp_common.h> |
26 | 26 | #include <osmocom/mgcp/mgcp_endp.h> |
27 | #include <osmocom/mgcp/mgcp_sdp.h> | |
28 | #include <osmocom/mgcp/mgcp_codec.h> | |
27 | 29 | #include <osmocom/gsm/gsm_utils.h> |
30 | #include <osmocom/core/rate_ctr.h> | |
28 | 31 | #include <ctype.h> |
32 | ||
33 | static const struct rate_ctr_desc rate_ctr_desc[] = { | |
34 | [IN_STREAM_ERR_TSTMP_CTR] = {"stream_err_tstmp:in", "Inbound rtp-stream timestamp errors."}, | |
35 | [OUT_STREAM_ERR_TSTMP_CTR] = {"stream_err_tstmp:out", "Outbound rtp-stream timestamp errors."}, | |
36 | [RTP_PACKETS_RX_CTR] = {"rtp:packets_rx", "Inbound rtp packets."}, | |
37 | [RTP_OCTETS_RX_CTR] = {"rtp:octets_rx", "Inbound rtp octets."}, | |
38 | [RTP_PACKETS_TX_CTR] = {"rtp:packets_tx", "Outbound rtp packets."}, | |
39 | [RTP_OCTETS_TX_CTR] = {"rtp:octets_rx", "Outbound rtp octets."}, | |
40 | [RTP_DROPPED_PACKETS_CTR] = {"rtp:dropped", "dropped rtp packets."} | |
41 | }; | |
42 | ||
43 | const static struct rate_ctr_group_desc rate_ctr_group_desc = { | |
44 | .group_name_prefix = "conn_rtp", | |
45 | .group_description = "rtp connection statistics", | |
46 | .class_id = 1, | |
47 | .num_ctr = ARRAY_SIZE(rate_ctr_desc), | |
48 | .ctr_desc = rate_ctr_desc | |
49 | }; | |
50 | ||
29 | 51 | |
30 | 52 | /* Allocate a new connection identifier. According to RFC3435, they must |
31 | 53 | * be unique only within the scope of the endpoint. (Caller must provide |
66 | 88 | return -1; |
67 | 89 | } |
68 | 90 | |
69 | /* Reset codec state and free memory */ | |
70 | static void mgcp_rtp_codec_init(struct mgcp_rtp_codec *codec) | |
71 | { | |
72 | codec->payload_type = -1; | |
73 | codec->subtype_name = NULL; | |
74 | codec->audio_name = NULL; | |
75 | codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; | |
76 | codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; | |
77 | codec->rate = DEFAULT_RTP_AUDIO_DEFAULT_RATE; | |
78 | codec->channels = DEFAULT_RTP_AUDIO_DEFAULT_CHANNELS; | |
79 | ||
80 | /* see also mgcp_sdp.c, mgcp_set_audio_info() */ | |
81 | talloc_free(codec->subtype_name); | |
82 | talloc_free(codec->audio_name); | |
83 | } | |
84 | ||
85 | 91 | /* Initialize rtp connection struct with default values */ |
86 | 92 | static void mgcp_rtp_conn_init(struct mgcp_conn_rtp *conn_rtp, struct mgcp_conn *conn) |
87 | 93 | { |
88 | 94 | struct mgcp_rtp_end *end = &conn_rtp->end; |
95 | /* FIXME: Each new rate counter group requires an unique index. At the | |
96 | * moment we generate this index using this counter, but perhaps there | |
97 | * is a more concious way to assign the indexes. */ | |
98 | static unsigned int rate_ctr_index = 0; | |
89 | 99 | |
90 | 100 | conn_rtp->type = MGCP_RTP_DEFAULT; |
91 | 101 | conn_rtp->osmux.allocated_cid = -1; |
95 | 105 | |
96 | 106 | end->rtp.fd = -1; |
97 | 107 | end->rtcp.fd = -1; |
98 | memset(&end->stats, 0, sizeof(end->stats)); | |
99 | 108 | end->rtp_port = end->rtcp_port = 0; |
100 | 109 | talloc_free(end->fmtp_extra); |
101 | 110 | end->fmtp_extra = NULL; |
104 | 113 | end->frames_per_packet = 0; /* unknown */ |
105 | 114 | end->packet_duration_ms = DEFAULT_RTP_AUDIO_PACKET_DURATION_MS; |
106 | 115 | end->output_enabled = 0; |
107 | ||
108 | mgcp_rtp_codec_init(&end->codec); | |
109 | mgcp_rtp_codec_init(&end->alt_codec); | |
116 | end->maximum_packet_time = -1; | |
117 | ||
118 | conn_rtp->rate_ctr_group = rate_ctr_group_alloc(conn, &rate_ctr_group_desc, rate_ctr_index); | |
119 | conn_rtp->state.in_stream.err_ts_ctr = &conn_rtp->rate_ctr_group->ctr[IN_STREAM_ERR_TSTMP_CTR]; | |
120 | conn_rtp->state.out_stream.err_ts_ctr = &conn_rtp->rate_ctr_group->ctr[OUT_STREAM_ERR_TSTMP_CTR]; | |
121 | rate_ctr_index++; | |
122 | ||
123 | /* Make sure codec table is reset */ | |
124 | mgcp_codec_reset_all(conn_rtp); | |
110 | 125 | } |
111 | 126 | |
112 | 127 | /* Cleanup rtp connection struct */ |
115 | 130 | osmux_disable_conn(conn_rtp); |
116 | 131 | osmux_release_cid(conn_rtp); |
117 | 132 | mgcp_free_rtp_port(&conn_rtp->end); |
133 | rate_ctr_group_free(conn_rtp->rate_ctr_group); | |
118 | 134 | } |
119 | 135 | |
120 | 136 | /*! allocate a new connection list entry. |
221 | 221 | |
222 | 222 | if (seq == sstate->last_seq) { |
223 | 223 | if (timestamp != sstate->last_timestamp) { |
224 | sstate->err_ts_counter += 1; | |
224 | rate_ctr_inc(sstate->err_ts_ctr); | |
225 | 225 | LOGP(DRTP, LOGL_ERROR, |
226 | 226 | "The %s timestamp delta is != 0 but the sequence " |
227 | 227 | "number %d is the same, " |
271 | 271 | ts_alignment_error(sstate, state->packet_duration, timestamp); |
272 | 272 | |
273 | 273 | if (timestamp_error) { |
274 | sstate->err_ts_counter += 1; | |
274 | rate_ctr_inc(sstate->err_ts_ctr); | |
275 | 275 | LOGP(DRTP, LOGL_NOTICE, |
276 | 276 | "The %s timestamp has an alignment error of %d " |
277 | 277 | "on 0x%x SSRC: %u " |
309 | 309 | ENDPOINT_NUMBER(endp), tsdelta, |
310 | 310 | inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); |
311 | 311 | } else { |
312 | tsdelta = rtp_end->codec.rate * 20 / 1000; | |
312 | tsdelta = rtp_end->codec->rate * 20 / 1000; | |
313 | 313 | LOGP(DRTP, LOGL_NOTICE, |
314 | 314 | "Fixed packet duration and last timestamp delta " |
315 | 315 | "are not available on 0x%x, " |
398 | 398 | |
399 | 399 | /*! dummy callback to disable transcoding (see also cfg->setup_rtp_processing_cb). |
400 | 400 | * \param[in] associated endpoint |
401 | * \param[in] destination RTP end | |
402 | * \param[in] source RTP end | |
401 | * \param[in] destination RTP connnection | |
402 | * \param[in] source RTP connection | |
403 | 403 | * \returns ignores input parameters, return always 0 */ |
404 | 404 | int mgcp_setup_rtp_processing_default(struct mgcp_endpoint *endp, |
405 | struct mgcp_rtp_end *dst_end, | |
406 | struct mgcp_rtp_end *src_end) | |
405 | struct mgcp_conn_rtp *conn_dst, | |
406 | struct mgcp_conn_rtp *conn_src) | |
407 | 407 | { |
408 | 408 | LOGP(DRTP, LOGL_DEBUG, "endpoint:0x%x transcoding disabled\n", |
409 | 409 | ENDPOINT_NUMBER(endp)); |
420 | 420 | "endpoint:0x%x conn:%s using format defaults\n", |
421 | 421 | ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn->conn)); |
422 | 422 | |
423 | *payload_type = conn->end.codec.payload_type; | |
424 | *audio_name = conn->end.codec.audio_name; | |
423 | *payload_type = conn->end.codec->payload_type; | |
424 | *audio_name = conn->end.codec->audio_name; | |
425 | 425 | *fmtp_extra = conn->end.fmtp_extra; |
426 | 426 | } |
427 | 427 | |
489 | 489 | uint16_t seq; |
490 | 490 | uint32_t timestamp, ssrc; |
491 | 491 | struct rtp_hdr *rtp_hdr; |
492 | int payload = rtp_end->codec.payload_type; | |
492 | int payload = rtp_end->codec->payload_type; | |
493 | 493 | |
494 | 494 | if (len < sizeof(*rtp_hdr)) |
495 | 495 | return; |
497 | 497 | rtp_hdr = (struct rtp_hdr *)data; |
498 | 498 | seq = ntohs(rtp_hdr->sequence); |
499 | 499 | timestamp = ntohl(rtp_hdr->timestamp); |
500 | arrival_time = get_current_ts(rtp_end->codec.rate); | |
500 | arrival_time = get_current_ts(rtp_end->codec->rate); | |
501 | 501 | ssrc = ntohl(rtp_hdr->ssrc); |
502 | 502 | transit = arrival_time - timestamp; |
503 | 503 | |
510 | 510 | state->in_stream.last_tsdelta = 0; |
511 | 511 | state->packet_duration = |
512 | 512 | mgcp_rtp_packet_duration(endp, rtp_end); |
513 | state->out_stream = state->in_stream; | |
513 | state->out_stream.last_seq = seq - 1; | |
514 | state->out_stream.ssrc = state->patch.orig_ssrc = ssrc; | |
515 | state->out_stream.last_tsdelta = 0; | |
514 | 516 | state->out_stream.last_timestamp = timestamp; |
515 | 517 | state->out_stream.ssrc = ssrc - 1; /* force output SSRC change */ |
516 | 518 | LOGP(DRTP, LOGL_INFO, |
521 | 523 | inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); |
522 | 524 | if (state->packet_duration == 0) { |
523 | 525 | state->packet_duration = |
524 | rtp_end->codec.rate * 20 / 1000; | |
526 | rtp_end->codec->rate * 20 / 1000; | |
525 | 527 | LOGP(DRTP, LOGL_NOTICE, |
526 | 528 | "endpoint:0x%x fixed packet duration is not available, " |
527 | 529 | "using fixed 20ms instead: %d from %s:%d\n", |
677 | 679 | } |
678 | 680 | |
679 | 681 | LOGP(DRTP, LOGL_DEBUG, |
680 | "endpoint:0x%x loop:%d, mode:%d ", | |
681 | ENDPOINT_NUMBER(endp), tcfg->audio_loop, conn_src->conn->mode); | |
682 | if (conn_src->conn->mode == MGCP_CONN_LOOPBACK) | |
683 | LOGPC(DRTP, LOGL_DEBUG, "(loopback)\n"); | |
684 | else | |
685 | LOGPC(DRTP, LOGL_DEBUG, "\n"); | |
682 | "endpoint:0x%x loop:%d, mode:%d%s\n", | |
683 | ENDPOINT_NUMBER(endp), tcfg->audio_loop, conn_src->conn->mode, | |
684 | conn_src->conn->mode == MGCP_CONN_LOOPBACK ? " (loopback)" : ""); | |
686 | 685 | |
687 | 686 | /* Note: In case of loopback configuration, both, the source and the |
688 | 687 | * destination will point to the same connection. */ |
691 | 690 | dest_name = conn_dst->conn->name; |
692 | 691 | |
693 | 692 | if (!rtp_end->output_enabled) { |
694 | rtp_end->stats.dropped_packets += 1; | |
693 | rate_ctr_inc(&conn_dst->rate_ctr_group->ctr[RTP_DROPPED_PACKETS_CTR]); | |
695 | 694 | LOGP(DRTP, LOGL_DEBUG, |
696 | 695 | "endpoint:0x%x output disabled, drop to %s %s " |
697 | 696 | "rtp_port:%u rtcp_port:%u\n", |
732 | 731 | * 'e400', or it will reject the RAB assignment. It seems to not harm other femto |
733 | 732 | * cells (as long as we patch only the first RTP payload in each stream). |
734 | 733 | */ |
735 | if (!rtp_state->patched_first_rtp_payload) { | |
734 | if (!rtp_state->patched_first_rtp_payload | |
735 | && conn_src->conn->mode == MGCP_CONN_LOOPBACK) { | |
736 | 736 | uint8_t *data = (uint8_t *) & buf[12]; |
737 | data[0] = 0xe4; | |
738 | data[1] = 0x00; | |
739 | rtp_state->patched_first_rtp_payload = true; | |
737 | if (data[0] == 0xe0) { | |
738 | data[0] = 0xe4; | |
739 | data[1] = 0x00; | |
740 | rtp_state->patched_first_rtp_payload = true; | |
741 | LOGP(DRTP, LOGL_DEBUG, | |
742 | "endpoint:0x%x Patching over first two bytes" | |
743 | " to fake an IuUP Initialization Ack\n", | |
744 | ENDPOINT_NUMBER(endp)); | |
745 | } | |
740 | 746 | } |
741 | 747 | |
742 | 748 | len = mgcp_udp_send(rtp_end->rtp.fd, |
746 | 752 | if (len <= 0) |
747 | 753 | return len; |
748 | 754 | |
749 | conn_dst->end.stats.packets_tx += 1; | |
750 | conn_dst->end.stats.octets_tx += len; | |
755 | rate_ctr_inc(&conn_dst->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]); | |
756 | rate_ctr_add(&conn_dst->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], len); | |
751 | 757 | |
752 | 758 | nbytes += len; |
753 | 759 | buflen = cont; |
766 | 772 | &rtp_end->addr, |
767 | 773 | rtp_end->rtcp_port, buf, len); |
768 | 774 | |
769 | conn_dst->end.stats.packets_tx += 1; | |
770 | conn_dst->end.stats.octets_tx += len; | |
775 | rate_ctr_inc(&conn_dst->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]); | |
776 | rate_ctr_add(&conn_dst->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], len); | |
771 | 777 | |
772 | 778 | return len; |
773 | 779 | } |
864 | 870 | struct mgcp_endpoint *endp; |
865 | 871 | endp = conn->conn->endp; |
866 | 872 | |
873 | /* Note: it is legal to create a connection but never setting a port | |
874 | * and IP-address for outgoing data. */ | |
875 | if (strcmp(inet_ntoa(conn->end.addr), "0.0.0.0") == 0 && conn->end.rtp_port == 0) { | |
876 | LOGP(DRTP, LOGL_DEBUG, | |
877 | "endpoint:0x%x destination IP-address and rtp port is (not yet) known\n", | |
878 | ENDPOINT_NUMBER(endp)); | |
879 | return -1; | |
880 | } | |
881 | ||
867 | 882 | if (strcmp(inet_ntoa(conn->end.addr), "0.0.0.0") == 0) { |
868 | 883 | LOGP(DRTP, LOGL_ERROR, |
869 | 884 | "endpoint:0x%x destination IP-address is invalid\n", |
927 | 942 | } |
928 | 943 | |
929 | 944 | /* Increment RX statistics */ |
930 | conn->end.stats.packets_rx += 1; | |
931 | conn->end.stats.octets_rx += rc; | |
945 | rate_ctr_inc(&conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]); | |
946 | rate_ctr_add(&conn->rate_ctr_group->ctr[RTP_OCTETS_RX_CTR], rc); | |
932 | 947 | |
933 | 948 | /* Forward a copy of the RTP data to a debug ip/port */ |
934 | 949 | forward_data(fd->fd, &conn->tap_in, buf, rc); |
255 | 255 | .sin_port = conn_net->end.rtp_port, |
256 | 256 | }; |
257 | 257 | |
258 | conn_bts->end.stats.octets_tx += msg->len; | |
259 | conn_bts->end.stats.packets_tx++; | |
258 | rate_ctr_inc(&conn_bts->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]); | |
259 | rate_ctr_add(&conn_bts->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], msg->len); | |
260 | 260 | |
261 | 261 | /* Send RTP data to NET */ |
262 | 262 | /* FIXME: Get rid of conn_bts and conn_net! */ |
282 | 282 | .sin_port = conn_bts->end.rtp_port, |
283 | 283 | }; |
284 | 284 | |
285 | conn_net->end.stats.octets_tx += msg->len; | |
286 | conn_net->end.stats.packets_tx++; | |
285 | rate_ctr_inc(&conn_net->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]); | |
286 | rate_ctr_add(&conn_net->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], msg->len); | |
287 | 287 | |
288 | 288 | /* Send RTP data to BTS */ |
289 | 289 | /* FIXME: Get rid of conn_bts and conn_net! */ |
321 | 321 | { |
322 | 322 | struct msgb *msg; |
323 | 323 | struct osmux_hdr *osmuxh; |
324 | struct llist_head list; | |
324 | struct sockaddr_in addr; | |
325 | struct mgcp_config *cfg = ofd->data; | |
326 | uint32_t rem; | |
327 | struct mgcp_conn_rtp *conn_bts = NULL; | |
328 | ||
329 | msg = osmux_recv(ofd, &addr); | |
330 | if (!msg) | |
331 | return -1; | |
332 | ||
333 | /* not any further processing dummy messages */ | |
334 | if (msg->data[0] == MGCP_DUMMY_LOAD) | |
335 | goto out; | |
336 | ||
337 | rem = msg->len; | |
338 | while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { | |
339 | struct mgcp_endpoint *endp; | |
340 | ||
341 | /* Yes, we use MGCP_DEST_NET to locate the origin */ | |
342 | endp = endpoint_lookup(cfg, osmuxh->circuit_id, | |
343 | &addr.sin_addr, MGCP_DEST_NET); | |
344 | ||
345 | /* FIXME: Get rid of CONN_ID_XXX! */ | |
346 | conn_bts = mgcp_conn_get_rtp(endp, CONN_ID_BTS); | |
347 | if (!conn_bts) | |
348 | goto out; | |
349 | ||
350 | if (!endp) { | |
351 | LOGP(DLMGCP, LOGL_ERROR, | |
352 | "Cannot find an endpoint for circuit_id=%d\n", | |
353 | osmuxh->circuit_id); | |
354 | goto out; | |
355 | } | |
356 | conn_bts->osmux.stats.octets += osmux_chunk_length(msg, rem); | |
357 | conn_bts->osmux.stats.chunks++; | |
358 | rem = msg->len; | |
359 | ||
360 | osmux_xfrm_output_sched(&conn_bts->osmux.out, osmuxh); | |
361 | } | |
362 | out: | |
363 | msgb_free(msg); | |
364 | return 0; | |
365 | } | |
366 | ||
367 | /* This is called from the bsc-nat */ | |
368 | static int osmux_handle_dummy(struct mgcp_config *cfg, struct sockaddr_in *addr, | |
369 | struct msgb *msg) | |
370 | { | |
371 | struct mgcp_endpoint *endp; | |
372 | uint8_t osmux_cid; | |
373 | struct mgcp_conn_rtp *conn_net = NULL; | |
374 | ||
375 | if (msg->len < 1 + sizeof(osmux_cid)) { | |
376 | LOGP(DLMGCP, LOGL_ERROR, | |
377 | "Discarding truncated Osmux dummy load\n"); | |
378 | goto out; | |
379 | } | |
380 | ||
381 | LOGP(DLMGCP, LOGL_DEBUG, "Received Osmux dummy load from %s\n", | |
382 | inet_ntoa(addr->sin_addr)); | |
383 | ||
384 | if (!cfg->osmux) { | |
385 | LOGP(DLMGCP, LOGL_ERROR, | |
386 | "bsc wants to use Osmux but bsc-nat did not request it\n"); | |
387 | goto out; | |
388 | } | |
389 | ||
390 | /* extract the osmux CID from the dummy message */ | |
391 | memcpy(&osmux_cid, &msg->data[1], sizeof(osmux_cid)); | |
392 | ||
393 | endp = endpoint_lookup(cfg, osmux_cid, &addr->sin_addr, MGCP_DEST_BTS); | |
394 | if (!endp) { | |
395 | LOGP(DLMGCP, LOGL_ERROR, | |
396 | "Cannot find endpoint for Osmux CID %d\n", osmux_cid); | |
397 | goto out; | |
398 | } | |
399 | ||
400 | conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); | |
401 | if (!conn_net) | |
402 | goto out; | |
403 | ||
404 | if (conn_net->osmux.state == OSMUX_STATE_ENABLED) | |
405 | goto out; | |
406 | ||
407 | if (osmux_enable_conn(endp, conn_net, &addr->sin_addr, addr->sin_port) < 0 ) { | |
408 | LOGP(DLMGCP, LOGL_ERROR, | |
409 | "Could not enable osmux in endpoint %d\n", | |
410 | ENDPOINT_NUMBER(endp)); | |
411 | goto out; | |
412 | } | |
413 | ||
414 | LOGP(DLMGCP, LOGL_INFO, "Enabling osmux in endpoint %d for %s:%u\n", | |
415 | ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr), | |
416 | ntohs(addr->sin_port)); | |
417 | out: | |
418 | msgb_free(msg); | |
419 | return 0; | |
420 | } | |
421 | ||
422 | int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what) | |
423 | { | |
424 | struct msgb *msg; | |
425 | struct osmux_hdr *osmuxh; | |
325 | 426 | struct sockaddr_in addr; |
326 | 427 | struct mgcp_config *cfg = ofd->data; |
327 | 428 | uint32_t rem; |
333 | 434 | |
334 | 435 | /* not any further processing dummy messages */ |
335 | 436 | if (msg->data[0] == MGCP_DUMMY_LOAD) |
336 | goto out; | |
437 | return osmux_handle_dummy(cfg, &addr, msg); | |
337 | 438 | |
338 | 439 | rem = msg->len; |
339 | 440 | while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { |
340 | 441 | struct mgcp_endpoint *endp; |
341 | 442 | |
342 | /* Yes, we use MGCP_DEST_NET to locate the origin */ | |
443 | /* Yes, we use MGCP_DEST_BTS to locate the origin */ | |
343 | 444 | endp = endpoint_lookup(cfg, osmuxh->circuit_id, |
344 | &addr.sin_addr, MGCP_DEST_NET); | |
445 | &addr.sin_addr, MGCP_DEST_BTS); | |
345 | 446 | |
346 | 447 | /* FIXME: Get rid of CONN_ID_XXX! */ |
347 | 448 | conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); |
358 | 459 | conn_net->osmux.stats.chunks++; |
359 | 460 | rem = msg->len; |
360 | 461 | |
361 | osmux_xfrm_output(osmuxh, &conn_net->osmux.out, &list); | |
362 | osmux_tx_sched(&list, scheduled_tx_bts_cb, endp); | |
363 | } | |
364 | out: | |
365 | msgb_free(msg); | |
366 | return 0; | |
367 | } | |
368 | ||
369 | /* This is called from the bsc-nat */ | |
370 | static int osmux_handle_dummy(struct mgcp_config *cfg, struct sockaddr_in *addr, | |
371 | struct msgb *msg) | |
372 | { | |
373 | struct mgcp_endpoint *endp; | |
374 | uint8_t osmux_cid; | |
375 | struct mgcp_conn_rtp *conn_net = NULL; | |
376 | ||
377 | if (msg->len < 1 + sizeof(osmux_cid)) { | |
378 | LOGP(DLMGCP, LOGL_ERROR, | |
379 | "Discarding truncated Osmux dummy load\n"); | |
380 | goto out; | |
381 | } | |
382 | ||
383 | LOGP(DLMGCP, LOGL_DEBUG, "Received Osmux dummy load from %s\n", | |
384 | inet_ntoa(addr->sin_addr)); | |
385 | ||
386 | if (!cfg->osmux) { | |
387 | LOGP(DLMGCP, LOGL_ERROR, | |
388 | "bsc wants to use Osmux but bsc-nat did not request it\n"); | |
389 | goto out; | |
390 | } | |
391 | ||
392 | /* extract the osmux CID from the dummy message */ | |
393 | memcpy(&osmux_cid, &msg->data[1], sizeof(osmux_cid)); | |
394 | ||
395 | endp = endpoint_lookup(cfg, osmux_cid, &addr->sin_addr, MGCP_DEST_BTS); | |
396 | if (!endp) { | |
397 | LOGP(DLMGCP, LOGL_ERROR, | |
398 | "Cannot find endpoint for Osmux CID %d\n", osmux_cid); | |
399 | goto out; | |
400 | } | |
401 | ||
402 | conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); | |
403 | if (!conn_net) | |
404 | goto out; | |
405 | ||
406 | if (conn_net->osmux.state == OSMUX_STATE_ENABLED) | |
407 | goto out; | |
408 | ||
409 | if (osmux_enable_conn(endp, conn_net, &addr->sin_addr, addr->sin_port) < 0 ) { | |
410 | LOGP(DLMGCP, LOGL_ERROR, | |
411 | "Could not enable osmux in endpoint %d\n", | |
412 | ENDPOINT_NUMBER(endp)); | |
413 | goto out; | |
414 | } | |
415 | ||
416 | LOGP(DLMGCP, LOGL_INFO, "Enabling osmux in endpoint %d for %s:%u\n", | |
417 | ENDPOINT_NUMBER(endp), inet_ntoa(addr->sin_addr), | |
418 | ntohs(addr->sin_port)); | |
419 | out: | |
420 | msgb_free(msg); | |
421 | return 0; | |
422 | } | |
423 | ||
424 | int osmux_read_from_bsc_cb(struct osmo_fd *ofd, unsigned int what) | |
425 | { | |
426 | struct msgb *msg; | |
427 | struct osmux_hdr *osmuxh; | |
428 | struct llist_head list; | |
429 | struct sockaddr_in addr; | |
430 | struct mgcp_config *cfg = ofd->data; | |
431 | uint32_t rem; | |
432 | struct mgcp_conn_rtp *conn_net = NULL; | |
433 | ||
434 | msg = osmux_recv(ofd, &addr); | |
435 | if (!msg) | |
436 | return -1; | |
437 | ||
438 | /* not any further processing dummy messages */ | |
439 | if (msg->data[0] == MGCP_DUMMY_LOAD) | |
440 | return osmux_handle_dummy(cfg, &addr, msg); | |
441 | ||
442 | rem = msg->len; | |
443 | while((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { | |
444 | struct mgcp_endpoint *endp; | |
445 | ||
446 | /* Yes, we use MGCP_DEST_BTS to locate the origin */ | |
447 | endp = endpoint_lookup(cfg, osmuxh->circuit_id, | |
448 | &addr.sin_addr, MGCP_DEST_BTS); | |
449 | ||
450 | /* FIXME: Get rid of CONN_ID_XXX! */ | |
451 | conn_net = mgcp_conn_get_rtp(endp, CONN_ID_NET); | |
452 | if (!conn_net) | |
453 | goto out; | |
454 | ||
455 | if (!endp) { | |
456 | LOGP(DLMGCP, LOGL_ERROR, | |
457 | "Cannot find an endpoint for circuit_id=%d\n", | |
458 | osmuxh->circuit_id); | |
459 | goto out; | |
460 | } | |
461 | conn_net->osmux.stats.octets += osmux_chunk_length(msg, rem); | |
462 | conn_net->osmux.stats.chunks++; | |
463 | rem = msg->len; | |
464 | ||
465 | osmux_xfrm_output(osmuxh, &conn_net->osmux.out, &list); | |
466 | osmux_tx_sched(&list, scheduled_tx_net_cb, endp); | |
462 | osmux_xfrm_output_sched(&conn_net->osmux.out, osmuxh); | |
467 | 463 | } |
468 | 464 | out: |
469 | 465 | msgb_free(msg); |
552 | 548 | switch (endp->cfg->role) { |
553 | 549 | case MGCP_BSC_NAT: |
554 | 550 | conn->type = MGCP_OSMUX_BSC_NAT; |
551 | osmux_xfrm_output_set_tx_cb(&conn->osmux.out, | |
552 | scheduled_tx_net_cb, endp); | |
555 | 553 | break; |
556 | 554 | case MGCP_BSC: |
557 | 555 | conn->type = MGCP_OSMUX_BSC; |
556 | osmux_xfrm_output_set_tx_cb(&conn->osmux.out, | |
557 | scheduled_tx_bts_cb, endp); | |
558 | 558 | break; |
559 | 559 | } |
560 | 560 | |
575 | 575 | |
576 | 576 | LOGP(DLMGCP, LOGL_INFO, "Releasing connection %s using Osmux CID %u\n", |
577 | 577 | conn->conn->id, conn->osmux.cid); |
578 | ||
579 | /* We are closing, we don't need pending RTP packets to be transmitted */ | |
580 | osmux_xfrm_output_set_tx_cb(&conn->osmux.out, NULL, NULL); | |
581 | osmux_xfrm_output_flush(&conn->osmux.out); | |
582 | ||
578 | 583 | osmux_xfrm_input_close_circuit(conn->osmux.in, conn->osmux.cid); |
579 | 584 | conn->osmux.state = OSMUX_STATE_DISABLED; |
580 | 585 | conn->osmux.cid = -1; |
39 | 39 | #include <osmocom/mgcp/mgcp_msg.h> |
40 | 40 | #include <osmocom/mgcp/mgcp_endp.h> |
41 | 41 | #include <osmocom/mgcp/mgcp_sdp.h> |
42 | #include <osmocom/mgcp/mgcp_codec.h> | |
42 | 43 | |
43 | 44 | struct mgcp_request { |
44 | 45 | char *name; |
87 | 88 | } |
88 | 89 | } |
89 | 90 | |
90 | return cfg->setup_rtp_processing_cb(endp, &conn_dst->end, | |
91 | &conn_src->end); | |
91 | return cfg->setup_rtp_processing_cb(endp, conn_dst, conn_src); | |
92 | 92 | } |
93 | 93 | |
94 | 94 | /* array of function pointers for handling various |
355 | 355 | |
356 | 356 | /* Try to find a free port by attempting to bind on it. Also handle the |
357 | 357 | * counter that points on the next free port. Since we have a pointer |
358 | * to the next free port, binding should work on the first attempt, | |
359 | * nevertheless, try at least the next 200 ports before giving up */ | |
358 | * to the next free port, binding should in work on the first attempt in | |
359 | * general. In case of failure the next port is tryed until the whole port | |
360 | * range is tryed once. */ | |
360 | 361 | static int allocate_port(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn) |
361 | 362 | { |
362 | 363 | int i; |
363 | 364 | struct mgcp_rtp_end *end; |
364 | 365 | struct mgcp_port_range *range; |
366 | unsigned int tries; | |
365 | 367 | |
366 | 368 | OSMO_ASSERT(conn); |
367 | 369 | end = &conn->end; |
370 | 372 | range = &endp->cfg->net_ports; |
371 | 373 | |
372 | 374 | /* attempt to find a port */ |
373 | for (i = 0; i < 200; ++i) { | |
375 | tries = (range->range_end - range->range_start) / 2; | |
376 | for (i = 0; i < tries; ++i) { | |
374 | 377 | int rc; |
375 | 378 | |
376 | 379 | if (range->last_port >= range->range_end) |
386 | 389 | } |
387 | 390 | |
388 | 391 | LOGP(DLMGCP, LOGL_ERROR, |
389 | "Allocating a RTP/RTCP port failed 200 times 0x%x.\n", | |
390 | ENDPOINT_NUMBER(endp)); | |
392 | "Allocating a RTP/RTCP port failed %u times 0x%x.\n", | |
393 | tries, ENDPOINT_NUMBER(endp)); | |
394 | return -1; | |
395 | } | |
396 | ||
397 | /*! Helper function for check_local_cx_options() to get a pointer of the next | |
398 | * lco option identifier | |
399 | * \param[in] lco string | |
400 | * \returns pointer to the beginning of the LCO identifier, NULL on failure */ | |
401 | char *get_lco_identifier(const char *options) | |
402 | { | |
403 | char *ptr; | |
404 | unsigned int count = 0; | |
405 | ||
406 | /* Jump to the end of the lco identifier */ | |
407 | ptr = strstr(options, ":"); | |
408 | if (!ptr) | |
409 | return NULL; | |
410 | ||
411 | /* Walk backwards until the pointer points to the beginning of the | |
412 | * lco identifier. We know that we stand at the beginning when we | |
413 | * are either at the beginning of the memory or see a space or | |
414 | * comma. (this is tolerant, it will accept a:10, b:11 as well as | |
415 | * a:10,b:11) */ | |
416 | while (1) { | |
417 | /* Endless loop protection */ | |
418 | if (count > 10000) | |
419 | return NULL; | |
420 | else if (ptr < options || *ptr == ' ' || *ptr == ',') { | |
421 | ptr++; | |
422 | break; | |
423 | } | |
424 | ptr--; | |
425 | count++; | |
426 | } | |
427 | ||
428 | /* Check if we got any result */ | |
429 | if (*ptr == ':') | |
430 | return NULL; | |
431 | ||
432 | return ptr; | |
433 | } | |
434 | ||
435 | /*! Check the LCO option. This function checks for multiple appearence of LCO | |
436 | * options, which is illegal | |
437 | * \param[in] ctx talloc context | |
438 | * \param[in] lco string | |
439 | * \returns 0 on success, -1 on failure */ | |
440 | int check_local_cx_options(void *ctx, const char *options) | |
441 | { | |
442 | int i; | |
443 | char *options_copy; | |
444 | char *lco_identifier; | |
445 | char *lco_identifier_end; | |
446 | char *next_lco_identifier; | |
447 | ||
448 | char **lco_seen; | |
449 | unsigned int lco_seen_n = 0; | |
450 | ||
451 | if (!options) | |
452 | return -1; | |
453 | ||
454 | lco_seen = | |
455 | (char **)talloc_zero_size(ctx, strlen(options) * sizeof(char *)); | |
456 | options_copy = talloc_strdup(ctx, options); | |
457 | lco_identifier = options_copy; | |
458 | ||
459 | do { | |
460 | /* Move the lco_identifier pointer to the beginning of the | |
461 | * current lco option identifier */ | |
462 | lco_identifier = get_lco_identifier(lco_identifier); | |
463 | if (!lco_identifier) | |
464 | goto error; | |
465 | ||
466 | /* Look ahead to the next LCO option early, since we | |
467 | * will parse destructively */ | |
468 | next_lco_identifier = strstr(lco_identifier + 1, ","); | |
469 | ||
470 | /* Pinch off the end of the lco field identifier name | |
471 | * and see if we still got something, also check if | |
472 | * there is some value after the colon. */ | |
473 | lco_identifier_end = strstr(lco_identifier, ":"); | |
474 | if (!lco_identifier_end) | |
475 | goto error; | |
476 | if (*(lco_identifier_end + 1) == ' ' | |
477 | || *(lco_identifier_end + 1) == ',' | |
478 | || *(lco_identifier_end + 1) == '\0') | |
479 | goto error; | |
480 | *lco_identifier_end = '\0'; | |
481 | if (strlen(lco_identifier) == 0) | |
482 | goto error; | |
483 | ||
484 | /* Check if we have already seen the current field identifier | |
485 | * before. If yes, we must bail, an LCO must only appear once | |
486 | * in the LCO string */ | |
487 | for (i = 0; i < lco_seen_n; i++) { | |
488 | if (strcmp(lco_seen[i], lco_identifier) == 0) | |
489 | goto error; | |
490 | } | |
491 | lco_seen[lco_seen_n] = lco_identifier; | |
492 | lco_seen_n++; | |
493 | ||
494 | /* The first identifier must always be found at the beginnning | |
495 | * of the LCO string */ | |
496 | if (lco_seen[0] != options_copy) | |
497 | goto error; | |
498 | ||
499 | /* Go to the next lco option */ | |
500 | lco_identifier = next_lco_identifier; | |
501 | } while (lco_identifier); | |
502 | ||
503 | talloc_free(lco_seen); | |
504 | talloc_free(options_copy); | |
505 | return 0; | |
506 | error: | |
507 | talloc_free(lco_seen); | |
508 | talloc_free(options_copy); | |
391 | 509 | return -1; |
392 | 510 | } |
393 | 511 | |
401 | 519 | char *p_opt, *a_opt; |
402 | 520 | char codec[9]; |
403 | 521 | |
522 | if (!options) | |
523 | return 0; | |
524 | if (strlen(options) == 0) | |
525 | return 0; | |
526 | ||
527 | /* Make sure the encoding of the LCO is consistant before we proceed */ | |
528 | if (check_local_cx_options(ctx, options) != 0) { | |
529 | LOGP(DLMGCP, LOGL_ERROR, | |
530 | "local CX options: Internal inconsistency in Local Connection Options!\n"); | |
531 | return 524; | |
532 | } | |
533 | ||
404 | 534 | talloc_free(lco->string); |
405 | talloc_free(lco->codec); | |
406 | lco->codec = NULL; | |
407 | lco->pkt_period_min = lco->pkt_period_max = 0; | |
408 | lco->string = talloc_strdup(ctx, options ? options : ""); | |
535 | lco->string = talloc_strdup(ctx, options); | |
409 | 536 | |
410 | 537 | p_opt = strstr(lco->string, "p:"); |
411 | 538 | if (p_opt && sscanf(p_opt, "p:%d-%d", |
412 | 539 | &lco->pkt_period_min, &lco->pkt_period_max) == 1) |
413 | 540 | lco->pkt_period_max = lco->pkt_period_min; |
414 | 541 | |
542 | /* FIXME: LCO also supports the negotiation of more then one codec. | |
543 | * (e.g. a:PCMU;G726-32) But this implementation only supports a single | |
544 | * codec only. */ | |
415 | 545 | a_opt = strstr(lco->string, "a:"); |
416 | if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1) | |
546 | if (a_opt && sscanf(a_opt, "a:%8[^,]", codec) == 1) { | |
547 | talloc_free(lco->codec); | |
417 | 548 | lco->codec = talloc_strdup(ctx, codec); |
549 | } | |
418 | 550 | |
419 | 551 | LOGP(DLMGCP, LOGL_DEBUG, |
420 | 552 | "local CX options: lco->pkt_period_max: %i, lco->codec: %s\n", |
455 | 587 | /* Get the number of frames per channel and packet */ |
456 | 588 | if (rtp->frames_per_packet) |
457 | 589 | f = rtp->frames_per_packet; |
458 | else if (rtp->packet_duration_ms && rtp->codec.frame_duration_num) { | |
459 | int den = 1000 * rtp->codec.frame_duration_num; | |
460 | f = (rtp->packet_duration_ms * rtp->codec.frame_duration_den + | |
590 | else if (rtp->packet_duration_ms && rtp->codec->frame_duration_num) { | |
591 | int den = 1000 * rtp->codec->frame_duration_num; | |
592 | f = (rtp->packet_duration_ms * rtp->codec->frame_duration_den + | |
461 | 593 | den / 2) |
462 | 594 | / den; |
463 | 595 | } |
464 | 596 | |
465 | return rtp->codec.rate * f * rtp->codec.frame_duration_num / | |
466 | rtp->codec.frame_duration_den; | |
597 | return rtp->codec->rate * f * rtp->codec->frame_duration_num / | |
598 | rtp->codec->frame_duration_den; | |
467 | 599 | } |
468 | 600 | |
469 | 601 | static int mgcp_osmux_setup(struct mgcp_endpoint *endp, const char *line) |
477 | 609 | } |
478 | 610 | |
479 | 611 | return mgcp_parse_osmux_cid(line); |
612 | } | |
613 | ||
614 | /* Process codec information contained in CRCX/MDCX */ | |
615 | static int handle_codec_info(struct mgcp_conn_rtp *conn, | |
616 | struct mgcp_parse_data *p, int have_sdp, bool crcx) | |
617 | { | |
618 | struct mgcp_endpoint *endp = p->endp; | |
619 | int rc; | |
620 | char *cmd; | |
621 | ||
622 | if (crcx) | |
623 | cmd = "CRCX"; | |
624 | else | |
625 | cmd = "MDCX"; | |
626 | ||
627 | /* Collect codec information */ | |
628 | if (have_sdp) { | |
629 | /* If we have SDP, we ignore the local connection options and | |
630 | * use only the SDP information. */ | |
631 | mgcp_codec_reset_all(conn); | |
632 | rc = mgcp_parse_sdp_data(endp, conn, p); | |
633 | if (rc != 0) { | |
634 | LOGP(DLMGCP, LOGL_ERROR, | |
635 | "%s: endpoint:%x sdp not parseable\n", cmd, | |
636 | ENDPOINT_NUMBER(endp)); | |
637 | ||
638 | /* See also RFC 3661: Protocol error */ | |
639 | return 510; | |
640 | } | |
641 | } else if (endp->local_options.codec) { | |
642 | /* When no SDP is available, we use the codec information from | |
643 | * the local connection options (if present) */ | |
644 | mgcp_codec_reset_all(conn); | |
645 | rc = mgcp_codec_add(conn, PTYPE_UNDEFINED, endp->local_options.codec); | |
646 | if (rc != 0) | |
647 | goto error; | |
648 | } | |
649 | ||
650 | /* Make sure we always set a sane default codec */ | |
651 | if (conn->end.codecs_assigned == 0) { | |
652 | /* When SDP and/or LCO did not supply any codec information, | |
653 | * than it makes sense to pick a sane default: (payload-type 0, | |
654 | * PCMU), see also: OS#2658 */ | |
655 | mgcp_codec_reset_all(conn); | |
656 | rc = mgcp_codec_add(conn, 0, NULL); | |
657 | if (rc != 0) | |
658 | goto error; | |
659 | } | |
660 | ||
661 | /* Make codec decision */ | |
662 | if (mgcp_codec_decide(conn) != 0) | |
663 | goto error; | |
664 | ||
665 | return 0; | |
666 | ||
667 | error: | |
668 | LOGP(DLMGCP, LOGL_ERROR, | |
669 | "%s: endpoint:0x%x codec negotiation failure\n", cmd, | |
670 | ENDPOINT_NUMBER(endp)); | |
671 | ||
672 | /* See also RFC 3661: Codec negotiation failure */ | |
673 | return 534; | |
480 | 674 | } |
481 | 675 | |
482 | 676 | /* CRCX command handler, processes the received command */ |
596 | 790 | * connection ids) */ |
597 | 791 | endp->callid = talloc_strdup(tcfg->endpoints, callid); |
598 | 792 | |
599 | /* Extract audio codec information */ | |
600 | rc = set_local_cx_options(endp->tcfg->endpoints, &endp->local_options, | |
601 | local_options); | |
602 | if (rc != 0) { | |
603 | LOGP(DLMGCP, LOGL_ERROR, | |
604 | "CRCX: endpoint:%x inavlid local connection options!\n", | |
605 | ENDPOINT_NUMBER(endp)); | |
606 | error_code = rc; | |
607 | goto error2; | |
608 | } | |
609 | ||
610 | 793 | snprintf(conn_name, sizeof(conn_name), "%s", callid); |
611 | 794 | _conn = mgcp_conn_alloc(NULL, endp, MGCP_CONN_TYPE_RTP, conn_name); |
612 | 795 | if (!_conn) { |
637 | 820 | goto error2; |
638 | 821 | } |
639 | 822 | |
640 | /* set up RTP media parameters */ | |
641 | if (have_sdp) | |
642 | mgcp_parse_sdp_data(endp, conn, p); | |
643 | else if (endp->local_options.codec) | |
644 | mgcp_set_audio_info(p->cfg, &conn->end.codec, | |
645 | PTYPE_UNDEFINED, endp->local_options.codec); | |
823 | /* Set local connection options, if present */ | |
824 | if (local_options) { | |
825 | rc = set_local_cx_options(endp->tcfg->endpoints, | |
826 | &endp->local_options, local_options); | |
827 | if (rc != 0) { | |
828 | LOGP(DLMGCP, LOGL_ERROR, | |
829 | "CRCX: endpoint:%x inavlid local connection options!\n", | |
830 | ENDPOINT_NUMBER(endp)); | |
831 | error_code = rc; | |
832 | goto error2; | |
833 | } | |
834 | } | |
835 | ||
836 | /* Handle codec information and decide for a suitable codec */ | |
837 | rc = handle_codec_info(conn, p, have_sdp, true); | |
838 | mgcp_codec_summary(conn); | |
839 | if (rc) { | |
840 | error_code = rc; | |
841 | goto error2; | |
842 | } | |
843 | ||
646 | 844 | conn->end.fmtp_extra = talloc_strdup(tcfg->endpoints, |
647 | 845 | tcfg->audio_fmtp_extra); |
648 | 846 | |
717 | 915 | error2: |
718 | 916 | mgcp_endp_release(endp); |
719 | 917 | LOGP(DLMGCP, LOGL_NOTICE, |
720 | "CRCX: endpoint:0x%x unable to create connection resource error\n", | |
918 | "CRCX: endpoint:0x%x unable to create connection\n", | |
721 | 919 | ENDPOINT_NUMBER(endp)); |
722 | 920 | return create_err_response(endp, error_code, "CRCX", p->trans); |
723 | 921 | } |
922 | ||
923 | ||
924 | ||
925 | ||
724 | 926 | |
725 | 927 | /* MDCX command handler, processes the received command */ |
726 | 928 | static struct msgb *handle_modify_con(struct mgcp_parse_data *p) |
813 | 1015 | } else |
814 | 1016 | conn->conn->mode = conn->conn->mode_orig; |
815 | 1017 | |
816 | if (have_sdp) | |
817 | mgcp_parse_sdp_data(endp, conn, p); | |
818 | ||
819 | rc = set_local_cx_options(endp->tcfg->endpoints, &endp->local_options, | |
820 | local_options); | |
821 | if (rc != 0) { | |
822 | LOGP(DLMGCP, LOGL_ERROR, | |
823 | "MDCX: endpoint:%x inavlid local connection options!\n", | |
824 | ENDPOINT_NUMBER(endp)); | |
1018 | /* Set local connection options, if present */ | |
1019 | if (local_options) { | |
1020 | rc = set_local_cx_options(endp->tcfg->endpoints, | |
1021 | &endp->local_options, local_options); | |
1022 | if (rc != 0) { | |
1023 | LOGP(DLMGCP, LOGL_ERROR, | |
1024 | "MDCX: endpoint:%x inavlid local connection options!\n", | |
1025 | ENDPOINT_NUMBER(endp)); | |
1026 | error_code = rc; | |
1027 | goto error3; | |
1028 | } | |
1029 | } | |
1030 | ||
1031 | /* Handle codec information and decide for a suitable codec */ | |
1032 | rc = handle_codec_info(conn, p, have_sdp, false); | |
1033 | mgcp_codec_summary(conn); | |
1034 | if (rc) { | |
825 | 1035 | error_code = rc; |
826 | 1036 | goto error3; |
827 | 1037 | } |
828 | ||
829 | if (!have_sdp && endp->local_options.codec) | |
830 | mgcp_set_audio_info(p->cfg, &conn->end.codec, | |
831 | PTYPE_UNDEFINED, endp->local_options.codec); | |
832 | 1038 | |
833 | 1039 | /* check connection mode setting */ |
834 | 1040 | if (conn->conn->mode != MGCP_CONN_LOOPBACK |
840 | 1046 | error_code = 527; |
841 | 1047 | goto error3; |
842 | 1048 | } |
1049 | ||
843 | 1050 | |
844 | 1051 | if (setup_rtp_processing(endp, conn) != 0) |
845 | 1052 | goto error3; |
24 | 24 | #include <osmocom/mgcp/mgcp_internal.h> |
25 | 25 | #include <osmocom/mgcp/mgcp_msg.h> |
26 | 26 | #include <osmocom/mgcp/mgcp_endp.h> |
27 | #include <osmocom/mgcp/mgcp_codec.h> | |
27 | 28 | |
28 | 29 | #include <errno.h> |
29 | 30 | |
31 | /* A struct to store intermediate parsing results. The function | |
32 | * mgcp_parse_sdp_data() is using it as temporary storage for parsing the SDP | |
33 | * codec information. */ | |
30 | 34 | struct sdp_rtp_map { |
31 | 35 | /* the type */ |
32 | 36 | int payload_type; |
39 | 43 | int channels; |
40 | 44 | }; |
41 | 45 | |
42 | /*! Set codec configuration depending on payload type and codec name. | |
43 | * \param[in] ctx talloc context. | |
44 | * \param[out] codec configuration (caller provided memory). | |
45 | * \param[in] payload_type codec type id (e.g. 3 for GSM, -1 when undefined). | |
46 | * \param[in] audio_name audio codec name (e.g. "GSM/8000/1"). | |
47 | * \returns 0 on success, -1 on failure. */ | |
48 | int mgcp_set_audio_info(void *ctx, struct mgcp_rtp_codec *codec, | |
49 | int payload_type, const char *audio_name) | |
50 | { | |
51 | int rate = codec->rate; | |
52 | int channels = codec->channels; | |
53 | char audio_codec[64]; | |
54 | ||
55 | talloc_free(codec->subtype_name); | |
56 | codec->subtype_name = NULL; | |
57 | talloc_free(codec->audio_name); | |
58 | codec->audio_name = NULL; | |
59 | ||
60 | if (payload_type != PTYPE_UNDEFINED) | |
61 | codec->payload_type = payload_type; | |
62 | ||
63 | if (!audio_name) { | |
64 | switch (payload_type) { | |
65 | case 0: | |
66 | audio_name = "PCMU/8000/1"; | |
67 | break; | |
68 | case 3: | |
69 | audio_name = "GSM/8000/1"; | |
70 | break; | |
71 | case 8: | |
72 | audio_name = "PCMA/8000/1"; | |
73 | break; | |
74 | case 18: | |
75 | audio_name = "G729/8000/1"; | |
76 | break; | |
77 | default: | |
78 | /* Payload type is unknown, don't change rate and | |
79 | * channels. */ | |
80 | /* TODO: return value? */ | |
81 | return 0; | |
82 | } | |
83 | } | |
84 | ||
85 | if (sscanf(audio_name, "%63[^/]/%d/%d", | |
86 | audio_codec, &rate, &channels) < 1) | |
87 | return -EINVAL; | |
88 | ||
89 | codec->rate = rate; | |
90 | codec->channels = channels; | |
91 | codec->subtype_name = talloc_strdup(ctx, audio_codec); | |
92 | codec->audio_name = talloc_strdup(ctx, audio_name); | |
93 | ||
94 | if (!strcmp(audio_codec, "G729")) { | |
95 | codec->frame_duration_num = 10; | |
96 | codec->frame_duration_den = 1000; | |
97 | } else { | |
98 | codec->frame_duration_num = DEFAULT_RTP_AUDIO_FRAME_DUR_NUM; | |
99 | codec->frame_duration_den = DEFAULT_RTP_AUDIO_FRAME_DUR_DEN; | |
100 | } | |
101 | ||
102 | if (payload_type < 0) { | |
103 | payload_type = 96; | |
104 | if (rate == 8000 && channels == 1) { | |
105 | if (!strcmp(audio_codec, "GSM")) | |
106 | payload_type = 3; | |
107 | else if (!strcmp(audio_codec, "PCMA")) | |
108 | payload_type = 8; | |
109 | else if (!strcmp(audio_codec, "PCMU")) | |
110 | payload_type = 0; | |
111 | else if (!strcmp(audio_codec, "G729")) | |
112 | payload_type = 18; | |
113 | } | |
114 | ||
115 | codec->payload_type = payload_type; | |
116 | } | |
117 | ||
118 | if (channels != 1) | |
119 | LOGP(DLMGCP, LOGL_NOTICE, | |
120 | "Channels != 1 in SDP: '%s'\n", audio_name); | |
121 | ||
122 | return 0; | |
123 | } | |
124 | ||
46 | /* Helper function to extrapolate missing codec parameters in a codec mao from | |
47 | * an already filled in payload_type, called from: mgcp_parse_sdp_data() */ | |
125 | 48 | static void codecs_initialize(void *ctx, struct sdp_rtp_map *codecs, int used) |
126 | 49 | { |
127 | 50 | int i; |
148 | 71 | codecs[i].rate = 8000; |
149 | 72 | codecs[i].channels = 1; |
150 | 73 | break; |
151 | } | |
152 | } | |
153 | } | |
154 | ||
74 | default: | |
75 | codecs[i].codec_name = NULL; | |
76 | codecs[i].rate = 0; | |
77 | codecs[i].channels = 0; | |
78 | } | |
79 | } | |
80 | } | |
81 | ||
82 | /* Helper function to update codec map information with additional data from | |
83 | * SDP, called from: mgcp_parse_sdp_data() */ | |
155 | 84 | static void codecs_update(void *ctx, struct sdp_rtp_map *codecs, int used, |
156 | 85 | int payload, const char *audio_name) |
157 | 86 | { |
161 | 90 | char audio_codec[64]; |
162 | 91 | int rate = -1; |
163 | 92 | int channels = -1; |
93 | ||
94 | /* Note: We can only update payload codecs that already exist | |
95 | * in our codec list. If we get an unexpected payload type, | |
96 | * we just drop it */ | |
164 | 97 | if (codecs[i].payload_type != payload) |
165 | 98 | continue; |
99 | ||
166 | 100 | if (sscanf(audio_name, "%63[^/]/%d/%d", |
167 | 101 | audio_codec, &rate, &channels) < 1) { |
168 | 102 | LOGP(DLMGCP, LOGL_ERROR, "Failed to parse '%s'\n", |
181 | 115 | audio_name); |
182 | 116 | } |
183 | 117 | |
184 | /* Check if the codec matches what is set up in the trunk config */ | |
185 | static int is_codec_compatible(const struct mgcp_endpoint *endp, | |
186 | const struct sdp_rtp_map *codec) | |
187 | { | |
188 | char *codec_str; | |
189 | char audio_codec[64]; | |
190 | ||
191 | if (!codec->codec_name) | |
192 | return 0; | |
193 | ||
194 | /* GSM, GSM/8000 and GSM/8000/1 should all be compatible... | |
195 | * let's go by name first. */ | |
196 | codec_str = endp->tcfg->audio_name; | |
197 | if (sscanf(codec_str, "%63[^/]/%*d/%*d", audio_codec) < 1) | |
198 | return 0; | |
199 | ||
200 | return strcasecmp(audio_codec, codec->codec_name) == 0; | |
118 | /* Extract payload types from SDP, also check for duplicates */ | |
119 | static int pt_from_sdp(void *ctx, struct sdp_rtp_map *codecs, | |
120 | unsigned int codecs_len, char *sdp) | |
121 | { | |
122 | char *str; | |
123 | char *str_ptr; | |
124 | char *pt_str; | |
125 | unsigned int pt; | |
126 | unsigned int count = 0; | |
127 | unsigned int i; | |
128 | ||
129 | str = talloc_zero_size(ctx, strlen(sdp) + 1); | |
130 | str_ptr = str; | |
131 | strcpy(str_ptr, sdp); | |
132 | ||
133 | str_ptr = strstr(str_ptr, "RTP/AVP "); | |
134 | if (!str_ptr) | |
135 | goto exit; | |
136 | ||
137 | pt_str = strtok(str_ptr, " "); | |
138 | if (!pt_str) | |
139 | goto exit; | |
140 | ||
141 | while (1) { | |
142 | /* Do not allow excessive payload types */ | |
143 | if (count > codecs_len) | |
144 | goto error; | |
145 | ||
146 | pt_str = strtok(NULL, " "); | |
147 | if (!pt_str) | |
148 | break; | |
149 | ||
150 | pt = atoi(pt_str); | |
151 | ||
152 | /* Do not allow duplicate payload types */ | |
153 | for (i = 0; i < count; i++) | |
154 | if (codecs[i].payload_type == pt) | |
155 | goto error; | |
156 | ||
157 | codecs[count].payload_type = pt; | |
158 | count++; | |
159 | } | |
160 | ||
161 | exit: | |
162 | talloc_free(str); | |
163 | return count; | |
164 | error: | |
165 | talloc_free(str); | |
166 | return -EINVAL; | |
201 | 167 | } |
202 | 168 | |
203 | 169 | /*! Analyze SDP input string. |
204 | 170 | * \param[in] endp trunk endpoint. |
205 | 171 | * \param[out] conn associated rtp connection. |
206 | 172 | * \param[out] caller provided memory to store the parsing results. |
207 | * \returns 0 on success, -1 on failure. | |
208 | 173 | * |
209 | 174 | * Note: In conn (conn->end) the function returns the packet duration, |
210 | * the rtp port and the rtcp port */ | |
175 | * rtp port, rtcp port and the codec information. | |
176 | * \returns 0 on success, -1 on failure. */ | |
211 | 177 | int mgcp_parse_sdp_data(const struct mgcp_endpoint *endp, |
212 | struct mgcp_conn_rtp *conn, | |
213 | struct mgcp_parse_data *p) | |
214 | { | |
215 | struct sdp_rtp_map codecs[10]; | |
216 | int codecs_used = 0; | |
178 | struct mgcp_conn_rtp *conn, struct mgcp_parse_data *p) | |
179 | { | |
180 | struct sdp_rtp_map codecs[MGCP_MAX_CODECS]; | |
181 | unsigned int codecs_used = 0; | |
217 | 182 | char *line; |
218 | int maxptime = -1; | |
219 | int i; | |
220 | int codecs_assigned = 0; | |
183 | unsigned int i; | |
221 | 184 | void *tmp_ctx = talloc_new(NULL); |
222 | 185 | struct mgcp_rtp_end *rtp; |
223 | 186 | |
254 | 217 | rtp->packet_duration_ms = 0; |
255 | 218 | else |
256 | 219 | rtp->packet_duration_ms = ptime; |
257 | } else if (sscanf(line, "a=maxptime:%d", &ptime2) | |
258 | == 1) { | |
259 | maxptime = ptime2; | |
220 | } else if (sscanf(line, "a=maxptime:%d", &ptime2) == 1) { | |
221 | rtp->maximum_packet_time = ptime2; | |
260 | 222 | } |
261 | 223 | break; |
262 | 224 | case 'm': |
263 | rc = sscanf(line, | |
264 | "m=audio %d RTP/AVP %d %d %d %d %d %d %d %d %d %d", | |
265 | &port, &codecs[0].payload_type, | |
266 | &codecs[1].payload_type, | |
267 | &codecs[2].payload_type, | |
268 | &codecs[3].payload_type, | |
269 | &codecs[4].payload_type, | |
270 | &codecs[5].payload_type, | |
271 | &codecs[6].payload_type, | |
272 | &codecs[7].payload_type, | |
273 | &codecs[8].payload_type, | |
274 | &codecs[9].payload_type); | |
275 | if (rc >= 2) { | |
225 | rc = sscanf(line, "m=audio %d RTP/AVP", &port); | |
226 | if (rc == 1) { | |
276 | 227 | rtp->rtp_port = htons(port); |
277 | 228 | rtp->rtcp_port = htons(port + 1); |
278 | codecs_used = rc - 1; | |
279 | codecs_initialize(tmp_ctx, codecs, codecs_used); | |
280 | 229 | } |
230 | ||
231 | rc = pt_from_sdp(conn->conn, codecs, | |
232 | ARRAY_SIZE(codecs), line); | |
233 | if (rc > 0) | |
234 | codecs_used = rc; | |
281 | 235 | break; |
282 | 236 | case 'c': |
283 | 237 | |
298 | 252 | break; |
299 | 253 | } |
300 | 254 | } |
301 | ||
302 | /* Now select the primary and alt_codec */ | |
303 | for (i = 0; i < codecs_used && codecs_assigned < 2; ++i) { | |
304 | struct mgcp_rtp_codec *codec = codecs_assigned == 0 ? | |
305 | &rtp->codec : &rtp->alt_codec; | |
306 | ||
307 | if (endp->tcfg->no_audio_transcoding && | |
308 | !is_codec_compatible(endp, &codecs[i])) { | |
309 | LOGP(DLMGCP, LOGL_NOTICE, "Skipping codec %s\n", | |
310 | codecs[i].codec_name); | |
311 | continue; | |
312 | } | |
313 | ||
314 | mgcp_set_audio_info(p->cfg, codec, | |
315 | codecs[i].payload_type, codecs[i].map_line); | |
316 | codecs_assigned += 1; | |
317 | } | |
318 | ||
319 | if (codecs_assigned > 0) { | |
320 | /* TODO/XXX: Store this per codec and derive it on use */ | |
321 | if (maxptime >= 0 && maxptime * rtp->codec.frame_duration_den > | |
322 | rtp->codec.frame_duration_num * 1500) { | |
323 | /* more than 1 frame */ | |
324 | rtp->packet_duration_ms = 0; | |
325 | } | |
326 | ||
327 | LOGP(DLMGCP, LOGL_NOTICE, | |
328 | "Got media info via SDP: port %d, payload %d (%s), " | |
329 | "duration %d, addr %s\n", | |
330 | ntohs(rtp->rtp_port), rtp->codec.payload_type, | |
331 | rtp->codec.subtype_name ? rtp-> | |
332 | codec.subtype_name : "unknown", rtp->packet_duration_ms, | |
333 | inet_ntoa(rtp->addr)); | |
255 | OSMO_ASSERT(codecs_used <= MGCP_MAX_CODECS); | |
256 | ||
257 | /* So far we have only set the payload type in the codec struct. Now we | |
258 | * fill up the remaining fields of the codec description with some default | |
259 | * information */ | |
260 | codecs_initialize(tmp_ctx, codecs, codecs_used); | |
261 | ||
262 | /* Store parsed codec information */ | |
263 | for (i = 0; i < codecs_used; i++) { | |
264 | rc = mgcp_codec_add(conn, codecs[i].payload_type, codecs[i].map_line); | |
265 | if (rc < 0) | |
266 | LOGP(DLMGCP, LOGL_NOTICE, "endpoint:0x%x, failed to add codec\n", ENDPOINT_NUMBER(p->endp)); | |
334 | 267 | } |
335 | 268 | |
336 | 269 | talloc_free(tmp_ctx); |
337 | return codecs_assigned > 0; | |
270 | ||
271 | LOGP(DLMGCP, LOGL_NOTICE, | |
272 | "Got media info via SDP: port:%d, addr:%s, duration:%d, payload-types:", | |
273 | ntohs(rtp->rtp_port), inet_ntoa(rtp->addr), | |
274 | rtp->packet_duration_ms); | |
275 | if (codecs_used == 0) | |
276 | LOGPC(DLMGCP, LOGL_NOTICE, "none"); | |
277 | for (i = 0; i < codecs_used; i++) { | |
278 | LOGPC(DLMGCP, LOGL_NOTICE, "%d=%s", | |
279 | rtp->codecs[i].payload_type, | |
280 | rtp->codecs[i].subtype_name ? rtp-> codecs[i].subtype_name : "unknown"); | |
281 | LOGPC(DLMGCP, LOGL_NOTICE, " "); | |
282 | } | |
283 | LOGPC(DLMGCP, LOGL_NOTICE, "\n"); | |
284 | ||
285 | return 0; | |
338 | 286 | } |
339 | 287 | |
340 | 288 | /*! Generate SDP response string. |
379 | 327 | if (rc < 0) |
380 | 328 | goto buffer_too_small; |
381 | 329 | |
382 | if (audio_name && endp->tcfg->audio_send_name) { | |
330 | /* FIXME: Check if the payload type is from the static range, | |
331 | * if yes, omitthe a=rtpmap since it is unnecessary */ | |
332 | if (audio_name && endp->tcfg->audio_send_name && (payload_type >= 96 && payload_type <= 127)) { | |
383 | 333 | rc = msgb_printf(sdp, "a=rtpmap:%d %s\r\n", |
384 | 334 | payload_type, audio_name); |
385 | 335 |
24 | 24 | #include <osmocom/mgcp/mgcp_stat.h> |
25 | 25 | #include <osmocom/mgcp/mgcp_endp.h> |
26 | 26 | #include <limits.h> |
27 | #include <inttypes.h> | |
27 | 28 | |
28 | 29 | /* Helper function for mgcp_format_stats_rtp() to calculate packet loss */ |
29 | void calc_loss(struct mgcp_rtp_state *state, | |
30 | struct mgcp_rtp_end *end, uint32_t *expected, | |
31 | int *loss) | |
30 | void calc_loss(struct mgcp_conn_rtp *conn, uint32_t *expected, int *loss) | |
32 | 31 | { |
32 | struct mgcp_rtp_state *state = &conn->state; | |
33 | struct rate_ctr *packets_rx = &conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]; | |
34 | ||
33 | 35 | *expected = state->stats.cycles + state->stats.max_seq; |
34 | 36 | *expected = *expected - state->stats.base_seq + 1; |
35 | 37 | |
43 | 45 | * Make sure the sign is correct and use the biggest |
44 | 46 | * positive/negative number that fits. |
45 | 47 | */ |
46 | *loss = *expected - end->stats.packets_rx; | |
47 | if (*expected < end->stats.packets_rx) { | |
48 | *loss = *expected - packets_rx->current; | |
49 | if (*expected < packets_rx->current) { | |
48 | 50 | if (*loss > 0) |
49 | 51 | *loss = INT_MIN; |
50 | 52 | } else { |
69 | 71 | int ploss; |
70 | 72 | int nchars; |
71 | 73 | |
72 | calc_loss(&conn->state, &conn->end, &expected, &ploss); | |
74 | struct rate_ctr *packets_rx = &conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]; | |
75 | struct rate_ctr *octets_rx = &conn->rate_ctr_group->ctr[RTP_OCTETS_RX_CTR]; | |
76 | struct rate_ctr *packets_tx = &conn->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]; | |
77 | struct rate_ctr *octets_tx = &conn->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR]; | |
78 | ||
79 | calc_loss(conn, &expected, &ploss); | |
73 | 80 | jitter = calc_jitter(&conn->state); |
74 | 81 | |
75 | 82 | nchars = snprintf(str, str_len, |
76 | "\r\nP: PS=%u, OS=%u, PR=%u, OR=%u, PL=%d, JI=%u", | |
77 | conn->end.stats.packets_tx, conn->end.stats.octets_tx, | |
78 | conn->end.stats.packets_rx, conn->end.stats.octets_rx, | |
83 | "\r\nP: PS=%" PRIu64 ", OS=%" PRIu64 ", PR=%" PRIu64 ", OR=%" PRIu64 ", PL=%d, JI=%u", | |
84 | packets_tx->current, octets_tx->current, | |
85 | packets_rx->current, octets_rx->current, | |
79 | 86 | ploss, jitter); |
80 | 87 | if (nchars < 0 || nchars >= str_len) |
81 | 88 | goto truncate; |
86 | 93 | if (conn->conn->endp->cfg->osmux != OSMUX_USAGE_OFF) { |
87 | 94 | /* Error Counter */ |
88 | 95 | nchars = snprintf(str, str_len, |
89 | "\r\nX-Osmo-CP: EC TI=%u, TO=%u", | |
90 | conn->state.in_stream.err_ts_counter, | |
91 | conn->state.out_stream.err_ts_counter); | |
96 | "\r\nX-Osmo-CP: EC TI=%" PRIu64 ", TO=%" PRIu64, | |
97 | conn->state.in_stream.err_ts_ctr->current, | |
98 | conn->state.out_stream.err_ts_ctr->current); | |
92 | 99 | if (nchars < 0 || nchars >= str_len) |
93 | 100 | goto truncate; |
94 | 101 |
29 | 29 | #include <osmocom/mgcp/mgcp_endp.h> |
30 | 30 | |
31 | 31 | #include <string.h> |
32 | #include <inttypes.h> | |
32 | 33 | |
33 | 34 | #define RTCP_OMIT_STR "Drop RTCP packets in both directions\n" |
34 | 35 | #define RTP_PATCH_STR "Modify RTP packet header in both directions\n" |
153 | 154 | return CMD_SUCCESS; |
154 | 155 | } |
155 | 156 | |
156 | static void dump_rtp_end(struct vty *vty, struct mgcp_rtp_state *state, | |
157 | struct mgcp_rtp_end *end) | |
158 | { | |
159 | struct mgcp_rtp_codec *codec = &end->codec; | |
157 | static void dump_rtp_end(struct vty *vty, struct mgcp_conn_rtp *conn) | |
158 | { | |
159 | struct mgcp_rtp_state *state = &conn->state; | |
160 | struct mgcp_rtp_end *end = &conn->end; | |
161 | struct mgcp_rtp_codec *codec = end->codec; | |
162 | struct rate_ctr *dropped_packets; | |
163 | ||
164 | dropped_packets = &conn->rate_ctr_group->ctr[RTP_DROPPED_PACKETS_CTR]; | |
160 | 165 | |
161 | 166 | vty_out(vty, |
162 | " Timestamp Errs: %d->%d%s" | |
163 | " Dropped Packets: %d%s" | |
167 | " Timestamp Errs: %" PRIu64 "->%" PRIu64 "%s" | |
168 | " Dropped Packets: %" PRIu64 "%s" | |
164 | 169 | " Payload Type: %d Rate: %u Channels: %d %s" |
165 | 170 | " Frame Duration: %u Frame Denominator: %u%s" |
166 | 171 | " FPP: %d Packet Duration: %u%s" |
167 | 172 | " FMTP-Extra: %s Audio-Name: %s Sub-Type: %s%s" |
168 | 173 | " Output-Enabled: %d Force-PTIME: %d%s", |
169 | state->in_stream.err_ts_counter, | |
170 | state->out_stream.err_ts_counter, VTY_NEWLINE, | |
171 | end->stats.dropped_packets, VTY_NEWLINE, | |
174 | state->in_stream.err_ts_ctr->current, | |
175 | state->out_stream.err_ts_ctr->current, | |
176 | VTY_NEWLINE, | |
177 | dropped_packets->current, VTY_NEWLINE, | |
172 | 178 | codec->payload_type, codec->rate, codec->channels, VTY_NEWLINE, |
173 | 179 | codec->frame_duration_num, codec->frame_duration_den, |
174 | 180 | VTY_NEWLINE, end->frames_per_packet, end->packet_duration_ms, |
206 | 212 | * connection types (E1) as soon as |
207 | 213 | * the implementation is available */ |
208 | 214 | if (conn->type == MGCP_CONN_TYPE_RTP) { |
209 | dump_rtp_end(vty, &conn->u.rtp.state, | |
210 | &conn->u.rtp.end); | |
215 | dump_rtp_end(vty, &conn->u.rtp); | |
211 | 216 | } |
212 | 217 | } |
213 | 218 | } |
280 | 285 | return CMD_WARNING; |
281 | 286 | } |
282 | 287 | |
283 | static void parse_range(struct mgcp_port_range *range, const char **argv) | |
284 | { | |
285 | range->range_start = atoi(argv[0]); | |
286 | range->range_end = atoi(argv[1]); | |
287 | range->last_port = g_cfg->net_ports.range_start; | |
288 | } | |
289 | ||
290 | 288 | #define RTP_STR "RTP configuration\n" |
291 | 289 | #define UDP_PORT_STR "UDP Port number\n" |
292 | 290 | #define NET_START_STR "First UDP port allocated\n" |
295 | 293 | |
296 | 294 | DEFUN(cfg_mgcp_rtp_port_range, |
297 | 295 | cfg_mgcp_rtp_port_range_cmd, |
298 | "rtp port-range <0-65534> <0-65534>", | |
296 | "rtp port-range <1024-65534> <1025-65535>", | |
299 | 297 | RTP_STR "Range of ports to use for the NET side\n" |
300 | 298 | RANGE_START_STR RANGE_END_STR) |
301 | 299 | { |
302 | parse_range(&g_cfg->net_ports, argv); | |
300 | int start; | |
301 | int end; | |
302 | ||
303 | start = atoi(argv[0]); | |
304 | end = atoi(argv[1]); | |
305 | ||
306 | if (end < start) { | |
307 | vty_out(vty, "range end port (%i) must be greater than the range start port (%i)!%s", | |
308 | end, start, VTY_NEWLINE); | |
309 | return CMD_WARNING; | |
310 | } | |
311 | ||
312 | if (start & 1) { | |
313 | vty_out(vty, "range must begin at an even port number, autocorrecting port (%i) to: %i%s", | |
314 | start, start & 0xFFFE, VTY_NEWLINE); | |
315 | start &= 0xFFFE; | |
316 | } | |
317 | ||
318 | if ((end & 1) == 0) { | |
319 | vty_out(vty, "range must end at an odd port number, autocorrecting port (%i) to: %i%s", | |
320 | end, end | 1, VTY_NEWLINE); | |
321 | end |= 1; | |
322 | } | |
323 | ||
324 | g_cfg->net_ports.range_start = start; | |
325 | g_cfg->net_ports.range_end = end; | |
326 | g_cfg->net_ports.last_port = g_cfg->net_ports.range_start; | |
327 | ||
303 | 328 | return CMD_SUCCESS; |
304 | 329 | } |
305 | 330 | ALIAS_DEPRECATED(cfg_mgcp_rtp_port_range, |
19 | 19 | |
20 | 20 | # This is not at all related to the release version, but a range of supported |
21 | 21 | # API versions. Read TODO_RELEASE in the source tree's root! |
22 | MGCP_CLIENT_LIBVERSION=3:0:0 | |
22 | MGCP_CLIENT_LIBVERSION=4:0:1 | |
23 | 23 | |
24 | 24 | lib_LTLIBRARIES = \ |
25 | 25 | libosmo-mgcp-client.la \ |
35 | 35 | #include <unistd.h> |
36 | 36 | #include <string.h> |
37 | 37 | |
38 | /* Codec descripton for dynamic payload types (SDP) */ | |
39 | static const struct value_string codec_table[] = { | |
40 | { CODEC_PCMU_8000_1, "PCMU/8000/1" }, | |
41 | { CODEC_GSM_8000_1, "GSM/8000/1" }, | |
42 | { CODEC_PCMA_8000_1, "PCMA/8000/1" }, | |
43 | { CODEC_G729_8000_1, "G729/8000/1" }, | |
44 | { CODEC_GSMEFR_8000_1, "GSM-EFR/8000/1" }, | |
45 | { CODEC_GSMHR_8000_1, "GSM-HR-08/8000/1" }, | |
46 | { CODEC_AMR_8000_1, "AMR/8000/1" }, | |
47 | { CODEC_AMRWB_16000_1, "AMR-WB/16000/1" }, | |
48 | { 0, NULL }, | |
49 | }; | |
50 | ||
51 | /* Get encoding name from a full codec string e,g. | |
52 | * ("CODEC/8000/2" => returns "CODEC") */ | |
53 | static char *extract_codec_name(const char *str) | |
54 | { | |
55 | static char buf[64]; | |
56 | unsigned int i; | |
57 | ||
58 | if (!str) | |
59 | return NULL; | |
60 | ||
61 | /* FIXME osmo_strlcpy */ | |
62 | osmo_strlcpy(buf, str, sizeof(buf)); | |
63 | ||
64 | for (i = 0; i < strlen(buf); i++) { | |
65 | if (buf[i] == '/') | |
66 | buf[i] = '\0'; | |
67 | } | |
68 | ||
69 | return buf; | |
70 | } | |
71 | ||
72 | /*! Map a string to a codec. | |
73 | * \ptmap[in] str input string (e.g "GSM/8000/1", "GSM/8000" or "GSM") | |
74 | * \returns codec that corresponds to the given string representation. */ | |
75 | enum mgcp_codecs map_str_to_codec(const char *str) | |
76 | { | |
77 | unsigned int i; | |
78 | char *codec_name; | |
79 | char str_buf[64]; | |
80 | ||
81 | osmo_strlcpy(str_buf, extract_codec_name(str), sizeof(str_buf)); | |
82 | ||
83 | for (i = 0; i < ARRAY_SIZE(codec_table); i++) { | |
84 | codec_name = extract_codec_name(codec_table[i].str); | |
85 | if (!codec_name) | |
86 | continue; | |
87 | if (strcmp(codec_name, str_buf) == 0) | |
88 | return codec_table[i].value; | |
89 | } | |
90 | ||
91 | return -1; | |
92 | } | |
93 | ||
94 | /* Check the ptmap for illegal mappings */ | |
95 | static int check_ptmap(struct ptmap *ptmap) | |
96 | { | |
97 | /* Check if there are mappings that leave the IANA assigned dynamic | |
98 | * payload type range. Under normal conditions such mappings should | |
99 | * not occur */ | |
100 | ||
101 | /* Its ok to have a 1:1 mapping in the statically defined | |
102 | * range, this won't hurt */ | |
103 | if (ptmap->codec == ptmap->pt) | |
104 | return 0; | |
105 | ||
106 | if (ptmap->codec < 96 || ptmap->codec > 127) | |
107 | goto error; | |
108 | if (ptmap->pt < 96 || ptmap->pt > 127) | |
109 | goto error; | |
110 | ||
111 | return 0; | |
112 | error: | |
113 | LOGP(DLMGCP, LOGL_ERROR, | |
114 | "ptmap contains illegal mapping: codec=%u maps to pt=%u\n", | |
115 | ptmap->codec, ptmap->pt); | |
116 | return -1; | |
117 | } | |
118 | ||
119 | /*! Map a codec to a payload type. | |
120 | * \ptmap[in] payload pointer to payload type map with specified payload types. | |
121 | * \ptmap[in] ptmap_len length of the payload type map. | |
122 | * \ptmap[in] codec the codec for which the payload type should be looked up. | |
123 | * \returns assigned payload type */ | |
124 | unsigned int map_codec_to_pt(struct ptmap *ptmap, unsigned int ptmap_len, | |
125 | enum mgcp_codecs codec) | |
126 | { | |
127 | unsigned int i; | |
128 | ||
129 | /*! Note: If the payload type map is empty or the codec is not found | |
130 | * in the map, then a 1:1 mapping is performed. If the codec falls | |
131 | * into the statically defined range or if the mapping table isself | |
132 | * tries to map to the statically defined range, then the mapping | |
133 | * is also ignored and a 1:1 mapping is performed instead. */ | |
134 | ||
135 | /* we may return the codec directly since enum mgcp_codecs directly | |
136 | * corresponds to the statićally assigned payload types */ | |
137 | if (codec < 96 || codec > 127) | |
138 | return codec; | |
139 | ||
140 | for (i = 0; i < ptmap_len; i++) { | |
141 | /* Skip illegal map entries */ | |
142 | if (check_ptmap(ptmap) == 0 && ptmap->codec == codec) | |
143 | return ptmap->pt; | |
144 | ptmap++; | |
145 | } | |
146 | ||
147 | /* If nothing is found, do not perform any mapping */ | |
148 | return codec; | |
149 | } | |
150 | ||
151 | /*! Map a payload type to a codec. | |
152 | * \ptmap[in] payload pointer to payload type map with specified payload types. | |
153 | * \ptmap[in] ptmap_len length of the payload type map. | |
154 | * \ptmap[in] payload type for which the codec should be looked up. | |
155 | * \returns codec that corresponds to the specified payload type */ | |
156 | enum mgcp_codecs map_pt_to_codec(struct ptmap *ptmap, unsigned int ptmap_len, | |
157 | unsigned int pt) | |
158 | { | |
159 | unsigned int i; | |
160 | ||
161 | /*! Note: If the payload type map is empty or the payload type is not | |
162 | * found in the map, then a 1:1 mapping is performed. If the payload | |
163 | * type falls into the statically defined range or if the mapping | |
164 | * table isself tries to map to the statically defined range, then | |
165 | * the mapping is also ignored and a 1:1 mapping is performed | |
166 | * instead. */ | |
167 | ||
168 | /* See also note in map_codec_to_pt() */ | |
169 | if (pt < 96 || pt > 127) | |
170 | return pt; | |
171 | ||
172 | for (i = 0; i < ptmap_len; i++) { | |
173 | if (check_ptmap(ptmap) == 0 && ptmap->pt == pt) | |
174 | return ptmap->codec; | |
175 | ptmap++; | |
176 | } | |
177 | ||
178 | /* If nothing is found, do not perform any mapping */ | |
179 | return pt; | |
180 | } | |
181 | ||
38 | 182 | /*! Initalize MGCP client configuration struct with default values. |
39 | 183 | * \param[out] conf Client configuration.*/ |
40 | 184 | void mgcp_client_conf_init(struct mgcp_client_conf *conf) |
177 | 321 | return true; |
178 | 322 | } |
179 | 323 | |
180 | /* Parse a line like "m=audio 16002 RTP/AVP 98" */ | |
181 | static int mgcp_parse_audio_port(struct mgcp_response *r, const char *line) | |
182 | { | |
183 | if (sscanf(line, "m=audio %hu", | |
184 | &r->audio_port) != 1) | |
185 | goto response_parse_failure; | |
186 | ||
324 | /* Parse a line like "m=audio 16002 RTP/AVP 98", extract port and payload types */ | |
325 | static int mgcp_parse_audio_port_pt(struct mgcp_response *r, char *line) | |
326 | { | |
327 | char *pt_str; | |
328 | unsigned int pt; | |
329 | unsigned int count = 0; | |
330 | unsigned int i; | |
331 | ||
332 | /* Extract port information */ | |
333 | if (sscanf(line, "m=audio %hu", &r->audio_port) != 1) | |
334 | goto response_parse_failure_port; | |
187 | 335 | if (r->audio_port == 0) |
188 | goto response_parse_failure; | |
189 | ||
336 | goto response_parse_failure_port; | |
337 | ||
338 | /* Extract payload types */ | |
339 | line = strstr(line, "RTP/AVP "); | |
340 | if (!line) | |
341 | goto exit; | |
342 | ||
343 | pt_str = strtok(line, " "); | |
344 | while (1) { | |
345 | /* Do not allow excessive payload types */ | |
346 | if (count > ARRAY_SIZE(r->codecs)) | |
347 | goto response_parse_failure_pt; | |
348 | ||
349 | pt_str = strtok(NULL, " "); | |
350 | if (!pt_str) | |
351 | break; | |
352 | pt = atoi(pt_str); | |
353 | ||
354 | /* Do not allow duplicate payload types */ | |
355 | for (i = 0; i < count; i++) | |
356 | if (r->codecs[i] == pt) | |
357 | goto response_parse_failure_pt; | |
358 | ||
359 | /* Note: The payload type we store may not necessarly match | |
360 | * the codec types we have defined in enum mgcp_codecs. To | |
361 | * ensure that the end result only contains codec types which | |
362 | * match enum mgcp_codecs, we will go through afterwards and | |
363 | * remap the affected entries with the inrofmation we learn | |
364 | * from rtpmap */ | |
365 | r->codecs[count] = pt; | |
366 | count++; | |
367 | } | |
368 | ||
369 | r->codecs_len = count; | |
370 | ||
371 | exit: | |
190 | 372 | return 0; |
191 | 373 | |
192 | response_parse_failure: | |
374 | response_parse_failure_port: | |
193 | 375 | LOGP(DLMGCP, LOGL_ERROR, |
194 | "Failed to parse MGCP response header (audio port)\n"); | |
376 | "Failed to parse SDP parameter port (%s)\n", line); | |
195 | 377 | return -EINVAL; |
378 | ||
379 | response_parse_failure_pt: | |
380 | LOGP(DLMGCP, LOGL_ERROR, | |
381 | "Failed to parse SDP parameter payload types (%s)\n", line); | |
382 | return -EINVAL; | |
383 | } | |
384 | ||
385 | /* Parse a line like "m=audio 16002 RTP/AVP 98", extract port and payload types */ | |
386 | static int mgcp_parse_audio_ptime_rtpmap(struct mgcp_response *r, const char *line) | |
387 | { | |
388 | unsigned int pt; | |
389 | char codec_resp[64]; | |
390 | unsigned int codec; | |
391 | ||
392 | ||
393 | if (strstr(line, "ptime")) { | |
394 | if (sscanf(line, "a=ptime:%u", &r->ptime) != 1) | |
395 | goto response_parse_failure_ptime; | |
396 | } else if (strstr(line, "rtpmap")) { | |
397 | if (sscanf(line, "a=rtpmap:%d %63s", &pt, codec_resp) == 2) { | |
398 | /* The MGW may assign an own payload type in the | |
399 | * response if the choosen codec falls into the IANA | |
400 | * assigned dynamic payload type range (96-127). | |
401 | * Normally the MGW should obey the 3gpp payload type | |
402 | * assignments, which are fixed, so we likely wont see | |
403 | * anything unexpected here. In order to be sure that | |
404 | * we will now check the codec string and if the result | |
405 | * does not match to what is IANA / 3gpp assigned, we | |
406 | * will create an entry in the ptmap table so we can | |
407 | * lookup later what has been assigned. */ | |
408 | codec = map_str_to_codec(codec_resp); | |
409 | if (codec != pt) { | |
410 | if (r->ptmap_len < ARRAY_SIZE(r->ptmap)) { | |
411 | r->ptmap[r->ptmap_len].pt = pt; | |
412 | r->ptmap[r->ptmap_len].codec = codec; | |
413 | r->ptmap_len++; | |
414 | } else | |
415 | goto response_parse_failure_rtpmap; | |
416 | } | |
417 | ||
418 | } else | |
419 | goto response_parse_failure_rtpmap; | |
420 | } | |
421 | ||
422 | return 0; | |
423 | ||
424 | response_parse_failure_ptime: | |
425 | LOGP(DLMGCP, LOGL_ERROR, | |
426 | "Failed to parse SDP parameter, invalid ptime (%s)\n", line); | |
427 | return -EINVAL; | |
428 | response_parse_failure_rtpmap: | |
429 | LOGP(DLMGCP, LOGL_ERROR, | |
430 | "Failed to parse SDP parameter, invalid rtpmap (%s)\n", line); | |
431 | return -EINVAL; | |
196 | 432 | } |
197 | 433 | |
198 | 434 | /* Parse a line like "c=IN IP4 10.11.12.13" */ |
252 | 488 | int rc; |
253 | 489 | char *data; |
254 | 490 | char *data_ptr; |
491 | int i; | |
255 | 492 | |
256 | 493 | /* Since this functions performs a destructive parsing, we create a |
257 | 494 | * local copy of the body data */ |
276 | 513 | return -EINVAL; |
277 | 514 | |
278 | 515 | switch (line[0]) { |
516 | case 'a': | |
517 | rc = mgcp_parse_audio_ptime_rtpmap(r, line); | |
518 | if (rc) | |
519 | goto exit; | |
520 | break; | |
279 | 521 | case 'm': |
280 | rc = mgcp_parse_audio_port(r, line); | |
522 | rc = mgcp_parse_audio_port_pt(r, line); | |
281 | 523 | if (rc) |
282 | 524 | goto exit; |
283 | 525 | break; |
291 | 533 | break; |
292 | 534 | } |
293 | 535 | } |
536 | ||
537 | /* See also note in mgcp_parse_audio_port_pt() */ | |
538 | for (i = 0; i < r->codecs_len; i++) | |
539 | r->codecs[i] = map_pt_to_codec(r->ptmap, r->ptmap_len, r->codecs[i]); | |
294 | 540 | |
295 | 541 | rc = 0; |
296 | 542 | exit: |
812 | 1058 | #define MGCP_AUEP_MANDATORY (MGCP_MSG_PRESENCE_ENDPOINT) |
813 | 1059 | #define MGCP_RSIP_MANDATORY 0 /* none */ |
814 | 1060 | |
1061 | /* Helper function for mgcp_msg_gen(): Add LCO information to MGCP message */ | |
1062 | static int add_lco(struct msgb *msg, struct mgcp_msg *mgcp_msg) | |
1063 | { | |
1064 | unsigned int i; | |
1065 | int rc = 0; | |
1066 | const char *codec; | |
1067 | unsigned int pt; | |
1068 | ||
1069 | rc += msgb_printf(msg, "L:"); | |
1070 | ||
1071 | if (mgcp_msg->ptime) | |
1072 | rc += msgb_printf(msg, " p:%u,", mgcp_msg->ptime); | |
1073 | ||
1074 | if (mgcp_msg->codecs_len) { | |
1075 | rc += msgb_printf(msg, " a:"); | |
1076 | for (i = 0; i < mgcp_msg->codecs_len; i++) { | |
1077 | pt = mgcp_msg->codecs[i]; | |
1078 | codec = get_value_string_or_null(codec_table, pt); | |
1079 | ||
1080 | /* Note: Use codec descriptors from enum mgcp_codecs | |
1081 | * in mgcp_client only! */ | |
1082 | OSMO_ASSERT(codec); | |
1083 | rc += msgb_printf(msg, "%s", extract_codec_name(codec)); | |
1084 | if (i < mgcp_msg->codecs_len - 1) | |
1085 | rc += msgb_printf(msg, ";"); | |
1086 | } | |
1087 | rc += msgb_printf(msg, ","); | |
1088 | } | |
1089 | ||
1090 | rc += msgb_printf(msg, " nt:IN\r\n"); | |
1091 | ||
1092 | return rc; | |
1093 | } | |
1094 | ||
1095 | /* Helper function for mgcp_msg_gen(): Add SDP information to MGCP message */ | |
1096 | static int add_sdp(struct msgb *msg, struct mgcp_msg *mgcp_msg, struct mgcp_client *mgcp) | |
1097 | { | |
1098 | unsigned int i; | |
1099 | int rc = 0; | |
1100 | char local_ip[INET_ADDRSTRLEN]; | |
1101 | const char *codec; | |
1102 | unsigned int pt; | |
1103 | ||
1104 | /* Add separator to mark the beginning of the SDP block */ | |
1105 | rc += msgb_printf(msg, "\r\n"); | |
1106 | ||
1107 | /* Add SDP protocol version */ | |
1108 | rc += msgb_printf(msg, "v=0\r\n"); | |
1109 | ||
1110 | /* Determine local IP-Address */ | |
1111 | if (osmo_sock_local_ip(local_ip, mgcp->actual.remote_addr) < 0) { | |
1112 | LOGP(DLMGCP, LOGL_ERROR, | |
1113 | "Could not determine local IP-Address!\n"); | |
1114 | msgb_free(msg); | |
1115 | return -2; | |
1116 | } | |
1117 | ||
1118 | /* Add owner/creator (SDP) */ | |
1119 | rc += msgb_printf(msg, "o=- %x 23 IN IP4 %s\r\n", | |
1120 | mgcp_msg->call_id, local_ip); | |
1121 | ||
1122 | /* Add session name (none) */ | |
1123 | rc += msgb_printf(msg, "s=-\r\n"); | |
1124 | ||
1125 | /* Add RTP address and port */ | |
1126 | if (mgcp_msg->audio_port == 0) { | |
1127 | LOGP(DLMGCP, LOGL_ERROR, | |
1128 | "Invalid port number, can not generate MGCP message\n"); | |
1129 | msgb_free(msg); | |
1130 | return -2; | |
1131 | } | |
1132 | if (strlen(mgcp_msg->audio_ip) <= 0) { | |
1133 | LOGP(DLMGCP, LOGL_ERROR, | |
1134 | "Empty ip address, can not generate MGCP message\n"); | |
1135 | msgb_free(msg); | |
1136 | return -2; | |
1137 | } | |
1138 | rc += msgb_printf(msg, "c=IN IP4 %s\r\n", mgcp_msg->audio_ip); | |
1139 | ||
1140 | /* Add time description, active time (SDP) */ | |
1141 | rc += msgb_printf(msg, "t=0 0\r\n"); | |
1142 | ||
1143 | rc += msgb_printf(msg, "m=audio %u RTP/AVP", mgcp_msg->audio_port); | |
1144 | for (i = 0; i < mgcp_msg->codecs_len; i++) { | |
1145 | pt = map_codec_to_pt(mgcp_msg->ptmap, mgcp_msg->ptmap_len, mgcp_msg->codecs[i]); | |
1146 | rc += msgb_printf(msg, " %u", pt); | |
1147 | ||
1148 | } | |
1149 | rc += msgb_printf(msg, "\r\n"); | |
1150 | ||
1151 | for (i = 0; i < mgcp_msg->codecs_len; i++) { | |
1152 | pt = map_codec_to_pt(mgcp_msg->ptmap, mgcp_msg->ptmap_len, mgcp_msg->codecs[i]); | |
1153 | ||
1154 | /* Note: Only dynamic payload type from the range 96-127 | |
1155 | * require to be explained further via rtpmap. All others | |
1156 | * are implcitly definedby the number in m=audio */ | |
1157 | if (pt >= 96 && pt <= 127) { | |
1158 | codec = get_value_string_or_null(codec_table, mgcp_msg->codecs[i]); | |
1159 | ||
1160 | /* Note: Use codec descriptors from enum mgcp_codecs | |
1161 | * in mgcp_client only! */ | |
1162 | OSMO_ASSERT(codec); | |
1163 | ||
1164 | rc += msgb_printf(msg, "a=rtpmap:%u %s\r\n", pt, codec); | |
1165 | } | |
1166 | } | |
1167 | ||
1168 | if (mgcp_msg->ptime) | |
1169 | rc += msgb_printf(msg, "a=ptime:%u\r\n", mgcp_msg->ptime); | |
1170 | ||
1171 | return rc; | |
1172 | } | |
1173 | ||
815 | 1174 | /*! Generate an MGCP message |
816 | 1175 | * \param[in] mgcp MGCP client descriptor. |
817 | 1176 | * \param[in] mgcp_msg Message description |
822 | 1181 | uint32_t mandatory_mask; |
823 | 1182 | struct msgb *msg = msgb_alloc_headroom(4096, 128, "MGCP tx"); |
824 | 1183 | int rc = 0; |
825 | char local_ip[INET_ADDRSTRLEN]; | |
1184 | int rc_sdp; | |
1185 | bool use_sdp = false; | |
826 | 1186 | |
827 | 1187 | msg->l2h = msg->data; |
828 | 1188 | msg->cb[MSGB_CB_MGCP_TRANS_ID] = trans_id; |
901 | 1261 | rc += msgb_printf(msg, "I: %s\r\n", mgcp_msg->conn_id); |
902 | 1262 | } |
903 | 1263 | |
904 | /* Add local connection options */ | |
905 | if (mgcp_msg->verb == MGCP_VERB_CRCX) | |
906 | rc += msgb_printf(msg, "L: p:20, a:AMR, nt:IN\r\n"); | |
1264 | /* Using SDP makes sense when a valid IP/Port combination is specifiec, | |
1265 | * if we do not know this information yet, we fall back to LCO */ | |
1266 | if (mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_IP | |
1267 | && mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_PORT) | |
1268 | use_sdp = true; | |
1269 | ||
1270 | /* Add local connection options (LCO) */ | |
1271 | if (!use_sdp | |
1272 | && (mgcp_msg->verb == MGCP_VERB_CRCX | |
1273 | || mgcp_msg->verb == MGCP_VERB_MDCX)) | |
1274 | rc += add_lco(msg, mgcp_msg); | |
907 | 1275 | |
908 | 1276 | /* Add mode */ |
909 | 1277 | if (mgcp_msg->presence & MGCP_MSG_PRESENCE_CONN_MODE) |
911 | 1279 | msgb_printf(msg, "M: %s\r\n", |
912 | 1280 | mgcp_client_cmode_name(mgcp_msg->conn_mode)); |
913 | 1281 | |
914 | /* Add SDP body */ | |
915 | if (mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_IP | |
916 | && mgcp_msg->presence & MGCP_MSG_PRESENCE_AUDIO_PORT) { | |
917 | ||
918 | /* Add separator to mark the beginning of the SDP block */ | |
919 | rc += msgb_printf(msg, "\r\n"); | |
920 | ||
921 | /* Add SDP protocol version */ | |
922 | rc += msgb_printf(msg, "v=0\r\n"); | |
923 | ||
924 | /* Determine local IP-Address */ | |
925 | if (osmo_sock_local_ip(local_ip, mgcp->actual.remote_addr) < 0) { | |
926 | LOGP(DLMGCP, LOGL_ERROR, | |
927 | "Could not determine local IP-Address!\n"); | |
928 | msgb_free(msg); | |
1282 | /* Add session description protocol (SDP) */ | |
1283 | if (use_sdp | |
1284 | && (mgcp_msg->verb == MGCP_VERB_CRCX | |
1285 | || mgcp_msg->verb == MGCP_VERB_MDCX)) { | |
1286 | rc_sdp = add_sdp(msg, mgcp_msg, mgcp); | |
1287 | if (rc_sdp == -2) | |
929 | 1288 | return NULL; |
930 | } | |
931 | ||
932 | /* Add owner/creator (SDP) */ | |
933 | rc += msgb_printf(msg, "o=- %x 23 IN IP4 %s\r\n", | |
934 | mgcp_msg->call_id, local_ip); | |
935 | ||
936 | /* Add session name (none) */ | |
937 | rc += msgb_printf(msg, "s=-\r\n"); | |
938 | ||
939 | /* Add RTP address and port */ | |
940 | if (mgcp_msg->audio_port == 0) { | |
941 | LOGP(DLMGCP, LOGL_ERROR, | |
942 | "Invalid port number, can not generate MGCP message\n"); | |
943 | msgb_free(msg); | |
944 | return NULL; | |
945 | } | |
946 | if (strlen(mgcp_msg->audio_ip) <= 0) { | |
947 | LOGP(DLMGCP, LOGL_ERROR, | |
948 | "Empty ip address, can not generate MGCP message\n"); | |
949 | msgb_free(msg); | |
950 | return NULL; | |
951 | } | |
952 | rc += msgb_printf(msg, "c=IN IP4 %s\r\n", mgcp_msg->audio_ip); | |
953 | ||
954 | /* Add time description, active time (SDP) */ | |
955 | rc += msgb_printf(msg, "t=0 0\r\n"); | |
956 | ||
957 | rc += | |
958 | msgb_printf(msg, "m=audio %u RTP/AVP 255\r\n", | |
959 | mgcp_msg->audio_port); | |
1289 | else | |
1290 | rc += rc_sdp; | |
960 | 1291 | } |
961 | 1292 | |
962 | 1293 | if (rc != 0) { |
111 | 111 | .verb = MGCP_VERB_CRCX, |
112 | 112 | .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | MGCP_MSG_PRESENCE_CONN_MODE), |
113 | 113 | .call_id = mgcp_ctx->conn_peer_local.call_id, |
114 | .conn_mode = MGCP_CONN_LOOPBACK, | |
114 | .conn_mode = MGCP_CONN_RECV_ONLY, | |
115 | .ptime = mgcp_ctx->conn_peer_local.ptime, | |
116 | .codecs_len = mgcp_ctx->conn_peer_local.codecs_len | |
115 | 117 | }; |
116 | 118 | osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_local.endpoint, MGCP_ENDPOINT_MAXLEN); |
119 | memcpy(mgcp_msg.codecs, mgcp_ctx->conn_peer_local.codecs, sizeof(mgcp_msg.codecs)); | |
117 | 120 | |
118 | 121 | return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg); |
119 | 122 | } |
123 | 126 | struct mgcp_msg mgcp_msg; |
124 | 127 | |
125 | 128 | mgcp_msg = (struct mgcp_msg) { |
126 | .verb = MGCP_VERB_CRCX,.presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | | |
127 | MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | | |
128 | MGCP_MSG_PRESENCE_AUDIO_PORT), | |
129 | .verb = MGCP_VERB_CRCX, | |
130 | .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | | |
131 | MGCP_MSG_PRESENCE_CONN_MODE | MGCP_MSG_PRESENCE_AUDIO_IP | | |
132 | MGCP_MSG_PRESENCE_AUDIO_PORT), | |
129 | 133 | .call_id = mgcp_ctx->conn_peer_local.call_id, |
130 | 134 | .conn_mode = MGCP_CONN_RECV_SEND, |
131 | 135 | .audio_ip = mgcp_ctx->conn_peer_local.addr, |
132 | 136 | .audio_port = mgcp_ctx->conn_peer_local.port, |
137 | .ptime = mgcp_ctx->conn_peer_local.ptime, | |
138 | .codecs_len = mgcp_ctx->conn_peer_local.codecs_len | |
133 | 139 | }; |
134 | 140 | osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_local.endpoint, MGCP_ENDPOINT_MAXLEN); |
141 | memcpy(mgcp_msg.codecs, mgcp_ctx->conn_peer_local.codecs, sizeof(mgcp_msg.codecs)); | |
135 | 142 | |
136 | 143 | return mgcp_msg_gen(mgcp_ctx->mgcp, &mgcp_msg); |
137 | 144 | } |
149 | 156 | .conn_mode = MGCP_CONN_RECV_SEND, |
150 | 157 | .audio_ip = mgcp_ctx->conn_peer_local.addr, |
151 | 158 | .audio_port = mgcp_ctx->conn_peer_local.port, |
159 | .ptime = mgcp_ctx->conn_peer_local.ptime, | |
160 | .codecs_len = mgcp_ctx->conn_peer_local.codecs_len | |
152 | 161 | }; |
153 | 162 | osmo_strlcpy(mgcp_msg.endpoint, mgcp_ctx->conn_peer_remote.endpoint, MGCP_ENDPOINT_MAXLEN); |
163 | memcpy(mgcp_msg.codecs, mgcp_ctx->conn_peer_local.codecs, sizeof(mgcp_msg.codecs)); | |
154 | 164 | |
155 | 165 | /* Note: We take the endpoint and the call_id from the remote |
156 | 166 | * connection info, because we can be confident that the |
212 | 222 | OSMO_ASSERT(false); |
213 | 223 | break; |
214 | 224 | } |
225 | } | |
226 | ||
227 | /* Return the CI that the MGW allocated during CRCX response. This is purely informational for logging | |
228 | * and identity tracking; the mgcp_conn_*() functions take care of using the right CI internally. */ | |
229 | const char *mgcp_conn_get_ci(struct osmo_fsm_inst *fi) | |
230 | { | |
231 | struct mgcp_ctx *mgcp_ctx = fi->priv; | |
232 | return mgcp_ctx->conn_id; | |
215 | 233 | } |
216 | 234 | |
217 | 235 | static void mgw_crcx_resp_cb(struct mgcp_response *r, void *priv) |
475 | 493 | * mgcp_conn_delete() to instruct the FSM to perform a graceful exit */ |
476 | 494 | if (strlen(mgcp_ctx->conn_id)) { |
477 | 495 | LOGPFSML(fi, LOGL_ERROR, |
478 | "MGW/DLCX: aprupt FSM termination with connections still present, sending unconditional DLCX...\n"); | |
496 | "MGW/DLCX: abrupt FSM termination with connections still present, sending unconditional DLCX...\n"); | |
479 | 497 | msg = make_dlcx_msg(mgcp_ctx); |
480 | 498 | OSMO_ASSERT(msg); |
481 | 499 | mgcp_client_tx(mgcp, msg, NULL, NULL); |
547 | 565 | |
548 | 566 | /*! allocate FSM, and create a new connection on the MGW. |
549 | 567 | * \param[in] mgcp MGCP client descriptor. |
550 | * \param[in] mgcpparent_fi Parent FSM instance. | |
568 | * \param[in] parent_fi Parent FSM instance. | |
551 | 569 | * \param[in] parent_term_evt Event to be sent to parent when terminating. |
552 | 570 | * \param[in] parent_evt Event to be sent to parent when operation is done. |
553 | 571 | * \param[in] conn_peer Connection parameters (ip, port...). |
564 | 582 | OSMO_ASSERT(mgcp); |
565 | 583 | OSMO_ASSERT(conn_peer); |
566 | 584 | |
567 | /* Check if IP/Port informstaion in conn info makes sense */ | |
585 | /* Check if IP/Port information in conn info makes sense */ | |
568 | 586 | if (conn_peer->port && inet_aton(conn_peer->addr, &ip_test) == 0) |
569 | 587 | return NULL; |
570 | 588 | |
621 | 639 | OSMO_ASSERT(fi->state != ST_DLCX_RESP); |
622 | 640 | |
623 | 641 | /* Check if IP/Port parameters make sense */ |
624 | if (conn_peer->port == 0) | |
642 | if (conn_peer->port == 0) { | |
643 | LOGPFSML(fi, LOGL_ERROR, "Cannot MDCX, port == 0\n"); | |
625 | 644 | return -EINVAL; |
626 | if (inet_aton(conn_peer->addr, &ip_test) == 0) | |
645 | } | |
646 | if (inet_aton(conn_peer->addr, &ip_test) == 0) { | |
647 | LOGPFSML(fi, LOGL_ERROR, "Cannot MDCX, IP address == 0.0.0.0\n"); | |
627 | 648 | return -EINVAL; |
649 | } | |
628 | 650 | |
629 | 651 | /*! The user may supply an endpoint identifier in conn_peer. The |
630 | 652 | * identifier is then checked. This check is optional. Later steps do |
631 | 653 | * not depend on the endpoint identifier supplied here because it is |
632 | 654 | * already implicitly known from the CRCX phase. */ |
633 | if (strlen(conn_peer->endpoint) && strcmp(conn_peer->endpoint, mgcp_ctx->conn_peer_remote.endpoint)) | |
655 | if (strlen(conn_peer->endpoint) && strcmp(conn_peer->endpoint, mgcp_ctx->conn_peer_remote.endpoint)) { | |
656 | LOGPFSML(fi, LOGL_ERROR, "Cannot MDCX, endpoint mismatches: requested %s, should be %s\n", | |
657 | conn_peer->endpoint, mgcp_ctx->conn_peer_remote.endpoint); | |
634 | 658 | return -EINVAL; |
659 | } | |
635 | 660 | |
636 | 661 | /*! Note: The call-id is implicitly known from the previous CRCX and |
637 | 662 | * will not be checked even when it is set in conn_peer. */ |
25 | 25 | #include <osmocom/mgcp/mgcp_stat.h> |
26 | 26 | #include <osmocom/mgcp/mgcp_msg.h> |
27 | 27 | #include <osmocom/mgcp/mgcp_endp.h> |
28 | #include <osmocom/mgcp/mgcp_sdp.h> | |
29 | #include <osmocom/mgcp/mgcp_codec.h> | |
28 | 30 | |
29 | 31 | #include <osmocom/core/application.h> |
30 | 32 | #include <osmocom/core/talloc.h> |
155 | 157 | "s=-\r\n" \ |
156 | 158 | "c=IN IP4 0.0.0.0\r\n" \ |
157 | 159 | "t=0 0\r\n" \ |
158 | "m=audio 16002 RTP/AVP 96\r\n" \ | |
159 | "a=rtpmap:96 AMR\r\n" \ | |
160 | "m=audio 16002 RTP/AVP 112\r\n" \ | |
161 | "a=rtpmap:112 AMR\r\n" \ | |
160 | 162 | "a=ptime:40\r\n" |
161 | 163 | |
162 | 164 | #define MDCX4_PT1 \ |
403 | 405 | "v=0\r\n" \ |
404 | 406 | "o=- 1439038275 1439038275 IN IP4 192.168.181.247\r\n" \ |
405 | 407 | "s=-\r\nc=IN IP4 192.168.181.247\r\n" \ |
406 | "t=0 0\r\nm=audio 29084 RTP/AVP 255 0 8 3 18 4 96 97 101\r\n" \ | |
408 | "t=0 0\r\nm=audio 29084 RTP/AVP 0 8 3 18 4 96 97 101\r\n" \ | |
407 | 409 | "a=rtpmap:0 PCMU/8000\r\n" \ |
408 | 410 | "a=rtpmap:8 PCMA/8000\r\n" \ |
409 | 411 | "a=rtpmap:3 gsm/8000\r\n" \ |
424 | 426 | "I: %s\r\n" \ |
425 | 427 | "\r\n" \ |
426 | 428 | "c=IN IP4 8.8.8.8\r\n" \ |
427 | "m=audio 16434 RTP/AVP 255\r\n" | |
429 | "m=audio 16434 RTP/AVP 3\r\n" | |
430 | ||
431 | #define CRCX_NO_LCO_NO_SDP \ | |
432 | "CRCX 2 6@mgw MGCP 1.0\r\n" \ | |
433 | "M: recvonly\r\n" \ | |
434 | "C: 2\r\n" | |
435 | ||
436 | #define CRCX_NO_LCO_NO_SDP_RET \ | |
437 | "200 2 OK\r\n" \ | |
438 | "I: %s\r\n" \ | |
439 | "\r\n" \ | |
440 | "v=0\r\n" \ | |
441 | "o=- %s 23 IN IP4 0.0.0.0\r\n" \ | |
442 | "s=-\r\n" \ | |
443 | "c=IN IP4 0.0.0.0\r\n" \ | |
444 | "t=0 0\r\n" \ | |
445 | "m=audio 16008 RTP/AVP 0\r\n" \ | |
446 | "a=ptime:20\r\n" | |
428 | 447 | |
429 | 448 | struct mgcp_test { |
430 | 449 | const char *name; |
461 | 480 | {"MDCX3", MDCX3, MDCX3_FMTP_RET, PTYPE_NONE,.extra_fmtp = |
462 | 481 | "a=fmtp:126 0/1/2"}, |
463 | 482 | {"DLCX", DLCX, DLCX_RET, PTYPE_IGNORE,.extra_fmtp = "a=fmtp:126 0/1/2"}, |
483 | {"CRCX", CRCX_NO_LCO_NO_SDP, CRCX_NO_LCO_NO_SDP_RET, 97}, | |
464 | 484 | }; |
465 | 485 | |
466 | 486 | static const struct mgcp_test retransmit[] = { |
763 | 783 | fprintf(stderr, "endpoint %d: " |
764 | 784 | "payload type %d (expected %d)\n", |
765 | 785 | last_endpoint, |
766 | conn->end.codec.payload_type, t->ptype); | |
786 | conn->end.codec->payload_type, t->ptype); | |
767 | 787 | |
768 | 788 | if (t->ptype != PTYPE_IGNORE) |
769 | OSMO_ASSERT(conn->end.codec.payload_type == | |
789 | OSMO_ASSERT(conn->end.codec->payload_type == | |
770 | 790 | t->ptype); |
771 | 791 | |
772 | 792 | /* Reset them again for next test */ |
773 | conn->end.codec.payload_type = PTYPE_NONE; | |
793 | conn->end.codec->payload_type = PTYPE_NONE; | |
774 | 794 | } |
775 | 795 | } |
776 | 796 | |
916 | 936 | static void test_packet_loss_calc(void) |
917 | 937 | { |
918 | 938 | int i; |
939 | struct mgcp_endpoint endp; | |
940 | struct mgcp_trunk_config trunk; | |
941 | ||
919 | 942 | printf("Testing packet loss calculation.\n"); |
943 | ||
944 | memset(&endp, 0, sizeof(endp)); | |
945 | memset(&trunk, 0, sizeof(trunk)); | |
946 | ||
947 | endp.type = &ep_typeset.rtp; | |
948 | trunk.vty_number_endpoints = 1; | |
949 | trunk.endpoints = &endp; | |
950 | endp.tcfg = &trunk; | |
951 | INIT_LLIST_HEAD(&endp.conns); | |
920 | 952 | |
921 | 953 | for (i = 0; i < ARRAY_SIZE(pl_test_dat); ++i) { |
922 | 954 | uint32_t expected; |
923 | 955 | int loss; |
924 | struct mgcp_rtp_state state; | |
925 | struct mgcp_rtp_end rtp; | |
926 | memset(&state, 0, sizeof(state)); | |
927 | memset(&rtp, 0, sizeof(rtp)); | |
928 | ||
929 | state.stats.initialized = 1; | |
930 | state.stats.base_seq = pl_test_dat[i].base_seq; | |
931 | state.stats.max_seq = pl_test_dat[i].max_seq; | |
932 | state.stats.cycles = pl_test_dat[i].cycles; | |
933 | ||
934 | rtp.stats.packets_rx = pl_test_dat[i].packets; | |
935 | calc_loss(&state, &rtp, &expected, &loss); | |
956 | ||
957 | struct mgcp_conn_rtp *conn = NULL; | |
958 | struct mgcp_conn *_conn = NULL; | |
959 | struct mgcp_rtp_state *state; | |
960 | struct rate_ctr *packets_rx; | |
961 | ||
962 | _conn = | |
963 | mgcp_conn_alloc(NULL, &endp, MGCP_CONN_TYPE_RTP, | |
964 | "test-connection"); | |
965 | conn = mgcp_conn_get_rtp(&endp, _conn->id); | |
966 | state = &conn->state; | |
967 | packets_rx = &conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]; | |
968 | ||
969 | state->stats.initialized = 1; | |
970 | state->stats.base_seq = pl_test_dat[i].base_seq; | |
971 | state->stats.max_seq = pl_test_dat[i].max_seq; | |
972 | state->stats.cycles = pl_test_dat[i].cycles; | |
973 | ||
974 | packets_rx->current = pl_test_dat[i].packets; | |
975 | calc_loss(conn, &expected, &loss); | |
936 | 976 | |
937 | 977 | if (loss != pl_test_dat[i].loss |
938 | 978 | || expected != pl_test_dat[i].expected) { |
941 | 981 | i, loss, pl_test_dat[i].loss, expected, |
942 | 982 | pl_test_dat[i].expected); |
943 | 983 | } |
984 | ||
985 | mgcp_conn_free_all(&endp); | |
944 | 986 | } |
987 | ||
945 | 988 | } |
946 | 989 | |
947 | 990 | int mgcp_parse_stats(struct msgb *msg, uint32_t *ps, uint32_t *os, |
1128 | 1171 | uint32_t last_ssrc = 0; |
1129 | 1172 | uint32_t last_timestamp = 0; |
1130 | 1173 | uint32_t last_seqno = 0; |
1131 | int last_in_ts_err_cnt = 0; | |
1132 | int last_out_ts_err_cnt = 0; | |
1174 | uint64_t last_in_ts_err_cnt = 0; | |
1175 | uint64_t last_out_ts_err_cnt = 0; | |
1133 | 1176 | struct mgcp_conn_rtp *conn = NULL; |
1134 | 1177 | struct mgcp_conn *_conn = NULL; |
1178 | struct rate_ctr test_ctr_in; | |
1179 | struct rate_ctr test_ctr_out; | |
1135 | 1180 | |
1136 | 1181 | printf("Testing packet error detection%s%s.\n", |
1137 | 1182 | patch_ssrc ? ", patch SSRC" : "", |
1140 | 1185 | memset(&trunk, 0, sizeof(trunk)); |
1141 | 1186 | memset(&endp, 0, sizeof(endp)); |
1142 | 1187 | memset(&state, 0, sizeof(state)); |
1188 | ||
1189 | memset(&test_ctr_in, 0, sizeof(test_ctr_in)); | |
1190 | memset(&test_ctr_out, 0, sizeof(test_ctr_out)); | |
1191 | state.in_stream.err_ts_ctr = &test_ctr_in; | |
1192 | state.out_stream.err_ts_ctr = &test_ctr_out; | |
1143 | 1193 | |
1144 | 1194 | endp.type = &ep_typeset.rtp; |
1145 | 1195 | |
1159 | 1209 | |
1160 | 1210 | rtp = &conn->end; |
1161 | 1211 | |
1162 | rtp->codec.payload_type = 98; | |
1212 | OSMO_ASSERT(mgcp_codec_add(conn, PTYPE_UNDEFINED, "AMR/8000/1") == 0); | |
1213 | rtp->codec = &rtp->codecs[0]; | |
1163 | 1214 | |
1164 | 1215 | for (i = 0; i < ARRAY_SIZE(test_rtp_packets1); ++i) { |
1165 | 1216 | struct rtp_packet_info *info = test_rtp_packets1 + i; |
1185 | 1236 | state.in_stream.last_tsdelta, state.in_stream.last_seq); |
1186 | 1237 | |
1187 | 1238 | printf("Out TS change: %d, dTS: %d, Seq change: %d, " |
1188 | "TS Err change: in %+d, out %+d\n", | |
1239 | "TS Err change: in +%u, out +%u\n", | |
1189 | 1240 | state.out_stream.last_timestamp - last_timestamp, |
1190 | 1241 | state.out_stream.last_tsdelta, |
1191 | 1242 | state.out_stream.last_seq - last_seqno, |
1192 | state.in_stream.err_ts_counter - last_in_ts_err_cnt, | |
1193 | state.out_stream.err_ts_counter - last_out_ts_err_cnt); | |
1243 | (unsigned int) (state.in_stream.err_ts_ctr->current - last_in_ts_err_cnt), | |
1244 | (unsigned int) (state.out_stream.err_ts_ctr->current - last_out_ts_err_cnt)); | |
1194 | 1245 | |
1195 | 1246 | printf("Stats: Jitter = %u, Transit = %d\n", |
1196 | 1247 | calc_jitter(&state), state.stats.transit); |
1197 | 1248 | |
1198 | last_in_ts_err_cnt = state.in_stream.err_ts_counter; | |
1199 | last_out_ts_err_cnt = state.out_stream.err_ts_counter; | |
1249 | last_in_ts_err_cnt = state.in_stream.err_ts_ctr->current; | |
1250 | last_out_ts_err_cnt = state.out_stream.err_ts_ctr->current; | |
1200 | 1251 | last_timestamp = state.out_stream.last_timestamp; |
1201 | 1252 | last_seqno = state.out_stream.last_seq; |
1202 | 1253 | } |
1235 | 1286 | endp = &cfg->trunk.endpoints[last_endpoint]; |
1236 | 1287 | conn = mgcp_conn_get_rtp(endp, conn_id); |
1237 | 1288 | OSMO_ASSERT(conn); |
1238 | OSMO_ASSERT(conn->end.codec.payload_type == 18); | |
1239 | OSMO_ASSERT(conn->end.alt_codec.payload_type == 97); | |
1289 | OSMO_ASSERT(conn->end.codec->payload_type == 18); | |
1240 | 1290 | |
1241 | 1291 | /* Allocate 2@mgw with three codecs, last one ignored */ |
1242 | 1292 | last_endpoint = -1; |
1251 | 1301 | endp = &cfg->trunk.endpoints[last_endpoint]; |
1252 | 1302 | conn = mgcp_conn_get_rtp(endp, conn_id); |
1253 | 1303 | OSMO_ASSERT(conn); |
1254 | OSMO_ASSERT(conn->end.codec.payload_type == 18); | |
1255 | OSMO_ASSERT(conn->end.alt_codec.payload_type == 97); | |
1256 | ||
1257 | /* Allocate 3@mgw with no codecs, check for PT == -1 */ | |
1304 | OSMO_ASSERT(conn->end.codec->payload_type == 18); | |
1305 | ||
1306 | /* Allocate 3@mgw with no codecs, check for PT == 0 */ | |
1307 | /* Note: It usually makes no sense to leave the payload type list | |
1308 | * out. However RFC 2327 does not clearly forbid this case and | |
1309 | * it makes and since we already decided in OS#2658 that a missing | |
1310 | * LCO should pick a sane default codec, it makes sense to expect | |
1311 | * the same behaviour if SDP lacks proper payload type information */ | |
1258 | 1312 | last_endpoint = -1; |
1259 | 1313 | inp = create_msg(CRCX_MULT_3, NULL); |
1260 | 1314 | resp = mgcp_handle_message(cfg, inp); |
1267 | 1321 | endp = &cfg->trunk.endpoints[last_endpoint]; |
1268 | 1322 | conn = mgcp_conn_get_rtp(endp, conn_id); |
1269 | 1323 | OSMO_ASSERT(conn); |
1270 | OSMO_ASSERT(conn->end.codec.payload_type == -1); | |
1271 | OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); | |
1324 | OSMO_ASSERT(conn->end.codec->payload_type == 0); | |
1272 | 1325 | |
1273 | 1326 | /* Allocate 4@mgw with a single codec */ |
1274 | 1327 | last_endpoint = -1; |
1283 | 1336 | endp = &cfg->trunk.endpoints[last_endpoint]; |
1284 | 1337 | conn = mgcp_conn_get_rtp(endp, conn_id); |
1285 | 1338 | OSMO_ASSERT(conn); |
1286 | OSMO_ASSERT(conn->end.codec.payload_type == 18); | |
1287 | OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); | |
1339 | OSMO_ASSERT(conn->end.codec->payload_type == 18); | |
1288 | 1340 | |
1289 | 1341 | /* Allocate 5@mgw at select GSM.. */ |
1290 | 1342 | last_endpoint = -1; |
1302 | 1354 | endp = &cfg->trunk.endpoints[last_endpoint]; |
1303 | 1355 | conn = mgcp_conn_get_rtp(endp, conn_id); |
1304 | 1356 | OSMO_ASSERT(conn); |
1305 | OSMO_ASSERT(conn->end.codec.payload_type == 3); | |
1306 | OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); | |
1357 | OSMO_ASSERT(conn->end.codec->payload_type == 3); | |
1307 | 1358 | |
1308 | 1359 | inp = create_msg(MDCX_NAT_DUMMY, conn_id); |
1309 | 1360 | last_endpoint = -1; |
1314 | 1365 | endp = &cfg->trunk.endpoints[last_endpoint]; |
1315 | 1366 | conn = mgcp_conn_get_rtp(endp, conn_id); |
1316 | 1367 | OSMO_ASSERT(conn); |
1317 | OSMO_ASSERT(conn->end.codec.payload_type == 3); | |
1318 | OSMO_ASSERT(conn->end.alt_codec.payload_type == -1); | |
1368 | OSMO_ASSERT(conn->end.codec->payload_type == 3); | |
1319 | 1369 | OSMO_ASSERT(conn->end.rtp_port == htons(16434)); |
1320 | 1370 | memset(&addr, 0, sizeof(addr)); |
1321 | 1371 | inet_aton("8.8.8.8", &addr); |
1345 | 1395 | endp = &cfg->trunk.endpoints[last_endpoint]; |
1346 | 1396 | conn = mgcp_conn_get_rtp(endp, conn_id); |
1347 | 1397 | OSMO_ASSERT(conn); |
1348 | OSMO_ASSERT(conn->end.codec.payload_type == 255); | |
1349 | OSMO_ASSERT(conn->end.alt_codec.payload_type == 0); | |
1398 | OSMO_ASSERT(conn->end.codec->payload_type == 0); | |
1350 | 1399 | |
1351 | 1400 | talloc_free(cfg); |
1352 | 1401 | } |
1463 | 1512 | .cat = log_categories, |
1464 | 1513 | .num_cat = ARRAY_SIZE(log_categories), |
1465 | 1514 | }; |
1515 | ||
1516 | static void test_get_lco_identifier(void) | |
1517 | { | |
1518 | char *test; | |
1519 | printf("Testing get_lco_identifier()\n"); | |
1520 | ||
1521 | /* Normal case at the beginning */ | |
1522 | test = "p:10, a:PCMU"; | |
1523 | printf("%s -> %s\n", test, get_lco_identifier(test)); | |
1524 | ||
1525 | test = "p:10, a:PCMU"; | |
1526 | printf("%s -> %s\n", test, get_lco_identifier(test)); | |
1527 | ||
1528 | /* Begin parsing in the middle of the value part of | |
1529 | * the previous LCO option value */ | |
1530 | test = "XXXX, p:10, a:PCMU"; | |
1531 | printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); | |
1532 | ||
1533 | test = "XXXX,p:10,a:PCMU"; | |
1534 | printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); | |
1535 | ||
1536 | test = "10,a:PCMU"; | |
1537 | printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); | |
1538 | ||
1539 | test = "10, a:PCMU"; | |
1540 | printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); | |
1541 | ||
1542 | test = "10,a: PCMU"; | |
1543 | printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); | |
1544 | ||
1545 | test = "10 ,a: PCMU"; | |
1546 | printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); | |
1547 | ||
1548 | /* Begin parsing right at the end of the previous LCO | |
1549 | * option value */ | |
1550 | test = ", a:PCMU"; | |
1551 | printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); | |
1552 | ||
1553 | test = " a:PCMU"; | |
1554 | printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); | |
1555 | ||
1556 | /* Empty string, result should be (null) */ | |
1557 | test = ""; | |
1558 | printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); | |
1559 | ||
1560 | /* Missing colons, result should be (null) */ | |
1561 | test = "p10, aPCMU"; | |
1562 | printf("%s -> %s\n", test, get_lco_identifier(test)); | |
1563 | ||
1564 | /* Colon separated from the identifier, result should be (null) */ | |
1565 | test = "10,a :PCMU"; | |
1566 | printf("'%s' -> '%s'\n", test, get_lco_identifier(test)); | |
1567 | } | |
1568 | ||
1569 | static void test_check_local_cx_options(void *ctx) | |
1570 | { | |
1571 | /* Legal cases */ | |
1572 | OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:PCMU") == 0); | |
1573 | OSMO_ASSERT(check_local_cx_options(ctx, "a:PCMU") == 0); | |
1574 | OSMO_ASSERT(check_local_cx_options(ctx, "a:PCMU, p:10, IN:10") == 0); | |
1575 | OSMO_ASSERT(check_local_cx_options(ctx, "one:AAA, two:BB, tree:C") == | |
1576 | 0); | |
1577 | OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:PCMU") == 0); | |
1578 | OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:G726-32") == 0); | |
1579 | OSMO_ASSERT(check_local_cx_options(ctx, "p:10-20, b:64") == 0); | |
1580 | OSMO_ASSERT(check_local_cx_options(ctx, "b:32-64, e:off") == 0); | |
1581 | OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:PCMU;G726-32") == 0); | |
1582 | ||
1583 | /* Illegal spaces before and after colon */ | |
1584 | OSMO_ASSERT(check_local_cx_options(ctx, "a:PCMU, p :10") == -1); | |
1585 | OSMO_ASSERT(check_local_cx_options(ctx, "a :PCMU, p:10") == -1); | |
1586 | OSMO_ASSERT(check_local_cx_options(ctx, "p: 10, a:PCMU") == -1); | |
1587 | ||
1588 | /* Check if multiple appearances of LCOs are rejected */ | |
1589 | OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:PCMU, p:10") == -1); | |
1590 | OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:PCMU, a:PCMU, p:10") == | |
1591 | -1); | |
1592 | OSMO_ASSERT(check_local_cx_options(ctx, "p:10, p:10") == -1); | |
1593 | ||
1594 | /* Check if empty LCO are rejected */ | |
1595 | OSMO_ASSERT(check_local_cx_options(ctx, "p: , a:PCMU") == -1); | |
1596 | OSMO_ASSERT(check_local_cx_options(ctx, "p: , a: PCMU") == -1); | |
1597 | OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a: PCMU") == -1); | |
1598 | OSMO_ASSERT(check_local_cx_options(ctx, "p:, a:PCMU") == -1); | |
1599 | OSMO_ASSERT(check_local_cx_options(ctx, "p:10, a:") == -1); | |
1600 | ||
1601 | /* Garbeled beginning and ends */ | |
1602 | OSMO_ASSERT(check_local_cx_options(ctx, ":10, a:10") == -1); | |
1603 | OSMO_ASSERT(check_local_cx_options(ctx, "10, a:PCMU") == -1); | |
1604 | OSMO_ASSERT(check_local_cx_options(ctx, ", a:PCMU") == -1); | |
1605 | OSMO_ASSERT(check_local_cx_options(ctx, " a:PCMU") == -1); | |
1606 | OSMO_ASSERT(check_local_cx_options(ctx, "a:PCMU,") == -1); | |
1607 | OSMO_ASSERT(check_local_cx_options(ctx, "a:PCMU, ") == -1); | |
1608 | ||
1609 | /* Illegal strings */ | |
1610 | OSMO_ASSERT(check_local_cx_options(ctx, " ") == -1); | |
1611 | OSMO_ASSERT(check_local_cx_options(ctx, "") == -1); | |
1612 | OSMO_ASSERT(check_local_cx_options(ctx, "AAA") == -1); | |
1613 | OSMO_ASSERT(check_local_cx_options(ctx, ":,") == -1); | |
1614 | OSMO_ASSERT(check_local_cx_options(ctx, ",:") == -1); | |
1615 | OSMO_ASSERT(check_local_cx_options(ctx, ",,,") == -1); | |
1616 | } | |
1466 | 1617 | |
1467 | 1618 | int main(int argc, char **argv) |
1468 | 1619 | { |
1485 | 1636 | test_no_cycle(); |
1486 | 1637 | test_no_name(); |
1487 | 1638 | test_osmux_cid(); |
1639 | test_get_lco_identifier(); | |
1640 | test_check_local_cx_options(ctx); | |
1488 | 1641 | |
1489 | 1642 | OSMO_ASSERT(talloc_total_size(msgb_ctx) == 0); |
1490 | 1643 | OSMO_ASSERT(talloc_total_blocks(msgb_ctx) == 1); |
418 | 418 | Testing CRCX |
419 | 419 | creating message from statically defined input: |
420 | 420 | ---------8<--------- |
421 | CRCX 2 6@mgw MGCP 1.0 | |
422 | M: recvonly | |
423 | C: 2 | |
424 | ||
425 | ---------8<--------- | |
426 | checking response: | |
427 | using message with patched conn_id for comparison | |
428 | Response matches our expectations. | |
429 | (response does not contain a connection id) | |
430 | Dummy packets: 2 | |
431 | ||
432 | ================================================ | |
433 | Testing CRCX | |
434 | creating message from statically defined input: | |
435 | ---------8<--------- | |
421 | 436 | CRCX 2 1@mgw MGCP 1.0 |
422 | 437 | M: recvonly |
423 | 438 | C: 2 |
1041 | 1056 | s=- |
1042 | 1057 | c=IN IP4 192.168.181.247 |
1043 | 1058 | t=0 0 |
1044 | m=audio 29084 RTP/AVP 255 0 8 3 18 4 96 97 101 | |
1059 | m=audio 29084 RTP/AVP 0 8 3 18 4 96 97 101 | |
1045 | 1060 | a=rtpmap:0 PCMU/8000 |
1046 | 1061 | a=rtpmap:8 PCMA/8000 |
1047 | 1062 | a=rtpmap:3 gsm/8000 |
1064 | 1079 | I: %s |
1065 | 1080 | |
1066 | 1081 | c=IN IP4 8.8.8.8 |
1067 | m=audio 16434 RTP/AVP 255 | |
1082 | m=audio 16434 RTP/AVP 3 | |
1068 | 1083 | |
1069 | 1084 | ---------8<--------- |
1070 | 1085 | creating message from statically defined input: |
1079 | 1094 | s=- |
1080 | 1095 | c=IN IP4 192.168.181.247 |
1081 | 1096 | t=0 0 |
1082 | m=audio 29084 RTP/AVP 255 0 8 3 18 4 96 97 101 | |
1097 | m=audio 29084 RTP/AVP 0 8 3 18 4 96 97 101 | |
1083 | 1098 | a=rtpmap:0 PCMU/8000 |
1084 | 1099 | a=rtpmap:8 PCMA/8000 |
1085 | 1100 | a=rtpmap:3 gsm/8000 |
1114 | 1129 | checking response: |
1115 | 1130 | using message with patched conn_id for comparison |
1116 | 1131 | Response matches our expectations. |
1132 | Testing get_lco_identifier() | |
1133 | p:10, a:PCMU -> p:10, a:PCMU | |
1134 | p:10, a:PCMU -> p:10, a:PCMU | |
1135 | 'XXXX, p:10, a:PCMU' -> 'p:10, a:PCMU' | |
1136 | 'XXXX,p:10,a:PCMU' -> 'p:10,a:PCMU' | |
1137 | '10,a:PCMU' -> 'a:PCMU' | |
1138 | '10, a:PCMU' -> 'a:PCMU' | |
1139 | '10,a: PCMU' -> 'a: PCMU' | |
1140 | '10 ,a: PCMU' -> 'a: PCMU' | |
1141 | ', a:PCMU' -> 'a:PCMU' | |
1142 | ' a:PCMU' -> 'a:PCMU' | |
1143 | '' -> '(null)' | |
1144 | p10, aPCMU -> (null) | |
1145 | '10,a :PCMU' -> '(null)' | |
1117 | 1146 | Done |
94 | 94 | |
95 | 95 | void test_response_cb(struct mgcp_response *response, void *priv) |
96 | 96 | { |
97 | unsigned int i; | |
97 | 98 | OSMO_ASSERT(priv == mgcp); |
98 | 99 | mgcp_response_parse_params(response); |
99 | 100 | |
100 | printf("response cb received:\n" | |
101 | " head.response_code = %d\n" | |
102 | " head.trans_id = %u\n" | |
103 | " head.comment = %s\n" | |
104 | " audio_port = %u\n" | |
105 | " audio_ip = %s\n", | |
106 | response->head.response_code, | |
107 | response->head.trans_id, | |
108 | response->head.comment, | |
109 | response->audio_port, | |
110 | response->audio_ip | |
111 | ); | |
101 | printf("response cb received:\n"); | |
102 | printf(" head.response_code = %d\n", response->head.response_code); | |
103 | printf(" head.trans_id = %u\n", response->head.trans_id); | |
104 | printf(" head.comment = %s\n", response->head.comment); | |
105 | printf(" audio_port = %u\n", response->audio_port); | |
106 | printf(" audio_ip = %s\n", response->audio_ip); | |
107 | printf(" ptime = %u\n", response->ptime); | |
108 | printf(" codecs_len = %u\n", response->codecs_len); | |
109 | for(i=0;i<response->codecs_len;i++) | |
110 | printf(" codecs[%u] = %u\n", i, response->codecs[i]); | |
111 | printf(" ptmap_len = %u\n", response->ptmap_len); | |
112 | for(i=0;i<response->ptmap_len;i++) { | |
113 | printf(" ptmap[%u].codec = %u\n", i, response->ptmap[i].codec); | |
114 | printf(" ptmap[%u].pt = %u\n", i, response->ptmap[i].pt); | |
115 | } | |
116 | ||
112 | 117 | } |
113 | 118 | |
114 | 119 | mgcp_trans_id_t dummy_mgcp_send(struct msgb *msg) |
148 | 153 | "s=-\r\n" |
149 | 154 | "c=IN IP4 10.9.1.120\r\n" |
150 | 155 | "t=0 0\r\n" |
151 | "m=audio 16002 RTP/AVP 98\r\n" | |
152 | "a=rtpmap:98 AMR/8000\r\n" | |
156 | "m=audio 16002 RTP/AVP 110 96\r\n" | |
157 | "a=rtpmap:110 AMR/8000\r\n" | |
158 | "a=rtpmap:96 GSM-EFR/8000\r\n" | |
153 | 159 | "a=ptime:20\r\n"); |
154 | 160 | } |
155 | 161 | |
165 | 171 | .audio_port = 1234, |
166 | 172 | .call_id = 47, |
167 | 173 | .conn_id = "11", |
168 | .conn_mode = MGCP_CONN_RECV_SEND | |
174 | .conn_mode = MGCP_CONN_RECV_SEND, | |
175 | .ptime = 20, | |
176 | .codecs[0] = CODEC_GSM_8000_1, | |
177 | .codecs[1] = CODEC_AMR_8000_1, | |
178 | .codecs[2] = CODEC_GSMEFR_8000_1, | |
179 | .codecs_len = 1, | |
180 | .ptmap[0].codec = CODEC_GSMEFR_8000_1, | |
181 | .ptmap[0].pt = 96, | |
182 | .ptmap_len = 1 | |
169 | 183 | }; |
170 | 184 | |
171 | 185 | if (mgcp) |
182 | 196 | msg = mgcp_msg_gen(mgcp, &mgcp_msg); |
183 | 197 | printf("%s\n", (char *)msg->data); |
184 | 198 | |
199 | printf("Generated CRCX message (two codecs):\n"); | |
200 | mgcp_msg.verb = MGCP_VERB_CRCX; | |
201 | mgcp_msg.presence = | |
202 | (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | | |
203 | MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE); | |
204 | mgcp_msg.codecs_len = 2; | |
205 | msg = mgcp_msg_gen(mgcp, &mgcp_msg); | |
206 | mgcp_msg.codecs_len = 1; | |
207 | printf("%s\n", (char *)msg->data); | |
208 | ||
209 | printf("Generated CRCX message (three codecs, one with custom pt):\n"); | |
210 | mgcp_msg.verb = MGCP_VERB_CRCX; | |
211 | mgcp_msg.presence = | |
212 | (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | | |
213 | MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE); | |
214 | mgcp_msg.codecs_len = 3; | |
215 | msg = mgcp_msg_gen(mgcp, &mgcp_msg); | |
216 | mgcp_msg.codecs_len = 1; | |
217 | printf("%s\n", (char *)msg->data); | |
218 | ||
185 | 219 | printf("Generated MDCX message:\n"); |
186 | 220 | mgcp_msg.verb = MGCP_VERB_MDCX; |
187 | 221 | mgcp_msg.presence = |
190 | 224 | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT); |
191 | 225 | msg = mgcp_msg_gen(mgcp, &mgcp_msg); |
192 | 226 | printf("%s\n", (char *)msg->data); |
227 | ||
228 | printf("Generated MDCX message (two codecs):\n"); | |
229 | mgcp_msg.verb = MGCP_VERB_MDCX; | |
230 | mgcp_msg.presence = | |
231 | (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | | |
232 | MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE | | |
233 | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT); | |
234 | mgcp_msg.codecs_len = 2; | |
235 | msg = mgcp_msg_gen(mgcp, &mgcp_msg); | |
236 | mgcp_msg.codecs_len = 1; | |
237 | printf("%s\n", (char *)msg->data); | |
238 | ||
239 | printf("Generated MDCX message (three codecs, one with custom pt):\n"); | |
240 | mgcp_msg.verb = MGCP_VERB_MDCX; | |
241 | mgcp_msg.presence = | |
242 | (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID | | |
243 | MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE | | |
244 | MGCP_MSG_PRESENCE_AUDIO_IP | MGCP_MSG_PRESENCE_AUDIO_PORT); | |
245 | mgcp_msg.codecs_len = 3; | |
246 | msg = mgcp_msg_gen(mgcp, &mgcp_msg); | |
247 | mgcp_msg.codecs_len = 1; | |
248 | printf("%s\n", (char *)msg->data); | |
193 | 249 | |
194 | 250 | printf("Generated DLCX message:\n"); |
195 | 251 | mgcp_msg.verb = MGCP_VERB_DLCX; |
241 | 297 | .conn_mode = MGCP_CONN_RECV_SEND, |
242 | 298 | .presence = (MGCP_MSG_PRESENCE_ENDPOINT | MGCP_MSG_PRESENCE_CALL_ID |
243 | 299 | | MGCP_MSG_PRESENCE_CONN_ID | MGCP_MSG_PRESENCE_CONN_MODE), |
300 | .ptime = 20, | |
301 | .codecs[0] = CODEC_AMR_8000_1, | |
302 | .codecs_len = 1 | |
244 | 303 | }; |
245 | 304 | |
246 | 305 | printf("\n%s():\n", __func__); |
375 | 434 | OSMO_ASSERT(!failures); |
376 | 435 | } |
377 | 436 | |
437 | static void test_map_pt_to_codec(void) | |
438 | { | |
439 | /* Full form */ | |
440 | OSMO_ASSERT(map_str_to_codec("PCMU/8000/1") == CODEC_PCMU_8000_1); | |
441 | OSMO_ASSERT(map_str_to_codec("GSM/8000/1") == CODEC_GSM_8000_1); | |
442 | OSMO_ASSERT(map_str_to_codec("PCMA/8000/1") == CODEC_PCMA_8000_1); | |
443 | OSMO_ASSERT(map_str_to_codec("G729/8000/1") == CODEC_G729_8000_1); | |
444 | OSMO_ASSERT(map_str_to_codec("GSM-EFR/8000/1") == CODEC_GSMEFR_8000_1); | |
445 | OSMO_ASSERT(map_str_to_codec("GSM-HR-08/8000/1") == CODEC_GSMHR_8000_1); | |
446 | OSMO_ASSERT(map_str_to_codec("AMR/8000/1") == CODEC_AMR_8000_1); | |
447 | OSMO_ASSERT(map_str_to_codec("AMR-WB/16000/1") == CODEC_AMRWB_16000_1); | |
448 | ||
449 | /* Short form */ | |
450 | OSMO_ASSERT(map_str_to_codec("GSM-EFR") == CODEC_GSMEFR_8000_1); | |
451 | OSMO_ASSERT(map_str_to_codec("G729") == CODEC_G729_8000_1); | |
452 | OSMO_ASSERT(map_str_to_codec("GSM-HR-08") == CODEC_GSMHR_8000_1); | |
453 | ||
454 | /* We do not care about what is after the first delimiter */ | |
455 | OSMO_ASSERT(map_str_to_codec("AMR-WB/123///456") == CODEC_AMRWB_16000_1); | |
456 | OSMO_ASSERT(map_str_to_codec("PCMA/asdf") == CODEC_PCMA_8000_1); | |
457 | OSMO_ASSERT(map_str_to_codec("GSM/qwertz") == CODEC_GSM_8000_1); | |
458 | ||
459 | /* A trailing delimiter should not hurt */ | |
460 | OSMO_ASSERT(map_str_to_codec("AMR/") == CODEC_AMR_8000_1); | |
461 | OSMO_ASSERT(map_str_to_codec("G729/") == CODEC_G729_8000_1); | |
462 | OSMO_ASSERT(map_str_to_codec("GSM/") == CODEC_GSM_8000_1); | |
463 | ||
464 | /* This is expected to fail */ | |
465 | OSMO_ASSERT(map_str_to_codec("INVALID/1234/7") == -1); | |
466 | OSMO_ASSERT(map_str_to_codec(NULL) == -1); | |
467 | OSMO_ASSERT(map_str_to_codec("") == -1); | |
468 | OSMO_ASSERT(map_str_to_codec("/////") == -1); | |
469 | ||
470 | /* The buffers are 64 bytes long, check what happens with overlong | |
471 | * strings as input (This schould still work.) */ | |
472 | OSMO_ASSERT(map_str_to_codec("AMR-WB/16000/1############################################################################################################") == CODEC_AMRWB_16000_1); | |
473 | ||
474 | /* This should not work, as there is no delimiter after the codec | |
475 | * name */ | |
476 | OSMO_ASSERT(map_str_to_codec("AMR-WB####################################################################################################################") == -1); | |
477 | } | |
478 | ||
479 | static void test_map_codec_to_pt_and_map_pt_to_codec(void) | |
480 | { | |
481 | struct ptmap ptmap[10]; | |
482 | unsigned int ptmap_len; | |
483 | unsigned int i; | |
484 | ||
485 | ptmap[0].codec = CODEC_GSMEFR_8000_1; | |
486 | ptmap[0].pt = 96; | |
487 | ptmap[1].codec = CODEC_GSMHR_8000_1; | |
488 | ptmap[1].pt = 97; | |
489 | ptmap[2].codec = CODEC_AMR_8000_1; | |
490 | ptmap[2].pt = 98; | |
491 | ptmap[3].codec = CODEC_AMRWB_16000_1; | |
492 | ptmap[3].pt = 99; | |
493 | ptmap_len = 4; | |
494 | ||
495 | /* Mappings that are covered by the table */ | |
496 | for (i = 0; i < ptmap_len; i++) | |
497 | printf(" %u => %u\n", ptmap[i].codec, map_codec_to_pt(ptmap, ptmap_len, ptmap[i].codec)); | |
498 | for (i = 0; i < ptmap_len; i++) | |
499 | printf(" %u <= %u\n", ptmap[i].pt, map_pt_to_codec(ptmap, ptmap_len, ptmap[i].pt)); | |
500 | printf("\n"); | |
501 | ||
502 | /* Map some codecs/payload types from the static range, result must | |
503 | * always be a 1:1 mapping */ | |
504 | printf(" %u => %u\n", CODEC_PCMU_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_PCMU_8000_1)); | |
505 | printf(" %u => %u\n", CODEC_GSM_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_GSM_8000_1)); | |
506 | printf(" %u => %u\n", CODEC_PCMA_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_PCMA_8000_1)); | |
507 | printf(" %u => %u\n", CODEC_G729_8000_1, map_codec_to_pt(ptmap, ptmap_len, CODEC_G729_8000_1)); | |
508 | printf(" %u <= %u\n", CODEC_PCMU_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_PCMU_8000_1)); | |
509 | printf(" %u <= %u\n", CODEC_GSM_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_GSM_8000_1)); | |
510 | printf(" %u <= %u\n", CODEC_PCMA_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_PCMA_8000_1)); | |
511 | printf(" %u <= %u\n", CODEC_G729_8000_1, map_pt_to_codec(ptmap, ptmap_len, CODEC_G729_8000_1)); | |
512 | printf("\n"); | |
513 | ||
514 | /* Try to do mappings from statically defined range to danymic range and vice versa. This | |
515 | * is illegal and should result into a 1:1 mapping */ | |
516 | ptmap[3].codec = CODEC_AMRWB_16000_1; | |
517 | ptmap[3].pt = 2; | |
518 | ptmap[4].codec = CODEC_PCMU_8000_1; | |
519 | ptmap[4].pt = 100; | |
520 | ptmap_len = 5; | |
521 | ||
522 | /* Apply all mappings again, the illegal ones we defined should result into 1:1 mappings */ | |
523 | for (i = 0; i < ptmap_len; i++) | |
524 | printf(" %u => %u\n", ptmap[i].codec, map_codec_to_pt(ptmap, ptmap_len, ptmap[i].codec)); | |
525 | for (i = 0; i < ptmap_len; i++) | |
526 | printf(" %u <= %u\n", ptmap[i].pt, map_pt_to_codec(ptmap, ptmap_len, ptmap[i].pt)); | |
527 | printf("\n"); | |
528 | } | |
529 | ||
378 | 530 | static const struct log_info_cat log_categories[] = { |
379 | 531 | }; |
380 | 532 | |
402 | 554 | test_mgcp_msg(); |
403 | 555 | test_mgcp_client_cancel(); |
404 | 556 | test_sdp_section_start(); |
557 | test_map_codec_to_pt_and_map_pt_to_codec(); | |
558 | test_map_pt_to_codec(); | |
405 | 559 | |
406 | 560 | printf("Done\n"); |
407 | 561 | fprintf(stderr, "Done\n"); |
61 | 61 | body: "some mgcp header data\r\nand header params\n\r\rm=audio 23\r\n" |
62 | 62 | DLMGCP MGCP response: cannot find start of SDP parameters |
63 | 63 | got rc=-22 |
64 | DLMGCP ptmap contains illegal mapping: codec=113 maps to pt=2 | |
65 | DLMGCP ptmap contains illegal mapping: codec=0 maps to pt=100 | |
66 | DLMGCP ptmap contains illegal mapping: codec=113 maps to pt=2 | |
67 | DLMGCP ptmap contains illegal mapping: codec=0 maps to pt=100 | |
64 | 68 | Done |
17 | 17 | s=- |
18 | 18 | c=IN IP4 10.9.1.120 |
19 | 19 | t=0 0 |
20 | m=audio 16002 RTP/AVP 98 | |
21 | a=rtpmap:98 AMR/8000 | |
20 | m=audio 16002 RTP/AVP 110 96 | |
21 | a=rtpmap:110 AMR/8000 | |
22 | a=rtpmap:96 GSM-EFR/8000 | |
22 | 23 | a=ptime:20 |
23 | 24 | |
24 | 25 | ----- |
28 | 29 | head.comment = OK |
29 | 30 | audio_port = 16002 |
30 | 31 | audio_ip = 10.9.1.120 |
32 | ptime = 20 | |
33 | codecs_len = 2 | |
34 | codecs[0] = 112 | |
35 | codecs[1] = 110 | |
36 | ptmap_len = 2 | |
37 | ptmap[0].codec = 112 | |
38 | ptmap[0].pt = 110 | |
39 | ptmap[1].codec = 110 | |
40 | ptmap[1].pt = 96 | |
31 | 41 | |
32 | 42 | Generated CRCX message: |
33 | 43 | CRCX 1 23@mgw MGCP 1.0 |
34 | 44 | C: 2f |
35 | 45 | I: 11 |
36 | L: p:20, a:AMR, nt:IN | |
46 | L: p:20, a:GSM, nt:IN | |
47 | M: sendrecv | |
48 | ||
49 | Generated CRCX message (two codecs): | |
50 | CRCX 2 23@mgw MGCP 1.0 | |
51 | C: 2f | |
52 | I: 11 | |
53 | L: p:20, a:GSM;AMR, nt:IN | |
54 | M: sendrecv | |
55 | ||
56 | Generated CRCX message (three codecs, one with custom pt): | |
57 | CRCX 3 23@mgw MGCP 1.0 | |
58 | C: 2f | |
59 | I: 11 | |
60 | L: p:20, a:GSM;AMR;GSM-EFR, nt:IN | |
37 | 61 | M: sendrecv |
38 | 62 | |
39 | 63 | Generated MDCX message: |
40 | MDCX 2 23@mgw MGCP 1.0 | |
64 | MDCX 4 23@mgw MGCP 1.0 | |
41 | 65 | C: 2f |
42 | 66 | I: 11 |
43 | 67 | M: sendrecv |
47 | 71 | s=- |
48 | 72 | c=IN IP4 192.168.100.23 |
49 | 73 | t=0 0 |
50 | m=audio 1234 RTP/AVP 255 | |
74 | m=audio 1234 RTP/AVP 3 | |
75 | a=ptime:20 | |
76 | ||
77 | Generated MDCX message (two codecs): | |
78 | MDCX 5 23@mgw MGCP 1.0 | |
79 | C: 2f | |
80 | I: 11 | |
81 | M: sendrecv | |
82 | ||
83 | v=0 | |
84 | o=- 2f 23 IN IP4 127.0.0.1 | |
85 | s=- | |
86 | c=IN IP4 192.168.100.23 | |
87 | t=0 0 | |
88 | m=audio 1234 RTP/AVP 3 112 | |
89 | a=rtpmap:112 AMR/8000/1 | |
90 | a=ptime:20 | |
91 | ||
92 | Generated MDCX message (three codecs, one with custom pt): | |
93 | MDCX 6 23@mgw MGCP 1.0 | |
94 | C: 2f | |
95 | I: 11 | |
96 | M: sendrecv | |
97 | ||
98 | v=0 | |
99 | o=- 2f 23 IN IP4 127.0.0.1 | |
100 | s=- | |
101 | c=IN IP4 192.168.100.23 | |
102 | t=0 0 | |
103 | m=audio 1234 RTP/AVP 3 112 96 | |
104 | a=rtpmap:112 AMR/8000/1 | |
105 | a=rtpmap:96 GSM-EFR/8000/1 | |
106 | a=ptime:20 | |
51 | 107 | |
52 | 108 | Generated DLCX message: |
53 | DLCX 3 23@mgw MGCP 1.0 | |
109 | DLCX 7 23@mgw MGCP 1.0 | |
54 | 110 | C: 2f |
55 | 111 | I: 11 |
56 | 112 | |
57 | 113 | Generated AUEP message: |
58 | AUEP 4 23@mgw MGCP 1.0 | |
114 | AUEP 8 23@mgw MGCP 1.0 | |
59 | 115 | |
60 | 116 | Generated RSIP message: |
61 | RSIP 5 23@mgw MGCP 1.0 | |
117 | RSIP 9 23@mgw MGCP 1.0 | |
62 | 118 | |
63 | 119 | Overfolow test: |
64 | 120 | |
101 | 157 | test_sdp_section_start() test [8]: |
102 | 158 | |
103 | 159 | test_sdp_section_start() test [9]: |
160 | 110 => 96 | |
161 | 111 => 97 | |
162 | 112 => 98 | |
163 | 113 => 99 | |
164 | 96 <= 110 | |
165 | 97 <= 111 | |
166 | 98 <= 112 | |
167 | 99 <= 113 | |
168 | ||
169 | 0 => 0 | |
170 | 3 => 3 | |
171 | 8 => 8 | |
172 | 18 => 18 | |
173 | 0 <= 0 | |
174 | 3 <= 3 | |
175 | 8 <= 8 | |
176 | 18 <= 18 | |
177 | ||
178 | 110 => 96 | |
179 | 111 => 97 | |
180 | 112 => 98 | |
181 | 113 => 113 | |
182 | 0 => 0 | |
183 | 96 <= 110 | |
184 | 97 <= 111 | |
185 | 98 <= 112 | |
186 | 2 <= 2 | |
187 | 100 <= 100 | |
188 | ||
104 | 189 | Done |