OpenSolaris_b135/cmd/isns/isnsd/pdu.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <poll.h>
#include <errno.h>

#include "isns_server.h"
#include "isns_log.h"
#include "isns_pdu.h"

#define	ISNS_MAX_IOVEC		5
#define	MAX_XID			(2^16)
#define	MAX_RCV_RSP_COUNT	10	/* Maximum number of unmatched xid */
#define	ISNS_RCV_RETRY_MAX	2
#define	IPV4_RSVD_BYTES		10

/* externs */
#ifdef DEBUG
extern void dump_pdu2(isns_pdu_t *);
#endif

/*
 * local functions.
 */

size_t
isns_rcv_pdu(
	int fd,
	isns_pdu_t **pdu,
	size_t *pdu_size,
	int rcv_timeout
)
{
	int poll_cnt;
	struct pollfd fds;
	iovec_t iovec[ISNS_MAX_IOVEC];
	isns_pdu_t *tmp_pdu_hdr;
	ssize_t bytes_received, total_bytes_received = 0;
	struct msghdr msg;
	uint8_t *tmp_pdu_data;

	uint16_t payload_len = 0;

	/* initialize to zero */
	*pdu = NULL;
	*pdu_size = 0;

	fds.fd = fd;
	fds.events = (POLLIN | POLLRDNORM);
	fds.revents = 0;

	/* Receive the header first */
	tmp_pdu_hdr = (isns_pdu_t *)malloc(ISNSP_HEADER_SIZE);
	if (tmp_pdu_hdr == NULL) {
		return (0);
	}
	(void) memset((void *)&tmp_pdu_hdr[0], 0, ISNSP_HEADER_SIZE);
	(void) memset((void *)&iovec[0], 0, sizeof (iovec_t));
	iovec[0].iov_base = (void *)tmp_pdu_hdr;
	iovec[0].iov_len = ISNSP_HEADER_SIZE;

	/* Initialization of the message header. */
	bzero(&msg, sizeof (msg));
	msg.msg_iov = &iovec[0];
	/* msg.msg_flags   = MSG_WAITALL, */
	msg.msg_iovlen  = 1;

	/* Poll and receive the pdu header */
	poll_cnt = 0;
	do {
		int err = poll(&fds, 1, rcv_timeout * 1000);
		if (err <= 0) {
			poll_cnt ++;
		} else {
			bytes_received = recvmsg(fd, &msg, MSG_WAITALL);
			break;
		}
	} while (poll_cnt < ISNS_RCV_RETRY_MAX);

	if (poll_cnt >= ISNS_RCV_RETRY_MAX) {
		free(tmp_pdu_hdr);
		return (0);
	}

	if (bytes_received <= 0) {
		free(tmp_pdu_hdr);
		return (0);
	}

	total_bytes_received += bytes_received;

	payload_len = ntohs(tmp_pdu_hdr->payload_len);
	/* Verify the received payload len is within limit */
	if (payload_len > ISNSP_MAX_PAYLOAD_SIZE) {
		free(tmp_pdu_hdr);
		return (0);
	}

	/* Proceed to receive additional data. */
	tmp_pdu_data = malloc(payload_len);
	if (tmp_pdu_data == NULL) {
		free(tmp_pdu_hdr);
		return (0);
	}
	(void) memset((void *)&iovec[0], 0, sizeof (iovec_t));
	iovec[0].iov_base = (void *)tmp_pdu_data;
	iovec[0].iov_len = payload_len;

	/* Initialization of the message header. */
	bzero(&msg, sizeof (msg));
	msg.msg_iov = &iovec[0];
	/* msg.msg_flags   = MSG_WAITALL, */
	msg.msg_iovlen  = 1;

	/* poll and receive the pdu payload */
	poll_cnt = 0;
	do {
		int err = poll(&fds, 1, rcv_timeout * 1000);
		if (err <= 0) {
			poll_cnt ++;
		} else {
			bytes_received = recvmsg(fd, &msg, MSG_WAITALL);
			break;
		}
	} while (poll_cnt < ISNS_RCV_RETRY_MAX);

	if (poll_cnt >= ISNS_RCV_RETRY_MAX) {
		free(tmp_pdu_data);
		free(tmp_pdu_hdr);
		return (0);
	}

	if (bytes_received <= 0) {
		free(tmp_pdu_data);
		free(tmp_pdu_hdr);
		return (0);
	}

	total_bytes_received += bytes_received;

	*pdu_size = ISNSP_HEADER_SIZE + payload_len;
	(*pdu) = (isns_pdu_t *)malloc(*pdu_size);
	if (*pdu == NULL) {
		*pdu_size = 0;
		free(tmp_pdu_data);
		free(tmp_pdu_hdr);
		return (0);
	}
	(*pdu)->version = ntohs(tmp_pdu_hdr->version);
	(*pdu)->func_id = ntohs(tmp_pdu_hdr->func_id);
	(*pdu)->payload_len = payload_len;
	(*pdu)->flags = ntohs(tmp_pdu_hdr->flags);
	(*pdu)->xid = ntohs(tmp_pdu_hdr->xid);
	(*pdu)->seq = ntohs(tmp_pdu_hdr->seq);
	(void) memcpy(&((*pdu)->payload), tmp_pdu_data, payload_len);

	free(tmp_pdu_data);
	tmp_pdu_data = NULL;
	free(tmp_pdu_hdr);
	tmp_pdu_hdr = NULL;

	return (total_bytes_received);
}

