Skip to content

ext/sockets: FreeBSD SO_SPLICE support. #18549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ext/sockets/php_sockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ typedef struct {
} php_socket;

extern PHP_SOCKETS_API zend_class_entry *socket_ce;
#ifdef SO_SPLICE
extern PHP_SOCKETS_API zend_class_entry *socket_so_splice_ce;
#endif

static inline php_socket *socket_from_obj(zend_object *obj) {
return (php_socket *)((char *)(obj) - XtOffsetOf(php_socket, std));
Expand Down
70 changes: 70 additions & 0 deletions ext/sockets/sockets.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ static PHP_RSHUTDOWN_FUNCTION(sockets);
zend_class_entry *socket_ce;
static zend_object_handlers socket_object_handlers;

#ifdef SO_SPLICE
zend_class_entry *socket_so_splice_ce;
#endif

static zend_object *socket_create_object(zend_class_entry *class_type) {
php_socket *intern = zend_object_alloc(sizeof(php_socket), class_type);

Expand Down Expand Up @@ -482,6 +486,10 @@ static PHP_MINIT_FUNCTION(sockets)
socket_object_handlers.get_gc = socket_get_gc;
socket_object_handlers.compare = zend_objects_not_comparable;

#if defined(SO_SPLICE)
socket_so_splice_ce = register_class_SocketSoSplice();
#endif

address_info_ce = register_class_AddressInfo();
address_info_ce->create_object = address_info_create_object;
address_info_ce->default_object_handlers = &address_info_object_handlers;
Expand Down Expand Up @@ -2301,6 +2309,68 @@ PHP_FUNCTION(socket_set_option)
}
#endif

#ifdef SO_SPLICE
case SO_SPLICE: {
if (Z_TYPE_P(arg4) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(arg4), socket_so_splice_ce)) {
zend_argument_type_error(4, "must be of type SocketSoSplice, %s given", zend_zval_value_name(arg4));
RETURN_THROWS();
}

zend_object *so_splice_obj = Z_OBJ_P(arg4);
zval tmpA, tmpB, tmpC;

zval *socket = zend_read_property(socket_so_splice_ce, so_splice_obj, "socket", strlen("socket"), false, &tmpA);
if (Z_TYPE_P(socket) == IS_NULL) {
zend_argument_type_error(4, "socket cannot be null");
RETURN_THROWS();
}
zval *max = zend_read_property(socket_so_splice_ce, so_splice_obj, "max", strlen("max"), false, &tmpB);
zval *array = zend_read_property(socket_so_splice_ce, so_splice_obj, "time", strlen("time"), false, &tmpC);
if (Z_TYPE_P(array) == IS_NULL) {
zend_argument_type_error(4, "time cannot be null");
RETURN_THROWS();
}

php_socket *php_sock = Z_SOCKET_P(socket);
zend_long php_max = Z_LVAL_P(max);
zend_array *php_arr = Z_ARRVAL_P(array);

ENSURE_SOCKET_VALID(php_sock);

if (php_max < 0) {
zend_argument_value_error(4, "\"max\" key must be greater than or equal to 0");
RETURN_THROWS();
}

zval *tv_sec;
zval *tv_usec;

if ((tv_sec = zend_hash_str_find(php_arr, "sec", strlen("sec"))) == NULL) {
zend_argument_value_error(4, "time must have key \"sec\"");
RETURN_THROWS();
}
if ((tv_usec = zend_hash_str_find(php_arr, "usec", strlen("usec"))) == NULL) {
zend_argument_value_error(4, "time must have key \"usec\"");
RETURN_THROWS();
}

struct splice s;
s.sp_fd = (int)php_sock->bsd_socket;
s.sp_max = (off_t)php_max;
if (Z_LVAL_P(tv_sec) > 999999) {
s.sp_idle.tv_sec = Z_LVAL_P(tv_sec) + (Z_LVAL_P(tv_usec) / 1000000);
s.sp_idle.tv_usec = Z_LVAL_P(tv_usec) % 1000000;
} else {
s.sp_idle.tv_sec = Z_LVAL_P(tv_sec);
s.sp_idle.tv_usec = Z_LVAL_P(tv_usec);
}

opt_ptr = &s;
optlen = sizeof(s);
break;
}
#endif

