LazyAddrIterator::LazyAddrIterator(DnsResult& dns_result, BaseResolver* resolver, int port, int transport, SAS::TrailId trail) : _resolver(resolver), _trail(trail), _first_call(true) { _hostname = dns_result.domain(); AddrInfo ai; ai.port = port; ai.transport = transport; // Reserve memory for the unused results vector to avoid reallocation. _unused_results.reserve(dns_result.records().size()); // Create an AddrInfo object for each returned DnsRRecord, and add them to the // query results vector. for (std::vector<DnsRRecord*>::const_iterator result_it = dns_result.records().begin(); result_it != dns_result.records().end(); ++result_it) { ai.address = _resolver->to_ip46(*result_it); _unused_results.push_back(ai); } // Shuffle the results for load balancing purposes std::random_shuffle(_unused_results.begin(), _unused_results.end()); }
BaseAddrIterator* BaseResolver::a_resolve_iter(const std::string& hostname, int af, int port, int transport, int& ttl, SAS::TrailId trail) { DnsResult result = _dns_client->dns_query(hostname, (af == AF_INET) ? ns_t_a : ns_t_aaaa, trail); ttl = result.ttl(); TRC_DEBUG("Found %ld A/AAAA records, creating iterator", result.records().size()); return new LazyAddrIterator(result, this, port, transport, trail); }
BaseResolver::SRVPriorityList* BaseResolver::SRVCacheFactory::get(std::string key, int& ttl, SAS::TrailId trail) { TRC_DEBUG("SRV cache factory called for %s", key.c_str()); SRVPriorityList* srv_list = NULL; DnsResult result = _dns_client->dns_query(key, ns_t_srv, trail); if (!result.records().empty()) { // We have a result. TRC_DEBUG("SRV query returned %d records", result.records().size()); srv_list = new SRVPriorityList; ttl = result.ttl(); // Sort the records on priority. std::sort(result.records().begin(), result.records().end(), compare_srv_priority); // Now rearrange the results in to an SRV priority list (a map of vectors // for each priority level). for (std::vector<DnsRRecord*>::const_iterator i = result.records().begin(); i != result.records().end(); ++i) { DnsSrvRecord* srv_record = (DnsSrvRecord*)(*i); // Get the appropriate priority list of SRVs. std::vector<SRV>& plist = (*srv_list)[srv_record->priority()]; // Add a new entry for this SRV. plist.push_back(SRV()); SRV& srv = plist.back(); srv.target = srv_record->target(); srv.port = srv_record->port(); srv.priority = srv_record->priority(); srv.weight = srv_record->weight(); // Adjust the weight. Any items which have weight 0 are increase to // weight of one, and non-zero weights are multiplied by 100. This gives // the right behaviour as per RFC2782 - when all weights are zero we // round-robin (but still have the ability to blacklist) and when there // are non-zero weights the zero weighted items have a small (but not // specified in RFC2782) chance of selection. srv.weight = (srv.weight == 0) ? 1 : srv.weight * 100; } } else { // No results from SRV query, so return no entry with the default TTL ttl = _default_ttl; } return srv_list; }
void SIPResolver::resolve(const std::string& name, int af, int port, int transport, int retries, std::vector<AddrInfo>& targets, SAS::TrailId trail) { int dummy_ttl = 0; targets.clear(); // First determine the transport following the process in RFC3263 section // 4.1. AddrInfo ai; TRC_DEBUG("SIPResolver::resolve for name %s, port %d, transport %d, family %d", name.c_str(), port, transport, af); if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_START, 0); event.add_var_param(name); std::string port_str = std::to_string(port); std::string transport_str = get_transport_str(transport); event.add_var_param(port_str); event.add_var_param(transport_str); SAS::report_event(event); } if (parse_ip_target(name, ai.address)) { // The name is already an IP address, so no DNS resolution is possible. // Use specified transport and port or defaults if not specified. TRC_DEBUG("Target is an IP address - default port/transport if required"); ai.transport = (transport != -1) ? transport : IPPROTO_UDP; ai.port = (port != 0) ? port : 5060; targets.push_back(ai); if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_IP_ADDRESS, 0); event.add_var_param(name); std::string port_str = std::to_string(ai.port); std::string transport_str = get_transport_str(ai.transport); event.add_var_param(transport_str); event.add_var_param(port_str); SAS::report_event(event); } } else { std::string srv_name; std::string a_name = name; if (port != 0) { // Port is specified, so don't do NAPTR or SRV look-ups. Default transport // if required and move straight to A record look-up. TRC_DEBUG("Port is specified"); transport = (transport != -1) ? transport : IPPROTO_UDP; if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_PORT_A_LOOKUP, 0); event.add_var_param(name); std::string port_str = std::to_string(port); std::string transport_str = get_transport_str(transport); event.add_var_param(transport_str); event.add_var_param(port_str); SAS::report_event(event); } } else if (transport == -1) { // Transport protocol isn't specified, so do a NAPTR lookup for the target. TRC_DEBUG("Do NAPTR look-up for %s", name.c_str()); if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_NAPTR_LOOKUP, 0); event.add_var_param(name); SAS::report_event(event); } NAPTRReplacement* naptr = _naptr_cache->get(name, dummy_ttl, trail); if (naptr != NULL) { // NAPTR resolved to a supported service TRC_DEBUG("NAPTR resolved to transport %d", naptr->transport); transport = naptr->transport; if (strcasecmp(naptr->flags.c_str(), "S") == 0) { // Do an SRV lookup with the replacement domain from the NAPTR lookup. srv_name = naptr->replacement; if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_NAPTR_SUCCESS_SRV, 0); event.add_var_param(name); event.add_var_param(srv_name); std::string transport_str = get_transport_str(naptr->transport); event.add_var_param(transport_str); SAS::report_event(event); } } else { // Move straight to A/AAAA lookup of the domain returned by NAPTR. a_name = naptr->replacement; if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_NAPTR_SUCCESS_A, 0); event.add_var_param(name); event.add_var_param(a_name); SAS::report_event(event); } } } else { // NAPTR resolution failed, so do SRV lookups for both UDP and TCP to // see which transports are supported. TRC_DEBUG("NAPTR lookup failed, so do SRV lookups for UDP and TCP"); if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_NAPTR_FAILURE, 0); event.add_var_param(name); SAS::report_event(event); } std::vector<std::string> domains; domains.push_back("_sip._udp." + name); domains.push_back("_sip._tcp." + name); std::vector<DnsResult> results; _dns_client->dns_query(domains, ns_t_srv, results, trail); DnsResult& udp_result = results[0]; TRC_DEBUG("UDP SRV record %s returned %d records", udp_result.domain().c_str(), udp_result.records().size()); DnsResult& tcp_result = results[1]; TRC_DEBUG("TCP SRV record %s returned %d records", tcp_result.domain().c_str(), tcp_result.records().size()); if (!udp_result.records().empty()) { // UDP SRV lookup returned some records, so use UDP transport. TRC_DEBUG("UDP SRV lookup successful, select UDP transport"); transport = IPPROTO_UDP; srv_name = udp_result.domain(); } else if (!tcp_result.records().empty()) { // TCP SRV lookup returned some records, so use TCP transport. TRC_DEBUG("TCP SRV lookup successful, select TCP transport"); transport = IPPROTO_TCP; srv_name = tcp_result.domain(); } else { // Neither UDP nor TCP SRV lookup returned any results, so default to // UDP transport and move straight to A/AAAA record lookups. TRC_DEBUG("UDP and TCP SRV queries unsuccessful, default to UDP"); transport = IPPROTO_UDP; } } _naptr_cache->dec_ref(name); } else if (transport == IPPROTO_UDP) { // Use specified transport and try an SRV lookup. if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_TRANSPORT_SRV_LOOKUP, 0); event.add_var_param(name); std::string transport_str = get_transport_str(transport); event.add_var_param(transport_str); SAS::report_event(event); } DnsResult result = _dns_client->dns_query("_sip._udp." + name, ns_t_srv, trail); if (!result.records().empty()) { srv_name = result.domain(); } } else if (transport == IPPROTO_TCP) { // Use specified transport and try an SRV lookup. if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_TRANSPORT_SRV_LOOKUP, 0); event.add_var_param(name); std::string transport_str = get_transport_str(transport); event.add_var_param(transport_str); SAS::report_event(event); } DnsResult result = _dns_client->dns_query("_sip._tcp." + name, ns_t_srv, trail); if (!result.records().empty()) { srv_name = result.domain(); } } if (srv_name != "") { TRC_DEBUG("Do SRV lookup for %s", srv_name.c_str()); if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_SRV_LOOKUP, 0); event.add_var_param(srv_name); std::string transport_str = get_transport_str(transport); event.add_var_param(transport_str); SAS::report_event(event); } srv_resolve(srv_name, af, transport, retries, targets, dummy_ttl, trail); } else { TRC_DEBUG("Perform A/AAAA record lookup only, name = %s", a_name.c_str()); port = (port != 0) ? port : 5060; if (trail != 0) { SAS::Event event(trail, SASEvent::SIPRESOLVE_A_LOOKUP, 0); event.add_var_param(a_name); std::string transport_str = get_transport_str(transport); std::string port_str = std::to_string(port); event.add_var_param(transport_str); event.add_var_param(port_str); SAS::report_event(event); } a_resolve(a_name, af, port, transport, retries, targets, dummy_ttl, trail); } } }
BaseResolver::NAPTRReplacement* BaseResolver::NAPTRCacheFactory::get(std::string key, int& ttl, SAS::TrailId trail) { // Iterate NAPTR lookups starting with querying the target domain until // we get a terminal result. TRC_DEBUG("NAPTR cache factory called for %s", key.c_str()); NAPTRReplacement* repl = NULL; std::string query_key = key; int expires = 0; bool loop_again = true; while (loop_again) { // Assume this is our last loop - we'll correct this if we find we should go round again. loop_again = false; // Issue the NAPTR query. TRC_DEBUG("Sending DNS NAPTR query for %s", query_key.c_str()); DnsResult result = _dns_client->dns_query(query_key, ns_t_naptr, trail); if (!result.records().empty()) { // Process the NAPTR records as per RFC2915 section 4. First step is to // collect the records that match one of the requested services and have // acceptable flags. std::vector<DnsNaptrRecord*> filtered; for (std::vector<DnsRRecord*>::const_iterator i = result.records().begin(); i != result.records().end(); ++i) { DnsNaptrRecord* naptr = (DnsNaptrRecord*)(*i); if ((_services.find(naptr->service()) != _services.end()) && ((strcasecmp(naptr->flags().c_str(), "S") == 0) || (strcasecmp(naptr->flags().c_str(), "A") == 0) || (strcasecmp(naptr->flags().c_str(), "") == 0))) { // This record has passed the filter. filtered.push_back(naptr); } } // Now sort the resulting filtered list on order and preference. std::sort(filtered.begin(), filtered.end(), BaseResolver::NAPTRCacheFactory::compare_naptr_order_preference); // Now loop through the records looking for the first match - where a // match is either a record with a replacement string, or a regular // expression which matches the input. Note that we must always use // the originally specified domain in the regex - using the result of // a previous regex application is not allowed. for (size_t ii = 0; ii < filtered.size(); ++ii) { DnsNaptrRecord* naptr = filtered[ii]; std::string replacement = naptr->replacement(); if ((replacement == "") && (naptr->regexp() != "")) { // Record has no replacement value, but does have a regular expression. boost::regex regex; std::string replace; if (parse_regex_replace(naptr->regexp(), regex, replace)) { // We have a valid regex so try to match it to the original // queried domain to generate a new key. Note that going straight // to replace is okay. We haven't set the format_no_copy flag so // parts of the string that do not match the regex will not be // copied, so no match means we end up with an empty string. replacement = boost::regex_replace(key, regex, replace, boost::regex_constants::format_first_only); } } // Update ttl with the expiry of this record, so if we do get // a positive result we only cache it while all of the NAPTR records // we followed are still valid. if ((expires == 0) || (expires > naptr->expires())) { expires = naptr->expires(); } if (replacement != "") { // We have a match, so we can terminate processing here and look // at the flags to decide what to do next. if (strcasecmp(naptr->flags().c_str(), "") == 0) { // We need to iterate the NAPTR query. query_key = replacement; loop_again = true; } else { // This is a terminal record, so set up the result. repl = new NAPTRReplacement; repl->replacement = replacement; repl->flags = naptr->flags(); repl->transport = _services[naptr->service()]; ttl = expires - time(NULL); } break; } } } else { // No NAPTR record found, so return no entry with the default TTL. ttl = _default_ttl; } } return repl; }