also parse authority and additional RRs
[m6w6/ext-ares] / php_ares.c
index 1685d43069d20402e5ddb5054ce22bd991a0c73a..a0c526bfe298b8bdefca286c6672ac17d4e7c864 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "php.h"
 #include "php_ini.h"
+#include "php_streams.h"
 #include "ext/standard/info.h"
 #include "php_ares.h"
 
@@ -68,10 +69,23 @@ static int le_ares_query;
 static const char *php_ares_C_names[] = {
        "INVALID",
        "IN",
+       "C2"
        "CHAOS",
-       "HS",
-       "NONE",
-       "ANY",
+       "HS",           /* 4 */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
+       "","","","","","","","","",                                                                             /*  9x */
+       "NONE",         /* 254 */
+       "ANY",          /* 255 */
 };
 
 static const char *php_ares_T_names[] = {
@@ -128,8 +142,6 @@ static const char *php_ares_T_names[] = {
        "","","","","","","","","","","","","","","","","","","","",    /* 20x */
        "","","","","","","","","","","","","","","","","","","","",    /* 20x */
        "","","","","","","","","","","","","","","","","","","","",    /* 20x */
-       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
-       "","","","","","","","","","","","","","","","","","","","",    /* 20x */
        "","","","","","",                                                                                              /*  6x */
        "TKEY", /* 249 */
        "TSIG",
@@ -163,6 +175,18 @@ static const char *php_ares_T_names[] = {
 
 #define PHP_ARES_QUERY_BUFFER_LEN 2<<0xf
 
+#ifdef HAVE_ARES_INET_PTON
+#      define inet_pton ares_inet_pton_wrapper
+static inline int ares_inet_pton_wrapper(int af, const void *src, char *dst, socklen_t size)
+{
+       return dst == ares_inet_pton(af, src, dst, size);
+}
+#endif
+
+#ifdef HAVE_ARES_INET_NTOP
+#      define inet_ntop ares_inet_ntop
+#endif
+
 /* {{{ typedefs */
 typedef struct _php_ares_options {
        struct ares_options strct;
@@ -490,9 +514,26 @@ local void php_ares_query_free(php_ares_query **query) /* {{{ */
 }
 /* }}} */
 
+local zend_bool is_numeric(zval **p, long *lval) { /* {{{ */
+       zval *tmp = *p;
+       switch (Z_TYPE_PP(p)) {
+       case IS_STRING:
+               convert_to_long_ex(&tmp);
+               /* no break */
+       case IS_LONG:
+               *lval = Z_LVAL_P(tmp);
+               if (tmp != *p) {
+                       zval_ptr_dtor(&tmp);
+               }
+               return !!*lval;
+       }
+       return 0;
+} /* }}} */
+
 local php_ares_options *php_ares_options_ctor(php_ares_options *options, HashTable *ht) /* {{{ */
 {
        int i;
+       long lval;
        zval **opt, **entry;
        
        if (!options) {
@@ -501,29 +542,43 @@ local php_ares_options *php_ares_options_ctor(php_ares_options *options, HashTab
        memset(options, 0, sizeof(php_ares_options));
        
        if (ht && zend_hash_num_elements(ht)) {
-               if ((SUCCESS == zend_hash_find(ht, "flags", sizeof("flags"), (void *) &opt)) && (Z_TYPE_PP(opt) == IS_LONG)) {
+               if ((SUCCESS == zend_hash_find(ht, "flags", sizeof("flags"), (void *) &opt)) && is_numeric(opt, &lval)) {
                        options->flags |= ARES_OPT_FLAGS;
-                       options->strct.flags = Z_LVAL_PP(opt);
+                       options->strct.flags = lval;
                }
-               if ((SUCCESS == zend_hash_find(ht, "timeout", sizeof("timeout"), (void *) &opt)) && (Z_TYPE_PP(opt) == IS_LONG)) {
+#ifdef ARES_OPT_TIMEOUTMS
+               if ((SUCCESS == zend_hash_find(ht, "timeoutms", sizeof("timeoutms"), (void *) &opt)) && is_numeric(opt, &lval)) {
+                       options->flags |= ARES_OPT_TIMEOUTMS;
+                       options->strct.timeout = lval;
+               } else
+#endif
+               if ((SUCCESS == zend_hash_find(ht, "timeout", sizeof("timeout"), (void *) &opt)) && is_numeric(opt, &lval)) {
                        options->flags |= ARES_OPT_TIMEOUT;
-                       options->strct.timeout = Z_LVAL_PP(opt);
+                       options->strct.timeout = lval;
                }
-               if ((SUCCESS == zend_hash_find(ht, "tries", sizeof("tries"), (void *) &opt)) && (Z_TYPE_PP(opt) == IS_LONG)) {
+               if ((SUCCESS == zend_hash_find(ht, "tries", sizeof("tries"), (void *) &opt)) && is_numeric(opt, &lval)) {
                        options->flags |= ARES_OPT_TRIES;
-                       options->strct.tries = Z_LVAL_PP(opt);
+                       options->strct.tries = lval;
                }
-               if ((SUCCESS == zend_hash_find(ht, "ndots", sizeof("ndots"), (void *) &opt)) && (Z_TYPE_PP(opt) == IS_LONG)) {
+               if ((SUCCESS == zend_hash_find(ht, "ndots", sizeof("ndots"), (void *) &opt)) && is_numeric(opt, &lval)) {
                        options->flags |= ARES_OPT_NDOTS;
-                       options->strct.ndots = Z_LVAL_PP(opt);
+                       options->strct.ndots = lval;
                }
-               if ((SUCCESS == zend_hash_find(ht, "udp_port", sizeof("udp_port"), (void *) &opt)) && (Z_TYPE_PP(opt) == IS_LONG)) {
+               if ((SUCCESS == zend_hash_find(ht, "udp_port", sizeof("udp_port"), (void *) &opt)) && is_numeric(opt, &lval)) {
                        options->flags |= ARES_OPT_UDP_PORT;
-                       options->strct.udp_port = htons((unsigned short) Z_LVAL_PP(opt));
+#ifdef PHP_ARES_CARES
+                       options->strct.udp_port = (unsigned short) lval;
+#else
+                       options->strct.udp_port = htons((unsigned short) lval);
+#endif
                }
-               if ((SUCCESS == zend_hash_find(ht, "tcp_port", sizeof("tcp_port"), (void *) &opt)) && (Z_TYPE_PP(opt) == IS_LONG)) {
+               if ((SUCCESS == zend_hash_find(ht, "tcp_port", sizeof("tcp_port"), (void *) &opt)) && is_numeric(opt, &lval)) {
                        options->flags |= ARES_OPT_TCP_PORT;
-                       options->strct.tcp_port = htons((unsigned short) Z_LVAL_PP(opt));
+#ifdef PHP_ARES_CARES
+                       options->strct.tcp_port = (unsigned short) lval;
+#else
+                       options->strct.tcp_port = htons((unsigned short) lval);
+#endif
                }
                if ((SUCCESS == zend_hash_find(ht, "servers", sizeof("servers"), (void *) &opt)) && (Z_TYPE_PP(opt) == IS_ARRAY) && (i = zend_hash_num_elements(Z_ARRVAL_PP(opt)))) {
                        options->strct.servers = ecalloc(i, sizeof(struct in_addr));
@@ -531,7 +586,7 @@ local php_ares_options *php_ares_options_ctor(php_ares_options *options, HashTab
                                        SUCCESS == zend_hash_get_current_data(Z_ARRVAL_PP(opt), (void *) &entry);
                                        zend_hash_move_forward(Z_ARRVAL_PP(opt))) {
                                if (Z_TYPE_PP(entry) == IS_STRING) {
-                                       inet_aton(Z_STRVAL_PP(entry), &options->strct.servers[options->strct.nservers++]);
+                                       inet_pton(AF_INET, Z_STRVAL_PP(entry), &options->strct.servers[options->strct.nservers++]);
                                }
                        }
                        if (options->strct.nservers) {
@@ -555,6 +610,17 @@ local php_ares_options *php_ares_options_ctor(php_ares_options *options, HashTab
                        options->flags |= ARES_OPT_LOOKUPS;
                        options->strct.lookups = estrdup(Z_STRVAL_PP(opt));
                }
+#ifdef ARES_OPT_ROTATE
+               if ((SUCCESS == zend_hash_find(ht, "rotate", sizeof("rotate"), (void *) &opt)) && i_zend_is_true(*opt)) {
+                       options->flags |= ARES_OPT_ROTATE;
+               }
+#endif
+#ifdef ARES_OPT_EDNSPSZ
+               if ((SUCCESS == zend_hash_find(ht, "ednspsz", sizeof("ednspsz"), (void *) &opt)) && is_numeric(opt, &lval)) {
+                       options->flags |= ARES_OPT_EDNSPSZ;
+                       options->strct.ednspsz = lval;
+               }
+#endif
        }
        
        return options;
@@ -607,13 +673,153 @@ local PHP_ARES_EXPAND_LEN_TYPE php_ares_skip(const unsigned char *pointer, const
        return -1;
 }
 
+local int php_ares_parse_record(const unsigned char *abuf, const int alen, const unsigned char **rptr, zval *result TSRMLS_DC)
+{
+       uint16_t stmp, type, class;
+       uint32_t ltmp, ttl;
+       zval *entry = NULL;
+       PHP_ARES_EXPAND_LEN_TYPE byte_count;
+       const unsigned char *pointer = *rptr;
+       char *name;
+       int rc;
+
+       if (0 > (byte_count = php_ares_skip(pointer, abuf, alen TSRMLS_CC))) {
+               return FAILURE;
+       }
+
+       pointer += byte_count;
+
+       MAKE_STD_ZVAL(entry);
+       array_init(entry);
+
+       GETSHORT(type, pointer);
+       add_assoc_string(entry, "type", estrdup(php_ares_T_names[type]), 0);
+       GETSHORT(class, pointer);
+       add_assoc_string(entry, "class", estrdup(php_ares_C_names[class]), 0);
+       GETLONG(ttl, pointer);
+       add_assoc_long(entry, "ttl", ttl);
+       GETSHORT(byte_count, pointer);
+#if 0
+       fprintf(stderr, ">> processing %s answer of length %d\n", php_ares_T_names[type], byte_count);
+#endif
+       switch (type) {
+               case T_A:
+                       name = ecalloc(1, 16);
+                       inet_ntop(AF_INET, pointer, name, 16);
+                       add_assoc_string(entry, "addr", name, 0);
+                       pointer += byte_count;
+                       break;
+#ifdef T_AAAA
+               case T_AAAA:
+                       name = ecalloc(1, 48);
+                       inet_ntop(AF_INET6, pointer, name, 48);
+                       add_assoc_string(entry, "addr", name, 0);
+                       pointer += byte_count;
+                       break;
+#endif
+
+               case T_NS:
+               case T_PTR:
+               case T_CNAME:
+                       if (ARES_SUCCESS != (rc = ares_expand_name(pointer, abuf, alen, &name, &byte_count))) {
+                               PHP_ARES_ERROR(rc);
+                               return FAILURE;
+                       }
+                       pointer += byte_count;
+                       add_assoc_string(entry, "name", name, 1);
+                       ares_free_string(name);
+                       break;
+
+               case T_MX:
+                       GETSHORT(stmp, pointer);
+                       if (ARES_SUCCESS != (rc = ares_expand_name(pointer, abuf, alen, &name, &byte_count))) {
+                               PHP_ARES_ERROR(rc);
+                               return FAILURE;
+                       }
+                       pointer += byte_count;
+                       add_assoc_long(entry, "weight", stmp);
+                       add_assoc_string(entry, "name", name, 1);
+                       ares_free_string(name);
+                       break;
+
+               case T_SRV:
+                       GETSHORT(stmp, pointer);
+                       add_assoc_long(entry, "priority", stmp);
+                       GETSHORT(stmp, pointer);
+                       add_assoc_long(entry, "weight", stmp);
+                       GETSHORT(stmp, pointer);
+                       add_assoc_long(entry, "port", stmp);
+
+                       if (ARES_SUCCESS != (rc = ares_expand_name(pointer, abuf, alen, &name, &byte_count))) {
+                               PHP_ARES_ERROR(rc);
+                               zval_ptr_dtor(&entry);
+                               return FAILURE;
+                       }
+                       pointer += byte_count;
+                       add_assoc_string(entry, "name", name, 1);
+                       ares_free_string(name);
+                       break;
+
+               case T_SOA:
+                       if (ARES_SUCCESS != (rc = ares_expand_name(pointer, abuf, alen, &name, &byte_count))) {
+                               PHP_ARES_ERROR(rc);
+                               zval_ptr_dtor(&entry);
+                               return FAILURE;
+                       }
+                       pointer += byte_count;
+                       add_assoc_string(entry, "name", name, 1);
+                       ares_free_string(name);
+
+                       if (ARES_SUCCESS != (rc = ares_expand_name(pointer, abuf, alen, &name, &byte_count))) {
+                               PHP_ARES_ERROR(rc);
+                               zval_ptr_dtor(&entry);
+                               return FAILURE;
+                       }
+                       pointer += byte_count;
+                       add_assoc_string(entry, "mail", name, 1);
+                       ares_free_string(name);
+
+                       GETLONG(ltmp, pointer);
+                       add_assoc_long(entry, "serial", ltmp);
+                       GETLONG(ltmp, pointer);
+                       add_assoc_long(entry, "refresh", ltmp);
+                       GETLONG(ltmp, pointer);
+                       add_assoc_long(entry, "retry", ltmp);
+                       GETLONG(ltmp, pointer);
+                       add_assoc_long(entry, "expire", ltmp);
+                       GETLONG(ltmp, pointer);
+                       add_assoc_long(entry, "minimum-ttl", ltmp);
+                       break;
+
+               case T_TXT:
+                       for (ltmp = 0; ltmp < byte_count; ltmp += pointer[ltmp] + 1) {
+                               add_next_index_stringl(entry, (const char *) &pointer[ltmp + 1], pointer[ltmp], 1);
+                       }
+                       pointer += byte_count;
+                       break;
+
+               default:
+#ifndef HAVE_INET_PTON
+                       skip:
+#endif
+                       zval_ptr_dtor(&entry);
+                       entry = NULL;
+                       pointer += byte_count;
+                       break;
+       }
+
+       if (entry) {
+               add_next_index_zval(result, entry);
+       }
+       return SUCCESS;
+}
+
 local int php_ares_parse(const unsigned char *abuf, int alen, zval *result TSRMLS_DC) /* {{{ */
 {
        HEADER *header;
        PHP_ARES_EXPAND_LEN_TYPE byte_count;
        const unsigned char *pointer;
-       char *name;
-       int rc, query_count, answer_count;
+       int query_count, answer_count, auth_count, other_count;
 
        convert_to_array(result);
 
@@ -623,7 +829,7 @@ local int php_ares_parse(const unsigned char *abuf, int alen, zval *result TSRML
 
        header = (HEADER *) abuf;
        pointer = abuf + HFIXEDSZ;
-       
+
        for (query_count = ntohs(header->qdcount); query_count--; pointer += byte_count + QFIXEDSZ) {
                if (0 > (byte_count = php_ares_skip(pointer, abuf, alen TSRMLS_CC))) {
                        return FAILURE;
@@ -631,125 +837,20 @@ local int php_ares_parse(const unsigned char *abuf, int alen, zval *result TSRML
        }
 
        for (answer_count = ntohs(header->ancount); answer_count-- && pointer < (abuf + alen); ) {
-               uint16_t stmp, type, class;
-               uint32_t ltmp, ttl;
-               zval **entry_ptr, *entry = NULL;
-
-               if (0 > (byte_count = php_ares_skip(pointer, abuf, alen TSRMLS_CC))) {
+               if (SUCCESS != php_ares_parse_record(abuf, alen, &pointer, result TSRMLS_CC)) {
                        return FAILURE;
                }
+       }
 
-               pointer += byte_count;
-
-               MAKE_STD_ZVAL(entry);
-               array_init(entry);
-
-               GETSHORT(type, pointer);
-               add_assoc_string(entry, "type", estrdup(php_ares_T_names[type]), 0);
-               GETSHORT(class, pointer);
-               add_assoc_string(entry, "class", estrdup(php_ares_C_names[class]), 0);
-               GETLONG(ttl, pointer);
-               add_assoc_long(entry, "ttl", ttl);
-               GETSHORT(byte_count, pointer);
-#if 0
-               fprintf(stderr, ">> processing %s answer of length %d\n", php_ares_T_names[type], byte_count);
-#endif
-               switch (type) {
-                       case T_A:
-                               spprintf(&name, 0, "%d.%d.%d.%d", pointer[0], pointer[1], pointer[2], pointer[3]);
-                               add_assoc_string(entry, "addr", name, 0);
-                               pointer += byte_count;
-                               break;
-
-                       case T_NS:
-                       case T_PTR:
-                       case T_CNAME:
-                               if (ARES_SUCCESS != (rc = ares_expand_name(pointer, abuf, alen, &name, &byte_count))) {
-                                       PHP_ARES_ERROR(rc);
-                                       return FAILURE;
-                               }
-                               pointer += byte_count;
-                               add_assoc_string(entry, "name", name, 1);
-                               ares_free_string(name);
-                               break;
-
-                       case T_MX:
-                               GETSHORT(stmp, pointer);
-                               if (ARES_SUCCESS != (rc = ares_expand_name(pointer, abuf, alen, &name, &byte_count))) {
-                                       PHP_ARES_ERROR(rc);
-                                       return FAILURE;
-                               }
-                               pointer += byte_count;
-                               add_assoc_long(entry, "weight", stmp);
-                               add_assoc_string(entry, "name", name, 1);
-                               ares_free_string(name);
-                               break;
-
-                       case T_SRV:
-                               GETSHORT(stmp, pointer);
-                               add_assoc_long(entry, "priority", stmp);
-                               GETSHORT(stmp, pointer);
-                               add_assoc_long(entry, "weight", stmp);
-                               GETSHORT(stmp, pointer);
-                               add_assoc_long(entry, "port", stmp);
-
-                               if (ARES_SUCCESS != (rc = ares_expand_name(pointer, abuf, alen, &name, &byte_count))) {
-                                       PHP_ARES_ERROR(rc);
-                                       zval_ptr_dtor(&entry);
-                                       return FAILURE;
-                               }
-                               pointer += byte_count;
-                               add_assoc_string(entry, "name", name, 1);
-                               ares_free_string(name);
-                               break;
-
-                       case T_SOA:
-                               if (ARES_SUCCESS != (rc = ares_expand_name(pointer, abuf, alen, &name, &byte_count))) {
-                                       PHP_ARES_ERROR(rc);
-                                       zval_ptr_dtor(&entry);
-                                       return FAILURE;
-                               }
-                               pointer += byte_count;
-                               add_assoc_string(entry, "name", name, 1);
-                               ares_free_string(name);
-
-                               if (ARES_SUCCESS != (rc = ares_expand_name(pointer, abuf, alen, &name, &byte_count))) {
-                                       PHP_ARES_ERROR(rc);
-                                       zval_ptr_dtor(&entry);
-                                       return FAILURE;
-                               }
-                               pointer += byte_count;
-                               add_assoc_string(entry, "mail", name, 1);
-                               ares_free_string(name);
-
-                               GETLONG(ltmp, pointer);
-                               add_assoc_long(entry, "serial", ltmp);
-                               GETLONG(ltmp, pointer);
-                               add_assoc_long(entry, "refresh", ltmp);
-                               GETLONG(ltmp, pointer);
-                               add_assoc_long(entry, "retry", ltmp);
-                               GETLONG(ltmp, pointer);
-                               add_assoc_long(entry, "expire", ltmp);
-                               GETLONG(ltmp, pointer);
-                               add_assoc_long(entry, "minimum-ttl", ltmp);
-                               break;
-
-                       case T_TXT:
-                               for (ltmp = 0; ltmp < byte_count; ltmp += pointer[ltmp] + 1) {
-                                       add_next_index_stringl(entry, (const char *) &pointer[ltmp + 1], pointer[ltmp], 1);
-                               }
-                               pointer += byte_count;
-                               break;
-
-                       default:
-                               zval_ptr_dtor(&entry);
-                               entry = NULL;
-                               pointer += byte_count;
-                               break;
+       for (auth_count = ntohs(header->nscount); auth_count-- && pointer < (abuf + alen); ) {
+               if (SUCCESS != php_ares_parse_record(abuf, alen, &pointer, result TSRMLS_CC)) {
+                       return FAILURE;
                }
+       }
 
-               if (entry) {
-                       add_next_index_zval(result, entry);
+       for (other_count = ntohs(header->arcount); other_count-- && pointer < (abuf + alen); ) {
+               if (SUCCESS != php_ares_parse_record(abuf, alen, &pointer, result TSRMLS_CC)) {
+                       return FAILURE;
                }
        }
 
@@ -771,7 +872,7 @@ static void php_ares_callback_func_old(void *aq, int status, unsigned char *abuf
                
                MAKE_STD_ZVAL(parsed);
                ZVAL_NULL(parsed);
-               if (SUCCESS == php_ares_parse(abuf, alen, parsed)) {
+               if (SUCCESS == php_ares_parse(abuf, alen, parsed TSRMLS_CC)) {
                        q->result.std.arr = parsed;
                } else {
                        zval_ptr_dtor(&parsed);
@@ -958,13 +1059,19 @@ local int php_ares_process(php_ares *ares, long max_timeout) /* {{{ */
 }
 /* }}} */
 
-local int php_ares_publish_fds(fd_set *R, fd_set *W, zval *r, zval *w) /* {{{ */
+local int php_ares_publish_fds(fd_set *R, fd_set *W, zval *r, zval *w, HashTable *resource_map) /* {{{ */
 {
        int i, nfds = 0;
+       zval **fd;
        
        for (i = 0; i < FD_SETSIZE; ++i) {
                if (FD_ISSET(i, R)) {
-                       add_next_index_long(r, i);
+                       if (resource_map && (SUCCESS == zend_hash_index_find(resource_map, i, (void *) &fd))) {
+                               zval_add_ref(fd);
+                               add_next_index_zval(r, *fd);
+                       } else {
+                               add_next_index_long(r, i);
+                       }
                        if (i > nfds) {
                                nfds = i;
                        }
@@ -973,7 +1080,12 @@ local int php_ares_publish_fds(fd_set *R, fd_set *W, zval *r, zval *w) /* {{{ */
        
        for (i = 0; i < FD_SETSIZE; ++i) {
                if (FD_ISSET(i, W)) {
-                       add_next_index_long(w, i);
+                       if (resource_map && (SUCCESS == zend_hash_index_find(resource_map, i, (void *) &fd))) {
+                               zval_add_ref(fd);
+                               add_next_index_zval(r, *fd);
+                       } else {
+                               add_next_index_long(w, i);
+                       }
                        if (i > nfds) {
                                nfds = i;
                        }
@@ -984,16 +1096,33 @@ local int php_ares_publish_fds(fd_set *R, fd_set *W, zval *r, zval *w) /* {{{ */
 }
 /* }}} */
 
-local int php_ares_extract_fds(zval *r, zval *w, fd_set *R, fd_set *W) /* {{{ */
+local int php_ares_extract_fds(zval *r, zval *w, fd_set *R, fd_set *W, HashTable *resource_map TSRMLS_DC) /* {{{ */
 {
        zval **fd;
        int nfds = 0;
+       zval zmap;
        
+       INIT_ZVAL(zmap);
+       Z_ARRVAL(zmap) = resource_map;
+       Z_TYPE(zmap) = IS_ARRAY;
        if (r && zend_hash_num_elements(Z_ARRVAL_P(r))) {
                for (   zend_hash_internal_pointer_reset(Z_ARRVAL_P(r));
                                SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(r), (void *) &fd);
                                zend_hash_move_forward(Z_ARRVAL_P(r))) {
-                       if (Z_TYPE_PP(fd) == IS_LONG) {
+                       if (Z_TYPE_PP(fd) == IS_RESOURCE) {
+                               php_stream *s = NULL;
+                               int id = 0;
+
+                               ZEND_FETCH_RESOURCE_NO_RETURN(s, php_stream *, fd, -1, NULL, php_file_le_stream());
+                               if (s && (SUCCESS == php_stream_cast(s, PHP_STREAM_AS_FD_FOR_SELECT, (void *) &id, 1))) {
+                                       zval_add_ref(fd);
+                                       add_index_zval(&zmap, id, *fd);
+                                       FD_SET(id, R);
+                                       if (id > nfds) {
+                                               nfds = id;
+                                       }
+                               }
+                       } else if (Z_TYPE_PP(fd) == IS_LONG) {
                                FD_SET(Z_LVAL_PP(fd), R);
                                if (Z_LVAL_PP(fd) > nfds) {
                                        nfds = Z_LVAL_PP(fd);
@@ -1006,7 +1135,20 @@ local int php_ares_extract_fds(zval *r, zval *w, fd_set *R, fd_set *W) /* {{{ */
                for (   zend_hash_internal_pointer_reset(Z_ARRVAL_P(w));
                                SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(w), (void *) &fd);
                                zend_hash_move_forward(Z_ARRVAL_P(w))) {
-                       if (Z_TYPE_PP(fd) == IS_LONG) {
+                       if (Z_TYPE_PP(fd) == IS_RESOURCE) {
+                               php_stream *s = NULL;
+                               int id = 0;
+
+                               ZEND_FETCH_RESOURCE_NO_RETURN(s, php_stream *, fd, -1, NULL, php_file_le_stream());
+                               if (s && (SUCCESS == php_stream_cast(s, PHP_STREAM_AS_FD_FOR_SELECT, (void *) &id, 1))) {
+                                       zval_add_ref(fd);
+                                       add_index_zval(&zmap, id, *fd);
+                                       FD_SET(id, W);
+                                       if (id > nfds) {
+                                               nfds = id;
+                                       }
+                               }
+                       } else if (Z_TYPE_PP(fd) == IS_LONG) {
                                FD_SET(Z_LVAL_PP(fd), W);
                                if (Z_LVAL_PP(fd) > nfds) {
                                        nfds = Z_LVAL_PP(fd);
@@ -1048,11 +1190,11 @@ static PHP_FUNCTION(ares_init)
        php_ares *ares = NULL;
        int err;
        
-       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &opt_array)) {
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a/!", &opt_array)) {
                RETURN_FALSE;
        }
        
-       ares = emalloc(sizeof(php_ares));
+       ares = ecalloc(1, sizeof(php_ares));
        TSRMLS_SET_CTX(ares->tsrm_ls);
        zend_llist_init(&ares->queries, sizeof(php_ares_query *), (llist_dtor_func_t) php_ares_query_llist_dtor, 0);
        php_ares_options_ctor(&ares->options, opt_array ? Z_ARRVAL_P(opt_array) : NULL);
@@ -1068,6 +1210,65 @@ static PHP_FUNCTION(ares_init)
 }
 /* }}} */
 
+#ifdef HAVE_ARES_SET_LOCAL_DEV
+/* {{{ proto void ares_set_local_dev(resource ares, string dev)
+        Set the local interface name to bind to. */
+static PHP_FUNCTION(ares_set_local_dev)
+{
+       zval *rsrc;
+       char *dev_str = NULL;
+       int dev_len = 0;
+       php_ares *ares;
+
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs!", &rsrc, &dev_str, &dev_len)) {
+               RETURN_FALSE;
+       }
+       ZEND_FETCH_RESOURCE(ares, php_ares *, &rsrc, -1, PHP_ARES_LE_NAME, le_ares);
+
+       ares_set_local_dev(ares->channel, dev_str);
+}
+#endif
+
+#ifdef HAVE_ARES_SET_LOCAL_IP4
+/* {{{ proto void ares_set_local_ip4(resource ares, int addr)
+        Set the local IPv4 address to bind to. */
+static PHP_FUNCTION(ares_set_local_ip4)
+{
+       zval *rsrc;
+       long ip4_num;
+       php_ares *ares;
+
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &rsrc, &ip4_num)) {
+               RETURN_FALSE;
+       }
+       ZEND_FETCH_RESOURCE(ares, php_ares *, &rsrc, -1, PHP_ARES_LE_NAME, le_ares);
+
+       ares_set_local_ip4(ares->channel, (unsigned int) ip4_num);
+}
+#endif
+
+#ifdef HAVE_ARES_SET_LOCAL_IP6
+/* {{{ proto void ares_set_local_ip6(resource ares, string addr128bit)
+        Set the local IPv6 address to bind to. */
+static PHP_FUNCTION(ares_set_local_ip6)
+{
+       zval *rsrc;
+       char *ip6_str;
+       int ip6_len;
+       php_ares *ares;
+
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &rsrc, &ip6_str, &ip6_len)) {
+               RETURN_FALSE;
+       }
+       if (16 != ip6_len) {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Binary IPv6 address string must be exactly 16 bytes long");
+       }
+       ZEND_FETCH_RESOURCE(ares, php_ares *, &rsrc, -1, PHP_ARES_LE_NAME, le_ares);
+
+       ares_set_local_ip6(ares->channel, (unsigned char *) ip6_str);
+}
+#endif
+
 /* {{{ proto void ares_destroy(resource ares)
        Destroy the ares handle */
 static PHP_FUNCTION(ares_destroy)
@@ -1375,26 +1576,6 @@ static PHP_FUNCTION(ares_result)
        }
        
        switch (query->error) {
-               case 0:
-                       switch (query->type) {
-                               case PHP_ARES_CB_STD:
-                                       if (query->result.std.arr) {
-                                               RETVAL_ZVAL(query->result.std.arr, 1, 0);
-                                       } else {
-                                               RETVAL_STRINGL(query->result.std.buf, query->result.std.len, 1);
-                                       }
-                                       break;
-                               case PHP_ARES_CB_HOST:
-                                       object_init(return_value);
-                                       php_ares_hostent_to_struct(&query->result.host, HASH_OF(return_value));
-                                       break;
-                               case PHP_ARES_CB_NINFO:
-                                       object_init(return_value);
-                                       add_property_string(return_value, "node", query->result.ninfo.node ? query->result.ninfo.node : "", 1);
-                                       add_property_string(return_value, "service", query->result.ninfo.service ? query->result.ninfo.service : "", 1);
-                                       break;
-                       }
-                       break;
                case -1:
                        RETVAL_FALSE;
                        break;
@@ -1410,6 +1591,30 @@ static PHP_FUNCTION(ares_result)
 #endif
                        }
                        RETVAL_FALSE;
+                       /* no break */
+               case 0:
+                       switch (query->type) {
+                               case PHP_ARES_CB_STD:
+                                       if (query->result.std.arr) {
+                                               RETVAL_ZVAL(query->result.std.arr, 1, 0);
+                                       } else if (query->result.std.len > 0) {
+                                               RETVAL_STRINGL(query->result.std.buf, query->result.std.len, 1);
+                                       }
+                                       break;
+                               case PHP_ARES_CB_HOST:
+                                       if (query->result.host.h_name) {
+                                               object_init(return_value);
+                                               php_ares_hostent_to_struct(&query->result.host, HASH_OF(return_value));
+                                       }
+                                       break;
+                               case PHP_ARES_CB_NINFO:
+                                       if (query->result.ninfo.node || query->result.ninfo.service) {
+                                               object_init(return_value);
+                                               add_property_string(return_value, "node", query->result.ninfo.node ? query->result.ninfo.node : "", 1);
+                                               add_property_string(return_value, "service", query->result.ninfo.service ? query->result.ninfo.service : "", 1);
+                                       }
+                                       break;
+                       }
                        break;
        }
 }
@@ -1553,7 +1758,7 @@ static PHP_FUNCTION(ares_process)
        FD_ZERO(&R);
        FD_ZERO(&W);
        
-       php_ares_extract_fds(read, write, &R, &W);
+       php_ares_extract_fds(read, write, &R, &W, NULL TSRMLS_CC);
        ares_process(ares->channel, &R, &W);
 }
 /* }}} */
@@ -1567,6 +1772,7 @@ static PHP_FUNCTION(ares_select)
        int nfds;
        long timeout;
        struct timeval tv;
+       HashTable resource_map;
        
        if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "aal", &read, &write, &timeout)) {
                RETURN_FALSE;
@@ -1583,14 +1789,17 @@ static PHP_FUNCTION(ares_select)
        FD_ZERO(&R);
        FD_ZERO(&W);
        
-       nfds = php_ares_extract_fds(read, write, &R, &W);
+       zend_hash_init(&resource_map, zend_hash_num_elements(Z_ARRVAL_P(read) + zend_hash_num_elements(Z_ARRVAL_P(write))), NULL, ZVAL_PTR_DTOR, 0);
+       nfds = php_ares_extract_fds(read, write, &R, &W, &resource_map TSRMLS_CC);
        if (-1 < select(nfds, &R, &W, NULL, &tv)) {
                zend_hash_clean(Z_ARRVAL_P(read));
                zend_hash_clean(Z_ARRVAL_P(write));
-               php_ares_publish_fds(&R, &W, read, write);
-               RETURN_TRUE;
+               php_ares_publish_fds(&R, &W, read, write, &resource_map);
+               RETVAL_TRUE;
+       } else {
+               RETVAL_FALSE;
        }
-       RETURN_FALSE;
+       zend_hash_destroy(&resource_map);
 }
 /* }}} */
 
@@ -1636,7 +1845,7 @@ static PHP_FUNCTION(ares_fds)
        array_init(read);
        array_init(write);
        ares_fds(ares->channel, &R, &W);
-       RETVAL_LONG(php_ares_publish_fds(&R, &W, read, write));
+       RETVAL_LONG(php_ares_publish_fds(&R, &W, read, write, NULL));
 }
 /* }}} */
 
@@ -1713,9 +1922,9 @@ static PHP_MINIT_FUNCTION(ares)
 #ifdef ARES_EADDRGETNETWORKPARAMS
        REGISTER_LONG_CONSTANT("ARES_EADDRGETNETWORKPARAMS", ARES_EADDRGETNETWORKPARAMS, CONST_PERSISTENT|CONST_CS);
 #endif
-
-/* More error codes */
-#define ARES_ECANCELLED         24          /* introduced in 1.7.0 */
+#ifdef ARES_ECANCELLED
+       REGISTER_LONG_CONSTANT("ARES_ECANCELLED", ARES_ECANCELLED, CONST_PERSISTENT|CONST_CS);
+#endif
 
        REGISTER_LONG_CONSTANT("ARES_FLAG_USEVC", ARES_FLAG_USEVC, CONST_PERSISTENT|CONST_CS);
        REGISTER_LONG_CONSTANT("ARES_FLAG_PRIMARY", ARES_FLAG_PRIMARY, CONST_PERSISTENT|CONST_CS);
@@ -1725,6 +1934,9 @@ static PHP_MINIT_FUNCTION(ares)
        REGISTER_LONG_CONSTANT("ARES_FLAG_NOSEARCH", ARES_FLAG_NOSEARCH, CONST_PERSISTENT|CONST_CS);
        REGISTER_LONG_CONSTANT("ARES_FLAG_NOALIASES", ARES_FLAG_NOALIASES, CONST_PERSISTENT|CONST_CS);
        REGISTER_LONG_CONSTANT("ARES_FLAG_NOCHECKRESP", ARES_FLAG_NOCHECKRESP, CONST_PERSISTENT|CONST_CS);
+#ifdef ARES_FLAG_EDNS
+       REGISTER_LONG_CONSTANT("ARES_FLAG_EDNS", ARES_FLAG_EDNS, CONST_PERSISTENT|CONST_CS);
+#endif
        
        /*
         * Address Family Constants
@@ -1990,10 +2202,12 @@ static PHP_MINIT_FUNCTION(ares)
 #ifdef T_OPT
        /* (41)  EDNS0 option (meta-RR)  */
         REGISTER_LONG_CONSTANT("ARES_T_OPT", T_OPT, CONST_CS|CONST_PERSISTENT);
+#endif
 #ifdef T_TSIG
        /* (250)  Transaction signature.  */
        REGISTER_LONG_CONSTANT("ARES_T_TSIG", T_TSIG, CONST_CS|CONST_PERSISTENT);
-#endif T_IXFR
+#endif
+#ifdef T_IXFR
        /* (251)  Incremental zone transfer.  */
        REGISTER_LONG_CONSTANT("ARES_T_IXFR", T_IXFR, CONST_CS|CONST_PERSISTENT);
 #endif
@@ -2139,6 +2353,15 @@ zend_function_entry ares_functions[] = {
        PHP_FE(ares_select, ai_ares_select)
        PHP_FE(ares_fds, ai_ares_fds)
        PHP_FE(ares_timeout, NULL)
+#ifdef HAVE_ARES_SET_LOCAL_DEV
+       PHP_FE(ares_set_local_dev, NULL)
+#endif
+#ifdef HAVE_ARES_SET_LOCAL_IP4
+       PHP_FE(ares_set_local_ip4, NULL)
+#endif
+#ifdef HAVE_ARES_SET_LOCAL_IP6
+       PHP_FE(ares_set_local_ip6, NULL)
+#endif
        {NULL, NULL, NULL}
 };
 /* }}} */