diff --git a/src/conn.c b/src/conn.c index 757540f..8b43fdc 100644 --- a/src/conn.c +++ b/src/conn.c @@ -426,11 +426,11 @@ int xmpp_connect_client(xmpp_conn_t * const conn, xmpp_conn_handler callback, void * const userdata) { - char *buf = NULL; + resolver_srv_rr_t *srv_rr_list = NULL; char *domain; const char *host; unsigned short port; - int found = 0; + int found = XMPP_DOMAIN_NOT_FOUND; int rc; domain = xmpp_jid_domain(conn->ctx, conn->jid); @@ -440,19 +440,39 @@ int xmpp_connect_client(xmpp_conn_t * const conn, xmpp_debug(conn->ctx, "xmpp", "Connecting via altdomain."); host = altdomain; port = altport ? altport : _conn_default_port(conn, XMPP_CLIENT); - found = 1; + found = XMPP_DOMAIN_ALTDOMAIN; /* SSL tunneled connection on 5223 port is legacy and doesn't * have an SRV record. */ } else if (!conn->tls_legacy_ssl) { - host = buf = xmpp_alloc(conn->ctx, MAX_DOMAIN_LEN); - if (buf != NULL) { - found = resolver_srv_lookup("xmpp-client", "tcp", domain, - buf, MAX_DOMAIN_LEN, &port); + host = xmpp_alloc(conn->ctx, MAX_DOMAIN_LEN); + srv_rr_list = xmpp_alloc(conn->ctx, sizeof(resolver_srv_rr_t)); + if (srv_rr_list != NULL) { + srv_rr_list->next = NULL; + found = resolver_srv_lookup(conn->ctx, "xmpp-client", "tcp", domain, + &srv_rr_list); + + /* Try DNS-SRV list connection*/ + if (found == XMPP_DOMAIN_FOUND) { + resolver_srv_rr_t *srv_rr_p = srv_rr_list; + sock_t sock_try; + while(srv_rr_p != NULL) { + xmpp_debug(conn->ctx, "xmpp", "Try sock_connect %s:%d ",srv_rr_p->target, srv_rr_p->port); + sock_try = sock_connect(srv_rr_p->target, srv_rr_p->port); + if (sock_try == 0){ + host = srv_rr_p->target; + port = srv_rr_p->port; + found = XMPP_DOMAIN_FOUND; + break; + } + srv_rr_p = srv_rr_p->next; + found = XMPP_DOMAIN_NOT_FOUND; + } + } } } - if (!found) { + if (XMPP_DOMAIN_NOT_FOUND == found) { xmpp_debug(conn->ctx, "xmpp", "SRV lookup failed, " "connecting via domain."); host = domain; @@ -462,7 +482,11 @@ int xmpp_connect_client(xmpp_conn_t * const conn, rc = _conn_connect(conn, domain, host, port, XMPP_CLIENT, callback, userdata); xmpp_free(conn->ctx, domain); - if (buf) xmpp_free(conn->ctx, buf); + while (srv_rr_list!=NULL) { + resolver_srv_rr_t *rr_next = srv_rr_list->next; + xmpp_free(conn->ctx, srv_rr_list); + srv_rr_list = rr_next; + } return rc; } diff --git a/src/resolver.c b/src/resolver.c index ac9cbd2..1d39421 100644 --- a/src/resolver.c +++ b/src/resolver.c @@ -111,23 +111,91 @@ static unsigned message_name_len(const unsigned char *buf, size_t buf_len, return message_name_get(buf, buf_len, buf_offset, NULL, SIZE_MAX); } -int resolver_srv_lookup_buf(const unsigned char *buf, size_t len, - char *target, size_t target_len, - unsigned short *port) +int resolver_srv_list_sort(resolver_srv_rr_t **srv_rr_list) { - int set = 0; + resolver_srv_rr_t * rr_head; + resolver_srv_rr_t * rr_current; + resolver_srv_rr_t * rr_next; + resolver_srv_rr_t * rr_prev; + int swap; + + rr_head = *srv_rr_list; + + if ((rr_head == NULL) || (rr_head->next == NULL)) { + /* Empty or single record list */ + return 0; + } + + do { + rr_prev = NULL; + rr_current = rr_head; + rr_next = rr_head->next; + swap = 0; + while (rr_next!=NULL) { + /* + * RFC2052: When selecting a target host among the + * those that have the same priority, the chance of trying + * this one first SHOULD be proportional to its weight. + */ + if ((rr_current->priority > rr_next->priority) || + (rr_current->priority == rr_next->priority && rr_current->weight < rr_next->weight )) + { + /* Swap node */ + swap = 1; + if (rr_prev!=NULL) { + rr_prev->next = rr_next; + } else { + /* Swap head node */ + rr_head = rr_next; + } + rr_current->next = rr_next->next; + rr_next->next = rr_current; + + rr_prev = rr_next; + rr_next = rr_current->next; + } else { + /* Next node */ + rr_prev = rr_current; + rr_current = rr_current->next; + rr_next = rr_current->next; + } + } + }while (swap != 0) ; + + *srv_rr_list = rr_head; + + return 0; +} + +int resolver_srv_list_add(resolver_srv_rr_t **srv_rr_list, resolver_srv_rr_t *srv_rr_data) +{ + resolver_srv_rr_t *rr_head; + + rr_head = *srv_rr_list; + if (strlen(rr_head->target) > 0) { + srv_rr_data->next = rr_head; + } + + *srv_rr_list = srv_rr_data; + + return 0; +} + +int resolver_srv_lookup_buf(xmpp_ctx_t *ctx, const unsigned char *buf, size_t len, + resolver_srv_rr_t **srv_rr_list) +{ + int set = XMPP_DOMAIN_NOT_FOUND; unsigned i; unsigned j; unsigned name_len; unsigned rdlength; uint16_t type; uint16_t class; - uint16_t priority; - uint16_t priority_min; struct message_header header; + resolver_srv_rr_t *srv_rr_data; if (len < MESSAGE_HEADER_LEN) - return 0; + return XMPP_DOMAIN_NOT_FOUND; header.id = xmpp_ntohs_ptr(&buf[0]); header.octet2 = buf[2]; @@ -139,7 +207,7 @@ int resolver_srv_lookup_buf(const unsigned char *buf, size_t len, if (message_header_qr(&header) != MESSAGE_RESPONSE || message_header_rcode(&header) != 0) { - return 0; + return XMPP_DOMAIN_NOT_FOUND; } j = MESSAGE_HEADER_LEN; @@ -148,7 +216,7 @@ int resolver_srv_lookup_buf(const unsigned char *buf, size_t len, name_len = message_name_len(buf, len, j); if (name_len == 0) { /* error in name format */ - return 0; + return XMPP_DOMAIN_NOT_FOUND; } j += name_len + 4; } @@ -157,7 +225,6 @@ int resolver_srv_lookup_buf(const unsigned char *buf, size_t len, * RFC2052: A client MUST attempt to contact the target host * with the lowest-numbered priority it can reach. */ - priority_min = UINT16_MAX; for (i = 0; i < header.ancount; ++i) { name_len = message_name_len(buf, len, j); j += name_len; @@ -166,49 +233,53 @@ int resolver_srv_lookup_buf(const unsigned char *buf, size_t len, rdlength = xmpp_ntohs_ptr(&buf[j + 8]); j += 10; if (type == MESSAGE_T_SRV && class == MESSAGE_C_IN) { - priority = xmpp_ntohs_ptr(&buf[j]); - if (!set || priority < priority_min) { - *port = xmpp_ntohs_ptr(&buf[j + 4]); - name_len = message_name_get(buf, len, j + 6, target, target_len); - set = name_len > 0 ? 1 : 0; - priority_min = priority; - } + srv_rr_data = xmpp_alloc(ctx, sizeof(*srv_rr_data)); + srv_rr_data->priority = xmpp_ntohs_ptr(&buf[j]); + srv_rr_data->weight = xmpp_ntohs_ptr(&buf[j+2]); + srv_rr_data->port = xmpp_ntohs_ptr(&buf[j + 4]); + name_len = message_name_get(buf, len, j + 6, &(srv_rr_data->target), MAX_DOMAIN_LEN); + srv_rr_data->next = NULL; + set = name_len > 0 ? XMPP_DOMAIN_FOUND : XMPP_DOMAIN_NOT_FOUND; + resolver_srv_list_add(srv_rr_list,srv_rr_data); } j += rdlength; } + resolver_srv_list_sort(srv_rr_list); return set; } -int resolver_srv_lookup(const char *service, const char *proto, - const char *domain, char *target, - size_t target_len, unsigned short *port) +int resolver_srv_lookup(xmpp_ctx_t *ctx, const char *service, const char *proto, + const char *domain, resolver_srv_rr_t **srv_rr_list) { char fulldomain[2048]; unsigned char buf[65535]; int len; - int set = 0; + int set = XMPP_DOMAIN_NOT_FOUND; +#ifdef _WIN32 + resolver_srv_rr_t *rr; +#endif xmpp_snprintf(fulldomain, sizeof(fulldomain), "_%s._%s.%s", service, proto, domain); #ifdef _WIN32 - set = resolver_win32_srv_lookup(fulldomain, target, target_len, port); - if (set) - return set; + rr = xmpp_alloc(ctx, sizeof(*rr)); + rr->next = NULL; + set = resolver_win32_srv_lookup(fulldomain, rr->target, sizeof(rr->target), &rr->port); + if (set) *srv_rr_list = rr; + else xmpp_free(ctx, rr); len = resolver_win32_srv_query(fulldomain, buf, sizeof(buf)); #else /* _WIN32 */ len = res_query(fulldomain, MESSAGE_C_IN, MESSAGE_T_SRV, buf, sizeof(buf)); #endif /* _WIN32 */ if (len > 0) - set = resolver_srv_lookup_buf(buf, (size_t)len, target, target_len, port); + set = resolver_srv_lookup_buf(ctx, buf, (size_t)len, srv_rr_list); return set; } -/* FIXME: interface that returns array of results, maybe sorted by priority */ - #ifdef _WIN32 /******************************************************************************* diff --git a/src/resolver.h b/src/resolver.h index f4f662f..a46b616 100644 --- a/src/resolver.h +++ b/src/resolver.h @@ -17,25 +17,35 @@ #define __LIBSTROPHE_RESOLVER_H__ #include "ostypes.h" +#include "common.h" -/** Perform lookup for RFC1035 message format. */ -int resolver_srv_lookup_buf(const unsigned char *buf, size_t len, - char *target, size_t target_len, - unsigned short *port); +typedef enum { + XMPP_DOMAIN_NOT_FOUND, + XMPP_DOMAIN_FOUND, + XMPP_DOMAIN_ALTDOMAIN +} xmpp_domain_state_t; +typedef struct resolver_srv_rr_struc { + uint16_t priority; + uint16_t weight; + uint16_t port; + char target[MAX_DOMAIN_LEN]; + struct resolver_srv_rr_struc *next; +}resolver_srv_rr_t; + +/* This function allocates all elements including the 1st one. (*srv_rr_list) is the result */ +int resolver_srv_lookup_buf(xmpp_ctx_t *ctx, const unsigned char *buf, size_t len, + resolver_srv_rr_t **srv_rr_list); /** Resolve SRV record. * * @param service service of the SRV record * @param proto protocol of the SRV record * @param domain resolving domain - * @param target pre-allocated string where result is stored - * @param target_len maximum size of the target - * @param port pointer where resulting port is stored + * @param srv_rr_list is the result * - * @return 1 on success or 0 on fail + * @return XMPP_DOMAIN_FOUND on success or XMPP_DOMAIN_NOT_FOUND on fail */ -int resolver_srv_lookup(const char *service, const char *proto, - const char *domain, char *target, - size_t target_len, unsigned short *port); +int resolver_srv_lookup(xmpp_ctx_t *ctx, const char *service, const char *proto, + const char *domain, resolver_srv_rr_t **srv_rr_list); #endif /* __LIBSTROPHE_RESOLVER_H__ */