OpenSolaris_b135/cmd/iscsi/iscsitgtd/isns.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.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <ctype.h>
#include <pthread.h>
#include <netdb.h>
#include <libintl.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/byteorder.h>

#include "isns_protocol.h"
#include "isns_client.h"
#include "queue.h"

extern target_queue_t	*mgmtq;

static	uint16_t	xid = 0;
static	pthread_mutex_t	xid_lock = PTHREAD_MUTEX_INITIALIZER;

/*
 * more than 1 processes can accessing get_xid
 */
static uint16_t
get_xid()
{
	uint16_t	tmp;

	(void) pthread_mutex_lock(&xid_lock);
	tmp = xid++;
	(void) pthread_mutex_unlock(&xid_lock);
	return (tmp);
}

void
ntoh_tlv(isns_tlv_t *tlv)
{
	uint32_t	val;

	tlv->attr_id = ntohl(tlv->attr_id);
	tlv->attr_len = ntohl(tlv->attr_len);

	switch (tlv->attr_id) {
		case ISNS_DELIMITER_ATTR_ID:
			break;
		case ISNS_ISCSI_NAME_ATTR_ID:
		case ISNS_EID_ATTR_ID:
		case ISNS_ISCSI_ALIAS_ATTR_ID:
		case ISNS_PORTAL_NAME_ATTR_ID:
		case ISNS_PG_ISCSI_NAME_ATTR_ID:
			break;

		case ISNS_PORTAL_IP_ADDR_ATTR_ID:
		case ISNS_PG_PORTAL_IP_ADDR_ATTR_ID:
			bcopy(tlv->attr_value, &val, 4);
			*tlv->attr_value = ntohl(val);
			break;

		default:
			switch (tlv->attr_len) {
				case 4:
					val = ntohl(
					    (uint32_t)(*tlv->attr_value));
					bcopy(&val, tlv->attr_value, 4);
					break;
				default:
					break;
			}
			break;
	}
}

/*
 * print_ntoh_tlv print network byte order tag-length-value attribute
 */
void
print_ntoh_tlv(isns_tlv_t *tlv)
{
	uint32_t	tag, len, val, pf_type;
	char		buf[256];
	struct sockaddr_in6	sin6;

	tag = ntohl(tlv->attr_id);
	len = ntohl(tlv->attr_len);

	if (len == 0) {
		queue_prt(mgmtq, Q_ISNS_DBG, "Zero length tag: %d\n", tag);
		return;
	}

	switch (tag) {
		case ISNS_DELIMITER_ATTR_ID:
			break;
		case ISNS_ISCSI_NAME_ATTR_ID:
		case ISNS_EID_ATTR_ID:
		case ISNS_ISCSI_ALIAS_ATTR_ID:
		case ISNS_PORTAL_NAME_ATTR_ID:
		case ISNS_PG_ISCSI_NAME_ATTR_ID:
			queue_prt(mgmtq, Q_ISNS_DBG,
			    "Tag %d: Value: %s\n", tag, tlv->attr_value);
			break;

		case ISNS_PORTAL_IP_ADDR_ATTR_ID:
		case ISNS_PG_PORTAL_IP_ADDR_ATTR_ID:
			bcopy(tlv->attr_value, &pf_type, 4);
			pf_type = ntohl(pf_type);
			pf_type = (pf_type == sizeof (in6_addr_t))
			    ? PF_INET6 : PF_INET;
			switch (pf_type) {
				case PF_INET:
					/* RFC2372 IPv4 mapped IPv6 address */
					if (inet_ntop(pf_type,
					    (void *)&tlv->attr_value[12],
					    buf, 256) == NULL) {
						syslog(LOG_ERR,
						    "inet_ntop failed");
						break;
					}
					queue_prt(mgmtq, Q_ISNS_DBG,
					    "IP_ADDR %s\n", buf);
					break;
				case PF_INET6:
					bcopy(tlv->attr_value, &sin6,
					    sizeof (struct sockaddr_in6));
					(void) inet_ntop(pf_type,
					    (void *)&sin6.sin6_addr,
					    buf, 256);
					break;
				default:
					queue_prt(mgmtq, Q_ISNS_DBG,
					    "unknown pf_type\n");
					break;
			}
			break;

		default:
			switch (len) {
				case 4:
					bcopy(tlv->attr_value, &val, 4);
					val = ntohl(val);
					queue_prt(mgmtq, Q_ISNS_DBG,
					    "Tag: %d Value: %ld\n", tag, val);
					break;
				default:
					break;
			}
			break;
	}
}

