]> granicus.if.org Git - php/commitdiff
- Added multicast support to the sockets extension (bug #40510).
authorGustavo André dos Santos Lopes <cataphract@php.net>
Mon, 14 Mar 2011 00:08:29 +0000 (00:08 +0000)
committerGustavo André dos Santos Lopes <cataphract@php.net>
Mon, 14 Mar 2011 00:08:29 +0000 (00:08 +0000)
ext/sockets/config.m4
ext/sockets/config.w32
ext/sockets/multicast.c [new file with mode: 0644]
ext/sockets/multicast.h [new file with mode: 0644]
ext/sockets/sockets.c
ext/sockets/tests/mcast_ipv4_recv.phpt [new file with mode: 0644]
ext/sockets/tests/mcast_ipv4_send.phpt [new file with mode: 0644]
ext/sockets/tests/mcast_ipv6_recv.phpt [new file with mode: 0644]
ext/sockets/tests/mcast_ipv6_recv_limited.phpt [new file with mode: 0644]
ext/sockets/tests/mcast_ipv6_send.phpt [new file with mode: 0644]

index 574548d60575e809edfaab99f0351a0a991e3145..096a420b5206846cbb3f94545213dc3654a55898 100644 (file)
@@ -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 <sys/types.h>
@@ -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
index 8b633819003924ec7f052798f2bf9532687bdf7a..9c234db8f8df8f703bea205dc0af7080b2165fb2 100644 (file)
@@ -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 (file)
index 0000000..a22419b
--- /dev/null
@@ -0,0 +1,496 @@
+#ifdef HAVE_CONFIG_H\r
+#include "config.h"\r
+#endif\r
+\r
+#include "php.h"\r
+\r
+#if HAVE_SOCKETS\r
+\r
+#include "php_network.h"\r
+#ifdef PHP_WIN32\r
+# include "win32/inet.h"\r
+# include <winsock2.h>\r
+# include <windows.h>\r
+# include <Ws2tcpip.h>\r
+# include <Ws2ipdef.h>\r
+# include "php_sockets.h"\r
+# include "win32/sockets.h"\r
+# define NTDDI_XP NTDDI_WINXP /* bug in SDK */\r
+# include <IPHlpApi.h>\r
+# undef NTDDI_XP\r
+#else\r
+#include <sys/socket.h>\r
+#include <sys/ioctl.h>\r
+#include <net/if.h>\r
+#include <netinet/in.h>\r
+#include <arpa/inet.h>\r
+#endif\r
+\r
+#include "php_sockets.h"\r
+#include "multicast.h"\r
+#include "main/php_network.h"\r
+\r
+#if defined(MCAST_JOIN_GROUP) && 1 &&\\r
+       (!defined(PHP_WIN32) || (_WIN32_WINNT >= 0x600 && SOCKETS_ENABLE_VISTA_API))\r
+#define RFC3678_API 1\r
+#endif\r
+\r
+\r
+enum source_op {\r
+       JOIN_SOURCE,\r
+       LEAVE_SOURCE,\r
+       BLOCK_SOURCE,\r
+       UNBLOCK_SOURCE\r
+};\r
+\r
+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);\r
+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);\r
+#if RFC3678_API\r
+static int _php_source_op_to_rfc3678_op(enum source_op sop);\r
+#else\r
+static const char *_php_source_op_to_string(enum source_op sop);\r
+static int _php_source_op_to_ipv4_op(enum source_op sop);\r
+#endif\r
+\r
+int php_mcast_join(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       unsigned int if_index TSRMLS_DC)\r
+{\r
+       return _php_mcast_join_leave(sock, level, group, group_len, if_index, 1 TSRMLS_CC);\r
+}\r
+\r
+int php_mcast_leave(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       unsigned int if_index TSRMLS_DC)\r
+{\r
+       return _php_mcast_join_leave(sock, level, group, group_len, if_index, 0 TSRMLS_CC);\r
+}\r
+\r
+int php_mcast_join_source(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       struct sockaddr *source,\r
+       socklen_t source_len,\r
+       unsigned int if_index TSRMLS_DC)\r
+{\r
+       return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, JOIN_SOURCE TSRMLS_CC);\r
+}\r
+\r
+int php_mcast_leave_source(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       struct sockaddr *source,\r
+       socklen_t source_len,\r
+       unsigned int if_index TSRMLS_DC)\r
+{\r
+       return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, LEAVE_SOURCE TSRMLS_CC);\r
+}\r
+\r
+int php_mcast_block_source(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       struct sockaddr *source,\r
+       socklen_t source_len,\r
+       unsigned int if_index TSRMLS_DC)\r
+{\r
+       return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, BLOCK_SOURCE TSRMLS_CC);\r
+}\r
+\r
+int php_mcast_unblock_source(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       struct sockaddr *source,\r
+       socklen_t source_len,\r
+       unsigned int if_index TSRMLS_DC)\r
+{\r
+       return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, UNBLOCK_SOURCE TSRMLS_CC);\r
+}\r
+\r
+static int _php_mcast_join_leave(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group, /* struct sockaddr_in/sockaddr_in6 */\r
+       socklen_t group_len,\r
+       unsigned int if_index,\r
+       int join TSRMLS_DC)\r
+{\r
+#ifdef RFC3678_API\r
+       struct group_req greq = {0};\r
+       \r
+       memcpy(&greq.gr_group, group, group_len);\r
+       assert(greq.gr_group.ss_family != 0); /* the caller has set this */\r
+       greq.gr_interface = if_index;\r
+\r
+       return setsockopt(sock->bsd_socket, level,\r
+                       join ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP, (char*)&greq,\r
+                       sizeof(greq));  \r
+#else\r
+       if (sock->type == AF_INET) {\r
+               struct ip_mreq mreq = {0};\r
+               struct in_addr addr;\r
+               \r
+               assert(group_len == sizeof(struct sockaddr_in));\r
+               \r
+               if (if_index != 0) {\r
+                       if (php_if_index_to_addr4(if_index, sock, &addr TSRMLS_CC) ==\r
+                                       FAILURE)\r
+                               return -2; /* failure, but notice already emitted */\r
+                       mreq.imr_interface = addr;\r
+               } else {\r
+                       mreq.imr_interface.s_addr = htonl(INADDR_ANY);\r
+               }\r
+               mreq.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr;\r
+               return setsockopt(sock->bsd_socket, level,\r
+                               join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, (char*)&mreq,\r
+                               sizeof(mreq));\r
+       }\r
+#if HAVE_IPV6\r
+       else if (sock->type == AF_INET6) {\r
+               struct ipv6_mreq mreq = {0};\r
+               \r
+               assert(group_len == sizeof(struct sockaddr_in6));\r
+\r
+               mreq.ipv6mr_multiaddr = ((struct sockaddr_in6*)group)->sin6_addr;\r
+               mreq.ipv6mr_interface = if_index;\r
+               \r
+               return setsockopt(sock->bsd_socket, level,\r
+                               join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, (char*)&mreq,\r
+                               sizeof(mreq));\r
+       }\r
+#endif\r
+       else {\r
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                       "Option %s is inapplicable to this socket type",\r
+                       join ? "MCAST_JOIN_GROUP" : "MCAST_LEAVE_GROUP");\r
+               return -2;\r
+       }\r
+#endif\r
+}\r
+\r
+static int _php_mcast_source_op(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       struct sockaddr *source,\r
+       socklen_t source_len,\r
+       unsigned int if_index,\r
+       enum source_op sop TSRMLS_DC)\r
+{\r
+#ifdef RFC3678_API\r
+       struct group_source_req gsreq = {0};\r
+       \r
+       memcpy(&gsreq.gsr_group, group, group_len);\r
+       assert(gsreq.gsr_group.ss_family != 0);\r
+       memcpy(&gsreq.gsr_source, source, source_len);\r
+       assert(gsreq.gsr_source.ss_family != 0);\r
+       gsreq.gsr_interface = if_index;\r
+       \r
+       return setsockopt(sock->bsd_socket, level,\r
+                       _php_source_op_to_rfc3678_op(sop), (char*)&gsreq, sizeof(gsreq));\r
+#else\r
+       if (sock->type == AF_INET) {\r
+               struct ip_mreq_source mreqs = {0};\r
+               struct in_addr addr;\r
+               \r
+               mreqs.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr;\r
+               mreqs.imr_sourceaddr =  ((struct sockaddr_in*)source)->sin_addr;\r
+               \r
+               assert(group_len == sizeof(struct sockaddr_in));\r
+               assert(source_len == sizeof(struct sockaddr_in));\r
+               \r
+               if (if_index != 0) {\r
+                       if (php_if_index_to_addr4(if_index, sock, &addr TSRMLS_CC) ==\r
+                                       FAILURE)\r
+                               return -2; /* failure, but notice already emitted */\r
+                       mreqs.imr_interface = addr;\r
+               } else {\r
+                       mreqs.imr_interface.s_addr = htonl(INADDR_ANY);\r
+               }\r
+               \r
+               return setsockopt(sock->bsd_socket, level,\r
+                               _php_source_op_to_ipv4_op(sop), (char*)&mreqs, sizeof(mreqs));\r
+       }\r
+#if HAVE_IPV6\r
+       else if (sock->type == AF_INET6) {\r
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                       "This platform does not support %s for IPv6 sockets",\r
+                       _php_source_op_to_string(sop));\r
+               return -2;\r
+       }\r
+#endif\r
+       else {\r
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                       "Option %s is inapplicable to this socket type",\r
+                       _php_source_op_to_string(sop));\r
+               return -2;\r
+       }\r
+#endif\r
+}\r
+\r
+#if RFC3678_API\r
+static int _php_source_op_to_rfc3678_op(enum source_op sop)\r
+{\r
+       switch (sop) {\r
+       case JOIN_SOURCE:\r
+               return MCAST_JOIN_SOURCE_GROUP;\r
+       case LEAVE_SOURCE:\r
+               return MCAST_LEAVE_SOURCE_GROUP;\r
+       case BLOCK_SOURCE:\r
+               return MCAST_BLOCK_SOURCE;\r
+       case UNBLOCK_SOURCE:\r
+               return MCAST_UNBLOCK_SOURCE;\r
+       }\r
+       \r
+       assert(0);\r
+       return 0;\r
+}\r
+#else\r
+static const char *_php_source_op_to_string(enum source_op sop)\r
+{\r
+       switch (sop) {\r
+       case JOIN_SOURCE:\r
+               return "MCAST_JOIN_SOURCE_GROUP";\r
+       case LEAVE_SOURCE:\r
+               return "MCAST_LEAVE_SOURCE_GROUP";\r
+       case BLOCK_SOURCE:\r
+               return "MCAST_BLOCK_SOURCE";\r
+       case UNBLOCK_SOURCE:\r
+               return "MCAST_UNBLOCK_SOURCE";\r
+       }\r
+       \r
+       assert(0);\r
+       return "";\r
+}\r
+\r
+static int _php_source_op_to_ipv4_op(enum source_op sop)\r
+{\r
+       switch (sop) {\r
+       case JOIN_SOURCE:\r
+               return IP_ADD_SOURCE_MEMBERSHIP;\r
+       case LEAVE_SOURCE:\r
+               return IP_DROP_SOURCE_MEMBERSHIP;\r
+       case BLOCK_SOURCE:\r
+               return IP_BLOCK_SOURCE;\r
+       case UNBLOCK_SOURCE:\r
+               return IP_UNBLOCK_SOURCE;\r
+       }\r
+       \r
+       assert(0);\r
+       return 0;\r
+}\r
+#endif\r
+\r
+#if PHP_WIN32\r
+int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_addr *out_addr TSRMLS_DC)\r
+{\r
+       MIB_IPADDRTABLE *addr_table;\r
+    ULONG size;\r
+    DWORD retval;\r
+       DWORD i;\r
+\r
+       (void) php_sock; /* not necessary */\r
+\r
+       if (if_index == 0) {\r
+               out_addr->s_addr = INADDR_ANY;\r
+               return SUCCESS;\r
+       }\r
+\r
+       size = 4 * (sizeof *addr_table);\r
+       addr_table = emalloc(size);\r
+retry:\r
+       retval = GetIpAddrTable(addr_table, &size, 0);\r
+       if (retval == ERROR_INSUFFICIENT_BUFFER) {\r
+               efree(addr_table);\r
+               addr_table = emalloc(size);\r
+               goto retry;\r
+       }\r
+       if (retval != NO_ERROR) {\r
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                       "GetIpAddrTable failed with error %lu", retval);\r
+               return FAILURE;\r
+       }\r
+       for (i = 0; i < addr_table->dwNumEntries; i++) {\r
+               MIB_IPADDRROW r = addr_table->table[i];\r
+               if (r.dwIndex == if_index) {\r
+                       out_addr->s_addr = r.dwAddr;\r
+                       return SUCCESS;\r
+               }\r
+       }\r
+       php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+               "No interface with index %u was found", if_index);\r
+       return FAILURE;\r
+}\r
+\r
+int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *if_index TSRMLS_DC)\r
+{\r
+       MIB_IPADDRTABLE *addr_table;\r
+    ULONG size;\r
+    DWORD retval;\r
+       DWORD i;\r
+\r
+       (void) php_sock; /* not necessary */\r
+\r
+       if (addr->s_addr == INADDR_ANY) {\r
+               *if_index = 0;\r
+               return SUCCESS;\r
+       }\r
+\r
+       size = 4 * (sizeof *addr_table);\r
+       addr_table = emalloc(size);\r
+retry:\r
+       retval = GetIpAddrTable(addr_table, &size, 0);\r
+       if (retval == ERROR_INSUFFICIENT_BUFFER) {\r
+               efree(addr_table);\r
+               addr_table = emalloc(size);\r
+               goto retry;\r
+       }\r
+       if (retval != NO_ERROR) {\r
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                       "GetIpAddrTable failed with error %lu", retval);\r
+               return FAILURE;\r
+       }\r
+       for (i = 0; i < addr_table->dwNumEntries; i++) {\r
+               MIB_IPADDRROW r = addr_table->table[i];\r
+               if (r.dwAddr == addr->s_addr) {\r
+                       *if_index = r.dwIndex;\r
+                       return SUCCESS;\r
+               }\r
+       }\r
+\r
+       {\r
+               char addr_str[17] = {0};\r
+               inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str));\r
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                       "The interface with IP address %s was not found", addr_str);\r
+       }\r
+       return FAILURE;\r
+}\r
+\r
+#else\r
+\r
+int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_addr *out_addr TSRMLS_DC)\r
+{\r
+       struct ifreq if_req;\r
+       \r
+       if (if_index == 0) {\r
+               out_addr->s_addr = INADDR_ANY;\r
+               return SUCCESS;\r
+       }\r
+\r
+       if_req.ifr_ifindex = if_index;\r
+       if (ioctl(php_sock->bsd_socket, SIOCGIFNAME, &if_req) == -1) {\r
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                       "Failed obtaining address for interface %u: error %d", if_index, errno);\r
+               return FAILURE;\r
+       }\r
+       if (ioctl(php_sock->bsd_socket, SIOCGIFADDR, &if_req) == -1) {\r
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                       "Failed obtaining address for interface %u: error %d", if_index, errno);\r
+               return FAILURE;\r
+       }\r
+       \r
+       memcpy(out_addr, &((struct sockaddr_in *) &if_req.ifr_addr)->sin_addr,\r
+               sizeof *out_addr);\r
+       return SUCCESS;\r
+}\r
+\r
+int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *if_index TSRMLS_DC)\r
+{\r
+       struct ifconf   if_conf = {0};\r
+       char                    *buf = NULL,\r
+                                       *p;\r
+       int                             size = 0,\r
+                                       lastsize = 0;\r
+       size_t                  entry_len;\r
+       \r
+       if (addr->s_addr == INADDR_ANY) {\r
+               *if_index = 0;\r
+               return SUCCESS;\r
+       }\r
+       \r
+       for(;;) {\r
+               size += 5 * sizeof(struct ifreq);\r
+               buf = ecalloc(size, 1);\r
+               if_conf.ifc_len = size;\r
+               if_conf.ifc_buf = buf;\r
+               \r
+               if (ioctl(php_sock->bsd_socket, SIOCGIFCONF, (char*)&if_conf) == -1 &&\r
+                               (errno != EINVAL || lastsize != 0)) {\r
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                               "Failed obtaining interfaces list: error %d", errno);\r
+                       goto err;\r
+               }\r
+               \r
+               if (if_conf.ifc_len == lastsize)\r
+                       /* not increasing anymore */\r
+                       break;\r
+               else {\r
+                       lastsize = if_conf.ifc_len;\r
+                       efree(buf);\r
+                       buf = NULL;\r
+               }\r
+       }\r
+       \r
+       for (p = if_conf.ifc_buf;\r
+                p < if_conf.ifc_buf + if_conf.ifc_len;\r
+                p += entry_len) {\r
+               struct ifreq *cur_req;\r
+               \r
+               /* let's hope the pointer is aligned */\r
+               cur_req = (struct ifreq*) p;\r
+               \r
+#ifdef HAVE_SOCKADDR_SA_LEN\r
+               entry_len = cur_req->ifr_addr.sa_len + sizeof(cur_req->ifr_name);\r
+#else\r
+               /* if there's no sa_len, assume the ifr_addr field is a sockaddr */\r
+               entry_len = sizeof(struct sockaddr) + sizeof(cur_req->ifr_name);\r
+#endif\r
+               entry_len = MAX(entry_len, sizeof(*cur_req));\r
+               \r
+               if ((((struct sockaddr*)&cur_req->ifr_addr)->sa_family == AF_INET) &&\r
+                               (((struct sockaddr_in*)&cur_req->ifr_addr)->sin_addr.s_addr ==\r
+                                       addr->s_addr)) {\r
+                       if (ioctl(php_sock->bsd_socket, SIOCGIFINDEX, (char*)cur_req)\r
+                                       == -1) {\r
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                                       "Error converting interface name to index: error %d",\r
+                                       errno);\r
+                               goto err;\r
+                       } else {\r
+                               *if_index = cur_req->ifr_ifindex;\r
+                               efree(buf);\r
+                               return SUCCESS;\r
+                       }\r
+               }\r
+       }\r
+\r
+       {\r
+               char addr_str[17] = {0};\r
+               inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str));\r
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,\r
+                       "The interface with IP address %s was not found", addr_str);\r
+       }\r
+       \r
+err:\r
+       if (buf != NULL)\r
+               efree(buf);\r
+       return FAILURE;\r
+}\r
+#endif\r
+\r
+#endif /* HAVE_SOCKETS */\r
diff --git a/ext/sockets/multicast.h b/ext/sockets/multicast.h
new file mode 100644 (file)
index 0000000..f5ca064
--- /dev/null
@@ -0,0 +1,59 @@
+int php_if_index_to_addr4(\r
+        unsigned if_index,\r
+        php_socket *php_sock,\r
+        struct in_addr *out_addr TSRMLS_DC);\r
+\r
+int php_add4_to_if_index(\r
+        struct in_addr *addr,\r
+        php_socket *php_sock,\r
+        unsigned *if_index TSRMLS_DC);\r
+\r
+int php_mcast_join(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       unsigned int if_index TSRMLS_DC);\r
+\r
+int php_mcast_leave(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       unsigned int if_index TSRMLS_DC);\r
+\r
+int php_mcast_join_source(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       struct sockaddr *source,\r
+       socklen_t source_len,\r
+       unsigned int if_index TSRMLS_DC);\r
+\r
+int php_mcast_leave_source(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       struct sockaddr *source,\r
+       socklen_t source_len,\r
+       unsigned int if_index TSRMLS_DC);\r
+\r
+int php_mcast_block_source(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       struct sockaddr *source,\r
+       socklen_t source_len,\r
+       unsigned int if_index TSRMLS_DC);\r
+\r
+int php_mcast_unblock_source(\r
+       php_socket *sock,\r
+       int level,\r
+       struct sockaddr *group,\r
+       socklen_t group_len,\r
+       struct sockaddr *source,\r
+       socklen_t source_len,\r
+       unsigned int if_index TSRMLS_DC);\r
index 7d836862d6ed684404e0d2b693476d9cfea6d41c..3203543997e2e9c052517531b7b6a2413ca105ab 100644 (file)
@@ -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 <sys/types.h>
 # include <sys/socket.h>
 # define IS_INVALID_SOCKET(a)  (a->bsd_socket < 0)
 # define set_errno(a) (errno = a)
 # include "php_sockets.h"
