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