From beffab1d680104f2c469d0947a2a8b0e09d9c343 Mon Sep 17 00:00:00 2001 From: Dragos Oancea Date: Sun, 20 Mar 2022 17:25:37 +0300 Subject: [PATCH] [core] RTP: a media timeout fix + add pcap based-unit tests. --- configure.ac | 16 + src/switch_core_media.c | 12 +- src/switch_rtp.c | 2 +- tests/unit/Makefile.am | 6 + tests/unit/conf_rtp/freeswitch.xml | 73 +++ tests/unit/pcap/milliwatt.long.pcmu.rtp.pcap | Bin 0 -> 164014 bytes tests/unit/pcap/milliwatt.pcmu.rtp.pcap | Bin 0 -> 53384 bytes tests/unit/switch_rtp_pcap.c | 580 +++++++++++++++++++ 8 files changed, 687 insertions(+), 2 deletions(-) create mode 100644 tests/unit/conf_rtp/freeswitch.xml create mode 100644 tests/unit/pcap/milliwatt.long.pcmu.rtp.pcap create mode 100644 tests/unit/pcap/milliwatt.pcmu.rtp.pcap create mode 100644 tests/unit/switch_rtp_pcap.c diff --git a/configure.ac b/configure.ac index 4131e629a6..3ee7c58f9d 100644 --- a/configure.ac +++ b/configure.ac @@ -1970,6 +1970,22 @@ else AC_MSG_WARN([python3 support disabled, building mod_python3 will fail!]) fi +# pcap lib for unit-testing +AC_MSG_CHECKING(libpcap) +AC_CHECK_PROG(HAVE_PCAP_CONFIG,pcap-config,[true],[false]) +if test x"$HAVE_PCAP_CONFIG" = x"true"; then + AC_MSG_RESULT(yes) + PCAP_CONFIG=pcap-config + PCAP_LIBS="`$PCAP_CONFIG --libs`" + PCAP_CFLAGS="`$PCAP_CONFIG --cflags`" + AM_CONDITIONAL([HAVE_PCAP], [true]) +else + AC_MSG_RESULT(no) + AM_CONDITIONAL([HAVE_PCAP], [false]) +fi +AC_SUBST([PCAP_CFLAGS]) +AC_SUBST([PCAP_LIBS]) + # # SNMP checks for mod_snmp # diff --git a/src/switch_core_media.c b/src/switch_core_media.c index e27b81e166..347735cf1b 100644 --- a/src/switch_core_media.c +++ b/src/switch_core_media.c @@ -2882,8 +2882,18 @@ static void check_media_timeout_params(switch_core_session_t *session, switch_rt if (switch_rtp_ready(engine->rtp_session) && engine->media_timeout) { switch_rtp_set_media_timeout(engine->rtp_session, engine->media_timeout); - } + if (engine->type == SWITCH_MEDIA_TYPE_AUDIO) { + /* the values are in milliseconds, not in seconds as the deprecated rtp_timeout_sec */ + engine->max_missed_packets = (engine->read_impl.samples_per_second * engine->media_timeout / 1000) / engine->read_impl.samples_per_packet; + switch_rtp_set_max_missed_packets(engine->rtp_session, engine->max_missed_packets); + if (!engine->media_hold_timeout) { + engine->media_hold_timeout = engine->media_timeout * 10; + } + + engine->max_missed_hold_packets = (engine->read_impl.samples_per_second * engine->media_hold_timeout / 1000) / engine->read_impl.samples_per_packet; + } + } } SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session_t *session, switch_frame_t **frame, diff --git a/src/switch_rtp.c b/src/switch_rtp.c index a7bd2f8d83..5f4ce0f738 100644 --- a/src/switch_rtp.c +++ b/src/switch_rtp.c @@ -7350,7 +7350,7 @@ static void check_timeout(switch_rtp_t *rtp_session) } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_DEBUG10, - "%s MEDIA TIMEOUT %s %d/%d", switch_core_session_get_name(rtp_session->session), rtp_type(rtp_session), + "%s MEDIA TIMEOUT %s %d/%d\n", switch_core_session_get_name(rtp_session->session), rtp_type(rtp_session), elapsed, rtp_session->media_timeout); if (elapsed > rtp_session->media_timeout) { diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index d24e30f62b..e2cec69089 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -5,6 +5,12 @@ noinst_PROGRAMS = switch_event switch_hash switch_ivr_originate switch_utils swi noinst_PROGRAMS += switch_core_video switch_core_db switch_vad switch_packetizer switch_core_session test_sofia switch_ivr_async switch_core_asr switch_log noinst_PROGRAMS+= switch_hold switch_sip + +if HAVE_PCAP +noinst_PROGRAMS += switch_rtp_pcap +AM_LDFLAGS += $(PCAP_LIBS) +endif + AM_LDFLAGS += -avoid-version -no-undefined $(SWITCH_AM_LDFLAGS) $(openssl_LIBS) AM_LDFLAGS += $(FREESWITCH_LIBS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS) diff --git a/tests/unit/conf_rtp/freeswitch.xml b/tests/unit/conf_rtp/freeswitch.xml new file mode 100644 index 0000000000..69130f8f82 --- /dev/null +++ b/tests/unit/conf_rtp/freeswitch.xml @@ -0,0 +1,73 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + +
+
diff --git a/tests/unit/pcap/milliwatt.long.pcmu.rtp.pcap b/tests/unit/pcap/milliwatt.long.pcmu.rtp.pcap new file mode 100644 index 0000000000000000000000000000000000000000..36ed6a1779602d0846fa8cc3f5e5b73d2bdabd00 GIT binary patch literal 164014 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

