#if HAVE_CRT #define _CRTDBG_MAP_ALLOC #include #include #endif //HAVE_CRT /* * Copyright (C) 2020, University of the Basque Country (UPV/EHU) * Contact for licensing options: * * The original file was part of Open Source Doubango Framework * Copyright (C) 2010-2011 Mamadou Diop. * Copyright (C) 2012 Doubango Telecom * * This file is part of Open Source Doubango Framework. * * DOUBANGO is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * DOUBANGO is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with DOUBANGO. * */ /**@file tnet_dns.c * @brief DNS utility functions (RFCS [1034 1035] [3401 3402 3403 3404] [3761]). * */ #include "tnet_dns.h" #include "tnet_dns_regexp.h" #include "tnet_dns_message.h" #include "tnet_dns_opt.h" #include "tnet_dns_srv.h" #include "tnet_dns_naptr.h" #include "tnet_types.h" #include "tsk_memory.h" #include "tsk_time.h" #include "tsk_debug.h" #include "tsk_string.h" #include /* tsk_strlen, memser, .... */ #include /* isdigist */ /* DNS cache functions */ int tnet_dns_cache_maintenance(tnet_dns_ctx_t *ctx); int tnet_dns_cache_entry_add(tnet_dns_ctx_t *ctx, const char* qname, tnet_dns_qclass_t qclass, tnet_dns_qtype_t qtype, tnet_dns_response_t* response); const tnet_dns_cache_entry_t* tnet_dns_cache_entry_get(tnet_dns_ctx_t *ctx, const char* qname, tnet_dns_qclass_t qclass, tnet_dns_qtype_t qtype); /**@defgroup tnet_dns_group DNS utility functions (RFCS [1034 1035] [3401 3402 3403 3404]). * *

11 DNS

*

* The DNS Stack (RFC 1034 and RFC 1035) contains all network functions to send queries (both IPv4 and IPv6) and parse responses.
* The core framework implements RFC 3401, 3402, 3403 and 3404, also known as Dynamic Delegation Discovery System (DDDS). *

*