void
print_attr(isns_tlv_t *attr, void *pval, uint32_t ival)
{
	uint32_t	tag = ntohl(attr->attr_id);
	uint32_t	len = ntohl(attr->attr_len);
	uint32_t	pf_type;
	char		buf[256];

	queue_prt(mgmtq, Q_ISNS_DBG, "Tag: %d Length: %d\n", tag, len);
	switch (tag) {
		case ISNS_DELIMITER_ATTR_ID:
			break;

		case ISNS_ISCSI_NAME_ATTR_ID:
		case ISNS_EID_ATTR_ID:
		case ISNS_ISCSI_ALIAS_ATTR_ID:
		case ISNS_PORTAL_NAME_ATTR_ID:
		case ISNS_PG_ISCSI_NAME_ATTR_ID:
			if (len && pval != NULL) {
				queue_prt(mgmtq, Q_ISNS_DBG, "Value: %s\n",
				    attr->attr_value);
			}
			break;

		case ISNS_PORTAL_IP_ADDR_ATTR_ID:
		case ISNS_PG_PORTAL_IP_ADDR_ATTR_ID:
			if (len) {
				pf_type = (ival == sizeof (in6_addr_t))
				    ? PF_INET6 : PF_INET;
				(void) inet_ntop(pf_type, pval, buf, 256);
				queue_prt(mgmtq, Q_ISNS_DBG, "IP_ADDR %s\n",
				    buf);
			}
			break;

		default:
		switch (len) {
			case 4:
				queue_prt(mgmtq, Q_ISNS_DBG,
				    "Value: %d\n",
				    ntohl(*attr->attr_value));
				break;
			default:
				break;
		}
		break;
	}
}

void
print_isns_hdr(isns_hdr_t *hdr)
{
	queue_prt(mgmtq, Q_ISNS_DBG, "hdr->version %d\n", hdr->version);
	queue_prt(mgmtq, Q_ISNS_DBG, "hdr->func_id %x\n", hdr->func_id);
	queue_prt(mgmtq, Q_ISNS_DBG, "hdr->pdu_len %d\n", hdr->pdu_len);
	queue_prt(mgmtq, Q_ISNS_DBG, "hdr->flags %x\n", hdr->flags);
	queue_prt(mgmtq, Q_ISNS_DBG, "hdr->xid %d\n", hdr->xid);
	queue_prt(mgmtq, Q_ISNS_DBG, "hdr->seqid %d\n", hdr->seqid);
}

void
ntoh_isns_hdr(isns_hdr_t *hdr)
{
	hdr->version = ntohs(hdr->version);
	hdr->func_id = ntohs(hdr->func_id);
	hdr->pdu_len = ntohs(hdr->pdu_len);
	hdr->flags = ntohs(hdr->flags);
	hdr->xid = ntohs(hdr->xid);
	hdr->seqid = ntohs(hdr->seqid);
}

