From c8b247d50f3412aa913b089fc0b249f66f4e7ac1 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 10 Jan 2025 21:49:22 +0000 Subject: [PATCH 1/2] ext/sockets: linux AF_PACKET support. --- ext/sockets/config.m4 | 2 +- ext/sockets/sockets.c | 146 ++++++++++++++++++++++--- ext/sockets/sockets.stub.php | 33 +++++- ext/sockets/sockets_arginfo.h | 19 +++- ext/sockets/tests/socket_afpacket.phpt | 37 +++++++ 5 files changed, 218 insertions(+), 19 deletions(-) create mode 100644 ext/sockets/tests/socket_afpacket.phpt diff --git a/ext/sockets/config.m4 b/ext/sockets/config.m4 index e6d1de4c0c70b..37f927a78186a 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]) - AC_CHECK_HEADERS([sys/sockio.h linux/filter.h]) + AC_CHECK_HEADERS([sys/sockio.h linux/filter.h linux/if_packet.h linux/if_ether.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 f7a32894257b5..88a26f19b4f82 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -64,6 +64,12 @@ # else # undef SO_BPF_EXTENSIONS # endif +# if defined(HAVE_LINUX_IF_PACKET_H) +# include +# endif +# if defined(HAVE_LINUX_IF_ETHER_H) +# include +# endif #endif #include @@ -91,6 +97,18 @@ ZEND_DECLARE_MODULE_GLOBALS(sockets) #define PF_INET AF_INET #endif +#if defined(AF_PACKET) +#define PHP_ETH_PROTO_CHECK(protocol, family) \ + do { \ + /* We ll let EINVAL errno warning about miusage, too many protocols conflicts */ \ + if (protocol <= USHRT_MAX && family == AF_PACKET) { \ + protocol = htons(protocol); \ + } \ + } while (0) +#else +#define PHP_ETH_PROTO_CHECK(protocol, family) (0) +#endif + static PHP_GINIT_FUNCTION(sockets); static PHP_GSHUTDOWN_FUNCTION(sockets); static PHP_MINIT_FUNCTION(sockets); @@ -960,13 +978,16 @@ PHP_FUNCTION(socket_read) /* {{{ Queries the remote side of the given socket which may either result in host/port or in a UNIX filesystem path, dependent on its type. */ PHP_FUNCTION(socket_getsockname) { - zval *arg1, *addr, *port = NULL; + zval *arg1, *addr, *objint = NULL; php_sockaddr_storage sa_storage = {0}; php_socket *php_sock; struct sockaddr *sa; struct sockaddr_in *sin; #ifdef HAVE_IPV6 struct sockaddr_in6 *sin6; +#endif +#ifdef AF_PACKET + struct sockaddr_ll *sll; #endif char addrbuf[INET6_ADDRSTRLEN]; struct sockaddr_un *s_un; @@ -977,7 +998,7 @@ PHP_FUNCTION(socket_getsockname) Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce) Z_PARAM_ZVAL(addr) Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(port) + Z_PARAM_ZVAL(objint) ZEND_PARSE_PARAMETERS_END(); php_sock = Z_SOCKET_P(arg1); @@ -997,8 +1018,8 @@ PHP_FUNCTION(socket_getsockname) inet_ntop(AF_INET6, &sin6->sin6_addr, addrbuf, sizeof(addrbuf)); ZEND_TRY_ASSIGN_REF_STRING(addr, addrbuf); - if (port != NULL) { - ZEND_TRY_ASSIGN_REF_LONG(port, htons(sin6->sin6_port)); + if (objint != NULL) { + ZEND_TRY_ASSIGN_REF_LONG(objint, htons(sin6->sin6_port)); } RETURN_TRUE; break; @@ -1008,8 +1029,8 @@ PHP_FUNCTION(socket_getsockname) addr_string = inet_ntop(AF_INET, &sin->sin_addr, addrbuf, sizeof(addrbuf)); ZEND_TRY_ASSIGN_REF_STRING(addr, addr_string); - if (port != NULL) { - ZEND_TRY_ASSIGN_REF_LONG(port, htons(sin->sin_port)); + if (objint != NULL) { + ZEND_TRY_ASSIGN_REF_LONG(objint, htons(sin->sin_port)); } RETURN_TRUE; break; @@ -1020,9 +1041,26 @@ PHP_FUNCTION(socket_getsockname) ZEND_TRY_ASSIGN_REF_STRING(addr, s_un->sun_path); RETURN_TRUE; break; +#ifdef AF_PACKET + case AF_PACKET: + sll = (struct sockaddr_ll *) sa; + char ifrname[IFNAMSIZ]; + + if (UNEXPECTED(!if_indextoname(sll->sll_ifindex, ifrname))) { + zend_throw_error(NULL, "invalid interface index"); + RETURN_THROWS(); + } + + ZEND_TRY_ASSIGN_REF_STRING(addr, ifrname); + if (objint != NULL) { + ZEND_TRY_ASSIGN_REF_LONG(objint, sll->sll_ifindex); + } + RETURN_TRUE; + 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_PACKET, AF_INET, or AF_INET6"); RETURN_THROWS(); } } @@ -1117,9 +1155,12 @@ PHP_FUNCTION(socket_create) if (domain != AF_UNIX #ifdef HAVE_IPV6 && domain != AF_INET6 +#endif +#ifdef AF_PACKET + && domain != AF_PACKET #endif && domain != AF_INET) { - zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET6, or AF_INET"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_PACKET, AF_INET6, or AF_INET"); RETURN_THROWS(); } @@ -1138,6 +1179,8 @@ PHP_FUNCTION(socket_create) RETURN_THROWS(); } + PHP_ETH_PROTO_CHECK(protocol, domain); + object_init_ex(return_value, socket_ce); php_sock = Z_SOCKET_P(return_value); @@ -1275,20 +1318,20 @@ PHP_FUNCTION(socket_bind) php_socket *php_sock; char *addr; size_t addr_len; - zend_long port = 0; + zend_long objint = 0; zend_long retval = 0; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce) Z_PARAM_STRING(addr, addr_len) Z_PARAM_OPTIONAL - Z_PARAM_LONG(port) + Z_PARAM_LONG(objint) ZEND_PARSE_PARAMETERS_END(); php_sock = Z_SOCKET_P(arg1); ENSURE_SOCKET_VALID(php_sock); - if (port < 0 || port > USHRT_MAX) { + if (objint < 0 || objint > USHRT_MAX) { zend_argument_value_error(3, "must be between 0 and %u", USHRT_MAX); RETURN_THROWS(); } @@ -1316,7 +1359,7 @@ PHP_FUNCTION(socket_bind) struct sockaddr_in *sa = (struct sockaddr_in *) sock_type; sa->sin_family = AF_INET; - sa->sin_port = htons((unsigned short) port); + sa->sin_port = htons((unsigned short) objint); if (! php_set_inet_addr(sa, addr, php_sock)) { RETURN_FALSE; @@ -1331,7 +1374,7 @@ PHP_FUNCTION(socket_bind) struct sockaddr_in6 *sa = (struct sockaddr_in6 *) sock_type; sa->sin6_family = AF_INET6; - sa->sin6_port = htons((unsigned short) port); + sa->sin6_port = htons((unsigned short) objint); if (! php_set_inet6_addr(sa, addr, php_sock)) { RETURN_FALSE; @@ -1340,9 +1383,26 @@ PHP_FUNCTION(socket_bind) retval = bind(php_sock->bsd_socket, (struct sockaddr *)sa, sizeof(struct sockaddr_in6)); break; } +#endif +#ifdef AF_PACKET + case AF_PACKET: + { + struct sockaddr_ll *sa = (struct sockaddr_ll *) sock_type; + socklen_t sa_len = sizeof(sa); + + if (getsockname(php_sock->bsd_socket, sock_type, &sa_len) < 0) { + zend_value_error("invalid AF_PACKET socket"); + RETURN_THROWS(); + } + + sa->sll_ifindex = if_nametoindex(addr); + + retval = bind(php_sock->bsd_socket, sock_type, sizeof(struct sockaddr_ll)); + 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_PACKET, AF_INET, or AF_INET6"); RETURN_THROWS(); } @@ -1443,6 +1503,9 @@ PHP_FUNCTION(socket_recvfrom) struct sockaddr_in sin; #ifdef HAVE_IPV6 struct sockaddr_in6 sin6; +#endif +#ifdef AF_PACKET + //struct sockaddr_ll sll; #endif char addrbuf[INET6_ADDRSTRLEN]; socklen_t slen; @@ -1547,6 +1610,38 @@ PHP_FUNCTION(socket_recvfrom) ZEND_TRY_ASSIGN_REF_STRING(arg5, addrbuf[0] ? addrbuf : "::"); ZEND_TRY_ASSIGN_REF_LONG(arg6, ntohs(sin6.sin6_port)); break; +#endif +#ifdef AF_PACKET + /* + case AF_PACKET: + // TODO expose and use proper ethernet frame type instead i.e. src mac, dst mac and payload to userland + // ditto for socket_sendto + slen = sizeof(sll); + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + char ifrname[IFNAMSIZ]; + + retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sll, (socklen_t *)&slen); + + if (retval < 0) { + PHP_SOCKET_ERROR(php_sock, "unable to recvfrom", errno); + zend_string_efree(recv_buf); + RETURN_FALSE; + } + ZSTR_LEN(recv_buf) = retval; + ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0'; + + if (UNEXPECTED(!if_indextoname(sll.sll_ifindex, ifrname))) { + PHP_SOCKET_ERROR(php_sock, "unable to get the interface name", errno); + zend_string_efree(recv_buf); + RETURN_FALSE; + } + + ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf); + ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); + ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + break; + */ #endif default: zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); @@ -1566,6 +1661,9 @@ PHP_FUNCTION(socket_sendto) struct sockaddr_in sin; #ifdef HAVE_IPV6 struct sockaddr_in6 sin6; +#endif +#ifdef AF_PACKET + //struct sockaddr_ll sll; #endif int retval; size_t buf_len, addr_len; @@ -1639,6 +1737,22 @@ PHP_FUNCTION(socket_sendto) retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &sin6, sizeof(sin6)); break; +#endif +#ifdef AF_PACKET + /* + case AF_PACKET: + if (port_is_null) { + zend_argument_value_error(6, "cannot be null when the socket type is AF_PACKET"); + RETURN_THROWS(); + } + + memset(&sll, 0, sizeof(sll)); + sll.sll_family = AF_PACKET; + sll.sll_ifindex = port; + + retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &sin, sizeof(sin)); + break; + */ #endif default: zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); @@ -2702,6 +2816,8 @@ PHP_FUNCTION(socket_addrinfo_bind) ai = Z_ADDRESS_INFO_P(arg1); + PHP_ETH_PROTO_CHECK(ai->addrinfo.ai_protocol, ai->addrinfo.ai_family); + object_init_ex(return_value, socket_ce); php_sock = Z_SOCKET_P(return_value); @@ -2765,6 +2881,8 @@ PHP_FUNCTION(socket_addrinfo_connect) ai = Z_ADDRESS_INFO_P(arg1); + PHP_ETH_PROTO_CHECK(ai->addrinfo.ai_protocol, ai->addrinfo.ai_family); + object_init_ex(return_value, socket_ce); php_sock = Z_SOCKET_P(return_value); diff --git a/ext/sockets/sockets.stub.php b/ext/sockets/sockets.stub.php index 325ade65c9f88..e9682c5600d1d 100644 --- a/ext/sockets/sockets.stub.php +++ b/ext/sockets/sockets.stub.php @@ -26,6 +26,13 @@ */ const AF_DIVERT = UNKNOWN; #endif +#ifdef AF_PACKET +/** + * @var int + * @cvalue AF_PACKET + */ +const AF_PACKET = UNKNOWN; +#endif /** * @var int * @cvalue SOCK_STREAM @@ -1978,6 +1985,28 @@ */ const UDPLITE_RECV_CSCOV = UNKNOWN; #endif +#if defined(ETH_P_ALL) +/** + * @var int + * @cvalue ETH_P_IP + */ +const ETH_P_IP = UNKNOWN; +/** + * @var int + * @cvalue ETH_P_IPV6 + */ +const ETH_P_IPV6 = UNKNOWN; +/** + * @var int + * @cvalue ETH_P_LOOP + */ +const ETH_P_LOOP = UNKNOWN; +/** + * @var int + * @cvalue ETH_P_ALL + */ +const ETH_P_ALL = UNKNOWN; +#endif /** * @strict-properties @@ -2017,13 +2046,13 @@ function socket_read(Socket $socket, int $length, int $mode = PHP_BINARY_READ): * @param string $address * @param int $port */ -function socket_getsockname(Socket $socket, &$address, &$port = null): bool {} +function socket_getsockname(Socket $socket, &$address, ?int &$objint = null): bool {} /** * @param string $address * @param int $port */ -function socket_getpeername(Socket $socket, &$address, &$port = null): bool {} +function socket_getpeername(Socket $socket, &$address, ?int &$objint = null): bool {} function socket_create(int $domain, int $type, int $protocol): Socket|false {} diff --git a/ext/sockets/sockets_arginfo.h b/ext/sockets/sockets_arginfo.h index fdaa537b41daf..15ceb6fcfa69d 100644 --- a/ext/sockets/sockets_arginfo.h +++ b/ext/sockets/sockets_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 2c5f4685d5dab42426d4fe0553dd17cb9935a572 */ + * Stub hash: 1b54a83cd0f640fb0df2565155070455b8814f4b */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_socket_select, 0, 4, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(1, read, IS_ARRAY, 1) @@ -48,7 +48,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_socket_getsockname, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, socket, Socket, 0) ZEND_ARG_INFO(1, address) - ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, port, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(1, objint, IS_LONG, 1, "null") ZEND_END_ARG_INFO() #define arginfo_socket_getpeername arginfo_socket_getsockname @@ -319,6 +319,9 @@ static void register_sockets_symbols(int module_number) #endif #if defined(AF_DIVERT) REGISTER_LONG_CONSTANT("AF_DIVERT", AF_DIVERT, CONST_PERSISTENT); +#endif +#if defined(AF_PACKET) + REGISTER_LONG_CONSTANT("AF_PACKET", AF_PACKET, CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("SOCK_STREAM", SOCK_STREAM, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SOCK_DGRAM", SOCK_DGRAM, CONST_PERSISTENT); @@ -1087,6 +1090,18 @@ static void register_sockets_symbols(int module_number) #if defined(UDPLITE_RECV_CSCOV) REGISTER_LONG_CONSTANT("UDPLITE_RECV_CSCOV", UDPLITE_RECV_CSCOV, CONST_PERSISTENT); #endif +#if defined(ETH_P_ALL) + REGISTER_LONG_CONSTANT("ETH_P_IP", ETH_P_IP, CONST_PERSISTENT); +#endif +#if defined(ETH_P_ALL) + REGISTER_LONG_CONSTANT("ETH_P_IPV6", ETH_P_IPV6, CONST_PERSISTENT); +#endif +#if defined(ETH_P_ALL) + REGISTER_LONG_CONSTANT("ETH_P_LOOP", ETH_P_LOOP, CONST_PERSISTENT); +#endif +#if defined(ETH_P_ALL) + REGISTER_LONG_CONSTANT("ETH_P_ALL", ETH_P_ALL, CONST_PERSISTENT); +#endif } static zend_class_entry *register_class_Socket(void) diff --git a/ext/sockets/tests/socket_afpacket.phpt b/ext/sockets/tests/socket_afpacket.phpt new file mode 100644 index 0000000000000..d0de69f7b1a19 --- /dev/null +++ b/ext/sockets/tests/socket_afpacket.phpt @@ -0,0 +1,37 @@ +--TEST-- +socket_getsockname from AF_PACKET socket +--EXTENSIONS-- +sockets +posix +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +bool(true) +string(2) "lo" +int(%i) + +Warning: socket_getpeername(): unable to retrieve peer name [95]: Operation not supported in %s on line %d From 9f2b2f3c3437e6415d7ddecd2f9be71b5551bbc6 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 29 Jan 2025 12:34:10 +0000 Subject: [PATCH 2/2] Revert BC break --- ext/sockets/sockets.stub.php | 4 ++-- ext/sockets/sockets_arginfo.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/sockets/sockets.stub.php b/ext/sockets/sockets.stub.php index e9682c5600d1d..142772fc7cdb7 100644 --- a/ext/sockets/sockets.stub.php +++ b/ext/sockets/sockets.stub.php @@ -2046,13 +2046,13 @@ function socket_read(Socket $socket, int $length, int $mode = PHP_BINARY_READ): * @param string $address * @param int $port */ -function socket_getsockname(Socket $socket, &$address, ?int &$objint = null): bool {} +function socket_getsockname(Socket $socket, &$address, &$port = null): bool {} /** * @param string $address * @param int $port */ -function socket_getpeername(Socket $socket, &$address, ?int &$objint = null): bool {} +function socket_getpeername(Socket $socket, &$address, &$port = null): bool {} function socket_create(int $domain, int $type, int $protocol): Socket|false {} diff --git a/ext/sockets/sockets_arginfo.h b/ext/sockets/sockets_arginfo.h index 15ceb6fcfa69d..56d6a280bbafe 100644 --- a/ext/sockets/sockets_arginfo.h +++ b/ext/sockets/sockets_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 1b54a83cd0f640fb0df2565155070455b8814f4b */ + * Stub hash: 0be24cb2f268ab3d43121637ae451d8da4b50410 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_socket_select, 0, 4, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(1, read, IS_ARRAY, 1) @@ -48,7 +48,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_socket_getsockname, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_INFO(0, socket, Socket, 0) ZEND_ARG_INFO(1, address) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(1, objint, IS_LONG, 1, "null") + ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, port, "null") ZEND_END_ARG_INFO() #define arginfo_socket_getpeername arginfo_socket_getsockname