mirror of
https://github.com/signalwire/freeswitch.git
synced 2026-10-04 18:44:05 +00:00
The RTCP NACK feedback handler used the packet's header length field as the loop bound instead of the byte count the caller already validated. An oversized length ran the loop past the fixed-size RTCP buffer, an out-of-bounds access. Clamp the entry count to the feedback words that fit in the received packet. Add an end-to-end test feeding an oversized-length NACK through the RTCP reader.
676 lines
24 KiB
C
676 lines
24 KiB
C
/*
|
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
|
* Copyright (C) 2005-2021, Anthony Minessale II <anthm@freeswitch.org>
|
|
*
|
|
* 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 <anthm@freeswitch.org>
|
|
* Portions created by the Initial Developer are Copyright (C)
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Dragos Oancea <dragos@signalwire.com>
|
|
*
|
|
* switch_rtp_pcap.c -- tests RTP stack using PCAP.
|
|
*/
|
|
|
|
|
|
#include <switch.h>
|
|
#include <test/switch_test.h>
|
|
|
|
/* 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 <pcap.h>
|
|
|
|
#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() */
|
|
#if 0
|
|
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;
|
|
}
|
|
|
|
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_OFFER);
|
|
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);
|
|
|
|
/* switch_media_handle_destroy() already destroyed the session's RTP session; clear both
|
|
pointers so callers and later tests never act on the freed objects. */
|
|
*psession = NULL;
|
|
rtp_session = NULL;
|
|
|
|
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_rtcp_nack_oversized_length)
|
|
{
|
|
switch_core_session_t *session = NULL;
|
|
switch_status_t status;
|
|
switch_socket_t *sock_rtp = NULL;
|
|
switch_sockaddr_t *sock_addr = NULL;
|
|
const char *str_err = NULL;
|
|
char rpacket[SWITCH_RECOMMENDED_BUFFER_SIZE];
|
|
switch_payload_t pt = { 0 };
|
|
switch_frame_flag_t frameflags = { 0 };
|
|
uint32_t rcvd_datalen;
|
|
switch_size_t send_len;
|
|
int i;
|
|
/* RTPFB NACK feedback: V=2/P=0/FMT=1, PT=205 (byte 0xCD -> header.pt 77), header.length
|
|
claims 0xFFFF words but only two FCI words are present. The reader must process two
|
|
entries (clamped to the datagram), not the 0xFFFF - 2 implied by the length field. */
|
|
unsigned char nack[20] = {
|
|
0x81, 0xcd, 0xff, 0xff, /* V/P/FMT=1, PT=205, length=0xFFFF */
|
|
0xde, 0xad, 0xbe, 0xef, /* sender SSRC */
|
|
0xca, 0xfe, 0xba, 0xbe, /* media SSRC */
|
|
0x00, 0x01, 0x00, 0x00, /* FCI word 0: PID=1, BLP=0 */
|
|
0x00, 0x02, 0x00, 0x00 /* FCI word 1: PID=2, BLP=0 */
|
|
};
|
|
|
|
status = rtp_test_start_call(&session);
|
|
if (status != SWITCH_STATUS_SUCCESS || !session) {
|
|
fst_fail("failed to start PCMU test call");
|
|
goto nack_oob_cleanup;
|
|
}
|
|
|
|
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);
|
|
if (!rtp_session) {
|
|
fst_fail("no RTP session");
|
|
goto nack_oob_cleanup;
|
|
}
|
|
|
|
switch_rtp_clear_flag(rtp_session, SWITCH_RTP_FLAG_PAUSE);
|
|
/* This test feeds only RTCP; drop the media timeout so the no-RTP reads don't return a
|
|
media-timeout error that the post-condition below would misread as a reader failure. */
|
|
switch_rtp_set_media_timeout(rtp_session, 0);
|
|
/* Accept the feedback packet from the test socket and route it through the video
|
|
NACK branch of the RTCP reader. */
|
|
switch_rtp_set_flag(rtp_session, SWITCH_RTP_FLAG_AUTOADJ);
|
|
switch_rtp_set_flag(rtp_session, SWITCH_RTP_FLAG_VIDEO);
|
|
switch_rtp_set_flag(rtp_session, SWITCH_RTP_FLAG_NACK);
|
|
|
|
if (switch_socket_create(&sock_rtp, AF_INET, SOCK_DGRAM, 0, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) {
|
|
fst_fail("failed to create test socket");
|
|
goto nack_oob_cleanup;
|
|
}
|
|
|
|
switch_sockaddr_new(&sock_addr, rx_host, audio_rx_port, switch_core_session_get_pool(session));
|
|
if (!sock_addr) {
|
|
fst_fail("failed to create sockaddr");
|
|
goto nack_oob_cleanup;
|
|
}
|
|
|
|
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);
|
|
|
|
/* rtcp-mux: the feedback arrives on the RTP socket. Send and read a few rounds so the
|
|
source address is adopted and the packet reaches the RTCP NACK parser. With the FCI
|
|
count clamped the parser stays in bounds; without it the read trips an out-of-bounds
|
|
access (ASan/Valgrind abort, or heap corruption). */
|
|
for (i = 0; i < 4; i++) {
|
|
send_len = sizeof(nack);
|
|
if (switch_socket_sendto(sock_rtp, sock_addr, MSG_CONFIRM, (const char *)nack, &send_len) != SWITCH_STATUS_SUCCESS) {
|
|
fst_fail("failed to send NACK packet");
|
|
goto nack_oob_cleanup;
|
|
}
|
|
|
|
rcvd_datalen = sizeof(rpacket);
|
|
status = switch_rtp_read(rtp_session, (void *)rpacket, &rcvd_datalen, &pt, &frameflags, io_flags);
|
|
}
|
|
|
|
/* In-band, synchronous post-condition: the read we just issued on this thread returned a
|
|
non-error status, i.e. the reader tolerated the malformed feedback rather than breaking.
|
|
The out-of-bounds access itself is caught by ASan/Valgrind on the read above. */
|
|
fst_xcheck(status != SWITCH_STATUS_GENERR && status != SWITCH_STATUS_FALSE,
|
|
"RTP reader tolerates an oversized RTCP NACK and keeps reading");
|
|
|
|
nack_oob_cleanup:
|
|
if (sock_rtp) switch_socket_close(sock_rtp);
|
|
/* rtp_test_end_call() -> switch_media_handle_destroy() destroys the RTP session. */
|
|
if (session) rtp_test_end_call(&session);
|
|
}
|
|
FST_TEST_END()
|
|
|
|
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()
|
|
|