/*
* 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);
}