int
isns_append_attr(isns_pdu_t *pdu, uint32_t tag,  uint32_t len,
	void *pval, uint32_t ival)
{
	uint32_t	val;
	uint32_t	pad_len;
	uint16_t	pdu_len;
	isns_tlv_t	*attr;
	char		*tlv;

	if (pdu == NULL) {
		syslog(LOG_ALERT, "NULL PDU\n");
		return (-1);
	}

	/* get current pdu payload length */
	pdu_len = ntohs(pdu->payload_len);

	/* pad 4 bytes alignment */
	pad_len = PAD4(len);

	if ((pdu_len + pad_len) > MAX_PDU_PAYLOAD_SZ) {
		syslog(LOG_ALERT, "Exceeded PDU size\n");
		return (-1);
	}

	if ((attr = (isns_tlv_t *)malloc(ISNS_ATTR_SZ(pad_len))) == NULL) {
		syslog(LOG_ALERT, "Malloc error");
		return (-1);
	}
	bzero(attr, ISNS_ATTR_SZ(pad_len));
	attr->attr_id = htonl(tag);
	attr->attr_len = htonl(pad_len);

	switch (tag) {
		case ISNS_DELIMITER_ATTR_ID:
			break;

		case ISNS_ISCSI_NAME_ATTR_ID:
		case ISNS_EID_ATTR_ID:
		case ISNS_ISCSI_ALIAS_ATTR_ID:
		case ISNS_PORTAL_NAME_ATTR_ID:
		case ISNS_PG_ISCSI_NAME_ATTR_ID:
			if (len && pval != NULL) {
				bcopy(pval, attr->attr_value, len);
			}
			break;

		case ISNS_PORTAL_IP_ADDR_ATTR_ID:
		case ISNS_PG_PORTAL_IP_ADDR_ATTR_ID:
			if (len && ival == sizeof (in_addr_t)) {
				/* IPv4 */
				attr->attr_value[10] = 0xFF;
				attr->attr_value[11] = 0xFF;
				bcopy(pval, ((attr->attr_value) + 12), ival);
			} else if (len && ival == sizeof (in6_addr_t)) {
				/* IPv6 */
				bcopy(pval, attr->attr_value, ival);
			}
			break;

		default:
			switch (len) {
				case 4:
					val = htonl(ival);
					bcopy(&val, attr->attr_value, 4);
					break;
				default:
					break;
			}
			break;
	}

	/* copy attribute to pdu */
	tlv = (char *)pdu + ISNSP_HEADER_SIZE + pdu_len;
	bcopy(attr, tlv, ISNS_ATTR_SZ(pad_len));
	pdu->payload_len = htons(pdu_len + ISNS_ATTR_SZ(pad_len));

	/* debug only */
	print_ntoh_tlv(attr);

	free(attr);
	return (0);
}

int
isns_create_pdu(uint16_t func_id, uint32_t flags, isns_pdu_t **pdu)
{
	size_t	pdu_sz = MAX_PDU_SZ;

	if ((*pdu = (isns_pdu_t *)malloc(pdu_sz)) == NULL) {
		syslog(LOG_ERR, "isns_create_pdu malloc failure");
		return (-1);
	}

	bzero(*pdu, pdu_sz);
	(*pdu)->payload_len = 0;
	(*pdu)->seq = 0;
	(*pdu)->xid = htons(get_xid());
	(*pdu)->version = htons((uint16_t)ISNSP_VERSION);
	(*pdu)->func_id = htons((uint16_t)(func_id));
	(*pdu)->flags = htons((uint16_t)(flags | ISNS_FLAG_CLIENT |
	    ISNS_FLAG_FIRST_PDU | ISNS_FLAG_LAST_PDU));
	return (0);
}

void
isns_free_pdu(void *pdu)
{
	free(pdu);
}

/*
 * Desc: Open connection to the isns server
 * Args: isns server name or isns server ip-addr
 * Return: -1 if open failed, descriptor to socket if open succeeded
 */
