mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00

UDP segmentation offload is an optimisation attempt by sending multiple large enough datagrams over UDP which reduces syscalls as by default, they have to be broke down in small UDP packets, it is better if the hardware supports it, other handed down to the software implementation. close GH-18213
3136 lines
80 KiB
C
3136 lines
80 KiB
C
/*
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| https://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
| Authors: Chris Vandomelen <chrisv@b0rked.dhs.org> |
|
|
| Sterling Hughes <sterling@php.net> |
|
|
| Jason Greene <jason@php.net> |
|
|
| Gustavo Lopes <cataphract@php.net> |
|
|
| WinSock: Daniel Beulshausen <daniel@php4win.de> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "php.h"
|
|
|
|
#include "php_network.h"
|
|
#include "ext/standard/file.h"
|
|
#include "ext/standard/info.h"
|
|
#include "php_ini.h"
|
|
#ifdef PHP_WIN32
|
|
# include "windows_common.h"
|
|
# include <windows.h>
|
|
# include <Ws2tcpip.h>
|
|
# include "php_sockets.h"
|
|
# include <win32/sockets.h>
|
|
# include <win32/winutil.h>
|
|
#else
|
|
# include <sys/types.h>
|
|
# include <sys/socket.h>
|
|
# include <netdb.h>
|
|
# include <netinet/in.h>
|
|
# include <netinet/tcp.h>
|
|
# include <sys/un.h>
|
|
# include <arpa/inet.h>
|
|
# include <sys/time.h>
|
|
# include <unistd.h>
|
|
# include <errno.h>
|
|
# include <fcntl.h>
|
|
# include <signal.h>
|
|
# include <sys/uio.h>
|
|
# define set_errno(a) (errno = a)
|
|
# include "php_sockets.h"
|
|
# ifdef HAVE_IF_NAMETOINDEX
|
|
# include <net/if.h>
|
|
# endif
|
|
# if defined(HAVE_LINUX_SOCK_DIAG_H)
|
|
# include <linux/sock_diag.h>
|
|
# else
|
|
# undef SO_MEMINFO
|
|
# endif
|
|
# if defined(HAVE_LINUX_FILTER_H)
|
|
# include <linux/filter.h>
|
|
# else
|
|
# undef SO_BPF_EXTENSIONS
|
|
# endif
|
|
# if defined(HAVE_LINUX_IF_PACKET_H)
|
|
# include <linux/if_packet.h>
|
|
# endif
|
|
# if defined(HAVE_LINUX_IF_ETHER_H)
|
|
# include <linux/if_ether.h>
|
|
# endif
|
|
# if defined(HAVE_LINUX_UDP_H)
|
|
# include <linux/udp.h>
|
|
# endif
|
|
#endif
|
|
|
|
#include <stddef.h>
|
|
|
|
#include "sockaddr_conv.h"
|
|
#include "multicast.h"
|
|
#include "sendrecvmsg.h"
|
|
#include "sockets_arginfo.h"
|
|
|
|
ZEND_DECLARE_MODULE_GLOBALS(sockets)
|
|
|
|
#define SUN_LEN_NO_UB(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
|
|
/* The SUN_LEN macro does pointer arithmetics on NULL which triggers errors in the Clang UBSAN build */
|
|
#ifdef __has_feature
|
|
# if __has_feature(undefined_behavior_sanitizer)
|
|
# undef SUN_LEN
|
|
# define SUN_LEN(su) SUN_LEN_NO_UB(su)
|
|
# endif
|
|
#endif
|
|
#ifndef SUN_LEN
|
|
# define SUN_LEN(su) SUN_LEN_NO_UB(su)
|
|
#endif
|
|
|
|
#ifndef PF_INET
|
|
#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);
|
|
static PHP_MSHUTDOWN_FUNCTION(sockets);
|
|
static PHP_MINFO_FUNCTION(sockets);
|
|
static PHP_RSHUTDOWN_FUNCTION(sockets);
|
|
|
|
/* Socket class */
|
|
|
|
zend_class_entry *socket_ce;
|
|
static zend_object_handlers socket_object_handlers;
|
|
|
|
static zend_object *socket_create_object(zend_class_entry *class_type) {
|
|
php_socket *intern = zend_object_alloc(sizeof(php_socket), class_type);
|
|
|
|
zend_object_std_init(&intern->std, class_type);
|
|
object_properties_init(&intern->std, class_type);
|
|
|
|
intern->bsd_socket = -1; /* invalid socket */
|
|
intern->type = PF_UNSPEC;
|
|
intern->error = 0;
|
|
intern->blocking = 1;
|
|
ZVAL_UNDEF(&intern->zstream);
|
|
|
|
return &intern->std;
|
|
}
|
|
|
|
static zend_function *socket_get_constructor(zend_object *object) {
|
|
zend_throw_error(NULL, "Cannot directly construct Socket, use socket_create() instead");
|
|
return NULL;
|
|
}
|
|
|
|
static void socket_free_obj(zend_object *object)
|
|
{
|
|
php_socket *socket = socket_from_obj(object);
|
|
|
|
if (Z_ISUNDEF(socket->zstream)) {
|
|
if (!IS_INVALID_SOCKET(socket)) {
|
|
close(socket->bsd_socket);
|
|
}
|
|
} else {
|
|
zval_ptr_dtor(&socket->zstream);
|
|
}
|
|
|
|
zend_object_std_dtor(&socket->std);
|
|
}
|
|
|
|
static HashTable *socket_get_gc(zend_object *object, zval **table, int *n)
|
|
{
|
|
php_socket *socket = socket_from_obj(object);
|
|
|
|
*table = &socket->zstream;
|
|
*n = 1;
|
|
|
|
return zend_std_get_properties(object);
|
|
}
|
|
|
|
/* AddressInfo class */
|
|
|
|
typedef struct {
|
|
struct addrinfo addrinfo;
|
|
zend_object std;
|
|
} php_addrinfo;
|
|
|
|
zend_class_entry *address_info_ce;
|
|
static zend_object_handlers address_info_object_handlers;
|
|
|
|
static inline php_addrinfo *address_info_from_obj(zend_object *obj) {
|
|
return (php_addrinfo *)((char *)(obj) - XtOffsetOf(php_addrinfo, std));
|
|
}
|
|
|
|
#define Z_ADDRESS_INFO_P(zv) address_info_from_obj(Z_OBJ_P(zv))
|
|
|
|
static zend_object *address_info_create_object(zend_class_entry *class_type) {
|
|
php_addrinfo *intern = zend_object_alloc(sizeof(php_addrinfo), class_type);
|
|
|
|
zend_object_std_init(&intern->std, class_type);
|
|
object_properties_init(&intern->std, class_type);
|
|
|
|
return &intern->std;
|
|
}
|
|
|
|
static zend_function *address_info_get_constructor(zend_object *object) {
|
|
zend_throw_error(NULL, "Cannot directly construct AddressInfo, use socket_addrinfo_lookup() instead");
|
|
return NULL;
|
|
}
|
|
|
|
static void address_info_free_obj(zend_object *object)
|
|
{
|
|
php_addrinfo *address_info = address_info_from_obj(object);
|
|
|
|
if (address_info->addrinfo.ai_canonname != NULL) {
|
|
efree(address_info->addrinfo.ai_canonname);
|
|
}
|
|
efree(address_info->addrinfo.ai_addr);
|
|
|
|
zend_object_std_dtor(&address_info->std);
|
|
}
|
|
|
|
/* Module registration */
|
|
|
|
zend_module_entry sockets_module_entry = {
|
|
STANDARD_MODULE_HEADER,
|
|
"sockets",
|
|
ext_functions,
|
|
PHP_MINIT(sockets),
|
|
PHP_MSHUTDOWN(sockets),
|
|
NULL,
|
|
PHP_RSHUTDOWN(sockets),
|
|
PHP_MINFO(sockets),
|
|
PHP_SOCKETS_VERSION,
|
|
PHP_MODULE_GLOBALS(sockets),
|
|
PHP_GINIT(sockets),
|
|
PHP_GSHUTDOWN(sockets),
|
|
NULL,
|
|
STANDARD_MODULE_PROPERTIES_EX
|
|
};
|
|
|
|
|
|
#ifdef COMPILE_DL_SOCKETS
|
|
#ifdef ZTS
|
|
ZEND_TSRMLS_CACHE_DEFINE()
|
|
#endif
|
|
ZEND_GET_MODULE(sockets)
|
|
#endif
|
|
|
|
static bool php_open_listen_sock(php_socket *sock, unsigned short port, int backlog) /* {{{ */
|
|
{
|
|
struct sockaddr_in la = {0};
|
|
struct hostent *hp;
|
|
|
|
#ifndef PHP_WIN32
|
|
if ((hp = php_network_gethostbyname("0.0.0.0")) == NULL) {
|
|
#else
|
|
if ((hp = php_network_gethostbyname("localhost")) == NULL) {
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
memcpy((char *) &la.sin_addr, hp->h_addr, hp->h_length);
|
|
la.sin_family = hp->h_addrtype;
|
|
la.sin_port = htons(port);
|
|
|
|
sock->bsd_socket = socket(PF_INET, SOCK_STREAM, 0);
|
|
sock->blocking = 1;
|
|
|
|
if (IS_INVALID_SOCKET(sock)) {
|
|
PHP_SOCKET_ERROR(sock, "unable to create listening socket", errno);
|
|
return 0;
|
|
}
|
|
|
|
sock->type = PF_INET;
|
|
|
|
if (bind(sock->bsd_socket, (struct sockaddr *)&la, sizeof(la)) != 0) {
|
|
PHP_SOCKET_ERROR(sock, "unable to bind to given address", errno);
|
|
close(sock->bsd_socket);
|
|
return 0;
|
|
}
|
|
|
|
if (listen(sock->bsd_socket, backlog) != 0) {
|
|
PHP_SOCKET_ERROR(sock, "unable to listen on socket", errno);
|
|
close(sock->bsd_socket);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
static bool php_accept_connect(php_socket *in_sock, php_socket *out_sock, struct sockaddr *la, socklen_t *la_len) /* {{{ */
|
|
{
|
|
out_sock->bsd_socket = accept(in_sock->bsd_socket, la, la_len);
|
|
|
|
if (IS_INVALID_SOCKET(out_sock)) {
|
|
PHP_SOCKET_ERROR(out_sock, "unable to accept incoming connection", errno);
|
|
return 0;
|
|
}
|
|
|
|
#if !defined(PHP_WIN32)
|
|
/**
|
|
* accept4 could had been used but not all platforms support it (e.g. Haiku, solaris < 11.4, ...)
|
|
* win32, not having any concept of child process, has no need to address it.
|
|
*/
|
|
int mode;
|
|
|
|
if ((mode = fcntl(out_sock->bsd_socket, F_GETFD)) < 0) {
|
|
PHP_SOCKET_ERROR(out_sock, "unable to get fcntl mode on the socket", errno);
|
|
return 0;
|
|
}
|
|
|
|
int cloexec = (mode | FD_CLOEXEC);
|
|
|
|
if (mode != cloexec) {
|
|
if (fcntl(out_sock->bsd_socket, F_SETFD, cloexec) < 0) {
|
|
PHP_SOCKET_ERROR(out_sock, "unable to set cloexec mode on the socket", errno);
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
out_sock->error = 0;
|
|
out_sock->blocking = 1;
|
|
out_sock->type = la->sa_family;
|
|
|
|
return 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ php_read -- wrapper around read() so that it only reads to a \r or \n. */
|
|
static int php_read(php_socket *sock, void *buf, size_t maxlen, int flags)
|
|
{
|
|
int m = 0;
|
|
size_t n = 0;
|
|
int no_read = 0;
|
|
int nonblock = 0;
|
|
char *t = (char *) buf;
|
|
|
|
#ifndef PHP_WIN32
|
|
m = fcntl(sock->bsd_socket, F_GETFL);
|
|
if (m < 0) {
|
|
return m;
|
|
}
|
|
nonblock = (m & O_NONBLOCK);
|
|
m = 0;
|
|
#else
|
|
nonblock = !sock->blocking;
|
|
#endif
|
|
set_errno(0);
|
|
|
|
*t = '\0';
|
|
while (*t != '\n' && *t != '\r' && n < maxlen) {
|
|
if (m > 0) {
|
|
t++;
|
|
n++;
|
|
} else if (m == 0) {
|
|
no_read++;
|
|
if (nonblock && no_read >= 2) {
|
|
return n;
|
|
/* The first pass, m always is 0, so no_read becomes 1
|
|
* in the first pass. no_read becomes 2 in the second pass,
|
|
* and if this is nonblocking, we should return.. */
|
|
}
|
|
|
|
if (no_read > 200) {
|
|
set_errno(ECONNRESET);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (n < maxlen) {
|
|
m = recv(sock->bsd_socket, (void *) t, 1, flags);
|
|
}
|
|
|
|
if (errno != 0 && errno != ESPIPE && errno != EAGAIN) {
|
|
return -1;
|
|
}
|
|
|
|
set_errno(0);
|
|
}
|
|
|
|
if (n < maxlen) {
|
|
n++;
|
|
/* The only reasons it makes it to here is
|
|
* if '\n' or '\r' are encountered. So, increase
|
|
* the return by 1 to make up for the lack of the
|
|
* '\n' or '\r' in the count (since read() takes
|
|
* place at the end of the loop..) */
|
|
}
|
|
|
|
return n;
|
|
}
|
|
/* }}} */
|
|
|
|
char *sockets_strerror(int error) /* {{{ */
|
|
{
|
|
const char *buf;
|
|
|
|
#ifndef PHP_WIN32
|
|
if (error < -10000) {
|
|
if (error == INT_MIN) {
|
|
error = 2147473648;
|
|
} else {
|
|
error = -error - 10000;
|
|
}
|
|
|
|
#ifdef HAVE_HSTRERROR
|
|
buf = hstrerror(error);
|
|
#else
|
|
{
|
|
if (SOCKETS_G(strerror_buf)) {
|
|
efree(SOCKETS_G(strerror_buf));
|
|
}
|
|
|
|
spprintf(&(SOCKETS_G(strerror_buf)), 0, "Host lookup error %d", error);
|
|
buf = SOCKETS_G(strerror_buf);
|
|
}
|
|
#endif
|
|
} else {
|
|
buf = strerror(error);
|
|
}
|
|
#else
|
|
{
|
|
char *tmp = php_win32_error_to_msg(error);
|
|
buf = NULL;
|
|
|
|
if (tmp[0]) {
|
|
if (SOCKETS_G(strerror_buf)) {
|
|
efree(SOCKETS_G(strerror_buf));
|
|
}
|
|
|
|
SOCKETS_G(strerror_buf) = estrdup(tmp);
|
|
free(tmp);
|
|
|
|
buf = SOCKETS_G(strerror_buf);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return (buf ? (char *) buf : "");
|
|
}
|
|
/* }}} */
|
|
|
|
#ifdef PHP_WIN32
|
|
static void sockets_destroy_wsa_info(zval *data)
|
|
{/*{{{*/
|
|
HANDLE h = (HANDLE)Z_PTR_P(data);
|
|
CloseHandle(h);
|
|
}/*}}}*/
|
|
#endif
|
|
|
|
/* {{{ PHP_GINIT_FUNCTION */
|
|
static PHP_GINIT_FUNCTION(sockets)
|
|
{
|
|
#if defined(COMPILE_DL_SOCKETS) && defined(ZTS)
|
|
ZEND_TSRMLS_CACHE_UPDATE();
|
|
#endif
|
|
sockets_globals->last_error = 0;
|
|
sockets_globals->strerror_buf = NULL;
|
|
#ifdef PHP_WIN32
|
|
sockets_globals->wsa_child_count = 0;
|
|
zend_hash_init(&sockets_globals->wsa_info, 0, NULL, sockets_destroy_wsa_info, 1);
|
|
#endif
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ PHP_GSHUTDOWN_FUNCTION */
|
|
static PHP_GSHUTDOWN_FUNCTION(sockets)
|
|
{
|
|
#ifdef PHP_WIN32
|
|
zend_hash_destroy(&sockets_globals->wsa_info);
|
|
#endif
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ PHP_MINIT_FUNCTION */
|
|
static PHP_MINIT_FUNCTION(sockets)
|
|
{
|
|
#if defined(COMPILE_DL_SOCKETS) && defined(ZTS)
|
|
ZEND_TSRMLS_CACHE_UPDATE();
|
|
#endif
|
|
|
|
socket_ce = register_class_Socket();
|
|
socket_ce->create_object = socket_create_object;
|
|
socket_ce->default_object_handlers = &socket_object_handlers;
|
|
|
|
memcpy(&socket_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
|
|
socket_object_handlers.offset = XtOffsetOf(php_socket, std);
|
|
socket_object_handlers.free_obj = socket_free_obj;
|
|
socket_object_handlers.get_constructor = socket_get_constructor;
|
|
socket_object_handlers.clone_obj = NULL;
|
|
socket_object_handlers.get_gc = socket_get_gc;
|
|
socket_object_handlers.compare = zend_objects_not_comparable;
|
|
|
|
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;
|
|
|
|
memcpy(&address_info_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
|
|
address_info_object_handlers.offset = XtOffsetOf(php_addrinfo, std);
|
|
address_info_object_handlers.free_obj = address_info_free_obj;
|
|
address_info_object_handlers.get_constructor = address_info_get_constructor;
|
|
address_info_object_handlers.clone_obj = NULL;
|
|
address_info_object_handlers.compare = zend_objects_not_comparable;
|
|
|
|
register_sockets_symbols(module_number);
|
|
|
|
php_socket_sendrecvmsg_init(INIT_FUNC_ARGS_PASSTHRU);
|
|
|
|
return SUCCESS;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ PHP_MSHUTDOWN_FUNCTION */
|
|
static PHP_MSHUTDOWN_FUNCTION(sockets)
|
|
{
|
|
php_socket_sendrecvmsg_shutdown(SHUTDOWN_FUNC_ARGS_PASSTHRU);
|
|
|
|
return SUCCESS;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ PHP_MINFO_FUNCTION */
|
|
static PHP_MINFO_FUNCTION(sockets)
|
|
{
|
|
php_info_print_table_start();
|
|
php_info_print_table_row(2, "Sockets Support", "enabled");
|
|
php_info_print_table_end();
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ PHP_RSHUTDOWN_FUNCTION */
|
|
static PHP_RSHUTDOWN_FUNCTION(sockets)
|
|
{
|
|
if (SOCKETS_G(strerror_buf)) {
|
|
efree(SOCKETS_G(strerror_buf));
|
|
SOCKETS_G(strerror_buf) = NULL;
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
/* }}} */
|
|
|
|
static int php_sock_array_to_fd_set(uint32_t arg_num, zval *sock_array, fd_set *fds, PHP_SOCKET *max_fd) /* {{{ */
|
|
{
|
|
zval *element;
|
|
php_socket *php_sock;
|
|
int num = 0;
|
|
|
|
if (Z_TYPE_P(sock_array) != IS_ARRAY) return 0;
|
|
|
|
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(sock_array), element) {
|
|
ZVAL_DEREF(element);
|
|
|
|
if (Z_TYPE_P(element) != IS_OBJECT || Z_OBJCE_P(element) != socket_ce) {
|
|
zend_argument_type_error(arg_num, "must only have elements of type Socket, %s given", zend_zval_value_name(element));
|
|
return -1;
|
|
}
|
|
|
|
php_sock = Z_SOCKET_P(element);
|
|
if (IS_INVALID_SOCKET(php_sock)) {
|
|
zend_argument_type_error(arg_num, "contains a closed socket");
|
|
return -1;
|
|
}
|
|
|
|
PHP_SAFE_FD_SET(php_sock->bsd_socket, fds);
|
|
if (php_sock->bsd_socket > *max_fd) {
|
|
*max_fd = php_sock->bsd_socket;
|
|
}
|
|
num++;
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
return num ? 1 : 0;
|
|
}
|
|
/* }}} */
|
|
|
|
static void php_sock_array_from_fd_set(zval *sock_array, fd_set *fds) /* {{{ */
|
|
{
|
|
zval *element;
|
|
zval *dest_element;
|
|
php_socket *php_sock;
|
|
zval new_hash;
|
|
zend_ulong num_key;
|
|
zend_string *key;
|
|
|
|
ZEND_ASSERT(Z_TYPE_P(sock_array) == IS_ARRAY);
|
|
|
|
array_init(&new_hash);
|
|
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(sock_array), num_key, key, element) {
|
|
ZVAL_DEREF(element);
|
|
|
|
php_sock = Z_SOCKET_P(element);
|
|
ZEND_ASSERT(php_sock); /* element is supposed to be Socket object */
|
|
ZEND_ASSERT(!IS_INVALID_SOCKET(php_sock));
|
|
|
|
if (PHP_SAFE_FD_ISSET(php_sock->bsd_socket, fds)) {
|
|
/* Add fd to new array */
|
|
if (key) {
|
|
dest_element = zend_hash_add(Z_ARRVAL(new_hash), key, element);
|
|
} else {
|
|
dest_element = zend_hash_index_update(Z_ARRVAL(new_hash), num_key, element);
|
|
}
|
|
if (dest_element) {
|
|
Z_ADDREF_P(dest_element);
|
|
}
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
/* Destroy old array, add new one */
|
|
zval_ptr_dtor(sock_array);
|
|
|
|
ZVAL_COPY_VALUE(sock_array, &new_hash);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Runs the select() system call on the sets mentioned with a timeout specified by tv_sec and tv_usec */
|
|
PHP_FUNCTION(socket_select)
|
|
{
|
|
zval *r_array, *w_array, *e_array;
|
|
struct timeval tv;
|
|
struct timeval *tv_p = NULL;
|
|
fd_set rfds, wfds, efds;
|
|
PHP_SOCKET max_fd = 0;
|
|
int retval, sets = 0;
|
|
zend_long sec, usec = 0;
|
|
bool sec_is_null = 0;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(4, 5)
|
|
Z_PARAM_ARRAY_EX2(r_array, 1, 1, 0)
|
|
Z_PARAM_ARRAY_EX2(w_array, 1, 1, 0)
|
|
Z_PARAM_ARRAY_EX2(e_array, 1, 1, 0)
|
|
Z_PARAM_LONG_OR_NULL(sec, sec_is_null)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_LONG(usec)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_ZERO(&wfds);
|
|
FD_ZERO(&efds);
|
|
|
|
if (r_array != NULL) {
|
|
sets += retval = php_sock_array_to_fd_set(1, r_array, &rfds, &max_fd);
|
|
if (retval == -1) {
|
|
RETURN_THROWS();
|
|
}
|
|
}
|
|
if (w_array != NULL) {
|
|
sets += retval = php_sock_array_to_fd_set(2, w_array, &wfds, &max_fd);
|
|
if (retval == -1) {
|
|
RETURN_THROWS();
|
|
}
|
|
}
|
|
if (e_array != NULL) {
|
|
sets += retval = php_sock_array_to_fd_set(3, e_array, &efds, &max_fd);
|
|
if (retval == -1) {
|
|
RETURN_THROWS();
|
|
}
|
|
}
|
|
|
|
if (!sets) {
|
|
zend_value_error("socket_select(): At least one array argument must be passed");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
if (!PHP_SAFE_MAX_FD(max_fd, 0)) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
/* If seconds is not set to null, build the timeval, else we wait indefinitely */
|
|
if (!sec_is_null) {
|
|
/* Solaris + BSD do not like microsecond values which are >= 1 sec */
|
|
if (usec > 999999) {
|
|
tv.tv_sec = sec + (usec / 1000000);
|
|
tv.tv_usec = usec % 1000000;
|
|
} else {
|
|
tv.tv_sec = sec;
|
|
tv.tv_usec = usec;
|
|
}
|
|
|
|
tv_p = &tv;
|
|
}
|
|
|
|
retval = select(max_fd+1, &rfds, &wfds, &efds, tv_p);
|
|
|
|
if (retval == -1) {
|
|
SOCKETS_G(last_error) = errno;
|
|
php_error_docref(NULL, E_WARNING, "Unable to select [%d]: %s", errno, sockets_strerror(errno));
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
if (r_array != NULL) php_sock_array_from_fd_set(r_array, &rfds);
|
|
if (w_array != NULL) php_sock_array_from_fd_set(w_array, &wfds);
|
|
if (e_array != NULL) php_sock_array_from_fd_set(e_array, &efds);
|
|
|
|
RETURN_LONG(retval);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Opens a socket on port to accept connections */
|
|
PHP_FUNCTION(socket_create_listen)
|
|
{
|
|
php_socket *php_sock;
|
|
zend_long port, backlog = SOMAXCONN;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 2)
|
|
Z_PARAM_LONG(port)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_LONG(backlog)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
if (port < 0 || port > USHRT_MAX) {
|
|
zend_argument_value_error(1, "must be between 0 and %u", USHRT_MAX);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
object_init_ex(return_value, socket_ce);
|
|
php_sock = Z_SOCKET_P(return_value);
|
|
|
|
if (!php_open_listen_sock(php_sock, (unsigned short)port, backlog)) {
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
php_sock->error = 0;
|
|
php_sock->blocking = 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Accepts a connection on the listening socket fd */
|
|
PHP_FUNCTION(socket_accept)
|
|
{
|
|
zval *arg1;
|
|
php_socket *php_sock, *new_sock;
|
|
php_sockaddr_storage sa;
|
|
socklen_t php_sa_len = sizeof(sa);
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
object_init_ex(return_value, socket_ce);
|
|
new_sock = Z_SOCKET_P(return_value);
|
|
|
|
if (!php_accept_connect(php_sock, new_sock, (struct sockaddr*)&sa, &php_sa_len)) {
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_FALSE;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Sets nonblocking mode on a socket resource */
|
|
PHP_FUNCTION(socket_set_nonblock)
|
|
{
|
|
zval *arg1;
|
|
php_socket *php_sock;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
if (!Z_ISUNDEF(php_sock->zstream)) {
|
|
php_stream *stream;
|
|
/* omit notice if resource doesn't exist anymore */
|
|
stream = zend_fetch_resource2_ex(&php_sock->zstream, NULL, php_file_le_stream(), php_file_le_pstream());
|
|
if (stream != NULL) {
|
|
if (php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, 0,
|
|
NULL) != -1) {
|
|
php_sock->blocking = 0;
|
|
RETURN_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (php_set_sock_blocking(php_sock->bsd_socket, 0) == SUCCESS) {
|
|
php_sock->blocking = 0;
|
|
RETURN_TRUE;
|
|
} else {
|
|
PHP_SOCKET_ERROR(php_sock, "unable to set nonblocking mode", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Sets blocking mode on a socket resource */
|
|
PHP_FUNCTION(socket_set_block)
|
|
{
|
|
zval *arg1;
|
|
php_socket *php_sock;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
/* if socket was created from a stream, give the stream a chance to take
|
|
* care of the operation itself, thereby allowing it to update its internal
|
|
* state */
|
|
if (!Z_ISUNDEF(php_sock->zstream)) {
|
|
php_stream *stream;
|
|
stream = zend_fetch_resource2_ex(&php_sock->zstream, NULL, php_file_le_stream(), php_file_le_pstream());
|
|
if (stream != NULL) {
|
|
if (php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, 1,
|
|
NULL) != -1) {
|
|
php_sock->blocking = 1;
|
|
RETURN_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (php_set_sock_blocking(php_sock->bsd_socket, 1) == SUCCESS) {
|
|
php_sock->blocking = 1;
|
|
RETURN_TRUE;
|
|
} else {
|
|
PHP_SOCKET_ERROR(php_sock, "unable to set blocking mode", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Sets the maximum number of connections allowed to be waited for on the socket specified by fd */
|
|
PHP_FUNCTION(socket_listen)
|
|
{
|
|
zval *arg1;
|
|
php_socket *php_sock;
|
|
zend_long backlog = 0;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 2)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_LONG(backlog)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
if (listen(php_sock->bsd_socket, backlog) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "unable to listen on socket", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
RETURN_TRUE;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Closes a file descriptor */
|
|
PHP_FUNCTION(socket_close)
|
|
{
|
|
zval *arg1;
|
|
php_socket *php_socket;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_socket = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_socket);
|
|
|
|
if (!Z_ISUNDEF(php_socket->zstream)) {
|
|
php_stream *stream = NULL;
|
|
php_stream_from_zval_no_verify(stream, &php_socket->zstream);
|
|
if (stream != NULL) {
|
|
/* close & destroy stream, incl. removing it from the rsrc list;
|
|
* resource stored in php_sock->zstream will become invalid */
|
|
php_stream_free(stream,
|
|
PHP_STREAM_FREE_KEEP_RSRC | PHP_STREAM_FREE_CLOSE |
|
|
(stream->is_persistent?PHP_STREAM_FREE_CLOSE_PERSISTENT:0));
|
|
}
|
|
} else {
|
|
if (!IS_INVALID_SOCKET(php_socket)) {
|
|
close(php_socket->bsd_socket);
|
|
}
|
|
}
|
|
|
|
ZVAL_UNDEF(&php_socket->zstream);
|
|
php_socket->bsd_socket = -1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Writes the buffer to the socket resource, length is optional */
|
|
PHP_FUNCTION(socket_write)
|
|
{
|
|
zval *arg1;
|
|
php_socket *php_sock;
|
|
int retval;
|
|
size_t str_len;
|
|
zend_long length = 0;
|
|
bool length_is_null = 1;
|
|
char *str;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(2, 3)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_STRING(str, str_len)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_LONG_OR_NULL(length, length_is_null)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
if (length < 0) {
|
|
zend_argument_value_error(3, "must be greater than or equal to 0");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
if (length_is_null) {
|
|
length = str_len;
|
|
}
|
|
|
|
#ifndef PHP_WIN32
|
|
retval = write(php_sock->bsd_socket, str, MIN(length, str_len));
|
|
#else
|
|
retval = send(php_sock->bsd_socket, str, min(length, str_len), 0);
|
|
#endif
|
|
|
|
if (retval < 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "unable to write to socket", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
RETURN_LONG(retval);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Reads a maximum of length bytes from socket */
|
|
PHP_FUNCTION(socket_read)
|
|
{
|
|
zval *arg1;
|
|
php_socket *php_sock;
|
|
zend_string *tmpbuf;
|
|
int retval;
|
|
zend_long length, type = PHP_BINARY_READ;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(2, 3)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_LONG(length)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_LONG(type)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
/* overflow check */
|
|
if (length <= 0 || length == ZEND_LONG_MAX) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
tmpbuf = zend_string_alloc(length, 0);
|
|
|
|
if (type == PHP_NORMAL_READ) {
|
|
retval = php_read(php_sock, ZSTR_VAL(tmpbuf), length, 0);
|
|
} else {
|
|
retval = recv(php_sock->bsd_socket, ZSTR_VAL(tmpbuf), length, 0);
|
|
}
|
|
|
|
if (retval == -1) {
|
|
/* if the socket is in non-blocking mode and there's no data to read,
|
|
don't output any error, as this is a normal situation, and not an error */
|
|
if (PHP_IS_TRANSIENT_ERROR(errno)) {
|
|
php_sock->error = errno;
|
|
SOCKETS_G(last_error) = errno;
|
|
} else {
|
|
PHP_SOCKET_ERROR(php_sock, "unable to read from socket", errno);
|
|
}
|
|
|
|
zend_string_efree(tmpbuf);
|
|
RETURN_FALSE;
|
|
} else if (!retval) {
|
|
zend_string_efree(tmpbuf);
|
|
RETURN_EMPTY_STRING();
|
|
}
|
|
|
|
tmpbuf = zend_string_truncate(tmpbuf, retval, 0);
|
|
ZSTR_LEN(tmpbuf) = retval;
|
|
ZSTR_VAL(tmpbuf)[ZSTR_LEN(tmpbuf)] = '\0' ;
|
|
|
|
RETURN_NEW_STR(tmpbuf);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ 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, *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;
|
|
const char *addr_string;
|
|
socklen_t salen = sizeof(php_sockaddr_storage);
|
|
|
|
ZEND_PARSE_PARAMETERS_START(2, 3)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_ZVAL(addr)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_ZVAL(objint)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
sa = (struct sockaddr *) &sa_storage;
|
|
|
|
if (getsockname(php_sock->bsd_socket, sa, &salen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "unable to retrieve socket name", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
switch (sa->sa_family) {
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
sin6 = (struct sockaddr_in6 *) sa;
|
|
inet_ntop(AF_INET6, &sin6->sin6_addr, addrbuf, sizeof(addrbuf));
|
|
ZEND_TRY_ASSIGN_REF_STRING(addr, addrbuf);
|
|
|
|
if (objint != NULL) {
|
|
ZEND_TRY_ASSIGN_REF_LONG(objint, htons(sin6->sin6_port));
|
|
}
|
|
RETURN_TRUE;
|
|
break;
|
|
#endif
|
|
case AF_INET:
|
|
sin = (struct sockaddr_in *) sa;
|
|
addr_string = inet_ntop(AF_INET, &sin->sin_addr, addrbuf, sizeof(addrbuf));
|
|
ZEND_TRY_ASSIGN_REF_STRING(addr, addr_string);
|
|
|
|
if (objint != NULL) {
|
|
ZEND_TRY_ASSIGN_REF_LONG(objint, htons(sin->sin_port));
|
|
}
|
|
RETURN_TRUE;
|
|
break;
|
|
|
|
case AF_UNIX:
|
|
s_un = (struct sockaddr_un *) sa;
|
|
|
|
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_PACKET, AF_INET, or AF_INET6");
|
|
RETURN_THROWS();
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ 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_getpeername)
|
|
{
|
|
zval *arg1, *arg2, *arg3 = 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
|
|
char addrbuf[INET6_ADDRSTRLEN];
|
|
struct sockaddr_un *s_un;
|
|
const char *addr_string;
|
|
socklen_t salen = sizeof(php_sockaddr_storage);
|
|
|
|
ZEND_PARSE_PARAMETERS_START(2, 3)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_ZVAL(arg2)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_ZVAL(arg3)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
sa = (struct sockaddr *) &sa_storage;
|
|
|
|
if (getpeername(php_sock->bsd_socket, sa, &salen) < 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "unable to retrieve peer name", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
switch (sa->sa_family) {
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
sin6 = (struct sockaddr_in6 *) sa;
|
|
inet_ntop(AF_INET6, &sin6->sin6_addr, addrbuf, sizeof(addrbuf));
|
|
|
|
ZEND_TRY_ASSIGN_REF_STRING(arg2, addrbuf);
|
|
|
|
if (arg3 != NULL) {
|
|
ZEND_TRY_ASSIGN_REF_LONG(arg3, htons(sin6->sin6_port));
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
break;
|
|
#endif
|
|
case AF_INET:
|
|
sin = (struct sockaddr_in *) sa;
|
|
addr_string = inet_ntop(AF_INET, &sin->sin_addr, addrbuf, sizeof(addrbuf));
|
|
ZEND_TRY_ASSIGN_REF_STRING(arg2, addr_string);
|
|
|
|
if (arg3 != NULL) {
|
|
ZEND_TRY_ASSIGN_REF_LONG(arg3, htons(sin->sin_port));
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
break;
|
|
|
|
case AF_UNIX:
|
|
s_un = (struct sockaddr_un *) sa;
|
|
|
|
ZEND_TRY_ASSIGN_REF_STRING(arg2, s_un->sun_path);
|
|
RETURN_TRUE;
|
|
break;
|
|
|
|
default:
|
|
zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6");
|
|
RETURN_THROWS();
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Creates an endpoint for communication in the domain specified by domain, of type specified by type */
|
|
PHP_FUNCTION(socket_create)
|
|
{
|
|
zend_long domain, type, checktype, protocol;
|
|
php_socket *php_sock;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(3, 3)
|
|
Z_PARAM_LONG(domain)
|
|
Z_PARAM_LONG(type)
|
|
Z_PARAM_LONG(protocol)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
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_PACKET, AF_INET6, or AF_INET");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
checktype = type;
|
|
#ifdef SOCK_NONBLOCK
|
|
checktype &= ~(SOCK_CLOEXEC | SOCK_NONBLOCK);
|
|
#endif
|
|
|
|
if (checktype > 10) {
|
|
zend_argument_value_error(2, "must be one of SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET,"
|
|
" SOCK_RAW, or SOCK_RDM"
|
|
#ifdef SOCK_NONBLOCK
|
|
" optionally OR'ed with SOCK_CLOEXEC, SOCK_NONBLOCK"
|
|
#endif
|
|
);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
PHP_ETH_PROTO_CHECK(protocol, domain);
|
|
|
|
object_init_ex(return_value, socket_ce);
|
|
php_sock = Z_SOCKET_P(return_value);
|
|
|
|
php_sock->bsd_socket = socket(domain, type, protocol);
|
|
php_sock->type = domain;
|
|
|
|
if (IS_INVALID_SOCKET(php_sock)) {
|
|
SOCKETS_G(last_error) = errno;
|
|
php_error_docref(NULL, E_WARNING, "Unable to create socket [%d]: %s", errno, sockets_strerror(errno));
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
php_sock->error = 0;
|
|
php_sock->blocking = 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Opens a connection to addr:port on the socket specified by socket */
|
|
PHP_FUNCTION(socket_connect)
|
|
{
|
|
zval *resource_socket;
|
|
php_socket *php_sock;
|
|
zend_string *addr;
|
|
int retval;
|
|
zend_long port;
|
|
bool port_is_null = 1;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(2, 3)
|
|
Z_PARAM_OBJECT_OF_CLASS(resource_socket, socket_ce)
|
|
Z_PARAM_STR(addr)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_LONG_OR_NULL(port, port_is_null)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(resource_socket);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
switch(php_sock->type) {
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6: {
|
|
struct sockaddr_in6 sin6 = {0};
|
|
|
|
if (port_is_null) {
|
|
zend_argument_value_error(3, "cannot be null when the socket type is AF_INET6");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
memset(&sin6, 0, sizeof(struct sockaddr_in6));
|
|
|
|
sin6.sin6_family = AF_INET6;
|
|
sin6.sin6_port = htons((unsigned short int)port);
|
|
|
|
if (! php_set_inet6_addr(&sin6, addr, php_sock)) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
retval = connect(php_sock->bsd_socket, (struct sockaddr *)&sin6, sizeof(struct sockaddr_in6));
|
|
break;
|
|
}
|
|
#endif
|
|
case AF_INET: {
|
|
struct sockaddr_in sin = {0};
|
|
|
|
if (port_is_null) {
|
|
zend_argument_value_error(3, "cannot be null when the socket type is AF_INET");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_port = htons((unsigned short int)port);
|
|
|
|
if (! php_set_inet_addr(&sin, addr, php_sock)) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
retval = connect(php_sock->bsd_socket, (struct sockaddr *)&sin, sizeof(struct sockaddr_in));
|
|
break;
|
|
}
|
|
|
|
case AF_UNIX: {
|
|
struct sockaddr_un s_un = {0};
|
|
|
|
if (ZSTR_LEN(addr) >= sizeof(s_un.sun_path)) {
|
|
zend_argument_value_error(2, "must be less than %d", sizeof(s_un.sun_path));
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
s_un.sun_family = AF_UNIX;
|
|
memcpy(&s_un.sun_path, ZSTR_VAL(addr), ZSTR_LEN(addr));
|
|
retval = connect(php_sock->bsd_socket, (struct sockaddr *) &s_un,
|
|
(socklen_t)(XtOffsetOf(struct sockaddr_un, sun_path) + ZSTR_LEN(addr)));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
if (retval != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "unable to connect", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Returns a string describing an error */
|
|
PHP_FUNCTION(socket_strerror)
|
|
{
|
|
zend_long arg1;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_LONG(arg1)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
if (ZEND_LONG_EXCEEDS_INT(arg1)) {
|
|
zend_argument_value_error(1, "must be between %d and %d", INT_MIN, INT_MAX);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
RETURN_STRING(sockets_strerror(arg1));
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Binds an open socket to a listening port, port is only specified in AF_INET family. */
|
|
PHP_FUNCTION(socket_bind)
|
|
{
|
|
zval *arg1;
|
|
php_sockaddr_storage sa_storage = {0};
|
|
struct sockaddr *sock_type = (struct sockaddr*) &sa_storage;
|
|
php_socket *php_sock;
|
|
zend_string *addr;
|
|
zend_long objint = 0;
|
|
zend_long retval = 0;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(2, 3)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_STR(addr)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_LONG(objint)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
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();
|
|
}
|
|
|
|
switch(php_sock->type) {
|
|
case AF_UNIX:
|
|
{
|
|
struct sockaddr_un *sa = (struct sockaddr_un *) sock_type;
|
|
|
|
sa->sun_family = AF_UNIX;
|
|
|
|
if (ZSTR_LEN(addr) >= sizeof(sa->sun_path)) {
|
|
zend_argument_value_error(2, "must be less than %d", sizeof(sa->sun_path));
|
|
RETURN_THROWS();
|
|
}
|
|
memcpy(&sa->sun_path, ZSTR_VAL(addr), ZSTR_LEN(addr));
|
|
|
|
retval = bind(php_sock->bsd_socket, (struct sockaddr *) sa,
|
|
offsetof(struct sockaddr_un, sun_path) + ZSTR_LEN(addr));
|
|
break;
|
|
}
|
|
|
|
case AF_INET:
|
|
{
|
|
struct sockaddr_in *sa = (struct sockaddr_in *) sock_type;
|
|
|
|
sa->sin_family = AF_INET;
|
|
sa->sin_port = htons((unsigned short) objint);
|
|
|
|
if (! php_set_inet_addr(sa, addr, php_sock)) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
retval = bind(php_sock->bsd_socket, (struct sockaddr *)sa, sizeof(struct sockaddr_in));
|
|
break;
|
|
}
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
{
|
|
struct sockaddr_in6 *sa = (struct sockaddr_in6 *) sock_type;
|
|
|
|
sa->sin6_family = AF_INET6;
|
|
sa->sin6_port = htons((unsigned short) objint);
|
|
|
|
if (! php_set_inet6_addr(sa, addr, php_sock)) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
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(ZSTR_VAL(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_PACKET, AF_INET, or AF_INET6");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
if (retval != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to bind address", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Receives data from a connected socket */
|
|
PHP_FUNCTION(socket_recv)
|
|
{
|
|
zval *php_sock_res, *buf;
|
|
zend_string *recv_buf;
|
|
php_socket *php_sock;
|
|
int retval;
|
|
zend_long len, flags;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(4, 4)
|
|
Z_PARAM_OBJECT_OF_CLASS(php_sock_res, socket_ce)
|
|
Z_PARAM_ZVAL(buf)
|
|
Z_PARAM_LONG(len)
|
|
Z_PARAM_LONG(flags)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(php_sock_res);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
/* overflow check */
|
|
if (len <= 0 || len == ZEND_LONG_MAX) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
recv_buf = zend_string_alloc(len, 0);
|
|
|
|
if ((retval = recv(php_sock->bsd_socket, ZSTR_VAL(recv_buf), len, flags)) < 1) {
|
|
zend_string_efree(recv_buf);
|
|
ZEND_TRY_ASSIGN_REF_NULL(buf);
|
|
} else {
|
|
ZSTR_LEN(recv_buf) = retval;
|
|
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0';
|
|
ZEND_TRY_ASSIGN_REF_NEW_STR(buf, recv_buf);
|
|
}
|
|
|
|
if (retval == -1) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to read from socket", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
RETURN_LONG(retval);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Sends data to a connected socket */
|
|
PHP_FUNCTION(socket_send)
|
|
{
|
|
zval *arg1;
|
|
php_socket *php_sock;
|
|
size_t buf_len, retval;
|
|
zend_long len, flags;
|
|
char *buf;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(4, 4)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_STRING(buf, buf_len)
|
|
Z_PARAM_LONG(len)
|
|
Z_PARAM_LONG(flags)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
if (len < 0) {
|
|
zend_argument_value_error(3, "must be greater than or equal to 0");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
retval = send(php_sock->bsd_socket, buf, (buf_len < (size_t)len ? buf_len : (size_t)len), flags);
|
|
|
|
if (retval == (size_t)-1) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to write to socket", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
RETURN_LONG(retval);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Receives data from a socket, connected or not */
|
|
PHP_FUNCTION(socket_recvfrom)
|
|
{
|
|
zval *arg1, *arg2, *arg5, *arg6 = NULL;
|
|
php_socket *php_sock;
|
|
struct sockaddr_un s_un;
|
|
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;
|
|
int retval;
|
|
zend_long arg3, arg4;
|
|
const char *address;
|
|
zend_string *recv_buf;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(5, 6)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_ZVAL(arg2)
|
|
Z_PARAM_LONG(arg3)
|
|
Z_PARAM_LONG(arg4)
|
|
Z_PARAM_ZVAL(arg5)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_ZVAL(arg6)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
/* overflow check */
|
|
/* Shouldthrow ? */
|
|
|
|
if (arg3 <= 0 || arg3 > ZEND_LONG_MAX - 1) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
recv_buf = zend_string_alloc(arg3 + 1, 0);
|
|
|
|
switch (php_sock->type) {
|
|
case AF_UNIX:
|
|
slen = sizeof(s_un);
|
|
memset(&s_un, 0, slen);
|
|
s_un.sun_family = AF_UNIX;
|
|
|
|
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&s_un, (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';
|
|
|
|
ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf);
|
|
ZEND_TRY_ASSIGN_REF_STRING(arg5, s_un.sun_path);
|
|
break;
|
|
|
|
case AF_INET:
|
|
slen = sizeof(sin);
|
|
memset(&sin, 0, slen);
|
|
sin.sin_family = AF_INET;
|
|
|
|
if (arg6 == NULL) {
|
|
zend_string_efree(recv_buf);
|
|
WRONG_PARAM_COUNT;
|
|
}
|
|
|
|
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sin, (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';
|
|
|
|
address = inet_ntop(AF_INET, &sin.sin_addr, addrbuf, sizeof(addrbuf));
|
|
|
|
ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf);
|
|
ZEND_TRY_ASSIGN_REF_STRING(arg5, address ? address : "0.0.0.0");
|
|
ZEND_TRY_ASSIGN_REF_LONG(arg6, ntohs(sin.sin_port));
|
|
break;
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
slen = sizeof(sin6);
|
|
memset(&sin6, 0, slen);
|
|
sin6.sin6_family = AF_INET6;
|
|
|
|
if (arg6 == NULL) {
|
|
zend_string_efree(recv_buf);
|
|
WRONG_PARAM_COUNT;
|
|
}
|
|
|
|
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sin6, (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';
|
|
|
|
memset(addrbuf, 0, INET6_ADDRSTRLEN);
|
|
inet_ntop(AF_INET6, &sin6.sin6_addr, addrbuf, sizeof(addrbuf));
|
|
|
|
ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf);
|
|
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");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
RETURN_LONG(retval);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Sends a message to a socket, whether it is connected or not */
|
|
PHP_FUNCTION(socket_sendto)
|
|
{
|
|
zval *arg1;
|
|
php_socket *php_sock;
|
|
struct sockaddr_un s_un;
|
|
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;
|
|
zend_long len, flags, port = 0;
|
|
bool port_is_null = 1;
|
|
char *buf;
|
|
zend_string *addr;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(5, 6)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_STRING(buf, buf_len)
|
|
Z_PARAM_LONG(len)
|
|
Z_PARAM_LONG(flags)
|
|
Z_PARAM_STR(addr)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_LONG_OR_NULL(port, port_is_null)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
if (port < 0 || port > USHRT_MAX) {
|
|
zend_argument_value_error(6, "must be between 0 and %u", USHRT_MAX);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
|
|
if (len < 0) {
|
|
zend_argument_value_error(3, "must be greater than or equal to 0");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
switch (php_sock->type) {
|
|
case AF_UNIX:
|
|
memset(&s_un, 0, sizeof(s_un));
|
|
s_un.sun_family = AF_UNIX;
|
|
snprintf(s_un.sun_path, sizeof(s_un.sun_path), "%s", ZSTR_VAL(addr));
|
|
|
|
retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &s_un, SUN_LEN(&s_un));
|
|
break;
|
|
|
|
case AF_INET:
|
|
if (port_is_null) {
|
|
zend_argument_value_error(6, "cannot be null when the socket type is AF_INET");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
memset(&sin, 0, sizeof(sin));
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_port = htons((unsigned short) port);
|
|
|
|
if (! php_set_inet_addr(&sin, addr, php_sock)) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &sin, sizeof(sin));
|
|
break;
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
if (port_is_null) {
|
|
zend_argument_value_error(6, "cannot be null when the socket type is AF_INET6");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
memset(&sin6, 0, sizeof(sin6));
|
|
sin6.sin6_family = AF_INET6;
|
|
sin6.sin6_port = htons((unsigned short) port);
|
|
|
|
if (! php_set_inet6_addr(&sin6, addr, php_sock)) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
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");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
if (retval == -1) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to write to socket", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
RETURN_LONG(retval);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Gets socket options for the socket */
|
|
PHP_FUNCTION(socket_get_option)
|
|
{
|
|
zval *arg1;
|
|
struct linger linger_val;
|
|
struct timeval tv;
|
|
#ifdef PHP_WIN32
|
|
DWORD timeout = 0;
|
|
#else
|
|
struct timeval timeout;
|
|
#endif
|
|
socklen_t optlen;
|
|
php_socket *php_sock;
|
|
int other_val;
|
|
zend_long level, optname;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(3, 3)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_LONG(level)
|
|
Z_PARAM_LONG(optname)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
if (level == IPPROTO_IP) {
|
|
switch (optname) {
|
|
case IP_MULTICAST_IF: {
|
|
struct in_addr if_addr;
|
|
unsigned int if_index;
|
|
optlen = sizeof(if_addr);
|
|
if (getsockopt(php_sock->bsd_socket, level, optname, (char*)&if_addr, &optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to retrieve socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
if (php_add4_to_if_index(&if_addr, php_sock, &if_index) == SUCCESS) {
|
|
RETURN_LONG((zend_long) if_index);
|
|
} else {
|
|
RETURN_FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#ifdef HAVE_IPV6
|
|
else if (level == IPPROTO_IPV6) {
|
|
int ret = php_do_getsockopt_ipv6_rfc3542(php_sock, level, optname, return_value);
|
|
if (ret == SUCCESS) {
|
|
return;
|
|
} else if (ret == FAILURE) {
|
|
RETURN_FALSE;
|
|
} /* else continue */
|
|
}
|
|
#endif
|
|
|
|
if (level == IPPROTO_TCP) {
|
|
switch (optname) {
|
|
#ifdef TCP_CONGESTION
|
|
case TCP_CONGESTION: {
|
|
char name[16];
|
|
optlen = sizeof(name);
|
|
if (getsockopt(php_sock->bsd_socket, level, optname, name, &optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to retrieve socket option", errno);
|
|
RETURN_FALSE;
|
|
} else {
|
|
array_init(return_value);
|
|
|
|
add_assoc_string(return_value, "name", name);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef TCP_FUNCTION_BLK
|
|
case TCP_FUNCTION_BLK:
|
|
#ifdef TCP_FUNCTION_ALIAS
|
|
case TCP_FUNCTION_ALIAS:
|
|
#endif
|
|
{
|
|
|
|
struct tcp_function_set tsf = {0};
|
|
optlen = sizeof(tsf);
|
|
|
|
if (getsockopt(php_sock->bsd_socket, level, optname, &tsf, &optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to retrieve socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
array_init_size(return_value, 2);
|
|
|
|
add_assoc_string(return_value, "function_set_name", tsf.function_set_name);
|
|
add_assoc_long(return_value, "pcbcnt", tsf.pcbcnt);
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (level == SOL_SOCKET) {
|
|
switch (optname) {
|
|
#ifdef SO_LINGER_SEC
|
|
case SO_LINGER_SEC:
|
|
#endif
|
|
case SO_LINGER:
|
|
optlen = sizeof(linger_val);
|
|
|
|
if (getsockopt(php_sock->bsd_socket, level, optname, (char*)&linger_val, &optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to retrieve socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
array_init_size(return_value, 2);
|
|
add_assoc_long(return_value, "l_onoff", linger_val.l_onoff);
|
|
add_assoc_long(return_value, "l_linger", linger_val.l_linger);
|
|
return;
|
|
|
|
case SO_RCVTIMEO:
|
|
case SO_SNDTIMEO:
|
|
optlen = sizeof(timeout);
|
|
|
|
if (getsockopt(php_sock->bsd_socket, level, optname, (char*)&timeout, &optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to retrieve socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
#ifndef PHP_WIN32
|
|
tv.tv_sec = timeout.tv_sec;
|
|
tv.tv_usec = timeout.tv_usec;
|
|
#else
|
|
tv.tv_sec = timeout ? (long)(timeout / 1000) : 0;
|
|
tv.tv_usec = timeout ? (long)((timeout % 1000) * 1000) : 0;
|
|
#endif
|
|
|
|
array_init_size(return_value, 2);
|
|
|
|
add_assoc_long(return_value, "sec", tv.tv_sec);
|
|
add_assoc_long(return_value, "usec", tv.tv_usec);
|
|
return;
|
|
#ifdef SO_MEMINFO
|
|
case SO_MEMINFO: {
|
|
uint32_t minfo[SK_MEMINFO_VARS];
|
|
optlen = sizeof(minfo);
|
|
|
|
if (getsockopt(php_sock->bsd_socket, level, optname, (char*)minfo, &optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to retrieve socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
if (UNEXPECTED(optlen != sizeof(minfo))) {
|
|
// unlikely since the kernel fills up the whole array if getsockopt succeeded
|
|
// but just an extra precaution in case.
|
|
php_error_docref(NULL, E_WARNING, "Unable to retrieve all socket meminfo data");
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
array_init_size(return_value, 9);
|
|
|
|
add_assoc_long(return_value, "rmem_alloc", minfo[SK_MEMINFO_RMEM_ALLOC]);
|
|
add_assoc_long(return_value, "rcvbuf", minfo[SK_MEMINFO_RCVBUF]);
|
|
add_assoc_long(return_value, "wmem_alloc", minfo[SK_MEMINFO_WMEM_ALLOC]);
|
|
add_assoc_long(return_value, "sndbuf", minfo[SK_MEMINFO_SNDBUF]);
|
|
add_assoc_long(return_value, "fwd_alloc", minfo[SK_MEMINFO_FWD_ALLOC]);
|
|
add_assoc_long(return_value, "wmem_queued", minfo[SK_MEMINFO_WMEM_QUEUED]);
|
|
add_assoc_long(return_value, "optmem", minfo[SK_MEMINFO_OPTMEM]);
|
|
add_assoc_long(return_value, "backlog", minfo[SK_MEMINFO_BACKLOG]);
|
|
add_assoc_long(return_value, "drops", minfo[SK_MEMINFO_DROPS]);
|
|
return;
|
|
}
|
|
#endif
|
|
#ifdef SO_ACCEPTFILTER
|
|
case SO_ACCEPTFILTER: {
|
|
|
|
struct accept_filter_arg af = {0};
|
|
optlen = sizeof(af);
|
|
|
|
if (getsockopt(php_sock->bsd_socket, level, optname, (char*)&af, &optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to retrieve socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
array_init_size(return_value, 1);
|
|
|
|
add_assoc_string(return_value, "af_name", af.af_name);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
#ifdef SOL_FILTER
|
|
if (level == SOL_FILTER) {
|
|
switch (optname) {
|
|
|
|
case FIL_LIST: {
|
|
size_t i;
|
|
struct fil_info fi[32] = {{0}};
|
|
optlen = sizeof(fi);
|
|
|
|
if (getsockopt(php_sock->bsd_socket, level, optname, (char*)fi, &optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to retrieve socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
size_t arrlen = optlen / sizeof(struct fil_info);
|
|
|
|
array_init_size(return_value, arrlen);
|
|
zend_hash_real_init_packed(Z_ARRVAL_P(return_value));
|
|
|
|
for (i = 0; i < arrlen; i++) {
|
|
add_index_string(return_value, i, fi[i].fi_name);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
optlen = sizeof(other_val);
|
|
|
|
if (getsockopt(php_sock->bsd_socket, level, optname, (char*)&other_val, &optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to retrieve socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
if (optlen == 1) {
|
|
other_val = *((unsigned char *)&other_val);
|
|
}
|
|
|
|
RETURN_LONG(other_val);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Sets socket options for the socket */
|
|
PHP_FUNCTION(socket_set_option)
|
|
{
|
|
zval *arg1, *arg4;
|
|
struct linger lv;
|
|
php_socket *php_sock;
|
|
int ov, optlen, retval;
|
|
#ifdef PHP_WIN32
|
|
DWORD timeout;
|
|
#else
|
|
struct timeval tv;
|
|
#endif
|
|
zend_long level, optname;
|
|
void *opt_ptr;
|
|
HashTable *opt_ht;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(4, 4)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_LONG(level)
|
|
Z_PARAM_LONG(optname)
|
|
Z_PARAM_ZVAL(arg4)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
set_errno(0);
|
|
|
|
#define HANDLE_SUBCALL(res) \
|
|
do { \
|
|
if (res == 1) { goto default_case; } \
|
|
else if (res == SUCCESS) { RETURN_TRUE; } \
|
|
else { RETURN_FALSE; } \
|
|
} while (0)
|
|
|
|
|
|
if (level == IPPROTO_IP) {
|
|
int res = php_do_setsockopt_ip_mcast(php_sock, level, optname, arg4);
|
|
HANDLE_SUBCALL(res);
|
|
}
|
|
|
|
#ifdef HAVE_IPV6
|
|
else if (level == IPPROTO_IPV6) {
|
|
int res = php_do_setsockopt_ipv6_mcast(php_sock, level, optname, arg4);
|
|
if (res == 1) {
|
|
res = php_do_setsockopt_ipv6_rfc3542(php_sock, level, optname, arg4);
|
|
}
|
|
HANDLE_SUBCALL(res);
|
|
}
|
|
#endif
|
|
|
|
if (level == IPPROTO_TCP) {
|
|
switch (optname) {
|
|
#ifdef TCP_CONGESTION
|
|
case TCP_CONGESTION: {
|
|
if (Z_TYPE_P(arg4) != IS_STRING) {
|
|
zend_argument_type_error(4, "must be of type string when argument #3 ($option) is TCP_CONGESTION, %s given", zend_zval_value_name(arg4));
|
|
RETURN_THROWS();
|
|
}
|
|
opt_ptr = Z_STRVAL_P(arg4);
|
|
optlen = Z_STRLEN_P(arg4);
|
|
if (setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to set socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
#endif
|
|
|
|
#ifdef TCP_FUNCTION_BLK
|
|
case TCP_FUNCTION_BLK: {
|
|
if (Z_TYPE_P(arg4) != IS_STRING) {
|
|
zend_argument_type_error(4, "must be of type string when argument #3 ($option) is TCP_FUNCTION_BLK, %s given", zend_zval_value_name(arg4));
|
|
RETURN_THROWS();
|
|
}
|
|
if (zend_str_has_nul_byte(Z_STR_P(arg4))) {
|
|
zend_argument_value_error(4, "must not contain null bytes when argument #3 ($option) is TCP_FUNCTION_BLK");
|
|
RETURN_THROWS();
|
|
}
|
|
/* [...] the unique name of the TCP stack, and should be no longer than
|
|
* TCP_FUNCTION_NAME_LEN_MAX-1 characters in length.
|
|
* https://github.com/freebsd/freebsd-src/blob/da2c88dfcf4f425e6e0a58d6df3a7c8e88d8df92/share/man/man9/tcp_functions.9#L193C1-L194C50 */
|
|
if (Z_STRLEN_P(arg4) >= TCP_FUNCTION_NAME_LEN_MAX) {
|
|
zend_argument_value_error(4, "must be less than %zu bytes when argument #3 ($option) is TCP_FUNCTION_BLK", TCP_FUNCTION_NAME_LEN_MAX);
|
|
RETURN_THROWS();
|
|
}
|
|
struct tcp_function_set tfs = {0};
|
|
/* Copy the trailing nul byte */
|
|
memcpy(tfs.function_set_name, Z_STRVAL_P(arg4), Z_STRLEN_P(arg4)+1);
|
|
|
|
optlen = sizeof(tfs);
|
|
opt_ptr = &tfs;
|
|
if (setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to set socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
switch (optname) {
|
|
#ifdef SO_LINGER_SEC
|
|
case SO_LINGER_SEC:
|
|
#endif
|
|
case SO_LINGER: {
|
|
const char l_onoff_key[] = "l_onoff";
|
|
const char l_linger_key[] = "l_linger";
|
|
zval *l_onoff, *l_linger;
|
|
|
|
if (Z_TYPE_P(arg4) != IS_ARRAY) {
|
|
if (UNEXPECTED(Z_TYPE_P(arg4) != IS_OBJECT)) {
|
|
zend_argument_type_error(4, "must be of type array when argument #3 ($option) is SO_LINGER, %s given", zend_zval_value_name(arg4));
|
|
RETURN_THROWS();
|
|
} else {
|
|
opt_ht = Z_OBJPROP_P(arg4);
|
|
}
|
|
} else {
|
|
opt_ht = Z_ARRVAL_P(arg4);
|
|
}
|
|
|
|
if ((l_onoff = zend_hash_str_find(opt_ht, l_onoff_key, sizeof(l_onoff_key) - 1)) == NULL) {
|
|
zend_argument_value_error(4, "must have key \"%s\"", l_onoff_key);
|
|
RETURN_THROWS();
|
|
}
|
|
if ((l_linger = zend_hash_str_find(opt_ht, l_linger_key, sizeof(l_linger_key) - 1)) == NULL) {
|
|
zend_argument_value_error(4, "must have key \"%s\"", l_linger_key);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
zend_long val_lonoff = zval_get_long(l_onoff);
|
|
zend_long val_linger = zval_get_long(l_linger);
|
|
|
|
if (val_lonoff < 0 || val_lonoff > USHRT_MAX) {
|
|
zend_argument_value_error(4, "\"%s\" must be between 0 and %u", l_onoff_key, USHRT_MAX);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
if (val_linger < 0 || val_linger > USHRT_MAX) {
|
|
zend_argument_value_error(4, "\"%s\" must be between 0 and %d", l_linger, USHRT_MAX);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
lv.l_onoff = (unsigned short)val_lonoff;
|
|
lv.l_linger = (unsigned short)val_linger;
|
|
|
|
optlen = sizeof(lv);
|
|
opt_ptr = &lv;
|
|
break;
|
|
}
|
|
|
|
case SO_RCVTIMEO:
|
|
case SO_SNDTIMEO: {
|
|
const char sec_key[] = "sec";
|
|
const char usec_key[] = "usec";
|
|
zval *sec, *usec;
|
|
|
|
if (Z_TYPE_P(arg4) != IS_ARRAY) {
|
|
if (UNEXPECTED(Z_TYPE_P(arg4) != IS_OBJECT)) {
|
|
zend_argument_type_error(4, "must be of type array when argument #3 ($option) is %s, %s given",
|
|
optname == SO_RCVTIMEO ? "SO_RCVTIMEO" : "SO_SNDTIMEO",
|
|
zend_zval_value_name(arg4));
|
|
RETURN_THROWS();
|
|
} else {
|
|
opt_ht = Z_OBJPROP_P(arg4);
|
|
}
|
|
} else {
|
|
opt_ht = Z_ARRVAL_P(arg4);
|
|
}
|
|
|
|
if ((sec = zend_hash_str_find(opt_ht, sec_key, sizeof(sec_key) - 1)) == NULL) {
|
|
zend_argument_value_error(4, "must have key \"%s\"", sec_key);
|
|
RETURN_THROWS();
|
|
}
|
|
if ((usec = zend_hash_str_find(opt_ht, usec_key, sizeof(usec_key) - 1)) == NULL) {
|
|
zend_argument_value_error(4, "must have key \"%s\"", usec_key);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
zend_long valsec = zval_get_long(sec);
|
|
zend_long valusec = zval_get_long(usec);
|
|
#ifndef PHP_WIN32
|
|
tv.tv_sec = valsec;
|
|
tv.tv_usec = valusec;
|
|
optlen = sizeof(tv);
|
|
opt_ptr = &tv;
|
|
#else
|
|
if (valsec < 0 || valsec > ULONG_MAX / 1000) {
|
|
zend_argument_value_error(4, "\"%s\" must be between 0 and %u", sec_key, (ULONG_MAX / 1000));
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
timeout = valsec * 1000;
|
|
|
|
|
|
/*
|
|
* We deliberately throw if (valusec / 1000) > ULONG_MAX, treating it as a programmer error.
|
|
* On Windows, ULONG_MAX = 2^32, unlike ZEND_LONG_MAX = 2^63.
|
|
*/
|
|
if (valusec < 0 || timeout > ULONG_MAX - (valusec / 1000)) {
|
|
zend_argument_value_error(4, "\"%s\" must be between 0 and %u", usec_key, (DWORD)(ULONG_MAX - (valusec / 1000)));
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
timeout += valusec / 1000;
|
|
optlen = sizeof(timeout);
|
|
opt_ptr = &timeout;
|
|
#endif
|
|
break;
|
|
}
|
|
#ifdef SO_BINDTODEVICE
|
|
case SO_BINDTODEVICE: {
|
|
if (Z_TYPE_P(arg4) != IS_STRING) {
|
|
zend_argument_type_error(4, "must be of type string when argument #3 ($option) is SO_BINDTODEVICE, %s given", zend_zval_value_name(arg4));
|
|
RETURN_THROWS();
|
|
}
|
|
opt_ptr = Z_STRVAL_P(arg4);
|
|
optlen = Z_STRLEN_P(arg4);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
#ifdef SO_ACCEPTFILTER
|
|
case SO_ACCEPTFILTER: {
|
|
if (Z_TYPE_P(arg4) != IS_STRING) {
|
|
zend_argument_type_error(4, "must be of type string when argument #3 ($option) is SO_ACCEPTFILTER, %s given", zend_zval_value_name(arg4));
|
|
RETURN_THROWS();
|
|
}
|
|
if (zend_str_has_nul_byte(Z_STR_P(arg4))) {
|
|
zend_argument_value_error(4, "must not contain null bytes when argument #3 ($option) is SO_ACCEPTFILTER");
|
|
RETURN_THROWS();
|
|
}
|
|
struct accept_filter_arg af = {0};
|
|
if (Z_STRLEN_P(arg4) >= sizeof(af.af_name)) {
|
|
zend_argument_value_error(4, "must be less than %zu bytes when argument #3 ($option) is SO_ACCEPTFILTER", sizeof(af.af_name));
|
|
RETURN_THROWS();
|
|
}
|
|
/* Copy the trailing nul byte */
|
|
memcpy(af.af_name, Z_STRVAL_P(arg4), Z_STRLEN_P(arg4)+1);
|
|
opt_ptr = ⁡
|
|
optlen = sizeof(af);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
#ifdef FIL_ATTACH
|
|
case FIL_ATTACH:
|
|
case FIL_DETACH: {
|
|
if (level != SOL_FILTER) {
|
|
php_error_docref(NULL, E_WARNING, "Invalid level");
|
|
RETURN_FALSE;
|
|
}
|
|
if (Z_TYPE_P(arg4) != IS_STRING) {
|
|
zend_argument_type_error(4, "must be of type string when argument #3 ($option) is FIL_ATTACH or FIL_DETACH, %s given", zend_zval_value_name(arg4));
|
|
RETURN_THROWS();
|
|
}
|
|
opt_ptr = Z_STRVAL_P(arg4);
|
|
optlen = Z_STRLEN_P(arg4);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
#ifdef SO_ATTACH_REUSEPORT_CBPF
|
|
case SO_ATTACH_REUSEPORT_CBPF: {
|
|
zend_long cbpf_val = zval_get_long(arg4);
|
|
|
|
if (!cbpf_val) {
|
|
ov = 1;
|
|
optlen = sizeof(ov);
|
|
opt_ptr = &ov;
|
|
optname = SO_DETACH_BPF;
|
|
} else {
|
|
uint32_t k = (uint32_t)cbpf_val;
|
|
static struct sock_filter cbpf[8] = {0};
|
|
static struct sock_fprog bpfprog;
|
|
|
|
switch (k) {
|
|
case SKF_AD_CPU:
|
|
case SKF_AD_QUEUE:
|
|
cbpf[0].code = (BPF_LD|BPF_W|BPF_ABS);
|
|
cbpf[0].k = (uint32_t)(SKF_AD_OFF + k);
|
|
cbpf[1].code = (BPF_RET|BPF_A);
|
|
bpfprog.len = 2;
|
|
break;
|
|
default:
|
|
php_error_docref(NULL, E_WARNING, "Unsupported CBPF filter");
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
bpfprog.filter = cbpf;
|
|
optlen = sizeof(bpfprog);
|
|
opt_ptr = &bpfprog;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
#if defined(UDP_SEGMENT)
|
|
case UDP_SEGMENT: {
|
|
ov = zval_get_long(arg4);
|
|
|
|
// UDP segmentation offload maximum size or 0 to disable it
|
|
if (ov < 0 || ov > USHRT_MAX) {
|
|
zend_argument_value_error(4, "must be of between 0 and %u", USHRT_MAX);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
optlen = sizeof(ov);
|
|
opt_ptr = &ov;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
default:
|
|
default_case:
|
|
ov = zval_get_long(arg4);
|
|
|
|
optlen = sizeof(ov);
|
|
opt_ptr = &ov;
|
|
break;
|
|
}
|
|
|
|
retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen);
|
|
if (retval != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to set socket option", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
/* }}} */
|
|
|
|
#ifdef HAVE_SOCKETPAIR
|
|
/* {{{ Creates a pair of indistinguishable sockets and stores them in fds. */
|
|
PHP_FUNCTION(socket_create_pair)
|
|
{
|
|
zval retval[2], *fds_array_zval;
|
|
php_socket *php_sock[2];
|
|
PHP_SOCKET fds_array[2];
|
|
zend_long domain, type, checktype, protocol;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(4, 4)
|
|
Z_PARAM_LONG(domain)
|
|
Z_PARAM_LONG(type)
|
|
Z_PARAM_LONG(protocol)
|
|
Z_PARAM_ZVAL(fds_array_zval)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
if (domain != AF_INET
|
|
#ifdef HAVE_IPV6
|
|
&& domain != AF_INET6
|
|
#endif
|
|
&& domain != AF_UNIX) {
|
|
zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET6, or AF_INET");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
checktype = type;
|
|
#ifdef SOCK_NONBLOCK
|
|
checktype &= ~(SOCK_CLOEXEC | SOCK_NONBLOCK);
|
|
#endif
|
|
|
|
if (checktype > 10) {
|
|
zend_argument_value_error(2, "must be one of SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET,"
|
|
" SOCK_RAW, or SOCK_RDM"
|
|
#ifdef SOCK_NONBLOCK
|
|
" optionally OR'ed with SOCK_CLOEXEC, SOCK_NONBLOCK"
|
|
#endif
|
|
);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
object_init_ex(&retval[0], socket_ce);
|
|
php_sock[0] = Z_SOCKET_P(&retval[0]);
|
|
|
|
object_init_ex(&retval[1], socket_ce);
|
|
php_sock[1] = Z_SOCKET_P(&retval[1]);
|
|
|
|
if (socketpair(domain, type, protocol, fds_array) != 0) {
|
|
SOCKETS_G(last_error) = errno;
|
|
php_error_docref(NULL, E_WARNING, "Unable to create socket pair [%d]: %s", errno, sockets_strerror(errno));
|
|
zval_ptr_dtor(&retval[0]);
|
|
zval_ptr_dtor(&retval[1]);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
fds_array_zval = zend_try_array_init_size(fds_array_zval, 2);
|
|
if (!fds_array_zval) {
|
|
zval_ptr_dtor(&retval[0]);
|
|
zval_ptr_dtor(&retval[1]);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
php_sock[0]->bsd_socket = fds_array[0];
|
|
php_sock[1]->bsd_socket = fds_array[1];
|
|
php_sock[0]->type = domain;
|
|
php_sock[1]->type = domain;
|
|
php_sock[0]->error = 0;
|
|
php_sock[1]->error = 0;
|
|
php_sock[0]->blocking = 1;
|
|
php_sock[1]->blocking = 1;
|
|
|
|
add_index_zval(fds_array_zval, 0, &retval[0]);
|
|
add_index_zval(fds_array_zval, 1, &retval[1]);
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
/* }}} */
|
|
#endif
|
|
|
|
#ifdef HAVE_SHUTDOWN
|
|
/* {{{ Shuts down a socket for receiving, sending, or both. */
|
|
PHP_FUNCTION(socket_shutdown)
|
|
{
|
|
zval *arg1;
|
|
zend_long how_shutdown = 2;
|
|
php_socket *php_sock;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 2)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_LONG(how_shutdown)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
if (shutdown(php_sock->bsd_socket, how_shutdown) != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to shutdown socket", errno);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
/* }}} */
|
|
#endif
|
|
|
|
#ifdef HAVE_SOCKATMARK
|
|
PHP_FUNCTION(socket_atmark)
|
|
{
|
|
zval *arg1;
|
|
php_socket *php_sock;
|
|
int r;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, socket_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
r = sockatmark(php_sock->bsd_socket);
|
|
if (r < 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to apply sockmark", errno);
|
|
RETURN_FALSE;
|
|
} else if (r == 0) {
|
|
RETURN_FALSE;
|
|
} else {
|
|
RETURN_TRUE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* {{{ Returns the last socket error (either the last used or the provided socket resource) */
|
|
PHP_FUNCTION(socket_last_error)
|
|
{
|
|
zval *arg1 = NULL;
|
|
php_socket *php_sock;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(0, 1)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_OBJECT_OF_CLASS_OR_NULL(arg1, socket_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
if (arg1) {
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
RETVAL_LONG(php_sock->error);
|
|
} else {
|
|
RETVAL_LONG(SOCKETS_G(last_error));
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Clears the error on the socket or the last error code. */
|
|
PHP_FUNCTION(socket_clear_error)
|
|
{
|
|
zval *arg1 = NULL;
|
|
php_socket *php_sock;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(0, 1)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_OBJECT_OF_CLASS_OR_NULL(arg1, socket_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
if (arg1) {
|
|
php_sock = Z_SOCKET_P(arg1);
|
|
ENSURE_SOCKET_VALID(php_sock);
|
|
|
|
php_sock->error = 0;
|
|
} else {
|
|
SOCKETS_G(last_error) = 0;
|
|
}
|
|
|
|
return;
|
|
}
|
|
/* }}} */
|
|
|
|
bool socket_import_file_descriptor(PHP_SOCKET socket, php_socket *retsock)
|
|
{
|
|
#ifdef SO_DOMAIN
|
|
int type;
|
|
socklen_t type_len = sizeof(type);
|
|
#endif
|
|
php_sockaddr_storage addr;
|
|
socklen_t addr_len = sizeof(addr);
|
|
#ifndef PHP_WIN32
|
|
int t;
|
|
#endif
|
|
|
|
retsock->bsd_socket = socket;
|
|
|
|
/* determine family */
|
|
#ifdef SO_DOMAIN
|
|
if (getsockopt(socket, SOL_SOCKET, SO_DOMAIN, &type, &type_len) == 0) {
|
|
retsock->type = type;
|
|
} else
|
|
#endif
|
|
if (getsockname(socket, (struct sockaddr*)&addr, &addr_len) == 0) {
|
|
retsock->type = addr.ss_family;
|
|
} else {
|
|
PHP_SOCKET_ERROR(retsock, "Unable to obtain socket family", errno);
|
|
return 0;
|
|
}
|
|
|
|
/* determine blocking mode */
|
|
#ifndef PHP_WIN32
|
|
t = fcntl(socket, F_GETFL);
|
|
if (t == -1) {
|
|
PHP_SOCKET_ERROR(retsock, "Unable to obtain blocking state", errno);
|
|
return 0;
|
|
} else {
|
|
retsock->blocking = !(t & O_NONBLOCK);
|
|
}
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* {{{ Imports a stream that encapsulates a socket into a socket extension resource. */
|
|
PHP_FUNCTION(socket_import_stream)
|
|
{
|
|
zval *zstream;
|
|
php_stream *stream;
|
|
php_socket *retsock = NULL;
|
|
PHP_SOCKET socket; /* fd */
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_RESOURCE(zstream)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
php_stream_from_zval(stream, zstream);
|
|
|
|
if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void**)&socket, 1)) {
|
|
/* error supposedly already shown */
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
object_init_ex(return_value, socket_ce);
|
|
retsock = Z_SOCKET_P(return_value);
|
|
|
|
if (!socket_import_file_descriptor(socket, retsock)) {
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
#ifdef PHP_WIN32
|
|
/* on windows, check if the stream is a socket stream and read its
|
|
* private data; otherwise assume it's in non-blocking mode */
|
|
if (php_stream_is(stream, PHP_STREAM_IS_SOCKET)) {
|
|
retsock->blocking =
|
|
((php_netstream_data_t *)stream->abstract)->is_blocked;
|
|
} else {
|
|
retsock->blocking = 1;
|
|
}
|
|
#endif
|
|
|
|
/* hold a zval reference to the stream (holding a php_stream* directly could
|
|
* also be done, but this makes socket_export_stream a bit simpler) */
|
|
ZVAL_COPY(&retsock->zstream, zstream);
|
|
|
|
php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_NONE, NULL);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Exports a socket extension resource into a stream that encapsulates a socket. */
|
|
PHP_FUNCTION(socket_export_stream)
|
|
{
|
|
zval *zsocket;
|
|
php_socket *socket;
|
|
php_stream *stream = NULL;
|
|
php_netstream_data_t *stream_data;
|
|
const char *protocol = NULL;
|
|
size_t protocollen = 0;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_OBJECT_OF_CLASS(zsocket, socket_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
socket = Z_SOCKET_P(zsocket);
|
|
ENSURE_SOCKET_VALID(socket);
|
|
|
|
/* Either we already exported a stream or the socket came from an import,
|
|
* just return the existing stream */
|
|
if (!Z_ISUNDEF(socket->zstream)) {
|
|
RETURN_COPY(&socket->zstream);
|
|
}
|
|
|
|
/* Determine if socket is using a protocol with one of the default registered
|
|
* socket stream wrappers */
|
|
if (socket->type == PF_INET
|
|
#ifdef HAVE_IPV6
|
|
|| socket->type == PF_INET6
|
|
#endif
|
|
) {
|
|
int protoid;
|
|
socklen_t protoidlen = sizeof(protoid);
|
|
|
|
getsockopt(socket->bsd_socket, SOL_SOCKET, SO_TYPE, (char *) &protoid, &protoidlen);
|
|
|
|
if (protoid == SOCK_STREAM) {
|
|
/* SO_PROTOCOL is not (yet?) supported on OS X, so let's assume it's TCP there */
|
|
#ifdef SO_PROTOCOL
|
|
protoidlen = sizeof(protoid);
|
|
getsockopt(socket->bsd_socket, SOL_SOCKET, SO_PROTOCOL, (char *) &protoid, &protoidlen);
|
|
if (protoid == IPPROTO_TCP)
|
|
#endif
|
|
{
|
|
protocol = "tcp://";
|
|
protocollen = sizeof("tcp://") - 1;
|
|
}
|
|
} else if (protoid == SOCK_DGRAM) {
|
|
protocol = "udp://";
|
|
protocollen = sizeof("udp://") - 1;
|
|
}
|
|
#ifdef PF_UNIX
|
|
} else if (socket->type == PF_UNIX) {
|
|
int type;
|
|
socklen_t typelen = sizeof(type);
|
|
|
|
getsockopt(socket->bsd_socket, SOL_SOCKET, SO_TYPE, (char *) &type, &typelen);
|
|
|
|
if (type == SOCK_STREAM) {
|
|
protocol = "unix://";
|
|
protocollen = sizeof("unix://") - 1;
|
|
} else if (type == SOCK_DGRAM) {
|
|
protocol = "udg://";
|
|
protocollen = sizeof("udg://") - 1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Try to get a stream with the registered sockops for the protocol in use
|
|
* We don't want streams to actually *do* anything though, so don't give it
|
|
* anything apart from the protocol */
|
|
if (protocol != NULL) {
|
|
stream = php_stream_xport_create(protocol, protocollen, 0, 0, NULL, NULL, NULL, NULL, NULL);
|
|
}
|
|
|
|
/* Fall back to creating a generic socket stream */
|
|
if (stream == NULL) {
|
|
stream = php_stream_sock_open_from_socket(socket->bsd_socket, 0);
|
|
|
|
if (stream == NULL) {
|
|
php_error_docref(NULL, E_WARNING, "Failed to create stream");
|
|
RETURN_FALSE;
|
|
}
|
|
}
|
|
|
|
stream_data = (php_netstream_data_t *) stream->abstract;
|
|
stream_data->socket = socket->bsd_socket;
|
|
stream_data->is_blocked = socket->blocking;
|
|
stream_data->timeout.tv_sec = FG(default_socket_timeout);
|
|
stream_data->timeout.tv_usec = 0;
|
|
|
|
php_stream_to_zval(stream, &socket->zstream);
|
|
|
|
RETURN_COPY(&socket->zstream);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Gets array with contents of getaddrinfo about the given hostname. */
|
|
PHP_FUNCTION(socket_addrinfo_lookup)
|
|
{
|
|
char *service = NULL;
|
|
size_t service_len = 0;
|
|
zend_string *hostname, *key;
|
|
zval *hint, *zhints = NULL;
|
|
|
|
struct addrinfo hints, *result, *rp;
|
|
php_addrinfo *res;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 3)
|
|
Z_PARAM_STR(hostname)
|
|
Z_PARAM_OPTIONAL
|
|
Z_PARAM_STRING_OR_NULL(service, service_len)
|
|
Z_PARAM_ARRAY(zhints)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
#if defined(PHP_WIN32)
|
|
# if !defined(AF_MAX)
|
|
# define AF_MAX (AF_BTH + 1)
|
|
# endif
|
|
#endif
|
|
|
|
if (zhints) {
|
|
if (UNEXPECTED(HT_IS_PACKED(Z_ARRVAL_P(zhints)))) {
|
|
zend_argument_value_error(3, "must only contain array keys \"ai_flags\", \"ai_socktype\", "
|
|
"\"ai_protocol\", or \"ai_family\"");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zhints), key, hint) {
|
|
if (key) {
|
|
bool failed = false;
|
|
if (zend_string_equals_literal(key, "ai_flags")) {
|
|
zend_long val = zval_try_get_long(hint, &failed);
|
|
if (failed) {
|
|
zend_argument_type_error(3, "\"ai_flags\" key must be of type int, %s given", zend_zval_type_name(hint));
|
|
RETURN_THROWS();
|
|
}
|
|
if (val < 0 || val > INT_MAX) {
|
|
zend_argument_value_error(3, "\"ai_flags\" key must be between 0 and %d", INT_MAX);
|
|
RETURN_THROWS();
|
|
}
|
|
hints.ai_flags = (int)val;
|
|
} else if (zend_string_equals_literal(key, "ai_socktype")) {
|
|
zend_long val = zval_try_get_long(hint, &failed);
|
|
if (failed) {
|
|
zend_argument_type_error(3, "\"ai_socktype\" key must be of type int, %s given", zend_zval_type_name(hint));
|
|
RETURN_THROWS();
|
|
}
|
|
if (val < 0 || val > INT_MAX) {
|
|
zend_argument_value_error(3, "\"ai_socktype\" key must be between 0 and %d", INT_MAX);
|
|
RETURN_THROWS();
|
|
}
|
|
hints.ai_socktype = (int)val;
|
|
} else if (zend_string_equals_literal(key, "ai_protocol")) {
|
|
zend_long val = zval_try_get_long(hint, &failed);
|
|
if (failed) {
|
|
zend_argument_type_error(3, "\"ai_protocol\" key must be of type int, %s given", zend_zval_type_name(hint));
|
|
RETURN_THROWS();
|
|
}
|
|
if (val < 0 || val > INT_MAX) {
|
|
zend_argument_value_error(3, "\"ai_protocol\" key must be between 0 and %d", INT_MAX);
|
|
RETURN_THROWS();
|
|
}
|
|
hints.ai_protocol = (int)val;
|
|
} else if (zend_string_equals_literal(key, "ai_family")) {
|
|
zend_long val = zval_try_get_long(hint, &failed);
|
|
if (failed) {
|
|
zend_argument_type_error(3, "\"ai_family\" key must be of type int, %s given", zend_zval_type_name(hint));
|
|
RETURN_THROWS();
|
|
}
|
|
if (val < 0 || val >= AF_MAX) {
|
|
zend_argument_value_error(3, "\"ai_family\" key must be between 0 and %d", AF_MAX - 1);
|
|
RETURN_THROWS();
|
|
}
|
|
hints.ai_family = (int)val;
|
|
} else {
|
|
zend_argument_value_error(3, "must only contain array keys \"ai_flags\", \"ai_socktype\", "
|
|
"\"ai_protocol\", or \"ai_family\"");
|
|
RETURN_THROWS();
|
|
}
|
|
} else {
|
|
zend_argument_value_error(3, "must only contain array keys \"ai_flags\", \"ai_socktype\", "
|
|
"\"ai_protocol\", or \"ai_family\"");
|
|
RETURN_THROWS();
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
}
|
|
|
|
if (getaddrinfo(ZSTR_VAL(hostname), service, &hints, &result) != 0) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
array_init(return_value);
|
|
zend_hash_real_init_packed(Z_ARRVAL_P(return_value));
|
|
|
|
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
|
if (rp->ai_family != AF_UNSPEC) {
|
|
zval zaddr;
|
|
|
|
object_init_ex(&zaddr, address_info_ce);
|
|
res = Z_ADDRESS_INFO_P(&zaddr);
|
|
|
|
memcpy(&res->addrinfo, rp, sizeof(struct addrinfo));
|
|
|
|
res->addrinfo.ai_addr = emalloc(rp->ai_addrlen);
|
|
memcpy(res->addrinfo.ai_addr, rp->ai_addr, rp->ai_addrlen);
|
|
|
|
if (rp->ai_canonname != NULL) {
|
|
res->addrinfo.ai_canonname = estrdup(rp->ai_canonname);
|
|
}
|
|
|
|
add_next_index_zval(return_value, &zaddr);
|
|
}
|
|
}
|
|
|
|
freeaddrinfo(result);
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Creates and binds to a socket from a given addrinfo resource */
|
|
PHP_FUNCTION(socket_addrinfo_bind)
|
|
{
|
|
zval *arg1;
|
|
int retval;
|
|
php_addrinfo *ai;
|
|
php_socket *php_sock;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, address_info_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
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);
|
|
|
|
php_sock->bsd_socket = socket(ai->addrinfo.ai_family, ai->addrinfo.ai_socktype, ai->addrinfo.ai_protocol);
|
|
php_sock->type = ai->addrinfo.ai_family;
|
|
|
|
if (IS_INVALID_SOCKET(php_sock)) {
|
|
SOCKETS_G(last_error) = errno;
|
|
php_error_docref(NULL, E_WARNING, "Unable to create socket [%d]: %s", errno, sockets_strerror(errno));
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
php_sock->error = 0;
|
|
php_sock->blocking = 1;
|
|
|
|
switch(php_sock->type) {
|
|
case AF_UNIX:
|
|
{
|
|
// AF_UNIX sockets via getaddrino are not implemented due to security problems
|
|
close(php_sock->bsd_socket);
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
case AF_INET:
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
#endif
|
|
{
|
|
retval = bind(php_sock->bsd_socket, ai->addrinfo.ai_addr, ai->addrinfo.ai_addrlen);
|
|
break;
|
|
}
|
|
default:
|
|
close(php_sock->bsd_socket);
|
|
zval_ptr_dtor(return_value);
|
|
zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6");
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
if (retval != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to bind address", errno);
|
|
close(php_sock->bsd_socket);
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_FALSE;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Creates and connects to a socket from a given addrinfo resource */
|
|
PHP_FUNCTION(socket_addrinfo_connect)
|
|
{
|
|
zval *arg1;
|
|
int retval;
|
|
php_addrinfo *ai;
|
|
php_socket *php_sock;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, address_info_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
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);
|
|
|
|
php_sock->bsd_socket = socket(ai->addrinfo.ai_family, ai->addrinfo.ai_socktype, ai->addrinfo.ai_protocol);
|
|
php_sock->type = ai->addrinfo.ai_family;
|
|
|
|
if (IS_INVALID_SOCKET(php_sock)) {
|
|
SOCKETS_G(last_error) = errno;
|
|
php_error_docref(NULL, E_WARNING, "Unable to create socket [%d]: %s", errno, sockets_strerror(errno));
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
php_sock->error = 0;
|
|
php_sock->blocking = 1;
|
|
|
|
switch(php_sock->type) {
|
|
case AF_UNIX:
|
|
{
|
|
// AF_UNIX sockets via getaddrino are not implemented due to security problems
|
|
close(php_sock->bsd_socket);
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
case AF_INET:
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
#endif
|
|
{
|
|
retval = connect(php_sock->bsd_socket, ai->addrinfo.ai_addr, ai->addrinfo.ai_addrlen);
|
|
break;
|
|
}
|
|
default:
|
|
zend_argument_value_error(1, "socket type must be one of AF_UNIX, AF_INET, or AF_INET6");
|
|
close(php_sock->bsd_socket);
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
if (retval != 0) {
|
|
PHP_SOCKET_ERROR(php_sock, "Unable to connect address", errno);
|
|
close(php_sock->bsd_socket);
|
|
zval_ptr_dtor(return_value);
|
|
RETURN_FALSE;
|
|
}
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Creates and connects to a socket from a given addrinfo resource */
|
|
PHP_FUNCTION(socket_addrinfo_explain)
|
|
{
|
|
zval *arg1, sockaddr;
|
|
php_addrinfo *ai;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_OBJECT_OF_CLASS(arg1, address_info_ce)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
ai = Z_ADDRESS_INFO_P(arg1);
|
|
|
|
array_init(return_value);
|
|
|
|
add_assoc_long(return_value, "ai_flags", ai->addrinfo.ai_flags);
|
|
add_assoc_long(return_value, "ai_family", ai->addrinfo.ai_family);
|
|
add_assoc_long(return_value, "ai_socktype", ai->addrinfo.ai_socktype);
|
|
add_assoc_long(return_value, "ai_protocol", ai->addrinfo.ai_protocol);
|
|
if (ai->addrinfo.ai_canonname != NULL) {
|
|
add_assoc_string(return_value, "ai_canonname", ai->addrinfo.ai_canonname);
|
|
}
|
|
|
|
array_init(&sockaddr);
|
|
switch (ai->addrinfo.ai_family) {
|
|
case AF_INET:
|
|
{
|
|
struct sockaddr_in *sa = (struct sockaddr_in *) ai->addrinfo.ai_addr;
|
|
char addr[INET_ADDRSTRLEN];
|
|
|
|
add_assoc_long(&sockaddr, "sin_port", ntohs((unsigned short) sa->sin_port));
|
|
inet_ntop(ai->addrinfo.ai_family, &sa->sin_addr, addr, sizeof(addr));
|
|
add_assoc_string(&sockaddr, "sin_addr", addr);
|
|
break;
|
|
}
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
{
|
|
struct sockaddr_in6 *sa = (struct sockaddr_in6 *) ai->addrinfo.ai_addr;
|
|
char addr[INET6_ADDRSTRLEN];
|
|
|
|
add_assoc_long(&sockaddr, "sin6_port", ntohs((unsigned short) sa->sin6_port));
|
|
inet_ntop(ai->addrinfo.ai_family, &sa->sin6_addr, addr, sizeof(addr));
|
|
add_assoc_string(&sockaddr, "sin6_addr", addr);
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
add_assoc_zval(return_value, "ai_addr", &sockaddr);
|
|
}
|
|
/* }}} */
|
|
|
|
#ifdef PHP_WIN32
|
|
|
|
/* {{{ Exports the network socket information suitable to be used in another process and returns the info id. */
|
|
PHP_FUNCTION(socket_wsaprotocol_info_export)
|
|
{
|
|
WSAPROTOCOL_INFO wi;
|
|
zval *zsocket;
|
|
php_socket *socket;
|
|
zend_long target_pid;
|
|
zend_string *seg_name;
|
|
HANDLE map;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(2, 2)
|
|
Z_PARAM_OBJECT_OF_CLASS(zsocket, socket_ce)
|
|
Z_PARAM_LONG(target_pid)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
socket = Z_SOCKET_P(zsocket);
|
|
ENSURE_SOCKET_VALID(socket);
|
|
|
|
if (SOCKET_ERROR == WSADuplicateSocket(socket->bsd_socket, (DWORD)target_pid, &wi)) {
|
|
DWORD err = WSAGetLastError();
|
|
char *buf = php_win32_error_to_msg(err);
|
|
|
|
if (!buf[0]) {
|
|
php_error_docref(NULL, E_WARNING, "Unable to export WSA protocol info [0x%08lx]", err);
|
|
} else {
|
|
php_error_docref(NULL, E_WARNING, "Unable to export WSA protocol info [0x%08lx]: %s", err, buf);
|
|
}
|
|
|
|
php_win32_error_msg_free(buf);
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
seg_name = zend_strpprintf(0, "php_wsa_for_%u", SOCKETS_G(wsa_child_count)++);
|
|
map = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(WSAPROTOCOL_INFO), ZSTR_VAL(seg_name));
|
|
if (NULL != map) {
|
|
LPVOID view = MapViewOfFile(map, FILE_MAP_WRITE, 0, 0, 0);
|
|
if (view) {
|
|
memcpy(view, &wi, sizeof(wi));
|
|
UnmapViewOfFile(view);
|
|
zend_hash_add_ptr(&(SOCKETS_G(wsa_info)), seg_name, map);
|
|
RETURN_STR(seg_name);
|
|
} else {
|
|
DWORD err = GetLastError();
|
|
php_error_docref(NULL, E_WARNING, "Unable to map file view [0x%08lx]", err);
|
|
}
|
|
} else {
|
|
DWORD err = GetLastError();
|
|
php_error_docref(NULL, E_WARNING, "Unable to create file mapping [0x%08lx]", err);
|
|
}
|
|
zend_string_release_ex(seg_name, 0);
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Imports the network socket information using the supplied id and creates a new socket on its base. */
|
|
PHP_FUNCTION(socket_wsaprotocol_info_import)
|
|
{
|
|
char *id;
|
|
size_t id_len;
|
|
WSAPROTOCOL_INFO wi;
|
|
PHP_SOCKET sock;
|
|
php_socket *php_sock;
|
|
HANDLE map;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_STRING(id, id_len)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
map = OpenFileMapping(FILE_MAP_READ, FALSE, id);
|
|
if (map) {
|
|
LPVOID view = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
|
|
if (view) {
|
|
memcpy(&wi, view, sizeof(WSAPROTOCOL_INFO));
|
|
UnmapViewOfFile(view);
|
|
} else {
|
|
DWORD err = GetLastError();
|
|
php_error_docref(NULL, E_WARNING, "Unable to map file view [0x%08lx]", err);
|
|
RETURN_FALSE;
|
|
}
|
|
CloseHandle(map);
|
|
} else {
|
|
DWORD err = GetLastError();
|
|
php_error_docref(NULL, E_WARNING, "Unable to open file mapping [0x%08lx]", err);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
sock = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, &wi, 0, 0);
|
|
if (INVALID_SOCKET == sock) {
|
|
DWORD err = WSAGetLastError();
|
|
char *buf = php_win32_error_to_msg(err);
|
|
|
|
if (!buf[0]) {
|
|
php_error_docref(NULL, E_WARNING, "Unable to import WSA protocol info [0x%08lx]", err);
|
|
} else {
|
|
php_error_docref(NULL, E_WARNING, "Unable to import WSA protocol info [0x%08lx]: %s", err, buf);
|
|
}
|
|
|
|
php_win32_error_msg_free(buf);
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
object_init_ex(return_value, socket_ce);
|
|
php_sock = Z_SOCKET_P(return_value);
|
|
|
|
php_sock->bsd_socket = sock;
|
|
php_sock->type = wi.iAddressFamily;
|
|
php_sock->error = 0;
|
|
php_sock->blocking = 1;
|
|
}
|
|
/* }}} */
|
|
|
|
/* {{{ Frees the exported info and corresponding resources using the supplied id. */
|
|
PHP_FUNCTION(socket_wsaprotocol_info_release)
|
|
{
|
|
char *id;
|
|
size_t id_len;
|
|
|
|
ZEND_PARSE_PARAMETERS_START(1, 1)
|
|
Z_PARAM_STRING(id, id_len)
|
|
ZEND_PARSE_PARAMETERS_END();
|
|
|
|
RETURN_BOOL(SUCCESS == zend_hash_str_del(&(SOCKETS_G(wsa_info)), id, id_len));
|
|
}
|
|
/* }}} */
|
|
#endif
|