VUF4hXe!69VdXEZ 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 M4&K}+VKc!04{qP;cmMzZ literal 0 HcmV?d00001 diff --git a/tests/unit/pcap/milliwatt.pcmu.rtp.pcap b/tests/unit/pcap/milliwatt.pcmu.rtp.pcap new file mode 100644 index 0000000000000000000000000000000000000000..8f68eaca98346a4eba5c4d4c77d71034ec9b4184 GIT binary patch literal 53384 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 literal 0 HcmV?d00001 diff --git a/tests/unit/switch_rtp_pcap.c b/tests/unit/switch_rtp_pcap.c new file mode 100644 index 0000000000..74b79f9a24 --- /dev/null +++ b/tests/unit/switch_rtp_pcap.c @@ -0,0 +1,580 @@ +/* +* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application +* Copyright (C) 2005-2021, Anthony Minessale II +* +* Version: MPL 1.1 +* +* The contents of this file are subject to the Mozilla Public License Version +* 1.1 (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* http://www.mozilla.org/MPL/ +* +* Software distributed under the License is distributed on an "AS IS" basis, +* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +* for the specific language governing rights and limitations under the +* License. +* +* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application +* +* The Initial Developer of the Original Code is +* Anthony Minessale II +* Portions created by the Initial Developer are Copyright (C) +* the Initial Developer. All Rights Reserved. +* +* Contributor(s): +* Dragos Oancea +* +* switch_rtp_pcap.c -- tests RTP stack using PCAP. +*/ + + +#include +#include + +/* before adding a pcap file: tcprewrite --dstipmap=X.X.X.X/32:192.168.0.1/32 --srcipmap=X.X.X.X/32:192.168.0.2/32 -i in.pcap -o out.pcap */ + +#include + +#ifndef MSG_CONFIRM +#define MSG_CONFIRM 0 +#endif + +static const char *rx_host = "127.0.0.1"; +static const char *tx_host = "127.0.0.1"; +static switch_rtp_t *rtp_session = NULL; +const char *err = NULL; +switch_rtp_packet_t rtp_packet; +switch_frame_flag_t *frame_flags; +switch_io_flag_t io_flags; +switch_payload_t read_pt; +static switch_port_t audio_rx_port = 1234; + +static int got_media_timeout = 0; + +//#define USE_RTCP_PCAP + +#define NTP_TIME_OFFSET 2208988800UL + +/* https://www.tcpdump.org/pcap.html */ +/* IP header */ +struct sniff_ip { + u_char ip_vhl; /* version << 4 | header length >> 2 */ + u_char ip_tos; /* type of service */ + u_short ip_len; /* total length */ + u_short ip_id; /* identification */ + u_short ip_off; /* fragment offset field */ +#define IP_RF 0x8000 /* reserved fragment flag */ +#define IP_DF 0x4000 /* dont fragment flag */ +#define IP_MF 0x2000 /* more fragments flag */ +#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ + u_char ip_ttl; /* time to live */ + u_char ip_p; /* protocol */ + u_short ip_sum; /* checksum */ + struct in_addr ip_src,ip_dst; /* source and dest address */ +}; + +#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f) + +/* switch_rtp.c - calc_local_lsr_now() */ +static inline uint32_t test_calc_local_lsr_now(switch_time_t now, uint32_t past /*milliseconds*/) +{ +// switch_time_t now; + uint32_t ntp_sec, ntp_usec, lsr_now, sec; +// now = switch_micro_time_now() - (past * 1000); + now = now - (past * 1000); + sec = (uint32_t)(now/1000000); /* convert to seconds */ + ntp_sec = sec+NTP_TIME_OFFSET; /* convert to NTP seconds */ + ntp_usec = (uint32_t)(now - ((switch_time_t) sec*1000000)); /* remove seconds to keep only the microseconds */ + + lsr_now = (uint32_t)(ntp_usec*0.065536) | (ntp_sec&0x0000ffff)<<16; /* 0.065536 is used for convertion from useconds to fraction of 65536 (x65536/1000000) */ + return lsr_now; +} + +#if 0 +static void test_prepare_rtcp(void *rtcp_packet, float est_last, uint32_t rtt, uint8_t loss) +{ + /* taken from switch_rtp.c, rtcp_generate_sender_info() */ + /* === */ + char *rtcp_sr_trigger = rtcp_packet; + switch_time_t now; + uint32_t sec, ntp_sec, ntp_usec; + uint32_t ntp_msw; + uint32_t ntp_lsw; + uint32_t *ptr_msw; + uint32_t *ptr_lsw; + uint32_t lsr; + uint32_t *ptr_lsr; + uint32_t dlsr = 0; + uint32_t *ptr_dlsr; + uint8_t *ptr_loss; + + now = switch_micro_time_now(); + sec = (uint32_t)(now/1000000); /* convert to seconds */ + ntp_sec = sec+NTP_TIME_OFFSET; /* convert to NTP seconds */ + ntp_msw = htonl(ntp_sec); /* store result in "most significant word" */ + ntp_usec = (uint32_t)(now - (sec*1000000)); /* remove seconds to keep only the microseconds */ + ntp_lsw = htonl((u_long)(ntp_usec*(double)(((uint64_t)1)<<32)*1.0e-6)); + + /* === */ + + /*patch the RTCP payload to set the RTT we want */ + + ptr_msw = (uint32_t *)rtcp_sr_trigger + 2; + *ptr_msw = ntp_msw; + + ptr_lsw = (uint32_t *)rtcp_sr_trigger + 3; + *ptr_lsw = ntp_lsw; + + lsr = test_calc_local_lsr_now(now, est_last * 1000 + rtt /*ms*/); + + ptr_lsr = (uint32_t *)rtcp_sr_trigger + 11; + *ptr_lsr = htonl(lsr); + + ptr_dlsr = (uint32_t *)rtcp_sr_trigger + 12; + *ptr_dlsr = htonl(dlsr); + + ptr_loss = (uint8_t *)rtcp_sr_trigger + 32; + *ptr_loss = loss; +} +#endif + +static switch_status_t rtp_test_start_call(switch_core_session_t **psession) +{ + char *r_sdp; + uint8_t match = 0, p = 0; + switch_core_session_t *session; + switch_channel_t *channel = NULL; + switch_status_t status; + switch_media_handle_t *media_handle; + switch_core_media_params_t *mparams; + switch_stream_handle_t stream = { 0 }; + switch_call_cause_t cause; + + /*tone stream extension*/ + status = switch_ivr_originate(NULL, psession, &cause, "null/+1234", 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL); + session = *psession; + + if (!(session)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "no session\n"); + return SWITCH_STATUS_FALSE; + } + + if (status != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_ivr_originate() failed\n"); + return SWITCH_STATUS_FALSE; + } + + channel = switch_core_session_get_channel(session); + if (!channel) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_core_session_get_channel() failed\n"); + return SWITCH_STATUS_FALSE; + } + mparams = switch_core_session_alloc(session, sizeof(switch_core_media_params_t)); + mparams->inbound_codec_string = switch_core_session_strdup(session, "PCMU"); + mparams->outbound_codec_string = switch_core_session_strdup(session, "PCMU"); + mparams->rtpip = switch_core_session_strdup(session, (char *)rx_host); + + status = switch_media_handle_create(&media_handle, session, mparams); + if (status != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_media_handle_create() failed\n"); + return SWITCH_STATUS_FALSE; + } + + switch_channel_set_variable(channel, "absolute_codec_string", "PCMU"); + switch_channel_set_variable(channel, "send_silence_when_idle", "-1"); + switch_channel_set_variable(channel, "rtp_timer_name", "soft"); + switch_channel_set_variable(channel, "media_timeout", "1000"); + + switch_channel_set_variable(channel, SWITCH_LOCAL_MEDIA_IP_VARIABLE, rx_host); + switch_channel_set_variable_printf(channel, SWITCH_LOCAL_MEDIA_PORT_VARIABLE, "%d", audio_rx_port); + + r_sdp = switch_core_session_sprintf(session, + "v=0\n" + "o=FreeSWITCH 1632033305 1632033306 IN IP4 %s\n" + "s=-\n" + "c=IN IP4 %s\n" + "t=0 0\n" + "m=audio 11114 RTP/AVP 0 101\n" + "a=rtpmap:0 PCMU/8000\n" + "a=rtpmap:101 telephone-event/8000\n" + "a=rtcp-mux\n", + tx_host, tx_host); + + status = switch_core_media_prepare_codecs(session, SWITCH_FALSE); + if (status != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_core_media_prepare_codecs() failed\n"); + return SWITCH_STATUS_FALSE; + } + + match = switch_core_media_negotiate_sdp(session, r_sdp, &p, SDP_TYPE_REQUEST); + if (match != 1) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_core_media_negotiate_sdp() failed\n"); + return SWITCH_STATUS_FALSE; + } + + status = switch_core_media_choose_ports(session, SWITCH_TRUE, SWITCH_FALSE); + if (status != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_core_media_choose_ports() failed\n"); + return SWITCH_STATUS_FALSE; + } + + status = switch_core_media_activate_rtp(session); + if (status != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "switch_core_media_activate_rtp() failed\n"); + return SWITCH_STATUS_FALSE; + } + + switch_core_media_set_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_DEBUG_RTP_READ); + switch_core_media_set_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_DEBUG_RTP_WRITE); + + SWITCH_STANDARD_STREAM(stream); + switch_api_execute("fsctl", "debug_level 10", session, &stream); + switch_safe_free(stream.data); + + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t rtp_test_end_call(switch_core_session_t **psession) +{ + switch_channel_t *channel = NULL; + switch_core_session_t *session = *psession; + + channel = switch_core_session_get_channel(session); + if (!channel) { + return SWITCH_STATUS_FALSE; + } + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_media_handle_destroy(session); + switch_core_session_rwunlock(session); + + return SWITCH_STATUS_SUCCESS; +} + +static void rtp_test_init_frame(switch_frame_t **pwrite_frame, switch_core_session_t **psession) +{ + const unsigned char hdr_packet[]="\x80\x00\xcd\x15\xfd\x86\x00\x00\x61\x5a\xe1\x37"; + + switch_frame_alloc(pwrite_frame, SWITCH_RECOMMENDED_BUFFER_SIZE); + (*pwrite_frame)->codec = switch_core_session_get_write_codec(*psession); + + (*pwrite_frame)->datalen = SWITCH_RTP_HEADER_LEN; /*init with dummy RTP header*/ + memcpy((*pwrite_frame)->data, &hdr_packet, SWITCH_RTP_HEADER_LEN); +} + +static void show_event(switch_event_t *event) { + char *str; + /*print the event*/ + switch_event_serialize_json(event, &str); + if (str) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s\n", str); + switch_safe_free(str); + } +} + +static void event_handler(switch_event_t *event) +{ + const char *new_ev = switch_event_get_header(event, "Event-Name"); + + if (new_ev && !strcmp(new_ev, "CHANNEL_HANGUP")) { + if (!strcmp(switch_event_get_header(event, "Hangup-Cause"), "MEDIA_TIMEOUT")) { + got_media_timeout = 1; + } + } + + show_event(event); +} + +FST_CORE_DB_BEGIN("./conf_rtp") +{ +FST_SUITE_BEGIN(switch_rtp_pcap) +{ + +FST_SETUP_BEGIN() +{ + fst_requires_module("mod_loopback"); +} +FST_SETUP_END() + +FST_TEARDOWN_BEGIN() +{ +} +FST_TEARDOWN_END() +#if 0 + FST_TEST_BEGIN(test_rtp_stall_with_rtcp_muxed_with_timer) + { + switch_core_session_t *session = NULL; + switch_status_t status; + uint32_t plen = SWITCH_RTP_HEADER_LEN; + char rpacket[SWITCH_RECOMMENDED_BUFFER_SIZE]; + switch_payload_t pt = { 0 }; + switch_frame_flag_t frameflags = { 0 }; + int x = 0; + switch_frame_t *write_frame; + pcap_t *pcap; + const unsigned char *packet; + char errbuf[PCAP_ERRBUF_SIZE]; + struct pcap_pkthdr pcap_header; + char rtcp_sr_trigger[] = "\x81\xc8\x00\x0c\x78\x9d\xac\x45\xe2\x67\xa5\x74\x30\x60\x56\x81\x00\x19" + "\xaa\x00\x00\x00\x06\xd7\x00\x01\x2c\x03\x5e\xbd\x2f\x0b\x00" + "\x00\x00\x00\x00\x00\x57\xc4\x00\x00\x00\x39\xa5\x73\xfe\x90\x00\x00\x2c\x87" + "\x81\xca\x00\x0c\x78\x9d\xac\x45\x01\x18\x73\x69\x70\x3a\x64\x72\x40\x31\x39\x32\x2e" + "\x31\x36\x38\x2e\x30\x2e\x31\x33\x3a\x37\x30\x36\x30\x06\x0e\x4c\x69\x6e\x70\x68\x6f" + "\x6e\x65\x2d\x33\x2e\x36\x2e\x31\x00\x00"; + const struct sniff_ip *ip; /* The IP header */ + int size_ip, jump_over; + struct timeval prev_ts = { 0 }; + switch_time_t time_nowpacket = 0, time_prevpacket = 0; + switch_socket_t *sock_rtp = NULL; + switch_sockaddr_t *sock_addr = NULL; + const char *str_err; + switch_size_t rough_add = 0; + + status = rtp_test_start_call(&session); + fst_requires(status == SWITCH_STATUS_SUCCESS); + fst_requires(session); + + pcap = pcap_open_offline_with_tstamp_precision("pcap/milliwatt.long.pcmu.rtp.pcap", PCAP_TSTAMP_PRECISION_MICRO, errbuf); + fst_requires(pcap); + + switch_core_media_set_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_ENABLE_RTCP); + + rtp_session = switch_core_media_get_rtp_session(session, SWITCH_MEDIA_TYPE_AUDIO); + + rtp_test_init_frame(&write_frame, &session); + + switch_rtp_clear_flag(rtp_session, SWITCH_RTP_FLAG_PAUSE); + + if (switch_socket_create(&sock_rtp, AF_INET, SOCK_DGRAM, 0, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + fst_requires(0); /*exit*/ + } + + switch_sockaddr_new(&sock_addr, rx_host, audio_rx_port, switch_core_session_get_pool(session)); + fst_requires(sock_addr); + + switch_rtp_set_remote_address(rtp_session, tx_host, switch_sockaddr_get_port(sock_addr), 0, SWITCH_FALSE, &str_err); + switch_rtp_reset(rtp_session); + + while ((packet = pcap_next(pcap, &pcap_header))) { + /*assume only UDP/RTP packets in the pcap*/ + uint32_t rcvd_datalen = pcap_header.caplen; + size_t len; + switch_size_t tmp_len; + + int diff_us = (pcap_header.ts.tv_sec-prev_ts.tv_sec)*1000000+(pcap_header.ts.tv_usec-prev_ts.tv_usec); + + if (diff_us > 0) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "SENT pkt diff: %d us\n", diff_us); + usleep(diff_us); + } + + prev_ts = pcap_header.ts; + + len = pcap_header.caplen; + + if (len <= 42) { + continue; + } + + ip = (struct sniff_ip*)(packet + 14); + size_ip = IP_HL(ip) * 4; + + jump_over = 14 /*SIZE_ETHERNET*/ + size_ip /*IP HDR size*/ + 8 /* UDP HDR SIZE */; /* jump 42 bytes over network layers/headers */ + packet += jump_over; + x++; + + if (!(x%10)) { /* send a RTCP SR packet every 10th RTP packet */ + int add_rtt = 200; + test_prepare_rtcp(&rtcp_sr_trigger, 2, add_rtt, 0xa0); + tmp_len = sizeof(rtcp_sr_trigger); + /*RTCP muxed*/ + if (switch_socket_sendto(sock_rtp, sock_addr, MSG_CONFIRM, (const char*)rtcp_sr_trigger, &tmp_len) != SWITCH_STATUS_SUCCESS) { + fst_requires(0); + } + + plen = sizeof(rtcp_sr_trigger); + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Sent RTCP. Packet size = [%u]\n", plen); + status = switch_rtp_read(rtp_session, (void *)rpacket, &rcvd_datalen, &pt, &frameflags, io_flags); + if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG/RTCP, skip\n"); + while (1) { + status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags); + if (frameflags || SFF_RTCP) break; + } + } + fst_requires(status == SWITCH_STATUS_SUCCESS); + } + + if (packet[0] == 0x80 && packet[1] == 0 /*PCMU*/) { + int16_t *seq = (int16_t *)packet + 1; + plen = len - jump_over; + tmp_len = plen; + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Sent RTP. Packet size = [%u] seq = [%d]\n", plen, htons(*seq)); + if (switch_socket_sendto(sock_rtp, sock_addr, MSG_CONFIRM, (const char*)packet, &tmp_len) != SWITCH_STATUS_SUCCESS) { + fst_requires(0); + } + } + + status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags); + if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG, skip\n"); + continue; + } + time_prevpacket = time_nowpacket; + time_nowpacket = switch_time_now(); + if (time_prevpacket) { // skip init. + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "RECV pkt diff: %ld us\n", time_nowpacket - time_prevpacket); + + fst_requires((time_nowpacket - time_prevpacket) < 80000); + rough_add += time_nowpacket - time_prevpacket; /* just add to var for visual comparison */ + } + fst_requires(status == SWITCH_STATUS_SUCCESS); + if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) continue; + fst_requires(rcvd_datalen == plen - SWITCH_RTP_HEADER_LEN); + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "RECV total delay: %lu\n", rough_add); /*around 17092408 us*/ + switch_yield(1000 * 1000); + + if (write_frame) switch_frame_free(&write_frame); + + switch_rtp_destroy(&rtp_session); + + rtp_test_end_call(&session); + + switch_socket_close(sock_rtp); + + pcap_close(pcap); + + switch_yield(1000 * 1000); + } + FST_TEST_END() +#endif + + FST_TEST_BEGIN(test_rtp_media_timeout) + { + switch_core_session_t *session = NULL; + switch_status_t status; + uint32_t plen = SWITCH_RTP_HEADER_LEN; + char rpacket[SWITCH_RECOMMENDED_BUFFER_SIZE]; + switch_payload_t pt = { 0 }; + switch_frame_flag_t frameflags = { 0 }; + int x = 0; + switch_frame_t *write_frame; + pcap_t *pcap; + const unsigned char *packet; + char errbuf[PCAP_ERRBUF_SIZE]; + struct pcap_pkthdr pcap_header; + const struct sniff_ip *ip; /* The IP header */ + int size_ip, jump_over; + struct timeval prev_ts = { 0 }; + switch_socket_t *sock_rtp = NULL; + switch_sockaddr_t *sock_addr = NULL; + const char *str_err; + + status = rtp_test_start_call(&session); + fst_requires(status == SWITCH_STATUS_SUCCESS); + fst_requires(session); + + switch_event_bind("", SWITCH_EVENT_ALL, SWITCH_EVENT_SUBCLASS_ANY, event_handler, NULL); + + pcap = pcap_open_offline_with_tstamp_precision("pcap/milliwatt.pcmu.rtp.pcap", PCAP_TSTAMP_PRECISION_MICRO, errbuf); + fst_requires(pcap); + + switch_core_media_set_rtp_flag(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_RTP_FLAG_ENABLE_RTCP); + + rtp_session = switch_core_media_get_rtp_session(session, SWITCH_MEDIA_TYPE_AUDIO); + fst_requires(rtp_session); + + rtp_test_init_frame(&write_frame, &session); + + switch_rtp_clear_flag(rtp_session, SWITCH_RTP_FLAG_PAUSE); + + if (switch_socket_create(&sock_rtp, AF_INET, SOCK_DGRAM, 0, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + fst_requires(0); /*exit*/ + } + + switch_sockaddr_new(&sock_addr, rx_host, audio_rx_port, switch_core_session_get_pool(session)); + fst_requires(sock_addr); + + switch_rtp_set_remote_address(rtp_session, tx_host, switch_sockaddr_get_port(sock_addr), 0, SWITCH_FALSE, &str_err); + switch_rtp_reset(rtp_session); + + /* send 3 packets then wait and expect RTP timeout */ + while ((packet = pcap_next(pcap, &pcap_header)) && x < 3) { + /*assume only UDP/RTP packets in the pcap*/ + uint32_t rcvd_datalen = pcap_header.caplen; + size_t len; + switch_size_t tmp_len; + + int diff_us = (pcap_header.ts.tv_sec-prev_ts.tv_sec)*1000000+(pcap_header.ts.tv_usec-prev_ts.tv_usec); + if (diff_us > 0) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "SENT pkt diff: %d us\n", diff_us); + usleep(diff_us); + } + + x++; + + prev_ts = pcap_header.ts; + + len = pcap_header.caplen; + + if (len <= 42) { + continue; + } + + ip = (struct sniff_ip*)(packet + 14); + size_ip = IP_HL(ip) * 4; + + jump_over = 14 /*SIZE_ETHERNET*/ + size_ip /*IP HDR size*/ + 8 /* UDP HDR SIZE */; /* jump 42 bytes over network layers/headers */ + packet += jump_over; + + if (packet[0] == 0x80 && packet[1] == 0 /*PCMU*/) { + int16_t *seq = (int16_t *)packet + 1; + plen = len - jump_over; + tmp_len = plen; + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Sent RTP. Packet size = [%u] seq = [%d]\n", plen, htons(*seq)); + if (switch_socket_sendto(sock_rtp, sock_addr, MSG_CONFIRM, (const char*)packet, &tmp_len) != SWITCH_STATUS_SUCCESS) { + fst_requires(0); + } + } + + status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags); + if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG, skip\n"); + continue; + } + + fst_requires(status == SWITCH_STATUS_SUCCESS); + fst_requires(rcvd_datalen == plen - SWITCH_RTP_HEADER_LEN); + } + + x = 150; /* 3 seconds max */ + while (x || !got_media_timeout) { + uint32_t rcvd_datalen; + status = switch_rtp_read(rtp_session, (void *)&rpacket, &rcvd_datalen, &pt, &frameflags, io_flags); + if (pt == SWITCH_RTP_CNG_PAYLOAD /*timeout*/) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "read CNG, skip\n"); + } + switch_yield(20 * 1000); + fst_requires(status == SWITCH_STATUS_SUCCESS); + x--; + } + + if (write_frame) switch_frame_free(&write_frame); + + switch_rtp_destroy(&rtp_session); + + rtp_test_end_call(&session); + + switch_socket_close(sock_rtp); + + pcap_close(pcap); + + fst_check(got_media_timeout); + } + FST_TEST_END() +} +FST_SUITE_END() +} +FST_CORE_END() +