| /* |
| * iSNS functions |
| * |
| * Copyright (C) 2006 FUJITA Tomonori <tomof@acm.org> |
| * Copyright (C) 2007 - 2018 Vladislav Bolkhovitin |
| * Copyright (C) 2007 - 2018 Western Digital Corporation |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of the |
| * License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. |
| */ |
| |
| #include <errno.h> |
| #include <netdb.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <netinet/in.h> |
| #include <netinet/tcp.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| |
| #include "iscsid.h" |
| #include "isns_proto.h" |
| #include "misc.h" |
| |
| #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) |
| #define BUFSIZE (1 << 18) |
| |
| struct isns_io { |
| char *buf; |
| int offset; |
| }; |
| |
| struct isns_qry_mgmt { |
| char name[ISCSI_NAME_LEN]; |
| uint16_t transaction; |
| struct __qelem qlist; |
| }; |
| |
| struct isns_initiator { |
| char name[ISCSI_NAME_LEN]; |
| struct __qelem ilist; |
| }; |
| |
| char *isns_server; |
| int isns_access_control; |
| char isns_entity_target_name[ISCSI_NAME_LEN]; |
| int isns_timeout = -1; |
| |
| static LIST_HEAD(qry_list); |
| static uint16_t scn_listen_port; |
| static int isns_fd, scn_listen_fd, scn_fd; |
| static struct isns_io isns_rx, scn_rx; |
| static char *rxbuf; |
| static uint16_t transaction; |
| static uint32_t current_timeout = 30; /* seconds */ |
| static char eid[ISCSI_NAME_LEN]; |
| static uint8_t ip[16]; /* SCST iSCSI supports only one portal */ |
| static struct sockaddr_storage ss; |
| |
| int isns_scn_access_allowed(uint32_t tid, char *name) |
| { |
| struct isns_initiator *ini; |
| struct target *target = target_find_by_id(tid); |
| |
| if ((isns_server == NULL) || !isns_access_control) |
| return 1; |
| |
| if (!target) |
| return 0; |
| |
| list_for_each_entry(ini, &target->isns_head, ilist) { |
| if (!strcmp(ini->name, name)) |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int isns_get_ip(int fd) |
| { |
| int err, i; |
| uint32_t addr; |
| union { |
| struct sockaddr sa; |
| struct sockaddr_in sin; |
| struct sockaddr_in6 sin6; |
| } lss; |
| socklen_t slen = sizeof(lss); |
| |
| err = getsockname(fd, &lss.sa, &slen); |
| if (err) { |
| log_error("getsockname error: %s!", strerror(errno)); |
| return err; |
| } |
| |
| err = getnameinfo(&lss.sa, sizeof(lss), |
| eid, sizeof(eid), NULL, 0, 0); |
| if (err == EAI_AGAIN) |
| err = getnameinfo(&lss.sa, sizeof(lss), |
| eid, sizeof(eid), NULL, 0, NI_NUMERICHOST); |
| if (err) { |
| log_error("getnameinfo error: %s!", get_error_str(err)); |
| return err; |
| } |
| |
| switch (lss.sa.sa_family) { |
| case AF_INET: |
| addr = lss.sin.sin_addr.s_addr; |
| |
| ip[10] = ip[11] = 0xff; |
| ip[15] = 0xff & (addr >> 24); |
| ip[14] = 0xff & (addr >> 16); |
| ip[13] = 0xff & (addr >> 8); |
| ip[12] = 0xff & addr; |
| break; |
| case AF_INET6: |
| for (i = 0; i < ARRAY_SIZE(ip); i++) |
| ip[i] = lss.sin6.sin6_addr.s6_addr[i]; |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int isns_connect(void) |
| { |
| int fd, err; |
| |
| log_debug(1, "Going to connect to iSNS server %s", isns_server); |
| |
| fd = socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP); |
| if (fd < 0) { |
| log_error("unable to create (%s) %d!", strerror(errno), |
| ss.ss_family); |
| return -errno; |
| } |
| |
| /* |
| * ToDo: must be made non-blocking, otherwise for an unreacheable |
| * server it blocks all other events processing until timeout (30 secs). |
| */ |
| err = connect(fd, (struct sockaddr *)&ss, sizeof(ss)); |
| if (err < 0) { |
| log_error("unable to connect (%s) %d!", strerror(errno), |
| ss.ss_family); |
| close(fd); |
| return -errno; |
| } |
| |
| log_info("%s %d: new connection %d", __func__, __LINE__, fd); |
| |
| if (!strlen(eid)) { |
| err = isns_get_ip(fd); |
| if (err) { |
| close(fd); |
| return err; |
| } |
| } |
| |
| isns_fd = fd; |
| isns_set_fd(fd, scn_listen_fd, scn_fd); |
| |
| return fd; |
| } |
| |
| static void isns_hdr_init(struct isns_hdr *hdr, uint16_t function, |
| uint16_t length, uint16_t flags, |
| uint16_t trans, uint16_t sequence) |
| { |
| hdr->version = htons(0x0001); |
| hdr->function = htons(function); |
| hdr->length = htons(length); |
| hdr->flags = htons(flags); |
| hdr->transaction = htons(trans); |
| hdr->sequence = htons(sequence); |
| } |
| |
| static int isns_tlv_set(struct isns_tlv **tlv, int max_tlv_buflen, |
| uint32_t tag, uint32_t length, void *value) |
| { |
| int l = length; |
| int res; |
| |
| if (l % ISNS_ALIGN) |
| l += (ISNS_ALIGN - (l % ISNS_ALIGN)); |
| |
| if (sizeof(struct isns_tlv) + l > max_tlv_buflen) { |
| log_error("Too big tlv len %d (max allowed %d)", l, |
| max_tlv_buflen); |
| res = -EOVERFLOW; |
| goto out; |
| } |
| |
| (*tlv)->tag = htonl(tag); |
| (*tlv)->length = htonl(l); |
| |
| if (length) |
| memcpy((*tlv)->value, value, length); |
| |
| l += sizeof(struct isns_tlv); |
| *tlv = (struct isns_tlv *)((char *)*tlv + l); |
| |
| res = l; |
| |
| out: |
| return res; |
| } |
| |
| static int isns_scn_deregister(char *name) |
| { |
| int err; |
| uint16_t flags, length = 0; |
| char buf[2048]; |
| struct isns_hdr *hdr = (struct isns_hdr *)buf; |
| struct isns_tlv *tlv; |
| int max_buf; |
| |
| if (!isns_fd) { |
| err = isns_connect(); |
| if (err < 0) |
| goto out; |
| } |
| |
| memset(buf, 0, sizeof(buf)); |
| tlv = (struct isns_tlv *)hdr->pdu; |
| max_buf = sizeof(buf) - offsetof(struct isns_hdr, pdu); |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(name) + 1, name); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(name) + 1, name); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; |
| isns_hdr_init(hdr, ISNS_FUNC_SCN_DEREG, length, flags, |
| ++transaction, 0); |
| |
| err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); |
| if (err < 0) |
| log_error("%s %d: %s", __func__, __LINE__, strerror(errno)); |
| |
| out: |
| return err; |
| } |
| |
| #if __BYTE_ORDER == __LITTLE_ENDIAN |
| #define correct_scn_flag_endiannes(x) \ |
| { \ |
| x = (x & 0x55555555) << 1 | (x & 0xaaaaaaaa) >> 1; \ |
| x = (x & 0x33333333) << 2 | (x & 0xcccccccc) >> 2; \ |
| x = (x & 0x0f0f0f0f) << 4 | (x & 0xf0f0f0f0) >> 4; \ |
| x = (x & 0x00ff00ff) << 8 | (x & 0xff00ff00) >> 8; \ |
| x = (x & 0x0000ffff) << 16 | (x & 0xffff0000) >> 16; \ |
| } |
| #else |
| #define correct_scn_flag_endiannes(x) { } |
| #endif |
| |
| static int isns_scn_register(void) |
| { |
| int err; |
| uint16_t flags, length = 0; |
| uint32_t scn_flags; |
| char buf[4096]; |
| struct isns_hdr *hdr = (struct isns_hdr *)buf; |
| int max_buf; |
| struct isns_tlv *tlv; |
| struct target *target; |
| |
| if (list_empty(&targets_list)) |
| return 0; |
| |
| if (!isns_fd) { |
| err = isns_connect(); |
| if (err < 0) |
| goto out; |
| } |
| |
| memset(buf, 0, sizeof(buf)); |
| tlv = (struct isns_tlv *)hdr->pdu; |
| max_buf = sizeof(buf) - offsetof(struct isns_hdr, pdu); |
| |
| if (strlen(isns_entity_target_name) < 1) { |
| target = list_entry(targets_list.q_forw, struct target, tlist); |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(target->name) + 1, target->name); |
| } else { |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(isns_entity_target_name) + 1, isns_entity_target_name); |
| } |
| |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, 0, 0, 0); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| scn_flags = ISNS_SCN_FLAG_INITIATOR | ISNS_SCN_FLAG_OBJECT_REMOVE | |
| ISNS_SCN_FLAG_OBJECT_ADDED | ISNS_SCN_FLAG_OBJECT_UPDATED; |
| correct_scn_flag_endiannes(scn_flags); |
| scn_flags = htonl(scn_flags); |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_SCN_BITMAP, |
| sizeof(scn_flags), &scn_flags); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; |
| isns_hdr_init(hdr, ISNS_FUNC_SCN_REG, length, flags, ++transaction, 0); |
| |
| err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); |
| if (err < 0) |
| log_error("%s %d: %s", __func__, __LINE__, strerror(errno)); |
| |
| out: |
| return err; |
| } |
| |
| static int isns_attr_query(char *name) |
| { |
| int err; |
| uint16_t flags, length = 0; |
| char buf[4096]; |
| struct isns_hdr *hdr = (struct isns_hdr *)buf; |
| struct isns_tlv *tlv; |
| struct target *target; |
| uint32_t node = htonl(ISNS_NODE_INITIATOR); |
| struct isns_qry_mgmt *mgmt; |
| int max_buf; |
| |
| if (list_empty(&targets_list)) |
| return 0; |
| |
| if (!isns_fd) { |
| err = isns_connect(); |
| if (err < 0) |
| goto out; |
| } |
| |
| mgmt = malloc(sizeof(*mgmt)); |
| if (!mgmt) |
| return 0; |
| list_add_tail(&mgmt->qlist, &qry_list); |
| |
| memset(buf, 0, sizeof(buf)); |
| tlv = (struct isns_tlv *)hdr->pdu; |
| max_buf = sizeof(buf) - offsetof(struct isns_hdr, pdu); |
| |
| if (name) |
| snprintf(mgmt->name, sizeof(mgmt->name), "%s", name); |
| else { |
| mgmt->name[0] = '\0'; |
| target = list_entry(targets_list.q_forw, struct target, tlist); |
| name = target->name; |
| } |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(name) + 1, name); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NODE_TYPE, |
| sizeof(node), &node); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, 0, 0, 0); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, 0, 0); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NODE_TYPE, 0, 0); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_PORTAL_IP_ADDRESS, 0, 0); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; |
| isns_hdr_init(hdr, ISNS_FUNC_DEV_ATTR_QRY, length, flags, |
| ++transaction, 0); |
| mgmt->transaction = transaction; |
| |
| err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); |
| if (err < 0) |
| log_error("%s %d: %s", __func__, __LINE__, strerror(errno)); |
| |
| out: |
| return err; |
| } |
| |
| static int isns_deregister(void) |
| { |
| int err; |
| uint16_t flags, length = 0; |
| char buf[4096]; |
| struct isns_hdr *hdr = (struct isns_hdr *)buf; |
| struct isns_tlv *tlv; |
| struct target *target; |
| int max_buf; |
| |
| if (list_empty(&targets_list)) |
| return 0; |
| |
| if (!isns_fd) { |
| err = isns_connect(); |
| if (err < 0) |
| goto out; |
| } |
| |
| memset(buf, 0, sizeof(buf)); |
| tlv = (struct isns_tlv *)hdr->pdu; |
| max_buf = sizeof(buf) - offsetof(struct isns_hdr, pdu); |
| |
| target = list_entry(targets_list.q_forw, struct target, tlist); |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(target->name) + 1, target->name); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, 0, 0, 0); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ENTITY_IDENTIFIER, |
| strlen(eid) + 1, eid); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; |
| isns_hdr_init(hdr, ISNS_FUNC_DEV_DEREG, length, flags, |
| ++transaction, 0); |
| |
| err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); |
| if (err < 0) |
| log_error("%s %d: %s", __func__, __LINE__, strerror(errno)); |
| out: |
| return err; |
| } |
| |
| int isns_target_register(char *name) |
| { |
| char buf[4096]; |
| uint16_t flags = 0, length = 0; |
| struct isns_hdr *hdr = (struct isns_hdr *)buf; |
| struct isns_tlv *tlv; |
| uint32_t port = htonl(server_port); |
| uint32_t node = htonl(ISNS_NODE_TARGET); |
| uint32_t type = htonl(2); |
| struct target *target; |
| int err, initial = list_length_is_one(&targets_list); |
| int max_buf; |
| |
| if (isns_server == NULL) |
| return 0; |
| |
| if (!isns_fd) { |
| err = isns_connect(); |
| if (err < 0) |
| return err; |
| } |
| |
| memset(buf, 0, sizeof(buf)); |
| tlv = (struct isns_tlv *)hdr->pdu; |
| max_buf = sizeof(buf) - offsetof(struct isns_hdr, pdu); |
| |
| if (strlen(isns_entity_target_name) < 1) { |
| target = list_entry(targets_list.q_forw, struct target, tlist); |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(target->name) + 1, target->name); |
| } else { |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(isns_entity_target_name) + 1, isns_entity_target_name); |
| } |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ENTITY_IDENTIFIER, |
| strlen(eid) + 1, eid); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, 0, 0, 0); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ENTITY_IDENTIFIER, |
| strlen(eid) + 1, eid); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| if (initial) { |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ENTITY_PROTOCOL, |
| sizeof(type), &type); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_PORTAL_IP_ADDRESS, |
| sizeof(ip), &ip); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_PORTAL_PORT, |
| sizeof(port), &port); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| flags = ISNS_FLAG_REPLACE; |
| |
| if (scn_listen_port) { |
| uint32_t sport = htonl(scn_listen_port); |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_SCN_PORT, |
| sizeof(sport), &sport); |
| if (err < 0) |
| goto out; |
| length += err; |
| } |
| } |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(name) + 1, name); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NODE_TYPE, |
| sizeof(node), &node); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| flags |= ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; |
| isns_hdr_init(hdr, ISNS_FUNC_DEV_ATTR_REG, length, flags, |
| ++transaction, 0); |
| |
| err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); |
| if (err < 0) |
| log_error("%s %d: %s", __func__, __LINE__, strerror(errno)); |
| |
| if (scn_listen_port) |
| isns_scn_register(); |
| |
| isns_attr_query(name); |
| |
| out: |
| return err; |
| } |
| |
| static void free_all_acl(struct target *target) |
| { |
| struct isns_initiator *ini; |
| |
| while (!list_empty(&target->isns_head)) { |
| ini = list_entry(target->isns_head.q_forw, typeof(*ini), ilist); |
| list_del(&ini->ilist); |
| free(ini); |
| } |
| } |
| |
| int isns_target_deregister(char *name) |
| { |
| char buf[4096]; |
| uint16_t flags, length = 0; |
| struct isns_hdr *hdr = (struct isns_hdr *)buf; |
| struct isns_tlv *tlv; |
| int err, last = list_empty(&targets_list); |
| struct target *target; |
| int max_buf; |
| |
| target = target_find_by_name(name); |
| if (target) |
| free_all_acl(target); |
| |
| if (isns_server == NULL) |
| return 0; |
| |
| if (!isns_fd) { |
| err = isns_connect(); |
| if (err < 0) |
| goto out; |
| } |
| |
| isns_scn_deregister(name); |
| |
| memset(buf, 0, sizeof(buf)); |
| tlv = (struct isns_tlv *)hdr->pdu; |
| max_buf = sizeof(buf) - offsetof(struct isns_hdr, pdu); |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(name) + 1, name); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, 0, 0, 0); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| if (last) { |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ENTITY_IDENTIFIER, |
| strlen(eid) + 1, eid); |
| if (err < 0) |
| goto out; |
| length += err; |
| } else { |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(name) + 1, name); |
| if (err < 0) |
| goto out; |
| length += err; |
| } |
| |
| flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; |
| isns_hdr_init(hdr, ISNS_FUNC_DEV_DEREG, length, flags, |
| ++transaction, 0); |
| |
| err = write(isns_fd, buf, length + sizeof(struct isns_hdr)); |
| if (err < 0) |
| log_error("%s %d: %s", __func__, __LINE__, strerror(errno)); |
| |
| out: |
| return err; |
| } |
| |
| static int recv_hdr(int fd, struct isns_io *rx, struct isns_hdr *hdr) |
| { |
| int err; |
| |
| if (rx->offset < sizeof(*hdr)) { |
| err = read(fd, rx->buf + rx->offset, |
| sizeof(*hdr) - rx->offset); |
| if (err < 0) { |
| if (errno == EAGAIN || errno == EINTR) |
| return -EAGAIN; |
| log_error("header read error %d %d %s %d", |
| fd, err, strerror(errno), rx->offset); |
| return -1; |
| } else if (err == 0) |
| return -1; |
| |
| log_debug(1, "header %d %d bytes!", fd, err); |
| rx->offset += err; |
| |
| if (rx->offset < sizeof(*hdr)) { |
| log_debug(1, "header wait %d %d", rx->offset, err); |
| return -EAGAIN; |
| } |
| } |
| |
| return 0; |
| } |
| |
| #define get_hdr_param(hdr, function, length, flags, transaction, sequence) \ |
| { \ |
| function = ntohs(hdr->function); \ |
| length = ntohs(hdr->length); \ |
| flags = ntohs(hdr->flags); \ |
| transaction = ntohs(hdr->transaction); \ |
| sequence = ntohs(hdr->sequence); \ |
| } |
| |
| static int recv_pdu(int fd, struct isns_io *rx, struct isns_hdr *hdr) |
| { |
| uint16_t function, length, flags, transaction, sequence; |
| int err; |
| |
| err = recv_hdr(fd, rx, hdr); |
| if (err) |
| return err; |
| |
| /* Now we got a complete header */ |
| get_hdr_param(hdr, function, length, flags, transaction, sequence); |
| log_debug(1, "got a header %x %u %x %u %u", function, length, flags, |
| transaction, sequence); |
| |
| if (length + sizeof(*hdr) > BUFSIZE) { |
| log_error("ToDo: we cannot handle this yet %u!", length); |
| return -1; |
| } |
| |
| if (rx->offset < length + sizeof(*hdr)) { |
| err = read(fd, rx->buf + rx->offset, |
| length + sizeof(*hdr) - rx->offset); |
| if (err < 0) { |
| if (errno == EAGAIN || errno == EINTR) |
| return -EAGAIN; |
| log_error("pdu read error %d %d %s %d", |
| fd, err, strerror(errno), rx->offset); |
| return -1; |
| } else if (err == 0) |
| return -1; |
| |
| log_debug(1, "pdu %u %u", fd, err); |
| rx->offset += err; |
| |
| if (rx->offset < length + sizeof(*hdr)) { |
| log_error("pdu wait %d %d", rx->offset, err); |
| return -EAGAIN; |
| } |
| } |
| |
| /* Now we got everything. */ |
| rx->offset = 0; |
| |
| return 0; |
| } |
| |
| #define print_unknown_pdu(hdr) \ |
| { \ |
| uint16_t function, length, flags, transaction, sequence; \ |
| get_hdr_param(hdr, function, length, flags, transaction, \ |
| sequence) \ |
| log_error("%s %d: unknown function %x %u %x %u %u", \ |
| __func__, __LINE__, \ |
| function, length, flags, transaction, sequence); \ |
| } |
| |
| static char *print_scn_pdu(struct isns_hdr *hdr) |
| { |
| struct isns_tlv *tlv = (struct isns_tlv *)hdr->pdu; |
| uint16_t function __attribute__((unused)); |
| uint16_t length; |
| uint16_t flags __attribute__((unused)); |
| uint16_t transaction __attribute__((unused)); |
| uint16_t sequence __attribute__((unused)); |
| char *name = NULL; |
| |
| get_hdr_param(hdr, function, length, flags, transaction, sequence); |
| |
| while (length) { |
| uint32_t vlen = ntohl(tlv->length); |
| |
| if (vlen + sizeof(*tlv) > length) |
| vlen = length - sizeof(*tlv); |
| |
| if (vlen < 4) |
| goto next; |
| |
| switch (ntohl(tlv->tag)) { |
| case ISNS_ATTR_ISCSI_NAME: |
| ((char *)tlv->value)[vlen-1] = '\0'; |
| log_debug(3, "scn name: %u, %s", vlen, (char *)tlv->value); |
| if (!name) |
| name = (char *)tlv->value; |
| break; |
| case ISNS_ATTR_TIMESTAMP: |
| if (vlen < 8) |
| goto next; |
| /* log_debug(3, "%u : %u : %" PRIx64, ntohl(tlv->tag), vlen, */ |
| /* *((uint64_t *)tlv->value)); */ |
| break; |
| case ISNS_ATTR_ISCSI_SCN_BITMAP: |
| log_debug(3, "scn bitmap : %x", *((uint32_t *)tlv->value)); |
| break; |
| } |
| |
| next: |
| length -= (sizeof(*tlv) + vlen); |
| tlv = (struct isns_tlv *)((char *)tlv->value + vlen); |
| } |
| |
| return name; |
| } |
| |
| static void qry_rsp_handle(struct isns_hdr *hdr) |
| { |
| struct isns_tlv *tlv; |
| uint16_t function __attribute__((unused)); |
| uint16_t flags __attribute__((unused)); |
| uint16_t sequence __attribute__((unused)); |
| uint16_t length, transaction; |
| uint32_t status = (uint32_t) (*hdr->pdu); |
| struct isns_qry_mgmt *mgmt, *n; |
| struct target *target; |
| struct isns_initiator *ini; |
| char *name = NULL; |
| |
| get_hdr_param(hdr, function, length, flags, transaction, sequence); |
| |
| list_for_each_entry_safe(mgmt, n, &qry_list, qlist) { |
| if (mgmt->transaction == transaction) { |
| list_del(&mgmt->qlist); |
| goto found; |
| } |
| } |
| |
| log_error("%s %d: transaction not found %u", |
| __func__, __LINE__, transaction); |
| |
| return; |
| |
| found: |
| if (status) { |
| log_error("%s %d: error response %u", |
| __func__, __LINE__, status); |
| |
| goto free_qry_mgmt; |
| } |
| |
| if (!strlen(mgmt->name)) { |
| log_debug(1, "%s %d: skip %u", |
| __func__, __LINE__, transaction); |
| goto free_qry_mgmt; |
| } |
| |
| target = target_find_by_name(mgmt->name); |
| if (!target) { |
| log_error("%s %d: invalid tid %s", |
| __func__, __LINE__, mgmt->name); |
| goto free_qry_mgmt; |
| } |
| |
| free_all_acl(target); |
| |
| /* skip status */ |
| if (length < 4) |
| goto free_qry_mgmt; |
| tlv = (struct isns_tlv *)((char *)hdr->pdu + 4); |
| length -= 4; |
| |
| while (length) { |
| uint32_t vlen = ntohl(tlv->length); |
| |
| if (vlen + sizeof(*tlv) > length) |
| vlen = length - sizeof(*tlv); |
| |
| if (vlen < 4) |
| goto next; |
| |
| switch (ntohl(tlv->tag)) { |
| case ISNS_ATTR_ISCSI_NAME: |
| ((char *)tlv->value)[vlen-1] = '\0'; |
| name = (char *)tlv->value; |
| break; |
| case ISNS_ATTR_ISCSI_NODE_TYPE: |
| if (ntohl(*(tlv->value)) == ISNS_NODE_INITIATOR && name) { |
| log_debug(3, "%s %d: %s", __func__, __LINE__, |
| (char *)name); |
| ini = malloc(sizeof(*ini)); |
| if (!ini) |
| goto free_qry_mgmt; |
| snprintf(ini->name, sizeof(ini->name), "%s", |
| name); |
| list_add_tail(&ini->ilist, &target->isns_head); |
| } else |
| name = NULL; |
| break; |
| default: |
| name = NULL; |
| break; |
| } |
| |
| next: |
| length -= (sizeof(*tlv) + vlen); |
| tlv = (struct isns_tlv *)((char *)tlv->value + vlen); |
| } |
| |
| free_qry_mgmt: |
| free(mgmt); |
| } |
| |
| int isns_handle(int is_timeout) |
| { |
| int err; |
| struct isns_io *rx = &isns_rx; |
| struct isns_hdr *hdr = (struct isns_hdr *)rx->buf; |
| uint16_t function; |
| uint16_t length __attribute__((unused)); |
| uint16_t flags __attribute__((unused)); |
| uint16_t transaction __attribute__((unused)); |
| uint16_t sequence __attribute__((unused)); |
| char *name = NULL; |
| |
| if (isns_server == NULL) |
| return 0; |
| |
| if (is_timeout) |
| return isns_attr_query(NULL); |
| |
| err = recv_pdu(isns_fd, rx, hdr); |
| if (err) { |
| if (err == -EAGAIN) |
| return err; |
| log_debug(1, "%s %d: close connection %d", __func__, __LINE__, |
| isns_fd); |
| close(isns_fd); |
| isns_fd = 0; |
| isns_set_fd(0, scn_listen_fd, scn_fd); |
| return err; |
| } |
| |
| get_hdr_param(hdr, function, length, flags, transaction, sequence); |
| |
| switch (function) { |
| case ISNS_FUNC_DEV_ATTR_REG_RSP: |
| break; |
| case ISNS_FUNC_DEV_ATTR_QRY_RSP: |
| qry_rsp_handle(hdr); |
| break; |
| case ISNS_FUNC_DEV_DEREG_RSP: |
| case ISNS_FUNC_SCN_REG_RSP: |
| case ISNS_FUNC_SCN_DEREG_RSP: |
| break; |
| case ISNS_FUNC_SCN: |
| name = print_scn_pdu(hdr); |
| if (name) { |
| log_debug(3, "%s %d: %s", __func__, __LINE__, name); |
| isns_attr_query(name); |
| } |
| break; |
| default: |
| print_unknown_pdu(hdr); |
| } |
| |
| return 0; |
| } |
| |
| static int scn_accept_connection(void) |
| { |
| union { |
| struct sockaddr sa; |
| struct sockaddr_in sin; |
| struct sockaddr_in6 sin6; |
| } from; |
| socklen_t slen; |
| int fd, err, opt = 1; |
| |
| if (isns_server == NULL) { |
| /* |
| * Sometimes we have (leftover?) events after disable iSNS |
| * server, so ignore them |
| */ |
| goto out; |
| } |
| |
| slen = sizeof(from); |
| fd = accept(scn_listen_fd, &from.sa, &slen); |
| if (fd < 0) { |
| log_error("%s %d: accept error: %s", __func__, __LINE__, |
| strerror(errno)); |
| return -errno; |
| } |
| log_info("Accept scn connection %d", fd); |
| |
| err = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); |
| if (err) |
| log_error("%s %d: %s\n", __func__, __LINE__, |
| strerror(errno)); |
| /* not critical, so ignore. */ |
| |
| scn_fd = fd; |
| isns_set_fd(isns_fd, scn_listen_fd, scn_fd); |
| |
| out: |
| return 0; |
| } |
| |
| static int send_scn_rsp(char *name, uint16_t transaction) |
| { |
| char buf[1024]; |
| struct isns_hdr *hdr = (struct isns_hdr *)buf; |
| struct isns_tlv *tlv; |
| uint16_t flags, length = 0; |
| int err, max_buf; |
| |
| memset(buf, 0, sizeof(buf)); |
| *((uint32_t *)hdr->pdu) = 0; |
| max_buf = sizeof(buf) - offsetof(struct isns_hdr, pdu); |
| tlv = (struct isns_tlv *)((char *)hdr->pdu + 4); |
| length += 4; |
| |
| err = isns_tlv_set(&tlv, max_buf - length, ISNS_ATTR_ISCSI_NAME, |
| strlen(name) + 1, name); |
| if (err < 0) |
| goto out; |
| length += err; |
| |
| flags = ISNS_FLAG_CLIENT | ISNS_FLAG_LAST_PDU | ISNS_FLAG_FIRST_PDU; |
| isns_hdr_init(hdr, ISNS_FUNC_SCN_RSP, length, flags, transaction, 0); |
| |
| err = write(scn_fd, buf, length + sizeof(struct isns_hdr)); |
| if (err < 0) |
| log_error("%s %d: %s", __func__, __LINE__, strerror(errno)); |
| |
| out: |
| return err; |
| } |
| |
| int isns_scn_handle(int is_accept) |
| { |
| int err; |
| struct isns_io *rx = &scn_rx; |
| struct isns_hdr *hdr = (struct isns_hdr *)rx->buf; |
| uint16_t function, transaction; |
| uint16_t length __attribute__((unused)); |
| uint16_t flags __attribute__((unused)); |
| uint16_t sequence __attribute__((unused)); |
| char *name = NULL; |
| |
| log_debug(3, "%s %d: %d", __func__, __LINE__, is_accept); |
| |
| if (is_accept) |
| return scn_accept_connection(); |
| |
| err = recv_pdu(scn_fd, rx, hdr); |
| if (err) { |
| if (err == -EAGAIN) |
| return err; |
| log_debug(1, "%s %d: close connection %d", __func__, __LINE__, |
| scn_fd); |
| close(scn_fd); |
| scn_fd = 0; |
| isns_set_fd(isns_fd, scn_listen_fd, 0); |
| return err; |
| } |
| |
| get_hdr_param(hdr, function, length, flags, transaction, sequence); |
| |
| switch (function) { |
| case ISNS_FUNC_SCN: |
| name = print_scn_pdu(hdr); |
| break; |
| default: |
| print_unknown_pdu(hdr); |
| } |
| |
| if (name) { |
| send_scn_rsp(name, transaction); |
| isns_attr_query(name); |
| } |
| |
| return 0; |
| } |
| |
| static int scn_init(void) |
| { |
| int fd, opt, err; |
| union { |
| struct sockaddr sa; |
| struct sockaddr_in sin; |
| struct sockaddr_in6 sin6; |
| } lss; |
| socklen_t slen; |
| |
| fd = socket(ss.ss_family, SOCK_STREAM, IPPROTO_TCP); |
| if (fd < 0) { |
| log_error("%s %d: %s\n", __func__, __LINE__, strerror(errno)); |
| err = -errno; |
| goto out; |
| } |
| |
| opt = 1; |
| if (ss.ss_family == AF_INET6) { |
| err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); |
| if (err) { |
| log_error("%s %d: %s\n", __func__, __LINE__, |
| strerror(errno)); |
| goto out_close; |
| } |
| } |
| |
| err = listen(fd, 5); |
| if (err) { |
| log_error("%s %d: %s\n", __func__, __LINE__, strerror(errno)); |
| goto out_close; |
| } |
| |
| slen = sizeof(lss); |
| err = getsockname(fd, (struct sockaddr *)&lss, &slen); |
| if (err) { |
| log_error("%s %d: %s\n", __func__, __LINE__, strerror(errno)); |
| goto out_close; |
| } |
| |
| /* protocol independent way ? */ |
| if (lss.sa.sa_family == AF_INET6) |
| scn_listen_port = ntohs(lss.sin6.sin6_port); |
| else |
| scn_listen_port = ntohs(lss.sin.sin_port); |
| |
| log_info("scn listen port %u %d %d\n", scn_listen_port, fd, err); |
| |
| out_close: |
| if (err) |
| close(fd); |
| else { |
| scn_listen_fd = fd; |
| isns_set_fd(isns_fd, scn_listen_fd, scn_fd); |
| } |
| |
| out: |
| return err; |
| } |
| |
| int isns_init(void) |
| { |
| int err; |
| char port[8]; |
| struct addrinfo hints, *res; |
| |
| snprintf(port, sizeof(port), "%d", ISNS_PORT); |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_socktype = SOCK_STREAM; |
| err = getaddrinfo(isns_server, (char *)&port, &hints, &res); |
| if (err) { |
| log_error("getaddrinfo error: %s, %s", get_error_str(err), |
| isns_server); |
| goto out; |
| } |
| memcpy(&ss, res->ai_addr, sizeof(*res->ai_addr)); |
| freeaddrinfo(res); |
| |
| rxbuf = calloc(2, BUFSIZE); |
| if (!rxbuf) { |
| log_error("oom!"); |
| err = -ENOMEM; |
| goto out; |
| } |
| |
| err = scn_init(); |
| if (err != 0) |
| goto out_free; |
| |
| isns_rx.buf = rxbuf; |
| isns_rx.offset = 0; |
| scn_rx.buf = rxbuf + BUFSIZE; |
| scn_rx.offset = 0; |
| |
| isns_timeout = current_timeout * 1000; |
| |
| err = isns_connect(); |
| if (err > 0) |
| err = 0; |
| |
| out: |
| return err; |
| |
| out_free: |
| free(rxbuf); |
| goto out; |
| } |
| |
| void isns_exit(void) |
| { |
| struct target *target; |
| |
| if (isns_server == NULL) |
| goto out; |
| |
| if (!isns_fd) |
| goto close; |
| |
| list_for_each_entry(target, &targets_list, tlist) |
| isns_scn_deregister(target->name); |
| |
| isns_deregister(); |
| /* we can't receive events any more. */ |
| isns_set_fd(0, 0, 0); |
| |
| close: |
| if (isns_fd) { |
| close(isns_fd); |
| isns_fd = 0; |
| } |
| if (scn_listen_fd) { |
| close(scn_listen_fd); |
| scn_listen_fd = 0; |
| } |
| if (scn_fd) { |
| close(scn_fd); |
| scn_fd = 0; |
| } |
| |
| free(rxbuf); |
| rxbuf = NULL; |
| |
| free(isns_server); |
| isns_server = NULL; |
| |
| isns_timeout = -1; |
| |
| out: |
| return; |
| } |