[php-src] master: ext/sockets: CBPF reuseport CBPF filter improvements/strengthening.

David Carlier <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: David Carlier (devnexen)
Date: 2026-08-30T14:41:24+01:00

Commit: https://github.com/php/php-src/commit/c98e10797b231bd716b626e511e68f25056eca38
Raw diff: https://github.com/php/php-src/commit/c98e10797b231bd716b626e511e68f25056eca38.diff

ext/sockets: CBPF reuseport CBPF filter improvements/strengthening.

A zero SO_ATTACH_REUSEPORT_CBPF value detached via SO_DETACH_BPF, an
alias of SO_DETACH_FILTER, leaving the reuseport program attached.
SO_DETACH_REUSEPORT_BPF is used and exported now, the value is
restricted to integers and the level is validated.

Close GH-23278

Changed paths:
  A  ext/sockets/tests/socket_reuseport_cbpf_detach.phpt
  M  NEWS
  M  UPGRADING
  M  ext/sockets/sockets.c
  M  ext/sockets/sockets.stub.php
  M  ext/sockets/sockets_arginfo.h
  M  ext/sockets/tests/socket_reuseport_cbpf.phpt


Diff:

diff --git a/NEWS b/NEWS
index e112bfb5351e..cc52980d890b 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,11 @@ PHP                                                                        NEWS
   . Fixed bug GH-23477 (Memory leak on duplicate native Phar manifest entries).
     (Weilin Du)
 
+- Sockets:
+  . Fixed socket_set_option() with SO_ATTACH_REUSEPORT_CBPF and a zero value,
+    which detached the classic BPF filter instead of the reuseport program.
+    (David Carlier)
+
 - Standard:
   . Fixed a segfault when a stream filter callback unsets StreamBucket::$data
     before re-attaching the bucket. (iliaal)
diff --git a/UPGRADING b/UPGRADING
index 148cad212238..a164599c6f15 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -199,6 +199,16 @@ PHP 8.6 UPGRADE NOTES
     rules" message. Code that compares the exact message may need to be
     updated.
 
+- Sockets:
+  . socket_set_option() with SO_ATTACH_REUSEPORT_CBPF now requires an int
+    $value and a $level of SOL_SOCKET. Any other value type throws a TypeError
+    instead of being coerced, and any other level raises a warning and returns
+    false.
+  . socket_set_option() with SO_ATTACH_REUSEPORT_CBPF and a $value of 0 now
+    detaches the reuseport filter through SO_DETACH_REUSEPORT_BPF. It
+    previously used SO_DETACH_BPF, an alias of SO_DETACH_FILTER, which left the
+    reuseport program attached.
+
 - Sodium:
   . The password-hashing functions sodium_crypto_pwhash(),
     sodium_crypto_pwhash_str(),
@@ -860,6 +870,7 @@ PHP 8.6 UPGRADE NOTES
   . EAI_ALLDONE.
   . EAI_INTR.
   . EAI_IDN_ENCODE.
+  . SO_DETACH_REUSEPORT_BPF (Linux only).
 
 - Standard:
   . ARRAY_FILTER_USE_VALUE.
diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c
index c4c6f224dcfa..66df65657988 100644
--- a/ext/sockets/sockets.c
+++ b/ext/sockets/sockets.c
@@ -2341,13 +2341,26 @@ PHP_FUNCTION(socket_set_option)
 
 #ifdef SO_ATTACH_REUSEPORT_CBPF
 		case SO_ATTACH_REUSEPORT_CBPF: {
+			if (level != SOL_SOCKET) {
+				php_error_docref(NULL, E_WARNING, "Invalid level");
+				RETURN_FALSE;
+			}
+			if (Z_TYPE_P(arg4) != IS_LONG) {
+				zend_argument_type_error(4, "must be of type int when argument #3 ($option) is SO_ATTACH_REUSEPORT_CBPF, %s given", zend_zval_value_name(arg4));
+				RETURN_THROWS();
+			}
 			zend_long cbpf_val = zval_get_long(arg4);
 
 			if (!cbpf_val) {
+#ifdef SO_DETACH_REUSEPORT_BPF
 				ov = 1;
 				optlen = sizeof(ov);
 				opt_ptr = &ov;
-				optname = SO_DETACH_BPF;
+				optname = SO_DETACH_REUSEPORT_BPF;
+#else
+				php_error_docref(NULL, E_WARNING, "Detaching a reuseport CBPF filter is unsupported");
+				RETURN_FALSE;
+#endif
 			} else {
 				uint32_t k = (uint32_t)cbpf_val;
 
diff --git a/ext/sockets/sockets.stub.php b/ext/sockets/sockets.stub.php
index fab32628544d..681d5ba6d0ff 100644
--- a/ext/sockets/sockets.stub.php
+++ b/ext/sockets/sockets.stub.php
@@ -1898,6 +1898,13 @@
  */
 const SO_ATTACH_REUSEPORT_CBPF = UNKNOWN;
 #endif
+#if defined(SO_DETACH_REUSEPORT_BPF)
+/**
+ * @var int
+ * @cvalue SO_DETACH_REUSEPORT_BPF
+ */
+const SO_DETACH_REUSEPORT_BPF = UNKNOWN;
+#endif
 #if defined(SO_DETACH_FILTER)
 /**
  * @var int
diff --git a/ext/sockets/sockets_arginfo.h b/ext/sockets/sockets_arginfo.h
index cfd792244084..203c010f5171 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: 711d3b84051445917c4a8a1d0cdc1d0c6328be07 */
+ * Stub hash: aceee39bed5332f7f26d5d768976c4d5ab96ab3c */
 
 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)
@@ -990,6 +990,9 @@ static void register_sockets_symbols(int module_number)
 #if defined(SO_ATTACH_REUSEPORT_CBPF)
 	REGISTER_LONG_CONSTANT("SO_ATTACH_REUSEPORT_CBPF", SO_ATTACH_REUSEPORT_CBPF, CONST_PERSISTENT);
 #endif
+#if defined(SO_DETACH_REUSEPORT_BPF)
+	REGISTER_LONG_CONSTANT("SO_DETACH_REUSEPORT_BPF", SO_DETACH_REUSEPORT_BPF, CONST_PERSISTENT);
+#endif
 #if defined(SO_DETACH_FILTER)
 	REGISTER_LONG_CONSTANT("SO_DETACH_FILTER", SO_DETACH_FILTER, CONST_PERSISTENT);
 #endif
diff --git a/ext/sockets/tests/socket_reuseport_cbpf.phpt b/ext/sockets/tests/socket_reuseport_cbpf.phpt
index 2210c4438f00..a9b8d5c731ff 100644
--- a/ext/sockets/tests/socket_reuseport_cbpf.phpt
+++ b/ext/sockets/tests/socket_reuseport_cbpf.phpt
@@ -19,19 +19,18 @@ if (!$socket) {
 var_dump(socket_set_option( $socket, SOL_SOCKET, SO_REUSEADDR, true));
 var_dump(socket_set_option( $socket, SOL_SOCKET, SO_REUSEPORT, true));
 try {
-	socket_set_option( $socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, array());
+	socket_set_option( $socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, []);
 } catch (\TypeError $e) {
-	echo $e->getMessage() . PHP_EOL;
+	echo $e::class, ': ', $e->getMessage(), "\n";
 }
 var_dump(socket_set_option( $socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SKF_AD_CPU));
 var_dump(socket_bind($socket, '0.0.0.0'));
 socket_listen($socket);
 socket_close($socket);
 ?>
---EXPECTF--
+--EXPECT--
 bool(true)
 bool(true)
-
-Warning: socket_set_option(): Unable to set socket option [2]: No such file or directory in %s on line %d
+TypeError: socket_set_option(): Argument #4 ($value) must be of type int when argument #3 ($option) is SO_ATTACH_REUSEPORT_CBPF, array given
 bool(true)
 bool(true)
diff --git a/ext/sockets/tests/socket_reuseport_cbpf_detach.phpt b/ext/sockets/tests/socket_reuseport_cbpf_detach.phpt
new file mode 100644
index 000000000000..bd1608a132d5
--- /dev/null
+++ b/ext/sockets/tests/socket_reuseport_cbpf_detach.phpt
@@ -0,0 +1,38 @@
+--TEST--
+socket_set_option() attach/detach round trip for reuseport CBPF filters
+--EXTENSIONS--
+sockets
+--SKIPIF--
+<?php
+if (!defined("SO_ATTACH_REUSEPORT_CBPF") || !defined("SO_DETACH_REUSEPORT_BPF")) {
+	die('SKIP on platforms not supporting reuseport CBPF filters');
+}
+?>
+--FILE--
+<?php
+$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_REUSEPORT, true));
+var_dump(socket_bind($socket, '127.0.0.1', 0));
+var_dump(socket_listen($socket));
+
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SKF_AD_CPU));
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_DETACH_REUSEPORT_BPF, 1));
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_DETACH_REUSEPORT_BPF, 1));
+
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SKF_AD_QUEUE));
+var_dump(socket_set_option($socket, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, 0));
+
+socket_close($socket);
+?>
+--EXPECTF--
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+
+Warning: socket_set_option(): Unable to set socket option [%d]: %s in %s on line %d
+bool(false)
+bool(true)
+bool(true)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.