From: Gustavo André dos Santos Lopes Date: Mon, 14 Mar 2011 00:08:29 +0000 (+0000) Subject: - Added multicast support to the sockets extension (bug #40510). X-Git-Tag: php-5.4.0alpha1~191^2~167 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e4298bf0fb4e3340a0be109521124353a622c17d;p=php - Added multicast support to the sockets extension (bug #40510). --- diff --git a/ext/sockets/config.m4 b/ext/sockets/config.m4 index 574548d605..096a420b52 100644 --- a/ext/sockets/config.m4 +++ b/ext/sockets/config.m4 @@ -18,7 +18,7 @@ if test "$PHP_SOCKETS" != "no"; then AC_DEFINE(HAVE_CMSGHDR,1,[Whether you have struct cmsghdr]) fi - AC_CHECK_FUNCS([hstrerror socketpair]) + AC_CHECK_FUNCS([hstrerror socketpair if_nametoindex]) AC_CHECK_HEADERS([netdb.h netinet/tcp.h sys/un.h errno.h]) AC_TRY_COMPILE([ #include @@ -28,6 +28,6 @@ if test "$PHP_SOCKETS" != "no"; then ) AC_DEFINE([HAVE_SOCKETS], 1, [ ]) - PHP_NEW_EXTENSION([sockets], [sockets.c], [$ext_shared]) + PHP_NEW_EXTENSION([sockets], [sockets.c multicast.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 8b63381900..9c234db8f8 100644 --- a/ext/sockets/config.w32 +++ b/ext/sockets/config.w32 @@ -5,8 +5,9 @@ ARG_ENABLE("sockets", "SOCKETS support", "no"); 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'); + EXTENSION('sockets', 'sockets.c multicast.c'); AC_DEFINE('HAVE_SOCKETS', 1); PHP_INSTALL_HEADERS("ext/sockets", "php_sockets.h"); } else { diff --git a/ext/sockets/multicast.c b/ext/sockets/multicast.c new file mode 100644 index 0000000000..a22419b4d5 --- /dev/null +++ b/ext/sockets/multicast.c @@ -0,0 +1,496 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" + +#if HAVE_SOCKETS + +#include "php_network.h" +#ifdef PHP_WIN32 +# include "win32/inet.h" +# include +# include +# include +# include +# include "php_sockets.h" +# include "win32/sockets.h" +# define NTDDI_XP NTDDI_WINXP /* bug in SDK */ +# include +# undef NTDDI_XP +#else +#include +#include +#include +#include +#include +#endif + +#include "php_sockets.h" +#include "multicast.h" +#include "main/php_network.h" + +#if defined(MCAST_JOIN_GROUP) && 1 &&\ + (!defined(PHP_WIN32) || (_WIN32_WINNT >= 0x600 && SOCKETS_ENABLE_VISTA_API)) +#define RFC3678_API 1 +#endif + + +enum source_op { + JOIN_SOURCE, + LEAVE_SOURCE, + BLOCK_SOURCE, + UNBLOCK_SOURCE +}; + +static int _php_mcast_join_leave(php_socket *sock, int level, struct sockaddr *group, socklen_t group_len, unsigned int if_index, int join TSRMLS_DC); +static int _php_mcast_source_op(php_socket *sock, int level, struct sockaddr *group, socklen_t group_len, struct sockaddr *source, socklen_t source_len, unsigned int if_index, enum source_op sop TSRMLS_DC); +#if RFC3678_API +static int _php_source_op_to_rfc3678_op(enum source_op sop); +#else +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 + +int php_mcast_join( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + unsigned int if_index TSRMLS_DC) +{ + return _php_mcast_join_leave(sock, level, group, group_len, if_index, 1 TSRMLS_CC); +} + +int php_mcast_leave( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + unsigned int if_index TSRMLS_DC) +{ + return _php_mcast_join_leave(sock, level, group, group_len, if_index, 0 TSRMLS_CC); +} + +int php_mcast_join_source( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index TSRMLS_DC) +{ + return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, JOIN_SOURCE TSRMLS_CC); +} + +int php_mcast_leave_source( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index TSRMLS_DC) +{ + return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, LEAVE_SOURCE TSRMLS_CC); +} + +int php_mcast_block_source( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index TSRMLS_DC) +{ + return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, BLOCK_SOURCE TSRMLS_CC); +} + +int php_mcast_unblock_source( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index TSRMLS_DC) +{ + return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, UNBLOCK_SOURCE TSRMLS_CC); +} + +static int _php_mcast_join_leave( + php_socket *sock, + int level, + struct sockaddr *group, /* struct sockaddr_in/sockaddr_in6 */ + socklen_t group_len, + unsigned int if_index, + int join TSRMLS_DC) +{ +#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)); +#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) + return -2; /* failure, but notice already emitted */ + mreq.imr_interface = addr; + } else { + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + } + mreq.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr; + return setsockopt(sock->bsd_socket, level, + join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, (char*)&mreq, + sizeof(mreq)); + } +#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)); + } +#endif + else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Option %s is inapplicable to this socket type", + join ? "MCAST_JOIN_GROUP" : "MCAST_LEAVE_GROUP"); + return -2; + } +#endif +} + +static int _php_mcast_source_op( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index, + enum source_op sop TSRMLS_DC) +{ +#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) + return -2; /* failure, but notice already emitted */ + mreqs.imr_interface = addr; + } 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)); + } +#if HAVE_IPV6 + else if (sock->type == AF_INET6) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "This platform does not support %s for IPv6 sockets", + _php_source_op_to_string(sop)); + return -2; + } +#endif + else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Option %s is inapplicable to this socket type", + _php_source_op_to_string(sop)); + return -2; + } +#endif +} + +#if RFC3678_API +static int _php_source_op_to_rfc3678_op(enum source_op sop) +{ + switch (sop) { + case JOIN_SOURCE: + return MCAST_JOIN_SOURCE_GROUP; + case LEAVE_SOURCE: + return MCAST_LEAVE_SOURCE_GROUP; + case BLOCK_SOURCE: + return MCAST_BLOCK_SOURCE; + case UNBLOCK_SOURCE: + return MCAST_UNBLOCK_SOURCE; + } + + assert(0); + return 0; +} +#else +static const char *_php_source_op_to_string(enum source_op sop) +{ + switch (sop) { + case JOIN_SOURCE: + return "MCAST_JOIN_SOURCE_GROUP"; + case LEAVE_SOURCE: + return "MCAST_LEAVE_SOURCE_GROUP"; + case BLOCK_SOURCE: + return "MCAST_BLOCK_SOURCE"; + case UNBLOCK_SOURCE: + return "MCAST_UNBLOCK_SOURCE"; + } + + assert(0); + return ""; +} + +static int _php_source_op_to_ipv4_op(enum source_op sop) +{ + switch (sop) { + case JOIN_SOURCE: + return IP_ADD_SOURCE_MEMBERSHIP; + case LEAVE_SOURCE: + return IP_DROP_SOURCE_MEMBERSHIP; + case BLOCK_SOURCE: + return IP_BLOCK_SOURCE; + case UNBLOCK_SOURCE: + return IP_UNBLOCK_SOURCE; + } + + assert(0); + return 0; +} +#endif + +#if PHP_WIN32 +int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_addr *out_addr TSRMLS_DC) +{ + MIB_IPADDRTABLE *addr_table; + ULONG size; + DWORD retval; + DWORD i; + + (void) php_sock; /* not necessary */ + + if (if_index == 0) { + out_addr->s_addr = INADDR_ANY; + return SUCCESS; + } + + size = 4 * (sizeof *addr_table); + addr_table = emalloc(size); +retry: + retval = GetIpAddrTable(addr_table, &size, 0); + if (retval == ERROR_INSUFFICIENT_BUFFER) { + efree(addr_table); + addr_table = emalloc(size); + goto retry; + } + if (retval != NO_ERROR) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "GetIpAddrTable failed with error %lu", retval); + return FAILURE; + } + for (i = 0; i < addr_table->dwNumEntries; i++) { + MIB_IPADDRROW r = addr_table->table[i]; + if (r.dwIndex == if_index) { + out_addr->s_addr = r.dwAddr; + return SUCCESS; + } + } + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "No interface with index %u was found", if_index); + return FAILURE; +} + +int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *if_index TSRMLS_DC) +{ + MIB_IPADDRTABLE *addr_table; + ULONG size; + DWORD retval; + DWORD i; + + (void) php_sock; /* not necessary */ + + if (addr->s_addr == INADDR_ANY) { + *if_index = 0; + return SUCCESS; + } + + size = 4 * (sizeof *addr_table); + addr_table = emalloc(size); +retry: + retval = GetIpAddrTable(addr_table, &size, 0); + if (retval == ERROR_INSUFFICIENT_BUFFER) { + efree(addr_table); + addr_table = emalloc(size); + goto retry; + } + if (retval != NO_ERROR) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "GetIpAddrTable failed with error %lu", retval); + return FAILURE; + } + for (i = 0; i < addr_table->dwNumEntries; i++) { + MIB_IPADDRROW r = addr_table->table[i]; + if (r.dwAddr == addr->s_addr) { + *if_index = r.dwIndex; + return SUCCESS; + } + } + + { + char addr_str[17] = {0}; + inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str)); + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "The interface with IP address %s was not found", addr_str); + } + return FAILURE; +} + +#else + +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_req.ifr_ifindex = if_index; + if (ioctl(php_sock->bsd_socket, SIOCGIFNAME, &if_req) == -1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "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; +} + +int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *if_index TSRMLS_DC) +{ + struct ifconf if_conf = {0}; + char *buf = NULL, + *p; + 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; + else { + lastsize = if_conf.ifc_len; + efree(buf); + 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 + /* if there's no sa_len, assume the ifr_addr field is a sockaddr */ + 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)) { + if (ioctl(php_sock->bsd_socket, SIOCGIFINDEX, (char*)cur_req) + == -1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Error converting interface name to index: error %d", + errno); + goto err; + } else { + *if_index = cur_req->ifr_ifindex; + efree(buf); + return SUCCESS; + } + } + } + + { + char addr_str[17] = {0}; + inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str)); + 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 new file mode 100644 index 0000000000..f5ca064c04 --- /dev/null +++ b/ext/sockets/multicast.h @@ -0,0 +1,59 @@ +int php_if_index_to_addr4( + unsigned if_index, + php_socket *php_sock, + struct in_addr *out_addr TSRMLS_DC); + +int php_add4_to_if_index( + struct in_addr *addr, + php_socket *php_sock, + unsigned *if_index TSRMLS_DC); + +int php_mcast_join( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + unsigned int if_index TSRMLS_DC); + +int php_mcast_leave( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + unsigned int if_index TSRMLS_DC); + +int php_mcast_join_source( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index TSRMLS_DC); + +int php_mcast_leave_source( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index TSRMLS_DC); + +int php_mcast_block_source( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index TSRMLS_DC); + +int php_mcast_unblock_source( + php_socket *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index TSRMLS_DC); diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 7d836862d6..3203543997 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -56,6 +56,9 @@ # 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 #else # include # include @@ -73,8 +76,13 @@ # define IS_INVALID_SOCKET(a) (a->bsd_socket < 0) # define set_errno(a) (errno = a) # include "php_sockets.h" +# if HAVE_IF_NAMETOINDEX +# include +# endif #endif +#include "multicast.h" + ZEND_DECLARE_MODULE_GLOBALS(sockets) static PHP_GINIT_FUNCTION(sockets); @@ -604,6 +612,111 @@ static int php_set_inet_addr(struct sockaddr_in *sin, char *string, php_socket * } /* }}} */ +/* 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) { @@ -666,12 +779,42 @@ 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 MCAST_JOIN_GROUP +#define MCAST_JOIN_GROUP IP_ADD_MEMBERSHIP +#define MCAST_LEAVE_GROUP IP_DROP_MEMBERSHIP +#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 + + 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); + 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("IP_MULTICAST_IF", IP_MULTICAST_IF, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IP_MULTICAST_TTL", IP_MULTICAST_TTL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IP_MULTICAST_LOOP", IP_MULTICAST_LOOP, CONST_CS | CONST_PERSISTENT); +#if HAVE_IPV6 + REGISTER_LONG_CONSTANT("IPV6_MULTICAST_IF", IPV6_MULTICAST_IF, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_MULTICAST_HOPS", IPV6_MULTICAST_HOPS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IPV6_MULTICAST_LOOP", IPV6_MULTICAST_LOOP, CONST_CS | CONST_PERSISTENT); +#endif + #ifndef WIN32 # include "unix_socket_constants.h" #else # include "win32_socket_constants.h" #endif + REGISTER_LONG_CONSTANT("IPPROTO_IP", IPPROTO_IP, CONST_CS | CONST_PERSISTENT); +#if HAVE_IPV6 + REGISTER_LONG_CONSTANT("IPPROTO_IPV6", IPPROTO_IPV6, CONST_CS | CONST_PERSISTENT); +#endif + if ((pe = getprotobyname("tcp"))) { REGISTER_LONG_CONSTANT("SOL_TCP", pe->p_proto, CONST_CS | CONST_PERSISTENT); } @@ -1738,6 +1881,26 @@ PHP_FUNCTION(socket_get_option) ZEND_FETCH_RESOURCE(php_sock, php_socket *, &arg1, -1, le_socket_name, le_socket); + if (level == IPPROTO_IP) { + switch (optname) { + case IP_MULTICAST_IF: { + struct in_addr if_addr; + unsigned int if_index; + optlen = sizeof(if_addr); + if (getsockopt(php_sock->bsd_socket, level, optname, (char*)&if_addr, &optlen) != 0) { + PHP_SOCKET_ERROR(php_sock, "unable to retrieve socket option", errno); + RETURN_FALSE; + } + if (php_add4_to_if_index(&if_addr, php_sock, &if_index TSRMLS_CC) == SUCCESS) { + RETURN_LONG((long) if_index); + } else { + RETURN_FALSE; + } + } + } + } + + /* sol_socket options and general case */ switch(optname) { case SO_LINGER: optlen = sizeof(linger_val); @@ -1778,7 +1941,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); @@ -1786,6 +1949,8 @@ PHP_FUNCTION(socket_get_option) PHP_SOCKET_ERROR(php_sock, "unable to retrieve socket option", errno); RETURN_FALSE; } + if (optlen == 1) + other_val = *((unsigned char *)&other_val); RETURN_LONG(other_val); break; @@ -1793,30 +1958,122 @@ 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); + int (*mcast_sreq_fun)(php_socket *, int, struct sockaddr *, socklen_t, + struct sockaddr *, socklen_t, unsigned TSRMLS_DC); + + 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; + } + + 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; + } + 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) { - zval *arg1, **arg4; - struct linger lv; - php_socket *php_sock; - int ov, optlen, retval; + zval *arg1, **arg4; + struct linger lv; + php_socket *php_sock; + int ov, optlen, retval; #ifdef PHP_WIN32 - int timeout; + int timeout; #else - struct timeval tv; + struct timeval tv; #endif - long level, optname; - void *opt_ptr; - HashTable *opt_ht; - zval **l_onoff, **l_linger; - zval **sec, **usec; - /* key name constants */ - char *l_onoff_key = "l_onoff"; - char *l_linger_key = "l_linger"; - char *sec_key = "sec"; - char *usec_key = "usec"; - + long level, optname; + void *opt_ptr; + 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; } @@ -1825,16 +2082,90 @@ PHP_FUNCTION(socket_set_option) set_errno(0); + if (level == IPPROTO_IP) { + switch (optname) { + case MCAST_JOIN_GROUP: + case MCAST_LEAVE_GROUP: + case MCAST_BLOCK_SOURCE: + case MCAST_UNBLOCK_SOURCE: + case MCAST_JOIN_SOURCE_GROUP: + case MCAST_LEAVE_SOURCE_GROUP: + if (php_do_mcast_opt(php_sock, level, optname, arg4 TSRMLS_CC) == FAILURE) { + RETURN_FALSE; + } else { + RETURN_TRUE; + } + + case IP_MULTICAST_IF: + if (php_get_if_index_from_zval(*arg4, &if_index TSRMLS_CC) == FAILURE) { + RETURN_FALSE; + } + + if (php_if_index_to_addr4(if_index, php_sock, &if_addr TSRMLS_CC) == FAILURE) { + RETURN_FALSE; + } + opt_ptr = &if_addr; + optlen = sizeof(if_addr); + goto dosockopt; + + case IP_MULTICAST_LOOP: + case IP_MULTICAST_TTL: + convert_to_long_ex(arg4); + 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 HAVE_IPV6 + else if (level == IPPROTO_IPV6) { + switch (optname) { + case MCAST_JOIN_GROUP: + case MCAST_LEAVE_GROUP: + case MCAST_BLOCK_SOURCE: + case MCAST_UNBLOCK_SOURCE: + case MCAST_JOIN_SOURCE_GROUP: + case MCAST_LEAVE_SOURCE_GROUP: + 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: + case IPV6_MULTICAST_HOPS: + convert_to_long_ex(arg4); + ov = (int) Z_LVAL_PP(arg4); + opt_ptr = &ov; + optlen = sizeof(ov); + goto dosockopt; + } + } +#endif + switch (optname) { - case SO_LINGER: + case SO_LINGER: { + const char l_onoff_key[] = "l_onoff"; + const char l_linger_key[] = "l_linger"; + convert_to_array_ex(arg4); opt_ht = HASH_OF(*arg4); - if (zend_hash_find(opt_ht, l_onoff_key, strlen(l_onoff_key) + 1, (void **)&l_onoff) == FAILURE) { + if (zend_hash_find(opt_ht, l_onoff_key, sizeof(l_onoff_key), (void **)&l_onoff) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "no key \"%s\" passed in optval", l_onoff_key); RETURN_FALSE; } - if (zend_hash_find(opt_ht, l_linger_key, strlen(l_linger_key) + 1, (void **)&l_linger) == FAILURE) { + if (zend_hash_find(opt_ht, l_linger_key, sizeof(l_linger_key), (void **)&l_linger) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "no key \"%s\" passed in optval", l_linger_key); RETURN_FALSE; } @@ -1848,17 +2179,21 @@ PHP_FUNCTION(socket_set_option) optlen = sizeof(lv); opt_ptr = &lv; break; + } case SO_RCVTIMEO: - case SO_SNDTIMEO: + case SO_SNDTIMEO: { + const char sec_key[] = "sec"; + const char usec_key[] = "usec"; + convert_to_array_ex(arg4); opt_ht = HASH_OF(*arg4); - if (zend_hash_find(opt_ht, sec_key, strlen(sec_key) + 1, (void **)&sec) == FAILURE) { + if (zend_hash_find(opt_ht, sec_key, sizeof(sec_key), (void **)&sec) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "no key \"%s\" passed in optval", sec_key); RETURN_FALSE; } - if (zend_hash_find(opt_ht, usec_key, strlen(usec_key) + 1, (void **)&usec) == FAILURE) { + if (zend_hash_find(opt_ht, usec_key, sizeof(usec_key), (void **)&usec) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "no key \"%s\" passed in optval", usec_key); RETURN_FALSE; } @@ -1876,7 +2211,8 @@ PHP_FUNCTION(socket_set_option) opt_ptr = &timeout; #endif break; - + } + default: convert_to_long_ex(arg4); ov = Z_LVAL_PP(arg4); @@ -1886,10 +2222,12 @@ PHP_FUNCTION(socket_set_option) break; } +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); + if (retval != -2) { /* error, but message already emitted */ + PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno); + } RETURN_FALSE; } diff --git a/ext/sockets/tests/mcast_ipv4_recv.phpt b/ext/sockets/tests/mcast_ipv4_recv.phpt new file mode 100644 index 0000000000..a7aab86ff9 --- /dev/null +++ b/ext/sockets/tests/mcast_ipv4_recv.phpt @@ -0,0 +1,192 @@ +--TEST-- +Multicast support: IPv4 receive options +--SKIPIF-- + '224.0.0.23', + "interface" => 'lo', +)); +if ($so === false) { + die('skip interface \'lo\' is unavailable.'); +} +--FILE-- + $mcastaddr, + "interface" => $interface, +)); +var_dump($so); + +$r = socket_sendto($sends1, $m = "initial packet", strlen($m), 0, $mcastaddr, 3000); +var_dump($r); + +$i = 0; +while (($str = socket_read($s, 3000)) !== FALSE) { + $i++; + echo "$i> ", $str, "\n"; + +if ($i == 1) { + echo "leaving group\n"; + $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); + $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", 3000); + var_dump($r); +} +if ($i == 2) { + echo "re-joining group\n"; + $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + )); + var_dump($so); + $r = socket_sendto($sends2, $m = "ignored mcast packet (different interface)", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); + $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); +} +if ($i == 3) { + echo "blocking source\n"; + $so = socket_set_option($s, $level, MCAST_BLOCK_SOURCE, array( + "group" => $mcastaddr, + "interface" => $interface, + "source" => $sblock, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "ignored packet (blocked source)", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); + $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", 3000); + var_dump($r); +} +if ($i == 4) { + echo "unblocking source\n"; + $so = socket_set_option($s, $level, MCAST_UNBLOCK_SOURCE, array( + "group" => $mcastaddr, + "interface" => $interface, + "source" => $sblock, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "mcast packet from 127.0.0.1", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); +} +if ($i == 5) { + echo "leaving group\n"; + $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); + $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", 3000); + var_dump($r); +} +if ($i == 6) { + echo "joining source group\n"; + $so = socket_set_option($s, $level, MCAST_JOIN_SOURCE_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + "source" => $sblock, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "mcast packet from 127.0.0.1", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); +} +if ($i == 7) { + echo "leaving source group\n"; + $so = socket_set_option($s, $level, MCAST_LEAVE_SOURCE_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + "source" => $sblock, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); + $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", 3000); + var_dump($r); +} +if ($i == 8) { +/* echo "rjsg\n"; + $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + )); + var_dump($so);*/ + break; +} + +} +--EXPECT-- +creating send socket bound to 127.0.0.1 +bool(true) +creating unbound socket and hoping the routing table causes an interface other than lo to be used for sending messages to 224.0.0.23 +bool(true) +creating receive socket +resource(6) of type (Socket) +bool(true) +bool(true) +int(14) +1> initial packet +leaving group +bool(true) +int(20) +int(14) +2> unicast packet +re-joining group +bool(true) +int(42) +int(12) +3> mcast packet +blocking source +bool(true) +int(31) +int(14) +4> unicast packet +unblocking source +bool(true) +int(27) +5> mcast packet from 127.0.0.1 +leaving group +bool(true) +int(20) +int(14) +6> unicast packet +joining source group +bool(true) +int(27) +7> mcast packet from 127.0.0.1 +leaving source group +bool(true) +int(20) +int(14) +8> unicast packet diff --git a/ext/sockets/tests/mcast_ipv4_send.phpt b/ext/sockets/tests/mcast_ipv4_send.phpt new file mode 100644 index 0000000000..ac5bce9162 --- /dev/null +++ b/ext/sockets/tests/mcast_ipv4_send.phpt @@ -0,0 +1,65 @@ +--TEST-- +Multicast support: IPv4 send options +--SKIPIF-- + 'ff01::114', + "interface" => 0, +)); +if ($so === false) { + die('skip unable to join multicast group on any interface.'); +} +$r = socket_sendto($s, $m = "testing packet", strlen($m), 0, 'ff01::114', 3000); +if ($r === false) { + die('skip unable to send multicast packet.'); +} +$so = socket_set_option($s, IPPROTO_IPV6, MCAST_LEAVE_GROUP, array( + "group" => 'ff01::114', + "interface" => 0, +)); +$so = socket_set_option($s, IPPROTO_IPV6, MCAST_JOIN_SOURCE_GROUP, array( + "group" => 'ff01::114', + "interface" => 0, + "source" => '2001::dead:beef', +)); +if ($so === false) { + die('skip protocol independent multicast API is unavailable.'); +} + +--FILE-- + $mcastaddr, + "interface" => $interface, +)) or die("err"); +var_dump($so); + +$r = socket_sendto($sends1, $m = "testing packet", strlen($m), 0, $mcastaddr, 3000); +var_dump($r); +$r = socket_recvfrom($s, $str, 2000, 0, $from, $fromPort); +var_dump($r, $str, $from); +$sblock = $from; + +$r = socket_sendto($sends1, $m = "initial packet", strlen($m), 0, $mcastaddr, 3000); +var_dump($r); + +$i = 0; +while (($str = socket_read($s, 3000)) !== FALSE) { + $i++; + echo "$i> ", $str, "\n"; + +if ($i == 1) { + echo "leaving group\n"; + $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); + $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000); + var_dump($r); +} +if ($i == 2) { + echo "re-joining group\n"; + $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); +} +if ($i == 3) { + echo "blocking source\n"; + $so = socket_set_option($s, $level, MCAST_BLOCK_SOURCE, array( + "group" => $mcastaddr, + "interface" => $interface, + "source" => $sblock, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "ignored packet (blocked source)", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); + $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000); + var_dump($r); +} +if ($i == 4) { + echo "unblocking source\n"; + $so = socket_set_option($s, $level, MCAST_UNBLOCK_SOURCE, array( + "group" => $mcastaddr, + "interface" => $interface, + "source" => $sblock, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); +} +if ($i == 5) { + echo "leaving group\n"; + $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); + $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000); + var_dump($r); +} +if ($i == 6) { + echo "joining source group\n"; + $so = socket_set_option($s, $level, MCAST_JOIN_SOURCE_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + "source" => $sblock, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "mcast packet from desired source", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); +} +if ($i == 7) { + echo "leaving source group\n"; + $so = socket_set_option($s, $level, MCAST_LEAVE_SOURCE_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + "source" => $sblock, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); + $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000); + var_dump($r); +} +if ($i == 8) { + /*echo "joining source group\n"; + $so = socket_set_option($s, $level, MCAST_JOIN_SOURCE_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + "source" => $sblock, + )); + var_dump($so);*/ + break; +} + +} +--EXPECTF-- +creating send socket +resource(4) of type (Socket) +creating receive socket +resource(5) of type (Socket) +bool(true) +bool(true) +int(14) +int(14) +string(14) "testing packet" +string(%d) "%s" +int(14) +1> initial packet +leaving group +bool(true) +int(20) +int(14) +2> unicast packet +re-joining group +bool(true) +int(12) +3> mcast packet +blocking source +bool(true) +int(31) +int(14) +4> unicast packet +unblocking source +bool(true) +int(12) +5> mcast packet +leaving group +bool(true) +int(20) +int(14) +6> unicast packet +joining source group +bool(true) +int(32) +7> mcast packet from desired source +leaving source group +bool(true) +int(20) +int(14) +8> unicast packet diff --git a/ext/sockets/tests/mcast_ipv6_recv_limited.phpt b/ext/sockets/tests/mcast_ipv6_recv_limited.phpt new file mode 100644 index 0000000000..2afea569bc --- /dev/null +++ b/ext/sockets/tests/mcast_ipv6_recv_limited.phpt @@ -0,0 +1,126 @@ +--TEST-- +Multicast support: IPv6 receive options (limited) +--SKIPIF-- + 'ff01::114', + "interface" => 0, +)); +if ($so === false) { + die('skip unable to join multicast group on any interface.'); +} +$r = socket_sendto($s, $m = "testing packet", strlen($m), 0, 'ff01::114', 3000); +if ($r === false) { + die('skip unable to send multicast packet.'); +} +$so = socket_set_option($s, IPPROTO_IPV6, MCAST_LEAVE_GROUP, array( + "group" => 'ff01::114', + "interface" => 0, +)); +$so = socket_set_option($s, IPPROTO_IPV6, MCAST_JOIN_SOURCE_GROUP, array( + "group" => 'ff01::114', + "interface" => 0, + "source" => '2001::dead:beef', +)); +if ($so !== false) { + die('skip protocol independent multicast API is available.'); +} + +--FILE-- + $mcastaddr, + "interface" => $interface, +)) or die("err"); +var_dump($so); + +$r = socket_sendto($sends1, $m = "testing packet", strlen($m), 0, $mcastaddr, 3000); +var_dump($r); +$r = socket_recvfrom($s, $str, 2000, 0, $from, $fromPort); +var_dump($r, $str, $from); +$sblock = $from; + +$r = socket_sendto($sends1, $m = "initial packet", strlen($m), 0, $mcastaddr, 3000); +var_dump($r); + +$i = 0; +while (($str = socket_read($s, 3000)) !== FALSE) { + $i++; + echo "$i> ", $str, "\n"; + +if ($i == 1) { + echo "leaving group\n"; + $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); + $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000); + var_dump($r); +} +if ($i == 2) { + echo "re-joining group\n"; + $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array( + "group" => $mcastaddr, + "interface" => $interface, + )); + var_dump($so); + $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, 3000); + var_dump($r); +} +if ($i == 3) { + break; +} + +} +--EXPECTF-- +creating send socket +resource(4) of type (Socket) +creating receive socket +resource(5) of type (Socket) +bool(true) +bool(true) +int(14) +int(14) +string(14) "testing packet" +string(%d) "%s" +int(14) +1> initial packet +leaving group +bool(true) +int(20) +int(14) +2> unicast packet +re-joining group +bool(true) +int(12) +3> mcast packet diff --git a/ext/sockets/tests/mcast_ipv6_send.phpt b/ext/sockets/tests/mcast_ipv6_send.phpt new file mode 100644 index 0000000000..f9b67143fc --- /dev/null +++ b/ext/sockets/tests/mcast_ipv6_send.phpt @@ -0,0 +1,68 @@ +--TEST-- +Multicast support: IPv6 send options +--SKIPIF-- +