]> granicus.if.org Git - php/commitdiff
Fix #72733: Expose getaddrinfo C function, and supporting connect/bind
authorDavid Walker <dave@mudsite.com>
Sat, 6 Aug 2016 00:01:41 +0000 (18:01 -0600)
committerDavid Walker <dave@mudsite.com>
Fri, 12 Aug 2016 17:29:34 +0000 (11:29 -0600)
Feature request was to expose getaddrinfo().  I accomplish this
by having an array of resources that are the addrinfo structures.
The resources can be used in new functions to connect/bind, and
one function to examine the contents of the resource.

ext/sockets/php_sockets.h
ext/sockets/sockets.c
ext/sockets/tests/socket_addrinfo_bind.phpt [new file with mode: 0644]
ext/sockets/tests/socket_addrinfo_connect.phpt [new file with mode: 0644]
ext/sockets/tests/socket_addrinfo_explain.phpt [new file with mode: 0644]
ext/sockets/tests/socket_addrinfo_lookup.phpt [new file with mode: 0644]

index ce5cdaf313f4f951c511cbb4ba776f48dda444a6..37f3453c019f5d8094845d94ce25891f6e65f85a 100644 (file)
@@ -74,8 +74,10 @@ struct       sockaddr_un {
 PHP_SOCKETS_API int php_sockets_le_socket(void);
 PHP_SOCKETS_API php_socket *php_create_socket(void);
 PHP_SOCKETS_API void php_destroy_socket(zend_resource *rsrc);
+PHP_SOCKETS_API void php_destroy_sockaddr(zend_resource *rsrc);
 
 #define php_sockets_le_socket_name "Socket"
+#define php_sockets_le_addrinfo_name "AddressInfo"
 
 #define PHP_SOCKET_ERROR(socket, msg, errn) \
                do { \
index c23f984720c4f931164625c5a41ac5e0e718c770..c39a6172ae8c7310db2650d315743a74ae43764e 100644 (file)
@@ -97,6 +97,9 @@ ZEND_DECLARE_MODULE_GLOBALS(sockets)
 static int le_socket;
 #define le_socket_name php_sockets_le_socket_name
 
+static int le_addrinfo;
+#define le_addrinfo_name php_sockets_le_addrinfo_name
+
 /* {{{ arginfo */
 ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_select, 0, 0, 4)
        ZEND_ARG_INFO(1, read_fds)
@@ -271,6 +274,25 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_cmsg_space, 0, 0, 2)
        ZEND_ARG_INFO(0, level)
        ZEND_ARG_INFO(0, type)
 ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_addrinfo_lookup, 0, 0, 1)
+       ZEND_ARG_INFO(0, host)
+       ZEND_ARG_INFO(0, service)
+       ZEND_ARG_INFO(0, hints)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_addrinfo_connect, 0, 0, 1)
+       ZEND_ARG_INFO(0, addr)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_addrinfo_bind, 0, 0, 1)
+       ZEND_ARG_INFO(0, addr)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_addrinfo_explain, 0, 0, 1)
+       ZEND_ARG_INFO(0, addr)
+ZEND_END_ARG_INFO()
+
 /* }}} */
 
 static PHP_GINIT_FUNCTION(sockets);
@@ -310,6 +332,10 @@ PHP_FUNCTION(socket_last_error);
 PHP_FUNCTION(socket_clear_error);
 PHP_FUNCTION(socket_import_stream);
 PHP_FUNCTION(socket_export_stream);
+PHP_FUNCTION(socket_addrinfo_lookup);
+PHP_FUNCTION(socket_addrinfo_connect);
+PHP_FUNCTION(socket_addrinfo_bind);
+PHP_FUNCTION(socket_addrinfo_explain);
 
 /* {{{ sockets_functions[]
  */
