/*
* Handle DD registration/deregistration
*
* Discovery domains are weird, even in the context of
* iSNS. For one thing, all other objects have unique
* attributes; DDs attributes can appear several times.
* They should really have made each DD member an object
* in its own right.
*
* Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
*/
#include <stdlib.h>
#include <string.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 DD_DEBUG
enum {
ISNS_DD_MEMBER_ISCSI_NODE = 1,
ISNS_DD_MEMBER_IFCP_NODE,
ISNS_DD_MEMBER_PORTAL,
};
/* Must be zero/one: */
enum {
NOTIFY_MEMBER_ADDED = 0,
NOTIFY_MEMBER_REMOVED = 1
};
typedef struct isns_dd isns_dd_t;
typedef struct isns_dd_list isns_dd_list_t;
typedef struct isns_dd_member isns_dd_member_t;
struct isns_dd {
uint32_t dd_id;
char * dd_name;
uint32_t dd_features;
isns_dd_member_t * dd_members;
unsigned int dd_inserted : 1;
isns_object_t * dd_object;
};
struct isns_dd_member {
isns_dd_member_t * ddm_next;
unsigned int ddm_type;
isns_object_ref_t ddm_object;
unsigned int ddm_added : 1;
union {
uint32_t ddm_index;
/* Index must be first in all structs below.
* Yeah, I know. Aliasing is bad. */
struct isns_dd_portal {
uint32_t index;
isns_portal_info_t info;
} ddm_portal;
struct isns_dd_iscsi_node {
uint32_t index;
char * name;
} ddm_iscsi_node;
struct isns_dd_ifcp_node {
uint32_t index;
char * name;
} ddm_ifcp_node;
};
};
struct isns_dd_list {
unsigned int ddl_count;
isns_dd_t ** ddl_data;
};
/*
* List of all discovery domains.
* This duplicates the DD information from the database,
* but unfortunately this can't be helped - we need to
* have fast algorithms to compute the membership of a
* node, and the relative visibility of two nodes.
*/
static int isns_dd_list_initialized = 0;
static isns_dd_list_t isns_dd_list;
static uint32_t isns_dd_next_id = 1;
static isns_dd_t * isns_dd_alloc(void);
static isns_dd_t * isns_dd_clone(const isns_dd_t *);
static void isns_dd_release(isns_dd_t *);
static int isns_dd_parse_attrs(isns_dd_t *,
isns_db_t *, const isns_attr_list_t *,
const isns_dd_t *, int);
static int isns_dd_remove_members(isns_dd_t *,
isns_db_t *,
isns_dd_t *);
static void isns_dd_notify(const isns_dd_t *,
isns_dd_member_t *,
isns_dd_member_t *,
int);
static void isns_dd_add_members(isns_dd_t *,
isns_db_t *,
isns_dd_t *);
static void isns_dd_store(isns_db_t *, const isns_dd_t *, int);
static void isns_dd_destroy(isns_db_t *, isns_dd_t *);
static void isns_dd_insert(isns_dd_t *);
static isns_dd_t * isns_dd_by_id(uint32_t);
static isns_dd_t * isns_dd_by_name(const char *);
static isns_dd_member_t * isns_dd_create_member(isns_object_t *);
static inline void isns_dd_member_free(isns_dd_member_t *);
static int isns_dd_remove_member(isns_dd_t *, isns_object_t *);
static void isns_dd_list_resize(isns_dd_list_t *, unsigned int);
static void isns_dd_list_insert(isns_dd_list_t *, isns_dd_t *);
static void isns_dd_list_remove(isns_dd_list_t *, isns_dd_t *);
static isns_object_t * isns_dd_get_member_object(isns_db_t *,
const isns_attr_t *, const isns_attr_t *,
int);
/*
* Create DDReg messages
*/
isns_simple_t *
isns_create_dd_registration(isns_client_t *clnt, const isns_attr_list_t *attrs)
{
isns_simple_t *msg;
isns_attr_t *id_attr;
msg = isns_simple_create(ISNS_DD_REGISTER, clnt->ic_source, NULL);
if (msg == NULL)
return NULL;
/* If the caller specified a DD_ID, use it in the
* message key. */
if (isns_attr_list_get_attr(attrs, ISNS_TAG_DD_ID, &id_attr))
isns_attr_list_append_attr(&msg->is_message_attrs, id_attr);
isns_attr_list_copy(&msg->is_operating_attrs, attrs);
return msg;
}
isns_simple_t *
isns_create_dd_deregistration(isns_client_t *clnt,
uint32_t dd_id, const isns_attr_list_t *attrs)
{
isns_simple_t *msg;
msg = isns_simple_create(ISNS_DD_DEREGISTER, clnt->ic_source, NULL);
if (msg == NULL)
return NULL;
isns_attr_list_append_uint32(&msg->is_message_attrs,
ISNS_TAG_DD_ID, dd_id);
isns_attr_list_copy(&msg->is_operating_attrs, attrs);
return msg;
}
/*
* Process a DD registration
*/
int
isns_process_dd_registration(isns_server_t *srv, isns_simple_t *call, isns_simple_t **result)
{
isns_simple_t *reply = NULL;
isns_attr_list_t *keys = &call->is_message_attrs;
isns_attr_list_t *attrs = &call->is_operating_attrs;
isns_db_t *db = srv->is_db;
isns_dd_t *dd = NULL, *temp_dd = NULL;
isns_attr_t *attr;
uint32_t id = 0;
int status;
/*
* 5.6.5.9.
* The Message Key, if used, contains the DD_ID of the Discovery
* Domain to be registered. If the Message Key contains a DD_ID
* of an existing DD entry in the iSNS database, then the DDReg
* message SHALL attempt to update the existing entry. If the
* DD_ID in the Message Key (if used) does not match an existing
* DD entry, then the iSNS server SHALL reject the DDReg message
* with a status code of 3 (Invalid Registration).
*/
switch (keys->ial_count) {
case 0:
/* Security: check if the client is allowed to
* create a discovery domain */
if (!isns_policy_validate_object_creation(call->is_policy,
call->is_source,
&isns_dd_template,
keys, attrs,
call->is_function))
goto unauthorized;
break;
case 1:
attr = keys->ial_data[0];
if (attr->ia_tag_id != ISNS_TAG_DD_ID)
goto reject;
if (ISNS_ATTR_IS_NIL(attr))
break;
if (!ISNS_ATTR_IS_UINT32(attr))
goto reject;
id = attr->ia_value.iv_uint32;
if (id == 0)
goto reject;
dd = isns_dd_by_id(id);
if (dd == NULL) {
isns_debug_state("DDReg for unknown ID=%u\n", id);
goto reject;
}
/* Security: check if the client is allowed to
* mess with this DD. */
isns_assert(dd->dd_object);
if (!isns_policy_validate_object_update(call->is_policy,
call->is_source,
dd->dd_object, attrs,
call->is_function))
goto unauthorized;
break;
default:
goto reject;
}
temp_dd = isns_dd_alloc();
/* Parse the attributes and build a DD object. */
status = isns_dd_parse_attrs(temp_dd, db, attrs, dd, 1);
if (status != ISNS_SUCCESS)
goto out;
if (dd == NULL) {
/* Create the DD, and copy the general information
* such asn features and symbolic name from temp_dd */
dd = isns_dd_clone(temp_dd);
/* Don't assign the attrs to the DD right away.
* First and foremost, they may be unsorted. Second,
* we really want to hand-pick through them due to
* the weird semantics mandated by the RFC. */
dd->dd_object = isns_create_object(&isns_dd_template, NULL, NULL);
if (dd->dd_object == NULL)
goto reject;
/* Insert new domain into database */
isns_db_insert(db, dd->dd_object);
/* Add it to the internal list. Assign DD_ID and
* symbolic name if none were given.
*/
isns_dd_insert(dd);
} else {
if (!dd->dd_id)
dd->dd_id = temp_dd->dd_id;
dd->dd_features = temp_dd->dd_features;
isns_assign_string(&dd->dd_name, temp_dd->dd_name);
}
/* Send notifications. This must be done before merging
* the list of new members into the DD.
*/
isns_dd_notify(dd, dd->dd_members, temp_dd->dd_members,
NOTIFY_MEMBER_ADDED);
/* Update the DD */
isns_dd_add_members(dd, db, temp_dd);
/* And add it to the database. */
isns_dd_store(db, dd, 0);
reply = isns_simple_create(ISNS_DD_REGISTER, srv->is_source, NULL);
isns_object_extract_all(dd->dd_object, &reply->is_operating_attrs);
status = ISNS_SUCCESS;
out:
isns_dd_release(temp_dd);
isns_dd_release(dd);
*result = reply;
return status;
reject:
status = ISNS_INVALID_REGISTRATION;
goto out;
unauthorized:
status = ISNS_SOURCE_UNAUTHORIZED;
goto out;
}
/*
* Process a DD deregistration
*/
int
isns_process_dd_deregistration(isns_server_t *srv, isns_simple_t *call, isns_simple_t **result)
{
isns_simple_t *reply = NULL;
isns_attr_list_t *keys = &call->is_message_attrs;
isns_attr_list_t *attrs = &call->is_operating_attrs;
isns_db_t *db = srv->is_db;
isns_dd_t *dd = NULL, *temp_dd = NULL;
isns_attr_t *attr;
uint32_t id = 0;
int status;
/*
* 5.6.5.10.
* The Message Key Attribute for a DDDereg message is the DD
* ID for the Discovery Domain being removed or having members
* removed.
*/
if (keys->ial_count != 1)
goto reject;
attr = keys->ial_data[0];
if (attr->ia_tag_id != ISNS_TAG_DD_ID
|| ISNS_ATTR_IS_NIL(attr)
|| !ISNS_ATTR_IS_UINT32(attr))
goto reject;
id = attr->ia_value.iv_uint32;
if (id == 0)
goto reject;
dd = isns_dd_by_id(id);
if (dd == NULL)
goto reject;
/* Security: check if the client is permitted to
* modify the DD object.
*/
if (!isns_policy_validate_object_update(call->is_policy,
call->is_source,
dd->dd_object, attrs,
call->is_function))
goto unauthorized;
/*
* 5.6.5.10.
* If the DD ID matches an existing DD and there are
* no Operating Attributes, then the DD SHALL be removed and a
* success Status Code returned. Any existing members of that
* DD SHALL remain in the iSNS database without membership in
* the just-removed DD.
*/
if (attrs->ial_count == 0) {
isns_dd_member_t *mp;
/* Zap the membership bit */
for (mp = dd->dd_members; mp; mp = mp->ddm_next) {
isns_object_t *obj = mp->ddm_object.obj;
isns_object_clear_membership(obj, dd->dd_id);
}
/* Notify all DD members that they will lose the other
* nodes. */
isns_dd_notify(dd, NULL, dd->dd_members, NOTIFY_MEMBER_REMOVED);
isns_dd_destroy(db, dd);
} else {
/* Parse the attributes and build a temporary DD object. */
temp_dd = isns_dd_alloc();
status = isns_dd_parse_attrs(temp_dd, db, attrs, dd, 0);
if (status != ISNS_SUCCESS)
goto out;
/* Update the DD object */
status = isns_dd_remove_members(dd, db, temp_dd);
if (status != ISNS_SUCCESS)
goto out;
/* Send notifications. This must be done before
* updating the DD.
*/
isns_dd_notify(dd, dd->dd_members, temp_dd->dd_members,
NOTIFY_MEMBER_REMOVED);
/* Store it in the database. */
isns_dd_store(db, dd, 1);
}
reply = isns_simple_create(ISNS_DD_DEREGISTER, srv->is_source, NULL);
status = ISNS_SUCCESS;
out:
isns_dd_release(temp_dd);
isns_dd_release(dd);
*result = reply;
return status;
reject:
status = ISNS_INVALID_DEREGISTRATION;
goto out;
unauthorized:
status = ISNS_SOURCE_UNAUTHORIZED;
goto out;
}
static isns_dd_t *
isns_dd_alloc(void)
{
return isns_calloc(1, sizeof(isns_dd_t));
}
/*
* Allocate a clone of the orig_dd, but without
* copying the members.
*/
static isns_dd_t *
isns_dd_clone(const isns_dd_t *orig_dd)
{
isns_dd_t *dd;
dd = isns_dd_alloc();
dd->dd_id = orig_dd->dd_id;
dd->dd_features = orig_dd->dd_features;
dd->dd_object = isns_object_get(orig_dd->dd_object);
isns_assign_string(&dd->dd_name, orig_dd->dd_name);
return dd;
}
static void
isns_dd_release(isns_dd_t *dd)
{
isns_dd_member_t *member;
if (dd == NULL || dd->dd_inserted)
return;
while ((member = dd->dd_members) != NULL) {
dd->dd_members = member->ddm_next;
isns_dd_member_free(member);
}
if (dd->dd_object)
isns_object_release(dd->dd_object);
isns_free(dd->dd_name);
isns_free(dd);
}
static isns_dd_member_t *
isns_dd_create_member(isns_object_t *obj)
{
isns_dd_member_t *new;
new = isns_calloc(1, sizeof(*new));
new->ddm_added = 1;
if (ISNS_IS_ISCSI_NODE(obj))
new->ddm_type = ISNS_DD_MEMBER_ISCSI_NODE;
else if (ISNS_IS_PORTAL(obj))
new->ddm_type = ISNS_DD_MEMBER_PORTAL;
else if (ISNS_IS_FC_NODE(obj))
new->ddm_type = ISNS_DD_MEMBER_IFCP_NODE;
else {
isns_free(new);
return NULL;
}
isns_object_reference_set(&new->ddm_object, obj);
return new;
}
static inline void
isns_dd_member_free(isns_dd_member_t *member)
{
switch (member->ddm_type) {
case ISNS_DD_MEMBER_ISCSI_NODE:
isns_free(member->ddm_iscsi_node.name);
break;
case ISNS_DD_MEMBER_IFCP_NODE:
isns_free(member->ddm_ifcp_node.name);
break;
}
isns_object_reference_drop(&member->ddm_object);
isns_free(member);
}
void
isns_dd_get_members(uint32_t dd_id, isns_object_list_t *list, int active_only)
{
isns_dd_t *dd;
isns_dd_member_t *mp;
dd = isns_dd_by_id(dd_id);
if (dd == NULL)
return;
for (mp = dd->dd_members; mp; mp = mp->ddm_next) {
isns_object_t *obj = mp->ddm_object.obj;
if (active_only
&& obj->ie_state != ISNS_OBJECT_STATE_MATURE)
continue;
isns_object_list_append(list, obj);
}
}
/*
* Helper function to remove a member referencing the given object
*/
static int
isns_dd_remove_member(isns_dd_t *dd, isns_object_t *obj)
{
isns_dd_member_t *mp, **pos;
pos = &dd->dd_members;
while ((mp = *pos) != NULL) {
if (mp->ddm_object.obj == obj) {
*pos = mp->ddm_next;
isns_dd_member_free(mp);
return 1;
} else {
pos = &mp->ddm_next;
}
}
return 0;
}
static void
isns_dd_insert(isns_dd_t *dd)
{
if (dd->dd_inserted)
return;
if (dd->dd_id == 0) {
uint32_t id = isns_dd_next_id;
unsigned int i;
for (i = 0; i < isns_dd_list.ddl_count; ++i) {
isns_dd_t *cur = isns_dd_list.ddl_data[i];
if (cur->dd_id > id)
break;
if (cur->dd_id == id)
++id;
}
isns_debug_state("Allocated new DD_ID %d\n", id);
dd->dd_id = id;
isns_dd_next_id = id + 1;
}
/*
* When creating a new DD, if the DD_Symbolic_Name is
* not included in the Operating Attributes, or if it
* is included with a zero-length TLV, then the iSNS
* server SHALL provide a unique DD_Symbolic_Name value
* for the created DD. The assigned DD_Symbolic_Name
* value SHALL be returned in the DDRegRsp message.
*/
if (dd->dd_name == NULL) {
char namebuf[64];
snprintf(namebuf, sizeof(namebuf), "isns.dd%u", dd->dd_id);
isns_assign_string(&dd->dd_name, namebuf);
}
isns_dd_list_insert(&isns_dd_list, dd);
dd->dd_inserted = 1;
#ifdef DD_DEBUG
/* Safety first - make sure domains are sorted by DD_ID */
{
unsigned int i, prev_id = 0;
for (i = 0; i < isns_dd_list.ddl_count; ++i) {
isns_dd_t *cur = isns_dd_list.ddl_data[i];
isns_assert(cur->dd_id > prev_id);
prev_id = cur->dd_id;
}
}
#endif
}
/*
* Resize the DD list
*/
#define LIST_SIZE(n) (((n) + 15) & ~15)
void
isns_dd_list_resize(isns_dd_list_t *list, unsigned int last_index)
{
unsigned int new_size;
isns_dd_t **new_data;
new_size = LIST_SIZE(last_index + 1);
if (new_size < list->ddl_count)
return;
/* We don't use realloc here because we need
* to zero the new pointers anyway. */
new_data = isns_calloc(new_size, sizeof(void *));
isns_assert(new_data);
memcpy(new_data, list->ddl_data,
list->ddl_count * sizeof(void *));
isns_free(list->ddl_data);
list->ddl_data = new_data;
list->ddl_count = last_index + 1;
}
/*
* Find the insert position for a given DD ID.
* returns true iff the DD was found in the list.
*/
static int
__isns_dd_list_find_pos(isns_dd_list_t *list, unsigned int id,
unsigned int *where)
{
unsigned int hi, lo, md;
lo = 0;
hi = list->ddl_count;
/* binary search */
while (lo < hi) {
isns_dd_t *cur;
md = (lo + hi) / 2;
cur = list->ddl_data[md];
if (id == cur->dd_id) {
*where = md;
return 1;
}
if (id < cur->dd_id) {
hi = md;
} else {
lo = md + 1;
}
}
*where = hi;
return 0;
}
/*
* In-order insert
*/
static void
isns_dd_list_insert(isns_dd_list_t *list, isns_dd_t *dd)
{
unsigned int pos;
if (__isns_dd_list_find_pos(list, dd->dd_id, &pos)) {
isns_error("Internal error in %s: DD already listed\n",
__FUNCTION__);
return;
}
isns_dd_list_resize(list, list->ddl_count);
/* Shift the tail of the list to make room for new entry. */
memmove(list->ddl_data + pos + 1,
list->ddl_data + pos,
(list->ddl_count - pos - 1) * sizeof(void *));
list->ddl_data[pos] = dd;
}
/*
* Remove DD from list
*/
void
isns_dd_list_remove(isns_dd_list_t *list, isns_dd_t *dd)
{
unsigned int pos;
if (!__isns_dd_list_find_pos(list, dd->dd_id, &pos))
return;
/* Shift the tail of the list */
memmove(list->ddl_data + pos,
list->ddl_data + pos + 1,
(list->ddl_count - pos - 1) * sizeof(void *));
list->ddl_count -= 1;
}
isns_dd_t *
isns_dd_by_id(uint32_t id)
{
unsigned int i;
for (i = 0; i < isns_dd_list.ddl_count; ++i) {
isns_dd_t *dd = isns_dd_list.ddl_data[i];
if (dd && dd->dd_id == id)
return dd;
}
return NULL;
}
static isns_dd_t *
isns_dd_by_name(const char *name)
{
unsigned int i;
for (i = 0; i < isns_dd_list.ddl_count; ++i) {
isns_dd_t *dd = isns_dd_list.ddl_data[i];
if (dd && !strcmp(dd->dd_name, name))
return dd;
}
return NULL;
}
/*
* Validate the operating attributes, which is surprisingly
* tedious for DDs. It appears as if the whole DD/DDset
* stuff has been slapped onto iSNS as an afterthought.
*
* DDReg has some funky rules about how eg iSCSI nodes
* can be identified by either name or index, and how they
* relate to each other. Unfortunately, the RFC is very vague
* in describing how to treat DDReg message that mix these
* two types of identification, except by saying they
* need to be consistent.
*/
static int
isns_dd_parse_attrs(isns_dd_t *dd, isns_db_t *db,
const isns_attr_list_t *attrs,
const isns_dd_t *orig_dd,
int is_registration)
{
isns_dd_member_t **tail;
const isns_dd_t *conflict;
unsigned int i;
int rv = ISNS_SUCCESS;
if (orig_dd) {
dd->dd_id = orig_dd->dd_id;
dd->dd_features = orig_dd->dd_features;
isns_assign_string(&dd->dd_name, orig_dd->dd_name);
}
isns_assert(dd->dd_members == NULL);
tail = &dd->dd_members;
for (i = 0; i < attrs->ial_count; ++i) {
isns_object_t *obj = NULL;
isns_attr_t *attr, *next = NULL;
const char *name;
uint32_t id;
attr = attrs->ial_data[i];
if (!isns_object_attr_valid(&isns_dd_template, attr->ia_tag_id))
return ISNS_INVALID_REGISTRATION;
switch (attr->ia_tag_id) {
case ISNS_TAG_DD_ID:
/* Ignore this attribute in DDDereg messages */
if (!is_registration)
continue;
/*
* 5.6.5.9.
* A DDReg message with no Message Key SHALL result
* in the attempted creation of a new Discovery Domain
* (DD). If the DD_ID attribute (with non-zero length)
* is included among the Operating Attributes in the
* DDReg message, then the new Discovery Domain SHALL be
* assigned the value contained in that DD_ID attribute.
*
* If the DD_ID is included in both the Message
* Key and Operating Attributes, then the DD_ID
* value in the Message Key MUST be the same as
* the DD_ID value in the Operating Attributes.
*
* Implementer's note: It's not clear why the standard
* makes an exception for the DD_ID, while all other
* index attributes are read-only.
*/
if (ISNS_ATTR_IS_NIL(attr))
break;
id = attr->ia_value.iv_uint32;
if (dd->dd_id != 0) {
if (dd->dd_id != id)
goto invalid;
} else if ((conflict = isns_dd_by_id(id)) != NULL) {
isns_debug_state("DDReg: requested ID %d "
"clashes with existing DD (%s)\n",
id, conflict->dd_name);
goto invalid;
}
dd->dd_id = id;
break;
case ISNS_TAG_DD_SYMBOLIC_NAME:
/* Ignore this attribute in DDDereg messages */
if (!is_registration)
continue;
/*
* If the DD_Symbolic_Name is an operating
* attribute and its value is unique (i.e., it
* does not match the registered DD_Symbolic_Name
* for another DD), then the value SHALL be stored
* in the iSNS database as the DD_Symbolic_Name
* for the specified Discovery Domain. If the
* value for the DD_Symbolic_Name is not unique,
* then the iSNS server SHALL reject the attempted
* DD registration with a status code of 3
* (Invalid Registration).
*/
if (ISNS_ATTR_IS_NIL(attr))
break;
name = attr->ia_value.iv_string;
if (dd->dd_name && strcmp(name, dd->dd_name)) {
isns_debug_state("DDReg: symbolic name conflict: "
"id=%d name=%s requested=%s\n",
dd->dd_id, dd->dd_name, name);
goto invalid;
}
if (dd->dd_name)
break;
if ((conflict = isns_dd_by_name(name)) != NULL) {
isns_debug_state("DDReg: requested symbolic name (%s) "
"clashes with existing DD (id=%d)\n",
name, conflict->dd_id);
goto invalid;
}
isns_assign_string(&dd->dd_name, name);
break;
case ISNS_TAG_DD_FEATURES:
/* Ignore this attribute in DDDereg messages */
if (!is_registration)
continue;
/*
* When creating a new DD, if the DD_Features
* attribute is not included in the Operating
* Attributes, then the iSNS server SHALL assign
* the default value. The default value for
* DD_Features is 0.
*/
if (ISNS_ATTR_IS_UINT32(attr))
dd->dd_features = attr->ia_value.iv_uint32;
break;
case ISNS_TAG_DD_MEMBER_PORTAL_IP_ADDR:
/* portal address must be followed by port */
if (i + 1 >= attrs->ial_count)
goto invalid;
next = attrs->ial_data[i + 1];
if (next->ia_tag_id != ISNS_TAG_DD_MEMBER_PORTAL_TCP_UDP_PORT)
goto invalid;
i += 1;
/* fallthru to normal case */
case ISNS_TAG_DD_MEMBER_PORTAL_INDEX:
case ISNS_TAG_DD_MEMBER_ISCSI_INDEX:
case ISNS_TAG_DD_MEMBER_ISCSI_NAME:
case ISNS_TAG_DD_MEMBER_FC_PORT_NAME:
if (ISNS_ATTR_IS_NIL(attr))
goto invalid;
obj = isns_dd_get_member_object(db,
attr, next,
is_registration);
/* For a DD deregistration, it's okay if the
* object does not exist. */
if (obj == NULL && is_registration)
goto invalid;
break;
invalid:
rv = ISNS_INVALID_REGISTRATION;
continue;
}
if (obj) {
if (is_registration
&& isns_object_test_membership(obj, dd->dd_id)) {
/* Duplicates are ignored */
isns_debug_state("Ignoring duplicate DD registration "
"for %s %u\n",
obj->ie_template->iot_name,
obj->ie_index);
} else {
/* This just adds the member to the temporary DD object,
* without changing any state in the database. */
isns_dd_member_t *new;
new = isns_dd_create_member(obj);
if (new) {
*tail = new;
tail = &new->ddm_next;
}
/* mark this object as a member of this DD */
isns_object_mark_membership(obj, dd->dd_id);
}
isns_object_release(obj);
}
}
return rv;
}
/*
* Helper function: extract live nodes from the DD member list
*/
static inline void
isns_dd_get_member_nodes(isns_dd_member_t *members, isns_object_list_t *result)
{
isns_dd_member_t *mp;
/* Extract iSCSI nodes from both list. */
for (mp = members; mp; mp = mp->ddm_next) {
isns_object_t *obj = mp->ddm_object.obj;
if (ISNS_IS_ISCSI_NODE(obj)
&& obj->ie_state == ISNS_OBJECT_STATE_MATURE)
isns_object_list_append(result, obj);
}
}
void
isns_dd_notify(const isns_dd_t *dd, isns_dd_member_t *unchanged,
isns_dd_member_t *changed, int removed)
{
isns_object_list_t dd_objects = ISNS_OBJECT_LIST_INIT;
isns_object_list_t changed_objects = ISNS_OBJECT_LIST_INIT;
unsigned int i, j, event;
/* Extract iSCSI nodes from both list. */
isns_dd_get_member_nodes(unchanged, &dd_objects);
isns_dd_get_member_nodes(changed, &changed_objects);
/* Send a management SCN multicast to all
* control nodes that care. */
event = removed? ISNS_SCN_DD_MEMBER_REMOVED_MASK : ISNS_SCN_DD_MEMBER_ADDED_MASK;
for (i = 0; i < changed_objects.iol_count; ++i) {
isns_object_t *obj = changed_objects.iol_data[i];
isns_object_event(obj,
event | ISNS_SCN_MANAGEMENT_REGISTRATION_MASK,
dd->dd_object);
}
#ifdef notagoodidea
/* Not sure - it may be good to send OBJECT ADDED/REMOVED instead
* of the DD membership messages. However, right now the SCN code
* will nuke all SCN registrations for a node when it sees a
* REMOVE event for it.
*/
event = removed? ISNS_SCN_OBJECT_REMOVED_MASK : ISNS_SCN_OBJECT_ADDED_MASK;
#endif
/* If we added an iscsi node, loop over all members
* and send unicast events to each iscsi node,
* informing them that a new member has been added/removed.
*/
for (j = 0; j < changed_objects.iol_count; ++j) {
isns_object_t *changed = changed_objects.iol_data[j];
for (i = 0; i < dd_objects.iol_count; ++i) {
isns_object_t *obj = dd_objects.iol_data[i];
/* For member removal, do not send notifications
* if the two nodes are still visible to each
* other through a different discovery domain */
if (removed && isns_object_test_visibility(obj, changed))
continue;
/* Inform the old node that the new node was
* added/removed. */
isns_unicast_event(obj, changed, event, NULL);
/* Inform the new node that the old node became
* (in)accessible to it. */
isns_unicast_event(changed, obj, event, NULL);
}
/* Finally, inform each changed node of the other
* DD members that became (in)accessible to it. */
for (i = 0; i < changed_objects.iol_count; ++i) {
isns_object_t *obj = changed_objects.iol_data[i];
if (obj == changed)
continue;
if (removed && isns_object_test_visibility(obj, changed))
continue;
isns_unicast_event(changed, obj, event, NULL);
}
}
}
void
isns_dd_add_members(isns_dd_t *dd, isns_db_t *db, isns_dd_t *new_dd)
{
isns_dd_member_t *mp, **tail;
for (mp = new_dd->dd_members; mp; mp = mp->ddm_next) {
const char *node_name;
isns_object_t *obj = mp->ddm_object.obj;
/*
* If the Operating Attributes contain a DD
* Member iSCSI Name value for a Storage Node
* that is currently not registered in the iSNS
* database, then the iSNS server MUST allocate an
* unused iSCSI Node Index for that Storage Node.
* The assigned iSCSI Node Index SHALL be returned
* in the DDRegRsp message as the DD Member iSCSI
* Node Index. The allocated iSCSI Node Index
* value SHALL be assigned to the Storage Node
* if and when it registers in the iSNS database.
* [And likewise for portals]
*/
if (obj->ie_index == 0)
isns_db_insert_limbo(db, obj);
mp->ddm_index = obj->ie_index;
/* Record the fact that the object is a member of
this DD */
isns_object_mark_membership(obj, dd->dd_id);
switch (mp->ddm_type) {
case ISNS_DD_MEMBER_ISCSI_NODE:
if (isns_object_get_string(obj, ISNS_TAG_ISCSI_NAME, &node_name))
isns_assign_string(&mp->ddm_iscsi_node.name, node_name);
break;
case ISNS_DD_MEMBER_IFCP_NODE:
if (isns_object_get_string(obj, ISNS_TAG_FC_PORT_NAME_WWPN, &node_name))
isns_assign_string(&mp->ddm_ifcp_node.name, node_name);
break;
case ISNS_DD_MEMBER_PORTAL:
isns_portal_from_object(&mp->ddm_portal.info,
ISNS_TAG_PORTAL_IP_ADDRESS,
ISNS_TAG_PORTAL_TCP_UDP_PORT,
obj);
break;
}
}
/* Find the tail of the DD member list */
tail = &dd->dd_members;
while ((mp = *tail) != NULL)
tail = &mp->ddm_next;
/* Append the new list of members */
*tail = new_dd->dd_members;
new_dd->dd_members = NULL;
}
/*
* Remove members from a DD
*/
int
isns_dd_remove_members(isns_dd_t *dd, isns_db_t *db, isns_dd_t *temp_dd)
{
isns_dd_member_t *mp;
for (mp = temp_dd->dd_members; mp; mp = mp->ddm_next) {
isns_object_t *obj = mp->ddm_object.obj;
/* Clear the membership bit. If the object wasn't in this
* DD to begin with, bail out right away. */
if (!isns_object_clear_membership(obj, dd->dd_id)) {
isns_debug_state("DD dereg: object %d is not in this DD\n",
obj->ie_index);
continue;
}
if (!isns_dd_remove_member(dd, obj))
isns_error("%s: DD member not found in internal list\n",
__FUNCTION__);
}
return ISNS_SUCCESS;
}
void
isns_dd_store(isns_db_t *db, const isns_dd_t *dd, int rewrite)
{
isns_object_t *obj = dd->dd_object;
isns_dd_member_t *member;
if (rewrite)
isns_object_prune_attrs(obj);
isns_object_set_uint32(obj, ISNS_TAG_DD_ID, dd->dd_id);
isns_object_set_string(obj, ISNS_TAG_DD_SYMBOLIC_NAME, dd->dd_name);
isns_object_set_uint32(obj, ISNS_TAG_DD_FEATURES, dd->dd_features);
for (member = dd->dd_members; member; member = member->ddm_next) {
struct isns_dd_iscsi_node *node;
struct isns_dd_portal *portal;
if (!member->ddm_added && !rewrite)
continue;
switch (member->ddm_type) {
case ISNS_DD_MEMBER_ISCSI_NODE:
node = &member->ddm_iscsi_node;
isns_object_set_uint32(obj,
ISNS_TAG_DD_MEMBER_ISCSI_INDEX,
node->index);
if (node->name)
isns_object_set_string(obj,
ISNS_TAG_DD_MEMBER_ISCSI_NAME,
node->name);
break;
case ISNS_DD_MEMBER_PORTAL:
portal = &member->ddm_portal;
isns_object_set_uint32(obj,
ISNS_TAG_DD_MEMBER_PORTAL_INDEX,
portal->index);
if (portal->info.addr.sin6_family != AF_UNSPEC) {
isns_portal_to_object(&portal->info,
ISNS_TAG_DD_MEMBER_PORTAL_IP_ADDR,
ISNS_TAG_DD_MEMBER_PORTAL_TCP_UDP_PORT,
obj);
}
break;
}
member->ddm_added = 0;
}
}
/*
* Destroy a DD
* The caller should call isns_dd_release to free the DD object.
*/
void
isns_dd_destroy(isns_db_t *db, isns_dd_t *dd)
{
isns_db_remove(db, dd->dd_object);
isns_dd_list_remove(&isns_dd_list, dd);
dd->dd_inserted = 0;
}
int
isns_dd_load_all(isns_db_t *db)
{
isns_object_list_t list = ISNS_OBJECT_LIST_INIT;
unsigned int i;
int rc;
if (isns_dd_list_initialized)
return ISNS_SUCCESS;
rc = isns_db_gang_lookup(db, &isns_dd_template, NULL, &list);
if (rc != ISNS_SUCCESS)
return rc;
for (i = 0; i < list.iol_count; ++i) {
isns_object_t *obj = list.iol_data[i];
isns_dd_t *dd = NULL, *temp_dd = NULL;
isns_dd_member_t *mp;
temp_dd = isns_dd_alloc();
rc = isns_dd_parse_attrs(temp_dd, db, &obj->ie_attrs, NULL, 1);
if (rc) {
if (temp_dd->dd_id == 0) {
isns_error("Problem converting DD object (index 0x%x). No DD_ID\n",
obj->ie_index);
goto next;
}
isns_error("Problem converting DD %u. Proceeding anyway.\n",
temp_dd->dd_id);
} else {
isns_debug_state("Loaded DD %d from database\n", temp_dd->dd_id);
}
dd = isns_dd_clone(temp_dd);
/*
* XXX duplicate call? isns_object_get() is already called
* at the end of isns_dd_clone()
*/
dd->dd_object = isns_object_get(obj);
isns_dd_insert(dd);
isns_dd_add_members(dd, db, temp_dd);
/* Clear the ddm_added flag for all members, to
* prevent all information from being duplicated
* to the DB on the next DD modification. */
for (mp = dd->dd_members; mp; mp = mp->ddm_next)
mp->ddm_added = 0;
next:
isns_dd_release(temp_dd);
}
isns_object_list_destroy(&list);
isns_dd_list_initialized = 1;
return ISNS_SUCCESS;
}
isns_object_t *
isns_dd_get_member_object(isns_db_t *db, const isns_attr_t *key1,
const isns_attr_t *key2,
int create)
{
isns_attr_list_t query = ISNS_ATTR_LIST_INIT;
isns_object_template_t *tmpl = NULL;
isns_object_t *obj;
isns_portal_info_t portal_info;
const char *key_string = NULL;
uint32_t key_index = 0;
switch (key1->ia_tag_id) {
case ISNS_TAG_DD_MEMBER_ISCSI_INDEX:
key_index = key1->ia_value.iv_uint32;
isns_attr_list_append_uint32(&query,
ISNS_TAG_ISCSI_NODE_INDEX,
key_index);
tmpl = &isns_iscsi_node_template;
break;
case ISNS_TAG_DD_MEMBER_ISCSI_NAME:
key_string = key1->ia_value.iv_string;
isns_attr_list_append_string(&query,
ISNS_TAG_ISCSI_NAME,
key_string);
tmpl = &isns_iscsi_node_template;
break;
case ISNS_TAG_DD_MEMBER_FC_PORT_NAME:
key_string = key1->ia_value.iv_string;
isns_attr_list_append_string(&query,
ISNS_TAG_FC_PORT_NAME_WWPN,
key_string);
tmpl = &isns_fc_port_template;
break;
case ISNS_TAG_DD_MEMBER_PORTAL_INDEX:
key_index = key1->ia_value.iv_uint32;
isns_attr_list_append_uint32(&query,
ISNS_TAG_PORTAL_INDEX,
key_index);
tmpl = &isns_portal_template;
break;
case ISNS_TAG_DD_MEMBER_PORTAL_IP_ADDR:
if (!isns_portal_from_attr_pair(&portal_info, key1, key2)
|| !isns_portal_to_attr_list(&portal_info,
ISNS_TAG_PORTAL_IP_ADDRESS,
ISNS_TAG_PORTAL_TCP_UDP_PORT,
&query))
return NULL;
key_string = isns_portal_string(&portal_info);
tmpl = &isns_portal_template;
break;
default:
return NULL;
}
obj = isns_db_lookup(db, tmpl, &query);
if (!obj && create) {
if (!key_string) {
isns_debug_state("Attempt to register %s DD member "
"with unknown index %u\n",
tmpl->iot_name, key_index);
goto out;
}
obj = isns_create_object(tmpl, &query, NULL);
if (obj != NULL)
isns_debug_state("Created limbo object for "
"%s DD member %s\n",
tmpl->iot_name, key_string);
}
out:
isns_attr_list_destroy(&query);
return obj;
}