int
isns_send_pdu(
	int fd,
	isns_pdu_t *pdu,
	size_t pl
)
{
	uint8_t *payload;
	uint16_t flags;
	uint16_t seq;
	iovec_t iovec[ISNS_MAX_IOVEC];
	struct msghdr msg = { 0 };

	size_t send_len;
	ssize_t bytes_sent;


	/* Initialization of the message header. */
	msg.msg_iov = &iovec[0];
	/* msg.msg_flags   = MSG_WAITALL, */
	msg.msg_iovlen  = 2;

	/*
	 * Initialize the pdu flags.
	 */
	flags = ISNS_FLAG_SERVER;
	flags |= ISNS_FLAG_FIRST_PDU;

	/*
	 * Initialize the pdu sequence id.
	 */
	seq = 0;

	iovec[0].iov_base = (void *)pdu;
	iovec[0].iov_len = (ISNSP_HEADER_SIZE);

	payload = pdu->payload;

#ifdef DEBUG
	pdu->flags = htons(flags);
	pdu->seq = htons(0);
	pdu->payload_len = htons(pl);
	dump_pdu2(pdu);
#endif

	do {
		/* set the payload for sending */
		iovec[1].iov_base = (void *)payload;

		if (pl > ISNSP_MAX_PAYLOAD_SIZE) {
			send_len = ISNSP_MAX_PAYLOAD_SIZE;
		} else {
			send_len = pl;
			/* set the last pdu flag */
			flags |= ISNS_FLAG_LAST_PDU;
		}
		iovec[1].iov_len = send_len;
		pdu->payload_len = htons(send_len);

		/* set the pdu flags */
		pdu->flags = htons(flags);
		/* set the pdu sequence id */
		pdu->seq = htons(seq);

		/* send the packet */
		bytes_sent = sendmsg(fd, &msg, 0);

		/* get rid of the first pdu flag */
		flags &= ~(ISNS_FLAG_FIRST_PDU);

		/* next part of payload */
		payload += send_len;
		pl -= send_len;

		/* add the length of header for verification */
		send_len += ISNSP_HEADER_SIZE;

		/* increase the sequence id by one */
		seq ++;
	} while (bytes_sent == send_len && pl > 0);

	if (bytes_sent == send_len) {
		return (0);
	} else {
		isnslog(LOG_DEBUG, "isns_send_pdu", "sending pdu failed.");
		return (-1);
	}
}

#define	RSP_PDU_FRAG_SZ	(ISNSP_MAX_PDU_SIZE / 10)
static int
pdu_reset(
	isns_pdu_t **rsp,
	size_t *sz
)
{
	int ec = 0;

	if (*rsp == NULL) {
		*rsp = (isns_pdu_t *)malloc(RSP_PDU_FRAG_SZ);
		if (*rsp != NULL) {
			*sz = RSP_PDU_FRAG_SZ;
		} else {
			ec = ISNS_RSP_INTERNAL_ERROR;
		}
	}

	return (ec);
}

