diff --git a/ext/sockets/config.m4 b/ext/sockets/config.m4 index bf183f5e6e7d..83b3734fc2f1 100644 --- a/ext/sockets/config.m4 +++ b/ext/sockets/config.m4 @@ -5,7 +5,7 @@ PHP_ARG_ENABLE([sockets], if test "$PHP_SOCKETS" != "no"; then AC_CHECK_FUNCS([hstrerror if_nametoindex if_indextoname sockatmark accept4]) - AC_CHECK_HEADERS([sys/sockio.h linux/filter.h linux/if_packet.h linux/if_ether.h linux/udp.h]) + AC_CHECK_HEADERS([sys/sockio.h sys/vsock.h linux/filter.h linux/if_packet.h linux/if_ether.h linux/udp.h linux/vm_sockets.h]) AC_DEFINE([HAVE_SOCKETS], [1], [Define to 1 if the PHP extension 'sockets' is available.]) diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 66df65657988..ee72c671d79b 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -72,6 +72,14 @@ # if defined(HAVE_LINUX_UDP_H) # include # endif +# if defined(HAVE_LINUX_VM_SOCKETS_H) +# include +# elif defined(HAVE_SYS_VSOCK_H) +# include +# define PHP_VSOCK_HAS_SVM_LEN 1 +# else +# undef AF_VSOCK +# endif #endif #include @@ -113,6 +121,68 @@ ZEND_DECLARE_MODULE_GLOBALS(sockets) (void)family #endif +#ifdef AF_VSOCK +#if SIZEOF_ZEND_LONG > 4 +# define PHP_VSOCK_ID_OUT_OF_RANGE(v) ((v) < INT32_MIN || (v) > (zend_long) UINT32_MAX) +#else +# define PHP_VSOCK_ID_OUT_OF_RANGE(v) 0 +#endif + +static bool php_vsock_parse_cid(zend_string *addr, uint32_t *cid) +{ + zend_long lval; + double dval; + + switch (is_numeric_string(ZSTR_VAL(addr), ZSTR_LEN(addr), &lval, &dval, 0)) { + case IS_LONG: + if (PHP_VSOCK_ID_OUT_OF_RANGE(lval)) { + return false; + } + *cid = (uint32_t) lval; + return true; + + case IS_DOUBLE: + if (!(dval >= (double) INT32_MIN && dval <= (double) UINT32_MAX) + || dval != (double) (int64_t) dval) { + return false; + } + *cid = (uint32_t) (int64_t) dval; + return true; + + default: + return false; + } +} + +static bool php_set_vsock_addr(struct sockaddr_vm *svm, zend_string *addr, zend_long port, + uint32_t addr_arg_num, uint32_t port_arg_num) +{ + uint32_t cid; + + if (!php_vsock_parse_cid(addr, &cid)) { + zend_argument_value_error(addr_arg_num, "must be a numeric context ID between %d and " ZEND_ULONG_FMT, + INT32_MIN, (zend_ulong) UINT32_MAX); + return false; + } + + if (PHP_VSOCK_ID_OUT_OF_RANGE(port)) { + zend_argument_value_error(port_arg_num, "must be between %d and " ZEND_ULONG_FMT, + INT32_MIN, (zend_ulong) UINT32_MAX); + return false; + } + + memset(svm, 0, sizeof(*svm)); +#ifdef PHP_VSOCK_HAS_SVM_LEN + svm->svm_len = sizeof(*svm); +#endif + svm->svm_family = AF_VSOCK; + svm->svm_cid = cid; + svm->svm_port = (uint32_t) port; + + return true; +} +#endif + static PHP_GINIT_FUNCTION(sockets); static PHP_GSHUTDOWN_FUNCTION(sockets); static PHP_MINIT_FUNCTION(sockets); @@ -994,6 +1064,9 @@ PHP_FUNCTION(socket_getsockname) #endif #ifdef AF_PACKET struct sockaddr_ll *sll; +#endif +#ifdef AF_VSOCK + struct sockaddr_vm *svm; #endif char addrbuf[INET6_ADDRSTRLEN]; struct sockaddr_un *s_un; @@ -1060,9 +1133,23 @@ PHP_FUNCTION(socket_getsockname) } RETURN_TRUE; #endif +#ifdef AF_VSOCK + case AF_VSOCK: { + size_t cid_len; + + svm = (struct sockaddr_vm *) sa; + cid_len = (size_t) snprintf(addrbuf, sizeof(addrbuf), "%u", svm->svm_cid); + + ZEND_TRY_ASSIGN_REF_STRINGL(addr, addrbuf, cid_len); + if (objint != NULL) { + ZEND_TRY_ASSIGN_REF_LONG(objint, (zend_long) svm->svm_port); + } + RETURN_TRUE; + } +#endif default: - zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_VSOCK, AF_INET, or AF_INET6"); RETURN_THROWS(); } } @@ -1078,6 +1165,9 @@ PHP_FUNCTION(socket_getpeername) struct sockaddr_in *sin; #ifdef HAVE_IPV6 struct sockaddr_in6 *sin6; +#endif +#ifdef AF_VSOCK + struct sockaddr_vm *svm; #endif char addrbuf[INET6_ADDRSTRLEN]; struct sockaddr_un *s_un; @@ -1131,9 +1221,23 @@ PHP_FUNCTION(socket_getpeername) ZEND_TRY_ASSIGN_REF_STRING(arg2, s_un->sun_path); RETURN_TRUE; +#ifdef AF_VSOCK + case AF_VSOCK: { + size_t cid_len; + + svm = (struct sockaddr_vm *) sa; + cid_len = (size_t) snprintf(addrbuf, sizeof(addrbuf), "%u", svm->svm_cid); + + ZEND_TRY_ASSIGN_REF_STRINGL(arg2, addrbuf, cid_len); + if (arg3 != NULL) { + ZEND_TRY_ASSIGN_REF_LONG(arg3, (zend_long) svm->svm_port); + } + RETURN_TRUE; + } +#endif default: - zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_VSOCK, AF_INET, or AF_INET6"); RETURN_THROWS(); } } @@ -1157,9 +1261,12 @@ PHP_FUNCTION(socket_create) #endif #ifdef AF_PACKET && domain != AF_PACKET +#endif +#ifdef AF_VSOCK + && domain != AF_VSOCK #endif && domain != AF_INET) { - zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET6, or AF_INET"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_VSOCK, AF_INET6, or AF_INET"); RETURN_THROWS(); } @@ -1272,9 +1379,26 @@ PHP_FUNCTION(socket_connect) (socklen_t)(offsetof(struct sockaddr_un, sun_path) + ZSTR_LEN(addr))); break; } +#ifdef AF_VSOCK + case AF_VSOCK: { + struct sockaddr_vm svm; + + if (port_is_null) { + zend_argument_value_error(3, "cannot be null when the socket type is AF_VSOCK"); + RETURN_THROWS(); + } + + if (!php_set_vsock_addr(&svm, addr, port, 2, 3)) { + RETURN_THROWS(); + } + + retval = connect(php_sock->bsd_socket, (struct sockaddr *) &svm, sizeof(svm)); + break; + } +#endif default: - zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_VSOCK, AF_INET, or AF_INET6"); RETURN_THROWS(); } @@ -1326,10 +1450,16 @@ PHP_FUNCTION(socket_bind) php_sock = Z_SOCKET_P(arg1); ENSURE_SOCKET_VALID(php_sock); - if (objint < 0 || objint > USHRT_MAX) { - zend_argument_value_error(3, "must be between 0 and %u", USHRT_MAX); - RETURN_THROWS(); +#ifdef AF_VSOCK + if (php_sock->type != AF_VSOCK) { +#endif + if (objint < 0 || objint > USHRT_MAX) { + zend_argument_value_error(3, "must be between 0 and %u", USHRT_MAX); + RETURN_THROWS(); + } +#ifdef AF_VSOCK } +#endif switch(php_sock->type) { case AF_UNIX: @@ -1395,9 +1525,22 @@ PHP_FUNCTION(socket_bind) retval = bind(php_sock->bsd_socket, sock_type, sizeof(struct sockaddr_ll)); break; } +#endif +#ifdef AF_VSOCK + case AF_VSOCK: + { + struct sockaddr_vm *sa = (struct sockaddr_vm *) sock_type; + + if (!php_set_vsock_addr(sa, addr, objint, 2, 3)) { + RETURN_THROWS(); + } + + retval = bind(php_sock->bsd_socket, sock_type, sizeof(struct sockaddr_vm)); + break; + } #endif default: - zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_VSOCK, AF_INET, or AF_INET6"); RETURN_THROWS(); } @@ -1501,6 +1644,9 @@ PHP_FUNCTION(socket_recvfrom) #endif #ifdef AF_PACKET struct sockaddr_ll sll; +#endif +#ifdef AF_VSOCK + struct sockaddr_vm svm; #endif char addrbuf[INET6_ADDRSTRLEN]; socklen_t slen; @@ -1677,9 +1823,37 @@ PHP_FUNCTION(socket_recvfrom) } break; } +#endif +#ifdef AF_VSOCK + case AF_VSOCK: { + size_t cid_len; + + slen = sizeof(svm); + memset(&svm, 0, slen); + + retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), length, flags, (struct sockaddr *)&svm, &slen); + + if (retval < 0) { + PHP_SOCKET_ERROR(php_sock, "unable to recvfrom", errno); + zend_string_efree(recv_buf); + RETURN_FALSE; + } + ZSTR_LEN(recv_buf) = MIN((size_t)retval, (size_t)length); + ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0'; + + cid_len = (size_t) snprintf(addrbuf, sizeof(addrbuf), "%u", svm.svm_cid); + + ZEND_TRY_ASSIGN_REF_NEW_STR(zdata, recv_buf); + ZEND_TRY_ASSIGN_REF_STRINGL(zaddr, addrbuf, cid_len); + + if (zport) { + ZEND_TRY_ASSIGN_REF_LONG(zport, (zend_long) svm.svm_port); + } + break; + } #endif default: - zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_VSOCK, AF_INET, or AF_INET6"); RETURN_THROWS(); } @@ -1699,6 +1873,9 @@ PHP_FUNCTION(socket_sendto) #endif #ifdef AF_PACKET struct sockaddr_ll sll; +#endif +#ifdef AF_VSOCK + struct sockaddr_vm svm; #endif int retval; size_t buf_len; @@ -1722,11 +1899,17 @@ PHP_FUNCTION(socket_sendto) #ifdef AF_PACKET if (php_sock->type != AF_PACKET) { +#endif +#ifdef AF_VSOCK + if (php_sock->type != AF_VSOCK) { #endif if (port < 0 || port > USHRT_MAX) { zend_argument_value_error(6, "must be between 0 and %u", USHRT_MAX); RETURN_THROWS(); } +#ifdef AF_VSOCK + } +#endif #ifdef AF_PACKET } #endif @@ -1798,9 +1981,23 @@ PHP_FUNCTION(socket_sendto) retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *)&sll, sizeof(sll)); break; +#endif +#ifdef AF_VSOCK + case AF_VSOCK: + if (port_is_null) { + zend_argument_value_error(6, "cannot be null when the socket type is AF_VSOCK"); + RETURN_THROWS(); + } + + if (!php_set_vsock_addr(&svm, addr, port, 5, 6)) { + RETURN_THROWS(); + } + + retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *)&svm, sizeof(svm)); + break; #endif default: - zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_VSOCK, AF_INET, or AF_INET6"); RETURN_THROWS(); } diff --git a/ext/sockets/sockets.stub.php b/ext/sockets/sockets.stub.php index 681d5ba6d0ff..2aec81a9fbc1 100644 --- a/ext/sockets/sockets.stub.php +++ b/ext/sockets/sockets.stub.php @@ -40,6 +40,40 @@ */ const AF_PACKET = UNKNOWN; #endif +#ifdef AF_VSOCK +/** + * @var int + * @cvalue AF_VSOCK + */ +const AF_VSOCK = UNKNOWN; +/** + * @var int + * @cvalue VMADDR_CID_ANY + */ +const VMADDR_CID_ANY = UNKNOWN; +/** + * @var int + * @cvalue VMADDR_CID_HYPERVISOR + */ +const VMADDR_CID_HYPERVISOR = UNKNOWN; +#ifdef VMADDR_CID_LOCAL +/** + * @var int + * @cvalue VMADDR_CID_LOCAL + */ +const VMADDR_CID_LOCAL = UNKNOWN; +#endif +/** + * @var int + * @cvalue VMADDR_CID_HOST + */ +const VMADDR_CID_HOST = UNKNOWN; +/** + * @var int + * @cvalue VMADDR_PORT_ANY + */ +const VMADDR_PORT_ANY = UNKNOWN; +#endif /** * @var int * @cvalue SOCK_STREAM diff --git a/ext/sockets/sockets_arginfo.h b/ext/sockets/sockets_arginfo.h index 3f62bb3607fe..08f6c2efa7ba 100644 --- a/ext/sockets/sockets_arginfo.h +++ b/ext/sockets/sockets_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit sockets.stub.php instead. - * Stub hash: aceee39bed5332f7f26d5d768976c4d5ab96ab3c */ + * Stub hash: e3d05b2f0e972e7e9f8c8d7b90ce4f621aa49714 */ #include "zend_constants.h" @@ -328,6 +328,18 @@ static void register_sockets_symbols(int module_number) #endif #if defined(AF_PACKET) REGISTER_LONG_CONSTANT("AF_PACKET", AF_PACKET, CONST_PERSISTENT); +#endif +#if defined(AF_VSOCK) + REGISTER_LONG_CONSTANT("AF_VSOCK", AF_VSOCK, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("VMADDR_CID_ANY", VMADDR_CID_ANY, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("VMADDR_CID_HYPERVISOR", VMADDR_CID_HYPERVISOR, CONST_PERSISTENT); +#endif +#if defined(AF_VSOCK) && defined(VMADDR_CID_LOCAL) + REGISTER_LONG_CONSTANT("VMADDR_CID_LOCAL", VMADDR_CID_LOCAL, CONST_PERSISTENT); +#endif +#if defined(AF_VSOCK) + REGISTER_LONG_CONSTANT("VMADDR_CID_HOST", VMADDR_CID_HOST, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("VMADDR_PORT_ANY", VMADDR_PORT_ANY, CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("SOCK_STREAM", SOCK_STREAM, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_DGRAM", SOCK_DGRAM, CONST_PERSISTENT); diff --git a/ext/sockets/tests/socket_vsock_errors.phpt b/ext/sockets/tests/socket_vsock_errors.phpt new file mode 100644 index 000000000000..9a47be34e3ba --- /dev/null +++ b/ext/sockets/tests/socket_vsock_errors.phpt @@ -0,0 +1,58 @@ +--TEST-- +AF_VSOCK address and port validation +--EXTENSIONS-- +sockets +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +try { + socket_bind($socket, '1.5', 1024); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +try { + socket_bind($socket, '4294967296', 1024); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +try { + socket_connect($socket, (string) VMADDR_CID_ANY); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +try { + socket_sendto($socket, 'x', 1, 0, 'host', 1024); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +var_dump(socket_bind($socket, (string) VMADDR_CID_ANY, VMADDR_PORT_ANY)); + +socket_close($socket); +?> +--EXPECT-- +ValueError: socket_bind(): Argument #2 ($address) must be a numeric context ID between -2147483648 and 4294967295 +ValueError: socket_bind(): Argument #2 ($address) must be a numeric context ID between -2147483648 and 4294967295 +ValueError: socket_bind(): Argument #2 ($address) must be a numeric context ID between -2147483648 and 4294967295 +ValueError: socket_connect(): Argument #3 ($port) cannot be null when the socket type is AF_VSOCK +ValueError: socket_sendto(): Argument #5 ($address) must be a numeric context ID between -2147483648 and 4294967295 +bool(true) diff --git a/ext/sockets/tests/socket_vsock_loopback.phpt b/ext/sockets/tests/socket_vsock_loopback.phpt new file mode 100644 index 000000000000..8461a528381f --- /dev/null +++ b/ext/sockets/tests/socket_vsock_loopback.phpt @@ -0,0 +1,87 @@ +--TEST-- +AF_VSOCK stream round trip over the loopback transport +--EXTENSIONS-- +sockets +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +string(1) "1" +bool(true) +bool(true) +bool(true) +int(4) +string(4) "ping" +int(4) +int(4) +string(4) "pong" +int(4) +int(4) +string(4) "peng" +bool(true) +string(1) "1" +bool(true) +bool(true) +bool(true) diff --git a/ext/sockets/tests/socket_vsock_port_range.phpt b/ext/sockets/tests/socket_vsock_port_range.phpt new file mode 100644 index 000000000000..3be0ca685ff3 --- /dev/null +++ b/ext/sockets/tests/socket_vsock_port_range.phpt @@ -0,0 +1,58 @@ +--TEST-- +AF_VSOCK rejects a context ID or port beyond the 32 bit range +--EXTENSIONS-- +sockets +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +try { + socket_sendto($socket, 'x', 1, 0, (string) VMADDR_CID_ANY, 4294967296); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +try { + socket_bind($socket, (string) VMADDR_CID_ANY, -2147483649); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +try { + socket_bind($socket, '-2147483649', 0); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +try { + socket_bind($socket, '4294967296', 0); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +socket_close($socket); +?> +--EXPECT-- +ValueError: socket_bind(): Argument #3 ($port) must be between -2147483648 and 4294967295 +ValueError: socket_sendto(): Argument #6 ($port) must be between -2147483648 and 4294967295 +ValueError: socket_bind(): Argument #3 ($port) must be between -2147483648 and 4294967295 +ValueError: socket_bind(): Argument #2 ($address) must be a numeric context ID between -2147483648 and 4294967295 +ValueError: socket_bind(): Argument #2 ($address) must be a numeric context ID between -2147483648 and 4294967295