summaryrefslogtreecommitdiff
path: root/utils/open-isns/dd.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/open-isns/dd.c')
-rw-r--r--utils/open-isns/dd.c1307
1 files changed, 1307 insertions, 0 deletions
diff --git a/utils/open-isns/dd.c b/utils/open-isns/dd.c
new file mode 100644
index 0000000..b392036
--- /dev/null
+++ b/utils/open-isns/dd.c
@@ -0,0 +1,1307 @@
+/*
+ * Handle DD registration/deregistration
+ *
+ * Discovery domains are weird, even in the context of
+ * iSNS. For once 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 "isns.h"
+#include "attrs.h"
+#include "objects.h"
+#include "message.h"
+#include "security.h"
+#include "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 after
+ * 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, cur_size;
+ isns_dd_t **new_data;
+
+ cur_size = LIST_SIZE(list->ddl_count);
+ 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;
+ }
+ }
+ 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);
+
+ 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;
+
+}