/*
* Handle ESI events
*
* Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "config.h"
#include <libisns/isns.h>
#include <libisns/attrs.h>
#include "objects.h"
#include <libisns/message.h>
#include "security.h"
#include <libisns/util.h>
#include "db.h"
#define ESI_RETRANS_TIMEOUT 60
typedef struct isns_esi isns_esi_t;
typedef struct isns_esi_portal isns_esi_portal_t;
struct isns_esi {
isns_list_t esi_list;
isns_object_t * esi_object;
isns_list_t esi_portals;
unsigned int esi_update : 1;
};
struct isns_esi_portal {
isns_list_t esp_list;
isns_object_t * esp_object;
isns_portal_info_t esp_portal;
unsigned int esp_interval;
isns_portal_info_t esp_dest;
isns_socket_t * esp_socket;
unsigned int esp_retries;
unsigned int esp_timeout;
time_t esp_start;
time_t esp_next_xmit;
uint32_t esp_xid;
};
int isns_esi_enabled = 0;
static isns_server_t * isns_esi_server = NULL;
static ISNS_LIST_DECLARE(isns_esi_list);
static void isns_esi_transmit(void *);
static void isns_esi_sendto(isns_esi_t *, isns_esi_portal_t *);
static void isns_process_esi_response(uint32_t, int,
isns_simple_t *);
static void isns_esi_disconnect(isns_esi_portal_t *);
static void isns_esi_restart(isns_esi_portal_t *);
static void isns_esi_drop_portal(isns_esi_portal_t *, isns_db_t *, int);
static void isns_esi_drop_entity(isns_esi_t *, isns_db_t *, int);
static int isns_esi_update(isns_esi_t *);
static void isns_esi_schedule(int);
static void isns_esi_callback(const isns_db_event_t *, void *);
void
isns_esi_init(isns_server_t *srv)
{
if (isns_config.ic_esi_retries == 0) {
isns_debug_esi("ESI disabled by administrator\n");
} else {
unsigned int max_interval;
isns_register_callback(isns_esi_callback, NULL);
isns_esi_schedule(0);
max_interval = isns_config.ic_registration_period / 2;
if (isns_config.ic_esi_max_interval > max_interval) {
isns_warning("Max ESI interval adjusted to %u sec "
"to match registration period\n",
max_interval);
isns_config.ic_esi_max_interval = max_interval;
if (isns_config.ic_esi_min_interval > max_interval)
isns_config.ic_esi_min_interval = max_interval;
}
isns_esi_server = srv;
isns_esi_enabled = 1;
}
}
/*
* Timer callback to send out ESI messages.
*/
void
isns_esi_transmit(void *ptr)
{
isns_db_t *db = isns_esi_server->is_db;
isns_list_t *esi_pos, *esi_next;
time_t now;
isns_object_t *obj;
time_t next_timeout;
now = time(NULL);
next_timeout = now + 3600;
isns_list_foreach(&isns_esi_list, esi_pos, esi_next) {
isns_list_t *esp_pos, *esp_next;
isns_esi_t *esi = isns_list_item(isns_esi_t, esi_list, esi_pos);
if (esi->esi_update) {
esi->esi_update = 0;
if (!isns_esi_update(esi))
continue;
}
isns_list_foreach(&esi->esi_portals, esp_pos, esp_next) {
isns_esi_portal_t *esp = isns_list_item(isns_esi_portal_t,
esp_list, esp_pos);
/* Check whether the portal object still exist */
obj = esp->esp_object;
if (obj->ie_state != ISNS_OBJECT_STATE_MATURE) {
isns_esi_drop_portal(esp, db, 0);
continue;
}
if (esp->esp_next_xmit <= now) {
if (esp->esp_retries == 0) {
isns_debug_esi("No ESI response from %s - dropping\n",
isns_portal_string(&esp->esp_dest));
isns_esi_drop_portal(esp, db, 1);
continue;
}
esp->esp_retries -= 1;
esp->esp_next_xmit = now + esp->esp_timeout;
isns_esi_sendto(esi, esp);
}
if (esp->esp_next_xmit < next_timeout)
next_timeout = esp->esp_next_xmit;
}
if (isns_list_empty(&esi->esi_portals))
isns_esi_drop_entity(esi, db, 1);
}
isns_debug_esi("Next ESI message in %d seconds\n", next_timeout - now);
isns_esi_schedule(next_timeout - now);
}
/*
* Send an ESI message
*/
void
isns_esi_sendto(isns_esi_t *esi, isns_esi_portal_t *esp)
{
isns_attr_list_t attrs = ISNS_ATTR_LIST_INIT;
isns_socket_t *sock;
isns_simple_t *msg;
/* For TCP portals, kill the TCP socket every time. */
if (esp->esp_dest.proto == IPPROTO_TCP)
isns_esi_disconnect(esp);
if (esp->esp_socket == NULL) {
sock = isns_connect_to_portal(&esp->esp_dest);
if (sock == NULL)
return;
isns_socket_set_security_ctx(sock,
isns_default_security_context(0));
/* sock->is_disconnect_fatal = 1; */
esp->esp_socket = sock;
}
isns_attr_list_append_uint64(&attrs,
ISNS_TAG_TIMESTAMP,
time(NULL));
/* The following will extract the ENTITY IDENTIFIER */
isns_object_extract_keys(esi->esi_object, &attrs);
isns_portal_to_attr_list(&esp->esp_portal,
ISNS_TAG_PORTAL_IP_ADDRESS,
ISNS_TAG_PORTAL_TCP_UDP_PORT,
&attrs);
msg = isns_simple_create(ISNS_ENTITY_STATUS_INQUIRY,
NULL, &attrs);
if (msg == NULL)
return;
isns_debug_esi("*** Sending ESI message to %s (xid=0x%x); %u retries left\n",
isns_portal_string(&esp->esp_dest),
msg->is_xid, esp->esp_retries);
isns_simple_transmit(esp->esp_socket, msg,
NULL, esp->esp_timeout - 1,
isns_process_esi_response);
esp->esp_xid = msg->is_xid;
isns_simple_free(msg);
}
/*
* A new entity was added. See if it uses ESI, and create
* portals and such.
*/
static void
isns_esi_add_entity(isns_object_t *obj)
{
isns_esi_t *esi;
isns_debug_esi("Enable ESI monitoring for entity %u\n", obj->ie_index);
esi = isns_calloc(1, sizeof(*esi));
esi->esi_object = isns_object_get(obj);
esi->esi_update = 1;
isns_list_init(&esi->esi_list);
isns_list_init(&esi->esi_portals);
isns_list_append(&isns_esi_list, &esi->esi_list);
}
/*
* Given an entity, see if we can find ESI state for it.
*/
static isns_esi_t *
isns_esi_find(isns_object_t *obj)
{
isns_list_t *pos, *next;
isns_list_foreach(&isns_esi_list, pos, next) {
isns_esi_t *esi = isns_list_item(isns_esi_t, esi_list, pos);
if (esi->esi_object == obj)
return esi;
}
return NULL;
}
/*
* Update the ESI state after an entity has changed
*/
static int
isns_esi_update(isns_esi_t *esi)
{
isns_object_t *entity = esi->esi_object;
ISNS_LIST_DECLARE(hold);
isns_esi_portal_t *esp;
unsigned int i;
isns_debug_esi("Updating ESI state for entity %u\n", entity->ie_index);
isns_list_move(&hold, &esi->esi_portals);
for (i = 0; i < entity->ie_children.iol_count; ++i) {
isns_object_t *child = entity->ie_children.iol_data[i];
isns_portal_info_t esi_portal, portal_info;
uint32_t esi_interval;
isns_list_t *pos, *next;
int changed = 0;
if (!ISNS_IS_PORTAL(child))
continue;
if (!isns_portal_from_object(&portal_info,
ISNS_TAG_PORTAL_IP_ADDRESS,
ISNS_TAG_PORTAL_TCP_UDP_PORT,
child)
|| !isns_portal_from_object(&esi_portal,
ISNS_TAG_PORTAL_IP_ADDRESS,
ISNS_TAG_ESI_PORT,
child)
|| !isns_object_get_uint32(child,
ISNS_TAG_ESI_INTERVAL,
&esi_interval))
continue;
isns_list_foreach(&hold, pos, next) {
esp = isns_list_item(isns_esi_portal_t, esp_list, pos);
if (esp->esp_object == child) {
isns_debug_esi("Updating ESI state for %s\n",
isns_portal_string(&portal_info));
isns_list_del(&esp->esp_list);
goto update;
}
}
isns_debug_esi("Creating ESI state for %s\n",
isns_portal_string(&portal_info));
esp = isns_calloc(1, sizeof(*esp));
esp->esp_object = isns_object_get(child);
isns_list_init(&esp->esp_list);
changed = 1;
update:
if (!isns_portal_equal(&esp->esp_portal, &portal_info)) {
esp->esp_portal = portal_info;
changed++;
}
if (!isns_portal_equal(&esp->esp_dest, &esi_portal)) {
isns_esi_disconnect(esp);
esp->esp_dest = esi_portal;
changed++;
}
if (esp->esp_interval != esi_interval) {
esp->esp_interval = esi_interval;
changed++;
}
isns_esi_restart(esp);
isns_list_append(&esi->esi_portals, &esp->esp_list);
}
/* Destroy any old ESI portals */
while (!isns_list_empty(&hold)) {
esp = isns_list_item(isns_esi_portal_t, esp_list, hold.next);
isns_esi_drop_portal(esp, NULL, 0);
}
/* If the client explicitly unregistered all ESI portals,
* stop monitoring it but *without* destroying the entity. */
if (isns_list_empty(&esi->esi_portals)) {
isns_esi_drop_entity(esi, NULL, 0);
return 0;
}
return 1;
}
void
isns_esi_restart(isns_esi_portal_t *esp)
{
unsigned int timeo;
isns_esi_disconnect(esp);
esp->esp_start = time(NULL);
esp->esp_retries = isns_config.ic_esi_retries;
esp->esp_next_xmit = esp->esp_start + esp->esp_interval;
esp->esp_xid = 0;
timeo = esp->esp_interval / esp->esp_retries;
if (timeo == 0)
timeo = 1;
else if (timeo > ESI_RETRANS_TIMEOUT)
timeo = ESI_RETRANS_TIMEOUT;
esp->esp_timeout = timeo;
}
void
isns_esi_disconnect(isns_esi_portal_t *esp)
{
if (esp->esp_socket)
isns_socket_free(esp->esp_socket);
esp->esp_socket = NULL;
}
/*
* Generic wrapper to dropping an object
*/
static inline void
__isns_esi_drop_object(isns_db_t *db, isns_object_t *obj, unsigned int dead)
{
if (db && obj && obj->ie_state == ISNS_OBJECT_STATE_MATURE && dead)
isns_db_remove(db, obj);
isns_object_release(obj);
}
/*
* Portal did not respond in time. Drop it
*/
void
isns_esi_drop_portal(isns_esi_portal_t *esp, isns_db_t *db, int dead)
{
isns_debug_esi("ESI: dropping portal %s\n",
isns_portal_string(&esp->esp_portal));
isns_list_del(&esp->esp_list);
isns_esi_disconnect(esp);
__isns_esi_drop_object(db, esp->esp_object, dead);
isns_free(esp);
}
/*
* We ran out of ESI portals for this entity.
*/
void
isns_esi_drop_entity(isns_esi_t *esi, isns_db_t *db, int dead)
{
isns_debug_esi("ESI: dropping entity %u\n",
esi->esi_object->ie_index);
isns_list_del(&esi->esi_list);
__isns_esi_drop_object(db, esi->esi_object, dead);
while (!isns_list_empty(&esi->esi_portals)) {
isns_esi_portal_t *esp;
esp = isns_list_item(isns_esi_portal_t, esp_list,
esi->esi_portals.next);
isns_esi_drop_portal(esp, db, dead);
}
isns_free(esi);
}
/*
* When receiving an ESI response, find the portal we sent the
* original message to.
*/
static isns_esi_portal_t *
isns_esi_get_msg_portal(uint32_t xid, isns_esi_t **esip)
{
isns_list_t *esi_pos, *esi_next;
isns_list_foreach(&isns_esi_list, esi_pos, esi_next) {
isns_esi_t *esi = isns_list_item(isns_esi_t, esi_list, esi_pos);
isns_list_t *esp_pos, *esp_next;
isns_list_foreach(&esi->esi_portals, esp_pos, esp_next) {
isns_esi_portal_t *esp = isns_list_item(isns_esi_portal_t,
esp_list, esp_pos);
if (esp->esp_xid == xid) {
*esip = esi;
return esp;
}
}
}
return NULL;
}
/*
* Handle incoming ESI request
*/
int
isns_process_esi(isns_server_t *srv, isns_simple_t *call, isns_simple_t **reply)
{
const isns_attr_list_t *attrs = &call->is_message_attrs;
isns_object_t *portal = NULL;
/* We just echo back the attributes sent to us by the server,
* without further checking. */
*reply = isns_simple_create(ISNS_ENTITY_STATUS_INQUIRY,
srv->is_source, attrs);
/* Look up the portal and update its mtime.
* This can help the application find out if a portal has
* seen ESIs recently, and react.
*/
if (srv->is_db && attrs->ial_count == 4) {
const isns_attr_t *addr_attr, *port_attr;
addr_attr = attrs->ial_data[2];
port_attr = attrs->ial_data[3];
if (addr_attr->ia_tag_id == ISNS_TAG_PORTAL_IP_ADDRESS
&& port_attr->ia_tag_id == ISNS_TAG_PORTAL_TCP_UDP_PORT) {
isns_attr_list_t key;
key.ial_count = 2;
key.ial_data = attrs->ial_data + 2;
portal = isns_db_lookup(srv->is_db,
&isns_portal_template,
&key);
}
if (portal)
portal->ie_mtime = time(NULL);
}
return ISNS_SUCCESS;
}
void
isns_process_esi_response(uint32_t xid, int status, isns_simple_t *msg)
{
isns_portal_info_t portal_info;
isns_esi_portal_t *esp;
isns_esi_t *esi;
if (msg == NULL) {
isns_debug_esi("ESI call 0x%x timed out\n", xid);
return;
}
/* FIXME: As a matter of security, we should probably
* verify that the ESI response originated from the
* portal we sent it to; or at least that it was authenticated
* by the client we think we're talking to. */
/* Get the portal */
if (!isns_portal_from_attr_list(&portal_info,
ISNS_TAG_PORTAL_IP_ADDRESS,
ISNS_TAG_PORTAL_TCP_UDP_PORT,
&msg->is_message_attrs)) {
isns_debug_esi("Ignoring unintelligible ESI response\n");
return;
}
if (!(esp = isns_esi_get_msg_portal(xid, &esi))) {
isns_debug_esi("Ignoring unmatched ESI reply\n");
return;
}
if (!isns_portal_equal(&esp->esp_portal, &portal_info)) {
isns_warning("Faked ESI response for portal %s\n",
isns_portal_string(&portal_info));
return;
}
isns_debug_esi("Good ESI response from %s\n",
isns_portal_string(&portal_info));
isns_esi_restart(esp);
/* Refresh the entity's registration timestamp */
isns_object_set_uint64(esi->esi_object,
ISNS_TAG_TIMESTAMP,
time(NULL));
isns_db_sync(isns_esi_server->is_db);
}
/*
* Helper function to schedule the next timeout
*/
static void
isns_esi_schedule(int timeout)
{
isns_cancel_timer(isns_esi_transmit, NULL);
isns_add_oneshot_timer(timeout, isns_esi_transmit, NULL);
}
/*
* Register an entity for ESI monitoring.
* This is called when reloading the database.
*/
void
isns_esi_register(isns_object_t *obj)
{
if (!isns_esi_find(obj))
isns_esi_add_entity(obj);
/* We do not call esi_schedule(0) here; that happens in
* isns_esi_init already. */
}
/*
* This callback is invoked whenever an object is added/removed/modified.
* We use this to keep track of ESI portals and such.
*/
void
isns_esi_callback(const isns_db_event_t *ev, void *ptr)
{
isns_object_t *obj, *entity;
isns_esi_t *esi;
uint32_t event;
obj = ev->ie_object;
event = ev->ie_bits;
if (obj->ie_flags & ISNS_OBJECT_PRIVATE)
return;
isns_debug_esi("isns_esi_callback(%p, 0x%x)\n", obj, event);
if (ISNS_IS_ENTITY(obj)
&& (event & ISNS_SCN_OBJECT_ADDED_MASK)) {
if (!isns_esi_find(obj))
isns_esi_add_entity(obj);
/* Schedule an immediate ESI timer run */
isns_esi_schedule(0);
return;
}
if (!(entity = isns_object_get_entity(obj)))
return;
esi = isns_esi_find(entity);
if (esi != NULL)
esi->esi_update = 1;
/* Schedule an immediate ESI timer run */
isns_esi_schedule(0);
}