Index: head/sys/dev/iscsi/iscsi.c =================================================================== --- head/sys/dev/iscsi/iscsi.c (revision 366952) +++ head/sys/dev/iscsi/iscsi.c (revision 366953) @@ -1,2624 +1,2625 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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. * */ #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 #ifdef ICL_KERNEL_PROXY #include #endif #ifdef ICL_KERNEL_PROXY FEATURE(iscsi_kernel_proxy, "iSCSI initiator built with ICL_KERNEL_PROXY"); #endif /* * XXX: This is global so the iscsi_unload() can access it. * Think about how to do this properly. */ static struct iscsi_softc *sc; SYSCTL_NODE(_kern, OID_AUTO, iscsi, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "iSCSI initiator"); static int debug = 1; SYSCTL_INT(_kern_iscsi, OID_AUTO, debug, CTLFLAG_RWTUN, &debug, 0, "Enable debug messages"); static int ping_timeout = 5; SYSCTL_INT(_kern_iscsi, OID_AUTO, ping_timeout, CTLFLAG_RWTUN, &ping_timeout, 0, "Timeout for ping (NOP-Out) requests, in seconds"); static int iscsid_timeout = 60; SYSCTL_INT(_kern_iscsi, OID_AUTO, iscsid_timeout, CTLFLAG_RWTUN, &iscsid_timeout, 0, "Time to wait for iscsid(8) to handle reconnection, in seconds"); static int login_timeout = 60; SYSCTL_INT(_kern_iscsi, OID_AUTO, login_timeout, CTLFLAG_RWTUN, &login_timeout, 0, "Time to wait for iscsid(8) to finish Login Phase, in seconds"); static int maxtags = 255; SYSCTL_INT(_kern_iscsi, OID_AUTO, maxtags, CTLFLAG_RWTUN, &maxtags, 0, "Max number of IO requests queued"); static int fail_on_disconnection = 0; SYSCTL_INT(_kern_iscsi, OID_AUTO, fail_on_disconnection, CTLFLAG_RWTUN, &fail_on_disconnection, 0, "Destroy CAM SIM on connection failure"); static int fail_on_shutdown = 1; SYSCTL_INT(_kern_iscsi, OID_AUTO, fail_on_shutdown, CTLFLAG_RWTUN, &fail_on_shutdown, 0, "Fail disconnected sessions on shutdown"); static MALLOC_DEFINE(M_ISCSI, "iSCSI", "iSCSI initiator"); static uma_zone_t iscsi_outstanding_zone; #define CONN_SESSION(X) ((struct iscsi_session *)X->ic_prv0) #define PDU_SESSION(X) (CONN_SESSION(X->ip_conn)) #define ISCSI_DEBUG(X, ...) \ do { \ if (debug > 1) \ printf("%s: " X "\n", __func__, ## __VA_ARGS__);\ } while (0) #define ISCSI_WARN(X, ...) \ do { \ if (debug > 0) { \ printf("WARNING: %s: " X "\n", \ __func__, ## __VA_ARGS__); \ } \ } while (0) #define ISCSI_SESSION_DEBUG(S, X, ...) \ do { \ if (debug > 1) { \ printf("%s: %s (%s): " X "\n", \ __func__, S->is_conf.isc_target_addr, \ S->is_conf.isc_target, ## __VA_ARGS__); \ } \ } while (0) #define ISCSI_SESSION_WARN(S, X, ...) \ do { \ if (debug > 0) { \ printf("WARNING: %s (%s): " X "\n", \ S->is_conf.isc_target_addr, \ S->is_conf.isc_target, ## __VA_ARGS__); \ } \ } while (0) #define ISCSI_SESSION_LOCK(X) mtx_lock(&X->is_lock) #define ISCSI_SESSION_UNLOCK(X) mtx_unlock(&X->is_lock) #define ISCSI_SESSION_LOCK_ASSERT(X) mtx_assert(&X->is_lock, MA_OWNED) #define ISCSI_SESSION_LOCK_ASSERT_NOT(X) mtx_assert(&X->is_lock, MA_NOTOWNED) static int iscsi_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int mode, struct thread *td); static struct cdevsw iscsi_cdevsw = { .d_version = D_VERSION, .d_ioctl = iscsi_ioctl, .d_name = "iscsi", }; static void iscsi_pdu_queue_locked(struct icl_pdu *request); static void iscsi_pdu_queue(struct icl_pdu *request); static void iscsi_pdu_update_statsn(const struct icl_pdu *response); static void iscsi_pdu_handle_nop_in(struct icl_pdu *response); static void iscsi_pdu_handle_scsi_response(struct icl_pdu *response); static void iscsi_pdu_handle_task_response(struct icl_pdu *response); static void iscsi_pdu_handle_data_in(struct icl_pdu *response); static void iscsi_pdu_handle_logout_response(struct icl_pdu *response); static void iscsi_pdu_handle_r2t(struct icl_pdu *response); static void iscsi_pdu_handle_async_message(struct icl_pdu *response); static void iscsi_pdu_handle_reject(struct icl_pdu *response); static void iscsi_session_reconnect(struct iscsi_session *is); static void iscsi_session_terminate(struct iscsi_session *is); static void iscsi_action(struct cam_sim *sim, union ccb *ccb); static void iscsi_poll(struct cam_sim *sim); static struct iscsi_outstanding *iscsi_outstanding_find(struct iscsi_session *is, uint32_t initiator_task_tag); static struct iscsi_outstanding *iscsi_outstanding_add(struct iscsi_session *is, struct icl_pdu *request, union ccb *ccb, uint32_t *initiator_task_tagp); static void iscsi_outstanding_remove(struct iscsi_session *is, struct iscsi_outstanding *io); static bool iscsi_pdu_prepare(struct icl_pdu *request) { struct iscsi_session *is; struct iscsi_bhs_scsi_command *bhssc; is = PDU_SESSION(request); ISCSI_SESSION_LOCK_ASSERT(is); /* * We're only using fields common for all the request * (initiator -> target) PDUs. */ bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; /* * Data-Out PDU does not contain CmdSN. */ if (bhssc->bhssc_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_OUT) { if (ISCSI_SNGT(is->is_cmdsn, is->is_maxcmdsn) && (bhssc->bhssc_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) { /* * Current MaxCmdSN prevents us from sending any more * SCSI Command PDUs to the target; postpone the PDU. * It will get resent by either iscsi_pdu_queue(), * or by maintenance thread. */ #if 0 ISCSI_SESSION_DEBUG(is, "postponing send, CmdSN %u, " "ExpCmdSN %u, MaxCmdSN %u, opcode 0x%x", is->is_cmdsn, is->is_expcmdsn, is->is_maxcmdsn, bhssc->bhssc_opcode); #endif return (true); } bhssc->bhssc_cmdsn = htonl(is->is_cmdsn); if ((bhssc->bhssc_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0) is->is_cmdsn++; } bhssc->bhssc_expstatsn = htonl(is->is_statsn + 1); return (false); } static void iscsi_session_send_postponed(struct iscsi_session *is) { struct icl_pdu *request; bool postpone; ISCSI_SESSION_LOCK_ASSERT(is); if (STAILQ_EMPTY(&is->is_postponed)) return; while ((request = STAILQ_FIRST(&is->is_postponed)) != NULL) { postpone = iscsi_pdu_prepare(request); if (postpone) return; STAILQ_REMOVE_HEAD(&is->is_postponed, ip_next); icl_pdu_queue(request); } xpt_release_simq(is->is_sim, 1); } static void iscsi_pdu_queue_locked(struct icl_pdu *request) { struct iscsi_session *is; bool postpone; is = PDU_SESSION(request); ISCSI_SESSION_LOCK_ASSERT(is); iscsi_session_send_postponed(is); postpone = iscsi_pdu_prepare(request); if (postpone) { if (STAILQ_EMPTY(&is->is_postponed)) xpt_freeze_simq(is->is_sim, 1); STAILQ_INSERT_TAIL(&is->is_postponed, request, ip_next); return; } icl_pdu_queue(request); } static void iscsi_pdu_queue(struct icl_pdu *request) { struct iscsi_session *is; is = PDU_SESSION(request); ISCSI_SESSION_LOCK(is); iscsi_pdu_queue_locked(request); ISCSI_SESSION_UNLOCK(is); } static void iscsi_session_logout(struct iscsi_session *is) { struct icl_pdu *request; struct iscsi_bhs_logout_request *bhslr; request = icl_pdu_new(is->is_conn, M_NOWAIT); if (request == NULL) return; bhslr = (struct iscsi_bhs_logout_request *)request->ip_bhs; bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGOUT_REQUEST; bhslr->bhslr_reason = BHSLR_REASON_CLOSE_SESSION; iscsi_pdu_queue_locked(request); } static void iscsi_session_terminate_task(struct iscsi_session *is, struct iscsi_outstanding *io, cam_status status) { ISCSI_SESSION_LOCK_ASSERT(is); if (io->io_ccb != NULL) { io->io_ccb->ccb_h.status &= ~(CAM_SIM_QUEUED | CAM_STATUS_MASK); io->io_ccb->ccb_h.status |= status; if ((io->io_ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { io->io_ccb->ccb_h.status |= CAM_DEV_QFRZN; xpt_freeze_devq(io->io_ccb->ccb_h.path, 1); ISCSI_SESSION_DEBUG(is, "freezing devq"); } xpt_done(io->io_ccb); } iscsi_outstanding_remove(is, io); } static void iscsi_session_terminate_tasks(struct iscsi_session *is, cam_status status) { struct iscsi_outstanding *io, *tmp; ISCSI_SESSION_LOCK_ASSERT(is); TAILQ_FOREACH_SAFE(io, &is->is_outstanding, io_next, tmp) { iscsi_session_terminate_task(is, io, status); } } static void iscsi_session_cleanup(struct iscsi_session *is, bool destroy_sim) { struct icl_pdu *pdu; ISCSI_SESSION_LOCK_ASSERT(is); /* * Don't queue any new PDUs. */ if (is->is_sim != NULL && is->is_simq_frozen == false) { ISCSI_SESSION_DEBUG(is, "freezing"); xpt_freeze_simq(is->is_sim, 1); is->is_simq_frozen = true; } /* * Remove postponed PDUs. */ if (!STAILQ_EMPTY(&is->is_postponed)) xpt_release_simq(is->is_sim, 1); while ((pdu = STAILQ_FIRST(&is->is_postponed)) != NULL) { STAILQ_REMOVE_HEAD(&is->is_postponed, ip_next); icl_pdu_free(pdu); } if (destroy_sim == false) { /* * Terminate SCSI tasks, asking CAM to requeue them. */ iscsi_session_terminate_tasks(is, CAM_REQUEUE_REQ); return; } iscsi_session_terminate_tasks(is, CAM_DEV_NOT_THERE); if (is->is_sim == NULL) return; ISCSI_SESSION_DEBUG(is, "deregistering SIM"); xpt_async(AC_LOST_DEVICE, is->is_path, NULL); if (is->is_simq_frozen) { is->is_simq_frozen = false; xpt_release_simq(is->is_sim, 1); } xpt_free_path(is->is_path); is->is_path = NULL; xpt_bus_deregister(cam_sim_path(is->is_sim)); cam_sim_free(is->is_sim, TRUE /*free_devq*/); is->is_sim = NULL; is->is_devq = NULL; } static void iscsi_maintenance_thread_reconnect(struct iscsi_session *is) { icl_conn_close(is->is_conn); ISCSI_SESSION_LOCK(is); is->is_connected = false; is->is_reconnecting = false; is->is_login_phase = false; #ifdef ICL_KERNEL_PROXY if (is->is_login_pdu != NULL) { icl_pdu_free(is->is_login_pdu); is->is_login_pdu = NULL; } cv_signal(&is->is_login_cv); #endif if (fail_on_disconnection) { ISCSI_SESSION_DEBUG(is, "connection failed, destroying devices"); iscsi_session_cleanup(is, true); } else { iscsi_session_cleanup(is, false); } KASSERT(TAILQ_EMPTY(&is->is_outstanding), ("destroying session with active tasks")); KASSERT(STAILQ_EMPTY(&is->is_postponed), ("destroying session with postponed PDUs")); if (is->is_conf.isc_enable == 0 && is->is_conf.isc_discovery == 0) { ISCSI_SESSION_UNLOCK(is); return; } /* * Request immediate reconnection from iscsid(8). */ //ISCSI_SESSION_DEBUG(is, "waking up iscsid(8)"); is->is_waiting_for_iscsid = true; strlcpy(is->is_reason, "Waiting for iscsid(8)", sizeof(is->is_reason)); is->is_timeout = 0; ISCSI_SESSION_UNLOCK(is); cv_signal(&is->is_softc->sc_cv); } static void iscsi_maintenance_thread_terminate(struct iscsi_session *is) { struct iscsi_softc *sc; sc = is->is_softc; sx_xlock(&sc->sc_lock); TAILQ_REMOVE(&sc->sc_sessions, is, is_next); sx_xunlock(&sc->sc_lock); icl_conn_close(is->is_conn); callout_drain(&is->is_callout); ISCSI_SESSION_LOCK(is); KASSERT(is->is_terminating, ("is_terminating == false")); #ifdef ICL_KERNEL_PROXY if (is->is_login_pdu != NULL) { icl_pdu_free(is->is_login_pdu); is->is_login_pdu = NULL; } cv_signal(&is->is_login_cv); #endif iscsi_session_cleanup(is, true); KASSERT(TAILQ_EMPTY(&is->is_outstanding), ("destroying session with active tasks")); KASSERT(STAILQ_EMPTY(&is->is_postponed), ("destroying session with postponed PDUs")); ISCSI_SESSION_UNLOCK(is); icl_conn_free(is->is_conn); mtx_destroy(&is->is_lock); cv_destroy(&is->is_maintenance_cv); #ifdef ICL_KERNEL_PROXY cv_destroy(&is->is_login_cv); #endif ISCSI_SESSION_DEBUG(is, "terminated"); free(is, M_ISCSI); /* * The iscsi_unload() routine might be waiting. */ cv_signal(&sc->sc_cv); } static void iscsi_maintenance_thread(void *arg) { struct iscsi_session *is = arg; ISCSI_SESSION_LOCK(is); for (;;) { if (is->is_reconnecting == false && is->is_terminating == false && (STAILQ_EMPTY(&is->is_postponed) || ISCSI_SNGT(is->is_cmdsn, is->is_maxcmdsn))) cv_wait(&is->is_maintenance_cv, &is->is_lock); /* Terminate supersedes reconnect. */ if (is->is_terminating) { ISCSI_SESSION_UNLOCK(is); iscsi_maintenance_thread_terminate(is); kthread_exit(); return; } if (is->is_reconnecting) { ISCSI_SESSION_UNLOCK(is); iscsi_maintenance_thread_reconnect(is); ISCSI_SESSION_LOCK(is); continue; } iscsi_session_send_postponed(is); } ISCSI_SESSION_UNLOCK(is); } static void iscsi_session_reconnect(struct iscsi_session *is) { /* * XXX: We can't use locking here, because * it's being called from various contexts. * Hope it doesn't break anything. */ if (is->is_reconnecting) return; is->is_reconnecting = true; cv_signal(&is->is_maintenance_cv); } static void iscsi_session_terminate(struct iscsi_session *is) { if (is->is_terminating) return; is->is_terminating = true; #if 0 iscsi_session_logout(is); #endif cv_signal(&is->is_maintenance_cv); } static void iscsi_callout(void *context) { struct icl_pdu *request; struct iscsi_bhs_nop_out *bhsno; struct iscsi_session *is; bool reconnect_needed = false; is = context; ISCSI_SESSION_LOCK(is); if (is->is_terminating) { ISCSI_SESSION_UNLOCK(is); return; } callout_schedule(&is->is_callout, 1 * hz); if (is->is_conf.isc_enable == 0) goto out; is->is_timeout++; if (is->is_waiting_for_iscsid) { if (iscsid_timeout > 0 && is->is_timeout > iscsid_timeout) { ISCSI_SESSION_WARN(is, "timed out waiting for iscsid(8) " "for %d seconds; reconnecting", is->is_timeout); reconnect_needed = true; } goto out; } if (is->is_login_phase) { if (login_timeout > 0 && is->is_timeout > login_timeout) { ISCSI_SESSION_WARN(is, "login timed out after %d seconds; " "reconnecting", is->is_timeout); reconnect_needed = true; } goto out; } if (ping_timeout <= 0) { /* * Pings are disabled. Don't send NOP-Out in this case. * Reset the timeout, to avoid triggering reconnection, * should the user decide to reenable them. */ is->is_timeout = 0; goto out; } if (is->is_timeout >= ping_timeout) { ISCSI_SESSION_WARN(is, "no ping reply (NOP-In) after %d seconds; " "reconnecting", ping_timeout); reconnect_needed = true; goto out; } ISCSI_SESSION_UNLOCK(is); /* * If the ping was reset less than one second ago - which means * that we've received some PDU during the last second - assume * the traffic flows correctly and don't bother sending a NOP-Out. * * (It's 2 - one for one second, and one for incrementing is_timeout * earlier in this routine.) */ if (is->is_timeout < 2) return; request = icl_pdu_new(is->is_conn, M_NOWAIT); if (request == NULL) { ISCSI_SESSION_WARN(is, "failed to allocate PDU"); 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_target_transfer_tag = 0xffffffff; iscsi_pdu_queue(request); return; out: if (is->is_terminating) { ISCSI_SESSION_UNLOCK(is); return; } ISCSI_SESSION_UNLOCK(is); if (reconnect_needed) iscsi_session_reconnect(is); } static void iscsi_pdu_update_statsn(const struct icl_pdu *response) { const struct iscsi_bhs_data_in *bhsdi; struct iscsi_session *is; uint32_t expcmdsn, maxcmdsn, statsn; is = PDU_SESSION(response); ISCSI_SESSION_LOCK_ASSERT(is); /* * We're only using fields common for all the response * (target -> initiator) PDUs. */ bhsdi = (const struct iscsi_bhs_data_in *)response->ip_bhs; /* * Ok, I lied. In case of Data-In, "The fields StatSN, Status, * and Residual Count only have meaningful content if the S bit * is set to 1", so we also need to check the bit specific for * Data-In PDU. */ if (bhsdi->bhsdi_opcode != ISCSI_BHS_OPCODE_SCSI_DATA_IN || (bhsdi->bhsdi_flags & BHSDI_FLAGS_S) != 0) { statsn = ntohl(bhsdi->bhsdi_statsn); if (statsn != is->is_statsn && statsn != (is->is_statsn + 1)) { /* XXX: This is normal situation for MCS */ ISCSI_SESSION_WARN(is, "PDU 0x%x StatSN %u != " "session ExpStatSN %u (or + 1); reconnecting", bhsdi->bhsdi_opcode, statsn, is->is_statsn); iscsi_session_reconnect(is); } if (ISCSI_SNGT(statsn, is->is_statsn)) is->is_statsn = statsn; } expcmdsn = ntohl(bhsdi->bhsdi_expcmdsn); maxcmdsn = ntohl(bhsdi->bhsdi_maxcmdsn); if (ISCSI_SNLT(maxcmdsn + 1, expcmdsn)) { ISCSI_SESSION_DEBUG(is, "PDU MaxCmdSN %u + 1 < PDU ExpCmdSN %u; ignoring", maxcmdsn, expcmdsn); } else { if (ISCSI_SNGT(maxcmdsn, is->is_maxcmdsn)) { is->is_maxcmdsn = maxcmdsn; /* * Command window increased; kick the maintanance thread * to send out postponed commands. */ if (!STAILQ_EMPTY(&is->is_postponed)) cv_signal(&is->is_maintenance_cv); } else if (ISCSI_SNLT(maxcmdsn, is->is_maxcmdsn)) { /* XXX: This is normal situation for MCS */ ISCSI_SESSION_DEBUG(is, "PDU MaxCmdSN %u < session MaxCmdSN %u; ignoring", maxcmdsn, is->is_maxcmdsn); } if (ISCSI_SNGT(expcmdsn, is->is_expcmdsn)) { is->is_expcmdsn = expcmdsn; } else if (ISCSI_SNLT(expcmdsn, is->is_expcmdsn)) { /* XXX: This is normal situation for MCS */ ISCSI_SESSION_DEBUG(is, "PDU ExpCmdSN %u < session ExpCmdSN %u; ignoring", expcmdsn, is->is_expcmdsn); } } /* * Every incoming PDU - not just NOP-In - resets the ping timer. * The purpose of the timeout is to reset the connection when it stalls; * we don't want this to happen when NOP-In or NOP-Out ends up delayed * in some queue. */ is->is_timeout = 0; } static void iscsi_receive_callback(struct icl_pdu *response) { struct iscsi_session *is; is = PDU_SESSION(response); ISCSI_SESSION_LOCK(is); iscsi_pdu_update_statsn(response); #ifdef ICL_KERNEL_PROXY if (is->is_login_phase) { if (is->is_login_pdu == NULL) is->is_login_pdu = response; else icl_pdu_free(response); ISCSI_SESSION_UNLOCK(is); cv_signal(&is->is_login_cv); return; } #endif /* * 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); ISCSI_SESSION_UNLOCK(is); break; case ISCSI_BHS_OPCODE_SCSI_RESPONSE: iscsi_pdu_handle_scsi_response(response); /* Session lock dropped inside. */ ISCSI_SESSION_LOCK_ASSERT_NOT(is); break; case ISCSI_BHS_OPCODE_TASK_RESPONSE: iscsi_pdu_handle_task_response(response); ISCSI_SESSION_UNLOCK(is); break; case ISCSI_BHS_OPCODE_SCSI_DATA_IN: iscsi_pdu_handle_data_in(response); /* Session lock dropped inside. */ ISCSI_SESSION_LOCK_ASSERT_NOT(is); break; case ISCSI_BHS_OPCODE_LOGOUT_RESPONSE: iscsi_pdu_handle_logout_response(response); ISCSI_SESSION_UNLOCK(is); break; case ISCSI_BHS_OPCODE_R2T: iscsi_pdu_handle_r2t(response); ISCSI_SESSION_UNLOCK(is); break; case ISCSI_BHS_OPCODE_ASYNC_MESSAGE: iscsi_pdu_handle_async_message(response); ISCSI_SESSION_UNLOCK(is); break; case ISCSI_BHS_OPCODE_REJECT: iscsi_pdu_handle_reject(response); ISCSI_SESSION_UNLOCK(is); break; default: ISCSI_SESSION_WARN(is, "received PDU with unsupported " "opcode 0x%x; reconnecting", response->ip_bhs->bhs_opcode); iscsi_session_reconnect(is); ISCSI_SESSION_UNLOCK(is); icl_pdu_free(response); } } static void iscsi_error_callback(struct icl_conn *ic) { struct iscsi_session *is; is = CONN_SESSION(ic); ISCSI_SESSION_WARN(is, "connection error; reconnecting"); iscsi_session_reconnect(is); } static void iscsi_pdu_handle_nop_in(struct icl_pdu *response) { struct iscsi_session *is; struct iscsi_bhs_nop_out *bhsno; struct iscsi_bhs_nop_in *bhsni; struct icl_pdu *request; void *data = NULL; size_t datasize; int error; is = PDU_SESSION(response); bhsni = (struct iscsi_bhs_nop_in *)response->ip_bhs; if (bhsni->bhsni_target_transfer_tag == 0xffffffff) { /* * Nothing to do; iscsi_pdu_update_statsn() already * zeroed the timeout. */ icl_pdu_free(response); return; } datasize = icl_pdu_data_segment_length(response); if (datasize > 0) { data = malloc(datasize, M_ISCSI, M_NOWAIT | M_ZERO); if (data == NULL) { ISCSI_SESSION_WARN(is, "failed to allocate memory; " "reconnecting"); icl_pdu_free(response); iscsi_session_reconnect(is); return; } icl_pdu_get_data(response, 0, data, datasize); } request = icl_pdu_new(response->ip_conn, M_NOWAIT); if (request == NULL) { ISCSI_SESSION_WARN(is, "failed to allocate memory; " "reconnecting"); free(data, M_ISCSI); icl_pdu_free(response); iscsi_session_reconnect(is); 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; bhsno->bhsno_target_transfer_tag = bhsni->bhsni_target_transfer_tag; if (datasize > 0) { error = icl_pdu_append_data(request, data, datasize, M_NOWAIT); if (error != 0) { ISCSI_SESSION_WARN(is, "failed to allocate memory; " "reconnecting"); free(data, M_ISCSI); icl_pdu_free(request); icl_pdu_free(response); iscsi_session_reconnect(is); return; } free(data, M_ISCSI); } icl_pdu_free(response); iscsi_pdu_queue_locked(request); } static void iscsi_pdu_handle_scsi_response(struct icl_pdu *response) { struct iscsi_bhs_scsi_response *bhssr; struct iscsi_outstanding *io; struct iscsi_session *is; union ccb *ccb; struct ccb_scsiio *csio; size_t data_segment_len, received; uint16_t sense_len; uint32_t resid; is = PDU_SESSION(response); bhssr = (struct iscsi_bhs_scsi_response *)response->ip_bhs; io = iscsi_outstanding_find(is, bhssr->bhssr_initiator_task_tag); if (io == NULL || io->io_ccb == NULL) { ISCSI_SESSION_WARN(is, "bad itt 0x%x", bhssr->bhssr_initiator_task_tag); icl_pdu_free(response); iscsi_session_reconnect(is); ISCSI_SESSION_UNLOCK(is); return; } ccb = io->io_ccb; /* * With iSER, after getting good response we can be sure * that all the data has been successfully transferred. */ if (is->is_conn->ic_iser) { resid = ntohl(bhssr->bhssr_residual_count); if (bhssr->bhssr_flags & BHSSR_FLAGS_RESIDUAL_UNDERFLOW) { io->io_received = ccb->csio.dxfer_len - resid; } else if (bhssr->bhssr_flags & BHSSR_FLAGS_RESIDUAL_OVERFLOW) { ISCSI_SESSION_WARN(is, "overflow: target indicates %d", resid); } else { io->io_received = ccb->csio.dxfer_len; } } received = io->io_received; iscsi_outstanding_remove(is, io); ISCSI_SESSION_UNLOCK(is); if (bhssr->bhssr_response != BHSSR_RESPONSE_COMMAND_COMPLETED) { ISCSI_SESSION_WARN(is, "service response 0x%x", bhssr->bhssr_response); if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ISCSI_SESSION_DEBUG(is, "freezing devq"); } ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN; } else if (bhssr->bhssr_status == 0) { ccb->ccb_h.status = CAM_REQ_CMP; } else { if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ISCSI_SESSION_DEBUG(is, "freezing devq"); } ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_DEV_QFRZN; ccb->csio.scsi_status = bhssr->bhssr_status; } csio = &ccb->csio; data_segment_len = icl_pdu_data_segment_length(response); if (data_segment_len > 0) { if (data_segment_len < sizeof(sense_len)) { ISCSI_SESSION_WARN(is, "truncated data segment (%zd bytes)", data_segment_len); if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ISCSI_SESSION_DEBUG(is, "freezing devq"); } ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN; goto out; } icl_pdu_get_data(response, 0, &sense_len, sizeof(sense_len)); sense_len = ntohs(sense_len); #if 0 ISCSI_SESSION_DEBUG(is, "sense_len %d, data len %zd", sense_len, data_segment_len); #endif if (sizeof(sense_len) + sense_len > data_segment_len) { ISCSI_SESSION_WARN(is, "truncated data segment " "(%zd bytes, should be %zd)", data_segment_len, sizeof(sense_len) + sense_len); if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ISCSI_SESSION_DEBUG(is, "freezing devq"); } ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN; goto out; } else if (sizeof(sense_len) + sense_len < data_segment_len) ISCSI_SESSION_WARN(is, "oversize data segment " "(%zd bytes, should be %zd)", data_segment_len, sizeof(sense_len) + sense_len); if (sense_len > csio->sense_len) { ISCSI_SESSION_DEBUG(is, "truncating sense from %d to %d", sense_len, csio->sense_len); sense_len = csio->sense_len; } icl_pdu_get_data(response, sizeof(sense_len), &csio->sense_data, sense_len); csio->sense_resid = csio->sense_len - sense_len; ccb->ccb_h.status |= CAM_AUTOSNS_VALID; } out: if (bhssr->bhssr_flags & BHSSR_FLAGS_RESIDUAL_UNDERFLOW) csio->resid = ntohl(bhssr->bhssr_residual_count); if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { KASSERT(received <= csio->dxfer_len, ("received > csio->dxfer_len")); if (received < csio->dxfer_len) { if (csio->resid != csio->dxfer_len - received) { ISCSI_SESSION_WARN(is, "underflow mismatch: " "target indicates %d, we calculated %zd", csio->resid, csio->dxfer_len - received); } csio->resid = csio->dxfer_len - received; } } xpt_done(ccb); icl_pdu_free(response); } static void iscsi_pdu_handle_task_response(struct icl_pdu *response) { struct iscsi_bhs_task_management_response *bhstmr; struct iscsi_outstanding *io, *aio; struct iscsi_session *is; is = PDU_SESSION(response); bhstmr = (struct iscsi_bhs_task_management_response *)response->ip_bhs; io = iscsi_outstanding_find(is, bhstmr->bhstmr_initiator_task_tag); if (io == NULL || io->io_ccb != NULL) { ISCSI_SESSION_WARN(is, "bad itt 0x%x", bhstmr->bhstmr_initiator_task_tag); icl_pdu_free(response); iscsi_session_reconnect(is); return; } if (bhstmr->bhstmr_response != BHSTMR_RESPONSE_FUNCTION_COMPLETE) { ISCSI_SESSION_WARN(is, "task response 0x%x", bhstmr->bhstmr_response); } else { aio = iscsi_outstanding_find(is, io->io_datasn); if (aio != NULL && aio->io_ccb != NULL) iscsi_session_terminate_task(is, aio, CAM_REQ_ABORTED); } iscsi_outstanding_remove(is, io); icl_pdu_free(response); } static void iscsi_pdu_handle_data_in(struct icl_pdu *response) { struct iscsi_bhs_data_in *bhsdi; struct iscsi_outstanding *io; struct iscsi_session *is; union ccb *ccb; struct ccb_scsiio *csio; size_t data_segment_len, received, oreceived; is = PDU_SESSION(response); bhsdi = (struct iscsi_bhs_data_in *)response->ip_bhs; io = iscsi_outstanding_find(is, bhsdi->bhsdi_initiator_task_tag); if (io == NULL || io->io_ccb == NULL) { ISCSI_SESSION_WARN(is, "bad itt 0x%x", bhsdi->bhsdi_initiator_task_tag); icl_pdu_free(response); iscsi_session_reconnect(is); ISCSI_SESSION_UNLOCK(is); return; } data_segment_len = icl_pdu_data_segment_length(response); if (data_segment_len == 0) { /* * "The sending of 0 length data segments should be avoided, * but initiators and targets MUST be able to properly receive * 0 length data segments." */ ISCSI_SESSION_UNLOCK(is); icl_pdu_free(response); return; } /* * We need to track this for security reasons - without it, malicious target * could respond to SCSI READ without sending Data-In PDUs, which would result * in read operation on the initiator side returning random kernel data. */ if (ntohl(bhsdi->bhsdi_buffer_offset) != io->io_received) { ISCSI_SESSION_WARN(is, "data out of order; expected offset %zd, got %zd", io->io_received, (size_t)ntohl(bhsdi->bhsdi_buffer_offset)); icl_pdu_free(response); iscsi_session_reconnect(is); ISCSI_SESSION_UNLOCK(is); return; } ccb = io->io_ccb; csio = &ccb->csio; if (io->io_received + data_segment_len > csio->dxfer_len) { ISCSI_SESSION_WARN(is, "oversize data segment (%zd bytes " "at offset %zd, buffer is %d)", data_segment_len, io->io_received, csio->dxfer_len); icl_pdu_free(response); iscsi_session_reconnect(is); ISCSI_SESSION_UNLOCK(is); return; } oreceived = io->io_received; io->io_received += data_segment_len; received = io->io_received; if ((bhsdi->bhsdi_flags & BHSDI_FLAGS_S) != 0) iscsi_outstanding_remove(is, io); ISCSI_SESSION_UNLOCK(is); icl_pdu_get_data(response, 0, csio->data_ptr + oreceived, data_segment_len); /* * XXX: Check DataSN. * XXX: Check F. */ if ((bhsdi->bhsdi_flags & BHSDI_FLAGS_S) == 0) { /* * Nothing more to do. */ icl_pdu_free(response); return; } //ISCSI_SESSION_DEBUG(is, "got S flag; status 0x%x", bhsdi->bhsdi_status); if (bhsdi->bhsdi_status == 0) { ccb->ccb_h.status = CAM_REQ_CMP; } else { if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ISCSI_SESSION_DEBUG(is, "freezing devq"); } ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | CAM_DEV_QFRZN; csio->scsi_status = bhsdi->bhsdi_status; } if ((csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) { KASSERT(received <= csio->dxfer_len, ("received > csio->dxfer_len")); if (received < csio->dxfer_len) { csio->resid = ntohl(bhsdi->bhsdi_residual_count); if (csio->resid != csio->dxfer_len - received) { ISCSI_SESSION_WARN(is, "underflow mismatch: " "target indicates %d, we calculated %zd", csio->resid, csio->dxfer_len - received); } csio->resid = csio->dxfer_len - received; } } xpt_done(ccb); icl_pdu_free(response); } static void iscsi_pdu_handle_logout_response(struct icl_pdu *response) { ISCSI_SESSION_DEBUG(PDU_SESSION(response), "logout response"); icl_pdu_free(response); } static void iscsi_pdu_handle_r2t(struct icl_pdu *response) { struct icl_pdu *request; struct iscsi_session *is; struct iscsi_bhs_r2t *bhsr2t; struct iscsi_bhs_data_out *bhsdo; struct iscsi_outstanding *io; struct ccb_scsiio *csio; size_t off, len, total_len; int error; is = PDU_SESSION(response); bhsr2t = (struct iscsi_bhs_r2t *)response->ip_bhs; io = iscsi_outstanding_find(is, bhsr2t->bhsr2t_initiator_task_tag); if (io == NULL || io->io_ccb == NULL) { ISCSI_SESSION_WARN(is, "bad itt 0x%x; reconnecting", bhsr2t->bhsr2t_initiator_task_tag); icl_pdu_free(response); iscsi_session_reconnect(is); return; } csio = &io->io_ccb->csio; if ((csio->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_OUT) { ISCSI_SESSION_WARN(is, "received R2T for read command; reconnecting"); icl_pdu_free(response); iscsi_session_reconnect(is); return; } /* * XXX: Verify R2TSN. */ io->io_datasn = 0; off = ntohl(bhsr2t->bhsr2t_buffer_offset); if (off > csio->dxfer_len) { ISCSI_SESSION_WARN(is, "target requested invalid offset " "%zd, buffer is is %d; reconnecting", off, csio->dxfer_len); icl_pdu_free(response); iscsi_session_reconnect(is); return; } total_len = ntohl(bhsr2t->bhsr2t_desired_data_transfer_length); if (total_len == 0 || total_len > csio->dxfer_len) { ISCSI_SESSION_WARN(is, "target requested invalid length " "%zd, buffer is %d; reconnecting", total_len, csio->dxfer_len); icl_pdu_free(response); iscsi_session_reconnect(is); return; } //ISCSI_SESSION_DEBUG(is, "r2t; off %zd, len %zd", off, total_len); for (;;) { len = total_len; if (len > is->is_max_send_data_segment_length) len = is->is_max_send_data_segment_length; if (off + len > csio->dxfer_len) { ISCSI_SESSION_WARN(is, "target requested invalid " "length/offset %zd, buffer is %d; reconnecting", off + len, csio->dxfer_len); icl_pdu_free(response); iscsi_session_reconnect(is); return; } request = icl_pdu_new(response->ip_conn, M_NOWAIT); if (request == NULL) { icl_pdu_free(response); iscsi_session_reconnect(is); return; } bhsdo = (struct iscsi_bhs_data_out *)request->ip_bhs; bhsdo->bhsdo_opcode = ISCSI_BHS_OPCODE_SCSI_DATA_OUT; bhsdo->bhsdo_lun = bhsr2t->bhsr2t_lun; bhsdo->bhsdo_initiator_task_tag = bhsr2t->bhsr2t_initiator_task_tag; bhsdo->bhsdo_target_transfer_tag = bhsr2t->bhsr2t_target_transfer_tag; bhsdo->bhsdo_datasn = htonl(io->io_datasn++); bhsdo->bhsdo_buffer_offset = htonl(off); error = icl_pdu_append_data(request, csio->data_ptr + off, len, M_NOWAIT); if (error != 0) { ISCSI_SESSION_WARN(is, "failed to allocate memory; " "reconnecting"); icl_pdu_free(request); icl_pdu_free(response); iscsi_session_reconnect(is); return; } off += len; total_len -= len; if (total_len == 0) { bhsdo->bhsdo_flags |= BHSDO_FLAGS_F; //ISCSI_SESSION_DEBUG(is, "setting F, off %zd", off); } else { //ISCSI_SESSION_DEBUG(is, "not finished, off %zd", off); } iscsi_pdu_queue_locked(request); if (total_len == 0) break; } icl_pdu_free(response); } static void iscsi_pdu_handle_async_message(struct icl_pdu *response) { struct iscsi_bhs_asynchronous_message *bhsam; struct iscsi_session *is; is = PDU_SESSION(response); bhsam = (struct iscsi_bhs_asynchronous_message *)response->ip_bhs; switch (bhsam->bhsam_async_event) { case BHSAM_EVENT_TARGET_REQUESTS_LOGOUT: ISCSI_SESSION_WARN(is, "target requests logout; removing session"); iscsi_session_logout(is); iscsi_session_terminate(is); break; case BHSAM_EVENT_TARGET_TERMINATES_CONNECTION: ISCSI_SESSION_WARN(is, "target indicates it will drop the connection"); break; case BHSAM_EVENT_TARGET_TERMINATES_SESSION: ISCSI_SESSION_WARN(is, "target indicates it will drop the session"); break; default: /* * XXX: Technically, we're obligated to also handle * parameter renegotiation. */ ISCSI_SESSION_WARN(is, "ignoring AsyncEvent %d", bhsam->bhsam_async_event); break; } icl_pdu_free(response); } static void iscsi_pdu_handle_reject(struct icl_pdu *response) { struct iscsi_bhs_reject *bhsr; struct iscsi_session *is; is = PDU_SESSION(response); bhsr = (struct iscsi_bhs_reject *)response->ip_bhs; ISCSI_SESSION_WARN(is, "received Reject PDU, reason 0x%x; protocol error?", bhsr->bhsr_reason); icl_pdu_free(response); } static int iscsi_ioctl_daemon_wait(struct iscsi_softc *sc, struct iscsi_daemon_request *request) { struct iscsi_session *is; struct icl_drv_limits idl; int error; sx_slock(&sc->sc_lock); for (;;) { TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { ISCSI_SESSION_LOCK(is); if (is->is_conf.isc_enable == 0 && is->is_conf.isc_discovery == 0) { ISCSI_SESSION_UNLOCK(is); continue; } if (is->is_waiting_for_iscsid) break; ISCSI_SESSION_UNLOCK(is); } 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_waiting_for_iscsid = false; is->is_login_phase = true; is->is_reason[0] = '\0'; ISCSI_SESSION_UNLOCK(is); request->idr_session_id = is->is_id; memcpy(&request->idr_isid, &is->is_isid, sizeof(request->idr_isid)); request->idr_tsih = 0; /* New or reinstated session. */ memcpy(&request->idr_conf, &is->is_conf, sizeof(request->idr_conf)); error = icl_limits(is->is_conf.isc_offload, is->is_conf.isc_iser, &idl); if (error != 0) { ISCSI_SESSION_WARN(is, "icl_limits for offload \"%s\" " "failed with error %d", is->is_conf.isc_offload, error); sx_sunlock(&sc->sc_lock); return (error); } request->idr_limits.isl_max_recv_data_segment_length = idl.idl_max_recv_data_segment_length; request->idr_limits.isl_max_send_data_segment_length = idl.idl_max_send_data_segment_length; request->idr_limits.isl_max_burst_length = idl.idl_max_burst_length; request->idr_limits.isl_first_burst_length = idl.idl_first_burst_length; sx_sunlock(&sc->sc_lock); return (0); } } static int iscsi_ioctl_daemon_handoff(struct iscsi_softc *sc, struct iscsi_daemon_handoff *handoff) { struct iscsi_session *is; struct icl_conn *ic; int error; sx_slock(&sc->sc_lock); /* * Find the session to hand off socket to. */ TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { if (is->is_id == handoff->idh_session_id) break; } if (is == NULL) { sx_sunlock(&sc->sc_lock); return (ESRCH); } ISCSI_SESSION_LOCK(is); ic = is->is_conn; if (is->is_conf.isc_discovery || is->is_terminating) { ISCSI_SESSION_UNLOCK(is); sx_sunlock(&sc->sc_lock); return (EINVAL); } if (is->is_connected) { /* * This might have happened because another iscsid(8) * instance handed off the connection in the meantime. * Just return. */ ISCSI_SESSION_WARN(is, "handoff on already connected " "session"); ISCSI_SESSION_UNLOCK(is); sx_sunlock(&sc->sc_lock); return (EBUSY); } strlcpy(is->is_target_alias, handoff->idh_target_alias, sizeof(is->is_target_alias)); is->is_tsih = handoff->idh_tsih; is->is_statsn = handoff->idh_statsn; + is->is_protocol_level = handoff->idh_protocol_level; is->is_initial_r2t = handoff->idh_initial_r2t; is->is_immediate_data = handoff->idh_immediate_data; is->is_max_recv_data_segment_length = handoff->idh_max_recv_data_segment_length; is->is_max_send_data_segment_length = handoff->idh_max_send_data_segment_length; is->is_max_burst_length = handoff->idh_max_burst_length; is->is_first_burst_length = handoff->idh_first_burst_length; if (handoff->idh_header_digest == ISCSI_DIGEST_CRC32C) ic->ic_header_crc32c = true; else ic->ic_header_crc32c = false; if (handoff->idh_data_digest == ISCSI_DIGEST_CRC32C) ic->ic_data_crc32c = true; else ic->ic_data_crc32c = false; ic->ic_maxtags = maxtags; is->is_cmdsn = 0; is->is_expcmdsn = 0; is->is_maxcmdsn = 0; is->is_waiting_for_iscsid = false; is->is_login_phase = false; is->is_timeout = 0; is->is_connected = true; is->is_reason[0] = '\0'; ISCSI_SESSION_UNLOCK(is); /* * If we're going through the proxy, the idh_socket will be 0, * and the ICL module can simply ignore this call. It can also * use it to determine it's no longer in the Login phase. */ error = icl_conn_handoff(ic, handoff->idh_socket); if (error != 0) { sx_sunlock(&sc->sc_lock); iscsi_session_terminate(is); return (error); } sx_sunlock(&sc->sc_lock); if (is->is_sim != NULL) { /* * When reconnecting, there already is SIM allocated for the session. */ KASSERT(is->is_simq_frozen, ("reconnect without frozen simq")); ISCSI_SESSION_LOCK(is); ISCSI_SESSION_DEBUG(is, "releasing"); is->is_simq_frozen = false; xpt_release_simq(is->is_sim, 1); ISCSI_SESSION_UNLOCK(is); } else { ISCSI_SESSION_LOCK(is); is->is_devq = cam_simq_alloc(ic->ic_maxtags); if (is->is_devq == NULL) { ISCSI_SESSION_WARN(is, "failed to allocate simq"); iscsi_session_terminate(is); return (ENOMEM); } is->is_sim = cam_sim_alloc(iscsi_action, iscsi_poll, "iscsi", is, is->is_id /* unit */, &is->is_lock, 1, ic->ic_maxtags, is->is_devq); if (is->is_sim == NULL) { ISCSI_SESSION_UNLOCK(is); ISCSI_SESSION_WARN(is, "failed to allocate SIM"); cam_simq_free(is->is_devq); iscsi_session_terminate(is); return (ENOMEM); } error = xpt_bus_register(is->is_sim, NULL, 0); if (error != 0) { ISCSI_SESSION_UNLOCK(is); ISCSI_SESSION_WARN(is, "failed to register bus"); iscsi_session_terminate(is); return (ENOMEM); } error = xpt_create_path(&is->is_path, /*periph*/NULL, cam_sim_path(is->is_sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); if (error != CAM_REQ_CMP) { ISCSI_SESSION_UNLOCK(is); ISCSI_SESSION_WARN(is, "failed to create path"); iscsi_session_terminate(is); return (ENOMEM); } ISCSI_SESSION_UNLOCK(is); } return (0); } static int iscsi_ioctl_daemon_fail(struct iscsi_softc *sc, struct iscsi_daemon_fail *fail) { struct iscsi_session *is; sx_slock(&sc->sc_lock); TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { if (is->is_id == fail->idf_session_id) break; } if (is == NULL) { sx_sunlock(&sc->sc_lock); return (ESRCH); } ISCSI_SESSION_LOCK(is); ISCSI_SESSION_DEBUG(is, "iscsid(8) failed: %s", fail->idf_reason); strlcpy(is->is_reason, fail->idf_reason, sizeof(is->is_reason)); //is->is_waiting_for_iscsid = false; //is->is_login_phase = true; //iscsi_session_reconnect(is); ISCSI_SESSION_UNLOCK(is); sx_sunlock(&sc->sc_lock); return (0); } #ifdef ICL_KERNEL_PROXY static int iscsi_ioctl_daemon_connect(struct iscsi_softc *sc, struct iscsi_daemon_connect *idc) { struct iscsi_session *is; struct sockaddr *from_sa, *to_sa; int error; sx_slock(&sc->sc_lock); TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { if (is->is_id == idc->idc_session_id) break; } if (is == NULL) { sx_sunlock(&sc->sc_lock); return (ESRCH); } sx_sunlock(&sc->sc_lock); if (idc->idc_from_addrlen > 0) { error = getsockaddr(&from_sa, (void *)idc->idc_from_addr, idc->idc_from_addrlen); if (error != 0) { ISCSI_SESSION_WARN(is, "getsockaddr failed with error %d", error); return (error); } } else { from_sa = NULL; } error = getsockaddr(&to_sa, (void *)idc->idc_to_addr, idc->idc_to_addrlen); if (error != 0) { ISCSI_SESSION_WARN(is, "getsockaddr failed with error %d", error); free(from_sa, M_SONAME); return (error); } ISCSI_SESSION_LOCK(is); is->is_statsn = 0; is->is_cmdsn = 0; is->is_expcmdsn = 0; is->is_maxcmdsn = 0; is->is_waiting_for_iscsid = false; is->is_login_phase = true; is->is_timeout = 0; ISCSI_SESSION_UNLOCK(is); error = icl_conn_connect(is->is_conn, idc->idc_domain, idc->idc_socktype, idc->idc_protocol, from_sa, to_sa); free(from_sa, M_SONAME); free(to_sa, M_SONAME); /* * Digests are always disabled during login phase. */ is->is_conn->ic_header_crc32c = false; is->is_conn->ic_data_crc32c = false; return (error); } static int iscsi_ioctl_daemon_send(struct iscsi_softc *sc, struct iscsi_daemon_send *ids) { struct iscsi_session *is; struct icl_pdu *ip; size_t datalen; void *data; int error; sx_slock(&sc->sc_lock); TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { if (is->is_id == ids->ids_session_id) break; } if (is == NULL) { sx_sunlock(&sc->sc_lock); return (ESRCH); } sx_sunlock(&sc->sc_lock); if (is->is_login_phase == false) return (EBUSY); if (is->is_terminating || is->is_reconnecting) return (EIO); datalen = ids->ids_data_segment_len; if (datalen > is->is_max_send_data_segment_length) return (EINVAL); if (datalen > 0) { data = malloc(datalen, M_ISCSI, M_WAITOK); error = copyin(ids->ids_data_segment, data, datalen); if (error != 0) { free(data, M_ISCSI); return (error); } } ip = icl_pdu_new(is->is_conn, M_WAITOK); memcpy(ip->ip_bhs, ids->ids_bhs, sizeof(*ip->ip_bhs)); if (datalen > 0) { error = icl_pdu_append_data(ip, data, datalen, M_WAITOK); KASSERT(error == 0, ("icl_pdu_append_data(..., M_WAITOK) failed")); free(data, M_ISCSI); } iscsi_pdu_queue(ip); return (0); } static int iscsi_ioctl_daemon_receive(struct iscsi_softc *sc, struct iscsi_daemon_receive *idr) { struct iscsi_session *is; struct icl_pdu *ip; void *data; int error; sx_slock(&sc->sc_lock); TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { if (is->is_id == idr->idr_session_id) break; } if (is == NULL) { sx_sunlock(&sc->sc_lock); return (ESRCH); } sx_sunlock(&sc->sc_lock); if (is->is_login_phase == false) return (EBUSY); ISCSI_SESSION_LOCK(is); while (is->is_login_pdu == NULL && is->is_terminating == false && is->is_reconnecting == false) { error = cv_wait_sig(&is->is_login_cv, &is->is_lock); if (error != 0) { ISCSI_SESSION_UNLOCK(is); return (error); } } if (is->is_terminating || is->is_reconnecting) { ISCSI_SESSION_UNLOCK(is); return (EIO); } ip = is->is_login_pdu; is->is_login_pdu = NULL; ISCSI_SESSION_UNLOCK(is); if (ip->ip_data_len > idr->idr_data_segment_len) { icl_pdu_free(ip); return (EMSGSIZE); } copyout(ip->ip_bhs, idr->idr_bhs, sizeof(*ip->ip_bhs)); if (ip->ip_data_len > 0) { data = malloc(ip->ip_data_len, M_ISCSI, M_WAITOK); icl_pdu_get_data(ip, 0, data, ip->ip_data_len); copyout(data, idr->idr_data_segment, ip->ip_data_len); free(data, M_ISCSI); } icl_pdu_free(ip); return (0); } #endif /* ICL_KERNEL_PROXY */ static void iscsi_sanitize_session_conf(struct iscsi_session_conf *isc) { /* * Just make sure all the fields are null-terminated. * * XXX: This is not particularly secure. We should * create our own conf and then copy in relevant * fields. */ isc->isc_initiator[ISCSI_NAME_LEN - 1] = '\0'; isc->isc_initiator_addr[ISCSI_ADDR_LEN - 1] = '\0'; isc->isc_initiator_alias[ISCSI_ALIAS_LEN - 1] = '\0'; isc->isc_target[ISCSI_NAME_LEN - 1] = '\0'; isc->isc_target_addr[ISCSI_ADDR_LEN - 1] = '\0'; isc->isc_user[ISCSI_NAME_LEN - 1] = '\0'; isc->isc_secret[ISCSI_SECRET_LEN - 1] = '\0'; isc->isc_mutual_user[ISCSI_NAME_LEN - 1] = '\0'; isc->isc_mutual_secret[ISCSI_SECRET_LEN - 1] = '\0'; } static bool iscsi_valid_session_conf(const struct iscsi_session_conf *isc) { if (isc->isc_initiator[0] == '\0') { ISCSI_DEBUG("empty isc_initiator"); return (false); } if (isc->isc_target_addr[0] == '\0') { ISCSI_DEBUG("empty isc_target_addr"); return (false); } if (isc->isc_discovery != 0 && isc->isc_target[0] != 0) { ISCSI_DEBUG("non-empty isc_target for discovery session"); return (false); } if (isc->isc_discovery == 0 && isc->isc_target[0] == 0) { ISCSI_DEBUG("empty isc_target for non-discovery session"); return (false); } return (true); } 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_conf(&isa->isa_conf); if (iscsi_valid_session_conf(&isa->isa_conf) == false) return (EINVAL); is = malloc(sizeof(*is), M_ISCSI, M_ZERO | M_WAITOK); memcpy(&is->is_conf, &isa->isa_conf, sizeof(is->is_conf)); /* * Set some default values, from RFC 3720, section 12. * * These values are updated by the handoff IOCTL, but are * needed prior to the handoff to support sending the ISER * login PDU. */ is->is_max_recv_data_segment_length = 8192; is->is_max_send_data_segment_length = 8192; is->is_max_burst_length = 262144; is->is_first_burst_length = 65536; sx_xlock(&sc->sc_lock); /* * Prevent duplicates. */ TAILQ_FOREACH(is2, &sc->sc_sessions, is_next) { if (!!is->is_conf.isc_discovery != !!is2->is_conf.isc_discovery) continue; if (strcmp(is->is_conf.isc_target_addr, is2->is_conf.isc_target_addr) != 0) continue; if (is->is_conf.isc_discovery == 0 && strcmp(is->is_conf.isc_target, is2->is_conf.isc_target) != 0) continue; sx_xunlock(&sc->sc_lock); free(is, M_ISCSI); return (EBUSY); } is->is_conn = icl_new_conn(is->is_conf.isc_offload, is->is_conf.isc_iser, "iscsi", &is->is_lock); if (is->is_conn == NULL) { sx_xunlock(&sc->sc_lock); free(is, M_ISCSI); return (EINVAL); } is->is_conn->ic_receive = iscsi_receive_callback; is->is_conn->ic_error = iscsi_error_callback; is->is_conn->ic_prv0 = is; TAILQ_INIT(&is->is_outstanding); STAILQ_INIT(&is->is_postponed); mtx_init(&is->is_lock, "iscsi_lock", NULL, MTX_DEF); cv_init(&is->is_maintenance_cv, "iscsi_mt"); #ifdef ICL_KERNEL_PROXY cv_init(&is->is_login_cv, "iscsi_login"); #endif is->is_softc = sc; sc->sc_last_session_id++; is->is_id = sc->sc_last_session_id; is->is_isid[0] = 0x80; /* RFC 3720, 10.12.5: 10b, "Random" ISID. */ arc4rand(&is->is_isid[1], 5, 0); is->is_tsih = 0; callout_init(&is->is_callout, 1); error = kthread_add(iscsi_maintenance_thread, is, NULL, NULL, 0, 0, "iscsimt"); if (error != 0) { ISCSI_SESSION_WARN(is, "kthread_add(9) failed with error %d", error); sx_xunlock(&sc->sc_lock); return (error); } callout_reset(&is->is_callout, 1 * hz, iscsi_callout, is); TAILQ_INSERT_TAIL(&sc->sc_sessions, is, is_next); ISCSI_SESSION_LOCK(is); /* * Don't notify iscsid(8) if the session is disabled and it's not * a discovery session, */ if (is->is_conf.isc_enable == 0 && is->is_conf.isc_discovery == 0) { ISCSI_SESSION_UNLOCK(is); sx_xunlock(&sc->sc_lock); return (0); } is->is_waiting_for_iscsid = true; strlcpy(is->is_reason, "Waiting for iscsid(8)", sizeof(is->is_reason)); ISCSI_SESSION_UNLOCK(is); cv_signal(&sc->sc_cv); sx_xunlock(&sc->sc_lock); return (0); } static bool iscsi_session_conf_matches(unsigned int id1, const struct iscsi_session_conf *c1, unsigned int id2, const struct iscsi_session_conf *c2) { if (id2 != 0 && id2 != id1) return (false); if (c2->isc_target[0] != '\0' && strcmp(c1->isc_target, c2->isc_target) != 0) return (false); if (c2->isc_target_addr[0] != '\0' && strcmp(c1->isc_target_addr, c2->isc_target_addr) != 0) return (false); return (true); } static int iscsi_ioctl_session_remove(struct iscsi_softc *sc, struct iscsi_session_remove *isr) { struct iscsi_session *is, *tmp; bool found = false; iscsi_sanitize_session_conf(&isr->isr_conf); sx_xlock(&sc->sc_lock); TAILQ_FOREACH_SAFE(is, &sc->sc_sessions, is_next, tmp) { ISCSI_SESSION_LOCK(is); if (iscsi_session_conf_matches(is->is_id, &is->is_conf, isr->isr_session_id, &isr->isr_conf)) { found = true; iscsi_session_logout(is); iscsi_session_terminate(is); } ISCSI_SESSION_UNLOCK(is); } sx_xunlock(&sc->sc_lock); if (!found) 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; struct iscsi_session_state iss; 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); } memset(&iss, 0, sizeof(iss)); memcpy(&iss.iss_conf, &is->is_conf, sizeof(iss.iss_conf)); iss.iss_id = is->is_id; strlcpy(iss.iss_target_alias, is->is_target_alias, sizeof(iss.iss_target_alias)); strlcpy(iss.iss_reason, is->is_reason, sizeof(iss.iss_reason)); strlcpy(iss.iss_offload, is->is_conn->ic_offload, sizeof(iss.iss_offload)); if (is->is_conn->ic_header_crc32c) iss.iss_header_digest = ISCSI_DIGEST_CRC32C; else iss.iss_header_digest = ISCSI_DIGEST_NONE; if (is->is_conn->ic_data_crc32c) iss.iss_data_digest = ISCSI_DIGEST_CRC32C; else iss.iss_data_digest = ISCSI_DIGEST_NONE; iss.iss_max_send_data_segment_length = is->is_max_send_data_segment_length; iss.iss_max_recv_data_segment_length = is->is_max_recv_data_segment_length; iss.iss_max_burst_length = is->is_max_burst_length; iss.iss_first_burst_length = is->is_first_burst_length; iss.iss_immediate_data = is->is_immediate_data; iss.iss_connected = is->is_connected; error = copyout(&iss, isl->isl_pstates + i, sizeof(iss)); 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_session_modify(struct iscsi_softc *sc, struct iscsi_session_modify *ism) { struct iscsi_session *is; const struct iscsi_session *is2; iscsi_sanitize_session_conf(&ism->ism_conf); if (iscsi_valid_session_conf(&ism->ism_conf) == false) return (EINVAL); sx_xlock(&sc->sc_lock); TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { ISCSI_SESSION_LOCK(is); if (is->is_id == ism->ism_session_id) { /* Note that the session remains locked. */ break; } ISCSI_SESSION_UNLOCK(is); } if (is == NULL) { sx_xunlock(&sc->sc_lock); return (ESRCH); } /* * Prevent duplicates. */ TAILQ_FOREACH(is2, &sc->sc_sessions, is_next) { if (is == is2) continue; if (!!ism->ism_conf.isc_discovery != !!is2->is_conf.isc_discovery) continue; if (strcmp(ism->ism_conf.isc_target_addr, is2->is_conf.isc_target_addr) != 0) continue; if (ism->ism_conf.isc_discovery == 0 && strcmp(ism->ism_conf.isc_target, is2->is_conf.isc_target) != 0) continue; ISCSI_SESSION_UNLOCK(is); sx_xunlock(&sc->sc_lock); return (EBUSY); } sx_xunlock(&sc->sc_lock); memcpy(&is->is_conf, &ism->ism_conf, sizeof(is->is_conf)); ISCSI_SESSION_UNLOCK(is); iscsi_session_reconnect(is); 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 ISCSIDFAIL: return (iscsi_ioctl_daemon_fail(sc, (struct iscsi_daemon_fail *)arg)); #ifdef ICL_KERNEL_PROXY case ISCSIDCONNECT: return (iscsi_ioctl_daemon_connect(sc, (struct iscsi_daemon_connect *)arg)); case ISCSIDSEND: return (iscsi_ioctl_daemon_send(sc, (struct iscsi_daemon_send *)arg)); case ISCSIDRECEIVE: return (iscsi_ioctl_daemon_receive(sc, (struct iscsi_daemon_receive *)arg)); #endif /* ICL_KERNEL_PROXY */ case ISCSISADD: return (iscsi_ioctl_session_add(sc, (struct iscsi_session_add *)arg)); case ISCSISREMOVE: return (iscsi_ioctl_session_remove(sc, (struct iscsi_session_remove *)arg)); case ISCSISLIST: return (iscsi_ioctl_session_list(sc, (struct iscsi_session_list *)arg)); case ISCSISMODIFY: return (iscsi_ioctl_session_modify(sc, (struct iscsi_session_modify *)arg)); default: return (EINVAL); } } static struct iscsi_outstanding * iscsi_outstanding_find(struct iscsi_session *is, uint32_t initiator_task_tag) { struct iscsi_outstanding *io; ISCSI_SESSION_LOCK_ASSERT(is); TAILQ_FOREACH(io, &is->is_outstanding, io_next) { if (io->io_initiator_task_tag == initiator_task_tag) return (io); } return (NULL); } static struct iscsi_outstanding * iscsi_outstanding_find_ccb(struct iscsi_session *is, union ccb *ccb) { struct iscsi_outstanding *io; ISCSI_SESSION_LOCK_ASSERT(is); TAILQ_FOREACH(io, &is->is_outstanding, io_next) { if (io->io_ccb == ccb) return (io); } return (NULL); } static struct iscsi_outstanding * iscsi_outstanding_add(struct iscsi_session *is, struct icl_pdu *request, union ccb *ccb, uint32_t *initiator_task_tagp) { struct iscsi_outstanding *io; int error; ISCSI_SESSION_LOCK_ASSERT(is); io = uma_zalloc(iscsi_outstanding_zone, M_NOWAIT | M_ZERO); if (io == NULL) { ISCSI_SESSION_WARN(is, "failed to allocate %zd bytes", sizeof(*io)); return (NULL); } error = icl_conn_task_setup(is->is_conn, request, &ccb->csio, initiator_task_tagp, &io->io_icl_prv); if (error != 0) { ISCSI_SESSION_WARN(is, "icl_conn_task_setup() failed with error %d", error); uma_zfree(iscsi_outstanding_zone, io); return (NULL); } KASSERT(iscsi_outstanding_find(is, *initiator_task_tagp) == NULL, ("initiator_task_tag 0x%x already added", *initiator_task_tagp)); io->io_initiator_task_tag = *initiator_task_tagp; io->io_ccb = ccb; TAILQ_INSERT_TAIL(&is->is_outstanding, io, io_next); return (io); } static void iscsi_outstanding_remove(struct iscsi_session *is, struct iscsi_outstanding *io) { ISCSI_SESSION_LOCK_ASSERT(is); icl_conn_task_done(is->is_conn, io->io_icl_prv); TAILQ_REMOVE(&is->is_outstanding, io, io_next); uma_zfree(iscsi_outstanding_zone, io); } static void iscsi_action_abort(struct iscsi_session *is, union ccb *ccb) { struct icl_pdu *request; struct iscsi_bhs_task_management_request *bhstmr; struct ccb_abort *cab = &ccb->cab; struct iscsi_outstanding *io, *aio; uint32_t initiator_task_tag; ISCSI_SESSION_LOCK_ASSERT(is); #if 0 KASSERT(is->is_login_phase == false, ("%s called during Login Phase", __func__)); #else if (is->is_login_phase) { ccb->ccb_h.status = CAM_REQ_ABORTED; xpt_done(ccb); return; } #endif aio = iscsi_outstanding_find_ccb(is, cab->abort_ccb); if (aio == NULL) { ccb->ccb_h.status = CAM_REQ_CMP; xpt_done(ccb); return; } request = icl_pdu_new(is->is_conn, M_NOWAIT); if (request == NULL) { ccb->ccb_h.status = CAM_RESRC_UNAVAIL; xpt_done(ccb); return; } initiator_task_tag = is->is_initiator_task_tag++; io = iscsi_outstanding_add(is, request, NULL, &initiator_task_tag); if (io == NULL) { icl_pdu_free(request); ccb->ccb_h.status = CAM_RESRC_UNAVAIL; xpt_done(ccb); return; } io->io_datasn = aio->io_initiator_task_tag; bhstmr = (struct iscsi_bhs_task_management_request *)request->ip_bhs; bhstmr->bhstmr_opcode = ISCSI_BHS_OPCODE_TASK_REQUEST; bhstmr->bhstmr_function = 0x80 | BHSTMR_FUNCTION_ABORT_TASK; bhstmr->bhstmr_lun = htobe64(CAM_EXTLUN_BYTE_SWIZZLE(ccb->ccb_h.target_lun)); bhstmr->bhstmr_initiator_task_tag = initiator_task_tag; bhstmr->bhstmr_referenced_task_tag = aio->io_initiator_task_tag; iscsi_pdu_queue_locked(request); } static void iscsi_action_scsiio(struct iscsi_session *is, union ccb *ccb) { struct icl_pdu *request; struct iscsi_bhs_scsi_command *bhssc; struct ccb_scsiio *csio; struct iscsi_outstanding *io; size_t len; uint32_t initiator_task_tag; int error; ISCSI_SESSION_LOCK_ASSERT(is); #if 0 KASSERT(is->is_login_phase == false, ("%s called during Login Phase", __func__)); #else if (is->is_login_phase) { ISCSI_SESSION_DEBUG(is, "called during login phase"); if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ISCSI_SESSION_DEBUG(is, "freezing devq"); } ccb->ccb_h.status = CAM_REQ_ABORTED | CAM_DEV_QFRZN; xpt_done(ccb); return; } #endif request = icl_pdu_new(is->is_conn, M_NOWAIT); if (request == NULL) { if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ISCSI_SESSION_DEBUG(is, "freezing devq"); } ccb->ccb_h.status = CAM_RESRC_UNAVAIL | CAM_DEV_QFRZN; xpt_done(ccb); return; } initiator_task_tag = is->is_initiator_task_tag++; io = iscsi_outstanding_add(is, request, ccb, &initiator_task_tag); if (io == NULL) { icl_pdu_free(request); if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ISCSI_SESSION_DEBUG(is, "freezing devq"); } ccb->ccb_h.status = CAM_RESRC_UNAVAIL | CAM_DEV_QFRZN; xpt_done(ccb); return; } csio = &ccb->csio; bhssc = (struct iscsi_bhs_scsi_command *)request->ip_bhs; bhssc->bhssc_opcode = ISCSI_BHS_OPCODE_SCSI_COMMAND; bhssc->bhssc_flags |= BHSSC_FLAGS_F; switch (csio->ccb_h.flags & CAM_DIR_MASK) { case CAM_DIR_IN: bhssc->bhssc_flags |= BHSSC_FLAGS_R; break; case CAM_DIR_OUT: bhssc->bhssc_flags |= BHSSC_FLAGS_W; break; } if ((ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0) { switch (csio->tag_action) { case MSG_HEAD_OF_Q_TAG: bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_HOQ; 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 MSG_SIMPLE_Q_TAG: default: bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_SIMPLE; break; } } else bhssc->bhssc_flags |= BHSSC_FLAGS_ATTR_UNTAGGED; bhssc->bhssc_lun = htobe64(CAM_EXTLUN_BYTE_SWIZZLE(ccb->ccb_h.target_lun)); bhssc->bhssc_initiator_task_tag = 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", (size_t)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); if (is->is_immediate_data && (csio->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT) { len = csio->dxfer_len; //ISCSI_SESSION_DEBUG(is, "adding %zd of immediate data", len); if (len > is->is_first_burst_length) { ISCSI_SESSION_DEBUG(is, "len %zd -> %d", len, is->is_first_burst_length); len = is->is_first_burst_length; } if (len > is->is_max_send_data_segment_length) { ISCSI_SESSION_DEBUG(is, "len %zd -> %d", len, is->is_max_send_data_segment_length); len = is->is_max_send_data_segment_length; } error = icl_pdu_append_data(request, csio->data_ptr, len, M_NOWAIT); if (error != 0) { iscsi_outstanding_remove(is, io); icl_pdu_free(request); if ((ccb->ccb_h.status & CAM_DEV_QFRZN) == 0) { xpt_freeze_devq(ccb->ccb_h.path, 1); ISCSI_SESSION_DEBUG(is, "freezing devq"); } ccb->ccb_h.status = CAM_RESRC_UNAVAIL | CAM_DEV_QFRZN; xpt_done(ccb); return; } } iscsi_pdu_queue_locked(request); } static void iscsi_action(struct cam_sim *sim, union ccb *ccb) { struct iscsi_session *is; is = cam_sim_softc(sim); ISCSI_SESSION_LOCK_ASSERT(is); if (is->is_terminating || (is->is_connected == false && fail_on_disconnection)) { ccb->ccb_h.status = CAM_DEV_NOT_THERE; xpt_done(ccb); return; } /* * Make sure CAM doesn't sneak in a CCB just after freezing the queue. */ if (is->is_simq_frozen == true) { ccb->ccb_h.status &= ~(CAM_SIM_QUEUED | CAM_STATUS_MASK); ccb->ccb_h.status |= CAM_REQUEUE_REQ; /* Don't freeze the devq - the SIM queue is already frozen. */ xpt_done(ccb); return; } 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_EXTLUNS; /* * XXX: It shouldn't ever be NULL; this could be turned * into a KASSERT eventually. */ if (is->is_conn == NULL) ISCSI_WARN("NULL conn"); else if (is->is_conn->ic_unmapped) cpi->hba_misc |= PIM_UNMAPPED; cpi->hba_eng_cnt = 0; cpi->max_target = 0; /* * Note that the variable below is only relevant for targets * that don't claim compliance with anything above SPC2, which * means they don't support REPORT_LUNS. */ cpi->max_lun = 255; cpi->initiator_id = ~0; strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strlcpy(cpi->hba_vid, "iSCSI", HBA_IDLEN); strlcpy(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_SPC3; cpi->maxio = MAXPHYS; cpi->ccb_h.status = CAM_REQ_CMP; break; } case XPT_GET_TRAN_SETTINGS: { struct ccb_trans_settings *cts; struct ccb_trans_settings_scsi *scsi; cts = &ccb->cts; scsi = &cts->proto_specific.scsi; cts->protocol = PROTO_SCSI; cts->protocol_version = SCSI_REV_SPC3; cts->transport = XPORT_ISCSI; cts->transport_version = 0; scsi->valid = CTS_SCSI_VALID_TQ; scsi->flags = CTS_SCSI_FLAGS_TAG_ENB; cts->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; #if 0 /* * XXX: What's the point? */ case XPT_RESET_BUS: case XPT_TERM_IO: ISCSI_SESSION_DEBUG(is, "faking success for reset, abort, or term_io"); ccb->ccb_h.status = CAM_REQ_CMP; break; #endif case XPT_ABORT: iscsi_action_abort(is, ccb); return; case XPT_SCSI_IO: iscsi_action_scsiio(is, ccb); return; default: #if 0 ISCSI_SESSION_DEBUG(is, "got unsupported code 0x%x", ccb->ccb_h.func_code); #endif ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; break; } xpt_done(ccb); } static void iscsi_poll(struct cam_sim *sim) { KASSERT(0, ("%s: you're not supposed to be here", __func__)); } static void iscsi_terminate_sessions(struct iscsi_softc *sc) { struct iscsi_session *is; sx_slock(&sc->sc_lock); TAILQ_FOREACH(is, &sc->sc_sessions, is_next) iscsi_session_terminate(is); while(!TAILQ_EMPTY(&sc->sc_sessions)) { ISCSI_DEBUG("waiting for sessions to terminate"); cv_wait(&sc->sc_cv, &sc->sc_lock); } ISCSI_DEBUG("all sessions terminated"); sx_sunlock(&sc->sc_lock); } static void iscsi_shutdown_pre(struct iscsi_softc *sc) { struct iscsi_session *is; if (!fail_on_shutdown) return; /* * If we have any sessions waiting for reconnection, request * maintenance thread to fail them immediately instead of waiting * for reconnect timeout. * * This prevents LUNs with mounted filesystems that are supported * by disconnected iSCSI sessions from hanging, however it will * fail all queued BIOs. */ ISCSI_DEBUG("forcing failing all disconnected sessions due to shutdown"); fail_on_disconnection = 1; sx_slock(&sc->sc_lock); TAILQ_FOREACH(is, &sc->sc_sessions, is_next) { ISCSI_SESSION_LOCK(is); if (!is->is_connected) { ISCSI_SESSION_DEBUG(is, "force failing disconnected session early"); iscsi_session_reconnect(is); } ISCSI_SESSION_UNLOCK(is); } sx_sunlock(&sc->sc_lock); } static void iscsi_shutdown_post(struct iscsi_softc *sc) { if (!KERNEL_PANICKED()) { ISCSI_DEBUG("removing all sessions due to shutdown"); iscsi_terminate_sessions(sc); } } static int iscsi_load(void) { int error; sc = malloc(sizeof(*sc), M_ISCSI, M_ZERO | M_WAITOK); sx_init(&sc->sc_lock, "iscsi"); TAILQ_INIT(&sc->sc_sessions); cv_init(&sc->sc_cv, "iscsi_cv"); iscsi_outstanding_zone = uma_zcreate("iscsi_outstanding", sizeof(struct iscsi_outstanding), NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); error = make_dev_p(MAKEDEV_CHECKNAME, &sc->sc_cdev, &iscsi_cdevsw, NULL, UID_ROOT, GID_WHEEL, 0600, "iscsi"); if (error != 0) { ISCSI_WARN("failed to create device node, error %d", error); return (error); } sc->sc_cdev->si_drv1 = sc; sc->sc_shutdown_pre_eh = EVENTHANDLER_REGISTER(shutdown_pre_sync, iscsi_shutdown_pre, sc, SHUTDOWN_PRI_FIRST); /* * shutdown_post_sync needs to run after filesystem shutdown and before * CAM shutdown - otherwise when rebooting with an iSCSI session that is * disconnected but has outstanding requests, dashutdown() will hang on * cam_periph_runccb(). */ sc->sc_shutdown_post_eh = EVENTHANDLER_REGISTER(shutdown_post_sync, iscsi_shutdown_post, sc, SHUTDOWN_PRI_DEFAULT - 1); return (0); } static int iscsi_unload(void) { if (sc->sc_cdev != NULL) { ISCSI_DEBUG("removing device node"); destroy_dev(sc->sc_cdev); ISCSI_DEBUG("device node removed"); } if (sc->sc_shutdown_pre_eh != NULL) EVENTHANDLER_DEREGISTER(shutdown_pre_sync, sc->sc_shutdown_pre_eh); if (sc->sc_shutdown_post_eh != NULL) EVENTHANDLER_DEREGISTER(shutdown_post_sync, sc->sc_shutdown_post_eh); iscsi_terminate_sessions(sc); uma_zdestroy(iscsi_outstanding_zone); sx_destroy(&sc->sc_lock); cv_destroy(&sc->sc_cv); free(sc, M_ISCSI); return (0); } static int iscsi_quiesce(void) { sx_slock(&sc->sc_lock); if (!TAILQ_EMPTY(&sc->sc_sessions)) { sx_sunlock(&sc->sc_lock); return (EBUSY); } sx_sunlock(&sc->sc_lock); return (0); } static int iscsi_modevent(module_t mod, int what, void *arg) { int error; switch (what) { case MOD_LOAD: error = iscsi_load(); break; case MOD_UNLOAD: error = iscsi_unload(); break; case MOD_QUIESCE: error = iscsi_quiesce(); break; default: error = EINVAL; break; } return (error); } moduledata_t iscsi_data = { "iscsi", iscsi_modevent, 0 }; DECLARE_MODULE(iscsi, iscsi_data, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_DEPEND(iscsi, cam, 1, 1, 1); MODULE_DEPEND(iscsi, icl, 1, 1, 1); Index: head/sys/dev/iscsi/iscsi.h =================================================================== --- head/sys/dev/iscsi/iscsi.h (revision 366952) +++ head/sys/dev/iscsi/iscsi.h (revision 366953) @@ -1,141 +1,140 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef ISCSI_H #define ISCSI_H struct iscsi_softc; struct icl_conn; #define ISCSI_NAME_LEN 224 /* 223 bytes, by RFC 3720, + '\0' */ #define ISCSI_ADDR_LEN 47 /* INET6_ADDRSTRLEN + '\0' */ #define ISCSI_SECRET_LEN 17 /* 16 + '\0' */ struct iscsi_outstanding { TAILQ_ENTRY(iscsi_outstanding) io_next; union ccb *io_ccb; size_t io_received; uint32_t io_initiator_task_tag; uint32_t io_datasn; void *io_icl_prv; }; struct iscsi_session { TAILQ_ENTRY(iscsi_session) is_next; struct icl_conn *is_conn; struct mtx is_lock; uint32_t is_statsn; uint32_t is_cmdsn; uint32_t is_expcmdsn; uint32_t is_maxcmdsn; uint32_t is_initiator_task_tag; - int is_header_digest; - int is_data_digest; + int is_protocol_level; int is_initial_r2t; int is_max_burst_length; int is_first_burst_length; uint8_t is_isid[6]; uint16_t is_tsih; bool is_immediate_data; int is_max_recv_data_segment_length; int is_max_send_data_segment_length; char is_target_alias[ISCSI_ALIAS_LEN]; TAILQ_HEAD(, iscsi_outstanding) is_outstanding; STAILQ_HEAD(, icl_pdu) is_postponed; struct callout is_callout; unsigned int is_timeout; /* * XXX: This could be rewritten using a single variable, * but somehow it results in uglier code. */ /* * We're waiting for iscsid(8); after iscsid_timeout * expires, kernel will wake up an iscsid(8) to handle * the session. */ bool is_waiting_for_iscsid; /* * Some iscsid(8) instance is handling the session; * after login_timeout expires, kernel will wake up * another iscsid(8) to handle the session. */ bool is_login_phase; /* * We're in the process of removing the iSCSI session. */ bool is_terminating; /* * We're waiting for the maintenance thread to do some * reconnection tasks. */ bool is_reconnecting; bool is_connected; struct cam_devq *is_devq; struct cam_sim *is_sim; struct cam_path *is_path; struct cv is_maintenance_cv; struct iscsi_softc *is_softc; unsigned int is_id; struct iscsi_session_conf is_conf; bool is_simq_frozen; char is_reason[ISCSI_REASON_LEN]; #ifdef ICL_KERNEL_PROXY struct cv is_login_cv; struct icl_pdu *is_login_pdu; #endif }; struct iscsi_softc { device_t sc_dev; struct sx sc_lock; struct cdev *sc_cdev; TAILQ_HEAD(, iscsi_session) sc_sessions; struct cv sc_cv; unsigned int sc_last_session_id; eventhandler_tag sc_shutdown_pre_eh; eventhandler_tag sc_shutdown_post_eh; }; #endif /* !ISCSI_H */ Index: head/sys/dev/iscsi/iscsi_ioctl.h =================================================================== --- head/sys/dev/iscsi/iscsi_ioctl.h (revision 366952) +++ head/sys/dev/iscsi/iscsi_ioctl.h (revision 366953) @@ -1,239 +1,240 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef ISCSI_IOCTL_H #define ISCSI_IOCTL_H #ifdef ICL_KERNEL_PROXY #include #endif #define ISCSI_PATH "/dev/iscsi" #define ISCSI_MAX_DATA_SEGMENT_LENGTH (128 * 1024) #define ISCSI_NAME_LEN 224 /* 223 bytes, by RFC 3720, + '\0' */ #define ISCSI_ADDR_LEN 47 /* INET6_ADDRSTRLEN + '\0' */ #define ISCSI_ALIAS_LEN 256 /* XXX: Where did it come from? */ #define ISCSI_SECRET_LEN 17 /* 16 + '\0' */ #define ISCSI_OFFLOAD_LEN 8 #define ISCSI_REASON_LEN 64 #define ISCSI_DIGEST_NONE 0 #define ISCSI_DIGEST_CRC32C 1 /* * Session configuration, set when adding the session. */ struct iscsi_session_conf { char isc_initiator[ISCSI_NAME_LEN]; char isc_initiator_addr[ISCSI_ADDR_LEN]; char isc_initiator_alias[ISCSI_ALIAS_LEN]; char isc_target[ISCSI_NAME_LEN]; char isc_target_addr[ISCSI_ADDR_LEN]; char isc_user[ISCSI_NAME_LEN]; char isc_secret[ISCSI_SECRET_LEN]; char isc_mutual_user[ISCSI_NAME_LEN]; char isc_mutual_secret[ISCSI_SECRET_LEN]; int isc_discovery; int isc_header_digest; int isc_data_digest; int isc_iser; char isc_offload[ISCSI_OFFLOAD_LEN]; int isc_enable; int isc_dscp; int isc_spare[3]; }; /* * Additional constraints imposed by chosen ICL offload module; * iscsid(8) must obey those when negotiating operational parameters. */ struct iscsi_session_limits { size_t isl_spare0; int isl_max_recv_data_segment_length; int isl_max_send_data_segment_length; int isl_max_burst_length; int isl_first_burst_length; int isl_spare[4]; }; /* * Session state, negotiated by iscsid(8) and queried by iscsictl(8). */ struct iscsi_session_state { struct iscsi_session_conf iss_conf; unsigned int iss_id; char iss_target_alias[ISCSI_ALIAS_LEN]; int iss_header_digest; int iss_data_digest; int iss_max_recv_data_segment_length; int iss_max_burst_length; int iss_first_burst_length; int iss_immediate_data; int iss_connected; char iss_reason[ISCSI_REASON_LEN]; char iss_offload[ISCSI_OFFLOAD_LEN]; int iss_max_send_data_segment_length; int iss_spare[3]; }; /* * The following ioctls are used by iscsid(8). */ struct iscsi_daemon_request { unsigned int idr_session_id; struct iscsi_session_conf idr_conf; uint8_t idr_isid[6]; uint16_t idr_tsih; uint16_t idr_spare_cid; struct iscsi_session_limits idr_limits; int idr_spare[4]; }; struct iscsi_daemon_handoff { unsigned int idh_session_id; int idh_socket; char idh_target_alias[ISCSI_ALIAS_LEN]; - uint8_t idh_spare_isid[6]; + int idh_protocol_level; + uint16_t idh_spare; uint16_t idh_tsih; uint16_t idh_spare_cid; uint32_t idh_statsn; int idh_header_digest; int idh_data_digest; size_t spare[3]; int idh_immediate_data; int idh_initial_r2t; int idh_max_recv_data_segment_length; int idh_max_send_data_segment_length; int idh_max_burst_length; int idh_first_burst_length; }; struct iscsi_daemon_fail { unsigned int idf_session_id; char idf_reason[ISCSI_REASON_LEN]; int idf_spare[4]; }; #define ISCSIDWAIT _IOR('I', 0x01, struct iscsi_daemon_request) #define ISCSIDHANDOFF _IOW('I', 0x02, struct iscsi_daemon_handoff) #define ISCSIDFAIL _IOW('I', 0x03, struct iscsi_daemon_fail) #ifdef ICL_KERNEL_PROXY /* * When ICL_KERNEL_PROXY is not defined, the iscsid(8) is responsible * for creating the socket, connecting, and performing Login Phase using * the socket in the usual userspace way, and then passing the socket * file descriptor to the kernel part using ISCSIDHANDOFF. * * When ICL_KERNEL_PROXY is defined, the iscsid(8) creates the session * using ISCSICONNECT, performs Login Phase using ISCSISEND/ISCSIRECEIVE * instead of read(2)/write(2), and then calls ISCSIDHANDOFF with * idh_socket set to 0. * * The purpose of ICL_KERNEL_PROXY is to workaround the fact that, * at this time, it's not possible to do iWARP (RDMA) in userspace. */ struct iscsi_daemon_connect { unsigned int idc_session_id; int idc_iser; int idc_domain; int idc_socktype; int idc_protocol; struct sockaddr *idc_from_addr; socklen_t idc_from_addrlen; struct sockaddr *idc_to_addr; socklen_t idc_to_addrlen; int idc_spare[4]; }; struct iscsi_daemon_send { unsigned int ids_session_id; void *ids_bhs; size_t ids_spare; void *ids_spare2; size_t ids_data_segment_len; void *ids_data_segment; int ids_spare3[4]; }; struct iscsi_daemon_receive { unsigned int idr_session_id; void *idr_bhs; size_t idr_spare; void *idr_spare2; size_t idr_data_segment_len; void *idr_data_segment; int idr_spare3[4]; }; #define ISCSIDCONNECT _IOWR('I', 0x04, struct iscsi_daemon_connect) #define ISCSIDSEND _IOWR('I', 0x05, struct iscsi_daemon_send) #define ISCSIDRECEIVE _IOWR('I', 0x06, struct iscsi_daemon_receive) #endif /* ICL_KERNEL_PROXY */ /* * The following ioctls are used by iscsictl(8). */ struct iscsi_session_add { struct iscsi_session_conf isa_conf; int isa_spare[4]; }; struct iscsi_session_remove { unsigned int isr_session_id; struct iscsi_session_conf isr_conf; int isr_spare[4]; }; struct iscsi_session_list { unsigned int isl_nentries; struct iscsi_session_state *isl_pstates; int isl_spare[4]; }; struct iscsi_session_modify { unsigned int ism_session_id; struct iscsi_session_conf ism_conf; int ism_spare[4]; }; #define ISCSISADD _IOW('I', 0x11, struct iscsi_session_add) #define ISCSISREMOVE _IOW('I', 0x12, struct iscsi_session_remove) #define ISCSISLIST _IOWR('I', 0x13, struct iscsi_session_list) #define ISCSISMODIFY _IOWR('I', 0x14, struct iscsi_session_modify) #endif /* !ISCSI_IOCTL_H */ Index: head/sys/dev/iscsi/iscsi_proto.h =================================================================== --- head/sys/dev/iscsi/iscsi_proto.h (revision 366952) +++ head/sys/dev/iscsi/iscsi_proto.h (revision 366953) @@ -1,458 +1,462 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef ISCSI_PROTO_H #define ISCSI_PROTO_H #ifndef CTASSERT #define CTASSERT(x) _CTASSERT(x, __LINE__) #define _CTASSERT(x, y) __CTASSERT(x, y) #define __CTASSERT(x, y) typedef char __assert_ ## y [(x) ? 1 : -1] #endif #define ISCSI_SNGT(x, y) ((int32_t)(x) - (int32_t)(y) > 0) #define ISCSI_SNLT(x, y) ((int32_t)(x) - (int32_t)(y) < 0) #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 +#define BHSSC_PRI_MASK 0xf0 +#define BHSSC_PRI_SHIFT 4 + struct iscsi_bhs_scsi_command { uint8_t bhssc_opcode; uint8_t bhssc_flags; - uint8_t bhssc_reserved[2]; + uint8_t bhssc_pri; + uint8_t bhssc_reserved; 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]; uint16_t bhssr_status_qualifier; uint16_t bhssr_reserved; uint32_t bhssr_reserved2; 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 #define BHSTMR_FUNCTION_QUERY_TASK 9 #define BHSTMR_FUNCTION_QUERY_TASK_SET 10 #define BHSTMR_FUNCTION_I_T_NEXUS_RESET 11 #define BHSTMR_FUNCTION_QUERY_ASYNC_EVENT 12 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_TASK_DOES_NOT_EXIST 1 #define BHSTMR_RESPONSE_LUN_DOES_NOT_EXIST 2 #define BHSTMR_RESPONSE_TASK_STILL_ALLEGIANT 3 #define BHSTMR_RESPONSE_TASK_ALL_REASS_NOT_SUPP 4 #define BHSTMR_RESPONSE_FUNCTION_NOT_SUPPORTED 5 #define BHSTMR_RESPONSE_FUNCTION_AUTH_FAIL 6 #define BHSTMR_RESPONSE_FUNCTION_SUCCEEDED 7 #define BHSTMR_RESPONSE_FUNCTION_REJECTED 255 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]; uint8_t bhstmr_additional_reponse_information[3]; uint8_t bhstmr_reserved2[5]; uint32_t bhstmr_initiator_task_tag; uint32_t bhstmr_reserved3; uint32_t bhstmr_statsn; uint32_t bhstmr_expcmdsn; uint32_t bhstmr_maxcmdsn; uint8_t bhstmr_reserved4[12]; }; CTASSERT(sizeof(struct iscsi_bhs_task_management_response) == ISCSI_BHS_SIZE); #define BHSLR_FLAGS_TRANSIT 0x80 #define BHSLR_FLAGS_CONTINUE 0x40 #define BHSLR_STAGE_SECURITY_NEGOTIATION 0 #define BHSLR_STAGE_OPERATIONAL_NEGOTIATION 1 #define BHSLR_STAGE_FULL_FEATURE_PHASE 3 /* Yes, 3. */ struct iscsi_bhs_login_request { uint8_t bhslr_opcode; uint8_t bhslr_flags; uint8_t bhslr_version_max; uint8_t bhslr_version_min; uint8_t bhslr_total_ahs_len; uint8_t bhslr_data_segment_len[3]; uint8_t bhslr_isid[6]; uint16_t bhslr_tsih; uint32_t bhslr_initiator_task_tag; uint16_t bhslr_cid; uint16_t bhslr_reserved; uint32_t bhslr_cmdsn; uint32_t bhslr_expstatsn; uint8_t bhslr_reserved2[16]; }; CTASSERT(sizeof(struct iscsi_bhs_login_request) == ISCSI_BHS_SIZE); struct iscsi_bhs_login_response { uint8_t bhslr_opcode; uint8_t bhslr_flags; uint8_t bhslr_version_max; uint8_t bhslr_version_active; uint8_t bhslr_total_ahs_len; uint8_t bhslr_data_segment_len[3]; uint8_t bhslr_isid[6]; uint16_t bhslr_tsih; uint32_t bhslr_initiator_task_tag; uint32_t bhslr_reserved; uint32_t bhslr_statsn; uint32_t bhslr_expcmdsn; uint32_t bhslr_maxcmdsn; uint8_t bhslr_status_class; uint8_t bhslr_status_detail; uint16_t bhslr_reserved2; uint8_t bhslr_reserved3[8]; }; CTASSERT(sizeof(struct iscsi_bhs_login_response) == ISCSI_BHS_SIZE); #define BHSTR_FLAGS_FINAL 0x80 #define BHSTR_FLAGS_CONTINUE 0x40 struct iscsi_bhs_text_request { uint8_t bhstr_opcode; uint8_t bhstr_flags; uint16_t bhstr_reserved; uint8_t bhstr_total_ahs_len; uint8_t bhstr_data_segment_len[3]; uint64_t bhstr_lun; uint32_t bhstr_initiator_task_tag; uint32_t bhstr_target_transfer_tag; uint32_t bhstr_cmdsn; uint32_t bhstr_expstatsn; uint8_t bhstr_reserved2[16]; }; CTASSERT(sizeof(struct iscsi_bhs_text_request) == ISCSI_BHS_SIZE); struct iscsi_bhs_text_response { uint8_t bhstr_opcode; uint8_t bhstr_flags; uint16_t bhstr_reserved; uint8_t bhstr_total_ahs_len; uint8_t bhstr_data_segment_len[3]; uint64_t bhstr_lun; uint32_t bhstr_initiator_task_tag; uint32_t bhstr_target_transfer_tag; uint32_t bhstr_statsn; uint32_t bhstr_expcmdsn; uint32_t bhstr_maxcmdsn; uint8_t bhstr_reserved2[12]; }; CTASSERT(sizeof(struct iscsi_bhs_text_response) == ISCSI_BHS_SIZE); #define BHSDO_FLAGS_F 0x80 struct iscsi_bhs_data_out { uint8_t bhsdo_opcode; uint8_t bhsdo_flags; uint8_t bhsdo_reserved[2]; uint8_t bhsdo_total_ahs_len; uint8_t bhsdo_data_segment_len[3]; uint64_t bhsdo_lun; uint32_t bhsdo_initiator_task_tag; uint32_t bhsdo_target_transfer_tag; uint32_t bhsdo_reserved2; uint32_t bhsdo_expstatsn; uint32_t bhsdo_reserved3; uint32_t bhsdo_datasn; uint32_t bhsdo_buffer_offset; uint32_t bhsdo_reserved4; }; CTASSERT(sizeof(struct iscsi_bhs_data_out) == ISCSI_BHS_SIZE); #define BHSDI_FLAGS_F 0x80 #define BHSDI_FLAGS_A 0x40 #define BHSDI_FLAGS_O 0x04 #define BHSDI_FLAGS_U 0x02 #define BHSDI_FLAGS_S 0x01 struct iscsi_bhs_data_in { uint8_t bhsdi_opcode; uint8_t bhsdi_flags; uint8_t bhsdi_reserved; uint8_t bhsdi_status; uint8_t bhsdi_total_ahs_len; uint8_t bhsdi_data_segment_len[3]; uint64_t bhsdi_lun; uint32_t bhsdi_initiator_task_tag; uint32_t bhsdi_target_transfer_tag; uint32_t bhsdi_statsn; uint32_t bhsdi_expcmdsn; uint32_t bhsdi_maxcmdsn; uint32_t bhsdi_datasn; uint32_t bhsdi_buffer_offset; uint32_t bhsdi_residual_count; }; CTASSERT(sizeof(struct iscsi_bhs_data_in) == ISCSI_BHS_SIZE); struct iscsi_bhs_r2t { uint8_t bhsr2t_opcode; uint8_t bhsr2t_flags; uint16_t bhsr2t_reserved; uint8_t bhsr2t_total_ahs_len; uint8_t bhsr2t_data_segment_len[3]; uint64_t bhsr2t_lun; uint32_t bhsr2t_initiator_task_tag; uint32_t bhsr2t_target_transfer_tag; uint32_t bhsr2t_statsn; uint32_t bhsr2t_expcmdsn; uint32_t bhsr2t_maxcmdsn; uint32_t bhsr2t_r2tsn; uint32_t bhsr2t_buffer_offset; uint32_t bhsr2t_desired_data_transfer_length; }; CTASSERT(sizeof(struct iscsi_bhs_r2t) == ISCSI_BHS_SIZE); struct iscsi_bhs_nop_out { uint8_t bhsno_opcode; uint8_t bhsno_flags; uint16_t bhsno_reserved; uint8_t bhsno_total_ahs_len; uint8_t bhsno_data_segment_len[3]; uint64_t bhsno_lun; uint32_t bhsno_initiator_task_tag; uint32_t bhsno_target_transfer_tag; uint32_t bhsno_cmdsn; uint32_t bhsno_expstatsn; uint8_t bhsno_reserved2[16]; }; CTASSERT(sizeof(struct iscsi_bhs_nop_out) == ISCSI_BHS_SIZE); struct iscsi_bhs_nop_in { uint8_t bhsni_opcode; uint8_t bhsni_flags; uint16_t bhsni_reserved; uint8_t bhsni_total_ahs_len; uint8_t bhsni_data_segment_len[3]; uint64_t bhsni_lun; uint32_t bhsni_initiator_task_tag; uint32_t bhsni_target_transfer_tag; uint32_t bhsni_statsn; uint32_t bhsni_expcmdsn; uint32_t bhsni_maxcmdsn; uint8_t bhsno_reserved2[12]; }; CTASSERT(sizeof(struct iscsi_bhs_nop_in) == ISCSI_BHS_SIZE); #define BHSLR_REASON_CLOSE_SESSION 0 #define BHSLR_REASON_CLOSE_CONNECTION 1 #define BHSLR_REASON_REMOVE_FOR_RECOVERY 2 struct iscsi_bhs_logout_request { uint8_t bhslr_opcode; uint8_t bhslr_reason; uint16_t bhslr_reserved; uint8_t bhslr_total_ahs_len; uint8_t bhslr_data_segment_len[3]; uint64_t bhslr_reserved2; uint32_t bhslr_initiator_task_tag; uint16_t bhslr_cid; uint16_t bhslr_reserved3; uint32_t bhslr_cmdsn; uint32_t bhslr_expstatsn; uint8_t bhslr_reserved4[16]; }; CTASSERT(sizeof(struct iscsi_bhs_logout_request) == ISCSI_BHS_SIZE); #define BHSLR_RESPONSE_CLOSED_SUCCESSFULLY 0 #define BHSLR_RESPONSE_RECOVERY_NOT_SUPPORTED 2 struct iscsi_bhs_logout_response { uint8_t bhslr_opcode; uint8_t bhslr_flags; uint8_t bhslr_response; uint8_t bhslr_reserved; uint8_t bhslr_total_ahs_len; uint8_t bhslr_data_segment_len[3]; uint64_t bhslr_reserved2; uint32_t bhslr_initiator_task_tag; uint32_t bhslr_reserved3; uint32_t bhslr_statsn; uint32_t bhslr_expcmdsn; uint32_t bhslr_maxcmdsn; uint32_t bhslr_reserved4; uint16_t bhslr_time2wait; uint16_t bhslr_time2retain; uint32_t bhslr_reserved5; }; CTASSERT(sizeof(struct iscsi_bhs_logout_response) == ISCSI_BHS_SIZE); #define BHSAM_EVENT_TARGET_REQUESTS_LOGOUT 1 #define BHSAM_EVENT_TARGET_TERMINATES_CONNECTION 2 #define BHSAM_EVENT_TARGET_TERMINATES_SESSION 3 struct iscsi_bhs_asynchronous_message { uint8_t bhsam_opcode; uint8_t bhsam_flags; uint16_t bhsam_reserved; uint8_t bhsam_total_ahs_len; uint8_t bhsam_data_segment_len[3]; uint64_t bhsam_lun; uint32_t bhsam_0xffffffff; uint32_t bhsam_reserved2; uint32_t bhsam_statsn; uint32_t bhsam_expcmdsn; uint32_t bhsam_maxcmdsn; uint8_t bhsam_async_event; uint8_t bhsam_async_vcode; uint16_t bhsam_parameter1; uint16_t bhsam_parameter2; uint16_t bhsam_parameter3; uint32_t bhsam_reserved3; }; CTASSERT(sizeof(struct iscsi_bhs_asynchronous_message) == ISCSI_BHS_SIZE); #define BHSSR_REASON_DATA_DIGEST_ERROR 0x02 #define BHSSR_PROTOCOL_ERROR 0x04 #define BHSSR_COMMAND_NOT_SUPPORTED 0x05 #define BHSSR_INVALID_PDU_FIELD 0x09 struct iscsi_bhs_reject { uint8_t bhsr_opcode; uint8_t bhsr_flags; uint8_t bhsr_reason; uint8_t bhsr_reserved; uint8_t bhsr_total_ahs_len; uint8_t bhsr_data_segment_len[3]; uint64_t bhsr_reserved2; uint32_t bhsr_0xffffffff; uint32_t bhsr_reserved3; uint32_t bhsr_statsn; uint32_t bhsr_expcmdsn; uint32_t bhsr_maxcmdsn; uint32_t bhsr_datasn_r2tsn; uint32_t bhsr_reserved4; uint32_t bhsr_reserved5; }; CTASSERT(sizeof(struct iscsi_bhs_reject) == ISCSI_BHS_SIZE); #endif /* !ISCSI_PROTO_H */ Index: head/usr.sbin/iscsid/iscsid.c =================================================================== --- head/usr.sbin/iscsid/iscsid.c (revision 366952) +++ head/usr.sbin/iscsid/iscsid.c (revision 366953) @@ -1,660 +1,662 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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. * */ #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 "iscsid.h" static volatile bool sigalrm_received = false; static int nchildren = 0; static void usage(void) { fprintf(stderr, "usage: iscsid [-P pidfile][-d][-m maxproc][-t timeout]\n"); exit(1); } char * checked_strdup(const char *s) { char *c; c = strdup(s); if (c == NULL) log_err(1, "strdup"); return (c); } static void resolve_addr(const struct connection *conn, const char *address, struct addrinfo **ai, bool initiator_side) { struct addrinfo hints; char *arg, *addr, *ch; const char *port; int error, colons = 0; arg = checked_strdup(address); if (arg[0] == '\0') { fail(conn, "empty address"); log_errx(1, "empty address"); } if (arg[0] == '[') { /* * IPv6 address in square brackets, perhaps with port. */ arg++; addr = strsep(&arg, "]"); if (arg == NULL) { fail(conn, "malformed address"); log_errx(1, "malformed address %s", address); } if (arg[0] == '\0') { port = NULL; } else if (arg[0] == ':') { port = arg + 1; } else { fail(conn, "malformed address"); log_errx(1, "malformed address %s", address); } } 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 = NULL; } else { addr = strsep(&arg, ":"); if (arg == NULL) port = NULL; else port = arg; } } if (port == NULL && !initiator_side) port = "3260"; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; if (initiator_side) hints.ai_flags |= AI_PASSIVE; error = getaddrinfo(addr, port, &hints, ai); if (error != 0) { fail(conn, gai_strerror(error)); log_errx(1, "getaddrinfo for %s failed: %s", address, gai_strerror(error)); } } static struct connection * connection_new(int iscsi_fd, const struct iscsi_daemon_request *request) { struct connection *conn; struct iscsi_session_limits *isl; struct addrinfo *from_ai, *to_ai; const char *from_addr, *to_addr; #ifdef ICL_KERNEL_PROXY struct iscsi_daemon_connect idc; #endif int error, sockbuf; conn = calloc(1, sizeof(*conn)); if (conn == NULL) log_err(1, "calloc"); /* * Default values, from RFC 3720, section 12. */ + conn->conn_protocol_level = 0; conn->conn_header_digest = CONN_DIGEST_NONE; conn->conn_data_digest = CONN_DIGEST_NONE; conn->conn_initial_r2t = true; conn->conn_immediate_data = true; conn->conn_max_recv_data_segment_length = 8192; conn->conn_max_send_data_segment_length = 8192; conn->conn_max_burst_length = 262144; conn->conn_first_burst_length = 65536; conn->conn_iscsi_fd = iscsi_fd; conn->conn_session_id = request->idr_session_id; memcpy(&conn->conn_conf, &request->idr_conf, sizeof(conn->conn_conf)); memcpy(&conn->conn_isid, &request->idr_isid, sizeof(conn->conn_isid)); conn->conn_tsih = request->idr_tsih; /* * Read the driver limits and provide reasonable defaults for the ones * the driver doesn't care about. If a max_snd_dsl is not explicitly * provided by the driver then we'll make sure both conn->max_snd_dsl * and isl->max_snd_dsl are set to the rcv_dsl. This preserves historic * behavior. */ isl = &conn->conn_limits; memcpy(isl, &request->idr_limits, sizeof(*isl)); if (isl->isl_max_recv_data_segment_length == 0) isl->isl_max_recv_data_segment_length = (1 << 24) - 1; if (isl->isl_max_send_data_segment_length == 0) isl->isl_max_send_data_segment_length = isl->isl_max_recv_data_segment_length; if (isl->isl_max_burst_length == 0) isl->isl_max_burst_length = (1 << 24) - 1; if (isl->isl_first_burst_length == 0) isl->isl_first_burst_length = (1 << 24) - 1; if (isl->isl_first_burst_length > isl->isl_max_burst_length) isl->isl_first_burst_length = isl->isl_max_burst_length; /* * Limit default send length in case it won't be negotiated. * We can't do it for other limits, since they may affect both * sender and receiver operation, and we must obey defaults. */ if (conn->conn_max_send_data_segment_length > isl->isl_max_send_data_segment_length) { conn->conn_max_send_data_segment_length = isl->isl_max_send_data_segment_length; } from_addr = conn->conn_conf.isc_initiator_addr; to_addr = conn->conn_conf.isc_target_addr; if (from_addr[0] != '\0') resolve_addr(conn, from_addr, &from_ai, true); else from_ai = NULL; resolve_addr(conn, to_addr, &to_ai, false); #ifdef ICL_KERNEL_PROXY if (conn->conn_conf.isc_iser) { memset(&idc, 0, sizeof(idc)); idc.idc_session_id = conn->conn_session_id; if (conn->conn_conf.isc_iser) idc.idc_iser = 1; idc.idc_domain = to_ai->ai_family; idc.idc_socktype = to_ai->ai_socktype; idc.idc_protocol = to_ai->ai_protocol; if (from_ai != NULL) { idc.idc_from_addr = from_ai->ai_addr; idc.idc_from_addrlen = from_ai->ai_addrlen; } idc.idc_to_addr = to_ai->ai_addr; idc.idc_to_addrlen = to_ai->ai_addrlen; log_debugx("connecting to %s using ICL kernel proxy", to_addr); error = ioctl(iscsi_fd, ISCSIDCONNECT, &idc); if (error != 0) { fail(conn, strerror(errno)); log_err(1, "failed to connect to %s " "using ICL kernel proxy: ISCSIDCONNECT", to_addr); } return (conn); } #endif /* ICL_KERNEL_PROXY */ if (conn->conn_conf.isc_iser) { fail(conn, "iSER not supported"); log_errx(1, "iscsid(8) compiled without ICL_KERNEL_PROXY " "does not support iSER"); } conn->conn_socket = socket(to_ai->ai_family, to_ai->ai_socktype, to_ai->ai_protocol); if (conn->conn_socket < 0) { fail(conn, strerror(errno)); log_err(1, "failed to create socket for %s", from_addr); } sockbuf = SOCKBUF_SIZE; if (setsockopt(conn->conn_socket, SOL_SOCKET, SO_RCVBUF, &sockbuf, sizeof(sockbuf)) == -1) log_warn("setsockopt(SO_RCVBUF) failed"); sockbuf = SOCKBUF_SIZE; if (setsockopt(conn->conn_socket, SOL_SOCKET, SO_SNDBUF, &sockbuf, sizeof(sockbuf)) == -1) log_warn("setsockopt(SO_SNDBUF) failed"); if (conn->conn_conf.isc_dscp != -1) { int tos = conn->conn_conf.isc_dscp << 2; if (to_ai->ai_family == AF_INET) { if (setsockopt(conn->conn_socket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1) log_warn("setsockopt(IP_TOS) " "failed for %s", from_addr); } else if (to_ai->ai_family == AF_INET6) { if (setsockopt(conn->conn_socket, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) == -1) log_warn("setsockopt(IPV6_TCLASS) " "failed for %s", from_addr); } } if (from_ai != NULL) { error = bind(conn->conn_socket, from_ai->ai_addr, from_ai->ai_addrlen); if (error != 0) { fail(conn, strerror(errno)); log_err(1, "failed to bind to %s", from_addr); } } log_debugx("connecting to %s", to_addr); error = connect(conn->conn_socket, to_ai->ai_addr, to_ai->ai_addrlen); if (error != 0) { fail(conn, strerror(errno)); log_err(1, "failed to connect to %s", to_addr); } return (conn); } static void handoff(struct connection *conn) { struct iscsi_daemon_handoff idh; int error; log_debugx("handing off connection to the kernel"); memset(&idh, 0, sizeof(idh)); idh.idh_session_id = conn->conn_session_id; idh.idh_socket = conn->conn_socket; strlcpy(idh.idh_target_alias, conn->conn_target_alias, sizeof(idh.idh_target_alias)); idh.idh_tsih = conn->conn_tsih; idh.idh_statsn = conn->conn_statsn; + idh.idh_protocol_level = conn->conn_protocol_level; idh.idh_header_digest = conn->conn_header_digest; idh.idh_data_digest = conn->conn_data_digest; idh.idh_initial_r2t = conn->conn_initial_r2t; idh.idh_immediate_data = conn->conn_immediate_data; idh.idh_max_recv_data_segment_length = conn->conn_max_recv_data_segment_length; idh.idh_max_send_data_segment_length = conn->conn_max_send_data_segment_length; idh.idh_max_burst_length = conn->conn_max_burst_length; idh.idh_first_burst_length = conn->conn_first_burst_length; error = ioctl(conn->conn_iscsi_fd, ISCSIDHANDOFF, &idh); if (error != 0) log_err(1, "ISCSIDHANDOFF"); } void fail(const struct connection *conn, const char *reason) { struct iscsi_daemon_fail idf; int error, saved_errno; saved_errno = errno; memset(&idf, 0, sizeof(idf)); idf.idf_session_id = conn->conn_session_id; strlcpy(idf.idf_reason, reason, sizeof(idf.idf_reason)); error = ioctl(conn->conn_iscsi_fd, ISCSIDFAIL, &idf); if (error != 0) log_err(1, "ISCSIDFAIL"); errno = saved_errno; } /* * XXX: I CANT INTO LATIN */ static void capsicate(struct connection *conn) { cap_rights_t rights; #ifdef ICL_KERNEL_PROXY const unsigned long cmds[] = { ISCSIDCONNECT, ISCSIDSEND, ISCSIDRECEIVE, ISCSIDHANDOFF, ISCSIDFAIL, ISCSISADD, ISCSISREMOVE, ISCSISMODIFY }; #else const unsigned long cmds[] = { ISCSIDHANDOFF, ISCSIDFAIL, ISCSISADD, ISCSISREMOVE, ISCSISMODIFY }; #endif cap_rights_init(&rights, CAP_IOCTL); if (caph_rights_limit(conn->conn_iscsi_fd, &rights) < 0) log_err(1, "cap_rights_limit"); if (caph_ioctls_limit(conn->conn_iscsi_fd, cmds, nitems(cmds)) < 0) log_err(1, "cap_ioctls_limit"); if (caph_enter() != 0) log_err(1, "cap_enter"); if (cap_sandboxed()) log_debugx("Capsicum capability mode enabled"); else log_warnx("Capsicum capability mode not supported"); } bool timed_out(void) { return (sigalrm_received); } static void sigalrm_handler(int dummy __unused) { /* * It would be easiest to just log an error and exit. We can't * do this, though, because log_errx() is not signal safe, since * it calls syslog(3). Instead, set a flag checked by pdu_send() * and pdu_receive(), to call log_errx() there. Should they fail * to notice, we'll exit here one second later. */ if (sigalrm_received) { /* * Oh well. Just give up and quit. */ _exit(2); } sigalrm_received = true; } static void set_timeout(int timeout) { struct sigaction sa; struct itimerval itv; int error; if (timeout <= 0) { log_debugx("session timeout disabled"); return; } bzero(&sa, sizeof(sa)); sa.sa_handler = sigalrm_handler; sigfillset(&sa.sa_mask); error = sigaction(SIGALRM, &sa, NULL); if (error != 0) log_err(1, "sigaction"); /* * First SIGALRM will arive after conf_timeout seconds. * If we do nothing, another one will arrive a second later. */ bzero(&itv, sizeof(itv)); itv.it_interval.tv_sec = 1; itv.it_value.tv_sec = timeout; log_debugx("setting session timeout to %d seconds", timeout); error = setitimer(ITIMER_REAL, &itv, NULL); if (error != 0) log_err(1, "setitimer"); } static void sigchld_handler(int dummy __unused) { /* * The only purpose of this handler is to make SIGCHLD * interrupt the ISCSIDWAIT ioctl(2), so we can call * wait_for_children(). */ } static void register_sigchld(void) { struct sigaction sa; int error; bzero(&sa, sizeof(sa)); sa.sa_handler = sigchld_handler; sigfillset(&sa.sa_mask); error = sigaction(SIGCHLD, &sa, NULL); if (error != 0) log_err(1, "sigaction"); } static void handle_request(int iscsi_fd, const struct iscsi_daemon_request *request, int timeout) { struct connection *conn; log_set_peer_addr(request->idr_conf.isc_target_addr); if (request->idr_conf.isc_target[0] != '\0') { log_set_peer_name(request->idr_conf.isc_target); setproctitle("%s (%s)", request->idr_conf.isc_target_addr, request->idr_conf.isc_target); } else { setproctitle("%s", request->idr_conf.isc_target_addr); } conn = connection_new(iscsi_fd, request); set_timeout(timeout); capsicate(conn); login(conn); if (conn->conn_conf.isc_discovery != 0) discovery(conn); else handoff(conn); log_debugx("nothing more to do; exiting"); exit (0); } static int wait_for_children(bool block) { pid_t pid; int status; int num = 0; for (;;) { /* * If "block" is true, wait for at least one process. */ if (block && num == 0) pid = wait4(-1, &status, 0, NULL); else pid = wait4(-1, &status, WNOHANG, NULL); if (pid <= 0) break; if (WIFSIGNALED(status)) { log_warnx("child process %d terminated with signal %d", pid, WTERMSIG(status)); } else if (WEXITSTATUS(status) != 0) { log_warnx("child process %d terminated with exit status %d", pid, WEXITSTATUS(status)); } else { log_debugx("child process %d terminated gracefully", pid); } num++; } return (num); } int main(int argc, char **argv) { int ch, debug = 0, error, iscsi_fd, maxproc = 30, retval, saved_errno, timeout = 60; bool dont_daemonize = false; struct pidfh *pidfh; pid_t pid, otherpid; const char *pidfile_path = DEFAULT_PIDFILE; struct iscsi_daemon_request request; while ((ch = getopt(argc, argv, "P:dl:m:t:")) != -1) { switch (ch) { case 'P': pidfile_path = optarg; break; case 'd': dont_daemonize = true; debug++; break; case 'l': debug = atoi(optarg); break; case 'm': maxproc = atoi(optarg); break; case 't': timeout = atoi(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 && errno == ENOENT) { saved_errno = errno; retval = kldload("iscsi"); if (retval != -1) iscsi_fd = open(ISCSI_PATH, O_RDWR); else errno = saved_errno; } if (iscsi_fd < 0) log_err(1, "failed to open %s", ISCSI_PATH); if (dont_daemonize == false) { if (daemon(0, 0) == -1) { log_warn("cannot daemonize"); pidfile_remove(pidfh); exit(1); } } pidfile_write(pidfh); register_sigchld(); for (;;) { log_debugx("waiting for request from the kernel"); memset(&request, 0, sizeof(request)); error = ioctl(iscsi_fd, ISCSIDWAIT, &request); if (error != 0) { if (errno == EINTR) { nchildren -= wait_for_children(false); assert(nchildren >= 0); continue; } log_err(1, "ISCSIDWAIT"); } if (dont_daemonize) { log_debugx("not forking due to -d flag; " "will exit after servicing a single request"); } else { nchildren -= wait_for_children(false); assert(nchildren >= 0); while (maxproc > 0 && nchildren >= maxproc) { log_debugx("maxproc limit of %d child processes hit; " "waiting for child process to exit", maxproc); nchildren -= wait_for_children(true); assert(nchildren >= 0); } log_debugx("incoming connection; forking child process #%d", nchildren); nchildren++; pid = fork(); if (pid < 0) log_err(1, "fork"); if (pid > 0) continue; } pidfile_close(pidfh); handle_request(iscsi_fd, &request, timeout); } return (0); } Index: head/usr.sbin/iscsid/iscsid.h =================================================================== --- head/usr.sbin/iscsid/iscsid.h (revision 366952) +++ head/usr.sbin/iscsid/iscsid.h (revision 366953) @@ -1,152 +1,153 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2012 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef ISCSID_H #define ISCSID_H #include #include #include #define DEFAULT_PIDFILE "/var/run/iscsid.pid" #define CONN_DIGEST_NONE 0 #define CONN_DIGEST_CRC32C 1 #define CONN_MUTUAL_CHALLENGE_LEN 1024 #define SOCKBUF_SIZE 1048576 struct connection { int conn_iscsi_fd; int conn_socket; unsigned int conn_session_id; struct iscsi_session_conf conn_conf; struct iscsi_session_limits conn_limits; char conn_target_alias[ISCSI_ADDR_LEN]; uint8_t conn_isid[6]; uint16_t conn_tsih; uint32_t conn_statsn; + int conn_protocol_level; int conn_header_digest; int conn_data_digest; bool conn_initial_r2t; bool conn_immediate_data; int conn_max_recv_data_segment_length; int conn_max_send_data_segment_length; int conn_max_burst_length; int conn_first_burst_length; struct chap *conn_mutual_chap; }; struct pdu { struct connection *pdu_connection; struct iscsi_bhs *pdu_bhs; char *pdu_data; size_t pdu_data_len; }; #define KEYS_MAX 1024 struct keys { char *keys_names[KEYS_MAX]; char *keys_values[KEYS_MAX]; char *keys_data; size_t keys_data_len; }; #define CHAP_CHALLENGE_LEN 1024 #define CHAP_DIGEST_LEN 16 /* Equal to MD5 digest size. */ struct chap { unsigned char chap_id; char chap_challenge[CHAP_CHALLENGE_LEN]; char chap_response[CHAP_DIGEST_LEN]; }; struct rchap { char *rchap_secret; unsigned char rchap_id; void *rchap_challenge; size_t rchap_challenge_len; }; struct chap *chap_new(void); char *chap_get_id(const struct chap *chap); char *chap_get_challenge(const struct chap *chap); int chap_receive(struct chap *chap, const char *response); int chap_authenticate(struct chap *chap, const char *secret); void chap_delete(struct chap *chap); struct rchap *rchap_new(const char *secret); int rchap_receive(struct rchap *rchap, const char *id, const char *challenge); char *rchap_get_response(struct rchap *rchap); void rchap_delete(struct rchap *rchap); struct keys *keys_new(void); void keys_delete(struct keys *key); void keys_load(struct keys *keys, const struct pdu *pdu); void keys_save(struct keys *keys, struct pdu *pdu); const char *keys_find(struct keys *keys, const char *name); void keys_add(struct keys *keys, const char *name, const char *value); void keys_add_int(struct keys *keys, const char *name, int value); struct pdu *pdu_new(struct connection *ic); struct pdu *pdu_new_response(struct pdu *request); void pdu_receive(struct pdu *request); void pdu_send(struct pdu *response); void pdu_delete(struct pdu *ip); void login(struct connection *ic); void discovery(struct connection *ic); void log_init(int level); void log_set_peer_name(const char *name); void log_set_peer_addr(const char *addr); void log_err(int, const char *, ...) __dead2 __printflike(2, 3); void log_errx(int, const char *, ...) __dead2 __printflike(2, 3); void log_warn(const char *, ...) __printflike(1, 2); void log_warnx(const char *, ...) __printflike(1, 2); void log_debugx(const char *, ...) __printflike(1, 2); char *checked_strdup(const char *); bool timed_out(void); void fail(const struct connection *, const char *); #endif /* !ISCSID_H */ Index: head/usr.sbin/iscsid/login.c =================================================================== --- head/usr.sbin/iscsid/login.c (revision 366952) +++ head/usr.sbin/iscsid/login.c (revision 366953) @@ -1,892 +1,898 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "iscsid.h" #include "iscsi_proto.h" static int login_nsg(const struct pdu *response) { struct iscsi_bhs_login_response *bhslr; bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; return (bhslr->bhslr_flags & 0x03); } static void login_set_nsg(struct pdu *request, int nsg) { struct iscsi_bhs_login_request *bhslr; assert(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION || nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || nsg == BHSLR_STAGE_FULL_FEATURE_PHASE); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; bhslr->bhslr_flags &= 0xFC; bhslr->bhslr_flags |= nsg; } static void login_set_csg(struct pdu *request, int csg) { struct iscsi_bhs_login_request *bhslr; assert(csg == BHSLR_STAGE_SECURITY_NEGOTIATION || csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || csg == BHSLR_STAGE_FULL_FEATURE_PHASE); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; bhslr->bhslr_flags &= 0xF3; bhslr->bhslr_flags |= csg << 2; } static const char * login_target_error_str(int class, int detail) { static char msg[128]; /* * RFC 3270, 10.13.5. Status-Class and Status-Detail */ switch (class) { case 0x01: switch (detail) { case 0x01: return ("Target moved temporarily"); case 0x02: return ("Target moved permanently"); default: snprintf(msg, sizeof(msg), "unknown redirection; " "Status-Class 0x%x, Status-Detail 0x%x", class, detail); return (msg); } case 0x02: switch (detail) { case 0x00: return ("Initiator error"); case 0x01: return ("Authentication failure"); case 0x02: return ("Authorization failure"); case 0x03: return ("Not found"); case 0x04: return ("Target removed"); case 0x05: return ("Unsupported version"); case 0x06: return ("Too many connections"); case 0x07: return ("Missing parameter"); case 0x08: return ("Can't include in session"); case 0x09: return ("Session type not supported"); case 0x0a: return ("Session does not exist"); case 0x0b: return ("Invalid during login"); default: snprintf(msg, sizeof(msg), "unknown initiator error; " "Status-Class 0x%x, Status-Detail 0x%x", class, detail); return (msg); } case 0x03: switch (detail) { case 0x00: return ("Target error"); case 0x01: return ("Service unavailable"); case 0x02: return ("Out of resources"); default: snprintf(msg, sizeof(msg), "unknown target error; " "Status-Class 0x%x, Status-Detail 0x%x", class, detail); return (msg); } default: snprintf(msg, sizeof(msg), "unknown error; " "Status-Class 0x%x, Status-Detail 0x%x", class, detail); return (msg); } } static void kernel_modify(const struct connection *conn, const char *target_address) { struct iscsi_session_modify ism; int error; memset(&ism, 0, sizeof(ism)); ism.ism_session_id = conn->conn_session_id; memcpy(&ism.ism_conf, &conn->conn_conf, sizeof(ism.ism_conf)); strlcpy(ism.ism_conf.isc_target_addr, target_address, sizeof(ism.ism_conf.isc_target_addr)); error = ioctl(conn->conn_iscsi_fd, ISCSISMODIFY, &ism); if (error != 0) { log_err(1, "failed to redirect to %s: ISCSISMODIFY", target_address); } } /* * XXX: The way it works is suboptimal; what should happen is described * in draft-gilligan-iscsi-fault-tolerance-00. That, however, would * be much more complicated: we would need to keep "dependencies" * for sessions, so that, in case described in draft and using draft * terminology, we would have three sessions: one for discovery, * one for initial target portal, and one for redirect portal. * This would allow us to "backtrack" on connection failure, * as described in draft. */ static void login_handle_redirection(struct connection *conn, struct pdu *response) { struct iscsi_bhs_login_response *bhslr; struct keys *response_keys; const char *target_address; bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; assert (bhslr->bhslr_status_class == 1); response_keys = keys_new(); keys_load(response_keys, response); target_address = keys_find(response_keys, "TargetAddress"); if (target_address == NULL) log_errx(1, "received redirection without TargetAddress"); if (target_address[0] == '\0') log_errx(1, "received redirection with empty TargetAddress"); if (strlen(target_address) >= sizeof(conn->conn_conf.isc_target_addr) - 1) log_errx(1, "received TargetAddress is too long"); log_debugx("received redirection to \"%s\"", target_address); kernel_modify(conn, target_address); keys_delete(response_keys); } static struct pdu * login_receive(struct connection *conn) { struct pdu *response; struct iscsi_bhs_login_response *bhslr; const char *errorstr; static bool initial = true; response = pdu_new(conn); pdu_receive(response); if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) { log_errx(1, "protocol error: received invalid opcode 0x%x", response->pdu_bhs->bhs_opcode); } bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; /* * XXX: Implement the C flag some day. */ if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) log_errx(1, "received Login PDU with unsupported \"C\" flag"); if (bhslr->bhslr_version_max != 0x00) log_errx(1, "received Login PDU with unsupported " "Version-max 0x%x", bhslr->bhslr_version_max); if (bhslr->bhslr_version_active != 0x00) log_errx(1, "received Login PDU with unsupported " "Version-active 0x%x", bhslr->bhslr_version_active); if (bhslr->bhslr_status_class == 1) { login_handle_redirection(conn, response); log_debugx("redirection handled; exiting"); exit(0); } if (bhslr->bhslr_status_class != 0) { errorstr = login_target_error_str(bhslr->bhslr_status_class, bhslr->bhslr_status_detail); fail(conn, errorstr); log_errx(1, "target returned error: %s", errorstr); } if (initial == false && ntohl(bhslr->bhslr_statsn) != conn->conn_statsn + 1) { /* * It's a warning, not an error, to work around what seems * to be bug in NetBSD iSCSI target. */ log_warnx("received Login PDU with wrong StatSN: " "is %u, should be %u", ntohl(bhslr->bhslr_statsn), conn->conn_statsn + 1); } conn->conn_tsih = ntohs(bhslr->bhslr_tsih); conn->conn_statsn = ntohl(bhslr->bhslr_statsn); initial = false; return (response); } static struct pdu * login_new_request(struct connection *conn, int csg) { struct pdu *request; struct iscsi_bhs_login_request *bhslr; int nsg; request = pdu_new(conn); bhslr = (struct iscsi_bhs_login_request *)request->pdu_bhs; bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST | ISCSI_BHS_OPCODE_IMMEDIATE; bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT; switch (csg) { case BHSLR_STAGE_SECURITY_NEGOTIATION: nsg = BHSLR_STAGE_OPERATIONAL_NEGOTIATION; break; case BHSLR_STAGE_OPERATIONAL_NEGOTIATION: nsg = BHSLR_STAGE_FULL_FEATURE_PHASE; break; default: assert(!"invalid csg"); log_errx(1, "invalid csg %d", csg); } login_set_csg(request, csg); login_set_nsg(request, nsg); memcpy(bhslr->bhslr_isid, &conn->conn_isid, sizeof(bhslr->bhslr_isid)); bhslr->bhslr_tsih = htons(conn->conn_tsih); bhslr->bhslr_initiator_task_tag = 0; bhslr->bhslr_cmdsn = 0; bhslr->bhslr_expstatsn = htonl(conn->conn_statsn + 1); return (request); } static int login_list_prefers(const char *list, const char *choice1, const char *choice2) { char *tofree, *str, *token; tofree = str = checked_strdup(list); while ((token = strsep(&str, ",")) != NULL) { if (strcmp(token, choice1) == 0) { free(tofree); return (1); } if (strcmp(token, choice2) == 0) { free(tofree); return (2); } } free(tofree); return (-1); } static void login_negotiate_key(struct connection *conn, const char *name, const char *value) { struct iscsi_session_limits *isl; int which, tmp; isl = &conn->conn_limits; if (strcmp(name, "TargetAlias") == 0) { strlcpy(conn->conn_target_alias, value, sizeof(conn->conn_target_alias)); } else if (strcmp(value, "Irrelevant") == 0) { /* Ignore. */ + } else if (strcmp(name, "iSCSIProtocolLevel") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp < 0 || tmp > 31) + log_errx(1, "received invalid iSCSIProtocolLevel"); + conn->conn_protocol_level = tmp; } else if (strcmp(name, "HeaderDigest") == 0) { which = login_list_prefers(value, "CRC32C", "None"); switch (which) { case 1: log_debugx("target prefers CRC32C " "for header digest; we'll use it"); conn->conn_header_digest = CONN_DIGEST_CRC32C; break; case 2: log_debugx("target prefers not to do " "header digest; we'll comply"); break; default: log_warnx("target sent unrecognized " "HeaderDigest value \"%s\"; will use None", value); break; } } else if (strcmp(name, "DataDigest") == 0) { which = login_list_prefers(value, "CRC32C", "None"); switch (which) { case 1: log_debugx("target prefers CRC32C " "for data digest; we'll use it"); conn->conn_data_digest = CONN_DIGEST_CRC32C; break; case 2: log_debugx("target prefers not to do " "data digest; we'll comply"); break; default: log_warnx("target sent unrecognized " "DataDigest value \"%s\"; will use None", value); break; } } else if (strcmp(name, "MaxConnections") == 0) { /* Ignore. */ } else if (strcmp(name, "InitialR2T") == 0) { if (strcmp(value, "Yes") == 0) conn->conn_initial_r2t = true; else conn->conn_initial_r2t = false; } else if (strcmp(name, "ImmediateData") == 0) { if (strcmp(value, "Yes") == 0) conn->conn_immediate_data = true; else conn->conn_immediate_data = false; } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) log_errx(1, "received invalid " "MaxRecvDataSegmentLength"); if (tmp > isl->isl_max_send_data_segment_length) { log_debugx("capping max_send_data_segment_length " "from %d to %d", tmp, isl->isl_max_send_data_segment_length); tmp = isl->isl_max_send_data_segment_length; } conn->conn_max_send_data_segment_length = tmp; /* We received target's limit, that means it accepted our's. */ conn->conn_max_recv_data_segment_length = isl->isl_max_recv_data_segment_length; } else if (strcmp(name, "MaxBurstLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) log_errx(1, "received invalid MaxBurstLength"); if (tmp > isl->isl_max_burst_length) { log_debugx("capping MaxBurstLength " "from %d to %d", tmp, isl->isl_max_burst_length); tmp = isl->isl_max_burst_length; } conn->conn_max_burst_length = tmp; } else if (strcmp(name, "FirstBurstLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) log_errx(1, "received invalid FirstBurstLength"); if (tmp > isl->isl_first_burst_length) { log_debugx("capping FirstBurstLength " "from %d to %d", tmp, isl->isl_first_burst_length); tmp = isl->isl_first_burst_length; } conn->conn_first_burst_length = tmp; } else if (strcmp(name, "DefaultTime2Wait") == 0) { /* Ignore */ } else if (strcmp(name, "DefaultTime2Retain") == 0) { /* Ignore */ } else if (strcmp(name, "MaxOutstandingR2T") == 0) { /* Ignore */ } else if (strcmp(name, "DataPDUInOrder") == 0) { /* Ignore */ } else if (strcmp(name, "DataSequenceInOrder") == 0) { /* Ignore */ } else if (strcmp(name, "ErrorRecoveryLevel") == 0) { /* Ignore */ } else if (strcmp(name, "OFMarker") == 0) { /* Ignore */ } else if (strcmp(name, "IFMarker") == 0) { /* Ignore */ } else if (strcmp(name, "RDMAExtensions") == 0) { if (conn->conn_conf.isc_iser == 1 && strcmp(value, "Yes") != 0) { log_errx(1, "received unsupported RDMAExtensions"); } } else if (strcmp(name, "InitiatorRecvDataSegmentLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) log_errx(1, "received invalid " "InitiatorRecvDataSegmentLength"); if ((int)tmp > isl->isl_max_recv_data_segment_length) { log_debugx("capping InitiatorRecvDataSegmentLength " "from %d to %d", tmp, isl->isl_max_recv_data_segment_length); tmp = isl->isl_max_recv_data_segment_length; } conn->conn_max_recv_data_segment_length = tmp; } else if (strcmp(name, "TargetPortalGroupTag") == 0) { /* Ignore */ } else if (strcmp(name, "TargetRecvDataSegmentLength") == 0) { tmp = strtoul(value, NULL, 10); if (tmp <= 0) { log_errx(1, "received invalid TargetRecvDataSegmentLength"); } if (tmp > isl->isl_max_send_data_segment_length) { log_debugx("capping TargetRecvDataSegmentLength " "from %d to %d", tmp, isl->isl_max_send_data_segment_length); tmp = isl->isl_max_send_data_segment_length; } conn->conn_max_send_data_segment_length = tmp; } else { log_debugx("unknown key \"%s\"; ignoring", name); } } static void login_negotiate(struct connection *conn) { struct pdu *request, *response; struct keys *request_keys, *response_keys; struct iscsi_bhs_login_response *bhslr; int i, nrequests = 0; struct iscsi_session_limits *isl; log_debugx("beginning operational parameter negotiation"); request = login_new_request(conn, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); request_keys = keys_new(); isl = &conn->conn_limits; log_debugx("Limits for offload \"%s\" are " "MaxRecvDataSegment=%d, max_send_dsl=%d, " "MaxBurstLength=%d, FirstBurstLength=%d", conn->conn_conf.isc_offload, isl->isl_max_recv_data_segment_length, isl->isl_max_send_data_segment_length, isl->isl_max_burst_length, isl->isl_first_burst_length); /* * The following keys are irrelevant for discovery sessions. */ if (conn->conn_conf.isc_discovery == 0) { + keys_add(request_keys, "iSCSIProtocolLevel", "2"); if (conn->conn_conf.isc_header_digest != 0) keys_add(request_keys, "HeaderDigest", "CRC32C"); else keys_add(request_keys, "HeaderDigest", "None"); if (conn->conn_conf.isc_data_digest != 0) keys_add(request_keys, "DataDigest", "CRC32C"); else keys_add(request_keys, "DataDigest", "None"); keys_add(request_keys, "ImmediateData", "Yes"); keys_add_int(request_keys, "MaxBurstLength", isl->isl_max_burst_length); keys_add_int(request_keys, "FirstBurstLength", isl->isl_first_burst_length); keys_add(request_keys, "InitialR2T", "Yes"); keys_add(request_keys, "MaxOutstandingR2T", "1"); if (conn->conn_conf.isc_iser == 1) { keys_add_int(request_keys, "InitiatorRecvDataSegmentLength", isl->isl_max_recv_data_segment_length); keys_add_int(request_keys, "TargetRecvDataSegmentLength", isl->isl_max_send_data_segment_length); keys_add(request_keys, "RDMAExtensions", "Yes"); } else { keys_add_int(request_keys, "MaxRecvDataSegmentLength", isl->isl_max_recv_data_segment_length); } } else { keys_add(request_keys, "HeaderDigest", "None"); keys_add(request_keys, "DataDigest", "None"); keys_add_int(request_keys, "MaxRecvDataSegmentLength", isl->isl_max_recv_data_segment_length); } keys_add(request_keys, "DefaultTime2Wait", "0"); keys_add(request_keys, "DefaultTime2Retain", "0"); keys_add(request_keys, "ErrorRecoveryLevel", "0"); keys_save(request_keys, request); keys_delete(request_keys); request_keys = NULL; pdu_send(request); pdu_delete(request); request = NULL; response = login_receive(conn); response_keys = keys_new(); keys_load(response_keys, response); for (i = 0; i < KEYS_MAX; i++) { if (response_keys->keys_names[i] == NULL) break; login_negotiate_key(conn, response_keys->keys_names[i], response_keys->keys_values[i]); } keys_delete(response_keys); response_keys = NULL; for (;;) { bhslr = (struct iscsi_bhs_login_response *)response->pdu_bhs; if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0) break; nrequests++; if (nrequests > 5) { log_warnx("received login response " "without the \"T\" flag too many times; giving up"); break; } log_debugx("received login response " "without the \"T\" flag; sending another request"); pdu_delete(response); request = login_new_request(conn, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); pdu_send(request); pdu_delete(request); response = login_receive(conn); } if (login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE) log_warnx("received final login response with wrong NSG 0x%x", login_nsg(response)); pdu_delete(response); log_debugx("operational parameter negotiation done; " "transitioning to Full Feature phase"); } static void login_send_chap_a(struct connection *conn) { struct pdu *request; struct keys *request_keys; request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION); request_keys = keys_new(); keys_add(request_keys, "CHAP_A", "5"); keys_save(request_keys, request); keys_delete(request_keys); pdu_send(request); pdu_delete(request); } static void login_send_chap_r(struct pdu *response) { struct connection *conn; struct pdu *request; struct keys *request_keys, *response_keys; struct rchap *rchap; const char *chap_a, *chap_c, *chap_i; char *chap_r; int error; char *mutual_chap_c, *mutual_chap_i; /* * As in the rest of the initiator, 'request' means * 'initiator -> target', and 'response' means 'target -> initiator', * * So, here the 'response' from the target is the packet that contains * CHAP challenge; our CHAP response goes into 'request'. */ conn = response->pdu_connection; response_keys = keys_new(); keys_load(response_keys, response); /* * First, compute the response. */ chap_a = keys_find(response_keys, "CHAP_A"); if (chap_a == NULL) log_errx(1, "received CHAP packet without CHAP_A"); chap_c = keys_find(response_keys, "CHAP_C"); if (chap_c == NULL) log_errx(1, "received CHAP packet without CHAP_C"); chap_i = keys_find(response_keys, "CHAP_I"); if (chap_i == NULL) log_errx(1, "received CHAP packet without CHAP_I"); if (strcmp(chap_a, "5") != 0) { log_errx(1, "received CHAP packet " "with unsupported CHAP_A \"%s\"", chap_a); } rchap = rchap_new(conn->conn_conf.isc_secret); error = rchap_receive(rchap, chap_i, chap_c); if (error != 0) { log_errx(1, "received CHAP packet " "with malformed CHAP_I or CHAP_C"); } chap_r = rchap_get_response(rchap); rchap_delete(rchap); keys_delete(response_keys); request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION); request_keys = keys_new(); keys_add(request_keys, "CHAP_N", conn->conn_conf.isc_user); keys_add(request_keys, "CHAP_R", chap_r); free(chap_r); /* * If we want mutual authentication, we're expected to send * our CHAP_I/CHAP_C now. */ if (conn->conn_conf.isc_mutual_user[0] != '\0') { log_debugx("requesting mutual authentication; " "binary challenge size is %zd bytes", sizeof(conn->conn_mutual_chap->chap_challenge)); assert(conn->conn_mutual_chap == NULL); conn->conn_mutual_chap = chap_new(); mutual_chap_i = chap_get_id(conn->conn_mutual_chap); mutual_chap_c = chap_get_challenge(conn->conn_mutual_chap); keys_add(request_keys, "CHAP_I", mutual_chap_i); keys_add(request_keys, "CHAP_C", mutual_chap_c); free(mutual_chap_i); free(mutual_chap_c); } keys_save(request_keys, request); keys_delete(request_keys); pdu_send(request); pdu_delete(request); } static void login_verify_mutual(const struct pdu *response) { struct connection *conn; struct keys *response_keys; const char *chap_n, *chap_r; int error; conn = response->pdu_connection; response_keys = keys_new(); keys_load(response_keys, response); chap_n = keys_find(response_keys, "CHAP_N"); if (chap_n == NULL) log_errx(1, "received CHAP Response PDU without CHAP_N"); chap_r = keys_find(response_keys, "CHAP_R"); if (chap_r == NULL) log_errx(1, "received CHAP Response PDU without CHAP_R"); error = chap_receive(conn->conn_mutual_chap, chap_r); if (error != 0) log_errx(1, "received CHAP Response PDU with invalid CHAP_R"); if (strcmp(chap_n, conn->conn_conf.isc_mutual_user) != 0) { fail(conn, "Mutual CHAP failed"); log_errx(1, "mutual CHAP authentication failed: wrong user"); } error = chap_authenticate(conn->conn_mutual_chap, conn->conn_conf.isc_mutual_secret); if (error != 0) { fail(conn, "Mutual CHAP failed"); log_errx(1, "mutual CHAP authentication failed: wrong secret"); } keys_delete(response_keys); chap_delete(conn->conn_mutual_chap); conn->conn_mutual_chap = NULL; log_debugx("mutual CHAP authentication succeeded"); } static void login_chap(struct connection *conn) { struct pdu *response; log_debugx("beginning CHAP authentication; sending CHAP_A"); login_send_chap_a(conn); log_debugx("waiting for CHAP_A/CHAP_C/CHAP_I"); response = login_receive(conn); log_debugx("sending CHAP_N/CHAP_R"); login_send_chap_r(response); pdu_delete(response); /* * XXX: Make sure this is not susceptible to MITM. */ log_debugx("waiting for CHAP result"); response = login_receive(conn); if (conn->conn_conf.isc_mutual_user[0] != '\0') login_verify_mutual(response); pdu_delete(response); log_debugx("CHAP authentication done"); } void login(struct connection *conn) { struct pdu *request, *response; struct keys *request_keys, *response_keys; struct iscsi_bhs_login_response *bhslr2; const char *auth_method; int i; log_debugx("beginning Login phase; sending Login PDU"); request = login_new_request(conn, BHSLR_STAGE_SECURITY_NEGOTIATION); request_keys = keys_new(); if (conn->conn_conf.isc_mutual_user[0] != '\0') { keys_add(request_keys, "AuthMethod", "CHAP"); } else if (conn->conn_conf.isc_user[0] != '\0') { /* * Give target a chance to skip authentication if it * doesn't feel like it. * * None is first, CHAP second; this is to work around * what seems to be LIO (Linux target) bug: otherwise, * if target is configured with no authentication, * and we are configured to authenticate, the target * will erroneously respond with AuthMethod=CHAP * instead of AuthMethod=None, and will subsequently * fail the connection. This usually happens with * Discovery sessions, which default to no authentication. */ keys_add(request_keys, "AuthMethod", "None,CHAP"); } else { keys_add(request_keys, "AuthMethod", "None"); } keys_add(request_keys, "InitiatorName", conn->conn_conf.isc_initiator); if (conn->conn_conf.isc_initiator_alias[0] != '\0') { keys_add(request_keys, "InitiatorAlias", conn->conn_conf.isc_initiator_alias); } if (conn->conn_conf.isc_discovery == 0) { keys_add(request_keys, "SessionType", "Normal"); keys_add(request_keys, "TargetName", conn->conn_conf.isc_target); } else { keys_add(request_keys, "SessionType", "Discovery"); } keys_save(request_keys, request); keys_delete(request_keys); pdu_send(request); pdu_delete(request); response = login_receive(conn); response_keys = keys_new(); keys_load(response_keys, response); for (i = 0; i < KEYS_MAX; i++) { if (response_keys->keys_names[i] == NULL) break; /* * Not interested in AuthMethod at this point; we only need * to parse things such as TargetAlias. * * XXX: This is somewhat ugly. We should have a way to apply * all the keys to the session and use that by default * instead of discarding them. */ if (strcmp(response_keys->keys_names[i], "AuthMethod") == 0) continue; login_negotiate_key(conn, response_keys->keys_names[i], response_keys->keys_values[i]); } bhslr2 = (struct iscsi_bhs_login_response *)response->pdu_bhs; if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 && login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { if (conn->conn_conf.isc_mutual_user[0] != '\0') { log_errx(1, "target requested transition " "to operational parameter negotiation, " "but we require mutual CHAP"); } log_debugx("target requested transition " "to operational parameter negotiation"); keys_delete(response_keys); pdu_delete(response); login_negotiate(conn); return; } auth_method = keys_find(response_keys, "AuthMethod"); if (auth_method == NULL) log_errx(1, "received response without AuthMethod"); if (strcmp(auth_method, "None") == 0) { if (conn->conn_conf.isc_mutual_user[0] != '\0') { log_errx(1, "target does not require authantication, " "but we require mutual CHAP"); } log_debugx("target does not require authentication"); keys_delete(response_keys); pdu_delete(response); login_negotiate(conn); return; } if (strcmp(auth_method, "CHAP") != 0) { fail(conn, "Unsupported AuthMethod"); log_errx(1, "received response " "with unsupported AuthMethod \"%s\"", auth_method); } if (conn->conn_conf.isc_user[0] == '\0' || conn->conn_conf.isc_secret[0] == '\0') { fail(conn, "Authentication required"); log_errx(1, "target requests CHAP authentication, but we don't " "have user and secret"); } keys_delete(response_keys); response_keys = NULL; pdu_delete(response); response = NULL; login_chap(conn); login_negotiate(conn); }