int
pdu_reset_rsp(
	isns_pdu_t **rsp,
	size_t *pl,
	size_t *sz
)
{
	int ec = pdu_reset(rsp, sz);

	if (ec == 0) {
		/* leave space for status code */
		*pl = 4;
	}

	return (ec);
}

int
pdu_reset_scn(
	isns_pdu_t **pdu,
	size_t *pl,
	size_t *sz
)
{
	int ec = pdu_reset(pdu, sz);

	if (ec == 0) {
		*pl = 0;
	}

	return (ec);
}

int
pdu_reset_esi(
	isns_pdu_t **pdu,
	size_t *pl,
	size_t *sz
)
{
	return (pdu_reset_scn(pdu, pl, sz));
}

int
pdu_update_code(
	isns_pdu_t *pdu,
	size_t *pl,
	int code
)
{
	isns_resp_t *resp;

	resp = (isns_resp_t *)pdu->payload;

	/* reset the payload length */
	if (code != ISNS_RSP_SUCCESSFUL || *pl == 0) {
		*pl = 4;
	}

	resp->status = htonl(code);

	return (0);
}

int
pdu_add_tlv(
	isns_pdu_t **pdu,
	size_t *pl,
	size_t *sz,
	uint32_t attr_id,
	uint32_t attr_len,
	void *attr_data,
	int pflag
)
{
	int ec = 0;

	isns_pdu_t *new_pdu;
	size_t new_sz;

	isns_tlv_t *attr_tlv;
	uint8_t *payload_ptr;
	uint32_t normalized_attr_len;
	uint64_t attr_tlv_len;

	/* The attribute length must be 4-byte aligned. Section 5.1.3. */
	normalized_attr_len = (attr_len % 4) == 0 ? (attr_len) :
	    (attr_len + (4 - (attr_len % 4)));
	attr_tlv_len = ISNS_TLV_ATTR_ID_LEN
	    + ISNS_TLV_ATTR_LEN_LEN
	    + normalized_attr_len;
	/* Check if we are going to exceed the maximum PDU length. */
	if ((ISNSP_HEADER_SIZE + *pl + attr_tlv_len) > *sz) {
		new_sz = *sz + RSP_PDU_FRAG_SZ;
		new_pdu = (isns_pdu_t *)realloc(*pdu, new_sz);
		if (new_pdu != NULL) {
			*sz = new_sz;
			*pdu = new_pdu;
		} else {
			ec = ISNS_RSP_INTERNAL_ERROR;
			return (ec);
		}
	}

	attr_tlv = (isns_tlv_t *)malloc(attr_tlv_len);
	(void) memset((void *)attr_tlv, 0, attr_tlv_len);

	attr_tlv->attr_id = htonl(attr_id);

	switch (attr_id) {
		case ISNS_DELIMITER_ATTR_ID:
		break;

		case ISNS_PORTAL_IP_ADDR_ATTR_ID:
		case ISNS_PG_PORTAL_IP_ADDR_ATTR_ID:
			/* IPv6 */
			ASSERT(attr_len == sizeof (in6_addr_t));
			(void) memcpy(attr_tlv->attr_value, attr_data,
			    sizeof (in6_addr_t));
		break;

		case ISNS_EID_ATTR_ID:
		case ISNS_ISCSI_NAME_ATTR_ID:
		case ISNS_ISCSI_ALIAS_ATTR_ID:
		case ISNS_PG_ISCSI_NAME_ATTR_ID:
			(void) memcpy(attr_tlv->attr_value, (char *)attr_data,
			    attr_len);
		break;

		default:
			if (attr_len == 8) {
				if (pflag == 0) {
				/*
				 * In the iSNS protocol, there is only one
				 * attribute ISNS_TIMESTAMP_ATTR_ID which has
				 * 8 bytes length integer value and when the
				 * function "pdu_add_tlv" is called for adding
				 * the timestamp attribute, the value of
				 * the attribute is always passed in as its
				 * address, i.e. the pflag sets to 1.
				 * So it is an error when we get to this code
				 * path.
				 */
					ec = ISNS_RSP_INTERNAL_ERROR;
					return (ec);
				} else {
					*(uint64_t *)attr_tlv->attr_value =
					    *(uint64_t *)attr_data;
				}
			} else if (attr_len == 4) {
				if (pflag == 0) {
					*(uint32_t *)attr_tlv->attr_value =
					    htonl((uint32_t)attr_data);
				} else {
					*(uint32_t *)attr_tlv->attr_value =
					    *(uint32_t *)attr_data;
				}
			}
		break;
	}

	attr_tlv->attr_len = htonl(normalized_attr_len);
	/*
	 * Convert the network byte ordered payload length to host byte
	 * ordered for local address calculation.
	 */
	payload_ptr = (*pdu)->payload + *pl;
	(void) memcpy(payload_ptr, attr_tlv, attr_tlv_len);
	*pl += attr_tlv_len;

	/*
	 * The payload length might exceed the maximum length of a
	 * payload that isnsp allows, we will split the payload and
	 * set the size of each payload before they are sent.
	 */

	free(attr_tlv);
	attr_tlv = NULL;

	return (ec);
}

