summaryrefslogtreecommitdiff
path: root/ext/sockets
diff options
context:
space:
mode:
Diffstat (limited to 'ext/sockets')
-rw-r--r--ext/sockets/config.m42
-rw-r--r--ext/sockets/config.w322
-rw-r--r--ext/sockets/conversions.c1528
-rw-r--r--ext/sockets/conversions.h84
-rw-r--r--ext/sockets/multicast.c377
-rw-r--r--ext/sockets/multicast.h38
-rw-r--r--ext/sockets/php_sockets.h32
-rw-r--r--ext/sockets/sendrecvmsg.c452
-rw-r--r--ext/sockets/sendrecvmsg.h36
-rw-r--r--ext/sockets/sockaddr_conv.c119
-rw-r--r--ext/sockets/sockaddr_conv.h31
-rw-r--r--ext/sockets/sockets.c667
-rw-r--r--ext/sockets/tests/mcast_ipv6_send.phpt4
-rw-r--r--ext/sockets/tests/socket_cmsg_credentials.phpt89
-rw-r--r--ext/sockets/tests/socket_cmsg_rights.phpt100
-rw-r--r--ext/sockets/tests/socket_recvmsg.phpt86
-rw-r--r--ext/sockets/tests/socket_sendrecvmsg_multi_msg-win32.phpt110
-rw-r--r--ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt106
-rw-r--r--ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt6
-rw-r--r--ext/sockets/tests/socket_sentto_recvfrom_ipv6_udp.phpt4
-rw-r--r--ext/sockets/tests/socket_sentto_recvfrom_unix.phpt5
-rw-r--r--ext/sockets/tests/socket_set_option_in6_pktinfo.phpt32
-rw-r--r--ext/sockets/windows_common.h120
23 files changed, 3484 insertions, 546 deletions
diff --git a/ext/sockets/config.m4 b/ext/sockets/config.m4
index 4032621ce6..9c75249646 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 conversions.c sockaddr_conv.c sendrecvmsg.c], [$ext_shared])
PHP_INSTALL_HEADERS([ext/sockets/], [php_sockets.h])
fi
diff --git a/ext/sockets/config.w32 b/ext/sockets/config.w32
index 9c234db8f8..aeaa8ed425 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
new file mode 100644
index 0000000000..e3ff271f84
--- /dev/null
+++ b/ext/sockets/conversions.c
@@ -0,0 +1,1528 @@
+#include "sockaddr_conv.h"
+#include "conversions.h"
+#include "sendrecvmsg.h" /* for ancillary registry */
+#ifdef PHP_WIN32
+# include "windows_common.h"
+#endif
+
+#include <Zend/zend_llist.h>
+#include <ext/standard/php_smart_str.h>
+
+#ifndef PHP_WIN32
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <arpa/inet.h>
+# include <netinet/in.h>
+# include <sys/un.h>
+# include <sys/ioctl.h>
+# include <net/if.h>
+#else
+# include <win32/php_stdint.h>
+#endif
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+
+#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
+
+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"
+
+const struct key_value empty_key_value_list[] = {{0}};
+
+/* 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);
+}
+ZEND_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);
+}
+ZEND_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_LVAL(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_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;
+ memset(*control_buf, '\0', *control_len - *offset);
+ 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, (size_t)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)
+{
+ 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) { /* 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 (Z_TYPE_P(zv) != IS_STRING) {
+ ZVAL_COPY_VALUE(&lzval, zv);
+ zval_copy_ctor(&lzval);
+ convert_to_string(&lzval);
+ 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 "
+ "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_ifindex, 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];
+ TSRMLS_FETCH();
+
+ 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 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);
+ }
+
+ 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 0000000000..7d515246a0
--- /dev/null
+++ b/ext/sockets/conversions.h
@@ -0,0 +1,84 @@
+#ifndef PHP_SOCK_CONVERSIONS_H
+#define PHP_SOCK_CONVERSIONS_H 1
+
+#include <php.h>
+
+#ifndef PHP_WIN32
+# include <netinet/in.h>
+# include <sys/socket.h>
+#else
+# include <Ws2tcpip.h>
+#endif
+
+#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/multicast.c b/ext/sockets/multicast.c
index 43b6f7dddf..7466c6266e 100644
--- a/ext/sockets/multicast.c
+++ b/ext/sockets/multicast.c
@@ -24,20 +24,9 @@
#include "php.h"
-#if HAVE_SOCKETS
-
#include "php_network.h"
#ifdef PHP_WIN32
-# include "win32/inet.h"
-# include <winsock2.h>
-# include <windows.h>
-# include <Ws2tcpip.h>
-# include <Ws2ipdef.h>
-# include "php_sockets.h"
-# include "win32/sockets.h"
-# define NTDDI_XP NTDDI_WINXP /* bug in SDK */
-# include <IPHlpApi.h>
-# undef NTDDI_XP
+# include "windows_common.h"
#else
#include <sys/socket.h>
#include <sys/ioctl.h>
@@ -51,6 +40,7 @@
#include "php_sockets.h"
#include "multicast.h"
+#include "sockaddr_conv.h"
#include "main/php_network.h"
@@ -73,6 +63,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 PHP_MCAST_JOIN_GROUP:
+ mcast_req_fun = &php_mcast_join;
+ goto mcast_req_fun;
+ case PHP_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 PHP_MCAST_BLOCK_SOURCE:
+ mcast_sreq_fun = &php_mcast_block_source;
+ goto mcast_sreq_fun;
+ case PHP_MCAST_UNBLOCK_SOURCE:
+ mcast_sreq_fun = &php_mcast_unblock_source;
+ goto mcast_sreq_fun;
+ case PHP_MCAST_JOIN_SOURCE_GROUP:
+ mcast_sreq_fun = &php_mcast_join_source;
+ goto mcast_sreq_fun;
+ case PHP_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 TSRMLS_DC)
+{
+ 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 PHP_MCAST_JOIN_GROUP:
+ case PHP_MCAST_LEAVE_GROUP:
+#ifdef HAS_MCAST_EXT
+ case PHP_MCAST_BLOCK_SOURCE:
+ case PHP_MCAST_UNBLOCK_SOURCE:
+ case PHP_MCAST_JOIN_SOURCE_GROUP:
+ case PHP_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 TSRMLS_DC)
+{
+ unsigned int if_index;
+ void *opt_ptr;
+ socklen_t optlen;
+ int ov;
+ int retval;
+
+ switch (optname) {
+ case PHP_MCAST_JOIN_GROUP:
+ case PHP_MCAST_LEAVE_GROUP:
+#ifdef HAS_MCAST_EXT
+ case PHP_MCAST_BLOCK_SOURCE:
+ case PHP_MCAST_UNBLOCK_SOURCE:
+ case PHP_MCAST_JOIN_SOURCE_GROUP:
+ case PHP_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,
@@ -154,21 +447,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)
@@ -185,12 +478,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));
@@ -218,26 +511,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)
@@ -246,7 +539,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));
}
@@ -280,7 +573,7 @@ static int _php_source_op_to_rfc3678_op(enum source_op sop)
case UNBLOCK_SOURCE:
return MCAST_UNBLOCK_SOURCE;
}
-
+
assert(0);
return 0;
}
@@ -297,7 +590,7 @@ static const char *_php_source_op_to_string(enum source_op sop)
case UNBLOCK_SOURCE:
return "MCAST_UNBLOCK_SOURCE";
}
-
+
assert(0);
return "";
}
@@ -314,7 +607,7 @@ static int _php_source_op_to_ipv4_op(enum source_op sop)
case UNBLOCK_SOURCE:
return IP_UNBLOCK_SOURCE;
}
-
+
assert(0);
return 0;
}
@@ -413,16 +706,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) {
@@ -435,13 +728,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;
@@ -455,25 +748,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;
@@ -483,15 +776,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
@@ -499,7 +792,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)) {
@@ -534,12 +827,10 @@ 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);
return FAILURE;
}
#endif
-
-#endif /* HAVE_SOCKETS */
diff --git a/ext/sockets/multicast.h b/ext/sockets/multicast.h
index f46a6a8f61..3614306bbb 100644
--- a/ext/sockets/multicast.h
+++ b/ext/sockets/multicast.h
@@ -18,17 +18,43 @@
/* $Id$ */
-#if defined(MCAST_JOIN_GROUP) && \
- (!defined(PHP_WIN32) || (_WIN32_WINNT >= 0x600 && SOCKETS_ENABLE_VISTA_API)) && \
- !defined(__APPLE__)
-#define RFC3678_API 1
+#if defined(MCAST_JOIN_GROUP) && !defined(__APPLE__)
+# define RFC3678_API 1
/* has block/unblock and source membership, in this case for both IPv4 and IPv6 */
-#define HAS_MCAST_EXT 1
+# define HAS_MCAST_EXT 1
#elif defined(IP_ADD_SOURCE_MEMBERSHIP) && !defined(__APPLE__)
/* has block/unblock and source membership, but only for IPv4 */
-#define HAS_MCAST_EXT 1
+# define HAS_MCAST_EXT 1
#endif
+#ifndef RFC3678_API
+# define PHP_MCAST_JOIN_GROUP IP_ADD_MEMBERSHIP
+# define PHP_MCAST_LEAVE_GROUP IP_DROP_MEMBERSHIP
+# ifdef HAS_MCAST_EXT
+# define PHP_MCAST_BLOCK_SOURCE IP_BLOCK_SOURCE
+# define PHP_MCAST_UNBLOCK_SOURCE IP_UNBLOCK_SOURCE
+# define PHP_MCAST_JOIN_SOURCE_GROUP IP_ADD_SOURCE_MEMBERSHIP
+# define PHP_MCAST_LEAVE_SOURCE_GROUP IP_DROP_SOURCE_MEMBERSHIP
+# endif
+#else
+# define PHP_MCAST_JOIN_GROUP MCAST_JOIN_GROUP
+# define PHP_MCAST_LEAVE_GROUP MCAST_LEAVE_GROUP
+# define PHP_MCAST_BLOCK_SOURCE MCAST_BLOCK_SOURCE
+# define PHP_MCAST_UNBLOCK_SOURCE MCAST_UNBLOCK_SOURCE
+# define PHP_MCAST_JOIN_SOURCE_GROUP MCAST_JOIN_SOURCE_GROUP
+# define PHP_MCAST_LEAVE_SOURCE_GROUP MCAST_LEAVE_SOURCE_GROUP
+#endif
+
+int php_do_setsockopt_ip_mcast(php_socket *php_sock,
+ int level,
+ int optname,
+ zval **arg4 TSRMLS_DC);
+
+int php_do_setsockopt_ipv6_mcast(php_socket *php_sock,
+ int level,
+ int optname,
+ zval **arg4 TSRMLS_DC);
+
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 fabc9c4c3e..17abf95a19 100644
--- a/ext/sockets/php_sockets.h
+++ b/ext/sockets/php_sockets.h
@@ -24,13 +24,22 @@
/* $Id$ */
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
#if HAVE_SOCKETS
+#include <php.h>
+#ifdef PHP_WIN32
+# include "windows_common.h"
+#endif
+
extern zend_module_entry sockets_module_entry;
#define phpext_sockets_ptr &sockets_module_entry
#ifdef PHP_WIN32
-#include <winsock.h>
+#include <Winsock2.h>
#else
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
@@ -64,6 +73,16 @@ 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; \
+ if (_err != EAGAIN && _err != EWOULDBLOCK && _err != EINPROGRESS) { \
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s [%d]: %s", msg, _err, sockets_strerror(_err TSRMLS_CC)); \
+ } \
+ } while (0)
+
ZEND_BEGIN_MODULE_GLOBALS(sockets)
int last_error;
char *strerror_buf;
@@ -75,6 +94,17 @@ ZEND_END_MODULE_GLOBALS(sockets)
#define SOCKETS_G(v) (sockets_globals.v)
#endif
+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);
+
#else
#define phpext_sockets_ptr NULL
#endif
diff --git a/ext/sockets/sendrecvmsg.c b/ext/sockets/sendrecvmsg.c
new file mode 100644
index 0000000000..50b43ec38a
--- /dev/null
+++ b/ext/sockets/sendrecvmsg.c
@@ -0,0 +1,452 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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 <cataphract@php.net> |
+ +----------------------------------------------------------------------+
+ */
+
+#include <php.h>
+#include "php_sockets.h"
+#include "sendrecvmsg.h"
+#include "conversions.h"
+#include <limits.h>
+#include <Zend/zend_llist.h>
+#ifdef ZTS
+#include <TSRM/TSRM.h>
+#endif
+
+#define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024))
+#define DEFAULT_BUFF_SIZE 8192
+#define MAX_ARRAY_KEY_SIZE 128
+
+#ifdef PHP_WIN32
+#include "windows_common.h"
+#include <Mswsock.h>
+#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) { \
+ 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)
+
+static struct {
+ int initialized;
+ HashTable ht;
+} ancillary_registry;
+
+
+#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);
+
+#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)
+
+#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
+
+#ifdef IPV6_TCLASS
+ PUT_ENTRY(sizeof(int), 0, 0, from_zval_write_int,
+ to_zval_read_int, IPPROTO_IPV6, IPV6_TCLASS);
+#endif
+
+#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 void destroy_ancillary_registry(void)
+{
+ if (ancillary_registry.initialized) {
+ zend_hash_destroy(&ancillary_registry.ht);
+ ancillary_registry.initialized = 0;
+ }
+}
+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 {
+ PHP_SOCKET_ERROR(php_sock, "error in sendmsg", errno);
+ 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;
+ allocations_dispose(&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,
+ n = 0;
+ ancillary_reg_entry *entry;
+
+ 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) {
+ php_error_docref0(NULL TSRMLS_CC, E_WARNING, "The pair level %ld/type %ld is "
+ "not supported by PHP", level, type);
+ return;
+ }
+
+ 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->var_el_size));
+}
+
+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;
+ void *opt_ptr;
+ socklen_t optlen;
+ int retval;
+
+ assert(level == IPPROTO_IPV6);
+
+ 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) {
+ 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 TSRMLS_DC)
+{
+ 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 TSRMLS_CC);
+ 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 */
+#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);
+ REGISTER_LONG_CONSTANT("IPV6_RECVDSTOPTS", IPV6_RECVDSTOPTS, CONST_CS | CONST_PERSISTENT);
+ */
+#ifdef IPV6_RECVTCLASS
+ REGISTER_LONG_CONSTANT("IPV6_RECVTCLASS", IPV6_RECVTCLASS, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("IPV6_TCLASS", IPV6_TCLASS, CONST_CS | CONST_PERSISTENT);
+#endif
+
+ /*
+ 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);
+ */
+
+#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();
+#endif
+}
+
+void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS)
+{
+#ifdef ZTS
+ tsrm_mutex_free(ancillary_mutex);
+#endif
+
+ destroy_ancillary_registry();
+}
diff --git a/ext/sockets/sendrecvmsg.h b/ext/sockets/sendrecvmsg.h
new file mode 100644
index 0000000000..5a3798274f
--- /dev/null
+++ b/ext/sockets/sendrecvmsg.h
@@ -0,0 +1,36 @@
+#ifndef PHP_SENDRECVMSG_H
+#define PHP_SENDRECVMSG_H 1
+
+#include <php.h>
+#include "conversions.h"
+
+/* for sockets.c */
+PHP_FUNCTION(socket_sendmsg);
+PHP_FUNCTION(socket_recvmsg);
+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 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 {
+ 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
diff --git a/ext/sockets/sockaddr_conv.c b/ext/sockets/sockaddr_conv.c
new file mode 100644
index 0000000000..a40b6b4936
--- /dev/null
+++ b/ext/sockets/sockaddr_conv.c
@@ -0,0 +1,119 @@
+#include <php.h>
+#include <php_network.h>
+#include "php_sockets.h"
+
+#ifdef PHP_WIN32
+#include "windows_common.h"
+#else
+#include <netdb.h>
+#include <arpa/inet.h>
+#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 0000000000..8e51edac8f
--- /dev/null
+++ b/ext/sockets/sockaddr_conv.h
@@ -0,0 +1,31 @@
+#ifndef PHP_SOCKADR_CONV_H
+#define PHP_SOCKADR_CONV_H
+
+#include <php_network.h>
+#include "php_sockets.h" /* php_socket */
+
+#ifndef PHP_WIN32
+# include <netinet/in.h>
+#else
+# include <Winsock2.h>
+#endif
+
+
+/*
+ * 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 06bd0ec6b4..f305fa07d8 100644
--- a/ext/sockets/sockets.c
+++ b/ext/sockets/sockets.c
@@ -28,38 +28,17 @@
#include "php.h"
-#if HAVE_SOCKETS
-
#include "php_network.h"
#include "ext/standard/file.h"
#include "ext/standard/info.h"
#include "php_ini.h"
#ifdef PHP_WIN32
-# include "win32/inet.h"
-# include <winsock2.h>
+# include "windows_common.h"
+# include <win32/inet.h>
# include <windows.h>
# include <Ws2tcpip.h>
# 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)
-# if _WIN32_WINNT >= 0x0600 && SOCKETS_ENABLE_VISTA_API
-# define HAVE_IF_NAMETOINDEX 1
-# endif
+# include <win32/sockets.h>
#else
# include <sys/types.h>
# include <sys/socket.h>
@@ -85,10 +64,11 @@
# endif
#endif
+#include "sockaddr_conv.h"
#include "multicast.h"
+#include "sendrecvmsg.h"
ZEND_DECLARE_MODULE_GLOBALS(sockets)
-static PHP_GINIT_FUNCTION(sockets);
#ifndef MSG_WAITALL
#ifdef LINUX
@@ -112,16 +92,6 @@ static PHP_GINIT_FUNCTION(sockets);
#define PF_INET AF_INET
#endif
-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
@@ -277,15 +247,34 @@ 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);
-PHP_MINFO_FUNCTION(sockets);
-PHP_RSHUTDOWN_FUNCTION(sockets);
+static PHP_GINIT_FUNCTION(sockets);
+static PHP_MINIT_FUNCTION(sockets);
+static PHP_MSHUTDOWN_FUNCTION(sockets);
+static PHP_MINFO_FUNCTION(sockets);
+static PHP_RSHUTDOWN_FUNCTION(sockets);
PHP_FUNCTION(socket_select);
PHP_FUNCTION(socket_create_listen);
@@ -351,6 +340,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)
@@ -365,7 +357,7 @@ zend_module_entry sockets_module_entry = {
"sockets",
sockets_functions,
PHP_MINIT(sockets),
- NULL,
+ PHP_MSHUTDOWN(sockets),
NULL,
PHP_RSHUTDOWN(sockets),
PHP_MINFO(sockets),
@@ -396,13 +388,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;
}
/* }}} */
@@ -559,7 +551,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;
@@ -606,188 +598,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)
-{
- 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)
{
@@ -798,7 +608,7 @@ static PHP_GINIT_FUNCTION(sockets)
/* {{{ PHP_MINIT_FUNCTION
*/
-PHP_MINIT_FUNCTION(sockets)
+static PHP_MINIT_FUNCTION(sockets)
{
le_socket = zend_register_list_destructors_ex(php_destroy_socket, NULL, le_socket_name, module_number);
@@ -812,11 +622,11 @@ 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);
-#ifdef MSG_DONTWAIT
- REGISTER_LONG_CONSTANT("MSG_DONTWAIT", MSG_DONTWAIT, CONST_CS | CONST_PERSISTENT);
-#endif
+ 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);
#ifdef MSG_EOR
@@ -825,6 +635,29 @@ PHP_MINIT_FUNCTION(sockets)
#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);
+#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
+#ifdef MSG_MORE
+ REGISTER_LONG_CONSTANT("MSG_MORE", MSG_MORE, CONST_CS | CONST_PERSISTENT);
+#endif
+#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
@@ -842,6 +675,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);
@@ -851,24 +687,13 @@ PHP_MINIT_FUNCTION(sockets)
REGISTER_LONG_CONSTANT("PHP_NORMAL_READ", PHP_NORMAL_READ, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("PHP_BINARY_READ", PHP_BINARY_READ, CONST_CS | CONST_PERSISTENT);
-#ifndef RFC3678_API
-#define MCAST_JOIN_GROUP IP_ADD_MEMBERSHIP
-#define MCAST_LEAVE_GROUP IP_DROP_MEMBERSHIP
+ REGISTER_LONG_CONSTANT("MCAST_JOIN_GROUP", PHP_MCAST_JOIN_GROUP, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("MCAST_LEAVE_GROUP", PHP_MCAST_LEAVE_GROUP, CONST_CS | CONST_PERSISTENT);
#ifdef HAS_MCAST_EXT
-#define MCAST_BLOCK_SOURCE IP_BLOCK_SOURCE
-#define MCAST_UNBLOCK_SOURCE IP_UNBLOCK_SOURCE
-#define MCAST_JOIN_SOURCE_GROUP IP_ADD_SOURCE_MEMBERSHIP
-#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
- REGISTER_LONG_CONSTANT("MCAST_BLOCK_SOURCE", MCAST_BLOCK_SOURCE, CONST_CS | CONST_PERSISTENT);
- REGISTER_LONG_CONSTANT("MCAST_UNBLOCK_SOURCE", MCAST_UNBLOCK_SOURCE, CONST_CS | CONST_PERSISTENT);
- REGISTER_LONG_CONSTANT("MCAST_JOIN_SOURCE_GROUP", MCAST_JOIN_SOURCE_GROUP, CONST_CS | CONST_PERSISTENT);
- REGISTER_LONG_CONSTANT("MCAST_LEAVE_SOURCE_GROUP", MCAST_LEAVE_SOURCE_GROUP, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("MCAST_BLOCK_SOURCE", PHP_MCAST_BLOCK_SOURCE, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("MCAST_UNBLOCK_SOURCE", PHP_MCAST_UNBLOCK_SOURCE, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("MCAST_JOIN_SOURCE_GROUP", PHP_MCAST_JOIN_SOURCE_GROUP, CONST_CS | CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("MCAST_LEAVE_SOURCE_GROUP", PHP_MCAST_LEAVE_SOURCE_GROUP, CONST_CS | CONST_PERSISTENT);
#endif
REGISTER_LONG_CONSTANT("IP_MULTICAST_IF", IP_MULTICAST_IF, CONST_CS | CONST_PERSISTENT);
@@ -894,13 +719,29 @@ 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
+
+ php_socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU);
+
+ return SUCCESS;
+}
+/* }}} */
+
+/* {{{ PHP_MSHUTDOWN_FUNCTION
+ */
+static PHP_MSHUTDOWN_FUNCTION(sockets)
+{
+ php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS_PASSTHRU);
+
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MINFO_FUNCTION
*/
-PHP_MINFO_FUNCTION(sockets)
+static PHP_MINFO_FUNCTION(sockets)
{
php_info_print_table_start();
php_info_print_table_row(2, "Sockets Support", "enabled");
@@ -909,7 +750,7 @@ PHP_MINFO_FUNCTION(sockets)
/* }}} */
/* {{{ PHP_RSHUTDOWN_FUNCTION */
-PHP_RSHUTDOWN_FUNCTION(sockets)
+static PHP_RSHUTDOWN_FUNCTION(sockets)
{
if (SOCKETS_G(strerror_buf)) {
efree(SOCKETS_G(strerror_buf));
@@ -1056,7 +897,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;
}
@@ -1125,7 +966,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 */
@@ -1162,7 +1003,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 */
@@ -1516,7 +1357,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;
}
@@ -1549,7 +1390,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;
@@ -1570,7 +1411,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;
@@ -1589,7 +1430,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;
@@ -1626,7 +1467,7 @@ PHP_FUNCTION(socket_strerror)
return;
}
- RETURN_STRING(php_strerror(arg1 TSRMLS_CC), 1);
+ RETURN_STRING(sockets_strerror(arg1 TSRMLS_CC), 1);
}
/* }}} */
@@ -2017,8 +1858,15 @@ PHP_FUNCTION(socket_get_option)
}
}
}
+ } else if (level == IPPROTO_IPV6) {
+ int ret = php_do_getsockopt_ipv6_rfc3542(php_sock, level, optname, return_value TSRMLS_CC);
+ if (ret == SUCCESS) {
+ return;
+ } else if (ret == FAILURE) {
+ RETURN_FALSE;
+ } /* else continue */
}
-
+
/* sol_socket options and general case */
switch(optname) {
case SO_LINGER:
@@ -2060,7 +1908,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);
@@ -2077,102 +1925,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)
@@ -2191,12 +1943,8 @@ 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;
}
@@ -2205,94 +1953,26 @@ PHP_FUNCTION(socket_set_option)
set_errno(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;
- }
+#define HANDLE_SUBCALL(res) \
+ do { \
+ if (res == 1) { goto default_case; } \
+ else if (res == SUCCESS) { RETURN_TRUE; } \
+ else { RETURN_FALSE; } \
+ } while (0)
- 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;
- }
+ if (level == IPPROTO_IP) {
+ 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) {
- 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 TSRMLS_CC);
+ if (res == 1) {
+ res = php_do_setsockopt_ipv6_rfc3542(php_sock, level, optname, arg4 TSRMLS_CC);
}
+ HANDLE_SUBCALL(res);
}
#endif
@@ -2355,8 +2035,9 @@ ipv6_loop_hops:
#endif
break;
}
-
+
default:
+default_case:
convert_to_long_ex(arg4);
ov = Z_LVAL_PP(arg4);
@@ -2365,12 +2046,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;
}
@@ -2411,7 +2089,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;
@@ -2510,6 +2188,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)
@@ -2518,44 +2243,23 @@ 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
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstream) == FAILURE) {
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;
- } 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);
+
+ retsock = socket_import_file_descriptor(socket TSRMLS_CC);
+ if (retsock == NULL) {
+ RETURN_FALSE;
}
-#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)) {
@@ -2565,7 +2269,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) */
@@ -2574,21 +2278,14 @@ 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:
- if (retsock != NULL)
- efree(retsock);
- RETURN_FALSE;
}
/* }}} */
-#endif
-
/*
* Local variables:
* tab-width: 4
diff --git a/ext/sockets/tests/mcast_ipv6_send.phpt b/ext/sockets/tests/mcast_ipv6_send.phpt
index b8d38bf68f..f75bb09903 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--
diff --git a/ext/sockets/tests/socket_cmsg_credentials.phpt b/ext/sockets/tests/socket_cmsg_credentials.phpt
new file mode 100644
index 0000000000..6a1c23fa8c
--- /dev/null
+++ b/ext/sockets/tests/socket_cmsg_credentials.phpt
@@ -0,0 +1,89 @@
+--TEST--
+recvmsg(): receive SCM_CREDENTIALS messages
+--SKIPIF--
+<?php
+if (!extension_loaded('sockets')) {
+die('skip sockets extension not available.');
+}
+if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
+die('skip not for Microsoft Windows');
+}
+--CLEAN--
+<?php
+$path = __DIR__ . "/unix_sock";
+@unlink($path);
+
+--FILE--
+<?php
+include __DIR__."/mcast_helpers.php.inc";
+$path = __DIR__ . "/unix_sock";
+
+@unlink($path);
+
+echo "creating send socket\n";
+$sends1 = socket_create(AF_UNIX, SOCK_DGRAM, 0) or die("err");
+var_dump($sends1);
+socket_set_nonblock($sends1) or die("Could not put in non-blocking mode");
+
+echo "creating receive socket\n";
+$s = socket_create(AF_UNIX, SOCK_DGRAM, 0) or die("err");
+var_dump($s);
+$br = socket_bind($s, $path) or die("err");
+var_dump($br);
+socket_set_nonblock($sends1) or die("Could not put in non-blocking mode");
+socket_set_option($s, SOL_SOCKET, SO_PASSCRED, 1) or die("could not set SO_PASSCRED");
+
+
+//$r = socket_sendmsg($sends1, [
+// "iov" => ["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)
diff --git a/ext/sockets/tests/socket_cmsg_rights.phpt b/ext/sockets/tests/socket_cmsg_rights.phpt
new file mode 100644
index 0000000000..8290f03880
--- /dev/null
+++ b/ext/sockets/tests/socket_cmsg_rights.phpt
@@ -0,0 +1,100 @@
+--TEST--
+recvmsg(): receive SCM_CREDENTIALS messages
+--SKIPIF--
+<?php
+if (!extension_loaded('sockets')) {
+die('skip sockets extension not available.');
+}
+if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
+die('skip not for Microsoft Windows');
+}
+--CLEAN--
+<?php
+$path = __DIR__ . "/unix_sock";
+@unlink($path);
+
+--FILE--
+<?php
+include __DIR__."/mcast_helpers.php.inc";
+$path = __DIR__ . "/unix_sock";
+
+@unlink($path);
+
+echo "creating send socket\n";
+$sends1 = socket_create(AF_UNIX, SOCK_DGRAM, 0) or die("err");
+var_dump($sends1);
+socket_set_nonblock($sends1) or die("Could not put in non-blocking mode");
+
+echo "creating receive socket\n";
+$s = socket_create(AF_UNIX, SOCK_DGRAM, 0) or die("err");
+var_dump($s);
+$br = socket_bind($s, $path) or die("err");
+var_dump($br);
+socket_set_nonblock($s) or die("Could not put in non-blocking mode");
+
+$r = socket_sendmsg($sends1, [
+ "name" => [ "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
+)
diff --git a/ext/sockets/tests/socket_recvmsg.phpt b/ext/sockets/tests/socket_recvmsg.phpt
new file mode 100644
index 0000000000..30263a4fbd
--- /dev/null
+++ b/ext/sockets/tests/socket_recvmsg.phpt
@@ -0,0 +1,86 @@
+--TEST--
+recvmsg(): basic test
+--SKIPIF--
+<?php
+if (!extension_loaded('sockets')) {
+die('skip sockets extension not available.');
+}
+if (!defined('IPPROTO_IPV6')) {
+die('skip IPv6 not available.');
+}
+
+--FILE--
+<?php
+include __DIR__."/mcast_helpers.php.inc";
+$addr = '::1';
+
+echo "creating send socket\n";
+$sends1 = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("err");
+var_dump($sends1);
+$br = socket_bind($sends1, '::', 7001) or die("err");
+var_dump($br);
+socket_set_nonblock($sends1) or die("Could not put in non-blocking mode");
+
+echo "creating receive socket\n";
+$s = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("err");
+var_dump($s);
+$br = socket_bind($s, '::0', 3000) or die("err");
+var_dump($br);
+
+socket_set_option($s, IPPROTO_IPV6, IPV6_RECVPKTINFO, 1) or die("err");
+
+$r = socket_sendto($sends1, $m = "testing packet", strlen($m), 0, $addr, 3000);
+var_dump($r);
+if ($r < 12) die;
+checktimeout($s, 500);
+
+$data = [
+ "name" => ["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
+)
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 0000000000..3aba012726
--- /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--
+<?php
+if (!extension_loaded('sockets'))
+ die('skip sockets extension not available.');
+if (!defined('IPPROTO_IPV6'))
+ die('skip IPv6 not available.');
+if (substr(PHP_OS, 0, 3) != 'WIN')
+ die('skip Only for Windows!');
+/* Windows supports IPV6_RECVTCLASS and is able to receive the tclass via
+ * WSARecvMsg (though only the top 6 bits seem to reported), but WSASendMsg
+ * does not accept IPV6_TCLASS messages. We still test that sendmsg() works
+ * corectly by sending an IPV6_PKTINFO message that will have no effect */
+
+--FILE--
+<?php
+include __DIR__."/mcast_helpers.php.inc";
+$addr = '::1';
+
+echo "creating send socket\n";
+$sends1 = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("err");
+var_dump($sends1);
+$br = socket_bind($sends1, '::', 7001) or die("err");
+var_dump($br);
+socket_set_nonblock($sends1) or die("Could not put in non-blocking mode");
+
+echo "creating receive socket\n";
+$s = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("err");
+var_dump($s);
+$br = socket_bind($s, '::0', 3000) or die("err");
+var_dump($br);
+
+socket_set_option($s, IPPROTO_IPV6, IPV6_RECVPKTINFO, 1) or die("err");
+socket_set_option($s, IPPROTO_IPV6, IPV6_RECVTCLASS, 1) or die("err");
+
+$r = socket_sendmsg($sends1, [
+ "name" => [ "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
new file mode 100644
index 0000000000..212f7e186f
--- /dev/null
+++ b/ext/sockets/tests/socket_sendrecvmsg_multi_msg.phpt
@@ -0,0 +1,106 @@
+--TEST--
+sendmsg()/recvmsg(): test ability to receive multiple messages
+--SKIPIF--
+<?php
+if (!extension_loaded('sockets'))
+ 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 the Windows!');
+/* Windows supports IPV6_RECVTCLASS and is able to receive the tclass via
+ * WSARecvMsg (though only the top 6 bits seem to reported), but WSASendMsg
+ * does not accept IPV6_TCLASS messages */
+
+--FILE--
+<?php
+include __DIR__."/mcast_helpers.php.inc";
+$addr = '::1';
+
+echo "creating send socket\n";
+$sends1 = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("err");
+var_dump($sends1);
+$br = socket_bind($sends1, '::', 7001) or die("err");
+var_dump($br);
+socket_set_nonblock($sends1) or die("Could not put in non-blocking mode");
+
+echo "creating receive socket\n";
+$s = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("err");
+var_dump($s);
+$br = socket_bind($s, '::0', 3000) or die("err");
+var_dump($br);
+
+socket_set_option($s, IPPROTO_IPV6, IPV6_RECVPKTINFO, 1) or die("err");
+socket_set_option($s, IPPROTO_IPV6, IPV6_RECVTCLASS, 1) or die("err");
+
+$r = socket_sendmsg($sends1, [
+ "name" => [ "addr" => "::1", "port" => 3000],
+ "iov" => ["test ", "thing", "\n"],
+ "control" => [[
+ "level" => IPPROTO_IPV6,
+ "type" => IPV6_TCLASS,
+ "data" => 40,
+ ]]
+], 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] => 40
+ )
+
+ )
+
+ [iov] => Array
+ (
+ [0] => test thing
+
+ )
+
+ [flags] => 0
+)
diff --git a/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt b/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt
index bf95044d48..5aeaa0824f 100644
--- a/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt
+++ b/ext/sockets/tests/socket_sentto_recvfrom_ipv4_udp.phpt
@@ -14,12 +14,14 @@ 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
+
$address = '127.0.0.1';
socket_sendto($socket, '', 1, 0, $address); // cause warning
if (!socket_bind($socket, $address, 1223)) {
die("Unable to bind to $address:1223");
}
+
+ var_dump(socket_recvfrom($socket, $buf, 12, 0, $from, $port)); //false (EAGAIN - no warning)
$msg = "Ping!";
$len = strlen($msg);
@@ -44,9 +46,9 @@ if (!extension_loaded('sockets')) {
socket_close($socket);
--EXPECTF--
-Warning: socket_recvfrom(): unable to recvfrom [%d]: %a in %s on line %d
Warning: Wrong parameter count for socket_sendto() in %s on line %d
+bool(false)
Warning: socket_recvfrom() expects at least 5 parameters, 4 given 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 2beb8080cd..bd07904277 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 55ad75c65e..e25bf4df1a 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)
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 0000000000..27b6ae59c5
--- /dev/null
+++ b/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt
@@ -0,0 +1,32 @@
+--TEST--
+socket_set_option() with IPV6_PKTINFO
+--SKIPIF--
+<?php
+if (!extension_loaded('sockets')) {
+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--
+<?php
+
+$s = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("err");
+var_dump(socket_set_option($s, IPPROTO_IPV6, IPV6_PKTINFO, []));
+var_dump(socket_set_option($s, IPPROTO_IPV6, IPV6_PKTINFO, [
+ "addr" => '::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)
+
diff --git a/ext/sockets/windows_common.h b/ext/sockets/windows_common.h
new file mode 100644
index 0000000000..3a9cb59129
--- /dev/null
+++ b/ext/sockets/windows_common.h
@@ -0,0 +1,120 @@
+/*
+ +----------------------------------------------------------------------+
+ | 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. |
+ +----------------------------------------------------------------------+
+ */
+#ifndef WINDOWS_COMMON_H
+#define WINDOWS_COMMON_H
+
+#include <Winsock2.h>
+#define NTDDI_XP NTDDI_WINXP /* bug in SDK */
+#include <IPHlpApi.h> /* conflicting definition of CMSG_DATA */
+#undef NTDDI_XP
+
+#define HAVE_IF_NAMETOINDEX 1
+
+#define IS_INVALID_SOCKET(a) (a->bsd_socket == INVALID_SOCKET)
+
+#ifdef errno
+# undef errno
+#endif
+#define errno WSAGetLastError()
+#define h_errno WSAGetLastError()
+#define set_errno(a) WSASetLastError(a)
+#define close(a) closesocket(a)
+
+#ifdef ENETUNREACH /* errno.h probably included */
+# undef EWOULDBLOCK
+# undef EINPROGRESS
+# undef EALREADY
+# undef ENOTSOCK
+# undef EDESTADDRREQ
+# undef EMSGSIZE
+# undef EPROTOTYPE
+# undef ENOPROTOOPT
+# undef EPROTONOSUPPORT
+# undef ESOCKTNOSUPPORT
+# undef EOPNOTSUPP
+# undef EPFNOSUPPORT
+# undef EAFNOSUPPORT
+# undef EADDRINUSE
+# undef EADDRNOTAVAIL
+# undef ENETDOWN
+# undef ENETUNREACH
+# undef ENETRESET
+# undef ECONNABORTED
+# undef ECONNRESET
+# undef ENOBUFS
+# undef EISCONN
+# undef ENOTCONN
+# undef ESHUTDOWN
+# undef ETOOMANYREFS
+# undef ETIMEDOUT
+# undef ECONNREFUSED
+# undef ELOOP
+# undef ENAMETOOLONG
+# undef EHOSTDOWN
+# undef EHOSTUNREACH
+# undef ENOTEMPTY
+# undef EPROCLIM
+# undef EUSERS
+# undef EDQUOT
+# undef ESTALE
+# undef EREMOTE
+
+# undef EAGAIN
+#endif
+
+/* section disabled in WinSock2.h */
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#define EINPROGRESS WSAEINPROGRESS
+#define EALREADY WSAEALREADY
+#define ENOTSOCK WSAENOTSOCK
+#define EDESTADDRREQ WSAEDESTADDRREQ
+#define EMSGSIZE WSAEMSGSIZE
+#define EPROTOTYPE WSAEPROTOTYPE
+#define ENOPROTOOPT WSAENOPROTOOPT
+#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
+#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
+#define EOPNOTSUPP WSAEOPNOTSUPP
+#define EPFNOSUPPORT WSAEPFNOSUPPORT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#define EADDRINUSE WSAEADDRINUSE
+#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#define ENETDOWN WSAENETDOWN
+#define ENETUNREACH WSAENETUNREACH
+#define ENETRESET WSAENETRESET
+#define ECONNABORTED WSAECONNABORTED
+#define ECONNRESET WSAECONNRESET
+#define ENOBUFS WSAENOBUFS
+#define EISCONN WSAEISCONN
+#define ENOTCONN WSAENOTCONN
+#define ESHUTDOWN WSAESHUTDOWN
+#define ETOOMANYREFS WSAETOOMANYREFS
+#define ETIMEDOUT WSAETIMEDOUT
+#define ECONNREFUSED WSAECONNREFUSED
+#define ELOOP WSAELOOP
+#define ENAMETOOLONG WSAENAMETOOLONG
+#define EHOSTDOWN WSAEHOSTDOWN
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#define ENOTEMPTY WSAENOTEMPTY
+#define EPROCLIM WSAEPROCLIM
+#define EUSERS WSAEUSERS
+#define EDQUOT WSAEDQUOT
+#define ESTALE WSAESTALE
+#define EREMOTE WSAEREMOTE
+
+/* and an extra one */
+#define EAGAIN WSAEWOULDBLOCK
+
+#endif