diff --git a/daemon/main.c b/daemon/main.c index 6f9da95b5..8e4083756 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -308,6 +308,7 @@ static void options(int *argc, char ***argv) { double max_load = 0; double max_cpu = 0; char *dtmf_udp_ep = NULL; + char *endpoint_learning = NULL; GOptionEntry e[] = { { "table", 't', 0, G_OPTION_ARG_INT, &rtpe_config.kernel_table, "Kernel table to use", "INT" }, @@ -374,6 +375,7 @@ static void options(int *argc, char ***argv) { { "mysql-user", 0, 0, G_OPTION_ARG_STRING, &rtpe_config.mysql_user,"MySQL connection credentials", "USERNAME" }, { "mysql-pass", 0, 0, G_OPTION_ARG_STRING, &rtpe_config.mysql_pass,"MySQL connection credentials", "PASSWORD" }, { "mysql-query",0, 0, G_OPTION_ARG_STRING, &rtpe_config.mysql_query,"MySQL select query", "STRING" }, + { "endpoint-learning",0,0,G_OPTION_ARG_STRING, &endpoint_learning, "RTP endpoint learning algorithm", "delayed|immediate|off|heuristic" }, { NULL, } }; @@ -547,6 +549,21 @@ static void options(int *argc, char ***argv) { die("Too many '%%' placeholders (%u) present in --mysql-query='%s'", count, rtpe_config.mysql_query); } + + enum endpoint_learning el_config = EL_DELAYED; + if (endpoint_learning) { + if (!strcasecmp(endpoint_learning, "delayed")) + el_config = EL_DELAYED; + else if (!strcasecmp(endpoint_learning, "immediate")) + el_config = EL_IMMEDIATE; + else if (!strcasecmp(endpoint_learning, "off")) + el_config = EL_OFF; + else if (!strcasecmp(endpoint_learning, "heuristic")) + el_config = EL_HEURISTIC; + else + die("Invalid --endpoint-learning option ('%s')", endpoint_learning); + } + rtpe_config.endpoint_learning = el_config; } void fill_initial_rtpe_cfg(struct rtpengine_config* ini_rtpe_cfg) { @@ -608,6 +625,7 @@ void fill_initial_rtpe_cfg(struct rtpengine_config* ini_rtpe_cfg) { ini_rtpe_cfg->redis_ep = rtpe_config.redis_ep; ini_rtpe_cfg->redis_write_ep = rtpe_config.redis_write_ep; ini_rtpe_cfg->homer_ep = rtpe_config.homer_ep; + ini_rtpe_cfg->endpoint_learning = rtpe_config.endpoint_learning; ini_rtpe_cfg->b2b_url = g_strdup(rtpe_config.b2b_url); ini_rtpe_cfg->redis_auth = g_strdup(rtpe_config.redis_auth); diff --git a/daemon/media_socket.c b/daemon/media_socket.c index dffc70792..31b7d069d 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -1532,7 +1532,7 @@ static int media_packet_address_check(struct packet_handler_ctx *phc) } /* do not pay attention to source addresses of incoming packets for asymmetric streams */ - if (MEDIA_ISSET(phc->mp.media, ASYMMETRIC)) + if (MEDIA_ISSET(phc->mp.media, ASYMMETRIC) || rtpe_config.endpoint_learning == EL_OFF) PS_SET(phc->mp.stream, CONFIRMED); /* confirm sink for unidirectional streams in order to kernelize */ @@ -1574,22 +1574,55 @@ static int media_packet_address_check(struct packet_handler_ctx *phc) goto out; } + const struct endpoint *use_endpoint_confirm = &phc->mp.fsin; + + if (rtpe_config.endpoint_learning == EL_IMMEDIATE) + goto confirm_now; + + if (rtpe_config.endpoint_learning == EL_HEURISTIC + && phc->mp.stream->advertised_endpoint.address.family + && phc->mp.stream->advertised_endpoint.port) + { + // possible endpoints that can be detected in order of preference: + // 0: endpoint that matches the address advertised in the SDP + // 1: endpoint with the same address but different port + // 2: endpoint with the same port but different address + // 3: endpoint with both different port and different address + unsigned int idx = 0; + if (phc->mp.fsin.port != phc->mp.stream->advertised_endpoint.port) + idx |= 1; + if (memcmp(&phc->mp.fsin.address, &phc->mp.stream->advertised_endpoint.address, + sizeof(sockaddr_t))) + idx |= 2; + + // fill appropriate slot + phc->mp.stream->detected_endpoints[idx] = phc->mp.fsin; + + // now grab the best matched endpoint + for (idx = 0; idx < 4; idx++) { + use_endpoint_confirm = &phc->mp.stream->detected_endpoints[idx]; + if (use_endpoint_confirm->address.family) + break; + } + } + /* wait at least 3 seconds after last signal before committing to a particular * endpoint address */ if (!phc->mp.call->last_signal || rtpe_now.tv_sec <= phc->mp.call->last_signal + 3) goto update_peerinfo; +confirm_now: phc->kernelize = 1; phc->update = 1; - ilog(LOG_INFO, "Confirmed peer address as %s%s%s", FMT_M(endpoint_print_buf(&phc->mp.fsin))); + ilog(LOG_INFO, "Confirmed peer address as %s%s%s", FMT_M(endpoint_print_buf(use_endpoint_confirm))); PS_SET(phc->mp.stream, CONFIRMED); update_peerinfo: mutex_lock(&phc->mp.stream->out_lock); endpoint = phc->mp.stream->endpoint; - phc->mp.stream->endpoint = phc->mp.fsin; + phc->mp.stream->endpoint = *use_endpoint_confirm; if (memcmp(&endpoint, &phc->mp.stream->endpoint, sizeof(endpoint))) { phc->unkernelize = 1; phc->update = 1; diff --git a/daemon/rtpengine.pod b/daemon/rtpengine.pod index 146fe5c44..406e51f8f 100644 --- a/daemon/rtpengine.pod +++ b/daemon/rtpengine.pod @@ -655,6 +655,22 @@ An example configuration might look like this: mysql-query = select data from voip.files where id = %llu +=item B<--endpoint-learning=>B|B|B|B + +Chooses one of the available algorithms to learn RTP endpoint addresses. The +legacy setting is B which waits 3 seconds before committing to an +endpoint address, which is then learned from the first incoming RTP packet seen +after this delay. The setting B learns the endpoint address from the +first incoming packet seen without the 3-second delay. Using B disables +endpoint learning altogether, likely breaking clients behind NAT. The setting +B includes the 3-second delay, but source addresses seen from +incoming RTP packets are ranked according to preference: If a packet with a +source address and port matching the SDP address is seen, this address is used. +Otherwise, if a packet with a matching source address (but a different port) is +seen, that address is used. Otherwise, if a packet with a matching source port +(but different address) is seen, that address is used. Otherwise, the source +address of any incoming packet seen is used. + =back =head1 INTERFACES diff --git a/include/call.h b/include/call.h index 635ddba24..022ed8391 100644 --- a/include/call.h +++ b/include/call.h @@ -260,6 +260,7 @@ struct packet_stream { struct packet_stream *rtcp_sibling; /* LOCK: call->master_lock */ const struct streamhandler *handler; /* LOCK: in_lock */ struct endpoint endpoint; /* LOCK: out_lock */ + struct endpoint detected_endpoints[4]; /* LOCK: out_lock */ struct endpoint advertised_endpoint; /* RO */ struct crypto_context crypto; /* OUT direction, LOCK: out_lock */ struct ssrc_ctx *ssrc_in, /* LOCK: in_lock */ // XXX eliminate these diff --git a/include/main.h b/include/main.h index 1b689b620..1ac8feb41 100644 --- a/include/main.h +++ b/include/main.h @@ -18,6 +18,14 @@ enum log_format { __LF_LAST }; +enum endpoint_learning { + EL_DELAYED = 0, + EL_IMMEDIATE = 1, + EL_OFF = 2, + EL_HEURISTIC = 3, + + __EL_LAST +}; struct rtpengine_config { /* everything below protected by config_lock */ @@ -84,6 +92,7 @@ struct rtpengine_config { char *mysql_pass; char *mysql_query; endpoint_t dtmf_udp_ep; + enum endpoint_learning endpoint_learning; };