Skip to content
This repository has been archived by the owner on Dec 16, 2019. It is now read-only.

WIP: #776 - socket_recvfrom / socket_sendto support #780

Merged
merged 11 commits into from
Jan 29, 2018
Merged
50 changes: 50 additions & 0 deletions classes/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ PHP_METHOD(Socket, select);
PHP_METHOD(Socket, read);
PHP_METHOD(Socket, write);
PHP_METHOD(Socket, send);
PHP_METHOD(Socket, recvfrom);
PHP_METHOD(Socket, sendto);

PHP_METHOD(Socket, setBlocking);
PHP_METHOD(Socket, getPeerName);
Expand Down Expand Up @@ -85,6 +87,22 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(Socket_send, 0, 3, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, flags, IS_LONG, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(Socket_recvfrom, 0, 0, 4)
ZEND_ARG_TYPE_INFO(1, buffer, IS_STRING, 1)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, flags, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(1, name, IS_STRING, 1)
ZEND_ARG_TYPE_INFO(1, port, IS_LONG, 1)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(Socket_sendto, 0, 0, 4)
ZEND_ARG_TYPE_INFO(0, buffer, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, length, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, flags, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, addr, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, port, IS_LONG, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(Socket_setBlocking, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, blocking, _IS_BOOL, 0)
ZEND_END_ARG_INFO()
Expand Down Expand Up @@ -124,6 +142,8 @@ zend_function_entry pthreads_socket_methods[] = {
PHP_ME(Socket, read, Socket_read, ZEND_ACC_PUBLIC)
PHP_ME(Socket, write, Socket_write, ZEND_ACC_PUBLIC)
PHP_ME(Socket, send, Socket_send, ZEND_ACC_PUBLIC)
PHP_ME(Socket, recvfrom, Socket_recvfrom, ZEND_ACC_PUBLIC)
PHP_ME(Socket, sendto, Socket_sendto, ZEND_ACC_PUBLIC)
PHP_ME(Socket, setBlocking, Socket_setBlocking, ZEND_ACC_PUBLIC)
PHP_ME(Socket, getPeerName, Socket_getHost, ZEND_ACC_PUBLIC)
PHP_ME(Socket, getSockName, Socket_getHost, ZEND_ACC_PUBLIC)
Expand Down Expand Up @@ -270,6 +290,36 @@ PHP_METHOD(Socket, send) {
pthreads_socket_send(getThis(), buffer, length, flags, return_value);
} /* }}} */

/* {{{ proto bool Socket::recvfrom(string &buf, int length, int flags, string &name [, int &port ]) */
PHP_METHOD(Socket, recvfrom) {
zval *buffer, *name, *port = NULL;
zend_long len, flags;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "z/llz/|z/", &buffer, &len, &flags, &name, &port) == FAILURE) {
return;
}

/* overflow check */
if ((len + 2) < 3) {
RETURN_FALSE;
}

pthreads_socket_recvfrom(getThis(), buffer, len, flags, name, port, return_value);
} /* }}} */

/* {{{ proto bool Socket::sendto(string buf, int length, int flags, string addr [, int port ]) */
PHP_METHOD(Socket, sendto) {
zend_string *buffer, *address = NULL;
zend_long len, flags, port = 0;
int argc = ZEND_NUM_ARGS();

if (zend_parse_parameters(argc, "SllS|l", &buffer, &len, &flags, &address, &port) == FAILURE) {
return;
}

pthreads_socket_sendto(getThis(), argc, buffer, len, flags, address, port, return_value);
} /* }}} */

/* {{{ proto bool Socket::setBlocking(bool blocking) */
PHP_METHOD(Socket, setBlocking) {
zend_bool blocking = 0;
Expand Down
168 changes: 168 additions & 0 deletions src/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -707,4 +707,172 @@ void pthreads_socket_free(pthreads_socket_t *socket, zend_bool closing) {

efree(socket);
}

void pthreads_socket_recvfrom(zval *object, zval *buffer, zend_long len, zend_long flags, zval *name, zval *port, zval *return_value) {
pthreads_object_t *threaded =
PTHREADS_FETCH_FROM(Z_OBJ_P(object));

socklen_t slen;
int retval;
zend_string *recv_buf;

recv_buf = zend_string_alloc(len + 1, 0);

switch (threaded->store.sock->domain) {
#ifndef _WIN32
case AF_UNIX: {
struct sockaddr_un s_un;

slen = sizeof(s_un);
s_un.sun_family = AF_UNIX;
retval = recvfrom(threaded->store.sock->fd, ZSTR_VAL(recv_buf), len, flags, (struct sockaddr *)&s_un, (socklen_t *)&slen);

if (retval < 0) {
zend_string_free(recv_buf);
PTHREADS_SOCKET_ERROR();
}
ZSTR_LEN(recv_buf) = retval;
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0';

zval_dtor(buffer);
zval_dtor(name);

ZVAL_NEW_STR(buffer, recv_buf);
ZVAL_STRING(name, s_un.sun_path);
} break;
#endif
case AF_INET: {
struct sockaddr_in sin;
char *address;

slen = sizeof(sin);
memset(&sin, 0, slen);
sin.sin_family = AF_INET;

if (port == NULL) {
zend_string_free(recv_buf);
WRONG_PARAM_COUNT;
}

retval = recvfrom(threaded->store.sock->fd, ZSTR_VAL(recv_buf), len, flags, (struct sockaddr *)&sin, (socklen_t *)&slen);

if (retval < 0) {
zend_string_free(recv_buf);
PTHREADS_SOCKET_ERROR();
}
ZSTR_LEN(recv_buf) = retval;
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0';

zval_dtor(buffer);
zval_dtor(name);
zval_dtor(port);

address = inet_ntoa(sin.sin_addr);

ZVAL_NEW_STR(buffer, recv_buf);
ZVAL_STRING(name, address ? address : "0.0.0.0");
ZVAL_LONG(port, ntohs(sin.sin_port));
} break;
#if HAVE_IPV6
case AF_INET6: {
struct sockaddr_in6 sin6;
char addr6[INET6_ADDRSTRLEN];

slen = sizeof(sin6);
memset(&sin6, 0, slen);
sin6.sin6_family = AF_INET6;

if (port == NULL) {
zend_string_free(recv_buf);
WRONG_PARAM_COUNT;
}

retval = recvfrom(threaded->store.sock->fd, ZSTR_VAL(recv_buf), len, flags, (struct sockaddr *)&sin6, (socklen_t *)&slen);

if (retval < 0) {
zend_string_free(recv_buf);
PTHREADS_SOCKET_ERROR();
}
ZSTR_LEN(recv_buf) = retval;
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0';

zval_dtor(buffer);
zval_dtor(name);
zval_dtor(port);

memset(addr6, 0, INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, &sin6.sin6_addr, addr6, INET6_ADDRSTRLEN);

ZVAL_NEW_STR(buffer, recv_buf);
ZVAL_STRING(name, addr6[0] ? addr6 : "::");
ZVAL_LONG(port, ntohs(sin6.sin6_port));
} break;
#endif
}

RETURN_LONG(retval);
}

void pthreads_socket_sendto(zval *object, int argc, zend_string *buf, zend_long len, zend_long flags, zend_string *addr, zend_long port, zval *return_value) {
pthreads_object_t *threaded =
PTHREADS_FETCH_FROM(Z_OBJ_P(object));

int retval;

switch (threaded->store.sock->domain) {
#ifndef _WIN32
case AF_UNIX: {
struct sockaddr_un s_un;

memset(&s_un, 0, sizeof(s_un));
s_un.sun_family = AF_UNIX;
snprintf(s_un.sun_path, 108, "%s", ZSTR_VAL(addr));

retval = sendto(threaded->store.sock->fd, ZSTR_VAL(buf), ((size_t)len > ZSTR_LEN(buf)) ? ZSTR_LEN(buf) : (size_t)len, flags, (struct sockaddr *) &s_un, SUN_LEN(&s_un));
} break;
#endif
case AF_INET: {
struct sockaddr_in sin;

if (argc != 5) {
WRONG_PARAM_COUNT;
}

memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons((unsigned short) port);

if (! pthreads_socket_set_inet_addr(threaded->store.sock, &sin, addr)) {
RETURN_FALSE;
}

retval = sendto(threaded->store.sock->fd, ZSTR_VAL(buf), ((size_t)len > ZSTR_LEN(buf)) ? ZSTR_LEN(buf) : (size_t)len, flags, (struct sockaddr *) &sin, sizeof(sin));
} break;
#if HAVE_IPV6
case AF_INET6: {
struct sockaddr_in6 sin6;

if (argc != 5) {
WRONG_PARAM_COUNT;
}

memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_port = htons((unsigned short) port);

if (! pthreads_socket_set_inet6_addr(threaded->store.sock, &sin6, addr)) {
RETURN_FALSE;
}

retval = sendto(threaded->store.sock->fd, ZSTR_VAL(buf), ((size_t)len > ZSTR_LEN(buf)) ? ZSTR_LEN(buf) : (size_t)len, flags, (struct sockaddr *) &sin6, sizeof(sin6));
} break;
#endif
}

if (retval == -1) {
PTHREADS_SOCKET_ERROR();
}

RETURN_LONG(retval);
}
#endif
3 changes: 3 additions & 0 deletions src/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,7 @@ void pthreads_socket_get_peer_name(zval *object, zend_bool port, zval *return_va
void pthreads_socket_get_sock_name(zval *object, zend_bool port, zval *return_value);
void pthreads_socket_select(zval *read, zval *write, zval *except, uint32_t sec, uint32_t usec, zval *return_value);
void pthreads_socket_free(pthreads_socket_t *socket, zend_bool closing);
void pthreads_socket_recvfrom(zval *object, zval *buffer, zend_long len, zend_long flags, zval *name, zval *port, zval *return_value);
void pthreads_socket_sendto(zval *object, int argc, zend_string *buf, zend_long len, zend_long flags, zend_string *addr, zend_long port, zval *return_value);

#endif
58 changes: 58 additions & 0 deletions tests/socket-sentto-recvfrom-ipv4-udp.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
--TEST--
Test if socket_recvfrom() receives data sent by socket_sendto() via IPv4 UDP
--CREDITS--
Copied from php/php-src and adjusted, originally created by
Falko Menge <mail at falko-menge dot de>
PHP Testfest Berlin 2009-05-09
--FILE--
<?php
$socket = new Socket(\Socket::AF_INET, \Socket::SOCK_DGRAM, \Socket::SOL_UDP);
if (!$socket) {
die('Unable to create AF_INET socket');
}
if (!$socket->setBlocking(false)) {
die('Unable to set nonblocking mode for socket');
}

$address = '127.0.0.1';
$socket->sendto('', 1, 0, $address); // cause warning
if (!$socket->bind($address, 1223)) {
die("Unable to bind to $address:1223");
}

try {
$socket->recvfrom($buf, 12, 0, $from, $port);
} catch(RuntimeException $exception) {
var_dump($exception->getMessage());
}
$msg = "Ping!";
$len = strlen($msg);
$bytes_sent = $socket->sendto($msg, $len, 0, $address, 1223);
if ($bytes_sent == -1) {
die('An error occurred while sending to the socket');
} else if ($bytes_sent != $len) {
die($bytes_sent . ' bytes have been sent instead of the ' . $len . ' bytes expected');
}

$from = "";
$port = 0;
$socket->recvfrom($buf, 12, 0); // cause warning
$socket->recvfrom($buf, 12, 0, $from); // cause warning
$bytes_received = $socket->recvfrom($buf, 12, 0, $from, $port);
if ($bytes_received == -1) {
die('An error occurred while receiving from the socket');
} else if ($bytes_received != $len) {
die($bytes_received . ' bytes have been received instead of the ' . $len . ' bytes expected');
}
echo "Received $buf from remote address $from and remote port $port" . PHP_EOL;

$socket->close();
--EXPECTF--

Warning: Wrong parameter count for Socket::sendto() in %s on line %d
string(%d) "Error (%d): Resource temporarily unavailable"

Warning: Socket::recvfrom() expects at least 4 parameters, 3 given in %s on line %d

Warning: Wrong parameter count for Socket::recvfrom() in %s on line %d
Received Ping! from remote address 127.0.0.1 and remote port 1223
64 changes: 64 additions & 0 deletions tests/socket-sentto-recvfrom-ipv6-udp.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--TEST--
Test if socket_recvfrom() receives data sent by socket_sendto() via IPv6 UDP
--CREDITS--
Copied from php/php-src and adjusted, originally created by
Falko Menge <mail at falko-menge dot de>
PHP Testfest Berlin 2009-05-09
--SKIPIF--
<?php
if (substr(PHP_OS, 0, 3) == 'WIN') {
die('skip Not valid for Windows');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is valid if compiled with --enable-ipv6. The AppVeyor configure options might need some adjustments.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you adjust AppVeyor to support IPV6?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'll create a PR. Also, some of these tests aren't working correctly (see https://travis-ci.org/krakjoe/pthreads/jobs/334677874#L800)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a travis issue travis-ci/travis-ci#8711

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the time being fixed with eaa0a58

}
require 'assets/ipv6_skipif.inc';
--FILE--
<?php
$socket = new Socket(\Socket::AF_INET6, \Socket::SOCK_DGRAM, \Socket::SOL_UDP);
if (!$socket) {
die('Unable to create AF_INET6 socket');
}
if (!$socket->setBlocking(false)) {
die('Unable to set nonblocking mode for socket');
}

try {
$socket->recvfrom($buf, 12, 0, $from, $port);
} catch(RuntimeException $exception) {
var_dump($exception->getMessage());
}
$address = '::1';
$socket->sendto('', 1, 0, $address); // cause warning
if (!$socket->bind($address, 1223)) {
die("Unable to bind to $address:1223");
}

$msg = "Ping!";
$len = strlen($msg);
$bytes_sent = $socket->sendto($msg, $len, 0, $address, 1223);
if ($bytes_sent == -1) {
die('An error occurred while sending to the socket');
} else if ($bytes_sent != $len) {
die($bytes_sent . ' bytes have been sent instead of the ' . $len . ' bytes expected');
}

$from = "";
$port = 0;
$socket->recvfrom($buf, 12, 0); // cause warning
$socket->recvfrom($buf, 12, 0, $from); // cause warning
$bytes_received = $socket->recvfrom($buf, 12, 0, $from, $port);
if ($bytes_received == -1) {
die('An error occurred while receiving from the socket');
} else if ($bytes_received != $len) {
die($bytes_received . ' bytes have been received instead of the ' . $len . ' bytes expected');
}
echo "Received $buf from remote address $from and remote port $port" . PHP_EOL;

$socket->close();
--EXPECTF--
string(%d) "Error (%d): Resource temporarily unavailable"

Warning: Wrong parameter count for Socket::sendto() in %s on line %d

Warning: Socket::recvfrom() expects at least 4 parameters, 3 given in %s on line %d

Warning: Wrong parameter count for Socket::recvfrom() in %s on line %d
Received Ping! from remote address ::1 and remote port 1223
Loading