default:
default_case:
ov = zval_get_long(arg4);
Expand Down
16 changes: 16 additions & 0 deletions ext/sockets/sockets.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -1907,6 +1907,13 @@
* @cvalue SO_NOSIGPIPE
*/
const SO_NOSIGPIPE = UNKNOWN;
#if defined(SO_SPLICE)
/**
* @var int
* @cvalue SO_SPLICE
*/
const SO_SPLICE = UNKNOWN;
#endif
#endif
#if defined(TCP_QUICKACK)
/**
Expand Down Expand Up @@ -2038,6 +2045,15 @@ final class AddressInfo
{
}

#ifdef SO_SPLICE
final class SocketSoSplice
{
public \Socket $socket;
public int $max;
public array $time;
}
#endif

function socket_select(?array &$read, ?array &$write, ?array &$except, ?int $seconds, int $microseconds = 0): int|false {}

function socket_create_listen(int $port, int $backlog = SOMAXCONN): Socket|false {}
Expand Down
36 changes: 35 additions & 1 deletion ext/sockets/sockets_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 101 additions & 0 deletions ext/sockets/tests/socket_set_option_so_splice.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
--TEST--
SO_SPLICE usage with socket_set_option
--EXTENSIONS--
sockets
--SKIPIF--
<?php

if (!defined('SO_SPLICE')) {
die('skip SO_SPLICE not available.');
}

?>
--FILE--
<?php
class badClass {}
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$todrain = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

try {
socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $todrain);
} catch (\TypeError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
socket_set_option($socket, SOL_SOCKET, SO_SPLICE, new badClass());
} catch (\TypeError $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
$s = new SocketSoSplice();
$s->max = -1;
$s->socket = $todrain;
$s->time = ["sec" => 0, "usec" => 0];
socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $s);
} catch (\ValueError $e) {
echo $e->getMessage(), PHP_EOL;
}


try {
$s = new SocketSoSplice();
socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $s);
} catch (Error $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
$s = new SocketSoSplice();
$s->max = 1024;
$s->socket = $todrain;
$s->time = ["invalid" => 0, "usec" => 0];
socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $s);
} catch (Error $e) {
echo $e->getMessage(), PHP_EOL;
}

try {
$s = new SocketSoSplice();
$s->max = 1024;
$s->socket = $todrain;
$s->time = ["sec" => 0, "nosec" => 0];
socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $s);
} catch (Error $e) {
echo $e->getMessage(), PHP_EOL;
}

socket_bind($todrain, '127.0.0.1', 0);
socket_listen($todrain, 1);

socket_getsockname($todrain, $addr, $port);
socket_connect($socket, $addr, $port);
$peer = socket_accept($todrain);

$s1 = new SocketSoSplice();
$s1->max = 5;
$s1->socket = $todrain;
$s1->time = ["sec" => 1, "usec" => 1];

$s2 = new SocketSoSplice();
$s2->max = 5;
$s2->socket = $socket;
$s2->time = ["sec" => 1, "usec" => 1];
var_dump(socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $s1));
var_dump(socket_set_option($todrain, SOL_SOCKET, SO_SPLICE, $s2));
socket_write($socket, "HELLO", 5);
var_dump(socket_read($peer, 5, PHP_NORMAL_READ));

socket_close($peer);
socket_close($todrain);
socket_close($socket);

?>
--EXPECT--
socket_set_option(): Argument #4 ($value) must be of type SocketSoSplice, Socket given
socket_set_option(): Argument #4 ($value) must be of type SocketSoSplice, badClass given
socket_set_option(): Argument #4 ($value) "max" key must be greater than or equal to 0
Typed property SocketSoSplice::$socket must not be accessed before initialization
socket_set_option(): Argument #4 ($value) time must have key "sec"
socket_set_option(): Argument #4 ($value) time must have key "usec"