1 /*
2 iscsi test-tool multipath support
3
4 Copyright (C) 2015 David Disseldorp
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #define _GNU_SOURCE
23 #include <assert.h>
24 #include <sys/syscall.h>
25 #include <dlfcn.h>
26 #include <sys/types.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <stdint.h>
30 #include <stdarg.h>
31 #include <inttypes.h>
32 #include <string.h>
33 #include <poll.h>
34 #include <fnmatch.h>
35 #include <errno.h>
36
37 #ifdef HAVE_SG_IO
38 #include <fcntl.h>
39 #include <sys/ioctl.h>
40 #include <scsi/sg.h>
41 #endif
42
43 #include "slist.h"
44 #include "iscsi.h"
45 #include "scsi-lowlevel.h"
46 #include "iscsi-private.h"
47 #include "iscsi-support.h"
48 #include "iscsi-multipath.h"
49
50 int mp_num_sds = 0;
51 struct scsi_device *mp_sds[MPATH_MAX_DEVS];
52
53 static void
mpath_des_free(struct scsi_inquiry_device_designator * des)54 mpath_des_free(struct scsi_inquiry_device_designator *des)
55 {
56 if (!des) {
57 return;
58 }
59
60 free(des->designator);
61 free(des);
62 }
63
64 static int
mpath_des_copy(struct scsi_inquiry_device_designator * des,struct scsi_inquiry_device_designator ** _des_cp)65 mpath_des_copy(struct scsi_inquiry_device_designator *des,
66 struct scsi_inquiry_device_designator **_des_cp)
67 {
68 struct scsi_inquiry_device_designator *des_cp;
69
70 if (!_des_cp) {
71 return -1;
72 }
73
74 des_cp = malloc(sizeof(*des_cp));
75 if (des_cp == NULL) {
76 return -1;
77 }
78
79 des_cp->protocol_identifier = des->protocol_identifier;
80 des_cp->code_set = des->code_set;
81 des_cp->piv = des->piv;
82 des_cp->association = des->association;
83 des_cp->designator_type = des->designator_type;
84 des_cp->designator_length = des->designator_length;
85 des_cp->designator = malloc(des->designator_length);
86 if (des_cp->designator == NULL) {
87 free(des_cp);
88 return -1;
89 }
90 memcpy(des_cp->designator, des->designator, des->designator_length);
91 *_des_cp = des_cp;
92
93 return 0;
94 }
95
96 static int
mpath_des_cmp(struct scsi_inquiry_device_designator * des1,struct scsi_inquiry_device_designator * des2)97 mpath_des_cmp(struct scsi_inquiry_device_designator *des1,
98 struct scsi_inquiry_device_designator *des2)
99 {
100 if (des1->protocol_identifier != des2->protocol_identifier) {
101 return -1;
102 }
103
104 if (des1->code_set != des2->code_set) {
105 return -1;
106 }
107
108 if (des1->piv != des2->piv) {
109 return -1;
110 }
111
112 if (des1->association != des2->association) {
113 return -1;
114 }
115
116 if (des1->designator_type != des2->designator_type) {
117 return -1;
118 }
119
120 if (des1->designator_length != des2->designator_length) {
121 return -1;
122 }
123
124 return memcmp(des1->designator, des2->designator,
125 des1->designator_length);
126 }
127
128 static int
mpath_check_matching_ids_devid_vpd(int num_sds,struct scsi_device ** sds)129 mpath_check_matching_ids_devid_vpd(int num_sds,
130 struct scsi_device **sds)
131 {
132 int i;
133 int num_sds_with_valid_id = 0;
134 struct scsi_task *inq_task = NULL;
135 struct scsi_inquiry_device_designator *des_saved = NULL;
136
137 for (i = 0; i < num_sds; i++) {
138 int ret;
139 int full_size;
140 struct scsi_inquiry_device_identification *inq_id_data;
141 struct scsi_inquiry_device_designator *des;
142
143 /*
144 * dev ID inquiry to confirm that all multipath devices carry
145 * an identical logical unit identifier.
146 */
147 inquiry(sds[i], &inq_task, 1,
148 SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION,
149 64,
150 EXPECT_STATUS_GOOD);
151 if (inq_task == NULL || inq_task->status != SCSI_STATUS_GOOD) {
152 printf("Inquiry command failed : %s\n",
153 sds[i]->error_str);
154 goto err_cleanup;
155 }
156 full_size = scsi_datain_getfullsize(inq_task);
157 if (full_size > inq_task->datain.size) {
158 /* we need more data */
159 scsi_free_scsi_task(inq_task);
160 inq_task = NULL;
161 inquiry(sds[i], &inq_task, 1,
162 SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION,
163 full_size,
164 EXPECT_STATUS_GOOD);
165 if (inq_task == NULL) {
166 printf("Inquiry command failed : %s\n",
167 sds[i]->error_str);
168 goto err_cleanup;
169 }
170 }
171
172 inq_id_data = scsi_datain_unmarshall(inq_task);
173 if (inq_id_data == NULL) {
174 printf("failed to unmarshall inquiry ID datain blob\n");
175 goto err_cleanup;
176 }
177
178 if (inq_id_data->qualifier
179 != SCSI_INQUIRY_PERIPHERAL_QUALIFIER_CONNECTED) {
180 printf("error: multipath device not connected\n");
181 goto err_cleanup;
182 }
183
184 if (inq_id_data->device_type
185 != SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS) {
186 printf("error: multipath devices must be SBC\n");
187 goto err_cleanup;
188 }
189
190 /* walk the list of IDs, and find a suitable LU candidate */
191 for (des = inq_id_data->designators;
192 des != NULL;
193 des = des->next) {
194 if (des->association != SCSI_ASSOCIATION_LOGICAL_UNIT) {
195 printf("skipping non-LU designator: %d\n",
196 des->association);
197 continue;
198 }
199
200 if ((des->designator_type != SCSI_DESIGNATOR_TYPE_EUI_64)
201 && (des->designator_type != SCSI_DESIGNATOR_TYPE_NAA)
202 && (des->designator_type != SCSI_DESIGNATOR_TYPE_MD5_LOGICAL_UNIT_IDENTIFIER)
203 && (des->designator_type != SCSI_DESIGNATOR_TYPE_SCSI_NAME_STRING)) {
204 printf("skipping unsupported des type: %d\n",
205 des->designator_type);
206 continue;
207 }
208
209 if (des->designator_length <= 0) {
210 printf("skipping designator with bad len: %d\n",
211 des->designator_length);
212 continue;
213 }
214
215 if (des_saved == NULL) {
216 ret = mpath_des_copy(des, &des_saved);
217 if (ret < 0) {
218 goto err_cleanup;
219 }
220 /*
221 * we now have a reference to look for in all
222 * subsequent paths.
223 */
224 num_sds_with_valid_id++;
225 break;
226 } else if (mpath_des_cmp(des, des_saved) == 0) {
227 /* found match for previous path designator */
228 num_sds_with_valid_id++;
229 break;
230 }
231 /* no match yet, keep checking other designators */
232 }
233
234 scsi_free_scsi_task(inq_task);
235 inq_task = NULL;
236 }
237 mpath_des_free(des_saved);
238
239 if (num_sds_with_valid_id != num_sds) {
240 printf("failed to find matching LU device ID for all paths\n");
241 return -1;
242 }
243
244 printf("found matching LU device identifier for all (%d) paths\n",
245 num_sds);
246 return 0;
247
248 err_cleanup:
249 mpath_des_free(des_saved);
250 scsi_free_scsi_task(inq_task);
251 return -1;
252 }
253
254 static int
mpath_check_matching_ids_serial_vpd(int num_sds,struct scsi_device ** sds)255 mpath_check_matching_ids_serial_vpd(int num_sds,
256 struct scsi_device **sds)
257 {
258 int i;
259 int num_sds_with_valid_id = 0;
260 struct scsi_task *inq_task = NULL;
261 char *usn_saved = NULL;
262
263 for (i = 0; i < num_sds; i++) {
264 int full_size;
265 struct scsi_inquiry_unit_serial_number *inq_serial;
266
267 /*
268 * inquiry to confirm that all multipath devices carry an
269 * identical unit serial number.
270 */
271 inq_task = NULL;
272 inquiry(sds[i], &inq_task, 1,
273 SCSI_INQUIRY_PAGECODE_UNIT_SERIAL_NUMBER, 64,
274 EXPECT_STATUS_GOOD);
275 if (inq_task == NULL || inq_task->status != SCSI_STATUS_GOOD) {
276 printf("Inquiry command failed : %s\n",
277 sds[i]->error_str);
278 goto err_cleanup;
279 }
280 full_size = scsi_datain_getfullsize(inq_task);
281 if (full_size > inq_task->datain.size) {
282 scsi_free_scsi_task(inq_task);
283
284 /* we need more data */
285 inq_task = NULL;
286 inquiry(sds[i], &inq_task, 1,
287 SCSI_INQUIRY_PAGECODE_UNIT_SERIAL_NUMBER,
288 full_size,
289 EXPECT_STATUS_GOOD);
290 if (inq_task == NULL) {
291 printf("Inquiry command failed : %s\n",
292 sds[i]->error_str);
293 goto err_cleanup;
294 }
295 }
296
297 inq_serial = scsi_datain_unmarshall(inq_task);
298 if (inq_serial == NULL) {
299 printf("failed to unmarshall inquiry datain blob\n");
300 goto err_cleanup;
301 }
302
303 if (inq_serial->qualifier
304 != SCSI_INQUIRY_PERIPHERAL_QUALIFIER_CONNECTED) {
305 printf("error: multipath device not connected\n");
306 goto err_cleanup;
307 }
308
309 if (inq_serial->device_type
310 != SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS) {
311 printf("error: multipath devices must be SBC\n");
312 goto err_cleanup;
313 }
314
315 if (inq_serial->usn == NULL) {
316 printf("error: empty usn for multipath device\n");
317 goto err_cleanup;
318 }
319
320 if (usn_saved == NULL) {
321 usn_saved = strdup(inq_serial->usn);
322 if (usn_saved == NULL) {
323 goto err_cleanup;
324 }
325 num_sds_with_valid_id++;
326 } else if (strcmp(usn_saved, inq_serial->usn) == 0) {
327 num_sds_with_valid_id++;
328 } else {
329 printf("multipath unit serial mismatch: %s != %s\n",
330 usn_saved, inq_serial->usn);
331 }
332
333 scsi_free_scsi_task(inq_task);
334 inq_task = NULL;
335 }
336
337 if (num_sds_with_valid_id != num_sds) {
338 printf("failed to find matching serial number for all paths\n");
339 goto err_cleanup;
340 }
341
342 printf("found matching serial number for all (%d) paths: %s\n",
343 num_sds, usn_saved);
344 free(usn_saved);
345
346 return 0;
347
348 err_cleanup:
349 free(usn_saved);
350 scsi_free_scsi_task(inq_task);
351 return -1;
352 }
353
354 int
mpath_check_matching_ids(int num_sds,struct scsi_device ** sds)355 mpath_check_matching_ids(int num_sds,
356 struct scsi_device **sds)
357 {
358 int ret;
359
360 /*
361 * first check all devices for a matching LU identifier in the device
362 * identification INQUIRY VPD page.
363 */
364 ret = mpath_check_matching_ids_devid_vpd(num_sds, sds);
365 if (ret == 0) {
366 return 0; /* found matching */
367 }
368
369 /* fall back to a unit serial number check */
370 ret = mpath_check_matching_ids_serial_vpd(num_sds, sds);
371 return ret;
372 }
373
374 int
mpath_count_iscsi(int num_sds,struct scsi_device ** sds)375 mpath_count_iscsi(int num_sds,
376 struct scsi_device **sds)
377 {
378 int i;
379 int found = 0;
380
381 for (i = 0; i < num_sds; i++) {
382 if (sds[i]->iscsi_ctx != NULL) {
383 found++;
384 }
385 }
386
387 return found;
388 }
389
390 /*
391 * use an existing multi-path connection, or clone iscsi sd1.
392 */
393 int
mpath_sd2_get_or_clone(struct scsi_device * sd1,struct scsi_device ** _sd2)394 mpath_sd2_get_or_clone(struct scsi_device *sd1, struct scsi_device **_sd2)
395 {
396 struct scsi_device *sd2;
397
398 if (mp_num_sds > 1) {
399 logging(LOG_VERBOSE, "using multipath dev for second session");
400 *_sd2 = mp_sds[1];
401 return 0;
402 }
403
404 if (sd1->iscsi_ctx == NULL) {
405 logging(LOG_NORMAL, "can't clone non-iscsi device");
406 return -EINVAL;
407 }
408
409 logging(LOG_VERBOSE, "cloning sd1 for second session");
410 sd2 = malloc(sizeof(*sd2));
411 if (sd2 == NULL) {
412 return -ENOMEM;
413 }
414
415 memset(sd2, 0, sizeof(*sd2));
416 sd2->iscsi_url = sd1->iscsi_url;
417 sd2->iscsi_lun = sd1->iscsi_lun;
418 sd2->iscsi_ctx = iscsi_context_login(initiatorname2, sd2->iscsi_url,
419 &sd2->iscsi_lun);
420 if (sd2->iscsi_ctx == NULL) {
421 logging(LOG_VERBOSE, "Failed to login to target");
422 free(sd2);
423 return -ENOMEM;
424 }
425 *_sd2 = sd2;
426
427 return 0;
428 }
429
430 void
mpath_sd2_put(struct scsi_device * sd2)431 mpath_sd2_put(struct scsi_device *sd2)
432 {
433 if (mp_num_sds > 1) {
434 if (sd2 != mp_sds[1]) {
435 logging(LOG_NORMAL, "Invalid sd2!");
436 }
437 return;
438 }
439
440 /* sd2 was allocated by mp_get - cleanup */
441 iscsi_logout_sync(sd2->iscsi_ctx);
442 iscsi_destroy_context(sd2->iscsi_ctx);
443 free(sd2);
444 }
445