]> granicus.if.org Git - php/commitdiff
Support sticky IPV6_PKTINFO
authorGustavo Lopes <glopes@nebm.ist.utl.pt>
Tue, 6 Nov 2012 16:27:08 +0000 (17:27 +0100)
committerGustavo Lopes <glopes@nebm.ist.utl.pt>
Sat, 2 Feb 2013 15:38:08 +0000 (16:38 +0100)
ext/sockets/sendrecvmsg.c
ext/sockets/sendrecvmsg.h
ext/sockets/sockets.c
ext/sockets/tests/socket_set_option_in6_pktinfo.phpt [new file with mode: 0644]

index 201adbda4336233b28ad1993458e67a249488052..6b1a528c5b1dfbad3fda14331fdd472ac26789c2 100644 (file)
@@ -77,6 +77,7 @@ struct key_value {
 #define KEY_FILL_SOCKADDR "fill_sockaddr"
 #define KEY_RECVMSG_RET "recvmsg_ret"
 #define KEY_CMSG_LEN   "cmsg_len"
+static const struct key_value empty_key_value_list[] = {{0}};
 
 
 typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx);
@@ -222,6 +223,12 @@ static void err_msg_dispose(struct err_s *err TSRMLS_DC)
                }
        }
 }
+static void allocations_dispose(zend_llist **allocations)
+{
+       zend_llist_destroy(*allocations);
+       efree(*allocations);
+       *allocations = NULL;
+}
 
 static unsigned from_array_iterate(const zval *arr,
                                                                   void (*func)(zval **elem, unsigned i, void **args, ser_context *ctx),
@@ -1660,8 +1667,7 @@ PHP_FUNCTION(socket_recvmsg)
 
                /* we don;t need msghdr anymore; free it */
                msghdr = NULL;
-               zend_llist_destroy(allocations);
-               efree(allocations);
+               allocations_dispose(&allocations);
 
                zval_dtor(zmsg);
                if (!err.has_error) {
@@ -1723,6 +1729,86 @@ PHP_FUNCTION(socket_cmsg_space)
        RETURN_LONG((long)CMSG_SPACE(entry->size + n * entry->var_el_size));
 }
 
+int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4)
+{
+       struct err_s    err = {0};
+       zend_llist              *allocations = NULL;
+       void                    *opt_ptr;
+       socklen_t               optlen;
+       int                             retval;
+
+       assert(level == IPPROTO_IPV6);
+
+       switch (optname) {
+#ifdef IPV6_PKTINFO
+       case IPV6_PKTINFO:
+               opt_ptr = from_zval_run_conversions(*arg4, php_sock, from_zval_write_in6_pktinfo,
+                               sizeof(struct in6_pktinfo),     "in6_pktinfo", &allocations, &err);
+               if (err.has_error) {
+                       err_msg_dispose(&err TSRMLS_CC);
+                       return FAILURE;
+               }
+
+               optlen = sizeof(struct in6_pktinfo);
+               goto dosockopt;
+#endif
+       }
+
+       /* we also support IPV6_TCLASS, but that can be handled by the default
+        * integer optval handling in the caller */
+       return 1;
+
+dosockopt:
+       retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen);
+       if (retval != 0) {
+               PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno);
+       }
+       allocations_dispose(&allocations);
+
+       return retval != 0 ? FAILURE : SUCCESS;
+}
+
+int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result)
+{
+       struct err_s            err = {0};
+       void                            *buffer;
+       socklen_t                       size;
+       int                                     res;
+       to_zval_read_field      *reader;
+
+       assert(level == IPPROTO_IPV6);
+
+       switch (optname) {
+#ifdef IPV6_PKTINFO
+       case IPV6_PKTINFO:
+               size = sizeof(struct in6_pktinfo);
+               reader = &to_zval_read_in6_pktinfo;
+               break;
+#endif
+       default:
+               return 1;
+       }
+
+       buffer = ecalloc(1, size);
+       res = getsockopt(php_sock->bsd_socket, level, optname, buffer, &size);
+       if (res != 0) {
+               PHP_SOCKET_ERROR(php_sock, "unable to get socket option", errno);
+       } else {
+               zval *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo",
+                               empty_key_value_list, &err);
+               if (err.has_error) {
+                       err_msg_dispose(&err);
+                       res = -1;
+               } else {
+                       ZVAL_COPY_VALUE(result, zv);
+                       efree(zv);
+               }
+       }
+       efree(buffer);
+
+       return res == 0 ? SUCCESS : FAILURE;
+}
+
 void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS)
 {
        /* IPv6 ancillary data
index 929a6ad9cfe17ea9ba58dbff1025965781ea2333..82fb38b4b5f8b4266619c973dfd79c5f2eae55c0 100644 (file)
@@ -6,3 +6,6 @@ PHP_FUNCTION(socket_cmsg_space);
 
 void php_socket_sendrecvmsg_init(INIT_FUNC_ARGS);
 void php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS);
+
+int php_do_setsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval **arg4);
+int php_do_getsockopt_ipv6_rfc3542(php_socket *php_sock, int level, int optname, zval *result);
index 1d86028691b532a9c6ba723aa5be9dcfa29a7bcd..9c57e2d98d22ca22d5fa0024fe065fb2192d6076 100644 (file)
@@ -1877,6 +1877,13 @@ PHP_FUNCTION(socket_get_option)
                        }
                }
                }
+       } else if (level == IPPROTO_IPV6) {
+               int ret = php_do_getsockopt_ipv6_rfc3542(php_sock, level, optname, return_value);
+               if (ret == SUCCESS) {
+                       return;
+               } else if (ret == FAILURE) {
+                       RETURN_FALSE;
+               } /* else continue */
        }
 
        /* sol_socket options and general case */
@@ -1981,6 +1988,9 @@ PHP_FUNCTION(socket_set_option)
 #if HAVE_IPV6
        else if (level == IPPROTO_IPV6) {
                int res = php_do_setsockopt_ipv6_mcast(php_sock, level, optname, arg4);
+               if (res == 1) {
+                       res = php_do_setsockopt_ipv6_rfc3542(php_sock, level, optname, arg4);
+               }
                HANDLE_SUBCALL(res);
        }
 #endif
diff --git a/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt b/ext/sockets/tests/socket_set_option_in6_pktinfo.phpt
new file mode 100644 (file)
index 0000000..53320ca
--- /dev/null
@@ -0,0 +1,31 @@
+--TEST--
+socket_set_option() with IPV6_PKTINFO
+--SKIPIF--
+<?php
+if (!extension_loaded('sockets')) {
+die('skip sockets extension not available.');
+}
+if (!defined('IPPROTO_IPV6')) {
+die('skip IPv6 not available.');
+}
+
+--FILE--
+<?php
+
+$s = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("err");
+var_dump(socket_set_option($s, IPPROTO_IPV6, IPV6_PKTINFO, []));
+var_dump(socket_set_option($s, IPPROTO_IPV6, IPV6_PKTINFO, [
+    "addr" => '::1',
+    "ifindex" => 0
+]));
+//Oddly, Linux does not support IPV6_PKTINFO in sockgetopt().
+//See do_ipv6_getsockopt() on the kernel sources
+//A work-around with is sort-of possible (with IPV6_2292PKTOPTIONS),
+//but not worth it
+//var_dump(socket_get_option($s, IPPROTO_IPV6, IPV6_PKTINFO));
+
+--EXPECTF--
+Warning: socket_set_option(): error converting user data (path: in6_pktinfo): The key 'addr' is required in %s on line %d
+bool(false)
+bool(true)
+