Index: sys/dev/iscsi/icl_soft.c =================================================================== --- sys/dev/iscsi/icl_soft.c +++ sys/dev/iscsi/icl_soft.c @@ -1519,6 +1519,7 @@ return (error); } + #endif /* ICL_KERNEL_PROXY */ static int Index: sys/dev/iscsi/iscsi.h =================================================================== --- sys/dev/iscsi/iscsi.h +++ sys/dev/iscsi/iscsi.h @@ -36,6 +36,8 @@ struct iscsi_softc; struct icl_conn; +MALLOC_DECLARE(M_ISCSI); + #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' */ @@ -50,6 +52,8 @@ void *io_icl_prv; }; +struct iscsi_rchap; + struct iscsi_session { TAILQ_ENTRY(iscsi_session) is_next; @@ -90,9 +94,19 @@ bool is_waiting_for_iscsid; /* + * For boot session: + * Trigger kernel login thread now to perform login. + */ + bool is_trigger_kern_login; + + /* + * For userland-initiated sessions: * Some iscsid(8) instance is handling the session; * after login_timeout expires, kernel will wake up * another iscsid(8) to handle the session. + * + * For boot sessions: + * A boot session kernel thread is handling the session. */ bool is_login_phase; @@ -115,6 +129,7 @@ struct cv is_maintenance_cv; struct iscsi_softc *is_softc; unsigned int is_id; + bool is_boot_session; struct iscsi_session_conf is_conf; bool is_simq_frozen; @@ -124,8 +139,53 @@ struct cv is_login_cv; struct icl_pdu *is_login_pdu; #endif + + struct { + struct cv bl_login_cv; + struct thread *bl_login_thread; + struct iscsi_chap *bl_mutual_chap; + struct sockaddr_storage bl_from_ss; + struct sockaddr_storage bl_to_ss; + } is_boot_login; }; +#define ISCSI_DEBUG(X, ...) \ + do { \ + if (iscsi_debug > 1) \ + printf("%s: " X "\n", __func__, ## __VA_ARGS__);\ + } while (0) + +#define ISCSI_WARN(X, ...) \ + do { \ + if (iscsi_debug > 0) { \ + printf("WARNING: %s: " X "\n", \ + __func__, ## __VA_ARGS__); \ + } \ + } while (0) + +#define ISCSI_SESSION_DEBUG(S, X, ...) \ + do { \ + if (iscsi_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 (iscsi_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) + struct iscsi_softc { device_t sc_dev; struct sx sc_lock; @@ -138,4 +198,88 @@ eventhandler_tag sc_shutdown_post_eh; }; +#define ISCSI_KEYS_MAX 1024 + +struct iscsi_keys { + char *ik_names[ISCSI_KEYS_MAX]; + char *ik_values[ISCSI_KEYS_MAX]; + char *ik_data; + size_t ik_data_len; +}; + +#define CHAP_CHALLENGE_LEN 1024 +#define CHAP_DIGEST_LEN 16 /* Equal to MD5 digest size. */ + +struct iscsi_chap { + unsigned char chap_id; + char chap_challenge[CHAP_CHALLENGE_LEN]; + char chap_response[CHAP_DIGEST_LEN]; +}; + +struct iscsi_rchap { + char *rchap_secret; + unsigned char rchap_id; + void *rchap_challenge; + size_t rchap_challenge_len; +}; + +struct iscsi_kernel_login { + struct iscsi_session *ikl_is; + struct icl_drv_limits ikl_idl; +}; + +struct iscsi_kernel_handoff { + char ikh_target_alias[ISCSI_ALIAS_LEN]; + int ikh_protocol_level; + int ikh_header_digest; + int ikh_data_digest; + int ikh_immediate_data; + int ikh_initial_r2t; + int ikh_max_recv_data_segment_length; + int ikh_max_send_data_segment_length; + int ikh_max_burst_length; + int ikh_first_burst_length; + int ikh_tsid; +}; + +extern int iscsi_debug; + +struct iscsi_chap *iscsi_chap_new(void); +char *iscsi_chap_get_id(const struct iscsi_chap *chap); +char *iscsi_chap_get_challenge( + const struct iscsi_chap *chap); +int iscsi_chap_receive(struct iscsi_chap *chap, + const char *response); +int iscsi_chap_authenticate(struct iscsi_chap *chap, + const char *secret); +void iscsi_chap_delete(struct iscsi_chap *chap); + +struct iscsi_rchap *iscsi_rchap_new(const char *secret); +int iscsi_rchap_receive(struct iscsi_rchap *rchap, + const char *id, const char *challenge); +char *iscsi_rchap_get_response(struct iscsi_rchap *rchap); +void iscsi_rchap_delete(struct iscsi_rchap *rchap); + +struct iscsi_keys *iscsi_keys_new(int mflags); +void iscsi_keys_delete(struct iscsi_keys *ik); +int iscsi_keys_load(struct iscsi_keys *ik, + struct icl_pdu *ip, int mflags); +int iscsi_keys_save(struct iscsi_keys *ik, + struct icl_pdu *ip, int mflags); +const char *iscsi_keys_find(struct iscsi_keys *ik, + const char *name); +int iscsi_keys_add(struct iscsi_keys *ik, + const char *name, const char *value, int mflags); +int iscsi_keys_add_int(struct iscsi_keys *ik, + const char *name, int value, int mflags); + +int iscsi_login(struct iscsi_kernel_login *login, + struct iscsi_kernel_handoff *handoff); + +/* iscsi_base64.c Should be moved to libkern */ +int iscsi_b64_ntop(u_char const *src, size_t srclength, + char *target, size_t targsize); +int iscsi_b64_pton(const char *src, u_char *target, + size_t targsize); + #endif /* !ISCSI_H */ Index: sys/dev/iscsi/iscsi.c =================================================================== --- sys/dev/iscsi/iscsi.c +++ sys/dev/iscsi/iscsi.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -45,7 +46,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -65,14 +69,41 @@ #include #include +#include #include #include #include +#include +#include +#include +#include +#include + +#include +#include +#include + #ifdef ICL_KERNEL_PROXY #include #endif +/* + * Boot session configuration. + */ +struct iscsi_boot_session_conf { + char isc_initiator[ISCSI_NAME_LEN]; + struct sockaddr_storage isc_initiator_sa; + char isc_initiator_alias[ISCSI_ALIAS_LEN]; + char isc_target[ISCSI_NAME_LEN]; + struct sockaddr_storage isc_target_sa; + int isc_auth_type; + 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]; +}; + #ifdef ICL_KERNEL_PROXY FEATURE(iscsi_kernel_proxy, "iSCSI initiator built with ICL_KERNEL_PROXY"); #endif @@ -85,9 +116,9 @@ SYSCTL_NODE(_kern, OID_AUTO, iscsi, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "iSCSI initiator"); -static int debug = 1; +int iscsi_debug = 1; SYSCTL_INT(_kern_iscsi, OID_AUTO, debug, CTLFLAG_RWTUN, - &debug, 0, "Enable debug messages"); + &iscsi_debug, 0, "Enable debug messages"); static int ping_timeout = 5; SYSCTL_INT(_kern_iscsi, OID_AUTO, ping_timeout, CTLFLAG_RWTUN, &ping_timeout, @@ -108,49 +139,12 @@ 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"); +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); @@ -167,6 +161,7 @@ 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_login_response(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); @@ -181,6 +176,7 @@ uint32_t *initiator_task_tagp); static void iscsi_outstanding_remove(struct iscsi_session *is, struct iscsi_outstanding *io); +static void iscsi_boot_login_thread(void *); static bool iscsi_pdu_prepare(struct icl_pdu *request) @@ -437,15 +433,25 @@ 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); + if (!is->is_boot_session) { + /* + * 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); + } else { + is->is_trigger_kern_login = true; + strlcpy(is->is_reason, "Boot session connecting", + sizeof(is->is_reason)); + is->is_timeout = 0; + ISCSI_SESSION_UNLOCK(is); + cv_signal(&is->is_boot_login.bl_login_cv); + } } static void @@ -458,6 +464,13 @@ TAILQ_REMOVE(&sc->sc_sessions, is, is_next); sx_xunlock(&sc->sc_lock); + ISCSI_SESSION_LOCK(is); + if (is->is_boot_session && is->is_boot_login.bl_login_thread != NULL) { + cv_signal(&is->is_boot_login.bl_login_cv); + cv_wait(&is->is_boot_login.bl_login_cv, &is->is_lock); + } + ISCSI_SESSION_UNLOCK(is); + icl_conn_close(is->is_conn); callout_drain(&is->is_callout); @@ -488,6 +501,8 @@ #ifdef ICL_KERNEL_PROXY cv_destroy(&is->is_login_cv); #endif + if (is->is_boot_session) + cv_destroy(&is->is_boot_login.bl_login_cv); ISCSI_SESSION_DEBUG(is, "terminated"); free(is, M_ISCSI); @@ -787,6 +802,10 @@ /* Session lock dropped inside. */ ISCSI_SESSION_LOCK_ASSERT_NOT(is); break; + case ISCSI_BHS_OPCODE_LOGIN_RESPONSE: + iscsi_pdu_handle_login_response(response); + ISCSI_SESSION_UNLOCK(is); + break; case ISCSI_BHS_OPCODE_LOGOUT_RESPONSE: iscsi_pdu_handle_logout_response(response); ISCSI_SESSION_UNLOCK(is); @@ -1206,6 +1225,14 @@ icl_pdu_free(response); } +static void +iscsi_pdu_handle_login_response(struct icl_pdu *response) +{ + + ISCSI_SESSION_DEBUG(PDU_SESSION(response), "login response"); + icl_pdu_free(response); +} + static void iscsi_pdu_handle_logout_response(struct icl_pdu *response) { @@ -1460,6 +1487,60 @@ } } +static int +iscsi_prepare_sim_post_login(struct iscsi_session *is) +{ + int error; + + 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(is->is_conn->ic_maxtags); + if (is->is_devq == NULL) { + ISCSI_SESSION_UNLOCK(is); + ISCSI_SESSION_WARN(is, "failed to allocate simq"); + return (ENOMEM); + } + + is->is_sim = cam_sim_alloc(iscsi_action, NULL, "iscsi", + is, is->is_id /* unit */, &is->is_lock, + 1, is->is_conn->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); + return (ENOMEM); + } + + if (xpt_bus_register(is->is_sim, NULL, 0) != 0) { + ISCSI_SESSION_UNLOCK(is); + ISCSI_SESSION_WARN(is, "failed to register bus"); + 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"); + return (ENOMEM); + } + ISCSI_SESSION_UNLOCK(is); + } + return (0); +} + static int iscsi_ioctl_daemon_handoff(struct iscsi_softc *sc, struct iscsi_daemon_handoff *handoff) @@ -1557,55 +1638,10 @@ 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_UNLOCK(is); - ISCSI_SESSION_WARN(is, "failed to allocate simq"); - iscsi_session_terminate(is); - return (ENOMEM); - } - - is->is_sim = cam_sim_alloc(iscsi_action, NULL, "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); - } - - if (xpt_bus_register(is->is_sim, NULL, 0) != 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); + error = iscsi_prepare_sim_post_login(is); + if (error) { + iscsi_session_terminate(is); + return (error); } return (0); @@ -1975,6 +2011,165 @@ return (0); } +static bool +iscsi_is_addr_v4mapped(uint8_t *addr) +{ + uint32_t *addr4 = (uint32_t *)addr; + + if (be32toh(addr4[0]) == 0 && be32toh(addr4[1]) == 0 && + be32toh(addr4[2]) == 0x0000ffffU) + return (true); + return (false); +} + +static void +iscsi_sin2p(char *dst, const struct sockaddr_storage *sa, bool is_tgt __unused) +{ + const struct sockaddr_in *sin; + const struct sockaddr_in6 *sin6; + + KASSERT(sa->ss_family == AF_INET || sa->ss_family == AF_INET6, + ("%s: Bogus %s address family", __func__, + is_tgt ? "target" : "initiator")); + if (sa->ss_family == AF_INET) { + KASSERT(sa->ss_len == sizeof(*sin), + ("%s: Bogus initiator sockaddr size", __func__)); + sin = (const struct sockaddr_in *)sa; + inet_ntop(AF_INET, &sin->sin_addr, dst, ISCSI_ADDR_LEN); + } else { + KASSERT(sa->ss_len == sizeof(*sin6), + ("%s: Bogus initiator sockaddr size", __func__)); + sin6 = (const struct sockaddr_in6 *)sa; + inet_ntop(AF_INET6, &sin6->sin6_addr, dst, ISCSI_ADDR_LEN); + } +} + +static void +iscsi_fill_is_conf(struct iscsi_session_conf *dst, + const struct iscsi_boot_session_conf *src) +{ + bzero(dst, sizeof(*dst)); + strlcpy(dst->isc_initiator, src->isc_initiator, + sizeof(dst->isc_initiator)); + iscsi_sin2p(dst->isc_initiator_addr, &src->isc_initiator_sa, false); + strlcpy(dst->isc_target, src->isc_target, sizeof(dst->isc_target)); + iscsi_sin2p(dst->isc_target_addr, &src->isc_target_sa, true); + if (src->isc_auth_type == IBFT_CHAP_CHAP || + src->isc_auth_type == IBFT_CHAP_MUTUAL) { + strlcpy(dst->isc_user, src->isc_user, sizeof(dst->isc_user)); + strlcpy(dst->isc_secret, src->isc_secret, + sizeof(dst->isc_secret)); + if (src->isc_auth_type == IBFT_CHAP_MUTUAL) { + strlcpy(dst->isc_mutual_user, src->isc_mutual_user, + sizeof(dst->isc_mutual_user)); + strlcpy(dst->isc_mutual_secret, src->isc_mutual_secret, + sizeof(dst->isc_mutual_secret)); + } + } + dst->isc_discovery = 0; + dst->isc_enable = 1; +} + +static int +iscsi_boot_session_add(struct iscsi_softc *sc, + struct iscsi_boot_session_conf *conf) +{ + struct iscsi_session *is, *is2; + int error; + + is = malloc(sizeof(*is), M_ISCSI, M_ZERO | M_WAITOK); + iscsi_fill_is_conf(&is->is_conf, conf); + + 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_boot_login.bl_from_ss = conf->isc_initiator_sa; + is->is_boot_login.bl_to_ss = conf->isc_target_sa; + + /* + * XXX: There should be no offload and iSER for now. + * Offloading is actually desirable. + */ + 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_boot_session = true; + cv_init(&is->is_boot_login.bl_login_cv, "iscsi_boot_login"); + + /* + * 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_conn->ic_max_recv_data_segment_length = 8192; + is->is_conn->ic_max_send_data_segment_length = 8192; + is->is_max_burst_length = 262144; + is->is_first_burst_length = 65536; + + 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); + + is->is_trigger_kern_login = true; + error = kthread_add(iscsi_boot_login_thread, is, NULL, + &is->is_boot_login.bl_login_thread, 0, 0, "iscsiblt"); + if (error != 0) + ISCSI_SESSION_WARN(is, "kthread_add(9) failed with error %d", error); + sx_xunlock(&sc->sc_lock); + return (error); +} + static bool iscsi_session_conf_matches(unsigned int id1, const struct iscsi_session_conf *c1, unsigned int id2, const struct iscsi_session_conf *c2) @@ -2003,8 +2198,10 @@ sx_xlock(&sc->sc_lock); TAILQ_FOREACH_SAFE(is, &sc->sc_sessions, is_next, tmp) { ISCSI_SESSION_LOCK(is); + /* Do not remove boot sessions when wildcard matching */ if (iscsi_session_conf_matches(is->is_id, &is->is_conf, - isr->isr_session_id, &isr->isr_conf)) { + isr->isr_session_id, &isr->isr_conf) && + !(isr->isr_session_id == 0 && is->is_boot_session)) { found = true; iscsi_session_logout(is); iscsi_session_terminate(is); @@ -2616,6 +2813,430 @@ } } +static void +iscsi_boot_login_thread(void *arg) +{ + struct sockaddr_storage from_ss, to_ss; + struct sockaddr *from_sap; + struct iscsi_kernel_handoff handoff; + struct iscsi_kernel_login login; + struct iscsi_session *is; + struct icl_conn *ic; + int error; + + is = (struct iscsi_session *)arg; + KASSERT(is->is_boot_session, ("%s: launched on user session", + __func__)); + + ISCSI_SESSION_LOCK(is); + ic = is->is_conn; + do { + while (!is->is_trigger_kern_login && !is->is_terminating) + cv_wait(&is->is_boot_login.bl_login_cv, &is->is_lock); + if (is->is_terminating) + break; + + is->is_trigger_kern_login = false; + is->is_login_phase = true; + memcpy(&from_ss, &is->is_boot_login.bl_from_ss, + sizeof(from_ss)); + memcpy(&to_ss, &is->is_boot_login.bl_to_ss, sizeof(to_ss)); + is->is_statsn = 0; + is->is_cmdsn = 0; + is->is_expcmdsn = 0; + is->is_maxcmdsn = 0; + is->is_timeout = 0; + is->is_reason[0] = '\0'; + + /* + * Digests are always disabled during login phase. + */ + ic->ic_header_crc32c = false; + ic->ic_data_crc32c = false; + + memset(&handoff, 0, sizeof(handoff)); + handoff.ikh_header_digest = ISCSI_DIGEST_NONE; + handoff.ikh_data_digest = ISCSI_DIGEST_NONE; + handoff.ikh_immediate_data = true; + ic->ic_max_recv_data_segment_length = + handoff.ikh_max_recv_data_segment_length = 8192; + ic->ic_max_send_data_segment_length = + handoff.ikh_max_send_data_segment_length = 8192; + handoff.ikh_max_burst_length = 262144; + handoff.ikh_first_burst_length = 65536; + handoff.ikh_tsid = 0; + ISCSI_SESSION_UNLOCK(is); + + if (from_ss.ss_len != 0) + from_sap = (struct sockaddr *)&from_ss; + else + from_sap = NULL; + error = icl_conn_connect(ic, to_ss.ss_family, SOCK_STREAM, 0, + from_sap, (struct sockaddr *)&to_ss); + if (error != 0) { + ISCSI_SESSION_LOCK(is); + snprintf(is->is_reason, sizeof(is->is_reason), + "Failed to connect to endpoint. error: %d", error); + ISCSI_SESSION_DEBUG(is, "%s", is->is_reason); + ISCSI_SESSION_UNLOCK(is); + goto notify; + } + login.ikl_is = is; + error = icl_limits(ic->ic_offload, ic->ic_iser, &login.ikl_idl); + if (error != 0) { + ISCSI_SESSION_LOCK(is); + snprintf(is->is_reason, sizeof(is->is_reason), + "icl_limits for offload \"%s\" " + "failed with error %d", is->is_conf.isc_offload, + error); + ISCSI_SESSION_UNLOCK(is); + goto notify; + } + + ISCSI_SESSION_LOCK(is); + if (is->is_terminating) + break; + do { + error = iscsi_login(&login, &handoff); + } while (error == EAGAIN); + if (error != 0) { + ISCSI_SESSION_UNLOCK(is); + goto notify; + } + + strlcpy(is->is_target_alias, handoff.ikh_target_alias, + sizeof(is->is_target_alias)); + is->is_protocol_level = handoff.ikh_protocol_level; + is->is_initial_r2t = handoff.ikh_initial_r2t; + is->is_immediate_data = handoff.ikh_immediate_data; + + ic->ic_max_recv_data_segment_length = + handoff.ikh_max_recv_data_segment_length; + ic->ic_max_send_data_segment_length = + handoff.ikh_max_send_data_segment_length; + is->is_max_burst_length = handoff.ikh_max_burst_length; + is->is_first_burst_length = handoff.ikh_first_burst_length; + + if (handoff.ikh_header_digest == ISCSI_DIGEST_CRC32C) + ic->ic_header_crc32c = true; + else + ic->ic_header_crc32c = false; + if (handoff.ikh_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); + + error = iscsi_prepare_sim_post_login(is); + if (error) { + ISCSI_SESSION_LOCK(is); + iscsi_session_terminate(is); + break; + } + +notify: + ISCSI_SESSION_LOCK(is); + cv_signal(&is->is_boot_login.bl_login_cv); + } while (1); + + is->is_boot_login.bl_login_thread = NULL; + cv_signal(&is->is_boot_login.bl_login_cv); + ISCSI_SESSION_UNLOCK(is); + kthread_exit(); +} + +static u_int +iscsi_match_lladdr(void *arg, struct sockaddr_dl *sadl, u_int count) +{ + struct ibft_i_nic_s *nic; + + nic = (struct ibft_i_nic_s *)arg; + if (bcmp(nic->ns_mac, LLADDR(sadl), sizeof(nic->ns_mac)) == 0) + return (1); + return (0); +} + +static int +iscsi_ibft_configure_nic(struct ibft_i_nic_s *nic) +{ + struct sockaddr_in gwsa, gwmasksa, zerodstsa; + struct sockaddr_in6 gwsa6, gwmasksa6, zerodstsa6; + struct sockaddr *gwsap, *gwmasksap, *zerodstsap; + struct in_aliasreq ifra; + struct in6_aliasreq ifra6; + struct rt_addrinfo info; + struct rib_cmd_info rc; + struct ifnet *ifp; + struct socket *so; + struct thread *td; + struct ifreq ifr; + void *ifrap; + int prefixlen; + u_long ioc; + bool is_v6; + int error; + int i; + + td = curthread; + prefixlen = nic->ns_prefixlen; + ifrap = &ifra6; + ioc = SIOCAIFADDR_IN6; + is_v6 = !iscsi_is_addr_v4mapped(nic->ns_ip); + bzero(&gwsa, sizeof(gwsa)); + bzero(&gwsa6, sizeof(gwsa6)); + bzero(&gwmasksa, sizeof(gwmasksa)); + bzero(&gwmasksa6, sizeof(gwmasksa6)); + bzero(&zerodstsa, sizeof(zerodstsa)); + bzero(&zerodstsa6, sizeof(zerodstsa6)); + gwsap = sin6tosa(&gwsa6); + gwmasksap = sin6tosa(&gwmasksa6); + zerodstsap = sin6tosa(&zerodstsa6); + bzero(&ifra6, sizeof(ifra6)); + bzero(&ifra, sizeof(ifra)); + bzero(&ifr, sizeof(ifr)); + + CURVNET_SET(TD_TO_VNET(td)); + IFNET_RLOCK(); + CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) { + if (if_foreach_lladdr(ifp, iscsi_match_lladdr, nic) != 0) { + strlcpy(ifr.ifr_name, ifp->if_xname, + sizeof(ifr.ifr_name)); + break; + } + } + IFNET_RUNLOCK(); + CURVNET_RESTORE(); + if (ifr.ifr_name[0] == '\0') { + ISCSI_WARN("No matching MAC for NIC%hhu." + "MAC wanted: %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + nic->ns_idx, nic->ns_mac[0], nic->ns_mac[1], nic->ns_mac[2], + nic->ns_mac[3], nic->ns_mac[4], nic->ns_mac[5]); + return (ENODEV); + } + + error = socreate(AF_INET, &so, SOCK_DGRAM, 0, td->td_ucred, td); + if (error != 0) { + ISCSI_WARN("Failed creating socket"); + return (error); + } + ISCSI_DEBUG("Finished creating socket for addr setting"); + + /* Bring up the interface so we can set addr */ + error = ifioctl(so, SIOCGIFFLAGS, (caddr_t)&ifr, td); + if (error) { + ISCSI_WARN("ifioctl SIOCGIFFLAGS\n"); + goto out; + } + ISCSI_DEBUG("Finished getting interface flags"); + ifr.ifr_flags |= IFF_UP; + error = ifioctl(so, SIOCSIFFLAGS, (caddr_t)&ifr, td); + if (error) { + ISCSI_WARN("ifioctl SIOCSIFFLAGS\n"); + goto out; + } + ISCSI_DEBUG("Finished bringing up interface"); + + ifra6.ifra_addr.sin6_len = sizeof(ifra6.ifra_addr); + ifra6.ifra_addr.sin6_family = AF_INET6; + memcpy(&ifra6.ifra_addr.sin6_addr, nic->ns_ip, + sizeof(ifra6.ifra_addr.sin6_addr)); + if (!is_v6) { + strlcpy(ifra.ifra_name, ifr.ifr_name, + sizeof(ifra.ifra_name)); + in6_sin6_2_sin(&ifra.ifra_addr, &ifra6.ifra_addr); + ifra.ifra_mask.sin_len = sizeof(ifra.ifra_mask); + ifra.ifra_mask.sin_family = AF_INET; + if (prefixlen > 32) { + error = EINVAL; + goto out; + } + ifra.ifra_mask.sin_addr.s_addr = + htonl(~0 << (32 - prefixlen)); + bcopy(&ifra.ifra_addr, &ifra.ifra_broadaddr, + sizeof(ifra.ifra_broadaddr)); + ifra.ifra_broadaddr.sin_addr.s_addr |= + ~ifra.ifra_mask.sin_addr.s_addr; + ifrap = &ifra; + ioc = SIOCAIFADDR; + ISCSI_DEBUG("Interface %s - IP: %hhu.%hhu.%hhu.%hhu. " + "Netmask: %hhu.%hhu.%hhu.%hhu", + ifra.ifra_name, + ((char *)&ifra.ifra_addr.sin_addr.s_addr)[0], + ((char *)&ifra.ifra_addr.sin_addr.s_addr)[1], + ((char *)&ifra.ifra_addr.sin_addr.s_addr)[2], + ((char *)&ifra.ifra_addr.sin_addr.s_addr)[3], + ((char *)&ifra.ifra_mask.sin_addr.s_addr)[0], + ((char *)&ifra.ifra_mask.sin_addr.s_addr)[1], + ((char *)&ifra.ifra_mask.sin_addr.s_addr)[2], + ((char *)&ifra.ifra_mask.sin_addr.s_addr)[3]); + } else { + strlcpy(ifra6.ifra_name, ifr.ifr_name, + sizeof(ifra6.ifra_name)); + ifra6.ifra_prefixmask.sin6_len = sizeof(ifra6.ifra_prefixmask); + ifra6.ifra_prefixmask.sin6_family = AF_INET6; + if (prefixlen > 128) { + error = EINVAL; + goto out; + } + for (i = 0; i < prefixlen / 8; i++) + ifra6.ifra_prefixmask.sin6_addr.s6_addr[i] = 0xff; + if (prefixlen % 8 != 0) { + ifra6.ifra_prefixmask.sin6_addr.s6_addr[i] = + ~0 << (8 - (prefixlen % 8)); + } + ifra6.ifra_lifetime.ia6t_expire = 0; + ifra6.ifra_lifetime.ia6t_preferred = 0; + ifra6.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; + ifra6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; + } + error = ifioctl(so, ioc, ifrap, td); + if (error != 0) { + ISCSI_WARN("Failed setting interface addr. error %d", + error); + goto out; + } + ISCSI_DEBUG("Finished addr setting"); + + if (bcmp(nic->ns_gateway, &in6addr_any, sizeof(in6addr_any))) { + /* + * Gateway is set in the NIC + */ + + gwsa6.sin6_len = sizeof(gwsa); + gwsa6.sin6_family = AF_INET6; + memcpy(&gwsa6.sin6_addr, nic->ns_ip, sizeof(gwsa6.sin6_addr)); + gwmasksa6.sin6_len = sizeof(gwsa6); + gwmasksa6.sin6_family = AF_INET6; + zerodstsa6.sin6_len = sizeof(zerodstsa6); + zerodstsa6.sin6_family = AF_INET6; + if (!is_v6) { + in6_sin6_2_sin(&gwsa, &gwsa6); + in6_sin6_2_sin(&gwmasksa, &gwmasksa6); + in6_sin6_2_sin(&zerodstsa, &zerodstsa6); + + gwsap = sintosa(&gwsa); + gwmasksap = sintosa(&gwmasksa); + zerodstsap = sintosa(&zerodstsa); + } + + info.rti_flags = RTF_UP | RTF_GATEWAY; + info.rti_info[RTAX_DST] = zerodstsap; + info.rti_info[RTAX_GATEWAY] = gwsap; + info.rti_info[RTAX_NETMASK] = gwmasksap; + error = rib_action(RT_DEFAULT_FIB, RTM_ADD, &info, &rc); + if (error != 0) { + ISCSI_WARN("Failed setting up routing table. error %d", + error); + goto out; + } + } +out: + soclose(so); + return (error); +} + +static int +iscsi_ibft_add_target(struct ibft_i_tgt_s *target) +{ + struct iscsi_boot_session_conf *conf; + struct sockaddr_in6 srcsa6, dstsa6; + struct ibft_i_nic_s *nic; + bool is_v4; + int error; + + conf = (struct iscsi_boot_session_conf *)malloc(sizeof(*conf), M_ISCSI, + M_ZERO | M_NOWAIT); + if (conf == NULL) + return (ENOMEM); + nic = &ibft_nics[target->ts_nic_idx]; + if (!nic->ns_present) { + ISCSI_WARN("No target presented in the IBFT table"); + error = ENODEV; + goto out; + } + is_v4 = iscsi_is_addr_v4mapped(target->ts_ip); + + error = iscsi_ibft_configure_nic(nic); + if (error != 0) { + ISCSI_WARN("Failed to configure NIC%hhu for iSCSI target%hhu", + target->ts_nic_idx, target->ts_idx); + goto out; + } + ISCSI_DEBUG("Finished configuring NIC%hhu for iSCSI target%hhu", + target->ts_nic_idx, target->ts_idx); + + bzero(&srcsa6, sizeof(srcsa6)); + srcsa6.sin6_len = sizeof(srcsa6); + srcsa6.sin6_family = AF_INET6; + memcpy(&srcsa6.sin6_addr, nic->ns_ip, sizeof(nic->ns_ip)); + if (is_v4) { + in6_sin6_2_sin((struct sockaddr_in *)&conf->isc_initiator_sa, + &srcsa6); + } else { + memcpy(&conf->isc_initiator_sa, &srcsa6, sizeof(srcsa6)); + } + + iscsi_ibft_getstr(conf->isc_initiator, + sizeof(conf->isc_initiator), ibft_initiator->is_name, + ibft_initiator->is_name_len); + iscsi_ibft_getstr(conf->isc_target, + sizeof(conf->isc_target), target->ts_tgt_name, + target->ts_tgt_name_len); + bzero(&dstsa6, sizeof(dstsa6)); + dstsa6.sin6_len = sizeof(dstsa6); + dstsa6.sin6_family = AF_INET6; + dstsa6.sin6_port = htons(target->ts_port); + memcpy(&dstsa6.sin6_addr.s6_addr, target->ts_ip, sizeof(target->ts_ip)); + if (is_v4) { + in6_sin6_2_sin((struct sockaddr_in *)&conf->isc_target_sa, + &dstsa6); + } else { + memcpy(&conf->isc_target_sa, &dstsa6, sizeof(dstsa6)); + } + conf->isc_auth_type = target->ts_chap_type; + iscsi_ibft_getstr(conf->isc_user, sizeof(conf->isc_user), + target->ts_chap_name, target->ts_chap_name_len); + iscsi_ibft_getstr(conf->isc_secret, sizeof(conf->isc_secret), + target->ts_chap_secret, target->ts_chap_secret_len); + iscsi_ibft_getstr(conf->isc_mutual_user, sizeof(conf->isc_mutual_user), + target->ts_rchap_name, target->ts_rchap_name_len); + iscsi_ibft_getstr(conf->isc_mutual_secret, + sizeof(conf->isc_mutual_secret), target->ts_rchap_secret, + target->ts_rchap_secret_len); + + error = iscsi_boot_session_add(sc, conf); + if (error) + ISCSI_WARN("Failed to add iSCSI target %s", conf->isc_target); + ISCSI_DEBUG("Finished adding iSCSI target %s", conf->isc_target); + +out: + free(conf, M_ISCSI); + return (error); +} + +static void +iscsi_ibft_sysinit(void *arg __unused) +{ + struct ibft_i_tgt_s *target; + + iscsi_ibft_init(); + TAILQ_FOREACH(target, &ibft_targets_list, ts_entry) { + iscsi_ibft_add_target(target); + } +} +SYSINIT(iscsi_ibft_sysinit, SI_SUB_LAST, SI_ORDER_ANY, iscsi_ibft_sysinit, + NULL); + static int iscsi_load(void) { @@ -2656,6 +3277,8 @@ iscsi_unload(void) { + iscsi_ibft_fini(); + /* Awaken any threads asleep in iscsi_ioctl(). */ sx_xlock(&sc->sc_lock); sc->sc_unloading = true; Index: sys/dev/iscsi/iscsi_base64.c =================================================================== --- /dev/null +++ sys/dev/iscsi/iscsi_base64.c @@ -0,0 +1,320 @@ +/* + * Copyright (c) 1996, 1998 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Portions Copyright (c) 1995 by International Business Machines, Inc. + * + * International Business Machines, Inc. (hereinafter called IBM) grants + * permission under its copyrights to use, copy, modify, and distribute this + * Software with or without fee, provided that the above copyright notice and + * all paragraphs of this notice appear in all copies, and that the name of IBM + * not be used in connection with the marketing of any product incorporating + * the Software or modifications thereof, without specific, written prior + * permission. + * + * To the extent it has a right to do so, IBM grants an immunity from suit + * under its patents, if any, for the use, sale or manufacture of products to + * the extent that such products are used for performing Domain Name System + * dynamic updates in TCP/IP networks by means of the Software. No immunity is + * granted for any product per se or for any other function of any product. + * + * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN + * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include + +#include "icl.h" +#include "icl_wrappers.h" +#include "iscsi_ioctl.h" +#include "iscsi_proto.h" +#include "iscsi.h" + +#define Assert(Cond) if (!(Cond)) panic(#Cond) + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt) + The following encoding technique is taken from RFC 1521 by Borenstein + and Freed. It is reproduced here in a slightly edited form for + convenience. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8-bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a quantity. When fewer than 24 input + bits are available in an input group, zero bits are added (on the + right) to form an integral number of 6-bit groups. Padding at the + end of the data is performed using the '=' character. + + Since all base64 input is an integral number of octets, only the + ------------------------------------------------- + following cases can arise: + + (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded + output will be an integral multiple of 4 characters + with no "=" padding, + (2) the final quantum of encoding input is exactly 8 bits; + here, the final unit of encoded output will be two + characters followed by two "=" padding characters, or + (3) the final quantum of encoding input is exactly 16 bits; + here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + +int +iscsi_b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize) { + size_t datalength = 0; + u_char input[3]; + u_char output[4]; + size_t i; + + while (2 < srclength) { + input[0] = *src++; + input[1] = *src++; + input[2] = *src++; + srclength -= 3; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + output[3] = input[2] & 0x3f; + Assert(output[0] < 64); + Assert(output[1] < 64); + Assert(output[2] < 64); + Assert(output[3] < 64); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + target[datalength++] = Base64[output[2]]; + target[datalength++] = Base64[output[3]]; + } + + /* Now we worry about padding. */ + if (0 != srclength) { + /* Get what's left. */ + input[0] = input[1] = input[2] = '\0'; + for (i = 0; i < srclength; i++) + input[i] = *src++; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + Assert(output[0] < 64); + Assert(output[1] < 64); + Assert(output[2] < 64); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + if (srclength == 1) + target[datalength++] = Pad64; + else + target[datalength++] = Base64[output[2]]; + target[datalength++] = Pad64; + } + if (datalength >= targsize) + return (-1); + target[datalength] = '\0'; /* Returned value doesn't count \0. */ + return (datalength); +} + +/* skips all whitespace anywhere. + converts characters, four at a time, starting at (or after) + src from base - 64 numbers into three 8 bit bytes in the target area. + it returns the number of data bytes stored at the target, or -1 on error. + */ + +int +iscsi_b64_pton(const char *src, u_char *target, size_t targsize) +{ + int tarindex, state, ch; + u_char nextbyte; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = *src++) != '\0') { + if (isspace((unsigned char)ch)) /* Skip whitespace anywhere. */ + continue; + + if (ch == Pad64) + break; + + pos = strchr(Base64, ch); + if (pos == NULL) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if ((size_t)tarindex >= targsize) + return (-1); + target[tarindex] = (pos - Base64) << 2; + } + state = 1; + break; + case 1: + if (target) { + if ((size_t)tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 4; + nextbyte = ((pos - Base64) & 0x0f) << 4; + if ((size_t)tarindex + 1 < targsize) + target[tarindex + 1] = nextbyte; + else if (nextbyte) + return (-1); + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + if ((size_t)tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 2; + nextbyte = ((pos - Base64) & 0x03) << 6; + if ((size_t)tarindex + 1 < targsize) + target[tarindex + 1] = nextbyte; + else if (nextbyte) + return (-1); + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + if ((size_t)tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + default: + __assert_unreachable(); + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (!isspace((unsigned char)ch)) + break; + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) + return (-1); + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (!isspace((unsigned char)ch)) + return (-1); + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target && (size_t)tarindex < targsize && + target[tarindex] != 0) + return (-1); + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + return (tarindex); +} Index: sys/dev/iscsi/iscsi_chap.c =================================================================== --- /dev/null +++ sys/dev/iscsi/iscsi_chap.c @@ -0,0 +1,442 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2014, 2021 The FreeBSD Foundation + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Portions of this software was developed by Ka Ho Ng 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 "icl.h" +#include "icl_wrappers.h" +#include "iscsi_ioctl.h" +#include "iscsi_proto.h" +#include "iscsi.h" + +#define b64_pton iscsi_b64_pton + +static void +chap_compute_md5(const char id, const char *secret, + const void *challenge, size_t challenge_len, void *response, + size_t response_len) +{ + MD5_CTX ctx; + + KASSERT(response_len == CHAP_DIGEST_LEN, + ("%s: response length mismatch", __func__)); + + MD5Init(&ctx); + MD5Update(&ctx, &id, sizeof(id)); + MD5Update(&ctx, secret, strlen(secret)); + MD5Update(&ctx, challenge, challenge_len); + MD5Final(response, &ctx); +} + +static int +chap_hex2int(const char hex) +{ + switch (hex) { + case '0': + return (0x00); + case '1': + return (0x01); + case '2': + return (0x02); + case '3': + return (0x03); + case '4': + return (0x04); + case '5': + return (0x05); + case '6': + return (0x06); + case '7': + return (0x07); + case '8': + return (0x08); + case '9': + return (0x09); + case 'a': + case 'A': + return (0x0a); + case 'b': + case 'B': + return (0x0b); + case 'c': + case 'C': + return (0x0c); + case 'd': + case 'D': + return (0x0d); + case 'e': + case 'E': + return (0x0e); + case 'f': + case 'F': + return (0x0f); + default: + return (-1); + } +} + +static int +chap_b642bin(const char *b64, void **binp, size_t *bin_lenp) +{ + char *bin; + int b64_len, bin_len; + + b64_len = strlen(b64); + bin_len = (b64_len + 3) / 4 * 3; + bin = malloc(bin_len, M_ISCSI, M_NOWAIT | M_ZERO); + if (bin == NULL) + return (ENOMEM); + + bin_len = b64_pton(b64, bin, bin_len); + if (bin_len < 0) { + ISCSI_WARN("malformed base64 variable"); + free(bin, M_ISCSI); + return (-1); + } + *binp = bin; + *bin_lenp = bin_len; + return (0); +} + +/* + * XXX: Review this _carefully_. + */ +static int +chap_hex2bin(const char *hex, void **binp, size_t *bin_lenp) +{ + int i, hex_len, nibble; + bool lo = true; /* As opposed to 'hi'. */ + char *bin; + size_t bin_off, bin_len; + + if (strncasecmp(hex, "0b", strlen("0b")) == 0) + return (chap_b642bin(hex + 2, binp, bin_lenp)); + + if (strncasecmp(hex, "0x", strlen("0x")) != 0) { + ISCSI_WARN("malformed variable, should start with \"0x\"" + " or \"0b\""); + return (-1); + } + + hex += strlen("0x"); + hex_len = strlen(hex); + if (hex_len < 1) { + ISCSI_WARN("malformed variable; doesn't contain anything " + "but \"0x\""); + return (-1); + } + + bin_len = hex_len / 2 + hex_len % 2; + bin = malloc(bin_len, M_ISCSI, M_NOWAIT | M_ZERO); + if (bin == NULL) { + return (-1); + } + + bin_off = bin_len - 1; + for (i = hex_len - 1; i >= 0; i--) { + nibble = chap_hex2int(hex[i]); + if (nibble < 0) { + ISCSI_WARN("malformed variable, invalid char \"%c\"", + hex[i]); + free(bin, M_ISCSI); + return (-1); + } + + KASSERT(bin_off < bin_len, ("%s: bin_off < bin_len failed", + __func__)); + if (lo) { + bin[bin_off] = nibble; + lo = false; + } else { + bin[bin_off] |= nibble << 4; + bin_off--; + lo = true; + } + } + + *binp = bin; + *bin_lenp = bin_len; + return (0); +} + +#ifdef USE_BASE64 +static char * +chap_bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *b64, *tmp; + size_t b64_len; + + b64_len = (bin_len + 2) / 3 * 4 + 3; /* +2 for "0b", +1 for '\0'. */ + b64 = malloc(b64_len); + if (b64 == NULL) + log_err(1, "malloc"); + + tmp = b64; + tmp += sprintf(tmp, "0b"); + b64_ntop(bin, bin_len, tmp, b64_len - 2); + + return (b64); +} +#else +static char * +chap_bin2hex(const char *bin, size_t bin_len) +{ + unsigned char *hex, *tmp, ch; + size_t hex_len; + size_t i; + + hex_len = bin_len * 2 + 3; /* +2 for "0x", +1 for '\0'. */ + hex = malloc(hex_len, M_ISCSI, M_NOWAIT | M_ZERO); + if (hex == NULL) + return (NULL); + + tmp = hex; + tmp += sprintf(tmp, "0x"); + for (i = 0; i < bin_len; i++) { + ch = bin[i]; + tmp += sprintf(tmp, "%02x", ch); + } + + return (hex); +} +#endif /* !USE_BASE64 */ + +struct iscsi_chap * +iscsi_chap_new(void) +{ + struct iscsi_chap *chap; + + chap = malloc(sizeof(*chap), M_ISCSI, M_NOWAIT | M_ZERO); + if (chap == NULL) + return (NULL); + + /* + * Generate the challenge. + */ + arc4random_buf(chap->chap_challenge, sizeof(chap->chap_challenge)); + arc4random_buf(&chap->chap_id, sizeof(chap->chap_id)); + + return (chap); +} + +char * +iscsi_chap_get_id(const struct iscsi_chap *chap) +{ + char *chap_i; + int ret; + + ret = asprintf(&chap_i, M_ISCSI, "%d", chap->chap_id); + if (ret < 0) + return (NULL); + + return (chap_i); +} + +char * +iscsi_chap_get_challenge(const struct iscsi_chap *chap) +{ + char *chap_c; + + chap_c = chap_bin2hex(chap->chap_challenge, + sizeof(chap->chap_challenge)); + + return (chap_c); +} + +static int +chap_receive_bin(struct iscsi_chap *chap, void *response, size_t response_len) +{ + + if (response_len != sizeof(chap->chap_response)) { + //log_debugx("got CHAP response with invalid length; " + // "got %zd, should be %zd", + // response_len, sizeof(chap->chap_response)); + return (1); + } + + memcpy(chap->chap_response, response, response_len); + return (0); +} + +int +iscsi_chap_receive(struct iscsi_chap *chap, const char *response) +{ + void *response_bin; + size_t response_bin_len; + int error; + + error = chap_hex2bin(response, &response_bin, &response_bin_len); + if (error != 0) { + ISCSI_DEBUG("got incorrectly encoded CHAP response \"%s\"", + response); + return (1); + } + + error = chap_receive_bin(chap, response_bin, response_bin_len); + free(response_bin, M_ISCSI); + + return (error); +} + +int +iscsi_chap_authenticate(struct iscsi_chap *chap, const char *secret) +{ + char expected_response[CHAP_DIGEST_LEN]; + + chap_compute_md5(chap->chap_id, secret, + chap->chap_challenge, sizeof(chap->chap_challenge), + expected_response, sizeof(expected_response)); + + if (memcmp(chap->chap_response, + expected_response, sizeof(expected_response)) != 0) { + return (-1); + } + + return (0); +} + +void +iscsi_chap_delete(struct iscsi_chap *chap) +{ + + free(chap, M_ISCSI); +} + +struct iscsi_rchap * +iscsi_rchap_new(const char *secret) +{ + struct iscsi_rchap *rchap; + + rchap = malloc(sizeof(*rchap), M_ISCSI, M_NOWAIT | M_ZERO); + if (rchap == NULL) + return (NULL); + + rchap->rchap_secret = strdup_flags(secret, M_ISCSI, M_NOWAIT); + + return (rchap); +} + +static int +rchap_receive_bin(struct iscsi_rchap *rchap, const unsigned char id, + const void *challenge, size_t challenge_len) +{ + + rchap->rchap_id = id; + rchap->rchap_challenge = malloc(challenge_len, M_ISCSI, + M_NOWAIT | M_ZERO); + if (rchap->rchap_challenge == NULL) + return (1); + memcpy(rchap->rchap_challenge, challenge, challenge_len); + rchap->rchap_challenge_len = challenge_len; + return (0); +} + +int +iscsi_rchap_receive(struct iscsi_rchap *rchap, const char *id, + const char *challenge) +{ + unsigned char id_bin; + void *challenge_bin; + size_t challenge_bin_len; + + int error; + + id_bin = strtoul(id, NULL, 10); + + error = chap_hex2bin(challenge, &challenge_bin, &challenge_bin_len); + if (error != 0) { + //log_debugx("got incorrectly encoded CHAP challenge \"%s\"", + // challenge); + return (1); + } + + error = rchap_receive_bin(rchap, id_bin, challenge_bin, + challenge_bin_len); + free(challenge_bin, M_ISCSI); + + return (error); +} + +static int +rchap_get_response_bin(struct iscsi_rchap *rchap, + void **responsep, size_t *response_lenp) +{ + void *response_bin; + size_t response_bin_len = CHAP_DIGEST_LEN; + + response_bin = malloc(response_bin_len, M_ISCSI, M_NOWAIT | M_ZERO); + if (response_bin == NULL) + return (1); + + chap_compute_md5(rchap->rchap_id, rchap->rchap_secret, + rchap->rchap_challenge, rchap->rchap_challenge_len, + response_bin, response_bin_len); + + *responsep = response_bin; + *response_lenp = response_bin_len; + return (0); +} + +char * +iscsi_rchap_get_response(struct iscsi_rchap *rchap) +{ + void *response; + size_t response_len; + char *chap_r; + + if (rchap_get_response_bin(rchap, &response, &response_len) != 0) + return (NULL); + chap_r = chap_bin2hex(response, response_len); + free(response, M_ISCSI); + + return (chap_r); +} + +void +iscsi_rchap_delete(struct iscsi_rchap *rchap) +{ + + free(rchap->rchap_secret, M_ISCSI); + free(rchap->rchap_challenge, M_ISCSI); + free(rchap, M_ISCSI); +} Index: sys/dev/iscsi/iscsi_ibft.h =================================================================== --- /dev/null +++ sys/dev/iscsi/iscsi_ibft.h @@ -0,0 +1,276 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 The FreeBSD Foundation + * + * This software was developed by Ka Ho Ng 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_IBFT_H +#define ISCSI_IBFT_H + +#define IBFT_STRUCT_ALIGN 8 + /* Structure alignment in bytes */ + +#define IBFT_SCAN_START_PA 545288 + /* Scan start physical address */ +#define IBFT_SCAN_END_PA 1048576 + /* Scan end physical address */ +#define IBFT_SCAN_ALIGN 16 + /* Scan alignment in bytes */ + +#define IBFT_S_ID_RESERVED 0 /* Reserved */ +#define IBFT_S_ID_CONTROL 1 /* Control structure */ +#define IBFT_S_ID_INITIATOR 2 /* Initiator structure */ +#define IBFT_S_ID_NIC 3 /* NIC structure */ +#define IBFT_S_ID_TARGET 4 /* Target structure */ +#define IBFT_S_ID_EXTENSION 5 /* Extension structure */ + +#define IBFT_CHAP_NONE 0 /* No auth */ +#define IBFT_CHAP_CHAP 1 /* CHAP auth */ +#define IBFT_CHAP_MUTUAL 2 /* Mutual CHAP auth */ + +#define IBFT_OFF_UNUSED 0 + /* Unused offset field should be zero */ + +#define IBFT_S_CTRL_MIN_LEN 18 +#define IBFT_S_INITIATOR_LEN 74 +#define IBFT_S_NIC_LEN 102 +#define IBFT_S_TGT_LEN 54 + +#define IBFT_TABLE_SIGNATURE "iBFT" + +/* + * 3.2 iBFT Standard Structure Header + */ +struct ibft_std_s_hdr { + uint8_t sh_s_id; + uint8_t sh_version; + uint16_t sh_length; + uint8_t sh_index; + uint8_t sh_flags; +}; + +/* + * 3.3 iBFT Table Header + */ +struct ibft_tbl_hdr { + uint8_t th_signature[4]; + uint32_t th_length; + uint8_t th_revision; + uint8_t th_checksum; + uint8_t th_oemid[6]; + uint8_t th_oem_tbl_id[8]; + uint8_t th_reserved0[24]; +}; +_Static_assert(sizeof(struct ibft_tbl_hdr) == 48, + "iBFT Table Header must be 48 bytes in size"); + +/* + * 3.4 Control Structure + * + * Structure ID : control structure + * Structure Version : 1 + * Structure Length : >=18 + * Structure Index : 0 + * Structure Flags : + * * Bit0 - Boot Failover Flag + */ +struct ibft_ctrl_s { + struct ibft_std_s_hdr cs_hdr; + uint16_t cs_extensions; + uint16_t cs_initiator_off; + uint16_t cs_nic0_off; + uint16_t cs_tgt0_off; + uint16_t cs_nic1_off; + uint16_t cs_tgt1_off; +}; + +/* + * 3.5 Initiator Structure + * + * Structure ID : initiator structure + * Structure Version : 1 + * Structure Length : 74 + * Structure Index : 0 + * Structure Flags : + * - Bit0 - Block Valid Flag + * - Bit1 - Firmware Boot Selected Flag + */ +struct ibft_initiator_s { + struct ibft_std_s_hdr is_hdr; + uint8_t is_isns[16]; + uint8_t is_slp[16]; + uint8_t is_pri_radius[16]; + uint8_t is_sec_radius[16]; + uint16_t is_initiator_name_len; + uint16_t is_initiator_name_off; +}; + +/* + * 3.6 NIC Structure + * + * Structure ID : NIC structure + * Structure Version : 1 + * Structure Length : 102 + * Structure Index : 0...255 + * Structure Flags : + * - Bit0 - Block Valid Flag + * - Bit1 - Firmware Boot Selected Flag + * - Bit2 - Global/Link Local + */ +struct ibft_nic_s { + struct ibft_std_s_hdr ns_hdr; + uint8_t ns_ip[16]; + uint8_t ns_prefixlen; + uint8_t ns_origin; + uint8_t ns_gateway[16]; + uint8_t ns_pri_dns[16]; + uint8_t ns_sec_dns[16]; + uint8_t ns_dhcp_dns[16]; + uint16_t ns_vlan; + uint8_t ns_mac[6]; + uint16_t ns_pci_bdf; + uint16_t ns_hostname_len; + uint16_t ns_hostname_off; +}; + +/* + * 3.7 Target Structure + * + * Structure ID : target structure + * Structure Version : 1 + * Structure Length : 54 + * Structure Index : 0...255 + * Structure Flags : + * - Bit0 - Block Valid Flag + * - Bit1 - Firmware Boot Selected Flag + * - Bit2 - Use Radius CHAP + * - Bit3 - Use Radius rCHAP + */ +struct ibft_tgt_s { + struct ibft_std_s_hdr ts_hdr; + uint8_t ts_ip[16]; + uint16_t ts_port; + uint64_t ts_lun; + uint8_t ts_chap_type; + uint8_t ts_nic_idx; + uint16_t ts_tgt_name_len; + uint16_t ts_tgt_name_off; + uint16_t ts_chap_name_len; + uint16_t ts_chap_name_off; + uint16_t ts_chap_secret_len; + uint16_t ts_chap_secret_off; + uint16_t ts_rchap_name_len; + uint16_t ts_rchap_name_off; + uint16_t ts_rchap_secret_len; + uint16_t ts_rchap_secret_off; +}; + +/* + * Below are in-memory representation of the above structures. + */ + +struct ibft_i_initiator_s { + struct ibft_initiator_s *is_rawptr; + bool is_present; + unsigned int is_flags; + uint8_t is_isns[16]; + uint8_t is_slp[16]; + uint8_t is_pri_radius[16]; + uint8_t is_sec_radius[16]; + size_t is_name_len; + const char *is_name; +}; + +struct ibft_i_nic_s { + struct ibft_nic_s *ns_rawptr; + bool ns_present; + TAILQ_ENTRY(ibft_i_nic_s) ns_entry; + uint8_t ns_idx; + unsigned int ns_flags; + uint8_t ns_ip[16]; + uint8_t ns_prefixlen; + uint8_t ns_origin; + uint8_t ns_gateway[16]; + uint8_t ns_pri_dns[16]; + uint8_t ns_sec_dns[16]; + uint8_t ns_dhcp_dns[16]; + uint16_t ns_vlan; + uint8_t ns_mac[6]; + uint16_t ns_pci_bdf; + size_t ns_hostname_len; + const char *ns_hostname; +}; + +struct ibft_i_tgt_s { + struct ibft_tgt_s *ts_rawptr; + bool ts_present; + TAILQ_ENTRY(ibft_i_tgt_s) ts_entry; + uint8_t ts_idx; + unsigned int ts_flags; + uint8_t ts_ip[16]; + uint16_t ts_port; + uint64_t ts_lun; + uint8_t ts_chap_type; + uint8_t ts_nic_idx; + size_t ts_tgt_name_len; + const char *ts_tgt_name; + size_t ts_chap_name_len; + const char *ts_chap_name; + size_t ts_chap_secret_len; + const char *ts_chap_secret; + size_t ts_rchap_name_len; + const char *ts_rchap_name; + size_t ts_rchap_secret_len; + const char *ts_rchap_secret; +}; + +#define IBFT_MAX_IDX (255) +#define IBFT_MAX_N_STRUCTS (IBFT_MAX_IDX + 1) + +TAILQ_HEAD(ibft_i_nics_head, ibft_i_nic_s); +TAILQ_HEAD(ibft_i_tgts_head, ibft_i_tgt_s); + +extern struct ibft_i_initiator_s *ibft_initiator; +extern struct ibft_i_nics_head ibft_nics_list; +extern struct ibft_i_tgts_head ibft_targets_list; +extern struct ibft_i_nic_s *ibft_nics; +extern struct ibft_i_tgt_s *ibft_targets; + +int iscsi_ibft_init(void); +void iscsi_ibft_fini(void); + +static inline void +iscsi_ibft_getstr(char *dst, size_t dstsz, const char *src, size_t srclen) +{ + bzero(dst, dstsz); + if (src == NULL) + return; + memcpy(dst, src, min(dstsz - 1, srclen)); +} + +#endif \ No newline at end of file Index: sys/dev/iscsi/iscsi_ibft.c =================================================================== --- /dev/null +++ sys/dev/iscsi/iscsi_ibft.c @@ -0,0 +1,275 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2012, 2021 The FreeBSD Foundation + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Portions of this software was developed by Ka Ho Ng 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 "icl.h" +#include "icl_wrappers.h" +#include "iscsi_ibft.h" +#include "iscsi_ioctl.h" +#include "iscsi_proto.h" +#include "iscsi.h" + +static char *ibft_region; +static const struct ibft_ctrl_s *ibft_ctl; + +struct ibft_i_initiator_s *ibft_initiator; +struct ibft_i_nic_s *ibft_nics; +struct ibft_i_tgt_s *ibft_targets; +static size_t ibft_num_nics, ibft_num_targets; + +struct ibft_i_nics_head ibft_nics_list; +struct ibft_i_tgts_head ibft_targets_list; + +static int iscsi_verify_ibft(const struct ibft_tbl_hdr *ibftp); + +static int +iscsi_verify_ibft(const struct ibft_tbl_hdr *ibftp) +{ + const struct ibft_ctrl_s *ctrlp; + + if (le16toh(ibftp->th_length) < sizeof(*ibftp)) + return (1); + if (ibftp->th_revision != 1) + return (1); + + ctrlp = (const struct ibft_ctrl_s *)(ibftp + 1); + if (ctrlp->cs_hdr.sh_s_id != IBFT_S_ID_CONTROL) + return (1); + if (ctrlp->cs_hdr.sh_version != 1) + return (1); + if (le16toh(ctrlp->cs_hdr.sh_length) < sizeof(*ctrlp)) + return (1); + if (ctrlp->cs_hdr.sh_index != 0) + return (1); + + return (0); +} + +#define IBFTSTRFETCH(name, dest, raw) { \ + if (le16toh((raw)->name##_off) != 0) \ + (dest)->name = ibft_region + \ + le16toh((raw)->name##_off); \ +} +static void +iscsi_ibft_extract_initiator(struct ibft_initiator_s *raw, + struct ibft_i_initiator_s *initiator) +{ + bzero(initiator, sizeof(*initiator)); + initiator->is_rawptr = raw; + initiator->is_present = true; + initiator->is_flags = raw->is_hdr.sh_flags; + memcpy(initiator->is_isns, raw->is_isns, sizeof(raw->is_isns)); + memcpy(initiator->is_slp, raw->is_slp, sizeof(initiator->is_slp)); + memcpy(initiator->is_pri_radius, raw->is_pri_radius, + sizeof(initiator->is_pri_radius)); + memcpy(initiator->is_sec_radius, raw->is_sec_radius, + sizeof(initiator->is_sec_radius)); + initiator->is_name_len = le16toh(raw->is_initiator_name_len); + initiator->is_name = ibft_region + le16toh(raw->is_initiator_name_off); +} + +static void +iscsi_ibft_extract_nic(struct ibft_nic_s *raw, + struct ibft_i_nic_s *nic) +{ + bzero(nic, sizeof(*nic)); + nic->ns_rawptr = raw; + nic->ns_present = true; + nic->ns_idx = raw->ns_hdr.sh_index; + nic->ns_flags = raw->ns_hdr.sh_flags; + memcpy(nic->ns_ip, raw->ns_ip, sizeof(raw->ns_ip)); + nic->ns_prefixlen = raw->ns_prefixlen; + nic->ns_origin = raw->ns_origin; + memcpy(nic->ns_gateway, raw->ns_gateway, sizeof(raw->ns_gateway)); + memcpy(nic->ns_pri_dns, raw->ns_pri_dns, sizeof(raw->ns_pri_dns)); + memcpy(nic->ns_sec_dns, raw->ns_sec_dns, sizeof(raw->ns_sec_dns)); + memcpy(nic->ns_dhcp_dns, raw->ns_dhcp_dns, sizeof(raw->ns_dhcp_dns)); + nic->ns_vlan = le16toh(raw->ns_vlan); + memcpy(nic->ns_mac, raw->ns_mac, sizeof(raw->ns_mac)); + nic->ns_pci_bdf = le16toh(raw->ns_pci_bdf); + nic->ns_hostname_len = le16toh(raw->ns_hostname_len); + IBFTSTRFETCH(ns_hostname, nic, raw); +} + +static void +iscsi_ibft_extract_target(struct ibft_tgt_s *raw, + struct ibft_i_tgt_s *tgt) +{ + bzero(tgt, sizeof(*tgt)); + tgt->ts_rawptr = raw; + tgt->ts_present = true; + tgt->ts_idx = raw->ts_hdr.sh_index; + tgt->ts_flags = raw->ts_hdr.sh_flags; + memcpy(tgt->ts_ip, raw->ts_ip, sizeof(raw->ts_ip)); + tgt->ts_port = le16toh(raw->ts_port); + tgt->ts_lun = le64toh(raw->ts_lun); + tgt->ts_chap_type = raw->ts_chap_type; + tgt->ts_nic_idx = raw->ts_nic_idx; + tgt->ts_tgt_name_len = le16toh(raw->ts_tgt_name_len); + IBFTSTRFETCH(ts_tgt_name, tgt, raw); + tgt->ts_chap_name_len = le16toh(raw->ts_chap_name_len); + IBFTSTRFETCH(ts_chap_name, tgt, raw); + tgt->ts_chap_secret_len = le16toh(raw->ts_chap_secret_len); + IBFTSTRFETCH(ts_chap_secret, tgt, raw); + tgt->ts_rchap_name_len = le16toh(raw->ts_rchap_name_len); + IBFTSTRFETCH(ts_rchap_name, tgt, raw); + tgt->ts_rchap_secret_len = le16toh(raw->ts_rchap_secret_len); + IBFTSTRFETCH(ts_rchap_secret, tgt, raw); +} +#undef IBFTSTRFETCH + +int +iscsi_ibft_init(void) +{ + struct ibft_std_s_hdr *hdr; + const uint16_t *offs; + int n, noffs; + uint16_t off; + struct ibft_tbl_hdr *ibft; + ACPI_STATUS r; + + TAILQ_INIT(&ibft_nics_list); + TAILQ_INIT(&ibft_targets_list); + + r = AcpiGetTable(ACPI_SIG_IBFT, 1, (ACPI_TABLE_HEADER **)&ibft); + if (ACPI_FAILURE(r)) { + r = AcpiGetTable(IBFT_TABLE_SIGNATURE, 1, + (ACPI_TABLE_HEADER **)&ibft); + if (ACPI_FAILURE(r)) { + ISCSI_DEBUG("Cannot find IBFT table"); + return (1); + } + } + if (iscsi_verify_ibft((struct ibft_tbl_hdr *)ibft) != 0) { + ISCSI_WARN("Failed verifying IBFT table"); + return (1); + } + + ibft_region = (char *)ibft; + ibft_ctl = (const struct ibft_ctrl_s *)(ibft + 1); + + ibft_initiator = (struct ibft_i_initiator_s *)malloc( + sizeof(*ibft_initiator), M_ISCSI, M_NOWAIT); + if (ibft_initiator == NULL) + goto fail; + ibft_nics = mallocarray(IBFT_MAX_N_STRUCTS, sizeof(*ibft_nics), M_ISCSI, + M_ZERO | M_NOWAIT); + if (ibft_nics == NULL) + goto fail; + ibft_targets = mallocarray(IBFT_MAX_N_STRUCTS, sizeof(*ibft_targets), + M_ISCSI, M_ZERO | M_NOWAIT); + if (ibft_targets == NULL) + goto fail; + + /* + * Parse Initiator, NICs and Targets. + * + * Per 3.4.3 Optional Structure Expansion + */ + offs = &ibft_ctl->cs_initiator_off; + noffs = (le16toh(ibft_ctl->cs_hdr.sh_length) - + offsetof(struct ibft_ctrl_s, cs_initiator_off)) / 2; + for (n = 0; n < noffs; n++) { + off = le16toh(offs[n]); + if (off == 0) + continue; + if (off >= le32toh(ibft->th_length)) { + ISCSI_WARN("Invalid optional offset in IBFT table. " + "Index in Optional Structure Expansion: %d. off: %hu. noff: %d", + n, off, noffs); + continue; + } + hdr = (struct ibft_std_s_hdr *)(ibft_region + off); + switch (hdr->sh_s_id) { + case IBFT_S_ID_INITIATOR: + iscsi_ibft_extract_initiator( + (struct ibft_initiator_s *)hdr, + ibft_initiator); + break; + case IBFT_S_ID_NIC: + iscsi_ibft_extract_nic((struct ibft_nic_s *)hdr, + &ibft_nics[hdr->sh_index]); + TAILQ_INSERT_TAIL(&ibft_nics_list, + &ibft_nics[hdr->sh_index], ns_entry); + ibft_num_nics++; + break; + case IBFT_S_ID_TARGET: + iscsi_ibft_extract_target((struct ibft_tgt_s *)hdr, + &ibft_targets[hdr->sh_index]); + TAILQ_INSERT_TAIL(&ibft_targets_list, + &ibft_targets[hdr->sh_index], ts_entry); + ibft_num_targets++; + break; + default: + ISCSI_WARN("Unexpected id in Optional Structure Expansion: %hhu", + hdr->sh_s_id); + } + } + + ISCSI_DEBUG("Done parsing IBFT table. NICs: %zu, Targets: %zu", + ibft_num_nics, ibft_num_targets); + return (0); +fail: + iscsi_ibft_fini(); + return (1); +} + +void +iscsi_ibft_fini(void) +{ + TAILQ_INIT(&ibft_nics_list); + TAILQ_INIT(&ibft_targets_list); + + free(ibft_initiator, M_ISCSI); + ibft_initiator = NULL; + free(ibft_nics, M_ISCSI); + ibft_nics = NULL; + free(ibft_targets, M_ISCSI); + ibft_targets = NULL; + + ibft_region = NULL; + ibft_num_nics = ibft_num_targets = 0; +} \ No newline at end of file Index: sys/dev/iscsi/iscsi_keys.c =================================================================== --- /dev/null +++ sys/dev/iscsi/iscsi_keys.c @@ -0,0 +1,257 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2012, 2021 The FreeBSD Foundation + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Portions of this software was developed by Ka Ho Ng 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 "icl.h" +#include "icl_wrappers.h" +#include "iscsi_ioctl.h" +#include "iscsi.h" + +struct iscsi_keys * +iscsi_keys_new(int mflags) +{ + struct iscsi_keys *ik; + + ik = malloc(sizeof(*ik), M_ISCSI, mflags | M_ZERO); + if (ik == NULL) + ISCSI_WARN("keys_new malloc failed"); + + return (ik); +} + +static bool +str_out_of_data(struct iscsi_keys *ik, const char *str) +{ + ptrdiff_t diff; + + diff = str - ik->ik_data; + if (diff < 0 || diff >= ik->ik_data_len) + return (true); + return (false); +} + +void +iscsi_keys_delete(struct iscsi_keys *ik) +{ + int i; + + for (i = 0; i < ISCSI_KEYS_MAX; i++) { + if (ik->ik_names[i] == NULL) + break; + if (str_out_of_data(ik, ik->ik_names[i])) + free(ik->ik_names[i], M_ISCSI); + KASSERT(ik->ik_values[i] != NULL, + ("iscsi: keys_delete encountered null value")); + if (str_out_of_data(ik, ik->ik_values[i])) + free(ik->ik_values[i], M_ISCSI); + } + free(ik->ik_data, M_ISCSI); + free(ik, M_ISCSI); +} + +int +iscsi_keys_load(struct iscsi_keys *ik, struct icl_pdu *ip, int mflags) +{ + int i; + char *pair; + size_t data_len, pair_len; + int error; + + i = 0; + + if (ip->ip_data_len == 0) + return (0); + + KASSERT(ik->ik_data == NULL, ("iscsi: ik_data non-null")); + KASSERT(ik->ik_names[0] == NULL, ("iscsi: added kv pairs exists")); + data_len = ip->ip_data_len; + ik->ik_data = malloc(data_len, M_ISCSI, mflags); + if (ik->ik_data == NULL) { + ISCSI_WARN("keys_load out of memory for ik_data"); + return (ENOMEM); + } + ik->ik_data_len = data_len; + icl_pdu_get_data(ip, 0, ik->ik_data, ik->ik_data_len); + if (ik->ik_data[data_len - 1] != '\0') { + ISCSI_WARN("protocol error: key not NULL-terminated. data_len : %zu", + data_len); + //hexdump(ik->ik_data, data_len, "iscsi_keys_load pdu: ", 0); + error = EINTEGRITY; + goto fail; + } + + pair = ik->ik_data; + for (; i < ISCSI_KEYS_MAX; i++) { + pair_len = strlen(pair); + + ik->ik_values[i] = pair; + ik->ik_names[i] = strsep(&ik->ik_values[i], "="); + if (ik->ik_names[i] == NULL || ik->ik_values[i] == NULL) { + ISCSI_WARN("malformed keys"); + error = EINTEGRITY; + goto fail; + } + ISCSI_DEBUG("key received: \"%s=%s\"", ik->ik_names[i], + ik->ik_values[i]); + + pair += pair_len + 1; /* +1 to skip the terminating '\0'. */ + if (pair == ik->ik_data + ik->ik_data_len) + break; + KASSERT(pair < ik->ik_data + ik->ik_data_len, + ("iscsi: keys_load pair out of bound")); + } + if (i >= ISCSI_KEYS_MAX) { + ISCSI_WARN("too many keys received."); + error = EINTEGRITY; + goto fail; + } + + return (0); +fail: + free(ik->ik_data, M_ISCSI); + ik->ik_data = NULL; + ik->ik_data_len = 0; + while (i-- > 0) + ik->ik_names[i] = ik->ik_values[i] = NULL; + return (error); +} + +int +iscsi_keys_save(struct iscsi_keys *ik, struct icl_pdu *ip, int mflags) +{ + size_t len; + int i, error; + + error = 0; + + for (i = 0; i < ISCSI_KEYS_MAX; i++) { + if (ik->ik_names[i] == NULL) + break; + len = strlen(ik->ik_names[i]); + error = icl_pdu_append_data(ip, ik->ik_names[i], len, mflags); + if (error) + break; + error = icl_pdu_append_data(ip, "=", 1, mflags); + if (error) + break; + len = strlen(ik->ik_values[i]); + error = icl_pdu_append_data(ip, ik->ik_values[i], len, mflags); + if (error) + break; + error = icl_pdu_append_data(ip, "\0", 1, mflags); + if (error) + break; + } + + return (error); +} + +const char * +iscsi_keys_find(struct iscsi_keys *ik, const char *name) +{ + int i; + + /* + * Note that we don't handle duplicated key names here, + * as they are not supposed to happen in requests, and if they do, + * it's an initiator error. + */ + for (i = 0; i < ISCSI_KEYS_MAX; i++) { + if (ik->ik_names[i] == NULL) + return (NULL); + if (strcmp(ik->ik_names[i], name) == 0) + return (ik->ik_values[i]); + } + return (NULL); +} + +int +iscsi_keys_add(struct iscsi_keys *ik, const char *name, const char *value, + int mflags) +{ + int i; + + KASSERT(name != NULL, ("iscsi: keys_add with null name")); + KASSERT(value != NULL, ("iscsi: keys_add with null value")); + ISCSI_DEBUG("key to send: \"%s=%s\"", name, value); + + /* + * Note that we don't check for duplicates here, as they are perfectly + * fine in responses, e.g. the "TargetName" keys in discovery sesion + * response. + */ + for (i = 0; i < ISCSI_KEYS_MAX; i++) { + if (ik->ik_names[i] == NULL) { + ik->ik_names[i] = strdup_flags(name, M_ISCSI, mflags); + if (ik->ik_names[i] == NULL) + return (ENOMEM); + ik->ik_values[i] = strdup_flags(value, M_ISCSI, mflags); + if (ik->ik_values[i] == NULL) { + free(ik->ik_names[i], M_ISCSI); + return (ENOMEM); + } + return (0); + } + } + panic("iscsi: keys_add with too many keys.\n"); +} + +int +iscsi_keys_add_int(struct iscsi_keys *ik, const char *name, int value, + int mflags) +{ + char *str; + int ret, error; + + ret = asprintf(&str, M_ISCSI, "%d", value); + if (ret <= 0) { + ISCSI_WARN("keys_add_int runs out of memory."); + return (ENOMEM); + } + + error = iscsi_keys_add(ik, name, str, mflags); + free(str, M_ISCSI); + return (error); +} \ No newline at end of file Index: sys/dev/iscsi/iscsi_login.c =================================================================== --- /dev/null +++ sys/dev/iscsi/iscsi_login.c @@ -0,0 +1,1123 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2012, 2021 The FreeBSD Foundation + * + * This software was developed by Edward Tomasz Napierala under sponsorship + * from the FreeBSD Foundation. + * + * Portions of this software was developed by Ka Ho Ng 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 "icl.h" +#include "icl_wrappers.h" +#include "iscsi_ioctl.h" +#include "iscsi_proto.h" +#include "iscsi.h" + +static int +login_nsg(const struct icl_pdu *response) +{ + struct iscsi_bhs_login_response *bhslr; + + bhslr = (struct iscsi_bhs_login_response *)response->ip_bhs; + + return (bhslr->bhslr_flags & 0x03); +} + +static void +login_set_nsg(struct icl_pdu *request, int nsg) +{ + struct iscsi_bhs_login_request *bhslr; + + KASSERT(nsg == BHSLR_STAGE_SECURITY_NEGOTIATION || + nsg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || + nsg == BHSLR_STAGE_FULL_FEATURE_PHASE, + ("%s: invalid nsg 0x%x", __func__, nsg)); + + bhslr = (struct iscsi_bhs_login_request *)request->ip_bhs; + + bhslr->bhslr_flags &= 0xFC; + bhslr->bhslr_flags |= nsg; +} + +static void +login_set_csg(struct icl_pdu *request, int csg) +{ + struct iscsi_bhs_login_request *bhslr; + + KASSERT(csg == BHSLR_STAGE_SECURITY_NEGOTIATION || + csg == BHSLR_STAGE_OPERATIONAL_NEGOTIATION || + csg == BHSLR_STAGE_FULL_FEATURE_PHASE, + ("%s: invalid csg 0x%x", __func__, csg)); + + bhslr = (struct iscsi_bhs_login_request *)request->ip_bhs; + + bhslr->bhslr_flags &= 0xF3; + bhslr->bhslr_flags |= csg << 2; +} + +static const char * +login_target_error_str(int class, int detail) +{ + /* + * 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: + return ("unknown redirection Status-Detail"); + } + 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: + return ("unknown initiator error Status-Detail"); + } + case 0x03: + switch (detail) { + case 0x00: + return ("Target error"); + case 0x01: + return ("Service unavailable"); + case 0x02: + return ("Out of resources"); + default: + return ("unknown target error Status-Detail"); + } + default: + return ("unknown target error Status-Class"); + } +} + +/* + * XXX: Currently redirection is not supported + */ +static int +login_handle_redirection(struct iscsi_session *is, struct icl_pdu *response) +{ + return (EOPNOTSUPP); +} + +static void +login_send(struct iscsi_session *is, struct icl_pdu *ip) +{ + ISCSI_SESSION_LOCK_ASSERT(is); + icl_pdu_queue(ip); +} + +static int +login_receive(struct iscsi_session *is, struct icl_pdu **ip) +{ + struct iscsi_bhs_login_response *bhslr; + struct icl_pdu *response; + const char *errorstr; + int error; + + ISCSI_SESSION_LOCK_ASSERT(is); + + while (is->is_login_pdu == NULL && + !is->is_terminating && !is->is_reconnecting) { + error = cv_wait_sig(&is->is_login_cv, &is->is_lock); + if (error != 0) + break; + } + if (is->is_terminating || is->is_reconnecting) + return (EPIPE); + response = is->is_login_pdu; + is->is_login_pdu = NULL; + + bhslr = (struct iscsi_bhs_login_response *)response->ip_bhs; + error = EINTEGRITY; + + if (bhslr->bhslr_opcode != ISCSI_BHS_OPCODE_LOGIN_RESPONSE) { + ISCSI_SESSION_WARN(is, + "protocol error: received invalid opcode 0x%x", + bhslr->bhslr_opcode); + goto out; + } + /* + * XXX: Implement the C flag some day. + */ + if ((bhslr->bhslr_flags & BHSLR_FLAGS_CONTINUE) != 0) { + ISCSI_SESSION_WARN(is, + "received Login PDU with unsupported \"C\" flag"); + goto out; + } + if (bhslr->bhslr_version_max != 0x00) { + ISCSI_SESSION_WARN(is, "received Login PDU with unsupported " + "Version-max 0x%x", bhslr->bhslr_version_max); + goto out; + } + if (bhslr->bhslr_version_active != 0x00) { + ISCSI_SESSION_WARN(is, "received Login PDU with unsupported " + "Version-active 0x%x", bhslr->bhslr_version_active); + goto out; + } + if (bhslr->bhslr_status_class == 1) { + error = login_handle_redirection(is, response); + if (error == 0) { + ISCSI_DEBUG("redirection handled"); + error = EAGAIN; + } + goto out; + } + if (bhslr->bhslr_status_class != 0) { + errorstr = login_target_error_str(bhslr->bhslr_status_class, + bhslr->bhslr_status_detail); + ISCSI_SESSION_WARN(is, "target returned error: %s", errorstr); + goto out; + } + is->is_tsih = ntohs(bhslr->bhslr_tsih); + error = 0; + +out: + *ip = response; + return (error); +} + +static struct icl_pdu * +login_new_request(struct iscsi_session *is, int csg) +{ + struct icl_pdu *request; + struct iscsi_bhs_login_request *bhslr; + int nsg; + + request = icl_pdu_new(is->is_conn, M_NOWAIT); + if (request == NULL) + return (NULL); + bhslr = (struct iscsi_bhs_login_request *)request->ip_bhs; + bhslr->bhslr_opcode = ISCSI_BHS_OPCODE_LOGIN_REQUEST | + ISCSI_BHS_OPCODE_IMMEDIATE; + + bhslr->bhslr_flags = BHSLR_FLAGS_TRANSIT; + 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: + panic("%s: invalid csg %d", __func__, csg); + } + login_set_csg(request, csg); + login_set_nsg(request, nsg); + + memcpy(bhslr->bhslr_isid, is->is_isid, sizeof(bhslr->bhslr_isid)); + bhslr->bhslr_tsih = htons(is->is_tsih); + bhslr->bhslr_initiator_task_tag = 0; + bhslr->bhslr_cmdsn = 0; + bhslr->bhslr_expstatsn = htonl(is->is_statsn + 1); + + return (request); +} + +static int +login_list_prefers(const char *list, + const char *choice1, const char *choice2) +{ + char *tofree, *str, *token; + + tofree = str = strdup_flags(list, M_ISCSI, M_NOWAIT); + + while ((token = strsep(&str, ",")) != NULL) { + if (strcmp(token, choice1) == 0) { + free(tofree, M_ISCSI); + return (1); + } + if (strcmp(token, choice2) == 0) { + free(tofree, M_ISCSI); + return (2); + } + } + free(tofree, M_ISCSI); + return (-1); +} + +static int +login_negotiate_key(struct iscsi_session *is, struct icl_drv_limits *idl, + const char *name, const char *value, struct iscsi_kernel_handoff *handoff) +{ + struct icl_conn *ic; + int which, tmp; + + ic = is->is_conn; + + if (strcmp(name, "TargetAlias") == 0) { + strlcpy(handoff->ikh_target_alias, value, + sizeof(handoff->ikh_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) { + ISCSI_SESSION_WARN(is, + "received invalid iSCSIProtocolLevel"); + return (EINVAL); + } + handoff->ikh_protocol_level = tmp; + } else if (strcmp(name, "HeaderDigest") == 0) { + which = login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + handoff->ikh_header_digest = ISCSI_DIGEST_CRC32C; + break; + case 2: + handoff->ikh_header_digest = ISCSI_DIGEST_NONE; + break; + default: + ISCSI_SESSION_WARN(is, "target sent unrecognized " + "HeaderDigest value \"%s\"; will use None", value); + handoff->ikh_header_digest = ISCSI_DIGEST_NONE; + break; + } + } else if (strcmp(name, "DataDigest") == 0) { + which = login_list_prefers(value, "CRC32C", "None"); + switch (which) { + case 1: + handoff->ikh_data_digest = ISCSI_DIGEST_CRC32C; + break; + case 2: + handoff->ikh_data_digest = ISCSI_DIGEST_NONE; + break; + default: + ISCSI_SESSION_WARN(is, "target sent unrecognized " + "DataDigest value \"%s\"; will use None", value); + handoff->ikh_data_digest = ISCSI_DIGEST_NONE; + break; + } + } else if (strcmp(name, "MaxConnections") == 0) { + /* Ignore. */ + } else if (strcmp(name, "InitialR2T") == 0) { + if (strcmp(value, "Yes") == 0) + handoff->ikh_initial_r2t = 1; + else + handoff->ikh_initial_r2t = 0; + } else if (strcmp(name, "ImmediateData") == 0) { + if (strcmp(value, "Yes") == 0) + handoff->ikh_immediate_data = 1; + else + handoff->ikh_immediate_data = 0; + } else if (strcmp(name, "MaxRecvDataSegmentLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + ISCSI_SESSION_WARN(is, + "received invalid MaxRecvDataSegmentLength"); + return (EINVAL); + } + if (tmp > idl->idl_max_send_data_segment_length) { + ISCSI_SESSION_DEBUG(is, + "capping max_send_data_segment_length " + "from %d to %d", tmp, + idl->idl_max_send_data_segment_length); + tmp = idl->idl_max_send_data_segment_length; + } + ic->ic_max_send_data_segment_length = + handoff->ikh_max_send_data_segment_length = tmp; + } else if (strcmp(name, "MaxBurstLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + ISCSI_SESSION_WARN(is, + "received invalid MaxBurstLength"); + return (EINVAL); + } + if (tmp > idl->idl_max_burst_length) { + ISCSI_SESSION_DEBUG(is, + "capping MaxBurstLength " + "from %d to %d", tmp, idl->idl_max_burst_length); + tmp = idl->idl_max_burst_length; + } + handoff->ikh_max_burst_length = tmp; + } else if (strcmp(name, "FirstBurstLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + ISCSI_SESSION_WARN(is, + "received invalid FirstBurstLength"); + return (EINVAL); + } + if (tmp > idl->idl_first_burst_length) { + ISCSI_SESSION_DEBUG(is, + "capping FirstBurstLength " + "from %d to %d", tmp, + idl->idl_first_burst_length); + tmp = idl->idl_first_burst_length; + } + handoff->ikh_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 (is->is_conf.isc_iser == 1 && + strcmp(value, "Yes") != 0) { + ISCSI_SESSION_WARN(is, + "received unsupported RDMAExtensions"); + return (EINVAL); + } + } else if (strcmp(name, "InitiatorRecvDataSegmentLength") == 0) { + tmp = strtoul(value, NULL, 10); + if (tmp <= 0) { + ISCSI_SESSION_WARN(is, "received invalid " + "InitiatorRecvDataSegmentLength"); + return (EINVAL); + } + if ((int)tmp > idl->idl_max_recv_data_segment_length) { + ISCSI_SESSION_DEBUG(is, + "capping InitiatorRecvDataSegmentLength " + "from %d to %d", tmp, + idl->idl_max_recv_data_segment_length); + tmp = idl->idl_max_recv_data_segment_length; + } + ic->ic_max_recv_data_segment_length = + handoff->ikh_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) { + ISCSI_SESSION_WARN(is, + "%s: received invalid " + "TargetRecvDataSegmentLength", __func__); + return (EINVAL); + } + if (tmp > idl->idl_max_send_data_segment_length) { + ISCSI_SESSION_WARN(is, + "capping TargetRecvDataSegmentLength " + "from %d to %d", tmp, + idl->idl_max_send_data_segment_length); + tmp = idl->idl_max_send_data_segment_length; + } + ic->ic_max_send_data_segment_length = + handoff->ikh_max_send_data_segment_length = tmp; + } else { + ISCSI_SESSION_WARN(is, "unknown key \"%s\"; ignoring", name); + } + return (0); +} + +static int +login_negotiate(struct iscsi_session *is, struct icl_drv_limits *idl, + struct iscsi_kernel_handoff *handoff) +{ + struct icl_pdu *request, *response; + struct iscsi_bhs_login_response *bhslr; + struct iscsi_keys *request_keys, *response_keys; + struct icl_conn *ic; + int i, nrequests = 0; + int error; + + ISCSI_SESSION_DEBUG(is, "beginning operational parameter negotiation"); + ic = is->is_conn; + error = ENOMEM; + request_keys = NULL; + response_keys = NULL; + response = NULL; + request = login_new_request(is, BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + if (request == NULL) + return (ENOMEM); + request_keys = iscsi_keys_new(M_NOWAIT); + if (request_keys == NULL) + goto out; + + ISCSI_SESSION_DEBUG(is, "Limits for offload \"%s\" are " + "MaxRecvDataSegment=%d, max_send_dsl=%d, " + "MaxBurstLength=%d, FirstBurstLength=%d", + ic->ic_offload, idl->idl_max_recv_data_segment_length, + idl->idl_max_send_data_segment_length, idl->idl_max_burst_length, + idl->idl_first_burst_length); + + /* + * The following keys are irrelevant for discovery sessions. + */ + if (is->is_conf.isc_discovery == 0) { + error = iscsi_keys_add(request_keys, "iSCSIProtocolLevel", "2", + M_NOWAIT); + if (ic->ic_header_crc32c) { + error |= iscsi_keys_add(request_keys, "HeaderDigest", + "CRC32C", M_NOWAIT); + } else { + error |= iscsi_keys_add(request_keys, "HeaderDigest", + "None", M_NOWAIT); + } + if (ic->ic_data_crc32c) { + error |= iscsi_keys_add(request_keys, "DataDigest", + "CRC32C", M_NOWAIT); + } else { + error |= iscsi_keys_add(request_keys, "DataDigest", + "None", M_NOWAIT); + } + + error |= iscsi_keys_add(request_keys, "ImmediateData", "Yes", + M_NOWAIT); + error |= iscsi_keys_add_int(request_keys, "MaxBurstLength", + idl->idl_max_burst_length, M_NOWAIT); + error |= iscsi_keys_add_int(request_keys, "FirstBurstLength", + idl->idl_first_burst_length, M_NOWAIT); + error |= iscsi_keys_add(request_keys, "InitialR2T", "Yes", + M_NOWAIT); + error |= iscsi_keys_add(request_keys, "MaxOutstandingR2T", "1", + M_NOWAIT); + if (ic->ic_iser) { + error |= iscsi_keys_add_int(request_keys, + "InitiatorRecvDataSegmentLength", + idl->idl_max_recv_data_segment_length, M_NOWAIT); + error |= iscsi_keys_add_int(request_keys, + "TargetRecvDataSegmentLength", + idl->idl_max_send_data_segment_length, M_NOWAIT); + error |= iscsi_keys_add(request_keys, "RDMAExtensions", + "Yes", M_NOWAIT); + } else { + error |= iscsi_keys_add_int(request_keys, + "MaxRecvDataSegmentLength", + idl->idl_max_recv_data_segment_length, M_NOWAIT); + } + } else { + error = iscsi_keys_add(request_keys, "HeaderDigest", "None", + M_NOWAIT); + error |= iscsi_keys_add(request_keys, "DataDigest", "None", + M_NOWAIT); + error |= iscsi_keys_add_int(request_keys, + "MaxRecvDataSegmentLength", + idl->idl_max_recv_data_segment_length, M_NOWAIT); + } + + ic->ic_max_recv_data_segment_length = + handoff->ikh_max_recv_data_segment_length = + idl->idl_max_recv_data_segment_length; + + error |= iscsi_keys_add(request_keys, "DefaultTime2Wait", "0", + M_NOWAIT); + error |= iscsi_keys_add(request_keys, "DefaultTime2Retain", "0", + M_NOWAIT); + error |= iscsi_keys_add(request_keys, "ErrorRecoveryLevel", "0", + M_NOWAIT); + if (error != 0) { + ISCSI_SESSION_WARN(is, "failed to add keys into request"); + error = ENOMEM; + goto out; + } + error = iscsi_keys_save(request_keys, request, M_NOWAIT); + if (error != 0) { + ISCSI_SESSION_WARN(is, "failed to save keys to request"); + goto out; + } + iscsi_keys_delete(request_keys); + request_keys = NULL; + + login_send(is, request); + request = NULL; + error = login_receive(is, &response); + if (error != 0) + goto out; + + response_keys = iscsi_keys_new(M_NOWAIT); + iscsi_keys_load(response_keys, response, M_NOWAIT); + for (i = 0; i < ISCSI_KEYS_MAX; i++) { + if (response_keys->ik_names[i] == NULL) + break; + + login_negotiate_key(is, idl, response_keys->ik_names[i], + response_keys->ik_values[i], handoff); + } + + iscsi_keys_delete(response_keys); + response_keys = NULL; + + for (;;) { + bhslr = (struct iscsi_bhs_login_response *)response->ip_bhs; + if ((bhslr->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0) + break; + + nrequests++; + if (nrequests > 5) { + ISCSI_SESSION_WARN(is, "received login response " + "without the \"T\" flag too many times; giving up"); + break; + } + + ISCSI_SESSION_DEBUG(is, "received login response " + "without the \"T\" flag; sending another request"); + + icl_pdu_free(response); + response = NULL; + + request = login_new_request(is, + BHSLR_STAGE_OPERATIONAL_NEGOTIATION); + login_send(is, request); + request = NULL; + error = login_receive(is, &response); + if (error != 0) + goto out; + } + + if (login_nsg(response) != BHSLR_STAGE_FULL_FEATURE_PHASE) { + ISCSI_SESSION_WARN(is, + "received final login response with wrong NSG 0x%x", + login_nsg(response)); + } + icl_pdu_free(response); + response = NULL; + + ISCSI_SESSION_DEBUG(is, "operational parameter negotiation done; " + "transitioning to Full Feature phase"); +out: + if (request_keys != NULL) + iscsi_keys_delete(request_keys); + if (response_keys != NULL) + iscsi_keys_delete(response_keys); + if (request != NULL) + icl_pdu_free(request); + if (response != NULL) + icl_pdu_free(response); + return (error); +} + +static void +login_send_chap_a(struct iscsi_session *is) +{ + struct icl_pdu *request; + struct iscsi_keys *request_keys; + + request = login_new_request(is, BHSLR_STAGE_SECURITY_NEGOTIATION); + request_keys = iscsi_keys_new(M_NOWAIT); + iscsi_keys_add(request_keys, "CHAP_A", "5", M_NOWAIT); + iscsi_keys_save(request_keys, request, M_NOWAIT); + iscsi_keys_delete(request_keys); + login_send(is, request); +} + +static int +login_send_chap_r(struct iscsi_session *is, struct icl_pdu *response) +{ + struct icl_pdu *request; + struct iscsi_keys *request_keys, *response_keys; + struct iscsi_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'. + */ + + request = NULL; + request_keys = NULL; + rchap = NULL; + chap_r = NULL; + mutual_chap_c = mutual_chap_i = NULL; + + response_keys = iscsi_keys_new(M_NOWAIT); + if (response_keys == NULL) + return (ENOMEM); + error = iscsi_keys_load(response_keys, response, M_NOWAIT); + if (error != 0) { + iscsi_keys_delete(response_keys); + return (error); + } + + /* + * First, compute the response. + */ + chap_a = iscsi_keys_find(response_keys, "CHAP_A"); + if (chap_a == NULL) { + ISCSI_SESSION_WARN(is, "received CHAP packet without CHAP_A"); + error = EPROTO; + goto out; + } + chap_c = iscsi_keys_find(response_keys, "CHAP_C"); + if (chap_c == NULL) { + ISCSI_SESSION_WARN(is, "received CHAP packet without CHAP_C"); + error = EPROTO; + goto out; + } + chap_i = iscsi_keys_find(response_keys, "CHAP_I"); + if (chap_i == NULL) { + ISCSI_SESSION_WARN(is, "received CHAP packet without CHAP_I"); + error = EPROTO; + goto out; + } + + if (strcmp(chap_a, "5") != 0) { + ISCSI_SESSION_WARN(is, "received CHAP packet " + "with unsupported CHAP_A \"%s\"", chap_a); + error = EPROTO; + goto out; + } + + rchap = iscsi_rchap_new(is->is_conf.isc_secret); + if (rchap == NULL) { + error = ENOMEM; + goto out; + } + error = iscsi_rchap_receive(rchap, chap_i, chap_c); + if (error != 0) { + ISCSI_SESSION_WARN(is, "received CHAP packet " + "with malformed CHAP_I or CHAP_C"); + error = EPROTO; + goto out; + } + chap_r = iscsi_rchap_get_response(rchap); + iscsi_rchap_delete(rchap); + rchap = NULL; + + iscsi_keys_delete(response_keys); + response_keys = NULL; + + request = login_new_request(is, BHSLR_STAGE_SECURITY_NEGOTIATION); + if (request == NULL) { + ISCSI_SESSION_WARN(is, "login_new_request out of memory"); + error = ENOMEM; + goto out; + } + request_keys = iscsi_keys_new(M_NOWAIT); + if (request_keys == NULL) { + ISCSI_SESSION_WARN(is, "iscsi_keys_new out of memory"); + error = ENOMEM; + goto out; + } + error = iscsi_keys_add(request_keys, "CHAP_N", is->is_conf.isc_user, + M_NOWAIT); + error |= iscsi_keys_add(request_keys, "CHAP_R", chap_r, M_NOWAIT); + if (error != 0) { + ISCSI_SESSION_WARN(is, "iscsi_keys_add out of memory"); + error = ENOMEM; + goto out; + } + free(chap_r, M_ISCSI); + chap_r = NULL; + + /* + * If we want mutual authentication, we're expected to send + * our CHAP_I/CHAP_C now. + */ + if (is->is_conf.isc_mutual_user[0] != '\0') { + ISCSI_SESSION_DEBUG(is, "requesting mutual authentication; " + "binary challenge size is %zd bytes", + sizeof(is->is_boot_login.bl_mutual_chap->chap_challenge)); + + KASSERT(is->is_boot_login.bl_mutual_chap == NULL, + ("%s: is->is_boot_login.bl_mutual_chap non-null\n", + __func__)); + is->is_boot_login.bl_mutual_chap = iscsi_chap_new(); + mutual_chap_i = iscsi_chap_get_id( + is->is_boot_login.bl_mutual_chap); + if (mutual_chap_i == NULL) + goto out; + mutual_chap_c = iscsi_chap_get_challenge( + is->is_boot_login.bl_mutual_chap); + if (mutual_chap_c == NULL) + goto out; + error = iscsi_keys_add(request_keys, "CHAP_I", mutual_chap_i, + M_NOWAIT); + if (error != 0) + goto out; + error = iscsi_keys_add(request_keys, "CHAP_C", mutual_chap_c, + M_NOWAIT); + if (error != 0) + goto out; + free(mutual_chap_i, M_ISCSI); + free(mutual_chap_c, M_ISCSI); + mutual_chap_i = mutual_chap_c = NULL; + } + + iscsi_keys_save(request_keys, request, M_NOWAIT); + iscsi_keys_delete(request_keys); + request_keys = NULL; + login_send(is, request); + request = NULL; + +out: + if (request != NULL) + icl_pdu_free(request); + if (request_keys != NULL) + iscsi_keys_delete(request_keys); + if (response_keys != NULL) + iscsi_keys_delete(response_keys); + if (rchap != NULL) + iscsi_rchap_delete(rchap); + free(chap_r, M_ISCSI); + free(mutual_chap_c, M_ISCSI); + free(mutual_chap_i, M_ISCSI); + return (error); +} + +static int +login_verify_mutual(struct iscsi_session *is, + struct icl_pdu *response) +{ + struct iscsi_keys *response_keys; + const char *chap_n, *chap_r; + int error; + + KASSERT(is->is_boot_login.bl_mutual_chap != NULL, + ("%s: without mutual_chap.", __func__)); + + response_keys = iscsi_keys_new(M_NOWAIT); + if (response_keys == NULL) + return (ENOMEM); + error = iscsi_keys_load(response_keys, response, M_NOWAIT); + if (error != 0) + goto out; + + chap_n = iscsi_keys_find(response_keys, "CHAP_N"); + if (chap_n == NULL) { + ISCSI_SESSION_WARN(is, + "received CHAP Response PDU without CHAP_N"); + error = EPROTO; + goto out; + } + chap_r = iscsi_keys_find(response_keys, "CHAP_R"); + if (chap_r == NULL) { + ISCSI_SESSION_WARN(is, + "received CHAP Response PDU without CHAP_R"); + error = EPROTO; + goto out; + } + + error = iscsi_chap_receive(is->is_boot_login.bl_mutual_chap, chap_r); + if (error != 0) { + ISCSI_SESSION_WARN(is, + "received CHAP Response PDU with invalid CHAP_R"); + error = EPROTO; + goto out; + } + + if (strcmp(chap_n, is->is_conf.isc_mutual_user) != 0) { + ISCSI_SESSION_WARN(is, + "mutual CHAP authentication failed: wrong user"); + error = EAUTH; + goto out; + } + + error = iscsi_chap_authenticate(is->is_boot_login.bl_mutual_chap, + is->is_conf.isc_mutual_secret); + if (error != 0) { + ISCSI_SESSION_WARN(is, + "mutual CHAP authentication failed: wrong secret"); + error = EAUTH; + goto out; + } + ISCSI_SESSION_DEBUG(is, "mutual CHAP authentication succeeded"); + +out: + iscsi_keys_delete(response_keys); + iscsi_chap_delete(is->is_boot_login.bl_mutual_chap); + is->is_boot_login.bl_mutual_chap = NULL; + return (error); +} + +static int +login_chap(struct iscsi_session *is) +{ + struct icl_pdu *response; + int error; + + ISCSI_SESSION_DEBUG(is, "beginning CHAP authentication; " + "sending CHAP_A"); + login_send_chap_a(is); + + ISCSI_SESSION_DEBUG(is, "waiting for CHAP_A/CHAP_C/CHAP_I"); + error = login_receive(is, &response); + if (error != 0) + return (error); + + ISCSI_SESSION_DEBUG(is, "sending CHAP_N/CHAP_R"); + error = login_send_chap_r(is, response); + if (error != 0) + goto out; + icl_pdu_free(response); + response = NULL; + + /* + * XXX: Make sure this is not susceptible to MITM. + */ + + ISCSI_SESSION_DEBUG(is, "waiting for CHAP result"); + error = login_receive(is, &response); + if (error != 0) + goto out; + if (is->is_conf.isc_mutual_user[0] != '\0') { + error = login_verify_mutual(is, response); + if (error != 0) + goto out; + } + icl_pdu_free(response); + response = NULL; + + ISCSI_SESSION_DEBUG(is, "CHAP authentication done"); + +out: + if (response != NULL) + icl_pdu_free(response); + return (error); +} + +int +iscsi_login(struct iscsi_kernel_login *login, struct iscsi_kernel_handoff *handoff) +{ + struct iscsi_session *is; + struct icl_drv_limits *idl; + struct icl_pdu *request, *response; + struct iscsi_keys *request_keys, *response_keys; + struct iscsi_bhs_login_response *bhslr2; + const char *auth_method; + int error; + int i; + + ISCSI_SESSION_LOCK_ASSERT(login->ikl_is); + is = login->ikl_is; + idl = &login->ikl_idl; + response = NULL; + request_keys = response_keys = NULL; + error = 0; + is->is_tsih = handoff->ikh_tsid; + + ISCSI_SESSION_DEBUG(is, "beginning Login phase; sending Login PDU"); + request = login_new_request(is, BHSLR_STAGE_SECURITY_NEGOTIATION); + if (request == NULL) + return (ENOMEM); + request_keys = iscsi_keys_new(M_NOWAIT); + if (request_keys == NULL) { + error = ENOMEM; + goto out; + } + if (is->is_conf.isc_mutual_user[0] != '\0') { + error = iscsi_keys_add(request_keys, "AuthMethod", "CHAP", + M_NOWAIT); + } else if (is->is_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. + */ + error = iscsi_keys_add(request_keys, "AuthMethod", "None,CHAP", + M_NOWAIT); + } else { + error = iscsi_keys_add(request_keys, "AuthMethod", "None", + M_NOWAIT); + } + error |= iscsi_keys_add(request_keys, "InitiatorName", + is->is_conf.isc_initiator, M_NOWAIT); + if (is->is_conf.isc_initiator_alias[0] != '\0') { + error |= iscsi_keys_add(request_keys, "InitiatorAlias", + is->is_conf.isc_initiator_alias, M_NOWAIT); + } + if (is->is_conf.isc_discovery == 0) { + error |= iscsi_keys_add(request_keys, "SessionType", "Normal", M_NOWAIT); + error |= iscsi_keys_add(request_keys, + "TargetName", is->is_conf.isc_target, M_NOWAIT); + } else { + error |= iscsi_keys_add(request_keys, "SessionType", "Discovery", + M_NOWAIT); + } + if (error != 0) { + error = ENOMEM; + goto out; + } + error = iscsi_keys_save(request_keys, request, M_NOWAIT); + if (error != 0) + goto out; + iscsi_keys_delete(request_keys); + request_keys = NULL; + + login_send(is, request); + request = NULL; + error = login_receive(is, &response); + if (error != 0) + goto out; + + response_keys = iscsi_keys_new(M_NOWAIT); + iscsi_keys_load(response_keys, response, M_NOWAIT); + + for (i = 0; i < ISCSI_KEYS_MAX; i++) { + if (response_keys->ik_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->ik_names[i], "AuthMethod") == 0) + continue; + + error = login_negotiate_key(is, idl, response_keys->ik_names[i], + response_keys->ik_values[i], handoff); + if (error != 0) + goto out; + } + + bhslr2 = (struct iscsi_bhs_login_response *)response->ip_bhs; + if ((bhslr2->bhslr_flags & BHSLR_FLAGS_TRANSIT) != 0 && + login_nsg(response) == BHSLR_STAGE_OPERATIONAL_NEGOTIATION) { + if (is->is_conf.isc_mutual_user[0] != '\0') { + ISCSI_SESSION_WARN(is, "target requested transition " + "to operational parameter negotiation, " + "but we require mutual CHAP"); + error = EPROTO; + goto out; + } + + ISCSI_SESSION_DEBUG(is, "target requested transition " + "to operational parameter negotiation"); + iscsi_keys_delete(response_keys); + response_keys = NULL; + icl_pdu_free(response); + response = NULL; + error = login_negotiate(is, idl, handoff); + goto out; + } + + auth_method = iscsi_keys_find(response_keys, "AuthMethod"); + if (auth_method == NULL) { + ISCSI_SESSION_WARN(is, "received response without AuthMethod"); + error = EPROTO; + goto out; + } + if (strcmp(auth_method, "None") == 0) { + if (is->is_conf.isc_mutual_user[0] != '\0') { + ISCSI_SESSION_WARN(is, + "target does not require authantication, " + "but we require mutual CHAP"); + error = EAUTH; + goto out; + } + + ISCSI_SESSION_DEBUG(is, + "target does not require authentication"); + iscsi_keys_delete(response_keys); + response_keys = NULL; + icl_pdu_free(response); + error = login_negotiate(is, idl, handoff); + goto out; + } + + if (strcmp(auth_method, "CHAP") != 0) { + ISCSI_SESSION_WARN(is, + "received response with unsupported AuthMethod \"%s\"", + auth_method); + error = EAUTH; + goto out; + } + + if (is->is_conf.isc_user[0] == '\0' || + is->is_conf.isc_secret[0] == '\0') { + ISCSI_SESSION_WARN(is, + "target requests CHAP authentication, but we don't " + "have user and secret"); + error = EAUTH; + goto out; + } + + iscsi_keys_delete(response_keys); + response_keys = NULL; + icl_pdu_free(response); + response = NULL; + + error = login_chap(is); + if (error != 0) + goto out; + error = login_negotiate(is, idl, handoff); + +out: + if (request != NULL) + icl_pdu_free(request); + if (response != NULL) + icl_pdu_free(response); + if (request_keys != NULL) + iscsi_keys_delete(request_keys); + if (response_keys != NULL) + iscsi_keys_delete(response_keys); + return (error); +} Index: sys/modules/iscsi/Makefile =================================================================== --- sys/modules/iscsi/Makefile +++ sys/modules/iscsi/Makefile @@ -15,10 +15,15 @@ SRCS+= device_if.h SRCS+= icl_conn_if.c SRCS+= icl_conn_if.h +SRCS+= iscsi_login.c +SRCS+= iscsi_keys.c +SRCS+= iscsi_chap.c +SRCS+= iscsi_base64.c +SRCS+= iscsi_ibft.c + +SRCS+= opt_acpi.h -.if ${MK_OFED} != "no" || defined(ALL_MODULES) CFLAGS+=-DICL_KERNEL_PROXY -.endif MFILES= kern/bus_if.m kern/device_if.m dev/iscsi/icl_conn_if.m