diff options
author | David Walker <dave@mudsite.com> | 2016-08-05 18:01:41 -0600 |
---|---|---|
committer | David Walker <dave@mudsite.com> | 2016-08-12 11:29:34 -0600 |
commit | 7373ce914914e9a434216554a7e29aa4d1775d97 (patch) | |
tree | ab8eba72d9f769688ae36c01971624c4a51e74f5 | |
parent | 02544170821b91c77e0786c3b1ff8f58f916a48b (diff) | |
download | php-git-7373ce914914e9a434216554a7e29aa4d1775d97.tar.gz |
Fix #72733: Expose getaddrinfo C function, and supporting connect/bind
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.
-rw-r--r-- | ext/sockets/php_sockets.h | 2 | ||||
-rw-r--r-- | ext/sockets/sockets.c | 296 | ||||
-rw-r--r-- | ext/sockets/tests/socket_addrinfo_bind.phpt | 18 | ||||
-rw-r--r-- | ext/sockets/tests/socket_addrinfo_connect.phpt | 18 | ||||
-rw-r--r-- | ext/sockets/tests/socket_addrinfo_explain.phpt | 34 | ||||
-rw-r--r-- | ext/sockets/tests/socket_addrinfo_lookup.phpt | 18 |
6 files changed, 386 insertions, 0 deletions
diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index ce5cdaf313..37f3453c01 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -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 { \ diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index c23f984720..c39a6172ae 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -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 index 0000000000..eb15afee25 --- /dev/null +++ b/ext/sockets/tests/socket_addrinfo_bind.phpt @@ -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 index 0000000000..009d54839b --- /dev/null +++ b/ext/sockets/tests/socket_addrinfo_connect.phpt @@ -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 index 0000000000..aa891039dd --- /dev/null +++ b/ext/sockets/tests/socket_addrinfo_explain.phpt @@ -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 index 0000000000..be35792f45 --- /dev/null +++ b/ext/sockets/tests/socket_addrinfo_lookup.phpt @@ -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 |