diff -urNp freebsd/src/etc/defaults/rc.conf iscsi/etc/defaults/rc.conf --- freebsd/src/etc/defaults/rc.conf 2013-01-15 08:43:37.000000000 +0100 +++ iscsi/etc/defaults/rc.conf 2013-01-16 08:21:27.000000000 +0100 @@ -270,6 +270,7 @@ inetd_flags="-wW -C 60" # Optional flag hastd_enable="NO" # Run the HAST daemon (YES/NO). hastd_program="/sbin/hastd" # path to hastd, if you want a different one. hastd_flags="" # Optional flags to hastd. +ctld_enable="NO" # CAM Target Layer daemon. # # named. It may be possible to run named in a sandbox, man security for # details. diff -urNp freebsd/src/etc/rc.d/Makefile iscsi/etc/rc.d/Makefile --- freebsd/src/etc/rc.d/Makefile 2013-01-15 08:43:39.000000000 +0100 +++ iscsi/etc/rc.d/Makefile 2013-01-16 08:21:28.000000000 +0100 @@ -30,6 +30,7 @@ FILES= DAEMON \ cleanvar \ cleartmp \ cron \ + ctld \ ddb \ defaultroute \ devd \ diff -urNp freebsd/src/etc/rc.d/ctld iscsi/etc/rc.d/ctld --- freebsd/src/etc/rc.d/ctld 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/etc/rc.d/ctld 2013-01-17 00:19:40.000000000 +0100 @@ -0,0 +1,21 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: ctld +# REQUIRE: FILESYSTEMS +# BEFORE: DAEMON +# KEYWORD: nojail + +. /etc/rc.subr + +name="ctld" +rcvar="ctld_enable" +pidfile="/var/run/${name}.pid" +command="/usr/sbin/${name}" +required_files="/etc/ctl.conf" +extra_commands="reload" + +load_rc_config $name +run_rc_command "$1" diff -urNp freebsd/src/sys/cam/ctl/ctl.c iscsi/sys/cam/ctl/ctl.c --- freebsd/src/sys/cam/ctl/ctl.c 2013-01-15 08:50:57.000000000 +0100 +++ iscsi/sys/cam/ctl/ctl.c 2013-02-19 17:25:19.000000000 +0100 @@ -89,9 +89,9 @@ struct ctl_softc *control_softc = NULL; #define CTL_DONE_THREAD /* - * * Use the serial number and device ID provided by the backend, rather than - * * making up our own. - * */ + * Use the serial number and device ID provided by the backend, rather than + * making up our own. + */ #define CTL_USE_BACKEND_SN /* @@ -892,8 +892,13 @@ ctl_isc_event_handler(ctl_ha_channel cha struct ctl_lun *lun; struct ctl_page_index *page_index; struct copan_aps_subpage *current_sp; + uint32_t targ_lun; + + targ_lun = msg_info.hdr.nexus.targ_lun; + if (msg_info.hdr.nexus.lun_map_fn != NULL) + targ_lun = msg_info.hdr.nexus.lun_map_fn(msg_info.hdr.nexus.lun_map_arg, targ_lun); - lun = ctl_softc->ctl_luns[msg_info.hdr.nexus.targ_lun]; + lun = ctl_softc->ctl_luns[targ_lun]; page_index = &lun->mode_pages.index[index_to_aps_page]; current_sp = (struct copan_aps_subpage *) (page_index->page_data + @@ -1095,7 +1100,8 @@ ctl_init(void) ctl_pool_free(softc, other_pool); return; } - printf("ctl: CAM Target Layer loaded\n"); + if (bootverbose) + printf("ctl: CAM Target Layer loaded\n"); /* * Initialize the initiator and portname mappings @@ -1190,7 +1196,8 @@ ctl_shutdown(void) free(control_softc, M_DEVBUF); control_softc = NULL; - printf("ctl: CAM Target Layer unloaded\n"); + if (bootverbose) + printf("ctl: CAM Target Layer unloaded\n"); } /* @@ -1658,12 +1665,16 @@ ctl_serialize_other_sc_cmd(struct ctl_sc union ctl_ha_msg msg_info; struct ctl_lun *lun; int retval = 0; + uint32_t targ_lun; ctl_softc = control_softc; if (have_lock == 0) mtx_lock(&ctl_softc->ctl_lock); - lun = ctl_softc->ctl_luns[ctsio->io_hdr.nexus.targ_lun]; + targ_lun = ctsio->io_hdr.nexus.targ_lun; + if (ctsio->io_hdr.nexus.lun_map_fn != NULL) + targ_lun = ctsio->io_hdr.nexus.lun_map_fn(ctsio->io_hdr.nexus.lun_map_arg, targ_lun); + lun = ctl_softc->ctl_luns[targ_lun]; if (lun==NULL) { /* @@ -2959,6 +2970,7 @@ ctl_ioctl(struct cdev *dev, u_long cmd, struct sbuf *sb; struct ctl_lun *lun; struct ctl_lun_list *list; + struct ctl_be_lun_option *opt; list = (struct ctl_lun_list *)addr; @@ -3076,17 +3088,16 @@ ctl_ioctl(struct cdev *dev, u_long cmd, if (retval != 0) break; - if (lun->backend->lun_info == NULL) { - retval = sbuf_printf(sb, "\n"); + if (lun->backend->lun_info != NULL) { + retval = lun->backend->lun_info(lun->be_lun->be_lun, sb); + if (retval != 0) + break; + } + STAILQ_FOREACH(opt, &lun->be_lun->options, links) { + retval = sbuf_printf(sb, "<%s>%s", opt->name, opt->value, opt->name); if (retval != 0) break; - continue; } - - retval =lun->backend->lun_info(lun->be_lun->be_lun, sb); - - if (retval != 0) - break; retval = sbuf_printf(sb, "\n"); @@ -3116,6 +3127,28 @@ ctl_ioctl(struct cdev *dev, u_long cmd, sbuf_delete(sb); break; } + case CTL_ISCSI: { + struct ctl_iscsi *ci; + struct ctl_frontend *fe; + + ci = (struct ctl_iscsi *)addr; + + mtx_lock(&softc->ctl_lock); + STAILQ_FOREACH(fe, &softc->fe_list, links) { + if (strcmp(fe->port_name, "iscsi") == 0) + break; + } + mtx_unlock(&softc->ctl_lock); + + if (fe == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), "Backend \"iscsi\" not found."); + break; + } + + retval = fe->ioctl(dev, cmd, addr, flag, td); + break; + } default: { /* XXX KDM should we fix this? */ #if 0 @@ -4407,9 +4440,14 @@ ctl_free_lun(struct ctl_lun *lun) */ for (io = (union ctl_io *)STAILQ_FIRST(&softc->rtr_queue); io != NULL; io = next_io) { + uint32_t targ_lun; + next_io = (union ctl_io *)STAILQ_NEXT(&io->io_hdr, links); + targ_lun = io->io_hdr.nexus.targ_lun; + if (io->io_hdr.nexus.lun_map_fn != NULL) + targ_lun = io->io_hdr.nexus.lun_map_fn(io->io_hdr.nexus.lun_map_arg, targ_lun); if ((io->io_hdr.nexus.targ_target.id == lun->target.id) - && (io->io_hdr.nexus.targ_lun == lun->lun)) + && (targ_lun == lun->lun)) STAILQ_REMOVE(&softc->rtr_queue, &io->io_hdr, ctl_io_hdr, links); } @@ -6913,7 +6951,6 @@ ctl_maintenance_in(struct ctl_scsiio *ct lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; retval = CTL_RETVAL_COMPLETE; - mtx_lock(&softc->ctl_lock); if ((cdb->byte2 & SERVICE_ACTION_MASK) != SA_RPRT_TRGT_GRP) { ctl_set_invalid_field(/*ctsio*/ ctsio, @@ -6962,7 +6999,7 @@ ctl_maintenance_in(struct ctl_scsiio *ct tp_desc_ptr1_2 = (struct scsi_target_port_descriptor *) &tp_desc_ptr1_1->desc_list[0]; - + mtx_lock(&softc->ctl_lock); if (ctl_is_single == 0) { tpg_desc_ptr2 = (struct scsi_target_port_group_descriptor *) @@ -8223,12 +8260,16 @@ ctl_hndl_per_res_out_on_other_sc(union c struct ctl_lun *lun; struct ctl_softc *softc; int i; + uint32_t targ_lun; softc = control_softc; mtx_lock(&softc->ctl_lock); - lun = softc->ctl_luns[msg->hdr.nexus.targ_lun]; + targ_lun = msg->hdr.nexus.targ_lun; + if (msg->hdr.nexus.lun_map_fn != NULL) + targ_lun = msg->hdr.nexus.lun_map_fn(msg->hdr.nexus.lun_map_arg, targ_lun); + lun = softc->ctl_luns[targ_lun]; switch(msg->pr.pr_info.action) { case CTL_PR_REG_KEY: if (!lun->per_res[msg->pr.pr_info.residx].registered) { @@ -8577,7 +8618,7 @@ ctl_report_luns(struct ctl_scsiio *ctsio int num_luns, retval; uint32_t alloc_len, lun_datalen; int num_filled, well_known; - uint32_t initidx; + uint32_t initidx, targ_lun_id, lun_id; retval = CTL_RETVAL_COMPLETE; well_known = 0; @@ -8638,63 +8679,47 @@ ctl_report_luns(struct ctl_scsiio *ctsio lun_data = (struct scsi_report_luns_data *)ctsio->kern_data_ptr; ctsio->kern_sg_entries = 0; - if (lun_datalen < alloc_len) { - ctsio->residual = alloc_len - lun_datalen; - ctsio->kern_data_len = lun_datalen; - ctsio->kern_total_len = lun_datalen; - } else { - ctsio->residual = 0; - ctsio->kern_data_len = alloc_len; - ctsio->kern_total_len = alloc_len; - } - ctsio->kern_data_resid = 0; - ctsio->kern_rel_offset = 0; - ctsio->kern_sg_entries = 0; - initidx = ctl_get_initindex(&ctsio->io_hdr.nexus); - /* - * We set this to the actual data length, regardless of how much - * space we actually have to return results. If the user looks at - * this value, he'll know whether or not he allocated enough space - * and reissue the command if necessary. We don't support well - * known logical units, so if the user asks for that, return none. - */ - scsi_ulto4b(lun_datalen - 8, lun_data->length); - mtx_lock(&control_softc->ctl_lock); - for (num_filled = 0, lun = STAILQ_FIRST(&control_softc->lun_list); - (lun != NULL) && (num_filled < num_luns); - lun = STAILQ_NEXT(lun, links)) { + for (targ_lun_id = 0, num_filled = 0; targ_lun_id < CTL_MAX_LUNS && num_filled < num_luns; targ_lun_id++) { + lun_id = targ_lun_id; + if (ctsio->io_hdr.nexus.lun_map_fn != NULL) + lun_id = ctsio->io_hdr.nexus.lun_map_fn(ctsio->io_hdr.nexus.lun_map_arg, lun_id); + if (lun_id >= CTL_MAX_LUNS) + continue; + lun = control_softc->ctl_luns[lun_id]; + if (lun == NULL) + continue; - if (lun->lun <= 0xff) { + if (targ_lun_id <= 0xff) { /* * Peripheral addressing method, bus number 0. */ lun_data->luns[num_filled].lundata[0] = RPL_LUNDATA_ATYP_PERIPH; - lun_data->luns[num_filled].lundata[1] = lun->lun; + lun_data->luns[num_filled].lundata[1] = targ_lun_id; num_filled++; - } else if (lun->lun <= 0x3fff) { + } else if (targ_lun_id <= 0x3fff) { /* * Flat addressing method. */ lun_data->luns[num_filled].lundata[0] = RPL_LUNDATA_ATYP_FLAT | - (lun->lun & RPL_LUNDATA_FLAT_LUN_MASK); + (targ_lun_id & RPL_LUNDATA_FLAT_LUN_MASK); #ifdef OLDCTLHEADERS (SRLD_ADDR_FLAT << SRLD_ADDR_SHIFT) | - (lun->lun & SRLD_BUS_LUN_MASK); + (targ_lun_id & SRLD_BUS_LUN_MASK); #endif lun_data->luns[num_filled].lundata[1] = #ifdef OLDCTLHEADERS - lun->lun >> SRLD_BUS_LUN_BITS; + targ_lun_id >> SRLD_BUS_LUN_BITS; #endif - lun->lun >> RPL_LUNDATA_FLAT_LUN_BITS; + targ_lun_id >> RPL_LUNDATA_FLAT_LUN_BITS; num_filled++; } else { printf("ctl_report_luns: bogus LUN number %jd, " - "skipping\n", (intmax_t)lun->lun); + "skipping\n", (intmax_t)targ_lun_id); } /* * According to SPC-3, rev 14 section 6.21: @@ -8719,6 +8744,35 @@ ctl_report_luns(struct ctl_scsiio *ctsio mtx_unlock(&control_softc->ctl_lock); /* + * It's quite possible that we've returned fewer LUNs than we allocated + * space for. Trim it. + */ + lun_datalen = sizeof(*lun_data) + + (num_filled * sizeof(struct scsi_report_luns_lundata)); + + if (lun_datalen < alloc_len) { + ctsio->residual = alloc_len - lun_datalen; + ctsio->kern_data_len = lun_datalen; + ctsio->kern_total_len = lun_datalen; + } else { + ctsio->residual = 0; + ctsio->kern_data_len = alloc_len; + ctsio->kern_total_len = alloc_len; + } + ctsio->kern_data_resid = 0; + ctsio->kern_rel_offset = 0; + ctsio->kern_sg_entries = 0; + + /* + * We set this to the actual data length, regardless of how much + * space we actually have to return results. If the user looks at + * this value, he'll know whether or not he allocated enough space + * and reissue the command if necessary. We don't support well + * known logical units, so if the user asks for that, return none. + */ + scsi_ulto4b(lun_datalen - 8, lun_data->length); + + /* * We can only return SCSI_STATUS_CHECK_COND when we can't satisfy * this request. */ @@ -8920,6 +8974,8 @@ ctl_inquiry_evpd_supported(struct ctl_sc int sup_page_size; struct ctl_lun *lun; + CTL_DEBUG_PRINT(("ctl_inquiry_evpd_supported\n")); + lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; sup_page_size = sizeof(struct scsi_vpd_supported_pages) + @@ -8987,6 +9043,8 @@ ctl_inquiry_evpd_serial(struct ctl_scsii char tmpstr[32]; #endif + CTL_DEBUG_PRINT(("ctl_inquiry_evpd_serial\n")); + lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; /* XXX KDM which malloc flags here?? */ @@ -9069,7 +9127,17 @@ ctl_inquiry_evpd_devid(struct ctl_scsiio #endif /* CTL_USE_BACKEND_SN */ int devid_len; + CTL_DEBUG_PRINT(("ctl_inquiry_evpd_devid\n")); + ctl_softc = control_softc; + + mtx_lock(&ctl_softc->ctl_lock); + fe = ctl_softc->ctl_ports[ctl_port_idx(ctsio->io_hdr.nexus.targ_port)]; + mtx_unlock(&ctl_softc->ctl_lock); + + if (fe->devid != NULL) + return ((fe->devid)(ctsio, alloc_len)); + lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; devid_len = sizeof(struct scsi_vpd_device_id) + @@ -9130,8 +9198,6 @@ ctl_inquiry_evpd_devid(struct ctl_scsiio mtx_lock(&ctl_softc->ctl_lock); - fe = ctl_softc->ctl_ports[ctl_port_idx(ctsio->io_hdr.nexus.targ_port)]; - /* * For Fibre channel, */ @@ -9246,6 +9312,8 @@ ctl_inquiry_evpd(struct ctl_scsiio *ctsi struct scsi_inquiry *cdb; int alloc_len, retval; + CTL_DEBUG_PRINT(("ctl_inquiry_evpd\n")); + cdb = (struct scsi_inquiry *)ctsio->cdb; retval = CTL_RETVAL_COMPLETE; @@ -9287,6 +9355,8 @@ ctl_inquiry_std(struct ctl_scsiio *ctsio uint32_t alloc_len; int is_fc; + CTL_DEBUG_PRINT(("ctl_inquiry_std\n")); + ctl_softc = control_softc; /* @@ -10355,9 +10425,11 @@ ctl_scsiio_precheck(struct ctl_softc *ct struct ctl_lun *lun; struct ctl_cmd_entry *entry; uint8_t opcode; - uint32_t initidx; + uint32_t initidx, targ_lun; int retval; + CTL_DEBUG_PRINT(("ctl_scsiio_precheck cdb[0]=%02X\n", ctsio->cdb[0])); + retval = 0; lun = NULL; @@ -10366,9 +10438,12 @@ ctl_scsiio_precheck(struct ctl_softc *ct mtx_lock(&ctl_softc->ctl_lock); - if ((ctsio->io_hdr.nexus.targ_lun < CTL_MAX_LUNS) - && (ctl_softc->ctl_luns[ctsio->io_hdr.nexus.targ_lun] != NULL)) { - lun = ctl_softc->ctl_luns[ctsio->io_hdr.nexus.targ_lun]; + targ_lun = ctsio->io_hdr.nexus.targ_lun; + if (ctsio->io_hdr.nexus.lun_map_fn != NULL) + targ_lun = ctsio->io_hdr.nexus.lun_map_fn(ctsio->io_hdr.nexus.lun_map_arg, targ_lun); + if ((targ_lun < CTL_MAX_LUNS) + && (ctl_softc->ctl_luns[targ_lun] != NULL)) { + lun = ctl_softc->ctl_luns[targ_lun]; /* * If the LUN is invalid, pretend that it doesn't exist. * It will go away as soon as all pending I/O has been @@ -10408,6 +10483,7 @@ ctl_scsiio_precheck(struct ctl_softc *ct ctl_set_unsupported_lun(ctsio); mtx_unlock(&ctl_softc->ctl_lock); ctl_done((union ctl_io *)ctsio); + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out due to invalid LUN\n")); goto bailout; } else { /* @@ -10428,6 +10504,7 @@ ctl_scsiio_precheck(struct ctl_softc *ct ctl_set_invalid_opcode(ctsio); mtx_unlock(&ctl_softc->ctl_lock); ctl_done((union ctl_io *)ctsio); + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out; T_PROCESSOR\n")); goto bailout; } break; @@ -10438,6 +10515,7 @@ ctl_scsiio_precheck(struct ctl_softc *ct ctl_set_invalid_opcode(ctsio); mtx_unlock(&ctl_softc->ctl_lock); ctl_done((union ctl_io *)ctsio); + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out; T_DIRECT\n")); goto bailout; } break; @@ -10509,6 +10587,7 @@ ctl_scsiio_precheck(struct ctl_softc *ct ~ua_type; mtx_unlock(&ctl_softc->ctl_lock); ctl_done((union ctl_io *)ctsio); + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out; !CTL_UA_NONE\n")); goto bailout; } } @@ -10518,6 +10597,7 @@ ctl_scsiio_precheck(struct ctl_softc *ct if (ctl_scsiio_lun_check(ctl_softc, lun, entry, ctsio) != 0) { mtx_unlock(&ctl_softc->ctl_lock); ctl_done((union ctl_io *)ctsio); + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out; lun check\n")); goto bailout; } @@ -10568,6 +10648,7 @@ ctl_scsiio_precheck(struct ctl_softc *ct * so that we have an idea of what we're waiting for from * the other side. */ + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out; CTL_LUN_PRIMARY_SC\n")); goto bailout_unlock; } @@ -10578,22 +10659,27 @@ ctl_scsiio_precheck(struct ctl_softc *ct ctsio->io_hdr.flags |= CTL_FLAG_BLOCKED; TAILQ_INSERT_TAIL(&lun->blocked_queue, &ctsio->io_hdr, blocked_links); + + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out; CTL_ACTION_BLOCK\n")); goto bailout_unlock; break; /* NOTREACHED */ case CTL_ACTION_PASS: case CTL_ACTION_SKIP: + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: CTL_ACTION_PASS; going to queue_rtr\n")); goto queue_rtr; break; /* NOTREACHED */ case CTL_ACTION_OVERLAP: ctl_set_overlapped_cmd(ctsio); mtx_unlock(&ctl_softc->ctl_lock); ctl_done((union ctl_io *)ctsio); + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out; CTL_ACTION_OVERLAP\n")); goto bailout; break; /* NOTREACHED */ case CTL_ACTION_OVERLAP_TAG: ctl_set_overlapped_tag(ctsio, ctsio->tag_num & 0xff); mtx_unlock(&ctl_softc->ctl_lock); ctl_done((union ctl_io *)ctsio); + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out; CTL_ACTION_OVERLAP_TAG\n")); goto bailout; break; /* NOTREACHED */ case CTL_ACTION_ERROR: @@ -10603,13 +10689,16 @@ ctl_scsiio_precheck(struct ctl_softc *ct /*retry_count*/ 0); mtx_unlock(&ctl_softc->ctl_lock); ctl_done((union ctl_io *)ctsio); + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out; CTL_ACTION_ERROR\n")); goto bailout; break; /* NOTREACHED */ } + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: bailing out; bailout_unlock\n")); goto bailout_unlock; queue_rtr: + CTL_DEBUG_PRINT(("ctl_scsiio_precheck: queue_rtr\n")); ctsio->io_hdr.flags |= CTL_FLAG_IS_WAS_ON_RTR; STAILQ_INSERT_TAIL(&ctl_softc->rtr_queue, &ctsio->io_hdr, links); @@ -10774,6 +10863,7 @@ ctl_abort_task(union ctl_io *io) char printbuf[128]; #endif int found; + uint32_t targ_lun; ctl_softc = control_softc; found = 0; @@ -10781,9 +10871,12 @@ ctl_abort_task(union ctl_io *io) /* * Look up the LUN. */ - if ((io->io_hdr.nexus.targ_lun < CTL_MAX_LUNS) - && (ctl_softc->ctl_luns[io->io_hdr.nexus.targ_lun] != NULL)) - lun = ctl_softc->ctl_luns[io->io_hdr.nexus.targ_lun]; + targ_lun = io->io_hdr.nexus.targ_lun; + if (io->io_hdr.nexus.lun_map_fn != NULL) + targ_lun = io->io_hdr.nexus.lun_map_fn(io->io_hdr.nexus.lun_map_arg, targ_lun); + if ((targ_lun < CTL_MAX_LUNS) + && (ctl_softc->ctl_luns[targ_lun] != NULL)) + lun = ctl_softc->ctl_luns[targ_lun]; else goto bailout; @@ -10973,6 +11066,8 @@ ctl_run_task_queue(struct ctl_softc *ctl int retval; targ_lun = io->io_hdr.nexus.targ_lun; + if (io->io_hdr.nexus.lun_map_fn != NULL) + targ_lun = io->io_hdr.nexus.lun_map_fn(io->io_hdr.nexus.lun_map_arg, targ_lun); if ((targ_lun < CTL_MAX_LUNS) && (ctl_softc->ctl_luns[targ_lun] != NULL)) @@ -11047,7 +11142,7 @@ ctl_run_task_queue(struct ctl_softc *ctl (uintmax_t)io->io_hdr.nexus.initid.id, io->io_hdr.nexus.targ_port, (uintmax_t)io->io_hdr.nexus.targ_target.id, - io->io_hdr.nexus.targ_lun, + io->io_hdr.nexus.targ_lun /* XXX */, (io->io_hdr.io_type == CTL_IO_TASK) ? io->taskio.tag_num : io->scsiio.tag_num); STAILQ_REMOVE(&ctl_softc->task_queue, &io->io_hdr, @@ -11071,10 +11166,14 @@ ctl_handle_isc(union ctl_io *io) int free_io; struct ctl_lun *lun; struct ctl_softc *ctl_softc; + uint32_t targ_lun; ctl_softc = control_softc; - lun = ctl_softc->ctl_luns[io->io_hdr.nexus.targ_lun]; + targ_lun = io->io_hdr.nexus.targ_lun; + if (io->io_hdr.nexus.lun_map_fn != NULL) + targ_lun = io->io_hdr.nexus.lun_map_fn(io->io_hdr.nexus.lun_map_arg, targ_lun); + lun = ctl_softc->ctl_luns[targ_lun]; switch (io->io_hdr.msg_type) { case CTL_MSG_SERIALIZE: @@ -12632,7 +12731,7 @@ ctl_queue_sense(union ctl_io *io) { struct ctl_lun *lun; struct ctl_softc *ctl_softc; - uint32_t initidx; + uint32_t initidx, targ_lun; ctl_softc = control_softc; @@ -12651,9 +12750,12 @@ ctl_queue_sense(union ctl_io *io) * If we don't have a LUN for this, just toss the sense * information. */ - if ((io->io_hdr.nexus.targ_lun < CTL_MAX_LUNS) - && (ctl_softc->ctl_luns[io->io_hdr.nexus.targ_lun] != NULL)) - lun = ctl_softc->ctl_luns[io->io_hdr.nexus.targ_lun]; + targ_lun = io->io_hdr.nexus.targ_lun; + if (io->io_hdr.nexus.lun_map_fn != NULL) + targ_lun = io->io_hdr.nexus.lun_map_fn(io->io_hdr.nexus.lun_map_arg, targ_lun); + if ((targ_lun < CTL_MAX_LUNS) + && (ctl_softc->ctl_luns[targ_lun] != NULL)) + lun = ctl_softc->ctl_luns[targ_lun]; else goto bailout; diff -urNp freebsd/src/sys/cam/ctl/ctl.h iscsi/sys/cam/ctl/ctl.h --- freebsd/src/sys/cam/ctl/ctl.h 2012-11-09 12:32:55.000000000 +0100 +++ iscsi/sys/cam/ctl/ctl.h 2013-01-03 21:29:52.161774000 +0100 @@ -52,6 +52,7 @@ typedef enum { CTL_PORT_SCSI = 0x02, CTL_PORT_IOCTL = 0x04, CTL_PORT_INTERNAL = 0x08, + CTL_PORT_ISCSI = 0x10, CTL_PORT_ALL = 0xff, CTL_PORT_ISC = 0x100 // FC port for inter-shelf communication } ctl_port_type; diff -urNp freebsd/src/sys/cam/ctl/ctl_backend.h iscsi/sys/cam/ctl/ctl_backend.h --- freebsd/src/sys/cam/ctl/ctl_backend.h 2012-11-09 12:32:55.000000000 +0100 +++ iscsi/sys/cam/ctl/ctl_backend.h 2013-01-03 21:29:52.161774000 +0100 @@ -173,6 +173,12 @@ typedef void (*be_lun_config_t)(void *be * The links field is for CTL internal use only, and should not be used by * the backend. */ +struct ctl_be_lun_option { + STAILQ_ENTRY(ctl_be_lun_option) links; + char *name; + char *value; +}; + struct ctl_be_lun { uint8_t lun_type; /* passed to CTL */ ctl_backend_lun_flags flags; /* passed to CTL */ @@ -187,6 +193,7 @@ struct ctl_be_lun { be_lun_config_t lun_config_status; /* passed to CTL */ struct ctl_backend_driver *be; /* passed to CTL */ void *ctl_lun; /* used by CTL */ + STAILQ_HEAD(, ctl_be_lun_option) options; /* passed to CTL */ STAILQ_ENTRY(ctl_be_lun) links; /* used by CTL */ }; diff -urNp freebsd/src/sys/cam/ctl/ctl_backend_block.c iscsi/sys/cam/ctl/ctl_backend_block.c --- freebsd/src/sys/cam/ctl/ctl_backend_block.c 2012-11-09 12:32:55.000000000 +0100 +++ iscsi/sys/cam/ctl/ctl_backend_block.c 2013-01-25 22:50:35.000000000 +0100 @@ -1638,6 +1638,7 @@ ctl_be_block_create(struct ctl_be_block_ STAILQ_INIT(&be_lun->input_queue); STAILQ_INIT(&be_lun->config_write_queue); STAILQ_INIT(&be_lun->datamove_queue); + STAILQ_INIT(&be_lun->ctl_be_lun.options); sprintf(be_lun->lunname, "cblk%d", softc->num_luns); mtx_init(&be_lun->lock, be_lun->lunname, NULL, MTX_DEF); @@ -1658,7 +1659,7 @@ ctl_be_block_create(struct ctl_be_block_ if (be_lun->ctl_be_lun.lun_type == T_DIRECT) { for (i = 0; i < req->num_be_args; i++) { - if (strcmp(req->kern_be_args[i].name, "file") == 0) { + if (strcmp(req->kern_be_args[i].kname, "file") == 0) { file_arg = &req->kern_be_args[i]; break; } @@ -1673,7 +1674,7 @@ ctl_be_block_create(struct ctl_be_block_ be_lun->dev_path = malloc(file_arg->vallen, M_CTLBLK, M_WAITOK | M_ZERO); - strlcpy(be_lun->dev_path, (char *)file_arg->value, + strlcpy(be_lun->dev_path, (char *)file_arg->kvalue, file_arg->vallen); retval = ctl_be_block_open(softc, be_lun, req); @@ -1712,7 +1713,7 @@ ctl_be_block_create(struct ctl_be_block_ * the loop above, */ for (i = 0; i < req->num_be_args; i++) { - if (strcmp(req->kern_be_args[i].name, "num_threads") == 0) { + if (strcmp(req->kern_be_args[i].kname, "num_threads") == 0) { struct ctl_be_arg *thread_arg; char num_thread_str[16]; int tmp_num_threads; @@ -1720,7 +1721,7 @@ ctl_be_block_create(struct ctl_be_block_ thread_arg = &req->kern_be_args[i]; - strlcpy(num_thread_str, (char *)thread_arg->value, + strlcpy(num_thread_str, (char *)thread_arg->kvalue, min(thread_arg->vallen, sizeof(num_thread_str))); @@ -1739,6 +1740,16 @@ ctl_be_block_create(struct ctl_be_block_ } num_threads = tmp_num_threads; + } else if (strcmp(req->kern_be_args[i].kname, "file") != 0 && + strcmp(req->kern_be_args[i].kname, "dev") != 0) { + struct ctl_be_lun_option *opt; + + opt = malloc(sizeof(*opt), M_CTLBLK, M_WAITOK); + opt->name = malloc(strlen(req->kern_be_args[i].kname) + 1, M_CTLBLK, M_WAITOK); + strcpy(opt->name, req->kern_be_args[i].kname); + opt->value = malloc(strlen(req->kern_be_args[i].kvalue) + 1, M_CTLBLK, M_WAITOK); + strcpy(opt->value, req->kern_be_args[i].kvalue); + STAILQ_INSERT_TAIL(&be_lun->ctl_be_lun.options, opt, links); } } diff -urNp freebsd/src/sys/cam/ctl/ctl_backend_ramdisk.c iscsi/sys/cam/ctl/ctl_backend_ramdisk.c --- freebsd/src/sys/cam/ctl/ctl_backend_ramdisk.c 2012-11-09 12:32:55.000000000 +0100 +++ iscsi/sys/cam/ctl/ctl_backend_ramdisk.c 2013-01-25 22:50:35.000000000 +0100 @@ -496,7 +496,7 @@ ctl_backend_ramdisk_create(struct ctl_be struct ctl_lun_create_params *params; uint32_t blocksize; char tmpstr[32]; - int retval; + int i, retval; retval = 0; params = &req->reqdata.create; @@ -514,6 +514,7 @@ ctl_backend_ramdisk_create(struct ctl_be sizeof(*be_lun)); goto bailout_error; } + STAILQ_INIT(&be_lun->ctl_be_lun.options); if (params->flags & CTL_LUN_FLAG_DEV_TYPE) be_lun->ctl_be_lun.lun_type = params->device_type; @@ -550,6 +551,17 @@ ctl_backend_ramdisk_create(struct ctl_be be_lun->softc = softc; + for (i = 0; i < req->num_be_args; i++) { + struct ctl_be_lun_option *opt; + + opt = malloc(sizeof(*opt), M_RAMDISK, M_WAITOK); + opt->name = malloc(strlen(req->kern_be_args[i].kname) + 1, M_RAMDISK, M_WAITOK); + strcpy(opt->name, req->kern_be_args[i].kname); + opt->value = malloc(strlen(req->kern_be_args[i].kvalue) + 1, M_RAMDISK, M_WAITOK); + strcpy(opt->value, req->kern_be_args[i].kvalue); + STAILQ_INSERT_TAIL(&be_lun->ctl_be_lun.options, opt, links); + } + be_lun->flags = CTL_BE_RAMDISK_LUN_UNCONFIGURED; be_lun->ctl_be_lun.flags = CTL_LUN_FLAG_PRIMARY; be_lun->ctl_be_lun.be_lun = be_lun; diff -urNp freebsd/src/sys/cam/ctl/ctl_frontend.h iscsi/sys/cam/ctl/ctl_frontend.h --- freebsd/src/sys/cam/ctl/ctl_frontend.h 2012-11-09 12:32:56.000000000 +0100 +++ iscsi/sys/cam/ctl/ctl_frontend.h 2013-02-19 17:25:19.000000000 +0100 @@ -49,6 +49,9 @@ typedef enum { typedef void (*port_func_t)(void *onoff_arg); typedef int (*targ_func_t)(void *arg, struct ctl_id targ_id); typedef int (*lun_func_t)(void *arg, struct ctl_id targ_id, int lun_id); +typedef int (*fe_ioctl_t)(struct cdev *dev, u_long cmd, caddr_t addr, int flag, + struct thread *td); +typedef int (*fe_devid_t)(struct ctl_scsiio *ctsio, int alloc_len); /* * The ctl_frontend structure is the registration mechanism between a FETD @@ -213,6 +216,8 @@ struct ctl_frontend { targ_func_t targ_disable; /* passed to CTL */ lun_func_t lun_enable; /* passed to CTL */ lun_func_t lun_disable; /* passed to CTL */ + fe_ioctl_t ioctl; /* passed to CTL */ + fe_devid_t devid; /* passed to CTL */ void *targ_lun_arg; /* passed to CTL */ void (*fe_datamove)(union ctl_io *io); /* passed to CTL */ void (*fe_done)(union ctl_io *io); /* passed to CTL */ diff -urNp freebsd/src/sys/cam/ctl/ctl_frontend_iscsi.c iscsi/sys/cam/ctl/ctl_frontend_iscsi.c --- freebsd/src/sys/cam/ctl/ctl_frontend_iscsi.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/sys/cam/ctl/ctl_frontend_iscsi.c 2013-02-19 17:44:21.000000000 +0100 @@ -0,0 +1,3195 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +/* + * CTL frontend for the iSCSI protocol. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct cfiscsi_target { + TAILQ_ENTRY(cfiscsi_target) ct_next; + int ct_luns[CTL_MAX_LUNS]; + struct cfiscsi_softc *ct_softc; + volatile u_int ct_refcount; + char ct_name[CTL_ISCSI_NAME_LEN + 1]; +}; + +struct cfiscsi_pdu { + TAILQ_ENTRY(cfiscsi_pdu) cp_next; + struct cfiscsi_session *cp_session; + struct iscsi_bhs *cp_bhs; + struct mbuf *cp_bhs_mbuf; + size_t cp_ahs_len; + struct mbuf *cp_ahs_mbuf; + size_t cp_data_len; + struct mbuf *cp_data_mbuf; + uint32_t cp_expdatasn; + uint32_t cp_total_transfer_len; + uint32_t cp_r2tsn; +}; + +struct cfiscsi_data_wait { + TAILQ_ENTRY(cfiscsi_data_wait) cdw_next; + union ctl_io *cdw_ctl_io; + uint32_t cdw_target_transfer_tag; + uint32_t cdw_initiator_task_tag; + int cdw_sg_index; + char *cdw_sg_addr; + size_t cdw_sg_len; +}; + +#define CFISCSI_SESSION_LOCK(X) do { \ + if (debug > 2) { \ + if (mtx_trylock(&X->cs_lock) == 0) { \ + printf("%s: mutex\n", __func__);\ + mtx_lock(&X->cs_lock); \ + } \ + } else { \ + mtx_lock(&X->cs_lock); \ + } \ + } while (0) +#define CFISCSI_SESSION_UNLOCK(X) mtx_unlock(&X->cs_lock) +#define CFISCSI_SESSION_LOCK_ASSERT(X) mtx_assert(&X->cs_lock, MA_OWNED) + +#define CFISCSI_SESSION_STATE_INVALID 0 +#define CFISCSI_SESSION_STATE_BHS 1 +#define CFISCSI_SESSION_STATE_AHS 2 +#define CFISCSI_SESSION_STATE_HEADER_DIGEST 3 +#define CFISCSI_SESSION_STATE_DATA 4 +#define CFISCSI_SESSION_STATE_DATA_DIGEST 5 + +struct cfiscsi_session { + TAILQ_ENTRY(cfiscsi_session) cs_next; + struct cfiscsi_target *cs_target; + struct socket *cs_socket; + TAILQ_HEAD(, cfiscsi_pdu) cs_to_send; + TAILQ_HEAD(, cfiscsi_data_wait) cs_waiting_for_data_out; + struct callout cs_terminate_callout; + struct callout cs_ping_callout; + int cs_ping_waiting; + int cs_portal_group_tag; + uint32_t cs_cmdsn; + uint32_t cs_statsn; + uint32_t cs_target_transfer_tag; + struct mtx cs_lock; + int cs_sending_pdus; + int cs_terminating; + volatile u_int cs_outstanding_pdus; + int cs_receive_state; + size_t cs_receive_len; + struct cfiscsi_pdu *cs_receive_pdu; + size_t cs_max_data_segment_length; + char cs_initiator_name[CTL_ISCSI_NAME_LEN]; + char cs_initiator_addr[CTL_ISCSI_ADDR_LEN]; + uint32_t cs_header_digest; + uint32_t cs_data_digest; + unsigned int cs_id; + int cs_ctl_initid; +}; + +struct cfiscsi_softc { + struct ctl_frontend fe; + char port_name[32]; + struct mtx lock; + int online; + unsigned int last_session_id; + TAILQ_HEAD(, cfiscsi_target) targets; + TAILQ_HEAD(, cfiscsi_session) sessions; + char ctl_initids[CTL_MAX_INIT_PER_PORT]; + int max_initiators; +}; + +#define CFISCSI_DEBUG(X) \ + if (debug > 1) { \ + printf X; \ + } while (0) +#define CFISCSI_WARN(X) \ + if (debug > 0) { \ + printf("WARNING: "); \ + printf X; \ + } while (0) + +MALLOC_DEFINE(M_ISCSI, "iscsi", "Memory used for CTL iSCSI target"); +static uma_zone_t cfiscsi_pdu_zone; +static uma_zone_t cfiscsi_data_wait_zone; + +SYSCTL_NODE(_kern_cam_ctl, OID_AUTO, iscsi, CTLFLAG_RD, 0, + "CAM Target Layer iSCSI Frontend"); +static int debug = 1; +TUNABLE_INT("kern.cam.ctl.iscsi.debug", &debug); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, debug, CTLFLAG_RW, + &debug, 1, "Enable debug messages"); +static int ping_interval = 5; +TUNABLE_INT("kern.cam.ctl.iscsi.ping_interval", &ping_interval); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, ping_interval, CTLFLAG_RW, + &ping_interval, 5, "Interval between ping (NOP-In) requests, in seconds"); +static int maxcmdsn_delta = 256; +TUNABLE_INT("kern.cam.ctl.iscsi.maxcmdsn_delta", &maxcmdsn_delta); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, maxcmdsn_delta, CTLFLAG_RW, + &maxcmdsn_delta, 256, "Number of commands the initiator can send " + "without confirmation"); +static int partial_receive_len = 128; /* XXX: More? */ +TUNABLE_INT("kern.cam.ctl.iscsi.partial_receive_len", &partial_receive_len); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, partial_receive_len, CTLFLAG_RW, + &partial_receive_len, 128, "Minimum read size for partially received " + "data segment"); + +int cfiscsi_init(void); +static void cfiscsi_online(void *arg); +static void cfiscsi_offline(void *arg); +static int cfiscsi_targ_enable(void *arg, struct ctl_id targ_id); +static int cfiscsi_targ_disable(void *arg, struct ctl_id targ_id); +static int cfiscsi_lun_enable(void *arg, + struct ctl_id target_id, int lun_id); +static int cfiscsi_lun_disable(void *arg, + struct ctl_id target_id, int lun_id); +static int cfiscsi_ioctl(struct cdev *dev, + u_long cmd, caddr_t addr, int flag, struct thread *td); +static int cfiscsi_devid(struct ctl_scsiio *ctsio, int alloc_len); +static void cfiscsi_datamove(union ctl_io *io); +static void cfiscsi_done(union ctl_io *io); +static uint32_t cfiscsi_map_lun(void *arg, uint32_t lun); +static void cfiscsi_pdu_update_cmdsn(const struct cfiscsi_pdu *request); +static void cfiscsi_pdu_handle_nop_out(struct cfiscsi_pdu *request); +static void cfiscsi_pdu_handle_scsi_command(struct cfiscsi_pdu *request); +static void cfiscsi_pdu_handle_task_request(struct cfiscsi_pdu *request); +static void cfiscsi_pdu_handle_data_out(struct cfiscsi_pdu *request); +static void cfiscsi_pdu_handle_logout_request(struct cfiscsi_pdu *request); +static void cfiscsi_session_terminate(struct cfiscsi_session *cs); +static struct cfiscsi_target *cfiscsi_target_find(struct cfiscsi_softc + *softc, const char *name); +static void cfiscsi_target_release(struct cfiscsi_target *ct); + +static struct cfiscsi_softc cfiscsi_softc; +extern struct ctl_softc *control_softc; +extern int ctl_disable; + +SYSINIT(cfiscsi_init, SI_SUB_CONFIGURE, SI_ORDER_FOURTH, cfiscsi_init, NULL); + +static struct mbuf * +cfiscsi_session_receive(struct cfiscsi_session *cs, size_t len) +{ + struct uio uio; + struct socket *so; + struct mbuf *m; + int error, flags; + + so = cs->cs_socket; + + if (so->so_error != 0) { + CFISCSI_WARN(("%s: connection error %d\n", + __func__, so->so_error)); + return (NULL); + } + + memset(&uio, 0, sizeof(uio)); + uio.uio_resid = len; + + flags = MSG_DONTWAIT; + error = soreceive(so, NULL, &uio, &m, NULL, &flags); + if (error != 0) { + CFISCSI_WARN(("%s: soreceive error %d\n", __func__, error)); + return (NULL); + } + if (uio.uio_resid != 0) { + m_freem(m); + CFISCSI_WARN(("%s: short read\n", __func__)); + return (NULL); + } + + return (m); +} + +static struct cfiscsi_pdu * +cfiscsi_pdu_new(struct cfiscsi_session *cs, int flags) +{ + struct cfiscsi_pdu *cp; + + refcount_acquire(&cs->cs_outstanding_pdus); + cp = uma_zalloc(cfiscsi_pdu_zone, flags | M_ZERO); + if (cp == NULL) { + CFISCSI_WARN(("%s: failed to allocate %zd bytes\n", + __func__, sizeof(*cp))); + refcount_release(&cs->cs_outstanding_pdus); + return (NULL); + } + + cp->cp_session = cs; + + return (cp); +} + +static void +cfiscsi_pdu_free(struct cfiscsi_pdu *cp) +{ + struct cfiscsi_session *cs; + + cs = cp->cp_session; + + m_freem(cp->cp_bhs_mbuf); + m_freem(cp->cp_ahs_mbuf); + m_freem(cp->cp_data_mbuf); + uma_zfree(cfiscsi_pdu_zone, cp); + refcount_release(&cs->cs_outstanding_pdus); +} + +/* + * Allocate cfiscsi_pdu with empty BHS to fill up by the caller. + */ +static struct cfiscsi_pdu * +cfiscsi_pdu_new_bhs(struct cfiscsi_session *cs, int flags) +{ + struct cfiscsi_pdu *cp; + + cp = cfiscsi_pdu_new(cs, flags); + if (cp == NULL) + return (NULL); + + cp->cp_bhs_mbuf = m_getm2(NULL, sizeof(struct iscsi_bhs), + flags, MT_DATA, M_PKTHDR); + if (cp->cp_bhs_mbuf == NULL) { + CFISCSI_WARN(("%s: failed to allocate %zd bytes\n", + __func__, sizeof(*cp))); + cfiscsi_pdu_free(cp); + return (NULL); + } + cp->cp_bhs = mtod(cp->cp_bhs_mbuf, struct iscsi_bhs *); + memset(cp->cp_bhs, 0, sizeof(struct iscsi_bhs)); + cp->cp_bhs_mbuf->m_len = sizeof(struct iscsi_bhs); + + return (cp); +} + +static struct cfiscsi_pdu * +cfiscsi_pdu_new_response(struct cfiscsi_pdu *request, int flags) +{ + + return (cfiscsi_pdu_new_bhs(request->cp_session, flags)); +} + +static int +cfiscsi_pdu_ahs_length(const struct cfiscsi_pdu *request) +{ + + return (request->cp_bhs->bhs_total_ahs_len * 4); +} + +static int +cfiscsi_pdu_data_segment_length(const struct cfiscsi_pdu *request) +{ + uint32_t len = 0; + + len += request->cp_bhs->bhs_data_segment_len[0]; + len <<= 8; + len += request->cp_bhs->bhs_data_segment_len[1]; + len <<= 8; + len += request->cp_bhs->bhs_data_segment_len[2]; + + return (len); +} + +static void +cfiscsi_pdu_set_data_segment_length(struct cfiscsi_pdu *response, uint32_t len) +{ + + response->cp_bhs->bhs_data_segment_len[2] = len; + response->cp_bhs->bhs_data_segment_len[1] = len >> 8; + response->cp_bhs->bhs_data_segment_len[0] = len >> 16; +} + +static size_t +cfiscsi_pdu_padding(const struct cfiscsi_pdu *cp) +{ + + if ((cp->cp_data_len % 4) != 0) + return (4 - (cp->cp_data_len % 4)); + + return (0); +} + +static size_t +cfiscsi_pdu_size(const struct cfiscsi_pdu *response) +{ + size_t len; + + KASSERT(response->cp_ahs_len == 0, ("responding with AHS")); + + len = sizeof(struct iscsi_bhs) + response->cp_data_len + + cfiscsi_pdu_padding(response); + if (response->cp_session->cs_header_digest) + len += ISCSI_HEADER_DIGEST_SIZE; + if (response->cp_session->cs_data_digest) + len += ISCSI_DATA_DIGEST_SIZE; + + return (len); +} + +static int +cfiscsi_pdu_receive_bhs(struct cfiscsi_pdu *request, size_t *availablep) +{ + struct mbuf *m; + + m = cfiscsi_session_receive(request->cp_session, + sizeof(struct iscsi_bhs)); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to receive BHS", __func__)); + return (-1); + } + + request->cp_bhs_mbuf = m_pullup(m, sizeof(struct iscsi_bhs)); + if (request->cp_bhs_mbuf == NULL) { + CFISCSI_WARN(("%s: m_pulup failed", __func__)); + return (-1); + } + request->cp_bhs = mtod(request->cp_bhs_mbuf, struct iscsi_bhs *); + + /* + * XXX: For architectures with strict alignment requirements + * we may need to allocate cp_bhs and copy the data into it. + */ + + *availablep -= sizeof(struct iscsi_bhs); + return (0); +} + +static int +cfiscsi_pdu_receive_ahs(struct cfiscsi_pdu *request, size_t *availablep) +{ + + request->cp_ahs_len = cfiscsi_pdu_ahs_length(request); + if (request->cp_ahs_len == 0) + return (0); + + request->cp_ahs_mbuf = cfiscsi_session_receive(request->cp_session, + request->cp_ahs_len); + if (request->cp_ahs_mbuf == NULL) { + CFISCSI_WARN(("%s: failed to receive AHS", __func__)); + return (-1); + } + + *availablep -= request->cp_ahs_len; + return (0); +} + +static uint32_t +cfiscsi_mbuf_to_crc32c(const struct mbuf *m0) +{ + uint32_t digest = 0xffffffff; + const struct mbuf *m; + + for (m = m0; m != NULL; m = m->m_next) + digest = calculate_crc32c(digest, + mtod(m, const void *), m->m_len); + + digest = digest ^ 0xffffffff; + + return (digest); +} + +static int +cfiscsi_pdu_check_header_digest(struct cfiscsi_pdu *request, size_t *availablep) +{ + struct mbuf *m; + uint32_t received_digest, valid_digest; + + if (request->cp_session->cs_header_digest == CTL_ISCSI_DIGEST_NONE) + return (0); + + m = cfiscsi_session_receive(request->cp_session, + ISCSI_HEADER_DIGEST_SIZE); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to receive header digest", __func__)); + return (-1); + } + + CTASSERT(sizeof(received_digest) == ISCSI_HEADER_DIGEST_SIZE); + memcpy(&received_digest, mtod(m, void *), ISCSI_HEADER_DIGEST_SIZE); + m_freem(m); + + *availablep -= ISCSI_HEADER_DIGEST_SIZE; + + /* + * XXX: Handle AHS. + */ + valid_digest = cfiscsi_mbuf_to_crc32c(request->cp_bhs_mbuf); + if (received_digest != valid_digest) { + CFISCSI_WARN(("%s: header digest check failed; got 0x%x, " + "should be 0x%x", __func__, received_digest, valid_digest)); + return (-1); + } + + return (0); +} + +/* + * Return the number of bytes that should be waiting in the receive socket + * before cfiscsi_pdu_receive_data_segment() gets called. + */ +static size_t +cfiscsi_pdu_data_segment_receive_len(const struct cfiscsi_pdu *request) +{ + size_t len; + + len = cfiscsi_pdu_data_segment_length(request); + if (len == 0) + return (0); + + /* + * Account for the parts of data segment already read from + * the socket buffer. + */ + KASSERT(len > request->cp_data_len, ("len <= request->cp_data_len")); + len -= request->cp_data_len; + + /* + * Don't always wait for the full data segment to be delivered + * to the socket; this might badly affect performance due to + * TCP window scaling. + */ + if (len > partial_receive_len) { +#if 0 + CFISCSI_DEBUG(("%s: need %zd bytes of data, limiting to %zd\n", + __func__, len, partial_receive_len)); +#endif + len = partial_receive_len; + + return (len); + } + + /* + * Account for padding. Note that due to the way code is written, + * the cfiscsi_pdu_receive_data_segment() must always receive padding + * along with the last part of data segment, because it would be + * impossible to tell whether we've already received the full data + * segment including padding, or without it. + */ + if ((len % 4) != 0) + len += 4 - (len % 4); + +#if 0 + CFISCSI_DEBUG(("%s: need %zd bytes of data\n", __func__, len)); +#endif + + return (len); +} + +static int +cfiscsi_pdu_receive_data_segment(struct cfiscsi_pdu *request, + size_t *availablep, bool *more_neededp) +{ + struct cfiscsi_session *cs; + size_t len, padding = 0; + struct mbuf *m; + + cs = request->cp_session; + + *more_neededp = false; + cs->cs_receive_len = 0; + + len = cfiscsi_pdu_data_segment_length(request); + if (len == 0) + return (0); + + if ((len % 4) != 0) + padding = 4 - (len % 4); + + /* + * Account for already received parts of data segment. + */ + KASSERT(len > request->cp_data_len, ("len <= request->cp_data_len")); + len -= request->cp_data_len; + + if (len + padding > *availablep) { + /* + * Not enough data in the socket buffer. Receive as much + * as we can. Don't receive padding, since, obviously, it's + * not the end of data segment yet. + */ +#if 0 + CFISCSI_DEBUG(("%s: limited from %zd to %zd\n", + __func__, len + padding, *availablep - padding)); +#endif + len = *availablep - padding; + *more_neededp = true; + padding = 0; + } + + /* + * Must not try to receive padding without at least one byte + * of actual data segment. + */ + if (len > 0) { + m = cfiscsi_session_receive(request->cp_session, len + padding); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to receive data segment", __func__)); + return (-1); + } + + if (request->cp_data_mbuf == NULL) + request->cp_data_mbuf = m; + else + m_cat(request->cp_data_mbuf, m); + + request->cp_data_len += len; + *availablep -= len + padding; + } else + CFISCSI_DEBUG(("%s: len 0\n", __func__)); + + if (*more_neededp) + cs->cs_receive_len = cfiscsi_pdu_data_segment_receive_len(request); + + return (0); +} + +static int +cfiscsi_pdu_check_data_digest(struct cfiscsi_pdu *request, size_t *availablep) +{ + struct mbuf *m; + uint32_t received_digest, valid_digest; + + if (request->cp_session->cs_data_digest == CTL_ISCSI_DIGEST_NONE) + return (0); + + if (request->cp_data_len == 0) + return (0); + + m = cfiscsi_session_receive(request->cp_session, + ISCSI_DATA_DIGEST_SIZE); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to receive data digest", __func__)); + return (-1); + } + + CTASSERT(sizeof(received_digest) == ISCSI_DATA_DIGEST_SIZE); + memcpy(&received_digest, mtod(m, void *), ISCSI_DATA_DIGEST_SIZE); + m_freem(m); + + *availablep -= ISCSI_DATA_DIGEST_SIZE; + + /* + * Note that cp_data_mbuf also contains padding; since digest + * calculation is supposed to include that, we iterate over + * the entire cp_data_mbuf chain, not just cp_data_len bytes of it. + */ + valid_digest = cfiscsi_mbuf_to_crc32c(request->cp_data_mbuf); + if (received_digest != valid_digest) { + CFISCSI_WARN(("%s: data digest check failed; got 0x%x, " + "should be 0x%x", __func__, received_digest, valid_digest)); + return (-1); + } + + return (0); +} + +/* + * Somewhat contrary to the name, this attempts to receive only one + * "part" of PDU at a time; call it repeatedly until it returns non-NULL. + */ +static struct cfiscsi_pdu * +cfiscsi_session_receive_pdu(struct cfiscsi_session *cs, size_t *availablep) +{ + struct cfiscsi_pdu *request; + struct socket *so; + size_t len; + int error; + bool more_needed; + + so = cs->cs_socket; + + if (cs->cs_receive_state == CFISCSI_SESSION_STATE_BHS) { + KASSERT(cs->cs_receive_pdu == NULL, + ("cs->cs_receive_pdu != NULL")); + request = cfiscsi_pdu_new(cs, M_NOWAIT); + if (request == NULL) { + CFISCSI_WARN(("%s: failed to allocate PDU; " + "dropping connection\n", __func__)); + cfiscsi_session_terminate(cs); + return (NULL); + } + cs->cs_receive_pdu = request; + } else { + KASSERT(cs->cs_receive_pdu != NULL, + ("cs->cs_receive_pdu == NULL")); + request = cs->cs_receive_pdu; + } + + if (*availablep < cs->cs_receive_len) { +#if 0 + CFISCSI_DEBUG(("%s: not enough data; need %zd, " + "have %zd\n", __func__, cs->cs_receive_len, *availablep)); +#endif + return (NULL); + } + + switch (cs->cs_receive_state) { + case CFISCSI_SESSION_STATE_BHS: + //CFISCSI_DEBUG(("%s: receiving BHS\n", __func__)); + error = cfiscsi_pdu_receive_bhs(request, availablep); + if (error != 0) { + CFISCSI_WARN(("%s: failed to receive BHS; " + "dropping connection\n", __func__)); + break; + } + + /* + * We don't enforce any limit for AHS length; + * its length is stored in 8 bit field. + */ + + len = cfiscsi_pdu_data_segment_length(request); + if (len > cs->cs_max_data_segment_length) { + CFISCSI_WARN(("%s: received data segment " + "length %zd is larger than negotiated " + "MaxDataSegmentLength %zd; " + "dropping connection\n", __func__, + len, cs->cs_max_data_segment_length)); + break; + } + + cs->cs_receive_state = CFISCSI_SESSION_STATE_AHS; + cs->cs_receive_len = cfiscsi_pdu_ahs_length(request); + break; + + case CFISCSI_SESSION_STATE_AHS: + //CFISCSI_DEBUG(("%s: receiving AHS\n", __func__)); + error = cfiscsi_pdu_receive_ahs(request, availablep); + if (error != 0) { + CFISCSI_WARN(("%s: failed to receive AHS; " + "dropping connection\n", __func__)); + break; + } + cs->cs_receive_state = CFISCSI_SESSION_STATE_HEADER_DIGEST; + if (cs->cs_header_digest == CTL_ISCSI_DIGEST_NONE) + cs->cs_receive_len = 0; + else + cs->cs_receive_len = ISCSI_DATA_DIGEST_SIZE; + break; + + case CFISCSI_SESSION_STATE_HEADER_DIGEST: + //CFISCSI_DEBUG(("%s: receiving header digest\n", __func__)); + error = cfiscsi_pdu_check_header_digest(request, availablep); + if (error != 0) { + CFISCSI_WARN(("%s: header digest failed; " + "dropping connection\n", __func__)); + break; + } + + cs->cs_receive_state = CFISCSI_SESSION_STATE_DATA; + cs->cs_receive_len = cfiscsi_pdu_data_segment_receive_len(request); + break; + + case CFISCSI_SESSION_STATE_DATA: + //CFISCSI_DEBUG(("%s: receiving data segment\n", __func__)); + error = cfiscsi_pdu_receive_data_segment(request, availablep, + &more_needed); + if (error != 0) { + CFISCSI_WARN(("%s: failed to receive data segment;" + "dropping connection\n", + __func__)); + break; + } + + if (more_needed) + break; + + cs->cs_receive_state = CFISCSI_SESSION_STATE_DATA_DIGEST; + if (cs->cs_data_digest == CTL_ISCSI_DIGEST_NONE) + cs->cs_receive_len = 0; + else + cs->cs_receive_len = ISCSI_HEADER_DIGEST_SIZE; + break; + + case CFISCSI_SESSION_STATE_DATA_DIGEST: + //CFISCSI_DEBUG(("%s: receiving data digest\n", __func__)); + error = cfiscsi_pdu_check_data_digest(request, availablep); + if (error != 0) { + CFISCSI_WARN(("%s: data digest failed; " + "dropping connection\n", __func__)); + break; + } + + /* + * We've received complete PDU; reset the receive state machine + * and return the PDU. + */ + cs->cs_receive_state = CFISCSI_SESSION_STATE_BHS; + cs->cs_receive_len = sizeof(struct iscsi_bhs); + cs->cs_receive_pdu = NULL; + return (request); + + default: + panic("invalid cs_receive_state %d\n", cs->cs_receive_state); + } + + if (error != 0) { + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + } + + return (NULL); +} + +static void +cfiscsi_session_receive_pdus(struct cfiscsi_session *cs, size_t available) +{ + struct cfiscsi_pdu *request; + struct socket *so; + + so = cs->cs_socket; + + for (;;) { + if (cs->cs_terminating) + return; + + if (so->so_error != 0) { + CFISCSI_WARN(("%s: connection error %d; " + "dropping connection\n", __func__, so->so_error)); + cfiscsi_session_terminate(cs); + return; + } + + /* + * Loop until we have a complete PDU or there is not enough + * data in the socket buffer. + */ + if (available < cs->cs_receive_len) { +#if 0 + CFISCSI_DEBUG(("%s: not enough data; have %zd, " + "need %zd\n", __func__, available, + cs->cs_receive_len)); +#endif + return; + } + + request = cfiscsi_session_receive_pdu(cs, &available); + if (request == NULL) + continue; + + cfiscsi_pdu_update_cmdsn(request); + + if (request->cp_ahs_len > 0) { + CFISCSI_WARN(("%s: received PDU with unsupported " + "AHS; opcode 0x%x; dropping connection\n", + __func__, request->cp_bhs->bhs_opcode)); + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + + /* + * Handle the PDU; this includes e.g. receiving the remaining + * part of PDU and submitting the SCSI command to CTL + * or queueing a reply. The handling routine is responsible + * for freeing the PDU when it's no longer needed. + */ + switch (request->cp_bhs->bhs_opcode & + ~ISCSI_BHS_OPCODE_IMMEDIATE) { + case ISCSI_BHS_OPCODE_NOP_OUT: + cfiscsi_pdu_handle_nop_out(request); + break; + case ISCSI_BHS_OPCODE_SCSI_COMMAND: + cfiscsi_pdu_handle_scsi_command(request); + break; + case ISCSI_BHS_OPCODE_TASK_REQUEST: + cfiscsi_pdu_handle_task_request(request); + break; + case ISCSI_BHS_OPCODE_SCSI_DATA_OUT: + cfiscsi_pdu_handle_data_out(request); + break; + case ISCSI_BHS_OPCODE_LOGOUT_REQUEST: + cfiscsi_pdu_handle_logout_request(request); + break; + default: + CFISCSI_WARN(("%s: received PDU with unsupported " + "opcode 0x%x; dropping connection\n", + __func__, request->cp_bhs->bhs_opcode)); + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + } + } +} + +static int +cfiscsi_soupcall_receive(struct socket *so, void *arg, int waitflag) +{ + struct cfiscsi_session *cs; + size_t available; + + cs = arg; + + /* + * Note that this code assumes that the receive upcall won't + * ever be running concurrently on another CPU, and that there + * is nobody else reading from the socket. + */ + for (;;) { + available = so->so_rcv.sb_cc; + SOCKBUF_UNLOCK(&cs->cs_socket->so_rcv); + cfiscsi_session_receive_pdus(cs, available); + SOCKBUF_LOCK(&cs->cs_socket->so_rcv); + /* + * Some data could have arrived while we didn't own the sockbuf + * lock. Check again if there is nothing to receive. + */ + if (so->so_rcv.sb_cc >= cs->cs_receive_len && + cs->cs_terminating == 0) { + CFISCSI_DEBUG(("%s: new data arrived, need %zd, " + "have %zd\n", __func__, cs->cs_receive_len, + so->so_rcv.sb_cc)); + continue; + } + + break; + } + + return (SU_OK); +} + +static int +cfiscsi_pdu_send(struct cfiscsi_pdu *response) +{ + struct cfiscsi_session *cs; + struct iscsi_bhs_scsi_response *bhssr; + struct socket *so; + struct mbuf *m; + int error; + bool advance_statsn = true; + size_t padding, pdu_len; + uint32_t digest; + + cs = response->cp_session; + so = cs->cs_socket; + + /* + * We're only using fields common for all the response + * (target -> initiator) PDUs. + */ + bhssr = (struct iscsi_bhs_scsi_response *)response->cp_bhs; + + /* + * 10.8.3: "The StatSN for this connection is not advanced + * after this PDU is sent." + */ + if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_R2T) + advance_statsn = false; + + /* + * 10.19.2: "However, when the Initiator Task Tag is set to 0xffffffff, + * StatSN for the connection is not advanced after this PDU is sent." + */ + if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_NOP_IN && + bhssr->bhssr_initiator_task_tag == 0xffffffff) + advance_statsn = false; + + /* + * See the comment below - StatSN is not meaningful and must + * not be advanced. + */ + if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_SCSI_DATA_IN) + advance_statsn = false; + + CFISCSI_SESSION_LOCK(cs); + /* + * 10.7.3: "The fields StatSN, Status, and Residual Count + * only have meaningful content if the S bit is set to 1." + */ + if (bhssr->bhssr_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_IN) + bhssr->bhssr_statsn = htonl(cs->cs_statsn); + bhssr->bhssr_expcmdsn = htonl(cs->cs_cmdsn + 1); + bhssr->bhssr_maxcmdsn = htonl(cs->cs_cmdsn + maxcmdsn_delta); + CFISCSI_SESSION_UNLOCK(cs); + cfiscsi_pdu_set_data_segment_length(response, response->cp_data_len); + + pdu_len = cfiscsi_pdu_size(response); + + if (so->so_error != 0) { + CFISCSI_WARN(("%s: connection error %d\n", + __func__, so->so_error)); + return (1); + } + + if (cs->cs_header_digest != CTL_ISCSI_DIGEST_NONE) { + digest = cfiscsi_mbuf_to_crc32c(response->cp_bhs_mbuf); + + m = m_getm2(NULL, sizeof(digest), M_NOWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to allocate mbuf " + "for header digest\n", __func__)); + return (1); + } + memcpy(mtod(m, void *), &digest, sizeof(digest)); + m->m_len = sizeof(digest); + m_cat(response->cp_bhs_mbuf, m); + m = NULL; + } + + if (response->cp_data_len != 0) { + padding = cfiscsi_pdu_padding(response); + if (padding > 0) { + /* + * XXX: Is there a way to do this in place, instead + * of allocating a 4-byte mbuf and appending it? + */ + m = m_getm2(NULL, padding, M_NOWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to allocate mbuf " + "for padding\n", __func__)); + return (1); + } + memset(mtod(m, void *), 0, padding); + m->m_len = padding; + m_cat(response->cp_data_mbuf, m); + m = NULL; + } + + if (cs->cs_data_digest != CTL_ISCSI_DIGEST_NONE) { + digest = cfiscsi_mbuf_to_crc32c(response->cp_data_mbuf); + + m = m_getm2(NULL, sizeof(digest), M_NOWAIT, MT_DATA, + M_PKTHDR); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to allocate mbuf " + "for data digest\n", __func__)); + return (1); + } + memcpy(mtod(m, void *), &digest, sizeof(digest)); + m->m_len = sizeof(digest); + m_cat(response->cp_data_mbuf, m); + m = NULL; + } + + m_cat(response->cp_bhs_mbuf, response->cp_data_mbuf); + response->cp_data_mbuf = NULL; + } + + response->cp_bhs_mbuf->m_pkthdr.len = pdu_len; + + error = sosend(so, NULL, NULL, response->cp_bhs_mbuf, + NULL, MSG_DONTWAIT, curthread); + response->cp_bhs_mbuf = NULL; /* Sosend consumes the mbuf. */ + if (error != 0) { + CFISCSI_WARN(("%s: sosend error %d\n", __func__, error)); + return (1); + } + + if (advance_statsn) { + CFISCSI_SESSION_LOCK(cs); + cs->cs_statsn++; + CFISCSI_SESSION_UNLOCK(cs); + } + + return (0); +} + +static void +cfiscsi_session_send_pdus(struct cfiscsi_session *cs) +{ + struct cfiscsi_pdu *response; + int error; + + CFISCSI_SESSION_LOCK(cs); + if (cs->cs_sending_pdus) { + /* + * Reason for this check is to avoid deadlock between sosend + * and the socket upcall, caused by calling sosend while holding + * a mutex and trying to acquire that mutex in soupcall. + */ + CFISCSI_SESSION_UNLOCK(cs); + return; + } + cs->cs_sending_pdus = 1; + while (!TAILQ_EMPTY(&cs->cs_to_send)) { + response = TAILQ_FIRST(&cs->cs_to_send); + SOCKBUF_LOCK(&cs->cs_socket->so_snd); + /* + * The reason for the error check is that in case the connection + * is already dead, we want the code to try to send queued PDUs, + * fail, and free them. + */ + if (cs->cs_socket->so_error == 0 && + sbspace(&cs->cs_socket->so_snd) < + cfiscsi_pdu_size(response)) { + SOCKBUF_UNLOCK(&cs->cs_socket->so_snd); +#if 0 + CFISCSI_DEBUG(("%s: no space to send; " + "have %zd, need %zd\n", __func__, + sbspace(&cs->cs_socket->so_snd), + cfiscsi_pdu_size(response))); +#endif + break; + } else { +#if 0 + CFISCSI_DEBUG(("%s: have %zd of space to send\n", + __func__, sbspace(&cs->cs_socket->so_snd))); +#endif + } + + SOCKBUF_UNLOCK(&cs->cs_socket->so_snd); + TAILQ_REMOVE(&cs->cs_to_send, response, cp_next); + CFISCSI_SESSION_UNLOCK(cs); + error = cfiscsi_pdu_send(response); + if (error != 0) { + CFISCSI_WARN(("%s: failed to send PDU; " + "dropping connection\n", __func__)); + cfiscsi_pdu_free(response); + cfiscsi_session_terminate(cs); + } else + cfiscsi_pdu_free(response); + CFISCSI_SESSION_LOCK(cs); + } + cs->cs_sending_pdus = 0; + CFISCSI_SESSION_UNLOCK(cs); +} + +static int +cfiscsi_soupcall_send(struct socket *so, void *arg, int waitflag) +{ + struct cfiscsi_session *cs; + + cs = arg; + //CFISCSI_DEBUG(("%s: running\n", __func__)); + SOCKBUF_UNLOCK(&cs->cs_socket->so_snd); + cfiscsi_session_send_pdus(cs); + SOCKBUF_LOCK(&cs->cs_socket->so_snd); + //CFISCSI_DEBUG(("%s: done\n", __func__)); + return (SU_OK); +} + +static void +cfiscsi_pdu_append_data(struct cfiscsi_pdu *response, const void *addr, size_t len) +{ + struct mbuf *mb, *newmb; + size_t copylen, off = 0; + + KASSERT(len > 0, ("len == 0")); + + newmb = m_getm2(NULL, len, M_WAITOK, MT_DATA, M_PKTHDR); + if (newmb == NULL) { + CFISCSI_WARN(("%s: failed to allocate mbuf for %zd bytes\n", + __func__, len)); + cfiscsi_session_terminate(response->cp_session); + return; + } + + for (mb = newmb; mb != NULL; mb = mb->m_next) { + copylen = min(M_TRAILINGSPACE(mb), len - off); + memcpy(mtod(mb, char *), (const char *)addr + off, copylen); + mb->m_len = copylen; + off += copylen; + } + KASSERT(off == len, ("%s: off != len", __func__)); + + if (response->cp_data_mbuf == NULL) { + response->cp_data_mbuf = newmb; + response->cp_data_len = len; + } else { + m_cat(response->cp_data_mbuf, newmb); + response->cp_data_len += len; + } +} + +static void +cfiscsi_pdu_queue(struct cfiscsi_pdu *cp) +{ + struct cfiscsi_session *cs; + + cs = cp->cp_session; + + CFISCSI_SESSION_LOCK(cs); + TAILQ_INSERT_TAIL(&cs->cs_to_send, cp, cp_next); + CFISCSI_SESSION_UNLOCK(cs); + cfiscsi_session_send_pdus(cs); +} + +static void +cfiscsi_pdu_queue_head(struct cfiscsi_pdu *cp) +{ + struct cfiscsi_session *cs; + + cs = cp->cp_session; + + CFISCSI_SESSION_LOCK(cs); + TAILQ_INSERT_HEAD(&cs->cs_to_send, cp, cp_next); + CFISCSI_SESSION_UNLOCK(cs); + cfiscsi_session_send_pdus(cs); +} + +static void +cfiscsi_pdu_update_cmdsn(const struct cfiscsi_pdu *request) +{ + const struct iscsi_bhs_scsi_command *bhssc; + struct cfiscsi_session *cs; + uint32_t cmdsn, expstatsn; + + cs = request->cp_session; + + /* + * Data-Out PDUs don't contain CmdSN. + */ + if ((request->cp_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_DATA_OUT) + return; + + /* + * We're only using fields common for all the request + * (initiator -> target) PDUs. + */ + bhssc = (const struct iscsi_bhs_scsi_command *)request->cp_bhs; + cmdsn = ntohl(bhssc->bhssc_cmdsn); + expstatsn = ntohl(bhssc->bhssc_expstatsn); + + CFISCSI_SESSION_LOCK(cs); +#if 0 + if (expstatsn != cs->cs_statsn) { + CFISCSI_DEBUG(("%s: received PDU with ExpStatSN %d, " + "while current StatSN is %d\n", __func__, expstatsn, + cs->cs_statsn)); + cs->cs_statsn = expstatsn; + } +#endif + + /* + * Immediate commands don't advance CmdSN. + */ + if (request->cp_bhs->bhs_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) { + CFISCSI_SESSION_UNLOCK(cs); + return; + } + + /* + * XXX: The target MUST silently ignore any non-immediate command + * outside of this range or non-immediate duplicates within + * the range. + */ + if (cmdsn != cs->cs_cmdsn + 1) { + CFISCSI_DEBUG(("%s: received PDU with CmdSN %d, " + "while previous CmdSN was %d\n", __func__, cmdsn, + cs->cs_cmdsn)); +#if 0 + CFISCSI_SESSION_UNLOCK(cs); + return; +#endif + } + + /* + * XXX: The CmdSN of the rejected command PDU (if it is a non-immediate + * command) MUST NOT be considered received by the target + * (i.e., a command sequence gap must be assumed for the CmdSN) + */ + + cs->cs_cmdsn = cmdsn; + CFISCSI_SESSION_UNLOCK(cs); +} + +static uint32_t +cfiscsi_decode_lun(uint64_t encoded) +{ + uint8_t lun[8]; + uint32_t result; + + /* + * The LUN field in iSCSI PDUs may look like an ordinary 64 bit number, + * but is in fact an evil, multidimensional structure defined + * in SCSI Architecture Model 5 (SAM-5), section 4.6. + */ + memcpy(lun, &encoded, sizeof(lun)); + switch (lun[0] & 0xC0) { + case 0x00: + if ((lun[0] & 0x3f) != 0 || lun[2] != 0 || lun[3] != 0 || + lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0) { + CFISCSI_WARN(("%s: malformed LUN " + "(peripheral device addressing method): 0x%lx\n", + __func__, encoded)); + result = 0xffffffff; + break; + } + result = lun[1]; + break; + case 0x40: + if (lun[2] != 0 || lun[3] != 0 || lun[4] != 0 || lun[5] != 0 || + lun[6] != 0 || lun[7] != 0) { + CFISCSI_WARN(("%s: malformed LUN " + "(flat address space addressing method): 0x%lx\n", + __func__, encoded)); + result = 0xffffffff; + break; + } + result = ((lun[0] & 0x3f) << 8) + lun[1]; + break; + case 0xC0: + if (lun[0] != 0xD2 || lun[4] != 0 || lun[5] != 0 || + lun[6] != 0 || lun[7] != 0) { + CFISCSI_WARN(("%s: malformed LUN (extended flat " + "address space addressing method): 0x%lx\n", + __func__, encoded)); + result = 0xffffffff; + break; + } + result = (lun[1] << 16) + (lun[2] << 8) + lun[3]; + default: + CFISCSI_WARN(("%s: unsupported LUN format 0x%lx\n", + __func__, encoded)); + result = 0xffffffff; + break; + } + + return (result); +} + +static void +cfiscsi_pdu_handle_nop_out(struct cfiscsi_pdu *request) +{ + struct iscsi_bhs_nop_out *bhsno; + struct iscsi_bhs_nop_in *bhsni; + struct cfiscsi_pdu *response; + + bhsno = (struct iscsi_bhs_nop_out *)request->cp_bhs; + + if (bhsno->bhsno_initiator_task_tag == 0xffffffff) { + CFISCSI_SESSION_LOCK(request->cp_session); + request->cp_session->cs_ping_waiting = 0; + CFISCSI_SESSION_UNLOCK(request->cp_session); + cfiscsi_pdu_free(request); + return; + } + + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + cfiscsi_pdu_free(request); + return; + } + bhsni = (struct iscsi_bhs_nop_in *)response->cp_bhs; + bhsni->bhsni_opcode = ISCSI_BHS_OPCODE_NOP_IN; + bhsni->bhsni_flags = 0x80; + bhsni->bhsni_initiator_task_tag = bhsno->bhsno_initiator_task_tag; + bhsni->bhsni_target_transfer_tag = 0xffffffff; + + response->cp_data_len = request->cp_data_len; + response->cp_data_mbuf = request->cp_data_mbuf; + request->cp_data_len = 0; + request->cp_data_mbuf = NULL; + + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_pdu_handle_scsi_command(struct cfiscsi_pdu *request) +{ + struct iscsi_bhs_scsi_command *bhssc; + struct cfiscsi_session *cs; + union ctl_io *io; + int error; + + cs = request->cp_session; + bhssc = (struct iscsi_bhs_scsi_command *)request->cp_bhs; + //CFISCSI_DEBUG(("%s: initiator task tag 0x%x\n", + // __func__, bhssc->bhssc_initiator_task_tag)); + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_WARN(("%s: can't allocate ctl_io\n", __func__)); + cfiscsi_pdu_free(request); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = request; + io->io_hdr.io_type = CTL_IO_SCSI; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + io->io_hdr.nexus.targ_lun = cfiscsi_decode_lun(bhssc->bhssc_lun); + io->io_hdr.nexus.lun_map_fn = cfiscsi_map_lun; + io->io_hdr.nexus.lun_map_arg = cs; + io->scsiio.tag_num = bhssc->bhssc_initiator_task_tag; + switch ((bhssc->bhssc_flags & BHSSC_FLAGS_ATTR)) { + case 0: + io->scsiio.tag_type = CTL_TAG_UNTAGGED; + break; + case 1: + io->scsiio.tag_type = CTL_TAG_SIMPLE; + break; + case 2: + io->scsiio.tag_type = CTL_TAG_ORDERED; + break; + case 3: + io->scsiio.tag_type = CTL_TAG_HEAD_OF_QUEUE; + break; + case 4: + io->scsiio.tag_type = CTL_TAG_ACA; + break; + default: + io->scsiio.tag_type = CTL_TAG_UNTAGGED; + CFISCSI_WARN(("%s: unhandled tag type %d\n", + __func__, (bhssc->bhssc_flags & BHSSC_FLAGS_ATTR))); + break; + } + io->scsiio.cdb_len = sizeof(bhssc->bhssc_cdb); /* Which is 16. */ + memcpy(io->scsiio.cdb, bhssc->bhssc_cdb, sizeof(bhssc->bhssc_cdb)); + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_WARN(("%s: ctl_queue() failed; error %d\n", + __func__, error)); + ctl_free_io(io); + cfiscsi_pdu_free(request); + } +} + +static void +cfiscsi_pdu_handle_task_request(struct cfiscsi_pdu *request) +{ + struct iscsi_bhs_task_management_request *bhstmr; + struct iscsi_bhs_task_management_response *bhstmr2; + struct cfiscsi_pdu *response; + struct cfiscsi_session *cs; + union ctl_io *io; + int error; + + cs = request->cp_session; + bhstmr = (struct iscsi_bhs_task_management_request *)request->cp_bhs; + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_WARN(("%s: can't allocate ctl_io\n", __func__)); + cfiscsi_pdu_free(request); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = request; + io->io_hdr.io_type = CTL_IO_TASK; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + io->io_hdr.nexus.targ_lun = cfiscsi_decode_lun(bhstmr->bhstmr_lun); + io->io_hdr.nexus.lun_map_fn = cfiscsi_map_lun; + io->io_hdr.nexus.lun_map_arg = cs; + io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ + + switch (bhstmr->bhstmr_function & ~0x80) { + case BHSTMR_FUNCTION_ABORT_TASK: + CFISCSI_DEBUG(("%s: BHSTMR_FUNCTION_ABORT_TASK\n", __func__)); + io->taskio.task_action = CTL_TASK_ABORT_TASK; + //CFISCSI_DEBUG(("%s: referenced task tag 0x%x\n", + // __func__, bhstmr->bhstmr_referenced_task_tag)); + io->taskio.tag_num = bhstmr->bhstmr_referenced_task_tag; + break; + case BHSTMR_FUNCTION_LOGICAL_UNIT_RESET: + CFISCSI_DEBUG(("%s: BHSTMR_FUNCTION_LOGICAL_UNIT_RESET\n", + __func__)); + io->taskio.task_action = CTL_TASK_LUN_RESET; + break; + case BHSTMR_FUNCTION_TARGET_COLD_RESET: + CFISCSI_DEBUG(("%s: BHSTMR_FUNCTION_TARGET_COLD_RESET\n", + __func__)); + io->taskio.task_action = CTL_TASK_BUS_RESET; + break; + default: + CFISCSI_DEBUG(("%s: unsupported function 0x%x\n", + __func__, bhstmr->bhstmr_function & ~0x80)); + ctl_free_io(io); + + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + cfiscsi_pdu_free(request); + return; + } + bhstmr2 = (struct iscsi_bhs_task_management_response *) + response->cp_bhs; + bhstmr2->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_RESPONSE; + bhstmr2->bhstmr_flags = 0x80; + bhstmr2->bhstmr_response = + BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED; + bhstmr2->bhstmr_initiator_task_tag = + bhstmr->bhstmr_initiator_task_tag; + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); + return; + } + + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_WARN(("%s: ctl_queue() failed; error %d\n", + __func__, error)); + ctl_free_io(io); + cfiscsi_pdu_free(request); + } +} + +static void +cfiscsi_pdu_handle_data_out(struct cfiscsi_pdu *request) +{ + struct iscsi_bhs_data_out *bhsdo; + struct cfiscsi_session *cs; + struct cfiscsi_data_wait *cdw = NULL; + struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; + union ctl_io *io; + size_t copy_len, off; + int ctl_sg_count; + + cs = request->cp_session; + bhsdo = (struct iscsi_bhs_data_out *)request->cp_bhs; + + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH(cdw, &cs->cs_waiting_for_data_out, cdw_next) { +#if 0 + CFISCSI_DEBUG(("%s: have ttt 0x%x, itt 0x%x; looking for " + "ttt 0x%x, itt 0x%x\n", __func__, + bhsdo->bhsdo_target_transfer_tag, + bhsdo->bhsdo_initiator_task_tag, + cdw->cdw_target_transfer_tag, cdw->cdw_initiator_task_tag)); +#endif + if (bhsdo->bhsdo_target_transfer_tag == + cdw->cdw_target_transfer_tag) + break; + } + CFISCSI_SESSION_UNLOCK(cs); + if (cdw == NULL) { + CFISCSI_WARN(("%s: data transfer tag 0x%x, initiator task tag " + "0x%x, not found\n", __func__, + bhsdo->bhsdo_target_transfer_tag, + bhsdo->bhsdo_initiator_task_tag)); + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + + io = cdw->cdw_ctl_io; + KASSERT((io->io_hdr.flags & CTL_FLAG_DATA_MASK) != CTL_FLAG_DATA_IN, + ("CTL_FLAG_DATA_IN")); + +#if 0 + CFISCSI_DEBUG(("%s: received %zd bytes out of %d\n", + __func__, request->cp_data_len, io->scsiio.kern_total_len)); +#endif + + if (io->scsiio.kern_sg_entries > 0) { + ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; + ctl_sg_count = io->scsiio.kern_sg_entries; + } else { + ctl_sglist = &ctl_sg_entry; + ctl_sglist->addr = io->scsiio.kern_data_ptr; + ctl_sglist->len = io->scsiio.kern_data_len; + ctl_sg_count = 1; + } +#if 0 + if (ctl_sg_count > 1) + CFISCSI_DEBUG(("%s: ctl_sg_count = %d\n", + __func__, ctl_sg_count)); +#endif + off = 0; + for (;;) { + KASSERT(cdw->cdw_sg_index < ctl_sg_count, + ("cdw->cdw_sg_index >= ctl_sg_count")); + if (cdw->cdw_sg_len == 0) { + cdw->cdw_sg_addr = ctl_sglist[cdw->cdw_sg_index].addr; + cdw->cdw_sg_len = ctl_sglist[cdw->cdw_sg_index].len; + } + copy_len = request->cp_data_len - off; + if (copy_len > cdw->cdw_sg_len) + copy_len = cdw->cdw_sg_len; + + m_copydata(request->cp_data_mbuf, off, copy_len, + cdw->cdw_sg_addr); + cdw->cdw_sg_addr += copy_len; + cdw->cdw_sg_len -= copy_len; + off += copy_len; + io->scsiio.ext_data_filled += copy_len; + + if (cdw->cdw_sg_len == 0) { + if (cdw->cdw_sg_index == ctl_sg_count - 1) + break; + cdw->cdw_sg_index++; + } + if (off == request->cp_data_len) + break; + } + + if (off < request->cp_data_len) { + CFISCSI_WARN(("%s: received too much data: got %zd bytes, " + "expected %zd\n", __func__, request->cp_data_len, off)); + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + + if (bhsdo->bhsdo_flags & BHSDO_FLAGS_F || + io->scsiio.ext_data_filled == io->scsiio.kern_total_len) { + if ((bhsdo->bhsdo_flags & BHSDO_FLAGS_F) == 0) + CFISCSI_WARN(("%s: got the final packet without " + "the F flag; flags = 0x%x\n", + __func__, bhsdo->bhsdo_flags)); + if (io->scsiio.ext_data_filled != io->scsiio.kern_total_len) + CFISCSI_WARN(("%s: got the final packet, but the " + "transmitted size was %zd bytes instead of %d\n", + __func__, (size_t)io->scsiio.ext_data_filled, + io->scsiio.kern_total_len)); +#if 0 + CFISCSI_DEBUG(("%s: no longer expecting Data-Out with target " + "transfer tag 0x%x\n", __func__, + cdw->cdw_target_transfer_tag)); +#endif + CFISCSI_SESSION_LOCK(cs); + TAILQ_REMOVE(&cs->cs_waiting_for_data_out, cdw, cdw_next); + CFISCSI_SESSION_UNLOCK(cs); + uma_zfree(cfiscsi_data_wait_zone, cdw); + + io->scsiio.be_move_done(io); + } + + cfiscsi_pdu_free(request); +} + +static void +cfiscsi_pdu_handle_logout_request(struct cfiscsi_pdu *request) +{ + struct iscsi_bhs_logout_request *bhslr; + struct iscsi_bhs_logout_response *bhslr2; + struct cfiscsi_pdu *response; + struct cfiscsi_session *cs; + + cs = request->cp_session; + bhslr = (struct iscsi_bhs_logout_request *)request->cp_bhs; + switch (bhslr->bhslr_reason & 0x7f) { + case BHSLR_REASON_CLOSE_SESSION: + case BHSLR_REASON_CLOSE_CONNECTION: + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + bhslr2 = (struct iscsi_bhs_logout_response *)response->cp_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; + bhslr2->bhslr_flags = 0x80; + bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; + bhslr2->bhslr_initiator_task_tag = + bhslr->bhslr_initiator_task_tag; + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); + cfiscsi_session_terminate(cs); + break; + case BHSLR_REASON_REMOVE_FOR_RECOVERY: + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + bhslr2 = (struct iscsi_bhs_logout_response *)response->cp_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; + bhslr2->bhslr_flags = 0x80; + bhslr2->bhslr_response = BHSLR_RESPONSE_RECOVERY_NOT_SUPPORTED; + bhslr2->bhslr_initiator_task_tag = + bhslr->bhslr_initiator_task_tag; + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); + break; + default: + CFISCSI_WARN(("%s: invalid reason 0%x; dropping connection\n", + __func__, bhslr->bhslr_reason)); + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + break; + } +} + +static void +cfiscsi_ping(void *context) +{ + struct cfiscsi_pdu *cp; + struct iscsi_bhs_nop_in *bhsni; + struct cfiscsi_session *cs; + + cs = context; + + if (cs->cs_terminating) + return; + + if (cs->cs_ping_waiting == 1) { + CFISCSI_DEBUG(("%s: still waiting for ping reply (NOP-Out)\n", + __func__)); + } else if (cs->cs_ping_waiting >= 2) { + CFISCSI_WARN(("%s: no ping reply (NOP-Out) after %d seconds; " + "dropping connection\n", __func__, ping_interval * 3)); + cfiscsi_session_terminate(cs); + return; + } + + CFISCSI_SESSION_LOCK(cs); + cs->cs_ping_waiting++; + CFISCSI_SESSION_UNLOCK(cs); + + cp = cfiscsi_pdu_new_bhs(cs, M_WAITOK); + bhsni = (struct iscsi_bhs_nop_in *)cp->cp_bhs; + bhsni->bhsni_opcode = ISCSI_BHS_OPCODE_NOP_IN; + bhsni->bhsni_flags = 0x80; + bhsni->bhsni_initiator_task_tag = 0xffffffff; + + /* + * We wan't this to get replied to as soon as possible. There might + * be quite a few PDUs waiting in the send queue, so queue it at the + * head instead of appending at the tail. + */ + cfiscsi_pdu_queue_head(cp); + + callout_schedule(&cs->cs_ping_callout, ping_interval * hz); +} + +static int +cfiscsi_session_register_initiator(struct cfiscsi_session *cs) +{ + int error, i; + struct cfiscsi_softc *softc; + + KASSERT(cs->cs_ctl_initid == -1, ("already registered")); + + softc = cs->cs_target->ct_softc; + + mtx_lock(&softc->lock); + for (i = 0; i < softc->max_initiators; i++) { + if (softc->ctl_initids[i] == 0) + break; + } + if (i == softc->max_initiators) { + CFISCSI_WARN(("%s: too many concurrent sessions (%d)\n", + __func__, softc->max_initiators)); + mtx_unlock(&softc->lock); + return (1); + } + softc->ctl_initids[i] = 1; + mtx_unlock(&softc->lock); + + CFISCSI_DEBUG(("%s: adding initiator id %d, max %d\n", + __func__, i, softc->max_initiators)); + cs->cs_ctl_initid = i; + error = ctl_add_initiator(0x0, softc->fe.targ_port, cs->cs_ctl_initid); + if (error != 0) { + CFISCSI_WARN(("%s: ctl_add_initiator failed with error %d\n", + __func__, error)); + mtx_lock(&softc->lock); + softc->ctl_initids[cs->cs_ctl_initid] = 0; + mtx_unlock(&softc->lock); + cs->cs_ctl_initid = -1; + return (1); + } + + return (0); +} + +static void +cfiscsi_session_unregister_initiator(struct cfiscsi_session *cs) +{ + int error; + struct cfiscsi_softc *softc; + + if (cs->cs_ctl_initid == -1) + return; + + softc = cs->cs_target->ct_softc; + + error = ctl_remove_initiator(softc->fe.targ_port, cs->cs_ctl_initid); + if (error != 0) { + CFISCSI_WARN(("%s: ctl_remove_initiator failed " + "with error %d\n", __func__, error)); + } + mtx_lock(&softc->lock); + softc->ctl_initids[cs->cs_ctl_initid] = 0; + mtx_unlock(&softc->lock); + cs->cs_ctl_initid = -1; +} + +static struct cfiscsi_session * +cfiscsi_session_new(struct cfiscsi_softc *softc, int socket, + const char *initiator_name, const char *initiator_addr, + const char *target, uint32_t cmdsn, uint32_t statsn, + uint32_t max_recv_data_segment_length, uint32_t header_digest, + uint32_t data_digest, int portal_group_tag) +{ + struct cfiscsi_session *cs; + struct file *fp; + struct sockopt opt; + int error, one = 1; +#if 1 + size_t bufsize; +#else + struct sockbuf *sb; +#endif + + cs = malloc(sizeof(*cs), M_ISCSI, M_NOWAIT | M_ZERO); + if (cs == NULL) { + CFISCSI_WARN(("%s: malloc failed\n", __func__)); + return (NULL); + } + cs->cs_ctl_initid = -1; + cs->cs_target = cfiscsi_target_find(softc, target); + if (cs->cs_target == NULL) { + CFISCSI_WARN(("%s: target not found\n", __func__)); + free(cs, M_ISCSI); + return (NULL); + } + if ((error = fget(curthread, socket, CAP_SOCK_ALL, &fp)) != 0) { + CFISCSI_WARN(("%s: fget failed, error %d\n", __func__, error)); + goto fail; + } + if (fp->f_type != DTYPE_SOCKET) { + CFISCSI_WARN(("%s: passed file is not a socket\n", __func__)); + fdrop(fp, curthread); + goto fail; + } + cs->cs_socket = fp->f_data; + if (cs->cs_socket->so_type != SOCK_STREAM) { + CFISCSI_WARN(("%s: passed socket is not a stream\n", __func__)); + fdrop(fp, curthread); + goto fail; + } + /* + * Steal the socket from the userland. + */ + fp->f_ops = &badfileops; + fp->f_data = NULL; + fdrop(fp, curthread); + + /* + * Register initiator with CTL. + */ + cfiscsi_session_register_initiator(cs); + + refcount_init(&cs->cs_outstanding_pdus, 0); + TAILQ_INIT(&cs->cs_to_send); + TAILQ_INIT(&cs->cs_waiting_for_data_out); + mtx_init(&cs->cs_lock, "cfiscsi_lock", NULL, MTX_DEF); + + /* + * First PDU of Full Feature phase has the same CmdSN as the last + * PDU from the Login Phase received from the initiator. Thus, + * the -1 below. + */ + cs->cs_portal_group_tag = portal_group_tag; + cs->cs_cmdsn = cmdsn - 1; + cs->cs_statsn = statsn; + cs->cs_max_data_segment_length = max_recv_data_segment_length; + cs->cs_header_digest = header_digest; + cs->cs_data_digest = data_digest; + + strlcpy(cs->cs_initiator_name, + initiator_name, sizeof(cs->cs_initiator_name)); + strlcpy(cs->cs_initiator_addr, + initiator_addr, sizeof(cs->cs_initiator_addr)); + + mtx_lock(&softc->lock); + cs->cs_id = softc->last_session_id + 1; + softc->last_session_id++; + mtx_unlock(&softc->lock); + + cs->cs_receive_state = CFISCSI_SESSION_STATE_BHS; + cs->cs_receive_len = sizeof(struct iscsi_bhs); + + /* + * Use max available sockbuf size for sending. Do it manually + * instead of sbreserve(9) to work around resource limits. + * + * XXX: This kind of sucks. On one hand, we don't currently support + * sending a part of data segment; we always do it in one piece, + * so we have to make sure it can fit in the socket buffer. + * Once I've implemented partial send, we'll get rid of this + * and use autoscaling. + */ +#if 1 + bufsize = (sizeof(struct iscsi_bhs) + + cs->cs_max_data_segment_length) * 8; + error = soreserve(cs->cs_socket, bufsize, bufsize); + if (error != 0) { + error = soreserve(cs->cs_socket, bufsize, bufsize); + if (error != 0) { + CFISCSI_WARN(("%s: soreserve failed with error %d\n", + __func__, error)); + goto fail; + } + } +#elif 0 + /* + * Poor performance. + */ + bufsize = (sizeof(struct iscsi_bhs) + + cs->cs_max_data_segment_length) * 8; + sb = &cs->cs_socket->so_snd; + error = sbreserve(sb, bufsize, cs->cs_socket, NULL); + if (error == 0) { + bufsize = (sizeof(struct iscsi_bhs) + + cs->cs_max_data_segment_length) * 2; + error = sbreserve(sb, bufsize, cs->cs_socket, NULL); + if (error == 0) { + CFISCSI_WARN(("%s: sbreserve failed with error %d\n", + __func__, error)); + goto fail; + } + } +#elif 0 + /* + * Horrible performance. + */ + sb = &cs->cs_socket->so_snd; + SOCKBUF_LOCK(sb); + sb->sb_mbmax = SB_MAX; + if (sb->sb_lowat > sb->sb_hiwat) + sb->sb_lowat = sb->sb_hiwat; + if (sb->sb_lowat == 0) + sb->sb_lowat = MCLBYTES; + SOCKBUF_UNLOCK(sb); +#endif + + /* + * Disable Nagle. + */ + bzero(&opt, sizeof(opt)); + opt.sopt_dir = SOPT_SET; + opt.sopt_level = IPPROTO_TCP; + opt.sopt_name = TCP_NODELAY; + opt.sopt_val = &one; + opt.sopt_valsize = sizeof(one); + error = sosetopt(cs->cs_socket, &opt); + if (error != 0) { + CFISCSI_WARN(("%s: disabling TCP_NODELAY failed " + "with error %d\n", __func__, error)); + goto fail; + } + + /* + * XXX: Set the low watermark on the send socket, so we don't + * get called too often. + */ + + /* + * Register socket upcall, to get notified about incoming PDUs + * and free space to send outgoing ones. + */ + SOCKBUF_LOCK(&cs->cs_socket->so_snd); + soupcall_set(cs->cs_socket, SO_SND, cfiscsi_soupcall_send, cs); + SOCKBUF_UNLOCK(&cs->cs_socket->so_snd); + SOCKBUF_LOCK(&cs->cs_socket->so_rcv); + soupcall_set(cs->cs_socket, SO_RCV, cfiscsi_soupcall_receive, cs); + SOCKBUF_UNLOCK(&cs->cs_socket->so_rcv); + + mtx_lock(&softc->lock); + TAILQ_INSERT_TAIL(&softc->sessions, cs, cs_next); + mtx_unlock(&softc->lock); + + /* + * Send the first ping (Data-In) as soon as possible to kick + * receive callout to start processing packets. + */ + callout_init(&cs->cs_ping_callout, 1); + callout_reset(&cs->cs_ping_callout, 5, cfiscsi_ping, cs); + + return (cs); +fail: + CFISCSI_WARN(("%s: failed\n", __func__)); + cfiscsi_session_unregister_initiator(cs); + if (cs->cs_socket != NULL) + soclose(cs->cs_socket); + if (cs->cs_target != NULL) + cfiscsi_target_release(cs->cs_target); + free(cs, M_ISCSI); + return (NULL); +} + +static void +cfiscsi_session_delete(struct cfiscsi_session *cs) +{ + struct cfiscsi_softc *softc; + + softc = cs->cs_target->ct_softc; + + KASSERT(cs->cs_outstanding_pdus == 0, + ("destroying session with outstanding pdus")); + KASSERT(TAILQ_EMPTY(&cs->cs_to_send), + ("destroying session with non-empty queue")); + KASSERT(TAILQ_EMPTY(&cs->cs_waiting_for_data_out), + ("destroying session with non-empty queue")); + + cfiscsi_session_unregister_initiator(cs); + cfiscsi_target_release(cs->cs_target); + soclose(cs->cs_socket); + + mtx_lock(&softc->lock); + TAILQ_REMOVE(&softc->sessions, cs, cs_next); + mtx_unlock(&softc->lock); + + free(cs, M_ISCSI); +} + +static void +cfiscsi_session_terminate_tasks(struct cfiscsi_session *cs) +{ + struct cfiscsi_data_wait *cdw, *tmpcdw; + union ctl_io *io; + int error; + +#ifdef notyet + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_WARN(("%s: can't allocate ctl_io\n", __func__)); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = NULL; + io->io_hdr.io_type = CTL_IO_TASK; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + io->io_hdr.nexus.targ_lun = lun; + io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ + io->taskio.task_action = CTL_TASK_ABORT_TASK_SET; + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_WARN(("%s: ctl_queue() failed; error %d\n", + __func__, error)); + ctl_free_io(io); + } +#else + /* + * CTL doesn't currently support CTL_TASK_ABORT_TASK_SET, so instead + * just iterate over tasks that are waiting for something - data - and + * terminate those. + */ + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH_SAFE(cdw, + &cs->cs_waiting_for_data_out, cdw_next, tmpcdw) { + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_WARN(("%s: can't allocate ctl_io\n", __func__)); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = NULL; + io->io_hdr.io_type = CTL_IO_TASK; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = + cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + //io->io_hdr.nexus.targ_lun = lun; /* Not needed? */ + io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ + io->taskio.task_action = CTL_TASK_ABORT_TASK; + io->taskio.tag_num = cdw->cdw_initiator_task_tag; + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_WARN(("%s: ctl_queue() failed; error %d\n", + __func__, error)); + ctl_free_io(io); + return; + } + CFISCSI_DEBUG(("%s: removing csw for initiator task tag " + "0x%x\n", __func__, cdw->cdw_initiator_task_tag)); + cdw->cdw_ctl_io->scsiio.be_move_done(cdw->cdw_ctl_io); + TAILQ_REMOVE(&cs->cs_waiting_for_data_out, cdw, cdw_next); + uma_zfree(cfiscsi_data_wait_zone, cdw); + } + CFISCSI_SESSION_UNLOCK(cs); +#endif +} + +static void +cfiscsi_terminate_callout(void *context) +{ + struct cfiscsi_session *cs; + struct cfiscsi_pdu *response; + + cs = context; + + KASSERT(cs->cs_terminating > 0, ("cs_terminating == 0")); + + cfiscsi_session_terminate_tasks(cs); + callout_drain(&cs->cs_ping_callout); + + cs->cs_terminating++; + if (cs->cs_receive_pdu != NULL) { + CFISCSI_DEBUG(("%s: freeing partially received PDU\n", + __func__)); + cfiscsi_pdu_free(cs->cs_receive_pdu); + cs->cs_receive_pdu = NULL; + } + + if (cs->cs_terminating <= 30 && cs->cs_outstanding_pdus != 0) { + /* + * Give the send upcall 30 seconds to send PDUs + * to the initiator. + */ + CFISCSI_DEBUG(("%s: can't terminate; still have %d PDUs " + "outstanding\n", __func__, cs->cs_outstanding_pdus)); + callout_schedule(&cs->cs_terminate_callout, 1 * hz); + return; + } + + soshutdown(cs->cs_socket, SHUT_RDWR); + + /* + * Receive upcall gets deleted earlier, in cfiscsi_session_terminate(). + */ + SOCKBUF_LOCK(&cs->cs_socket->so_snd); + soupcall_clear(cs->cs_socket, SO_SND); + SOCKBUF_UNLOCK(&cs->cs_socket->so_snd); + + /* + * Remove PDUs from the send queue. + */ + while (!TAILQ_EMPTY(&cs->cs_to_send)) { + response = TAILQ_FIRST(&cs->cs_to_send); + cfiscsi_pdu_free(response); + TAILQ_REMOVE(&cs->cs_to_send, response, cp_next); + } + KASSERT(cs->cs_outstanding_pdus == 0, + ("still have %d PDUs outstanding", cs->cs_outstanding_pdus)); + + /* + * XXX: There is still a small race with the send upcall. + */ + + cfiscsi_session_delete(cs); +} + +static void +cfiscsi_session_terminate(struct cfiscsi_session *cs) +{ + + CFISCSI_SESSION_LOCK(cs); + if (cs->cs_terminating != 0) { + CFISCSI_SESSION_UNLOCK(cs); + return; + } + cs->cs_terminating = 1; + CFISCSI_SESSION_UNLOCK(cs); + + /* + * Unregister the receive socket upcall, so that new requests + * don't get delivered any more. + */ + SOCKBUF_LOCK(&cs->cs_socket->so_rcv); + soupcall_clear(cs->cs_socket, SO_RCV); + SOCKBUF_UNLOCK(&cs->cs_socket->so_rcv); + + /* + * Wait one second before trying to destroy the session + * to avoid race with cfiscsi_soupcall_receive(). + */ + callout_init(&cs->cs_terminate_callout, 1); + callout_reset(&cs->cs_terminate_callout, 1 * hz, + cfiscsi_terminate_callout, cs); +} + +int +cfiscsi_init(void) +{ + struct cfiscsi_softc *softc; + struct ctl_frontend *fe; + int retval; + + /* Don't continue if CTL is disabled */ + if (ctl_disable != 0) + return (0); + + softc = &cfiscsi_softc; + retval = 0; + bzero(softc, sizeof(*softc)); + mtx_init(&softc->lock, "iscsi", NULL, MTX_DEF); + + TAILQ_INIT(&softc->sessions); + TAILQ_INIT(&softc->targets); + + fe = &softc->fe; + fe->port_type = CTL_PORT_ISCSI; + /* XXX KDM what should the real number be here? */ + fe->num_requested_ctl_io = 4096; + snprintf(softc->port_name, sizeof(softc->port_name), "iscsi"); + fe->port_name = softc->port_name; + fe->port_online = cfiscsi_online; + fe->port_offline = cfiscsi_offline; + fe->onoff_arg = softc; + fe->targ_enable = cfiscsi_targ_enable; + fe->targ_disable = cfiscsi_targ_disable; + fe->lun_enable = cfiscsi_lun_enable; + fe->lun_disable = cfiscsi_lun_disable; + fe->targ_lun_arg = softc; + fe->ioctl = cfiscsi_ioctl; + fe->devid = cfiscsi_devid; + fe->fe_datamove = cfiscsi_datamove; + fe->fe_done = cfiscsi_done; + + /* XXX KDM what should we report here? */ + /* XXX These should probably be fetched from CTL. */ + fe->max_targets = 1; + fe->max_target_id = 15; + + retval = ctl_frontend_register(fe, /*master_SC*/ 1); + if (retval != 0) { + CFISCSI_WARN(("%s: ctl_frontend_register() failed " + "with error %d!\n", __func__, retval)); + retval = 1; + goto bailout; + } + + softc->max_initiators = fe->max_initiators; + + cfiscsi_pdu_zone = uma_zcreate("cfiscsi_pdu", + sizeof(struct cfiscsi_pdu), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + cfiscsi_data_wait_zone = uma_zcreate("cfiscsi_data_wait", + sizeof(struct cfiscsi_data_wait), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + + return (0); + +bailout: + return (retval); +} + +static void +cfiscsi_online(void *arg) +{ + struct cfiscsi_softc *softc; + + softc = (struct cfiscsi_softc *)arg; + + softc->online = 1; +} + +static void +cfiscsi_offline(void *arg) +{ + struct cfiscsi_softc *softc; + struct cfiscsi_session *cs; + + softc = (struct cfiscsi_softc *)arg; + + softc->online = 0; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) + cfiscsi_session_terminate(cs); + mtx_unlock(&softc->lock); +} + +static int +cfiscsi_targ_enable(void *arg, struct ctl_id targ_id) +{ + + return (0); +} + +static int +cfiscsi_targ_disable(void *arg, struct ctl_id targ_id) +{ + + return (0); +} + +static void +cfiscsi_ioctl_handoff(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_handoff_params *cihp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + + cihp = (struct ctl_iscsi_handoff_params *)&(ci->data); + softc = &cfiscsi_softc; + +#if 0 + CFISCSI_DEBUG(("%s: socket = %d, target = %s, cmdsn = %d, " + "statsn = %d, dslen = %d\n", __func__, cihp->socket, + cihp->target_name, cihp->cmdsn, cihp->statsn, + cihp->max_recv_data_segment_length)); +#else + CFISCSI_DEBUG(("%s: new connection from %s (%s) to %s\n", + __func__, cihp->initiator_name, cihp->initiator_addr, + cihp->target_name)); +#endif + if (softc->online == 0) { + CFISCSI_WARN(("%s: iscsi CTL port offline; " + "dropping connection\n", __func__)); + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: port offline", __func__); + } + + cs = cfiscsi_session_new(softc, cihp->socket, cihp->initiator_name, + cihp->initiator_addr, cihp->target_name, cihp->cmdsn, cihp->statsn, + cihp->max_recv_data_segment_length, cihp->header_digest, + cihp->data_digest, cihp->portal_group_tag); + if (cs == NULL) { + CFISCSI_WARN(("%s: session NOT created\n", __func__)); + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: failed to create session", __func__); + return; + } + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_list(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_list_params *cilp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + struct sbuf *sb; + int error; + + cilp = (struct ctl_iscsi_list_params *)&(ci->data); + softc = &cfiscsi_softc; + + sb = sbuf_new(NULL, NULL, cilp->alloc_len, SBUF_FIXEDLEN); + if (sb == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "Unable to allocate %d bytes for iSCSI session list", + cilp->alloc_len); + return; + } + + sbuf_printf(sb, "\n"); + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) { + error = sbuf_printf(sb, "" + "%s" + "%s" + "%s\n", + cs->cs_id, cs->cs_initiator_name, cs->cs_initiator_addr, + cs->cs_target->ct_name); + if (error != 0) + break; + } + mtx_unlock(&softc->lock); + error = sbuf_printf(sb, "\n"); + if (error != 0) { + sbuf_delete(sb); + ci->status = CTL_ISCSI_LIST_NEED_MORE_SPACE; + snprintf(ci->error_str, sizeof(ci->error_str), + "Out of space, %d bytes is too small", cilp->alloc_len); + return; + } + sbuf_finish(sb); + + error = copyout(sbuf_data(sb), cilp->conn_xml, sbuf_len(sb) + 1); + cilp->fill_len = sbuf_len(sb) + 1; + ci->status = CTL_ISCSI_OK; + sbuf_delete(sb); +} + +static void +cfiscsi_ioctl_terminate(struct ctl_iscsi *ci) +{ + struct cfiscsi_pdu *response; + struct iscsi_bhs_asynchronous_message *bhsam; + struct ctl_iscsi_terminate_params *citp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + int found = 0; + + citp = (struct ctl_iscsi_terminate_params *)&(ci->data); + softc = &cfiscsi_softc; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) { + if (citp->all == 0 && cs->cs_id != citp->connection_id && + strcmp(cs->cs_initiator_name, citp->initiator_name) != 0 && + strcmp(cs->cs_initiator_addr, citp->initiator_addr) != 0) + continue; + + response = cfiscsi_pdu_new_bhs(cs, M_NOWAIT); + if (response == NULL) { + /* + * Oh well. Just terminate the connection. + */ + } else { + bhsam = (struct iscsi_bhs_asynchronous_message *) + response->cp_bhs; + bhsam->bhsam_opcode = ISCSI_BHS_OPCODE_ASYNC_MESSAGE; + bhsam->bhsam_flags = 0x80; + bhsam->bhsam_0xffffffff = 0xffffffff; + bhsam->bhsam_async_event = + BHSAM_EVENT_TARGET_TERMINATES_SESSION; + cfiscsi_pdu_queue(response); + } + cfiscsi_session_terminate(cs); + found++; + } + mtx_unlock(&softc->lock); + + if (found == 0) { + ci->status = CTL_ISCSI_SESSION_NOT_FOUND; + snprintf(ci->error_str, sizeof(ci->error_str), + "No matching connections found"); + return; + } + + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_logout(struct ctl_iscsi *ci) +{ + struct cfiscsi_pdu *response; + struct iscsi_bhs_asynchronous_message *bhsam; + struct ctl_iscsi_logout_params *cilp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + int found = 0; + + cilp = (struct ctl_iscsi_logout_params *)&(ci->data); + softc = &cfiscsi_softc; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) { + if (cilp->all == 0 && cs->cs_id != cilp->connection_id && + strcmp(cs->cs_initiator_name, cilp->initiator_name) != 0 && + strcmp(cs->cs_initiator_addr, cilp->initiator_addr) != 0) + continue; + + response = cfiscsi_pdu_new_bhs(cs, M_NOWAIT); + if (response == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "Unable to allocate memory"); + mtx_unlock(&softc->lock); + return; + } + bhsam = + (struct iscsi_bhs_asynchronous_message *)response->cp_bhs; + bhsam->bhsam_opcode = ISCSI_BHS_OPCODE_ASYNC_MESSAGE; + bhsam->bhsam_flags = 0x80; + bhsam->bhsam_async_event = BHSAM_EVENT_TARGET_REQUESTS_LOGOUT; + bhsam->bhsam_parameter3 = htons(10); + cfiscsi_pdu_queue(response); + found++; + } + mtx_unlock(&softc->lock); + + if (found == 0) { + ci->status = CTL_ISCSI_SESSION_NOT_FOUND; + snprintf(ci->error_str, sizeof(ci->error_str), + "No matching connections found"); + return; + } + + ci->status = CTL_ISCSI_OK; +} + +static int +cfiscsi_ioctl(struct cdev *dev, + u_long cmd, caddr_t addr, int flag, struct thread *td) +{ + struct ctl_iscsi *ci; + + if (cmd != CTL_ISCSI) + return (ENOTTY); + + ci = (struct ctl_iscsi *)addr; + switch (ci->type) { + case CTL_ISCSI_HANDOFF: + cfiscsi_ioctl_handoff(ci); + break; + case CTL_ISCSI_LIST: + cfiscsi_ioctl_list(ci); + break; + case CTL_ISCSI_TERMINATE: + cfiscsi_ioctl_terminate(ci); + break; + case CTL_ISCSI_LOGOUT: + cfiscsi_ioctl_logout(ci); + break; + default: + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: invalid iSCSI request type %d", __func__, ci->type); + break; + } + + return (0); +} + +static int +cfiscsi_devid(struct ctl_scsiio *ctsio, int alloc_len) +{ + struct scsi_vpd_device_id *devid_ptr; + struct scsi_vpd_id_descriptor *desc, *desc1; + struct scsi_vpd_id_descriptor *desc2, *desc3; /* for types 4h and 5h */ + struct scsi_vpd_id_t10 *t10id; + struct ctl_lun *lun; + const struct cfiscsi_pdu *request; + size_t devid_len, wwpn_len; + + lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; + request = ctsio->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + + wwpn_len = strlen(request->cp_session->cs_target->ct_name); + wwpn_len += strlen(",t,0x01"); + wwpn_len += 1; /* '\0' */ + if ((wwpn_len % 4) != 0) + wwpn_len += (4 - (wwpn_len % 4)); + + devid_len = sizeof(struct scsi_vpd_device_id) + + sizeof(struct scsi_vpd_id_descriptor) + + sizeof(struct scsi_vpd_id_t10) + CTL_DEVID_LEN + + sizeof(struct scsi_vpd_id_descriptor) + wwpn_len + + sizeof(struct scsi_vpd_id_descriptor) + + sizeof(struct scsi_vpd_id_rel_trgt_port_id) + + sizeof(struct scsi_vpd_id_descriptor) + + sizeof(struct scsi_vpd_id_trgt_port_grp_id); + + ctsio->kern_data_ptr = malloc(devid_len, M_CTL, M_WAITOK | M_ZERO); + devid_ptr = (struct scsi_vpd_device_id *)ctsio->kern_data_ptr; + ctsio->kern_sg_entries = 0; + + if (devid_len < alloc_len) { + ctsio->residual = alloc_len - devid_len; + ctsio->kern_data_len = devid_len; + ctsio->kern_total_len = devid_len; + } else { + ctsio->residual = 0; + ctsio->kern_data_len = alloc_len; + ctsio->kern_total_len = alloc_len; + } + ctsio->kern_data_resid = 0; + ctsio->kern_rel_offset = 0; + ctsio->kern_sg_entries = 0; + + desc = (struct scsi_vpd_id_descriptor *)devid_ptr->desc_list; + t10id = (struct scsi_vpd_id_t10 *)&desc->identifier[0]; + desc1 = (struct scsi_vpd_id_descriptor *)(&desc->identifier[0] + + sizeof(struct scsi_vpd_id_t10) + CTL_DEVID_LEN); + desc2 = (struct scsi_vpd_id_descriptor *)(&desc1->identifier[0] + + wwpn_len); + desc3 = (struct scsi_vpd_id_descriptor *)(&desc2->identifier[0] + + sizeof(struct scsi_vpd_id_rel_trgt_port_id)); + + if (lun != NULL) + devid_ptr->device = (SID_QUAL_LU_CONNECTED << 5) | + lun->be_lun->lun_type; + else + devid_ptr->device = (SID_QUAL_LU_OFFLINE << 5) | T_DIRECT; + + devid_ptr->page_code = SVPD_DEVICE_ID; + + scsi_ulto2b(devid_len - 4, devid_ptr->length); + + /* + * We're using a LUN association here. i.e., this device ID is a + * per-LUN identifier. + */ + desc->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_ASCII; + desc->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_LUN | SVPD_ID_TYPE_T10; + desc->length = sizeof(*t10id) + CTL_DEVID_LEN; + strncpy((char *)t10id->vendor, CTL_VENDOR, sizeof(t10id->vendor)); + + /* + * If we've actually got a backend, copy the device id from the + * per-LUN data. Otherwise, set it to all spaces. + */ + if (lun != NULL) { + /* + * Copy the backend's LUN ID. + */ + strncpy((char *)t10id->vendor_spec_id, + (char *)lun->be_lun->device_id, CTL_DEVID_LEN); + } else { + /* + * No backend, set this to spaces. + */ + memset(t10id->vendor_spec_id, 0x20, CTL_DEVID_LEN); + } + + /* + * desc1 is for the WWPN which is a port asscociation. + */ + desc1->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_UTF8; + desc1->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_PORT | + SVPD_ID_TYPE_SCSI_NAME; + desc1->length = wwpn_len; + snprintf(desc1->identifier, wwpn_len, "%s,t,0x%x", + request->cp_session->cs_target->ct_name, + request->cp_session->cs_portal_group_tag); + + /* + * desc2 is for the Relative Target Port(type 4h) identifier + */ + desc2->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_BINARY; + desc2->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_PORT | + SVPD_ID_TYPE_RELTARG; + desc2->length = 4; + desc2->identifier[3] = 1; + + /* + * desc3 is for the Target Port Group(type 5h) identifier + */ + desc3->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_BINARY; + desc3->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_PORT | + SVPD_ID_TYPE_TPORTGRP; + desc3->length = 4; + desc3->identifier[3] = 1; + + ctsio->scsi_status = SCSI_STATUS_OK; + + ctsio->be_move_done = ctl_config_move_done; + ctl_datamove((union ctl_io *)ctsio); + + return (CTL_RETVAL_COMPLETE); +} + +static void +cfiscsi_target_hold(struct cfiscsi_target *ct) +{ + + refcount_acquire(&ct->ct_refcount); +} + +static void +cfiscsi_target_release(struct cfiscsi_target *ct) +{ + int old; + struct cfiscsi_softc *softc; + + softc = ct->ct_softc; + + old = ct->ct_refcount; + if (old > 1 && atomic_cmpset_int(&ct->ct_refcount, old, old - 1)) + return; + + mtx_lock(&softc->lock); + if (refcount_release(&ct->ct_refcount)) { + TAILQ_REMOVE(&softc->targets, ct, ct_next); + mtx_unlock(&softc->lock); + free(ct, M_ISCSI); + + return; + } + mtx_unlock(&softc->lock); +} + +static struct cfiscsi_target * +cfiscsi_target_find(struct cfiscsi_softc *softc, const char *name) +{ + struct cfiscsi_target *ct; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(ct, &softc->targets, ct_next) { + if (strcmp(name, ct->ct_name) != 0) + continue; + cfiscsi_target_hold(ct); + mtx_unlock(&softc->lock); + return (ct); + } + mtx_unlock(&softc->lock); + + return (NULL); +} + +static struct cfiscsi_target * +cfiscsi_target_find_or_create(struct cfiscsi_softc *softc, const char *name) +{ + struct cfiscsi_target *ct, *newct; + int i; + + if (name[0] == '\0' || strlen(name) > CTL_ISCSI_NAME_LEN) + return (NULL); + + newct = malloc(sizeof(*newct), M_ISCSI, M_WAITOK | M_ZERO); + + mtx_lock(&softc->lock); + TAILQ_FOREACH(ct, &softc->targets, ct_next) { + if (strcmp(name, ct->ct_name) != 0) + continue; + cfiscsi_target_hold(ct); + mtx_unlock(&softc->lock); + free(newct, M_ISCSI); + return (ct); + } + + for (i = 0; i < CTL_MAX_LUNS; i++) + newct->ct_luns[i] = -1; + + strcpy(newct->ct_name, name); + refcount_init(&newct->ct_refcount, 1); + newct->ct_softc = softc; + TAILQ_INSERT_TAIL(&softc->targets, newct, ct_next); + mtx_unlock(&softc->lock); + + return (newct); +} + +/* + * Takes LUN from the target space and returns LUN from the CTL space. + */ +static uint32_t +cfiscsi_map_lun(void *arg, uint32_t lun) +{ + struct cfiscsi_session *cs; + + cs = arg; + + if (lun >= CTL_MAX_LUNS) { + CFISCSI_DEBUG(("%s: requested lun number %d is higher " + "than maximum %d\n", __func__, lun, CTL_MAX_LUNS - 1)); + return (0xffffffff); + } + + if (cs->cs_target->ct_luns[lun] < 0) + return (0xffffffff); + + return (cs->cs_target->ct_luns[lun]); +} + +static int +cfiscsi_target_set_lun(struct cfiscsi_target *ct, + unsigned long lun_id, unsigned long ctl_lun_id) +{ + + if (lun_id >= CTL_MAX_LUNS) { + CFISCSI_WARN(("%s: requested lun number %ld is higher " + "than maximum %d\n", __func__, lun_id, CTL_MAX_LUNS - 1)); + return (-1); + } + + if (ct->ct_luns[lun_id] >= 0) { + /* + * CTL calls cfiscsi_lun_enable() twice for each LUN - once + * when the LUN is created, and a second time just before + * the port is brought online; don't emit warnings + * for that case. + */ + if (ct->ct_luns[lun_id] == ctl_lun_id) + return (0); + CFISCSI_WARN(("%s: lun %ld already allocated\n", + __func__, lun_id)); + return (-1); + } + + CFISCSI_DEBUG(("%s: adding mapping for lun %ld, target %s " + "to ctl lun %ld\n", __func__, lun_id, ct->ct_name, ctl_lun_id)); + + ct->ct_luns[lun_id] = ctl_lun_id; + cfiscsi_target_hold(ct); + + return (0); +} + +static int +cfiscsi_target_unset_lun(struct cfiscsi_target *ct, unsigned long lun_id) +{ + + if (ct->ct_luns[lun_id] < 0) { + CFISCSI_WARN(("%s: lun %ld not allocated\n", __func__, lun_id)); + return (-1); + } + + ct->ct_luns[lun_id] = -1; + cfiscsi_target_release(ct); + + return (0); +} + +static int +cfiscsi_lun_enable(void *arg, struct ctl_id target_id, int lun_id) +{ + struct cfiscsi_softc *softc; + struct cfiscsi_target *ct; + struct ctl_be_lun_option *opt; + const char *ctld_target = NULL, *ctld_lun = NULL; + unsigned long ctld_lun_id; + + softc = (struct cfiscsi_softc *)arg; + + STAILQ_FOREACH(opt, + &control_softc->ctl_luns[lun_id]->be_lun->options, links) { + if (strcmp(opt->name, "ctld_target") == 0) + ctld_target = opt->value; + else if (strcmp(opt->name, "ctld_lun") == 0) + ctld_lun = opt->value; + } + + if (ctld_target == NULL && ctld_lun == NULL) + return (0); + + if (ctld_target == NULL || ctld_lun == NULL) { + CFISCSI_DEBUG(("%s: lun added with ctld_target, but without " + "ctld_lun, or the other way around; ignoring\n", __func__)); + return (0); + } + + ct = cfiscsi_target_find_or_create(softc, ctld_target); + if (ct == NULL) { + CFISCSI_WARN(("%s: failed to create target \"%s\"\n", + __func__, ctld_target)); + return (0); + } + + ctld_lun_id = strtoul(ctld_lun, NULL, 10); + cfiscsi_target_set_lun(ct, ctld_lun_id, lun_id); + return (0); +} + +static int +cfiscsi_lun_disable(void *arg, struct ctl_id target_id, int lun_id) +{ + struct cfiscsi_softc *softc; + struct cfiscsi_target *ct; + int i; + + softc = (struct cfiscsi_softc *)arg; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(ct, &softc->targets, ct_next) { + for (i = 0; i < CTL_MAX_LUNS; i++) { + if (ct->ct_luns[i] < 0) + continue; + if (ct->ct_luns[i] != lun_id) + continue; + cfiscsi_target_unset_lun(ct, i); + break; + } + } + mtx_unlock(&softc->lock); + return (0); +} + +static void +cfiscsi_datamove(union ctl_io *io) +{ + struct cfiscsi_session *cs; + struct cfiscsi_pdu *request, *response; + const struct iscsi_bhs_scsi_command *bhssc; + struct iscsi_bhs_data_in *bhsdi; + struct iscsi_bhs_r2t *bhsr2t; + struct cfiscsi_data_wait *cdw; + struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; + size_t copy_len, len, off; + const char *addr; + int ctl_sg_count, i; + uint32_t target_transfer_tag; + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = request->cp_session; + + bhssc = (const struct iscsi_bhs_scsi_command *)request->cp_bhs; + KASSERT((bhssc->bhssc_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_COMMAND, + ("bhssc->bhssc_opcode != ISCSI_BHS_OPCODE_SCSI_COMMAND")); + + if (io->scsiio.kern_sg_entries > 0) { + ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; + ctl_sg_count = io->scsiio.kern_sg_entries; + } else { + ctl_sglist = &ctl_sg_entry; + ctl_sglist->addr = io->scsiio.kern_data_ptr; + ctl_sglist->len = io->scsiio.kern_data_len; + ctl_sg_count = 1; + } + + /* + * We need to record it so that we can properly report + * underflow/underflow. + */ + request->cp_total_transfer_len = io->scsiio.kern_total_len; + + if ((io->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN) { +#if 0 + if (ctl_sg_count > 1) + CFISCSI_DEBUG(("%s: ctl_sg_count = %d\n", + __func__, ctl_sg_count)); +#endif + + /* + * This is the offset within the current SCSI command; + * i.e. for the first call of datamove(), it will be 0, + * and for subsequent ones it will be the sum of lengths + * of previous ones. + */ + off = htonl(io->scsiio.kern_rel_offset); + if (off > 1) + CFISCSI_DEBUG(("%s: off = %zd\n", __func__, off)); + + i = 0; + addr = NULL; + len = 0; + response = NULL; + bhsdi = NULL; + for (;;) { + KASSERT(i < ctl_sg_count, ("i >= ctl_sg_count")); + if (response == NULL) { + response = + cfiscsi_pdu_new_response(request, M_WAITOK); + bhsdi = (struct iscsi_bhs_data_in *) + response->cp_bhs; + bhsdi->bhsdi_opcode = + ISCSI_BHS_OPCODE_SCSI_DATA_IN; + bhsdi->bhsdi_initiator_task_tag = + bhssc->bhssc_initiator_task_tag; + bhsdi->bhsdi_datasn = + htonl(request->cp_expdatasn); + request->cp_expdatasn++; + bhsdi->bhsdi_buffer_offset = htonl(off); + } + + if (len == 0) { + addr = ctl_sglist[i].addr; + len = ctl_sglist[i].len; + KASSERT(len > 0, ("len <= 0")); + } + + copy_len = len; + if (response->cp_data_len + copy_len > + cs->cs_max_data_segment_length) + copy_len = cs->cs_max_data_segment_length - + response->cp_data_len; + KASSERT(copy_len <= len, ("copy_len > len")); + cfiscsi_pdu_append_data(response, addr, copy_len); + addr += copy_len; + len -= copy_len; + off += copy_len; + io->scsiio.ext_data_filled += copy_len; + + if (len == 0) { + /* + * End of scatter-gather segment; + * proceed to the next one... + */ + if (i == ctl_sg_count - 1) { + /* + * ... unless this was the last one. + */ + break; + } + i++; + } + + if (response->cp_data_len == + cs->cs_max_data_segment_length) { + /* + * Can't stuff more data into the current PDU; + * queue it. Note that's not enough to check + * for kern_data_resid == 0 instead; there + * may be several Data-In PDUs for the final + * call to cfiscsi_datamove(), and we want + * to set the F flag only on the last of them. + */ + if (off == io->scsiio.kern_total_len) + bhsdi->bhsdi_flags |= BHSDI_FLAGS_F; + KASSERT(response->cp_data_len > 0, + ("sending empty Data-In")); + cfiscsi_pdu_queue(response); + response = NULL; + bhsdi = NULL; + } + } + KASSERT(i == ctl_sg_count - 1, ("missed SG segment")); + KASSERT(len == 0, ("missed data from SG segment")); + if (response != NULL) { + if (off == io->scsiio.kern_total_len) { + bhsdi->bhsdi_flags |= BHSDI_FLAGS_F; + } else { + CFISCSI_DEBUG(("%s: not setting the F flag; " + "have %zd, need %zd\n", __func__, off, + (size_t)io->scsiio.kern_total_len)); + } + KASSERT(response->cp_data_len > 0, + ("sending empty Data-In")); + cfiscsi_pdu_queue(response); + } + + io->scsiio.be_move_done(io); + } else { + CFISCSI_SESSION_LOCK(cs); + target_transfer_tag = cs->cs_target_transfer_tag; + cs->cs_target_transfer_tag++; + CFISCSI_SESSION_UNLOCK(cs); + +#if 0 + CFISCSI_DEBUG(("%s: expecting Data-Out with initiator " + "task tag 0x%x, target transfer tag 0x%x\n", __func__, + bhssc->bhssc_initiator_task_tag, target_transfer_tag)); +#endif + cdw = uma_zalloc(cfiscsi_data_wait_zone, M_WAITOK | M_ZERO); + cdw->cdw_ctl_io = io; + cdw->cdw_target_transfer_tag = htonl(target_transfer_tag); + cdw->cdw_initiator_task_tag = bhssc->bhssc_initiator_task_tag; + CFISCSI_SESSION_LOCK(cs); + TAILQ_INSERT_TAIL(&cs->cs_waiting_for_data_out, cdw, cdw_next); + CFISCSI_SESSION_UNLOCK(cs); + + /* + * XXX: We should limit the number of outstanding R2T PDUs + * per task to MaxOutstandingR2T. + */ + response = cfiscsi_pdu_new_response(request, M_WAITOK); + bhsr2t = (struct iscsi_bhs_r2t *)response->cp_bhs; + bhsr2t->bhsr2t_opcode = ISCSI_BHS_OPCODE_R2T; + bhsr2t->bhsr2t_flags = 0x80; + bhsr2t->bhsr2t_lun = bhssc->bhssc_lun; + bhsr2t->bhsr2t_initiator_task_tag = + bhssc->bhssc_initiator_task_tag; + bhsr2t->bhsr2t_target_transfer_tag = + htonl(target_transfer_tag); + /* + * XXX: Here we assume that cfiscsi_datamove() won't ever + * be running concurrently on several CPUs for a given + * command. + */ + bhsr2t->bhsr2t_r2tsn = htonl(request->cp_r2tsn); + request->cp_r2tsn++; + /* + * This is the offset within the current SCSI command; + * i.e. for the first call of datamove(), it will be 0, + * and for subsequent ones it will be the sum of lengths + * of previous ones. + */ + bhsr2t->bhsr2t_buffer_offset = + htonl(io->scsiio.kern_rel_offset); + /* + * This is the total length (sum of S/G lengths) this call + * to cfiscsi_datamove() is supposed to handle. + */ + bhsr2t->bhsr2t_desired_data_transfer_length = + htonl(io->scsiio.kern_data_len); + cfiscsi_pdu_queue(response); + } +} + +static void +cfiscsi_scsi_command_done(union ctl_io *io) +{ + struct cfiscsi_pdu *request, *response; + struct iscsi_bhs_scsi_command *bhssc; + struct iscsi_bhs_scsi_response *bhssr; +#ifdef DIAGNOSTIC + struct cfiscsi_data_wait *cdw; +#endif + struct cfiscsi_session *cs; + uint16_t sense_length; + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = request->cp_session; + bhssc = (struct iscsi_bhs_scsi_command *)request->cp_bhs; + KASSERT((bhssc->bhssc_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_COMMAND, + ("replying to wrong opcode 0x%x", bhssc->bhssc_opcode)); + + //CFISCSI_DEBUG(("%s: initiator task tag 0x%x\n", + // __func__, bhssc->bhssc_initiator_task_tag)); + +#ifdef DIAGNOSTIC + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH(cdw, &cs->cs_waiting_for_data_out, cdw_next) + KASSERT(bhssc->bhssc_initiator_task_tag != + cdw->cdw_initiator_task_tag, ("dangling cdw")); + CFISCSI_SESSION_UNLOCK(cs); +#endif + + response = cfiscsi_pdu_new_response(request, M_WAITOK); + bhssr = (struct iscsi_bhs_scsi_response *)response->cp_bhs; + bhssr->bhssr_opcode = ISCSI_BHS_OPCODE_SCSI_RESPONSE; + bhssr->bhssr_flags = 0x80; + /* + * XXX: We don't deal with bidirectional under/overflows; + * does anything actually support those? + */ + if (request->cp_total_transfer_len < + ntohl(bhssc->bhssc_expected_data_transfer_length)) { + bhssr->bhssr_flags |= BHSSR_FLAGS_RESIDUAL_UNDERFLOW; + bhssr->bhssr_residual_count = + htonl(ntohl(bhssc->bhssc_expected_data_transfer_length) - + request->cp_total_transfer_len); + //CFISCSI_DEBUG(("%s: underflow; residual count %d\n", + // __func__, ntohl(bhssr->bhssr_residual_count))); + } else if (request->cp_total_transfer_len > + ntohl(bhssc->bhssc_expected_data_transfer_length)) { + bhssr->bhssr_flags |= BHSSR_FLAGS_RESIDUAL_OVERFLOW; + bhssr->bhssr_residual_count = + htonl(request->cp_total_transfer_len - + ntohl(bhssc->bhssc_expected_data_transfer_length)); + //CFISCSI_DEBUG(("%s: overflow; residual count %d\n", + // __func__, ntohl(bhssr->bhssr_residual_count))); + } + bhssr->bhssr_response = BHSSR_RESPONSE_COMMAND_COMPLETED; + bhssr->bhssr_status = io->scsiio.scsi_status; + bhssr->bhssr_initiator_task_tag = bhssc->bhssc_initiator_task_tag; + bhssr->bhssr_expdatasn = htonl(request->cp_expdatasn); + + if (io->scsiio.sense_len > 0) { + CFISCSI_DEBUG(("%s: returning %d bytes of sense data\n", + __func__, io->scsiio.sense_len)); + sense_length = htons(io->scsiio.sense_len); + cfiscsi_pdu_append_data(response, + &sense_length, sizeof(sense_length)); + cfiscsi_pdu_append_data(response, + &io->scsiio.sense_data, io->scsiio.sense_len); + } + + ctl_free_io(io); + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_task_management_done(union ctl_io *io) +{ + struct cfiscsi_pdu *request, *response; + struct iscsi_bhs_task_management_request *bhstmr; + struct iscsi_bhs_task_management_response *bhstmr2; + struct cfiscsi_data_wait *cdw, *tmpcdw; + struct cfiscsi_session *cs; + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = request->cp_session; + bhstmr = (struct iscsi_bhs_task_management_request *)request->cp_bhs; + KASSERT((bhstmr->bhstmr_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_TASK_REQUEST, + ("replying to wrong opcode 0x%x", bhstmr->bhstmr_opcode)); + +#if 0 + CFISCSI_DEBUG(("%s: initiator task tag 0x%x; referenced task tag " + "0x%x\n", __func__, bhstmr->bhstmr_initiator_task_tag, + bhstmr->bhstmr_referenced_task_tag)); +#endif + + if ((bhstmr->bhstmr_function & ~0x80) == + BHSTMR_FUNCTION_ABORT_TASK) { + /* + * Make sure we no longer wait for Data-Out for this command. + */ + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH_SAFE(cdw, + &cs->cs_waiting_for_data_out, cdw_next, tmpcdw) { + if (bhstmr->bhstmr_referenced_task_tag != + cdw->cdw_initiator_task_tag) + continue; + +#if 0 + CFISCSI_DEBUG(("%s: removing csw for initiator task " + "tag 0x%x\n", __func__, + bhstmr->bhstmr_initiator_task_tag)); +#endif + TAILQ_REMOVE(&cs->cs_waiting_for_data_out, + cdw, cdw_next); + cdw->cdw_ctl_io->scsiio.be_move_done(cdw->cdw_ctl_io); + uma_zfree(cfiscsi_data_wait_zone, cdw); + } + CFISCSI_SESSION_UNLOCK(cs); + } + + response = cfiscsi_pdu_new_response(request, M_WAITOK); + bhstmr2 = (struct iscsi_bhs_task_management_response *) + response->cp_bhs; + bhstmr2->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_RESPONSE; + bhstmr2->bhstmr_flags = 0x80; + if (io->io_hdr.status == CTL_SUCCESS) { + bhstmr2->bhstmr_response = BHSTMR_RESPONSE_FUNCTION_COMPLETE; + } else { + /* + * XXX: How to figure out what exactly went wrong? iSCSI spec + * expects us to provide detailed error, e.g. "Task does + * not exist" or "LUN does not exist". + */ + CFISCSI_DEBUG(("%s: BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED\n", + __func__)); + bhstmr2->bhstmr_response = + BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED; + } + bhstmr2->bhstmr_initiator_task_tag = bhstmr->bhstmr_initiator_task_tag; + + ctl_free_io(io); + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_done(union ctl_io *io) +{ + struct cfiscsi_pdu *request; + struct cfiscsi_session *cs; + + KASSERT(((io->io_hdr.status & CTL_STATUS_MASK) != CTL_STATUS_NONE), + ("invalid CTL status %#x", io->io_hdr.status)); + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + if (request == NULL) { + /* + * Implicit task termination has just completed; nothing to do. + */ + return; + } + + cs = request->cp_session; + + switch (request->cp_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) { + case ISCSI_BHS_OPCODE_SCSI_COMMAND: + cfiscsi_scsi_command_done(io); + break; + case ISCSI_BHS_OPCODE_TASK_REQUEST: + cfiscsi_task_management_done(io); + break; + default: + panic("cfiscsi_done called with wrong opcode 0x%x", + request->cp_bhs->bhs_opcode); + } +} diff -urNp freebsd/src/sys/cam/ctl/ctl_io.h iscsi/sys/cam/ctl/ctl_io.h --- freebsd/src/sys/cam/ctl/ctl_io.h 2012-11-09 12:32:56.000000000 +0100 +++ iscsi/sys/cam/ctl/ctl_io.h 2013-01-03 21:29:52.161774000 +0100 @@ -204,6 +204,8 @@ struct ctl_nexus { uint32_t targ_port; /* Target port, filled in by PORT */ struct ctl_id targ_target; /* Destination target */ uint32_t targ_lun; /* Destination lun */ + uint32_t (*lun_map_fn)(void *arg, uint32_t lun); + void *lun_map_arg; }; typedef enum { diff -urNp freebsd/src/sys/cam/ctl/ctl_ioctl.h iscsi/sys/cam/ctl/ctl_ioctl.h --- freebsd/src/sys/cam/ctl/ctl_ioctl.h 2012-11-09 12:32:56.000000000 +0100 +++ iscsi/sys/cam/ctl/ctl_ioctl.h 2013-02-19 17:44:21.000000000 +0100 @@ -40,6 +40,8 @@ #ifndef _CTL_IOCTL_H_ #define _CTL_IOCTL_H_ +#include + #define CTL_DEFAULT_DEV "/dev/cam/ctl" /* * Maximum number of targets we support. @@ -588,6 +590,106 @@ struct ctl_lun_list { /* passed to userland */ }; +/* + * iSCSI status + * + * OK: Request completed successfully. + * + * ERROR: An error occured, look at the error string for a + * description of the error. + * + * CTL_ISCSI_LIST_NEED_MORE_SPACE: + * User has to pass larger buffer for CTL_ISCSI_LIST ioctl. + */ +typedef enum { + CTL_ISCSI_OK, + CTL_ISCSI_ERROR, + CTL_ISCSI_LIST_NEED_MORE_SPACE, + CTL_ISCSI_SESSION_NOT_FOUND +} ctl_iscsi_status; + +typedef enum { + CTL_ISCSI_HANDOFF, + CTL_ISCSI_LIST, + CTL_ISCSI_LOGOUT, + CTL_ISCSI_TERMINATE, +} ctl_iscsi_type; + +typedef enum { + CTL_ISCSI_DIGEST_NONE, + CTL_ISCSI_DIGEST_CRC32C +} ctl_iscsi_digest; + +#define CTL_ISCSI_NAME_LEN 224 /* 223 bytes, by RFC 3720, + '\0' */ +#define CTL_ISCSI_ADDR_LEN 47 /* INET6_ADDRSTRLEN + '0\ */ + +struct ctl_iscsi_handoff_params { + char initiator_name[CTL_ISCSI_NAME_LEN]; + char initiator_addr[CTL_ISCSI_ADDR_LEN]; + char target_name[CTL_ISCSI_NAME_LEN]; + int socket; + int portal_group_tag; + + /* + * Connection parameters negotiated by ctld(8). + */ + ctl_iscsi_digest header_digest; + ctl_iscsi_digest data_digest; + uint32_t cmdsn; + uint32_t statsn; + uint32_t max_recv_data_segment_length; + uint32_t max_burst_length; + uint32_t max_outstanding_r2t; +}; + +struct ctl_iscsi_list_params { + uint32_t alloc_len; /* passed to kernel */ + char *conn_xml; /* filled in kernel */ + uint32_t fill_len; /* passed to userland */ +}; + +struct ctl_iscsi_logout_params { + int connection_id; /* passed to kernel */ + char initiator_name[CTL_ISCSI_NAME_LEN]; + /* passed to kernel */ + char initiator_addr[CTL_ISCSI_ADDR_LEN]; + /* passed to kernel */ + int all; /* passed to kernel */ +}; + +struct ctl_iscsi_terminate_params { + int connection_id; /* passed to kernel */ + char initiator_name[CTL_ISCSI_NAME_LEN]; + /* passed to kernel */ + char initiator_addr[CTL_ISCSI_NAME_LEN]; + /* passed to kernel */ + int all; /* passed to kernel */ +}; + +union ctl_iscsi_data { + struct ctl_iscsi_handoff_params handoff; + struct ctl_iscsi_list_params list; + struct ctl_iscsi_logout_params logout; + struct ctl_iscsi_terminate_params terminate; +}; + +/* + * iSCSI interface + * + * status: The status of the request. See above for the + * description of the values of this field. + * + * error_str: If the status indicates an error, this string will + * be filled in to describe the error. + */ +struct ctl_iscsi { + ctl_iscsi_type type; /* passed to kernel */ + union ctl_iscsi_data data; /* passed to kernel */ + ctl_iscsi_status status; /* passed to userland */ + char error_str[CTL_ERROR_STR_LEN]; + /* passed to userland */ +}; + #define CTL_IO _IOWR(CTL_MINOR, 0x00, union ctl_io) #define CTL_ENABLE_PORT _IOW(CTL_MINOR, 0x04, struct ctl_port_entry) #define CTL_DISABLE_PORT _IOW(CTL_MINOR, 0x05, struct ctl_port_entry) @@ -612,6 +714,7 @@ struct ctl_lun_list { #define CTL_LUN_LIST _IOWR(CTL_MINOR, 0x22, struct ctl_lun_list) #define CTL_ERROR_INJECT_DELETE _IOW(CTL_MINOR, 0x23, struct ctl_error_desc) #define CTL_SET_PORT_WWNS _IOW(CTL_MINOR, 0x24, struct ctl_port_entry) +#define CTL_ISCSI _IOWR(CTL_MINOR, 0x25, struct ctl_iscsi) #endif /* _CTL_IOCTL_H_ */ diff -urNp freebsd/src/sys/cam/ctl/iscsi.h iscsi/sys/cam/ctl/iscsi.h --- freebsd/src/sys/cam/ctl/iscsi.h 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/sys/cam/ctl/iscsi.h 2013-02-13 15:12:54.000000000 +0100 @@ -0,0 +1,433 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#ifndef ISCSI_H +#define ISCSI_H + +#ifndef CTASSERT +#define CTASSERT(x) _CTASSERT(x, __LINE__) +#define _CTASSERT(x, y) __CTASSERT(x, y) +#define __CTASSERT(x, y) typedef char __assert_ ## y [(x) ? 1 : -1] +#endif + +#define ISCSI_BHS_SIZE 48 +#define ISCSI_HEADER_DIGEST_SIZE 4 +#define ISCSI_DATA_DIGEST_SIZE 4 + +#define ISCSI_BHS_OPCODE_IMMEDIATE 0x40 + +#define ISCSI_BHS_OPCODE_NOP_OUT 0x00 +#define ISCSI_BHS_OPCODE_SCSI_COMMAND 0x01 +#define ISCSI_BHS_OPCODE_TASK_REQUEST 0x02 +#define ISCSI_BHS_OPCODE_LOGIN_REQUEST 0x03 +#define ISCSI_BHS_OPCODE_TEXT_REQUEST 0x04 +#define ISCSI_BHS_OPCODE_SCSI_DATA_OUT 0x05 +#define ISCSI_BHS_OPCODE_LOGOUT_REQUEST 0x06 + +#define ISCSI_BHS_OPCODE_NOP_IN 0x20 +#define ISCSI_BHS_OPCODE_SCSI_RESPONSE 0x21 +#define ISCSI_BHS_OPCODE_TASK_RESPONSE 0x22 +#define ISCSI_BHS_OPCODE_LOGIN_RESPONSE 0x23 +#define ISCSI_BHS_OPCODE_TEXT_RESPONSE 0x24 +#define ISCSI_BHS_OPCODE_SCSI_DATA_IN 0x25 +#define ISCSI_BHS_OPCODE_LOGOUT_RESPONSE 0x26 +#define ISCSI_BHS_OPCODE_R2T 0x31 +#define ISCSI_BHS_OPCODE_ASYNC_MESSAGE 0x32 +#define ISCSI_BHS_OPCODE_REJECT 0x3f + +struct iscsi_bhs { + uint8_t bhs_opcode; + uint8_t bhs_opcode_specific1[3]; + uint8_t bhs_total_ahs_len; + uint8_t bhs_data_segment_len[3]; + uint64_t bhs_lun; + uint8_t bhs_inititator_task_tag[4]; + uint8_t bhs_opcode_specific4[28]; +}; +CTASSERT(sizeof(struct iscsi_bhs) == ISCSI_BHS_SIZE); + +#define BHSSC_FLAGS_F 0x80 +#define BHSSC_FLAGS_R 0x40 +#define BHSSC_FLAGS_W 0x20 +#define BHSSC_FLAGS_ATTR 0x07 + +struct iscsi_bhs_scsi_command { + uint8_t bhssc_opcode; + uint8_t bhssc_flags; + uint8_t bhssc_reserved[2]; + uint8_t bhssc_total_ahs_len; + uint8_t bhssc_data_segment_len[3]; + uint64_t bhssc_lun; + uint32_t bhssc_initiator_task_tag; + uint32_t bhssc_expected_data_transfer_length; + uint32_t bhssc_cmdsn; + uint32_t bhssc_expstatsn; + uint8_t bhssc_cdb[16]; +}; +CTASSERT(sizeof(struct iscsi_bhs_scsi_command) == ISCSI_BHS_SIZE); + +#define BHSSR_FLAGS_RESIDUAL_UNDERFLOW 0x02 +#define BHSSR_FLAGS_RESIDUAL_OVERFLOW 0x04 + +#define BHSSR_RESPONSE_COMMAND_COMPLETED 0x00 + +struct iscsi_bhs_scsi_response { + uint8_t bhssr_opcode; + uint8_t bhssr_flags; + uint8_t bhssr_response; + uint8_t bhssr_status; + uint8_t bhssr_total_ahs_len; + uint8_t bhssr_data_segment_len[3]; + uint64_t bhssr_reserved; + uint32_t bhssr_initiator_task_tag; + uint32_t bhssr_snack_tag; + uint32_t bhssr_statsn; + uint32_t bhssr_expcmdsn; + uint32_t bhssr_maxcmdsn; + uint32_t bhssr_expdatasn; + uint32_t bhssr_bidirectional_read_residual_count; + uint32_t bhssr_residual_count; +}; +CTASSERT(sizeof(struct iscsi_bhs_scsi_response) == ISCSI_BHS_SIZE); + +#define BHSTMR_FUNCTION_ABORT_TASK 1 +#define BHSTMR_FUNCTION_ABORT_TASK_SET 2 +#define BHSTMR_FUNCTION_CLEAR_ACA 3 +#define BHSTMR_FUNCTION_CLEAR_TASK_SET 4 +#define BHSTMR_FUNCTION_LOGICAL_UNIT_RESET 5 +#define BHSTMR_FUNCTION_TARGET_WARM_RESET 6 +#define BHSTMR_FUNCTION_TARGET_COLD_RESET 7 +#define BHSTMR_FUNCTION_TASK_REASSIGN 8 + +struct iscsi_bhs_task_management_request { + uint8_t bhstmr_opcode; + uint8_t bhstmr_function; + uint8_t bhstmr_reserved[2]; + uint8_t bhstmr_total_ahs_len; + uint8_t bhstmr_data_segment_len[3]; + uint64_t bhstmr_lun; + uint32_t bhstmr_initiator_task_tag; + uint32_t bhstmr_referenced_task_tag; + uint32_t bhstmr_cmdsn; + uint32_t bhstmr_expstatsn; + uint32_t bhstmr_refcmdsn; + uint32_t bhstmr_expdatasn; + uint64_t bhstmr_reserved2; +}; +CTASSERT(sizeof(struct iscsi_bhs_task_management_request) == ISCSI_BHS_SIZE); + +#define BHSTMR_RESPONSE_FUNCTION_COMPLETE 0 +#define BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED 5 + +struct iscsi_bhs_task_management_response { + uint8_t bhstmr_opcode; + uint8_t bhstmr_flags; + uint8_t bhstmr_response; + uint8_t bhstmr_reserved; + uint8_t bhstmr_total_ahs_len; + uint8_t bhstmr_data_segment_len[3]; + uint64_t bhstmr_reserved2; + uint32_t bhstmr_initiator_task_tag; + uint32_t bhstmr_reserved3; + uint32_t bhstmr_statsn; + uint32_t bhstmr_expcmdsn; + uint32_t bhstmr_maxcmdsn; + uint8_t bhstmr_reserved4[12]; +}; +CTASSERT(sizeof(struct iscsi_bhs_task_management_response) == ISCSI_BHS_SIZE); + +#define BHSLR_FLAGS_TRANSIT 0x80 +#define BHSLR_FLAGS_CONTINUE 0x40 + +#define BHSLR_STAGE_SECURITY_NEGOTIATION 0 +#define BHSLR_STAGE_OPERATIONAL_NEGOTIATION 1 +#define BHSLR_STAGE_FULL_FEATURE_PHASE 3 /* Yes, 3. */ + +struct iscsi_bhs_login_request { + uint8_t bhslr_opcode; + uint8_t bhslr_flags; + uint8_t bhslr_version_max; + uint8_t bhslr_version_min; + uint8_t bhslr_total_ahs_len; + uint8_t bhslr_data_segment_len[3]; + uint8_t bhslr_isid[6]; + uint16_t bhslr_tsih; + uint32_t bhslr_initiator_task_tag; + uint16_t bhslr_cid; + uint16_t bhslr_reserved; + uint32_t bhslr_cmdsn; + uint32_t bhslr_expstatsn; + uint8_t bhslr_reserved2[16]; +}; +CTASSERT(sizeof(struct iscsi_bhs_login_request) == ISCSI_BHS_SIZE); + +struct iscsi_bhs_login_response { + uint8_t bhslr_opcode; + uint8_t bhslr_flags; + uint8_t bhslr_version_max; + uint8_t bhslr_version_active; + uint8_t bhslr_total_ahs_len; + uint8_t bhslr_data_segment_len[3]; + uint8_t bhslr_isid[6]; + uint16_t bhslr_tsih; + uint32_t bhslr_initiator_task_tag; + uint32_t bhslr_reserved; + uint32_t bhslr_statsn; + uint32_t bhslr_expcmdsn; + uint32_t bhslr_maxcmdsn; + uint8_t bhslr_status_class; + uint8_t bhslr_status_detail; + uint16_t bhslr_reserved2; + uint8_t bhslr_reserved3[8]; +}; +CTASSERT(sizeof(struct iscsi_bhs_login_response) == ISCSI_BHS_SIZE); + +#define BHSTR_FLAGS_FINAL 0x80 +#define BHSTR_FLAGS_CONTINUE 0x40 + +struct iscsi_bhs_text_request { + uint8_t bhstr_opcode; + uint8_t bhstr_flags; + uint16_t bhstr_reserved; + uint8_t bhstr_total_ahs_len; + uint8_t bhstr_data_segment_len[3]; + uint64_t bhstr_lun; + uint32_t bhstr_initiator_task_tag; + uint32_t bhstr_target_transfer_tag; + uint32_t bhstr_cmdsn; + uint32_t bhstr_expstatsn; + uint8_t bhstr_reserved2[16]; +}; +CTASSERT(sizeof(struct iscsi_bhs_text_request) == ISCSI_BHS_SIZE); + +struct iscsi_bhs_text_response { + uint8_t bhstr_opcode; + uint8_t bhstr_flags; + uint16_t bhstr_reserved; + uint8_t bhstr_total_ahs_len; + uint8_t bhstr_data_segment_len[3]; + uint64_t bhstr_lun; + uint32_t bhstr_initiator_task_tag; + uint32_t bhstr_target_transfer_tag; + uint32_t bhstr_statsn; + uint32_t bhstr_expcmdsn; + uint32_t bhstr_maxcmdsn; + uint8_t bhstr_reserved2[12]; +}; +CTASSERT(sizeof(struct iscsi_bhs_text_response) == ISCSI_BHS_SIZE); + + +#define BHSDO_FLAGS_F 0x80 + +struct iscsi_bhs_data_out { + uint8_t bhsdo_opcode; + uint8_t bhsdo_flags; + uint8_t bhsdo_reserved[2]; + uint8_t bhsdo_total_ahs_len; + uint8_t bhsdo_data_segment_len[3]; + uint64_t bhsdo_lun; + uint32_t bhsdo_initiator_task_tag; + uint32_t bhsdo_target_transfer_tag; + uint32_t bhsdo_reserved2; + uint32_t bhsdo_expstatsn; + uint32_t bhsdo_reserved3; + uint32_t bhsdo_datasn; + uint32_t bhsdo_buffer_offset; + uint32_t bhsdo_reserved4; +}; +CTASSERT(sizeof(struct iscsi_bhs_data_out) == ISCSI_BHS_SIZE); + +#define BHSDI_FLAGS_F 0x80 +#define BHSDI_FLAGS_A 0x40 +#define BHSDI_FLAGS_O 0x04 +#define BHSDI_FLAGS_U 0x02 +#define BHSDI_FLAGS_S 0x01 + +struct iscsi_bhs_data_in { + uint8_t bhsdi_opcode; + uint8_t bhsdi_flags; + uint8_t bhsdi_reserved; + uint8_t bhsdi_status; + uint8_t bhsdi_total_ahs_len; + uint8_t bhsdi_data_segment_len[3]; + uint64_t bhsdi_lun; + uint32_t bhsdi_initiator_task_tag; + uint32_t bhsdi_target_transfer_tag; + uint32_t bhsdi_statsn; + uint32_t bhsdi_expcmdsn; + uint32_t bhsdi_maxcmdsn; + uint32_t bhsdi_datasn; + uint32_t bhsdi_buffer_offset; + uint32_t bhsdi_residual_count; +}; +CTASSERT(sizeof(struct iscsi_bhs_data_in) == ISCSI_BHS_SIZE); + +struct iscsi_bhs_r2t { + uint8_t bhsr2t_opcode; + uint8_t bhsr2t_flags; + uint16_t bhsr2t_reserved; + uint8_t bhsr2t_total_ahs_len; + uint8_t bhsr2t_data_segment_len[3]; + uint64_t bhsr2t_lun; + uint32_t bhsr2t_initiator_task_tag; + uint32_t bhsr2t_target_transfer_tag; + uint32_t bhsr2t_statsn; + uint32_t bhsr2t_expcmdsn; + uint32_t bhsr2t_maxcmdsn; + uint32_t bhsr2t_r2tsn; + uint32_t bhsr2t_buffer_offset; + uint32_t bhsr2t_desired_data_transfer_length; +}; +CTASSERT(sizeof(struct iscsi_bhs_r2t) == ISCSI_BHS_SIZE); + +struct iscsi_bhs_nop_out { + uint8_t bhsno_opcode; + uint8_t bhsno_flags; + uint16_t bhsno_reserved; + uint8_t bhsno_total_ahs_len; + uint8_t bhsno_data_segment_len[3]; + uint64_t bhsno_lun; + uint32_t bhsno_initiator_task_tag; + uint32_t bhsno_target_transfer_tag; + uint32_t bhsno_cmdsn; + uint32_t bhsno_expstatsn; + uint8_t bhsno_reserved2[16]; +}; +CTASSERT(sizeof(struct iscsi_bhs_nop_out) == ISCSI_BHS_SIZE); + +struct iscsi_bhs_nop_in { + uint8_t bhsni_opcode; + uint8_t bhsni_flags; + uint16_t bhsni_reserved; + uint8_t bhsni_total_ahs_len; + uint8_t bhsni_data_segment_len[3]; + uint64_t bhsni_lun; + uint32_t bhsni_initiator_task_tag; + uint32_t bhsni_target_transfer_tag; + uint32_t bhsni_statsn; + uint32_t bhsni_expcmdsn; + uint32_t bhsni_maxcmdsn; + uint8_t bhsno_reserved2[12]; +}; +CTASSERT(sizeof(struct iscsi_bhs_nop_in) == ISCSI_BHS_SIZE); + +#define BHSLR_REASON_CLOSE_SESSION 0 +#define BHSLR_REASON_CLOSE_CONNECTION 1 +#define BHSLR_REASON_REMOVE_FOR_RECOVERY 2 + +struct iscsi_bhs_logout_request { + uint8_t bhslr_opcode; + uint8_t bhslr_reason; + uint16_t bhslr_reserved; + uint8_t bhslr_total_ahs_len; + uint8_t bhslr_data_segment_len[3]; + uint64_t bhslr_reserved2; + uint32_t bhslr_initiator_task_tag; + uint16_t bhslr_cid; + uint16_t bhslr_reserved3; + uint32_t bhslr_cmdsn; + uint32_t bhslr_expstatsn; + uint8_t bhslr_reserved4[16]; +}; +CTASSERT(sizeof(struct iscsi_bhs_logout_request) == ISCSI_BHS_SIZE); + +#define BHSLR_RESPONSE_CLOSED_SUCCESSFULLY 0 +#define BHSLR_RESPONSE_RECOVERY_NOT_SUPPORTED 2 + +struct iscsi_bhs_logout_response { + uint8_t bhslr_opcode; + uint8_t bhslr_flags; + uint8_t bhslr_response; + uint8_t bhslr_reserved; + uint8_t bhslr_total_ahs_len; + uint8_t bhslr_data_segment_len[3]; + uint64_t bhslr_reserved2; + uint32_t bhslr_initiator_task_tag; + uint32_t bhslr_reserved3; + uint32_t bhslr_statsn; + uint32_t bhslr_expcmdsn; + uint32_t bhslr_maxcmdsn; + uint32_t bhslr_reserved4; + uint16_t bhslr_time2wait; + uint16_t bhslr_time2retain; + uint32_t bhslr_reserved5; +}; +CTASSERT(sizeof(struct iscsi_bhs_logout_response) == ISCSI_BHS_SIZE); + +#define BHSAM_EVENT_TARGET_REQUESTS_LOGOUT 1 +#define BHSAM_EVENT_TARGET_TERMINATES_SESSION 3 + +struct iscsi_bhs_asynchronous_message { + uint8_t bhsam_opcode; + uint8_t bhsam_flags; + uint16_t bhsam_reserved; + uint8_t bhsam_total_ahs_len; + uint8_t bhsam_data_segment_len[3]; + uint64_t bhsam_lun; + uint32_t bhsam_0xffffffff; + uint32_t bhsam_reserved2; + uint32_t bhsam_statsn; + uint32_t bhsam_expcmdsn; + uint32_t bhsam_maxcmdsn; + uint8_t bhsam_async_event; + uint8_t bhsam_async_vcode; + uint16_t bhsam_parameter1; + uint16_t bhsam_parameter2; + uint16_t bhsam_parameter3; + uint32_t bhsam_reserved3; +}; +CTASSERT(sizeof(struct iscsi_bhs_asynchronous_message) == ISCSI_BHS_SIZE); + +#define BHSSR_REASON_DATA_DIGEST_ERROR 0x02 +#define BHSSR_PROTOCOL_ERROR 0x04 +#define BHSSR_COMMAND_NOT_SUPPORTED 0x05 +#define BHSSR_INVALID_PDU_FIELD 0x09 + +struct iscsi_bhs_reject { + uint8_t bhsr_opcode; + uint8_t bhsr_flags; + uint8_t bhsr_reason; + uint8_t bhsr_reserved; + uint8_t bhsr_total_ahs_len; + uint8_t bhsr_data_segment_len[3]; + uint64_t bhsr_reserved2; + uint32_t bhsr_0xffffffff; + uint32_t bhsr_reserved3; + uint32_t bhsr_statsn; + uint32_t bhsr_expcmdsn; + uint32_t bhsr_maxcmdsn; + uint32_t bhsr_datasn_r2tsn; + uint32_t bhsr_reserved4; + uint32_t bhsr_reserved5; +}; +CTASSERT(sizeof(struct iscsi_bhs_reject) == ISCSI_BHS_SIZE); + +#endif /* !ISCSI_H */ diff -urNp freebsd/src/sys/cam/scsi/scsi_all.h iscsi/sys/cam/scsi/scsi_all.h --- freebsd/src/sys/cam/scsi/scsi_all.h 2012-11-09 12:32:58.000000000 +0100 +++ iscsi/sys/cam/scsi/scsi_all.h 2013-02-19 17:25:20.000000000 +0100 @@ -1263,13 +1263,14 @@ struct scsi_vpd_id_descriptor #define SCSI_PROTO_SSA 0x02 #define SCSI_PROTO_1394 0x03 #define SCSI_PROTO_RDMA 0x04 -#define SCSI_PROTO_iSCSI 0x05 +#define SCSI_PROTO_ISCSI 0x05 #define SCSI_PROTO_SAS 0x06 #define SCSI_PROTO_ADT 0x07 #define SCSI_PROTO_ATA 0x08 #define SVPD_ID_PROTO_SHIFT 4 #define SVPD_ID_CODESET_BINARY 0x01 #define SVPD_ID_CODESET_ASCII 0x02 +#define SVPD_ID_CODESET_UTF8 0x03 #define SVPD_ID_CODESET_MASK 0x0f u_int8_t id_type; #define SVPD_ID_PIV 0x80 diff -urNp freebsd/src/sys/conf/files iscsi/sys/conf/files --- freebsd/src/sys/conf/files 2013-02-14 14:27:51.000000000 +0100 +++ iscsi/sys/conf/files 2013-02-14 17:58:08.000000000 +0100 @@ -123,6 +123,7 @@ cam/ctl/ctl_cmd_table.c optional ctl cam/ctl/ctl_frontend.c optional ctl cam/ctl/ctl_frontend_cam_sim.c optional ctl cam/ctl/ctl_frontend_internal.c optional ctl +cam/ctl/ctl_frontend_iscsi.c optional ctl cam/ctl/ctl_mem_pool.c optional ctl cam/ctl/ctl_scsi_all.c optional ctl cam/ctl/ctl_error.c optional ctl diff -urNp freebsd/src/usr.sbin/Makefile iscsi/usr.sbin/Makefile --- freebsd/src/usr.sbin/Makefile 2013-01-29 06:08:57.000000000 +0100 +++ iscsi/usr.sbin/Makefile 2013-02-19 19:36:28.000000000 +0100 @@ -17,6 +17,7 @@ SUBDIR= adduser \ crashinfo \ cron \ ctladm \ + ctld \ daemon \ dconschat \ devinfo \ diff -urNp freebsd/src/usr.sbin/ctladm/ctladm.8 iscsi/usr.sbin/ctladm/ctladm.8 --- freebsd/src/usr.sbin/ctladm/ctladm.8 2012-11-09 13:02:49.000000000 +0100 +++ iscsi/usr.sbin/ctladm/ctladm.8 2013-02-02 13:42:14.000000000 +0100 @@ -34,7 +34,7 @@ .\" $Id: //depot/users/kenm/FreeBSD-test2/usr.sbin/ctladm/ctladm.8#3 $ .\" $FreeBSD: head/usr.sbin/ctladm/ctladm.8 236509 2012-06-03 11:29:48Z joel $ .\" -.Dd March 6, 2012 +.Dd December 16, 2012 .Dt CTLADM 8 .Os .Sh NAME @@ -197,6 +197,15 @@ .Nm .Ic dumpstructs .Nm +.Ic islist +.Op Fl x +.Nm +.Ic islogout +.Aq Fl A | Fl a Ar addr | Fl c Ar connection-id | Fl n Ar name +.Nm +.Ic isterminate +.Aq Fl A | Fl a Ar addr | Fl c Ar connection-id | Fl n Ar name +.Nm .Ic help .Sh DESCRIPTION The @@ -883,6 +892,39 @@ If you specify .Fl x , the entire LUN database is displayed in XML format. .El +.It Ic islist +Get a list of currently running iSCSI connections. +This includes initiator and target names and the unique connection IDs. +.Bl -tag -width 11n +.It Fl x +Dump the raw XML. +The connections list information from the kernel comes in XML format, and this +option allows the display of the raw XML data. +.El +.It Ic islogout +Ask the initiator to log out iSCSI connections matching criteria. +.Bl -tag -width 11n +.It Fl A +Log out all connections. +.It Fl a +Specify initiator IP address. +.It Fl c +Specify connection ID. +.It Fl n +Specify initiator name. +.El +.It Ic isterminate +Forcibly terminate iSCSI connections matching criteria. +.Bl -tag -width 11n +.It Fl A +Terminate all connections. +.It Fl a +Specify initiator IP address. +.It Fl c +Specify connection ID. +.It Fl n +Specify initiator name. +.El .It Ic help Display .Nm @@ -976,7 +1018,8 @@ This will result in a sense key of NOT R .Xr cam_cdbparse 3 , .Xr cam 4 , .Xr xpt 4 , -.Xr camcontrol 8 +.Xr camcontrol 8 , +.Xr ctld 8 .Sh HISTORY The .Nm diff -urNp freebsd/src/usr.sbin/ctladm/ctladm.c iscsi/usr.sbin/ctladm/ctladm.c --- freebsd/src/usr.sbin/ctladm/ctladm.c 2012-11-09 13:02:49.000000000 +0100 +++ iscsi/usr.sbin/ctladm/ctladm.c 2013-02-13 15:12:55.000000000 +0100 @@ -116,7 +116,10 @@ typedef enum { CTLADM_CMD_PRES_OUT, CTLADM_CMD_INQ_VPD_DEVID, CTLADM_CMD_RTPG, - CTLADM_CMD_MODIFY + CTLADM_CMD_MODIFY, + CTLADM_CMD_ISLIST, + CTLADM_CMD_ISLOGOUT, + CTLADM_CMD_ISTERMINATE } ctladm_cmdfunction; typedef enum { @@ -179,6 +182,9 @@ static struct ctladm_opts option_table[] {"help", CTLADM_CMD_HELP, CTLADM_ARG_NONE, NULL}, {"inject", CTLADM_CMD_ERR_INJECT, CTLADM_ARG_NEED_TL, "cd:i:p:r:s:"}, {"inquiry", CTLADM_CMD_INQUIRY, CTLADM_ARG_NEED_TL, NULL}, + {"islist", CTLADM_CMD_ISLIST, CTLADM_ARG_NONE, "x"}, + {"islogout", CTLADM_CMD_ISLOGOUT, CTLADM_ARG_NONE, "Aa:c:n:"}, + {"isterminate", CTLADM_CMD_ISTERMINATE, CTLADM_ARG_NONE, "Aa:c:n:"}, {"lunlist", CTLADM_CMD_LUNLIST, CTLADM_ARG_NONE, NULL}, {"modesense", CTLADM_CMD_MODESENSE, CTLADM_ARG_NEED_TL, "P:S:dlm:c:"}, {"modify", CTLADM_CMD_MODIFY, CTLADM_ARG_NONE, "b:l:s:"}, @@ -488,6 +494,9 @@ retry: case CTL_PORT_ISC: type = "ISC"; break; + case CTL_PORT_ISCSI: + type = "ISCSI"; + break; default: type = "UNKNOWN"; break; @@ -576,6 +585,7 @@ static struct ctladm_opts cctl_fe_table[ {"fc", CTL_PORT_FC, CTLADM_ARG_NONE, NULL}, {"scsi", CTL_PORT_SCSI, CTLADM_ARG_NONE, NULL}, {"internal", CTL_PORT_INTERNAL, CTLADM_ARG_NONE, NULL}, + {"iscsi", CTL_PORT_ISCSI, CTLADM_ARG_NONE, NULL}, {"all", CTL_PORT_ALL, CTLADM_ARG_NONE, NULL}, {NULL, 0, 0, NULL} }; @@ -3397,6 +3407,363 @@ bailout: return (retval); } +struct cctl_islist_conn { + int connection_id; + char *initiator; + char *initiator_addr; + char *target; + STAILQ_ENTRY(cctl_islist_conn) links; +}; + +struct cctl_islist_data { + int num_conns; + STAILQ_HEAD(,cctl_islist_conn) conn_list; + struct cctl_islist_conn *cur_conn; + int level; + struct sbuf *cur_sb[32]; +}; + +static void +cctl_islist_start_element(void *user_data, const char *name, const char **attr) +{ + int i; + struct cctl_islist_data *islist; + struct cctl_islist_conn *cur_conn; + + islist = (struct cctl_islist_data *)user_data; + cur_conn = islist->cur_conn; + islist->level++; + if ((u_int)islist->level > (sizeof(islist->cur_sb) / + sizeof(islist->cur_sb[0]))) + errx(1, "%s: too many nesting levels, %zd max", __func__, + sizeof(islist->cur_sb) / sizeof(islist->cur_sb[0])); + + islist->cur_sb[islist->level] = sbuf_new_auto(); + if (islist->cur_sb[islist->level] == NULL) + err(1, "%s: Unable to allocate sbuf", __func__); + + if (strcmp(name, "connection") == 0) { + if (cur_conn != NULL) + errx(1, "%s: improper connection element nesting", + __func__); + + cur_conn = calloc(1, sizeof(*cur_conn)); + if (cur_conn == NULL) + err(1, "%s: cannot allocate %zd bytes", __func__, + sizeof(*cur_conn)); + + islist->num_conns++; + islist->cur_conn = cur_conn; + + STAILQ_INSERT_TAIL(&islist->conn_list, cur_conn, links); + + for (i = 0; attr[i] != NULL; i += 2) { + if (strcmp(attr[i], "id") == 0) { + cur_conn->connection_id = + strtoull(attr[i+1], NULL, 0); + } else { + errx(1, + "%s: invalid connection attribute %s = %s", + __func__, attr[i], attr[i+1]); + } + } + } +} + +static void +cctl_islist_end_element(void *user_data, const char *name) +{ + struct cctl_islist_data *islist; + struct cctl_islist_conn *cur_conn; + char *str; + + islist = (struct cctl_islist_data *)user_data; + cur_conn = islist->cur_conn; + + if ((cur_conn == NULL) + && (strcmp(name, "ctlislist") != 0)) + errx(1, "%s: cur_conn == NULL! (name = %s)", __func__, name); + + if (islist->cur_sb[islist->level] == NULL) + errx(1, "%s: no valid sbuf at level %d (name %s)", __func__, + islist->level, name); + + sbuf_finish(islist->cur_sb[islist->level]); + str = strdup(sbuf_data(islist->cur_sb[islist->level])); + if (str == NULL) + err(1, "%s can't allocate %zd bytes for string", __func__, + sbuf_len(islist->cur_sb[islist->level])); + + if (strlen(str) == 0) { + free(str); + str = NULL; + } + + sbuf_delete(islist->cur_sb[islist->level]); + islist->cur_sb[islist->level] = NULL; + islist->level--; + + if (strcmp(name, "initiator") == 0) { + cur_conn->initiator = str; + str = NULL; + } else if (strcmp(name, "initiator_addr") == 0) { + cur_conn->initiator_addr = str; + str = NULL; + } else if (strcmp(name, "target") == 0) { + cur_conn->target = str; + str = NULL; + } else if (strcmp(name, "connection") == 0) { + islist->cur_conn = NULL; + } else if (strcmp(name, "ctlislist") == 0) { + } else + errx(1, "unknown element %s", name); + + free(str); +} + +static void +cctl_islist_char_handler(void *user_data, const XML_Char *str, int len) +{ + struct cctl_islist_data *islist; + + islist = (struct cctl_islist_data *)user_data; + + sbuf_bcat(islist->cur_sb[islist->level], str, len); +} + +static int +cctl_islist(int fd, int argc, char **argv, char *combinedopt) +{ + struct ctl_iscsi req; + struct cctl_islist_data islist; + struct cctl_islist_conn *conn; + XML_Parser parser; + char *conn_str; + int conn_len; + int dump_xml = 0; + int retval, c; + + retval = 0; + conn_len = 4096; + + bzero(&islist, sizeof(islist)); + STAILQ_INIT(&islist.conn_list); + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'x': + dump_xml = 1; + break; + default: + break; + } + } + +retry: + conn_str = malloc(conn_len); + + bzero(&req, sizeof(req)); + req.type = CTL_ISCSI_LIST; + req.data.list.alloc_len = conn_len; + req.data.list.conn_xml = conn_str; + + if (ioctl(fd, CTL_ISCSI, &req) == -1) { + warn("%s: error issuing CTL_ISCSI ioctl", __func__); + retval = 1; + goto bailout; + } + + if (req.status == CTL_ISCSI_ERROR) { + warnx("%s: error returned from CTL_ISCSI ioctl:\n%s", + __func__, req.error_str); + } else if (req.status == CTL_ISCSI_LIST_NEED_MORE_SPACE) { + conn_len = conn_len << 1; + goto retry; + } + + if (dump_xml != 0) { + printf("%s", conn_str); + goto bailout; + } + + parser = XML_ParserCreate(NULL); + if (parser == NULL) { + warn("%s: Unable to create XML parser", __func__); + retval = 1; + goto bailout; + } + + XML_SetUserData(parser, &islist); + XML_SetElementHandler(parser, cctl_islist_start_element, + cctl_islist_end_element); + XML_SetCharacterDataHandler(parser, cctl_islist_char_handler); + + retval = XML_Parse(parser, conn_str, strlen(conn_str), 1); + XML_ParserFree(parser); + if (retval != 1) { + retval = 1; + goto bailout; + } + + printf("%4s %-16s %-36s %-36s\n", "ID", "Address", "Initiator", + "Target"); + STAILQ_FOREACH(conn, &islist.conn_list, links) { + printf("%4u %-16s %-36s %-36s\n", + conn->connection_id, conn->initiator_addr, conn->initiator, + conn->target); + } +bailout: + free(conn_str); + + return (retval); +} + +static int +cctl_islogout(int fd, int argc, char **argv, char *combinedopt) +{ + struct ctl_iscsi req; + int retval = 0, c; + int all = 0, connection_id = -1, nargs = 0; + char *initiator_name = NULL, *initiator_addr = NULL; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'A': + all = 1; + nargs++; + break; + case 'a': + initiator_addr = strdup(optarg); + if (initiator_addr == NULL) + err(1, "%s: strdup", __func__); + nargs++; + break; + case 'c': + connection_id = strtoul(optarg, NULL, 0); + nargs++; + break; + case 'n': + initiator_name = strdup(optarg); + if (initiator_name == NULL) + err(1, "%s: strdup", __func__); + nargs++; + break; + default: + break; + } + } + + if (nargs == 0) + errx(1, "%s: either -A, -a, -c, or -n must be specified", + __func__); + if (nargs > 1) + errx(1, "%s: only one of -A, -a, -c, or -n may be specified", + __func__); + + bzero(&req, sizeof(req)); + req.type = CTL_ISCSI_LOGOUT; + req.data.logout.connection_id = connection_id; + if (initiator_addr != NULL) + strlcpy(req.data.logout.initiator_addr, + initiator_addr, sizeof(req.data.logout.initiator_addr)); + if (initiator_name != NULL) + strlcpy(req.data.logout.initiator_name, + initiator_name, sizeof(req.data.logout.initiator_name)); + if (all != 0) + req.data.logout.all = 1; + + if (ioctl(fd, CTL_ISCSI, &req) == -1) { + warn("%s: error issuing CTL_ISCSI ioctl", __func__); + retval = 1; + goto bailout; + } + + if (req.status != CTL_ISCSI_OK) { + warnx("%s: error returned from CTL iSCSI logout request:\n%s", + __func__, req.error_str); + retval = 1; + goto bailout; + } + + printf("iSCSI logout requests submitted\n"); + +bailout: + return (retval); +} + +static int +cctl_isterminate(int fd, int argc, char **argv, char *combinedopt) +{ + struct ctl_iscsi req; + int retval = 0, c; + int all = 0, connection_id = -1, nargs = 0; + char *initiator_name = NULL, *initiator_addr = NULL; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'A': + all = 1; + nargs++; + break; + case 'a': + initiator_addr = strdup(optarg); + if (initiator_addr == NULL) + err(1, "%s: strdup", __func__); + nargs++; + break; + case 'c': + connection_id = strtoul(optarg, NULL, 0); + nargs++; + break; + case 'n': + initiator_name = strdup(optarg); + if (initiator_name == NULL) + err(1, "%s: strdup", __func__); + nargs++; + break; + default: + break; + } + } + + if (nargs == 0) + errx(1, "%s: either -A, -a, -c, or -n must be specified", + __func__); + if (nargs > 1) + errx(1, "%s: only one of -A, -a, -c, or -n may be specified", + __func__); + + + bzero(&req, sizeof(req)); + req.type = CTL_ISCSI_TERMINATE; + req.data.terminate.connection_id = connection_id; + if (initiator_addr != NULL) + strlcpy(req.data.terminate.initiator_addr, + initiator_addr, sizeof(req.data.terminate.initiator_addr)); + if (initiator_name != NULL) + strlcpy(req.data.terminate.initiator_name, + initiator_name, sizeof(req.data.terminate.initiator_name)); + if (all != 0) + req.data.terminate.all = 1; + + if (ioctl(fd, CTL_ISCSI, &req) == -1) { + warn("%s: error issuing CTL_ISCSI ioctl", __func__); + retval = 1; + goto bailout; + } + + if (req.status != CTL_ISCSI_OK) { + warnx("%s: error returned from CTL iSCSI connection " + "termination request:\n%s", __func__, req.error_str); + retval = 1; + goto bailout; + } + + printf("iSCSI connections terminated\n"); + +bailout: + return (retval); +} /* * Name/value pair used for per-LUN attributes. @@ -3710,6 +4077,9 @@ usage(int error) " [-s len fmt [args]] [-c] [-d delete_id]\n" " ctladm port <-l | -o | [-w wwnn][-W wwpn]>\n" " [-p targ_port] [-t port_type] [-q] [-x]\n" +" ctladm islist [-x]\n" +" ctladm islogout <-A | -a addr | -c connection-id | -n name>\n" +" ctladm isterminate <-A | -a addr | -c connection-id | -n name>\n" " ctladm dumpooa\n" " ctladm dumpstructs\n" " ctladm help\n" @@ -4081,6 +4451,15 @@ main(int argc, char **argv) case CTLADM_CMD_MODIFY: retval = cctl_modify_lun(fd, argc, argv, combinedopt); break; + case CTLADM_CMD_ISLIST: + retval = cctl_islist(fd, argc, argv, combinedopt); + break; + case CTLADM_CMD_ISLOGOUT: + retval = cctl_islogout(fd, argc, argv, combinedopt); + break; + case CTLADM_CMD_ISTERMINATE: + retval = cctl_isterminate(fd, argc, argv, combinedopt); + break; case CTLADM_CMD_HELP: default: usage(retval); diff -urNp freebsd/src/usr.sbin/ctld/Makefile iscsi/usr.sbin/ctld/Makefile --- freebsd/src/usr.sbin/ctld/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/Makefile 2013-01-21 22:31:57.000000000 +0100 @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PROG= ctld +SRCS= ctld.c discovery.c kernel.c keys.c log.c login.c parse.y pdu.c token.l y.tab.h +CFLAGS+= -I${.CURDIR} +CFLAGS+= -I${.CURDIR}/../../sys +CFLAGS+= -I${.CURDIR}/../../sys/cam/ctl +MAN= ctld.8 ctl.conf.5 + +DPADD= ${LIBCAM} ${LIBSBUF} ${LIBBSDXML} ${LIBUTIL} +LDADD= -lcam -lsbuf -lbsdxml -lutil -lfl -lssl + +YFLAGS+= -v +CLEANFILES= y.tab.c y.tab.h y.output + +WARNS= 6 + +.include diff -urNp freebsd/src/usr.sbin/ctld/ctl.conf.5 iscsi/usr.sbin/ctld/ctl.conf.5 --- freebsd/src/usr.sbin/ctld/ctl.conf.5 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/ctl.conf.5 2013-02-13 20:36:17.000000000 +0100 @@ -0,0 +1,247 @@ +.\" Copyright (c) 2012 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" 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 AUTHORS 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 AUTHORS 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 20, 2012 +.Dt CTL.CONF 5 +.Os +.Sh NAME +.Nm ctl.conf +.Nd CAM Target Layer / iSCSI target daemon configuration file +.Sh DESCRIPTION +The +.Nm +file is used by +.Xr ctld 8 +daemon. +Lines starting with +.Ql # +and empty lines are interpreted as comments. +.Sh BASIC SYNTAX +General syntax of the +.Nm +file is the following: +.Bd -literal -offset indent +pidfile + +auth-group { + chap username secret + ... +} + +portal-group { + listen
+ discovery-auth-group + ... +} + +target { + auth-group + portal-group + lun { + path + } + ... +} +.Ed +.Pp +.Sh DESCRIPTION +The following statements are available: +.Pp +At the global level: +.Bl -tag -width ".Ic 1234" +.It Ic auth-group Aq name +.Pp +Opens an auth-group section, defining an authentication group, +which can then be assigned to any number of targets. +.It Ic debug Aq level +.Pp +Specifies debug level. +The default is 0. +.It Ic pidfile Aq path +.Pp +Specifies path to pidfile. +The default is /var/run/ctld.pid. +.It Ic portal-group Aq name +.Pp +Opens a portal-group section, defining a portal group, +which can then be assigned to any number of targets. +.It Ic target Aq name +.Pp +Opens a target configuration section. +.El +.Pp +At the auth-group level: +.Bl -tag -width ".Ic 1234" +.It Ic chap Aq user secret +.Pp +Specifies CHAP authentication credentials. +.It Ic chap-mutual Aq user secret mutualuser mutualsecret +.Pp +Specifies mutual CHAP authentication credentials. +Note that for any auth-group, configuration may contain either chap, +or chap-mutual entries; it's an error to mix them. +.El +.Pp +At the portal-group level: +.Bl -tag -width ".Ic 1234" +.It Ic discovery-auth-group Aq name +.Pp +Assigns previously defined authentication group to that portal group, +to be used for target discovery. +By default, the discovery will be denied. +A special auth-group, "no-authentication", may be used to allow for discovery +without authentication. +.It Ic listen Aq address +.Pp +Specifies IPv4 or IPv6 address and port to listen on for incoming connections. +.El +.Pp +At the target level: +.Bl -tag -width ".Ic 1234" +.It Ic alias Aq text +.Pp +Assigns human-readable description to that target. There is no default. +.It Ic auth-group Aq name +.Pp +Assigns previously defined authentication group to that target. +There is no default; every target must use either auth-group, +or chap, or chap-mutual statements. +A special auth-group, "no-authentication", may be used to permit access +without authentication. +.It Ic chap Aq user secret +.Pp +Specifies CHAP authentication credentials. +Note that targets must use either auth-group, or chap, +or chap-mutual clauses; it's a configuration error to mix them in one target. +.It Ic chap-mutual Aq user secret mutualuser mutualsecret +.Pp +Specifies mutual CHAP authentication credentials. +Note that targets must use either auth-group, chap, +chap-mutual clauses; it's a configuration error to mix them in one target. +.It Ic portal-group Aq name +.Pp +Assigns previously defined portal group to that target. +Default portal group is "default", which makes the target available +on TCP port 3260 on all configured IPv4 and IPv6 addresses. +.It Ic lun Aq number +.Pp +Opens a lun configuration section, defining LUN exported by a target. +.El +.Pp +At the lun level: +.Bl -tag -width ".Ic 1234" +.It Ic backend Aq block|ramdisk +.Pp +Specifies the CTL backend to use for a given LUN. +Valid choices are "block" and "ramdisk"; block is used for LUNs backed +by files in the filesystem; ramdisk is a bitsink device, used mostly for +testing. +The default backend is block. +.It Ic blocksize Aq size +.Pp +Specifies blocksize visible to the initiator. +The default blocksize is 512. +.It Ic device-id Aq string +.Pp +Specifies SCSI Device Identification string presented to the initiator. +.It Ic option Aq name value +.Pp +Specifies CTL-specific options passed to the kernel. +.It Ic path Aq path +.Pp +Specifies path to file used to back the LUN. +.It Ic serial Aq string +.Pp +Specifies SCSI serial number presented to the initiator. +.It Ic size Aq size +.Pp +Specifies LUN size, in bytes. +.El +.Sh EXAMPLES +.Bd -literal -offset indent +pidfile /var/run/ctld.pid + +auth-group example2 { + chap-mutual user secret mutualuser mutualsecret + chap-mutual user2 secret2 mutualuser mutualsecret +} + +portal-group example2 { + discovery-auth-group no-authentication + listen 127.0.0.1 + listen 0.0.0.0:3261 + listen [::]:3261 + listen [fe80::be:ef] +} + +target iqn.2012-06.com.example:target0 { + alias "Testing target" + auth-group no-authentication + lun 0 { + path /dev/zvol/example_0 + blocksize 4096 + size 4G + } +} + +target iqn.2012-06.com.example:target3 { + chap chapuser chapsecret + lun 0 { + path /dev/zvol/example_3 + } +} + +target iqn.2012-06.com.example:target2 { + auth-group example2 + portal-group example2 + lun 0 { + path /dev/zvol/example2_0 + } + lun 1 { + path /dev/zvol/example2_1 + option foo bar + } +} +.Ed +.Sh FILES +.Bl -tag -width ".Pa /etc/ctl.conf" -compact +.It Pa /etc/ctl.conf +The default location of the +.Xr ctld 8 +configuration file. +.El +.Sh SEE ALSO +.Xr ctladm 8 , +.Xr ctld 8 +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff -urNp freebsd/src/usr.sbin/ctld/ctld.8 iscsi/usr.sbin/ctld/ctld.8 --- freebsd/src/usr.sbin/ctld/ctld.8 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/ctld.8 2013-02-13 21:07:01.000000000 +0100 @@ -0,0 +1,113 @@ +.\" Copyright (c) 2012 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" 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 AUTHORS 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 AUTHORS 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 20, 2012 +.Dt CTLD 8 +.Os +.Sh NAME +.Nm ctld +.Nd CAM Target Layer / iSCSI target daemon +.Sh SYNOPSIS +.Nm +.Op Fl d +.Op Fl f Ar config-file +.Sh DESCRIPTION +The +.Nm +daemon is responsible for managing the CAM Target Layer configuration, +accepting incoming iSCSI connections, performing authentication and +passing connections to the kernel part of the native iSCSI target. +.Pp +.Pp +Upon startup, the +.Nm +daemon parses the configuration file and exits, if it encounters any errors. +Then it compares the configuration with the kernel list of LUNs managed +by previously running +.Nm +instances, removes LUNs no longer existing in the configuration file, +and creates new LUNs as neccessary. +After that it listens for the incoming iSCSI connections, performs +authentication, and, if successful, passes the connections to the kernel part +of CTL iSCSI target, which handles it from that point. +.Pp +When it receives a SIGHUP signal, the +.Nm +reloads its configuration and applies the changes to the kernel. +Changes are applied in a way that avoids unneccessary disruptions; +for example removing one LUN does not affect other LUNs. +.Pp +When exiting gracefully, the +.Nm +daemon removes LUNs it managed and forcibly disconnect all the clients. +Otherwise - e.g. when killed with SIGKILL - LUNs stay configured +and clients remain connected. +.Pp +To perform administrative actions that apply to already connected +sessions, such as forcing termination, use +.Xr ctladm 8 . +.Pp +The following options are available: +.Bl -tag -width ".Fl P Ar pidfile" +.It Fl f Ar config-file +Specifies the name of the configuration file. +The default is +.Pa /etc/ctl.conf . +.It Fl d +Debug mode. +The server sends verbose debug output to standard error, and does not +put itself in the background. +The server will also not fork and will exit after processing one connection. +This option is only intended for debugging the target. +.El +.Sh FILES +.Bl -tag -width ".Pa /var/run/ctld.pid" -compact +.It Pa /etc/ctl.conf +The configuration file for +.Nm . +The file format and configuration options are described in +.Xr ctl.conf 5 . +.It Pa /var/run/ctld.pid +The default location of the +.Nm +PID file. +.El +.Sh EXIT STATUS +The +.Nm +utility exits 0 on success, and >0 if an error occurs. +.Sh SEE ALSO +.Xr ctl.conf 5 , +.Xr ctladm 8 +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff -urNp freebsd/src/usr.sbin/ctld/ctld.c iscsi/usr.sbin/ctld/ctld.c --- freebsd/src/usr.sbin/ctld/ctld.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/ctld.c 2013-02-19 17:44:21.000000000 +0100 @@ -0,0 +1,1523 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ctld.h" + +volatile bool sighup_received = false; +volatile bool sigterm_received = false; + +static void +usage(void) +{ + + fprintf(stderr, "usage: ctld [-d][-f config-file]\n"); + exit(1); +} + +char * +checked_strdup(const char *s) +{ + char *c; + + c = strdup(s); + if (c == NULL) + log_err(1, "strdup"); + return (c); +} + +struct ctld_conf * +ctld_conf_new(void) +{ + struct ctld_conf *cc; + + cc = calloc(1, sizeof(*cc)); + if (cc == NULL) + log_err(1, "calloc"); + TAILQ_INIT(&cc->cc_targets); + TAILQ_INIT(&cc->cc_auth_groups); + TAILQ_INIT(&cc->cc_portal_groups); + + return (cc); +} + +void +ctld_conf_delete(struct ctld_conf *cc) +{ + struct ctld_target *ct, *tmp; + struct ctld_auth_group *cag, *cagtmp; + struct ctld_portal_group *cpg, *cpgtmp; + + assert(cc->cc_pidfh == NULL); + + TAILQ_FOREACH_SAFE(ct, &cc->cc_targets, ct_next, tmp) + ctld_target_delete(ct); + TAILQ_FOREACH_SAFE(cag, &cc->cc_auth_groups, cag_next, cagtmp) + ctld_auth_group_delete(cag); + TAILQ_FOREACH_SAFE(cpg, &cc->cc_portal_groups, cpg_next, cpgtmp) + ctld_portal_group_delete(cpg); + free(cc->cc_pidfile_path); + free(cc); +} + +static struct ctld_auth * +ctld_auth_new(struct ctld_auth_group *cag) +{ + struct ctld_auth *ca; + + ca = calloc(1, sizeof(*ca)); + if (ca == NULL) + log_err(1, "calloc"); + ca->ca_auth_group = cag; + TAILQ_INSERT_TAIL(&cag->cag_auths, ca, ca_next); + return (ca); +} + +static void +ctld_auth_delete(struct ctld_auth *ca) +{ + TAILQ_REMOVE(&ca->ca_auth_group->cag_auths, ca, ca_next); + + free(ca->ca_user); + free(ca->ca_secret); + free(ca->ca_mutual_user); + free(ca->ca_mutual_secret); + free(ca); +} + +const struct ctld_auth * +ctld_auth_find(struct ctld_auth_group *cag, const char *user) +{ + const struct ctld_auth *ca; + + TAILQ_FOREACH(ca, &cag->cag_auths, ca_next) { + if (strcmp(ca->ca_user, user) == 0) + return (ca); + } + + return (NULL); +} + +struct ctld_auth_group * +ctld_auth_group_new(struct ctld_conf *cc, const char *name) +{ + struct ctld_auth_group *cag; + + if (name != NULL) { + cag = ctld_auth_group_find(cc, name); + if (cag != NULL) { + log_warnx("duplicated auth-group \"%s\"", name); + return (NULL); + } + } + + cag = calloc(1, sizeof(*cag)); + if (cag == NULL) + log_err(1, "calloc"); + if (name != NULL) + cag->cag_name = checked_strdup(name); + TAILQ_INIT(&cag->cag_auths); + cag->cag_conf = cc; + TAILQ_INSERT_TAIL(&cc->cc_auth_groups, cag, cag_next); + + return (cag); +} + +void +ctld_auth_group_delete(struct ctld_auth_group *cag) +{ + struct ctld_auth *ca, *tmp; + + TAILQ_REMOVE(&cag->cag_conf->cc_auth_groups, cag, cag_next); + + TAILQ_FOREACH_SAFE(ca, &cag->cag_auths, ca_next, tmp) + ctld_auth_delete(ca); + free(cag->cag_name); + free(cag); +} + +struct ctld_auth_group * +ctld_auth_group_find(struct ctld_conf *cc, const char *name) +{ + struct ctld_auth_group *cag; + + TAILQ_FOREACH(cag, &cc->cc_auth_groups, cag_next) { + if (cag->cag_name != NULL && strcmp(cag->cag_name, name) == 0) + return (cag); + } + + return (NULL); +} + +static void +ctld_auth_check_secret_length(struct ctld_auth *ca) +{ + size_t len; + + len = strlen(ca->ca_secret); + if (len > 16) { + if (ca->ca_auth_group->cag_name != NULL) + log_warnx("secret for user \"%s\", auth-group \"%s\", " + "is too long; it should be at most 16 characters " + "long", ca->ca_user, ca->ca_auth_group->cag_name); + else + log_warnx("secret for user \"%s\", target \"%s\", " + "is too long; it should be at most 16 characters " + "long", ca->ca_user, + ca->ca_auth_group->cag_target->ct_iqn); + } + if (len < 12) { + if (ca->ca_auth_group->cag_name != NULL) + log_warnx("secret for user \"%s\", auth-group \"%s\", " + "is too short; it should be at least 12 characters " + "long", ca->ca_user, + ca->ca_auth_group->cag_name); + else + log_warnx("secret for user \"%s\", target \"%s\", " + "is too short; it should be at least 16 characters " + "long", ca->ca_user, + ca->ca_auth_group->cag_target->ct_iqn); + } + + if (ca->ca_mutual_secret != NULL) { + len = strlen(ca->ca_secret); + if (len > 16) { + if (ca->ca_auth_group->cag_name != NULL) + log_warnx("mutual secret for user \"%s\", " + "auth-group \"%s\", is too long; it should " + "be at most 16 characters long", + ca->ca_user, ca->ca_auth_group->cag_name); + else + log_warnx("mutual secret for user \"%s\", " + "target \"%s\", is too long; it should " + "be at most 16 characters long", + ca->ca_user, + ca->ca_auth_group->cag_target->ct_iqn); + } + if (len < 12) { + if (ca->ca_auth_group->cag_name != NULL) + log_warnx("mutual secret for user \"%s\", " + "auth-group \"%s\", is too short; it " + "should be at least 12 characters long", + ca->ca_user, ca->ca_auth_group->cag_name); + else + log_warnx("mutual secret for user \"%s\", " + "target \"%s\", is too short; it should be " + "at least 16 characters long", + ca->ca_user, + ca->ca_auth_group->cag_target->ct_iqn); + } + } +} + +const struct ctld_auth * +ctld_auth_new_chap(struct ctld_auth_group *cag, const char *user, + const char *secret) +{ + struct ctld_auth *ca; + + if (cag->cag_type == CAG_TYPE_UNKNOWN) + cag->cag_type = CAG_TYPE_CHAP; + if (cag->cag_type != CAG_TYPE_CHAP) { + if (cag->cag_name != NULL) + log_warnx("cannot mix \"chap\" authentication with " + "other types for auth-group \"%s\"", cag->cag_name); + else + log_warnx("cannot mix \"chap\" authentication with " + "other types for target \"%s\"", + cag->cag_target->ct_iqn); + return (NULL); + } + + ca = ctld_auth_new(cag); + ca->ca_user = checked_strdup(user); + ca->ca_secret = checked_strdup(secret); + + ctld_auth_check_secret_length(ca); + + return (ca); +} + +const struct ctld_auth * +ctld_auth_new_chap_mutual(struct ctld_auth_group *cag, const char *user, + const char *secret, const char *user2, const char *secret2) +{ + struct ctld_auth *ca; + + if (cag->cag_type == CAG_TYPE_UNKNOWN) + cag->cag_type = CAG_TYPE_CHAP_MUTUAL; + if (cag->cag_type != CAG_TYPE_CHAP_MUTUAL) { + if (cag->cag_name != NULL) + log_warnx("cannot mix \"chap-mutual\" authentication " + "with other types for auth-group \"%s\"", + cag->cag_name); + else + log_warnx("cannot mix \"chap-mutual\" authentication " + "with other types for target \"%s\"", + cag->cag_target->ct_iqn); + return (NULL); + } + + ca = ctld_auth_new(cag); + ca->ca_user = checked_strdup(user); + ca->ca_secret = checked_strdup(secret); + ca->ca_mutual_user = checked_strdup(user2); + ca->ca_mutual_secret = checked_strdup(secret2); + + ctld_auth_check_secret_length(ca); + + return (ca); +} + +static struct ctld_portal * +ctld_portal_new(struct ctld_portal_group *cpg) +{ + struct ctld_portal *cp; + + cp = calloc(1, sizeof(*cp)); + if (cp == NULL) + log_err(1, "calloc"); + TAILQ_INIT(&cp->cp_targets); + cp->cp_portal_group = cpg; + TAILQ_INSERT_TAIL(&cpg->cpg_portals, cp, cp_next); + return (cp); +} + +static void +ctld_portal_delete(struct ctld_portal *cp) +{ + TAILQ_REMOVE(&cp->cp_portal_group->cpg_portals, cp, cp_next); + freeaddrinfo(cp->cp_ai); + free(cp->cp_listen); + free(cp); +} + +struct ctld_portal_group * +ctld_portal_group_new(struct ctld_conf *cc, const char *name) +{ + struct ctld_portal_group *cpg; + + if (name != NULL) { + cpg = ctld_portal_group_find(cc, name); + if (cpg != NULL) { + log_warnx("duplicated portal-group \"%s\"", name); + return (NULL); + } + } + + cpg = calloc(1, sizeof(*cpg)); + if (cpg == NULL) + log_err(1, "calloc"); + cpg->cpg_name = checked_strdup(name); + TAILQ_INIT(&cpg->cpg_portals); + cpg->cpg_conf = cc; + cc->cc_last_portal_group_tag++; + cpg->cpg_tag = cc->cc_last_portal_group_tag; + TAILQ_INSERT_TAIL(&cc->cc_portal_groups, cpg, cpg_next); + + return (cpg); +} + +void +ctld_portal_group_delete(struct ctld_portal_group *cpg) +{ + struct ctld_portal *cp, *tmp; + + TAILQ_REMOVE(&cpg->cpg_conf->cc_portal_groups, cpg, cpg_next); + + TAILQ_FOREACH_SAFE(cp, &cpg->cpg_portals, cp_next, tmp) + ctld_portal_delete(cp); + free(cpg->cpg_name); + free(cpg); +} + +struct ctld_portal_group * +ctld_portal_group_find(struct ctld_conf *cc, const char *name) +{ + struct ctld_portal_group *cpg; + + TAILQ_FOREACH(cpg, &cc->cc_portal_groups, cpg_next) { + if (strcmp(cpg->cpg_name, name) == 0) + return (cpg); + } + + return (NULL); +} + +int +ctld_portal_group_add_listen(struct ctld_portal_group *cpg, const char *value) +{ + struct addrinfo hints; + struct ctld_portal *cp; + char *addr, *ch, *arg; + const char *port; + int error, colons = 0; + + cp = ctld_portal_new(cpg); + cp->cp_listen = checked_strdup(value); + + arg = cp->cp_listen; + if (arg[0] == '\0') { + log_warnx("empty listen address"); + free(cp->cp_listen); + free(cp); + return (1); + } + if (arg[0] == '[') { + /* + * IPv6 address in square brackets, perhaps with port. + */ + arg++; + addr = strsep(&arg, "]"); + if (arg == NULL) { + log_warnx("invalid listen address %s", cp->cp_listen); + free(cp->cp_listen); + free(cp); + return (1); + } + if (arg[0] == '\0') { + port = "3260"; + } else if (arg[0] == ':') { + port = arg + 1; + } else { + log_warnx("invalid listen address %s", cp->cp_listen); + free(cp->cp_listen); + free(cp); + return (1); + } + } else { + /* + * Either IPv6 address without brackets - and without + * a port - or IPv4 address. Just count the colons. + */ + for (ch = arg; *ch != '\0'; ch++) { + if (*ch == ':') + colons++; + } + if (colons > 1) { + addr = arg; + port = "3260"; + } else { + addr = strsep(&arg, ":"); + if (arg == NULL) + port = "3260"; + else + port = arg; + } + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + error = getaddrinfo(addr, port, &hints, &cp->cp_ai); + if (error != 0) { + log_warnx("getaddrinfo for %s failed: %s", + cp->cp_listen, gai_strerror(error)); + free(cp->cp_listen); + free(cp); + return (1); + } + + /* + * XXX: getaddrinfo(3) may return multiple addresses; we should turn + * those into multiple portals. + */ + + return (0); +} + +static bool +ctld_valid_hex(const char ch) +{ + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'A': + case 'b': + case 'B': + case 'c': + case 'C': + case 'd': + case 'D': + case 'e': + case 'E': + case 'f': + case 'F': + return (true); + default: + return (false); + } +} + +bool +ctld_valid_iscsi_name(const char *name) +{ + int i; + + if (strlen(name) >= CTLD_MAX_NAME_LEN) { + log_warnx("overlong name for target \"%s\"; max length allowed " + "by iSCSI specification is %d characters", + name, CTLD_MAX_NAME_LEN); + return (false); + } + + /* + * In the cases below, we don't return an error, just in case the admin + * was right, and we're wrong. + */ + if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) { + for (i = strlen("iqn."); name[i] != '\0'; i++) { + /* + * XXX: We should verify UTF-8 normalisation, as defined + * by 3.2.6.2: iSCSI Name Encoding. + */ + if (isalnum(name[i])) + continue; + if (name[i] == '-' || name[i] == '.' || name[i] == ':') + continue; + log_warnx("invalid character \"%c\" in target name " + "\"%s\"; allowed characters are letters, digits, " + "'-', '.', and ':'", name[i], name); + break; + } + /* + * XXX: Check more stuff: valid date and a valid reversed domain. + */ + } else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) { + if (strlen(name) != strlen("eui.") + 16) + log_warnx("invalid target name \"%s\"; the \"eui.\" " + "should be followed by exactly 16 hexadecimal " + "digits", name); + for (i = strlen("eui."); name[i] != '\0'; i++) { + if (!ctld_valid_hex(name[i])) { + log_warnx("invalid character \"%c\" in target " + "name \"%s\"; allowed characters are 1-9 " + "and A-F", name[i], name); + break; + } + } + } else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) { + if (strlen(name) > strlen("naa.") + 32) + log_warnx("invalid target name \"%s\"; the \"naa.\" " + "should be followed by at most 32 hexadecimal " + "digits", name); + for (i = strlen("naa."); name[i] != '\0'; i++) { + if (!ctld_valid_hex(name[i])) { + log_warnx("invalid character \"%c\" in target " + "name \"%s\"; allowed characters are 1-9 " + "and A-F", name[i], name); + break; + } + } + } else { + log_warnx("invalid target name \"%s\"; should start with " + "either \".iqn\", \"eui.\", or \"naa.\"", + name); + } + return (true); +} + +struct ctld_target * +ctld_target_new(struct ctld_conf *cc, const char *iqn) +{ + struct ctld_target *ct; + int i, len; + + ct = ctld_target_find(cc, iqn); + if (ct != NULL) { + log_warnx("duplicated target \"%s\"", iqn); + return (NULL); + } + if (ctld_valid_iscsi_name(iqn) == false) { + log_warnx("target name \"%s\" is invalid", iqn); + return (NULL); + } + ct = calloc(1, sizeof(*ct)); + if (ct == NULL) + log_err(1, "calloc"); + ct->ct_iqn = checked_strdup(iqn); + + /* + * RFC 3722 requires us to normalize the name to lowercase. + */ + len = strlen(iqn); + for (i = 0; i < len; i++) + ct->ct_iqn[i] = tolower(ct->ct_iqn[i]); + + TAILQ_INIT(&ct->ct_luns); + ct->ct_conf = cc; + TAILQ_INSERT_TAIL(&cc->cc_targets, ct, ct_next); + + return (ct); +} + +void +ctld_target_delete(struct ctld_target *ct) +{ + struct ctld_lun *cl, *tmp; + + TAILQ_REMOVE(&ct->ct_conf->cc_targets, ct, ct_next); + + TAILQ_FOREACH_SAFE(cl, &ct->ct_luns, cl_next, tmp) + ctld_lun_delete(cl); + free(ct->ct_iqn); + free(ct); +} + +struct ctld_target * +ctld_target_find(struct ctld_conf *cc, const char *iqn) +{ + struct ctld_target *ct; + + TAILQ_FOREACH(ct, &cc->cc_targets, ct_next) { + if (strcasecmp(ct->ct_iqn, iqn) == 0) + return (ct); + } + + return (NULL); +} + +struct ctld_lun * +ctld_lun_new(struct ctld_target *ct, int lun) +{ + struct ctld_lun *cl; + + cl = ctld_lun_find(ct, lun); + if (cl != NULL) { + log_warnx("duplicated lun %d for target \"%s\"", + lun, ct->ct_iqn); + return (NULL); + } + + cl = calloc(1, sizeof(*cl)); + if (cl == NULL) + log_err(1, "calloc"); + cl->cl_lun = lun; + TAILQ_INIT(&cl->cl_options); + cl->cl_target = ct; + TAILQ_INSERT_TAIL(&ct->ct_luns, cl, cl_next); + + return (cl); +} + +void +ctld_lun_delete(struct ctld_lun *cl) +{ + struct ctld_lun_option *clo, *tmp; + + TAILQ_REMOVE(&cl->cl_target->ct_luns, cl, cl_next); + + TAILQ_FOREACH_SAFE(clo, &cl->cl_options, clo_next, tmp) + ctld_lun_option_delete(clo); + free(cl->cl_backend); + free(cl->cl_device_id); + free(cl->cl_path); + free(cl->cl_serial); + free(cl); +} + +struct ctld_lun * +ctld_lun_find(struct ctld_target *ct, int lun) +{ + struct ctld_lun *cl; + + TAILQ_FOREACH(cl, &ct->ct_luns, cl_next) { + if (cl->cl_lun == lun) + return (cl); + } + + return (NULL); +} + +void +ctld_lun_set_backend(struct ctld_lun *cl, const char *value) +{ + free(cl->cl_backend); + cl->cl_backend = checked_strdup(value); +} + +void +ctld_lun_set_blocksize(struct ctld_lun *cl, size_t value) +{ + + cl->cl_blocksize = value; +} + +void +ctld_lun_set_device_id(struct ctld_lun *cl, const char *value) +{ + free(cl->cl_device_id); + cl->cl_device_id = checked_strdup(value); +} + +void +ctld_lun_set_path(struct ctld_lun *cl, const char *value) +{ + free(cl->cl_path); + cl->cl_path = checked_strdup(value); +} + +void +ctld_lun_set_serial(struct ctld_lun *cl, const char *value) +{ + free(cl->cl_serial); + cl->cl_serial = checked_strdup(value); +} + +void +ctld_lun_set_size(struct ctld_lun *cl, size_t value) +{ + + cl->cl_size = value; +} + +void +ctld_lun_set_ctl_lun(struct ctld_lun *cl, uint32_t value) +{ + + cl->cl_ctl_lun = value; +} + +struct ctld_lun_option * +ctld_lun_option_new(struct ctld_lun *cl, const char *name, const char *value) +{ + struct ctld_lun_option *clo; + + clo = ctld_lun_option_find(cl, name); + if (clo != NULL) { + log_warnx("duplicated lun option %s for lun %d, target \"%s\"", + name, cl->cl_lun, cl->cl_target->ct_iqn); + return (NULL); + } + + clo = calloc(1, sizeof(*clo)); + if (clo == NULL) + log_err(1, "calloc"); + clo->clo_name = checked_strdup(name); + clo->clo_value = checked_strdup(value); + clo->clo_lun = cl; + TAILQ_INSERT_TAIL(&cl->cl_options, clo, clo_next); + + return (clo); +} + +void +ctld_lun_option_delete(struct ctld_lun_option *clo) +{ + + TAILQ_REMOVE(&clo->clo_lun->cl_options, clo, clo_next); + + free(clo->clo_name); + free(clo->clo_value); + free(clo); +} + +struct ctld_lun_option * +ctld_lun_option_find(struct ctld_lun *cl, const char *name) +{ + struct ctld_lun_option *clo; + + TAILQ_FOREACH(clo, &cl->cl_options, clo_next) { + if (strcmp(clo->clo_name, name) == 0) + return (clo); + } + + return (NULL); +} + +void +ctld_lun_option_set(struct ctld_lun_option *clo, const char *value) +{ + + free(clo->clo_value); + clo->clo_value = checked_strdup(value); +} + +static struct ctld_connection * +ctld_connection_new(struct ctld_portal *cp, int fd, const char *host) +{ + struct ctld_connection *cc; + + cc = calloc(1, sizeof(*cc)); + if (cc == NULL) + log_err(1, "calloc"); + cc->cc_portal = cp; + cc->cc_socket = fd; + cc->cc_initiator_addr = checked_strdup(host); + cc->cc_max_data_segment_length = 8192; + + return (cc); +} + +#if 0 +static void +ctld_conf_print(struct ctld_conf *cc) +{ + struct ctld_auth_group *cag; + struct ctld_auth *ca; + struct ctld_portal_group *cpg; + struct ctld_portal *cp; + struct ctld_target *ct; + struct ctld_lun *cl; + struct ctld_lun_option *clo; + + TAILQ_FOREACH(cag, &cc->cc_auth_groups, cag_next) { + fprintf(stderr, "auth-group %s {\n", cag->cag_name); + TAILQ_FOREACH(ca, &cag->cag_auths, ca_next) + fprintf(stderr, "\t chap-mutual %s %s %s %s\n", ca->ca_user, ca->ca_secret, ca->ca_mutual_user, ca->ca_mutual_secret); + fprintf(stderr, "}\n"); + } + TAILQ_FOREACH(cpg, &cc->cc_portal_groups, cpg_next) { + fprintf(stderr, "portal-group %s {\n", cpg->cpg_name); + TAILQ_FOREACH(cp, &cpg->cpg_portals, cp_next) + fprintf(stderr, "\t listen %s\n", cp->cp_listen); + fprintf(stderr, "}\n"); + } + TAILQ_FOREACH(ct, &cc->cc_targets, ct_next) { + fprintf(stderr, "target %s {\n", ct->ct_iqn); + if (ct->ct_alias != NULL) + fprintf(stderr, "\t alias %s\n", ct->ct_alias); + TAILQ_FOREACH(cl, &ct->ct_luns, cl_next) { + fprintf(stderr, "\tlun %d {\n", cl->cl_lun); + fprintf(stderr, "\t\tpath %s\n", cl->cl_path); + TAILQ_FOREACH(clo, &cl->cl_options, clo_next) + fprintf(stderr, "\t\toption %s %s\n", clo->clo_name, clo->clo_value); + fprintf(stderr, "\t}\n"); + } + fprintf(stderr, "}\n"); + } +} +#endif + +int +ctld_conf_verify(struct ctld_conf *cc) +{ + struct ctld_auth_group *cag; + struct ctld_portal_group *cpg; + struct ctld_target *ct; + struct ctld_lun *cl, *cl2; + bool found_lun0; + + if (cc->cc_pidfile_path == NULL) + cc->cc_pidfile_path = checked_strdup(CTLD_DEFAULT_PIDFILE); + + TAILQ_FOREACH(ct, &cc->cc_targets, ct_next) { + if (ct->ct_auth_group == NULL) { + log_warnx("missing authentication for target \"%s\"; " + "must specify either \"auth-group\", \"chap\", " + "or \"chap-mutual\"", ct->ct_iqn); + return (1); + } + if (ct->ct_portal_group == NULL) { + ct->ct_portal_group = ctld_portal_group_find(cc, + "default"); + assert(ct->ct_portal_group != NULL); + } + found_lun0 = false; + TAILQ_FOREACH(cl, &ct->ct_luns, cl_next) { + if (cl->cl_lun == 0) + found_lun0 = true; + if (cl->cl_backend == NULL) + ctld_lun_set_backend(cl, "block"); + if (strcmp(cl->cl_backend, "block") == 0 && + cl->cl_path == NULL) { + log_warnx("missing path for lun %d, " + "target \"%s\"", cl->cl_lun, ct->ct_iqn); + return (1); + } + if (strcmp(cl->cl_backend, "ramdisk") == 0) { + if (cl->cl_size == 0) { + log_warnx("missing size for " + "ramdisk-backed lun %d, " + "target \"%s\"", + cl->cl_lun, ct->ct_iqn); + return (1); + } + if (cl->cl_path != NULL) { + log_warnx("path must not be specified " + "for ramdisk-backed lun %d, " + "target \"%s\"", + cl->cl_lun, ct->ct_iqn); + return (1); + } + } + if (cl->cl_lun < 0 || cl->cl_lun > 255) { + log_warnx("invalid lun number for lun %d, " + "target \"%s\"; must be between 0 and 255", + cl->cl_lun, ct->ct_iqn); + return (1); + } +#if 1 /* Should we? */ + TAILQ_FOREACH(cl2, &ct->ct_luns, cl_next) { + if (cl == cl2) + continue; + if (cl->cl_path != NULL && + cl2->cl_path != NULL && + strcmp(cl->cl_path, cl2->cl_path) == 0) + log_debugx("WARNING: duplicate path " + "for lun %d, target \"%s\"", + cl->cl_lun, ct->ct_iqn); + } +#endif + if (cl->cl_blocksize == 0) { + ctld_lun_set_blocksize(cl, + CTLD_DEFAULT_BLOCKSIZE); + } else if (cl->cl_blocksize <= 0) { + log_warnx("invalid blocksize for lun %d, " + "target \"%s\"; must be larger than 0", + cl->cl_lun, ct->ct_iqn); + return (1); + } + if (cl->cl_size != 0 && + cl->cl_size % cl->cl_blocksize != 0) { + log_warnx("invalid size for lun %d, target " + "\"%s\"; must be multitude of blocksize", + cl->cl_lun, ct->ct_iqn); + return (1); + } + } + if (!found_lun0) { + log_warnx("mandatory LUN 0 not configured " + "for target \"%s\"", ct->ct_iqn); + return (1); + } + } + TAILQ_FOREACH(cpg, &cc->cc_portal_groups, cpg_next) { + assert(cpg->cpg_name != NULL); + if (cpg->cpg_discovery_auth_group == NULL) { + cpg->cpg_discovery_auth_group = + ctld_auth_group_find(cc, "no-access"); + assert(cpg->cpg_discovery_auth_group != NULL); + } + + TAILQ_FOREACH(ct, &cc->cc_targets, ct_next) { + if (ct->ct_portal_group == cpg) + break; + } + if (ct == NULL) { + if (strcmp(cpg->cpg_name, "default") != 0) + log_warnx("portal-group \"%s\" not assigned " + "to any target", cpg->cpg_name); + cpg->cpg_unassigned = true; + } else + cpg->cpg_unassigned = false; + } + TAILQ_FOREACH(cag, &cc->cc_auth_groups, cag_next) { + if (cag->cag_name == NULL) + assert(cag->cag_target != NULL); + else + assert(cag->cag_target == NULL); + + TAILQ_FOREACH(ct, &cc->cc_targets, ct_next) { + if (ct->ct_auth_group == cag) + break; + } + if (ct == NULL && cag->cag_name != NULL && + strcmp(cag->cag_name, "no-authentication") != 0 && + strcmp(cag->cag_name, "no-access") != 0) { + log_warnx("auth-group \"%s\" not assigned " + "to any target", cag->cag_name); + } + } + + return (0); +} + +static int +ctld_conf_apply(struct ctld_conf *oldconf, struct ctld_conf *newconf) +{ + struct ctld_target *oldct, *newct, *tmpct; + struct ctld_lun *oldcl, *newcl, *tmpcl; + struct ctld_portal_group *oldcpg, *newcpg; + struct ctld_portal *oldcp, *newcp; + pid_t otherpid; + int changed, cumulated_error = 0, one = 1, error; + + if (oldconf->cc_debug != newconf->cc_debug) { + log_debugx("changing debug level to %d", newconf->cc_debug); + log_init(newconf->cc_debug); + } + + if (oldconf->cc_pidfh != NULL) { + assert(oldconf->cc_pidfile_path != NULL); + if (newconf->cc_pidfile_path != NULL && + strcmp(oldconf->cc_pidfile_path, + newconf->cc_pidfile_path) == 0) { + newconf->cc_pidfh = oldconf->cc_pidfh; + oldconf->cc_pidfh = NULL; + } else { + log_debugx("removing pidfile %s", + oldconf->cc_pidfile_path); + pidfile_remove(oldconf->cc_pidfh); + oldconf->cc_pidfh = NULL; + } + } + + if (newconf->cc_pidfh == NULL && newconf->cc_pidfile_path != NULL) { + log_debugx("opening pidfile %s", newconf->cc_pidfile_path); + newconf->cc_pidfh = + pidfile_open(newconf->cc_pidfile_path, 0600, &otherpid); + if (newconf->cc_pidfh == NULL) { + if (errno == EEXIST) + log_errx(1, "daemon already running, pid: %jd.", + (intmax_t)otherpid); + log_err(1, "cannot open or create pidfile \"%s\"", + newconf->cc_pidfile_path); + } + } + + TAILQ_FOREACH_SAFE(oldct, &oldconf->cc_targets, ct_next, tmpct) { + /* + * First, remove any targets present in the old configuration + * and missing in the new one. + */ + newct = ctld_target_find(newconf, oldct->ct_iqn); + if (newct == NULL) { + TAILQ_FOREACH_SAFE(oldcl, &oldct->ct_luns, cl_next, + tmpcl) { + log_debugx("target %s not found in the " + "configuration file; removing its lun %d, " + "backed by CTL lun %d", + oldct->ct_iqn, oldcl->cl_lun, + oldcl->cl_ctl_lun); + error = ctld_kernel_lun_remove(oldcl); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + cumulated_error++; + } + ctld_lun_delete(oldcl); + } + ctld_target_delete(oldct); + continue; + } + + /* + * Second, remove any LUNs present in the old target + * and missing in the new one. + */ + TAILQ_FOREACH_SAFE(oldcl, &oldct->ct_luns, cl_next, tmpcl) { + newcl = ctld_lun_find(newct, oldcl->cl_lun); + if (newcl == NULL) { + log_debugx("lun %d, target %s, CTL lun %d " + "not found in the configuration file; " + "removing", oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + error = ctld_kernel_lun_remove(oldcl); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + cumulated_error++; + } + ctld_lun_delete(oldcl); + continue; + } + + /* + * Also remove the LUNs changed by more than size. + */ + changed = 0; + assert(oldcl->cl_backend != NULL); + assert(newcl->cl_backend != NULL); + if (strcmp(newcl->cl_backend, oldcl->cl_backend) != 0) { + log_debugx("backend for lun %d, target %s, " + "CTL lun %d changed; removing", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + changed = 1; + } + if (oldcl->cl_blocksize != newcl->cl_blocksize) { + log_debugx("blocksize for lun %d, target %s, " + "CTL lun %d changed; removing", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + changed = 1; + } + if (newcl->cl_device_id != NULL && + (oldcl->cl_device_id == NULL || + strcmp(oldcl->cl_device_id, newcl->cl_device_id) != + 0)) { + log_debugx("device-id for lun %d, target %s, " + "CTL lun %d changed; removing", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + changed = 1; + } + if (newcl->cl_path != NULL && + (oldcl->cl_path == NULL || + strcmp(oldcl->cl_path, newcl->cl_path) != 0)) { + log_debugx("path for lun %d, target %s, " + "CTL lun %d, changed; removing", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + changed = 1; + } + if (newcl->cl_serial != NULL && + (oldcl->cl_serial == NULL || + strcmp(oldcl->cl_serial, newcl->cl_serial) != 0)) { + log_debugx("serial for lun %d, target %s, " + "CTL lun %d changed; removing", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + changed = 1; + } + if (changed) { + error = ctld_kernel_lun_remove(oldcl); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + cumulated_error++; + } + ctld_lun_delete(oldcl); + continue; + } + + ctld_lun_set_ctl_lun(newcl, oldcl->cl_ctl_lun); + } + } + + /* + * Now add new targets or modify existing ones. + */ + TAILQ_FOREACH(newct, &newconf->cc_targets, ct_next) { + oldct = ctld_target_find(oldconf, newct->ct_iqn); + + TAILQ_FOREACH(newcl, &newct->ct_luns, cl_next) { + if (oldct != NULL) { + oldcl = ctld_lun_find(oldct, newcl->cl_lun); + if (oldcl != NULL) { + if (newcl->cl_size != oldcl->cl_size) { + log_debugx("resizing lun %d, " + "target %s, CTL lun %d", + newcl->cl_lun, + newct->ct_iqn, + newcl->cl_ctl_lun); + error = + ctld_kernel_lun_resize(newcl); + if (error != 0) { + log_warnx("failed to " + "resize lun %d, " + "target %s, " + "CTL lun %d", + newcl->cl_lun, + newct->ct_iqn, + newcl->cl_lun); + cumulated_error++; + } + } + continue; + } + } + log_debugx("adding lun %d, target %s", + newcl->cl_lun, newct->ct_iqn); + error = ctld_kernel_lun_add(newcl); + if (error != 0) { + log_warnx("failed to add lun %d, target %s", + newcl->cl_lun, newct->ct_iqn); + cumulated_error++; + } + } + } + + /* + * Go through the new portals, opening the sockets as neccessary. + */ + TAILQ_FOREACH(newcpg, &newconf->cc_portal_groups, cpg_next) { + if (newcpg->cpg_unassigned) { + log_debugx("not listening on portal-group \"%s\", " + "not assigned to any target", + newcpg->cpg_name); + continue; + } + TAILQ_FOREACH(newcp, &newcpg->cpg_portals, cp_next) { + /* + * Try to find already open portal and reuse + * the listening socket. We don't care about + * what portal or portal group that was, what + * matters is the listening address. + */ + TAILQ_FOREACH(oldcpg, &oldconf->cc_portal_groups, + cpg_next) { + TAILQ_FOREACH(oldcp, &oldcpg->cpg_portals, + cp_next) { + if (strcmp(newcp->cp_listen, + oldcp->cp_listen) == 0 && + oldcp->cp_socket > 0) { + newcp->cp_socket = + oldcp->cp_socket; + oldcp->cp_socket = 0; + break; + } + } + } + if (newcp->cp_socket > 0) { + /* + * We're done with this portal. + */ + continue; + } + + log_debugx("listening on %s, portal-group \"%s\"", + newcp->cp_listen, newcpg->cpg_name); + + newcp->cp_socket = socket(newcp->cp_ai->ai_family, + newcp->cp_ai->ai_socktype, + newcp->cp_ai->ai_protocol); + if (newcp->cp_socket < 0) { + log_warn("socket(2) failed for %s", + newcp->cp_listen); + cumulated_error++; + continue; + } + error = setsockopt(newcp->cp_socket, SOL_SOCKET, + SO_REUSEADDR, &one, sizeof(one)); + if (error != 0) { + log_warn("setsockopt(SO_REUSEADDR) failed " + "for %s", newcp->cp_listen); + close(newcp->cp_socket); + newcp->cp_socket = 0; + cumulated_error++; + continue; + } + error = bind(newcp->cp_socket, newcp->cp_ai->ai_addr, + newcp->cp_ai->ai_addrlen); + if (error != 0) { + log_warn("bind(2) failed for %s", + newcp->cp_listen); + close(newcp->cp_socket); + newcp->cp_socket = 0; + cumulated_error++; + continue; + } + error = listen(newcp->cp_socket, -1); + if (error != 0) { + log_warn("listen(2) failed for %s", + newcp->cp_listen); + close(newcp->cp_socket); + newcp->cp_socket = 0; + cumulated_error++; + continue; + } + } + } + + /* + * Go through the no longer used sockets, closing them. + */ + TAILQ_FOREACH(oldcpg, &oldconf->cc_portal_groups, cpg_next) { + TAILQ_FOREACH(oldcp, &oldcpg->cpg_portals, cp_next) { + if (oldcp->cp_socket <= 0) + continue; + log_debugx("closing socket for %s, portal-group \"%s\"", + oldcp->cp_listen, oldcpg->cpg_name); + close(oldcp->cp_socket); + oldcp->cp_socket = 0; + } + } + + return (cumulated_error); +} + +static void +ctld_new_connection(struct ctld_portal *cp, int fd, bool dont_fork) +{ + struct ctld_connection *cc; + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + pid_t pid; + int error; + char host[NI_MAXHOST + 1]; + + if (dont_fork > 0) { + log_debugx("incoming connection; not forking due to -d flag"); + } else { + pid = fork(); + if (pid < 0) + log_err(1, "fork"); + if (pid > 0) { + close(fd); + return; + } + } + pidfile_close(cp->cp_portal_group->cpg_conf->cc_pidfh); + + error = getpeername(fd, (struct sockaddr *)&ss, &sslen); + if (error != 0) + log_err(1, "getpeername"); + error = getnameinfo((struct sockaddr *)&ss, sslen, + host, sizeof(host), NULL, 0, NI_NUMERICHOST); + if (error != 0) + log_errx(1, "getaddrinfo: %s", gai_strerror(error)); + + log_debugx("accepted connection from %s; portal group \"%s\"", + host, cp->cp_portal_group->cpg_name); + cc = ctld_connection_new(cp, fd, host); + ctld_login(cc); + if (cc->cc_session_type == CC_SESSION_TYPE_NORMAL) { + ctld_kernel_handoff(cc); + log_debugx("connection handed off to the kernel"); + } else { + assert(cc->cc_session_type == CC_SESSION_TYPE_DISCOVERY); + ctld_discovery(cc); + } + log_debugx("nothing more to do; exiting"); + exit(0); +} + +static int +fd_add(int fd, fd_set *fdset, int nfds) +{ + + /* + * Skip sockets which we failed to bind. + */ + if (fd <= 0) + return (nfds); + + FD_SET(fd, fdset); + if (fd > nfds) + nfds = fd; + return (nfds); +} + +static void +ctld_main_loop(struct ctld_conf *cc, bool dont_fork) +{ + struct ctld_portal_group *cpg; + struct ctld_portal *cp; + fd_set fdset; + int error, nfds, client_fd; + + pidfile_write(cc->cc_pidfh); + + for (;;) { + if (sighup_received || sigterm_received) + return; + + FD_ZERO(&fdset); + nfds = 0; + TAILQ_FOREACH(cpg, &cc->cc_portal_groups, cpg_next) { + TAILQ_FOREACH(cp, &cpg->cpg_portals, cp_next) + nfds = fd_add(cp->cp_socket, &fdset, nfds); + } + error = select(nfds + 1, &fdset, NULL, NULL, NULL); + if (error <= 0) { + if (errno == EINTR) + return; + log_err(1, "select"); + } + TAILQ_FOREACH(cpg, &cc->cc_portal_groups, cpg_next) { + TAILQ_FOREACH(cp, &cpg->cpg_portals, cp_next) { + if (!FD_ISSET(cp->cp_socket, &fdset)) + continue; + client_fd = accept(cp->cp_socket, NULL, 0); + if (client_fd < 0) + log_err(1, "accept"); + ctld_new_connection(cp, client_fd, dont_fork); + break; + } + } + } +} + +static void +ctld_sighup_handler(int dummy __unused) +{ + + sighup_received = true; +} + +static void +ctld_sigterm_handler(int dummy __unused) +{ + + sigterm_received = true; +} + +static void +ctld_register_signals(void) +{ + struct sigaction sa; + int error; + + bzero(&sa, sizeof(sa)); + sa.sa_handler = ctld_sighup_handler; + sigfillset(&sa.sa_mask); + error = sigaction(SIGHUP, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); + + sa.sa_handler = ctld_sigterm_handler; + error = sigaction(SIGTERM, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); + + sa.sa_handler = ctld_sigterm_handler; + error = sigaction(SIGINT, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); +} + +int +main(int argc, char **argv) +{ + struct ctld_conf *oldconf, *newconf, *tmpconf; + const char *config_path = CTLD_DEFAULT_CONFIG; + int debug = 0, ch, error; + bool dont_daemonize = false; + + while ((ch = getopt(argc, argv, "df:")) != -1) { + switch (ch) { + case 'd': + dont_daemonize = true; + debug++; + break; + case 'f': + config_path = optarg; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + if (argc != 0) + usage(); + + log_init(debug); + ctld_kernel_init(); + + oldconf = ctld_conf_new_from_kernel(); + newconf = ctld_conf_new_from_file(config_path); + if (newconf == NULL) + log_errx(1, "configuration error, exiting"); + if (debug > 0) { + oldconf->cc_debug = debug; + newconf->cc_debug = debug; + } + + if (dont_daemonize == false) { + if (daemon(0, 0) == -1) { + log_warn("cannot daemonize"); + pidfile_remove(newconf->cc_pidfh); + exit(1); + } + } + + error = ctld_conf_apply(oldconf, newconf); + if (error != 0) + log_errx(1, "failed to apply configuration, exiting"); + ctld_conf_delete(oldconf); + oldconf = NULL; + + ctld_register_signals(); + + log_debugx("enabling CTL iSCSI port"); + error = ctld_kernel_port_on(); + if (error != 0) + log_errx(1, "failed to enable CTL iSCSI port, exiting"); + + for (;;) { + ctld_main_loop(newconf, debug); + if (sighup_received) { + sighup_received = false; + log_debugx("received SIGHUP, reloading configuration"); + tmpconf = ctld_conf_new_from_file(config_path); + if (tmpconf == NULL) { + log_warnx("configuration error, " + "continuing with old configuration"); + } else { + if (debug > 0) + tmpconf->cc_debug = debug; + oldconf = newconf; + newconf = tmpconf; + error = ctld_conf_apply(oldconf, newconf); + if (error != 0) + log_warnx("failed to reload " + "configuration"); + ctld_conf_delete(oldconf); + oldconf = NULL; + } + } else { + log_debugx("exiting on signal; " + "reloading empty configuration"); + + log_debugx("disabling CTL iSCSI port " + "and terminating all connections"); + error = ctld_kernel_port_off(); + if (error != 0) + log_warnx("failed to disable CTL iSCSI port"); + + oldconf = newconf; + newconf = ctld_conf_new(); + if (debug > 0) + newconf->cc_debug = debug; + error = ctld_conf_apply(oldconf, newconf); + if (error != 0) + log_warnx("failed to apply configuration"); + + log_warnx("exiting on signal"); + exit(0); + } + } + /* NOTREACHED */ +} diff -urNp freebsd/src/usr.sbin/ctld/ctld.h iscsi/usr.sbin/ctld/ctld.h --- freebsd/src/usr.sbin/ctld/ctld.h 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/ctld.h 2013-02-19 09:34:46.000000000 +0100 @@ -0,0 +1,279 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#ifndef CTLD_H +#define CTLD_H + +#include +#include +#include + +#define CTLD_DEFAULT_CONFIG "/etc/ctl.conf" +#define CTLD_DEFAULT_PIDFILE "/var/run/ctld.pid" +#define CTLD_DEFAULT_BLOCKSIZE 512 + +#define CTLD_MAX_NAME_LEN 223 +#define CTLD_MAX_DATA_SEGMENT_LENGTH 65536 + +struct ctld_auth { + TAILQ_ENTRY(ctld_auth) ca_next; + struct ctld_auth_group *ca_auth_group; + char *ca_user; + char *ca_secret; + char *ca_mutual_user; + char *ca_mutual_secret; +}; + +#define CAG_TYPE_UNKNOWN 0 +#define CAG_TYPE_NO_AUTHENTICATION 1 +#define CAG_TYPE_CHAP 2 +#define CAG_TYPE_CHAP_MUTUAL 3 + +struct ctld_auth_group { + TAILQ_ENTRY(ctld_auth_group) cag_next; + struct ctld_conf *cag_conf; + char *cag_name; + struct ctld_target *cag_target; + int cag_type; + TAILQ_HEAD(, ctld_auth) cag_auths; +}; + +struct ctld_portal { + TAILQ_ENTRY(ctld_portal) cp_next; + struct ctld_portal_group *cp_portal_group; + struct addrinfo *cp_ai; + char *cp_listen; + + TAILQ_HEAD(, ctld_target) cp_targets; + int cp_socket; +}; + +struct ctld_portal_group { + TAILQ_ENTRY(ctld_portal_group) cpg_next; + struct ctld_conf *cpg_conf; + char *cpg_name; + struct ctld_auth_group *cpg_discovery_auth_group; + bool cpg_unassigned; + TAILQ_HEAD(, ctld_portal) cpg_portals; + + uint16_t cpg_tag; +}; + +struct ctld_lun_option { + TAILQ_ENTRY(ctld_lun_option) clo_next; + struct ctld_lun *clo_lun; + char *clo_name; + char *clo_value; +}; + +struct ctld_lun { + TAILQ_ENTRY(ctld_lun) cl_next; + TAILQ_HEAD(, ctld_lun_option) cl_options; + struct ctld_target *cl_target; + int cl_lun; + char *cl_backend; + int cl_blocksize; + char *cl_device_id; + char *cl_path; + char *cl_serial; + int64_t cl_size; + + int cl_ctl_lun; +}; + +struct ctld_target { + TAILQ_ENTRY(ctld_target) ct_next; + TAILQ_ENTRY(ctld_target) ct_portal_next; + TAILQ_HEAD(, ctld_lun) ct_luns; + struct ctld_conf *ct_conf; + struct ctld_auth_group *ct_auth_group; + struct ctld_portal_group *ct_portal_group; + char *ct_iqn; + char *ct_alias; +}; + +struct ctld_conf { + char *cc_pidfile_path; + TAILQ_HEAD(, ctld_target) cc_targets; + TAILQ_HEAD(, ctld_auth_group) cc_auth_groups; + TAILQ_HEAD(, ctld_portal_group) cc_portal_groups; + int cc_debug; + + uint16_t cc_last_portal_group_tag; + struct pidfh *cc_pidfh; +}; + +#define CC_SESSION_TYPE_NONE 0 +#define CC_SESSION_TYPE_DISCOVERY 1 +#define CC_SESSION_TYPE_NORMAL 2 + +#define CC_DIGEST_NONE 0 +#define CC_DIGEST_CRC32C 1 + +struct ctld_connection { + struct ctld_portal *cc_portal; + struct ctld_target *cc_target; + int cc_socket; + int cc_session_type; + char *cc_initiator_name; + char *cc_initiator_addr; + uint32_t cc_cmdsn; + uint32_t cc_statsn; + size_t cc_max_data_segment_length; + size_t cc_max_burst_length; + size_t cc_max_outstanding_r2t; + int cc_header_digest; + int cc_data_digest; +}; + +struct ctld_pdu { + struct ctld_connection *cp_connection; + struct iscsi_bhs *cp_bhs; + char *cp_data; + size_t cp_data_len; +}; + +#define CTLD_KEYS_MAX 1024 + +struct ctld_keys { + char *ck_names[CTLD_KEYS_MAX]; + char *ck_values[CTLD_KEYS_MAX]; + char *ck_data; + size_t ck_data_len; +}; + +struct ctld_conf *ctld_conf_new(void); +struct ctld_conf *ctld_conf_new_from_file(const char *path); +struct ctld_conf *ctld_conf_new_from_kernel(void); +void ctld_conf_delete(struct ctld_conf *cc); +int ctld_conf_verify(struct ctld_conf *cc); + +struct ctld_auth_group *ctld_auth_group_new(struct ctld_conf *cc, + const char *name); +void ctld_auth_group_delete(struct ctld_auth_group *cag); +struct ctld_auth_group *ctld_auth_group_find(struct ctld_conf *cc, + const char *name); + +const struct ctld_auth *ctld_auth_new_chap(struct ctld_auth_group *cag, + const char *user, const char *secret); +const struct ctld_auth *ctld_auth_new_chap_mutual(struct ctld_auth_group *cag, + const char *user, const char *secret, + const char *user2, const char *secret2); +const struct ctld_auth *ctld_auth_find(struct ctld_auth_group *cag, + const char *user); + +struct ctld_portal_group *ctld_portal_group_new(struct ctld_conf *cc, + const char *name); +void ctld_portal_group_delete(struct ctld_portal_group *cag); +struct ctld_portal_group *ctld_portal_group_find(struct ctld_conf *cc, + const char *name); +int ctld_portal_group_add_listen(struct ctld_portal_group + *cag, const char *listen); + +struct ctld_target *ctld_target_new(struct ctld_conf *cc, const char *iqn); +void ctld_target_delete(struct ctld_target *ct); +struct ctld_target *ctld_target_find(struct ctld_conf *cc, + const char *iqn); + +struct ctld_lun *ctld_lun_new(struct ctld_target *ct, int lun); +void ctld_lun_delete(struct ctld_lun *ct); +struct ctld_lun *ctld_lun_find(struct ctld_target *ct, int lun); +void ctld_lun_set_backend(struct ctld_lun *cl, + const char *value); +void ctld_lun_set_blocksize(struct ctld_lun *cl, + size_t value); +void ctld_lun_set_device_id(struct ctld_lun *cl, + const char *value); +void ctld_lun_set_path(struct ctld_lun *cl, + const char *value); +void ctld_lun_set_serial(struct ctld_lun *cl, + const char *value); +void ctld_lun_set_size(struct ctld_lun *cl, + size_t value); +void ctld_lun_set_ctl_lun(struct ctld_lun *cl, + uint32_t value); + +struct ctld_lun_option *ctld_lun_option_new(struct ctld_lun *cl, + const char *name, const char *value); +void ctld_lun_option_delete(struct ctld_lun_option *clo); +struct ctld_lun_option *ctld_lun_option_find(struct ctld_lun *cl, + const char *name); +void ctld_lun_option_set(struct ctld_lun_option *clo, + const char *value); + +void ctld_kernel_init(void); +int ctld_kernel_lun_add(struct ctld_lun *cl); +int ctld_kernel_lun_resize(struct ctld_lun *cl); +int ctld_kernel_lun_remove(struct ctld_lun *cl); +void ctld_kernel_handoff(struct ctld_connection *cc); +int ctld_kernel_port_on(void); +int ctld_kernel_port_off(void); + +struct ctld_keys *ctld_keys_new(void); +void ctld_keys_delete(struct ctld_keys *ck); +void ctld_keys_load(struct ctld_keys *ck, + const struct ctld_pdu *cp); +void ctld_keys_save(struct ctld_keys *ck, + struct ctld_pdu *cp); +const char *ctld_keys_find(struct ctld_keys *ck, const char *name); +int ctld_keys_find_int(struct ctld_keys *ck, + const char *name); +void ctld_keys_add(struct ctld_keys *ck, + const char *name, const char *value); +void ctld_keys_add_int(struct ctld_keys *ck, + const char *name, int value); + +struct ctld_pdu *ctld_pdu_new(struct ctld_connection *cc); +struct ctld_pdu *ctld_pdu_new_response(struct ctld_pdu *request); +void ctld_pdu_receive(struct ctld_pdu *request); +void ctld_pdu_send(struct ctld_pdu *response); +void ctld_pdu_delete(struct ctld_pdu *cp); + +void ctld_login(struct ctld_connection *cc); + +void ctld_discovery(struct ctld_connection *cc); + +void log_init(int level); +void log_err(int, const char *, ...) + __dead2 __printf0like(2, 3); +void log_errx(int, const char *, ...) + __dead2 __printf0like(2, 3); +void log_warn(const char *, ...) __printf0like(1, 2); +void log_warnx(const char *, ...) __printflike(1, 2); +void log_debugx(const char *, ...) __printf0like(1, 2); + +char *checked_strdup(const char *); +bool ctld_valid_iscsi_name(const char *name); + +void yyerror(const char *); +int yylex(void); + +#endif /* !CTLD_H */ diff -urNp freebsd/src/usr.sbin/ctld/discovery.c iscsi/usr.sbin/ctld/discovery.c --- freebsd/src/usr.sbin/ctld/discovery.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/discovery.c 2013-01-26 00:37:13.000000000 +0100 @@ -0,0 +1,219 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include "ctld.h" +#include "iscsi.h" + +static struct ctld_pdu * +ctld_text_receive(struct ctld_connection *cc) +{ + struct ctld_pdu *request; + struct iscsi_bhs_text_request *bhstr; + + request = ctld_pdu_new(cc); + ctld_pdu_receive(request); + if ((request->cp_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != + ISCSI_BHS_OPCODE_TEXT_REQUEST) + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->cp_bhs->bhs_opcode); + bhstr = (struct iscsi_bhs_text_request *)request->cp_bhs; +#if 0 + if ((bhstr->bhstr_flags & ISCSI_BHSTR_FLAGS_FINAL) != 0) + log_errx(1, "received Text PDU without the \"F\" flag"); +#endif + /* + * XXX: Implement the C flag some day. + */ + if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0) + log_errx(1, "received Text PDU with unsupported \"C\" flag"); + if (request->cp_data_len == 0) + log_errx(1, "received Text PDU with empty data segment"); + + if (ntohl(bhstr->bhstr_cmdsn) < cc->cc_cmdsn) { + log_errx(1, "received Text PDU with decreasing CmdSN: " + "was %d, is %d", cc->cc_cmdsn, ntohl(bhstr->bhstr_cmdsn)); + } + if (ntohl(bhstr->bhstr_expstatsn) != cc->cc_statsn) { + log_errx(1, "received Text PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhstr->bhstr_expstatsn), + cc->cc_statsn); + } + cc->cc_cmdsn = ntohl(bhstr->bhstr_cmdsn); + + return (request); +} + +static struct ctld_pdu * +ctld_text_new_response(struct ctld_pdu *request) +{ + struct ctld_pdu *response; + struct ctld_connection *cc; + struct iscsi_bhs_text_request *bhstr; + struct iscsi_bhs_text_response *bhstr2; + + bhstr = (struct iscsi_bhs_text_request *)request->cp_bhs; + cc = request->cp_connection; + + response = ctld_pdu_new_response(request); + bhstr2 = (struct iscsi_bhs_text_response *)response->cp_bhs; + bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE; + bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL; + bhstr2->bhstr_lun = bhstr->bhstr_lun; + bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag; + bhstr2->bhstr_target_transfer_tag = bhstr->bhstr_target_transfer_tag; + bhstr2->bhstr_statsn = htonl(cc->cc_statsn++); + bhstr2->bhstr_expcmdsn = htonl(cc->cc_cmdsn); + bhstr2->bhstr_maxcmdsn = htonl(cc->cc_cmdsn); + + return (response); +} + +static struct ctld_pdu * +ctld_logout_receive(struct ctld_connection *cc) +{ + struct ctld_pdu *request; + struct iscsi_bhs_logout_request *bhslr; + + request = ctld_pdu_new(cc); + ctld_pdu_receive(request); + if ((request->cp_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != + ISCSI_BHS_OPCODE_LOGOUT_REQUEST) + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->cp_bhs->bhs_opcode); + bhslr = (struct iscsi_bhs_logout_request *)request->cp_bhs; + if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION) + log_debugx("received Logout PDU with invalid reason 0x%x; " + "continuing anyway", bhslr->bhslr_reason & 0x7f); + if (ntohl(bhslr->bhslr_cmdsn) < cc->cc_cmdsn) { + log_errx(1, "received Logout PDU with decreasing CmdSN: " + "was %d, is %d", cc->cc_cmdsn, ntohl(bhslr->bhslr_cmdsn)); + } + if (ntohl(bhslr->bhslr_expstatsn) != cc->cc_statsn) { + log_errx(1, "received Logout PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn), + cc->cc_statsn); + } + cc->cc_cmdsn = ntohl(bhslr->bhslr_cmdsn); + + return (request); +} + +static struct ctld_pdu * +ctld_logout_new_response(struct ctld_pdu *request) +{ + struct ctld_pdu *response; + struct ctld_connection *cc; + struct iscsi_bhs_logout_request *bhslr; + struct iscsi_bhs_logout_response *bhslr2; + + bhslr = (struct iscsi_bhs_logout_request *)request->cp_bhs; + cc = request->cp_connection; + + response = ctld_pdu_new_response(request); + bhslr2 = (struct iscsi_bhs_logout_response *)response->cp_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; + bhslr2->bhslr_flags = 0x80; + bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; + bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; + bhslr2->bhslr_statsn = htonl(cc->cc_statsn++); + bhslr2->bhslr_expcmdsn = htonl(cc->cc_cmdsn); + bhslr2->bhslr_maxcmdsn = htonl(cc->cc_cmdsn); + + return (response); +} + +void +ctld_discovery(struct ctld_connection *cc) +{ + struct ctld_pdu *request, *response; + struct ctld_keys *request_keys, *response_keys; + struct ctld_target *ct; + const char *send_targets; + + log_debugx("beginning discovery session; waiting for Text PDU"); + request = ctld_text_receive(cc); + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + + send_targets = ctld_keys_find(request_keys, "SendTargets"); + if (send_targets == NULL) + log_errx(1, "received Text PDU without SendTargets"); + + response = ctld_text_new_response(request); + response_keys = ctld_keys_new(); + + if (strcmp(send_targets, "All") == 0) { + TAILQ_FOREACH(ct, + &cc->cc_portal->cp_portal_group->cpg_conf->cc_targets, + ct_next) { + if (ct->ct_portal_group != + cc->cc_portal->cp_portal_group) { + log_debugx("not returning target \"%s\"; " + "belongs to a different portal group", + ct->ct_iqn); + continue; + } + ctld_keys_add(response_keys, "TargetName", ct->ct_iqn); + } + } else { + ct = ctld_target_find(cc->cc_portal->cp_portal_group->cpg_conf, + send_targets); + if (ct == NULL) { + log_debugx("initiator requested information on unknown " + "target \"%s\"; returning nothing", send_targets); + } else { + ctld_keys_add(response_keys, "TargetName", ct->ct_iqn); + } + } + ctld_keys_save(response_keys, response); + + ctld_pdu_send(response); + ctld_pdu_delete(response); + ctld_keys_delete(response_keys); + ctld_pdu_delete(request); + ctld_keys_delete(request_keys); + + log_debugx("done sending targets; waiting for Logout PDU"); + request = ctld_logout_receive(cc); + response = ctld_logout_new_response(request); + + ctld_pdu_send(response); + ctld_pdu_delete(response); + ctld_pdu_delete(request); + + log_debugx("discovery session done"); +} diff -urNp freebsd/src/usr.sbin/ctld/kernel.c iscsi/usr.sbin/ctld/kernel.c --- freebsd/src/usr.sbin/ctld/kernel.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/kernel.c 2013-02-19 17:44:21.000000000 +0100 @@ -0,0 +1,628 @@ +/*- + * Copyright (c) 2003, 2004 Silicon Graphics International Corp. + * Copyright (c) 1997-2007 Kenneth D. Merry + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * Portions of this software were 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, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ctld.h" + +static int ctl_fd = 0; + +void +ctld_kernel_init(void) +{ + + ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR); + if (ctl_fd < 0) + log_err(1, "failed to open %s", CTL_DEFAULT_DEV); +} + +/* + * Name/value pair used for per-LUN attributes. + */ +struct cctl_lun_nv { + char *name; + char *value; + STAILQ_ENTRY(cctl_lun_nv) links; +}; + +/* + * Backend LUN information. + */ +struct cctl_lun { + uint64_t lun_id; + char *backend_type; + uint64_t size_blocks; + uint32_t blocksize; + char *serial_number; + char *device_id; + char *ctld_target; + int ctld_lun; + STAILQ_HEAD(,cctl_lun_nv) attr_list; + STAILQ_ENTRY(cctl_lun) links; +}; + +struct cctl_devlist_data { + int num_luns; + STAILQ_HEAD(,cctl_lun) lun_list; + struct cctl_lun *cur_lun; + int level; + struct sbuf *cur_sb[32]; +}; + +static void +cctl_start_element(void *user_data, const char *name, const char **attr) +{ + int i; + struct cctl_devlist_data *devlist; + struct cctl_lun *cur_lun; + + devlist = (struct cctl_devlist_data *)user_data; + cur_lun = devlist->cur_lun; + devlist->level++; + if ((u_int)devlist->level > (sizeof(devlist->cur_sb) / + sizeof(devlist->cur_sb[0]))) + log_errx(1, "%s: too many nesting levels, %zd max", __func__, + sizeof(devlist->cur_sb) / sizeof(devlist->cur_sb[0])); + + devlist->cur_sb[devlist->level] = sbuf_new_auto(); + if (devlist->cur_sb[devlist->level] == NULL) + log_err(1, "%s: unable to allocate sbuf", __func__); + + if (strcmp(name, "lun") == 0) { + if (cur_lun != NULL) + log_errx(1, "%s: improper lun element nesting", + __func__); + + cur_lun = calloc(1, sizeof(*cur_lun)); + if (cur_lun == NULL) + log_err(1, "%s: cannot allocate %zd bytes", __func__, + sizeof(*cur_lun)); + + devlist->num_luns++; + devlist->cur_lun = cur_lun; + + STAILQ_INIT(&cur_lun->attr_list); + STAILQ_INSERT_TAIL(&devlist->lun_list, cur_lun, links); + + for (i = 0; attr[i] != NULL; i += 2) { + if (strcmp(attr[i], "id") == 0) { + cur_lun->lun_id = strtoull(attr[i+1], NULL, 0); + } else { + log_errx(1, "%s: invalid LUN attribute %s = %s", + __func__, attr[i], attr[i+1]); + } + } + } +} + +static void +cctl_end_element(void *user_data, const char *name) +{ + struct cctl_devlist_data *devlist; + struct cctl_lun *cur_lun; + char *str; + + devlist = (struct cctl_devlist_data *)user_data; + cur_lun = devlist->cur_lun; + + if ((cur_lun == NULL) + && (strcmp(name, "ctllunlist") != 0)) + log_errx(1, "%s: cur_lun == NULL! (name = %s)", __func__, name); + + if (devlist->cur_sb[devlist->level] == NULL) + log_errx(1, "%s: no valid sbuf at level %d (name %s)", __func__, + devlist->level, name); + + sbuf_finish(devlist->cur_sb[devlist->level]); + str = checked_strdup(sbuf_data(devlist->cur_sb[devlist->level])); + + if (strlen(str) == 0) { + free(str); + str = NULL; + } + + sbuf_delete(devlist->cur_sb[devlist->level]); + devlist->cur_sb[devlist->level] = NULL; + devlist->level--; + + if (strcmp(name, "backend_type") == 0) { + cur_lun->backend_type = str; + str = NULL; + } else if (strcmp(name, "size") == 0) { + cur_lun->size_blocks = strtoull(str, NULL, 0); + } else if (strcmp(name, "blocksize") == 0) { + cur_lun->blocksize = strtoul(str, NULL, 0); + } else if (strcmp(name, "serial_number") == 0) { + cur_lun->serial_number = str; + str = NULL; + } else if (strcmp(name, "device_id") == 0) { + cur_lun->device_id = str; + str = NULL; + } else if (strcmp(name, "ctld_target") == 0) { + cur_lun->ctld_target = str; + str = NULL; + } else if (strcmp(name, "ctld_lun") == 0) { + cur_lun->ctld_lun = strtoul(str, NULL, 0); + } else if (strcmp(name, "lun") == 0) { + devlist->cur_lun = NULL; + } else if (strcmp(name, "ctllunlist") == 0) { + + } else { + struct cctl_lun_nv *nv; + + nv = calloc(1, sizeof(*nv)); + if (nv == NULL) + log_err(1, "%s: can't allocate %zd bytes for nv pair", + __func__, sizeof(*nv)); + + nv->name = checked_strdup(name); + + nv->value = str; + str = NULL; + STAILQ_INSERT_TAIL(&cur_lun->attr_list, nv, links); + } + + free(str); +} + +static void +cctl_char_handler(void *user_data, const XML_Char *str, int len) +{ + struct cctl_devlist_data *devlist; + + devlist = (struct cctl_devlist_data *)user_data; + + sbuf_bcat(devlist->cur_sb[devlist->level], str, len); +} + +struct ctld_conf * +ctld_conf_new_from_kernel(void) +{ + struct ctld_conf *cc = NULL; + struct ctld_target *ct; + struct ctld_lun *cl; + struct ctld_lun_option *clo; + struct ctl_lun_list list; + struct cctl_devlist_data devlist; + struct cctl_lun *lun; + XML_Parser parser; + char *lun_str = NULL; + int lun_len; + int retval; + + lun_len = 4096; + + bzero(&devlist, sizeof(devlist)); + STAILQ_INIT(&devlist.lun_list); + + log_debugx("obtaining previously configured CTL luns from the kernel"); + +retry: + lun_str = realloc(lun_str, lun_len); + if (lun_str == NULL) + log_err(1, "realloc"); + + bzero(&list, sizeof(list)); + list.alloc_len = lun_len; + list.status = CTL_LUN_LIST_NONE; + list.lun_xml = lun_str; + + if (ioctl(ctl_fd, CTL_LUN_LIST, &list) == -1) { + log_warn("error issuing CTL_LUN_LIST ioctl"); + free(lun_str); + return (NULL); + } + + if (list.status == CTL_LUN_LIST_ERROR) { + log_warnx("error returned from CTL_LUN_LIST ioctl: %s", + list.error_str); + free(lun_str); + return (NULL); + } + + if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) { + lun_len = lun_len << 1; + goto retry; + } + + parser = XML_ParserCreate(NULL); + if (parser == NULL) { + log_warnx("unable to create XML parser"); + free(lun_str); + return (NULL); + } + + XML_SetUserData(parser, &devlist); + XML_SetElementHandler(parser, cctl_start_element, cctl_end_element); + XML_SetCharacterDataHandler(parser, cctl_char_handler); + + retval = XML_Parse(parser, lun_str, strlen(lun_str), 1); + XML_ParserFree(parser); + free(lun_str); + if (retval != 1) { + log_warnx("XML_Parse failed"); + return (NULL); + } + + cc = ctld_conf_new(); + + STAILQ_FOREACH(lun, &devlist.lun_list, links) { + struct cctl_lun_nv *nv; + + if (lun->ctld_target == NULL) { + log_debugx("CTL lun %ju wasn't managed by ctld; " + "ignoring", (uintmax_t)lun->lun_id); + continue; + } + + ct = ctld_target_find(cc, lun->ctld_target); + if (ct == NULL) { +#if 0 + log_debugx("found new kernel target %s for CTL lun %ld", + lun->ctld_target, lun->lun_id); +#endif + ct = ctld_target_new(cc, lun->ctld_target); + if (ct == NULL) { + log_warnx("ctld_target_new failed"); + continue; + } + } + + cl = ctld_lun_find(ct, lun->ctld_lun); + if (cl != NULL) { + log_warnx("found CTL lun %ju, backing lun %d, target " + "%s, also backed by CTL lun %d; ignoring", + (uintmax_t) lun->lun_id, cl->cl_lun, + cl->cl_target->ct_iqn, cl->cl_ctl_lun); + continue; + } + + log_debugx("found CTL lun %ju, backing lun %d, target %s", + (uintmax_t)lun->lun_id, lun->ctld_lun, lun->ctld_target); + + cl = ctld_lun_new(ct, lun->ctld_lun); + if (cl == NULL) { + log_warnx("ctld_lun_new failed"); + continue; + } + ctld_lun_set_backend(cl, lun->backend_type); + ctld_lun_set_blocksize(cl, lun->blocksize); + ctld_lun_set_device_id(cl, lun->device_id); + ctld_lun_set_serial(cl, lun->serial_number); + ctld_lun_set_size(cl, lun->size_blocks * cl->cl_blocksize); + ctld_lun_set_ctl_lun(cl, lun->lun_id); + + STAILQ_FOREACH(nv, &lun->attr_list, links) { + if (strcmp(nv->name, "file") == 0 || + strcmp(nv->name, "dev") == 0) { + ctld_lun_set_path(cl, nv->value); + continue; + } + clo = ctld_lun_option_new(cl, nv->name, nv->value); + if (clo == NULL) + log_warnx("unable to add CTL lun option %s " + "for CTL lun %ju for lun %d, target %s", + nv->name, (uintmax_t) lun->lun_id, + cl->cl_lun, cl->cl_target->ct_iqn); + } + } + + return (cc); +} + +int +ctld_kernel_lun_add(struct ctld_lun *cl) +{ + struct ctld_lun_option *clo; + struct ctl_lun_req req; + char *tmp; + int error, i, num_options; + + bzero(&req, sizeof(req)); + + strlcpy(req.backend, cl->cl_backend, sizeof(req.backend)); + req.reqtype = CTL_LUNREQ_CREATE; + + req.reqdata.create.blocksize_bytes = cl->cl_blocksize; + + if (cl->cl_size != 0) + req.reqdata.create.lun_size_bytes = cl->cl_size; + + req.reqdata.create.flags |= CTL_LUN_FLAG_DEV_TYPE; + req.reqdata.create.device_type = T_DIRECT; + + if (cl->cl_serial != NULL) { + strlcpy(req.reqdata.create.serial_num, cl->cl_serial, + sizeof(req.reqdata.create.serial_num)); + req.reqdata.create.flags |= CTL_LUN_FLAG_SERIAL_NUM; + } + + if (cl->cl_device_id != NULL) { + strlcpy(req.reqdata.create.device_id, cl->cl_device_id, + sizeof(req.reqdata.create.device_id)); + req.reqdata.create.flags |= CTL_LUN_FLAG_DEVID; + } + + if (cl->cl_path != NULL) { + clo = ctld_lun_option_find(cl, "file"); + if (clo != NULL) { + ctld_lun_option_set(clo, cl->cl_path); + } else { + clo = ctld_lun_option_new(cl, "file", cl->cl_path); + assert(clo != NULL); + } + } + + clo = ctld_lun_option_find(cl, "ctld_target"); + if (clo != NULL) { + ctld_lun_option_set(clo, cl->cl_target->ct_iqn); + } else { + clo = ctld_lun_option_new(cl, "ctld_target", + cl->cl_target->ct_iqn); + assert(clo != NULL); + } + + asprintf(&tmp, "%d", cl->cl_lun); + if (tmp == NULL) + log_errx(1, "asprintf"); + clo = ctld_lun_option_find(cl, "ctld_lun"); + if (clo != NULL) { + ctld_lun_option_set(clo, tmp); + free(tmp); + } else { + clo = ctld_lun_option_new(cl, "ctld_lun", tmp); + free(tmp); + assert(clo != NULL); + } + + num_options = 0; + TAILQ_FOREACH(clo, &cl->cl_options, clo_next) + num_options++; + + req.num_be_args = num_options; + if (num_options > 0) { + req.be_args = malloc(num_options * sizeof(*req.be_args)); + if (req.be_args == NULL) { + log_warn("error allocating %zd bytes", + num_options * sizeof(*req.be_args)); + return (1); + } + + i = 0; + TAILQ_FOREACH(clo, &cl->cl_options, clo_next) { + /* + * +1 for the terminating '\0' + */ + req.be_args[i].namelen = strlen(clo->clo_name) + 1; + req.be_args[i].name = clo->clo_name; + req.be_args[i].vallen = strlen(clo->clo_value) + 1; + req.be_args[i].value = clo->clo_value; + req.be_args[i].flags = CTL_BEARG_ASCII | CTL_BEARG_RD; + i++; + } + assert(i == num_options); + } + + error = ioctl(ctl_fd, CTL_LUN_REQ, &req); + free(req.be_args); + if (error != 0) { + log_warn("error issuing CTL_LUN_REQ ioctl"); + return (1); + } + + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from LUN creation request: %s", + req.error_str); + return (1); + } + + if (req.status != CTL_LUN_OK) { + log_warnx("unknown LUN creation request status %d", + req.status); + return (1); + } + + ctld_lun_set_ctl_lun(cl, req.reqdata.create.req_lun_id); + + return (0); +} + +int +ctld_kernel_lun_resize(struct ctld_lun *cl) +{ + struct ctl_lun_req req; + + bzero(&req, sizeof(req)); + + strlcpy(req.backend, cl->cl_backend, sizeof(req.backend)); + req.reqtype = CTL_LUNREQ_MODIFY; + + req.reqdata.modify.lun_id = cl->cl_ctl_lun; + req.reqdata.modify.lun_size_bytes = cl->cl_size; + + if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) { + log_warn("error issuing CTL_LUN_REQ ioctl"); + return (1); + } + + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from LUN modification request: %s", + req.error_str); + return (1); + } + + if (req.status != CTL_LUN_OK) { + log_warnx("unknown LUN modification request status %d", + req.status); + return (1); + } + + return (0); +} + +int +ctld_kernel_lun_remove(struct ctld_lun *cl) +{ + struct ctl_lun_req req; + + bzero(&req, sizeof(req)); + + strlcpy(req.backend, cl->cl_backend, sizeof(req.backend)); + req.reqtype = CTL_LUNREQ_RM; + + req.reqdata.rm.lun_id = cl->cl_ctl_lun; + + if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) { + log_warn("error issuing CTL_LUN_REQ ioctl"); + return (1); + } + + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from LUN removal request: %s", + req.error_str); + return (1); + } + + if (req.status != CTL_LUN_OK) { + log_warnx("unknown LUN removal request status %d", req.status); + return (1); + } + + return (0); +} + +void +ctld_kernel_handoff(struct ctld_connection *cc) +{ + struct ctl_iscsi req; + + bzero(&req, sizeof(req)); + + req.type = CTL_ISCSI_HANDOFF; + strlcpy(req.data.handoff.initiator_name, + cc->cc_initiator_name, sizeof(req.data.handoff.initiator_name)); + strlcpy(req.data.handoff.initiator_addr, + cc->cc_initiator_addr, sizeof(req.data.handoff.initiator_addr)); + strlcpy(req.data.handoff.target_name, + cc->cc_target->ct_iqn, sizeof(req.data.handoff.target_name)); + req.data.handoff.socket = cc->cc_socket; + req.data.handoff.portal_group_tag = + cc->cc_portal->cp_portal_group->cpg_tag; + if (cc->cc_header_digest == CC_DIGEST_CRC32C) + req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C; + if (cc->cc_data_digest == CC_DIGEST_CRC32C) + req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C; + req.data.handoff.cmdsn = cc->cc_cmdsn; + req.data.handoff.statsn = cc->cc_statsn; + req.data.handoff.max_recv_data_segment_length = + cc->cc_max_data_segment_length; + req.data.handoff.max_burst_length = cc->cc_max_burst_length; + req.data.handoff.max_outstanding_r2t = cc->cc_max_outstanding_r2t; + + if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) + log_err(1, "error issuing CTL_ISCSI ioctl; " + "dropping connection"); + + if (req.status != CTL_ISCSI_OK) + log_errx(1, "error returned from CTL iSCSI handoff request: " + "%s; dropping connection", req.error_str); +} + +int +ctld_kernel_port_on(void) +{ + struct ctl_port_entry entry; + int error; + + bzero(&entry, sizeof(&entry)); + + entry.port_type = CTL_PORT_ISCSI; + entry.targ_port = -1; + + error = ioctl(ctl_fd, CTL_ENABLE_PORT, &entry); + if (error != 0) { + log_warn("CTL_ENABLE_PORT ioctl failed"); + return (-1); + } + + return (0); +} + +int +ctld_kernel_port_off(void) +{ + struct ctl_port_entry entry; + int error; + + bzero(&entry, sizeof(&entry)); + + entry.port_type = CTL_PORT_ISCSI; + entry.targ_port = -1; + + error = ioctl(ctl_fd, CTL_DISABLE_PORT, &entry); + if (error != 0) { + log_warn("CTL_DISABLE_PORT ioctl failed"); + return (-1); + } + + return (0); +} diff -urNp freebsd/src/usr.sbin/ctld/keys.c iscsi/usr.sbin/ctld/keys.c --- freebsd/src/usr.sbin/ctld/keys.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/keys.c 2013-02-13 15:12:55.000000000 +0100 @@ -0,0 +1,215 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include "ctld.h" + +struct ctld_keys * +ctld_keys_new(void) +{ + struct ctld_keys *ck; + + ck = calloc(sizeof(*ck), 1); + if (ck == NULL) + log_err(1, "calloc"); + + return (ck); +} + +void +ctld_keys_delete(struct ctld_keys *ck) +{ + + free(ck->ck_data); + free(ck); +} + +void +ctld_keys_load(struct ctld_keys *ck, const struct ctld_pdu *cp) +{ + int i; + char *pair; + size_t pair_len; + + if (cp->cp_data_len == 0) + log_errx(1, "protocol error: empty data segment"); + + if (cp->cp_data[cp->cp_data_len - 1] != '\0') + log_errx(1, "protocol error: key not NULL-terminated\n"); + + assert(ck->ck_data == NULL); + ck->ck_data_len = cp->cp_data_len; + ck->ck_data = malloc(ck->ck_data_len); + if (ck->ck_data == NULL) + log_err(1, "malloc"); + memcpy(ck->ck_data, cp->cp_data, ck->ck_data_len); + + /* + * XXX: Review this carefully. + */ + pair = ck->ck_data; + for (i = 0;; i++) { + if (i >= CTLD_KEYS_MAX) + log_errx(1, "too many keys received"); + + pair_len = strlen(pair); + + ck->ck_values[i] = pair; + ck->ck_names[i] = strsep(&ck->ck_values[i], "="); + if (ck->ck_names[i] == NULL || ck->ck_values[i] == NULL) + log_errx(1, "malformed keys"); + log_debugx("key received: \"%s=%s\"", + ck->ck_names[i], ck->ck_values[i]); + + pair += pair_len + 1; /* +1 to skip the terminating '\0'. */ + if (pair == ck->ck_data + ck->ck_data_len) + break; + assert(pair < ck->ck_data + ck->ck_data_len); + } +} + +void +ctld_keys_save(struct ctld_keys *ck, struct ctld_pdu *cp) +{ + char *data; + size_t len; + int i; + + /* + * XXX: Not particularly efficient. + */ + len = 0; + for (i = 0; i < CTLD_KEYS_MAX; i++) { + if (ck->ck_names[i] == NULL) + break; + /* + * +1 for '=', +1 for '\0'. + */ + len += strlen(ck->ck_names[i]) + strlen(ck->ck_values[i]) + 2; + } + + if (len == 0) + return; + + data = malloc(len); + if (data == NULL) + log_err(1, "malloc"); + + cp->cp_data = data; + cp->cp_data_len = len; + + for (i = 0; i < CTLD_KEYS_MAX; i++) { + if (ck->ck_names[i] == NULL) + break; + data += + sprintf(data, "%s=%s", ck->ck_names[i], ck->ck_values[i]); + data += 1; /* for '\0'. */ + } +} + +const char * +ctld_keys_find(struct ctld_keys *ck, const char *name) +{ + int i; + + /* + * Note that we don't handle duplicated key names here, + * as they are not supposed to happen in requests, and if they do, + * it's an initiator error. + */ + for (i = 0; i < CTLD_KEYS_MAX; i++) { + if (ck->ck_names[i] == NULL) + return (NULL); + if (strcmp(ck->ck_names[i], name) == 0) + return (ck->ck_values[i]); + } + return (NULL); +} + +int +ctld_keys_find_int(struct ctld_keys *ck, const char *name) +{ + const char *str; + char *endptr; + int num; + + str = ctld_keys_find(ck, name); + if (str == NULL) + return (-1); + + num = strtoul(str, &endptr, 10); + if (*endptr != '\0') { + log_debugx("invalid numeric value \"%s\"", str); + return (-1); + } + + return (num); +} + +void +ctld_keys_add(struct ctld_keys *ck, const char *name, const char *value) +{ + int i; + + log_debugx("key to send: \"%s=%s\"", name, value); + + /* + * Note that we don't check for duplicates here, as they are perfectly + * fine in responses, e.g. the "TargetName" keys in discovery sesion + * response. + */ + for (i = 0; i < CTLD_KEYS_MAX; i++) { + if (ck->ck_names[i] == NULL) { + ck->ck_names[i] = checked_strdup(name); + ck->ck_values[i] = checked_strdup(value); + return; + } + } + log_errx(1, "too many keys"); +} + +void +ctld_keys_add_int(struct ctld_keys *ck, const char *name, int value) +{ + char *str; + int ret; + + ret = asprintf(&str, "%d", value); + if (ret <= 0) + log_err(1, "asprintf"); + + ctld_keys_add(ck, name, str); + free(str); +} diff -urNp freebsd/src/usr.sbin/ctld/log.c iscsi/usr.sbin/ctld/log.c --- freebsd/src/usr.sbin/ctld/log.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/log.c 2013-01-17 00:19:41.000000000 +0100 @@ -0,0 +1,145 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include + +#include "ctld.h" + +static int log_level = 0; + +void +log_init(int level) +{ + + log_level = level; + openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON); +} + +void +log_err(int eval, const char *fmt, ...) +{ + va_list ap, ap2; + int saved_errno; + + saved_errno = errno; + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": %s\n", strerror(saved_errno)); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_CRIT, fmt, ap2); + va_end(ap2); + + exit(eval); +} + +void +log_errx(int eval, const char *fmt, ...) +{ + va_list ap, ap2; + + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_CRIT, fmt, ap2); + va_end(ap2); + + exit(eval); +} + +void +log_warn(const char *fmt, ...) +{ + va_list ap, ap2; + int saved_errno; + + saved_errno = errno; + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": %s\n", strerror(saved_errno)); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_WARNING, fmt, ap2); + va_end(ap2); +} + +void +log_warnx(const char *fmt, ...) +{ + va_list ap, ap2; + + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_WARNING, fmt, ap2); + va_end(ap2); +} + +void +log_debugx(const char *fmt, ...) +{ + va_list ap, ap2; + + if (log_level == 0) + return; + + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_DEBUG, fmt, ap2); + va_end(ap2); +} diff -urNp freebsd/src/usr.sbin/ctld/login.c iscsi/usr.sbin/ctld/login.c --- freebsd/src/usr.sbin/ctld/login.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/login.c 2013-02-13 15:12:55.000000000 +0100 @@ -0,0 +1,1012 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctld.h" +#include "iscsi.h" + +static void ctld_login_send_error(struct ctld_pdu *request, + char class, char detail); + +static void +ctld_login_set_nsg(struct ctld_pdu *response, int nsg) +{ + struct iscsi_bhs_login_response *bhslr; + + assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION || + nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || + nsg == BHSLR_STAGE_FULL_FEATURE_PHASE); + + bhslr = (struct iscsi_bhs_login_response *)response->cp_bhs; + + bhslr->bhslr_flags &= 0xFC; + bhslr->bhslr_flags |= nsg; +} + +static int +ctld_login_csg(const struct ctld_pdu *request) +{ + struct iscsi_bhs_login_request *bhslr; + + bhslr = (struct iscsi_bhs_login_request *)request->cp_bhs; + + return ((bhslr->bhslr_flags & 0x0C) >> 2); +} + +static void +ctld_login_set_csg(struct ctld_pdu *response, int csg) +{ + struct iscsi_bhs_login_response *bhslr; + + assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION || + csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || + csg == BHSLR_STAGE_FULL_FEATURE_PHASE); + + bhslr = (struct iscsi_bhs_login_response *)response->cp_bhs; + + bhslr->bhslr_flags &= 0xF3; + bhslr->bhslr_flags |= csg << 2; +} + +static struct ctld_pdu * +ctld_login_receive(struct ctld_connection *cc, bool initial) +{ + struct ctld_pdu *request; + struct iscsi_bhs_login_request *bhslr; + + request = ctld_pdu_new(cc); + ctld_pdu_receive(request); + if ((request->cp_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != + ISCSI_BHS_OPCODE_LOGIN_REQUEST) { + /* + * The first PDU in session is special - if we receive any PDU + * different than login request, we have to drop the connection + * without sending response ("A target receiving any PDU + * except a Login request before the Login phase is started MUST + * immediately terminate the connection on which the PDU + * was received.") + */ + if (initial == false) + ctld_login_send_error(request, 0x02, 0x0b); + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->cp_bhs->bhs_opcode); + } + bhslr = (struct iscsi_bhs_login_request *)request->cp_bhs; + /* + * XXX: Implement the C flag some day. + */ + if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) { + ctld_login_send_error(request, 0x03, 0x00); + log_errx(1, "received Login PDU with unsupported \"C\" flag"); + } + if (bhslr->bhslr_version_max != 0x00) { + ctld_login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with unsupported " + "Version-max 0x%x", bhslr->bhslr_version_max); + } + if (bhslr->bhslr_version_min != 0x00) { + ctld_login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with unsupported " + "Version-min 0x%x", bhslr->bhslr_version_min); + } + if (request->cp_data_len == 0) { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received Login PDU with empty data segment"); + } + if (ntohl(bhslr->bhslr_cmdsn) < cc->cc_cmdsn) { + ctld_login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with decreasing CmdSN: " + "was %d, is %d", cc->cc_cmdsn, + ntohl(bhslr->bhslr_cmdsn)); + } + if (initial == 0 && ntohl(bhslr->bhslr_expstatsn) != cc->cc_statsn) { + ctld_login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn), + cc->cc_statsn); + } + cc->cc_cmdsn = ntohl(bhslr->bhslr_cmdsn); + + return (request); +} + +static struct ctld_pdu * +ctld_login_new_response(struct ctld_pdu *request) +{ + struct ctld_pdu *response; + struct ctld_connection *cc; + struct iscsi_bhs_login_request *bhslr; + struct iscsi_bhs_login_response *bhslr2; + + bhslr = (struct iscsi_bhs_login_request *)request->cp_bhs; + cc = request->cp_connection; + + response = ctld_pdu_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->cp_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_RESPONSE; + ctld_login_set_csg(response, BHSLR_STAGE_SECURITY_NEGOTIATION); + memcpy(bhslr2->bhslr_isid, + bhslr->bhslr_isid, sizeof(bhslr2->bhslr_isid)); + bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; + bhslr2->bhslr_statsn = htonl(cc->cc_statsn++); + bhslr2->bhslr_expcmdsn = htonl(cc->cc_cmdsn); + bhslr2->bhslr_maxcmdsn = htonl(cc->cc_cmdsn); + + return (response); +} + +static void +ctld_login_send_error(struct ctld_pdu *request, char class, char detail) +{ + struct ctld_pdu *response; + struct iscsi_bhs_login_response *bhslr2; + + log_debugx("sending Login Response PDU with failure class 0x%x/0x%x; " + "see next line for reason", class, detail); + response = ctld_login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->cp_bhs; + bhslr2->bhslr_status_class = class; + bhslr2->bhslr_status_detail = detail; + + ctld_pdu_send(response); + ctld_pdu_delete(response); +} + +static int +ctld_login_list_contains(const char *list, const char *what) +{ + char *tofree, *str, *token; + + tofree = str = checked_strdup(list); + + while ((token = strsep(&str, ",")) != NULL) { + if (strcmp(token, what) == 0) { + free(tofree); + return (1); + } + } + free(tofree); + return (0); +} + +static int +ctld_login_list_prefers(const char *list, + const char *choice1, const char *choice2) +{ + char *tofree, *str, *token; + + tofree = str = checked_strdup(list); + + while ((token = strsep(&str, ",")) != NULL) { + if (strcmp(token, choice1) == 0) { + free(tofree); + return (1); + } + if (strcmp(token, choice2) == 0) { + free(tofree); + return (2); + } + } + free(tofree); + return (-1); +} + +static int +ctld_login_hex2int(const char hex) +{ + switch (hex) { + case '0': + return (0x00); + case '1': + return (0x01); + case '2': + return (0x02); + case '3': + return (0x03); + case '4': + return (0x04); + case '5': + return (0x05); + case '6': + return (0x06); + case '7': + return (0x07); + case '8': + return (0x08); + case '9': + return (0x09); + case 'a': + case 'A': + return (0x0a); + case 'b': + case 'B': + return (0x0b); + case 'c': + case 'C': + return (0x0c); + case 'd': + case 'D': + return (0x0d); + case 'e': + case 'E': + return (0x0e); + case 'f': + case 'F': + return (0x0f); + default: + return (-1); + } +} + +/* + * XXX: Review this _carefully_. + */ +static int +ctld_login_hex2bin(const char *hex, char **binp, size_t *bin_lenp) +{ + int i, hex_len, nibble; + bool lo = true; /* As opposed to 'hi'. */ + char *bin; + size_t bin_off, bin_len; + + if (strncasecmp(hex, "0x", strlen("0x")) != 0) { + log_warnx("malformed variable, should start with \"0x\""); + return (-1); + } + + hex += strlen("0x"); + hex_len = strlen(hex); + if (hex_len < 1) { + log_warnx("malformed variable; doesn't contain anything " + "but \"0x\""); + return (-1); + } + + bin_len = hex_len / 2 + hex_len % 2; + bin = calloc(bin_len, 1); + if (bin == NULL) + log_err(1, "calloc"); + + bin_off = bin_len - 1; + for (i = hex_len - 1; i >= 0; i--) { + nibble = ctld_login_hex2int(hex[i]); + if (nibble < 0) { + log_warnx("malformed variable, invalid char \"%c\"", + hex[i]); + return (-1); + } + + assert(bin_off < bin_len); + if (lo) { + bin[bin_off] = nibble; + lo = false; + } else { + bin[bin_off] |= nibble << 4; + bin_off--; + lo = true; + } + } + + *binp = bin; + *bin_lenp = bin_len; + return (0); +} + +static char * +ctld_login_bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *hex, *tmp, ch; + size_t hex_len; + size_t i; + + hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ + hex = malloc(hex_len); + if (hex == NULL) + log_err(1, "malloc"); + + tmp = hex; + tmp += sprintf(tmp, "0x"); + for (i = 0; i < bin_len; i++) { + ch = bin[i]; + tmp += sprintf(tmp, "%02x", ch); + } + + return (hex); +} + +static void +ctld_login_compute_md5(const char chap_i, const char *secret, + const void *challenge, size_t challenge_len, void *response, + size_t response_len) +{ + MD5_CTX ctx; + int rv; + + assert(response_len == MD5_DIGEST_LENGTH); + + MD5_Init(&ctx); + MD5_Update(&ctx, &chap_i, sizeof(chap_i)); + MD5_Update(&ctx, secret, strlen(secret)); + MD5_Update(&ctx, challenge, challenge_len); + rv = MD5_Final(response, &ctx); + if (rv != 1) + log_errx(1, "MD5_Final"); +} + +#define LOGIN_CHALLENGE_LEN 1024 + +static struct ctld_pdu * +ctld_login_receive_chap_a(struct ctld_connection *cc) +{ + struct ctld_pdu *request; + struct ctld_keys *request_keys; + const char *chap_a; + + request = ctld_login_receive(cc, false); + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + + chap_a = ctld_keys_find(request_keys, "CHAP_A"); + if (chap_a == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU without CHAP_A"); + } + if (ctld_login_list_contains(chap_a, "5") == 0) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "received CHAP Login PDU with unsupported CHAP_A " + "\"%s\"", chap_a); + } + ctld_keys_delete(request_keys); + + return (request); +} + +static void +ctld_login_send_chap_c(struct ctld_pdu *request, const unsigned char id, + const void *challenge, const size_t challenge_len) +{ + struct ctld_pdu *response; + struct ctld_keys *response_keys; + char *chap_c, chap_i[4]; + + chap_c = ctld_login_bin2hex(challenge, challenge_len); + snprintf(chap_i, sizeof(chap_i), "%d", id); + + response = ctld_login_new_response(request); + response_keys = ctld_keys_new(); + ctld_keys_add(response_keys, "CHAP_A", "5"); + ctld_keys_add(response_keys, "CHAP_I", chap_i); + ctld_keys_add(response_keys, "CHAP_C", chap_c); + free(chap_c); + ctld_keys_save(response_keys, response); + ctld_keys_delete(response_keys); + ctld_pdu_send(response); +} + +static struct ctld_pdu * +ctld_login_receive_chap_r(struct ctld_connection *cc, + struct ctld_auth_group *cag, const unsigned char id, const void *challenge, + const size_t challenge_len, const struct ctld_auth **cap) +{ + struct ctld_pdu *request; + struct ctld_keys *request_keys; + const char *chap_n, *chap_r; + char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH]; + size_t response_bin_len; + const struct ctld_auth *ca; + int error; + + request = ctld_login_receive(cc, false); + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + + chap_n = ctld_keys_find(request_keys, "CHAP_N"); + if (chap_n == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU without CHAP_N"); + } + chap_r = ctld_keys_find(request_keys, "CHAP_R"); + if (chap_r == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU without CHAP_R"); + } + error = ctld_login_hex2bin(chap_r, &response_bin, &response_bin_len); + if (error != 0) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU with malformed CHAP_R"); + } + + /* + * Verify the response. + */ + assert(cag->cag_type == CAG_TYPE_CHAP || + cag->cag_type == CAG_TYPE_CHAP_MUTUAL); + ca = ctld_auth_find(cag, chap_n); + if (ca == NULL) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "received CHAP Login with invalid user \"%s\"", + chap_n); + } + + assert(ca->ca_secret != NULL); + assert(strlen(ca->ca_secret) > 0); + ctld_login_compute_md5(id, ca->ca_secret, challenge, + challenge_len, expected_response_bin, + sizeof(expected_response_bin)); + + if (memcmp(response_bin, expected_response_bin, + sizeof(expected_response_bin)) != 0) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "CHAP authentication failed for user \"%s\"", + ca->ca_user); + } + + ctld_keys_delete(request_keys); + free(response_bin); + + *cap = ca; + return (request); +} + +static void +ctld_login_send_chap_success(struct ctld_pdu *request, + const struct ctld_auth *ca) +{ + struct ctld_pdu *response; + struct ctld_keys *request_keys, *response_keys; + struct iscsi_bhs_login_response *bhslr2; + const char *chap_i, *chap_c; + char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH]; + size_t challenge_len; + unsigned char id; + int error; + + response = ctld_login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->cp_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + ctld_login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + + /* + * Actually, one more thing: mutual authentication. + */ + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + chap_i = ctld_keys_find(request_keys, "CHAP_I"); + chap_c = ctld_keys_find(request_keys, "CHAP_C"); + if (chap_i != NULL || chap_c != NULL) { + if (chap_i == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "initiator requested target " + "authentication, but didn't send CHAP_I"); + } + if (chap_c == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "initiator requested target " + "authentication, but didn't send CHAP_C"); + } + if (ca->ca_auth_group->cag_type != CAG_TYPE_CHAP_MUTUAL) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "initiator requests target authentication " + "for user \"%s\", but mutual user/secret " + "is not set", ca->ca_user); + } + + id = strtoul(chap_i, NULL, 10); + error = ctld_login_hex2bin(chap_c, &challenge, &challenge_len); + if (error != 0) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU with malformed " + "CHAP_C"); + } + + log_debugx("performing mutual authentication as user \"%s\"", + ca->ca_mutual_user); + ctld_login_compute_md5(id, ca->ca_mutual_secret, challenge, + challenge_len, response_bin, sizeof(response_bin)); + + chap_r = ctld_login_bin2hex(response_bin, + sizeof(response_bin)); + response_keys = ctld_keys_new(); + ctld_keys_add(response_keys, "CHAP_N", ca->ca_mutual_user); + ctld_keys_add(response_keys, "CHAP_R", chap_r); + free(chap_r); + ctld_keys_save(response_keys, response); + ctld_keys_delete(response_keys); + } else { + log_debugx("initiator did not request target authentication"); + } + + ctld_keys_delete(request_keys); + ctld_pdu_send(response); +} + +static void +ctld_login_chap(struct ctld_connection *cc, struct ctld_auth_group *cag) +{ + const struct ctld_auth *ca; + struct ctld_pdu *request; + char challenge_bin[LOGIN_CHALLENGE_LEN]; + unsigned char id; + int rv; + + /* + * Receive CHAP_A PDU. + */ + log_debugx("beginning CHAP authentication; waiting for CHAP_A"); + request = ctld_login_receive_chap_a(cc); + + /* + * Generate the challenge. + */ + rv = RAND_bytes(challenge_bin, sizeof(challenge_bin)); + if (rv != 1) { + ctld_login_send_error(request, 0x03, 0x02); + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + rv = RAND_bytes(&id, sizeof(id)); + if (rv != 1) { + ctld_login_send_error(request, 0x03, 0x02); + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + /* + * Send the challenge. + */ + log_debugx("sending CHAP_C, binary challenge size is %zd bytes", + sizeof(challenge_bin)); + ctld_login_send_chap_c(request, id, challenge_bin, + sizeof(challenge_bin)); + ctld_pdu_delete(request); + + /* + * Receive CHAP_N/CHAP_R PDU and authenticate. + */ + log_debugx("waiting for CHAP_N/CHAP_R"); + request = ctld_login_receive_chap_r(cc, cag, id, challenge_bin, + sizeof(challenge_bin), &ca); + + /* + * Yay, authentication succeeded! + */ + log_debugx("authentication succeeded for user \"%s\"; " + "transitioning to Negotiation phase", ca->ca_user); + ctld_login_send_chap_success(request, ca); + ctld_pdu_delete(request); +} + +static void +ctld_login_negotiate_key(struct ctld_pdu *request, const char *name, + const char *value, bool skipped_security, struct ctld_keys *response_keys) +{ + int which, tmp; + struct ctld_connection *cc; + + cc = request->cp_connection; + + if (strcmp(name, "InitiatorName") == 0) { + if (!skipped_security) + log_errx(1, "initiator resent InitiatorName"); + } else if (strcmp(name, "SessionType") == 0) { + if (!skipped_security) + log_errx(1, "initiator resent SessionType"); + } else if (strcmp(name, "TargetName") == 0) { + if (!skipped_security) + log_errx(1, "initiator resent TargetName"); + } else if (strcmp(name, "InitiatorAlias") == 0) { + /* Ignore. */ + } else if (strcmp(name, "HeaderDigest") == 0) { + /* + * We don't handle digests for discovery sessions. + */ + if (cc->cc_session_type == CC_SESSION_TYPE_DISCOVERY) { + log_debugx("discovery session; digests disabled"); + ctld_keys_add(response_keys, name, "None"); + return; + } + + which = ctld_login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("initiator prefers CRC32C " + "for header digest; we'll use it"); + cc->cc_header_digest = CC_DIGEST_CRC32C; + ctld_keys_add(response_keys, name, "CRC32C"); + break; + case 2: + log_debugx("initiator prefers not to do " + "header digest; we'll comply"); + ctld_keys_add(response_keys, name, "None"); + break; + default: + log_warnx("initiator sent unrecognized " + "HeaderDigest value \"%s\"; will use None", value); + ctld_keys_add(response_keys, name, "None"); + break; + } + } else if (strcmp(name, "DataDigest") == 0) { + if (cc->cc_session_type == CC_SESSION_TYPE_DISCOVERY) { + log_debugx("discovery session; digests disabled"); + ctld_keys_add(response_keys, name, "None"); + return; + } + + which = ctld_login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("initiator prefers CRC32C " + "for data digest; we'll use it"); + cc->cc_data_digest = CC_DIGEST_CRC32C; + ctld_keys_add(response_keys, name, "CRC32C"); + break; + case 2: + log_debugx("initiator prefers not to do " + "data digest; we'll comply"); + ctld_keys_add(response_keys, name, "None"); + break; + default: + log_warnx("initiator sent unrecognized " + "DataDigest value \"%s\"; will use None", value); + ctld_keys_add(response_keys, name, "None"); + break; + } + } else if (strcmp(name, "MaxConnections") == 0) { + ctld_keys_add(response_keys, name, "1"); + } else if (strcmp(name, "InitialR2T") == 0) { + ctld_keys_add(response_keys, name, "Yes"); + } else if (strcmp(name, "ImmediateData") == 0) { + ctld_keys_add(response_keys, name, "No"); + } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received invalid " + "MaxRecvDataSegmentLength"); + } + if (tmp > CTLD_MAX_DATA_SEGMENT_LENGTH) { + log_debugx("capping MaxDataSegmentLength from %d to %d", + tmp, CTLD_MAX_DATA_SEGMENT_LENGTH); + tmp = CTLD_MAX_DATA_SEGMENT_LENGTH; + } + cc->cc_max_data_segment_length = tmp; + ctld_keys_add_int(response_keys, name, tmp); + } else if (strcmp(name, "MaxBurstLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received invalid MaxBurstLength"); + } + cc->cc_max_burst_length = tmp; + ctld_keys_add(response_keys, name, value); + } else if (strcmp(name, "FirstBurstLength") == 0) { + ctld_keys_add(response_keys, name, "Irrelevant"); + } else if (strcmp(name, "DefaultTime2Wait") == 0) { + ctld_keys_add(response_keys, name, value); + } else if (strcmp(name, "DefaultTime2Retain") == 0) { + ctld_keys_add(response_keys, name, "0"); + } else if (strcmp(name, "MaxOutstandingR2T") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received invalid MaxOutstandingR2T"); + } + cc->cc_max_outstanding_r2t = tmp; + ctld_keys_add(response_keys, name, value); + } else if (strcmp(name, "DataPDUInOrder") == 0) { + ctld_keys_add(response_keys, name, "Yes"); + } else if (strcmp(name, "DataSequenceInOrder") == 0) { + ctld_keys_add(response_keys, name, "Yes"); + } else if (strcmp(name, "ErrorRecoveryLevel") == 0) { + ctld_keys_add(response_keys, name, "0"); + } else if (strcmp(name, "OFMarker") == 0) { + ctld_keys_add(response_keys, name, "No"); + } else if (strcmp(name, "IFMarker") == 0) { + ctld_keys_add(response_keys, name, "No"); + } else { + log_debugx("unknown key \"%s\"; responding " + "with NotUnderstood", name); + ctld_keys_add(response_keys, name, "NotUnderstood"); + } +} + +static void +ctld_login_negotiate(struct ctld_connection *cc, struct ctld_pdu *request) +{ + struct ctld_pdu *response; + struct iscsi_bhs_login_response *bhslr2; + struct ctld_keys *request_keys, *response_keys; + int i; + bool skipped_security; + + if (request == NULL) { + log_debugx("beginning parameter negotiation; " + "waiting for Login PDU"); + request = ctld_login_receive(cc, false); + skipped_security = false; + } else + skipped_security = true; + + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + + response = ctld_login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->cp_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + bhslr2->bhslr_tsih = htons(0xbadd); + ctld_login_set_csg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + ctld_login_set_nsg(response, BHSLR_STAGE_FULL_FEATURE_PHASE); + response_keys = ctld_keys_new(); + for (i = 0; i < CTLD_KEYS_MAX; i++) { + if (request_keys->ck_names[i] == NULL) + break; + + ctld_login_negotiate_key(request, request_keys->ck_names[i], + request_keys->ck_values[i], skipped_security, + response_keys); + } + + log_debugx("parameter negotiation done; " + "transitioning to Full Feature phase"); + + ctld_keys_save(response_keys, response); + ctld_pdu_send(response); + ctld_pdu_delete(response); + ctld_keys_delete(response_keys); + ctld_pdu_delete(request); + ctld_keys_delete(request_keys); +} + +void +ctld_login(struct ctld_connection *cc) +{ + struct ctld_pdu *request, *response; + struct iscsi_bhs_login_request *bhslr; + struct iscsi_bhs_login_response *bhslr2; + struct ctld_keys *request_keys, *response_keys; + struct ctld_auth_group *cag; + const char *initiator_name, *session_type, *target_name, *auth_method; + char *portal_group_tag; + int rv; + + /* + * Handle the initial Login Request - figure out required authentication + * method and either transition to the next phase, if no authentication + * is required, or call appropriate authentication code. + */ + log_debugx("beginning Login phase; waiting for Login PDU"); + request = ctld_login_receive(cc, true); + bhslr = (struct iscsi_bhs_login_request *)request->cp_bhs; + if (bhslr->bhslr_tsih != 0) { + ctld_login_send_error(request, 0x02, 0x0a); + log_errx(1, "received Login PDU with non-zero TSIH"); + } + + /* + * XXX: Implement the C flag some day. + */ + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + + assert(cc->cc_initiator_name == NULL); + initiator_name = ctld_keys_find(request_keys, "InitiatorName"); + if (initiator_name == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received Login PDU without InitiatorName"); + } + if (ctld_valid_iscsi_name(initiator_name) == false) { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received Login PDU with invalid InitiatorName"); + } + cc->cc_initiator_name = checked_strdup(initiator_name); + + assert(cc->cc_session_type == CC_SESSION_TYPE_NONE); + session_type = ctld_keys_find(request_keys, "SessionType"); + if (session_type != NULL) { + if (strcmp(session_type, "Normal") == 0) { + cc->cc_session_type = CC_SESSION_TYPE_NORMAL; + } else if (strcmp(session_type, "Discovery") == 0) { + cc->cc_session_type = CC_SESSION_TYPE_DISCOVERY; + } else { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received Login PDU with invalid " + "SessionType \"%s\"", session_type); + } + } else + cc->cc_session_type = CC_SESSION_TYPE_NORMAL; + + assert(cc->cc_target == NULL); + if (cc->cc_session_type == CC_SESSION_TYPE_NORMAL) { + target_name = ctld_keys_find(request_keys, "TargetName"); + if (target_name == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received Login PDU without TargetName"); + } + + cc->cc_target = + ctld_target_find(cc->cc_portal->cp_portal_group->cpg_conf, + target_name); + if (cc->cc_target == NULL) { + ctld_login_send_error(request, 0x02, 0x03); + log_errx(1, "requested target \"%s\" not found", + target_name); + } + } + + /* + * At this point we know what kind of authentication we need. + */ + if (cc->cc_session_type == CC_SESSION_TYPE_NORMAL) { + cag = cc->cc_target->ct_auth_group; + if (cag->cag_name != NULL) { + log_debugx("initiator \"%s\" requests to connect " + "to target \"%s\"; auth-group \"%s\"", + cc->cc_initiator_name, cc->cc_target->ct_iqn, + cc->cc_target->ct_auth_group->cag_name); + } else { + log_debugx("initiator \"%s\" requests to connect " + "to target \"%s\"", + cc->cc_initiator_name, cc->cc_target->ct_iqn); + } + } else { + assert(cc->cc_session_type == CC_SESSION_TYPE_DISCOVERY); + cag = cc->cc_portal->cp_portal_group->cpg_discovery_auth_group; + if (cag->cag_name != NULL) { + log_debugx("initiator \"%s\" requests " + "discovery session; auth-group \"%s\"", + cc->cc_initiator_name, cag->cag_name); + } else { + log_debugx("initiator \"%s\" requests " + "discovery session", cc->cc_initiator_name); + } + } + + /* + * Let's see if the initiator intends to do any kind of authentication + * at all. + */ + if (ctld_login_csg(request) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { + if (cag->cag_type != CAG_TYPE_NO_AUTHENTICATION) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "initiator skipped the authentication " + "phase, but authentication is required"); + } + + ctld_keys_delete(request_keys); + + log_debugx("initiator skipped the authentication phase, " + "and we don't need it; proceeding with negotiation"); + ctld_login_negotiate(cc, request); + return; + } + + if (cag->cag_type == CAG_TYPE_NO_AUTHENTICATION) { + /* + * Initiator might want to to authenticate, + * but we don't need it. + */ + log_debugx("authentication not required; " + "transitioning to Negotiation phase"); + + if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) + log_warnx("initiator did not set the \"T\" flag; " + "transitioning anyway"); + + response = ctld_login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->cp_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + ctld_login_set_nsg(response, + BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + response_keys = ctld_keys_new(); + if (cc->cc_session_type == CC_SESSION_TYPE_NORMAL) { + rv = asprintf(&portal_group_tag, "%d", + cc->cc_portal->cp_portal_group->cpg_tag); + if (rv <= 0) + log_err(1, "asprintf"); + ctld_keys_add(response_keys, + "TargetPortalGroupTag", portal_group_tag); + free(portal_group_tag); + if (cc->cc_target->ct_alias != NULL) + ctld_keys_add(response_keys, + "TargetAlias", cc->cc_target->ct_alias); + } + /* + * Required by Linux initiator. + */ + auth_method = ctld_keys_find(request_keys, "AuthMethod"); + if (auth_method != NULL && + ctld_login_list_contains(auth_method, "None")) + ctld_keys_add(response_keys, "AuthMethod", "None"); + + ctld_keys_save(response_keys, response); + ctld_pdu_send(response); + ctld_pdu_delete(response); + ctld_keys_delete(response_keys); + ctld_pdu_delete(request); + ctld_keys_delete(request_keys); + + ctld_login_negotiate(cc, NULL); + return; + } + + auth_method = ctld_keys_find(request_keys, "AuthMethod"); + if (auth_method == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received Login PDU without AuthMethod"); + } + /* + * XXX: This should be Reject, not just a login failure (5.3.2). + */ + if (ctld_login_list_contains(auth_method, "CHAP") == 0) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "initiator requests unsupported AuthMethod \"%s\" " + "instead of \"CHAP\"", auth_method); + } + + log_debugx("CHAP authentication required"); + + response = ctld_login_new_response(request); + + response_keys = ctld_keys_new(); + ctld_keys_add(response_keys, "AuthMethod", "CHAP"); + if (cc->cc_session_type == CC_SESSION_TYPE_NORMAL) { + rv = asprintf(&portal_group_tag, "%d", + cc->cc_portal->cp_portal_group->cpg_tag); + if (rv <= 0) + log_err(1, "asprintf"); + ctld_keys_add(response_keys, + "TargetPortalGroupTag", portal_group_tag); + free(portal_group_tag); + if (cc->cc_target->ct_alias != NULL) + ctld_keys_add(response_keys, + "TargetAlias", cc->cc_target->ct_alias); + } + ctld_keys_save(response_keys, response); + + ctld_pdu_send(response); + ctld_pdu_delete(response); + ctld_keys_delete(response_keys); + ctld_pdu_delete(request); + ctld_keys_delete(request_keys); + + ctld_login_chap(cc, cag); + + ctld_login_negotiate(cc, NULL); +} diff -urNp freebsd/src/usr.sbin/ctld/parse.y iscsi/usr.sbin/ctld/parse.y --- freebsd/src/usr.sbin/ctld/parse.y 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/parse.y 2013-02-13 20:36:17.000000000 +0100 @@ -0,0 +1,593 @@ +%{ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ctld.h" + +extern FILE *yyin; +extern char *yytext; +extern int lineno; + +static struct ctld_conf *conf = NULL; +static struct ctld_auth_group *auth_group = NULL; +static struct ctld_portal_group *portal_group = NULL; +static struct ctld_target *target = NULL; +static struct ctld_lun *lun = NULL; + +extern void yyrestart(FILE *); + +%} + +%token ALIAS AUTH_GROUP BACKEND BLOCKSIZE CHAP CHAP_MUTUAL CLOSING_BRACKET +%token DEBUG DEVICE_ID DISCOVERY_AUTH_GROUP LISTEN LUN NUM OPENING_BRACKET +%token OPTION PATH PIDFILE PORTAL_GROUP SERIAL SIZE STR TARGET + +%union +{ + uint64_t num; + char *str; +} + +%token NUM +%token STR + +%% + +statements: + | + statements statement + ; + +statement: + debug_statement + | + pidfile_statement + | + auth_group_definition + | + portal_group_definition + | + target_statement + ; + +debug_statement: DEBUG NUM + { + conf->cc_debug = $2; + } + ; + +pidfile_statement: PIDFILE STR + { + if (conf->cc_pidfile_path != NULL) { + log_warnx("pidfile specified more than once"); + free($2); + return (1); + } + conf->cc_pidfile_path = $2; + } + ; + +auth_group_definition: AUTH_GROUP auth_group_name + OPENING_BRACKET auth_group_entries CLOSING_BRACKET + { + auth_group = NULL; + } + ; + +auth_group_name: STR + { + auth_group = ctld_auth_group_new(conf, $1); + free($1); + if (auth_group == NULL) + return (1); + } + ; + +auth_group_entries: + | + auth_group_entries auth_group_entry + ; + +auth_group_entry: + auth_group_chap + | + auth_group_chap_mutual + ; + +auth_group_chap: CHAP STR STR + { + const struct ctld_auth *ca; + + ca = ctld_auth_new_chap(auth_group, $2, $3); + free($2); + free($3); + if (ca == NULL) + return (1); + } + ; + +auth_group_chap_mutual: CHAP_MUTUAL STR STR STR STR + { + const struct ctld_auth *ca; + + ca = ctld_auth_new_chap_mutual(auth_group, $2, $3, $4, $5); + free($2); + free($3); + free($4); + free($5); + if (ca == NULL) + return (1); + } + ; + +portal_group_definition: PORTAL_GROUP portal_group_name + OPENING_BRACKET portal_group_entries CLOSING_BRACKET + { + portal_group = NULL; + } + ; + +portal_group_name: STR + { + portal_group = ctld_portal_group_new(conf, $1); + free($1); + if (portal_group == NULL) + return (1); + } + ; + +portal_group_entries: + | + portal_group_entries portal_group_entry + ; + +portal_group_entry: + portal_group_discovery_auth_group + | + portal_group_listen + ; + +portal_group_discovery_auth_group: DISCOVERY_AUTH_GROUP STR + { + if (portal_group->cpg_discovery_auth_group != NULL) { + log_warnx("discovery-auth-group for portal-group " + "\"%s\" specified more than once", + portal_group->cpg_name); + return (1); + } + portal_group->cpg_discovery_auth_group = + ctld_auth_group_find(conf, $2); + if (portal_group->cpg_discovery_auth_group == NULL) { + log_warnx("unknown discovery-auth-group \"%s\" " + "for portal-group \"%s\"", + $2, portal_group->cpg_name); + return (1); + } + free($2); + } + ; + +portal_group_listen: LISTEN STR + { + int error; + + error = ctld_portal_group_add_listen(portal_group, $2); + free($2); + if (error != 0) + return (1); + } + ; + +target_statement: TARGET target_iqn + OPENING_BRACKET target_entries CLOSING_BRACKET + { + target = NULL; + } + ; + +target_iqn: STR + { + target = ctld_target_new(conf, $1); + free($1); + if (target == NULL) + return (1); + } + ; + +target_entries: + | + target_entries target_entry + ; + +target_entry: + alias_statement + | + auth_group_statement + | + chap_statement + | + chap_mutual_statement + | + portal_group_statement + | + lun_statement + ; + +alias_statement: ALIAS STR + { + if (target->ct_alias != NULL) { + log_warnx("alias for target \"%s\" " + "specified more than once", target->ct_iqn); + return (1); + } + target->ct_alias = $2; + } + ; + +auth_group_statement: AUTH_GROUP STR + { + if (target->ct_auth_group != NULL) { + if (target->ct_auth_group->cag_name != NULL) + log_warnx("auth-group for target \"%s\" " + "specified more than once", target->ct_iqn); + else + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->ct_iqn); + return (1); + } + target->ct_auth_group = ctld_auth_group_find(conf, $2); + if (target->ct_auth_group == NULL) { + log_warnx("unknown auth-group \"%s\" for target " + "\"%s\"", $2, target->ct_iqn); + return (1); + } + free($2); + } + ; + +chap_statement: CHAP STR STR + { + const struct ctld_auth *ca; + + if (target->ct_auth_group != NULL) { + if (target->ct_auth_group->cag_name != NULL) { + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->ct_iqn); + free($2); + free($3); + return (1); + } + } else { + target->ct_auth_group = ctld_auth_group_new(conf, NULL); + if (target->ct_auth_group == NULL) { + free($2); + free($3); + return (1); + } + target->ct_auth_group->cag_target = target; + } + ca = ctld_auth_new_chap(target->ct_auth_group, $2, $3); + free($2); + free($3); + if (ca == NULL) + return (1); + } + ; + +chap_mutual_statement: CHAP_MUTUAL STR STR STR STR + { + const struct ctld_auth *ca; + + if (target->ct_auth_group != NULL) { + if (target->ct_auth_group->cag_name != NULL) { + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->ct_iqn); + free($2); + free($3); + free($4); + free($5); + return (1); + } + } else { + target->ct_auth_group = ctld_auth_group_new(conf, NULL); + if (target->ct_auth_group == NULL) { + free($2); + free($3); + free($4); + free($5); + return (1); + } + target->ct_auth_group->cag_target = target; + } + ca = ctld_auth_new_chap_mutual(target->ct_auth_group, + $2, $3, $4, $5); + free($2); + free($3); + free($4); + free($5); + if (ca == NULL) + return (1); + } + ; + +portal_group_statement: PORTAL_GROUP STR + { + if (target->ct_portal_group != NULL) { + log_warnx("portal-group for target \"%s\" " + "specified more than once", target->ct_iqn); + free($2); + return (1); + } + target->ct_portal_group = ctld_portal_group_find(conf, $2); + if (target->ct_portal_group == NULL) { + log_warnx("unknown portal-group \"%s\" for target " + "\"%s\"", $2, target->ct_iqn); + free($2); + return (1); + } + free($2); + } + ; + +lun_statement: LUN lun_number + OPENING_BRACKET lun_statement_entries CLOSING_BRACKET + { + lun = NULL; + } + ; + +lun_number: NUM + { + lun = ctld_lun_new(target, $1); + if (lun == NULL) + return (1); + } + ; + +lun_statement_entries: + | + lun_statement_entries lun_statement_entry + ; + +lun_statement_entry: + backend_statement + | + blocksize_statement + | + device_id_statement + | + option_statement + | + path_statement + | + serial_statement + | + size_statement + ; + +backend_statement: BACKEND STR + { + if (lun->cl_backend != NULL) { + log_warnx("backend for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + free($2); + return (1); + } + ctld_lun_set_backend(lun, $2); + free($2); + } + ; + +blocksize_statement: BLOCKSIZE NUM + { + if (lun->cl_blocksize != 0) { + log_warnx("blocksize for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + return (1); + } + ctld_lun_set_blocksize(lun, $2); + } + ; + +device_id_statement: DEVICE_ID STR + { + if (lun->cl_device_id != NULL) { + log_warnx("device_id for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + free($2); + return (1); + } + ctld_lun_set_device_id(lun, $2); + free($2); + } + ; + +option_statement: OPTION STR STR + { + struct ctld_lun_option *clo; + + clo = ctld_lun_option_new(lun, $2, $3); + free($2); + free($3); + if (clo == NULL) + return (1); + } + ; + +path_statement: PATH STR + { + if (lun->cl_path != NULL) { + log_warnx("path for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + free($2); + return (1); + } + ctld_lun_set_path(lun, $2); + free($2); + } + ; + +serial_statement: SERIAL STR + { + if (lun->cl_serial != NULL) { + log_warnx("serial for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + free($2); + return (1); + } + ctld_lun_set_serial(lun, $2); + free($2); + } + ; + +size_statement: SIZE NUM + { + if (lun->cl_size != 0) { + log_warnx("size for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + return (1); + } + ctld_lun_set_size(lun, $2); + } + ; +%% + +void +yyerror(const char *str) +{ + + log_warnx("error in configuration file at line %d near '%s': %s", + lineno, yytext, str); +} + +static void +ctld_check_perms(const char *path) +{ + struct stat sb; + int error; + + error = stat(path, &sb); + if (error != 0) { + log_warn("stat"); + return; + } + if (sb.st_mode & S_IWOTH) { + log_warnx("%s is world-writable", path); + } else if (sb.st_mode & S_IROTH) { + log_warnx("%s is world-readable", path); + } else if (sb.st_mode & S_IXOTH) { + /* + * Ok, this one doesn't matter, but still do it, + * just for consistency. + */ + log_warnx("%s is world-executable", path); + } + + /* + * XXX: Should we also check for owner != 0? + */ +} + +struct ctld_conf * +ctld_conf_new_from_file(const char *path) +{ + struct ctld_conf *cc; + struct ctld_auth_group *cag; + struct ctld_portal_group *cpg; + int error; + + log_debugx("obtaining configuration from %s", path); + + cc = ctld_conf_new(); + + cag = ctld_auth_group_new(cc, "no-authentication"); + cag->cag_type = CAG_TYPE_NO_AUTHENTICATION; + + /* + * Here, the type doesn't really matter, as the group doesn't contain + * any entries and thus will always deny access. + */ + cag = ctld_auth_group_new(cc, "no-access"); + cag->cag_type = CAG_TYPE_CHAP; + + cpg = ctld_portal_group_new(cc, "default"); + ctld_portal_group_add_listen(cpg, "0.0.0.0:3260"); + ctld_portal_group_add_listen(cpg, "[::]:3260"); + + ctld_check_perms(path); + + yyin = fopen(path, "r"); + if (yyin == NULL) { + log_warn("unable to open configuration file %s", path); + ctld_conf_delete(cc); + return (NULL); + } + lineno = 0; + conf = cc; + yyrestart(yyin); + error = yyparse(); + conf = NULL; + auth_group = NULL; + portal_group = NULL; + target = NULL; + lun = NULL; + fclose(yyin); + if (error != 0) { + ctld_conf_delete(cc); + return (NULL); + } + + error = ctld_conf_verify(cc); + if (error != 0) { + ctld_conf_delete(cc); + return (NULL); + } + + return (cc); +} diff -urNp freebsd/src/usr.sbin/ctld/pdu.c iscsi/usr.sbin/ctld/pdu.c --- freebsd/src/usr.sbin/ctld/pdu.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/pdu.c 2013-01-28 21:03:00.000000000 +0100 @@ -0,0 +1,206 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ctld.h" +#include "iscsi.h" + +static int +ctld_pdu_ahs_length(const struct ctld_pdu *request) +{ + + return (request->cp_bhs->bhs_total_ahs_len * 4); +} + +static int +ctld_pdu_data_segment_length(const struct ctld_pdu *request) +{ + uint32_t len = 0; + + len += request->cp_bhs->bhs_data_segment_len[0]; + len <<= 8; + len += request->cp_bhs->bhs_data_segment_len[1]; + len <<= 8; + len += request->cp_bhs->bhs_data_segment_len[2]; + + return (len); +} + +static void +ctld_pdu_set_data_segment_length(struct ctld_pdu *response, uint32_t len) +{ + + response->cp_bhs->bhs_data_segment_len[2] = len; + response->cp_bhs->bhs_data_segment_len[1] = len >> 8; + response->cp_bhs->bhs_data_segment_len[0] = len >> 16; +} + +struct ctld_pdu * +ctld_pdu_new(struct ctld_connection *cc) +{ + struct ctld_pdu *cp; + + cp = calloc(sizeof(*cp), 1); + if (cp == NULL) + log_err(1, "calloc"); + + cp->cp_bhs = calloc(sizeof(*cp->cp_bhs), 1); + if (cp->cp_bhs == NULL) + log_err(1, "calloc"); + + cp->cp_connection = cc; + + return (cp); +} + +struct ctld_pdu * +ctld_pdu_new_response(struct ctld_pdu *request) +{ + + return (ctld_pdu_new(request->cp_connection)); +} + +static size_t +ctld_pdu_padding(const struct ctld_pdu *cp) +{ + + if ((cp->cp_data_len % 4) != 0) + return (4 - (cp->cp_data_len % 4)); + + return (0); +} + +static void +ctld_pdu_read(int fd, char *data, size_t len) +{ + ssize_t ret; + + while (len > 0) { + ret = read(fd, data, len); + if (ret < 0) + log_err(1, "read"); + else if (ret == 0) + log_errx(1, "read: connection lost"); + len -= ret; + data += ret; + } +} + +void +ctld_pdu_receive(struct ctld_pdu *request) +{ + size_t len, padding; + char dummy[4]; + + ctld_pdu_read(request->cp_connection->cc_socket, + (char *)request->cp_bhs, sizeof(*request->cp_bhs)); + + len = ctld_pdu_ahs_length(request); + if (len > 0) + log_errx(1, "protocol error: non-empty AHS"); + + len = ctld_pdu_data_segment_length(request); + if (len > 0) { + if (len > request->cp_connection->cc_max_data_segment_length) + log_errx(1, "protocol error: received PDU " + "with DataSegmentLength exceeding %zd", + request->cp_connection->cc_max_data_segment_length); + + request->cp_data_len = len; + request->cp_data = malloc(len); + if (request->cp_data == NULL) + log_err(1, "malloc"); + + ctld_pdu_read(request->cp_connection->cc_socket, + (char *)request->cp_data, request->cp_data_len); + + padding = ctld_pdu_padding(request); + if (padding != 0) { + assert(padding < sizeof(dummy)); + ctld_pdu_read(request->cp_connection->cc_socket, + (char *)dummy, padding); + } + } +} + +void +ctld_pdu_send(struct ctld_pdu *response) +{ + ssize_t ret, total_len; + size_t padding; + uint32_t zero = 0; + struct iovec iov[3]; + int iovcnt; + + ctld_pdu_set_data_segment_length(response, response->cp_data_len); + iov[0].iov_base = response->cp_bhs; + iov[0].iov_len = sizeof(*response->cp_bhs); + total_len = iov[0].iov_len; + iovcnt = 1; + + if (response->cp_data_len > 0) { + iov[1].iov_base = response->cp_data; + iov[1].iov_len = response->cp_data_len; + total_len += iov[1].iov_len; + iovcnt = 2; + + padding = ctld_pdu_padding(response); + if (padding > 0) { + assert(padding < sizeof(zero)); + iov[2].iov_base = &zero; + iov[2].iov_len = padding; + total_len += iov[2].iov_len; + iovcnt = 3; + } + } + + ret = writev(response->cp_connection->cc_socket, iov, iovcnt); + if (ret < 0) + log_err(1, "writev"); + if (ret != total_len) + log_errx(1, "short write"); +} + +void +ctld_pdu_delete(struct ctld_pdu *cp) +{ + + free(cp->cp_data); + free(cp->cp_bhs); + free(cp); +} diff -urNp freebsd/src/usr.sbin/ctld/token.l iscsi/usr.sbin/ctld/token.l --- freebsd/src/usr.sbin/ctld/token.l 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/token.l 2013-01-25 10:30:40.000000000 +0100 @@ -0,0 +1,80 @@ +%{ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * 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. + * + * $FreeBSD$ + */ + +#include +#include +#include + +#include "ctld.h" +#include "y.tab.h" + +int depth; +int lineno; + +%} + +%option noinput +%option nounput + +%% +alias { return ALIAS; } +auth-group { return AUTH_GROUP; } +backend { return BACKEND; } +blocksize { return BLOCKSIZE; } +chap { return CHAP; } +chap-mutual { return CHAP_MUTUAL; } +debug { return DEBUG; } +device-id { return DEVICE_ID; } +discovery-auth-group { return DISCOVERY_AUTH_GROUP; } +listen { return LISTEN; } +lun { return LUN; } +option { return OPTION; } +path { return PATH; } +pidfile { return PIDFILE; } +portal-group { return PORTAL_GROUP; } +serial { return SERIAL; } +size { return SIZE; } +target { return TARGET; } +[0-9]+[kKmMgGtTpPeE]? { if (expand_number(yytext, &yylval.num) == 0) + return NUM; + else + return STR; + } +\"[^"]+\" { yylval.str = strndup(yytext + 1, + strlen(yytext) - 2); return STR; } +[a-zA-Z0-9\.\-_/\:\[\]]+ { yylval.str = strdup(yytext); return STR; } +\{ { depth++; return OPENING_BRACKET; } +\} { depth--; return CLOSING_BRACKET; } +#.*$ /* ignore comments */; +\n { lineno++; } +[ \t]+ /* ignore whitespace */; +%%