/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include "libiscsiutil.h" /* Construct a new TextRequest PDU. */ static struct pdu * text_new_request(struct connection *conn, uint32_t ttt) { struct pdu *request; struct iscsi_bhs_text_request *bhstr; request = pdu_new(conn); bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; bhstr->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_REQUEST | ISCSI_BHS_OPCODE_IMMEDIATE; bhstr->bhstr_flags = BHSTR_FLAGS_FINAL; bhstr->bhstr_initiator_task_tag = 0; bhstr->bhstr_target_transfer_tag = ttt; bhstr->bhstr_cmdsn = conn->conn_cmdsn; bhstr->bhstr_expstatsn = htonl(conn->conn_statsn + 1); return (request); } /* Receive a TextRequest PDU from a connection. */ static struct pdu * text_receive_request(struct connection *conn) { struct pdu *request; struct iscsi_bhs_text_request *bhstr; request = pdu_new(conn); pdu_receive(request); if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != ISCSI_BHS_OPCODE_TEXT_REQUEST) log_errx(1, "protocol error: received invalid opcode 0x%x", request->pdu_bhs->bhs_opcode); bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; /* * XXX: Implement the C flag some day. */ if ((bhstr->bhstr_flags & (BHSTR_FLAGS_FINAL | BHSTR_FLAGS_CONTINUE)) != BHSTR_FLAGS_FINAL) log_errx(1, "received TextRequest PDU with invalid " "flags: %u", bhstr->bhstr_flags); if (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) { log_errx(1, "received TextRequest PDU with decreasing CmdSN: " "was %u, is %u", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn)); } conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn); if ((bhstr->bhstr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) conn->conn_cmdsn++; return (request); } /* Construct a new TextResponse PDU in reply to a request. */ static struct pdu * text_new_response(struct pdu *request, uint32_t ttt, bool final) { struct pdu *response; struct connection *conn; struct iscsi_bhs_text_request *bhstr; struct iscsi_bhs_text_response *bhstr2; bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; conn = request->pdu_connection; response = pdu_new_response(request); bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs; bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE; if (final) bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL; else bhstr2->bhstr_flags = BHSTR_FLAGS_CONTINUE; bhstr2->bhstr_lun = bhstr->bhstr_lun; bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag; bhstr2->bhstr_target_transfer_tag = ttt; bhstr2->bhstr_statsn = htonl(conn->conn_statsn++); bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn); bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn); return (response); } /* Receive a TextResponse PDU from a connection. */ static struct pdu * text_receive_response(struct connection *conn) { struct pdu *response; struct iscsi_bhs_text_response *bhstr; uint8_t flags; response = pdu_new(conn); pdu_receive(response); if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_TEXT_RESPONSE) log_errx(1, "protocol error: received invalid opcode 0x%x", response->pdu_bhs->bhs_opcode); bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs; flags = bhstr->bhstr_flags & (BHSTR_FLAGS_FINAL | BHSTR_FLAGS_CONTINUE); switch (flags) { case BHSTR_FLAGS_CONTINUE: if (bhstr->bhstr_target_transfer_tag == 0xffffffff) log_errx(1, "received continue TextResponse PDU with " "invalid TTT 0x%x", bhstr->bhstr_target_transfer_tag); break; case BHSTR_FLAGS_FINAL: if (bhstr->bhstr_target_transfer_tag != 0xffffffff) log_errx(1, "received final TextResponse PDU with " "invalid TTT 0x%x", bhstr->bhstr_target_transfer_tag); break; default: log_errx(1, "received TextResponse PDU with invalid " "flags: %u", bhstr->bhstr_flags); } if (ntohl(bhstr->bhstr_statsn) != conn->conn_statsn + 1) { log_errx(1, "received TextResponse PDU with wrong StatSN: " "is %u, should be %u", ntohl(bhstr->bhstr_statsn), conn->conn_statsn + 1); } conn->conn_statsn = ntohl(bhstr->bhstr_statsn); return (response); } /* * Send a list of keys from the initiator to the target in a * TextRequest PDU. */ void text_send_request(struct connection *conn, struct keys *request_keys) { struct pdu *request; request = text_new_request(conn, 0xffffffff); keys_save_pdu(request_keys, request); if (request->pdu_data_len == 0) log_errx(1, "No keys to send in a TextRequest"); if (request->pdu_data_len > (size_t)conn->conn_max_send_data_segment_length) log_errx(1, "Keys to send in TextRequest are too long"); pdu_send(request); pdu_delete(request); } /* * Read a list of keys from the target in a series of TextResponse * PDUs. */ struct keys * text_read_response(struct connection *conn) { struct keys *response_keys; char *keys_data; size_t keys_len; uint32_t ttt; keys_data = NULL; keys_len = 0; ttt = 0xffffffff; for (;;) { struct pdu *request, *response; struct iscsi_bhs_text_response *bhstr; response = text_receive_response(conn); bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs; if (keys_data == NULL) { ttt = bhstr->bhstr_target_transfer_tag; keys_data = response->pdu_data; keys_len = response->pdu_data_len; response->pdu_data = NULL; } else { keys_data = realloc(keys_data, keys_len + response->pdu_data_len); if (keys_data == NULL) log_err(1, "failed to grow keys block"); memcpy(keys_data + keys_len, response->pdu_data, response->pdu_data_len); keys_len += response->pdu_data_len; } if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) != 0) { pdu_delete(response); break; } if (bhstr->bhstr_target_transfer_tag != ttt) log_errx(1, "received non-final TextRequest PDU with " "invalid TTT 0x%x", bhstr->bhstr_target_transfer_tag); pdu_delete(response); /* Send an empty request. */ request = text_new_request(conn, ttt); pdu_send(request); pdu_delete(request); } response_keys = keys_new(); keys_load(response_keys, keys_data, keys_len); free(keys_data); return (response_keys); } /* * Read a list of keys from the initiator in a TextRequest PDU. */ struct keys * text_read_request(struct connection *conn, struct pdu **requestp) { struct iscsi_bhs_text_request *bhstr; struct pdu *request; struct keys *request_keys; request = text_receive_request(conn); bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; if (bhstr->bhstr_target_transfer_tag != 0xffffffff) log_errx(1, "received TextRequest PDU with invalid TTT 0x%x", bhstr->bhstr_target_transfer_tag); if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) { log_errx(1, "received TextRequest PDU with wrong ExpStatSN: " "is %u, should be %u", ntohl(bhstr->bhstr_expstatsn), conn->conn_statsn); } request_keys = keys_new(); keys_load_pdu(request_keys, request); *requestp = request; return (request_keys); } /* * Send a response back to the initiator as a series of TextResponse * PDUs. */ void text_send_response(struct pdu *request, struct keys *response_keys) { struct connection *conn = request->pdu_connection; char *keys_data; size_t keys_len; size_t keys_offset; uint32_t ttt; keys_save(response_keys, &keys_data, &keys_len); keys_offset = 0; ttt = keys_len; for (;;) { struct pdu *request2, *response; struct iscsi_bhs_text_request *bhstr; size_t todo; bool final; todo = keys_len - keys_offset; if (todo > (size_t)conn->conn_max_send_data_segment_length) { final = false; todo = conn->conn_max_send_data_segment_length; } else { final = true; ttt = 0xffffffff; } response = text_new_response(request, ttt, final); response->pdu_data = keys_data + keys_offset; response->pdu_data_len = todo; keys_offset += todo; pdu_send(response); response->pdu_data = NULL; pdu_delete(response); if (final) break; /* * Wait for an empty request. * * XXX: Linux's Open-iSCSI initiator doesn't update * ExpStatSN when receiving a TextResponse PDU. */ request2 = text_receive_request(conn); bhstr = (struct iscsi_bhs_text_request *)request2->pdu_bhs; if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) == 0) log_errx(1, "received continuation TextRequest PDU " "without F set"); if (pdu_data_segment_length(request2) != 0) log_errx(1, "received non-empty continuation " "TextRequest PDU"); if (bhstr->bhstr_target_transfer_tag != ttt) log_errx(1, "received TextRequest PDU with invalid " "TTT 0x%x", bhstr->bhstr_target_transfer_tag); pdu_delete(request2); } free(keys_data); }