* The DNS servers are automatically loaded by the stack when you create a context (@ref tnet_dns_ctx_create()).
* On Windows systems (XP, VISTA, 7 and CE) the servers are retrieved using WIN32 APIs. On Unix-like systems (both desktop and embedded) such as Debian, Ubuntu, Nokia�s N900� the list of DNS servers comes from �/etc/resolv.conf� file.
* On Google Android operating system, this file is missing and the DNS settings are stored in the shared memory. You can access this shared memory by using @a property_get() and @a property_set() function which are part of Bionic.
* In all cases, you can retrieve the DNS servers yourself (e.g. using java/C# Frameworks) and add them to the context using @ref tnet_dns_add_server(). *

*

* DNS resolution is always performed in a synchronous manner and is thread-safe. For all DNS requests the default timeout value is 5 seconds (@ref TNET_DNS_TIMEOUT_DEFAULT). * The stack also implements the ENUM protocol (RFC 3761). *

* *

11.1 Resource Records

* The table below lists all DNS Resource Records (RR) for which we provide a parser. * * * * * * * * * * * * * *
Code RFC Description Well-defined type
A RFC 1035 IPv4 address tnet_dns_a_t
AAAA RFC 3596 IPv6 address tnet_dns_aaaa_t
CNAME RFC 1035 Canonical name tnet_dns_cname_t
MX RFC 2035 Mail exchange tnet_dns_mx_t
NAPTR RFC 3403 Naming Authority Pointer tnet_dns_naptr_t
NS RFC 1035 Name Server tnet_dns_ns_t
OPT RFC 2671 Option tnet_dns_opt_t
PTR RFC 1035 Pointer record tnet_dns_ptr_t
SOA RFC1035 Start Of Authority record tnet_dns_soa_t
SRV RFC 2782 Service locator tnet_dns_srv_t
* * Here is an example of how to use the DNS stack to perform DNS NAPTR resolution and print the result to the console. * * @code tnet_dns_ctx_t *ctx = tnet_dns_ctx_create(); tnet_dns_response_t *response = tsk_null; const tsk_list_item_t* item; const tnet_dns_rr_t* rr; if((response = tnet_dns_resolve(ctx, "sip2sip.info", qclass_in, qtype_naptr))) { if(TNET_DNS_RESPONSE_IS_SUCCESS(response)){ TSK_DEBUG_INFO("We got a success response from the DNS server."); // loop through the answers tsk_list_foreach(item, response->Answers){ rr = item->data; if(rr->qtype == qtype_naptr){ const tnet_dns_naptr_t *naptr = (const tnet_dns_naptr_t*)rr; TSK_DEBUG_INFO("order=%u pref=%u flags=%s services=%s regexp=%s replacement=%s", naptr->order, naptr->preference, naptr->flags, naptr->services, naptr->regexp, naptr->replacement); } } } else{ TSK_DEBUG_ERROR("We got an error response from the DNS server. Error code: %u", response->Header.RCODE); } } TSK_OBJECT_SAFE_FREE(response); TSK_OBJECT_SAFE_FREE(ctx); * @endcode * The @a ctx could be used several times and is a well-defined object.
* The console will output: * INFO: We got a success response from the DNS server.
* INFO: order=10 pref=0 flags=S services=SIP+d2u regexp=(null) replacement=_sip._udp.sip2sip.info
* *

11.2 ENUM

* ENUM (E.164 Number Mapping) protocol has been defined in RFC 3761. * ENUM protocol is used to transform telephone numbers of the PSTN network (e.g. +33600000) into internet resource addresses (e.g. sip:diopmamadou@example.com) by using DNS lookup (NAPTR). The internet resource address could be an email, ICQ, IAX2, H.323 � * In our case (3GPP IMS) it is typically used to convert TEL URIs (e.g. tel:+33600000) into SIP URIs (sip:+33600000;user=phone). The telephone number to convert should be a valid E.164 number. * *

11.2.1 Usage

* The code below shows how to gets the SIP address (with the higher order) associated to an E.164 telephone number. * * @code tnet_dns_ctx_t *ctx = tnet_dns_ctx_create(); tnet_dns_response_t* response = tsk_null; char* uri = tsk_null; if((uri = tnet_dns_enum_2(ctx, "E2U+SIP", "+1-800-555-5555","e164.org"))){ TSK_DEBUG_INFO("URI=%s", uri); TSK_FREE(uri); } else{ TSK_DEBUG_ERROR("ENUM(%s) failed", "+1-800-555-5555"); } TSK_OBJECT_SAFE_FREE(response); TSK_OBJECT_SAFE_FREE(ctx); * @endcode * * Console Output:
* INFO: URI=sip:16416418000-555-5555@sip.tollfreegateway.com
* * E2U+SIP is the name of SIP ENUM service assigned by the IANA. Any assigned service () could be used even if the associated addresse type isn�t a well-knonw internet address. * To get all internet addresses (email, IAX2, ICQ, H.323 �) associated to the telephone, use @ref tnet_dns_enum() instead of @ref tnet_dns_enum_2(). */ /**@ingroup tnet_dns_group * Creates new DNS context. */ tnet_dns_ctx_t* tnet_dns_ctx_create() { return tsk_object_new(tnet_dns_ctx_def_t); } /**@ingroup tnet_dns_group * Creates new DNS cache entry. */ tnet_dns_cache_entry_t* tnet_dns_cache_entry_create(const char* qname, tnet_dns_qclass_t qclass, tnet_dns_qtype_t qtype, tnet_dns_response_t* answer) { return tsk_object_new(tnet_dns_cache_entry_def_t, qname, qclass, qtype, answer); } /**@ingroup tnet_dns_group * Cleanup the internal DNS cache. * @param ctx The DNS context containing the cache to cleanup. * The context contains the user's preference and should be created using @ref tnet_dns_ctx_create(). * @retval Zero if succeeed and non-zero error code otherwise. */ int tnet_dns_cache_clear(tnet_dns_ctx_t* ctx) { if (ctx){ tsk_safeobj_lock(ctx); tsk_list_clear_items(ctx->cache); tsk_safeobj_unlock(ctx); return 0; } return -1; } /**@ingroup tnet_dns_group * Sends DNS request over the network. The request will be sent each 500 milliseconds until @ref TNET_DNS_TIMEOUT_DEFAULT milliseconds is reached. * @param ctx The DNS context to use. The context contains the user's preference and should be created using @ref tnet_dns_ctx_create(). * @param qname The domain name (e.g. google.com). * @param qclass The CLASS of the query. * @param qtype The type of the query. * @retval The DNS response. The @a answers in the @a response are already filtered. * MUST be destroyed using @a TSK_OBJECT_SAFE_FREE() macro. * @sa @ref tnet_dns_query_srv, @ref tnet_dns_query_naptr_srv, @ref tnet_dns_enum. * * @code * tnet_dns_ctx_t *ctx = tnet_dns_ctx_create(); * tnet_dns_response_t *response = tnet_dns_resolve(ctx, "sip2sip.info", qclass_in, qtype_srv); * TSK_OBJECT_SAFE_FREE(response); * TSK_OBJECT_SAFE_FREE(ctx); * @endcode */ tnet_dns_response_t *tnet_dns_resolve(tnet_dns_ctx_t* ctx, const char* qname, tnet_dns_qclass_t qclass, tnet_dns_qtype_t qtype) { #if HAVE_DNS_H struct sockaddr_storage result; struct sockaddr *from; uint32_t fromlen; char buf[TNET_DNS_DGRAM_SIZE_DEFAULT]; int32_t ret; tnet_dns_response_t *response = tsk_null; tnet_socket_t *localsocket4 = tnet_socket_create(TNET_SOCKET_HOST_ANY, TNET_SOCKET_PORT_ANY, tnet_socket_type_udp_ipv4); tnet_socket_t *localsocket6 = tnet_socket_create(TNET_SOCKET_HOST_ANY, TNET_SOCKET_PORT_ANY, tnet_socket_type_udp_ipv6); tsk_safeobj_lock(ctx); // First, try with IPv4 if(TNET_SOCKET_IS_VALID(localsocket4)){ if((ret = tnet_getsockname(localsocket4->fd, &result))){ TNET_PRINT_LAST_ERROR("tnet_getsockname() failed."); goto ipv6; } from = (struct sockaddr *) &result; fromlen = tnet_get_sockaddr_size(from); if ((ret = dns_search(ctx->resolv_handle, qname, qclass, qtype, buf, TNET_DNS_DGRAM_SIZE_DEFAULT, from, &fromlen)) > 0) { response = tnet_dns_message_deserialize((uint8_t *) buf, ret); goto done; } else{ TNET_PRINT_LAST_ERROR("dns_search_v4()"); } } ipv6: // Then, try with IPv6 if(TNET_SOCKET_IS_VALID(localsocket6)){ if((ret = tnet_getsockname(localsocket6->fd, &result))){ TNET_PRINT_LAST_ERROR("tnet_getsockname() failed."); goto done; } from = (struct sockaddr *) &result; fromlen = tnet_get_sockaddr_size(from); if((ret = dns_search(ctx->resolv_handle, qname, qclass, qtype, buf, TNET_DNS_DGRAM_SIZE_DEFAULT, from, &fromlen)) > 0){ response = tnet_dns_message_deserialize((uint8_t *) buf, ret); goto done; } else{ TNET_PRINT_LAST_ERROR("dns_search_v6()"); } } done: tsk_safeobj_unlock(ctx); TSK_OBJECT_SAFE_FREE(localsocket4); TSK_OBJECT_SAFE_FREE(localsocket6); return response; #else tsk_buffer_t *output = tsk_null; tnet_dns_query_t* query = tnet_dns_query_create(qname, qclass, qtype); tnet_dns_response_t *response = tsk_null; tsk_bool_t from_cache = tsk_false; /* Check validity */ if (!ctx || !query){ goto bail; } /* Is there any DNS Server? */ if (TSK_LIST_IS_EMPTY(ctx->servers)){ TSK_DEBUG_ERROR("Failed to load DNS Servers. You can add new DNS servers by using \"tnet_dns_add_server\"."); goto bail; } /* Cache maintenance */ if (!TSK_LIST_IS_EMPTY(ctx->cache)){ /* Only do maintenance if the cache is not empty */ tnet_dns_cache_maintenance(ctx); } /* Retrieve data from cache. */ if (ctx->caching){ const tnet_dns_cache_entry_t *entry = tnet_dns_cache_entry_get(ctx, qname, qclass, qtype); if (entry){ response = tsk_object_ref(((tnet_dns_cache_entry_t*)entry)->response); from_cache = tsk_true; goto bail; } } /* Set user preference */ query->Header.RD = ctx->recursion; /* EDNS0 */ if (ctx->edns0){ tnet_dns_opt_t *rr_opt = tnet_dns_opt_create(TNET_DNS_DGRAM_SIZE_DEFAULT); if (!query->Additionals){ query->Additionals = tsk_list_create(); } tsk_list_push_back_data(query->Additionals, (void**)&rr_opt); query->Header.ARCOUNT++; } /* Serialize and send to the server. */ if (!(output = tnet_dns_message_serialize(query))){ TSK_DEBUG_ERROR("Failed to serialize the DNS message."); goto bail; } /* ============================ */ // Send and Recaive data /* ============================ */ { int ret = -1; struct timeval tv; fd_set set; tnet_fd_t maxFD; uint64_t timeout = 0; tsk_list_item_t *item; const tnet_address_t *address; struct sockaddr_storage server; tnet_socket_t *localsocket4 = tnet_socket_create(TNET_SOCKET_HOST_ANY, TNET_SOCKET_PORT_ANY, tnet_socket_type_udp_ipv4); tnet_socket_t *localsocket6 = tnet_socket_create(TNET_SOCKET_HOST_ANY, TNET_SOCKET_PORT_ANY, tnet_socket_type_udp_ipv6); tsk_bool_t useIPv6 = TNET_SOCKET_IS_VALID(localsocket6); tsk_safeobj_lock(ctx); /* Check socket validity */ if (!TNET_SOCKET_IS_VALID(localsocket4)){ goto done; } /* Always wait 500ms before retransmission */ tv.tv_sec = 0; tv.tv_usec = (500 * 1000); do { // // Send data (loop through all intefaces) // tsk_list_foreach(item, ctx->servers) { address = item->data; if (!address->ip || (address->family != AF_INET && address->family != AF_INET6) || (address->family == AF_INET6 && !TNET_SOCKET_IS_VALID(localsocket6))){ continue; } if (tnet_sockaddr_init(address->ip, ctx->server_port, (address->family == AF_INET ? tnet_socket_type_udp_ipv4 : tnet_socket_type_udp_ipv6), &server)){ TSK_DEBUG_ERROR("Failed to initialize the DNS server address: \"%s\"", address->ip); continue; } TSK_DEBUG_INFO("Sending DNS query to \"%s\"", address->ip); if (address->family == AF_INET6){ if ((ret = tnet_sockfd_sendto(localsocket6->fd, (const struct sockaddr*)&server, output->data, output->size))){ // succeed? break; } } else{ if ((ret = tnet_sockfd_sendto(localsocket4->fd, (const struct sockaddr*)&server, output->data, output->size))){ // succeed? break; } } } // // Received data // /* First time? ==> set timeout value */ if (!timeout){ timeout = tsk_time_epoch() + ctx->timeout; } /* Set FDs */ FD_ZERO(&set); FD_SET(localsocket4->fd, &set); if (useIPv6){ FD_SET(localsocket6->fd, &set); maxFD = TSK_MAX(localsocket4->fd, localsocket6->fd); } else{ maxFD = localsocket4->fd; } /* wait for response */ if ((ret = select(maxFD + 1, &set, NULL, NULL, &tv)) < 0){ /* Error */ TNET_PRINT_LAST_ERROR("Select failed."); goto done; } else if (ret == 0){ /* timeout ==> do nothing */ } else{ /* there is data to read */ unsigned int len = 0; void* data = 0; tnet_fd_t active_fd; /* Find active file descriptor */ if (FD_ISSET(localsocket4->fd, &set)){ active_fd = localsocket4->fd; } else if (FD_ISSET(localsocket6->fd, &set)){ active_fd = localsocket4->fd; } else{ TSK_DEBUG_ERROR("FD_ISSET ==> Invalid file descriptor."); continue; } /* Check how how many bytes are pending */ if ((ret = tnet_ioctlt(active_fd, FIONREAD, &len)) < 0){ TSK_DEBUG_ERROR("tnet_ioctlt failed with error code:%d", tnet_geterrno()); goto done; } /* Receive pending data */ #if HAVE_CRT //Debug memory data = calloc(len, sizeof(uint8_t)); #else data = tsk_calloc(len, sizeof(uint8_t)); #endif //HAVE_CRT if ((ret = tnet_sockfd_recv(active_fd, data, len, 0)) < 0){ TSK_FREE(data); TSK_DEBUG_ERROR("tnet_sockfd_recv failed with error code:%d", tnet_geterrno()); goto done; } /* Parse the incoming response. */ response = tnet_dns_message_deserialize(data, (tsk_size_t)ret); TSK_FREE(data); if (response) { /* response successfuly parsed */ if (query->Header.ID != response->Header.ID || !TNET_DNS_MESSAGE_IS_RESPONSE(response)){ /* Not same transaction id ==> continue*/ TSK_OBJECT_SAFE_FREE(response); } else goto done; } } } while (timeout > tsk_time_epoch()); done: tsk_safeobj_unlock(ctx); TSK_OBJECT_SAFE_FREE(localsocket4); TSK_OBJECT_SAFE_FREE(localsocket6); goto bail; } bail: TSK_OBJECT_SAFE_FREE(query); TSK_OBJECT_SAFE_FREE(output); /* Add the result to the cache. */ if (response){ if (!from_cache && ctx->caching){ tnet_dns_cache_entry_add(ctx, qname, qclass, qtype, response); } } else{ TSK_DEBUG_ERROR("Failed to contact the DNS server."); } return response; #endif } /**@ingroup tnet_dns_group * Gets list of URIs associated to this telephone number by using ENUM protocol (RFC 3761). * @param ctx The DNS context. * The context contains the user's preference and should be created using @ref tnet_dns_ctx_create(). * @param e164num A valid E.164 number (e.g. +1-800-555-5555). * @param domain The domain name (e.g e164.arpa, e164.org, ...). If Null, default value is "e164.arpa" (IANA). * @retval The DNS response with NAPTR RRs. The @a answers in the @a response are already filtered. * MUST be destroyed using @a TSK_OBJECT_SAFE_FREE macro. * @sa @ref tnet_dns_resolve, @ref tnet_dns_enum_2. */ tnet_dns_response_t* tnet_dns_enum(tnet_dns_ctx_t* ctx, const char* e164num, const char* domain) { char e164domain[255]; tnet_dns_response_t* ret = tsk_null; int e164size; int i, j; // must be signed e164size = (int)tsk_strlen(e164num); if (!ctx || !e164num || !e164size){ goto bail; } if (e164size /* max=15 digits + ".e164.arpa" + '+' */ >= (sizeof(e164domain) - 1)){ TSK_DEBUG_ERROR("%s is an invalid E.164 number.", e164num); goto bail; } memset(e164domain, '\0', sizeof(e164domain)); /* RFC 3761 - 2.4. Valid Databases 1. Remove all characters with the exception of the digits. For example, the First Well Known Rule produced the Key "+442079460148". This step would simply remove the leading "+", producing "442079460148". 2. Put dots (".") between each digit. Example: 4.4.2.0.7.9.4.6.0.1.4.8 3. Reverse the order of the digits. Example: 8.4.1.0.6.4.9.7.0.2.4.4 4. Append the string ".e164.arpa" to the end. Example: 8.4.1.0.6.4.9.7.0.2.4.4.e164.arpa This domain-name is used to request NAPTR records which may contain the end result or, if the flags field is blank, produces new keys in the form of domain-names from the DNS. */ for (i = e164size - 1, j = 0; i >= 0; i--){ if (!isdigit(e164num[i])){ continue; } e164domain[j++] = e164num[i]; e164domain[j++] = '.'; } // append domain name if (domain){ memcpy(&e164domain[j], domain, ((tsk_strlen(domain) + j) >= sizeof(e164domain) - 1) ? (sizeof(e164domain) - j - 1) : tsk_strlen(domain)); } else{ memcpy(&e164domain[j], "e164.arpa", 9); } /* == Performs DNS (NAPTR) lookup == */ ret = tnet_dns_resolve(ctx, e164domain, qclass_in, qtype_naptr); bail: return ret; } /**@ingroup tnet_dns_group * Gets the internate address associated to this telephone number by using ENUM protocol (RFC 3761). * Only terminale rules containing uris(flags="u") will be considered and the regex string will be executed on the original string for * substitution.
* Parsing complex regexp will probably fail (99.99% chance). Please use @ref tnet_dns_enum if you want to use your own regexp parser. * @param ctx The DNS context. * The context contains the user's preference and should be created using @ref tnet_dns_ctx_create(). * @param service The ENUM service (e.g. E2U+SIP). * @param e164num A valid E.164 number (e.g. +1-800-555-5555). * @param domain The domain name (e.g e164.arpa, e164.org, ...). If Null, default value is "e164.arpa" (IANA). * @retval The Internet address (SIP, email, ICQ, fax, ...) associated to this service. * MUST be freed using @a TSK_FREE macro. * @sa @ref tnet_dns_resolve, @ref tnet_dns_enum. */ char* tnet_dns_enum_2(tnet_dns_ctx_t* ctx, const char* service, const char* e164num, const char* domain) { tnet_dns_response_t* response; const tsk_list_item_t* item; char* ret = tsk_null; const tnet_dns_rr_t* rr; if ((response = tnet_dns_enum(ctx, e164num, domain))){ if (TNET_DNS_RESPONSE_IS_SUCCESS(response)){ tsk_list_foreach(item, response->Answers){ rr = item->data; if (rr->qtype == qtype_naptr){ const tnet_dns_naptr_t *naptr = (const tnet_dns_naptr_t*)rr; /* RFC 3403 - 6.2 E164 Example Both the ENUM [18] and URI Resolution [4] Applications use the 'u' flag. This flag states that the Rule is terminal and that the output is a URI which contains the information needed to contact that telephone service. */ if (tsk_striequals(naptr->flags, "u") && tsk_striequals(naptr->services, service)){ /* RFC 3403 - 4.1 Packet Format The fields (replacement and regexp) are also mutually exclusive. If a record is returned that has values for both fields then it is considered to be in error and SHOULD be either ignored or an error returned. */ if (naptr->regexp && naptr->replacement){ continue; } if ((ret = tnet_dns_regex_parse(e164num, naptr->regexp))){ break; } } } } } else{ TSK_DEBUG_ERROR("We got an error response from the DNS server. Error code: %u", response->Header.RCODE); } TSK_OBJECT_SAFE_FREE(response); } return ret; } /**@ingroup tnet_dns_group * Performs DNS SRV resolution. * @param ctx The DNS context. * The context contains the user's preference and should be created using @ref tnet_dns_ctx_create. * @param service The name of the service (e.g. SIP+D2U). * @param hostname The result containing an IP address or FQDN. * @param port The port associated to the result. * @retval Zero if succeed and non-zero error code otherwise. * @sa @ref tnet_dns_resolve. * * @code * tnet_dns_ctx_t *ctx = tnet_dns_ctx_create(); * char* hostname = 0; * tnet_port_t port = 0; * * if(!tnet_dns_query_srv(ctx, "_sip._udp.sip2sip.info", &hostname, &port)){ * TSK_DEBUG_INFO("DNS SRV succeed ==> hostname=%s and port=%u", hostname, port); * } * * TSK_FREE(hostname); * TSK_OBJECT_SAFE_FREE(ctx); * @endcode */ int tnet_dns_query_srv(tnet_dns_ctx_t *ctx, const char* service, char** hostname, tnet_port_t* port) { tnet_dns_response_t *response; if (!ctx){ return -1; } // tnet_dns_resolve is thread-safe if ((response = tnet_dns_resolve(ctx, service, qclass_in, qtype_srv))) { const tsk_list_item_t *item; const tnet_dns_rr_t* rr; tsk_list_foreach(item, response->Answers) /* Already Filtered ==> Peek the first One */ { rr = item->data; if (rr->qtype == qtype_srv){ const tnet_dns_srv_t *srv = (const tnet_dns_srv_t*)rr; tsk_strupdate(hostname, srv->target); *port = srv->port; break; } } } TSK_OBJECT_SAFE_FREE(response); return (hostname && !tsk_strnullORempty(*hostname)) ? 0 : -2; } /**@ingroup tnet_dns_group * Performs DNS NAPTR followed by DNS SRV resolution. * @param ctx The DNS context. * The context contains the user's preference and should be created using @ref tnet_dns_ctx_create(). * @param domain The Name of the domain (e.g. google.com). * @param service The name of the service (e.g. SIP+D2U). * @param hostname The result containing an IP address or FQDN. Should be Null. * @param port The port associated to the result. * @retval Zero if succeed and non-zero error code otherwise. * @sa @ref tnet_dns_resolve. * * @code * tnet_dns_ctx_t *ctx = tnet_dns_ctx_create(); * char* hostname = tsk_null; * tnet_port_t port = 0; * * if(!tnet_dns_query_naptr_srv(ctx, "sip2sip.info", "SIP+D2U", &hostname, &port)){ * TSK_DEBUG_INFO("DNS NAPTR+SRV succeed ==> hostname=%s and port=%u", hostname, port); * } * * TSK_FREE(hostname); * TSK_OBJECT_SAFE_FREE(ctx); * @endcode */ int tnet_dns_query_naptr_srv(tnet_dns_ctx_t *ctx, const char* domain, const char* service, char** hostname, tnet_port_t* port) { tnet_dns_response_t *response; if (!ctx || !hostname){ TSK_DEBUG_ERROR("Invalid parameters."); return -1; } /* reset (do not free the user supplied value). trying to free dummy value will cause access violation error ==> zero. */ *hostname = tsk_null; // tnet_dns_resolve is thread-safe if ((response = tnet_dns_resolve(ctx, domain, qclass_in, qtype_naptr))){ const tsk_list_item_t *item; const tnet_dns_rr_t* rr; char* replacement = tsk_null; /* e.g. _sip._udp.example.com */ char* flags = tsk_null;/* e.g. S, A, AAAA, A6, U, P ... */ tsk_list_foreach(item, response->Answers) /* Already Filtered ==> Peek the first One */ { rr = item->data; if (rr->qtype == qtype_naptr){ tnet_dns_naptr_t *naptr = (tnet_dns_naptr_t*)rr; if (tsk_striequals(service, naptr->services)){ tsk_strupdate(&replacement, naptr->replacement); tsk_strupdate(&flags, naptr->flags); break; } } } if (flags && replacement){ if (tsk_striequals(flags, "S")){ tnet_dns_query_srv(ctx, replacement, hostname, port); } else if (tsk_striequals(flags, "A") || tsk_striequals(flags, "AAAA") || tsk_striequals(flags, "A6")){ TSK_DEBUG_WARN("Defaulting port value."); tsk_strupdate(hostname, replacement); *port = 5060; } else{ TSK_DEBUG_ERROR("DNS NAPTR query returned invalid flags"); } } else{ TSK_DEBUG_INFO("DNS NAPTR (%s) query returned zero result", domain); } TSK_FREE(flags); TSK_FREE(replacement); } TSK_OBJECT_SAFE_FREE(response); return (hostname && *hostname && !tsk_strempty(*hostname)) ? 0 : -2; } // remove timedout entries int tnet_dns_cache_maintenance(tnet_dns_ctx_t *ctx) { if (ctx) { tsk_list_item_t *item; tsk_safeobj_lock(ctx); again: tsk_list_foreach(item, ctx->cache) { // FIXME: ttl should be from RR::ttl tnet_dns_cache_entry_t *entry = (tnet_dns_cache_entry_t*)item->data; if ((entry->epoch + ctx->cache_ttl) < tsk_time_epoch()){ tsk_list_remove_item_by_data(ctx->cache, entry); goto again; /* Do not delete data while looping */ } } tsk_safeobj_unlock(ctx); return 0; } return -1; } // add an entry to the cache int tnet_dns_cache_entry_add(tnet_dns_ctx_t *ctx, const char* qname, tnet_dns_qclass_t qclass, tnet_dns_qtype_t qtype, tnet_dns_response_t* response) { int ret = -1; if (ctx) { tnet_dns_cache_entry_t *entry; tsk_safeobj_lock(ctx); entry = 0; /* Retrieve from cache */ entry = (tnet_dns_cache_entry_t*)tnet_dns_cache_entry_get(ctx, qname, qclass, qtype); if (entry){ /* UPDATE */ TSK_OBJECT_SAFE_FREE(entry->response); entry->response = tsk_object_ref(response); entry->epoch = tsk_time_epoch(); ret = 0; goto done; } else{ /* CREATE */ entry = tnet_dns_cache_entry_create(qname, qclass, qtype, response); if (entry){ tsk_list_push_back_data(ctx->cache, (void**)&entry); ret = 0; goto done; } else{ ret = -2; goto done; } } done: tsk_safeobj_unlock(ctx); } return ret; } // get an entry from the cache const tnet_dns_cache_entry_t* tnet_dns_cache_entry_get(tnet_dns_ctx_t *ctx, const char* qname, tnet_dns_qclass_t qclass, tnet_dns_qtype_t qtype) { tnet_dns_cache_entry_t *ret = tsk_null; if (ctx) { tsk_list_item_t *item; tsk_safeobj_lock(ctx); tsk_list_foreach(item, ctx->cache){ tnet_dns_cache_entry_t *entry = (tnet_dns_cache_entry_t*)item->data; if (entry->qtype == qtype && entry->qclass == qclass && tsk_strequals(entry->qname, qname)){ ret = entry; break; } } tsk_safeobj_unlock(ctx); } return ret; } /**@ingroup tnet_dns_group * Adds new DNS server to the list of the list of servers to query. * @param ctx DNS context containing the user parameters. The new DNS server will be added to this context. * @param host The IP address (or FQDN) of the dns server to add to the server. * @retval zero if succeed and non-zero error code otherwise. */ int tnet_dns_add_server(tnet_dns_ctx_t *ctx, const char* host) { tnet_address_t *address; if (!ctx || !host){ return -1; } if (!ctx->servers){ ctx->servers = tsk_list_create(); } if ((address = tnet_address_create(host))){ address->family = tnet_get_family(host, TNET_DNS_SERVER_PORT_DEFAULT); address->dnsserver = 1; tsk_list_push_ascending_data(ctx->servers, (void**)&address); return 0; } return -2; } //================================================================================================= // [[DNS CACHE ENTRY]] object definition // static tsk_object_t* tnet_dns_cache_entry_ctor(tsk_object_t * self, va_list * app) { tnet_dns_cache_entry_t *entry = self; if (entry){ entry->qname = tsk_strdup(va_arg(*app, const char*)); entry->qclass = va_arg(*app, tnet_dns_qtype_t); entry->qtype = va_arg(*app, tnet_dns_qtype_t); entry->response = tsk_object_ref(va_arg(*app, tnet_dns_response_t*)); entry->epoch = tsk_time_epoch(); } return self; } static tsk_object_t* tnet_dns_cache_entry_dtor(tsk_object_t * self) { tnet_dns_cache_entry_t *entry = self; if (entry){ TSK_OBJECT_SAFE_FREE(entry->response); } return self; } static const tsk_object_def_t tnet_dns_cache_entry_def_s = { sizeof(tnet_dns_cache_entry_t), tnet_dns_cache_entry_ctor, tnet_dns_cache_entry_dtor, tsk_null, }; const tsk_object_def_t *tnet_dns_cache_entry_def_t = &tnet_dns_cache_entry_def_s; //================================================================================================= // [[DNS CONTEXT]] object definition // static tsk_object_t* tnet_dns_ctx_ctor(tsk_object_t * self, va_list * app) { tnet_dns_ctx_t *ctx = self; if (ctx){ ctx->timeout = TNET_DNS_TIMEOUT_DEFAULT; ctx->recursion = tsk_true; ctx->edns0 = tsk_true; ctx->caching = tsk_false; ctx->cache_ttl = TNET_DNS_CACHE_TTL; ctx->server_port = TNET_DNS_SERVER_PORT_DEFAULT; /* Gets all dns servers. */ ctx->servers = tnet_get_addresses_all_dnsservers(); /* Creates empty cache. */ ctx->cache = tsk_list_create(); #if HAVE_DNS_H ctx->resolv_handle = dns_open(NULL); #endif tsk_safeobj_init(ctx); } return self; } static tsk_object_t* tnet_dns_ctx_dtor(tsk_object_t * self) { tnet_dns_ctx_t *ctx = self; if (ctx){ tsk_safeobj_deinit(ctx); TSK_OBJECT_SAFE_FREE(ctx->servers); TSK_OBJECT_SAFE_FREE(ctx->cache); #if HAVE_DNS_H dns_free(ctx->resolv_handle); #endif } return self; } static const tsk_object_def_t tnet_dns_ctx_def_s = { sizeof(tnet_dns_ctx_t), tnet_dns_ctx_ctor, tnet_dns_ctx_dtor, tsk_null, }; const tsk_object_def_t *tnet_dns_ctx_def_t = &tnet_dns_ctx_def_s;