From a100c25ea4b5864adc5203b6b979e8912062540a Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 1 Feb 2013 11:12:11 +0100 Subject: [PATCH 01/40] Fix overbroad skipif include --- ext/sockets/tests/ipv6_skipif.inc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ext/sockets/tests/ipv6_skipif.inc b/ext/sockets/tests/ipv6_skipif.inc index ad8cf77b357..1f824630cb8 100644 --- a/ext/sockets/tests/ipv6_skipif.inc +++ b/ext/sockets/tests/ipv6_skipif.inc @@ -2,7 +2,5 @@ if (!defined("AF_INET6")) { die('skip no IPv6 support'); } -/* If IPv6 is supported on the platform this will error out with code 111 - Connection refused. - If IPv6 is NOT supported, $errno will be set to something else (indicating parse/getaddrinfo error) */ -@stream_socket_client('tcp://[::1]:0', $errno); -if ($errno != 111) die('skip no IPv6 support'); +if (@stream_socket_client('udp://[::1]:8888') === false) + die('skip no IPv6 support'); From 97d656fc82104d9879b59dba9b80773346ec1f61 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 1 Feb 2013 14:35:14 +0100 Subject: [PATCH 02/40] Move some declarations to sockets.c --- ext/sockets/php_sockets.h | 35 ----------------------------------- ext/sockets/sockets.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index df205e5114d..2b6700d8838 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -37,41 +37,6 @@ extern zend_module_entry sockets_module_entry; #endif #endif -PHP_MINIT_FUNCTION(sockets); -PHP_MINFO_FUNCTION(sockets); -PHP_RSHUTDOWN_FUNCTION(sockets); - -PHP_FUNCTION(socket_select); -PHP_FUNCTION(socket_create_listen); -#ifdef HAVE_SOCKETPAIR -PHP_FUNCTION(socket_create_pair); -#endif -PHP_FUNCTION(socket_accept); -PHP_FUNCTION(socket_set_nonblock); -PHP_FUNCTION(socket_set_block); -PHP_FUNCTION(socket_listen); -PHP_FUNCTION(socket_close); -PHP_FUNCTION(socket_write); -PHP_FUNCTION(socket_read); -PHP_FUNCTION(socket_getsockname); -PHP_FUNCTION(socket_getpeername); -PHP_FUNCTION(socket_create); -PHP_FUNCTION(socket_connect); -PHP_FUNCTION(socket_strerror); -PHP_FUNCTION(socket_bind); -PHP_FUNCTION(socket_recv); -PHP_FUNCTION(socket_send); -PHP_FUNCTION(socket_recvfrom); -PHP_FUNCTION(socket_sendto); -PHP_FUNCTION(socket_get_option); -PHP_FUNCTION(socket_set_option); -#ifdef HAVE_SHUTDOWN -PHP_FUNCTION(socket_shutdown); -#endif -PHP_FUNCTION(socket_last_error); -PHP_FUNCTION(socket_clear_error); -PHP_FUNCTION(socket_import_stream); - #ifndef PHP_WIN32 typedef int PHP_SOCKET; # define PHP_SOCKETS_API PHPAPI diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 43bae9f6613..1cb36cd7eb3 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -279,6 +279,41 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_import_stream, 0, 0, 1) ZEND_END_ARG_INFO() /* }}} */ +PHP_MINIT_FUNCTION(sockets); +PHP_MINFO_FUNCTION(sockets); +PHP_RSHUTDOWN_FUNCTION(sockets); + +PHP_FUNCTION(socket_select); +PHP_FUNCTION(socket_create_listen); +#ifdef HAVE_SOCKETPAIR +PHP_FUNCTION(socket_create_pair); +#endif +PHP_FUNCTION(socket_accept); +PHP_FUNCTION(socket_set_nonblock); +PHP_FUNCTION(socket_set_block); +PHP_FUNCTION(socket_listen); +PHP_FUNCTION(socket_close); +PHP_FUNCTION(socket_write); +PHP_FUNCTION(socket_read); +PHP_FUNCTION(socket_getsockname); +PHP_FUNCTION(socket_getpeername); +PHP_FUNCTION(socket_create); +PHP_FUNCTION(socket_connect); +PHP_FUNCTION(socket_strerror); +PHP_FUNCTION(socket_bind); +PHP_FUNCTION(socket_recv); +PHP_FUNCTION(socket_send); +PHP_FUNCTION(socket_recvfrom); +PHP_FUNCTION(socket_sendto); +PHP_FUNCTION(socket_get_option); +PHP_FUNCTION(socket_set_option); +#ifdef HAVE_SHUTDOWN +PHP_FUNCTION(socket_shutdown); +#endif +PHP_FUNCTION(socket_last_error); +PHP_FUNCTION(socket_clear_error); +PHP_FUNCTION(socket_import_stream); + /* {{{ sockets_functions[] */ const zend_function_entry sockets_functions[] = { From 9283b8aea4c681e39fed772543919bea4bba44a1 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 1 Feb 2013 14:39:56 +0100 Subject: [PATCH 03/40] Move & improve PHP_SOCKET_ERROR def --- ext/sockets/php_sockets.h | 14 +++++++------- ext/sockets/sockets.c | 4 ---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 2b6700d8838..3762e026aa5 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -64,13 +64,13 @@ PHP_SOCKETS_API int php_sockets_le_socket(void); #define php_sockets_le_socket_name "Socket" -/* Prototypes */ -#ifdef ilia_0 /* not needed, only causes a compiler warning */ -static int php_open_listen_sock(php_socket **php_sock, int port, int backlog TSRMLS_DC); -static int php_accept_connect(php_socket *in_sock, php_socket **new_sock, struct sockaddr *la TSRMLS_DC); -static int php_read(php_socket *sock, void *buf, size_t maxlen, int flags); -static char *php_strerror(int error TSRMLS_DC); -#endif +#define PHP_SOCKET_ERROR(socket, msg, errn) \ + do { \ + int _err = (errn); /* save value to avoid repeated calls to WSAGetLastError() on Windows */ \ + (socket)->error = _err; \ + SOCKETS_G(last_error) = _err; \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ + } while (0) ZEND_BEGIN_MODULE_GLOBALS(sockets) int last_error; diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 1cb36cd7eb3..6069fc5ec72 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -117,10 +117,6 @@ static char *php_strerror(int error TSRMLS_DC); #define PHP_NORMAL_READ 0x0001 #define PHP_BINARY_READ 0x0002 -#define PHP_SOCKET_ERROR(socket,msg,errn) socket->error = errn; \ - SOCKETS_G(last_error) = errn; \ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, errn, php_strerror(errn TSRMLS_CC)) - static int le_socket; #define le_socket_name php_sockets_le_socket_name From 24e380f97033b54ba0994fcf9f7c6c76111a3c93 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sat, 2 Feb 2013 12:44:00 +0100 Subject: [PATCH 04/40] Remove a Windows only warning --- main/network.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/main/network.c b/main/network.c index 4b7a8d410ae..ba2ee1c4983 100644 --- a/main/network.c +++ b/main/network.c @@ -1076,11 +1076,6 @@ PHPAPI int php_set_sock_blocking(int socketd, int block TSRMLS_DC) /* with ioctlsocket, a non-zero sets nonblocking, a zero sets blocking */ flags = !block; if (ioctlsocket(socketd, FIONBIO, &flags) == SOCKET_ERROR) { - char *error_string; - - error_string = php_socket_strerror(WSAGetLastError(), NULL, 0); - php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", error_string); - efree(error_string); ret = FAILURE; } #else From 40663ede837c401212e950a3f65a7cd2885ccead Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 1 Feb 2013 14:58:35 +0100 Subject: [PATCH 05/40] Fix tests (Windows) --- .../tests/socket_import_stream-4-win.phpt | 3 - ...socket_sentto_recvfrom_ipv6_udp-win32.phpt | 62 +++++++++++++++++++ .../socket_sentto_recvfrom_ipv6_udp.phpt | 3 + 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp-win32.phpt diff --git a/ext/sockets/tests/socket_import_stream-4-win.phpt b/ext/sockets/tests/socket_import_stream-4-win.phpt index e2fc523ce58..b36764f6177 100644 --- a/ext/sockets/tests/socket_import_stream-4-win.phpt +++ b/ext/sockets/tests/socket_import_stream-4-win.phpt @@ -80,9 +80,6 @@ stream_set_blocking Warning: stream_set_blocking(): %d is not a valid stream resource in %s on line %d socket_set_block -Warning: socket_set_block(): An operation was attempted on something that is not a socket. - in %ssocket_import_stream-4-win.php on line %d - Warning: socket_set_block(): unable to set blocking mode [%d]: An operation was attempted on something that is not a socket. in %ssocket_import_stream-4-win.php on line %d diff --git a/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp-win32.phpt b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp-win32.phpt new file mode 100644 index 00000000000..ec965094bcc --- /dev/null +++ b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp-win32.phpt @@ -0,0 +1,62 @@ +--TEST-- +Test if socket_recvfrom() receives data sent by socket_sendto() via IPv6 UDP (Win32) +--SKIPIF-- + +PHP Testfest Berlin 2009-05-09 diff --git a/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt index 04f62eddd37..2beb8080cdb 100644 --- a/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt +++ b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt @@ -5,6 +5,9 @@ Test if socket_recvfrom() receives data sent by socket_sendto() via IPv6 UDP if (!extension_loaded('sockets')) { die('SKIP The sockets extension is not loaded.'); } +if (substr(PHP_OS, 0, 3) == 'WIN') { + die('skip Not valid for Windows'); +} require 'ipv6_skipif.inc'; --FILE-- Date: Fri, 1 Feb 2013 16:38:54 +0100 Subject: [PATCH 06/40] Fix wrong blocking state being set --- ext/sockets/sockets.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 6069fc5ec72..c6c5477967c 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1126,7 +1126,7 @@ PHP_FUNCTION(socket_set_nonblock) if (stream != NULL) { if (php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, 0, NULL) != -1) { - php_sock->blocking = 1; + php_sock->blocking = 0; RETURN_TRUE; } } From ac47448abb477be99963f0b38fe82ffe78c21a8b Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sat, 2 Feb 2013 15:43:05 +0100 Subject: [PATCH 07/40] Ignore warnings on EAGAIN/EWOULDBLOCK/EINPROGRESS See bug #63570 --- ext/sockets/php_sockets.h | 4 +++- ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt | 4 ++-- ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt | 4 ++-- ext/sockets/tests/socket_sentto_recvfrom_unix.phpt | 5 ++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 3762e026aa5..9c5dc5a4aa5 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -69,7 +69,9 @@ PHP_SOCKETS_API int php_sockets_le_socket(void); int _err = (errn); /* save value to avoid repeated calls to WSAGetLastError() on Windows */ \ (socket)->error = _err; \ SOCKETS_G(last_error) = _err; \ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ + if (_err != EAGAIN && _err != EWOULDBLOCK && _err != EINPROGRESS) { \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ + } \ } while (0) ZEND_BEGIN_MODULE_GLOBALS(sockets) diff --git a/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt b/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt index bf95044d48a..00d69a855fb 100644 --- a/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt +++ b/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt @@ -14,7 +14,7 @@ if (!extension_loaded('sockets')) { if (!socket_set_nonblock($socket)) { die('Unable to set nonblocking mode for socket'); } - socket_recvfrom($socket, $buf, 12, 0, $from, $port); // cause warning + var_dump(socket_recvfrom($socket, $buf, 12, 0, $from, $port)); //false (EAGAIN - no warning) $address = '127.0.0.1'; socket_sendto($socket, '', 1, 0, $address); // cause warning if (!socket_bind($socket, $address, 1223)) { @@ -44,7 +44,7 @@ if (!extension_loaded('sockets')) { socket_close($socket); --EXPECTF-- -Warning: socket_recvfrom(): unable to recvfrom [%d]: %a in %s on line %d +bool(false) Warning: Wrong parameter count for socket_sendto() in %s on line %d diff --git a/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt index 2beb8080cdb..bd079042777 100644 --- a/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt +++ b/ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt @@ -18,7 +18,7 @@ require 'ipv6_skipif.inc'; if (!socket_set_nonblock($socket)) { die('Unable to set nonblocking mode for socket'); } - socket_recvfrom($socket, $buf, 12, 0, $from, $port); // cause warning + var_dump(socket_recvfrom($socket, $buf, 12, 0, $from, $port)); // false (EAGAIN, no warning) $address = '::1'; socket_sendto($socket, '', 1, 0, $address); // cause warning if (!socket_bind($socket, $address, 1223)) { @@ -48,7 +48,7 @@ require 'ipv6_skipif.inc'; socket_close($socket); --EXPECTF-- -Warning: socket_recvfrom(): unable to recvfrom [11]: Resource temporarily unavailable in %s on line %d +bool(false) Warning: Wrong parameter count for socket_sendto() in %s on line %d diff --git a/ext/sockets/tests/socket_sentto_recvfrom_unix.phpt b/ext/sockets/tests/socket_sentto_recvfrom_unix.phpt index 55ad75c65e4..e25bf4df1a5 100644 --- a/ext/sockets/tests/socket_sentto_recvfrom_unix.phpt +++ b/ext/sockets/tests/socket_sentto_recvfrom_unix.phpt @@ -18,7 +18,7 @@ if (!extension_loaded('sockets')) { if (!socket_set_nonblock($socket)) { die('Unable to set nonblocking mode for socket'); } - socket_recvfrom($socket, $buf, 12, 0, $from, $port); // cause warning + var_dump(socket_recvfrom($socket, $buf, 12, 0, $from, $port)); //false (EAGAIN, no warning) $address = sprintf("/tmp/%s.sock", uniqid()); if (!socket_bind($socket, $address)) { die("Unable to bind to $address"); @@ -53,8 +53,7 @@ if (!extension_loaded('sockets')) { ?> --EXPECTF-- Warning: socket_create(): Unable to create socket [%d]: Protocol not supported in %s on line %d - -Warning: socket_recvfrom(): unable to recvfrom [%d]: Resource temporarily unavailable in %s on line %d +bool(false) Warning: socket_sendto() expects at least 5 parameters, 4 given in %s on line %d bool(false) From 5e51c851431189677aa80f7a3a863699488678cd Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 1 Nov 2012 20:38:42 +0100 Subject: [PATCH 08/40] Wrap recvmsg() and sendmsg() This introduces two new functions: int socket_recvmsg(resource $socket, array &$msghdr, int $flags) int socket_sendmsg(resource $socket, array $msghdr, int $flags) The arrays representing struct msghdr follow the native counterpart closely: structs are mapped to arrays, fields to array elements whose key is the name of the field without the prefix (e.g. "name" instead of "msg_name") and array are mapped to sequential numeric PHP arrays. Right now the only type of ancillary data supported is fot the level/type pair IPPROTO_IPV6/IPV6_PKTINFO. I also refactored out the name resolution functions and made sockets_strerror() a global function. --- ext/sockets/config.m4 | 2 +- ext/sockets/php_sockets.h | 10 + ext/sockets/sendrecvmsg.c | 1377 +++++++++++++++++++++++++++++++++++ ext/sockets/sendrecvmsg.h | 8 + ext/sockets/sockaddr_conv.c | 119 +++ ext/sockets/sockaddr_conv.h | 24 + ext/sockets/sockets.c | 204 ++---- 7 files changed, 1599 insertions(+), 145 deletions(-) create mode 100644 ext/sockets/sendrecvmsg.c create mode 100644 ext/sockets/sendrecvmsg.h create mode 100644 ext/sockets/sockaddr_conv.c create mode 100644 ext/sockets/sockaddr_conv.h diff --git a/ext/sockets/config.m4 b/ext/sockets/config.m4 index 4032621ce6b..3b5b90714b7 100644 --- a/ext/sockets/config.m4 +++ b/ext/sockets/config.m4 @@ -43,6 +43,6 @@ if test "$PHP_SOCKETS" != "no"; then AC_DEFINE(HAVE_SA_SS_FAMILY,1,[Whether you have sockaddr_storage.ss_family]) fi - PHP_NEW_EXTENSION([sockets], [sockets.c multicast.c], [$ext_shared]) + PHP_NEW_EXTENSION([sockets], [sockets.c multicast.c sockaddr_conv.c sendrecvmsg.c], [$ext_shared]) PHP_INSTALL_HEADERS([ext/sockets/], [php_sockets.h]) fi diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 9c5dc5a4aa5..9158ca49072 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -85,6 +85,16 @@ ZEND_END_MODULE_GLOBALS(sockets) #define SOCKETS_G(v) (sockets_globals.v) #endif +ZEND_EXTERN_MODULE_GLOBALS(sockets); + +char *sockets_strerror(int error TSRMLS_DC); + +#define PHP_SOCKET_ERROR(socket,msg,errn) \ + socket->error = errn; \ + SOCKETS_G(last_error) = errn; \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, errn, \ + sockets_strerror(errn TSRMLS_CC)) + #else #define phpext_sockets_ptr NULL #endif diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c new file mode 100644 index 00000000000..379af125d70 --- /dev/null +++ b/ext/sockets/sendrecvmsg.c @@ -0,0 +1,1377 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2012 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Gustavo Lopes | + +----------------------------------------------------------------------+ + */ + +#include +#include "php_sockets.h" +#include "sockaddr_conv.h" +#include "sendrecvmsg.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef ZTS +#include +#endif + +#define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024)) +#define DEFAULT_BUFF_SIZE 8192 +#define MAX_ARRAY_KEY_SIZE 128 + +#define LONG_CHECK_VALID_INT(l) \ + do { \ + if ((l) < INT_MIN && (l) > INT_MAX) { \ + php_error_docref0(NULL TSRMLS_CC, E_WARNING, "The value %ld does not fit inside " \ + "the boundaries of a native integer", (l)); \ + return; \ + } \ + } while (0) + +struct err_s { + int has_error; + char *msg; + int level; + int should_free; +}; + +typedef struct { + HashTable params; /* stores pointers; has to be first */ + struct err_s err; + zend_llist keys, + /* common part to res_context ends here */ + allocations; + php_socket *sock; +} ser_context; + +typedef struct { + HashTable params; /* stores pointers; has to be first */ + struct err_s err; + zend_llist keys; +} res_context; + +struct key_value { + const char *key; + unsigned key_size; + void *value; +}; +#define KEY_FILL_SOCKADDR "fill_sockaddr" +#define KEY_RECVMSG_RET "recvmsg_ret" + + +typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); +typedef void (to_zval_read_field)(const char *data, zval *zv, res_context *ctx); + +typedef struct { + /* zval info */ + const char *name; + unsigned name_size; + int required; + + /* structure info */ + size_t field_offset; /* 0 to pass full structure, e.g. when more than + one field is to be changed; in that case the + callbacks need to know the name of the fields */ + + /* callbacks */ + from_zval_write_field *from_zval; + to_zval_read_field *to_zval; +} field_descriptor; + +typedef struct { + int cmsg_level; /* originating protocol */ + int msg_type; /* protocol-specific type */ +} anc_reg_key; + +static struct { + int initialized; + HashTable ht; +} ancillary_registry; + +typedef socklen_t (*ancillary_size)(void); + +typedef struct { + socklen_t size; /* size of native structure */ + from_zval_write_field *from_array; + to_zval_read_field *to_array; +} ancillary_reg_entry; + +/* PARAMETERS */ +static int param_get_bool(void *ctx, const char *key, int def) +{ + int **elem; + if (zend_hash_find(ctx, key, strlen(key) + 1, (void**)&elem) == SUCCESS) { + return **elem; + } else { + return def; + } +} + +/* FORWARD DECLARATIONS */ +static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type); + +static inline void *accounted_emalloc(size_t alloc_size, ser_context *ctx) +{ + void *ret = emalloc(alloc_size); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} + +static inline void *accounted_ecalloc(size_t nmemb, size_t alloc_size, ser_context *ctx) +{ + void *ret = ecalloc(nmemb, alloc_size); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} +static inline void *accounted_safe_ecalloc(size_t nmemb, size_t alloc_size, size_t offset, ser_context *ctx) +{ + void *ret = safe_emalloc(nmemb, alloc_size, offset); + memset(ret, '\0', nmemb * alloc_size + offset); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} + +static void do_from_to_zval_err(struct err_s *err, + zend_llist *keys, + const char *what_conv, + const char *fmt, + va_list ap) +{ + smart_str path = {0}; + const char **node; + char *user_msg; + int user_msg_size; + zend_llist_position pos; + + if (err->has_error) { + return; + } + + for (node = zend_llist_get_first_ex(keys, &pos); + node != NULL; + node = zend_llist_get_next_ex(keys, &pos)) { + smart_str_appends(&path, *node); + smart_str_appends(&path, " > "); + } + + if (path.len > 3) { + path.len -= 3; + } + smart_str_0(&path); + + user_msg_size = vspprintf(&user_msg, 0, fmt, ap); + + err->has_error = 1; + err->level = E_WARNING; + spprintf(&err->msg, 0, "error converting %s data (path: %s): %.*s", + what_conv, + path.c && path.c != '\0' ? path.c : "unavailable", + user_msg_size, user_msg); + err->should_free = 1; + + efree(user_msg); + smart_str_free_ex(&path, 0); +} +__attribute__ ((format (printf, 2, 3))) +static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); + va_end(ap); +} +__attribute__ ((format (printf, 2, 3))) +static void do_to_zval_err(res_context *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "native", fmt, ap); + va_end(ap); +} +static void err_msg_dispose(struct err_s *err TSRMLS_DC) +{ + if (err->msg != NULL) { + php_error_docref0(NULL TSRMLS_CC, err->level, "%s", err->msg); + if (err->should_free) { + efree(err->msg); + } + } +} + + +/* Generic Aggregated conversions */ +static void from_zval_write_aggregation(const zval *container, + char *structure, + const field_descriptor *descriptors, + ser_context *ctx) +{ + const field_descriptor *descr; + zval **elem; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + } + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + if (zend_hash_find(Z_ARRVAL_P(container), + descr->name, descr->name_size, (void**)&elem) == SUCCESS) { + + if (descr->from_zval == NULL) { + do_from_zval_err(ctx, "No information on how to convert value " + "of key '%s'", descr->name); + break; + } + + zend_llist_add_element(&ctx->keys, (void*)&descr->name); + descr->from_zval(*elem, ((char*)structure) + descr->field_offset, ctx); + zend_llist_remove_tail(&ctx->keys); + + } else if (descr->required) { + do_from_zval_err(ctx, "The key '%s' is required", descr->name); + break; + } + } +} +static void to_zval_read_aggregation(const char *structure, + zval *zarr, /* initialized array */ + const field_descriptor *descriptors, + res_context *ctx) +{ + const field_descriptor *descr; + + assert(Z_TYPE_P(zarr) == IS_ARRAY); + assert(Z_ARRVAL_P(zarr) != NULL); + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + zval *new_zv; + + if (descr->to_zval == NULL) { + do_to_zval_err(ctx, "No information on how to convert native " + "field into value for key '%s'", descr->name); + break; + } + + ALLOC_INIT_ZVAL(new_zv); + add_assoc_zval_ex(zarr, descr->name, descr->name_size, new_zv); + + zend_llist_add_element(&ctx->keys, (void*)&descr->name); + descr->to_zval(structure + descr->field_offset, new_zv, ctx); + zend_llist_remove_tail(&ctx->keys); + } +} + +/* CONVERSIONS for integers */ +static long from_zval_integer_common(const zval *arr_value, ser_context *ctx) +{ + long ret = 0; + zval lzval = zval_used_for_init; + + if (Z_TYPE_P(arr_value) != IS_LONG) { + ZVAL_COPY_VALUE(&lzval, arr_value); + zval_copy_ctor(&lzval); + arr_value = &lzval; + } + + switch (Z_TYPE_P(arr_value)) { + case IS_LONG: +long_case: + ret = Z_LVAL_P(arr_value); + break; + + /* if not long we're operating on lzval */ + case IS_DOUBLE: +double_case: + convert_to_long(&lzval); + goto long_case; + + case IS_OBJECT: + case IS_STRING: { + long lval; + double dval; + + convert_to_string(&lzval); + + switch (is_numeric_string(Z_STRVAL(lzval), Z_STRLEN(lzval), &lval, &dval, 0)) { + case IS_DOUBLE: + zval_dtor(&lzval); + Z_TYPE(lzval) = IS_DOUBLE; + Z_DVAL(lzval) = dval; + goto double_case; + + case IS_LONG: + zval_dtor(&lzval); + Z_TYPE(lzval) = IS_LONG; + Z_DVAL(lzval) = lval; + goto long_case; + } + + /* if we get here, we don't have a numeric string */ + do_from_zval_err(ctx, "expected an integer, but got a non numeric " + "string (possibly from a converted object): '%s'", Z_STRVAL_P(arr_value)); + break; + } + + default: + do_from_zval_err(ctx, "%s", "expected an integer, either of a PHP " + "integer type or of a convertible type"); + break; + } + + zval_dtor(&lzval); + + return ret; +} +static void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + int ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval > INT_MAX || lval < INT_MIN) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a native int"); + return; + } + + ival = (int)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_unsigned(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + unsigned ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (sizeof(long) > sizeof(ival) && (lval < 0 || lval > UINT_MAX)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a native unsigned int"); + return; + } + + ival = (unsigned)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_uint32(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint32_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (sizeof(long) > sizeof(uint32_t) && (lval < 0 || lval > 0xFFFFFFFF)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 32-bit integer"); + return; + } + + ival = (uint32_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint16_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > 0xFFFF) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 16-bit integer"); + return; + } + + ival = htons((uint16_t)lval); + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_sa_family(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + sa_family_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > (sa_family_t)-1) { /* sa_family_t is unsigned */ + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a sa_family_t value"); + return; + } + + ival = (sa_family_t)lval; + memcpy(field, &ival, sizeof(ival)); +} + +static void to_zval_read_int(const char *data, zval *zv, res_context *ctx) +{ + int ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) +{ + unsigned ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) +{ + uint16_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ntohs(ival)); +} +static void to_zval_read_uint32(const char *data, zval *zv, res_context *ctx) +{ + uint32_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_sa_family(const char *data, zval *zv, res_context *ctx) +{ + sa_family_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} + +/* CONVERSIONS for sockaddr */ +static void from_zval_write_sin_addr(const zval *zaddr_str, char *inaddr, ser_context *ctx) +{ + int res; + struct sockaddr_in saddr = {0}; + zval lzval = zval_used_for_init; + TSRMLS_FETCH(); + + if (Z_TYPE_P(zaddr_str) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zaddr_str); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zaddr_str = &lzval; + } + + res = php_set_inet_addr(&saddr, Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); + if (res) { + memcpy(inaddr, &saddr.sin_addr, sizeof saddr.sin_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET " + "address", Z_STRVAL_P(zaddr_str)); + } + + zval_dtor(&lzval); +} +static void to_zval_read_sin_addr(const char *data, zval *zv, res_context *ctx) +{ + const struct in_addr *addr = (const struct in_addr *)data; + socklen_t size = INET_ADDRSTRLEN; + + Z_TYPE_P(zv) = IS_STRING; + Z_STRVAL_P(zv) = ecalloc(1, size); + Z_STRLEN_P(zv) = 0; + + if (inet_ntop(AF_INET, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, "could not convert IPv4 address to string " + "(errno %d)", errno); + return; + } + + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); +} +static const field_descriptor descriptors_sockaddr_in[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_in, sin_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in, sin_addr), from_zval_write_sin_addr, to_zval_read_sin_addr}, + {"port", sizeof("port"), 0, offsetof(struct sockaddr_in, sin_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, + {0} +}; +static void from_zval_write_sockaddr_in(const zval *container, char *sockaddr, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_in, ctx); +} +static void to_zval_read_sockaddr_in(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_in, ctx); +} +static void from_zval_write_sin6_addr(const zval *zaddr_str, char *addr6, ser_context *ctx) +{ + int res; + struct sockaddr_in6 saddr6 = {0}; + zval lzval = zval_used_for_init; + TSRMLS_FETCH(); + + if (Z_TYPE_P(zaddr_str) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zaddr_str); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zaddr_str = &lzval; + } + + res = php_set_inet6_addr(&saddr6, + Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); + if (res) { + memcpy(addr6, &saddr6.sin6_addr, sizeof saddr6.sin6_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET6 " + "address", Z_STRVAL_P(zaddr_str)); + } + + zval_dtor(&lzval); +} +static void to_zval_read_sin6_addr(const char *data, zval *zv, res_context *ctx) +{ + const struct in6_addr *addr = (const struct in6_addr *)data; + socklen_t size = INET6_ADDRSTRLEN; + + Z_TYPE_P(zv) = IS_STRING; + Z_STRVAL_P(zv) = ecalloc(1, size); + Z_STRLEN_P(zv) = 0; + + if (inet_ntop(AF_INET6, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, "could not convert IPv6 address to string " + "(errno %d)", errno); + return; + } + + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); +} +static const field_descriptor descriptors_sockaddr_in6[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_in6, sin6_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in6, sin6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, + {"port", sizeof("port"), 0, offsetof(struct sockaddr_in6, sin6_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, + {"flowinfo", sizeof("flowinfo"), 0, offsetof(struct sockaddr_in6, sin6_flowinfo), from_zval_write_uint32, to_zval_read_uint32}, + {"scope_id", sizeof("scope_id"), 0, offsetof(struct sockaddr_in6, sin6_scope_id), from_zval_write_uint32, to_zval_read_uint32}, + {0} +}; +static void from_zval_write_sockaddr_in6(const zval *container, char *sockaddr6, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr6, descriptors_sockaddr_in6, ctx); +} +static void to_zval_read_sockaddr_in6(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_in6, ctx); +} +static void from_zval_write_sockaddr_aux(const zval *container, + struct sockaddr **sockaddr_ptr, + socklen_t *sockaddr_len, + ser_context *ctx) +{ + int family; + zval **elem; + int fill_sockaddr; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + fill_sockaddr = param_get_bool(ctx, KEY_FILL_SOCKADDR, 1); + + if (zend_hash_find(Z_ARRVAL_P(container), "family", sizeof("family"), (void**)&elem) == SUCCESS + && Z_TYPE_PP(elem) != IS_NULL) { + const char *node = "family"; + zend_llist_add_element(&ctx->keys, &node); + from_zval_write_int(*elem, (char*)&family, ctx); + zend_llist_remove_tail(&ctx->keys); + } else { + family = ctx->sock->type; + } + + switch (family) { + case AF_INET: + /* though not all OSes support sockaddr_in used in IPv6 sockets */ + if (ctx->sock->type != AF_INET && ctx->sock->type != AF_INET6) { + do_from_zval_err(ctx, "the specified family (number %d) is not " + "supported on this socket", family); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in), ctx); + *sockaddr_len = sizeof(struct sockaddr_in); + if (fill_sockaddr) { + from_zval_write_sockaddr_in(container, (char*)*sockaddr_ptr, ctx); + } + break; + case AF_INET6: + if (ctx->sock->type != AF_INET6) { + do_from_zval_err(ctx, "the specified family (AF_INET6) is not " + "supported on this socket"); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in6), ctx); + *sockaddr_len = sizeof(struct sockaddr_in6); + if (fill_sockaddr) { + from_zval_write_sockaddr_in6(container, (char*)*sockaddr_ptr, ctx); + } + break; + default: + do_from_zval_err(ctx, "%s", "the only families currently supported are " + "AF_INET and AF_INET6"); + break; + } +} +static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_context *ctx) +{ + const struct sockaddr *saddr = (struct sockaddr *)sockaddr_c; + + assert(Z_TYPE_P(zv) == IS_ARRAY); + + switch (saddr->sa_family) { + case AF_INET: + to_zval_read_sockaddr_in(sockaddr_c, zv, ctx); + break; + + case AF_INET6: + to_zval_read_sockaddr_in6(sockaddr_c, zv, ctx); + break; + + default: + do_to_zval_err(ctx, "cannot read struct sockaddr with family %d; " + "not supported", + (int)saddr->sa_family); + break; + } +} + +/* CONVERSIONS for cmsghdr */ +/* + * [ level => , type => , data => [],] + * struct cmsghdr { + * socklen_t cmsg_len; // data byte count, including header + * int cmsg_level; // originating protocol + * int cmsg_type; // protocol-specific type + * // followed by unsigned char cmsg_data[]; + * }; + */ +static void from_zval_write_control(const zval *arr, + void **control_buf, + zend_llist_element *alloc, + size_t *control_len, + size_t *offset, + ser_context *ctx) +{ + struct cmsghdr *cmsghdr; + int level, + type; + size_t req_space, + space_left; + ancillary_reg_entry *entry; + + static const field_descriptor descriptor_level[] = { + {"level", sizeof("level"), 0, 0, from_zval_write_int, 0}, + {0} + }; + static const field_descriptor descriptor_type[] = { + {"type", sizeof("type"), 0, 0, from_zval_write_int, 0}, + {0} + }; + field_descriptor descriptor_data[] = { + {"data", sizeof("data"), 0, 0, 0, 0}, + {0} + }; + + from_zval_write_aggregation(arr, (char *)&level, descriptor_level, ctx); + if (ctx->err.has_error) { + return; + } + from_zval_write_aggregation(arr, (char *)&type, descriptor_type, ctx); + if (ctx->err.has_error) { + return; + } + + entry = get_ancillary_reg_entry(level, type); + if (entry == NULL) { + do_from_zval_err(ctx, "cmsghdr with level %d and type %d not supported", + level, type); + return; + } + + req_space = CMSG_SPACE(entry->size); + space_left = *control_len - *offset; + assert(*control_len >= *offset); + + if (space_left < req_space) { + *control_buf = safe_erealloc(*control_buf, 2, req_space, *control_len); + *control_len += 2 * req_space; + memcpy(&alloc->data, *control_buf, sizeof *control_buf); + } + + cmsghdr = (struct cmsghdr*)(((char*)*control_buf) + *offset); + cmsghdr->cmsg_level = level; + cmsghdr->cmsg_type = type; + cmsghdr->cmsg_len = CMSG_LEN(entry->size); + + descriptor_data[0].from_zval = entry->from_array; + from_zval_write_aggregation(arr, (char*)CMSG_DATA(cmsghdr), descriptor_data, ctx); + + *offset += req_space; +} +static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_context *ctx) +{ + HashPosition pos; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + zval **elem; + uint32_t i; + int num_elems; + void *control_buf; + zend_llist_element *alloc; + size_t control_len, + cur_offset; + struct msghdr *msg = (struct msghdr*)msghdr_c; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elems == 0) { + return; + } + + /* estimate each message at 20 bytes */ + control_buf = accounted_safe_ecalloc(num_elems, CMSG_SPACE(20), 0, ctx); + alloc = ctx->allocations.tail; + control_len = (size_t)num_elems * CMSG_SPACE(20); + cur_offset = 0; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + from_zval_write_control(*elem, &control_buf, alloc, &control_len, + &cur_offset, ctx); + + zend_llist_remove_tail(&ctx->keys); + } + + msg->msg_control = control_buf; + msg->msg_controllen = control_len; +} +static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) +{ + const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; + ancillary_reg_entry *entry; + + entry = get_ancillary_reg_entry(cmsg->cmsg_level, cmsg->cmsg_type); + if (entry == NULL) { + do_to_zval_err(ctx, "cmsghdr with level %d and type %d not supported", + cmsg->cmsg_level, cmsg->cmsg_type); + return; + } + if (CMSG_LEN(entry->size) > cmsg->cmsg_len) { + do_to_zval_err(ctx, "the cmsghdr structure is unexpectedly small; " + "expected a length of at least %ld, but got %ld", + (long)CMSG_LEN(entry->size), (long)cmsg->cmsg_len); + return; + } + + entry->to_array((const char *)CMSG_DATA(cmsg), zv, ctx); +} +static void to_zval_read_control(const char *cmsghdr_c, zval *zv, res_context *ctx) +{ + /* takes a cmsghdr, not a msghdr like from_zval_write_control */ + static const field_descriptor descriptors[] = { + {"level", sizeof("level"), 0, offsetof(struct cmsghdr, cmsg_level), 0, to_zval_read_int}, + {"type", sizeof("type"), 0, offsetof(struct cmsghdr, cmsg_type), 0, to_zval_read_int}, + {"data", sizeof("data"), 0, 0 /* cmsghdr passed */, 0, to_zval_read_cmsg_data}, + {0} + }; + + array_init_size(zv, 3); + to_zval_read_aggregation(cmsghdr_c, zv, descriptors, ctx); +} +static void to_zval_read_control_array(const char *msghdr_c, zval *zv, res_context *ctx) +{ + struct msghdr *msg = (struct msghdr *)msghdr_c; + struct cmsghdr *cmsg; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + uint32_t i = 1; + + /*if (msg->msg_flags & MSG_CTRUNC) { + php_error_docref0(NULL, E_WARNING, "The MSG_CTRUNC flag is present; will not " + "attempt to read control messages"); + ZVAL_FALSE(zv); + return; + }*/ + + array_init(zv); + + for (cmsg = CMSG_FIRSTHDR(msg); + cmsg != NULL && !ctx->err.has_error; + cmsg = CMSG_NXTHDR(msg,cmsg)) { + zval *elem; + + ALLOC_INIT_ZVAL(elem); + add_next_index_zval(zv, elem); + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + to_zval_read_control((const char *)cmsg, elem, ctx); + + zend_llist_remove_tail(&ctx->keys); + } +} + +/* CONVERSIONS for msghdr */ +static void from_zval_write_name(const zval *zname_arr, char *msghdr_c, ser_context *ctx) +{ + struct sockaddr *sockaddr; + socklen_t sockaddr_len; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + + from_zval_write_sockaddr_aux(zname_arr, &sockaddr, &sockaddr_len, ctx); + + msghdr->msg_name = sockaddr; + msghdr->msg_namelen = sockaddr_len; +} +static void to_zval_read_name(const char *sockaddr_p, zval *zv, res_context *ctx) +{ + void *name = (void*)*(void**)sockaddr_p; + if (name == NULL) { + ZVAL_NULL(zv); + } else { + array_init(zv); + to_zval_read_sockaddr_aux(name, zv, ctx); + } +} +static void from_zval_write_msghdr_buffer_size(const zval *elem, char *msghdr_c, ser_context *ctx) +{ + long lval; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + + lval = from_zval_integer_common(elem, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > MAX_USER_BUFF_SIZE) { + do_from_zval_err(ctx, "the buffer size must be between 1 and %ld; " + "given %ld", (long)MAX_USER_BUFF_SIZE, lval); + return; + } + + msghdr->msg_iovlen = 1; + msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); + msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)lval, ctx); + msghdr->msg_iov[0].iov_len = (size_t)lval; +} +static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_context *ctx) +{ + HashPosition pos; + int num_elem; + zval **elem; + unsigned i; + struct msghdr *msg = (struct msghdr*)msghdr_c; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + } + + num_elem = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elem == 0) { + return; + } + + msg->msg_iov = accounted_safe_ecalloc(num_elem, sizeof *msg->msg_iov, 0, ctx); + msg->msg_iovlen = (size_t)num_elem; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { + size_t len; + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + zval_add_ref(elem); + convert_to_string_ex(elem); + + len = Z_STRLEN_PP(elem); + msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); + msg->msg_iov[i - 1].iov_len = len; + memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); + + zval_ptr_dtor(elem); + + zend_llist_remove_tail(&ctx->keys); + } + +} +static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_context *ctx) +{ + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + uint32_t len; + + /* controllen should be an unsigned with at least 32-bit. Let's assume + * this least common denominator + */ + from_zval_write_uint32(elem, (char*)&len, ctx); + if (!ctx->err.has_error && len == 0) { + do_from_zval_err(ctx, "controllen cannot be 0"); + } + msghdr->msg_control = accounted_emalloc(len, ctx); + msghdr->msg_controllen = len; +} +static void from_zval_write_msghdr_send(const zval *container, char *msghdr_c, ser_context *ctx) +{ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, + {"iov", sizeof("iov"), 0, 0, from_zval_write_iov_array, 0}, + {"control", sizeof("control"), 0, 0, from_zval_write_control_array, 0}, + {0} + }; + + from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); +} +static void from_zval_write_msghdr_recv(const zval *container, char *msghdr_c, ser_context *ctx) +{ + /* zval to struct msghdr, version for recvmsg(). It differs from the version + * for sendmsg() in that it: + * - has a buffer_size instead of an iov array; + * - has no control element; has a controllen element instead + * struct msghdr { + * void *msg_name; + * socklen_t msg_namelen; + * struct iovec *msg_iov; + * size_t msg_iovlen; + * void *msg_control; + * size_t msg_controllen; //can also be socklen_t + * int msg_flags; + * }; + */ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, + {"buffer_size", sizeof("buffer_size"), 0, 0, from_zval_write_msghdr_buffer_size, 0}, + {"controllen", sizeof("controllen"), 1, 0, from_zval_write_controllen, 0}, + {0} + }; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + const int falsev = 0, + *falsevp = &falsev; + + if (zend_hash_add(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR), + (void*)&falsevp, sizeof(falsevp), NULL) == FAILURE) { + do_from_zval_err(ctx, "could not add fill_sockaddr; this is a bug"); + return; + } + + from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); + + zend_hash_del(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR)); + if (ctx->err.has_error) { + return; + } + + if (msghdr->msg_iovlen == 0) { + msghdr->msg_iovlen = 1; + msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); + msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)DEFAULT_BUFF_SIZE, ctx); + msghdr->msg_iov[0].iov_len = (size_t)DEFAULT_BUFF_SIZE; + } +} + +static void to_zval_read_iov(const char *msghdr_c, zval *zv, res_context *ctx) +{ + const struct msghdr *msghdr = (const struct msghdr *)msghdr_c; + size_t iovlen = msghdr->msg_iovlen; + ssize_t **recvmsg_ret, + bytes_left; + uint i; + + if (iovlen > UINT_MAX) { + do_to_zval_err(ctx, "unexpectedly large value for iov_len: %lu", + (unsigned long)iovlen); + } + array_init_size(zv, (uint)iovlen); + + if (zend_hash_find(&ctx->params, KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), + (void**)&recvmsg_ret) == FAILURE) { + do_to_zval_err(ctx, "recvmsg_ret not found in params. This is a bug"); + return; + } + bytes_left = **recvmsg_ret; + + for (i = 0; bytes_left > 0 && i < (uint)iovlen; i++) { + zval *elem; + size_t len = MIN(msghdr->msg_iov[i].iov_len, bytes_left); + char *buf = safe_emalloc(1, len, 1); + + MAKE_STD_ZVAL(elem); + memcpy(buf, msghdr->msg_iov[i].iov_base, len); + buf[len] = '\0'; + + ZVAL_STRINGL(elem, buf, len, 0); + add_next_index_zval(zv, elem); + bytes_left -= len; + } +} +static void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx) +{ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, offsetof(struct msghdr, msg_name), 0, to_zval_read_name}, + {"control", sizeof("control"), 0, 0, 0, to_zval_read_control_array}, + {"iov", sizeof("iov"), 0, 0, 0, to_zval_read_iov}, + {"flags", sizeof("flags"), 0, offsetof(struct msghdr, msg_flags), 0, to_zval_read_int}, + {0} + }; + + array_init_size(zv, 4); + + to_zval_read_aggregation(msghdr_c, zv, descriptors, ctx); +} + + +/* CONVERSIONS for struct in6_pktinfo */ +static const field_descriptor descriptors_in6_pktinfo[] = { + {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, + {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_unsigned, to_zval_read_unsigned}, + {0} +}; +static void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) +{ + from_zval_write_aggregation(container, in6_pktinfo_c, descriptors_in6_pktinfo, ctx); +} +static void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx) +{ + array_init_size(zv, 2); + + to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); +} + +/* ENTRY POINT for conversions */ +static void free_from_zval_allocation(void *alloc_ptr_ptr) +{ + efree(*(void**)alloc_ptr_ptr); +} +static void *from_zval_run_conversions(const zval *container, + php_socket *sock, + from_zval_write_field *writer, + size_t struct_size, + const char *top_name, + zend_llist **allocations /* out */, + struct err_s *err /* in/out */) +{ + ser_context ctx = {{0}}; + char *structure = NULL; + + *allocations = NULL; + + if (err->has_error) { + return NULL; + } + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_init(&ctx.allocations, sizeof(void *), &free_from_zval_allocation, 0); + ctx.sock = sock; + + structure = ecalloc(1, struct_size); + + zend_llist_add_element(&ctx.keys, &top_name); + zend_llist_add_element(&ctx.allocations, &structure); + + /* main call */ + writer(container, structure, &ctx); + + if (ctx.err.has_error) { + zend_llist_destroy(&ctx.allocations); /* deallocates structure as well */ + structure = NULL; + *err = ctx.err; + } else { + *allocations = emalloc(sizeof **allocations); + **allocations = ctx.allocations; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return structure; +} +static zval *to_zval_run_conversions(const char *structure, + to_zval_read_field *reader, + const char *top_name, + const struct key_value *key_value_pairs, + struct err_s *err) +{ + res_context ctx = {{0}, {0}}; + const struct key_value *kv; + zval *zv = NULL; + + if (err->has_error) { + return NULL; + } + + ALLOC_INIT_ZVAL(zv); + + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_add_element(&ctx.keys, &top_name); + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + for (kv = key_value_pairs; kv->key != NULL; kv++) { + zend_hash_update(&ctx.params, kv->key, kv->key_size, + (void*)&kv->value, sizeof(kv->value), NULL); + } + + /* main call */ + reader(structure, zv, &ctx); + + if (ctx.err.has_error) { + zval_ptr_dtor(&zv); + zv = NULL; + *err = ctx.err; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return zv; +} + +#ifdef ZTS +static MUTEX_T ancillary_mutex; +#endif +static void init_ancillary_registry(void) +{ + ancillary_reg_entry entry; + anc_reg_key key; + ancillary_registry.initialized = 1; + + zend_hash_init(&ancillary_registry.ht, 32, NULL, NULL, 1); + + /* struct in6_pktinfo *pktinfo; */ + entry.size = sizeof(struct in6_pktinfo); + entry.from_array = from_zval_write_in6_pktinfo; + entry.to_array = to_zval_read_in6_pktinfo; + key.cmsg_level = IPPROTO_IPV6; + key.msg_type = IPV6_PKTINFO; + zend_hash_update(&ancillary_registry.ht, (char*)&key, sizeof(key), + (void*)&entry, sizeof(entry), NULL); +} +static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) +{ + anc_reg_key key = { cmsg_level, msg_type }; + ancillary_reg_entry *entry; + +#ifdef ZTS + tsrm_mutex_lock(ancillary_mutex); +#endif + if (!ancillary_registry.initialized) { + init_ancillary_registry(); + } +#ifdef ZTS + tsrm_mutex_unlock(ancillary_mutex); +#endif + + if (zend_hash_find(&ancillary_registry.ht, (char*)&key, sizeof(key), + (void**)&entry) == SUCCESS) { + return entry; + } else { + return NULL; + } +} + +PHP_FUNCTION(socket_sendmsg) +{ + zval *zsocket, + *zmsg; + long flags = 0; + php_socket *php_sock; + struct msghdr *msghdr; + zend_llist *allocations; + struct err_s err = {0}; + ssize_t res; + + /* zmsg should be passed by ref */ + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ra|l", &zsocket, &zmsg, &flags) == FAILURE) { + return; + } + + LONG_CHECK_VALID_INT(flags); + + ZEND_FETCH_RESOURCE(php_sock, php_socket *, &zsocket, -1, + php_sockets_le_socket_name, php_sockets_le_socket()); + + msghdr = from_zval_run_conversions(zmsg, php_sock, from_zval_write_msghdr_send, + sizeof(*msghdr), "msghdr", &allocations, &err); + + if (err.has_error) { + err_msg_dispose(&err TSRMLS_CC); + RETURN_FALSE; + } + + res = sendmsg(php_sock->bsd_socket, msghdr, (int)flags); + + if (res != -1) { + zend_llist_destroy(allocations); + efree(allocations); + + RETURN_LONG((long)res); + } else { + SOCKETS_G(last_error) = errno; + php_error_docref(NULL TSRMLS_CC, E_WARNING, "error in sendmsg [%d]: %s", + errno, sockets_strerror(errno TSRMLS_CC)); + RETURN_FALSE; + } +} + +PHP_FUNCTION(socket_recvmsg) +{ + zval *zsocket, + *zmsg; + long flags = 0; + php_socket *php_sock; + ssize_t res; + struct msghdr *msghdr; + zend_llist *allocations; + struct err_s err = {0}; + + //ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ra|l", + &zsocket, &zmsg, &flags) == FAILURE) { + return; + } + + LONG_CHECK_VALID_INT(flags); + + ZEND_FETCH_RESOURCE(php_sock, php_socket *, &zsocket, -1, + php_sockets_le_socket_name, php_sockets_le_socket()); + + msghdr = from_zval_run_conversions(zmsg, php_sock, from_zval_write_msghdr_recv, + sizeof(*msghdr), "msghdr", &allocations, &err); + + if (err.has_error) { + err_msg_dispose(&err TSRMLS_CC); + RETURN_FALSE; + } + + res = recvmsg(php_sock->bsd_socket, msghdr, (int)flags); + + if (res != -1) { + zval *zres; + struct key_value kv[] = { + {KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), &res}, + {0} + }; + + + zres = to_zval_run_conversions((char *)msghdr, to_zval_read_msghdr, + "msghdr", kv, &err); + + /* we don;t need msghdr anymore; free it */ + msghdr = NULL; + zend_llist_destroy(allocations); + efree(allocations); + + zval_dtor(zmsg); + if (!err.has_error) { + ZVAL_COPY_VALUE(zmsg, zres); + efree(zres); /* only shallow destruction */ + } else { + err_msg_dispose(&err TSRMLS_CC); + ZVAL_FALSE(zmsg); + /* no need to destroy/free zres -- it's NULL in this circumstance */ + assert(zres == NULL); + } + } else { + SOCKETS_G(last_error) = errno; + php_error_docref(NULL TSRMLS_CC, E_WARNING, "error in recvmsg [%d]: %s", + errno, sockets_strerror(errno TSRMLS_CC)); + RETURN_FALSE; + } + + RETURN_LONG((long)res); +} + +PHP_FUNCTION(socket_cmsg_space) +{ + long level, + type; + ancillary_reg_entry *entry; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &level, &type) == FAILURE) { + return; + } + + LONG_CHECK_VALID_INT(level); + LONG_CHECK_VALID_INT(type); + + entry = get_ancillary_reg_entry(level, type); + if (entry == NULL) { + php_error_docref0(NULL TSRMLS_CC, E_WARNING, "The pair level %ld/type %ld is " + "not supported by PHP", level, type); + return; + } + + RETURN_LONG((long)CMSG_SPACE(entry->size)); +} + +void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) +{ + REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT); + +#ifdef ZTS + ancillary_mutex = tsrm_mutex_alloc(); +#endif +} + +void _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS) +{ +#ifdef ZTS + tsrm_mutex_free(ancillary_mutex); +#endif +} diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h new file mode 100644 index 00000000000..82dc456e655 --- /dev/null +++ b/ext/sockets/sendrecvmsg.h @@ -0,0 +1,8 @@ +#include + +PHP_FUNCTION(socket_sendmsg); +PHP_FUNCTION(socket_recvmsg); +PHP_FUNCTION(socket_cmsg_space); + +void _socket_sendrecvmsg_init(INIT_FUNC_ARGS); +void _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); diff --git a/ext/sockets/sockaddr_conv.c b/ext/sockets/sockaddr_conv.c new file mode 100644 index 00000000000..19c61740d0c --- /dev/null +++ b/ext/sockets/sockaddr_conv.c @@ -0,0 +1,119 @@ +#include +#include +#include "php_sockets.h" + +#ifdef PHP_WIN32 +#include +#else +#include +#include +#endif + +#if HAVE_IPV6 +/* Sets addr by hostname, or by ip in string form (AF_INET6) */ +int php_set_inet6_addr(struct sockaddr_in6 *sin6, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ +{ + struct in6_addr tmp; +#if HAVE_GETADDRINFO + struct addrinfo hints; + struct addrinfo *addrinfo = NULL; +#endif + + if (inet_pton(AF_INET6, string, &tmp)) { + memcpy(&(sin6->sin6_addr.s6_addr), &(tmp.s6_addr), sizeof(struct in6_addr)); + } else { +#if HAVE_GETADDRINFO + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET6; + hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; + getaddrinfo(string, NULL, &hints, &addrinfo); + if (!addrinfo) { +#ifdef PHP_WIN32 + PHP_SOCKET_ERROR(php_sock, "Host lookup failed", WSAGetLastError()); +#else + PHP_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); +#endif + return 0; + } + if (addrinfo->ai_family != PF_INET6 || addrinfo->ai_addrlen != sizeof(struct sockaddr_in6)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: Non AF_INET6 domain returned on AF_INET6 socket"); + freeaddrinfo(addrinfo); + return 0; + } + + memcpy(&(sin6->sin6_addr.s6_addr), ((struct sockaddr_in6*)(addrinfo->ai_addr))->sin6_addr.s6_addr, sizeof(struct in6_addr)); + freeaddrinfo(addrinfo); + +#else + /* No IPv6 specific hostname resolution is available on this system? */ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: getaddrinfo() not available on this system"); + return 0; +#endif + + } + + return 1; +} +/* }}} */ +#endif + +/* Sets addr by hostname, or by ip in string form (AF_INET) */ +int php_set_inet_addr(struct sockaddr_in *sin, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ +{ + struct in_addr tmp; + struct hostent *host_entry; + + if (inet_aton(string, &tmp)) { + sin->sin_addr.s_addr = tmp.s_addr; + } else { + if (! (host_entry = gethostbyname(string))) { + /* Note: < -10000 indicates a host lookup error */ +#ifdef PHP_WIN32 + PHP_SOCKET_ERROR(php_sock, "Host lookup failed", WSAGetLastError()); +#else + PHP_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); +#endif + return 0; + } + if (host_entry->h_addrtype != AF_INET) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: Non AF_INET domain returned on AF_INET socket"); + return 0; + } + memcpy(&(sin->sin_addr.s_addr), host_entry->h_addr_list[0], host_entry->h_length); + } + + return 1; +} +/* }}} */ + +/* Sets addr by hostname or by ip in string form (AF_INET or AF_INET6, + * depending on the socket) */ +int php_set_inet46_addr(php_sockaddr_storage *ss, socklen_t *ss_len, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ +{ + if (php_sock->type == AF_INET) { + struct sockaddr_in t = {0}; + if (php_set_inet_addr(&t, string, php_sock TSRMLS_CC)) { + memcpy(ss, &t, sizeof t); + ss->ss_family = AF_INET; + *ss_len = sizeof(t); + return 1; + } + } +#if HAVE_IPV6 + else if (php_sock->type == AF_INET6) { + struct sockaddr_in6 t = {0}; + if (php_set_inet6_addr(&t, string, php_sock TSRMLS_CC)) { + memcpy(ss, &t, sizeof t); + ss->ss_family = AF_INET6; + *ss_len = sizeof(t); + return 1; + } + } +#endif + else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "IP address used in the context of an unexpected type of socket"); + } + return 0; +} diff --git a/ext/sockets/sockaddr_conv.h b/ext/sockets/sockaddr_conv.h new file mode 100644 index 00000000000..444d749fe73 --- /dev/null +++ b/ext/sockets/sockaddr_conv.h @@ -0,0 +1,24 @@ +#ifndef PHP_SOCKADR_CONV_H +#define PHP_SOCKADR_CONV_H + +#include +#include "php_sockets.h" + +/* + * Convert an IPv6 literal or a hostname info a sockaddr_in6. + * The IPv6 literal can be a IPv4 mapped address (like ::ffff:127.0.0.1). + * If the hostname yields no IPv6 addresses, a mapped IPv4 address may be returned (AI_V4MAPPED) + */ +int php_set_inet6_addr(struct sockaddr_in6 *sin6, char *string, php_socket *php_sock TSRMLS_DC); + +/* + * Convert an IPv4 literal or a hostname into a sockaddr_in. + */ +int php_set_inet_addr(struct sockaddr_in *sin, char *string, php_socket *php_sock TSRMLS_DC); + +/* + * Calls either php_set_inet6_addr() or php_set_inet_addr(), depending on the type of the socket. + */ +int php_set_inet46_addr(php_sockaddr_storage *ss, socklen_t *ss_len, char *string, php_socket *php_sock TSRMLS_DC); + +#endif diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 33d8a09d7d5..8f06d96f7f7 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -86,7 +86,9 @@ # endif #endif +#include "sockaddr_conv.h" #include "multicast.h" +#include "sendrecvmsg.h" ZEND_DECLARE_MODULE_GLOBALS(sockets) static PHP_GINIT_FUNCTION(sockets); @@ -113,8 +115,6 @@ static PHP_GINIT_FUNCTION(sockets); #define PF_INET AF_INET #endif -static char *php_strerror(int error TSRMLS_DC); - #define PHP_NORMAL_READ 0x0001 #define PHP_BINARY_READ 0x0002 @@ -270,10 +270,27 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_clear_error, 0, 0, 0) ZEND_ARG_INFO(0, socket) ZEND_END_ARG_INFO() - + ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_import_stream, 0, 0, 1) ZEND_ARG_INFO(0, stream) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_sendmsg, 0, 0, 3) + ZEND_ARG_INFO(0, socket) + ZEND_ARG_INFO(0, msghdr) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_socket_recvmsg, 0, 0, 3) + ZEND_ARG_INFO(0, socket) + ZEND_ARG_INFO(1, msghdr) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +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() /* }}} */ PHP_MINIT_FUNCTION(sockets); @@ -344,6 +361,9 @@ const zend_function_entry sockets_functions[] = { PHP_FE(socket_last_error, arginfo_socket_last_error) PHP_FE(socket_clear_error, arginfo_socket_clear_error) PHP_FE(socket_import_stream, arginfo_socket_import_stream) + PHP_FE(socket_sendmsg, arginfo_socket_sendmsg) + PHP_FE(socket_recvmsg, arginfo_socket_recvmsg) + PHP_FE(socket_cmsg_space, arginfo_socket_cmsg_space) /* for downwards compatability */ PHP_FALIAS(socket_getopt, socket_get_option, arginfo_socket_get_option) @@ -389,13 +409,13 @@ PHP_SOCKETS_API int php_sockets_le_socket(void) /* {{{ */ static php_socket *php_create_socket(void) /* {{{ */ { php_socket *php_sock = emalloc(sizeof *php_sock); - + php_sock->bsd_socket = -1; /* invalid socket */ php_sock->type = PF_UNSPEC; php_sock->error = 0; php_sock->blocking = 1; php_sock->zstream = NULL; - + return php_sock; } /* }}} */ @@ -552,7 +572,7 @@ static int php_read(php_socket *sock, void *buf, size_t maxlen, int flags) } /* }}} */ -static char *php_strerror(int error TSRMLS_DC) /* {{{ */ +char *sockets_strerror(int error TSRMLS_DC) /* {{{ */ { const char *buf; @@ -599,113 +619,6 @@ static char *php_strerror(int error TSRMLS_DC) /* {{{ */ } /* }}} */ -#if HAVE_IPV6 -/* Sets addr by hostname, or by ip in string form (AF_INET6) */ -static int php_set_inet6_addr(struct sockaddr_in6 *sin6, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ -{ - struct in6_addr tmp; -#if HAVE_GETADDRINFO - struct addrinfo hints; - struct addrinfo *addrinfo = NULL; -#endif - - if (inet_pton(AF_INET6, string, &tmp)) { - memcpy(&(sin6->sin6_addr.s6_addr), &(tmp.s6_addr), sizeof(struct in6_addr)); - } else { -#if HAVE_GETADDRINFO - - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = PF_INET6; - getaddrinfo(string, NULL, &hints, &addrinfo); - if (!addrinfo) { -#ifdef PHP_WIN32 - PHP_SOCKET_ERROR(php_sock, "Host lookup failed", WSAGetLastError()); -#else - PHP_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); -#endif - return 0; - } - if (addrinfo->ai_family != PF_INET6 || addrinfo->ai_addrlen != sizeof(struct sockaddr_in6)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: Non AF_INET6 domain returned on AF_INET6 socket"); - freeaddrinfo(addrinfo); - return 0; - } - - memcpy(&(sin6->sin6_addr.s6_addr), ((struct sockaddr_in6*)(addrinfo->ai_addr))->sin6_addr.s6_addr, sizeof(struct in6_addr)); - freeaddrinfo(addrinfo); - -#else - /* No IPv6 specific hostname resolution is available on this system? */ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: getaddrinfo() not available on this system"); - return 0; -#endif - - } - - return 1; -} -/* }}} */ -#endif - -/* Sets addr by hostname, or by ip in string form (AF_INET) */ -static int php_set_inet_addr(struct sockaddr_in *sin, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ -{ - struct in_addr tmp; - struct hostent *host_entry; - - if (inet_aton(string, &tmp)) { - sin->sin_addr.s_addr = tmp.s_addr; - } else { - if (! (host_entry = gethostbyname(string))) { - /* Note: < -10000 indicates a host lookup error */ -#ifdef PHP_WIN32 - PHP_SOCKET_ERROR(php_sock, "Host lookup failed", WSAGetLastError()); -#else - PHP_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); -#endif - return 0; - } - if (host_entry->h_addrtype != AF_INET) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host lookup failed: Non AF_INET domain returned on AF_INET socket"); - return 0; - } - memcpy(&(sin->sin_addr.s_addr), host_entry->h_addr_list[0], host_entry->h_length); - } - - return 1; -} -/* }}} */ - -/* Sets addr by hostname or by ip in string form (AF_INET or AF_INET6, - * depending on the socket) */ -static int php_set_inet46_addr(php_sockaddr_storage *ss, socklen_t *ss_len, char *string, php_socket *php_sock TSRMLS_DC) /* {{{ */ -{ - if (php_sock->type == AF_INET) { - struct sockaddr_in t = {0}; - if (php_set_inet_addr(&t, string, php_sock TSRMLS_CC)) { - memcpy(ss, &t, sizeof t); - ss->ss_family = AF_INET; - *ss_len = sizeof(t); - return 1; - } - } -#if HAVE_IPV6 - else if (php_sock->type == AF_INET6) { - struct sockaddr_in6 t = {0}; - if (php_set_inet6_addr(&t, string, php_sock TSRMLS_CC)) { - memcpy(ss, &t, sizeof t); - ss->ss_family = AF_INET6; - *ss_len = sizeof(t); - return 1; - } - } -#endif - else { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "IP address used in the context of an unexpected type of socket"); - } - return 0; -} static int php_get_if_index_from_zval(zval *val, unsigned *out TSRMLS_DC) { @@ -751,12 +664,12 @@ static int php_get_if_index_from_array(const HashTable *ht, const char *key, php_socket *sock, unsigned int *if_index TSRMLS_DC) { zval **val; - + if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { *if_index = 0; /* default: 0 */ return SUCCESS; } - + return php_get_if_index_from_zval(*val, if_index TSRMLS_CC); } @@ -765,14 +678,14 @@ static int php_get_address_from_array(const HashTable *ht, const char *key, { zval **val, *valcp; - + if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "no key \"%s\" passed in optval", key); return FAILURE; } valcp = *val; zval_add_ref(&valcp); - convert_to_string_ex(val); + convert_to_string_ex(val); if (!php_set_inet46_addr(ss, ss_len, Z_STRVAL_P(valcp), sock TSRMLS_CC)) { zval_ptr_dtor(&valcp); return FAILURE; @@ -854,7 +767,7 @@ PHP_MINIT_FUNCTION(sockets) #define MCAST_LEAVE_SOURCE_GROUP IP_DROP_SOURCE_MEMBERSHIP #endif #endif - + REGISTER_LONG_CONSTANT("MCAST_JOIN_GROUP", MCAST_JOIN_GROUP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MCAST_LEAVE_GROUP", MCAST_LEAVE_GROUP, CONST_CS | CONST_PERSISTENT); #ifdef HAS_MCAST_EXT @@ -887,6 +800,8 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("SOL_TCP", IPPROTO_TCP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOL_UDP", IPPROTO_UDP, CONST_CS | CONST_PERSISTENT); + _socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU); + return SUCCESS; } /* }}} */ @@ -908,6 +823,7 @@ PHP_RSHUTDOWN_FUNCTION(sockets) efree(SOCKETS_G(strerror_buf)); SOCKETS_G(strerror_buf) = NULL; } + _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS_PASSTHRU); return SUCCESS; } @@ -1049,7 +965,7 @@ PHP_FUNCTION(socket_select) if (retval == -1) { SOCKETS_G(last_error) = errno; - php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to select [%d]: %s", errno, php_strerror(errno TSRMLS_CC)); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to select [%d]: %s", errno, sockets_strerror(errno TSRMLS_CC)); RETURN_FALSE; } @@ -1118,7 +1034,7 @@ PHP_FUNCTION(socket_set_nonblock) } ZEND_FETCH_RESOURCE(php_sock, php_socket *, &arg1, -1, le_socket_name, le_socket); - + if (php_sock->zstream != NULL) { php_stream *stream; /* omit notice if resource doesn't exist anymore */ @@ -1155,7 +1071,7 @@ PHP_FUNCTION(socket_set_block) } ZEND_FETCH_RESOURCE(php_sock, php_socket *, &arg1, -1, le_socket_name, le_socket); - + /* if socket was created from a stream, give the stream a chance to take * care of the operation itself, thereby allowing it to update its internal * state */ @@ -1509,7 +1425,7 @@ PHP_FUNCTION(socket_create) if (IS_INVALID_SOCKET(php_sock)) { SOCKETS_G(last_error) = errno; - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, php_strerror(errno TSRMLS_CC)); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, sockets_strerror(errno TSRMLS_CC)); efree(php_sock); RETURN_FALSE; } @@ -1542,7 +1458,7 @@ PHP_FUNCTION(socket_connect) #if HAVE_IPV6 case AF_INET6: { struct sockaddr_in6 sin6 = {0}; - + if (argc != 3) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Socket of type AF_INET6 requires 3 arguments"); RETURN_FALSE; @@ -1563,7 +1479,7 @@ PHP_FUNCTION(socket_connect) #endif case AF_INET: { struct sockaddr_in sin = {0}; - + if (argc != 3) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Socket of type AF_INET requires 3 arguments"); RETURN_FALSE; @@ -1582,7 +1498,7 @@ PHP_FUNCTION(socket_connect) case AF_UNIX: { struct sockaddr_un s_un = {0}; - + if (addr_len >= sizeof(s_un.sun_path)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Path too long"); RETURN_FALSE; @@ -1619,7 +1535,7 @@ PHP_FUNCTION(socket_strerror) return; } - RETURN_STRING(php_strerror(arg1 TSRMLS_CC), 1); + RETURN_STRING(sockets_strerror(arg1 TSRMLS_CC), 1); } /* }}} */ @@ -2011,7 +1927,7 @@ PHP_FUNCTION(socket_get_option) } } } - + /* sol_socket options and general case */ switch(optname) { case SO_LINGER: @@ -2053,7 +1969,7 @@ PHP_FUNCTION(socket_get_option) add_assoc_long(return_value, "sec", tv.tv_sec); add_assoc_long(return_value, "usec", tv.tv_usec); break; - + default: optlen = sizeof(other_val); @@ -2126,12 +2042,12 @@ mcast_req_fun: source = {0}; socklen_t glen, slen; - + mcast_sreq_fun = &php_mcast_leave_source; mcast_sreq_fun: convert_to_array_ex(arg4); opt_ht = HASH_OF(*arg4); - + if (php_get_address_from_array(opt_ht, "group", php_sock, &group, &glen TSRMLS_CC) == FAILURE) { return FAILURE; @@ -2144,7 +2060,7 @@ mcast_req_fun: &if_index TSRMLS_CC) == FAILURE) { return FAILURE; } - + retval = mcast_sreq_fun(php_sock, level, (struct sockaddr*)&group, glen, (struct sockaddr*)&source, slen, if_index TSRMLS_CC); break; @@ -2184,12 +2100,12 @@ PHP_FUNCTION(socket_set_option) HashTable *opt_ht; zval **l_onoff, **l_linger; zval **sec, **usec; - + /* Multicast */ unsigned int if_index; struct in_addr if_addr; unsigned char ipv4_mcast_ttl_lback; - + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rllZ", &arg1, &level, &optname, &arg4) == FAILURE) { return; } @@ -2265,7 +2181,7 @@ ipv4_loop_ttl: if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { RETURN_FALSE; } - + opt_ptr = &if_index; optlen = sizeof(if_index); goto dosockopt; @@ -2348,7 +2264,7 @@ ipv6_loop_hops: #endif break; } - + default: convert_to_long_ex(arg4); ov = Z_LVAL_PP(arg4); @@ -2404,7 +2320,7 @@ PHP_FUNCTION(socket_create_pair) if (socketpair(domain, type, protocol, fds_array) != 0) { SOCKETS_G(last_error) = errno; - php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to create socket pair [%d]: %s", errno, php_strerror(errno TSRMLS_CC)); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to create socket pair [%d]: %s", errno, sockets_strerror(errno TSRMLS_CC)); efree(php_sock[0]); efree(php_sock[1]); RETURN_FALSE; @@ -2521,16 +2437,16 @@ PHP_FUNCTION(socket_import_stream) return; } php_stream_from_zval(stream, &zstream); - + if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void**)&socket, 1)) { /* error supposedly already shown */ RETURN_FALSE; } - + retsock = php_create_socket(); - + retsock->bsd_socket = socket; - + /* determine family */ if (getsockname(socket, (struct sockaddr*)&addr, &addr_len) == 0) { retsock->type = addr.ss_family; @@ -2538,7 +2454,7 @@ PHP_FUNCTION(socket_import_stream) PHP_SOCKET_ERROR(retsock, "unable to obtain socket family", errno); goto error; } - + /* determine blocking mode */ #ifndef PHP_WIN32 t = fcntl(socket, F_GETFL); @@ -2558,7 +2474,7 @@ PHP_FUNCTION(socket_import_stream) retsock->blocking = 1; } #endif - + /* hold a zval reference to the stream (holding a php_stream* directly could * also be done, but this might be slightly better if in the future we want * to provide a socket_export_stream) */ @@ -2567,10 +2483,10 @@ PHP_FUNCTION(socket_import_stream) zval_copy_ctor(retsock->zstream); Z_UNSET_ISREF_P(retsock->zstream); Z_SET_REFCOUNT_P(retsock->zstream, 1); - + php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_NONE, NULL); - + ZEND_REGISTER_RESOURCE(return_value, retsock, le_socket); return; error: From eb4b1f6d46d94772611a24c70b15e46c557caeec Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 2 Nov 2012 14:03:47 +0100 Subject: [PATCH 09/40] Add test for recvmsg() --- ext/sockets/tests/recvmsg.phpt | 86 ++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 ext/sockets/tests/recvmsg.phpt diff --git a/ext/sockets/tests/recvmsg.phpt b/ext/sockets/tests/recvmsg.phpt new file mode 100644 index 00000000000..30263a4fbd0 --- /dev/null +++ b/ext/sockets/tests/recvmsg.phpt @@ -0,0 +1,86 @@ +--TEST-- +recvmsg(): basic test +--SKIPIF-- + ["family" => AF_INET6, "addr" => "::1"], + "buffer_size" => 2000, + "controllen" => socket_cmsg_space(IPPROTO_IPV6, IPV6_PKTINFO), +]; +if (!socket_recvmsg($s, $data, 0)) die("recvmsg"); +print_r($data); + +--EXPECTF-- +creating send socket +resource(%d) of type (Socket) +bool(true) +creating receive socket +resource(%d) of type (Socket) +bool(true) +int(14) +Array +( + [name] => Array + ( + [family] => %d + [addr] => ::1 + [port] => 7001 + [flowinfo] => 0 + [scope_id] => 0 + ) + + [control] => Array + ( + [0] => Array + ( + [level] => %d + [type] => %d + [data] => Array + ( + [addr] => ::1 + [ifindex] => %d + ) + + ) + + ) + + [iov] => Array + ( + [0] => testing packet + ) + + [flags] => 0 +) From 806a6e6399568d3bfbef355992fb3d09e29a607c Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 2 Nov 2012 15:02:47 +0100 Subject: [PATCH 10/40] Add IPV6_UNICAST_HOPS option constant. --- ext/sockets/sockets.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 8f06d96f7f7..b213b0a6cd9 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -800,6 +800,10 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("SOL_TCP", IPPROTO_TCP, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOL_UDP", IPPROTO_UDP, CONST_CS | CONST_PERSISTENT); +#if HAVE_IPV6 + REGISTER_LONG_CONSTANT("IPV6_UNICAST_HOPS", IPV6_UNICAST_HOPS, CONST_CS | CONST_PERSISTENT); +#endif + _socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU); return SUCCESS; From b06f00477ce4f20516c6f727797f208ffaefcae9 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 2 Nov 2012 17:52:13 +0100 Subject: [PATCH 11/40] Fix bug converting zval sockaddr The bug ocurred when the family was not specified but was instead guessed. --- ext/sockets/sendrecvmsg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 379af125d70..837ae237357 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -627,6 +627,7 @@ static void from_zval_write_sockaddr_aux(const zval *container, *sockaddr_len = sizeof(struct sockaddr_in); if (fill_sockaddr) { from_zval_write_sockaddr_in(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_INET; } break; case AF_INET6: @@ -639,6 +640,7 @@ static void from_zval_write_sockaddr_aux(const zval *container, *sockaddr_len = sizeof(struct sockaddr_in6); if (fill_sockaddr) { from_zval_write_sockaddr_in6(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_INET6; } break; default: From b27c22d627e342687eb0c21bac8859ea1d91b54b Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 2 Nov 2012 17:53:48 +0100 Subject: [PATCH 12/40] Fix bug in from_zval_write_control_array() --- ext/sockets/sendrecvmsg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 837ae237357..0489e977be4 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -792,7 +792,7 @@ static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_c } msg->msg_control = control_buf; - msg->msg_controllen = control_len; + msg->msg_controllen = cur_offset; /* not control_len, which may be larger */ } static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) { From 73ab2385cb4dd2da08be940f0f6b0ef9f06f15b8 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Fri, 2 Nov 2012 17:54:31 +0100 Subject: [PATCH 13/40] Support for IPV6_HOPLIMIT and IPV6_TCLASS --- ext/sockets/sendrecvmsg.c | 43 ++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 0489e977be4..e47bd46e383 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -98,7 +98,7 @@ typedef struct { typedef struct { int cmsg_level; /* originating protocol */ - int msg_type; /* protocol-specific type */ + int cmsg_type; /* protocol-specific type */ } anc_reg_key; static struct { @@ -1194,14 +1194,32 @@ static void init_ancillary_registry(void) zend_hash_init(&ancillary_registry.ht, 32, NULL, NULL, 1); - /* struct in6_pktinfo *pktinfo; */ +#define PUT_ENTRY() \ + zend_hash_update(&ancillary_registry.ht, (char*)&key, sizeof(key), \ + (void*)&entry, sizeof(entry), NULL) + entry.size = sizeof(struct in6_pktinfo); entry.from_array = from_zval_write_in6_pktinfo; entry.to_array = to_zval_read_in6_pktinfo; key.cmsg_level = IPPROTO_IPV6; - key.msg_type = IPV6_PKTINFO; - zend_hash_update(&ancillary_registry.ht, (char*)&key, sizeof(key), - (void*)&entry, sizeof(entry), NULL); + key.cmsg_type = IPV6_PKTINFO; + PUT_ENTRY(); + + entry.size = sizeof(int); + entry.from_array = from_zval_write_int; + entry.to_array = to_zval_read_int; + key.cmsg_level = IPPROTO_IPV6; + key.cmsg_type = IPV6_HOPLIMIT; + PUT_ENTRY(); + + entry.size = sizeof(int); + entry.from_array = from_zval_write_int; + entry.to_array = to_zval_read_int; + key.cmsg_level = IPPROTO_IPV6; + key.cmsg_type = IPV6_TCLASS; + PUT_ENTRY(); + + } static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) { @@ -1364,7 +1382,22 @@ PHP_FUNCTION(socket_cmsg_space) void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_RECVHOPLIMIT", IPV6_RECVHOPLIMIT, CONST_CS | CONST_PERSISTENT); + /* would require some effort: + REGISTER_LONG_CONSTANT("IPV6_RECVRTHDR", IPV6_RECVRTHDR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_RECVHOPOPTS", IPV6_RECVHOPOPTS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_RECVDSTOPTS", IPV6_RECVDSTOPTS, CONST_CS | CONST_PERSISTENT); + */ + REGISTER_LONG_CONSTANT("IPV6_RECVTCLASS", IPV6_RECVTCLASS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_HOPLIMIT", IPV6_HOPLIMIT, CONST_CS | CONST_PERSISTENT); + /* + REGISTER_LONG_CONSTANT("IPV6_RTHDR", IPV6_RTHDR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_HOPOPTS", IPV6_HOPOPTS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_DSTOPTS", IPV6_DSTOPTS, CONST_CS | CONST_PERSISTENT); + */ + REGISTER_LONG_CONSTANT("IPV6_TCLASS", IPV6_TCLASS, CONST_CS | CONST_PERSISTENT); #ifdef ZTS ancillary_mutex = tsrm_mutex_alloc(); From 0f849fe2aa7c8894b2dbde57abd8a3a3aa8f764a Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sun, 4 Nov 2012 19:16:10 +0100 Subject: [PATCH 14/40] Add test and slightly tweak another --- .../{recvmsg.phpt => socket_recvmsg.phpt} | 0 .../tests/socket_sendrecvmsg_multi_msg.phpt | 103 ++++++++++++++++++ 2 files changed, 103 insertions(+) rename ext/sockets/tests/{recvmsg.phpt => socket_recvmsg.phpt} (100%) create mode 100644 ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt diff --git a/ext/sockets/tests/recvmsg.phpt b/ext/sockets/tests/socket_recvmsg.phpt similarity index 100% rename from ext/sockets/tests/recvmsg.phpt rename to ext/sockets/tests/socket_recvmsg.phpt diff --git a/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt b/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt new file mode 100644 index 00000000000..055e263f724 --- /dev/null +++ b/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt @@ -0,0 +1,103 @@ +--TEST-- +sendmsg()/recvmsg(): test ability to receive multiple messages +--SKIPIF-- + [ "addr" => "::1", "port" => 3000], + "iov" => ["test ", "thing", "\n"], + "control" => [[ + "level" => IPPROTO_IPV6, + "type" => IPV6_TCLASS, + "data" => 42, + ]] +], 0); +var_dump($r); +checktimeout($s, 500); + +$data = [ + "name" => ["family" => AF_INET6, "addr" => "::1"], + "buffer_size" => 2000, + "controllen" => socket_cmsg_space(IPPROTO_IPV6, IPV6_PKTINFO) + + socket_cmsg_space(IPPROTO_IPV6, IPV6_TCLASS), +]; +if (!socket_recvmsg($s, $data, 0)) die("recvmsg"); +print_r($data); + +--EXPECTF-- +creating send socket +resource(5) of type (Socket) +bool(true) +creating receive socket +resource(6) of type (Socket) +bool(true) +int(11) +Array +( + [name] => Array + ( + [family] => %d + [addr] => ::1 + [port] => 7001 + [flowinfo] => 0 + [scope_id] => 0 + ) + + [control] => Array + ( + [0] => Array + ( + [level] => %d + [type] => %d + [data] => Array + ( + [addr] => ::1 + [ifindex] => %d + ) + + ) + + [1] => Array + ( + [level] => %d + [type] => %d + [data] => 42 + ) + + ) + + [iov] => Array + ( + [0] => test thing + + ) + + [flags] => 0 +) From 17540788ad8c25969f1dbd02c1a3b75a8417fe9c Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 11:36:00 +0100 Subject: [PATCH 15/40] Added missing return statements --- ext/sockets/sendrecvmsg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index e47bd46e383..3405215ef33 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -918,6 +918,7 @@ static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_conte if (Z_TYPE_P(arr) != IS_ARRAY) { do_from_zval_err(ctx, "%s", "expected an array here"); + return; } num_elem = zend_hash_num_elements(Z_ARRVAL_P(arr)); @@ -964,6 +965,7 @@ static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_con from_zval_write_uint32(elem, (char*)&len, ctx); if (!ctx->err.has_error && len == 0) { do_from_zval_err(ctx, "controllen cannot be 0"); + return; } msghdr->msg_control = accounted_emalloc(len, ctx); msghdr->msg_controllen = len; From b3effa60c73922ddf4a7df3be3a0e4e5ca47f70d Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 23 Oct 2012 13:09:38 +0200 Subject: [PATCH 16/40] Improve imported socket family detection Also added constant SO_FAMILY. --- ext/sockets/sockets.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index b213b0a6cd9..863825df13f 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -748,6 +748,9 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("SO_SNDTIMEO", SO_SNDTIMEO, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_RCVTIMEO", SO_RCVTIMEO, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_TYPE", SO_TYPE, CONST_CS | CONST_PERSISTENT); +#ifdef SO_FAMILY + REGISTER_LONG_CONSTANT("SO_FAMILY", SO_FAMILY, CONST_CS | CONST_PERSISTENT); +#endif REGISTER_LONG_CONSTANT("SO_ERROR", SO_ERROR, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOL_SOCKET", SOL_SOCKET, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOMAXCONN", SOMAXCONN, CONST_CS | CONST_PERSISTENT); @@ -2436,6 +2439,10 @@ PHP_FUNCTION(socket_import_stream) #ifndef PHP_WIN32 int t; #endif +#ifdef SO_DOMAIN + int type; + socklen_t type_len = sizeof(type); +#endif if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstream) == FAILURE) { return; @@ -2452,6 +2459,11 @@ PHP_FUNCTION(socket_import_stream) retsock->bsd_socket = socket; /* determine family */ +#ifdef SO_DOMAIN + if (getsockopt(socket, SOL_SOCKET, SO_DOMAIN, &type, &type_len) == 0) { + retsock->type = type; + } else +#endif if (getsockname(socket, (struct sockaddr*)&addr, &addr_len) == 0) { retsock->type = addr.ss_family; } else { From 131245474bf95490cf1a1dfdb5debe5d46133522 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 14:52:48 +0100 Subject: [PATCH 17/40] Redactor to expose socket_import_file_descriptor() --- ext/sockets/php_sockets.h | 1 + ext/sockets/sockets.c | 90 ++++++++++++++++++++++----------------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 9158ca49072..3138eb60c45 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -88,6 +88,7 @@ ZEND_END_MODULE_GLOBALS(sockets) ZEND_EXTERN_MODULE_GLOBALS(sockets); char *sockets_strerror(int error TSRMLS_DC); +php_socket *socket_import_file_descriptor(PHP_SOCKET sock TSRMLS_DC); #define PHP_SOCKET_ERROR(socket,msg,errn) \ socket->error = errn; \ diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 863825df13f..449be8f9373 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -2426,6 +2426,53 @@ PHP_FUNCTION(socket_clear_error) } /* }}} */ +php_socket *socket_import_file_descriptor(PHP_SOCKET socket TSRMLS_DC) +{ +#ifdef SO_DOMAIN + int type; + socklen_t type_len = sizeof(type); +#endif + php_socket *retsock; + php_sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); +#ifndef PHP_WIN32 + int t; +#endif + + retsock = php_create_socket(); + retsock->bsd_socket = socket; + + /* determine family */ +#ifdef SO_DOMAIN + if (getsockopt(socket, SOL_SOCKET, SO_DOMAIN, &type, &type_len) == 0) { + retsock->type = type; + } else +#endif + if (getsockname(socket, (struct sockaddr*)&addr, &addr_len) == 0) { + retsock->type = addr.ss_family; + } else { + PHP_SOCKET_ERROR(retsock, "unable to obtain socket family", errno); + goto error; + } + + /* determine blocking mode */ +#ifndef PHP_WIN32 + t = fcntl(socket, F_GETFL); + if (t == -1) { + PHP_SOCKET_ERROR(retsock, "unable to obtain blocking state", errno); + goto error; + } else { + retsock->blocking = !(t & O_NONBLOCK); + } +#endif + + return retsock; + +error: + efree(retsock); + return NULL; +} + /* {{{ proto void socket_import_stream(resource stream) Imports a stream that encapsulates a socket into a socket extension resource. */ PHP_FUNCTION(socket_import_stream) @@ -2434,15 +2481,6 @@ PHP_FUNCTION(socket_import_stream) php_stream *stream; php_socket *retsock = NULL; PHP_SOCKET socket; /* fd */ - php_sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); -#ifndef PHP_WIN32 - int t; -#endif -#ifdef SO_DOMAIN - int type; - socklen_t type_len = sizeof(type); -#endif if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstream) == FAILURE) { return; @@ -2454,33 +2492,12 @@ PHP_FUNCTION(socket_import_stream) RETURN_FALSE; } - retsock = php_create_socket(); - - retsock->bsd_socket = socket; - - /* determine family */ -#ifdef SO_DOMAIN - if (getsockopt(socket, SOL_SOCKET, SO_DOMAIN, &type, &type_len) == 0) { - retsock->type = type; - } else -#endif - if (getsockname(socket, (struct sockaddr*)&addr, &addr_len) == 0) { - retsock->type = addr.ss_family; - } else { - PHP_SOCKET_ERROR(retsock, "unable to obtain socket family", errno); - goto error; + retsock = socket_import_file_descriptor(socket); + if (retsock == NULL) { + RETURN_FALSE; } - /* determine blocking mode */ -#ifndef PHP_WIN32 - t = fcntl(socket, F_GETFL); - if(t == -1) { - PHP_SOCKET_ERROR(retsock, "unable to obtain blocking state", errno); - goto error; - } else { - retsock->blocking = !(t & O_NONBLOCK); - } -#else +#ifdef PHP_WIN32 /* on windows, check if the stream is a socket stream and read its * private data; otherwise assume it's in non-blocking mode */ if (php_stream_is(stream, PHP_STREAM_IS_SOCKET)) { @@ -2504,11 +2521,6 @@ PHP_FUNCTION(socket_import_stream) PHP_STREAM_BUFFER_NONE, NULL); ZEND_REGISTER_RESOURCE(return_value, retsock, le_socket); - return; -error: - if (retsock != NULL) - efree(retsock); - RETURN_FALSE; } /* }}} */ From a85d7f28f69fbc522ed90aee1926d3733be7620d Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 00:38:23 +0100 Subject: [PATCH 18/40] Added support for AF_UNIX messages Added constants: SCM_RIGHTS, SCM_CREDENTIALS and SO_PASSCRED. The function socket_cmsg_space() was modified to support message types with variable size. Its new signature is: int socket_cmsg_space(int $level, int $type, int $n) where $n is the number of repetable elements that the message is composed of. --- ext/sockets/sendrecvmsg.c | 420 ++++++++++++++++++++++++++++++++------ 1 file changed, 363 insertions(+), 57 deletions(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 3405215ef33..f6a71dbefc7 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -75,10 +76,12 @@ struct key_value { }; #define KEY_FILL_SOCKADDR "fill_sockaddr" #define KEY_RECVMSG_RET "recvmsg_ret" +#define KEY_CMSG_LEN "cmsg_len" typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); typedef void (to_zval_read_field)(const char *data, zval *zv, res_context *ctx); +typedef size_t (calculate_req_space)(const zval *value, ser_context *ctx); typedef struct { /* zval info */ @@ -110,6 +113,8 @@ typedef socklen_t (*ancillary_size)(void); typedef struct { socklen_t size; /* size of native structure */ + socklen_t var_el_size; /* size of repeatable component */ + calculate_req_space *calc_space; from_zval_write_field *from_array; to_zval_read_field *to_array; } ancillary_reg_entry; @@ -218,6 +223,33 @@ static void err_msg_dispose(struct err_s *err TSRMLS_DC) } } +static unsigned from_array_iterate(const zval *arr, + void (*func)(zval **elem, unsigned i, void **args, ser_context *ctx), + void **args, + ser_context *ctx) +{ + HashPosition pos; + unsigned i; + zval **elem; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 1; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos), i++) { + if (snprintf(buf, sizeof(buf), "element #%u", i) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + func(elem, i, args, ctx); + + zend_llist_remove_tail(&ctx->keys); + } + + return i -1; +} /* Generic Aggregated conversions */ static void from_zval_write_aggregation(const zval *container, @@ -436,6 +468,53 @@ static void from_zval_write_sa_family(const zval *arr_value, char *field, ser_co ival = (sa_family_t)lval; memcpy(field, &ival, sizeof(ival)); } +static void from_zval_write_pid_t(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + pid_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || (pid_t)lval != lval) { /* pid_t is signed */ + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a pid_t value"); + return; + } + + ival = (pid_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uid_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + /* uid_t can be signed or unsigned (generally unsigned) */ + if ((uid_t)-1 > (uid_t)0) { + if (sizeof(long) > sizeof(uid_t) && (lval < 0 || (uid_t)lval != lval)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a uid_t value"); + return; + } + } else { + if (sizeof(long) > sizeof(uid_t) && (uid_t)lval != lval) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a uid_t value"); + return; + } + } + + ival = (uid_t)lval; + memcpy(field, &ival, sizeof(ival)); +} static void to_zval_read_int(const char *data, zval *zv, res_context *ctx) { @@ -472,6 +551,20 @@ static void to_zval_read_sa_family(const char *data, zval *zv, res_context *ctx) ZVAL_LONG(zv, (long)ival); } +static void to_zval_read_pid_t(const char *data, zval *zv, res_context *ctx) +{ + pid_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_uid_t(const char *data, zval *zv, res_context *ctx) +{ + uid_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} /* CONVERSIONS for sockaddr */ static void from_zval_write_sin_addr(const zval *zaddr_str, char *inaddr, ser_context *ctx) @@ -589,6 +682,53 @@ static void to_zval_read_sockaddr_in6(const char *data, zval *zv, res_context *c { to_zval_read_aggregation(data, zv, descriptors_sockaddr_in6, ctx); } +static void from_zval_write_sun_path(const zval *path, char *sockaddr_un_c, ser_context *ctx) +{ + zval lzval = zval_used_for_init; + struct sockaddr_un *saddr = (struct sockaddr_un*)sockaddr_un_c; + + if (Z_TYPE_P(path) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, path); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + path = &lzval; + } + + if (Z_STRLEN_P(path) >= sizeof(saddr->sun_path)) { + do_from_zval_err(ctx, "the path is too long, the maximum permitted " + "length is %ld", sizeof(saddr->sun_path) - 1); + return; + } + + memcpy(&saddr->sun_path, Z_STRVAL_P(path), Z_STRLEN_P(path)); + saddr->sun_path[Z_STRLEN_P(path)] = '\0'; + + zval_dtor(&lzval); +} +static void to_zval_read_sun_path(const char *data, zval *zv, res_context *ctx) { + struct sockaddr_un *saddr = (struct sockaddr_un*)data; + char *nul_pos; + + nul_pos = memchr(&saddr->sun_path, '\0', sizeof(saddr->sun_path)); + if (nul_pos == NULL) { + do_to_zval_err(ctx, "could not find a NUL in the path"); + return; + } + + ZVAL_STRINGL(zv, saddr->sun_path, nul_pos - (char*)&saddr->sun_path, 1); +} +static const field_descriptor descriptors_sockaddr_un[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_un, sun_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"path", sizeof("path"), 0, offsetof(struct sockaddr_un, sun_path), from_zval_write_sun_path, to_zval_read_sun_path}, +}; +static void from_zval_write_sockaddr_un(const zval *container, char *sockaddr, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_un, ctx); +} +static void to_zval_read_sockaddr_un(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_un, ctx); +} static void from_zval_write_sockaddr_aux(const zval *container, struct sockaddr **sockaddr_ptr, socklen_t *sockaddr_len, @@ -630,6 +770,7 @@ static void from_zval_write_sockaddr_aux(const zval *container, (*sockaddr_ptr)->sa_family = AF_INET; } break; + case AF_INET6: if (ctx->sock->type != AF_INET6) { do_from_zval_err(ctx, "the specified family (AF_INET6) is not " @@ -643,9 +784,24 @@ static void from_zval_write_sockaddr_aux(const zval *container, (*sockaddr_ptr)->sa_family = AF_INET6; } break; + + case AF_UNIX: + if (ctx->sock->type != AF_UNIX) { + do_from_zval_err(ctx, "the specified family (AF_UNIX) is not " + "supported on this socket"); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_un), ctx); + *sockaddr_len = sizeof(struct sockaddr_un); + if (fill_sockaddr) { + from_zval_write_sockaddr_un(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_UNIX; + } + break; + default: do_from_zval_err(ctx, "%s", "the only families currently supported are " - "AF_INET and AF_INET6"); + "AF_INET, AF_INET6 and AF_UNIX"); break; } } @@ -653,7 +809,12 @@ static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_cont { const struct sockaddr *saddr = (struct sockaddr *)sockaddr_c; - assert(Z_TYPE_P(zv) == IS_ARRAY); + if (saddr->sa_family == 0) { + ZVAL_NULL(zv); + return; + } + + array_init(zv); switch (saddr->sa_family) { case AF_INET: @@ -664,6 +825,10 @@ static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_cont to_zval_read_sockaddr_in6(sockaddr_c, zv, ctx); break; + case AF_UNIX: + to_zval_read_sockaddr_un(sockaddr_c, zv, ctx); + break; + default: do_to_zval_err(ctx, "cannot read struct sockaddr with family %d; " "not supported", @@ -725,7 +890,14 @@ static void from_zval_write_control(const zval *arr, return; } - req_space = CMSG_SPACE(entry->size); + if (entry->calc_space) { + entry->calc_space(arr, ctx); + if (ctx->err.has_error) { + return; + } + } else { + req_space = CMSG_SPACE(entry->size); + } space_left = *control_len - *offset; assert(*control_len >= *offset); @@ -796,8 +968,10 @@ static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_c } static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) { - const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; - ancillary_reg_entry *entry; + const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; + ancillary_reg_entry *entry; + size_t len, + *len_p = &len; entry = get_ancillary_reg_entry(cmsg->cmsg_level, cmsg->cmsg_type); if (entry == NULL) { @@ -812,7 +986,16 @@ static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context return; } + len = (size_t)cmsg->cmsg_len; /* use another var because type of cmsg_len varies */ + if (zend_hash_add(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), + &len_p, sizeof(len_p), NULL) == FAILURE) { + do_to_zval_err(ctx, "%s", "could not set parameter " KEY_CMSG_LEN); + return; + } + entry->to_array((const char *)CMSG_DATA(cmsg), zv, ctx); + + zend_hash_del(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN)); } static void to_zval_read_control(const char *cmsghdr_c, zval *zv, res_context *ctx) { @@ -881,7 +1064,6 @@ static void to_zval_read_name(const char *sockaddr_p, zval *zv, res_context *ctx if (name == NULL) { ZVAL_NULL(zv); } else { - array_init(zv); to_zval_read_sockaddr_aux(name, zv, ctx); } } @@ -906,15 +1088,25 @@ static void from_zval_write_msghdr_buffer_size(const zval *elem, char *msghdr_c, msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)lval, ctx); msghdr->msg_iov[0].iov_len = (size_t)lval; } +static void from_zval_write_iov_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +{ + struct msghdr *msg = args[0]; + size_t len; + + zval_add_ref(elem); + convert_to_string_ex(elem); + + len = Z_STRLEN_PP(elem); + msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); + msg->msg_iov[i - 1].iov_len = len; + memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); + + zval_ptr_dtor(elem); +} static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_context *ctx) { - HashPosition pos; int num_elem; - zval **elem; - unsigned i; struct msghdr *msg = (struct msghdr*)msghdr_c; - char buf[sizeof("element #4294967295")]; - char *bufp = buf; if (Z_TYPE_P(arr) != IS_ARRAY) { do_from_zval_err(ctx, "%s", "expected an array here"); @@ -929,30 +1121,7 @@ static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_conte msg->msg_iov = accounted_safe_ecalloc(num_elem, sizeof *msg->msg_iov, 0, ctx); msg->msg_iovlen = (size_t)num_elem; - for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; - !ctx->err.has_error - && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; - zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { - size_t len; - - if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { - memcpy(buf, "element", sizeof("element")); - } - zend_llist_add_element(&ctx->keys, &bufp); - - zval_add_ref(elem); - convert_to_string_ex(elem); - - len = Z_STRLEN_PP(elem); - msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); - msg->msg_iov[i - 1].iov_len = len; - memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); - - zval_ptr_dtor(elem); - - zend_llist_remove_tail(&ctx->keys); - } - + from_array_iterate(arr, from_zval_write_iov_array_aux, (void**)&msg, ctx); } static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_context *ctx) { @@ -1096,6 +1265,118 @@ static void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ct to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); } +/* CONVERSIONS for struct ucred */ +static const field_descriptor descriptors_ucred[] = { + {"pid", sizeof("pid"), 1, offsetof(struct ucred, pid), from_zval_write_pid_t, to_zval_read_pid_t}, + {"uid", sizeof("uid"), 1, offsetof(struct ucred, uid), from_zval_write_uid_t, to_zval_read_uid_t}, + /* assume the type gid_t is the same as uid_t: */ + {"gid", sizeof("gid"), 1, offsetof(struct ucred, gid), from_zval_write_uid_t, to_zval_read_uid_t}, + {0} +}; +static void from_zval_write_ucred(const zval *container, char *ucred_c, ser_context *ctx) +{ + from_zval_write_aggregation(container, ucred_c, descriptors_ucred, ctx); +} +static void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx) +{ + array_init_size(zv, 3); + + to_zval_read_aggregation(data, zv, descriptors_ucred, ctx); +} + +/* CONVERSIONS for SCM_RIGHTS */ +static size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) +{ + int num_elems; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return (size_t)-1; + } + + num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elems == 0) { + do_from_zval_err(ctx, "%s", "expected at least one element in this array"); + return (size_t)-1; + } + + return zend_hash_num_elements(Z_ARRVAL_P(arr)) * sizeof(int); +} +static void from_zval_write_int_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +{ + int *iarr = args[0]; + + if (Z_TYPE_PP(elem) == IS_LONG) { + + from_zval_write_int(*elem, (char*)&iarr[i], ctx); + + } else if (Z_TYPE_PP(elem) == IS_RESOURCE) { + php_stream *stream; + php_socket *sock; + + ZEND_FETCH_RESOURCE_NO_RETURN(sock, php_socket *, elem, -1, + php_sockets_le_socket_name, php_sockets_le_socket()); + if (sock) { + iarr[i] = sock->bsd_socket; + return; + } + + ZEND_FETCH_RESOURCE2_NO_RETURN(stream, php_stream *, elem, -1, + "stream", php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + do_from_zval_err(ctx, "resource is not a stream or a socket"); + return; + } + + if (php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&iarr[i], + REPORT_ERRORS) == FAILURE) { + do_from_zval_err(ctx, "cast stream to file descriptor failed"); + return; + } + } else { + do_from_zval_err(ctx, "expected an integer or resource variable"); + } +} +static void from_zval_write_int_array(const zval *arr, char *int_arr, ser_context *ctx) +{ + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + from_array_iterate(arr, &from_zval_write_int_array_aux, (void**)&int_arr, ctx); +} +static void to_zval_read_int_array(const char *data, zval *zv, res_context *ctx) +{ + size_t **cmsg_len; + int num_elems, + i; + struct cmsghdr *dummy_cmsg = 0; + size_t data_offset; + + data_offset = (unsigned char *)CMSG_DATA(dummy_cmsg) + - (unsigned char *)dummy_cmsg; + + if (zend_hash_find(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), + (void **)&cmsg_len) == FAILURE) { + do_to_zval_err(ctx, "could not get value of parameter " KEY_CMSG_LEN); + return; + } + + if (**cmsg_len < data_offset) { + do_to_zval_err(ctx, "length of cmsg is smaller than its data member " + "offset (%ld vs %ld)", (long)**cmsg_len, (long)data_offset); + return; + } + num_elems = (**cmsg_len - data_offset) / sizeof(int); + + array_init_size(zv, num_elems); + + for (i = 0; i < num_elems; i++) { + add_next_index_long(zv, (long)*((int *)data + i)); + } +} + /* ENTRY POINT for conversions */ static void free_from_zval_allocation(void *alloc_ptr_ptr) { @@ -1196,31 +1477,31 @@ static void init_ancillary_registry(void) zend_hash_init(&ancillary_registry.ht, 32, NULL, NULL, 1); -#define PUT_ENTRY() \ +#define PUT_ENTRY(sizev, var_size, calc, from, to, level, type) \ + entry.size = sizev; \ + entry.var_el_size = var_size; \ + entry.calc_space = calc; \ + entry.from_array = from; \ + entry.to_array = to; \ + key.cmsg_level = level; \ + key.cmsg_type = type; \ zend_hash_update(&ancillary_registry.ht, (char*)&key, sizeof(key), \ (void*)&entry, sizeof(entry), NULL) - entry.size = sizeof(struct in6_pktinfo); - entry.from_array = from_zval_write_in6_pktinfo; - entry.to_array = to_zval_read_in6_pktinfo; - key.cmsg_level = IPPROTO_IPV6; - key.cmsg_type = IPV6_PKTINFO; - PUT_ENTRY(); + PUT_ENTRY(sizeof(struct in6_pktinfo), 0, 0, from_zval_write_in6_pktinfo, + to_zval_read_in6_pktinfo, IPPROTO_IPV6, IPV6_PKTINFO); - entry.size = sizeof(int); - entry.from_array = from_zval_write_int; - entry.to_array = to_zval_read_int; - key.cmsg_level = IPPROTO_IPV6; - key.cmsg_type = IPV6_HOPLIMIT; - PUT_ENTRY(); + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, + to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT); - entry.size = sizeof(int); - entry.from_array = from_zval_write_int; - entry.to_array = to_zval_read_int; - key.cmsg_level = IPPROTO_IPV6; - key.cmsg_type = IPV6_TCLASS; - PUT_ENTRY(); + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, + to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS); + PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred, + to_zval_read_ucred, SOL_SOCKET, SCM_CREDENTIALS); + + PUT_ENTRY(0, sizeof(int), calculate_scm_rights_space, from_zval_write_int_array, + to_zval_read_int_array, SOL_SOCKET, SCM_RIGHTS); } static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) @@ -1361,15 +1642,24 @@ PHP_FUNCTION(socket_recvmsg) PHP_FUNCTION(socket_cmsg_space) { long level, - type; + type, + n = 0; ancillary_reg_entry *entry; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &level, &type) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll|l", + &level, &type, &n) == FAILURE) { return; } LONG_CHECK_VALID_INT(level); LONG_CHECK_VALID_INT(type); + LONG_CHECK_VALID_INT(n); + + if (n < 0) { + php_error_docref0(NULL TSRMLS_CC, E_WARNING, "The third argument " + "cannot be negative"); + return; + } entry = get_ancillary_reg_entry(level, type); if (entry == NULL) { @@ -1378,11 +1668,22 @@ PHP_FUNCTION(socket_cmsg_space) return; } - RETURN_LONG((long)CMSG_SPACE(entry->size)); + if (entry->var_el_size > 0 && n > (LONG_MAX - (long)entry->size - + (long)CMSG_SPACE(0) - 15L) / entry->var_el_size) { + /* the -15 is to account for any padding CMSG_SPACE may add after the data */ + php_error_docref0(NULL TSRMLS_CC, E_WARNING, "The value for the " + "third argument (%ld) is too large", n); + return; + } + + RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->size)); } void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) { + /* IPv6 ancillary data + * Note that support for sticky options via setsockopt() is not implemented + * yet (where special support is needed, i.e., the optval is not an int). */ REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_RECVHOPLIMIT", IPV6_RECVHOPLIMIT, CONST_CS | CONST_PERSISTENT); /* would require some effort: @@ -1401,6 +1702,11 @@ void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) */ REGISTER_LONG_CONSTANT("IPV6_TCLASS", IPV6_TCLASS, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("SCM_RIGHTS", SCM_RIGHTS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SCM_CREDENTIALS", SCM_CREDENTIALS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SO_PASSCRED", SO_PASSCRED, CONST_CS | CONST_PERSISTENT); + #ifdef ZTS ancillary_mutex = tsrm_mutex_alloc(); #endif From 7fc4671df985ab1cedcd9b03d7bd792cc1188758 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 11:40:24 +0100 Subject: [PATCH 19/40] Add test for CMSG_CREDENTIALS message --- ext/sockets/sendrecvmsg.c | 60 ++++++++----- .../tests/socket_cmsg_credentials.phpt | 89 +++++++++++++++++++ 2 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 ext/sockets/tests/socket_cmsg_credentials.phpt diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index f6a71dbefc7..385c2322336 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -719,7 +719,8 @@ static void to_zval_read_sun_path(const char *data, zval *zv, res_context *ctx) } static const field_descriptor descriptors_sockaddr_un[] = { {"family", sizeof("family"), 0, offsetof(struct sockaddr_un, sun_family), from_zval_write_sa_family, to_zval_read_sa_family}, - {"path", sizeof("path"), 0, offsetof(struct sockaddr_un, sun_path), from_zval_write_sun_path, to_zval_read_sun_path}, + {"path", sizeof("path"), 0, 0, from_zval_write_sun_path, to_zval_read_sun_path}, + {0} }; static void from_zval_write_sockaddr_un(const zval *container, char *sockaddr, ser_context *ctx) { @@ -857,7 +858,8 @@ static void from_zval_write_control(const zval *arr, struct cmsghdr *cmsghdr; int level, type; - size_t req_space, + size_t data_len, + req_space, space_left; ancillary_reg_entry *entry; @@ -891,13 +893,14 @@ static void from_zval_write_control(const zval *arr, } if (entry->calc_space) { - entry->calc_space(arr, ctx); + data_len = entry->calc_space(arr, ctx); if (ctx->err.has_error) { return; } } else { - req_space = CMSG_SPACE(entry->size); + data_len = entry->size; } + req_space = CMSG_SPACE(data_len); space_left = *control_len - *offset; assert(*control_len >= *offset); @@ -910,7 +913,7 @@ static void from_zval_write_control(const zval *arr, cmsghdr = (struct cmsghdr*)(((char*)*control_buf) + *offset); cmsghdr->cmsg_level = level; cmsghdr->cmsg_type = type; - cmsghdr->cmsg_len = CMSG_LEN(entry->size); + cmsghdr->cmsg_len = CMSG_LEN(data_len); descriptor_data[0].from_zval = entry->from_array; from_zval_write_aggregation(arr, (char*)CMSG_DATA(cmsghdr), descriptor_data, ctx); @@ -1302,27 +1305,23 @@ static size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) return zend_hash_num_elements(Z_ARRVAL_P(arr)) * sizeof(int); } -static void from_zval_write_int_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +static void from_zval_write_fd_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) { int *iarr = args[0]; - if (Z_TYPE_PP(elem) == IS_LONG) { - - from_zval_write_int(*elem, (char*)&iarr[i], ctx); - - } else if (Z_TYPE_PP(elem) == IS_RESOURCE) { + if (Z_TYPE_PP(elem) == IS_RESOURCE) { php_stream *stream; php_socket *sock; ZEND_FETCH_RESOURCE_NO_RETURN(sock, php_socket *, elem, -1, - php_sockets_le_socket_name, php_sockets_le_socket()); + NULL, php_sockets_le_socket()); if (sock) { iarr[i] = sock->bsd_socket; return; } ZEND_FETCH_RESOURCE2_NO_RETURN(stream, php_stream *, elem, -1, - "stream", php_file_le_stream(), php_file_le_pstream()); + NULL, php_file_le_stream(), php_file_le_pstream()); if (stream == NULL) { do_from_zval_err(ctx, "resource is not a stream or a socket"); return; @@ -1334,25 +1333,26 @@ static void from_zval_write_int_array_aux(zval **elem, unsigned i, void **args, return; } } else { - do_from_zval_err(ctx, "expected an integer or resource variable"); + do_from_zval_err(ctx, "expected a resource variable"); } } -static void from_zval_write_int_array(const zval *arr, char *int_arr, ser_context *ctx) +static void from_zval_write_fd_array(const zval *arr, char *int_arr, ser_context *ctx) { if (Z_TYPE_P(arr) != IS_ARRAY) { do_from_zval_err(ctx, "%s", "expected an array here"); return; } - from_array_iterate(arr, &from_zval_write_int_array_aux, (void**)&int_arr, ctx); + from_array_iterate(arr, &from_zval_write_fd_array_aux, (void**)&int_arr, ctx); } -static void to_zval_read_int_array(const char *data, zval *zv, res_context *ctx) +static void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) { size_t **cmsg_len; int num_elems, i; struct cmsghdr *dummy_cmsg = 0; size_t data_offset; + TSRMLS_FETCH(); data_offset = (unsigned char *)CMSG_DATA(dummy_cmsg) - (unsigned char *)dummy_cmsg; @@ -1373,7 +1373,25 @@ static void to_zval_read_int_array(const char *data, zval *zv, res_context *ctx) array_init_size(zv, num_elems); for (i = 0; i < num_elems; i++) { - add_next_index_long(zv, (long)*((int *)data + i)); + zval *elem; + int fd; + struct stat statbuf; + + MAKE_STD_ZVAL(elem); + + fd = *((int *)data + i); + + /* determine whether we have a socket */ + fstat(fd, &statbuf); + if (S_ISSOCK(statbuf.st_mode)) { + php_socket *sock = socket_import_file_descriptor(fd); + zend_register_resource(elem, sock, php_sockets_le_socket()); + } else { + php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); + php_stream_to_zval(stream, elem); + } + + add_next_index_zval(zv, elem); } } @@ -1500,8 +1518,8 @@ static void init_ancillary_registry(void) PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred, to_zval_read_ucred, SOL_SOCKET, SCM_CREDENTIALS); - PUT_ENTRY(0, sizeof(int), calculate_scm_rights_space, from_zval_write_int_array, - to_zval_read_int_array, SOL_SOCKET, SCM_RIGHTS); + PUT_ENTRY(0, sizeof(int), calculate_scm_rights_space, from_zval_write_fd_array, + to_zval_read_fd_array, SOL_SOCKET, SCM_RIGHTS); } static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) @@ -1676,7 +1694,7 @@ PHP_FUNCTION(socket_cmsg_space) return; } - RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->size)); + RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->var_el_size)); } void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) diff --git a/ext/sockets/tests/socket_cmsg_credentials.phpt b/ext/sockets/tests/socket_cmsg_credentials.phpt new file mode 100644 index 00000000000..6a1c23fa8c5 --- /dev/null +++ b/ext/sockets/tests/socket_cmsg_credentials.phpt @@ -0,0 +1,89 @@ +--TEST-- +recvmsg(): receive SCM_CREDENTIALS messages +--SKIPIF-- + ["test ", "thing", "\n"], +//], 0); +$r = socket_sendto($sends1, $msg = "dread", strlen($msg), 0, $path); +var_dump($r); +checktimeout($s, 500); + +$data = [ + "name" => [], + "buffer_size" => 2000, + "controllen" => socket_cmsg_space(SOL_SOCKET, SCM_CREDENTIALS) +]; +if (!socket_recvmsg($s, $data, 0)) die("recvmsg"); +print_r($data); + +$pid = getmypid(); +var_dump($data['control'][0]['data']['pid'] === $pid); + +--EXPECTF-- +creating send socket +resource(%d) of type (Socket) +creating receive socket +resource(%d) of type (Socket) +bool(true) +int(5) +Array +( + [name] => + [control] => Array + ( + [0] => Array + ( + [level] => %d + [type] => %d + [data] => Array + ( + [pid] => %d + [uid] => %d + [gid] => %d + ) + + ) + + ) + + [iov] => Array + ( + [0] => dread + ) + + [flags] => 0 +) +bool(true) From 74cf40c2fdccdfaed419482d080be4f73fb23a7e Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 15:15:36 +0100 Subject: [PATCH 20/40] Add test for CMSG_RIGHTS --- ext/sockets/tests/socket_cmsg_rights.phpt | 100 ++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 ext/sockets/tests/socket_cmsg_rights.phpt diff --git a/ext/sockets/tests/socket_cmsg_rights.phpt b/ext/sockets/tests/socket_cmsg_rights.phpt new file mode 100644 index 00000000000..8290f03880c --- /dev/null +++ b/ext/sockets/tests/socket_cmsg_rights.phpt @@ -0,0 +1,100 @@ +--TEST-- +recvmsg(): receive SCM_CREDENTIALS messages +--SKIPIF-- + [ "path" => $path ], + "iov" => ["test ", "thing", "\n"], + "control" => [ + [ + "level" => SOL_SOCKET, + "type" => SCM_RIGHTS, + "data" => [$sends1, STDIN, STDOUT, STDERR], + ] + ] +], 0); +var_dump($r); +checktimeout($s, 500); + +$data = [ + "name" => [], + "buffer_size" => 2000, + "controllen" => socket_cmsg_space(SOL_SOCKET, SCM_RIGHTS, 3) +]; +var_dump($data); +if (!socket_recvmsg($s, $data, 0)) die("recvmsg"); +print_r($data); +--EXPECTF-- +creating send socket +resource(%d) of type (Socket) +creating receive socket +resource(%d) of type (Socket) +bool(true) +int(11) +array(3) { + ["name"]=> + array(0) { + } + ["buffer_size"]=> + int(2000) + ["controllen"]=> + int(32) +} +Array +( + [name] => + [control] => Array + ( + [0] => Array + ( + [level] => %d + [type] => %d + [data] => Array + ( + [0] => Resource id #%d + [1] => Resource id #%d + [2] => Resource id #%d + ) + + ) + + ) + + [iov] => Array + ( + [0] => test thing + + ) + + [flags] => 0 +) From 51e65667f5dcb60af24603a543946aa258ac9003 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 16:12:21 +0100 Subject: [PATCH 21/40] Register extra MSG_* constants --- ext/sockets/sockets.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 449be8f9373..37e2e9fe98a 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -718,19 +718,38 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("SOCK_RAW", SOCK_RAW, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_SEQPACKET",SOCK_SEQPACKET, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_RDM", SOCK_RDM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_OOB", MSG_OOB, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_WAITALL", MSG_WAITALL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_CTRUNC", MSG_CTRUNC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_TRUNC", MSG_TRUNC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_PEEK", MSG_PEEK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_DONTROUTE", MSG_DONTROUTE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_EOR", MSG_EOR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MSG_EOF", MSG_EOF, CONST_CS | CONST_PERSISTENT); + +#ifdef MSG_CONFIRM + REGISTER_LONG_CONSTANT("MSG_CONFIRM", MSG_CONFIRM, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef MSG_ERRQUEUE + REGISTER_LONG_CONSTANT("MSG_ERRQUEUE", MSG_ERRQUEUE, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef MSG_NOSIGNAL + REGISTER_LONG_CONSTANT("MSG_NOSIGNAL", MSG_NOSIGNAL, CONST_CS | CONST_PERSISTENT); +#endif #ifdef MSG_DONTWAIT REGISTER_LONG_CONSTANT("MSG_DONTWAIT", MSG_DONTWAIT, CONST_CS | CONST_PERSISTENT); #endif - REGISTER_LONG_CONSTANT("MSG_PEEK", MSG_PEEK, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("MSG_DONTROUTE", MSG_DONTROUTE, CONST_CS | CONST_PERSISTENT); -#ifdef MSG_EOR - REGISTER_LONG_CONSTANT("MSG_EOR", MSG_EOR, CONST_CS | CONST_PERSISTENT); +#ifdef MSG_MORE + REGISTER_LONG_CONSTANT("MSG_MORE", MSG_MORE, CONST_CS | CONST_PERSISTENT); #endif -#ifdef MSG_EOF - REGISTER_LONG_CONSTANT("MSG_EOF", MSG_EOF, CONST_CS | CONST_PERSISTENT); +#ifdef MSG_WAITFORONE + REGISTER_LONG_CONSTANT("MSG_WAITFORONE",MSG_WAITFORONE, CONST_CS | CONST_PERSISTENT); #endif +#ifdef MSG_CMSG_CLOEXEC + REGISTER_LONG_CONSTANT("MSG_CMSG_CLOEXEC",MSG_CMSG_CLOEXEC,CONST_CS | CONST_PERSISTENT); +#endif + REGISTER_LONG_CONSTANT("SO_DEBUG", SO_DEBUG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_REUSEADDR", SO_REUSEADDR, CONST_CS | CONST_PERSISTENT); #ifdef SO_REUSEPORT From 190a0ed71377519425f1b33ef3b21f41064e416b Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 17:10:10 +0100 Subject: [PATCH 22/40] Fix build on Mac OS X By deactivating unsupported features on this OS. --- ext/sockets/sendrecvmsg.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 385c2322336..4436d18e841 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -1252,6 +1252,7 @@ static void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx /* CONVERSIONS for struct in6_pktinfo */ +#ifdef IPV6_PKTINFO static const field_descriptor descriptors_in6_pktinfo[] = { {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_unsigned, to_zval_read_unsigned}, @@ -1267,8 +1268,10 @@ static void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ct to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); } +#endif /* CONVERSIONS for struct ucred */ +#ifdef SO_PASSCRED static const field_descriptor descriptors_ucred[] = { {"pid", sizeof("pid"), 1, offsetof(struct ucred, pid), from_zval_write_pid_t, to_zval_read_pid_t}, {"uid", sizeof("uid"), 1, offsetof(struct ucred, uid), from_zval_write_uid_t, to_zval_read_uid_t}, @@ -1286,8 +1289,10 @@ static void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx) to_zval_read_aggregation(data, zv, descriptors_ucred, ctx); } +#endif /* CONVERSIONS for SCM_RIGHTS */ +#ifdef SCM_RIGHTS static size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) { int num_elems; @@ -1394,6 +1399,7 @@ static void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) add_next_index_zval(zv, elem); } } +#endif /* ENTRY POINT for conversions */ static void free_from_zval_allocation(void *alloc_ptr_ptr) @@ -1506,20 +1512,28 @@ static void init_ancillary_registry(void) zend_hash_update(&ancillary_registry.ht, (char*)&key, sizeof(key), \ (void*)&entry, sizeof(entry), NULL) +#ifdef IPV6_PKTINFO PUT_ENTRY(sizeof(struct in6_pktinfo), 0, 0, from_zval_write_in6_pktinfo, to_zval_read_in6_pktinfo, IPPROTO_IPV6, IPV6_PKTINFO); +#endif +#ifdef IPV6_HOPLIMIT PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT); +#endif PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS); +#ifdef SO_PASSCRED PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred, to_zval_read_ucred, SOL_SOCKET, SCM_CREDENTIALS); +#endif +#ifdef SCM_RIGHTS PUT_ENTRY(0, sizeof(int), calculate_scm_rights_space, from_zval_write_fd_array, to_zval_read_fd_array, SOL_SOCKET, SCM_RIGHTS); +#endif } static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) @@ -1702,8 +1716,14 @@ void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) /* IPv6 ancillary data * Note that support for sticky options via setsockopt() is not implemented * yet (where special support is needed, i.e., the optval is not an int). */ +#ifdef IPV6_RECVPKTINFO REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef IPV6_RECVHOPLIMIT REGISTER_LONG_CONSTANT("IPV6_RECVHOPLIMIT", IPV6_RECVHOPLIMIT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_HOPLIMIT", IPV6_HOPLIMIT, CONST_CS | CONST_PERSISTENT); +#endif /* would require some effort: REGISTER_LONG_CONSTANT("IPV6_RECVRTHDR", IPV6_RECVRTHDR, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_RECVHOPOPTS", IPV6_RECVHOPOPTS, CONST_CS | CONST_PERSISTENT); @@ -1711,8 +1731,6 @@ void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) */ REGISTER_LONG_CONSTANT("IPV6_RECVTCLASS", IPV6_RECVTCLASS, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("IPV6_HOPLIMIT", IPV6_HOPLIMIT, CONST_CS | CONST_PERSISTENT); /* REGISTER_LONG_CONSTANT("IPV6_RTHDR", IPV6_RTHDR, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_HOPOPTS", IPV6_HOPOPTS, CONST_CS | CONST_PERSISTENT); @@ -1720,10 +1738,13 @@ void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) */ REGISTER_LONG_CONSTANT("IPV6_TCLASS", IPV6_TCLASS, CONST_CS | CONST_PERSISTENT); - +#ifdef SCM_RIGHTS REGISTER_LONG_CONSTANT("SCM_RIGHTS", SCM_RIGHTS, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef SO_PASSCRED REGISTER_LONG_CONSTANT("SCM_CREDENTIALS", SCM_CREDENTIALS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SO_PASSCRED", SO_PASSCRED, CONST_CS | CONST_PERSISTENT); +#endif #ifdef ZTS ancillary_mutex = tsrm_mutex_alloc(); From 5bf7b08efd691780f421e0b4f176404fe3a80b2c Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 5 Nov 2012 17:35:46 +0100 Subject: [PATCH 23/40] Check return of fstat() --- ext/sockets/sendrecvmsg.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 4436d18e841..16330e0ded0 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -1387,7 +1387,12 @@ static void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) fd = *((int *)data + i); /* determine whether we have a socket */ - fstat(fd, &statbuf); + if (fstat(fd, &statbuf) == -1) { + do_to_zval_err(ctx, "error creating resource for received file " + "descriptor %d: fstat() call failed with errno %d", fd, errno); + efree(elem); + return; + } if (S_ISSOCK(statbuf.st_mode)) { php_socket *sock = socket_import_file_descriptor(fd); zend_register_resource(elem, sock, php_sockets_le_socket()); From 3e515a2fd93204594c80ad2379f42fbb2db18d78 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 6 Nov 2012 11:25:23 +0100 Subject: [PATCH 24/40] Fix mcast_ipv6_send test --- ext/sockets/tests/mcast_ipv6_send.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/sockets/tests/mcast_ipv6_send.phpt b/ext/sockets/tests/mcast_ipv6_send.phpt index b8d38bf68f4..f75bb09903c 100644 --- a/ext/sockets/tests/mcast_ipv6_send.phpt +++ b/ext/sockets/tests/mcast_ipv6_send.phpt @@ -9,8 +9,8 @@ if (!defined('IPPROTO_IPV6')) { die('skip IPv6 not available.'); } $level = IPPROTO_IPV6; -$s = socket_create($domain, SOCK_DGRAM, SOL_UDP) or die("skip Can not create socket"); -if (socket_set_option($s, $level, IP_MULTICAST_IF, 1) === false) { +$s = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("skip Can not create socket"); +if (socket_set_option($s, $level, IPV6_MULTICAST_IF, 1) === false) { die("skip interface 1 either doesn't exist or has no ipv6 address"); } --FILE-- From 51394f76a5fca718fbf218888d97402f845ee261 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 6 Nov 2012 12:48:47 +0100 Subject: [PATCH 25/40] Move some multicast stuff to multicast.c --- ext/sockets/multicast.c | 362 +++++++++++++++++++++++++++++++++++--- ext/sockets/multicast.h | 10 ++ ext/sockets/php_sockets.h | 6 + ext/sockets/sockets.c | 276 ++--------------------------- 4 files changed, 363 insertions(+), 291 deletions(-) diff --git a/ext/sockets/multicast.c b/ext/sockets/multicast.c index d4a00a8d174..dc242693acc 100644 --- a/ext/sockets/multicast.c +++ b/ext/sockets/multicast.c @@ -54,6 +54,7 @@ #include "php_sockets.h" #include "multicast.h" +#include "sockaddr_conv.h" #include "main/php_network.h" @@ -76,6 +77,309 @@ static const char *_php_source_op_to_string(enum source_op sop); static int _php_source_op_to_ipv4_op(enum source_op sop); #endif +static int php_get_if_index_from_zval(zval *val, unsigned *out TSRMLS_DC) +{ + int ret; + + if (Z_TYPE_P(val) == IS_LONG) { + if (Z_LVAL_P(val) < 0 || Z_LVAL_P(val) > UINT_MAX) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "the interface index cannot be negative or larger than %u;" + " given %ld", UINT_MAX, Z_LVAL_P(val)); + ret = FAILURE; + } else { + *out = Z_LVAL_P(val); + ret = SUCCESS; + } + } else { +#if HAVE_IF_NAMETOINDEX + unsigned int ind; + zval_add_ref(&val); + convert_to_string_ex(&val); + ind = if_nametoindex(Z_STRVAL_P(val)); + if (ind == 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "no interface with name \"%s\" could be found", Z_STRVAL_P(val)); + ret = FAILURE; + } else { + *out = ind; + ret = SUCCESS; + } + zval_ptr_dtor(&val); +#else + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "this platform does not support looking up an interface by " + "name, an integer interface index must be supplied instead"); + ret = FAILURE; +#endif + } + + return ret; +} + +static int php_get_if_index_from_array(const HashTable *ht, const char *key, + php_socket *sock, unsigned int *if_index TSRMLS_DC) +{ + zval **val; + + if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { + *if_index = 0; /* default: 0 */ + return SUCCESS; + } + + return php_get_if_index_from_zval(*val, if_index TSRMLS_CC); +} + +static int php_get_address_from_array(const HashTable *ht, const char *key, + php_socket *sock, php_sockaddr_storage *ss, socklen_t *ss_len TSRMLS_DC) +{ + zval **val, + *valcp; + + if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "no key \"%s\" passed in optval", key); + return FAILURE; + } + valcp = *val; + zval_add_ref(&valcp); + convert_to_string_ex(val); + if (!php_set_inet46_addr(ss, ss_len, Z_STRVAL_P(valcp), sock TSRMLS_CC)) { + zval_ptr_dtor(&valcp); + return FAILURE; + } + zval_ptr_dtor(&valcp); + return SUCCESS; +} + +static int php_do_mcast_opt(php_socket *php_sock, int level, int optname, zval **arg4 TSRMLS_DC) +{ + HashTable *opt_ht; + unsigned int if_index; + int retval; + int (*mcast_req_fun)(php_socket *, int, struct sockaddr *, socklen_t, + unsigned TSRMLS_DC); +#ifdef HAS_MCAST_EXT + int (*mcast_sreq_fun)(php_socket *, int, struct sockaddr *, socklen_t, + struct sockaddr *, socklen_t, unsigned TSRMLS_DC); +#endif + + switch (optname) { + case MCAST_JOIN_GROUP: + mcast_req_fun = &php_mcast_join; + goto mcast_req_fun; + case MCAST_LEAVE_GROUP: + { + php_sockaddr_storage group = {0}; + socklen_t glen; + + mcast_req_fun = &php_mcast_leave; +mcast_req_fun: + convert_to_array_ex(arg4); + opt_ht = HASH_OF(*arg4); + + if (php_get_address_from_array(opt_ht, "group", php_sock, &group, + &glen TSRMLS_CC) == FAILURE) { + return FAILURE; + } + if (php_get_if_index_from_array(opt_ht, "interface", php_sock, + &if_index TSRMLS_CC) == FAILURE) { + return FAILURE; + } + + retval = mcast_req_fun(php_sock, level, (struct sockaddr*)&group, + glen, if_index TSRMLS_CC); + break; + } + +#ifdef HAS_MCAST_EXT + case MCAST_BLOCK_SOURCE: + mcast_sreq_fun = &php_mcast_block_source; + goto mcast_sreq_fun; + case MCAST_UNBLOCK_SOURCE: + mcast_sreq_fun = &php_mcast_unblock_source; + goto mcast_sreq_fun; + case MCAST_JOIN_SOURCE_GROUP: + mcast_sreq_fun = &php_mcast_join_source; + goto mcast_sreq_fun; + case MCAST_LEAVE_SOURCE_GROUP: + { + php_sockaddr_storage group = {0}, + source = {0}; + socklen_t glen, + slen; + + mcast_sreq_fun = &php_mcast_leave_source; + mcast_sreq_fun: + convert_to_array_ex(arg4); + opt_ht = HASH_OF(*arg4); + + if (php_get_address_from_array(opt_ht, "group", php_sock, &group, + &glen TSRMLS_CC) == FAILURE) { + return FAILURE; + } + if (php_get_address_from_array(opt_ht, "source", php_sock, &source, + &slen TSRMLS_CC) == FAILURE) { + return FAILURE; + } + if (php_get_if_index_from_array(opt_ht, "interface", php_sock, + &if_index TSRMLS_CC) == FAILURE) { + return FAILURE; + } + + retval = mcast_sreq_fun(php_sock, level, (struct sockaddr*)&group, + glen, (struct sockaddr*)&source, slen, if_index TSRMLS_CC); + break; + } +#endif + default: + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "unexpected option in php_do_mcast_opt (level %d, option %d). " + "This is a bug.", level, optname); + return FAILURE; + } + + if (retval != 0) { + if (retval != -2) { /* error, but message already emitted */ + PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); + } + return FAILURE; + } + return SUCCESS; +} + +int php_do_setsockopt_ip_mcast(php_socket *php_sock, + int level, + int optname, + zval **arg4) +{ + unsigned int if_index; + struct in_addr if_addr; + void *opt_ptr; + socklen_t optlen; + unsigned char ipv4_mcast_ttl_lback; + int retval; + + switch (optname) { + case MCAST_JOIN_GROUP: + case MCAST_LEAVE_GROUP: +#ifdef HAS_MCAST_EXT + case MCAST_BLOCK_SOURCE: + case MCAST_UNBLOCK_SOURCE: + case MCAST_JOIN_SOURCE_GROUP: + case MCAST_LEAVE_SOURCE_GROUP: +#endif + if (php_do_mcast_opt(php_sock, level, optname, arg4 TSRMLS_CC) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } + + case IP_MULTICAST_IF: + if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { + return FAILURE; + } + + if (php_if_index_to_addr4(if_index, php_sock, &if_addr TSRMLS_CC) == FAILURE) { + return FAILURE; + } + opt_ptr = &if_addr; + optlen = sizeof(if_addr); + goto dosockopt; + + case IP_MULTICAST_LOOP: + convert_to_boolean_ex(arg4); + goto ipv4_loop_ttl; + + case IP_MULTICAST_TTL: + convert_to_long_ex(arg4); + if (Z_LVAL_PP(arg4) < 0L || Z_LVAL_PP(arg4) > 255L) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Expected a value between 0 and 255"); + return FAILURE; + } +ipv4_loop_ttl: + ipv4_mcast_ttl_lback = (unsigned char) Z_LVAL_PP(arg4); + opt_ptr = &ipv4_mcast_ttl_lback; + optlen = sizeof(ipv4_mcast_ttl_lback); + goto dosockopt; + } + + return 1; + +dosockopt: + retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen); + if (retval != 0) { + PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); + return FAILURE; + } + + return SUCCESS; +} + +int php_do_setsockopt_ipv6_mcast(php_socket *php_sock, + int level, + int optname, + zval **arg4) +{ + unsigned int if_index; + void *opt_ptr; + socklen_t optlen; + int ov; + int retval; + + switch (optname) { + case MCAST_JOIN_GROUP: + case MCAST_LEAVE_GROUP: +#ifdef HAS_MCAST_EXT + case MCAST_BLOCK_SOURCE: + case MCAST_UNBLOCK_SOURCE: + case MCAST_JOIN_SOURCE_GROUP: + case MCAST_LEAVE_SOURCE_GROUP: +#endif + if (php_do_mcast_opt(php_sock, level, optname, arg4 TSRMLS_CC) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } + + case IPV6_MULTICAST_IF: + if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { + return FAILURE; + } + + opt_ptr = &if_index; + optlen = sizeof(if_index); + goto dosockopt; + + case IPV6_MULTICAST_LOOP: + convert_to_boolean_ex(arg4); + goto ipv6_loop_hops; + case IPV6_MULTICAST_HOPS: + convert_to_long_ex(arg4); + if (Z_LVAL_PP(arg4) < -1L || Z_LVAL_PP(arg4) > 255L) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Expected a value between -1 and 255"); + return FAILURE; + } +ipv6_loop_hops: + ov = (int) Z_LVAL_PP(arg4); + opt_ptr = &ov; + optlen = sizeof(ov); + goto dosockopt; + } + + return 1; /* not handled */ + +dosockopt: + retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen); + if (retval != 0) { + PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); + return FAILURE; + } + + return SUCCESS; +} + int php_mcast_join( php_socket *sock, int level, @@ -157,21 +461,21 @@ static int _php_mcast_join_leave( { #ifdef RFC3678_API struct group_req greq = {0}; - + memcpy(&greq.gr_group, group, group_len); assert(greq.gr_group.ss_family != 0); /* the caller has set this */ greq.gr_interface = if_index; return setsockopt(sock->bsd_socket, level, join ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP, (char*)&greq, - sizeof(greq)); + sizeof(greq)); #else if (sock->type == AF_INET) { struct ip_mreq mreq = {0}; struct in_addr addr; - + assert(group_len == sizeof(struct sockaddr_in)); - + if (if_index != 0) { if (php_if_index_to_addr4(if_index, sock, &addr TSRMLS_CC) == FAILURE) @@ -188,12 +492,12 @@ static int _php_mcast_join_leave( #if HAVE_IPV6 else if (sock->type == AF_INET6) { struct ipv6_mreq mreq = {0}; - + assert(group_len == sizeof(struct sockaddr_in6)); mreq.ipv6mr_multiaddr = ((struct sockaddr_in6*)group)->sin6_addr; mreq.ipv6mr_interface = if_index; - + return setsockopt(sock->bsd_socket, level, join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, (char*)&mreq, sizeof(mreq)); @@ -221,26 +525,26 @@ static int _php_mcast_source_op( { #ifdef RFC3678_API struct group_source_req gsreq = {0}; - + memcpy(&gsreq.gsr_group, group, group_len); assert(gsreq.gsr_group.ss_family != 0); memcpy(&gsreq.gsr_source, source, source_len); assert(gsreq.gsr_source.ss_family != 0); gsreq.gsr_interface = if_index; - + return setsockopt(sock->bsd_socket, level, _php_source_op_to_rfc3678_op(sop), (char*)&gsreq, sizeof(gsreq)); #else if (sock->type == AF_INET) { struct ip_mreq_source mreqs = {0}; struct in_addr addr; - + mreqs.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr; mreqs.imr_sourceaddr = ((struct sockaddr_in*)source)->sin_addr; - + assert(group_len == sizeof(struct sockaddr_in)); assert(source_len == sizeof(struct sockaddr_in)); - + if (if_index != 0) { if (php_if_index_to_addr4(if_index, sock, &addr TSRMLS_CC) == FAILURE) @@ -249,7 +553,7 @@ static int _php_mcast_source_op( } else { mreqs.imr_interface.s_addr = htonl(INADDR_ANY); } - + return setsockopt(sock->bsd_socket, level, _php_source_op_to_ipv4_op(sop), (char*)&mreqs, sizeof(mreqs)); } @@ -283,7 +587,7 @@ static int _php_source_op_to_rfc3678_op(enum source_op sop) case UNBLOCK_SOURCE: return MCAST_UNBLOCK_SOURCE; } - + assert(0); return 0; } @@ -300,7 +604,7 @@ static const char *_php_source_op_to_string(enum source_op sop) case UNBLOCK_SOURCE: return "MCAST_UNBLOCK_SOURCE"; } - + assert(0); return ""; } @@ -317,7 +621,7 @@ static int _php_source_op_to_ipv4_op(enum source_op sop) case UNBLOCK_SOURCE: return IP_UNBLOCK_SOURCE; } - + assert(0); return 0; } @@ -416,16 +720,16 @@ retry: int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_addr *out_addr TSRMLS_DC) { struct ifreq if_req; - + if (if_index == 0) { out_addr->s_addr = INADDR_ANY; return SUCCESS; } - + #if !defined(ifr_ifindex) && defined(ifr_index) #define ifr_ifindex ifr_index #endif - + #if defined(SIOCGIFNAME) if_req.ifr_ifindex = if_index; if (ioctl(php_sock->bsd_socket, SIOCGIFNAME, &if_req) == -1) { @@ -438,13 +742,13 @@ int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_add "Failed obtaining address for interface %u: error %d", if_index, errno); return FAILURE; } - + if (ioctl(php_sock->bsd_socket, SIOCGIFADDR, &if_req) == -1) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed obtaining address for interface %u: error %d", if_index, errno); return FAILURE; } - + memcpy(out_addr, &((struct sockaddr_in *) &if_req.ifr_addr)->sin_addr, sizeof *out_addr); return SUCCESS; @@ -458,25 +762,25 @@ int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *i int size = 0, lastsize = 0; size_t entry_len; - + if (addr->s_addr == INADDR_ANY) { *if_index = 0; return SUCCESS; } - + for(;;) { size += 5 * sizeof(struct ifreq); buf = ecalloc(size, 1); if_conf.ifc_len = size; if_conf.ifc_buf = buf; - + if (ioctl(php_sock->bsd_socket, SIOCGIFCONF, (char*)&if_conf) == -1 && (errno != EINVAL || lastsize != 0)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed obtaining interfaces list: error %d", errno); goto err; } - + if (if_conf.ifc_len == lastsize) /* not increasing anymore */ break; @@ -486,15 +790,15 @@ int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *i buf = NULL; } } - + for (p = if_conf.ifc_buf; p < if_conf.ifc_buf + if_conf.ifc_len; p += entry_len) { struct ifreq *cur_req; - + /* let's hope the pointer is aligned */ cur_req = (struct ifreq*) p; - + #ifdef HAVE_SOCKADDR_SA_LEN entry_len = cur_req->ifr_addr.sa_len + sizeof(cur_req->ifr_name); #else @@ -502,7 +806,7 @@ int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *i entry_len = sizeof(struct sockaddr) + sizeof(cur_req->ifr_name); #endif entry_len = MAX(entry_len, sizeof(*cur_req)); - + if ((((struct sockaddr*)&cur_req->ifr_addr)->sa_family == AF_INET) && (((struct sockaddr_in*)&cur_req->ifr_addr)->sin_addr.s_addr == addr->s_addr)) { @@ -537,7 +841,7 @@ int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *i php_error_docref(NULL TSRMLS_CC, E_WARNING, "The interface with IP address %s was not found", addr_str); } - + err: if (buf != NULL) efree(buf); diff --git a/ext/sockets/multicast.h b/ext/sockets/multicast.h index 498a71f67a1..c363b584727 100644 --- a/ext/sockets/multicast.h +++ b/ext/sockets/multicast.h @@ -27,6 +27,16 @@ #define HAS_MCAST_EXT 1 #endif +int php_do_setsockopt_ip_mcast(php_socket *php_sock, + int level, + int optname, + zval **arg4); + +int php_do_setsockopt_ipv6_mcast(php_socket *php_sock, + int level, + int optname, + zval **arg4); + int php_if_index_to_addr4( unsigned if_index, php_socket *php_sock, diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 3138eb60c45..78da0c29e68 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -87,6 +87,12 @@ ZEND_END_MODULE_GLOBALS(sockets) ZEND_EXTERN_MODULE_GLOBALS(sockets); +enum sockopt_return { + SOCKOPT_ERROR, + SOCKOPT_CONTINUE, + SOCKOPT_SUCCESS +}; + char *sockets_strerror(int error TSRMLS_DC); php_socket *socket_import_file_descriptor(PHP_SOCKET sock TSRMLS_DC); diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 37e2e9fe98a..9f115944259 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -619,81 +619,6 @@ char *sockets_strerror(int error TSRMLS_DC) /* {{{ */ } /* }}} */ - -static int php_get_if_index_from_zval(zval *val, unsigned *out TSRMLS_DC) -{ - int ret; - - if (Z_TYPE_P(val) == IS_LONG) { - if (Z_LVAL_P(val) < 0 || Z_LVAL_P(val) > UINT_MAX) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "the interface index cannot be negative or larger than %u;" - " given %ld", UINT_MAX, Z_LVAL_P(val)); - ret = FAILURE; - } else { - *out = Z_LVAL_P(val); - ret = SUCCESS; - } - } else { -#if HAVE_IF_NAMETOINDEX - unsigned int ind; - zval_add_ref(&val); - convert_to_string_ex(&val); - ind = if_nametoindex(Z_STRVAL_P(val)); - if (ind == 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "no interface with name \"%s\" could be found", Z_STRVAL_P(val)); - ret = FAILURE; - } else { - *out = ind; - ret = SUCCESS; - } - zval_ptr_dtor(&val); -#else - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "this platform does not support looking up an interface by " - "name, an integer interface index must be supplied instead"); - ret = FAILURE; -#endif - } - - return ret; -} - -static int php_get_if_index_from_array(const HashTable *ht, const char *key, - php_socket *sock, unsigned int *if_index TSRMLS_DC) -{ - zval **val; - - if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { - *if_index = 0; /* default: 0 */ - return SUCCESS; - } - - return php_get_if_index_from_zval(*val, if_index TSRMLS_CC); -} - -static int php_get_address_from_array(const HashTable *ht, const char *key, - php_socket *sock, php_sockaddr_storage *ss, socklen_t *ss_len TSRMLS_DC) -{ - zval **val, - *valcp; - - if (zend_hash_find(ht, key, strlen(key) + 1, (void **)&val) == FAILURE) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "no key \"%s\" passed in optval", key); - return FAILURE; - } - valcp = *val; - zval_add_ref(&valcp); - convert_to_string_ex(val); - if (!php_set_inet46_addr(ss, ss_len, Z_STRVAL_P(valcp), sock TSRMLS_CC)) { - zval_ptr_dtor(&valcp); - return FAILURE; - } - zval_ptr_dtor(&valcp); - return SUCCESS; -} - /* {{{ PHP_GINIT_FUNCTION */ static PHP_GINIT_FUNCTION(sockets) { @@ -2012,102 +1937,6 @@ PHP_FUNCTION(socket_get_option) } /* }}} */ -static int php_do_mcast_opt(php_socket *php_sock, int level, int optname, zval **arg4 TSRMLS_DC) -{ - HashTable *opt_ht; - unsigned int if_index; - int retval; - int (*mcast_req_fun)(php_socket *, int, struct sockaddr *, socklen_t, - unsigned TSRMLS_DC); -#ifdef HAS_MCAST_EXT - int (*mcast_sreq_fun)(php_socket *, int, struct sockaddr *, socklen_t, - struct sockaddr *, socklen_t, unsigned TSRMLS_DC); -#endif - - switch (optname) { - case MCAST_JOIN_GROUP: - mcast_req_fun = &php_mcast_join; - goto mcast_req_fun; - case MCAST_LEAVE_GROUP: - { - php_sockaddr_storage group = {0}; - socklen_t glen; - - mcast_req_fun = &php_mcast_leave; -mcast_req_fun: - convert_to_array_ex(arg4); - opt_ht = HASH_OF(*arg4); - - if (php_get_address_from_array(opt_ht, "group", php_sock, &group, - &glen TSRMLS_CC) == FAILURE) { - return FAILURE; - } - if (php_get_if_index_from_array(opt_ht, "interface", php_sock, - &if_index TSRMLS_CC) == FAILURE) { - return FAILURE; - } - - retval = mcast_req_fun(php_sock, level, (struct sockaddr*)&group, - glen, if_index TSRMLS_CC); - break; - } - -#ifdef HAS_MCAST_EXT - case MCAST_BLOCK_SOURCE: - mcast_sreq_fun = &php_mcast_block_source; - goto mcast_sreq_fun; - case MCAST_UNBLOCK_SOURCE: - mcast_sreq_fun = &php_mcast_unblock_source; - goto mcast_sreq_fun; - case MCAST_JOIN_SOURCE_GROUP: - mcast_sreq_fun = &php_mcast_join_source; - goto mcast_sreq_fun; - case MCAST_LEAVE_SOURCE_GROUP: - { - php_sockaddr_storage group = {0}, - source = {0}; - socklen_t glen, - slen; - - mcast_sreq_fun = &php_mcast_leave_source; - mcast_sreq_fun: - convert_to_array_ex(arg4); - opt_ht = HASH_OF(*arg4); - - if (php_get_address_from_array(opt_ht, "group", php_sock, &group, - &glen TSRMLS_CC) == FAILURE) { - return FAILURE; - } - if (php_get_address_from_array(opt_ht, "source", php_sock, &source, - &slen TSRMLS_CC) == FAILURE) { - return FAILURE; - } - if (php_get_if_index_from_array(opt_ht, "interface", php_sock, - &if_index TSRMLS_CC) == FAILURE) { - return FAILURE; - } - - retval = mcast_sreq_fun(php_sock, level, (struct sockaddr*)&group, - glen, (struct sockaddr*)&source, slen, if_index TSRMLS_CC); - break; - } -#endif - default: - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "unexpected option in php_do_mcast_opt (level %d, option %d). " - "This is a bug.", level, optname); - return FAILURE; - } - - if (retval != 0) { - if (retval != -2) { /* error, but message already emitted */ - PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); - } - return FAILURE; - } - return SUCCESS; -} - /* {{{ proto bool socket_set_option(resource socket, int level, int optname, int|array optval) Sets socket options for the socket */ PHP_FUNCTION(socket_set_option) @@ -2127,10 +1956,6 @@ PHP_FUNCTION(socket_set_option) zval **l_onoff, **l_linger; zval **sec, **usec; - /* Multicast */ - unsigned int if_index; - struct in_addr if_addr; - unsigned char ipv4_mcast_ttl_lback; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rllZ", &arg1, &level, &optname, &arg4) == FAILURE) { return; @@ -2140,94 +1965,23 @@ PHP_FUNCTION(socket_set_option) set_errno(0); +#define HANDLE_SUBCALL(res) \ + do { \ + if (res == 1) { goto default_case; } \ + else if (res == SUCCESS) { RETURN_TRUE; } \ + else { RETURN_FALSE; } \ + } while (0) + + if (level == IPPROTO_IP) { - switch (optname) { - case MCAST_JOIN_GROUP: - case MCAST_LEAVE_GROUP: -#ifdef HAS_MCAST_EXT - case MCAST_BLOCK_SOURCE: - case MCAST_UNBLOCK_SOURCE: - case MCAST_JOIN_SOURCE_GROUP: - case MCAST_LEAVE_SOURCE_GROUP: -#endif - if (php_do_mcast_opt(php_sock, level, optname, arg4 TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } else { - RETURN_TRUE; - } - - case IP_MULTICAST_IF: - if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } - - if (php_if_index_to_addr4(if_index, php_sock, &if_addr TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } - opt_ptr = &if_addr; - optlen = sizeof(if_addr); - goto dosockopt; - - case IP_MULTICAST_LOOP: - convert_to_boolean_ex(arg4); - goto ipv4_loop_ttl; - case IP_MULTICAST_TTL: - convert_to_long_ex(arg4); - if (Z_LVAL_PP(arg4) < 0L || Z_LVAL_PP(arg4) > 255L) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Expected a value between 0 and 255"); - RETURN_FALSE; - } -ipv4_loop_ttl: - ipv4_mcast_ttl_lback = (unsigned char) Z_LVAL_PP(arg4); - opt_ptr = &ipv4_mcast_ttl_lback; - optlen = sizeof(ipv4_mcast_ttl_lback); - goto dosockopt; - } + int res = php_do_setsockopt_ip_mcast(php_sock, level, optname, arg4); + HANDLE_SUBCALL(res); } #if HAVE_IPV6 else if (level == IPPROTO_IPV6) { - switch (optname) { - case MCAST_JOIN_GROUP: - case MCAST_LEAVE_GROUP: -#ifdef HAS_MCAST_EXT - case MCAST_BLOCK_SOURCE: - case MCAST_UNBLOCK_SOURCE: - case MCAST_JOIN_SOURCE_GROUP: - case MCAST_LEAVE_SOURCE_GROUP: -#endif - if (php_do_mcast_opt(php_sock, level, optname, arg4 TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } else { - RETURN_TRUE; - } - - case IPV6_MULTICAST_IF: - if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } - - opt_ptr = &if_index; - optlen = sizeof(if_index); - goto dosockopt; - - case IPV6_MULTICAST_LOOP: - convert_to_boolean_ex(arg4); - goto ipv6_loop_hops; - case IPV6_MULTICAST_HOPS: - convert_to_long_ex(arg4); - if (Z_LVAL_PP(arg4) < -1L || Z_LVAL_PP(arg4) > 255L) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Expected a value between -1 and 255"); - RETURN_FALSE; - } -ipv6_loop_hops: - ov = (int) Z_LVAL_PP(arg4); - opt_ptr = &ov; - optlen = sizeof(ov); - goto dosockopt; - } + int res = php_do_setsockopt_ipv6_mcast(php_sock, level, optname, arg4); + HANDLE_SUBCALL(res); } #endif @@ -2292,6 +2046,7 @@ ipv6_loop_hops: } default: +default_case: convert_to_long_ex(arg4); ov = Z_LVAL_PP(arg4); @@ -2300,12 +2055,9 @@ ipv6_loop_hops: break; } -dosockopt: retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen); if (retval != 0) { - if (retval != -2) { /* error, but message already emitted */ - PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); - } + PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); RETURN_FALSE; } From 8fb1aa618453149bb876bda4cafd1860468c4443 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 6 Nov 2012 13:36:40 +0100 Subject: [PATCH 26/40] Destroy ancillary registry on shutdown --- ext/sockets/sendrecvmsg.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 16330e0ded0..88b937f8285 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -1540,6 +1540,13 @@ static void init_ancillary_registry(void) to_zval_read_fd_array, SOL_SOCKET, SCM_RIGHTS); #endif +} +static void destroy_ancillary_registry(void) +{ + if (ancillary_registry.initialized) { + zend_hash_destroy(&ancillary_registry.ht); + ancillary_registry.initialized = 0; + } } static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) { @@ -1761,4 +1768,6 @@ void _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS) #ifdef ZTS tsrm_mutex_free(ancillary_mutex); #endif + + destroy_ancillary_registry(); } From b18bd8904e41941db204ac6b2bf4cf43421e8838 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 6 Nov 2012 13:38:57 +0100 Subject: [PATCH 27/40] Rename some functions for consistency --- ext/sockets/sendrecvmsg.c | 4 ++-- ext/sockets/sendrecvmsg.h | 4 ++-- ext/sockets/sockets.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 88b937f8285..201adbda433 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -1723,7 +1723,7 @@ PHP_FUNCTION(socket_cmsg_space) RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->var_el_size)); } -void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) +void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS) { /* IPv6 ancillary data * Note that support for sticky options via setsockopt() is not implemented @@ -1763,7 +1763,7 @@ void _socket_sendrecvmsg_init(INIT_FUNC_ARGS) #endif } -void _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS) +void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS) { #ifdef ZTS tsrm_mutex_free(ancillary_mutex); diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h index 82dc456e655..929a6ad9cfe 100644 --- a/ext/sockets/sendrecvmsg.h +++ b/ext/sockets/sendrecvmsg.h @@ -4,5 +4,5 @@ PHP_FUNCTION(socket_sendmsg); PHP_FUNCTION(socket_recvmsg); PHP_FUNCTION(socket_cmsg_space); -void _socket_sendrecvmsg_init(INIT_FUNC_ARGS); -void _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); +void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS); +void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 9f115944259..1d86028691b 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -751,7 +751,7 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("IPV6_UNICAST_HOPS", IPV6_UNICAST_HOPS, CONST_CS | CONST_PERSISTENT); #endif - _socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU); + php_socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU); return SUCCESS; } @@ -774,7 +774,7 @@ PHP_RSHUTDOWN_FUNCTION(sockets) efree(SOCKETS_G(strerror_buf)); SOCKETS_G(strerror_buf) = NULL; } - _socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS_PASSTHRU); + php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS_PASSTHRU); return SUCCESS; } From 66ea02458746a4853ff6190c6c75da5e95677911 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 6 Nov 2012 17:27:08 +0100 Subject: [PATCH 28/40] Support sticky IPV6_PKTINFO --- ext/sockets/sendrecvmsg.c | 90 ++++++++++++++++++- ext/sockets/sendrecvmsg.h | 3 + ext/sockets/sockets.c | 10 +++ .../tests/socket_set_option_in6_pktinfo.phpt | 31 +++++++ 4 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 ext/sockets/tests/socket_set_option_in6_pktinfo.phpt diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 201adbda433..6b1a528c5b1 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -77,6 +77,7 @@ struct key_value { #define KEY_FILL_SOCKADDR "fill_sockaddr" #define KEY_RECVMSG_RET "recvmsg_ret" #define KEY_CMSG_LEN "cmsg_len" +static const struct key_value empty_key_value_list[] = {{0}}; typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); @@ -222,6 +223,12 @@ static void err_msg_dispose(struct err_s *err TSRMLS_DC) } } } +static void allocations_dispose(zend_llist **allocations) +{ + zend_llist_destroy(*allocations); + efree(*allocations); + *allocations = NULL; +} static unsigned from_array_iterate(const zval *arr, void (*func)(zval **elem, unsigned i, void **args, ser_context *ctx), @@ -1660,8 +1667,7 @@ PHP_FUNCTION(socket_recvmsg) /* we don;t need msghdr anymore; free it */ msghdr = NULL; - zend_llist_destroy(allocations); - efree(allocations); + allocations_dispose(&allocations); zval_dtor(zmsg); if (!err.has_error) { @@ -1723,6 +1729,86 @@ PHP_FUNCTION(socket_cmsg_space) RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->var_el_size)); } +int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4) +{ + struct err_s err = {0}; + zend_llist *allocations = NULL; + void *opt_ptr; + socklen_t optlen; + int retval; + + assert(level == IPPROTO_IPV6); + + switch (optname) { +#ifdef IPV6_PKTINFO + case IPV6_PKTINFO: + opt_ptr = from_zval_run_conversions(*arg4, php_sock, from_zval_write_in6_pktinfo, + sizeof(struct in6_pktinfo), "in6_pktinfo", &allocations, &err); + if (err.has_error) { + err_msg_dispose(&err TSRMLS_CC); + return FAILURE; + } + + optlen = sizeof(struct in6_pktinfo); + goto dosockopt; +#endif + } + + /* we also support IPV6_TCLASS, but that can be handled by the default + * integer optval handling in the caller */ + return 1; + +dosockopt: + retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen); + if (retval != 0) { + PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); + } + allocations_dispose(&allocations); + + return retval != 0 ? FAILURE : SUCCESS; +} + +int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result) +{ + struct err_s err = {0}; + void *buffer; + socklen_t size; + int res; + to_zval_read_field *reader; + + assert(level == IPPROTO_IPV6); + + switch (optname) { +#ifdef IPV6_PKTINFO + case IPV6_PKTINFO: + size = sizeof(struct in6_pktinfo); + reader = &to_zval_read_in6_pktinfo; + break; +#endif + default: + return 1; + } + + buffer = ecalloc(1, size); + res = getsockopt(php_sock->bsd_socket, level, optname, buffer, &size); + if (res != 0) { + PHP_SOCKET_ERROR(php_sock, "unable to get socket option", errno); + } else { + zval *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo", + empty_key_value_list, &err); + if (err.has_error) { + err_msg_dispose(&err); + res = -1; + } else { + ZVAL_COPY_VALUE(result, zv); + efree(zv); + } + } + efree(buffer); + + return res == 0 ? SUCCESS : FAILURE; +} + void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS) { /* IPv6 ancillary data diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h index 929a6ad9cfe..82fb38b4b5f 100644 --- a/ext/sockets/sendrecvmsg.h +++ b/ext/sockets/sendrecvmsg.h @@ -6,3 +6,6 @@ PHP_FUNCTION(socket_cmsg_space); void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS); void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); + +int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4); +int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result); diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 1d86028691b..9c57e2d98d2 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1877,6 +1877,13 @@ PHP_FUNCTION(socket_get_option) } } } + } else if (level == IPPROTO_IPV6) { + int ret = php_do_getsockopt_ipv6_rfc3542(php_sock, level, optname, return_value); + if (ret == SUCCESS) { + return; + } else if (ret == FAILURE) { + RETURN_FALSE; + } /* else continue */ } /* sol_socket options and general case */ @@ -1981,6 +1988,9 @@ PHP_FUNCTION(socket_set_option) #if HAVE_IPV6 else if (level == IPPROTO_IPV6) { int res = php_do_setsockopt_ipv6_mcast(php_sock, level, optname, arg4); + if (res == 1) { + res = php_do_setsockopt_ipv6_rfc3542(php_sock, level, optname, arg4); + } HANDLE_SUBCALL(res); } #endif diff --git a/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt b/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt new file mode 100644 index 00000000000..53320cad0c3 --- /dev/null +++ b/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt @@ -0,0 +1,31 @@ +--TEST-- +socket_set_option() with IPV6_PKTINFO +--SKIPIF-- + '::1', + "ifindex" => 0 +])); +//Oddly, Linux does not support IPV6_PKTINFO in sockgetopt(). +//See do_ipv6_getsockopt() on the kernel sources +//A work-around with is sort-of possible (with IPV6_2292PKTOPTIONS), +//but not worth it +//var_dump(socket_get_option($s, IPPROTO_IPV6, IPV6_PKTINFO)); + +--EXPECTF-- +Warning: socket_set_option(): error converting user data (path: in6_pktinfo): The key 'addr' is required in %s on line %d +bool(false) +bool(true) + From 4414b33abd087bba26cb2cbdc2bf05938d5a6690 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 12 Nov 2012 00:40:38 +0100 Subject: [PATCH 29/40] Refactoring: move stuff to new conversions.c --- ext/sockets/config.m4 | 2 +- ext/sockets/conversions.c | 1477 +++++++++++++++++++++++++++++++++++++ ext/sockets/conversions.h | 78 ++ ext/sockets/sendrecvmsg.c | 1461 +----------------------------------- ext/sockets/sendrecvmsg.h | 27 +- 5 files changed, 1584 insertions(+), 1461 deletions(-) create mode 100644 ext/sockets/conversions.c create mode 100644 ext/sockets/conversions.h diff --git a/ext/sockets/config.m4 b/ext/sockets/config.m4 index 3b5b90714b7..9c752496463 100644 --- a/ext/sockets/config.m4 +++ b/ext/sockets/config.m4 @@ -43,6 +43,6 @@ if test "$PHP_SOCKETS" != "no"; then AC_DEFINE(HAVE_SA_SS_FAMILY,1,[Whether you have sockaddr_storage.ss_family]) fi - PHP_NEW_EXTENSION([sockets], [sockets.c multicast.c sockaddr_conv.c sendrecvmsg.c], [$ext_shared]) + PHP_NEW_EXTENSION([sockets], [sockets.c multicast.c conversions.c sockaddr_conv.c sendrecvmsg.c], [$ext_shared]) PHP_INSTALL_HEADERS([ext/sockets/], [php_sockets.h]) fi diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c new file mode 100644 index 00000000000..7ca9972ac0d --- /dev/null +++ b/ext/sockets/conversions.c @@ -0,0 +1,1477 @@ +#include "conversions.h" +#include "sockaddr_conv.h" +#include "conversions.h" +#include "sendrecvmsg.h" /* for ancillary registry */ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024)) +#define DEFAULT_BUFF_SIZE 8192 + +struct _ser_context { + HashTable params; /* stores pointers; has to be first */ + struct err_s err; + zend_llist keys, + /* common part to res_context ends here */ + allocations; + php_socket *sock; +}; +struct _res_context { + HashTable params; /* stores pointers; has to be first */ + struct err_s err; + zend_llist keys; +}; + +typedef struct { + /* zval info */ + const char *name; + unsigned name_size; + int required; + + /* structure info */ + size_t field_offset; /* 0 to pass full structure, e.g. when more than + one field is to be changed; in that case the + callbacks need to know the name of the fields */ + + /* callbacks */ + from_zval_write_field *from_zval; + to_zval_read_field *to_zval; +} field_descriptor; + +#define KEY_FILL_SOCKADDR "fill_sockaddr" +#define KEY_RECVMSG_RET "recvmsg_ret" +#define KEY_CMSG_LEN "cmsg_len" + +/* PARAMETERS */ +static int param_get_bool(void *ctx, const char *key, int def) +{ + int **elem; + if (zend_hash_find(ctx, key, strlen(key) + 1, (void**)&elem) == SUCCESS) { + return **elem; + } else { + return def; + } +} + +/* MEMORY */ +static inline void *accounted_emalloc(size_t alloc_size, ser_context *ctx) +{ + void *ret = emalloc(alloc_size); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} +static inline void *accounted_ecalloc(size_t nmemb, size_t alloc_size, ser_context *ctx) +{ + void *ret = ecalloc(nmemb, alloc_size); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} +static inline void *accounted_safe_ecalloc(size_t nmemb, size_t alloc_size, size_t offset, ser_context *ctx) +{ + void *ret = safe_emalloc(nmemb, alloc_size, offset); + memset(ret, '\0', nmemb * alloc_size + offset); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} + +/* ERRORS */ +static void do_from_to_zval_err(struct err_s *err, + zend_llist *keys, + const char *what_conv, + const char *fmt, + va_list ap) +{ + smart_str path = {0}; + const char **node; + char *user_msg; + int user_msg_size; + zend_llist_position pos; + + if (err->has_error) { + return; + } + + for (node = zend_llist_get_first_ex(keys, &pos); + node != NULL; + node = zend_llist_get_next_ex(keys, &pos)) { + smart_str_appends(&path, *node); + smart_str_appends(&path, " > "); + } + + if (path.len > 3) { + path.len -= 3; + } + smart_str_0(&path); + + user_msg_size = vspprintf(&user_msg, 0, fmt, ap); + + err->has_error = 1; + err->level = E_WARNING; + spprintf(&err->msg, 0, "error converting %s data (path: %s): %.*s", + what_conv, + path.c && path.c != '\0' ? path.c : "unavailable", + user_msg_size, user_msg); + err->should_free = 1; + + efree(user_msg); + smart_str_free_ex(&path, 0); +} +__attribute__ ((format (printf, 2, 3))) +static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); + va_end(ap); +} +__attribute__ ((format (printf, 2, 3))) +static void do_to_zval_err(res_context *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "native", fmt, ap); + va_end(ap); +} + +void err_msg_dispose(struct err_s *err TSRMLS_DC) +{ + if (err->msg != NULL) { + php_error_docref0(NULL TSRMLS_CC, err->level, "%s", err->msg); + if (err->should_free) { + efree(err->msg); + } + } +} +void allocations_dispose(zend_llist **allocations) +{ + zend_llist_destroy(*allocations); + efree(*allocations); + *allocations = NULL; +} + +static unsigned from_array_iterate(const zval *arr, + void (*func)(zval **elem, unsigned i, void **args, ser_context *ctx), + void **args, + ser_context *ctx) +{ + HashPosition pos; + unsigned i; + zval **elem; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 1; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos), i++) { + if (snprintf(buf, sizeof(buf), "element #%u", i) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + func(elem, i, args, ctx); + + zend_llist_remove_tail(&ctx->keys); + } + + return i -1; +} + +/* Generic Aggregated conversions */ +static void from_zval_write_aggregation(const zval *container, + char *structure, + const field_descriptor *descriptors, + ser_context *ctx) +{ + const field_descriptor *descr; + zval **elem; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + } + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + if (zend_hash_find(Z_ARRVAL_P(container), + descr->name, descr->name_size, (void**)&elem) == SUCCESS) { + + if (descr->from_zval == NULL) { + do_from_zval_err(ctx, "No information on how to convert value " + "of key '%s'", descr->name); + break; + } + + zend_llist_add_element(&ctx->keys, (void*)&descr->name); + descr->from_zval(*elem, ((char*)structure) + descr->field_offset, ctx); + zend_llist_remove_tail(&ctx->keys); + + } else if (descr->required) { + do_from_zval_err(ctx, "The key '%s' is required", descr->name); + break; + } + } +} +static void to_zval_read_aggregation(const char *structure, + zval *zarr, /* initialized array */ + const field_descriptor *descriptors, + res_context *ctx) +{ + const field_descriptor *descr; + + assert(Z_TYPE_P(zarr) == IS_ARRAY); + assert(Z_ARRVAL_P(zarr) != NULL); + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + zval *new_zv; + + if (descr->to_zval == NULL) { + do_to_zval_err(ctx, "No information on how to convert native " + "field into value for key '%s'", descr->name); + break; + } + + ALLOC_INIT_ZVAL(new_zv); + add_assoc_zval_ex(zarr, descr->name, descr->name_size, new_zv); + + zend_llist_add_element(&ctx->keys, (void*)&descr->name); + descr->to_zval(structure + descr->field_offset, new_zv, ctx); + zend_llist_remove_tail(&ctx->keys); + } +} + +/* CONVERSIONS for integers */ +static long from_zval_integer_common(const zval *arr_value, ser_context *ctx) +{ + long ret = 0; + zval lzval = zval_used_for_init; + + if (Z_TYPE_P(arr_value) != IS_LONG) { + ZVAL_COPY_VALUE(&lzval, arr_value); + zval_copy_ctor(&lzval); + arr_value = &lzval; + } + + switch (Z_TYPE_P(arr_value)) { + case IS_LONG: +long_case: + ret = Z_LVAL_P(arr_value); + break; + + /* if not long we're operating on lzval */ + case IS_DOUBLE: +double_case: + convert_to_long(&lzval); + goto long_case; + + case IS_OBJECT: + case IS_STRING: { + long lval; + double dval; + + convert_to_string(&lzval); + + switch (is_numeric_string(Z_STRVAL(lzval), Z_STRLEN(lzval), &lval, &dval, 0)) { + case IS_DOUBLE: + zval_dtor(&lzval); + Z_TYPE(lzval) = IS_DOUBLE; + Z_DVAL(lzval) = dval; + goto double_case; + + case IS_LONG: + zval_dtor(&lzval); + Z_TYPE(lzval) = IS_LONG; + Z_DVAL(lzval) = lval; + goto long_case; + } + + /* if we get here, we don't have a numeric string */ + do_from_zval_err(ctx, "expected an integer, but got a non numeric " + "string (possibly from a converted object): '%s'", Z_STRVAL_P(arr_value)); + break; + } + + default: + do_from_zval_err(ctx, "%s", "expected an integer, either of a PHP " + "integer type or of a convertible type"); + break; + } + + zval_dtor(&lzval); + + return ret; +} +void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + int ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval > INT_MAX || lval < INT_MIN) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a native int"); + return; + } + + ival = (int)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_unsigned(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + unsigned ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (sizeof(long) > sizeof(ival) && (lval < 0 || lval > UINT_MAX)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a native unsigned int"); + return; + } + + ival = (unsigned)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_uint32(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint32_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (sizeof(long) > sizeof(uint32_t) && (lval < 0 || lval > 0xFFFFFFFF)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 32-bit integer"); + return; + } + + ival = (uint32_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint16_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > 0xFFFF) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 16-bit integer"); + return; + } + + ival = htons((uint16_t)lval); + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_sa_family(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + sa_family_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > (sa_family_t)-1) { /* sa_family_t is unsigned */ + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a sa_family_t value"); + return; + } + + ival = (sa_family_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_pid_t(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + pid_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || (pid_t)lval != lval) { /* pid_t is signed */ + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a pid_t value"); + return; + } + + ival = (pid_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uid_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + /* uid_t can be signed or unsigned (generally unsigned) */ + if ((uid_t)-1 > (uid_t)0) { + if (sizeof(long) > sizeof(uid_t) && (lval < 0 || (uid_t)lval != lval)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a uid_t value"); + return; + } + } else { + if (sizeof(long) > sizeof(uid_t) && (uid_t)lval != lval) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a uid_t value"); + return; + } + } + + ival = (uid_t)lval; + memcpy(field, &ival, sizeof(ival)); +} + +void to_zval_read_int(const char *data, zval *zv, res_context *ctx) +{ + int ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) +{ + unsigned ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) +{ + uint16_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ntohs(ival)); +} +static void to_zval_read_uint32(const char *data, zval *zv, res_context *ctx) +{ + uint32_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_sa_family(const char *data, zval *zv, res_context *ctx) +{ + sa_family_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_pid_t(const char *data, zval *zv, res_context *ctx) +{ + pid_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_uid_t(const char *data, zval *zv, res_context *ctx) +{ + uid_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} + +/* CONVERSIONS for sockaddr */ +static void from_zval_write_sin_addr(const zval *zaddr_str, char *inaddr, ser_context *ctx) +{ + int res; + struct sockaddr_in saddr = {0}; + zval lzval = zval_used_for_init; + TSRMLS_FETCH(); + + if (Z_TYPE_P(zaddr_str) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zaddr_str); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zaddr_str = &lzval; + } + + res = php_set_inet_addr(&saddr, Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); + if (res) { + memcpy(inaddr, &saddr.sin_addr, sizeof saddr.sin_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET " + "address", Z_STRVAL_P(zaddr_str)); + } + + zval_dtor(&lzval); +} +static void to_zval_read_sin_addr(const char *data, zval *zv, res_context *ctx) +{ + const struct in_addr *addr = (const struct in_addr *)data; + socklen_t size = INET_ADDRSTRLEN; + + Z_TYPE_P(zv) = IS_STRING; + Z_STRVAL_P(zv) = ecalloc(1, size); + Z_STRLEN_P(zv) = 0; + + if (inet_ntop(AF_INET, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, "could not convert IPv4 address to string " + "(errno %d)", errno); + return; + } + + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); +} +static const field_descriptor descriptors_sockaddr_in[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_in, sin_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in, sin_addr), from_zval_write_sin_addr, to_zval_read_sin_addr}, + {"port", sizeof("port"), 0, offsetof(struct sockaddr_in, sin_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, + {0} +}; +static void from_zval_write_sockaddr_in(const zval *container, char *sockaddr, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_in, ctx); +} +static void to_zval_read_sockaddr_in(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_in, ctx); +} +static void from_zval_write_sin6_addr(const zval *zaddr_str, char *addr6, ser_context *ctx) +{ + int res; + struct sockaddr_in6 saddr6 = {0}; + zval lzval = zval_used_for_init; + TSRMLS_FETCH(); + + if (Z_TYPE_P(zaddr_str) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zaddr_str); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zaddr_str = &lzval; + } + + res = php_set_inet6_addr(&saddr6, + Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); + if (res) { + memcpy(addr6, &saddr6.sin6_addr, sizeof saddr6.sin6_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET6 " + "address", Z_STRVAL_P(zaddr_str)); + } + + zval_dtor(&lzval); +} +static void to_zval_read_sin6_addr(const char *data, zval *zv, res_context *ctx) +{ + const struct in6_addr *addr = (const struct in6_addr *)data; + socklen_t size = INET6_ADDRSTRLEN; + + Z_TYPE_P(zv) = IS_STRING; + Z_STRVAL_P(zv) = ecalloc(1, size); + Z_STRLEN_P(zv) = 0; + + if (inet_ntop(AF_INET6, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, "could not convert IPv6 address to string " + "(errno %d)", errno); + return; + } + + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); +} +static const field_descriptor descriptors_sockaddr_in6[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_in6, sin6_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in6, sin6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, + {"port", sizeof("port"), 0, offsetof(struct sockaddr_in6, sin6_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, + {"flowinfo", sizeof("flowinfo"), 0, offsetof(struct sockaddr_in6, sin6_flowinfo), from_zval_write_uint32, to_zval_read_uint32}, + {"scope_id", sizeof("scope_id"), 0, offsetof(struct sockaddr_in6, sin6_scope_id), from_zval_write_uint32, to_zval_read_uint32}, + {0} +}; +static void from_zval_write_sockaddr_in6(const zval *container, char *sockaddr6, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr6, descriptors_sockaddr_in6, ctx); +} +static void to_zval_read_sockaddr_in6(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_in6, ctx); +} +static void from_zval_write_sun_path(const zval *path, char *sockaddr_un_c, ser_context *ctx) +{ + zval lzval = zval_used_for_init; + struct sockaddr_un *saddr = (struct sockaddr_un*)sockaddr_un_c; + + if (Z_TYPE_P(path) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, path); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + path = &lzval; + } + + if (Z_STRLEN_P(path) >= sizeof(saddr->sun_path)) { + do_from_zval_err(ctx, "the path is too long, the maximum permitted " + "length is %ld", sizeof(saddr->sun_path) - 1); + return; + } + + memcpy(&saddr->sun_path, Z_STRVAL_P(path), Z_STRLEN_P(path)); + saddr->sun_path[Z_STRLEN_P(path)] = '\0'; + + zval_dtor(&lzval); +} +static void to_zval_read_sun_path(const char *data, zval *zv, res_context *ctx) { + struct sockaddr_un *saddr = (struct sockaddr_un*)data; + char *nul_pos; + + nul_pos = memchr(&saddr->sun_path, '\0', sizeof(saddr->sun_path)); + if (nul_pos == NULL) { + do_to_zval_err(ctx, "could not find a NUL in the path"); + return; + } + + ZVAL_STRINGL(zv, saddr->sun_path, nul_pos - (char*)&saddr->sun_path, 1); +} +static const field_descriptor descriptors_sockaddr_un[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_un, sun_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"path", sizeof("path"), 0, 0, from_zval_write_sun_path, to_zval_read_sun_path}, + {0} +}; +static void from_zval_write_sockaddr_un(const zval *container, char *sockaddr, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_un, ctx); +} +static void to_zval_read_sockaddr_un(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_un, ctx); +} +static void from_zval_write_sockaddr_aux(const zval *container, + struct sockaddr **sockaddr_ptr, + socklen_t *sockaddr_len, + ser_context *ctx) +{ + int family; + zval **elem; + int fill_sockaddr; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + fill_sockaddr = param_get_bool(ctx, KEY_FILL_SOCKADDR, 1); + + if (zend_hash_find(Z_ARRVAL_P(container), "family", sizeof("family"), (void**)&elem) == SUCCESS + && Z_TYPE_PP(elem) != IS_NULL) { + const char *node = "family"; + zend_llist_add_element(&ctx->keys, &node); + from_zval_write_int(*elem, (char*)&family, ctx); + zend_llist_remove_tail(&ctx->keys); + } else { + family = ctx->sock->type; + } + + switch (family) { + case AF_INET: + /* though not all OSes support sockaddr_in used in IPv6 sockets */ + if (ctx->sock->type != AF_INET && ctx->sock->type != AF_INET6) { + do_from_zval_err(ctx, "the specified family (number %d) is not " + "supported on this socket", family); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in), ctx); + *sockaddr_len = sizeof(struct sockaddr_in); + if (fill_sockaddr) { + from_zval_write_sockaddr_in(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_INET; + } + break; + + case AF_INET6: + if (ctx->sock->type != AF_INET6) { + do_from_zval_err(ctx, "the specified family (AF_INET6) is not " + "supported on this socket"); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in6), ctx); + *sockaddr_len = sizeof(struct sockaddr_in6); + if (fill_sockaddr) { + from_zval_write_sockaddr_in6(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_INET6; + } + break; + + case AF_UNIX: + if (ctx->sock->type != AF_UNIX) { + do_from_zval_err(ctx, "the specified family (AF_UNIX) is not " + "supported on this socket"); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_un), ctx); + *sockaddr_len = sizeof(struct sockaddr_un); + if (fill_sockaddr) { + from_zval_write_sockaddr_un(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_UNIX; + } + break; + + default: + do_from_zval_err(ctx, "%s", "the only families currently supported are " + "AF_INET, AF_INET6 and AF_UNIX"); + break; + } +} +static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_context *ctx) +{ + const struct sockaddr *saddr = (struct sockaddr *)sockaddr_c; + + if (saddr->sa_family == 0) { + ZVAL_NULL(zv); + return; + } + + array_init(zv); + + switch (saddr->sa_family) { + case AF_INET: + to_zval_read_sockaddr_in(sockaddr_c, zv, ctx); + break; + + case AF_INET6: + to_zval_read_sockaddr_in6(sockaddr_c, zv, ctx); + break; + + case AF_UNIX: + to_zval_read_sockaddr_un(sockaddr_c, zv, ctx); + break; + + default: + do_to_zval_err(ctx, "cannot read struct sockaddr with family %d; " + "not supported", + (int)saddr->sa_family); + break; + } +} + +/* CONVERSIONS for cmsghdr */ +/* + * [ level => , type => , data => [],] + * struct cmsghdr { + * socklen_t cmsg_len; // data byte count, including header + * int cmsg_level; // originating protocol + * int cmsg_type; // protocol-specific type + * // followed by unsigned char cmsg_data[]; + * }; + */ +static void from_zval_write_control(const zval *arr, + void **control_buf, + zend_llist_element *alloc, + size_t *control_len, + size_t *offset, + ser_context *ctx) +{ + struct cmsghdr *cmsghdr; + int level, + type; + size_t data_len, + req_space, + space_left; + ancillary_reg_entry *entry; + + static const field_descriptor descriptor_level[] = { + {"level", sizeof("level"), 0, 0, from_zval_write_int, 0}, + {0} + }; + static const field_descriptor descriptor_type[] = { + {"type", sizeof("type"), 0, 0, from_zval_write_int, 0}, + {0} + }; + field_descriptor descriptor_data[] = { + {"data", sizeof("data"), 0, 0, 0, 0}, + {0} + }; + + from_zval_write_aggregation(arr, (char *)&level, descriptor_level, ctx); + if (ctx->err.has_error) { + return; + } + from_zval_write_aggregation(arr, (char *)&type, descriptor_type, ctx); + if (ctx->err.has_error) { + return; + } + + entry = get_ancillary_reg_entry(level, type); + if (entry == NULL) { + do_from_zval_err(ctx, "cmsghdr with level %d and type %d not supported", + level, type); + return; + } + + if (entry->calc_space) { + data_len = entry->calc_space(arr, ctx); + if (ctx->err.has_error) { + return; + } + } else { + data_len = entry->size; + } + req_space = CMSG_SPACE(data_len); + space_left = *control_len - *offset; + assert(*control_len >= *offset); + + if (space_left < req_space) { + *control_buf = safe_erealloc(*control_buf, 2, req_space, *control_len); + *control_len += 2 * req_space; + memcpy(&alloc->data, *control_buf, sizeof *control_buf); + } + + cmsghdr = (struct cmsghdr*)(((char*)*control_buf) + *offset); + cmsghdr->cmsg_level = level; + cmsghdr->cmsg_type = type; + cmsghdr->cmsg_len = CMSG_LEN(data_len); + + descriptor_data[0].from_zval = entry->from_array; + from_zval_write_aggregation(arr, (char*)CMSG_DATA(cmsghdr), descriptor_data, ctx); + + *offset += req_space; +} +static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_context *ctx) +{ + HashPosition pos; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + zval **elem; + uint32_t i; + int num_elems; + void *control_buf; + zend_llist_element *alloc; + size_t control_len, + cur_offset; + struct msghdr *msg = (struct msghdr*)msghdr_c; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elems == 0) { + return; + } + + /* estimate each message at 20 bytes */ + control_buf = accounted_safe_ecalloc(num_elems, CMSG_SPACE(20), 0, ctx); + alloc = ctx->allocations.tail; + control_len = (size_t)num_elems * CMSG_SPACE(20); + cur_offset = 0; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + from_zval_write_control(*elem, &control_buf, alloc, &control_len, + &cur_offset, ctx); + + zend_llist_remove_tail(&ctx->keys); + } + + msg->msg_control = control_buf; + msg->msg_controllen = cur_offset; /* not control_len, which may be larger */ +} +static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) +{ + const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; + ancillary_reg_entry *entry; + size_t len, + *len_p = &len; + + entry = get_ancillary_reg_entry(cmsg->cmsg_level, cmsg->cmsg_type); + if (entry == NULL) { + do_to_zval_err(ctx, "cmsghdr with level %d and type %d not supported", + cmsg->cmsg_level, cmsg->cmsg_type); + return; + } + if (CMSG_LEN(entry->size) > cmsg->cmsg_len) { + do_to_zval_err(ctx, "the cmsghdr structure is unexpectedly small; " + "expected a length of at least %ld, but got %ld", + (long)CMSG_LEN(entry->size), (long)cmsg->cmsg_len); + return; + } + + len = (size_t)cmsg->cmsg_len; /* use another var because type of cmsg_len varies */ + if (zend_hash_add(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), + &len_p, sizeof(len_p), NULL) == FAILURE) { + do_to_zval_err(ctx, "%s", "could not set parameter " KEY_CMSG_LEN); + return; + } + + entry->to_array((const char *)CMSG_DATA(cmsg), zv, ctx); + + zend_hash_del(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN)); +} +static void to_zval_read_control(const char *cmsghdr_c, zval *zv, res_context *ctx) +{ + /* takes a cmsghdr, not a msghdr like from_zval_write_control */ + static const field_descriptor descriptors[] = { + {"level", sizeof("level"), 0, offsetof(struct cmsghdr, cmsg_level), 0, to_zval_read_int}, + {"type", sizeof("type"), 0, offsetof(struct cmsghdr, cmsg_type), 0, to_zval_read_int}, + {"data", sizeof("data"), 0, 0 /* cmsghdr passed */, 0, to_zval_read_cmsg_data}, + {0} + }; + + array_init_size(zv, 3); + to_zval_read_aggregation(cmsghdr_c, zv, descriptors, ctx); +} +static void to_zval_read_control_array(const char *msghdr_c, zval *zv, res_context *ctx) +{ + struct msghdr *msg = (struct msghdr *)msghdr_c; + struct cmsghdr *cmsg; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + uint32_t i = 1; + + /*if (msg->msg_flags & MSG_CTRUNC) { + php_error_docref0(NULL, E_WARNING, "The MSG_CTRUNC flag is present; will not " + "attempt to read control messages"); + ZVAL_FALSE(zv); + return; + }*/ + + array_init(zv); + + for (cmsg = CMSG_FIRSTHDR(msg); + cmsg != NULL && !ctx->err.has_error; + cmsg = CMSG_NXTHDR(msg,cmsg)) { + zval *elem; + + ALLOC_INIT_ZVAL(elem); + add_next_index_zval(zv, elem); + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + to_zval_read_control((const char *)cmsg, elem, ctx); + + zend_llist_remove_tail(&ctx->keys); + } +} + +/* CONVERSIONS for msghdr */ +static void from_zval_write_name(const zval *zname_arr, char *msghdr_c, ser_context *ctx) +{ + struct sockaddr *sockaddr; + socklen_t sockaddr_len; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + + from_zval_write_sockaddr_aux(zname_arr, &sockaddr, &sockaddr_len, ctx); + + msghdr->msg_name = sockaddr; + msghdr->msg_namelen = sockaddr_len; +} +static void to_zval_read_name(const char *sockaddr_p, zval *zv, res_context *ctx) +{ + void *name = (void*)*(void**)sockaddr_p; + if (name == NULL) { + ZVAL_NULL(zv); + } else { + to_zval_read_sockaddr_aux(name, zv, ctx); + } +} +static void from_zval_write_msghdr_buffer_size(const zval *elem, char *msghdr_c, ser_context *ctx) +{ + long lval; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + + lval = from_zval_integer_common(elem, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > MAX_USER_BUFF_SIZE) { + do_from_zval_err(ctx, "the buffer size must be between 1 and %ld; " + "given %ld", (long)MAX_USER_BUFF_SIZE, lval); + return; + } + + msghdr->msg_iovlen = 1; + msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); + msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)lval, ctx); + msghdr->msg_iov[0].iov_len = (size_t)lval; +} +static void from_zval_write_iov_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +{ + struct msghdr *msg = args[0]; + size_t len; + + zval_add_ref(elem); + convert_to_string_ex(elem); + + len = Z_STRLEN_PP(elem); + msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); + msg->msg_iov[i - 1].iov_len = len; + memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); + + zval_ptr_dtor(elem); +} +static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_context *ctx) +{ + int num_elem; + struct msghdr *msg = (struct msghdr*)msghdr_c; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + num_elem = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elem == 0) { + return; + } + + msg->msg_iov = accounted_safe_ecalloc(num_elem, sizeof *msg->msg_iov, 0, ctx); + msg->msg_iovlen = (size_t)num_elem; + + from_array_iterate(arr, from_zval_write_iov_array_aux, (void**)&msg, ctx); +} +static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_context *ctx) +{ + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + uint32_t len; + + /* controllen should be an unsigned with at least 32-bit. Let's assume + * this least common denominator + */ + from_zval_write_uint32(elem, (char*)&len, ctx); + if (!ctx->err.has_error && len == 0) { + do_from_zval_err(ctx, "controllen cannot be 0"); + return; + } + msghdr->msg_control = accounted_emalloc(len, ctx); + msghdr->msg_controllen = len; +} +void from_zval_write_msghdr_send(const zval *container, char *msghdr_c, ser_context *ctx) +{ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, + {"iov", sizeof("iov"), 0, 0, from_zval_write_iov_array, 0}, + {"control", sizeof("control"), 0, 0, from_zval_write_control_array, 0}, + {0} + }; + + from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); +} +void from_zval_write_msghdr_recv(const zval *container, char *msghdr_c, ser_context *ctx) +{ + /* zval to struct msghdr, version for recvmsg(). It differs from the version + * for sendmsg() in that it: + * - has a buffer_size instead of an iov array; + * - has no control element; has a controllen element instead + * struct msghdr { + * void *msg_name; + * socklen_t msg_namelen; + * struct iovec *msg_iov; + * size_t msg_iovlen; + * void *msg_control; + * size_t msg_controllen; //can also be socklen_t + * int msg_flags; + * }; + */ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, + {"buffer_size", sizeof("buffer_size"), 0, 0, from_zval_write_msghdr_buffer_size, 0}, + {"controllen", sizeof("controllen"), 1, 0, from_zval_write_controllen, 0}, + {0} + }; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + const int falsev = 0, + *falsevp = &falsev; + + if (zend_hash_add(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR), + (void*)&falsevp, sizeof(falsevp), NULL) == FAILURE) { + do_from_zval_err(ctx, "could not add fill_sockaddr; this is a bug"); + return; + } + + from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); + + zend_hash_del(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR)); + if (ctx->err.has_error) { + return; + } + + if (msghdr->msg_iovlen == 0) { + msghdr->msg_iovlen = 1; + msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); + msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)DEFAULT_BUFF_SIZE, ctx); + msghdr->msg_iov[0].iov_len = (size_t)DEFAULT_BUFF_SIZE; + } +} + +static void to_zval_read_iov(const char *msghdr_c, zval *zv, res_context *ctx) +{ + const struct msghdr *msghdr = (const struct msghdr *)msghdr_c; + size_t iovlen = msghdr->msg_iovlen; + ssize_t **recvmsg_ret, + bytes_left; + uint i; + + if (iovlen > UINT_MAX) { + do_to_zval_err(ctx, "unexpectedly large value for iov_len: %lu", + (unsigned long)iovlen); + } + array_init_size(zv, (uint)iovlen); + + if (zend_hash_find(&ctx->params, KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), + (void**)&recvmsg_ret) == FAILURE) { + do_to_zval_err(ctx, "recvmsg_ret not found in params. This is a bug"); + return; + } + bytes_left = **recvmsg_ret; + + for (i = 0; bytes_left > 0 && i < (uint)iovlen; i++) { + zval *elem; + size_t len = MIN(msghdr->msg_iov[i].iov_len, bytes_left); + char *buf = safe_emalloc(1, len, 1); + + MAKE_STD_ZVAL(elem); + memcpy(buf, msghdr->msg_iov[i].iov_base, len); + buf[len] = '\0'; + + ZVAL_STRINGL(elem, buf, len, 0); + add_next_index_zval(zv, elem); + bytes_left -= len; + } +} +void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx) +{ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, offsetof(struct msghdr, msg_name), 0, to_zval_read_name}, + {"control", sizeof("control"), 0, 0, 0, to_zval_read_control_array}, + {"iov", sizeof("iov"), 0, 0, 0, to_zval_read_iov}, + {"flags", sizeof("flags"), 0, offsetof(struct msghdr, msg_flags), 0, to_zval_read_int}, + {0} + }; + + array_init_size(zv, 4); + + to_zval_read_aggregation(msghdr_c, zv, descriptors, ctx); +} + +/* CONVERSIONS for if_index */ +static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context *ctx) +{ + zval *va; unsigned *out; + unsigned ret; + zval lzval = zval_used_for_init; + + if (Z_TYPE_P(zv) == IS_LONG) { + if (Z_LVAL_P(zv) < 0 || Z_LVAL_P(zv) > UINT_MAX) { + do_from_zval_err(ctx, "the interface index cannot be negative or " + "larger than %u; given %ld", UINT_MAX, Z_LVAL_P(zv)); + } else { + ret = (unsigned)Z_LVAL_P(zv); + } + } else { +#if HAVE_IF_NAMETOINDEX + + if (Z_TYPE_P(zv) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zv); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zv = &lzval; + } + + ret = if_nametoindex(Z_STRVAL_P(zv)); + if (ret == 0) { + do_from_zval_err(ctx, "no interface with name \"%s\" could be " + "found", Z_STRVAL_P(zv)); + } +#else + do_from_zval_err(ctx, + "this platform does not support looking up an interface by " + "name, an integer interface index must be supplied instead"); +#endif + } + + if (!ctx->err.has_error) { + memcpy(uinteger, &ret, sizeof(ret)); + } + + zval_dtor(&lzval); +} + +/* CONVERSIONS for struct in6_pktinfo */ +#ifdef IPV6_PKTINFO +static const field_descriptor descriptors_in6_pktinfo[] = { + {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, + {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_unsigned, to_zval_read_unsigned}, + {0} +}; +void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) +{ + from_zval_write_aggregation(container, in6_pktinfo_c, descriptors_in6_pktinfo, ctx); +} +void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx) +{ + array_init_size(zv, 2); + + to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); +} +#endif + +/* CONVERSIONS for struct ucred */ +#ifdef SO_PASSCRED +static const field_descriptor descriptors_ucred[] = { + {"pid", sizeof("pid"), 1, offsetof(struct ucred, pid), from_zval_write_pid_t, to_zval_read_pid_t}, + {"uid", sizeof("uid"), 1, offsetof(struct ucred, uid), from_zval_write_uid_t, to_zval_read_uid_t}, + /* assume the type gid_t is the same as uid_t: */ + {"gid", sizeof("gid"), 1, offsetof(struct ucred, gid), from_zval_write_uid_t, to_zval_read_uid_t}, + {0} +}; +void from_zval_write_ucred(const zval *container, char *ucred_c, ser_context *ctx) +{ + from_zval_write_aggregation(container, ucred_c, descriptors_ucred, ctx); +} +void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx) +{ + array_init_size(zv, 3); + + to_zval_read_aggregation(data, zv, descriptors_ucred, ctx); +} +#endif + +/* CONVERSIONS for SCM_RIGHTS */ +#ifdef SCM_RIGHTS +size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) +{ + int num_elems; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return (size_t)-1; + } + + num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elems == 0) { + do_from_zval_err(ctx, "%s", "expected at least one element in this array"); + return (size_t)-1; + } + + return zend_hash_num_elements(Z_ARRVAL_P(arr)) * sizeof(int); +} +static void from_zval_write_fd_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +{ + int *iarr = args[0]; + + if (Z_TYPE_PP(elem) == IS_RESOURCE) { + php_stream *stream; + php_socket *sock; + + ZEND_FETCH_RESOURCE_NO_RETURN(sock, php_socket *, elem, -1, + NULL, php_sockets_le_socket()); + if (sock) { + iarr[i] = sock->bsd_socket; + return; + } + + ZEND_FETCH_RESOURCE2_NO_RETURN(stream, php_stream *, elem, -1, + NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + do_from_zval_err(ctx, "resource is not a stream or a socket"); + return; + } + + if (php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&iarr[i], + REPORT_ERRORS) == FAILURE) { + do_from_zval_err(ctx, "cast stream to file descriptor failed"); + return; + } + } else { + do_from_zval_err(ctx, "expected a resource variable"); + } +} +void from_zval_write_fd_array(const zval *arr, char *int_arr, ser_context *ctx) +{ + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + from_array_iterate(arr, &from_zval_write_fd_array_aux, (void**)&int_arr, ctx); +} +void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) +{ + size_t **cmsg_len; + int num_elems, + i; + struct cmsghdr *dummy_cmsg = 0; + size_t data_offset; + TSRMLS_FETCH(); + + data_offset = (unsigned char *)CMSG_DATA(dummy_cmsg) + - (unsigned char *)dummy_cmsg; + + if (zend_hash_find(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), + (void **)&cmsg_len) == FAILURE) { + do_to_zval_err(ctx, "could not get value of parameter " KEY_CMSG_LEN); + return; + } + + if (**cmsg_len < data_offset) { + do_to_zval_err(ctx, "length of cmsg is smaller than its data member " + "offset (%ld vs %ld)", (long)**cmsg_len, (long)data_offset); + return; + } + num_elems = (**cmsg_len - data_offset) / sizeof(int); + + array_init_size(zv, num_elems); + + for (i = 0; i < num_elems; i++) { + zval *elem; + int fd; + struct stat statbuf; + + MAKE_STD_ZVAL(elem); + + fd = *((int *)data + i); + + /* determine whether we have a socket */ + if (fstat(fd, &statbuf) == -1) { + do_to_zval_err(ctx, "error creating resource for received file " + "descriptor %d: fstat() call failed with errno %d", fd, errno); + efree(elem); + return; + } + if (S_ISSOCK(statbuf.st_mode)) { + php_socket *sock = socket_import_file_descriptor(fd); + zend_register_resource(elem, sock, php_sockets_le_socket()); + } else { + php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); + php_stream_to_zval(stream, elem); + } + + add_next_index_zval(zv, elem); + } +} +#endif + +/* ENTRY POINT for conversions */ +static void free_from_zval_allocation(void *alloc_ptr_ptr) +{ + efree(*(void**)alloc_ptr_ptr); +} +void *from_zval_run_conversions(const zval *container, + php_socket *sock, + from_zval_write_field *writer, + size_t struct_size, + const char *top_name, + zend_llist **allocations /* out */, + struct err_s *err /* in/out */) +{ + ser_context ctx = {{0}}; + char *structure = NULL; + + *allocations = NULL; + + if (err->has_error) { + return NULL; + } + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_init(&ctx.allocations, sizeof(void *), &free_from_zval_allocation, 0); + ctx.sock = sock; + + structure = ecalloc(1, struct_size); + + zend_llist_add_element(&ctx.keys, &top_name); + zend_llist_add_element(&ctx.allocations, &structure); + + /* main call */ + writer(container, structure, &ctx); + + if (ctx.err.has_error) { + zend_llist_destroy(&ctx.allocations); /* deallocates structure as well */ + structure = NULL; + *err = ctx.err; + } else { + *allocations = emalloc(sizeof **allocations); + **allocations = ctx.allocations; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return structure; +} +zval *to_zval_run_conversions(const char *structure, + to_zval_read_field *reader, + const char *top_name, + const struct key_value *key_value_pairs, + struct err_s *err) +{ + res_context ctx = {{0}, {0}}; + const struct key_value *kv; + zval *zv = NULL; + + if (err->has_error) { + return NULL; + } + + ALLOC_INIT_ZVAL(zv); + + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_add_element(&ctx.keys, &top_name); + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + for (kv = key_value_pairs; kv->key != NULL; kv++) { + zend_hash_update(&ctx.params, kv->key, kv->key_size, + (void*)&kv->value, sizeof(kv->value), NULL); + } + + /* main call */ + reader(structure, zv, &ctx); + + if (ctx.err.has_error) { + zval_ptr_dtor(&zv); + zv = NULL; + *err = ctx.err; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return zv; +} diff --git a/ext/sockets/conversions.h b/ext/sockets/conversions.h new file mode 100644 index 00000000000..70f31ba676e --- /dev/null +++ b/ext/sockets/conversions.h @@ -0,0 +1,78 @@ +#ifndef PHP_SOCK_CONVERSIONS_H +#define PHP_SOCK_CONVERSIONS_H 1 + +#include +#include +#include +#include "php_sockets.h" + +/* TYPE DEFINITIONS */ +struct err_s { + int has_error; + char *msg; + int level; + int should_free; +}; + +struct key_value { + const char *key; + unsigned key_size; + void *value; +}; + +/* the complete types of these two are not relevant to the outside */ +typedef struct _ser_context ser_context; +typedef struct _res_context res_context; + +#define KEY_RECVMSG_RET "recvmsg_ret" + +typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); +typedef void (to_zval_read_field)(const char *data, zval *zv, res_context *ctx); + +/* VARIABLE DECLARATIONS */ +extern const struct key_value empty_key_value_list[]; + +/* AUX FUNCTIONS */ +void err_msg_dispose(struct err_s *err TSRMLS_DC); +void allocations_dispose(zend_llist **allocations); + +/* CONVERSION FUNCTIONS */ +void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx); +void to_zval_read_int(const char *data, zval *zv, res_context *ctx); + +#ifdef IPV6_PKTINFO +void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx); +void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx); +#endif + +#ifdef SO_PASSCRED +void from_zval_write_ucred(const zval *container, char *ucred_c, ser_context *ctx); +void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx); +#endif + +#ifdef SCM_RIGHTS +size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx); +void from_zval_write_fd_array(const zval *arr, char *int_arr, ser_context *ctx); +void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx); +#endif + +void from_zval_write_msghdr_send(const zval *container, char *msghdr_c, ser_context *ctx); +void from_zval_write_msghdr_recv(const zval *container, char *msghdr_c, ser_context *ctx); +void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx); + +/* ENTRY POINTS FOR CONVERSIONS */ +void *from_zval_run_conversions(const zval *container, + php_socket *sock, + from_zval_write_field *writer, + size_t struct_size, + const char *top_name, + zend_llist **allocations /* out */, + struct err_s *err /* in/out */); + +zval *to_zval_run_conversions(const char *structure, + to_zval_read_field *reader, + const char *top_name, + const struct key_value *key_value_pairs, + struct err_s *err); + +#endif diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 6b1a528c5b1..6479bf90a85 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -18,18 +18,10 @@ #include #include "php_sockets.h" -#include "sockaddr_conv.h" #include "sendrecvmsg.h" -#include -#include -#include -#include +#include "conversions.h" #include -#include -#include -#include #include -#include #ifdef ZTS #include #endif @@ -47,1460 +39,11 @@ } \ } while (0) -struct err_s { - int has_error; - char *msg; - int level; - int should_free; -}; - -typedef struct { - HashTable params; /* stores pointers; has to be first */ - struct err_s err; - zend_llist keys, - /* common part to res_context ends here */ - allocations; - php_socket *sock; -} ser_context; - -typedef struct { - HashTable params; /* stores pointers; has to be first */ - struct err_s err; - zend_llist keys; -} res_context; - -struct key_value { - const char *key; - unsigned key_size; - void *value; -}; -#define KEY_FILL_SOCKADDR "fill_sockaddr" -#define KEY_RECVMSG_RET "recvmsg_ret" -#define KEY_CMSG_LEN "cmsg_len" -static const struct key_value empty_key_value_list[] = {{0}}; - - -typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); -typedef void (to_zval_read_field)(const char *data, zval *zv, res_context *ctx); -typedef size_t (calculate_req_space)(const zval *value, ser_context *ctx); - -typedef struct { - /* zval info */ - const char *name; - unsigned name_size; - int required; - - /* structure info */ - size_t field_offset; /* 0 to pass full structure, e.g. when more than - one field is to be changed; in that case the - callbacks need to know the name of the fields */ - - /* callbacks */ - from_zval_write_field *from_zval; - to_zval_read_field *to_zval; -} field_descriptor; - -typedef struct { - int cmsg_level; /* originating protocol */ - int cmsg_type; /* protocol-specific type */ -} anc_reg_key; - static struct { int initialized; HashTable ht; } ancillary_registry; -typedef socklen_t (*ancillary_size)(void); - -typedef struct { - socklen_t size; /* size of native structure */ - socklen_t var_el_size; /* size of repeatable component */ - calculate_req_space *calc_space; - from_zval_write_field *from_array; - to_zval_read_field *to_array; -} ancillary_reg_entry; - -/* PARAMETERS */ -static int param_get_bool(void *ctx, const char *key, int def) -{ - int **elem; - if (zend_hash_find(ctx, key, strlen(key) + 1, (void**)&elem) == SUCCESS) { - return **elem; - } else { - return def; - } -} - -/* FORWARD DECLARATIONS */ -static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type); - -static inline void *accounted_emalloc(size_t alloc_size, ser_context *ctx) -{ - void *ret = emalloc(alloc_size); - zend_llist_add_element(&ctx->allocations, &ret); - return ret; -} - -static inline void *accounted_ecalloc(size_t nmemb, size_t alloc_size, ser_context *ctx) -{ - void *ret = ecalloc(nmemb, alloc_size); - zend_llist_add_element(&ctx->allocations, &ret); - return ret; -} -static inline void *accounted_safe_ecalloc(size_t nmemb, size_t alloc_size, size_t offset, ser_context *ctx) -{ - void *ret = safe_emalloc(nmemb, alloc_size, offset); - memset(ret, '\0', nmemb * alloc_size + offset); - zend_llist_add_element(&ctx->allocations, &ret); - return ret; -} - -static void do_from_to_zval_err(struct err_s *err, - zend_llist *keys, - const char *what_conv, - const char *fmt, - va_list ap) -{ - smart_str path = {0}; - const char **node; - char *user_msg; - int user_msg_size; - zend_llist_position pos; - - if (err->has_error) { - return; - } - - for (node = zend_llist_get_first_ex(keys, &pos); - node != NULL; - node = zend_llist_get_next_ex(keys, &pos)) { - smart_str_appends(&path, *node); - smart_str_appends(&path, " > "); - } - - if (path.len > 3) { - path.len -= 3; - } - smart_str_0(&path); - - user_msg_size = vspprintf(&user_msg, 0, fmt, ap); - - err->has_error = 1; - err->level = E_WARNING; - spprintf(&err->msg, 0, "error converting %s data (path: %s): %.*s", - what_conv, - path.c && path.c != '\0' ? path.c : "unavailable", - user_msg_size, user_msg); - err->should_free = 1; - - efree(user_msg); - smart_str_free_ex(&path, 0); -} -__attribute__ ((format (printf, 2, 3))) -static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); - va_end(ap); -} -__attribute__ ((format (printf, 2, 3))) -static void do_to_zval_err(res_context *ctx, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - do_from_to_zval_err(&ctx->err, &ctx->keys, "native", fmt, ap); - va_end(ap); -} -static void err_msg_dispose(struct err_s *err TSRMLS_DC) -{ - if (err->msg != NULL) { - php_error_docref0(NULL TSRMLS_CC, err->level, "%s", err->msg); - if (err->should_free) { - efree(err->msg); - } - } -} -static void allocations_dispose(zend_llist **allocations) -{ - zend_llist_destroy(*allocations); - efree(*allocations); - *allocations = NULL; -} - -static unsigned from_array_iterate(const zval *arr, - void (*func)(zval **elem, unsigned i, void **args, ser_context *ctx), - void **args, - ser_context *ctx) -{ - HashPosition pos; - unsigned i; - zval **elem; - char buf[sizeof("element #4294967295")]; - char *bufp = buf; - - for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 1; - !ctx->err.has_error - && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; - zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos), i++) { - if (snprintf(buf, sizeof(buf), "element #%u", i) >= sizeof(buf)) { - memcpy(buf, "element", sizeof("element")); - } - zend_llist_add_element(&ctx->keys, &bufp); - - func(elem, i, args, ctx); - - zend_llist_remove_tail(&ctx->keys); - } - - return i -1; -} - -/* Generic Aggregated conversions */ -static void from_zval_write_aggregation(const zval *container, - char *structure, - const field_descriptor *descriptors, - ser_context *ctx) -{ - const field_descriptor *descr; - zval **elem; - - if (Z_TYPE_P(container) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - } - - for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { - if (zend_hash_find(Z_ARRVAL_P(container), - descr->name, descr->name_size, (void**)&elem) == SUCCESS) { - - if (descr->from_zval == NULL) { - do_from_zval_err(ctx, "No information on how to convert value " - "of key '%s'", descr->name); - break; - } - - zend_llist_add_element(&ctx->keys, (void*)&descr->name); - descr->from_zval(*elem, ((char*)structure) + descr->field_offset, ctx); - zend_llist_remove_tail(&ctx->keys); - - } else if (descr->required) { - do_from_zval_err(ctx, "The key '%s' is required", descr->name); - break; - } - } -} -static void to_zval_read_aggregation(const char *structure, - zval *zarr, /* initialized array */ - const field_descriptor *descriptors, - res_context *ctx) -{ - const field_descriptor *descr; - - assert(Z_TYPE_P(zarr) == IS_ARRAY); - assert(Z_ARRVAL_P(zarr) != NULL); - - for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { - zval *new_zv; - - if (descr->to_zval == NULL) { - do_to_zval_err(ctx, "No information on how to convert native " - "field into value for key '%s'", descr->name); - break; - } - - ALLOC_INIT_ZVAL(new_zv); - add_assoc_zval_ex(zarr, descr->name, descr->name_size, new_zv); - - zend_llist_add_element(&ctx->keys, (void*)&descr->name); - descr->to_zval(structure + descr->field_offset, new_zv, ctx); - zend_llist_remove_tail(&ctx->keys); - } -} - -/* CONVERSIONS for integers */ -static long from_zval_integer_common(const zval *arr_value, ser_context *ctx) -{ - long ret = 0; - zval lzval = zval_used_for_init; - - if (Z_TYPE_P(arr_value) != IS_LONG) { - ZVAL_COPY_VALUE(&lzval, arr_value); - zval_copy_ctor(&lzval); - arr_value = &lzval; - } - - switch (Z_TYPE_P(arr_value)) { - case IS_LONG: -long_case: - ret = Z_LVAL_P(arr_value); - break; - - /* if not long we're operating on lzval */ - case IS_DOUBLE: -double_case: - convert_to_long(&lzval); - goto long_case; - - case IS_OBJECT: - case IS_STRING: { - long lval; - double dval; - - convert_to_string(&lzval); - - switch (is_numeric_string(Z_STRVAL(lzval), Z_STRLEN(lzval), &lval, &dval, 0)) { - case IS_DOUBLE: - zval_dtor(&lzval); - Z_TYPE(lzval) = IS_DOUBLE; - Z_DVAL(lzval) = dval; - goto double_case; - - case IS_LONG: - zval_dtor(&lzval); - Z_TYPE(lzval) = IS_LONG; - Z_DVAL(lzval) = lval; - goto long_case; - } - - /* if we get here, we don't have a numeric string */ - do_from_zval_err(ctx, "expected an integer, but got a non numeric " - "string (possibly from a converted object): '%s'", Z_STRVAL_P(arr_value)); - break; - } - - default: - do_from_zval_err(ctx, "%s", "expected an integer, either of a PHP " - "integer type or of a convertible type"); - break; - } - - zval_dtor(&lzval); - - return ret; -} -static void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - int ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval > INT_MAX || lval < INT_MIN) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a native int"); - return; - } - - ival = (int)lval; - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_unsigned(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - unsigned ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (sizeof(long) > sizeof(ival) && (lval < 0 || lval > UINT_MAX)) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a native unsigned int"); - return; - } - - ival = (unsigned)lval; - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_uint32(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - uint32_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (sizeof(long) > sizeof(uint32_t) && (lval < 0 || lval > 0xFFFFFFFF)) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for an unsigned 32-bit integer"); - return; - } - - ival = (uint32_t)lval; - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - uint16_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval < 0 || lval > 0xFFFF) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for an unsigned 16-bit integer"); - return; - } - - ival = htons((uint16_t)lval); - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_sa_family(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - sa_family_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval < 0 || lval > (sa_family_t)-1) { /* sa_family_t is unsigned */ - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a sa_family_t value"); - return; - } - - ival = (sa_family_t)lval; - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_pid_t(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - pid_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval < 0 || (pid_t)lval != lval) { /* pid_t is signed */ - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a pid_t value"); - return; - } - - ival = (pid_t)lval; - memcpy(field, &ival, sizeof(ival)); -} -static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - uid_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - /* uid_t can be signed or unsigned (generally unsigned) */ - if ((uid_t)-1 > (uid_t)0) { - if (sizeof(long) > sizeof(uid_t) && (lval < 0 || (uid_t)lval != lval)) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a uid_t value"); - return; - } - } else { - if (sizeof(long) > sizeof(uid_t) && (uid_t)lval != lval) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a uid_t value"); - return; - } - } - - ival = (uid_t)lval; - memcpy(field, &ival, sizeof(ival)); -} - -static void to_zval_read_int(const char *data, zval *zv, res_context *ctx) -{ - int ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} -static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) -{ - unsigned ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} -static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) -{ - uint16_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ntohs(ival)); -} -static void to_zval_read_uint32(const char *data, zval *zv, res_context *ctx) -{ - uint32_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} -static void to_zval_read_sa_family(const char *data, zval *zv, res_context *ctx) -{ - sa_family_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} -static void to_zval_read_pid_t(const char *data, zval *zv, res_context *ctx) -{ - pid_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} -static void to_zval_read_uid_t(const char *data, zval *zv, res_context *ctx) -{ - uid_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} - -/* CONVERSIONS for sockaddr */ -static void from_zval_write_sin_addr(const zval *zaddr_str, char *inaddr, ser_context *ctx) -{ - int res; - struct sockaddr_in saddr = {0}; - zval lzval = zval_used_for_init; - TSRMLS_FETCH(); - - if (Z_TYPE_P(zaddr_str) != IS_STRING) { - ZVAL_COPY_VALUE(&lzval, zaddr_str); - zval_copy_ctor(&lzval); - convert_to_string(&lzval); - zaddr_str = &lzval; - } - - res = php_set_inet_addr(&saddr, Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); - if (res) { - memcpy(inaddr, &saddr.sin_addr, sizeof saddr.sin_addr); - } else { - /* error already emitted, but let's emit another more relevant */ - do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET " - "address", Z_STRVAL_P(zaddr_str)); - } - - zval_dtor(&lzval); -} -static void to_zval_read_sin_addr(const char *data, zval *zv, res_context *ctx) -{ - const struct in_addr *addr = (const struct in_addr *)data; - socklen_t size = INET_ADDRSTRLEN; - - Z_TYPE_P(zv) = IS_STRING; - Z_STRVAL_P(zv) = ecalloc(1, size); - Z_STRLEN_P(zv) = 0; - - if (inet_ntop(AF_INET, addr, Z_STRVAL_P(zv), size) == NULL) { - do_to_zval_err(ctx, "could not convert IPv4 address to string " - "(errno %d)", errno); - return; - } - - Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); -} -static const field_descriptor descriptors_sockaddr_in[] = { - {"family", sizeof("family"), 0, offsetof(struct sockaddr_in, sin_family), from_zval_write_sa_family, to_zval_read_sa_family}, - {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in, sin_addr), from_zval_write_sin_addr, to_zval_read_sin_addr}, - {"port", sizeof("port"), 0, offsetof(struct sockaddr_in, sin_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, - {0} -}; -static void from_zval_write_sockaddr_in(const zval *container, char *sockaddr, ser_context *ctx) -{ - from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_in, ctx); -} -static void to_zval_read_sockaddr_in(const char *data, zval *zv, res_context *ctx) -{ - to_zval_read_aggregation(data, zv, descriptors_sockaddr_in, ctx); -} -static void from_zval_write_sin6_addr(const zval *zaddr_str, char *addr6, ser_context *ctx) -{ - int res; - struct sockaddr_in6 saddr6 = {0}; - zval lzval = zval_used_for_init; - TSRMLS_FETCH(); - - if (Z_TYPE_P(zaddr_str) != IS_STRING) { - ZVAL_COPY_VALUE(&lzval, zaddr_str); - zval_copy_ctor(&lzval); - convert_to_string(&lzval); - zaddr_str = &lzval; - } - - res = php_set_inet6_addr(&saddr6, - Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); - if (res) { - memcpy(addr6, &saddr6.sin6_addr, sizeof saddr6.sin6_addr); - } else { - /* error already emitted, but let's emit another more relevant */ - do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET6 " - "address", Z_STRVAL_P(zaddr_str)); - } - - zval_dtor(&lzval); -} -static void to_zval_read_sin6_addr(const char *data, zval *zv, res_context *ctx) -{ - const struct in6_addr *addr = (const struct in6_addr *)data; - socklen_t size = INET6_ADDRSTRLEN; - - Z_TYPE_P(zv) = IS_STRING; - Z_STRVAL_P(zv) = ecalloc(1, size); - Z_STRLEN_P(zv) = 0; - - if (inet_ntop(AF_INET6, addr, Z_STRVAL_P(zv), size) == NULL) { - do_to_zval_err(ctx, "could not convert IPv6 address to string " - "(errno %d)", errno); - return; - } - - Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); -} -static const field_descriptor descriptors_sockaddr_in6[] = { - {"family", sizeof("family"), 0, offsetof(struct sockaddr_in6, sin6_family), from_zval_write_sa_family, to_zval_read_sa_family}, - {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in6, sin6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, - {"port", sizeof("port"), 0, offsetof(struct sockaddr_in6, sin6_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, - {"flowinfo", sizeof("flowinfo"), 0, offsetof(struct sockaddr_in6, sin6_flowinfo), from_zval_write_uint32, to_zval_read_uint32}, - {"scope_id", sizeof("scope_id"), 0, offsetof(struct sockaddr_in6, sin6_scope_id), from_zval_write_uint32, to_zval_read_uint32}, - {0} -}; -static void from_zval_write_sockaddr_in6(const zval *container, char *sockaddr6, ser_context *ctx) -{ - from_zval_write_aggregation(container, sockaddr6, descriptors_sockaddr_in6, ctx); -} -static void to_zval_read_sockaddr_in6(const char *data, zval *zv, res_context *ctx) -{ - to_zval_read_aggregation(data, zv, descriptors_sockaddr_in6, ctx); -} -static void from_zval_write_sun_path(const zval *path, char *sockaddr_un_c, ser_context *ctx) -{ - zval lzval = zval_used_for_init; - struct sockaddr_un *saddr = (struct sockaddr_un*)sockaddr_un_c; - - if (Z_TYPE_P(path) != IS_STRING) { - ZVAL_COPY_VALUE(&lzval, path); - zval_copy_ctor(&lzval); - convert_to_string(&lzval); - path = &lzval; - } - - if (Z_STRLEN_P(path) >= sizeof(saddr->sun_path)) { - do_from_zval_err(ctx, "the path is too long, the maximum permitted " - "length is %ld", sizeof(saddr->sun_path) - 1); - return; - } - - memcpy(&saddr->sun_path, Z_STRVAL_P(path), Z_STRLEN_P(path)); - saddr->sun_path[Z_STRLEN_P(path)] = '\0'; - - zval_dtor(&lzval); -} -static void to_zval_read_sun_path(const char *data, zval *zv, res_context *ctx) { - struct sockaddr_un *saddr = (struct sockaddr_un*)data; - char *nul_pos; - - nul_pos = memchr(&saddr->sun_path, '\0', sizeof(saddr->sun_path)); - if (nul_pos == NULL) { - do_to_zval_err(ctx, "could not find a NUL in the path"); - return; - } - - ZVAL_STRINGL(zv, saddr->sun_path, nul_pos - (char*)&saddr->sun_path, 1); -} -static const field_descriptor descriptors_sockaddr_un[] = { - {"family", sizeof("family"), 0, offsetof(struct sockaddr_un, sun_family), from_zval_write_sa_family, to_zval_read_sa_family}, - {"path", sizeof("path"), 0, 0, from_zval_write_sun_path, to_zval_read_sun_path}, - {0} -}; -static void from_zval_write_sockaddr_un(const zval *container, char *sockaddr, ser_context *ctx) -{ - from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_un, ctx); -} -static void to_zval_read_sockaddr_un(const char *data, zval *zv, res_context *ctx) -{ - to_zval_read_aggregation(data, zv, descriptors_sockaddr_un, ctx); -} -static void from_zval_write_sockaddr_aux(const zval *container, - struct sockaddr **sockaddr_ptr, - socklen_t *sockaddr_len, - ser_context *ctx) -{ - int family; - zval **elem; - int fill_sockaddr; - - if (Z_TYPE_P(container) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - return; - } - - fill_sockaddr = param_get_bool(ctx, KEY_FILL_SOCKADDR, 1); - - if (zend_hash_find(Z_ARRVAL_P(container), "family", sizeof("family"), (void**)&elem) == SUCCESS - && Z_TYPE_PP(elem) != IS_NULL) { - const char *node = "family"; - zend_llist_add_element(&ctx->keys, &node); - from_zval_write_int(*elem, (char*)&family, ctx); - zend_llist_remove_tail(&ctx->keys); - } else { - family = ctx->sock->type; - } - - switch (family) { - case AF_INET: - /* though not all OSes support sockaddr_in used in IPv6 sockets */ - if (ctx->sock->type != AF_INET && ctx->sock->type != AF_INET6) { - do_from_zval_err(ctx, "the specified family (number %d) is not " - "supported on this socket", family); - return; - } - *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in), ctx); - *sockaddr_len = sizeof(struct sockaddr_in); - if (fill_sockaddr) { - from_zval_write_sockaddr_in(container, (char*)*sockaddr_ptr, ctx); - (*sockaddr_ptr)->sa_family = AF_INET; - } - break; - - case AF_INET6: - if (ctx->sock->type != AF_INET6) { - do_from_zval_err(ctx, "the specified family (AF_INET6) is not " - "supported on this socket"); - return; - } - *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in6), ctx); - *sockaddr_len = sizeof(struct sockaddr_in6); - if (fill_sockaddr) { - from_zval_write_sockaddr_in6(container, (char*)*sockaddr_ptr, ctx); - (*sockaddr_ptr)->sa_family = AF_INET6; - } - break; - - case AF_UNIX: - if (ctx->sock->type != AF_UNIX) { - do_from_zval_err(ctx, "the specified family (AF_UNIX) is not " - "supported on this socket"); - return; - } - *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_un), ctx); - *sockaddr_len = sizeof(struct sockaddr_un); - if (fill_sockaddr) { - from_zval_write_sockaddr_un(container, (char*)*sockaddr_ptr, ctx); - (*sockaddr_ptr)->sa_family = AF_UNIX; - } - break; - - default: - do_from_zval_err(ctx, "%s", "the only families currently supported are " - "AF_INET, AF_INET6 and AF_UNIX"); - break; - } -} -static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_context *ctx) -{ - const struct sockaddr *saddr = (struct sockaddr *)sockaddr_c; - - if (saddr->sa_family == 0) { - ZVAL_NULL(zv); - return; - } - - array_init(zv); - - switch (saddr->sa_family) { - case AF_INET: - to_zval_read_sockaddr_in(sockaddr_c, zv, ctx); - break; - - case AF_INET6: - to_zval_read_sockaddr_in6(sockaddr_c, zv, ctx); - break; - - case AF_UNIX: - to_zval_read_sockaddr_un(sockaddr_c, zv, ctx); - break; - - default: - do_to_zval_err(ctx, "cannot read struct sockaddr with family %d; " - "not supported", - (int)saddr->sa_family); - break; - } -} - -/* CONVERSIONS for cmsghdr */ -/* - * [ level => , type => , data => [],] - * struct cmsghdr { - * socklen_t cmsg_len; // data byte count, including header - * int cmsg_level; // originating protocol - * int cmsg_type; // protocol-specific type - * // followed by unsigned char cmsg_data[]; - * }; - */ -static void from_zval_write_control(const zval *arr, - void **control_buf, - zend_llist_element *alloc, - size_t *control_len, - size_t *offset, - ser_context *ctx) -{ - struct cmsghdr *cmsghdr; - int level, - type; - size_t data_len, - req_space, - space_left; - ancillary_reg_entry *entry; - - static const field_descriptor descriptor_level[] = { - {"level", sizeof("level"), 0, 0, from_zval_write_int, 0}, - {0} - }; - static const field_descriptor descriptor_type[] = { - {"type", sizeof("type"), 0, 0, from_zval_write_int, 0}, - {0} - }; - field_descriptor descriptor_data[] = { - {"data", sizeof("data"), 0, 0, 0, 0}, - {0} - }; - - from_zval_write_aggregation(arr, (char *)&level, descriptor_level, ctx); - if (ctx->err.has_error) { - return; - } - from_zval_write_aggregation(arr, (char *)&type, descriptor_type, ctx); - if (ctx->err.has_error) { - return; - } - - entry = get_ancillary_reg_entry(level, type); - if (entry == NULL) { - do_from_zval_err(ctx, "cmsghdr with level %d and type %d not supported", - level, type); - return; - } - - if (entry->calc_space) { - data_len = entry->calc_space(arr, ctx); - if (ctx->err.has_error) { - return; - } - } else { - data_len = entry->size; - } - req_space = CMSG_SPACE(data_len); - space_left = *control_len - *offset; - assert(*control_len >= *offset); - - if (space_left < req_space) { - *control_buf = safe_erealloc(*control_buf, 2, req_space, *control_len); - *control_len += 2 * req_space; - memcpy(&alloc->data, *control_buf, sizeof *control_buf); - } - - cmsghdr = (struct cmsghdr*)(((char*)*control_buf) + *offset); - cmsghdr->cmsg_level = level; - cmsghdr->cmsg_type = type; - cmsghdr->cmsg_len = CMSG_LEN(data_len); - - descriptor_data[0].from_zval = entry->from_array; - from_zval_write_aggregation(arr, (char*)CMSG_DATA(cmsghdr), descriptor_data, ctx); - - *offset += req_space; -} -static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_context *ctx) -{ - HashPosition pos; - char buf[sizeof("element #4294967295")]; - char *bufp = buf; - zval **elem; - uint32_t i; - int num_elems; - void *control_buf; - zend_llist_element *alloc; - size_t control_len, - cur_offset; - struct msghdr *msg = (struct msghdr*)msghdr_c; - - if (Z_TYPE_P(arr) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - return; - } - - num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); - if (num_elems == 0) { - return; - } - - /* estimate each message at 20 bytes */ - control_buf = accounted_safe_ecalloc(num_elems, CMSG_SPACE(20), 0, ctx); - alloc = ctx->allocations.tail; - control_len = (size_t)num_elems * CMSG_SPACE(20); - cur_offset = 0; - - for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; - !ctx->err.has_error - && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; - zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { - - if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { - memcpy(buf, "element", sizeof("element")); - } - zend_llist_add_element(&ctx->keys, &bufp); - - from_zval_write_control(*elem, &control_buf, alloc, &control_len, - &cur_offset, ctx); - - zend_llist_remove_tail(&ctx->keys); - } - - msg->msg_control = control_buf; - msg->msg_controllen = cur_offset; /* not control_len, which may be larger */ -} -static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) -{ - const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; - ancillary_reg_entry *entry; - size_t len, - *len_p = &len; - - entry = get_ancillary_reg_entry(cmsg->cmsg_level, cmsg->cmsg_type); - if (entry == NULL) { - do_to_zval_err(ctx, "cmsghdr with level %d and type %d not supported", - cmsg->cmsg_level, cmsg->cmsg_type); - return; - } - if (CMSG_LEN(entry->size) > cmsg->cmsg_len) { - do_to_zval_err(ctx, "the cmsghdr structure is unexpectedly small; " - "expected a length of at least %ld, but got %ld", - (long)CMSG_LEN(entry->size), (long)cmsg->cmsg_len); - return; - } - - len = (size_t)cmsg->cmsg_len; /* use another var because type of cmsg_len varies */ - if (zend_hash_add(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), - &len_p, sizeof(len_p), NULL) == FAILURE) { - do_to_zval_err(ctx, "%s", "could not set parameter " KEY_CMSG_LEN); - return; - } - - entry->to_array((const char *)CMSG_DATA(cmsg), zv, ctx); - - zend_hash_del(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN)); -} -static void to_zval_read_control(const char *cmsghdr_c, zval *zv, res_context *ctx) -{ - /* takes a cmsghdr, not a msghdr like from_zval_write_control */ - static const field_descriptor descriptors[] = { - {"level", sizeof("level"), 0, offsetof(struct cmsghdr, cmsg_level), 0, to_zval_read_int}, - {"type", sizeof("type"), 0, offsetof(struct cmsghdr, cmsg_type), 0, to_zval_read_int}, - {"data", sizeof("data"), 0, 0 /* cmsghdr passed */, 0, to_zval_read_cmsg_data}, - {0} - }; - - array_init_size(zv, 3); - to_zval_read_aggregation(cmsghdr_c, zv, descriptors, ctx); -} -static void to_zval_read_control_array(const char *msghdr_c, zval *zv, res_context *ctx) -{ - struct msghdr *msg = (struct msghdr *)msghdr_c; - struct cmsghdr *cmsg; - char buf[sizeof("element #4294967295")]; - char *bufp = buf; - uint32_t i = 1; - - /*if (msg->msg_flags & MSG_CTRUNC) { - php_error_docref0(NULL, E_WARNING, "The MSG_CTRUNC flag is present; will not " - "attempt to read control messages"); - ZVAL_FALSE(zv); - return; - }*/ - - array_init(zv); - - for (cmsg = CMSG_FIRSTHDR(msg); - cmsg != NULL && !ctx->err.has_error; - cmsg = CMSG_NXTHDR(msg,cmsg)) { - zval *elem; - - ALLOC_INIT_ZVAL(elem); - add_next_index_zval(zv, elem); - - if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { - memcpy(buf, "element", sizeof("element")); - } - zend_llist_add_element(&ctx->keys, &bufp); - - to_zval_read_control((const char *)cmsg, elem, ctx); - - zend_llist_remove_tail(&ctx->keys); - } -} - -/* CONVERSIONS for msghdr */ -static void from_zval_write_name(const zval *zname_arr, char *msghdr_c, ser_context *ctx) -{ - struct sockaddr *sockaddr; - socklen_t sockaddr_len; - struct msghdr *msghdr = (struct msghdr *)msghdr_c; - - from_zval_write_sockaddr_aux(zname_arr, &sockaddr, &sockaddr_len, ctx); - - msghdr->msg_name = sockaddr; - msghdr->msg_namelen = sockaddr_len; -} -static void to_zval_read_name(const char *sockaddr_p, zval *zv, res_context *ctx) -{ - void *name = (void*)*(void**)sockaddr_p; - if (name == NULL) { - ZVAL_NULL(zv); - } else { - to_zval_read_sockaddr_aux(name, zv, ctx); - } -} -static void from_zval_write_msghdr_buffer_size(const zval *elem, char *msghdr_c, ser_context *ctx) -{ - long lval; - struct msghdr *msghdr = (struct msghdr *)msghdr_c; - - lval = from_zval_integer_common(elem, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval < 0 || lval > MAX_USER_BUFF_SIZE) { - do_from_zval_err(ctx, "the buffer size must be between 1 and %ld; " - "given %ld", (long)MAX_USER_BUFF_SIZE, lval); - return; - } - - msghdr->msg_iovlen = 1; - msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); - msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)lval, ctx); - msghdr->msg_iov[0].iov_len = (size_t)lval; -} -static void from_zval_write_iov_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) -{ - struct msghdr *msg = args[0]; - size_t len; - - zval_add_ref(elem); - convert_to_string_ex(elem); - - len = Z_STRLEN_PP(elem); - msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); - msg->msg_iov[i - 1].iov_len = len; - memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); - - zval_ptr_dtor(elem); -} -static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_context *ctx) -{ - int num_elem; - struct msghdr *msg = (struct msghdr*)msghdr_c; - - if (Z_TYPE_P(arr) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - return; - } - - num_elem = zend_hash_num_elements(Z_ARRVAL_P(arr)); - if (num_elem == 0) { - return; - } - - msg->msg_iov = accounted_safe_ecalloc(num_elem, sizeof *msg->msg_iov, 0, ctx); - msg->msg_iovlen = (size_t)num_elem; - - from_array_iterate(arr, from_zval_write_iov_array_aux, (void**)&msg, ctx); -} -static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_context *ctx) -{ - struct msghdr *msghdr = (struct msghdr *)msghdr_c; - uint32_t len; - - /* controllen should be an unsigned with at least 32-bit. Let's assume - * this least common denominator - */ - from_zval_write_uint32(elem, (char*)&len, ctx); - if (!ctx->err.has_error && len == 0) { - do_from_zval_err(ctx, "controllen cannot be 0"); - return; - } - msghdr->msg_control = accounted_emalloc(len, ctx); - msghdr->msg_controllen = len; -} -static void from_zval_write_msghdr_send(const zval *container, char *msghdr_c, ser_context *ctx) -{ - static const field_descriptor descriptors[] = { - {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, - {"iov", sizeof("iov"), 0, 0, from_zval_write_iov_array, 0}, - {"control", sizeof("control"), 0, 0, from_zval_write_control_array, 0}, - {0} - }; - - from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); -} -static void from_zval_write_msghdr_recv(const zval *container, char *msghdr_c, ser_context *ctx) -{ - /* zval to struct msghdr, version for recvmsg(). It differs from the version - * for sendmsg() in that it: - * - has a buffer_size instead of an iov array; - * - has no control element; has a controllen element instead - * struct msghdr { - * void *msg_name; - * socklen_t msg_namelen; - * struct iovec *msg_iov; - * size_t msg_iovlen; - * void *msg_control; - * size_t msg_controllen; //can also be socklen_t - * int msg_flags; - * }; - */ - static const field_descriptor descriptors[] = { - {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, - {"buffer_size", sizeof("buffer_size"), 0, 0, from_zval_write_msghdr_buffer_size, 0}, - {"controllen", sizeof("controllen"), 1, 0, from_zval_write_controllen, 0}, - {0} - }; - struct msghdr *msghdr = (struct msghdr *)msghdr_c; - const int falsev = 0, - *falsevp = &falsev; - - if (zend_hash_add(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR), - (void*)&falsevp, sizeof(falsevp), NULL) == FAILURE) { - do_from_zval_err(ctx, "could not add fill_sockaddr; this is a bug"); - return; - } - - from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); - - zend_hash_del(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR)); - if (ctx->err.has_error) { - return; - } - - if (msghdr->msg_iovlen == 0) { - msghdr->msg_iovlen = 1; - msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); - msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)DEFAULT_BUFF_SIZE, ctx); - msghdr->msg_iov[0].iov_len = (size_t)DEFAULT_BUFF_SIZE; - } -} - -static void to_zval_read_iov(const char *msghdr_c, zval *zv, res_context *ctx) -{ - const struct msghdr *msghdr = (const struct msghdr *)msghdr_c; - size_t iovlen = msghdr->msg_iovlen; - ssize_t **recvmsg_ret, - bytes_left; - uint i; - - if (iovlen > UINT_MAX) { - do_to_zval_err(ctx, "unexpectedly large value for iov_len: %lu", - (unsigned long)iovlen); - } - array_init_size(zv, (uint)iovlen); - - if (zend_hash_find(&ctx->params, KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), - (void**)&recvmsg_ret) == FAILURE) { - do_to_zval_err(ctx, "recvmsg_ret not found in params. This is a bug"); - return; - } - bytes_left = **recvmsg_ret; - - for (i = 0; bytes_left > 0 && i < (uint)iovlen; i++) { - zval *elem; - size_t len = MIN(msghdr->msg_iov[i].iov_len, bytes_left); - char *buf = safe_emalloc(1, len, 1); - - MAKE_STD_ZVAL(elem); - memcpy(buf, msghdr->msg_iov[i].iov_base, len); - buf[len] = '\0'; - - ZVAL_STRINGL(elem, buf, len, 0); - add_next_index_zval(zv, elem); - bytes_left -= len; - } -} -static void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx) -{ - static const field_descriptor descriptors[] = { - {"name", sizeof("name"), 0, offsetof(struct msghdr, msg_name), 0, to_zval_read_name}, - {"control", sizeof("control"), 0, 0, 0, to_zval_read_control_array}, - {"iov", sizeof("iov"), 0, 0, 0, to_zval_read_iov}, - {"flags", sizeof("flags"), 0, offsetof(struct msghdr, msg_flags), 0, to_zval_read_int}, - {0} - }; - - array_init_size(zv, 4); - - to_zval_read_aggregation(msghdr_c, zv, descriptors, ctx); -} - - -/* CONVERSIONS for struct in6_pktinfo */ -#ifdef IPV6_PKTINFO -static const field_descriptor descriptors_in6_pktinfo[] = { - {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, - {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_unsigned, to_zval_read_unsigned}, - {0} -}; -static void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) -{ - from_zval_write_aggregation(container, in6_pktinfo_c, descriptors_in6_pktinfo, ctx); -} -static void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx) -{ - array_init_size(zv, 2); - - to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); -} -#endif - -/* CONVERSIONS for struct ucred */ -#ifdef SO_PASSCRED -static const field_descriptor descriptors_ucred[] = { - {"pid", sizeof("pid"), 1, offsetof(struct ucred, pid), from_zval_write_pid_t, to_zval_read_pid_t}, - {"uid", sizeof("uid"), 1, offsetof(struct ucred, uid), from_zval_write_uid_t, to_zval_read_uid_t}, - /* assume the type gid_t is the same as uid_t: */ - {"gid", sizeof("gid"), 1, offsetof(struct ucred, gid), from_zval_write_uid_t, to_zval_read_uid_t}, - {0} -}; -static void from_zval_write_ucred(const zval *container, char *ucred_c, ser_context *ctx) -{ - from_zval_write_aggregation(container, ucred_c, descriptors_ucred, ctx); -} -static void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx) -{ - array_init_size(zv, 3); - - to_zval_read_aggregation(data, zv, descriptors_ucred, ctx); -} -#endif - -/* CONVERSIONS for SCM_RIGHTS */ -#ifdef SCM_RIGHTS -static size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) -{ - int num_elems; - - if (Z_TYPE_P(arr) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - return (size_t)-1; - } - - num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); - if (num_elems == 0) { - do_from_zval_err(ctx, "%s", "expected at least one element in this array"); - return (size_t)-1; - } - - return zend_hash_num_elements(Z_ARRVAL_P(arr)) * sizeof(int); -} -static void from_zval_write_fd_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) -{ - int *iarr = args[0]; - - if (Z_TYPE_PP(elem) == IS_RESOURCE) { - php_stream *stream; - php_socket *sock; - - ZEND_FETCH_RESOURCE_NO_RETURN(sock, php_socket *, elem, -1, - NULL, php_sockets_le_socket()); - if (sock) { - iarr[i] = sock->bsd_socket; - return; - } - - ZEND_FETCH_RESOURCE2_NO_RETURN(stream, php_stream *, elem, -1, - NULL, php_file_le_stream(), php_file_le_pstream()); - if (stream == NULL) { - do_from_zval_err(ctx, "resource is not a stream or a socket"); - return; - } - - if (php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&iarr[i], - REPORT_ERRORS) == FAILURE) { - do_from_zval_err(ctx, "cast stream to file descriptor failed"); - return; - } - } else { - do_from_zval_err(ctx, "expected a resource variable"); - } -} -static void from_zval_write_fd_array(const zval *arr, char *int_arr, ser_context *ctx) -{ - if (Z_TYPE_P(arr) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - return; - } - - from_array_iterate(arr, &from_zval_write_fd_array_aux, (void**)&int_arr, ctx); -} -static void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) -{ - size_t **cmsg_len; - int num_elems, - i; - struct cmsghdr *dummy_cmsg = 0; - size_t data_offset; - TSRMLS_FETCH(); - - data_offset = (unsigned char *)CMSG_DATA(dummy_cmsg) - - (unsigned char *)dummy_cmsg; - - if (zend_hash_find(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), - (void **)&cmsg_len) == FAILURE) { - do_to_zval_err(ctx, "could not get value of parameter " KEY_CMSG_LEN); - return; - } - - if (**cmsg_len < data_offset) { - do_to_zval_err(ctx, "length of cmsg is smaller than its data member " - "offset (%ld vs %ld)", (long)**cmsg_len, (long)data_offset); - return; - } - num_elems = (**cmsg_len - data_offset) / sizeof(int); - - array_init_size(zv, num_elems); - - for (i = 0; i < num_elems; i++) { - zval *elem; - int fd; - struct stat statbuf; - - MAKE_STD_ZVAL(elem); - - fd = *((int *)data + i); - - /* determine whether we have a socket */ - if (fstat(fd, &statbuf) == -1) { - do_to_zval_err(ctx, "error creating resource for received file " - "descriptor %d: fstat() call failed with errno %d", fd, errno); - efree(elem); - return; - } - if (S_ISSOCK(statbuf.st_mode)) { - php_socket *sock = socket_import_file_descriptor(fd); - zend_register_resource(elem, sock, php_sockets_le_socket()); - } else { - php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); - php_stream_to_zval(stream, elem); - } - - add_next_index_zval(zv, elem); - } -} -#endif - -/* ENTRY POINT for conversions */ -static void free_from_zval_allocation(void *alloc_ptr_ptr) -{ - efree(*(void**)alloc_ptr_ptr); -} -static void *from_zval_run_conversions(const zval *container, - php_socket *sock, - from_zval_write_field *writer, - size_t struct_size, - const char *top_name, - zend_llist **allocations /* out */, - struct err_s *err /* in/out */) -{ - ser_context ctx = {{0}}; - char *structure = NULL; - - *allocations = NULL; - - if (err->has_error) { - return NULL; - } - - zend_hash_init(&ctx.params, 8, NULL, NULL, 0); - zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); - zend_llist_init(&ctx.allocations, sizeof(void *), &free_from_zval_allocation, 0); - ctx.sock = sock; - - structure = ecalloc(1, struct_size); - - zend_llist_add_element(&ctx.keys, &top_name); - zend_llist_add_element(&ctx.allocations, &structure); - - /* main call */ - writer(container, structure, &ctx); - - if (ctx.err.has_error) { - zend_llist_destroy(&ctx.allocations); /* deallocates structure as well */ - structure = NULL; - *err = ctx.err; - } else { - *allocations = emalloc(sizeof **allocations); - **allocations = ctx.allocations; - } - - zend_llist_destroy(&ctx.keys); - zend_hash_destroy(&ctx.params); - - return structure; -} -static zval *to_zval_run_conversions(const char *structure, - to_zval_read_field *reader, - const char *top_name, - const struct key_value *key_value_pairs, - struct err_s *err) -{ - res_context ctx = {{0}, {0}}; - const struct key_value *kv; - zval *zv = NULL; - - if (err->has_error) { - return NULL; - } - - ALLOC_INIT_ZVAL(zv); - - zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); - zend_llist_add_element(&ctx.keys, &top_name); - - zend_hash_init(&ctx.params, 8, NULL, NULL, 0); - for (kv = key_value_pairs; kv->key != NULL; kv++) { - zend_hash_update(&ctx.params, kv->key, kv->key_size, - (void*)&kv->value, sizeof(kv->value), NULL); - } - - /* main call */ - reader(structure, zv, &ctx); - - if (ctx.err.has_error) { - zval_ptr_dtor(&zv); - zv = NULL; - *err = ctx.err; - } - - zend_llist_destroy(&ctx.keys); - zend_hash_destroy(&ctx.params); - - return zv; -} #ifdef ZTS static MUTEX_T ancillary_mutex; @@ -1555,7 +98,7 @@ static void destroy_ancillary_registry(void) ancillary_registry.initialized = 0; } } -static ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) +ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type) { anc_reg_key key = { cmsg_level, msg_type }; ancillary_reg_entry *entry; diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h index 82fb38b4b5f..55dca3c1fbc 100644 --- a/ext/sockets/sendrecvmsg.h +++ b/ext/sockets/sendrecvmsg.h @@ -1,5 +1,10 @@ -#include +#ifndef PHP_SENDRECVMSG_H +#define PHP_SENDRECVMSG_H 1 +#include +#include "conversions.h" + +/* for sockets.c */ PHP_FUNCTION(socket_sendmsg); PHP_FUNCTION(socket_recvmsg); PHP_FUNCTION(socket_cmsg_space); @@ -9,3 +14,23 @@ void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4); int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result); + +/* for conversions.c */ +typedef struct { + int cmsg_level; /* originating protocol */ + int cmsg_type; /* protocol-specific type */ +} anc_reg_key; + +typedef size_t (calculate_req_space)(const zval *value, ser_context *ctx); + +typedef struct { + socklen_t size; /* size of native structure */ + socklen_t var_el_size; /* size of repeatable component */ + calculate_req_space *calc_space; + from_zval_write_field *from_array; + to_zval_read_field *to_array; +} ancillary_reg_entry; + +ancillary_reg_entry *get_ancillary_reg_entry(int cmsg_level, int msg_type); + +#endif From bd580db373ce35aa2e60ca452ae4eb1984b0520e Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Tue, 1 Jan 2013 23:38:19 +0100 Subject: [PATCH 30/40] Build fixes; accept names for if_index --- ext/sockets/conversions.c | 61 +++++++++++++++++++++------------------ ext/sockets/multicast.c | 4 +-- ext/sockets/multicast.h | 4 +-- ext/sockets/sendrecvmsg.c | 10 +++---- ext/sockets/sendrecvmsg.h | 4 +-- ext/sockets/sockets.c | 10 +++---- 6 files changed, 48 insertions(+), 45 deletions(-) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index 7ca9972ac0d..54631604ab4 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -12,6 +12,9 @@ #include #include +#include +#include + #include #include #include @@ -53,6 +56,8 @@ typedef struct { #define KEY_RECVMSG_RET "recvmsg_ret" #define KEY_CMSG_LEN "cmsg_len" +const struct key_value empty_key_value_list[] = {{0}}; + /* PARAMETERS */ static int param_get_bool(void *ctx, const char *key, int def) { @@ -331,25 +336,6 @@ void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) ival = (int)lval; memcpy(field, &ival, sizeof(ival)); } -static void from_zval_write_unsigned(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - unsigned ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (sizeof(long) > sizeof(ival) && (lval < 0 || lval > UINT_MAX)) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for a native unsigned int"); - return; - } - - ival = (unsigned)lval; - memcpy(field, &ival, sizeof(ival)); -} static void from_zval_write_uint32(const zval *arr_value, char *field, ser_context *ctx) { long lval; @@ -1192,20 +1178,17 @@ void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx) /* CONVERSIONS for if_index */ static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context *ctx) { - zval *va; unsigned *out; - unsigned ret; - zval lzval = zval_used_for_init; + unsigned ret; + zval lzval = zval_used_for_init; if (Z_TYPE_P(zv) == IS_LONG) { - if (Z_LVAL_P(zv) < 0 || Z_LVAL_P(zv) > UINT_MAX) { + if (Z_LVAL_P(zv) < 0 || Z_LVAL_P(zv) > UINT_MAX) { /* allow 0 (unspecified interface) */ do_from_zval_err(ctx, "the interface index cannot be negative or " "larger than %u; given %ld", UINT_MAX, Z_LVAL_P(zv)); } else { ret = (unsigned)Z_LVAL_P(zv); } } else { -#if HAVE_IF_NAMETOINDEX - if (Z_TYPE_P(zv) != IS_STRING) { ZVAL_COPY_VALUE(&lzval, zv); zval_copy_ctor(&lzval); @@ -1213,11 +1196,32 @@ static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context zv = &lzval; } +#if HAVE_IF_NAMETOINDEX ret = if_nametoindex(Z_STRVAL_P(zv)); if (ret == 0) { do_from_zval_err(ctx, "no interface with name \"%s\" could be " "found", Z_STRVAL_P(zv)); } +#elif defined(SIOCGIFINDEX) + { + struct ifreq ifr; + if (strlcpy(ifr.ifr_name, Z_STRVAL_P(zv), sizeof(ifr.ifr_name)) + >= sizeof(ifr.ifr_name)) { + do_from_zval_err(ctx, "the interface name \"%s\" is too large ", + Z_STRVAL_P(zv)); + } else if (ioctl(ctx->sock->bsd_socket, SIOCGIFINDEX, &ifr) < 0) { + if (errno == ENODEV) { + do_from_zval_err(ctx, "no interface with name \"%s\" could be " + "found", Z_STRVAL_P(zv)); + } else { + do_from_zval_err(ctx, "error fetching interface index for " + "interface with name \"%s\" (errno %d)", + Z_STRVAL_P(zv), errno); + } + } else { + ret = (unsigned)ifr.ifr_ifindex; + } + } #else do_from_zval_err(ctx, "this platform does not support looking up an interface by " @@ -1236,7 +1240,7 @@ static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context #ifdef IPV6_PKTINFO static const field_descriptor descriptors_in6_pktinfo[] = { {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, - {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_unsigned, to_zval_read_unsigned}, + {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_ifindex, to_zval_read_unsigned}, {0} }; void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) @@ -1294,6 +1298,7 @@ size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) static void from_zval_write_fd_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) { int *iarr = args[0]; + TSRMLS_FETCH(); if (Z_TYPE_PP(elem) == IS_RESOURCE) { php_stream *stream; @@ -1375,8 +1380,8 @@ void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) return; } if (S_ISSOCK(statbuf.st_mode)) { - php_socket *sock = socket_import_file_descriptor(fd); - zend_register_resource(elem, sock, php_sockets_le_socket()); + php_socket *sock = socket_import_file_descriptor(fd TSRMLS_CC); + zend_register_resource(elem, sock, php_sockets_le_socket() TSRMLS_CC); } else { php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); php_stream_to_zval(stream, elem); diff --git a/ext/sockets/multicast.c b/ext/sockets/multicast.c index dc242693acc..5d29c9c5e26 100644 --- a/ext/sockets/multicast.c +++ b/ext/sockets/multicast.c @@ -250,7 +250,7 @@ mcast_req_fun: int php_do_setsockopt_ip_mcast(php_socket *php_sock, int level, int optname, - zval **arg4) + zval **arg4 TSRMLS_DC) { unsigned int if_index; struct in_addr if_addr; @@ -319,7 +319,7 @@ dosockopt: int php_do_setsockopt_ipv6_mcast(php_socket *php_sock, int level, int optname, - zval **arg4) + zval **arg4 TSRMLS_DC) { unsigned int if_index; void *opt_ptr; diff --git a/ext/sockets/multicast.h b/ext/sockets/multicast.h index c363b584727..1ad4673feb7 100644 --- a/ext/sockets/multicast.h +++ b/ext/sockets/multicast.h @@ -30,12 +30,12 @@ int php_do_setsockopt_ip_mcast(php_socket *php_sock, int level, int optname, - zval **arg4); + zval **arg4 TSRMLS_DC); int php_do_setsockopt_ipv6_mcast(php_socket *php_sock, int level, int optname, - zval **arg4); + zval **arg4 TSRMLS_DC); int php_if_index_to_addr4( unsigned if_index, diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index 6479bf90a85..b83b3ae4823 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -272,7 +272,7 @@ PHP_FUNCTION(socket_cmsg_space) RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->var_el_size)); } -int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4) +int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4 TSRMLS_DC) { struct err_s err = {0}; zend_llist *allocations = NULL; @@ -311,7 +311,7 @@ dosockopt: return retval != 0 ? FAILURE : SUCCESS; } -int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result) +int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result TSRMLS_DC) { struct err_s err = {0}; void *buffer; @@ -340,7 +340,7 @@ int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo", empty_key_value_list, &err); if (err.has_error) { - err_msg_dispose(&err); + err_msg_dispose(&err TSRMLS_CC); res = -1; } else { ZVAL_COPY_VALUE(result, zv); @@ -354,9 +354,7 @@ int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS) { - /* IPv6 ancillary data - * Note that support for sticky options via setsockopt() is not implemented - * yet (where special support is needed, i.e., the optval is not an int). */ + /* IPv6 ancillary data */ #ifdef IPV6_RECVPKTINFO REGISTER_LONG_CONSTANT("IPV6_RECVPKTINFO", IPV6_RECVPKTINFO, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IPV6_PKTINFO", IPV6_PKTINFO, CONST_CS | CONST_PERSISTENT); diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h index 55dca3c1fbc..5a3798274fd 100644 --- a/ext/sockets/sendrecvmsg.h +++ b/ext/sockets/sendrecvmsg.h @@ -12,8 +12,8 @@ PHP_FUNCTION(socket_cmsg_space); void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS); void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS); -int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4); -int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result); +int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4 TSRMLS_DC); +int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result TSRMLS_DC); /* for conversions.c */ typedef struct { diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 9c57e2d98d2..2aeb4b0fd33 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -1878,7 +1878,7 @@ PHP_FUNCTION(socket_get_option) } } } else if (level == IPPROTO_IPV6) { - int ret = php_do_getsockopt_ipv6_rfc3542(php_sock, level, optname, return_value); + int ret = php_do_getsockopt_ipv6_rfc3542(php_sock, level, optname, return_value TSRMLS_CC); if (ret == SUCCESS) { return; } else if (ret == FAILURE) { @@ -1981,15 +1981,15 @@ PHP_FUNCTION(socket_set_option) if (level == IPPROTO_IP) { - int res = php_do_setsockopt_ip_mcast(php_sock, level, optname, arg4); + int res = php_do_setsockopt_ip_mcast(php_sock, level, optname, arg4 TSRMLS_CC); HANDLE_SUBCALL(res); } #if HAVE_IPV6 else if (level == IPPROTO_IPV6) { - int res = php_do_setsockopt_ipv6_mcast(php_sock, level, optname, arg4); + int res = php_do_setsockopt_ipv6_mcast(php_sock, level, optname, arg4 TSRMLS_CC); if (res == 1) { - res = php_do_setsockopt_ipv6_rfc3542(php_sock, level, optname, arg4); + res = php_do_setsockopt_ipv6_rfc3542(php_sock, level, optname, arg4 TSRMLS_CC); } HANDLE_SUBCALL(res); } @@ -2273,7 +2273,7 @@ PHP_FUNCTION(socket_import_stream) RETURN_FALSE; } - retsock = socket_import_file_descriptor(socket); + retsock = socket_import_file_descriptor(socket TSRMLS_CC); if (retsock == NULL) { RETURN_FALSE; } From c846fcef685c14a42ae770d56340a41d936deae9 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 00:40:17 +0100 Subject: [PATCH 31/40] Fix buf in string -> int conv. --- ext/sockets/conversions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index 54631604ab4..9cbc6e5f88e 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -297,7 +297,7 @@ double_case: case IS_LONG: zval_dtor(&lzval); Z_TYPE(lzval) = IS_LONG; - Z_DVAL(lzval) = lval; + Z_LVAL(lzval) = lval; goto long_case; } From f10baf14eda4a6fd0e4c8a24d008975184e31207 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 00:59:05 +0100 Subject: [PATCH 32/40] Payload of HOPLIMIT/TCLASS are 8-bit --- ext/sockets/conversions.c | 30 ++++++++++++++++++++++++++++-- ext/sockets/conversions.h | 4 ++-- ext/sockets/sendrecvmsg.c | 10 ++++++---- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index 9cbc6e5f88e..ef1f884210a 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -317,7 +317,7 @@ double_case: return ret; } -void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) +static void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) { long lval; int ival; @@ -355,6 +355,25 @@ static void from_zval_write_uint32(const zval *arr_value, char *field, ser_conte ival = (uint32_t)lval; memcpy(field, &ival, sizeof(ival)); } +void from_zval_write_uint8(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint8_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > 0xFF) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 8-bit integer"); + return; + } + + ival = (uint8_t)lval; + memcpy(field, &ival, sizeof(ival)); +} static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) { long lval; @@ -441,7 +460,7 @@ static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_contex memcpy(field, &ival, sizeof(ival)); } -void to_zval_read_int(const char *data, zval *zv, res_context *ctx) +static void to_zval_read_int(const char *data, zval *zv, res_context *ctx) { int ival; memcpy(&ival, data, sizeof(ival)); @@ -455,6 +474,13 @@ static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) ZVAL_LONG(zv, (long)ival); } +void to_zval_read_uint8(const char *data, zval *zv, res_context *ctx) +{ + uint8_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) { uint16_t ival; diff --git a/ext/sockets/conversions.h b/ext/sockets/conversions.h index 70f31ba676e..79ca4ab76e2 100644 --- a/ext/sockets/conversions.h +++ b/ext/sockets/conversions.h @@ -37,8 +37,8 @@ void err_msg_dispose(struct err_s *err TSRMLS_DC); void allocations_dispose(zend_llist **allocations); /* CONVERSION FUNCTIONS */ -void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx); -void to_zval_read_int(const char *data, zval *zv, res_context *ctx); +void from_zval_write_uint8(const zval *arr_value, char *field, ser_context *ctx); +void to_zval_read_uint8(const char *data, zval *zv, res_context *ctx); #ifdef IPV6_PKTINFO void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx); diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index b83b3ae4823..f325b0378a9 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -73,12 +73,14 @@ static void init_ancillary_registry(void) #endif #ifdef IPV6_HOPLIMIT - PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, - to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT); + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_uint8, + to_zval_read_uint8, IPPROTO_IPV6, IPV6_HOPLIMIT); #endif - PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, - to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS); +#ifdef IPV6_TCLASS + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_uint8, + to_zval_read_uint8, IPPROTO_IPV6, IPV6_TCLASS); +#endif #ifdef SO_PASSCRED PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred, From 5c0a8b1a2a34ec504091e4e105e1c3b79d9fff89 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 15:25:55 +0100 Subject: [PATCH 33/40] Ensure memory is initialized --- ext/sockets/conversions.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index ef1f884210a..d0d0c4b7986 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -858,6 +858,7 @@ static void from_zval_write_control(const zval *arr, if (space_left < req_space) { *control_buf = safe_erealloc(*control_buf, 2, req_space, *control_len); *control_len += 2 * req_space; + memset(*control_buf, '\0', *control_len - *offset); memcpy(&alloc->data, *control_buf, sizeof *control_buf); } From 95f8d34f9c0980924098ce9554e899e461ce7cec Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 15:26:10 +0100 Subject: [PATCH 34/40] Revert "Payload of HOPLIMIT/TCLASS are 8-bit" This reverts commit 61a5ec7381ba5388a52926779fe3f58af0caea83. I checked Linux and OpenBSD and both use integers to write the IPV6_TCLASS messages and they don't force any endianness. This is despite RFC 3542 explicitly saying the first byte of cmsg_data will have the result. In any case, it doesn't make any difference in little-endian archs. --- ext/sockets/conversions.c | 30 ++---------------------------- ext/sockets/conversions.h | 4 ++-- ext/sockets/sendrecvmsg.c | 10 ++++------ 3 files changed, 8 insertions(+), 36 deletions(-) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index d0d0c4b7986..fa6d9494867 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -317,7 +317,7 @@ double_case: return ret; } -static void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) +void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) { long lval; int ival; @@ -355,25 +355,6 @@ static void from_zval_write_uint32(const zval *arr_value, char *field, ser_conte ival = (uint32_t)lval; memcpy(field, &ival, sizeof(ival)); } -void from_zval_write_uint8(const zval *arr_value, char *field, ser_context *ctx) -{ - long lval; - uint8_t ival; - - lval = from_zval_integer_common(arr_value, ctx); - if (ctx->err.has_error) { - return; - } - - if (lval < 0 || lval > 0xFF) { - do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " - "for an unsigned 8-bit integer"); - return; - } - - ival = (uint8_t)lval; - memcpy(field, &ival, sizeof(ival)); -} static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) { long lval; @@ -460,7 +441,7 @@ static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_contex memcpy(field, &ival, sizeof(ival)); } -static void to_zval_read_int(const char *data, zval *zv, res_context *ctx) +void to_zval_read_int(const char *data, zval *zv, res_context *ctx) { int ival; memcpy(&ival, data, sizeof(ival)); @@ -474,13 +455,6 @@ static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) ZVAL_LONG(zv, (long)ival); } -void to_zval_read_uint8(const char *data, zval *zv, res_context *ctx) -{ - uint8_t ival; - memcpy(&ival, data, sizeof(ival)); - - ZVAL_LONG(zv, (long)ival); -} static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) { uint16_t ival; diff --git a/ext/sockets/conversions.h b/ext/sockets/conversions.h index 79ca4ab76e2..70f31ba676e 100644 --- a/ext/sockets/conversions.h +++ b/ext/sockets/conversions.h @@ -37,8 +37,8 @@ void err_msg_dispose(struct err_s *err TSRMLS_DC); void allocations_dispose(zend_llist **allocations); /* CONVERSION FUNCTIONS */ -void from_zval_write_uint8(const zval *arr_value, char *field, ser_context *ctx); -void to_zval_read_uint8(const char *data, zval *zv, res_context *ctx); +void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx); +void to_zval_read_int(const char *data, zval *zv, res_context *ctx); #ifdef IPV6_PKTINFO void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx); diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index f325b0378a9..b83b3ae4823 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -73,14 +73,12 @@ static void init_ancillary_registry(void) #endif #ifdef IPV6_HOPLIMIT - PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_uint8, - to_zval_read_uint8, IPPROTO_IPV6, IPV6_HOPLIMIT); + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, + to_zval_read_int, IPPROTO_IPV6, IPV6_HOPLIMIT); #endif -#ifdef IPV6_TCLASS - PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_uint8, - to_zval_read_uint8, IPPROTO_IPV6, IPV6_TCLASS); -#endif + PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int, + to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS); #ifdef SO_PASSCRED PUT_ENTRY(sizeof(struct ucred), 0, 0, from_zval_write_ucred, From 8561680533c7fd6b66497ed10246fe9e57e9d351 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Wed, 30 Jan 2013 21:40:45 +0100 Subject: [PATCH 35/40] Remove some pre-vista code --- win32/inet.c | 79 ---------------------------------------------------- win32/inet.h | 11 ++------ 2 files changed, 2 insertions(+), 88 deletions(-) diff --git a/win32/inet.c b/win32/inet.c index d424c8a5468..686cf1265c5 100644 --- a/win32/inet.c +++ b/win32/inet.c @@ -1,83 +1,4 @@ -#include "config.w32.h" -#include "php.h" -#include -#include -#include - #include "inet.h" -#if (_WIN32_WINNT < 0x0600) /* Vista/2k8 have these functions */ - - -PHPAPI int inet_pton(int af, const char* src, void* dst) -{ - int address_length; - struct sockaddr_storage sa; - struct sockaddr_in *sin = (struct sockaddr_in *)&sa; - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sa; - - switch (af) { - case AF_INET: - address_length = sizeof (struct sockaddr_in); - break; - - case AF_INET6: - address_length = sizeof (struct sockaddr_in6); - break; - - default: - return -1; - } - - if (WSAStringToAddress ((LPTSTR) src, af, NULL, (LPSOCKADDR) &sa, &address_length) == 0) { - switch (af) { - case AF_INET: - memcpy (dst, &sin->sin_addr, sizeof (struct in_addr)); - break; - - case AF_INET6: - memcpy (dst, &sin6->sin6_addr, sizeof (struct in6_addr)); - break; - } - return 1; - } - - return 0; -} - -PHPAPI const char* inet_ntop(int af, const void* src, char* dst, size_t size) -{ - int address_length; - DWORD string_length = size; - struct sockaddr_storage sa; - struct sockaddr_in *sin = (struct sockaddr_in *)&sa; - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sa; - - memset (&sa, 0, sizeof (sa)); - switch (af) { - case AF_INET: - address_length = sizeof (struct sockaddr_in); - sin->sin_family = af; - memcpy (&sin->sin_addr, src, sizeof (struct in_addr)); - break; - - case AF_INET6: - address_length = sizeof (struct sockaddr_in6); - sin6->sin6_family = af; - memcpy (&sin6->sin6_addr, src, sizeof (struct in6_addr)); - break; - - default: - return NULL; - } - - if (WSAAddressToString ((LPSOCKADDR) &sa, address_length, NULL, dst, &string_length) == 0) { - return dst; - } - - return NULL; -} - -#endif int inet_aton(const char *cp, struct in_addr *inp) { inp->s_addr = inet_addr(cp); diff --git a/win32/inet.h b/win32/inet.h index 623d114dfd5..d71723759fa 100644 --- a/win32/inet.h +++ b/win32/inet.h @@ -1,11 +1,4 @@ -#if _MSC_VER >= 1500 -# include -#endif -#include - -#if (_WIN32_WINNT <= 0x502) -PHPAPI int inet_pton(int af, const char* src, void* dst); -PHPAPI const char* inet_ntop(int af, const void* src, char* dst, size_t size); -#endif +#include +#include PHPAPI int inet_aton(const char *cp, struct in_addr *inp); From 7066cc726713f0eaf79db9eac0da90bc6d8740b4 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 14:01:31 +0100 Subject: [PATCH 36/40] send/recvmsg() support for Windows --- ext/sockets/config.w32 | 2 +- ext/sockets/conversions.c | 69 ++++++++--- ext/sockets/conversions.h | 10 +- ext/sockets/php_sockets.h | 4 +- ext/sockets/sendrecvmsg.c | 54 ++++++++- ext/sockets/sockaddr_conv.c | 2 +- ext/sockets/sockaddr_conv.h | 10 +- ext/sockets/sockets.c | 30 ++--- .../socket_sendrecvmsg_multi_msg-win32.phpt | 110 ++++++++++++++++++ .../tests/socket_sendrecvmsg_multi_msg.phpt | 19 +-- .../tests/socket_set_option_in6_pktinfo.phpt | 3 +- ext/sockets/windows_common.h | 41 +++++++ 12 files changed, 300 insertions(+), 54 deletions(-) create mode 100644 ext/sockets/tests/socket_sendrecvmsg_multi_msg-win32.phpt create mode 100644 ext/sockets/windows_common.h diff --git a/ext/sockets/config.w32 b/ext/sockets/config.w32 index 9c234db8f8d..aeaa8ed425e 100644 --- a/ext/sockets/config.w32 +++ b/ext/sockets/config.w32 @@ -7,7 +7,7 @@ if (PHP_SOCKETS != "no") { if (CHECK_LIB("ws2_32.lib", "sockets", PHP_SOCKETS) && CHECK_LIB("Iphlpapi.lib", "sockets", PHP_SOCKETS) && CHECK_HEADER_ADD_INCLUDE("winsock.h", "CFLAGS_SOCKETS")) { - EXTENSION('sockets', 'sockets.c multicast.c'); + EXTENSION('sockets', 'sockets.c multicast.c conversions.c sockaddr_conv.c sendrecvmsg.c'); AC_DEFINE('HAVE_SOCKETS', 1); PHP_INSTALL_HEADERS("ext/sockets", "php_sockets.h"); } else { diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index fa6d9494867..6c79166806b 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -1,24 +1,67 @@ -#include "conversions.h" #include "sockaddr_conv.h" #include "conversions.h" #include "sendrecvmsg.h" /* for ancillary registry */ +#include "windows_common.h" #include #include -#include -#include -#include -#include -#include - -#include -#include +#ifndef PHP_WIN32 +# include +# include +# include +# include +# include +# include +# include +#else +# include +#endif #include #include #include +#ifdef PHP_WIN32 +typedef unsigned short sa_family_t; +# define msghdr _WSAMSG +/* +struct _WSAMSG { + LPSOCKADDR name; //void *msg_name + INT namelen; //socklen_t msg_namelen + LPWSABUF lpBuffers; //struct iovec *msg_iov + ULONG dwBufferCount; //size_t msg_iovlen + WSABUF Control; //void *msg_control, size_t msg_controllen + DWORD dwFlags; //int msg_flags +} +struct __WSABUF { + u_long len; //size_t iov_len (2nd member) + char FAR *buf; //void *iov_base (1st member) +} +struct _WSACMSGHDR { + UINT cmsg_len; //socklen_t cmsg_len + INT cmsg_level; //int cmsg_level + INT cmsg_type; //int cmsg_type; + followed by UCHAR cmsg_data[] +} +*/ +# define msg_name name +# define msg_namelen namelen +# define msg_iov lpBuffers +# define msg_iovlen dwBufferCount +# define msg_control Control.buf +# define msg_controllen Control.len +# define msg_flags dwFlags +# define iov_base buf +# define iov_len len + +# define cmsghdr _WSACMSGHDR +# ifdef CMSG_DATA +# undef CMSG_DATA +# endif +# define CMSG_DATA WSA_CMSG_DATA +#endif + #define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024)) #define DEFAULT_BUFF_SIZE 8192 @@ -132,7 +175,7 @@ static void do_from_to_zval_err(struct err_s *err, efree(user_msg); smart_str_free_ex(&path, 0); } -__attribute__ ((format (printf, 2, 3))) +ZEND_ATTRIBUTE_FORMAT(printf, 2 ,3) static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) { va_list ap; @@ -141,7 +184,7 @@ static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); va_end(ap); } -__attribute__ ((format (printf, 2, 3))) +ZEND_ATTRIBUTE_FORMAT(printf, 2 ,3) static void do_to_zval_err(res_context *ctx, const char *fmt, ...) { va_list ap; @@ -958,7 +1001,7 @@ static void to_zval_read_control_array(const char *msghdr_c, zval *zv, res_conte for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL && !ctx->err.has_error; - cmsg = CMSG_NXTHDR(msg,cmsg)) { + cmsg = CMSG_NXTHDR(msg, cmsg)) { zval *elem; ALLOC_INIT_ZVAL(elem); @@ -1149,7 +1192,7 @@ static void to_zval_read_iov(const char *msghdr_c, zval *zv, res_context *ctx) for (i = 0; bytes_left > 0 && i < (uint)iovlen; i++) { zval *elem; - size_t len = MIN(msghdr->msg_iov[i].iov_len, bytes_left); + size_t len = MIN(msghdr->msg_iov[i].iov_len, (size_t)bytes_left); char *buf = safe_emalloc(1, len, 1); MAKE_STD_ZVAL(elem); diff --git a/ext/sockets/conversions.h b/ext/sockets/conversions.h index 70f31ba676e..7d515246a0b 100644 --- a/ext/sockets/conversions.h +++ b/ext/sockets/conversions.h @@ -2,8 +2,14 @@ #define PHP_SOCK_CONVERSIONS_H 1 #include -#include -#include + +#ifndef PHP_WIN32 +# include +# include +#else +# include +#endif + #include "php_sockets.h" /* TYPE DEFINITIONS */ diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 78da0c29e68..a5699c75149 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -26,11 +26,13 @@ #if HAVE_SOCKETS +#include + extern zend_module_entry sockets_module_entry; #define phpext_sockets_ptr &sockets_module_entry #ifdef PHP_WIN32 -#include +#include #else #if HAVE_SYS_SOCKET_H #include diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c index b83b3ae4823..f75fdcdedea 100644 --- a/ext/sockets/sendrecvmsg.c +++ b/ext/sockets/sendrecvmsg.c @@ -30,6 +30,44 @@ #define DEFAULT_BUFF_SIZE 8192 #define MAX_ARRAY_KEY_SIZE 128 +#ifdef PHP_WIN32 +#include "windows_common.h" +#include +#define IPV6_RECVPKTINFO IPV6_PKTINFO +#define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT +#define msghdr _WSAMSG + +static GUID WSARecvMsg_GUID = WSAID_WSARECVMSG; +static __declspec(thread) LPFN_WSARECVMSG WSARecvMsg = NULL; +inline ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + DWORD recvd = 0, + bytesReturned; + + if (WSARecvMsg == NULL) { + int res = WSAIoctl((SOCKET) sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER, + &WSARecvMsg_GUID, sizeof(WSARecvMsg_GUID), + &WSARecvMsg, sizeof(WSARecvMsg), + &bytesReturned, NULL, NULL); + if (res != 0) { + return -1; + } + } + + msg->dwFlags = (DWORD)flags; + return WSARecvMsg((SOCKET)sockfd, msg, &recvd, NULL, NULL) == 0 + ? (ssize_t)recvd + : -1; +} +inline ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + DWORD sent = 0; + return WSASendMsg((SOCKET)sockfd, (struct msghdr*)msg, (DWORD)flags, &sent, NULL, NULL) == 0 + ? (ssize_t)sent + : -1; +} +#endif + #define LONG_CHECK_VALID_INT(l) \ do { \ if ((l) < INT_MIN && (l) > INT_MAX) { \ @@ -158,9 +196,7 @@ PHP_FUNCTION(socket_sendmsg) RETURN_LONG((long)res); } else { - SOCKETS_G(last_error) = errno; - php_error_docref(NULL TSRMLS_CC, E_WARNING, "error in sendmsg [%d]: %s", - errno, sockets_strerror(errno TSRMLS_CC)); + PHP_SOCKET_ERROR(php_sock, "error in sendmsg", errno); RETURN_FALSE; } } @@ -285,6 +321,18 @@ int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, switch (optname) { #ifdef IPV6_PKTINFO case IPV6_PKTINFO: +#ifdef PHP_WIN32 + if (Z_TYPE_PP(arg4) == IS_ARRAY) { + php_error_docref0(NULL TSRMLS_CC, E_WARNING, "Windows does not " + "support sticky IPV6_PKTINFO"); + return FAILURE; + } else { + /* windows has no IPV6_RECVPKTINFO, and uses IPV6_PKTINFO + * for the same effect. We define IPV6_RECVPKTINFO to be + * IPV6_PKTINFO, so assume the assume user used IPV6_RECVPKTINFO */ + return 1; + } +#endif opt_ptr = from_zval_run_conversions(*arg4, php_sock, from_zval_write_in6_pktinfo, sizeof(struct in6_pktinfo), "in6_pktinfo", &allocations, &err); if (err.has_error) { diff --git a/ext/sockets/sockaddr_conv.c b/ext/sockets/sockaddr_conv.c index 19c61740d0c..a40b6b4936c 100644 --- a/ext/sockets/sockaddr_conv.c +++ b/ext/sockets/sockaddr_conv.c @@ -3,7 +3,7 @@ #include "php_sockets.h" #ifdef PHP_WIN32 -#include +#include "windows_common.h" #else #include #include diff --git a/ext/sockets/sockaddr_conv.h b/ext/sockets/sockaddr_conv.h index 444d749fe73..665c73913f1 100644 --- a/ext/sockets/sockaddr_conv.h +++ b/ext/sockets/sockaddr_conv.h @@ -1,8 +1,16 @@ #ifndef PHP_SOCKADR_CONV_H #define PHP_SOCKADR_CONV_H +#define HAVE_SOCKETS 1 #include -#include "php_sockets.h" +#include "php_sockets.h" /* php_socket */ + +#ifndef PHP_WIN32 +# include +#else +# include +#endif + /* * Convert an IPv6 literal or a hostname info a sockaddr_in6. diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 2aeb4b0fd33..5636cd3cdc9 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -35,32 +35,12 @@ #include "ext/standard/info.h" #include "php_ini.h" #ifdef PHP_WIN32 -# include "win32/inet.h" -# include +# include "windows_common.h" +# include # include # include # include "php_sockets.h" -# include "win32/sockets.h" -# define IS_INVALID_SOCKET(a) (a->bsd_socket == INVALID_SOCKET) -# ifdef EPROTONOSUPPORT -# undef EPROTONOSUPPORT -# endif -# ifdef ECONNRESET -# undef ECONNRESET -# endif -# define EPROTONOSUPPORT WSAEPROTONOSUPPORT -# define ECONNRESET WSAECONNRESET -# ifdef errno -# undef errno -# endif -# define errno WSAGetLastError() -# define h_errno WSAGetLastError() -# define set_errno(a) WSASetLastError(a) -# define close(a) closesocket(a) -# include -# if _WIN32_WINNT >= 0x0600 -# define HAVE_IF_NAMETOINDEX 1 -# endif +# include #else # include # include @@ -650,8 +630,12 @@ PHP_MINIT_FUNCTION(sockets) REGISTER_LONG_CONSTANT("MSG_TRUNC", MSG_TRUNC, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_PEEK", MSG_PEEK, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MSG_DONTROUTE", MSG_DONTROUTE, CONST_CS | CONST_PERSISTENT); +#ifdef MSG_EOR REGISTER_LONG_CONSTANT("MSG_EOR", MSG_EOR, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef MSG_EOF REGISTER_LONG_CONSTANT("MSG_EOF", MSG_EOF, CONST_CS | CONST_PERSISTENT); +#endif #ifdef MSG_CONFIRM REGISTER_LONG_CONSTANT("MSG_CONFIRM", MSG_CONFIRM, CONST_CS | CONST_PERSISTENT); diff --git a/ext/sockets/tests/socket_sendrecvmsg_multi_msg-win32.phpt b/ext/sockets/tests/socket_sendrecvmsg_multi_msg-win32.phpt new file mode 100644 index 00000000000..3aba012726f --- /dev/null +++ b/ext/sockets/tests/socket_sendrecvmsg_multi_msg-win32.phpt @@ -0,0 +1,110 @@ +--TEST-- +sendmsg()/recvmsg(): test ability to receive multiple messages (WIN32) +--SKIPIF-- + [ "addr" => "::1", "port" => 3000], + "iov" => ["test ", "thing", "\n"], + "control" => [[ + "level" => IPPROTO_IPV6, + "type" => IPV6_PKTINFO, + "data" => [ + 'addr' => '::1', + 'ifindex' => 1 /* we're assuming loopback is 1. Is this a safe assumption? */ + ], + ]] +], 0); +var_dump($r); +checktimeout($s, 500); + +$data = [ + "name" => ["family" => AF_INET6, "addr" => "::1"], + "buffer_size" => 2000, + "controllen" => socket_cmsg_space(IPPROTO_IPV6, IPV6_PKTINFO) + + socket_cmsg_space(IPPROTO_IPV6, IPV6_TCLASS), +]; +if (!socket_recvmsg($s, $data, 0)) die("recvmsg"); +print_r($data); + +--EXPECTF-- +creating send socket +resource(5) of type (Socket) +bool(true) +creating receive socket +resource(6) of type (Socket) +bool(true) +int(11) +Array +( + [name] => Array + ( + [family] => %d + [addr] => ::1 + [port] => 7001 + [flowinfo] => 0 + [scope_id] => 0 + ) + + [control] => Array + ( + [0] => Array + ( + [level] => %d + [type] => %d + [data] => Array + ( + [addr] => ::1 + [ifindex] => %d + ) + + ) + + [1] => Array + ( + [level] => %d + [type] => %d + [data] => 0 + ) + + ) + + [iov] => Array + ( + [0] => test thing + + ) + + [flags] => 0 +) diff --git a/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt b/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt index 055e263f724..212f7e186f2 100644 --- a/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt +++ b/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt @@ -2,12 +2,15 @@ sendmsg()/recvmsg(): test ability to receive multiple messages --SKIPIF-- [[ "level" => IPPROTO_IPV6, "type" => IPV6_TCLASS, - "data" => 42, + "data" => 40, ]] ], 0); var_dump($r); @@ -88,7 +91,7 @@ Array ( [level] => %d [type] => %d - [data] => 42 + [data] => 40 ) ) diff --git a/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt b/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt index 53320cad0c3..27b6ae59c5d 100644 --- a/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt +++ b/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt @@ -8,7 +8,8 @@ die('skip sockets extension not available.'); if (!defined('IPPROTO_IPV6')) { die('skip IPv6 not available.'); } - +if (substr(PHP_OS, 0, 3) == 'WIN') + die('skip Not for Windows!'); --FILE-- +#include /* conflicting definition of CMSG_DATA */ + +#define HAVE_IF_NAMETOINDEX 1 + +#define IS_INVALID_SOCKET(a) (a->bsd_socket == INVALID_SOCKET) +#ifdef EPROTONOSUPPORT +# undef EPROTONOSUPPORT +#endif +#ifdef ECONNRESET +# undef ECONNRESET +#endif +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#define ECONNRESET WSAECONNRESET +#ifdef errno +# undef errno +#endif +#define errno WSAGetLastError() +#define h_errno WSAGetLastError() +#define set_errno(a) WSASetLastError(a) +#define close(a) closesocket(a) + +#endif \ No newline at end of file From 608254fa5757ee07999d5c4bec660b8c29780938 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 16:24:02 +0100 Subject: [PATCH 37/40] Fix non-Windows build --- ext/sockets/conversions.c | 4 +++- main/php_network.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c index 6c79166806b..e3ff271f846 100644 --- a/ext/sockets/conversions.c +++ b/ext/sockets/conversions.c @@ -1,7 +1,9 @@ #include "sockaddr_conv.h" #include "conversions.h" #include "sendrecvmsg.h" /* for ancillary registry */ -#include "windows_common.h" +#ifdef PHP_WIN32 +# include "windows_common.h" +#endif #include #include diff --git a/main/php_network.h b/main/php_network.h index 8ffb51ca407..c1535ee040b 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -21,6 +21,8 @@ #ifndef _PHP_NETWORK_H #define _PHP_NETWORK_H +#include + #ifdef PHP_WIN32 # include "win32/inet.h" #else From e2fc17c833c5122327438c82fc0dc4b689268f59 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Thu, 31 Jan 2013 16:34:46 +0100 Subject: [PATCH 38/40] Fix multicast.c not defining errno on Windows Small cleanups in includes as well. --- ext/sockets/multicast.c | 14 +------------- ext/sockets/sockaddr_conv.h | 1 - ext/sockets/windows_common.h | 2 ++ 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/ext/sockets/multicast.c b/ext/sockets/multicast.c index 5d29c9c5e26..c6de3198e46 100644 --- a/ext/sockets/multicast.c +++ b/ext/sockets/multicast.c @@ -28,19 +28,7 @@ #include "php_network.h" #ifdef PHP_WIN32 -# include "win32/inet.h" -# include -# include -# include -# include -# include "php_sockets.h" -# include "win32/sockets.h" -# define NTDDI_XP NTDDI_WINXP /* bug in SDK */ -# include -# undef NTDDI_XP -# if _WIN32_WINNT >= 0x0600 -# define HAVE_IF_NAMETOINDEX 1 -# endif +# include "windows_common.h" #else #include #include diff --git a/ext/sockets/sockaddr_conv.h b/ext/sockets/sockaddr_conv.h index 665c73913f1..8e51edac8f8 100644 --- a/ext/sockets/sockaddr_conv.h +++ b/ext/sockets/sockaddr_conv.h @@ -1,7 +1,6 @@ #ifndef PHP_SOCKADR_CONV_H #define PHP_SOCKADR_CONV_H -#define HAVE_SOCKETS 1 #include #include "php_sockets.h" /* php_socket */ diff --git a/ext/sockets/windows_common.h b/ext/sockets/windows_common.h index 1dc966ac001..c72c6987e68 100644 --- a/ext/sockets/windows_common.h +++ b/ext/sockets/windows_common.h @@ -17,7 +17,9 @@ #define WINDOWS_COMMON_H #include +#define NTDDI_XP NTDDI_WINXP /* bug in SDK */ #include /* conflicting definition of CMSG_DATA */ +#undef NTDDI_XP #define HAVE_IF_NAMETOINDEX 1 From 0110662ae9e89d21c119b3287118e82fd435f779 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sat, 2 Feb 2013 18:32:38 +0100 Subject: [PATCH 39/40] Move macro back to .c file Because it depends on a static function on that .c file. --- ext/sockets/php_sockets.h | 8 -------- ext/sockets/sockets.c | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 3762e026aa5..fabc9c4c3e7 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -64,14 +64,6 @@ PHP_SOCKETS_API int php_sockets_le_socket(void); #define php_sockets_le_socket_name "Socket" -#define PHP_SOCKET_ERROR(socket, msg, errn) \ - do { \ - int _err = (errn); /* save value to avoid repeated calls to WSAGetLastError() on Windows */ \ - (socket)->error = _err; \ - SOCKETS_G(last_error) = _err; \ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ - } while (0) - ZEND_BEGIN_MODULE_GLOBALS(sockets) int last_error; char *strerror_buf; diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index c6c5477967c..06bd0ec6b4f 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -114,6 +114,14 @@ static PHP_GINIT_FUNCTION(sockets); static char *php_strerror(int error TSRMLS_DC); +#define PHP_SOCKET_ERROR(socket, msg, errn) \ + do { \ + int _err = (errn); /* save value to avoid repeated calls to WSAGetLastError() on Windows */ \ + (socket)->error = _err; \ + SOCKETS_G(last_error) = _err; \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ + } while (0) + #define PHP_NORMAL_READ 0x0001 #define PHP_BINARY_READ 0x0002 From 03b1da5d7ada4a80a91d9953c388fa05f294d711 Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Sat, 2 Feb 2013 18:39:47 +0100 Subject: [PATCH 40/40] php_strerror in ext/sockets renamed in 5.5 --- ext/sockets/php_sockets.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 5082a9bc1e8..bad83b34ccc 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -72,7 +72,7 @@ PHP_SOCKETS_API int php_sockets_le_socket(void); (socket)->error = _err; \ SOCKETS_G(last_error) = _err; \ if (_err != EAGAIN && _err != EWOULDBLOCK && _err != EINPROGRESS) { \ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, php_strerror(_err TSRMLS_CC)); \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, sockets_strerror(_err TSRMLS_CC)); \ } \ } while (0)