int
isns_open(char *server)
{
	struct addrinfo		hints, *ai, *aip;
	struct sockaddr		*sa;
	struct sockaddr_in	sin;
	struct sockaddr_in6	sin6;
	size_t	sa_len;
	int	so;
	int	ret;
	fd_set rfdset;
	fd_set wfdset;
	fd_set errfdset;
	struct timeval timeout;
	Boolean_t shouldsockblock = False;
	int socket_ready = 0;
	timeout.tv_sec = 5;   /* 5 Secs Timeout */
	timeout.tv_usec = 0;   /* 0 uSecs Timeout */

	if (server == NULL) {
		syslog(LOG_ERR, "ISNS server ID required");
		return (-1);
	}

	bzero(&hints, sizeof (struct addrinfo));
	hints.ai_family = AF_UNSPEC;
	if ((ret = getaddrinfo(server, NULL, NULL, &ai)) != 0) {
		syslog(LOG_ALERT, "getaddrinfo failed on server %s: %s", server,
		    gai_strerror(ret));
		return (-1);
	}

	aip = ai;
	do {
		so = socket(aip->ai_family, SOCK_STREAM, 0);
		if (so != -1) {

			/* set it to non blocking so connect wont hang */
			if (setsocknonblocking(so) == -1) {
				(void) close(so);
				continue;
			}

			sa_len = aip->ai_addrlen;
			switch (aip->ai_family) {
				case PF_INET:
					bzero(&sin,
					    sizeof (struct sockaddr_in));
					sa = (struct sockaddr *)&sin;
					bcopy(aip->ai_addr, sa, sa_len);
					sin.sin_port = htons(
					    ISNS_DEFAULT_SERVER_PORT);
					break;
				case PF_INET6:
					bzero(&sin6,
					    sizeof (struct sockaddr_in6));
					sa = (struct sockaddr *)&sin6;
					bcopy(aip->ai_addr, sa, sa_len);
					sin6.sin6_port =
					    htons(ISNS_DEFAULT_SERVER_PORT);
					break;
				default:
					syslog(LOG_ALERT, "Bad protocol");
					(void) close(so);
					continue;
			}

			ret = connect(so, sa, sa_len);
			if (ret == 0) {
				/*
				 * connection succeeded with out
				 * blocking
				 */
				shouldsockblock = True;
			}

			if (ret < 0) {
				if (errno == EINPROGRESS) {
					FD_ZERO(&rfdset);
					FD_ZERO(&wfdset);
					FD_ZERO(&errfdset);
					FD_SET(so, &rfdset);
					FD_SET(so, &wfdset);
					FD_SET(so, &errfdset);
					socket_ready =
					    select(so + 1, &rfdset, &wfdset,
					    &errfdset, &timeout);
					if (socket_ready < 0) {
						syslog(LOG_ALERT,
						    "failed to connect with"
						" isns, err=%d", errno);
						(void) close(so);
					} else if (socket_ready == 0) {
						syslog(LOG_ALERT,
						    "time out failed"
						    " to connect with isns");
						(void) close(so);
					} else { /* Socket is ready */
						/*
						 * Check if socket is ready
						 */
						if (is_socket_ready(so,
						    &rfdset, &wfdset,
						    &errfdset) == True)
							shouldsockblock = True;
						else
							(void) close(so);
					}
				} else {
					syslog(LOG_WARNING,
					    "Connect failed no progress");
					(void) close(so);
				}
			}

			if (shouldsockblock == True) {
				if (-1 == setsockblocking(so)) {
					(void) close(so);
					shouldsockblock = False;
				} else {
					freeaddrinfo(ai);
					return (so);
				}
			}

		}
	} while ((aip = aip->ai_next) != NULL);

	if (ai != NULL)
		freeaddrinfo(ai);
	return (-1);
}

/*
 * According to:
 * UNIX Network Programming Volume 1, Third Edition:
 * The Sockets Networking APIBOOK:
 *
 * When the connection completes successfully, the descriptor becomes
 * writable (p. 531 of TCPv2).
 * When the connection establishment encounters an error, the descriptor
 * becomes both readable and writable (p. 530 of TCPv2).
 */
Boolean_t
is_socket_ready(int so, fd_set *rfdset, fd_set *wfdset,
		    fd_set *errfdset)
{
	if ((FD_ISSET(so, wfdset) &&
	    FD_ISSET(so, rfdset)) ||
	    FD_ISSET(so, errfdset)) {
		return (False);
	} else {
		return (True);
	}
}

int
setsocknonblocking(int so)
{
	int flags;
	/* set it to non blocking */
	if (-1 == (flags = fcntl(so, F_GETFL, 0))) {
		syslog(LOG_WARNING,
		    "Failed to get socket flags. Blocking..");
		return (-1);
	}

	if (fcntl(so, F_SETFL, flags | O_NONBLOCK) == -1) {
		syslog(LOG_WARNING,
		    "Failed to set socket in non blocking mode");
		return (-1);
	}
	return (0);
}

int
setsockblocking(int so)
{
	int flags;
	/* set it to non blocking */
	if (-1 == (flags = fcntl(so, F_GETFL, 0))) {
		syslog(LOG_WARNING, " Failed to get flags on socket..");
		return (-1);
	}

	flags &= ~O_NONBLOCK;
	if (fcntl(so, F_SETFL, flags) == -1) {
		syslog(LOG_WARNING, " failed to set socket to blocking");
		return (-1);
	}
	return (0);
}