isns_tlv_t *
pdu_get_source(
	isns_pdu_t *pdu
)
{
	uint8_t *payload = &pdu->payload[0];
	uint16_t payload_len = pdu->payload_len;
	isns_tlv_t *tlv = NULL;

	/* response code */
	if (pdu->func_id & ISNS_RSP_MASK) {
		if (payload_len < 4) {
			return (NULL);
		}
		payload += 4;
		payload_len -= 4;
	}

	if (payload_len > 8) {
		tlv = (isns_tlv_t *)payload;
		tlv->attr_id = ntohl(tlv->attr_id);
		tlv->attr_len = ntohl(tlv->attr_len);
	}

	return (tlv);
}

isns_tlv_t *
pdu_get_key(
	isns_pdu_t *pdu,
	size_t *key_len
)
{
	uint8_t *payload = &pdu->payload[0];
	uint16_t payload_len = pdu->payload_len;
	isns_tlv_t *tlv, *key;

	/* reset */
	*key_len = 0;

	/* response code */
	if (pdu->func_id & ISNS_RSP_MASK) {
		if (payload_len <= 4) {
			return (NULL);
		}
		payload += 4;
		payload_len -= 4;
	}

	/* skip the soure */
	if (payload_len >= 8) {
		tlv = (isns_tlv_t *)payload;
		payload += (8 + tlv->attr_len);
		payload_len -= (8 + tlv->attr_len);
		key = (isns_tlv_t *)payload;
		while (payload_len >= 8) {
			tlv = (isns_tlv_t *)payload;
			tlv->attr_id = ntohl(tlv->attr_id);
			tlv->attr_len = ntohl(tlv->attr_len);
			if (tlv->attr_id == ISNS_DELIMITER_ATTR_ID) {
				break;
			}
			*key_len += (8 + tlv->attr_len);
			payload += (8 + tlv->attr_len);
			payload_len -= (8 + tlv->attr_len);
		}
	}

	if (*key_len >= 8) {
		return (key);
	}

	return (NULL);
}

isns_tlv_t *
pdu_get_operand(
	isns_pdu_t *pdu,
	size_t *op_len
)
{
	uint8_t *payload = &pdu->payload[0];
	uint16_t payload_len = pdu->payload_len;
	isns_tlv_t *tlv, *op = NULL;
	int found_op = 0;

	/* reset */
	*op_len = 0;

	/* response code */
	if (pdu->func_id & ISNS_RSP_MASK) {
		if (payload_len < 4) {
			return (NULL);
		}
		payload += 4;
		payload_len -= 4;
	}

	/* tlvs */
	while (payload_len >= 8) {
		tlv = (isns_tlv_t *)payload;
		if (found_op != 0) {
			tlv->attr_id = ntohl(tlv->attr_id);
			tlv->attr_len = ntohl(tlv->attr_len);
			payload += (8 + tlv->attr_len);
			payload_len -= (8 + tlv->attr_len);
		} else {
			payload += (8 + tlv->attr_len);
			payload_len -= (8 + tlv->attr_len);
			if (tlv->attr_id == ISNS_DELIMITER_ATTR_ID) {
				/* found it */
				op = (isns_tlv_t *)payload;
				*op_len = payload_len;
				found_op = 1;
			}
		}
	}

	if (*op_len >= 8) {
		return (op);
	}

	return (NULL);
}