diff -urNp freebsd/src/etc/defaults/rc.conf iscsi/etc/defaults/rc.conf --- freebsd/src/etc/defaults/rc.conf 2013-04-20 19:54:08.000000000 +0200 +++ iscsi/etc/defaults/rc.conf 2013-04-19 18:33:52.000000000 +0200 @@ -267,9 +267,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 freebsd/src/etc/rc.d/Makefile iscsi/etc/rc.d/Makefile --- freebsd/src/etc/rc.d/Makefile 2013-04-20 19:54:12.000000000 +0200 +++ iscsi/etc/rc.d/Makefile 2013-04-19 18:33:52.000000000 +0200 @@ -30,6 +30,7 @@ FILES= DAEMON \ cleanvar \ cleartmp \ cron \ + ctld \ ddb \ defaultroute \ devd \ @@ -63,6 +64,8 @@ FILES= DAEMON \ ipnat \ ipsec \ ${_ipxrouted} \ + iscsictl \ + iscsid \ jail \ kadmind \ kerberos \ diff -urNp freebsd/src/etc/rc.d/ctld iscsi/etc/rc.d/ctld --- freebsd/src/etc/rc.d/ctld 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/etc/rc.d/ctld 2013-04-08 22:45:38.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 freebsd/src/etc/rc.d/iscsictl iscsi/etc/rc.d/iscsictl --- freebsd/src/etc/rc.d/iscsictl 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/etc/rc.d/iscsictl 2013-04-19 18:33:52.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 freebsd/src/etc/rc.d/iscsid iscsi/etc/rc.d/iscsid --- freebsd/src/etc/rc.d/iscsid 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/etc/rc.d/iscsid 2013-04-19 18:33:52.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 freebsd/src/sbin/iscontrol/iscontrol.8 iscsi/sbin/iscontrol/iscontrol.8 --- freebsd/src/sbin/iscontrol/iscontrol.8 2013-04-20 19:57:59.000000000 +0200 +++ iscsi/sbin/iscontrol/iscontrol.8 2013-03-19 12:22:25.000000000 +0100 @@ -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 freebsd/src/sbin/iscontrol/iscsi.conf.5 iscsi/sbin/iscontrol/iscsi.conf.5 --- freebsd/src/sbin/iscontrol/iscsi.conf.5 2013-04-20 19:58:00.000000000 +0200 +++ iscsi/sbin/iscontrol/iscsi.conf.5 2013-03-19 12:23:04.000000000 +0100 @@ -199,6 +199,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 freebsd/src/sys/cam/ctl/ctl.c iscsi/sys/cam/ctl/ctl.c --- freebsd/src/sys/cam/ctl/ctl.c 2013-04-20 20:02:13.000000000 +0200 +++ iscsi/sys/cam/ctl/ctl.c 2013-04-20 19:04:00.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 + @@ -1070,9 +1075,11 @@ ctl_init(void) softc->emergency_pool = emergency_pool; softc->othersc_pool = other_pool; + mtx_lock(&softc->ctl_lock); ctl_pool_acquire(internal_pool); ctl_pool_acquire(emergency_pool); ctl_pool_acquire(other_pool); + mtx_unlock(&softc->ctl_lock); /* * We used to allocate a processor LUN here. The new scheme is to @@ -1088,13 +1095,16 @@ ctl_init(void) "ctl_thrd"); if (error != 0) { printf("error creating CTL work thread!\n"); + mtx_lock(&softc->ctl_lock); ctl_free_lun(lun); ctl_pool_free(softc, internal_pool); ctl_pool_free(softc, emergency_pool); ctl_pool_free(softc, other_pool); + 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 @@ -1190,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 @@ -1372,7 +1383,6 @@ ctl_ioctl_offline(void *arg) /* * Remove an initiator by port number and initiator ID. * Returns 0 for success, 1 for failure. - * Assumes the caller does NOT hold the CTL lock. */ int ctl_remove_initiator(int32_t targ_port, uint32_t iid) @@ -1381,6 +1391,8 @@ ctl_remove_initiator(int32_t targ_port, softc = control_softc; + mtx_assert(&softc->ctl_lock, MA_NOTOWNED); + if ((targ_port < 0) || (targ_port > CTL_MAX_PORTS)) { printf("%s: invalid port number %d\n", __func__, targ_port); @@ -1404,7 +1416,6 @@ ctl_remove_initiator(int32_t targ_port, /* * Add an initiator to the initiator map. * Returns 0 for success, 1 for failure. - * Assumes the caller does NOT hold the CTL lock. */ int ctl_add_initiator(uint64_t wwpn, int32_t targ_port, uint32_t iid) @@ -1414,6 +1425,8 @@ ctl_add_initiator(uint64_t wwpn, int32_t softc = control_softc; + mtx_assert(&softc->ctl_lock, MA_NOTOWNED); + retval = 0; if ((targ_port < 0) @@ -1672,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) { /* @@ -1970,7 +1987,6 @@ ctl_ioctl_bbrread_callback(void *arg, st } /* - * Must be called with the ctl_lock held. * Returns 0 for success, errno for failure. */ static int @@ -1982,6 +1998,8 @@ ctl_ioctl_fill_ooa(struct ctl_lun *lun, retval = 0; + mtx_assert(&control_softc->ctl_lock, MA_OWNED); + for (io = (union ctl_io *)TAILQ_FIRST(&lun->ooa_queue); (io != NULL); (*cur_fill_num)++, io = (union ctl_io *)TAILQ_NEXT(&io->io_hdr, ooa_links)) { @@ -2973,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; @@ -3090,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"); @@ -3130,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 @@ -3395,12 +3435,12 @@ bailout: return (retval); } -/* - * Caller must hold ctl_softc->ctl_lock. - */ int ctl_pool_acquire(struct ctl_io_pool *pool) { + + mtx_assert(&control_softc->ctl_lock, MA_OWNED); + if (pool == NULL) return (-EINVAL); @@ -3412,12 +3452,12 @@ ctl_pool_acquire(struct ctl_io_pool *poo return (0); } -/* - * Caller must hold ctl_softc->ctl_lock. - */ int ctl_pool_invalidate(struct ctl_io_pool *pool) { + + mtx_assert(&control_softc->ctl_lock, MA_OWNED); + if (pool == NULL) return (-EINVAL); @@ -3426,12 +3466,12 @@ ctl_pool_invalidate(struct ctl_io_pool * return (0); } -/* - * Caller must hold ctl_softc->ctl_lock. - */ int ctl_pool_release(struct ctl_io_pool *pool) { + + mtx_assert(&control_softc->ctl_lock, MA_OWNED); + if (pool == NULL) return (-EINVAL); @@ -3443,14 +3483,13 @@ ctl_pool_release(struct ctl_io_pool *poo return (0); } -/* - * Must be called with ctl_softc->ctl_lock held. - */ void ctl_pool_free(struct ctl_softc *ctl_softc, struct ctl_io_pool *pool) { union ctl_io *cur_io, *next_io; + mtx_assert(&ctl_softc->ctl_lock, MA_OWNED); + for (cur_io = (union ctl_io *)STAILQ_FIRST(&pool->free_queue); cur_io != NULL; cur_io = next_io) { next_io = (union ctl_io *)STAILQ_NEXT(&cur_io->io_hdr, @@ -4392,7 +4431,6 @@ ctl_alloc_lun(struct ctl_softc *ctl_soft /* * Delete a LUN. * Assumptions: - * - caller holds ctl_softc->ctl_lock. * - LUN has already been marked invalid and any pending I/O has been taken * care of. */ @@ -4409,6 +4447,8 @@ ctl_free_lun(struct ctl_lun *lun) softc = lun->ctl_softc; + mtx_assert(&softc->ctl_lock, MA_OWNED); + STAILQ_REMOVE(&softc->lun_list, lun, ctl_lun, links); ctl_clear_mask(softc->ctl_lun_mask, lun->lun); @@ -4425,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); } @@ -8240,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) { @@ -8594,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; @@ -8655,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: @@ -8736,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. */ @@ -9087,6 +9149,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) + @@ -9147,8 +9217,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, */ @@ -9803,7 +9871,6 @@ ctl_check_for_blockage(union ctl_io *pen /* * Check for blockage or overlaps against the OOA (Order Of Arrival) queue. * Assumptions: - * - caller holds ctl_lock * - pending_io is generally either incoming, or on the blocked queue * - starting I/O is the I/O we want to start the check with. */ @@ -9814,6 +9881,8 @@ ctl_check_ooa(struct ctl_lun *lun, union union ctl_io *ooa_io; ctl_action action; + mtx_assert(&control_softc->ctl_lock, MA_OWNED); + /* * Run back along the OOA queue, starting with the current * blocked I/O and going through every I/O before it on the @@ -9854,13 +9923,14 @@ ctl_check_ooa(struct ctl_lun *lun, union * Assumptions: * - An I/O has just completed, and has been removed from the per-LUN OOA * queue, so some items on the blocked queue may now be unblocked. - * - The caller holds ctl_softc->ctl_lock */ static int ctl_check_blocked(struct ctl_lun *lun) { union ctl_io *cur_blocked, *next_blocked; + mtx_assert(&control_softc->ctl_lock, MA_OWNED); + /* * Run forward from the head of the blocked queue, checking each * entry against the I/Os prior to it on the OOA queue to see if @@ -10372,7 +10442,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; @@ -10383,9 +10453,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 @@ -10425,6 +10498,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 { /* @@ -10791,6 +10865,7 @@ ctl_abort_task(union ctl_io *io) char printbuf[128]; #endif int found; + uint32_t targ_lun; ctl_softc = control_softc; found = 0; @@ -10798,9 +10873,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; @@ -10924,8 +11002,6 @@ bailout: } /* - * Assumptions: caller holds ctl_softc->ctl_lock - * * This routine cannot block! It must be callable from an interrupt * handler as well as from the work thread. */ @@ -10934,6 +11010,8 @@ ctl_run_task_queue(struct ctl_softc *ctl { union ctl_io *io, *next_io; + mtx_assert(&ctl_softc->ctl_lock, MA_OWNED); + CTL_DEBUG_PRINT(("ctl_run_task_queue\n")); for (io = (union ctl_io *)STAILQ_FIRST(&ctl_softc->task_queue); @@ -10990,6 +11068,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)) @@ -11064,7 +11144,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, @@ -11088,10 +11168,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: @@ -11235,14 +11319,13 @@ ctl_cmd_pattern_match(struct ctl_scsiio return (filtered_pattern); } -/* - * Called with the CTL lock held. - */ static void ctl_inject_error(struct ctl_lun *lun, union ctl_io *io) { struct ctl_error_desc *desc, *desc2; + mtx_assert(&control_softc->ctl_lock, MA_OWNED); + STAILQ_FOREACH_SAFE(desc, &lun->error_list, links, desc2) { ctl_lun_error_pattern pattern; /* @@ -11312,14 +11395,13 @@ ctl_datamove_timer_wakeup(void *arg) } #endif /* CTL_IO_DELAY */ -/* - * Assumption: caller does NOT hold ctl_lock - */ void ctl_datamove(union ctl_io *io) { void (*fe_datamove)(union ctl_io *io); + mtx_assert(&control_softc->ctl_lock, MA_NOTOWNED); + CTL_DEBUG_PRINT(("ctl_datamove\n")); #ifdef CTL_TIME_IO @@ -12165,8 +12247,6 @@ ctl_datamove_remote_read(union ctl_io *i * first. Once that is complete, the data gets DMAed into the remote * controller's memory. For reads, we DMA from the remote controller's * memory into our memory first, and then move it out to the FETD. - * - * Should be called without the ctl_lock held. */ static void ctl_datamove_remote(union ctl_io *io) @@ -12175,6 +12255,8 @@ ctl_datamove_remote(union ctl_io *io) softc = control_softc; + mtx_assert(&softc->ctl_lock, MA_NOTOWNED); + /* * Note that we look for an aborted I/O here, but don't do some of * the other checks that ctl_datamove() normally does. We don't @@ -12649,7 +12731,7 @@ ctl_queue_sense(union ctl_io *io) { struct ctl_lun *lun; struct ctl_softc *ctl_softc; - uint32_t initidx; + uint32_t initidx, targ_lun; ctl_softc = control_softc; @@ -12668,9 +12750,12 @@ ctl_queue_sense(union ctl_io *io) * If we don't have a LUN for this, just toss the sense * information. */ - if ((io->io_hdr.nexus.targ_lun < CTL_MAX_LUNS) - && (ctl_softc->ctl_luns[io->io_hdr.nexus.targ_lun] != NULL)) - lun = ctl_softc->ctl_luns[io->io_hdr.nexus.targ_lun]; + targ_lun = io->io_hdr.nexus.targ_lun; + if (io->io_hdr.nexus.lun_map_fn != NULL) + targ_lun = io->io_hdr.nexus.lun_map_fn(io->io_hdr.nexus.lun_map_arg, targ_lun); + if ((targ_lun < CTL_MAX_LUNS) + && (ctl_softc->ctl_luns[targ_lun] != NULL)) + lun = ctl_softc->ctl_luns[targ_lun]; else goto bailout; @@ -13071,6 +13156,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 freebsd/src/sys/cam/ctl/ctl.h iscsi/sys/cam/ctl/ctl.h --- freebsd/src/sys/cam/ctl/ctl.h 2013-04-20 20:02:13.000000000 +0200 +++ iscsi/sys/cam/ctl/ctl.h 2013-04-02 16:55:13.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 freebsd/src/sys/cam/ctl/ctl_backend.h iscsi/sys/cam/ctl/ctl_backend.h --- freebsd/src/sys/cam/ctl/ctl_backend.h 2013-04-20 20:02:13.000000000 +0200 +++ iscsi/sys/cam/ctl/ctl_backend.h 2013-04-02 16:55:13.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 freebsd/src/sys/cam/ctl/ctl_backend_block.c iscsi/sys/cam/ctl/ctl_backend_block.c --- freebsd/src/sys/cam/ctl/ctl_backend_block.c 2013-04-20 20:02:14.000000000 +0200 +++ iscsi/sys/cam/ctl/ctl_backend_block.c 2013-04-08 21:24:53.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 freebsd/src/sys/cam/ctl/ctl_backend_ramdisk.c iscsi/sys/cam/ctl/ctl_backend_ramdisk.c --- freebsd/src/sys/cam/ctl/ctl_backend_ramdisk.c 2013-04-20 20:02:14.000000000 +0200 +++ iscsi/sys/cam/ctl/ctl_backend_ramdisk.c 2013-04-02 16:55:13.000000000 +0200 @@ -496,7 +496,7 @@ ctl_backend_ramdisk_create(struct ctl_be struct ctl_lun_create_params *params; uint32_t blocksize; char tmpstr[32]; - int retval; + int i, retval; retval = 0; params = &req->reqdata.create; @@ -514,6 +514,7 @@ ctl_backend_ramdisk_create(struct ctl_be sizeof(*be_lun)); goto bailout_error; } + STAILQ_INIT(&be_lun->ctl_be_lun.options); if (params->flags & CTL_LUN_FLAG_DEV_TYPE) be_lun->ctl_be_lun.lun_type = params->device_type; @@ -550,6 +551,17 @@ ctl_backend_ramdisk_create(struct ctl_be be_lun->softc = softc; + for (i = 0; i < req->num_be_args; i++) { + struct ctl_be_lun_option *opt; + + opt = malloc(sizeof(*opt), M_RAMDISK, M_WAITOK); + opt->name = malloc(strlen(req->kern_be_args[i].kname) + 1, M_RAMDISK, M_WAITOK); + strcpy(opt->name, req->kern_be_args[i].kname); + opt->value = malloc(strlen(req->kern_be_args[i].kvalue) + 1, M_RAMDISK, M_WAITOK); + strcpy(opt->value, req->kern_be_args[i].kvalue); + STAILQ_INSERT_TAIL(&be_lun->ctl_be_lun.options, opt, links); + } + be_lun->flags = CTL_BE_RAMDISK_LUN_UNCONFIGURED; be_lun->ctl_be_lun.flags = CTL_LUN_FLAG_PRIMARY; be_lun->ctl_be_lun.be_lun = be_lun; diff -urNp freebsd/src/sys/cam/ctl/ctl_frontend.h iscsi/sys/cam/ctl/ctl_frontend.h --- freebsd/src/sys/cam/ctl/ctl_frontend.h 2013-04-20 20:02:14.000000000 +0200 +++ iscsi/sys/cam/ctl/ctl_frontend.h 2013-04-02 16:55:14.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 freebsd/src/sys/cam/ctl/ctl_frontend_iscsi.c iscsi/sys/cam/ctl/ctl_frontend_iscsi.c --- freebsd/src/sys/cam/ctl/ctl_frontend_iscsi.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/sys/cam/ctl/ctl_frontend_iscsi.c 2013-04-19 20:11:39.000000000 +0200 @@ -0,0 +1,3274 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * CTL frontend for the iSCSI protocol. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct cfiscsi_target { + TAILQ_ENTRY(cfiscsi_target) ct_next; + int ct_luns[CTL_MAX_LUNS]; + struct cfiscsi_softc *ct_softc; + volatile u_int ct_refcount; + char ct_name[CTL_ISCSI_NAME_LEN + 1]; +}; + +struct cfiscsi_pdu { + TAILQ_ENTRY(cfiscsi_pdu) cp_next; + struct cfiscsi_session *cp_session; + struct iscsi_bhs *cp_bhs; + struct mbuf *cp_bhs_mbuf; + size_t cp_ahs_len; + struct mbuf *cp_ahs_mbuf; + size_t cp_data_len; + struct mbuf *cp_data_mbuf; + uint32_t cp_expdatasn; + uint32_t cp_total_transfer_len; + uint32_t cp_r2tsn; +}; + +struct cfiscsi_data_wait { + TAILQ_ENTRY(cfiscsi_data_wait) cdw_next; + union ctl_io *cdw_ctl_io; + uint32_t cdw_target_transfer_tag; + uint32_t cdw_initiator_task_tag; + int cdw_sg_index; + char *cdw_sg_addr; + size_t cdw_sg_len; +}; + +#define CFISCSI_SESSION_LOCK(X) do { \ + if (debug > 2) { \ + if (mtx_trylock(&X->cs_lock) == 0) { \ + printf("%s: mutex\n", __func__);\ + mtx_lock(&X->cs_lock); \ + } \ + } else { \ + mtx_lock(&X->cs_lock); \ + } \ + } while (0) +#define CFISCSI_SESSION_UNLOCK(X) mtx_unlock(&X->cs_lock) +#define CFISCSI_SESSION_LOCK_ASSERT(X) mtx_assert(&X->cs_lock, MA_OWNED) + +#define CFISCSI_SESSION_STATE_INVALID 0 +#define CFISCSI_SESSION_STATE_BHS 1 +#define CFISCSI_SESSION_STATE_AHS 2 +#define CFISCSI_SESSION_STATE_HEADER_DIGEST 3 +#define CFISCSI_SESSION_STATE_DATA 4 +#define CFISCSI_SESSION_STATE_DATA_DIGEST 5 + +struct cfiscsi_session { + TAILQ_ENTRY(cfiscsi_session) cs_next; + struct cfiscsi_target *cs_target; + struct socket *cs_socket; + TAILQ_HEAD(, cfiscsi_pdu) cs_to_send; + TAILQ_HEAD(, cfiscsi_data_wait) cs_waiting_for_data_out; + struct callout cs_terminate_callout; + struct callout cs_ping_callout; + int cs_ping_waiting; + int cs_portal_group_tag; + uint32_t cs_cmdsn; + uint32_t cs_statsn; + uint32_t cs_target_transfer_tag; + struct mtx cs_lock; + struct cv cs_send_cv; + struct cv cs_receive_cv; + int cs_terminating; + int cs_send_terminating; + volatile u_int cs_outstanding_pdus; + volatile u_int cs_outstanding_ctl_pdus; + int cs_receive_state; + size_t cs_receive_len; + struct cfiscsi_pdu *cs_receive_pdu; + size_t cs_max_data_segment_length; + char cs_initiator_name[CTL_ISCSI_NAME_LEN]; + char cs_initiator_addr[CTL_ISCSI_ADDR_LEN]; + uint32_t cs_header_digest; + uint32_t cs_data_digest; + unsigned int cs_id; + int cs_ctl_initid; +}; + +struct cfiscsi_softc { + struct ctl_frontend fe; + char port_name[32]; + struct mtx lock; + int online; + unsigned int last_session_id; + TAILQ_HEAD(, cfiscsi_target) targets; + TAILQ_HEAD(, cfiscsi_session) sessions; + char ctl_initids[CTL_MAX_INIT_PER_PORT]; + int max_initiators; +}; + +#define CFISCSI_DEBUG(X) \ + if (debug > 1) { \ + printf X; \ + } while (0) +#define CFISCSI_WARN(X) \ + if (debug > 0) { \ + printf("WARNING: "); \ + printf X; \ + } while (0) + +MALLOC_DEFINE(M_CFISCSI, "cfiscsi", "Memory used for CTL iSCSI frontend"); +static uma_zone_t cfiscsi_pdu_zone; +static uma_zone_t cfiscsi_data_wait_zone; + +SYSCTL_NODE(_kern_cam_ctl, OID_AUTO, iscsi, CTLFLAG_RD, 0, + "CAM Target Layer iSCSI Frontend"); +static int debug = 1; +TUNABLE_INT("kern.cam.ctl.iscsi.debug", &debug); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, debug, CTLFLAG_RW, + &debug, 1, "Enable debug messages"); +static int ping_interval = 5; +TUNABLE_INT("kern.cam.ctl.iscsi.ping_interval", &ping_interval); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, ping_interval, CTLFLAG_RW, + &ping_interval, 5, "Interval between ping (NOP-In) requests, in seconds"); +static int maxcmdsn_delta = 256; +TUNABLE_INT("kern.cam.ctl.iscsi.maxcmdsn_delta", &maxcmdsn_delta); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, maxcmdsn_delta, CTLFLAG_RW, + &maxcmdsn_delta, 256, "Number of commands the initiator can send " + "without confirmation"); +static int partial_receive_len = 128; /* XXX: More? */ +TUNABLE_INT("kern.cam.ctl.iscsi.partial_receive_len", &partial_receive_len); +SYSCTL_INT(_kern_cam_ctl_iscsi, OID_AUTO, partial_receive_len, CTLFLAG_RW, + &partial_receive_len, 128, "Minimum read size for partially received " + "data segment"); + +int cfiscsi_init(void); +static void cfiscsi_online(void *arg); +static void cfiscsi_offline(void *arg); +static int cfiscsi_targ_enable(void *arg, struct ctl_id targ_id); +static int cfiscsi_targ_disable(void *arg, struct ctl_id targ_id); +static int cfiscsi_lun_enable(void *arg, + struct ctl_id target_id, int lun_id); +static int cfiscsi_lun_disable(void *arg, + struct ctl_id target_id, int lun_id); +static int cfiscsi_ioctl(struct cdev *dev, + u_long cmd, caddr_t addr, int flag, struct thread *td); +static int cfiscsi_devid(struct ctl_scsiio *ctsio, int alloc_len); +static void cfiscsi_datamove(union ctl_io *io); +static void cfiscsi_done(union ctl_io *io); +static uint32_t cfiscsi_map_lun(void *arg, uint32_t lun); +static void cfiscsi_pdu_update_cmdsn(const struct cfiscsi_pdu *request); +static void cfiscsi_pdu_handle_nop_out(struct cfiscsi_pdu *request); +static void cfiscsi_pdu_handle_scsi_command(struct cfiscsi_pdu *request); +static void cfiscsi_pdu_handle_task_request(struct cfiscsi_pdu *request); +static void cfiscsi_pdu_handle_data_out(struct cfiscsi_pdu *request); +static void cfiscsi_pdu_handle_logout_request(struct cfiscsi_pdu *request); +static void cfiscsi_session_terminate(struct cfiscsi_session *cs); +static struct cfiscsi_target *cfiscsi_target_find(struct cfiscsi_softc + *softc, const char *name); +static void cfiscsi_target_release(struct cfiscsi_target *ct); + +static struct cfiscsi_softc cfiscsi_softc; +extern struct ctl_softc *control_softc; +extern int ctl_disable; + +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); + +static struct mbuf * +cfiscsi_session_receive(struct cfiscsi_session *cs, size_t len) +{ + struct uio uio; + struct socket *so; + struct mbuf *m; + int error, flags; + + so = cs->cs_socket; + + if (so->so_error != 0) { + CFISCSI_WARN(("%s: connection error %d\n", + __func__, so->so_error)); + return (NULL); + } + + memset(&uio, 0, sizeof(uio)); + uio.uio_resid = len; + + flags = MSG_DONTWAIT; + error = soreceive(so, NULL, &uio, &m, NULL, &flags); + if (error != 0) { + CFISCSI_WARN(("%s: soreceive error %d\n", __func__, error)); + return (NULL); + } + if (uio.uio_resid != 0) { + m_freem(m); + CFISCSI_WARN(("%s: short read\n", __func__)); + return (NULL); + } + + return (m); +} + +static struct cfiscsi_pdu * +cfiscsi_pdu_new(struct cfiscsi_session *cs, int flags) +{ + struct cfiscsi_pdu *cp; + + refcount_acquire(&cs->cs_outstanding_pdus); + cp = uma_zalloc(cfiscsi_pdu_zone, flags | M_ZERO); + if (cp == NULL) { + CFISCSI_WARN(("%s: failed to allocate %zd bytes\n", + __func__, sizeof(*cp))); + refcount_release(&cs->cs_outstanding_pdus); + return (NULL); + } + + cp->cp_session = cs; + + return (cp); +} + +static void +cfiscsi_pdu_free(struct cfiscsi_pdu *cp) +{ + struct cfiscsi_session *cs; + + cs = cp->cp_session; + + m_freem(cp->cp_bhs_mbuf); + m_freem(cp->cp_ahs_mbuf); + m_freem(cp->cp_data_mbuf); + uma_zfree(cfiscsi_pdu_zone, cp); + refcount_release(&cs->cs_outstanding_pdus); +} + +/* + * Allocate cfiscsi_pdu with empty BHS to fill up by the caller. + */ +static struct cfiscsi_pdu * +cfiscsi_pdu_new_bhs(struct cfiscsi_session *cs, int flags) +{ + struct cfiscsi_pdu *cp; + + cp = cfiscsi_pdu_new(cs, flags); + if (cp == NULL) + return (NULL); + + cp->cp_bhs_mbuf = m_getm2(NULL, sizeof(struct iscsi_bhs), + flags, MT_DATA, M_PKTHDR); + if (cp->cp_bhs_mbuf == NULL) { + CFISCSI_WARN(("%s: failed to allocate %zd bytes\n", + __func__, sizeof(*cp))); + cfiscsi_pdu_free(cp); + return (NULL); + } + cp->cp_bhs = mtod(cp->cp_bhs_mbuf, struct iscsi_bhs *); + memset(cp->cp_bhs, 0, sizeof(struct iscsi_bhs)); + cp->cp_bhs_mbuf->m_len = sizeof(struct iscsi_bhs); + + return (cp); +} + +static struct cfiscsi_pdu * +cfiscsi_pdu_new_response(struct cfiscsi_pdu *request, int flags) +{ + + return (cfiscsi_pdu_new_bhs(request->cp_session, flags)); +} + +static int +cfiscsi_pdu_ahs_length(const struct cfiscsi_pdu *request) +{ + + return (request->cp_bhs->bhs_total_ahs_len * 4); +} + +static int +cfiscsi_pdu_data_segment_length(const struct cfiscsi_pdu *request) +{ + uint32_t len = 0; + + len += request->cp_bhs->bhs_data_segment_len[0]; + len <<= 8; + len += request->cp_bhs->bhs_data_segment_len[1]; + len <<= 8; + len += request->cp_bhs->bhs_data_segment_len[2]; + + return (len); +} + +static void +cfiscsi_pdu_set_data_segment_length(struct cfiscsi_pdu *response, uint32_t len) +{ + + response->cp_bhs->bhs_data_segment_len[2] = len; + response->cp_bhs->bhs_data_segment_len[1] = len >> 8; + response->cp_bhs->bhs_data_segment_len[0] = len >> 16; +} + +static size_t +cfiscsi_pdu_padding(const struct cfiscsi_pdu *cp) +{ + + if ((cp->cp_data_len % 4) != 0) + return (4 - (cp->cp_data_len % 4)); + + return (0); +} + +static size_t +cfiscsi_pdu_size(const struct cfiscsi_pdu *response) +{ + size_t len; + + KASSERT(response->cp_ahs_len == 0, ("responding with AHS")); + + len = sizeof(struct iscsi_bhs) + response->cp_data_len + + cfiscsi_pdu_padding(response); + if (response->cp_session->cs_header_digest) + len += ISCSI_HEADER_DIGEST_SIZE; + if (response->cp_session->cs_data_digest) + len += ISCSI_DATA_DIGEST_SIZE; + + return (len); +} + +static int +cfiscsi_pdu_receive_bhs(struct cfiscsi_pdu *request, size_t *availablep) +{ + struct mbuf *m; + + m = cfiscsi_session_receive(request->cp_session, + sizeof(struct iscsi_bhs)); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to receive BHS", __func__)); + return (-1); + } + + request->cp_bhs_mbuf = m_pullup(m, sizeof(struct iscsi_bhs)); + if (request->cp_bhs_mbuf == NULL) { + CFISCSI_WARN(("%s: m_pulup failed", __func__)); + return (-1); + } + request->cp_bhs = mtod(request->cp_bhs_mbuf, struct iscsi_bhs *); + + /* + * XXX: For architectures with strict alignment requirements + * we may need to allocate cp_bhs and copy the data into it. + */ + + *availablep -= sizeof(struct iscsi_bhs); + return (0); +} + +static int +cfiscsi_pdu_receive_ahs(struct cfiscsi_pdu *request, size_t *availablep) +{ + + request->cp_ahs_len = cfiscsi_pdu_ahs_length(request); + if (request->cp_ahs_len == 0) + return (0); + + request->cp_ahs_mbuf = cfiscsi_session_receive(request->cp_session, + request->cp_ahs_len); + if (request->cp_ahs_mbuf == NULL) { + CFISCSI_WARN(("%s: failed to receive AHS", __func__)); + return (-1); + } + + *availablep -= request->cp_ahs_len; + return (0); +} + +static uint32_t +cfiscsi_mbuf_to_crc32c(const struct mbuf *m0) +{ + uint32_t digest = 0xffffffff; + const struct mbuf *m; + + for (m = m0; m != NULL; m = m->m_next) + digest = calculate_crc32c(digest, + mtod(m, const void *), m->m_len); + + digest = digest ^ 0xffffffff; + + return (digest); +} + +static int +cfiscsi_pdu_check_header_digest(struct cfiscsi_pdu *request, size_t *availablep) +{ + struct mbuf *m; + uint32_t received_digest, valid_digest; + + if (request->cp_session->cs_header_digest == CTL_ISCSI_DIGEST_NONE) + return (0); + + m = cfiscsi_session_receive(request->cp_session, + ISCSI_HEADER_DIGEST_SIZE); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to receive header digest", __func__)); + return (-1); + } + + CTASSERT(sizeof(received_digest) == ISCSI_HEADER_DIGEST_SIZE); + memcpy(&received_digest, mtod(m, void *), ISCSI_HEADER_DIGEST_SIZE); + m_freem(m); + + *availablep -= ISCSI_HEADER_DIGEST_SIZE; + + /* + * XXX: Handle AHS. + */ + valid_digest = cfiscsi_mbuf_to_crc32c(request->cp_bhs_mbuf); + if (received_digest != valid_digest) { + CFISCSI_WARN(("%s: header digest check failed; got 0x%x, " + "should be 0x%x", __func__, received_digest, valid_digest)); + return (-1); + } + + return (0); +} + +/* + * Return the number of bytes that should be waiting in the receive socket + * before cfiscsi_pdu_receive_data_segment() gets called. + */ +static size_t +cfiscsi_pdu_data_segment_receive_len(const struct cfiscsi_pdu *request) +{ + size_t len; + + len = cfiscsi_pdu_data_segment_length(request); + if (len == 0) + return (0); + + /* + * Account for the parts of data segment already read from + * the socket buffer. + */ + KASSERT(len > request->cp_data_len, ("len <= request->cp_data_len")); + len -= request->cp_data_len; + + /* + * Don't always wait for the full data segment to be delivered + * to the socket; this might badly affect performance due to + * TCP window scaling. + */ + if (len > partial_receive_len) { +#if 0 + CFISCSI_DEBUG(("%s: need %zd bytes of data, limiting to %zd\n", + __func__, len, partial_receive_len)); +#endif + len = partial_receive_len; + + return (len); + } + + /* + * Account for padding. Note that due to the way code is written, + * the cfiscsi_pdu_receive_data_segment() must always receive padding + * along with the last part of data segment, because it would be + * impossible to tell whether we've already received the full data + * segment including padding, or without it. + */ + if ((len % 4) != 0) + len += 4 - (len % 4); + +#if 0 + CFISCSI_DEBUG(("%s: need %zd bytes of data\n", __func__, len)); +#endif + + return (len); +} + +static int +cfiscsi_pdu_receive_data_segment(struct cfiscsi_pdu *request, + size_t *availablep, bool *more_neededp) +{ + struct cfiscsi_session *cs; + size_t len, padding = 0; + struct mbuf *m; + + cs = request->cp_session; + + *more_neededp = false; + cs->cs_receive_len = 0; + + len = cfiscsi_pdu_data_segment_length(request); + if (len == 0) + return (0); + + if ((len % 4) != 0) + padding = 4 - (len % 4); + + /* + * Account for already received parts of data segment. + */ + KASSERT(len > request->cp_data_len, ("len <= request->cp_data_len")); + len -= request->cp_data_len; + + if (len + padding > *availablep) { + /* + * Not enough data in the socket buffer. Receive as much + * as we can. Don't receive padding, since, obviously, it's + * not the end of data segment yet. + */ +#if 0 + CFISCSI_DEBUG(("%s: limited from %zd to %zd\n", + __func__, len + padding, *availablep - padding)); +#endif + len = *availablep - padding; + *more_neededp = true; + padding = 0; + } + + /* + * Must not try to receive padding without at least one byte + * of actual data segment. + */ + if (len > 0) { + m = cfiscsi_session_receive(request->cp_session, len + padding); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to receive data segment", __func__)); + return (-1); + } + + if (request->cp_data_mbuf == NULL) + request->cp_data_mbuf = m; + else + m_cat(request->cp_data_mbuf, m); + + request->cp_data_len += len; + *availablep -= len + padding; + } else + CFISCSI_DEBUG(("%s: len 0\n", __func__)); + + if (*more_neededp) + cs->cs_receive_len = cfiscsi_pdu_data_segment_receive_len(request); + + return (0); +} + +static int +cfiscsi_pdu_check_data_digest(struct cfiscsi_pdu *request, size_t *availablep) +{ + struct mbuf *m; + uint32_t received_digest, valid_digest; + + if (request->cp_session->cs_data_digest == CTL_ISCSI_DIGEST_NONE) + return (0); + + if (request->cp_data_len == 0) + return (0); + + m = cfiscsi_session_receive(request->cp_session, + ISCSI_DATA_DIGEST_SIZE); + if (m == NULL) { + CFISCSI_WARN(("%s: failed to receive data digest", __func__)); + return (-1); + } + + CTASSERT(sizeof(received_digest) == ISCSI_DATA_DIGEST_SIZE); + memcpy(&received_digest, mtod(m, void *), ISCSI_DATA_DIGEST_SIZE); + m_freem(m); + + *availablep -= ISCSI_DATA_DIGEST_SIZE; + + /* + * Note that cp_data_mbuf also contains padding; since digest + * calculation is supposed to include that, we iterate over + * the entire cp_data_mbuf chain, not just cp_data_len bytes of it. + */ + valid_digest = cfiscsi_mbuf_to_crc32c(request->cp_data_mbuf); + if (received_digest != valid_digest) { + CFISCSI_WARN(("%s: data digest check failed; got 0x%x, " + "should be 0x%x", __func__, received_digest, valid_digest)); + return (-1); + } + + return (0); +} + +/* + * Somewhat contrary to the name, this attempts to receive only one + * "part" of PDU at a time; call it repeatedly until it returns non-NULL. + */ +static struct cfiscsi_pdu * +cfiscsi_session_receive_pdu(struct cfiscsi_session *cs, size_t *availablep) +{ + struct cfiscsi_pdu *request; + struct socket *so; + size_t len; + int error; + bool more_needed; + + so = cs->cs_socket; + + if (cs->cs_receive_state == CFISCSI_SESSION_STATE_BHS) { + KASSERT(cs->cs_receive_pdu == NULL, + ("cs->cs_receive_pdu != NULL")); + request = cfiscsi_pdu_new(cs, M_NOWAIT); + if (request == NULL) { + CFISCSI_WARN(("%s: failed to allocate PDU; " + "dropping connection\n", __func__)); + cfiscsi_session_terminate(cs); + return (NULL); + } + cs->cs_receive_pdu = request; + } else { + KASSERT(cs->cs_receive_pdu != NULL, + ("cs->cs_receive_pdu == NULL")); + request = cs->cs_receive_pdu; + } + + if (*availablep < cs->cs_receive_len) { +#if 0 + CFISCSI_DEBUG(("%s: not enough data; need %zd, " + "have %zd\n", __func__, cs->cs_receive_len, *availablep)); +#endif + return (NULL); + } + + switch (cs->cs_receive_state) { + case CFISCSI_SESSION_STATE_BHS: + //CFISCSI_DEBUG(("%s: receiving BHS\n", __func__)); + error = cfiscsi_pdu_receive_bhs(request, availablep); + if (error != 0) { + CFISCSI_WARN(("%s: failed to receive BHS; " + "dropping connection\n", __func__)); + break; + } + + /* + * We don't enforce any limit for AHS length; + * its length is stored in 8 bit field. + */ + + len = cfiscsi_pdu_data_segment_length(request); + if (len > cs->cs_max_data_segment_length) { + CFISCSI_WARN(("%s: received data segment " + "length %zd is larger than negotiated " + "MaxDataSegmentLength %zd; " + "dropping connection\n", __func__, + len, cs->cs_max_data_segment_length)); + break; + } + + cs->cs_receive_state = CFISCSI_SESSION_STATE_AHS; + cs->cs_receive_len = cfiscsi_pdu_ahs_length(request); + break; + + case CFISCSI_SESSION_STATE_AHS: + //CFISCSI_DEBUG(("%s: receiving AHS\n", __func__)); + error = cfiscsi_pdu_receive_ahs(request, availablep); + if (error != 0) { + CFISCSI_WARN(("%s: failed to receive AHS; " + "dropping connection\n", __func__)); + break; + } + cs->cs_receive_state = CFISCSI_SESSION_STATE_HEADER_DIGEST; + if (cs->cs_header_digest == CTL_ISCSI_DIGEST_NONE) + cs->cs_receive_len = 0; + else + cs->cs_receive_len = ISCSI_DATA_DIGEST_SIZE; + break; + + case CFISCSI_SESSION_STATE_HEADER_DIGEST: + //CFISCSI_DEBUG(("%s: receiving header digest\n", __func__)); + error = cfiscsi_pdu_check_header_digest(request, availablep); + if (error != 0) { + CFISCSI_WARN(("%s: header digest failed; " + "dropping connection\n", __func__)); + break; + } + + cs->cs_receive_state = CFISCSI_SESSION_STATE_DATA; + cs->cs_receive_len = cfiscsi_pdu_data_segment_receive_len(request); + break; + + case CFISCSI_SESSION_STATE_DATA: + //CFISCSI_DEBUG(("%s: receiving data segment\n", __func__)); + error = cfiscsi_pdu_receive_data_segment(request, availablep, + &more_needed); + if (error != 0) { + CFISCSI_WARN(("%s: failed to receive data segment;" + "dropping connection\n", + __func__)); + break; + } + + if (more_needed) + break; + + cs->cs_receive_state = CFISCSI_SESSION_STATE_DATA_DIGEST; + if (cs->cs_data_digest == CTL_ISCSI_DIGEST_NONE) + cs->cs_receive_len = 0; + else + cs->cs_receive_len = ISCSI_HEADER_DIGEST_SIZE; + break; + + case CFISCSI_SESSION_STATE_DATA_DIGEST: + //CFISCSI_DEBUG(("%s: receiving data digest\n", __func__)); + error = cfiscsi_pdu_check_data_digest(request, availablep); + if (error != 0) { + CFISCSI_WARN(("%s: data digest failed; " + "dropping connection\n", __func__)); + break; + } + + /* + * We've received complete PDU; reset the receive state machine + * and return the PDU. + */ + cs->cs_receive_state = CFISCSI_SESSION_STATE_BHS; + cs->cs_receive_len = sizeof(struct iscsi_bhs); + cs->cs_receive_pdu = NULL; + return (request); + + default: + panic("invalid cs_receive_state %d\n", cs->cs_receive_state); + } + + if (error != 0) { + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + } + + return (NULL); +} + +static void +cfiscsi_session_receive_pdus(struct cfiscsi_session *cs, size_t available) +{ + struct cfiscsi_pdu *request; + struct socket *so; + + so = cs->cs_socket; + + for (;;) { + if (cs->cs_terminating) + return; + + if (so->so_error != 0) { + CFISCSI_WARN(("%s: connection error %d; " + "dropping connection\n", __func__, so->so_error)); + cfiscsi_session_terminate(cs); + return; + } + + /* + * Loop until we have a complete PDU or there is not enough + * data in the socket buffer. + */ + if (available < cs->cs_receive_len) { +#if 0 + CFISCSI_DEBUG(("%s: not enough data; have %zd, " + "need %zd\n", __func__, available, + cs->cs_receive_len)); +#endif + return; + } + + request = cfiscsi_session_receive_pdu(cs, &available); + if (request == NULL) + continue; + + cfiscsi_pdu_update_cmdsn(request); + + if (request->cp_ahs_len > 0) { + CFISCSI_WARN(("%s: received PDU with unsupported " + "AHS; opcode 0x%x; dropping connection\n", + __func__, request->cp_bhs->bhs_opcode)); + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + + /* + * Handle the PDU; this includes e.g. receiving the remaining + * part of PDU and submitting the SCSI command to CTL + * or queueing a reply. The handling routine is responsible + * for freeing the PDU when it's no longer needed. + */ + switch (request->cp_bhs->bhs_opcode & + ~ISCSI_BHS_OPCODE_IMMEDIATE) { + case ISCSI_BHS_OPCODE_NOP_OUT: + cfiscsi_pdu_handle_nop_out(request); + break; + case ISCSI_BHS_OPCODE_SCSI_COMMAND: + cfiscsi_pdu_handle_scsi_command(request); + break; + case ISCSI_BHS_OPCODE_TASK_REQUEST: + cfiscsi_pdu_handle_task_request(request); + break; + case ISCSI_BHS_OPCODE_SCSI_DATA_OUT: + cfiscsi_pdu_handle_data_out(request); + break; + case ISCSI_BHS_OPCODE_LOGOUT_REQUEST: + cfiscsi_pdu_handle_logout_request(request); + break; + default: + CFISCSI_WARN(("%s: received PDU with unsupported " + "opcode 0x%x; dropping connection\n", + __func__, request->cp_bhs->bhs_opcode)); + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + } + } +} + +static void +cfiscsi_receive_thread(void *arg) +{ + struct cfiscsi_session *cs; + size_t available; + struct socket *so; + + cs = arg; + so = cs->cs_socket; + + for (;;) { + //CFISCSI_DEBUG(("%s: running\n", __func__)); + if (cs->cs_terminating) { + //CFISCSI_DEBUG(("%s: exiting\n", __func__)); + kthread_exit(); + return; + } + + SOCKBUF_LOCK(&so->so_rcv); + available = so->so_rcv.sb_cc; + if (available < cs->cs_receive_len) + cv_wait(&cs->cs_receive_cv, &so->so_rcv.sb_mtx); + SOCKBUF_UNLOCK(&so->so_rcv); + + cfiscsi_session_receive_pdus(cs, available); + } +} + +static int +cfiscsi_soupcall_receive(struct socket *so, void *arg, int waitflag) +{ + struct cfiscsi_session *cs; + + cs = arg; + cv_signal(&cs->cs_receive_cv); + return (SU_OK); +} + +static int +cfiscsi_pdu_send(struct cfiscsi_pdu *response) +{ + struct cfiscsi_session *cs; + struct iscsi_bhs_scsi_response *bhssr; + struct socket *so; + int error, ok; + bool advance_statsn = true; + size_t padding, pdu_len; + uint32_t digest, zero = 0; + + cs = response->cp_session; + so = cs->cs_socket; + + /* + * We're only using fields common for all the response + * (target -> initiator) PDUs. + */ + bhssr = (struct iscsi_bhs_scsi_response *)response->cp_bhs; + + /* + * 10.8.3: "The StatSN for this connection is not advanced + * after this PDU is sent." + */ + if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_R2T) + advance_statsn = false; + + /* + * 10.19.2: "However, when the Initiator Task Tag is set to 0xffffffff, + * StatSN for the connection is not advanced after this PDU is sent." + */ + if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_NOP_IN && + bhssr->bhssr_initiator_task_tag == 0xffffffff) + advance_statsn = false; + + /* + * See the comment below - StatSN is not meaningful and must + * not be advanced. + */ + if (bhssr->bhssr_opcode == ISCSI_BHS_OPCODE_SCSI_DATA_IN) + advance_statsn = false; + + CFISCSI_SESSION_LOCK(cs); + /* + * 10.7.3: "The fields StatSN, Status, and Residual Count + * only have meaningful content if the S bit is set to 1." + */ + if (bhssr->bhssr_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_IN) + bhssr->bhssr_statsn = htonl(cs->cs_statsn); + bhssr->bhssr_expcmdsn = htonl(cs->cs_cmdsn + 1); + bhssr->bhssr_maxcmdsn = htonl(cs->cs_cmdsn + maxcmdsn_delta); + CFISCSI_SESSION_UNLOCK(cs); + cfiscsi_pdu_set_data_segment_length(response, response->cp_data_len); + + pdu_len = cfiscsi_pdu_size(response); + + if (so->so_error != 0) { + CFISCSI_WARN(("%s: connection error %d\n", + __func__, so->so_error)); + return (1); + } + + if (cs->cs_header_digest != CTL_ISCSI_DIGEST_NONE) { + digest = cfiscsi_mbuf_to_crc32c(response->cp_bhs_mbuf); + ok = m_append(response->cp_bhs_mbuf, sizeof(digest), + (void *)&digest); + if (ok != 1) { + CFISCSI_WARN(("%s: failed to append header digest\n", + __func__)); + return (1); + } + } + + if (response->cp_data_len != 0) { + padding = cfiscsi_pdu_padding(response); + if (padding > 0) { + ok = m_append(response->cp_data_mbuf, padding, + (void *)&zero); + if (ok != 1) { + CFISCSI_WARN(("%s: failed to append padding\n", + __func__)); + return (1); + } + } + + if (cs->cs_data_digest != CTL_ISCSI_DIGEST_NONE) { + digest = cfiscsi_mbuf_to_crc32c(response->cp_data_mbuf); + + ok = m_append(response->cp_data_mbuf, sizeof(digest), + (void *)&digest); + if (ok != 1) { + CFISCSI_WARN(("%s: failed to append header " + "digest\n", __func__)); + return (1); + } + } + + m_cat(response->cp_bhs_mbuf, response->cp_data_mbuf); + response->cp_data_mbuf = NULL; + } + + response->cp_bhs_mbuf->m_pkthdr.len = pdu_len; + + error = sosend(so, NULL, NULL, response->cp_bhs_mbuf, + NULL, MSG_DONTWAIT, curthread); + response->cp_bhs_mbuf = NULL; /* Sosend consumes the mbuf. */ + if (error != 0) { + CFISCSI_WARN(("%s: sosend error %d\n", __func__, error)); + return (1); + } + + if (advance_statsn) { + CFISCSI_SESSION_LOCK(cs); + cs->cs_statsn++; + CFISCSI_SESSION_UNLOCK(cs); + } + + return (0); +} + +static void +cfiscsi_session_send_pdus(struct cfiscsi_session *cs) +{ + struct cfiscsi_pdu *response; + int error; + + CFISCSI_SESSION_LOCK(cs); + while (!TAILQ_EMPTY(&cs->cs_to_send)) { + response = TAILQ_FIRST(&cs->cs_to_send); + SOCKBUF_LOCK(&cs->cs_socket->so_snd); + /* + * The reason for the error check is that in case the connection + * is already dead, we want the code to try to send queued PDUs, + * fail, and free them. + */ + if (cs->cs_socket->so_error == 0 && + sbspace(&cs->cs_socket->so_snd) < + cfiscsi_pdu_size(response)) { + SOCKBUF_UNLOCK(&cs->cs_socket->so_snd); +#if 0 + CFISCSI_DEBUG(("%s: no space to send; " + "have %zd, need %zd\n", __func__, + sbspace(&cs->cs_socket->so_snd), + cfiscsi_pdu_size(response))); +#endif + break; + } else { +#if 0 + CFISCSI_DEBUG(("%s: have %zd of space to send\n", + __func__, sbspace(&cs->cs_socket->so_snd))); +#endif + } + + SOCKBUF_UNLOCK(&cs->cs_socket->so_snd); + TAILQ_REMOVE(&cs->cs_to_send, response, cp_next); + CFISCSI_SESSION_UNLOCK(cs); + error = cfiscsi_pdu_send(response); + if (error != 0) { + CFISCSI_WARN(("%s: failed to send PDU; " + "dropping connection\n", __func__)); + cfiscsi_pdu_free(response); + cfiscsi_session_terminate(cs); + } else + cfiscsi_pdu_free(response); + CFISCSI_SESSION_LOCK(cs); + } + CFISCSI_SESSION_UNLOCK(cs); +} + +static void +cfiscsi_send_thread(void *arg) +{ + struct cfiscsi_session *cs; + + cs = arg; + + for (;;) { + CFISCSI_SESSION_LOCK(cs); + cv_wait(&cs->cs_send_cv, &cs->cs_lock); + CFISCSI_SESSION_UNLOCK(cs); + + //CFISCSI_DEBUG(("%s: running\n", __func__)); + if (cs->cs_send_terminating) { + //CFISCSI_DEBUG(("%s: exiting\n", __func__)); + kthread_exit(); + return; + } + + cfiscsi_session_send_pdus(cs); + } +} + +static int +cfiscsi_soupcall_send(struct socket *so, void *arg, int waitflag) +{ + struct cfiscsi_session *cs; + + cs = arg; + cv_signal(&cs->cs_send_cv); + return (SU_OK); +} + +static void +cfiscsi_pdu_append_data(struct cfiscsi_pdu *response, const void *addr, size_t len) +{ + struct mbuf *mb, *newmb; + size_t copylen, off = 0; + + KASSERT(len > 0, ("len == 0")); + + newmb = m_getm2(NULL, len, M_WAITOK, MT_DATA, M_PKTHDR); + if (newmb == NULL) { + CFISCSI_WARN(("%s: failed to allocate mbuf for %zd bytes\n", + __func__, len)); + cfiscsi_session_terminate(response->cp_session); + return; + } + + for (mb = newmb; mb != NULL; mb = mb->m_next) { + copylen = min(M_TRAILINGSPACE(mb), len - off); + memcpy(mtod(mb, char *), (const char *)addr + off, copylen); + mb->m_len = copylen; + off += copylen; + } + KASSERT(off == len, ("%s: off != len", __func__)); + + if (response->cp_data_mbuf == NULL) { + response->cp_data_mbuf = newmb; + response->cp_data_len = len; + } else { + m_cat(response->cp_data_mbuf, newmb); + response->cp_data_len += len; + } +} + +static void +cfiscsi_pdu_queue(struct cfiscsi_pdu *cp) +{ + struct cfiscsi_session *cs; + + cs = cp->cp_session; + + CFISCSI_SESSION_LOCK(cs); + TAILQ_INSERT_TAIL(&cs->cs_to_send, cp, cp_next); + CFISCSI_SESSION_UNLOCK(cs); + cv_signal(&cs->cs_send_cv); +} + +static void +cfiscsi_pdu_queue_head(struct cfiscsi_pdu *cp) +{ + struct cfiscsi_session *cs; + + cs = cp->cp_session; + + CFISCSI_SESSION_LOCK(cs); + TAILQ_INSERT_HEAD(&cs->cs_to_send, cp, cp_next); + CFISCSI_SESSION_UNLOCK(cs); + cv_signal(&cs->cs_send_cv); +} + +static void +cfiscsi_pdu_update_cmdsn(const struct cfiscsi_pdu *request) +{ + const struct iscsi_bhs_scsi_command *bhssc; + struct cfiscsi_session *cs; + uint32_t cmdsn, expstatsn; + + cs = request->cp_session; + + /* + * Data-Out PDUs don't contain CmdSN. + */ + if ((request->cp_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_DATA_OUT) + return; + + /* + * We're only using fields common for all the request + * (initiator -> target) PDUs. + */ + bhssc = (const struct iscsi_bhs_scsi_command *)request->cp_bhs; + cmdsn = ntohl(bhssc->bhssc_cmdsn); + expstatsn = ntohl(bhssc->bhssc_expstatsn); + + CFISCSI_SESSION_LOCK(cs); +#if 0 + if (expstatsn != cs->cs_statsn) { + CFISCSI_DEBUG(("%s: received PDU with ExpStatSN %d, " + "while current StatSN is %d\n", __func__, expstatsn, + cs->cs_statsn)); + cs->cs_statsn = expstatsn; + } +#endif + + /* + * Immediate commands don't advance CmdSN. + */ + if (request->cp_bhs->bhs_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) { + CFISCSI_SESSION_UNLOCK(cs); + return; + } + + /* + * XXX: The target MUST silently ignore any non-immediate command + * outside of this range or non-immediate duplicates within + * the range. + */ + if (cmdsn != cs->cs_cmdsn + 1) { + CFISCSI_DEBUG(("%s: received PDU with CmdSN %d, " + "while previous CmdSN was %d\n", __func__, cmdsn, + cs->cs_cmdsn)); +#if 0 + CFISCSI_SESSION_UNLOCK(cs); + return; +#endif + } + + /* + * XXX: The CmdSN of the rejected command PDU (if it is a non-immediate + * command) MUST NOT be considered received by the target + * (i.e., a command sequence gap must be assumed for the CmdSN) + */ + + cs->cs_cmdsn = cmdsn; + CFISCSI_SESSION_UNLOCK(cs); +} + +static uint32_t +cfiscsi_decode_lun(uint64_t encoded) +{ + uint8_t lun[8]; + uint32_t result; + + /* + * The LUN field in iSCSI PDUs may look like an ordinary 64 bit number, + * but is in fact an evil, multidimensional structure defined + * in SCSI Architecture Model 5 (SAM-5), section 4.6. + */ + memcpy(lun, &encoded, sizeof(lun)); + switch (lun[0] & 0xC0) { + case 0x00: + if ((lun[0] & 0x3f) != 0 || lun[2] != 0 || lun[3] != 0 || + lun[4] != 0 || lun[5] != 0 || lun[6] != 0 || lun[7] != 0) { + CFISCSI_WARN(("%s: malformed LUN " + "(peripheral device addressing method): 0x%jx\n", + __func__, (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(("%s: malformed LUN " + "(flat address space addressing method): 0x%jx\n", + __func__, (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(("%s: malformed LUN (extended flat " + "address space addressing method): 0x%jx\n", + __func__, (uintmax_t)encoded)); + result = 0xffffffff; + break; + } + result = (lun[1] << 16) + (lun[2] << 8) + lun[3]; + default: + CFISCSI_WARN(("%s: unsupported LUN format 0x%jx\n", + __func__, (uintmax_t)encoded)); + result = 0xffffffff; + break; + } + + return (result); +} + +static void +cfiscsi_pdu_handle_nop_out(struct cfiscsi_pdu *request) +{ + struct iscsi_bhs_nop_out *bhsno; + struct iscsi_bhs_nop_in *bhsni; + struct cfiscsi_pdu *response; + + bhsno = (struct iscsi_bhs_nop_out *)request->cp_bhs; + + if (bhsno->bhsno_initiator_task_tag == 0xffffffff) { + CFISCSI_SESSION_LOCK(request->cp_session); + request->cp_session->cs_ping_waiting = 0; + CFISCSI_SESSION_UNLOCK(request->cp_session); + cfiscsi_pdu_free(request); + return; + } + + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + cfiscsi_pdu_free(request); + return; + } + bhsni = (struct iscsi_bhs_nop_in *)response->cp_bhs; + bhsni->bhsni_opcode = ISCSI_BHS_OPCODE_NOP_IN; + bhsni->bhsni_flags = 0x80; + bhsni->bhsni_initiator_task_tag = bhsno->bhsno_initiator_task_tag; + bhsni->bhsni_target_transfer_tag = 0xffffffff; + + response->cp_data_len = request->cp_data_len; + response->cp_data_mbuf = request->cp_data_mbuf; + request->cp_data_len = 0; + request->cp_data_mbuf = NULL; + + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue_head(response); +} + +static void +cfiscsi_pdu_handle_scsi_command(struct cfiscsi_pdu *request) +{ + struct iscsi_bhs_scsi_command *bhssc; + struct cfiscsi_session *cs; + union ctl_io *io; + int error; + + cs = request->cp_session; + bhssc = (struct iscsi_bhs_scsi_command *)request->cp_bhs; + //CFISCSI_DEBUG(("%s: initiator task tag 0x%x\n", + // __func__, bhssc->bhssc_initiator_task_tag)); + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_WARN(("%s: can't allocate ctl_io\n", __func__)); + cfiscsi_pdu_free(request); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = request; + io->io_hdr.io_type = CTL_IO_SCSI; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + io->io_hdr.nexus.targ_lun = cfiscsi_decode_lun(bhssc->bhssc_lun); + io->io_hdr.nexus.lun_map_fn = cfiscsi_map_lun; + io->io_hdr.nexus.lun_map_arg = cs; + io->scsiio.tag_num = bhssc->bhssc_initiator_task_tag; + switch ((bhssc->bhssc_flags & BHSSC_FLAGS_ATTR)) { + case 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_WARN(("%s: unhandled tag type %d\n", + __func__, (bhssc->bhssc_flags & BHSSC_FLAGS_ATTR))); + break; + } + io->scsiio.cdb_len = sizeof(bhssc->bhssc_cdb); /* Which is 16. */ + memcpy(io->scsiio.cdb, bhssc->bhssc_cdb, sizeof(bhssc->bhssc_cdb)); + refcount_acquire(&cs->cs_outstanding_ctl_pdus); + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_WARN(("%s: ctl_queue() failed; error %d\n", + __func__, error)); + ctl_free_io(io); + refcount_release(&cs->cs_outstanding_ctl_pdus); + cfiscsi_pdu_free(request); + } +} + +static void +cfiscsi_pdu_handle_task_request(struct cfiscsi_pdu *request) +{ + struct iscsi_bhs_task_management_request *bhstmr; + struct iscsi_bhs_task_management_response *bhstmr2; + struct cfiscsi_pdu *response; + struct cfiscsi_session *cs; + union ctl_io *io; + int error; + + cs = request->cp_session; + bhstmr = (struct iscsi_bhs_task_management_request *)request->cp_bhs; + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_WARN(("%s: can't allocate ctl_io\n", __func__)); + cfiscsi_pdu_free(request); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = request; + io->io_hdr.io_type = CTL_IO_TASK; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + io->io_hdr.nexus.targ_lun = cfiscsi_decode_lun(bhstmr->bhstmr_lun); + io->io_hdr.nexus.lun_map_fn = cfiscsi_map_lun; + io->io_hdr.nexus.lun_map_arg = cs; + io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ + + switch (bhstmr->bhstmr_function & ~0x80) { + case BHSTMR_FUNCTION_ABORT_TASK: +#if 0 + CFISCSI_DEBUG(("%s: BHSTMR_FUNCTION_ABORT_TASK\n", __func__)); +#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_DEBUG(("%s: BHSTMR_FUNCTION_LOGICAL_UNIT_RESET\n", + __func__)); +#endif + io->taskio.task_action = CTL_TASK_LUN_RESET; + break; + case BHSTMR_FUNCTION_TARGET_COLD_RESET: +#if 0 + CFISCSI_DEBUG(("%s: BHSTMR_FUNCTION_TARGET_COLD_RESET\n", + __func__)); +#endif + io->taskio.task_action = CTL_TASK_BUS_RESET; + break; + default: + CFISCSI_DEBUG(("%s: unsupported function 0x%x\n", + __func__, bhstmr->bhstmr_function & ~0x80)); + ctl_free_io(io); + + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + cfiscsi_pdu_free(request); + return; + } + bhstmr2 = (struct iscsi_bhs_task_management_response *) + response->cp_bhs; + bhstmr2->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_RESPONSE; + bhstmr2->bhstmr_flags = 0x80; + bhstmr2->bhstmr_response = + BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED; + bhstmr2->bhstmr_initiator_task_tag = + bhstmr->bhstmr_initiator_task_tag; + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); + return; + } + + refcount_acquire(&cs->cs_outstanding_ctl_pdus); + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_WARN(("%s: ctl_queue() failed; error %d\n", + __func__, error)); + ctl_free_io(io); + refcount_release(&cs->cs_outstanding_ctl_pdus); + cfiscsi_pdu_free(request); + } +} + +static void +cfiscsi_pdu_handle_data_out(struct cfiscsi_pdu *request) +{ + struct iscsi_bhs_data_out *bhsdo; + struct cfiscsi_session *cs; + struct cfiscsi_data_wait *cdw = NULL; + struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; + union ctl_io *io; + size_t copy_len, off; + int ctl_sg_count; + + cs = request->cp_session; + bhsdo = (struct iscsi_bhs_data_out *)request->cp_bhs; + + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH(cdw, &cs->cs_waiting_for_data_out, cdw_next) { +#if 0 + CFISCSI_DEBUG(("%s: have ttt 0x%x, itt 0x%x; looking for " + "ttt 0x%x, itt 0x%x\n", __func__, + bhsdo->bhsdo_target_transfer_tag, + bhsdo->bhsdo_initiator_task_tag, + cdw->cdw_target_transfer_tag, cdw->cdw_initiator_task_tag)); +#endif + if (bhsdo->bhsdo_target_transfer_tag == + cdw->cdw_target_transfer_tag) + break; + } + CFISCSI_SESSION_UNLOCK(cs); + if (cdw == NULL) { + CFISCSI_WARN(("%s: data transfer tag 0x%x, initiator task tag " + "0x%x, not found\n", __func__, + bhsdo->bhsdo_target_transfer_tag, + bhsdo->bhsdo_initiator_task_tag)); + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + + io = cdw->cdw_ctl_io; + KASSERT((io->io_hdr.flags & CTL_FLAG_DATA_MASK) != CTL_FLAG_DATA_IN, + ("CTL_FLAG_DATA_IN")); + +#if 0 + CFISCSI_DEBUG(("%s: received %zd bytes out of %d\n", + __func__, request->cp_data_len, io->scsiio.kern_total_len)); +#endif + + if (io->scsiio.kern_sg_entries > 0) { + ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; + ctl_sg_count = io->scsiio.kern_sg_entries; + } else { + ctl_sglist = &ctl_sg_entry; + ctl_sglist->addr = io->scsiio.kern_data_ptr; + ctl_sglist->len = io->scsiio.kern_data_len; + ctl_sg_count = 1; + } +#if 0 + if (ctl_sg_count > 1) + CFISCSI_DEBUG(("%s: ctl_sg_count = %d\n", + __func__, ctl_sg_count)); +#endif + off = 0; + for (;;) { + KASSERT(cdw->cdw_sg_index < ctl_sg_count, + ("cdw->cdw_sg_index >= ctl_sg_count")); + if (cdw->cdw_sg_len == 0) { + cdw->cdw_sg_addr = ctl_sglist[cdw->cdw_sg_index].addr; + cdw->cdw_sg_len = ctl_sglist[cdw->cdw_sg_index].len; + } + copy_len = request->cp_data_len - off; + if (copy_len > cdw->cdw_sg_len) + copy_len = cdw->cdw_sg_len; + + m_copydata(request->cp_data_mbuf, off, copy_len, + cdw->cdw_sg_addr); + cdw->cdw_sg_addr += copy_len; + cdw->cdw_sg_len -= copy_len; + off += copy_len; + io->scsiio.ext_data_filled += copy_len; + + if (cdw->cdw_sg_len == 0) { + if (cdw->cdw_sg_index == ctl_sg_count - 1) + break; + cdw->cdw_sg_index++; + } + if (off == request->cp_data_len) + break; + } + + if (off < request->cp_data_len) { + CFISCSI_WARN(("%s: received too much data: got %zd bytes, " + "expected %zd\n", __func__, request->cp_data_len, off)); + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + + if (bhsdo->bhsdo_flags & BHSDO_FLAGS_F || + io->scsiio.ext_data_filled == io->scsiio.kern_total_len) { + if ((bhsdo->bhsdo_flags & BHSDO_FLAGS_F) == 0) + CFISCSI_WARN(("%s: got the final packet without " + "the F flag; flags = 0x%x\n", + __func__, bhsdo->bhsdo_flags)); + if (io->scsiio.ext_data_filled != io->scsiio.kern_total_len) + CFISCSI_WARN(("%s: got the final packet, but the " + "transmitted size was %zd bytes instead of %d\n", + __func__, (size_t)io->scsiio.ext_data_filled, + io->scsiio.kern_total_len)); +#if 0 + CFISCSI_DEBUG(("%s: no longer expecting Data-Out with target " + "transfer tag 0x%x\n", __func__, + cdw->cdw_target_transfer_tag)); +#endif + CFISCSI_SESSION_LOCK(cs); + TAILQ_REMOVE(&cs->cs_waiting_for_data_out, cdw, cdw_next); + CFISCSI_SESSION_UNLOCK(cs); + uma_zfree(cfiscsi_data_wait_zone, cdw); + + io->scsiio.be_move_done(io); + } + + cfiscsi_pdu_free(request); +} + +static void +cfiscsi_pdu_handle_logout_request(struct cfiscsi_pdu *request) +{ + struct iscsi_bhs_logout_request *bhslr; + struct iscsi_bhs_logout_response *bhslr2; + struct cfiscsi_pdu *response; + struct cfiscsi_session *cs; + + cs = request->cp_session; + bhslr = (struct iscsi_bhs_logout_request *)request->cp_bhs; + switch (bhslr->bhslr_reason & 0x7f) { + case BHSLR_REASON_CLOSE_SESSION: + case BHSLR_REASON_CLOSE_CONNECTION: + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + bhslr2 = (struct iscsi_bhs_logout_response *)response->cp_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; + bhslr2->bhslr_flags = 0x80; + bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; + bhslr2->bhslr_initiator_task_tag = + bhslr->bhslr_initiator_task_tag; + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); + cfiscsi_session_terminate(cs); + break; + case BHSLR_REASON_REMOVE_FOR_RECOVERY: + response = cfiscsi_pdu_new_response(request, M_NOWAIT); + if (response == NULL) { + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + return; + } + bhslr2 = (struct iscsi_bhs_logout_response *)response->cp_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; + bhslr2->bhslr_flags = 0x80; + bhslr2->bhslr_response = BHSLR_RESPONSE_RECOVERY_NOT_SUPPORTED; + bhslr2->bhslr_initiator_task_tag = + bhslr->bhslr_initiator_task_tag; + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); + break; + default: + CFISCSI_WARN(("%s: invalid reason 0%x; dropping connection\n", + __func__, bhslr->bhslr_reason)); + cfiscsi_pdu_free(request); + cfiscsi_session_terminate(cs); + break; + } +} + +static void +cfiscsi_ping(void *context) +{ + struct cfiscsi_pdu *cp; + struct iscsi_bhs_nop_in *bhsni; + struct cfiscsi_session *cs; + + cs = context; + + if (cs->cs_terminating) + return; + + if (cs->cs_ping_waiting == 1) { + CFISCSI_DEBUG(("%s: still waiting for ping reply (NOP-Out)\n", + __func__)); + } else if (cs->cs_ping_waiting >= 2) { + CFISCSI_WARN(("%s: no ping reply (NOP-Out) after %d seconds; " + "dropping connection\n", __func__, ping_interval * 3)); + cfiscsi_session_terminate(cs); + return; + } + + CFISCSI_SESSION_LOCK(cs); + cs->cs_ping_waiting++; + CFISCSI_SESSION_UNLOCK(cs); + + cp = cfiscsi_pdu_new_bhs(cs, M_WAITOK); + bhsni = (struct iscsi_bhs_nop_in *)cp->cp_bhs; + bhsni->bhsni_opcode = ISCSI_BHS_OPCODE_NOP_IN; + bhsni->bhsni_flags = 0x80; + bhsni->bhsni_initiator_task_tag = 0xffffffff; + + /* + * We wan't this to get replied to as soon as possible. There might + * be quite a few PDUs waiting in the send queue, so queue it at the + * head instead of appending at the tail. + */ + cfiscsi_pdu_queue_head(cp); + + callout_schedule(&cs->cs_ping_callout, ping_interval * hz); +} + +static int +cfiscsi_session_register_initiator(struct cfiscsi_session *cs) +{ + int error, i; + struct cfiscsi_softc *softc; + + KASSERT(cs->cs_ctl_initid == -1, ("already registered")); + + softc = cs->cs_target->ct_softc; + + mtx_lock(&softc->lock); + for (i = 0; i < softc->max_initiators; i++) { + if (softc->ctl_initids[i] == 0) + break; + } + if (i == softc->max_initiators) { + CFISCSI_WARN(("%s: too many concurrent sessions (%d)\n", + __func__, softc->max_initiators)); + mtx_unlock(&softc->lock); + return (1); + } + softc->ctl_initids[i] = 1; + mtx_unlock(&softc->lock); + +#if 0 + CFISCSI_DEBUG(("%s: adding initiator id %d, max %d\n", + __func__, 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_WARN(("%s: ctl_add_initiator failed with error %d\n", + __func__, error)); + mtx_lock(&softc->lock); + softc->ctl_initids[cs->cs_ctl_initid] = 0; + mtx_unlock(&softc->lock); + cs->cs_ctl_initid = -1; + return (1); + } + + return (0); +} + +static void +cfiscsi_session_unregister_initiator(struct cfiscsi_session *cs) +{ + int error; + struct cfiscsi_softc *softc; + + if (cs->cs_ctl_initid == -1) + return; + + softc = cs->cs_target->ct_softc; + + error = ctl_remove_initiator(softc->fe.targ_port, cs->cs_ctl_initid); + if (error != 0) { + CFISCSI_WARN(("%s: ctl_remove_initiator failed " + "with error %d\n", __func__, error)); + } + mtx_lock(&softc->lock); + softc->ctl_initids[cs->cs_ctl_initid] = 0; + mtx_unlock(&softc->lock); + cs->cs_ctl_initid = -1; +} + +static struct cfiscsi_session * +cfiscsi_session_new(struct cfiscsi_softc *softc, int socket, + const char *initiator_name, const char *initiator_addr, + const char *target, uint32_t cmdsn, uint32_t statsn, + uint32_t max_recv_data_segment_length, uint32_t header_digest, + uint32_t data_digest, int portal_group_tag) +{ + struct cfiscsi_session *cs; + struct file *fp; + struct sockopt opt; + int error, one = 1; +#if 1 + size_t bufsize; +#else + struct sockbuf *sb; +#endif + + cs = malloc(sizeof(*cs), M_CFISCSI, M_NOWAIT | M_ZERO); + if (cs == NULL) { + CFISCSI_WARN(("%s: malloc failed\n", __func__)); + return (NULL); + } + cs->cs_ctl_initid = -1; + cs->cs_target = cfiscsi_target_find(softc, target); + if (cs->cs_target == NULL) { + CFISCSI_WARN(("%s: target not found\n", __func__)); + free(cs, M_CFISCSI); + return (NULL); + } + if ((error = fget(curthread, socket, CAP_SOCK_ALL, &fp)) != 0) { + CFISCSI_WARN(("%s: fget failed, error %d\n", __func__, error)); + goto fail; + } + if (fp->f_type != DTYPE_SOCKET) { + CFISCSI_WARN(("%s: passed file is not a socket\n", __func__)); + fdrop(fp, curthread); + goto fail; + } + cs->cs_socket = fp->f_data; + if (cs->cs_socket->so_type != SOCK_STREAM) { + CFISCSI_WARN(("%s: passed socket is not a stream\n", __func__)); + fdrop(fp, curthread); + goto fail; + } + /* + * Steal the socket from the userland. + */ + fp->f_ops = &badfileops; + fp->f_data = NULL; + fdrop(fp, curthread); + + /* + * Register initiator with CTL. + */ + cfiscsi_session_register_initiator(cs); + + refcount_init(&cs->cs_outstanding_pdus, 0); + refcount_init(&cs->cs_outstanding_ctl_pdus, 0); + TAILQ_INIT(&cs->cs_to_send); + TAILQ_INIT(&cs->cs_waiting_for_data_out); + mtx_init(&cs->cs_lock, "cfiscsi_lock", NULL, MTX_DEF); + cv_init(&cs->cs_send_cv, "cfiscsi_tx"); + cv_init(&cs->cs_receive_cv, "cfiscsi_rx"); + + /* + * First PDU of Full Feature phase has the same CmdSN as the last + * PDU from the Login Phase received from the initiator. Thus, + * the -1 below. + */ + cs->cs_portal_group_tag = portal_group_tag; + cs->cs_cmdsn = cmdsn - 1; + cs->cs_statsn = statsn; + cs->cs_max_data_segment_length = max_recv_data_segment_length; + cs->cs_header_digest = header_digest; + cs->cs_data_digest = data_digest; + + strlcpy(cs->cs_initiator_name, + initiator_name, sizeof(cs->cs_initiator_name)); + strlcpy(cs->cs_initiator_addr, + initiator_addr, sizeof(cs->cs_initiator_addr)); + + mtx_lock(&softc->lock); + cs->cs_id = softc->last_session_id + 1; + softc->last_session_id++; + mtx_unlock(&softc->lock); + + cs->cs_receive_state = CFISCSI_SESSION_STATE_BHS; + cs->cs_receive_len = sizeof(struct iscsi_bhs); + + /* + * Use max available sockbuf size for sending. Do it manually + * instead of sbreserve(9) to work around resource limits. + * + * XXX: This kind of sucks. On one hand, we don't currently support + * sending a part of data segment; we always do it in one piece, + * so we have to make sure it can fit in the socket buffer. + * Once I've implemented partial send, we'll get rid of this + * and use autoscaling. + */ +#if 1 + bufsize = (sizeof(struct iscsi_bhs) + + cs->cs_max_data_segment_length) * 8; + error = soreserve(cs->cs_socket, bufsize, bufsize); + if (error != 0) { + error = soreserve(cs->cs_socket, bufsize, bufsize); + if (error != 0) { + CFISCSI_WARN(("%s: soreserve failed with error %d\n", + __func__, error)); + goto fail; + } + } +#elif 0 + /* + * Poor performance. + */ + bufsize = (sizeof(struct iscsi_bhs) + + cs->cs_max_data_segment_length) * 8; + sb = &cs->cs_socket->so_snd; + error = sbreserve(sb, bufsize, cs->cs_socket, NULL); + if (error == 0) { + bufsize = (sizeof(struct iscsi_bhs) + + cs->cs_max_data_segment_length) * 2; + error = sbreserve(sb, bufsize, cs->cs_socket, NULL); + if (error == 0) { + CFISCSI_WARN(("%s: sbreserve failed with error %d\n", + __func__, error)); + goto fail; + } + } +#elif 0 + /* + * Horrible performance. + */ + sb = &cs->cs_socket->so_snd; + SOCKBUF_LOCK(sb); + sb->sb_mbmax = SB_MAX; + if (sb->sb_lowat > sb->sb_hiwat) + sb->sb_lowat = sb->sb_hiwat; + if (sb->sb_lowat == 0) + sb->sb_lowat = MCLBYTES; + SOCKBUF_UNLOCK(sb); +#endif + + /* + * Disable Nagle. + */ + bzero(&opt, sizeof(opt)); + opt.sopt_dir = SOPT_SET; + opt.sopt_level = IPPROTO_TCP; + opt.sopt_name = TCP_NODELAY; + opt.sopt_val = &one; + opt.sopt_valsize = sizeof(one); + error = sosetopt(cs->cs_socket, &opt); + if (error != 0) { + CFISCSI_WARN(("%s: disabling TCP_NODELAY failed " + "with error %d\n", __func__, error)); + goto fail; + } + + /* + * Start the transmit/receive threads. + */ + error = kthread_add(cfiscsi_send_thread, cs, NULL, NULL, 0, 0, + "cfiscsitx"); + if (error != 0) { + CFISCSI_WARN(("%s: kthread_add(9) failed with error %d\n", + __func__, error)); + goto fail; + } + + error = kthread_add(cfiscsi_receive_thread, cs, NULL, NULL, 0, 0, + "cfiscsirx"); + if (error != 0) { + CFISCSI_WARN(("%s: kthread_add(9) failed with error %d\n", + __func__, error)); + goto fail; + } + + /* + * XXX: Set the low watermark on the send socket, so we don't + * get called too often. + */ + + /* + * Register socket upcall, to get notified about incoming PDUs + * and free space to send outgoing ones. + */ + SOCKBUF_LOCK(&cs->cs_socket->so_snd); + soupcall_set(cs->cs_socket, SO_SND, cfiscsi_soupcall_send, cs); + SOCKBUF_UNLOCK(&cs->cs_socket->so_snd); + SOCKBUF_LOCK(&cs->cs_socket->so_rcv); + soupcall_set(cs->cs_socket, SO_RCV, cfiscsi_soupcall_receive, cs); + SOCKBUF_UNLOCK(&cs->cs_socket->so_rcv); + + mtx_lock(&softc->lock); + TAILQ_INSERT_TAIL(&softc->sessions, cs, cs_next); + mtx_unlock(&softc->lock); + + /* + * Kick the receive thread. + */ + cv_signal(&cs->cs_receive_cv); + + /* + * Start pinging the initiator. + */ + callout_init(&cs->cs_ping_callout, 1); + callout_reset(&cs->cs_ping_callout, ping_interval * hz, + cfiscsi_ping, cs); + + return (cs); +fail: + CFISCSI_WARN(("%s: failed\n", __func__)); + cfiscsi_session_unregister_initiator(cs); + if (cs->cs_socket != NULL) + soclose(cs->cs_socket); + if (cs->cs_target != NULL) + cfiscsi_target_release(cs->cs_target); + free(cs, M_CFISCSI); + return (NULL); +} + +static void +cfiscsi_session_delete(struct cfiscsi_session *cs) +{ + struct cfiscsi_softc *softc; + + softc = cs->cs_target->ct_softc; + + KASSERT(cs->cs_outstanding_ctl_pdus == 0, + ("destroying session with outstanding CTL pdus")); + KASSERT(cs->cs_outstanding_pdus == 0, + ("destroying session with outstanding pdus")); + KASSERT(TAILQ_EMPTY(&cs->cs_to_send), + ("destroying session with non-empty queue")); + KASSERT(TAILQ_EMPTY(&cs->cs_waiting_for_data_out), + ("destroying session with non-empty queue")); + + cfiscsi_session_unregister_initiator(cs); + cfiscsi_target_release(cs->cs_target); + soclose(cs->cs_socket); + + mtx_lock(&softc->lock); + TAILQ_REMOVE(&softc->sessions, cs, cs_next); + mtx_unlock(&softc->lock); + + free(cs, M_CFISCSI); +} + +static void +cfiscsi_session_terminate_tasks(struct cfiscsi_session *cs) +{ + struct cfiscsi_data_wait *cdw, *tmpcdw; + union ctl_io *io; + int error; + +#ifdef notyet + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_WARN(("%s: can't allocate ctl_io\n", __func__)); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = NULL; + io->io_hdr.io_type = CTL_IO_TASK; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + io->io_hdr.nexus.targ_lun = lun; + io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ + io->taskio.task_action = CTL_TASK_ABORT_TASK_SET; + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_WARN(("%s: ctl_queue() failed; error %d\n", + __func__, error)); + ctl_free_io(io); + } +#else + /* + * CTL doesn't currently support CTL_TASK_ABORT_TASK_SET, so instead + * just iterate over tasks that are waiting for something - data - and + * terminate those. + */ + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH_SAFE(cdw, + &cs->cs_waiting_for_data_out, cdw_next, tmpcdw) { + io = ctl_alloc_io(cs->cs_target->ct_softc->fe.ctl_pool_ref); + if (io == NULL) { + CFISCSI_WARN(("%s: can't allocate ctl_io\n", __func__)); + return; + } + ctl_zero_io(io); + io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = NULL; + io->io_hdr.io_type = CTL_IO_TASK; + io->io_hdr.nexus.initid.id = cs->cs_ctl_initid; + io->io_hdr.nexus.targ_port = + cs->cs_target->ct_softc->fe.targ_port; + io->io_hdr.nexus.targ_target.id = 0; + //io->io_hdr.nexus.targ_lun = lun; /* Not needed? */ + io->taskio.tag_type = CTL_TAG_SIMPLE; /* XXX */ + io->taskio.task_action = CTL_TASK_ABORT_TASK; + io->taskio.tag_num = cdw->cdw_initiator_task_tag; + error = ctl_queue(io); + if (error != CTL_RETVAL_COMPLETE) { + CFISCSI_WARN(("%s: ctl_queue() failed; error %d\n", + __func__, error)); + ctl_free_io(io); + return; + } +#if 0 + CFISCSI_DEBUG(("%s: removing csw for initiator task tag " + "0x%x\n", __func__, 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_terminate_callout(void *context) +{ + struct cfiscsi_session *cs; + struct cfiscsi_pdu *response; + + cs = context; + + KASSERT(cs->cs_terminating > 0, ("cs_terminating == 0")); + + cfiscsi_session_terminate_tasks(cs); + callout_drain(&cs->cs_ping_callout); + cv_signal(&cs->cs_receive_cv); + cv_signal(&cs->cs_send_cv); + + cs->cs_terminating++; + if (cs->cs_receive_pdu != NULL) { +#if 0 + CFISCSI_DEBUG(("%s: freeing partially received PDU\n", + __func__)); +#endif + cfiscsi_pdu_free(cs->cs_receive_pdu); + cs->cs_receive_pdu = NULL; + } + + /* + * Wait up to 30 seconds to send out queued PDUs. + */ + if (cs->cs_terminating <= 30 && cs->cs_outstanding_pdus != 0) { + /* + * Give the send upcall 30 seconds to send PDUs + * to the initiator. + */ + CFISCSI_DEBUG(("%s: can't terminate; still have %d PDUs " + "outstanding, %d queued to CTL\n", __func__, + cs->cs_outstanding_pdus, cs->cs_outstanding_ctl_pdus)); + callout_schedule(&cs->cs_terminate_callout, 1 * hz); + return; + } + + if (cs->cs_send_terminating == 0) { + cs->cs_send_terminating = 1; + cv_signal(&cs->cs_send_cv); + callout_schedule(&cs->cs_terminate_callout, 1 * hz); + return; + } + + soshutdown(cs->cs_socket, SHUT_RDWR); + + /* + * Receive upcall gets deleted earlier, in cfiscsi_session_terminate(). + */ + SOCKBUF_LOCK(&cs->cs_socket->so_snd); + soupcall_clear(cs->cs_socket, SO_SND); + SOCKBUF_UNLOCK(&cs->cs_socket->so_snd); + + /* + * Remove any outstanding PDUs from the send queue. + */ + while (!TAILQ_EMPTY(&cs->cs_to_send)) { + response = TAILQ_FIRST(&cs->cs_to_send); + cfiscsi_pdu_free(response); + TAILQ_REMOVE(&cs->cs_to_send, response, cp_next); + } + + cfiscsi_session_delete(cs); +} + +static void +cfiscsi_session_terminate(struct cfiscsi_session *cs) +{ + + CFISCSI_SESSION_LOCK(cs); + if (cs->cs_terminating != 0) { + CFISCSI_SESSION_UNLOCK(cs); + return; + } + cs->cs_terminating = 1; + CFISCSI_SESSION_UNLOCK(cs); + + /* + * Unregister the receive socket upcall, so that new requests + * don't get delivered any more. + */ + SOCKBUF_LOCK(&cs->cs_socket->so_rcv); + soupcall_clear(cs->cs_socket, SO_RCV); + SOCKBUF_UNLOCK(&cs->cs_socket->so_rcv); + + /* + * Kick the sending thread. + */ + cv_signal(&cs->cs_send_cv); + + /* + * Wait one second before trying to destroy the session + * to avoid race with cfiscsi_soupcall_receive(). + */ + callout_init(&cs->cs_terminate_callout, 1); + callout_reset(&cs->cs_terminate_callout, 1 * hz, + cfiscsi_terminate_callout, cs); +} + +int +cfiscsi_init(void) +{ + struct cfiscsi_softc *softc; + struct ctl_frontend *fe; + int retval; + + /* Don't continue if CTL is disabled */ + if (ctl_disable != 0) + return (0); + + softc = &cfiscsi_softc; + retval = 0; + bzero(softc, sizeof(*softc)); + mtx_init(&softc->lock, "cfiscsi", NULL, MTX_DEF); + + TAILQ_INIT(&softc->sessions); + TAILQ_INIT(&softc->targets); + + fe = &softc->fe; + fe->port_type = CTL_PORT_ISCSI; + /* XXX KDM what should the real number be here? */ + fe->num_requested_ctl_io = 4096; + snprintf(softc->port_name, sizeof(softc->port_name), "iscsi"); + fe->port_name = softc->port_name; + fe->port_online = cfiscsi_online; + fe->port_offline = cfiscsi_offline; + fe->onoff_arg = softc; + fe->targ_enable = cfiscsi_targ_enable; + fe->targ_disable = cfiscsi_targ_disable; + fe->lun_enable = cfiscsi_lun_enable; + fe->lun_disable = cfiscsi_lun_disable; + fe->targ_lun_arg = softc; + fe->ioctl = cfiscsi_ioctl; + fe->devid = cfiscsi_devid; + fe->fe_datamove = cfiscsi_datamove; + fe->fe_done = cfiscsi_done; + + /* XXX KDM what should we report here? */ + /* XXX These should probably be fetched from CTL. */ + fe->max_targets = 1; + fe->max_target_id = 15; + + retval = ctl_frontend_register(fe, /*master_SC*/ 1); + if (retval != 0) { + CFISCSI_WARN(("%s: ctl_frontend_register() failed " + "with error %d!\n", __func__, retval)); + retval = 1; + goto bailout; + } + + softc->max_initiators = fe->max_initiators; + + cfiscsi_pdu_zone = uma_zcreate("cfiscsi_pdu", + sizeof(struct cfiscsi_pdu), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + cfiscsi_data_wait_zone = uma_zcreate("cfiscsi_data_wait", + sizeof(struct cfiscsi_data_wait), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + + return (0); + +bailout: + return (retval); +} + +static 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); + } +} + +static void +cfiscsi_online(void *arg) +{ + struct cfiscsi_softc *softc; + + softc = (struct cfiscsi_softc *)arg; + + softc->online = 1; +} + +static void +cfiscsi_offline(void *arg) +{ + struct cfiscsi_softc *softc; + struct cfiscsi_session *cs; + + softc = (struct cfiscsi_softc *)arg; + + softc->online = 0; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) + cfiscsi_session_terminate(cs); + mtx_unlock(&softc->lock); +} + +static int +cfiscsi_targ_enable(void *arg, struct ctl_id targ_id) +{ + + return (0); +} + +static int +cfiscsi_targ_disable(void *arg, struct ctl_id targ_id) +{ + + return (0); +} + +static void +cfiscsi_ioctl_handoff(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_handoff_params *cihp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + + cihp = (struct ctl_iscsi_handoff_params *)&(ci->data); + softc = &cfiscsi_softc; + +#if 0 + CFISCSI_DEBUG(("%s: socket = %d, target = %s, cmdsn = %d, " + "statsn = %d, dslen = %d\n", __func__, cihp->socket, + cihp->target_name, cihp->cmdsn, cihp->statsn, + cihp->max_recv_data_segment_length)); +#else + CFISCSI_DEBUG(("%s: new connection from %s (%s) to %s\n", + __func__, cihp->initiator_name, cihp->initiator_addr, + cihp->target_name)); +#endif + if (softc->online == 0) { + CFISCSI_WARN(("%s: iscsi CTL port offline; " + "dropping connection\n", __func__)); + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: port offline", __func__); + } + + cs = cfiscsi_session_new(softc, cihp->socket, cihp->initiator_name, + cihp->initiator_addr, cihp->target_name, cihp->cmdsn, cihp->statsn, + cihp->max_recv_data_segment_length, cihp->header_digest, + cihp->data_digest, cihp->portal_group_tag); + if (cs == NULL) { + CFISCSI_WARN(("%s: session NOT created\n", __func__)); + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: failed to create session", __func__); + return; + } + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_list(struct ctl_iscsi *ci) +{ + struct ctl_iscsi_list_params *cilp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + struct sbuf *sb; + int error; + + cilp = (struct ctl_iscsi_list_params *)&(ci->data); + softc = &cfiscsi_softc; + + sb = sbuf_new(NULL, NULL, cilp->alloc_len, SBUF_FIXEDLEN); + if (sb == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "Unable to allocate %d bytes for iSCSI session list", + cilp->alloc_len); + return; + } + + sbuf_printf(sb, "\n"); + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) { + error = sbuf_printf(sb, "" + "%s" + "%s" + "%s\n", + cs->cs_id, cs->cs_initiator_name, cs->cs_initiator_addr, + cs->cs_target->ct_name); + if (error != 0) + break; + } + mtx_unlock(&softc->lock); + error = sbuf_printf(sb, "\n"); + if (error != 0) { + sbuf_delete(sb); + ci->status = CTL_ISCSI_LIST_NEED_MORE_SPACE; + snprintf(ci->error_str, sizeof(ci->error_str), + "Out of space, %d bytes is too small", cilp->alloc_len); + return; + } + sbuf_finish(sb); + + error = copyout(sbuf_data(sb), cilp->conn_xml, sbuf_len(sb) + 1); + cilp->fill_len = sbuf_len(sb) + 1; + ci->status = CTL_ISCSI_OK; + sbuf_delete(sb); +} + +static void +cfiscsi_ioctl_terminate(struct ctl_iscsi *ci) +{ + struct cfiscsi_pdu *response; + struct iscsi_bhs_asynchronous_message *bhsam; + struct ctl_iscsi_terminate_params *citp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + int found = 0; + + citp = (struct ctl_iscsi_terminate_params *)&(ci->data); + softc = &cfiscsi_softc; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) { + if (citp->all == 0 && cs->cs_id != citp->connection_id && + strcmp(cs->cs_initiator_name, citp->initiator_name) != 0 && + strcmp(cs->cs_initiator_addr, citp->initiator_addr) != 0) + continue; + + response = cfiscsi_pdu_new_bhs(cs, M_NOWAIT); + if (response == NULL) { + /* + * Oh well. Just terminate the connection. + */ + } else { + bhsam = (struct iscsi_bhs_asynchronous_message *) + response->cp_bhs; + bhsam->bhsam_opcode = ISCSI_BHS_OPCODE_ASYNC_MESSAGE; + bhsam->bhsam_flags = 0x80; + bhsam->bhsam_0xffffffff = 0xffffffff; + bhsam->bhsam_async_event = + BHSAM_EVENT_TARGET_TERMINATES_SESSION; + cfiscsi_pdu_queue(response); + } + cfiscsi_session_terminate(cs); + found++; + } + mtx_unlock(&softc->lock); + + if (found == 0) { + ci->status = CTL_ISCSI_SESSION_NOT_FOUND; + snprintf(ci->error_str, sizeof(ci->error_str), + "No matching connections found"); + return; + } + + ci->status = CTL_ISCSI_OK; +} + +static void +cfiscsi_ioctl_logout(struct ctl_iscsi *ci) +{ + struct cfiscsi_pdu *response; + struct iscsi_bhs_asynchronous_message *bhsam; + struct ctl_iscsi_logout_params *cilp; + struct cfiscsi_session *cs; + struct cfiscsi_softc *softc; + int found = 0; + + cilp = (struct ctl_iscsi_logout_params *)&(ci->data); + softc = &cfiscsi_softc; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(cs, &softc->sessions, cs_next) { + if (cilp->all == 0 && cs->cs_id != cilp->connection_id && + strcmp(cs->cs_initiator_name, cilp->initiator_name) != 0 && + strcmp(cs->cs_initiator_addr, cilp->initiator_addr) != 0) + continue; + + response = cfiscsi_pdu_new_bhs(cs, M_NOWAIT); + if (response == NULL) { + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "Unable to allocate memory"); + mtx_unlock(&softc->lock); + return; + } + bhsam = + (struct iscsi_bhs_asynchronous_message *)response->cp_bhs; + bhsam->bhsam_opcode = ISCSI_BHS_OPCODE_ASYNC_MESSAGE; + bhsam->bhsam_flags = 0x80; + bhsam->bhsam_async_event = BHSAM_EVENT_TARGET_REQUESTS_LOGOUT; + bhsam->bhsam_parameter3 = htons(10); + cfiscsi_pdu_queue(response); + found++; + } + mtx_unlock(&softc->lock); + + if (found == 0) { + ci->status = CTL_ISCSI_SESSION_NOT_FOUND; + snprintf(ci->error_str, sizeof(ci->error_str), + "No matching connections found"); + return; + } + + ci->status = CTL_ISCSI_OK; +} + +static int +cfiscsi_ioctl(struct cdev *dev, + u_long cmd, caddr_t addr, int flag, struct thread *td) +{ + struct ctl_iscsi *ci; + + if (cmd != CTL_ISCSI) + return (ENOTTY); + + ci = (struct ctl_iscsi *)addr; + switch (ci->type) { + case CTL_ISCSI_HANDOFF: + cfiscsi_ioctl_handoff(ci); + break; + case CTL_ISCSI_LIST: + cfiscsi_ioctl_list(ci); + break; + case CTL_ISCSI_TERMINATE: + cfiscsi_ioctl_terminate(ci); + break; + case CTL_ISCSI_LOGOUT: + cfiscsi_ioctl_logout(ci); + break; + default: + ci->status = CTL_ISCSI_ERROR; + snprintf(ci->error_str, sizeof(ci->error_str), + "%s: invalid iSCSI request type %d", __func__, ci->type); + break; + } + + return (0); +} + +static int +cfiscsi_devid(struct ctl_scsiio *ctsio, int alloc_len) +{ + struct scsi_vpd_device_id *devid_ptr; + struct scsi_vpd_id_descriptor *desc, *desc1; + struct scsi_vpd_id_descriptor *desc2, *desc3; /* for types 4h and 5h */ + struct scsi_vpd_id_t10 *t10id; + struct ctl_lun *lun; + const struct cfiscsi_pdu *request; + size_t devid_len, wwpn_len; + + lun = (struct ctl_lun *)ctsio->io_hdr.ctl_private[CTL_PRIV_LUN].ptr; + request = ctsio->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + + wwpn_len = strlen(request->cp_session->cs_target->ct_name); + wwpn_len += strlen(",t,0x01"); + wwpn_len += 1; /* '\0' */ + if ((wwpn_len % 4) != 0) + wwpn_len += (4 - (wwpn_len % 4)); + + devid_len = sizeof(struct scsi_vpd_device_id) + + sizeof(struct scsi_vpd_id_descriptor) + + sizeof(struct scsi_vpd_id_t10) + CTL_DEVID_LEN + + sizeof(struct scsi_vpd_id_descriptor) + wwpn_len + + sizeof(struct scsi_vpd_id_descriptor) + + sizeof(struct scsi_vpd_id_rel_trgt_port_id) + + sizeof(struct scsi_vpd_id_descriptor) + + sizeof(struct scsi_vpd_id_trgt_port_grp_id); + + ctsio->kern_data_ptr = malloc(devid_len, M_CTL, M_WAITOK | M_ZERO); + devid_ptr = (struct scsi_vpd_device_id *)ctsio->kern_data_ptr; + ctsio->kern_sg_entries = 0; + + if (devid_len < alloc_len) { + ctsio->residual = alloc_len - devid_len; + ctsio->kern_data_len = devid_len; + ctsio->kern_total_len = devid_len; + } else { + ctsio->residual = 0; + ctsio->kern_data_len = alloc_len; + ctsio->kern_total_len = alloc_len; + } + ctsio->kern_data_resid = 0; + ctsio->kern_rel_offset = 0; + ctsio->kern_sg_entries = 0; + + desc = (struct scsi_vpd_id_descriptor *)devid_ptr->desc_list; + t10id = (struct scsi_vpd_id_t10 *)&desc->identifier[0]; + desc1 = (struct scsi_vpd_id_descriptor *)(&desc->identifier[0] + + sizeof(struct scsi_vpd_id_t10) + CTL_DEVID_LEN); + desc2 = (struct scsi_vpd_id_descriptor *)(&desc1->identifier[0] + + wwpn_len); + desc3 = (struct scsi_vpd_id_descriptor *)(&desc2->identifier[0] + + sizeof(struct scsi_vpd_id_rel_trgt_port_id)); + + if (lun != NULL) + devid_ptr->device = (SID_QUAL_LU_CONNECTED << 5) | + lun->be_lun->lun_type; + else + devid_ptr->device = (SID_QUAL_LU_OFFLINE << 5) | T_DIRECT; + + devid_ptr->page_code = SVPD_DEVICE_ID; + + scsi_ulto2b(devid_len - 4, devid_ptr->length); + + /* + * We're using a LUN association here. i.e., this device ID is a + * per-LUN identifier. + */ + desc->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_ASCII; + desc->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_LUN | SVPD_ID_TYPE_T10; + desc->length = sizeof(*t10id) + CTL_DEVID_LEN; + strncpy((char *)t10id->vendor, CTL_VENDOR, sizeof(t10id->vendor)); + + /* + * If we've actually got a backend, copy the device id from the + * per-LUN data. Otherwise, set it to all spaces. + */ + if (lun != NULL) { + /* + * Copy the backend's LUN ID. + */ + strncpy((char *)t10id->vendor_spec_id, + (char *)lun->be_lun->device_id, CTL_DEVID_LEN); + } else { + /* + * No backend, set this to spaces. + */ + memset(t10id->vendor_spec_id, 0x20, CTL_DEVID_LEN); + } + + /* + * desc1 is for the WWPN which is a port asscociation. + */ + desc1->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_UTF8; + desc1->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_PORT | + SVPD_ID_TYPE_SCSI_NAME; + desc1->length = wwpn_len; + snprintf(desc1->identifier, wwpn_len, "%s,t,0x%x", + request->cp_session->cs_target->ct_name, + request->cp_session->cs_portal_group_tag); + + /* + * desc2 is for the Relative Target Port(type 4h) identifier + */ + desc2->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_BINARY; + desc2->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_PORT | + SVPD_ID_TYPE_RELTARG; + desc2->length = 4; + desc2->identifier[3] = 1; + + /* + * desc3 is for the Target Port Group(type 5h) identifier + */ + desc3->proto_codeset = (SCSI_PROTO_ISCSI << 4) | SVPD_ID_CODESET_BINARY; + desc3->id_type = SVPD_ID_PIV | SVPD_ID_ASSOC_PORT | + SVPD_ID_TYPE_TPORTGRP; + desc3->length = 4; + desc3->identifier[3] = 1; + + ctsio->scsi_status = SCSI_STATUS_OK; + + ctsio->be_move_done = ctl_config_move_done; + ctl_datamove((union ctl_io *)ctsio); + + return (CTL_RETVAL_COMPLETE); +} + +static void +cfiscsi_target_hold(struct cfiscsi_target *ct) +{ + + refcount_acquire(&ct->ct_refcount); +} + +static void +cfiscsi_target_release(struct cfiscsi_target *ct) +{ + int old; + struct cfiscsi_softc *softc; + + softc = ct->ct_softc; + + old = ct->ct_refcount; + if (old > 1 && atomic_cmpset_int(&ct->ct_refcount, old, old - 1)) + return; + + mtx_lock(&softc->lock); + if (refcount_release(&ct->ct_refcount)) { + TAILQ_REMOVE(&softc->targets, ct, ct_next); + mtx_unlock(&softc->lock); + free(ct, M_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) +{ + 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; + + strcpy(newct->ct_name, name); + refcount_init(&newct->ct_refcount, 1); + newct->ct_softc = softc; + TAILQ_INSERT_TAIL(&softc->targets, newct, ct_next); + mtx_unlock(&softc->lock); + + return (newct); +} + +/* + * Takes LUN from the target space and returns LUN from the CTL space. + */ +static uint32_t +cfiscsi_map_lun(void *arg, uint32_t lun) +{ + struct cfiscsi_session *cs; + + cs = arg; + + if (lun >= CTL_MAX_LUNS) { + CFISCSI_DEBUG(("%s: requested lun number %d is higher " + "than maximum %d\n", __func__, lun, CTL_MAX_LUNS - 1)); + return (0xffffffff); + } + + if (cs->cs_target->ct_luns[lun] < 0) + return (0xffffffff); + + return (cs->cs_target->ct_luns[lun]); +} + +static int +cfiscsi_target_set_lun(struct cfiscsi_target *ct, + unsigned long lun_id, unsigned long ctl_lun_id) +{ + + if (lun_id >= CTL_MAX_LUNS) { + CFISCSI_WARN(("%s: requested lun number %ld is higher " + "than maximum %d\n", __func__, lun_id, CTL_MAX_LUNS - 1)); + return (-1); + } + + if (ct->ct_luns[lun_id] >= 0) { + /* + * CTL calls cfiscsi_lun_enable() twice for each LUN - once + * when the LUN is created, and a second time just before + * the port is brought online; don't emit warnings + * for that case. + */ + if (ct->ct_luns[lun_id] == ctl_lun_id) + return (0); + CFISCSI_WARN(("%s: lun %ld already allocated\n", + __func__, lun_id)); + return (-1); + } + +#if 0 + CFISCSI_DEBUG(("%s: adding mapping for lun %ld, target %s " + "to ctl lun %ld\n", __func__, 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(("%s: lun %ld not allocated\n", __func__, lun_id)); + return (-1); + } + + ct->ct_luns[lun_id] = -1; + cfiscsi_target_release(ct); + + return (0); +} + +static int +cfiscsi_lun_enable(void *arg, struct ctl_id target_id, int lun_id) +{ + struct cfiscsi_softc *softc; + struct cfiscsi_target *ct; + struct ctl_be_lun_option *opt; + const char *ctld_target = NULL, *ctld_lun = NULL; + unsigned long ctld_lun_id; + + softc = (struct cfiscsi_softc *)arg; + + STAILQ_FOREACH(opt, + &control_softc->ctl_luns[lun_id]->be_lun->options, links) { + if (strcmp(opt->name, "ctld_target") == 0) + ctld_target = opt->value; + else if (strcmp(opt->name, "ctld_lun") == 0) + ctld_lun = opt->value; + } + + if (ctld_target == NULL && ctld_lun == NULL) + return (0); + + if (ctld_target == NULL || ctld_lun == NULL) { + CFISCSI_WARN(("%s: lun added with ctld_target, but without " + "ctld_lun, or the other way around; ignoring\n", __func__)); + return (0); + } + + ct = cfiscsi_target_find_or_create(softc, ctld_target); + if (ct == NULL) { + CFISCSI_WARN(("%s: failed to create target \"%s\"\n", + __func__, ctld_target)); + return (0); + } + + ctld_lun_id = strtoul(ctld_lun, NULL, 10); + cfiscsi_target_set_lun(ct, ctld_lun_id, lun_id); + return (0); +} + +static int +cfiscsi_lun_disable(void *arg, struct ctl_id target_id, int lun_id) +{ + struct cfiscsi_softc *softc; + struct cfiscsi_target *ct; + int i; + + softc = (struct cfiscsi_softc *)arg; + + mtx_lock(&softc->lock); + TAILQ_FOREACH(ct, &softc->targets, ct_next) { + for (i = 0; i < CTL_MAX_LUNS; i++) { + if (ct->ct_luns[i] < 0) + continue; + if (ct->ct_luns[i] != lun_id) + continue; + cfiscsi_target_unset_lun(ct, i); + break; + } + } + mtx_unlock(&softc->lock); + return (0); +} + +static void +cfiscsi_datamove(union ctl_io *io) +{ + struct cfiscsi_session *cs; + struct cfiscsi_pdu *request, *response; + const struct iscsi_bhs_scsi_command *bhssc; + struct iscsi_bhs_data_in *bhsdi; + struct iscsi_bhs_r2t *bhsr2t; + struct cfiscsi_data_wait *cdw; + struct ctl_sg_entry ctl_sg_entry, *ctl_sglist; + size_t copy_len, len, off; + const char *addr; + int ctl_sg_count, i; + uint32_t target_transfer_tag; + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = request->cp_session; + + bhssc = (const struct iscsi_bhs_scsi_command *)request->cp_bhs; + KASSERT((bhssc->bhssc_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_COMMAND, + ("bhssc->bhssc_opcode != ISCSI_BHS_OPCODE_SCSI_COMMAND")); + + if (io->scsiio.kern_sg_entries > 0) { + ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr; + ctl_sg_count = io->scsiio.kern_sg_entries; + } else { + ctl_sglist = &ctl_sg_entry; + ctl_sglist->addr = io->scsiio.kern_data_ptr; + ctl_sglist->len = io->scsiio.kern_data_len; + ctl_sg_count = 1; + } + + /* + * We need to record it so that we can properly report + * underflow/underflow. + */ + request->cp_total_transfer_len = io->scsiio.kern_total_len; + + if ((io->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN) { +#if 0 + if (ctl_sg_count > 1) + CFISCSI_DEBUG(("%s: ctl_sg_count = %d\n", + __func__, ctl_sg_count)); +#endif + + /* + * This is the offset within the current SCSI command; + * i.e. for the first call of datamove(), it will be 0, + * and for subsequent ones it will be the sum of lengths + * of previous ones. + */ + off = htonl(io->scsiio.kern_rel_offset); + if (off > 1) + CFISCSI_DEBUG(("%s: off = %zd\n", __func__, off)); + + i = 0; + addr = NULL; + len = 0; + response = NULL; + bhsdi = NULL; + for (;;) { + KASSERT(i < ctl_sg_count, ("i >= ctl_sg_count")); + if (response == NULL) { + response = + cfiscsi_pdu_new_response(request, M_WAITOK); + bhsdi = (struct iscsi_bhs_data_in *) + response->cp_bhs; + bhsdi->bhsdi_opcode = + ISCSI_BHS_OPCODE_SCSI_DATA_IN; + bhsdi->bhsdi_initiator_task_tag = + bhssc->bhssc_initiator_task_tag; + bhsdi->bhsdi_datasn = + htonl(request->cp_expdatasn); + request->cp_expdatasn++; + bhsdi->bhsdi_buffer_offset = htonl(off); + } + + if (len == 0) { + addr = ctl_sglist[i].addr; + len = ctl_sglist[i].len; + KASSERT(len > 0, ("len <= 0")); + } + + copy_len = len; + if (response->cp_data_len + copy_len > + cs->cs_max_data_segment_length) + copy_len = cs->cs_max_data_segment_length - + response->cp_data_len; + KASSERT(copy_len <= len, ("copy_len > len")); + cfiscsi_pdu_append_data(response, addr, copy_len); + addr += copy_len; + len -= copy_len; + off += copy_len; + io->scsiio.ext_data_filled += copy_len; + + if (len == 0) { + /* + * End of scatter-gather segment; + * proceed to the next one... + */ + if (i == ctl_sg_count - 1) { + /* + * ... unless this was the last one. + */ + break; + } + i++; + } + + if (response->cp_data_len == + cs->cs_max_data_segment_length) { + /* + * Can't stuff more data into the current PDU; + * queue it. Note that's not enough to check + * for kern_data_resid == 0 instead; there + * may be several Data-In PDUs for the final + * call to cfiscsi_datamove(), and we want + * to set the F flag only on the last of them. + */ + if (off == io->scsiio.kern_total_len) + bhsdi->bhsdi_flags |= BHSDI_FLAGS_F; + KASSERT(response->cp_data_len > 0, + ("sending empty Data-In")); + cfiscsi_pdu_queue(response); + response = NULL; + bhsdi = NULL; + } + } + KASSERT(i == ctl_sg_count - 1, ("missed SG segment")); + KASSERT(len == 0, ("missed data from SG segment")); + if (response != NULL) { + if (off == io->scsiio.kern_total_len) { + bhsdi->bhsdi_flags |= BHSDI_FLAGS_F; + } else { + CFISCSI_DEBUG(("%s: not setting the F flag; " + "have %zd, need %zd\n", __func__, off, + (size_t)io->scsiio.kern_total_len)); + } + KASSERT(response->cp_data_len > 0, + ("sending empty Data-In")); + cfiscsi_pdu_queue(response); + } + + io->scsiio.be_move_done(io); + } else { + CFISCSI_SESSION_LOCK(cs); + target_transfer_tag = cs->cs_target_transfer_tag; + cs->cs_target_transfer_tag++; + CFISCSI_SESSION_UNLOCK(cs); + +#if 0 + CFISCSI_DEBUG(("%s: expecting Data-Out with initiator " + "task tag 0x%x, target transfer tag 0x%x\n", __func__, + bhssc->bhssc_initiator_task_tag, target_transfer_tag)); +#endif + cdw = uma_zalloc(cfiscsi_data_wait_zone, M_WAITOK | M_ZERO); + cdw->cdw_ctl_io = io; + cdw->cdw_target_transfer_tag = htonl(target_transfer_tag); + cdw->cdw_initiator_task_tag = bhssc->bhssc_initiator_task_tag; + CFISCSI_SESSION_LOCK(cs); + TAILQ_INSERT_TAIL(&cs->cs_waiting_for_data_out, cdw, cdw_next); + CFISCSI_SESSION_UNLOCK(cs); + + /* + * XXX: We should limit the number of outstanding R2T PDUs + * per task to MaxOutstandingR2T. + */ + response = cfiscsi_pdu_new_response(request, M_WAITOK); + bhsr2t = (struct iscsi_bhs_r2t *)response->cp_bhs; + bhsr2t->bhsr2t_opcode = ISCSI_BHS_OPCODE_R2T; + bhsr2t->bhsr2t_flags = 0x80; + bhsr2t->bhsr2t_lun = bhssc->bhssc_lun; + bhsr2t->bhsr2t_initiator_task_tag = + bhssc->bhssc_initiator_task_tag; + bhsr2t->bhsr2t_target_transfer_tag = + htonl(target_transfer_tag); + /* + * XXX: Here we assume that cfiscsi_datamove() won't ever + * be running concurrently on several CPUs for a given + * command. + */ + bhsr2t->bhsr2t_r2tsn = htonl(request->cp_r2tsn); + request->cp_r2tsn++; + /* + * This is the offset within the current SCSI command; + * i.e. for the first call of datamove(), it will be 0, + * and for subsequent ones it will be the sum of lengths + * of previous ones. + */ + bhsr2t->bhsr2t_buffer_offset = + htonl(io->scsiio.kern_rel_offset); + /* + * This is the total length (sum of S/G lengths) this call + * to cfiscsi_datamove() is supposed to handle. + */ + bhsr2t->bhsr2t_desired_data_transfer_length = + htonl(io->scsiio.kern_data_len); + cfiscsi_pdu_queue(response); + } +} + +static void +cfiscsi_scsi_command_done(union ctl_io *io) +{ + struct cfiscsi_pdu *request, *response; + struct iscsi_bhs_scsi_command *bhssc; + struct iscsi_bhs_scsi_response *bhssr; +#ifdef DIAGNOSTIC + struct cfiscsi_data_wait *cdw; +#endif + struct cfiscsi_session *cs; + uint16_t sense_length; + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = request->cp_session; + bhssc = (struct iscsi_bhs_scsi_command *)request->cp_bhs; + KASSERT((bhssc->bhssc_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_SCSI_COMMAND, + ("replying to wrong opcode 0x%x", bhssc->bhssc_opcode)); + + //CFISCSI_DEBUG(("%s: initiator task tag 0x%x\n", + // __func__, bhssc->bhssc_initiator_task_tag)); + +#ifdef DIAGNOSTIC + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH(cdw, &cs->cs_waiting_for_data_out, cdw_next) + KASSERT(bhssc->bhssc_initiator_task_tag != + cdw->cdw_initiator_task_tag, ("dangling cdw")); + CFISCSI_SESSION_UNLOCK(cs); +#endif + + response = cfiscsi_pdu_new_response(request, M_WAITOK); + bhssr = (struct iscsi_bhs_scsi_response *)response->cp_bhs; + bhssr->bhssr_opcode = ISCSI_BHS_OPCODE_SCSI_RESPONSE; + bhssr->bhssr_flags = 0x80; + /* + * XXX: We don't deal with bidirectional under/overflows; + * does anything actually support those? + */ + if (request->cp_total_transfer_len < + ntohl(bhssc->bhssc_expected_data_transfer_length)) { + bhssr->bhssr_flags |= BHSSR_FLAGS_RESIDUAL_UNDERFLOW; + bhssr->bhssr_residual_count = + htonl(ntohl(bhssc->bhssc_expected_data_transfer_length) - + request->cp_total_transfer_len); + //CFISCSI_DEBUG(("%s: underflow; residual count %d\n", + // __func__, ntohl(bhssr->bhssr_residual_count))); + } else if (request->cp_total_transfer_len > + ntohl(bhssc->bhssc_expected_data_transfer_length)) { + bhssr->bhssr_flags |= BHSSR_FLAGS_RESIDUAL_OVERFLOW; + bhssr->bhssr_residual_count = + htonl(request->cp_total_transfer_len - + ntohl(bhssc->bhssc_expected_data_transfer_length)); + //CFISCSI_DEBUG(("%s: overflow; residual count %d\n", + // __func__, ntohl(bhssr->bhssr_residual_count))); + } + bhssr->bhssr_response = BHSSR_RESPONSE_COMMAND_COMPLETED; + bhssr->bhssr_status = io->scsiio.scsi_status; + bhssr->bhssr_initiator_task_tag = bhssc->bhssc_initiator_task_tag; + bhssr->bhssr_expdatasn = htonl(request->cp_expdatasn); + + if (io->scsiio.sense_len > 0) { + CFISCSI_DEBUG(("%s: returning %d bytes of sense data\n", + __func__, io->scsiio.sense_len)); + sense_length = htons(io->scsiio.sense_len); + cfiscsi_pdu_append_data(response, + &sense_length, sizeof(sense_length)); + cfiscsi_pdu_append_data(response, + &io->scsiio.sense_data, io->scsiio.sense_len); + } + + ctl_free_io(io); + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_task_management_done(union ctl_io *io) +{ + struct cfiscsi_pdu *request, *response; + struct iscsi_bhs_task_management_request *bhstmr; + struct iscsi_bhs_task_management_response *bhstmr2; + struct cfiscsi_data_wait *cdw, *tmpcdw; + struct cfiscsi_session *cs; + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + cs = request->cp_session; + bhstmr = (struct iscsi_bhs_task_management_request *)request->cp_bhs; + KASSERT((bhstmr->bhstmr_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) == + ISCSI_BHS_OPCODE_TASK_REQUEST, + ("replying to wrong opcode 0x%x", bhstmr->bhstmr_opcode)); + +#if 0 + CFISCSI_DEBUG(("%s: initiator task tag 0x%x; referenced task tag " + "0x%x\n", __func__, bhstmr->bhstmr_initiator_task_tag, + bhstmr->bhstmr_referenced_task_tag)); +#endif + + if ((bhstmr->bhstmr_function & ~0x80) == + BHSTMR_FUNCTION_ABORT_TASK) { + /* + * Make sure we no longer wait for Data-Out for this command. + */ + CFISCSI_SESSION_LOCK(cs); + TAILQ_FOREACH_SAFE(cdw, + &cs->cs_waiting_for_data_out, cdw_next, tmpcdw) { + if (bhstmr->bhstmr_referenced_task_tag != + cdw->cdw_initiator_task_tag) + continue; + +#if 0 + CFISCSI_DEBUG(("%s: removing csw for initiator task " + "tag 0x%x\n", __func__, + bhstmr->bhstmr_initiator_task_tag)); +#endif + TAILQ_REMOVE(&cs->cs_waiting_for_data_out, + cdw, cdw_next); + cdw->cdw_ctl_io->scsiio.be_move_done(cdw->cdw_ctl_io); + uma_zfree(cfiscsi_data_wait_zone, cdw); + } + CFISCSI_SESSION_UNLOCK(cs); + } + + response = cfiscsi_pdu_new_response(request, M_WAITOK); + bhstmr2 = (struct iscsi_bhs_task_management_response *) + response->cp_bhs; + bhstmr2->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_RESPONSE; + bhstmr2->bhstmr_flags = 0x80; + if (io->io_hdr.status == CTL_SUCCESS) { + bhstmr2->bhstmr_response = BHSTMR_RESPONSE_FUNCTION_COMPLETE; + } else { + /* + * XXX: How to figure out what exactly went wrong? iSCSI spec + * expects us to provide detailed error, e.g. "Task does + * not exist" or "LUN does not exist". + */ + CFISCSI_DEBUG(("%s: BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED\n", + __func__)); + bhstmr2->bhstmr_response = + BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED; + } + bhstmr2->bhstmr_initiator_task_tag = bhstmr->bhstmr_initiator_task_tag; + + ctl_free_io(io); + cfiscsi_pdu_free(request); + cfiscsi_pdu_queue(response); +} + +static void +cfiscsi_done(union ctl_io *io) +{ + struct cfiscsi_pdu *request; + struct cfiscsi_session *cs; + + KASSERT(((io->io_hdr.status & CTL_STATUS_MASK) != CTL_STATUS_NONE), + ("invalid CTL status %#x", io->io_hdr.status)); + + request = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr; + if (request == NULL) { + /* + * Implicit task termination has just completed; nothing to do. + */ + return; + } + + cs = request->cp_session; + refcount_release(&cs->cs_outstanding_ctl_pdus); + + switch (request->cp_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) { + case ISCSI_BHS_OPCODE_SCSI_COMMAND: + cfiscsi_scsi_command_done(io); + break; + case ISCSI_BHS_OPCODE_TASK_REQUEST: + cfiscsi_task_management_done(io); + break; + default: + panic("cfiscsi_done called with wrong opcode 0x%x", + request->cp_bhs->bhs_opcode); + } +} diff -urNp freebsd/src/sys/cam/ctl/ctl_io.h iscsi/sys/cam/ctl/ctl_io.h --- freebsd/src/sys/cam/ctl/ctl_io.h 2013-04-20 20:02:14.000000000 +0200 +++ iscsi/sys/cam/ctl/ctl_io.h 2013-04-02 16:55:15.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 freebsd/src/sys/cam/ctl/ctl_ioctl.h iscsi/sys/cam/ctl/ctl_ioctl.h --- freebsd/src/sys/cam/ctl/ctl_ioctl.h 2013-04-20 20:02:15.000000000 +0200 +++ iscsi/sys/cam/ctl/ctl_ioctl.h 2013-04-19 20:11:39.000000000 +0200 @@ -40,6 +40,8 @@ #ifndef _CTL_IOCTL_H_ #define _CTL_IOCTL_H_ +#include + #define CTL_DEFAULT_DEV "/dev/cam/ctl" /* * Maximum number of targets we support. @@ -588,6 +590,106 @@ struct ctl_lun_list { /* passed to userland */ }; +/* + * iSCSI status + * + * OK: Request completed successfully. + * + * ERROR: An error occured, look at the error string for a + * description of the error. + * + * CTL_ISCSI_LIST_NEED_MORE_SPACE: + * User has to pass larger buffer for CTL_ISCSI_LIST ioctl. + */ +typedef enum { + CTL_ISCSI_OK, + CTL_ISCSI_ERROR, + CTL_ISCSI_LIST_NEED_MORE_SPACE, + CTL_ISCSI_SESSION_NOT_FOUND +} ctl_iscsi_status; + +typedef enum { + CTL_ISCSI_HANDOFF, + CTL_ISCSI_LIST, + CTL_ISCSI_LOGOUT, + CTL_ISCSI_TERMINATE, +} ctl_iscsi_type; + +typedef enum { + CTL_ISCSI_DIGEST_NONE, + CTL_ISCSI_DIGEST_CRC32C +} ctl_iscsi_digest; + +#define CTL_ISCSI_NAME_LEN 224 /* 223 bytes, by RFC 3720, + '\0' */ +#define CTL_ISCSI_ADDR_LEN 47 /* INET6_ADDRSTRLEN + '\0' */ + +struct ctl_iscsi_handoff_params { + char initiator_name[CTL_ISCSI_NAME_LEN]; + char initiator_addr[CTL_ISCSI_ADDR_LEN]; + char target_name[CTL_ISCSI_NAME_LEN]; + int socket; + int portal_group_tag; + + /* + * Connection parameters negotiated by ctld(8). + */ + ctl_iscsi_digest header_digest; + ctl_iscsi_digest data_digest; + uint32_t cmdsn; + uint32_t statsn; + uint32_t max_recv_data_segment_length; + uint32_t max_burst_length; + uint32_t max_outstanding_r2t; +}; + +struct ctl_iscsi_list_params { + uint32_t alloc_len; /* passed to kernel */ + char *conn_xml; /* filled in kernel */ + uint32_t fill_len; /* passed to userland */ +}; + +struct ctl_iscsi_logout_params { + int connection_id; /* passed to kernel */ + char initiator_name[CTL_ISCSI_NAME_LEN]; + /* passed to kernel */ + char initiator_addr[CTL_ISCSI_ADDR_LEN]; + /* passed to kernel */ + int all; /* passed to kernel */ +}; + +struct ctl_iscsi_terminate_params { + int connection_id; /* passed to kernel */ + char initiator_name[CTL_ISCSI_NAME_LEN]; + /* passed to kernel */ + char initiator_addr[CTL_ISCSI_NAME_LEN]; + /* passed to kernel */ + int all; /* passed to kernel */ +}; + +union ctl_iscsi_data { + struct ctl_iscsi_handoff_params handoff; + struct ctl_iscsi_list_params list; + struct ctl_iscsi_logout_params logout; + struct ctl_iscsi_terminate_params terminate; +}; + +/* + * iSCSI interface + * + * status: The status of the request. See above for the + * description of the values of this field. + * + * error_str: If the status indicates an error, this string will + * be filled in to describe the error. + */ +struct ctl_iscsi { + ctl_iscsi_type type; /* passed to kernel */ + union ctl_iscsi_data data; /* passed to kernel */ + ctl_iscsi_status status; /* passed to userland */ + char error_str[CTL_ERROR_STR_LEN]; + /* passed to userland */ +}; + #define CTL_IO _IOWR(CTL_MINOR, 0x00, union ctl_io) #define CTL_ENABLE_PORT _IOW(CTL_MINOR, 0x04, struct ctl_port_entry) #define CTL_DISABLE_PORT _IOW(CTL_MINOR, 0x05, struct ctl_port_entry) @@ -612,6 +714,7 @@ struct ctl_lun_list { #define CTL_LUN_LIST _IOWR(CTL_MINOR, 0x22, struct ctl_lun_list) #define CTL_ERROR_INJECT_DELETE _IOW(CTL_MINOR, 0x23, struct ctl_error_desc) #define CTL_SET_PORT_WWNS _IOW(CTL_MINOR, 0x24, struct ctl_port_entry) +#define CTL_ISCSI _IOWR(CTL_MINOR, 0x25, struct ctl_iscsi) #endif /* _CTL_IOCTL_H_ */ diff -urNp freebsd/src/sys/cam/ctl/iscsi.h iscsi/sys/cam/ctl/iscsi.h --- freebsd/src/sys/cam/ctl/iscsi.h 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/sys/cam/ctl/iscsi.h 2013-04-19 18:33:53.000000000 +0200 @@ -0,0 +1,438 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef ISCSI_H +#define ISCSI_H + +#ifndef CTASSERT +#define CTASSERT(x) _CTASSERT(x, __LINE__) +#define _CTASSERT(x, y) __CTASSERT(x, y) +#define __CTASSERT(x, y) typedef char __assert_ ## y [(x) ? 1 : -1] +#endif + +#define ISCSI_BHS_SIZE 48 +#define ISCSI_HEADER_DIGEST_SIZE 4 +#define ISCSI_DATA_DIGEST_SIZE 4 + +#define ISCSI_BHS_OPCODE_IMMEDIATE 0x40 + +#define ISCSI_BHS_OPCODE_NOP_OUT 0x00 +#define ISCSI_BHS_OPCODE_SCSI_COMMAND 0x01 +#define ISCSI_BHS_OPCODE_TASK_REQUEST 0x02 +#define ISCSI_BHS_OPCODE_LOGIN_REQUEST 0x03 +#define ISCSI_BHS_OPCODE_TEXT_REQUEST 0x04 +#define ISCSI_BHS_OPCODE_SCSI_DATA_OUT 0x05 +#define ISCSI_BHS_OPCODE_LOGOUT_REQUEST 0x06 + +#define ISCSI_BHS_OPCODE_NOP_IN 0x20 +#define ISCSI_BHS_OPCODE_SCSI_RESPONSE 0x21 +#define ISCSI_BHS_OPCODE_TASK_RESPONSE 0x22 +#define ISCSI_BHS_OPCODE_LOGIN_RESPONSE 0x23 +#define ISCSI_BHS_OPCODE_TEXT_RESPONSE 0x24 +#define ISCSI_BHS_OPCODE_SCSI_DATA_IN 0x25 +#define ISCSI_BHS_OPCODE_LOGOUT_RESPONSE 0x26 +#define ISCSI_BHS_OPCODE_R2T 0x31 +#define ISCSI_BHS_OPCODE_ASYNC_MESSAGE 0x32 +#define ISCSI_BHS_OPCODE_REJECT 0x3f + +struct iscsi_bhs { + uint8_t bhs_opcode; + uint8_t bhs_opcode_specific1[3]; + uint8_t bhs_total_ahs_len; + uint8_t bhs_data_segment_len[3]; + uint64_t bhs_lun; + uint8_t bhs_inititator_task_tag[4]; + uint8_t bhs_opcode_specific4[28]; +}; +CTASSERT(sizeof(struct iscsi_bhs) == ISCSI_BHS_SIZE); + +#define BHSSC_FLAGS_F 0x80 +#define BHSSC_FLAGS_R 0x40 +#define BHSSC_FLAGS_W 0x20 +#define BHSSC_FLAGS_ATTR 0x07 + +#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_SESSION 3 + +struct iscsi_bhs_asynchronous_message { + uint8_t bhsam_opcode; + uint8_t bhsam_flags; + uint16_t bhsam_reserved; + uint8_t bhsam_total_ahs_len; + uint8_t bhsam_data_segment_len[3]; + uint64_t bhsam_lun; + uint32_t bhsam_0xffffffff; + uint32_t bhsam_reserved2; + uint32_t bhsam_statsn; + uint32_t bhsam_expcmdsn; + uint32_t bhsam_maxcmdsn; + uint8_t bhsam_async_event; + uint8_t bhsam_async_vcode; + uint16_t bhsam_parameter1; + uint16_t bhsam_parameter2; + uint16_t bhsam_parameter3; + uint32_t bhsam_reserved3; +}; +CTASSERT(sizeof(struct iscsi_bhs_asynchronous_message) == ISCSI_BHS_SIZE); + +#define BHSSR_REASON_DATA_DIGEST_ERROR 0x02 +#define BHSSR_PROTOCOL_ERROR 0x04 +#define BHSSR_COMMAND_NOT_SUPPORTED 0x05 +#define BHSSR_INVALID_PDU_FIELD 0x09 + +struct iscsi_bhs_reject { + uint8_t bhsr_opcode; + uint8_t bhsr_flags; + uint8_t bhsr_reason; + uint8_t bhsr_reserved; + uint8_t bhsr_total_ahs_len; + uint8_t bhsr_data_segment_len[3]; + uint64_t bhsr_reserved2; + uint32_t bhsr_0xffffffff; + uint32_t bhsr_reserved3; + uint32_t bhsr_statsn; + uint32_t bhsr_expcmdsn; + uint32_t bhsr_maxcmdsn; + uint32_t bhsr_datasn_r2tsn; + uint32_t bhsr_reserved4; + uint32_t bhsr_reserved5; +}; +CTASSERT(sizeof(struct iscsi_bhs_reject) == ISCSI_BHS_SIZE); + +#endif /* !ISCSI_H */ diff -urNp freebsd/src/sys/cam/scsi/scsi_all.h iscsi/sys/cam/scsi/scsi_all.h --- freebsd/src/sys/cam/scsi/scsi_all.h 2013-04-20 20:02:16.000000000 +0200 +++ iscsi/sys/cam/scsi/scsi_all.h 2013-04-08 21:24:56.000000000 +0200 @@ -1285,13 +1285,14 @@ struct scsi_vpd_id_descriptor #define SCSI_PROTO_SSA 0x02 #define SCSI_PROTO_1394 0x03 #define SCSI_PROTO_RDMA 0x04 -#define SCSI_PROTO_iSCSI 0x05 +#define SCSI_PROTO_ISCSI 0x05 #define SCSI_PROTO_SAS 0x06 #define SCSI_PROTO_ADT 0x07 #define SCSI_PROTO_ATA 0x08 #define SVPD_ID_PROTO_SHIFT 4 #define SVPD_ID_CODESET_BINARY 0x01 #define SVPD_ID_CODESET_ASCII 0x02 +#define SVPD_ID_CODESET_UTF8 0x03 #define SVPD_ID_CODESET_MASK 0x0f u_int8_t id_type; #define SVPD_ID_PIV 0x80 diff -urNp freebsd/src/sys/conf/files iscsi/sys/conf/files --- freebsd/src/sys/conf/files 2013-04-20 20:03:07.000000000 +0200 +++ iscsi/sys/conf/files 2013-04-20 19:04:08.000000000 +0200 @@ -123,6 +123,7 @@ cam/ctl/ctl_cmd_table.c optional ctl cam/ctl/ctl_frontend.c optional ctl cam/ctl/ctl_frontend_cam_sim.c optional ctl cam/ctl/ctl_frontend_internal.c optional ctl +cam/ctl/ctl_frontend_iscsi.c optional ctl cam/ctl/ctl_mem_pool.c optional ctl cam/ctl/ctl_scsi_all.c optional ctl cam/ctl/ctl_error.c optional ctl diff -urNp freebsd/src/sys/dev/iscsi/initiator2/iscsi.c iscsi/sys/dev/iscsi/initiator2/iscsi.c --- freebsd/src/sys/dev/iscsi/initiator2/iscsi.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/sys/dev/iscsi/initiator2/iscsi.c 2013-04-20 17:36:13.000000000 +0200 @@ -0,0 +1,2463 @@ +/*- + * 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 +#include +#include +#include +#include +#include +#include + +#include "iscsi_ioctl.h" +#include "iscsi.h" + +SYSCTL_NODE(_kern, OID_AUTO, iscsi, CTLFLAG_RD, 0, "iSCSI initiator"); +static int debug = 3; +TUNABLE_INT("kern.iscsi.debug", &debug); +SYSCTL_INT(_kern_iscsi, OID_AUTO, debug, CTLFLAG_RW, + &debug, 3, "Enable debug messages"); +static int ping_interval = 5; +TUNABLE_INT("kern.iscsi.ping_interval", &ping_interval); +SYSCTL_INT(_kern_iscsi, OID_AUTO, ping_interval, CTLFLAG_RW, &ping_interval, + 5, "Interval between ping (NOP-Out) requests, in seconds"); +static int daemon_timeout = 10; +TUNABLE_INT("kern.iscsi.daemon_timeout", &daemon_timeout); +SYSCTL_INT(_kern_iscsi, OID_AUTO, daemon_timeout, CTLFLAG_RW, &ping_interval, + 5, "Time to wait for iscsid(8) to login, requests, in seconds"); +static int partial_receive_len = 128; /* XXX: More? */ +TUNABLE_INT("kern.iscsi.partial_receive_len", &partial_receive_len); +SYSCTL_INT(_kern_iscsi, OID_AUTO, partial_receive_len, CTLFLAG_RW, + &partial_receive_len, 128, "Minimum read size for partially received " + "data segment"); + +MALLOC_DEFINE(M_ISCSI, "iSCSI", "iSCSI initiator"); +static uma_zone_t iscsi_pdu_zone; +static uma_zone_t iscsi_outstanding_zone; + +#define ISCSI_SESSION_LOCK(X) do { \ + if (debug > 2) { \ + if (mtx_trylock(&X->is_lock) == 0) { \ + printf("%s: mutex\n", __func__);\ + mtx_lock(&X->is_lock); \ + } \ + } else { \ + mtx_lock(&X->is_lock); \ + } \ + } while (0) +#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", +}; + +#define ISCSI_DEBUG(X) \ + if (debug > 1) { \ + printf X; \ + } while (0) +#define ISCSI_WARN(X) \ + if (debug > 0) { \ + printf("WARNING: "); \ + printf X; \ + } while (0) + +static void iscsi_pdu_update_statsn(const struct iscsi_pdu *response); +static void iscsi_pdu_handle_nop_in(struct iscsi_pdu *response); +static void iscsi_pdu_handle_scsi_response(struct iscsi_pdu *response); +static void iscsi_pdu_handle_task_response(struct iscsi_pdu *response); +static void iscsi_pdu_handle_data_in(struct iscsi_pdu *response); +static void iscsi_pdu_handle_logout_response(struct iscsi_pdu *response); +static void iscsi_pdu_handle_r2t(struct iscsi_pdu *response); +static void iscsi_pdu_handle_async_message(struct iscsi_pdu *response); +static void iscsi_pdu_handle_reject(struct iscsi_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 struct mbuf * +iscsi_session_receive(struct iscsi_session *is, size_t len) +{ + struct uio uio; + struct socket *so; + struct mbuf *m; + int error, flags; + + so = is->is_socket; + + if (so->so_error != 0) { + ISCSI_WARN(("%s: connection error %d\n", + __func__, so->so_error)); + return (NULL); + } + + memset(&uio, 0, sizeof(uio)); + uio.uio_resid = len; + + flags = MSG_DONTWAIT; + error = soreceive(so, NULL, &uio, &m, NULL, &flags); + if (error != 0) { + ISCSI_WARN(("%s: soreceive error %d\n", __func__, error)); + return (NULL); + } + if (uio.uio_resid != 0) { + m_freem(m); + ISCSI_WARN(("%s: short read\n", __func__)); + return (NULL); + } + + return (m); +} + +static struct iscsi_pdu * +iscsi_pdu_new(struct iscsi_session *is, int flags) +{ + struct iscsi_pdu *ip; + + refcount_acquire(&is->is_outstanding_pdus); + ip = uma_zalloc(iscsi_pdu_zone, flags | M_ZERO); + if (ip == NULL) { + ISCSI_WARN(("%s: failed to allocate %zd bytes\n", + __func__, sizeof(*ip))); + refcount_release(&is->is_outstanding_pdus); + return (NULL); + } + + ip->ip_session = is; + + return (ip); +} + +static void +iscsi_pdu_free(struct iscsi_pdu *ip) +{ + struct iscsi_session *is; + + is = ip->ip_session; + + m_freem(ip->ip_bhs_mbuf); + m_freem(ip->ip_ahs_mbuf); + m_freem(ip->ip_data_mbuf); + uma_zfree(iscsi_pdu_zone, ip); + refcount_release(&is->is_outstanding_pdus); +} + +/* + * Allocate iscsi_pdu with empty BHS to fill up by the caller. + */ +static struct iscsi_pdu * +iscsi_pdu_new_bhs(struct iscsi_session *is, int flags) +{ + struct iscsi_pdu *ip; + + ip = iscsi_pdu_new(is, 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) { + ISCSI_WARN(("%s: failed to allocate %zd bytes\n", + __func__, sizeof(*ip))); + iscsi_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 +iscsi_pdu_ahs_length(const struct iscsi_pdu *request) +{ + + return (request->ip_bhs->bhs_total_ahs_len * 4); +} + +static int +iscsi_pdu_data_segment_length(const struct iscsi_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 +iscsi_pdu_set_data_segment_length(struct iscsi_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 +iscsi_pdu_padding(const struct iscsi_pdu *ip) +{ + + if ((ip->ip_data_len % 4) != 0) + return (4 - (ip->ip_data_len % 4)); + + return (0); +} + +static size_t +iscsi_pdu_size(const struct iscsi_pdu *response) +{ + size_t len; + + KASSERT(response->ip_ahs_len == 0, ("responding with AHS")); + + len = sizeof(struct iscsi_bhs) + response->ip_data_len + + iscsi_pdu_padding(response); + if (response->ip_session->is_header_digest) + len += ISCSI_HEADER_DIGEST_SIZE; + if (response->ip_session->is_data_digest) + len += ISCSI_DATA_DIGEST_SIZE; + + return (len); +} + +static int +iscsi_pdu_receive_bhs(struct iscsi_pdu *request, size_t *availablep) +{ + struct mbuf *m; + + m = iscsi_session_receive(request->ip_session, + sizeof(struct iscsi_bhs)); + if (m == NULL) { + ISCSI_WARN(("%s: failed to receive BHS", __func__)); + return (-1); + } + + request->ip_bhs_mbuf = m_pullup(m, sizeof(struct iscsi_bhs)); + if (request->ip_bhs_mbuf == NULL) { + ISCSI_WARN(("%s: m_pulup failed", __func__)); + 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. + */ + + *availablep -= sizeof(struct iscsi_bhs); + return (0); +} + +static int +iscsi_pdu_receive_ahs(struct iscsi_pdu *request, size_t *availablep) +{ + + request->ip_ahs_len = iscsi_pdu_ahs_length(request); + if (request->ip_ahs_len == 0) + return (0); + + request->ip_ahs_mbuf = iscsi_session_receive(request->ip_session, + request->ip_ahs_len); + if (request->ip_ahs_mbuf == NULL) { + ISCSI_WARN(("%s: failed to receive AHS", __func__)); + return (-1); + } + + *availablep -= request->ip_ahs_len; + return (0); +} + +static uint32_t +iscsi_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 +iscsi_pdu_check_header_digest(struct iscsi_pdu *request, size_t *availablep) +{ + struct mbuf *m; + uint32_t received_digest, valid_digest; + + if (request->ip_session->is_header_digest == ISCSI_DIGEST_NONE) + return (0); + + m = iscsi_session_receive(request->ip_session, + ISCSI_HEADER_DIGEST_SIZE); + if (m == NULL) { + ISCSI_WARN(("%s: failed to receive header digest", __func__)); + return (-1); + } + + CTASSERT(sizeof(received_digest) == ISCSI_HEADER_DIGEST_SIZE); + memcpy(&received_digest, mtod(m, void *), ISCSI_HEADER_DIGEST_SIZE); + m_freem(m); + + *availablep -= ISCSI_HEADER_DIGEST_SIZE; + + /* + * XXX: Handle AHS. + */ + valid_digest = iscsi_mbuf_to_crc32c(request->ip_bhs_mbuf); + if (received_digest != valid_digest) { + ISCSI_WARN(("%s: header digest check failed; got 0x%x, " + "should be 0x%x", __func__, received_digest, valid_digest)); + return (-1); + } + + return (0); +} + +/* + * Return the number of bytes that should be waiting in the receive socket + * before iscsi_pdu_receive_data_segment() gets called. + */ +static size_t +iscsi_pdu_data_segment_receive_len(const struct iscsi_pdu *request) +{ + size_t len; + + len = iscsi_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 + ISCSI_DEBUG(("%s: need %zd bytes of data, limiting to %zd\n", + __func__, len, partial_receive_len)); +#endif + len = partial_receive_len; + + return (len); + } + + /* + * Account for padding. Note that due to the way code is written, + * the iscsi_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 + ISCSI_DEBUG(("%s: need %zd bytes of data\n", __func__, len)); +#endif + + return (len); +} + +static int +iscsi_pdu_receive_data_segment(struct iscsi_pdu *request, + size_t *availablep, bool *more_neededp) +{ + struct iscsi_session *is; + size_t len, padding = 0; + struct mbuf *m; + + is = request->ip_session; + + *more_neededp = false; + is->is_receive_len = 0; + + len = iscsi_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 + ISCSI_DEBUG(("%s: limited from %zd to %zd\n", + __func__, len + padding, *availablep - padding)); +#endif + len = *availablep - padding; + *more_neededp = true; + padding = 0; + } + + /* + * Must not try to receive padding without at least one byte + * of actual data segment. + */ + if (len > 0) { + m = iscsi_session_receive(request->ip_session, len + padding); + if (m == NULL) { + ISCSI_WARN(("%s: failed to receive data segment", + __func__)); + 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 + ISCSI_DEBUG(("%s: len 0\n", __func__)); + + if (*more_neededp) + is->is_receive_len = + iscsi_pdu_data_segment_receive_len(request); + + return (0); +} + +static int +iscsi_pdu_check_data_digest(struct iscsi_pdu *request, size_t *availablep) +{ + struct mbuf *m; + uint32_t received_digest, valid_digest; + + if (request->ip_session->is_data_digest == ISCSI_DIGEST_NONE) + return (0); + + if (request->ip_data_len == 0) + return (0); + + m = iscsi_session_receive(request->ip_session, + ISCSI_DATA_DIGEST_SIZE); + if (m == NULL) { + ISCSI_WARN(("%s: failed to receive data digest", __func__)); + return (-1); + } + + CTASSERT(sizeof(received_digest) == ISCSI_DATA_DIGEST_SIZE); + memcpy(&received_digest, mtod(m, void *), ISCSI_DATA_DIGEST_SIZE); + m_freem(m); + + *availablep -= ISCSI_DATA_DIGEST_SIZE; + + /* + * Note that 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 = iscsi_mbuf_to_crc32c(request->ip_data_mbuf); + if (received_digest != valid_digest) { + ISCSI_WARN(("%s: data digest check failed; got 0x%x, " + "should be 0x%x", __func__, received_digest, valid_digest)); + return (-1); + } + + return (0); +} + +/* + * Somewhat contrary to the name, this attempts to receive only one + * "part" of PDU at a time; call it repeatedly until it returns non-NULL. + */ +static struct iscsi_pdu * +iscsi_session_receive_pdu(struct iscsi_session *is, size_t *availablep) +{ + struct iscsi_pdu *request; + struct socket *so; + size_t len; + int error; + bool more_needed; + + so = is->is_socket; + + if (is->is_receive_state == ISCSI_SESSION_STATE_BHS) { + KASSERT(is->is_receive_pdu == NULL, + ("is->is_receive_pdu != NULL")); + request = iscsi_pdu_new(is, M_NOWAIT); + if (request == NULL) { + ISCSI_WARN(("%s: failed to allocate PDU; " + "dropping connection\n", __func__)); + iscsi_session_reconnect(is); + return (NULL); + } + is->is_receive_pdu = request; + } else { + KASSERT(is->is_receive_pdu != NULL, + ("is->is_receive_pdu == NULL")); + request = is->is_receive_pdu; + } + + if (*availablep < is->is_receive_len) { +#if 0 + ISCSI_DEBUG(("%s: not enough data; need %zd, " + "have %zd\n", __func__, is->is_receive_len, *availablep)); +#endif + return (NULL); + } + + switch (is->is_receive_state) { + case ISCSI_SESSION_STATE_BHS: + //ISCSI_DEBUG(("%s: receiving BHS\n", __func__)); + error = iscsi_pdu_receive_bhs(request, availablep); + if (error != 0) { + ISCSI_WARN(("%s: failed to receive BHS; " + "dropping connection\n", __func__)); + break; + } + + /* + * We don't enforce any limit for AHS length; + * its length is stored in 8 bit field. + */ + + len = iscsi_pdu_data_segment_length(request); + if (len > ISCSI_MAX_DATA_SEGMENT_LENGTH) { + ISCSI_WARN(("%s: received data segment " + "length %zd is larger than negotiated " + "MaxDataSegmentLength %d; " + "dropping connection\n", __func__, + len, ISCSI_MAX_DATA_SEGMENT_LENGTH)); + break; + } + + is->is_receive_state = ISCSI_SESSION_STATE_AHS; + is->is_receive_len = iscsi_pdu_ahs_length(request); + break; + + case ISCSI_SESSION_STATE_AHS: + //ISCSI_DEBUG(("%s: receiving AHS\n", __func__)); + error = iscsi_pdu_receive_ahs(request, availablep); + if (error != 0) { + ISCSI_WARN(("%s: failed to receive AHS; " + "dropping connection\n", __func__)); + break; + } + is->is_receive_state = ISCSI_SESSION_STATE_HEADER_DIGEST; + if (is->is_header_digest == ISCSI_DIGEST_NONE) + is->is_receive_len = 0; + else + is->is_receive_len = ISCSI_DATA_DIGEST_SIZE; + break; + + case ISCSI_SESSION_STATE_HEADER_DIGEST: + //ISCSI_DEBUG(("%s: receiving header digest\n", __func__)); + error = iscsi_pdu_check_header_digest(request, availablep); + if (error != 0) { + ISCSI_WARN(("%s: header digest failed; " + "dropping connection\n", __func__)); + break; + } + + is->is_receive_state = ISCSI_SESSION_STATE_DATA; + is->is_receive_len = + iscsi_pdu_data_segment_receive_len(request); + break; + + case ISCSI_SESSION_STATE_DATA: + //ISCSI_DEBUG(("%s: receiving data segment\n", __func__)); + error = iscsi_pdu_receive_data_segment(request, availablep, + &more_needed); + if (error != 0) { + ISCSI_WARN(("%s: failed to receive data segment;" + "dropping connection\n", + __func__)); + break; + } + + if (more_needed) + break; + + is->is_receive_state = ISCSI_SESSION_STATE_DATA_DIGEST; + if (is->is_data_digest == ISCSI_DIGEST_NONE) + is->is_receive_len = 0; + else + is->is_receive_len = ISCSI_HEADER_DIGEST_SIZE; + break; + + case ISCSI_SESSION_STATE_DATA_DIGEST: + //ISCSI_DEBUG(("%s: receiving data digest\n", __func__)); + error = iscsi_pdu_check_data_digest(request, availablep); + if (error != 0) { + ISCSI_WARN(("%s: data digest failed; " + "dropping connection\n", __func__)); + break; + } + + /* + * We've received complete PDU; reset the receive state machine + * and return the PDU. + */ + is->is_receive_state = ISCSI_SESSION_STATE_BHS; + is->is_receive_len = sizeof(struct iscsi_bhs); + is->is_receive_pdu = NULL; + return (request); + + default: + panic("invalid is_receive_state %d\n", is->is_receive_state); + } + + if (error != 0) { + iscsi_pdu_free(request); + iscsi_session_reconnect(is); + } + + return (NULL); +} + +static void +iscsi_session_receive_pdus(struct iscsi_session *is, size_t available) +{ + struct iscsi_pdu *response; + struct socket *so; + + so = is->is_socket; + + for (;;) { + if (is->is_terminating != 0 || is->is_reconnecting != 0) + return; + + if (so->so_error != 0) { + ISCSI_WARN(("%s: connection error %d; " + "dropping connection\n", __func__, so->so_error)); + iscsi_session_reconnect(is); + return; + } + + /* + * Loop until we have a complete PDU or there is not enough + * data in the socket buffer. + */ + if (available < is->is_receive_len) { +#if 0 + ISCSI_DEBUG(("%s: not enough data; have %zd, " + "need %zd\n", __func__, available, + is->is_receive_len)); +#endif + return; + } + + response = iscsi_session_receive_pdu(is, &available); + if (response == NULL) + continue; + + iscsi_pdu_update_statsn(response); + + if (response->ip_ahs_len > 0) { + ISCSI_WARN(("%s: received PDU with unsupported " + "AHS; opcode 0x%x; dropping connection\n", + __func__, response->ip_bhs->bhs_opcode)); + iscsi_pdu_free(response); + iscsi_session_reconnect(is); + return; + } + + /* + * Handle the PDU; this includes e.g. receiving the remaining + * part of PDU and submitting the SCSI command to CTL + * or queueing a reply. The handling routine is responsible + * for freeing the PDU when it's no longer needed. + */ + switch (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_TASK_RESPONSE: + iscsi_pdu_handle_task_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_WARN(("%s: received PDU with unsupported " + "opcode 0x%x; dropping connection\n", + __func__, response->ip_bhs->bhs_opcode)); + iscsi_pdu_free(response); + iscsi_session_reconnect(is); + } + } +} + +static void +iscsi_receive_thread(void *arg) +{ + struct iscsi_session *is; + size_t available; + struct socket *so; + + is = arg; + so = is->is_socket; + + for (;;) { + //ISCSI_DEBUG(("%s: running\n", __func__)); + if (is->is_terminating != 0 || is->is_reconnecting != 0) { + ISCSI_DEBUG(("%s: exiting\n", __func__)); + kthread_exit(); + return; + } + + SOCKBUF_LOCK(&so->so_rcv); + available = so->so_rcv.sb_cc; + if (available < is->is_receive_len) + cv_wait(&is->is_receive_cv, &so->so_rcv.sb_mtx); + SOCKBUF_UNLOCK(&so->so_rcv); + + iscsi_session_receive_pdus(is, available); + } +} + +static int +iscsi_soupcall_receive(struct socket *so, void *arg, int waitflag) +{ + struct iscsi_session *is; + + is = arg; + cv_signal(&is->is_receive_cv); + return (SU_OK); +} + +static int +iscsi_pdu_prepare(struct iscsi_pdu *request) +{ + struct iscsi_session *is; + struct iscsi_bhs_scsi_command *bhssc; + struct socket *so; + int ok; + size_t padding, pdu_len; + uint32_t digest, zero = 0; + + is = request->ip_session; + so = is->is_socket; + + /* + * We're only using fields common for all the request + * (initiator -> target) PDUs. + */ + bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; + + ISCSI_SESSION_LOCK(is); + //ISCSI_DEBUG(("%s: cmdsn %d\n", __func__, is->is_cmdsn)); + /* + * Data-Out PDU does not contain CmdSN. + */ + if (bhssc->bhssc_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_OUT) { + while (is->is_cmdsn > is->is_maxcmdsn && + (bhssc->bhssc_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) { + ISCSI_DEBUG(("%s: throttling send, cmdsn %d, expcmdsn %d, maxcmdsn %d\n", + __func__, is->is_cmdsn, is->is_expcmdsn, is->is_maxcmdsn)); + cv_wait(&is->is_cmdsn_cv, &is->is_lock); + } + 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); + ISCSI_SESSION_UNLOCK(is); + + iscsi_pdu_set_data_segment_length(request, request->ip_data_len); + + pdu_len = iscsi_pdu_size(request); + + if (so->so_error != 0) { + ISCSI_WARN(("%s: connection error %d\n", + __func__, so->so_error)); + return (1); + } + + if (is->is_header_digest != ISCSI_DIGEST_NONE) { + digest = iscsi_mbuf_to_crc32c(request->ip_bhs_mbuf); + ok = m_append(request->ip_bhs_mbuf, sizeof(digest), + (void *)&digest); + if (ok != 1) { + ISCSI_WARN(("%s: failed to append header digest\n", + __func__)); + return (1); + } + } + + if (request->ip_data_len != 0) { + padding = iscsi_pdu_padding(request); + if (padding > 0) { + ok = m_append(request->ip_data_mbuf, padding, + (void *)&zero); + if (ok != 1) { + ISCSI_WARN(("%s: failed to append padding\n", + __func__)); + return (1); + } + } + + if (is->is_data_digest != ISCSI_DIGEST_NONE) { + digest = iscsi_mbuf_to_crc32c(request->ip_data_mbuf); + + ok = m_append(request->ip_data_mbuf, sizeof(digest), + (void *)&digest); + if (ok != 1) { + ISCSI_WARN(("%s: failed to append header " + "digest\n", __func__)); + 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; + + return (0); +} + +static int +iscsi_pdu_send(struct iscsi_pdu *request) +{ + int error; + struct socket *so; + + so = request->ip_session->is_socket; + + 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) { + ISCSI_WARN(("%s: sosend error %d\n", __func__, error)); + return (error); + } + + return (0); +} + +static void +iscsi_session_send_pdus(struct iscsi_session *is) +{ + struct iscsi_pdu *request; + int error; + + ISCSI_SESSION_LOCK(is); + while (!TAILQ_EMPTY(&is->is_to_send)) { + if (is->is_terminating != 0 || is->is_reconnecting != 0) { + ISCSI_SESSION_UNLOCK(is); + return; + } + + request = TAILQ_FIRST(&is->is_to_send); + SOCKBUF_LOCK(&is->is_socket->so_snd); + if (sbspace(&is->is_socket->so_snd) < + iscsi_pdu_size(request)) { + SOCKBUF_UNLOCK(&is->is_socket->so_snd); +#if 0 + ISCSI_DEBUG(("%s: no space to send; " + "have %zd, need %zd\n", __func__, + sbspace(&is->is_socket->so_snd), + iscsi_pdu_size(request))); +#endif + break; + } else { +#if 0 + ISCSI_DEBUG(("%s: have %zd of space to send\n", + __func__, sbspace(&is->is_socket->so_snd))); +#endif + } + + SOCKBUF_UNLOCK(&is->is_socket->so_snd); + TAILQ_REMOVE(&is->is_to_send, request, ip_next); + ISCSI_SESSION_UNLOCK(is); + error = iscsi_pdu_prepare(request); + if (error != 0) { + ISCSI_WARN(("%s: failed to prepare PDU; " + "dropping connection\n", __func__)); + iscsi_pdu_free(request); + iscsi_session_reconnect(is); + return; + } + error = iscsi_pdu_send(request); + if (error != 0) { + ISCSI_WARN(("%s: failed to send PDU; " + "dropping connection\n", __func__)); + iscsi_session_reconnect(is); + ISCSI_SESSION_LOCK(is); + TAILQ_INSERT_HEAD(&is->is_to_send, request, ip_next); + ISCSI_SESSION_UNLOCK(is); + return; + } + iscsi_pdu_free(request); + ISCSI_SESSION_LOCK(is); + } + ISCSI_SESSION_UNLOCK(is); +} + +static void +iscsi_send_thread(void *arg) +{ + struct iscsi_session *is; + + is = arg; + + for (;;) { + //ISCSI_DEBUG(("%s: running\n", __func__)); + if (is->is_terminating != 0 || is->is_reconnecting != 0) { + ISCSI_DEBUG(("%s: exiting\n", __func__)); + kthread_exit(); + return; + } + ISCSI_SESSION_LOCK(is); + cv_wait(&is->is_send_cv, &is->is_lock); + ISCSI_SESSION_UNLOCK(is); + iscsi_session_send_pdus(is); + } +} + +static int +iscsi_soupcall_send(struct socket *so, void *arg, int waitflag) +{ + struct iscsi_session *is; + + is = arg; + cv_signal(&is->is_send_cv); + return (SU_OK); +} + +static void +iscsi_pdu_append_data(struct iscsi_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) { + ISCSI_WARN(("%s: failed to allocate mbuf for %zd bytes\n", + __func__, len)); + iscsi_session_reconnect(request->ip_session); + return; + } + + for (mb = newmb; mb != NULL; mb = mb->m_next) { + copylen = min(M_TRAILINGSPACE(mb), len - off); + memcpy(mtod(mb, char *), (const char *)addr + off, copylen); + mb->m_len = copylen; + off += copylen; + } + KASSERT(off == len, ("%s: off != len", __func__)); + + if (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; + } +} + +static void +iscsi_pdu_queue_locked(struct iscsi_pdu *ip) +{ + struct iscsi_session *is; + + is = ip->ip_session; + + TAILQ_INSERT_TAIL(&is->is_to_send, ip, ip_next); + cv_signal(&is->is_send_cv); +} + +static void +iscsi_pdu_queue_head(struct iscsi_pdu *ip) +{ + struct iscsi_session *is; + + is = ip->ip_session; + + ISCSI_SESSION_LOCK(is); + TAILQ_INSERT_HEAD(&is->is_to_send, ip, ip_next); + ISCSI_SESSION_UNLOCK(is); + cv_signal(&is->is_send_cv); +} + +static void +iscsi_pdu_update_statsn(const struct iscsi_pdu *response) +{ + const struct iscsi_bhs_data_in *bhsdi; + struct iscsi_session *is; + + is = response->ip_session; + + /* + * We're only using fields common for all the response + * (target -> initiator) PDUs. + */ + bhsdi = (const struct iscsi_bhs_data_in *)response->ip_bhs; + ISCSI_SESSION_LOCK(is); + /* + * 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". + */ + 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_WARN(("%s: statsn %d -> %d, opcode 0x%x\n", + __func__, is->is_statsn, ntohl(bhsdi->bhsdi_statsn), + bhsdi->bhsdi_opcode)); + is->is_statsn = ntohl(bhsdi->bhsdi_statsn); + } + is->is_expcmdsn = ntohl(bhsdi->bhsdi_expcmdsn); + is->is_maxcmdsn = ntohl(bhsdi->bhsdi_maxcmdsn); + + //ISCSI_DEBUG(("%s: expcmdsn %d, maxcmdsn %d\n", + // __func__, is->is_expcmdsn, is->is_maxcmdsn)); + + cv_signal(&is->is_cmdsn_cv); + + /* + * XXX: Check for target errors. + */ + + ISCSI_SESSION_UNLOCK(is); +} + +static void +iscsi_session_logout(struct iscsi_session *is) +{ + struct iscsi_pdu *request; + struct iscsi_bhs_logout_request *bhslr; + + request = iscsi_pdu_new_bhs(is, 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); +} + +#if 0 +static void +iscsi_session_terminate_tasks(struct iscsi_session *is) +{ + struct iscsi_outstanding *io, *tmp; + + /* + * XXX: Locking. + */ + TAILQ_FOREACH_SAFE(io, &is->is_outstanding, io_next, tmp) { + io->io_ccb->ccb_h.status = CAM_REQ_ABORTED; + xpt_done(io->io_ccb); + iscsi_outstanding_remove(is, io); + } +} +#endif + +static void +iscsi_maintenance_thread_reconnect(struct iscsi_session *is) +{ + + ISCSI_DEBUG(("%s: go\n", __func__)); + + if (is->is_socket != NULL) + soshutdown(is->is_socket, SHUT_RDWR); + + ISCSI_SESSION_LOCK(is); + + is->is_desc.isd_connected = 0; + + /* + * Wake up other threads, so they can properly terminate. + */ + cv_signal(&is->is_receive_cv); + cv_signal(&is->is_send_cv); + cv_signal(&is->is_cmdsn_cv); + + ISCSI_SESSION_UNLOCK(is); + pause("mt", 1 * hz); + ISCSI_SESSION_LOCK(is); + + if (is->is_receive_pdu != NULL) { + ISCSI_DEBUG(("%s: freeing partially received PDU\n", + __func__)); + iscsi_pdu_free(is->is_receive_pdu); + is->is_receive_pdu = NULL; + } + + ISCSI_DEBUG(("%s: freeing socket\n", __func__)); + + if (is->is_socket != NULL) { +#if 0 + SOCKBUF_LOCK(&is->is_socket->so_snd); + soupcall_clear(is->is_socket, SO_SND); + SOCKBUF_UNLOCK(&is->is_socket->so_snd); + SOCKBUF_LOCK(&is->is_socket->so_rcv); + soupcall_clear(is->is_socket, SO_RCV); + SOCKBUF_UNLOCK(&is->is_socket->so_rcv); +#endif + soclose(is->is_socket); + is->is_socket = NULL; + } + + is->is_reconnecting = 0; + + /* + * Request immediate reconnection from iscsid(8). + */ + ISCSI_DEBUG(("%s: waking up iscsid(8)\n", __func__)); + is->is_daemon_waiting = daemon_timeout + 1; + cv_signal(&is->is_softc->sc_cv); + ISCSI_SESSION_UNLOCK(is); +} + +static void +iscsi_maintenance_thread_terminate(struct iscsi_session *is) +{ + struct iscsi_pdu *request; + struct iscsi_softc *sc; + + ISCSI_DEBUG(("%s: go, is_terminating = %d\n", __func__, is->is_terminating)); + + ISCSI_SESSION_LOCK(is); + + KASSERT(is->is_terminating > 0, ("is_terminating == 0")); + + is->is_terminating++; + + /* + * Wake up other threads, so they can properly terminate. + */ + cv_signal(&is->is_receive_cv); + cv_signal(&is->is_send_cv); + cv_signal(&is->is_cmdsn_cv); + callout_drain(&is->is_callout); + + ISCSI_SESSION_UNLOCK(is); + pause("mt", 1 * hz); + ISCSI_SESSION_LOCK(is); + + if (is->is_receive_pdu != NULL) { + ISCSI_DEBUG(("%s: freeing partially received PDU\n", + __func__)); + iscsi_pdu_free(is->is_receive_pdu); + is->is_receive_pdu = NULL; + } + + /* + * Remove any outstanding PDUs from the send queue. + */ + while (!TAILQ_EMPTY(&is->is_to_send)) { + request = TAILQ_FIRST(&is->is_to_send); + iscsi_pdu_free(request); + TAILQ_REMOVE(&is->is_to_send, request, ip_next); + } + +#if 0 + /* + * Forcibly terminate SCSI tasks. + * + * XXX: Calling this under traffic causes panic. + * Why so? Is it needed at all? + */ + ISCSI_DEBUG(("%s: terminating tasks\n", __func__)); + iscsi_session_terminate_tasks(is); +#endif + + KASSERT(is->is_outstanding_pdus == 0, + ("destroying session with pdus")); + KASSERT(TAILQ_EMPTY(&is->is_to_send), + ("destroying session with non-empty send queue")); + KASSERT(TAILQ_EMPTY(&is->is_outstanding), + ("destroying session with active tasks")); + + /* + * Deregister CAM. + */ + if (is->is_sim != NULL) { + ISCSI_DEBUG(("%s: deregistering bus\n", __func__)); + xpt_bus_deregister(cam_sim_path(is->is_sim)); + ISCSI_DEBUG(("%s: deregistering sim\n", __func__)); + cam_sim_free(is->is_sim, TRUE /*free_devq*/); + ISCSI_DEBUG(("%s: done\n", __func__)); + is->is_sim = NULL; + ISCSI_DEBUG(("%s: really done\n", __func__)); + } + + ISCSI_SESSION_UNLOCK(is); + + if (is->is_socket != NULL) { + soshutdown(is->is_socket, SHUT_RDWR); +#if 0 + SOCKBUF_LOCK(&is->is_socket->so_snd); + soupcall_clear(is->is_socket, SO_SND); + SOCKBUF_UNLOCK(&is->is_socket->so_snd); + SOCKBUF_LOCK(&is->is_socket->so_rcv); + soupcall_clear(is->is_socket, SO_RCV); + SOCKBUF_UNLOCK(&is->is_socket->so_rcv); +#endif + soclose(is->is_socket); + is->is_socket = NULL; + } + + sc = is->is_softc; + sx_xlock(&sc->sc_lock); + TAILQ_REMOVE(&sc->sc_sessions, is, is_next); + free(is, M_ISCSI); + sx_xunlock(&sc->sc_lock); + + ISCSI_DEBUG(("%s: terminated\n", __func__)); +} + +static void +iscsi_maintenance_thread(void *arg) +{ + struct iscsi_session *is; + + is = arg; + + for (;;) { + ISCSI_SESSION_LOCK(is); + if (is->is_terminating == 0 && is->is_reconnecting == 0) { + cv_wait(&is->is_maintenance_cv, &is->is_lock); + ISCSI_SESSION_UNLOCK(is); + continue; + } + 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; + } + } +} + +static void +iscsi_session_reconnect(struct iscsi_session *is) +{ + if (is->is_reconnecting != 0) + return; + + is->is_reconnecting = 1; + cv_signal(&is->is_maintenance_cv); +} + +static void +iscsi_session_terminate(struct iscsi_session *is) +{ + if (is->is_terminating != 0) + return; + + is->is_terminating = 1; + + /* + * At this point, iscsi_action() and iscsi_callout() won't submit + * any new requests. Send logout request; this will also kick + * the sending thread. The rest will happen in callout. + */ + iscsi_session_logout(is); + cv_signal(&is->is_maintenance_cv); +} + +static void +iscsi_callout(void *context) +{ + struct iscsi_pdu *request; + struct iscsi_bhs_nop_out *bhsno; + struct iscsi_session *is; + + is = context; + + if (is->is_terminating) + return; + + ISCSI_SESSION_LOCK(is); + + if (is->is_socket == NULL) { + if (is->is_daemon_waiting > daemon_timeout) { + ISCSI_WARN(("%s: timed out waiting for iscsid(8)\n", + __func__)); + cv_signal(&is->is_softc->sc_cv); + } + + is->is_daemon_waiting++; + ISCSI_SESSION_UNLOCK(is); + callout_schedule(&is->is_callout, 1 * hz); + return; + } + + if (is->is_ping_waiting == 1) { + ISCSI_WARN(("%s: still waiting for ping reply (NOP-In)\n", + __func__)); + } else if (is->is_ping_waiting >= 2) { + ISCSI_WARN(("%s: no ping reply (NOP-In) after %d seconds; " + "dropping connection\n", __func__, ping_interval * 3)); + iscsi_session_reconnect(is); + ISCSI_SESSION_UNLOCK(is); + return; + } + + is->is_ping_waiting++; + ISCSI_SESSION_UNLOCK(is); + + request = iscsi_pdu_new_bhs(is, 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; + + /* + * We wan't this to get replied to as soon as possible. There might + * be quite a few PDUs waiting in the send queue, so queue it at the + * head instead of appending at the tail. + */ + iscsi_pdu_queue_head(request); + + callout_schedule(&is->is_callout, 1 * hz); +} + +static void +iscsi_pdu_handle_nop_in(struct iscsi_pdu *response) +{ + struct iscsi_bhs_nop_out *bhsno; + struct iscsi_bhs_nop_in *bhsni; + struct iscsi_pdu *request; + + bhsni = (struct iscsi_bhs_nop_in *)response->ip_bhs; + + if (bhsni->bhsni_target_transfer_tag == 0xffffffff) { + ISCSI_SESSION_LOCK(response->ip_session); + response->ip_session->is_ping_waiting = 0; + ISCSI_SESSION_UNLOCK(response->ip_session); + iscsi_pdu_free(response); + return; + } + + request = iscsi_pdu_new_bhs(response->ip_session, M_NOWAIT); + if (request == NULL) { + iscsi_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; + + iscsi_pdu_free(response); + iscsi_pdu_queue_head(request); +} + +static void +iscsi_pdu_handle_scsi_response(struct iscsi_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 = response->ip_session; + + bhssr = (struct iscsi_bhs_scsi_response *)response->ip_bhs; + //ISCSI_DEBUG(("%s: SCSI response, status 0x%x, response 0x%x\n", + // __func__, bhssr->bhssr_status, bhssr->bhssr_response)); + io = iscsi_outstanding_find(is, bhssr->bhssr_initiator_task_tag); + if (io == NULL) { + ISCSI_WARN(("%s: bad itt 0x%x\n", + __func__, bhssr->bhssr_initiator_task_tag)); + iscsi_pdu_free(response); + iscsi_session_reconnect(is); + return; + } + + if (bhssr->bhssr_response != BHSSR_RESPONSE_COMMAND_COMPLETED) { + ISCSI_WARN(("%s: bad response 0x%x\n", + __func__, 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 = iscsi_pdu_data_segment_length(response); + if (data_segment_len > 0) { + if (data_segment_len < 2) { + ISCSI_WARN(("%s: truncated data segment (%zd bytes)\n", + __func__, data_segment_len)); + io->io_ccb->ccb_h.status = CAM_REQ_CMP_ERR; + goto out; + } + m_copydata(response->ip_data_mbuf, 0, sizeof(sense_len), + (void *)&sense_len); + sense_len = ntohs(sense_len); + ISCSI_DEBUG(("%s: sense_len %d, data len %zd\n", + __func__, sense_len, data_segment_len)); + if (sizeof(sense_len) + sense_len > data_segment_len) { + ISCSI_WARN(("%s: truncated data segment " + "(%zd bytes, should be %ld)\n", __func__, + 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_WARN(("%s: oversize data segment " + "(%ld bytes, should be %zd)\n", __func__, + data_segment_len, sizeof(sense_len) + sense_len)); + if (sense_len > io->io_ccb->csio.sense_len) { + ISCSI_DEBUG(("%s: truncating sense from %d to %d\n", + __func__, sense_len, io->io_ccb->csio.sense_len)); + sense_len = io->io_ccb->csio.sense_len; + } + m_copydata(response->ip_data_mbuf, sizeof(sense_len), + sense_len, (void *)&io->io_ccb->csio.sense_data); + } + +out: + xpt_done(io->io_ccb); + iscsi_outstanding_remove(is, io); + iscsi_pdu_free(response); +} + +static void +iscsi_pdu_handle_task_response(struct iscsi_pdu *response) +{ + + ISCSI_DEBUG(("%s: task response\n", __func__)); + iscsi_pdu_free(response); +} + +static void +iscsi_pdu_handle_data_in(struct iscsi_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; + + //ISCSI_DEBUG(("%s: data in\n", __func__)); + + is = response->ip_session; + bhsdi = (struct iscsi_bhs_data_in *)response->ip_bhs; + io = iscsi_outstanding_find(is, bhsdi->bhsdi_initiator_task_tag); + if (io == NULL) { + ISCSI_WARN(("%s: bad itt 0x%x\n", + __func__, bhsdi->bhsdi_initiator_task_tag)); + iscsi_pdu_free(response); + iscsi_session_reconnect(is); + return; + } + + data_segment_len = iscsi_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." + */ + iscsi_pdu_free(response); + return; + } + + csio = &io->io_ccb->csio; + + if (ntohl(bhsdi->bhsdi_buffer_offset) + data_segment_len > + csio->dxfer_len) { + ISCSI_WARN(("%s: oversize data segment (%zd bytes " + "at offset %d, buffer is %d)\n", __func__, + data_segment_len, ntohl(bhsdi->bhsdi_buffer_offset), + csio->dxfer_len)); + iscsi_pdu_free(response); + iscsi_session_reconnect(is); + } + + m_copydata(response->ip_data_mbuf, 0, data_segment_len, + csio->data_ptr + ntohl(bhsdi->bhsdi_buffer_offset)); + + /* + * XXX: Check DataSN. + * XXX: Check F. + * XXX: How to pass overflow/underflow information to CAM? + */ + if (bhsdi->bhsdi_flags & BHSDI_FLAGS_S) { + //ISCSI_DEBUG(("%s: got S flag; status 0x%x\n", + // __func__, 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); + } + + iscsi_pdu_free(response); +} + +static void +iscsi_pdu_handle_logout_response(struct iscsi_pdu *response) +{ + + ISCSI_DEBUG(("%s: logout response\n", __func__)); + iscsi_pdu_free(response); +} + +static void +iscsi_pdu_handle_r2t(struct iscsi_pdu *response) +{ + struct iscsi_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 = response->ip_session; + bhsr2t = (struct iscsi_bhs_r2t *)response->ip_bhs; + io = iscsi_outstanding_find(is, bhsr2t->bhsr2t_initiator_task_tag); + if (io == NULL) { + ISCSI_WARN(("%s: bad itt 0x%x\n", + __func__, bhsr2t->bhsr2t_initiator_task_tag)); + iscsi_pdu_free(response); + iscsi_session_reconnect(is); + return; + } + + /* + * XXX: Verify R2TSN. + */ + + io->io_datasn = 0; + csio = &io->io_ccb->csio; + off = ntohl(bhsr2t->bhsr2t_buffer_offset); + total_len = ntohl(bhsr2t->bhsr2t_desired_data_transfer_length); + + //ISCSI_DEBUG(("%s: r2t; off %zd, len %zd\n", + // __func__, off, total_len)); + + for (;;) { + len = total_len; + + if (len > is->is_max_data_segment_length) + len = is->is_max_data_segment_length; + + if (off + len > csio->dxfer_len) { + ISCSI_WARN(("%s: bad off %zd, len %d\n", + __func__, off + len, csio->dxfer_len)); + iscsi_pdu_free(response); + iscsi_session_reconnect(is); + } + + request = iscsi_pdu_new_bhs(response->ip_session, M_NOWAIT); + if (request == NULL) { + iscsi_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); + iscsi_pdu_append_data(request, csio->data_ptr + off, len); + + off += len; + total_len -= len; + + if (total_len == 0) { + bhsdo->bhsdo_flags |= BHSDO_FLAGS_F; + //ISCSI_DEBUG(("%s: setting F, off %zd\n", + // __func__, off)); + } else { + //ISCSI_DEBUG(("%s: not finished, off %zd\n", + // __func__, off)); + } + + iscsi_pdu_queue_locked(request); + + if (total_len == 0) + break; + } + + iscsi_pdu_free(response); +} + +static void +iscsi_pdu_handle_async_message(struct iscsi_pdu *response) +{ + struct iscsi_bhs_asynchronous_message *bhsam; + + bhsam = (struct iscsi_bhs_asynchronous_message *)response->ip_bhs; + switch (bhsam->bhsam_async_event) { + case BHSAM_EVENT_TARGET_REQUESTS_LOGOUT: + ISCSI_WARN(("%s: target requests logout; removing session\n", + __func__)); + iscsi_session_terminate(response->ip_session); + break; + default: + /* + * XXX: Technically, we're obligated to also handle + * parameter renegotiation. + */ + ISCSI_DEBUG(("%s: ignoring AsyncEvent %d\n", + __func__, bhsam->bhsam_async_event)); + break; + } + + iscsi_pdu_free(response); +} + +static void +iscsi_pdu_handle_reject(struct iscsi_pdu *response) +{ + struct iscsi_bhs_reject *bhsr; + + bhsr = (struct iscsi_bhs_reject *)response->ip_bhs; + ISCSI_WARN(("%s: received Reject PDU, reason 0x%x; protocol error?\n", + __func__, bhsr->bhsr_reason)); + + iscsi_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_socket == NULL && is->is_terminating == 0 && + is->is_daemon_waiting > daemon_timeout) + 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; + } + + is->is_daemon_waiting = 0; + + 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) +{ + size_t bufsize; + struct file *fp; + struct socket *so; + struct iscsi_session *is; + struct sockopt opt; + int one = 1; + int error; + int num_reqs = 128; + + 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; + } + ISCSI_SESSION_LOCK(is); + if (is == NULL) { + ISCSI_SESSION_UNLOCK(is); + sx_sunlock(&sc->sc_lock); + return (ESRCH); + } + if (is->is_socket != NULL) { + ISCSI_SESSION_UNLOCK(is); + sx_sunlock(&sc->sc_lock); + return (EBUSY); + } + if (is->is_desc.isd_discovery || is->is_terminating) { + ISCSI_SESSION_UNLOCK(is); + sx_sunlock(&sc->sc_lock); + return (EINVAL); + } + + /* + * Steal the socket from userland. + */ + error = fget(curthread, handoff->idh_socket, CAP_SOCK_ALL, &fp); + if (error != 0) { + ISCSI_SESSION_UNLOCK(is); + sx_sunlock(&sc->sc_lock); + return (error); + } + if (fp->f_type != DTYPE_SOCKET) { + fdrop(fp, curthread); + ISCSI_SESSION_UNLOCK(is); + sx_sunlock(&sc->sc_lock); + return (EINVAL); + } + so = fp->f_data; + if (so->so_type != SOCK_STREAM) { + fdrop(fp, curthread); + ISCSI_SESSION_UNLOCK(is); + sx_sunlock(&sc->sc_lock); + return (EINVAL); + } + + is->is_socket = fp->f_data; + fp->f_ops = &badfileops; + fp->f_data = NULL; + fdrop(fp, curthread); + + strlcpy(is->is_desc.isd_target_alias, handoff->idh_target_alias, + sizeof(is->is_desc.isd_target_alias)); + memcpy(is->is_isid, handoff->idh_isid, sizeof(is->is_isid)); + + /* + * XXX: When those change, what to do with PDUs in the output queue? + */ + is->is_statsn = handoff->idh_statsn; + is->is_header_digest = handoff->idh_header_digest; + is->is_data_digest = handoff->idh_data_digest; + is->is_initial_r2t = handoff->idh_initial_r2t; + is->is_immediate_data = handoff->idh_immediate_data; + is->is_max_data_segment_length = handoff->idh_max_data_segment_length; + is->is_max_burst_length = handoff->idh_max_burst_length; + is->is_first_burst_length = handoff->idh_first_burst_length; + + is->is_reconnecting = 0; + is->is_receive_state = ISCSI_SESSION_STATE_BHS; + is->is_receive_len = sizeof(struct iscsi_bhs); + + /* + * Use max available sockbuf size for sending. Do it manually + * instead of sbreserve(9) to work around resource limits. + * + * XXX: This kind of sucks. On one hand, we don't currently support + * sending a part of data segment; we always do it in one piece, + * so we have to make sure it can fit in the socket buffer. + * Once I've implemented partial send, we'll get rid of this + * and use autoscaling. + */ + bufsize = (sizeof(struct iscsi_bhs) + + ISCSI_MAX_DATA_SEGMENT_LENGTH) * 8; + error = soreserve(is->is_socket, bufsize, bufsize); + if (error != 0) { + ISCSI_WARN(("%s: soreserve failed with error %d\n", + __func__, error)); + ISCSI_SESSION_UNLOCK(is); + sx_sunlock(&sc->sc_lock); + iscsi_session_terminate(is); + 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(is->is_socket, &opt); + if (error != 0) { + ISCSI_WARN(("%s: disabling TCP_NODELAY failed " + "with error %d\n", __func__, error)); + ISCSI_SESSION_UNLOCK(is); + sx_sunlock(&sc->sc_lock); + iscsi_session_terminate(is); + return (error); + } + ISCSI_SESSION_UNLOCK(is); + + /* + * Start threads. + */ + error = kthread_add(iscsi_send_thread, is, NULL, NULL, 0, 0, "iscsitx"); + if (error != 0) { + ISCSI_WARN(("%s: kthread_add(9) failed with error %d\n", + __func__, error)); + sx_sunlock(&sc->sc_lock); + iscsi_session_terminate(is); + return (error); + } + + error = kthread_add(iscsi_receive_thread, is, NULL, NULL, 0, 0, "iscsirx"); + if (error != 0) { + ISCSI_WARN(("%s: kthread_add(9) failed with error %d\n", + __func__, error)); + sx_sunlock(&sc->sc_lock); + iscsi_session_terminate(is); + return (error); + } + + /* + * XXX: Set the low watermark on the send socket, so we don't + * get called too often. + */ + + /* + * Register socket upcall, to get notified about incoming PDUs + * and free space to send outgoing ones. + */ + SOCKBUF_LOCK(&is->is_socket->so_snd); + soupcall_set(is->is_socket, SO_SND, iscsi_soupcall_send, is); + SOCKBUF_UNLOCK(&is->is_socket->so_snd); + SOCKBUF_LOCK(&is->is_socket->so_rcv); + soupcall_set(is->is_socket, SO_RCV, iscsi_soupcall_receive, is); + SOCKBUF_UNLOCK(&is->is_socket->so_rcv); + + sx_sunlock(&sc->sc_lock); + + /* + * When reconnecting, there already is SIM allocated for the session. + */ + if (is->is_sim == NULL) { + num_reqs = 128; /* XXX */ + is->is_devq = cam_simq_alloc(num_reqs); + if (is->is_devq == NULL) { + ISCSI_WARN(("%s: failed to allocate simq\n", __func__)); + 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, + num_reqs, num_reqs, is->is_devq); + if (is->is_sim == NULL) { + ISCSI_WARN(("%s: failed to allocate SIM\n", __func__)); + iscsi_session_terminate(is); + return (ENOMEM); + } + + ISCSI_SESSION_LOCK(is); + error = xpt_bus_register(is->is_sim, NULL, 0); + if (error != 0) { + ISCSI_SESSION_UNLOCK(is); + ISCSI_WARN(("%s: failed to register bus\n", __func__)); + iscsi_session_terminate(is); + return (ENOMEM); + } + ISCSI_SESSION_UNLOCK(is); + } + + is->is_desc.isd_connected = 1; + + return (0); +} + +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_target[ISCSI_NAME_LEN - 1] = '\0'; + isd->isd_target_addr[ISCSI_ADDR_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); + } + } + + refcount_init(&is->is_outstanding_pdus, 0); + TAILQ_INIT(&is->is_to_send); + TAILQ_INIT(&is->is_outstanding); + mtx_init(&is->is_lock, "iscsi_lock", NULL, MTX_DEF); + cv_init(&is->is_send_cv, "iscsi_tx"); + cv_init(&is->is_receive_cv, "iscsi_rx"); + cv_init(&is->is_cmdsn_cv, "iscsi_cmdsn"); + cv_init(&is->is_maintenance_cv, "iscsi_mt"); + + 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); + + /* + * Trigger immediate reconnection. + */ + is->is_daemon_waiting = daemon_timeout + 1; + cv_signal(&sc->sc_cv); + + error = kthread_add(iscsi_maintenance_thread, is, NULL, NULL, 0, 0, "iscsimt"); + if (error != 0) { + ISCSI_WARN(("%s: kthread_add(9) failed with error %d\n", + __func__, error)); + return (error); + } + + 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) { + if (iscsi_session_desc_matches(&is->is_desc, &isr->isr_desc)) { + found++; + iscsi_session_terminate(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)); + 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; + + 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; + + 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_WARN(("%s: failed to allocate %zd bytes\n", + __func__, 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) +{ + + 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 iscsi_pdu *request; + struct iscsi_bhs_scsi_command *bhssc; + struct ccb_scsiio *csio; + int error; + + mtx_assert(&is->is_mtx, MA_OWNED); + + csio = &ccb->csio; + + request = iscsi_pdu_new_bhs(is, M_NOWAIT); + if (request == NULL) { + ccb->ccb_h.status = CAM_REQ_ABORTED; /* XXX: Proper error code? */ + xpt_done(ccb); + return; + } + + 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) { + iscsi_pdu_free(request); + ccb->ccb_h.status = CAM_REQ_ABORTED; + xpt_done(ccb); + } + 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); + + mtx_assert(&is->is_lock, MA_OWNED); + + if (is->is_terminating) { + ccb->ccb_h.status = CAM_DEV_NOT_THERE; + xpt_done(ccb); + } + + 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 */ + /* + * XXX KDM where does this number come from? + */ + cpi->maxio = 256 * 1024; /* XXX */ + 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(("%s: faking success for reset, abort, or term_io\n", + __func__)); + 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) +{ + + /* + * XXX: What are we supposed to do here? We don't support dumping. + */ + ISCSI_WARN(("%s: called", __func__)); +} + +static int +iscsi_load(void) +{ + struct iscsi_softc *sc; + int error; + + sc = malloc(sizeof(*sc), M_ISCSI, M_ZERO | M_WAITOK); + error = make_dev_p(MAKEDEV_CHECKNAME, &sc->sc_cdev, &iscsi_cdevsw, + NULL, UID_ROOT, GID_WHEEL, 0600, "iscsi"); + if (error != 0) { + ISCSI_WARN(("%s: failed to create device node, error %d\n", + __func__, error)); + free(sc, M_ISCSI); + return (error); + } + sc->sc_cdev->si_drv1 = sc; + sx_init(&sc->sc_lock, "iscsi"); + TAILQ_INIT(&sc->sc_sessions); + cv_init(&sc->sc_cv, "iscsi_cv"); + + iscsi_pdu_zone = uma_zcreate("iscsi_pdu", + sizeof(struct iscsi_pdu), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + iscsi_outstanding_zone = uma_zcreate("iscsi_outstanding", + sizeof(struct iscsi_outstanding), NULL, NULL, NULL, NULL, + UMA_ALIGN_PTR, UMA_ZONE_NOFREE); + + return (0); +} + +static int +iscsi_unload(void) +{ + + return (EBUSY); +} + +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; + 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); diff -urNp freebsd/src/sys/dev/iscsi/initiator2/iscsi.h iscsi/sys/dev/iscsi/initiator2/iscsi.h --- freebsd/src/sys/dev/iscsi/initiator2/iscsi.h 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/sys/dev/iscsi/initiator2/iscsi.h 2013-04-20 17:20:23.000000000 +0200 @@ -0,0 +1,118 @@ +/*- + * 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_XXX_H +#define ISCSI_XXX_H + +#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_pdu { + TAILQ_ENTRY(iscsi_pdu) ip_next; + struct iscsi_session *ip_session; + 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; + uint32_t ip_expdatasn; + uint32_t ip_total_transfer_len; + uint32_t ip_r2tsn; +}; + +#define ISCSI_SESSION_STATE_INVALID 0 +#define ISCSI_SESSION_STATE_BHS 1 +#define ISCSI_SESSION_STATE_AHS 2 +#define ISCSI_SESSION_STATE_HEADER_DIGEST 3 +#define ISCSI_SESSION_STATE_DATA 4 +#define ISCSI_SESSION_STATE_DATA_DIGEST 5 + +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 iscsi_softc *is_softc; + struct iscsi_session_desc is_desc; + struct socket *is_socket; + + uint8_t is_isid[6]; + uint32_t is_statsn; + uint32_t is_expcmdsn; + uint32_t is_maxcmdsn; + int is_header_digest; + int is_data_digest; + int is_initial_r2t; + int is_immediate_data; + size_t is_max_data_segment_length; + size_t is_max_burst_length; + size_t is_first_burst_length; + + uint32_t is_cmdsn; + struct callout is_callout; + int is_ping_waiting; + int is_daemon_waiting; + volatile u_int is_outstanding_pdus; + size_t is_receive_len; + int is_receive_state; + struct iscsi_pdu *is_receive_pdu; + int is_terminating; + int is_reconnecting; + uint32_t is_initiator_task_tag; + + struct cv is_send_cv; + struct cv is_receive_cv; + struct cv is_cmdsn_cv; + struct cv is_maintenance_cv; + TAILQ_HEAD(, iscsi_pdu) is_to_send; + struct mtx is_lock; + + struct cam_devq *is_devq; + struct cam_sim *is_sim; + TAILQ_HEAD(, iscsi_outstanding) is_outstanding; +}; + +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; +}; + +#endif /* !ISCSI_XXX_H */ diff -urNp freebsd/src/sys/dev/iscsi/initiator2/iscsi_ioctl.h iscsi/sys/dev/iscsi/initiator2/iscsi_ioctl.h --- freebsd/src/sys/dev/iscsi/initiator2/iscsi_ioctl.h 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/sys/dev/iscsi/initiator2/iscsi_ioctl.h 2013-04-20 17:20:23.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 ISCSI_IOCTL_H +#define ISCSI_IOCTL_H + +#define ISCSI_PATH "/dev/iscsi" +#define ISCSI_MAX_DATA_SEGMENT_LENGTH 65535 + +#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_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) + +/* + * 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', 0x03, struct iscsi_session_add) +#define ISCSISREMOVE _IOW('I', 0x04, struct iscsi_session_remove) +#define ISCSISLIST _IOWR('I', 0x05, struct iscsi_session_list) + +#endif /* !ISCSI_IOCTL_H */ diff -urNp freebsd/src/sys/modules/ctl/Makefile iscsi/sys/modules/ctl/Makefile --- freebsd/src/sys/modules/ctl/Makefile 2013-04-20 20:14:46.000000000 +0200 +++ iscsi/sys/modules/ctl/Makefile 2013-04-20 19:05:23.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 diff -urNp freebsd/src/sys/modules/iscsi/Makefile iscsi/sys/modules/iscsi/Makefile --- freebsd/src/sys/modules/iscsi/Makefile 2013-04-20 20:14:48.000000000 +0200 +++ iscsi/sys/modules/iscsi/Makefile 2013-04-20 17:20:23.000000000 +0200 @@ -1,5 +1,5 @@ # $FreeBSD: head/sys/modules/iscsi/Makefile 171568 2007-07-24 15:35:02Z scottl $ -SUBDIR= initiator +SUBDIR= initiator initiator2 .include diff -urNp freebsd/src/sys/modules/iscsi/initiator2/Makefile iscsi/sys/modules/iscsi/initiator2/Makefile --- freebsd/src/sys/modules/iscsi/initiator2/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/sys/modules/iscsi/initiator2/Makefile 2013-04-20 17:20:23.000000000 +0200 @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../../dev/iscsi/initiator2 +KMOD= iscsi + +SRCS= iscsi.c +SRCS+= opt_cam.h +SRCS+= bus_if.h device_if.h + +.include diff -urNp freebsd/src/tools/regression/iscsi/ctl.conf iscsi/tools/regression/iscsi/ctl.conf --- freebsd/src/tools/regression/iscsi/ctl.conf 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/tools/regression/iscsi/ctl.conf 2013-04-06 12:38:47.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 freebsd/src/tools/regression/iscsi/initiator-instructions.txt iscsi/tools/regression/iscsi/initiator-instructions.txt --- freebsd/src/tools/regression/iscsi/initiator-instructions.txt 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/tools/regression/iscsi/initiator-instructions.txt 2013-04-06 12:38:47.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 freebsd/src/tools/regression/iscsi/iscsi-test.sh iscsi/tools/regression/iscsi/iscsi-test.sh --- freebsd/src/tools/regression/iscsi/iscsi-test.sh 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/tools/regression/iscsi/iscsi-test.sh 2013-04-06 12:38:47.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 freebsd/src/usr.bin/Makefile iscsi/usr.bin/Makefile --- freebsd/src/usr.bin/Makefile 2013-04-20 20:20:43.000000000 +0200 +++ iscsi/usr.bin/Makefile 2013-04-19 19:56:56.000000000 +0200 @@ -68,6 +68,7 @@ SUBDIR= alias \ id \ ipcrm \ ipcs \ + iscsictl \ join \ jot \ ${_kdump} \ diff -urNp freebsd/src/usr.bin/iscsictl/Makefile iscsi/usr.bin/iscsictl/Makefile --- freebsd/src/usr.bin/iscsictl/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.bin/iscsictl/Makefile 2013-04-19 19:56:56.000000000 +0200 @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PROG= iscsictl +SRCS= iscsictl.c parse.y token.l y.tab.h +CFLAGS+= -I${.CURDIR} +CFLAGS+= -I${.CURDIR}/../../sys/dev/iscsi/initiator2 +MAN= iscsictl.8 + +DPADD= ${LIBUTIL} +LDADD= -lutil -lfl + +YFLAGS+= -v +LFLAGS+= -i +CLEANFILES= y.tab.c y.tab.h y.output + +WARNS= 6 + +.include diff -urNp freebsd/src/usr.bin/iscsictl/iscsictl.8 iscsi/usr.bin/iscsictl/iscsictl.8 --- freebsd/src/usr.bin/iscsictl/iscsictl.8 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.bin/iscsictl/iscsictl.8 2013-04-19 19:56:56.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 D +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 freebsd/src/usr.bin/iscsictl/iscsictl.c iscsi/usr.bin/iscsictl/iscsictl.c --- freebsd/src/usr.bin/iscsictl/iscsictl.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.bin/iscsictl/iscsictl.c 2013-04-19 19:56:56.000000000 +0200 @@ -0,0 +1,567 @@ +/*- + * 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 "iscsictl.h" + +struct iscsictl_conf * +iscsictl_conf_new(void) +{ + struct iscsictl_conf *ic; + + ic = calloc(1, sizeof(*ic)); + if (ic == NULL) + err(1, "calloc"); + + TAILQ_INIT(&ic->ic_targets); + + return (ic); +} + +struct iscsictl_target * +iscsictl_target_find(struct iscsictl_conf *ic, const char *nickname) +{ + struct iscsictl_target *it; + + TAILQ_FOREACH(it, &ic->ic_targets, it_next) { + if (it->it_nickname != NULL && + strcasecmp(it->it_nickname, nickname) == 0) + return (it); + } + + return (NULL); +} + +struct iscsictl_target * +iscsictl_target_new(struct iscsictl_conf *ic) +{ + struct iscsictl_target *it; + + it = calloc(1, sizeof(*it)); + if (it == NULL) + err(1, "calloc"); + it->it_conf = ic; + TAILQ_INSERT_TAIL(&ic->ic_targets, it, it_next); + + return (it); +} + +void +iscsictl_target_delete(struct iscsictl_target *it) +{ + + TAILQ_REMOVE(&it->it_conf->ic_targets, it, it_next); + free(it); +} + +static char * +iscsictl_initiator_name(void) +{ + char *name; + size_t namelen; + int error; + + namelen = _POSIX_HOST_NAME_MAX + strlen(ISCSICTL_DEFAULT_IQN); + + name = calloc(1, namelen + 1); + if (name == NULL) + err(1, "calloc"); + strcpy(name, ISCSICTL_DEFAULT_IQN); + error = gethostname(name + strlen(ISCSICTL_DEFAULT_IQN), + namelen - strlen(ISCSICTL_DEFAULT_IQN)); + if (error != 0) + err(1, "gethostname"); + + return (name); +} + +void +iscsictl_conf_verify(struct iscsictl_conf *ic) +{ + struct iscsictl_target *it; + + TAILQ_FOREACH(it, &ic->ic_targets, it_next) { + assert(it->it_nickname != NULL); + if (it->it_session_type == ISCSICTL_SESSION_UNSPECIFIED) + it->it_session_type = ISCSICTL_SESSION_NORMAL; + if (it->it_session_type == ISCSICTL_SESSION_NORMAL && + it->it_name == NULL) + errx(1, "missing TargetName for target \"%s\"", + it->it_nickname); + if (it->it_session_type == ISCSICTL_SESSION_DISCOVERY && + it->it_name != NULL) + errx(1, "cannot specify TargetName for discovery " + "sessions for target \"%s\"", it->it_nickname); + if (it->it_address == NULL) + errx(1, "missing TargetAddress for target \"%s\"", + it->it_nickname); + if (it->it_initiator_name == NULL) + it->it_initiator_name = iscsictl_initiator_name(); + if (it->it_header_digest == ISCSICTL_DIGEST_UNSPECIFIED) + it->it_header_digest = ISCSICTL_DIGEST_NONE; + if (it->it_data_digest == ISCSICTL_DIGEST_UNSPECIFIED) + it->it_data_digest = ISCSICTL_DIGEST_NONE; + if (it->it_auth_method == ISCSICTL_AUTH_METHOD_UNSPECIFIED) { + if (it->it_user != NULL || it->it_secret != NULL || + it->it_mutual_user != NULL || + it->it_mutual_secret != NULL) + it->it_auth_method = + ISCSICTL_AUTH_METHOD_CHAP; + else + it->it_auth_method = + ISCSICTL_AUTH_METHOD_NONE; + } + if (it->it_auth_method == ISCSICTL_AUTH_METHOD_CHAP) { + if (it->it_user == NULL) { + errx(1, "missing chapIName for target \"%s\"", + it->it_nickname); + } + if (it->it_secret == NULL) + errx(1, "missing chapSecret for target \"%s\"", + it->it_nickname); + if (it->it_mutual_user != NULL || + it->it_mutual_secret != NULL) { + if (it->it_mutual_user == NULL) + errx(1, "missing tgtChapName for " + "target \"%s\"", it->it_nickname); + if (it->it_mutual_secret == NULL) + errx(1, "missing tgtChapSecret for " + "target \"%s\"", it->it_nickname); + } + } + } +} + +static void +iscsictl_desc_from_target(struct iscsi_session_desc *desc, + const struct iscsictl_target *it) +{ + memset(desc, 0, sizeof(*desc)); + + /* + * XXX: Check bounds and return error instead of silently truncating. + */ + if (it->it_initiator_name != NULL) + strlcpy(desc->isd_initiator, it->it_initiator_name, + sizeof(desc->isd_initiator)); + else + strlcpy(desc->isd_initiator, iscsictl_initiator_name(), + sizeof(desc->isd_initiator)); + if (it->it_initiator_address != NULL) + strlcpy(desc->isd_initiator_addr, it->it_initiator_address, + sizeof(desc->isd_initiator_addr)); + if (it->it_initiator_alias != NULL) + strlcpy(desc->isd_initiator_alias, it->it_initiator_alias, + sizeof(desc->isd_initiator_alias)); + if (it->it_name != NULL) + strlcpy(desc->isd_target, it->it_name, + sizeof(desc->isd_target)); + if (it->it_address != NULL) + strlcpy(desc->isd_target_addr, it->it_address, + sizeof(desc->isd_target_addr)); + if (it->it_user != NULL) + strlcpy(desc->isd_user, it->it_user, + sizeof(desc->isd_user)); + if (it->it_secret != NULL) + strlcpy(desc->isd_secret, it->it_secret, + sizeof(desc->isd_secret)); + if (it->it_mutual_user != NULL) + strlcpy(desc->isd_mutual_user, it->it_mutual_user, + sizeof(desc->isd_mutual_user)); + if (it->it_mutual_secret != NULL) + strlcpy(desc->isd_mutual_secret, it->it_mutual_secret, + sizeof(desc->isd_mutual_secret)); + if (it->it_session_type == ISCSICTL_SESSION_DISCOVERY) + desc->isd_discovery = 1; +} + +static int +iscsictl_kernel_add(int iscsi_fd, const struct iscsictl_target *it) +{ + struct iscsi_session_add isa; + int error; + + iscsictl_desc_from_target(&isa.isa_desc, it); + error = ioctl(iscsi_fd, ISCSISADD, &isa); + if (error != 0) + warn("ISCSISADD"); + return (error); +} + +static int +iscsictl_kernel_remove(int iscsi_fd, const struct iscsictl_target *it) +{ + struct iscsi_session_remove isr; + int error; + + iscsictl_desc_from_target(&isr.isr_desc, it); + error = ioctl(iscsi_fd, ISCSISREMOVE, &isr); + if (error != 0) + warn("ISCSISREMOVE"); + return (error); +} + +/* + * XXX: Add filtering. + */ +static int +iscsictl_kernel_list(int iscsi_fd, const struct iscsictl_target *it __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("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("Sesion type: %s\n", + descs[i].isd_discovery ? "Discovery" : "Normal"); + printf("Sesion state: %s\n", + descs[i].isd_connected ? + "Connected" : "Disconnected"); + printf("\n"); + } + } else { + printf("%-36s %-16s %-16s\n", + "Target", "Target addr", "State"); + 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\n", + descs[i].isd_target, descs[i].isd_target_addr, + state); + } + } + + 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 = ISCSICTL_DEFAULT_CONFIG; + char *nickname = NULL, *discovery_host = NULL, *host = NULL, + *target = NULL, *user = NULL, *secret = NULL; + int ch, error, iscsi_fd; + int failed = 0; + struct iscsictl_conf *conf; + struct iscsictl_target *it; + + 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 = iscsictl_conf_new_from_file(conf_path); + + TAILQ_FOREACH(it, &conf->ic_targets, it_next) + failed += iscsictl_kernel_add(iscsi_fd, it); + } else if (nickname != NULL) { + conf = iscsictl_conf_new_from_file(conf_path); + it = iscsictl_target_find(conf, nickname); + if (it == NULL) + errx(1, "target %s not found in the configuration file", + nickname); + + if (Aflag != 0) + failed += iscsictl_kernel_add(iscsi_fd, it); + else if (Rflag != 0) + failed += iscsictl_kernel_remove(iscsi_fd, it); + else + failed += iscsictl_kernel_list(iscsi_fd, it, vflag); + } else { + conf = iscsictl_conf_new(); + it = iscsictl_target_new(conf); + it->it_name = target; + if (discovery_host != NULL) { + it->it_session_type = ISCSICTL_SESSION_DISCOVERY; + it->it_address = discovery_host; + } else { + it->it_session_type = ISCSICTL_SESSION_NORMAL; + it->it_address = host; + } + it->it_user = user; + it->it_secret = secret; + + if (Aflag != 0) + failed += iscsictl_kernel_add(iscsi_fd, it); + else if (Rflag != 0) + failed += iscsictl_kernel_remove(iscsi_fd, it); + else + failed += iscsictl_kernel_list(iscsi_fd, it, vflag); + } + + error = close(iscsi_fd); + if (error != 0) + err(1, "close"); + + if (failed > 0) + return (1); + return (0); +} diff -urNp freebsd/src/usr.bin/iscsictl/iscsictl.h iscsi/usr.bin/iscsictl/iscsictl.h --- freebsd/src/usr.bin/iscsictl/iscsictl.h 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.bin/iscsictl/iscsictl.h 2013-04-19 19:56:56.000000000 +0200 @@ -0,0 +1,113 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE 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 ISCSICTL_DEFAULT_CONFIG "/etc/iscsi.conf" +#define ISCSICTL_DEFAULT_IQN "iqn.1994-09.org.freebsd:" + +#define ISCSICTL_MAX_NAME_LEN 223 +#define ISCSICTL_MAX_DATA_SEGMENT_LENGTH 65536 + +#define ISCSICTL_AUTH_METHOD_UNSPECIFIED 0 +#define ISCSICTL_AUTH_METHOD_NONE 1 +#define ISCSICTL_AUTH_METHOD_CHAP 2 + +#define ISCSICTL_DIGEST_UNSPECIFIED 0 +#define ISCSICTL_DIGEST_NONE 1 +#define ISCSICTL_DIGEST_CRC32C 2 + +#define ISCSICTL_SESSION_UNSPECIFIED 0 +#define ISCSICTL_SESSION_NORMAL 1 +#define ISCSICTL_SESSION_DISCOVERY 2 + +struct iscsictl_target { + TAILQ_ENTRY(iscsictl_target) it_next; + struct iscsictl_conf *it_conf; + char *it_nickname; + char *it_name; + char *it_address; + char *it_initiator_name; + char *it_initiator_address; + char *it_initiator_alias; + int it_header_digest; + int it_data_digest; + int it_auth_method; + int it_session_type; + char *it_user; + char *it_secret; + char *it_mutual_user; + char *it_mutual_secret; +}; + +struct iscsictl_conf { + TAILQ_HEAD(, iscsictl_target) ic_targets; +}; + +#define CC_SESSION_TYPE_NONE 0 +#define CC_SESSION_TYPE_DISCOVERY 1 +#define CC_SESSION_TYPE_NORMAL 2 + +struct iscsictl_connection { + struct iscsictl_target *ic_target; + int ic_socket; + int ic_session_type; + uint32_t ic_cmdsn; + uint32_t ic_statsn; + size_t ic_max_data_segment_length; + size_t ic_max_burst_length; + size_t ic_max_outstanding_r2t; + int ic_header_digest; + int ic_data_digest; +}; + +struct iscsictl_conf *iscsictl_conf_new(void); +struct iscsictl_conf *iscsictl_conf_new_from_file(const char *path); +void iscsictl_conf_delete(struct iscsictl_conf *cc); +void iscsictl_conf_verify(struct iscsictl_conf *cc); + +struct iscsictl_target *iscsictl_target_new(struct iscsictl_conf *cc); +struct iscsictl_target *iscsictl_target_find(struct iscsictl_conf *ic, + const char *nickname); +void iscsictl_target_delete(struct iscsictl_target *ic); + +char *checked_strdup(const char *); +bool iscsictl_valid_iscsi_name(const char *name); + +void yyerror(const char *); +int yylex(void); + +#endif /* !ISCSICTL_H */ diff -urNp freebsd/src/usr.bin/iscsictl/parse.y iscsi/usr.bin/iscsictl/parse.y --- freebsd/src/usr.bin/iscsictl/parse.y 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.bin/iscsictl/parse.y 2013-04-19 19:56:57.000000000 +0200 @@ -0,0 +1,315 @@ +%{ +/*- + * 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 iscsictl_conf *conf; +static struct iscsictl_target *target; + +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 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 (iscsictl_target_find(conf, $1) != NULL) + errx(1, "duplicated target %s", $1); + target->it_nickname = $1; + target = iscsictl_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 + | + ignored_statement + ; + +target_name_statement: TARGET_NAME EQUALS STR + { + if (target->it_name != NULL) + errx(1, "duplicated TargetName at line %d", lineno + 1); + target->it_name = $3; + } + ; + +target_address_statement: TARGET_ADDRESS EQUALS STR + { + if (target->it_address != NULL) + errx(1, "duplicated TargetAddress at line %d", lineno + 1); + target->it_address = $3; + } + ; + +initiator_name_statement: INITIATOR_NAME EQUALS STR + { + if (target->it_initiator_name != NULL) + errx(1, "duplicated InitiatorName at line %d", lineno + 1); + target->it_initiator_name = $3; + } + ; + +initiator_address_statement: INITIATOR_ADDRESS EQUALS STR + { + if (target->it_initiator_address != NULL) + errx(1, "duplicated InitiatorAddress at line %d", lineno + 1); + target->it_initiator_address = $3; + } + ; + +initiator_alias_statement: INITIATOR_ALIAS EQUALS STR + { + if (target->it_initiator_alias != NULL) + errx(1, "duplicated InitiatorAlias at line %d", lineno + 1); + target->it_initiator_alias = $3; + } + ; + +user_statement: USER EQUALS STR + { + if (target->it_user != NULL) + errx(1, "duplicated chapIName at line %d", lineno + 1); + target->it_user = $3; + } + ; + +secret_statement: SECRET EQUALS STR + { + if (target->it_secret != NULL) + errx(1, "duplicated chapSecret at line %d", lineno + 1); + target->it_secret = $3; + } + ; + +mutual_user_statement: MUTUAL_USER EQUALS STR + { + if (target->it_mutual_user != NULL) + errx(1, "duplicated tgtChapName at line %d", lineno + 1); + target->it_mutual_user = $3; + } + ; + +mutual_secret_statement:MUTUAL_SECRET EQUALS STR + { + if (target->it_mutual_secret != NULL) + errx(1, "duplicated tgtChapSecret at line %d", lineno + 1); + target->it_mutual_secret = $3; + } + ; + +auth_method_statement: AUTH_METHOD EQUALS STR + { + if (target->it_auth_method != ISCSICTL_AUTH_METHOD_UNSPECIFIED) + errx(1, "duplicated AuthMethod at line %d", lineno + 1); + if (strcasecmp($3, "none") == 0) + target->it_auth_method = ISCSICTL_AUTH_METHOD_NONE; + else if (strcasecmp($3, "chap") == 0) + target->it_auth_method = ISCSICTL_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->it_header_digest != ISCSICTL_DIGEST_UNSPECIFIED) + errx(1, "duplicated HeaderDigest at line %d", lineno + 1); + if (strcasecmp($3, "none") == 0) + target->it_header_digest = ISCSICTL_DIGEST_NONE; + else if (strcasecmp($3, "CRC32C") == 0) + target->it_header_digest = ISCSICTL_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->it_data_digest != ISCSICTL_DIGEST_UNSPECIFIED) + errx(1, "duplicated DataDigest at line %d", lineno + 1); + if (strcasecmp($3, "none") == 0) + target->it_data_digest = ISCSICTL_DIGEST_NONE; + else if (strcasecmp($3, "CRC32C") == 0) + target->it_data_digest = ISCSICTL_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->it_session_type != ISCSICTL_SESSION_UNSPECIFIED) + errx(1, "duplicated SessionType at line %d", lineno + 1); + if (strcasecmp($3, "normal") == 0) + target->it_session_type = ISCSICTL_SESSION_NORMAL; + else if (strcasecmp($3, "discovery") == 0) + target->it_session_type = ISCSICTL_SESSION_DISCOVERY; + else + errx(1, "invalid SessionType at line %d; " + "must be either \"normal\" or \"discovery\"", 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 +iscsictl_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 iscsictl_conf * +iscsictl_conf_new_from_file(const char *path) +{ + int error; + + conf = iscsictl_conf_new(); + target = iscsictl_target_new(conf); + + yyin = fopen(path, "r"); + if (yyin == NULL) + err(1, "unable to open configuration file %s", path); + iscsictl_check_perms(path); + lineno = 0; + yyrestart(yyin); + error = yyparse(); + assert(error == 0); + fclose(yyin); + + assert(target->it_nickname == NULL); + iscsictl_target_delete(target); + + iscsictl_conf_verify(conf); + + return (conf); +} diff -urNp freebsd/src/usr.bin/iscsictl/token.l iscsi/usr.bin/iscsictl/token.l --- freebsd/src/usr.bin/iscsictl/token.l 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.bin/iscsictl/token.l 2013-04-19 19:56:57.000000000 +0200 @@ -0,0 +1,90 @@ +%{ +/*- + * 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 depth; +int lineno; + +%} + +%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; } +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; } +\{ { depth++; return OPENING_BRACKET; } +\} { depth--; return CLOSING_BRACKET; } += { return EQUALS; } +#.*$ /* ignore comments */; +\n { lineno++; } +[ \t]+ /* ignore whitespace */; +%% diff -urNp freebsd/src/usr.sbin/Makefile iscsi/usr.sbin/Makefile --- freebsd/src/usr.sbin/Makefile 2013-04-20 20:21:34.000000000 +0200 +++ iscsi/usr.sbin/Makefile 2013-04-19 19:56:57.000000000 +0200 @@ -16,6 +16,7 @@ SUBDIR= adduser \ crashinfo \ cron \ ctladm \ + ctld \ daemon \ dconschat \ devinfo \ @@ -34,6 +35,7 @@ SUBDIR= adduser \ ifmcstat \ inetd \ iostat \ + iscsid \ isfctl \ kldxref \ mailwrapper \ diff -urNp freebsd/src/usr.sbin/ctladm/ctladm.8 iscsi/usr.sbin/ctladm/ctladm.8 --- freebsd/src/usr.sbin/ctladm/ctladm.8 2013-04-20 20:21:50.000000000 +0200 +++ iscsi/usr.sbin/ctladm/ctladm.8 2013-04-20 19:05:45.000000000 +0200 @@ -197,6 +197,15 @@ .Nm .Ic dumpstructs .Nm +.Ic islist +.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 +892,39 @@ If you specify .Fl x , the entire LUN database is displayed in XML format. .El +.It Ic islist +Get a list of currently running iSCSI connections. +This includes initiator and target names and the unique connection IDs. +.Bl -tag -width 11n +.It Fl x +Dump the raw XML. +The connections list information from the kernel comes in XML format, and this +option allows the display of the raw XML data. +.El +.It Ic islogout +Ask the initiator to log out iSCSI connections matching criteria. +.Bl -tag -width 11n +.It Fl a +Log out all connections. +.It Fl 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 +1019,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 freebsd/src/usr.sbin/ctladm/ctladm.c iscsi/usr.sbin/ctladm/ctladm.c --- freebsd/src/usr.sbin/ctladm/ctladm.c 2013-04-20 20:21:51.000000000 +0200 +++ iscsi/usr.sbin/ctladm/ctladm.c 2013-04-20 19:05:46.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, "x"}, + {"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; @@ -577,6 +586,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} }; @@ -3398,6 +3408,362 @@ bailout: return (retval); } +struct cctl_islist_conn { + int connection_id; + char *initiator; + char *initiator_addr; + char *target; + STAILQ_ENTRY(cctl_islist_conn) links; +}; + +struct cctl_islist_data { + int num_conns; + STAILQ_HEAD(,cctl_islist_conn) conn_list; + struct cctl_islist_conn *cur_conn; + int level; + struct sbuf *cur_sb[32]; +}; + +static void +cctl_islist_start_element(void *user_data, const char *name, const char **attr) +{ + int i; + struct cctl_islist_data *islist; + struct cctl_islist_conn *cur_conn; + + islist = (struct cctl_islist_data *)user_data; + cur_conn = islist->cur_conn; + islist->level++; + if ((u_int)islist->level > (sizeof(islist->cur_sb) / + sizeof(islist->cur_sb[0]))) + errx(1, "%s: too many nesting levels, %zd max", __func__, + sizeof(islist->cur_sb) / sizeof(islist->cur_sb[0])); + + islist->cur_sb[islist->level] = sbuf_new_auto(); + if (islist->cur_sb[islist->level] == NULL) + err(1, "%s: Unable to allocate sbuf", __func__); + + if (strcmp(name, "connection") == 0) { + if (cur_conn != NULL) + errx(1, "%s: improper connection element nesting", + __func__); + + cur_conn = calloc(1, sizeof(*cur_conn)); + if (cur_conn == NULL) + err(1, "%s: cannot allocate %zd bytes", __func__, + sizeof(*cur_conn)); + + islist->num_conns++; + islist->cur_conn = cur_conn; + + STAILQ_INSERT_TAIL(&islist->conn_list, cur_conn, links); + + for (i = 0; attr[i] != NULL; i += 2) { + if (strcmp(attr[i], "id") == 0) { + cur_conn->connection_id = + strtoull(attr[i+1], NULL, 0); + } else { + errx(1, + "%s: invalid connection attribute %s = %s", + __func__, attr[i], attr[i+1]); + } + } + } +} + +static void +cctl_islist_end_element(void *user_data, const char *name) +{ + struct cctl_islist_data *islist; + struct cctl_islist_conn *cur_conn; + char *str; + + islist = (struct cctl_islist_data *)user_data; + cur_conn = islist->cur_conn; + + if ((cur_conn == NULL) + && (strcmp(name, "ctlislist") != 0)) + errx(1, "%s: cur_conn == NULL! (name = %s)", __func__, name); + + if (islist->cur_sb[islist->level] == NULL) + errx(1, "%s: no valid sbuf at level %d (name %s)", __func__, + islist->level, name); + + sbuf_finish(islist->cur_sb[islist->level]); + str = strdup(sbuf_data(islist->cur_sb[islist->level])); + if (str == NULL) + err(1, "%s can't allocate %zd bytes for string", __func__, + sbuf_len(islist->cur_sb[islist->level])); + + if (strlen(str) == 0) { + free(str); + str = NULL; + } + + sbuf_delete(islist->cur_sb[islist->level]); + islist->cur_sb[islist->level] = NULL; + islist->level--; + + if (strcmp(name, "initiator") == 0) { + cur_conn->initiator = str; + str = NULL; + } else if (strcmp(name, "initiator_addr") == 0) { + cur_conn->initiator_addr = str; + str = NULL; + } else if (strcmp(name, "target") == 0) { + cur_conn->target = str; + str = NULL; + } else if (strcmp(name, "connection") == 0) { + islist->cur_conn = NULL; + } else if (strcmp(name, "ctlislist") == 0) { + } else + errx(1, "unknown element %s", name); + + free(str); +} + +static void +cctl_islist_char_handler(void *user_data, const XML_Char *str, int len) +{ + struct cctl_islist_data *islist; + + islist = (struct cctl_islist_data *)user_data; + + sbuf_bcat(islist->cur_sb[islist->level], str, len); +} + +static int +cctl_islist(int fd, int argc, char **argv, char *combinedopt) +{ + struct ctl_iscsi req; + struct cctl_islist_data islist; + struct cctl_islist_conn *conn; + XML_Parser parser; + char *conn_str; + int conn_len; + int dump_xml = 0; + int retval, c; + + retval = 0; + conn_len = 4096; + + bzero(&islist, sizeof(islist)); + STAILQ_INIT(&islist.conn_list); + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'x': + dump_xml = 1; + break; + default: + break; + } + } + +retry: + conn_str = malloc(conn_len); + + bzero(&req, sizeof(req)); + req.type = CTL_ISCSI_LIST; + req.data.list.alloc_len = conn_len; + req.data.list.conn_xml = conn_str; + + if (ioctl(fd, CTL_ISCSI, &req) == -1) { + warn("%s: error issuing CTL_ISCSI ioctl", __func__); + retval = 1; + goto bailout; + } + + if (req.status == CTL_ISCSI_ERROR) { + warnx("%s: error returned from CTL_ISCSI ioctl:\n%s", + __func__, req.error_str); + } else if (req.status == CTL_ISCSI_LIST_NEED_MORE_SPACE) { + conn_len = conn_len << 1; + goto retry; + } + + if (dump_xml != 0) { + printf("%s", conn_str); + goto bailout; + } + + parser = XML_ParserCreate(NULL); + if (parser == NULL) { + warn("%s: Unable to create XML parser", __func__); + retval = 1; + goto bailout; + } + + XML_SetUserData(parser, &islist); + XML_SetElementHandler(parser, cctl_islist_start_element, + cctl_islist_end_element); + XML_SetCharacterDataHandler(parser, cctl_islist_char_handler); + + retval = XML_Parse(parser, conn_str, strlen(conn_str), 1); + XML_ParserFree(parser); + if (retval != 1) { + retval = 1; + goto bailout; + } + + printf("%4s %-16s %-36s %-36s\n", "ID", "Address", "Initiator", + "Target"); + STAILQ_FOREACH(conn, &islist.conn_list, links) { + printf("%4u %-16s %-36s %-36s\n", + conn->connection_id, conn->initiator_addr, conn->initiator, + conn->target); + } +bailout: + free(conn_str); + + return (retval); +} + +static int +cctl_islogout(int fd, int argc, char **argv, char *combinedopt) +{ + struct ctl_iscsi req; + int retval = 0, c; + int all = 0, connection_id = -1, nargs = 0; + char *initiator_name = NULL, *initiator_addr = NULL; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 'a': + all = 1; + nargs++; + break; + case '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. @@ -3711,6 +4077,9 @@ usage(int error) " [-s len fmt [args]] [-c] [-d delete_id]\n" " ctladm port <-l | -o | [-w wwnn][-W wwpn]>\n" " [-p targ_port] [-t port_type] [-q] [-x]\n" +" ctladm islist [-x]\n" +" ctladm islogout <-A | -a addr | -c connection-id | -n name>\n" +" ctladm isterminate <-A | -a addr | -c connection-id | -n name>\n" " ctladm dumpooa\n" " ctladm dumpstructs\n" " ctladm help\n" @@ -4091,6 +4460,15 @@ main(int argc, char **argv) case CTLADM_CMD_MODIFY: retval = cctl_modify_lun(fd, argc, argv, combinedopt); break; + case CTLADM_CMD_ISLIST: + retval = cctl_islist(fd, argc, argv, combinedopt); + break; + case CTLADM_CMD_ISLOGOUT: + retval = cctl_islogout(fd, argc, argv, combinedopt); + break; + case CTLADM_CMD_ISTERMINATE: + retval = cctl_isterminate(fd, argc, argv, combinedopt); + break; case CTLADM_CMD_HELP: default: usage(retval); diff -urNp freebsd/src/usr.sbin/ctld/Makefile iscsi/usr.sbin/ctld/Makefile --- freebsd/src/usr.sbin/ctld/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/Makefile 2013-01-21 22:31:57.000000000 +0100 @@ -0,0 +1,18 @@ +# $FreeBSD$ + +PROG= ctld +SRCS= ctld.c discovery.c kernel.c keys.c log.c login.c parse.y pdu.c token.l y.tab.h +CFLAGS+= -I${.CURDIR} +CFLAGS+= -I${.CURDIR}/../../sys +CFLAGS+= -I${.CURDIR}/../../sys/cam/ctl +MAN= ctld.8 ctl.conf.5 + +DPADD= ${LIBCAM} ${LIBSBUF} ${LIBBSDXML} ${LIBUTIL} +LDADD= -lcam -lsbuf -lbsdxml -lutil -lfl -lssl + +YFLAGS+= -v +CLEANFILES= y.tab.c y.tab.h y.output + +WARNS= 6 + +.include diff -urNp freebsd/src/usr.sbin/ctld/ctl.conf.5 iscsi/usr.sbin/ctld/ctl.conf.5 --- freebsd/src/usr.sbin/ctld/ctl.conf.5 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/ctl.conf.5 2013-04-19 18:33:53.000000000 +0200 @@ -0,0 +1,247 @@ +.\" Copyright (c) 2012 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Edward Tomasz Napierala under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 20, 2012 +.Dt CTL.CONF 5 +.Os +.Sh NAME +.Nm ctl.conf +.Nd CAM Target Layer / iSCSI target daemon configuration file +.Sh DESCRIPTION +The +.Nm +file is used by +.Xr ctld 8 +daemon. +Lines starting with +.Ql # +and empty lines are interpreted as comments. +.Sh BASIC SYNTAX +General syntax of the +.Nm +file is the following: +.Bd -literal -offset indent +pidfile + +auth-group { + chap + ... +} + +portal-group { + listen
+ discovery-auth-group + ... +} + +target { + auth-group + portal-group + lun { + path + } + ... +} +.Ed +.Pp +.Sh DESCRIPTION +The following statements are available: +.Pp +At the global level: +.Bl -tag -width ".Ic 1234" +.It Ic auth-group Aq name +.Pp +Opens an auth-group section, defining an authentication group, +which can then be assigned to any number of targets. +.It Ic debug Aq level +.Pp +Specifies debug level. +The default is 0. +.It Ic pidfile Aq path +.Pp +Specifies path to pidfile. +The default is /var/run/ctld.pid. +.It Ic portal-group Aq name +.Pp +Opens a portal-group section, defining a portal group, +which can then be assigned to any number of targets. +.It Ic target Aq name +.Pp +Opens a target configuration section. +.El +.Pp +At the auth-group level: +.Bl -tag -width ".Ic 1234" +.It Ic chap Aq user secret +.Pp +Specifies CHAP authentication credentials. +.It Ic chap-mutual Aq user secret mutualuser mutualsecret +.Pp +Specifies mutual CHAP authentication credentials. +Note that for any auth-group, configuration may contain either chap, +or chap-mutual entries; it's an error to mix them. +.El +.Pp +At the portal-group level: +.Bl -tag -width ".Ic 1234" +.It Ic discovery-auth-group Aq name +.Pp +Assigns previously defined authentication group to that portal group, +to be used for target discovery. +By default, the discovery will be denied. +A special auth-group, "no-authentication", may be used to allow for discovery +without authentication. +.It Ic listen Aq address +.Pp +Specifies IPv4 or IPv6 address and port to listen on for incoming connections. +.El +.Pp +At the target level: +.Bl -tag -width ".Ic 1234" +.It Ic alias Aq text +.Pp +Assigns human-readable description to that target. There is no default. +.It Ic auth-group Aq name +.Pp +Assigns previously defined authentication group to that target. +There is no default; every target must use either auth-group, +or chap, or chap-mutual statements. +A special auth-group, "no-authentication", may be used to permit access +without authentication. +.It Ic chap Aq user secret +.Pp +Specifies CHAP authentication credentials. +Note that targets must use either auth-group, or chap, +or chap-mutual clauses; it's a configuration error to mix them in one target. +.It Ic chap-mutual Aq user secret mutualuser mutualsecret +.Pp +Specifies mutual CHAP authentication credentials. +Note that targets must use either auth-group, chap, +chap-mutual clauses; it's a configuration error to mix them in one target. +.It Ic portal-group Aq name +.Pp +Assigns previously defined portal group to that target. +Default portal group is "default", which makes the target available +on TCP port 3260 on all configured IPv4 and IPv6 addresses. +.It Ic lun Aq number +.Pp +Opens a lun configuration section, defining LUN exported by a target. +.El +.Pp +At the lun level: +.Bl -tag -width ".Ic 1234" +.It Ic backend Aq block|ramdisk +.Pp +Specifies the CTL backend to use for a given LUN. +Valid choices are "block" and "ramdisk"; block is used for LUNs backed +by files in the filesystem; ramdisk is a bitsink device, used mostly for +testing. +The default backend is block. +.It Ic blocksize Aq size +.Pp +Specifies blocksize visible to the initiator. +The default blocksize is 512. +.It Ic device-id Aq string +.Pp +Specifies SCSI Device Identification string presented to the initiator. +.It Ic option Aq name value +.Pp +Specifies CTL-specific options passed to the kernel. +.It Ic path Aq path +.Pp +Specifies path to file used to back the LUN. +.It Ic serial Aq string +.Pp +Specifies SCSI serial number presented to the initiator. +.It Ic size Aq size +.Pp +Specifies LUN size, in bytes. +.El +.Sh EXAMPLES +.Bd -literal -offset indent +pidfile /var/run/ctld.pid + +auth-group example2 { + chap-mutual "user" "secret" "mutualuser" "mutualsecret" + chap-mutual "user2" "secret2" "mutualuser" "mutualsecret" +} + +portal-group example2 { + discovery-auth-group no-authentication + listen 127.0.0.1 + listen 0.0.0.0:3261 + listen [::]:3261 + listen [fe80::be:ef] +} + +target iqn.2012-06.com.example:target0 { + alias "Testing target" + auth-group no-authentication + lun 0 { + path /dev/zvol/example_0 + blocksize 4096 + size 4G + } +} + +target iqn.2012-06.com.example:target3 { + chap chapuser chapsecret + lun 0 { + path /dev/zvol/example_3 + } +} + +target iqn.2012-06.com.example:target2 { + auth-group example2 + portal-group example2 + lun 0 { + path /dev/zvol/example2_0 + } + lun 1 { + path /dev/zvol/example2_1 + option foo bar + } +} +.Ed +.Sh FILES +.Bl -tag -width ".Pa /etc/ctl.conf" -compact +.It Pa /etc/ctl.conf +The default location of the +.Xr ctld 8 +configuration file. +.El +.Sh SEE ALSO +.Xr ctladm 8 , +.Xr ctld 8 +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff -urNp freebsd/src/usr.sbin/ctld/ctld.8 iscsi/usr.sbin/ctld/ctld.8 --- freebsd/src/usr.sbin/ctld/ctld.8 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/ctld.8 2013-04-19 18:33:53.000000000 +0200 @@ -0,0 +1,113 @@ +.\" Copyright (c) 2012 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Edward Tomasz Napierala under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 20, 2012 +.Dt CTLD 8 +.Os +.Sh NAME +.Nm ctld +.Nd CAM Target Layer / iSCSI target daemon +.Sh SYNOPSIS +.Nm +.Op Fl d +.Op Fl f Ar config-file +.Sh DESCRIPTION +The +.Nm +daemon is responsible for managing the CAM Target Layer configuration, +accepting incoming iSCSI connections, performing authentication and +passing connections to the kernel part of the native iSCSI target. +.Pp +.Pp +Upon startup, the +.Nm +daemon parses the configuration file and exits, if it encounters any errors. +Then it compares the configuration with the kernel list of LUNs managed +by previously running +.Nm +instances, removes LUNs no longer existing in the configuration file, +and creates new LUNs as neccessary. +After that it listens for the incoming iSCSI connections, performs +authentication, and, if successful, passes the connections to the kernel part +of CTL iSCSI target, which handles it from that point. +.Pp +When it receives a SIGHUP signal, the +.Nm +reloads its configuration and applies the changes to the kernel. +Changes are applied in a way that avoids unneccessary disruptions; +for example removing one LUN does not affect other LUNs. +.Pp +When exiting gracefully, the +.Nm +daemon removes LUNs it managed and forcibly 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.conf 5 , +.Xr ctladm 8 +.Sh AUTHORS +The +.Nm +was developed by +.An Edward Tomasz Napierala Aq trasz@FreeBSD.org +under sponsorship from the FreeBSD Foundation. diff -urNp freebsd/src/usr.sbin/ctld/ctld.c iscsi/usr.sbin/ctld/ctld.c --- freebsd/src/usr.sbin/ctld/ctld.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/ctld.c 2013-04-19 18:33:53.000000000 +0200 @@ -0,0 +1,1528 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ctld.h" + +volatile bool sighup_received = false; +volatile bool sigterm_received = false; + +static void +usage(void) +{ + + fprintf(stderr, "usage: ctld [-d][-f config-file]\n"); + exit(1); +} + +char * +checked_strdup(const char *s) +{ + char *c; + + c = strdup(s); + if (c == NULL) + log_err(1, "strdup"); + return (c); +} + +struct ctld_conf * +ctld_conf_new(void) +{ + struct ctld_conf *cc; + + cc = calloc(1, sizeof(*cc)); + if (cc == NULL) + log_err(1, "calloc"); + TAILQ_INIT(&cc->cc_targets); + TAILQ_INIT(&cc->cc_auth_groups); + TAILQ_INIT(&cc->cc_portal_groups); + + return (cc); +} + +void +ctld_conf_delete(struct ctld_conf *cc) +{ + struct ctld_target *ct, *tmp; + struct ctld_auth_group *cag, *cagtmp; + struct ctld_portal_group *cpg, *cpgtmp; + + assert(cc->cc_pidfh == NULL); + + TAILQ_FOREACH_SAFE(ct, &cc->cc_targets, ct_next, tmp) + ctld_target_delete(ct); + TAILQ_FOREACH_SAFE(cag, &cc->cc_auth_groups, cag_next, cagtmp) + ctld_auth_group_delete(cag); + TAILQ_FOREACH_SAFE(cpg, &cc->cc_portal_groups, cpg_next, cpgtmp) + ctld_portal_group_delete(cpg); + free(cc->cc_pidfile_path); + free(cc); +} + +static struct ctld_auth * +ctld_auth_new(struct ctld_auth_group *cag) +{ + struct ctld_auth *ca; + + ca = calloc(1, sizeof(*ca)); + if (ca == NULL) + log_err(1, "calloc"); + ca->ca_auth_group = cag; + TAILQ_INSERT_TAIL(&cag->cag_auths, ca, ca_next); + return (ca); +} + +static void +ctld_auth_delete(struct ctld_auth *ca) +{ + TAILQ_REMOVE(&ca->ca_auth_group->cag_auths, ca, ca_next); + + free(ca->ca_user); + free(ca->ca_secret); + free(ca->ca_mutual_user); + free(ca->ca_mutual_secret); + free(ca); +} + +const struct ctld_auth * +ctld_auth_find(struct ctld_auth_group *cag, const char *user) +{ + const struct ctld_auth *ca; + + TAILQ_FOREACH(ca, &cag->cag_auths, ca_next) { + if (strcmp(ca->ca_user, user) == 0) + return (ca); + } + + return (NULL); +} + +struct ctld_auth_group * +ctld_auth_group_new(struct ctld_conf *cc, const char *name) +{ + struct ctld_auth_group *cag; + + if (name != NULL) { + cag = ctld_auth_group_find(cc, name); + if (cag != NULL) { + log_warnx("duplicated auth-group \"%s\"", name); + return (NULL); + } + } + + cag = calloc(1, sizeof(*cag)); + if (cag == NULL) + log_err(1, "calloc"); + if (name != NULL) + cag->cag_name = checked_strdup(name); + TAILQ_INIT(&cag->cag_auths); + cag->cag_conf = cc; + TAILQ_INSERT_TAIL(&cc->cc_auth_groups, cag, cag_next); + + return (cag); +} + +void +ctld_auth_group_delete(struct ctld_auth_group *cag) +{ + struct ctld_auth *ca, *tmp; + + TAILQ_REMOVE(&cag->cag_conf->cc_auth_groups, cag, cag_next); + + TAILQ_FOREACH_SAFE(ca, &cag->cag_auths, ca_next, tmp) + ctld_auth_delete(ca); + free(cag->cag_name); + free(cag); +} + +struct ctld_auth_group * +ctld_auth_group_find(struct ctld_conf *cc, const char *name) +{ + struct ctld_auth_group *cag; + + TAILQ_FOREACH(cag, &cc->cc_auth_groups, cag_next) { + if (cag->cag_name != NULL && strcmp(cag->cag_name, name) == 0) + return (cag); + } + + return (NULL); +} + +static void +ctld_auth_check_secret_length(struct ctld_auth *ca) +{ + size_t len; + + len = strlen(ca->ca_secret); + if (len > 16) { + if (ca->ca_auth_group->cag_name != NULL) + log_warnx("secret for user \"%s\", auth-group \"%s\", " + "is too long; it should be at most 16 characters " + "long", ca->ca_user, ca->ca_auth_group->cag_name); + else + log_warnx("secret for user \"%s\", target \"%s\", " + "is too long; it should be at most 16 characters " + "long", ca->ca_user, + ca->ca_auth_group->cag_target->ct_iqn); + } + if (len < 12) { + if (ca->ca_auth_group->cag_name != NULL) + log_warnx("secret for user \"%s\", auth-group \"%s\", " + "is too short; it should be at least 12 characters " + "long", ca->ca_user, + ca->ca_auth_group->cag_name); + else + log_warnx("secret for user \"%s\", target \"%s\", " + "is too short; it should be at least 16 characters " + "long", ca->ca_user, + ca->ca_auth_group->cag_target->ct_iqn); + } + + if (ca->ca_mutual_secret != NULL) { + len = strlen(ca->ca_secret); + if (len > 16) { + if (ca->ca_auth_group->cag_name != NULL) + log_warnx("mutual secret for user \"%s\", " + "auth-group \"%s\", is too long; it should " + "be at most 16 characters long", + ca->ca_user, ca->ca_auth_group->cag_name); + else + log_warnx("mutual secret for user \"%s\", " + "target \"%s\", is too long; it should " + "be at most 16 characters long", + ca->ca_user, + ca->ca_auth_group->cag_target->ct_iqn); + } + if (len < 12) { + if (ca->ca_auth_group->cag_name != NULL) + log_warnx("mutual secret for user \"%s\", " + "auth-group \"%s\", is too short; it " + "should be at least 12 characters long", + ca->ca_user, ca->ca_auth_group->cag_name); + else + log_warnx("mutual secret for user \"%s\", " + "target \"%s\", is too short; it should be " + "at least 16 characters long", + ca->ca_user, + ca->ca_auth_group->cag_target->ct_iqn); + } + } +} + +const struct ctld_auth * +ctld_auth_new_chap(struct ctld_auth_group *cag, const char *user, + const char *secret) +{ + struct ctld_auth *ca; + + if (cag->cag_type == CAG_TYPE_UNKNOWN) + cag->cag_type = CAG_TYPE_CHAP; + if (cag->cag_type != CAG_TYPE_CHAP) { + if (cag->cag_name != NULL) + log_warnx("cannot mix \"chap\" authentication with " + "other types for auth-group \"%s\"", cag->cag_name); + else + log_warnx("cannot mix \"chap\" authentication with " + "other types for target \"%s\"", + cag->cag_target->ct_iqn); + return (NULL); + } + + ca = ctld_auth_new(cag); + ca->ca_user = checked_strdup(user); + ca->ca_secret = checked_strdup(secret); + + ctld_auth_check_secret_length(ca); + + return (ca); +} + +const struct ctld_auth * +ctld_auth_new_chap_mutual(struct ctld_auth_group *cag, const char *user, + const char *secret, const char *user2, const char *secret2) +{ + struct ctld_auth *ca; + + if (cag->cag_type == CAG_TYPE_UNKNOWN) + cag->cag_type = CAG_TYPE_CHAP_MUTUAL; + if (cag->cag_type != CAG_TYPE_CHAP_MUTUAL) { + if (cag->cag_name != NULL) + log_warnx("cannot mix \"chap-mutual\" authentication " + "with other types for auth-group \"%s\"", + cag->cag_name); + else + log_warnx("cannot mix \"chap-mutual\" authentication " + "with other types for target \"%s\"", + cag->cag_target->ct_iqn); + return (NULL); + } + + ca = ctld_auth_new(cag); + ca->ca_user = checked_strdup(user); + ca->ca_secret = checked_strdup(secret); + ca->ca_mutual_user = checked_strdup(user2); + ca->ca_mutual_secret = checked_strdup(secret2); + + ctld_auth_check_secret_length(ca); + + return (ca); +} + +static struct ctld_portal * +ctld_portal_new(struct ctld_portal_group *cpg) +{ + struct ctld_portal *cp; + + cp = calloc(1, sizeof(*cp)); + if (cp == NULL) + log_err(1, "calloc"); + TAILQ_INIT(&cp->cp_targets); + cp->cp_portal_group = cpg; + TAILQ_INSERT_TAIL(&cpg->cpg_portals, cp, cp_next); + return (cp); +} + +static void +ctld_portal_delete(struct ctld_portal *cp) +{ + TAILQ_REMOVE(&cp->cp_portal_group->cpg_portals, cp, cp_next); + freeaddrinfo(cp->cp_ai); + free(cp->cp_listen); + free(cp); +} + +struct ctld_portal_group * +ctld_portal_group_new(struct ctld_conf *cc, const char *name) +{ + struct ctld_portal_group *cpg; + + if (name != NULL) { + cpg = ctld_portal_group_find(cc, name); + if (cpg != NULL) { + log_warnx("duplicated portal-group \"%s\"", name); + return (NULL); + } + } + + cpg = calloc(1, sizeof(*cpg)); + if (cpg == NULL) + log_err(1, "calloc"); + cpg->cpg_name = checked_strdup(name); + TAILQ_INIT(&cpg->cpg_portals); + cpg->cpg_conf = cc; + cc->cc_last_portal_group_tag++; + cpg->cpg_tag = cc->cc_last_portal_group_tag; + TAILQ_INSERT_TAIL(&cc->cc_portal_groups, cpg, cpg_next); + + return (cpg); +} + +void +ctld_portal_group_delete(struct ctld_portal_group *cpg) +{ + struct ctld_portal *cp, *tmp; + + TAILQ_REMOVE(&cpg->cpg_conf->cc_portal_groups, cpg, cpg_next); + + TAILQ_FOREACH_SAFE(cp, &cpg->cpg_portals, cp_next, tmp) + ctld_portal_delete(cp); + free(cpg->cpg_name); + free(cpg); +} + +struct ctld_portal_group * +ctld_portal_group_find(struct ctld_conf *cc, const char *name) +{ + struct ctld_portal_group *cpg; + + TAILQ_FOREACH(cpg, &cc->cc_portal_groups, cpg_next) { + if (strcmp(cpg->cpg_name, name) == 0) + return (cpg); + } + + return (NULL); +} + +int +ctld_portal_group_add_listen(struct ctld_portal_group *cpg, const char *value) +{ + struct addrinfo hints; + struct ctld_portal *cp; + char *addr, *ch, *arg; + const char *port; + int error, colons = 0; + + cp = ctld_portal_new(cpg); + cp->cp_listen = checked_strdup(value); + + arg = cp->cp_listen; + if (arg[0] == '\0') { + log_warnx("empty listen address"); + free(cp->cp_listen); + free(cp); + return (1); + } + if (arg[0] == '[') { + /* + * IPv6 address in square brackets, perhaps with port. + */ + arg++; + addr = strsep(&arg, "]"); + if (arg == NULL) { + log_warnx("invalid listen address %s", cp->cp_listen); + free(cp->cp_listen); + free(cp); + return (1); + } + if (arg[0] == '\0') { + port = "3260"; + } else if (arg[0] == ':') { + port = arg + 1; + } else { + log_warnx("invalid listen address %s", cp->cp_listen); + free(cp->cp_listen); + free(cp); + return (1); + } + } else { + /* + * Either IPv6 address without brackets - and without + * a port - or IPv4 address. Just count the colons. + */ + for (ch = arg; *ch != '\0'; ch++) { + if (*ch == ':') + colons++; + } + if (colons > 1) { + addr = arg; + port = "3260"; + } else { + addr = strsep(&arg, ":"); + if (arg == NULL) + port = "3260"; + else + port = arg; + } + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + error = getaddrinfo(addr, port, &hints, &cp->cp_ai); + if (error != 0) { + log_warnx("getaddrinfo for %s failed: %s", + cp->cp_listen, gai_strerror(error)); + free(cp->cp_listen); + free(cp); + return (1); + } + + /* + * XXX: getaddrinfo(3) may return multiple addresses; we should turn + * those into multiple portals. + */ + + return (0); +} + +static bool +ctld_valid_hex(const char ch) +{ + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'A': + case 'b': + case 'B': + case 'c': + case 'C': + case 'd': + case 'D': + case 'e': + case 'E': + case 'f': + case 'F': + return (true); + default: + return (false); + } +} + +bool +ctld_valid_iscsi_name(const char *name) +{ + int i; + + if (strlen(name) >= CTLD_MAX_NAME_LEN) { + log_warnx("overlong name for target \"%s\"; max length allowed " + "by iSCSI specification is %d characters", + name, CTLD_MAX_NAME_LEN); + return (false); + } + + /* + * In the cases below, we don't return an error, just in case the admin + * was right, and we're wrong. + */ + if (strncasecmp(name, "iqn.", strlen("iqn.")) == 0) { + for (i = strlen("iqn."); name[i] != '\0'; i++) { + /* + * XXX: We should verify UTF-8 normalisation, as defined + * by 3.2.6.2: iSCSI Name Encoding. + */ + if (isalnum(name[i])) + continue; + if (name[i] == '-' || name[i] == '.' || name[i] == ':') + continue; + log_warnx("invalid character \"%c\" in target name " + "\"%s\"; allowed characters are letters, digits, " + "'-', '.', and ':'", name[i], name); + break; + } + /* + * XXX: Check more stuff: valid date and a valid reversed domain. + */ + } else if (strncasecmp(name, "eui.", strlen("eui.")) == 0) { + if (strlen(name) != strlen("eui.") + 16) + log_warnx("invalid target name \"%s\"; the \"eui.\" " + "should be followed by exactly 16 hexadecimal " + "digits", name); + for (i = strlen("eui."); name[i] != '\0'; i++) { + if (!ctld_valid_hex(name[i])) { + log_warnx("invalid character \"%c\" in target " + "name \"%s\"; allowed characters are 1-9 " + "and A-F", name[i], name); + break; + } + } + } else if (strncasecmp(name, "naa.", strlen("naa.")) == 0) { + if (strlen(name) > strlen("naa.") + 32) + log_warnx("invalid target name \"%s\"; the \"naa.\" " + "should be followed by at most 32 hexadecimal " + "digits", name); + for (i = strlen("naa."); name[i] != '\0'; i++) { + if (!ctld_valid_hex(name[i])) { + log_warnx("invalid character \"%c\" in target " + "name \"%s\"; allowed characters are 1-9 " + "and A-F", name[i], name); + break; + } + } + } else { + log_warnx("invalid target name \"%s\"; should start with " + "either \".iqn\", \"eui.\", or \"naa.\"", + name); + } + return (true); +} + +struct ctld_target * +ctld_target_new(struct ctld_conf *cc, const char *iqn) +{ + struct ctld_target *ct; + int i, len; + + ct = ctld_target_find(cc, iqn); + if (ct != NULL) { + log_warnx("duplicated target \"%s\"", iqn); + return (NULL); + } + if (ctld_valid_iscsi_name(iqn) == false) { + log_warnx("target name \"%s\" is invalid", iqn); + return (NULL); + } + ct = calloc(1, sizeof(*ct)); + if (ct == NULL) + log_err(1, "calloc"); + ct->ct_iqn = checked_strdup(iqn); + + /* + * RFC 3722 requires us to normalize the name to lowercase. + */ + len = strlen(iqn); + for (i = 0; i < len; i++) + ct->ct_iqn[i] = tolower(ct->ct_iqn[i]); + + TAILQ_INIT(&ct->ct_luns); + ct->ct_conf = cc; + TAILQ_INSERT_TAIL(&cc->cc_targets, ct, ct_next); + + return (ct); +} + +void +ctld_target_delete(struct ctld_target *ct) +{ + struct ctld_lun *cl, *tmp; + + TAILQ_REMOVE(&ct->ct_conf->cc_targets, ct, ct_next); + + TAILQ_FOREACH_SAFE(cl, &ct->ct_luns, cl_next, tmp) + ctld_lun_delete(cl); + free(ct->ct_iqn); + free(ct); +} + +struct ctld_target * +ctld_target_find(struct ctld_conf *cc, const char *iqn) +{ + struct ctld_target *ct; + + TAILQ_FOREACH(ct, &cc->cc_targets, ct_next) { + if (strcasecmp(ct->ct_iqn, iqn) == 0) + return (ct); + } + + return (NULL); +} + +struct ctld_lun * +ctld_lun_new(struct ctld_target *ct, int lun) +{ + struct ctld_lun *cl; + + cl = ctld_lun_find(ct, lun); + if (cl != NULL) { + log_warnx("duplicated lun %d for target \"%s\"", + lun, ct->ct_iqn); + return (NULL); + } + + cl = calloc(1, sizeof(*cl)); + if (cl == NULL) + log_err(1, "calloc"); + cl->cl_lun = lun; + TAILQ_INIT(&cl->cl_options); + cl->cl_target = ct; + TAILQ_INSERT_TAIL(&ct->ct_luns, cl, cl_next); + + return (cl); +} + +void +ctld_lun_delete(struct ctld_lun *cl) +{ + struct ctld_lun_option *clo, *tmp; + + TAILQ_REMOVE(&cl->cl_target->ct_luns, cl, cl_next); + + TAILQ_FOREACH_SAFE(clo, &cl->cl_options, clo_next, tmp) + ctld_lun_option_delete(clo); + free(cl->cl_backend); + free(cl->cl_device_id); + free(cl->cl_path); + free(cl->cl_serial); + free(cl); +} + +struct ctld_lun * +ctld_lun_find(struct ctld_target *ct, int lun) +{ + struct ctld_lun *cl; + + TAILQ_FOREACH(cl, &ct->ct_luns, cl_next) { + if (cl->cl_lun == lun) + return (cl); + } + + return (NULL); +} + +void +ctld_lun_set_backend(struct ctld_lun *cl, const char *value) +{ + free(cl->cl_backend); + cl->cl_backend = checked_strdup(value); +} + +void +ctld_lun_set_blocksize(struct ctld_lun *cl, size_t value) +{ + + cl->cl_blocksize = value; +} + +void +ctld_lun_set_device_id(struct ctld_lun *cl, const char *value) +{ + free(cl->cl_device_id); + cl->cl_device_id = checked_strdup(value); +} + +void +ctld_lun_set_path(struct ctld_lun *cl, const char *value) +{ + free(cl->cl_path); + cl->cl_path = checked_strdup(value); +} + +void +ctld_lun_set_serial(struct ctld_lun *cl, const char *value) +{ + free(cl->cl_serial); + cl->cl_serial = checked_strdup(value); +} + +void +ctld_lun_set_size(struct ctld_lun *cl, size_t value) +{ + + cl->cl_size = value; +} + +void +ctld_lun_set_ctl_lun(struct ctld_lun *cl, uint32_t value) +{ + + cl->cl_ctl_lun = value; +} + +struct ctld_lun_option * +ctld_lun_option_new(struct ctld_lun *cl, const char *name, const char *value) +{ + struct ctld_lun_option *clo; + + clo = ctld_lun_option_find(cl, name); + if (clo != NULL) { + log_warnx("duplicated lun option %s for lun %d, target \"%s\"", + name, cl->cl_lun, cl->cl_target->ct_iqn); + return (NULL); + } + + clo = calloc(1, sizeof(*clo)); + if (clo == NULL) + log_err(1, "calloc"); + clo->clo_name = checked_strdup(name); + clo->clo_value = checked_strdup(value); + clo->clo_lun = cl; + TAILQ_INSERT_TAIL(&cl->cl_options, clo, clo_next); + + return (clo); +} + +void +ctld_lun_option_delete(struct ctld_lun_option *clo) +{ + + TAILQ_REMOVE(&clo->clo_lun->cl_options, clo, clo_next); + + free(clo->clo_name); + free(clo->clo_value); + free(clo); +} + +struct ctld_lun_option * +ctld_lun_option_find(struct ctld_lun *cl, const char *name) +{ + struct ctld_lun_option *clo; + + TAILQ_FOREACH(clo, &cl->cl_options, clo_next) { + if (strcmp(clo->clo_name, name) == 0) + return (clo); + } + + return (NULL); +} + +void +ctld_lun_option_set(struct ctld_lun_option *clo, const char *value) +{ + + free(clo->clo_value); + clo->clo_value = checked_strdup(value); +} + +static struct ctld_connection * +ctld_connection_new(struct ctld_portal *cp, int fd, const char *host) +{ + struct ctld_connection *cc; + + cc = calloc(1, sizeof(*cc)); + if (cc == NULL) + log_err(1, "calloc"); + cc->cc_portal = cp; + cc->cc_socket = fd; + cc->cc_initiator_addr = checked_strdup(host); + cc->cc_max_data_segment_length = 8192; + + return (cc); +} + +#if 0 +static void +ctld_conf_print(struct ctld_conf *cc) +{ + struct ctld_auth_group *cag; + struct ctld_auth *ca; + struct ctld_portal_group *cpg; + struct ctld_portal *cp; + struct ctld_target *ct; + struct ctld_lun *cl; + struct ctld_lun_option *clo; + + TAILQ_FOREACH(cag, &cc->cc_auth_groups, cag_next) { + fprintf(stderr, "auth-group %s {\n", cag->cag_name); + TAILQ_FOREACH(ca, &cag->cag_auths, ca_next) + fprintf(stderr, "\t chap-mutual %s %s %s %s\n", ca->ca_user, ca->ca_secret, ca->ca_mutual_user, ca->ca_mutual_secret); + fprintf(stderr, "}\n"); + } + TAILQ_FOREACH(cpg, &cc->cc_portal_groups, cpg_next) { + fprintf(stderr, "portal-group %s {\n", cpg->cpg_name); + TAILQ_FOREACH(cp, &cpg->cpg_portals, cp_next) + fprintf(stderr, "\t listen %s\n", cp->cp_listen); + fprintf(stderr, "}\n"); + } + TAILQ_FOREACH(ct, &cc->cc_targets, ct_next) { + fprintf(stderr, "target %s {\n", ct->ct_iqn); + if (ct->ct_alias != NULL) + fprintf(stderr, "\t alias %s\n", ct->ct_alias); + TAILQ_FOREACH(cl, &ct->ct_luns, cl_next) { + fprintf(stderr, "\tlun %d {\n", cl->cl_lun); + fprintf(stderr, "\t\tpath %s\n", cl->cl_path); + TAILQ_FOREACH(clo, &cl->cl_options, clo_next) + fprintf(stderr, "\t\toption %s %s\n", clo->clo_name, clo->clo_value); + fprintf(stderr, "\t}\n"); + } + fprintf(stderr, "}\n"); + } +} +#endif + +int +ctld_conf_verify(struct ctld_conf *cc) +{ + struct ctld_auth_group *cag; + struct ctld_portal_group *cpg; + struct ctld_target *ct; + struct ctld_lun *cl, *cl2; + bool found_lun0; + + if (cc->cc_pidfile_path == NULL) + cc->cc_pidfile_path = checked_strdup(CTLD_DEFAULT_PIDFILE); + + TAILQ_FOREACH(ct, &cc->cc_targets, ct_next) { + if (ct->ct_auth_group == NULL) { + log_warnx("missing authentication for target \"%s\"; " + "must specify either \"auth-group\", \"chap\", " + "or \"chap-mutual\"", ct->ct_iqn); + return (1); + } + if (ct->ct_portal_group == NULL) { + ct->ct_portal_group = ctld_portal_group_find(cc, + "default"); + assert(ct->ct_portal_group != NULL); + } + found_lun0 = false; + TAILQ_FOREACH(cl, &ct->ct_luns, cl_next) { + if (cl->cl_lun == 0) + found_lun0 = true; + if (cl->cl_backend == NULL) + ctld_lun_set_backend(cl, "block"); + if (strcmp(cl->cl_backend, "block") == 0 && + cl->cl_path == NULL) { + log_warnx("missing path for lun %d, " + "target \"%s\"", cl->cl_lun, ct->ct_iqn); + return (1); + } + if (strcmp(cl->cl_backend, "ramdisk") == 0) { + if (cl->cl_size == 0) { + log_warnx("missing size for " + "ramdisk-backed lun %d, " + "target \"%s\"", + cl->cl_lun, ct->ct_iqn); + return (1); + } + if (cl->cl_path != NULL) { + log_warnx("path must not be specified " + "for ramdisk-backed lun %d, " + "target \"%s\"", + cl->cl_lun, ct->ct_iqn); + return (1); + } + } + if (cl->cl_lun < 0 || cl->cl_lun > 255) { + log_warnx("invalid lun number for lun %d, " + "target \"%s\"; must be between 0 and 255", + cl->cl_lun, ct->ct_iqn); + return (1); + } +#if 1 /* Should we? */ + TAILQ_FOREACH(cl2, &ct->ct_luns, cl_next) { + if (cl == cl2) + continue; + if (cl->cl_path != NULL && + cl2->cl_path != NULL && + strcmp(cl->cl_path, cl2->cl_path) == 0) + log_debugx("WARNING: duplicate path " + "for lun %d, target \"%s\"", + cl->cl_lun, ct->ct_iqn); + } +#endif + if (cl->cl_blocksize == 0) { + ctld_lun_set_blocksize(cl, + CTLD_DEFAULT_BLOCKSIZE); + } else if (cl->cl_blocksize <= 0) { + log_warnx("invalid blocksize for lun %d, " + "target \"%s\"; must be larger than 0", + cl->cl_lun, ct->ct_iqn); + return (1); + } + if (cl->cl_size != 0 && + cl->cl_size % cl->cl_blocksize != 0) { + log_warnx("invalid size for lun %d, target " + "\"%s\"; must be multitude of blocksize", + cl->cl_lun, ct->ct_iqn); + return (1); + } + } + if (!found_lun0) { + log_warnx("mandatory LUN 0 not configured " + "for target \"%s\"", ct->ct_iqn); + return (1); + } + } + TAILQ_FOREACH(cpg, &cc->cc_portal_groups, cpg_next) { + assert(cpg->cpg_name != NULL); + if (cpg->cpg_discovery_auth_group == NULL) { + cpg->cpg_discovery_auth_group = + ctld_auth_group_find(cc, "no-access"); + assert(cpg->cpg_discovery_auth_group != NULL); + } + + TAILQ_FOREACH(ct, &cc->cc_targets, ct_next) { + if (ct->ct_portal_group == cpg) + break; + } + if (ct == NULL) { + if (strcmp(cpg->cpg_name, "default") != 0) + log_warnx("portal-group \"%s\" not assigned " + "to any target", cpg->cpg_name); + cpg->cpg_unassigned = true; + } else + cpg->cpg_unassigned = false; + } + TAILQ_FOREACH(cag, &cc->cc_auth_groups, cag_next) { + if (cag->cag_name == NULL) + assert(cag->cag_target != NULL); + else + assert(cag->cag_target == NULL); + + TAILQ_FOREACH(ct, &cc->cc_targets, ct_next) { + if (ct->ct_auth_group == cag) + break; + } + if (ct == NULL && cag->cag_name != NULL && + strcmp(cag->cag_name, "no-authentication") != 0 && + strcmp(cag->cag_name, "no-access") != 0) { + log_warnx("auth-group \"%s\" not assigned " + "to any target", cag->cag_name); + } + } + + return (0); +} + +static int +ctld_conf_apply(struct ctld_conf *oldconf, struct ctld_conf *newconf) +{ + struct ctld_target *oldct, *newct, *tmpct; + struct ctld_lun *oldcl, *newcl, *tmpcl; + struct ctld_portal_group *oldcpg, *newcpg; + struct ctld_portal *oldcp, *newcp; + pid_t otherpid; + int changed, cumulated_error = 0, one = 1, error; + + if (oldconf->cc_debug != newconf->cc_debug) { + log_debugx("changing debug level to %d", newconf->cc_debug); + log_init(newconf->cc_debug); + } + + if (oldconf->cc_pidfh != NULL) { + assert(oldconf->cc_pidfile_path != NULL); + if (newconf->cc_pidfile_path != NULL && + strcmp(oldconf->cc_pidfile_path, + newconf->cc_pidfile_path) == 0) { + newconf->cc_pidfh = oldconf->cc_pidfh; + oldconf->cc_pidfh = NULL; + } else { + log_debugx("removing pidfile %s", + oldconf->cc_pidfile_path); + pidfile_remove(oldconf->cc_pidfh); + oldconf->cc_pidfh = NULL; + } + } + + if (newconf->cc_pidfh == NULL && newconf->cc_pidfile_path != NULL) { + log_debugx("opening pidfile %s", newconf->cc_pidfile_path); + newconf->cc_pidfh = + pidfile_open(newconf->cc_pidfile_path, 0600, &otherpid); + if (newconf->cc_pidfh == NULL) { + if (errno == EEXIST) + log_errx(1, "daemon already running, pid: %jd.", + (intmax_t)otherpid); + log_err(1, "cannot open or create pidfile \"%s\"", + newconf->cc_pidfile_path); + } + } + + TAILQ_FOREACH_SAFE(oldct, &oldconf->cc_targets, ct_next, tmpct) { + /* + * First, remove any targets present in the old configuration + * and missing in the new one. + */ + newct = ctld_target_find(newconf, oldct->ct_iqn); + if (newct == NULL) { + TAILQ_FOREACH_SAFE(oldcl, &oldct->ct_luns, cl_next, + tmpcl) { + log_debugx("target %s not found in the " + "configuration file; removing its lun %d, " + "backed by CTL lun %d", + oldct->ct_iqn, oldcl->cl_lun, + oldcl->cl_ctl_lun); + error = ctld_kernel_lun_remove(oldcl); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + cumulated_error++; + } + ctld_lun_delete(oldcl); + } + ctld_target_delete(oldct); + continue; + } + + /* + * Second, remove any LUNs present in the old target + * and missing in the new one. + */ + TAILQ_FOREACH_SAFE(oldcl, &oldct->ct_luns, cl_next, tmpcl) { + newcl = ctld_lun_find(newct, oldcl->cl_lun); + if (newcl == NULL) { + log_debugx("lun %d, target %s, CTL lun %d " + "not found in the configuration file; " + "removing", oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + error = ctld_kernel_lun_remove(oldcl); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + cumulated_error++; + } + ctld_lun_delete(oldcl); + continue; + } + + /* + * Also remove the LUNs changed by more than size. + */ + changed = 0; + assert(oldcl->cl_backend != NULL); + assert(newcl->cl_backend != NULL); + if (strcmp(newcl->cl_backend, oldcl->cl_backend) != 0) { + log_debugx("backend for lun %d, target %s, " + "CTL lun %d changed; removing", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + changed = 1; + } + if (oldcl->cl_blocksize != newcl->cl_blocksize) { + log_debugx("blocksize for lun %d, target %s, " + "CTL lun %d changed; removing", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + changed = 1; + } + if (newcl->cl_device_id != NULL && + (oldcl->cl_device_id == NULL || + strcmp(oldcl->cl_device_id, newcl->cl_device_id) != + 0)) { + log_debugx("device-id for lun %d, target %s, " + "CTL lun %d changed; removing", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + changed = 1; + } + if (newcl->cl_path != NULL && + (oldcl->cl_path == NULL || + strcmp(oldcl->cl_path, newcl->cl_path) != 0)) { + log_debugx("path for lun %d, target %s, " + "CTL lun %d, changed; removing", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + changed = 1; + } + if (newcl->cl_serial != NULL && + (oldcl->cl_serial == NULL || + strcmp(oldcl->cl_serial, newcl->cl_serial) != 0)) { + log_debugx("serial for lun %d, target %s, " + "CTL lun %d changed; removing", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + changed = 1; + } + if (changed) { + error = ctld_kernel_lun_remove(oldcl); + if (error != 0) { + log_warnx("failed to remove lun %d, " + "target %s, CTL lun %d", + oldcl->cl_lun, oldct->ct_iqn, + oldcl->cl_ctl_lun); + cumulated_error++; + } + ctld_lun_delete(oldcl); + continue; + } + + ctld_lun_set_ctl_lun(newcl, oldcl->cl_ctl_lun); + } + } + + /* + * Now add new targets or modify existing ones. + */ + TAILQ_FOREACH(newct, &newconf->cc_targets, ct_next) { + oldct = ctld_target_find(oldconf, newct->ct_iqn); + + TAILQ_FOREACH(newcl, &newct->ct_luns, cl_next) { + if (oldct != NULL) { + oldcl = ctld_lun_find(oldct, newcl->cl_lun); + if (oldcl != NULL) { + if (newcl->cl_size != oldcl->cl_size) { + log_debugx("resizing lun %d, " + "target %s, CTL lun %d", + newcl->cl_lun, + newct->ct_iqn, + newcl->cl_ctl_lun); + error = + ctld_kernel_lun_resize(newcl); + if (error != 0) { + log_warnx("failed to " + "resize lun %d, " + "target %s, " + "CTL lun %d", + newcl->cl_lun, + newct->ct_iqn, + newcl->cl_lun); + cumulated_error++; + } + } + continue; + } + } + log_debugx("adding lun %d, target %s", + newcl->cl_lun, newct->ct_iqn); + error = ctld_kernel_lun_add(newcl); + if (error != 0) { + log_warnx("failed to add lun %d, target %s", + newcl->cl_lun, newct->ct_iqn); + cumulated_error++; + } + } + } + + /* + * Go through the new portals, opening the sockets as neccessary. + */ + TAILQ_FOREACH(newcpg, &newconf->cc_portal_groups, cpg_next) { + if (newcpg->cpg_unassigned) { + log_debugx("not listening on portal-group \"%s\", " + "not assigned to any target", + newcpg->cpg_name); + continue; + } + TAILQ_FOREACH(newcp, &newcpg->cpg_portals, cp_next) { + /* + * Try to find already open portal and reuse + * the listening socket. We don't care about + * what portal or portal group that was, what + * matters is the listening address. + */ + TAILQ_FOREACH(oldcpg, &oldconf->cc_portal_groups, + cpg_next) { + TAILQ_FOREACH(oldcp, &oldcpg->cpg_portals, + cp_next) { + if (strcmp(newcp->cp_listen, + oldcp->cp_listen) == 0 && + oldcp->cp_socket > 0) { + newcp->cp_socket = + oldcp->cp_socket; + oldcp->cp_socket = 0; + break; + } + } + } + if (newcp->cp_socket > 0) { + /* + * We're done with this portal. + */ + continue; + } + + log_debugx("listening on %s, portal-group \"%s\"", + newcp->cp_listen, newcpg->cpg_name); + + newcp->cp_socket = socket(newcp->cp_ai->ai_family, + newcp->cp_ai->ai_socktype, + newcp->cp_ai->ai_protocol); + if (newcp->cp_socket < 0) { + log_warn("socket(2) failed for %s", + newcp->cp_listen); + cumulated_error++; + continue; + } + error = setsockopt(newcp->cp_socket, SOL_SOCKET, + SO_REUSEADDR, &one, sizeof(one)); + if (error != 0) { + log_warn("setsockopt(SO_REUSEADDR) failed " + "for %s", newcp->cp_listen); + close(newcp->cp_socket); + newcp->cp_socket = 0; + cumulated_error++; + continue; + } + error = bind(newcp->cp_socket, newcp->cp_ai->ai_addr, + newcp->cp_ai->ai_addrlen); + if (error != 0) { + log_warn("bind(2) failed for %s", + newcp->cp_listen); + close(newcp->cp_socket); + newcp->cp_socket = 0; + cumulated_error++; + continue; + } + error = listen(newcp->cp_socket, -1); + if (error != 0) { + log_warn("listen(2) failed for %s", + newcp->cp_listen); + close(newcp->cp_socket); + newcp->cp_socket = 0; + cumulated_error++; + continue; + } + } + } + + /* + * Go through the no longer used sockets, closing them. + */ + TAILQ_FOREACH(oldcpg, &oldconf->cc_portal_groups, cpg_next) { + TAILQ_FOREACH(oldcp, &oldcpg->cpg_portals, cp_next) { + if (oldcp->cp_socket <= 0) + continue; + log_debugx("closing socket for %s, portal-group \"%s\"", + oldcp->cp_listen, oldcpg->cpg_name); + close(oldcp->cp_socket); + oldcp->cp_socket = 0; + } + } + + return (cumulated_error); +} + +static void +ctld_new_connection(struct ctld_portal *cp, int fd, bool dont_fork) +{ + struct ctld_connection *cc; + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + pid_t pid; + int error; + char host[NI_MAXHOST + 1]; + + if (dont_fork) { + log_debugx("incoming connection; not forking due to -d flag"); + } else { + pid = fork(); + if (pid < 0) + log_err(1, "fork"); + if (pid > 0) { + close(fd); + return; + } + } + pidfile_close(cp->cp_portal_group->cpg_conf->cc_pidfh); + + error = getpeername(fd, (struct sockaddr *)&ss, &sslen); + if (error != 0) + log_err(1, "getpeername"); + error = getnameinfo((struct sockaddr *)&ss, sslen, + host, sizeof(host), NULL, 0, NI_NUMERICHOST); + if (error != 0) + log_errx(1, "getaddrinfo: %s", gai_strerror(error)); + + log_debugx("accepted connection from %s; portal group \"%s\"", + host, cp->cp_portal_group->cpg_name); + cc = ctld_connection_new(cp, fd, host); + ctld_login(cc); + if (cc->cc_session_type == CC_SESSION_TYPE_NORMAL) { + ctld_kernel_handoff(cc); + log_debugx("connection handed off to the kernel"); + } else { + assert(cc->cc_session_type == CC_SESSION_TYPE_DISCOVERY); + ctld_discovery(cc); + } + log_debugx("nothing more to do; exiting"); + exit(0); +} + +static int +fd_add(int fd, fd_set *fdset, int nfds) +{ + + /* + * Skip sockets which we failed to bind. + */ + if (fd <= 0) + return (nfds); + + FD_SET(fd, fdset); + if (fd > nfds) + nfds = fd; + return (nfds); +} + +static void +ctld_main_loop(struct ctld_conf *cc, bool dont_fork) +{ + struct ctld_portal_group *cpg; + struct ctld_portal *cp; + fd_set fdset; + int error, nfds, client_fd; + + pidfile_write(cc->cc_pidfh); + + for (;;) { + if (sighup_received || sigterm_received) + return; + + FD_ZERO(&fdset); + nfds = 0; + TAILQ_FOREACH(cpg, &cc->cc_portal_groups, cpg_next) { + TAILQ_FOREACH(cp, &cpg->cpg_portals, cp_next) + nfds = fd_add(cp->cp_socket, &fdset, nfds); + } + error = select(nfds + 1, &fdset, NULL, NULL, NULL); + if (error <= 0) { + if (errno == EINTR) + return; + log_err(1, "select"); + } + TAILQ_FOREACH(cpg, &cc->cc_portal_groups, cpg_next) { + TAILQ_FOREACH(cp, &cpg->cpg_portals, cp_next) { + if (!FD_ISSET(cp->cp_socket, &fdset)) + continue; + client_fd = accept(cp->cp_socket, NULL, 0); + if (client_fd < 0) + log_err(1, "accept"); + ctld_new_connection(cp, client_fd, dont_fork); + break; + } + } + } +} + +static void +ctld_sighup_handler(int dummy __unused) +{ + + sighup_received = true; +} + +static void +ctld_sigterm_handler(int dummy __unused) +{ + + sigterm_received = true; +} + +static void +ctld_register_signals(void) +{ + struct sigaction sa; + int error; + + bzero(&sa, sizeof(sa)); + sa.sa_handler = ctld_sighup_handler; + sigfillset(&sa.sa_mask); + error = sigaction(SIGHUP, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); + + sa.sa_handler = ctld_sigterm_handler; + error = sigaction(SIGTERM, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); + + sa.sa_handler = ctld_sigterm_handler; + error = sigaction(SIGINT, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); + + sa.sa_handler = SIG_IGN; + error = sigaction(SIGCHLD, &sa, NULL); + if (error != 0) + log_err(1, "sigaction"); +} + +int +main(int argc, char **argv) +{ + struct ctld_conf *oldconf, *newconf, *tmpconf; + const char *config_path = CTLD_DEFAULT_CONFIG; + int debug = 0, ch, error; + bool dont_daemonize = false; + + while ((ch = getopt(argc, argv, "df:")) != -1) { + switch (ch) { + case 'd': + dont_daemonize = true; + debug++; + break; + case 'f': + config_path = optarg; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + if (argc != 0) + usage(); + + log_init(debug); + ctld_kernel_init(); + + oldconf = ctld_conf_new_from_kernel(); + newconf = ctld_conf_new_from_file(config_path); + if (newconf == NULL) + log_errx(1, "configuration error, exiting"); + if (debug > 0) { + oldconf->cc_debug = debug; + newconf->cc_debug = debug; + } + + if (dont_daemonize == false) { + if (daemon(0, 0) == -1) { + log_warn("cannot daemonize"); + pidfile_remove(newconf->cc_pidfh); + exit(1); + } + } + + error = ctld_conf_apply(oldconf, newconf); + if (error != 0) + log_errx(1, "failed to apply configuration, exiting"); + ctld_conf_delete(oldconf); + oldconf = NULL; + + ctld_register_signals(); + + log_debugx("enabling CTL iSCSI port"); + error = ctld_kernel_port_on(); + if (error != 0) + log_errx(1, "failed to enable CTL iSCSI port, exiting"); + + for (;;) { + ctld_main_loop(newconf, dont_daemonize); + if (sighup_received) { + sighup_received = false; + log_debugx("received SIGHUP, reloading configuration"); + tmpconf = ctld_conf_new_from_file(config_path); + if (tmpconf == NULL) { + log_warnx("configuration error, " + "continuing with old configuration"); + } else { + if (debug > 0) + tmpconf->cc_debug = debug; + oldconf = newconf; + newconf = tmpconf; + error = ctld_conf_apply(oldconf, newconf); + if (error != 0) + log_warnx("failed to reload " + "configuration"); + ctld_conf_delete(oldconf); + oldconf = NULL; + } + } else { + log_debugx("exiting on signal; " + "reloading empty configuration"); + + log_debugx("disabling CTL iSCSI port " + "and terminating all connections"); + error = ctld_kernel_port_off(); + if (error != 0) + log_warnx("failed to disable CTL iSCSI port"); + + oldconf = newconf; + newconf = ctld_conf_new(); + if (debug > 0) + newconf->cc_debug = debug; + error = ctld_conf_apply(oldconf, newconf); + if (error != 0) + log_warnx("failed to apply configuration"); + + log_warnx("exiting on signal"); + exit(0); + } + } + /* NOTREACHED */ +} diff -urNp freebsd/src/usr.sbin/ctld/ctld.h iscsi/usr.sbin/ctld/ctld.h --- freebsd/src/usr.sbin/ctld/ctld.h 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/ctld.h 2013-03-05 16:30:37.000000000 +0100 @@ -0,0 +1,278 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef CTLD_H +#define CTLD_H + +#include +#include +#include + +#define CTLD_DEFAULT_CONFIG "/etc/ctl.conf" +#define CTLD_DEFAULT_PIDFILE "/var/run/ctld.pid" +#define CTLD_DEFAULT_BLOCKSIZE 512 + +#define CTLD_MAX_NAME_LEN 223 +#define CTLD_MAX_DATA_SEGMENT_LENGTH 65536 + +struct ctld_auth { + TAILQ_ENTRY(ctld_auth) ca_next; + struct ctld_auth_group *ca_auth_group; + char *ca_user; + char *ca_secret; + char *ca_mutual_user; + char *ca_mutual_secret; +}; + +#define CAG_TYPE_UNKNOWN 0 +#define CAG_TYPE_NO_AUTHENTICATION 1 +#define CAG_TYPE_CHAP 2 +#define CAG_TYPE_CHAP_MUTUAL 3 + +struct ctld_auth_group { + TAILQ_ENTRY(ctld_auth_group) cag_next; + struct ctld_conf *cag_conf; + char *cag_name; + struct ctld_target *cag_target; + int cag_type; + TAILQ_HEAD(, ctld_auth) cag_auths; +}; + +struct ctld_portal { + TAILQ_ENTRY(ctld_portal) cp_next; + struct ctld_portal_group *cp_portal_group; + struct addrinfo *cp_ai; + char *cp_listen; + + TAILQ_HEAD(, ctld_target) cp_targets; + int cp_socket; +}; + +struct ctld_portal_group { + TAILQ_ENTRY(ctld_portal_group) cpg_next; + struct ctld_conf *cpg_conf; + char *cpg_name; + struct ctld_auth_group *cpg_discovery_auth_group; + bool cpg_unassigned; + TAILQ_HEAD(, ctld_portal) cpg_portals; + + uint16_t cpg_tag; +}; + +struct ctld_lun_option { + TAILQ_ENTRY(ctld_lun_option) clo_next; + struct ctld_lun *clo_lun; + char *clo_name; + char *clo_value; +}; + +struct ctld_lun { + TAILQ_ENTRY(ctld_lun) cl_next; + TAILQ_HEAD(, ctld_lun_option) cl_options; + struct ctld_target *cl_target; + int cl_lun; + char *cl_backend; + int cl_blocksize; + char *cl_device_id; + char *cl_path; + char *cl_serial; + int64_t cl_size; + + int cl_ctl_lun; +}; + +struct ctld_target { + TAILQ_ENTRY(ctld_target) ct_next; + TAILQ_HEAD(, ctld_lun) ct_luns; + struct ctld_conf *ct_conf; + struct ctld_auth_group *ct_auth_group; + struct ctld_portal_group *ct_portal_group; + char *ct_iqn; + char *ct_alias; +}; + +struct ctld_conf { + char *cc_pidfile_path; + TAILQ_HEAD(, ctld_target) cc_targets; + TAILQ_HEAD(, ctld_auth_group) cc_auth_groups; + TAILQ_HEAD(, ctld_portal_group) cc_portal_groups; + int cc_debug; + + uint16_t cc_last_portal_group_tag; + struct pidfh *cc_pidfh; +}; + +#define CC_SESSION_TYPE_NONE 0 +#define CC_SESSION_TYPE_DISCOVERY 1 +#define CC_SESSION_TYPE_NORMAL 2 + +#define CC_DIGEST_NONE 0 +#define CC_DIGEST_CRC32C 1 + +struct ctld_connection { + struct ctld_portal *cc_portal; + struct ctld_target *cc_target; + int cc_socket; + int cc_session_type; + char *cc_initiator_name; + char *cc_initiator_addr; + uint32_t cc_cmdsn; + uint32_t cc_statsn; + size_t cc_max_data_segment_length; + size_t cc_max_burst_length; + size_t cc_max_outstanding_r2t; + int cc_header_digest; + int cc_data_digest; +}; + +struct ctld_pdu { + struct ctld_connection *cp_connection; + struct iscsi_bhs *cp_bhs; + char *cp_data; + size_t cp_data_len; +}; + +#define CTLD_KEYS_MAX 1024 + +struct ctld_keys { + char *ck_names[CTLD_KEYS_MAX]; + char *ck_values[CTLD_KEYS_MAX]; + char *ck_data; + size_t ck_data_len; +}; + +struct ctld_conf *ctld_conf_new(void); +struct ctld_conf *ctld_conf_new_from_file(const char *path); +struct ctld_conf *ctld_conf_new_from_kernel(void); +void ctld_conf_delete(struct ctld_conf *cc); +int ctld_conf_verify(struct ctld_conf *cc); + +struct ctld_auth_group *ctld_auth_group_new(struct ctld_conf *cc, + const char *name); +void ctld_auth_group_delete(struct ctld_auth_group *cag); +struct ctld_auth_group *ctld_auth_group_find(struct ctld_conf *cc, + const char *name); + +const struct ctld_auth *ctld_auth_new_chap(struct ctld_auth_group *cag, + const char *user, const char *secret); +const struct ctld_auth *ctld_auth_new_chap_mutual(struct ctld_auth_group *cag, + const char *user, const char *secret, + const char *user2, const char *secret2); +const struct ctld_auth *ctld_auth_find(struct ctld_auth_group *cag, + const char *user); + +struct ctld_portal_group *ctld_portal_group_new(struct ctld_conf *cc, + const char *name); +void ctld_portal_group_delete(struct ctld_portal_group *cag); +struct ctld_portal_group *ctld_portal_group_find(struct ctld_conf *cc, + const char *name); +int ctld_portal_group_add_listen(struct ctld_portal_group + *cag, const char *listen); + +struct ctld_target *ctld_target_new(struct ctld_conf *cc, const char *iqn); +void ctld_target_delete(struct ctld_target *ct); +struct ctld_target *ctld_target_find(struct ctld_conf *cc, + const char *iqn); + +struct ctld_lun *ctld_lun_new(struct ctld_target *ct, int lun); +void ctld_lun_delete(struct ctld_lun *ct); +struct ctld_lun *ctld_lun_find(struct ctld_target *ct, int lun); +void ctld_lun_set_backend(struct ctld_lun *cl, + const char *value); +void ctld_lun_set_blocksize(struct ctld_lun *cl, + size_t value); +void ctld_lun_set_device_id(struct ctld_lun *cl, + const char *value); +void ctld_lun_set_path(struct ctld_lun *cl, + const char *value); +void ctld_lun_set_serial(struct ctld_lun *cl, + const char *value); +void ctld_lun_set_size(struct ctld_lun *cl, + size_t value); +void ctld_lun_set_ctl_lun(struct ctld_lun *cl, + uint32_t value); + +struct ctld_lun_option *ctld_lun_option_new(struct ctld_lun *cl, + const char *name, const char *value); +void ctld_lun_option_delete(struct ctld_lun_option *clo); +struct ctld_lun_option *ctld_lun_option_find(struct ctld_lun *cl, + const char *name); +void ctld_lun_option_set(struct ctld_lun_option *clo, + const char *value); + +void ctld_kernel_init(void); +int ctld_kernel_lun_add(struct ctld_lun *cl); +int ctld_kernel_lun_resize(struct ctld_lun *cl); +int ctld_kernel_lun_remove(struct ctld_lun *cl); +void ctld_kernel_handoff(struct ctld_connection *cc); +int ctld_kernel_port_on(void); +int ctld_kernel_port_off(void); + +struct ctld_keys *ctld_keys_new(void); +void ctld_keys_delete(struct ctld_keys *ck); +void ctld_keys_load(struct ctld_keys *ck, + const struct ctld_pdu *cp); +void ctld_keys_save(struct ctld_keys *ck, + struct ctld_pdu *cp); +const char *ctld_keys_find(struct ctld_keys *ck, const char *name); +int ctld_keys_find_int(struct ctld_keys *ck, + const char *name); +void ctld_keys_add(struct ctld_keys *ck, + const char *name, const char *value); +void ctld_keys_add_int(struct ctld_keys *ck, + const char *name, int value); + +struct ctld_pdu *ctld_pdu_new(struct ctld_connection *cc); +struct ctld_pdu *ctld_pdu_new_response(struct ctld_pdu *request); +void ctld_pdu_receive(struct ctld_pdu *request); +void ctld_pdu_send(struct ctld_pdu *response); +void ctld_pdu_delete(struct ctld_pdu *cp); + +void ctld_login(struct ctld_connection *cc); + +void ctld_discovery(struct ctld_connection *cc); + +void log_init(int level); +void log_err(int, const char *, ...) + __dead2 __printf0like(2, 3); +void log_errx(int, const char *, ...) + __dead2 __printf0like(2, 3); +void log_warn(const char *, ...) __printf0like(1, 2); +void log_warnx(const char *, ...) __printflike(1, 2); +void log_debugx(const char *, ...) __printf0like(1, 2); + +char *checked_strdup(const char *); +bool ctld_valid_iscsi_name(const char *name); + +void yyerror(const char *); +int yylex(void); + +#endif /* !CTLD_H */ diff -urNp freebsd/src/usr.sbin/ctld/discovery.c iscsi/usr.sbin/ctld/discovery.c --- freebsd/src/usr.sbin/ctld/discovery.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/discovery.c 2013-04-19 18:33:53.000000000 +0200 @@ -0,0 +1,219 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include "ctld.h" +#include "iscsi.h" + +static struct ctld_pdu * +ctld_text_receive(struct ctld_connection *cc) +{ + struct ctld_pdu *request; + struct iscsi_bhs_text_request *bhstr; + + request = ctld_pdu_new(cc); + ctld_pdu_receive(request); + if ((request->cp_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != + ISCSI_BHS_OPCODE_TEXT_REQUEST) + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->cp_bhs->bhs_opcode); + bhstr = (struct iscsi_bhs_text_request *)request->cp_bhs; +#if 0 + if ((bhstr->bhstr_flags & ISCSI_BHSTR_FLAGS_FINAL) == 0) + log_errx(1, "received Text PDU without the \"F\" flag"); +#endif + /* + * XXX: Implement the C flag some day. + */ + if ((bhstr->bhstr_flags & BHSTR_FLAGS_CONTINUE) != 0) + log_errx(1, "received Text PDU with unsupported \"C\" flag"); + if (request->cp_data_len == 0) + log_errx(1, "received Text PDU with empty data segment"); + + if (ntohl(bhstr->bhstr_cmdsn) < cc->cc_cmdsn) { + log_errx(1, "received Text PDU with decreasing CmdSN: " + "was %d, is %d", cc->cc_cmdsn, ntohl(bhstr->bhstr_cmdsn)); + } + if (ntohl(bhstr->bhstr_expstatsn) != cc->cc_statsn) { + log_errx(1, "received Text PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhstr->bhstr_expstatsn), + cc->cc_statsn); + } + cc->cc_cmdsn = ntohl(bhstr->bhstr_cmdsn); + + return (request); +} + +static struct ctld_pdu * +ctld_text_new_response(struct ctld_pdu *request) +{ + struct ctld_pdu *response; + struct ctld_connection *cc; + struct iscsi_bhs_text_request *bhstr; + struct iscsi_bhs_text_response *bhstr2; + + bhstr = (struct iscsi_bhs_text_request *)request->cp_bhs; + cc = request->cp_connection; + + response = ctld_pdu_new_response(request); + bhstr2 = (struct iscsi_bhs_text_response *)response->cp_bhs; + bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE; + bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL; + bhstr2->bhstr_lun = bhstr->bhstr_lun; + bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag; + bhstr2->bhstr_target_transfer_tag = bhstr->bhstr_target_transfer_tag; + bhstr2->bhstr_statsn = htonl(cc->cc_statsn++); + bhstr2->bhstr_expcmdsn = htonl(cc->cc_cmdsn); + bhstr2->bhstr_maxcmdsn = htonl(cc->cc_cmdsn); + + return (response); +} + +static struct ctld_pdu * +ctld_logout_receive(struct ctld_connection *cc) +{ + struct ctld_pdu *request; + struct iscsi_bhs_logout_request *bhslr; + + request = ctld_pdu_new(cc); + ctld_pdu_receive(request); + if ((request->cp_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != + ISCSI_BHS_OPCODE_LOGOUT_REQUEST) + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->cp_bhs->bhs_opcode); + bhslr = (struct iscsi_bhs_logout_request *)request->cp_bhs; + if ((bhslr->bhslr_reason & 0x7f) != BHSLR_REASON_CLOSE_SESSION) + log_debugx("received Logout PDU with invalid reason 0x%x; " + "continuing anyway", bhslr->bhslr_reason & 0x7f); + if (ntohl(bhslr->bhslr_cmdsn) < cc->cc_cmdsn) { + log_errx(1, "received Logout PDU with decreasing CmdSN: " + "was %d, is %d", cc->cc_cmdsn, ntohl(bhslr->bhslr_cmdsn)); + } + if (ntohl(bhslr->bhslr_expstatsn) != cc->cc_statsn) { + log_errx(1, "received Logout PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn), + cc->cc_statsn); + } + cc->cc_cmdsn = ntohl(bhslr->bhslr_cmdsn); + + return (request); +} + +static struct ctld_pdu * +ctld_logout_new_response(struct ctld_pdu *request) +{ + struct ctld_pdu *response; + struct ctld_connection *cc; + struct iscsi_bhs_logout_request *bhslr; + struct iscsi_bhs_logout_response *bhslr2; + + bhslr = (struct iscsi_bhs_logout_request *)request->cp_bhs; + cc = request->cp_connection; + + response = ctld_pdu_new_response(request); + bhslr2 = (struct iscsi_bhs_logout_response *)response->cp_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_RESPONSE; + bhslr2->bhslr_flags = 0x80; + bhslr2->bhslr_response = BHSLR_RESPONSE_CLOSED_SUCCESSFULLY; + bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; + bhslr2->bhslr_statsn = htonl(cc->cc_statsn++); + bhslr2->bhslr_expcmdsn = htonl(cc->cc_cmdsn); + bhslr2->bhslr_maxcmdsn = htonl(cc->cc_cmdsn); + + return (response); +} + +void +ctld_discovery(struct ctld_connection *cc) +{ + struct ctld_pdu *request, *response; + struct ctld_keys *request_keys, *response_keys; + struct ctld_target *ct; + const char *send_targets; + + log_debugx("beginning discovery session; waiting for Text PDU"); + request = ctld_text_receive(cc); + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + + send_targets = ctld_keys_find(request_keys, "SendTargets"); + if (send_targets == NULL) + log_errx(1, "received Text PDU without SendTargets"); + + response = ctld_text_new_response(request); + response_keys = ctld_keys_new(); + + if (strcmp(send_targets, "All") == 0) { + TAILQ_FOREACH(ct, + &cc->cc_portal->cp_portal_group->cpg_conf->cc_targets, + ct_next) { + if (ct->ct_portal_group != + cc->cc_portal->cp_portal_group) { + log_debugx("not returning target \"%s\"; " + "belongs to a different portal group", + ct->ct_iqn); + continue; + } + ctld_keys_add(response_keys, "TargetName", ct->ct_iqn); + } + } else { + ct = ctld_target_find(cc->cc_portal->cp_portal_group->cpg_conf, + send_targets); + if (ct == NULL) { + log_debugx("initiator requested information on unknown " + "target \"%s\"; returning nothing", send_targets); + } else { + ctld_keys_add(response_keys, "TargetName", ct->ct_iqn); + } + } + ctld_keys_save(response_keys, response); + + ctld_pdu_send(response); + ctld_pdu_delete(response); + ctld_keys_delete(response_keys); + ctld_pdu_delete(request); + ctld_keys_delete(request_keys); + + log_debugx("done sending targets; waiting for Logout PDU"); + request = ctld_logout_receive(cc); + response = ctld_logout_new_response(request); + + ctld_pdu_send(response); + ctld_pdu_delete(response); + ctld_pdu_delete(request); + + log_debugx("discovery session done"); +} diff -urNp freebsd/src/usr.sbin/ctld/kernel.c iscsi/usr.sbin/ctld/kernel.c --- freebsd/src/usr.sbin/ctld/kernel.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/kernel.c 2013-02-19 17:44:21.000000000 +0100 @@ -0,0 +1,628 @@ +/*- + * Copyright (c) 2003, 2004 Silicon Graphics International Corp. + * Copyright (c) 1997-2007 Kenneth D. Merry + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * Portions of this software were developed by Edward Tomasz Napierala + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ctld.h" + +static int ctl_fd = 0; + +void +ctld_kernel_init(void) +{ + + ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR); + if (ctl_fd < 0) + log_err(1, "failed to open %s", CTL_DEFAULT_DEV); +} + +/* + * Name/value pair used for per-LUN attributes. + */ +struct cctl_lun_nv { + char *name; + char *value; + STAILQ_ENTRY(cctl_lun_nv) links; +}; + +/* + * Backend LUN information. + */ +struct cctl_lun { + uint64_t lun_id; + char *backend_type; + uint64_t size_blocks; + uint32_t blocksize; + char *serial_number; + char *device_id; + char *ctld_target; + int ctld_lun; + STAILQ_HEAD(,cctl_lun_nv) attr_list; + STAILQ_ENTRY(cctl_lun) links; +}; + +struct cctl_devlist_data { + int num_luns; + STAILQ_HEAD(,cctl_lun) lun_list; + struct cctl_lun *cur_lun; + int level; + struct sbuf *cur_sb[32]; +}; + +static void +cctl_start_element(void *user_data, const char *name, const char **attr) +{ + int i; + struct cctl_devlist_data *devlist; + struct cctl_lun *cur_lun; + + devlist = (struct cctl_devlist_data *)user_data; + cur_lun = devlist->cur_lun; + devlist->level++; + if ((u_int)devlist->level > (sizeof(devlist->cur_sb) / + sizeof(devlist->cur_sb[0]))) + log_errx(1, "%s: too many nesting levels, %zd max", __func__, + sizeof(devlist->cur_sb) / sizeof(devlist->cur_sb[0])); + + devlist->cur_sb[devlist->level] = sbuf_new_auto(); + if (devlist->cur_sb[devlist->level] == NULL) + log_err(1, "%s: unable to allocate sbuf", __func__); + + if (strcmp(name, "lun") == 0) { + if (cur_lun != NULL) + log_errx(1, "%s: improper lun element nesting", + __func__); + + cur_lun = calloc(1, sizeof(*cur_lun)); + if (cur_lun == NULL) + log_err(1, "%s: cannot allocate %zd bytes", __func__, + sizeof(*cur_lun)); + + devlist->num_luns++; + devlist->cur_lun = cur_lun; + + STAILQ_INIT(&cur_lun->attr_list); + STAILQ_INSERT_TAIL(&devlist->lun_list, cur_lun, links); + + for (i = 0; attr[i] != NULL; i += 2) { + if (strcmp(attr[i], "id") == 0) { + cur_lun->lun_id = strtoull(attr[i+1], NULL, 0); + } else { + log_errx(1, "%s: invalid LUN attribute %s = %s", + __func__, attr[i], attr[i+1]); + } + } + } +} + +static void +cctl_end_element(void *user_data, const char *name) +{ + struct cctl_devlist_data *devlist; + struct cctl_lun *cur_lun; + char *str; + + devlist = (struct cctl_devlist_data *)user_data; + cur_lun = devlist->cur_lun; + + if ((cur_lun == NULL) + && (strcmp(name, "ctllunlist") != 0)) + log_errx(1, "%s: cur_lun == NULL! (name = %s)", __func__, name); + + if (devlist->cur_sb[devlist->level] == NULL) + log_errx(1, "%s: no valid sbuf at level %d (name %s)", __func__, + devlist->level, name); + + sbuf_finish(devlist->cur_sb[devlist->level]); + str = checked_strdup(sbuf_data(devlist->cur_sb[devlist->level])); + + if (strlen(str) == 0) { + free(str); + str = NULL; + } + + sbuf_delete(devlist->cur_sb[devlist->level]); + devlist->cur_sb[devlist->level] = NULL; + devlist->level--; + + if (strcmp(name, "backend_type") == 0) { + cur_lun->backend_type = str; + str = NULL; + } else if (strcmp(name, "size") == 0) { + cur_lun->size_blocks = strtoull(str, NULL, 0); + } else if (strcmp(name, "blocksize") == 0) { + cur_lun->blocksize = strtoul(str, NULL, 0); + } else if (strcmp(name, "serial_number") == 0) { + cur_lun->serial_number = str; + str = NULL; + } else if (strcmp(name, "device_id") == 0) { + cur_lun->device_id = str; + str = NULL; + } else if (strcmp(name, "ctld_target") == 0) { + cur_lun->ctld_target = str; + str = NULL; + } else if (strcmp(name, "ctld_lun") == 0) { + cur_lun->ctld_lun = strtoul(str, NULL, 0); + } else if (strcmp(name, "lun") == 0) { + devlist->cur_lun = NULL; + } else if (strcmp(name, "ctllunlist") == 0) { + + } else { + struct cctl_lun_nv *nv; + + nv = calloc(1, sizeof(*nv)); + if (nv == NULL) + log_err(1, "%s: can't allocate %zd bytes for nv pair", + __func__, sizeof(*nv)); + + nv->name = checked_strdup(name); + + nv->value = str; + str = NULL; + STAILQ_INSERT_TAIL(&cur_lun->attr_list, nv, links); + } + + free(str); +} + +static void +cctl_char_handler(void *user_data, const XML_Char *str, int len) +{ + struct cctl_devlist_data *devlist; + + devlist = (struct cctl_devlist_data *)user_data; + + sbuf_bcat(devlist->cur_sb[devlist->level], str, len); +} + +struct ctld_conf * +ctld_conf_new_from_kernel(void) +{ + struct ctld_conf *cc = NULL; + struct ctld_target *ct; + struct ctld_lun *cl; + struct ctld_lun_option *clo; + struct ctl_lun_list list; + struct cctl_devlist_data devlist; + struct cctl_lun *lun; + XML_Parser parser; + char *lun_str = NULL; + int lun_len; + int retval; + + lun_len = 4096; + + bzero(&devlist, sizeof(devlist)); + STAILQ_INIT(&devlist.lun_list); + + log_debugx("obtaining previously configured CTL luns from the kernel"); + +retry: + lun_str = realloc(lun_str, lun_len); + if (lun_str == NULL) + log_err(1, "realloc"); + + bzero(&list, sizeof(list)); + list.alloc_len = lun_len; + list.status = CTL_LUN_LIST_NONE; + list.lun_xml = lun_str; + + if (ioctl(ctl_fd, CTL_LUN_LIST, &list) == -1) { + log_warn("error issuing CTL_LUN_LIST ioctl"); + free(lun_str); + return (NULL); + } + + if (list.status == CTL_LUN_LIST_ERROR) { + log_warnx("error returned from CTL_LUN_LIST ioctl: %s", + list.error_str); + free(lun_str); + return (NULL); + } + + if (list.status == CTL_LUN_LIST_NEED_MORE_SPACE) { + lun_len = lun_len << 1; + goto retry; + } + + parser = XML_ParserCreate(NULL); + if (parser == NULL) { + log_warnx("unable to create XML parser"); + free(lun_str); + return (NULL); + } + + XML_SetUserData(parser, &devlist); + XML_SetElementHandler(parser, cctl_start_element, cctl_end_element); + XML_SetCharacterDataHandler(parser, cctl_char_handler); + + retval = XML_Parse(parser, lun_str, strlen(lun_str), 1); + XML_ParserFree(parser); + free(lun_str); + if (retval != 1) { + log_warnx("XML_Parse failed"); + return (NULL); + } + + cc = ctld_conf_new(); + + STAILQ_FOREACH(lun, &devlist.lun_list, links) { + struct cctl_lun_nv *nv; + + if (lun->ctld_target == NULL) { + log_debugx("CTL lun %ju wasn't managed by ctld; " + "ignoring", (uintmax_t)lun->lun_id); + continue; + } + + ct = ctld_target_find(cc, lun->ctld_target); + if (ct == NULL) { +#if 0 + log_debugx("found new kernel target %s for CTL lun %ld", + lun->ctld_target, lun->lun_id); +#endif + ct = ctld_target_new(cc, lun->ctld_target); + if (ct == NULL) { + log_warnx("ctld_target_new failed"); + continue; + } + } + + cl = ctld_lun_find(ct, lun->ctld_lun); + if (cl != NULL) { + log_warnx("found CTL lun %ju, backing lun %d, target " + "%s, also backed by CTL lun %d; ignoring", + (uintmax_t) lun->lun_id, cl->cl_lun, + cl->cl_target->ct_iqn, cl->cl_ctl_lun); + continue; + } + + log_debugx("found CTL lun %ju, backing lun %d, target %s", + (uintmax_t)lun->lun_id, lun->ctld_lun, lun->ctld_target); + + cl = ctld_lun_new(ct, lun->ctld_lun); + if (cl == NULL) { + log_warnx("ctld_lun_new failed"); + continue; + } + ctld_lun_set_backend(cl, lun->backend_type); + ctld_lun_set_blocksize(cl, lun->blocksize); + ctld_lun_set_device_id(cl, lun->device_id); + ctld_lun_set_serial(cl, lun->serial_number); + ctld_lun_set_size(cl, lun->size_blocks * cl->cl_blocksize); + ctld_lun_set_ctl_lun(cl, lun->lun_id); + + STAILQ_FOREACH(nv, &lun->attr_list, links) { + if (strcmp(nv->name, "file") == 0 || + strcmp(nv->name, "dev") == 0) { + ctld_lun_set_path(cl, nv->value); + continue; + } + clo = ctld_lun_option_new(cl, nv->name, nv->value); + if (clo == NULL) + log_warnx("unable to add CTL lun option %s " + "for CTL lun %ju for lun %d, target %s", + nv->name, (uintmax_t) lun->lun_id, + cl->cl_lun, cl->cl_target->ct_iqn); + } + } + + return (cc); +} + +int +ctld_kernel_lun_add(struct ctld_lun *cl) +{ + struct ctld_lun_option *clo; + struct ctl_lun_req req; + char *tmp; + int error, i, num_options; + + bzero(&req, sizeof(req)); + + strlcpy(req.backend, cl->cl_backend, sizeof(req.backend)); + req.reqtype = CTL_LUNREQ_CREATE; + + req.reqdata.create.blocksize_bytes = cl->cl_blocksize; + + if (cl->cl_size != 0) + req.reqdata.create.lun_size_bytes = cl->cl_size; + + req.reqdata.create.flags |= CTL_LUN_FLAG_DEV_TYPE; + req.reqdata.create.device_type = T_DIRECT; + + if (cl->cl_serial != NULL) { + strlcpy(req.reqdata.create.serial_num, cl->cl_serial, + sizeof(req.reqdata.create.serial_num)); + req.reqdata.create.flags |= CTL_LUN_FLAG_SERIAL_NUM; + } + + if (cl->cl_device_id != NULL) { + strlcpy(req.reqdata.create.device_id, cl->cl_device_id, + sizeof(req.reqdata.create.device_id)); + req.reqdata.create.flags |= CTL_LUN_FLAG_DEVID; + } + + if (cl->cl_path != NULL) { + clo = ctld_lun_option_find(cl, "file"); + if (clo != NULL) { + ctld_lun_option_set(clo, cl->cl_path); + } else { + clo = ctld_lun_option_new(cl, "file", cl->cl_path); + assert(clo != NULL); + } + } + + clo = ctld_lun_option_find(cl, "ctld_target"); + if (clo != NULL) { + ctld_lun_option_set(clo, cl->cl_target->ct_iqn); + } else { + clo = ctld_lun_option_new(cl, "ctld_target", + cl->cl_target->ct_iqn); + assert(clo != NULL); + } + + asprintf(&tmp, "%d", cl->cl_lun); + if (tmp == NULL) + log_errx(1, "asprintf"); + clo = ctld_lun_option_find(cl, "ctld_lun"); + if (clo != NULL) { + ctld_lun_option_set(clo, tmp); + free(tmp); + } else { + clo = ctld_lun_option_new(cl, "ctld_lun", tmp); + free(tmp); + assert(clo != NULL); + } + + num_options = 0; + TAILQ_FOREACH(clo, &cl->cl_options, clo_next) + num_options++; + + req.num_be_args = num_options; + if (num_options > 0) { + req.be_args = malloc(num_options * sizeof(*req.be_args)); + if (req.be_args == NULL) { + log_warn("error allocating %zd bytes", + num_options * sizeof(*req.be_args)); + return (1); + } + + i = 0; + TAILQ_FOREACH(clo, &cl->cl_options, clo_next) { + /* + * +1 for the terminating '\0' + */ + req.be_args[i].namelen = strlen(clo->clo_name) + 1; + req.be_args[i].name = clo->clo_name; + req.be_args[i].vallen = strlen(clo->clo_value) + 1; + req.be_args[i].value = clo->clo_value; + req.be_args[i].flags = CTL_BEARG_ASCII | CTL_BEARG_RD; + i++; + } + assert(i == num_options); + } + + error = ioctl(ctl_fd, CTL_LUN_REQ, &req); + free(req.be_args); + if (error != 0) { + log_warn("error issuing CTL_LUN_REQ ioctl"); + return (1); + } + + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from LUN creation request: %s", + req.error_str); + return (1); + } + + if (req.status != CTL_LUN_OK) { + log_warnx("unknown LUN creation request status %d", + req.status); + return (1); + } + + ctld_lun_set_ctl_lun(cl, req.reqdata.create.req_lun_id); + + return (0); +} + +int +ctld_kernel_lun_resize(struct ctld_lun *cl) +{ + struct ctl_lun_req req; + + bzero(&req, sizeof(req)); + + strlcpy(req.backend, cl->cl_backend, sizeof(req.backend)); + req.reqtype = CTL_LUNREQ_MODIFY; + + req.reqdata.modify.lun_id = cl->cl_ctl_lun; + req.reqdata.modify.lun_size_bytes = cl->cl_size; + + if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) { + log_warn("error issuing CTL_LUN_REQ ioctl"); + return (1); + } + + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from LUN modification request: %s", + req.error_str); + return (1); + } + + if (req.status != CTL_LUN_OK) { + log_warnx("unknown LUN modification request status %d", + req.status); + return (1); + } + + return (0); +} + +int +ctld_kernel_lun_remove(struct ctld_lun *cl) +{ + struct ctl_lun_req req; + + bzero(&req, sizeof(req)); + + strlcpy(req.backend, cl->cl_backend, sizeof(req.backend)); + req.reqtype = CTL_LUNREQ_RM; + + req.reqdata.rm.lun_id = cl->cl_ctl_lun; + + if (ioctl(ctl_fd, CTL_LUN_REQ, &req) == -1) { + log_warn("error issuing CTL_LUN_REQ ioctl"); + return (1); + } + + if (req.status == CTL_LUN_ERROR) { + log_warnx("error returned from LUN removal request: %s", + req.error_str); + return (1); + } + + if (req.status != CTL_LUN_OK) { + log_warnx("unknown LUN removal request status %d", req.status); + return (1); + } + + return (0); +} + +void +ctld_kernel_handoff(struct ctld_connection *cc) +{ + struct ctl_iscsi req; + + bzero(&req, sizeof(req)); + + req.type = CTL_ISCSI_HANDOFF; + strlcpy(req.data.handoff.initiator_name, + cc->cc_initiator_name, sizeof(req.data.handoff.initiator_name)); + strlcpy(req.data.handoff.initiator_addr, + cc->cc_initiator_addr, sizeof(req.data.handoff.initiator_addr)); + strlcpy(req.data.handoff.target_name, + cc->cc_target->ct_iqn, sizeof(req.data.handoff.target_name)); + req.data.handoff.socket = cc->cc_socket; + req.data.handoff.portal_group_tag = + cc->cc_portal->cp_portal_group->cpg_tag; + if (cc->cc_header_digest == CC_DIGEST_CRC32C) + req.data.handoff.header_digest = CTL_ISCSI_DIGEST_CRC32C; + if (cc->cc_data_digest == CC_DIGEST_CRC32C) + req.data.handoff.data_digest = CTL_ISCSI_DIGEST_CRC32C; + req.data.handoff.cmdsn = cc->cc_cmdsn; + req.data.handoff.statsn = cc->cc_statsn; + req.data.handoff.max_recv_data_segment_length = + cc->cc_max_data_segment_length; + req.data.handoff.max_burst_length = cc->cc_max_burst_length; + req.data.handoff.max_outstanding_r2t = cc->cc_max_outstanding_r2t; + + if (ioctl(ctl_fd, CTL_ISCSI, &req) == -1) + log_err(1, "error issuing CTL_ISCSI ioctl; " + "dropping connection"); + + if (req.status != CTL_ISCSI_OK) + log_errx(1, "error returned from CTL iSCSI handoff request: " + "%s; dropping connection", req.error_str); +} + +int +ctld_kernel_port_on(void) +{ + struct ctl_port_entry entry; + int error; + + bzero(&entry, sizeof(&entry)); + + entry.port_type = CTL_PORT_ISCSI; + entry.targ_port = -1; + + error = ioctl(ctl_fd, CTL_ENABLE_PORT, &entry); + if (error != 0) { + log_warn("CTL_ENABLE_PORT ioctl failed"); + return (-1); + } + + return (0); +} + +int +ctld_kernel_port_off(void) +{ + struct ctl_port_entry entry; + int error; + + bzero(&entry, sizeof(&entry)); + + entry.port_type = CTL_PORT_ISCSI; + entry.targ_port = -1; + + error = ioctl(ctl_fd, CTL_DISABLE_PORT, &entry); + if (error != 0) { + log_warn("CTL_DISABLE_PORT ioctl failed"); + return (-1); + } + + return (0); +} diff -urNp freebsd/src/usr.sbin/ctld/keys.c iscsi/usr.sbin/ctld/keys.c --- freebsd/src/usr.sbin/ctld/keys.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/keys.c 2013-02-13 15:12:55.000000000 +0100 @@ -0,0 +1,215 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include "ctld.h" + +struct ctld_keys * +ctld_keys_new(void) +{ + struct ctld_keys *ck; + + ck = calloc(sizeof(*ck), 1); + if (ck == NULL) + log_err(1, "calloc"); + + return (ck); +} + +void +ctld_keys_delete(struct ctld_keys *ck) +{ + + free(ck->ck_data); + free(ck); +} + +void +ctld_keys_load(struct ctld_keys *ck, const struct ctld_pdu *cp) +{ + int i; + char *pair; + size_t pair_len; + + if (cp->cp_data_len == 0) + log_errx(1, "protocol error: empty data segment"); + + if (cp->cp_data[cp->cp_data_len - 1] != '\0') + log_errx(1, "protocol error: key not NULL-terminated\n"); + + assert(ck->ck_data == NULL); + ck->ck_data_len = cp->cp_data_len; + ck->ck_data = malloc(ck->ck_data_len); + if (ck->ck_data == NULL) + log_err(1, "malloc"); + memcpy(ck->ck_data, cp->cp_data, ck->ck_data_len); + + /* + * XXX: Review this carefully. + */ + pair = ck->ck_data; + for (i = 0;; i++) { + if (i >= CTLD_KEYS_MAX) + log_errx(1, "too many keys received"); + + pair_len = strlen(pair); + + ck->ck_values[i] = pair; + ck->ck_names[i] = strsep(&ck->ck_values[i], "="); + if (ck->ck_names[i] == NULL || ck->ck_values[i] == NULL) + log_errx(1, "malformed keys"); + log_debugx("key received: \"%s=%s\"", + ck->ck_names[i], ck->ck_values[i]); + + pair += pair_len + 1; /* +1 to skip the terminating '\0'. */ + if (pair == ck->ck_data + ck->ck_data_len) + break; + assert(pair < ck->ck_data + ck->ck_data_len); + } +} + +void +ctld_keys_save(struct ctld_keys *ck, struct ctld_pdu *cp) +{ + char *data; + size_t len; + int i; + + /* + * XXX: Not particularly efficient. + */ + len = 0; + for (i = 0; i < CTLD_KEYS_MAX; i++) { + if (ck->ck_names[i] == NULL) + break; + /* + * +1 for '=', +1 for '\0'. + */ + len += strlen(ck->ck_names[i]) + strlen(ck->ck_values[i]) + 2; + } + + if (len == 0) + return; + + data = malloc(len); + if (data == NULL) + log_err(1, "malloc"); + + cp->cp_data = data; + cp->cp_data_len = len; + + for (i = 0; i < CTLD_KEYS_MAX; i++) { + if (ck->ck_names[i] == NULL) + break; + data += + sprintf(data, "%s=%s", ck->ck_names[i], ck->ck_values[i]); + data += 1; /* for '\0'. */ + } +} + +const char * +ctld_keys_find(struct ctld_keys *ck, const char *name) +{ + int i; + + /* + * Note that we don't handle duplicated key names here, + * as they are not supposed to happen in requests, and if they do, + * it's an initiator error. + */ + for (i = 0; i < CTLD_KEYS_MAX; i++) { + if (ck->ck_names[i] == NULL) + return (NULL); + if (strcmp(ck->ck_names[i], name) == 0) + return (ck->ck_values[i]); + } + return (NULL); +} + +int +ctld_keys_find_int(struct ctld_keys *ck, const char *name) +{ + const char *str; + char *endptr; + int num; + + str = ctld_keys_find(ck, name); + if (str == NULL) + return (-1); + + num = strtoul(str, &endptr, 10); + if (*endptr != '\0') { + log_debugx("invalid numeric value \"%s\"", str); + return (-1); + } + + return (num); +} + +void +ctld_keys_add(struct ctld_keys *ck, const char *name, const char *value) +{ + int i; + + log_debugx("key to send: \"%s=%s\"", name, value); + + /* + * Note that we don't check for duplicates here, as they are perfectly + * fine in responses, e.g. the "TargetName" keys in discovery sesion + * response. + */ + for (i = 0; i < CTLD_KEYS_MAX; i++) { + if (ck->ck_names[i] == NULL) { + ck->ck_names[i] = checked_strdup(name); + ck->ck_values[i] = checked_strdup(value); + return; + } + } + log_errx(1, "too many keys"); +} + +void +ctld_keys_add_int(struct ctld_keys *ck, const char *name, int value) +{ + char *str; + int ret; + + ret = asprintf(&str, "%d", value); + if (ret <= 0) + log_err(1, "asprintf"); + + ctld_keys_add(ck, name, str); + free(str); +} diff -urNp freebsd/src/usr.sbin/ctld/log.c iscsi/usr.sbin/ctld/log.c --- freebsd/src/usr.sbin/ctld/log.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/log.c 2013-03-05 16:30:37.000000000 +0100 @@ -0,0 +1,145 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include + +#include "ctld.h" + +static int log_level = 0; + +void +log_init(int level) +{ + + log_level = level; + openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON); +} + +void +log_err(int eval, const char *fmt, ...) +{ + va_list ap, ap2; + int saved_errno; + + saved_errno = errno; + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": %s\n", strerror(saved_errno)); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_CRIT, fmt, ap2); + va_end(ap2); + + exit(eval); +} + +void +log_errx(int eval, const char *fmt, ...) +{ + va_list ap, ap2; + + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_CRIT, fmt, ap2); + va_end(ap2); + + exit(eval); +} + +void +log_warn(const char *fmt, ...) +{ + va_list ap, ap2; + int saved_errno; + + saved_errno = errno; + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": %s\n", strerror(saved_errno)); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_WARNING, fmt, ap2); + va_end(ap2); +} + +void +log_warnx(const char *fmt, ...) +{ + va_list ap, ap2; + + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_WARNING, fmt, ap2); + va_end(ap2); +} + +void +log_debugx(const char *fmt, ...) +{ + va_list ap, ap2; + + if (log_level == 0) + return; + + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_DEBUG, fmt, ap2); + va_end(ap2); +} diff -urNp freebsd/src/usr.sbin/ctld/login.c iscsi/usr.sbin/ctld/login.c --- freebsd/src/usr.sbin/ctld/login.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/login.c 2013-04-19 20:11:39.000000000 +0200 @@ -0,0 +1,1012 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctld.h" +#include "iscsi.h" + +static void ctld_login_send_error(struct ctld_pdu *request, + char class, char detail); + +static void +ctld_login_set_nsg(struct ctld_pdu *response, int nsg) +{ + struct iscsi_bhs_login_response *bhslr; + + assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION || + nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || + nsg == BHSLR_STAGE_FULL_FEATURE_PHASE); + + bhslr = (struct iscsi_bhs_login_response *)response->cp_bhs; + + bhslr->bhslr_flags &= 0xFC; + bhslr->bhslr_flags |= nsg; +} + +static int +ctld_login_csg(const struct ctld_pdu *request) +{ + struct iscsi_bhs_login_request *bhslr; + + bhslr = (struct iscsi_bhs_login_request *)request->cp_bhs; + + return ((bhslr->bhslr_flags & 0x0C) >> 2); +} + +static void +ctld_login_set_csg(struct ctld_pdu *response, int csg) +{ + struct iscsi_bhs_login_response *bhslr; + + assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION || + csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || + csg == BHSLR_STAGE_FULL_FEATURE_PHASE); + + bhslr = (struct iscsi_bhs_login_response *)response->cp_bhs; + + bhslr->bhslr_flags &= 0xF3; + bhslr->bhslr_flags |= csg << 2; +} + +static struct ctld_pdu * +ctld_login_receive(struct ctld_connection *cc, bool initial) +{ + struct ctld_pdu *request; + struct iscsi_bhs_login_request *bhslr; + + request = ctld_pdu_new(cc); + ctld_pdu_receive(request); + if ((request->cp_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) != + ISCSI_BHS_OPCODE_LOGIN_REQUEST) { + /* + * The first PDU in session is special - if we receive any PDU + * different than login request, we have to drop the connection + * without sending response ("A target receiving any PDU + * except a Login request before the Login phase is started MUST + * immediately terminate the connection on which the PDU + * was received.") + */ + if (initial == false) + ctld_login_send_error(request, 0x02, 0x0b); + log_errx(1, "protocol error: received invalid opcode 0x%x", + request->cp_bhs->bhs_opcode); + } + bhslr = (struct iscsi_bhs_login_request *)request->cp_bhs; + /* + * XXX: Implement the C flag some day. + */ + if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) { + ctld_login_send_error(request, 0x03, 0x00); + log_errx(1, "received Login PDU with unsupported \"C\" flag"); + } + if (bhslr->bhslr_version_max != 0x00) { + ctld_login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with unsupported " + "Version-max 0x%x", bhslr->bhslr_version_max); + } + if (bhslr->bhslr_version_min != 0x00) { + ctld_login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with unsupported " + "Version-min 0x%x", bhslr->bhslr_version_min); + } + if (request->cp_data_len == 0) { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received Login PDU with empty data segment"); + } + if (ntohl(bhslr->bhslr_cmdsn) < cc->cc_cmdsn) { + ctld_login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with decreasing CmdSN: " + "was %d, is %d", cc->cc_cmdsn, + ntohl(bhslr->bhslr_cmdsn)); + } + if (initial == false && ntohl(bhslr->bhslr_expstatsn) != cc->cc_statsn) { + ctld_login_send_error(request, 0x02, 0x05); + log_errx(1, "received Login PDU with wrong ExpStatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_expstatsn), + cc->cc_statsn); + } + cc->cc_cmdsn = ntohl(bhslr->bhslr_cmdsn); + + return (request); +} + +static struct ctld_pdu * +ctld_login_new_response(struct ctld_pdu *request) +{ + struct ctld_pdu *response; + struct ctld_connection *cc; + struct iscsi_bhs_login_request *bhslr; + struct iscsi_bhs_login_response *bhslr2; + + bhslr = (struct iscsi_bhs_login_request *)request->cp_bhs; + cc = request->cp_connection; + + response = ctld_pdu_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->cp_bhs; + bhslr2->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_RESPONSE; + ctld_login_set_csg(response, BHSLR_STAGE_SECURITY_NEGOTIATION); + memcpy(bhslr2->bhslr_isid, + bhslr->bhslr_isid, sizeof(bhslr2->bhslr_isid)); + bhslr2->bhslr_initiator_task_tag = bhslr->bhslr_initiator_task_tag; + bhslr2->bhslr_statsn = htonl(cc->cc_statsn++); + bhslr2->bhslr_expcmdsn = htonl(cc->cc_cmdsn); + bhslr2->bhslr_maxcmdsn = htonl(cc->cc_cmdsn); + + return (response); +} + +static void +ctld_login_send_error(struct ctld_pdu *request, char class, char detail) +{ + struct ctld_pdu *response; + struct iscsi_bhs_login_response *bhslr2; + + log_debugx("sending Login Response PDU with failure class 0x%x/0x%x; " + "see next line for reason", class, detail); + response = ctld_login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->cp_bhs; + bhslr2->bhslr_status_class = class; + bhslr2->bhslr_status_detail = detail; + + ctld_pdu_send(response); + ctld_pdu_delete(response); +} + +static int +ctld_login_list_contains(const char *list, const char *what) +{ + char *tofree, *str, *token; + + tofree = str = checked_strdup(list); + + while ((token = strsep(&str, ",")) != NULL) { + if (strcmp(token, what) == 0) { + free(tofree); + return (1); + } + } + free(tofree); + return (0); +} + +static int +ctld_login_list_prefers(const char *list, + const char *choice1, const char *choice2) +{ + char *tofree, *str, *token; + + tofree = str = checked_strdup(list); + + while ((token = strsep(&str, ",")) != NULL) { + if (strcmp(token, choice1) == 0) { + free(tofree); + return (1); + } + if (strcmp(token, choice2) == 0) { + free(tofree); + return (2); + } + } + free(tofree); + return (-1); +} + +static int +ctld_login_hex2int(const char hex) +{ + switch (hex) { + case '0': + return (0x00); + case '1': + return (0x01); + case '2': + return (0x02); + case '3': + return (0x03); + case '4': + return (0x04); + case '5': + return (0x05); + case '6': + return (0x06); + case '7': + return (0x07); + case '8': + return (0x08); + case '9': + return (0x09); + case 'a': + case 'A': + return (0x0a); + case 'b': + case 'B': + return (0x0b); + case 'c': + case 'C': + return (0x0c); + case 'd': + case 'D': + return (0x0d); + case 'e': + case 'E': + return (0x0e); + case 'f': + case 'F': + return (0x0f); + default: + return (-1); + } +} + +/* + * XXX: Review this _carefully_. + */ +static int +ctld_login_hex2bin(const char *hex, char **binp, size_t *bin_lenp) +{ + int i, hex_len, nibble; + bool lo = true; /* As opposed to 'hi'. */ + char *bin; + size_t bin_off, bin_len; + + if (strncasecmp(hex, "0x", strlen("0x")) != 0) { + log_warnx("malformed variable, should start with \"0x\""); + return (-1); + } + + hex += strlen("0x"); + hex_len = strlen(hex); + if (hex_len < 1) { + log_warnx("malformed variable; doesn't contain anything " + "but \"0x\""); + return (-1); + } + + bin_len = hex_len / 2 + hex_len % 2; + bin = calloc(bin_len, 1); + if (bin == NULL) + log_err(1, "calloc"); + + bin_off = bin_len - 1; + for (i = hex_len - 1; i >= 0; i--) { + nibble = ctld_login_hex2int(hex[i]); + if (nibble < 0) { + log_warnx("malformed variable, invalid char \"%c\"", + hex[i]); + return (-1); + } + + assert(bin_off < bin_len); + if (lo) { + bin[bin_off] = nibble; + lo = false; + } else { + bin[bin_off] |= nibble << 4; + bin_off--; + lo = true; + } + } + + *binp = bin; + *bin_lenp = bin_len; + return (0); +} + +static char * +ctld_login_bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *hex, *tmp, ch; + size_t hex_len; + size_t i; + + hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ + hex = malloc(hex_len); + if (hex == NULL) + log_err(1, "malloc"); + + tmp = hex; + tmp += sprintf(tmp, "0x"); + for (i = 0; i < bin_len; i++) { + ch = bin[i]; + tmp += sprintf(tmp, "%02x", ch); + } + + return (hex); +} + +static void +ctld_login_compute_md5(const char 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 ctld_pdu * +ctld_login_receive_chap_a(struct ctld_connection *cc) +{ + struct ctld_pdu *request; + struct ctld_keys *request_keys; + const char *chap_a; + + request = ctld_login_receive(cc, false); + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + + chap_a = ctld_keys_find(request_keys, "CHAP_A"); + if (chap_a == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU without CHAP_A"); + } + if (ctld_login_list_contains(chap_a, "5") == 0) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "received CHAP Login PDU with unsupported CHAP_A " + "\"%s\"", chap_a); + } + ctld_keys_delete(request_keys); + + return (request); +} + +static void +ctld_login_send_chap_c(struct ctld_pdu *request, const unsigned char id, + const void *challenge, const size_t challenge_len) +{ + struct ctld_pdu *response; + struct ctld_keys *response_keys; + char *chap_c, chap_i[4]; + + chap_c = ctld_login_bin2hex(challenge, challenge_len); + snprintf(chap_i, sizeof(chap_i), "%d", id); + + response = ctld_login_new_response(request); + response_keys = ctld_keys_new(); + ctld_keys_add(response_keys, "CHAP_A", "5"); + ctld_keys_add(response_keys, "CHAP_I", chap_i); + ctld_keys_add(response_keys, "CHAP_C", chap_c); + free(chap_c); + ctld_keys_save(response_keys, response); + ctld_keys_delete(response_keys); + ctld_pdu_send(response); +} + +static struct ctld_pdu * +ctld_login_receive_chap_r(struct ctld_connection *cc, + struct ctld_auth_group *cag, const unsigned char id, const void *challenge, + const size_t challenge_len, const struct ctld_auth **cap) +{ + struct ctld_pdu *request; + struct ctld_keys *request_keys; + const char *chap_n, *chap_r; + char *response_bin, expected_response_bin[MD5_DIGEST_LENGTH]; + size_t response_bin_len; + const struct ctld_auth *ca; + int error; + + request = ctld_login_receive(cc, false); + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + + chap_n = ctld_keys_find(request_keys, "CHAP_N"); + if (chap_n == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU without CHAP_N"); + } + chap_r = ctld_keys_find(request_keys, "CHAP_R"); + if (chap_r == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU without CHAP_R"); + } + error = ctld_login_hex2bin(chap_r, &response_bin, &response_bin_len); + if (error != 0) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU with malformed CHAP_R"); + } + + /* + * Verify the response. + */ + assert(cag->cag_type == CAG_TYPE_CHAP || + cag->cag_type == CAG_TYPE_CHAP_MUTUAL); + ca = ctld_auth_find(cag, chap_n); + if (ca == NULL) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "received CHAP Login with invalid user \"%s\"", + chap_n); + } + + assert(ca->ca_secret != NULL); + assert(strlen(ca->ca_secret) > 0); + ctld_login_compute_md5(id, ca->ca_secret, challenge, + challenge_len, expected_response_bin, + sizeof(expected_response_bin)); + + if (memcmp(response_bin, expected_response_bin, + sizeof(expected_response_bin)) != 0) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "CHAP authentication failed for user \"%s\"", + ca->ca_user); + } + + ctld_keys_delete(request_keys); + free(response_bin); + + *cap = ca; + return (request); +} + +static void +ctld_login_send_chap_success(struct ctld_pdu *request, + const struct ctld_auth *ca) +{ + struct ctld_pdu *response; + struct ctld_keys *request_keys, *response_keys; + struct iscsi_bhs_login_response *bhslr2; + const char *chap_i, *chap_c; + char *chap_r, *challenge, response_bin[MD5_DIGEST_LENGTH]; + size_t challenge_len; + unsigned char id; + int error; + + response = ctld_login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->cp_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + ctld_login_set_nsg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + + /* + * Actually, one more thing: mutual authentication. + */ + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + chap_i = ctld_keys_find(request_keys, "CHAP_I"); + chap_c = ctld_keys_find(request_keys, "CHAP_C"); + if (chap_i != NULL || chap_c != NULL) { + if (chap_i == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "initiator requested target " + "authentication, but didn't send CHAP_I"); + } + if (chap_c == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "initiator requested target " + "authentication, but didn't send CHAP_C"); + } + if (ca->ca_auth_group->cag_type != CAG_TYPE_CHAP_MUTUAL) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "initiator requests target authentication " + "for user \"%s\", but mutual user/secret " + "is not set", ca->ca_user); + } + + id = strtoul(chap_i, NULL, 10); + error = ctld_login_hex2bin(chap_c, &challenge, &challenge_len); + if (error != 0) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received CHAP Login PDU with malformed " + "CHAP_C"); + } + + log_debugx("performing mutual authentication as user \"%s\"", + ca->ca_mutual_user); + ctld_login_compute_md5(id, ca->ca_mutual_secret, challenge, + challenge_len, response_bin, sizeof(response_bin)); + + chap_r = ctld_login_bin2hex(response_bin, + sizeof(response_bin)); + response_keys = ctld_keys_new(); + ctld_keys_add(response_keys, "CHAP_N", ca->ca_mutual_user); + ctld_keys_add(response_keys, "CHAP_R", chap_r); + free(chap_r); + ctld_keys_save(response_keys, response); + ctld_keys_delete(response_keys); + } else { + log_debugx("initiator did not request target authentication"); + } + + ctld_keys_delete(request_keys); + ctld_pdu_send(response); +} + +static void +ctld_login_chap(struct ctld_connection *cc, struct ctld_auth_group *cag) +{ + const struct ctld_auth *ca; + struct ctld_pdu *request; + char challenge_bin[LOGIN_CHALLENGE_LEN]; + unsigned char id; + int rv; + + /* + * Receive CHAP_A PDU. + */ + log_debugx("beginning CHAP authentication; waiting for CHAP_A"); + request = ctld_login_receive_chap_a(cc); + + /* + * Generate the challenge. + */ + rv = RAND_bytes(challenge_bin, sizeof(challenge_bin)); + if (rv != 1) { + ctld_login_send_error(request, 0x03, 0x02); + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + rv = RAND_bytes(&id, sizeof(id)); + if (rv != 1) { + ctld_login_send_error(request, 0x03, 0x02); + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + /* + * Send the challenge. + */ + log_debugx("sending CHAP_C, binary challenge size is %zd bytes", + sizeof(challenge_bin)); + ctld_login_send_chap_c(request, id, challenge_bin, + sizeof(challenge_bin)); + ctld_pdu_delete(request); + + /* + * Receive CHAP_N/CHAP_R PDU and authenticate. + */ + log_debugx("waiting for CHAP_N/CHAP_R"); + request = ctld_login_receive_chap_r(cc, cag, id, challenge_bin, + sizeof(challenge_bin), &ca); + + /* + * Yay, authentication succeeded! + */ + log_debugx("authentication succeeded for user \"%s\"; " + "transitioning to Negotiation phase", ca->ca_user); + ctld_login_send_chap_success(request, ca); + ctld_pdu_delete(request); +} + +static void +ctld_login_negotiate_key(struct ctld_pdu *request, const char *name, + const char *value, bool skipped_security, struct ctld_keys *response_keys) +{ + int which, tmp; + struct ctld_connection *cc; + + cc = request->cp_connection; + + if (strcmp(name, "InitiatorName") == 0) { + if (!skipped_security) + log_errx(1, "initiator resent InitiatorName"); + } else if (strcmp(name, "SessionType") == 0) { + if (!skipped_security) + log_errx(1, "initiator resent SessionType"); + } else if (strcmp(name, "TargetName") == 0) { + if (!skipped_security) + log_errx(1, "initiator resent TargetName"); + } else if (strcmp(name, "InitiatorAlias") == 0) { + /* Ignore. */ + } else if (strcmp(name, "HeaderDigest") == 0) { + /* + * We don't handle digests for discovery sessions. + */ + if (cc->cc_session_type == CC_SESSION_TYPE_DISCOVERY) { + log_debugx("discovery session; digests disabled"); + ctld_keys_add(response_keys, name, "None"); + return; + } + + which = ctld_login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("initiator prefers CRC32C " + "for header digest; we'll use it"); + cc->cc_header_digest = CC_DIGEST_CRC32C; + ctld_keys_add(response_keys, name, "CRC32C"); + break; + case 2: + log_debugx("initiator prefers not to do " + "header digest; we'll comply"); + ctld_keys_add(response_keys, name, "None"); + break; + default: + log_warnx("initiator sent unrecognized " + "HeaderDigest value \"%s\"; will use None", value); + ctld_keys_add(response_keys, name, "None"); + break; + } + } else if (strcmp(name, "DataDigest") == 0) { + if (cc->cc_session_type == CC_SESSION_TYPE_DISCOVERY) { + log_debugx("discovery session; digests disabled"); + ctld_keys_add(response_keys, name, "None"); + return; + } + + which = ctld_login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("initiator prefers CRC32C " + "for data digest; we'll use it"); + cc->cc_data_digest = CC_DIGEST_CRC32C; + ctld_keys_add(response_keys, name, "CRC32C"); + break; + case 2: + log_debugx("initiator prefers not to do " + "data digest; we'll comply"); + ctld_keys_add(response_keys, name, "None"); + break; + default: + log_warnx("initiator sent unrecognized " + "DataDigest value \"%s\"; will use None", value); + ctld_keys_add(response_keys, name, "None"); + break; + } + } else if (strcmp(name, "MaxConnections") == 0) { + ctld_keys_add(response_keys, name, "1"); + } else if (strcmp(name, "InitialR2T") == 0) { + ctld_keys_add(response_keys, name, "Yes"); + } else if (strcmp(name, "ImmediateData") == 0) { + ctld_keys_add(response_keys, name, "No"); + } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received invalid " + "MaxRecvDataSegmentLength"); + } + if (tmp > CTLD_MAX_DATA_SEGMENT_LENGTH) { + log_debugx("capping MaxDataSegmentLength from %d to %d", + tmp, CTLD_MAX_DATA_SEGMENT_LENGTH); + tmp = CTLD_MAX_DATA_SEGMENT_LENGTH; + } + cc->cc_max_data_segment_length = tmp; + ctld_keys_add_int(response_keys, name, tmp); + } else if (strcmp(name, "MaxBurstLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received invalid MaxBurstLength"); + } + cc->cc_max_burst_length = tmp; + ctld_keys_add(response_keys, name, value); + } else if (strcmp(name, "FirstBurstLength") == 0) { + ctld_keys_add(response_keys, name, "Irrelevant"); + } else if (strcmp(name, "DefaultTime2Wait") == 0) { + ctld_keys_add(response_keys, name, value); + } else if (strcmp(name, "DefaultTime2Retain") == 0) { + ctld_keys_add(response_keys, name, "0"); + } else if (strcmp(name, "MaxOutstandingR2T") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received invalid MaxOutstandingR2T"); + } + cc->cc_max_outstanding_r2t = tmp; + ctld_keys_add(response_keys, name, value); + } else if (strcmp(name, "DataPDUInOrder") == 0) { + ctld_keys_add(response_keys, name, "Yes"); + } else if (strcmp(name, "DataSequenceInOrder") == 0) { + ctld_keys_add(response_keys, name, "Yes"); + } else if (strcmp(name, "ErrorRecoveryLevel") == 0) { + ctld_keys_add(response_keys, name, "0"); + } else if (strcmp(name, "OFMarker") == 0) { + ctld_keys_add(response_keys, name, "No"); + } else if (strcmp(name, "IFMarker") == 0) { + ctld_keys_add(response_keys, name, "No"); + } else { + log_debugx("unknown key \"%s\"; responding " + "with NotUnderstood", name); + ctld_keys_add(response_keys, name, "NotUnderstood"); + } +} + +static void +ctld_login_negotiate(struct ctld_connection *cc, struct ctld_pdu *request) +{ + struct ctld_pdu *response; + struct iscsi_bhs_login_response *bhslr2; + struct ctld_keys *request_keys, *response_keys; + int i; + bool skipped_security; + + if (request == NULL) { + log_debugx("beginning parameter negotiation; " + "waiting for Login PDU"); + request = ctld_login_receive(cc, false); + skipped_security = false; + } else + skipped_security = true; + + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + + response = ctld_login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->cp_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + bhslr2->bhslr_tsih = htons(0xbadd); + ctld_login_set_csg(response, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + ctld_login_set_nsg(response, BHSLR_STAGE_FULL_FEATURE_PHASE); + response_keys = ctld_keys_new(); + for (i = 0; i < CTLD_KEYS_MAX; i++) { + if (request_keys->ck_names[i] == NULL) + break; + + ctld_login_negotiate_key(request, request_keys->ck_names[i], + request_keys->ck_values[i], skipped_security, + response_keys); + } + + log_debugx("parameter negotiation done; " + "transitioning to Full Feature phase"); + + ctld_keys_save(response_keys, response); + ctld_pdu_send(response); + ctld_pdu_delete(response); + ctld_keys_delete(response_keys); + ctld_pdu_delete(request); + ctld_keys_delete(request_keys); +} + +void +ctld_login(struct ctld_connection *cc) +{ + struct ctld_pdu *request, *response; + struct iscsi_bhs_login_request *bhslr; + struct iscsi_bhs_login_response *bhslr2; + struct ctld_keys *request_keys, *response_keys; + struct ctld_auth_group *cag; + const char *initiator_name, *session_type, *target_name, *auth_method; + char *portal_group_tag; + int rv; + + /* + * Handle the initial Login Request - figure out required authentication + * method and either transition to the next phase, if no authentication + * is required, or call appropriate authentication code. + */ + log_debugx("beginning Login phase; waiting for Login PDU"); + request = ctld_login_receive(cc, true); + bhslr = (struct iscsi_bhs_login_request *)request->cp_bhs; + if (bhslr->bhslr_tsih != 0) { + ctld_login_send_error(request, 0x02, 0x0a); + log_errx(1, "received Login PDU with non-zero TSIH"); + } + + /* + * XXX: Implement the C flag some day. + */ + request_keys = ctld_keys_new(); + ctld_keys_load(request_keys, request); + + assert(cc->cc_initiator_name == NULL); + initiator_name = ctld_keys_find(request_keys, "InitiatorName"); + if (initiator_name == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received Login PDU without InitiatorName"); + } + if (ctld_valid_iscsi_name(initiator_name) == false) { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received Login PDU with invalid InitiatorName"); + } + cc->cc_initiator_name = checked_strdup(initiator_name); + + assert(cc->cc_session_type == CC_SESSION_TYPE_NONE); + session_type = ctld_keys_find(request_keys, "SessionType"); + if (session_type != NULL) { + if (strcmp(session_type, "Normal") == 0) { + cc->cc_session_type = CC_SESSION_TYPE_NORMAL; + } else if (strcmp(session_type, "Discovery") == 0) { + cc->cc_session_type = CC_SESSION_TYPE_DISCOVERY; + } else { + ctld_login_send_error(request, 0x02, 0x00); + log_errx(1, "received Login PDU with invalid " + "SessionType \"%s\"", session_type); + } + } else + cc->cc_session_type = CC_SESSION_TYPE_NORMAL; + + assert(cc->cc_target == NULL); + if (cc->cc_session_type == CC_SESSION_TYPE_NORMAL) { + target_name = ctld_keys_find(request_keys, "TargetName"); + if (target_name == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received Login PDU without TargetName"); + } + + cc->cc_target = + ctld_target_find(cc->cc_portal->cp_portal_group->cpg_conf, + target_name); + if (cc->cc_target == NULL) { + ctld_login_send_error(request, 0x02, 0x03); + log_errx(1, "requested target \"%s\" not found", + target_name); + } + } + + /* + * At this point we know what kind of authentication we need. + */ + if (cc->cc_session_type == CC_SESSION_TYPE_NORMAL) { + cag = cc->cc_target->ct_auth_group; + if (cag->cag_name != NULL) { + log_debugx("initiator \"%s\" requests to connect " + "to target \"%s\"; auth-group \"%s\"", + cc->cc_initiator_name, cc->cc_target->ct_iqn, + cc->cc_target->ct_auth_group->cag_name); + } else { + log_debugx("initiator \"%s\" requests to connect " + "to target \"%s\"", + cc->cc_initiator_name, cc->cc_target->ct_iqn); + } + } else { + assert(cc->cc_session_type == CC_SESSION_TYPE_DISCOVERY); + cag = cc->cc_portal->cp_portal_group->cpg_discovery_auth_group; + if (cag->cag_name != NULL) { + log_debugx("initiator \"%s\" requests " + "discovery session; auth-group \"%s\"", + cc->cc_initiator_name, cag->cag_name); + } else { + log_debugx("initiator \"%s\" requests " + "discovery session", cc->cc_initiator_name); + } + } + + /* + * Let's see if the initiator intends to do any kind of authentication + * at all. + */ + if (ctld_login_csg(request) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { + if (cag->cag_type != CAG_TYPE_NO_AUTHENTICATION) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "initiator skipped the authentication " + "phase, but authentication is required"); + } + + ctld_keys_delete(request_keys); + + log_debugx("initiator skipped the authentication phase, " + "and we don't need it; proceeding with negotiation"); + ctld_login_negotiate(cc, request); + return; + } + + if (cag->cag_type == CAG_TYPE_NO_AUTHENTICATION) { + /* + * Initiator might want to to authenticate, + * but we don't need it. + */ + log_debugx("authentication not required; " + "transitioning to Negotiation phase"); + + if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) + log_warnx("initiator did not set the \"T\" flag; " + "transitioning anyway"); + + response = ctld_login_new_response(request); + bhslr2 = (struct iscsi_bhs_login_response *)response->cp_bhs; + bhslr2->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + ctld_login_set_nsg(response, + BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + response_keys = ctld_keys_new(); + if (cc->cc_session_type == CC_SESSION_TYPE_NORMAL) { + rv = asprintf(&portal_group_tag, "%d", + cc->cc_portal->cp_portal_group->cpg_tag); + if (rv <= 0) + log_err(1, "asprintf"); + ctld_keys_add(response_keys, + "TargetPortalGroupTag", portal_group_tag); + free(portal_group_tag); + if (cc->cc_target->ct_alias != NULL) + ctld_keys_add(response_keys, + "TargetAlias", cc->cc_target->ct_alias); + } + /* + * Required by Linux initiator. + */ + auth_method = ctld_keys_find(request_keys, "AuthMethod"); + if (auth_method != NULL && + ctld_login_list_contains(auth_method, "None")) + ctld_keys_add(response_keys, "AuthMethod", "None"); + + ctld_keys_save(response_keys, response); + ctld_pdu_send(response); + ctld_pdu_delete(response); + ctld_keys_delete(response_keys); + ctld_pdu_delete(request); + ctld_keys_delete(request_keys); + + ctld_login_negotiate(cc, NULL); + return; + } + + log_debugx("CHAP authentication required"); + + auth_method = ctld_keys_find(request_keys, "AuthMethod"); + if (auth_method == NULL) { + ctld_login_send_error(request, 0x02, 0x07); + log_errx(1, "received Login PDU without AuthMethod"); + } + /* + * XXX: This should be Reject, not just a login failure (5.3.2). + */ + if (ctld_login_list_contains(auth_method, "CHAP") == 0) { + ctld_login_send_error(request, 0x02, 0x01); + log_errx(1, "initiator requests unsupported AuthMethod \"%s\" " + "instead of \"CHAP\"", auth_method); + } + + response = ctld_login_new_response(request); + + response_keys = ctld_keys_new(); + ctld_keys_add(response_keys, "AuthMethod", "CHAP"); + if (cc->cc_session_type == CC_SESSION_TYPE_NORMAL) { + rv = asprintf(&portal_group_tag, "%d", + cc->cc_portal->cp_portal_group->cpg_tag); + if (rv <= 0) + log_err(1, "asprintf"); + ctld_keys_add(response_keys, + "TargetPortalGroupTag", portal_group_tag); + free(portal_group_tag); + if (cc->cc_target->ct_alias != NULL) + ctld_keys_add(response_keys, + "TargetAlias", cc->cc_target->ct_alias); + } + ctld_keys_save(response_keys, response); + + ctld_pdu_send(response); + ctld_pdu_delete(response); + ctld_keys_delete(response_keys); + ctld_pdu_delete(request); + ctld_keys_delete(request_keys); + + ctld_login_chap(cc, cag); + + ctld_login_negotiate(cc, NULL); +} diff -urNp freebsd/src/usr.sbin/ctld/parse.y iscsi/usr.sbin/ctld/parse.y --- freebsd/src/usr.sbin/ctld/parse.y 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/parse.y 2013-04-08 20:04:08.000000000 +0200 @@ -0,0 +1,592 @@ +%{ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ctld.h" + +extern FILE *yyin; +extern char *yytext; +extern int lineno; + +static struct ctld_conf *conf = NULL; +static struct ctld_auth_group *auth_group = NULL; +static struct ctld_portal_group *portal_group = NULL; +static struct ctld_target *target = NULL; +static struct ctld_lun *lun = NULL; + +extern void yyrestart(FILE *); + +%} + +%token ALIAS AUTH_GROUP BACKEND BLOCKSIZE CHAP CHAP_MUTUAL CLOSING_BRACKET +%token DEBUG DEVICE_ID DISCOVERY_AUTH_GROUP LISTEN LUN NUM OPENING_BRACKET +%token OPTION PATH PIDFILE PORTAL_GROUP SERIAL SIZE STR TARGET + +%union +{ + uint64_t num; + char *str; +} + +%token NUM +%token STR + +%% + +statements: + | + statements statement + ; + +statement: + debug_statement + | + pidfile_statement + | + auth_group_definition + | + portal_group_definition + | + target_statement + ; + +debug_statement: DEBUG NUM + { + conf->cc_debug = $2; + } + ; + +pidfile_statement: PIDFILE STR + { + if (conf->cc_pidfile_path != NULL) { + log_warnx("pidfile specified more than once"); + free($2); + return (1); + } + conf->cc_pidfile_path = $2; + } + ; + +auth_group_definition: AUTH_GROUP auth_group_name + OPENING_BRACKET auth_group_entries CLOSING_BRACKET + { + auth_group = NULL; + } + ; + +auth_group_name: STR + { + auth_group = ctld_auth_group_new(conf, $1); + free($1); + if (auth_group == NULL) + return (1); + } + ; + +auth_group_entries: + | + auth_group_entries auth_group_entry + ; + +auth_group_entry: + auth_group_chap + | + auth_group_chap_mutual + ; + +auth_group_chap: CHAP STR STR + { + const struct ctld_auth *ca; + + ca = ctld_auth_new_chap(auth_group, $2, $3); + free($2); + free($3); + if (ca == NULL) + return (1); + } + ; + +auth_group_chap_mutual: CHAP_MUTUAL STR STR STR STR + { + const struct ctld_auth *ca; + + ca = ctld_auth_new_chap_mutual(auth_group, $2, $3, $4, $5); + free($2); + free($3); + free($4); + free($5); + if (ca == NULL) + return (1); + } + ; + +portal_group_definition: PORTAL_GROUP portal_group_name + OPENING_BRACKET portal_group_entries CLOSING_BRACKET + { + portal_group = NULL; + } + ; + +portal_group_name: STR + { + portal_group = ctld_portal_group_new(conf, $1); + free($1); + if (portal_group == NULL) + return (1); + } + ; + +portal_group_entries: + | + portal_group_entries portal_group_entry + ; + +portal_group_entry: + portal_group_discovery_auth_group + | + portal_group_listen + ; + +portal_group_discovery_auth_group: DISCOVERY_AUTH_GROUP STR + { + if (portal_group->cpg_discovery_auth_group != NULL) { + log_warnx("discovery-auth-group for portal-group " + "\"%s\" specified more than once", + portal_group->cpg_name); + return (1); + } + portal_group->cpg_discovery_auth_group = + ctld_auth_group_find(conf, $2); + if (portal_group->cpg_discovery_auth_group == NULL) { + log_warnx("unknown discovery-auth-group \"%s\" " + "for portal-group \"%s\"", + $2, portal_group->cpg_name); + return (1); + } + free($2); + } + ; + +portal_group_listen: LISTEN STR + { + int error; + + error = ctld_portal_group_add_listen(portal_group, $2); + free($2); + if (error != 0) + return (1); + } + ; + +target_statement: TARGET target_iqn + OPENING_BRACKET target_entries CLOSING_BRACKET + { + target = NULL; + } + ; + +target_iqn: STR + { + target = ctld_target_new(conf, $1); + free($1); + if (target == NULL) + return (1); + } + ; + +target_entries: + | + target_entries target_entry + ; + +target_entry: + alias_statement + | + auth_group_statement + | + chap_statement + | + chap_mutual_statement + | + portal_group_statement + | + lun_statement + ; + +alias_statement: ALIAS STR + { + if (target->ct_alias != NULL) { + log_warnx("alias for target \"%s\" " + "specified more than once", target->ct_iqn); + return (1); + } + target->ct_alias = $2; + } + ; + +auth_group_statement: AUTH_GROUP STR + { + if (target->ct_auth_group != NULL) { + if (target->ct_auth_group->cag_name != NULL) + log_warnx("auth-group for target \"%s\" " + "specified more than once", target->ct_iqn); + else + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->ct_iqn); + return (1); + } + target->ct_auth_group = ctld_auth_group_find(conf, $2); + if (target->ct_auth_group == NULL) { + log_warnx("unknown auth-group \"%s\" for target " + "\"%s\"", $2, target->ct_iqn); + return (1); + } + free($2); + } + ; + +chap_statement: CHAP STR STR + { + const struct ctld_auth *ca; + + if (target->ct_auth_group != NULL) { + if (target->ct_auth_group->cag_name != NULL) { + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->ct_iqn); + free($2); + free($3); + return (1); + } + } else { + target->ct_auth_group = ctld_auth_group_new(conf, NULL); + if (target->ct_auth_group == NULL) { + free($2); + free($3); + return (1); + } + target->ct_auth_group->cag_target = target; + } + ca = ctld_auth_new_chap(target->ct_auth_group, $2, $3); + free($2); + free($3); + if (ca == NULL) + return (1); + } + ; + +chap_mutual_statement: CHAP_MUTUAL STR STR STR STR + { + const struct ctld_auth *ca; + + if (target->ct_auth_group != NULL) { + if (target->ct_auth_group->cag_name != NULL) { + log_warnx("cannot mix auth-grup with explicit " + "authorisations for target \"%s\"", + target->ct_iqn); + free($2); + free($3); + free($4); + free($5); + return (1); + } + } else { + target->ct_auth_group = ctld_auth_group_new(conf, NULL); + if (target->ct_auth_group == NULL) { + free($2); + free($3); + free($4); + free($5); + return (1); + } + target->ct_auth_group->cag_target = target; + } + ca = ctld_auth_new_chap_mutual(target->ct_auth_group, + $2, $3, $4, $5); + free($2); + free($3); + free($4); + free($5); + if (ca == NULL) + return (1); + } + ; + +portal_group_statement: PORTAL_GROUP STR + { + if (target->ct_portal_group != NULL) { + log_warnx("portal-group for target \"%s\" " + "specified more than once", target->ct_iqn); + free($2); + return (1); + } + target->ct_portal_group = ctld_portal_group_find(conf, $2); + if (target->ct_portal_group == NULL) { + log_warnx("unknown portal-group \"%s\" for target " + "\"%s\"", $2, target->ct_iqn); + free($2); + return (1); + } + free($2); + } + ; + +lun_statement: LUN lun_number + OPENING_BRACKET lun_statement_entries CLOSING_BRACKET + { + lun = NULL; + } + ; + +lun_number: NUM + { + lun = ctld_lun_new(target, $1); + if (lun == NULL) + return (1); + } + ; + +lun_statement_entries: + | + lun_statement_entries lun_statement_entry + ; + +lun_statement_entry: + backend_statement + | + blocksize_statement + | + device_id_statement + | + option_statement + | + path_statement + | + serial_statement + | + size_statement + ; + +backend_statement: BACKEND STR + { + if (lun->cl_backend != NULL) { + log_warnx("backend for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + free($2); + return (1); + } + ctld_lun_set_backend(lun, $2); + free($2); + } + ; + +blocksize_statement: BLOCKSIZE NUM + { + if (lun->cl_blocksize != 0) { + log_warnx("blocksize for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + return (1); + } + ctld_lun_set_blocksize(lun, $2); + } + ; + +device_id_statement: DEVICE_ID STR + { + if (lun->cl_device_id != NULL) { + log_warnx("device_id for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + free($2); + return (1); + } + ctld_lun_set_device_id(lun, $2); + free($2); + } + ; + +option_statement: OPTION STR STR + { + struct ctld_lun_option *clo; + + clo = ctld_lun_option_new(lun, $2, $3); + free($2); + free($3); + if (clo == NULL) + return (1); + } + ; + +path_statement: PATH STR + { + if (lun->cl_path != NULL) { + log_warnx("path for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + free($2); + return (1); + } + ctld_lun_set_path(lun, $2); + free($2); + } + ; + +serial_statement: SERIAL STR + { + if (lun->cl_serial != NULL) { + log_warnx("serial for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + free($2); + return (1); + } + ctld_lun_set_serial(lun, $2); + free($2); + } + ; + +size_statement: SIZE NUM + { + if (lun->cl_size != 0) { + log_warnx("size for lun %d, target \"%s\" " + "specified more than once", + lun->cl_lun, target->ct_iqn); + return (1); + } + ctld_lun_set_size(lun, $2); + } + ; +%% + +void +yyerror(const char *str) +{ + + log_warnx("error in configuration file at line %d near '%s': %s", + lineno, yytext, str); +} + +static void +ctld_check_perms(const char *path) +{ + struct stat sb; + int error; + + error = stat(path, &sb); + if (error != 0) { + log_warn("stat"); + return; + } + if (sb.st_mode & S_IWOTH) { + log_warnx("%s is world-writable", path); + } else if (sb.st_mode & S_IROTH) { + log_warnx("%s is world-readable", path); + } else if (sb.st_mode & S_IXOTH) { + /* + * Ok, this one doesn't matter, but still do it, + * just for consistency. + */ + log_warnx("%s is world-executable", path); + } + + /* + * XXX: Should we also check for owner != 0? + */ +} + +struct ctld_conf * +ctld_conf_new_from_file(const char *path) +{ + struct ctld_conf *cc; + struct ctld_auth_group *cag; + struct ctld_portal_group *cpg; + int error; + + log_debugx("obtaining configuration from %s", path); + + cc = ctld_conf_new(); + + cag = ctld_auth_group_new(cc, "no-authentication"); + cag->cag_type = CAG_TYPE_NO_AUTHENTICATION; + + /* + * Here, the type doesn't really matter, as the group doesn't contain + * any entries and thus will always deny access. + */ + cag = ctld_auth_group_new(cc, "no-access"); + cag->cag_type = CAG_TYPE_CHAP; + + cpg = ctld_portal_group_new(cc, "default"); + ctld_portal_group_add_listen(cpg, "0.0.0.0:3260"); + ctld_portal_group_add_listen(cpg, "[::]:3260"); + + yyin = fopen(path, "r"); + if (yyin == NULL) { + log_warn("unable to open configuration file %s", path); + ctld_conf_delete(cc); + return (NULL); + } + ctld_check_perms(path); + lineno = 0; + conf = cc; + yyrestart(yyin); + error = yyparse(); + conf = NULL; + auth_group = NULL; + portal_group = NULL; + target = NULL; + lun = NULL; + fclose(yyin); + if (error != 0) { + ctld_conf_delete(cc); + return (NULL); + } + + error = ctld_conf_verify(cc); + if (error != 0) { + ctld_conf_delete(cc); + return (NULL); + } + + return (cc); +} diff -urNp freebsd/src/usr.sbin/ctld/pdu.c iscsi/usr.sbin/ctld/pdu.c --- freebsd/src/usr.sbin/ctld/pdu.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/pdu.c 2013-04-19 20:11:39.000000000 +0200 @@ -0,0 +1,206 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ctld.h" +#include "iscsi.h" + +static int +ctld_pdu_ahs_length(const struct ctld_pdu *request) +{ + + return (request->cp_bhs->bhs_total_ahs_len * 4); +} + +static int +ctld_pdu_data_segment_length(const struct ctld_pdu *request) +{ + uint32_t len = 0; + + len += request->cp_bhs->bhs_data_segment_len[0]; + len <<= 8; + len += request->cp_bhs->bhs_data_segment_len[1]; + len <<= 8; + len += request->cp_bhs->bhs_data_segment_len[2]; + + return (len); +} + +static void +ctld_pdu_set_data_segment_length(struct ctld_pdu *response, uint32_t len) +{ + + response->cp_bhs->bhs_data_segment_len[2] = len; + response->cp_bhs->bhs_data_segment_len[1] = len >> 8; + response->cp_bhs->bhs_data_segment_len[0] = len >> 16; +} + +struct ctld_pdu * +ctld_pdu_new(struct ctld_connection *cc) +{ + struct ctld_pdu *cp; + + cp = calloc(sizeof(*cp), 1); + if (cp == NULL) + log_err(1, "calloc"); + + cp->cp_bhs = calloc(sizeof(*cp->cp_bhs), 1); + if (cp->cp_bhs == NULL) + log_err(1, "calloc"); + + cp->cp_connection = cc; + + return (cp); +} + +struct ctld_pdu * +ctld_pdu_new_response(struct ctld_pdu *request) +{ + + return (ctld_pdu_new(request->cp_connection)); +} + +static size_t +ctld_pdu_padding(const struct ctld_pdu *cp) +{ + + if ((cp->cp_data_len % 4) != 0) + return (4 - (cp->cp_data_len % 4)); + + return (0); +} + +static void +ctld_pdu_read(int fd, char *data, size_t len) +{ + ssize_t ret; + + while (len > 0) { + ret = read(fd, data, len); + if (ret < 0) + log_err(1, "read"); + else if (ret == 0) + log_errx(1, "read: connection lost"); + len -= ret; + data += ret; + } +} + +void +ctld_pdu_receive(struct ctld_pdu *request) +{ + size_t len, padding; + char dummy[4]; + + ctld_pdu_read(request->cp_connection->cc_socket, + (char *)request->cp_bhs, sizeof(*request->cp_bhs)); + + len = ctld_pdu_ahs_length(request); + if (len > 0) + log_errx(1, "protocol error: non-empty AHS"); + + len = ctld_pdu_data_segment_length(request); + if (len > 0) { + if (len > request->cp_connection->cc_max_data_segment_length) + log_errx(1, "protocol error: received PDU " + "with DataSegmentLength exceeding %zd", + request->cp_connection->cc_max_data_segment_length); + + request->cp_data_len = len; + request->cp_data = malloc(len); + if (request->cp_data == NULL) + log_err(1, "malloc"); + + ctld_pdu_read(request->cp_connection->cc_socket, + (char *)request->cp_data, request->cp_data_len); + + padding = ctld_pdu_padding(request); + if (padding != 0) { + assert(padding < sizeof(dummy)); + ctld_pdu_read(request->cp_connection->cc_socket, + (char *)dummy, padding); + } + } +} + +void +ctld_pdu_send(struct ctld_pdu *response) +{ + ssize_t ret, total_len; + size_t padding; + uint32_t zero = 0; + struct iovec iov[3]; + int iovcnt; + + ctld_pdu_set_data_segment_length(response, response->cp_data_len); + iov[0].iov_base = response->cp_bhs; + iov[0].iov_len = sizeof(*response->cp_bhs); + total_len = iov[0].iov_len; + iovcnt = 1; + + if (response->cp_data_len > 0) { + iov[1].iov_base = response->cp_data; + iov[1].iov_len = response->cp_data_len; + total_len += iov[1].iov_len; + iovcnt = 2; + + padding = ctld_pdu_padding(response); + if (padding > 0) { + assert(padding < sizeof(zero)); + iov[2].iov_base = &zero; + iov[2].iov_len = padding; + total_len += iov[2].iov_len; + iovcnt = 3; + } + } + + ret = writev(response->cp_connection->cc_socket, iov, iovcnt); + if (ret < 0) + log_err(1, "writev"); + if (ret != total_len) + log_errx(1, "short write"); +} + +void +ctld_pdu_delete(struct ctld_pdu *cp) +{ + + free(cp->cp_data); + free(cp->cp_bhs); + free(cp); +} diff -urNp freebsd/src/usr.sbin/ctld/token.l iscsi/usr.sbin/ctld/token.l --- freebsd/src/usr.sbin/ctld/token.l 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/ctld/token.l 2013-01-25 10:30:40.000000000 +0100 @@ -0,0 +1,80 @@ +%{ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include + +#include "ctld.h" +#include "y.tab.h" + +int depth; +int lineno; + +%} + +%option noinput +%option nounput + +%% +alias { return ALIAS; } +auth-group { return AUTH_GROUP; } +backend { return BACKEND; } +blocksize { return BLOCKSIZE; } +chap { return CHAP; } +chap-mutual { return CHAP_MUTUAL; } +debug { return DEBUG; } +device-id { return DEVICE_ID; } +discovery-auth-group { return DISCOVERY_AUTH_GROUP; } +listen { return LISTEN; } +lun { return LUN; } +option { return OPTION; } +path { return PATH; } +pidfile { return PIDFILE; } +portal-group { return PORTAL_GROUP; } +serial { return SERIAL; } +size { return SIZE; } +target { return TARGET; } +[0-9]+[kKmMgGtTpPeE]? { if (expand_number(yytext, &yylval.num) == 0) + return NUM; + else + return STR; + } +\"[^"]+\" { yylval.str = strndup(yytext + 1, + strlen(yytext) - 2); return STR; } +[a-zA-Z0-9\.\-_/\:\[\]]+ { yylval.str = strdup(yytext); return STR; } +\{ { depth++; return OPENING_BRACKET; } +\} { depth--; return CLOSING_BRACKET; } +#.*$ /* ignore comments */; +\n { lineno++; } +[ \t]+ /* ignore whitespace */; +%% diff -urNp freebsd/src/usr.sbin/iscsid/Makefile iscsi/usr.sbin/iscsid/Makefile --- freebsd/src/usr.sbin/iscsid/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/iscsid/Makefile 2013-04-19 19:56:57.000000000 +0200 @@ -0,0 +1,15 @@ +# $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/initiator2 +MAN= iscsid.8 + +DPADD= ${LIBUTIL} +LDADD= -lutil -lssl + +WARNS= 6 + +.include diff -urNp freebsd/src/usr.sbin/iscsid/discovery.c iscsi/usr.sbin/iscsid/discovery.c --- freebsd/src/usr.sbin/iscsid/discovery.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/iscsid/discovery.c 2013-04-19 19:56:57.000000000 +0200 @@ -0,0 +1,227 @@ +/*- + * 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 "ctl/iscsi.h" + +static struct iscsid_pdu * +iscsid_text_receive(struct iscsid_connection *ic) +{ + struct iscsid_pdu *response; + struct iscsi_bhs_text_response *bhstr; + + response = iscsid_pdu_new(ic); + iscsid_pdu_receive(response); + if (response->ip_bhs->bhs_opcode != ISCSI_BHS_OPCODE_TEXT_RESPONSE) + log_errx(1, "protocol error: received invalid opcode 0x%x", + response->ip_bhs->bhs_opcode); + bhstr = (struct iscsi_bhs_text_response *)response->ip_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->ip_data_len == 0) + log_errx(1, "received Text PDU with empty data segment"); + if (ntohl(bhstr->bhstr_statsn) != ic->ic_statsn + 1) { + log_errx(1, "received Text PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhstr->bhstr_statsn), + ic->ic_statsn + 1); + } + ic->ic_statsn = ntohl(bhstr->bhstr_statsn); + + return (response); +} + +static struct iscsid_pdu * +iscsid_text_new_request(struct iscsid_connection *ic) +{ + struct iscsid_pdu *request; + struct iscsi_bhs_text_request *bhstr; + + request = iscsid_pdu_new(ic); + bhstr = (struct iscsi_bhs_text_request *)request->ip_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(ic->ic_statsn + 1); + + return (request); +} + +static struct iscsid_pdu * +iscsid_logout_receive(struct iscsid_connection *ic) +{ + struct iscsid_pdu *response; + struct iscsi_bhs_logout_response *bhslr; + + response = iscsid_pdu_new(ic); + iscsid_pdu_receive(response); + if (response->ip_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGOUT_RESPONSE) + log_errx(1, "protocol error: received invalid opcode 0x%x", + response->ip_bhs->bhs_opcode); + bhslr = (struct iscsi_bhs_logout_response *)response->ip_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) != ic->ic_statsn + 1) { + log_errx(1, "received Logout PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_statsn), + ic->ic_statsn + 1); + } + ic->ic_statsn = ntohl(bhslr->bhslr_statsn); + + return (response); +} + +static struct iscsid_pdu * +iscsid_logout_new_request(struct iscsid_connection *ic) +{ + struct iscsid_pdu *request; + struct iscsi_bhs_logout_request *bhslr; + + request = iscsid_pdu_new(ic); + bhslr = (struct iscsi_bhs_logout_request *)request->ip_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(ic->ic_statsn + 1); + + return (request); +} + +static void +iscsid_kernel_add(int iscsi_fd, const struct iscsid_connection *ic, + const char *target) +{ + struct iscsi_session_add isa; + int error; + + memset(&isa, 0, sizeof(isa)); + memcpy(&isa.isa_desc, &ic->ic_desc, sizeof(isa)); + strlcpy(isa.isa_desc.isd_target, target, + sizeof(isa.isa_desc.isd_target)); + isa.isa_desc.isd_discovery = 0; + error = ioctl(iscsi_fd, ISCSISADD, &isa); + if (error != 0) + log_warn("ISCSISADD"); + + /* + * XXX: Perhaps we should have a mechanism to retry it? + */ +} + +static void +iscsid_kernel_remove(int iscsi_fd, const struct iscsid_connection *ic) +{ + struct iscsi_session_remove isr; + int error; + + memset(&isr, 0, sizeof(isr)); + isr.isr_desc.isd_id = ic->ic_desc.isd_id; + error = ioctl(iscsi_fd, ISCSISREMOVE, &isr); + if (error != 0) + log_warn("ISCSISREMOVE"); +} + +void +iscsid_discovery(struct iscsid_connection *ic, int iscsi_fd) +{ + struct iscsid_pdu *request, *response; + struct iscsid_keys *request_keys, *response_keys; + int i; + + log_debugx("beginning discovery session"); + request = iscsid_text_new_request(ic); + request_keys = iscsid_keys_new(); + iscsid_keys_add(request_keys, "SendTargets", "All"); + iscsid_keys_save(request_keys, request); + iscsid_keys_delete(request_keys); + request_keys = NULL; + iscsid_pdu_send(request); + iscsid_pdu_delete(request); + request = NULL; + + log_debugx("waiting for Text Response"); + response = iscsid_text_receive(ic); + response_keys = iscsid_keys_new(); + iscsid_keys_load(response_keys, response); + for (i = 0; i < ISCSID_KEYS_MAX; i++) { + if (response_keys->ik_names[i] == NULL) + break; + + if (strcmp(response_keys->ik_names[i], "TargetName") != 0) + continue; + + log_debugx("received target %s", response_keys->ik_values[i]); + /* + * XXX: Validate target name. + */ + iscsid_kernel_add(iscsi_fd, ic, response_keys->ik_values[i]); + } + iscsid_keys_delete(response_keys); + iscsid_pdu_delete(response); + + log_debugx("discovery done; logging out"); + request = iscsid_logout_new_request(ic); + iscsid_pdu_send(request); + request = NULL; + + log_debugx("waiting for Logout Response"); + response = iscsid_logout_receive(ic); + iscsid_pdu_delete(response); + + log_debugx("removing temporary discovery session"); + iscsid_kernel_remove(iscsi_fd, ic); + + log_debugx("discovery session done"); +} diff -urNp freebsd/src/usr.sbin/iscsid/iscsid.8 iscsi/usr.sbin/iscsid/iscsid.8 --- freebsd/src/usr.sbin/iscsid/iscsid.8 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/iscsid/iscsid.8 2013-04-19 19:56:57.000000000 +0200 @@ -0,0 +1,95 @@ +.\" 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 the iSCSI connections. +.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, the 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 freebsd/src/usr.sbin/iscsid/iscsid.c iscsi/usr.sbin/iscsid/iscsid.c --- freebsd/src/usr.sbin/iscsid/iscsid.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/iscsid/iscsid.c 2013-04-19 19:56:57.000000000 +0200 @@ -0,0 +1,353 @@ +/*- + * 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 "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 +iscsid_getaddrinfo(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 iscsid_connection * +iscsid_connection_new(void) +{ + struct iscsid_connection *ic; + + ic = calloc(1, sizeof(*ic)); + if (ic == NULL) + log_err(1, "calloc"); + + ic->ic_max_data_segment_length = 8192; + + return (ic); +} + +static void +iscsid_connect(struct iscsid_connection *ic) +{ + struct addrinfo *from_ai, *to_ai; + const char *from_addr, *to_addr; + int error; + + from_addr = ic->ic_desc.isd_initiator_addr; + to_addr = ic->ic_desc.isd_target_addr; + + if (from_addr[0] != '\0') { + error = iscsid_getaddrinfo(from_addr, &from_ai); + if (error != 0) + log_errx(1, "failed to resolve initiator address %s", + from_addr); + } else { + from_ai = NULL; + } + + error = iscsid_getaddrinfo(to_addr, &to_ai); + if (error != 0) + log_errx(1, "failed to resolve target address %s", to_addr); + + ic->ic_socket = socket(to_ai->ai_family, to_ai->ai_socktype, + to_ai->ai_protocol); + if (ic->ic_socket < 0) + log_err(1, "failed to create socket for %s", from_addr); + if (from_ai != NULL) { + error = bind(ic->ic_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(ic->ic_socket, to_ai->ai_addr, to_ai->ai_addrlen); + if (error != 0) + log_err(1, "failed to connect to %s", to_addr); +} + +static void +iscsid_handoff(struct iscsid_connection *ic, int iscsi_fd) +{ + 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 = ic->ic_desc.isd_id; + idh->idh_socket = ic->ic_socket; + strlcpy(idh->idh_target_alias, ic->ic_desc.isd_target_alias, sizeof(idh->idh_target_alias)); + memcpy(idh->idh_isid, ic->ic_isid, sizeof(idh->idh_isid)); + idh->idh_statsn = ic->ic_statsn; + idh->idh_header_digest = ic->ic_header_digest; + idh->idh_data_digest = ic->ic_data_digest; + idh->idh_initial_r2t = ic->ic_initial_r2t; + idh->idh_immediate_data = ic->ic_immediate_data; + idh->idh_max_data_segment_length = ic->ic_max_data_segment_length; + idh->idh_max_burst_length = ic->ic_max_burst_length; + idh->idh_first_burst_length = ic->ic_first_burst_length; + + error = ioctl(iscsi_fd, ISCSIDHANDOFF, idh); + if (error != 0) + log_err(1, "ISCSIDHANDOFF"); +} + +static void +iscsid_handle_request(int iscsi_fd, struct iscsi_daemon_request *request) +{ + struct iscsid_connection *ic; + + 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); + + if (request->idr_cmd != ISCSI_DAEMON_CMD_LOGIN) + log_errx(1, "received unsupported cmd 0x%x", request->idr_cmd); + + ic = iscsid_connection_new(); + + /* + * XXX: Should we sanitize this somehow? + */ + memcpy(&ic->ic_desc, &request->idr_desc, sizeof(ic->ic_desc)); + + iscsid_connect(ic); + + iscsid_login(ic); + + if (ic->ic_desc.isd_discovery != 0) + iscsid_discovery(ic, iscsi_fd); + else + iscsid_handoff(ic, iscsi_fd); + + log_debugx("nothing more to do; exiting"); + exit (0); +} + +static void +iscsid_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; + bool dont_daemonize = false; + struct pidfh *pidfh; + pid_t pid, otherpid; + const char *pidfile_path = ISCSID_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) + 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); + } + } + + iscsid_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); + iscsid_handle_request(iscsi_fd, request); + } + + return (0); +} diff -urNp freebsd/src/usr.sbin/iscsid/iscsid.h iscsi/usr.sbin/iscsid/iscsid.h --- freebsd/src/usr.sbin/iscsid/iscsid.h 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/iscsid/iscsid.h 2013-04-19 19:56:57.000000000 +0200 @@ -0,0 +1,115 @@ +/*- + * 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 + +#define ISCSID_DEFAULT_PIDFILE "/var/run/iscsid.pid" + +#define IC_DIGEST_NONE 0 +#define IC_DIGEST_CRC32C 1 + +#define IC_MUTUAL_CHALLENGE_LEN 1024 + +struct iscsid_connection { + int ic_socket; + struct iscsi_session_desc ic_desc; + uint8_t ic_isid[6]; + uint32_t ic_statsn; + int ic_header_digest; + int ic_data_digest; + int ic_initial_r2t; + int ic_immediate_data; + size_t ic_max_data_segment_length; + size_t ic_max_burst_length; + size_t ic_first_burst_length; + char ic_mutual_challenge[IC_MUTUAL_CHALLENGE_LEN]; + unsigned char ic_mutual_id; +}; + +struct iscsid_pdu { + struct iscsid_connection *ip_connection; + struct iscsi_bhs *ip_bhs; + char *ip_data; + size_t ip_data_len; +}; + +#define ISCSID_KEYS_MAX 1024 + +struct iscsid_keys { + char *ik_names[ISCSID_KEYS_MAX]; + char *ik_values[ISCSID_KEYS_MAX]; + char *ik_data; + size_t ik_data_len; +}; + +struct iscsid_keys *iscsid_keys_new(void); +void iscsid_keys_delete(struct iscsid_keys *ik); +void iscsid_keys_load(struct iscsid_keys *ik, + const struct iscsid_pdu *cp); +void iscsid_keys_save(struct iscsid_keys *ik, + struct iscsid_pdu *cp); +const char *iscsid_keys_find(struct iscsid_keys *ik, + const char *name); +int iscsid_keys_find_int(struct iscsid_keys *ik, + const char *name); +void iscsid_keys_add(struct iscsid_keys *ik, + const char *name, const char *value); +void iscsid_keys_add_int(struct iscsid_keys *ik, + const char *name, int value); + +struct iscsid_pdu *iscsid_pdu_new(struct iscsid_connection *ic); +struct iscsid_pdu *iscsid_pdu_new_response(struct iscsid_pdu *request); +void iscsid_pdu_receive(struct iscsid_pdu *request); +void iscsid_pdu_send(struct iscsid_pdu *response); +void iscsid_pdu_delete(struct iscsid_pdu *ip); + +void log_init(int level); +void log_err(int, const char *, ...) + __dead2 __printf0like(2, 3); +void log_errx(int, const char *, ...) + __dead2 __printf0like(2, 3); +void log_warn(const char *, ...) __printf0like(1, 2); +void log_warnx(const char *, ...) __printflike(1, 2); +void log_debugx(const char *, ...) __printf0like(1, 2); + +void iscsid_login(struct iscsid_connection *ic); + +void iscsid_discovery(struct iscsid_connection *ic, + int iscsi_fd); + +char *checked_strdup(const char *); + +#endif /* !ISCSID_H */ diff -urNp freebsd/src/usr.sbin/iscsid/keys.c iscsi/usr.sbin/iscsid/keys.c --- freebsd/src/usr.sbin/iscsid/keys.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/iscsid/keys.c 2013-04-19 19:56:57.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 "iscsid.h" + +struct iscsid_keys * +iscsid_keys_new(void) +{ + struct iscsid_keys *ik; + + ik = calloc(sizeof(*ik), 1); + if (ik == NULL) + log_err(1, "calloc"); + + return (ik); +} + +void +iscsid_keys_delete(struct iscsid_keys *ik) +{ + + free(ik->ik_data); + free(ik); +} + +void +iscsid_keys_load(struct iscsid_keys *ik, const struct iscsid_pdu *ip) +{ + int i; + char *pair; + size_t pair_len; + + if (ip->ip_data_len == 0) + log_errx(1, "protocol error: empty data segment"); + + if (ip->ip_data[ip->ip_data_len - 1] != '\0') + log_errx(1, "protocol error: key not NULL-terminated\n"); + + assert(ik->ik_data == NULL); + ik->ik_data_len = ip->ip_data_len; + ik->ik_data = malloc(ik->ik_data_len); + if (ik->ik_data == NULL) + log_err(1, "malloc"); + memcpy(ik->ik_data, ip->ip_data, ik->ik_data_len); + + /* + * XXX: Review this carefully. + */ + pair = ik->ik_data; + for (i = 0;; i++) { + if (i >= ISCSID_KEYS_MAX) + log_errx(1, "too many keys received"); + + pair_len = strlen(pair); + + ik->ik_values[i] = pair; + ik->ik_names[i] = strsep(&ik->ik_values[i], "="); + if (ik->ik_names[i] == NULL || ik->ik_values[i] == NULL) + log_errx(1, "malformed keys"); + log_debugx("key received: \"%s=%s\"", + ik->ik_names[i], ik->ik_values[i]); + + pair += pair_len + 1; /* +1 to skip the terminating '\0'. */ + if (pair == ik->ik_data + ik->ik_data_len) + break; + assert(pair < ik->ik_data + ik->ik_data_len); + } +} + +void +iscsid_keys_save(struct iscsid_keys *ik, struct iscsid_pdu *ip) +{ + char *data; + size_t len; + int i; + + /* + * XXX: Not particularly efficient. + */ + len = 0; + for (i = 0; i < ISCSID_KEYS_MAX; i++) { + if (ik->ik_names[i] == NULL) + break; + /* + * +1 for '=', +1 for '\0'. + */ + len += strlen(ik->ik_names[i]) + strlen(ik->ik_values[i]) + 2; + } + + if (len == 0) + return; + + data = malloc(len); + if (data == NULL) + log_err(1, "malloc"); + + ip->ip_data = data; + ip->ip_data_len = len; + + for (i = 0; i < ISCSID_KEYS_MAX; i++) { + if (ik->ik_names[i] == NULL) + break; + data += + sprintf(data, "%s=%s", ik->ik_names[i], ik->ik_values[i]); + data += 1; /* for '\0'. */ + } +} + +const char * +iscsid_keys_find(struct iscsid_keys *ik, 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 < ISCSID_KEYS_MAX; i++) { + if (ik->ik_names[i] == NULL) + return (NULL); + if (strcmp(ik->ik_names[i], name) == 0) + return (ik->ik_values[i]); + } + return (NULL); +} + +int +iscsid_keys_find_int(struct iscsid_keys *ik, const char *name) +{ + const char *str; + char *endptr; + int num; + + str = iscsid_keys_find(ik, 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 +iscsid_keys_add(struct iscsid_keys *ik, 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 < ISCSID_KEYS_MAX; i++) { + if (ik->ik_names[i] == NULL) { + ik->ik_names[i] = checked_strdup(name); + ik->ik_values[i] = checked_strdup(value); + return; + } + } + log_errx(1, "too many keys"); +} + +void +iscsid_keys_add_int(struct iscsid_keys *ik, const char *name, int value) +{ + char *str; + int ret; + + ret = asprintf(&str, "%d", value); + if (ret <= 0) + log_err(1, "asprintf"); + + iscsid_keys_add(ik, name, str); + free(str); +} diff -urNp freebsd/src/usr.sbin/iscsid/log.c iscsi/usr.sbin/iscsid/log.c --- freebsd/src/usr.sbin/iscsid/log.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/iscsid/log.c 2013-04-19 19:56:57.000000000 +0200 @@ -0,0 +1,145 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include +#include +#include + +#include "iscsid.h" + +static int log_level = 0; + +void +log_init(int level) +{ + + log_level = level; + openlog(getprogname(), LOG_NDELAY | LOG_PID, LOG_DAEMON); +} + +void +log_err(int eval, const char *fmt, ...) +{ + va_list ap, ap2; + int saved_errno; + + saved_errno = errno; + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": %s\n", strerror(saved_errno)); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_CRIT, fmt, ap2); + va_end(ap2); + + exit(eval); +} + +void +log_errx(int eval, const char *fmt, ...) +{ + va_list ap, ap2; + + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_CRIT, fmt, ap2); + va_end(ap2); + + exit(eval); +} + +void +log_warn(const char *fmt, ...) +{ + va_list ap, ap2; + int saved_errno; + + saved_errno = errno; + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": %s\n", strerror(saved_errno)); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_WARNING, fmt, ap2); + va_end(ap2); +} + +void +log_warnx(const char *fmt, ...) +{ + va_list ap, ap2; + + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_WARNING, fmt, ap2); + va_end(ap2); +} + +void +log_debugx(const char *fmt, ...) +{ + va_list ap, ap2; + + if (log_level == 0) + return; + + va_copy(ap2, ap); + va_start(ap, fmt); + fprintf(stderr, "%s: ", getprogname()); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + + va_start(ap2, fmt); + vsyslog(LOG_DEBUG, fmt, ap2); + va_end(ap2); +} diff -urNp freebsd/src/usr.sbin/iscsid/login.c iscsi/usr.sbin/iscsid/login.c --- freebsd/src/usr.sbin/iscsid/login.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/iscsid/login.c 2013-04-19 19:56:57.000000000 +0200 @@ -0,0 +1,756 @@ +/*- + * 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 "ctl/iscsi.h" + +static int +iscsid_login_nsg(const struct iscsid_pdu *response) +{ + struct iscsi_bhs_login_response *bhslr; + + bhslr = (struct iscsi_bhs_login_response *)response->ip_bhs; + + return (bhslr->bhslr_flags & 0x03); +} + +static void +iscsid_login_set_nsg(struct iscsid_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->ip_bhs; + + bhslr->bhslr_flags &= 0xFC; + bhslr->bhslr_flags |= nsg; +} + +static void +iscsid_login_set_csg(struct iscsid_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->ip_bhs; + + bhslr->bhslr_flags &= 0xF3; + bhslr->bhslr_flags |= csg << 2; +} + +static struct iscsid_pdu * +iscsid_login_receive(struct iscsid_connection *ic, bool initial) +{ + struct iscsid_pdu *response; + struct iscsi_bhs_login_response *bhslr; + + response = iscsid_pdu_new(ic); + iscsid_pdu_receive(response); + if (response->ip_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) { + log_errx(1, "protocol error: received invalid opcode 0x%x", + response->ip_bhs->bhs_opcode); + } + bhslr = (struct iscsi_bhs_login_response *)response->ip_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->ip_data_len == 0) + log_errx(1, "received Login PDU with empty data segment"); +#endif + if (initial == false && + ntohl(bhslr->bhslr_statsn) != ic->ic_statsn + 1) { + log_errx(1, "received Login PDU with wrong StatSN: " + "is %d, should be %d", ntohl(bhslr->bhslr_statsn), + ic->ic_statsn + 1); + } + ic->ic_statsn = ntohl(bhslr->bhslr_statsn); + + return (response); +} + +static struct iscsid_pdu * +iscsid_login_new_request(struct iscsid_connection *ic) +{ + struct iscsid_pdu *request; + struct iscsi_bhs_login_request *bhslr; + + request = iscsid_pdu_new(ic); + bhslr = (struct iscsi_bhs_login_request *)request->ip_bhs; + bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST | + ISCSI_BHS_OPCODE_IMMEDIATE; + bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT; + iscsid_login_set_csg(request, BHSLR_STAGE_SECURITY_NEGOTIATION); + iscsid_login_set_nsg(request, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + memcpy(bhslr->bhslr_isid, &ic->ic_isid, sizeof(bhslr->bhslr_isid)); + bhslr->bhslr_initiator_task_tag = 0; + bhslr->bhslr_cmdsn = 0; + bhslr->bhslr_expstatsn = htonl(ic->ic_statsn + 1); + + return (request); +} + +static int +iscsid_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 +iscsid_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 +iscsid_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 = iscsid_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 * +iscsid_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 +iscsid_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 +iscsid_login_negotiate_key(struct iscsid_connection *ic, const char *name, + const char *value) +{ + int which, tmp; + + if (strcmp(value, "Irrelevant") == 0) { + /* Ignore. */ + } else if (strcmp(name, "TargetAlias") == 0) { + strlcpy(ic->ic_desc.isd_target_alias, value, sizeof(ic->ic_desc.isd_target_alias)); + } else if (strcmp(name, "HeaderDigest") == 0) { + which = iscsid_login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("target prefers CRC32C " + "for header digest; we'll use it"); + ic->ic_header_digest = IC_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 = iscsid_login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + log_debugx("target prefers CRC32C " + "for data digest; we'll use it"); + ic->ic_data_digest = IC_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) + ic->ic_initial_r2t = 1; + else + ic->ic_initial_r2t = 0; + } else if (strcmp(name, "ImmediateData") == 0) { + if (strcmp(value, "Yes") == 0) + ic->ic_immediate_data = 1; + else + ic->ic_immediate_data = 0; + } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) + log_errx(1, "received invalid " + "MaxRecvDataSegmentLength"); + ic->ic_max_data_segment_length = tmp; + } else if (strcmp(name, "MaxBurstLength") == 0) { + if (ic->ic_immediate_data != 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) + log_errx(1, "received invalid MaxBurstLength"); + ic->ic_max_burst_length = tmp; + } + } else if (strcmp(name, "FirstBurstLength") == 0) { + if (ic->ic_immediate_data != 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) + log_errx(1, "received invalid FirstBurstLength"); + ic->ic_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 { + log_debugx("unknown key \"%s\"; ignoring", name); + } +} + +static void +iscsid_login_negotiate(struct iscsid_connection *ic) +{ + struct iscsid_pdu *request, *response; + struct iscsid_keys *request_keys, *response_keys; + struct iscsi_bhs_login_response *bhslr; + int i; + + log_debugx("beginning parameter negotiation"); + request = iscsid_login_new_request(ic); + iscsid_login_set_csg(request, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + iscsid_login_set_nsg(request, BHSLR_STAGE_FULL_FEATURE_PHASE); + request_keys = iscsid_keys_new(); + iscsid_keys_add(request_keys, "InitiatorAlias", + ic->ic_desc.isd_initiator_alias); + iscsid_keys_add(request_keys, "HeaderDigest", "None"); + iscsid_keys_add(request_keys, "DataDigest", "None"); + iscsid_keys_add(request_keys, "MaxConnections", "1"); + iscsid_keys_add(request_keys, "InitialR2T", "Yes"); + iscsid_keys_add(request_keys, "ImmediateData", "No"); + iscsid_keys_add_int(request_keys, "MaxRecvDataSegmentLength", + ISCSI_MAX_DATA_SEGMENT_LENGTH); +#ifdef notyet + /* + * We don't need those two, and reporting nonsensical value ("1") + * breaks COMSTAR. + */ + iscsid_keys_add(request_keys, "MaxBurstLength", "1"); + iscsid_keys_add(request_keys, "FirstBurstLength", "1"); +#endif + iscsid_keys_add(request_keys, "DefaultTime2Wait", "0"); + iscsid_keys_add(request_keys, "DefaultTime2Retain", "0"); + iscsid_keys_add(request_keys, "MaxOutstandingR2T", "1"); + iscsid_keys_add(request_keys, "DataPDUInOrder", "Yes"); + iscsid_keys_add(request_keys, "DataSequenceInOrder", "Yes"); + iscsid_keys_add(request_keys, "ErrorRecoveryLevel", "0"); + iscsid_keys_save(request_keys, request); + iscsid_keys_delete(request_keys); + request_keys = NULL; + iscsid_pdu_send(request); + iscsid_pdu_delete(request); + request = NULL; + + response = iscsid_login_receive(ic, false); + response_keys = iscsid_keys_new(); + iscsid_keys_load(response_keys, response); + for (i = 0; i < ISCSID_KEYS_MAX; i++) { + if (response_keys->ik_names[i] == NULL) + break; + + iscsid_login_negotiate_key(ic, + response_keys->ik_names[i], response_keys->ik_values[i]); + } + + bhslr = (struct iscsi_bhs_login_response *)response->ip_bhs; + if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) == 0) + log_warnx("received final login response " + "without the \"T\" flag"); + else if (iscsid_login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE) + log_warnx("received final login response with wrong NSG 0x%x", + iscsid_login_nsg(response)); + + log_debugx("parameter negotiation done; " + "transitioning to Full Feature phase"); + + iscsid_keys_delete(response_keys); + iscsid_pdu_delete(response); +} + +static void +iscsid_login_send_chap_a(struct iscsid_connection *ic) +{ + struct iscsid_pdu *request; + struct iscsid_keys *request_keys; + + request = iscsid_login_new_request(ic); + request_keys = iscsid_keys_new(); + iscsid_keys_add(request_keys, "CHAP_A", "5"); + iscsid_keys_save(request_keys, request); + iscsid_keys_delete(request_keys); + iscsid_pdu_send(request); + iscsid_pdu_delete(request); +} + +static void +iscsid_login_send_chap_r(struct iscsid_pdu *response) +{ + struct iscsid_connection *ic; + struct iscsid_pdu *request; + struct iscsid_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'. + */ + + ic = response->ip_connection; + + response_keys = iscsid_keys_new(); + iscsid_keys_load(response_keys, response); + + /* + * First, compute the response. + */ + chap_a = iscsid_keys_find(response_keys, "CHAP_A"); + if (chap_a == NULL) + log_errx(1, "received CHAP packet without CHAP_A"); + chap_c = iscsid_keys_find(response_keys, "CHAP_C"); + if (chap_c == NULL) + log_errx(1, "received CHAP packet without CHAP_C"); + chap_i = iscsid_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 = iscsid_login_hex2bin(chap_c, &challenge, &challenge_len); + if (error != 0) + log_errx(1, "received CHAP packet with malformed CHAP_C"); + iscsid_login_compute_md5(id, ic->ic_desc.isd_secret, + challenge, challenge_len, response_bin, sizeof(response_bin)); + free(challenge); + chap_r = iscsid_login_bin2hex(response_bin, sizeof(response_bin)); + + iscsid_keys_delete(response_keys); + + request = iscsid_login_new_request(ic); + request_keys = iscsid_keys_new(); + iscsid_keys_add(request_keys, "CHAP_N", ic->ic_desc.isd_user); + iscsid_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 (ic->ic_desc.isd_mutual_user[0] != '\0') { + log_debugx("requesting mutual authentication; " + "binary challenge size is %zd bytes", + sizeof(ic->ic_mutual_challenge)); + + rv = RAND_bytes(ic->ic_mutual_challenge, + sizeof(ic->ic_mutual_challenge)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + rv = RAND_bytes(&ic->ic_mutual_id, sizeof(ic->ic_mutual_id)); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + mutual_chap_c = iscsid_login_bin2hex(ic->ic_mutual_challenge, + sizeof(ic->ic_mutual_challenge)); + snprintf(mutual_chap_i, sizeof(mutual_chap_i), + "%d", ic->ic_mutual_id); + iscsid_keys_add(request_keys, "CHAP_I", mutual_chap_i); + iscsid_keys_add(request_keys, "CHAP_C", mutual_chap_c); + free(mutual_chap_c); + } + + iscsid_keys_save(request_keys, request); + iscsid_keys_delete(request_keys); + iscsid_pdu_send(request); + iscsid_pdu_delete(request); +} + +static void +iscsid_login_verify_mutual(const struct iscsid_pdu *response) +{ + struct iscsid_connection *ic; + struct iscsid_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; + + ic = response->ip_connection; + + response_keys = iscsid_keys_new(); + iscsid_keys_load(response_keys, response); + + chap_n = iscsid_keys_find(response_keys, "CHAP_N"); + if (chap_n == NULL) + log_errx(1, "received CHAP Response PDU without CHAP_N"); + chap_r = iscsid_keys_find(response_keys, "CHAP_R"); + if (chap_r == NULL) + log_errx(1, "received CHAP Response PDU without CHAP_R"); + error = iscsid_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, ic->ic_desc.isd_mutual_user) != 0) + log_errx(1, "mutual CHAP authentication failed: wrong user"); + + iscsid_login_compute_md5(ic->ic_mutual_id, + ic->ic_desc.isd_mutual_secret, ic->ic_mutual_challenge, + sizeof(ic->ic_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"); + + iscsid_keys_delete(response_keys); + free(response_bin); + + log_debugx("mutual CHAP authentication succeeded"); +} + +static void +iscsid_login_chap(struct iscsid_connection *ic) +{ + struct iscsid_pdu *response; + + log_debugx("beginning CHAP authentication; sending CHAP_A"); + iscsid_login_send_chap_a(ic); + + log_debugx("waiting for CHAP_A/CHAP_C/CHAP_I"); + response = iscsid_login_receive(ic, false); + + log_debugx("sending CHAP_N/CHAP_R"); + iscsid_login_send_chap_r(response); + iscsid_pdu_delete(response); + + /* + * XXX: Make sure this is not susceptible to MITM. + */ + + log_debugx("waiting for CHAP result"); + response = iscsid_login_receive(ic, false); + if (ic->ic_desc.isd_mutual_user[0] != '\0') + iscsid_login_verify_mutual(response); + iscsid_pdu_delete(response); + + log_debugx("CHAP authentication done"); +} + +static void +iscsid_login_create_isid(struct iscsid_connection *ic) +{ + int rv; + + /* + * RFC 3720, 10.12.5: 10b, "Random" ISID. + * + */ + ic->ic_isid[0] = 0x80; + + rv = RAND_bytes(&ic->ic_isid[1], 3); + if (rv != 1) { + log_errx(1, "RAND_bytes failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + } +} + +void +iscsid_login(struct iscsid_connection *ic) +{ + struct iscsid_pdu *request, *response; + struct iscsid_keys *request_keys, *response_keys; + struct iscsi_bhs_login_request *bhslr; + struct iscsi_bhs_login_response *bhslr2; + const char *auth_method; + + iscsid_login_create_isid(ic); + + log_debugx("beginning Login phase; sending Login PDU"); + request = iscsid_login_new_request(ic); + + bhslr = (struct iscsi_bhs_login_request *)request->ip_bhs; + bhslr->bhslr_flags |= BHSLR_FLAGS_TRANSIT; + + request_keys = iscsid_keys_new(); + if (ic->ic_desc.isd_user[0] == '\0') + iscsid_keys_add(request_keys, "AuthMethod", "None"); + else + iscsid_keys_add(request_keys, "AuthMethod", "CHAP,None"); + iscsid_keys_add(request_keys, + "InitiatorName", ic->ic_desc.isd_initiator); + if (ic->ic_desc.isd_discovery == 0) { + iscsid_keys_add(request_keys, "SessionType", "Normal"); + iscsid_keys_add(request_keys, + "TargetName", ic->ic_desc.isd_target); + } else { + iscsid_keys_add(request_keys, "SessionType", "Discovery"); + } + iscsid_keys_save(request_keys, request); + iscsid_keys_delete(request_keys); + iscsid_pdu_send(request); + iscsid_pdu_delete(request); + + response = iscsid_login_receive(ic, true); + bhslr2 = (struct iscsi_bhs_login_response *)response->ip_bhs; + if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 && + iscsid_login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { + log_debugx("target requested transition " + "to operational negotiation"); + + iscsid_pdu_delete(response); + iscsid_login_negotiate(ic); + return; + } + + response_keys = iscsid_keys_new(); + iscsid_keys_load(response_keys, response); + auth_method = iscsid_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"); + iscsid_keys_delete(response_keys); + iscsid_pdu_delete(response); + iscsid_login_negotiate(ic); + return; + } + + if (strcmp(auth_method, "CHAP") != 0) + log_errx(1, "received response " + "with unsupported AuthMethod \"%s\"", auth_method); + + + if (ic->ic_desc.isd_user[0] == '\0' || + ic->ic_desc.isd_secret[0] == '\0') + log_errx(1, "target requests CHAP authentication, but we don't " + "have user and secret"); + + iscsid_keys_delete(response_keys); + response_keys = NULL; + iscsid_pdu_delete(response); + response = NULL; + + iscsid_login_chap(ic); + iscsid_login_negotiate(ic); +} diff -urNp freebsd/src/usr.sbin/iscsid/pdu.c iscsi/usr.sbin/iscsid/pdu.c --- freebsd/src/usr.sbin/iscsid/pdu.c 1970-01-01 01:00:00.000000000 +0100 +++ iscsi/usr.sbin/iscsid/pdu.c 2013-04-20 17:36:13.000000000 +0200 @@ -0,0 +1,207 @@ +/*- + * 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 "ctl/iscsi.h" + +static int +iscsid_pdu_ahs_length(const struct iscsid_pdu *request) +{ + + return (request->ip_bhs->bhs_total_ahs_len * 4); +} + +static int +iscsid_pdu_data_segment_length(const struct iscsid_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 +iscsid_pdu_set_data_segment_length(struct iscsid_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; +} + +struct iscsid_pdu * +iscsid_pdu_new(struct iscsid_connection *cc) +{ + struct iscsid_pdu *ip; + + ip = calloc(sizeof(*ip), 1); + if (ip == NULL) + log_err(1, "calloc"); + + ip->ip_bhs = calloc(sizeof(*ip->ip_bhs), 1); + if (ip->ip_bhs == NULL) + log_err(1, "calloc"); + + ip->ip_connection = cc; + + return (ip); +} + +struct iscsid_pdu * +iscsid_pdu_new_response(struct iscsid_pdu *request) +{ + + return (iscsid_pdu_new(request->ip_connection)); +} + +static size_t +iscsid_pdu_padding(const struct iscsid_pdu *ip) +{ + + if ((ip->ip_data_len % 4) != 0) + return (4 - (ip->ip_data_len % 4)); + + return (0); +} + +static void +iscsid_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 +iscsid_pdu_receive(struct iscsid_pdu *request) +{ + size_t len, padding; + char dummy[4]; + + iscsid_pdu_read(request->ip_connection->ic_socket, + (char *)request->ip_bhs, sizeof(*request->ip_bhs)); + + len = iscsid_pdu_ahs_length(request); + if (len > 0) + log_errx(1, "protocol error: non-empty AHS"); + + len = iscsid_pdu_data_segment_length(request); + 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); + } + + request->ip_data_len = len; + request->ip_data = malloc(len); + if (request->ip_data == NULL) + log_err(1, "malloc"); + + iscsid_pdu_read(request->ip_connection->ic_socket, + (char *)request->ip_data, request->ip_data_len); + + padding = iscsid_pdu_padding(request); + if (padding != 0) { + assert(padding < sizeof(dummy)); + iscsid_pdu_read(request->ip_connection->ic_socket, + (char *)dummy, padding); + } + } +} + +void +iscsid_pdu_send(struct iscsid_pdu *response) +{ + ssize_t ret, total_len; + size_t padding; + uint32_t zero = 0; + struct iovec iov[3]; + int iovcnt; + + iscsid_pdu_set_data_segment_length(response, response->ip_data_len); + iov[0].iov_base = response->ip_bhs; + iov[0].iov_len = sizeof(*response->ip_bhs); + total_len = iov[0].iov_len; + iovcnt = 1; + + if (response->ip_data_len > 0) { + iov[1].iov_base = response->ip_data; + iov[1].iov_len = response->ip_data_len; + total_len += iov[1].iov_len; + iovcnt = 2; + + padding = iscsid_pdu_padding(response); + if (padding > 0) { + assert(padding < sizeof(zero)); + iov[2].iov_base = &zero; + iov[2].iov_len = padding; + total_len += iov[2].iov_len; + iovcnt = 3; + } + } + + ret = writev(response->ip_connection->ic_socket, iov, iovcnt); + if (ret < 0) + log_err(1, "writev"); + if (ret != total_len) + log_errx(1, "short write"); +} + +void +iscsid_pdu_delete(struct iscsid_pdu *ip) +{ + + free(ip->ip_data); + free(ip->ip_bhs); + free(ip); +}