From 84d80878a6bafa0f662904918c7e0e81eb6a4a7f Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Mon, 23 Sep 2019 16:46:26 +0200 Subject: [PATCH] test-tool: add iSCSI CHAP_A negotiation tests The iSCSICHAP.Simple test ensures that CHAP_A comma separated lists are correctly handled. iSCSICHAP.Invalid tests a range of invalid CHAP_A parameters. Signed-off-by: David Disseldorp --- test-tool/Makefile.am | 1 + test-tool/iscsi-test-cu.c | 9 ++ test-tool/iscsi-test-cu.h | 2 + test-tool/test_iscsi_chap.c | 221 ++++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 test-tool/test_iscsi_chap.c diff --git a/test-tool/Makefile.am b/test-tool/Makefile.am index f9faa90..642704b 100644 --- a/test-tool/Makefile.am +++ b/test-tool/Makefile.am @@ -42,6 +42,7 @@ iscsi_test_cu_SOURCES = iscsi-test-cu.c \ test_iscsi_datasn_invalid.c \ test_iscsi_sendtargets.c \ test_iscsi_nop_simple.c \ + test_iscsi_chap.c \ test_mandatory_sbc.c \ test_modesense6_all_pages.c \ test_modesense6_control.c \ diff --git a/test-tool/iscsi-test-cu.c b/test-tool/iscsi-test-cu.c index 670a705..7a795e4 100644 --- a/test-tool/iscsi-test-cu.c +++ b/test-tool/iscsi-test-cu.c @@ -569,6 +569,12 @@ static CU_TestInfo tests_iscsi_nop[] = { CU_TEST_INFO_NULL }; +static CU_TestInfo tests_iscsi_chap[] = { + { (char *)"Simple", test_iscsi_chap_simple }, + { (char *)"Invalid", test_iscsi_chap_invalid }, + CU_TEST_INFO_NULL +}; + static CU_TestInfo tests_iscsi_residuals[] = { { (char *)"Read10Invalid", test_read10_invalid }, { (char *)"Read10Residuals", test_read10_residuals }, @@ -603,6 +609,8 @@ static libiscsi_suite_info iscsi_suites[] = { tests_iscsi_sendtargets }, { "iSCSINop", NON_PGR_FUNCS, tests_iscsi_nop }, + { "iSCSICHAP", NON_PGR_FUNCS, + tests_iscsi_chap }, { NULL, NULL, NULL, NULL, NULL, NULL } }; @@ -662,6 +670,7 @@ static libiscsi_suite_info all_suites[] = { { "iSCSITMF", NON_PGR_FUNCS, tests_iscsi_tmf }, { "iSCSISendTargets", NON_PGR_FUNCS, tests_iscsi_sendtargets }, { "iSCSINop", NON_PGR_FUNCS, tests_iscsi_nop }, + { "iSCSICHAP", NON_PGR_FUNCS, tests_iscsi_chap }, { "MultipathIO", NON_PGR_FUNCS, tests_multipathio }, { NULL, NULL, NULL, NULL, NULL, NULL }, }; diff --git a/test-tool/iscsi-test-cu.h b/test-tool/iscsi-test-cu.h index b05bbe2..2cf26a5 100644 --- a/test-tool/iscsi-test-cu.h +++ b/test-tool/iscsi-test-cu.h @@ -81,6 +81,8 @@ void test_iscsi_datasn_invalid(void); void test_iscsi_sendtargets_simple(void); void test_iscsi_sendtargets_invalid(void); void test_iscsi_nop_simple(void); +void test_iscsi_chap_simple(void); +void test_iscsi_chap_invalid(void); void test_mandatory_sbc(void); diff --git a/test-tool/test_iscsi_chap.c b/test-tool/test_iscsi_chap.c new file mode 100644 index 0000000..ebefc1e --- /dev/null +++ b/test-tool/test_iscsi_chap.c @@ -0,0 +1,221 @@ +/* + Copyright (C) 2019 SUSE LLC + Copyright (C) 2013 by Ronnie Sahlberg + + 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; if not, see . +*/ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "iscsi.h" +#include "iscsi-private.h" +#include "scsi-lowlevel.h" +#include "iscsi-test-cu.h" + +static struct iscsi_transport iscsi_drv_orig; + +static int +test_iscsi_strip_tag(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, + const char *tag) +{ + unsigned char *s; + unsigned char *end; + size_t remain; + size_t toklen; + + toklen = strlen(tag); + if ((toklen < 2) || (tag[toklen - 1] != '=')) { + return -EINVAL; + } + + s = memmem(pdu->outdata.data, pdu->outdata.size, tag, toklen); + if (s == NULL) { + return -ENOENT; + } + + remain = pdu->outdata.size - (s - pdu->outdata.data); + if ((remain == 0) || (remain > pdu->outdata.size)) { + return -EINVAL; + } + + end = memchr(s, 0, remain); + if (end == NULL) { + return -EINVAL; + } + + toklen = end - s; + assert(toklen > 0); + + /* handle padding */ + while ((toklen < remain) && (s[toklen] == '\0')) { + toklen++; + } + + memmove(s, s + toklen, remain - toklen); + pdu->outdata.size -= toklen; + + /* update data segment length */ + scsi_set_uint32(&pdu->outdata.data[4], pdu->outdata.size + - ISCSI_HEADER_SIZE(iscsi->header_digest)); + logging(LOG_VERBOSE, "stripped %s key and value from PDU", tag); + + return 0; +} + +static int +chap_mod_strip_replace_queue(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, + const char *new_chap_a) +{ + int ret; + + if ((pdu->outdata.data[0] & 0x3f) != ISCSI_PDU_LOGIN_REQUEST) { + goto out; + } + + ret = test_iscsi_strip_tag(iscsi, pdu, "CHAP_A="); + if (ret == -ENOENT) { + logging(LOG_VERBOSE, "ignoring login PDU without CHAP_A"); + goto out;; + } + if (ret < 0) { + return ret; + } + ret = iscsi_pdu_add_data(iscsi, pdu, (const unsigned char *)new_chap_a, + strlen(new_chap_a) + 1); + if (ret < 0) { + return ret; + } + logging(LOG_VERBOSE, "replaced Login PDU CHAP_A setting with %s", new_chap_a); + /* restore drv */ + *iscsi->drv = iscsi_drv_orig; +out: + return iscsi_drv_orig.queue_pdu(iscsi, pdu); + +} + +static int +chap_mod_many_types_queue(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) +{ + return chap_mod_strip_replace_queue(iscsi, pdu, "CHAP_A=5,6,7,8"); +} + +static int +chap_mod_no_type_queue(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) +{ + return chap_mod_strip_replace_queue(iscsi, pdu, "CHAP_A="); +} + +static int +chap_mod_bad_type_queue(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) +{ + /* value starts with '5', to catch targets that only check one byte */ + return chap_mod_strip_replace_queue(iscsi, pdu, "CHAP_A=56"); +} + +static int +test_iscsi_chap_login(int (*test_queue_pdu)(struct iscsi_context *iscsi, + struct iscsi_pdu *pdu)) +{ + struct iscsi_context *iscsi; + struct iscsi_url *iscsi_url; + int ret; + + iscsi = iscsi_create_context(initiatorname2); + if (iscsi == NULL) { + return -ENOMEM; + } + + iscsi_url = iscsi_parse_full_url(iscsi, sd->iscsi_url); + if (iscsi_url == NULL) { + ret = -ENOMEM; + goto err_iscsi_destroy; + } + + iscsi_set_targetname(iscsi, iscsi_url->target); + iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL); + iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C); + iscsi_set_noautoreconnect(iscsi, 1); + + iscsi_set_initiator_username_pwd(iscsi, iscsi_url->user, + iscsi_url->passwd); + + /* override transport queue_pdu callback for PDU manipulation */ + iscsi_drv_orig = *iscsi->drv; + iscsi->drv->queue_pdu = test_queue_pdu; + + ret = iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun); + if (ret < 0) { + ret = -EIO; + goto err_url_destroy; + } + + ret = 0; +err_url_destroy: + iscsi_destroy_url(iscsi_url); + /* XXX no need to restore iscsi_drv_orig */ +err_iscsi_destroy: + iscsi_destroy_context(iscsi); + return ret; +} + +void +test_iscsi_chap_simple(void) +{ + int ret; + + logging(LOG_VERBOSE, LOG_BLANK_LINE); + logging(LOG_VERBOSE, "Test CHAP_A negotiation"); + + CHECK_FOR_ISCSI(sd); + if (sd->iscsi_ctx->chap_a != 5) { + const char *err = "[SKIPPED] This test requires " + "an iSCSI session with CHAP_A=5"; + logging(LOG_NORMAL, "%s", err); + CU_PASS(err); + return; + } + + ret = test_iscsi_chap_login(chap_mod_many_types_queue); + CU_ASSERT_EQUAL(ret, 0); +} + +void +test_iscsi_chap_invalid(void) +{ + int ret; + + logging(LOG_VERBOSE, LOG_BLANK_LINE); + logging(LOG_VERBOSE, "Test CHAP_A negotiation"); + + CHECK_FOR_ISCSI(sd); + if (sd->iscsi_ctx->chap_a != 5) { + const char *err = "[SKIPPED] This test requires " + "an iSCSI session with CHAP_A=5"; + logging(LOG_NORMAL, "%s", err); + CU_PASS(err); + return; + } + + ret = test_iscsi_chap_login(chap_mod_bad_type_queue); + CU_ASSERT_EQUAL(ret, -EIO); + + ret = test_iscsi_chap_login(chap_mod_no_type_queue); + CU_ASSERT_EQUAL(ret, -EIO); +}