@@ -348,6 +374,10 @@ const zend_function_entry sockets_functions[] = {
        PHP_FE(socket_sendmsg,                  arginfo_socket_sendmsg)
        PHP_FE(socket_recvmsg,                  arginfo_socket_recvmsg)
        PHP_FE(socket_cmsg_space,               arginfo_socket_cmsg_space)
+       PHP_FE(socket_addrinfo_lookup,  arginfo_socket_addrinfo_lookup)
+       PHP_FE(socket_addrinfo_connect, arginfo_socket_addrinfo_connect)
+       PHP_FE(socket_addrinfo_bind,    arginfo_socket_addrinfo_bind)
+       PHP_FE(socket_addrinfo_explain, arginfo_socket_addrinfo_explain)
 
        /* for downwards compatibility */
        PHP_FALIAS(socket_getopt, socket_get_option, arginfo_socket_get_option)
@@ -422,6 +452,17 @@ PHP_SOCKETS_API void php_destroy_socket(zend_resource *rsrc) /* {{{ */
 }
 /* }}} */
 
+PHP_SOCKETS_API void php_destroy_addrinfo(zend_resource *rsrc) /* {{{ */
+{
+       struct addrinfo *addr = rsrc->ptr;
+       efree(addr->ai_addr);
+       if (addr->ai_canonname != NULL) {
+               efree(addr->ai_canonname);
+       }
+       efree(addr);
+}
+/* }}} */
+
 static int php_open_listen_sock(php_socket **php_sock, int port, int backlog) /* {{{ */
 {
        struct sockaddr_in  la;
@@ -625,6 +666,7 @@ static PHP_MINIT_FUNCTION(sockets)
        ZEND_TSRMLS_CACHE_UPDATE();
 #endif
        le_socket = zend_register_list_destructors_ex(php_destroy_socket, NULL, le_socket_name, module_number);
+       le_addrinfo = zend_register_list_destructors_ex(php_destroy_addrinfo, NULL, le_addrinfo_name, module_number);
 
        REGISTER_LONG_CONSTANT("AF_UNIX",               AF_UNIX,                CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("AF_INET",               AF_INET,                CONST_CS | CONST_PERSISTENT);
@@ -744,6 +786,20 @@ static PHP_MINIT_FUNCTION(sockets)
        REGISTER_LONG_CONSTANT("IPV6_UNICAST_HOPS",                     IPV6_UNICAST_HOPS,      CONST_CS | CONST_PERSISTENT);
 #endif
 
+       REGISTER_LONG_CONSTANT("AI_PASSIVE",            AI_PASSIVE,                     CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("AI_CANONNAME",          AI_CANONNAME,           CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("AI_NUMERICHOST",        AI_NUMERICHOST,         CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("AI_V4MAPPED",           AI_V4MAPPED,            CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("AI_ALL",                        AI_ALL,                         CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("AI_ADDRCONFIG",         AI_ADDRCONFIG,          CONST_CS | CONST_PERSISTENT);
+#ifdef __USE_GNU
+       REGISTER_LONG_CONSTANT("AI_IDN",                        AI_IDN,                         CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("AI_CANONIDN",           AI_CANONIDN,            CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("AI_IDN_ALLOW_UNASSIGNED",               AI_IDN_ALLOW_UNASSIGNED, CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("AI_IDN_USE_STD3_ASCII_RULES",   AI_IDN_USE_STD3_ASCII_RULES, CONST_CS | CONST_PERSISTENT);
+#endif
+       REGISTER_LONG_CONSTANT("AI_NUMERICSERV",        AI_NUMERICSERV,         CONST_CS | CONST_PERSISTENT);
+
        php_socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU);
 
        return SUCCESS;
@@ -2456,6 +2512,246 @@ PHP_FUNCTION(socket_export_stream)
 }
 /* }}} */
 
+/* {{{ proto resource addrinfo socket_addrinfo_lookup(string hostname[, mixed service, array hints])
+   Gets array with contents of getaddrinfo about the given hostname. */
+PHP_FUNCTION(socket_addrinfo_lookup)
+{
+       zend_string *hostname, *key;
+       zval *hint, *service, *zhints = NULL;
+
+       struct addrinfo hints, *result, *rp, *res;
+
+       memset(&hints, 0, sizeof(hints));
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S|za", &hostname, &service, &zhints) == FAILURE) {
+               RETURN_NULL();
+       }
+
+       if (zhints) {
+               ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zhints), key, hint) {
+                       if (key) {
+                               if (strcmp(ZSTR_VAL(key), "ai_flags") == 0) {
+                                       hints.ai_flags = Z_LVAL_P(hint);
+                               } else if (strcmp(ZSTR_VAL(key), "ai_socktype") == 0) {
+                                       hints.ai_socktype= Z_LVAL_P(hint);
+                               } else if (strcmp(ZSTR_VAL(key), "ai_protocol") == 0) {
+                                       hints.ai_protocol = Z_LVAL_P(hint);
+                               } else if (strcmp(ZSTR_VAL(key), "ai_family") == 0) {
+                                       hints.ai_family = Z_LVAL_P(hint);
+                               }
+                       }
+               } ZEND_HASH_FOREACH_END();
+       }
+
+       convert_to_string(service);
+
+       if (getaddrinfo(ZSTR_VAL(hostname), Z_STRVAL_P(service), &hints, &result) != 0) {
+               RETURN_FALSE;
+       }
+
+       array_init(return_value);
+
+       for (rp = result; rp != NULL; rp = rp->ai_next) {
+               if (rp->ai_family != AF_UNSPEC) {
+                       res = emalloc(sizeof(struct addrinfo));
+                       memcpy(res, rp, sizeof(struct addrinfo));
+
+                       res->ai_addr = emalloc(sizeof(struct sockaddr));
+                       memcpy(res->ai_addr, rp->ai_addr, sizeof(struct sockaddr));
+
+                       if (rp->ai_canonname != NULL) {
+                               res->ai_canonname = estrdup(rp->ai_canonname);
+                       }
+
+                       add_next_index_resource(return_value, zend_register_resource(res, le_addrinfo));
+               }
+       }
+
+       freeaddrinfo(result);
+}
+/* }}} */
+
+/* {{{ proto resource socket_addrinfo_bind(resource addrinfo)
+   Creates and binds to a socket from a given addrinfo resource */
+PHP_FUNCTION(socket_addrinfo_bind)
+{
+       zval                    *arg1;
+       int                             retval;
+       struct addrinfo *ai;
+       php_socket              *php_sock = php_create_socket();
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &arg1) == FAILURE) {
+               efree(php_sock);
+               return;
+       }
+
+       if ((ai = (struct addrinfo *) zend_fetch_resource(Z_RES_P(arg1), le_addrinfo_name, le_addrinfo)) == NULL) {
+               efree(php_sock);
+               RETURN_FALSE;
+       }
+
+       php_sock->bsd_socket = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+       php_sock->type = ai->ai_family;
+
+       if (IS_INVALID_SOCKET(php_sock)) {
+               SOCKETS_G(last_error) = errno;
+               php_error_docref(NULL, E_WARNING, "Unable to create socket [%d]: %s", errno, sockets_strerror(errno));
+               efree(php_sock);
+               RETURN_FALSE;
+       }
+
+       php_sock->error = 0;
+       php_sock->blocking = 1;
+
+       switch(php_sock->type) {
+               case AF_UNIX:
+                       {
+                               // AF_UNIX sockets via getaddrino are not implemented due to security problems
+                               break;
+                       }
+
+               case AF_INET:
+#if HAVE_IPV6
+               case AF_INET6:
+#endif
+                       {
+                               retval = bind(php_sock->bsd_socket, ai->ai_addr, ai->ai_addrlen);
+                               break;
+                       }
+               default:
+                       php_error_docref(NULL, E_WARNING, "unsupported socket type '%d', must be AF_UNIX, AF_INET, or AF_INET6", php_sock->type);
+                       efree(php_sock);
+                       RETURN_FALSE;
+       }
+
+       if (retval != 0) {
+               PHP_SOCKET_ERROR(php_sock, "unable to bind address", errno);
+               efree(php_sock);
+               RETURN_FALSE;
+       }
+
+       RETURN_RES(zend_register_resource(php_sock, le_socket));
+}
+/* }}} */
+
+/* {{{ proto resource socket_addrinfo_connect(resource addrinfo)
+   Creates and connects to a socket from a given addrinfo resource */
+PHP_FUNCTION(socket_addrinfo_connect)
+{
+       zval                    *arg1;
+       int                             retval;
+       struct addrinfo *ai;
+       php_socket              *php_sock = php_create_socket();
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &arg1) == FAILURE) {
+               efree(php_sock);
+               return;
+       }
+
+       if ((ai = (struct addrinfo *) zend_fetch_resource(Z_RES_P(arg1), le_addrinfo_name, le_addrinfo)) == NULL) {
+               efree(php_sock);
+               RETURN_FALSE;
+       }
+
+       php_sock->bsd_socket = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+       php_sock->type = ai->ai_family;
+
+       if (IS_INVALID_SOCKET(php_sock)) {
+               SOCKETS_G(last_error) = errno;
+               php_error_docref(NULL, E_WARNING, "Unable to create socket [%d]: %s", errno, sockets_strerror(errno));
+               efree(php_sock);
+               RETURN_FALSE;
+       }
+
+       php_sock->error = 0;
+       php_sock->blocking = 1;
+
+       switch(php_sock->type) {
+               case AF_UNIX:
+                       {
+                               // AF_UNIX sockets via getaddrino are not implemented due to security problems
+                               break;
+                       }
+
+               case AF_INET:
+#if HAVE_IPV6
+               case AF_INET6:
+#endif
+                       {
+                               retval = connect(php_sock->bsd_socket, ai->ai_addr, ai->ai_addrlen);
+                               break;
+                       }
+               default:
+                       php_error_docref(NULL, E_WARNING, "unsupported socket type '%d', must be AF_UNIX, AF_INET, or AF_INET6", php_sock->type);
+                       efree(php_sock);
+                       RETURN_FALSE;
+       }
+
+       if (retval != 0) {
+               PHP_SOCKET_ERROR(php_sock, "unable to connect address", errno);
+               efree(php_sock);
+               RETURN_FALSE;
+       }
+
+       RETURN_RES(zend_register_resource(php_sock, le_socket));
+}
+/* }}} */
+
+/* {{{ proto resource socket_addrinfo_explain(resource addrinfo)
+   Creates and connects to a socket from a given addrinfo resource */
+PHP_FUNCTION(socket_addrinfo_explain)
+{
+       zval                    *arg1, sockaddr;
+       struct addrinfo *ai;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &arg1) == FAILURE) {
+               return;
+       }
+
+       if ((ai = (struct addrinfo *) zend_fetch_resource(Z_RES_P(arg1), le_addrinfo_name, le_addrinfo)) == NULL) {
+               RETURN_FALSE;
+       }
+
+       array_init(return_value);
+
+       add_assoc_long(return_value, "ai_flags", ai->ai_flags);
+       add_assoc_long(return_value, "ai_family", ai->ai_family);
+       add_assoc_long(return_value, "ai_socktype", ai->ai_socktype);
+       add_assoc_long(return_value, "ai_protocol", ai->ai_protocol);
+       if (ai->ai_canonname != NULL) {
+               add_assoc_string(return_value, "ai_canonname", ai->ai_canonname);
+       }
+
+       array_init(&sockaddr);
+       switch(ai->ai_addr->sa_family) {
+               case AF_INET:
+                       {
+                               struct sockaddr_in *sa = (struct sockaddr_in *) ai->ai_addr;
+                               char addr[INET_ADDRSTRLEN];
+
+                               add_assoc_long(&sockaddr, "sin_port", ntohs((unsigned short) sa->sin_port));
+                               inet_ntop(ai->ai_family, &sa->sin_addr, addr, sizeof(addr));
+                               add_assoc_string(&sockaddr, "sin_addr", addr);
+                               break;
+                       }
+#if HAVE_IPV6
+               case AF_INET6:
+                       {
+                               struct sockaddr_in6 *sa = (struct sockaddr_in6 *) ai->ai_addr;
+                               char addr[INET6_ADDRSTRLEN];
+
+                               add_assoc_long(&sockaddr, "sin6_port", ntohs((unsigned short) sa->sin6_port));
+                               inet_ntop(ai->ai_family, &sa->sin6_addr, addr, sizeof(addr));
+                               add_assoc_string(&sockaddr, "sin6_addr", addr);
+                               break;
+                       }
+#endif
+       }
+
+       add_assoc_zval(return_value, "ai_addr", &sockaddr);
+}
+/* }}} */
+
 /*
  * Local variables:
  * tab-width: 4
diff --git a/ext/sockets/tests/socket_addrinfo_bind.phpt b/ext/sockets/tests/socket_addrinfo_bind.phpt
new file mode 100644 (file)
index 0000000..eb15afe
--- /dev/null
@@ -0,0 +1,18 @@
+--TEST--
+Test socket_addrinfo_bind()
+--SKIPIF--
+<?php
+if (!extension_loaded('sockets')) {
+    die('SKIP The sockets extension is not loaded.');
+}
+--FILE--
+<?php
+$addrinfo = socket_addrinfo_lookup('127.0.0.1', 2000, array(
+    'ai_family' => AF_INET,
+    'ai_socktype' => SOCK_DGRAM,
+));
+var_dump(socket_addrinfo_bind($addrinfo[0]));
+echo "Done";
+--EXPECTF--
+resource(%d) of type (Socket)
+Done
diff --git a/ext/sockets/tests/socket_addrinfo_connect.phpt b/ext/sockets/tests/socket_addrinfo_connect.phpt
new file mode 100644 (file)
index 0000000..009d548
--- /dev/null
@@ -0,0 +1,18 @@
+--TEST--
+Test socket_addrinfo_connect()
+--SKIPIF--
+<?php
+if (!extension_loaded('sockets')) {
+    die('SKIP The sockets extension is not loaded.');
+}
+--FILE--
+<?php
+$addrinfo = socket_addrinfo_lookup('127.0.0.1', 2000, array(
+    'ai_family' => AF_INET,
+    'ai_socktype' => SOCK_DGRAM,
+));
+var_dump(socket_addrinfo_connect($addrinfo[0]));
+echo "Done";
+--EXPECTF--
+resource(%d) of type (Socket)
+Done
diff --git a/ext/sockets/tests/socket_addrinfo_explain.phpt b/ext/sockets/tests/socket_addrinfo_explain.phpt
new file mode 100644 (file)
index 0000000..aa89103
--- /dev/null
@@ -0,0 +1,34 @@
+--TEST--
+Test socket_addrinfo_explain()
+--SKIPIF--
+<?php
+if (!extension_loaded('sockets')) {
+    die('SKIP The sockets extension is not loaded.');
+}
+--FILE--
+<?php
+$addrinfo = socket_addrinfo_lookup('127.0.0.1', 2000, array(
+    'ai_family' => AF_INET,
+    'ai_socktype' => SOCK_DGRAM,
+));
+var_dump(socket_addrinfo_explain($addrinfo[0]));
+echo "Done";
+--EXPECT--
+array(5) {
+  ["ai_flags"]=>
+  int(0)
+  ["ai_family"]=>
+  int(2)
+  ["ai_socktype"]=>
+  int(2)
+  ["ai_protocol"]=>
+  int(17)
+  ["ai_addr"]=>
+  array(2) {
+    ["sin_port"]=>
+    int(2000)
+    ["sin_addr"]=>
+    string(9) "127.0.0.1"
+  }
+}
+Done
diff --git a/ext/sockets/tests/socket_addrinfo_lookup.phpt b/ext/sockets/tests/socket_addrinfo_lookup.phpt
new file mode 100644 (file)
index 0000000..be35792
--- /dev/null
@@ -0,0 +1,18 @@
+--TEST--
+Test socket_addrinfo_lookup()
+--SKIPIF--
+<?php
+if (!extension_loaded('sockets')) {
+    die('SKIP The sockets extension is not loaded.');
+}
+--FILE--
+<?php
+$addrinfo = socket_addrinfo_lookup('127.0.0.1', 2000, array(
+    'ai_family' => AF_INET,
+    'ai_socktype' => SOCK_DGRAM,
+));
+var_dump($addrinfo[0]);
+echo "Done";
+--EXPECTF--
+resource(%d) of type (AddressInfo)
+Done