#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
#define ISNS_RCV_RETRY_MAX 2
#define IPV4_RSVD_BYTES 10
#ifdef DEBUG
extern void dump_pdu2(isns_pdu_t *);
#endif
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;
*pdu = NULL;
*pdu_size = 0;
fds.fd = fd;
fds.events = (POLLIN | POLLRDNORM);
fds.revents = 0;
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;
bzero(&msg, sizeof (msg));
msg.msg_iov = &iovec[0];
msg.msg_iovlen = 1;
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);
if (payload_len > ISNSP_MAX_PAYLOAD_SIZE) {
free(tmp_pdu_hdr);
return (0);
}
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;
bzero(&msg, sizeof (msg));
msg.msg_iov = &iovec[0];
msg.msg_iovlen = 1;
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;
msg.msg_iov = &iovec[0];
msg.msg_iovlen = 2;
flags = ISNS_FLAG_SERVER;
flags |= ISNS_FLAG_FIRST_PDU;
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 {
iovec[1].iov_base = (void *)payload;
if (pl > ISNSP_MAX_PAYLOAD_SIZE) {
send_len = ISNSP_MAX_PAYLOAD_SIZE;
} else {
send_len = pl;
flags |= ISNS_FLAG_LAST_PDU;
}
iovec[1].iov_len = send_len;
pdu->payload_len = htons(send_len);
pdu->flags = htons(flags);
pdu->seq = htons(seq);
bytes_sent = sendmsg(fd, &msg, 0);
flags &= ~(ISNS_FLAG_FIRST_PDU);
payload += send_len;
pl -= send_len;
send_len += ISNSP_HEADER_SIZE;
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) {
*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;
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;
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;
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:
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) {
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);
payload_ptr = (*pdu)->payload + *pl;
(void) memcpy(payload_ptr, attr_tlv, attr_tlv_len);
*pl += attr_tlv_len;
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;
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;
*key_len = 0;
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;
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;
*op_len = 0;
if (pdu->func_id & ISNS_RSP_MASK) {
if (payload_len < 4) {
return (NULL);
}
payload += 4;
payload_len -= 4;
}
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) {
op = (isns_tlv_t *)payload;
*op_len = payload_len;
found_op = 1;
}
}
}
if (*op_len >= 8) {
return (op);
}
return (NULL);
}