xref: /freebsd/lib/libiscsiutil/text.c (revision bc7512cc58af2e8bbe5bbf5ca0059b1daa1da897)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2012 The FreeBSD Foundation
5  *
6  * This software was developed by Edward Tomasz Napierala under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 #include <sys/types.h>
32 #include <netinet/in.h>
33 
34 #include <stdlib.h>
35 #include <string.h>
36 
37 #include <iscsi_proto.h>
38 #include "libiscsiutil.h"
39 
40 /* Construct a new TextRequest PDU. */
41 static struct pdu *
42 text_new_request(struct connection *conn, uint32_t ttt)
43 {
44 	struct pdu *request;
45 	struct iscsi_bhs_text_request *bhstr;
46 
47 	request = pdu_new(conn);
48 	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
49 	bhstr->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_REQUEST |
50 	    ISCSI_BHS_OPCODE_IMMEDIATE;
51 	bhstr->bhstr_flags = BHSTR_FLAGS_FINAL;
52 	bhstr->bhstr_initiator_task_tag = 0;
53 	bhstr->bhstr_target_transfer_tag = ttt;
54 
55 	bhstr->bhstr_cmdsn = conn->conn_cmdsn;
56 	bhstr->bhstr_expstatsn = htonl(conn->conn_statsn + 1);
57 
58 	return (request);
59 }
60 
61 /* Receive a TextRequest PDU from a connection. */
62 static struct pdu *
63 text_receive_request(struct connection *conn)
64 {
65 	struct pdu *request;
66 	struct iscsi_bhs_text_request *bhstr;
67 
68 	request = pdu_new(conn);
69 	pdu_receive(request);
70 	if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
71 	    ISCSI_BHS_OPCODE_TEXT_REQUEST)
72 		log_errx(1, "protocol error: received invalid opcode 0x%x",
73 		    request->pdu_bhs->bhs_opcode);
74 	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
75 
76 	/*
77 	 * XXX: Implement the C flag some day.
78 	 */
79 	if ((bhstr->bhstr_flags & (BHSTR_FLAGS_FINAL | BHSTR_FLAGS_CONTINUE)) !=
80 	    BHSTR_FLAGS_FINAL)
81 		log_errx(1, "received TextRequest PDU with invalid "
82 		    "flags: %u", bhstr->bhstr_flags);
83 	if (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) {
84 		log_errx(1, "received TextRequest PDU with decreasing CmdSN: "
85 		    "was %u, is %u", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn));
86 	}
87 	conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn);
88 	if ((bhstr->bhstr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
89 		conn->conn_cmdsn++;
90 
91 	return (request);
92 }
93 
94 /* Construct a new TextResponse PDU in reply to a request. */
95 static struct pdu *
96 text_new_response(struct pdu *request, uint32_t ttt, bool final)
97 {
98 	struct pdu *response;
99 	struct connection *conn;
100 	struct iscsi_bhs_text_request *bhstr;
101 	struct iscsi_bhs_text_response *bhstr2;
102 
103 	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
104 	conn = request->pdu_connection;
105 
106 	response = pdu_new_response(request);
107 	bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs;
108 	bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE;
109 	if (final)
110 		bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL;
111 	else
112 		bhstr2->bhstr_flags = BHSTR_FLAGS_CONTINUE;
113 	bhstr2->bhstr_lun = bhstr->bhstr_lun;
114 	bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag;
115 	bhstr2->bhstr_target_transfer_tag = ttt;
116 	bhstr2->bhstr_statsn = htonl(conn->conn_statsn++);
117 	bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn);
118 	bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn);
119 
120 	return (response);
121 }
122 
123 /* Receive a TextResponse PDU from a connection. */
124 static struct pdu *
125 text_receive_response(struct connection *conn)
126 {
127 	struct pdu *response;
128 	struct iscsi_bhs_text_response *bhstr;
129 	uint8_t flags;
130 
131 	response = pdu_new(conn);
132 	pdu_receive(response);
133 	if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_TEXT_RESPONSE)
134 		log_errx(1, "protocol error: received invalid opcode 0x%x",
135 		    response->pdu_bhs->bhs_opcode);
136 	bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs;
137 	flags = bhstr->bhstr_flags & (BHSTR_FLAGS_FINAL | BHSTR_FLAGS_CONTINUE);
138 	switch (flags) {
139 	case BHSTR_FLAGS_CONTINUE:
140 		if (bhstr->bhstr_target_transfer_tag == 0xffffffff)
141 			log_errx(1, "received continue TextResponse PDU with "
142 			    "invalid TTT 0x%x",
143 			    bhstr->bhstr_target_transfer_tag);
144 		break;
145 	case BHSTR_FLAGS_FINAL:
146 		if (bhstr->bhstr_target_transfer_tag != 0xffffffff)
147 			log_errx(1, "received final TextResponse PDU with "
148 			    "invalid TTT 0x%x",
149 			    bhstr->bhstr_target_transfer_tag);
150 		break;
151 	default:
152 		log_errx(1, "received TextResponse PDU with invalid "
153 		    "flags: %u", bhstr->bhstr_flags);
154 	}
155 	if (ntohl(bhstr->bhstr_statsn) != conn->conn_statsn + 1) {
156 		log_errx(1, "received TextResponse PDU with wrong StatSN: "
157 		    "is %u, should be %u", ntohl(bhstr->bhstr_statsn),
158 		    conn->conn_statsn + 1);
159 	}
160 	conn->conn_statsn = ntohl(bhstr->bhstr_statsn);
161 
162 	return (response);
163 }
164 
165 /*
166  * Send a list of keys from the initiator to the target in a
167  * TextRequest PDU.
168  */
169 void
170 text_send_request(struct connection *conn, struct keys *request_keys)
171 {
172 	struct pdu *request;
173 
174 	request = text_new_request(conn, 0xffffffff);
175 	keys_save_pdu(request_keys, request);
176 	if (request->pdu_data_len == 0)
177 		log_errx(1, "No keys to send in a TextRequest");
178 	if (request->pdu_data_len >
179 	    (size_t)conn->conn_max_send_data_segment_length)
180 		log_errx(1, "Keys to send in TextRequest are too long");
181 
182 	pdu_send(request);
183 	pdu_delete(request);
184 }
185 
186 /*
187  * Read a list of keys from the target in a series of TextResponse
188  * PDUs.
189  */
190 struct keys *
191 text_read_response(struct connection *conn)
192 {
193 	struct keys *response_keys;
194 	char *keys_data;
195 	size_t keys_len;
196 	uint32_t ttt;
197 
198 	keys_data = NULL;
199 	keys_len = 0;
200 	ttt = 0xffffffff;
201 	for (;;) {
202 		struct pdu *request, *response;
203 		struct iscsi_bhs_text_response *bhstr;
204 
205 		response = text_receive_response(conn);
206 		bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs;
207 		if (keys_data == NULL) {
208 			ttt = bhstr->bhstr_target_transfer_tag;
209 			keys_data = response->pdu_data;
210 			keys_len = response->pdu_data_len;
211 			response->pdu_data = NULL;
212 		} else {
213 			keys_data = realloc(keys_data,
214 			    keys_len + response->pdu_data_len);
215 			if (keys_data == NULL)
216 				log_err(1, "failed to grow keys block");
217 			memcpy(keys_data + keys_len, response->pdu_data,
218 			    response->pdu_data_len);
219 			keys_len += response->pdu_data_len;
220 		}
221 		if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) != 0) {
222 			pdu_delete(response);
223 			break;
224 		}
225 		if (bhstr->bhstr_target_transfer_tag != ttt)
226 			log_errx(1, "received non-final TextRequest PDU with "
227 			    "invalid TTT 0x%x",
228 			    bhstr->bhstr_target_transfer_tag);
229 		pdu_delete(response);
230 
231 		/* Send an empty request. */
232 		request = text_new_request(conn, ttt);
233 		pdu_send(request);
234 		pdu_delete(request);
235 	}
236 
237 	response_keys = keys_new();
238 	keys_load(response_keys, keys_data, keys_len);
239 	free(keys_data);
240 	return (response_keys);
241 }
242 
243 /*
244  * Read a list of keys from the initiator in a TextRequest PDU.
245  */
246 struct keys *
247 text_read_request(struct connection *conn, struct pdu **requestp)
248 {
249 	struct iscsi_bhs_text_request *bhstr;
250 	struct pdu *request;
251 	struct keys *request_keys;
252 
253 	request = text_receive_request(conn);
254 	bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
255 	if (bhstr->bhstr_target_transfer_tag != 0xffffffff)
256 		log_errx(1, "received TextRequest PDU with invalid TTT 0x%x",
257 		    bhstr->bhstr_target_transfer_tag);
258 	if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) {
259 		log_errx(1, "received TextRequest PDU with wrong ExpStatSN: "
260 		    "is %u, should be %u", ntohl(bhstr->bhstr_expstatsn),
261 		    conn->conn_statsn);
262 	}
263 
264 	request_keys = keys_new();
265 	keys_load_pdu(request_keys, request);
266 	*requestp = request;
267 	return (request_keys);
268 }
269 
270 /*
271  * Send a response back to the initiator as a series of TextResponse
272  * PDUs.
273  */
274 void
275 text_send_response(struct pdu *request, struct keys *response_keys)
276 {
277 	struct connection *conn = request->pdu_connection;
278 	char *keys_data;
279 	size_t keys_len;
280 	size_t keys_offset;
281 	uint32_t ttt;
282 
283 	keys_save(response_keys, &keys_data, &keys_len);
284 	keys_offset = 0;
285 	ttt = keys_len;
286 	for (;;) {
287 		struct pdu *request2, *response;
288 		struct iscsi_bhs_text_request *bhstr;
289 		size_t todo;
290 		bool final;
291 
292 		todo = keys_len - keys_offset;
293 		if (todo > (size_t)conn->conn_max_send_data_segment_length) {
294 			final = false;
295 			todo = conn->conn_max_send_data_segment_length;
296 		} else {
297 			final = true;
298 			ttt = 0xffffffff;
299 		}
300 
301 		response = text_new_response(request, ttt, final);
302 		response->pdu_data = keys_data + keys_offset;
303 		response->pdu_data_len = todo;
304 		keys_offset += todo;
305 
306 		pdu_send(response);
307 		response->pdu_data = NULL;
308 		pdu_delete(response);
309 
310 		if (final)
311 			break;
312 
313 		/*
314 		 * Wait for an empty request.
315 		 *
316 		 * XXX: Linux's Open-iSCSI initiator doesn't update
317 		 * ExpStatSN when receiving a TextResponse PDU.
318 		 */
319 		request2 = text_receive_request(conn);
320 		bhstr = (struct iscsi_bhs_text_request *)request2->pdu_bhs;
321 		if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) == 0)
322 			log_errx(1, "received continuation TextRequest PDU "
323 			    "without F set");
324 		if (pdu_data_segment_length(request2) != 0)
325 			log_errx(1, "received non-empty continuation "
326 			    "TextRequest PDU");
327 		if (bhstr->bhstr_target_transfer_tag != ttt)
328 			log_errx(1, "received TextRequest PDU with invalid "
329 			    "TTT 0x%x", bhstr->bhstr_target_transfer_tag);
330 		pdu_delete(request2);
331 	}
332 	free(keys_data);
333 }
334