diff --git a/CHANGES b/CHANGES index 173a41127c721d80a5c0a03f1c10f721218e3669..d24f840e57a53a6666a413494f2d7ea64eb69fb4 100644 --- a/CHANGES +++ b/CHANGES @@ -257,6 +257,13 @@ res_pjsip_sdp_rtp originate from the media address instead of the operating system's "primary" ip address. +res_rtp_asterisk +------------------ + * A new configuration section - ice_host_candidates - has been added to + rtp.conf, allowing automatically discovered ICE host candidates to be + overriden. This allows an Asterisk server behind a 1:1 NAT to send its + external IP as a host candidate rather than relying on STUN to discover it. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 13.6.0 to Asterisk 13.7.0 ------------ ------------------------------------------------------------------------------ diff --git a/configs/samples/rtp.conf.sample b/configs/samples/rtp.conf.sample index c22acaa9fa57f05921998e9406aac033a8cf8262..2ef5dd28a7cfcbe95c3078792eb7546888c2fcd1 100644 --- a/configs/samples/rtp.conf.sample +++ b/configs/samples/rtp.conf.sample @@ -58,3 +58,30 @@ rtpend=20000 ; ; Password used to authenticate with TURN relay server. ; turnpassword= +; +[ice_host_candidates] +; +; When Asterisk is behind a static one-to-one NAT and ICE is in use, ICE will +; expose the server's internal IP address as one of the host candidates. +; Although using STUN (see the 'stunaddr' configuration option) will provide a +; publicly accessible IP, the internal IP will still be sent to the remote +; peer. To help hide the topology of your internal network, you can override +; the host candidates that Asterisk will send to the remote peer. +; +; IMPORTANT: Only use this functionality when your Asterisk server is behind a +; one-to-one NAT and you know what you're doing. If you do define anything +; here, you almost certainly will NOT want to specify 'stunaddr' or 'turnaddr' +; above. +; +; The format for these overrides is: +; +; <local address> => <advertised address> +; +; The following will replace 192.168.1.10 with 1.2.3.4 during ICE +; negotiation: +; +;192.168.1.10 => 1.2.3.4 +; +; You can define an override for more than 1 interface if you have a multihomed +; server. Any local interface that is not matched will be passed through +; unaltered. Both IPv4 and IPv6 addresses are supported. diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index 85d997fc2ffaa2bf882eb388b44d69677ed02a9c..9c7432e8c0743bbd0f742627b35c59c4c45b667c 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -182,6 +182,16 @@ struct ast_rtp_ioqueue_thread { /*! \brief List of ioqueue threads */ static AST_LIST_HEAD_STATIC(ioqueues, ast_rtp_ioqueue_thread); +/*! \brief Structure which contains ICE host candidate mapping information */ +struct ast_ice_host_candidate { + pj_sockaddr local; + pj_sockaddr advertised; + AST_RWLIST_ENTRY(ast_ice_host_candidate) next; +}; + +/*! \brief List of ICE host candidate mappings */ +static AST_RWLIST_HEAD_STATIC(host_candidates, ast_ice_host_candidate); + #endif #define FLAG_3389_WARNING (1 << 0) @@ -451,6 +461,38 @@ static void dtls_srtp_stop_timeout_timer(struct ast_rtp_instance *instance, stru static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int rtcp, int *ice, int use_srtp); #ifdef HAVE_PJPROJECT +/*! \brief Helper function which clears the ICE host candidate mapping */ +static void host_candidate_overrides_clear(void) +{ + struct ast_ice_host_candidate *candidate; + + AST_RWLIST_WRLOCK(&host_candidates); + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&host_candidates, candidate, next) { + AST_RWLIST_REMOVE_CURRENT(next); + ast_free(candidate); + } + AST_RWLIST_TRAVERSE_SAFE_END; + AST_RWLIST_UNLOCK(&host_candidates); +} + +/*! \brief Applies the ICE host candidate mapping */ +static void host_candidate_overrides_apply(unsigned int count, pj_sockaddr addrs[]) +{ + int pos; + struct ast_ice_host_candidate *candidate; + + AST_RWLIST_RDLOCK(&host_candidates); + for (pos = 0; pos < count; pos++) { + AST_LIST_TRAVERSE(&host_candidates, candidate, next) { + if (!pj_sockaddr_cmp(&candidate->local, &addrs[pos])) { + pj_sockaddr_copy_addr(&addrs[pos], &candidate->advertised); + break; + } + } + } + AST_RWLIST_UNLOCK(&host_candidates); +} + /*! \brief Helper function which updates an ast_sockaddr with the candidate used for the component */ static void update_address_with_ice_candidate(struct ast_rtp *rtp, enum ast_rtp_ice_component_type component, struct ast_sockaddr *cand_address) @@ -2368,6 +2410,8 @@ static void rtp_add_candidates_to_ice(struct ast_rtp_instance *instance, struct pj_enum_ip_interface(pj_AF_INET6(), &count, address); } + host_candidate_overrides_apply(count, address); + for (pos = 0; pos < count; pos++) { pj_sockaddr_set_port(&address[pos], port); ast_rtp_ice_add_cand(rtp, component, transport, PJ_ICE_CAND_TYPE_HOST, 65535, &address[pos], &address[pos], NULL, @@ -5256,6 +5300,11 @@ static int rtp_reload(int reload) const char *s; struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; +#ifdef HAVE_PJPROJECT + struct ast_variable *var; + struct ast_ice_host_candidate *candidate; +#endif + cfg = ast_config_load2("rtp.conf", "rtp", config_flags); if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) { return 0; @@ -5282,6 +5331,7 @@ static int rtp_reload(int reload) turnaddr = pj_str(NULL); turnusername = pj_str(NULL); turnpassword = pj_str(NULL); + host_candidate_overrides_clear(); #endif if (cfg) { @@ -5361,6 +5411,36 @@ static int rtp_reload(int reload) if ((s = ast_variable_retrieve(cfg, "general", "turnpassword"))) { pj_strdup2_with_null(pool, &turnpassword, s); } + + AST_RWLIST_WRLOCK(&host_candidates); + for (var = ast_variable_browse(cfg, "ice_host_candidates"); var; var = var->next) { + struct ast_sockaddr local_addr, advertised_addr; + pj_str_t address; + + ast_sockaddr_setnull(&local_addr); + ast_sockaddr_setnull(&advertised_addr); + + if (ast_parse_arg(var->name, PARSE_ADDR | PARSE_PORT_IGNORE, &local_addr)) { + ast_log(LOG_WARNING, "Invalid local ICE host address: %s\n", var->name); + continue; + } + + if (ast_parse_arg(var->value, PARSE_ADDR | PARSE_PORT_IGNORE, &advertised_addr)) { + ast_log(LOG_WARNING, "Invalid advertised ICE host address: %s\n", var->value); + continue; + } + + if (!(candidate = ast_calloc(1, sizeof(*candidate)))) { + ast_log(LOG_ERROR, "Failed to allocate ICE host candidate mapping.\n"); + break; + } + + pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&address, ast_sockaddr_stringify(&local_addr)), &candidate->local); + pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&address, ast_sockaddr_stringify(&advertised_addr)), &candidate->advertised); + + AST_RWLIST_INSERT_TAIL(&host_candidates, candidate, next); + } + AST_RWLIST_UNLOCK(&host_candidates); #endif ast_config_destroy(cfg); } @@ -5463,6 +5543,7 @@ static int unload_module(void) ast_cli_unregister_multiple(cli_rtp, ARRAY_LEN(cli_rtp)); #ifdef HAVE_PJPROJECT + host_candidate_overrides_clear(); pj_thread_register_check(); rtp_terminate_pjproject(); #endif