From 92b99d1933ae035d72d999b05b0bd2dead3b6839 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 8 Sep 2026 21:21:39 +0100 Subject: [PATCH] ext/sockets: AF_VSOCK support addition. vsock carries traffic across the VM boundary with no NIC, no IP and no DNS, so it works before guest networking is up, or where there is none at all as in AWS Nitro Enclaves. A guest connects to VMADDR_CID_HOST and a host to the guest's context ID, both through the usual socket_bind()/socket_connect() with the CID passed as the address string. --- ext/sockets/config.m4 | 2 +- ext/sockets/sockets.c | 211 +++++++++++++++++- ext/sockets/sockets.stub.php | 34 +++ ext/sockets/sockets_arginfo.h | 14 +- ext/sockets/tests/socket_vsock_errors.phpt | 58 +++++ ext/sockets/tests/socket_vsock_loopback.phpt | 83 +++++++ .../tests/socket_vsock_port_range.phpt | 58 +++++ 7 files changed, 448 insertions(+), 12 deletions(-) create mode 100644 ext/sockets/tests/socket_vsock_errors.phpt create mode 100644 ext/sockets/tests/socket_vsock_loopback.phpt create mode 100644 ext/sockets/tests/socket_vsock_port_range.phpt 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 1d4214ea9bfc..eb0184e62c14 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,62 @@ ZEND_DECLARE_MODULE_GLOBALS(sockets) (void)family #endif +#ifdef AF_VSOCK +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 (ZEND_LONG_UINT_OVFL(lval)) { + return false; + } + *cid = (uint32_t) lval; + return true; + + case IS_DOUBLE: + if (!(dval >= 0 && 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 0 and " ZEND_ULONG_FMT, + (zend_ulong) UINT32_MAX); + return false; + } + + if (ZEND_LONG_UINT_OVFL(port)) { + zend_argument_value_error(port_arg_num, "must be between 0 and " ZEND_ULONG_FMT, + (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); @@ -1003,6 +1067,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; @@ -1069,9 +1136,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(); } } @@ -1087,6 +1168,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; @@ -1140,9 +1224,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(); } } @@ -1166,9 +1264,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(); } @@ -1281,9 +1382,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(); } @@ -1335,10 +1453,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: @@ -1404,9 +1528,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(); } @@ -1510,6 +1647,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; @@ -1686,9 +1826,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(); } @@ -1708,6 +1876,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; @@ -1731,11 +1902,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 @@ -1807,9 +1984,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..556199e44d12 --- /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 0 and 4294967295 +ValueError: socket_bind(): Argument #2 ($address) must be a numeric context ID between 0 and 4294967295 +ValueError: socket_bind(): Argument #2 ($address) must be a numeric context ID between 0 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 0 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..eb1745b0453d --- /dev/null +++ b/ext/sockets/tests/socket_vsock_loopback.phpt @@ -0,0 +1,83 @@ +--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) 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..dd5c21b45832 --- /dev/null +++ b/ext/sockets/tests/socket_vsock_port_range.phpt @@ -0,0 +1,58 @@ +--TEST-- +AF_VSOCK rejects a context ID or port outside the unsigned 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, -1); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +try { + socket_bind($socket, '-1', 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 0 and 4294967295 +ValueError: socket_sendto(): Argument #6 ($port) must be between 0 and 4294967295 +ValueError: socket_bind(): Argument #3 ($port) must be between 0 and 4294967295 +ValueError: socket_bind(): Argument #2 ($address) must be a numeric context ID between 0 and 4294967295 +ValueError: socket_bind(): Argument #2 ($address) must be a numeric context ID between 0 and 4294967295