void
isns_close(int so)
{
	if (so) {
		(void) close(so);
	}
}

/*
 * isns_send allocated pdu, caller needs to free pdu when done processing
 */
int
isns_send(int so, isns_pdu_t *pdu)
{
	size_t	len;

	assert(pdu != NULL);

	len = ISNSP_HEADER_SIZE + ntohs(pdu->payload_len);
	if (send(so, pdu, len, 0) == -1) {
		syslog(LOG_ALERT, "isns_send failure");
		return (-1);
	}
	return (0);
}

/*
 * Desc: isns_recv malloc memory for the isns response message, user needs
 *	to isns_free() memory after process the response message.  The
 *	isns header is converted to host byte order, the remaining TLV
 *	attributes can be converted using ntoh_tlv()
 */
int
isns_recv(int so, isns_rsp_t **pdu)
{
	isns_hdr_t	hdr, hdr1;
	isns_rsp_t	*rsp;
	uint8_t		*ptr;
	size_t		total_pdu_len;
	int		len;
	uint16_t	xid_x, func_id_x, seqid_x;
	boolean_t	done;

	*pdu = NULL;
	total_pdu_len = 0;
	seqid_x = 0;
	done = FALSE;

	do {
		/* read pdu header 1st */
		do {
			len = recv(so, &hdr1, ISNSP_HEADER_SIZE, MSG_WAITALL);
		} while ((len == -1) && (errno == EINTR));

		if (len != ISNSP_HEADER_SIZE) {
			syslog(LOG_ALERT, "isns_recv fail to read header");
			return (-1);
		}

		/* normalize the pdu header for processing */
		bcopy(&hdr1, &hdr, sizeof (isns_hdr_t));
		ntoh_isns_hdr(&hdr);

		if (IS_1ST_PDU(hdr.flags)) {
			if (hdr.seqid != 0) {
				syslog(LOG_ALERT, "ISNS out of sequence");
				return (-1);
			}
			xid_x = hdr.xid;
			func_id_x = hdr.func_id;
		}

		if (IS_LAST_PDU(hdr.flags)) {
			done = TRUE;
		}

		/* verify seq, xid, func_id */
		if (seqid_x != hdr.seqid) {
			syslog(LOG_ALERT, "ISNS out of sequence");
			return (-1);
		}
		if (xid_x != hdr.xid || func_id_x != hdr.func_id) {
			syslog(LOG_ALERT, "Non matching xid or func_id");
			return (-1);
		}

		++seqid_x;	/* next expected seqid */

		/* malloc size + previous payload length */
		if ((ptr = malloc(ISNSP_HEADER_SIZE + hdr.pdu_len
		    + total_pdu_len)) == NULL) {
			syslog(LOG_ALERT, "Malloc failure");
			return (-1);
		}
		bzero(ptr, ISNSP_HEADER_SIZE + hdr.pdu_len);

		if (hdr.seqid == 0) {
			*pdu = (void *)ptr;
			bcopy(&hdr1, ptr, ISNSP_HEADER_SIZE);
			ptr += ISNSP_HEADER_SIZE;
			if ((len = recv(so, ptr, hdr.pdu_len, MSG_WAITALL))
			    != hdr.pdu_len) {
				syslog(LOG_ERR,
				    "isns_recv fail to read 1st payload");
				free(*pdu);
				*pdu = NULL;
				return (-1);
			}
		} else {
			/* merge the pdu */
			bcopy(*pdu, ptr, ISNSP_HEADER_SIZE + total_pdu_len);
			free(*pdu);
			*pdu = (void *)ptr;
			ptr += (ISNSP_HEADER_SIZE + total_pdu_len);
			if (recv(so, ptr, hdr.pdu_len, MSG_WAITALL)
			    != hdr.pdu_len) {
				syslog(LOG_ERR,
				    "isns_recv fail to read payload");
				free(*pdu);
				*pdu = NULL;
				return (-1);
			}
		}
		total_pdu_len += hdr.pdu_len;

	} while (done == FALSE);

	/* normalize the response status */
	rsp = (isns_rsp_t *)*pdu;
	rsp->pdu_len = htons(total_pdu_len);
	ntoh_isns_hdr((isns_hdr_t *)rsp);

	return (0);
}