Page MenuHomePhabricator (Chris)

amqp_connection_resource.c
No OneTemporary

Authored By
Unknown
Size
24 KB
Referenced Files
None
Subscribers
None

amqp_connection_resource.c

/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2007 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: |
| http://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. |
+----------------------------------------------------------------------+
| Author: Alexandre Kalendarev akalend@mail.ru Copyright (c) 2009-2010 |
| Lead: |
| - Pieter de Zwart |
| Maintainers: |
| - Brad Rodriguez |
| - Jonathan Tansavatdi |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "ext/standard/datetime.h"
#include "zend_exceptions.h"
#ifdef PHP_WIN32
#if PHP_VERSION_ID >= 80000
#include <stdint.h>
#else
#include "win32/php_stdint.h"
#endif
#include "win32/signal.h"
#else
#include <signal.h>
#include <stdint.h>
#endif
#if HAVE_LIBRABBITMQ_NEW_LAYOUT
#include <rabbitmq-c/amqp.h>
#include <rabbitmq-c/framing.h>
#include <rabbitmq-c/tcp_socket.h>
#include <rabbitmq-c/ssl_socket.h>
#else
#include <amqp.h>
#include <amqp_framing.h>
#include <amqp_tcp_socket.h>
#include <amqp_ssl_socket.h>
#endif
#ifdef PHP_WIN32
#include "win32/unistd.h"
#else
#include <unistd.h>
#endif
#include "amqp_methods_handling.h"
#include "amqp_connection_resource.h"
#include "amqp_channel.h"
#include "php_amqp.h"
#ifndef E_DEPRECATED
#define E_DEPRECATED E_WARNING
#endif
int le_amqp_connection_resource;
int le_amqp_connection_resource_persistent;
static void connection_resource_destructor(amqp_connection_resource *resource, int persistent);
static void php_amqp_close_connection_from_server(
amqp_rpc_reply_t reply,
char **message,
amqp_connection_resource *resource
);
static void php_amqp_close_channel_from_server(
amqp_rpc_reply_t reply,
char **message,
amqp_connection_resource *resource,
amqp_channel_t channel_id
);
/* Figure out what's going on connection and handle protocol exceptions, if any */
int php_amqp_connection_resource_error(
amqp_rpc_reply_t reply,
char **message,
amqp_connection_resource *resource,
amqp_channel_t channel_id
)
{
assert(resource != NULL);
switch (reply.reply_type) {
case AMQP_RESPONSE_NORMAL:
return PHP_AMQP_RESOURCE_RESPONSE_OK;
case AMQP_RESPONSE_NONE:
spprintf(message, 0, "Missing RPC reply type.");
return PHP_AMQP_RESOURCE_RESPONSE_ERROR;
case AMQP_RESPONSE_LIBRARY_EXCEPTION:
spprintf(message, 0, "%s", amqp_error_string2(reply.library_error));
return PHP_AMQP_RESOURCE_RESPONSE_ERROR;
case AMQP_RESPONSE_SERVER_EXCEPTION:
switch (reply.reply.id) {
case AMQP_CONNECTION_CLOSE_METHOD: {
php_amqp_close_connection_from_server(reply, message, resource);
return PHP_AMQP_RESOURCE_RESPONSE_ERROR_CONNECTION_CLOSED;
}
case AMQP_CHANNEL_CLOSE_METHOD: {
php_amqp_close_channel_from_server(reply, message, resource, channel_id);
return PHP_AMQP_RESOURCE_RESPONSE_ERROR_CHANNEL_CLOSED;
}
}
/* Default for the above switch should be handled by the below default. */
default:
spprintf(message, 0, "Unknown server error, method id 0x%08X", reply.reply.id);
return PHP_AMQP_RESOURCE_RESPONSE_ERROR;
}
/* Should not never get here*/
}
static void php_amqp_close_connection_from_server(
amqp_rpc_reply_t reply,
char **message,
amqp_connection_resource *resource
)
{
amqp_connection_close_t *m = (amqp_connection_close_t *) reply.reply.decoded;
int result;
if (!reply.reply.id) {
PHP_AMQP_G(error_code) = -1;
spprintf(
message,
0,
"Server connection error: %ld, message: %s",
(long) PHP_AMQP_G(error_code),
"unexpected response"
);
} else {
PHP_AMQP_G(error_code) = m->reply_code;
spprintf(
message,
0,
"Server connection error: %d, message: %.*s",
m->reply_code,
(int) m->reply_text.len,
(char *) m->reply_text.bytes
);
}
/*
* - If r.reply.id == AMQP_CONNECTION_CLOSE_METHOD a connection exception
* occurred, cast r.reply.decoded to amqp_connection_close_t* to see
* details of the exception. The client amqp_send_method() a
* amqp_connection_close_ok_t and disconnect from the broker.
*/
amqp_connection_close_ok_t *decoded = (amqp_connection_close_ok_t *) NULL;
result = amqp_send_method(
resource->connection_state,
0, /* NOTE: 0-channel is reserved for things like this */
AMQP_CONNECTION_CLOSE_OK_METHOD,
&decoded
);
if (result != AMQP_STATUS_OK) {
zend_throw_exception(amqp_channel_exception_class_entry, "An error occurred while closing the connection.", 0);
}
/* Prevent finishing AMQP connection in connection resource destructor */
resource->is_connected = '\0';
}
static void php_amqp_close_channel_from_server(
amqp_rpc_reply_t reply,
char **message,
amqp_connection_resource *resource,
amqp_channel_t channel_id
)
{
assert(channel_id > 0 && channel_id <= resource->max_slots);
amqp_channel_close_t *m = (amqp_channel_close_t *) reply.reply.decoded;
if (!reply.reply.id) {
PHP_AMQP_G(error_code) = -1;
spprintf(
message,
0,
"Server channel error: %ld, message: %s",
(long) PHP_AMQP_G(error_code),
"unexpected response"
);
} else {
PHP_AMQP_G(error_code) = m->reply_code;
spprintf(
message,
0,
"Server channel error: %d, message: %.*s",
m->reply_code,
(int) m->reply_text.len,
(char *) m->reply_text.bytes
);
}
/*
* - If r.reply.id == AMQP_CHANNEL_CLOSE_METHOD a channel exception
* occurred, cast r.reply.decoded to amqp_channel_close_t* to see details
* of the exception. The client should amqp_send_method() a
* amqp_channel_close_ok_t. The channel must be re-opened before it
* can be used again. Any resources associated with the channel
* (auto-delete exchanges, auto-delete queues, consumers) are invalid
* and must be recreated before attempting to use them again.
*/
if (resource) {
int result;
amqp_channel_close_ok_t *decoded = (amqp_channel_close_ok_t *) NULL;
result = amqp_send_method(resource->connection_state, channel_id, AMQP_CHANNEL_CLOSE_OK_METHOD, &decoded);
if (result != AMQP_STATUS_OK) {
zend_throw_exception(amqp_channel_exception_class_entry, "An error occurred while closing channel.", 0);
}
}
}
int php_amqp_connection_resource_error_advanced(amqp_rpc_reply_t reply, char **message, amqp_channel_object *channel)
{
amqp_frame_t frame;
assert(AMQP_RESPONSE_LIBRARY_EXCEPTION == reply.reply_type);
assert(AMQP_STATUS_UNEXPECTED_STATE == reply.library_error);
if (channel->channel_resource->channel_id < 0 ||
AMQP_STATUS_OK !=
amqp_simple_wait_frame(channel->channel_resource->connection_resource->connection_state, &frame)) {
if (*message != NULL) {
efree(*message);
}
spprintf(message, 0, "%s", amqp_error_string2(reply.library_error));
return PHP_AMQP_RESOURCE_RESPONSE_ERROR;
}
if (channel->channel_resource->channel_id != frame.channel) {
spprintf(message, 0, "Channel mismatch");
return PHP_AMQP_RESOURCE_RESPONSE_ERROR;
}
if (AMQP_FRAME_METHOD == frame.frame_type) {
switch (frame.payload.method.id) {
case AMQP_CONNECTION_CLOSE_METHOD: {
php_amqp_close_connection_from_server(reply, message, channel->channel_resource->connection_resource);
return PHP_AMQP_RESOURCE_RESPONSE_ERROR_CONNECTION_CLOSED;
}
case AMQP_CHANNEL_CLOSE_METHOD: {
php_amqp_close_channel_from_server(
reply,
message,
channel->channel_resource->connection_resource,
channel->channel_resource->channel_id
);
return PHP_AMQP_RESOURCE_RESPONSE_ERROR_CHANNEL_CLOSED;
}
case AMQP_BASIC_ACK_METHOD:
/* if we've turned publisher confirms on, and we've published a message
* here is a message being confirmed
*/
return php_amqp_handle_basic_ack(message, channel, &frame.payload.method);
case AMQP_BASIC_NACK_METHOD:
/* if we've turned publisher confirms on, and we've published a message
* here is a message being confirmed
*/
return php_amqp_handle_basic_nack(message, channel, &frame.payload.method);
case AMQP_BASIC_RETURN_METHOD:
/* if a published message couldn't be routed and the mandatory flag was set
* this is what would be returned. The message then needs to be read.
*/
return php_amqp_handle_basic_return(message, channel, &frame.payload.method);
default:
if (*message != NULL) {
efree(*message);
}
spprintf(message, 0, "An unexpected method was received 0x%08X\n", frame.payload.method.id);
return PHP_AMQP_RESOURCE_RESPONSE_ERROR;
}
}
if (*message != NULL) {
efree(*message);
}
spprintf(message, 0, "%s", amqp_error_string2(reply.library_error));
return PHP_AMQP_RESOURCE_RESPONSE_ERROR;
}
/* Socket-related functions */
int php_amqp_set_resource_read_timeout(amqp_connection_resource *resource, double timeout)
{
assert(timeout >= 0.0);
#ifdef PHP_WIN32
DWORD read_timeout;
/*
In Windows, setsockopt with SO_RCVTIMEO sets actual timeout
to a value that's 500ms greater than specified value.
Also, it's not possible to set timeout to any value below 500ms.
Zero timeout works like it should, however.
*/
if (timeout == 0.) {
read_timeout = 0;
} else {
read_timeout = (int) (max(timeout * 1.e+3 - .5e+3, 1.));
}
#else
struct timeval read_timeout;
read_timeout.tv_sec = (int) floor(timeout);
read_timeout.tv_usec = (int) ((timeout - floor(timeout)) * 1.e+6);
#endif
if (0 != setsockopt(
amqp_get_sockfd(resource->connection_state),
SOL_SOCKET,
SO_RCVTIMEO,
(char *) &read_timeout,
sizeof(read_timeout)
)) {
zend_throw_exception(amqp_connection_exception_class_entry, "Socket error: cannot setsockopt SO_RCVTIMEO", 0);
return 0;
}
return 1;
}
int php_amqp_set_resource_rpc_timeout(amqp_connection_resource *resource, double timeout)
{
assert(timeout >= 0.0);
#if AMQP_VERSION_MAJOR * 100 + AMQP_VERSION_MINOR * 10 + AMQP_VERSION_PATCH >= 90
struct timeval rpc_timeout;
if (timeout == 0.)
return 1;
rpc_timeout.tv_sec = (int) floor(timeout);
rpc_timeout.tv_usec = (int) ((timeout - floor(timeout)) * 1.e+6);
if (AMQP_STATUS_OK != amqp_set_rpc_timeout(resource->connection_state, &rpc_timeout)) {
zend_throw_exception(amqp_connection_exception_class_entry, "Cannot set rpc_timeout", 0);
return 0;
}
#endif
return 1;
}
int php_amqp_set_resource_write_timeout(amqp_connection_resource *resource, double timeout)
{
assert(timeout >= 0.0);
#ifdef PHP_WIN32
DWORD write_timeout;
if (timeout == 0.) {
write_timeout = 0;
} else {
write_timeout = (int) (max(timeout * 1.e+3 - .5e+3, 1.));
}
#else
struct timeval write_timeout;
write_timeout.tv_sec = (int) floor(timeout);
write_timeout.tv_usec = (int) ((timeout - floor(timeout)) * 1.e+6);
#endif
if (0 != setsockopt(
amqp_get_sockfd(resource->connection_state),
SOL_SOCKET,
SO_SNDTIMEO,
(char *) &write_timeout,
sizeof(write_timeout)
)) {
zend_throw_exception(amqp_connection_exception_class_entry, "Socket error: cannot setsockopt SO_SNDTIMEO", 0);
return 0;
}
return 1;
}
/* Channel-related functions */
amqp_channel_t php_amqp_connection_resource_get_available_channel_id(amqp_connection_resource *resource)
{
assert(resource != NULL);
assert(resource->slots != NULL);
/* Check if there are any open slots */
if (resource->used_slots >= resource->max_slots) {
return 0;
}
amqp_channel_t slot;
for (slot = resource->used_slots; slot < resource->max_slots; slot++) {
if (resource->slots[slot] == 0) {
return (amqp_channel_t) (slot + 1);
}
}
return 0;
}
int php_amqp_connection_resource_register_channel(
amqp_connection_resource *resource,
amqp_channel_resource *channel_resource,
amqp_channel_t channel_id
)
{
assert(resource != NULL);
assert(resource->slots != NULL);
assert(channel_id > 0 && channel_id <= resource->max_slots);
if (resource->slots[channel_id - 1] != 0) {
return FAILURE;
}
resource->slots[channel_id - 1] = channel_resource;
channel_resource->connection_resource = resource;
resource->used_slots++;
return SUCCESS;
}
int php_amqp_connection_resource_unregister_channel(amqp_connection_resource *resource, amqp_channel_t channel_id)
{
assert(resource != NULL);
assert(resource->slots != NULL);
assert(channel_id > 0 && channel_id <= resource->max_slots);
if (resource->slots[channel_id - 1] != 0) {
resource->slots[channel_id - 1]->connection_resource = NULL;
resource->slots[channel_id - 1] = 0;
resource->used_slots--;
}
return SUCCESS;
}
/* Creating and destroying resource */
amqp_connection_resource *connection_resource_constructor(amqp_connection_params *params, bool persistent)
{
struct timeval tv = {0};
struct timeval *tv_ptr = &tv;
char *std_datetime;
amqp_table_entry_t client_properties_entries[4];
amqp_table_t client_properties_table;
amqp_table_entry_t custom_properties_entries[2];
amqp_table_t custom_properties_table;
amqp_connection_resource *resource;
/* Allocate space for the connection resource */
resource = (amqp_connection_resource *) pecalloc(1, sizeof(amqp_connection_resource), persistent);
/* Create the connection */
resource->connection_state = amqp_new_connection();
/* Create socket object */
if (params->cacert) {
resource->socket = amqp_ssl_socket_new(resource->connection_state);
if (!resource->socket) {
zend_throw_exception(
amqp_connection_exception_class_entry,
"Socket error: could not create SSL socket.",
0
);
return NULL;
}
if (amqp_ssl_socket_set_cacert(resource->socket, params->cacert) != AMQP_STATUS_OK) {
zend_throw_exception(
amqp_connection_exception_class_entry,
"Socket error: could not set CA certificate.",
0
);
connection_resource_destructor(resource, persistent);
return NULL;
}
#if AMQP_VERSION_MAJOR * 100 + AMQP_VERSION_MINOR * 10 + AMQP_VERSION_PATCH >= 80
amqp_ssl_socket_set_verify_peer(resource->socket, params->verify);
amqp_ssl_socket_set_verify_hostname(resource->socket, params->verify);
#else
amqp_ssl_socket_set_verify(resource->socket, params->verify);
#endif
if (params->cert && params->key) {
int client_cert_result = amqp_ssl_socket_set_key(resource->socket, params->cert, params->key);
if (client_cert_result != AMQP_STATUS_OK) {
zend_throw_exception_ex(
amqp_connection_exception_class_entry,
0,
"Socket error: could not set client cert, %s",
amqp_error_string2(client_cert_result)
);
connection_resource_destructor(resource, persistent);
return NULL;
}
}
} else {
resource->socket = amqp_tcp_socket_new(resource->connection_state);
if (!resource->socket) {
zend_throw_exception(amqp_connection_exception_class_entry, "Socket error: could not create socket.", 0);
return NULL;
}
}
if (params->connect_timeout > 0) {
tv.tv_sec = (long int) params->connect_timeout;
tv.tv_usec = (long int) ((params->connect_timeout - tv.tv_sec) * 1000000);
} else {
tv_ptr = NULL;
}
/* Try to connect and verify that no error occurred */
int connection_result = amqp_socket_open_noblock(resource->socket, params->host, params->port, tv_ptr);
if (connection_result != AMQP_STATUS_OK) {
zend_throw_exception_ex(
amqp_connection_exception_class_entry,
0,
"Socket error: could not connect to host, %s",
amqp_error_string2(connection_result)
);
connection_resource_destructor(resource, persistent);
return NULL;
}
if (!php_amqp_set_resource_read_timeout(resource, params->read_timeout)) {
connection_resource_destructor(resource, persistent);
return NULL;
}
if (!php_amqp_set_resource_write_timeout(resource, params->write_timeout)) {
connection_resource_destructor(resource, persistent);
return NULL;
}
if (!php_amqp_set_resource_rpc_timeout(resource, params->rpc_timeout)) {
connection_resource_destructor(resource, persistent);
return NULL;
}
std_datetime = php_std_date(time(NULL));
client_properties_entries[0].key = amqp_cstring_bytes("type");
client_properties_entries[0].value.kind = AMQP_FIELD_KIND_UTF8;
client_properties_entries[0].value.value.bytes = amqp_cstring_bytes("php-amqp extension");
client_properties_entries[1].key = amqp_cstring_bytes("version");
client_properties_entries[1].value.kind = AMQP_FIELD_KIND_UTF8;
client_properties_entries[1].value.value.bytes = amqp_cstring_bytes(PHP_AMQP_VERSION);
client_properties_entries[2].key = amqp_cstring_bytes("connection type");
client_properties_entries[2].value.kind = AMQP_FIELD_KIND_UTF8;
client_properties_entries[2].value.value.bytes = amqp_cstring_bytes(persistent ? "persistent" : "transient");
client_properties_entries[3].key = amqp_cstring_bytes("connection started");
client_properties_entries[3].value.kind = AMQP_FIELD_KIND_UTF8;
client_properties_entries[3].value.value.bytes = amqp_cstring_bytes(std_datetime);
client_properties_table.entries = client_properties_entries;
client_properties_table.num_entries = sizeof(client_properties_entries) / sizeof(amqp_table_entry_t);
custom_properties_entries[0].key = amqp_cstring_bytes("client");
custom_properties_entries[0].value.kind = AMQP_FIELD_KIND_TABLE;
custom_properties_entries[0].value.value.table = client_properties_table;
if (params->connection_name) {
custom_properties_entries[1].key = amqp_cstring_bytes("connection_name");
custom_properties_entries[1].value.kind = AMQP_FIELD_KIND_UTF8;
custom_properties_entries[1].value.value.bytes = amqp_cstring_bytes(params->connection_name);
}
custom_properties_table.entries = custom_properties_entries;
custom_properties_table.num_entries = params->connection_name ? 2 : 1;
/* We can assume that connection established here but it is not true, real handshake goes during login */
assert(params->frame_max > 0);
amqp_rpc_reply_t res = amqp_login_with_properties(
resource->connection_state,
params->vhost,
params->channel_max,
params->frame_max,
params->heartbeat,
&custom_properties_table,
params->sasl_method,
params->login,
params->password
);
efree(std_datetime);
if (AMQP_RESPONSE_NORMAL != res.reply_type) {
char *message = NULL, *long_message = NULL;
php_amqp_connection_resource_error(res, &message, resource, 0);
spprintf(&long_message, 0, "%s", message);
zend_throw_exception(amqp_connection_exception_class_entry, long_message, PHP_AMQP_G(error_code));
efree(message);
efree(long_message);
/* https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf
*
* 2.2.4 The Connection Class:
* ... a peer that detects an error MUST close the socket without sending any further data.
*
* 4.10.2 Denial of Service Attacks:
* ... The general response to any exceptional condition in the connection negotiation is to pause that connection
* (presumably a thread) for a period of several seconds and then to close the network connection. This
* includes syntax errors, over-sized data, and failed attempts to authenticate.
*/
connection_resource_destructor(resource, persistent);
return NULL;
}
/* Allocate space for the channel slots in the ring buffer */
resource->max_slots = (amqp_channel_t) amqp_get_channel_max(resource->connection_state);
assert(resource->max_slots > 0);
resource->slots =
(amqp_channel_resource **) pecalloc(resource->max_slots + 1, sizeof(amqp_channel_object *), persistent);
resource->is_connected = '\1';
return resource;
}
ZEND_RSRC_DTOR_FUNC(amqp_connection_resource_dtor_persistent)
{
amqp_connection_resource *resource = (amqp_connection_resource *) res->ptr;
connection_resource_destructor(resource, 1);
}
ZEND_RSRC_DTOR_FUNC(amqp_connection_resource_dtor)
{
amqp_connection_resource *resource = (amqp_connection_resource *) res->ptr;
connection_resource_destructor(resource, 0);
}
static void connection_resource_destructor(amqp_connection_resource *resource, int persistent)
{
assert(resource != NULL);
#ifndef PHP_WIN32
void *old_handler;
/*
If we are trying to close the connection and the connection already closed, it will throw
SIGPIPE, which is fine, so ignore all SIGPIPES
*/
/* Start ignoring SIGPIPE */
old_handler = signal(SIGPIPE, SIG_IGN);
#endif
if (resource->parent) {
resource->parent->connection_resource = NULL;
}
if (resource->slots) {
php_amqp_prepare_for_disconnect(resource);
pefree(resource->slots, persistent);
resource->slots = NULL;
}
/* connection may be closed in case of previous failure */
if (resource->is_connected) {
amqp_connection_close(resource->connection_state, AMQP_REPLY_SUCCESS);
}
amqp_destroy_connection(resource->connection_state);
#ifndef PHP_WIN32
/* End ignoring of SIGPIPEs */
signal(SIGPIPE, old_handler);
#endif
pefree(resource, persistent);
}
void php_amqp_prepare_for_disconnect(amqp_connection_resource *resource)
{
if (resource == NULL) {
return;
}
if (resource->slots != NULL) {
/* NOTE: when we have persistent connection we do not move channels between php requests
* due to current php-amqp extension limitation in AMQPChannel where __construct == channel.open AMQP method call
* and __destruct = channel.close AMQP method call
*/
/* Clean up old memory allocations which are now invalid (new connection) */
amqp_channel_t slot;
for (slot = 0; slot < resource->max_slots; slot++) {
if (resource->slots[slot] != 0) {
php_amqp_close_channel(resource->slots[slot], 0);
}
}
}
/* If it's persistent connection do not destroy connection resource (this keep connection alive) */
if (resource->is_persistent) {
/* Cleanup buffers to reduce memory usage in idle mode */
amqp_maybe_release_buffers(resource->connection_state);
}
return;
}

File Metadata

Mime Type
text/x-c
Expires
Sun, Nov 15, 12:30 AM (16 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
111221
Default Alt Text
amqp_connection_resource.c (24 KB)

Event Timeline