diff options
Diffstat (limited to 'ext/sockets')
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 |