1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
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 *
text_new_request(struct connection * conn,uint32_t ttt)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 *
text_receive_request(struct connection * conn)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 *
text_new_response(struct pdu * request,uint32_t ttt,bool final)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 *
text_receive_response(struct connection * conn)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
text_send_request(struct connection * conn,struct keys * request_keys)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 *
text_read_response(struct connection * conn)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 *
text_read_request(struct connection * conn,struct pdu ** requestp)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
text_send_response(struct pdu * request,struct keys * response_keys)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