Commit 83e9325f authored by Simon Morlat's avatar Simon Morlat
Browse files

implement IPv6+IPv4 DNS resolution, with AI_V4MAPPED results for IPv4 results.

parent e6206880
......@@ -115,7 +115,8 @@ BELLE_SIP_DECLARE_TYPES_BEGIN(belle_sip,1)
BELLE_SIP_TYPE_ID(belle_sip_dns_srv_t),
BELLE_SIP_TYPE_ID(belle_sip_simple_resolver_context_t),
BELLE_SIP_TYPE_ID(belle_sip_combined_resolver_context_t),
BELLE_SIP_TYPE_ID(belle_sip_dict_t)
BELLE_SIP_TYPE_ID(belle_sip_dict_t),
BELLE_SIP_TYPE_ID(belle_sip_dual_resolver_context_t)
BELLE_SIP_DECLARE_TYPES_END
......
......@@ -56,7 +56,15 @@ BELLESIP_EXPORT struct addrinfo * belle_sip_ip_address_to_addrinfo(int family, c
/**
* Asynchronously performs DNS SRV followed A or AAAA query. Automatically fallbacks to A or AAAA if SRV isn't found.
* Asynchronously performs DNS SRV followed A/AAAA query. Automatically fallbacks to A/AAAA if SRV isn't found.
* @param stack the belle_sip_stack_t, used to schedule asynchronous execution.
* @param transport the queried transport ("udp", "tcp", "tls")
* @param name the SIP domain name
* @param port a port that will be filled in the addrinfo list returned by the callback, for the case where no SRV records are found.
* @param family address family expected in the addrinfo result list. AF_INET or AF_INET6. If AF_INET6 is used but no IPv4 records are found, the IPv4 addresses
* will be returned as IPv6 with v4 mapping (AI_V4MAPPED).
* @param cb a callback function that will be called to notify the results.
* @param data a user pointer passed through the callback as first argument.
* @return a #belle_sip_resolver_context_t that can be used to cancel the resolution if needed. The context must have been ref'd with belle_sip_object_ref().
**/
BELLESIP_EXPORT belle_sip_resolver_context_t * belle_sip_stack_resolve(belle_sip_stack_t *stack, const char *transport, const char *name, int port, int family,
......@@ -64,6 +72,13 @@ belle_sip_resolver_callback_t cb, void *data);
/**
* Asynchronously performs DNS A or AAAA query.
* @param stack the belle_sip_stack_t, used to schedule asynchronous execution.
* @param name the SIP domain name
* @param port a port that will be filled in the addrinfo list returned by the callback, for the case where no SRV records are found.
* @param family address family expected in the addrinfo result list. AF_INET or AF_INET6. If AF_INET6 is used but no IPv4 records are found, the IPv4 addresses
* will be returned as IPv6 with v4 mapping (AI_V4MAPPED).
* @param cb a callback function that will be called to notify the results.
* @param data a user pointer passed through the callback as first argument.
* @return a #belle_sip_resolver_context_t that can be used to cancel the resolution if needed. The context must have been ref'd with belle_sip_object_ref().
**/
BELLESIP_EXPORT belle_sip_resolver_context_t * belle_sip_stack_resolve_a(belle_sip_stack_t *stack, const char *name, int port, int family, belle_sip_resolver_callback_t cb, void *data);
......
......@@ -202,6 +202,9 @@ BELLE_SIP_DECLARE_CUSTOM_VPTR_END
BELLE_SIP_DECLARE_CUSTOM_VPTR_BEGIN(belle_sip_combined_resolver_context_t,belle_sip_resolver_context_t)
BELLE_SIP_DECLARE_CUSTOM_VPTR_END
BELLE_SIP_DECLARE_CUSTOM_VPTR_BEGIN(belle_sip_dual_resolver_context_t,belle_sip_resolver_context_t)
BELLE_SIP_DECLARE_CUSTOM_VPTR_END
typedef void (*belle_sip_source_remove_callback_t)(belle_sip_source_t *);
struct belle_sip_source{
......
......@@ -35,6 +35,10 @@ typedef struct belle_sip_simple_resolver_context belle_sip_simple_resolver_conte
typedef struct belle_sip_combined_resolver_context belle_sip_combined_resolver_context_t;
#define BELLE_SIP_COMBINED_RESOLVER_CONTEXT(obj) BELLE_SIP_CAST(obj,belle_sip_combined_resolver_context_t)
typedef struct belle_sip_dual_resolver_context belle_sip_dual_resolver_context_t;
#define BELLE_SIP_DUAL_RESOLVER_CONTEXT(obj) BELLE_SIP_CAST(obj,belle_sip_dual_resolver_context_t)
struct belle_sip_dns_srv{
belle_sip_object_t base;
unsigned short priority;
......@@ -113,6 +117,7 @@ struct belle_sip_simple_resolver_context{
struct addrinfo *ai_list;
belle_sip_list_t *srv_list; /*list of belle_sip_dns_srv_t*/
int family;
int flags;
uint64_t start_time;
};
......@@ -129,6 +134,19 @@ struct belle_sip_combined_resolver_context{
belle_sip_resolver_context_t *a_fallback_ctx;
};
struct belle_sip_dual_resolver_context{
belle_sip_resolver_context_t base;
belle_sip_resolver_callback_t cb;
void *cb_data;
char *name;
belle_sip_resolver_context_t *a_ctx;
belle_sip_resolver_context_t *aaaa_ctx;
struct addrinfo *a_results;
struct addrinfo *aaaa_results;
unsigned char a_done;
unsigned char aaaa_done;
};
void belle_sip_resolver_context_init(belle_sip_resolver_context_t *obj, belle_sip_stack_t *stack){
obj->stack=stack;
}
......@@ -243,11 +261,14 @@ static void notify_results(belle_sip_simple_resolver_context_t *ctx){
static void append_dns_result(belle_sip_simple_resolver_context_t *ctx, struct sockaddr *addr, socklen_t addrlen){
char host[NI_MAXHOST + 1];
int gai_err;
int family=ctx->family;
if ((gai_err=getnameinfo(addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST)) != 0){
belle_sip_error("append_dns_result(): getnameinfo() failed: %s",gai_strerror(gai_err));
return;
}
ctx->ai_list = ai_list_append(ctx->ai_list, belle_sip_ip_address_to_addrinfo(ctx->family, host, ctx->port));
if (ctx->flags & AI_V4MAPPED) family=AF_INET6;
ctx->ai_list = ai_list_append(ctx->ai_list, belle_sip_ip_address_to_addrinfo(family, host, ctx->port));
belle_sip_message("%s resolved to %s", ctx->name, host);
}
......@@ -432,8 +453,9 @@ struct addrinfo * belle_sip_ip_address_to_addrinfo(int family, const char *ipadd
hints.ai_flags=AI_NUMERICSERV|AI_NUMERICHOST;
hints.ai_socktype=SOCK_STREAM; //not used but it's needed to specify it because otherwise getaddrinfo returns one struct addrinfo per socktype.
if (family==AF_INET6) hints.ai_flags|=AI_V4MAPPED;
if (family==AF_INET6 && strchr(ipaddress,'.')!=NULL) {
hints.ai_flags|=AI_V4MAPPED;
}
err=getaddrinfo(ipaddress,serv,&hints,&res);
if (err!=0){
return NULL;
......@@ -477,6 +499,29 @@ static void belle_sip_simple_resolver_context_destroy(belle_sip_simple_resolver_
}
}
static void belle_sip_dual_resolver_context_destroy(belle_sip_dual_resolver_context_t *obj){
if (obj->a_ctx){
belle_sip_object_unref(obj->a_ctx);
obj->a_ctx=NULL;
}
if (obj->aaaa_ctx){
belle_sip_object_unref(obj->aaaa_ctx);
obj->aaaa_ctx=NULL;
}
if (obj->a_results){
freeaddrinfo(obj->a_results);
obj->a_results=NULL;
}
if (obj->aaaa_results){
freeaddrinfo(obj->aaaa_results);
obj->aaaa_results=NULL;
}
if (obj->name){
belle_sip_free(obj->name);
obj->name=NULL;
}
}
static void simple_resolver_cancel(belle_sip_resolver_context_t *obj){
belle_sip_main_loop_remove_source(obj->stack->ml,(belle_sip_source_t*)obj);
belle_sip_object_unref(obj);
......@@ -498,6 +543,19 @@ static void combined_resolver_cancel(belle_sip_resolver_context_t *p){
belle_sip_object_unref(obj);
}
static void dual_resolver_cancel(belle_sip_resolver_context_t *p){
belle_sip_dual_resolver_context_t *obj=(belle_sip_dual_resolver_context_t*)p;
if (obj->a_ctx){
belle_sip_resolver_context_cancel(obj->a_ctx);
obj->a_ctx=NULL;
}
if (obj->aaaa_ctx){
belle_sip_resolver_context_cancel(obj->aaaa_ctx);
obj->aaaa_ctx=NULL;
}
belle_sip_object_unref(p);
}
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(belle_sip_resolver_context_t);
BELLE_SIP_INSTANCIATE_CUSTOM_VPTR(belle_sip_resolver_context_t)={
{
......@@ -509,8 +567,8 @@ BELLE_SIP_INSTANCIATE_CUSTOM_VPTR(belle_sip_resolver_context_t)={
NULL
};
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(belle_sip_simple_resolver_context_t);
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(belle_sip_simple_resolver_context_t);
BELLE_SIP_INSTANCIATE_CUSTOM_VPTR(belle_sip_simple_resolver_context_t)={
{
{
......@@ -523,6 +581,19 @@ BELLE_SIP_INSTANCIATE_CUSTOM_VPTR(belle_sip_simple_resolver_context_t)={
}
};
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(belle_sip_dual_resolver_context_t);
BELLE_SIP_INSTANCIATE_CUSTOM_VPTR(belle_sip_dual_resolver_context_t)={
{
{
BELLE_SIP_VPTR_INIT(belle_sip_dual_resolver_context_t,belle_sip_resolver_context_t,TRUE),
(belle_sip_object_destroy_t) belle_sip_dual_resolver_context_destroy,
NULL,
NULL
},
dual_resolver_cancel
}
};
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(belle_sip_combined_resolver_context_t);
BELLE_SIP_INSTANCIATE_CUSTOM_VPTR(belle_sip_combined_resolver_context_t)={
{
......@@ -629,7 +700,7 @@ static void process_srv_results(void *data, const char *name, belle_sip_list_t *
}
/**
* Perform combined SRV + AAAA resolution.
* Perform combined SRV + A / AAAA resolution.
**/
belle_sip_resolver_context_t * belle_sip_stack_resolve(belle_sip_stack_t *stack, const char *transport, const char *name, int port, int family, belle_sip_resolver_callback_t cb, void *data) {
struct addrinfo *res = belle_sip_ip_address_to_addrinfo(family, name, port);
......@@ -661,26 +732,88 @@ belle_sip_resolver_context_t * belle_sip_stack_resolve(belle_sip_stack_t *stack,
}
}
static belle_sip_resolver_context_t * belle_sip_stack_resolve_single(belle_sip_stack_t *stack, const char *name, int port, int family, int flags, belle_sip_resolver_callback_t cb , void *data){
/* Then perform asynchronous DNS A or AAAA query */
belle_sip_simple_resolver_context_t *ctx = belle_sip_object_new(belle_sip_simple_resolver_context_t);
belle_sip_resolver_context_init((belle_sip_resolver_context_t*)ctx,stack);
ctx->cb_data = data;
ctx->cb = cb;
ctx->name = belle_sip_strdup(name);
ctx->port = port;
ctx->flags = flags;
if (family == 0) family = AF_UNSPEC;
ctx->family = family;
ctx->type = (ctx->family == AF_INET6) ? DNS_T_AAAA : DNS_T_A;
return (belle_sip_resolver_context_t*)resolver_start_query(ctx);
}
static void notify_dual_results(belle_sip_dual_resolver_context_t *ctx){
if (ctx->a_done && ctx->aaaa_done){
struct addrinfo *results=ctx->aaaa_results;
results=ai_list_append(results,ctx->a_results);
ctx->a_results=NULL;
ctx->aaaa_results=NULL;
ctx->cb(ctx->cb_data,ctx->name,results);
belle_sip_object_unref(ctx);
}
}
static void on_ipv4_results(void *data, const char *name, struct addrinfo *ai_list){
belle_sip_dual_resolver_context_t *ctx=(belle_sip_dual_resolver_context_t *)data;
ctx->a_done=TRUE;
ctx->a_results=ai_list;
notify_dual_results(ctx);
}
static void on_ipv6_results(void *data, const char *name, struct addrinfo *ai_list){
belle_sip_dual_resolver_context_t *ctx=(belle_sip_dual_resolver_context_t *)data;
ctx->aaaa_done=TRUE;
ctx->aaaa_results=ai_list;
notify_dual_results(ctx);
}
static belle_sip_resolver_context_t * belle_sip_stack_resolve_dual(belle_sip_stack_t *stack, const char *name, int port, belle_sip_resolver_callback_t cb , void *data){
/* Then perform asynchronous DNS A or AAAA query */
belle_sip_dual_resolver_context_t *ctx = belle_sip_object_new(belle_sip_dual_resolver_context_t);
belle_sip_resolver_context_init((belle_sip_resolver_context_t*)ctx,stack);
ctx->cb_data = data;
ctx->cb = cb;
ctx->name = belle_sip_strdup(name);
belle_sip_object_ref(ctx);
ctx->a_ctx=belle_sip_stack_resolve_single(stack,name,port,AF_INET, AI_V4MAPPED, on_ipv4_results,ctx);
if (ctx->a_ctx) belle_sip_object_ref(ctx->a_ctx);
ctx->aaaa_ctx=belle_sip_stack_resolve_single(stack, name, port, AF_INET6, 0, on_ipv6_results, ctx);
if (ctx->aaaa_ctx) belle_sip_object_ref(ctx->aaaa_ctx);
if (ctx->aaaa_ctx==NULL && ctx->a_ctx==NULL){
/*all results found synchronously*/
belle_sip_object_unref(ctx);
ctx=NULL;
}
return BELLE_SIP_RESOLVER_CONTEXT(ctx);
}
belle_sip_resolver_context_t * belle_sip_stack_resolve_a(belle_sip_stack_t *stack, const char *name, int port, int family, belle_sip_resolver_callback_t cb , void *data) {
struct addrinfo *res = belle_sip_ip_address_to_addrinfo(family, name, port);
if (res == NULL) {
/* Then perform asynchronous DNS A or AAAA query */
belle_sip_simple_resolver_context_t *ctx = belle_sip_object_new(belle_sip_simple_resolver_context_t);
belle_sip_resolver_context_init((belle_sip_resolver_context_t*)ctx,stack);
ctx->cb_data = data;
ctx->cb = cb;
ctx->name = belle_sip_strdup(name);
ctx->port = port;
if (family == 0) family = AF_UNSPEC;
ctx->family = family;
ctx->type = (ctx->family == AF_INET6) ? DNS_T_AAAA : DNS_T_A;
return (belle_sip_resolver_context_t*)resolver_start_query(ctx);
switch(family){
case AF_UNSPEC:
family=AF_INET6;
case AF_INET6:
return belle_sip_stack_resolve_dual(stack,name,port,cb,data);
break;
case AF_INET:
return belle_sip_stack_resolve_single(stack,name,port,AF_INET,0,cb,data);
break;
default:
belle_sip_error("belle_sip_stack_resolve_a(): unsupported address family [%i]",family);
}
} else {
/* There is no resolve to be done */
cb(data, name, res);
return NULL;
}
return NULL;
}
belle_sip_resolver_context_t * belle_sip_stack_resolve_srv(belle_sip_stack_t *stack, const char *transport, const char *name, belle_sip_resolver_srv_callback_t cb, void *data) {
......
......@@ -27,8 +27,12 @@
#define IPV4_SIP_IP "91.121.209.194"
#define IPV4_SIP_BAD_DOMAIN "dummy.linphone.org"
#define IPV4_MULTIRES_DOMAIN "google.com"
/* videolan.org has an IPv6 and an IPv4 IP*/
#define IPV6_SIP_DOMAIN "videolan.org"
#define IPV6_SIP_IP "2a01:e0d:1:3:58bf:fa02:0:1"
#define IPV6_SIP_IPV4 "88.191.250.2"
#define SRV_DOMAIN "linphone.org"
#define SIP_PORT 5060
......@@ -209,6 +213,21 @@ static void ipv4_a_query_multiple_results(void) {
destroy_endpoint(client);
}
static void ipv4_a_query_with_v4mapped_results(void) {
int timeout;
endpoint_t *client = create_endpoint();
CU_ASSERT_PTR_NOT_NULL_FATAL(client);
timeout = belle_sip_stack_get_dns_timeout(client->stack);
client->resolver_ctx = belle_sip_stack_resolve_a(client->stack, IPV4_SIP_DOMAIN, SIP_PORT, AF_INET6, a_resolve_done, client);
CU_ASSERT_NOT_EQUAL(client->resolver_ctx, NULL);
CU_ASSERT_TRUE(wait_for(client->stack, &client->resolve_done, 1, timeout));
CU_ASSERT_PTR_NOT_NULL(client->ai_list);
destroy_endpoint(client);
}
/* Successful IPv6 AAAA query */
static void ipv6_aaaa_query(void) {
struct addrinfo *ai;
......@@ -222,9 +241,14 @@ static void ipv6_aaaa_query(void) {
CU_ASSERT_TRUE(wait_for(client->stack, &client->resolve_done, 1, timeout));
CU_ASSERT_PTR_NOT_EQUAL(client->ai_list, NULL);
if (client->ai_list) {
struct addrinfo *next;
struct sockaddr_in6 *sock_in6 = (struct sockaddr_in6 *)client->ai_list->ai_addr;
CU_ASSERT_EQUAL(ntohs(sock_in6->sin6_port), SIP_PORT);
/*the IPv6 address shall return first, and must be a real ipv6 address*/
CU_ASSERT_TRUE(client->ai_list->ai_family==AF_INET6);
CU_ASSERT_FALSE(client->ai_list->ai_flags & AI_V4MAPPED);
ai = belle_sip_ip_address_to_addrinfo(AF_INET6, IPV6_SIP_IP, SIP_PORT);
CU_ASSERT_PTR_NOT_NULL(ai);
if (ai) {
struct in6_addr *ipv6_address = &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
int i;
......@@ -233,8 +257,25 @@ static void ipv6_aaaa_query(void) {
}
freeaddrinfo(ai);
}
next=client->ai_list->ai_next;
CU_ASSERT_PTR_NOT_NULL(next);
if (next){
sock_in6 = (struct sockaddr_in6 *)next->ai_addr;
CU_ASSERT_TRUE(next->ai_family==AF_INET6);
CU_ASSERT_TRUE(next->ai_flags & AI_V4MAPPED);
CU_ASSERT_EQUAL(ntohs(sock_in6->sin6_port), SIP_PORT);
ai = belle_sip_ip_address_to_addrinfo(AF_INET6, IPV6_SIP_IPV4, SIP_PORT);
CU_ASSERT_PTR_NOT_NULL(ai);
if (ai) {
struct in6_addr *ipv6_address = &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
int i;
for (i = 0; i < 8; i++) {
CU_ASSERT_EQUAL(sock_in6->sin6_addr.s6_addr[i], ipv6_address->s6_addr[i]);
}
freeaddrinfo(ai);
}
}
}
destroy_endpoint(client);
}
......@@ -346,12 +387,13 @@ test_t resolver_tests[] = {
{ "A query (IPv4) with send failure", ipv4_a_query_send_failure },
{ "A query (IPv4) with timeout", ipv4_a_query_timeout },
{ "A query (IPv4) with multiple results", ipv4_a_query_multiple_results },
{ "A query (IPv4) with AI_V4MAPPED results", ipv4_a_query_with_v4mapped_results },
{ "Local query", local_query },
{ "AAAA query (IPv6)", ipv6_aaaa_query },
{ "SRV query", srv_query },
{ "SRV + A query", srv_a_query },
{ "SRV + A query with no SRV result", srv_a_query_no_srv_result },
{ "Local A+SRV query", local_full_query },
{ "Local SRV+A query", local_full_query },
{ "No query needed", no_query_needed },
};
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment