/* * Common code for setting up discovery and normal sessions. * * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman * Copyright (C) 2006 - 2009 Mike Christie * Copyright (C) 2006 - 2009 Red Hat, Inc. All rights reserved. * maintained by open-iscsi@googlegroups.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * See the file COPYING included with this distribution for more details. */ #include #include #include #include #include #include #include "initiator.h" #include "transport.h" #include "iscsid.h" #include "iscsi_ipc.h" #include "log.h" #include "iscsi_sysfs.h" #include "iscsi_settings.h" #include "iface.h" #include "host.h" #include "sysdeps.h" #include "iscsi_err.h" #include "iscsi_net_util.h" struct iscsi_session *session_find_by_sid(uint32_t sid) { struct iscsi_transport *t; struct iscsi_session *session; list_for_each_entry(t, &transports, list) { list_for_each_entry(session, &t->sessions, list) { if (session->id == sid) return session; } } return NULL; } const static unsigned int align_32_down(unsigned int param) { return param & ~0x3; } int iscsi_setup_authentication(struct iscsi_session *session, struct iscsi_auth_config *auth_cfg) { /* if we have any incoming credentials, we insist on authenticating * the target or not logging in at all */ if (auth_cfg->username_in[0] || auth_cfg->password_in_length) { /* sanity check the config */ if (auth_cfg->password_length == 0) { log_warning("CHAP configuration has incoming " "authentication credentials but has no " "outgoing credentials configured."); return EINVAL; } session->bidirectional_auth = 1; } else { /* no or 1-way authentication */ session->bidirectional_auth = 0; } /* copy in whatever credentials we have */ strlcpy(session->username, auth_cfg->username, sizeof (session->username)); session->username[sizeof (session->username) - 1] = '\0'; if ((session->password_length = auth_cfg->password_length)) memcpy(session->password, auth_cfg->password, session->password_length); strlcpy(session->username_in, auth_cfg->username_in, sizeof (session->username_in)); session->username_in[sizeof (session->username_in) - 1] = '\0'; if ((session->password_in_length = auth_cfg->password_in_length)) memcpy(session->password_in, auth_cfg->password_in, session->password_in_length); if (session->password_length || session->password_in_length) { /* setup the auth buffers */ session->auth_buffers[0].address = &session->auth_client_block; session->auth_buffers[0].length = sizeof (session->auth_client_block); session->auth_buffers[1].address = &session->auth_recv_string_block; session->auth_buffers[1].length = sizeof (session->auth_recv_string_block); session->auth_buffers[2].address = &session->auth_send_string_block; session->auth_buffers[2].length = sizeof (session->auth_send_string_block); session->auth_buffers[3].address = &session->auth_recv_binary_block; session->auth_buffers[3].length = sizeof (session->auth_recv_binary_block); session->auth_buffers[4].address = &session->auth_send_binary_block; session->auth_buffers[4].length = sizeof (session->auth_send_binary_block); session->num_auth_buffers = 5; log_debug(6, "authentication setup complete..."); } else { session->num_auth_buffers = 0; log_debug(6, "no authentication configured..."); } return 0; } void iscsi_copy_operational_params(struct iscsi_conn *conn, struct iscsi_session_operational_config *session_conf, struct iscsi_conn_operational_config *conn_conf) { struct iscsi_session *session = conn->session; struct iscsi_transport *t = session->t; conn->hdrdgst_en = conn_conf->HeaderDigest; conn->datadgst_en = conn_conf->DataDigest; conn->max_recv_dlength = align_32_down(conn_conf->MaxRecvDataSegmentLength); if (conn->max_recv_dlength < ISCSI_MIN_MAX_RECV_SEG_LEN || conn->max_recv_dlength > ISCSI_MAX_MAX_RECV_SEG_LEN) { log_error("Invalid iscsi.MaxRecvDataSegmentLength. Must be " "within %u and %u. Setting to %u", ISCSI_MIN_MAX_RECV_SEG_LEN, ISCSI_MAX_MAX_RECV_SEG_LEN, DEF_INI_MAX_RECV_SEG_LEN); conn_conf->MaxRecvDataSegmentLength = DEF_INI_MAX_RECV_SEG_LEN; conn->max_recv_dlength = DEF_INI_MAX_RECV_SEG_LEN; } /* zero indicates to use the target's value */ conn->max_xmit_dlength = align_32_down(conn_conf->MaxXmitDataSegmentLength); if (conn->max_xmit_dlength == 0) conn->max_xmit_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; if (conn->max_xmit_dlength < ISCSI_MIN_MAX_RECV_SEG_LEN || conn->max_xmit_dlength > ISCSI_MAX_MAX_RECV_SEG_LEN) { log_error("Invalid iscsi.MaxXmitDataSegmentLength. Must be " "within %u and %u. Setting to %u", ISCSI_MIN_MAX_RECV_SEG_LEN, ISCSI_MAX_MAX_RECV_SEG_LEN, DEF_INI_MAX_RECV_SEG_LEN); conn_conf->MaxXmitDataSegmentLength = DEF_INI_MAX_RECV_SEG_LEN; conn->max_xmit_dlength = DEF_INI_MAX_RECV_SEG_LEN; } /* session's operational parameters */ session->initial_r2t_en = session_conf->InitialR2T; session->max_r2t = session_conf->MaxOutstandingR2T; session->imm_data_en = session_conf->ImmediateData; session->first_burst = align_32_down(session_conf->FirstBurstLength); /* * some targets like netapp fail the login if sent bad first_burst * and max_burst lens, even when immediate data=no and * initial r2t = Yes, so we always check the user values. */ if (session->first_burst < ISCSI_MIN_FIRST_BURST_LEN || session->first_burst > ISCSI_MAX_FIRST_BURST_LEN) { log_error("Invalid iscsi.FirstBurstLength of %u. Must be " "within %u and %u. Setting to %u", session->first_burst, ISCSI_MIN_FIRST_BURST_LEN, ISCSI_MAX_FIRST_BURST_LEN, DEF_INI_FIRST_BURST_LEN); session_conf->FirstBurstLength = DEF_INI_FIRST_BURST_LEN; session->first_burst = DEF_INI_FIRST_BURST_LEN; } session->max_burst = align_32_down(session_conf->MaxBurstLength); if (session->max_burst < ISCSI_MIN_MAX_BURST_LEN || session->max_burst > ISCSI_MAX_MAX_BURST_LEN) { log_error("Invalid iscsi.MaxBurstLength of %u. Must be " "within %u and %u. Setting to %u", session->max_burst, ISCSI_MIN_MAX_BURST_LEN, ISCSI_MAX_MAX_BURST_LEN, DEF_INI_MAX_BURST_LEN); session_conf->MaxBurstLength = DEF_INI_MAX_BURST_LEN; session->max_burst = DEF_INI_MAX_BURST_LEN; } if (session->first_burst > session->max_burst) { log_error("Invalid iscsi.FirstBurstLength of %u. Must be " "less than iscsi.MaxBurstLength. Setting to %u", session->first_burst, session->max_burst); session_conf->FirstBurstLength = session->max_burst; session->first_burst = session->max_burst; } session->def_time2wait = session_conf->DefaultTime2Wait; session->def_time2retain = session_conf->DefaultTime2Retain; session->erl = session_conf->ERL; if (session->type == ISCSI_SESSION_TYPE_DISCOVERY) { /* * Right now, we only support 8K max for kernel based * sendtargets discovery, because the recv pdu buffers are * limited to this size. */ if ((t->caps & CAP_TEXT_NEGO) && conn->max_recv_dlength > ISCSI_DEF_MAX_RECV_SEG_LEN) conn->max_recv_dlength = ISCSI_DEF_MAX_RECV_SEG_LEN; /* We do not support discovery sessions with digests */ conn->hdrdgst_en = ISCSI_DIGEST_NONE; conn->datadgst_en = ISCSI_DIGEST_NONE; } if (t->template->create_conn) t->template->create_conn(conn); } int iscsi_setup_portal(struct iscsi_conn *conn, char *address, int port) { char serv[NI_MAXSERV]; sprintf(serv, "%d", port); if (resolve_address(address, serv, &conn->saddr)) { log_error("cannot resolve host name %s", address); return ISCSI_ERR_TRANS; } conn->failback_saddr = conn->saddr; getnameinfo((struct sockaddr *)&conn->saddr, sizeof(conn->saddr), conn->host, sizeof(conn->host), NULL, 0, NI_NUMERICHOST); log_debug(4, "resolved %s to %s", address, conn->host); return 0; } static int host_set_param(struct iscsi_transport *t, uint32_t host_no, int param, char *value, int type) { int rc; rc = ipc->set_host_param(t->handle, host_no, param, value, type); /* 2.6.20 and below returns EINVAL */ if (rc && rc != -ENOSYS && rc != -EINVAL) { log_error("can't set operational parameter %d for " "host %d, retcode %d (%d)", param, host_no, rc, errno); return ISCSI_ERR_INVAL; } return 0; } static void print_param_value(enum iscsi_param param, void *value, int type) { log_debug(3, "set operational parameter %d to:", param); if (type == ISCSI_STRING) log_debug(3, "%s", value ? (char *)value : "NULL"); else log_debug(3, "%u", *(uint32_t *)value); } #define MAX_HOST_PARAMS 2 int iscsi_host_set_params(struct iscsi_session *session) { struct iscsi_transport *t = session->t; int i; struct hostparam { int param; int type; void *value; } hosttbl[MAX_HOST_PARAMS] = { { .param = ISCSI_HOST_PARAM_NETDEV_NAME, .value = session->nrec.iface.netdev, .type = ISCSI_STRING, }, { .param = ISCSI_HOST_PARAM_HWADDRESS, .value = session->nrec.iface.hwaddress, .type = ISCSI_STRING, }, }; for (i = 0; i < MAX_HOST_PARAMS; i++) { if (host_set_param(t, session->hostno, hosttbl[i].param, hosttbl[i].value, hosttbl[i].type)) { return EPERM; } print_param_value(hosttbl[i].param, hosttbl[i].value, hosttbl[i].type); } return 0; } static inline void iscsi_session_clear_param(struct iscsi_session *session, int param) { session->param_mask &= ~(1ULL << param); } void iscsi_session_init_params(struct iscsi_session *session) { session->param_mask = ~0ULL; if (!(session->t->caps & CAP_MULTI_R2T)) iscsi_session_clear_param(session, ISCSI_PARAM_MAX_R2T); if (!(session->t->caps & CAP_HDRDGST)) iscsi_session_clear_param(session, ISCSI_PARAM_HDRDGST_EN); if (!(session->t->caps & CAP_DATADGST)) iscsi_session_clear_param(session, ISCSI_PARAM_DATADGST_EN); if (!(session->t->caps & CAP_MARKERS)) { iscsi_session_clear_param(session, ISCSI_PARAM_IFMARKER_EN); iscsi_session_clear_param(session, ISCSI_PARAM_OFMARKER_EN); } } #define MAX_SESSION_NEG_PARAMS 16 int iscsi_session_set_neg_params(struct iscsi_conn *conn) { struct iscsi_session *session = conn->session; int i, rc; uint32_t zero = 0; struct connparam { int param; int type; void *value; int conn_only; } conntbl[MAX_SESSION_NEG_PARAMS] = { { .param = ISCSI_PARAM_MAX_RECV_DLENGTH, .value = &conn->max_recv_dlength, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_MAX_XMIT_DLENGTH, .value = &conn->max_xmit_dlength, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_HDRDGST_EN, .value = &conn->hdrdgst_en, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_DATADGST_EN, .value = &conn->datadgst_en, .type = ISCSI_INT, .conn_only = 1, }, { .param = ISCSI_PARAM_INITIAL_R2T_EN, .value = &session->initial_r2t_en, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_MAX_R2T, .value = &session->max_r2t, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_IMM_DATA_EN, .value = &session->imm_data_en, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_FIRST_BURST, .value = &session->first_burst, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_MAX_BURST, .value = &session->max_burst, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_PDU_INORDER_EN, .value = &session->pdu_inorder_en, .type = ISCSI_INT, .conn_only = 0, }, { .param =ISCSI_PARAM_DATASEQ_INORDER_EN, .value = &session->dataseq_inorder_en, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_ERL, .value = &zero, /* FIXME: session->erl */ .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_IFMARKER_EN, .value = &zero,/* FIXME: session->ifmarker_en */ .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_OFMARKER_EN, .value = &zero,/* FIXME: session->ofmarker_en */ .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_EXP_STATSN, .value = &conn->exp_statsn, .type = ISCSI_UINT, .conn_only = 1, }, { .param = ISCSI_PARAM_TPGT, .value = &session->portal_group_tag, .type = ISCSI_INT, .conn_only = 0, }, }; iscsi_session_init_params(session); /* Entered full-feature phase! */ for (i = 0; i < MAX_SESSION_NEG_PARAMS; i++) { if (conn->id != 0 && !conntbl[i].conn_only) continue; if (!(session->param_mask & (1ULL << conntbl[i].param))) continue; rc = ipc->set_param(session->t->handle, session->id, conn->id, conntbl[i].param, conntbl[i].value, conntbl[i].type); if (rc && rc != -ENOSYS) { log_error("can't set operational parameter %d for " "connection %d:%d, retcode %d (%d)", conntbl[i].param, session->id, conn->id, rc, errno); return EPERM; } print_param_value(conntbl[i].param, conntbl[i].value, conntbl[i].type); } return 0; } #define MAX_SESSION_PARAMS 20 int iscsi_session_set_params(struct iscsi_conn *conn) { struct iscsi_session *session = conn->session; int i, rc; struct connparam { int param; int type; void *value; int conn_only; } conntbl[MAX_SESSION_PARAMS] = { { .param = ISCSI_PARAM_TARGET_NAME, .conn_only = 0, .type = ISCSI_STRING, .value = session->target_name, }, { .param = ISCSI_PARAM_PERSISTENT_ADDRESS, .value = session->nrec.conn[conn->id].address, .type = ISCSI_STRING, .conn_only = 1, }, { .param = ISCSI_PARAM_PERSISTENT_PORT, .value = &session->nrec.conn[conn->id].port, .type = ISCSI_INT, .conn_only = 1, }, { .param = ISCSI_PARAM_SESS_RECOVERY_TMO, .value = &session->replacement_timeout, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_USERNAME, .value = session->username, .type = ISCSI_STRING, .conn_only = 0, }, { .param = ISCSI_PARAM_USERNAME_IN, .value = session->username_in, .type = ISCSI_STRING, .conn_only = 0, }, { .param = ISCSI_PARAM_PASSWORD, .value = session->password, .type = ISCSI_STRING, .conn_only = 0, }, { .param = ISCSI_PARAM_PASSWORD_IN, .value = session->password_in, .type = ISCSI_STRING, .conn_only = 0, }, { .param = ISCSI_PARAM_FAST_ABORT, .value = &session->fast_abort, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_ABORT_TMO, .value = &session->abort_timeout, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_LU_RESET_TMO, .value = &session->lu_reset_timeout, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_TGT_RESET_TMO, .value = &session->tgt_reset_timeout, .type = ISCSI_INT, .conn_only = 0, }, { .param = ISCSI_PARAM_PING_TMO, .value = &conn->noop_out_timeout, .type = ISCSI_INT, .conn_only = 1, }, { .param = ISCSI_PARAM_RECV_TMO, .value = &conn->noop_out_interval, .type = ISCSI_INT, .conn_only = 1, }, { .param = ISCSI_PARAM_IFACE_NAME, .value = session->nrec.iface.name, .type = ISCSI_STRING, .conn_only = 0, }, { .param = ISCSI_PARAM_INITIATOR_NAME, .value = session->initiator_name, .type = ISCSI_STRING, .conn_only = 0, }, { .param = ISCSI_PARAM_BOOT_ROOT, .value = session->nrec.session.boot_root, .type = ISCSI_STRING, .conn_only = 0, }, { .param = ISCSI_PARAM_BOOT_NIC, .value = session->nrec.session.boot_nic, .type = ISCSI_STRING, .conn_only = 0, }, { .param = ISCSI_PARAM_BOOT_TARGET, .value = session->nrec.session.boot_target, .type = ISCSI_STRING, .conn_only = 0, }, { .param = ISCSI_PARAM_DISCOVERY_SESS, .value = &session->type, .type = ISCSI_INT, .conn_only = 0, }, }; iscsi_session_init_params(session); /* some llds will send nops internally */ if (!iscsi_sysfs_session_supports_nop(session->id)) { iscsi_session_clear_param(session, ISCSI_PARAM_PING_TMO); iscsi_session_clear_param(session, ISCSI_PARAM_RECV_TMO); } /* Entered full-feature phase! */ for (i = 0; i < MAX_SESSION_PARAMS; i++) { if (conn->id != 0 && !conntbl[i].conn_only) continue; if (!(session->param_mask & (1ULL << conntbl[i].param))) continue; rc = ipc->set_param(session->t->handle, session->id, conn->id, conntbl[i].param, conntbl[i].value, conntbl[i].type); if (rc && rc != -ENOSYS) { log_error("can't set operational parameter %d for " "connection %d:%d, retcode %d (%d)", conntbl[i].param, session->id, conn->id, rc, errno); return EPERM; } if (rc == -ENOSYS) { switch (conntbl[i].param) { case ISCSI_PARAM_PING_TMO: /* * older kernels may not support nops * in kernel */ conn->userspace_nop = 1; break; #if 0 TODO handle this case ISCSI_PARAM_INITIATOR_NAME: /* use host level one instead */ hosttbl[ISCSI_HOST_PARAM_INITIATOR_NAME].set = 1; break; #endif } } print_param_value(conntbl[i].param, conntbl[i].value, conntbl[i].type); } return 0; } int iscsi_set_net_config(struct iscsi_transport *t, iscsi_session_t *session, struct iface_rec *iface) { if (t->template->set_net_config) { /* uip needs the netdev name */ struct host_info hinfo; int hostno, rc; /* this assumes that the netdev or hw address is going to be set */ hostno = iscsi_sysfs_get_host_no_from_hwinfo(iface, &rc); if (rc) { log_debug(4, "Couldn't get host no."); return rc; } /* uip needs the netdev name */ if (!strlen(iface->netdev)) { memset(&hinfo, 0, sizeof(hinfo)); hinfo.host_no = hostno; iscsi_sysfs_get_hostinfo_by_host_no(&hinfo); strcpy(iface->netdev, hinfo.iface.netdev); } return t->template->set_net_config(t, iface, session); } return 0; } int iscsi_host_set_net_params(struct iface_rec *iface, struct iscsi_session *session) { struct iscsi_transport *t = session->t; int rc = 0; char *netdev; struct host_info hinfo; log_debug(3, "setting iface %s, dev %s, set ip %s, hw %s, " "transport %s.", iface->name, iface->netdev, iface->ipaddress, iface->hwaddress, iface->transport_name); if (!t->template->set_host_ip) return 0; /* if we need to set the ip addr then set all the iface net settings */ if (!iface_is_bound_by_ipaddr(iface)) { if (t->template->set_host_ip == SET_HOST_IP_REQ) { log_warning("Please set the iface.ipaddress for iface " "%s, then retry the login command.", iface->name); return ISCSI_ERR_INVAL; } else if (t->template->set_host_ip == SET_HOST_IP_OPT) { log_info("Optional iface.ipaddress for iface %s " "not set.", iface->name); return 0; } else { return ISCSI_ERR_INVAL; } } /* these type of drivers need the netdev upd */ if (strlen(iface->netdev)) netdev = iface->netdev; else { memset(&hinfo, 0, sizeof(hinfo)); hinfo.host_no = session->hostno; iscsi_sysfs_get_hostinfo_by_host_no(&hinfo); netdev = hinfo.iface.netdev; } if (!t->template->no_netdev && net_ifup_netdev(netdev)) log_warning("Could not brining up netdev %s. Try running " "'ifup %s' first if login fails.", netdev, netdev); rc = iscsi_set_net_config(t, session, iface); if (rc != 0) return rc; rc = host_set_param(t, session->hostno, ISCSI_HOST_PARAM_IPADDRESS, iface->ipaddress, ISCSI_STRING); if (rc) return rc; if (iface_is_bound_by_netdev(iface)) { rc = host_set_param(t, session->hostno, ISCSI_HOST_PARAM_NETDEV_NAME, iface->netdev, ISCSI_STRING); if (rc) return rc; } if (iface_is_bound_by_hwaddr(iface)) { rc = host_set_param(t, session->hostno, ISCSI_HOST_PARAM_HWADDRESS, iface->hwaddress, ISCSI_STRING); if (rc) return rc; } return 0; }