+# if HAVE_IF_NAMETOINDEX
+#  include <net/if.h>
+# 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 (file)
index 0000000..a7aab86
--- /dev/null
@@ -0,0 +1,192 @@
+--TEST--\r
+Multicast support: IPv4 receive options\r
+--SKIPIF--\r
+<?php\r
+if (!extension_loaded('sockets')) {\r
+    die('skip sockets extension not available.');\r
+}\r
+$s = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);\r
+$br = socket_bind($s, '0.0.0.0', 3000);\r
+$so = socket_set_option($s, IPPROTO_IP, MCAST_JOIN_GROUP, array(\r
+       "group" => '224.0.0.23',\r
+       "interface" => 'lo',\r
+));\r
+if ($so === false) {\r
+    die('skip interface \'lo\' is unavailable.');\r
+}\r
+--FILE--\r
+<?php\r
+$domain = AF_INET;\r
+$level = IPPROTO_IP;\r
+$interface = "lo";\r
+$mcastaddr = '224.0.0.23';\r
+$sblock = "127.0.0.1";\r
+\r
+echo "creating send socket bound to 127.0.0.1\n";\r
+$sends1 = socket_create($domain, SOCK_DGRAM, SOL_UDP);\r
+$br = socket_bind($sends1, '127.0.0.1');\r
+var_dump($br);\r
+\r
+echo "creating unbound socket and hoping the routing table causes an interface other than lo to be used for sending messages to $mcastaddr\n";\r
+$sends2 = socket_create($domain, SOCK_DGRAM, SOL_UDP);\r
+var_dump($br);\r
+\r
+echo "creating receive socket\n";\r
+$s = socket_create($domain, SOCK_DGRAM, SOL_UDP);\r
+var_dump($s);\r
+$br = socket_bind($s, '0.0.0.0', 3000);\r
+var_dump($br);\r
+\r
+$so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array(\r
+       "group" => $mcastaddr,\r
+       "interface" => $interface,\r
+));\r
+var_dump($so);\r
+\r
+$r = socket_sendto($sends1, $m = "initial packet", strlen($m), 0, $mcastaddr, 3000);\r
+var_dump($r);\r
+\r
+$i = 0;\r
+while (($str = socket_read($s, 3000)) !== FALSE) {\r
+       $i++;\r
+       echo "$i> ", $str, "\n";\r
+\r
+if ($i == 1) {\r
+       echo "leaving group\n";\r
+       $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+       $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 2) {\r
+       echo "re-joining group\n";\r
+       $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends2, $m = "ignored mcast packet (different interface)", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+       $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 3) {\r
+       echo "blocking source\n";\r
+       $so = socket_set_option($s, $level, MCAST_BLOCK_SOURCE, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+               "source" => $sblock,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "ignored packet (blocked source)", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+       $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 4) {\r
+       echo "unblocking source\n";\r
+       $so = socket_set_option($s, $level, MCAST_UNBLOCK_SOURCE, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+               "source" => $sblock,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "mcast packet from 127.0.0.1", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 5) {\r
+       echo "leaving group\n";\r
+       $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+       $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 6) {\r
+       echo "joining source group\n";\r
+       $so = socket_set_option($s, $level, MCAST_JOIN_SOURCE_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+               "source" => $sblock,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "mcast packet from 127.0.0.1", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 7) {\r
+       echo "leaving source group\n";\r
+       $so = socket_set_option($s, $level, MCAST_LEAVE_SOURCE_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+               "source" => $sblock,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+       $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "127.0.0.1", 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 8) {\r
+/*     echo "rjsg\n";\r
+       $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+       ));\r
+       var_dump($so);*/\r
+       break;\r
+}\r
+\r
+}\r
+--EXPECT--\r
+creating send socket bound to 127.0.0.1\r
+bool(true)\r
+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\r
+bool(true)\r
+creating receive socket\r
+resource(6) of type (Socket)\r
+bool(true)\r
+bool(true)\r
+int(14)\r
+1> initial packet\r
+leaving group\r
+bool(true)\r
+int(20)\r
+int(14)\r
+2> unicast packet\r
+re-joining group\r
+bool(true)\r
+int(42)\r
+int(12)\r
+3> mcast packet\r
+blocking source\r
+bool(true)\r
+int(31)\r
+int(14)\r
+4> unicast packet\r
+unblocking source\r
+bool(true)\r
+int(27)\r
+5> mcast packet from 127.0.0.1\r
+leaving group\r
+bool(true)\r
+int(20)\r
+int(14)\r
+6> unicast packet\r
+joining source group\r
+bool(true)\r
+int(27)\r
+7> mcast packet from 127.0.0.1\r
+leaving source group\r
+bool(true)\r
+int(20)\r
+int(14)\r
+8> unicast packet\r
diff --git a/ext/sockets/tests/mcast_ipv4_send.phpt b/ext/sockets/tests/mcast_ipv4_send.phpt
new file mode 100644 (file)
index 0000000..ac5bce9
--- /dev/null
@@ -0,0 +1,65 @@
+--TEST--\r
+Multicast support: IPv4 send options\r
+--SKIPIF--\r
+<?php\r
+if (!extension_loaded('sockets')) {\r
+    die('skip sockets extension not available.');\r
+}\r
+if (socket_set_option($s, $level, IP_MULTICAST_IF, 1) === false) {\r
+       die("skip interface 1 either doesn't exist or has no ipv4 address");\r
+}\r
+--FILE--\r
+<?php\r
+$domain = AF_INET;\r
+$level = IPPROTO_IP;\r
+$s = socket_create($domain, SOCK_DGRAM, SOL_UDP) or die("err");\r
+\r
+echo "Setting IP_MULTICAST_TTL\n";\r
+$r = socket_set_option($s, $level, IP_MULTICAST_TTL, 9);\r
+var_dump($r);\r
+$r = socket_get_option($s, $level, IP_MULTICAST_TTL);\r
+var_dump($r);\r
+echo "\n";\r
+\r
+echo "Setting IP_MULTICAST_LOOP\n";\r
+$r = socket_set_option($s, $level, IP_MULTICAST_LOOP, 0);\r
+var_dump($r);\r
+$r = socket_get_option($s, $level, IP_MULTICAST_LOOP);\r
+var_dump($r);\r
+$r = socket_set_option($s, $level, IP_MULTICAST_LOOP, 1);\r
+var_dump($r);\r
+$r = socket_get_option($s, $level, IP_MULTICAST_LOOP);\r
+var_dump($r);\r
+echo "\n";\r
+\r
+echo "Setting IP_MULTICAST_IF\n";\r
+echo "interface 0:\n";\r
+$r = socket_set_option($s, $level, IP_MULTICAST_IF, 0);\r
+var_dump($r);\r
+$r = socket_get_option($s, $level, IP_MULTICAST_IF);\r
+var_dump($r);\r
+echo "interface 1:\n";\r
+$r = socket_set_option($s, $level, IP_MULTICAST_IF, 1);\r
+var_dump($r);\r
+$r = socket_get_option($s, $level, IP_MULTICAST_IF);\r
+var_dump($r);\r
+echo "\n";\r
+\r
+--EXPECT--\r
+Setting IP_MULTICAST_TTL\r
+bool(true)\r
+int(9)\r
+\r
+Setting IP_MULTICAST_LOOP\r
+bool(true)\r
+int(0)\r
+bool(true)\r
+int(1)\r
+\r
+Setting IP_MULTICAST_IF\r
+interface 0:\r
+bool(true)\r
+int(0)\r
+interface 1:\r
+bool(true)\r
+int(1)\r
diff --git a/ext/sockets/tests/mcast_ipv6_recv.phpt b/ext/sockets/tests/mcast_ipv6_recv.phpt
new file mode 100644 (file)
index 0000000..b33306d
--- /dev/null
@@ -0,0 +1,216 @@
+--TEST--\r
+Multicast support: IPv6 receive options\r
+--SKIPIF--\r
+<?php\r
+if (!extension_loaded('sockets')) {\r
+    die('skip sockets extension not available.');\r
+}\r
+if (!defined('IPPROTO_IPV6')) {\r
+       die('skip IPv6 not available.');\r
+}\r
+$s = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP);\r
+$br = socket_bind($s, '::', 3000);\r
+/* On Linux, there is no route ff00::/8 by default on lo, which makes it\r
+ * troublesome to send multicast traffic from lo, which we must since\r
+ * we're dealing with interface-local traffic... */\r
+$so = socket_set_option($s, IPPROTO_IPV6, MCAST_JOIN_GROUP, array(\r
+       "group" => 'ff01::114',\r
+       "interface" => 0,\r
+));\r
+if ($so === false) {\r
+    die('skip unable to join multicast group on any interface.');\r
+}\r
+$r = socket_sendto($s, $m = "testing packet", strlen($m), 0, 'ff01::114', 3000);\r
+if ($r === false) {\r
+       die('skip unable to send multicast packet.');\r
+}\r
+$so = socket_set_option($s, IPPROTO_IPV6, MCAST_LEAVE_GROUP, array(\r
+       "group" => 'ff01::114',\r
+       "interface" => 0,\r
+));\r
+$so = socket_set_option($s, IPPROTO_IPV6, MCAST_JOIN_SOURCE_GROUP, array(\r
+       "group" => 'ff01::114',\r
+       "interface" => 0,\r
+       "source" => '2001::dead:beef',\r
+));\r
+if ($so === false) {\r
+    die('skip protocol independent multicast API is unavailable.');\r
+}\r
+\r
+--FILE--\r
+<?php\r
+$domain = AF_INET6;\r
+$level = IPPROTO_IPV6;\r
+$interface = 0;\r
+$mcastaddr = 'ff01::114';\r
+$sblock = "?";\r
+\r
+echo "creating send socket\n";\r
+$sends1 = socket_create($domain, SOCK_DGRAM, SOL_UDP) or die("err");\r
+var_dump($sends1);\r
+\r
+echo "creating receive socket\n";\r
+$s = socket_create($domain, SOCK_DGRAM, SOL_UDP) or die("err");\r
+var_dump($s);\r
+$br = socket_bind($s, '::0', 3000) or die("err");\r
+var_dump($br);\r
+\r
+$so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array(\r
+       "group" => $mcastaddr,\r
+       "interface" => $interface,\r
+)) or die("err");\r
+var_dump($so);\r
+\r
+$r = socket_sendto($sends1, $m = "testing packet", strlen($m), 0, $mcastaddr, 3000);\r
+var_dump($r);\r
+$r = socket_recvfrom($s, $str, 2000, 0, $from, $fromPort);\r
+var_dump($r, $str, $from);\r
+$sblock = $from;\r
+\r
+$r = socket_sendto($sends1, $m = "initial packet", strlen($m), 0, $mcastaddr, 3000);\r
+var_dump($r);\r
+\r
+$i = 0;\r
+while (($str = socket_read($s, 3000)) !== FALSE) {\r
+       $i++;\r
+       echo "$i> ", $str, "\n";\r
+\r
+if ($i == 1) {\r
+       echo "leaving group\n";\r
+       $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+       $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 2) {\r
+       echo "re-joining group\n";\r
+       $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 3) {\r
+       echo "blocking source\n";\r
+       $so = socket_set_option($s, $level, MCAST_BLOCK_SOURCE, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+               "source" => $sblock,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "ignored packet (blocked source)", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+       $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 4) {\r
+       echo "unblocking source\n";\r
+       $so = socket_set_option($s, $level, MCAST_UNBLOCK_SOURCE, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+               "source" => $sblock,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 5) {\r
+       echo "leaving group\n";\r
+       $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+       $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 6) {\r
+       echo "joining source group\n";\r
+       $so = socket_set_option($s, $level, MCAST_JOIN_SOURCE_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+               "source" => $sblock,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "mcast packet from desired source", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 7) {\r
+       echo "leaving source group\n";\r
+       $so = socket_set_option($s, $level, MCAST_LEAVE_SOURCE_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+               "source" => $sblock,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+       $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 8) {\r
+       /*echo "joining source group\n";\r
+       $so = socket_set_option($s, $level, MCAST_JOIN_SOURCE_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+               "source" => $sblock,\r
+       ));\r
+       var_dump($so);*/\r
+       break;\r
+}\r
+\r
+}\r
+--EXPECTF--\r
+creating send socket\r
+resource(4) of type (Socket)\r
+creating receive socket\r
+resource(5) of type (Socket)\r
+bool(true)\r
+bool(true)\r
+int(14)\r
+int(14)\r
+string(14) "testing packet"\r
+string(%d) "%s"\r
+int(14)\r
+1> initial packet\r
+leaving group\r
+bool(true)\r
+int(20)\r
+int(14)\r
+2> unicast packet\r
+re-joining group\r
+bool(true)\r
+int(12)\r
+3> mcast packet\r
+blocking source\r
+bool(true)\r
+int(31)\r
+int(14)\r
+4> unicast packet\r
+unblocking source\r
+bool(true)\r
+int(12)\r
+5> mcast packet\r
+leaving group\r
+bool(true)\r
+int(20)\r
+int(14)\r
+6> unicast packet\r
+joining source group\r
+bool(true)\r
+int(32)\r
+7> mcast packet from desired source\r
+leaving source group\r
+bool(true)\r
+int(20)\r
+int(14)\r
+8> unicast packet\r
diff --git a/ext/sockets/tests/mcast_ipv6_recv_limited.phpt b/ext/sockets/tests/mcast_ipv6_recv_limited.phpt
new file mode 100644 (file)
index 0000000..2afea56
--- /dev/null
@@ -0,0 +1,126 @@
+--TEST--\r
+Multicast support: IPv6 receive options (limited)\r
+--SKIPIF--\r
+<?php\r
+if (!extension_loaded('sockets')) {\r
+    die('skip sockets extension not available.');\r
+}\r
+if (!defined('IPPROTO_IPV6')) {\r
+       die('skip IPv6 not available.');\r
+}\r
+$s = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP);\r
+$br = socket_bind($s, '::', 3000);\r
+/* On Linux, there is no route ff00::/8 by default on lo, which makes it\r
+ * troublesome to send multicast traffic from lo, which we must since\r
+ * we're dealing with interface-local traffic... */\r
+$so = socket_set_option($s, IPPROTO_IPV6, MCAST_JOIN_GROUP, array(\r
+       "group" => 'ff01::114',\r
+       "interface" => 0,\r
+));\r
+if ($so === false) {\r
+    die('skip unable to join multicast group on any interface.');\r
+}\r
+$r = socket_sendto($s, $m = "testing packet", strlen($m), 0, 'ff01::114', 3000);\r
+if ($r === false) {\r
+       die('skip unable to send multicast packet.');\r
+}\r
+$so = socket_set_option($s, IPPROTO_IPV6, MCAST_LEAVE_GROUP, array(\r
+       "group" => 'ff01::114',\r
+       "interface" => 0,\r
+));\r
+$so = socket_set_option($s, IPPROTO_IPV6, MCAST_JOIN_SOURCE_GROUP, array(\r
+       "group" => 'ff01::114',\r
+       "interface" => 0,\r
+       "source" => '2001::dead:beef',\r
+));\r
+if ($so !== false) {\r
+    die('skip protocol independent multicast API is available.');\r
+}\r
+\r
+--FILE--\r
+<?php\r
+$domain = AF_INET6;\r
+$level = IPPROTO_IPV6;\r
+$interface = 0;\r
+$mcastaddr = 'ff01::114';\r
+$sblock = "?";\r
+\r
+echo "creating send socket\n";\r
+$sends1 = socket_create($domain, SOCK_DGRAM, SOL_UDP) or die("err");\r
+var_dump($sends1);\r
+\r
+echo "creating receive socket\n";\r
+$s = socket_create($domain, SOCK_DGRAM, SOL_UDP) or die("err");\r
+var_dump($s);\r
+$br = socket_bind($s, '::0', 3000) or die("err");\r
+var_dump($br);\r
+\r
+$so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array(\r
+       "group" => $mcastaddr,\r
+       "interface" => $interface,\r
+)) or die("err");\r
+var_dump($so);\r
+\r
+$r = socket_sendto($sends1, $m = "testing packet", strlen($m), 0, $mcastaddr, 3000);\r
+var_dump($r);\r
+$r = socket_recvfrom($s, $str, 2000, 0, $from, $fromPort);\r
+var_dump($r, $str, $from);\r
+$sblock = $from;\r
+\r
+$r = socket_sendto($sends1, $m = "initial packet", strlen($m), 0, $mcastaddr, 3000);\r
+var_dump($r);\r
+\r
+$i = 0;\r
+while (($str = socket_read($s, 3000)) !== FALSE) {\r
+       $i++;\r
+       echo "$i> ", $str, "\n";\r
+\r
+if ($i == 1) {\r
+       echo "leaving group\n";\r
+       $so = socket_set_option($s, $level, MCAST_LEAVE_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "ignored mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+       $r = socket_sendto($sends1, $m = "unicast packet", strlen($m), 0, "::1", 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 2) {\r
+       echo "re-joining group\n";\r
+       $so = socket_set_option($s, $level, MCAST_JOIN_GROUP, array(\r
+               "group" => $mcastaddr,\r
+               "interface" => $interface,\r
+       ));\r
+       var_dump($so);\r
+       $r = socket_sendto($sends1, $m = "mcast packet", strlen($m), 0, $mcastaddr, 3000);\r
+       var_dump($r);\r
+}\r
+if ($i == 3) {\r
+       break;\r
+}\r
+\r
+}\r
+--EXPECTF--\r
+creating send socket\r
+resource(4) of type (Socket)\r
+creating receive socket\r
+resource(5) of type (Socket)\r
+bool(true)\r
+bool(true)\r
+int(14)\r
+int(14)\r
+string(14) "testing packet"\r
+string(%d) "%s"\r
+int(14)\r
+1> initial packet\r
+leaving group\r
+bool(true)\r
+int(20)\r
+int(14)\r
+2> unicast packet\r
+re-joining group\r
+bool(true)\r
+int(12)\r
+3> mcast packet\r
diff --git a/ext/sockets/tests/mcast_ipv6_send.phpt b/ext/sockets/tests/mcast_ipv6_send.phpt
new file mode 100644 (file)
index 0000000..f9b6714
--- /dev/null
@@ -0,0 +1,68 @@
+--TEST--\r
+Multicast support: IPv6 send options\r
+--SKIPIF--\r
+<?php\r
+if (!extension_loaded('sockets')) {\r
+    die('skip sockets extension not available.');\r
+}\r
+if (!defined('IPPROTO_IPV6')) {\r
+       die('skip IPv6 not available.');\r
+}\r
+if (socket_set_option($s, $level, IP_MULTICAST_IF, 1) === false) {\r
+       die("skip interface 1 either doesn't exist or has no ipv6 address");\r
+}\r
+--FILE--\r
+<?php\r
+$domain = AF_INET6;\r
+$level = IPPROTO_IPV6;\r
+$s = socket_create($domain, SOCK_DGRAM, SOL_UDP) or die("err");\r
+\r
+echo "Setting IPV6_MULTICAST_TTL\n";\r
+$r = socket_set_option($s, $level, IPV6_MULTICAST_HOPS, 9);\r
+var_dump($r);\r
+$r = socket_get_option($s, $level, IPV6_MULTICAST_HOPS);\r
+var_dump($r);\r
+echo "\n";\r
+\r
+echo "Setting IPV6_MULTICAST_LOOP\n";\r
+$r = socket_set_option($s, $level, IPV6_MULTICAST_LOOP, 0);\r
+var_dump($r);\r
+$r = socket_get_option($s, $level, IPV6_MULTICAST_LOOP);\r
+var_dump($r);\r
+$r = socket_set_option($s, $level, IPV6_MULTICAST_LOOP, 1);\r
+var_dump($r);\r
+$r = socket_get_option($s, $level, IPV6_MULTICAST_LOOP);\r
+var_dump($r);\r
+echo "\n";\r
+\r
+echo "Setting IPV6_MULTICAST_IF\n";\r
+echo "interface 0:\n";\r
+$r = socket_set_option($s, $level, IPV6_MULTICAST_IF, 0);\r
+var_dump($r);\r
+$r = socket_get_option($s, $level, IPV6_MULTICAST_IF);\r
+var_dump($r);\r
+echo "interface 1:\n";\r
+$r = socket_set_option($s, $level, IPV6_MULTICAST_IF, 1);\r
+var_dump($r);\r
+$r = socket_get_option($s, $level, IPV6_MULTICAST_IF);\r
+var_dump($r);\r
+echo "\n";\r
+\r
+--EXPECT--\r
+Setting IPV6_MULTICAST_TTL\r
+bool(true)\r
+int(9)\r
+\r
+Setting IPV6_MULTICAST_LOOP\r
+bool(true)\r
+int(0)\r
+bool(true)\r
+int(1)\r
+\r
+Setting IPV6_MULTICAST_IF\r
+interface 0:\r
+bool(true)\r
+int(0)\r
+interface 1:\r
+bool(true)\r
+int(1)\r