diff -urNp p4/freebsd/src/etc/defaults/rc.conf p4/iscsi/etc/defaults/rc.conf --- p4/freebsd/src/etc/defaults/rc.conf 2013-07-24 07:52:00.000000000 +0200 +++ p4/iscsi/etc/defaults/rc.conf 2013-07-25 19:17:06.000000000 +0200 @@ -263,9 +263,13 @@ syslogd_flags="-s" # Flags to syslogd ( inetd_enable="NO" # Run the network daemon dispatcher (YES/NO). inetd_program="/usr/sbin/inetd" # path to inetd, if you want a different one. inetd_flags="-wW -C 60" # Optional flags to inetd +iscsid_enable="NO" # iSCSI initiator daemon. +iscsictl_enable="NO" # iSCSI initiator autostart. +iscsictl_flags="-Aa" # Optional flags to iscsictl. 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 / iSCSI target daemon. # # named. It may be possible to run named in a sandbox, man security for # details. diff -urNp p4/freebsd/src/etc/rc.d/Makefile p4/iscsi/etc/rc.d/Makefile --- p4/freebsd/src/etc/rc.d/Makefile 2013-07-24 07:52:00.000000000 +0200 +++ p4/iscsi/etc/rc.d/Makefile 2013-07-25 19:17:06.000000000 +0200 @@ -30,6 +30,7 @@ FILES= DAEMON \ cleanvar \ cleartmp \ cron \ + ctld \ ddb \ defaultroute \ devd \ @@ -62,6 +63,8 @@ FILES= DAEMON \ ipnat \ ipsec \ ${_ipxrouted} \ + iscsictl \ + iscsid \ jail \ kadmind \ kerberos \ diff -urNp p4/freebsd/src/etc/rc.d/ctld p4/iscsi/etc/rc.d/ctld --- p4/freebsd/src/etc/rc.d/ctld 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/etc/rc.d/ctld 2013-07-23 23:41:34.000000000 +0200 @@ -0,0 +1,22 @@ +#!/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" +required_modules="ctl" +extra_commands="reload" + +load_rc_config $name +run_rc_command "$1" diff -urNp p4/freebsd/src/etc/rc.d/iscsictl p4/iscsi/etc/rc.d/iscsictl --- p4/freebsd/src/etc/rc.d/iscsictl 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/etc/rc.d/iscsictl 2013-07-23 23:41:34.000000000 +0200 @@ -0,0 +1,20 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: iscsictl +# REQUIRE: NETWORK iscsid +# BEFORE: DAEMON +# KEYWORD: nojail + +. /etc/rc.subr + +name="iscsictl" +rcvar="iscsictl_enable" +command="/usr/bin/${name}" +command_args="${iscsictl_flags}" +required_modules="iscsi" + +load_rc_config $name +run_rc_command "$1" diff -urNp p4/freebsd/src/etc/rc.d/iscsid p4/iscsi/etc/rc.d/iscsid --- p4/freebsd/src/etc/rc.d/iscsid 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/etc/rc.d/iscsid 2013-07-23 23:41:34.000000000 +0200 @@ -0,0 +1,20 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: iscsid +# REQUIRE: NETWORK +# BEFORE: DAEMON +# KEYWORD: nojail + +. /etc/rc.subr + +name="iscsid" +rcvar="iscsid_enable" +pidfile="/var/run/${name}.pid" +command="/usr/sbin/${name}" +required_modules="iscsi" + +load_rc_config $name +run_rc_command "$1" diff -urNp p4/freebsd/src/sbin/iscontrol/iscontrol.8 p4/iscsi/sbin/iscontrol/iscontrol.8 --- p4/freebsd/src/sbin/iscontrol/iscontrol.8 2013-04-20 19:57:59.000000000 +0200 +++ p4/iscsi/sbin/iscontrol/iscontrol.8 2013-07-23 23:42:14.000000000 +0200 @@ -112,6 +112,7 @@ whatever options are specified, and star .Xr iscsi_initiator 4 , .Xr sa 4 , .Xr iscsi.conf 5 , +.Xr iscsictl 8 , .Xr camcontrol 8 .Sh STANDARDS RFC 3720 diff -urNp p4/freebsd/src/sbin/iscontrol/iscsi.conf.5 p4/iscsi/sbin/iscontrol/iscsi.conf.5 --- p4/freebsd/src/sbin/iscontrol/iscsi.conf.5 2013-06-19 11:02:14.000000000 +0200 +++ p4/iscsi/sbin/iscontrol/iscsi.conf.5 2013-07-23 23:42:14.000000000 +0200 @@ -201,6 +201,7 @@ The parsing is very primitive, so do not error messages. .Sh SEE ALSO .Xr iscsi_initiator 4 , +.Xr iscsictl 8 , .Xr iscontrol 8 .Sh STANDARDS ISCSI RFC 3720 diff -urNp p4/freebsd/src/share/man/man4/ctl.4 p4/iscsi/share/man/man4/ctl.4 --- p4/freebsd/src/share/man/man4/ctl.4 2013-04-20 20:00:27.000000000 +0200 +++ p4/iscsi/share/man/man4/ctl.4 2013-07-23 23:42:39.000000000 +0200 @@ -75,8 +75,11 @@ Error injection support .It All I/O handled in-kernel, no userland context switch overhead .El +.Pp +It also serves as a kernel component of the native iSCSI target. .Sh SEE ALSO .Xr ctladm 8 , +.Xr ctld 8 , .Xr ctlstat 8 .Sh HISTORY The diff -urNp p4/freebsd/src/sys/cam/ctl/ctl.c p4/iscsi/sys/cam/ctl/ctl.c --- p4/freebsd/src/sys/cam/ctl/ctl.c 2013-08-21 09:44:51.000000000 +0200 +++ p4/iscsi/sys/cam/ctl/ctl.c 2013-08-21 10:49:03.000000000 +0200 @@ -894,8 +894,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; - lun = ctl_softc->ctl_luns[msg_info.hdr.nexus.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[targ_lun]; page_index = &lun->mode_pages.index[index_to_aps_page]; current_sp = (struct copan_aps_subpage *) (page_index->page_data + @@ -1098,7 +1103,8 @@ ctl_init(void) mtx_unlock(&softc->ctl_lock); return (error); } - printf("ctl: CAM Target Layer loaded\n"); + if (bootverbose) + printf("ctl: CAM Target Layer loaded\n"); /* * Initialize the initiator and portname mappings @@ -1194,7 +1200,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"); } static int @@ -1678,12 +1685,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) { /* @@ -2980,6 +2991,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; @@ -3097,17 +3109,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"); @@ -3137,6 +3148,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 @@ -4432,9 +4465,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); } @@ -8247,12 +8285,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) { @@ -8601,7 +8643,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; @@ -8662,63 +8704,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: @@ -8743,6 +8769,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. */ @@ -9077,6 +9132,14 @@ ctl_inquiry_evpd_devid(struct ctl_scsiio int devid_len; 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 +9193,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, */ @@ -10350,7 +10411,7 @@ 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; retval = 0; @@ -10361,9 +10422,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 @@ -10403,6 +10467,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 { /* @@ -10769,6 +10834,7 @@ ctl_abort_task(union ctl_io *io) char printbuf[128]; #endif int found; + uint32_t targ_lun; ctl_softc = control_softc; found = 0; @@ -10776,9 +10842,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; @@ -10968,6 +11037,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)) @@ -11042,7 +11113,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, @@ -11066,10 +11137,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: @@ -12625,7 +12700,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; @@ -12644,9 +12719,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; @@ -13047,6 +13125,8 @@ ctl_isc_start(struct ctl_ha_component *c { ctl_ha_comp_status ret = CTL_HA_COMP_STATUS_OK; + printf("%s: go\n", __func__); + // UNKNOWN->HA or UNKNOWN->SINGLE (bootstrap) if (c->state == CTL_HA_STATE_UNKNOWN ) { ctl_is_single = 0; diff -urNp p4/freebsd/src/sys/cam/ctl/ctl.h p4/iscsi/sys/cam/ctl/ctl.h --- p4/freebsd/src/sys/cam/ctl/ctl.h 2013-08-20 09:20:19.000000000 +0200 +++ p4/iscsi/sys/cam/ctl/ctl.h 2013-07-23 23:42:57.000000000 +0200 @@ -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 p4/freebsd/src/sys/cam/ctl/ctl_backend.h p4/iscsi/sys/cam/ctl/ctl_backend.h --- p4/freebsd/src/sys/cam/ctl/ctl_backend.h 2013-08-20 09:20:20.000000000 +0200 +++ p4/iscsi/sys/cam/ctl/ctl_backend.h 2013-07-23 23:42:57.000000000 +0200 @@ -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 p4/freebsd/src/sys/cam/ctl/ctl_backend_block.c p4/iscsi/sys/cam/ctl/ctl_backend_block.c --- p4/freebsd/src/sys/cam/ctl/ctl_backend_block.c 2013-08-20 09:20:20.000000000 +0200 +++ p4/iscsi/sys/cam/ctl/ctl_backend_block.c 2013-07-23 23:42:57.000000000 +0200 @@ -1639,6 +1639,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); @@ -1740,6 +1741,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 p4/freebsd/src/sys/cam/ctl/ctl_backend_ramdisk.c p4/iscsi/sys/cam/ctl/ctl_backend_ramdisk.c --- p4/freebsd/src/sys/cam/ctl/ctl_backend_ramdisk.c 2013-08-20 09:20:20.000000000 +0200 +++ p4/iscsi/sys/cam/ctl/ctl_backend_ramdisk.c 2013-07-25 19:17:08.000000000 +0200 @@ -491,7 +491,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; @@ -509,6 +509,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; @@ -545,6 +546,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 p4/freebsd/src/sys/cam/ctl/ctl_frontend.h p4/iscsi/sys/cam/ctl/ctl_frontend.h --- p4/freebsd/src/sys/cam/ctl/ctl_frontend.h 2013-08-20 09:20:20.000000000 +0200 +++ p4/iscsi/sys/cam/ctl/ctl_frontend.h 2013-07-23 23:42:57.000000000 +0200 @@ -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 p4/freebsd/src/sys/cam/ctl/ctl_frontend_iscsi.c p4/iscsi/sys/cam/ctl/ctl_frontend_iscsi.c --- p4/freebsd/src/sys/cam/ctl/ctl_frontend_iscsi.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/sys/cam/ctl/ctl_frontend_iscsi.c 2013-08-20 11:52:22.000000000 +0200 @@ -0,0 +1,2637 @@ +/*- + * 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 "../../dev/iscsi/icl.h" +#include "../../dev/iscsi/iscsi_proto.h" +#include "ctl_frontend_iscsi.h" + +#ifdef ICL_KERNEL_PROXY +#include +#endif + +MALLOC_DEFINE(M_CFISCSI, "cfiscsi", "Memory used for CTL iSCSI frontend"); +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 = 3; +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_timeout = 5; +TUNABLE_INT("kern.cam.ctl.iscsi.ping_timeout", &ping_timeout); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, ping_timeout, CTLFLAG_RW, + &ping_timeout, 5, "Interval between ping (NOP-Out) requests, in seconds"); +static int login_timeout = 60; +TUNABLE_INT("kern.cam.ctl.iscsi.login_timeout", &login_timeout); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, login_timeout, CTLFLAG_RW, + &login_timeout, 60, "Time to wait for ctld(8) to finish Login Phase, 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"); + +#define CFISCSI_DEBUG(X, ...) \ + if (debug > 1) { \ + printf("%s: " X "\n", __func__, ## __VA_ARGS__);\ + } while (0) + +#define CFISCSI_WARN(X, ...) \ + if (debug > 0) { \ + printf("WARNING: %s: " X "\n", \ + __func__, ## __VA_ARGS__); \ + } while (0) + +#define CFISCSI_SESSION_DEBUG(S, X, ...) \ + if (debug > 1) { \ + printf("%s: %s (%s): " X "\n", \ + __func__, S->cs_initiator_addr, \ + S->cs_initiator_name, ## __VA_ARGS__); \ + } while (0) + +#define CFISCSI_SESSION_WARN(S, X, ...) \ + if (debug > 0) { \ + printf("WARNING: %s (%s): " X "\n", \ + S->cs_initiator_addr, \ + S->cs_initiator_name, ## __VA_ARGS__); \ + } while (0) + +#define CFISCSI_SESSION_LOCK(X) mtx_lock(&X->cs_lock) +#define CFISCSI_SESSION_UNLOCK(X) mtx_unlock(&X->cs_lock) +#define CFISCSI_SESSION_LOCK_ASSERT(X) mtx_assert(&X->cs_lock, MA_OWNED) + +#define CONN_SESSION(X) ((struct cfiscsi_session *)(X)->ic_prv0) +#define PDU_SESSION(X) CONN_SESSION((X)->ip_conn) +#define PDU_EXPDATASN(X) (X)->ip_prv0 +#define PDU_TOTAL_TRANSFER_LEN(X) (X)->ip_prv1 +#define PDU_R2TSN(X) (X)->ip_prv2 + +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 icl_pdu *request); +static void cfiscsi_pdu_handle_nop_out(struct icl_pdu *request); +static void cfiscsi_pdu_handle_scsi_command(struct icl_pdu *request); +static void cfiscsi_pdu_handle_task_request(struct icl_pdu *request); +static void cfiscsi_pdu_handle_data_out(struct icl_pdu *request); +static void cfiscsi_pdu_handle_logout_request(struct icl_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 void cfiscsi_session_delete(struct cfiscsi_session *cs); + +static struct cfiscsi_softc cfiscsi_softc; +extern struct ctl_softc *control_softc; + +static int cfiscsi_module_event_handler(module_t, int /*modeventtype_t*/, void *); + +static moduledata_t cfiscsi_moduledata = { + "ctlcfiscsi", + cfiscsi_module_event_handler, + NULL +}; + +DECLARE_MODULE(ctlcfiscsi, cfiscsi_moduledata, SI_SUB_CONFIGURE, SI_ORDER_FOURTH); +MODULE_VERSION(ctlcfiscsi, 1); +MODULE_DEPEND(ctlcfiscsi, ctl, 1, 1, 1); +MODULE_DEPEND(ctlcfiscsi, icl, 1, 1, 1); + +static struct icl_pdu * +cfiscsi_pdu_new_response(struct icl_pdu *request, int flags) +{ + + return (icl_pdu_new_bhs(request->ip_conn, flags)); +} + +static void +cfiscsi_pdu_update_cmdsn(const struct icl_pdu *request) +{ + const struct iscsi_bhs_scsi_command *bhssc; + struct cfiscsi_session *cs; + uint32_t cmdsn, expstatsn; + + cs = PDU_SESSION(request); + + /* + * Every incoming PDU - not just NOP-Out - resets the ping timer. + * The purpose of the timeout is to reset the connection when it stalls; + * we don't want this to happen when NOP-In or NOP-Out ends up delayed + * in some queue. + * + * XXX: Locking? + */ + cs->cs_timeout = 0; + + /* + * Data-Out PDUs don't contain CmdSN. + */ + if ((request->ip_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->ip_bhs; + cmdsn = ntohl(bhssc->bhssc_cmdsn); + expstatsn = ntohl(bhssc->bhssc_expstatsn); + + CFISCSI_SESSION_LOCK(cs); +#if 0 + if (expstatsn != cs->cs_statsn) { + CFISCSI_SESSION_DEBUG(cs, "received PDU with ExpStatSN %d, " + "while current StatSN is %d", expstatsn, + cs->cs_statsn); + } +#endif + + /* + * 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) { + CFISCSI_SESSION_WARN(cs, "received PDU with CmdSN %d, " + "while expected CmdSN was %d", cmdsn, cs->cs_cmdsn); + cs->cs_cmdsn = cmdsn + 1; + CFISCSI_SESSION_UNLOCK(cs); + return; + } + + /* + * 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) + */ + + if ((request->ip_bhs->bhs_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) + cs->cs_cmdsn++; + + CFISCSI_SESSION_UNLOCK(cs); +} + +static void +cfiscsi_pdu_handle(struct icl_pdu *request) +{ + struct cfiscsi_session *cs; + + cs = PDU_SESSION(request); + + cfiscsi_pdu_update_cmdsn(request); + + /* + * 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->ip_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_SESSION_WARN(cs, "received PDU with unsupported " + "opcode 0x%x; dropping connection", + request->ip_bhs->bhs_opcode); + cfiscsi_session_terminate(cs); + icl_pdu_free(request); + } + +} + +static void +cfiscsi_receive_callback(struct icl_pdu *request) +{ + struct cfiscsi_session *cs; + + cs = PDU_SESSION(request); + +#ifdef ICL_KERNEL_PROXY + if (cs->cs_waiting_for_ctld || cs->cs_login_phase) { + if (cs->cs_login_pdu == NULL) + cs->cs_login_pdu = request; + else + icl_pdu_free(request); + cv_signal(&cs->cs_login_cv); + return; + } +#endif + + cfiscsi_pdu_handle(request); +} + +static void +cfiscsi_error_callback(struct icl_conn *ic) +{ + struct cfiscsi_session *cs; + + cs = CONN_SESSION(ic); + + CFISCSI_SESSION_WARN(cs, "connection error; dropping connection"); + cfiscsi_session_terminate(cs); +} + +static int +cfiscsi_pdu_prepare(struct icl_pdu *response) +{ + struct cfiscsi_session *cs; + struct iscsi_bhs_scsi_response *bhssr; + bool advance_statsn = true; + + cs = PDU_SESSION(response); + + CFISCSI_SESSION_LOCK_ASSERT(cs); + + /* + * We're only using fields common for all the response + * (target -> initiator) PDUs. + */ + bhssr = (struct iscsi_bhs_scsi_response *)response->ip_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; + + /* + * 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); + bhssr->bhssr_maxcmdsn = htonl(cs->cs_cmdsn + maxcmdsn_delta); + + if (advance_statsn) + cs->cs_statsn++; + + return (0); +} + +static void +cfiscsi_pdu_queue(struct icl_pdu *response) +{ + struct cfiscsi_session *cs; + + cs = PDU_SESSION(response); + + CFISCSI_SESSION_LOCK(cs); + cfiscsi_pdu_prepare(response); + icl_pdu_queue(response); + 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("malformed LUN " + "(peripheral device addressing method): 0x%jx", + (uintmax_t)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("malformed LUN " + "(flat address space addressing method): 0x%jx", + (uintmax_t)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("malformed LUN (extended flat " + "address space addressing method): 0x%jx", + (uintmax_t)encoded); + result = 0xffffffff; + break; + } + result = (lun[1] << 16) + (lun[2] << 8) + lun[3]; + default: + CFISCSI_WARN("unsupported LUN format 0x%jx", + (uintmax_t)encoded); + result = 0xffffffff; + break; + } + + return (result); +} + +static void +cfiscsi_pdu_handle_nop_out(struct icl_pdu *request) +{ + struct cfiscsi_session *cs; + struct iscsi_bhs_nop_out *bhsno; + struct iscsi_bhs_nop_in *bhsni; + struct icl_pdu *response; + + cs = PDU_SESSION(request); + bhsno = (struct iscsi_bhs_nop_out *)request->ip_bhs; + + if (bhsno->bhsno_initiator_task_tag == 0xffffffff) { + /* + * Nothing to do, iscsi_pdu_update_statsn() already + * zeroed the timeout. + */ + icl_pdu_free(request); + return; + } + + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + icl_pdu_free(request); + return; + } + bhsni = (struct iscsi_bhs_nop_in *)response->ip_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; + +#if 0 + /* XXX */ + response->ip_data_len = request->ip_data_len; + response->ip_data_mbuf = request->ip_data_mbuf; + request->ip_data_len = 0; + request->ip_data_mbuf = NULL; +#endif + + icl_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_pdu_handle_scsi_command(struct icl_pdu *request) +{ + struct iscsi_bhs_scsi_command *bhssc; + struct cfiscsi_session *cs; + union ctl_io *io; + int error; + + cs = PDU_SESSION(request); + bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; + //CFISCSI_SESSION_DEBUG(cs, "initiator task tag 0x%x", + // bhssc->bhssc_initiator_task_tag); + + if (request->ip_data_len > 0 && cs->cs_immediate_data == false) { + CFISCSI_SESSION_WARN(cs, "unsolicited data with " + "ImmediateData=No; dropping connection"); + cfiscsi_session_terminate(cs); + icl_pdu_free(request); + return; + } + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_SESSION_WARN(cs, "can't allocate ctl_io"); + icl_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 BHSSC_FLAGS_ATTR_UNTAGGED: + io->scsiio.tag_type = CTL_TAG_UNTAGGED; + break; + case BHSSC_FLAGS_ATTR_SIMPLE: + io->scsiio.tag_type = CTL_TAG_SIMPLE; + break; + case BHSSC_FLAGS_ATTR_ORDERED: + io->scsiio.tag_type = CTL_TAG_ORDERED; + break; + case BHSSC_FLAGS_ATTR_HOQ: + io->scsiio.tag_type = CTL_TAG_HEAD_OF_QUEUE; + break; + case BHSSC_FLAGS_ATTR_ACA: + io->scsiio.tag_type = CTL_TAG_ACA; + break; + default: + io->scsiio.tag_type = CTL_TAG_UNTAGGED; + CFISCSI_SESSION_WARN(cs, "unhandled tag type %d", + 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)); + refcount_acquire(&cs->cs_outstanding_ctl_pdus); + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_SESSION_WARN(cs, "ctl_queue() failed; error %d", error); + ctl_free_io(io); + refcount_release(&cs->cs_outstanding_ctl_pdus); + icl_pdu_free(request); + } +} + +static void +cfiscsi_pdu_handle_task_request(struct icl_pdu *request) +{ + struct iscsi_bhs_task_management_request *bhstmr; + struct iscsi_bhs_task_management_response *bhstmr2; + struct icl_pdu *response; + struct cfiscsi_session *cs; + union ctl_io *io; + int error; + + cs = PDU_SESSION(request); + bhstmr = (struct iscsi_bhs_task_management_request *)request->ip_bhs; + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_SESSION_WARN(cs, "can't allocate ctl_io"); + icl_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: +#if 0 + CFISCSI_SESSION_DEBUG(cs, "BHSTMR_FUNCTION_ABORT_TASK"); +#endif + io->taskio.task_action = CTL_TASK_ABORT_TASK; + io->taskio.tag_num = bhstmr->bhstmr_referenced_task_tag; + break; + case BHSTMR_FUNCTION_LOGICAL_UNIT_RESET: +#if 0 + CFISCSI_SESSION_DEBUG(cs, "BHSTMR_FUNCTION_LOGICAL_UNIT_RESET"); +#endif + io->taskio.task_action = CTL_TASK_LUN_RESET; + break; + case BHSTMR_FUNCTION_TARGET_COLD_RESET: +#if 0 + CFISCSI_SESSION_DEBUG(cs, "BHSTMR_FUNCTION_TARGET_COLD_RESET"); +#endif + io->taskio.task_action = CTL_TASK_BUS_RESET; + break; + default: + CFISCSI_SESSION_DEBUG(cs, "unsupported function 0x%x", + bhstmr->bhstmr_function & ~0x80); + ctl_free_io(io); + + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + icl_pdu_free(request); + return; + } + bhstmr2 = (struct iscsi_bhs_task_management_response *) + response->ip_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; + icl_pdu_free(request); + cfiscsi_pdu_queue(response); + return; + } + + refcount_acquire(&cs->cs_outstanding_ctl_pdus); + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_SESSION_WARN(cs, "ctl_queue() failed; error %d", error); + ctl_free_io(io); + refcount_release(&cs->cs_outstanding_ctl_pdus); + icl_pdu_free(request); + } +} + +static bool +cfiscsi_handle_data_segment(struct icl_pdu *request, struct cfiscsi_data_wait *cdw) +{ + struct iscsi_bhs_data_out *bhsdo; + struct cfiscsi_session *cs; + struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; + size_t copy_len, off, buffer_offset; + int ctl_sg_count; + union ctl_io *io; + + cs = PDU_SESSION(request); + + KASSERT((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_DATA_OUT || + (request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_COMMAND, + ("bad opcode 0x%x", request->ip_bhs->bhs_opcode)); + + /* + * We're only using fields common for Data Out and SCSI Command PDUs. + */ + bhsdo = (struct iscsi_bhs_data_out *)request->ip_bhs; + + 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_SESSION_DEBUG(cs, "received %zd bytes out of %d", + request->ip_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_SESSION_DEBUG(cs, "ctl_sg_count = %d", ctl_sg_count); +#endif + + if ((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_DATA_OUT) + buffer_offset = ntohl(bhsdo->bhsdo_buffer_offset); + else + buffer_offset = 0; + + /* + * Make sure the offset, as sent by the initiator, matches the offset + * we're supposed to be at in the scatter-gather list. + */ + if (buffer_offset != io->scsiio.ext_data_filled) { + CFISCSI_SESSION_WARN(cs, "received bad buffer offset %zd, " + "expected %zd", buffer_offset, io->scsiio.ext_data_filled); + cfiscsi_session_terminate(cs); + return (true); + } + + 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 = icl_pdu_data_segment_length(request) - off; + if (copy_len > cdw->cdw_sg_len) + copy_len = cdw->cdw_sg_len; + + icl_pdu_get_data(request, off, cdw->cdw_sg_addr, copy_len); + 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 == icl_pdu_data_segment_length(request)) + break; + } + + if (off < icl_pdu_data_segment_length(request)) { + CFISCSI_SESSION_WARN(cs, "received too much data: got %zd bytes, " + "expected %zd", icl_pdu_data_segment_length(request), off); + cfiscsi_session_terminate(cs); + return (true); + } + + 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_SESSION_WARN(cs, "got the final packet without " + "the F flag; flags = 0x%x; dropping connection", + bhsdo->bhsdo_flags); + cfiscsi_session_terminate(cs); + return (true); + } + + if (io->scsiio.ext_data_filled != io->scsiio.kern_total_len) { + if ((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_DATA_OUT) { + CFISCSI_SESSION_WARN(cs, "got the final packet, but the " + "transmitted size was %zd bytes instead of %d; " + "dropping connection", + (size_t)io->scsiio.ext_data_filled, + io->scsiio.kern_total_len); + cfiscsi_session_terminate(cs); + return (true); + } else { + /* + * For SCSI Command PDU, this just means we need to + * solicit more data by sending R2T. + */ + return (false); + } + } +#if 0 + CFISCSI_SESSION_DEBUG(cs, "no longer expecting Data-Out with target " + "transfer tag 0x%x", cdw->cdw_target_transfer_tag); +#endif + + return (true); + } + + return (false); +} + +static void +cfiscsi_pdu_handle_data_out(struct icl_pdu *request) +{ + struct iscsi_bhs_data_out *bhsdo; + struct cfiscsi_session *cs; + struct cfiscsi_data_wait *cdw = NULL; + union ctl_io *io; + bool done; + + cs = PDU_SESSION(request); + bhsdo = (struct iscsi_bhs_data_out *)request->ip_bhs; + + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH(cdw, &cs->cs_waiting_for_data_out, cdw_next) { +#if 0 + CFISCSI_SESSION_DEBUG(cs, "have ttt 0x%x, itt 0x%x; looking for " + "ttt 0x%x, itt 0x%x", + 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_SESSION_WARN(cs, "data transfer tag 0x%x, initiator task tag " + "0x%x, not found", bhsdo->bhsdo_target_transfer_tag, + bhsdo->bhsdo_initiator_task_tag); + icl_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")); + + done = cfiscsi_handle_data_segment(request, cdw); + if (done) { + 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); + } + + icl_pdu_free(request); +} + +static void +cfiscsi_pdu_handle_logout_request(struct icl_pdu *request) +{ + struct iscsi_bhs_logout_request *bhslr; + struct iscsi_bhs_logout_response *bhslr2; + struct icl_pdu *response; + struct cfiscsi_session *cs; + + cs = PDU_SESSION(request); + bhslr = (struct iscsi_bhs_logout_request *)request->ip_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) { + icl_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + bhslr2 = (struct iscsi_bhs_logout_response *)response->ip_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; + icl_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) { + icl_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + bhslr2 = (struct iscsi_bhs_logout_response *)response->ip_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; + icl_pdu_free(request); + cfiscsi_pdu_queue(response); + break; + default: + CFISCSI_SESSION_WARN(cs, "invalid reason 0%x; dropping connection", + bhslr->bhslr_reason); + icl_pdu_free(request); + cfiscsi_session_terminate(cs); + break; + } +} + +static void +cfiscsi_callout(void *context) +{ + struct icl_pdu *cp; + struct iscsi_bhs_nop_in *bhsni; + struct cfiscsi_session *cs; + + cs = context; + + if (cs->cs_terminating) + return; + + callout_schedule(&cs->cs_callout, 1 * hz); + + CFISCSI_SESSION_LOCK(cs); + cs->cs_timeout++; + CFISCSI_SESSION_UNLOCK(cs); + +#ifdef ICL_KERNEL_PROXY + if (cs->cs_waiting_for_ctld || cs->cs_login_phase) { + if (cs->cs_timeout > login_timeout) { + CFISCSI_SESSION_WARN(cs, "login timed out after " + "%d seconds; dropping connection", cs->cs_timeout); + cfiscsi_session_terminate(cs); + } + return; + } +#endif + + if (cs->cs_timeout >= ping_timeout) { + CFISCSI_SESSION_WARN(cs, "no ping reply (NOP-Out) after %d seconds; " + "dropping connection", ping_timeout); + cfiscsi_session_terminate(cs); + return; + } + + /* + * If the ping was reset less than one second ago - which means + * that we've received some PDU during the last second - assume + * the traffic flows correctly and don't bother sending a NOP-Out. + * + * (It's 2 - one for one second, and one for incrementing is_timeout + * earlier in this routine.) + */ + if (cs->cs_timeout < 2) + return; + + cp = icl_pdu_new_bhs(cs->cs_conn, M_WAITOK); + bhsni = (struct iscsi_bhs_nop_in *)cp->ip_bhs; + bhsni->bhsni_opcode = ISCSI_BHS_OPCODE_NOP_IN; + bhsni->bhsni_flags = 0x80; + bhsni->bhsni_initiator_task_tag = 0xffffffff; + + cfiscsi_pdu_queue(cp); +} + +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_SESSION_WARN(cs, "can't allocate ctl_io"); + 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_SESSION_WARN(cs, "ctl_queue() failed; error %d", 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_SESSION_WARN(cs, "can't allocate ctl_io"); + 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_SESSION_WARN(cs, "ctl_queue() failed; error %d", error); + ctl_free_io(io); + return; + } +#if 0 + CFISCSI_SESSION_DEBUG(cs, "removing csw for initiator task tag " + "0x%x", cdw->cdw_initiator_task_tag); +#endif + 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_maintenance_thread(void *arg) +{ + struct cfiscsi_session *cs; + + cs = arg; + + for (;;) { + CFISCSI_SESSION_LOCK(cs); + if (cs->cs_terminating == false) + cv_wait(&cs->cs_maintenance_cv, &cs->cs_lock); + CFISCSI_SESSION_UNLOCK(cs); + + if (cs->cs_terminating) { + cfiscsi_session_terminate_tasks(cs); + callout_drain(&cs->cs_callout); + + icl_conn_shutdown(cs->cs_conn); + icl_conn_close(cs->cs_conn); + + cs->cs_terminating++; + + /* + * XXX: We used to wait up to 30 seconds to deliver queued PDUs + * to the initiator. We also tried hard to deliver SCSI Responses + * for the aborted PDUs. We don't do that anymore. We might need + * to revisit that. + */ + + cfiscsi_session_delete(cs); + kthread_exit(); + return; + } + CFISCSI_SESSION_DEBUG(cs, "nothing to do"); + } +} + +static void +cfiscsi_session_terminate(struct cfiscsi_session *cs) +{ + + if (cs->cs_terminating != 0) + return; + cs->cs_terminating = 1; + cv_signal(&cs->cs_maintenance_cv); +} + +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 = &cfiscsi_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_SESSION_WARN(cs, "too many concurrent sessions (%d)", + softc->max_initiators); + mtx_unlock(&softc->lock); + return (1); + } + softc->ctl_initids[i] = 1; + mtx_unlock(&softc->lock); + +#if 0 + CFISCSI_SESSION_DEBUG(cs, "adding initiator id %d, max %d", + i, softc->max_initiators); +#endif + cs->cs_ctl_initid = i; + error = ctl_add_initiator(0x0, softc->fe.targ_port, cs->cs_ctl_initid); + if (error != 0) { + CFISCSI_SESSION_WARN(cs, "ctl_add_initiator failed with error %d", 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 = &cfiscsi_softc; + + error = ctl_remove_initiator(softc->fe.targ_port, cs->cs_ctl_initid); + if (error != 0) { + CFISCSI_SESSION_WARN(cs, "ctl_remove_initiator failed with error %d", + 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) +{ + struct cfiscsi_session *cs; + int error; + + cs = malloc(sizeof(*cs), M_CFISCSI, M_NOWAIT | M_ZERO); + if (cs == NULL) { + CFISCSI_WARN("malloc failed"); + return (NULL); + } + cs->cs_ctl_initid = -1; + + refcount_init(&cs->cs_outstanding_ctl_pdus, 0); + TAILQ_INIT(&cs->cs_waiting_for_data_out); + mtx_init(&cs->cs_lock, "cfiscsi_lock", NULL, MTX_DEF); + cv_init(&cs->cs_maintenance_cv, "cfiscsi_mt"); +#ifdef ICL_KERNEL_PROXY + cv_init(&cs->cs_login_cv, "cfiscsi_login"); +#endif + + cs->cs_conn = icl_conn_new(); + cs->cs_conn->ic_receive = cfiscsi_receive_callback; + cs->cs_conn->ic_error = cfiscsi_error_callback; + cs->cs_conn->ic_prv0 = cs; + + error = kthread_add(cfiscsi_maintenance_thread, cs, NULL, NULL, 0, 0, "cfiscsimt"); + if (error != 0) { + CFISCSI_SESSION_WARN(cs, "kthread_add(9) failed with error %d", error); + free(cs, M_CFISCSI); + return (NULL); + } + + mtx_lock(&softc->lock); + cs->cs_id = softc->last_session_id + 1; + softc->last_session_id++; + mtx_unlock(&softc->lock); + + mtx_lock(&softc->lock); + TAILQ_INSERT_TAIL(&softc->sessions, cs, cs_next); + mtx_unlock(&softc->lock); + + /* + * Start pinging the initiator. + */ + callout_init(&cs->cs_callout, 1); + callout_reset(&cs->cs_callout, 1 * hz, cfiscsi_callout, cs); + + return (cs); +} + +static void +cfiscsi_session_delete(struct cfiscsi_session *cs) +{ + struct cfiscsi_softc *softc; + + softc = &cfiscsi_softc; + + KASSERT(cs->cs_outstanding_ctl_pdus == 0, + ("destroying session with outstanding CTL pdus")); + KASSERT(TAILQ_EMPTY(&cs->cs_waiting_for_data_out), + ("destroying session with non-empty queue")); + + cfiscsi_session_unregister_initiator(cs); + if (cs->cs_target != NULL) + cfiscsi_target_release(cs->cs_target); + icl_conn_close(cs->cs_conn); + icl_conn_free(cs->cs_conn); + + mtx_lock(&softc->lock); + TAILQ_REMOVE(&softc->sessions, cs, cs_next); + mtx_unlock(&softc->lock); + + free(cs, M_CFISCSI); +} + +int +cfiscsi_init(void) +{ + struct cfiscsi_softc *softc; + struct ctl_frontend *fe; + int retval; + + softc = &cfiscsi_softc; + retval = 0; + bzero(softc, sizeof(*softc)); + mtx_init(&softc->lock, "cfiscsi", NULL, MTX_DEF); + +#ifdef ICL_KERNEL_PROXY + cv_init(&softc->accept_cv, "cfiscsi_accept"); +#endif + 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("ctl_frontend_register() failed with error %d", + retval); + retval = 1; + goto bailout; + } + + softc->max_initiators = fe->max_initiators; + + 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 int +cfiscsi_module_event_handler(module_t mod, int what, void *arg) +{ + + switch (what) { + case MOD_LOAD: + return (cfiscsi_init()); + case MOD_UNLOAD: + return (EBUSY); + default: + return (EOPNOTSUPP); + } +} + +#ifdef ICL_KERNEL_PROXY +static void +cfiscsi_accept(struct socket *so) +{ + struct cfiscsi_session *cs; + + cs = cfiscsi_session_new(&cfiscsi_softc); + if (cs == NULL) { + CFISCSI_WARN("failed to create session"); + return; + } + + icl_conn_handoff_sock(cs->cs_conn, so); + cs->cs_waiting_for_ctld = true; + cv_signal(&cfiscsi_softc.accept_cv); +} +#endif + +static void +cfiscsi_online(void *arg) +{ + struct cfiscsi_softc *softc; + + softc = (struct cfiscsi_softc *)arg; + + softc->online = 1; +#ifdef ICL_KERNEL_PROXY + if (softc->listener != NULL) + icl_listen_free(softc->listener); + softc->listener = icl_listen_new(cfiscsi_accept); +#endif +} + +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); + +#ifdef ICL_KERNEL_PROXY + icl_listen_free(softc->listener); + softc->listener = NULL; +#endif +} + +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 cfiscsi_softc *softc; + struct cfiscsi_session *cs; + struct cfiscsi_target *ct; + struct ctl_iscsi_handoff_params *cihp; +#ifndef ICL_KERNEL_PROXY + int error; +#endif + + cihp = (struct ctl_iscsi_handoff_params *)&(ci->data); + softc = &cfiscsi_softc; + + CFISCSI_DEBUG("new connection from %s (%s) to %s", + cihp->initiator_name, cihp->initiator_addr, + cihp->target_name); + + if (softc->online == 0) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: port offline", __func__); + return; + } + + ct = cfiscsi_target_find(softc, cihp->target_name); + if (ct == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: target not found", __func__); + return; + } + +#ifdef ICL_KERNEL_PROXY + mtx_lock(&cfiscsi_softc.lock); + TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { + if (cs->cs_id == cihp->socket) + break; + } + if (cs == NULL) { + mtx_unlock(&cfiscsi_softc.lock); + snprintf(ci->error_str, sizeof(ci->error_str), "connection not found"); + ci->status = CTL_ISCSI_ERROR; + return; + } + mtx_unlock(&cfiscsi_softc.lock); +#else + cs = cfiscsi_session_new(softc); + if (cs == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: cfiscsi_session_new failed", __func__); + cfiscsi_target_release(ct); + return; + } +#endif + cs->cs_target = ct; + + /* + * 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 = cihp->portal_group_tag; + cs->cs_cmdsn = cihp->cmdsn; + cs->cs_statsn = cihp->statsn; + cs->cs_max_data_segment_length = cihp->max_recv_data_segment_length; + cs->cs_max_burst_length = cihp->max_burst_length; + cs->cs_immediate_data = !!cihp->immediate_data; + if (cihp->header_digest == CTL_ISCSI_DIGEST_CRC32C) + cs->cs_conn->ic_header_crc32c = true; + if (cihp->data_digest == CTL_ISCSI_DIGEST_CRC32C) + cs->cs_conn->ic_data_crc32c = true; + + strlcpy(cs->cs_initiator_name, + cihp->initiator_name, sizeof(cs->cs_initiator_name)); + strlcpy(cs->cs_initiator_addr, + cihp->initiator_addr, sizeof(cs->cs_initiator_addr)); + strlcpy(cs->cs_initiator_alias, + cihp->initiator_alias, sizeof(cs->cs_initiator_alias)); + +#ifdef ICL_KERNEL_PROXY + cs->cs_login_phase = false; +#else + error = icl_conn_handoff(cs->cs_conn, cihp->socket); + if (error != 0) { + cfiscsi_session_delete(cs); + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: icl_conn_handoff failed with error %d", + __func__, error); + return; + } +#endif + + /* + * Register initiator with CTL. + */ + cfiscsi_session_register_initiator(cs); + +#ifdef ICL_KERNEL_PROXY + /* + * First PDU of the Full Feature phase has likely already arrived. + * We have to pick it up and execute properly. + */ + if (cs->cs_login_pdu != NULL) { + CFISCSI_SESSION_DEBUG(cs, "picking up first PDU"); + cfiscsi_pdu_handle(cs->cs_login_pdu); + cs->cs_login_pdu = NULL; + } +#endif + + 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) { +#ifdef ICL_KERNEL_PROXY + if (cs->cs_target == NULL) + continue; +#endif + error = sbuf_printf(sb, "" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%zd" + "%zd" + "%d" + "\n", + cs->cs_id, + cs->cs_initiator_name, cs->cs_initiator_addr, cs->cs_initiator_alias, + cs->cs_target->ct_name, cs->cs_target->ct_alias, + cs->cs_conn->ic_header_crc32c ? "CRC32C" : "None", + cs->cs_conn->ic_data_crc32c ? "CRC32C" : "None", + cs->cs_max_data_segment_length, + cs->cs_immediate_data, + cs->cs_conn->ic_iser); + 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 icl_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 = icl_pdu_new_bhs(cs->cs_conn, M_NOWAIT); + if (response == NULL) { + /* + * Oh well. Just terminate the connection. + */ + } else { + bhsam = (struct iscsi_bhs_asynchronous_message *) + response->ip_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 icl_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 = icl_pdu_new_bhs(cs->cs_conn, 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->ip_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; +} + +#ifdef ICL_KERNEL_PROXY +static void +cfiscsi_ioctl_listen(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_listen_params *cilp; + struct sockaddr *sa; + int error; + + cilp = (struct ctl_iscsi_listen_params *)&(ci->data); + + if (cfiscsi_softc.listener == NULL) { + CFISCSI_DEBUG("no listener"); + snprintf(ci->error_str, sizeof(ci->error_str), "no listener"); + ci->status = CTL_ISCSI_ERROR; + return; + } + + error = getsockaddr(&sa, (void *)cilp->addr, cilp->addrlen); + if (error != 0) { + CFISCSI_DEBUG("getsockaddr, error %d", error); + snprintf(ci->error_str, sizeof(ci->error_str), "getsockaddr failed"); + ci->status = CTL_ISCSI_ERROR; + return; + } + + error = icl_listen_add(cfiscsi_softc.listener, cilp->iser, cilp->domain, + cilp->socktype, cilp->protocol, sa); + if (error != 0) { + free(sa, M_SONAME); + CFISCSI_DEBUG("icl_listen_add, error %d", error); + snprintf(ci->error_str, sizeof(ci->error_str), + "icl_listen_add failed, error %d", error); + ci->status = CTL_ISCSI_ERROR; + return; + } + + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_accept(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_accept_params *ciap; + struct cfiscsi_session *cs; + int error; + + ciap = (struct ctl_iscsi_accept_params *)&(ci->data); + + mtx_lock(&cfiscsi_softc.lock); + for (;;) { + TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { + if (cs->cs_waiting_for_ctld) + break; + } + if (cs != NULL) + break; + error = cv_wait_sig(&cfiscsi_softc.accept_cv, &cfiscsi_softc.lock); + if (error != 0) { + mtx_unlock(&cfiscsi_softc.lock); + snprintf(ci->error_str, sizeof(ci->error_str), "interrupted"); + ci->status = CTL_ISCSI_ERROR; + return; + } + } + mtx_unlock(&cfiscsi_softc.lock); + + cs->cs_waiting_for_ctld = false; + cs->cs_login_phase = true; + + ciap->connection_id = cs->cs_id; + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_send(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_send_params *cisp; + struct cfiscsi_session *cs; + struct icl_pdu *ip; + size_t datalen; + void *data; + int error; + + cisp = (struct ctl_iscsi_send_params *)&(ci->data); + + mtx_lock(&cfiscsi_softc.lock); + TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { + if (cs->cs_id == cisp->connection_id) + break; + } + if (cs == NULL) { + mtx_unlock(&cfiscsi_softc.lock); + snprintf(ci->error_str, sizeof(ci->error_str), "connection not found"); + ci->status = CTL_ISCSI_ERROR; + return; + } + mtx_unlock(&cfiscsi_softc.lock); + +#if 0 + if (cs->cs_login_phase == false) + return (EBUSY); +#endif + + if (cs->cs_terminating) { + snprintf(ci->error_str, sizeof(ci->error_str), "connection is terminating"); + ci->status = CTL_ISCSI_ERROR; + return; + } + + datalen = cisp->data_segment_len; + /* + * XXX + */ + //if (datalen > CFISCSI_MAX_DATA_SEGMENT_LENGTH) { + if (datalen > 65535) { + snprintf(ci->error_str, sizeof(ci->error_str), "data segment too big"); + ci->status = CTL_ISCSI_ERROR; + return; + } + if (datalen > 0) { + data = malloc(datalen, M_CFISCSI, M_WAITOK); + error = copyin(cisp->data_segment, data, datalen); + if (error != 0) { + free(data, M_CFISCSI); + snprintf(ci->error_str, sizeof(ci->error_str), "copyin error %d", error); + ci->status = CTL_ISCSI_ERROR; + return; + } + } + + ip = icl_pdu_new_bhs(cs->cs_conn, M_WAITOK); + memcpy(ip->ip_bhs, cisp->bhs, sizeof(*ip->ip_bhs)); + if (datalen > 0) { + icl_pdu_append_data(ip, data, datalen); + free(data, M_CFISCSI); + } + icl_pdu_queue(ip); + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_receive(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_receive_params *cirp; + struct cfiscsi_session *cs; + struct icl_pdu *ip; + void *data; + + cirp = (struct ctl_iscsi_receive_params *)&(ci->data); + + mtx_lock(&cfiscsi_softc.lock); + TAILQ_FOREACH(cs, &cfiscsi_softc.sessions, cs_next) { + if (cs->cs_id == cirp->connection_id) + break; + } + if (cs == NULL) { + mtx_unlock(&cfiscsi_softc.lock); + snprintf(ci->error_str, sizeof(ci->error_str), "connection not found"); + ci->status = CTL_ISCSI_ERROR; + return; + } + mtx_unlock(&cfiscsi_softc.lock); + +#if 0 + if (is->is_login_phase == false) + return (EBUSY); +#endif + + CFISCSI_SESSION_LOCK(cs); + while (cs->cs_login_pdu == NULL && + cs->cs_terminating == false) + cv_wait(&cs->cs_login_cv, &cs->cs_lock); + if (cs->cs_terminating) { + CFISCSI_SESSION_UNLOCK(cs); + snprintf(ci->error_str, sizeof(ci->error_str), "connection terminating"); + ci->status = CTL_ISCSI_ERROR; + return; + } + ip = cs->cs_login_pdu; + cs->cs_login_pdu = NULL; + CFISCSI_SESSION_UNLOCK(cs); + + if (ip->ip_data_len > cirp->data_segment_len) { + icl_pdu_free(ip); + snprintf(ci->error_str, sizeof(ci->error_str), "data segment too big"); + ci->status = CTL_ISCSI_ERROR; + return; + } + + copyout(ip->ip_bhs, cirp->bhs, sizeof(*ip->ip_bhs)); + if (ip->ip_data_len > 0) { + data = malloc(ip->ip_data_len, M_CFISCSI, M_WAITOK); + icl_pdu_get_data(ip, 0, data, ip->ip_data_len); + copyout(data, cirp->data_segment, ip->ip_data_len); + free(data, M_CFISCSI); + } + + icl_pdu_free(ip); + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_close(struct ctl_iscsi *ci) +{ + /* + * XXX + */ +} +#endif /* !ICL_KERNEL_PROXY */ + +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; +#ifdef ICL_KERNEL_PROXY + case CTL_ISCSI_LISTEN: + cfiscsi_ioctl_listen(ci); + break; + case CTL_ISCSI_ACCEPT: + cfiscsi_ioctl_accept(ci); + break; + case CTL_ISCSI_SEND: + cfiscsi_ioctl_send(ci); + break; + case CTL_ISCSI_RECEIVE: + cfiscsi_ioctl_receive(ci); + break; + case CTL_ISCSI_CLOSE: + cfiscsi_ioctl_close(ci); + break; +#endif /* ICL_KERNEL_PROXY */ + 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 cfiscsi_session *cs; + 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 icl_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; + cs = PDU_SESSION(request); + + wwpn_len = strlen(cs->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", + cs->cs_target->ct_name, cs->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_CFISCSI); + + 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, + const char *alias) +{ + struct cfiscsi_target *ct, *newct; + int i; + + if (name[0] == '\0' || strlen(name) >= CTL_ISCSI_NAME_LEN) + return (NULL); + + newct = malloc(sizeof(*newct), M_CFISCSI, 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_CFISCSI); + return (ct); + } + + for (i = 0; i < CTL_MAX_LUNS; i++) + newct->ct_luns[i] = -1; + + strlcpy(newct->ct_name, name, sizeof(newct->ct_name)); + if (alias != NULL) + strlcpy(newct->ct_alias, alias, sizeof(newct->ct_alias)); + 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("requested lun number %d is higher " + "than maximum %d", 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("requested lun number %ld is higher " + "than maximum %d", 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("lun %ld already allocated", lun_id); + return (-1); + } + +#if 0 + CFISCSI_DEBUG("adding mapping for lun %ld, target %s " + "to ctl lun %ld", lun_id, ct->ct_name, ctl_lun_id); +#endif + + 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("lun %ld not allocated", 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_target_alias = NULL; + const char *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_target_alias") == 0) + ctld_target_alias = 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_WARN("lun added with ctld_target, but without " + "ctld_lun, or the other way around; ignoring"); + return (0); + } + + ct = cfiscsi_target_find_or_create(softc, ctld_target, ctld_target_alias); + if (ct == NULL) { + CFISCSI_WARN("failed to create target \"%s\"", 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 icl_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; + bool done; + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = PDU_SESSION(request); + + bhssc = (const struct iscsi_bhs_scsi_command *)request->ip_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. + */ + PDU_TOTAL_TRANSFER_LEN(request) = 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_SESSION_DEBUG(cs, "ctl_sg_count = %d", 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_SESSION_DEBUG(cs, "off = %zd", 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->ip_bhs; + bhsdi->bhsdi_opcode = + ISCSI_BHS_OPCODE_SCSI_DATA_IN; + bhsdi->bhsdi_initiator_task_tag = + bhssc->bhssc_initiator_task_tag; + bhsdi->bhsdi_datasn = + htonl(PDU_EXPDATASN(request)); + PDU_EXPDATASN(request)++; + 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->ip_data_len + copy_len > + cs->cs_max_data_segment_length) + copy_len = cs->cs_max_data_segment_length - + response->ip_data_len; + KASSERT(copy_len <= len, ("copy_len > len")); + icl_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->ip_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->ip_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_SESSION_DEBUG(cs, "not setting the F flag; " + "have %zd, need %zd", off, + (size_t)io->scsiio.kern_total_len); + } + KASSERT(response->ip_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_SESSION_DEBUG(cs, "expecting Data-Out with initiator " + "task tag 0x%x, target transfer tag 0x%x", + 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; + + if (cs->cs_immediate_data && + icl_pdu_data_segment_length(request) > 0) { + done = cfiscsi_handle_data_segment(request, cdw); + if (done) { + uma_zfree(cfiscsi_data_wait_zone, cdw); + io->scsiio.be_move_done(io); + return; + } + +#if 0 + if (io->scsiio.ext_data_filled != 0) + CFISCSI_SESSION_DEBUG(cs, "got %zd bytes of immediate data, need %zd", + io->scsiio.ext_data_filled, io->scsiio.kern_data_len); +#endif + } + + 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->ip_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(PDU_R2TSN(request)); + PDU_R2TSN(request)++; + /* + * 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. + * + * The ext_data_filled is to account for unsolicited + * (immediate) data that might have already arrived. + */ + bhsr2t->bhsr2t_buffer_offset = + htonl(io->scsiio.kern_rel_offset + io->scsiio.ext_data_filled); + /* + * This is the total length (sum of S/G lengths) this call + * to cfiscsi_datamove() is supposed to handle. + * + * XXX: Limit it to MaxBurstLength. + */ + bhsr2t->bhsr2t_desired_data_transfer_length = + htonl(io->scsiio.kern_data_len - io->scsiio.ext_data_filled); + cfiscsi_pdu_queue(response); + } +} + +static void +cfiscsi_scsi_command_done(union ctl_io *io) +{ + struct icl_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 = PDU_SESSION(request); + bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; + KASSERT((bhssc->bhssc_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_COMMAND, + ("replying to wrong opcode 0x%x", bhssc->bhssc_opcode)); + + //CFISCSI_SESSION_DEBUG(cs, "initiator task tag 0x%x", + // 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->ip_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 (PDU_TOTAL_TRANSFER_LEN(request) < + 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) - + PDU_TOTAL_TRANSFER_LEN(request)); + //CFISCSI_SESSION_DEBUG(cs, "underflow; residual count %d", + // ntohl(bhssr->bhssr_residual_count)); + } else if (PDU_TOTAL_TRANSFER_LEN(request) > + ntohl(bhssc->bhssc_expected_data_transfer_length)) { + bhssr->bhssr_flags |= BHSSR_FLAGS_RESIDUAL_OVERFLOW; + bhssr->bhssr_residual_count = + htonl(PDU_TOTAL_TRANSFER_LEN(request) - + ntohl(bhssc->bhssc_expected_data_transfer_length)); + //CFISCSI_SESSION_DEBUG(cs, "overflow; residual count %d", + // 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(PDU_EXPDATASN(request)); + + if (io->scsiio.sense_len > 0) { +#if 0 + CFISCSI_SESSION_DEBUG(cs, "returning %d bytes of sense data", + io->scsiio.sense_len); +#endif + sense_length = htons(io->scsiio.sense_len); + icl_pdu_append_data(response, + &sense_length, sizeof(sense_length)); + icl_pdu_append_data(response, + &io->scsiio.sense_data, io->scsiio.sense_len); + } + + ctl_free_io(io); + icl_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_task_management_done(union ctl_io *io) +{ + struct icl_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 = PDU_SESSION(request); + bhstmr = (struct iscsi_bhs_task_management_request *)request->ip_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_SESSION_DEBUG(cs, "initiator task tag 0x%x; referenced task tag 0x%x", + 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_SESSION_DEBUG(cs, "removing csw for initiator task " + "tag 0x%x", 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->ip_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_SESSION_DEBUG(cs, "BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED"); + bhstmr2->bhstmr_response = + BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED; + } + bhstmr2->bhstmr_initiator_task_tag = bhstmr->bhstmr_initiator_task_tag; + + ctl_free_io(io); + icl_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_done(union ctl_io *io) +{ + struct icl_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 = PDU_SESSION(request); + refcount_release(&cs->cs_outstanding_ctl_pdus); + + switch (request->ip_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->ip_bhs->bhs_opcode); + } +} diff -urNp p4/freebsd/src/sys/cam/ctl/ctl_frontend_iscsi.h p4/iscsi/sys/cam/ctl/ctl_frontend_iscsi.h --- p4/freebsd/src/sys/cam/ctl/ctl_frontend_iscsi.h 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/sys/cam/ctl/ctl_frontend_iscsi.h 2013-08-18 13:07:25.000000000 +0200 @@ -0,0 +1,112 @@ +/*- + * 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 CTL_FRONTEND_ISCSI_H +#define CTL_FRONTEND_ISCSI_H + +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]; + char ct_alias[CTL_ISCSI_ALIAS_LEN]; +}; + +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_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 mtx cs_lock; + struct icl_conn *cs_conn; + uint32_t cs_cmdsn; + uint32_t cs_statsn; + uint32_t cs_target_transfer_tag; + volatile u_int cs_outstanding_ctl_pdus; + TAILQ_HEAD(, cfiscsi_data_wait) cs_waiting_for_data_out; + struct cfiscsi_target *cs_target; + struct callout cs_callout; + int cs_timeout; + int cs_portal_group_tag; + struct cv cs_maintenance_cv; + int cs_terminating; + size_t cs_max_data_segment_length; + size_t cs_max_burst_length; + bool cs_immediate_data; + char cs_initiator_name[CTL_ISCSI_NAME_LEN]; + char cs_initiator_addr[CTL_ISCSI_ADDR_LEN]; + char cs_initiator_alias[CTL_ISCSI_ALIAS_LEN]; + unsigned int cs_id; + int cs_ctl_initid; +#ifdef ICL_KERNEL_PROXY + bool cs_login_phase; + bool cs_waiting_for_ctld; + struct cv cs_login_cv; + struct icl_pdu *cs_login_pdu; +#endif +}; + +#ifdef ICL_KERNEL_PROXY +struct icl_listen; +#endif + +struct cfiscsi_softc { + struct ctl_frontend fe; + struct mtx lock; + char port_name[32]; + 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; +#ifdef ICL_KERNEL_PROXY + struct icl_listen *listener; + struct cv accept_cv; +#endif +}; + +#endif /* !CTL_FRONTEND_ISCSI_H */ diff -urNp p4/freebsd/src/sys/cam/ctl/ctl_io.h p4/iscsi/sys/cam/ctl/ctl_io.h --- p4/freebsd/src/sys/cam/ctl/ctl_io.h 2013-08-20 09:20:21.000000000 +0200 +++ p4/iscsi/sys/cam/ctl/ctl_io.h 2013-07-23 23:42:57.000000000 +0200 @@ -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 p4/freebsd/src/sys/cam/ctl/ctl_ioctl.h p4/iscsi/sys/cam/ctl/ctl_ioctl.h --- p4/freebsd/src/sys/cam/ctl/ctl_ioctl.h 2013-08-20 09:20:21.000000000 +0200 +++ p4/iscsi/sys/cam/ctl/ctl_ioctl.h 2013-08-18 13:07:25.000000000 +0200 @@ -40,6 +40,12 @@ #ifndef _CTL_IOCTL_H_ #define _CTL_IOCTL_H_ +#ifdef ICL_KERNEL_PROXY +#include +#endif + +#include + #define CTL_DEFAULT_DEV "/dev/cam/ctl" /* * Maximum number of targets we support. @@ -588,6 +594,168 @@ 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, +#ifdef ICL_KERNEL_PROXY + CTL_ISCSI_LISTEN, + CTL_ISCSI_ACCEPT, + CTL_ISCSI_SEND, + CTL_ISCSI_RECEIVE, + CTL_ISCSI_CLOSE, +#endif +} 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' */ +#define CTL_ISCSI_ALIAS_LEN 128 /* Arbitrary. */ + +struct ctl_iscsi_handoff_params { + char initiator_name[CTL_ISCSI_NAME_LEN]; + char initiator_addr[CTL_ISCSI_ADDR_LEN]; + char initiator_alias[CTL_ISCSI_ALIAS_LEN]; + char target_name[CTL_ISCSI_NAME_LEN]; +#ifdef ICL_KERNEL_PROXY + int connection_id; + /* + * XXX + */ + int socket; +#else + int socket; +#endif + 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 first_burst_length; + uint32_t immediate_data; +}; + +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 */ +}; + +#ifdef ICL_KERNEL_PROXY +struct ctl_iscsi_listen_params { + int iser; + int domain; + int socktype; + int protocol; + struct sockaddr *addr; + socklen_t addrlen; +}; + +struct ctl_iscsi_accept_params { + int connection_id; +}; + +struct ctl_iscsi_send_params { + int connection_id; + void *bhs; + size_t spare; + void *spare2; + size_t data_segment_len; + void *data_segment; +}; + +struct ctl_iscsi_receive_params { + int connection_id; + void *bhs; + size_t spare; + void *spare2; + size_t data_segment_len; + void *data_segment; +}; + +struct ctl_iscsi_close_params { + int connection_id; +}; +#endif /* ICL_KERNEL_PROXY */ + +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; +#ifdef ICL_KERNEL_PROXY + struct ctl_iscsi_listen_params listen; + struct ctl_iscsi_accept_params accept; + struct ctl_iscsi_send_params send; + struct ctl_iscsi_receive_params receive; + struct ctl_iscsi_close_params close; +#endif +}; + +/* + * 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 +780,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 p4/freebsd/src/sys/conf/NOTES p4/iscsi/sys/conf/NOTES --- p4/freebsd/src/sys/conf/NOTES 2013-08-21 09:45:16.000000000 +0200 +++ p4/iscsi/sys/conf/NOTES 2013-08-21 10:49:32.000000000 +0200 @@ -1529,6 +1529,7 @@ device ahb device ahc device ahd device esp +device iscsi device iscsi_initiator device isp hint.isp.0.disable="1" diff -urNp p4/freebsd/src/sys/conf/files p4/iscsi/sys/conf/files --- p4/freebsd/src/sys/conf/files 2013-08-22 23:16:07.000000000 +0200 +++ p4/iscsi/sys/conf/files 2013-08-23 00:09:30.000000000 +0200 @@ -124,6 +124,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 @@ -1521,6 +1522,9 @@ ipw_monitor.fw optional ipwmonitorfw | compile-with "${NORMAL_FW}" \ no-obj no-implicit-rule \ clean "ipw_monitor.fw" +dev/iscsi/icl.c optional iscsi scbus +dev/iscsi/icl_proxy.c optional iscsi scbus +dev/iscsi/iscsi.c optional iscsi scbus dev/iscsi_initiator/iscsi.c optional iscsi_initiator scbus dev/iscsi_initiator/iscsi_subr.c optional iscsi_initiator scbus dev/iscsi_initiator/isc_cam.c optional iscsi_initiator scbus diff -urNp p4/freebsd/src/sys/dev/iscsi/icl.c p4/iscsi/sys/dev/iscsi/icl.c --- p4/freebsd/src/sys/dev/iscsi/icl.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/sys/dev/iscsi/icl.c 2013-08-20 11:52:22.000000000 +0200 @@ -0,0 +1,1292 @@ +/*- + * 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$ + */ + +/* + * iSCSI Common Layer. It's used by both the initiator and target to send + * and receive iSCSI PDUs. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "icl.h" +#include "iscsi_proto.h" + +SYSCTL_NODE(_kern, OID_AUTO, icl, CTLFLAG_RD, 0, "iSCSI Common Layer"); +static int debug = 1; +TUNABLE_INT("kern.icl.debug", &debug); +SYSCTL_INT(_kern_icl, OID_AUTO, debug, CTLFLAG_RW, + &debug, 1, "Enable debug messages"); +static int partial_receive_len = 1 * 1024; /* XXX: More? */ +TUNABLE_INT("kern.icl.partial_receive_len", &partial_receive_len); +SYSCTL_INT(_kern_icl, OID_AUTO, partial_receive_len, CTLFLAG_RW, + &partial_receive_len, 1 * 1024, "Minimum read size for partially received " + "data segment"); + +static uma_zone_t icl_conn_zone; +static uma_zone_t icl_pdu_zone; + +static volatile u_int icl_ncons; + +#define ICL_DEBUG(X, ...) \ + if (debug > 1) { \ + printf("%s: " X "\n", __func__, ## __VA_ARGS__);\ + } while (0) + +#define ICL_WARN(X, ...) \ + if (debug > 0) { \ + printf("WARNING: %s: " X "\n", \ + __func__, ## __VA_ARGS__); \ + } while (0) + +#define ICL_CONN_LOCK(X) mtx_lock(&X->ic_lock) +#define ICL_CONN_UNLOCK(X) mtx_unlock(&X->ic_lock) +#define ICL_CONN_LOCK_ASSERT(X) mtx_assert(&X->ic_lock, MA_OWNED) + +static uma_zone_t icl_pdu_zone; + +static void +icl_conn_fail(struct icl_conn *ic) +{ + if (ic->ic_socket == NULL) + return; + + /* + * XXX + */ + ic->ic_socket->so_error = EDOOFUS; + (ic->ic_error)(ic); +} + +static struct mbuf * +icl_conn_receive(struct icl_conn *ic, size_t len) +{ + struct uio uio; + struct socket *so; + struct mbuf *m; + int error, flags; + + so = ic->ic_socket; + + memset(&uio, 0, sizeof(uio)); + uio.uio_resid = len; + + flags = MSG_DONTWAIT; + error = soreceive(so, NULL, &uio, &m, NULL, &flags); + if (error != 0) { + ICL_DEBUG("soreceive error %d", error); + return (NULL); + } + if (uio.uio_resid != 0) { + m_freem(m); + ICL_DEBUG("short read"); + return (NULL); + } + + return (m); +} + +static struct icl_pdu * +icl_pdu_new(struct icl_conn *ic, int flags) +{ + struct icl_pdu *ip; + + refcount_acquire(&ic->ic_outstanding_pdus); + ip = uma_zalloc(icl_pdu_zone, flags | M_ZERO); + if (ip == NULL) { + ICL_WARN("failed to allocate %zd bytes", sizeof(*ip)); + refcount_release(&ic->ic_outstanding_pdus); + return (NULL); + } + + ip->ip_conn = ic; + + return (ip); +} + +void +icl_pdu_free(struct icl_pdu *ip) +{ + struct icl_conn *ic; + + ic = ip->ip_conn; + + m_freem(ip->ip_bhs_mbuf); + m_freem(ip->ip_ahs_mbuf); + m_freem(ip->ip_data_mbuf); + uma_zfree(icl_pdu_zone, ip); + refcount_release(&ic->ic_outstanding_pdus); +} + +/* + * Allocate icl_pdu with empty BHS to fill up by the caller. + */ +struct icl_pdu * +icl_pdu_new_bhs(struct icl_conn *ic, int flags) +{ + struct icl_pdu *ip; + + ip = icl_pdu_new(ic, flags); + if (ip == NULL) + return (NULL); + + ip->ip_bhs_mbuf = m_getm2(NULL, sizeof(struct iscsi_bhs), + flags, MT_DATA, M_PKTHDR); + if (ip->ip_bhs_mbuf == NULL) { + ICL_WARN("failed to allocate %zd bytes", sizeof(*ip)); + icl_pdu_free(ip); + return (NULL); + } + ip->ip_bhs = mtod(ip->ip_bhs_mbuf, struct iscsi_bhs *); + memset(ip->ip_bhs, 0, sizeof(struct iscsi_bhs)); + ip->ip_bhs_mbuf->m_len = sizeof(struct iscsi_bhs); + + return (ip); +} + +static int +icl_pdu_ahs_length(const struct icl_pdu *request) +{ + + return (request->ip_bhs->bhs_total_ahs_len * 4); +} + +int +icl_pdu_data_segment_length(const struct icl_pdu *request) +{ + uint32_t len = 0; + + len += request->ip_bhs->bhs_data_segment_len[0]; + len <<= 8; + len += request->ip_bhs->bhs_data_segment_len[1]; + len <<= 8; + len += request->ip_bhs->bhs_data_segment_len[2]; + + return (len); +} + +static void +icl_pdu_set_data_segment_length(struct icl_pdu *response, uint32_t len) +{ + + response->ip_bhs->bhs_data_segment_len[2] = len; + response->ip_bhs->bhs_data_segment_len[1] = len >> 8; + response->ip_bhs->bhs_data_segment_len[0] = len >> 16; +} + +static size_t +icl_pdu_padding(const struct icl_pdu *ip) +{ + + if ((ip->ip_data_len % 4) != 0) + return (4 - (ip->ip_data_len % 4)); + + return (0); +} + +static size_t +icl_pdu_size(const struct icl_pdu *response) +{ + size_t len; + + KASSERT(response->ip_ahs_len == 0, ("responding with AHS")); + + len = sizeof(struct iscsi_bhs) + response->ip_data_len + + icl_pdu_padding(response); + if (response->ip_conn->ic_header_crc32c) + len += ISCSI_HEADER_DIGEST_SIZE; + if (response->ip_conn->ic_data_crc32c) + len += ISCSI_DATA_DIGEST_SIZE; + + return (len); +} + +static int +icl_pdu_receive_bhs(struct icl_pdu *request, size_t *availablep) +{ + struct mbuf *m; + + m = icl_conn_receive(request->ip_conn, sizeof(struct iscsi_bhs)); + if (m == NULL) { + ICL_DEBUG("failed to receive BHS"); + return (-1); + } + + request->ip_bhs_mbuf = m_pullup(m, sizeof(struct iscsi_bhs)); + if (request->ip_bhs_mbuf == NULL) { + ICL_WARN("m_pullup failed"); + return (-1); + } + request->ip_bhs = mtod(request->ip_bhs_mbuf, struct iscsi_bhs *); + + /* + * XXX: For architectures with strict alignment requirements + * we may need to allocate ip_bhs and copy the data into it. + * For some reason, though, not doing this doesn't seem + * to cause problems; tested on sparc64. + */ + + *availablep -= sizeof(struct iscsi_bhs); + return (0); +} + +static int +icl_pdu_receive_ahs(struct icl_pdu *request, size_t *availablep) +{ + + request->ip_ahs_len = icl_pdu_ahs_length(request); + if (request->ip_ahs_len == 0) + return (0); + + request->ip_ahs_mbuf = icl_conn_receive(request->ip_conn, + request->ip_ahs_len); + if (request->ip_ahs_mbuf == NULL) { + ICL_DEBUG("failed to receive AHS"); + return (-1); + } + + *availablep -= request->ip_ahs_len; + return (0); +} + +static uint32_t +icl_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 +icl_pdu_check_header_digest(struct icl_pdu *request, size_t *availablep) +{ + struct mbuf *m; + uint32_t received_digest, valid_digest; + + if (request->ip_conn->ic_header_crc32c == false) + return (0); + + m = icl_conn_receive(request->ip_conn, ISCSI_HEADER_DIGEST_SIZE); + if (m == NULL) { + ICL_DEBUG("failed to receive header digest"); + 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 = icl_mbuf_to_crc32c(request->ip_bhs_mbuf); + if (received_digest != valid_digest) { + ICL_WARN("header digest check failed; got 0x%x, " + "should be 0x%x", received_digest, valid_digest); + return (-1); + } + + return (0); +} + +/* + * Return the number of bytes that should be waiting in the receive socket + * before icl_pdu_receive_data_segment() gets called. + */ +static size_t +icl_pdu_data_segment_receive_len(const struct icl_pdu *request) +{ + size_t len; + + len = icl_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->ip_data_len, ("len <= request->ip_data_len")); + len -= request->ip_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 + ICL_DEBUG("need %zd bytes of data, limiting to %zd", + len, partial_receive_len)); +#endif + len = partial_receive_len; + + return (len); + } + + /* + * Account for padding. Note that due to the way code is written, + * the icl_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 + ICL_DEBUG("need %zd bytes of data", len)); +#endif + + return (len); +} + +static int +icl_pdu_receive_data_segment(struct icl_pdu *request, + size_t *availablep, bool *more_neededp) +{ + struct icl_conn *ic; + size_t len, padding = 0; + struct mbuf *m; + + ic = request->ip_conn; + + *more_neededp = false; + ic->ic_receive_len = 0; + + len = icl_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->ip_data_len, ("len <= request->ip_data_len")); + len -= request->ip_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 + ICL_DEBUG("limited from %zd to %zd", + 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 = icl_conn_receive(request->ip_conn, len + padding); + if (m == NULL) { + ICL_DEBUG("failed to receive data segment"); + return (-1); + } + + if (request->ip_data_mbuf == NULL) + request->ip_data_mbuf = m; + else + m_cat(request->ip_data_mbuf, m); + + request->ip_data_len += len; + *availablep -= len + padding; + } else + ICL_DEBUG("len 0"); + + if (*more_neededp) + ic->ic_receive_len = + icl_pdu_data_segment_receive_len(request); + + return (0); +} + +static int +icl_pdu_check_data_digest(struct icl_pdu *request, size_t *availablep) +{ + struct mbuf *m; + uint32_t received_digest, valid_digest; + + if (request->ip_conn->ic_data_crc32c == false) + return (0); + + if (request->ip_data_len == 0) + return (0); + + m = icl_conn_receive(request->ip_conn, ISCSI_DATA_DIGEST_SIZE); + if (m == NULL) { + ICL_DEBUG("failed to receive data digest"); + 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 ip_data_mbuf also contains padding; since digest + * calculation is supposed to include that, we iterate over + * the entire ip_data_mbuf chain, not just ip_data_len bytes of it. + */ + valid_digest = icl_mbuf_to_crc32c(request->ip_data_mbuf); + if (received_digest != valid_digest) { + ICL_WARN("data digest check failed; got 0x%x, " + "should be 0x%x", 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 icl_pdu * +icl_conn_receive_pdu(struct icl_conn *ic, size_t *availablep) +{ + struct icl_pdu *request; + struct socket *so; + size_t len; + int error; + bool more_needed; + + so = ic->ic_socket; + + if (ic->ic_receive_state == ICL_CONN_STATE_BHS) { + KASSERT(ic->ic_receive_pdu == NULL, + ("ic->ic_receive_pdu != NULL")); + request = icl_pdu_new(ic, M_NOWAIT); + if (request == NULL) { + ICL_DEBUG("failed to allocate PDU; " + "dropping connection"); + icl_conn_fail(ic); + return (NULL); + } + ic->ic_receive_pdu = request; + } else { + KASSERT(ic->ic_receive_pdu != NULL, + ("ic->ic_receive_pdu == NULL")); + request = ic->ic_receive_pdu; + } + + if (*availablep < ic->ic_receive_len) { +#if 0 + ICL_DEBUG("not enough data; need %zd, " + "have %zd", ic->ic_receive_len, *availablep); +#endif + return (NULL); + } + + switch (ic->ic_receive_state) { + case ICL_CONN_STATE_BHS: + //ICL_DEBUG("receiving BHS"); + error = icl_pdu_receive_bhs(request, availablep); + if (error != 0) { + ICL_DEBUG("failed to receive BHS; " + "dropping connection"); + break; + } + + /* + * We don't enforce any limit for AHS length; + * its length is stored in 8 bit field. + */ + + len = icl_pdu_data_segment_length(request); + if (len > ic->ic_max_data_segment_length) { + ICL_WARN("received data segment " + "length %zd is larger than negotiated " + "MaxDataSegmentLength %zd; " + "dropping connection", + len, ic->ic_max_data_segment_length); + break; + } + + ic->ic_receive_state = ICL_CONN_STATE_AHS; + ic->ic_receive_len = icl_pdu_ahs_length(request); + break; + + case ICL_CONN_STATE_AHS: + //ICL_DEBUG("receiving AHS"); + error = icl_pdu_receive_ahs(request, availablep); + if (error != 0) { + ICL_DEBUG("failed to receive AHS; " + "dropping connection"); + break; + } + ic->ic_receive_state = ICL_CONN_STATE_HEADER_DIGEST; + if (ic->ic_header_crc32c == false) + ic->ic_receive_len = 0; + else + ic->ic_receive_len = ISCSI_HEADER_DIGEST_SIZE; + break; + + case ICL_CONN_STATE_HEADER_DIGEST: + //ICL_DEBUG("receiving header digest"); + error = icl_pdu_check_header_digest(request, availablep); + if (error != 0) { + ICL_DEBUG("header digest failed; " + "dropping connection"); + break; + } + + ic->ic_receive_state = ICL_CONN_STATE_DATA; + ic->ic_receive_len = + icl_pdu_data_segment_receive_len(request); + break; + + case ICL_CONN_STATE_DATA: + //ICL_DEBUG("receiving data segment"); + error = icl_pdu_receive_data_segment(request, availablep, + &more_needed); + if (error != 0) { + ICL_DEBUG("failed to receive data segment;" + "dropping connection"); + break; + } + + if (more_needed) + break; + + ic->ic_receive_state = ICL_CONN_STATE_DATA_DIGEST; + if (ic->ic_data_crc32c == false) + ic->ic_receive_len = 0; + else + ic->ic_receive_len = ISCSI_DATA_DIGEST_SIZE; + break; + + case ICL_CONN_STATE_DATA_DIGEST: + //ICL_DEBUG("receiving data digest"); + error = icl_pdu_check_data_digest(request, availablep); + if (error != 0) { + ICL_DEBUG("data digest failed; " + "dropping connection"); + break; + } + + /* + * We've received complete PDU; reset the receive state machine + * and return the PDU. + */ + ic->ic_receive_state = ICL_CONN_STATE_BHS; + ic->ic_receive_len = sizeof(struct iscsi_bhs); + ic->ic_receive_pdu = NULL; + return (request); + + default: + panic("invalid ic_receive_state %d\n", ic->ic_receive_state); + } + + if (error != 0) { + icl_pdu_free(request); + icl_conn_fail(ic); + } + + return (NULL); +} + +static void +icl_conn_receive_pdus(struct icl_conn *ic, size_t available) +{ + struct icl_pdu *response; + struct socket *so; + + so = ic->ic_socket; + + /* + * This can never happen; we're careful to only mess with ic->ic_socket + * pointer when the send/receive threads are not running. + */ + KASSERT(so != NULL, ("NULL socket")); + + for (;;) { + if (ic->ic_disconnecting) + return; + + if (so->so_error != 0) { + ICL_DEBUG("connection error %d; " + "dropping connection", so->so_error); + icl_conn_fail(ic); + return; + } + + /* + * Loop until we have a complete PDU or there is not enough + * data in the socket buffer. + */ + if (available < ic->ic_receive_len) { +#if 0 + ICL_DEBUG("not enough data; have %zd, " + "need %zd", available, + ic->ic_receive_len); +#endif + return; + } + + response = icl_conn_receive_pdu(ic, &available); + if (response == NULL) + continue; + + if (response->ip_ahs_len > 0) { + ICL_WARN("received PDU with unsupported " + "AHS; opcode 0x%x; dropping connection", + response->ip_bhs->bhs_opcode); + icl_pdu_free(response); + icl_conn_fail(ic); + return; + } + + (ic->ic_receive)(response); + } +} + +static void +icl_receive_thread(void *arg) +{ + struct icl_conn *ic; + size_t available; + struct socket *so; + + ic = arg; + so = ic->ic_socket; + + ICL_CONN_LOCK(ic); + ic->ic_receive_running = true; + ICL_CONN_UNLOCK(ic); + + for (;;) { + if (ic->ic_disconnecting) { + //ICL_DEBUG("terminating"); + ICL_CONN_LOCK(ic); + ic->ic_receive_running = false; + ICL_CONN_UNLOCK(ic); + kthread_exit(); + return; + } + + SOCKBUF_LOCK(&so->so_rcv); + available = so->so_rcv.sb_cc; + if (available < ic->ic_receive_len) { + so->so_rcv.sb_lowat = ic->ic_receive_len; + cv_wait(&ic->ic_receive_cv, &so->so_rcv.sb_mtx); + } + SOCKBUF_UNLOCK(&so->so_rcv); + + icl_conn_receive_pdus(ic, available); + } +} + +static int +icl_soupcall_receive(struct socket *so, void *arg, int waitflag) +{ + struct icl_conn *ic; + + ic = arg; + cv_signal(&ic->ic_receive_cv); + return (SU_OK); +} + +static int +icl_pdu_send(struct icl_pdu *request) +{ + size_t padding, pdu_len; + uint32_t digest, zero = 0; + int error, ok; + struct socket *so; + struct icl_conn *ic; + + ic = request->ip_conn; + so = request->ip_conn->ic_socket; + + ICL_CONN_LOCK_ASSERT(ic); + + icl_pdu_set_data_segment_length(request, request->ip_data_len); + + pdu_len = icl_pdu_size(request); + + if (ic->ic_header_crc32c) { + digest = icl_mbuf_to_crc32c(request->ip_bhs_mbuf); + ok = m_append(request->ip_bhs_mbuf, sizeof(digest), + (void *)&digest); + if (ok != 1) { + ICL_WARN("failed to append header digest"); + return (1); + } + } + + if (request->ip_data_len != 0) { + padding = icl_pdu_padding(request); + if (padding > 0) { + ok = m_append(request->ip_data_mbuf, padding, + (void *)&zero); + if (ok != 1) { + ICL_WARN("failed to append padding"); + return (1); + } + } + + if (ic->ic_data_crc32c) { + digest = icl_mbuf_to_crc32c(request->ip_data_mbuf); + + ok = m_append(request->ip_data_mbuf, sizeof(digest), + (void *)&digest); + if (ok != 1) { + ICL_WARN("failed to append header digest"); + return (1); + } + } + + m_cat(request->ip_bhs_mbuf, request->ip_data_mbuf); + request->ip_data_mbuf = NULL; + } + + request->ip_bhs_mbuf->m_pkthdr.len = pdu_len; + + error = sosend(so, NULL, NULL, request->ip_bhs_mbuf, + NULL, MSG_DONTWAIT, curthread); + request->ip_bhs_mbuf = NULL; /* Sosend consumes the mbuf. */ + if (error != 0) { + ICL_DEBUG("sosend error %d", error); + return (error); + } + + return (0); +} + +static void +icl_conn_send_pdus(struct icl_conn *ic) +{ + struct icl_pdu *request; + struct socket *so; + size_t available, size; + int error; + + ICL_CONN_LOCK_ASSERT(ic); + + so = ic->ic_socket; + + SOCKBUF_LOCK(&so->so_snd); + available = sbspace(&so->so_snd); + SOCKBUF_UNLOCK(&so->so_snd); + + while (!TAILQ_EMPTY(&ic->ic_to_send)) { + if (ic->ic_disconnecting) + return; + + request = TAILQ_FIRST(&ic->ic_to_send); + size = icl_pdu_size(request); + if (available < size) { + /* + * Set the low watermark on the socket, + * to avoid waking up until there is enough + * space. + */ + SOCKBUF_LOCK(&so->so_snd); + so->so_snd.sb_lowat = size; + SOCKBUF_UNLOCK(&so->so_snd); +#if 1 + ICL_DEBUG("no space to send; " + "have %zd, need %zd", + available, size); +#endif + return; + } + available -= size; + TAILQ_REMOVE(&ic->ic_to_send, request, ip_next); + error = icl_pdu_send(request); + if (error != 0) { + ICL_DEBUG("failed to send PDU; " + "dropping connection"); + icl_conn_fail(ic); + return; + } + icl_pdu_free(request); + } +} + +static void +icl_send_thread(void *arg) +{ + struct icl_conn *ic; + + ic = arg; + + ICL_CONN_LOCK(ic); + ic->ic_send_running = true; + ICL_CONN_UNLOCK(ic); + + for (;;) { + ICL_CONN_LOCK(ic); + if (ic->ic_disconnecting) { + //ICL_DEBUG("terminating"); + ic->ic_send_running = false; + ICL_CONN_UNLOCK(ic); + kthread_exit(); + return; + } + if (TAILQ_EMPTY(&ic->ic_to_send)) + cv_wait(&ic->ic_send_cv, &ic->ic_lock); + icl_conn_send_pdus(ic); + ICL_CONN_UNLOCK(ic); + } +} + +static int +icl_soupcall_send(struct socket *so, void *arg, int waitflag) +{ + struct icl_conn *ic; + + ic = arg; + cv_signal(&ic->ic_send_cv); + return (SU_OK); +} + +void +icl_pdu_append_data(struct icl_pdu *request, 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) { + ICL_WARN("failed to allocate mbuf for %zd bytes", len); + icl_conn_fail(request->ip_conn); + 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 (request->ip_data_mbuf == NULL) { + request->ip_data_mbuf = newmb; + request->ip_data_len = len; + } else { + m_cat(request->ip_data_mbuf, newmb); + request->ip_data_len += len; + } +} + +void +icl_pdu_get_data(struct icl_pdu *ip, size_t off, void *addr, size_t len) +{ + + m_copydata(ip->ip_data_mbuf, off, len, addr); +} + +void +icl_pdu_queue(struct icl_pdu *ip) +{ + struct icl_conn *ic; + + ic = ip->ip_conn; + + ICL_CONN_LOCK(ic); + if (ic->ic_disconnecting || ic->ic_socket == NULL) { + ICL_DEBUG("icl_pdu_queue on closed connection"); + ICL_CONN_UNLOCK(ic); + return; + } + TAILQ_INSERT_TAIL(&ic->ic_to_send, ip, ip_next); + ICL_CONN_UNLOCK(ic); + cv_signal(&ic->ic_send_cv); +} + +struct icl_conn * +icl_conn_new(void) +{ + struct icl_conn *ic; + + refcount_acquire(&icl_ncons); + + ic = uma_zalloc(icl_conn_zone, M_WAITOK | M_ZERO); + + TAILQ_INIT(&ic->ic_to_send); + mtx_init(&ic->ic_lock, "icl_lock", NULL, MTX_DEF); + cv_init(&ic->ic_send_cv, "icl_tx"); + cv_init(&ic->ic_receive_cv, "icl_rx"); + refcount_init(&ic->ic_outstanding_pdus, 0); + ic->ic_max_data_segment_length = ICL_MAX_DATA_SEGMENT_LENGTH; + + return (ic); +} + +void +icl_conn_free(struct icl_conn *ic) +{ + + mtx_destroy(&ic->ic_lock); + cv_destroy(&ic->ic_send_cv); + cv_destroy(&ic->ic_receive_cv); + uma_zfree(icl_conn_zone, ic); + refcount_release(&icl_ncons); +} + +static int +icl_conn_start(struct icl_conn *ic) +{ + size_t bufsize; + struct sockopt opt; + int error, one = 1; + + ICL_CONN_LOCK(ic); + + /* + * XXX: Ugly hack. + */ + if (ic->ic_socket == NULL) { + ICL_CONN_UNLOCK(ic); + return (EINVAL); + } + + ic->ic_receive_state = ICL_CONN_STATE_BHS; + ic->ic_receive_len = sizeof(struct iscsi_bhs); + ic->ic_disconnecting = false; + + ICL_CONN_UNLOCK(ic); + + /* + * 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. + */ + bufsize = (sizeof(struct iscsi_bhs) + + ic->ic_max_data_segment_length) * 8; + error = soreserve(ic->ic_socket, bufsize, bufsize); + if (error != 0) { + ICL_WARN("soreserve failed with error %d", error); + icl_conn_close(ic); + return (error); + } + + /* + * 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(ic->ic_socket, &opt); + if (error != 0) { + ICL_WARN("disabling TCP_NODELAY failed with error %d", error); + icl_conn_close(ic); + return (error); + } + + /* + * Start threads. + */ + error = kthread_add(icl_send_thread, ic, NULL, NULL, 0, 0, "icltx"); + if (error != 0) { + ICL_WARN("kthread_add(9) failed with error %d", error); + icl_conn_close(ic); + return (error); + } + + error = kthread_add(icl_receive_thread, ic, NULL, NULL, 0, 0, "iclrx"); + if (error != 0) { + ICL_WARN("kthread_add(9) failed with error %d", error); + icl_conn_close(ic); + return (error); + } + + /* + * Register socket upcall, to get notified about incoming PDUs + * and free space to send outgoing ones. + */ + SOCKBUF_LOCK(&ic->ic_socket->so_snd); + soupcall_set(ic->ic_socket, SO_SND, icl_soupcall_send, ic); + SOCKBUF_UNLOCK(&ic->ic_socket->so_snd); + SOCKBUF_LOCK(&ic->ic_socket->so_rcv); + soupcall_set(ic->ic_socket, SO_RCV, icl_soupcall_receive, ic); + SOCKBUF_UNLOCK(&ic->ic_socket->so_rcv); + + return (0); +} + +int +icl_conn_handoff(struct icl_conn *ic, int fd) +{ + struct file *fp; + struct socket *so; + int error; + + /* + * Steal the socket from userland. + */ + error = fget(curthread, fd, CAP_SOCK_ALL, &fp); + if (error != 0) { + return (error); + } + if (fp->f_type != DTYPE_SOCKET) { + fdrop(fp, curthread); + return (EINVAL); + } + so = fp->f_data; + if (so->so_type != SOCK_STREAM) { + fdrop(fp, curthread); + return (EINVAL); + } + + ICL_CONN_LOCK(ic); + + if (ic->ic_socket != NULL) { + ICL_CONN_UNLOCK(ic); + fdrop(fp, curthread); + return (EBUSY); + } + + ic->ic_socket = fp->f_data; + fp->f_ops = &badfileops; + fp->f_data = NULL; + fdrop(fp, curthread); + ICL_CONN_UNLOCK(ic); + + error = icl_conn_start(ic); + + return (error); +} + +void +icl_conn_shutdown(struct icl_conn *ic) +{ + + ICL_CONN_LOCK(ic); + if (ic->ic_socket == NULL) { + ICL_CONN_UNLOCK(ic); + return; + } + ICL_CONN_UNLOCK(ic); + + soshutdown(ic->ic_socket, SHUT_RDWR); +} + +void +icl_conn_close(struct icl_conn *ic) +{ + struct icl_pdu *pdu; + + ICL_CONN_LOCK(ic); + if (ic->ic_socket == NULL) { + ICL_CONN_UNLOCK(ic); + return; + } + + ic->ic_disconnecting = true; + + /* + * Wake up the threads, so they can properly terminate. + */ + cv_signal(&ic->ic_receive_cv); + cv_signal(&ic->ic_send_cv); + while (ic->ic_receive_running || ic->ic_send_running) { + //ICL_DEBUG("waiting for send/receive threads to terminate"); + ICL_CONN_UNLOCK(ic); + cv_signal(&ic->ic_receive_cv); + cv_signal(&ic->ic_send_cv); + pause("icl_close", 1 * hz); + ICL_CONN_LOCK(ic); + } + //ICL_DEBUG("send/receive threads terminated"); + + soclose(ic->ic_socket); + ic->ic_socket = NULL; + + if (ic->ic_receive_pdu != NULL) { + //ICL_DEBUG("freeing partially received PDU"); + icl_pdu_free(ic->ic_receive_pdu); + ic->ic_receive_pdu = NULL; + } + + /* + * Remove any outstanding PDUs from the send queue. + */ + while (!TAILQ_EMPTY(&ic->ic_to_send)) { + pdu = TAILQ_FIRST(&ic->ic_to_send); + TAILQ_REMOVE(&ic->ic_to_send, pdu, ip_next); + icl_pdu_free(pdu); + } + + KASSERT(TAILQ_EMPTY(&ic->ic_to_send), + ("destroying session with non-empty send queue")); + /* + * XXX + */ +#if 0 + KASSERT(ic->ic_outstanding_pdus == 0, + ("destroying session with %d outstanding PDUs", + ic->ic_outstanding_pdus)); +#endif + ICL_CONN_UNLOCK(ic); +} + +bool +icl_conn_connected(struct icl_conn *ic) +{ + + ICL_CONN_LOCK(ic); + if (ic->ic_socket == NULL) { + ICL_CONN_UNLOCK(ic); + return (false); + } + if (ic->ic_socket->so_error != 0) { + ICL_CONN_UNLOCK(ic); + return (false); + } + ICL_CONN_UNLOCK(ic); + return (true); +} + +#ifdef ICL_KERNEL_PROXY +int +icl_conn_handoff_sock(struct icl_conn *ic, struct socket *so) +{ + int error; + + if (so->so_type != SOCK_STREAM) + return (EINVAL); + + ICL_CONN_LOCK(ic); + if (ic->ic_socket != NULL) { + ICL_CONN_UNLOCK(ic); + return (EBUSY); + } + ic->ic_socket = so; + ICL_CONN_UNLOCK(ic); + + error = icl_conn_start(ic); + + return (error); +} +#endif /* ICL_KERNEL_PROXY */ + +static int +icl_unload(void) +{ + + if (icl_ncons != 0) + return (EBUSY); + + uma_zdestroy(icl_conn_zone); + uma_zdestroy(icl_pdu_zone); + + return (0); +} + +static void +icl_load(void) +{ + + icl_conn_zone = uma_zcreate("icl_conn", + sizeof(struct icl_conn), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + icl_pdu_zone = uma_zcreate("icl_pdu", + sizeof(struct icl_pdu), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + + refcount_init(&icl_ncons, 0); +} + +static int +icl_modevent(module_t mod, int what, void *arg) +{ + + switch (what) { + case MOD_LOAD: + icl_load(); + return (0); + case MOD_UNLOAD: + return (icl_unload()); + default: + return (EINVAL); + } +} + +moduledata_t icl_data = { + "icl", + icl_modevent, + 0 +}; + +DECLARE_MODULE(icl, icl_data, SI_SUB_DRIVERS, SI_ORDER_FIRST); +MODULE_VERSION(icl, 1); diff -urNp p4/freebsd/src/sys/dev/iscsi/icl.h p4/iscsi/sys/dev/iscsi/icl.h --- p4/freebsd/src/sys/dev/iscsi/icl.h 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/sys/dev/iscsi/icl.h 2013-07-23 23:45:08.000000000 +0200 @@ -0,0 +1,151 @@ +/*- + * 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 ICL_H +#define ICL_H + +/* + * iSCSI Common Layer. It's used by both the initiator and target to send + * and receive iSCSI PDUs. + */ + +struct icl_conn; + +struct icl_pdu { + TAILQ_ENTRY(icl_pdu) ip_next; + struct icl_conn *ip_conn; + struct iscsi_bhs *ip_bhs; + struct mbuf *ip_bhs_mbuf; + size_t ip_ahs_len; + struct mbuf *ip_ahs_mbuf; + size_t ip_data_len; + struct mbuf *ip_data_mbuf; + + /* + * User (initiator or provider) private fields. + */ + uint32_t ip_prv0; + uint32_t ip_prv1; + uint32_t ip_prv2; +}; + +struct icl_pdu *icl_pdu_new_bhs(struct icl_conn *ic, int flags); +int icl_pdu_data_segment_length(const struct icl_pdu *ip); +void icl_pdu_append_data(struct icl_pdu *ip, const void *addr, size_t len); +void icl_pdu_get_data(struct icl_pdu *ip, size_t off, void *addr, size_t len); +void icl_pdu_queue(struct icl_pdu *ip); +void icl_pdu_free(struct icl_pdu *ip); + +#define ICL_CONN_STATE_INVALID 0 +#define ICL_CONN_STATE_BHS 1 +#define ICL_CONN_STATE_AHS 2 +#define ICL_CONN_STATE_HEADER_DIGEST 3 +#define ICL_CONN_STATE_DATA 4 +#define ICL_CONN_STATE_DATA_DIGEST 5 + +#define ICL_MAX_DATA_SEGMENT_LENGTH (128 * 1024) + +struct icl_conn { + struct mtx ic_lock; + struct socket *ic_socket; + volatile u_int ic_outstanding_pdus; + TAILQ_HEAD(, icl_pdu) ic_to_send; + size_t ic_receive_len; + int ic_receive_state; + struct icl_pdu *ic_receive_pdu; + struct cv ic_send_cv; + struct cv ic_receive_cv; + bool ic_header_crc32c; + bool ic_data_crc32c; + bool ic_send_running; + bool ic_receive_running; + size_t ic_max_data_segment_length; + bool ic_disconnecting; + bool ic_iser; + + void (*ic_receive)(struct icl_pdu *); + void (*ic_error)(struct icl_conn *); + + /* + * User (initiator or provider) private fields. + */ + void *ic_prv0; +}; + +struct icl_conn *icl_conn_new(void); +void icl_conn_free(struct icl_conn *ic); +int icl_conn_handoff(struct icl_conn *ic, int fd); +void icl_conn_shutdown(struct icl_conn *ic); +void icl_conn_close(struct icl_conn *ic); +bool icl_conn_connected(struct icl_conn *ic); + +#ifdef ICL_KERNEL_PROXY + +struct sockaddr; +struct icl_listen; + +struct icl_listen_sock { + TAILQ_ENTRY(icl_listen_sock) ils_next; + struct icl_listen *ils_listen; + struct socket *ils_socket; + bool ils_running; + bool ils_disconnecting; +}; + +struct icl_listen { + TAILQ_HEAD(, icl_listen_sock) il_sockets; + struct sx il_lock; + void (*il_accept)(struct socket *); +}; + +/* + * Initiator part. + */ +int icl_conn_connect(struct icl_conn *ic, bool rdma, + int domain, int socktype, int protocol, + struct sockaddr *from_sa, struct sockaddr *to_sa); +/* + * Target part. + */ +struct icl_listen *icl_listen_new(void (*accept_cb)(struct socket *)); +void icl_listen_free(struct icl_listen *il); +int icl_listen_add(struct icl_listen *il, bool rdma, int domain, + int socktype, int protocol, struct sockaddr *sa); +int icl_listen_remove(struct icl_listen *il, struct sockaddr *sa); + +/* + * This one is not a public API; only to be used by icl_proxy.c. + */ +int icl_conn_handoff_sock(struct icl_conn *ic, struct socket *so); + +#endif /* ICL_KERNEL_PROXY */ + +#endif /* !ICL_H */ diff -urNp p4/freebsd/src/sys/dev/iscsi/icl_proxy.c p4/iscsi/sys/dev/iscsi/icl_proxy.c --- p4/freebsd/src/sys/dev/iscsi/icl_proxy.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/sys/dev/iscsi/icl_proxy.c 2013-07-23 23:45:08.000000000 +0200 @@ -0,0 +1,397 @@ +/*- + * 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$ + */ +/*- + * Copyright (c) 1982, 1986, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * sendfile(2) and related extensions: + * Copyright (c) 1998, David Greenman. All rights reserved. + * + * 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. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)uipc_syscalls.c 8.4 (Berkeley) 2/21/94 + */ + +/* + * iSCSI Common Layer, kernel proxy part. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "icl.h" + +#ifdef ICL_KERNEL_PROXY + +static int debug = 1; + +#define ICL_DEBUG(X, ...) \ + if (debug > 1) { \ + printf("%s: " X "\n", __func__, ## __VA_ARGS__);\ + } while (0) + +#define ICL_WARN(X, ...) \ + if (debug > 0) { \ + printf("WARNING: %s: " X "\n", \ + __func__, ## __VA_ARGS__); \ + } while (0) + +MALLOC_DEFINE(M_ICL_PROXY, "ICL_PROXY", "iSCSI common layer proxy"); + +#ifdef ICL_RDMA +static int icl_conn_connect_rdma(struct icl_conn *ic, int domain, int socktype, + int protocol, struct sockaddr *from_sa, struct sockaddr *to_sa); +static int icl_listen_add_rdma(struct icl_listen *il, int domain, int socktype, int protocol, + struct sockaddr *sa); +#endif /* ICL_RDMA */ + +static int +icl_conn_connect_tcp(struct icl_conn *ic, int domain, int socktype, + int protocol, struct sockaddr *from_sa, struct sockaddr *to_sa) +{ + struct socket *so; + int error; + int interrupted = 0; + + error = socreate(domain, &so, socktype, protocol, + curthread->td_ucred, curthread); + if (error != 0) + return (error); + + if (from_sa != NULL) { + error = sobind(so, from_sa, curthread); + if (error != 0) { + soclose(so); + return (error); + } + } + + error = soconnect(so, to_sa, curthread); + if (error != 0) { + soclose(so); + return (error); + } + + SOCK_LOCK(so); + while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0) { + error = msleep(&so->so_timeo, SOCK_MTX(so), PSOCK | PCATCH, + "icl_connect", 0); + if (error) { + if (error == EINTR || error == ERESTART) + interrupted = 1; + break; + } + } + if (error == 0) { + error = so->so_error; + so->so_error = 0; + } + SOCK_UNLOCK(so); + + if (error != 0) { + soclose(so); + return (error); + } + + error = icl_conn_handoff_sock(ic, so); + if (error != 0) + soclose(so); + + return (error); +} + +int +icl_conn_connect(struct icl_conn *ic, bool rdma, int domain, int socktype, + int protocol, struct sockaddr *from_sa, struct sockaddr *to_sa) +{ + + if (rdma) { +#ifdef ICL_RDMA + return (icl_conn_connect_rdma(ic, domain, socktype, protocol, from_sa, to_sa)); +#else + ICL_DEBUG("RDMA not supported"); + return (EOPNOTSUPP); +#endif + } + + return (icl_conn_connect_tcp(ic, domain, socktype, protocol, from_sa, to_sa)); +} + +struct icl_listen * +icl_listen_new(void (*accept_cb)(struct socket *)) +{ + struct icl_listen *il; + + il = malloc(sizeof(*il), M_ICL_PROXY, M_ZERO | M_WAITOK); + TAILQ_INIT(&il->il_sockets); + sx_init(&il->il_lock, "icl_listen"); + il->il_accept = accept_cb; + + return (il); +} + +void +icl_listen_free(struct icl_listen *il) +{ + struct icl_listen_sock *ils; + + sx_xlock(&il->il_lock); + while (!TAILQ_EMPTY(&il->il_sockets)) { + ils = TAILQ_FIRST(&il->il_sockets); + while (ils->ils_running) { + ICL_DEBUG("waiting for accept thread to terminate"); + sx_xunlock(&il->il_lock); + ils->ils_disconnecting = true; + wakeup(&ils->ils_socket->so_timeo); + pause("icl_unlisten", 1 * hz); + sx_xlock(&il->il_lock); + } + + TAILQ_REMOVE(&il->il_sockets, ils, ils_next); + soclose(ils->ils_socket); + free(ils, M_ICL_PROXY); + } + sx_xunlock(&il->il_lock); + + free(il, M_ICL_PROXY); +} + +/* + * XXX: Doing accept in a separate thread in each socket might not be the best way + * to do stuff, but it's pretty clean and debuggable - and you probably won't + * have hundreds of listening sockets anyway. + */ +static void +icl_accept_thread(void *arg) +{ + struct icl_listen_sock *ils; + struct socket *head, *so; + struct sockaddr *sa; + int error; + + ils = arg; + head = ils->ils_socket; + + ils->ils_running = true; + + for (;;) { + ACCEPT_LOCK(); + while (TAILQ_EMPTY(&head->so_comp) && head->so_error == 0 && ils->ils_disconnecting == false) { + if (head->so_rcv.sb_state & SBS_CANTRCVMORE) { + head->so_error = ECONNABORTED; + break; + } + error = msleep(&head->so_timeo, &accept_mtx, PSOCK | PCATCH, + "accept", 0); + if (error) { + ACCEPT_UNLOCK(); + ICL_WARN("msleep failed with error %d", error); + continue; + } + if (ils->ils_disconnecting) { + ACCEPT_UNLOCK(); + ICL_DEBUG("terminating"); + ils->ils_running = false; + kthread_exit(); + return; + } + } + if (head->so_error) { + error = head->so_error; + head->so_error = 0; + ACCEPT_UNLOCK(); + ICL_WARN("socket error %d", error); + continue; + } + so = TAILQ_FIRST(&head->so_comp); + KASSERT(so != NULL, ("NULL so")); + KASSERT(!(so->so_qstate & SQ_INCOMP), ("accept1: so SQ_INCOMP")); + KASSERT(so->so_qstate & SQ_COMP, ("accept1: so not SQ_COMP")); + + /* + * Before changing the flags on the socket, we have to bump the + * reference count. Otherwise, if the protocol calls sofree(), + * the socket will be released due to a zero refcount. + */ + SOCK_LOCK(so); /* soref() and so_state update */ + soref(so); /* file descriptor reference */ + + TAILQ_REMOVE(&head->so_comp, so, so_list); + head->so_qlen--; + so->so_state |= (head->so_state & SS_NBIO); + so->so_qstate &= ~SQ_COMP; + so->so_head = NULL; + + SOCK_UNLOCK(so); + ACCEPT_UNLOCK(); + + sa = NULL; + error = soaccept(so, &sa); + if (error != 0) { + ICL_WARN("soaccept error %d", error); + if (sa != NULL) + free(sa, M_SONAME); + soclose(so); + } + + (ils->ils_listen->il_accept)(so); + } +} + +static int +icl_listen_add_tcp(struct icl_listen *il, int domain, int socktype, int protocol, + struct sockaddr *sa) +{ + struct icl_listen_sock *ils; + struct socket *so; + struct sockopt sopt; + int error, one = 1; + + error = socreate(domain, &so, socktype, protocol, + curthread->td_ucred, curthread); + if (error != 0) { + ICL_WARN("socreate failed with error %d", error); + return (error); + } + + sopt.sopt_dir = SOPT_SET; + sopt.sopt_level = SOL_SOCKET; + sopt.sopt_name = SO_REUSEADDR; + sopt.sopt_val = &one; + sopt.sopt_valsize = sizeof(one); + sopt.sopt_td = NULL; + error = sosetopt(so, &sopt); + if (error != 0) { + ICL_WARN("failed to set SO_REUSEADDR with error %d", error); + soclose(so); + return (error); + } + + error = sobind(so, sa, curthread); + if (error != 0) { + ICL_WARN("sobind failed with error %d", error); + soclose(so); + return (error); + } + + error = solisten(so, -1, curthread); + if (error != 0) { + ICL_WARN("solisten failed with error %d", error); + soclose(so); + return (error); + } + + ils = malloc(sizeof(*ils), M_ICL_PROXY, M_ZERO | M_WAITOK); + ils->ils_listen = il; + ils->ils_socket = so; + + error = kthread_add(icl_accept_thread, ils, NULL, NULL, 0, 0, "iclacc"); + if (error != 0) { + ICL_WARN("kthread_add failed with error %d", error); + soclose(so); + free(ils, M_ICL_PROXY); + + return (error); + } + + sx_xlock(&il->il_lock); + TAILQ_INSERT_TAIL(&il->il_sockets, ils, ils_next); + sx_xunlock(&il->il_lock); + + return (0); +} + +int +icl_listen_add(struct icl_listen *il, bool rdma, int domain, int socktype, int protocol, + struct sockaddr *sa) +{ + + if (rdma) { +#ifndef ICL_RDMA + ICL_DEBUG("RDMA not supported"); + return (EOPNOTSUPP); +#else + return (icl_listen_add_rdma(il, domain, socktype, protocol, sa)); +#endif + } + + + return (icl_listen_add_tcp(il, domain, socktype, protocol, sa)); +} + +int +icl_listen_remove(struct icl_listen *il, struct sockaddr *sa) +{ + + /* + * XXX + */ + + return (EOPNOTSUPP); +} + +#endif /* ICL_KERNEL_PROXY */ diff -urNp p4/freebsd/src/sys/dev/iscsi/iscsi.c p4/iscsi/sys/dev/iscsi/iscsi.c --- p4/freebsd/src/sys/dev/iscsi/iscsi.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/sys/dev/iscsi/iscsi.c 2013-08-20 11:52:23.000000000 +0200 @@ -0,0 +1,1949 @@ +/*- + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iscsi_ioctl.h" +#include "iscsi.h" +#include "icl.h" +#include "iscsi_proto.h" + +#ifdef ICL_KERNEL_PROXY +#include +#endif + +/* + * XXX: This is global so the iscsi_unload() can access it. + * Think about how to do this properly. + */ +static struct iscsi_softc *sc; + +SYSCTL_NODE(_kern, OID_AUTO, iscsi, CTLFLAG_RD, 0, "iSCSI initiator"); +static int debug = 2; +TUNABLE_INT("kern.iscsi.debug", &debug); +SYSCTL_INT(_kern_iscsi, OID_AUTO, debug, CTLFLAG_RW, + &debug, 2, "Enable debug messages"); +static int ping_timeout = 5; +TUNABLE_INT("kern.iscsi.ping_timeout", &ping_timeout); +SYSCTL_INT(_kern_iscsi, OID_AUTO, ping_timeout, CTLFLAG_RW, &ping_timeout, + 5, "Timeout for ping (NOP-Out) requests, in seconds"); +static int iscsid_timeout = 60; +TUNABLE_INT("kern.iscsi.iscsid_timeout", &iscsid_timeout); +SYSCTL_INT(_kern_iscsi, OID_AUTO, iscsid_timeout, CTLFLAG_RW, &iscsid_timeout, + 60, "Time to wait for iscsid(8) to handle reconnection, in seconds"); +static int login_timeout = 60; +TUNABLE_INT("kern.iscsi.login_timeout", &login_timeout); +SYSCTL_INT(_kern_iscsi, OID_AUTO, login_timeout, CTLFLAG_RW, &login_timeout, + 60, "Time to wait for iscsid(8) to finish Login Phase, in seconds"); +static int maxtags = 255; +TUNABLE_INT("kern.iscsi.maxtags", &maxtags); +SYSCTL_INT(_kern_iscsi, OID_AUTO, maxtags, CTLFLAG_RW, &maxtags, + 255, "Max number of IO requests queued"); + +MALLOC_DEFINE(M_ISCSI, "iSCSI", "iSCSI initiator"); +static uma_zone_t iscsi_outstanding_zone; + +#define CONN_SESSION(X) ((struct iscsi_session *)X->ic_prv0) +#define PDU_SESSION(X) (CONN_SESSION(X->ip_conn)) + +#define ISCSI_DEBUG(X, ...) \ + if (debug > 1) { \ + printf("%s: " X "\n", __func__, ## __VA_ARGS__);\ + } while (0) + +#define ISCSI_WARN(X, ...) \ + if (debug > 0) { \ + printf("WARNING: %s: " X "\n", \ + __func__, ## __VA_ARGS__); \ + } while (0) + +#define ISCSI_SESSION_DEBUG(S, X, ...) \ + if (debug > 1) { \ + printf("%s: %s (%s): " X "\n", \ + __func__, S->is_desc.isd_target_addr, \ + S->is_desc.isd_target, ## __VA_ARGS__); \ + } while (0) + +#define ISCSI_SESSION_WARN(S, X, ...) \ + if (debug > 0) { \ + printf("WARNING: %s (%s): " X "\n", \ + S->is_desc.isd_target_addr, \ + S->is_desc.isd_target, ## __VA_ARGS__); \ + } while (0) + +#define ISCSI_SESSION_LOCK(X) mtx_lock(&X->is_lock) +#define ISCSI_SESSION_UNLOCK(X) mtx_unlock(&X->is_lock) +#define ISCSI_SESSION_LOCK_ASSERT(X) mtx_assert(&X->is_lock, MA_OWNED) + +static int iscsi_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, + int mode, struct thread *td); + +static struct cdevsw iscsi_cdevsw = { + .d_version = D_VERSION, + .d_ioctl = iscsi_ioctl, + .d_name = "iscsi", +}; + +static void iscsi_pdu_queue_locked(struct icl_pdu *request); +static void iscsi_pdu_queue(struct icl_pdu *request); +static void iscsi_pdu_update_statsn(const struct icl_pdu *response); +static void iscsi_pdu_handle_nop_in(struct icl_pdu *response); +static void iscsi_pdu_handle_scsi_response(struct icl_pdu *response); +static void iscsi_pdu_handle_data_in(struct icl_pdu *response); +static void iscsi_pdu_handle_logout_response(struct icl_pdu *response); +static void iscsi_pdu_handle_r2t(struct icl_pdu *response); +static void iscsi_pdu_handle_async_message(struct icl_pdu *response); +static void iscsi_pdu_handle_reject(struct icl_pdu *response); +static void iscsi_session_reconnect(struct iscsi_session *is); +static void iscsi_session_terminate(struct iscsi_session *is); +static void iscsi_action(struct cam_sim *sim, union ccb *ccb); +static void iscsi_poll(struct cam_sim *sim); +static struct iscsi_outstanding *iscsi_outstanding_find(struct iscsi_session *is, + uint32_t initiator_task_tag); +static int iscsi_outstanding_add(struct iscsi_session *is, + uint32_t initiator_task_tag, union ccb *ccb); +static void iscsi_outstanding_remove(struct iscsi_session *is, + struct iscsi_outstanding *io); + +static bool +iscsi_pdu_prepare(struct icl_pdu *request) +{ + struct iscsi_session *is; + struct iscsi_bhs_scsi_command *bhssc; + + is = PDU_SESSION(request); + + ISCSI_SESSION_LOCK_ASSERT(is); + + /* + * We're only using fields common for all the request + * (initiator -> target) PDUs. + */ + bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; + + /* + * Data-Out PDU does not contain CmdSN. + */ + if (bhssc->bhssc_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_OUT) { + if (is->is_cmdsn > is->is_maxcmdsn && + (bhssc->bhssc_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) { + /* + * Current MaxCmdSN prevents us from sending any more + * SCSI Command PDUs to the target; postpone the PDU. + * It will get resent by either iscsi_pdu_queue(), + * or by maintenance thread. + */ +#if 0 + ISCSI_SESSION_DEBUG(is, "postponing send, CmdSN %d, ExpCmdSN %d, MaxCmdSN %d, opcode 0x%x", + is->is_cmdsn, is->is_expcmdsn, is->is_maxcmdsn, bhssc->bhssc_opcode); +#endif + return (true); + } + bhssc->bhssc_cmdsn = htonl(is->is_cmdsn); + if ((bhssc->bhssc_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) + is->is_cmdsn++; + } + bhssc->bhssc_expstatsn = htonl(is->is_statsn + 1); + + return (false); +} + +static void +iscsi_session_send_postponed(struct iscsi_session *is) +{ + struct icl_pdu *request; + bool postpone; + + ISCSI_SESSION_LOCK_ASSERT(is); + + while (!TAILQ_EMPTY(&is->is_postponed)) { + request = TAILQ_FIRST(&is->is_postponed); + postpone = iscsi_pdu_prepare(request); + if (postpone) + break; + TAILQ_REMOVE(&is->is_postponed, request, ip_next); + icl_pdu_queue(request); + } +} + +static void +iscsi_pdu_queue_locked(struct icl_pdu *request) +{ + struct iscsi_session *is; + bool postpone; + + is = PDU_SESSION(request); + ISCSI_SESSION_LOCK_ASSERT(is); + iscsi_session_send_postponed(is); + postpone = iscsi_pdu_prepare(request); + if (postpone) { + TAILQ_INSERT_TAIL(&is->is_postponed, request, ip_next); + return; + } + icl_pdu_queue(request); +} + +static void +iscsi_pdu_queue(struct icl_pdu *request) +{ + struct iscsi_session *is; + + is = PDU_SESSION(request); + ISCSI_SESSION_LOCK(is); + iscsi_pdu_queue_locked(request); + ISCSI_SESSION_UNLOCK(is); +} + +static void +iscsi_session_logout(struct iscsi_session *is) +{ + struct icl_pdu *request; + struct iscsi_bhs_logout_request *bhslr; + + request = icl_pdu_new_bhs(is->is_conn, M_NOWAIT); + if (request == NULL) + return; + + bhslr = (struct iscsi_bhs_logout_request *)request->ip_bhs; + bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_REQUEST; + bhslr->bhslr_reason = BHSLR_REASON_CLOSE_SESSION; + iscsi_pdu_queue_locked(request); +} + +static void +iscsi_session_terminate_tasks(struct iscsi_session *is, bool requeue) +{ + struct iscsi_outstanding *io, *tmp; + + ISCSI_SESSION_LOCK_ASSERT(is); + + TAILQ_FOREACH_SAFE(io, &is->is_outstanding, io_next, tmp) { + if (requeue) { + io->io_ccb->ccb_h.status &= ~CAM_SIM_QUEUED; + io->io_ccb->ccb_h.status |= CAM_REQUEUE_REQ; + } else { + io->io_ccb->ccb_h.status = CAM_REQ_ABORTED; + } + xpt_done(io->io_ccb); + iscsi_outstanding_remove(is, io); + } +} + +static void +iscsi_maintenance_thread_reconnect(struct iscsi_session *is) +{ + struct icl_pdu *pdu; + + icl_conn_shutdown(is->is_conn); + icl_conn_close(is->is_conn); + + ISCSI_SESSION_LOCK(is); + +#ifdef ICL_KERNEL_PROXY + if (is->is_login_pdu != NULL) { + icl_pdu_free(is->is_login_pdu); + is->is_login_pdu = NULL; + } + cv_signal(&is->is_login_cv); +#endif + + /* + * Don't queue any new PDUs. + */ + if (is->is_sim != NULL && is->is_simq_frozen == false) { + ISCSI_SESSION_DEBUG(is, "freezing"); + xpt_freeze_simq(is->is_sim, 1); + is->is_simq_frozen = true; + } + + /* + * Remove postponed PDUs. + */ + while (!TAILQ_EMPTY(&is->is_postponed)) { + pdu = TAILQ_FIRST(&is->is_postponed); + TAILQ_REMOVE(&is->is_postponed, pdu, ip_next); + icl_pdu_free(pdu); + } + + /* + * Terminate SCSI tasks, asking CAM to requeue them. + */ + //ISCSI_SESSION_DEBUG(is, "terminating tasks"); + iscsi_session_terminate_tasks(is, true); + + KASSERT(TAILQ_EMPTY(&is->is_outstanding), + ("destroying session with active tasks")); + KASSERT(TAILQ_EMPTY(&is->is_postponed), + ("destroying session with postponed PDUs")); + + /* + * Request immediate reconnection from iscsid(8). + */ + //ISCSI_SESSION_DEBUG(is, "waking up iscsid(8)"); + is->is_desc.isd_connected = 0; + is->is_reconnecting = false; + is->is_login_phase = false; + is->is_waiting_for_iscsid = true; + is->is_timeout = 0; + ISCSI_SESSION_UNLOCK(is); + cv_signal(&is->is_softc->sc_cv); +} + +static void +iscsi_maintenance_thread_terminate(struct iscsi_session *is) +{ + struct iscsi_softc *sc; + struct icl_pdu *pdu; + + sc = is->is_softc; + sx_xlock(&sc->sc_lock); + TAILQ_REMOVE(&sc->sc_sessions, is, is_next); + sx_xunlock(&sc->sc_lock); + + icl_conn_close(is->is_conn); + + ISCSI_SESSION_LOCK(is); + + KASSERT(is->is_terminating, ("is_terminating == false")); + +#ifdef ICL_KERNEL_PROXY + if (is->is_login_pdu != NULL) { + icl_pdu_free(is->is_login_pdu); + is->is_login_pdu = NULL; + } + cv_signal(&is->is_login_cv); +#endif + + /* + * Don't queue any new PDUs. + */ + callout_drain(&is->is_callout); + if (is->is_sim != NULL && is->is_simq_frozen == false) { + ISCSI_SESSION_DEBUG(is, "freezing"); + xpt_freeze_simq(is->is_sim, 1); + is->is_simq_frozen = true; + } + + /* + * Remove postponed PDUs. + */ + while (!TAILQ_EMPTY(&is->is_postponed)) { + pdu = TAILQ_FIRST(&is->is_postponed); + TAILQ_REMOVE(&is->is_postponed, pdu, ip_next); + icl_pdu_free(pdu); + } + + /* + * Forcibly terminate SCSI tasks. + */ + ISCSI_SESSION_DEBUG(is, "terminating tasks"); + iscsi_session_terminate_tasks(is, false); + + /* + * Deregister CAM. + */ + if (is->is_sim != NULL) { + ISCSI_SESSION_DEBUG(is, "deregistering SIM"); + xpt_async(AC_LOST_DEVICE, is->is_path, NULL); + + if (is->is_simq_frozen) { + xpt_release_simq(is->is_sim, 1); + is->is_simq_frozen = false; + } + + xpt_free_path(is->is_path); + xpt_bus_deregister(cam_sim_path(is->is_sim)); + cam_sim_free(is->is_sim, TRUE /*free_devq*/); + is->is_sim = NULL; + } + + KASSERT(TAILQ_EMPTY(&is->is_outstanding), + ("destroying session with active tasks")); + KASSERT(TAILQ_EMPTY(&is->is_postponed), + ("destroying session with postponed PDUs")); + + ISCSI_SESSION_UNLOCK(is); + + icl_conn_free(is->is_conn); + mtx_destroy(&is->is_lock); + cv_destroy(&is->is_maintenance_cv); +#ifdef ICL_KERNEL_PROXY + cv_destroy(&is->is_login_cv); +#endif + ISCSI_SESSION_DEBUG(is, "terminated"); + free(is, M_ISCSI); + + /* + * The iscsi_unload() routine might be waiting. + */ + cv_signal(&sc->sc_cv); +} + +static void +iscsi_maintenance_thread(void *arg) +{ + struct iscsi_session *is; + + is = arg; + + for (;;) { + ISCSI_SESSION_LOCK(is); + if (is->is_reconnecting == false && + is->is_terminating == false && + TAILQ_EMPTY(&is->is_postponed)) + cv_wait(&is->is_maintenance_cv, &is->is_lock); + ISCSI_SESSION_UNLOCK(is); + + if (is->is_reconnecting) { + iscsi_maintenance_thread_reconnect(is); + continue; + } + + if (is->is_terminating) { + iscsi_maintenance_thread_terminate(is); + kthread_exit(); + return; + } + + ISCSI_SESSION_LOCK(is); + iscsi_session_send_postponed(is); + ISCSI_SESSION_UNLOCK(is); + } +} + +static void +iscsi_session_reconnect(struct iscsi_session *is) +{ + + /* + * XXX: We can't use locking here, because + * it's being called from various contexts. + * Hope it doesn't break anything. + */ + if (is->is_reconnecting) + return; + + is->is_reconnecting = true; + cv_signal(&is->is_maintenance_cv); +} + +static void +iscsi_session_terminate(struct iscsi_session *is) +{ + if (is->is_terminating) + return; + + is->is_terminating = true; + +#if 0 + iscsi_session_logout(is); +#endif + cv_signal(&is->is_maintenance_cv); +} + +static void +iscsi_callout(void *context) +{ + struct icl_pdu *request; + struct iscsi_bhs_nop_out *bhsno; + struct iscsi_session *is; + bool reconnect_needed = false; + + is = context; + + if (is->is_terminating) + return; + + callout_schedule(&is->is_callout, 1 * hz); + + ISCSI_SESSION_LOCK(is); + is->is_timeout++; + + if (is->is_waiting_for_iscsid) { + if (is->is_timeout > iscsid_timeout) { + ISCSI_SESSION_WARN(is, "timed out waiting for iscsid(8) " + "for %d seconds; reconnecting", + is->is_timeout); + reconnect_needed = true; + } + goto out; + } + + if (is->is_login_phase) { + if (is->is_timeout > login_timeout) { + ISCSI_SESSION_WARN(is, "login timed out after %d seconds; " + "reconnecting", is->is_timeout); + reconnect_needed = true; + } + goto out; + } + + if (is->is_timeout >= ping_timeout) { + ISCSI_SESSION_WARN(is, "no ping reply (NOP-In) after %d seconds; " + "reconnecting", ping_timeout); + reconnect_needed = true; + goto out; + } + + ISCSI_SESSION_UNLOCK(is); + + /* + * If the ping was reset less than one second ago - which means + * that we've received some PDU during the last second - assume + * the traffic flows correctly and don't bother sending a NOP-Out. + * + * (It's 2 - one for one second, and one for incrementing is_timeout + * earlier in this routine.) + */ + if (is->is_timeout < 2) + return; + + request = icl_pdu_new_bhs(is->is_conn, M_WAITOK); + bhsno = (struct iscsi_bhs_nop_out *)request->ip_bhs; + bhsno->bhsno_opcode = ISCSI_BHS_OPCODE_NOP_OUT | + ISCSI_BHS_OPCODE_IMMEDIATE; + bhsno->bhsno_flags = 0x80; + bhsno->bhsno_target_transfer_tag = 0xffffffff; + iscsi_pdu_queue(request); + return; + +out: + ISCSI_SESSION_UNLOCK(is); + + if (reconnect_needed) + iscsi_session_reconnect(is); +} + +static void +iscsi_pdu_update_statsn(const struct icl_pdu *response) +{ + const struct iscsi_bhs_data_in *bhsdi; + struct iscsi_session *is; + uint32_t expcmdsn, maxcmdsn; + + is = PDU_SESSION(response); + + ISCSI_SESSION_LOCK_ASSERT(is); + + /* + * We're only using fields common for all the response + * (target -> initiator) PDUs. + */ + bhsdi = (const struct iscsi_bhs_data_in *)response->ip_bhs; + /* + * Ok, I lied. In case of Data-In, "The fields StatSN, Status, + * and Residual Count only have meaningful content if the S bit + * is set to 1", so we also need to check the bit specific for + * Data-In PDU. + */ + if (bhsdi->bhsdi_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_IN || + (bhsdi->bhsdi_flags & BHSDI_FLAGS_S) != 0) { + if (ntohl(bhsdi->bhsdi_statsn) < is->is_statsn) { + ISCSI_SESSION_WARN(is, + "PDU StatSN %d >= session StatSN %d, opcode 0x%x", + is->is_statsn, ntohl(bhsdi->bhsdi_statsn), + bhsdi->bhsdi_opcode); + } + is->is_statsn = ntohl(bhsdi->bhsdi_statsn); + } + + expcmdsn = ntohl(bhsdi->bhsdi_expcmdsn); + maxcmdsn = ntohl(bhsdi->bhsdi_maxcmdsn); + + /* + * XXX: Compare using Serial Arithmetic Sense. + */ + if (maxcmdsn + 1 < expcmdsn) { + ISCSI_SESSION_DEBUG(is, "PDU MaxCmdSN %d + 1 < PDU ExpCmdSN %d; ignoring", + maxcmdsn, expcmdsn); + } else { + if (maxcmdsn > is->is_maxcmdsn) { + is->is_maxcmdsn = maxcmdsn; + + /* + * Command window increased; kick the maintanance thread + * to send out postponed commands. + */ + cv_signal(&is->is_maintenance_cv); + } else if (maxcmdsn < is->is_maxcmdsn) { + ISCSI_SESSION_DEBUG(is, "PDU MaxCmdSN %d < session MaxCmdSN %d; ignoring", + maxcmdsn, is->is_maxcmdsn); + } + + if (expcmdsn > is->is_expcmdsn) { + is->is_expcmdsn = expcmdsn; + } else if (expcmdsn < is->is_expcmdsn) { + ISCSI_SESSION_DEBUG(is, "PDU ExpCmdSN %d < session ExpCmdSN %d; ignoring", + expcmdsn, is->is_expcmdsn); + } + } + + /* + * Every incoming PDU - not just NOP-In - resets the ping timer. + * The purpose of the timeout is to reset the connection when it stalls; + * we don't want this to happen when NOP-In or NOP-Out ends up delayed + * in some queue. + */ + is->is_timeout = 0; +} + +static void +iscsi_receive_callback(struct icl_pdu *response) +{ + struct iscsi_session *is; + + is = PDU_SESSION(response); + + ISCSI_SESSION_LOCK(is); + +#ifdef ICL_KERNEL_PROXY + if (is->is_login_phase) { + if (is->is_login_pdu == NULL) + is->is_login_pdu = response; + else + icl_pdu_free(response); + ISCSI_SESSION_UNLOCK(is); + cv_signal(&is->is_login_cv); + return; + } +#endif + + iscsi_pdu_update_statsn(response); + + /* + * The handling routine is responsible for freeing the PDU + * when it's no longer needed. + */ + switch (response->ip_bhs->bhs_opcode) { + case ISCSI_BHS_OPCODE_NOP_IN: + iscsi_pdu_handle_nop_in(response); + break; + case ISCSI_BHS_OPCODE_SCSI_RESPONSE: + iscsi_pdu_handle_scsi_response(response); + break; + case ISCSI_BHS_OPCODE_SCSI_DATA_IN: + iscsi_pdu_handle_data_in(response); + break; + case ISCSI_BHS_OPCODE_LOGOUT_RESPONSE: + iscsi_pdu_handle_logout_response(response); + break; + case ISCSI_BHS_OPCODE_R2T: + iscsi_pdu_handle_r2t(response); + break; + case ISCSI_BHS_OPCODE_ASYNC_MESSAGE: + iscsi_pdu_handle_async_message(response); + break; + case ISCSI_BHS_OPCODE_REJECT: + iscsi_pdu_handle_reject(response); + break; + default: + ISCSI_SESSION_WARN(is, "received PDU with unsupported " + "opcode 0x%x; reconnecting", + response->ip_bhs->bhs_opcode); + iscsi_session_reconnect(is); + icl_pdu_free(response); + } + + ISCSI_SESSION_UNLOCK(is); +} + +static void +iscsi_error_callback(struct icl_conn *ic) +{ + struct iscsi_session *is; + + is = CONN_SESSION(ic); + + ISCSI_SESSION_WARN(is, "connection error; reconnecting"); + iscsi_session_reconnect(is); +} + +static void +iscsi_pdu_handle_nop_in(struct icl_pdu *response) +{ + struct iscsi_bhs_nop_out *bhsno; + struct iscsi_bhs_nop_in *bhsni; + struct icl_pdu *request; + + bhsni = (struct iscsi_bhs_nop_in *)response->ip_bhs; + + if (bhsni->bhsni_target_transfer_tag == 0xffffffff) { + /* + * Nothing to do; iscsi_pdu_update_statsn() already + * zeroed the timeout. + */ + icl_pdu_free(response); + return; + } + + request = icl_pdu_new_bhs(response->ip_conn, M_NOWAIT); + if (request == NULL) { + icl_pdu_free(response); + return; + } + bhsno = (struct iscsi_bhs_nop_out *)request->ip_bhs; + bhsno->bhsno_opcode = ISCSI_BHS_OPCODE_NOP_OUT | + ISCSI_BHS_OPCODE_IMMEDIATE; + bhsno->bhsno_flags = 0x80; + bhsno->bhsno_initiator_task_tag = 0xffffffff; /* XXX */ + bhsno->bhsno_target_transfer_tag = bhsni->bhsni_target_transfer_tag; + + request->ip_data_len = response->ip_data_len; + request->ip_data_mbuf = response->ip_data_mbuf; + response->ip_data_len = 0; + response->ip_data_mbuf = NULL; + + icl_pdu_free(response); + iscsi_pdu_queue_locked(request); +} + +static void +iscsi_pdu_handle_scsi_response(struct icl_pdu *response) +{ + struct iscsi_bhs_scsi_response *bhssr; + struct iscsi_outstanding *io; + struct iscsi_session *is; + uint16_t sense_len; + size_t data_segment_len; + + is = PDU_SESSION(response); + + bhssr = (struct iscsi_bhs_scsi_response *)response->ip_bhs; + io = iscsi_outstanding_find(is, bhssr->bhssr_initiator_task_tag); + if (io == NULL) { + ISCSI_SESSION_WARN(is, "bad itt 0x%x", bhssr->bhssr_initiator_task_tag); + icl_pdu_free(response); + iscsi_session_reconnect(is); + return; + } + + if (bhssr->bhssr_response != BHSSR_RESPONSE_COMMAND_COMPLETED) { + ISCSI_SESSION_WARN(is, "bad response 0x%x", bhssr->bhssr_response); + io->io_ccb->ccb_h.status = CAM_REQ_CMP_ERR; + } else if (bhssr->bhssr_status == 0) { + io->io_ccb->ccb_h.status = CAM_REQ_CMP; + } else { + io->io_ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; + io->io_ccb->csio.scsi_status = bhssr->bhssr_status; + } + + data_segment_len = icl_pdu_data_segment_length(response); + if (data_segment_len > 0) { + if (data_segment_len < 2) { + ISCSI_SESSION_WARN(is, "truncated data segment (%zd bytes)", + data_segment_len); + io->io_ccb->ccb_h.status = CAM_REQ_CMP_ERR; + goto out; + } + icl_pdu_get_data(response, 0, &sense_len, sizeof(sense_len)); + sense_len = ntohs(sense_len); +#if 0 + ISCSI_SESSION_DEBUG(is, "sense_len %d, data len %zd", + sense_len, data_segment_len); +#endif + if (sizeof(sense_len) + sense_len > data_segment_len) { + ISCSI_SESSION_WARN(is, "truncated data segment " + "(%zd bytes, should be %ld)", + data_segment_len, sizeof(sense_len) + sense_len); + io->io_ccb->ccb_h.status = CAM_REQ_CMP_ERR; + goto out; + } else if (sizeof(sense_len) + sense_len > data_segment_len) + ISCSI_SESSION_WARN(is, "oversize data segment " + "(%ld bytes, should be %zd)", + data_segment_len, sizeof(sense_len) + sense_len); + if (sense_len > io->io_ccb->csio.sense_len) { + ISCSI_SESSION_DEBUG(is, "truncating sense from %d to %d", + sense_len, io->io_ccb->csio.sense_len); + sense_len = io->io_ccb->csio.sense_len; + } + icl_pdu_get_data(response, sizeof(sense_len), &io->io_ccb->csio.sense_data, sense_len); + io->io_ccb->ccb_h.status |= CAM_AUTOSNS_VALID; + } + +out: + xpt_done(io->io_ccb); + iscsi_outstanding_remove(is, io); + icl_pdu_free(response); +} + +static void +iscsi_pdu_handle_data_in(struct icl_pdu *response) +{ + struct iscsi_bhs_data_in *bhsdi; + struct iscsi_outstanding *io; + struct iscsi_session *is; + struct ccb_scsiio *csio; + size_t data_segment_len; + + is = PDU_SESSION(response); + bhsdi = (struct iscsi_bhs_data_in *)response->ip_bhs; + io = iscsi_outstanding_find(is, bhsdi->bhsdi_initiator_task_tag); + if (io == NULL) { + ISCSI_SESSION_WARN(is, "bad itt 0x%x", bhsdi->bhsdi_initiator_task_tag); + icl_pdu_free(response); + iscsi_session_reconnect(is); + return; + } + + data_segment_len = icl_pdu_data_segment_length(response); + if (data_segment_len == 0) { + /* + * "The sending of 0 length data segments should be avoided, + * but initiators and targets MUST be able to properly receive + * 0 length data segments." + */ + icl_pdu_free(response); + return; + } + + csio = &io->io_ccb->csio; + + if (ntohl(bhsdi->bhsdi_buffer_offset) + data_segment_len > + csio->dxfer_len) { + ISCSI_SESSION_WARN(is, "oversize data segment (%zd bytes " + "at offset %d, buffer is %d)", + data_segment_len, ntohl(bhsdi->bhsdi_buffer_offset), + csio->dxfer_len); + icl_pdu_free(response); + iscsi_session_reconnect(is); + } + + icl_pdu_get_data(response, 0, csio->data_ptr + ntohl(bhsdi->bhsdi_buffer_offset), data_segment_len); + + /* + * XXX: Check DataSN. + * XXX: Check F. + * XXX: How to pass overflow/underflow information to CAM? + */ + if (bhsdi->bhsdi_flags & BHSDI_FLAGS_S) { + //ISCSI_SESSION_DEBUG(is, "got S flag; status 0x%x", bhsdi->bhsdi_status); + if (bhsdi->bhsdi_status == 0) { + io->io_ccb->ccb_h.status = CAM_REQ_CMP; + } else { + io->io_ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; + csio->scsi_status = bhsdi->bhsdi_status; + } + xpt_done(io->io_ccb); + iscsi_outstanding_remove(is, io); + } + + icl_pdu_free(response); +} + +static void +iscsi_pdu_handle_logout_response(struct icl_pdu *response) +{ + + ISCSI_SESSION_DEBUG(PDU_SESSION(response), "logout response"); + icl_pdu_free(response); +} + +static void +iscsi_pdu_handle_r2t(struct icl_pdu *response) +{ + struct icl_pdu *request; + struct iscsi_session *is; + struct iscsi_bhs_r2t *bhsr2t; + struct iscsi_bhs_data_out *bhsdo; + struct iscsi_outstanding *io; + struct ccb_scsiio *csio; + size_t off, len, total_len; + + is = PDU_SESSION(response); + + bhsr2t = (struct iscsi_bhs_r2t *)response->ip_bhs; + io = iscsi_outstanding_find(is, bhsr2t->bhsr2t_initiator_task_tag); + if (io == NULL) { + ISCSI_SESSION_WARN(is, "bad itt 0x%x; reconnecting", + bhsr2t->bhsr2t_initiator_task_tag); + icl_pdu_free(response); + iscsi_session_reconnect(is); + return; + } + + csio = &io->io_ccb->csio; + + if ((csio->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_OUT) { + ISCSI_SESSION_WARN(is, "received R2T for read command; reconnecting"); + icl_pdu_free(response); + iscsi_session_reconnect(is); + return; + } + + /* + * XXX: Verify R2TSN. + */ + + io->io_datasn = 0; + off = ntohl(bhsr2t->bhsr2t_buffer_offset); + total_len = ntohl(bhsr2t->bhsr2t_desired_data_transfer_length); + + //ISCSI_SESSION_DEBUG(is, "r2t; off %zd, len %zd", off, total_len); + + for (;;) { + len = total_len; + + if (len > is->is_desc.isd_max_data_segment_length) + len = is->is_desc.isd_max_data_segment_length; + + if (off + len > csio->dxfer_len) { + ISCSI_SESSION_WARN(is, "bad off %zd, len %d", + off + len, csio->dxfer_len); + icl_pdu_free(response); + iscsi_session_reconnect(is); + } + + request = icl_pdu_new_bhs(response->ip_conn, M_NOWAIT); + if (request == NULL) { + icl_pdu_free(response); + iscsi_session_reconnect(is); + return; + } + + bhsdo = (struct iscsi_bhs_data_out *)request->ip_bhs; + bhsdo->bhsdo_opcode = ISCSI_BHS_OPCODE_SCSI_DATA_OUT; + bhsdo->bhsdo_lun = bhsr2t->bhsr2t_lun; + bhsdo->bhsdo_initiator_task_tag = + bhsr2t->bhsr2t_initiator_task_tag; + bhsdo->bhsdo_target_transfer_tag = + bhsr2t->bhsr2t_target_transfer_tag; + bhsdo->bhsdo_datasn = htonl(io->io_datasn++); + bhsdo->bhsdo_buffer_offset = htonl(off); + /* + * XXX: Ugly. + */ + ISCSI_SESSION_UNLOCK(is); + icl_pdu_append_data(request, csio->data_ptr + off, len); + ISCSI_SESSION_LOCK(is); + + off += len; + total_len -= len; + + if (total_len == 0) { + bhsdo->bhsdo_flags |= BHSDO_FLAGS_F; + //ISCSI_SESSION_DEBUG(is, "setting F, off %zd", off); + } else { + //ISCSI_SESSION_DEBUG(is, "not finished, off %zd", off); + } + + iscsi_pdu_queue_locked(request); + + if (total_len == 0) + break; + } + + icl_pdu_free(response); +} + +static void +iscsi_pdu_handle_async_message(struct icl_pdu *response) +{ + struct iscsi_bhs_asynchronous_message *bhsam; + struct iscsi_session *is; + + is = PDU_SESSION(response); + bhsam = (struct iscsi_bhs_asynchronous_message *)response->ip_bhs; + switch (bhsam->bhsam_async_event) { + case BHSAM_EVENT_TARGET_REQUESTS_LOGOUT: + ISCSI_SESSION_WARN(is, "target requests logout; removing session"); + iscsi_session_logout(is); + iscsi_session_terminate(is); + break; + case BHSAM_EVENT_TARGET_TERMINATES_CONNECTION: + ISCSI_SESSION_DEBUG(is, "target indicates it will drop drop the connection"); + break; + case BHSAM_EVENT_TARGET_TERMINATES_SESSION: + ISCSI_SESSION_DEBUG(is, "target indicates it will drop drop the session"); + break; + default: + /* + * XXX: Technically, we're obligated to also handle + * parameter renegotiation. + */ + ISCSI_SESSION_WARN(is, "ignoring AsyncEvent %d", bhsam->bhsam_async_event); + break; + } + + icl_pdu_free(response); +} + +static void +iscsi_pdu_handle_reject(struct icl_pdu *response) +{ + struct iscsi_bhs_reject *bhsr; + struct iscsi_session *is; + + is = PDU_SESSION(response); + bhsr = (struct iscsi_bhs_reject *)response->ip_bhs; + ISCSI_SESSION_WARN(is, "received Reject PDU, reason 0x%x; protocol error?", + bhsr->bhsr_reason); + + icl_pdu_free(response); +} + +static int +iscsi_ioctl_daemon_wait(struct iscsi_softc *sc, + struct iscsi_daemon_request *request) +{ + struct iscsi_session *is; + int error; + + sx_slock(&sc->sc_lock); + for (;;) { + TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { + if (is->is_waiting_for_iscsid) + break; + } + + if (is == NULL) { + /* + * No session requires attention from iscsid(8); wait. + */ + error = cv_wait_sig(&sc->sc_cv, &sc->sc_lock); + if (error != 0) { + sx_sunlock(&sc->sc_lock); + return (error); + } + continue; + } + + ISCSI_SESSION_LOCK(is); + is->is_waiting_for_iscsid = false; + is->is_login_phase = true; + ISCSI_SESSION_UNLOCK(is); + + request->idr_cmd = ISCSI_DAEMON_CMD_LOGIN; + memcpy(&request->idr_desc, &is->is_desc, + sizeof(request->idr_desc)); + + sx_sunlock(&sc->sc_lock); + return (0); + } +} + +static int +iscsi_ioctl_daemon_handoff(struct iscsi_softc *sc, + struct iscsi_daemon_handoff *handoff) +{ + struct iscsi_session *is; + int error; + + sx_slock(&sc->sc_lock); + + /* + * Find the session to hand off socket to. + */ + TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { + if (is->is_desc.isd_id == handoff->idh_session_id) + break; + } + if (is == NULL) { + sx_sunlock(&sc->sc_lock); + return (ESRCH); + } + ISCSI_SESSION_LOCK(is); + if (is->is_desc.isd_discovery || is->is_terminating) { + ISCSI_SESSION_UNLOCK(is); + sx_sunlock(&sc->sc_lock); + return (EINVAL); + } + + strlcpy(is->is_desc.isd_target_alias, handoff->idh_target_alias, + sizeof(is->is_desc.isd_target_alias)); + /* + * XXX: This is ugly. Find a better way to pass those to iscsictl(8), + * and put them into struct iscsi_session. + */ + is->is_desc.isd_immediate_data = handoff->idh_immediate_data; + is->is_desc.isd_max_data_segment_length = handoff->idh_max_data_segment_length; + is->is_desc.isd_connected = 1; + + is->is_cmdsn = 0; + is->is_expcmdsn = 1; + is->is_maxcmdsn = 1; + is->is_statsn = handoff->idh_statsn; + is->is_initial_r2t = handoff->idh_initial_r2t; + is->is_max_burst_length = handoff->idh_max_burst_length; + is->is_first_burst_length = handoff->idh_first_burst_length; + is->is_waiting_for_iscsid = false; + is->is_login_phase = false; + is->is_timeout = 0; + + memcpy(is->is_isid, handoff->idh_isid, sizeof(is->is_isid)); + + ISCSI_SESSION_UNLOCK(is); + + if (handoff->idh_header_digest == ISCSI_DIGEST_CRC32C) + is->is_conn->ic_header_crc32c = true; + if (handoff->idh_data_digest == ISCSI_DIGEST_CRC32C) + is->is_conn->ic_data_crc32c = true; + +#ifndef ICL_KERNEL_PROXY + error = icl_conn_handoff(is->is_conn, handoff->idh_socket); + if (error != 0) { + sx_sunlock(&sc->sc_lock); + iscsi_session_terminate(is); + return (error); + } +#endif + + sx_sunlock(&sc->sc_lock); + + if (is->is_sim != NULL) { + /* + * When reconnecting, there already is SIM allocated for the session. + */ + KASSERT(is->is_simq_frozen, ("reconnect without frozen simq")); + ISCSI_SESSION_LOCK(is); + ISCSI_SESSION_DEBUG(is, "releasing"); + xpt_release_simq(is->is_sim, 1); + is->is_simq_frozen = false; + ISCSI_SESSION_UNLOCK(is); + + } else { + ISCSI_SESSION_LOCK(is); + is->is_devq = cam_simq_alloc(maxtags); + if (is->is_devq == NULL) { + ISCSI_SESSION_WARN(is, "failed to allocate simq"); + iscsi_session_terminate(is); + return (ENOMEM); + } + + is->is_sim = cam_sim_alloc(iscsi_action, iscsi_poll, "iscsi", + is, is->is_desc.isd_id /* unit */, &is->is_lock, + maxtags, maxtags, is->is_devq); + if (is->is_sim == NULL) { + ISCSI_SESSION_UNLOCK(is); + ISCSI_SESSION_WARN(is, "failed to allocate SIM"); + cam_simq_free(is->is_devq); + iscsi_session_terminate(is); + return (ENOMEM); + } + + error = xpt_bus_register(is->is_sim, NULL, 0); + if (error != 0) { + ISCSI_SESSION_UNLOCK(is); + ISCSI_SESSION_WARN(is, "failed to register bus"); + iscsi_session_terminate(is); + return (ENOMEM); + } + + error = xpt_create_path(&is->is_path, /*periph*/NULL, + cam_sim_path(is->is_sim), CAM_TARGET_WILDCARD, + CAM_LUN_WILDCARD); + if (error != CAM_REQ_CMP) { + ISCSI_SESSION_UNLOCK(is); + ISCSI_SESSION_WARN(is, "failed to create path"); + iscsi_session_terminate(is); + return (ENOMEM); + } + ISCSI_SESSION_UNLOCK(is); + } + + return (0); +} + +#ifdef ICL_KERNEL_PROXY +static int +iscsi_ioctl_daemon_connect(struct iscsi_softc *sc, + struct iscsi_daemon_connect *idc) +{ + struct iscsi_session *is; + struct sockaddr *from_sa, *to_sa; + int error; + + sx_slock(&sc->sc_lock); + TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { + if (is->is_desc.isd_id == idc->idc_session_id) + break; + } + if (is == NULL) { + sx_sunlock(&sc->sc_lock); + return (ESRCH); + } + sx_sunlock(&sc->sc_lock); + + if (idc->idc_from_addrlen > 0) { + error = getsockaddr(&from_sa, (void *)idc->idc_from_addr, idc->idc_from_addrlen); + if (error != 0) + return (error); + } else { + from_sa = NULL; + } + error = getsockaddr(&to_sa, (void *)idc->idc_to_addr, idc->idc_to_addrlen); + if (error != 0) { + free(from_sa, M_SONAME); + return (error); + } + + ISCSI_SESSION_LOCK(is); + is->is_waiting_for_iscsid = false; + is->is_login_phase = true; + is->is_timeout = 0; + ISCSI_SESSION_UNLOCK(is); + + error = icl_conn_connect(is->is_conn, idc->idc_iser, idc->idc_domain, + idc->idc_socktype, idc->idc_protocol, from_sa, to_sa); + free(from_sa, M_SONAME); + free(to_sa, M_SONAME); + + /* + * Digests are always disabled during login phase. + */ + is->is_conn->ic_header_crc32c = false; + is->is_conn->ic_data_crc32c = false; + + return (error); +} + +static int +iscsi_ioctl_daemon_send(struct iscsi_softc *sc, + struct iscsi_daemon_send *ids) +{ + struct iscsi_session *is; + struct icl_pdu *ip; + size_t datalen; + void *data; + int error; + + sx_slock(&sc->sc_lock); + TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { + if (is->is_desc.isd_id == ids->ids_session_id) + break; + } + if (is == NULL) { + sx_sunlock(&sc->sc_lock); + return (ESRCH); + } + sx_sunlock(&sc->sc_lock); + + if (is->is_login_phase == false) + return (EBUSY); + + if (is->is_terminating || is->is_reconnecting) + return (EIO); + + datalen = ids->ids_data_segment_len; + if (datalen > ISCSI_MAX_DATA_SEGMENT_LENGTH) + return (EINVAL); + if (datalen > 0) { + data = malloc(datalen, M_ISCSI, M_WAITOK); + error = copyin(ids->ids_data_segment, data, datalen); + if (error != 0) { + free(data, M_ISCSI); + return (error); + } + } + + ip = icl_pdu_new_bhs(is->is_conn, M_WAITOK); + memcpy(ip->ip_bhs, ids->ids_bhs, sizeof(*ip->ip_bhs)); + if (datalen > 0) { + icl_pdu_append_data(ip, data, datalen); + free(data, M_ISCSI); + } + icl_pdu_queue(ip); + + return (0); +} + +static int +iscsi_ioctl_daemon_receive(struct iscsi_softc *sc, + struct iscsi_daemon_receive *idr) +{ + struct iscsi_session *is; + struct icl_pdu *ip; + void *data; + + sx_slock(&sc->sc_lock); + TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { + if (is->is_desc.isd_id == idr->idr_session_id) + break; + } + if (is == NULL) { + sx_sunlock(&sc->sc_lock); + return (ESRCH); + } + sx_sunlock(&sc->sc_lock); + + if (is->is_login_phase == false) + return (EBUSY); + + ISCSI_SESSION_LOCK(is); + while (is->is_login_pdu == NULL && + is->is_terminating == false && + is->is_reconnecting == false) + cv_wait(&is->is_login_cv, &is->is_lock); + if (is->is_terminating || is->is_reconnecting) { + ISCSI_SESSION_UNLOCK(is); + return (EIO); + } + ip = is->is_login_pdu; + is->is_login_pdu = NULL; + ISCSI_SESSION_UNLOCK(is); + + if (ip->ip_data_len > idr->idr_data_segment_len) { + icl_pdu_free(ip); + return (EMSGSIZE); + } + + copyout(ip->ip_bhs, idr->idr_bhs, sizeof(*ip->ip_bhs)); + if (ip->ip_data_len > 0) { + data = malloc(ip->ip_data_len, M_ISCSI, M_WAITOK); + icl_pdu_get_data(ip, 0, data, ip->ip_data_len); + copyout(data, idr->idr_data_segment, ip->ip_data_len); + free(data, M_ISCSI); + } + + icl_pdu_free(ip); + + return (0); +} + +static int +iscsi_ioctl_daemon_close(struct iscsi_softc *sc, + struct iscsi_daemon_close *idc) +{ + struct iscsi_session *is; + + sx_slock(&sc->sc_lock); + TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { + if (is->is_desc.isd_id == idc->idc_session_id) + break; + } + if (is == NULL) { + sx_sunlock(&sc->sc_lock); + return (ESRCH); + } + sx_sunlock(&sc->sc_lock); + + iscsi_session_reconnect(is); + + return (0); +} +#endif /* ICL_KERNEL_PROXY */ + +static void +iscsi_sanitize_session_desc(struct iscsi_session_desc *isd) +{ + /* + * Just make sure all the fields are null-terminated. + * + * XXX: This is not particularly secure. We should + * create our own desc and then copy in relevant + * fields. + */ + isd->isd_initiator[ISCSI_NAME_LEN - 1] = '\0'; + isd->isd_initiator_addr[ISCSI_ADDR_LEN - 1] = '\0'; + isd->isd_initiator_alias[ISCSI_ALIAS_LEN - 1] = '\0'; + isd->isd_target[ISCSI_NAME_LEN - 1] = '\0'; + isd->isd_target_addr[ISCSI_ADDR_LEN - 1] = '\0'; + isd->isd_target_alias[ISCSI_ALIAS_LEN - 1] = '\0'; + isd->isd_user[ISCSI_NAME_LEN - 1] = '\0'; + isd->isd_secret[ISCSI_SECRET_LEN - 1] = '\0'; + isd->isd_mutual_user[ISCSI_NAME_LEN - 1] = '\0'; + isd->isd_mutual_secret[ISCSI_SECRET_LEN - 1] = '\0'; + isd->isd_connected = 0; +} + +static int +iscsi_ioctl_session_add(struct iscsi_softc *sc, struct iscsi_session_add *isa) +{ + struct iscsi_session *is; + const struct iscsi_session *is2; + int error; + + iscsi_sanitize_session_desc(&isa->isa_desc); + + is = malloc(sizeof(*is), M_ISCSI, M_ZERO | M_WAITOK); + memcpy(&is->is_desc, &isa->isa_desc, sizeof(is->is_desc)); + + if (is->is_desc.isd_initiator[0] == '\0' || + is->is_desc.isd_target == '\0' || + is->is_desc.isd_target_addr == '\0') { + free(is, M_ISCSI); + return (EINVAL); + } + + sx_xlock(&sc->sc_lock); + + /* + * Prevent duplicates. + */ + TAILQ_FOREACH(is2, &sc->sc_sessions, is_next) { + if (strcmp(is2->is_desc.isd_target, + is->is_desc.isd_target) == 0) { + sx_xunlock(&sc->sc_lock); + return (EBUSY); + } + } + + is->is_conn = icl_conn_new(); + is->is_conn->ic_receive = iscsi_receive_callback; + is->is_conn->ic_error = iscsi_error_callback; + is->is_conn->ic_prv0 = is; + TAILQ_INIT(&is->is_outstanding); + TAILQ_INIT(&is->is_postponed); + mtx_init(&is->is_lock, "iscsi_lock", NULL, MTX_DEF); + cv_init(&is->is_maintenance_cv, "iscsi_mt"); +#ifdef ICL_KERNEL_PROXY + cv_init(&is->is_login_cv, "iscsi_login"); +#endif + + is->is_softc = sc; + sc->sc_last_session_id++; + is->is_desc.isd_id = sc->sc_last_session_id; + callout_init(&is->is_callout, 1); + callout_reset(&is->is_callout, 1 * hz, iscsi_callout, is); + TAILQ_INSERT_TAIL(&sc->sc_sessions, is, is_next); + + error = kthread_add(iscsi_maintenance_thread, is, NULL, NULL, 0, 0, "iscsimt"); + if (error != 0) { + ISCSI_SESSION_WARN(is, "kthread_add(9) failed with error %d", error); + return (error); + } + + /* + * Trigger immediate reconnection. + */ + is->is_waiting_for_iscsid = true; + cv_signal(&sc->sc_cv); + + sx_xunlock(&sc->sc_lock); + + return (0); +} + +static bool +iscsi_session_desc_matches(const struct iscsi_session_desc *d1, + const struct iscsi_session_desc *d2) +{ + if (d2->isd_id == 0 && d2->isd_target[0] == '\0' && + d2->isd_target_addr[0] == '\0') + return (true); + if (d2->isd_id != 0 && d2->isd_id == d1->isd_id) + return (true); + if (d2->isd_target[0] != '\0' && + strcmp(d1->isd_target, d2->isd_target) == 0) + return (true); + if (d2->isd_target_addr[0] != '\0' && + strcmp(d1->isd_target_addr, d2->isd_target_addr) == 0) + return (true); + return (false); +} + +static int +iscsi_ioctl_session_remove(struct iscsi_softc *sc, + struct iscsi_session_remove *isr) +{ + int found = 0; + struct iscsi_session *is, *tmp; + + iscsi_sanitize_session_desc(&isr->isr_desc); + + sx_xlock(&sc->sc_lock); + TAILQ_FOREACH_SAFE(is, &sc->sc_sessions, is_next, tmp) { + ISCSI_SESSION_LOCK(is); + if (iscsi_session_desc_matches(&is->is_desc, &isr->isr_desc)) { + found++; + iscsi_session_logout(is); + iscsi_session_terminate(is); + } + ISCSI_SESSION_UNLOCK(is); + } + sx_xunlock(&sc->sc_lock); + + if (found == 0) + return (ESRCH); + + return (0); +} + +static int +iscsi_ioctl_session_list(struct iscsi_softc *sc, struct iscsi_session_list *isl) +{ + int error; + unsigned int i = 0; + struct iscsi_session *is; + + sx_slock(&sc->sc_lock); + TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { + if (i >= isl->isl_nentries) { + sx_sunlock(&sc->sc_lock); + return (EMSGSIZE); + } + error = copyout(&is->is_desc, isl->isl_pdesc + i, + sizeof(is->is_desc)); + if (error != 0) { + sx_sunlock(&sc->sc_lock); + return (error); + } + i++; + } + sx_sunlock(&sc->sc_lock); + + isl->isl_nentries = i; + + return (0); +} + +static int +iscsi_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + struct iscsi_softc *sc; + + sc = dev->si_drv1; + + switch (cmd) { + case ISCSIDWAIT: + return (iscsi_ioctl_daemon_wait(sc, + (struct iscsi_daemon_request *)arg)); + case ISCSIDHANDOFF: + return (iscsi_ioctl_daemon_handoff(sc, + (struct iscsi_daemon_handoff *)arg)); +#ifdef ICL_KERNEL_PROXY + case ISCSIDCONNECT: + return (iscsi_ioctl_daemon_connect(sc, + (struct iscsi_daemon_connect *)arg)); + case ISCSIDSEND: + return (iscsi_ioctl_daemon_send(sc, + (struct iscsi_daemon_send *)arg)); + case ISCSIDRECEIVE: + return (iscsi_ioctl_daemon_receive(sc, + (struct iscsi_daemon_receive *)arg)); + case ISCSIDCLOSE: + return (iscsi_ioctl_daemon_close(sc, + (struct iscsi_daemon_close *)arg)); +#endif /* ICL_KERNEL_PROXY */ + case ISCSISADD: + return (iscsi_ioctl_session_add(sc, + (struct iscsi_session_add *)arg)); + case ISCSISREMOVE: + return (iscsi_ioctl_session_remove(sc, + (struct iscsi_session_remove *)arg)); + case ISCSISLIST: + return (iscsi_ioctl_session_list(sc, + (struct iscsi_session_list *)arg)); + default: + return (EINVAL); + } +} + +static uint64_t +iscsi_encode_lun(uint32_t lun) +{ + uint8_t encoded[8]; + uint64_t result; + + memset(encoded, 0, sizeof(encoded)); + + if (lun < 256) { + /* + * Peripheral device addressing. + */ + encoded[1] = lun; + } else if (lun < 16384) { + /* + * Flat space addressing. + */ + encoded[0] = 0x40; + encoded[0] |= (lun >> 8) & 0x3f; + encoded[1] = lun & 0xff; + } else { + /* + * Extended flat space addressing. + */ + encoded[0] = 0xd2; + encoded[1] = lun >> 16; + encoded[2] = lun >> 8; + encoded[3] = lun; + } + + memcpy(&result, encoded, sizeof(result)); + return (result); +} + +static struct iscsi_outstanding * +iscsi_outstanding_find(struct iscsi_session *is, uint32_t initiator_task_tag) +{ + struct iscsi_outstanding *io; + + ISCSI_SESSION_LOCK_ASSERT(is); + + TAILQ_FOREACH(io, &is->is_outstanding, io_next) { + if (io->io_initiator_task_tag == initiator_task_tag) + return (io); + } + return (NULL); +} + +static int +iscsi_outstanding_add(struct iscsi_session *is, + uint32_t initiator_task_tag, union ccb *ccb) +{ + struct iscsi_outstanding *io; + + ISCSI_SESSION_LOCK_ASSERT(is); + + KASSERT(iscsi_outstanding_find(is, initiator_task_tag) == NULL, + ("initiator_task_tag 0x%x already added", initiator_task_tag)); + + io = uma_zalloc(iscsi_outstanding_zone, M_NOWAIT | M_ZERO); + if (io == NULL) { + ISCSI_SESSION_WARN(is, "failed to allocate %zd bytes", sizeof(*io)); + return (ENOMEM); + } + io->io_initiator_task_tag = initiator_task_tag; + io->io_ccb = ccb; + TAILQ_INSERT_TAIL(&is->is_outstanding, io, io_next); + return (0); +} + +static void +iscsi_outstanding_remove(struct iscsi_session *is, struct iscsi_outstanding *io) +{ + + ISCSI_SESSION_LOCK_ASSERT(is); + + TAILQ_REMOVE(&is->is_outstanding, io, io_next); + uma_zfree(iscsi_outstanding_zone, io); +} + +static void +iscsi_action_scsiio(struct iscsi_session *is, union ccb *ccb) +{ + struct icl_pdu *request; + struct iscsi_bhs_scsi_command *bhssc; + struct ccb_scsiio *csio; + size_t len; + int error; + + ISCSI_SESSION_LOCK_ASSERT(is); + +#if 0 + KASSERT(is->is_login_phase == false, ("%s called during Login Phase", __func__)); +#else + if (is->is_login_phase) { + ISCSI_SESSION_DEBUG(is, "called during login phase"); + ccb->ccb_h.status = CAM_REQ_ABORTED; /* XXX: Proper error code? */ + xpt_done(ccb); + return; + } +#endif + + request = icl_pdu_new_bhs(is->is_conn, M_NOWAIT); + if (request == NULL) { + ccb->ccb_h.status = CAM_REQ_ABORTED; /* XXX: Proper error code? */ + xpt_done(ccb); + return; + } + + csio = &ccb->csio; + bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; + bhssc->bhssc_opcode = ISCSI_BHS_OPCODE_SCSI_COMMAND; + bhssc->bhssc_flags |= BHSSC_FLAGS_F; + switch (csio->ccb_h.flags & CAM_DIR_MASK) { + case CAM_DIR_IN: + bhssc->bhssc_flags |= BHSSC_FLAGS_R; + break; + case CAM_DIR_OUT: + bhssc->bhssc_flags |= BHSSC_FLAGS_W; + break; + } + + switch (csio->tag_action) { + case MSG_HEAD_OF_Q_TAG: + bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_HOQ; + break; + break; + case MSG_ORDERED_Q_TAG: + bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_ORDERED; + break; + case MSG_ACA_TASK: + bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_ACA; + break; + case CAM_TAG_ACTION_NONE: + case MSG_SIMPLE_Q_TAG: + default: + bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_SIMPLE; + break; + } + + bhssc->bhssc_lun = iscsi_encode_lun(csio->ccb_h.target_lun); + bhssc->bhssc_initiator_task_tag = is->is_initiator_task_tag; + is->is_initiator_task_tag++; + bhssc->bhssc_expected_data_transfer_length = htonl(csio->dxfer_len); + KASSERT(csio->cdb_len <= sizeof(bhssc->bhssc_cdb), + ("unsupported CDB size %zd", csio->cdb_len)); + + if (csio->ccb_h.flags & CAM_CDB_POINTER) + memcpy(&bhssc->bhssc_cdb, csio->cdb_io.cdb_ptr, csio->cdb_len); + else + memcpy(&bhssc->bhssc_cdb, csio->cdb_io.cdb_bytes, csio->cdb_len); + + error = iscsi_outstanding_add(is, bhssc->bhssc_initiator_task_tag, ccb); + if (error != 0) { + icl_pdu_free(request); + ccb->ccb_h.status = CAM_REQ_ABORTED; + xpt_done(ccb); + } + + if (is->is_desc.isd_immediate_data && + (csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) { + len = csio->dxfer_len; + //ISCSI_SESSION_DEBUG(is, "adding %zd of immediate data", len); + if (len > is->is_first_burst_length) { + ISCSI_SESSION_DEBUG(is, "len %zd -> %zd", len, is->is_first_burst_length); + len = is->is_first_burst_length; + } + + /* + * XXX: Ugly. + */ + ISCSI_SESSION_UNLOCK(is); + icl_pdu_append_data(request, csio->data_ptr, len); + ISCSI_SESSION_LOCK(is); + } + iscsi_pdu_queue_locked(request); +} + +static void +iscsi_action(struct cam_sim *sim, union ccb *ccb) +{ + struct iscsi_session *is; + + is = cam_sim_softc(sim); + + ISCSI_SESSION_LOCK_ASSERT(is); + + switch (ccb->ccb_h.func_code) { + case XPT_PATH_INQ: + { + struct ccb_pathinq *cpi = &ccb->cpi; + + cpi->version_num = 1; + cpi->hba_inquiry = PI_TAG_ABLE; + cpi->target_sprt = 0; + //cpi->hba_misc = PIM_NOBUSRESET; + cpi->hba_misc = 0; + cpi->hba_eng_cnt = 0; + cpi->max_target = 0; + cpi->max_lun = 255; + //cpi->initiator_id = 0; /* XXX */ + cpi->initiator_id = 64; /* XXX */ + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "iSCSI", HBA_IDLEN); + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->bus_id = cam_sim_bus(sim); + cpi->base_transfer_speed = 150000; /* XXX */ + cpi->transport = XPORT_ISCSI; + cpi->transport_version = 0; + cpi->protocol = PROTO_SCSI; + cpi->protocol_version = SCSI_REV_SPC; /* XXX: SPC3? */ + cpi->maxio = MAXPHYS; + cpi->ccb_h.status = CAM_REQ_CMP; + break; + } + case XPT_CALC_GEOMETRY: + cam_calc_geometry(&ccb->ccg, /*extended*/1); + ccb->ccb_h.status = CAM_REQ_CMP; + break; + case XPT_RESET_BUS: + case XPT_ABORT: + case XPT_TERM_IO: + ISCSI_DEBUG("faking success for reset, abort, or term_io"); + ccb->ccb_h.status = CAM_REQ_CMP; + break; + case XPT_SCSI_IO: + iscsi_action_scsiio(is, ccb); + return; + default: + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + break; + } + xpt_done(ccb); +} + +static void +iscsi_poll(struct cam_sim *sim) +{ + + KASSERT(0, ("%s: you're not supposed to be here", __func__)); +} + +static void +iscsi_shutdown(struct iscsi_softc *sc) +{ + struct iscsi_session *is; + + ISCSI_DEBUG("removing all sessions due to shutdown"); + + sx_slock(&sc->sc_lock); + TAILQ_FOREACH(is, &sc->sc_sessions, is_next) + iscsi_session_terminate(is); + sx_sunlock(&sc->sc_lock); +} + +static int +iscsi_load(void) +{ + int error; + + sc = malloc(sizeof(*sc), M_ISCSI, M_ZERO | M_WAITOK); + sx_init(&sc->sc_lock, "iscsi"); + TAILQ_INIT(&sc->sc_sessions); + cv_init(&sc->sc_cv, "iscsi_cv"); + + iscsi_outstanding_zone = uma_zcreate("iscsi_outstanding", + sizeof(struct iscsi_outstanding), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + + error = make_dev_p(MAKEDEV_CHECKNAME, &sc->sc_cdev, &iscsi_cdevsw, + NULL, UID_ROOT, GID_WHEEL, 0600, "iscsi"); + if (error != 0) { + ISCSI_WARN("failed to create device node, error %d", error); + sx_destroy(&sc->sc_lock); + cv_destroy(&sc->sc_cv); + uma_zdestroy(iscsi_outstanding_zone); + free(sc, M_ISCSI); + return (error); + } + sc->sc_cdev->si_drv1 = sc; + + /* + * XXX: For some reason this doesn't do its job; active sessions still hang out there + * after final sync, making the reboot effectively hang. + */ + sc->sc_shutdown_eh = EVENTHANDLER_REGISTER(shutdown_post_sync, iscsi_shutdown, sc, SHUTDOWN_PRI_DEFAULT); + + return (0); +} + +static int +iscsi_unload(void) +{ + /* + * XXX: kldunload hangs on "devdrn". + */ + struct iscsi_session *is, *tmp; + + ISCSI_DEBUG("removing device node"); + destroy_dev(sc->sc_cdev); + ISCSI_DEBUG("device node removed"); + + EVENTHANDLER_DEREGISTER(shutdown_post_sync, sc->sc_shutdown_eh); + + sx_slock(&sc->sc_lock); + TAILQ_FOREACH_SAFE(is, &sc->sc_sessions, is_next, tmp) + iscsi_session_terminate(is); + while(!TAILQ_EMPTY(&sc->sc_sessions)) { + ISCSI_DEBUG("waiting for sessions to terminate"); + cv_wait(&sc->sc_cv, &sc->sc_lock); + } + ISCSI_DEBUG("all sessions terminated"); + sx_sunlock(&sc->sc_lock); + + uma_zdestroy(iscsi_outstanding_zone); + sx_destroy(&sc->sc_lock); + cv_destroy(&sc->sc_cv); + free(sc, M_ISCSI); + return (0); +} + +static int +iscsi_quiesce(void) +{ + sx_slock(&sc->sc_lock); + if (!TAILQ_EMPTY(&sc->sc_sessions)) { + sx_sunlock(&sc->sc_lock); + return (EBUSY); + } + sx_sunlock(&sc->sc_lock); + return (0); +} + +static int +iscsi_modevent(module_t mod, int what, void *arg) +{ + int error; + + switch (what) { + case MOD_LOAD: + error = iscsi_load(); + break; + case MOD_UNLOAD: + error = iscsi_unload(); + break; + case MOD_QUIESCE: + error = iscsi_quiesce(); + break; + default: + error = EINVAL; + break; + } + return (error); +} + +moduledata_t iscsi_data = { + "iscsi2", + iscsi_modevent, + 0 +}; + +DECLARE_MODULE(iscsi2, iscsi_data, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); +MODULE_DEPEND(iscsi2, cam, 1, 1, 1); +MODULE_DEPEND(iscsi2, icl, 1, 1, 1); diff -urNp p4/freebsd/src/sys/dev/iscsi/iscsi.h p4/iscsi/sys/dev/iscsi/iscsi.h --- p4/freebsd/src/sys/dev/iscsi/iscsi.h 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/sys/dev/iscsi/iscsi.h 2013-08-20 11:52:23.000000000 +0200 @@ -0,0 +1,126 @@ +/*- + * 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 + +struct iscsi_softc; +struct icl_conn; + +#define ISCSI_NAME_LEN 224 /* 223 bytes, by RFC 3720, + '\0' */ +#define ISCSI_ADDR_LEN 47 /* INET6_ADDRSTRLEN + '\0' */ +#define ISCSI_SECRET_LEN 17 /* 16 + '\0' */ + +struct iscsi_outstanding { + TAILQ_ENTRY(iscsi_outstanding) io_next; + uint32_t io_initiator_task_tag; + uint32_t io_datasn; + union ccb *io_ccb; +}; + +struct iscsi_session { + TAILQ_ENTRY(iscsi_session) is_next; + + struct icl_conn *is_conn; + struct mtx is_lock; + + uint32_t is_statsn; + uint32_t is_cmdsn; + uint32_t is_expcmdsn; + uint32_t is_maxcmdsn; + uint32_t is_initiator_task_tag; + int is_header_digest; + int is_data_digest; + int is_initial_r2t; + size_t is_max_burst_length; + size_t is_first_burst_length; + uint8_t is_isid[6]; + + TAILQ_HEAD(, iscsi_outstanding) is_outstanding; + TAILQ_HEAD(, icl_pdu) is_postponed; + + struct callout is_callout; + unsigned int is_timeout; + + /* + * XXX: This could be rewritten using a single variable, + * but somehow it results in uglier code. + */ + /* + * We're waiting for iscsid(8); after iscsid_timeout + * expires, kernel will wake up an iscsid(8) to handle + * the session. + */ + bool is_waiting_for_iscsid; + + /* + * Some iscsid(8) instance is handling the session; + * after login_timeout expires, kernel will wake up + * another iscsid(8) to handle the session. + */ + bool is_login_phase; + + /* + * We're in the process of removing the iSCSI session. + */ + bool is_terminating; + + /* + * We're waiting for the maintenance thread to do some + * reconnection tasks. + */ + bool is_reconnecting; + + struct cam_devq *is_devq; + struct cam_sim *is_sim; + struct cam_path *is_path; + struct cv is_maintenance_cv; + struct iscsi_softc *is_softc; + struct iscsi_session_desc is_desc; + bool is_simq_frozen; + +#ifdef ICL_KERNEL_PROXY + struct cv is_login_cv;; + struct icl_pdu *is_login_pdu; +#endif +}; + +struct iscsi_softc { + device_t sc_dev; + struct sx sc_lock; + struct cdev *sc_cdev; + TAILQ_HEAD(, iscsi_session) sc_sessions; + struct cv sc_cv; + unsigned int sc_last_session_id; + eventhandler_tag sc_shutdown_eh; +}; + +#endif /* !ISCSI_H */ diff -urNp p4/freebsd/src/sys/dev/iscsi/iscsi_ioctl.h p4/iscsi/sys/dev/iscsi/iscsi_ioctl.h --- p4/freebsd/src/sys/dev/iscsi/iscsi_ioctl.h 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/sys/dev/iscsi/iscsi_ioctl.h 2013-08-18 13:07:26.000000000 +0200 @@ -0,0 +1,179 @@ +/*- + * 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_IOCTL_H +#define ISCSI_IOCTL_H + +#ifdef ICL_KERNEL_PROXY +#include +#endif + +#define ISCSI_PATH "/dev/iscsi" +#define ISCSI_MAX_DATA_SEGMENT_LENGTH (128 * 1024) + +#define ISCSI_NAME_LEN 224 /* 223 bytes, by RFC 3720, + '\0' */ +#define ISCSI_ADDR_LEN 47 /* INET6_ADDRSTRLEN + '\0' */ +#define ISCSI_ALIAS_LEN 256 /* XXX: Where it's defined? */ +#define ISCSI_SECRET_LEN 17 /* 16 + '\0' */ + +#define ISCSI_DIGEST_NONE 0 +#define ISCSI_DIGEST_CRC32C 1 + +struct iscsi_session_desc { + unsigned int isd_id; + char isd_initiator[ISCSI_NAME_LEN]; + char isd_initiator_addr[ISCSI_ADDR_LEN]; + char isd_initiator_alias[ISCSI_ALIAS_LEN]; + char isd_target[ISCSI_NAME_LEN]; + char isd_target_addr[ISCSI_ADDR_LEN]; + char isd_target_alias[ISCSI_ALIAS_LEN]; + char isd_user[ISCSI_NAME_LEN]; + char isd_secret[ISCSI_SECRET_LEN]; + char isd_mutual_user[ISCSI_NAME_LEN]; + char isd_mutual_secret[ISCSI_SECRET_LEN]; + int isd_discovery; + int isd_header_digest; + int isd_data_digest; + int isd_max_data_segment_length; + int isd_immediate_data; + int isd_iser; + int isd_connected; +}; + +/* + * For use by iscsid(8). + */ + +#define ISCSI_DAEMON_CMD_LOGIN 0x01 + +struct iscsi_daemon_request { + int idr_cmd; + struct iscsi_session_desc idr_desc; +}; + +struct iscsi_daemon_handoff { + unsigned int idh_session_id; + int idh_socket; + char idh_target_alias[ISCSI_ALIAS_LEN]; + uint8_t idh_isid[6]; + uint32_t idh_statsn; + int idh_header_digest; + int idh_data_digest; + int idh_initial_r2t; + int idh_immediate_data; + size_t idh_max_data_segment_length; + size_t idh_max_burst_length; + size_t idh_first_burst_length; +}; + +#define ISCSIDWAIT _IOR('I', 0x01, struct iscsi_daemon_request) +#define ISCSIDHANDOFF _IOW('I', 0x02, struct iscsi_daemon_handoff) + +#ifdef ICL_KERNEL_PROXY + +/* + * When ICL_KERNEL_PROXY is not defined, the iscsid(8) is responsible + * for creating the socket, connecting, performing Login Phase using + * socked in the usual userspace way, and then passing the socket file + * descriptor to the kernel part using ISCSIDHANDOFF. + * + * When ICL_KERNEL_PROXY is defined, the iscsid(8) creates the session + * using ISCSICONNECT, performs Login Phase using ISCSISEND/ISCSIRECEIVE + * instead of read(2)/write(2), and then calls ISCSIDHANDOFF with + * idh_socket set to 0. + * + * The purpose of ICL_KERNEL_PROXY is to workaround the fact that, + * at this time, it's not possible to do iWARP (RDMA) in userspace. + */ + +struct iscsi_daemon_connect { + int idc_session_id; + int idc_iser; + int idc_domain; + int idc_socktype; + int idc_protocol; + struct sockaddr *idc_from_addr; + socklen_t idc_from_addrlen; + struct sockaddr *idc_to_addr; + socklen_t idc_to_addrlen; +}; + +struct iscsi_daemon_send { + int ids_session_id; + void *ids_bhs; + size_t ids_spare; + void *ids_spare2; + size_t ids_data_segment_len; + void *ids_data_segment; +}; + +struct iscsi_daemon_receive { + int idr_session_id; + void *idr_bhs; + size_t idr_spare; + void *idr_spare2; + size_t idr_data_segment_len; + void *idr_data_segment; +}; + +struct iscsi_daemon_close { + int idc_session_id; +}; + +#define ISCSIDCONNECT _IOWR('I', 0x03, struct iscsi_daemon_connect) +#define ISCSIDSEND _IOWR('I', 0x04, struct iscsi_daemon_send) +#define ISCSIDRECEIVE _IOWR('I', 0x05, struct iscsi_daemon_receive) +#define ISCSIDCLOSE _IOWR('I', 0x06, struct iscsi_daemon_close) + +#endif /* ICL_KERNEL_PROXY */ + +/* + * For use by iscsictl(8). + */ + +struct iscsi_session_add { + struct iscsi_session_desc isa_desc; +}; + +struct iscsi_session_remove { + struct iscsi_session_desc isr_desc; +}; + +struct iscsi_session_list { + unsigned int isl_nentries; + struct iscsi_session_desc *isl_pdesc; +}; + +#define ISCSISADD _IOW('I', 0x11, struct iscsi_session_add) +#define ISCSISREMOVE _IOW('I', 0x12, struct iscsi_session_remove) +#define ISCSISLIST _IOWR('I', 0x13, struct iscsi_session_list) + +#endif /* !ISCSI_IOCTL_H */ diff -urNp p4/freebsd/src/sys/dev/iscsi/iscsi_proto.h p4/iscsi/sys/dev/iscsi/iscsi_proto.h --- p4/freebsd/src/sys/dev/iscsi/iscsi_proto.h 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/sys/dev/iscsi/iscsi_proto.h 2013-08-20 11:52:23.000000000 +0200 @@ -0,0 +1,439 @@ +/*- + * 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_PROTO_H +#define ISCSI_PROTO_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 + +#define BHSSC_FLAGS_ATTR_UNTAGGED 0 +#define BHSSC_FLAGS_ATTR_SIMPLE 1 +#define BHSSC_FLAGS_ATTR_ORDERED 2 +#define BHSSC_FLAGS_ATTR_HOQ 3 +#define BHSSC_FLAGS_ATTR_ACA 4 + +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_CONNECTION 2 +#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_PROTO_H */ diff -urNp p4/freebsd/src/sys/modules/ctl/Makefile p4/iscsi/sys/modules/ctl/Makefile --- p4/freebsd/src/sys/modules/ctl/Makefile 2013-04-20 20:14:46.000000000 +0200 +++ p4/iscsi/sys/modules/ctl/Makefile 2013-08-21 19:52:26.000000000 +0200 @@ -12,6 +12,7 @@ SRCS+= ctl_cmd_table.c SRCS+= ctl_frontend.c SRCS+= ctl_frontend_cam_sim.c SRCS+= ctl_frontend_internal.c +SRCS+= ctl_frontend_iscsi.c SRCS+= ctl_mem_pool.c SRCS+= ctl_scsi_all.c SRCS+= ctl_error.c @@ -23,4 +24,6 @@ SRCS+= vnode_if.h SRCS+= opt_cam.h SRCS+= opt_kdtrace.h +#CFLAGS+=-DICL_KERNEL_PROXY + .include diff -urNp p4/freebsd/src/sys/modules/iscsi/Makefile p4/iscsi/sys/modules/iscsi/Makefile --- p4/freebsd/src/sys/modules/iscsi/Makefile 2013-04-20 20:14:48.000000000 +0200 +++ p4/iscsi/sys/modules/iscsi/Makefile 2013-08-21 19:52:26.000000000 +0200 @@ -1,5 +1,25 @@ -# $FreeBSD: head/sys/modules/iscsi/Makefile 171568 2007-07-24 15:35:02Z scottl $ +# $FreeBSD$ -SUBDIR= initiator +.PATH: ${.CURDIR}/../../dev/iscsi/ +KMOD= iscsi -.include +SRCS= iscsi.c +.if defined(ICL_RDMA) +SRCS+= icl_rdma.c +.else +SRCS+= icl.c +.endif +SRCS+= icl_proxy.c +SRCS+= opt_cam.h +SRCS+= bus_if.h +SRCS+= device_if.h + +# Those below are required for RDMA. +SRCS+= vnode_if.h +SRCS+= opt_inet.h +SRCS+= opt_inet6.h + +CFLAGS+= -I${.CURDIR}/../../ofed/include +#CFLAGS+=-DICL_KERNEL_PROXY + +.include diff -urNp p4/freebsd/src/tools/regression/iscsi/ctl.conf p4/iscsi/tools/regression/iscsi/ctl.conf --- p4/freebsd/src/tools/regression/iscsi/ctl.conf 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/tools/regression/iscsi/ctl.conf 2013-07-23 23:47:15.000000000 +0200 @@ -0,0 +1,35 @@ +auth-group meh { + chap user secretsecret +} + +portal-group meh { + listen 0.0.0.0 + discovery-auth-group no-authentication +} + +target iqn.2012-06.com.example:1 { + auth-group no-authentication + portal-group meh + lun 0 { + path /var/tmp/example_t1l0 + size 4G + } + lun 1 { + path /var/tmp/example_t1l1 + size 4G + } +} + +target iqn.2012-06.com.example:2 { + auth-group meh + portal-group meh + lun 0 { + path /var/tmp/example_t2l0 + size 4G + } + lun 1 { + path /var/tmp/example_t2l1 + size 4G + } +} + diff -urNp p4/freebsd/src/tools/regression/iscsi/initiator-instructions.txt p4/iscsi/tools/regression/iscsi/initiator-instructions.txt --- p4/freebsd/src/tools/regression/iscsi/initiator-instructions.txt 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/tools/regression/iscsi/initiator-instructions.txt 2013-07-23 23:47:15.000000000 +0200 @@ -0,0 +1,59 @@ +How to prepare initiator virtual machines for iSCSI target testing +------------------------------------------------------------------ + +1. Install operating systems. + + - FreeBSD: Use default settings for everything. Don't install + ports from the system installer, use "portsnap fetch extract" + after installation instead. + + - Fedora: Change the environment to "Minimal install". + + - Solaris: Use defaults. + +2. Install required software. + + - FreeBSD: install from ports, with 'make install BATCH=1': + benchmarks/bonnie++ + benchmarks/iozone + benchmarks/postmark + databases/postgresql92-server + databases/postgresql92-contrib + + - Fedora: + yum install btrfs-progs bonnie++ postgresql-server postgresql-contrib iscsi-initiator-utils + chkconfig iscsid on + chkconfig iscsi on + + After that, install iozone and postmark from source; they are not + provided by Fedora; download sites: + + http://www.iozone.org/src/current/iozone3_397.tar + http://www.gtlib.gatech.edu/pub/debian/pool/main/p/postmark/postmark_1.53.orig.tar.gz + + To build iozone, use "make linux". Copy the 'postmark' and 'iozone' binaries + to /usr/local/bin/. + + - Solaris: + Install gcc: + + pkg install gcc-45 + pkg install system/header + + After that, install bonnie++, iozone, and postmark from source; download sites: + + http://www.coker.com.au/bonnie++/experimental/bonnie++-1.97.tgz + http://www.iozone.org/src/current/iozone3_397.tar + http://www.gtlib.gatech.edu/pub/debian/pool/main/p/postmark/postmark_1.53.orig.tar.gz + + To build iozone, use "make Solaris10gcc". Copy the 'bonnie++', 'postmark', and 'iozone' + binaries to /usr/bin/. + + Fetch the binary PostgreSQL distribution from the link below and untar to /usr/postgres/: + + http://ftp.postgresql.org/pub/binary/v9.2.3/solaris/solaris11/i386/postgresql-9.2.3-S11.i386-32.tar.bz2 + +3. Run the test script. + + ./iscsi-test.sh + diff -urNp p4/freebsd/src/tools/regression/iscsi/iscsi-test.sh p4/iscsi/tools/regression/iscsi/iscsi-test.sh --- p4/freebsd/src/tools/regression/iscsi/iscsi-test.sh 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/tools/regression/iscsi/iscsi-test.sh 2013-07-23 23:47:15.000000000 +0200 @@ -0,0 +1,725 @@ +#!/bin/sh +# +# 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$ +# + +# +# This expects that the iSCSI server being tested is at $TARGETIP and exports +# two targets: $TARGET1 and $TARGET2; the former requiring no authentication, +# and the latter using CHAP with user $USER and secret $SECRET. Discovery +# must be permitted without authentication. Each target must contain exactly +# two LUNs, 4GB each. For example, ctl.conf(5) should look like this: +# +# auth-group meh { +# chap user secretsecret +# } +# +# portal-group meh { +# listen 0.0.0.0 +# discovery-auth-group no-authentication +# } +# +# target iqn.2012-06.com.example:1 { +# auth-group no-authentication +# portal-group meh +# lun 0 { +# path /var/tmp/example_t1l0 +# size 4G +# } +# lun 1 { +# path /var/tmp/example_t1l1 +# size 4G +# } +# } +# +# target iqn.2012-06.com.example:2 { +# auth-group meh +# portal-group meh +# lun 0 { +# path /var/tmp/example_t2l0 +# size 4G +# } +# lun 1 { +# path /var/tmp/example_t2l1 +# size 4G +# } +# } +# +# Remember to create the backing files (/var/tmp/example_t1l0 etcc) +# +# On the initiator, $MNTDIR will be used for testing. +# + +TARGETIP=192.168.56.101 +TARGET1=iqn.2012-06.com.example:1 +TARGET2=iqn.2012-06.com.example:2 +USER=user +SECRET=secretsecret +MNTDIR=/mnt +TMPDIR=/tmp + +die() { + echo "$*" + exit 1 +} + +case `uname` in + FreeBSD) + LUN0=/dev/da0 + LUN1=/dev/da1 + LUN2=/dev/da2 + LUN3=/dev/da3 + ZFSPOOL=iscsipool + ;; + Linux) + LUN0=/dev/sdb + LUN1=/dev/sdc + LUN2=/dev/sdd + LUN3=/dev/sde + ;; + SunOS) + # LUN names are being set later, during attach. + ZFSPOOL=iscsipool + ;; + *) + die "unsupported system" + ;; +esac + +check() { + echo "# $@" > /dev/stderr + $@ || die "$@ failed" +} + +banner() { + echo "Will try to attach to $TARGET1 and $TARGET2 on $TARGETIP," + echo "user $USER, secret $SECRET. Will use mountpoint $MNTDIR, temporary dir $TMPDIR," + if [ -n "$LUN0" ]; then + echo "scratch disks $LUN0, $LUN1, $LUN2, $LUN3." + else + echo "scratch disks unknown at this stage." + fi + echo + echo "This script is NOT safe to run on multiuser system." + echo + echo "Press ^C to interrupt; will proceed in 5 seconds." + sleep 5 +} + +test_discovery_freebsd() { + kldload iscsi_initiator + check iscontrol -dt $TARGETIP > $TMPDIR/discovered + cat $TMPDIR/discovered + echo "TargetName=$TARGET1" > $TMPDIR/expected + echo "TargetName=$TARGET2" >> $TMPDIR/expected + check cmp $TMPDIR/expected $TMPDIR/discovered + rm -f $TMPDIR/expected $TMPDIR/discovered +} + +test_discovery_linux() { + cat > /etc/iscsi/iscsid.conf << END + +discovery.sendtargets.auth.authmethod = None +node.startup = manual + +END + + check iscsiadm -m discovery -t sendtargets -p $TARGETIP > $TMPDIR/discovered + cat $TMPDIR/discovered + echo "$TARGETIP:3260,-1 $TARGET1" > $TMPDIR/expected + echo "$TARGETIP:3260,-1 $TARGET2" >> $TMPDIR/expected + check cmp $TMPDIR/expected $TMPDIR/discovered + rm -f $TMPDIR/expected $TMPDIR/discovered + +} + +test_discovery_solaris() { + check iscsiadm add discovery-address $TARGETIP + check iscsiadm modify discovery --sendtargets enable + check iscsiadm modify discovery --static enable + check iscsiadm list target | awk '/^Target/ { print $2 }' | sort > $TMPDIR/discovered + check iscsiadm remove discovery-address $TARGETIP + cat $TMPDIR/discovered + echo "$TARGET1" > $TMPDIR/expected + echo "$TARGET2" >> $TMPDIR/expected + check cmp $TMPDIR/expected $TMPDIR/discovered + rm -f $TMPDIR/expected $TMPDIR/discovered +} + +test_discovery() { + echo "*** discovery test ***" + case `uname` in + FreeBSD) + test_discovery_freebsd + ;; + Linux) + test_discovery_linux + ;; + SunOS) + test_discovery_solaris + ;; + *) + die "unsupported system" + ;; + esac +} + +test_attach_freebsd() { + [ ! -e LUN0 ] || die "$LUN0 already exists" + [ ! -e LUN1 ] || die "$LUN1 already exists" + [ ! -e LUN2 ] || die "$LUN2 already exists" + [ ! -e LUN3 ] || die "$LUN3 already exists" + + cat > $TMPDIR/iscsi.conf << END + +target1 { + TargetName = $TARGET1 + TargetAddress = $TARGETIP +} + +target2 { + TargetName = $TARGET2 + TargetAddress = $TARGETIP + AuthMethod = CHAP + chapIName = $USER + chapSecret = $SECRET +} + +END + check iscontrol -c $TMPDIR/iscsi.conf -n target1 + check iscontrol -c $TMPDIR/iscsi.conf -n target2 + + echo "Waiting 10 seconds for attach to complete." + sleep 10 + + [ -e $LUN0 ] || die "$LUN0 doesn't exist" + [ -e $LUN1 ] || die "$LUN1 doesn't exist" + [ -e $LUN2 ] || die "$LUN2 doesn't exist" + [ -e $LUN3 ] || die "$LUN3 doesn't exist" + + rm $TMPDIR/iscsi.conf +} + +test_attach_linux() { + check iscsiadm --mode node --targetname "$TARGET1" -p "$TARGETIP:3260" --op=update --name node.session.auth.authmethod --value=None + check iscsiadm --mode node --targetname "$TARGET1" -p "$TARGETIP:3260" --login + check iscsiadm --mode node --targetname "$TARGET2" -p "$TARGETIP:3260" --op=update --name node.session.auth.authmethod --value=CHAP + check iscsiadm --mode node --targetname "$TARGET2" -p "$TARGETIP:3260" --op=update --name node.session.auth.username --value="$USER" + check iscsiadm --mode node --targetname "$TARGET2" -p "$TARGETIP:3260" --op=update --name node.session.auth.password --value="$SECRET" + check iscsiadm --mode node --targetname "$TARGET2" -p "$TARGETIP:3260" --login +} + +test_attach_solaris() { + # XXX: How to enter the CHAP secret from the script? For now, + # just use the first target, and thus first two LUNs. + #check iscsiadm modify initiator-node --authentication CHAP + #check iscsiadm modify initiator-node --CHAP-name $USER + #check iscsiadm modify initiator-node --CHAP-secret $SECRET + iscsiadm add static-config $TARGET1,$TARGETIP + LUN0=`iscsiadm list target -S | awk '/OS Device Name/ { print $4 }' | sed -n 1p` + LUN1=`iscsiadm list target -S | awk '/OS Device Name/ { print $4 }' | sed -n 2p` + LUN0=`echo ${LUN0}2 | sed 's/rdsk/dsk/'` + LUN1=`echo ${LUN1}2 | sed 's/rdsk/dsk/'` + [ -n "$LUN0" -a -n "LUN1" ] || die "attach failed" + echo "Scratch disks: $LUN0, $LUN1" +} + +test_attach() { + echo "*** attach test ***" + case `uname` in + FreeBSD) + test_attach_freebsd + ;; + Linux) + test_attach_linux + ;; + SunOS) + test_attach_solaris + ;; + *) + die "unsupported system" + ;; + esac +} + +test_newfs_freebsd_ufs() { + echo "*** UFS filesystem smoke test ***" + check newfs $LUN0 + check newfs $LUN1 + check newfs $LUN2 + check newfs $LUN3 + check fsck -t ufs $LUN0 + check fsck -t ufs $LUN1 + check fsck -t ufs $LUN2 + check fsck -t ufs $LUN3 +} + +test_newfs_freebsd_zfs() { + echo "*** ZFS filesystem smoke test ***" + check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3 + check zpool destroy -f $ZFSPOOL +} + +test_newfs_linux_ext4() { + echo "*** ext4 filesystem smoke test ***" + yes | check mkfs $LUN0 + yes | check mkfs $LUN1 + yes | check mkfs $LUN2 + yes | check mkfs $LUN3 + check fsck -f $LUN0 + check fsck -f $LUN1 + check fsck -f $LUN2 + check fsck -f $LUN3 +} + +test_newfs_linux_btrfs() { + echo "*** btrfs filesystem smoke test ***" + check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3 +} + + +test_newfs_solaris_ufs() { + echo "*** UFS filesystem smoke test ***" + yes | check newfs $LUN0 + yes | check newfs $LUN1 + check fsck -F ufs $LUN0 + check fsck -F ufs $LUN1 +} + +test_newfs_solaris_zfs() { + echo "*** ZFS filesystem smoke test ***" + check zpool create -f $ZFSPOOL $LUN0 $LUN1 + check zpool destroy -f $ZFSPOOL +} + +test_newfs() { + case `uname` in + FreeBSD) + test_newfs_freebsd_ufs + test_newfs_freebsd_zfs + ;; + Linux) + test_newfs_linux_ext4 + test_newfs_linux_btrfs + ;; + SunOS) + test_newfs_solaris_ufs + test_newfs_solaris_zfs + ;; + *) + die "unsupported system" + ;; + esac +} + +test_cp() { + echo "*** basic filesystem test ***" + case `uname` in + FreeBSD) + check newfs $LUN0 + check mount -t ufs $LUN0 $MNTDIR + check dd if=/dev/urandom of=$MNTDIR/1 bs=1m count=500 + check cp $MNTDIR/1 $MNTDIR/2 + check umount $MNTDIR + check fsck -t ufs $LUN0 + check mount -t ufs $LUN0 $MNTDIR + check cmp $MNTDIR/1 $MNTDIR/2 + check umount $MNTDIR + + check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3 + check dd if=/dev/urandom of=/$ZFSPOOL/1 bs=1m count=500 + check zpool scrub $ZFSPOOL + check cp /$ZFSPOOL/1 /$ZFSPOOL/2 + check cmp /$ZFSPOOL/1 /$ZFSPOOL/2 + check zpool destroy -f $ZFSPOOL + ;; + Linux) + yes | check mkfs $LUN0 + check mount $LUN0 $MNTDIR + check dd if=/dev/urandom of=$MNTDIR/1 bs=1M count=500 + check cp $MNTDIR/1 $MNTDIR/2 + check umount $MNTDIR + check fsck -f $LUN0 + check mount $LUN0 $MNTDIR + check cmp $MNTDIR/1 $MNTDIR/2 + check umount $MNTDIR + + check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3 + check mount $LUN0 $MNTDIR + check dd if=/dev/urandom of=$MNTDIR/1 bs=1M count=500 + check cp $MNTDIR/1 $MNTDIR/2 + check umount $MNTDIR + check mount $LUN0 $MNTDIR + check cmp $MNTDIR/1 $MNTDIR/2 + check umount $MNTDIR + ;; + SunOS) + yes | check newfs $LUN0 + check mount -F ufs $LUN0 $MNTDIR + check dd if=/dev/urandom of=$MNTDIR/1 bs=1024k count=500 + check cp $MNTDIR/1 $MNTDIR/2 + check umount $MNTDIR + check fsck -yF ufs $LUN0 + check mount -F ufs $LUN0 $MNTDIR + check cmp $MNTDIR/1 $MNTDIR/2 + check umount $MNTDIR + + check zpool create -f $ZFSPOOL $LUN0 $LUN1 + check dd if=/dev/urandom of=/$ZFSPOOL/1 bs=1024k count=500 + check zpool scrub $ZFSPOOL + check cp /$ZFSPOOL/1 /$ZFSPOOL/2 + check cmp /$ZFSPOOL/1 /$ZFSPOOL/2 + check zpool destroy -f $ZFSPOOL + ;; + *) + die "unsupported system" + ;; + esac +} + +test_bonnie() { + echo "*** bonnie++ ***" + case `uname` in + FreeBSD) + check newfs $LUN0 + check mount -t ufs $LUN0 $MNTDIR + check cd $MNTDIR + check bonnie++ -u root -s 2g -r 1g -n0 + check bonnie++ -u root -s 0 + check cd - + check umount $MNTDIR + check fsck -t ufs $LUN0 + + check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3 + check cd /$ZFSPOOL + check bonnie++ -u root -s 2g -r 1g -n0 + check bonnie++ -u root -s 0 + check cd - + check zpool destroy -f $ZFSPOOL + ;; + Linux) + yes | check mkfs $LUN0 + check mount $LUN0 $MNTDIR + check cd $MNTDIR + check bonnie++ -u root -s 2g -r 1g -n0 + check bonnie++ -u root -s 0 + check cd - + check umount $MNTDIR + check fsck -f $LUN0 + + check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3 + check mount $LUN0 $MNTDIR + check cd $MNTDIR + check bonnie++ -u root -s 2g -r 1g -n0 + check bonnie++ -u root -s 0 + check cd - + check umount $MNTDIR + ;; + SunOS) + yes | check newfs $LUN0 + check mount -F ufs $LUN0 $MNTDIR + check cd $MNTDIR + check bonnie++ -u root -s 2g -r 1g -n0 + check bonnie++ -u root -s 0 + check cd - + check umount $MNTDIR + check fsck -yF ufs $LUN0 + + check zpool create -f $ZFSPOOL $LUN0 $LUN1 + check cd /$ZFSPOOL + check bonnie++ -u root -s 2g -r 1g -n0 + check bonnie++ -u root -s 0 + check cd - + check zpool destroy -f $ZFSPOOL + ;; + *) + die "unsupported system" + ;; + esac +} + +test_iozone() { + echo "*** iozone ***" + case `uname` in + FreeBSD) + check newfs $LUN0 + check mount -t ufs $LUN0 $MNTDIR + check cd $MNTDIR + check iozone -a + check cd - + check umount $MNTDIR + check fsck -t ufs $LUN0 + + check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3 + check cd /$ZFSPOOL + check iozone -a + check cd - + check zpool destroy -f $ZFSPOOL + ;; + Linux) + yes | check mkfs $LUN0 + check mount $LUN0 $MNTDIR + check cd $MNTDIR + check iozone -a + check cd - + check umount $MNTDIR + check fsck -f $LUN0 + + check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3 + check mount $LUN0 $MNTDIR + check cd $MNTDIR + check iozone -a + check cd - + check umount $MNTDIR + ;; + SunOS) + yes | check newfs $LUN0 + check mount -F ufs $LUN0 $MNTDIR + check cd $MNTDIR + check iozone -a + check cd - + check umount $MNTDIR + check fsck -yF ufs $LUN0 + + check zpool create -f $ZFSPOOL $LUN0 $LUN1 + check cd /$ZFSPOOL + check iozone -a + check cd - + check zpool destroy -f $ZFSPOOL + ;; + *) + die "unsupported system" + ;; + esac + +} + +test_postmark() { + echo "*** postmark ***" + case `uname` in + FreeBSD) + check newfs $LUN0 + check mount -t ufs $LUN0 $MNTDIR + check cd $MNTDIR + printf "set number 10000\nrun\n" | check postmark + check cd - + check umount $MNTDIR + check fsck -t ufs $LUN0 + + check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3 + check cd /$ZFSPOOL + printf "set number 10000\nrun\n" | check postmark + check cd - + check zpool destroy -f $ZFSPOOL + ;; + Linux) + yes | check mkfs $LUN0 + check mount $LUN0 $MNTDIR + check cd $MNTDIR + printf "set number 10000\nrun\n" | check postmark + check cd - + check umount $MNTDIR + check fsck -f $LUN0 + + check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3 + check mount $LUN0 $MNTDIR + check cd $MNTDIR + printf "set number 10000\nrun\n" | check postmark + check cd - + check umount $MNTDIR + ;; + SunOS) + yes | check newfs $LUN0 + check mount -F ufs $LUN0 $MNTDIR + check cd $MNTDIR + printf "set number 10000\nrun\n" | check postmark + check cd - + check umount $MNTDIR + check fsck -yF ufs $LUN0 + + check zpool create -f $ZFSPOOL $LUN0 $LUN1 + check cd /$ZFSPOOL + printf "set number 10000\nrun\n" | check postmark + check cd - + check zpool destroy -f $ZFSPOOL + ;; + *) + die "unsupported system" + ;; + esac +} + +test_postgresql_freebsd() { + check newfs $LUN0 + check mount -t ufs $LUN0 $MNTDIR + check chown pgsql $MNTDIR + check chmod 755 $MNTDIR + check cd / + # XXX: How to make 'check' work here? + su -m pgsql -c "initdb -D $MNTDIR/db" + su -m pgsql -c "pg_ctl -D $MNTDIR/db -l /tmp/log start" + check sleep 10 + su -m pgsql -c "pgbench -i postgres" + su -m pgsql -c "pgbench -t 10000 postgres" + su -m pgsql -c "pg_ctl -D $MNTDIR/db stop" + check cd - + check umount $MNTDIR + check fsck -t ufs $LUN0 + + check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3 + check chown pgsql /$ZFSPOOL + check chmod 755 /$ZFSPOOL + check cd / + # XXX: How to make 'check' work here? + su -m pgsql -c "initdb -D /$ZFSPOOL/db" + su -m pgsql -c "pg_ctl -D /$ZFSPOOL/db -l /tmp/log start" + check sleep 10 + su -m pgsql -c "pgbench -i postgres" + su -m pgsql -c "pgbench -t 10000 postgres" + su -m pgsql -c "pg_ctl -D /$ZFSPOOL/db stop" + check cd - + check zpool destroy -f $ZFSPOOL +} + +test_postgresql_linux() { + yes | check mkfs $LUN0 + check mount $LUN0 $MNTDIR + check chown postgres $MNTDIR + check chmod 755 $MNTDIR + check cd / + # XXX: How to make 'check' work here? + su -m postgres -c "initdb -D $MNTDIR/db" + su -m postgres -c "pg_ctl -D $MNTDIR/db -l /tmp/log start" + check sleep 5 + su -m postgres -c "pgbench -i" + su -m postgres -c "pgbench -t 10000" + su -m postgres -c "pg_ctl -D $MNTDIR/db stop" + check cd - + check umount $MNTDIR + check fsck -f $LUN0 + + check mkfs.btrfs $LUN0 $LUN1 $LUN2 $LUN3 + check mount $LUN0 $MNTDIR + check chown postgres $MNTDIR + check chmod 755 $MNTDIR + check cd / + su -m postgres -c "initdb -D $MNTDIR/db" + su -m postgres -c "pg_ctl -D $MNTDIR/db -l /tmp/log start" + check sleep 5 + su -m postgres -c "pgbench -i" + su -m postgres -c "pgbench -t 10000" + su -m postgres -c "pg_ctl -D $MNTDIR/db stop" + check cd - + check umount $MNTDIR +} + +test_postgresql_solaris() { + PATH="$PATH:/usr/postgres/9.2-pgdg/bin" export PATH + yes | check newfs $LUN0 + check mount -F ufs $LUN0 $MNTDIR + check chown postgres $MNTDIR + check chmod 755 $MNTDIR + check cd / + # XXX: How to make 'check' work here? + su postgres -c "initdb -D $MNTDIR/db" + su postgres -c "pg_ctl -D $MNTDIR/db -l /tmp/log start" + check sleep 10 + su postgres -c "pgbench -i postgres" + su postgres -c "pgbench -t 10000 postgres" + su postgres -c "pg_ctl -D $MNTDIR/db stop" + check cd - + check umount $MNTDIR + check fsck -yF ufs $LUN0 + + check zpool create -f $ZFSPOOL $LUN0 $LUN1 $LUN2 $LUN3 + check chown postgres /$ZFSPOOL + check chmod 755 /$ZFSPOOL + check cd / + # XXX: How to make 'check' work here? + su postgres -c "initdb -D /$ZFSPOOL/db" + su postgres -c "pg_ctl -D /$ZFSPOOL/db -l /tmp/log start" + check sleep 10 + su postgres -c "pgbench -i postgres" + su postgres -c "pgbench -t 10000 postgres" + su postgres -c "pg_ctl -D /$ZFSPOOL/db stop" + check cd - + check zpool destroy -f $ZFSPOOL +} + +test_postgresql() { + echo "*** postgresql ***" + case `uname` in + FreeBSD) + test_postgresql_freebsd + ;; + Linux) + test_postgresql_linux + ;; + SunOS) + test_postgresql_solaris + ;; + *) + die "unsupported system" + ;; + esac +} + +test_detach() { + echo "*** detach ***" + case `uname` in + FreeBSD) + echo "*** detaching not supported on FreeBSD ***" + echo "*** please reboot the initiator VM before trying to run this script again ***" + ;; + Linux) + check iscsiadm -m node --logout + ;; + SunOS) + check iscsiadm remove static-config $TARGET1,$TARGETIP + ;; + *) + die "unsupported system" + ;; + esac +} + +banner +test_discovery +test_attach +test_newfs +test_cp +test_bonnie +test_iozone +test_postmark +test_postgresql +test_detach + +echo "*** done ***" + diff -urNp p4/freebsd/src/tools/test/xferxer/Makefile p4/iscsi/tools/test/xferxer/Makefile --- p4/freebsd/src/tools/test/xferxer/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/tools/test/xferxer/Makefile 2013-07-23 23:47:49.000000000 +0200 @@ -0,0 +1,11 @@ +# $FreeBSD$ + +PROG= xferxer +SRCS= xferxer.c +MAN= + +LDADD= -lssl + +WARNS= 6 + +.include diff -urNp p4/freebsd/src/tools/test/xferxer/xferxer.c p4/iscsi/tools/test/xferxer/xferxer.c --- p4/freebsd/src/tools/test/xferxer/xferxer.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/tools/test/xferxer/xferxer.c 2013-07-23 23:47:49.000000000 +0200 @@ -0,0 +1,231 @@ +/*- + * Copyright (c) 2013 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$ + */ + +/* + * Transfer exerciser. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void +test_read(int dev_fd, int file_fd, off_t off, size_t len) +{ + void *dev_buf, *file_buf; + ssize_t ret; + + dev_buf = malloc(len); + if (dev_buf == NULL) + err(1, "malloc"); + + file_buf = malloc(len); + if (dev_buf == NULL) + err(1, "malloc"); + + ret = pread(dev_fd, dev_buf, len, off); + if (ret < 0) + err(1, "read from device"); + if (ret != (ssize_t)len) + errx(1, "short read from device"); + + ret = pread(file_fd, file_buf, len, off); + if (ret < 0) + err(1, "read from file"); + if (ret != (ssize_t)len) + errx(1, "short read from file"); + + if (memcmp(dev_buf, file_buf, len)) + errx(1, "reading %zd bytes at offset %zd resulted in differing data", len, off); + + free(dev_buf); + free(file_buf); +} + +static void +test_write(int dev_fd, int file_fd, off_t off, size_t len) +{ + void *buf; + ssize_t ret; + int rv; + + buf = malloc(len); + if (buf == NULL) + err(1, "malloc"); + + rv = RAND_bytes(buf, len); + if (rv != 1) + errx(1, "RAND_bytes failed: %s", ERR_error_string(ERR_get_error(), NULL)); + + ret = pwrite(dev_fd, buf, len, off); + if (ret < 0) + err(1, "write to device"); + if (ret != (ssize_t)len) + errx(1, "short write to device"); + + ret = pwrite(file_fd, buf, len, off); + if (ret < 0) + err(1, "write to file"); + if (ret != (ssize_t)len) + errx(1, "short write to file"); + + free(buf); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: xferxer [-r][-v][-w] device-path file-path\n"); + exit(1); +} + +int +main(int argc, char **argv) +{ + bool verbose = false, reread = true, test_writes = false; + char *dev_name, *file_name; + int error, ch, do_write, dev_fd, file_fd, rv; + int max_len = 1024 * 1024; + off_t dev_size, off; + size_t len; + struct stat sb; + + while ((ch = getopt(argc, argv, "rvw")) != -1) { + switch (ch) { + case 'r': + reread = false; + break; + case 'v': + verbose = true; + break; + case 'w': + test_writes = true; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + if (argc != 2) + usage(); + + dev_name = argv[0]; + file_name = argv[1]; + + if (test_writes) + dev_fd = open(dev_name, O_RDWR); + else + dev_fd = open(dev_name, O_RDONLY); + if (dev_fd < 0) + err(1, "%s", dev_name); + + if (test_writes) + file_fd = open(file_name, O_RDWR); + else + file_fd = open(file_name, O_RDONLY); + if (file_fd < 0) + err(1, "%s", file_name); + + if (ioctl(dev_fd, DIOCGMEDIASIZE, &dev_size) == -1) + err(1,"DIOCGMEDIASIZE"); + error = fstat(file_fd, &sb); + if (error < 0) + err(1, "fstat"); + if (dev_size != sb.st_size) + errx(1, "file size %zd is different from device size %zd", sb.st_size, dev_size); + + if (verbose) { + if (test_writes) + printf("performing destructive write test on %s and %s, size %zd\n", dev_name, file_name, dev_size); + else + printf("performing read-only test on %s and %s, size %zd\n", dev_name, file_name, dev_size); + } + + for (;;) { + if (test_writes) { + rv = RAND_bytes((void *)&do_write, sizeof(do_write)); + if (rv != 1) + errx(1, "RAND_bytes failed: %s", ERR_error_string(ERR_get_error(), NULL)); + + do_write = do_write % 2; + if (do_write < 0) + do_write = -do_write; + assert(do_write == 0 || do_write == 1); + } else { + do_write = 0; + } + + rv = RAND_bytes((void *)&len, sizeof(len)); + if (rv != 1) + errx(1, "RAND_bytes failed: %s", ERR_error_string(ERR_get_error(), NULL)); + len = len % max_len; + len = (len / 512) * 512; + if (len == 0) + continue; + + rv = RAND_bytes((void *)&off, sizeof(off)); + if (rv != 1) + errx(1, "RAND_bytes failed: %s", ERR_error_string(ERR_get_error(), NULL)); + if (off < 0) + off = -off; + off = (off / 512) * 512; + if ((off_t)(off + len) >= dev_size) + off = off % (dev_size - len); + + if (do_write) { + if (verbose) + printf("writing %zd bytes at offset %zd\n", len, off); + test_write(dev_fd, file_fd, off, len); + if (reread) { + if (verbose) + printf("rereading %zd bytes at offset %zd\n", len, off); + test_read(dev_fd, file_fd, off, len); + } + } else { + if (verbose) + printf("reading %zd bytes at offset %zd\n", len, off); + test_read(dev_fd, file_fd, off, len); + } + } +} diff -urNp p4/freebsd/src/usr.bin/Makefile p4/iscsi/usr.bin/Makefile --- p4/freebsd/src/usr.bin/Makefile 2013-08-21 09:47:27.000000000 +0200 +++ p4/iscsi/usr.bin/Makefile 2013-08-21 10:52:19.000000000 +0200 @@ -68,6 +68,7 @@ SUBDIR= alias \ id \ ipcrm \ ipcs \ + iscsictl \ join \ jot \ ${_kdump} \ diff -urNp p4/freebsd/src/usr.bin/iscsictl/Makefile p4/iscsi/usr.bin/iscsictl/Makefile --- p4/freebsd/src/usr.bin/iscsictl/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.bin/iscsictl/Makefile 2013-07-23 23:47:58.000000000 +0200 @@ -0,0 +1,19 @@ +# $FreeBSD$ + +PROG= iscsictl +SRCS= iscsictl.c periphs.c parse.y token.l y.tab.h +CFLAGS+= -I${.CURDIR} +CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi +MAN= iscsictl.8 + +DPADD= ${LIBCAM} ${LIBUTIL} +LDADD= -lcam -lfl -lutil + +YFLAGS+= -v +LFLAGS+= -i +CLEANFILES= y.tab.c y.tab.h y.output + +WARNS= 6 +NO_WMISSING_VARIABLE_DECLARATIONS= + +.include diff -urNp p4/freebsd/src/usr.bin/iscsictl/iscsictl.8 p4/iscsi/usr.bin/iscsictl/iscsictl.8 --- p4/freebsd/src/usr.bin/iscsictl/iscsictl.8 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.bin/iscsictl/iscsictl.8 2013-07-23 23:47:58.000000000 +0200 @@ -0,0 +1,142 @@ +.\" 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 ISCSICTL 8 +.Os +.Sh NAME +.Nm iscsictl +.Nd iSCSI initiator management utility +.Sh SYNOPSIS +.Nm +.Fl A +.Fl h Ar host Fl t Ar target Op Fl u Ar user Fl s Ar secret +.Nm +.Fl A +.Fl d Ar discovery-host Op Fl u Ar user Fl s Ar secret +.Nm +.Fl A +.Fl a Op Fl c Ar path +.Nm +.Fl A +.Fl n Ar nickname Op Fl c Ar path +.Nm +.Fl R +.Op Fl h Ar host +.Op Fl t Ar target +.Nm +.Fl R +.Fl a +.Nm +.Fl R +.Fl n Ar nickname Op Fl c Ar path +.Nm +.Fl L +.Op Fl v +.Sh DESCRIPTION +The +.Nm +utility is used to configure the iSCSI initiator. +.Pp +The following options are available: +.Bl -tag -width ".Fl A" +.It Fl A +Add session. +.It Fl R +Remove session. +.It Fl L +List sessions. +.It Fl a +When adding, add all sessions defined in the configuration file. +When removing, remove all currently established sessions. +.It Fl c +Path to the configuration file. +The default is +.Pa /etc/iscsi.conf . +.It Fl d +Target host name or address used for SendTargets discovery. +When used, it will add a temporary discovery session. +After discovery is done, sessions will be added for each discovered target, +and the temporary discovery sesion will be removed. +.It Fl h +Target host name or address for statically defined targets. +.It Fl n +The "nickname" of session defined in the configuration file. +.It Fl t +Target name. +.It Fl v +Verbose mode. +.El +.Pp +Since connecting to the target is performed in background, non-zero +exit status does not mean that the session was successfully established. +Use +.Nm Fl L +to check the connection status. +The initiator notifies +.Xr devd 8 +when session gets connected or disconnected. +.Pp +Note that +.Fx +currently supports two different initiators: the old one, +.Xr iscsi_initiator 4 , +with its control utility +.Xr iscontrol 8 , +and the new one, +.Xr iscsi 4 , +with +.Xr iscsictl 8 +and +.Xr iscsid 8 . +The only thing the two have in common is the configuration file, +.Xr iscsi.conf 5 . +.Sh FILES +.Bl -tag -width ".Pa /etc/iscsi.conf" -compact +.It Pa /etc/iscsi.conf +iSCSI initiator configuration file. +.El +.Sh EXIT STATUS +The +.Nm +utility exits 0 on success, and >0 if an error occurs. +.Sh SEE ALSO +.Xr iscsid 8 +.Xr iscsi.conf 5 +.Sh HISTORY +The +.Nm +command appeared in +.Fx 10.0 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff -urNp p4/freebsd/src/usr.bin/iscsictl/iscsictl.c p4/iscsi/usr.bin/iscsictl/iscsictl.c --- p4/freebsd/src/usr.bin/iscsictl/iscsictl.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.bin/iscsictl/iscsictl.c 2013-08-18 13:07:26.000000000 +0200 @@ -0,0 +1,708 @@ +/*- + * 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 "iscsictl.h" + +struct conf * +conf_new(void) +{ + struct conf *conf; + + conf = calloc(1, sizeof(*conf)); + if (conf == NULL) + err(1, "calloc"); + + TAILQ_INIT(&conf->conf_targets); + + return (conf); +} + +struct target * +target_find(struct conf *conf, const char *nickname) +{ + struct target *targ; + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + if (targ->t_nickname != NULL && + strcasecmp(targ->t_nickname, nickname) == 0) + return (targ); + } + + return (NULL); +} + +struct target * +target_new(struct conf *conf) +{ + struct target *targ; + + targ = calloc(1, sizeof(*targ)); + if (targ == NULL) + err(1, "calloc"); + targ->t_conf = conf; + TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next); + + return (targ); +} + +void +target_delete(struct target *targ) +{ + + TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next); + free(targ); +} + + +static char * +default_initiator_name(void) +{ + char *name; + size_t namelen; + int error; + + namelen = _POSIX_HOST_NAME_MAX + strlen(DEFAULT_IQN); + + name = calloc(1, namelen + 1); + if (name == NULL) + err(1, "calloc"); + strcpy(name, DEFAULT_IQN); + error = gethostname(name + strlen(DEFAULT_IQN), + namelen - strlen(DEFAULT_IQN)); + if (error != 0) + err(1, "gethostname"); + + return (name); +} + +static bool +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 +valid_iscsi_name(const char *name) +{ + int i; + + if (strlen(name) >= MAX_NAME_LEN) { + warnx("overlong name for \"%s\"; max length allowed " + "by iSCSI specification is %d characters", + name, 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; + warnx("invalid character \"%c\" in iSCSI 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) + warnx("invalid iSCSI name \"%s\"; the \"eui.\" " + "should be followed by exactly 16 hexadecimal " + "digits", name); + for (i = strlen("eui."); name[i] != '\0'; i++) { + if (!valid_hex(name[i])) { + warnx("invalid character \"%c\" in iSCSI " + "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) + warnx("invalid iSCSI name \"%s\"; the \"naa.\" " + "should be followed by at most 32 hexadecimal " + "digits", name); + for (i = strlen("naa."); name[i] != '\0'; i++) { + if (!valid_hex(name[i])) { + warnx("invalid character \"%c\" in ISCSI " + "name \"%s\"; allowed characters are 1-9 " + "and A-F", name[i], name); + break; + } + } + } else { + warnx("invalid iSCSI name \"%s\"; should start with " + "either \".iqn\", \"eui.\", or \"naa.\"", + name); + } + return (true); +} + +void +conf_verify(struct conf *conf) +{ + struct target *targ; + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + assert(targ->t_nickname != NULL); + if (targ->t_session_type == SESSION_TYPE_UNSPECIFIED) + targ->t_session_type = SESSION_TYPE_NORMAL; + if (targ->t_session_type == SESSION_TYPE_NORMAL && + targ->t_name == NULL) + errx(1, "missing TargetName for target \"%s\"", + targ->t_nickname); + if (targ->t_session_type == SESSION_TYPE_DISCOVERY && + targ->t_name != NULL) + errx(1, "cannot specify TargetName for discovery " + "sessions for target \"%s\"", targ->t_nickname); + if (targ->t_name != NULL) { + if (valid_iscsi_name(targ->t_name) == false) + errx(1, "invalid target name \"%s\"", + targ->t_name); + } + if (targ->t_protocol == PROTOCOL_UNSPECIFIED) + targ->t_protocol = PROTOCOL_ISCSI; +#ifndef ICL_KERNEL_PROXY + if (targ->t_protocol == PROTOCOL_ISER) + errx(1, "iSER support requires ICL_KERNEL_PROXY; " + "see iscsi(4) for details"); +#endif + if (targ->t_address == NULL) + errx(1, "missing TargetAddress for target \"%s\"", + targ->t_nickname); + if (targ->t_initiator_name == NULL) + targ->t_initiator_name = default_initiator_name(); + if (valid_iscsi_name(targ->t_initiator_name) == false) + errx(1, "invalid initiator name \"%s\"", + targ->t_initiator_name); + if (targ->t_header_digest == DIGEST_UNSPECIFIED) + targ->t_header_digest = DIGEST_NONE; + if (targ->t_data_digest == DIGEST_UNSPECIFIED) + targ->t_data_digest = DIGEST_NONE; + if (targ->t_auth_method == AUTH_METHOD_UNSPECIFIED) { + if (targ->t_user != NULL || targ->t_secret != NULL || + targ->t_mutual_user != NULL || + targ->t_mutual_secret != NULL) + targ->t_auth_method = + AUTH_METHOD_CHAP; + else + targ->t_auth_method = + AUTH_METHOD_NONE; + } + if (targ->t_auth_method == AUTH_METHOD_CHAP) { + if (targ->t_user == NULL) { + errx(1, "missing chapIName for target \"%s\"", + targ->t_nickname); + } + if (targ->t_secret == NULL) + errx(1, "missing chapSecret for target \"%s\"", + targ->t_nickname); + if (targ->t_mutual_user != NULL || + targ->t_mutual_secret != NULL) { + if (targ->t_mutual_user == NULL) + errx(1, "missing tgtChapName for " + "target \"%s\"", targ->t_nickname); + if (targ->t_mutual_secret == NULL) + errx(1, "missing tgtChapSecret for " + "target \"%s\"", targ->t_nickname); + } + } + } +} + +static void +desc_from_target(struct iscsi_session_desc *desc, + const struct target *targ) +{ + memset(desc, 0, sizeof(*desc)); + + /* + * XXX: Check bounds and return error instead of silently truncating. + */ + if (targ->t_initiator_name != NULL) + strlcpy(desc->isd_initiator, targ->t_initiator_name, + sizeof(desc->isd_initiator)); + if (targ->t_initiator_address != NULL) + strlcpy(desc->isd_initiator_addr, targ->t_initiator_address, + sizeof(desc->isd_initiator_addr)); + if (targ->t_initiator_alias != NULL) + strlcpy(desc->isd_initiator_alias, targ->t_initiator_alias, + sizeof(desc->isd_initiator_alias)); + if (targ->t_name != NULL) + strlcpy(desc->isd_target, targ->t_name, + sizeof(desc->isd_target)); + if (targ->t_address != NULL) + strlcpy(desc->isd_target_addr, targ->t_address, + sizeof(desc->isd_target_addr)); + if (targ->t_user != NULL) + strlcpy(desc->isd_user, targ->t_user, + sizeof(desc->isd_user)); + if (targ->t_secret != NULL) + strlcpy(desc->isd_secret, targ->t_secret, + sizeof(desc->isd_secret)); + if (targ->t_mutual_user != NULL) + strlcpy(desc->isd_mutual_user, targ->t_mutual_user, + sizeof(desc->isd_mutual_user)); + if (targ->t_mutual_secret != NULL) + strlcpy(desc->isd_mutual_secret, targ->t_mutual_secret, + sizeof(desc->isd_mutual_secret)); + if (targ->t_session_type == SESSION_TYPE_DISCOVERY) + desc->isd_discovery = 1; + if (targ->t_protocol == PROTOCOL_ISER) + desc->isd_iser = 1; + if (targ->t_header_digest == DIGEST_CRC32C) + desc->isd_header_digest = 1; + if (targ->t_data_digest == DIGEST_CRC32C) + desc->isd_data_digest = 1; +} + +static int +kernel_add(int iscsi_fd, const struct target *targ) +{ + struct iscsi_session_add isa; + int error; + + desc_from_target(&isa.isa_desc, targ); + error = ioctl(iscsi_fd, ISCSISADD, &isa); + if (error != 0) + warn("ISCSISADD"); + return (error); +} + +static int +kernel_remove(int iscsi_fd, const struct target *targ) +{ + struct iscsi_session_remove isr; + int error; + + desc_from_target(&isr.isr_desc, targ); + error = ioctl(iscsi_fd, ISCSISREMOVE, &isr); + if (error != 0) + warn("ISCSISREMOVE"); + return (error); +} + +/* + * XXX: Add filtering. + */ +static int +kernel_list(int iscsi_fd, const struct target *targ __unused, + int verbose) +{ + struct iscsi_session_desc *descs = NULL; + struct iscsi_session_list isl; + const char *state; + unsigned int i, nentries = 1; + int error; + + for (;;) { + descs = realloc(descs, + nentries * sizeof(struct iscsi_session_desc)); + if (descs == NULL) + err(1, "realloc"); + + isl.isl_nentries = nentries; + isl.isl_pdesc = descs; + + error = ioctl(iscsi_fd, ISCSISLIST, &isl); + if (error != 0 && errno == EMSGSIZE) { + nentries *= 4; + continue; + } + break; + } + if (error != 0) { + warn("ISCSISLIST"); + return (error); + } + + if (verbose != 0) { + for (i = 0; i < isl.isl_nentries; i++) { + printf("Session ID: %d\n", descs[i].isd_id); + printf("Session type: %s\n", + descs[i].isd_discovery ? "Discovery" : "Normal"); + printf("Session state: %s\n", + descs[i].isd_connected ? + "Connected" : "Disconnected"); + printf("Initiator name: %s\n", descs[i].isd_initiator); + printf("Initiator addr: %s\n", + descs[i].isd_initiator_addr); + printf("Initiator alias: %s\n", + descs[i].isd_initiator_alias); + printf("Target name: %s\n", descs[i].isd_target); + printf("Target addr: %s\n", + descs[i].isd_target_addr); + printf("Target alias: %s\n", + descs[i].isd_target_alias); + printf("User: %s\n", descs[i].isd_user); + printf("Secret: %s\n", descs[i].isd_secret); + printf("Mutual user: %s\n", + descs[i].isd_mutual_user); + printf("Mutual secret: %s\n", + descs[i].isd_mutual_secret); + printf("Header digest: %s\n", + descs[i].isd_header_digest ? "CRC32C" : "None"); + printf("Data digest: %s\n", + descs[i].isd_data_digest ? "CRC32C" : "None"); + printf("DataSegmentLen: %d\n", + descs[i].isd_max_data_segment_length); + printf("ImmediateData: %s\n", + descs[i].isd_immediate_data ? "Yes" : "No"); + printf("iSER (RDMA): %s\n", + descs[i].isd_iser ? "Yes" : "No"); + printf("Devices: "); + print_periphs(descs[i].isd_id); + printf("\n\n"); + } + } else { + printf("%-36s %-16s %-16s %s\n", + "Target name", "Target addr", "State", "Devices"); + for (i = 0; i < isl.isl_nentries; i++) { + if (descs[i].isd_discovery) + state = "Discovery"; + else if (descs[i].isd_connected) + state = "Connected"; + else + state = "Disconnected"; + printf("%-36s %-16s %-16s ", + descs[i].isd_target, descs[i].isd_target_addr, + state); + print_periphs(descs[i].isd_id); + printf("\n"); + } + } + + return (0); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: iscsictl -A -h host -t target " + "[-u user -s secret]\n"); + fprintf(stderr, " iscsictl -A -d discovery-host " + "[-u user -s secret]\n"); + fprintf(stderr, " iscsictl -A -a [-c path]\n"); + fprintf(stderr, " iscsictl -A -n nickname [-c path]\n"); + fprintf(stderr, " iscsictl -R [-h host] [-t target]\n"); + fprintf(stderr, " iscsictl -R -a\n"); + fprintf(stderr, " iscsictl -R -n nickname [-c path]\n"); + fprintf(stderr, " iscsictl -L [-v]\n"); + exit(1); +} + +char * +checked_strdup(const char *s) +{ + char *c; + + c = strdup(s); + if (c == NULL) + err(1, "strdup"); + return (c); +} + +int +main(int argc, char **argv) +{ + int Aflag = 0, Rflag = 0, Lflag = 0, aflag = 0, vflag = 0; + const char *conf_path = DEFAULT_CONFIG_PATH; + char *nickname = NULL, *discovery_host = NULL, *host = NULL, + *target = NULL, *user = NULL, *secret = NULL; + int ch, error, iscsi_fd; + int failed = 0; + struct conf *conf; + struct target *targ; + + while ((ch = getopt(argc, argv, "ARLac:d:n:h:t:u:s:v")) != -1) { + switch (ch) { + case 'A': + Aflag = 1; + break; + case 'R': + Rflag = 1; + break; + case 'L': + Lflag = 1; + break; + case 'a': + aflag = 1; + break; + case 'c': + conf_path = optarg; + break; + case 'd': + discovery_host = optarg; + break; + case 'n': + nickname = optarg; + break; + case 'h': + host = optarg; + break; + case 't': + target = optarg; + break; + case 'u': + user = optarg; + break; + case 's': + secret = optarg; + break; + case 'v': + vflag = 1; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + if (argc != 0) + usage(); + + if (Aflag + Rflag + Lflag == 0) + Lflag = 1; + if (Aflag + Rflag + Lflag > 1) + errx(1, "at most one of -A, -R, or -L may be specified"); + + /* + * Note that we ignore unneccessary/inapplicable "-c" flag; so that + * people can do something like "alias ISCSICTL="iscsictl -c path" + * in shell scripts. + */ + if (Aflag != 0) { + if (aflag != 0) { + if (host != NULL) + errx(1, "-a and -h and mutually exclusive"); + if (target != NULL) + errx(1, "-a and -t and mutually exclusive"); + if (user != NULL) + errx(1, "-a and -u and mutually exclusive"); + if (secret != NULL) + errx(1, "-a and -s and mutually exclusive"); + if (nickname != NULL) + errx(1, "-a and -n and mutually exclusive"); + if (discovery_host != NULL) + errx(1, "-a and -d and mutually exclusive"); + } else if (nickname != NULL) { + if (host != NULL) + errx(1, "-n and -h and mutually exclusive"); + if (target != NULL) + errx(1, "-n and -t and mutually exclusive"); + if (user != NULL) + errx(1, "-n and -u and mutually exclusive"); + if (secret != NULL) + errx(1, "-n and -s and mutually exclusive"); + if (discovery_host != NULL) + errx(1, "-n and -d and mutually exclusive"); + } else if (discovery_host != NULL) { + if (host != NULL) + errx(1, "-d and -h and mutually exclusive"); + if (target != NULL) + errx(1, "-d and -t and mutually exclusive"); + } else { + if (target == NULL && host == NULL) + errx(1, "must specify -a, -n or -t/-h"); + + if (target != NULL && host == NULL) + errx(1, "-t must always be used with -h"); + if (host != NULL && target == NULL) + errx(1, "-h must always be used with -t"); + } + + if (user != NULL && secret == NULL) + errx(1, "-u must always be used with -s"); + if (secret != NULL && user == NULL) + errx(1, "-s must always be used with -u"); + + if (vflag != 0) + errx(1, "-v cannot be used with -A"); + + } else if (Rflag != 0) { + if (user != NULL) + errx(1, "-R and -u are mutually exclusive"); + if (secret != NULL) + errx(1, "-R and -s are mutually exclusive"); + if (discovery_host != NULL) + errx(1, "-R and -d are mutually exclusive"); + + if (aflag != 0) { + if (host != NULL) + errx(1, "-a and -h and mutually exclusive"); + if (target != NULL) + errx(1, "-a and -t and mutually exclusive"); + if (nickname != NULL) + errx(1, "-a and -n and mutually exclusive"); + } else if (nickname != NULL) { + if (host != NULL) + errx(1, "-n and -h and mutually exclusive"); + if (target != NULL) + errx(1, "-n and -t and mutually exclusive"); + } else if (host != NULL) { + if (target != NULL) + errx(1, "-h and -t and mutually exclusive"); + } else if (target != NULL) { + if (host != NULL) + errx(1, "-t and -h and mutually exclusive"); + } else + errx(1, "must specify either-a, -n, -t, or -h"); + + if (vflag != 0) + errx(1, "-v cannot be used with -R"); + + } else { + assert(Lflag != 0); + + if (host != NULL) + errx(1, "-L and -h and mutually exclusive"); + if (target != NULL) + errx(1, "-L and -t and mutually exclusive"); + if (user != NULL) + errx(1, "-L and -u and mutually exclusive"); + if (secret != NULL) + errx(1, "-L and -s and mutually exclusive"); + if (nickname != NULL) + errx(1, "-L and -n and mutually exclusive"); + if (discovery_host != NULL) + errx(1, "-L and -d and mutually exclusive"); + } + + iscsi_fd = open(ISCSI_PATH, O_RDWR); + if (iscsi_fd < 0) + err(1, "failed to open %s", ISCSI_PATH); + + if (Aflag != 0 && aflag != 0) { + conf = conf_new_from_file(conf_path); + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) + failed += kernel_add(iscsi_fd, targ); + } else if (nickname != NULL) { + conf = conf_new_from_file(conf_path); + targ = target_find(conf, nickname); + if (targ == NULL) + errx(1, "target %s not found in the configuration file", + nickname); + + if (Aflag != 0) + failed += kernel_add(iscsi_fd, targ); + else if (Rflag != 0) + failed += kernel_remove(iscsi_fd, targ); + else + failed += kernel_list(iscsi_fd, targ, vflag); + } else { + if (Aflag != 0 && target != NULL) { + if (valid_iscsi_name(target) == false) + errx(1, "invalid target name \"%s\"", target); + } + conf = conf_new(); + targ = target_new(conf); + targ->t_initiator_name = default_initiator_name(); + targ->t_header_digest = DIGEST_NONE; + targ->t_data_digest = DIGEST_NONE; + targ->t_name = target; + if (discovery_host != NULL) { + targ->t_session_type = SESSION_TYPE_DISCOVERY; + targ->t_address = discovery_host; + } else { + targ->t_session_type = SESSION_TYPE_NORMAL; + targ->t_address = host; + } + targ->t_user = user; + targ->t_secret = secret; + + if (Aflag != 0) + failed += kernel_add(iscsi_fd, targ); + else if (Rflag != 0) + failed += kernel_remove(iscsi_fd, targ); + else + failed += kernel_list(iscsi_fd, targ, vflag); + } + + error = close(iscsi_fd); + if (error != 0) + err(1, "close"); + + if (failed > 0) + return (1); + return (0); +} diff -urNp p4/freebsd/src/usr.bin/iscsictl/iscsictl.h p4/iscsi/usr.bin/iscsictl/iscsictl.h --- p4/freebsd/src/usr.bin/iscsictl/iscsictl.h 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.bin/iscsictl/iscsictl.h 2013-07-24 12:25:00.000000000 +0200 @@ -0,0 +1,116 @@ +/*- + * 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 ISCSICTL_H +#define ISCSICTL_H + +#include +#include +#include + +#define DEFAULT_CONFIG_PATH "/etc/iscsi.conf" +#define DEFAULT_IQN "iqn.1994-09.org.freebsd:" + +#define MAX_NAME_LEN 223 +#define MAX_DATA_SEGMENT_LENGTH 65536 + +#define AUTH_METHOD_UNSPECIFIED 0 +#define AUTH_METHOD_NONE 1 +#define AUTH_METHOD_CHAP 2 + +#define DIGEST_UNSPECIFIED 0 +#define DIGEST_NONE 1 +#define DIGEST_CRC32C 2 + +#define SESSION_TYPE_UNSPECIFIED 0 +#define SESSION_TYPE_NORMAL 1 +#define SESSION_TYPE_DISCOVERY 2 + +#define PROTOCOL_UNSPECIFIED 0 +#define PROTOCOL_ISCSI 1 +#define PROTOCOL_ISER 2 + +struct target { + TAILQ_ENTRY(target) t_next; + struct conf *t_conf; + char *t_nickname; + char *t_name; + char *t_address; + char *t_initiator_name; + char *t_initiator_address; + char *t_initiator_alias; + int t_header_digest; + int t_data_digest; + int t_auth_method; + int t_session_type; + int t_protocol; + char *t_user; + char *t_secret; + char *t_mutual_user; + char *t_mutual_secret; +}; + +struct conf { + TAILQ_HEAD(, target) conf_targets; +}; + +#define CONN_SESSION_TYPE_NONE 0 +#define CONN_SESSION_TYPE_DISCOVERY 1 +#define CONN_SESSION_TYPE_NORMAL 2 + +struct connection { + struct target *conn_target; + int conn_socket; + int conn_session_type; + uint32_t conn_cmdsn; + uint32_t conn_statsn; + size_t conn_max_data_segment_length; + size_t conn_max_burst_length; + size_t conn_max_outstanding_r2t; + int conn_header_digest; + int conn_data_digest; +}; + +struct conf *conf_new(void); +struct conf *conf_new_from_file(const char *path); +void conf_delete(struct conf *conf); +void conf_verify(struct conf *conf); + +struct target *target_new(struct conf *conf); +struct target *target_find(struct conf *conf, const char *nickname); +void target_delete(struct target *ic); + +void print_periphs(int session_id); + +char *checked_strdup(const char *); +bool valid_iscsi_name(const char *name); + +#endif /* !ISCSICTL_H */ diff -urNp p4/freebsd/src/usr.bin/iscsictl/parse.y p4/iscsi/usr.bin/iscsictl/parse.y --- p4/freebsd/src/usr.bin/iscsictl/parse.y 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.bin/iscsictl/parse.y 2013-07-24 12:25:00.000000000 +0200 @@ -0,0 +1,333 @@ +%{ +/*- + * 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 "iscsictl.h" + +extern FILE *yyin; +extern char *yytext; +extern int lineno; + +static struct conf *conf; +static struct target *target; + +extern void yyerror(const char *); +extern int yylex(void); +extern void yyrestart(FILE *); + +%} + +%token AUTH_METHOD HEADER_DIGEST DATA_DIGEST TARGET_NAME TARGET_ADDRESS +%token INITIATOR_NAME INITIATOR_ADDRESS INITIATOR_ALIAS USER SECRET +%token MUTUAL_USER MUTUAL_SECRET SESSION_TYPE PROTOCOL IGNORED +%token EQUALS OPENING_BRACKET CLOSING_BRACKET + +%union +{ + char *str; +} + +%token STR + +%% + +statements: + | + statements target_statement + ; + +target_statement: STR OPENING_BRACKET target_entries CLOSING_BRACKET + { + if (target_find(conf, $1) != NULL) + errx(1, "duplicated target %s", $1); + target->t_nickname = $1; + target = target_new(conf); + } + ; + +target_entries: + | + target_entries target_entry + ; + +target_entry: + target_name_statement + | + target_address_statement + | + initiator_name_statement + | + initiator_address_statement + | + initiator_alias_statement + | + user_statement + | + secret_statement + | + mutual_user_statement + | + mutual_secret_statement + | + auth_method_statement + | + header_digest_statement + | + data_digest_statement + | + session_type_statement + | + protocol_statement + | + ignored_statement + ; + +target_name_statement: TARGET_NAME EQUALS STR + { + if (target->t_name != NULL) + errx(1, "duplicated TargetName at line %d", lineno + 1); + target->t_name = $3; + } + ; + +target_address_statement: TARGET_ADDRESS EQUALS STR + { + if (target->t_address != NULL) + errx(1, "duplicated TargetAddress at line %d", lineno + 1); + target->t_address = $3; + } + ; + +initiator_name_statement: INITIATOR_NAME EQUALS STR + { + if (target->t_initiator_name != NULL) + errx(1, "duplicated InitiatorName at line %d", lineno + 1); + target->t_initiator_name = $3; + } + ; + +initiator_address_statement: INITIATOR_ADDRESS EQUALS STR + { + if (target->t_initiator_address != NULL) + errx(1, "duplicated InitiatorAddress at line %d", lineno + 1); + target->t_initiator_address = $3; + } + ; + +initiator_alias_statement: INITIATOR_ALIAS EQUALS STR + { + if (target->t_initiator_alias != NULL) + errx(1, "duplicated InitiatorAlias at line %d", lineno + 1); + target->t_initiator_alias = $3; + } + ; + +user_statement: USER EQUALS STR + { + if (target->t_user != NULL) + errx(1, "duplicated chapIName at line %d", lineno + 1); + target->t_user = $3; + } + ; + +secret_statement: SECRET EQUALS STR + { + if (target->t_secret != NULL) + errx(1, "duplicated chapSecret at line %d", lineno + 1); + target->t_secret = $3; + } + ; + +mutual_user_statement: MUTUAL_USER EQUALS STR + { + if (target->t_mutual_user != NULL) + errx(1, "duplicated tgtChapName at line %d", lineno + 1); + target->t_mutual_user = $3; + } + ; + +mutual_secret_statement:MUTUAL_SECRET EQUALS STR + { + if (target->t_mutual_secret != NULL) + errx(1, "duplicated tgtChapSecret at line %d", lineno + 1); + target->t_mutual_secret = $3; + } + ; + +auth_method_statement: AUTH_METHOD EQUALS STR + { + if (target->t_auth_method != AUTH_METHOD_UNSPECIFIED) + errx(1, "duplicated AuthMethod at line %d", lineno + 1); + if (strcasecmp($3, "none") == 0) + target->t_auth_method = AUTH_METHOD_NONE; + else if (strcasecmp($3, "chap") == 0) + target->t_auth_method = AUTH_METHOD_CHAP; + else + errx(1, "invalid AuthMethod at line %d; " + "must be either \"none\" or \"CHAP\"", lineno + 1); + } + ; + +header_digest_statement: HEADER_DIGEST EQUALS STR + { + if (target->t_header_digest != DIGEST_UNSPECIFIED) + errx(1, "duplicated HeaderDigest at line %d", lineno + 1); + if (strcasecmp($3, "none") == 0) + target->t_header_digest = DIGEST_NONE; + else if (strcasecmp($3, "CRC32C") == 0) + target->t_header_digest = DIGEST_CRC32C; + else + errx(1, "invalid HeaderDigest at line %d; " + "must be either \"none\" or \"CRC32C\"", lineno + 1); + } + ; + +data_digest_statement: DATA_DIGEST EQUALS STR + { + if (target->t_data_digest != DIGEST_UNSPECIFIED) + errx(1, "duplicated DataDigest at line %d", lineno + 1); + if (strcasecmp($3, "none") == 0) + target->t_data_digest = DIGEST_NONE; + else if (strcasecmp($3, "CRC32C") == 0) + target->t_data_digest = DIGEST_CRC32C; + else + errx(1, "invalid DataDigest at line %d; " + "must be either \"none\" or \"CRC32C\"", lineno + 1); + } + ; + +session_type_statement: SESSION_TYPE EQUALS STR + { + if (target->t_session_type != SESSION_TYPE_UNSPECIFIED) + errx(1, "duplicated SessionType at line %d", lineno + 1); + if (strcasecmp($3, "normal") == 0) + target->t_session_type = SESSION_TYPE_NORMAL; + else if (strcasecmp($3, "discovery") == 0) + target->t_session_type = SESSION_TYPE_DISCOVERY; + else + errx(1, "invalid SessionType at line %d; " + "must be either \"normal\" or \"discovery\"", lineno + 1); + } + ; + +protocol_statement: PROTOCOL EQUALS STR + { + if (target->t_protocol != PROTOCOL_UNSPECIFIED) + errx(1, "duplicated protocol at line %d", lineno + 1); + if (strcasecmp($3, "iscsi") == 0) + target->t_protocol = PROTOCOL_ISCSI; + else if (strcasecmp($3, "iser") == 0) + target->t_protocol = PROTOCOL_ISER; + else + errx(1, "invalid protocol at line %d; " + "must be either \"iscsi\" or \"iser\"", lineno + 1); + } + ; + +ignored_statement: IGNORED EQUALS STR + { + warnx("obsolete statement ignored at line %d", lineno + 1); + } + ; + +%% + +void +yyerror(const char *str) +{ + + errx(1, "error in configuration file at line %d near '%s': %s", + lineno + 1, yytext, str); +} + +static void +check_perms(const char *path) +{ + struct stat sb; + int error; + + error = stat(path, &sb); + if (error != 0) { + warn("stat"); + return; + } + if (sb.st_mode & S_IWOTH) { + warnx("%s is world-writable", path); + } else if (sb.st_mode & S_IROTH) { + 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. + */ + warnx("%s is world-executable", path); + } + + /* + * XXX: Should we also check for owner != 0? + */ +} + +struct conf * +conf_new_from_file(const char *path) +{ + int error; + + conf = conf_new(); + target = target_new(conf); + + yyin = fopen(path, "r"); + if (yyin == NULL) + err(1, "unable to open configuration file %s", path); + check_perms(path); + lineno = 0; + yyrestart(yyin); + error = yyparse(); + assert(error == 0); + fclose(yyin); + + assert(target->t_nickname == NULL); + target_delete(target); + + conf_verify(conf); + + return (conf); +} diff -urNp p4/freebsd/src/usr.bin/iscsictl/periphs.c p4/iscsi/usr.bin/iscsictl/periphs.c --- p4/freebsd/src/usr.bin/iscsictl/periphs.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.bin/iscsictl/periphs.c 2013-07-23 23:47:58.000000000 +0200 @@ -0,0 +1,186 @@ +/* + * 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. + * 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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. + */ + +#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 "iscsictl.h" + +void +print_periphs(int session_id) +{ + union ccb ccb; + int bufsize, fd; + unsigned int i; + int skip_bus, skip_device; + + if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) { + warn("couldn't open %s", XPT_DEVICE); + return; + } + + /* + * First, iterate over the whole list to find the bus. + */ + + bzero(&ccb, sizeof(union ccb)); + + ccb.ccb_h.path_id = CAM_XPT_PATH_ID; + ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; + ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; + + ccb.ccb_h.func_code = XPT_DEV_MATCH; + bufsize = sizeof(struct dev_match_result) * 100; + ccb.cdm.match_buf_len = bufsize; + ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize); + if (ccb.cdm.matches == NULL) { + warnx("can't malloc memory for matches"); + close(fd); + return; + } + ccb.cdm.num_matches = 0; + + /* + * We fetch all nodes, since we display most of them in the default + * case, and all in the verbose case. + */ + ccb.cdm.num_patterns = 0; + ccb.cdm.pattern_buf_len = 0; + + /* + * We do the ioctl multiple times if necessary, in case there are + * more than 100 nodes in the EDT. + */ + do { + if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { + warn("error sending CAMIOCOMMAND ioctl"); + break; + } + + if ((ccb.ccb_h.status != CAM_REQ_CMP) + || ((ccb.cdm.status != CAM_DEV_MATCH_LAST) + && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) { + warnx("got CAM error %#x, CDM error %d\n", + ccb.ccb_h.status, ccb.cdm.status); + break; + } + + skip_bus = 1; + skip_device = 1; + + for (i = 0; i < ccb.cdm.num_matches; i++) { + switch (ccb.cdm.matches[i].type) { + case DEV_MATCH_BUS: { + struct bus_match_result *bus_result; + + bus_result = &ccb.cdm.matches[i].result.bus_result; + + skip_bus = 1; + + if (strcmp(bus_result->dev_name, "iscsi") != 0) { + //printf("not iscsi\n"); + continue; + } + + if ((int)bus_result->unit_number != session_id) { + //printf("wrong unit, %d != %d\n", bus_result->unit_number, session_id); + continue; + } + + skip_bus = 0; + } + case DEV_MATCH_DEVICE: { + skip_device = 1; + + if (skip_bus != 0) + continue; + + skip_device = 0; + + break; + } + case DEV_MATCH_PERIPH: { + struct periph_match_result *periph_result; + + periph_result = + &ccb.cdm.matches[i].result.periph_result; + + if (skip_device != 0) + continue; + + if (strcmp(periph_result->periph_name, "pass") == 0) + continue; + + fprintf(stdout, "%s%d ", + periph_result->periph_name, + periph_result->unit_number); + + break; + } + default: + fprintf(stdout, "unknown match type\n"); + break; + } + } + + } while ((ccb.ccb_h.status == CAM_REQ_CMP) + && (ccb.cdm.status == CAM_DEV_MATCH_MORE)); + + close(fd); +} + diff -urNp p4/freebsd/src/usr.bin/iscsictl/token.l p4/iscsi/usr.bin/iscsictl/token.l --- p4/freebsd/src/usr.bin/iscsictl/token.l 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.bin/iscsictl/token.l 2013-07-24 12:25:00.000000000 +0200 @@ -0,0 +1,93 @@ +%{ +/*- + * 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 "iscsictl.h" +#include "y.tab.h" + +int lineno; + +#define YY_DECL int yylex(void) +extern int yylex(void); + +%} + +%option noinput +%option nounput + +%% +HeaderDigest { return HEADER_DIGEST; } +DataDigest { return DATA_DIGEST; } +TargetName { return TARGET_NAME; } +TargetAddress { return TARGET_ADDRESS; } +InitiatorName { return INITIATOR_NAME; } +InitiatorAddress { return INITIATOR_ADDRESS; } +InitiatorAlias { return INITIATOR_ALIAS; } +chapIName { return USER; } +chapSecret { return SECRET; } +tgtChapName { return MUTUAL_USER; } +tgtChapSecret { return MUTUAL_SECRET; } +AuthMethod { return AUTH_METHOD; } +SessionType { return SESSION_TYPE; } +protocol { return PROTOCOL; } +port { return IGNORED; } +MaxConnections { return IGNORED; } +TargetAlias { return IGNORED; } +TargetPortalGroupTag { return IGNORED; } +InitialR2T { return IGNORED; } +ImmediateData { return IGNORED; } +MaxRecvDataSegmentLength { return IGNORED; } +MaxBurstLength { return IGNORED; } +FirstBurstLength { return IGNORED; } +DefaultTime2Wait { return IGNORED; } +DefaultTime2Retain { return IGNORED; } +MaxOutstandingR2T { return IGNORED; } +DataPDUInOrder { return IGNORED; } +DataSequenceInOrder { return IGNORED; } +ErrorRecoveryLevel { return IGNORED; } +tags { return IGNORED; } +maxluns { return IGNORED; } +sockbufsize { return IGNORED; } +chapDigest { return IGNORED; } +\"[^"]+\" { yylval.str = strndup(yytext + 1, + strlen(yytext) - 2); return STR; } +[a-zA-Z0-9\.\-_/\:\[\]]+ { yylval.str = strdup(yytext); return STR; } +\{ { return OPENING_BRACKET; } +\} { return CLOSING_BRACKET; } += { return EQUALS; } +#.*$ /* ignore comments */; +\n { lineno++; } +[ \t]+ /* ignore whitespace */; +%% diff -urNp p4/freebsd/src/usr.sbin/Makefile p4/iscsi/usr.sbin/Makefile --- p4/freebsd/src/usr.sbin/Makefile 2013-08-22 23:16:51.000000000 +0200 +++ p4/iscsi/usr.sbin/Makefile 2013-08-23 00:09:57.000000000 +0200 @@ -17,6 +17,7 @@ SUBDIR= adduser \ crashinfo \ cron \ ctladm \ + ctld \ daemon \ dconschat \ devinfo \ @@ -35,6 +36,7 @@ SUBDIR= adduser \ ifmcstat \ inetd \ iostat \ + iscsid \ isfctl \ kldxref \ mailwrapper \ diff -urNp p4/freebsd/src/usr.sbin/ctladm/ctladm.8 p4/iscsi/usr.sbin/ctladm/ctladm.8 --- p4/freebsd/src/usr.sbin/ctladm/ctladm.8 2013-04-20 20:21:50.000000000 +0200 +++ p4/iscsi/usr.sbin/ctladm/ctladm.8 2013-07-23 23:48:06.000000000 +0200 @@ -197,6 +197,16 @@ .Nm .Ic dumpstructs .Nm +.Ic islist +.Op Fl v +.Op Fl x +.Nm +.Ic islogout +.Aq Fl a | Fl h Ar host | Fl c Ar connection-id | Fl i Ar name +.Nm +.Ic isterminate +.Aq Fl a | Fl h Ar host | Fl c Ar connection-id | Fl i Ar name +.Nm .Ic help .Sh DESCRIPTION The @@ -883,6 +893,41 @@ 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 v +Verbose mode. +.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 h +Specify initiator IP address. +.It Fl c +Specify connection ID. +.It Fl i +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 h +Specify initiator IP address. +.It Fl c +Specify connection ID. +.It Fl i +Specify initiator name. +.El .It Ic help Display .Nm @@ -977,7 +1022,8 @@ This will result in a sense key of NOT R .Xr cam 4 , .Xr ctl 4 , .Xr xpt 4 , -.Xr camcontrol 8 +.Xr camcontrol 8 , +.Xr ctld 8 .Sh HISTORY The .Nm diff -urNp p4/freebsd/src/usr.sbin/ctladm/ctladm.c p4/iscsi/usr.sbin/ctladm/ctladm.c --- p4/freebsd/src/usr.sbin/ctladm/ctladm.c 2013-06-19 11:03:28.000000000 +0200 +++ p4/iscsi/usr.sbin/ctladm/ctladm.c 2013-08-18 13:07:26.000000000 +0200 @@ -117,7 +117,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 { @@ -180,6 +183,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, "vx"}, + {"islogout", CTLADM_CMD_ISLOGOUT, CTLADM_ARG_NONE, "ah:c:i:"}, + {"isterminate", CTLADM_CMD_ISTERMINATE, CTLADM_ARG_NONE, "ah:c:i:"}, {"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:"}, @@ -489,6 +495,9 @@ retry: case CTL_PORT_ISC: type = "ISC"; break; + case CTL_PORT_ISCSI: + type = "ISCSI"; + break; default: type = "UNKNOWN"; break; @@ -578,6 +587,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} }; @@ -3399,6 +3409,403 @@ bailout: return (retval); } +struct cctl_islist_conn { + int connection_id; + char *initiator; + char *initiator_addr; + char *initiator_alias; + char *target; + char *target_alias; + char *header_digest; + char *data_digest; + char *max_data_segment_length;; + int immediate_data; + int iser; + 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])); + + 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, "initiator_alias") == 0) { + cur_conn->initiator_alias = str; + str = NULL; + } else if (strcmp(name, "target") == 0) { + cur_conn->target = str; + str = NULL; + } else if (strcmp(name, "target_alias") == 0) { + cur_conn->target_alias = str; + str = NULL; + } else if (strcmp(name, "header_digest") == 0) { + cur_conn->header_digest = str; + str = NULL; + } else if (strcmp(name, "data_digest") == 0) { + cur_conn->data_digest = str; + str = NULL; + } else if (strcmp(name, "max_data_segment_length") == 0) { + cur_conn->max_data_segment_length = str; + str = NULL; + } else if (strcmp(name, "immediate_data") == 0) { + cur_conn->immediate_data = atoi(str); + } else if (strcmp(name, "iser") == 0) { + cur_conn->iser = atoi(str); + } 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 c, retval, verbose = 0; + + retval = 0; + conn_len = 4096; + + bzero(&islist, sizeof(islist)); + STAILQ_INIT(&islist.conn_list); + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'v': + verbose = 1; + break; + 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; + } + + if (verbose != 0) { + STAILQ_FOREACH(conn, &islist.conn_list, links) { + printf("Session ID: %d\n", conn->connection_id); + printf("Initiator name: %s\n", conn->initiator); + printf("Initiator addr: %s\n", conn->initiator_addr); + printf("Initiator alias: %s\n", conn->initiator_alias); + printf("Target name: %s\n", conn->target); + printf("Target alias: %s\n", conn->target_alias); + printf("Header digest: %s\n", conn->header_digest); + printf("Data digest: %s\n", conn->data_digest); + printf("DataSegmentLen: %s\n", conn->max_data_segment_length); + printf("ImmediateData: %s\n", conn->immediate_data ? "Yes" : "No"); + printf("iSER (RDMA): %s\n", conn->iser ? "Yes" : "No"); + printf("\n"); + } + } else { + printf("%4s %-16s %-36s %-36s\n", "ID", "Address", "Initiator name", + "Target name"); + 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 'h': + 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 'i': + 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, -h, -c, or -i must be specified", + __func__); + if (nargs > 1) + errx(1, "%s: only one of -a, -h, -c, or -i 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 'h': + 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 'i': + 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, -h, -c, or -i must be specified", + __func__); + if (nargs > 1) + errx(1, "%s: only one of -a, -h, -c, or -i 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. @@ -3713,6 +4120,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 [-v | -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" @@ -4093,6 +4503,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 p4/freebsd/src/usr.sbin/ctld/Makefile p4/iscsi/usr.sbin/ctld/Makefile --- p4/freebsd/src/usr.sbin/ctld/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/Makefile 2013-08-20 11:52:23.000000000 +0200 @@ -0,0 +1,21 @@ +# $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 +CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi +#CFLAGS+= -DICL_KERNEL_PROXY +MAN= ctld.8 ctl.conf.5 + +DPADD= ${LIBCAM} ${LIBSBUF} ${LIBBSDXML} ${LIBUTIL} +LDADD= -lbsdxml -lcam -lcrypto -lfl -lsbuf -lssl -lutil + +YFLAGS+= -v +CLEANFILES= y.tab.c y.tab.h y.output + +WARNS= 6 +NO_WMISSING_VARIABLE_DECLARATIONS= + +.include diff -urNp p4/freebsd/src/usr.sbin/ctld/ctl.conf.5 p4/iscsi/usr.sbin/ctld/ctl.conf.5 --- p4/freebsd/src/usr.sbin/ctld/ctl.conf.5 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/ctl.conf.5 2013-07-23 23:48:06.000000000 +0200 @@ -0,0 +1,234 @@ +.\" 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 April 24, 2013 +.Dt CTL.CONF 5 +.Os +.Sh NAME +.Nm ctl.conf +.Nd CAM Target Layer / iSCSI target daemon configuration file +.Sh DESCRIPTION +The +.Nm +configuration file is used by the +.Xr ctld 8 +daemon. +Lines starting with +.Ql # +and empty lines are interpreted as comments. +The general syntax of the +.Nm +file is: +.Bd -literal -offset indent +pidfile + +auth-group { + chap + ... +} + +portal-group { + listen
+ listen-iser
+ discovery-auth-group + ... +} + +target { + auth-group + portal-group + lun { + path + } + ... +} +.Ed +.Ss global level +The following statements are available at the global level: +.Bl -tag -width indent +.It Ic auth-group Aq Ar name +Opens an auth-group section, defining an authentication group, +which can then be assigned to any number of targets. +.It Ic debug Aq Ar level +Specifies debug level. +The default is 0. +.It Ic pidfile Aq Ar path +Specifies path to pidfile. +The default is +.Pa /var/run/ctld.pid . +.It Ic portal-group Aq Ar name +Opens a portal-group section, defining a portal group, +which can then be assigned to any number of targets. +.It Ic target Aq Ar name +Opens a target configuration section. +.El +.Ss auth-grup level +The following statements are available at the auth-group level: +.Bl -tag -width indent +.It Ic chap Ao Ar user Ac Aq Ar secret +Specifies CHAP authentication credentials. +.It Ic chap-mutual Ao Ar user Ac Ao Ar secret Ac Ao Ar mutualuser Ac Aq Ar mutualsecret +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 +.Ss portal-group level +The following statements are available at the portal-group level: +.Bl -tag -width indent +.It Ic discovery-auth-group Aq Ar name +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 Ar address +Specifies IPv4 or IPv6 address and port to listen on for incoming connections. +.It Ic listen-iser Aq Ar address +Specifies IPv4 or IPv6 address and port to listen on for incoming connections +using iSER (iSCSI over RDMA) protocol. +.El +.Ss target level: +The following statements are available at the target level: +.Bl -tag -width indent +.It Ic alias Aq Ar text +Assigns human-readable description to that target. +There is no default. +.It Ic auth-group Aq Ar name +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 Ao Ar user Ac Aq Ar secret +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 Ao Ar user Ac Ao Ar secret Ac Ao Ar mutualuser Ac Aq Ar mutualsecret +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 Ar name +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 Ar number +Opens a lun configuration section, defining LUN exported by a target. +.El +.Ss lun level +The following statements are available at the lun level: +.Bl -tag -width indent +.It Ic backend Ao Ar block | Ar ramdisk Ac +Specifies the CTL backend to use for a given LUN. +Valid choices are +.Dq block +and +.Dq 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 Ar size +Specifies blocksize visible to the initiator. +The default blocksize is 512. +.It Ic device-id Aq Ar string +Specifies SCSI Device Identification string presented to the initiator. +.It Ic option Ao Ar name Ac Aq Ar value +Specifies CTL-specific options passed to the kernel. +.It Ic path Aq Ar path +Specifies path to file used to back the LUN. +.It Ic serial Aq Ar string +Specifies SCSI serial number presented to the initiator. +.It Ic size Aq Ar size +Specifies LUN size, in bytes. +.El +.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 EXAMPLES +.Bd -literal +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 SEE ALSO +.Xr ctl 4 , +.Xr ctladm 8 , +.Xr ctld 8 +.Sh AUTHORS +The +.Nm +configuration file functionality for +.Xr ctld 8 +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff -urNp p4/freebsd/src/usr.sbin/ctld/ctld.8 p4/iscsi/usr.sbin/ctld/ctld.8 --- p4/freebsd/src/usr.sbin/ctld/ctld.8 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/ctld.8 2013-07-23 23:48:06.000000000 +0200 @@ -0,0 +1,114 @@ +.\" 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 disconnects 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 4 , +.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 p4/freebsd/src/usr.sbin/ctld/ctld.c p4/iscsi/usr.sbin/ctld/ctld.c --- p4/freebsd/src/usr.sbin/ctld/ctld.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/ctld.c 2013-08-18 13:07:26.000000000 +0200 @@ -0,0 +1,1599 @@ +/*- + * 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" + +static volatile bool sighup_received = false; +static 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 conf * +conf_new(void) +{ + struct conf *conf; + + conf = calloc(1, sizeof(*conf)); + if (conf == NULL) + log_err(1, "calloc"); + TAILQ_INIT(&conf->conf_targets); + TAILQ_INIT(&conf->conf_auth_groups); + TAILQ_INIT(&conf->conf_portal_groups); + + return (conf); +} + +void +conf_delete(struct conf *conf) +{ + struct target *targ, *tmp; + struct auth_group *ag, *cagtmp; + struct portal_group *pg, *cpgtmp; + + assert(conf->conf_pidfh == NULL); + + TAILQ_FOREACH_SAFE(targ, &conf->conf_targets, t_next, tmp) + target_delete(targ); + TAILQ_FOREACH_SAFE(ag, &conf->conf_auth_groups, ag_next, cagtmp) + auth_group_delete(ag); + TAILQ_FOREACH_SAFE(pg, &conf->conf_portal_groups, pg_next, cpgtmp) + portal_group_delete(pg); + free(conf->conf_pidfile_path); + free(conf); +} + +static struct auth * +auth_new(struct auth_group *ag) +{ + struct auth *auth; + + auth = calloc(1, sizeof(*auth)); + if (auth == NULL) + log_err(1, "calloc"); + auth->a_auth_group = ag; + TAILQ_INSERT_TAIL(&ag->ag_auths, auth, a_next); + return (auth); +} + +static void +auth_delete(struct auth *auth) +{ + TAILQ_REMOVE(&auth->a_auth_group->ag_auths, auth, a_next); + + free(auth->a_user); + free(auth->a_secret); + free(auth->a_mutual_user); + free(auth->a_mutual_secret); + free(auth); +} + +const struct auth * +auth_find(struct auth_group *ag, const char *user) +{ + const struct auth *auth; + + TAILQ_FOREACH(auth, &ag->ag_auths, a_next) { + if (strcmp(auth->a_user, user) == 0) + return (auth); + } + + return (NULL); +} + +struct auth_group * +auth_group_new(struct conf *conf, const char *name) +{ + struct auth_group *ag; + + if (name != NULL) { + ag = auth_group_find(conf, name); + if (ag != NULL) { + log_warnx("duplicated auth-group \"%s\"", name); + return (NULL); + } + } + + ag = calloc(1, sizeof(*ag)); + if (ag == NULL) + log_err(1, "calloc"); + if (name != NULL) + ag->ag_name = checked_strdup(name); + TAILQ_INIT(&ag->ag_auths); + ag->ag_conf = conf; + TAILQ_INSERT_TAIL(&conf->conf_auth_groups, ag, ag_next); + + return (ag); +} + +void +auth_group_delete(struct auth_group *ag) +{ + struct auth *auth, *tmp; + + TAILQ_REMOVE(&ag->ag_conf->conf_auth_groups, ag, ag_next); + + TAILQ_FOREACH_SAFE(auth, &ag->ag_auths, a_next, tmp) + auth_delete(auth); + free(ag->ag_name); + free(ag); +} + +struct auth_group * +auth_group_find(struct conf *conf, const char *name) +{ + struct auth_group *ag; + + TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) { + if (ag->ag_name != NULL && strcmp(ag->ag_name, name) == 0) + return (ag); + } + + return (NULL); +} + +static void +auth_check_secret_length(struct auth *auth) +{ + size_t len; + + len = strlen(auth->a_secret); + if (len > 16) { + if (auth->a_auth_group->ag_name != NULL) + log_warnx("secret for user \"%s\", auth-group \"%s\", " + "is too long; it should be at most 16 characters " + "long", auth->a_user, auth->a_auth_group->ag_name); + else + log_warnx("secret for user \"%s\", target \"%s\", " + "is too long; it should be at most 16 characters " + "long", auth->a_user, + auth->a_auth_group->ag_target->t_iqn); + } + if (len < 12) { + if (auth->a_auth_group->ag_name != NULL) + log_warnx("secret for user \"%s\", auth-group \"%s\", " + "is too short; it should be at least 12 characters " + "long", auth->a_user, + auth->a_auth_group->ag_name); + else + log_warnx("secret for user \"%s\", target \"%s\", " + "is too short; it should be at least 16 characters " + "long", auth->a_user, + auth->a_auth_group->ag_target->t_iqn); + } + + if (auth->a_mutual_secret != NULL) { + len = strlen(auth->a_secret); + if (len > 16) { + if (auth->a_auth_group->ag_name != NULL) + log_warnx("mutual secret for user \"%s\", " + "auth-group \"%s\", is too long; it should " + "be at most 16 characters long", + auth->a_user, auth->a_auth_group->ag_name); + else + log_warnx("mutual secret for user \"%s\", " + "target \"%s\", is too long; it should " + "be at most 16 characters long", + auth->a_user, + auth->a_auth_group->ag_target->t_iqn); + } + if (len < 12) { + if (auth->a_auth_group->ag_name != NULL) + log_warnx("mutual secret for user \"%s\", " + "auth-group \"%s\", is too short; it " + "should be at least 12 characters long", + auth->a_user, auth->a_auth_group->ag_name); + else + log_warnx("mutual secret for user \"%s\", " + "target \"%s\", is too short; it should be " + "at least 16 characters long", + auth->a_user, + auth->a_auth_group->ag_target->t_iqn); + } + } +} + +const struct auth * +auth_new_chap(struct auth_group *ag, const char *user, + const char *secret) +{ + struct auth *auth; + + if (ag->ag_type == AG_TYPE_UNKNOWN) + ag->ag_type = AG_TYPE_CHAP; + if (ag->ag_type != AG_TYPE_CHAP) { + if (ag->ag_name != NULL) + log_warnx("cannot mix \"chap\" authentication with " + "other types for auth-group \"%s\"", ag->ag_name); + else + log_warnx("cannot mix \"chap\" authentication with " + "other types for target \"%s\"", + ag->ag_target->t_iqn); + return (NULL); + } + + auth = auth_new(ag); + auth->a_user = checked_strdup(user); + auth->a_secret = checked_strdup(secret); + + auth_check_secret_length(auth); + + return (auth); +} + +const struct auth * +auth_new_chap_mutual(struct auth_group *ag, const char *user, + const char *secret, const char *user2, const char *secret2) +{ + struct auth *auth; + + if (ag->ag_type == AG_TYPE_UNKNOWN) + ag->ag_type = AG_TYPE_CHAP_MUTUAL; + if (ag->ag_type != AG_TYPE_CHAP_MUTUAL) { + if (ag->ag_name != NULL) + log_warnx("cannot mix \"chap-mutual\" authentication " + "with other types for auth-group \"%s\"", + ag->ag_name); + else + log_warnx("cannot mix \"chap-mutual\" authentication " + "with other types for target \"%s\"", + ag->ag_target->t_iqn); + return (NULL); + } + + auth = auth_new(ag); + auth->a_user = checked_strdup(user); + auth->a_secret = checked_strdup(secret); + auth->a_mutual_user = checked_strdup(user2); + auth->a_mutual_secret = checked_strdup(secret2); + + auth_check_secret_length(auth); + + return (auth); +} + +static struct portal * +portal_new(struct portal_group *pg) +{ + struct portal *portal; + + portal = calloc(1, sizeof(*portal)); + if (portal == NULL) + log_err(1, "calloc"); + TAILQ_INIT(&portal->p_targets); + portal->p_portal_group = pg; + TAILQ_INSERT_TAIL(&pg->pg_portals, portal, p_next); + return (portal); +} + +static void +portal_delete(struct portal *portal) +{ + TAILQ_REMOVE(&portal->p_portal_group->pg_portals, portal, p_next); + freeaddrinfo(portal->p_ai); + free(portal->p_listen); + free(portal); +} + +struct portal_group * +portal_group_new(struct conf *conf, const char *name) +{ + struct portal_group *pg; + + if (name != NULL) { + pg = portal_group_find(conf, name); + if (pg != NULL) { + log_warnx("duplicated portal-group \"%s\"", name); + return (NULL); + } + } + + pg = calloc(1, sizeof(*pg)); + if (pg == NULL) + log_err(1, "calloc"); + pg->pg_name = checked_strdup(name); + TAILQ_INIT(&pg->pg_portals); + pg->pg_conf = conf; + conf->conf_last_portal_group_tag++; + pg->pg_tag = conf->conf_last_portal_group_tag; + TAILQ_INSERT_TAIL(&conf->conf_portal_groups, pg, pg_next); + + return (pg); +} + +void +portal_group_delete(struct portal_group *pg) +{ + struct portal *portal, *tmp; + + TAILQ_REMOVE(&pg->pg_conf->conf_portal_groups, pg, pg_next); + + TAILQ_FOREACH_SAFE(portal, &pg->pg_portals, p_next, tmp) + portal_delete(portal); + free(pg->pg_name); + free(pg); +} + +struct portal_group * +portal_group_find(struct conf *conf, const char *name) +{ + struct portal_group *pg; + + TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { + if (strcmp(pg->pg_name, name) == 0) + return (pg); + } + + return (NULL); +} + +int +portal_group_add_listen(struct portal_group *pg, const char *value, bool iser) +{ + struct addrinfo hints; + struct portal *portal; + char *addr, *ch, *arg; + const char *port; + int error, colons = 0; + +#ifndef ICL_KERNEL_PROXY + if (iser) { + log_warnx("ctld(8) compiled without ICL_KERNEL_PROXY " + "does not support iSER protocol"); + return (-1); + } +#endif + + portal = portal_new(pg); + portal->p_listen = checked_strdup(value); + portal->p_iser = iser; + + arg = portal->p_listen; + if (arg[0] == '\0') { + log_warnx("empty listen address"); + free(portal->p_listen); + free(portal); + 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", + portal->p_listen); + free(portal->p_listen); + free(portal); + return (1); + } + if (arg[0] == '\0') { + port = "3260"; + } else if (arg[0] == ':') { + port = arg + 1; + } else { + log_warnx("invalid listen address %s", + portal->p_listen); + free(portal->p_listen); + free(portal); + 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, &portal->p_ai); + if (error != 0) { + log_warnx("getaddrinfo for %s failed: %s", + portal->p_listen, gai_strerror(error)); + free(portal->p_listen); + free(portal); + return (1); + } + + /* + * XXX: getaddrinfo(3) may return multiple addresses; we should turn + * those into multiple portals. + */ + + return (0); +} + +static bool +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 +valid_iscsi_name(const char *name) +{ + int i; + + if (strlen(name) >= MAX_NAME_LEN) { + log_warnx("overlong name for target \"%s\"; max length allowed " + "by iSCSI specification is %d characters", + name, 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 (!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 (!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 target * +target_new(struct conf *conf, const char *iqn) +{ + struct target *targ; + int i, len; + + targ = target_find(conf, iqn); + if (targ != NULL) { + log_warnx("duplicated target \"%s\"", iqn); + return (NULL); + } + if (valid_iscsi_name(iqn) == false) { + log_warnx("target name \"%s\" is invalid", iqn); + return (NULL); + } + targ = calloc(1, sizeof(*targ)); + if (targ == NULL) + log_err(1, "calloc"); + targ->t_iqn = checked_strdup(iqn); + + /* + * RFC 3722 requires us to normalize the name to lowercase. + */ + len = strlen(iqn); + for (i = 0; i < len; i++) + targ->t_iqn[i] = tolower(targ->t_iqn[i]); + + TAILQ_INIT(&targ->t_luns); + targ->t_conf = conf; + TAILQ_INSERT_TAIL(&conf->conf_targets, targ, t_next); + + return (targ); +} + +void +target_delete(struct target *targ) +{ + struct lun *lun, *tmp; + + TAILQ_REMOVE(&targ->t_conf->conf_targets, targ, t_next); + + TAILQ_FOREACH_SAFE(lun, &targ->t_luns, l_next, tmp) + lun_delete(lun); + free(targ->t_iqn); + free(targ); +} + +struct target * +target_find(struct conf *conf, const char *iqn) +{ + struct target *targ; + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + if (strcasecmp(targ->t_iqn, iqn) == 0) + return (targ); + } + + return (NULL); +} + +struct lun * +lun_new(struct target *targ, int lun_id) +{ + struct lun *lun; + + lun = lun_find(targ, lun_id); + if (lun != NULL) { + log_warnx("duplicated lun %d for target \"%s\"", + lun_id, targ->t_iqn); + return (NULL); + } + + lun = calloc(1, sizeof(*lun)); + if (lun == NULL) + log_err(1, "calloc"); + lun->l_lun = lun_id; + TAILQ_INIT(&lun->l_options); + lun->l_target = targ; + TAILQ_INSERT_TAIL(&targ->t_luns, lun, l_next); + + return (lun); +} + +void +lun_delete(struct lun *lun) +{ + struct lun_option *lo, *tmp; + + TAILQ_REMOVE(&lun->l_target->t_luns, lun, l_next); + + TAILQ_FOREACH_SAFE(lo, &lun->l_options, lo_next, tmp) + lun_option_delete(lo); + free(lun->l_backend); + free(lun->l_device_id); + free(lun->l_path); + free(lun->l_serial); + free(lun); +} + +struct lun * +lun_find(struct target *targ, int lun_id) +{ + struct lun *lun; + + TAILQ_FOREACH(lun, &targ->t_luns, l_next) { + if (lun->l_lun == lun_id) + return (lun); + } + + return (NULL); +} + +void +lun_set_backend(struct lun *lun, const char *value) +{ + free(lun->l_backend); + lun->l_backend = checked_strdup(value); +} + +void +lun_set_blocksize(struct lun *lun, size_t value) +{ + + lun->l_blocksize = value; +} + +void +lun_set_device_id(struct lun *lun, const char *value) +{ + free(lun->l_device_id); + lun->l_device_id = checked_strdup(value); +} + +void +lun_set_path(struct lun *lun, const char *value) +{ + free(lun->l_path); + lun->l_path = checked_strdup(value); +} + +void +lun_set_serial(struct lun *lun, const char *value) +{ + free(lun->l_serial); + lun->l_serial = checked_strdup(value); +} + +void +lun_set_size(struct lun *lun, size_t value) +{ + + lun->l_size = value; +} + +void +lun_set_ctl_lun(struct lun *lun, uint32_t value) +{ + + lun->l_ctl_lun = value; +} + +struct lun_option * +lun_option_new(struct lun *lun, const char *name, const char *value) +{ + struct lun_option *lo; + + lo = lun_option_find(lun, name); + if (lo != NULL) { + log_warnx("duplicated lun option %s for lun %d, target \"%s\"", + name, lun->l_lun, lun->l_target->t_iqn); + return (NULL); + } + + lo = calloc(1, sizeof(*lo)); + if (lo == NULL) + log_err(1, "calloc"); + lo->lo_name = checked_strdup(name); + lo->lo_value = checked_strdup(value); + lo->lo_lun = lun; + TAILQ_INSERT_TAIL(&lun->l_options, lo, lo_next); + + return (lo); +} + +void +lun_option_delete(struct lun_option *lo) +{ + + TAILQ_REMOVE(&lo->lo_lun->l_options, lo, lo_next); + + free(lo->lo_name); + free(lo->lo_value); + free(lo); +} + +struct lun_option * +lun_option_find(struct lun *lun, const char *name) +{ + struct lun_option *lo; + + TAILQ_FOREACH(lo, &lun->l_options, lo_next) { + if (strcmp(lo->lo_name, name) == 0) + return (lo); + } + + return (NULL); +} + +void +lun_option_set(struct lun_option *lo, const char *value) +{ + + free(lo->lo_value); + lo->lo_value = checked_strdup(value); +} + +static struct connection * +connection_new(struct portal *portal, int fd, const char *host) +{ + struct connection *conn; + + conn = calloc(1, sizeof(*conn)); + if (conn == NULL) + log_err(1, "calloc"); + conn->conn_portal = portal; + conn->conn_socket = fd; + conn->conn_initiator_addr = checked_strdup(host); + + /* + * Default values, from RFC 3720, section 12. + */ + conn->conn_max_data_segment_length = 8192; + conn->conn_max_burst_length = 262144; + conn->conn_immediate_data = true; + + return (conn); +} + +#if 0 +static void +conf_print(struct conf *conf) +{ + struct auth_group *ag; + struct auth *auth; + struct portal_group *pg; + struct portal *portal; + struct target *targ; + struct lun *lun; + struct lun_option *lo; + + TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) { + fprintf(stderr, "auth-group %s {\n", ag->ag_name); + TAILQ_FOREACH(auth, &ag->ag_auths, a_next) + fprintf(stderr, "\t chap-mutual %s %s %s %s\n", + auth->a_user, auth->a_secret, + auth->a_mutual_user, auth->a_mutual_secret); + fprintf(stderr, "}\n"); + } + TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { + fprintf(stderr, "portal-group %s {\n", pg->pg_name); + TAILQ_FOREACH(portal, &pg->pg_portals, p_next) + fprintf(stderr, "\t listen %s\n", portal->p_listen); + fprintf(stderr, "}\n"); + } + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + fprintf(stderr, "target %s {\n", targ->t_iqn); + if (targ->t_alias != NULL) + fprintf(stderr, "\t alias %s\n", targ->t_alias); + TAILQ_FOREACH(lun, &targ->t_luns, l_next) { + fprintf(stderr, "\tlun %d {\n", lun->l_lun); + fprintf(stderr, "\t\tpath %s\n", lun->l_path); + TAILQ_FOREACH(lo, &lun->l_options, lo_next) + fprintf(stderr, "\t\toption %s %s\n", + lo->lo_name, lo->lo_value); + fprintf(stderr, "\t}\n"); + } + fprintf(stderr, "}\n"); + } +} +#endif + +int +conf_verify(struct conf *conf) +{ + struct auth_group *ag; + struct portal_group *pg; + struct target *targ; + struct lun *lun, *lun2; + bool found_lun0; + + if (conf->conf_pidfile_path == NULL) + conf->conf_pidfile_path = checked_strdup(DEFAULT_PIDFILE); + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + if (targ->t_auth_group == NULL) { + log_warnx("missing authentication for target \"%s\"; " + "must specify either \"auth-group\", \"chap\", " + "or \"chap-mutual\"", targ->t_iqn); + return (1); + } + if (targ->t_portal_group == NULL) { + targ->t_portal_group = portal_group_find(conf, + "default"); + assert(targ->t_portal_group != NULL); + } + found_lun0 = false; + TAILQ_FOREACH(lun, &targ->t_luns, l_next) { + if (lun->l_lun == 0) + found_lun0 = true; + if (lun->l_backend == NULL) + lun_set_backend(lun, "block"); + if (strcmp(lun->l_backend, "block") == 0 && + lun->l_path == NULL) { + log_warnx("missing path for lun %d, " + "target \"%s\"", lun->l_lun, targ->t_iqn); + return (1); + } + if (strcmp(lun->l_backend, "ramdisk") == 0) { + if (lun->l_size == 0) { + log_warnx("missing size for " + "ramdisk-backed lun %d, " + "target \"%s\"", + lun->l_lun, targ->t_iqn); + return (1); + } + if (lun->l_path != NULL) { + log_warnx("path must not be specified " + "for ramdisk-backed lun %d, " + "target \"%s\"", + lun->l_lun, targ->t_iqn); + return (1); + } + } + if (lun->l_lun < 0 || lun->l_lun > 255) { + log_warnx("invalid lun number for lun %d, " + "target \"%s\"; must be between 0 and 255", + lun->l_lun, targ->t_iqn); + return (1); + } +#if 1 /* Should we? */ + TAILQ_FOREACH(lun2, &targ->t_luns, l_next) { + if (lun == lun2) + continue; + if (lun->l_path != NULL && + lun2->l_path != NULL && + strcmp(lun->l_path, lun2->l_path) == 0) + log_debugx("WARNING: duplicate path " + "for lun %d, target \"%s\"", + lun->l_lun, targ->t_iqn); + } +#endif + if (lun->l_blocksize == 0) { + lun_set_blocksize(lun, DEFAULT_BLOCKSIZE); + } else if (lun->l_blocksize <= 0) { + log_warnx("invalid blocksize for lun %d, " + "target \"%s\"; must be larger than 0", + lun->l_lun, targ->t_iqn); + return (1); + } + if (lun->l_size != 0 && + lun->l_size % lun->l_blocksize != 0) { + log_warnx("invalid size for lun %d, target " + "\"%s\"; must be multiple of blocksize", + lun->l_lun, targ->t_iqn); + return (1); + } + } + if (!found_lun0) { + log_warnx("mandatory LUN 0 not configured " + "for target \"%s\"", targ->t_iqn); + return (1); + } + } + TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { + assert(pg->pg_name != NULL); + if (pg->pg_discovery_auth_group == NULL) { + pg->pg_discovery_auth_group = + auth_group_find(conf, "no-access"); + assert(pg->pg_discovery_auth_group != NULL); + } + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + if (targ->t_portal_group == pg) + break; + } + if (targ == NULL) { + if (strcmp(pg->pg_name, "default") != 0) + log_warnx("portal-group \"%s\" not assigned " + "to any target", pg->pg_name); + pg->pg_unassigned = true; + } else + pg->pg_unassigned = false; + } + TAILQ_FOREACH(ag, &conf->conf_auth_groups, ag_next) { + if (ag->ag_name == NULL) + assert(ag->ag_target != NULL); + else + assert(ag->ag_target == NULL); + + TAILQ_FOREACH(targ, &conf->conf_targets, t_next) { + if (targ->t_auth_group == ag) + break; + } + if (targ == NULL && ag->ag_name != NULL && + strcmp(ag->ag_name, "no-authentication") != 0 && + strcmp(ag->ag_name, "no-access") != 0) { + log_warnx("auth-group \"%s\" not assigned " + "to any target", ag->ag_name); + } + } + + return (0); +} + +static int +conf_apply(struct conf *oldconf, struct conf *newconf) +{ + struct target *oldtarg, *newtarg, *tmptarg; + struct lun *oldlun, *newlun, *tmplun; + struct portal_group *oldpg, *newpg; + struct portal *oldp, *newp; + pid_t otherpid; + int changed, cumulated_error = 0, error; +#ifndef ICL_KERNEL_PROXY + int one = 1; +#endif + + if (oldconf->conf_debug != newconf->conf_debug) { + log_debugx("changing debug level to %d", newconf->conf_debug); + log_init(newconf->conf_debug); + } + + if (oldconf->conf_pidfh != NULL) { + assert(oldconf->conf_pidfile_path != NULL); + if (newconf->conf_pidfile_path != NULL && + strcmp(oldconf->conf_pidfile_path, + newconf->conf_pidfile_path) == 0) { + newconf->conf_pidfh = oldconf->conf_pidfh; + oldconf->conf_pidfh = NULL; + } else { + log_debugx("removing pidfile %s", + oldconf->conf_pidfile_path); + pidfile_remove(oldconf->conf_pidfh); + oldconf->conf_pidfh = NULL; + } + } + + if (newconf->conf_pidfh == NULL && newconf->conf_pidfile_path != NULL) { + log_debugx("opening pidfile %s", newconf->conf_pidfile_path); + newconf->conf_pidfh = + pidfile_open(newconf->conf_pidfile_path, 0600, &otherpid); + if (newconf->conf_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->conf_pidfile_path); + } + } + + TAILQ_FOREACH_SAFE(oldtarg, &oldconf->conf_targets, t_next, tmptarg) { + /* + * First, remove any targets present in the old configuration + * and missing in the new one. + */ + newtarg = target_find(newconf, oldtarg->t_iqn); + if (newtarg == NULL) { + TAILQ_FOREACH_SAFE(oldlun, &oldtarg->t_luns, l_next, + tmplun) { + log_debugx("target %s not found in the " + "configuration file; removing its lun %d, " + "backed by CTL lun %d", + oldtarg->t_iqn, oldlun->l_lun, + oldlun->l_ctl_lun); + error = kernel_lun_remove(oldlun); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + cumulated_error++; + } + lun_delete(oldlun); + } + target_delete(oldtarg); + continue; + } + + /* + * Second, remove any LUNs present in the old target + * and missing in the new one. + */ + TAILQ_FOREACH_SAFE(oldlun, &oldtarg->t_luns, l_next, tmplun) { + newlun = lun_find(newtarg, oldlun->l_lun); + if (newlun == NULL) { + log_debugx("lun %d, target %s, CTL lun %d " + "not found in the configuration file; " + "removing", oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + error = kernel_lun_remove(oldlun); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + cumulated_error++; + } + lun_delete(oldlun); + continue; + } + + /* + * Also remove the LUNs changed by more than size. + */ + changed = 0; + assert(oldlun->l_backend != NULL); + assert(newlun->l_backend != NULL); + if (strcmp(newlun->l_backend, oldlun->l_backend) != 0) { + log_debugx("backend for lun %d, target %s, " + "CTL lun %d changed; removing", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + changed = 1; + } + if (oldlun->l_blocksize != newlun->l_blocksize) { + log_debugx("blocksize for lun %d, target %s, " + "CTL lun %d changed; removing", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + changed = 1; + } + if (newlun->l_device_id != NULL && + (oldlun->l_device_id == NULL || + strcmp(oldlun->l_device_id, newlun->l_device_id) != + 0)) { + log_debugx("device-id for lun %d, target %s, " + "CTL lun %d changed; removing", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + changed = 1; + } + if (newlun->l_path != NULL && + (oldlun->l_path == NULL || + strcmp(oldlun->l_path, newlun->l_path) != 0)) { + log_debugx("path for lun %d, target %s, " + "CTL lun %d, changed; removing", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + changed = 1; + } + if (newlun->l_serial != NULL && + (oldlun->l_serial == NULL || + strcmp(oldlun->l_serial, newlun->l_serial) != 0)) { + log_debugx("serial for lun %d, target %s, " + "CTL lun %d changed; removing", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + changed = 1; + } + if (changed) { + error = kernel_lun_remove(oldlun); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldlun->l_lun, oldtarg->t_iqn, + oldlun->l_ctl_lun); + cumulated_error++; + } + lun_delete(oldlun); + continue; + } + + lun_set_ctl_lun(newlun, oldlun->l_ctl_lun); + } + } + + /* + * Now add new targets or modify existing ones. + */ + TAILQ_FOREACH(newtarg, &newconf->conf_targets, t_next) { + oldtarg = target_find(oldconf, newtarg->t_iqn); + + TAILQ_FOREACH(newlun, &newtarg->t_luns, l_next) { + if (oldtarg != NULL) { + oldlun = lun_find(oldtarg, newlun->l_lun); + if (oldlun != NULL) { + if (newlun->l_size != oldlun->l_size) { + log_debugx("resizing lun %d, " + "target %s, CTL lun %d", + newlun->l_lun, + newtarg->t_iqn, + newlun->l_ctl_lun); + error = + kernel_lun_resize(newlun); + if (error != 0) { + log_warnx("failed to " + "resize lun %d, " + "target %s, " + "CTL lun %d", + newlun->l_lun, + newtarg->t_iqn, + newlun->l_lun); + cumulated_error++; + } + } + continue; + } + } + log_debugx("adding lun %d, target %s", + newlun->l_lun, newtarg->t_iqn); + error = kernel_lun_add(newlun); + if (error != 0) { + log_warnx("failed to add lun %d, target %s", + newlun->l_lun, newtarg->t_iqn); + cumulated_error++; + } + } + } + + /* + * Go through the new portals, opening the sockets as neccessary. + */ + TAILQ_FOREACH(newpg, &newconf->conf_portal_groups, pg_next) { + if (newpg->pg_unassigned) { + log_debugx("not listening on portal-group \"%s\", " + "not assigned to any target", + newpg->pg_name); + continue; + } + TAILQ_FOREACH(newp, &newpg->pg_portals, p_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(oldpg, &oldconf->conf_portal_groups, + pg_next) { + TAILQ_FOREACH(oldp, &oldpg->pg_portals, + p_next) { + if (strcmp(newp->p_listen, + oldp->p_listen) == 0 && + oldp->p_socket > 0) { + newp->p_socket = + oldp->p_socket; + oldp->p_socket = 0; + break; + } + } + } + if (newp->p_socket > 0) { + /* + * We're done with this portal. + */ + continue; + } + +#ifdef ICL_KERNEL_PROXY + log_debugx("listening on %s, portal-group \"%s\" using ICL proxy", + newp->p_listen, newpg->pg_name); + kernel_listen(newp->p_ai, newp->p_iser); +#else + assert(newp->p_iser == false); + + log_debugx("listening on %s, portal-group \"%s\"", + newp->p_listen, newpg->pg_name); + newp->p_socket = socket(newp->p_ai->ai_family, + newp->p_ai->ai_socktype, + newp->p_ai->ai_protocol); + if (newp->p_socket < 0) { + log_warn("socket(2) failed for %s", + newp->p_listen); + cumulated_error++; + continue; + } + error = setsockopt(newp->p_socket, SOL_SOCKET, + SO_REUSEADDR, &one, sizeof(one)); + if (error != 0) { + log_warn("setsockopt(SO_REUSEADDR) failed " + "for %s", newp->p_listen); + close(newp->p_socket); + newp->p_socket = 0; + cumulated_error++; + continue; + } + error = bind(newp->p_socket, newp->p_ai->ai_addr, + newp->p_ai->ai_addrlen); + if (error != 0) { + log_warn("bind(2) failed for %s", + newp->p_listen); + close(newp->p_socket); + newp->p_socket = 0; + cumulated_error++; + continue; + } + error = listen(newp->p_socket, -1); + if (error != 0) { + log_warn("listen(2) failed for %s", + newp->p_listen); + close(newp->p_socket); + newp->p_socket = 0; + cumulated_error++; + continue; + } +#endif /* !ICL_KERNEL_PROXY */ + } + } + + /* + * Go through the no longer used sockets, closing them. + */ + TAILQ_FOREACH(oldpg, &oldconf->conf_portal_groups, pg_next) { + TAILQ_FOREACH(oldp, &oldpg->pg_portals, p_next) { + if (oldp->p_socket <= 0) + continue; + log_debugx("closing socket for %s, portal-group \"%s\"", + oldp->p_listen, oldpg->pg_name); + close(oldp->p_socket); + oldp->p_socket = 0; + } + } + + return (cumulated_error); +} + +static void +new_connection(struct portal *portal, int fd, bool dont_fork) +{ + struct connection *conn; +#ifndef ICL_KERNEL_PROXY + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + int error; +#endif + pid_t pid; + char host[NI_MAXHOST + 1]; + + if (dont_fork) { + 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(portal->p_portal_group->pg_conf->conf_pidfh); + +#ifdef ICL_KERNEL_PROXY + /* + * XXX + */ + log_set_peer_addr("XXX"); +#else + 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, portal->p_portal_group->pg_name); + log_set_peer_addr(host); + setproctitle("%s", host); +#endif + + conn = connection_new(portal, fd, host); + kernel_capsicate(); + login(conn); + if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { + kernel_handoff(conn); + log_debugx("connection handed off to the kernel"); + } else { + assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY); + discovery(conn); + } + log_debugx("nothing more to do; exiting"); + exit(0); +} + +#ifndef ICL_KERNEL_PROXY +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); +} +#endif + +static void +main_loop(struct conf *conf, bool dont_fork) +{ + struct portal_group *pg; + struct portal *portal; +#ifdef ICL_KERNEL_PROXY + int connection_id; +#else + fd_set fdset; + int error, nfds, client_fd; +#endif + + pidfile_write(conf->conf_pidfh); + + for (;;) { + if (sighup_received || sigterm_received) + return; + +#ifdef ICL_KERNEL_PROXY + connection_id = kernel_accept(); + if (connection_id == 0) + continue; + + /* + * XXX: This is obviously temporary. + */ + pg = TAILQ_FIRST(&conf->conf_portal_groups); + portal = TAILQ_FIRST(&pg->pg_portals); + + new_connection(portal, connection_id, dont_fork); +#else + FD_ZERO(&fdset); + nfds = 0; + TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { + TAILQ_FOREACH(portal, &pg->pg_portals, p_next) + nfds = fd_add(portal->p_socket, &fdset, nfds); + } + error = select(nfds + 1, &fdset, NULL, NULL, NULL); + if (error <= 0) { + if (errno == EINTR) + return; + log_err(1, "select"); + } + TAILQ_FOREACH(pg, &conf->conf_portal_groups, pg_next) { + TAILQ_FOREACH(portal, &pg->pg_portals, p_next) { + if (!FD_ISSET(portal->p_socket, &fdset)) + continue; + client_fd = accept(portal->p_socket, NULL, 0); + if (client_fd < 0) + log_err(1, "accept"); + new_connection(portal, client_fd, dont_fork); + break; + } + } +#endif /* !ICL_KERNEL_PROXY */ + } +} + +static void +sighup_handler(int dummy __unused) +{ + + sighup_received = true; +} + +static void +sigterm_handler(int dummy __unused) +{ + + sigterm_received = true; +} + +static void +register_signals(void) +{ + struct sigaction sa; + int error; + + bzero(&sa, sizeof(sa)); + sa.sa_handler = sighup_handler; + sigfillset(&sa.sa_mask); + error = sigaction(SIGHUP, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); + + sa.sa_handler = sigterm_handler; + error = sigaction(SIGTERM, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); + + sa.sa_handler = sigterm_handler; + error = sigaction(SIGINT, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); + + sa.sa_handler = SIG_IGN; + error = sigaction(SIGCHLD, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); +} + +int +main(int argc, char **argv) +{ + struct conf *oldconf, *newconf, *tmpconf; + const char *config_path = DEFAULT_CONFIG_PATH; + 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); + kernel_init(); + + oldconf = conf_new_from_kernel(); + newconf = conf_new_from_file(config_path); + if (newconf == NULL) + log_errx(1, "configuration error, exiting"); + if (debug > 0) { + oldconf->conf_debug = debug; + newconf->conf_debug = debug; + } + + if (dont_daemonize == false) { + if (daemon(0, 0) == -1) { + log_warn("cannot daemonize"); + pidfile_remove(newconf->conf_pidfh); + exit(1); + } + } + +#ifdef ICL_KERNEL_PROXY + log_debugx("enabling CTL iSCSI port"); + error = kernel_port_on(); + if (error != 0) + log_errx(1, "failed to enable CTL iSCSI port, exiting"); +#endif + + error = conf_apply(oldconf, newconf); + if (error != 0) + log_errx(1, "failed to apply configuration, exiting"); + conf_delete(oldconf); + oldconf = NULL; + + register_signals(); + +#ifndef ICL_KERNEL_PROXY + log_debugx("enabling CTL iSCSI port"); + error = kernel_port_on(); + if (error != 0) + log_errx(1, "failed to enable CTL iSCSI port, exiting"); +#endif + + for (;;) { + main_loop(newconf, dont_daemonize); + if (sighup_received) { + sighup_received = false; + log_debugx("received SIGHUP, reloading configuration"); + tmpconf = conf_new_from_file(config_path); + if (tmpconf == NULL) { + log_warnx("configuration error, " + "continuing with old configuration"); + } else { + if (debug > 0) + tmpconf->conf_debug = debug; + oldconf = newconf; + newconf = tmpconf; + error = conf_apply(oldconf, newconf); + if (error != 0) + log_warnx("failed to reload " + "configuration"); + 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 = kernel_port_off(); + if (error != 0) + log_warnx("failed to disable CTL iSCSI port"); + + oldconf = newconf; + newconf = conf_new(); + if (debug > 0) + newconf->conf_debug = debug; + error = conf_apply(oldconf, newconf); + if (error != 0) + log_warnx("failed to apply configuration"); + + log_warnx("exiting on signal"); + exit(0); + } + } + /* NOTREACHED */ +} diff -urNp p4/freebsd/src/usr.sbin/ctld/ctld.h p4/iscsi/usr.sbin/ctld/ctld.h --- p4/freebsd/src/usr.sbin/ctld/ctld.h 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/ctld.h 2013-08-18 13:07:26.000000000 +0200 @@ -0,0 +1,274 @@ +/*- + * 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 DEFAULT_CONFIG_PATH "/etc/ctl.conf" +#define DEFAULT_PIDFILE "/var/run/ctld.pid" +#define DEFAULT_BLOCKSIZE 512 + +#define MAX_NAME_LEN 223 +#define MAX_DATA_SEGMENT_LENGTH (128 * 1024) +#define MAX_BURST_LENGTH 16776192 + +struct auth { + TAILQ_ENTRY(auth) a_next; + struct auth_group *a_auth_group; + char *a_user; + char *a_secret; + char *a_mutual_user; + char *a_mutual_secret; +}; + +#define AG_TYPE_UNKNOWN 0 +#define AG_TYPE_NO_AUTHENTICATION 1 +#define AG_TYPE_CHAP 2 +#define AG_TYPE_CHAP_MUTUAL 3 + +struct auth_group { + TAILQ_ENTRY(auth_group) ag_next; + struct conf *ag_conf; + char *ag_name; + struct target *ag_target; + int ag_type; + TAILQ_HEAD(, auth) ag_auths; +}; + +struct portal { + TAILQ_ENTRY(portal) p_next; + struct portal_group *p_portal_group; + bool p_iser; + char *p_listen; + struct addrinfo *p_ai; + + TAILQ_HEAD(, target) p_targets; + int p_socket; +}; + +struct portal_group { + TAILQ_ENTRY(portal_group) pg_next; + struct conf *pg_conf; + char *pg_name; + struct auth_group *pg_discovery_auth_group; + bool pg_unassigned; + TAILQ_HEAD(, portal) pg_portals; + + uint16_t pg_tag; +}; + +struct lun_option { + TAILQ_ENTRY(lun_option) lo_next; + struct lun *lo_lun; + char *lo_name; + char *lo_value; +}; + +struct lun { + TAILQ_ENTRY(lun) l_next; + TAILQ_HEAD(, lun_option) l_options; + struct target *l_target; + int l_lun; + char *l_backend; + int l_blocksize; + char *l_device_id; + char *l_path; + char *l_serial; + int64_t l_size; + + int l_ctl_lun; +}; + +struct target { + TAILQ_ENTRY(target) t_next; + TAILQ_HEAD(, lun) t_luns; + struct conf *t_conf; + struct auth_group *t_auth_group; + struct portal_group *t_portal_group; + char *t_iqn; + char *t_alias; +}; + +struct conf { + char *conf_pidfile_path; + TAILQ_HEAD(, target) conf_targets; + TAILQ_HEAD(, auth_group) conf_auth_groups; + TAILQ_HEAD(, portal_group) conf_portal_groups; + int conf_debug; + + uint16_t conf_last_portal_group_tag; + struct pidfh *conf_pidfh; +}; + +#define CONN_SESSION_TYPE_NONE 0 +#define CONN_SESSION_TYPE_DISCOVERY 1 +#define CONN_SESSION_TYPE_NORMAL 2 + +#define CONN_DIGEST_NONE 0 +#define CONN_DIGEST_CRC32C 1 + +struct connection { + struct portal *conn_portal; + struct target *conn_target; + int conn_socket; + int conn_session_type; + char *conn_initiator_name; + char *conn_initiator_addr; + char *conn_initiator_alias; + uint32_t conn_cmdsn; + uint32_t conn_statsn; + size_t conn_max_data_segment_length; + size_t conn_max_burst_length; + int conn_immediate_data; + int conn_header_digest; + int conn_data_digest; +}; + +struct pdu { + struct connection *pdu_connection; + struct iscsi_bhs *pdu_bhs; + char *pdu_data; + size_t pdu_data_len; +}; + +#define KEYS_MAX 1024 + +struct keys { + char *keys_names[KEYS_MAX]; + char *keys_values[KEYS_MAX]; + char *keys_data; + size_t keys_data_len; +}; + +struct conf *conf_new(void); +struct conf *conf_new_from_file(const char *path); +struct conf *conf_new_from_kernel(void); +void conf_delete(struct conf *conf); +int conf_verify(struct conf *conf); + +struct auth_group *auth_group_new(struct conf *conf, const char *name); +void auth_group_delete(struct auth_group *ag); +struct auth_group *auth_group_find(struct conf *conf, const char *name); + +const struct auth *auth_new_chap(struct auth_group *ag, + const char *user, const char *secret); +const struct auth *auth_new_chap_mutual(struct auth_group *ag, + const char *user, const char *secret, + const char *user2, const char *secret2); +const struct auth *auth_find(struct auth_group *ag, + const char *user); + +struct portal_group *portal_group_new(struct conf *conf, const char *name); +void portal_group_delete(struct portal_group *pg); +struct portal_group *portal_group_find(struct conf *conf, const char *name); +int portal_group_add_listen(struct portal_group *pg, + const char *listen, bool iser); + +struct target *target_new(struct conf *conf, const char *iqn); +void target_delete(struct target *target); +struct target *target_find(struct conf *conf, + const char *iqn); + +struct lun *lun_new(struct target *target, int lun_id); +void lun_delete(struct lun *lun); +struct lun *lun_find(struct target *target, int lun_id); +void lun_set_backend(struct lun *lun, const char *value); +void lun_set_blocksize(struct lun *lun, size_t value); +void lun_set_device_id(struct lun *lun, const char *value); +void lun_set_path(struct lun *lun, const char *value); +void lun_set_serial(struct lun *lun, const char *value); +void lun_set_size(struct lun *lun, size_t value); +void lun_set_ctl_lun(struct lun *lun, uint32_t value); + +struct lun_option *lun_option_new(struct lun *lun, + const char *name, const char *value); +void lun_option_delete(struct lun_option *clo); +struct lun_option *lun_option_find(struct lun *lun, const char *name); +void lun_option_set(struct lun_option *clo, + const char *value); + +void kernel_init(void); +int kernel_lun_add(struct lun *lun); +int kernel_lun_resize(struct lun *lun); +int kernel_lun_remove(struct lun *lun); +void kernel_handoff(struct connection *conn); +int kernel_port_on(void); +int kernel_port_off(void); +void kernel_capsicate(void); + +/* + * ICL_KERNEL_PROXY + */ +void kernel_listen(struct addrinfo *ai, bool iser); +int kernel_accept(void); +void kernel_send(struct pdu *pdu); +void kernel_receive(struct pdu *pdu); + +struct keys *keys_new(void); +void keys_delete(struct keys *keys); +void keys_load(struct keys *keys, const struct pdu *pdu); +void keys_save(struct keys *keys, struct pdu *pdu); +const char *keys_find(struct keys *keys, const char *name); +int keys_find_int(struct keys *keys, const char *name); +void keys_add(struct keys *keys, + const char *name, const char *value); +void keys_add_int(struct keys *keys, + const char *name, int value); + +struct pdu *pdu_new(struct connection *conn); +struct pdu *pdu_new_response(struct pdu *request); +void pdu_delete(struct pdu *pdu); +void pdu_receive(struct pdu *request); +void pdu_send(struct pdu *response); + +void login(struct connection *conn); + +void discovery(struct connection *conn); + +void log_init(int level); +void log_set_peer_name(const char *name); +void log_set_peer_addr(const char *addr); +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 valid_iscsi_name(const char *name); + +#endif /* !CTLD_H */ diff -urNp p4/freebsd/src/usr.sbin/ctld/discovery.c p4/iscsi/usr.sbin/ctld/discovery.c --- p4/freebsd/src/usr.sbin/ctld/discovery.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/discovery.c 2013-08-20 11:52:23.000000000 +0200 @@ -0,0 +1,221 @@ +/*- + * 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_proto.h" + +static struct pdu * +text_receive(struct connection *conn) +{ + struct pdu *request; + struct iscsi_bhs_text_request *bhstr; + + request = pdu_new(conn); + pdu_receive(request); + if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != + ISCSI_BHS_OPCODE_TEXT_REQUEST) + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->pdu_bhs->bhs_opcode); + bhstr = (struct iscsi_bhs_text_request *)request->pdu_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->pdu_data_len == 0) + log_errx(1, "received Text PDU with empty data segment"); + + if (ntohl(bhstr->bhstr_cmdsn) < conn->conn_cmdsn) { + log_errx(1, "received Text PDU with decreasing CmdSN: " + "was %d, is %d", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn)); + } + if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) { + log_errx(1, "received Text PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhstr->bhstr_expstatsn), + conn->conn_statsn); + } + conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn); + + return (request); +} + +static struct pdu * +text_new_response(struct pdu *request) +{ + struct pdu *response; + struct connection *conn; + struct iscsi_bhs_text_request *bhstr; + struct iscsi_bhs_text_response *bhstr2; + + bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; + conn = request->pdu_connection; + + response = pdu_new_response(request); + bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_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(conn->conn_statsn++); + bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn); + bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn); + + return (response); +} + +static struct pdu * +logout_receive(struct connection *conn) +{ + struct pdu *request; + struct iscsi_bhs_logout_request *bhslr; + + request = pdu_new(conn); + pdu_receive(request); + if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != + ISCSI_BHS_OPCODE_LOGOUT_REQUEST) + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->pdu_bhs->bhs_opcode); + bhslr = (struct iscsi_bhs_logout_request *)request->pdu_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) < conn->conn_cmdsn) { + log_errx(1, "received Logout PDU with decreasing CmdSN: " + "was %d, is %d", conn->conn_cmdsn, + ntohl(bhslr->bhslr_cmdsn)); + } + if (ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) { + log_errx(1, "received Logout PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn), + conn->conn_statsn); + } + conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn); + + return (request); +} + +static struct pdu * +logout_new_response(struct pdu *request) +{ + struct pdu *response; + struct connection *conn; + struct iscsi_bhs_logout_request *bhslr; + struct iscsi_bhs_logout_response *bhslr2; + + bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; + conn = request->pdu_connection; + + response = pdu_new_response(request); + bhslr2 = (struct iscsi_bhs_logout_response *)response->pdu_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(conn->conn_statsn++); + bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn); + bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn); + + return (response); +} + +void +discovery(struct connection *conn) +{ + struct pdu *request, *response; + struct keys *request_keys, *response_keys; + struct target *targ; + const char *send_targets; + + log_debugx("beginning discovery session; waiting for Text PDU"); + request = text_receive(conn); + request_keys = keys_new(); + keys_load(request_keys, request); + + send_targets = keys_find(request_keys, "SendTargets"); + if (send_targets == NULL) + log_errx(1, "received Text PDU without SendTargets"); + + response = text_new_response(request); + response_keys = keys_new(); + + if (strcmp(send_targets, "All") == 0) { + TAILQ_FOREACH(targ, + &conn->conn_portal->p_portal_group->pg_conf->conf_targets, + t_next) { + if (targ->t_portal_group != + conn->conn_portal->p_portal_group) { + log_debugx("not returning target \"%s\"; " + "belongs to a different portal group", + targ->t_iqn); + continue; + } + keys_add(response_keys, "TargetName", targ->t_iqn); + } + } else { + targ = target_find(conn->conn_portal->p_portal_group->pg_conf, + send_targets); + if (targ == NULL) { + log_debugx("initiator requested information on unknown " + "target \"%s\"; returning nothing", send_targets); + } else { + keys_add(response_keys, "TargetName", targ->t_iqn); + } + } + keys_save(response_keys, response); + + pdu_send(response); + pdu_delete(response); + keys_delete(response_keys); + pdu_delete(request); + keys_delete(request_keys); + + log_debugx("done sending targets; waiting for Logout PDU"); + request = logout_receive(conn); + response = logout_new_response(request); + + pdu_send(response); + pdu_delete(response); + pdu_delete(request); + + log_debugx("discovery session done"); +} diff -urNp p4/freebsd/src/usr.sbin/ctld/kernel.c p4/iscsi/usr.sbin/ctld/kernel.c --- p4/freebsd/src/usr.sbin/ctld/kernel.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/kernel.c 2013-08-18 13:07:26.000000000 +0200 @@ -0,0 +1,780 @@ +/*- + * 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 +#include +#include + +#ifdef ICL_KERNEL_PROXY +#include +#endif + +#include "ctld.h" + +static int ctl_fd = 0; + +void +kernel_init(void) +{ + int retval, saved_errno; + + ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR); + if (ctl_fd < 0) { + saved_errno = errno; + retval = kldload("ctl"); + if (retval != -1) + ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR); + else + errno = saved_errno; + } + 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; + char *ctld_target_alias; + 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_target_alias") == 0) { + cur_lun->ctld_target_alias = 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 conf * +conf_new_from_kernel(void) +{ + struct conf *conf = NULL; + struct target *targ; + struct lun *cl; + struct lun_option *lo; + 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); + } + + conf = 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; + } + + targ = target_find(conf, lun->ctld_target); + if (targ == NULL) { +#if 0 + log_debugx("found new kernel target %s for CTL lun %ld", + lun->ctld_target, lun->lun_id); +#endif + targ = target_new(conf, lun->ctld_target); + if (targ == NULL) { + log_warnx("target_new failed"); + continue; + } + } + + cl = lun_find(targ, 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->l_lun, + cl->l_target->t_iqn, cl->l_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 = lun_new(targ, lun->ctld_lun); + if (cl == NULL) { + log_warnx("lun_new failed"); + continue; + } + lun_set_backend(cl, lun->backend_type); + lun_set_blocksize(cl, lun->blocksize); + lun_set_device_id(cl, lun->device_id); + lun_set_serial(cl, lun->serial_number); + lun_set_size(cl, lun->size_blocks * cl->l_blocksize); + 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) { + lun_set_path(cl, nv->value); + continue; + } + lo = lun_option_new(cl, nv->name, nv->value); + if (lo == 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->l_lun, cl->l_target->t_iqn); + } + } + + return (conf); +} + +int +kernel_lun_add(struct lun *lun) +{ + struct lun_option *lo; + struct ctl_lun_req req; + char *tmp; + int error, i, num_options; + + bzero(&req, sizeof(req)); + + strlcpy(req.backend, lun->l_backend, sizeof(req.backend)); + req.reqtype = CTL_LUNREQ_CREATE; + + req.reqdata.create.blocksize_bytes = lun->l_blocksize; + + if (lun->l_size != 0) + req.reqdata.create.lun_size_bytes = lun->l_size; + + req.reqdata.create.flags |= CTL_LUN_FLAG_DEV_TYPE; + req.reqdata.create.device_type = T_DIRECT; + + if (lun->l_serial != NULL) { + strlcpy(req.reqdata.create.serial_num, lun->l_serial, + sizeof(req.reqdata.create.serial_num)); + req.reqdata.create.flags |= CTL_LUN_FLAG_SERIAL_NUM; + } + + if (lun->l_device_id != NULL) { + strlcpy(req.reqdata.create.device_id, lun->l_device_id, + sizeof(req.reqdata.create.device_id)); + req.reqdata.create.flags |= CTL_LUN_FLAG_DEVID; + } + + if (lun->l_path != NULL) { + lo = lun_option_find(lun, "file"); + if (lo != NULL) { + lun_option_set(lo, lun->l_path); + } else { + lo = lun_option_new(lun, "file", lun->l_path); + assert(lo != NULL); + } + } + + lo = lun_option_find(lun, "ctld_target"); + if (lo != NULL) { + lun_option_set(lo, lun->l_target->t_iqn); + } else { + lo = lun_option_new(lun, "ctld_target", + lun->l_target->t_iqn); + assert(lo != NULL); + } + + if (lun->l_target->t_alias != NULL) { + lo = lun_option_find(lun, "ctld_target_alias"); + if (lo != NULL) { + lun_option_set(lo, lun->l_target->t_alias); + } else { + lo = lun_option_new(lun, "ctld_target_alias", + lun->l_target->t_alias); + assert(lo != NULL); + } + } + + asprintf(&tmp, "%d", lun->l_lun); + if (tmp == NULL) + log_errx(1, "asprintf"); + lo = lun_option_find(lun, "ctld_lun"); + if (lo != NULL) { + lun_option_set(lo, tmp); + free(tmp); + } else { + lo = lun_option_new(lun, "ctld_lun", tmp); + free(tmp); + assert(lo != NULL); + } + + num_options = 0; + TAILQ_FOREACH(lo, &lun->l_options, lo_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(lo, &lun->l_options, lo_next) { + /* + * +1 for the terminating '\0' + */ + req.be_args[i].namelen = strlen(lo->lo_name) + 1; + req.be_args[i].name = lo->lo_name; + req.be_args[i].vallen = strlen(lo->lo_value) + 1; + req.be_args[i].value = lo->lo_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); + } + + lun_set_ctl_lun(lun, req.reqdata.create.req_lun_id); + + return (0); +} + +int +kernel_lun_resize(struct lun *lun) +{ + struct ctl_lun_req req; + + bzero(&req, sizeof(req)); + + strlcpy(req.backend, lun->l_backend, sizeof(req.backend)); + req.reqtype = CTL_LUNREQ_MODIFY; + + req.reqdata.modify.lun_id = lun->l_ctl_lun; + req.reqdata.modify.lun_size_bytes = lun->l_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 +kernel_lun_remove(struct lun *lun) +{ + struct ctl_lun_req req; + + bzero(&req, sizeof(req)); + + strlcpy(req.backend, lun->l_backend, sizeof(req.backend)); + req.reqtype = CTL_LUNREQ_RM; + + req.reqdata.rm.lun_id = lun->l_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 +kernel_handoff(struct connection *conn) +{ + struct ctl_iscsi req; + + bzero(&req, sizeof(req)); + + req.type = CTL_ISCSI_HANDOFF; + strlcpy(req.data.handoff.initiator_name, + conn->conn_initiator_name, sizeof(req.data.handoff.initiator_name)); + strlcpy(req.data.handoff.initiator_addr, + conn->conn_initiator_addr, sizeof(req.data.handoff.initiator_addr)); + if (conn->conn_initiator_alias != NULL) { + strlcpy(req.data.handoff.initiator_alias, + conn->conn_initiator_alias, sizeof(req.data.handoff.initiator_alias)); + } + strlcpy(req.data.handoff.target_name, + conn->conn_target->t_iqn, sizeof(req.data.handoff.target_name)); + req.data.handoff.socket = conn->conn_socket; + req.data.handoff.portal_group_tag = + conn->conn_portal->p_portal_group->pg_tag; + if (conn->conn_header_digest == CONN_DIGEST_CRC32C) + req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C; + if (conn->conn_data_digest == CONN_DIGEST_CRC32C) + req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C; + req.data.handoff.cmdsn = conn->conn_cmdsn; + req.data.handoff.statsn = conn->conn_statsn; + req.data.handoff.max_recv_data_segment_length = + conn->conn_max_data_segment_length; + req.data.handoff.max_burst_length = conn->conn_max_burst_length; + req.data.handoff.immediate_data = conn->conn_immediate_data; + + 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 +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 +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); +} + +#ifdef ICL_KERNEL_PROXY +void +kernel_listen(struct addrinfo *ai, bool iser) +{ + struct ctl_iscsi req; + + bzero(&req, sizeof(req)); + + req.type = CTL_ISCSI_LISTEN; + req.data.listen.iser = iser; + req.data.listen.domain = ai->ai_family; + req.data.listen.socktype = ai->ai_socktype; + req.data.listen.protocol = ai->ai_protocol; + req.data.listen.addr = ai->ai_addr; + req.data.listen.addrlen = ai->ai_addrlen; + + if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) + log_warn("error issuing CTL_ISCSI_LISTEN ioctl"); +} + +int +kernel_accept(void) +{ + struct ctl_iscsi req; + + bzero(&req, sizeof(req)); + + req.type = CTL_ISCSI_ACCEPT; + + if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) { + log_warn("error issuing CTL_ISCSI_LISTEN ioctl"); + return (0); + } + + return (req.data.accept.connection_id); +} + +void +kernel_send(struct pdu *pdu) +{ + struct ctl_iscsi req; + + bzero(&req, sizeof(req)); + + req.type = CTL_ISCSI_SEND; + req.data.send.connection_id = pdu->pdu_connection->conn_socket; + req.data.send.bhs = pdu->pdu_bhs; + req.data.send.data_segment_len = pdu->pdu_data_len; + req.data.send.data_segment = pdu->pdu_data; + + 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 send: " + "%s; dropping connection", req.error_str); +} + +void +kernel_receive(struct pdu *pdu) +{ + struct ctl_iscsi req; + + pdu->pdu_data = malloc(MAX_DATA_SEGMENT_LENGTH); + if (pdu->pdu_data == NULL) + log_err(1, "malloc"); + + bzero(&req, sizeof(req)); + + req.type = CTL_ISCSI_RECEIVE; + req.data.receive.connection_id = pdu->pdu_connection->conn_socket; + req.data.receive.bhs = pdu->pdu_bhs; + req.data.receive.data_segment_len = MAX_DATA_SEGMENT_LENGTH; + req.data.receive.data_segment = pdu->pdu_data; + + 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 receive: " + "%s; dropping connection", req.error_str); + +} + +#endif /* ICL_KERNEL_PROXY */ + +/* + * XXX: I CANT INTO LATIN + */ +void +kernel_capsicate(void) +{ + int error; + const unsigned long cmds[] = { CTL_ISCSI }; + + error = cap_rights_limit(ctl_fd, CAP_IOCTL); + if (error != 0 && errno != ENOSYS) + log_err(1, "cap_rights_limit"); + + error = cap_ioctls_limit(ctl_fd, cmds, + sizeof(cmds) / sizeof(cmds[0])); + if (error != 0 && errno != ENOSYS) + log_err(1, "cap_ioctls_limit"); + + error = cap_enter(); + if (error != 0 && errno != ENOSYS) + log_err(1, "cap_enter"); + + if (cap_sandboxed()) + log_debugx("Capsicum capability mode enabled"); + else + log_warnx("Capsicum capability mode not supported"); +} + diff -urNp p4/freebsd/src/usr.sbin/ctld/keys.c p4/iscsi/usr.sbin/ctld/keys.c --- p4/freebsd/src/usr.sbin/ctld/keys.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/keys.c 2013-07-23 23:48:06.000000000 +0200 @@ -0,0 +1,216 @@ +/*- + * 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 keys * +keys_new(void) +{ + struct keys *keys; + + keys = calloc(sizeof(*keys), 1); + if (keys == NULL) + log_err(1, "calloc"); + + return (keys); +} + +void +keys_delete(struct keys *keys) +{ + + free(keys->keys_data); + free(keys); +} + +void +keys_load(struct keys *keys, const struct pdu *pdu) +{ + int i; + char *pair; + size_t pair_len; + + if (pdu->pdu_data_len == 0) + log_errx(1, "protocol error: empty data segment"); + + if (pdu->pdu_data[pdu->pdu_data_len - 1] != '\0') + log_errx(1, "protocol error: key not NULL-terminated\n"); + + assert(keys->keys_data == NULL); + keys->keys_data_len = pdu->pdu_data_len; + keys->keys_data = malloc(keys->keys_data_len); + if (keys->keys_data == NULL) + log_err(1, "malloc"); + memcpy(keys->keys_data, pdu->pdu_data, keys->keys_data_len); + + /* + * XXX: Review this carefully. + */ + pair = keys->keys_data; + for (i = 0;; i++) { + if (i >= KEYS_MAX) + log_errx(1, "too many keys received"); + + pair_len = strlen(pair); + + keys->keys_values[i] = pair; + keys->keys_names[i] = strsep(&keys->keys_values[i], "="); + if (keys->keys_names[i] == NULL || keys->keys_values[i] == NULL) + log_errx(1, "malformed keys"); + log_debugx("key received: \"%s=%s\"", + keys->keys_names[i], keys->keys_values[i]); + + pair += pair_len + 1; /* +1 to skip the terminating '\0'. */ + if (pair == keys->keys_data + keys->keys_data_len) + break; + assert(pair < keys->keys_data + keys->keys_data_len); + } +} + +void +keys_save(struct keys *keys, struct pdu *pdu) +{ + char *data; + size_t len; + int i; + + /* + * XXX: Not particularly efficient. + */ + len = 0; + for (i = 0; i < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) + break; + /* + * +1 for '=', +1 for '\0'. + */ + len += strlen(keys->keys_names[i]) + + strlen(keys->keys_values[i]) + 2; + } + + if (len == 0) + return; + + data = malloc(len); + if (data == NULL) + log_err(1, "malloc"); + + pdu->pdu_data = data; + pdu->pdu_data_len = len; + + for (i = 0; i < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) + break; + data += sprintf(data, "%s=%s", + keys->keys_names[i], keys->keys_values[i]); + data += 1; /* for '\0'. */ + } +} + +const char * +keys_find(struct keys *keys, 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 < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) + return (NULL); + if (strcmp(keys->keys_names[i], name) == 0) + return (keys->keys_values[i]); + } + return (NULL); +} + +int +keys_find_int(struct keys *keys, const char *name) +{ + const char *str; + char *endptr; + int num; + + str = keys_find(keys, 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 +keys_add(struct keys *keys, 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 < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) { + keys->keys_names[i] = checked_strdup(name); + keys->keys_values[i] = checked_strdup(value); + return; + } + } + log_errx(1, "too many keys"); +} + +void +keys_add_int(struct keys *keys, const char *name, int value) +{ + char *str; + int ret; + + ret = asprintf(&str, "%d", value); + if (ret <= 0) + log_err(1, "asprintf"); + + keys_add(keys, name, str); + free(str); +} diff -urNp p4/freebsd/src/usr.sbin/ctld/log.c p4/iscsi/usr.sbin/ctld/log.c --- p4/freebsd/src/usr.sbin/ctld/log.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/log.c 2013-07-23 23:48:06.000000000 +0200 @@ -0,0 +1,181 @@ +/*- + * 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; +static char *peer_name = NULL; +static char *peer_addr = NULL; + +void +log_init(int level) +{ + + log_level = level; + openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON); +} + +void +log_set_peer_name(const char *name) +{ + + /* + * XXX: Turn it into assertion? + */ + if (peer_name != NULL) + log_errx(1, "%s called twice", __func__); + if (peer_addr == NULL) + log_errx(1, "%s called before log_set_peer_addr", __func__); + + peer_name = checked_strdup(name); +} + +void +log_set_peer_addr(const char *addr) +{ + + /* + * XXX: Turn it into assertion? + */ + if (peer_addr != NULL) + log_errx(1, "%s called twice", __func__); + + peer_addr = checked_strdup(addr); +} + +static void +log_common(int priority, int log_errno, const char *fmt, va_list ap) +{ + int ret; + char *msg; + + ret = vasprintf(&msg, fmt, ap); + if (ret == -1) { + /* + * XXX: vasprintf(3) doesn't set errno, right? + */ + fprintf(stderr, "%s: %s: %s\n", + getprogname(), __func__, strerror(ENOMEM)); + syslog(LOG_CRIT, "%s: %s", __func__, strerror(ENOMEM)); + exit(1); + } + + if (log_errno == -1) { + if (peer_name != NULL) { + fprintf(stderr, "%s: %s (%s): %s\n", getprogname(), peer_addr, peer_name, msg); + syslog(priority, "%s (%s): %s", peer_addr, peer_name, msg); + } else if (peer_addr != NULL) { + fprintf(stderr, "%s: %s: %s\n", getprogname(), peer_addr, msg); + syslog(priority, "%s: %s", peer_addr, msg); + } else { + fprintf(stderr, "%s: %s\n", getprogname(), msg); + syslog(priority, "%s", msg); + } + + } else { + if (peer_name != NULL) { + fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(), peer_addr, peer_name, msg, strerror(errno)); + syslog(priority, "%s (%s): %s: %s", peer_addr, peer_name, msg, strerror(errno)); + } else if (peer_addr != NULL) { + fprintf(stderr, "%s: %s: %s: %s\n", getprogname(), peer_addr, msg, strerror(errno)); + syslog(priority, "%s: %s: %s", peer_addr, msg, strerror(errno)); + } else { + fprintf(stderr, "%s: %s: %s\n", getprogname(), msg, strerror(errno)); + syslog(priority, "%s: %s", msg, strerror(errno)); + } + } + + free(msg); +} + +void +log_err(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_CRIT, errno, fmt, ap); + va_end(ap); + + exit(eval); +} + +void +log_errx(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_CRIT, -1, fmt, ap); + va_end(ap); + + exit(eval); +} + +void +log_warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_WARNING, errno, fmt, ap); + va_end(ap); +} + +void +log_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_WARNING, -1, fmt, ap); + va_end(ap); +} + +void +log_debugx(const char *fmt, ...) +{ + va_list ap; + + if (log_level == 0) + return; + + va_start(ap, fmt); + log_common(LOG_DEBUG, -1, fmt, ap); + va_end(ap); +} diff -urNp p4/freebsd/src/usr.sbin/ctld/login.c p4/iscsi/usr.sbin/ctld/login.c --- p4/freebsd/src/usr.sbin/ctld/login.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/login.c 2013-08-20 11:52:23.000000000 +0200 @@ -0,0 +1,1051 @@ +/*- + * 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 "ctld.h" +#include "iscsi_proto.h" + +static void login_send_error(struct pdu *request, + char class, char detail); + +static void +login_set_nsg(struct 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->pdu_bhs; + + bhslr->bhslr_flags &= 0xFC; + bhslr->bhslr_flags |= nsg; +} + +static int +login_csg(const struct pdu *request) +{ + struct iscsi_bhs_login_request *bhslr; + + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + + return ((bhslr->bhslr_flags & 0x0C) >> 2); +} + +static void +login_set_csg(struct 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->pdu_bhs; + + bhslr->bhslr_flags &= 0xF3; + bhslr->bhslr_flags |= csg << 2; +} + +static struct pdu * +login_receive(struct connection *conn, bool initial) +{ + struct pdu *request; + struct iscsi_bhs_login_request *bhslr; + + request = pdu_new(conn); + pdu_receive(request); + if ((request->pdu_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) + login_send_error(request, 0x02, 0x0b); + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->pdu_bhs->bhs_opcode); + } + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + /* + * XXX: Implement the C flag some day. + */ + if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) { + login_send_error(request, 0x03, 0x00); + log_errx(1, "received Login PDU with unsupported \"C\" flag"); + } + if (bhslr->bhslr_version_max != 0x00) { + 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) { + login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with unsupported " + "Version-min 0x%x", bhslr->bhslr_version_min); + } + if (request->pdu_data_len == 0) { + login_send_error(request, 0x02, 0x00); + log_errx(1, "received Login PDU with empty data segment"); + } + if (ntohl(bhslr->bhslr_cmdsn) < conn->conn_cmdsn) { + login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with decreasing CmdSN: " + "was %d, is %d", conn->conn_cmdsn, + ntohl(bhslr->bhslr_cmdsn)); + } + if (initial == false && + ntohl(bhslr->bhslr_expstatsn) != conn->conn_statsn) { + login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with wrong ExpStatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn), + conn->conn_statsn); + } + conn->conn_cmdsn = ntohl(bhslr->bhslr_cmdsn); + + return (request); +} + +static struct pdu * +login_new_response(struct pdu *request) +{ + struct pdu *response; + struct connection *conn; + struct iscsi_bhs_login_request *bhslr; + struct iscsi_bhs_login_response *bhslr2; + + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + conn = request->pdu_connection; + + response = pdu_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_RESPONSE; + 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(conn->conn_statsn++); + bhslr2->bhslr_expcmdsn = htonl(conn->conn_cmdsn); + bhslr2->bhslr_maxcmdsn = htonl(conn->conn_cmdsn); + + return (response); +} + +static void +login_send_error(struct pdu *request, char class, char detail) +{ + struct 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 = login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + bhslr2->bhslr_status_class = class; + bhslr2->bhslr_status_detail = detail; + + pdu_send(response); + pdu_delete(response); +} + +static int +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 +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 +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 +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 = 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 * +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 +login_compute_md5(const char id, 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, &id, sizeof(id)); + 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 pdu * +login_receive_chap_a(struct connection *conn) +{ + struct pdu *request; + struct keys *request_keys; + const char *chap_a; + + request = login_receive(conn, false); + request_keys = keys_new(); + keys_load(request_keys, request); + + chap_a = keys_find(request_keys, "CHAP_A"); + if (chap_a == NULL) { + login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU without CHAP_A"); + } + if (login_list_contains(chap_a, "5") == 0) { + login_send_error(request, 0x02, 0x01); + log_errx(1, "received CHAP Login PDU with unsupported CHAP_A " + "\"%s\"", chap_a); + } + keys_delete(request_keys); + + return (request); +} + +static void +login_send_chap_c(struct pdu *request, const unsigned char id, + const void *challenge, const size_t challenge_len) +{ + struct pdu *response; + struct keys *response_keys; + char *chap_c, chap_i[4]; + + chap_c = login_bin2hex(challenge, challenge_len); + snprintf(chap_i, sizeof(chap_i), "%d", id); + + response = login_new_response(request); + response_keys = keys_new(); + keys_add(response_keys, "CHAP_A", "5"); + keys_add(response_keys, "CHAP_I", chap_i); + keys_add(response_keys, "CHAP_C", chap_c); + free(chap_c); + keys_save(response_keys, response); + keys_delete(response_keys); + pdu_send(response); +} + +static struct pdu * +login_receive_chap_r(struct connection *conn, + struct auth_group *ag, const unsigned char id, const void *challenge, + const size_t challenge_len, const struct auth **cap) +{ + struct pdu *request; + struct 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 auth *auth; + int error; + + request = login_receive(conn, false); + request_keys = keys_new(); + keys_load(request_keys, request); + + chap_n = keys_find(request_keys, "CHAP_N"); + if (chap_n == NULL) { + login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU without CHAP_N"); + } + chap_r = keys_find(request_keys, "CHAP_R"); + if (chap_r == NULL) { + login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU without CHAP_R"); + } + error = login_hex2bin(chap_r, &response_bin, &response_bin_len); + if (error != 0) { + login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU with malformed CHAP_R"); + } + + /* + * Verify the response. + */ + assert(ag->ag_type == AG_TYPE_CHAP || + ag->ag_type == AG_TYPE_CHAP_MUTUAL); + auth = auth_find(ag, chap_n); + if (auth == NULL) { + login_send_error(request, 0x02, 0x01); + log_errx(1, "received CHAP Login with invalid user \"%s\"", + chap_n); + } + + assert(auth->a_secret != NULL); + assert(strlen(auth->a_secret) > 0); + login_compute_md5(id, auth->a_secret, challenge, + challenge_len, expected_response_bin, + sizeof(expected_response_bin)); + + if (memcmp(response_bin, expected_response_bin, + sizeof(expected_response_bin)) != 0) { + login_send_error(request, 0x02, 0x01); + log_errx(1, "CHAP authentication failed for user \"%s\"", + auth->a_user); + } + + keys_delete(request_keys); + free(response_bin); + + *cap = auth; + return (request); +} + +static void +login_send_chap_success(struct pdu *request, + const struct auth *auth) +{ + struct pdu *response; + struct 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 = login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + + /* + * Actually, one more thing: mutual authentication. + */ + request_keys = keys_new(); + keys_load(request_keys, request); + chap_i = keys_find(request_keys, "CHAP_I"); + chap_c = keys_find(request_keys, "CHAP_C"); + if (chap_i != NULL || chap_c != NULL) { + if (chap_i == NULL) { + login_send_error(request, 0x02, 0x07); + log_errx(1, "initiator requested target " + "authentication, but didn't send CHAP_I"); + } + if (chap_c == NULL) { + login_send_error(request, 0x02, 0x07); + log_errx(1, "initiator requested target " + "authentication, but didn't send CHAP_C"); + } + if (auth->a_auth_group->ag_type != AG_TYPE_CHAP_MUTUAL) { + login_send_error(request, 0x02, 0x01); + log_errx(1, "initiator requests target authentication " + "for user \"%s\", but mutual user/secret " + "is not set", auth->a_user); + } + + id = strtoul(chap_i, NULL, 10); + error = login_hex2bin(chap_c, &challenge, &challenge_len); + if (error != 0) { + 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\"", + auth->a_mutual_user); + login_compute_md5(id, auth->a_mutual_secret, challenge, + challenge_len, response_bin, sizeof(response_bin)); + + chap_r = login_bin2hex(response_bin, + sizeof(response_bin)); + response_keys = keys_new(); + keys_add(response_keys, "CHAP_N", auth->a_mutual_user); + keys_add(response_keys, "CHAP_R", chap_r); + free(chap_r); + keys_save(response_keys, response); + keys_delete(response_keys); + } else { + log_debugx("initiator did not request target authentication"); + } + + keys_delete(request_keys); + pdu_send(response); +} + +static void +login_chap(struct connection *conn, struct auth_group *ag) +{ + const struct auth *auth; + struct 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 = login_receive_chap_a(conn); + + /* + * Generate the challenge. + */ + rv = RAND_bytes(challenge_bin, sizeof(challenge_bin)); + if (rv != 1) { + 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) { + 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)); + login_send_chap_c(request, id, challenge_bin, + sizeof(challenge_bin)); + pdu_delete(request); + + /* + * Receive CHAP_N/CHAP_R PDU and authenticate. + */ + log_debugx("waiting for CHAP_N/CHAP_R"); + request = login_receive_chap_r(conn, ag, id, challenge_bin, + sizeof(challenge_bin), &auth); + + /* + * Yay, authentication succeeded! + */ + log_debugx("authentication succeeded for user \"%s\"; " + "transitioning to Negotiation phase", auth->a_user); + login_send_chap_success(request, auth); + pdu_delete(request); +} + +static void +login_negotiate_key(struct pdu *request, const char *name, + const char *value, bool skipped_security, struct keys *response_keys) +{ + int which, tmp; + struct connection *conn; + + conn = request->pdu_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) { + if (conn->conn_initiator_alias != NULL) + free(conn->conn_initiator_alias); + conn->conn_initiator_alias = checked_strdup(value); + } else if (strcmp(value, "Irrelevant") == 0) { + /* Ignore. */ + } else if (strcmp(name, "HeaderDigest") == 0) { + /* + * We don't handle digests for discovery sessions. + */ + if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { + log_debugx("discovery session; digests disabled"); + keys_add(response_keys, name, "None"); + return; + } + + which = login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("initiator prefers CRC32C " + "for header digest; we'll use it"); + conn->conn_header_digest = CONN_DIGEST_CRC32C; + keys_add(response_keys, name, "CRC32C"); + break; + case 2: + log_debugx("initiator prefers not to do " + "header digest; we'll comply"); + keys_add(response_keys, name, "None"); + break; + default: + log_warnx("initiator sent unrecognized " + "HeaderDigest value \"%s\"; will use None", value); + keys_add(response_keys, name, "None"); + break; + } + } else if (strcmp(name, "DataDigest") == 0) { + if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { + log_debugx("discovery session; digests disabled"); + keys_add(response_keys, name, "None"); + return; + } + + which = login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("initiator prefers CRC32C " + "for data digest; we'll use it"); + conn->conn_data_digest = CONN_DIGEST_CRC32C; + keys_add(response_keys, name, "CRC32C"); + break; + case 2: + log_debugx("initiator prefers not to do " + "data digest; we'll comply"); + keys_add(response_keys, name, "None"); + break; + default: + log_warnx("initiator sent unrecognized " + "DataDigest value \"%s\"; will use None", value); + keys_add(response_keys, name, "None"); + break; + } + } else if (strcmp(name, "MaxConnections") == 0) { + keys_add(response_keys, name, "1"); + } else if (strcmp(name, "InitialR2T") == 0) { + keys_add(response_keys, name, "Yes"); + } else if (strcmp(name, "ImmediateData") == 0) { + if (conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY) { + log_debugx("discovery session; ImmediateData irrelevant"); + keys_add(response_keys, name, "Irrelevant"); + } else { + if (strcmp(value, "Yes") == 0) { + conn->conn_immediate_data = true; + keys_add(response_keys, name, "Yes"); + } else { + conn->conn_immediate_data = false; + keys_add(response_keys, name, "No"); + } + } + } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + login_send_error(request, 0x02, 0x00); + log_errx(1, "received invalid " + "MaxRecvDataSegmentLength"); + } + if (tmp > MAX_DATA_SEGMENT_LENGTH) { + log_debugx("capping MaxDataSegmentLength from %d to %d", + tmp, MAX_DATA_SEGMENT_LENGTH); + tmp = MAX_DATA_SEGMENT_LENGTH; + } + conn->conn_max_data_segment_length = tmp; + keys_add_int(response_keys, name, tmp); + } else if (strcmp(name, "MaxBurstLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + login_send_error(request, 0x02, 0x00); + log_errx(1, "received invalid MaxBurstLength"); + } + if (tmp > MAX_BURST_LENGTH) { + log_debugx("capping MaxBurstLength from %d to %d", + tmp, MAX_BURST_LENGTH); + tmp = MAX_BURST_LENGTH; + } + conn->conn_max_burst_length = tmp; + keys_add(response_keys, name, value); + } else if (strcmp(name, "FirstBurstLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + login_send_error(request, 0x02, 0x00); + log_errx(1, "received invalid " + "FirstBurstLength"); + } + if (tmp > MAX_DATA_SEGMENT_LENGTH) { + log_debugx("capping FirstBurstLength from %d to %d", + tmp, MAX_DATA_SEGMENT_LENGTH); + tmp = MAX_DATA_SEGMENT_LENGTH; + } + /* + * We don't pass the value to the kernel; it only enforces + * hardcoded limit anyway. + */ + keys_add_int(response_keys, name, tmp); + } else if (strcmp(name, "DefaultTime2Wait") == 0) { + keys_add(response_keys, name, value); + } else if (strcmp(name, "DefaultTime2Retain") == 0) { + keys_add(response_keys, name, "0"); + } else if (strcmp(name, "MaxOutstandingR2T") == 0) { + keys_add(response_keys, name, "1"); + } else if (strcmp(name, "DataPDUInOrder") == 0) { + keys_add(response_keys, name, "Yes"); + } else if (strcmp(name, "DataSequenceInOrder") == 0) { + keys_add(response_keys, name, "Yes"); + } else if (strcmp(name, "ErrorRecoveryLevel") == 0) { + keys_add(response_keys, name, "0"); + } else if (strcmp(name, "OFMarker") == 0) { + keys_add(response_keys, name, "No"); + } else if (strcmp(name, "IFMarker") == 0) { + keys_add(response_keys, name, "No"); + } else { + log_debugx("unknown key \"%s\"; responding " + "with NotUnderstood", name); + keys_add(response_keys, name, "NotUnderstood"); + } +} + +static void +login_negotiate(struct connection *conn, struct pdu *request) +{ + struct pdu *response; + struct iscsi_bhs_login_response *bhslr2; + struct keys *request_keys, *response_keys; + int i; + bool skipped_security; + + if (request == NULL) { + log_debugx("beginning parameter negotiation; " + "waiting for Login PDU"); + request = login_receive(conn, false); + skipped_security = false; + } else + skipped_security = true; + + request_keys = keys_new(); + keys_load(request_keys, request); + + response = login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + bhslr2->bhslr_tsih = htons(0xbadd); + login_set_csg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + login_set_nsg(response, BHSLR_STAGE_FULL_FEATURE_PHASE); + response_keys = keys_new(); + for (i = 0; i < KEYS_MAX; i++) { + if (request_keys->keys_names[i] == NULL) + break; + + login_negotiate_key(request, request_keys->keys_names[i], + request_keys->keys_values[i], skipped_security, + response_keys); + } + + log_debugx("parameter negotiation done; " + "transitioning to Full Feature phase"); + + keys_save(response_keys, response); + pdu_send(response); + pdu_delete(response); + keys_delete(response_keys); + pdu_delete(request); + keys_delete(request_keys); +} + +void +login(struct connection *conn) +{ + struct pdu *request, *response; + struct iscsi_bhs_login_request *bhslr; + struct iscsi_bhs_login_response *bhslr2; + struct keys *request_keys, *response_keys; + struct auth_group *ag; + const char *initiator_name, *initiator_alias, *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 = login_receive(conn, true); + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + if (bhslr->bhslr_tsih != 0) { + 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 = keys_new(); + keys_load(request_keys, request); + + assert(conn->conn_initiator_name == NULL); + initiator_name = keys_find(request_keys, "InitiatorName"); + if (initiator_name == NULL) { + login_send_error(request, 0x02, 0x07); + log_errx(1, "received Login PDU without InitiatorName"); + } + if (valid_iscsi_name(initiator_name) == false) { + login_send_error(request, 0x02, 0x00); + log_errx(1, "received Login PDU with invalid InitiatorName"); + } + conn->conn_initiator_name = checked_strdup(initiator_name); + log_set_peer_name(conn->conn_initiator_name); + /* + * XXX: This doesn't work (does nothing) because of Capsicum. + */ + setproctitle("%s (%s)", conn->conn_initiator_addr, conn->conn_initiator_name); + + initiator_alias = keys_find(request_keys, "InitiatorAlias"); + if (initiator_alias != NULL) + conn->conn_initiator_alias = checked_strdup(initiator_alias); + + assert(conn->conn_session_type == CONN_SESSION_TYPE_NONE); + session_type = keys_find(request_keys, "SessionType"); + if (session_type != NULL) { + if (strcmp(session_type, "Normal") == 0) { + conn->conn_session_type = CONN_SESSION_TYPE_NORMAL; + } else if (strcmp(session_type, "Discovery") == 0) { + conn->conn_session_type = CONN_SESSION_TYPE_DISCOVERY; + } else { + login_send_error(request, 0x02, 0x00); + log_errx(1, "received Login PDU with invalid " + "SessionType \"%s\"", session_type); + } + } else + conn->conn_session_type = CONN_SESSION_TYPE_NORMAL; + + assert(conn->conn_target == NULL); + if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { + target_name = keys_find(request_keys, "TargetName"); + if (target_name == NULL) { + login_send_error(request, 0x02, 0x07); + log_errx(1, "received Login PDU without TargetName"); + } + + conn->conn_target = + target_find(conn->conn_portal->p_portal_group->pg_conf, + target_name); + if (conn->conn_target == NULL) { + 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 (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { + ag = conn->conn_target->t_auth_group; + if (ag->ag_name != NULL) { + log_debugx("initiator requests to connect " + "to target \"%s\"; auth-group \"%s\"", + conn->conn_target->t_iqn, + conn->conn_target->t_auth_group->ag_name); + } else { + log_debugx("initiator requests to connect " + "to target \"%s\"", conn->conn_target->t_iqn); + } + } else { + assert(conn->conn_session_type == CONN_SESSION_TYPE_DISCOVERY); + ag = conn->conn_portal->p_portal_group->pg_discovery_auth_group; + if (ag->ag_name != NULL) { + log_debugx("initiator requests " + "discovery session; auth-group \"%s\"", ag->ag_name); + } else { + log_debugx("initiator requests discovery session"); + } + } + + /* + * Let's see if the initiator intends to do any kind of authentication + * at all. + */ + if (login_csg(request) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { + if (ag->ag_type != AG_TYPE_NO_AUTHENTICATION) { + login_send_error(request, 0x02, 0x01); + log_errx(1, "initiator skipped the authentication " + "phase, but authentication is required"); + } + + keys_delete(request_keys); + + log_debugx("initiator skipped the authentication phase, " + "and we don't need it; proceeding with negotiation"); + login_negotiate(conn, request); + return; + } + + if (ag->ag_type == AG_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 = login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + login_set_nsg(response, + BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + response_keys = keys_new(); + /* + * Required by Linux initiator. + */ + auth_method = keys_find(request_keys, "AuthMethod"); + if (auth_method != NULL && + login_list_contains(auth_method, "None")) + keys_add(response_keys, "AuthMethod", "None"); + + if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { + if (conn->conn_target->t_alias != NULL) + keys_add(response_keys, + "TargetAlias", conn->conn_target->t_alias); + rv = asprintf(&portal_group_tag, "%d", + conn->conn_portal->p_portal_group->pg_tag); + if (rv <= 0) + log_err(1, "asprintf"); + keys_add(response_keys, + "TargetPortalGroupTag", portal_group_tag); + free(portal_group_tag); + } + keys_save(response_keys, response); + pdu_send(response); + pdu_delete(response); + keys_delete(response_keys); + pdu_delete(request); + keys_delete(request_keys); + + login_negotiate(conn, NULL); + return; + } + + log_debugx("CHAP authentication required"); + + auth_method = keys_find(request_keys, "AuthMethod"); + if (auth_method == NULL) { + 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 (login_list_contains(auth_method, "CHAP") == 0) { + login_send_error(request, 0x02, 0x01); + log_errx(1, "initiator requests unsupported AuthMethod \"%s\" " + "instead of \"CHAP\"", auth_method); + } + + response = login_new_response(request); + + response_keys = keys_new(); + keys_add(response_keys, "AuthMethod", "CHAP"); + if (conn->conn_session_type == CONN_SESSION_TYPE_NORMAL) { + rv = asprintf(&portal_group_tag, "%d", + conn->conn_portal->p_portal_group->pg_tag); + if (rv <= 0) + log_err(1, "asprintf"); + keys_add(response_keys, + "TargetPortalGroupTag", portal_group_tag); + free(portal_group_tag); + if (conn->conn_target->t_alias != NULL) + keys_add(response_keys, + "TargetAlias", conn->conn_target->t_alias); + } + keys_save(response_keys, response); + + pdu_send(response); + pdu_delete(response); + keys_delete(response_keys); + pdu_delete(request); + keys_delete(request_keys); + + login_chap(conn, ag); + + login_negotiate(conn, NULL); +} diff -urNp p4/freebsd/src/usr.sbin/ctld/parse.y p4/iscsi/usr.sbin/ctld/parse.y --- p4/freebsd/src/usr.sbin/ctld/parse.y 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/parse.y 2013-07-24 12:25:00.000000000 +0200 @@ -0,0 +1,604 @@ +%{ +/*- + * 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 conf *conf = NULL; +static struct auth_group *auth_group = NULL; +static struct portal_group *portal_group = NULL; +static struct target *target = NULL; +static struct lun *lun = NULL; + +extern void yyerror(const char *); +extern int yylex(void); +extern void yyrestart(FILE *); + +%} + +%token ALIAS AUTH_GROUP BACKEND BLOCKSIZE CHAP CHAP_MUTUAL CLOSING_BRACKET +%token DEBUG DEVICE_ID DISCOVERY_AUTH_GROUP LISTEN LISTEN_ISER LUN NUM +%token OPENING_BRACKET 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->conf_debug = $2; + } + ; + +pidfile_statement: PIDFILE STR + { + if (conf->conf_pidfile_path != NULL) { + log_warnx("pidfile specified more than once"); + free($2); + return (1); + } + conf->conf_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 = 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 auth *ca; + + ca = 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 auth *ca; + + ca = 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 = 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_listen_iser + ; + +portal_group_discovery_auth_group: DISCOVERY_AUTH_GROUP STR + { + if (portal_group->pg_discovery_auth_group != NULL) { + log_warnx("discovery-auth-group for portal-group " + "\"%s\" specified more than once", + portal_group->pg_name); + return (1); + } + portal_group->pg_discovery_auth_group = + auth_group_find(conf, $2); + if (portal_group->pg_discovery_auth_group == NULL) { + log_warnx("unknown discovery-auth-group \"%s\" " + "for portal-group \"%s\"", + $2, portal_group->pg_name); + return (1); + } + free($2); + } + ; + +portal_group_listen: LISTEN STR + { + int error; + + error = portal_group_add_listen(portal_group, $2, false); + free($2); + if (error != 0) + return (1); + } + ; + +portal_group_listen_iser: LISTEN_ISER STR + { + int error; + + error = portal_group_add_listen(portal_group, $2, true); + free($2); + if (error != 0) + return (1); + } + ; + +target_statement: TARGET target_iqn + OPENING_BRACKET target_entries CLOSING_BRACKET + { + target = NULL; + } + ; + +target_iqn: STR + { + target = 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->t_alias != NULL) { + log_warnx("alias for target \"%s\" " + "specified more than once", target->t_iqn); + return (1); + } + target->t_alias = $2; + } + ; + +auth_group_statement: AUTH_GROUP STR + { + if (target->t_auth_group != NULL) { + if (target->t_auth_group->ag_name != NULL) + log_warnx("auth-group for target \"%s\" " + "specified more than once", target->t_iqn); + else + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->t_iqn); + return (1); + } + target->t_auth_group = auth_group_find(conf, $2); + if (target->t_auth_group == NULL) { + log_warnx("unknown auth-group \"%s\" for target " + "\"%s\"", $2, target->t_iqn); + return (1); + } + free($2); + } + ; + +chap_statement: CHAP STR STR + { + const struct auth *ca; + + if (target->t_auth_group != NULL) { + if (target->t_auth_group->ag_name != NULL) { + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->t_iqn); + free($2); + free($3); + return (1); + } + } else { + target->t_auth_group = auth_group_new(conf, NULL); + if (target->t_auth_group == NULL) { + free($2); + free($3); + return (1); + } + target->t_auth_group->ag_target = target; + } + ca = auth_new_chap(target->t_auth_group, $2, $3); + free($2); + free($3); + if (ca == NULL) + return (1); + } + ; + +chap_mutual_statement: CHAP_MUTUAL STR STR STR STR + { + const struct auth *ca; + + if (target->t_auth_group != NULL) { + if (target->t_auth_group->ag_name != NULL) { + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->t_iqn); + free($2); + free($3); + free($4); + free($5); + return (1); + } + } else { + target->t_auth_group = auth_group_new(conf, NULL); + if (target->t_auth_group == NULL) { + free($2); + free($3); + free($4); + free($5); + return (1); + } + target->t_auth_group->ag_target = target; + } + ca = auth_new_chap_mutual(target->t_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->t_portal_group != NULL) { + log_warnx("portal-group for target \"%s\" " + "specified more than once", target->t_iqn); + free($2); + return (1); + } + target->t_portal_group = portal_group_find(conf, $2); + if (target->t_portal_group == NULL) { + log_warnx("unknown portal-group \"%s\" for target " + "\"%s\"", $2, target->t_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 = 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->l_backend != NULL) { + log_warnx("backend for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + free($2); + return (1); + } + lun_set_backend(lun, $2); + free($2); + } + ; + +blocksize_statement: BLOCKSIZE NUM + { + if (lun->l_blocksize != 0) { + log_warnx("blocksize for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + return (1); + } + lun_set_blocksize(lun, $2); + } + ; + +device_id_statement: DEVICE_ID STR + { + if (lun->l_device_id != NULL) { + log_warnx("device_id for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + free($2); + return (1); + } + lun_set_device_id(lun, $2); + free($2); + } + ; + +option_statement: OPTION STR STR + { + struct lun_option *clo; + + clo = lun_option_new(lun, $2, $3); + free($2); + free($3); + if (clo == NULL) + return (1); + } + ; + +path_statement: PATH STR + { + if (lun->l_path != NULL) { + log_warnx("path for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + free($2); + return (1); + } + lun_set_path(lun, $2); + free($2); + } + ; + +serial_statement: SERIAL STR + { + if (lun->l_serial != NULL) { + log_warnx("serial for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + free($2); + return (1); + } + lun_set_serial(lun, $2); + free($2); + } + ; + +size_statement: SIZE NUM + { + if (lun->l_size != 0) { + log_warnx("size for lun %d, target \"%s\" " + "specified more than once", + lun->l_lun, target->t_iqn); + return (1); + } + 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 +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 conf * +conf_new_from_file(const char *path) +{ + struct auth_group *ag; + struct portal_group *pg; + int error; + + log_debugx("obtaining configuration from %s", path); + + conf = conf_new(); + + ag = auth_group_new(conf, "no-authentication"); + ag->ag_type = AG_TYPE_NO_AUTHENTICATION; + + /* + * Here, the type doesn't really matter, as the group doesn't contain + * any entries and thus will always deny access. + */ + ag = auth_group_new(conf, "no-access"); + ag->ag_type = AG_TYPE_CHAP; + + pg = portal_group_new(conf, "default"); + portal_group_add_listen(pg, "0.0.0.0:3260", false); + portal_group_add_listen(pg, "[::]:3260", false); + + yyin = fopen(path, "r"); + if (yyin == NULL) { + log_warn("unable to open configuration file %s", path); + conf_delete(conf); + return (NULL); + } + check_perms(path); + lineno = 0; + yyrestart(yyin); + error = yyparse(); + auth_group = NULL; + portal_group = NULL; + target = NULL; + lun = NULL; + fclose(yyin); + if (error != 0) { + conf_delete(conf); + return (NULL); + } + + error = conf_verify(conf); + if (error != 0) { + conf_delete(conf); + return (NULL); + } + + return (conf); +} diff -urNp p4/freebsd/src/usr.sbin/ctld/pdu.c p4/iscsi/usr.sbin/ctld/pdu.c --- p4/freebsd/src/usr.sbin/ctld/pdu.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/pdu.c 2013-08-20 11:52:23.000000000 +0200 @@ -0,0 +1,241 @@ +/*- + * 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_proto.h" + +#ifdef ICL_KERNEL_PROXY +#include +#endif + +static int +pdu_ahs_length(const struct pdu *pdu) +{ + + return (pdu->pdu_bhs->bhs_total_ahs_len * 4); +} + +static int +pdu_data_segment_length(const struct pdu *pdu) +{ + uint32_t len = 0; + + len += pdu->pdu_bhs->bhs_data_segment_len[0]; + len <<= 8; + len += pdu->pdu_bhs->bhs_data_segment_len[1]; + len <<= 8; + len += pdu->pdu_bhs->bhs_data_segment_len[2]; + + return (len); +} + +static void +pdu_set_data_segment_length(struct pdu *pdu, uint32_t len) +{ + + pdu->pdu_bhs->bhs_data_segment_len[2] = len; + pdu->pdu_bhs->bhs_data_segment_len[1] = len >> 8; + pdu->pdu_bhs->bhs_data_segment_len[0] = len >> 16; +} + +struct pdu * +pdu_new(struct connection *conn) +{ + struct pdu *pdu; + + pdu = calloc(sizeof(*pdu), 1); + if (pdu == NULL) + log_err(1, "calloc"); + + pdu->pdu_bhs = calloc(sizeof(*pdu->pdu_bhs), 1); + if (pdu->pdu_bhs == NULL) + log_err(1, "calloc"); + + pdu->pdu_connection = conn; + + return (pdu); +} + +struct pdu * +pdu_new_response(struct pdu *request) +{ + + return (pdu_new(request->pdu_connection)); +} + +#ifdef ICL_KERNEL_PROXY + +void +pdu_receive(struct pdu *pdu) +{ + size_t len; + + kernel_receive(pdu); + + len = pdu_ahs_length(pdu); + if (len > 0) + log_errx(1, "protocol error: non-empty AHS"); + + len = pdu_data_segment_length(pdu); + assert(len <= MAX_DATA_SEGMENT_LENGTH); + pdu->pdu_data_len = len; +} + +void +pdu_send(struct pdu *pdu) +{ + + pdu_set_data_segment_length(pdu, pdu->pdu_data_len); + kernel_send(pdu); +} + +#else /* !ICL_KERNEL_PROXY */ + +static size_t +pdu_padding(const struct pdu *pdu) +{ + + if ((pdu->pdu_data_len % 4) != 0) + return (4 - (pdu->pdu_data_len % 4)); + + return (0); +} + +static void +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 +pdu_receive(struct pdu *pdu) +{ + size_t len, padding; + char dummy[4]; + + pdu_read(pdu->pdu_connection->conn_socket, + (char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs)); + + len = pdu_ahs_length(pdu); + if (len > 0) + log_errx(1, "protocol error: non-empty AHS"); + + len = pdu_data_segment_length(pdu); + if (len > 0) { + if (len > MAX_DATA_SEGMENT_LENGTH) { + log_errx(1, "protocol error: received PDU " + "with DataSegmentLength exceeding %d", + MAX_DATA_SEGMENT_LENGTH); + } + + pdu->pdu_data_len = len; + pdu->pdu_data = malloc(len); + if (pdu->pdu_data == NULL) + log_err(1, "malloc"); + + pdu_read(pdu->pdu_connection->conn_socket, + (char *)pdu->pdu_data, pdu->pdu_data_len); + + padding = pdu_padding(pdu); + if (padding != 0) { + assert(padding < sizeof(dummy)); + pdu_read(pdu->pdu_connection->conn_socket, + (char *)dummy, padding); + } + } +} + +void +pdu_send(struct pdu *pdu) +{ + ssize_t ret, total_len; + size_t padding; + uint32_t zero = 0; + struct iovec iov[3]; + int iovcnt; + + pdu_set_data_segment_length(pdu, pdu->pdu_data_len); + iov[0].iov_base = pdu->pdu_bhs; + iov[0].iov_len = sizeof(*pdu->pdu_bhs); + total_len = iov[0].iov_len; + iovcnt = 1; + + if (pdu->pdu_data_len > 0) { + iov[1].iov_base = pdu->pdu_data; + iov[1].iov_len = pdu->pdu_data_len; + total_len += iov[1].iov_len; + iovcnt = 2; + + padding = pdu_padding(pdu); + 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(pdu->pdu_connection->conn_socket, iov, iovcnt); + if (ret < 0) + log_err(1, "writev"); + if (ret != total_len) + log_errx(1, "short write"); +} + +#endif /* !ICL_KERNEL_PROXY */ + +void +pdu_delete(struct pdu *pdu) +{ + + free(pdu->pdu_data); + free(pdu->pdu_bhs); + free(pdu); +} diff -urNp p4/freebsd/src/usr.sbin/ctld/token.l p4/iscsi/usr.sbin/ctld/token.l --- p4/freebsd/src/usr.sbin/ctld/token.l 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/ctld/token.l 2013-07-24 12:25:00.000000000 +0200 @@ -0,0 +1,83 @@ +%{ +/*- + * 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 lineno; + +#define YY_DECL int yylex(void) +extern int yylex(void); + +%} + +%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; } +listen-iser { return LISTEN_ISER; } +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; } +\{ { return OPENING_BRACKET; } +\} { return CLOSING_BRACKET; } +#.*$ /* ignore comments */; +\n { lineno++; } +[ \t]+ /* ignore whitespace */; +%% diff -urNp p4/freebsd/src/usr.sbin/iscsid/Makefile p4/iscsi/usr.sbin/iscsid/Makefile --- p4/freebsd/src/usr.sbin/iscsid/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/iscsid/Makefile 2013-08-19 12:30:04.000000000 +0200 @@ -0,0 +1,16 @@ +# $FreeBSD$ + +PROG= iscsid +SRCS= discovery.c iscsid.c keys.c log.c login.c pdu.c +CFLAGS+= -I${.CURDIR} +CFLAGS+= -I${.CURDIR}/../../sys/cam +CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi +#CFLAGS+= -DICL_KERNEL_PROXY +MAN= iscsid.8 + +DPADD= ${LIBUTIL} +LDADD= -lcrypto -lssl -lutil + +WARNS= 6 + +.include diff -urNp p4/freebsd/src/usr.sbin/iscsid/discovery.c p4/iscsi/usr.sbin/iscsid/discovery.c --- p4/freebsd/src/usr.sbin/iscsid/discovery.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/iscsid/discovery.c 2013-08-20 11:52:23.000000000 +0200 @@ -0,0 +1,226 @@ +/*- + * 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 "iscsid.h" +#include "iscsi_proto.h" + +static struct pdu * +text_receive(struct connection *conn) +{ + struct pdu *response; + struct iscsi_bhs_text_response *bhstr; + + response = pdu_new(conn); + pdu_receive(response); + if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_TEXT_RESPONSE) + log_errx(1, "protocol error: received invalid opcode 0x%x", + response->pdu_bhs->bhs_opcode); + bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs; +#if 0 + if ((bhstr->bhstr_flags & 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 (response->pdu_data_len == 0) + log_errx(1, "received Text PDU with empty data segment"); + if (ntohl(bhstr->bhstr_statsn) != conn->conn_statsn + 1) { + log_errx(1, "received Text PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhstr->bhstr_statsn), + conn->conn_statsn + 1); + } + conn->conn_statsn = ntohl(bhstr->bhstr_statsn); + + return (response); +} + +static struct pdu * +text_new_request(struct connection *conn) +{ + struct pdu *request; + struct iscsi_bhs_text_request *bhstr; + + request = pdu_new(conn); + bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs; + bhstr->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_REQUEST | + ISCSI_BHS_OPCODE_IMMEDIATE; + bhstr->bhstr_flags = BHSTR_FLAGS_FINAL; + bhstr->bhstr_initiator_task_tag = 0; + bhstr->bhstr_target_transfer_tag = 0xffffffff; + + bhstr->bhstr_initiator_task_tag = 0; /* XXX */ + bhstr->bhstr_cmdsn = 0; /* XXX */ + bhstr->bhstr_expstatsn = htonl(conn->conn_statsn + 1); + + return (request); +} + +static struct pdu * +logout_receive(struct connection *conn) +{ + struct pdu *response; + struct iscsi_bhs_logout_response *bhslr; + + response = pdu_new(conn); + pdu_receive(response); + if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGOUT_RESPONSE) + log_errx(1, "protocol error: received invalid opcode 0x%x", + response->pdu_bhs->bhs_opcode); + bhslr = (struct iscsi_bhs_logout_response *)response->pdu_bhs; + if (ntohs(bhslr->bhslr_response) != BHSLR_RESPONSE_CLOSED_SUCCESSFULLY) + log_warnx("received Logout Response with reason %d", + ntohs(bhslr->bhslr_response)); + if (ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) { + log_errx(1, "received Logout PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_statsn), + conn->conn_statsn + 1); + } + conn->conn_statsn = ntohl(bhslr->bhslr_statsn); + + return (response); +} + +static struct pdu * +logout_new_request(struct connection *conn) +{ + struct pdu *request; + struct iscsi_bhs_logout_request *bhslr; + + request = pdu_new(conn); + bhslr = (struct iscsi_bhs_logout_request *)request->pdu_bhs; + bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_REQUEST | + ISCSI_BHS_OPCODE_IMMEDIATE; + bhslr->bhslr_reason = BHSLR_REASON_CLOSE_SESSION; + bhslr->bhslr_reason |= 0x80; + bhslr->bhslr_initiator_task_tag = 0; /* XXX */ + bhslr->bhslr_cmdsn = 0; /* XXX */ + bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1); + + return (request); +} + +static void +kernel_add(const struct connection *conn, const char *target) +{ + struct iscsi_session_add isa; + int error; + + memset(&isa, 0, sizeof(isa)); + memcpy(&isa.isa_desc, &conn->conn_desc, sizeof(isa)); + strlcpy(isa.isa_desc.isd_target, target, + sizeof(isa.isa_desc.isd_target)); + isa.isa_desc.isd_discovery = 0; + error = ioctl(conn->conn_iscsi_fd, ISCSISADD, &isa); + if (error != 0) + log_warn("failed to add %s: ISCSISADD", target); + + /* + * XXX: Perhaps we should have a mechanism to retry it? + */ +} + +static void +kernel_remove(const struct connection *conn) +{ + struct iscsi_session_remove isr; + int error; + + memset(&isr, 0, sizeof(isr)); + isr.isr_desc.isd_id = conn->conn_desc.isd_id; + error = ioctl(conn->conn_iscsi_fd, ISCSISREMOVE, &isr); + if (error != 0) + log_warn("ISCSISREMOVE"); +} + +void +discovery(struct connection *conn) +{ + struct pdu *request, *response; + struct keys *request_keys, *response_keys; + int i; + + log_debugx("beginning discovery session"); + request = text_new_request(conn); + request_keys = keys_new(); + keys_add(request_keys, "SendTargets", "All"); + keys_save(request_keys, request); + keys_delete(request_keys); + request_keys = NULL; + pdu_send(request); + pdu_delete(request); + request = NULL; + + log_debugx("waiting for Text Response"); + response = text_receive(conn); + response_keys = keys_new(); + keys_load(response_keys, response); + for (i = 0; i < KEYS_MAX; i++) { + if (response_keys->keys_names[i] == NULL) + break; + + if (strcmp(response_keys->keys_names[i], "TargetName") != 0) + continue; + + log_debugx("adding target %s", response_keys->keys_values[i]); + /* + * XXX: Validate the target name? + */ + kernel_add(conn, response_keys->keys_values[i]); + } + keys_delete(response_keys); + pdu_delete(response); + + log_debugx("discovery done; logging out"); + request = logout_new_request(conn); + pdu_send(request); + request = NULL; + + log_debugx("waiting for Logout Response"); + response = logout_receive(conn); + pdu_delete(response); + + log_debugx("removing temporary discovery session"); + kernel_remove(conn); + + log_debugx("discovery session done"); +} diff -urNp p4/freebsd/src/usr.sbin/iscsid/iscsid.8 p4/iscsi/usr.sbin/iscsid/iscsid.8 --- p4/freebsd/src/usr.sbin/iscsid/iscsid.8 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/iscsid/iscsid.8 2013-07-23 23:48:07.000000000 +0200 @@ -0,0 +1,96 @@ +.\" 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 ISCSID 8 +.Os +.Sh NAME +.Nm iscsid +.Nd iSCSI initiator daemon +.Sh SYNOPSIS +.Nm +.Op Fl d +.Sh DESCRIPTION +The +.Nm +daemon is responsible for performing the Login Phase of iSCSI connections, +as well as performing SendTargets discovery. +.Pp +.Pp +Upon startup, the +.Nm +daemon opens the iSCSI initiator device file and waits for kernel requests. +It doesn't use any configuration file; all the information it needs it gets +from the kernel. +.Pp +When the +.Nm +damon is not running, already established iSCSI connections continue +to work. +However, establishing new connections, or recovering existing ones in case +of connection error, is not possible. +.Pp +The following options are available: +.Bl -tag -width ".Fl P Ar pidfile" +.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 initiator. +.It Fl P Ar pidfile +Specify alternative location of a file where main process PID will be stored. +The default location is /var/run/iscsid.pid. +.El +.Sh FILES +.Bl -tag -width ".Pa /var/run/iscsid.pid" -compact +.It Pa /dev/iscsi +The iSCSI initiator device file. +.It Pa /var/run/iscsid.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 iscsictl 8 +.Sh HISTORY +The +.Nm +command appeared in +.Fx 10.0 . +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff -urNp p4/freebsd/src/usr.sbin/iscsid/iscsid.c p4/iscsi/usr.sbin/iscsid/iscsid.c --- p4/freebsd/src/usr.sbin/iscsid/iscsid.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/iscsid/iscsid.c 2013-08-18 13:07:26.000000000 +0200 @@ -0,0 +1,449 @@ +/*- + * 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 +#include + +#include + +#include "iscsid.h" + +static void +usage(void) +{ + + fprintf(stderr, "usage: iscsid [-d][-P pidfile]\n"); + exit(1); +} + +char * +checked_strdup(const char *s) +{ + char *c; + + c = strdup(s); + if (c == NULL) + log_err(1, "strdup"); + return (c); +} + +static int +resolve_addr(const char *address, struct addrinfo **ai) +{ + struct addrinfo hints; + char *arg, *addr, *ch; + const char *port; + int error, colons = 0; + + arg = checked_strdup(address); + + if (arg[0] == '\0') { + log_warnx("empty address"); + return (1); + } + if (arg[0] == '[') { + /* + * IPv6 address in square brackets, perhaps with port. + */ + arg++; + addr = strsep(&arg, "]"); + if (arg == NULL) { + log_warnx("invalid address %s", address); + return (1); + } + if (arg[0] == '\0') { + port = "3260"; + } else if (arg[0] == ':') { + port = arg + 1; + } else { + log_warnx("invalid address %s", address); + 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, ai); + if (error != 0) { + log_warnx("getaddrinfo for %s failed: %s", + address, gai_strerror(error)); + return (1); + } + + return (0); +} + +static struct connection * +connection_new(const struct iscsi_session_desc *desc, int iscsi_fd) +{ + struct connection *conn; + struct addrinfo *from_ai, *to_ai; + const char *from_addr, *to_addr; +#ifdef ICL_KERNEL_PROXY + struct iscsi_daemon_connect *idc; +#endif + int error; + + conn = calloc(1, sizeof(*conn)); + if (conn == NULL) + log_err(1, "calloc"); + + /* + * Default values, from RFC 3720, section 12. + */ + conn->conn_header_digest = CONN_DIGEST_NONE; + conn->conn_data_digest = CONN_DIGEST_NONE; + conn->conn_initial_r2t = true; + conn->conn_immediate_data = true; + conn->conn_max_data_segment_length = 8192; + conn->conn_max_burst_length = 262144; + conn->conn_first_burst_length = 65536; + + /* + * XXX: Should we sanitize this somehow? + */ + memcpy(&conn->conn_desc, desc, sizeof(conn->conn_desc)); + + from_addr = conn->conn_desc.isd_initiator_addr; + to_addr = conn->conn_desc.isd_target_addr; + + if (from_addr[0] != '\0') { + error = resolve_addr(from_addr, &from_ai); + if (error != 0) + log_errx(1, "failed to resolve initiator address %s", + from_addr); + } else { + from_ai = NULL; + } + + error = resolve_addr(to_addr, &to_ai); + if (error != 0) + log_errx(1, "failed to resolve target address %s", to_addr); + + conn->conn_iscsi_fd = iscsi_fd; + +#ifdef ICL_KERNEL_PROXY + + idc = calloc(1, sizeof(*idc)); + if (idc == NULL) + log_err(1, "calloc"); + + idc->idc_session_id = conn->conn_desc.isd_id; + if (conn->conn_desc.isd_iser) + idc->idc_iser = 1; + idc->idc_domain = to_ai->ai_family; + idc->idc_socktype = to_ai->ai_socktype; + idc->idc_protocol = to_ai->ai_protocol; + if (from_ai != NULL) { + idc->idc_from_addr = from_ai->ai_addr; + idc->idc_from_addrlen = from_ai->ai_addrlen; + } + idc->idc_to_addr = to_ai->ai_addr; + idc->idc_to_addrlen = to_ai->ai_addrlen; + + log_debugx("connecting to %s using ICL kernel proxy", to_addr); + error = ioctl(iscsi_fd, ISCSIDCONNECT, idc); + if (error != 0) + log_err(1, "failed to connect to %s using ICL kernel proxy", + to_addr); + +#else /* !ICL_KERNEL_PROXY */ + + if (conn->conn_desc.isd_iser) + log_errx(1, "iscsid(8) compiled without ICL_KERNEL_PROXY " + "does not support iSER"); + + conn->conn_socket = socket(to_ai->ai_family, to_ai->ai_socktype, + to_ai->ai_protocol); + if (conn->conn_socket < 0) + log_err(1, "failed to create socket for %s", from_addr); + if (from_ai != NULL) { + error = bind(conn->conn_socket, from_ai->ai_addr, + from_ai->ai_addrlen); + if (error != 0) + log_err(1, "failed to bind to %s", from_addr); + } + log_debugx("connecting to %s", to_addr); + error = connect(conn->conn_socket, to_ai->ai_addr, to_ai->ai_addrlen); + if (error != 0) + log_err(1, "failed to connect to %s", to_addr); + +#endif /* !ICL_KERNEL_PROXY */ + + return (conn); +} + +static void +handoff(struct connection *conn) +{ + struct iscsi_daemon_handoff *idh; + int error; + + log_debugx("handing off connection to the kernel"); + + idh = calloc(1, sizeof(*idh)); + if (idh == NULL) + log_err(1, "calloc"); + idh->idh_session_id = conn->conn_desc.isd_id; +#ifndef ICL_KERNEL_PROXY + idh->idh_socket = conn->conn_socket; +#endif + strlcpy(idh->idh_target_alias, conn->conn_desc.isd_target_alias, + sizeof(idh->idh_target_alias)); + memcpy(idh->idh_isid, conn->conn_isid, sizeof(idh->idh_isid)); + idh->idh_statsn = conn->conn_statsn; + idh->idh_header_digest = conn->conn_header_digest; + idh->idh_data_digest = conn->conn_data_digest; + idh->idh_initial_r2t = conn->conn_initial_r2t; + idh->idh_immediate_data = conn->conn_immediate_data; + idh->idh_max_data_segment_length = conn->conn_max_data_segment_length; + idh->idh_max_burst_length = conn->conn_max_burst_length; + idh->idh_first_burst_length = conn->conn_first_burst_length; + + error = ioctl(conn->conn_iscsi_fd, ISCSIDHANDOFF, idh); + if (error != 0) + log_err(1, "ISCSIDHANDOFF"); +} + +/* + * XXX: I CANT INTO LATIN + */ +static void +capsicate(struct connection *conn) +{ + int error; +#ifdef ICL_KERNEL_PROXY + const unsigned long cmds[] = { ISCSIDCONNECT, ISCSIDSEND, ISCSIDRECEIVE, + ISCSIDHANDOFF, ISCSISADD, ISCSISREMOVE }; +#else + const unsigned long cmds[] = { ISCSIDHANDOFF, ISCSISADD, ISCSISREMOVE }; +#endif + + error = cap_rights_limit(conn->conn_iscsi_fd, CAP_IOCTL); + if (error != 0 && errno != ENOSYS) + log_err(1, "cap_rights_limit"); + + error = cap_ioctls_limit(conn->conn_iscsi_fd, cmds, + sizeof(cmds) / sizeof(cmds[0])); + if (error != 0 && errno != ENOSYS) + log_err(1, "cap_ioctls_limit"); + + error = cap_enter(); + if (error != 0 && errno != ENOSYS) + log_err(1, "cap_enter"); + + if (cap_sandboxed()) + log_debugx("Capsicum capability mode enabled"); + else + log_warnx("Capsicum capability mode not supported"); +} + +static void +handle_request(int iscsi_fd, struct iscsi_daemon_request *request) +{ + struct connection *conn; + + log_set_peer_addr(request->idr_desc.isd_target_addr); + if (request->idr_desc.isd_target[0] != '\0') { + log_set_peer_name(request->idr_desc.isd_target); + setproctitle("%s (%s)", request->idr_desc.isd_target_addr, request->idr_desc.isd_target); + } else { + setproctitle("%s", request->idr_desc.isd_target_addr); + } + +#if 0 + log_debugx("got request 0x%x, id %d, from %s (%s) to %s (%s), " + "auth %s %s %s %s, fd %d", request->idr_cmd, + request->idr_desc.isd_id, request->idr_desc.isd_initiator, + request->idr_desc.isd_initiator_addr, request->idr_desc.isd_target, + request->idr_desc.isd_target_addr, request->idr_desc.isd_user, + request->idr_desc.isd_secret, request->idr_desc.isd_mutual_user, + request->idr_desc.isd_mutual_secret, iscsi_fd); +#endif + + if (request->idr_cmd != ISCSI_DAEMON_CMD_LOGIN) + log_errx(1, "received unsupported cmd 0x%x", request->idr_cmd); + + conn = connection_new(&request->idr_desc, iscsi_fd); + capsicate(conn); + login(conn); + if (conn->conn_desc.isd_discovery != 0) + discovery(conn); + else + handoff(conn); + + log_debugx("nothing more to do; exiting"); + exit (0); +} + +static void +register_signals(void) +{ + struct sigaction sa; + int error; + + bzero(&sa, sizeof(sa)); + sigfillset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + error = sigaction(SIGCHLD, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); +} + +int +main(int argc, char **argv) +{ + int debug = 0, error, ch, iscsi_fd, retval, saved_errno; + bool dont_daemonize = false; + struct pidfh *pidfh; + pid_t pid, otherpid; + const char *pidfile_path = DEFAULT_PIDFILE; + struct iscsi_daemon_request *request; + + while ((ch = getopt(argc, argv, "dP:")) != -1) { + switch (ch) { + case 'd': + dont_daemonize = true; + debug++; + break; + case 'P': + pidfile_path = optarg; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + if (argc != 0) + usage(); + + log_init(debug); + + pidfh = pidfile_open(pidfile_path, 0600, &otherpid); + if (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\"", + pidfile_path); + } + + iscsi_fd = open(ISCSI_PATH, O_RDWR); + if (iscsi_fd < 0) { + saved_errno = errno; + retval = kldload("iscsi"); + if (retval != -1) + iscsi_fd = open(ISCSI_PATH, O_RDWR); + else + errno = saved_errno; + } + if (iscsi_fd < 0) + log_err(1, "failed to open %s", ISCSI_PATH); + + if (dont_daemonize == false) { + if (daemon(0, 0) == -1) { + log_warn("cannot daemonize"); + pidfile_remove(pidfh); + exit(1); + } + } + + register_signals(); + pidfile_write(pidfh); + + for (;;) { + log_debugx("waiting for request from the kernel"); + + request = calloc(1, sizeof(*request)); + if (request == NULL) + log_err(1, "calloc"); + + error = ioctl(iscsi_fd, ISCSIDWAIT, request); + if (error != 0) + log_err(1, "ISCSIDWAIT"); + + log_debugx("got request from kernel; cmd = 0x%x", + request->idr_cmd); + + if (dont_daemonize) { + log_debugx("not forking due to -d flag; " + "will exit after servicing a single request"); + } else { + pid = fork(); + if (pid < 0) + log_err(1, "fork"); + if (pid > 0) + continue; + } + + pidfile_close(pidfh); + handle_request(iscsi_fd, request); + } + + return (0); +} diff -urNp p4/freebsd/src/usr.sbin/iscsid/iscsid.h p4/iscsi/usr.sbin/iscsid/iscsid.h --- p4/freebsd/src/usr.sbin/iscsid/iscsid.h 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/iscsid/iscsid.h 2013-08-18 13:07:26.000000000 +0200 @@ -0,0 +1,116 @@ +/*- + * 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 ISCSID_H +#define ISCSID_H + +#include +#include + +#include + +#define DEFAULT_PIDFILE "/var/run/iscsid.pid" + +#define CONN_DIGEST_NONE 0 +#define CONN_DIGEST_CRC32C 1 + +#define CONN_MUTUAL_CHALLENGE_LEN 1024 + +struct connection { + int conn_iscsi_fd; +#ifndef ICL_KERNEL_PROXY + int conn_socket; +#endif + struct iscsi_session_desc conn_desc; + uint8_t conn_isid[6]; + uint32_t conn_statsn; + int conn_header_digest; + int conn_data_digest; + bool conn_initial_r2t; + bool conn_immediate_data; + size_t conn_max_data_segment_length; + size_t conn_max_burst_length; + size_t conn_first_burst_length; + char conn_mutual_challenge[CONN_MUTUAL_CHALLENGE_LEN]; + unsigned char conn_mutual_id; +}; + +struct pdu { + struct connection *pdu_connection; + struct iscsi_bhs *pdu_bhs; + char *pdu_data; + size_t pdu_data_len; +}; + +#define KEYS_MAX 1024 + +struct keys { + char *keys_names[KEYS_MAX]; + char *keys_values[KEYS_MAX]; + char *keys_data; + size_t keys_data_len; +}; + +struct keys *keys_new(void); +void keys_delete(struct keys *key); +void keys_load(struct keys *keys, const struct pdu *pdu); +void keys_save(struct keys *keys, struct pdu *pdu); +const char *keys_find(struct keys *keys, const char *name); +int keys_find_int(struct keys *keys, const char *name); +void keys_add(struct keys *keys, + const char *name, const char *value); +void keys_add_int(struct keys *keys, + const char *name, int value); + +struct pdu *pdu_new(struct connection *ic); +struct pdu *pdu_new_response(struct pdu *request); +void pdu_receive(struct pdu *request); +void pdu_send(struct pdu *response); +void pdu_delete(struct pdu *ip); + +void log_init(int level); +void log_set_peer_name(const char *name); +void log_set_peer_addr(const char *addr); +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); + +void login(struct connection *ic); + +void discovery(struct connection *ic); + +char *checked_strdup(const char *); + +#endif /* !ISCSID_H */ diff -urNp p4/freebsd/src/usr.sbin/iscsid/keys.c p4/iscsi/usr.sbin/iscsid/keys.c --- p4/freebsd/src/usr.sbin/iscsid/keys.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/iscsid/keys.c 2013-07-23 23:48:07.000000000 +0200 @@ -0,0 +1,217 @@ +/*- + * 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 "iscsid.h" + +struct keys * +keys_new(void) +{ + struct keys *keys; + + keys = calloc(sizeof(*keys), 1); + if (keys == NULL) + log_err(1, "calloc"); + + return (keys); +} + +void +keys_delete(struct keys *keys) +{ + + free(keys->keys_data); + free(keys); +} + +void +keys_load(struct keys *keys, const struct pdu *pdu) +{ + int i; + char *pair; + size_t pair_len; + + if (pdu->pdu_data_len == 0) + log_errx(1, "protocol error: empty data segment"); + + if (pdu->pdu_data[pdu->pdu_data_len - 1] != '\0') + log_errx(1, "protocol error: key not NULL-terminated\n"); + + assert(keys->keys_data == NULL); + keys->keys_data_len = pdu->pdu_data_len; + keys->keys_data = malloc(keys->keys_data_len); + if (keys->keys_data == NULL) + log_err(1, "malloc"); + memcpy(keys->keys_data, pdu->pdu_data, keys->keys_data_len); + + /* + * XXX: Review this carefully. + */ + pair = keys->keys_data; + for (i = 0;; i++) { + if (i >= KEYS_MAX) + log_errx(1, "too many keys received"); + + pair_len = strlen(pair); + + keys->keys_values[i] = pair; + keys->keys_names[i] = strsep(&keys->keys_values[i], "="); + if (keys->keys_names[i] == NULL || keys->keys_values[i] == NULL) + log_errx(1, "malformed keys"); + log_debugx("key received: \"%s=%s\"", + keys->keys_names[i], keys->keys_values[i]); + + pair += pair_len + 1; /* +1 to skip the terminating '\0'. */ + if (pair == keys->keys_data + keys->keys_data_len) + break; + assert(pair < keys->keys_data + keys->keys_data_len); + } +} + +void +keys_save(struct keys *keys, struct pdu *pdu) +{ + char *data; + size_t len; + int i; + + /* + * XXX: Not particularly efficient. + */ + len = 0; + for (i = 0; i < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) + break; + /* + * +1 for '=', +1 for '\0'. + */ + len += strlen(keys->keys_names[i]) + + strlen(keys->keys_values[i]) + 2; + } + + if (len == 0) + return; + + data = malloc(len); + if (data == NULL) + log_err(1, "malloc"); + + pdu->pdu_data = data; + pdu->pdu_data_len = len; + + for (i = 0; i < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) + break; + data += sprintf(data, "%s=%s", + keys->keys_names[i], keys->keys_values[i]); + data += 1; /* for '\0'. */ + } +} + +const char * +keys_find(struct keys *keys, 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 < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) + return (NULL); + if (strcmp(keys->keys_names[i], name) == 0) + return (keys->keys_values[i]); + } + return (NULL); +} + +int +keys_find_int(struct keys *keys, const char *name) +{ + const char *str; + char *endptr; + int num; + + str = keys_find(keys, 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 +keys_add(struct keys *keys, 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 < KEYS_MAX; i++) { + if (keys->keys_names[i] == NULL) { + keys->keys_names[i] = checked_strdup(name); + keys->keys_values[i] = checked_strdup(value); + return; + } + } + log_errx(1, "too many keys"); +} + +void +keys_add_int(struct keys *keys, const char *name, int value) +{ + char *str; + int ret; + + ret = asprintf(&str, "%d", value); + if (ret <= 0) + log_err(1, "asprintf"); + + keys_add(keys, name, str); + free(str); +} diff -urNp p4/freebsd/src/usr.sbin/iscsid/log.c p4/iscsi/usr.sbin/iscsid/log.c --- p4/freebsd/src/usr.sbin/iscsid/log.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/iscsid/log.c 2013-07-23 23:48:07.000000000 +0200 @@ -0,0 +1,181 @@ +/*- + * 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 "iscsid.h" + +static int log_level = 0; +static char *peer_name = NULL; +static char *peer_addr = NULL; + +void +log_init(int level) +{ + + log_level = level; + openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON); +} + +void +log_set_peer_name(const char *name) +{ + + /* + * XXX: Turn it into assertion? + */ + if (peer_name != NULL) + log_errx(1, "%s called twice", __func__); + if (peer_addr == NULL) + log_errx(1, "%s called before log_set_peer_addr", __func__); + + peer_name = checked_strdup(name); +} + +void +log_set_peer_addr(const char *addr) +{ + + /* + * XXX: Turn it into assertion? + */ + if (peer_addr != NULL) + log_errx(1, "%s called twice", __func__); + + peer_addr = checked_strdup(addr); +} + +static void +log_common(int priority, int log_errno, const char *fmt, va_list ap) +{ + int ret; + char *msg; + + ret = vasprintf(&msg, fmt, ap); + if (ret == -1) { + /* + * XXX: vasprintf(3) doesn't set errno, right? + */ + fprintf(stderr, "%s: %s: %s\n", + getprogname(), __func__, strerror(ENOMEM)); + syslog(LOG_CRIT, "%s: %s", __func__, strerror(ENOMEM)); + exit(1); + } + + if (log_errno == -1) { + if (peer_name != NULL) { + fprintf(stderr, "%s: %s (%s): %s\n", getprogname(), peer_addr, peer_name, msg); + syslog(priority, "%s (%s): %s", peer_addr, peer_name, msg); + } else if (peer_addr != NULL) { + fprintf(stderr, "%s: %s: %s\n", getprogname(), peer_addr, msg); + syslog(priority, "%s: %s", peer_addr, msg); + } else { + fprintf(stderr, "%s: %s\n", getprogname(), msg); + syslog(priority, "%s", msg); + } + + } else { + if (peer_name != NULL) { + fprintf(stderr, "%s: %s (%s): %s: %s\n", getprogname(), peer_addr, peer_name, msg, strerror(errno)); + syslog(priority, "%s (%s): %s: %s", peer_addr, peer_name, msg, strerror(errno)); + } else if (peer_addr != NULL) { + fprintf(stderr, "%s: %s: %s: %s\n", getprogname(), peer_addr, msg, strerror(errno)); + syslog(priority, "%s: %s: %s", peer_addr, msg, strerror(errno)); + } else { + fprintf(stderr, "%s: %s: %s\n", getprogname(), msg, strerror(errno)); + syslog(priority, "%s: %s", msg, strerror(errno)); + } + } + + free(msg); +} + +void +log_err(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_CRIT, errno, fmt, ap); + va_end(ap); + + exit(eval); +} + +void +log_errx(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_CRIT, -1, fmt, ap); + va_end(ap); + + exit(eval); +} + +void +log_warn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_WARNING, errno, fmt, ap); + va_end(ap); +} + +void +log_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_common(LOG_WARNING, -1, fmt, ap); + va_end(ap); +} + +void +log_debugx(const char *fmt, ...) +{ + va_list ap; + + if (log_level == 0) + return; + + va_start(ap, fmt); + log_common(LOG_DEBUG, -1, fmt, ap); + va_end(ap); +} diff -urNp p4/freebsd/src/usr.sbin/iscsid/login.c p4/iscsi/usr.sbin/iscsid/login.c --- p4/freebsd/src/usr.sbin/iscsid/login.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/iscsid/login.c 2013-08-20 11:52:23.000000000 +0200 @@ -0,0 +1,779 @@ +/*- + * 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 "iscsid.h" +#include "iscsi_proto.h" + +static int +login_nsg(const struct pdu *response) +{ + struct iscsi_bhs_login_response *bhslr; + + bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; + + return (bhslr->bhslr_flags & 0x03); +} + +static void +login_set_nsg(struct pdu *request, int nsg) +{ + struct iscsi_bhs_login_request *bhslr; + + assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION || + nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || + nsg == BHSLR_STAGE_FULL_FEATURE_PHASE); + + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + + bhslr->bhslr_flags &= 0xFC; + bhslr->bhslr_flags |= nsg; +} + +static void +login_set_csg(struct pdu *request, int csg) +{ + struct iscsi_bhs_login_request *bhslr; + + assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION || + csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || + csg == BHSLR_STAGE_FULL_FEATURE_PHASE); + + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + + bhslr->bhslr_flags &= 0xF3; + bhslr->bhslr_flags |= csg << 2; +} + +static struct pdu * +login_receive(struct connection *conn, bool initial) +{ + struct pdu *response; + struct iscsi_bhs_login_response *bhslr; + + response = pdu_new(conn); + pdu_receive(response); + if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) { + log_errx(1, "protocol error: received invalid opcode 0x%x", + response->pdu_bhs->bhs_opcode); + } + bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; + /* + * XXX: Implement the C flag some day. + */ + if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) + log_errx(1, "received Login PDU with unsupported \"C\" flag"); + if (bhslr->bhslr_version_max != 0x00) + log_errx(1, "received Login PDU with unsupported " + "Version-max 0x%x", bhslr->bhslr_version_max); + if (bhslr->bhslr_version_active != 0x00) + log_errx(1, "received Login PDU with unsupported " + "Version-active 0x%x", bhslr->bhslr_version_active); + if (bhslr->bhslr_status_class != 0) + log_errx(1, "target returned error: status class 0x%x, " + "detail 0x%x", bhslr->bhslr_status_class, + bhslr->bhslr_status_detail); +#if 0 + if (response->pdu_data_len == 0) + log_errx(1, "received Login PDU with empty data segment"); +#endif + if (initial == false && + ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) { + log_errx(1, "received Login PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_statsn), + conn->conn_statsn + 1); + } + conn->conn_statsn = ntohl(bhslr->bhslr_statsn); + + return (response); +} + +static struct pdu * +login_new_request(struct connection *conn) +{ + struct pdu *request; + struct iscsi_bhs_login_request *bhslr; + + request = pdu_new(conn); + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST | + ISCSI_BHS_OPCODE_IMMEDIATE; + bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT; + login_set_csg(request, BHSLR_STAGE_SECURITY_NEGOTIATION); + login_set_nsg(request, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + memcpy(bhslr->bhslr_isid, &conn->conn_isid, sizeof(bhslr->bhslr_isid)); + bhslr->bhslr_initiator_task_tag = 0; + bhslr->bhslr_cmdsn = 0; + bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1); + + return (request); +} + +static int +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 +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 +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 = 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 * +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 +login_compute_md5(const char id, 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, &id, sizeof(id)); + 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"); +} + +static void +login_negotiate_key(struct connection *conn, const char *name, + const char *value) +{ + int which, tmp; + + if (strcmp(name, "TargetAlias") == 0) { + strlcpy(conn->conn_desc.isd_target_alias, value, + sizeof(conn->conn_desc.isd_target_alias)); + } else if (strcmp(value, "Irrelevant") == 0) { + /* Ignore. */ + } else if (strcmp(name, "HeaderDigest") == 0) { + which = login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("target prefers CRC32C " + "for header digest; we'll use it"); + conn->conn_header_digest = CONN_DIGEST_CRC32C; + break; + case 2: + log_debugx("target prefers not to do " + "header digest; we'll comply"); + break; + default: + log_warnx("target sent unrecognized " + "HeaderDigest value \"%s\"; will use None", value); + break; + } + } else if (strcmp(name, "DataDigest") == 0) { + which = login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("target prefers CRC32C " + "for data digest; we'll use it"); + conn->conn_data_digest = CONN_DIGEST_CRC32C; + break; + case 2: + log_debugx("target prefers not to do " + "data digest; we'll comply"); + break; + default: + log_warnx("target sent unrecognized " + "DataDigest value \"%s\"; will use None", value); + break; + } + } else if (strcmp(name, "MaxConnections") == 0) { + /* Ignore. */ + } else if (strcmp(name, "InitialR2T") == 0) { + if (strcmp(value, "Yes") == 0) + conn->conn_initial_r2t = true; + else + conn->conn_initial_r2t = false; + } else if (strcmp(name, "ImmediateData") == 0) { + if (strcmp(value, "Yes") == 0) + conn->conn_immediate_data = true; + else + conn->conn_immediate_data = false; + } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) + log_errx(1, "received invalid " + "MaxRecvDataSegmentLength"); + conn->conn_max_data_segment_length = tmp; + } else if (strcmp(name, "MaxBurstLength") == 0) { + if (conn->conn_immediate_data) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) + log_errx(1, "received invalid MaxBurstLength"); + conn->conn_max_burst_length = tmp; + } + } else if (strcmp(name, "FirstBurstLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) + log_errx(1, "received invalid FirstBurstLength"); + conn->conn_first_burst_length = tmp; + } else if (strcmp(name, "DefaultTime2Wait") == 0) { + /* Ignore */ + } else if (strcmp(name, "DefaultTime2Retain") == 0) { + /* Ignore */ + } else if (strcmp(name, "MaxOutstandingR2T") == 0) { + /* Ignore */ + } else if (strcmp(name, "DataPDUInOrder") == 0) { + /* Ignore */ + } else if (strcmp(name, "DataSequenceInOrder") == 0) { + /* Ignore */ + } else if (strcmp(name, "ErrorRecoveryLevel") == 0) { + /* Ignore */ + } else if (strcmp(name, "OFMarker") == 0) { + /* Ignore */ + } else if (strcmp(name, "IFMarker") == 0) { + /* Ignore */ + } else if (strcmp(name, "TargetPortalGroupTag") == 0) { + /* Ignore */ + } else { + log_debugx("unknown key \"%s\"; ignoring", name); + } +} + +static void +login_negotiate(struct connection *conn) +{ + struct pdu *request, *response; + struct keys *request_keys, *response_keys; + struct iscsi_bhs_login_response *bhslr; + int i; + + log_debugx("beginning parameter negotiation"); + request = login_new_request(conn); + login_set_csg(request, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + login_set_nsg(request, BHSLR_STAGE_FULL_FEATURE_PHASE); + request_keys = keys_new(); + if (conn->conn_desc.isd_discovery == 0) { + if (conn->conn_desc.isd_header_digest != 0) + keys_add(request_keys, "HeaderDigest", "CRC32C"); + if (conn->conn_desc.isd_data_digest != 0) + keys_add(request_keys, "DataDigest", "CRC32C"); + + keys_add(request_keys, "ImmediateData", "Yes"); + keys_add_int(request_keys, "MaxBurstLength", + ISCSI_MAX_DATA_SEGMENT_LENGTH); + keys_add_int(request_keys, "FirstBurstLength", + ISCSI_MAX_DATA_SEGMENT_LENGTH); + } + keys_add(request_keys, "InitialR2T", "Yes"); + keys_add_int(request_keys, "MaxRecvDataSegmentLength", + ISCSI_MAX_DATA_SEGMENT_LENGTH); + keys_add(request_keys, "DefaultTime2Wait", "0"); + keys_add(request_keys, "DefaultTime2Retain", "0"); + keys_save(request_keys, request); + keys_delete(request_keys); + request_keys = NULL; + pdu_send(request); + pdu_delete(request); + request = NULL; + + response = login_receive(conn, false); + response_keys = keys_new(); + keys_load(response_keys, response); + for (i = 0; i < KEYS_MAX; i++) { + if (response_keys->keys_names[i] == NULL) + break; + + login_negotiate_key(conn, + response_keys->keys_names[i], response_keys->keys_values[i]); + } + + bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; + if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) + log_warnx("received final login response " + "without the \"T\" flag"); + else if (login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE) + log_warnx("received final login response with wrong NSG 0x%x", + login_nsg(response)); + + log_debugx("parameter negotiation done; " + "transitioning to Full Feature phase"); + + keys_delete(response_keys); + pdu_delete(response); +} + +static void +login_send_chap_a(struct connection *conn) +{ + struct pdu *request; + struct keys *request_keys; + + request = login_new_request(conn); + request_keys = keys_new(); + keys_add(request_keys, "CHAP_A", "5"); + keys_save(request_keys, request); + keys_delete(request_keys); + pdu_send(request); + pdu_delete(request); +} + +static void +login_send_chap_r(struct pdu *response) +{ + struct connection *conn; + struct pdu *request; + struct keys *request_keys, *response_keys; + const char *chap_a, *chap_c, *chap_i; + char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH]; + size_t challenge_len; + int error, rv; + unsigned char id; + char *mutual_chap_c, mutual_chap_i[4]; + + /* + * As in the rest of the initiator, 'request' means + * 'initiator -> target', and 'response' means 'target -> initiator', + * + * So, here the 'response' from the target is the packet that contains + * CHAP challenge; our CHAP response goes into 'request'. + */ + + conn = response->pdu_connection; + + response_keys = keys_new(); + keys_load(response_keys, response); + + /* + * First, compute the response. + */ + chap_a = keys_find(response_keys, "CHAP_A"); + if (chap_a == NULL) + log_errx(1, "received CHAP packet without CHAP_A"); + chap_c = keys_find(response_keys, "CHAP_C"); + if (chap_c == NULL) + log_errx(1, "received CHAP packet without CHAP_C"); + chap_i = keys_find(response_keys, "CHAP_I"); + if (chap_i == NULL) + log_errx(1, "received CHAP packet without CHAP_I"); + + if (strcmp(chap_a, "5") != 0) + log_errx(1, "received CHAP packet " + "with unsupported CHAP_A \"%s\"", chap_a); + id = strtoul(chap_i, NULL, 10); + error = login_hex2bin(chap_c, &challenge, &challenge_len); + if (error != 0) + log_errx(1, "received CHAP packet with malformed CHAP_C"); + login_compute_md5(id, conn->conn_desc.isd_secret, + challenge, challenge_len, response_bin, sizeof(response_bin)); + free(challenge); + chap_r = login_bin2hex(response_bin, sizeof(response_bin)); + + keys_delete(response_keys); + + request = login_new_request(conn); + request_keys = keys_new(); + keys_add(request_keys, "CHAP_N", conn->conn_desc.isd_user); + keys_add(request_keys, "CHAP_R", chap_r); + free(chap_r); + + /* + * If we want mutual authentication, we're expected to send + * our CHAP_I/CHAP_C now. + */ + if (conn->conn_desc.isd_mutual_user[0] != '\0') { + log_debugx("requesting mutual authentication; " + "binary challenge size is %zd bytes", + sizeof(conn->conn_mutual_challenge)); + + rv = RAND_bytes(conn->conn_mutual_challenge, + sizeof(conn->conn_mutual_challenge)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + rv = RAND_bytes(&conn->conn_mutual_id, + sizeof(conn->conn_mutual_id)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + mutual_chap_c = login_bin2hex(conn->conn_mutual_challenge, + sizeof(conn->conn_mutual_challenge)); + snprintf(mutual_chap_i, sizeof(mutual_chap_i), + "%d", conn->conn_mutual_id); + keys_add(request_keys, "CHAP_I", mutual_chap_i); + keys_add(request_keys, "CHAP_C", mutual_chap_c); + free(mutual_chap_c); + } + + keys_save(request_keys, request); + keys_delete(request_keys); + pdu_send(request); + pdu_delete(request); +} + +static void +login_verify_mutual(const struct pdu *response) +{ + struct connection *conn; + struct keys *response_keys; + const char *chap_n, *chap_r; + char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH]; + size_t response_bin_len; + int error; + + conn = response->pdu_connection; + + response_keys = keys_new(); + keys_load(response_keys, response); + + chap_n = keys_find(response_keys, "CHAP_N"); + if (chap_n == NULL) + log_errx(1, "received CHAP Response PDU without CHAP_N"); + chap_r = keys_find(response_keys, "CHAP_R"); + if (chap_r == NULL) + log_errx(1, "received CHAP Response PDU without CHAP_R"); + error = login_hex2bin(chap_r, &response_bin, &response_bin_len); + if (error != 0) + log_errx(1, "received CHAP Response PDU with malformed CHAP_R"); + + if (strcmp(chap_n, conn->conn_desc.isd_mutual_user) != 0) + log_errx(1, "mutual CHAP authentication failed: wrong user"); + + login_compute_md5(conn->conn_mutual_id, + conn->conn_desc.isd_mutual_secret, conn->conn_mutual_challenge, + sizeof(conn->conn_mutual_challenge), expected_response_bin, + sizeof(expected_response_bin)); + + if (memcmp(response_bin, expected_response_bin, + sizeof(expected_response_bin)) != 0) + log_errx(1, "mutual CHAP authentication failed: wrong secret"); + + keys_delete(response_keys); + free(response_bin); + + log_debugx("mutual CHAP authentication succeeded"); +} + +static void +login_chap(struct connection *conn) +{ + struct pdu *response; + + log_debugx("beginning CHAP authentication; sending CHAP_A"); + login_send_chap_a(conn); + + log_debugx("waiting for CHAP_A/CHAP_C/CHAP_I"); + response = login_receive(conn, false); + + log_debugx("sending CHAP_N/CHAP_R"); + login_send_chap_r(response); + pdu_delete(response); + + /* + * XXX: Make sure this is not susceptible to MITM. + */ + + log_debugx("waiting for CHAP result"); + response = login_receive(conn, false); + if (conn->conn_desc.isd_mutual_user[0] != '\0') + login_verify_mutual(response); + pdu_delete(response); + + log_debugx("CHAP authentication done"); +} + +static void +login_create_isid(struct connection *conn) +{ + int rv; + + /* + * RFC 3720, 10.12.5: 10b, "Random" ISID. + * + */ + conn->conn_isid[0] = 0x80; + + rv = RAND_bytes(&conn->conn_isid[1], 3); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } +} + +void +login(struct connection *conn) +{ + struct pdu *request, *response; + struct keys *request_keys, *response_keys; + struct iscsi_bhs_login_request *bhslr; + struct iscsi_bhs_login_response *bhslr2; + const char *auth_method; + int i; + + login_create_isid(conn); + + log_debugx("beginning Login phase; sending Login PDU"); + request = login_new_request(conn); + + bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; + bhslr->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + + request_keys = keys_new(); + if (conn->conn_desc.isd_user[0] == '\0') + keys_add(request_keys, "AuthMethod", "None"); + else + keys_add(request_keys, "AuthMethod", "CHAP,None"); + keys_add(request_keys, "InitiatorName", + conn->conn_desc.isd_initiator); + if (conn->conn_desc.isd_initiator_alias[0] != '\0') { + keys_add(request_keys, "InitiatorAlias", + conn->conn_desc.isd_initiator_alias); + } + if (conn->conn_desc.isd_discovery == 0) { + keys_add(request_keys, "SessionType", "Normal"); + keys_add(request_keys, + "TargetName", conn->conn_desc.isd_target); + } else { + keys_add(request_keys, "SessionType", "Discovery"); + } + keys_save(request_keys, request); + keys_delete(request_keys); + pdu_send(request); + pdu_delete(request); + + response = login_receive(conn, true); + + response_keys = keys_new(); + keys_load(response_keys, response); + + for (i = 0; i < KEYS_MAX; i++) { + if (response_keys->keys_names[i] == NULL) + break; + + /* + * Not interested in AuthMethod at this point; we only need + * to parse things such as TargetAlias. + * + * XXX: This is somewhat ugly. We should have a way to apply + * all the keys to the session and use that by default + * instead of discarding them. + */ + if (strcmp(response_keys->keys_names[i], "AuthMethod") == 0) + continue; + + login_negotiate_key(conn, + response_keys->keys_names[i], response_keys->keys_values[i]); + } + + bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; + if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 && + login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { + log_debugx("target requested transition " + "to operational negotiation"); + + keys_delete(response_keys); + pdu_delete(response); + login_negotiate(conn); + return; + } + + auth_method = keys_find(response_keys, "AuthMethod"); + if (auth_method == NULL) + log_errx(1, "received response without AuthMethod"); + if (strcmp(auth_method, "None") == 0) { + log_debugx("target does not require authentication"); + keys_delete(response_keys); + pdu_delete(response); + login_negotiate(conn); + return; + } + + if (strcmp(auth_method, "CHAP") != 0) + log_errx(1, "received response " + "with unsupported AuthMethod \"%s\"", auth_method); + + + if (conn->conn_desc.isd_user[0] == '\0' || + conn->conn_desc.isd_secret[0] == '\0') + log_errx(1, "target requests CHAP authentication, but we don't " + "have user and secret"); + + keys_delete(response_keys); + response_keys = NULL; + pdu_delete(response); + response = NULL; + + login_chap(conn); + login_negotiate(conn); +} diff -urNp p4/freebsd/src/usr.sbin/iscsid/pdu.c p4/iscsi/usr.sbin/iscsid/pdu.c --- p4/freebsd/src/usr.sbin/iscsid/pdu.c 1970-01-01 01:00:00.000000000 +0100 +++ p4/iscsi/usr.sbin/iscsid/pdu.c 2013-08-20 11:52:23.000000000 +0200 @@ -0,0 +1,276 @@ +/*- + * 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 "iscsid.h" +#include "iscsi_proto.h" + +#ifdef ICL_KERNEL_PROXY +#include +#endif + +static int +pdu_ahs_length(const struct pdu *pdu) +{ + + return (pdu->pdu_bhs->bhs_total_ahs_len * 4); +} + +static int +pdu_data_segment_length(const struct pdu *pdu) +{ + uint32_t len = 0; + + len += pdu->pdu_bhs->bhs_data_segment_len[0]; + len <<= 8; + len += pdu->pdu_bhs->bhs_data_segment_len[1]; + len <<= 8; + len += pdu->pdu_bhs->bhs_data_segment_len[2]; + + return (len); +} + +static void +pdu_set_data_segment_length(struct pdu *pdu, uint32_t len) +{ + + pdu->pdu_bhs->bhs_data_segment_len[2] = len; + pdu->pdu_bhs->bhs_data_segment_len[1] = len >> 8; + pdu->pdu_bhs->bhs_data_segment_len[0] = len >> 16; +} + +struct pdu * +pdu_new(struct connection *conn) +{ + struct pdu *pdu; + + pdu = calloc(sizeof(*pdu), 1); + if (pdu == NULL) + log_err(1, "calloc"); + + pdu->pdu_bhs = calloc(sizeof(*pdu->pdu_bhs), 1); + if (pdu->pdu_bhs == NULL) + log_err(1, "calloc"); + + pdu->pdu_connection = conn; + + return (pdu); +} + +struct pdu * +pdu_new_response(struct pdu *request) +{ + + return (pdu_new(request->pdu_connection)); +} + +#ifdef ICL_KERNEL_PROXY + +void +pdu_receive(struct pdu *pdu) +{ + struct iscsi_daemon_receive *idr; + size_t len; + int error; + + pdu->pdu_data = malloc(ISCSI_MAX_DATA_SEGMENT_LENGTH); + if (pdu->pdu_data == NULL) + log_err(1, "malloc"); + + idr = calloc(1, sizeof(*idr)); + if (idr == NULL) + log_err(1, "calloc"); + + idr->idr_session_id = pdu->pdu_connection->conn_desc.isd_id; + idr->idr_bhs = pdu->pdu_bhs; + idr->idr_data_segment_len = ISCSI_MAX_DATA_SEGMENT_LENGTH; + idr->idr_data_segment = pdu->pdu_data; + + error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDRECEIVE, idr); + if (error != 0) + log_err(1, "ISCSIDRECEIVE"); + + len = pdu_ahs_length(pdu); + if (len > 0) + log_errx(1, "protocol error: non-empty AHS"); + + len = pdu_data_segment_length(pdu); + assert(len <= ISCSI_MAX_DATA_SEGMENT_LENGTH); + pdu->pdu_data_len = len; + + free(idr); +} + +void +pdu_send(struct pdu *pdu) +{ + struct iscsi_daemon_send *ids; + int error; + + pdu_set_data_segment_length(pdu, pdu->pdu_data_len); + + ids = calloc(1, sizeof(*ids)); + if (ids == NULL) + log_err(1, "calloc"); + + ids->ids_session_id = pdu->pdu_connection->conn_desc.isd_id; + ids->ids_bhs = pdu->pdu_bhs; + ids->ids_data_segment_len = pdu->pdu_data_len; + ids->ids_data_segment = pdu->pdu_data; + + error = ioctl(pdu->pdu_connection->conn_iscsi_fd, ISCSIDSEND, ids); + if (error != 0) + log_err(1, "ISCSIDSEND"); + + free(ids); +} + +#else /* !ICL_KERNEL_PROXY */ + +static size_t +pdu_padding(const struct pdu *pdu) +{ + + if ((pdu->pdu_data_len % 4) != 0) + return (4 - (pdu->pdu_data_len % 4)); + + return (0); +} + +static void +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 +pdu_receive(struct pdu *pdu) +{ + size_t len, padding; + char dummy[4]; + + pdu_read(pdu->pdu_connection->conn_socket, + (char *)pdu->pdu_bhs, sizeof(*pdu->pdu_bhs)); + + len = pdu_ahs_length(pdu); + if (len > 0) + log_errx(1, "protocol error: non-empty AHS"); + + len = pdu_data_segment_length(pdu); + if (len > 0) { + if (len > ISCSI_MAX_DATA_SEGMENT_LENGTH) { + log_errx(1, "protocol error: received PDU " + "with DataSegmentLength exceeding %d", + ISCSI_MAX_DATA_SEGMENT_LENGTH); + } + + pdu->pdu_data_len = len; + pdu->pdu_data = malloc(len); + if (pdu->pdu_data == NULL) + log_err(1, "malloc"); + + pdu_read(pdu->pdu_connection->conn_socket, + (char *)pdu->pdu_data, pdu->pdu_data_len); + + padding = pdu_padding(pdu); + if (padding != 0) { + assert(padding < sizeof(dummy)); + pdu_read(pdu->pdu_connection->conn_socket, + (char *)dummy, padding); + } + } +} + +void +pdu_send(struct pdu *pdu) +{ + ssize_t ret, total_len; + size_t padding; + uint32_t zero = 0; + struct iovec iov[3]; + int iovcnt; + + pdu_set_data_segment_length(pdu, pdu->pdu_data_len); + iov[0].iov_base = pdu->pdu_bhs; + iov[0].iov_len = sizeof(*pdu->pdu_bhs); + total_len = iov[0].iov_len; + iovcnt = 1; + + if (pdu->pdu_data_len > 0) { + iov[1].iov_base = pdu->pdu_data; + iov[1].iov_len = pdu->pdu_data_len; + total_len += iov[1].iov_len; + iovcnt = 2; + + padding = pdu_padding(pdu); + 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(pdu->pdu_connection->conn_socket, iov, iovcnt); + if (ret < 0) + log_err(1, "writev"); + if (ret != total_len) + log_errx(1, "short write"); +} + +#endif /* !ICL_KERNEL_PROXY */ + +void +pdu_delete(struct pdu *pdu) +{ + + free(pdu->pdu_data); + free(pdu->pdu_bhs); + free(pdu); +}